December 16, 2022
Nicholas Morey
Argo CD Build Environment Examples
In many cases, the location or name of an application's deployment will influence its configuration. Consider that an app will deploy to multiple environments with slight differences (e.g., the same Helm chart deployed with different values files).
Depending on the tool, the location or name of the application will need to be duplicated throughout the configuration. This is bad practice because this configuration value could be updated in one location but forgotten in another. Instead, configurations should be kept DRY (“don't repeat yourself”). A single source of truth prevents consistency errors.
Argo CD is a popular tool for implementing GitOps principles for application deployment and lifecycle management in Kubernetes. If you are new to Argo CD, check out our “Argo 101” post.
With Argo CD Applications, I can reuse some of the values from the specification in the configuration by taking advantage of the variables provided in the Build Environment.
I'll illustrate three scenarios where I have used this:
- Using the Application Name in an Ingress.
- Selecting a Helm values file based on the Application namespace.
- Setting the
targetRevision
,repoURL
, anddestination.name
in an App of Apps.
Then I'll briefly touch on the next evolution of templating Applications, ApplicationSets.
Use-case 1: Using the Application Name in an Ingress
When using Helm, I have access to the {{ .Release.name }}
in the Templates, but what if I want to use the release name in a value of the Helm chart? I can't set a value to a variable, so I must set it as a parameter. This means duplicating the information.
helm install my-release ./my-chart -p ingress.host=my-release.my-domain.com
If I maintain the chart, it's easy to update the manifests in templates/
to use the {{ .Release.name }}
. However, it's not that simple if it's a third-party Helm chart. I may get lucky that the chart is in an open-source repo where I can submit a pull request to suggest an update to get the release name added to the manifests. But it may take weeks to get the attention of the maintainers to get a review or approval, if ever. This is where I can take advantage of the Build Environment variables in Argo CD Applications.
Let's say that my cluster has an instance of Grafana, and the ingress includes the application name in the FQDN (Full Qualified Domain Name) to differentiate it under the cluster domain. With the Build Environment, I can use the $ARGOCD_APP_NAME
variable in the ingress.hostname
parameter of the Helm chart.
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: grafana namespace: argocd spec: destination: name: in-cluster namespace: front-end project: default source: repoURL: https://charts.bitnami.com/bitnami targetRevision: 8.2.20 chart: grafana helm: parameters: - name: 'ingress.hostname' value: $ARGOCD_APP_NAME.my-cluster.company.com
In this example, the ingress for the application would be grafana.my-cluster.company.com
(note the grafana
subdomain which is the Application name).
Use-case 2: Selecting a Helm Values file based on the Application Namespace
A typical pattern when using Helm charts is to have separate values files for different instances. For example, I will have the typical values.yaml
file, which contains the defaults for the chart. Then the values-dev.yaml
and values-prod.yaml
files contain the slight differences required to deploy them into those environments (dev and prod).
templates/ Chart.yaml values.yaml # default values values-dev.yaml # application instance values values-prod.yaml
Following this example, the namespaces in the cluster correspond to the suffix used on the values files (i.e., values-dev.yaml
is for the dev
namespace). The Build Environment provides the Application's namespace as a variable ($ARGOCD_APP_NAMESPACE
). This allows the Application to dynamically set it for the source.helm.valueFiles
parameter.
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: springboot namespace: argocd spec: destination: name: in-cluster namespace: dev project: default source: repoURL: https://github.com/morey-tech/argocd-example-apps.git targetRevision: main path: helm-springboot helm: valueFiles: - “values-$ARGOCD_APP_NAMESPACE.yaml”
Use-case 3: Setting the targetRevision
and repoURL
in an App of Apps
This is personally my favourite use case for the Build Environment. When I first tried it out, it was very satisfying to deploy. The scenario is based on a Helm chart for an App of Apps with Application templates where the chart's values are used to populate the targetRevision
, repoURL
, and destination.name
of the spec.
templates/ applications.yaml Chart.yaml values.yaml
# templates/applications.yaml {{- range .Values.applications }} --- apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: {{ . }} namespace: {{ $.Values.namespace }} # finalizers: # - resources-finalizer.argocd.argoproj.io spec: destination: namespace: {{ . }} name: {{ $.Values.spec.destination.name }} project: default source: path: {{ . }} repoURL: {{ $.Values.spec.source.repoURL }} targetRevision: {{ $.Values.spec.source.targetRevision }} syncPolicy: automated: {} syncOptions: - CreateNamespace=true {{- end }}
In the parent Application of the App of Apps, I can use the Build Environment variables to populate the parameters of the Helm chart.
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: production namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: destination: name: in-cluster project: default source: path: apps repoURL: https://github.com/morey-tech/argocd-example-apps targetRevision: main helm: parameters: - name: spec.destination.name value: $ARGOCD_APP_NAME - name: spec.source.repoURL value: $ARGOCD_APP_SOURCE_REPO_URL - name: spec.source.targetRevision value: $ARGOCD_APP_SOURCE_TARGET_REVISION
Here, the Helm chart will template Application manifests and, using the Build Environment, populate the Helm values corresponding to the destination name, repo URL, and target revision of the child Applications.
I find this interesting because if I want to deploy the same set of applications to a different environment, I can simply deploy another instance of that App of Apps application and change the app name to that new cluster destination name. That way, I have a parent app named after each application destination cluster, and all child applications automatically follow.
Also, suppose I want to change the target revision for all applications in an environment. In that case, I can change the target revision of the parent application, which means I can set what an environment is tracking for my GitOps repo in one place.
ApplicationSets
Of course, I should mention that I can also accomplish this with ApplicationSets. They provide a much more powerful set of templating functionality that can render entire Application specs using a variety of generators. For example, an arbitrary list of cluster names that correspond to a path in the source and cluster URLs for the destination data.
apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: guestbook spec: generators: - list: elements: - cluster: engineering-dev url: https://1.2.3.4 - cluster: engineering-prod url: https://2.4.6.8 - cluster: finance-preprod url: https://9.8.7.6 template: metadata: name: '{{cluster}}-guestbook' spec: project: my-project source: repoURL: https://github.com/infra-team/cluster-deployments.git targetRevision: HEAD path: guestbook/{{cluster}} destination: server: '{{url}}' namespace: guestbook
Conclusion
Whether it's wanting to include the application name in an FQDN, or dynamically set values file for my Helm chart based on the name, or wanting to control an app of apps based on application metadata, the Build Environment can help solve all of these problems.
I hope you find this blog post interesting. If you want to learn more about Argo CD, please get in touch with me (Nicholas Morey) on the CNCF Slack. You can find me in the #argo-*
channels, and don't hesitate to send me a direct message.