0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

EKS Capabilities(Kro + ACK)と ASCP でシークレット管理を効率化してみた

0
Last updated at Posted at 2026-07-09

こんにちは、タカサオです!

EKSクラスタ上のアプリケーションPodが使うシークレットをAWS Secret Managerで管理したい場合、ASCP (AWS Secrets and Config Provider) が使えます。
ASCPを用いる場合、AWS Secrets Manager にシークレットを作成して、さらにそれを参照する SecretProviderClass も定義して…という作業を手作業で行っていました。

今回は Kro と ACK を組み合わせることで、「1 つのカスタムリソースを apply するだけで AWS Secrets Manager のシークレットと SecretProviderClass を同時に作成できるか」を EKS Auto Mode で検証してみたいと思います!

検証したのは以下の 3 点です。

  • kind: ManagedSecret という独自リソースを apply すると、ACK が SM シークレットを、Kro が SecretProviderClass を自動作成する
  • ユーザーが SM に実値を手動で設定した後、Pod 起動時に ASCP が k8s Secret を自動作成する
  • Pod の環境変数にシークレット値が注入される

使用技術の概要

Kro (Kube Resource Orchestrator) は Kubernetes のカスタムリソースを組み合わせて、複数のリソースを 1 つの独自 API として定義できるオーケストレーターです。ResourceGraphDefinition という CRD でリソース間の依存関係を定義し、CEL 式でリソース同士の値を参照できます。

ACK (AWS Controllers for Kubernetes) は AWS リソースを Kubernetes の CRD として管理するコントローラー群です。Secret リソースを apply すると、ACK が AWS Secrets Manager に実際のシークレットを作成します。

ASCP (AWS Secrets and Config Provider) は Secrets Store CSI Driver の AWS 向けプロバイダーで、CSI ボリュームをマウントしたタイミングで SM のシークレットを取得して k8s Secret を作成できます。

今回は Kro・ACK それぞれを EKS Capabilities として使います。EKS Capabilities はコントローラーをクラスター外の AWS マネージドインフラ上で動かす仕組みで、クラスター内に Helm でコントローラーを入れなくて済みます。

検証環境

項目 内容
Kubernetes バージョン v1.36
クラスタータイプ EKS Auto Mode
Kro v0.9.2-eks-3(EKS Capability)
ACK SecretsManager v46.108.1-eks-1(EKS Capability)
ASCP 3.1.1(IRSA 認証)
Secrets Store CSI Driver 1.6.0
リージョン ap-northeast-1

アーキテクチャ

今回検証する構成の流れは以下のとおりです。

Kro が起点となり、ACK 経由で SM シークレットを、直接 SecretProviderClass を作成します。その後ユーザーが実値を設定し、Pod 起動時に ASCP が k8s Secret を組み立てる流れです。

前提(構築済みの環境)

この記事では以下は構築済みとして、Kro の ResourceGraphDefinition の定義から解説します。

  • EKS Auto Mode クラスター(v1.36)
  • EKS Capabilities(Kro / ACK SecretsManager) の有効化
  • Secrets Store CSI Driver + ASCP のインストール(Helm)と IRSA の設定

検証1: ResourceGraphDefinition を定義する

Kro が解釈する ResourceGraphDefinition を定義します。EKS Capability の Kro は apiVersion: kro.run/v1alpha1 / kind: ResourceGraphDefinition を使います。

apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
  name: managed-secret
spec:
  schema:
    apiVersion: v1alpha1
    kind: ManagedSecret
    spec:
      # SM シークレット名(k8s リソース名のプレフィックスにも使う)
      secretName: string | required=true
    status:
      smArn: ${awsSecret.status.ackResourceMetadata.arn}
      smSynced: ${awsSecret.status.conditions[0].type}

  resources:
    # 1. ACK で AWS Secrets Manager にシークレットを作成(初期値なし)
    - id: awsSecret
      readyWhen:
        - ${has(awsSecret.status.ackResourceMetadata.arn)}
      template:
        apiVersion: secretsmanager.services.k8s.aws/v1alpha1
        kind: Secret
        metadata:
          name: ${schema.spec.secretName}
          namespace: ${schema.metadata.namespace}
        spec:
          name: ${schema.spec.secretName}
          description: "Managed by kro + ACK (EKS Capability)"

    # 2. SecretProviderClass(ASCP 用)
    - id: secretProviderClass
      template:
        apiVersion: secrets-store.csi.x-k8s.io/v1
        kind: SecretProviderClass
        metadata:
          name: ${schema.spec.secretName + "-spc"}
          namespace: ${schema.metadata.namespace}
        spec:
          provider: aws
          parameters:
            region: ap-northeast-1
            objects: |
              - objectName: "${schema.spec.secretName}"
                objectType: "secretsmanager"
                objectAlias: "db-password"
          secretObjects:
            - secretName: ${schema.spec.secretName + "-k8s-secret"}
              type: Opaque
              data:
                - objectName: "db-password"
                  key: db-password

ポイントをいくつか補足します。

namespace は ${schema.metadata.namespace} を参照する

当初は schema の specnamespace フィールドを定義していましたが、kro は ${schema.metadata.namespace} でインスタンスの Namespace を直接参照できます(公式ドキュメントでも namespace: ${schema.metadata.namespace} が使われています)。そのため spec 側で namespace を持たせる必要はなく、重複を避けられます。

readyWhen${...} で囲む

readyWhen${has(awsSecret.status.ackResourceMetadata.arn)} の形式で書きます。"has(...)" のようにダブルクォートだけで囲む形式は「only standalone expressions are allowed」というエラーになります。

リソースの依存関係は CEL 式から自動推定される

secretProviderClass は ACK Secret の情報を参照していませんが、awsSecret が Ready(ARN 確定)になるのを待たせたい場合は CEL 式で参照を持たせます。kro は ${} の参照から DAG を構築し、作成順序を自動で決めます。

apply すると ManagedSecret という CRD が生成されます。

$ kubectl get crd | grep managedsecret
managedsecrets.kro.run   2026-07-09T13:19:09Z

検証2: ManagedSecret を apply する

生成された独自 API を使ってインスタンスを作成します。metadata.namespace から Namespace が、spec.secretName から SM シークレット名が決まります。

apiVersion: kro.run/v1alpha1
kind: ManagedSecret
metadata:
  name: app-db-secret
  namespace: default
spec:
  secretName: eks-kro-ack-secrets-app-secret
kubectl apply -f managed-secret.yaml

しばらくすると ManagedSecret のステータスが ACTIVE になります。

$ kubectl get managedsecret app-db-secret -o yaml
...
status:
  smArn: arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:eks-kro-ack-secrets-app-secret-fqPnAw
  smSynced: ACK.ResourceSynced
  state: ACTIVE

status.smArn に SM シークレットの ARN が入りました。SecretProviderClass も自動作成されています。

$ kubectl get secretproviderclass
NAME                                 AGE
eks-kro-ack-secrets-app-secret-spc   113s

1 つの ManagedSecret を apply しただけで、ACK が SM シークレットを、Kro が SecretProviderClass を作成してくれました。

検証3: SM に実値を設定して Pod から参照する

この時点では SM シークレットは初期値なしで作成されています。ここでユーザーが実際の値を設定します。

aws secretsmanager put-secret-value \
  --secret-id eks-kro-ack-secrets-app-secret \
  --secret-string '{"db-password":"SuperSecretPassword123!"}' \
  --region ap-northeast-1

シークレット値を ManagedSecret のマニフェストに書かず、SM 側で直接設定できるので、値が Git や k8s マニフェストに残りません。

続いて、CSI ボリュームマウントと環境変数参照を組み合わせた Pod を起動します。

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
spec:
  serviceAccountName: secret-test-sa  # IRSA アノテーション付き SA
  containers:
    - name: app
      image: public.ecr.aws/docker/library/busybox:stable
      command: ["sh", "-c", "echo DB_PASSWORD=$DB_PASSWORD && sleep 3600"]
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: eks-kro-ack-secrets-app-secret-k8s-secret
              key: db-password
      volumeMounts:
        - name: secrets-store
          mountPath: /mnt/secrets
          readOnly: true
  volumes:
    - name: secrets-store
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: eks-kro-ack-secrets-app-secret-spc

ここで重要なのは、env var で k8s Secret を参照する場合でも CSI ボリュームのマウントは必須 という点です。ASCP は CSI ボリュームがマウントされたタイミングで SM からシークレットを取得し、k8s Secret を作成するためです。

Pod が起動すると k8s Secret が自動作成され、env var にシークレット値が注入されました。

$ kubectl exec secret-test-pod -- sh -c "echo DB_PASSWORD=$DB_PASSWORD"
DB_PASSWORD={"db-password":"SuperSecretPassword123!"}

$ kubectl get secret eks-kro-ack-secrets-app-secret-k8s-secret
NAME                                        TYPE     DATA   AGE
eks-kro-ack-secrets-app-secret-k8s-secret   Opaque   1      10s

SM シークレットを JSON 形式で格納した場合、デフォルトでは JSON 文字列がそのまま env var に入ります。特定のキーだけを取り出したい場合は SecretProviderClass の jmesPath を使います。

ハマりポイント

EKS Capabilities は k8s RBAC を手動で追加する

EKS Capabilities はクラスター外の AWS マネージドインフラで動作します。Capability を作成すると Access Entry は自動作成されますが、それだけでは Kubernetes リソースを操作する RBAC が不足します。Kro や ACK が k8s リソースを作成できるよう、ClusterRoleBinding を手動で追加する必要があります。

SM シークレット名は強制削除後すぐには再利用できない

検証をやり直す際、aws secretsmanager delete-secret --force-delete-without-recovery で削除したシークレットと同名で再作成しようとして、以下のエラーになりました。

InvalidRequestException: You can't create this secret because a secret 
with this name is already scheduled for deletion.

通常の削除では 7〜30 日の recovery window がありますが、--force-delete-without-recovery を使うと即時削除されます。ただし 公式ドキュメント にあるとおり、実際の削除は非同期のバックグラウンド処理なので、削除直後の同名再作成には少し待ち時間(back-off)が必要でした。

初期値は SM 側で設定する設計にした

当初は ACK の spec.secretString で k8s Secret を参照し、SM シークレットに初期値を入れる構成を検討しました。しかし今回の環境では ACK(EKS Capability)が参照先の k8s Secret を読み取れず、この方式を断念しました(RBAC 周りの追加検証が必要そうです)。

結果的に「SM シークレットを初期値なしで作成 → ユーザーが後から実値を設定」という設計にしましたが、これはシークレット値をマニフェストに書かずに済むため、セキュリティ的にはむしろ好ましい形になりました。

まとめ

今回は Kro + ACK(EKS Capabilities)と ASCP を組み合わせて、シークレット管理を自動化してみました。

  • kind: ManagedSecret → SM シークレット + SecretProviderClass の同時作成 → 成功。Kro の ResourceGraphDefinition でリソースを宣言するだけで、1 つの独自リソースから両方を作成できました
  • Pod 起動時の k8s Secret 自動作成と env var 注入 → 成功。CSI ボリュームのマウントをトリガーに ASCP が SM からシークレットを取得し、k8s Secret を作成します

いったん ManagedSecret という抽象を用意してしまえば、アプリ開発者は「シークレット名を 1 つ指定するだけ」でシークレット基盤を使えるようになるので、プラットフォームチームが用意する抽象化としては便利だなと感じました。

それではまた!

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?