背景
SREグループが管理しているEKSとGKEクラスタのマニフェストを一つのmonorepoで一元管理しています。Kubernetesマニフェストをmonorepoで管理するのが主流と言えますが、SREグループの管理方法に主に3つの特徴があります:
- YAMLファイルを直接扱わないで、Helmを利用しています。予め用意した共通Helmチャートを利用することでマニフェストをテンプレート化でき、運用が楽になります(共通Helmチャートについてはこのブログ記事にご参考ください)。オープンソースコミュニティが提供しているチャートを活かせるのも一つのメリットです。
- 開発環境と本番環境の設定の差、クラスタ間の設定の差など、環境の差を最小限に抑えるためにクラスタ共通の設定と各クラスタの専用設定を分けて管理しています。
- 環境変数など秘密情報を他の設定と一緒に一元管理するため、暗号化した上で同じリポジトリに保存しています(SOPS: Secrets OPerationS!記事で詳細に説明していますので是非読んでみてください)。
そのため、以下のようなフォルダ構造を採用しています。
. ├── namespaces │ ├── namespace-a │ │ ├── Chart.yaml # 共通チャートとコミュニティチャートを依存関係に設定 │ │ ├── common-values-a.yaml # クラスタ共通の設定値 │ │ └── common-values-a.secret.yaml # クラスタ共通の秘密設定値 │ └── namespace-b │ ├── common-values-b.yaml │ └── common-values-b.secret.yaml └── clusters ├── dev-cluster │ ├── namespace-a │ │ ├── dev-values-a.yaml # devクラスタ専用の設定値 │ │ └── dev-values-a.secret.yaml # devクラスタ専用の秘密設定値 │ └── namespace-b │ │ ├── dev-values-b.yaml │ │ └── dev-values-b.secret.yaml └── prod-cluster ├── namespace-a │ ├── prod-values-a.yaml # prodクラスタ専用の設定値 │ └── prod-values-a.secret.yaml # prodクラスタ専用の秘密設定値 └── namespace-b ├── prod-values-b.yaml └── prod-values-b.secret.yaml
Argo CD導入前
Argo CDに移行する前、特定のクラスタとネームスペースのマニフェストを適用するには helm
をラップした独自のCLIを以下のようにローカルから実行していました。
customcli --cluster dev-cluster --namespace namespace-a
裏では、CLIがクラスタ共通設定とクラスタ専用設定を統合したり、 helm dep update
でHelmチャートの依存関係を更新したり、 helm diff
で適用される設定の差分を出したりして最終的に helm upgrade
で設定をクラスタに適用していました。
そのやり方に問題がいくつかありました:
- 適用のハードルが高かった:ローカル環境から手動でコマンドを実行する必要があったため、例えば別作業中にマニフェストを変更する必要になった場合、ローカル環境をまず綺麗にする必要がありました。
- 適用が遅かった:Helmチャートの依存関係を毎回更新する必要があったため、適用に数十秒かかりました。
- 差分が見づらかった:適用される設定の差分が見れていたが、ターミナルでの表示のため差分が多かった場合にかなり見づらかったです。
- 適用状況を把握できなかった:CLIを実行して差分を確認しないと設定がクラスタに適用されているかどうかがわからなかったためクラスタの全体状況が把握しづらかったです。
- クラスタやネームスペースを横断した変更がやりづらかった:各クラスタとネームスペースごとにCLIを実行する必要があったため手間がかなりかかっていました。
- 細かい権限管理ができなかった:Ingress, Secret, ConfigMap, Deploymentなど様々なKubernetesリソースを一気に適用していたため、CLIを実行する人が強いKubernetes権限を持たないといけなかったです。そのため、SRE以外のエンジニアに適用権限を付与できなかったです。
- 自動化が難しかった:CLIをCIで実行することで自動化を実現するのを考えていましたが、CIに強い権限を付与することになってしまうため中々難しかったです。
MoTの成長に伴ってサービスの数も増えて運用が徐々に苦しくなっていたため、運用効率を上げるために約一年前にArgo CDを導入することになりました。上記の問題が解決され、期待していなかったメリットも得られて非常に満足しています。これからArgo CDについてとSREグループの使い方を紹介させていただきたいと思います。
Argo CD
Argo CDはKubernetesマニフェストをGitOpsで管理するためのオープンソースCDツールです。Gitリポジトリに管理されているマニフェストとKubernetesクラスタの状態を常に同期することで、Gitだけでクラスタの状態を確認、変更、リバートできます!
基本動作として、クラスタの状態を常に監視つつgitリポジトリを定期的に取得しています。差分が検知された場合、設定次第同期を自動的に実行してくれたり通知してくれたりします。また、gitのwebhookを設定することでマージした途端にArgo CDがgitリポジトリの最新情報を取得し同期を走らせてくれるためマニフェストを数秒で適用できます!
CI経由での適用、いわゆる「プッシュ型」から、適用を非同期で行う「プル型」になりますのでCIに強い権限を付与する必要がなくてセキュリティ観点的にも嬉しいです。様々なCIが影響を受けた2021年4月のCodecovセキュリティインシデント、Travis CIも影響を受けた2022年4月のGitHubセキュリティインシデント や 2023年1月のCircleCIセキュリティインシデントなど最近CI関連のセキュリティインシデントが増えていてKubernetes本番環境の権限をCIに付与することが難しくなっています。Argo CDのようなプル型の仕組みを使うことでCIに権限を付与する必要がなくなってセキュリティレベル向上に繋がります!
選定理由
Flux、SpinnakerなどArgo CD以外にGitOps系のCDツールがいくつかありますが、以下の理由でArgo CDを選定しました:
- 成熟度が高い:CNCFプロジェクトのgraduatedステータスを達成している
- 機能が充実している:RBAC、SSO、部分的同期、同期ウィンドウなどEnterpriseグレードの機能も提供している
- GUIが使いやすい:クラスタの状況を一発でわかるし、差分も見やすいし、全体的にGUIが自然
- 軽量性:CPUとメモリリソースをそこまで使用しない
- 「単純」な同期システム:いい意味でArgo CDはKubernetesマニフェストの同期しかしない単純なシステム。ビルドパイプラインなど必要としていない機能に混乱させられていなくて、やってほしいことをきちんとやってくれている
- カスタマイズ性:背景に話したように特殊なフォルダ構造を採用したり秘密情報の暗号化仕組みを利用したりしている関係で、カスタムプラグインを作れるArgo CDが魅力的だった
アーキテクチャ
Argo CDはKubernetesのコントローラーとして実装されています。一つのアプリケーションを表す App
と複数アプリケーションをまとめるための単位 AppProject
カスタムリソースが定義されています。 AppProject
と App
リソースをクラスタに適用することでArgo CDが参照すべきGitリポジトリと管理すべきKubernetesクラスタ、ネームスペース、リソースなどを指定できます。一つのArgo CDインスタンスで複数クラスタを管理できますが、管理のしやすさのためSREグループではクラスタごとにArgo CDをインストールし、そのクラスタだけを管理するようにしています。
AppProjectのサンプル
apiVersion: argoproj.io/v1alpha1 kind: AppProject metadata: name: my-project namespace: argocd spec: clusterResourceWhitelist: # プロジェクト内のアプリケーションが管理できるKubernetesリソース - group: '*' kind: '*' description: My Project destinations: # プロジェクト内のアプリケーションが管理できるクラスタとネームスペース - namespace: 'my-namespace' server: https://kubernetes.default.svc # ローカルクラスタ sourceRepos: - https://github.com/MyOrg/my-repo
Appのサンプル
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-app namespace: argocd spec: destination: namespace: my-namespace server: https://kubernetes.default.svc project: my-project # 上に定義されているプロジェクト source: repoURL: https://github.com/MyOrg/my-repo # 利用すべきgitリポジトリ path: manifests/my-app # マニフェストの保存パス targetRevision: HEAD # gitリポジトリのどのバージョンを同期すべきか
Argo CDがマイクロサービスアーキテクチャを採用していて以下のサービスが動いています:
- API Server:gRPC/REST APIを提供していて、認証、RBAC、アプリケーション管理、同期トリガーなど様々な責任を持っています。
- Repository Server:Gitリポジトリのキャッシュを常に持っていて、Kubernetesマニフェストを返す役割を持っています。
- Application Controller:Argo CD AppとKubernetesリソースを常に監視し比較しています。Gitリポジトリのマニフェストとクラスタの状態のずれを検知し同期しています。
- Dex:SSOのために利用するOpenID Connectプロバイダーです。
- Redis:状態をキャッシュするために利用されています(データが消えても問題なくて、Argo CDが完全にステートレスです)。
- Notifications Controller:Argo CD Notifications関連プロジェクトを使う場合、アプリケーションの同期状態をSlackなどに通知するためのサービスです。
- Commenter Controller Manager:argocd-commenter非正式プロジェクトを利用する場合、アプリケーションの同期状態をマニフェスト変更元PRにコメントとして残してくれるサービスです。
他に関連プロジェクトがたくさんありますので様々なユースケースに対応できます!
- Argo (親プロジェクト)
- Argo Rollouts:Blue-Greenデプロイとカナリアリリースなどを行うため。KubernetesのDeploymentリソースをRolloutカスタムリソースに切り替えるなど調整が必要のためSREグループでは利用していないです。
- Argo CD Image Updater:ECRなどにプッシュされたDockerイメージを自動的にKubernetesにデプロイする仕組みです。現時点では利用していないですが今後利用する可能性があります。
- Awesome Argo
SREの使い方
SREグループではEKSとGKEクラスタをいくつか運用していて、背景で説明した通りにマニフェストリポジトリで特殊なフォルダ構造を採用したり、Helmの共通チャートでマニフェストを生成したり、秘密情報の暗号化仕組みを利用したりしている関係でArgo CDの導入にあたって以下のようなアーキテクチャを採用しました。
- 一つのmonorepoで全てのクラスタのマニフェストを管理する
- クラスタごとにArgo CDをインストールし、Argo CDがインストールされているクラスタのリソースを管理する
- 各クラスタのArgo CDのGUIを使ってクラスタの状況を確認したり差分を閲覧したり同期を実行したりする
- 今まで手動で実行していたcustomcliと同様に、Argo CDがマニフェストリポジトリのフォルダ構造を対応したり秘密情報を復号したりカスタムHelmチャートを利用したりしている
- アプリケーションの同期状態に変化があった場合にSlackに通知される(monorepoに変更が入った時やKubernetesリソースの状況が変わった時や同期された時など)
App設計
適用タイミングと適用者を考慮し、マニフェストを以下の3種類に分けて考えました:
- クラスタワイド権限が必要な機能を提供するマニフェスト
- セキュリティに直接影響するネームスペースごとのマニフェスト
- アプリケーションごとのマニフェスト
- Deployment、ConfigMap、HPAなどアプリケーションを動かすためのリソース
- 変更が比較的に多い
Argo CDではKubernetesリソースの制御を AppProject 単位で行うため、以下の設計に至りました。
- 一つの
cluster
AppProjectでクラスタワイドマニフェストを管理する- 基本機能ごとに App を作る
- SREしか適用権限を持たない
- 一つの
namespaces
AppProjectでセキュリティに直接影響するネームスペースマニフェストを管理する- ネームスペースごとに App を作る
- ネームスペースリソースしか作れないようにする
- SREしか適用権限を持たない
- ネームスペースごとにそのネームスペースのアプリケーションマニフェストを管理するための AppProject
- アプリケーションごとに App を作る
- SRE以外のエンジニアにも権限を付与
そうすることで、クラスタの基本機能とセキュリティに影響するマニフェストの適用をSREまでとし、アプリケーションごとの設定の適用権限をエンジニアにも付与できます。
尚、事情があって現時点ではArgo CDで管理するマニフェストはあくまでも上記の cluster
と namespaces
AppProjectと該当するマニフェストのみにしていて、アプリケーションごとのマニフェストはまだ別の仕組みで管理しています。最終的に全てのマニフェストをArgo CD管理にしていきたいと思います。
適用フロー
以下のような適用フローを採用しました
- エンジニアがPRをmonorepoに対して出す
- SREに通知され、レビュー後PRをマージ
- マージした途端にArgo CDが最新のマニフェストを取得しクラスタの状態と比較し適用すべき差分を抽出しSlackに通知する
- SREがSlack通知をクリックするとArgo CDのGUIが表示され適用される差分を確認でき、同期ボタンを押す
- Argo CDがマニフェストを適用し、元のPRに適用結果をコメントとして残す
- エンジニアに通知される
Slack通知のサンプル:この例では monitoring
ネームスペースが変更されました。通知からArgo CDのGUIとgitリビジョンに直接飛べます。
GUIを開くと、ネームスペースの状況がすぐわかります。今回は kube-state-metrics
Deploymentが同期されていないことがわかります。
差分も簡単に確認できます。
「Sync」ボタンで同期を実行すると、設定が適用されネームスペースが同期されます。
PRを見ると、同期開始と同期成功イベントがコメントされているためエンジニアも同期状況を把握できます。
Argo CD導入前と違ってローカル環境の状態と関係なくなったのと適用スピードも加速したため適用体験がかなり良くなりました。上記のようなフローにすることで、レビューを必須にする流れと監査ログを残す形になったため体験以外にも嬉しいことがありました。
カスタムプラグイン
Argo CDはYAML、kustomize、helmなどマニフェストフォーマットをいくつかサポートしているため様々なユースケースに対応できています。更に、Argo CDのConfig Management Plugins機能を使えばカスタム処理を実行できます。最終的にYAMLマニフェストを生成さえすれば好きな処理を実行できるため柔軟性が高くて素晴らしい機能です。背景に話したようにSREグループでは特殊なフォルダ構造を採用したり秘密情報の暗号化仕組みを利用したりしているためカスタムプラグインを活かして対応しています。
カスタムプラグインを作るには2つのやり方があります:sidecarコンテナとして動かすかConfigMapに登録することでArgo CDコンテナ上で動かすか。ConfigMapのやり方が今後廃止されsidecarを利用しないといけなくなりますが、ConfigMapの方がわかりやすいのでその方法で説明させていただきます。
Argo CDのConfigMapに以下のような設定を追加することでカスタムスクリプトを用意できます。
data: configManagementPlugins: | - name: my-plugin init: # 任意初期化処理 command: ["sample command"] args: ["sample args"] generate: # YAMLマニフェスト生成コマンド command: ["sample command"] args: ["sample args"]
その後、 App
に以下の設定を追加することでプラグインを利用できます
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-app namespace: argocd spec: ... source: repoURL: https://github.com/MyOrg/my-repo path: manifests/my-app targetRevision: HEAD plugin: name: my-plugin # 「my-plugin」カスタムプラグインを利用
参考まで、SREグループで利用しているカスタムプラグインは以下のようなものです(擬似コード)。最後に helm template
でYAMLマニフェストを生成しています。
configManagementPlugins: |- - name: my-plugin lockRepo: true generate: command: ["bash", "-c"] args: - | set -e # 前提 # - クラスタ共通設定とクラスタ専用設定を別々のフォルダに保存している # - helmチャートキャッシュ用のフォルダを設けている # - 秘密情報管理にsopsを利用している (https://lab.mo-t.com/blog/secrets-sopsに参考) # 設定統合 prepare_build_folder # クラスタ共通設定とクラスタ専用設定を統合 # 依存関係取得 synchronize_cache # 依存チャートをキャッシュから取得 fetch_missing_dependencies # キャッシュにないチャートの取得 (helm dep updateベース) synchronize_cache # 取得したチャートをキャッシュに入れる # 秘密情報復号 decrypt_files # 暗号化ファイルを復号 (sopsを利用) trap handle_exit EXIT # 異常終了時も正常終了時も復号したファイルを削除 # マニフェスト生成 values="-f common.yaml -f common.secret.yaml -f dev.yaml -f dev.secret.yaml" helm -n $ARGOCD_APP_NAMESPACE template --release-name $ARGOCD_APP_NAME . $values
導入結果
一言で言うと、Argo CDを導入してから運用の効率が圧倒的に向上しました! 🎉
ローカル環境の状態と関係なくなったため、適用までのリードタイムが大幅に減りました。適用フローがわかりやすくなり、監査ログが取れるようになり、エンジニアとのコミュニケーションコストも減りました。チャートのキャッシュ仕組みを入れたことで同期自体が数十秒から数秒までに減少しました!
また、クラスタやネームスペースを横断した変更をマージした際、影響を受ける全てのクラスタとネームスペースに対してSlack通知が来るようになったため漏れなく適用できるようになりました。GUIを開くことだけでクラスタの状況を一発で分かるようになり非常に便利です。
Argo CD導入前の課題を振り返ると、
- 適用のハードルが高かった:気軽に適用できるようになった ✅
- 適用が遅かった:数秒で適用できるようになった ✅
- 差分が見づらかった:GUIのお陰で量が多くても差分がとても見やすくなった ✅
- 適用状況を把握できなかった:GUIで状況を一発で分かるようになった ✅
- クラスタやネームスペースを横断した変更がやりづらかった:ボタンを押すだけで同期できるため、ネームスペースごとにゆっくり同期することも、並列で一気に同期することも可能になった ✅
- 細かい権限管理ができなかった:まだ完全に活かせていないですがRBAC可能な設計になった ✅
- 自動化が難しかった:自動同期を有効にすれば可能 ✅
狙い通りに様々な改善を達成できました!元々考慮していなかったとても便利な「ドリフト検知」機能も利用できるようになったためその機能を紹介したいと思います。また、Argo CDを1年ぐらい運用した感想と残課題を共有させていただければと思います。
ドリフト検知
Argo CDを導入してから1年弱経っていますが、一番気に入っている機能が元々考慮もしていなかった「ドリフト検知」機能になります。Argo CDがKubernetesクラスタの状態を常に監視してくれているため、gitリポジトリと差分があった際に即時で通知してくれています。そのため、以下のような想定外な変更に気づけて早めに対応できます:
- Kubernetesリソースが手動で変更された場合
- 緊急対応の際の手動変更をgitに反映し忘れた場合
- (あくまでも想像)侵入されてしまって攻撃者がクラスタを変更している場合
- 設定変更の影響範囲が想定よりも広かった場合:特にマルチクラスタ運用の際に一つのクラスタのために入れた変更が他のクラスタにも影響があった場合
運用面
Argo CDはとても安定していて、クラッシュしたことがないです。gitリポジトリにマージした際にCPUがスパイクしていますがそれ以外の時間帯に関してはArgo CDの全てのマイクロサービスを合わせてもCPUコアを1つも使っていなくて、メモリは2GB程度です。
PersistentVolumeを使っていなくて、Redisもあくまでもキャッシュとしてしか利用されていないのでArgo CDが実質ステートレスです。そのため、運用もかなり楽です。
SREグループでは3ヶ月に一回ぐらいのペースでIstioやArgo CDなどKubernetesクラスタにインストールしているコンポーネントをアップデートしています。Argo CDを何回かアップデートしたことがありますがArgo CDの開発がそれなりに進んでいて毎回Helmチャート設定の微調整が発生することがわかりました。欠点はそれぐらいです!
残課題
導入してから運用が楽になりましたが、課題がいくつか残っています。
Argo CDに移行し切っていない
Argo CDで管理しているマニフェストはSREグループが開発・運用しているアプリケーションとSREが管理すべきセキュリティに影響するマニフェストのみになっています(Secret、Ingressなど)。サービス自体のマニフェストをまだ移行していなくて別の仕組みになっています。最終的にArgo CDに完全に移行していきたいと思います。
自動同期を有効にしていない
設定すれば簡単に有効にできますが以下の懸念があります。
- helmを利用している関係で変更をマージしない限り最終的なマニフェスト差分がわからないです。差分を確認してから実行したいため手動実行にしています。helmだけではなくてkustomizeなどでマニフェストを生成するツールを利用する場合同じ懸念があります。今後、マニフェストの差分をPRに自動的にコメントすることなどやり方を考えたいと思います。
- 環境の差を最小限にするためにクラスタ共通設定とクラスタ専用設定を分けて管理しています。クラスタ共通設定を変更した時に、複数クラスタに影響する可能性があって自動同期が怖いです。同様にマニフェストの差分をPRに自動的にコメントすればこの辺りも解決できそうです。
- 同期タイミングをコントールしたい場合があります。複数クラスタとネームスペースを横断した変更を行う際、ダッシュボードでメトリックスを確認しながら設定を順番に同期したい場合があります。その際、自動同期ではなくて手動同期で適用タイミングをコントロールしたいです。自動同期を一時的に無効にしたり、Argo CDの「Sync Window」機能を活かして手動実行用の時間帯を用意したりすることなど対策を考えています。
終わりに
Argo CDを導入することでマニフェストをGitOpsで管理できるようになりました。適用フローが明確になり、適用のハードルが下がり、リードタイムが減少し、全体の効率が圧倒的に向上しました! 🎉
運用観点でも利用観点でもArgo CDがとても楽です。カスタムプラグイン機能のおかげで様々な利用方法が可能になり、セキュリティとコンプライアンスによる制約に柔軟に対応できます。試していない機能がまだたくさんありますので、今後Argo CDをもっと活かしていきたいと思います!
Kubernetesのマニフェスト管理に迷っている方へ、是非Argo CDを検討してみてください!この記事がご参考になれば幸いです!