ここのブログでHashicorp Vaultを扱うのはそういえば初めてだったけど、Vault使う上で面倒なunsealなどを自動処理する方法を調べる機会があったのでまとめました。
なお、unseal keyやroot tokenを記事用に全部書き出してますが、機密情報なので実際は厳重に管理してください。
環境は以下の通り、HelmでK8sクラスタへデプロイしたHashicorp Vaultです。
- HashiCorp Vault 1.15.2
- chart version 0.27.0
- Kubernetes: 1.29.1
あと、Azureの権限周りはあまり詳しくなくてだいぶ適当なので、不要な権限を与えてるかもしれない。詳しい人教えてください。
なお、この記事はHashicorp Vaultのデプロイ方法について書いてるだけで、アプリケーションとしてのVaultの使用方法は全く書いていません。
Helmチャートの設定
helm repo add hashicorp https://helm.releases.hashicorp.com helm search repo hashicorp/vault
$ helm search repo hashicorp NAME CHART VERSION APP VERSION DESCRIPTION hashicorp/consul 1.3.2 1.17.2 Official HashiCorp Consul Chart hashicorp/terraform 1.1.2 Install and configure Terraform Cloud Operator ... hashicorp/terraform-cloud-operator 2.2.0 2.2.0 Official Helm chart for HashiCorp Terraform Clo... hashicorp/terraform-enterprise 1.1.1 1.16.0 Official HashiCorp Terraform-Enterprise Chart hashicorp/vault 0.27.0 1.15.2 Official HashiCorp Vault Chart hashicorp/vault-secrets-operator 0.4.3 0.4.3 Official Vault Secrets Operator Chart hashicorp/waypoint 0.1.21 0.11.3 Official Helm Chart for HashiCorp Waypoint
スタンドアロンモードのインストール(前置き)
読み飛ばし可
デプロイ
デフォルト値でVaultをインストール。(values.yaml
は不要)
$ helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace Release "my-vault" does not exist. Installing it now. NAME: my-vault LAST DEPLOYED: Sat Feb 10 16:01:21 2024 NAMESPACE: vault 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://developer.hashicorp.com/vault/docs Your release is named my-vault. To learn more about the release, try: $ helm status my-vault $ helm get manifest my-vault
StatefulSetでVaultのpodがデプロイされますがnot readyになります。
$ kubectl get pod,sts -n vault NAME READY STATUS RESTARTS AGE pod/my-vault-agent-injector-6b66d76649-xmvbc 1/1 Running 0 25s pod/my-vault-0 0/1 Running 0 25s NAME READY AGE statefulset.apps/my-vault 0/1 25s
ログを見ると以下の通り。
2024-02-10T07:01:56.144Z [INFO] core: security barrier not initialized 2024-02-10T07:01:56.144Z [INFO] core: seal configuration missing, not initialized
Vaultのステータスをvault status
コマンドを実行して確認すると、未初期化(Initializedがtrue
)であることが確認できます。
$ kubectl exec -n vault my-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.15.2 Build Date 2023-11-06T11:33:28Z Storage Type file HA Enabled false
初期化とunseal
Vaultを使える状態にするにはunsealが必要で、そのためにはまず初期化します。
初期化はVaultのコマンドvault operator init
を実行します。
$ kubectl exec -n vault my-vault-0 -- vault operator init Unseal Key 1: PZuSm+GpsG6xoLz/1hm+kdke/q/UWm+kx2ewtyp1yJ3x Unseal Key 2: 2eW+IlscQ1cKEu/pATh3ZJvJ5EZ9D5jopRbrUgACiC8h Unseal Key 3: DbVJzW0vcT4a4bhS3lNIihwTizYridY6Rzt282Prl34m Unseal Key 4: Ba4+tWfLXtyQ0hXvdHQbXlOOfHqCPvTgDDC7wtN6Ok86 Unseal Key 5: avg1gIAbhDDk4SRpSqby6gDENhOJPJzqk93CE8yCar+c Initial Root Token: hvs.jnkpJnAEN4PDyPZZF3RJoGJV Vault initialized with 5 key shares and a key threshold of 3. Please securely distribute the key shares printed above. When the Vault is re-sealed, restarted, or stopped, you must supply at least 3 of these keys to unseal it before it can start servicing requests. Vault does not store the generated root key. Without at least 3 keys to reconstruct the root key, Vault will remain permanently sealed! It is possible to generate new unseal keys, provided you have a quorum of existing unseal keys shares. See "vault operator rekey" for more information.
これでrootトークンと5つのunsealキーを取得できました。
ステータスを確認すると、Initializedがtrue
になります。
※ これは記事用に全部書き出してますが、機密情報なので実際は厳重に管理してください。
$ kubectl exec -n vault my-vault-0 -- vault status Key Value --- ----- Seal Type shamir Initialized true Sealed true Total Shares 5 Threshold 3 Unseal Progress 0/3 Unseal Nonce n/a Version 1.15.2 Build Date 2023-11-06T11:33:28Z Storage Type file HA Enabled false
初期化が済んだらunsealを行います。
unsealはvault operator unseal
を実行します。
ここでは記事用に引数に指定してますが、機密情報をローカルエコーするのはよくないので、引数無しで実行して対話的に入力した方がよいです。(が、自動化するならこの方法かな?)
$ kubectl exec -n vault my-vault-0 -- vault operator unseal PZuSm+GpsG6xoLz/1hm+kdke/q/UWm+kx2ewtyp1yJ3x Key Value --- ----- Seal Type shamir Initialized true Sealed true Total Shares 5 Threshold 3 Unseal Progress 1/3 Unseal Nonce b8ff2c62-5fdd-18b0-6e20-ddff956f7b2f Version 1.15.2 Build Date 2023-11-06T11:33:28Z Storage Type file HA Enabled false
Unseal Progressが1/3になりました。あと2回実行する必要があるので、5つあるキーのうちあと2個使って同じコマンドを実行します。
$ kubectl exec -n vault my-vault-0 -- vault operator unseal 2eW+IlscQ1cKEu/pATh3ZJvJ5EZ9D5jopRbrUgACiC8h Key Value --- ----- Seal Type shamir Initialized true Sealed true Total Shares 5 Threshold 3 Unseal Progress 2/3 Unseal Nonce b8ff2c62-5fdd-18b0-6e20-ddff956f7b2f Version 1.15.2 Build Date 2023-11-06T11:33:28Z Storage Type file HA Enabled false $ kubectl exec -n vault my-vault-0 -- vault operator unseal DbVJzW0vcT4a4bhS3lNIihwTizYridY6Rzt282Prl34m Key Value --- ----- Seal Type shamir Initialized true Sealed false Total Shares 5 Threshold 3 Version 1.15.2 Build Date 2023-11-06T11:33:28Z Storage Type file Cluster Name vault-cluster-2f48696e Cluster ID e109d3d3-5d52-c081-667f-8fefcc8067f6 HA Enabled false
これでSealedの値がfalse
になり、unseal状態になりました。
$ kubectl get pod -n vault NAME READY STATUS RESTARTS AGE my-vault-agent-injector-6b66d76649-xmvbc 1/1 Running 0 4m47s my-vault-0 1/1 Running 0 4m47s
この通りunsealされるとPodはready状態になり、利用準備ができました。
HAクラスターでのインストール
以下は、スタンドアロンでデプロイした環境はすべて削除済みで新規に構築しています。
ここからは耐障害性を高めるためのHAクラスタ構成でデプロイします。
※ AntiAffinityの設定によって1pod1nodeデプロイになるため、あらかじめノードは5ノードでKubernetesを構築しています。
$ kubectl get node NAME STATUS ROLES AGE VERSION b513c8231aad Ready <none> 6h41m v1.29.1+k3s2 9952de1bc166 Ready <none> 6h41m v1.29.1+k3s2 2f5893c02aa3 Ready <none> 6h40m v1.29.1+k3s2 412b213f3d10 Ready <none> 6h41m v1.29.1+k3s2 4c1f8de42bfe Ready control-plane,master 6h41m v1.29.1+k3s2 8239e04d52ef Ready <none> 6h41m v1.29.1+k3s2
values.yamlは以下の通り。
config
の部分はデフォルトのままですが、本題で使うので含めています。
setNodeId
は無くても良いですが、Vaultノード名が分かりやすくなるので有効にしています。
server: # Run Vault in "HA" mode. There are no storage requirements unless the audit log # persistence is required. In HA mode Vault will configure itself to use Consul # for its storage backend. The default configuration provided will work the Consul # Helm project by default. It is possible to manually configure Vault to use a # different HA backend. ha: enabled: true replicas: 1 # Enables Vault's integrated Raft storage. Unlike the typical HA modes where # Vault's persistence is external (such as Consul), enabling Raft mode will create # persistent volumes for Vault to store data according to the configuration under server.dataStorage. # The Vault cluster will coordinate leader elections and failovers internally. raft: # Enables Raft integrated storage enabled: true # Set the Node Raft ID to the name of the pod setNodeId: true # Note: Configuration files are stored in ConfigMaps so sensitive data # such as passwords should be either mounted through extraSecretEnvironmentVars # or through a Kube secret. For more information see: # https://developer.hashicorp.com/vault/docs/platform/k8s/helm/run#protecting-sensitive-vault-configurations config: | ui = true listener "tcp" { tls_disable = 1 address = "[::]:8200" cluster_address = "[::]:8201" # Enable unauthenticated metrics access (necessary for Prometheus Operator) #telemetry { # unauthenticated_metrics_access = "true" #} } storage "raft" { path = "/vault/data" } service_registration "kubernetes" {}
helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml
$ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-0 0/1 Running 0 5m47s 10.42.4.13 9952de1bc166 <none> <none> my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 5m47s 10.42.5.12 b513c8231aad <none> <none> my-vault-1 0/1 Running 0 5m46s 10.42.3.5 8239e04d52ef <none> <none> my-vault-2 0/1 Running 0 5m46s 10.42.2.5 4c1f8de42bfe <none> <none>
3podがデプロイされましたが、スタンドアロンの場合同様に、初期化とunsealが必要です。
リーダーノードの初期化とunseal
レプリカ3のStatefulSetなので、0番のpodでまず初期化とunsealを実施。
$ kubectl exec -n vault my-vault-0 -- vault operator init Unseal Key 1: ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2 Unseal Key 2: ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz Unseal Key 3: pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM Unseal Key 4: 6q2Fi8q7AOc0n6iPJeoWTnio6QjCDMlxwZFyD7uw9GqE Unseal Key 5: zLEVdCLOMXAptoZmBZRYVpSG8o8lCfK/bJi5pQ7jE8Sr Initial Root Token: hvs.GuR8WbGjN7X3roX6UoJYztON Vault initialized with 5 key shares and a key threshold of 3. Please securely distribute the key shares printed above. When the Vault is re-sealed, restarted, or stopped, you must supply at least 3 of these keys to unseal it before it can start servicing requests. Vault does not store the generated root key. Without at least 3 keys to reconstruct the root key, Vault will remain permanently sealed! It is possible to generate new unseal keys, provided you have a quorum of existing unseal keys shares. See "vault operator rekey" for more information.
kubectl exec -n vault my-vault-0 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2 kubectl exec -n vault my-vault-0 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz kubectl exec -n vault my-vault-0 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
これでvault-0がunsealされreadyになりました。
$ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 11m 10.42.5.12 b513c8231aad <none> <none> my-vault-1 0/1 Running 0 11m 10.42.3.5 8239e04d52ef <none> <none> my-vault-2 0/1 Running 0 11m 10.42.2.5 4c1f8de42bfe <none> <none> my-vault-0 1/1 Running 0 11m 10.42.4.13 9952de1bc166 <none> <none>
ノードのクラスターへの参加
現時点ではまだシングルノード状態。
/ $ vault login hvs.GuR8WbGjN7X3roX6UoJYztON 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.GuR8WbGjN7X3roX6UoJYztON token_accessor GpmzT1ZyBYs1bduOTRYVnALW token_duration ∞ token_renewable false token_policies ["root"] identity_policies [] policies ["root"] / $ vault operator members Host Name API Address Cluster Address Active Node Version Upgrade Version Redundancy Zone Last Echo --------- ----------- --------------- ----------- ------- --------------- --------------- --------- my-vault-0 http://10.42.4.13:8200 https://my-vault-0.my-vault-internal:8201 true 1.15.2 1.15.2 n/a n/a
HA構成にするため残りの2podをクラスターに参加させるには、それぞれraft join
操作をリーダーであるvault-0に対して行います。
podではHTTPでlistenしているので、http://
を付与するのをお忘れなく。
URL(pod名service名)はHelmのリリース名に依存するのでそこは読み替えること。
$ kubectl exec -n vault my-vault-1 -- vault operator raft join http://my-vault-0.my-vault-internal:8200 Key Value --- ----- Joined true $ kubectl exec -n vault my-vault-2 -- vault operator raft join http://my-vault-0.my-vault-internal:8200 Key Value --- ----- Joined true
$ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 33m 10.42.5.12 b513c8231aad <none> <none> my-vault-1 0/1 Running 0 33m 10.42.3.5 8239e04d52ef <none> <none> my-vault-2 0/1 Running 0 33m 10.42.2.5 4c1f8de42bfe <none> <none> my-vault-0 1/1 Running 0 33m 10.42.4.13 9952de1bc166 <none> <none>
これでvault-1およびvault-2は初期化済みseal状態になるので、それぞれunsealを行います。
kubectl exec -n vault my-vault-1 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2 kubectl exec -n vault my-vault-1 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz kubectl exec -n vault my-vault-1 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM kubectl exec -n vault my-vault-2 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2 kubectl exec -n vault my-vault-2 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz kubectl exec -n vault my-vault-2 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
これでようやく全podがready状態になり、クラスターに参加できた状態になりました。
$ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 35m 10.42.5.12 b513c8231aad <none> <none> my-vault-0 1/1 Running 0 35m 10.42.4.13 9952de1bc166 <none> <none> my-vault-1 1/1 Running 0 35m 10.42.3.5 8239e04d52ef <none> <none> my-vault-2 1/1 Running 0 35m 10.42.2.5 4c1f8de42bfe <none> <none> $ kubectl exec -it -n vault my-vault-0 -- vault operator members Host Name API Address Cluster Address Active Node Version Upgrade Version Redundancy Zone Last Echo --------- ----------- --------------- ----------- ------- --------------- --------------- --------- my-vault-2 http://10.42.2.5:8200 https://my-vault-2.my-vault-internal:8201 false 1.15.2 1.15.2 n/a 2024-02-10T12:30:09Z my-vault-1 http://10.42.3.5:8200 https://my-vault-1.my-vault-internal:8201 false 1.15.2 1.15.2 n/a 2024-02-10T12:30:07Z my-vault-0 http://10.42.4.13:8200 https://my-vault-0.my-vault-internal:8201 true 1.15.2 1.15.2 n/a n/a
これでHA構成でのVaultの利用準備ができました。
スケールアウト/スケールイン
values.yaml
にreplicas: 3
を指定したため、初期状態で3つのpodがデプロイされていますが、StatefulSetのレプリカ数を変更することでスケールできます。
例えば5台構成にするには、普通にkubectl scale
で設定できます。
$ kubectl get sts -n vault NAME READY AGE my-vault 3/3 11h $ kubectl scale -n vault statefulset --replicas 5 my-vault statefulset.apps/my-vault scaled $ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 11h 10.42.5.12 b513c8231aad <none> <none> my-vault-0 1/1 Running 0 11h 10.42.4.13 9952de1bc166 <none> <none> my-vault-1 1/1 Running 0 11h 10.42.3.5 8239e04d52ef <none> <none> my-vault-2 1/1 Running 0 11h 10.42.2.5 4c1f8de42bfe <none> <none> my-vault-3 0/1 Running 0 34s 10.42.0.5 2f5893c02aa3 <none> <none> my-vault-4 0/1 Running 0 34s 10.42.1.5 412b213f3d10 <none> <none>
ただし、新しく作成されたPodはやはりraft join
とunseal
が必要です。
kubectl exec -n vault my-vault-3 -- vault operator raft join http://my-vault-0.my-vault-internal:8200 kubectl exec -n vault my-vault-4 -- vault operator raft join http://my-vault-0.my-vault-internal:8200 kubectl exec -n vault my-vault-3 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2 kubectl exec -n vault my-vault-3 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz kubectl exec -n vault my-vault-3 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM kubectl exec -n vault my-vault-4 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2 kubectl exec -n vault my-vault-4 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz kubectl exec -n vault my-vault-4 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
これで5台クラスターとなりました。
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- my-vault-0 my-vault-0.my-vault-internal:8201 leader true my-vault-1 my-vault-1.my-vault-internal:8201 follower true my-vault-2 my-vault-2.my-vault-internal:8201 follower true my-vault-3 my-vault-3.my-vault-internal:8201 follower true my-vault-4 my-vault-4.my-vault-internal:8201 follower true
スケールインの場合は、いきなりレプリカ数を減らすのはNGで、まずVault的にクラスターから除外する必要があり、join
と逆でremove-peer
を行います。
ここでは追加でスケールしたvault-3とvault-4を削除し、レプリカ3に戻す作業を行ってみます。
といってもコマンドは簡単で、remove-peer
の引数にlist-peers
でリストされているNode名を指定します。
この例ではvalues.yaml
でsetNodeId: true
の設定を入れているのでNode名=Pod名になってます。(指定がオフの場合はランダムなハッシュ値になります)
$ kubectl exec -n vault my-vault-0 -- vault operator raft remove-peer my-vault-4 Peer removed successfully! $ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- my-vault-0 my-vault-0.my-vault-internal:8201 leader true my-vault-1 my-vault-1.my-vault-internal:8201 follower true my-vault-2 my-vault-2.my-vault-internal:8201 follower true my-vault-3 my-vault-3.my-vault-internal:8201 follower true
これでmy-vault-4
がクラスターから除外されました。
同じ要領でmy-vault-3
も除外します。
$ kubectl exec -n vault my-vault-0 -- vault operator raft remove-peer my-vault-3 Peer removed successfully! $ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- my-vault-0 my-vault-0.my-vault-internal:8201 leader true my-vault-1 my-vault-1.my-vault-internal:8201 follower true my-vault-2 my-vault-2.my-vault-internal:8201 follower true
これでStatefulSetのレプリカ数を3に戻せばOKです。
$ kubectl scale -n vault statefulset --replicas 3 my-vault statefulset.apps/my-vault scaled $ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 11h 10.42.5.12 b513c8231aad <none> <none> my-vault-0 1/1 Running 0 11h 10.42.4.13 9952de1bc166 <none> <none> my-vault-1 1/1 Running 0 11h 10.42.3.5 8239e04d52ef <none> <none> my-vault-2 1/1 Running 0 11h 10.42.2.5 4c1f8de42bfe <none> <none> my-vault-4 1/1 Terminating 0 19m 10.42.1.5 412b213f3d10 <none> <none> my-vault-3 1/1 Terminating 0 19m 10.42.0.5 2f5893c02aa3 <none> <none>
remove-peer
を行わずにスケールインを行うと、削除された分のPodはクラスターには残ったままunhealth状態で残ったままとなります。
いきなりレプリカ3にすると、以下のようになります。
peersのリストは5台ある状態。
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- my-vault-0 my-vault-0.my-vault-internal:8201 leader true my-vault-1 my-vault-1.my-vault-internal:8201 follower true my-vault-2 my-vault-2.my-vault-internal:8201 follower true my-vault-3 my-vault-3.my-vault-internal:8201 follower true my-vault-4 my-vault-4.my-vault-internal:8201 follower true
ただし、ステータスはhealthがfalseになります。
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state Healthy: false Failure Tolerance: 0 Leader: my-vault-0 Voters: my-vault-0 my-vault-1 my-vault-2 my-vault-3 my-vault-4 Servers: my-vault-0 : : my-vault-3 Name: my-vault-3 Address: my-vault-3.my-vault-internal:8201 Status: voter Node Status: alive Healthy: false : : my-vault-4 Name: my-vault-4 Address: my-vault-4.my-vault-internal:8201 Status: voter Node Status: alive Healthy: false
この状態からでもremove-peer my-vault-3
remove-peer my-vault-4
を実行すれば3台構成へ変更可能。
自動unsealと自動join (ここから本題)
ここまで手動での初期化とunsealを行ったデプロイについてみてきましたが…
Pod起動時に必ずunsealが必要なため、率直に言ってKubernetesのオートヒーリングなどとの相性も悪く、運用でこのコマンド実行はやってられないと思います。
例えばkubectl delete pod
して再作成されたpodもnot readyなので、都度unsealする必要があります。
最初はこの仕様を知ってnot readyなpodを検出してunsealを行うプログラムとか書いてやろうと思いましたが、そんなことしなくても標準でクラウドサービスの機密情報を保持する機能と連携してauto unsealを行う機能が備わっています。
本エントリでは、AzureのKey Vaultサービスを使ったauto unsealについて説明します。
※ ここまでは単にVaultと雑に記載してましたが、ここからはAzure Key Vaultとややこしくなるので、「HashiCorp Vault」「Azure Key Vault」と表記します。
auto unseal with Azure Key Vault
公式ドキュメントにチュートリアルがあります。
チュートリアルの流れをざっくり紹介すると、以下の通り
なので仕組みを把握するにはこの中身を追っていけばOK
言い方を変えると「自分の環境のVaultで自動unsealをするには」を実現するには中身を読み解く必要がありました。
アプリケーションとシークレットの作成
「アプリの登録」で、「新規登録」から新しくAuto-unseal用のトークン発行のためのアプリを登録します。
作成できたら、概要ページで以下の値をメモ
- アプリケーション(クライアント)ID
- ディレクトリ(テナント)ID
次に「証明書とシークレット」で「クライアント シークレット」を新規作成。
今回は動作確認用なので、デフォルトの180日期限。
作成できたらシークレットの値(IDでなく)をメモ(一度画面を離れると非表示になるので注意)。
次に「APIのアクセス許可」で「アクセス許可の追加」し、公式ドキュメントは「Azure Active Directory Graph」を追加するよう記載されてるけど2022年6月に非推奨になったようなので、かわりに「Microsoft Graph」を選択。
まず「委任されたアクセス許可」を押下し、「User」の項目にある「User.Read」にチェックされているのを確認(デフォルトでこれがチェックされてる)。
次に「アプリケーションの許可」を押下し、以下をチェック
- 「Application」の項目にある「Application.ReadWrite.All」
- 「Directory」の項目にある「Directory.ReadWrite.All」
最後に下部の「アクセス許可の追加」を押下。
APIのアクセス許可の画面に戻ると、状態が「既定のディレクトリに付与されていません」になっているので、「既定のディレクトリに管理者の同意を与えます」を押下。
「既定のディレクトリに付与されました」になればOK
次にサブスクリプションの画面で、まずサブスクリプションIDを確認。
次に「アクセス制御(IAM)」の画面で「+追加」から「ロール割り当ての追加」を押下。
「ロール」タブでOwner
を選択とあるけど……おそらく「特権管理者ロール」で「所有者」で検索して出てくるものを選択。ちょっと権限強すぎそうだけど、とりあえず。
次に「メンバー」タブを押下し、「アクセスの割り当先」に「ユーザー、グループ、またはサービスプリンシパル」をチェック、「メンバーを選択する」で、先ほど作成したアプリケーション名を入力。
条件はドキュメントには何も書かれてない(「所有者」を選択するとこれが必要)けど、デフォルトのまま進めると割り当てできないので、適当だけど「ロールとプリンシパルの選択」から「ロールの制約」->「ロールの追加」で「所有者」をチェック。これで「レビューと割り当て」が可能になります。
キーコンテナーの作成と権限付与
ここからはチュートリアルのドキュメントでなく、資材として用意されてるTerraformのコードを参考にします。
まずはキーコンテナーを作成します。
アクセス構成の画面では、「自分」に操作権限を付与するために、「アクセス許可モデル」を「コンテナーのアクセスポリシー」にチェックし、下部に追加される「アクセスポリシー」で自分をチェック。
ネットワークはデフォルトのままで作成します。
作成が完了したら、再度作成済みのキーコンテナーに移動し、「アクセスポリシー」画面で「作成」し、「キーのアクセス許可」で以下3つにチェックを入れます。
- 取得
- キーの折り返しを解除
- キーを折り返す
プリンシパルでは作成済みアプリケーションのIDか名前を入力して選択、あとはそのまま作成します。
次に「キー」に移動し、unsealに使うキーを作成します。
パラメタはデフォルトのRSA2048ビットのままでOKです。
TerraformのコードにはキーのオプションでwrapKey
/unwrapKey
がありますが、作成時に選択できる画面がないのでそのまま作成します。
作成後に詳細を見ると必要な項目はチェックがついてるのでそのまま使用します。
これでAzure Key Vaultの名前と、キーの名前がそろいました。
HashiCorp Vault設定
Azure Key Vaultを使った設定はこちら。
auto unseal設定
取得できた各値をセットして再デプロイします。
自動join設定はまだ入れてないので、いったんレプリカ数は1にし、消えたPodが使っていたストレージ(pvc/pv)も削除しておきます。
server: # Run Vault in "HA" mode. There are no storage requirements unless the audit log # persistence is required. In HA mode Vault will configure itself to use Consul # for its storage backend. The default configuration provided will work the Consul # Helm project by default. It is possible to manually configure Vault to use a # different HA backend. ha: enabled: true replicas: 1 # Enables Vault's integrated Raft storage. Unlike the typical HA modes where # Vault's persistence is external (such as Consul), enabling Raft mode will create # persistent volumes for Vault to store data according to the configuration under server.dataStorage. # The Vault cluster will coordinate leader elections and failovers internally. raft: # Enables Raft integrated storage enabled: true # Set the Node Raft ID to the name of the pod setNodeId: true # Note: Configuration files are stored in ConfigMaps so sensitive data # such as passwords should be either mounted through extraSecretEnvironmentVars # or through a Kube secret. For more information see: # https://developer.hashicorp.com/vault/docs/platform/k8s/helm/run#protecting-sensitive-vault-configurations config: | ui = true listener "tcp" { tls_disable = 1 address = "[::]:8200" cluster_address = "[::]:8201" # Enable unauthenticated metrics access (necessary for Prometheus Operator) #telemetry { # unauthenticated_metrics_access = "true" #} } storage "raft" { path = "/vault/data" } service_registration "kubernetes" {} seal "azurekeyvault" { client_id = "YOUR-APP-ID" client_secret = "YOUR-APP-PASSWORD" tenant_id = "YOUR-AZURE-TENANT-ID" vault_name = "Azure Key Value name on Azure" key_name = "Key name on Azure" }
helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml
ただし、設定のConfigMapは更新されるけど、Podは再作成されず設定が反映されないのでPodを削除します。
$ kubectl delete pod -n vault my-vault-0 pod "my-vault-0" deleted $ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 17h 10.42.5.12 b513c8231aad <none> <none> my-vault-0 0/1 Running 0 3s 10.42.4.18 9952de1bc166 <none> <none>
起動しましたが、まだAuto-unseal設定が反映されていないためnot readyになります。ここで-migrate
オプションをつけてunsealを行います。
$ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2 Key Value --- ----- Recovery Seal Type shamir Initialized true Sealed true Total Recovery Shares 5 Threshold 3 Unseal Progress 1/3 ... $ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz Key Value --- ----- Recovery Seal Type shamir Initialized true Sealed true Total Recovery Shares 5 Threshold 3 Unseal Progress 2/3 ... $ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM Key Value --- ----- Recovery Seal Type shamir Initialized true Sealed false Total Recovery Shares 5 Threshold 3 Seal Migration in Progress true $ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 17h 10.42.5.12 b513c8231aad <none> <none> my-vault-0 1/1 Running 0 49s 10.42.4.22 9952de1bc166 <none> <none>
これでunsealとともにAuto-unsealも有効になります。
$ kubectl delete pod -n vault my-vault-0 pod "my-vault-0" deleted
ためしにPodを削除してみると、
$ kubectl get pod -n vault -o wide -w NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 17h 10.42.5.12 b513c8231aad <none> <none> my-vault-0 0/1 Running 0 1s 10.42.4.25 9952de1bc166 <none> <none> my-vault-0 1/1 Running 0 5s 10.42.4.25 9952de1bc166 <none> <none>
この通り、自動でunsealされready状態になります。
auto join設定
この時点で自動unsealは動作しますが、レプリカ数を増やした時のクラスターへのjoinは別途必要です。
が、これも自動で処理するための設定があり、retry_join
でリーダーのサーバーを指定しておけば良いです。
config: | ui = true listener "tcp" { tls_disable = 1 address = "[::]:8200" cluster_address = "[::]:8201" # Enable unauthenticated metrics access (necessary for Prometheus Operator) #telemetry { # unauthenticated_metrics_access = "true" #} } storage "raft" { path = "/vault/data" retry_join { leader_api_addr = "http://my-vault-0.my-vault-internal:8200" } retry_join { leader_api_addr = "http://my-vault-1.my-vault-internal:8200" } retry_join { leader_api_addr = "http://my-vault-2.my-vault-internal:8200" } } service_registration "kubernetes" {} seal "azurekeyvault" { : }
これでレプリカを3にすると、
$ kubectl scale -n vault statefulset --replicas 3 my-vault statefulset.apps/my-vault scaled
$ kubectl get pod -n vault -o wide -w NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 18h 10.42.5.12 b513c8231aad <none> <none> my-vault-0 1/1 Running 0 2m9s 10.42.4.25 9952de1bc166 <none> <none> my-vault-1 0/1 Pending 0 2s <none> <none> <none> <none> my-vault-2 0/1 Pending 0 2s <none> <none> <none> <none> my-vault-1 0/1 Pending 0 3s <none> 8239e04d52ef <none> <none> my-vault-1 0/1 ContainerCreating 0 3s <none> 8239e04d52ef <none> <none> my-vault-1 0/1 Running 0 4s 10.42.3.10 8239e04d52ef <none> <none> my-vault-1 0/1 Running 0 4s 10.42.3.10 8239e04d52ef <none> <none> my-vault-1 0/1 Running 0 9s 10.42.3.10 8239e04d52ef <none> <none> my-vault-1 0/1 Running 0 9s 10.42.3.10 8239e04d52ef <none> <none> my-vault-1 1/1 Running 0 9s 10.42.3.10 8239e04d52ef <none> <none> my-vault-2 0/1 Pending 0 9s <none> b513c8231aad <none> <none> my-vault-2 0/1 ContainerCreating 0 9s <none> b513c8231aad <none> <none> my-vault-2 0/1 ContainerCreating 0 17s <none> b513c8231aad <none> <none> my-vault-2 0/1 Running 0 18s 10.42.5.14 b513c8231aad <none> <none> my-vault-2 0/1 Running 0 28s 10.42.5.14 b513c8231aad <none> <none> my-vault-2 0/1 Running 0 28s 10.42.5.14 b513c8231aad <none> <none> my-vault-2 1/1 Running 0 28s 10.42.5.14 b513c8231aad <none> <none> $ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- my-vault-0 my-vault-0.my-vault-internal:8201 leader true my-vault-1 my-vault-1.my-vault-internal:8201 follower true my-vault-2 my-vault-2.my-vault-internal:8201 follower true
この通り、自動でクラスターへ追加とunsealが行われるようになりました。
auto remove設定
この状態ならスケーリングの際の運用上の支障はほぼなくなったと思いますが、ついでなのでスケールインの際のクラスターからの除外も自動化してみます。
これはraftのautopilotを使います。
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state Healthy: true Failure Tolerance: 1 Leader: my-vault-0 Voters: my-vault-0 my-vault-1 my-vault-2 Servers: my-vault-0 : :
現在3台構成のため、耐障害性は1(1台だけならダウンしてもサービス可能)の状態。
ここにautopilotの設定を追加していきます。(ただ、コマンドかAPIで入れるしか例がないんだけど、設定ファイルでは不可?)
現在の設定
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot get-config Key Value --- ----- Cleanup Dead Servers false Last Contact Threshold 10s Dead Server Last Contact Threshold 24h0m0s Server Stabilization Time 10s Min Quorum 0 Max Trailing Logs 1000 Disable Upgrade Migration false
ここに「停止サーバーの削除判定閾値を1分」「停止サーバーを自動削除」「最小台数を3」「正常状態とみなすまでの時間」をセットします。
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot set-config \ -dead-server-last-contact-threshold=1m \ -cleanup-dead-servers=true \ -min-quorum=3 \ -server-stabilization-time=60 $ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot get-config Key Value --- ----- Cleanup Dead Servers true Last Contact Threshold 10s Dead Server Last Contact Threshold 1m0s Server Stabilization Time 1m0s Min Quorum 3 Max Trailing Logs 1000 Disable Upgrade Migration false
ではまずレプリカを5にセット。
$ kubectl scale -n vault statefulset --replicas 5 my-vault statefulset.apps/my-vault scaled $ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 18h 10.42.5.12 b513c8231aad <none> <none> my-vault-0 1/1 Running 0 28m 10.42.4.25 9952de1bc166 <none> <none> my-vault-1 1/1 Running 0 26m 10.42.3.10 8239e04d52ef <none> <none> my-vault-2 1/1 Running 0 26m 10.42.5.14 b513c8231aad <none> <none> my-vault-3 1/1 Running 0 33s 10.42.1.12 412b213f3d10 <none> <none> my-vault-4 1/1 Running 0 33s 10.42.2.14 4c1f8de42bfe <none> <none> $ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- my-vault-0 my-vault-0.my-vault-internal:8201 leader true my-vault-1 my-vault-1.my-vault-internal:8201 follower true my-vault-2 my-vault-2.my-vault-internal:8201 follower true my-vault-3 my-vault-3.my-vault-internal:8201 follower true my-vault-4 my-vault-4.my-vault-internal:8201 follower true $ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state Healthy: true Failure Tolerance: 2 Leader: my-vault-0 Voters: my-vault-0 my-vault-1 my-vault-2 my-vault-3 my-vault-4
5台構成(耐障害は2台まで)になったので、次は手動のクラスターから除外せずにレプリカを3に設定
$ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 18h 10.42.5.12 b513c8231aad <none> <none> my-vault-0 1/1 Running 0 30m 10.42.4.25 9952de1bc166 <none> <none> my-vault-1 1/1 Running 0 28m 10.42.3.10 8239e04d52ef <none> <none> my-vault-2 1/1 Running 0 28m 10.42.5.14 b513c8231aad <none> <none> my-vault-4 1/1 Terminating 0 3m5s 10.42.2.14 4c1f8de42bfe <none> <none> my-vault-3 1/1 Terminating 0 3m5s 10.42.1.12 412b213f3d10 <none> <none> $ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- my-vault-0 my-vault-0.my-vault-internal:8201 leader true my-vault-1 my-vault-1.my-vault-internal:8201 follower true my-vault-2 my-vault-2.my-vault-internal:8201 follower true my-vault-3 my-vault-3.my-vault-internal:8201 follower true my-vault-4 my-vault-4.my-vault-internal:8201 follower true # 約60秒後 $ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- my-vault-0 my-vault-0.my-vault-internal:8201 leader true my-vault-1 my-vault-1.my-vault-internal:8201 follower true my-vault-2 my-vault-2.my-vault-internal:8201 follower true
自動削除も動作確認できました。
なおこの機能は、最小ノード数が3のようなので、1台に戻すときには使えない模様(3台未満の時点でHAではなくなるから関係ないけどね)
初期構築で自動unseal設定にする
前述の手順は非auto unsealのHashicorp Vaultをauto unsealにする-migrate
を行うものでしたが、初期構築からauto unseal状態でデプロイももちろん可能です。
$ helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml $ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-0 0/1 Running 0 23s 10.42.4.28 9952de1bc166 <none> <none> my-vault-agent-injector-6b66d76649-7vq86 1/1 Running 0 23s 10.42.5.16 b513c8231aad <none> <none> $ kubectl exec -n vault my-vault-0 -- vault status Key Value --- ----- Recovery Seal Type azurekeyvault Initialized false Sealed true Total Recovery Shares 0 :
初期デプロイ時は未初期化のため最初の初期化を行います。
$ kubectl exec -n vault my-vault-0 -- vault operator init Recovery Key 1: ck667PevKK5uImgLyxWIuo5Gjnbtegi+m64koc5jC1GC Recovery Key 2: Vz3NMGw7C2EiTMqcicF0oHbcTZrGXvvYBM3cT7ElITo6 Recovery Key 3: /HhvNK2UuQEAtYBSqbcbavEZ5b4SSB51gJA08jpPk6Vt Recovery Key 4: GQz/MJfpysvc/cokvq1ynMFV65nId6JS+JD84DGnExE8 Recovery Key 5: yDilNc0RQpEa1B+Fyle35fMQvkJ4s1GTmKaAmlBwXRHP Initial Root Token: hvs.a1CBYXlcb0t7nq5eMGZdLxqC Success! Vault is initialized Recovery key initialized with 5 key shares and a key threshold of 3. Please securely distribute the key shares printed above.
すると、この時点でunsealとなります。
$ kubectl get pod -n vault -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-vault-agent-injector-6b66d76649-7vq86 1/1 Running 0 97s 10.42.5.16 b513c8231aad <none> <none> my-vault-0 1/1 Running 0 97s 10.42.4.28 9952de1bc166 <none> <none>
まとめ
以上、Hashicorp VaultとAzure Key Vaultを使った自動unsealや自動joinについて動作確認してみました。
auto unsealはクラウドサービスと連携、クラスターへのauto joinはretry_join
設定、クラスターかrのauto removeはautopilot機能で実現できることを確認しました。
最後に繰り返しになりますが、本文中はunseal keyやroot tokenを全部書き出してますが、これは記事用のためで、実際は機密情報なので厳重に管理してください。
参考
- Auto-unseal using Azure Key Vault | Vault | HashiCorp Developer
- hashicorp-education/learn-vault-autounseal-azure
- Azure Key Vault - Seals - Configuration | Vault | HashiCorp Developer
- Vault HA cluster with integrated storage | Vault | HashiCorp Developer
- operator raft - Command | Vault | HashiCorp Developer
- Vault on Kubernetes deployment guide | Vault | HashiCorp Developer
- Azure Key Vault と連携して HashiCorp Vault を Auto Unseal する | 勇往邁進