--- title: Using GitOps with Linkerd with Argo CD description: Use Argo CD to manage Linkerd installation and upgrade lifecycle. --- GitOps is an approach to automate the management and delivery of your Kubernetes infrastructure and applications using Git as a single source of truth. It usually utilizes some software agents to detect and reconcile any divergence between version-controlled artifacts in Git with what's running in a cluster. This guide will show you how to set up [Argo CD](https://argoproj.github.io/argo-cd/) to manage the installation and upgrade of Linkerd using a GitOps workflow. {{< docs/production-note >}} Specifically, this guide provides instructions on how to securely generate and manage Linkerd's mTLS private keys and certificates using [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) and [cert-manager](https://cert-manager.io). It will also show you how to integrate the [auto proxy injection](../features/proxy-injection/) feature into your workflow. Finally, this guide conclude with steps to upgrade Linkerd to a newer version following a GitOps workflow. ![Linkerd GitOps workflow](/docs/images/gitops/architecture.png "Linkerd GitOps workflow") The software and tools used in this guide are selected for demonstration purposes only. Feel free to choose others that are most suited for your requirements. You will need to clone this [example repository](https://github.com/linkerd/linkerd-examples) to your local machine and replicate it in your Kubernetes cluster following the steps defined in the next section. This guide uses the [step cli](https://smallstep.com/cli/) to create certificates used by the Linkerd clusters to enforce mTLS, so make sure you have installed step for your environment. ## Set up the repositories Clone the example repository to your local machine: ```sh git clone https://github.com/linkerd/linkerd-examples.git ``` This repository will be used to demonstrate Git operations like `add`, `commit` and `push` later in this guide. Add a new remote endpoint to the repository to point to the in-cluster Git server, which will be set up in the next section: ```sh cd linkerd-examples git remote add git-server git://localhost/linkerd-examples.git ``` {{< note >}} To simplify the steps in this guide, we will be interacting with the in-cluster Git server via port-forwarding. Hence, the remote endpoint that we just created targets your localhost. {{< /note >}} Deploy the Git server to the `scm` namespace in your cluster: ```sh kubectl apply -f gitops/resources/git-server.yaml ``` Later in this guide, Argo CD will be configured to watch the repositories hosted by this Git server. {{< note >}} This Git server is configured to run as a [daemon](https://git-scm.com/book/en/v2/Git-on-the-Server-Git-Daemon) over the `git` protocol, with unauthenticated access to the Git data. This setup is not recommended for production use. {{< /note >}} Confirm that the Git server is healthy: ```sh kubectl -n scm rollout status deploy/git-server ``` Clone the example repository to your in-cluster Git server: ```sh git_server=`kubectl -n scm get po -l app=git-server -oname | awk -F/ '{ print $2 }'` kubectl -n scm exec "${git_server}" -- \ git clone --bare https://github.com/linkerd/linkerd-examples.git ``` Confirm that the remote repository is successfully cloned: ```sh kubectl -n scm exec "${git_server}" -- ls -al /git/linkerd-examples.git ``` Confirm that you can push from the local repository to the remote repository via port-forwarding: ```sh kubectl -n scm port-forward "${git_server}" 9418 & git push git-server master ``` ## Install the Argo CD CLI Before proceeding, install the Argo CD CLI in your local machine by following the [instructions](https://argo-cd.readthedocs.io/en/stable/cli_installation/) relevant to your OS. ## Deploy Argo CD Install Argo CD: ```sh kubectl create ns argocd kubectl -n argocd apply -f \ https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml ``` Confirm that all the pods are ready: ```sh for deploy in "dex-server" "redis" "repo-server" "server"; \ do kubectl -n argocd rollout status deploy/argocd-${deploy}; \ done kubectl -n argocd rollout status statefulset/argocd-application-controller ``` Use port-forward to access the Argo CD dashboard: ```sh kubectl -n argocd port-forward svc/argocd-server 8080:443 \ > /dev/null 2>&1 & ``` The Argo CD dashboard is now accessible at [https://localhost:8080](https://localhost:8080/), using the default `admin` username and [password](https://argoproj.github.io/argo-cd/getting_started/#4-login-using-the-cli). Authenticate the Argo CD CLI: ```sh password=`kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d` argocd login 127.0.0.1:8080 \ --username=admin \ --password="${password}" \ --insecure ``` ## Configure project access and permissions Set up the `demo` [project](https://argoproj.github.io/argo-cd/user-guide/projects/) to group our [applications](https://argoproj.github.io/argo-cd/operator-manual/declarative-setup/#applications): ```sh kubectl apply -f gitops/project.yaml ``` This project defines the list of permitted resource kinds and target clusters that our applications can work with. Confirm that the project is deployed correctly: ```sh argocd proj get demo ``` On the dashboard: ![New project in Argo CD dashboard](/docs/images/gitops/dashboard-project.png "New project in Argo CD dashboard") {.border} ### Deploy the applications Deploy the `main` application which serves as the "parent" for all the other applications: ```sh kubectl apply -f gitops/main.yaml ``` {{< note >}} The "app of apps" pattern is commonly used in Argo CD workflows to bootstrap applications. See the Argo CD documentation for more [information](https://argoproj.github.io/argo-cd/operator-manual/cluster-bootstrapping/#app-of-apps-pattern). {{< /note >}} Confirm that the `main` application is deployed successfully: ```sh argocd app get main ``` Sync the `main` application: ```sh argocd app sync main ``` ![Synchronize the main application](/docs/images/gitops/dashboard-applications-main-sync.png "Synchronize the main application") {.border} Notice that only the `main` application is synchronized. Next, we will synchronize the remaining applications individually. ### Deploy cert-manager Synchronize the `cert-manager` application: ```sh argocd app sync cert-manager ``` Confirm that cert-manager is running: ```sh for deploy in "cert-manager" "cert-manager-cainjector" "cert-manager-webhook"; \ do kubectl -n cert-manager rollout status deploy/${deploy}; \ done ``` ![Synchronize the cert-manager application](/docs/images/gitops/dashboard-cert-manager-sync.png "Synchronize the cert-manager application") {.border} ### Deploy Sealed Secrets Synchronize the `sealed-secrets` application: ```sh argocd app sync sealed-secrets ``` Confirm that sealed-secrets is running: ```sh kubectl -n kube-system rollout status deploy/sealed-secrets ``` ![Synchronize the sealed-secrets application](/docs/images/gitops/dashboard-sealed-secrets-sync.png "Synchronize the sealed-secrets application") {.border} ### Create mTLS trust anchor Before proceeding with deploying Linkerd, we will need to create the mTLS trust anchor. Then we will also set up the `linkerd-bootstrap` application to manage the trust anchor certificate. Create a new mTLS trust anchor private key and certificate: ```sh step certificate create root.linkerd.cluster.local sample-trust.crt sample-trust.key \ --profile root-ca \ --no-password \ --not-after 43800h \ --insecure ``` Confirm the details (encryption algorithm, expiry date, SAN etc.) of the new trust anchor: ```sh step certificate inspect sample-trust.crt ``` Before creating the `SealedSecret`, make sure you have installed the `kubeseal` utility, as instructed [here](https://github.com/bitnami-labs/sealed-secrets/releases) Now create the `SealedSecret` resource to store the encrypted trust anchor: ```sh kubectl create ns linkerd kubectl -n linkerd create secret tls linkerd-trust-anchor \ --cert sample-trust.crt \ --key sample-trust.key \ --dry-run=client -oyaml | \ kubeseal --controller-name=sealed-secrets -oyaml - | \ kubectl patch -f - \ -p '{"spec": {"template": {"type":"kubernetes.io/tls", "metadata": {"labels": {"linkerd.io/control-plane-component":"identity", "linkerd.io/control-plane-ns":"linkerd"}, "annotations": {"linkerd.io/created-by":"linkerd/cli stable-2.12.0"}}}}}' \ --dry-run=client \ --type=merge \ --local -oyaml > gitops/resources/linkerd/trust-anchor.yaml ``` This will overwrite the existing `SealedSecret` resource in your local `gitops/resources/linkerd/trust-anchor.yaml` file. We will push this change to the in-cluster Git server. Confirm that only the `spec.encryptedData` is changed: ```sh git diff gitops/resources/linkerd/trust-anchor.yaml ``` Commit and push the new trust anchor secret to your in-cluster Git server: ```sh git add gitops/resources/linkerd/trust-anchor.yaml git commit -m "update encrypted trust anchor" git push git-server master ``` Confirm the commit is successfully pushed: ```sh kubectl -n scm exec "${git_server}" -- git --git-dir linkerd-examples.git log -1 ``` ## Deploy linkerd-bootstrap Synchronize the `linkerd-bootstrap` application: ```sh argocd app sync linkerd-bootstrap ``` {{< note >}} If the issuer and certificate resources appear in a degraded state, it's likely that the SealedSecrets controller failed to decrypt the sealed `linkerd-trust-anchor` secret. Check the SealedSecrets controller for error logs. For debugging purposes, the sealed resource can be retrieved using the `kubectl -n linkerd get sealedsecrets linkerd-trust-anchor -oyaml` command. Ensure that this resource matches the `gitops/resources/linkerd/trust-anchor.yaml` file you pushed to the in-cluster Git server earlier. {{< /note >}} ![Synchronize the linkerd-bootstrap application](/docs/images/gitops/dashboard-linkerd-bootstrap-sync.png "Synchronize the linkerd-bootstrap application") {.border} SealedSecrets should have created a secret containing the decrypted trust anchor. Retrieve the decrypted trust anchor from the secret: ```sh trust_anchor=`kubectl -n linkerd get secret linkerd-trust-anchor -ojsonpath="{.data['tls\.crt']}" | base64 -d -w 0 -` ``` Confirm that it matches the decrypted trust anchor certificate you created earlier in your local `sample-trust.crt` file: ```sh diff -b \ <(echo "${trust_anchor}" | step certificate inspect -) \ <(step certificate inspect sample-trust.crt) ``` ### Deploy Linkerd Now we are ready to install Linkerd. The decrypted trust anchor we just retrieved will be passed to the installation process using the `identityTrustAnchorsPEM` parameter. Prior to installing Linkerd, note that the `identityTrustAnchorsPEM` parameter is set to an "empty" certificate string: ```sh argocd app get linkerd-control-plane -ojson | \ jq -r '.spec.source.helm.parameters[] | select(.name == "identityTrustAnchorsPEM") | .value' ``` ![Empty default trust anchor](/docs/images/gitops/dashboard-trust-anchor-empty.png "Empty default trust anchor") {.border} We will override this parameter in the `linkerd` application with the value of `${trust_anchor}`. Locate the `identityTrustAnchorsPEM` variable in your local `gitops/argo-apps/linkerd-control-plane.yaml` file, and set its `value` to that of `${trust_anchor}`. Ensure that the multi-line string is indented correctly. E.g., ```yaml source: chart: linkerd-control-plane repoURL: https://helm.linkerd.io/stable targetRevision: 1.9.0 helm: parameters: - name: identityTrustAnchorsPEM value: | -----BEGIN CERTIFICATE----- MIIBlTCCATygAwIBAgIRAKQr9ASqULvXDeyWpY1LJUQwCgYIKoZIzj0EAwIwKTEn MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDkx ODIwMTAxMFoXDTI1MDkxNzIwMTAxMFowKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+PUp IR74PsU+geheoyseycyquYyes5eeksIb5FDm8ptOXQ2xPcBpvesZkj6uIyS3k4qV E0S9VtMmHNeycL7446NFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB Af8CAQEwHQYDVR0OBBYEFHypCh7hiSLNxsKhMylQgqD9t7NNMAoGCCqGSM49BAMC A0cAMEQCIEWhI86bXWEd4wKTnG07hBfBuVCT0bxopaYnn3wRFx7UAiAwXyh5uaVg MwCC5xL+PM+bm3PRqtrmI6TocWH07GbMxg== -----END CERTIFICATE----- ``` Confirm that only one `spec.source.helm.parameters.value` field is changed: ```sh git diff gitops/argo-apps/linkerd-control-plane.yaml ``` Commit and push the changes to the Git server: ```sh git add gitops/argo-apps/linkerd-control-plane.yaml git commit -m "set identityTrustAnchorsPEM parameter" git push git-server master ``` Synchronize the `main` application: ```sh argocd app sync main ``` Confirm that the new trust anchor is picked up by the `linkerd` application: ```sh argocd app get linkerd-control-plane -ojson | \ jq -r '.spec.source.helm.parameters[] | select(.name == "identityTrustAnchorsPEM") | .value' ``` ![Override mTLS trust anchor](/docs/images/gitops/dashboard-trust-anchor-override.png "Override mTLS trust anchor") {.border} Synchronize the `linkerd-crds` and `linkerd-control-plane` applications: ```sh argocd app sync linkerd-crds argocd app sync linkerd-control-plane ``` Check that Linkerd is ready: ```sh linkerd check ``` ![Synchronize Linkerd](/docs/images/gitops/dashboard-linkerd-sync.png "Synchronize Linkerd") {.border} ### Test with emojivoto Deploy emojivoto to test auto proxy injection: ```sh argocd app sync emojivoto ``` Check that the applications are healthy: ```sh for deploy in "emoji" "vote-bot" "voting" "web" ; \ do kubectl -n emojivoto rollout status deploy/${deploy}; \ done ``` ![Synchronize emojivoto](/docs/images/gitops/dashboard-emojivoto-sync.png "Synchronize emojivoto") {.border} ### Upgrade Linkerd to 2.12.1 (Assuming 2.12.1 has already been released ;-) ) Use your editor to change the `spec.source.targetRevision` field to `1.9.3` (that's the Helm chart version corresponding to linkerd stable-2.12.1) in the `gitops/argo-apps/linkerd-control-plane.yaml` file: Confirm that only the `targetRevision` field is changed: ```sh git diff gitops/argo-apps/linkerd-control-plane.yaml ``` Commit and push this change to the Git server: ```sh git add gitops/argo-apps/linkerd-control-plane.yaml git commit -m "upgrade Linkerd to 2.12.1" git push git-server master ``` Synchronize the `main` application: ```sh argocd app sync main ``` Synchronize the `linkerd-control-plane` application: ```sh argocd app sync linkerd-control-plane ``` Confirm that the upgrade completed successfully: ```sh linkerd check ``` Confirm the new version of the control plane: ```sh linkerd version ``` ### Clean up All the applications can be removed by removing the `main` application: ```sh argocd app delete main --cascade=true ```