この記事はZOZO Advent Calendar 2023 シリーズ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
で取得できますが、これを成形することでサポートされている最新のクラスタバージョンを取得することができたりします。
aws eks describe-addon-versions | jq -r ".addons[] | .addonVersions[] | .compatibilities[] | .clusterVersion" | sort -r | uniq | head -n 1
(もうちょっと綺麗なコマンドはあるとは思います。筆者はシェルが少々苦手です。)
また、現在使用してるクラスタのバージョンに関しては、以下のコマンドで取得できます。
aws eks describe-cluster --name <your cluster name> --query "cluster.version"
これを比較して、もし異なっていればクラスタのバージョンをアップデートするPRをGitHub Actionsから作成します。チームメンバーへのアサインまでするとなお良しです。
チームメンバーの取得はGitHub Teamsを使うと良いです。gh api
でGitHub Teamのメンバーリストが取得できるので、こんな感じでランダムなチームメンバーを取得できます。
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) が便利です。
> 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を拾うことができます。
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のリソース名の取得
deployments=`kubectl get deployment -n $ns -o=jsonpath="{range .items[*]}{.metadata.name}{'\n'}{end}"`
Podの再起動
parallel --no-run-if-empty "kubectl rollout restart deployment {} -n $ns" ::: $deployments
(並列実行すると楽)
Podのステータスチェック
for deployment in $deployments; do
kubectl rollout status deployments/$deployment -n $ns
done
以上をスクリプトに入れて、並列でnamespaceごとに走らせることでワーカーノードの更新は完了です。
kubectl get namespaces -o=jsonpath="{range .items[*]}{.metadata.name}{'\n'}{end}" | parallel --no-run-if-empty "./rollout-pods.sh {}"
ここまでの作業を開発環境で実行して、動作環境をした上で問題なければステージング、本番環境に進みます。
最後に
ざっくりとしたまとめになりましたが、以上が弊チームでEKSクラスタのバージョンアップデートを自動化する上で行った内容となります。
幸いそこまで大きなK8sクラスタを運用しているわけではないので、これくらいの作業感で収まっているところはあると思います。
紹介したツールやコマンドなどの内容が少しでも役に立てば幸いです。