Argo Workflowsを使ったKubernetes(EKS)のアップグレード

こんにちは、インフラグループ Kubernetesチームの福田です。
この記事は Enigmo Advent Calendar 2023 の22日目の記事です。

皆さんはKubernetesのアップグレード、どうしていますか?
Kubernetesは4ヶ月に一回、新しいマイナーバージョンがリリースされ、最新の3つのマイナーバージョンのみサポートされます。
つまり、原則は4ヶ月に一度、アップグレードをやらなければなりません。(最新バージョンであれば最大12ヶ月はサボれるという考え方もありますが。。。)
弊社ではKubernetes環境としてEKSを使っており、Kubernetes本体のリリースと微妙に間隔が違いますが、最新を維持するために大体3〜5ヶ月毎にアップグレード作業をやっています。
このKubernetes(EKS)のアップグレード作業をワークフロー化したのでそれを紹介します。

ワークフローとは

本記事でいうワークフローとはワークフローエンジンを使って自動化された作業のまとまりです。
ワークフローエンジンとは、作業プロセスをいい感じに管理してくれるツールです。
代表的なものとして、Apache Airflow, digdag, Argo Workflowsなどがあります。
我々の場合、Argo Workflowsを利用しています。

EKSアップグレードワークフローの概要

上図は実際のワークフローのキャプチャです。
3段階の構造(上から順に実施される)になっていて、以下を実施する内容となっています。

  1. 廃止されるAPIを使っていないかチェック
  2. EKSアドオンのアップグレードバージョンを特定
  3. CloudFormationテンプレートを更新するPullRequestを出す。

元々、弊社ではAWSリソースをCloudFormationで管理する運用になっていたことから、ワークフローのゴールがCloudFormationコードの修正(Pull Request作成)となっています。

ワークフローの入力

上図の通り、ワークフローには入力パラメータがあります。

  • ticket-id
    • JIRAのチケットIDを指定します。 コード修正時のコミットメッセージやブランチ名のプレフィックスとして利用されます。
  • kubernetes-cluster
    • アップグレードを実施する対象のクラスタをプルダウン形式で選択します。
  • eks-version
    • Kubernetes versionをマイナーバージョンまで指定します。例:1.28
  • ami
    • EKSノードで使用するAMIを指定します。

EKSアップグレードワークフローの詳細

1. 廃止されるAPIが無いかチェック

Kubernetesではマイナーバージョンの更新に伴って、機能追加や古い機能の削除のためにAPI Versionが更新され、既存のものが廃止されることがあります。
そのため、Kubernetesアップグレードを実施する前に廃止されるAPIが無いかチェックしています。
plutoというOSSを使って、クラスタ上にあるリソースで廃止されるものを使っていないかチェックをしています。

2. EKSアドオンのアップグレードバージョンを特定

EKSではKubernetesのマイナーバージョンの更新と併せて、EKSアドオンのアップグレードが必要な場合があります。我々の場合、「EKSアドオンのバージョンはデフォルトバージョン※を指定する」というポリシーで運用しています。
※ EKSではアドオン毎にKubernetesのマイナーバージョンに紐づくデフォルトのバージョンを公開しています。

具体的な方法としてはawsコマンドで、指定したKubernetesのマイナーバージョンに対応するEKSアドオンのデフォルトバージョンを取得しています。

aws eks describe-addon-versions --addon-name ADDON_NAME --kubernetes-version K8S_NAME --query 'addons[0].addonVersions[?compatibilities[0].defaultVersion==`true`].addonVersion | [0]'

3. CloudFormationのコードを管理しているリポジトリKubernetesアップグレードのためのPull Requestを出す

このステップについては前のステップで得られるEKSアドオンのアップグレードバージョンとワークフローへの入力値を元に、CloudFormationのコードを修正して、Pull Requestを出します。
また、Pull Requestに人手で実施する作業内容を記載します。
このステップにおける処理はGoで実装した自前のCLIツールで行っています。

考慮事項

このステップを実装するにあたり、考慮したことを紹介します。

VPC CNIのアップグレード

前のステップでEKSアドオンのアップグレードバージョンを特定していますが、VPC CNIについてここで問題があります。
VPC CNIのアップグレードは一度に1つのマイナーバージョンのみアップグレード可能です。
例えば、Kubernetes 1.27に対応するVPC CNIのデフォルトバージョンがv1.12.6-eksbuild.2Kubernetes 1.28に対応するVPC CNIのデフォルトバージョンがv1.14.1-eksbuild.1の場合、予めVPC CNIのバージョンをv1.13系にアップグレードする必要があります。
実際の実装としてはVPC CNIのアップグレードバージョンのマイナーバージョンが元のバージョンより2つ以上離れている場合は、本ワークフローを実施する前にVPC CNIのマイナーバージョンをアップグレードするように促すメッセージを出してワークフローはそこで失敗するようにしています。

手順書

Kubernetesアップグレードの実施自体の手順としてはCloudFormationの実行コマンドだけですが、実際の作業手順はもう少し複雑です。
我々の場合はアップグレードの実施コマンドに加えて以下を盛り込む必要がありました。

  • 作業前
    • 作業通知の開始アナウンス
    • 作業時の監視アラートのサイレント設定
  • 作業後
    • アップグレードが正しく完了したかの確認
    • 作業時の監視アラートのサイレント設定解除
    • 作業通知の終了アナウンス

yamlをGoで編集する際の副作用

CloudFormationのテンプレートはyamlですが、これをGoで書き換えると発生する副作用があります。
それはGoでyamlを扱うためにyamlファイルをエンコードして、プログラム上で何らかの処理をして、再度yamlファイルに戻してやると空行やコメント等が消失してしまうというものです。
yamlとしては等価なのだからという理由で切り捨ててしまえばそれまでですが、コメントや空行はリーダビリティの観点で有用なものも多いので、これは維持するようにしたいです。

まず、コメントの維持についてはyaml.v3を使うことで解決できます。
具体的には以下のようにyaml中にある書き換えたいプロパティをpathで指定して、それをnewValueで更新するようにしています。

func (e *yamlEditor) updateYamlProperty(node *yaml.Node, path []string, newValue string) error {
    if len(path) == 0 {
        return fmt.Errorf("invalid path")
    }
    for i := 0; i < len(node.Content); i += 2 {
        keyNode := node.Content[i]
        valueNode := node.Content[i+1]
        if keyNode.Value == path[0] {
            if len(path) == 1 {
                valueNode.Value = newValue
                return nil
            } else if valueNode.Kind == yaml.MappingNode {
                return e.updateYamlProperty(valueNode, path[1:], newValue)
            }
        }
    }
    return fmt.Errorf("invalid path")
}

空行についてはdiffコマンドのオプションで空行を無視する差分のdiffを生成して、それをpatchコマンドで元のファイルに修正として適用するようにしています。

## 元々存在するcloudformation template
target.yaml

## プログラムによって書き換えが行われたcloudformation template。空行はなくなってしまっている。
target.changed.yaml

diff -U0 -w -b -B target.yaml target.changed.yaml > target.diff
patch -i target.diff target.yaml

参考: https://github.com/mikefarah/yq/issues/515#issuecomment-830380295

まとめ

今回はArgo Workflowsを使ったKubernetes(EKS)のアップグレードについて紹介させていただきました。
紹介したワークフローはKubernetes1.27とKubernetes1.28のリリースの間で作成したもので、Kubernetes1.28のアップグレードはこのワークフローを使ってアップグレードしました。
これのおかげで面倒だったアップグレード作業を大分楽にすることができました。

明日の記事の担当は早野さんです。お楽しみに。


株式会社エニグモ すべての求人一覧

hrmos.co