Kubernetesで起動に失敗したPodが大量に発生した話

サムネイル

はじめに

この記事は、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からリポジトリを追加します。

※ 今回はデプロイ キーを使用しました。

参考: https://docs.github.com/ja/enterprise-cloud@latest/authentication/connecting-to-github-with-ssh/managing-deploy-keys#deploy-keys

5. ArgoCDにアプリケーションを追加する

先程設定したリポジトリを指定します。

しばらくするとSyncedになります。

2つのNodeにPodが配置されていることが分かります。

6. Pod数を1に変更する

kubectl edit を実行しyamlファイルを編集します。

kubectl edit deployment/nginx-deployment

replicas: 12replicas: 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-m02spec: の下に追加してください。

参考: 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 editnodeName を削除することで対処できましたが、検証環境で助かったと肝を冷やしました。

基本的には、ArgoCD経由で変更を適用するのが、安全&確実で、kubectl edit は安易に行ってはならない、と学びになりました。

プラットフォーム開発本部では一緒に働く仲間を募集しています! speakerdeck.com