DMM.comの、一番深くておもしろいトコロ。

マルチテナントKubernetes環境のKubernetes External Secrets が非推奨になるので External Secrets Operatorへ移行した話

マルチテナントKubernetes環境のKubernetes External Secrets が非推奨になるので External Secrets Operatorへ移行した話

  • このエントリーをはてなブックマークに追加

はじめに

この記事は DMMグループAdvent Calender 2022 の10日目の記事です。 10日目はプラットフォーム事業本部マイクロサービスアーキテクトグループSREチームの岩崎惣が担当します。

DMMプラットフォームではマイクロサービスアーキテクチャを採用しておりマイクロサービスを稼働させる環境としてKubernetes(EKS/GKE)を利用しています。Kubernetesには約120名のエンジニアが、様々なマイクロサービスを稼働させておりマイクロサービスをNamespaceで分離してマルチテナント化を行なっています。

今回の投稿では、マルチテナント環境で稼働しているKubernetes External Secrets(KES)をExternal Secrets Operator(ESO)へ移行をご紹介します。

マイクロサービスアーキテクチャやマルチテナント環境に関しては、pospomeが紹介していますので興味のある方はそちらの記事をご確認ください。

inside.dmm.com

www.pospome.work

Kubernetes External Secrets(KES)の非推奨

2021年11月頃からKESは非推奨になりNode.jsで書かれていたKESをGoで書き直したESOへの移行が公式からアナウンスされました。 移行用のツールなども整備が行われ、遂に2022年7月にはKESのリポジト自体もArchiveされメンテナンス自体も行われなくなりました。 github.com

KESからESOの変更点

ESOに書き直され、新たにSecretStoreリソースが追加されました。SecretStoreはExternalSecret Podの環境変数として渡されていたAWS/GCP/Azureなどへの接続情報をKubernetesリソースとして定義出来るようになりました。 SecretStoreが追加されたことでGCPの複数プロジェクトへの接続を別々のSecretStoreとして表現できるようになりました。

■KESでのGCPへの接続設定

---
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
  namespace: external-secrets
type: Opaque
stringData:
  gcp-creds.json: |-
    ${{JSON_KEYFILE}}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-secrets-kubernetes-external-secrets
  namespace: external-secrets
spec:
    spec:
      containers:
          name: kubernetes-external-secrets
          image: 'ghcr.io/external-secrets/kubernetes-external-secrets:8.4.0'
        - env:
            - name: GOOGLE_APPLICATION_CREDENTIALS
              value: /app/gcp-creds/gcp-creds.json

■ESOでのGCPへの接続設定

---
apiVersion: v1
kind: Secret
metadata:
  name: gcpsm-secret
  namespace: external-secrets-operator
  labels:
    type: gcpsm
type: Opaque
stringData:
  secret-access-credentials: |-
      ${{JSON_KEYFILE}}
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  # Set the same value as the projectID.
  name: pf-msa-test
spec:
  provider:
    gcpsm:
      # Specify the GCP project where the Secret Manager is located.
      projectID: pf-msa-test
      auth:
        secretRef:
          secretAccessKeySecretRef:
            namespace: external-secrets-operator
            name: gcpsm-secret
            key: secret-access-credentials

また、Secretリソースを作成するExternalSecretと外部への接続を管理するSecretStoreはクラスター全体で共有する、「ClusterExternalSecret」「ClusterSecretStore」とNamespace内で利用する「ExternalSecret」「SecretStore」の4つに分かれたことでRBAC機能での細やかなアクセス管理が可能になりました。

ESOでのマルチテナント構成

ESOはマルチテナントでの利用が想定されておりKubernetes管理者とアプリケーション開発者の責任境界をどうするかで3パターンの例が用意されています。

選択肢は3つありますが、セキュリティの最小限の原則から言えばNamespaceリソースとしてSecretStoreを作成しアクセスを絞れる「ESO as a Service」の構成をとるのがベストと言えます。 ですがプラットフォーム事業本部では、1つのKESサービスを複数のマイクロサービスで共有して利用していたため、元々複数のGCPプロジェクトへのアクセス可能なサービスアカウントが用意されていました。 ESOの移行作業とリソースの責任境界変更を同時に実施すると大変なので、まずは移行作業に集中するためにKESと同じ方針を踏襲することとしました。 既存のKESの方針を踏襲する形で移行を検討すると「ESO as a Service」より「Shared ClusterSecretStore」構成はKubernetes管理者が管理し、ExternalSecretをアプリケーション開発者が管理するという責任境界がKESに酷似しておりExternalSecretを作成するまでのフローに変更の必要がほとんどなく低コストで移行が可能だったので今回はこちらを選択することにしました。

■Shared ClusterSecretStoreの構成

KESからESOへの移行

KESからESOへの移行方法として公式は、kes-to-esoの利用を推奨しています。 ですが、kes-to-esoを検証していくうちに変更する影響範囲がかなり大きいため別の方法を模索することにしました。

kes-to-esoの挙動

kes-to-esoを実行すると下記の流れでKubernetes ClusterのExternalSecretの変更が行われます。

  1. ESOのreplicasを0にしてESOを停止させる
  2. KES設定を参照しESOリソースのSecretStoreとExternalSecretのマニフェストを生成する
  3. 生成したESOのSecretStoreとExternalSecretをデプロイ
  4. KESのreplicasを0にしてKESを停止させる
  5. KESリソースのExternalSecretで生成されたSecretリソースにあるmetadata.ownerReferencesをESOリソースのExternalSecretへ書き換える
  6. ESOのreplicasを1にしてESOを起動させる

ArgoCDの自動同期とkes-to-esoが競合してしまう

プラットフォーム事業本部では、全てのマイクロサービスのマニフェストを管理するGitHubリポジトリとKubernetes Clusterへデプロイを行うArgoCDをSREチームで提供しています。

マイクロサービスは、GitHubリポジトリに保存されているマニフェストと自動同期を行なっておりkes-to-esoを実行してしまうとKubernetes ClusterとGitHubリポジトリのマニフェストに差分が発生してしまいArgoCD側が差分を埋めるためにGitHubリポジトリ側のマニフェストをデプロイしてしまいます。

また、ArgoCDでの自動同期のタイミング次第でSecretに設定されているmetadata.ownerReferencesをKES ExternalSecretの設定に巻き戻してしまいESOを利用したSecret管理が行われなくなってしまいます。

これを回避する方法は、下記の手順で進める必要があります。

  1. ArgoCDの全アプリケーションの自動同期をOFFにする。(ArgoCDのデプロイを停止する)
  2. kes-to-eso を実行し、クラスター上のリソースをESOに差し替える。
  3. GitHubリポジトリ上のマニフェストファイルをESOのものに差し替える。
  4. ArgoCDの自動同期をONにする。

■KES管理のSecretをESOが作成しようとした場合のエラー

# kubectl describe externalsecret.external-secrets.io -n iwasaki-so-test test-secret-eso
Name:         test-secret-eso
Namespace:    iwasaki-so-test
Labels:       <none>
Annotations:  <none>
API Version:  external-secrets.io/v1beta1
Kind:         ExternalSecret
Status:
  Conditions:
    Last Transition Time:   2022-05-27T03:25:02Z
    Message:                could not update Secret
    Reason:                 SecretSyncedError
    Status:                 False
    Type:                   Ready
  Refresh Time:             2022-05-27T03:24:51Z
  Synced Resource Version:  1-a2ac4aecf101004be36ec1b3fae77c21
~~~~~~~~~
Events:
  Type     Reason        Age                    From              Message
  ----     ------        ----                   ----              -------
  Normal   Updated       3m26s (x9 over 7m28s)  external-secrets  Updated Secret
  Warning  UpdateFailed  3m14s (x17 over 10m)  external-secrets  could not set ExternalSecret controller reference: Object iwasaki-so-test/test-secret-eso is already owned by another ExternalSecret controller test-secret-kes

マイクロサービス単位でExternalSecretを作り直す

ダウンタイムと影響範囲を可能な限り最小で移行する方法を検討した結果、kes-to-esoを利用せずKESとESOを並行に稼働しそれぞれのマイクロサービス単位でExternalSecretを再作成していくのがベストだと判断しました。

KESとESOはapiVersionが別になっており並行稼働していても問題はないことがわかっていたため、ExternalSecretで生成するSecret名を別々にすればmetadata.ownerReferencesの書き換えも不要になりdeploymentリソースなどのSecretを参照しているマニフェストを機械的に置き換えるのみで移行が可能で問題発生時も参照を戻すだけのためロールバックが容易というメリットがあります。 ArgoCDはデプロイを行なったDeploymentなどのリソースから生成されるPodなどのリソースに対して変更を加える機能がないため、直接Kubernetes Clusterに生成されたSecretのmetadata.ownerReferencesを書き換える必要があったのですが、ExternalSecretを作り直すことでその作業が不要になったのも今回の手法を選択した理由の一つになりました。

また、この手法を取るとESO構築とClusterSecretStore追加をKubernetes管理者が行い、ExternalSecretをアプリケーション開発者が再作成し参照を切り替えるという責任境界で作業を分けられたのも大きなメリットになりました。

kubectlコマンドでのShortname重複挙動

ExternalSecretリソースをkubectlで操作する場合、shortnameはesになります。 ExternalSecretリソースを取得する場合は以下です。 ■ExternalSecretが取得できた場合の結果

# kubectl get es -A
NAMESPACE   NAME        STORE       REFRESH  INTERVAL       STATUS  READY
pf-test     secret-eso  pf-msa-test 30s      SecretSynced   True

しかし、このコマンドで取得できたリソースはArgo EventsのEventSourceという全く別のリソースでした。 ■実際に取得された結果

# kubectl get es -A
NAMESPACE     NAME         AGE
argo-events   calendar     100d
argo-events   gcp-pubsub   232d

ソースを確認したところKubernetesにあるAPIリソースをShortnameで呼び出す際に、APIリソースを導入した順番の早いものから呼び出されるという仕様によるものであるとわかりました。 APIリソースのマッピングを変更するには対象のCRD入れ直す、今回で言えばArgo EventsのEventSourceのCRDを一度削除して再度インストールするしか方法がありませんでした。 Argo Events入れ替えが大変であり、Shortname自体で呼び出しているユーザが少なかったのでユーザにShortnameが使えなくなった旨を周知して対応しないこととしました。

github.com

■KES削除後のapi-resourcesの状況

# kubectl api-resources
NAME                              SHORTNAMES           APIVERSION                             NAMESPACED   KIND
eventsources                      es                   argoproj.io/v1alpha1                   true         EventSource
externalsecrets                   es                   external-secrets.io/v1beta1            true         ExternalSecret

最後に

今回はExternal Secrets移行を書かせていただきましたが、マイクロサービスアーキテクトグループ SREチームではPlatform SREとしてマイクロサービス開発の開発速度や利用体験向上を目指しプラットフォームの開発・運用を行なっており一緒に取り組んでくれる方の募集もしております、ご興味ありましたらカジュアル面談もやっておりますのでぜひご検討ください。

dmm-corp.com

引き続きDMMアドベントカレンダー2022もお楽しみください。