Repository: linkerd/linkerd2
Pull request #4018 (2020-02-05 21:20:21)

Address external_issuer_test.go flakiness
From time to time we get this CI error when testing the external issuer
mechanism:
```
Test script: [external_issuer_test.go] Params:
[--linkerd-namespace=l5d-integration-external-issuer
--external-issuer=true]
--- FAIL: TestExternalIssuer (33.61s)
    external_issuer_test.go:89: Received error while ensuring test app
    works (before cert rotation): Error stripping header and trailing
    newline; full output:
    FAIL
```
https://github.com/alpeb/linkerd2/runs/428273855?check_suite_focus=true#step:6:526

This is caused by the "backend" pod not receiving traffic from
"slow-cooker" in a timely manner.
After those pods are deployed we're only checking that "backend" is
ready, but not "slow-cooker", so this change adds that check.

I'm also removing the `TestHelper.CheckDeployment` call because it's
redundant, since the preceeding `TestHelper.CheckPods` is already checking
that the deployment has all the specified replicas ready.


Patch:
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index 48671c88d..e595201b9 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -17,19 +17,23 @@ on:
 # go_lint
 # js_unit_tests
 
-# Docker build and integration tests for every master/tag push and linkerd org PR
+# master/tag push and linkerd org PR: Docker build and integration tests
 #
 # docker_pull
 # docker_build
 # kind_setup
-#   -> kind_integration
+#   -> kind_integration_host
 #     -> kind_cleanup
 
+# Forked repository PR: Docker build and integration tests
+#
+# docker_build
+#   -> kind_integration_github
+
 # Docker deploy and cloud integration tests for every master/tag push
 #
 #       -> docker_deploy
 #         -> cloud_integration
-#           -> cloud_cleanup
 
 jobs:
 
@@ -135,18 +139,17 @@ jobs:
   # Docker build and kind integration tests run for:
   # - every master push
   # - every tag push
-  # - every PR from a linkerd org member
+  # - every PR
   #
 
   docker_pull:
     name: Docker pull
+    if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
     runs-on: ubuntu-18.04
     steps:
     - name: Checkout code
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       uses: actions/checkout@v2
     - name: Docker SSH setup
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       run: |
         mkdir -p ~/.ssh/
         touch ~/.ssh/id && chmod 600 ~/.ssh/id
@@ -155,7 +158,6 @@ jobs:
         echo "${{ secrets.DOCKER_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
         ssh linkerd-docker docker version
     - name: Docker pull
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       env:
         DOCKER_HOST: ssh://linkerd-docker
       run: |
@@ -165,9 +167,10 @@ jobs:
   docker_build:
     name: Docker build
     runs-on: ubuntu-18.04
+    env:
+      IMAGE_ARCHIVES_DIR: /home/runner/archives
     steps:
     - name: Checkout code
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       uses: actions/checkout@v2
     - name: Docker SSH setup
       if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
@@ -178,13 +181,30 @@ jobs:
         echo "${{ secrets.DOCKER_PRIVATE_KEY }}" > ~/.ssh/id
         echo "${{ secrets.DOCKER_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
         ssh linkerd-docker docker version
-    - name: Docker build
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
-      env:
-        DOCKER_HOST: ssh://linkerd-docker
+        echo ::set-env name=DOCKER_HOST::ssh://linkerd-docker
+    - name: Build docker images
       run: |
         export PATH="`pwd`/bin:$PATH"
         DOCKER_TRACE=1 bin/docker-build
+    - name: Create artifact with CLI and image archives
+      if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
+      run: |
+        mkdir -p $IMAGE_ARCHIVES_DIR
+        cp target/cli/linux/linkerd $IMAGE_ARCHIVES_DIR/
+        TAG="$(CI_FORCE_CLEAN=1 bin/root-tag)"
+        for img in controller grafana proxy web ; do
+          docker save "gcr.io/linkerd-io/$img:$TAG" > $IMAGE_ARCHIVES_DIR/$img.tar
+        done
+    # `with.path` values do not support environment variables yet, so an
+    # absolute path is used here.
+    #
+    # https://github.com/actions/upload-artifact/issues/8
+    - name: Upload artifact
+      if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
+      uses: actions/upload-artifact@v1
+      with:
+        name: image-archives
+        path: /home/runner/archives
 
   kind_setup:
     strategy:
@@ -192,13 +212,12 @@ jobs:
       matrix:
         integration_test: [deep, upgrade, helm, helm_upgrade, custom_domain, external_issuer]
     name: Cluster setup (${{ matrix.integration_test }})
+    if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
     runs-on: ubuntu-18.04
     steps:
     - name: Checkout code
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       uses: actions/checkout@v2
     - name: Docker SSH setup
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       run: |
         mkdir -p ~/.ssh/
         touch ~/.ssh/id && chmod 600 ~/.ssh/id
@@ -207,7 +226,6 @@ jobs:
         echo "${{ secrets.DOCKER_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
         ssh linkerd-docker docker version
     - name: Kind cluster setup
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       env:
         DOCKER_HOST: ssh://linkerd-docker
       run: |
@@ -225,20 +243,27 @@ jobs:
             bin/kind create cluster --name=$KIND_CLUSTER --wait=2m --verbosity 3
         fi
 
-  kind_integration:
+  #
+  # Integration tests that run on the docker host are limited by the fact that
+  # while they all run in separate KinD clusters, they are on the same
+  # machine. The `strategy` context cannot be conditionally set, so there are
+  # separate `kind_integration_*` jobs. This allows the `kind_integration_github`
+  # job to run it's entire matrix in parallel.
+  #
+
+  kind_integration_host:
     strategy:
       max-parallel: 3
       matrix:
         integration_test: [deep, upgrade, helm, helm_upgrade, custom_domain, external_issuer]
     needs: [docker_pull, docker_build, kind_setup]
-    name: Integration tests (${{ matrix.integration_test }})
+    name: Host int. tests (${{ matrix.integration_test }})
+    if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
     runs-on: ubuntu-18.04
     steps:
     - name: Checkout code
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       uses: actions/checkout@v2
     - name: Docker SSH setup
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       run: |
         mkdir -p ~/.ssh/
         touch ~/.ssh/id && chmod 600 ~/.ssh/id
@@ -246,8 +271,14 @@ jobs:
         echo "${{ secrets.DOCKER_PRIVATE_KEY }}" > ~/.ssh/id
         echo "${{ secrets.DOCKER_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
         ssh linkerd-docker docker version
+    - name: Try to load cached Go modules
+      uses: actions/cache@v1
+      with:
+        path: ~/go/pkg/mod
+        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+        restore-keys: |
+          ${{ runner.os }}-go-
     - name: Kind load docker images
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       run: |
         TAG="$(CI_FORCE_CLEAN=1 bin/root-tag)"
         export KIND_CLUSTER=github-$TAG-${{ matrix.integration_test }}
@@ -262,7 +293,6 @@ jobs:
           done
         EOF
     - name: Install linkerd CLI
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       env:
         DOCKER_HOST: ssh://linkerd-docker
       run: |
@@ -275,7 +305,6 @@ jobs:
         [[ "$TAG" == "$($HOME/.linkerd version --short --client)" ]]
         echo "Installed Linkerd CLI version: $TAG"
     - name: Run integration tests
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       env:
         DOCKER_HOST: ssh://linkerd-docker
         GITCOOKIE_SH: ${{ secrets.GITCOOKIE_SH }}
@@ -302,18 +331,65 @@ jobs:
           ${{ matrix.integration_test }}_integration_tests
         )
 
+  kind_integration_github:
+    strategy:
+      matrix:
+        integration_test: [deep, upgrade, helm, helm_upgrade, custom_domain, external_issuer]
+    needs: [docker_build]
+    name: GitHub int. tests (${{ matrix.integration_test }})
+    if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
+    runs-on: ubuntu-18.04
+    steps:
+    - name: Checkout code
+      uses: actions/checkout@v2
+    - name: Download image archives
+      uses: actions/download-artifact@v1
+      with:
+        name: image-archives
+    - name: Try to load cached Go modules
+      uses: actions/cache@v1
+      with:
+        path: ~/go/pkg/mod
+        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+        restore-keys: |
+          ${{ runner.os }}-go-
+    - name: Setup KinD (default)
+      if: matrix.integration_test != 'custom_domain'
+      uses: engineerd/setup-kind@v0.3.0
+      with:
+        version: "v0.6.1"
+    - name: Setup KinD (custom_domain)
+      if: matrix.integration_test == 'custom_domain'
+      uses: engineerd/setup-kind@v0.3.0
+      with:
+        config: test/testdata/custom_cluster_domain_config.yaml
+        version: "v0.6.1"
+    - name: Load image archives into KinD
+      run: |
+        for img in controller grafana proxy web; do
+          kind load image-archive image-archives/$img.tar
+        done
+    - name: Install linkerd CLI
+      run: |
+        cp image-archives/linkerd $HOME/.linkerd
+        chmod +x $HOME/.linkerd
+    - name: Run integration tests
+      run: |
+        . bin/_test-run.sh
+        init_test_run $HOME/.linkerd
+        ${{ matrix.integration_test }}_integration_tests
+
   kind_cleanup:
-    if: always()
     strategy:
       fail-fast: false # always attempt to cleanup all clusters
       matrix:
         integration_test: [deep, upgrade, helm, helm_upgrade, custom_domain, external_issuer]
-    needs: [kind_integration]
+    needs: [kind_integration_host]
     name: Cluster cleanup (${{ matrix.integration_test }})
+    if: always() && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork)
     runs-on: ubuntu-18.04
     steps:
     - name: Checkout code
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       uses: actions/checkout@v2
     # for debugging
     - name: Dump env
@@ -328,7 +404,6 @@ jobs:
         JOB_CONTEXT: ${{ toJson(job) }}
       run: echo "$JOB_CONTEXT"
     - name: Docker SSH setup
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       run: |
         mkdir -p ~/.ssh/
         touch ~/.ssh/id && chmod 600 ~/.ssh/id
@@ -337,7 +412,6 @@ jobs:
         echo "${{ secrets.DOCKER_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
         ssh linkerd-docker docker version
     - name: Kind cluster cleanup
-      if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
       env:
         DOCKER_HOST: ssh://linkerd-docker
       run: |
@@ -353,38 +427,19 @@ jobs:
 
   docker_deploy:
     name: Docker deploy
+    if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
     runs-on: ubuntu-18.04
-    needs: [validate_go_deps, go_unit_tests, go_lint, js_unit_tests, kind_integration, kind_cleanup]
+    needs: [validate_go_deps, go_unit_tests, go_lint, js_unit_tests, kind_integration_host, kind_cleanup]
     steps:
     - name: Checkout code
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
       uses: actions/checkout@v2
     - name: Configure gcloud
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
-      env:
-        CLOUDSDK_CORE_DISABLE_PROMPTS: "1"
-        CLOUD_SDK_SERVICE_ACCOUNT_KEY: ${{ secrets.CLOUD_SDK_SERVICE_ACCOUNT_KEY }}
-        GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
-        GCP_ZONE: ${{ secrets.GCP_ZONE }}
-        GKE_CLUSTER: ${{ secrets.GKE_CLUSTER }}
-      run: |
-        # Install gcloud and kubectl.
-        echo "$CLOUD_SDK_SERVICE_ACCOUNT_KEY" > .gcp.json
-        dir="${CLOUDSDK_INSTALL_DIR:-${HOME}}/google-cloud-sdk"
-        (
-          . bin/_gcp.sh ;
-          install_gcloud "$dir"
-          gcloud components install kubectl
-          # Configure gcloud with a service account.
-          set_gcloud_config "$GCP_PROJECT" "$GCP_ZONE" "$GKE_CLUSTER"
-          # Get a kubernetes context.
-          get_k8s_ctx "$GCP_PROJECT" "$GCP_ZONE" "$GKE_CLUSTER"
-        )
-        . "$dir/path.bash.inc"
-        gcloud auth configure-docker
-        bin/kubectl version --short
+      uses: linkerd/linkerd2-action-gcloud@v1.0.0
+      with:
+        cloud_sdk_service_account_key: ${{ secrets.CLOUD_SDK_SERVICE_ACCOUNT_KEY }}
+        gcp_project: ${{ secrets.GCP_PROJECT }}
+        gcp_zone: ${{ secrets.GCP_ZONE }}
     - name: Docker SSH setup
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
       run: |
         mkdir -p ~/.ssh/
         touch ~/.ssh/id && chmod 600 ~/.ssh/id
@@ -393,7 +448,6 @@ jobs:
         echo "${{ secrets.DOCKER_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
         ssh linkerd-docker docker version
     - name: Docker push
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
       env:
         DOCKER_HOST: ssh://linkerd-docker
       run: |
@@ -406,38 +460,14 @@ jobs:
 
   cloud_integration:
     name: Cloud integration tests
+    if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
     runs-on: ubuntu-18.04
     needs: [docker_deploy]
     steps:
     - name: Checkout code
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
       uses: actions/checkout@v2
-    - name: Configure gcloud
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
-      env:
-        CLOUDSDK_CORE_DISABLE_PROMPTS: "1"
-        CLOUD_SDK_SERVICE_ACCOUNT_KEY: ${{ secrets.CLOUD_SDK_SERVICE_ACCOUNT_KEY }}
-        GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
-        GCP_ZONE: ${{ secrets.GCP_ZONE }}
-        GKE_CLUSTER: ${{ secrets.GKE_CLUSTER }}
-      run: |
-        # Install gcloud and kubectl.
-        echo "$CLOUD_SDK_SERVICE_ACCOUNT_KEY" > .gcp.json
-        dir="${CLOUDSDK_INSTALL_DIR:-${HOME}}/google-cloud-sdk"
-        (
-          . bin/_gcp.sh ;
-          install_gcloud "$dir"
-          gcloud components install kubectl
-          # Configure gcloud with a service account.
-          set_gcloud_config "$GCP_PROJECT" "$GCP_ZONE" "$GKE_CLUSTER"
-          # Get a kubernetes context.
-          get_k8s_ctx "$GCP_PROJECT" "$GCP_ZONE" "$GKE_CLUSTER"
-        )
-        . "$dir/path.bash.inc"
-        gcloud auth configure-docker
-        bin/kubectl version --short
     - name: Install linkerd CLI
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
+      id: install_cli
       run: |
         TAG="$(CI_FORCE_CLEAN=1 bin/root-tag)"
         image="gcr.io/linkerd-io/cli-bin:$TAG"
@@ -447,8 +477,16 @@ jobs:
         # validate CLI version matches the repo
         [[ "$TAG" == "$($HOME/.linkerd version --short --client)" ]]
         echo "Installed Linkerd CLI version: $TAG"
+        echo "::set-output name=tag::$TAG"
+    - name: Create GKE cluster
+      uses: linkerd/linkerd2-action-gcloud@v1.0.0
+      with:
+        cloud_sdk_service_account_key: ${{ secrets.CLOUD_SDK_SERVICE_ACCOUNT_KEY }}
+        gcp_project: ${{ secrets.GCP_PROJECT }}
+        gcp_zone: ${{ secrets.GCP_ZONE }}
+        create: true
+        name: testing-${{ steps.install_cli.outputs.tag }}-${{ github.run_id }}
     - name: Run integration tests
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
       env:
         GITCOOKIE_SH: ${{ secrets.GITCOOKIE_SH }}
       run: |
@@ -457,92 +495,39 @@ jobs:
         version="$($HOME/.linkerd version --client --short | tr -cd '[:alnum:]-')"
         bin/test-run $HOME/.linkerd linkerd-$version
     - name: CNI tests
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
       run: |
         export TAG="$($HOME/.linkerd version --client --short)"
         go test -cover -race -v -mod=readonly ./cni-plugin/test -integration-tests
 
-  cloud_cleanup:
-    if: always()
-    name: Cloud cleanup
-    runs-on: ubuntu-18.04
-    needs: [cloud_integration]
-    steps:
-    - name: Checkout code
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
-      uses: actions/checkout@v2
-    - name: Configure gcloud
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
-      env:
-        CLOUDSDK_CORE_DISABLE_PROMPTS: "1"
-        CLOUD_SDK_SERVICE_ACCOUNT_KEY: ${{ secrets.CLOUD_SDK_SERVICE_ACCOUNT_KEY }}
-        GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
-        GCP_ZONE: ${{ secrets.GCP_ZONE }}
-        GKE_CLUSTER: ${{ secrets.GKE_CLUSTER }}
-      run: |
-        # Install gcloud and kubectl.
-        echo "$CLOUD_SDK_SERVICE_ACCOUNT_KEY" > .gcp.json
-        dir="${CLOUDSDK_INSTALL_DIR:-${HOME}}/google-cloud-sdk"
-        (. bin/_gcp.sh ;
-          install_gcloud "$dir"
-          gcloud components install kubectl
-          # Configure gcloud with a service account.
-          set_gcloud_config "$GCP_PROJECT" "$GCP_ZONE" "$GKE_CLUSTER"
-          # Get a kubernetes context.
-          get_k8s_ctx "$GCP_PROJECT" "$GCP_ZONE" "$GKE_CLUSTER"
-        )
-        . "$dir/path.bash.inc"
-        gcloud auth configure-docker
-        bin/kubectl version --short
-    - name: Cleanup
-      if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
-      run: |
-        export PATH="`pwd`/bin:$PATH"
-        bin/test-cleanup
-
-  #
   # Helm chart artifact deploy run for:
   # - every tag push
   #
 
   chart_deploy:
     name: Helm chart deploy
+    if: startsWith(github.ref, 'refs/tags')
     runs-on: ubuntu-18.04
     needs: [cloud_integration]
     steps:
     - name: Checkout code
-      if: startsWith(github.ref, 'refs/tags')
       uses: actions/checkout@v2
     - name: Configure gsutils
-      if: startsWith(github.ref, 'refs/tags')
-      env:
-        CLOUDSDK_CORE_DISABLE_PROMPTS: "1"
-        CLOUD_SDK_SERVICE_ACCOUNT_KEY: ${{ secrets.LINKERD_SITE_TOKEN }}
-        GCP_PROJECT: ${{ secrets.LINKERD_SITE_PROJECT }}
-        GCP_ZONE: ${{ secrets.LINKERD_SITE_ZONE }}
-        GKE_CLUSTER: ${{ secrets.GKE_CLUSTER }}
-      run: |
-        # Install gcloud and gsutil.
-        echo "$CLOUD_SDK_SERVICE_ACCOUNT_KEY" > .gcp.json
-        dir="${CLOUDSDK_INSTALL_DIR:-${HOME}}/google-cloud-sdk"
-        (
-          . bin/_gcp.sh ;
-          install_gcloud "$dir"
-          gcloud components install gsutil
-          # Configure gcloud with a service account.
-          set_gcloud_config "$GCP_PROJECT" "$GCP_ZONE" "$GKE_CLUSTER"
-        )
-        . "$dir/path.bash.inc"
-        mkdir -p target/helm
+      uses: linkerd/linkerd2-action-gcloud@v1.0.0
+      with:
+        cloud_sdk_service_account_key: ${{ secrets.CLOUD_SDK_SERVICE_ACCOUNT_KEY }}
+        gcp_project: ${{ secrets.GCP_PROJECT }}
+        gcp_zone: ${{ secrets.GCP_ZONE }}
     - name: Edge Helm chart creation and upload
       if: startsWith(github.ref, 'refs/tags/edge')
       run: |
+        mkdir -p target/helm
         gsutil cp gs://helm.linkerd.io/edge/index.yaml target/helm/index-pre.yaml
         bin/helm-build package
         gsutil rsync target/helm gs://helm.linkerd.io/edge
     - name: Stable Helm chart creation and upload
       if: startsWith(github.ref, 'refs/tags/stable')
       run: |
+        mkdir -p target/helm
         gsutil cp gs://helm.linkerd.io/stable/index.yaml target/helm/index-pre.yaml
         bin/helm-build package
         gsutil rsync target/helm gs://helm.linkerd.io/stable
diff --git a/.gitignore b/.gitignore
index 449714e41..1ed553166 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ tmp.discovery
 **/CMakeLists.txt
 *.iml
 web/web
-web/app/node_modules
+**/node_modules
 web/app/dist
 web/app/yarn-error.log
 .gorun
diff --git a/CHANGES.md b/CHANGES.md
index e234ca567..0900b5a66 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,168 @@
+## stable-2.7.0
+
+This release adds support for integrating Linkerd's PKI with an external
+certificate issuer such as [`cert-manager`] as well as streamlining the
+certificate rotation process in general. For more details about cert-manager
+and certificate rotation, see the
+[docs](https://linkerd.io/2/tasks/use_external_certs/). This release also
+includes performance improvements to the dashboard, reduced memory usage of the
+proxy, various improvements to the Helm chart, and much much more.
+
+To install this release, run: `curl https://run.linkerd.io/install | sh`
+
+**Upgrade notes**: This release includes breaking changes to our Helm charts.
+Please see the [upgrade instructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-270).
+
+**Special thanks to**: @alenkacz, @bmcstdio, @daxmc99, @droidnoob, @ereslibre,
+@javaducky, @joakimr-axis, @JohannesEH, @KIVagant, @mayankshah1607,
+@Pothulapati, and @StupidScience!
+
+**Full release notes**:
+
+* CLI
+  * Updated the mTLS trust anchor checks to eliminate false positives caused by
+    extra trailing spaces
+  * Reduced the severity level of the Linkerd version checks, so that they
+    don't fail when the external version endpoint is unreachable
+    (thanks @mayankshah1607!)
+  * Added a new `tap` APIService check to aid with uncovering Kubernetes API
+    aggregatation layer issues (thanks @droidnoob!)
+  * Introduced CNI checks to confirm the CNI plugin is installed and ready;
+    this is done through `linkerd check --pre --linkerd-cni-enabled` before
+    installation and `linkerd check` after installation if the CNI plugin is
+    present
+  * Added support for the `--as-group` flag so that users can impersonate
+    groups for Kubernetes operations (thanks @mayankshah1607!)
+  * Added HA specific checks to `linkerd check` to ensure that the `kube-system`
+    namespace has the `config.linkerd.io/admission-webhooks:disabled`
+    label set
+  * Fixed a problem causing the presence of unnecessary empty fields in
+    generated resource definitions (thanks @mayankshah1607)
+  * Added the ability to pass both port numbers and port ranges to
+    `--skip-inbound-ports` and `--skip-outbound-ports` (thanks to @javaducky!)
+  * Increased the comprehensiveness of `linkerd check --pre`
+  * Added TLS certificate validation to `check` and `upgrade` commands
+  * Added support for injecting CronJobs and ReplicaSets, as well as the ability
+    to use them as targets in the CLI subcommands
+  * Introduced the new flags `--identity-issuer-certificate-file`,
+    `--identity-issuer-key-file` and `identity-trust-anchors-file` to `linkerd
+    upgrade` to support trust anchor and issuer certificate rotation
+  * Added a check that ensures using `--namespace` and `--all-namespaces`
+    results in an error as they are mutually exclusive
+  * Added a `Dashboard.Replicas` parameter to the Linkerd Helm chart to allow
+    configuring the number of dashboard replicas (thanks @KIVagant!)
+  * Removed redundant service profile check (thanks @alenkacz!)
+  * Updated `uninject` command to work with namespace resources
+    (thanks @mayankshah1607!)
+  * Added a new `--identity-external-issuer` flag to `linkerd install` that
+    configures Linkerd to use certificates issued by an external certificate
+    issuer (such as `cert-manager`)
+  * Added support for injecting a namespace to `linkerd inject` (thanks
+    @mayankshah1607!)
+  * Added checks to `linkerd check --preinstall` ensuring Kubernetes Secrets
+    can be created and accessed
+  * Fixed `linkerd tap` sometimes displaying incorrect pod names for unmeshed
+    IPs that match multiple running pods
+  * Made `linkerd install --ignore-cluster` and `--skip-checks` faster
+  * Fixed a bug causing `linkerd upgrade` to fail when used with
+  `--from-manifest`
+  * Made `--cluster-domain` an install-only flag (thanks @bmcstdio!)
+  * Updated `check` to ensure that proxy trust anchors match configuration
+       (thanks @ereslibre!)
+  * Added condition to the `linkerd stat` command that requires a window size
+    of at least 15 seconds to work properly with Prometheus
+* Controller
+  * Fixed an issue where an override of the Docker registry was not being
+    applied to debug containers (thanks @javaducky!)
+  * Added check for the Subject Alternate Name attributes to the API server
+    when access restrictions have been enabled (thanks @javaducky!)
+  * Added support for arbitrary pod labels so that users can leverage the
+    Linkerd provided Prometheus instance to scrape for their own labels
+    (thanks @daxmc99!)
+  * Fixed an issue with CNI config parsing
+  * Fixed a race condition in the `linkerd-web` service
+  * Updated Prometheus to 2.15.2 (thanks @Pothulapati)
+  * Increased minimum kubernetes version to 1.13.0
+  * Added support for pod ip and service cluster ip lookups in the destination 
+    service
+  * Added recommended kubernetes labels to control-plane
+  * Added the `--wait-before-exit-seconds` flag to linkerd inject for the proxy 
+    sidecar to delay the start of its shutdown process (a huge commit from 
+    @KIVagant, thanks!)
+  * Added a pre-sign check to the identity service 
+  * Fixed inject failures for pods with security context capabilities
+  * Added `conntrack` to the `debug` container to help with connection tracking
+    debugging
+  * Fixed a bug in `tap` where mismatch cluster domain and trust domain caused
+    `tap` to hang
+  * Fixed an issue in the `identity` RBAC resource which caused start up errors
+    in k8s 1.6 (thanks @Pothulapati!)
+  * Added support for using trust anchors from an external certificate issuer
+    (such as `cert-mananger`) to the `linkerd-identity` service
+  * Added support for headless services (thanks @JohannesEH!)
+* Helm
+  * **Breaking change**: Renamed `noInitContainer` parameter to `cniEnabled`
+  * **Breaking Change** Updated Helm charts to follow best practices (thanks
+    @Pothulapati and @javaducky!)
+  * Fixed an issue with `helm install` where the lists of ignored inbound and
+    outbound ports would not be reflected
+  * Fixed the `linkerd-cni` Helm chart not setting proper namespace annotations
+    and labels
+  * Fixed certificate issuance lifetime not being set when installing through
+    Helm
+  * Updated the helm build to retain previous releases
+  * Moved CNI template into its own Helm chart
+* Proxy
+  * Fixed an issue that could cause the OpenCensus exporter to stall
+  * Improved error classification and error responses for gRPC services
+  * Fixed a bug where the proxy could stop receiving service discovery updates,
+    resulting in 503 errors
+  * Improved debug/error logging to include detailed contextual information
+  * Fixed a bug in the proxy's logging subsystem that could cause the proxy to
+    consume memory until the process is OOM killed, especially when the proxy was
+    configured to log diagnostic information
+  * Updated proxy dependencies to address RUSTSEC-2019-0033, RUSTSEC-2019-0034,
+    and RUSTSEC-2020-02
+* Web UI
+  * Fixed an error when refreshing an already open dashboard when the Linkerd
+    version has changed
+  * Increased the speed of the dashboard by pausing network activity when the 
+    dashboard is not visible to the user
+  * Added support for CronJobs and ReplicaSets, including new Grafana dashboards
+    for them
+  * Added `linkerd check` to the dashboard in the `/controlplane` view
+  * Added request and response headers to the `tap` expanded view in the
+    dashboard
+  * Added filter to namespace select button
+  * Improved how empty tables are displayed
+  * Added `Host:` header validation to the `linkerd-web` service, to protect
+    against DNS rebinding attacks
+  * Made the dashboard sidebar component responsive
+  * Changed the navigation bar color to the one used on the [Linkerd](https://linkerd.io/) website
+* Internal
+  * Added validation to incoming sidecar injection requests that ensures
+    the value of `linkerd.io/inject` is either `enabled` or `disabled`
+    (thanks @mayankshah1607)
+  * Upgraded the Prometheus Go client library to v1.2.1 (thanks @daxmc99!)
+  * Fixed an issue causing `tap`, `injector` and `sp-validator` to use 
+    old certificates after `helm upgrade` due to not being restarted
+  * Fixed incomplete Swagger definition of the tap api, causing benign
+    error logging in the kube-apiserver
+  * Removed the destination container from the linkerd-controller deployment as
+    it now runs in the linkerd-destination deployment
+  * Allowed the control plane to be injected with the `debug` container
+  * Updated proxy image build script to support HTTP proxy options
+    (thanks @joakimr-axis!)
+  * Updated the CLI `doc` command to auto-generate documentation for the proxy
+    configuration annotations (thanks @StupidScience!)
+  * Added new `--trace-collector` and `--trace-collector-svc-account` flags to
+    `linkerd inject` that configures the OpenCensus trace collector used by
+    proxies in the injected workload (thanks @Pothulapati!)
+  * Added a new `--control-plane-tracing` flag to `linkerd install` that enables
+    distributed tracing in the control plane (thanks @Pothulapati!)
+  * Added distributed tracing support to the control plane (thanks
+    @Pothulapati!)
+
 ## edge-20.2.1
 
 This edge release is a release candidate for `stable-2.7` and fixes an issue
diff --git a/Dockerfile-proxy b/Dockerfile-proxy
index 0ae139182..7d0da11f4 100644
--- a/Dockerfile-proxy
+++ b/Dockerfile-proxy
@@ -9,7 +9,7 @@ RUN (proxy=$(bin/fetch-proxy $(cat proxy-version)) && \
     mv "$proxy" linkerd2-proxy)
 
 ## compile proxy-identity agent
-FROM gcr.io/linkerd-io/go-deps:8d2ce4bf as golang
+FROM gcr.io/linkerd-io/go-deps:93ea34a8 as golang
 WORKDIR /linkerd-build
 COPY pkg/flags pkg/flags
 COPY pkg/tls pkg/tls
diff --git a/bin/_gcp.sh b/bin/_gcp.sh
deleted file mode 100644
index 56fd29515..000000000
--- a/bin/_gcp.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-set -eu
-
-install_gcloud() {
-    dir="$1"
-
-    export CLOUDSDK_CORE_DISABLE_PROMPTS=1
-    if [ -d "$dir/bin" ]; then
-        . "$dir/path.bash.inc"
-        gcloud components update
-    else
-        rm -rf "$dir"
-        curl https://sdk.cloud.google.com | bash
-        . "$dir/path.bash.inc"
-    fi
-}
-
-set_gcloud_config() {
-    project="$1"
-    zone="$2"
-    cluster="$3"
-
-    gcloud auth activate-service-account --key-file .gcp.json
-    gcloud config set core/project "$project"
-    gcloud config set compute/zone "$zone"
-    gcloud config set container/cluster "$cluster"
-}
-
-get_k8s_ctx() {
-    project="$1"
-    zone="$2"
-    cluster="$3"
-
-    for c in $(kubectl config get-clusters |sed 1d) ; do
-        if [ "$c" = "gke_${project}_${zone}_${cluster}" ]; then
-            return 0
-        fi
-    done
-
-    gcloud container clusters get-credentials "$cluster"
-}
diff --git a/charts/linkerd2-cni/values.yaml b/charts/linkerd2-cni/values.yaml
index 610e5079f..3a3c502f9 100644
--- a/charts/linkerd2-cni/values.yaml
+++ b/charts/linkerd2-cni/values.yaml
@@ -6,7 +6,7 @@ ignoreInboundPorts: ""
 ignoreOutboundPorts: ""
 createdByAnnotation: linkerd.io/created-by
 cniPluginImage:   "gcr.io/linkerd-io/cni-plugin"
-cniPluginVersion: edge-20.2.1
+cniPluginVersion: stable-2.7.0
 logLevel:         info
 portsToRedirect:  ""
 proxyUID:         2102
diff --git a/charts/linkerd2/values.yaml b/charts/linkerd2/values.yaml
index 7123590b7..f8b609205 100644
--- a/charts/linkerd2/values.yaml
+++ b/charts/linkerd2/values.yaml
@@ -11,7 +11,7 @@ global:
   controlPlaneTracing: false
 
   # control plane version. See Proxy section for proxy version
-  linkerdVersion: &linkerd_version edge-20.2.1
+  linkerdVersion: &linkerd_version stable-2.7.0
 
   namespace: linkerd
 
diff --git a/cli/Dockerfile-bin b/cli/Dockerfile-bin
index ced183955..c465d05df 100644
--- a/cli/Dockerfile-bin
+++ b/cli/Dockerfile-bin
@@ -1,5 +1,5 @@
 ## compile binaries
-FROM gcr.io/linkerd-io/go-deps:8d2ce4bf as golang
+FROM gcr.io/linkerd-io/go-deps:93ea34a8 as golang
 WORKDIR /linkerd-build
 COPY cli cli
 COPY charts charts
diff --git a/cni-plugin/Dockerfile b/cni-plugin/Dockerfile
index b229039b6..9ae99bcb2 100644
--- a/cni-plugin/Dockerfile
+++ b/cni-plugin/Dockerfile
@@ -1,5 +1,5 @@
 ## compile cni-plugin utility
-FROM gcr.io/linkerd-io/go-deps:8d2ce4bf as golang
+FROM gcr.io/linkerd-io/go-deps:93ea34a8 as golang
 WORKDIR /linkerd-build
 COPY pkg pkg
 COPY controller controller
diff --git a/controller/Dockerfile b/controller/Dockerfile
index 1c9dd7f65..b4cc08afc 100644
--- a/controller/Dockerfile
+++ b/controller/Dockerfile
@@ -1,5 +1,5 @@
 ## compile controller service
-FROM gcr.io/linkerd-io/go-deps:8d2ce4bf as golang
+FROM gcr.io/linkerd-io/go-deps:93ea34a8 as golang
 WORKDIR /linkerd-build
 COPY controller/gen controller/gen
 COPY pkg pkg
diff --git a/go.mod b/go.mod
index 3d5d222ee..dd73d025b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/linkerd/linkerd2
 
-go 1.13.4
+go 1.13
 
 require (
 	contrib.go.opencensus.io/exporter/ocagent v0.6.0
diff --git a/go.sum b/go.sum
index 48a0f60aa..518fec565 100644
--- a/go.sum
+++ b/go.sum
@@ -211,10 +211,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/linkerd/linkerd2-proxy-api v0.1.10 h1:zGwz2Fek7fdfCxyZW7jq8IE6sexeVFW5UFBQFL6MrP4=
 github.com/linkerd/linkerd2-proxy-api v0.1.10/go.mod h1:2WJHEYXoww5ALM6c1QspRFiROGZg08tGxCO1js0tTVA=
-github.com/linkerd/linkerd2-proxy-init v1.3.0 h1:kyrMpaNKGPfd7FA8O/6VGxsYXx++TxxZVgRL6knWSa8=
-github.com/linkerd/linkerd2-proxy-init v1.3.0/go.mod h1:lK/sORPZDP6aFhPZps+AHMKphX5v9bKVDNESllz18C0=
-github.com/linkerd/linkerd2-proxy-init v1.3.1-0.20200117170322-a7441ab79b25 h1:RVz/okrTQugk9GKUDY9mJbl8mKUh90jfxTu97IvBvjg=
-github.com/linkerd/linkerd2-proxy-init v1.3.1-0.20200117170322-a7441ab79b25/go.mod h1:lK/sORPZDP6aFhPZps+AHMKphX5v9bKVDNESllz18C0=
 github.com/linkerd/linkerd2-proxy-init v1.3.1 h1:KTvUvPlSKUkpzZM0L/EIk+EJaKk7dwygu0CP95fOyGI=
 github.com/linkerd/linkerd2-proxy-init v1.3.1/go.mod h1:lK/sORPZDP6aFhPZps+AHMKphX5v9bKVDNESllz18C0=
 github.com/linkerd/stern v0.0.0-20190907020106-201e8ccdff9c h1:v8RmXOGO65HHLqcDpVNGGe3krA8FptfxcYS3wSS8bJU=
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index 2bba7aa45..27cdd4d38 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -166,6 +166,15 @@ const HintBaseURL = "https://linkerd.io/checks/#"
 // TODO: Make this default value overridiable, e.g. by CLI flag
 const AllowedClockSkew = time.Minute + tls.DefaultClockSkewAllowance
 
+var linkerdHAControlPlaneComponents = []string{
+	"linkerd-controller",
+	"linkerd-destination",
+	"linkerd-identity",
+	"linkerd-proxy-injector",
+	"linkerd-sp-validator",
+	"linkerd-tap",
+}
+
 var (
 	retryWindow    = 5 * time.Second
 	requestTimeout = 30 * time.Second
@@ -1167,11 +1176,43 @@ func (hc *HealthChecker) allCategories() []category {
 						return &SkipError{Reason: "not run for non HA installs"}
 					},
 				},
+				{
+					description:   "multiple replicas of control plane pods",
+					hintAnchor:    "l5d-control-plane-replicas",
+					retryDeadline: hc.RetryDeadline,
+					warning:       true,
+					check: func(ctx context.Context) error {
+						if hc.isHA() {
+							return hc.checkMinReplicasAvailable()
+						}
+						return &SkipError{Reason: "not run for non HA installs"}
+					},
+				},
 			},
 		},
 	}
 }
 
+func (hc *HealthChecker) checkMinReplicasAvailable() error {
+	faulty := []string{}
+
+	for _, component := range linkerdHAControlPlaneComponents {
+		conf, err := hc.kubeAPI.AppsV1().Deployments(hc.ControlPlaneNamespace).Get(component, metav1.GetOptions{})
+		if err != nil {
+			return err
+		}
+
+		if conf.Status.AvailableReplicas <= 1 {
+			faulty = append(faulty, component)
+		}
+	}
+
+	if len(faulty) > 0 {
+		return fmt.Errorf("not enough replicas available for %v", faulty)
+	}
+	return nil
+}
+
 func (hc *HealthChecker) issuerIdentity() string {
 	return fmt.Sprintf("identity.%s.%s", hc.ControlPlaneNamespace, hc.linkerdConfig.Global.IdentityContext.TrustDomain)
 }
diff --git a/web/Dockerfile b/web/Dockerfile
index 6de090f67..439831140 100644
--- a/web/Dockerfile
+++ b/web/Dockerfile
@@ -21,7 +21,7 @@ COPY web/app ./web/app
 RUN ./bin/web build
 
 ## compile go server
-FROM gcr.io/linkerd-io/go-deps:8d2ce4bf as golang
+FROM gcr.io/linkerd-io/go-deps:93ea34a8 as golang
 WORKDIR /linkerd-build
 RUN mkdir -p web
 COPY web/main.go web


Test patch:
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index 1f45532c9..0ce06776e 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -3029,3 +3029,125 @@ func TestCniChecks(t *testing.T) {
 	}
 
 }
+
+func TestMinReplicaCheck(t *testing.T) {
+	hc := NewHealthChecker(
+		[]CategoryID{LinkerdHAChecks},
+		&Options{
+			ControlPlaneNamespace: "linkerd",
+		},
+	)
+
+	var err error
+
+	testCases := []struct {
+		controlPlaneResourceDefs []string
+		expected                 error
+	}{
+		{
+			controlPlaneResourceDefs: generateAllControlPlaneDef(&controlPlaneReplicaOptions{
+				controller:    1,
+				destination:   3,
+				identity:      3,
+				proxyInjector: 3,
+				spValidator:   1,
+				tap:           3,
+			}, t),
+			expected: fmt.Errorf("not enough replicas available for [linkerd-controller linkerd-sp-validator]"),
+		},
+		{
+			controlPlaneResourceDefs: generateAllControlPlaneDef(&controlPlaneReplicaOptions{
+				controller:    3,
+				destination:   2,
+				identity:      1,
+				proxyInjector: 1,
+				spValidator:   0,
+				tap:           3,
+			}, t),
+			expected: fmt.Errorf("not enough replicas available for [linkerd-identity linkerd-proxy-injector linkerd-sp-validator]"),
+		},
+		{
+			controlPlaneResourceDefs: generateAllControlPlaneDef(&controlPlaneReplicaOptions{
+				controller:    3,
+				destination:   2,
+				identity:      2,
+				proxyInjector: 3,
+				spValidator:   2,
+				tap:           3,
+			}, t),
+			expected: nil,
+		},
+	}
+
+	for i, tc := range testCases {
+		tc := tc //pin
+		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+			hc.kubeAPI, err = k8s.NewFakeAPI(tc.controlPlaneResourceDefs...)
+			if err != nil {
+				t.Fatal(err)
+			}
+			err = hc.checkMinReplicasAvailable()
+			if err == nil && tc.expected != nil {
+				t.Log("Expected error: nil")
+				t.Logf("Received error: %s\n", err)
+				t.Fatal("test case failed")
+			}
+			if err != nil {
+				if err.Error() != tc.expected.Error() {
+					t.Logf("Expected error: %s\n", tc.expected)
+					t.Logf("Received error: %s\n", err)
+					t.Fatal("test case failed")
+				}
+			}
+		})
+	}
+}
+
+type controlPlaneReplicaOptions struct {
+	controller    int
+	destination   int
+	identity      int
+	proxyInjector int
+	spValidator   int
+	tap           int
+}
+
+func getSingleControlPlaneDef(component string, availableReplicas int) string {
+	return fmt.Sprintf(`
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: %s
+  namespace: linkerd
+spec:
+  template:
+    spec:
+      containers:
+        - image: "hello-world"
+          name: test
+status:
+  availableReplicas: %d`, component, availableReplicas)
+}
+
+func generateAllControlPlaneDef(replicaOptions *controlPlaneReplicaOptions, t *testing.T) []string {
+	resourceDefs := []string{}
+	for _, component := range linkerdHAControlPlaneComponents {
+		switch component {
+		case "linkerd-controller":
+			resourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.controller))
+		case "linkerd-destination":
+			resourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.destination))
+		case "linkerd-identity":
+			resourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.identity))
+		case "linkerd-sp-validator":
+			resourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.spValidator))
+		case "linkerd-proxy-injector":
+			resourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.proxyInjector))
+		case "linkerd-tap":
+			resourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.tap))
+		default:
+			t.Fatal("Could not find the resource")
+		}
+	}
+	return resourceDefs
+}
diff --git a/test/externalissuer/external_issuer_test.go b/test/externalissuer/external_issuer_test.go
index 12a03e4a2..75e97f7c8 100644
--- a/test/externalissuer/external_issuer_test.go
+++ b/test/externalissuer/external_issuer_test.go
@@ -52,13 +52,12 @@ func verifyInstallApp(t *testing.T) {
 		t.Fatalf("kubectl apply command failed\n%s", out)
 	}
 
-	// wait for deployment to start
 	if err := TestHelper.CheckPods(prefixedNs, TestAppBackendDeploymentName, 1); err != nil {
 		t.Error(err)
 	}
 
-	if err := TestHelper.CheckDeployment(prefixedNs, TestAppBackendDeploymentName, 1); err != nil {
-		t.Error(fmt.Errorf("Error validating deployment [%s]:\n%s", TestAppBackendDeploymentName, err))
+	if err := TestHelper.CheckPods(prefixedNs, "slow-cooker", 1); err != nil {
+		t.Error(err)
 	}
 }
 
diff --git a/test/install_test.go b/test/install_test.go
index a049475f0..2dcfdee30 100644
--- a/test/install_test.go
+++ b/test/install_test.go
@@ -334,15 +334,14 @@ func TestInstallOrUpgradeCli(t *testing.T) {
 // These need to be updated (if there are changes) once a new stable is released
 func helmOverridesStable(root *tls.CA) []string {
 	return []string{
-		"--set", "Namespace=" + TestHelper.GetLinkerdNamespace(),
-		"--set", "ControllerLogLevel=debug",
-		"--set", "LinkerdVersion=" + TestHelper.UpgradeHelmFromVersion(),
-		"--set", "Proxy.Image.Version=" + TestHelper.UpgradeHelmFromVersion(),
-		"--set", "Identity.TrustDomain=cluster.local",
-		"--set", "Identity.TrustAnchorsPEM=" + root.Cred.Crt.EncodeCertificatePEM(),
-		"--set", "Identity.Issuer.TLS.CrtPEM=" + root.Cred.Crt.EncodeCertificatePEM(),
-		"--set", "Identity.Issuer.TLS.KeyPEM=" + root.Cred.EncodePrivateKeyPEM(),
-		"--set", "Identity.Issuer.CrtExpiry=" + root.Cred.Crt.Certificate.NotAfter.Format(time.RFC3339),
+		"--set", "controllerLogLevel=debug",
+		"--set", "global.linkerdVersion=" + TestHelper.UpgradeHelmFromVersion(),
+		"--set", "global.proxy.image.version=" + TestHelper.UpgradeHelmFromVersion(),
+		"--set", "global.identityTrustDomain=cluster.local",
+		"--set", "global.identityTrustAnchorsPEM=" + root.Cred.Crt.EncodeCertificatePEM(),
+		"--set", "identity.issuer.tls.crtPEM=" + root.Cred.Crt.EncodeCertificatePEM(),
+		"--set", "identity.issuer.tls.keyPEM=" + root.Cred.EncodePrivateKeyPEM(),
+		"--set", "identity.issuer.crtExpiry=" + root.Cred.Crt.Certificate.NotAfter.Format(time.RFC3339),
 	}
 }