MoTLab -GO Inc. Engineering Blog-MoTLab -GO Inc. Engineering Blog-

Argo CDで運用効率アップ!

SRE
February 22, 2023

こんにちは、技術戦略部 SREグループのカンタンです!

Kubernetesクラスタを運用するとマニフェストの適用方法が気になる方が多いかと思います。 kubectlを使ってYAMLファイルをそのまま適用したり、Kustomizeファイルを適用したり、Helmチャートを適用したり、Argo CDFlux のようなCDツールを利用したりするなどやり方が色々あります。

約一年前からSREグループで管理しているマニフェストの適用方法をArgo CDに切り替えました。適用スピードが加速し、クラスタの状況が把握しやすくなり、セキュリティレベルが向上し、運用の効率が圧倒的に上がりました。とても良くできているツールのため、Argo CDの魅力とSREグループの使い方を紹介させていただいてご参考になれば幸いです!


背景

SREグループが管理しているEKSGKEクラスタのマニフェストを一つの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 で設定をクラスタに適用していました。

An image from Notion

そのやり方に問題がいくつかありました:

  • 適用のハードルが高かった:ローカル環境から手動でコマンドを実行する必要があったため、例えば別作業中にマニフェストを変更する必要になった場合、ローカル環境をまず綺麗にする必要がありました。
  • 適用が遅かった: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だけでクラスタの状態を確認、変更、リバートできます!

An image from Notion

基本動作として、クラスタの状態を常に監視つつ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に権限を付与する必要がなくなってセキュリティレベル向上に繋がります!

選定理由

FluxSpinnakerなどArgo CD以外にGitOps系のCDツールがいくつかありますが、以下の理由でArgo CDを選定しました:

  • 成熟度が高い:CNCFプロジェクトのgraduatedステータスを達成している
  • 機能が充実している:RBAC、SSO、部分的同期、同期ウィンドウなどEnterpriseグレードの機能も提供している
  • GUIが使いやすい:クラスタの状況を一発でわかるし、差分も見やすいし、全体的にGUIが自然
  • 軽量性:CPUとメモリリソースをそこまで使用しない
  • 「単純」な同期システム:いい意味でArgo CDはKubernetesマニフェストの同期しかしない単純なシステム。ビルドパイプラインなど必要としていない機能に混乱させられていなくて、やってほしいことをきちんとやってくれている
  • カスタマイズ性:背景に話したように特殊なフォルダ構造を採用したり秘密情報の暗号化仕組みを利用したりしている関係で、カスタムプラグインを作れるArgo CDが魅力的だった

アーキテクチャ

Argo CDはKubernetesのコントローラーとして実装されています。一つのアプリケーションを表す App と複数アプリケーションをまとめるための単位 AppProject カスタムリソースが定義されています。 AppProjectApp リソースをクラスタに適用することで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にコメントとして残してくれるサービスです。
An image from Notion

他に関連プロジェクトがたくさんありますので様々なユースケースに対応できます!

  • 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リソースの状況が変わった時や同期された時など)
An image from Notion

App設計

適用タイミングと適用者を考慮し、マニフェストを以下の3種類に分けて考えました:

  • クラスタワイド権限が必要な機能を提供するマニフェスト
    • ClusterRoleマニフェスト
    • クラスタの基本機能として動かす強い権限を持つアプリケーション(例えばArgo CD自体、Istioサービスメッシュなど)
    • クラスタの管理者しか適用しない
  • セキュリティに直接影響するネームスペースごとのマニフェスト
    • IngressとService:外部ロードバランサーでサービスを外に公開できてしまう
    • Secret:秘密情報にアクセスできてしまう
    • 変更頻度が比較的に少ない
  • アプリケーションごとのマニフェスト
    • Deployment、ConfigMap、HPAなどアプリケーションを動かすためのリソース
    • 変更が比較的に多い

Argo CDではKubernetesリソースの制御を AppProject 単位で行うため、以下の設計に至りました。

  • 一つの cluster AppProjectでクラスタワイドマニフェストを管理する
    • 基本機能ごとに App を作る
    • SREしか適用権限を持たない
  • 一つの namespaces AppProjectでセキュリティに直接影響するネームスペースマニフェストを管理する
    • ネームスペースごとに App を作る
    • ネームスペースリソースしか作れないようにする
    • SREしか適用権限を持たない
  • ネームスペースごとにそのネームスペースのアプリケーションマニフェストを管理するための AppProject
    • アプリケーションごとに App を作る
    • SRE以外のエンジニアにも権限を付与
An image from Notion

そうすることで、クラスタの基本機能とセキュリティに影響するマニフェストの適用をSREまでとし、アプリケーションごとの設定の適用権限をエンジニアにも付与できます。

尚、事情があって現時点ではArgo CDで管理するマニフェストはあくまでも上記の clusternamespaces AppProjectと該当するマニフェストのみにしていて、アプリケーションごとのマニフェストはまだ別の仕組みで管理しています。最終的に全てのマニフェストをArgo CD管理にしていきたいと思います。

適用フロー

以下のような適用フローを採用しました

  • エンジニアがPRをmonorepoに対して出す
  • SREに通知され、レビュー後PRをマージ
  • マージした途端にArgo CDが最新のマニフェストを取得しクラスタの状態と比較し適用すべき差分を抽出しSlackに通知する
  • SREがSlack通知をクリックするとArgo CDのGUIが表示され適用される差分を確認でき、同期ボタンを押す
  • Argo CDがマニフェストを適用し、元のPRに適用結果をコメントとして残す
  • エンジニアに通知される

An image from Notion

Slack通知のサンプル:この例では monitoring ネームスペースが変更されました。通知からArgo CDのGUIとgitリビジョンに直接飛べます。

An image from Notion

GUIを開くと、ネームスペースの状況がすぐわかります。今回は kube-state-metrics Deploymentが同期されていないことがわかります。

An image from Notion

差分も簡単に確認できます。

An image from Notion

「Sync」ボタンで同期を実行すると、設定が適用されネームスペースが同期されます。

An image from Notion

PRを見ると、同期開始と同期成功イベントがコメントされているためエンジニアも同期状況を把握できます。

An image from Notion

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を検討してみてください!この記事がご参考になれば幸いです!


We're Hiring!

📢
Mobility Technologies ではともに働くエンジニアを募集しています。

興味のある方は 採用ページ も見ていただけると嬉しいです。

Twitter @goinc_techtalk のフォローもよろしくお願いします!