EKS Upgrade to 1.24をした話

# はじめに
今年の8~9月に約2ヶ月間をかけて、サービス影響なく、EKS1.23から1.24にUpgradeしました。
1.23は既に2023/10/11でEOLを過ぎているため、需要は少ないかもしれませんが、自分にとっては初めてのEKS Upgrade作業で、苦労した案件だったので、記事にしました。

# Upgrade作業
## 前提
- Upgrade手法はIn-place
- Upgrade対象はマルチクラスター・マルチテナントの構成。
	- といっても、クラスター数は2つのみで、将来的には1つにする予定。
- ZOZOでは開発環境・事前環境・本番環境の3つの環境が存在する。開発環境と事前環境でUpgrade作業と検証をし、問題なければ本番環境を実施する流れ。
- AWSリソースは基本的にCFnで定義しています。
- 1.24以降はコンテナランタイムとしてDockershimをサポートしなくなる。
	- https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/dockershim-deprecation.html

## 全体
以下のStepで進めました。
Step1, 2, 3, 10, 11はcontainerd移行のための特別Stepです。それ以外のStepは、基本的にいずれのバージョンのUpgradeでも実施しているものです。

1. Podのdocker.sockマウントの廃止 ★containerd特別対応
2. containerd対応のノードグループ（1.23）を作成 ★containerd特別対応
3. containerd対応のノードグループ（1.23）へPodを全て移動 ★containerd特別対応
4. 非推奨apiVersionの確認と対応
5. 各種Custom ControllerのUpgrade
6. Control PlaneのUpgrade
7. EKSアドオンのUpgrade
8. minReplicasの確認
9. 1.23のDockershim対応のノードグループを1.24にUpgrade
10. 1.23のcontainerd対応のノードグループから1.24のノードグループへPodを移動 ★containerd特別対応
11. containerdの起動テンプレートとノードグループの削除 ★containerd特別対応

以降、各Stepの詳細を説明します。

## Step1. Podのdocker.sockマウントの廃止
[DDS](https://github.com/aws-containers/kubectl-detector-for-docker-socket)を利用して、既存リソースでdocker.sockマウントが存在するかのチェックをします。もし存在する場合は、該当部分をcontainerdマウントするように修正します。

![_EKS_Upgarade1_24の事前対応__Dockerソケットのマウントを削除する_by_gold-kou_·_Pull_Request__12849_·_st-tech_zozo-platform-infra_🔊.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221948/93f00cff-1577-d6f0-cd87-394f63f2fbad.png)

## Step2. containerd対応のノードグループ（1.23）を作成
EKS1.23で、起動テンプレートを用いてcontainerd対応のノードグループを作成します。

UserDataに `/etc/eks/bootstrap.sh my-cluster --container-runtime containerd` を持つ起動テンプレートを用意します。この起動テンプレートでは、ImageIdの値が変動しうるので、SSMのParameters Storeから取得するようにします。NodeGroup側ではその起動テンプレートを指定します。

```yaml
NodeImageIdSSMParam:
    Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"
    Default: /aws/service/eks/optimized-ami/1.23/amazon-linux-2/recommended/image_id
    Description: AWS Systems Manager Parameter Store parameter of the AMI ID for the worker node instances. Change this value to match the version of Kubernetes you are using.

...

LaunchTemplateContainerd:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: launch-template-containerd
      LaunchTemplateData:
        ImageId: !Ref NodeImageIdSSMParam
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            /etc/eks/bootstrap.sh ${GlobalEnvironment}-${GlobalPrefix} --container-runtime containerd
            
...

NodeGroupSystemM5Large01Containerd:
    Type: AWS::EKS::Nodegroup
    Properties:
      ...
      NodegroupName: "nodegroup-m5_xlarge01-containerd"
      LaunchTemplate:
        Id: !Ref LaunchTemplateContainerd
      ...
...
```

ref. https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/eks-optimized-ami.html

## Step3. containerd対応のノードグループ（1.23）へPodを全て移動 
dockershim対応のノードグループ上で起動しているPodをStep2で作成したノードグループへ移動させます。

一度に全てのPodを移動してしまうと、予期せぬ不具合が発生したり、何か問題が起きた際の影響範囲が大きいと考え、いくつかの単位で適当に分割しました。また、各SREチームが担当するマイクロサービスに関しては、各SREチームに移動と動作確認を依頼しました。したがって、自分は自SREチームの担当マイクロサービスと共通的に使用されているエコシステムのPod移動を担当しました。

移動方法は、nodeAffinityでcontainerd対応のノードグループを指定してapplyします。

![api-gateway_Move_to_containerd_nodegroup_1__by_gold-kou_·_Pull_Request__13984_·_st-tech_zozo-platform-infra.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221948/39f61f14-f905-48aa-b6a9-32c020580814.png)

全チームの作業完了後、移動漏れがないかを一つ一つのノードで手作業で確認しました。割とぽろぽろ移動漏れがあったので、漏れを発見しては担当チームへ連絡しました。自分の担当範囲内でも漏れがありました。。。

ただし、例えばDaemonsetのPodは残り続けてしまうため、そのようなPodは無視しました。

## Step4. 非推奨apiVersionの確認と対応
[pluto](https://github.com/FairwindsOps/pluto)を利用して、Upgrade後のバージョンで削除されたり非推奨になったAPIがあるかをチェックします。なお、ZOZOではこの作業はスクリプト化されています。

今回は存在しなかったため、対応しませんでした。

## Step5. 各種Custom ControllerのUpgrade
各種Custom Controllerを互換性のある中で最新にUpgradeします。例えば、cluster-autoscalerやfalcoです。
多くのCustom Controllerを使用しているため、残念ながら全てのCustome ControllerをUpgradeできているわけではないです。互換性が担保されていないものやセキュリティ関連のものを優先的にUpgradeしています。

## Step6. Control PlaneのUpgrade
Control PlaneをUpgradeします。
一時的にControl PlaneとData Planeのバージョンが一致しなくなりますが、動作上は問題ありません。ただし、1.23から1.24のUpgradeに関しては、「Control Plane(1.24) + Data Plane(containerd未対応の1.23)」の組み合わせになると、正常に動作しない可能性があります。今回はStep3を実施しているため、問題ありません。

## Step7. EKSアドオンのUpgrade
対象のEKSクラスターでは以下のオドオンを導入しています。全て、互換性のある中で最新にUpgradeします。

- coredns
- kube-proxy
- amazon-k8s-cni
- ebs-csi-driver

## Step8. minReplicasの確認
全てのHPAのminReplicaが2以上であることを確認します。
minReplicasが1の場合、PDBとConflictしてNodeがdrainできずにUpgradeが失敗した過去事例があり、Data PlaneのUpgrade前に必ず確認するようにしています。

## Step9. 1.23のDockershim対応のノードグループを1.24にUpgrade
「1.23のDockershim対応のノードグループ」とは、Upgrade作業前にPodが起動していたノードグループであり、Step3の作業により、Daemonset以外のPodが起動していません。つまり、実質的に空のノードです。これを1.24にUpgradeします。
1.24のノードはコンテナランタイムがcontainerdになっているため、Step2のような起動テンプレートなどの準備は不要です。

Upgrade後に、DockershimのNodeがいないことを以下で確認します。

```
$ k get node -o wide | grep docker
```

## Step10. 1.23のcontainerd対応のノードグループから1.24のノードグループへPodを移動
Step3で移動しておいたPodを、Step9でUpgradeしたノードへ移動させます。
方法は、nodeAffinityの設定を元に戻してapplyするだけです。

![_EKS_Upgrade_1_24__Step7_Fix_node_affinity_of_api-gateway_to_1_24_node_at_dev_dev-onprem_stg_by_gold-kou_·_Pull_Request__14307_·_st-tech_zozo-platform-infra.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221948/192ccaa1-c9dd-0489-d5bb-875c20fb3298.png)

こちらも、各SREチームが担当するマイクロサービスに関しては、各SREチームに移動と動作確認を依頼しました。移動漏れの確認も同じく実施しました。

## Step11. containerd対応のノードグループ（1.23）を削除
Step2で作成したリソースを削除します。

これにて、無事Upgrade作業は完了です！

# 発生した問題
続いて、Upgrade作業により発生してしまった問題を取り上げます。

## 問題1. ログの欠損
containerd対応するにあたって必要なfluentd-firehoseの設定変更が漏れており、一部システムのログが欠損してしまいました。
この問題は、parseのtypeをjsonからcriに変更したところ、改善されました。

> containerd/cri-o use different log format. To parse such logs, you need to use cri parser instead.

https://github.com/fluent/fluentd-kubernetes-daemonset#use-cri-parser-for-containerdcri-o-logs

この問題は、1つ目のクラスター対応完了時点で発覚したため、2つ目のクラスターへの影響はありませんでした。

## 問題2. 問題1の対応によるログフォーマット変更
parseのtypeをcriにすることでログを送信できるようにはなりましたが、フォーマットが変更されてしまいました。フォーマットが変わってしまうと、Athenaでログ検索する際に、従来のクエリが使用できません。そこで、従来フォーマットと新フォーマットの両方を送信するようにしました。

ただし、2つ目のクラスターの対応をするにあたって、dockershim対応のノードとcontainerd対応のノードが混在する状況で、ログの欠損なしでcontainerd移行を完了するため、 [あそびゅー社のワークアラウンド](https://tech.asoview.co.jp/entry/2023/02/10/134922#fluentd%E3%81%A7%E3%83%AD%E3%82%B0%E3%81%8C%E9%80%81%E4%BF%A1%E3%81%95%E3%82%8C%E3%81%AA%E3%81%8F%E3%81%AA%E3%82%8B%E5%95%8F%E9%A1%8C%E3%81%AE%E7%99%BA%E7%94%9F) を参考に、dockershim対応のconfとcontainerd対応のconfを2つconfigmapに用意し、それぞれのコンテナランタイムごとに使用するconfを使い分けるようにしました。使い分けの基準は `/.dockerenv` がfluentdのコンテナ上に存在するかどうかです。

- dockershim対応のノードグループ
  - /.dockerenvが存在する
- containerd対応のノードグループ
  - /.dockerenvが存在しない

しかしながら、この対応は暫定とし、ゆくゆくは新フォーマットのみの送信にしたいと考えています。

# 苦労したこと
- 影響範囲が大きい点です。もし作業に問題があった場合、クラスター全体に影響がでます。つまり、ZOZOTOWNのサービスが停止することになりますので、慎重に検証と作業を進める必要がありました。
- EOLが迫っていた点です。上述のとおり慎重に進める必要がありますが、スピーディに進める必要もあり、かなり大変でした。
- 通常のEKS Upgrade作業に加えて、containerd対応も必要だった点です。通常のEKS Upgradeよりも作業が多く、環境も複数あるため、作業時間が多くかかってしまいました。
- containerd対応の進め方です。最初は全てのPodの移動を自分が担当する方針としてアナウンスしたのですが、いざやってみると対象のPodの数が多すぎるのと、動作確認の担保が難しいため、途中から方針を切り替えて、複数のSREチームに依頼するようにしました。進め方や調整方法をもっとサブ担当者や上司と話し合ってから進めればよかったと反省しています。しかしながら、最終的には複数のチームを巻き込んで、無事達成できてよかったです。
- 自身のKubernetesの知見が乏しかったので、キャッチアップを多くする必要があった点です。特に、falcoやcluster-autoscalerなどのエコシステム周りの知識がありませんでした。

# 最後に
なんとかEOLまでに無事にUpgradeできてよかったです。
この案件で多くのことを学べました。KubernetesやFlux周りの知見向上はもちろんcontainerd移行で他のSREチームのメンバとも頻繁にコミュニケーションをとり、プロジェクトをリードする中でソフトスキルも向上した気がします。
そして、何よりサブ担当者のすだけんには非常に感謝しています。強力なサポートでした。