GKE Autopilot ASM and Vault

Published: Aug 16, 2022 by Isaac Johnson

I’ve had a few questions come up recently about how Istio and Vault play together. We know that Consul and Vault, both Hashicorp Products, work quite well together, but can we blend the commercially supported Anthos Service Mesh with Hashi Vault. Will they both add sidecars?

We’ll dig in with Google Kubernetes Engine and try using GKE Autopilot (which abstracts Node pool management), We’ll add Anthos Service Mesh (the GCP supported commercial Istio) and lastly add Hashi Vault to our cluster with and without sidecar injections.

Setup the cluster and services in Google Cloud

First I need to login to gcloud

$ gcloud auth login
Your browser has been opened to visit:
...
ou are now logged in as [isaac.johnson@gmail.com].
Your current project is [gkedemo01].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID


To take a quick anonymous survey, run:
  $ gcloud survey

Then I should find the project ID for my Anthos project

$ gcloud projects list --filter=anthos --limit 5
PROJECT_ID        NAME              PROJECT_NUMBER
myanthosproject2  myAnthosProject2  511842454269

I’ll need to enable Service Mesh and Anthos APIs (if I haven’t already)

$ export PROJECTID=myanthosproject2
$ gcloud services enable mesh.googleapis.com --project=$PROJECTID
Operation "operations/acat.p2-511842454269-5f80b86a-307c-4d3c-bff9-bcb5d0893020" finished successfully.
$ gcloud services enable anthos.googleapis.com --project=$PROJECTID
Operation "operations/acat.p2-511842454269-ef2fd078-6eba-42aa-b1a8-af161ea1c13d" finished successfully.

Now we can create

$ export PROJECTNUM=511842454269
$ gcloud container --project $PROJECTID clusters create-auto "freshbrewed-autopilot-cluster-1" --region "us-central1" --release-channel "regular" --network "projects/$PROJECTID/global/networks/default" --subnetwork "projects/$PROJECTID/regions/us-central1/subnetworks/default" --cluster-ipv4-cidr "/17" --services-ipv4-cidr "/22" --labels mesh_id=proj-$PROJECTNUM

The first time I ran, I got an error

ERROR: (gcloud.container.clusters.create-auto) unrecognized arguments:
  --labels
  mesh_id=proj-511842454269
  To search the help text of gcloud commands, run:
  gcloud help -- SEARCH_TERMS

This lead me to believe my gcloud was out of date

$ gcloud version
Google Cloud SDK 367.0.0
alpha 2021.12.10
app-engine-go 1.9.72
app-engine-python 1.9.98
app-engine-python-extras 1.9.96
beta 2021.12.10
bq 2.0.72
cloud-build-local 0.5.2
core 2021.12.10
gsutil 5.5
pubsub-emulator 0.6.0

Upgrading should be simple

$ gcloud components update
To help improve the quality of this product, we collect anonymized usage data and anonymized stacktraces when crashes are encountered;
additional information is available at <https://cloud.google.com/sdk/usage-statistics>. This data is handled in accordance with our privacy
policy <https://cloud.google.com/terms/cloud-privacy-notice>. You may choose to opt in this collection now (by choosing 'Y' at the below
prompt), or at any time in the future by running the following command:

    gcloud config set disable_usage_reporting false

Do you want to opt-in (y/N)?  y

Beginning update. This process may take several minutes.
ERROR: (gcloud.components.update)
You cannot perform this action because the Cloud SDK component manager
is disabled for this installation. You can run the following command
to achieve the same result for this installation:

sudo apt-get update && sudo apt-get --only-upgrade install kubectl google-cloud-sdk-app-engine-java google-cloud-sdk-pubsub-emulator google-cloud-sdk-config-connector google-cloud-sdk-spanner-emulator google-cloud-sdk-kpt google-cloud-sdk google-cloud-sdk-terraform-validator google-cloud-sdk-app-engine-go google-cloud-sdk-skaffold google-cloud-sdk-kubectl-oidc google-cloud-sdk-cbt google-cloud-sdk-firestore-emulator google-cloud-sdk-gke-gcloud-auth-plugin google-cloud-sdk-app-engine-grpc google-cloud-sdk-minikube google-cloud-sdk-anthos-auth google-cloud-sdk-datalab google-cloud-sdk-app-engine-python google-cloud-sdk-bigtable-emulator google-cloud-sdk-cloud-build-local google-cloud-sdk-local-extract google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-python-extras

However we have to use apt.

Run apt update and upgrade as suggested

$ sudo apt-get update && sudo apt-get --only-upgrade install kubectl google-cloud-sdk-app-engine-java google-cloud-sdk-pubsub-emulator google-cloud-sdk-config-connector google-cloud-sdk-spanner-emulator google-cloud-sdk-kpt google-cloud-sdk google-cloud-sdk-terraform-validator google-cloud-sdk-app-engine-go google-cloud-sdk-skaffold google-cloud-sdk-kubectl-oidc google-cloud-sdk-cbt google-cloud-sdk-firestore-emulator google-cloud-sdk-gke-gcloud-auth-plugin google-cloud-sdk-app-engine-grpc google-cloud-sdk-minikube google-cloud-sdk-anthos-auth google-cloud-sdk-datalab google-cloud-sdk-app-engine-python google-cloud-sdk-bigtable-emulator google-cloud-sdk-cloud-build-local google-cloud-sdk-local-extract google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-python-extras
[sudo] password for builder:
Hit:1 http://archive.ubuntu.com/ubuntu focal InRelease
Get:2 https://apt.releases.hashicorp.com focal InRelease [16.3 kB]
Get:3 http://archive.ubuntu.com/ubuntu focal-updates InRelease [114 kB]
Get:4 https://packages.microsoft.com/repos/azure-cli focal InRelease [10.4 kB]
Get:5 https://packages.microsoft.com/repos/microsoft-ubuntu-focal-prod focal InRelease [10.5 kB]
Get:6 https://apt.releases.hashicorp.com focal/main amd64 Packages [62.7 kB]
Get:7 http://security.ubuntu.com/ubuntu focal-security InRelease [114 kB]
Get:8 https://packages.microsoft.com/debian/11/prod bullseye InRelease [10.5 kB]
Get:9 http://archive.ubuntu.com/ubuntu focal-backports InRelease [108 kB]
Get:10 https://packages.cloud.google.com/apt cloud-sdk InRelease [6751 B]
Get:11 https://packages.microsoft.com/repos/azure-cli focal/main amd64 Packages [9297 B]
Get:12 https://packages.microsoft.com/repos/microsoft-ubuntu-focal-prod focal/main amd64 Packages [185 kB]
Get:13 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages [2036 kB]
Get:14 https://packages.cloud.google.com/apt cloud-sdk/main amd64 Packages [306 kB]
Get:15 https://packages.microsoft.com/debian/11/prod bullseye/main armhf Packages [16.2 kB]
Get:16 https://packages.microsoft.com/debian/11/prod bullseye/main arm64 Packages [14.7 kB]
Get:17 https://packages.microsoft.com/debian/11/prod bullseye/main amd64 Packages [75.9 kB]
Get:18 http://archive.ubuntu.com/ubuntu focal-updates/main Translation-en [364 kB]
Get:19 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 c-n-f Metadata [15.6 kB]
Get:20 http://archive.ubuntu.com/ubuntu focal-updates/restricted amd64 Packages [1222 kB]
Get:21 http://archive.ubuntu.com/ubuntu focal-updates/restricted Translation-en [174 kB]
Get:22 http://archive.ubuntu.com/ubuntu focal-updates/restricted amd64 c-n-f Metadata [584 B]
Get:23 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 Packages [936 kB]
Get:24 http://archive.ubuntu.com/ubuntu focal-updates/universe Translation-en [210 kB]
Get:25 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 c-n-f Metadata [21.0 kB]
Get:26 http://archive.ubuntu.com/ubuntu focal-updates/multiverse amd64 Packages [24.4 kB]
Get:27 http://archive.ubuntu.com/ubuntu focal-updates/multiverse amd64 c-n-f Metadata [588 B]
Get:28 http://archive.ubuntu.com/ubuntu focal-backports/main amd64 Packages [45.6 kB]
Get:29 http://archive.ubuntu.com/ubuntu focal-backports/main Translation-en [16.3 kB]
Get:30 http://archive.ubuntu.com/ubuntu focal-backports/main amd64 c-n-f Metadata [1420 B]
Get:31 http://archive.ubuntu.com/ubuntu focal-backports/universe amd64 Packages [24.0 kB]
Get:32 http://archive.ubuntu.com/ubuntu focal-backports/universe Translation-en [16.0 kB]
Get:33 http://security.ubuntu.com/ubuntu focal-security/main amd64 Packages [1672 kB]
Get:34 http://archive.ubuntu.com/ubuntu focal-backports/universe amd64 c-n-f Metadata [860 B]
Get:35 http://security.ubuntu.com/ubuntu focal-security/main Translation-en [282 kB]
Get:36 http://security.ubuntu.com/ubuntu focal-security/main amd64 c-n-f Metadata [10.8 kB]
Get:37 http://security.ubuntu.com/ubuntu focal-security/restricted amd64 Packages [1138 kB]
Get:38 http://security.ubuntu.com/ubuntu focal-security/restricted Translation-en [162 kB]
Get:39 http://security.ubuntu.com/ubuntu focal-security/restricted amd64 c-n-f Metadata [588 B]
Get:40 http://security.ubuntu.com/ubuntu focal-security/universe amd64 Packages [714 kB]
Get:41 http://security.ubuntu.com/ubuntu focal-security/universe Translation-en [130 kB]
Get:42 http://security.ubuntu.com/ubuntu focal-security/universe amd64 c-n-f Metadata [14.7 kB]
Get:43 http://security.ubuntu.com/ubuntu focal-security/multiverse amd64 Packages [22.2 kB]
Get:44 http://security.ubuntu.com/ubuntu focal-security/multiverse amd64 c-n-f Metadata [508 B]
Fetched 10.3 MB in 2s (5707 kB/s)
Reading package lists... Done
Reading package lists... Done
Building dependency tree
Reading state information... Done
Skipping google-cloud-sdk-anthos-auth, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-app-engine-grpc, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-app-engine-java, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-bigtable-emulator, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-cbt, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-config-connector, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-datalab, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-datastore-emulator, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-firestore-emulator, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-gke-gcloud-auth-plugin, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-kpt, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-kubectl-oidc, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-local-extract, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-minikube, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-skaffold, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-spanner-emulator, it is not installed and only upgrades are requested.
Skipping google-cloud-sdk-terraform-validator, it is not installed and only upgrades are requested.
Skipping kubectl, it is not installed and only upgrades are requested.
The following packages were automatically installed and are no longer required:
  liblttng-ust-ctl4 liblttng-ust0 python3-crcmod
Use 'sudo apt autoremove' to remove them.
Suggested packages:
  google-cloud-sdk-app-engine-java google-cloud-sdk-bigtable-emulator google-cloud-sdk-datastore-emulator kubectl
The following packages will be upgraded:
  google-cloud-sdk google-cloud-sdk-app-engine-go google-cloud-sdk-app-engine-python google-cloud-sdk-app-engine-python-extras
  google-cloud-sdk-cloud-build-local google-cloud-sdk-pubsub-emulator
6 upgraded, 0 newly installed, 0 to remove and 231 not upgraded.
Need to get 221 MB of archives.
After this operation, 244 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 https://packages.cloud.google.com/apt cloud-sdk/main amd64 google-cloud-sdk all 397.0.0-0 [134 MB]
Get:2 https://packages.cloud.google.com/apt cloud-sdk/main amd64 google-cloud-sdk-app-engine-python all 397.0.0-0 [5522 kB]
Get:3 https://packages.cloud.google.com/apt cloud-sdk/main amd64 google-cloud-sdk-app-engine-go amd64 397.0.0-0 [1883 kB]
Get:4 https://packages.cloud.google.com/apt cloud-sdk/main amd64 google-cloud-sdk-app-engine-python-extras all 397.0.0-0 [15.0 MB]
Get:5 https://packages.cloud.google.com/apt cloud-sdk/main amd64 google-cloud-sdk-cloud-build-local amd64 397.0.0-0 [2720 kB]
Get:6 https://packages.cloud.google.com/apt cloud-sdk/main amd64 google-cloud-sdk-pubsub-emulator all 397.0.0-0 [61.6 MB]
Fetched 221 MB in 5s (42.8 MB/s)
(Reading database ... 220787 files and directories currently installed.)
Preparing to unpack .../0-google-cloud-sdk_397.0.0-0_all.deb ...
Unpacking google-cloud-sdk (397.0.0-0) over (367.0.0-0) ...
Preparing to unpack .../1-google-cloud-sdk-app-engine-python_397.0.0-0_all.deb ...
Unpacking google-cloud-sdk-app-engine-python (397.0.0-0) over (367.0.0-0) ...
Preparing to unpack .../2-google-cloud-sdk-app-engine-go_397.0.0-0_amd64.deb ...
Unpacking google-cloud-sdk-app-engine-go (397.0.0-0) over (367.0.0-0) ...
Preparing to unpack .../3-google-cloud-sdk-app-engine-python-extras_397.0.0-0_all.deb ...
Unpacking google-cloud-sdk-app-engine-python-extras (397.0.0-0) over (367.0.0-0) ...
Preparing to unpack .../4-google-cloud-sdk-cloud-build-local_397.0.0-0_amd64.deb ...
Unpacking google-cloud-sdk-cloud-build-local (397.0.0-0) over (367.0.0-0) ...
Preparing to unpack .../5-google-cloud-sdk-pubsub-emulator_397.0.0-0_all.deb ...
Unpacking google-cloud-sdk-pubsub-emulator (397.0.0-0) over (367.0.0-0) ...
Setting up google-cloud-sdk (397.0.0-0) ...
Setting up google-cloud-sdk-app-engine-python (397.0.0-0) ...
Setting up google-cloud-sdk-cloud-build-local (397.0.0-0) ...
Setting up google-cloud-sdk-pubsub-emulator (397.0.0-0) ...
Processing triggers for man-db (2.9.1-1) ...
Processing triggers for google-cloud-sdk (397.0.0-0) ...
Setting up google-cloud-sdk-app-engine-python-extras (397.0.0-0) ...
Setting up google-cloud-sdk-app-engine-go (397.0.0-0) ...
Processing triggers for google-cloud-sdk (397.0.0-0) ...

Now updated, we can check versions

$ gcloud version
Google Cloud SDK 397.0.0
alpha 2022.08.05
app-engine-go 1.9.72
app-engine-python 1.9.100
app-engine-python-extras 1.9.96
beta 2022.08.05
bq 2.0.75
bundled-python3-unix 3.9.12
core 2022.08.05
gcloud-crc32c 1.0.0
gsutil 5.11
pubsub-emulator 0.7.0

However, even with the latest, the labels parameter just doesn’t exist on the command line for clusters create-auto

$ gcloud container --project $PROJECTID clusters create-auto "freshbrewed-autopilot-cluster-1" --region "us-central1" --release-channel "regular" --network "projects/$PROJECTID/global/networks/default" --subnetwork "projects/$PROJECTID/regions/us-central1/subnetworks/default" --cluster-ipv4-cidr "/17" --services-ipv4-cidr "/22" --labels mesh_id=proj-$PROJECTNUM
ERROR: (gcloud.container.clusters.create-auto) unrecognized arguments:
  --labels
  mesh_id=proj-511842454269
  To search the help text of gcloud commands, run:
  gcloud help -- SEARCH_TERMS


$ gcloud container --project "myanthosproject2" clusters create-auto "freshbrewed-autopilot-cluster-1" --region "us-central1" --release-channel "regular" --network "projects/myanthosproject2/global/networks/default" --subnetwork "projects/myanthosproject2/regions/us-central1/subnetworks/default" --cluster-ipv4-cidr "/17" --services-ipv4-cidr "/22" --labels="mesh_id=proj-511842454269"
ERROR: (gcloud.container.clusters.create-auto) unrecognized arguments: --labels=mesh_id=proj-511842454269

To search the help text of gcloud commands, run:
  gcloud help -- SEARCH_TERMS

$ gcloud container --project "myanthosproject2" clusters create-auto "freshbrewed-autopilot-cluster-1" --region "us-central1" --release-channel "regular" --network "projects/myanthosproject2/global/networks/default" --subnetwork "projects/myanthosproject2/regions/us-central1/subnetworks/default" --cluster-ipv4-cidr "/17" --services-ipv4-cidr "/22" --labels=mesh_id=proj-511842454269
ERROR: (gcloud.container.clusters.create-auto) unrecognized arguments: --labels=mesh_id=proj-511842454269

To search the help text of gcloud commands, run:
  gcloud help -- SEARCH_TERMS

even though gcloud web ui tells me it should

/content/images/2022/08/gkeasmvault-02.png

Note: labels work for clusters create however. I guess I’ll have to use the portal until they update the gcloud cli.

I went to the Gcloud Console and created with the web UI

/content/images/2022/08/gkeasmvault-03.png

When done, I could see the cluster is running

/content/images/2022/08/gkeasmvault-04.png

Once created, I could login

$ gcloud container clusters get-credentials freshbrewed-autopilot-cluster-1 --region us-central1 --project $PROJECTID
Fetching cluster endpoint and auth data.
CRITICAL: ACTION REQUIRED: gke-gcloud-auth-plugin, which is needed for continued use of kubectl, was not found or is not executable. Install gke-gcloud-auth-plugin for use with kubectl by following https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
kubeconfig entry generated for freshbrewed-autopilot-cluster-1.

And see indeed Istio is running

$ kubectl get pods --all-namespaces | grep istio

kube-system   istio-cni-node-4v48d                                       2/2     Running   0               2m21s
kube-system   istio-cni-node-57v6m                                       2/2     Running   0               3m15s
kube-system   istio-cni-node-d4t6n                                       2/2     Running   0               3m15s

Sample App

Let’s test Istio (Anthos Service Mesh, ASM) by launching a sample app

$ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/istio-samples/d8666bb6f72b63fa2f27c7001ce196394494a585/sample
-apps/helloserver/server/server.yaml
deployment.apps/helloserver created
service/hellosvc created

I can see it created a service

$ kubectl get svc
NAME         TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)        AGE
hellosvc     LoadBalancer   10.21.3.133   35.224.247.0   80:30972/TCP   2m33s
kubernetes   ClusterIP      10.21.0.1     <none>         443/TCP        17m

And can see it is running

/content/images/2022/08/gkeasmvault-05.png

However, nothing is listed in ASM

/content/images/2022/08/gkeasmvault-06.png

We need to add the ASM managed label (and remove “istio-injection-enabled” if it exists)

$ kubectl label namespace default  istio-injection- istio.io/rev=asm-managed --overwrite

label "istio-injection" not found.
namespace/default labeled

We can rotate the pod to see it add the ASM sidecar

$ kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
helloserver-64f6d5d74b-btgzr   1/1     Running   0          8m30s
$ kubectl delete pod helloserver-64f6d5d74b-btgzr
pod "helloserver-64f6d5d74b-btgzr" deleted
$ kubectl get pods
NAME                           READY   STATUS     RESTARTS   AGE
helloserver-64f6d5d74b-6ld69   0/2     Init:0/1   0          8s
$ kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
helloserver-64f6d5d74b-6ld69   2/2     Running   0          57s

We can now see it listed in Anthos Service Mesh

/content/images/2022/08/gkeasmvault-07.png

Installing Vault

Next, let’s add the Helm repo and update

$ helm repo add hashicorp https://helm.releases.hashicorp.com
"hashicorp" has been added to your repositories

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "myharbor" chart repository
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "confluentinc" chart repository
...Successfully got an update from the "kuma" chart repository
...Successfully got an update from the "azure-samples" chart repository
...Successfully got an update from the "actions-runner-controller" chart repository
...Successfully got an update from the "rhcharts" chart repository
...Successfully got an update from the "adwerx" chart repository
...Successfully got an update from the "sonarqube" chart repository
...Successfully got an update from the "uptime-kuma" chart repository
...Successfully got an update from the "dapr" chart repository
...Successfully got an update from the "novum-rgi-helm" chart repository
...Successfully got an update from the "kubecost" chart repository
...Successfully got an update from the "lifen-charts" chart repository
...Successfully got an update from the "epsagon" chart repository
...Successfully got an update from the "sumologic" chart repository
...Successfully got an update from the "harbor" chart repository
...Successfully got an update from the "nginx-stable" chart repository
...Successfully got an update from the "crossplane-stable" chart repository
...Successfully got an update from the "argo-cd" chart repository
...Successfully got an update from the "datadog" chart repository
...Successfully got an update from the "rancher-latest" chart repository
...Successfully got an update from the "incubator" chart repository
...Successfully got an update from the "gitlab" chart repository
...Successfully got an update from the "newrelic" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈

Then install Vault with helm

$ helm install vault hashicorp/vault
W0816 06:47:56.144942   20905 warnings.go:70] Autopilot set default resource requests for Deployment default/vault-agent-injector, as resource requests were not specified. See http://g.co/gke/autopilot-defaults.
W0816 06:47:56.314607   20905 warnings.go:70] Autopilot set default resource requests for StatefulSet default/vault, as resource requests were not specified. See http://g.co/gke/autopilot-defaults.
W0816 06:47:56.562105   20905 warnings.go:70] AdmissionWebhookController: mutated namespaceselector of the webhooks to enforce GKE Autopilot policies.
NAME: vault
LAST DEPLOYED: Tue Aug 16 06:47:50 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://www.vaultproject.io/docs/


Your release is named vault. To learn more about the release, try:

  $ helm status vault
  $ helm get manifest vault

We decided to use Autopilot to allow Google to manage the nodes. However, there is a cost to this; When launched, my pods were showing Pending

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
helloserver-64f6d5d74b-6ld69            2/2     Running   0          16m
vault-0                                 0/2     Pending   0          89s
vault-agent-injector-674bc999bb-9hjkc   0/2     Pending   0          89s

Describing vault-0 showed it triggered a scale up event

Events:
  Type     Reason            Age        From                                   Message
  ----     ------            ----       ----                                   -------
  Warning  FailedScheduling  74s        gke.io/optimize-utilization-scheduler  0/3 nodes are available: 3 Insufficient cpu, 3 Insufficient memory.
  Normal   TriggeredScaleUp  61s        cluster-autoscaler                     pod triggered scale-up: [{https://www.googleapis.com/compute/v1/projects/myanthosproject2/zones/us-central1-b/instanceGroups/gk3-freshbrewed-autopilo-nap-u5fcr0qm-262a9d55-grp 0->1 (max: 1000)} {https://www.googleapis.com/compute/v1/projects/myanthosproject2/zones/us-central1-f/instanceGroups/gk3-freshbrewed-autopilo-nap-u5fcr0qm-27c1613e-grp 0->1 (max: 1000)}]
  Warning  FailedScheduling  <invalid>  gke.io/optimize-utilization-scheduler  0/5 nodes are available: 2 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 3 Insufficient cpu, 3 Insufficient memory.

After a few moments, new nodes were added and we could see the Pod was running

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
helloserver-64f6d5d74b-6ld69            2/2     Running   0          17m
vault-0                                 1/2     Running   0          28s
vault-agent-injector-674bc999bb-mk9bw   2/2     Running   0          33s

While Vault is indeed running, it is waiting on us to initialize it

$ kubectl exec vault-0 -- vault status
Key                Value
---                -----
Seal Type          shamir
Initialized        false
Sealed             true
Total Shares       0
Threshold          0
Unseal Progress    0/0
Unseal Nonce       n/a
Version            1.11.2
Build Date         2022-07-29T09:48:47Z
Storage Type       file
HA Enabled         false
command terminated with exit code 2

We can port-forward and start the process

$  kubectl port-forward vault-0 8200:8200

/content/images/2022/08/gkeasmvault-08.png

I usually do 3 shares and 2 required.

This will then prompt us to save the tokens and keys

/content/images/2022/08/gkeasmvault-09.png

You can download the JSON

/content/images/2022/08/gkeasmvault-10.png

We then continue to the section where we use the keys to unseal the vault

/content/images/2022/08/gkeasmvault-11.png

Then we can login with the root token

/content/images/2022/08/gkeasmvault-12.png

which brings us to the full Vault UI

/content/images/2022/08/gkeasmvault-13.png

Enable KV secrets engine

One of the most common secrets engines to use with vault is the “Key/Value” one. We can enable in the UI or command line. Let’s use the command line this time

$ kubectl exec -it vault-0 -- /bin/sh
/ $ vault login
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.**********************
token_accessor       GugLWcBLsdwkLT77u3VDGidr
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]
/ $ vault secrets enable -path=secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/
/ $

We can then add a key vault entry

$ vault kv put secret/webapp/config username="myusername" password="justapassword"
====== Secret Path ======
secret/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2022-08-16T11:59:47.377338499Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

Add Kubernetes Auth Method

To allow automated auth for Kubernetes pods, we need to enable that first

/ $ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/
/ $ echo $KUBERNETES_PORT_443_TCP_ADDR
10.21.0.1
/ $ vault write auth/kubernetes/config token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" kubernetes
_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Success! Data written to: auth/kubernetes/config

Then create a read policy

/ $ vault policy write webapp - <<EOF
> path "secret/data/webapp/config" {
>   capabilities = ["read"]
> }
> EOF
Success! Uploaded policy: webapp

And apply it to a role

/ $ vault write auth/kubernetes/role/webapp bound_service_account_names=vault bound_service_account_namespaces=default policies=webapp ttl=24h
Success! Data written to: auth/kubernetes/role/webapp

Testing

Let’s create a sample app deployment that reads from the vault the user/pass secret we created. It’s the “webapp” label that will allow this pod to talk to Vault.

$ cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  labels:
    app: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      serviceAccountName: vault
      containers:
      - name: app
        image: burtlo/exampleapp-ruby:k8s
        imagePullPolicy: Always
        env:
        - name: VAULT_ADDR
          value: "http://vault:8200"
        - name: JWT_PATH
          value: "/var/run/secrets/kubernetes.io/serviceaccount/token"
        - name: SERVICE_PORT
          value: "8080"
---
apiVersion: v1
kind: Service
metadata:
  name: webappsvc
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: webapp
  type: LoadBalancer


$ kubectl apply -f deployment.yaml
Warning: Autopilot set default resource requests for Deployment default/webapp, as resource requests were not specified. See http://g.co/gke/autopilot-defaults.
deployment.apps/webapp created
service/webappsvc created

Now let’s test

$ kubectl get svc webappsvc
NAME        TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)        AGE
webappsvc   LoadBalancer   10.21.0.52   35.184.159.76   80:30500/TCP   36s

/content/images/2022/08/gkeasmvault-14.png

At the same time, we can see that indeed the webapp webapp is being handled by ASM

/content/images/2022/08/gkeasmvault-15.png

We can see it in the Traffic pane

/content/images/2022/08/gkeasmvault-16.png

And details in the Infrastructure pane

/content/images/2022/08/gkeasmvault-17.png

In fact, we can verify we have mTLS encryption happening by way of the Security pain which indicates the traffic between webapp and vault

/content/images/2022/08/gkeasmvault-18.png

Using Istio VirtualService

The APIs keep changing so I find it best to just ask my cluster what APIs we have

$ kubectl api-resources | grep istio
wasmplugins                                        extensions.istio.io/v1alpha1           true         WasmPlugin
istiooperators                     iop,io          install.istio.io/v1alpha1              true         IstioOperator
destinationrules                   dr              networking.istio.io/v1beta1            true         DestinationRule
envoyfilters                                       networking.istio.io/v1alpha3           true         EnvoyFilter
gateways                           gw              networking.istio.io/v1beta1            true         Gateway
proxyconfigs                                       networking.istio.io/v1beta1            true         ProxyConfig
serviceentries                     se              networking.istio.io/v1beta1            true         ServiceEntry
sidecars                                           networking.istio.io/v1beta1            true         Sidecar
virtualservices                    vs              networking.istio.io/v1beta1            true         VirtualService
workloadentries                    we              networking.istio.io/v1beta1            true         WorkloadEntry
workloadgroups                     wg              networking.istio.io/v1alpha3           true         WorkloadGroup
authorizationpolicies                              security.istio.io/v1beta1              true         AuthorizationPolicy
peerauthentications                pa              security.istio.io/v1beta1              true         PeerAuthentication
requestauthentications             ra              security.istio.io/v1beta1              true         RequestAuthentication
telemetries                        telemetry       telemetry.istio.io/v1alpha1            true         Telemetry

We can then create a VirtualService

$ cat virtualservice.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: webap-route
spec:
  hosts:
  - webapp2.svc.cluster.local
  http:
  - name: "webapp-v2-routes"
    match:
    - uri:
        prefix: "/"
    route:
    - destination:
        host: webapp.svc.cluster.local

$ kubectl apply -f virtualservice.yaml
virtualservice.networking.istio.io/webap-route created

$ kubectl get virtualservice
NAME          GATEWAYS   HOSTS                           AGE
webap-route              ["webapp2.svc.cluster.local"]   45s

Until we have a working Gateway, however, it’s not too useful

Vault Auto-inject

To have Vault sidecar injection automatically create a secrets file, we need to update the annotations on the deployment

$ kubectl get deployment webapp -o yaml > deployment.yaml
$ kubectl get deployment webapp -o yaml > deployment.yaml.bak
$ vi deployment.yaml
$ diff -c deployment.yaml.bak deployment.yaml
*** deployment.yaml.bak 2022-08-16 07:31:10.963487005 -0500
--- deployment.yaml     2022-08-16 07:33:44.140439006 -0500
***************
*** 2,7 ****
--- 2,9 ----
  kind: Deployment
  metadata:
    annotations:
+     vault.hashicorp.com/agent-inject-secret-username.txt: secret/data/webapp/config
+     vault.hashicorp.com/role: 'webapp2'
+     vault.hashicorp.com/agent-inject: "true"
      autopilot.gke.io/resource-adjustment: '{"input":{"containers":[{"name":"app"}]},"output":{"containers":[{"limits":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"requests":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"name":"app"}]},"modified":true}'
      deployment.kubernetes.io/revision: "1"
      kubectl.kubernetes.io/last-applied-configuration: |
--- 2,7 ----
$ kubectl apply -f deployment.yaml
deployment.apps/webapp configured

Now let’s test

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
helloserver-64f6d5d74b-6ld69            2/2     Running   0          62m
vault-0                                 2/2     Running   0          44m
vault-agent-injector-674bc999bb-mk9bw   2/2     Running   0          44m
webapp-9c8fc4549-rdrmz                  2/2     Running   0          28m
$ kubectl delete pod webapp-9c8fc4549-rdrmz
pod "webapp-9c8fc4549-rdrmz" deleted

Debugging

I tried a few more things… like creating a bespoke service account

$ kubectl create sa webapp
serviceaccount/webapp created

and adding it to a new role

/ $  vault write auth/kubernetes/role/webapp2 bound_service_account_names=webapp bound_service_account_namespaces=default policies=webapp ttl=24h
Success! Data written to: auth/kubernetes/role/webapp2

But nothing seemed to work until i really followed the guide and patched the deployment after launch

$ cat patch-secrets.yaml
spec:
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: 'true'
        vault.hashicorp.com/role: 'webapp2'
        vault.hashicorp.com/agent-inject-secret-user-config.txt: 'secret/data/webapp/config'
$ kubectl patch deployment webapp --patch "$(cat patch-secrets.yaml)"
deployment.apps/webapp patched

and while I could see it trying to init a container, it would not get past the Init stage.

$ kubectl get pods
NAME                                    READY   STATUS     RESTARTS   AGE
helloserver-64f6d5d74b-6ld69            2/2     Running    0          88m
vault-0                                 2/2     Running    0          71m
vault-agent-injector-674bc999bb-2dhq9   2/2     Running    0          8m10s
webapp-5f68f9758d-sbrkl                 0/3     Init:1/2   0          99s
webapp-686d589867-6jxcf                 2/2     Running    0          2m22s

I tried many paths, recreating the SA, changing roles but I continually got auth issues

$ kubectl logs webapp-695d6fdc84-qtf6p vault-agent-init | tail -n 4
2022-08-16T17:50:58.926Z [INFO]  auth.handler: authenticating
2022-08-16T17:50:58.935Z [ERROR] auth.handler: error authenticating: error="Put \"http://vault.default.svc:8200/v1/auth/kubernetes/login\": dial tcp 10.21.2.12:8200: connect: connection refused" backoff=4m41.48s
2022-08-16T17:55:40.417Z [INFO]  auth.handler: authenticating
2022-08-16T17:55:40.428Z [ERROR] auth.handler: error authenticating: error="Put \"http://vault.default.svc:8200/v1/auth/kubernetes/login\": dial tcp 10.21.2.12:8200: connect: connection refused" backoff=4m55.91s

That’s when I found this article about Istio (ASM) and Vault not working when used in tandem.

I followed a suggestion to add an exclusion on the 8200 port

$ cat patch-secrets.yaml
spec:
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: 'true'
        vault.hashicorp.com/role: 'internal-app'
        vault.hashicorp.com/agent-inject-secret-user-config.txt: 'secret/data/webapp/config'
        traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
$ kubectl patch deployment webapp --patch "$(cat patch-secrets.yaml)"
deployment.apps/webapp patched

Then ensured I was using the “internal-app” service user

$ cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    autopilot.gke.io/resource-adjustment: '{"input":{"containers":[{"name":"app"}]},"output":{"containers":[{"limits":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"requests":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"name":"app"}]},"modified":true}'
    deployment.kubernetes.io/revision: "5"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"autopilot.gke.io/resource-adjustment":"{\"input\":{\"containers\":[{\"name\":\"app\"}]},\"output\":{\"containers\":[{\"limits\":{\"cpu\":\"500m\",\"ephemeral-storage\":\"1Gi\",\"memory\":\"2Gi\"},\"requests\":{\"cpu\":\"500m\",\"ephemeral-storage\":\"1Gi\",\"memory\":\"2Gi\"},\"name\":\"app\"}]},\"modified\":true}","deployment.kubernetes.io/revision":"3","vault.hashicorp.com/agent-inject":"true","vault.hashicorp.com/agent-inject-secret-username.txt":"secret/data/webapp/config","vault.hashicorp.com/role":"internal-app"},"creationTimestamp":"2022-08-16T12:04:56Z","generation":8,"labels":{"app":"webapp"},"name":"webapp","namespace":"default","resourceVersion":"175838","uid":"4674ae91-6990-4078-bbed-44c8d9f3ec45"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"webapp"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"annotations":{"vault.hashicorp.com/agent-inject":"true","vault.hashicorp.com/agent-inject-secret-user-config.txt":"secret/data/webapp/config","vault.hashicorp.com/role":"internal-app"},"creationTimestamp":null,"labels":{"app":"webapp"}},"spec":{"containers":[{"env":[{"name":"VAULT_ADDR","value":"http://vault:8200"},{"name":"JWT_PATH","value":"/var/run/secrets/kubernetes.io/serviceaccount/token"},{"name":"SERVICE_PORT","value":"8080"}],"image":"burtlo/exampleapp-ruby:k8s","imagePullPolicy":"Always","name":"app","resources":{"limits":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"requests":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"}},"securityContext":{"capabilities":{"drop":["NET_RAW"]}},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{"seccompProfile":{"type":"RuntimeDefault"}},"serviceAccount":"webapp","serviceAccountName":"webapp","terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2022-08-16T12:51:44Z","lastUpdateTime":"2022-08-16T12:51:44Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"},{"lastTransitionTime":"2022-08-16T14:29:52Z","lastUpdateTime":"2022-08-16T14:29:52Z","message":"ReplicaSet \"webapp-5f68f9758d\" has timed out progressing.","reason":"ProgressDeadlineExceeded","status":"False","type":"Progressing"}],"observedGeneration":8,"readyReplicas":1,"replicas":2,"unavailableReplicas":1,"updatedReplicas":1}}
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/agent-inject-secret-username.txt: secret/data/webapp/config
    vault.hashicorp.com/role: internal-app
  creationTimestamp: "2022-08-16T12:04:56Z"
  generation: 10
  labels:
    app: webapp
  name: webapp
  namespace: default
  resourceVersion: "383401"
  uid: 4674ae91-6990-4078-bbed-44c8d9f3ec45
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: webapp
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      annotations:
        traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-inject-secret-user-config.txt: secret/data/webapp/config
        vault.hashicorp.com/role: internal-app
      creationTimestamp: null
      labels:
        app: webapp
    spec:
      containers:
      - env:
        - name: VAULT_ADDR
          value: http://vault:8200
        - name: JWT_PATH
          value: /var/run/secrets/kubernetes.io/serviceaccount/token
        - name: SERVICE_PORT
          value: "8080"
        image: burtlo/exampleapp-ruby:k8s
        imagePullPolicy: Always
        name: app
        resources:
          limits:
            cpu: 500m
            ephemeral-storage: 1Gi
            memory: 2Gi
          requests:
            cpu: 500m
            ephemeral-storage: 1Gi
            memory: 2Gi
        securityContext:
          capabilities:
            drop:
            - NET_RAW
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      serviceAccount: internal-app
      serviceAccountName: internal-app
      terminationGracePeriodSeconds: 30
status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: "2022-08-16T12:51:44Z"
    lastUpdateTime: "2022-08-16T12:51:44Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: "2022-08-16T18:00:18Z"
    lastUpdateTime: "2022-08-16T18:00:19Z"
    message: ReplicaSet "webapp-f9f557f7f" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  observedGeneration: 10
  readyReplicas: 1
  replicas: 2
  unavailableReplicas: 1
  updatedReplicas: 1

And it finally was unblocked

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
helloserver-64f6d5d74b-6ld69            2/2     Running   0          9h
vault-0                                 2/2     Running   0          9h
vault-agent-injector-674bc999bb-kb58z   2/2     Running   0          6h26m
webapp-7db5b4d86c-42ngl                 3/3     Running   0          173m

We can verify we can get the secret as a mounted file (as done by the Vault sidecar injector) using kubectl

$ kubectl exec `kubectl get pod -l app=webapp -o jsonpath="{.items[0].metadata.name}"` --container app -- ls /vault/secrets/
user-config.txt
$ kubectl exec `kubectl get pod -l app=webapp -o jsonpath="{.items[0].metadata.name}"` --container app -- cat /vault/secrets/user-config.txt
data: map[password:justapassword username:myusername]
metadata: map[created_time:2022-08-16T11:59:47.377338499Z custom_metadata:<nil> deletion_time: destroyed:false version:1]

Cleanup

We can cleanup the cluster we created if we are done exploring Vault and ASM

$  gcloud container clusters list --project $PROJECTID
NAME                             LOCATION     MASTER_VERSION   MASTER_IP     MACHINE_TYPE  NODE_VERSION     NUM_NODES  STATUS
freshbrewed-autopilot-cluster-1  us-central1  1.22.10-gke.600  35.239.95.24  e2-medium     1.22.10-gke.600  6          RUNNING

$ gcloud container clusters delete --project $PROJECTID --region us-central1 freshbrewed-autopilot-cluster-1
The following clusters will be deleted.
 - [freshbrewed-autopilot-cluster-1] in [us-central1]

Do you want to continue (Y/n)?  y

Deleting cluster freshbrewed-autopilot-cluster-1...⠏

Summary

We created a new GKE Autopilot cluster using the command line after enabling the required APIs. Once launched, we installed a sample app and verified Istio (ASM) was working.

Lastly, we added Hashi Vault with Helm. We created a secret and verified with a sample Ruby app we could fetch it then setup, and debugged, Vault sidecar injection working alongside Istio.

I would like to dig deeper into Istio gateways with TLS and if I were to keep the cluster, set up Vault auto-unseal with GCP KMS.

GCP GKE ASM Anthos Hashi Vault Istio

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes