Helm can query Kubernetes to find existing resources and use that information in your templates.
Here’s a simple example: Suppose you have a ConfigMap named my-config in the default namespace and you want to use a specific key from it, app.properties, in your Deployment’s environment variables.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: nginx:latest
env:
- name: APP_CONFIG
valueFrom:
configMapKeyRef:
name: my-config
key: app.properties
This is standard Kubernetes. But what if you don’t know the ConfigMap name or the key beforehand? What if it’s defined in a different Helm release, or even outside of Helm? This is where Helm’s lookup capabilities shine.
Helm allows you to access information about any Kubernetes resource that is accessible to your kubectl context, regardless of whether it was deployed by Helm or not. This is achieved using the lookup function.
The lookup function takes three arguments:
apiVersion: The API version of the resource (e.g., "v1", "apps/v1").kind: The kind of the resource (e.g., "ConfigMap", "Service", "Secret").name: The name of the resource.namespace(optional): The namespace of the resource. If omitted, it defaults to the release’s namespace.
Let’s rewrite the Deployment example to dynamically fetch the app.properties value from a ConfigMap named my-app-config in the default namespace.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: nginx:latest
env:
- name: APP_CONFIG
value: {{ lookup "v1" "ConfigMap" "my-app-config" "default" | fromJson | get "data.app.properties" }}
Here’s what’s happening:
lookup "v1" "ConfigMap" "my-app-config" "default": This queries the Kubernetes API for aConfigMapnamedmy-app-configin thedefaultnamespace. If found, it returns a JSON representation of thatConfigMapobject.| fromJson: This Helm template function parses the JSON string returned bylookupinto a Go map.| get "data.app.properties": This is a chain ofgetcalls. It first gets thedatafield from the map (which is itself a map containing theConfigMap’s data), and then it gets theapp.propertieskey from that inner map.
This approach is incredibly powerful because it decouples your Helm charts from needing to know exactly how dependent resources are provisioned. You can reference existing Secrets for database passwords, existing Services for internal endpoints, or even ConfigMaps that are managed by a completely different process or Helm release.
Consider a scenario where you’re deploying an application that needs to connect to a database. The database might already exist, provisioned by a separate Helm chart or a database-as-a-service offering. You can use lookup to get the database service’s cluster IP and port.
Let’s say an existing Service named my-db-service in the database namespace exposes a TCP port 5432.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: my-app-image:latest
env:
- name: DB_HOST
value: {{ lookup "v1" "Service" "my-db-service" "database" | fromJson | get "spec.clusterIP" }}
- name: DB_PORT
value: {{ lookup "v1" "Service" "my-db-service" "database" | fromJson | get "spec.ports[0].port" }}
Notice how we’re accessing spec.ports[0].port. When you query a Service object, spec.ports is an array. We’re assuming the first port defined in the service is the one we want. You might need more sophisticated logic (e.g., iterating or filtering ports by name or targetPort) if your service has multiple ports.
The lookup function can also be used to check for the existence of resources and conditionally render parts of your template. For instance, you might want to create a Secret only if one doesn’t already exist with a specific name.
{{/* templates/my-secret.yaml */}}
{{- if not (lookup "v1" "Secret" .Release.Namespace "my-app-existing-secret") -}}
apiVersion: v1
kind: Secret
metadata:
name: my-app-existing-secret
namespace: {{ .Release.Namespace }}
type: Opaque
data:
password: {{ "my-default-password" | b64enc }}
{{- end -}}
In this example, my-secret.yaml will only be rendered if a Secret named my-app-existing-secret does not already exist in the release’s namespace. If it exists, lookup will return its JSON representation, the not operator will evaluate to false, and the Secret definition will be skipped. This is useful for providing default configurations while allowing users to override them by pre-creating resources.
One critical aspect of lookup is its behavior during helm install vs. helm upgrade. During helm install, lookup will fail if the resource does not exist. However, during helm upgrade, lookup will not fail if the resource doesn’t exist; it will simply return nil. This difference is important for managing the lifecycle of your resources and avoiding unexpected errors during initial deployments.
The lookup function is a powerful tool for building more flexible and robust Helm charts that can adapt to existing infrastructure. It allows you to integrate with resources managed outside of Helm and to create charts that are less opinionated about the exact state of your Kubernetes cluster.
The next logical step after mastering lookup is understanding how to use include and template to modularize your Helm chart logic, especially when dealing with complex dependencies or conditional resource creation based on lookups.