第7回: Argo CDによるKubernetesへの継続的デリバリ

※本記事は、技術評論社「Software Design」(2023年10月号)に寄稿した連載記事「Google Cloudで実践するSREプラクティス」からの転載です。発行元からの許可を得て掲載しております。

はじめに

前回はRenovateによる依存関係の更新について解説しました。今回はArgo CD1を利用した、Kubernetesへの継続的デリバリ(Continuous Delivery、CD)について紹介します。Argo CDとは何か、なぜ使うのか、基本的な機能やキャディでどのように活用しているかを紹介します(図1)。

▼図1 CADDiスタックにおける今回の位置付け

Argo CDとは

Argo CDはKubernetesへの継続的デリバリを行うツールです。Gitリポジトリをソースとして継続的デリバリを行う手法をGitOpsと呼びます2。Argo CDはKubernetesへのデプロイをGitOpsに沿って行います。 Kubernetesへのデプロイは、デプロイ内容を記述したマニフェストファイルを、Kubernetes APIやkubectlコマンドに指定して実施します。 この作業は、ファイル数が増えると煩雑になるほか、ファイルの変更を追従してKubernetesに反映することが困難になります。 Argo CDはGitリポジトリにあるマニフェストファイルを取得し、Kubernetesへのマニフェストファイルの適用状況を可視化します。また、差分検知や履歴管理、ロールバック、自動反映といった機能も備えています。権限制御可能なWeb UI があるため、Argo CDを通してKubernetesにデプロイされているサービスの構成を把握する、管理者のみがArgo CD経由でデプロイ操作をするといった操作もできます。

なぜArgo CDか

Argo CDは豊富な機能を提供していますが、その中でも筆者らがArgo CDを採用している最大の理由は、リッチなWeb UIがあるからです。たかがUIされどUIです。百聞は一見にしかずですので、まだ触ったことがなければぜひ公式のデモ環境3を体験してみてください。 DevOps実現のため、開発者がkubectlコマンド使いこなすことはすばらしいことです。しかし、チーム内すべての開発者がそれを習得する必要はないと考えています。Web UIでは、簡単にデプロイしたりリソースの状態を参照したりできます。それによって開発者が、プロダクト(サービス)の本質的な価値向上のためにより多くの時間を使えるようになります。 また、キャディのArgo CD導入以前(2020年ごろ)のCDは、Push型GitOps4を採用しており、セキュリティやデプロイ単位の柔軟性・属人性といった面で次のような課題がありました。これらの課題の解消にもArgo CDは役立っています。

  • Google Kubernetes Engine(GKE)のPrivate Cluster5に対して、デプロイごとに CD Server側のIPを承認済みネットワーク6に追加する必要がある
  • CD Server側で、機密情報をDecryptしてマニフェストをデプロイする必要がある
  • 特定のプロダクトの単位でデプロイができない(一括で複数のプロダクトリソースをClusterに対してすべてまとめてデプロイしていた)
  • デプロイスクリプトを作り込んであり、作成者以外が簡単に変更できない

デプロイの流れ

図2は、Argo CDによるデプロイの流れを抽象化したものです。Gitリポジトリの変更を起点として、Argo CDがその変更を検知し、次の流れでデプロイを実行します。

①Argo CDがPollingによりGitリポジトリからKubernetesマニフェストを取得、差分検知する
②Argo CDが指定された差分をデプロイする
③開発者がWeb UI上でデプロイ結果を確認する

また、これは自動同期の設定を有効にしている場合の例です。自動同期の設定を無効にしておくと、①と②のステップの間で、開発者がWebUI上で差分を確認しながら手動で同期処理をトリガーできます。

▼図2 Argo CDによるデプロイの流れ

image

Argo CD のProjectとApplication

Argo CD を構成する重要な要素として、ProjectとApplicationがあります。KubernetesのCustom Resource Definition では、「AppProject」と「Application」という名前でそれぞれ定義されています。 図3は、ProjectとApplicationの構成例と簡単なデプロイの関係性を表したものです。

▼図3 ProjectとApplication image

Applicationのマニフェストには、デプロイ対象Kubernetesマニフェスト群(以降、K8sマニフェスト)の場所を定義します(リスト1)。 このApplicationがArgo CDによるデプロイの最小単位となります。 より具体的には、次のような情報を指定します。

  • デプロイ先のClusterやNamespace
  • 所属するProject
  • デプロイ対象K8sマニフェスト群の場所やRevision

GitリポジトリとCluster間で差分が発生したときの同期ポリシーデプロイ対象K8sマニフェスト群の指定は、デフォルトでは次のものに対応しています。

プラグインを別途入れることによって、そのほかのConfig管理ツールの利用も可能です。 また、Applicationは必ず1つのProjectにひも付きます。デフォルトでは、default Projectが用意されており、指定が可能となっていますが、特別な事情がない限り個別にProjectを作成することをお勧めします。

▼リスト1 application-example.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: example
  namespace: argocd
spec:
  destination:
    namespace: example-namespace
    server: https://kubernetes.default.svc
  project: example-project
  source:
    path: applications/example/overlays/dev
    repoURL: https://github.com/caddijp/example-cluster-config.git
    targetRevision: main
  syncPolicy:
    automated: {}

Projectは、Applicationを束ねるオブジェクトです(リスト2)。このマニフェストに、一定の制限を定義することで統制を効かせやすくなります。具体的には次のようなものです。

  • デプロイできるGitリポジトリの制限
  • デプロイ先のClusterやNamespaceの制限
  • デプロイできるKubernetesリソースの種類の制限
  • RBACで利用するProjectにひも付くロールの定義

RBAC(後述)の設定で、特定のProjectをそのオーナーとなる開発チームへ割り当てることで、誰が何を管理しているかを明確にしつつ、必要最小限の権限を付与できます。

▼リスト2 project-example.yaml

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: example-project
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  description: Admin Project
  sourceRepos:
    - '*'
  destinations:
    - namespace: example-namespace
      server: https://kubernetes.default.svc
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
  roles: []

キャディで利用している構成

図4は、キャディで利用している構成の概要図です。開発者を起点としたデプロイの流れは次のようになります。

①:開発者がGitHubのPull requestをマージもしくはRelease Tagを作成する
②:GitHub Actionsの指定されたWorkflowが起動する
③:Imageを作成しArtifact Registryにプッシュする
④:K8sマニフェストリポジトリの対象のImage Tagを書き換える
⑤:Argo CDが Polling によりGitリポジトリからK8sマニフェストを取得、差分検知する
⑥:Argo CDが指定された差分をデプロイする
⑦:Argo CDが指定されたSlack Channelに同期状態の変更を通知する

図4では表現できていないところを含め、詳細を解説していきます。

▼図4 キャディで利用している構成 image

Cluster構成

筆者らは、マルチテナント方式7でArgo CDを構築し、同じCluster上で複数のプロダクト(サービス)を運用しています。環境はCluster単位で分離し、Development/Staging/Productionの3つです。 また、Argo CDは仕様上1つのArgo CD環境で複数のClusterを管理できますが、筆者らClusterごとにArgo CDを構築するようにしています。おもな理由は3つです。

1つめは「単一障害点(SPOF)になるのを避ける」ためです。仮に1つのArgo CD環境ですべてのClusterを管理している場合、そのArgo CD環境が動かなくなったときにすべてのデリバリが止まってしまうリスクがあります。ClusterごとにArgo CDを構築しておくことで、依存関係のない独立したClusterとなり、そのリスクを最小化できます。

2つ目は「アップグレードがしやすい」からです。アップグレードの重要性は前回の連載で触れているため省略します。Argo CDは開発が活発で、リリースサイクルが早いです。仮に、アップグレード時に移行ミスがあった場合、デリバリが 止まってしまうリスクがあります。 Development環境のClusterからアップグレードを進め、適用後一定期間様子を見るなど、リスクを最小化するためのアップグレード戦略を立てやすくなります。

3つ目は「Kubernetes APIを外部に公開する必要がなくなる」からです。前述のとおり独立したClusterとなるため、外部にAPIを公開する必要がなく、Clusterをより安全に運用できます。

リポジトリ構成

GitHubリポジトリは次のような構成となっています。

K8sマニフェストはアプリケーション側のリポジトリでも管理できます。しかし筆者らは、それぞれの責務やライフサイクルが異なるため、Argo CDを採用する前から意図的にリポジトリを分離しています。ポイントは、公式ドキュメントのベストプラクティス8に記載されています。 K8sマニフェストとアプリケーションコードのリポジトリを分離する利点は次のとおりです。

  • それぞれのライフサイクルに依存しない継続的インテグレーションやデリバリを構築できる
  • 変更履歴(監査ログ)をきれいに保てる
  • それぞれのリポジトリでアクセス権や変更権限を分離できる

また、リポジトリを分離しない場合は次のような課題が残ります。

ブランチ戦略

図5はブランチ戦略を簡単に表現した図です。 アプリケーションコードのリポジトリK8sマニフェストリポジトリ、どちらもmainブランチのみを利用しています。K8sマニフェストリポジトリ上では、通常のマニフェストの変更はPull requestを作成する運用になっています。アプリケーションのデプロイパイプラインではImage TagのみをGitHub Actionsで自動的に書き換えています。

▼図5 ブランチ戦略

Development環境への反映

Development環境へ反映の流れは次のようになります。

①アプリケーションコードのリポジトリでPull requestをマージする
GitHub Actionsでテスト、Imageの作成後、K8sマニフェストリポジトリのWorkflowをトリガーする
K8sマニフェストリポジトリのWorkflowでDevelopment環境用のK8sマニフェストのImage Tagを書き換える

Image Tagの書き換えはGitHub Actionsのrepository_dispatch9を利用してK8sマニフェストリポジトリ側で実行しています。アプリケーションコードのリポジトリ側のWorkflowで書き換えると、コンフリクトが発生したり、余計な権限を持たせたりしないといけないからです。

Staging/Production環境への反映

Staging/Production環境へ反映の流れは次のようになります。基本的な流れはDevelopment環境の場合と同様ですが、起点と2環境ぶん同時にImage Tag書き換えをするところが異なります。

①アプリケーションコードのリポジトリでRelease Tagを作成する
GitHub Actionsでテスト、Imageの作成後、K8sマニフェストリポジトリのWorkflowをトリガーする
K8sマニフェストリポジトリのWorkflowでStaging/Production環境用のK8sマニフェストのImage Tagを書き換える

Production環境だけArgo CDの自動同期設定をOFFにしており、Staging環境での動作確認後、開発チームごとに任意のタイミングでWebUI上からデプロイやロールバックをする運用となっています。 同じCommit HashでImageがすでに作成済みのときは、Image作成処理をSkipすることでリードタイムを短縮する工夫をしています。Development 環境で検証済みの ImageをStaging/Production環境でも使うことは、アプリケーションコードの同一性担保にも役立ちます。

Argo CDの設定管理

Argo CDは、Kubernetesへデプロイするリソースを宣言的に管理します。開発者が追加するK8sマニフェストだけでなく、Argo CD本体やその設定も宣言的に管理10できます。 Argo CDをClusterへインストール後、Argo CDのProjectやApplicationをWeb UIから追加できますが、筆者らはそれらの設定もコード化しています。Argo CDの本体や設定をコード化するおもな理由は、次のようなことを実現するためです。

  • 再現性
  • 再利用性
  • 属人性の排除
  • 静的解析による統制

K8sマニフェストリポジトリでは、Kustomizeを利用し、ディレクトリ構成は下記のようになっています。

▼リスト3 K8sマニフェストリポジトリディレクトリ構成

applications/
├── product1/
│   ├── base/
│   │   ├── ui/
│   │   │   └── ...
│   │   ├── bff/
│   │   │   ├── deployment.yaml
│   │   │   ├── secret.yaml
│   │   │   ├── service.yaml
│   │   │   └── config.yaml
│   │   └── kustomization.yaml
│   └── overlays/
│       ├── dev
│       │   └── ...
│       │   └── kustomization.yaml
│       ├── stg
│       └── prod
├── product2
└── ...
argocd/
├── base/
│   ├── argocd-cm.yaml
│   ├── argocd-notifications-cm.yaml
│   ├── ...
│   └── kustomization.yaml
└── overlays/
    ├── dev/
    │   ├── pj-admin/
    │   │   ├── app-argocd.yaml
    │   │   ├── ...
    │   │   ├── helm-eso.yaml
    │   │   ├── helm-eso.values.yaml    
    │   │   └── project.yaml
    │   ├── pj-sample1/
    │   │   ├── app-product1.yaml
    │   │   └── app-product2.yaml
    │   ├── ...    
    │   ├── argocd-rbac-cm.yaml
    │   └── kustomization.yaml
    ├── stg
    └── prod

applicationsディレクトリでは、Argo CD Applicationから指定するK8sマニフェストを管理します。ここでは、プロダクト(サービス)ごとディレクトリを作成し、デプロイしたいK8sマニフェスト群の最小単位をまとめています。このK8sマニフェスト群の最小単位が、どのArgo CD Application/Project や Namespaceに所属するかは関心事として切り離されているため、意図的にフラットなディレクトリ構成としています。

argocdディレクトリでは、Argo CDの本体や設定を管理します。初回インストールは、KustomizeでArgo CDのリモートリソース指定し11K8sマニフェストを作成し、kubectlコマンドで反映します。そのK8sマニフェスト自体をapp-argocd.yaml(リスト4)で定義した1つのArgo CD Applicationとして、インストールされたArgo CDで管理します。

▼リスト4 app-argocd.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-argocd
  namespace: argocd
spec:
  destination:
    namespace: argocd
    server: https://kubernetes.default.svc
  project: pj-admin
  source:
    path: argocd/overlays/dev
    repoURL: https://github.com/caddijp/example-cluster-config.git
    targetRevision: main
  syncPolicy:

RBAC

Argo CDの認証にはさまざまな方法がとれますが、筆者らキャディではGitHub認証を使用しています。GitHubアカウントにひも付いているGitHub Team12とArgo CDのRole13をひも付けて権限を管理しています。 リソースとアクションを組み合わせることで、要件に合わせて柔軟に権限を定義し、ユーザーグループ(GitHub Team)へのひも付けができます。Argo CD Applicationに対して個別に権限付与するより、Argo CD Project単位で権限付与たほうが圧倒的に楽ですので、基本的に開発チーム単位でArgo CD Projectを定義するのがお勧めです。 しかし、キャディはスタートアップという特性上、事業や開発チームの変更頻度が高く、その運用だと開発チームの実態と Argo CD Projectがすぐに一致しなくなります。そのため、執筆時点では、プロダクト(サービス)や類似プロダクト群ごとにArgo CD Projectを作成するケースが多くなっています。

Secret管理

GKE内で機密情報(Secret)を安全かつ簡単に管理するために、External Secrets14を利用しています。機密情報の実体はGoogle CloudのSecret Manager15で管理していますExternal Secretsを利用することで、各KubernetesリソースからはKubernetes Secretを通して透過的に機密情報にアクセスできます。 また、Workload Identity16を利用し、External SecretsのKubernetesサービスアカウントとGoogle Cloudのサービスアカウントをひも付けることができます。これによって、Secret Managerを参照するための鍵情報(サービスアカウントキー)をGKE内に持たせず運用できています。 ちなみに、Google Cloudのサービスアカウントのベストプラクティス17を参考にして、External Secret以外のリソースも基本的にサービスアカウントを分離しWorkload Identityを利用しています。Google Cloudサービスアカウントの鍵情報を管理する必要がなくなることにより、両方のサービスアカウントの分離作業が楽になります。それは、サービスアカウントの権限を最小化し、トレーサビリティを向上させることも楽になるということです。

Slackへの通知

K8sマニフェストの同期状態をSlackへ通知18させて、継続的デリバリの状態を把握できるようにしています。Argo CDのv2.3からArgo CD Notificationsが内包19されるようになり、より簡単に通知の設定ができます。通知先は、Argo CD ProjectやArgo CD Applicationのannotationsでイベントごとに定義します。

おわりに

今回はArgo CDの概要とキャディでの採用理由、また基本的な機能や継続的デリバリの構築事例を紹介しました。キャディでは、2021年の初めからArgo CDへ移行し、今ではプロダクト(サービス)を構築、運用していくための欠かせないツールになっています。筆者自身、執筆していく中で、Argo CDがさまざまな運用の課題を解決してくれるすばらしいツールだとあらためて感じました。 来月はサービスメッシュについて紹介する予定です。お楽しみに。