Renovateを用いてCI/CD環境やKubernetesエコシステムのバージョン更新を楽にする

Update CI/CD and K8s tools easier with Renovate

はじめに

こんにちは、計測プラットフォーム開発本部SREブロックの髙木(@TAKAyuki_atkwsk)です。普段はZOZOMATZOZOGLASSZOZOFITなどの計測システムの開発・運用に携わっています。およそ2年ぶりのテックブログ執筆となりました。

さて、今回はCI/CD環境やKubernetesエコシステムのバージョン更新についてRenovateを使って楽しようという話をご紹介します。

CI/CDのワークフローや実行環境、Kubernetesを運用する上で導入するエコシステム1の多くはコード管理されています。そして、これらについてどのバージョンを使うかをコード上で指定することが多いです。しかし、コード化はされているもののバージョン更新まではなかなか手が回らなくなっており、どうにか解消したく取り組んだ話になります。

目次

背景や課題

計測プラットフォーム開発本部SREブロックでは計測システムにおける実行基盤(AWSやKubernetesを利用することが多い)やCI/CD環境、監視の仕組み、その他運用で必要なツールがあります。これらのほとんどはCloudFormationのテンプレートやKubernetesマニフェスト、ビルドパイプラインの設定ファイルなどコード化して管理されています。コード化を進めていくとパッケージを利用することが少なくないと思います。具体例を挙げると、Dockerイメージ、Helmチャート、CIの実行環境、プログラミング言語のライブラリなどになります。また、パッケージを利用する際にはその名前とバージョンをコード上で指定します。

これらのパッケージは継続的に更新されていて、脆弱性の対応やバグ修正、新機能追加、パフォーマンス向上などの内容が含まれています。したがって、更新された内容を取り込むには指定するバージョンを上げていく必要があります。latestタグなどを使い最新のバージョンを使う方法もありますが、予期しない変更が入ってしまったり、別のバージョンが意図せず同時に稼動したりするため限定的に利用される方法だと思います。

このため、パッケージのバージョン更新に付いていくのが理想ですが、私たちのチームではパッケージのバージョンは一度設定したままになっているか気づいたタイミングで更新するという状態に陥っていました。この状態の問題点は、新しいバージョンを利用したいと思ってもパッケージの変更の差分が大きくなり過ぎるため、更新を適用して問題ないか判断するのに時間が掛かってしまうことです。特にパッケージの特定バージョンのサポートが切れてしまったり脆弱性が見つかったりする場合にこの問題の影響を受けてしまいます。

バージョン更新のステップ

あるパッケージのバージョンを更新するには以下のステップが必要になると考えています。

  1. 更新されたことに気づく
  2. 更新して問題ないか判断する
  3. 更新を適用する

バージョン更新作業はなんとなく大変だなと思われている方は私以外にもいるかもしれません。この大変さというのを考えてみると、「更新されたことに気づく」「更新して問題ないか判断する」ステップにそれぞれ大変さの要素が存在すると考えが到りました。「更新を適用する」ステップに関しては、私たちは既にデプロイメントパイプラインを整えているため変更のPRをマージさえすれば適用される状態になっています。

  • 更新されたことに気づく
    • そもそも更新されたことに気づかない
      • 全てのパッケージの情報を追っていられない
    • 気づいた人がやることになる
      • 作業の負担が大きく属人化しやすい
  • 更新して問題ないか判断する
    • どう影響するか判断するのが大変
      • 変更差分を探してきて理解する
      • 自動テストが無いものは開発環境に反映して動作確認が必要

これらの要素が組み合わさると以下のような悪循環が発生してしまいます。図の中で示した特に大変さを感じるポイントは差分が溜まった上での更新して問題ないかの判断です。これを解消するには判断する作業自体の負担を減らすこと、更新が溜まらないようにすることが必要だと考えました。

大変さの悪循環

解決に向けて

ツールの導入

このような大変さに対してツールを使うことで緩和できるのではと考えました。フロントエンドやバックエンドの開発においては既に浸透しているものだと思います。ツールの具体例としては、DependabotRenovateScala Steward(Scalaに特化したものですが)があります。これらのツールを用いる場合のツールと人間の作業の棲み分けを以下のようにまとめました。いくつか人間のやることは残っていますが、上記で挙げた大変さの半分以上は解消されそうです。

ステップ ツールのやること 人間のやること
更新されたことに気づく プルリクエスト(以下PRと表記)の作成、レビュアやassigneeの割り当て PRの存在を把握
更新して問題ないか判断する 変更差分のまとめ 変更差分の理解、開発環境での動作確認、更新の適用を見送る場合はPRのクローズ
更新を適用する PRのマージ

さて、私たちはこのツールとしてRenovateを使うことにしました。他の選択肢としてはDependabotも候補に挙がりました。Renovateを使う判断の決め手となったのは更新検知される対象パッケージの豊富さでした。特に、CI/CDやKubernetesエコシステムに関してはRenovateに優位性が見られ、私たちの管理するパッケージがその対象範囲に多く含まれると考えました。また、社内で他の部署がRenovateを利用していたので先人からアドバイスをもらえるという点も良かったです。

docs.renovatebot.com

ただ、どちらのツールもサポートされる対象が決まっているので、対象外のパッケージについては私たち自身で更新に気づいて作業する必要があります。

Renovateの導入

RenovateはGitHub Appが提供されているのでリポジトリに対して設定することで簡単に導入できます。その他、GitHub Actionsを利用する方法などさまざまな導入方法が用意されていますので詳しくは以下のページを参考にしてください。

docs.renovatebot.com

GitHub Appを設定すると対象のリポジトリにRenovateの設定ファイルを追加するPRが自動的に作成されます(以下画像を参照)。必要であれば設定ファイルを編集します(設定ファイルの編集はマージ後でも可能です)。このPRをマージすると導入は完了です。

Renovate設定ファイル追加のPR

もし、リポジトリ内のパッケージに更新があればRenovateによって以下のようなPRが作成されます。PRの概要には対象のパッケージが何から何に更新されるかの情報とバージョン毎のリリースノートが記載されます。PRに含まれるソースコードの差分としては、以下の例ではArgoCDアプリケーションのソースとして指定するHelmチャートのバージョンを書き換えるものとなります。

External Secrets Operatorのバージョン更新PR概要 External Secrets Operatorのバージョン更新PRソースコード差分

リリースノートを探しに行くのは手間になるので、このように概要欄に展開してくれるのは非常に助かります。パッケージによっては記載されないものもあるので、その場合は自分でコミットを追うなりする必要があります。

試験的な運用

1つのリポジトリに導入するのであれば先ほど紹介したような形になりますので比較的容易だと思います。しかし、私たち計測プラットフォームSREが主に管理するリポジトリは現在8つあります。その内訳はインフラ用(CloudFormationテンプレートやTerraformのtfファイルの管理)、共通ツール用、各サービスのKubernetesマニフェスト用(6つある)となっています。Kubernetesマニフェスト用のリポジトリについては以下の記事で説明されていますので良ければ参考にしてみてください。

techblog.zozo.com

最終的にはこれらのリポジトリ全てにRenovateを導入しますが、一度に行うと今まで溜まっていたバージョン更新についてのPRが大量に来てメンバーが捌ききれなくなる懸念を持ちました。ツールの導入が目的ではなく、バージョン更新を楽して継続的にやっていくことが大事と考えたのでまずは試験的に運用することにしました。具体的には、対象のリポジトリを1つとし、対応するメンバーをチームの5人中3人に絞って上手く運用できるか試しました。

また、このように更新作業を進めましょうという簡単なフローを考えて試してみました。

試験運用フロー

簡単に説明すると、まずRenovateによってパッケージの更新が検知されると新しくPRが作成され自動的にレビュアが割り当てられます。対応メンバー内で最初に確認するメンバーを決めてPRのassigneeに割り当てます。assigneeは更新内容を確認し開発環境に反映して問題ないことを確認します。

確認方法はパッケージによって異なりますが、KubernetesエコシステムであればコントローラーのPodが起動しているか、エラーログが出力されていないかを主に確認します。CIの実行環境であれば、実際にジョブを動かしてみて成功するかを確認します。

確認して問題なければ他のメンバーをレビュアに割り当ててレビューを依頼します。開発環境に反映して問題がある場合、修正等で対応するか対応できないと判断してPRをクローズする、つまりバージョンを更新しないことになります。

このような形で試験運用を行う中で、誰が一番最初に見るかという問題が残っていることに気がつきました。Renovateによって対応するメンバー全員に対してレビュアが割り当てられていることで「誰かが最初の確認をやってくれるだろう」という状態になっていました。チーム内で相談した結果、明示的に担当を決めようということになりました。当初は週に一度PRを眺めて担当を決めていましたが、これも手間だと感じたためRenovateによってランダムアサインする設定を以下のように行いました。

{
  "assignees": ["Alice", "Bob", "Carol"],
  "assigneesSampleSize": 1
}

また、開発環境に反映しないと確認できないパッケージについては、確認方法を社内のコンフルエンスページにまとめることでどのメンバーがアサインされても作業が進められるようにしました。試験運用を経てチームメンバーが概ね問題なくバージョン更新の運用をできるようになったので残るリポジトリにもRenovateを導入し本格的に運用を始めました。

以上の改善点を踏まえた上で、以下のフローに沿って運用することにしました。「通常のレビューフロー」について補足すると、PR作成者がレビュアを指定し、レビュア2人からapproveされればマージ可能となっています。RenovateによるPRの場合assigneeがレビュアを指定するという点のみ異なります。また、CIで変更に問題ないことを担保できれば通常のレビューフローとほぼ変わらない形になります。

本運用フロー

Renovateの設定

「Renovateの導入」のセクションでGitHub Appを設定後に設定ファイルを追加するPRが作られると紹介しました。初期設定から追加した設定のいくつかをここでは紹介します。

JSON5

初期設定では renovate.json というファイルが作成されますが以下のドキュメントにある通りJSON5フォーマットにも対応されています。主にコメントを書くことができるという理由でJSON5フォーマットを利用することにしました。

docs.renovatebot.com

kustomizeのリモートファイル参照

私たちはKubernetesマニフェストを複数環境に対応させるためkustomizeを利用しています。kustomizeではリモートに存在するディレクトリやファイルを参照できます。例えば以下のように書いて kustomize build するとURLを参照しArgoCDインストールに必要なマニフェストを展開してくれます。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - https://github.com/argoproj/argo-rollouts/releases/download/v1.5.0/install.yaml

これはremote targetsと呼ばれる機能の1つであるremote filesを利用しています。しかしRenovateではこちらの機能はサポートされていません。以下リンク先のremote resourcesがkustomizeのremote directoriesに対応しています。

docs.renovatebot.com

調べたところRenovateのIssue #18986に同じようなトピックが存在し、ワークアラウンドが提示されているのを発見しました。これを利用して以下のような設定を追加しています。

{
  "regexManagers":[
    // GitHub上に存在するマニフェストファイルをkustomizationで利用する部分のバージョン管理
    {
      // kustomization.yml or kustomization.yaml
      "fileMatch": ["kustomization\\.ya?ml$"],
      // 例: https://github.com/argoproj/argo-rollouts/releases/download/v1.5.0/install.yaml
      "matchStrings": ["https:\/\/github\.com\/(?<depName>.*\/.*?)\/releases\/download\/(?<currentValue>.*?)\/"],
      "datasourceTemplate": "github-releases",
    },
    {
      "fileMatch": ["kustomization\\.ya?ml$"],
      // 例: https://raw.githubusercontent.com/argoproj/argo-cd/v2.7.2/manifests/install.yaml
      "matchStrings": ["https:\/\/raw.githubusercontent.com\/(?<depName>[^\/]*\/[^\/]*)\/(?<currentValue>.*?)\/"],
      "datasourceTemplate": "github-tags",
    }
  ]
}

この設定によって以下のようなPRが作成されるようになりました。

Argo Rolloutsのバージョン更新PR

ArgoCDのバージョン更新PR

詳細なバージョン更新を行わない

私たちはCIの基盤としてCircleCIを一部で利用しています。CircleCIのジョブでCircleCI Orbを利用する際に circleci/aws-cli@3 のようにメジャーバージョンのみを指定する箇所がありました。すると以下のようにバージョンを詳細化するPRが作成されました。

aws-cli Orbのバージョン更新PR

この変更は意図しないものだったのでPRをクローズしました。そして新しい設定を追加しました。rangeStrategyという設定でバージョンの対象範囲を制御しています。OrbのrangeStrategyはデフォルトでpinになっているため明示的にreplaceを指定するようにしました。

{
  "packageRules": [
    {
      "matchDatasources": ["orb"],
      // NOTE: orbの場合はpinになっていて、これだとメジャーバージョンのみ指定していても詳細なバージョンに更新してしまう
      //       これを回避するためにreplaceに設定する
      "rangeStrategy": "replace",
    },
  ]
}

本運用を行ってみて

試験運用を含めて約5か月間で77件中55件のPRをマージもしくはクローズしました。PRには例示したように、KubernetesエコシステムやCI/CDの環境に関するものが含まれています。以下のグラフはバージョン更新に関するPRの対応件数を示しています。このように、Renovate導入前はバージョン更新がほぼ手付かずになっていましたが、導入後は継続的に更新できるようになっています。

バージョン更新の件数

Renovateによる恩恵によって大変さの悪循環としては以下のように変化しました。まだ循環の要素としては残っていますが、更新差分が溜まりにくくなったことで作業の負担は減っています。

Renovate導入前 大変さの悪循環 Renovate導入後 大変さの悪循環改善後

実際に運用してみて気づいた点を2つ紹介します。1つ目はバージョン更新に必要な情報が均一化されて確認しやすくなったことです。人間によって作成されたPRでは情報の粒度が異なる傾向にあります。RenovateによってPRが作られることで、パッケージへのリンクやリリースノートといった情報が確実に含まれるのでレビューしやすくなると感じました。

気づいた点の2つ目は、長期休暇などで一時的にチームメンバーが減ると、PRがアサインされても確認に取り掛かれないことがありました。この点については、1週間の中でバージョン更新する時間をタイムボックスとして確保することや、パッチバージョンのみの更新であれば自動的にマージすることを対策として考えています。

おわりに

計測プラットフォーム開発本部ではこの記事で紹介したような既存サービスに関わる部分での改善、新規事業の開発など、幅広い業務を担当しています。現在、私たちと共にサービスを支える方を募集しています。少しでもご興味のある方は、以下のリンクからぜひご応募ください。

corp.zozo.com


  1. この記事では例えば、External Secrets OperatorAWS Load Balancer ControllerArgoCDのようなミドルウェアを指します。
カテゴリー