Kubernetesで重複定義した環境変数を片方削除するとResource側の環境変数が消える
はじめに
Kubernetesの環境変数周りで予期しない挙動に遭遇して事故ったので挙動・対処法を共有したいと思います。
TL;DR
- Manifest(今回はDeployment)に何らかの環境変数を重複定義(nameが同じものが複数存在)してkubectl applyする
- 重複している環境変数の内、片方を削除してkubectl applyする
- Podから環境変数が消える
- もう一度kubectl applyすれば直る
再現手順
環境構築
恐らくどのKubernetesでも再現するかと思いますが、今回はローカルで簡単に構築できるkindを使います。
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: envvar-cluster
nodes:
- role: control-plane
- role: worker
$ kind create cluster --config cluster.yaml
(略)
Have a nice day!
$
$
$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:56066
CoreDNS is running at https://127.0.0.1:56066/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
$
$
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
envvar-cluster-control-plane Ready control-plane,master 72m v1.23.4 172.19.0.3 <none> Ubuntu 21.10 5.10.76-linuxkit containerd://1.5.10
envvar-cluster-worker Ready <none> 72m v1.23.4 172.19.0.2 <none> Ubuntu 21.10 5.10.76-linuxkit containerd://1.5.10
動作確認
下記なんの変哲もないDeploymentをベースに確認を進めます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
env:
- name: SAMPLE_ENV_VAR1
value: sample1
- name: SAMPLE_ENV_VAR2
value: sample2
Step1: Deploymentを作成
$ kubectl apply -f deployment.yaml
deployment.apps/nginx-deployment created
$
$
$ kubectl get deployment -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 1/1 1 1 16s nginx nginx:latest app=nginx
$
$
$ kubectl get replicasets -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
nginx-deployment-dfc74d86 1 1 1 60s nginx nginx:latest app=nginx,pod-template-hash=dfc74d86
$
$
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-dfc74d86-qmck4 1/1 Running 0 79s 10.244.1.14 envvar-cluster-worker <none> <none>
以下のように環境変数が想定通りセットされています。
$ kubectl get deployments -o json | jq '.items[].spec.template.spec.containers[].env'
[
{
"name": "SAMPLE_ENV_VAR1",
"value": "sample1"
},
{
"name": "SAMPLE_ENV_VAR2",
"value": "sample2"
}
]
$ kubectl get replicasets -o json | jq '.items[].spec.template.spec.containers[].env'
[
{
"name": "SAMPLE_ENV_VAR1",
"value": "sample1"
},
{
"name": "SAMPLE_ENV_VAR2",
"value": "sample2"
}
]
$ kubectl get pods -o json | jq '.items[].spec.containers[].env'
[
{
"name": "SAMPLE_ENV_VAR1",
"value": "sample1"
},
{
"name": "SAMPLE_ENV_VAR2",
"value": "sample2"
}
]
Step2: 環境変数を重複させる
先程のManifestを以下のように編集しapplyします。
env:
- name: SAMPLE_ENV_VAR1
value: sample1
- name: SAMPLE_ENV_VAR2
value: sample2
+ - name: SAMPLE_ENV_VAR2
+ value: sample2
エラーが出るかと思いきや特に問題無く成功します。
diffも出ないのでこの段階で気が付くことは難しいでしょう。
$ kubectl diff -f deployment.yaml
$
$ kubectl apply -f deployment.yaml
deployment.apps/nginx-deployment configured
diffが無いので当然Resourceの環境変数にも変化はありません。
(Deployment, ReplicaSetも同様ですが冗長なのでログは省略)
$ kubectl get pods -o json | jq '.items[].spec.containers[].env'
[
{
"name": "SAMPLE_ENV_VAR1",
"value": "sample1"
},
{
"name": "SAMPLE_ENV_VAR2",
"value": "sample2"
}
]
Step3: 重複を片方削除する
重複している SAMPLE_ENV_VAR2
を削除しようとすると追加時には出なかったdiffが発生します。
env:
- name: SAMPLE_ENV_VAR1
value: sample1
- name: SAMPLE_ENV_VAR2
value: sample2
- - name: SAMPLE_ENV_VAR2
- value: sample2
$ kubectl diff -f deployment.yaml
diff -u -N /var/folders/wm/yhjlm5nd3vg1g_x433204z7c0000gp/T/LIVE-948535760/apps.v1.Deployment.default.nginx-deployment /var/folders/wm/yhjlm5nd3vg1g_x433204z7c0000gp/T/MERGED-3665042967/apps.v1.Deployment.default.nginx-deployment
--- /var/folders/wm/yhjlm5nd3vg1g_x433204z7c0000gp/T/LIVE-948535760/apps.v1.Deployment.default.nginx-deployment 2022-03-16 00:25:43.000000000 +0900
+++ /var/folders/wm/yhjlm5nd3vg1g_x433204z7c0000gp/T/MERGED-3665042967/apps.v1.Deployment.default.nginx-deployment 2022-03-16 00:25:43.000000000 +0900
@@ -6,7 +6,7 @@
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx-deployment","namespace":"default"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"nginx"}},"template":{"metadata":{"labels":{"app":"nginx"}},"spec":{"containers":[{"env":[{"name":"SAMPLE_ENV_VAR1","value":"sample1"},{"name":"SAMPLE_ENV_VAR2","value":"sample2"},{"name":"SAMPLE_ENV_VAR2","value":"sample2"}],"image":"nginx:latest","name":"nginx","ports":[{"containerPort":80}]}]}}}}
creationTimestamp: "2022-03-15T15:18:24Z"
- generation: 2
+ generation: 3
labels:
app: nginx
managedFields:
@@ -46,10 +46,6 @@
.: {}
f:name: {}
f:value: {}
- k:{"name":"SAMPLE_ENV_VAR2"}:
- .: {}
- f:name: {}
- f:value: {}
f:image: {}
f:imagePullPolicy: {}
f:name: {}
@@ -130,8 +126,6 @@
- env:
- name: SAMPLE_ENV_VAR1
value: sample1
- - name: SAMPLE_ENV_VAR2
- value: sample2
image: nginx:latest
imagePullPolicy: Always
name: nginx
「多分動くと思うからリリースしようぜ」とばかりにapplyすると……?
$ kubectl apply -f deployment.yaml
deployment.apps/nginx-deployment configured
diffの通り無慈悲にSAMPLE_ENV_VAR2が消滅してしまいます。
ちなみに value
の値は既存と重複させてもさせなくても同様の挙動になります。
$ kubectl get pods -o json | jq '.items[].spec.containers[].env'
[
{
"name": "SAMPLE_ENV_VAR1",
"value": "sample1"
}
]
Extra: 新規作成時に重複がある場合の挙動
このように重複させた値で新規作成すると警告は出ますがそのままapplyされ、特段エラーが出ることもなく正常にPodが立ち上がります。
env:
- name: SAMPLE_ENV_VAR1
value: sample1
- name: SAMPLE_ENV_VAR2
value: sample2
- name: SAMPLE_ENV_VAR2
value: sample2-duplicated
$ kubectl apply -f deployment.yaml
Warning: spec.template.spec.containers[0].env[2].name: duplicate name "SAMPLE_ENV_VAR2"
deployment.apps/nginx-deployment created
$
$
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-66759f486b-4qrrr 1/1 Running 0 42s
そしてPodの設定としてはconfiguredで重複させた時と違って同じ値が2つ存在しますが、実際にPod内部から見える値はどうもManifestで後ろに書いた値となるようです。
$ kubectl get pods -o json | jq '.items[].spec.containers[].env'
[
{
"name": "SAMPLE_ENV_VAR1",
"value": "sample1"
},
{
"name": "SAMPLE_ENV_VAR2",
"value": "sample2"
},
{
"name": "SAMPLE_ENV_VAR2",
"value": "sample2-duplicated"
}
]
$
$
$ kubectl exec -it nginx-deployment-66759f486b-4qrrr -- env | grep SAMPLE
SAMPLE_ENV_VAR2=sample2-duplicated
SAMPLE_ENV_VAR1=sample1
Manifestで SAMPLE_ENV_VAR2
の順序を入れ替えると以下のようになります。
$ kubectl exec -it nginx-deployment-74f47d648d-nb87h -- env | grep SAMPLE
SAMPLE_ENV_VAR1=sample1
SAMPLE_ENV_VAR2=sample2
肝心の重複箇所を片方削除した場合の挙動ですが、やはり先程と同様に当該環境変数が消えてしまいます。
$ kubectl exec -it nginx-deployment-55569b7c8b-vdcvc -- env | grep SAMPLE
SAMPLE_ENV_VAR1=sample1
対処法
こういう事態に陥らないために (予防)
- 何らかのLinterを導入する
- 但し既存では存在しないかも。軽くググッて出てきたkube-linter, kubevalでは警告が出なかった
- CI/CDフローのどこかでdiffをチェックする
- 環境変数の消失は予防できるが重複の発生は予防できない
- そもそも人力なのであまりアテにできない
- リリースに要する時間が伸びる
- ConfigMapやkustomize等を活用してなるべく直接編集する環境変数を減らす
うっかりapplyして環境変数を消してしまった場合 (火消し)
もう一度同じManifestでapplyすれば現状との差分が埋まるため意図した状態に戻ります。
慌てず騒がずkubectl applyを実行するなりCI/CDフローをもう一度回すなりして再デプロイしましょう。
重複定義を発見した場合 (地雷処理)
前述した通り迂闊に削除すると環境変数が足りないPodがデプロイされ事故が発生するので何らかの回避策が必要です。
パッと思いつく方法としてはDeploymentを別名で作成しServiceのLabelSelectorを一時的に切り替えてトラフィックを逃してやりその間に修復することでしょうか。
(今回は検証していませんがkubectl editでどうにかできるかもしれません)
1. 重複発見
2. 退避用Deployment作成
3. トラフィック切り替え
$ kubectl diff -f service.yaml
(略)
@@ -47,7 +47,7 @@
protocol: TCP
targetPort: 80
selector:
- app: nginx
+ app: nginx-temp
sessionAffinity: None
type: ClusterIP
status:
4. 修復
4. トラフィック切り戻し
$ kubectl diff -f service.yaml
(略)
@@ -47,7 +47,7 @@
protocol: TCP
targetPort: 80
selector:
+ app: nginx
- app: nginx-temp
sessionAffinity: None
type: ClusterIP
status:
5. 退避用Deployment削除
考察
-
env:
はKeyValueではなく配列なのでデータ構造的に重複定義できてしまうのは仕方ない.items[].spec.template.spec.containers[].env[]
- Kubernetes側でバリデーションし重複を禁止することは可能に思える
- なぜやっていないか(やらないことによるメリット)は思いつかない
- IssueやSlackも漁ってみたが類似の議論は見当たらない?
まとめ
- 迂闊に環境変数重複の解消を試みると事故るので気を付けよう
- もし重複してしまっても(valueにミスが無ければ)それだけでは悪影響無いはずなので慎重に修復しよう
Discussion