はじめに
この記事は、DMMグループ Advent Calendar 2024の19日目の記事です。
こんにちは、プラットフォーム開発本部 第三開発部 基盤開発グループの山口です。私が所属する基盤グループでは、API GatewayをKubernetes上で運用しています。
この記事では、とある障害の原因調査のため、Kubernetesのdeploymentの設定を変更した結果、ArgoCDの動作を考慮しておらず、out of memoryのPodが大量に発生してしまったトラブルについて、紹介したいと思います。
要約
要約すると、以下のような問題が起きました。(一部推測を含みます)
API Gatewayが動作する検証環境のKubernetesクラスタにはNodeが複数存在し、そのうちの1つのNodeにPodが乗ったときのみ、外部への接続が失敗するという問題が起きていました。
検証のため、対象のNodeに1Podだけ配置されるよう、Google Cloudのコンソール上からPod数を1に減らし、nodeName を指定しました。
その直後に、他の開発者がGitにコミットを行い、ArgoCDは差分を解消するよう動作しPod数が元に戻りました。
しかし nodeName
は元に戻りませんでした。
Nodeに乗り切らないPodが out of memory
となり、Podの起動に無限に失敗し続けることになりました。
再現させてみる
ArgoCDが動作すると、Pod数は元に戻るが nodeName
は元に戻らない事象を再現させます。
前提
- Docker Desktopがインストールされていること (hyperkitなどでも可)
- minikubeがインストールされていること
1. minikubeで複数Node環境を構築する
複数Node環境をローカルで構築します。
$ minikube start -p minikube-dev1 --cpus 2 --memory 2G --nodes 2 $ minikube addons -p minikube-dev1 enable metrics-server
作成されたNodeを確認します。
$ kubectl get nodes NAME STATUS ROLES AGE VERSION minikube-dev1 Ready control-plane 4m6s v1.31.0 minikube-dev1-m02 Ready <none> 3m55s v1.31.0
対象ノードのメモリーの上限を調べます。
$ kubectl describe node minikube-dev1-m02 Name: minikube-dev1-m02 ~~~ Capacity: cpu: 8 ephemeral-storage: 61202244Ki hugepages-1Gi: 0 hugepages-2Mi: 0 hugepages-32Mi: 0 hugepages-64Ki: 0 memory: 8027168Ki pods: 110 ~~~
約8GiBが上限と分かりました。
※ minikube startのコマンドと、kubernetesのNodeのCapacityは異なるので要確認。
2. ArgoCDのインストール
公式ドキュメントの通りに 1. Install Argo CD
から 4. Login Using The CLI
までを実行してください。
https://argo-cd.readthedocs.io/en/stable/getting_started/
3. プライベートリポジトリにmanifestを配置
manifestファイルをプライベートリポジトリに配置してください。
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx spec: selector: matchLabels: app: nginx replicas: 12 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.27.2 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 800Mi limits: cpu: 100m memory: 800Mi
service.yaml
apiVersion: v1 kind: Service metadata: name: nginx-service namespace: default spec: ports: - port: 80 targetPort: 80 protocol: TCP type: NodePort selector: app: nginx
4. ArgoCDにリポジトリを追加する
ArgoCDのSettings > Repositoriesからリポジトリを追加します。
※ 今回はデプロイ キーを使用しました。
5. ArgoCDにアプリケーションを追加する
先程設定したリポジトリを指定します。
しばらくするとSyncedになります。
2つのNodeにPodが配置されていることが分かります。
6. Pod数を1に変更する
kubectl edit
を実行しyamlファイルを編集します。
kubectl edit deployment/nginx-deployment
replicas: 12
を replicas: 1
に変更してください。
しばらくすると OutOfSync
になっていることが確認できます。
7. 空コミットをPushする
git commit --allow-empty -m "empty commit"
を実行し、空コミットを作成し git push
してください。
しばらくすると Synced
になっていることが確認できます。
8. nodeName
の設定を追加する
kubectl edit
を実行しyamlファイルを編集します。
kubectl edit deployment/nginx-deployment
nodeName: minikube-dev1-m02
を spec:
の下に追加してください。
参考: https://kubernetes.io/ja/docs/concepts/scheduling-eviction/assign-pod-node/
※ ArgoCDのWebUIが重くなるので注意してください。
起きること
ステータスが Synced
のまま OutOfSync
になりません。 空コミットをPushしても変化はありません。
特定のNode上でのみ、起動に失敗したPodが増え続けていることも確認できます。
$ kubectl get pods NAME READY STATUS RESTARTS AGE nginx-deployment-b69ff584f-22g5k 0/1 OutOfmemory 0 9s nginx-deployment-b69ff584f-22tnt 0/1 OutOfmemory 0 32s nginx-deployment-b69ff584f-247rs 0/1 OutOfmemory 0 24s nginx-deployment-b69ff584f-25mhq 0/1 OutOfmemory 0 34s nginx-deployment-b69ff584f-264lh 0/1 OutOfmemory 0 74s nginx-deployment-b69ff584f-272x8 0/1 OutOfmemory 0 74s nginx-deployment-b69ff584f-2bm9j 0/1 OutOfmemory 0 32s nginx-deployment-b69ff584f-2bssw 0/1 OutOfmemory 0 78s nginx-deployment-b69ff584f-2fprw 0/1 OutOfmemory 0 33s nginx-deployment-b69ff584f-2fw6c 0/1 OutOfmemory 0 38s nginx-deployment-b69ff584f-2gpn4 0/1 OutOfmemory 0 81s nginx-deployment-b69ff584f-2j4pf 0/1 OutOfmemory 0 67s nginx-deployment-b69ff584f-2jpr7 0/1 OutOfmemory 0 27s nginx-deployment-b69ff584f-2kcfb 1/1 Running 0 88s nginx-deployment-b69ff584f-2khst 0/1 OutOfmemory 0 65s nginx-deployment-b69ff584f-2kmj7 0/1 OutOfmemory 0 58s nginx-deployment-b69ff584f-2kz6r 0/1 OutOfmemory 0 68s ~~~
コマンドでも大量の OutOfmemory
が確認できます。
なぜ nodeName
だけ元に戻らなかったのか
ArgoCDは kubectl diff
と同じく kubectl edit
などで"追加"された項目は差分として検知しないためです。
参考: https://github.com/argoproj/argo-cd/issues/1613#issuecomment-492416829
※ replicas
は最初から存在したフィールドのために、値の差分は検出されたようです。
教訓
NodeにPodが乗り切らない可能性を考慮して、Pod数を1に減らした後に nodeName
を設定したのですが、kubectl edit
で追加した項目はArgoCDの管理外となることは想定外でした。
また、起動に失敗したところで問題は無いだろうと思っていたのですが、無限にDegradedのPodが増え続けることも想定していませんでした。
結果、kubectl edit
で nodeName
を削除することで対処できましたが、検証環境で助かったと肝を冷やしました。
基本的には、ArgoCD経由で変更を適用するのが、安全&確実で、kubectl edit
は安易に行ってはならない、と学びになりました。
プラットフォーム開発本部では一緒に働く仲間を募集しています! speakerdeck.com