LIVESENSE ENGINEER BLOG

リブセンスエンジニアの活動や注目していることを発信しています

Managed Node GroupでEKSの更新作業を楽にした話

こんにちは、かたいなかです。 最近、転職会議のEKSクラスタのワーカーノードをManaged Node Groupに置き換えました。

この記事ではManaged Node Group導入に際してどのような考慮が必要だったかを紹介します。

Cordon、Draining、Pod Disruption Budgetのおさらい

Kubernetesではノードのメンテナンスや入れ替えのために、CordonとDrainingという処理や、Drainingを安全に行うためのPod Disruption Budgetというリソースが提供されています。

Podが配置されないようにするCordon

Cordonすると、ノードが SchedulingDisabled とマークされ、Podがそのノードに新しく配置されなくなります。後述のDrainingとは違い、すでに当該ノード上で動いているPodが終了されることはありません。

$ kubectl cordon ip-XXX-XXX-XXX-XXX.ap-northeast-1.compute.internal
node/ip-XXX-XXX-XXX-XXX.ap-northeast-1.compute.internal cordoned

Podを他のノードに退避するDraining

Drainingは対象のノード上のPodを退避します。対象のノードをCordonした後で、現在動いているPodが終了されます。その後、Deployment(ReplicaSet)などの働きにより他のノードでPodが再度起動されます。

$ kubectl drain ip-XXX-XXX-XXX-XXX ap-northeast-1.compute.internal --ignore-daemonsets --delete-emptydir-data
node/ip-XXX-XXX-XXX-XXX.ap-northeast-1.compute.internal cordoned
evicting pod default/xxxxxxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxxx
evicting pod default/yyyyyyyyyyyyy-yyyyyyyyyy-yyyyyyyyyyyy
evicting pod default/zzzzzzzzzzzzz-zzzzzzzzzz-zzzzzzzzzzzz
...
node/ip-XXX-XXX-XXX-XXX.ap-northeast-1.compute.internal evicted

Kubernetesクラスタのバージョンアップなどに伴ってノードを入れ替える際は、Drainingを古いノードに対して順次実行していくことになります。

Drainingを安全に行うためのPod Disruption Budget

KubernetesではPod Disrutption Budget(以下PDB)を設定することで、Drainingを安全に行うことができます。

kubernetes.io

例えば、以下のような設定だと、Draining時に matchLabels で指定したラベルを持つPodが最低2台はAvailableである状態を保ちます。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: hoge
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: hoge

煩雑だったノードの入れ替え作業

転職会議では、トラフィックを受け付けているPodに関しては、PDBを設定していました。これにより、Draining時に障害が起きることを防いでいます。

ところが、バッチ処理ではPDBが設定されていませんでした。そのため、雑にDrainingを実行してしまうと実行途中のバッチ処理が途中で終了されてしまう可能性がありました。転職会議では多くのバッチ処理は実装が冪等ではあるものの、処理の途中で強制終了する可能性がある手順をEKSクラスタの更新作業等のたびに実施するのは怖いです。

そのため、ノードの入れ替えが必要な際には、既存のノードをCordonして新しくPodが配置されないようにし、Cordonされたノード上で実行されていたバッチ処理が終了したことを確認するという運用によって、バッチ処理が強制終了されることを防いでいました。

ノードの入れ替えの手順を列挙すると以下のようになります。

  1. 新しいバージョンのノードのAutoScaling GroupをTerraformで作成
  2. 既存のノードをCordonする
  3. Cordonしたノード上のバッチ処理が終了したのを確認
  4. 既存のノードをDraining
  5. 既存のノードのAutoScaling GroupをTerraformで削除

この手順は手数も多く、CordonやDrainingで使うコマンドも対象を絞り込む必要がある関係で比較的複雑なため、学習コストや心理的ハードルも高くなっていました。定期的に実施する必要がある更新作業のため、なるべく楽に行えるようにしたいという課題感がありました。

# コマンド例
# Cordon
$ kubectl --context eks/jobtalk get nodes -o wide | grep 'v1.XX.X' | awk '{print $1}' | xargs kubectl --context=eks/jobtalk cordon

# Draining
$ kubectl --context eks/jobtalk get nodes -o wide | grep 'v1.XX.X' | awk '{print $1}' | xargs -I % bash -c 'kubectl --context eks/jobtalk drain --delete-emptydir-data --ignore-daemonsets --force %; sleep 30'

Managed Node Group導入

ここまで紹介したような煩雑な更新処理を簡素化するため、Managed Node Groupを導入することにしました。

docs.aws.amazon.com

Managed Node Groupの更新処理

Managed Node Groupの更新処理時の動作は以下のドキュメントで説明されています。

docs.aws.amazon.com

今までの手順で面倒だったCordonやDrainingを任せることができます。一方で、Cordon後にバッチ処理が終了するまで待ってからDrainingを実行するといったことはできないため、バッチ処理の終了まで何らかの方法で待機させないといけません。

Workflowの定義でPDBを指定

幸いなことに、転職会議のバッチ処理を動かすために使用しているArgo WorkflowではPDBをサポートしています。これを利用することにより、Argo Workflowで作られたPodが終了するまでDrainingを待機させることができるようになります。

apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
  name: hoge-batch
spec:workflowSpec:
    podDisruptionBudget:
      # 複数Podが同時に立ち上がるような処理に適用されるケースを考慮して大きい数字を指定
      minAvailable: 65535

WorkflowのRetryPolicyをAlwaysに

PDBを設定していたWorkflowが動いている状態でDrainingを実行してみると、実際にWorkflowから作成されたPodの終了までDraining処理が待機してくれることが確認できました。しかし、Workflowの状態が Pod Deleted になり、処理が失敗扱いになってしまうことがありました。

この場合もPodでの処理自体は正しく最後まで完了しており、Workflowの状態が失敗になっているだけです。しかし、処理自体に問題がないのにWorkflowの失敗時のアラートが鳴ってしまうのも煩わしいです。

そのため、Workflowの retryPolicy でデフォルトのOnFailed ではなく Always を指定し、DrainingによってWorkflowが失敗扱いになったケースでも再実行させるようにしました。

apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
  name: hoge-batch
spec:
  workflowSpec:
    entrypoint: app
    templates:
      - name: app
        retryStrategy:
          retryPolicy: Always
          limit: 2

多くのバッチ処理はが冪等な転職会議では、ほとんどのケースでこれで十分な対応になります。

Managed Node Group導入後の更新処理

ここまでの対応を行った上でManaged Node Groupに切り替えを行いました。

これにより、処理の完了まで長い時間がかかるバッチ処理が実行されている時間帯さえ避ければ、TerraformでManaged Node Groupに指定しているAMIのRelease Versionを変更するだけでワーカーノードの更新が行えるようになりました。

ノードの更新を行う心理的ハードルを下げることができたのではないかと思います。

まとめ

手を付けようと思いながら後回しになってしまっていたManaged Node Groupの導入をやっと行うことができてホッとしています。

また、バッチ処理を冪等にするなど細かいの工夫がされていることが、新しい技術の導入局面でもプラスに働くことに気づくことができました。

今後も丁寧に足元を整えていくことで、大胆に新しい技術に挑戦できる状態を維持していけたらと思っています。

参考