🔄

Renovate で Terraform 管理の EKS クラスターとアドオンのバージョンを自動更新する

に公開

こんにちは、tsub です 😄

Terraform で EKS クラスターを管理している場合、アドオン(kube-proxy、vpc-cni、CoreDNS、EBS CSI ドライバー)のバージョン管理が課題になることがあります。

弊社ではこれまで、アドオンのバージョンを Terraform aws provider の aws_eks_addon_version datasource で動的に取得していました。この方法は常に「推奨バージョン」を参照するため、新しいバージョンがリリースされるたびに terraform plan のドリフトが生じます。日を跨いでからリリースすると、ドリフトが入っていて、リリースを中断してステージングで動作確認し直す手間があり、課題感がありました。

単純にバージョンを固定するだけでは、今度はアップデートの手間が増えます。そこで元々導入していた Renovate を使い、バージョン更新 PR を自動作成する仕組みを整備しました。あわせて、EKS クラスター自体のバージョンも Renovate で管理するようにしています。

この記事の対象読者

  • Terraform で Amazon EKS クラスターを管理しているインフラエンジニア
  • すでに Renovate(GitHub App 版)を導入しており、EKS への適用を検討している方
  • aws_eks_addon_version datasource によるドリフト問題を抱えている方

前提条件

  • Terraform v1.14.x
  • Amazon EKS 1.33
  • Renovate(GitHub App 版)

EKS クラスターバージョンの Renovate 管理

基本的には以下の GitHub Discussion で紹介されている方法を参考に実装しています。

https://github.com/renovatebot/renovate/discussions/35132#discussioncomment-14184594

Renovate には EKS クラスターバージョンを管理する公式の datasource がありません。しかし、AWS が管理する aws/eks-distro リポジトリは EKS と同じタイミングでリリースされるため、これを代替として利用できます。

まず、EKS クラスターのバージョン定義を Terraform 側で以下のように定義します。

locals {
  # ダッシュ区切りで管理することで Renovate が aws/eks-distro のリリースタグと照合できる
  # 環境ごとに異なるバージョンを設定でき、それぞれ別 PR として作成される
  eks_cluster_version_map = {
    production = "1-33", # renovate:eks
    staging    = "1-33", # renovate:eks
    review     = "1-33", # renovate:eks
  }

  # モジュールに渡す際はドット区切りに変換する
  eks_cluster_version = replace(local.eks_cluster_version_map[local.env], "-", ".")
}

EKS クラスターバージョンは通常 "1.33" のようにドット区切りで指定しますが、aws/eks-distro のリリースタグは v1-33-eks-10 のようにダッシュ区切りです。Renovate の regex versioning は 1.331-33 を別物として扱うため、コード上はダッシュ区切りで管理し、Terraform 側で replace() を使ってドット区切りに変換しています。

そして、Renovate の customManager 設定は以下のとおりです。

renovate.json5 (抜粋)
{
  "customManagers": [
    {
      "customType": "regex",
      "description": "EKS cluster version per environment",
      "fileMatch": ["^terraform/eks/locals\\.tf$"],
      "matchStrings": [
        // 環境名 (production/staging/review) を envName としてキャプチャする
        // ※ env は Renovate の Handlebars テンプレートで予約済みのため envName を使う
        "(?<envName>production|staging|review)\\s+=\\s+\"(?<currentValue>[\\d-]+)\",\\s+# renovate:eks",
      ],
      "depNameTemplate": "aws-eks-cluster-version-{{{envName}}}",
      "packageNameTemplate": "aws/eks-distro",
      "datasourceTemplate": "github-releases",
      // タグ v1-33-eks-10 からバージョン部分 (1-33) を抽出する
      "extractVersionTemplate": "^v(?<version>\\d+-\\d+)-eks-.*",
      // ダッシュ区切りを major/minor に分解することで 1 マイナーバージョンずつ PR が作られる
      "versioningTemplate": "regex:^(?<major>\\d+)-(?<minor>\\d+)$",
    }
  ]
}

depNameTemplate (depName) を環境ごとに分けているのは、弊社のアップデート手順にあわせるためです。先にステージング環境のコントロールプレーン・データプレーンをすべて検証し切ってから、本番環境のアップデートへ進む流れにしています。

この設定により、depNameaws-eks-cluster-version-production / aws-eks-cluster-version-staging / aws-eks-cluster-version-review という 3 つの依存関係として検出され、環境ごとに別 PR が作成されます。

マイナーバージョン制限と allowedVersions の自動更新

EKS のインプレースアップグレードは 1 マイナーバージョンずつしか実施できないため、Renovate の PR も 1 バージョンずつアップデートされるように作らせる必要があります。
この制約を packageRulesallowedVersions で表現します。

renovate.json5 (抜粋)
{
  "packageRules": [
    {
      matchManagers: ["custom.regex"],
      matchPackageNames: ["aws/eks-distro"],
      // 現在 1.33 -> 次は 1.34 のみ許可(2 バージョン以上のスキップを防ぐ)
      allowedVersions: "/^1-34$/",
      // PR タイトル・ブランチ名を 1-33 ではなく 1.33 形式で表示する
      commitMessageExtra: "to v{{{newMajor}}}.{{{newMinor}}}",
      branchTopic: "{{{depNameSanitized}}}-{{{newMajor}}}.{{{newMinor}}}",
    },
  ]
},

EKS クラスターを 1.33 -> 1.34 にアップデートしたらこの allowedVersions の指定を次のマイナーバージョンである 1.35 へ更新する必要があります。この部分は GitHub Actions を使って自動化しています。

.github/workflows/update_eks_renovate_allowedversions.yml (抜粋)
name: Update EKS Renovate allowedVersions

on:
  pull_request:
    types: [opened]
    branches:
      - master

jobs:
  update-eks-renovate-allowedversions:
    # Renovate が作成した EKS バージョンアップ PR のみ対象
    if: startsWith(github.head_ref, 'renovate/aws-eks-cluster-version-')
    runs-on: ubuntu-latest
    steps:
      # 略

      - name: Calculate next allowedVersions
        id: version
        run: |
          # production の EKS バージョンを読み取り、次のマイナーバージョンを計算する
          CURRENT=$(grep 'production = ' terraform/eks/locals.tf \
            | grep 'renovate:eks' \
            | grep -oP '"\K[0-9]+-[0-9]+(?=")' \
            | head -1)
          MAJOR=$(echo "$CURRENT" | cut -d- -f1)
          MINOR=$(echo "$CURRENT" | cut -d- -f2)
          echo "next=${MAJOR}-$((MINOR + 1))" >> "$GITHUB_OUTPUT"

      - name: Update allowedVersions
        run: |
          NEXT="${{ steps.version.outputs.next }}"
          sed -i 's|allowedVersions: "/\^[0-9]*-[0-9]*\$/"|allowedVersions: "/^'$NEXT'$/"|' renovate/eks.json5

Renovate で EKS クラスターのアップデート PR が作成された際に、上記ワークフローを実行し、allowedVersions の更新コミットを PR 内に含めています。なお、順番的に本番環境のクラスターアップデートが最後となるため、本番環境のアップデート PR のみでこのワークフローを実行しています。

EKS アドオンバージョンの Renovate 管理

EKS アドオンに関しても基本的には以下の GitHub Discussion で紹介されている方法を参考に実装しています。

https://github.com/renovatebot/renovate/discussions/29465#discussioncomment-9805303

まず、前提として EKS アドオンのバージョンは以下のように GitHub で公開されている OSS のバージョンの末尾に -eksbuild.x というサフィックスが付いた形式となっています。そのため、例えば aws-ebs-csi-driver アドオン のバージョンを kubernetes-sigs/aws-ebs-csi-driver リポジトリの Releases から取得しようとしてもうまくいきません。よって、専用のリリース情報を取得するための Renovate の datasource が必要となります。

kube-proxy の例
v1.21.1-eksbuild.7

Renovate には aws-eks-addon datasource という EKS アドオン専用の datasource が存在します。しかし、これは Self-hosted Renovate 専用 であり、GitHub App 版では利用できません。

単純に Self-hosted 版に移行すれば良いのですが、GitHub App 版と同じような使い勝手[1]を維持するための検証や実装などに時間を割くほどの課題感ではなかったため、今回はできるだけ GitHub App 版を維持しながらできる方法を選択しました。

よって、公式の datasource の代わりに、有志が管理する plumdog/eks-addon-configuration リポジトリを customDatasource として採用しました。

まず、EKS アドオンのバージョン定義を Terraform 側で以下のように定義します。

locals {
  kube_proxy_addon_version = {
    # depName に環境名を含めることで、環境ごとに別 PR として作成される
    production = "v1.33.3-eksbuild.4", # renovate: datasource=custom.eks-addon-configuration depName=kube-proxy packageName=kube-proxy extractVersion=(?<version>.+)
    staging    = "v1.33.3-eksbuild.4", # renovate: datasource=custom.eks-addon-configuration depName=kube-proxy packageName=kube-proxy extractVersion=(?<version>.+)
    review     = "v1.33.3-eksbuild.4", # renovate: datasource=custom.eks-addon-configuration depName=kube-proxy packageName=kube-proxy extractVersion=(?<version>.+)
  }[local.env]
}

そして、Renovate の設定は以下のとおりです。# renovate: datasource= のようなコメントを使ったアップデート対象の検出は 標準の customManagers プリセット を参考に設定しています。

renovate.json5 (抜粋)
{
  "customDatasources": {
    "eks-addon-configuration": {
      defaultRegistryUrlTemplate: "https://raw.githubusercontent.com/plumdog/eks-addon-configuration/main/data/{{packageName}}/addon.json",
      defaultVersioning: "aws-eks-addon",
      transformTemplates: [
        // clusterVersion で絞り込み、Renovate が期待する releases 形式に変換する
        // clusterVersion は allowedVersions の更新と同タイミングで GitHub Actions が自動更新する
        "{\"releases\": addonVersions[compatibilities[clusterVersion=\"1.34\"]].{\"version\": addonVersion}}",
      ],
    },
  },
  "packageRules": [
    {
      matchDatasources: ["custom.eks-addon-configuration"],
      versioning: "aws-eks-addon",
    },
  ],
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": [ "^terraform/.+\\.tf$" ],
      "matchStrings": [
        ".*\\s+=\\s+\"?(?<currentValue>.+?)\"?,?\\s+# renovate: datasource=(?<datasource>.+?) depName=(?<depName>.+?) packageName=(?<packageName>.+?)( extractVersion=(?<extractVersion>.+?))?( registryUrl=(?<registryUrl>.+?))?\\s",
      ],
      "extractVersionTemplate": "{{#if extractVersion}}{{{extractVersion}}}{{else}}^v?(?<version>.+)${{/if}}",
    },
  ]
}

plumdog/eks-addon-configuration は、EKS アドオンのバージョン情報を JSON ファイルとして公開しているリポジトリです。アドオン名をパスに含む URL(例: .../data/kube-proxy/addon.json)を叩くと、クラスターバージョンごとの対応アドオンバージョン一覧が取得できます。

packageRules には Renovate 公式の aws-eks-addon versioning の適用ルールを追加します。eksbuild サフィックス付きバージョン(例: v1.33.3-eksbuild.4)を正しく解釈するために必要です。

https://docs.renovatebot.com/modules/versioning/aws-eks-addon/

また、transformTemplatesclusterVersion を指定することで、現在のクラスターバージョンに対応したアドオンバージョンのみを対象にします。この値も EKS クラスターの方で使用している allowedVersions と同様に GitHub Actions で自動更新する仕組みにしています。

.github/workflows/update_eks_renovate_allowedversions.yml(抜粋)
steps:
  # 略
  - name: Update allowedVersions and clusterVersion
    run: |
      NEXT="${{ steps.version.outputs.next }}"
      NEXT_DOT=$(echo "$NEXT" | tr '-' '.')

      # allowedVersions を更新
      sed -i 's|allowedVersions: "/\^[0-9]*-[0-9]*\$/"|allowedVersions: "/^'$NEXT'$/"|' renovate/eks.json5

      # customDatasources の clusterVersion を更新
      CURRENT_CLUSTER_DOT=$(grep -oP 'clusterVersion=\\"\K[0-9]+\.[0-9]+(?=\\")' renovate/eks.json5 | head -1)
      sed -i 's|clusterVersion=\\"'"$CURRENT_CLUSTER_DOT"'\\"|clusterVersion=\\"'"$NEXT_DOT"'\\"|g' renovate/eks.json5

運用してわかったこと

最初の実装では、EKS クラスターバージョンのアップデート PR とアドオンの PR を groupName で同一 PR にまとめていました。EKS アドオンのバージョンはクラスターバージョンごとに対応バージョンが決まっているため、「クラスター更新 = アドオンも一緒に更新」という流れが自然に思えたためです。

// 当初の設定(アドオンがクラスターと同一 PR にまとまっていた頃)
{ matchDepNames: ["aws-eks-cluster-version-production"], groupName: "aws-eks-cluster-version-production" },
// アドオンを同じグループに含めることで、クラスター PR にアドオン更新もまとめる
{ matchDepPatterns: ["-production$"], matchDatasources: ["custom.eks-addon-configuration"], groupName: "aws-eks-cluster-version-production" },

しかし、EKS アドオン単体でアップデートしたいケースが出てきました。この場合、EKS クラスターのアップデート PR とグループ化されてしまっている関係で、単体での PR 作成がされないので、手動でアップデート PR を作成する必要があります。

調べた限り、おそらく Renovate の仕様上、同一のパッケージを「単体 PR」と「グループ化 PR」の両方として同時に作成することはできません。

そのため、EKS アドオン単体でのアップデートを優先し、グループ化を解除しました。

グループ化を解除したことで、EKS クラスターのアップデート時は複数の PR をマージする必要がありますが、一応 AWS ドキュメント でもクラスターアップデートは コントロールプレーン -> データプレーン -> アドオン の順に実施することが推奨されているため、PR が別々になっている方が柔軟性があり、手順上も明確になります。

Amazon EKS クラスターのアップグレードプロセスの概要は以下のとおりです。

  1. クラスターがアップグレードをサポートする状態であることを確認します。これには、クラスターにデプロイされたリソースで使用されている Kubernetes API をチェックし、クラスターに正常性の問題がないことを確認することが含まれます。クラスターのアップグレードの準備状況を評価する際には、Amazon EKS のアップグレードインサイトを使用する必要があります。
  2. コントロールプレーンを次のマイナーバージョンにアップグレードします (1.34 から 1.35 など)。
  3. データプレーン内のノードをコントロールプレーンのノードと一致するようにアップグレードします。
  4. その他にクラスターで実行されているアプリケーション (cluster-autoscaler など) がある場合は、そのアプリケーションをアップグレードします。
  5. Amazon EKS が提供するアドオン (デフォルトで含まれるアドオンなど) をアップグレードします。
    • Amazon VPC CNI の推奨バージョン
    • CoreDNS の推奨バージョン
    • kube-proxy の推奨バージョン
  6. クラスターと通信しているクライアントがある場合は、そのクライアントをアップグレードします (kubectl など)。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/update-cluster.html#update-cluster-summary

まとめ

Terraform で管理する EKS クラスターとアドオンのバージョンを Renovate で自動管理する方法を紹介しました。

  • EKS クラスター: aws/eks-distro の GitHub リリースを datasource として採用。ダッシュ区切りバージョンを regex versioning で管理し、allowedVersions で 1 マイナーバージョン制限を設ける。allowedVersions の更新は GitHub Actions で自動化する
  • EKS アドオン: GitHub App 版 Renovate では公式 datasource が使えないため、plumdog/eks-addon-configuration を customDatasource として利用。depName に環境名をエンコードして環境ごとの PR を分離する
  • 運用設計: クラスターとアドオンを同一 PR にまとめるとアドオンの更新が滞るため、個別 PR に分離するのが実用的

EKS 以外でも「バージョン管理のための datasource が Renovate にない」「GitHub App 版の制約がある」といった状況では、customDatasource と customManager を組み合わせることで同様のアプローチが使えます。

脚注
  1. 例えば Dependency Dashboard や Renovate PR でチェックボックスにチェックしたら即座に動作するようにするなど。Self-hosted 版は基本的に GitHub Actions で動作させることになるため、on.schedule による定期実行だけだと即時実行ができず、GitHub App 版と使い勝手が変わってしまいます。 ↩︎

Social PLUS Tech Blog

Discussion