MLOpsマルチテナントクラスタへのArgo CDの導入と運用

OGP

はじめに

こんにちは。ML・データ部MLOpsブロックの築山(@2kyym)です。
MLOpsブロックでは2022年の上期からArgo CDの導入に着手しました。本記事ではArgo CDの導入を検討した背景から導入のメリット、また導入における公式マニフェストへの変更点や、運用において必須である認証や権限管理など、具体的な手順についてご紹介します。少しでもArgo CDの導入を検討している方の助けになれば幸いです。

またArgo CDを導入するきっかけとなった、複数運用していたKubernetesクラスタを1つに集約するマルチテナントクラスタへの移行についても触れます。マルチテナントクラスタの設計や具体的な移行作業については述べると長くなってしまうため、詳細については改めて別の記事にてご紹介できればと思います。
Argo CDについては、昨年の計測SREブロックの記事でも触れられていますので是非こちらもご参照ください。

techblog.zozo.com

本記事ではArgo CD自体や導入のメリットについては簡単な紹介にとどめ、導入時の細かい作業や、導入後チーム運用に乗せるため必要な作業を中心として説明します。
また、本記事ではSSO(Single Sign-On)ログインと権限管理に弊社で利用しているAzure Active Directory(Azure AD)を使用することを前提とします。しかしArgo CDではAzure AD以外にもOktaなど別の認証基盤も数多くサポートしており、大まかな手順は変わらないはずです。また、出来る限りAzure ADに特化した記述を避けて説明します。

目次

背景

まずArgo CDについて述べる前に、背景としてMLOpsブロックにおけるインフラの運用課題と、それを解決するためのマルチテナントクラスタについて説明します。
なお前提として、MLOpsブロックではパブリッククラウドとしてGoogle Cloudを使用しており、インフラにはGoogle Kubernetes Engine(GKE)を使用しています。

従来の課題

MLOpsブロックでは、ZOZOTOWNWEARに機械学習系の機能(推薦、検索、etc...)を提供するサービスを幅広く開発・運用しています。これらサービスのAPI群や一部のバッチは先述の通りGKEクラスタ上にデプロイされています。
また、これらのワークロードは従来、全て1つのクラスタにデプロイされていたわけではありません。推薦や検索、類似画像検索などといった大まかなくくりでGoogle Cloudプロジェクト自体が分かれており、そのため各プロジェクトごとのGKEクラスタへ別々にデプロイされていました。
特に類似画像検索とWEAR向けの機能はサービスあたり1つのGoogle Cloudプロジェクト、すなわちGKEクラスタで運用していました。そのため、サービス数が増えるにしたがってMLOpsブロックで管理するクラスタ数がどんどん増えていきました。

マルチテナントクラスタ移行前はサービス別にGoogle CloudプロジェクトとGKEクラスタが分かれていた

管理するGKEクラスタの増加に伴い、開発運用において次の課題が生まれました。

  • 定期的に実施する必要があるKubernetesのバージョンアップ業務の工数が増加する
  • 新規サービスのインフラを構築する際に、新規のGKEクラスタを構築しなければならない手間が発生する
  • TerraformでGKE関連の冗長な記述が増加する、また共通の変更を加える際にはそれら全てに対して変更を加える手間が発生する

1点目については、GKE Autopilotなどのマネージドサービスを活用すればバージョンアップの自動化が可能ですが、MLOpsブロックでは安定性の観点からバージョンアップを手動で実施しています。そのため、クラスタの数が増加するとこちらの工数も単調増加してしまいます。
2点目については、上記の運用ですとサービスごとにGKEクラスタを分けているため、軽量なAPIが1つのみ必要な場合であっても新規のクラスタを構築する必要があります。 3点目については、我々はGoogle Cloud上のリソースを管理するためにTerraformを利用していますが、サービス別にクラスタを構築するとほぼ同じ内容のTerraformリソースが様々なリポジトリに記述されてしまっていました。

MLOpsマルチテナントクラスタについて

ここまで述べた開発運用における課題を解決するために、MLOpsブロックで管理するサービスのKubernetesワークロードを1つのGKEクラスタに集約する方針となりました。
マルチテナントクラスタの設計や移行の具体的な手順の検討については、詳細に説明するとそれだけで別の記事が書けてしまうボリュームとなるため、またの機会にぜひお伝えできればと思います。
ここでは簡単に触れるだけに留めますが、サービスごとにNamespaceとノードプールを分けて全てのサービスを1つのクラスタにデプロイするという点以外は、基本的に移行前と同じ構成で動作するように移行を進めました。
もちろん、開発運用・Staging環境・QA環境・本番環境はGoogle Cloudプロジェクト自体を分けて別のクラスタを用意しています。

マルチテナントクラスタではすべてのワークロードが集約され、namespaceを区切って運用する

このマルチテナントクラスタへの移行によって、先程述べた運用課題についてはある程度解決する見通しが立ちました。一方で、以前より感じていた次の運用課題は未だに残っていました。

  • 運用しているサービスの数が非常に多く、監視は適切に行っているものの、それらのデプロイ状況やHealthyかどうかの状況を一覧する術がない
  • 特定のサービスに紐づくKubernetesリソース(Deployment, Ingress, Service Account, Secretなど)の状況を確認する術がない
  • GitHub Actions(GHA)のJob内でkubectl applyを実行することでデプロイを行っており、デプロイ状況がリポジトリのDesired Stateからズレた際に検知・修復できない

マルチテナントクラスタへの移行に着手するタイミングで、こういった課題を解決するためにMLOpsブロックではArgo CDの導入を検討することにしました。
以下の章では、Argo CDについてごく簡単な紹介をした後に、導入して感じたメリットと具体的な導入手順について述べます。また、Argo CDでのデプロイを運用に乗せるにあたって必須になるであろう認証と権限管理についても出来る限り具体的に説明します。

Argo CDについて

まずはArgo CDについて簡単にご紹介します。Argo CDはKubernetes向けのCD(Continuous Delivery)ツールです。
仕組みとしては、Kubernetesクラスタ上のArgo CDが管理対象のサービス(Argo CDではApplicationという単位で管理)のマニフェストが存在するリポジトリを監視します。そしてリポジトリにおいてマニフェストの変更を検知すると、その状態に合わせて自動で同期するようデプロイが行われます。
また、管理対象のApplicationごとに同期の対象(Desired State)とするリポジトリとブランチを設定できます。そのため、例えばStaging環境のArgo CDではmainブランチを、本番環境のArgo CDではreleaseブランチをターゲットとすることで環境ごとのデプロイが実施できます。
Argo CDの公式ドキュメントでは宣言的(declarative)であることがアピールされています。具体的には、Argo CD自体の設定はもちろん、Argo CDによって管理するアプリケーションの設定まで全てをKubernetesのカスタムリソースとしてマニフェストに記載できます。
以下に、Argo CDによるデプロイフローの簡単な概念図をCloud Native Computing Foundation(CNCF)のブログ記事より引用します。

Argo CDによるデプロイフロー

Argo CD導入によるデプロイフローの変化

従来のGHAによるデプロイと比較して、Argo CDの導入によってデプロイフローがどう変化したか、ここで簡単に図を交えて説明します。図の左側は従来のGHAによるデプロイを、右側はArgo CDによるデプロイを示しています。
これまではGHAのJobから、つまりクラスタの外部から kubectl apply コマンドによってデプロイを行っていました。デプロイのタイミングはPull Requestがmain/qa/releaseブランチにマージされた際であり、それ以外のタイミングでは実施されません。
Argo CDの導入後は、クラスタ内部のArgo CD Controllerが同期の対象(Desired State)とするブランチを監視し、差分を検知したタイミングでデプロイが実施されます。例えばPull Requestがマージされた際はDesired Stateに差分が発生するのでデプロイが実施されます。後述する設定によって、何らかの問題や手動変更によってDesired Stateからズレた状態になってしまった際も自動デプロイを行うことが可能です。

GHAによってデプロイするフローから、Argo CDがリポジトリを監視して同期を行うデプロイフローに変化した

Argo CDの導入メリット

導入のきっかけは前章で述べた通り、マルチテナントクラスタへ移行するのに合わせて、運用課題を解決するツールを導入できればタイミングが良いという部分が大きいです。それに加えて、動作検証にあたって感じたメリットを、前章で述べた運用課題の裏返しになりますが以下に列挙します。

  • GUIが使いやすく、一覧性も高いため、数多の運用サービスのデプロイ状況やHealthyかどうかが簡単に確認できる
  • 特定のサービス、すなわちApplicationに紐づくすべてのKubernetesリソースとその状況が簡単に確認できる
  • 通知(Argo CD Notifications)や自動修復(Self Healing)機能によって、デプロイ状況がDesired Stateから乖離してしまった場合のアクションが取れる
  • 自動カナリアリリースのためにArgo Rolloutsの導入を検討していたが、同じArgo FamilyであるArgo CDを導入することでスマートに管理できる

まず1点目と2点目についてですが、マルチテナントクラスタへ集約することで、GKEのコンソールやkubectlコマンドによるサービス状況の確認がそもそも以前よりも行いやすくなっていました。Argo CDを導入したことで、それに加えて運用する全てのサービス、すなわちApplicationのデプロイ状況と、それぞれに紐づくDeployment以外のKubernetesリソース状況も簡単に確認できるようになりました。
3点目については、GHAによるデプロイではカバーできなかったDesired Stateから乖離した場合の対応が可能となりました。開発過程での動作確認を頻繁に行わない本番環境においては、自動修復(Self Healing)機能を有効にしています。こうすることで定期的にデプロイ状況とDesired Stateを照合し、差分が生じた場合は自動で再同期を行えます。
最後に4点目について、今回の記事では深く触れませんが、自動カナリアリリース実現のために導入を検討していたArgo Rolloutsのカスタムリソースをスマートに取り扱えるという利点もありました。どちらもArgo Family内のツールであるため互換性が高く、例えば自動カナリアリリースにおける切り戻し(Abort)といったアクションがArgo CDのGUIやCLIを通して簡単に実施が可能です。

このような利点を踏まえて動作検証を進め、マルチテナントクラスタへの移行と同時にArgo CDの導入を実施することになりました。

導入手順

ここからは具体的なArgo CDの導入手順について説明します。
導入にあたって全ての事柄について説明すると記事が長くなってしまうため、公式ドキュメントを一見して分かりやすい部分に関しては説明を省きます。具体的にはApplicationやAppProjectといった基本的なカスタムリソースの概念やそのマニフェストの書き方については、特筆すべき点がないため本記事では述べません。本章では次の内容について述べます。

  • マルチテナントクラスタに導入し、運用に乗せるにあたって公式マニフェストにパッチを当てた(カスタマイズした)内容
  • セキュアかつ使いやすいArgo CD環境を用意するための認証とSSO(Single Sign-On)の導入について
  • MLOpsブロック以外のチームのメンバーにArgo CDを使ってもらう際のきめ細かい権限管理について

次の節ではそれぞれの内容について、背景から導入手順までを実際のコードに則って説明します。

公式マニフェストにパッチを当てる

導入チームのインフラ運用次第ではありますが、公式マニフェストを導入しただけでは動作せず、一部マニフェストにパッチを当てる必要が出てくるケースが多いと考えます。今回の導入にあたっては公式マニフェストに幾つかパッチを当てる必要があったため、出来る限り実際のコードに則って説明を進めます。
我々の運用では、公式のマニフェストに含まれる次の3つのマニフェストにパッチを当てています。なお、IAP認証やSSOログイン、権限管理に関連したパッチについては後述します。

  • Deployment, StatefulSet群が定義されている argocd-repo-server-deploy.yaml
    • Argo CDのKubernetesワークロードが、マルチテナントクラスタのArgo CD専用のノードプールで起動するようにするためのパッチ
    • 各ワークロードごとに適切なResource Limitsを設定するためのパッチ
  • ConfigMap群が定義されている argocd-cm.yaml
    • デプロイ(同期)の結果をSlackに通知するためのパッチ
    • 本番環境のみ、事故防止のためにWeb UIの見た目をカスタマイズするためのパッチ
    • デフォルトで有効になっている、管理者向けのIDとパスワードによるArgo CDへのログインを無効化するためのパッチ
    • (後述)IAP認証、SSOログイン、権限管理に関連するパッチ
  • Service群が定義されている argocd-server-service.yaml
    • argocd-server をGoogle CloudのBackend Serviceと紐付けるためのパッチ

ここでは argocd-repo-server-deploy.yamlargocd-cm.yaml にフォーカスし、実際のパッチの例を示しつつ説明します。

まず、 argocd-repo-server-deploy.yaml に対して適用するパッチを以下に示します。ここではマニフェストの例として argocd-applicationset-controller を挙げています。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: argocd-applicationset-controller
spec:
  template:
    spec:
      containers:
      - name: argocd-applicationset-controller 
        resources:
          requests:
            cpu: 100m
            memory: 150Mi
          limits:
            cpu: 500m
            memory: 300Mi
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: cloud.google.com/gke-nodepool
                operator: In
                values:
                - "argocd-xxxx-v1"
      tolerations:
      - key: "dedicated"
        operator: "Equal"
        value: "argocd-xxxx-v1"
        effect: "NoSchedule"

リソースの割り当てに関しては特筆することはありませんが、ワークロードごとに適切な値を設定しています。
所望のノードへのスケジューリングに関しては、 nodeAffinitytolerations の両方を設定しています。MLOpsブロックではサービスごとにノードプールを分けて運用しており、Argo CDに関しても専用のノードプールを設けています。また、各ノードプールには不要なワークロードがスケジューリングされないよう、 NoSchedule taintを付与しています。そのため、Argo CD向けのノードで起動させるために nodeAffinity だけでなくノードプール名をvalueに指定した tolerations が必要です。

次に、 argocd-cm.yaml に対して適用するパッチを以下に示します。こちらには argocd-cm , argocd-rbac-cm , argocd-notifications-cm といった複数のConfigMapマニフェストが定義されています。本来同じファイルに記載しますが、ここでは分かりやすさのため分けて記載します。
先述の通りIAP認証、SSOログイン、権限管理に関しては後述するため、ここでは該当する部分の記述を省いています。

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
data:
  admin.enabled: "false"
  ui.cssurl: "./custom/my-styles.css"
  my-styles.css: |
    .nav-bar {
      background: red;
    }

まずは argocd-cm です。admin.enabled をfalseに指定することで、デフォルトで発行される管理者向けのIDとパスワードによるログインが無効化されます。この設定によって、Argo CDのWeb UIにおけるログイン画面でもSSOによるログインしか表示されなくなります。
同じくカスタムCSSを使用するための設定も記述しています。本番環境のArgo CDを操作する際、本番環境だと認識せずに意図しない操作をすることを防ぐため、UI上のサイドバーを警告色にするようカスタマイズしています。
なお my-styles.css に関しては argocd-repo-server-deploy.yaml で別途 /shared/app/custom にVolumeをマウントしています。
次に示す画像は1枚目が開発環境のUIであり、2枚目が本番環境のUIです。

開発環境のUIではサイドバーがデフォルトの色となっている

本番環境のUIではサイドバーが警告色となっている

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
data:
  policy.default: role:readonly

argocd-rbac-cm では、ロールが割り当てられていないユーザーがログインした際に、読み取り権限のみを持つロールをデフォルトで割り当てる設定をしています。

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
data:
  service.slack: |
    token: $slack_token
  config.yaml: |
    context:
      argocdUrl: <REPLACE_THIS_FIELD>
  template.app-sync-failed: |
    message: |
      <!channel>
      {{if eq .serviceType "slack"}}:exclamation:{{end}}  The sync operation of application {{.app.metadata.name}} has failed at {{.app.status.operationState.finishedAt}} with the following error: {{.app.status.operationState.message}}
      Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true .
    slack:
      attachments: |
        [{
          "title": "{{ .app.metadata.name}}",
          "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "color": "#E96D76",
          "fields": [
          {
            "title": "Sync Status",
            "value": "{{.app.status.sync.status}}",
            "short": true
          },
          {
            "title": "Repository",
            "value": "{{.app.spec.source.repoURL}}",
            "short": true
          }
          {{range $index, $c := .app.status.conditions}}
          {{if not $index}},{{end}}
          {{if $index}},{{end}}
          {
            "title": "{{$c.type}}",
            "value": "{{$c.message}}",
            "short": true
          }
          {{end}}
          ]
        }]
      deliveryPolicy: Post
      groupingKey: ""
      notifyBroadcast: false
  trigger.on-sync-failed: |
    - description: Application syncing has failed
      send:
      - app-sync-failed
      when: app.status.operationState.phase in ['Error', 'Failed']

argocd-notifications-cm は、Argo CDの公式プラグインであるArgo CD Notificationsに関するConfigMapです、この例では失敗時にSlack通知を行う記述について示します。 まず token はArgo CDの通知をするSlackアプリケーションのOAuthトークンであり、秘匿情報のためSecretを介して値を渡します。argocdUrl はSlack通知のメッセージに表示されるArgo CDのURLであり、環境ごとに値が違うためパッチによって上書きしています。template.app-sync-failedtrigger.on-sync-failed は、同期の失敗時に通知するメッセージのテンプレートと、その発火条件の設定です。
app-sync-failed を含む基本的な状態の通知に関しては、Argo CD Notificationsのマニフェストにはじめから定義されています。そのため本来であればパッチを定義する必要はないのですが、失敗時はSlackメンションを付けて通知をしたいため、少々冗長さを許容してこのような方法を取っています。

IAP認証とSSOログインについて

Argo CDのWeb UIにアクセス出来るように、今回導入したArgo CDは外部公開されており、認証は必須です。 これまでは、外部公開するサービスにはCloud ArmorによるIP制限を設け、社内ネットワークやVPNを通してのみアクセスできるようにしていたケースが多くありました。しかしこの運用はVPNによる通信速度の低下といった課題もあり開発体験が悪かったため、今回はIP制限を設けず、Google CloudのCloud IAPによってIAP認証を設ける方針としました。 また、IAP認証を通過したのちにArgo CDへログインできるユーザーをMLOpsブロックのメンバーを始めとする関係メンバーのみに絞るため、SSOログインを導入しました。

Argo CDはOktaやMicrosoft Azure Active Directory(Azure AD)を始めとした様々な認証基盤によるSSOログインをサポートしています。今回は弊社で社内向けアプリのログイン全般に利用しているAzure ADを利用して、関係メンバーのみがログインできる方針としました。
なお、Argo CDではIDとパスワードによるログイン運用も可能ですが、上記の方針だとよりセキュアかつ利用が簡単なため、管理者であってもIDとパスワードによるログインはできないようにしています。

この章ではIAP認証とSSOログインの導入について説明しますが、Azure ADに特化した内容については流れを把握するための簡単な説明だけに済ませます。

IAP認証の導入

この節では、IAP認証とSSOログインの導入について実際のマニフェストを交えつつ簡単に説明します。
まずIAP認証によって、MLOpsブロックの関係者に限らず社内アカウントを所持するメンバーを認証します。続くArgo CDのログインで関係者のみ権限を分けて認証します。
次はTerraformでArgo CDのBackend Service(Kubernetes Ingressをデプロイすると作成される)に対して、社内アカウントのアクセス権を付与している例です。

resource "google_iap_web_backend_service_iam_binding" "argo-cd-iap-iam-binding" {
  project             = local.project
  web_backend_service = data.google_compute_backend_service.argo-cd-backend-service.name
  role                = "roles/iap.httpsResourceAccessor"
  members = [
    "domain:zozo.com",
  ]
}

また、Cloud IAPによる認証をするため、前もってGoogle Cloud側でOAuth 2.0クライアントを作成する必要があります。次はTerraformによるOAuthクライアントのリソース定義の例です。

# Since the iap brand already existed, refer the brand name to create iap client: `gcloud alpha iap oauth-brands list`
resource "google_iap_client" "argo-cd-iap-client" {
  display_name = "argo-cd-iap-client"
  brand        = "projects/${local.project_number}/brands/${local.project_number}"
}

OAuthクライアントを作成後、IAP認証に利用するクライアントIDとクライアントSecretを取得してGoogle Secret Managerに別途保管しておきます。

そしてArgo CDのWeb UIを司る argocd-server のServiceに紐づく、GKEのカスタムリソースであるBackendConfigに次の設定を適用します。

apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: argocd-backend-config
spec:
  ...
  iap:
    enabled: true
    oauthclientCredentials:
      secretName: argocd-secret

ここで oauthclientCredentials として渡している argocd-secret という名前のSecretリソースに、先程作成したOAuthクライアントのIDとSecretの値を含めています。

上記の設定によりIAP認証を導入し、社内アカウントを持つメンバーの認証を行えるようになりました。

SSOログインの導入

続いて、SSOログインに関する設定について説明します。先述の通り、弊社では社内向けアプリの認証基盤としてAzure ADを利用しているため、今回のArgo CDへのSSOログインにおいてもこちらを用いる方針としました。
SSOログインを行うため、まずはAzure AD側でArgo CDと紐づくSAMLアプリケーションを用意する必要があります。アプリケーションの追加は、Argo CDの導入を済ませた上でコーポレートエンジニアリングチームにエンドポイントを伝えて依頼しました。Azure ADでなくOktaなどの別の認証基盤を利用しているケースでも、大体同じ流れになるかと思います。

またSAMLアプリケーションの作成を依頼する際、後述するきめ細かい権限管理のため、適切な粒度でのユーザーグループの作成も同時にお願いしました。ユーザーグループごとの権限はArgo CD側で細かく調整できるため、一旦は権限を付与する可能性のあるチームの単位(検索系、推薦系など)でユーザーグループを分けておきます。
結果として、環境ごとのSAMLアプリケーションと、それに紐づく7つのユーザーグループが用意できました。

Azure ADエンタープライズアプリケーションで複数のユーザーグループを用意した

認証基盤側の準備が完了した後は、 argocd-cm に次のパッチを当てることでSSOログインを有効化します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
data:
  url: https://${endpoint}
  dex.config: |
    logger:
      level: debug
      format: json
    connectors:
    - type: saml
      id: saml
      name: saml
      config:
        entityIssuer: https://${endpoint}api/dex/callback
        ssoURL: https://${ssoUrl}
        caData: $argocd_dex_saml_ca
        redirectURI: https://${endpoint}/api/dex/callback
        usernameAttr: email
        emailAttr: email
        groupsAttr: Group

こちらの設定も利用する認証基盤によって多少変化しますので、ここでは参考程度にお伝えします。
endpoint は構築したArgo CDのエンドポイントであり、 ssoUrl はAzure ADアプリケーション側で発行されたログインURLです。caData はSAML証明書であり、秘匿情報のためOAuthクライアントの情報と同様に argocd-secret というSecretに含めて渡しています。

これでIAP認証とSSOログインの両方を導入し、利用者にとってログインが簡単でかつセキュアな環境を整えることができました。

きめ細かい権限管理について

ここまでの作業によって、管理者であるMLOpsブロックのメンバーを含む利用者が、簡単かつセキュアにArgo CDを利用できるようになりました。この節では利用者ごとにきめ細かい権限管理をする方法について説明します。

Argo CDでは、全てのリソースに対する全権限を持つ admin ロールと、全てのリソースに対する読み取り権限を持つ readonly ロールが組み込まれています。また、デフォルトでは readonly ロールが割り当てられます。
しかし先述の通りMLOpsブロックが管理するサービスの領域は広いため、利用するメンバーによってどのArgo CDリソースの権限を付与するかを切り分けたいケースがあります。例えば管理者であるMLOpsブロックのメンバーには全リソースに対する権限を付与し、検索系のメンバーには検索に関連したApplicationに対する権限のみを付与したい、といったケースです。
スムーズな開発のため開発環境では編集権限を付与し、本番環境では読み取り権限のみを付与できると嬉しいですし、管理者でも先述の admin ロールよりも権限を絞る、といった柔軟な権限設定が望まれます。

Argo CDでは管理者が定義したカスタムユーザーまたはSSO構成ごとに、Argo CDリソースの権限レベルを細かく設定したカスタムロールを割り当てる、ロールベースアクセス制御(RBAC)が可能です。
Azure ADをはじめとした認証基盤側で、先述の通り割り当てたいロールごとに検索系メンバー、推薦系メンバーといったユーザーグループを用意しました。これらは後ほど解説するArgo CDカスタムロールにそれぞれ紐付けることができ、きめ細かい権限管理が行えます。

RBACはArgo CDに含まれるConfigMapのマニフェストである argocd-rbac-cm にパッチを当てることによって設定できます。参考までに、Staging環境における検索系チーム向けのロール割り当ての例を示します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
data:
  # Azure AD groups ref. https://portal.azure.com/...
  policy.csv: |
    p, role:search-member, applications, get, search/*, allow
    p, role:search-member, clusters, get, *, allow
    p, role:search-member, repositories, get, *, allow
    p, role:search-member, projects, get, search, allow
    g, "${azure_ad_search_group_object_id_stg}", role:search-member

search-member がロール名にあたるもので、最終行でこれをユーザーグループの識別子と紐付けています。
ここで azure_ad_search_group_object_id_stg はAzure ADユーザーグループのオブジェクトID(一意なIDであるため伏せています)です。なお、SSO構成によって紐付けに用いる識別子は変わります。開発環境ではなくStaging環境のため、メンバーには検索系のApplicationのみに対する読み取り権限と、その他リソースの読み取り権限を付与しています。

また、以下に同じくStaging環境におけるMLOpsブロックメンバー向けのロール割り当ての例を示します。本来上記と同じパッチに記述するものですが、ここでは分かりやすさのために分けて記述します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
data:
  policy.csv: |
    p, role:org-admin, applications, *, */*, allow
    p, role:org-admin, clusters, get, *, allow
    p, role:org-admin, repositories, get, *, allow
    p, role:org-admin, projects, get, *, allow
    g, "${azure_ad_admin_group_object_id_stg}", role:org-admin

検索系チーム向けのロールと比較すると分かるように、全てのApplicationに対する全ての操作権限を付与しています。一方で、Application以外のリソースに変更を加える際は基本的にマニフェストの修正をしてPRレビューを通すといったフローで行うため、その他リソースに関しては読み取り権限のみを付与しています。

このように、SSO構成と紐付いたRBACを設定することで、利用チーム別かつデプロイ環境別にきめ細かく安全な権限管理ができます。

補足: Secret管理のためのExternal Secrets Operatorの導入

本題からはずれますが、マルチテナントクラスタへの移行とArgo CDの導入において避けては通れなかったExternal Secrets Operatorの導入について、ここで参考までに述べます。

MLOpsブロックでの従来の運用では、Kubernetes Secret管理とデプロイにBerglasというGoogle製のオープンソースCLIツールを利用していました。このCLIツールは用途がKubernetesに限られないもので、コマンドを実行することで対象のSecret値の実体化が行えます。バックエンド(Secretの保管先)としてGCS、Google Secret Managerが利用できます。
従来の運用におけるSecretのデプロイフローを下の図に示します。

従来はGHA Job内でBerglasを利用してSecretを実体化させてからデプロイを行っていた

Argo CD導入前のため、GHAからSecretリソースのデプロイを行う前提です。まずBerglasコマンドを実行するスクリプトを介して、Google Secret Managerを参照してSecretの実体化を行い、その後クラスタへのデプロイを実施しています。
GHAのみならず、動作確認などローカル環境からの手動デプロイにおいても同じフローで実施しており、各メンバーのローカル環境でBerglasによるSecretの実体化を行う手間がありました。また開発メンバーにはGoogle Secret Managerに対するAccessorロールを付与する必要があり、詳細は省きますがマニフェストの構成が直感的でなく分かりづらいという問題もありました。

上記のような課題感があったため、Secret保管先としてはGoogle Secret Managerを引き続き利用しつつ、Berglasの利用を撤廃できる方針を検討していました。
Argo CDは先述の通りDesired Stateとするブランチに対して同期を行いますが、リポジトリ上にはもちろん秘匿情報は保管できません。また、Argo CD側からBerglasによってSecret値を実体化させる術もありません。そのため通常の同期ではSecretの値を実体化させてデプロイすることが出来ず、Berglasの利用を継続したままArgo CDを導入するのは難しいという点もありました。
そこで検討した結果候補に挙がったのが、Argo CDの公式プラグインであるArgo CD Vault Plugin(AVP)と、External Secrets Operatorの2つです。

AVPを用いた場合、Argo CDがバックエンドであるSecretの保管先を参照し、Secretリソース中のプレースホルダを上書きすることでSecretの実体化を行います。
バックエンドとしてGoogle Secret Managerを利用でき、かつArgo CDプラグインとして動作するため、運用するKubernetesワークロードが増えないという利点もあります。使い方もKubernetes Secretリソースにアノテーションを追加するだけでよくシンプルで、初めはBerglasからこちらに移行することを検討しました。
しかしあくまでもArgo CDのプラグインであるため、動作確認などを目的とする手動デプロイといった、Argo CDを介さないデプロイではSecretの値を実体化させることができず、運用が難しいという結論に至りました。

続いて検討したのがExternal Secrets Operatorです。こちらの運用ではマルチテナントクラスタ上にデプロイされたExternal Secrets Operatorが、Google Secret Managerの参照とSecretの実体化の役割を担います。ユーザーがExternalSecretというカスタムリソースを作成すると、External Secrets OperatorがExternalSecretに紐付くKubernetes Secretリソースを作成します。 次にExternal Secrets Operatorの概念図を公式ドキュメントより引用します。

External Secret Operatorの概念図

デプロイ時にクラスタ内部からバックエンドを参照してSecretの実体化を行うという点で、使い勝手の面ではAVPと大きく変わりません。
メリットとしてはArgo CDとは独立であるため、ExternalSecretリソースをArgo CDを介さずデプロイした場合であってもSecretの実体化を行える点が挙げられます。運用するワークロードが増えるというデメリットはありますが、使い勝手を考えこちらを導入することにしました。

Berglasの利用が撤廃できたため、デプロイ前にSecretを実体化させる手間がなくなりました。また、Google Secret Managerの権限をメンバーに付与する必要がなくなり、マニフェストの構成もシンプルになるなど、多くのメリットが得られました。

おわりに

最後まで読んでいただきありがとうございました。

MLOpsブロックでは多数のサービスを開発運用する上での課題を解決するために、マルチテナントクラスタへの移行とArgo CDの導入を実施し、運用において多くのメリットを得ることができました。
また本記事では、Argo CDを導入し運用していくにあたってほぼ必須となる、認証機構の導入やメンバーごとの権限管理をきめ細かく行う方法についても説明しました。本記事が皆様のお役に立てば幸いです。

最後になりますが、ZOZOでは一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください!

hrmos.co corp.zozo.com

カテゴリー