Repository: linkerd/linkerd2
Pull request #7574 (2022-01-07 23:03:40)

Add release instructions
Add instructions for releasing Linkerd

Signed-off-by: Alex Leong <alex@buoyant.io>

<!--  Thanks for sending a pull request!

If you already have a well-structured git commit message, chances are GitHub
set the title and description of this PR to the git commit message subject and
body, respectively. If so, you may delete these instructions and submit your PR.

If this is your first time, please read our contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md

The title and description of your Pull Request should match the git commit
subject and body, respectively. Git commit messages are structured as follows:

```
Subject

Problem

Solution

Validation

Fixes #[GitHub issue ID]

DCO Sign off
```

Example git commit message:

```
Introduce Pull Request Template

GitHub's community guidelines recommend a pull request template, the repo was
lacking one.

Introduce a `PULL_REQUEST_TEMPLATE.md` file.

Once merged, the
[Community profile checklist](https://github.com/linkerd/linkerd2/community)
should indicate the repo now provides a pull request template.

Fixes #3321

Signed-off-by: Jane Smith <jane.smith@example.com>
```

Note the git commit message subject becomes the pull request title.

For more details around git commits, see the section on Committing in our
contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md#committing
-->


Pothulapati: Hmm, I was following the Wiki until now https://github.com/linkerd/linkerd2/wiki/Linkerd's-Release-Process

Wondering if we should remove the Wiki and keep this? 🤔 

Patch:
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 000000000..153ba7fd4
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,59 @@
+FROM docker.io/golang:1.17-bullseye
+
+# Note: we do *not* delete the apt cache so subsequent steps (like docker,
+# dotfiles) need not pull the cache again. This comes at the cost of a fatter
+# container image.
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt update && apt upgrade -y
+RUN apt install -y \
+    clang \
+    cmake \
+    libssl-dev \
+    lldb \
+    lsb-release \
+    npm \
+    sudo \
+    time
+RUN npm install markdownlint-cli@0.23.1 --global
+
+ARG USER=code
+ARG USER_UID=1000
+ARG USER_GID=1000
+RUN groupadd --gid=$USER_GID $USER \
+    && useradd --uid=$USER_UID --gid=$USER_GID -m $USER \
+    && echo "$USER ALL=(root) NOPASSWD:ALL" >/etc/sudoers.d/$USER \
+    && chmod 0440 /etc/sudoers.d/$USER
+
+# Install a Docker client that uses the host's Docker daemon
+ARG USE_MOBY=false
+ENV DOCKER_BUILDKIT=1
+RUN curl --proto '=https' --tlsv1.3 -vsSfL https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/script-library/docker-debian.sh \
+    | bash -s --  true /var/run/docker-host.sock /var/run/docker.sock "${USER}" "${USE_MOBY}" latest
+
+RUN (echo "LC_ALL=en_US.UTF-8" \
+    && echo "LANGUAGE=en_US.UTF-8") >/etc/default/locale
+
+USER $USER
+ENV USER=$USER
+ENV HOME=/home/$USER
+RUN mkdir -p $HOME/bin
+ENV PATH=$HOME/bin:$PATH
+
+RUN curl --proto '=https' --tlsv1.3 -vsSfLo $HOME/kubectl "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
+    && chmod 755 $HOME/kubectl
+RUN curl --proto '=https' --tlsv1.3 -vsSfL https://raw.githubusercontent.com/rancher/k3d/main/install.sh \
+    | USE_SUDO=false K3D_INSTALL_DIR=$HOME/bin bash
+
+RUN curl --proto '=https' --tlsv1.3 -vsSfL https://sh.rustup.rs \
+    | sh -s -- -y --default-toolchain 1.56.1 -c rustfmt -c clippy -c rls
+
+RUN mkdir /tmp/cargo-deny && cd /tmp/cargo-deny && \
+    curl --proto '=https' --tlsv1.3 -vsSfL https://github.com/EmbarkStudios/cargo-deny/releases/download/0.11.0/cargo-deny-0.11.0-x86_64-unknown-linux-musl.tar.gz | tar zxf - && \
+    mv cargo-deny-0.11.0-x86_64-unknown-linux-musl/cargo-deny $HOME/bin && \
+    cd .. && rm -rf /tmp/cargo-deny
+
+RUN curl --proto '=https' --tlsv1.3 -vsSfL https://run.linkerd.io/install-edge | sh \
+    && ln -s $(readlink ~/.linkerd2/bin/linkerd) ~/bin/linkerd
+
+ENTRYPOINT ["/usr/local/share/docker-init.sh"]
+CMD ["sleep", "infinity"]
diff --git a/.devcontainer/README.md b/.devcontainer/README.md
new file mode 100644
index 000000000..a625e8bbd
--- /dev/null
+++ b/.devcontainer/README.md
@@ -0,0 +1,37 @@
+# devcontainer
+
+This directory provides a _devcontainer_ configuration that configures a
+reproducible development environment for this project.
+
+## Docker
+
+This configuration currently uses the parent host's Docker daemon (rather than
+running a separate docker daemon within in the container). It creates
+devcontainers on the host network so it's easy to use k3d clusters hosted in the
+parent host's docker daemon.
+
+## Customizing
+
+This configuration is supposed to provide a minimal setup without catering to
+any one developer's personal tastes. Devcontainers can be extended with per-user
+configuration.
+
+To add your own extensions to the devcontainer, configure default extensions in
+your VS Code settings:
+
+```jsonc
+    "remote.containers.defaultExtensions": [
+        "eamodio.gitlens",
+        "GitHub.copilot",
+        "GitHub.vscode-pull-request-github",
+        "mutantdino.resourcemonitor",
+        "stateful.edge"
+    ],
+```
+
+Furthermore, you can configure a _dotfiles_ repository to perform customizations
+with a configuration like:
+
+```jsonc
+    "dotfiles.repository": "https://github.com/olix0r/dotfiles.git",
+```
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 000000000..93c60d42e
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,28 @@
+{
+    "name": "linkerd2",
+    "image": "ghcr.io/linkerd/dev:v4",
+    // "dockerFile": "./Dockerfile",
+    "extensions": [
+        "DavidAnson.vscode-markdownlint",
+        "matklad.rust-analyzer",
+        "NathanRidley.autotrim",
+        "samverschueren.final-newline",
+        "streetsidesoftware.code-spell-checker",
+        "tamasfe.even-better-toml",
+        "vadimcn.vscode-lldb",
+        "zxh404.vscode-proto3"
+    ],
+    "runArgs": [
+        "--init",
+        // Use the host network so we can access k3d, etc.
+        "--net=host",
+        // For lldb
+        "--cap-add=SYS_PTRACE",
+        "--security-opt=seccomp=unconfined"
+    ],
+    "overrideCommand": false,
+    "remoteUser": "code",
+    "mounts": [
+        "source=/var/run/docker.sock,target=/var/run/docker-host.sock,type=bind"
+    ]
+}
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
index c75acf8ac..4ee7a9da1 100644
--- a/.github/workflows/codecov.yml
+++ b/.github/workflows/codecov.yml
@@ -50,7 +50,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 15
     container:
-      image: docker://rust:1.56.0
+      image: docker://rust:1.56.1
       options: --security-opt seccomp=unconfined
     steps:
       - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
diff --git a/.github/workflows/policy_controller.yml b/.github/workflows/policy_controller.yml
index a6fb0ca09..949c4e356 100644
--- a/.github/workflows/policy_controller.yml
+++ b/.github/workflows/policy_controller.yml
@@ -24,7 +24,7 @@ jobs:
     timeout-minutes: 5
     runs-on: ubuntu-latest
     container:
-      image: docker://rust:1.56.0
+      image: docker://rust:1.56.1
     steps:
       - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
       - run: rustup component add rustfmt
@@ -50,7 +50,7 @@ jobs:
     timeout-minutes: 5
     runs-on: ubuntu-latest
     container:
-      image: docker://rust:1.56.0
+      image: docker://rust:1.56.1
     steps:
       - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
       - run: rustup component add clippy
@@ -60,7 +60,7 @@ jobs:
     timeout-minutes: 20
     runs-on: ubuntu-latest
     container:
-      image: docker://rust:1.56.0
+      image: docker://rust:1.56.1
     steps:
       - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
       # Check each crate independently to ensure its Cargo.toml is sufficient.
@@ -77,7 +77,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 15
     container:
-      image: docker://rust:1.56.0
+      image: docker://rust:1.56.1
     steps:
       - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
       - run: cargo test --workspace --no-run
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a5e162e12..de55e3907 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -24,7 +24,6 @@ jobs:
         - cni-plugin
         - controller
         - debug
-        - grafana
         - jaeger-webhook
         - metrics-api
         - proxy
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 7c38ebd3c..bed52dc48 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -65,13 +65,13 @@
 - [Salt Security](https://salt.security/)
 - [SCA](https://sca.com.au)
 - [Search365](https://search365.ai/)
+- [Skit](https://skit.ai/)
 - [StackPulse](https://stackpulse.com)
 - [Studyo](https://studyo.co)
 - [Sue](https://sue.nl)
 - [The Zebra](https://www.thezebra.com)
 - [Tradeshift](https://tradeshift.com/)
 - [Transit](https://transit.app)
-- [Vernacular.ai](https://vernacular.ai/)
 - [Web Summit](https://websummit.com)
 - [xCloud](https://www.xbox.com/en-US/xbox-game-streaming/project-xcloud)
 - [YouMail](https://www.youmail.com)
diff --git a/BUILD.md b/BUILD.md
index 1a83d048a..e796b2e98 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -367,8 +367,6 @@ To develop with a webpack dev server:
     - `web` on :7777. This is the golang process that serves the dashboard.
     - `webpack-dev-server` on :8080 to manage rebuilding/reloading of the
       javascript.
-    - `grafana` is port-forwarded from the Kubernetes cluster via `kubectl` on
-      :3000
     - `metrics-api` is port-forwarded from the Kubernetes cluster via `kubectl`
       on :8085
 
diff --git a/CHANGES.md b/CHANGES.md
index ab025a709..a519309e3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,16 @@
 # Changes
 
+## edge-22.1.2
+
+This release sets the version of the extension Helm charts to 30.0.0-edge to
+ensure that previous versions of these charts can be upgraded properly.
+
+* Reset extensions Helm chart versions at 30.0.0-edge
+* Pin multicluster extension pause container version to 3.2 so that it will work
+  on Arm architectures
+* Create a unique PSP `RoleBinding` for each multicluster link to prevent
+  conflicts when PSP is enabled
+
 ## edge-22.1.1
 
 This release adds support for using the cert-manager CA Injector to configure
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 000000000..2bba52d82
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,314 @@
+# Linkerd2 Release
+
+This document contains instructions for releasing Linkerd2.
+
+## 1. Bump the proxy version
+
+Determine the commit SHA of the `linkerd2-proxy` repo to be included in the
+release. If
+[proxy-version](https://github.com/linkerd/linkerd2/blob/main/.proxy-version)
+is already at the desired SHA, skip to step 2.
+
+If updating to `linkerd-proxy` HEAD, note the commit SHA at
+[latest.txt](https://build.l5d.io/linkerd2-proxy/latest.txt) (Look for
+`linkerd2-proxy-<linkerd2-proxy-sha>.tar.gz`).
+
+Create a new branch in the `linkerd2` repo, `username/proxy-version-bump`.
+
+Then run:
+
+```bash
+bin/git-commit-proxy-version <linkerd2-proxy-sha>
+```
+
+The script will update the `.proxy-version` file. Submit a PR to obtain reviews
+and approval.
+
+## 2. Bump the proxy-init version
+
+If the `linkerd2/proxy-init` project has a new release (which is rare), the
+following updates are needed:
+
+- `go.mod`
+
+   ```mod
+   github.com/linkerd/linkerd2-proxy-init v1.2.0
+   ```
+
+- `pkg/version/version.go` (this also implies changes in unit test fixtures)
+
+   ```go
+   var ProxyInitVersion = "v1.2.0"
+   ```
+
+- `charts/linkerd2/values.yaml`
+
+   Upgrade the version in `global.proxyInit.image.version`
+
+Create a new branch in the `linkerd2` repo,
+`username/proxy-init-version-bump`.
+
+Open a pull request that includes the changes.
+
+## 3. Create a minor releases branch
+
+**This step only applies to minor stable releases (e.g. `2.9.1`).**
+
+If it doesn't exist yet, create and push a branch in the `linkerd2` repo where
+all the minor releases for a given major release will reside:
+
+```bash
+git checkout -b release/2.9
+git push -u origin release/2.9
+```
+
+The branch in the following step should be based off of this one.
+
+## 4. Create the release branch
+
+Create a branch in the `linkerd2` repo, `username/edge-X.X.X` (replace with
+your name and the actual release number, optionally replace `edge` with
+`stable`).
+
+## 5. Cherry pick changes from `main`
+
+**This step only applies to minor stable releases (e.g. `2.9.1`).**
+
+Locate all the commits in the git log that happened since the last stable
+release, that you'd like to include in the current minor stable release, and
+cherry-pick each one into the current branch using their corresponding SHAs:
+
+```bash
+git cherry-pick ae34bcc2
+git cherry-pick b34effab
+git cherry-pick 223bd232
+...
+```
+
+Each step might result in conflicts that you'll need to address.
+
+## 6. Update the Helm charts versions
+
+All the Helm charts (linkerd-crds, linkerd-control-plane, linkerd2-cni,
+linkerd-multicluster, linkerd-jaeger and linkerd-viz) have a `version` entry
+with a semver format `major.minor.patch[-edge]` that needs to be updated
+according to the following rules:
+
+- patch bump for minor changes
+- minor bump for additions/removals
+- major bump for backwards-incompatible changes, most notably changes that
+  change the structure of `values.yaml`
+
+Note that edge releases require the `-edge` suffix, which should be removed when
+releasing stable releases.
+
+Also note that the `appVersion` entry (for those charts that have it) is handled
+automatically by CI.
+
+Finally, keep in mind chart version changes require updating the charts README
+files (through `bin/helm-docs`) and golden files (through `go test ./...
+-update`).
+
+### linkerd-crds
+
+Almost all the charts are always updated, at least because the docker image
+versions referred in their templates change with each release. One exception is
+`linkerd-crds`, which doesn't contain image references. So this chart only
+requires bumping its `version` if there were changes in its templates files.
+
+## 7. Update the release notes
+
+On this branch, add the release notes for this version in `CHANGES.md`.
+
+Note: To see all of the changes since the previous release, run the command
+below in the `linkerd2` repo. If the last release was a stable release, be
+sure to use `stable-Y.Y.Y` instead of `edge-Y.Y.Y`.
+
+```bash
+git log edge-Y.Y.Y..HEAD
+```
+
+And this command in the `linkerd2-proxy` repo:
+
+```bash
+git log release/vX.X.X..release/vY.Y.Y
+```
+
+Where `release/vX.X.X` is the version of the proxy from the last release
+and `release/vY.Y.Y` is the version of the proxy for this release, e.g.:
+
+```bash
+git log release/v2.102.0..release/v2.103.0
+```
+
+## 8. Post a PR that includes the changes
+
+If you're preparing a minor release, make sure the PR's merge target is the
+releases branch you created above (e.g. `releases/stable-2.9`). For the other
+cases the target should just be `main`.
+
+This PR needs an approval from a "code owner." Feel free to ping one of the
+code owners if you've gotten feedback and approvals from other team members.
+
+## 9. Optional: push images
+
+To facilitate testing (particularly for stable releases) you might want to
+publish the docker images in your private repo.
+
+First tag the release:
+
+```bash
+git tag stable-2.9.1
+```
+
+Do *not* push this tag just yet, to avoid triggering the actual public release.
+
+Make sure you're logged into your Github docker registry:
+
+```bash
+echo "$GH_PAT" | docker login ghcr.io -u $GH_USERNAME --password-stdin
+```
+
+Where `$GH_USERNAME` is your Github username and `$GH_PAT` is a personal access
+token with enough permissions for creating Github packages.
+
+Then this will build the images and also push them to your personal Github
+docker registry (note this implies you've already set docker buildx in your
+machine, if not follow [these
+instructions](https://github.com/docker/buildx#installing)):
+
+```bash
+DOCKER_REGISTRY=ghcr.io/$GH_USERNAME DOCKER_MULTIARCH=1 DOCKER_PUSH=1 bin/docker-build
+```
+
+If this is the first time you push those images into your personal registry,
+you'll need to go to `https://github.com/$GH_USERNAME?tab=packages` and access
+the settings for each image in order to make them public.
+
+After having successfully pushed those images, delete the tag so you can create
+it again and push it for good as explained in the following step.
+
+Now testers can pull the CLI binaries through this:
+
+```bash
+DOCKER_REGISTRY=ghcr.io/$GH_USERNAME bin/docker-pull-binaries stable-2.9.1
+```
+
+which will dump them under the `target/release` directory.
+
+Besides using those particular binaries to install Linkerd, they'll also need to
+point to your registry using the `--registry` flag. Currently that flag doesn't
+apply to add-ons, so you need to also recur to the `--config` flag. Currently
+that only applies to Grafana:
+
+```bash
+$ cat linkerd-overrides.yml
+grafana:
+  enabled: true
+  image:
+    name: ghcr.io/alpeb/grafana
+
+$ target/release/linkerd2-cli-stable-2.9.1-darwin install --registry ghcr.io/$GH_USERNAME --config ~/tmp/linkerd-overrides.yml
+```
+
+## 10. Merge release notes branch, then create the release tag
+
+After the review has passed and the branches from step 2 and 4 have been merged,
+follow the instructions below to properly create and push the release tag from
+the appropriate branch.
+
+**Note**: The release script will create a GPG-signed tag, so users must have
+GPG signing setup in their local git config.
+
+If performing an edge release then issue these commands. The appropriate tag
+will be automatically calculated:
+
+```bash
+git checkout main
+git pull
+./bin/create-release-tag edge
+```
+
+If performing a stable release then issue these commands instead (in this case
+you need to explicitly pass the version to be released):
+
+```bash
+git checkout main
+git pull
+./bin/create-release-tag stable x.x.x
+```
+
+In both cases follow the instructions on screen for pushing the tag upstream.
+
+That will kick off a CI Release workflow run that will:
+
+- Build and push the docker images for the tag that was created
+- Run the k3d integration tests in the Github actions VMs themselves
+- Run a k3d integration test on a separate ARM64 host
+- Create a release in Github, and upload the CLI binaries with their checksums
+- Dispatch an event caught by the website repo that triggers a website rebuild
+  which will update the edge/stable versions in the website
+- Retrieve the installation script from [run.linkerd.io](https://run.linkerd.io)
+  and verify it installs the current version being released
+- Deploy the updated helm charts
+
+You can locate the CI run [here](https://github.com/linkerd/linkerd2/actions).
+
+## 11. Do a walkthrough verification of the release
+
+Go to the release page in Github and check that the notes are properly
+formatted and the files are there.  Download the file for your system along
+with its checksum, verify the checksum matches, and do a basic sanity check:
+
+```bash
+linkerd version
+linkerd install | kubectl apply -f -
+linkerd check
+linkerd viz install | kubectl apply -f -
+linkerd viz check
+linkerd dashboard
+```
+
+- Install linkerd onto your cluster, and run a few CLI commands
+- Open the dashboard and click around
+- Click through to Grafana
+- When finished with this step, you may want to delete your `alias` to prevent
+  confusion later
+
+## 12. Send the announcement email
+
+Send an email to cncf-linkerd-dev@lists.cncf.io,
+cncf-linkerd-users@lists.cncf.io, and cncf-linkerd-announce@lists.cncf.io,
+announcing the release.
+
+Subscribe to these mailing lists if you aren't on them:
+
+- [linkerd-users](https://lists.cncf.io/g/cncf-linkerd-users/join)
+- [linkerd-announce](https://lists.cncf.io/g/cncf-linkerd-announce/join)
+- [linkerd-dev](https://lists.cncf.io/g/cncf-linkerd-dev/join)
+
+Include the full release notes in the email. Liberally apply emoji. ⭐
+
+## 13. Send an announcement to Linkerd Slack's #announcements channel
+
+Ensure that you send a brief summary of the release in Linkerd Slack's
+[#announcement](https://linkerd.slack.com/messages/C0JV5E7BR) channel.
+
+## 14. Add a community page announcement to the website repo
+
+When doing a `stable-X.X.X` be sure to also include an announcement page for
+the Linkerd2 dashboard "Community" sidebar button.
+
+In the [website](https://github.com/linkerd/website) repo:
+
+1. Run `hugo new --contentDir linkerd.io/content/dashboard/ YYYYMMDD.md`
+2. Open the newly created file in your favorite editor and change the `title`
+   to match the announcement email title.
+3. Remove the `draft: true` section in the file and then add a brief summary
+   of the stable release.
+4. cd to the directory `linkerd.io`.
+5. Run `hugo serve` in the directory to test your changes.
+6. Verify your change by navigating to `http://localhost:1313/dashboard`. Make
+   sure that the announcement appears at the top of the page.
+7. Once you are satisfied with your changes, Post the PR for review.
+   Once merged, the change should deploy automatically.
diff --git a/bin/_docker.sh b/bin/_docker.sh
index 483467ea1..b475ffc54 100644
--- a/bin/_docker.sh
+++ b/bin/_docker.sh
@@ -35,7 +35,6 @@ export DOCKER_IMAGES=(${DOCKER_IMAGES:-
     policy-controller
     metrics-api
     debug
-    grafana
     proxy
     web
     jaeger-webhook
diff --git a/bin/docker-build b/bin/docker-build
index a8219e047..853c3b3ed 100755
--- a/bin/docker-build
+++ b/bin/docker-build
@@ -20,7 +20,6 @@ if [ -z "${LINKERD_LOCAL_BUILD_CLI:-}" ]; then
 else
     "$bindir"/build-cli-bin
 fi
-"$bindir"/docker-build-grafana
 "$bindir"/docker-build-jaeger-webhook
 "$bindir"/docker-build-metrics-api
 "$bindir"/docker-build-tap
diff --git a/bin/docker-build-grafana b/bin/docker-build-grafana
deleted file mode 100755
index 1ed794ce2..000000000
--- a/bin/docker-build-grafana
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-
-set -eu
-
-if [ $# -ne 0 ]; then
-    echo "no arguments allowed for ${0##*/}, given: $*" >&2
-    exit 64
-fi
-
-bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
-rootdir=$( cd "$bindir"/.. && pwd )
-
-# shellcheck source=_docker.sh
-. "$bindir"/_docker.sh
-# shellcheck source=_tag.sh
-. "$bindir"/_tag.sh
-
-dockerfile=$rootdir/grafana/Dockerfile
-
-docker_build grafana "${TAG:-$(head_root_tag)}" "$dockerfile"
diff --git a/bin/markdownlint b/bin/markdownlint
index afd8f8dd4..039a4d39c 100755
--- a/bin/markdownlint
+++ b/bin/markdownlint
@@ -4,16 +4,17 @@ set -eu
 
 bindir=$( cd "${0%/*}" && pwd )
 rootdir=$( cd "$bindir"/.. && pwd )
-markdownlintbin="$rootdir/node_modules/.bin/markdownlint"
 
-markdownlint_version=0.23.1
+version=0.23.1
 
-if [ ! -x "$markdownlintbin" ] || [ "$($markdownlintbin -V)" != $markdownlint_version ]; then
+export PATH="$rootdir/node_modules/.bin:$PATH"
+
+if ! command -v markdownlint >/dev/null || [ "$(markdownlint -V)" != "$version" ]; then
     if ! [ -x "$(command -v npm)" ]; then
-        echo 'Error: npm required to install markdownlint command'
+        echo 'Error: npm required to install markdownlint command' >&2
         exit 1
     fi
-    npm install markdownlint-cli@$markdownlint_version
+    npm install "markdownlint-cli@$version"
 fi
 
-"$markdownlintbin" "$@"
+markdownlint "$@"
diff --git a/bin/web b/bin/web
index 9e4b96418..f7b9077ba 100755
--- a/bin/web
+++ b/bin/web
@@ -26,17 +26,11 @@ USAGE
 
 check-for-linkerd-and-viz() {
   metrics_api_pod=$(get-pod linkerd-viz metrics-api)
-  grafana_pod=$(get-pod linkerd-viz grafana)
 
   if [[ -z "${metrics_api_pod// }" ]]; then
     err 'Metrics-api is not running. Have you installed Linkerd-Viz?'
     exit 1
   fi
-
-  if [[ -z "${grafana_pod// }" ]]; then
-    err 'Grafana is not running. Have you installed Linkerd-Viz?'
-    exit 1
-  fi
 }
 
 dev() {
@@ -104,7 +98,6 @@ run() {
 
   check-for-linkerd-and-viz && (
     port-forward linkerd-viz metrics-api 8085 &
-    port-forward linkerd-viz grafana 3000 &
   )
 
   cd "$ROOT"/web && \
diff --git a/charts/linkerd2-cni/Chart.yaml b/charts/linkerd2-cni/Chart.yaml
index 2fe14e117..f47f7331f 100644
--- a/charts/linkerd2-cni/Chart.yaml
+++ b/charts/linkerd2-cni/Chart.yaml
@@ -9,5 +9,5 @@ description: |
 kubeVersion: ">=1.20.0-0"
 icon: https://linkerd.io/images/logo-only-200h.png
 name: "linkerd2-cni"
-version: 1.0.0-edge
+version: 30.0.0-edge
 
diff --git a/charts/linkerd2-cni/README.md b/charts/linkerd2-cni/README.md
index 1fe6560b7..ee9741b7e 100644
--- a/charts/linkerd2-cni/README.md
+++ b/charts/linkerd2-cni/README.md
@@ -6,7 +6,7 @@ Linkerd [CNI plugin](https://linkerd.io/2/features/cni/) takes care of setting
 up your pod's network so  incoming and outgoing traffic is proxied through the
 data plane.
 
-![Version: 1.0.0-edge](https://img.shields.io/badge/Version-1.0.0--edge-informational?style=flat-square)
+![Version: 30.0.0-edge](https://img.shields.io/badge/Version-30.0.0--edge-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/go.mod b/go.mod
index 73c24f184..95b9df842 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/clarketm/json v1.15.7
 	github.com/containernetworking/cni v1.0.1
 	github.com/emicklei/proto v1.9.1
-	github.com/evanphx/json-patch v4.12.0+incompatible
+	github.com/evanphx/json-patch v5.6.0+incompatible
 	github.com/fatih/color v1.13.0
 	github.com/fsnotify/fsnotify v1.5.1
 	github.com/ghodss/yaml v1.0.0
diff --git a/go.sum b/go.sum
index 975873d87..cd6f59a0c 100644
--- a/go.sum
+++ b/go.sum
@@ -302,8 +302,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
 github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
+github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
 github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
diff --git a/grafana/Dockerfile b/grafana/Dockerfile
deleted file mode 100644
index 28a3e39ef..000000000
--- a/grafana/Dockerfile
+++ /dev/null
@@ -1,5 +0,0 @@
-FROM grafana/grafana:7.1.5
-
-COPY LICENSE                          /linkerd/LICENSE
-COPY grafana/dashboards               /var/lib/grafana/dashboards
-COPY grafana/dashboards/top-line.json /usr/share/grafana/public/dashboards/home.json
diff --git a/grafana/README.md b/grafana/README.md
new file mode 100644
index 000000000..0d27ebcce
--- /dev/null
+++ b/grafana/README.md
@@ -0,0 +1,43 @@
+# Using Grafana with Linkerd
+
+You can install Grafana in various ways, like using the [Grafana official Helm
+chart](https://github.com/grafana/helm-charts/tree/main/charts/grafana), or the
+[Grafana Operator](https://github.com/grafana-operator/grafana-operator). Hosted
+solutions are also available, like [Grafana
+Cloud](https://grafana.com/products/cloud/).
+
+The file `grafana/values.yaml` provides a default Helm config for the [Grafana
+official Helm
+chart](https://github.com/grafana/helm-charts/tree/main/charts/grafana), which
+pulls the Linkerd dashboards published at
+<https://grafana.com/orgs/linkerd/dashboards>.
+
+You can install the chart like this:
+
+```shell
+helm repo add grafana https://grafana.github.io/helm-charts
+helm install grafana -n grafana --create-namespace grafana/grafana \
+  -f https://raw.githubusercontent.com/linkerd/linkerd2/main/grafana/values.yaml
+```
+
+Please make sure to update the entries in `grafana/values.yaml` before using the
+file; in particular:
+
+- auth and log settings under `grafana.ini`
+- `datasources.datasources.yaml.datasources[0].url` should point to your
+  Prometheus service
+
+The other installation methods can easily import those same dashboards using
+their IDs, as listed in `grafana/values.yaml`.
+
+In order to have the Linkerd Viz Dashboard show the Grafana icon there where
+relevant, and have it link to the appropriate Grafana dashboard, make sure you
+have a proper location set up in the `grafana.url` setting in Linkerd Viz's
+`values.yaml`.
+
+## Note to developers
+
+The `grafana/dashboards` directory contains the same dashboard definitions
+published under <https://grafana.com/orgs/linkerd>. Please keep them in sync when
+making any changes. After logging into grafana.com (using the linkerd account),
+the dashboards can be managed under Org Settings -> My Dashboards.
diff --git a/grafana/dashboards/authority.json b/grafana/dashboards/authority.json
index a02bea67c..9c06a8b1b 100644
--- a/grafana/dashboards/authority.json
+++ b/grafana/dashboards/authority.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/cronjob.json b/grafana/dashboards/cronjob.json
index 2aabdf6ea..e646ec424 100644
--- a/grafana/dashboards/cronjob.json
+++ b/grafana/dashboards/cronjob.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
     "annotations": {
       "list": [
         {
diff --git a/grafana/dashboards/daemonset.json b/grafana/dashboards/daemonset.json
index 3d8db5a5b..b9d85e329 100644
--- a/grafana/dashboards/daemonset.json
+++ b/grafana/dashboards/daemonset.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/deployment.json b/grafana/dashboards/deployment.json
index 5a972c067..97234c6e6 100644
--- a/grafana/dashboards/deployment.json
+++ b/grafana/dashboards/deployment.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/grafana.json b/grafana/dashboards/grafana.json
deleted file mode 100644
index 2012d3768..000000000
--- a/grafana/dashboards/grafana.json
+++ /dev/null
@@ -1,1082 +0,0 @@
-{
-  "annotations": {
-    "list": [
-      {
-        "builtIn": 1,
-        "datasource": "-- Grafana --",
-        "enable": true,
-        "hide": true,
-        "iconColor": "rgba(0, 211, 255, 1)",
-        "name": "Annotations & Alerts",
-        "type": "dashboard"
-      }
-    ]
-  },
-  "description": "Metrics about Grafana",
-  "editable": true,
-  "gnetId": null,
-  "graphTooltip": 0,
-  "id": null,
-  "links": [
-    {
-      "icon": "external link",
-      "tags": [],
-      "targetBlank": true,
-      "title": "Available metrics",
-      "type": "link",
-      "url": "/metrics"
-    },
-    {
-      "icon": "external link",
-      "tags": [],
-      "title": "Grafana docs",
-      "type": "link",
-      "url": "http://docs.grafana.org/"
-    },
-    {
-      "icon": "external link",
-      "tags": [],
-      "title": "Prometheus docs",
-      "type": "link",
-      "url": "http://prometheus.io/docs/introduction/overview/"
-    }
-  ],
-  "panels": [
-    {
-      "cacheTimeout": null,
-      "colorBackground": false,
-      "colorValue": true,
-      "colors": [
-        "rgba(222, 3, 3, 0.9)",
-        "rgb(234, 245, 234)",
-        "rgb(235, 244, 235)"
-      ],
-      "datasource": "prometheus",
-      "decimals": 0,
-      "format": "none",
-      "gauge": {
-        "maxValue": 100,
-        "minValue": 0,
-        "show": false,
-        "thresholdLabels": false,
-        "thresholdMarkers": true
-      },
-      "gridPos": {
-        "h": 5,
-        "w": 4,
-        "x": 0,
-        "y": 0
-      },
-      "id": 4,
-      "interval": null,
-      "links": [],
-      "mappingType": 1,
-      "mappingTypes": [
-        {
-          "name": "value to text",
-          "value": 1
-        },
-        {
-          "name": "range to text",
-          "value": 2
-        }
-      ],
-      "maxDataPoints": 100,
-      "nullPointMode": "connected",
-      "nullText": null,
-      "options": {},
-      "postfix": "",
-      "postfixFontSize": "50%",
-      "prefix": "",
-      "prefixFontSize": "50%",
-      "rangeMaps": [
-        {
-          "from": "null",
-          "text": "N/A",
-          "to": "null"
-        }
-      ],
-      "sparkline": {
-        "fillColor": "rgba(31, 118, 189, 0.18)",
-        "full": false,
-        "lineColor": "rgb(31, 120, 193)",
-        "show": false
-      },
-      "tableColumn": "",
-      "targets": [
-        {
-          "expr": "up{job=\"grafana\"}",
-          "format": "time_series",
-          "instant": true,
-          "intervalFactor": 2,
-          "refId": "A",
-          "step": 60
-        }
-      ],
-      "thresholds": "1, 10000",
-      "title": "Active instances",
-      "type": "singlestat",
-      "valueFontSize": "120%",
-      "valueMaps": [
-        {
-          "op": "=",
-          "text": ":(",
-          "value": "0"
-        }
-      ],
-      "valueName": "avg"
-    },
-    {
-      "cacheTimeout": null,
-      "colorBackground": false,
-      "colorValue": false,
-      "colors": [
-        "rgba(245, 54, 54, 0.9)",
-        "rgba(237, 129, 40, 0.89)",
-        "rgba(50, 172, 45, 0.97)"
-      ],
-      "datasource": "prometheus",
-      "format": "none",
-      "gauge": {
-        "maxValue": 100,
-        "minValue": 0,
-        "show": false,
-        "thresholdLabels": false,
-        "thresholdMarkers": true
-      },
-      "gridPos": {
-        "h": 5,
-        "w": 4,
-        "x": 4,
-        "y": 0
-      },
-      "id": 8,
-      "interval": null,
-      "links": [],
-      "mappingType": 1,
-      "mappingTypes": [
-        {
-          "name": "value to text",
-          "value": 1
-        },
-        {
-          "name": "range to text",
-          "value": 2
-        }
-      ],
-      "maxDataPoints": 100,
-      "nullPointMode": "connected",
-      "nullText": null,
-      "options": {},
-      "postfix": "",
-      "postfixFontSize": "50%",
-      "prefix": "",
-      "prefixFontSize": "50%",
-      "rangeMaps": [
-        {
-          "from": "null",
-          "text": "N/A",
-          "to": "null"
-        }
-      ],
-      "sparkline": {
-        "fillColor": "rgba(31, 118, 189, 0.18)",
-        "full": false,
-        "lineColor": "rgb(31, 120, 193)",
-        "show": false
-      },
-      "tableColumn": "",
-      "targets": [
-        {
-          "expr": "grafana_stat_totals_dashboard",
-          "format": "time_series",
-          "instant": true,
-          "intervalFactor": 2,
-          "refId": "A",
-          "step": 60
-        }
-      ],
-      "thresholds": "",
-      "title": "Dashboard count",
-      "type": "singlestat",
-      "valueFontSize": "120%",
-      "valueMaps": [
-        {
-          "op": "=",
-          "text": "N/A",
-          "value": "null"
-        }
-      ],
-      "valueName": "avg"
-    },
-    {
-      "cacheTimeout": null,
-      "colorBackground": false,
-      "colorValue": false,
-      "colors": [
-        "rgba(245, 54, 54, 0.9)",
-        "rgba(237, 129, 40, 0.89)",
-        "rgba(50, 172, 45, 0.97)"
-      ],
-      "datasource": "prometheus",
-      "format": "none",
-      "gauge": {
-        "maxValue": 100,
-        "minValue": 0,
-        "show": false,
-        "thresholdLabels": false,
-        "thresholdMarkers": true
-      },
-      "gridPos": {
-        "h": 5,
-        "w": 4,
-        "x": 8,
-        "y": 0
-      },
-      "id": 9,
-      "interval": null,
-      "links": [],
-      "mappingType": 1,
-      "mappingTypes": [
-        {
-          "name": "value to text",
-          "value": 1
-        },
-        {
-          "name": "range to text",
-          "value": 2
-        }
-      ],
-      "maxDataPoints": 100,
-      "nullPointMode": "connected",
-      "nullText": null,
-      "options": {},
-      "postfix": "",
-      "postfixFontSize": "50%",
-      "prefix": "",
-      "prefixFontSize": "50%",
-      "rangeMaps": [
-        {
-          "from": "null",
-          "text": "N/A",
-          "to": "null"
-        }
-      ],
-      "sparkline": {
-        "fillColor": "rgba(31, 118, 189, 0.18)",
-        "full": false,
-        "lineColor": "rgb(31, 120, 193)",
-        "show": false
-      },
-      "tableColumn": "",
-      "targets": [
-        {
-          "expr": "grafana_stat_total_users",
-          "format": "time_series",
-          "instant": true,
-          "intervalFactor": 2,
-          "refId": "A",
-          "step": 60
-        }
-      ],
-      "thresholds": "",
-      "title": "User count",
-      "type": "singlestat",
-      "valueFontSize": "120%",
-      "valueMaps": [
-        {
-          "op": "=",
-          "text": "N/A",
-          "value": "null"
-        }
-      ],
-      "valueName": "avg"
-    },
-    {
-      "cacheTimeout": null,
-      "colorBackground": false,
-      "colorValue": false,
-      "colors": [
-        "rgba(245, 54, 54, 0.9)",
-        "rgba(237, 129, 40, 0.89)",
-        "rgba(50, 172, 45, 0.97)"
-      ],
-      "datasource": "prometheus",
-      "format": "none",
-      "gauge": {
-        "maxValue": 100,
-        "minValue": 0,
-        "show": false,
-        "thresholdLabels": false,
-        "thresholdMarkers": true
-      },
-      "gridPos": {
-        "h": 5,
-        "w": 4,
-        "x": 12,
-        "y": 0
-      },
-      "id": 10,
-      "interval": null,
-      "links": [],
-      "mappingType": 1,
-      "mappingTypes": [
-        {
-          "name": "value to text",
-          "value": 1
-        },
-        {
-          "name": "range to text",
-          "value": 2
-        }
-      ],
-      "maxDataPoints": 100,
-      "nullPointMode": "connected",
-      "nullText": null,
-      "options": {},
-      "postfix": "",
-      "postfixFontSize": "50%",
-      "prefix": "",
-      "prefixFontSize": "50%",
-      "rangeMaps": [
-        {
-          "from": "null",
-          "text": "N/A",
-          "to": "null"
-        }
-      ],
-      "sparkline": {
-        "fillColor": "rgba(31, 118, 189, 0.18)",
-        "full": false,
-        "lineColor": "rgb(31, 120, 193)",
-        "show": false
-      },
-      "tableColumn": "",
-      "targets": [
-        {
-          "expr": "grafana_stat_total_playlists",
-          "format": "time_series",
-          "instant": true,
-          "intervalFactor": 2,
-          "refId": "A",
-          "step": 60
-        }
-      ],
-      "thresholds": "",
-      "title": "Playlist count",
-      "type": "singlestat",
-      "valueFontSize": "120%",
-      "valueMaps": [
-        {
-          "op": "=",
-          "text": "N/A",
-          "value": "null"
-        }
-      ],
-      "valueName": "avg"
-    },
-    {
-      "columns": [],
-      "datasource": "prometheus",
-      "fontSize": "100%",
-      "gridPos": {
-        "h": 5,
-        "w": 4,
-        "x": 16,
-        "y": 0
-      },
-      "id": 17,
-      "links": [],
-      "options": {},
-      "pageSize": null,
-      "scroll": false,
-      "showHeader": true,
-      "sort": {
-        "col": 0,
-        "desc": true
-      },
-      "styles": [
-        {
-          "alias": "Time",
-          "dateFormat": "YYYY-MM-DD HH:mm:ss",
-          "link": false,
-          "pattern": "Time",
-          "type": "hidden"
-        },
-        {
-          "alias": "",
-          "colorMode": null,
-          "colors": [
-            "rgba(245, 54, 54, 0.9)",
-            "rgba(237, 129, 40, 0.89)",
-            "rgba(50, 172, 45, 0.97)"
-          ],
-          "decimals": 0,
-          "pattern": "/.*/",
-          "thresholds": [],
-          "type": "number",
-          "unit": "short"
-        }
-      ],
-      "targets": [
-        {
-          "expr": "topk(1, grafana_info)",
-          "format": "time_series",
-          "instant": true,
-          "intervalFactor": 2,
-          "legendFormat": "{{version}}",
-          "refId": "A",
-          "step": 20
-        }
-      ],
-      "title": "Grafana version",
-      "transform": "timeseries_to_rows",
-      "type": "table"
-    },
-    {
-      "content": "<br />\n<br />\n<center>\n<img src=\"public/img/grafana_icon.svg\"  style=\"height: 60px;\">\n<span style=\"color: red;font-size:3rem;line-height: 2rem;\">💖</span>\n<img src=\"public/app/plugins/datasource/prometheus/img/prometheus_logo.svg\" alt=\"Prometheus logo\" style=\"height: 60px;\">\n</center>",
-      "gridPos": {
-        "h": 5,
-        "w": 4,
-        "x": 20,
-        "y": 0
-      },
-      "id": 16,
-      "links": [],
-      "mode": "html",
-      "options": {},
-      "title": "",
-      "transparent": true,
-      "type": "text"
-    },
-    {
-      "aliasColors": {
-        "400": "#447EBC",
-        "500": "#BF1B00"
-      },
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "prometheus",
-      "fill": 1,
-      "gridPos": {
-        "h": 10,
-        "w": 10,
-        "x": 0,
-        "y": 5
-      },
-      "height": "",
-      "id": 15,
-      "legend": {
-        "avg": false,
-        "current": false,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "nullPointMode": "null as zero",
-      "options": {},
-      "percentage": false,
-      "pointradius": 5,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [
-        {}
-      ],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": "sum by (statuscode) (irate(http_request_total{job='grafana'}[5m]))",
-          "format": "time_series",
-          "intervalFactor": 3,
-          "legendFormat": "{{statuscode}}",
-          "refId": "B",
-          "step": 15,
-          "target": "dev.grafana.cb-office.alerting.active_alerts"
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "http status codes",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        },
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "aliasColors": {
-        "400": "#447EBC",
-        "500": "#BF1B00"
-      },
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "prometheus",
-      "fill": 1,
-      "gridPos": {
-        "h": 10,
-        "w": 10,
-        "x": 10,
-        "y": 5
-      },
-      "height": "",
-      "id": 11,
-      "legend": {
-        "avg": false,
-        "current": false,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "nullPointMode": "null as zero",
-      "options": {},
-      "percentage": false,
-      "pointradius": 5,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [
-        {}
-      ],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": "sum(irate(grafana_api_response_status_total[5m]))",
-          "format": "time_series",
-          "intervalFactor": 4,
-          "legendFormat": "api",
-          "refId": "A",
-          "step": 20
-        },
-        {
-          "expr": "sum(irate(grafana_proxy_response_status_total[5m]))",
-          "format": "time_series",
-          "intervalFactor": 4,
-          "legendFormat": "proxy",
-          "refId": "B",
-          "step": 20
-        },
-        {
-          "expr": "sum(irate(grafana_page_response_status_total[5m]))",
-          "format": "time_series",
-          "intervalFactor": 4,
-          "legendFormat": "web",
-          "refId": "C",
-          "step": 20
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Requests by routing group",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        },
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "columns": [],
-      "datasource": "prometheus",
-      "fontSize": "100%",
-      "gridPos": {
-        "h": 10,
-        "w": 4,
-        "x": 20,
-        "y": 5
-      },
-      "height": "",
-      "id": 12,
-      "links": [],
-      "options": {},
-      "pageSize": null,
-      "scroll": true,
-      "showHeader": true,
-      "sort": {
-        "col": 0,
-        "desc": true
-      },
-      "styles": [
-        {
-          "alias": "Time",
-          "dateFormat": "YYYY-MM-DD HH:mm:ss",
-          "link": false,
-          "pattern": "Time",
-          "type": "hidden"
-        },
-        {
-          "alias": "",
-          "colorMode": null,
-          "colors": [
-            "rgba(245, 54, 54, 0.9)",
-            "rgba(237, 129, 40, 0.89)",
-            "rgba(50, 172, 45, 0.97)"
-          ],
-          "decimals": 0,
-          "pattern": "/.*/",
-          "thresholds": [],
-          "type": "number",
-          "unit": "short"
-        }
-      ],
-      "targets": [
-        {
-          "expr": "sort(topk(8, sum by (handler) (http_request_total{job=\"grafana\"})))",
-          "format": "time_series",
-          "instant": true,
-          "intervalFactor": 10,
-          "legendFormat": "{{handler}}",
-          "refId": "A",
-          "step": 100
-        }
-      ],
-      "title": "Most used handlers",
-      "transform": "timeseries_to_rows",
-      "type": "table"
-    },
-    {
-      "aliasColors": {
-        "alerting": "#890F02",
-        "ok": "#7EB26D"
-      },
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "prometheus",
-      "fill": 1,
-      "gridPos": {
-        "h": 5,
-        "w": 12,
-        "x": 0,
-        "y": 15
-      },
-      "id": 6,
-      "legend": {
-        "avg": false,
-        "current": false,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "nullPointMode": "null",
-      "options": {},
-      "percentage": false,
-      "pointradius": 5,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [],
-      "spaceLength": 10,
-      "stack": true,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": "increase(grafana_alerting_active_alerts[1m])",
-          "format": "time_series",
-          "intervalFactor": 3,
-          "legendFormat": "{{state}}",
-          "refId": "A",
-          "step": 15
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Grafana active alerts",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        },
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "aliasColors": {
-        "alerting": "#890F02",
-        "alertname": "#BF1B00",
-        "firing alerts": "#BF1B00",
-        "ok": "#7EB26D"
-      },
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "prometheus",
-      "fill": 1,
-      "gridPos": {
-        "h": 5,
-        "w": 12,
-        "x": 12,
-        "y": 15
-      },
-      "id": 18,
-      "legend": {
-        "avg": false,
-        "current": false,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "nullPointMode": "null",
-      "options": {},
-      "percentage": false,
-      "pointradius": 5,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [
-        {
-          "alias": "Firing alerts",
-          "yaxis": 1
-        }
-      ],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": " sum (ALERTS)",
-          "format": "time_series",
-          "intervalFactor": 3,
-          "legendFormat": "firing alerts",
-          "refId": "A",
-          "step": 15
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Prometheus alerts",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        },
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "aliasColors": {},
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "prometheus",
-      "description": "Aggregated over all Grafana nodes.",
-      "fill": 1,
-      "gridPos": {
-        "h": 7,
-        "w": 24,
-        "x": 0,
-        "y": 20
-      },
-      "id": 7,
-      "legend": {
-        "avg": false,
-        "current": false,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "nullPointMode": "null",
-      "options": {},
-      "percentage": false,
-      "pointradius": 5,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [
-        {
-          "alias": "avg gc duration",
-          "yaxis": 2
-        },
-        {
-          "alias": "127.0.0.1:3000",
-          "yaxis": 1
-        },
-        {
-          "alias": "allocated memory",
-          "yaxis": 2
-        },
-        {
-          "alias": "used memory",
-          "yaxis": 2
-        },
-        {
-          "alias": "memory usage",
-          "yaxis": 2
-        }
-      ],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": "sum(go_goroutines{job=\"grafana\"})",
-          "format": "time_series",
-          "hide": false,
-          "intervalFactor": 4,
-          "legendFormat": "go routines",
-          "refId": "A",
-          "step": 8,
-          "target": "select metric",
-          "type": "timeserie"
-        },
-        {
-          "expr": "sum(process_resident_memory_bytes{job=\"grafana\"})",
-          "format": "time_series",
-          "intervalFactor": 4,
-          "legendFormat": "memory usage",
-          "refId": "B",
-          "step": 8
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Grafana performance",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        },
-        {
-          "format": "decbytes",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "content": "<div>\n  <div style=\"position: absolute; top: 0, left: 0\">\n    <a href=\"https://linkerd.io\" target=\"_blank\"><img src=\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\" style=\"height: 30px;\"></a>\n  </div>\n  <div id=\"version\" style=\"position: absolute; top: 0; right: 0; font-size: 15px\">\n  </div>\n</div>\n<script type=\"text/javascript\">\nvar localReqURL =\n  window.location.href.substring(\n    0,\n    window.location.href.indexOf(\n    \"/grafana/\"\n    )\n  )+'/overview';\n\nfetch(localReqURL, {\n  credentials: 'include',\n  headers: {\n    \"Content-Type\": \"text/html; charset=utf-8\",\n  },\n})\n.then(response => response.text())\n.then(text => (new window.DOMParser()).parseFromString(text, \"text/html\"))\n.then(html => {\n  var main = html.getElementById('main');\n  var localVersion = main.getAttribute(\"data-release-version\");\n  var versionElem = document.getElementById('version');\n\n  var channel;\n  var parts = localVersion.split(\"-\", 2);\n  if (parts.length === 2) {\n    channel = parts[0];\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\n  } else {\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\n  }\n  var uuid = main.getAttribute(\"data-uuid\");\n\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\n    credentials: 'include',\n    headers: {\n      \"Content-Type\": \"application/json; charset=utf-8\",\n    },\n  })\n  .then(response => response.json())\n  .then(json => {\n    if (!channel || !json[channel]) {\n      versionElem.innerHTML += 'Version check failed.'\n    } else if (json[channel] === localVersion) {\n      versionElem.innerHTML += 'Linkerd is up to date.';\n    } else {\n      parts = json[channel].split(\"-\", 2);\n      if (parts.length === 2) {\n        versionElem.innerHTML += \"A new \"+parts[0]+\" version (\"+parts[1]+\") is available.\"\n      } else {\n        versionElem.innerHTML += \"A new version (\"+json[channel]+\") is available.\"\n      }\n      versionElem.innerHTML += \" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\";\n    }\n  });\n});\n</script>",
-      "gridPos": {
-        "h": 3,
-        "w": 24,
-        "x": 0,
-        "y": 27
-      },
-      "height": "1px",
-      "id": 171,
-      "links": [],
-      "mode": "html",
-      "options": {},
-      "title": "",
-      "transparent": true,
-      "type": "text"
-    }
-  ],
-  "revision": "1.0",
-  "schemaVersion": 18,
-  "style": "dark",
-  "tags": [
-    "grafana",
-    "prometheus"
-  ],
-  "templating": {
-    "list": []
-  },
-  "time": {
-    "from": "now-6h",
-    "to": "now"
-  },
-  "timepicker": {
-    "refresh_intervals": [
-      "5s",
-      "10s",
-      "30s",
-      "1m",
-      "5m",
-      "15m",
-      "30m",
-      "1h",
-      "2h",
-      "1d"
-    ],
-    "time_options": [
-      "5m",
-      "15m",
-      "1h",
-      "6h",
-      "12h",
-      "24h",
-      "2d",
-      "7d",
-      "30d"
-    ]
-  },
-  "timezone": "",
-  "title": "Grafana metrics",
-  "uid": "grafana-metrics",
-  "version": 1
-}
diff --git a/grafana/dashboards/health.json b/grafana/dashboards/health.json
index 6e2f6f7b1..ed0a8e5e4 100644
--- a/grafana/dashboards/health.json
+++ b/grafana/dashboards/health.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/job.json b/grafana/dashboards/job.json
index f5b64aeab..1765af1f4 100644
--- a/grafana/dashboards/job.json
+++ b/grafana/dashboards/job.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/kubernetes.json b/grafana/dashboards/kubernetes.json
index 710b5175a..9b5a4ee25 100644
--- a/grafana/dashboards/kubernetes.json
+++ b/grafana/dashboards/kubernetes.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
@@ -2244,4 +2291,4 @@
   "title": "Kubernetes cluster monitoring (via Prometheus)",
   "uid": "k8s",
   "version": 1
-}
\ No newline at end of file
+}
diff --git a/grafana/dashboards/multicluster.json b/grafana/dashboards/multicluster.json
index d473f2388..aca0b5e6b 100644
--- a/grafana/dashboards/multicluster.json
+++ b/grafana/dashboards/multicluster.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
     "annotations": {
         "list": [
             {
diff --git a/grafana/dashboards/namespace.json b/grafana/dashboards/namespace.json
index 3d6a459ab..e0c56b632 100644
--- a/grafana/dashboards/namespace.json
+++ b/grafana/dashboards/namespace.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/pod.json b/grafana/dashboards/pod.json
index 1f99773c3..d66378319 100644
--- a/grafana/dashboards/pod.json
+++ b/grafana/dashboards/pod.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/prometheus-benchmark.json b/grafana/dashboards/prometheus-benchmark.json
index d05cf1e75..09b8631da 100644
--- a/grafana/dashboards/prometheus-benchmark.json
+++ b/grafana/dashboards/prometheus-benchmark.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/prometheus.json b/grafana/dashboards/prometheus.json
index fe9dad4cb..ec881687e 100644
--- a/grafana/dashboards/prometheus.json
+++ b/grafana/dashboards/prometheus.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/replicaset.json b/grafana/dashboards/replicaset.json
index 6c92e1d18..87e787913 100644
--- a/grafana/dashboards/replicaset.json
+++ b/grafana/dashboards/replicaset.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
     "annotations": {
       "list": [
         {
diff --git a/grafana/dashboards/replicationcontroller.json b/grafana/dashboards/replicationcontroller.json
index df3db574d..318359d55 100644
--- a/grafana/dashboards/replicationcontroller.json
+++ b/grafana/dashboards/replicationcontroller.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/route.json b/grafana/dashboards/route.json
index 3e6ea96a3..60223f308 100644
--- a/grafana/dashboards/route.json
+++ b/grafana/dashboards/route.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
     "annotations": {
       "list": [
         {
@@ -1259,4 +1306,4 @@
     "uid": "route",
     "version": 1
   }
-  
\ No newline at end of file
+  
diff --git a/grafana/dashboards/service.json b/grafana/dashboards/service.json
index db517460f..378e75fa1 100644
--- a/grafana/dashboards/service.json
+++ b/grafana/dashboards/service.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/statefulset.json b/grafana/dashboards/statefulset.json
index f699912b9..13a82730c 100644
--- a/grafana/dashboards/statefulset.json
+++ b/grafana/dashboards/statefulset.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/dashboards/top-line.json b/grafana/dashboards/top-line.json
index d9181a2b6..1d3c1deaf 100644
--- a/grafana/dashboards/top-line.json
+++ b/grafana/dashboards/top-line.json
@@ -1,4 +1,51 @@
 {
+  "__inputs": [
+  ],
+  "__elements": [],
+  "__requires": [
+    {
+      "type": "panel",
+      "id": "gauge",
+      "name": "Gauge",
+      "version": ""
+    },
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "8.3.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph (old)",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "heatmap",
+      "name": "Heatmap",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "prometheus",
+      "name": "Prometheus",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "stat",
+      "name": "Stat",
+      "version": ""
+    },
+    {
+      "type": "panel",
+      "id": "text",
+      "name": "Text",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
diff --git a/grafana/values.yaml b/grafana/values.yaml
new file mode 100644
index 000000000..3ff2ee39f
--- /dev/null
+++ b/grafana/values.yaml
@@ -0,0 +1,125 @@
+podAnnotations:
+  linkerd.io/inject: enabled
+
+grafana.ini:
+  server:
+    root_url: '%(protocol)s://%(domain)s:/grafana/'
+  auth:
+    disable_login_form: true
+  auth.anonymous:
+    enabled: true
+    org_role: Editor
+  auth.basic:
+    enabled: false
+  analytics:
+    check_for_updates: false
+  panels:
+    disable_sanitize_html: true
+  log:
+    mode: console
+  log.console:
+    format: text
+    level: info
+
+datasources:
+  datasources.yaml:
+    apiVersion: 1
+    datasources:
+    - name: prometheus
+      type: prometheus
+      access: proxy
+      orgId: 1
+      url: http://prometheus.linkerd-viz.svc.cluster.local:9090
+      isDefault: true
+      jsonData:
+        timeInterval: "5s"
+      editable: true
+
+dashboardProviders:
+  dashboardproviders.yaml:
+    apiVersion: 1
+    providers:
+    - name: 'default'
+      orgId: 1
+      folder: ''
+      type: file
+      disableDeletion: false
+      editable: true
+      options:
+        path: /var/lib/grafana/dashboards/default
+
+dashboards:
+  default:
+    # all these charts are hosted at https://grafana.com/grafana/dashboards/{id}
+    top-line:
+      gnetId: 15474
+      revision: 2
+      datasource: prometheus
+    health:
+      gnetId: 15486
+      revision: 1
+      datasource: prometheus
+    kubernetes:
+      gnetId: 15479
+      revision: 1
+      datasource: prometheus
+    namespace:
+      gnetId: 15478
+      revision: 1
+      datasource: prometheus
+    deployment:
+      gnetId: 15475
+      revision: 2
+      datasource: prometheus
+    pod:
+      gnetId: 15477
+      revision: 1
+      datasource: prometheus
+    service:
+      gnetId: 15480
+      revision: 1
+      datasource: prometheus
+    route:
+      gnetId: 15481
+      revision: 1
+      datasource: prometheus
+    authority:
+      gnetId: 15482
+      revision: 1
+      datasource: prometheus
+    cronjob:
+      gnetId: 15483
+      revision: 1
+      datasource: prometheus
+    job:
+      gnetId: 15487
+      revision: 1
+      datasource: prometheus
+    daemonset:
+      gnetId: 15484
+      revision: 1
+      datasource: prometheus
+    replicaset:
+      gnetId: 15491
+      revision: 1
+      datasource: prometheus
+    statefulset:
+      gnetId: 15493
+      revision: 1
+      datasource: prometheus
+    replicationcontroller:
+      gnetId: 15492
+      revision: 1
+      datasource: prometheus
+    prometheus:
+      gnetId: 15489
+      revision: 1
+      datasource: prometheus
+    prometheus-benchmark:
+      gnetId: 15490
+      revision: 1
+      datasource: prometheus
+    multicluster:
+      gnetId: 15488
+      revision: 1
+      datasource: prometheus
diff --git a/jaeger/charts/linkerd-jaeger/Chart.yaml b/jaeger/charts/linkerd-jaeger/Chart.yaml
index 9b880d569..d7c5e2b5f 100644
--- a/jaeger/charts/linkerd-jaeger/Chart.yaml
+++ b/jaeger/charts/linkerd-jaeger/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.20.0-0"
 name: linkerd-jaeger
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 1.0.0-edge
+version: 30.0.0-edge
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/jaeger/charts/linkerd-jaeger/README.md b/jaeger/charts/linkerd-jaeger/README.md
index 0b4f2ec0d..53e0682b4 100644
--- a/jaeger/charts/linkerd-jaeger/README.md
+++ b/jaeger/charts/linkerd-jaeger/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Jaeger extension adds distributed tracing to Linkerd using
 OpenCensus and Jaeger.
 
-![Version: 1.0.0-edge](https://img.shields.io/badge/Version-1.0.0--edge-informational?style=flat-square)
+![Version: 30.0.0-edge](https://img.shields.io/badge/Version-30.0.0--edge-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/multicluster/charts/linkerd-multicluster/Chart.yaml b/multicluster/charts/linkerd-multicluster/Chart.yaml
index bba3354e0..413b92663 100644
--- a/multicluster/charts/linkerd-multicluster/Chart.yaml
+++ b/multicluster/charts/linkerd-multicluster/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.20.0-0"
 name: "linkerd-multicluster"
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 1.0.0-edge
+version: 30.0.0-edge
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/multicluster/charts/linkerd-multicluster/README.md b/multicluster/charts/linkerd-multicluster/README.md
index 240e774e9..3e3d666f2 100644
--- a/multicluster/charts/linkerd-multicluster/README.md
+++ b/multicluster/charts/linkerd-multicluster/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Multicluster extension contains resources to support multicluster
 linking to remote clusters
 
-![Version: 1.0.0-edge](https://img.shields.io/badge/Version-1.0.0--edge-informational?style=flat-square)
+![Version: 30.0.0-edge](https://img.shields.io/badge/Version-30.0.0--edge-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/multicluster/charts/linkerd-multicluster/templates/gateway.yaml b/multicluster/charts/linkerd-multicluster/templates/gateway.yaml
index 008fe8c8b..415c2f849 100644
--- a/multicluster/charts/linkerd-multicluster/templates/gateway.yaml
+++ b/multicluster/charts/linkerd-multicluster/templates/gateway.yaml
@@ -40,7 +40,7 @@ spec:
       {{- end }}
       containers:
         - name: pause
-          image: gcr.io/google_containers/pause
+          image: gcr.io/google_containers/pause:3.2
       serviceAccountName: {{.Values.gateway.name}}
 {{- if .Values.enablePodAntiAffinity }}
 ---
diff --git a/pkg/charts/linkerd2/values.go b/pkg/charts/linkerd2/values.go
index eaa6477a2..a29c4ffe9 100644
--- a/pkg/charts/linkerd2/values.go
+++ b/pkg/charts/linkerd2/values.go
@@ -51,7 +51,6 @@ type (
 		IdentityTrustAnchorsPEM      string              `json:"identityTrustAnchorsPEM"`
 		IdentityTrustDomain          string              `json:"identityTrustDomain"`
 		PrometheusURL                string              `json:"prometheusUrl"`
-		GrafanaURL                   string              `json:"grafanaUrl"`
 		ImagePullSecrets             []map[string]string `json:"imagePullSecrets"`
 		LinkerdVersion               string              `json:"linkerdVersion"`
 
diff --git a/policy-controller/amd64.dockerfile b/policy-controller/amd64.dockerfile
index 42635acb4..81d36f10b 100644
--- a/policy-controller/amd64.dockerfile
+++ b/policy-controller/amd64.dockerfile
@@ -1,4 +1,4 @@
-ARG RUST_IMAGE=docker.io/library/rust:1.56.0
+ARG RUST_IMAGE=docker.io/library/rust:1.56.1
 ARG RUNTIME_IMAGE=gcr.io/distroless/cc
 
 # Builds the controller binary.
@@ -7,7 +7,7 @@ ARG TARGETARCH
 WORKDIR /build
 COPY Cargo.toml Cargo.lock policy-controller/ /build/
 RUN --mount=type=cache,target=target \
-    --mount=type=cache,from=rust:1.56.0,source=/usr/local/cargo,target=/usr/local/cargo \
+    --mount=type=cache,from=rust:1.56.1,source=/usr/local/cargo,target=/usr/local/cargo \
     cargo build --locked --target=x86_64-unknown-linux-gnu --release --package=linkerd-policy-controller && \
     mv target/x86_64-unknown-linux-gnu/release/linkerd-policy-controller /tmp/
 
diff --git a/policy-controller/arm.dockerfile b/policy-controller/arm.dockerfile
index f6169fd84..512860235 100644
--- a/policy-controller/arm.dockerfile
+++ b/policy-controller/arm.dockerfile
@@ -1,4 +1,4 @@
-ARG RUST_IMAGE=docker.io/library/rust:1.56.0
+ARG RUST_IMAGE=docker.io/library/rust:1.56.1
 ARG RUNTIME_IMAGE=gcr.io/distroless/cc
 
 FROM $RUST_IMAGE as build
@@ -11,7 +11,7 @@ WORKDIR /build
 COPY Cargo.toml Cargo.lock policy-controller/ /build/
 # XXX(ver) we can't easily cross-compile against openssl, so use rustls on arm.
 RUN --mount=type=cache,target=target \
-    --mount=type=cache,from=rust:1.56.0,source=/usr/local/cargo,target=/usr/local/cargo \
+    --mount=type=cache,from=rust:1.56.1,source=/usr/local/cargo,target=/usr/local/cargo \
     cargo build --locked --release --target=armv7-unknown-linux-gnueabihf \
         --package=linkerd-policy-controller --no-default-features --features="rustls" && \
     mv target/armv7-unknown-linux-gnueabihf/release/linkerd-policy-controller /tmp/
diff --git a/policy-controller/arm64.dockerfile b/policy-controller/arm64.dockerfile
index 0e1c2b487..3c565fcbb 100644
--- a/policy-controller/arm64.dockerfile
+++ b/policy-controller/arm64.dockerfile
@@ -1,4 +1,4 @@
-ARG RUST_IMAGE=docker.io/library/rust:1.56.0
+ARG RUST_IMAGE=docker.io/library/rust:1.56.1
 ARG RUNTIME_IMAGE=gcr.io/distroless/cc
 
 FROM $RUST_IMAGE as build
@@ -11,7 +11,7 @@ WORKDIR /build
 COPY Cargo.toml Cargo.lock policy-controller/ /build/
 # XXX(ver) we can't easily cross-compile against openssl, so use rustls on arm.
 RUN --mount=type=cache,target=target \
-    --mount=type=cache,from=rust:1.56.0,source=/usr/local/cargo,target=/usr/local/cargo \
+    --mount=type=cache,from=rust:1.56.1,source=/usr/local/cargo,target=/usr/local/cargo \
     cargo build --locked --release --target=aarch64-unknown-linux-gnu \
         --package=linkerd-policy-controller --no-default-features --features="rustls" && \
     mv target/aarch64-unknown-linux-gnu/release/linkerd-policy-controller /tmp/
diff --git a/rust-toolchain b/rust-toolchain
index 3ebf789f5..43c989b55 100644
--- a/rust-toolchain
+++ b/rust-toolchain
@@ -1,1 +1,1 @@
-1.56.0
+1.56.1
diff --git a/viz/charts/linkerd-viz/Chart.yaml b/viz/charts/linkerd-viz/Chart.yaml
index 71f107673..6eb96a97d 100644
--- a/viz/charts/linkerd-viz/Chart.yaml
+++ b/viz/charts/linkerd-viz/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.20.0-0"
 name: "linkerd-viz"
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 1.0.0-edge
+version: 30.0.0-edge
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/viz/charts/linkerd-viz/README.md b/viz/charts/linkerd-viz/README.md
index fb8fc7ce6..ce403eaa5 100644
--- a/viz/charts/linkerd-viz/README.md
+++ b/viz/charts/linkerd-viz/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Viz extension contains observability and visualization
 components for Linkerd.
 
-![Version: 1.0.0-edge](https://img.shields.io/badge/Version-1.0.0--edge-informational?style=flat-square)
+![Version: 30.0.0-edge](https://img.shields.io/badge/Version-30.0.0--edge-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
@@ -96,23 +96,7 @@ Kubernetes: `>=1.20.0-0`
 | defaultUID | int | `2103` | UID for all the viz components |
 | enablePSP | bool | `false` | Create Roles and RoleBindings to associate this extension's ServiceAccounts to the control plane PSP resource. This requires that `enabledPSP` is set to true on the control plane install. Note PSP has been deprecated since k8s v1.21 |
 | enablePodAntiAffinity | bool | `false` | Enables Pod Anti Affinity logic to balance the placement of replicas across hosts and zones for High Availability. Enable this only when you have multiple replicas of components. |
-| grafana.enabled | bool | `true` | toggle field to enable or disable grafana |
-| grafana.image.name | string | `"grafana"` | Docker image name for the grafana instance |
-| grafana.image.pullPolicy | string | defaultImagePullPolicy | Pull policy for the grafana instance |
-| grafana.image.registry | string | defaultRegistry | Docker registry for the grafana instance |
-| grafana.image.tag | string | linkerdVersion | Docker image tag for the grafana instance |
-| grafana.logFormat | string | defaultLogFormat | log format (plain, json) of the grafana instance |
-| grafana.logLevel | string | defaultLogLevel | log level of the grafana instance |
-| grafana.nodeSelector | object | `{"kubernetes.io/os":"linux"}` | NodeSelector section, See the [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information |
-| grafana.proxy | string | `nil` |  |
-| grafana.resources.cpu.limit | string | `nil` | Maximum amount of CPU units that the grafana container can use |
-| grafana.resources.cpu.request | string | `nil` | Amount of CPU units that the grafana container requests |
-| grafana.resources.ephemeral-storage.limit | string | `""` | Maximum amount of ephemeral storage that the grafana container can use |
-| grafana.resources.ephemeral-storage.request | string | `""` | Amount of ephemeral storage that the grafana container requests |
-| grafana.resources.memory.limit | string | `nil` | Maximum amount of memory that grafana container can use |
-| grafana.resources.memory.request | string | `nil` | Amount of memory that the grafana container requests |
-| grafana.tolerations | string | `nil` | Tolerations section, See the [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for more information |
-| grafanaUrl | string | `""` | url of external grafana instance with reverse proxy configured. |
+| grafana.url | string | `nil` | url of a Grafana instance with reverse proxy configured, used by the Linkerd viz web dashboard to provide direct links to specific Grafana dashboards. See the [Linkerd documentation](https://linkerd.io/2/tasks/grafana) for more information |
 | identityTrustDomain | string | clusterDomain | Trust domain used for identity |
 | imagePullSecrets | list | `[]` | For Private docker registries, authentication is needed.  Registry secrets are applied to the respective service accounts |
 | jaegerUrl | string | `""` | url of external jaeger instance Set this to `jaeger.linkerd-jaeger.svc.<clusterDomain>` if you plan to use jaeger extension |
diff --git a/viz/charts/linkerd-viz/templates/grafana-policy.yaml b/viz/charts/linkerd-viz/templates/grafana-policy.yaml
deleted file mode 100644
index ad5daf1d0..000000000
--- a/viz/charts/linkerd-viz/templates/grafana-policy.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
-{{ if .Values.grafana.enabled -}}
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  {{ include "partials.namespace" . }}
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    {{ include "partials.annotations.created-by" . }}
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-  port: http
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  {{ include "partials.namespace" . }}
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    {{ include "partials.annotations.created-by" . }}
-spec:
-  server:
-    name: grafana
-  client:
-    # web, prometheus and the kubelet probes
-    unauthenticated: true
-{{ end -}}
diff --git a/viz/charts/linkerd-viz/templates/grafana-rbac.yaml b/viz/charts/linkerd-viz/templates/grafana-rbac.yaml
deleted file mode 100644
index fe3f29013..000000000
--- a/viz/charts/linkerd-viz/templates/grafana-rbac.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-{{ if .Values.grafana.enabled -}}
----
-###
-### Grafana RBAC
-###
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: grafana
-  {{ include "partials.namespace" . }}
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: {{.Release.Namespace}}
-{{- include "partials.image-pull-secrets" .Values.imagePullSecrets }}
-{{ end -}}
diff --git a/viz/charts/linkerd-viz/templates/grafana.yaml b/viz/charts/linkerd-viz/templates/grafana.yaml
deleted file mode 100644
index 36fa85d40..000000000
--- a/viz/charts/linkerd-viz/templates/grafana.yaml
+++ /dev/null
@@ -1,181 +0,0 @@
-{{ if .Values.grafana.enabled -}}
----
-###
-### Grafana
-###
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: grafana-config
-  {{ include "partials.namespace" . }}
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: {{.Release.Namespace}}
-  annotations:
-    {{ include "partials.annotations.created-by" . }}
-data:
-  grafana.ini: |-
-    instance_name = grafana
-    [server]
-    root_url = %(protocol)s://%(domain)s:/grafana/
-    [auth]
-    disable_login_form = true
-    [auth.anonymous]
-    enabled = true
-    org_role = Editor
-    [auth.basic]
-    enabled = false
-    [analytics]
-    check_for_updates = false
-    [panels]
-    disable_sanitize_html = true
-    [log]
-    mode = console
-    [log.console]
-    format = {{ .Values.grafana.logFormat | default .Values.defaultLogFormat | replace "plain" "text" }}
-    level = {{ .Values.grafana.logLevel | default .Values.defaultLogLevel }}
-  datasources.yaml: |-
-    apiVersion: 1
-    datasources:
-    - name: prometheus
-      type: prometheus
-      access: proxy
-      orgId: 1
-      {{- if .Values.prometheusUrl }}
-      url: {{.Values.prometheusUrl}}
-      {{- else }}
-      url: http://prometheus.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}:9090
-      {{- end }}
-      isDefault: true
-      jsonData:
-        timeInterval: "5s"
-      version: 1
-      editable: true
-
-  dashboards.yaml: |-
-    apiVersion: 1
-    providers:
-    - name: 'default'
-      orgId: 1
-      folder: ''
-      type: file
-      disableDeletion: true
-      editable: true
-      options:
-        path: /var/lib/grafana/dashboards
-        homeDashboardId: linkerd-top-line
----
-kind: Service
-apiVersion: v1
-metadata:
-  name: grafana
-  {{ include "partials.namespace" . }}
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: {{.Release.Namespace}}
-  annotations:
-    {{ include "partials.annotations.created-by" . }}
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: grafana
-  ports:
-  - name: http
-    port: 3000
-    targetPort: 3000
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    {{ include "partials.annotations.created-by" . }}
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: grafana
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: {{default .Values.linkerdVersion .Values.cliVersion}}
-    component: grafana
-    namespace: {{.Release.Namespace}}
-  name: grafana
-  {{ include "partials.namespace" . }}
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-      namespace: {{.Release.Namespace}}
-  template:
-    metadata:
-      annotations:
-        {{ include "partials.annotations.created-by" . }}
-        {{- with .Values.grafana.proxy }}
-        {{- include "partials.proxy.config.annotations" .resources | nindent 8 }}
-        {{- end }}
-        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: grafana
-        namespace: {{.Release.Namespace}}
-        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}
-    spec:
-      {{- if .Values.grafana.tolerations -}}
-      {{- include "linkerd.tolerations" (dict "Values" .Values.grafana) | nindent 6 }}
-      {{- end -}}
-      {{- include "linkerd.node-selector" (dict "Values" .Values.grafana) | nindent 6 }}
-      containers:
-      - env:
-        - name: GF_PATHS_DATA
-          value: /data
-        # Force using the go-based DNS resolver instead of the OS' to avoid failures in some environments
-        # see https://github.com/grafana/grafana/issues/20096
-        - name: GODEBUG
-          value: netdns=go
-        image: {{.Values.grafana.image.registry | default .Values.defaultRegistry}}/{{.Values.grafana.image.name}}:{{.Values.grafana.image.tag | default .Values.linkerdVersion}}
-        imagePullPolicy: {{.Values.grafana.image.pullPolicy | default .Values.defaultImagePullPolicy}}
-        livenessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-          initialDelaySeconds: 30
-        name: grafana
-        ports:
-        - containerPort: 3000
-          name: http
-        readinessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-        {{- if .Values.grafana.resources -}}
-        {{- include "partials.resources" .Values.grafana.resources | nindent 8 }}
-        {{- end }}
-        securityContext:
-          runAsUser: 472
-        volumeMounts:
-        - mountPath: /data
-          name: data
-        - mountPath: /etc/grafana
-          name: grafana-config
-          readOnly: true
-      serviceAccountName: grafana
-      volumes:
-      - emptyDir: {}
-        name: data
-      - configMap:
-          items:
-          - key: grafana.ini
-            path: grafana.ini
-          - key: datasources.yaml
-            path: provisioning/datasources/datasources.yaml
-          - key: dashboards.yaml
-            path: provisioning/dashboards/dashboards.yaml
-          name: grafana-config
-        name: grafana-config
-{{ end -}}
diff --git a/viz/charts/linkerd-viz/templates/prometheus.yaml b/viz/charts/linkerd-viz/templates/prometheus.yaml
index 5c828f1fd..b03d8fc3f 100644
--- a/viz/charts/linkerd-viz/templates/prometheus.yaml
+++ b/viz/charts/linkerd-viz/templates/prometheus.yaml
@@ -30,19 +30,6 @@ data:
       static_configs:
       - targets: ['localhost:9090']
 
-    {{ if .Values.grafana.enabled -}}
-    - job_name: 'grafana'
-      kubernetes_sd_configs:
-      - role: pod
-        namespaces:
-          names: ['{{.Release.Namespace}}']
-      relabel_configs:
-      - source_labels:
-        - __meta_kubernetes_pod_container_name
-        action: keep
-        regex: ^grafana$
-    {{- end }}
-
     #  Required for: https://grafana.com/grafana/dashboards/315
     - job_name: 'kubernetes-nodes-cadvisor'
       scheme: https
diff --git a/viz/charts/linkerd-viz/templates/psp.yaml b/viz/charts/linkerd-viz/templates/psp.yaml
index 123a4ac3d..e6924d8e1 100644
--- a/viz/charts/linkerd-viz/templates/psp.yaml
+++ b/viz/charts/linkerd-viz/templates/psp.yaml
@@ -33,11 +33,6 @@ subjects:
 - kind: ServiceAccount
   name: web
   namespace: {{.Release.Namespace}}
-{{ if .Values.grafana.enabled -}}
-- kind: ServiceAccount
-  name: grafana
-  namespace: {{.Release.Namespace}}
-{{ end -}}
 {{ if .Values.prometheus.enabled -}}
 - kind: ServiceAccount
   name: prometheus
diff --git a/viz/charts/linkerd-viz/templates/service-profiles.yaml b/viz/charts/linkerd-viz/templates/service-profiles.yaml
index d003792eb..231d57d58 100644
--- a/viz/charts/linkerd-viz/templates/service-profiles.yaml
+++ b/viz/charts/linkerd-viz/templates/service-profiles.yaml
@@ -4,6 +4,8 @@ kind: ServiceProfile
 metadata:
   name: metrics-api.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}
   {{ include "partials.namespace" . }}
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/StatSummary
@@ -41,6 +43,8 @@ kind: ServiceProfile
 metadata:
   name: prometheus.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}
   {{ include "partials.namespace" . }}
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/query
@@ -56,57 +60,3 @@ spec:
       method: GET
       pathRegex: /api/v1/series
 {{ end -}}
-{{ if .Values.grafana.enabled -}}
----
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: grafana.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}
-  {{ include "partials.namespace" . }}
-spec:
-  routes:
-  - name: GET /api/annotations
-    condition:
-      method: GET
-      pathRegex: /api/annotations
-  - name: GET /api/dashboards/tags
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/tags
-  - name: GET /api/dashboards/uid/{uid}
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/uid/.*
-  - name: GET /api/dashboard/{dashboard}
-    condition:
-      method: GET
-      pathRegex: /api/dashboard/.*
-  - name: GET /api/datasources/proxy/1/api/v1/series
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/series
-  - name: GET /api/datasources/proxy/1/api/v1/query_range
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/query_range
-  - name: GET /api/search
-    condition:
-      method: GET
-      pathRegex: /api/search
-  - name: GET /d/{uid}/{dashboard-name}
-    condition:
-      method: GET
-      pathRegex: /d/[^/]*/.*
-  - name: GET /public/build/{style}.css
-    condition:
-      method: GET
-      pathRegex: /public/build/.*\.css
-  - name: GET /public/fonts/{font}
-    condition:
-      method: GET
-      pathRegex: /public/fonts/.*
-  - name: GET /public/img/{img}
-    condition:
-      method: GET
-      pathRegex: /public/img/.*
-{{ end -}}
diff --git a/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml b/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml
index 2b75c6fe3..e2c0267f1 100644
--- a/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml
+++ b/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml
@@ -34,6 +34,8 @@ metadata:
   name: tap-injector
   {{ include "partials.namespace" . }}
 {{- include "partials.image-pull-secrets" .Values.imagePullSecrets }}
+  labels:
+    linkerd.io/extension: viz
 ---
 {{- $host := printf "tap-injector.%s.svc" .Release.Namespace }}
 {{- $ca := genSelfSignedCert $host (list) (list $host) 365 }}
diff --git a/viz/charts/linkerd-viz/templates/web.yaml b/viz/charts/linkerd-viz/templates/web.yaml
index 33ac56476..ac31d00bd 100644
--- a/viz/charts/linkerd-viz/templates/web.yaml
+++ b/viz/charts/linkerd-viz/templates/web.yaml
@@ -73,10 +73,8 @@ spec:
       - args:
         - -linkerd-metrics-api-addr=metrics-api.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}:8085
         - -cluster-domain={{.Values.clusterDomain}}
-        {{- if .Values.grafanaUrl }}
-        - -grafana-addr={{.Values.grafanaUrl}}
-        {{- else if .Values.grafana.enabled }}
-        - -grafana-addr=grafana.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}:3000
+        {{- if .Values.grafana.url }}
+        - -grafana-addr={{.Values.grafana.url}}
         {{- end}}
         {{- if .Values.jaegerUrl }}
         - -jaeger-addr={{.Values.jaegerUrl}}
diff --git a/viz/charts/linkerd-viz/values-ha.yaml b/viz/charts/linkerd-viz/values-ha.yaml
index ba278b0b3..03365d2b6 100644
--- a/viz/charts/linkerd-viz/values-ha.yaml
+++ b/viz/charts/linkerd-viz/values-ha.yaml
@@ -22,14 +22,6 @@ tap:
 dashboard:
   resources: *ha_resources
 
-# grafana configuration
-grafana:
-  resources:
-    cpu: *ha_resources_cpu
-    memory:
-      limit: 1024Mi
-      request: 50Mi
-
 # prometheus configuration
 prometheus:
   resources:
@@ -38,4 +30,4 @@ prometheus:
       request: 300m
     memory:
       limit: 8192Mi
-      request: 300Mi
\ No newline at end of file
+      request: 300Mi
diff --git a/viz/charts/linkerd-viz/values.yaml b/viz/charts/linkerd-viz/values.yaml
index 32e97a737..b817393b7 100644
--- a/viz/charts/linkerd-viz/values.yaml
+++ b/viz/charts/linkerd-viz/values.yaml
@@ -55,9 +55,6 @@ enablePSP: false
 # -- url of external prometheus instance
 prometheusUrl: ""
 
-# -- url of external grafana instance with reverse proxy configured.
-grafanaUrl: ""
-
 # -- url of external jaeger instance
 # Set this to `jaeger.linkerd-jaeger.svc.<clusterDomain>` if you plan to use jaeger extension
 jaegerUrl: ""
@@ -347,56 +344,11 @@ dashboard:
     # resources:
 
 grafana:
-  # -- toggle field to enable or disable grafana
-  enabled: true
-  # -- log level of the grafana instance
-  # @default -- defaultLogLevel
-  logLevel: ""
-  # -- log format (plain, json) of the grafana instance
-  # @default -- defaultLogFormat
-  logFormat: ""
-  image:
-    # -- Docker registry for the grafana instance
-    # @default -- defaultRegistry
-    registry: ""
-    # -- Docker image name for the grafana instance
-    name: grafana
-    # -- Docker image tag for the grafana instance
-    # @default -- linkerdVersion
-    tag: ""
-    # -- Pull policy for the grafana instance
-    # @default -- defaultImagePullPolicy
-    pullPolicy: ""
-
-  resources:
-    cpu:
-      # -- Maximum amount of CPU units that the grafana container can use
-      limit:
-      # -- Amount of CPU units that the grafana container requests
-      request:
-    memory:
-      # -- Maximum amount of memory that grafana container can use
-      limit:
-      # -- Amount of memory that the grafana container requests
-      request:
-    ephemeral-storage:
-      # -- Maximum amount of ephemeral storage that the grafana container can use
-      limit: ""
-      # -- Amount of ephemeral storage that the grafana container requests
-      request: ""
-
-  proxy:
-    # -- If set, overrides default proxy resources for the proxy injected
-    # into the grafana component
-    # resources:
-
-  # -- NodeSelector section, See the
-  # [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information
-  nodeSelector: *default_node_selector
-  # -- Tolerations section, See the
-  # [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)
-  # for more information
-  tolerations: *default_tolerations
+  # -- url of a Grafana instance with reverse proxy configured, used by the
+  # Linkerd viz web dashboard to provide direct links to specific Grafana
+  # dashboards. See the [Linkerd
+  # documentation](https://linkerd.io/2/tasks/grafana) for more information
+  url:
 
 prometheus:
   # -- toggle field to enable or disable prometheus
diff --git a/viz/cmd/install.go b/viz/cmd/install.go
index 6d4d1bd6c..1a8d19385 100644
--- a/viz/cmd/install.go
+++ b/viz/cmd/install.go
@@ -25,7 +25,6 @@ var (
 	templatesViz = []string{
 		"templates/namespace.yaml",
 		"templates/metrics-api-rbac.yaml",
-		"templates/grafana-rbac.yaml",
 		"templates/prometheus-rbac.yaml",
 		"templates/tap-rbac.yaml",
 		"templates/web-rbac.yaml",
@@ -34,8 +33,6 @@ var (
 		"templates/proxy-admin-policy.yaml",
 		"templates/metrics-api.yaml",
 		"templates/metrics-api-policy.yaml",
-		"templates/grafana.yaml",
-		"templates/grafana-policy.yaml",
 		"templates/prometheus.yaml",
 		"templates/tap.yaml",
 		"templates/tap-policy.yaml",
diff --git a/web/app/package.json b/web/app/package.json
index cb8db2950..3d409dd57 100644
--- a/web/app/package.json
+++ b/web/app/package.json
@@ -28,8 +28,8 @@
     "lodash": "4.17.21",
     "make-plural": "^7.0.0",
     "path": "0.12.7",
-    "prop-types": "15.8.0",
-    "query-string": "^7.0.0",
+    "prop-types": "15.8.1",
+    "query-string": "^7.1.0",
     "react": "16.14.0",
     "react-dom": "16.14.0",
     "react-linkify": "1.0.0-alpha",
@@ -49,7 +49,7 @@
     "@babel/runtime": "^7.16.7",
     "@lingui/cli": "3.13.0",
     "babel-core": "^7.0.0-bridge.0",
-    "babel-jest": "^27.4.5",
+    "babel-jest": "^27.4.6",
     "babel-loader": "^8.2.3",
     "babel-plugin-import": "^1.13.3",
     "babel-plugin-macros": "^3.1.0",
@@ -70,7 +70,7 @@
     "file-loader": "^6.2.0",
     "history": "5.2.0",
     "html-webpack-plugin": "^5.5.0",
-    "jest": "^27.4.5",
+    "jest": "^27.4.7",
     "lodash-webpack-plugin": "^0.11.6",
     "react-test-renderer": "16.14.0",
     "sinon": "12.0.1",
diff --git a/web/app/yarn.lock b/web/app/yarn.lock
index 253aeb8b1..c05627369 100644
--- a/web/app/yarn.lock
+++ b/web/app/yarn.lock
@@ -14,7 +14,7 @@
   resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e"
   integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==
 
-"@babel/core@^7.1.0", "@babel/core@^7.16.7", "@babel/core@^7.7.2", "@babel/core@^7.7.5":
+"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.16.7", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.7.tgz#db990f931f6d40cb9b87a0dc7d2adc749f1dcbcf"
   integrity sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==
@@ -283,7 +283,7 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.1.0", "@babel/parser@^7.11.5", "@babel/parser@^7.16.7", "@babel/parser@^7.7.2":
+"@babel/parser@^7.1.0", "@babel/parser@^7.11.5", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.7.tgz#d372dda9c89fcec340a82630a9f533f2fe15877e"
   integrity sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA==
@@ -975,7 +975,7 @@
     "@babel/parser" "^7.16.7"
     "@babel/types" "^7.16.7"
 
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.7.2":
+"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.7.2":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.7.tgz#dac01236a72c2560073658dd1a285fe4e0865d76"
   integrity sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==
@@ -1102,27 +1102,27 @@
   resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
   integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
 
-"@jest/console@^27.4.2":
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.4.2.tgz#7a95612d38c007ddb528ee446fe5e5e785e685ce"
-  integrity sha512-xknHThRsPB/To1FUbi6pCe43y58qFC03zfb6R7fDb/FfC7k2R3i1l+izRBJf8DI46KhYGRaF14Eo9A3qbBoixg==
+"@jest/console@^27.4.6":
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.4.6.tgz#0742e6787f682b22bdad56f9db2a8a77f6a86107"
+  integrity sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==
   dependencies:
     "@jest/types" "^27.4.2"
     "@types/node" "*"
     chalk "^4.0.0"
-    jest-message-util "^27.4.2"
+    jest-message-util "^27.4.6"
     jest-util "^27.4.2"
     slash "^3.0.0"
 
-"@jest/core@^27.4.5":
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.4.5.tgz#cae2dc34259782f4866c6606c3b480cce920ed4c"
-  integrity sha512-3tm/Pevmi8bDsgvo73nX8p/WPng6KWlCyScW10FPEoN1HU4pwI83tJ3TsFvi1FfzsjwUlMNEPowgb/rPau/LTQ==
+"@jest/core@^27.4.7":
+  version "27.4.7"
+  resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.4.7.tgz#84eabdf42a25f1fa138272ed229bcf0a1b5e6913"
+  integrity sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==
   dependencies:
-    "@jest/console" "^27.4.2"
-    "@jest/reporters" "^27.4.5"
-    "@jest/test-result" "^27.4.2"
-    "@jest/transform" "^27.4.5"
+    "@jest/console" "^27.4.6"
+    "@jest/reporters" "^27.4.6"
+    "@jest/test-result" "^27.4.6"
+    "@jest/transform" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
@@ -1131,63 +1131,63 @@
     exit "^0.1.2"
     graceful-fs "^4.2.4"
     jest-changed-files "^27.4.2"
-    jest-config "^27.4.5"
-    jest-haste-map "^27.4.5"
-    jest-message-util "^27.4.2"
+    jest-config "^27.4.7"
+    jest-haste-map "^27.4.6"
+    jest-message-util "^27.4.6"
     jest-regex-util "^27.4.0"
-    jest-resolve "^27.4.5"
-    jest-resolve-dependencies "^27.4.5"
-    jest-runner "^27.4.5"
-    jest-runtime "^27.4.5"
-    jest-snapshot "^27.4.5"
+    jest-resolve "^27.4.6"
+    jest-resolve-dependencies "^27.4.6"
+    jest-runner "^27.4.6"
+    jest-runtime "^27.4.6"
+    jest-snapshot "^27.4.6"
     jest-util "^27.4.2"
-    jest-validate "^27.4.2"
-    jest-watcher "^27.4.2"
+    jest-validate "^27.4.6"
+    jest-watcher "^27.4.6"
     micromatch "^4.0.4"
     rimraf "^3.0.0"
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
-"@jest/environment@^27.4.4":
-  version "27.4.4"
-  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.4.4.tgz#66ebebc79673d84aad29d2bb70a8c51e6c29bb4d"
-  integrity sha512-q+niMx7cJgt/t/b6dzLOh4W8Ef/8VyKG7hxASK39jakijJzbFBGpptx3RXz13FFV7OishQ9lTbv+dQ5K3EhfDQ==
+"@jest/environment@^27.4.6":
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.4.6.tgz#1e92885d64f48c8454df35ed9779fbcf31c56d8b"
+  integrity sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==
   dependencies:
-    "@jest/fake-timers" "^27.4.2"
+    "@jest/fake-timers" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
-    jest-mock "^27.4.2"
+    jest-mock "^27.4.6"
 
-"@jest/fake-timers@^27.4.2":
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.4.2.tgz#d217f86c3ba2027bf29e0b731fd0cb761a72d093"
-  integrity sha512-f/Xpzn5YQk5adtqBgvw1V6bF8Nx3hY0OIRRpCvWcfPl0EAjdqWPdhH3t/3XpiWZqtjIEHDyMKP9ajpva1l4Zmg==
+"@jest/fake-timers@^27.4.6":
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.4.6.tgz#e026ae1671316dbd04a56945be2fa251204324e8"
+  integrity sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==
   dependencies:
     "@jest/types" "^27.4.2"
     "@sinonjs/fake-timers" "^8.0.1"
     "@types/node" "*"
-    jest-message-util "^27.4.2"
-    jest-mock "^27.4.2"
+    jest-message-util "^27.4.6"
+    jest-mock "^27.4.6"
     jest-util "^27.4.2"
 
-"@jest/globals@^27.4.4":
-  version "27.4.4"
-  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.4.4.tgz#fe501a80c23ea2dab585c42be2a519bb5e38530d"
-  integrity sha512-bqpqQhW30BOreXM8bA8t8JbOQzsq/WnPTnBl+It3UxAD9J8yxEAaBEylHx1dtBapAr/UBk8GidXbzmqnee8tYQ==
+"@jest/globals@^27.4.6":
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.4.6.tgz#3f09bed64b0fd7f5f996920258bd4be8f52f060a"
+  integrity sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==
   dependencies:
-    "@jest/environment" "^27.4.4"
+    "@jest/environment" "^27.4.6"
     "@jest/types" "^27.4.2"
-    expect "^27.4.2"
+    expect "^27.4.6"
 
-"@jest/reporters@^27.4.5":
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.4.5.tgz#e229acca48d18ea39e805540c1c322b075ae63ad"
-  integrity sha512-3orsG4vi8zXuBqEoy2LbnC1kuvkg1KQUgqNxmxpQgIOQEPeV0onvZu+qDQnEoX8qTQErtqn/xzcnbpeTuOLSiA==
+"@jest/reporters@^27.4.6":
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.4.6.tgz#b53dec3a93baf9b00826abf95b932de919d6d8dd"
+  integrity sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==
   dependencies:
     "@bcoe/v8-coverage" "^0.2.3"
-    "@jest/console" "^27.4.2"
-    "@jest/test-result" "^27.4.2"
-    "@jest/transform" "^27.4.5"
+    "@jest/console" "^27.4.6"
+    "@jest/test-result" "^27.4.6"
+    "@jest/transform" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
     chalk "^4.0.0"
@@ -1196,14 +1196,14 @@
     glob "^7.1.2"
     graceful-fs "^4.2.4"
     istanbul-lib-coverage "^3.0.0"
-    istanbul-lib-instrument "^4.0.3"
+    istanbul-lib-instrument "^5.1.0"
     istanbul-lib-report "^3.0.0"
     istanbul-lib-source-maps "^4.0.0"
-    istanbul-reports "^3.0.2"
-    jest-haste-map "^27.4.5"
-    jest-resolve "^27.4.5"
+    istanbul-reports "^3.1.3"
+    jest-haste-map "^27.4.6"
+    jest-resolve "^27.4.6"
     jest-util "^27.4.2"
-    jest-worker "^27.4.5"
+    jest-worker "^27.4.6"
     slash "^3.0.0"
     source-map "^0.6.0"
     string-length "^4.0.1"
@@ -1219,43 +1219,43 @@
     graceful-fs "^4.2.4"
     source-map "^0.6.0"
 
-"@jest/test-result@^27.4.2":
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.4.2.tgz#05fd4a5466ec502f3eae0b39dff2b93ea4d5d9ec"
-  integrity sha512-kr+bCrra9jfTgxHXHa2UwoQjxvQk3Am6QbpAiJ5x/50LW8llOYrxILkqY0lZRW/hu8FXesnudbql263+EW9iNA==
+"@jest/test-result@^27.4.6":
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.4.6.tgz#b3df94c3d899c040f602cea296979844f61bdf69"
+  integrity sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==
   dependencies:
-    "@jest/console" "^27.4.2"
+    "@jest/console" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
-"@jest/test-sequencer@^27.4.5":
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.4.5.tgz#1d7e026844d343b60d2ca7fd82c579a17b445d7d"
-  integrity sha512-n5woIn/1v+FT+9hniymHPARA9upYUmfi5Pw9ewVwXCDlK4F5/Gkees9v8vdjGdAIJ2MPHLHodiajLpZZanWzEQ==
+"@jest/test-sequencer@^27.4.6":
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz#447339b8a3d7b5436f50934df30854e442a9d904"
+  integrity sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==
   dependencies:
-    "@jest/test-result" "^27.4.2"
+    "@jest/test-result" "^27.4.6"
     graceful-fs "^4.2.4"
-    jest-haste-map "^27.4.5"
-    jest-runtime "^27.4.5"
+    jest-haste-map "^27.4.6"
+    jest-runtime "^27.4.6"
 
-"@jest/transform@^27.4.5":
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.4.5.tgz#3dfe2e3680cd4aa27356172bf25617ab5b94f195"
-  integrity sha512-PuMet2UlZtlGzwc6L+aZmR3I7CEBpqadO03pU40l2RNY2fFJ191b9/ITB44LNOhVtsyykx0OZvj0PCyuLm7Eew==
+"@jest/transform@^27.4.6":
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.4.6.tgz#153621940b1ed500305eacdb31105d415dc30231"
+  integrity sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==
   dependencies:
     "@babel/core" "^7.1.0"
     "@jest/types" "^27.4.2"
-    babel-plugin-istanbul "^6.0.0"
+    babel-plugin-istanbul "^6.1.1"
     chalk "^4.0.0"
     convert-source-map "^1.4.0"
     fast-json-stable-stringify "^2.0.0"
     graceful-fs "^4.2.4"
-    jest-haste-map "^27.4.5"
+    jest-haste-map "^27.4.6"
     jest-regex-util "^27.4.0"
     jest-util "^27.4.2"
     micromatch "^4.0.4"
-    pirates "^4.0.1"
+    pirates "^4.0.4"
     slash "^3.0.0"
     source-map "^0.6.1"
     write-file-atomic "^3.0.0"
@@ -2267,15 +2267,15 @@ babel-core@^7.0.0-bridge.0:
   resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
   integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
 
-babel-jest@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.4.5.tgz#d38bd0be8ea71d8b97853a5fc9f76deeb095c709"
-  integrity sha512-3uuUTjXbgtODmSv/DXO9nZfD52IyC2OYTFaXGRzL0kpykzroaquCrD5+lZNafTvZlnNqZHt5pb0M08qVBZnsnA==
+babel-jest@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.4.6.tgz#4d024e69e241cdf4f396e453a07100f44f7ce314"
+  integrity sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==
   dependencies:
-    "@jest/transform" "^27.4.5"
+    "@jest/transform" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/babel__core" "^7.1.14"
-    babel-plugin-istanbul "^6.0.0"
+    babel-plugin-istanbul "^6.1.1"
     babel-preset-jest "^27.4.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
@@ -2306,15 +2306,15 @@ babel-plugin-import@^1.13.3:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/runtime" "^7.0.0"
 
-babel-plugin-istanbul@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765"
-  integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==
+babel-plugin-istanbul@^6.1.1:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73"
+  integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
     "@istanbuljs/load-nyc-config" "^1.0.0"
     "@istanbuljs/schema" "^0.1.2"
-    istanbul-lib-instrument "^4.0.0"
+    istanbul-lib-instrument "^5.0.4"
     test-exclude "^6.0.0"
 
 babel-plugin-jest-hoist@^27.4.0:
@@ -3868,17 +3868,15 @@ exit@^0.1.2:
   resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
   integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=
 
-expect@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/expect/-/expect-27.4.2.tgz#4429b0f7e307771d176de9bdf23229b101db6ef6"
-  integrity sha512-BjAXIDC6ZOW+WBFNg96J22D27Nq5ohn+oGcuP2rtOtcjuxNoV9McpQ60PcQWhdFOSBIQdR72e+4HdnbZTFSTyg==
+expect@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-27.4.6.tgz#f335e128b0335b6ceb4fcab67ece7cbd14c942e6"
+  integrity sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==
   dependencies:
     "@jest/types" "^27.4.2"
-    ansi-styles "^5.0.0"
     jest-get-type "^27.4.0"
-    jest-matcher-utils "^27.4.2"
-    jest-message-util "^27.4.2"
-    jest-regex-util "^27.4.0"
+    jest-matcher-utils "^27.4.6"
+    jest-message-util "^27.4.6"
 
 express@^4.17.1:
   version "4.17.1"
@@ -4862,14 +4860,20 @@ istanbul-lib-coverage@^3.0.0:
   resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
   integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
 
-istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d"
-  integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==
+istanbul-lib-coverage@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
+  integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
+
+istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a"
+  integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==
   dependencies:
-    "@babel/core" "^7.7.5"
+    "@babel/core" "^7.12.3"
+    "@babel/parser" "^7.14.7"
     "@istanbuljs/schema" "^0.1.2"
-    istanbul-lib-coverage "^3.0.0"
+    istanbul-lib-coverage "^3.2.0"
     semver "^6.3.0"
 
 istanbul-lib-report@^3.0.0:
@@ -4890,10 +4894,10 @@ istanbul-lib-source-maps@^4.0.0:
     istanbul-lib-coverage "^3.0.0"
     source-map "^0.6.1"
 
-istanbul-reports@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b"
-  integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==
+istanbul-reports@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.3.tgz#4bcae3103b94518117930d51283690960b50d3c2"
+  integrity sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==
   dependencies:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
@@ -4907,86 +4911,86 @@ jest-changed-files@^27.4.2:
     execa "^5.0.0"
     throat "^6.0.1"
 
-jest-circus@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.4.5.tgz#70bfb78e0200cab9b84747bf274debacaa538467"
-  integrity sha512-eTNWa9wsvBwPykhMMShheafbwyakcdHZaEYh5iRrQ0PFJxkDP/e3U/FvzGuKWu2WpwUA3C3hPlfpuzvOdTVqnw==
+jest-circus@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.4.6.tgz#d3af34c0eb742a967b1919fbb351430727bcea6c"
+  integrity sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==
   dependencies:
-    "@jest/environment" "^27.4.4"
-    "@jest/test-result" "^27.4.2"
+    "@jest/environment" "^27.4.6"
+    "@jest/test-result" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
     chalk "^4.0.0"
     co "^4.6.0"
     dedent "^0.7.0"
-    expect "^27.4.2"
+    expect "^27.4.6"
     is-generator-fn "^2.0.0"
-    jest-each "^27.4.2"
-    jest-matcher-utils "^27.4.2"
-    jest-message-util "^27.4.2"
-    jest-runtime "^27.4.5"
-    jest-snapshot "^27.4.5"
+    jest-each "^27.4.6"
+    jest-matcher-utils "^27.4.6"
+    jest-message-util "^27.4.6"
+    jest-runtime "^27.4.6"
+    jest-snapshot "^27.4.6"
     jest-util "^27.4.2"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
     slash "^3.0.0"
     stack-utils "^2.0.3"
     throat "^6.0.1"
 
-jest-cli@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.4.5.tgz#8708f54c28d13681f3255ec9026a2b15b03d41e8"
-  integrity sha512-hrky3DSgE0u7sQxaCL7bdebEPHx5QzYmrGuUjaPLmPE8jx5adtvGuOlRspvMoVLTTDOHRnZDoRLYJuA+VCI7Hg==
+jest-cli@^27.4.7:
+  version "27.4.7"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.4.7.tgz#d00e759e55d77b3bcfea0715f527c394ca314e5a"
+  integrity sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==
   dependencies:
-    "@jest/core" "^27.4.5"
-    "@jest/test-result" "^27.4.2"
+    "@jest/core" "^27.4.7"
+    "@jest/test-result" "^27.4.6"
     "@jest/types" "^27.4.2"
     chalk "^4.0.0"
     exit "^0.1.2"
     graceful-fs "^4.2.4"
     import-local "^3.0.2"
-    jest-config "^27.4.5"
+    jest-config "^27.4.7"
     jest-util "^27.4.2"
-    jest-validate "^27.4.2"
+    jest-validate "^27.4.6"
     prompts "^2.0.1"
     yargs "^16.2.0"
 
-jest-config@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.4.5.tgz#77ed7f2ba7bcfd7d740ade711d0d13512e08a59e"
-  integrity sha512-t+STVJtPt+fpqQ8GBw850NtSQbnDOw/UzdPfzDaHQ48/AylQlW7LHj3dH+ndxhC1UxJ0Q3qkq7IH+nM1skwTwA==
+jest-config@^27.4.7:
+  version "27.4.7"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.4.7.tgz#4f084b2acbd172c8b43aa4cdffe75d89378d3972"
+  integrity sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==
   dependencies:
-    "@babel/core" "^7.1.0"
-    "@jest/test-sequencer" "^27.4.5"
+    "@babel/core" "^7.8.0"
+    "@jest/test-sequencer" "^27.4.6"
     "@jest/types" "^27.4.2"
-    babel-jest "^27.4.5"
+    babel-jest "^27.4.6"
     chalk "^4.0.0"
     ci-info "^3.2.0"
     deepmerge "^4.2.2"
     glob "^7.1.1"
     graceful-fs "^4.2.4"
-    jest-circus "^27.4.5"
-    jest-environment-jsdom "^27.4.4"
-    jest-environment-node "^27.4.4"
+    jest-circus "^27.4.6"
+    jest-environment-jsdom "^27.4.6"
+    jest-environment-node "^27.4.6"
     jest-get-type "^27.4.0"
-    jest-jasmine2 "^27.4.5"
+    jest-jasmine2 "^27.4.6"
     jest-regex-util "^27.4.0"
-    jest-resolve "^27.4.5"
-    jest-runner "^27.4.5"
+    jest-resolve "^27.4.6"
+    jest-runner "^27.4.6"
     jest-util "^27.4.2"
-    jest-validate "^27.4.2"
+    jest-validate "^27.4.6"
     micromatch "^4.0.4"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
     slash "^3.0.0"
 
-jest-diff@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.4.2.tgz#786b2a5211d854f848e2dcc1e324448e9481f36f"
-  integrity sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q==
+jest-diff@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.4.6.tgz#93815774d2012a2cbb6cf23f84d48c7a2618f98d"
+  integrity sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==
   dependencies:
     chalk "^4.0.0"
     diff-sequences "^27.4.0"
     jest-get-type "^27.4.0"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
 
 jest-docblock@^27.4.0:
   version "27.4.0"
@@ -4995,40 +4999,40 @@ jest-docblock@^27.4.0:
   dependencies:
     detect-newline "^3.0.0"
 
-jest-each@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.4.2.tgz#19364c82a692d0d26557642098d1f4619c9ee7d3"
-  integrity sha512-53V2MNyW28CTruB3lXaHNk6PkiIFuzdOC9gR3C6j8YE/ACfrPnz+slB0s17AgU1TtxNzLuHyvNlLJ+8QYw9nBg==
+jest-each@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.4.6.tgz#e7e8561be61d8cc6dbf04296688747ab186c40ff"
+  integrity sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==
   dependencies:
     "@jest/types" "^27.4.2"
     chalk "^4.0.0"
     jest-get-type "^27.4.0"
     jest-util "^27.4.2"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
 
-jest-environment-jsdom@^27.4.4:
-  version "27.4.4"
-  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.4.4.tgz#94f738e99514d7a880e8ed8e03e3a321d43b49db"
-  integrity sha512-cYR3ndNfHBqQgFvS1RL7dNqSvD//K56j/q1s2ygNHcfTCAp12zfIromO1w3COmXrxS8hWAh7+CmZmGCIoqGcGA==
+jest-environment-jsdom@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz#c23a394eb445b33621dfae9c09e4c8021dea7b36"
+  integrity sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==
   dependencies:
-    "@jest/environment" "^27.4.4"
-    "@jest/fake-timers" "^27.4.2"
+    "@jest/environment" "^27.4.6"
+    "@jest/fake-timers" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
-    jest-mock "^27.4.2"
+    jest-mock "^27.4.6"
     jest-util "^27.4.2"
     jsdom "^16.6.0"
 
-jest-environment-node@^27.4.4:
-  version "27.4.4"
-  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.4.4.tgz#42fe5e3b224cb69b99811ebf6f5eaa5a59618514"
-  integrity sha512-D+v3lbJ2GjQTQR23TK0kY3vFVmSeea05giInI41HHOaJnAwOnmUHTZgUaZL+VxUB43pIzoa7PMwWtCVlIUoVoA==
+jest-environment-node@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.4.6.tgz#ee8cd4ef458a0ef09d087c8cd52ca5856df90242"
+  integrity sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==
   dependencies:
-    "@jest/environment" "^27.4.4"
-    "@jest/fake-timers" "^27.4.2"
+    "@jest/environment" "^27.4.6"
+    "@jest/fake-timers" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
-    jest-mock "^27.4.2"
+    jest-mock "^27.4.6"
     jest-util "^27.4.2"
 
 jest-get-type@^26.3.0:
@@ -5041,10 +5045,10 @@ jest-get-type@^27.4.0:
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.4.0.tgz#7503d2663fffa431638337b3998d39c5e928e9b5"
   integrity sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==
 
-jest-haste-map@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.4.5.tgz#c2921224a59223f91e03ec15703905978ef0cc1a"
-  integrity sha512-oJm1b5qhhPs78K24EDGifWS0dELYxnoBiDhatT/FThgB9yxqUm5F6li3Pv+Q+apMBmmPNzOBnZ7ZxWMB1Leq1Q==
+jest-haste-map@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.4.6.tgz#c60b5233a34ca0520f325b7e2cc0a0140ad0862a"
+  integrity sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==
   dependencies:
     "@jest/types" "^27.4.2"
     "@types/graceful-fs" "^4.1.2"
@@ -5055,58 +5059,57 @@ jest-haste-map@^27.4.5:
     jest-regex-util "^27.4.0"
     jest-serializer "^27.4.0"
     jest-util "^27.4.2"
-    jest-worker "^27.4.5"
+    jest-worker "^27.4.6"
     micromatch "^4.0.4"
     walker "^1.0.7"
   optionalDependencies:
     fsevents "^2.3.2"
 
-jest-jasmine2@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.4.5.tgz#ff79d11561679ff6c89715b0cd6b1e8c0dfbc6dc"
-  integrity sha512-oUnvwhJDj2LhOiUB1kdnJjkx8C5PwgUZQb9urF77mELH9DGR4e2GqpWQKBOYXWs5+uTN9BGDqRz3Aeg5Wts7aw==
+jest-jasmine2@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz#109e8bc036cb455950ae28a018f983f2abe50127"
+  integrity sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==
   dependencies:
-    "@babel/traverse" "^7.1.0"
-    "@jest/environment" "^27.4.4"
+    "@jest/environment" "^27.4.6"
     "@jest/source-map" "^27.4.0"
-    "@jest/test-result" "^27.4.2"
+    "@jest/test-result" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
     chalk "^4.0.0"
     co "^4.6.0"
-    expect "^27.4.2"
+    expect "^27.4.6"
     is-generator-fn "^2.0.0"
-    jest-each "^27.4.2"
-    jest-matcher-utils "^27.4.2"
-    jest-message-util "^27.4.2"
-    jest-runtime "^27.4.5"
-    jest-snapshot "^27.4.5"
+    jest-each "^27.4.6"
+    jest-matcher-utils "^27.4.6"
+    jest-message-util "^27.4.6"
+    jest-runtime "^27.4.6"
+    jest-snapshot "^27.4.6"
     jest-util "^27.4.2"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
     throat "^6.0.1"
 
-jest-leak-detector@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.4.2.tgz#7fc3120893a7a911c553f3f2bdff9faa4454abbb"
-  integrity sha512-ml0KvFYZllzPBJWDei3mDzUhyp/M4ubKebX++fPaudpe8OsxUE+m+P6ciVLboQsrzOCWDjE20/eXew9QMx/VGw==
+jest-leak-detector@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz#ed9bc3ce514b4c582637088d9faf58a33bd59bf4"
+  integrity sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==
   dependencies:
     jest-get-type "^27.4.0"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
 
-jest-matcher-utils@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.4.2.tgz#d17c5038607978a255e0a9a5c32c24e984b6c60b"
-  integrity sha512-jyP28er3RRtMv+fmYC/PKG8wvAmfGcSNproVTW2Y0P/OY7/hWUOmsPfxN1jOhM+0u2xU984u2yEagGivz9OBGQ==
+jest-matcher-utils@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz#53ca7f7b58170638590e946f5363b988775509b8"
+  integrity sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==
   dependencies:
     chalk "^4.0.0"
-    jest-diff "^27.4.2"
+    jest-diff "^27.4.6"
     jest-get-type "^27.4.0"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
 
-jest-message-util@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.4.2.tgz#07f3f1bf207d69cf798ce830cc57f1a849f99388"
-  integrity sha512-OMRqRNd9E0DkBLZpFtZkAGYOXl6ZpoMtQJWTAREJKDOFa0M6ptB7L67tp+cszMBkvSgKOhNtQp2Vbcz3ZZKo/w==
+jest-message-util@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.4.6.tgz#9fdde41a33820ded3127465e1a5896061524da31"
+  integrity sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==
   dependencies:
     "@babel/code-frame" "^7.12.13"
     "@jest/types" "^27.4.2"
@@ -5114,14 +5117,14 @@ jest-message-util@^27.4.2:
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     micromatch "^4.0.4"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
-jest-mock@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.4.2.tgz#184ff197a25491bfe4570c286daa5d62eb760b88"
-  integrity sha512-PDDPuyhoukk20JrQKeofK12hqtSka7mWH0QQuxSNgrdiPsrnYYLS6wbzu/HDlxZRzji5ylLRULeuI/vmZZDrYA==
+jest-mock@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.4.6.tgz#77d1ba87fbd33ccb8ef1f061697e7341b7635195"
+  integrity sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==
   dependencies:
     "@jest/types" "^27.4.2"
     "@types/node" "*"
@@ -5145,40 +5148,40 @@ jest-regex-util@^27.4.0:
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.4.0.tgz#e4c45b52653128843d07ad94aec34393ea14fbca"
   integrity sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==
 
-jest-resolve-dependencies@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.5.tgz#9398af854bdb12d6a9e5a8a536ee401f889a3ecf"
-  integrity sha512-elEVvkvRK51y037NshtEkEnukMBWvlPzZHiL847OrIljJ8yIsujD2GXRPqDXC4rEVKbcdsy7W0FxoZb4WmEs7w==
+jest-resolve-dependencies@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz#fc50ee56a67d2c2183063f6a500cc4042b5e2327"
+  integrity sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==
   dependencies:
     "@jest/types" "^27.4.2"
     jest-regex-util "^27.4.0"
-    jest-snapshot "^27.4.5"
+    jest-snapshot "^27.4.6"
 
-jest-resolve@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.4.5.tgz#8dc44f5065fb8d58944c20f932cb7b9fe9760cca"
-  integrity sha512-xU3z1BuOz/hUhVUL+918KqUgK+skqOuUsAi7A+iwoUldK6/+PW+utK8l8cxIWT9AW7IAhGNXjSAh1UYmjULZZw==
+jest-resolve@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.4.6.tgz#2ec3110655e86d5bfcfa992e404e22f96b0b5977"
+  integrity sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==
   dependencies:
     "@jest/types" "^27.4.2"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
-    jest-haste-map "^27.4.5"
+    jest-haste-map "^27.4.6"
     jest-pnp-resolver "^1.2.2"
     jest-util "^27.4.2"
-    jest-validate "^27.4.2"
+    jest-validate "^27.4.6"
     resolve "^1.20.0"
     resolve.exports "^1.1.0"
     slash "^3.0.0"
 
-jest-runner@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.4.5.tgz#daba2ba71c8f34137dc7ac45616add35370a681e"
-  integrity sha512-/irauncTfmY1WkTaRQGRWcyQLzK1g98GYG/8QvIPviHgO1Fqz1JYeEIsSfF+9mc/UTA6S+IIHFgKyvUrtiBIZg==
+jest-runner@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.4.6.tgz#1d390d276ec417e9b4d0d081783584cbc3e24773"
+  integrity sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==
   dependencies:
-    "@jest/console" "^27.4.2"
-    "@jest/environment" "^27.4.4"
-    "@jest/test-result" "^27.4.2"
-    "@jest/transform" "^27.4.5"
+    "@jest/console" "^27.4.6"
+    "@jest/environment" "^27.4.6"
+    "@jest/test-result" "^27.4.6"
+    "@jest/transform" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
     chalk "^4.0.0"
@@ -5186,49 +5189,45 @@ jest-runner@^27.4.5:
     exit "^0.1.2"
     graceful-fs "^4.2.4"
     jest-docblock "^27.4.0"
-    jest-environment-jsdom "^27.4.4"
-    jest-environment-node "^27.4.4"
-    jest-haste-map "^27.4.5"
-    jest-leak-detector "^27.4.2"
-    jest-message-util "^27.4.2"
-    jest-resolve "^27.4.5"
-    jest-runtime "^27.4.5"
+    jest-environment-jsdom "^27.4.6"
+    jest-environment-node "^27.4.6"
+    jest-haste-map "^27.4.6"
+    jest-leak-detector "^27.4.6"
+    jest-message-util "^27.4.6"
+    jest-resolve "^27.4.6"
+    jest-runtime "^27.4.6"
     jest-util "^27.4.2"
-    jest-worker "^27.4.5"
+    jest-worker "^27.4.6"
     source-map-support "^0.5.6"
     throat "^6.0.1"
 
-jest-runtime@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.4.5.tgz#97703ad2a1799d4f50ab59049bd21a9ceaed2813"
-  integrity sha512-CIYqwuJQXHQtPd/idgrx4zgJ6iCb6uBjQq1RSAGQrw2S8XifDmoM1Ot8NRd80ooAm+ZNdHVwsktIMGlA1F1FAQ==
+jest-runtime@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.4.6.tgz#83ae923818e3ea04463b22f3597f017bb5a1cffa"
+  integrity sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==
   dependencies:
-    "@jest/console" "^27.4.2"
-    "@jest/environment" "^27.4.4"
-    "@jest/globals" "^27.4.4"
+    "@jest/environment" "^27.4.6"
+    "@jest/fake-timers" "^27.4.6"
+    "@jest/globals" "^27.4.6"
     "@jest/source-map" "^27.4.0"
-    "@jest/test-result" "^27.4.2"
-    "@jest/transform" "^27.4.5"
+    "@jest/test-result" "^27.4.6"
+    "@jest/transform" "^27.4.6"
     "@jest/types" "^27.4.2"
-    "@types/yargs" "^16.0.0"
     chalk "^4.0.0"
     cjs-module-lexer "^1.0.0"
     collect-v8-coverage "^1.0.0"
     execa "^5.0.0"
-    exit "^0.1.2"
     glob "^7.1.3"
     graceful-fs "^4.2.4"
-    jest-haste-map "^27.4.5"
-    jest-message-util "^27.4.2"
-    jest-mock "^27.4.2"
+    jest-haste-map "^27.4.6"
+    jest-message-util "^27.4.6"
+    jest-mock "^27.4.6"
     jest-regex-util "^27.4.0"
-    jest-resolve "^27.4.5"
-    jest-snapshot "^27.4.5"
+    jest-resolve "^27.4.6"
+    jest-snapshot "^27.4.6"
     jest-util "^27.4.2"
-    jest-validate "^27.4.2"
     slash "^3.0.0"
     strip-bom "^4.0.0"
-    yargs "^16.2.0"
 
 jest-serializer@^27.4.0:
   version "27.4.0"
@@ -5238,34 +5237,32 @@ jest-serializer@^27.4.0:
     "@types/node" "*"
     graceful-fs "^4.2.4"
 
-jest-snapshot@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.4.5.tgz#2ea909b20aac0fe62504bc161331f730b8a7ecc7"
-  integrity sha512-eCi/iM1YJFrJWiT9de4+RpWWWBqsHiYxFG9V9o/n0WXs6GpW4lUt4FAHAgFPTLPqCUVzrMQmSmTZSgQzwqR7IQ==
+jest-snapshot@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.4.6.tgz#e2a3b4fff8bdce3033f2373b2e525d8b6871f616"
+  integrity sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==
   dependencies:
     "@babel/core" "^7.7.2"
     "@babel/generator" "^7.7.2"
-    "@babel/parser" "^7.7.2"
     "@babel/plugin-syntax-typescript" "^7.7.2"
     "@babel/traverse" "^7.7.2"
     "@babel/types" "^7.0.0"
-    "@jest/transform" "^27.4.5"
+    "@jest/transform" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/babel__traverse" "^7.0.4"
     "@types/prettier" "^2.1.5"
     babel-preset-current-node-syntax "^1.0.0"
     chalk "^4.0.0"
-    expect "^27.4.2"
+    expect "^27.4.6"
     graceful-fs "^4.2.4"
-    jest-diff "^27.4.2"
+    jest-diff "^27.4.6"
     jest-get-type "^27.4.0"
-    jest-haste-map "^27.4.5"
-    jest-matcher-utils "^27.4.2"
-    jest-message-util "^27.4.2"
-    jest-resolve "^27.4.5"
+    jest-haste-map "^27.4.6"
+    jest-matcher-utils "^27.4.6"
+    jest-message-util "^27.4.6"
     jest-util "^27.4.2"
     natural-compare "^1.4.0"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
     semver "^7.3.2"
 
 jest-util@^27.4.2:
@@ -5292,24 +5289,24 @@ jest-validate@^26.5.2:
     leven "^3.1.0"
     pretty-format "^26.6.2"
 
-jest-validate@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.4.2.tgz#eecfcc1b1c9429aa007da08a2bae4e32a81bbbc3"
-  integrity sha512-hWYsSUej+Fs8ZhOm5vhWzwSLmVaPAxRy+Mr+z5MzeaHm9AxUpXdoVMEW4R86y5gOobVfBsMFLk4Rb+QkiEpx1A==
+jest-validate@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.4.6.tgz#efc000acc4697b6cf4fa68c7f3f324c92d0c4f1f"
+  integrity sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==
   dependencies:
     "@jest/types" "^27.4.2"
     camelcase "^6.2.0"
     chalk "^4.0.0"
     jest-get-type "^27.4.0"
     leven "^3.1.0"
-    pretty-format "^27.4.2"
+    pretty-format "^27.4.6"
 
-jest-watcher@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.4.2.tgz#c9037edfd80354c9fe90de4b6f8b6e2b8e736744"
-  integrity sha512-NJvMVyyBeXfDezhWzUOCOYZrUmkSCiatpjpm+nFUid74OZEHk6aMLrZAukIiFDwdbqp6mTM6Ui1w4oc+8EobQg==
+jest-watcher@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.4.6.tgz#673679ebeffdd3f94338c24f399b85efc932272d"
+  integrity sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==
   dependencies:
-    "@jest/test-result" "^27.4.2"
+    "@jest/test-result" "^27.4.6"
     "@jest/types" "^27.4.2"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
@@ -5326,23 +5323,23 @@ jest-worker@^27.0.6, jest-worker@^27.3.1:
     merge-stream "^2.0.0"
     supports-color "^8.0.0"
 
-jest-worker@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.5.tgz#d696e3e46ae0f24cff3fa7195ffba22889262242"
-  integrity sha512-f2s8kEdy15cv9r7q4KkzGXvlY0JTcmCbMHZBfSQDwW77REr45IDWwd0lksDFeVHH2jJ5pqb90T77XscrjeGzzg==
+jest-worker@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.6.tgz#5d2d93db419566cb680752ca0792780e71b3273e"
+  integrity sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==
   dependencies:
     "@types/node" "*"
     merge-stream "^2.0.0"
     supports-color "^8.0.0"
 
-jest@^27.4.5:
-  version "27.4.5"
-  resolved "https://registry.yarnpkg.com/jest/-/jest-27.4.5.tgz#66e45acba44137fac26be9d3cc5bb031e136dc0f"
-  integrity sha512-uT5MiVN3Jppt314kidCk47MYIRilJjA/l2mxwiuzzxGUeJIvA8/pDaJOAX5KWvjAo7SCydcW0/4WEtgbLMiJkg==
+jest@^27.4.7:
+  version "27.4.7"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-27.4.7.tgz#87f74b9026a1592f2da05b4d258e57505f28eca4"
+  integrity sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==
   dependencies:
-    "@jest/core" "^27.4.5"
+    "@jest/core" "^27.4.7"
     import-local "^3.0.2"
-    jest-cli "^27.4.5"
+    jest-cli "^27.4.7"
 
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
@@ -5972,9 +5969,9 @@ no-case@^3.0.4:
     tslib "^2.0.3"
 
 node-forge@>=0.10.0, node-forge@^0.10.0:
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
-  integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.0.tgz#850cec9230f62f9a7cd8ddd08dc74b4be52573ad"
+  integrity sha512-M4AsdaP0bGNaSPtatd/+f76asocI0cFaURRdeQVZvrJBrYp2Qohv5hDbGHykuNqCb1BYjWHjdS6HlN50qbztwA==
 
 node-gettext@^3.0.0:
   version "3.0.0"
@@ -5988,11 +5985,6 @@ node-int64@^0.4.0:
   resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
   integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
 
-node-modules-regexp@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
-  integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
-
 node-releases@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
@@ -6403,12 +6395,10 @@ pinkie@^2.0.0:
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
   integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
 
-pirates@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
-  integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
-  dependencies:
-    node-modules-regexp "^1.0.0"
+pirates@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6"
+  integrity sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==
 
 pkg-dir@^4.1.0, pkg-dir@^4.2.0:
   version "4.2.0"
@@ -6526,12 +6516,11 @@ pretty-format@^26.6.2:
     ansi-styles "^4.0.0"
     react-is "^17.0.1"
 
-pretty-format@^27.4.2:
-  version "27.4.2"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.2.tgz#e4ce92ad66c3888423d332b40477c87d1dac1fb8"
-  integrity sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw==
+pretty-format@^27.4.6:
+  version "27.4.6"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.6.tgz#1b784d2f53c68db31797b2348fa39b49e31846b7"
+  integrity sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==
   dependencies:
-    "@jest/types" "^27.4.2"
     ansi-regex "^5.0.1"
     ansi-styles "^5.0.0"
     react-is "^17.0.1"
@@ -6568,10 +6557,10 @@ prop-types-exact@^1.2.0:
     object.assign "^4.1.0"
     reflect.ownkeys "^0.2.0"
 
-prop-types@15.8.0, prop-types@^15.6.2, prop-types@^15.7.2:
-  version "15.8.0"
-  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.0.tgz#d237e624c45a9846e469f5f31117f970017ff588"
-  integrity sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g==
+prop-types@15.8.1, prop-types@^15.6.2, prop-types@^15.7.2:
+  version "15.8.1"
+  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
+  integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
   dependencies:
     loose-envify "^1.4.0"
     object-assign "^4.1.1"
@@ -6607,10 +6596,10 @@ qs@6.7.0:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
   integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
 
-query-string@^7.0.0:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.0.1.tgz#45bd149cf586aaa582dffc7ec7a8ad97dd02f75d"
-  integrity sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==
+query-string@^7.0.0, query-string@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.0.tgz#96b88f27b39794f97b8c8ccd060bc900495078ef"
+  integrity sha512-wnJ8covk+S9isYR5JIXPt93kFUmI2fQ4R/8130fuq+qwLiGVTurg7Klodgfw4NSz/oe7xnyi09y3lSrogUeM3g==
   dependencies:
     decode-uri-component "^0.2.0"
     filter-obj "^1.1.0"


Test patch:
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index f74d975a3..e1e3c5714 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -24,7 +24,6 @@ jobs:
         - cni-plugin
         - controller
         - debug
-        - grafana
         - jaeger-webhook
         - metrics-api
         - policy-controller
diff --git a/bin/_test-helpers.sh b/bin/_test-helpers.sh
index 1c66727ab..8f43b9b69 100644
--- a/bin/_test-helpers.sh
+++ b/bin/_test-helpers.sh
@@ -9,7 +9,7 @@ set +e
 export default_test_names=(deep external-issuer external-prometheus-deep helm-deep helm-upgrade uninstall upgrade-edge upgrade-stable default-policy-deny)
 export external_resource_test_names=(external-resources)
 export all_test_names=(cluster-domain cni-calico-deep multicluster "${default_test_names[*]}" "${external_resource_test_names[*]}")
-images_load_default=(proxy controller policy-controller web metrics-api grafana tap)
+images_load_default=(proxy controller policy-controller web metrics-api tap)
 
 tests_usage() {
   progname="${0##*/}"
diff --git a/cli/cmd/testdata/install_controlplane_tracing_output.golden b/cli/cmd/testdata/install_controlplane_tracing_output.golden
index 890f1ab6d..b870545f7 100644
--- a/cli/cmd/testdata/install_controlplane_tracing_output.golden
+++ b/cli/cmd/testdata/install_controlplane_tracing_output.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_custom_domain.golden b/cli/cmd/testdata/install_custom_domain.golden
index dd8bb5024..0b7d95508 100644
--- a/cli/cmd/testdata/install_custom_domain.golden
+++ b/cli/cmd/testdata/install_custom_domain.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_custom_registry.golden b/cli/cmd/testdata/install_custom_registry.golden
index e0b5d0b87..c3521f0ff 100644
--- a/cli/cmd/testdata/install_custom_registry.golden
+++ b/cli/cmd/testdata/install_custom_registry.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden
index dd8bb5024..0b7d95508 100644
--- a/cli/cmd/testdata/install_default.golden
+++ b/cli/cmd/testdata/install_default.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_default_override_dst_get_nets.golden b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
index 7cce0b272..8acfcd828 100644
--- a/cli/cmd/testdata/install_default_override_dst_get_nets.golden
+++ b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_default_token.golden b/cli/cmd/testdata/install_default_token.golden
index 240ddc706..a8791a011 100644
--- a/cli/cmd/testdata/install_default_token.golden
+++ b/cli/cmd/testdata/install_default_token.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_ha_output.golden b/cli/cmd/testdata/install_ha_output.golden
index 55deadfc5..a86a8f234 100644
--- a/cli/cmd/testdata/install_ha_output.golden
+++ b/cli/cmd/testdata/install_ha_output.golden
@@ -1123,7 +1123,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: true
-    grafanaUrl: ""
     heartbeatResources:
       cpu:
         limit: ""
diff --git a/cli/cmd/testdata/install_ha_with_overrides_output.golden b/cli/cmd/testdata/install_ha_with_overrides_output.golden
index 4104887e1..c807d7076 100644
--- a/cli/cmd/testdata/install_ha_with_overrides_output.golden
+++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden
@@ -1123,7 +1123,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: true
-    grafanaUrl: ""
     heartbeatResources:
       cpu:
         limit: ""
diff --git a/cli/cmd/testdata/install_heartbeat_disabled_output.golden b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
index 886d4774e..5e48b7ebd 100644
--- a/cli/cmd/testdata/install_heartbeat_disabled_output.golden
+++ b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
@@ -1045,7 +1045,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_helm_control_plane_output.golden b/cli/cmd/testdata/install_helm_control_plane_output.golden
index 6911f37ec..83a52f55b 100644
--- a/cli/cmd/testdata/install_helm_control_plane_output.golden
+++ b/cli/cmd/testdata/install_helm_control_plane_output.golden
@@ -404,7 +404,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_helm_control_plane_output_ha.golden b/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
index 0848bc33c..2ca7362ed 100644
--- a/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
+++ b/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
@@ -413,7 +413,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: true
-    grafanaUrl: ""
     heartbeatResources:
       cpu:
         limit: ""
diff --git a/cli/cmd/testdata/install_helm_output_ha_labels.golden b/cli/cmd/testdata/install_helm_output_ha_labels.golden
index 46b52ddca..5225e7523 100644
--- a/cli/cmd/testdata/install_helm_output_ha_labels.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_labels.golden
@@ -413,7 +413,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: true
-    grafanaUrl: ""
     heartbeatResources:
       cpu:
         limit: ""
diff --git a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
index 159c2c7d3..f9ac619fc 100644
--- a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
@@ -413,7 +413,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: true
-    grafanaUrl: ""
     heartbeatResources:
       cpu:
         limit: ""
diff --git a/cli/cmd/testdata/install_no_init_container.golden b/cli/cmd/testdata/install_no_init_container.golden
index 1d4decb99..b5a022481 100644
--- a/cli/cmd/testdata/install_no_init_container.golden
+++ b/cli/cmd/testdata/install_no_init_container.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden
index 87f86d8a8..f79bce2c6 100644
--- a/cli/cmd/testdata/install_output.golden
+++ b/cli/cmd/testdata/install_output.golden
@@ -1111,7 +1111,6 @@ data:
     enableEndpointSlices: false
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_proxy_ignores.golden b/cli/cmd/testdata/install_proxy_ignores.golden
index 07acfb70c..f04576139 100644
--- a/cli/cmd/testdata/install_proxy_ignores.golden
+++ b/cli/cmd/testdata/install_proxy_ignores.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/cli/cmd/testdata/install_values_file.golden b/cli/cmd/testdata/install_values_file.golden
index e4775ccc7..ec1397e5d 100644
--- a/cli/cmd/testdata/install_values_file.golden
+++ b/cli/cmd/testdata/install_values_file.golden
@@ -1114,7 +1114,6 @@ data:
     enableEndpointSlices: true
     enableH2Upgrade: true
     enablePodAntiAffinity: false
-    grafanaUrl: ""
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
diff --git a/multicluster/cmd/testdata/install_default.golden b/multicluster/cmd/testdata/install_default.golden
index 816e50914..ef1bed122 100644
--- a/multicluster/cmd/testdata/install_default.golden
+++ b/multicluster/cmd/testdata/install_default.golden
@@ -36,7 +36,7 @@ spec:
     spec:
       containers:
         - name: pause
-          image: gcr.io/google_containers/pause
+          image: gcr.io/google_containers/pause:3.2
       serviceAccountName: linkerd-gateway
 ---
 apiVersion: v1
diff --git a/multicluster/cmd/testdata/install_ha.golden b/multicluster/cmd/testdata/install_ha.golden
index 3a02c1389..373e6ae0a 100644
--- a/multicluster/cmd/testdata/install_ha.golden
+++ b/multicluster/cmd/testdata/install_ha.golden
@@ -59,7 +59,7 @@ spec:
             topologyKey: kubernetes.io/hostname
       containers:
         - name: pause
-          image: gcr.io/google_containers/pause
+          image: gcr.io/google_containers/pause:3.2
       serviceAccountName: linkerd-gateway
 ---
 kind: PodDisruptionBudget
diff --git a/multicluster/cmd/testdata/install_psp.golden b/multicluster/cmd/testdata/install_psp.golden
index 7b15614a9..0c2c084ce 100644
--- a/multicluster/cmd/testdata/install_psp.golden
+++ b/multicluster/cmd/testdata/install_psp.golden
@@ -36,7 +36,7 @@ spec:
     spec:
       containers:
         - name: pause
-          image: gcr.io/google_containers/pause
+          image: gcr.io/google_containers/pause:3.2
       serviceAccountName: linkerd-gateway
 ---
 apiVersion: v1
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index 7ec99bd25..0367bb6bb 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -1368,7 +1368,6 @@ func TestValidateControlPlanePods(t *testing.T) {
 
 	t.Run("Returns an error if not all pods are running", func(t *testing.T) {
 		pods := []corev1.Pod{
-			pod("linkerd-grafana-5b7d796646-hh46d", corev1.PodRunning, true),
 			pod("linkerd-destination-9849948665-37082", corev1.PodFailed, true),
 			pod("linkerd-identity-6849948664-27982", corev1.PodFailed, true),
 		}
@@ -2219,7 +2218,6 @@ data:
     controllerImageVersion: ControllerImageVersion
     controllerLogLevel: ControllerLogLevel
     enableEndpointSlices: false
-    grafanaUrl: ""
     highAvailability: false
     imagePullPolicy: ImagePullPolicy
     imagePullSecrets: null
@@ -2376,7 +2374,6 @@ data:
       controllerImageVersion: ControllerImageVersion
       controllerLogLevel: ControllerLogLevel
       enableEndpointSlices: false
-      grafanaUrl: ""
       highAvailability: false
       imagePullPolicy: ImagePullPolicy
       imagePullSecrets: null
diff --git a/test/integration/endpoints/endpoints_test.go b/test/integration/endpoints/endpoints_test.go
index e97670fa5..d3e310ce6 100644
--- a/test/integration/endpoints/endpoints_test.go
+++ b/test/integration/endpoints/endpoints_test.go
@@ -1,20 +1,26 @@
 package endpoints
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"os"
 	"regexp"
 	"strings"
 	"testing"
-	"text/template"
+	"time"
 
 	"github.com/linkerd/linkerd2/testutil"
 )
 
 var TestHelper *testutil.TestHelper
 
+type testCase struct {
+	name       string
+	authority  string
+	expectedRE string
+	ns         string
+}
+
 func TestMain(m *testing.M) {
 	TestHelper = testutil.NewTestHelper()
 	os.Exit(m.Run())
@@ -23,19 +29,9 @@ func TestMain(m *testing.M) {
 func TestGoodEndpoints(t *testing.T) {
 	ctx := context.Background()
 	controlNs := TestHelper.GetLinkerdNamespace()
-	testDataPath := "testdata"
-	cmd := []string{
-		"diagnostics",
-		"endpoints",
-		fmt.Sprintf("linkerd-dst.%s.svc.cluster.local:8086", controlNs),
-		fmt.Sprintf("linkerd-identity.%s.svc.cluster.local:8080", controlNs),
-		fmt.Sprintf("linkerd-proxy-injector.%s.svc.cluster.local:443", controlNs),
-		fmt.Sprintf("nginx.%s.svc.cluster.local:8080", "linkerd-endpoints-test"),
-		"-ojson",
-	}
 
 	TestHelper.WithDataPlaneNamespace(ctx, "endpoints-test", map[string]string{}, t, func(t *testing.T, ns string) {
-		out, err := TestHelper.Kubectl("", "apply", "-f", fmt.Sprintf("%s/%s", testDataPath, "nginx.yaml"), "-n", ns)
+		out, err := TestHelper.Kubectl("", "apply", "-f", "testdata/nginx.yaml", "-n", ns)
 		if err != nil {
 			testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v output:\n%s", err, out)
 		}
@@ -49,28 +45,40 @@ func TestGoodEndpoints(t *testing.T) {
 			}
 		}
 
-		out, err = TestHelper.LinkerdRun(cmd...)
-		if err != nil {
-			testutil.AnnotatedFatal(t, "unexpected error", err)
-		}
+		endpointCases := createTestCaseTable(controlNs, ns)
+		for _, endpointCase := range endpointCases {
+			testName := fmt.Sprintf("expect endpoints created for %s", endpointCase.name)
 
-		tpl := template.Must(template.ParseFiles(testDataPath + "/linkerd_endpoints.golden"))
-		vars := struct {
-			Ns         string
-			EndpointNs string
-		}{
-			controlNs,
-			ns,
-		}
-		var b bytes.Buffer
-		if err := tpl.Execute(&b, vars); err != nil {
-			testutil.AnnotatedFatalf(t, "failed to parse linkerd_endpoints.golden template", "failed to parse linkerd_endpoints.golden template: %s", err)
-		}
+			t.Run(testName, func(t *testing.T) {
+				err = TestHelper.RetryFor(5*time.Second, func() error {
+					out, err = TestHelper.LinkerdRun("diagnostics", "endpoints", endpointCase.authority, "-ojson")
+					if err != nil {
+						return fmt.Errorf("failed to get endpoints for %s: %s", endpointCase.authority, err)
+					}
 
-		r := regexp.MustCompile(b.String())
-		if !r.MatchString(out) {
-			testutil.AnnotatedErrorf(t, "unexpected output", "expected output:\n%s\nactual:\n%s", b.String(), out)
+					re := regexp.MustCompile(endpointCase.expectedRE)
+					if !re.MatchString(out) {
+						return fmt.Errorf("endpoint data does not match pattern\nexpected output:\n%s\nactual:\n%s", endpointCase.expectedRE, out)
+					}
+
+					matches := re.FindStringSubmatch(out)
+					if matches == nil || len(matches) < 2 {
+						return fmt.Errorf("invalid endpoint data\nexpected: \n%s\nactual: \n%s", endpointCase.expectedRE, out)
+					}
+
+					namespaceMatch := matches[1]
+					if namespaceMatch != endpointCase.ns {
+						return fmt.Errorf("endpoint namespace does not match\nexpected: %s, actual: %s", endpointCase.ns, namespaceMatch)
+					}
+
+					return nil
+				})
+				if err != nil {
+					testutil.AnnotatedErrorf(t, "unexpected error", "unexpected error: %v", err)
+				}
+			})
 		}
+
 	})
 }
 
@@ -88,3 +96,64 @@ func TestBadEndpoints(t *testing.T) {
 		testutil.AnnotatedErrorf(t, "unexpected error string", "unexpected error string: %s", stderrOut[0])
 	}
 }
+
+func createTestCaseTable(controlNs, endpointNs string) []testCase {
+	return []testCase{
+		{
+			name:      "linkerd-dst",
+			authority: fmt.Sprintf("linkerd-dst.%s.svc.cluster.local:8086", controlNs),
+			expectedRE: `\[
+  \{
+    "namespace": "(\S*)",
+    "ip": "\d+\.\d+\.\d+\.\d+",
+    "port": 8086,
+    "pod": "linkerd\-destination\-[a-f0-9]+\-[a-z0-9]+",
+    "service": "linkerd\-dst\.\S*"
+  \}
+\]`,
+			ns: controlNs,
+		},
+		{
+			name:      "linkerd-identity",
+			authority: fmt.Sprintf("linkerd-identity.%s.svc.cluster.local:8080", controlNs),
+			expectedRE: `\[
+  \{
+    "namespace": "(\S*)",
+    "ip": "\d+\.\d+\.\d+\.\d+",
+    "port": 8080,
+    "pod": "linkerd\-identity\-[a-f0-9]+\-[a-z0-9]+",
+    "service": "linkerd\-identity\.\S*"
+  \}
+\]`,
+			ns: controlNs,
+		},
+		{
+			name:      "linkerd-proxy-injector",
+			authority: fmt.Sprintf("linkerd-proxy-injector.%s.svc.cluster.local:443", controlNs),
+			expectedRE: `\[
+  \{
+    "namespace": "(\S*)",
+    "ip": "\d+\.\d+\.\d+\.\d+",
+    "port": 8443,
+    "pod": "linkerd\-proxy\-injector-[a-f0-9]+\-[a-z0-9]+",
+    "service": "linkerd\-proxy\-injector\.\S*"
+  \}
+\]`,
+			ns: controlNs,
+		},
+		{
+			name:      "nginx",
+			authority: fmt.Sprintf("nginx.%s.svc.cluster.local:8080", endpointNs),
+			expectedRE: `\[
+  \{
+    "namespace": "(\S*)",
+    "ip": "\d+\.\d+\.\d+\.\d+",
+    "port": 8080,
+    "pod": "nginx\-[a-f0-9]+\-[a-z0-9]+",
+    "service": "nginx\.\S*"
+  \}
+\]`,
+			ns: endpointNs,
+		},
+	}
+}
diff --git a/test/integration/endpoints/testdata/external_prometheus/linkerd_endpoints.golden b/test/integration/endpoints/testdata/external_prometheus/linkerd_endpoints.golden
deleted file mode 100644
index 85678ba98..000000000
--- a/test/integration/endpoints/testdata/external_prometheus/linkerd_endpoints.golden
+++ /dev/null
@@ -1,30 +0,0 @@
-\[
-  \{
-    "namespace": "external\-prometheus",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 9090,
-    "pod": "prometheus\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "prometheus.external\-prometheus"
-  \},
-  \{
-    "namespace": "{{.Ns}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8086,
-    "pod": "linkerd\-destination\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "linkerd\-dst\.{{.Ns}}"
-  \},
-  \{
-    "namespace": "{{.Ns}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8080,
-    "pod": "linkerd\-identity\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "linkerd\-identity\.{{.Ns}}"
-  \},
-  \{
-    "namespace": "{{.Ns}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8443,
-    "pod": "linkerd\-proxy\-injector-[a-f0-9]+\-[a-z0-9]+",
-    "service": "linkerd\-proxy\-injector\.{{.Ns}}"
-  \}
-\]
diff --git a/test/integration/endpoints/testdata/linkerd_endpoints.golden b/test/integration/endpoints/testdata/linkerd_endpoints.golden
deleted file mode 100644
index 2d7bf22d5..000000000
--- a/test/integration/endpoints/testdata/linkerd_endpoints.golden
+++ /dev/null
@@ -1,30 +0,0 @@
-\[
-  \{
-    "namespace": "{{.Ns}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8086,
-    "pod": "linkerd\-destination\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "linkerd\-dst\.{{.Ns}}"
-  \},
-  \{
-    "namespace": "{{.Ns}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8080,
-    "pod": "linkerd\-identity\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "linkerd\-identity\.{{.Ns}}"
-  \},
-  \{
-    "namespace": "{{.Ns}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8443,
-    "pod": "linkerd\-proxy\-injector-[a-f0-9]+\-[a-z0-9]+",
-    "service": "linkerd\-proxy\-injector\.{{.Ns}}"
-  \},
-  \{
-    "namespace": "{{.EndpointNs}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8080,
-    "pod": "nginx\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "nginx\.{{.EndpointNs}}"
-  \}
-\]
diff --git a/test/integration/install_test.go b/test/integration/install_test.go
index 7492051f1..964713870 100644
--- a/test/integration/install_test.go
+++ b/test/integration/install_test.go
@@ -43,7 +43,6 @@ var (
 
 	linkerdSvcEdge = []testutil.Service{
 		{Namespace: "linkerd", Name: "linkerd-dst"},
-		{Namespace: "linkerd-viz", Name: "grafana"},
 		{Namespace: "linkerd", Name: "linkerd-identity"},
 		{Namespace: "linkerd-viz", Name: "web"},
 		{Namespace: "linkerd-viz", Name: "tap"},
@@ -452,7 +451,10 @@ func TestInstallOrUpgradeCli(t *testing.T) {
 		testutil.AnnotatedFatal(t, "'linkerd viz install' command failed", err)
 	}
 
-	out, err = TestHelper.KubectlApply(out, "")
+	out, err = TestHelper.KubectlApplyWithArgs(out, []string{
+		"--prune",
+		"-l", "linkerd.io/extension=viz",
+	}...)
 	if err != nil {
 		testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
 			"'kubectl apply' command failed\n%s", out)
@@ -510,7 +512,6 @@ func helmOverridesEdge(root *tls.CA) ([]string, []string) {
 			"--set", "tap.image.registry="+override,
 			"--set", "tapInjector.image.registry="+override,
 			"--set", "dashboard.image.registry="+override,
-			"--set", "grafana.image.registry="+override,
 		)
 	}
 
@@ -1248,6 +1249,20 @@ func TestRestarts(t *testing.T) {
 	}
 }
 
+// TestCleanUp deletes the resources used in the above tests
+func TestCleanUp(t *testing.T) {
+	ctx := context.Background()
+	for _, tc := range injectionCases {
+		tc := tc // pin
+		t.Run(tc.ns, func(t *testing.T) {
+			prefixedNs := TestHelper.GetTestNamespace(tc.ns)
+			if err := TestHelper.DeleteNamespaceIfExists(ctx, prefixedNs); err != nil {
+				testutil.AnnotatedFatal(t, "Deleting namespace failed", err)
+			}
+		})
+	}
+}
+
 func TestCheckMulticluster(t *testing.T) {
 	if TestHelper.GetMulticlusterHelmReleaseName() != "" || TestHelper.Multicluster() {
 		cmd := []string{"multicluster", "check", "--wait=60m"}
diff --git a/test/integration/opaqueports/opaque_ports_test.go b/test/integration/opaqueports/opaque_ports_test.go
index 30ee4ef88..604b09c69 100644
--- a/test/integration/opaqueports/opaque_ports_test.go
+++ b/test/integration/opaqueports/opaque_ports_test.go
@@ -45,151 +45,146 @@ func TestMain(m *testing.M) {
 //////////////////////
 
 func TestOpaquePorts(t *testing.T) {
-	ctx := context.Background()
-
-	opaquePortsNs := TestHelper.GetTestNamespace("opaque-ports-test")
-	err := TestHelper.CreateDataPlaneNamespaceIfNotExists(ctx, opaquePortsNs, nil)
-	if err != nil {
-		testutil.AnnotatedFatalf(t, fmt.Sprintf("failed to create %s namespace", opaquePortsNs),
-			"failed to create %s namespace: %s", opaquePortsNs, err)
-	}
-
-	out, err := TestHelper.LinkerdRun("inject", "testdata/opaque_ports_application.yaml")
-	if err != nil {
-		testutil.AnnotatedFatal(t, "'linkerd inject' command failed", err)
-	}
-	out, err = TestHelper.KubectlApply(out, opaquePortsNs)
-	if err != nil {
-		testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
-			"'kubectl apply' command failed\n%s", out)
-	}
 
-	// Check that the opaque pod test application started correctly.
-	if err := TestHelper.CheckPods(ctx, opaquePortsNs, opaquePodApp, 1); err != nil {
-		if rce, ok := err.(*testutil.RestartCountError); ok {
-			testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
-		} else {
-			testutil.AnnotatedError(t, "CheckPods timed-out", err)
+	ctx := context.Background()
+	TestHelper.WithDataPlaneNamespace(ctx, "opaque-ports-test", map[string]string{}, t, func(t *testing.T, opaquePortsNs string) {
+		out, err := TestHelper.LinkerdRun("inject", "testdata/opaque_ports_application.yaml")
+		if err != nil {
+			testutil.AnnotatedFatal(t, "'linkerd inject' command failed", err)
 		}
-	}
-
-	// Check that the opaque service test application started correctly.
-	if err := TestHelper.CheckPods(ctx, opaquePortsNs, opaqueSvcApp, 1); err != nil {
-		if rce, ok := err.(*testutil.RestartCountError); ok {
-			testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
-		} else {
-			testutil.AnnotatedError(t, "CheckPods timed-out", err)
+		out, err = TestHelper.KubectlApply(out, opaquePortsNs)
+		if err != nil {
+			testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
+				"'kubectl apply' command failed\n%s", out)
 		}
-	}
 
-	// Check that the unmeshed opaque service application started successfully.
-	if err := TestHelper.CheckPods(ctx, opaquePortsNs, opaqueUnmeshedSvcPod, 1); err != nil {
-		if rce, ok := err.(*testutil.RestartCountError); ok {
-			testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
-		} else {
-			testutil.AnnotatedError(t, "CheckPods timed-out", err)
+		// Check that the opaque pod test application started correctly.
+		if err := TestHelper.CheckPods(ctx, opaquePortsNs, opaquePodApp, 1); err != nil {
+			if rce, ok := err.(*testutil.RestartCountError); ok {
+				testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
+			} else {
+				testutil.AnnotatedError(t, "CheckPods timed-out", err)
+			}
 		}
-	}
 
-	// Use a short time window for these tests to get rid of transient errors
-	// associated with slow cooker warm-up and initialization.
-	t.Run("expect absent HTTP outbound requests for opaque-pod slow cooker", func(t *testing.T) {
-		// Check the slow cooker metrics
-		err := TestHelper.RetryFor(30*time.Second, func() error {
-			pods, err := TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{"app": opaquePodSC})
-			if err != nil {
-				return fmt.Errorf("error getting pods\n%s", err)
-			}
-			metrics, err := getPodMetrics(pods[0], opaquePortsNs)
-			if err != nil {
-				return fmt.Errorf("error getting metrics for pod\n%s", err)
-			}
-			if httpRequestTotalMetricRE.MatchString(metrics) {
-				return fmt.Errorf("expected not to find HTTP outbound requests when pod is opaque\n%s", metrics)
+		// Check that the opaque service test application started correctly.
+		if err := TestHelper.CheckPods(ctx, opaquePortsNs, opaqueSvcApp, 1); err != nil {
+			if rce, ok := err.(*testutil.RestartCountError); ok {
+				testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
+			} else {
+				testutil.AnnotatedError(t, "CheckPods timed-out", err)
 			}
-			// Check the application metrics
-			pods, err = TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{"app": opaquePodApp})
-			if err != nil {
-				return fmt.Errorf("error getting pods\n%s", err)
+		}
+
+		// Check that the unmeshed opaque service application started successfully.
+		if err := TestHelper.CheckPods(ctx, opaquePortsNs, opaqueUnmeshedSvcPod, 1); err != nil {
+			if rce, ok := err.(*testutil.RestartCountError); ok {
+				testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
+			} else {
+				testutil.AnnotatedError(t, "CheckPods timed-out", err)
 			}
-			metrics, err = getPodMetrics(pods[0], opaquePortsNs)
+		}
+
+		// Use a short time window for these tests to get rid of transient errors
+		// associated with slow cooker warm-up and initialization.
+		t.Run("expect absent HTTP outbound requests for opaque-pod slow cooker", func(t *testing.T) {
+			// Check the slow cooker metrics
+			err := TestHelper.RetryFor(30*time.Second, func() error {
+				pods, err := TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{"app": opaquePodSC})
+				if err != nil {
+					return fmt.Errorf("error getting pods\n%s", err)
+				}
+				metrics, err := getPodMetrics(pods[0], opaquePortsNs)
+				if err != nil {
+					return fmt.Errorf("error getting metrics for pod\n%s", err)
+				}
+				if httpRequestTotalMetricRE.MatchString(metrics) {
+					return fmt.Errorf("expected not to find HTTP outbound requests when pod is opaque\n%s", metrics)
+				}
+				// Check the application metrics
+				pods, err = TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{"app": opaquePodApp})
+				if err != nil {
+					return fmt.Errorf("error getting pods\n%s", err)
+				}
+				metrics, err = getPodMetrics(pods[0], opaquePortsNs)
+				if err != nil {
+					return fmt.Errorf("error getting metrics for pod\n%s", err)
+				}
+				if !tcpMetricRE.MatchString(metrics) {
+					return fmt.Errorf("failed to find expected TCP metric when pod is opaque\n%s", metrics)
+				}
+
+				return nil
+			})
+
 			if err != nil {
-				return fmt.Errorf("error getting metrics for pod\n%s", err)
+				testutil.AnnotatedFatalf(t, "unexpected metric output", "unexpected metric output: %v", err)
 			}
-			if !tcpMetricRE.MatchString(metrics) {
-				return fmt.Errorf("failed to find expected TCP metric when pod is opaque\n%s", metrics)
-			}
-
-			return nil
 		})
 
-		if err != nil {
-			testutil.AnnotatedFatalf(t, "unexpected metric output", "unexpected metric output: %v", err)
-		}
-	})
+		t.Run("expect inbound TCP connection metric with expected TLS identity for opaque service app", func(t *testing.T) {
+			// Check the slow cooker metrics
+			err := TestHelper.RetryFor(30*time.Second, func() error {
+				pods, err := TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{"app": opaqueSvcSC})
+				if err != nil {
+					return fmt.Errorf("error getting pods\n%s", err)
+				}
+				metrics, err := getPodMetrics(pods[0], opaquePortsNs)
+				if err != nil {
+					return fmt.Errorf("error getting metrics for pod\n%s", err)
+				}
+				if httpRequestTotalMetricRE.MatchString(metrics) {
+					return fmt.Errorf("expected not to find HTTP outbound requests when service is opaque\n%s", metrics)
+				}
+				// Check the application metrics
+				pods, err = TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{"app": opaqueSvcApp})
+				if err != nil {
+					return fmt.Errorf("error getting pods\n%s", err)
+				}
+				metrics, err = getPodMetrics(pods[0], opaquePortsNs)
+				if err != nil {
+					return fmt.Errorf("error getting metrics for pod\n%s", err)
+				}
+				if !tcpMetricRE.MatchString(metrics) {
+					return fmt.Errorf("failed to find expected TCP metric when pod is opaque\n%s", metrics)
+				}
+
+				return nil
+			})
 
-	t.Run("expect inbound TCP connection metric with expected TLS identity for opaque service app", func(t *testing.T) {
-		// Check the slow cooker metrics
-		err := TestHelper.RetryFor(30*time.Second, func() error {
-			pods, err := TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{"app": opaqueSvcSC})
-			if err != nil {
-				return fmt.Errorf("error getting pods\n%s", err)
-			}
-			metrics, err := getPodMetrics(pods[0], opaquePortsNs)
 			if err != nil {
-				return fmt.Errorf("error getting metrics for pod\n%s", err)
-			}
-			if httpRequestTotalMetricRE.MatchString(metrics) {
-				return fmt.Errorf("expected not to find HTTP outbound requests when service is opaque\n%s", metrics)
-			}
-			// Check the application metrics
-			pods, err = TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{"app": opaqueSvcApp})
-			if err != nil {
-				return fmt.Errorf("error getting pods\n%s", err)
-			}
-			metrics, err = getPodMetrics(pods[0], opaquePortsNs)
-			if err != nil {
-				return fmt.Errorf("error getting metrics for pod\n%s", err)
-			}
-			if !tcpMetricRE.MatchString(metrics) {
-				return fmt.Errorf("failed to find expected TCP metric when pod is opaque\n%s", metrics)
+				testutil.AnnotatedFatalf(t, "unexpected metric output", "unexpected metric output: %v", err)
 			}
-
-			return nil
 		})
 
-		if err != nil {
-			testutil.AnnotatedFatalf(t, "unexpected metric output", "unexpected metric output: %v", err)
-		}
-	})
+		t.Run("expect inbound TCP connection metric with no TLS identity for traffic between meshed and unmeshed opaque service", func(t *testing.T) {
+			// Slow cooker is meshed, should have valid outbound TCP metric, valid
+			// inbound TCP metric and no HTTP metric.
+			err := TestHelper.RetryFor(30*time.Second, func() error {
+				pods, err := TestHelper.GetPods(ctx, opaquePortsNs,
+					map[string]string{"app": opaqueUnmeshedSvcSC})
+				if err != nil {
+					return fmt.Errorf("error getting pods\n%s", err)
+				}
+				metrics, err := getPodMetrics(pods[0], opaquePortsNs)
+				if err != nil {
+					return fmt.Errorf("error getting metrics for pod\n%s", err)
+				}
+
+				if httpRequestTotalUnmeshedRE.MatchString(metrics) {
+					return fmt.Errorf("expected not to find HTTP outbound requests when service is opaque\n%s", metrics)
+				}
+				if !tcpMetricOutUnmeshedRE.MatchString(metrics) {
+					return fmt.Errorf("failed to find expected TCP outbound metric when pod is opaque\n%s", metrics)
+				}
+
+				return nil
+			})
 
-	t.Run("expect inbound TCP connection metric with no TLS identity for traffic between meshed and unmeshed opaque service", func(t *testing.T) {
-		// Slow cooker is meshed, should have valid outbound TCP metric, valid
-		// inbound TCP metric and no HTTP metric.
-		err := TestHelper.RetryFor(30*time.Second, func() error {
-			pods, err := TestHelper.GetPods(ctx, opaquePortsNs,
-				map[string]string{"app": opaqueUnmeshedSvcSC})
 			if err != nil {
-				return fmt.Errorf("error getting pods\n%s", err)
+				testutil.AnnotatedFatalf(t, "unexpected metric output", "unexpected metric output: %v", err)
 			}
-			metrics, err := getPodMetrics(pods[0], opaquePortsNs)
-			if err != nil {
-				return fmt.Errorf("error getting metrics for pod\n%s", err)
-			}
-
-			if httpRequestTotalUnmeshedRE.MatchString(metrics) {
-				return fmt.Errorf("expected not to find HTTP outbound requests when service is opaque\n%s", metrics)
-			}
-			if !tcpMetricOutUnmeshedRE.MatchString(metrics) {
-				return fmt.Errorf("failed to find expected TCP outbound metric when pod is opaque\n%s", metrics)
-			}
-
-			return nil
 		})
-
-		if err != nil {
-			testutil.AnnotatedFatalf(t, "unexpected metric output", "unexpected metric output: %v", err)
-		}
 	})
 }
 
diff --git a/test/integration/routes/routes_test.go b/test/integration/routes/routes_test.go
index 6a03d4198..5e89212b3 100644
--- a/test/integration/routes/routes_test.go
+++ b/test/integration/routes/routes_test.go
@@ -62,7 +62,6 @@ func TestRoutes(t *testing.T) {
 	}
 
 	vizRouteStrings := []testCase{
-		{"grafana", 13},
 		{"metrics-api", 9},
 		{"tap", 4},
 		{"tap-injector", 2},
@@ -82,19 +81,4 @@ func TestRoutes(t *testing.T) {
 				"expected %d occurrences of \"%s\", got %d\n%s", r.c, r.s, count, out)
 		}
 	}
-
-	// smoke test / bb routes
-	prefixedNs := TestHelper.GetTestNamespace("smoke-test")
-	cmd = []string{"viz", "routes", "--namespace", prefixedNs, "deploy"}
-	golden := "routes.smoke.golden"
-
-	out, err = TestHelper.LinkerdRun(cmd...)
-	if err != nil {
-		testutil.AnnotatedFatal(t, "'linkerd routes' command failed", err)
-	}
-
-	err = TestHelper.ValidateOutput(out, golden)
-	if err != nil {
-		testutil.AnnotatedFatalf(t, "received unexpected output", "received unexpected output\n%s", err)
-	}
 }
diff --git a/test/integration/routes/testdata/routes.smoke.golden b/test/integration/routes/testdata/routes.smoke.golden
deleted file mode 100644
index 38e9c6ed5..000000000
--- a/test/integration/routes/testdata/routes.smoke.golden
+++ /dev/null
@@ -1,9 +0,0 @@
-==> deployment/smoke-test-gateway <==
-ROUTE                      SERVICE   SUCCESS   RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
-[DEFAULT]   smoke-test-gateway-svc         -     -             -             -             -
-
-==> deployment/smoke-test-terminus <==
-ROUTE                         SERVICE   SUCCESS   RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
-[DEFAULT]     smoke-test-terminus-svc         -     -             -             -             -
-theFunction   smoke-test-terminus-svc         -     -             -             -             -
-
diff --git a/test/integration/stat/stat_test.go b/test/integration/stat/stat_test.go
index 2d471ea7e..925c2e853 100644
--- a/test/integration/stat/stat_test.go
+++ b/test/integration/stat/stat_test.go
@@ -140,7 +140,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
 				args: []string{"viz", "stat", "deploy", "-n", TestHelper.GetVizNamespace()},
 				expectedRows: map[string]string{
 					"metrics-api":  "1/1",
-					"grafana":      "1/1",
 					"prometheus":   "1/1",
 					"tap":          "1/1",
 					"web":          "1/1",
@@ -150,7 +149,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
 			{
 				args: []string{"viz", "stat", "ns", TestHelper.GetVizNamespace()},
 				expectedRows: map[string]string{
-					TestHelper.GetVizNamespace(): "6/6",
+					TestHelper.GetVizNamespace(): "5/5",
 				},
 			},
 			{
@@ -172,7 +171,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
 				args: []string{"viz", "stat", "deploy", "-n", TestHelper.GetVizNamespace()},
 				expectedRows: map[string]string{
 					"metrics-api":  "1/1",
-					"grafana":      "1/1",
 					"tap":          "1/1",
 					"web":          "1/1",
 					"tap-injector": "1/1",
@@ -181,7 +179,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
 			{
 				args: []string{"viz", "stat", "ns", TestHelper.GetVizNamespace()},
 				expectedRows: map[string]string{
-					TestHelper.GetVizNamespace(): "5/5",
+					TestHelper.GetVizNamespace(): "4/4",
 				},
 			},
 		}...,
diff --git a/test/integration/testdata/external_prometheus.yaml b/test/integration/testdata/external_prometheus.yaml
index af557fdd2..4af8efe32 100644
--- a/test/integration/testdata/external_prometheus.yaml
+++ b/test/integration/testdata/external_prometheus.yaml
@@ -62,7 +62,7 @@ data:
       kubernetes_sd_configs:
       - role: pod
         namespaces:
-          names: ['linkerd']
+          names: ['grafana']
       relabel_configs:
       - source_labels:
         - __meta_kubernetes_pod_container_name
diff --git a/test/integration/tracing/tracing_test.go b/test/integration/tracing/tracing_test.go
index be5732c8c..91773cd66 100644
--- a/test/integration/tracing/tracing_test.go
+++ b/test/integration/tracing/tracing_test.go
@@ -56,16 +56,6 @@ func TestTracing(t *testing.T) {
 		t.Skip("Skipping due to flakiness. See linkerd/linkerd2#7538")
 	}
 
-	// cleanup cluster before proceeding
-	namespaces := []string{"smoke-test", "smoke-test-manual", "smoke-test-ann", "opaque-ports-test"}
-	for _, ns := range namespaces {
-		prefixedNs := TestHelper.GetTestNamespace(ns)
-		if err := TestHelper.DeleteNamespaceIfExists(ctx, prefixedNs); err != nil {
-			testutil.AnnotatedFatalf(t, "error deleting namespace",
-				"error deleting namespace '%s': %s", prefixedNs, err)
-		}
-	}
-
 	// linkerd-jaeger extension
 	tracingNs := "linkerd-jaeger"
 	out, err := TestHelper.LinkerdRun("jaeger", "install")
diff --git a/testutil/test_helper.go b/testutil/test_helper.go
index f342a3225..3312c2a60 100644
--- a/testutil/test_helper.go
+++ b/testutil/test_helper.go
@@ -85,7 +85,6 @@ var LinkerdDeployReplicasStable = LinkerdDeployReplicasEdge
 var LinkerdVizDeployReplicas = map[string]DeploySpec{
 	"prometheus":   {"linkerd-viz", 1},
 	"metrics-api":  {"linkerd-viz", 1},
-	"grafana":      {"linkerd-viz", 1},
 	"tap":          {"linkerd-viz", 1},
 	"tap-injector": {"linkerd-viz", 1},
 	"web":          {"linkerd-viz", 1},
diff --git a/viz/cmd/install_test.go b/viz/cmd/install_test.go
index bf3d6b289..82a35d852 100644
--- a/viz/cmd/install_test.go
+++ b/viz/cmd/install_test.go
@@ -65,7 +65,6 @@ func TestRender(t *testing.T) {
 			map[string]interface{}{
 				"prometheus": proxyResources,
 				"tap":        proxyResources,
-				"grafana":    proxyResources,
 				"dashboard":  proxyResources,
 			},
 			"install_proxy_resources.golden",
@@ -83,16 +82,10 @@ func TestRender(t *testing.T) {
 						"tag":      "stable-9.2",
 					},
 				},
+				"grafana": map[string]interface{}{"url": "grafana.grafana:3000"},
 			},
 			"install_default_overrides.golden",
 		},
-		{
-			map[string]interface{}{
-				"grafana":    map[string]interface{}{"enabled": false},
-				"grafanaUrl": "external-grafana.com",
-			},
-			"install_grafana_disabled.golden",
-		},
 	}
 
 	for i, tc := range testCases {
diff --git a/viz/cmd/testdata/install_default.golden b/viz/cmd/testdata/install_default.golden
index 9dda03e45..d4de5f160 100644
--- a/viz/cmd/testdata/install_default.golden
+++ b/viz/cmd/testdata/install_default.golden
@@ -63,19 +63,6 @@ metadata:
     component: metrics-api
 ---
 ###
-### Grafana RBAC
-###
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
----
-###
 ### Prometheus RBAC
 ###
 kind: ClusterRole
@@ -562,207 +549,6 @@ spec:
       - name: prometheus
 ---
 ###
-### Grafana
-###
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: grafana-config
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-data:
-  grafana.ini: |-
-    instance_name = grafana
-    [server]
-    root_url = %(protocol)s://%(domain)s:/grafana/
-    [auth]
-    disable_login_form = true
-    [auth.anonymous]
-    enabled = true
-    org_role = Editor
-    [auth.basic]
-    enabled = false
-    [analytics]
-    check_for_updates = false
-    [panels]
-    disable_sanitize_html = true
-    [log]
-    mode = console
-    [log.console]
-    format = text
-    level = info
-  datasources.yaml: |-
-    apiVersion: 1
-    datasources:
-    - name: prometheus
-      type: prometheus
-      access: proxy
-      orgId: 1
-      url: http://prometheus.linkerd-viz.svc.cluster.local:9090
-      isDefault: true
-      jsonData:
-        timeInterval: "5s"
-      version: 1
-      editable: true
-
-  dashboards.yaml: |-
-    apiVersion: 1
-    providers:
-    - name: 'default'
-      orgId: 1
-      folder: ''
-      type: file
-      disableDeletion: true
-      editable: true
-      options:
-        path: /var/lib/grafana/dashboards
-        homeDashboardId: linkerd-top-line
----
-kind: Service
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: grafana
-  ports:
-  - name: http
-    port: 3000
-    targetPort: 3000
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: grafana
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: grafana
-    namespace: linkerd-viz
-  name: grafana
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-      namespace: linkerd-viz
-  template:
-    metadata:
-      annotations:
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: grafana
-        namespace: linkerd-viz
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - env:
-        - name: GF_PATHS_DATA
-          value: /data
-        # Force using the go-based DNS resolver instead of the OS' to avoid failures in some environments
-        # see https://github.com/grafana/grafana/issues/20096
-        - name: GODEBUG
-          value: netdns=go
-        image: cr.l5d.io/linkerd/grafana:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-          initialDelaySeconds: 30
-        name: grafana
-        ports:
-        - containerPort: 3000
-          name: http
-        readinessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-        resources:
-        securityContext:
-          runAsUser: 472
-        volumeMounts:
-        - mountPath: /data
-          name: data
-        - mountPath: /etc/grafana
-          name: grafana-config
-          readOnly: true
-      serviceAccountName: grafana
-      volumes:
-      - emptyDir: {}
-        name: data
-      - configMap:
-          items:
-          - key: grafana.ini
-            path: grafana.ini
-          - key: datasources.yaml
-            path: provisioning/datasources/datasources.yaml
-          - key: dashboards.yaml
-            path: provisioning/dashboards/dashboards.yaml
-          name: grafana-config
-        name: grafana-config
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-  port: http
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: grafana
-  client:
-    # web, prometheus and the kubelet probes
-    unauthenticated: true
----
-###
 ### Prometheus
 ###
 kind: ConfigMap
@@ -792,17 +578,6 @@ data:
       static_configs:
       - targets: ['localhost:9090']
 
-    - job_name: 'grafana'
-      kubernetes_sd_configs:
-      - role: pod
-        namespaces:
-          names: ['linkerd-viz']
-      relabel_configs:
-      - source_labels:
-        - __meta_kubernetes_pod_container_name
-        action: keep
-        regex: ^grafana$
-
     #  Required for: https://grafana.com/grafana/dashboards/315
     - job_name: 'kubernetes-nodes-cadvisor'
       scheme: https
@@ -1187,6 +962,8 @@ apiVersion: v1
 metadata:
   name: tap-injector
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 ---
 apiVersion: admissionregistration.k8s.io/v1
 kind: MutatingWebhookConfiguration
@@ -1258,7 +1035,7 @@ spec:
   template:
     metadata:
       annotations:
-        checksum/config: 07c5bcd8a9872945d91827ee20c9412909a30ba3944731413022668c59067649
+        checksum/config: 390143015ec83a86ded6630634da5834c8ac7700b93d486a7dc101a15cb87f15
         linkerd.io/created-by: linkerd/helm dev-undefined
         linkerd.io/inject: enabled
       labels:
@@ -1406,7 +1183,6 @@ spec:
       - args:
         - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085
         - -cluster-domain=cluster.local
-        - -grafana-addr=grafana.linkerd-viz.svc.cluster.local:3000
         - -controller-namespace=linkerd
         - -viz-namespace=linkerd-viz
         - -log-level=info
@@ -1440,6 +1216,8 @@ kind: ServiceProfile
 metadata:
   name: metrics-api.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/StatSummary
@@ -1476,6 +1254,8 @@ kind: ServiceProfile
 metadata:
   name: prometheus.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/query
@@ -1490,55 +1270,3 @@ spec:
     condition:
       method: GET
       pathRegex: /api/v1/series
----
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: grafana.linkerd-viz.svc.cluster.local
-  namespace: linkerd-viz
-spec:
-  routes:
-  - name: GET /api/annotations
-    condition:
-      method: GET
-      pathRegex: /api/annotations
-  - name: GET /api/dashboards/tags
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/tags
-  - name: GET /api/dashboards/uid/{uid}
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/uid/.*
-  - name: GET /api/dashboard/{dashboard}
-    condition:
-      method: GET
-      pathRegex: /api/dashboard/.*
-  - name: GET /api/datasources/proxy/1/api/v1/series
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/series
-  - name: GET /api/datasources/proxy/1/api/v1/query_range
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/query_range
-  - name: GET /api/search
-    condition:
-      method: GET
-      pathRegex: /api/search
-  - name: GET /d/{uid}/{dashboard-name}
-    condition:
-      method: GET
-      pathRegex: /d/[^/]*/.*
-  - name: GET /public/build/{style}.css
-    condition:
-      method: GET
-      pathRegex: /public/build/.*\.css
-  - name: GET /public/fonts/{font}
-    condition:
-      method: GET
-      pathRegex: /public/fonts/.*
-  - name: GET /public/img/{img}
-    condition:
-      method: GET
-      pathRegex: /public/img/.*
diff --git a/viz/cmd/testdata/install_default_overrides.golden b/viz/cmd/testdata/install_default_overrides.golden
index e695f85d9..8958594ad 100644
--- a/viz/cmd/testdata/install_default_overrides.golden
+++ b/viz/cmd/testdata/install_default_overrides.golden
@@ -63,19 +63,6 @@ metadata:
     component: metrics-api
 ---
 ###
-### Grafana RBAC
-###
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
----
-###
 ### Prometheus RBAC
 ###
 kind: ClusterRole
@@ -562,207 +549,6 @@ spec:
       - name: prometheus
 ---
 ###
-### Grafana
-###
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: grafana-config
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-data:
-  grafana.ini: |-
-    instance_name = grafana
-    [server]
-    root_url = %(protocol)s://%(domain)s:/grafana/
-    [auth]
-    disable_login_form = true
-    [auth.anonymous]
-    enabled = true
-    org_role = Editor
-    [auth.basic]
-    enabled = false
-    [analytics]
-    check_for_updates = false
-    [panels]
-    disable_sanitize_html = true
-    [log]
-    mode = console
-    [log.console]
-    format = text
-    level = debug
-  datasources.yaml: |-
-    apiVersion: 1
-    datasources:
-    - name: prometheus
-      type: prometheus
-      access: proxy
-      orgId: 1
-      url: http://prometheus.linkerd-viz.svc.cluster.local:9090
-      isDefault: true
-      jsonData:
-        timeInterval: "5s"
-      version: 1
-      editable: true
-
-  dashboards.yaml: |-
-    apiVersion: 1
-    providers:
-    - name: 'default'
-      orgId: 1
-      folder: ''
-      type: file
-      disableDeletion: true
-      editable: true
-      options:
-        path: /var/lib/grafana/dashboards
-        homeDashboardId: linkerd-top-line
----
-kind: Service
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: grafana
-  ports:
-  - name: http
-    port: 3000
-    targetPort: 3000
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: grafana
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: grafana
-    namespace: linkerd-viz
-  name: grafana
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-      namespace: linkerd-viz
-  template:
-    metadata:
-      annotations:
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: grafana
-        namespace: linkerd-viz
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - env:
-        - name: GF_PATHS_DATA
-          value: /data
-        # Force using the go-based DNS resolver instead of the OS' to avoid failures in some environments
-        # see https://github.com/grafana/grafana/issues/20096
-        - name: GODEBUG
-          value: netdns=go
-        image: gcr.io/linkerd/grafana:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-          initialDelaySeconds: 30
-        name: grafana
-        ports:
-        - containerPort: 3000
-          name: http
-        readinessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-        resources:
-        securityContext:
-          runAsUser: 472
-        volumeMounts:
-        - mountPath: /data
-          name: data
-        - mountPath: /etc/grafana
-          name: grafana-config
-          readOnly: true
-      serviceAccountName: grafana
-      volumes:
-      - emptyDir: {}
-        name: data
-      - configMap:
-          items:
-          - key: grafana.ini
-            path: grafana.ini
-          - key: datasources.yaml
-            path: provisioning/datasources/datasources.yaml
-          - key: dashboards.yaml
-            path: provisioning/dashboards/dashboards.yaml
-          name: grafana-config
-        name: grafana-config
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-  port: http
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: grafana
-  client:
-    # web, prometheus and the kubelet probes
-    unauthenticated: true
----
-###
 ### Prometheus
 ###
 kind: ConfigMap
@@ -792,17 +578,6 @@ data:
       static_configs:
       - targets: ['localhost:9090']
 
-    - job_name: 'grafana'
-      kubernetes_sd_configs:
-      - role: pod
-        namespaces:
-          names: ['linkerd-viz']
-      relabel_configs:
-      - source_labels:
-        - __meta_kubernetes_pod_container_name
-        action: keep
-        regex: ^grafana$
-
     #  Required for: https://grafana.com/grafana/dashboards/315
     - job_name: 'kubernetes-nodes-cadvisor'
       scheme: https
@@ -1187,6 +962,8 @@ apiVersion: v1
 metadata:
   name: tap-injector
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 ---
 apiVersion: admissionregistration.k8s.io/v1
 kind: MutatingWebhookConfiguration
@@ -1258,7 +1035,7 @@ spec:
   template:
     metadata:
       annotations:
-        checksum/config: 07c5bcd8a9872945d91827ee20c9412909a30ba3944731413022668c59067649
+        checksum/config: 390143015ec83a86ded6630634da5834c8ac7700b93d486a7dc101a15cb87f15
         linkerd.io/created-by: linkerd/helm dev-undefined
         linkerd.io/inject: enabled
       labels:
@@ -1406,7 +1183,7 @@ spec:
       - args:
         - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085
         - -cluster-domain=cluster.local
-        - -grafana-addr=grafana.linkerd-viz.svc.cluster.local:3000
+        - -grafana-addr=grafana.grafana:3000
         - -controller-namespace=linkerd
         - -viz-namespace=linkerd-viz
         - -log-level=debug
@@ -1440,6 +1217,8 @@ kind: ServiceProfile
 metadata:
   name: metrics-api.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/StatSummary
@@ -1476,6 +1255,8 @@ kind: ServiceProfile
 metadata:
   name: prometheus.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/query
@@ -1490,55 +1271,3 @@ spec:
     condition:
       method: GET
       pathRegex: /api/v1/series
----
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: grafana.linkerd-viz.svc.cluster.local
-  namespace: linkerd-viz
-spec:
-  routes:
-  - name: GET /api/annotations
-    condition:
-      method: GET
-      pathRegex: /api/annotations
-  - name: GET /api/dashboards/tags
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/tags
-  - name: GET /api/dashboards/uid/{uid}
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/uid/.*
-  - name: GET /api/dashboard/{dashboard}
-    condition:
-      method: GET
-      pathRegex: /api/dashboard/.*
-  - name: GET /api/datasources/proxy/1/api/v1/series
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/series
-  - name: GET /api/datasources/proxy/1/api/v1/query_range
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/query_range
-  - name: GET /api/search
-    condition:
-      method: GET
-      pathRegex: /api/search
-  - name: GET /d/{uid}/{dashboard-name}
-    condition:
-      method: GET
-      pathRegex: /d/[^/]*/.*
-  - name: GET /public/build/{style}.css
-    condition:
-      method: GET
-      pathRegex: /public/build/.*\.css
-  - name: GET /public/fonts/{font}
-    condition:
-      method: GET
-      pathRegex: /public/fonts/.*
-  - name: GET /public/img/{img}
-    condition:
-      method: GET
-      pathRegex: /public/img/.*
diff --git a/viz/cmd/testdata/install_grafana_disabled.golden b/viz/cmd/testdata/install_grafana_disabled.golden
deleted file mode 100644
index 2d02dc93d..000000000
--- a/viz/cmd/testdata/install_grafana_disabled.golden
+++ /dev/null
@@ -1,1269 +0,0 @@
----
-###
-### Linkerd Viz Extension Namespace
-###
-kind: Namespace
-apiVersion: v1
-metadata:
-  name: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-  annotations:
----
-###
-### Metrics API RBAC
-###
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-linkerd-viz-metrics-api
-  labels:
-    linkerd.io/extension: viz
-    component: metrics-api
-rules:
-- apiGroups: ["extensions", "apps"]
-  resources: ["daemonsets", "deployments", "replicasets", "statefulsets"]
-  verbs: ["list", "get", "watch"]
-- apiGroups: ["extensions", "batch"]
-  resources: ["cronjobs", "jobs"]
-  verbs: ["list" , "get", "watch"]
-- apiGroups: [""]
-  resources: ["pods", "endpoints", "services", "replicationcontrollers", "namespaces"]
-  verbs: ["list", "get", "watch"]
-- apiGroups: ["linkerd.io"]
-  resources: ["serviceprofiles"]
-  verbs: ["list", "get", "watch"]
-- apiGroups: ["policy.linkerd.io"]
-  resources: ["servers", "serverauthorizations"]
-  verbs: ["list", "get"]
----
-kind: ClusterRoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-linkerd-viz-metrics-api
-  labels:
-    linkerd.io/extension: viz
-    component: metrics-api
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: linkerd-linkerd-viz-metrics-api
-subjects:
-- kind: ServiceAccount
-  name: metrics-api
-  namespace: linkerd-viz
----
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: metrics-api
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: metrics-api
----
-###
-### Prometheus RBAC
-###
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-linkerd-viz-prometheus
-  labels:
-    linkerd.io/extension: viz
-    component: prometheus
-rules:
-- apiGroups: [""]
-  resources: ["nodes", "nodes/proxy", "pods"]
-  verbs: ["get", "list", "watch"]
----
-kind: ClusterRoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-linkerd-viz-prometheus
-  labels:
-    linkerd.io/extension: viz
-    component: prometheus
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: linkerd-linkerd-viz-prometheus
-subjects:
-- kind: ServiceAccount
-  name: prometheus
-  namespace: linkerd-viz
----
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: prometheus
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: prometheus
-    namespace: linkerd-viz
----
-###
-### Tap RBAC
-###
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-linkerd-viz-tap
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-rules:
-- apiGroups: [""]
-  resources: ["pods", "services", "replicationcontrollers", "namespaces", "nodes"]
-  verbs: ["list", "get", "watch"]
-- apiGroups: ["extensions", "apps"]
-  resources: ["daemonsets", "deployments", "replicasets", "statefulsets"]
-  verbs: ["list", "get", "watch"]
-- apiGroups: ["extensions", "batch"]
-  resources: ["cronjobs", "jobs"]
-  verbs: ["list" , "get", "watch"]
----
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-linkerd-viz-tap-admin
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-rules:
-- apiGroups: [""]
-  resources: ["namespaces"]
-  verbs: ["list"]
-- apiGroups: ["tap.linkerd.io"]
-  resources: ["*"]
-  verbs: ["watch"]
----
-kind: ClusterRoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-linkerd-viz-tap
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: linkerd-linkerd-viz-tap
-subjects:
-- kind: ServiceAccount
-  name: tap
-  namespace: linkerd-viz
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: linkerd-linkerd-viz-tap-auth-delegator
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: system:auth-delegator
-subjects:
-- kind: ServiceAccount
-  name: tap
-  namespace: linkerd-viz
----
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: tap
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-    namespace: linkerd-viz
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
-  name: linkerd-linkerd-viz-tap-auth-reader
-  namespace: kube-system
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-    namespace: linkerd-viz
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: Role
-  name: extension-apiserver-authentication-reader
-subjects:
-- kind: ServiceAccount
-  name: tap
-  namespace: linkerd-viz
----
-apiVersion: apiregistration.k8s.io/v1
-kind: APIService
-metadata:
-  name: v1alpha1.tap.linkerd.io
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-spec:
-  group: tap.linkerd.io
-  version: v1alpha1
-  groupPriorityMinimum: 1000
-  versionPriority: 100
-  service:
-    name: tap
-    namespace: linkerd-viz
-  caBundle: dGVzdC10YXAtY2EtYnVuZGxl
----
-###
-### Web RBAC
-###
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
-  name: web
-  namespace: linkerd
-  labels:
-    linkerd.io/extension: viz
-    component: web
-    namespace: linkerd
-rules:
-- apiGroups: [""]
-  resources: ["configmaps"]
-  verbs: ["get"]
-  resourceNames: ["linkerd-config"]
-- apiGroups: [""]
-  resources: ["namespaces", "configmaps"]
-  verbs: ["get"]
-- apiGroups: [""]
-  resources: ["serviceaccounts", "pods"]
-  verbs: ["list"]
-- apiGroups: ["apps"]
-  resources: ["replicasets"]
-  verbs: ["list"]
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
-  name: web
-  namespace: linkerd
-  labels:
-    linkerd.io/extension: viz
-    component: web
-    namespace: linkerd
-roleRef:
-  kind: Role
-  name: web
-  apiGroup: rbac.authorization.k8s.io
-subjects:
-- kind: ServiceAccount
-  name: web
-  namespace: linkerd-viz
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: linkerd-linkerd-viz-web-check
-  labels:
-    linkerd.io/extension: viz
-    component: web
-rules:
-- apiGroups: ["rbac.authorization.k8s.io"]
-  resources: ["clusterroles", "clusterrolebindings"]
-  verbs: ["list"]
-- apiGroups: ["apiextensions.k8s.io"]
-  resources: ["customresourcedefinitions"]
-  verbs: ["list"]
-- apiGroups: ["admissionregistration.k8s.io"]
-  resources: ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"]
-  verbs: ["list"]
-- apiGroups: ["linkerd.io"]
-  resources: ["serviceprofiles"]
-  verbs: ["list"]
-- apiGroups: [""]
-  resources: ["nodes"]
-  verbs: ["list"]
-- apiGroups: ["apiregistration.k8s.io"]
-  resources: ["apiservices"]
-  verbs: ["get"]
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: linkerd-linkerd-viz-web-check
-  labels:
-    linkerd.io/extension: viz
-    component: web
-roleRef:
-  kind: ClusterRole
-  name: linkerd-linkerd-viz-web-check
-  apiGroup: rbac.authorization.k8s.io
-subjects:
-- kind: ServiceAccount
-  name: web
-  namespace: linkerd-viz
----
-kind: ClusterRoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-linkerd-viz-web-admin
-  labels:
-    linkerd.io/extension: viz
-    component: web
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: linkerd-linkerd-viz-tap-admin
-subjects:
-- kind: ServiceAccount
-  name: web
-  namespace: linkerd-viz
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: linkerd-linkerd-viz-web-api
-  labels:
-    linkerd.io/extension: viz
-    component: web
-rules:
-- apiGroups: [""]
-  resources: ["namespaces"]
-  verbs: ["list"]
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: linkerd-linkerd-viz-web-api
-  labels:
-    linkerd.io/extension: viz
-    component: web
-roleRef:
-  kind: ClusterRole
-  name: linkerd-linkerd-viz-web-api
-  apiGroup: rbac.authorization.k8s.io
-subjects:
-- kind: ServiceAccount
-  name: web
-  namespace: linkerd-viz
----
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: web
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: web
-    namespace: linkerd-viz
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: admin
-  labels:
-    linkerd.io/extension: viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-  port: admin-http
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: admin
-  labels:
-    linkerd.io/extension: viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: admin
-  client:
-    # for kubelet probes and prometheus scraping
-    unauthenticated: true
-
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: proxy-admin
-  labels:
-    linkerd.io/extension: viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-  port: linkerd-admin
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: proxy-admin
-  labels:
-    linkerd.io/extension: viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: proxy-admin
-  client:
-    # for kubelet probes
-    unauthenticated: true
-
----
-###
-### Metrics API
-###
-kind: Service
-apiVersion: v1
-metadata:
-  name: metrics-api
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: metrics-api
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: metrics-api
-  ports:
-  - name: http
-    port: 8085
-    targetPort: 8085
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: metrics-api
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: metrics-api
-  name: metrics-api
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: metrics-api
-  template:
-    metadata:
-      annotations:
-        checksum/config: ae930acf3edba3cbf32c43dacfa4808bf679c5c932870cb2a2a46de1023f01c8
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: metrics-api
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - args:
-        - -controller-namespace=linkerd
-        - -log-level=info
-        - -log-format=plain
-        - -cluster-domain=cluster.local
-        - -prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090
-        image: cr.l5d.io/linkerd/metrics-api:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /ping
-            port: 9995
-          initialDelaySeconds: 10
-        name: metrics-api
-        ports:
-        - containerPort: 8085
-          name: http
-        - containerPort: 9995
-          name: admin-http
-        readinessProbe:
-          failureThreshold: 7
-          httpGet:
-            path: /ready
-            port: 9995
-        resources:
-        securityContext:
-          runAsUser: 2103
-      serviceAccountName: metrics-api
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: metrics-api
-  labels:
-    linkerd.io/extension: viz
-    component: metrics-api
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: metrics-api
-  port: http
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: metrics-api
-  labels:
-    linkerd.io/extension: viz
-    component: metrics-api
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: metrics-api
-  client:
-    meshTLS:
-      serviceAccounts:
-      - name: web
-      - name: prometheus
----
-###
-### Prometheus
-###
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: prometheus-config
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: prometheus
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-data:
-  prometheus.yml: |-
-    global:
-      evaluation_interval: 10s
-      scrape_interval: 10s
-      scrape_timeout: 10s
-
-    rule_files:
-    - /etc/prometheus/*_rules.yml
-    - /etc/prometheus/*_rules.yaml
-
-    scrape_configs:
-    - job_name: 'prometheus'
-      static_configs:
-      - targets: ['localhost:9090']
-
-    
-
-    #  Required for: https://grafana.com/grafana/dashboards/315
-    - job_name: 'kubernetes-nodes-cadvisor'
-      scheme: https
-      tls_config:
-        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
-        insecure_skip_verify: true
-      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
-      kubernetes_sd_configs:
-      - role: node
-      relabel_configs:
-      - action: labelmap
-        regex: __meta_kubernetes_node_label_(.+)
-      - target_label: __address__
-        replacement: kubernetes.default.svc:443
-      - source_labels: [__meta_kubernetes_node_name]
-        regex: (.+)
-        target_label: __metrics_path__
-        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
-      metric_relabel_configs:
-      - source_labels: [__name__]
-        regex: '(container|machine)_(cpu|memory|network|fs)_(.+)'
-        action: keep
-      - source_labels: [__name__]
-        regex: 'container_memory_failures_total' # unneeded large metric
-        action: drop
-
-    - job_name: 'linkerd-controller'
-      kubernetes_sd_configs:
-      - role: pod
-        namespaces:
-          names:
-          - 'linkerd'
-          - 'linkerd-viz'
-      relabel_configs:
-      - source_labels:
-        - __meta_kubernetes_pod_container_port_name
-        action: keep
-        regex: admin-http
-      - source_labels: [__meta_kubernetes_pod_container_name]
-        action: replace
-        target_label: component
-
-    - job_name: 'linkerd-service-mirror'
-      kubernetes_sd_configs:
-      - role: pod
-      relabel_configs:
-      - source_labels:
-        - __meta_kubernetes_pod_label_linkerd_io_control_plane_component
-        - __meta_kubernetes_pod_container_port_name
-        action: keep
-        regex: linkerd-service-mirror;admin-http$
-      - source_labels: [__meta_kubernetes_pod_container_name]
-        action: replace
-        target_label: component
-
-    - job_name: 'linkerd-proxy'
-      kubernetes_sd_configs:
-      - role: pod
-      relabel_configs:
-      - source_labels:
-        - __meta_kubernetes_pod_container_name
-        - __meta_kubernetes_pod_container_port_name
-        - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns
-        action: keep
-        regex: ^linkerd-proxy;linkerd-admin;linkerd$
-      - source_labels: [__meta_kubernetes_namespace]
-        action: replace
-        target_label: namespace
-      - source_labels: [__meta_kubernetes_pod_name]
-        action: replace
-        target_label: pod
-      # special case k8s' "job" label, to not interfere with prometheus' "job"
-      # label
-      # __meta_kubernetes_pod_label_linkerd_io_proxy_job=foo =>
-      # k8s_job=foo
-      - source_labels: [__meta_kubernetes_pod_label_linkerd_io_proxy_job]
-        action: replace
-        target_label: k8s_job
-      # drop __meta_kubernetes_pod_label_linkerd_io_proxy_job
-      - action: labeldrop
-        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_job
-      # __meta_kubernetes_pod_label_linkerd_io_proxy_deployment=foo =>
-      # deployment=foo
-      - action: labelmap
-        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)
-      # drop all labels that we just made copies of in the previous labelmap
-      - action: labeldrop
-        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)
-      # __meta_kubernetes_pod_label_linkerd_io_foo=bar =>
-      # foo=bar
-      - action: labelmap
-        regex: __meta_kubernetes_pod_label_linkerd_io_(.+)
-      # Copy all pod labels to tmp labels
-      - action: labelmap
-        regex: __meta_kubernetes_pod_label_(.+)
-        replacement: __tmp_pod_label_$1
-      # Take `linkerd_io_` prefixed labels and copy them without the prefix
-      - action: labelmap
-        regex: __tmp_pod_label_linkerd_io_(.+)
-        replacement:  __tmp_pod_label_$1
-      # Drop the `linkerd_io_` originals
-      - action: labeldrop
-        regex: __tmp_pod_label_linkerd_io_(.+)
-      # Copy tmp labels into real labels
-      - action: labelmap
-        regex: __tmp_pod_label_(.+)
----
-kind: Service
-apiVersion: v1
-metadata:
-  name: prometheus
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: prometheus
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: prometheus
-  ports:
-  - name: admin-http
-    port: 9090
-    targetPort: 9090
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: prometheus
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: prometheus
-    namespace: linkerd-viz
-  name: prometheus
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: prometheus
-      namespace: linkerd-viz
-  template:
-    metadata:
-      annotations:
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: prometheus
-        namespace: linkerd-viz
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      securityContext:
-        fsGroup: 65534
-      containers:
-      - args:
-        - --log.level=info
-        - --log.format=logfmt
-        - --config.file=/etc/prometheus/prometheus.yml
-        - --storage.tsdb.path=/data
-        - --storage.tsdb.retention.time=6h
-        image: prom/prometheus:v2.30.3
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /-/healthy
-            port: 9090
-          initialDelaySeconds: 30
-          timeoutSeconds: 30
-        name: prometheus
-        ports:
-        - containerPort: 9090
-          name: admin-http
-        readinessProbe:
-          httpGet:
-            path: /-/ready
-            port: 9090
-          initialDelaySeconds: 30
-          timeoutSeconds: 30
-        resources:
-        securityContext:
-          runAsNonRoot: true
-          runAsUser: 65534
-          runAsGroup: 65534
-        volumeMounts:
-        - mountPath: /data
-          name: data
-        - mountPath: /etc/prometheus/prometheus.yml
-          name: prometheus-config
-          subPath: prometheus.yml
-          readOnly: true
-      serviceAccountName: prometheus
-      volumes:
-      - name: data
-        emptyDir: {}
-      - configMap:
-          name: prometheus-config
-        name: prometheus-config
----
-###
-### Tap
-###
-kind: Service
-apiVersion: v1
-metadata:
-  name: tap
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: tap
-  ports:
-  - name: grpc
-    port: 8088
-    targetPort: 8088
-  - name: apiserver
-    port: 443
-    targetPort: apiserver
----
-kind: Deployment
-apiVersion: apps/v1
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: tap
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: tap
-    namespace: linkerd-viz
-  name: tap
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: tap
-      namespace: linkerd-viz
-  template:
-    metadata:
-      annotations:
-        checksum/config: d6f2ea38c4004667c96eb4fb0135fe0d9d9a87f5c19aaee30e6ccb6ef7219324
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: tap
-        namespace: linkerd-viz
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - args:
-        - api
-        - -api-namespace=linkerd
-        - -log-level=info
-        - -log-format=plain
-        - -identity-trust-domain=cluster.local
-        image: cr.l5d.io/linkerd/tap:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /ping
-            port: 9998
-          initialDelaySeconds: 10
-        name: tap
-        ports:
-        - containerPort: 8088
-          name: grpc
-        - containerPort: 8089
-          name: apiserver
-        - containerPort: 9998
-          name: admin-http
-        readinessProbe:
-          failureThreshold: 7
-          httpGet:
-            path: /ready
-            port: 9998
-        resources:
-        securityContext:
-          runAsUser: 2103
-        volumeMounts:
-        - mountPath: /var/run/linkerd/tls
-          name: tls
-          readOnly: true
-      serviceAccountName: tap
-      volumes:
-      - name: tls
-        secret:
-          secretName: tap-k8s-tls
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: tap-api
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: tap
-  port: apiserver
-  proxyProtocol: TLS
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: tap
-  labels:
-    linkerd.io/extension: viz
-    component: tap
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: tap-api
-  client:
-    # traffic coming from kube-api
-    unauthenticated: true
----
-###
-### Tap Injector RBAC
-###
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-tap-injector
-  labels:
-    linkerd.io/extension: viz
-rules:
-- apiGroups: [""]
-  resources: ["namespaces"]
-  verbs: ["get", "list", "watch"]
----
-kind: ClusterRoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: linkerd-tap-injector
-  labels:
-    linkerd.io/extension: viz
-subjects:
-- kind: ServiceAccount
-  name: tap-injector
-  namespace: linkerd-viz
-roleRef:
-  kind: ClusterRole
-  name: linkerd-tap-injector
-  apiGroup: rbac.authorization.k8s.io
----
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: tap-injector
-  namespace: linkerd-viz
----
-apiVersion: admissionregistration.k8s.io/v1
-kind: MutatingWebhookConfiguration
-metadata:
-  name: linkerd-tap-injector-webhook-config
-  labels:
-    linkerd.io/extension: viz
-webhooks:
-- name: tap-injector.linkerd.io
-  clientConfig:
-    service:
-      name: tap-injector
-      namespace: linkerd-viz
-      path: "/"
-    caBundle: dGVzdC10YXAtY2EtYnVuZGxl
-  failurePolicy: Ignore
-  admissionReviewVersions: ["v1", "v1beta1"]
-  reinvocationPolicy: IfNeeded
-  rules:
-  - operations: [ "CREATE" ]
-    apiGroups: [""]
-    apiVersions: ["v1"]
-    resources: ["pods"]
-  sideEffects: None
----
-###
-### Tap Injector
-###
-kind: Service
-apiVersion: v1
-metadata:
-  name: tap-injector
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: tap-injector
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: tap-injector
-  ports:
-  - name: tap-injector
-    port: 443
-    targetPort: tap-injector
----
-kind: Deployment
-apiVersion: apps/v1
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: tap-injector
-    app.kubernetes.io/part-of: Linkerd
-    component: tap-injector
-  name: tap-injector
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      component: tap-injector
-  template:
-    metadata:
-      annotations:
-        checksum/config: 07c5bcd8a9872945d91827ee20c9412909a30ba3944731413022668c59067649
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: tap-injector
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - args:
-        - injector
-        - -tap-service-name=tap.linkerd-viz.serviceaccount.identity.linkerd.cluster.local
-        - -log-level=info
-        - -log-format=plain
-        image: cr.l5d.io/linkerd/tap:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /ping
-            port: 9995
-          initialDelaySeconds: 10
-        name: tap-injector
-        ports:
-        - containerPort: 8443
-          name: tap-injector
-        - containerPort: 9995
-          name: admin-http
-        readinessProbe:
-          failureThreshold: 7
-          httpGet:
-            path: /ready
-            port: 9995
-        resources:
-        securityContext:
-          runAsUser: 2103
-        volumeMounts:
-        - mountPath: /var/run/linkerd/tls
-          name: tls
-          readOnly: true
-      serviceAccountName: tap-injector
-      volumes:
-      - name: tls
-        secret:
-          secretName: tap-injector-k8s-tls
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: tap-injector-webhook
-  labels:
-    linkerd.io/extension: viz
-    component: tap-injector
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: tap-injector
-  port: tap-injector
-  proxyProtocol: TLS
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: tap-injector
-  labels:
-    linkerd.io/extension: viz
-    component: tap-injector
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: tap-injector-webhook
-  client:
-    # traffic coming from kube-api
-    unauthenticated: true
----
-###
-### Web
-###
-kind: Service
-apiVersion: v1
-metadata:
-  name: web
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: web
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: web
-  ports:
-  - name: http
-    port: 8084
-    targetPort: 8084
-  - name: admin-http
-    port: 9994
-    targetPort: 9994
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: web
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: web
-    namespace: linkerd-viz
-  name: web
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: web
-      namespace: linkerd-viz
-  template:
-    metadata:
-      annotations:
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: web
-        namespace: linkerd-viz
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - args:
-        - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085
-        - -cluster-domain=cluster.local
-        - -grafana-addr=external-grafana.com
-        - -controller-namespace=linkerd
-        - -viz-namespace=linkerd-viz
-        - -log-level=info
-        - -log-format=plain
-        - -enforced-host=^(localhost|127\.0\.0\.1|web\.linkerd-viz\.svc\.cluster\.local|web\.linkerd-viz\.svc|\[::1\])(:\d+)?$
-        image: cr.l5d.io/linkerd/web:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /ping
-            port: 9994
-          initialDelaySeconds: 10
-        name: web
-        ports:
-        - containerPort: 8084
-          name: http
-        - containerPort: 9994
-          name: admin-http
-        readinessProbe:
-          failureThreshold: 7
-          httpGet:
-            path: /ready
-            port: 9994
-        resources:
-        securityContext:
-          runAsUser: 2103
-      serviceAccountName: web
----
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: metrics-api.linkerd-viz.svc.cluster.local
-  namespace: linkerd-viz
-spec:
-  routes:
-  - name: POST /api/v1/StatSummary
-    condition:
-      method: POST
-      pathRegex: /api/v1/StatSummary
-  - name: POST /api/v1/TopRoutes
-    condition:
-      method: POST
-      pathRegex: /api/v1/TopRoutes
-  - name: POST /api/v1/ListPods
-    condition:
-      method: POST
-      pathRegex: /api/v1/ListPods
-  - name: POST /api/v1/ListServices
-    condition:
-      method: POST
-      pathRegex: /api/v1/ListServices
-  - name: POST /api/v1/SelfCheck
-    condition:
-      method: POST
-      pathRegex: /api/v1/SelfCheck
-  - name: POST /api/v1/Gateways
-    condition:
-      method: POST
-      pathRegex: /api/v1/Gateways
-  - name: POST /api/v1/Edges
-    condition:
-      method: POST
-      pathRegex: /api/v1/Edges
----
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: prometheus.linkerd-viz.svc.cluster.local
-  namespace: linkerd-viz
-spec:
-  routes:
-  - name: POST /api/v1/query
-    condition:
-      method: POST
-      pathRegex: /api/v1/query
-  - name: GET /api/v1/query_range
-    condition:
-      method: GET
-      pathRegex: /api/v1/query_range
-  - name: GET /api/v1/series
-    condition:
-      method: GET
-      pathRegex: /api/v1/series
diff --git a/viz/cmd/testdata/install_prometheus_disabled.golden b/viz/cmd/testdata/install_prometheus_disabled.golden
index 957dc6a99..5c35bd87d 100644
--- a/viz/cmd/testdata/install_prometheus_disabled.golden
+++ b/viz/cmd/testdata/install_prometheus_disabled.golden
@@ -64,19 +64,6 @@ metadata:
     component: metrics-api
 ---
 ###
-### Grafana RBAC
-###
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
----
-###
 ### Tap RBAC
 ###
 kind: ClusterRole
@@ -522,207 +509,6 @@ spec:
       - name: prometheus
 ---
 ###
-### Grafana
-###
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: grafana-config
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-data:
-  grafana.ini: |-
-    instance_name = grafana
-    [server]
-    root_url = %(protocol)s://%(domain)s:/grafana/
-    [auth]
-    disable_login_form = true
-    [auth.anonymous]
-    enabled = true
-    org_role = Editor
-    [auth.basic]
-    enabled = false
-    [analytics]
-    check_for_updates = false
-    [panels]
-    disable_sanitize_html = true
-    [log]
-    mode = console
-    [log.console]
-    format = text
-    level = info
-  datasources.yaml: |-
-    apiVersion: 1
-    datasources:
-    - name: prometheus
-      type: prometheus
-      access: proxy
-      orgId: 1
-      url: external-prom.com
-      isDefault: true
-      jsonData:
-        timeInterval: "5s"
-      version: 1
-      editable: true
-
-  dashboards.yaml: |-
-    apiVersion: 1
-    providers:
-    - name: 'default'
-      orgId: 1
-      folder: ''
-      type: file
-      disableDeletion: true
-      editable: true
-      options:
-        path: /var/lib/grafana/dashboards
-        homeDashboardId: linkerd-top-line
----
-kind: Service
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: grafana
-  ports:
-  - name: http
-    port: 3000
-    targetPort: 3000
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: grafana
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: grafana
-    namespace: linkerd-viz
-  name: grafana
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-      namespace: linkerd-viz
-  template:
-    metadata:
-      annotations:
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: grafana
-        namespace: linkerd-viz
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - env:
-        - name: GF_PATHS_DATA
-          value: /data
-        # Force using the go-based DNS resolver instead of the OS' to avoid failures in some environments
-        # see https://github.com/grafana/grafana/issues/20096
-        - name: GODEBUG
-          value: netdns=go
-        image: cr.l5d.io/linkerd/grafana:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-          initialDelaySeconds: 30
-        name: grafana
-        ports:
-        - containerPort: 3000
-          name: http
-        readinessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-        resources:
-        securityContext:
-          runAsUser: 472
-        volumeMounts:
-        - mountPath: /data
-          name: data
-        - mountPath: /etc/grafana
-          name: grafana-config
-          readOnly: true
-      serviceAccountName: grafana
-      volumes:
-      - emptyDir: {}
-        name: data
-      - configMap:
-          items:
-          - key: grafana.ini
-            path: grafana.ini
-          - key: datasources.yaml
-            path: provisioning/datasources/datasources.yaml
-          - key: dashboards.yaml
-            path: provisioning/dashboards/dashboards.yaml
-          name: grafana-config
-        name: grafana-config
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-  port: http
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: grafana
-  client:
-    # web, prometheus and the kubelet probes
-    unauthenticated: true
----
-###
 ### Tap
 ###
 kind: Service
@@ -895,6 +681,8 @@ apiVersion: v1
 metadata:
   name: tap-injector
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 ---
 apiVersion: admissionregistration.k8s.io/v1
 kind: MutatingWebhookConfiguration
@@ -966,7 +754,7 @@ spec:
   template:
     metadata:
       annotations:
-        checksum/config: 07c5bcd8a9872945d91827ee20c9412909a30ba3944731413022668c59067649
+        checksum/config: 390143015ec83a86ded6630634da5834c8ac7700b93d486a7dc101a15cb87f15
         linkerd.io/created-by: linkerd/helm dev-undefined
         linkerd.io/inject: enabled
       labels:
@@ -1114,7 +902,6 @@ spec:
       - args:
         - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085
         - -cluster-domain=cluster.local
-        - -grafana-addr=grafana.linkerd-viz.svc.cluster.local:3000
         - -controller-namespace=linkerd
         - -viz-namespace=linkerd-viz
         - -log-level=info
@@ -1148,6 +935,8 @@ kind: ServiceProfile
 metadata:
   name: metrics-api.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/StatSummary
@@ -1178,55 +967,3 @@ spec:
     condition:
       method: POST
       pathRegex: /api/v1/Edges
----
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: grafana.linkerd-viz.svc.cluster.local
-  namespace: linkerd-viz
-spec:
-  routes:
-  - name: GET /api/annotations
-    condition:
-      method: GET
-      pathRegex: /api/annotations
-  - name: GET /api/dashboards/tags
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/tags
-  - name: GET /api/dashboards/uid/{uid}
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/uid/.*
-  - name: GET /api/dashboard/{dashboard}
-    condition:
-      method: GET
-      pathRegex: /api/dashboard/.*
-  - name: GET /api/datasources/proxy/1/api/v1/series
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/series
-  - name: GET /api/datasources/proxy/1/api/v1/query_range
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/query_range
-  - name: GET /api/search
-    condition:
-      method: GET
-      pathRegex: /api/search
-  - name: GET /d/{uid}/{dashboard-name}
-    condition:
-      method: GET
-      pathRegex: /d/[^/]*/.*
-  - name: GET /public/build/{style}.css
-    condition:
-      method: GET
-      pathRegex: /public/build/.*\.css
-  - name: GET /public/fonts/{font}
-    condition:
-      method: GET
-      pathRegex: /public/fonts/.*
-  - name: GET /public/img/{img}
-    condition:
-      method: GET
-      pathRegex: /public/img/.*
diff --git a/viz/cmd/testdata/install_prometheus_loglevel_from_args.golden b/viz/cmd/testdata/install_prometheus_loglevel_from_args.golden
index 8de0f2257..d74133b88 100644
--- a/viz/cmd/testdata/install_prometheus_loglevel_from_args.golden
+++ b/viz/cmd/testdata/install_prometheus_loglevel_from_args.golden
@@ -63,19 +63,6 @@ metadata:
     component: metrics-api
 ---
 ###
-### Grafana RBAC
-###
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
----
-###
 ### Prometheus RBAC
 ###
 kind: ClusterRole
@@ -562,207 +549,6 @@ spec:
       - name: prometheus
 ---
 ###
-### Grafana
-###
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: grafana-config
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-data:
-  grafana.ini: |-
-    instance_name = grafana
-    [server]
-    root_url = %(protocol)s://%(domain)s:/grafana/
-    [auth]
-    disable_login_form = true
-    [auth.anonymous]
-    enabled = true
-    org_role = Editor
-    [auth.basic]
-    enabled = false
-    [analytics]
-    check_for_updates = false
-    [panels]
-    disable_sanitize_html = true
-    [log]
-    mode = console
-    [log.console]
-    format = text
-    level = info
-  datasources.yaml: |-
-    apiVersion: 1
-    datasources:
-    - name: prometheus
-      type: prometheus
-      access: proxy
-      orgId: 1
-      url: http://prometheus.linkerd-viz.svc.cluster.local:9090
-      isDefault: true
-      jsonData:
-        timeInterval: "5s"
-      version: 1
-      editable: true
-
-  dashboards.yaml: |-
-    apiVersion: 1
-    providers:
-    - name: 'default'
-      orgId: 1
-      folder: ''
-      type: file
-      disableDeletion: true
-      editable: true
-      options:
-        path: /var/lib/grafana/dashboards
-        homeDashboardId: linkerd-top-line
----
-kind: Service
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: grafana
-  ports:
-  - name: http
-    port: 3000
-    targetPort: 3000
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: grafana
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: grafana
-    namespace: linkerd-viz
-  name: grafana
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-      namespace: linkerd-viz
-  template:
-    metadata:
-      annotations:
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: grafana
-        namespace: linkerd-viz
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - env:
-        - name: GF_PATHS_DATA
-          value: /data
-        # Force using the go-based DNS resolver instead of the OS' to avoid failures in some environments
-        # see https://github.com/grafana/grafana/issues/20096
-        - name: GODEBUG
-          value: netdns=go
-        image: cr.l5d.io/linkerd/grafana:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-          initialDelaySeconds: 30
-        name: grafana
-        ports:
-        - containerPort: 3000
-          name: http
-        readinessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-        resources:
-        securityContext:
-          runAsUser: 472
-        volumeMounts:
-        - mountPath: /data
-          name: data
-        - mountPath: /etc/grafana
-          name: grafana-config
-          readOnly: true
-      serviceAccountName: grafana
-      volumes:
-      - emptyDir: {}
-        name: data
-      - configMap:
-          items:
-          - key: grafana.ini
-            path: grafana.ini
-          - key: datasources.yaml
-            path: provisioning/datasources/datasources.yaml
-          - key: dashboards.yaml
-            path: provisioning/dashboards/dashboards.yaml
-          name: grafana-config
-        name: grafana-config
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-  port: http
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: grafana
-  client:
-    # web, prometheus and the kubelet probes
-    unauthenticated: true
----
-###
 ### Prometheus
 ###
 kind: ConfigMap
@@ -792,17 +578,6 @@ data:
       static_configs:
       - targets: ['localhost:9090']
 
-    - job_name: 'grafana'
-      kubernetes_sd_configs:
-      - role: pod
-        namespaces:
-          names: ['linkerd-viz']
-      relabel_configs:
-      - source_labels:
-        - __meta_kubernetes_pod_container_name
-        action: keep
-        regex: ^grafana$
-
     #  Required for: https://grafana.com/grafana/dashboards/315
     - job_name: 'kubernetes-nodes-cadvisor'
       scheme: https
@@ -1187,6 +962,8 @@ apiVersion: v1
 metadata:
   name: tap-injector
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 ---
 apiVersion: admissionregistration.k8s.io/v1
 kind: MutatingWebhookConfiguration
@@ -1258,7 +1035,7 @@ spec:
   template:
     metadata:
       annotations:
-        checksum/config: 07c5bcd8a9872945d91827ee20c9412909a30ba3944731413022668c59067649
+        checksum/config: 390143015ec83a86ded6630634da5834c8ac7700b93d486a7dc101a15cb87f15
         linkerd.io/created-by: linkerd/helm dev-undefined
         linkerd.io/inject: enabled
       labels:
@@ -1406,7 +1183,6 @@ spec:
       - args:
         - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085
         - -cluster-domain=cluster.local
-        - -grafana-addr=grafana.linkerd-viz.svc.cluster.local:3000
         - -controller-namespace=linkerd
         - -viz-namespace=linkerd-viz
         - -log-level=info
@@ -1440,6 +1216,8 @@ kind: ServiceProfile
 metadata:
   name: metrics-api.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/StatSummary
@@ -1476,6 +1254,8 @@ kind: ServiceProfile
 metadata:
   name: prometheus.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/query
@@ -1490,55 +1270,3 @@ spec:
     condition:
       method: GET
       pathRegex: /api/v1/series
----
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: grafana.linkerd-viz.svc.cluster.local
-  namespace: linkerd-viz
-spec:
-  routes:
-  - name: GET /api/annotations
-    condition:
-      method: GET
-      pathRegex: /api/annotations
-  - name: GET /api/dashboards/tags
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/tags
-  - name: GET /api/dashboards/uid/{uid}
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/uid/.*
-  - name: GET /api/dashboard/{dashboard}
-    condition:
-      method: GET
-      pathRegex: /api/dashboard/.*
-  - name: GET /api/datasources/proxy/1/api/v1/series
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/series
-  - name: GET /api/datasources/proxy/1/api/v1/query_range
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/query_range
-  - name: GET /api/search
-    condition:
-      method: GET
-      pathRegex: /api/search
-  - name: GET /d/{uid}/{dashboard-name}
-    condition:
-      method: GET
-      pathRegex: /d/[^/]*/.*
-  - name: GET /public/build/{style}.css
-    condition:
-      method: GET
-      pathRegex: /public/build/.*\.css
-  - name: GET /public/fonts/{font}
-    condition:
-      method: GET
-      pathRegex: /public/fonts/.*
-  - name: GET /public/img/{img}
-    condition:
-      method: GET
-      pathRegex: /public/img/.*
diff --git a/viz/cmd/testdata/install_proxy_resources.golden b/viz/cmd/testdata/install_proxy_resources.golden
index 4204a57e4..98f839f27 100644
--- a/viz/cmd/testdata/install_proxy_resources.golden
+++ b/viz/cmd/testdata/install_proxy_resources.golden
@@ -63,19 +63,6 @@ metadata:
     component: metrics-api
 ---
 ###
-### Grafana RBAC
-###
-kind: ServiceAccount
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
----
-###
 ### Prometheus RBAC
 ###
 kind: ClusterRole
@@ -562,211 +549,6 @@ spec:
       - name: prometheus
 ---
 ###
-### Grafana
-###
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: grafana-config
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-data:
-  grafana.ini: |-
-    instance_name = grafana
-    [server]
-    root_url = %(protocol)s://%(domain)s:/grafana/
-    [auth]
-    disable_login_form = true
-    [auth.anonymous]
-    enabled = true
-    org_role = Editor
-    [auth.basic]
-    enabled = false
-    [analytics]
-    check_for_updates = false
-    [panels]
-    disable_sanitize_html = true
-    [log]
-    mode = console
-    [log.console]
-    format = text
-    level = info
-  datasources.yaml: |-
-    apiVersion: 1
-    datasources:
-    - name: prometheus
-      type: prometheus
-      access: proxy
-      orgId: 1
-      url: http://prometheus.linkerd-viz.svc.cluster.local:9090
-      isDefault: true
-      jsonData:
-        timeInterval: "5s"
-      version: 1
-      editable: true
-
-  dashboards.yaml: |-
-    apiVersion: 1
-    providers:
-    - name: 'default'
-      orgId: 1
-      folder: ''
-      type: file
-      disableDeletion: true
-      editable: true
-      options:
-        path: /var/lib/grafana/dashboards
-        homeDashboardId: linkerd-top-line
----
-kind: Service
-apiVersion: v1
-metadata:
-  name: grafana
-  namespace: linkerd-viz
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-    namespace: linkerd-viz
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-spec:
-  type: ClusterIP
-  selector:
-    linkerd.io/extension: viz
-    component: grafana
-  ports:
-  - name: http
-    port: 3000
-    targetPort: 3000
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-    linkerd.io/inject: enabled
-    config.linkerd.io/proxy-await: "enabled"
-  labels:
-    linkerd.io/extension: viz
-    app.kubernetes.io/name: grafana
-    app.kubernetes.io/part-of: Linkerd
-    app.kubernetes.io/version: dev-undefined
-    component: grafana
-    namespace: linkerd-viz
-  name: grafana
-  namespace: linkerd-viz
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-      namespace: linkerd-viz
-  template:
-    metadata:
-      annotations:
-        linkerd.io/created-by: linkerd/helm dev-undefined
-        config.linkerd.io/proxy-cpu-request: "500m"
-        config.linkerd.io/proxy-cpu-limit: "100m"
-        config.linkerd.io/proxy-memory-request: "20Mi"
-        config.linkerd.io/proxy-memory-limit: "250Mi"
-        linkerd.io/inject: enabled
-      labels:
-        linkerd.io/extension: viz
-        component: grafana
-        namespace: linkerd-viz
-    spec:
-      nodeSelector:
-        kubernetes.io/os: linux
-      containers:
-      - env:
-        - name: GF_PATHS_DATA
-          value: /data
-        # Force using the go-based DNS resolver instead of the OS' to avoid failures in some environments
-        # see https://github.com/grafana/grafana/issues/20096
-        - name: GODEBUG
-          value: netdns=go
-        image: cr.l5d.io/linkerd/grafana:dev-undefined
-        imagePullPolicy: IfNotPresent
-        livenessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-          initialDelaySeconds: 30
-        name: grafana
-        ports:
-        - containerPort: 3000
-          name: http
-        readinessProbe:
-          httpGet:
-            path: /api/health
-            port: 3000
-        resources:
-        securityContext:
-          runAsUser: 472
-        volumeMounts:
-        - mountPath: /data
-          name: data
-        - mountPath: /etc/grafana
-          name: grafana-config
-          readOnly: true
-      serviceAccountName: grafana
-      volumes:
-      - emptyDir: {}
-        name: data
-      - configMap:
-          items:
-          - key: grafana.ini
-            path: grafana.ini
-          - key: datasources.yaml
-            path: provisioning/datasources/datasources.yaml
-          - key: dashboards.yaml
-            path: provisioning/dashboards/dashboards.yaml
-          name: grafana-config
-        name: grafana-config
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  podSelector:
-    matchLabels:
-      linkerd.io/extension: viz
-      component: grafana
-  port: http
-  proxyProtocol: HTTP/1
----
-apiVersion: policy.linkerd.io/v1beta1
-kind: ServerAuthorization
-metadata:
-  namespace: linkerd-viz
-  name: grafana
-  labels:
-    linkerd.io/extension: viz
-    component: grafana
-  annotations:
-    linkerd.io/created-by: linkerd/helm dev-undefined
-spec:
-  server:
-    name: grafana
-  client:
-    # web, prometheus and the kubelet probes
-    unauthenticated: true
----
-###
 ### Prometheus
 ###
 kind: ConfigMap
@@ -796,17 +578,6 @@ data:
       static_configs:
       - targets: ['localhost:9090']
 
-    - job_name: 'grafana'
-      kubernetes_sd_configs:
-      - role: pod
-        namespaces:
-          names: ['linkerd-viz']
-      relabel_configs:
-      - source_labels:
-        - __meta_kubernetes_pod_container_name
-        action: keep
-        regex: ^grafana$
-
     #  Required for: https://grafana.com/grafana/dashboards/315
     - job_name: 'kubernetes-nodes-cadvisor'
       scheme: https
@@ -1199,6 +970,8 @@ apiVersion: v1
 metadata:
   name: tap-injector
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 ---
 apiVersion: admissionregistration.k8s.io/v1
 kind: MutatingWebhookConfiguration
@@ -1270,7 +1043,7 @@ spec:
   template:
     metadata:
       annotations:
-        checksum/config: 07c5bcd8a9872945d91827ee20c9412909a30ba3944731413022668c59067649
+        checksum/config: 390143015ec83a86ded6630634da5834c8ac7700b93d486a7dc101a15cb87f15
         linkerd.io/created-by: linkerd/helm dev-undefined
         linkerd.io/inject: enabled
       labels:
@@ -1422,7 +1195,6 @@ spec:
       - args:
         - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085
         - -cluster-domain=cluster.local
-        - -grafana-addr=grafana.linkerd-viz.svc.cluster.local:3000
         - -controller-namespace=linkerd
         - -viz-namespace=linkerd-viz
         - -log-level=info
@@ -1456,6 +1228,8 @@ kind: ServiceProfile
 metadata:
   name: metrics-api.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/StatSummary
@@ -1492,6 +1266,8 @@ kind: ServiceProfile
 metadata:
   name: prometheus.linkerd-viz.svc.cluster.local
   namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: viz
 spec:
   routes:
   - name: POST /api/v1/query
@@ -1506,55 +1282,3 @@ spec:
     condition:
       method: GET
       pathRegex: /api/v1/series
----
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: grafana.linkerd-viz.svc.cluster.local
-  namespace: linkerd-viz
-spec:
-  routes:
-  - name: GET /api/annotations
-    condition:
-      method: GET
-      pathRegex: /api/annotations
-  - name: GET /api/dashboards/tags
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/tags
-  - name: GET /api/dashboards/uid/{uid}
-    condition:
-      method: GET
-      pathRegex: /api/dashboards/uid/.*
-  - name: GET /api/dashboard/{dashboard}
-    condition:
-      method: GET
-      pathRegex: /api/dashboard/.*
-  - name: GET /api/datasources/proxy/1/api/v1/series
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/series
-  - name: GET /api/datasources/proxy/1/api/v1/query_range
-    condition:
-      method: GET
-      pathRegex: /api/datasources/proxy/1/api/v1/query_range
-  - name: GET /api/search
-    condition:
-      method: GET
-      pathRegex: /api/search
-  - name: GET /d/{uid}/{dashboard-name}
-    condition:
-      method: GET
-      pathRegex: /d/[^/]*/.*
-  - name: GET /public/build/{style}.css
-    condition:
-      method: GET
-      pathRegex: /public/build/.*\.css
-  - name: GET /public/fonts/{font}
-    condition:
-      method: GET
-      pathRegex: /public/fonts/.*
-  - name: GET /public/img/{img}
-    condition:
-      method: GET
-      pathRegex: /public/img/.*