EKSクラスタのバージョンアップデートを自動化する

この記事は[ZOZO Advent Calendar 2023](https://qiita.com/advent-calendar/2023/zozo) シリーズ9の12日目の記事です。

今年弊チームで運用しているEKSクラスタのバージョンアップデートを自動化する取り組みを行っていました。まだ運用に乗り切っていない部分はありますが、その取り組み内容と使用したツールやコマンドなどを紹介します。

K8sクラスタと一口に言ってもチームによって運用しているクラスタの規模、Fargateか否か、テナント数などなどによって、アップデートの大変さは異なると思うので、以下で補足します。

# 弊チームのK8sクラスタについて補足

#### 特徴

- EKS Fargateを使っている
- マルチテナント
- 日本とUSで2つクラスタを運用している
- Cloudformationで管理
- マニフェスト管理はArgoCD

#### バージョンアップデートの手順

弊チームでのクラスタバージョンアップデート手順は基本的に以下のようになります。Fargateを使っているおかげでかなり楽に済んでいると思われます。

- アップデートを検知する
- リリースノートを読んで、影響のある破壊的な変更があるか確認して対応する
- deprecatedになるAPIがあればアップデートする
- 開発環境で以下手順を実行する
    - クラスタのバージョンアップデートを実行
    - ワーカーノードの更新
    - それぞれのPodステータスに問題がなければOK
- ステージング、本番環境の更新に移る（手順は開発環境同様）

# 自動化をやっていく

GitHub Actionsを使用して上の一連の作業を実行していきます。

## アップデートの検知

EKSで新しいK8sバージョンがサポートされた際、基本的には公式のアナウンスによって知ることができます。

例えば、1.28であれば、この記事など。https://aws.amazon.com/jp/blogs/news/amazon-eks-now-supports-kubernetes-version-1-28/

ただ、自動化する際にはこれをコマンドで知りたいですよね。そこで、EKSのAddOnでサポートされているクラスタのバージョンが使えます。`aws eks describe-addon-versions`で取得できますが、これを成形することでサポートされている最新のクラスタバージョンを取得することができたりします。

```shell
aws eks describe-addon-versions | jq -r ".addons[] | .addonVersions[] | .compatibilities[] | .clusterVersion" | sort -r | uniq | head -n 1
```
（もうちょっと綺麗なコマンドはあるとは思います。筆者はシェルが少々苦手です。）

また、現在使用してるクラスタのバージョンに関しては、以下のコマンドで取得できます。

```shell
aws eks describe-cluster --name <your cluster name> --query "cluster.version"
```

これを比較して、もし異なっていればクラスタのバージョンをアップデートするPRをGitHub Actionsから作成します。チームメンバーへのアサインまでするとなお良しです。

チームメンバーの取得はGitHub Teamsを使うと良いです。`gh api`でGitHub Teamのメンバーリストが取得できるので、こんな感じでランダムなチームメンバーを取得できます。
```shell
organization=<your organization>
team=<your GitHub Team>
output=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /orgs/$organization/teams/$team/members)
member_list_length=$(echo $output | jq 'length')
random_member_index=$((RANDOM % member_list_length))
jq_id_op=".[$random_member_index].login"
echo "ASSIGNEE=$(echo $output | jq $jq_id_op)" >> $GITHUB_ENV
```

## リリースノートの確認

PRをアサインされたメンバーが確認します。

ここはさすがに自動化できていないですね...

## deprecated APIのチェック

アップデートする際、APIのバージョンが非推奨になったり使えなくなったりすることがあります。そのため、アップデート前にリリースノートを見て使用しているAPIがその対象になっているか確認する必要がありますが、ここは機械に任せることができます。

確認方法は、カスタムリソースかそうでないかで異なります。

### K8s API

plutoコマンド (https://github.com/FairwindsOps/pluto) が便利です。

```shell
> pluto detect-files -d <your k8s directory> --target-versions k8s=v1.28

There were no resources found with known deprecated apiVersions.
```

こんな感じで簡単にチェックしてくれます。

### カスタムリソースのAPI

こちらは若干厄介ではありますが、ArgoCDを使用している場合Application Controllerのログを確認することで、deprecatedになっているAPIを拾うことができます。

```shell
kubectl logs argocd-application-controller-0 -n argocd | grep -q 'deprecated'
```

これらをGitHub Actionsに載せて、先ほどのPR作成時に走らせておくとリリースノート確認の負荷がだいぶ下がります。

## クラスタのバージョンアップデートを実行

ここでは、[アップデートの検知](#アップデートの検知) で作成されたPRをマージして、それをトリガーにGitHub ActionsからCloudformationのスタック更新を行います。

弊チームでは、Cloudformationのスタックをsceptre (https://github.com/Sceptre/sceptre) を使って制御しています。

## ワーカーノードの更新

クラスタのバージョンが更新されたら、次はPodが割り当てられているノードを更新する必要があります。弊チームの場合だとFargateを使用しているため、それぞれのPodを再起動することで新しいバージョンのノードに再配置することができます。

ここはK8sコマンドを実行するスクリプトを組んでいて、GitHub Actionsからスクリプトの実行を行っています。

スクリプトの内容としては、一つのnamespaceの`Deployment`や`Statefulset`を全て取得して、`kubectl rollout restart`をかけます。Argo Rolloutsを使用している場合、`Rollout`をここに含める必要があります。その後、それぞれのPodのステータスを確認して、正常に動作していることを確認します。

例えば `Deployment` の場合、以下のようになります。

#### Deploymentのリソース名の取得
```shell
deployments=`kubectl get deployment -n $ns -o=jsonpath="{range .items[*]}{.metadata.name}{'\n'}{end}"`
```

#### Podの再起動
```shell
parallel --no-run-if-empty "kubectl rollout restart deployment {} -n $ns" ::: $deployments
```
（並列実行すると楽）

#### Podのステータスチェック
```shell
for deployment in $deployments; do
  kubectl rollout status deployments/$deployment -n $ns
done
```

以上をスクリプトに入れて、並列でnamespaceごとに走らせることでワーカーノードの更新は完了です。

```shell
kubectl get namespaces -o=jsonpath="{range .items[*]}{.metadata.name}{'\n'}{end}" | parallel --no-run-if-empty "./rollout-pods.sh {}"
```

ここまでの作業を開発環境で実行して、動作環境をした上で問題なければステージング、本番環境に進みます。

# 最後に

ざっくりとしたまとめになりましたが、以上が弊チームでEKSクラスタのバージョンアップデートを自動化する上で行った内容となります。


幸いそこまで大きなK8sクラスタを運用しているわけではないので、これくらいの作業感で収まっているところはあると思います。


紹介したツールやコマンドなどの内容が少しでも役に立てば幸いです。