はじめに
Kubernetesでアプリケーションを運用する際、データベースのパスワードやAPIキーなどの機密情報をどのように管理するかは重要な課題です。従来の方法では、これらの情報をマニフェストファイルに直接記述したり、手動でKubernetes Secretを作成する必要がありました。しかし、この方法では機密情報がコードに埋め込まれるリスクがあり、パスワード変更時の運用負荷も大きくなります。
このデモでは、Amazon EKSでExternal Secrets Operatorを使用して、AWS Secrets ManagerからRDSの認証情報を自動的に取得し、Kubernetesアプリケーションで安全に利用するデモ環境を構築しました。External Secrets Operatorを使用することで、機密情報を外部のシークレット管理サービスで一元管理し、Kubernetesクラスター内では自動的にSecretリソースを作成・更新できるようになります。
パターン別実装
External Secrets Operatorで取得したシークレットをアプリケーションで利用する方法として、2つの異なるアプローチを実装しています。それぞれに特徴があり、用途に応じて選択できます。
環境変数パターンは、KubernetesのSecretから環境変数として値を注入するシンプルな方法です。実装が簡単でデモや開発環境に適していますが、kubectl操作で環境変数の内容が見えてしまうセキュリティ上の懸念があります。
ファイルマウントパターンは、Secretをファイルとしてコンテナ内にマウントする方式で、kubectl操作では環境変数として表示されないため、よりセキュアです。ただし、アプリケーション側でファイルを読み込む実装が必要になるため、少し複雑になります。本番環境での利用に推奨されます。
External Secrets Operatorとは
External Secrets Operatorは、外部のシークレット管理サービス(AWS Secrets Manager、HashiCorp Vault等)からKubernetesのSecretを自動で作成・同期するオープンソースツールです。このツールを使用することで、機密情報の管理を外部サービスに委ね、Kubernetesクラスター内では自動的にSecretリソースを生成・更新できるようになります。
従来の課題と解決方法
従来のKubernetesでは、シークレットを扱う際に多くの課題がありました。パスワードをマニフェストファイルに直接書き込む必要があり、パスワード変更時には手動でファイルを更新する必要がありました。また、base64エンコードが必要で、実質的にはパスワードがコードに埋め込まれる形となり、セキュリティリスクが高い状態でした。
また、base64は暗号化ではなく、ただのエンコーディングなので、簡単に解読されてしまいます。
# 従来の方法:手動でSecretを作成する必要があった
apiVersion: v1
kind: Secret
metadata:
name: db-secret
data:
username: YWRtaW4= # base64エンコードが必要
password: cGFzc3dvcmQ= # パスワードがコードに埋め込まれる
External Secrets Operatorは、これらの課題を解決します。シークレットをAWS Secrets Managerなどの外部サービスで一元管理し、Kubernetesクラスター内では自動的にSecretリソースを作成・更新する仕組みを提供します。パスワードをコードに書く必要がなくなり、パスワード変更時も自動で同期されるため、セキュリティが大幅に向上し、運用負荷も軽減されます。
動作の流れ:
- AWS Secrets Managerにシークレットを保存
- External Secrets OperatorがAWS Secrets Managerを定期的に監視、シークレットを取得
- External Secrets OperatorがKubernetes Secretを自動作成・更新
- アプリケーションがそのSecretを利用
- 環境変数パターン: valueFrom secretKeyRefで環境変数として注入
- ファイルマウントパターン: volumeMountでファイルとしてマウント
メリット:
- セキュリティ向上: パスワードをコードに書かない
- 自動同期: パスワード変更時に自動でKubernetesに反映
- 中央管理: AWS Secrets Managerで一元管理
- 監査: アクセスログが残る
- ローテーション: 自動パスワードローテーション対応
システム構成
構築したシステムは、AWS上でマネージドサービスを活用したセキュアなアーキテクチャを採用しています。RDSの認証情報をAWS Secrets Managerで管理し、External Secrets OperatorがEKSクラスター内のKubernetes Secretとして自動同期する仕組みになっています。StreamlitアプリケーションはALB経由でインターネットからアクセス可能で、同期されたシークレットを使用してRDS PostgreSQLに接続します。
システムの主要コンポーネント:
- EKS: Kubernetesクラスターを提供するマネージドサービス
- AWS Secrets Manager: RDS PostgreSQLの認証情報を安全に保存
- External Secrets Operator: Secrets ManagerからKubernetes Secretを自動生成
- Streamlit App: PostgreSQLに接続してメッセージを保存・表示
- RDS PostgreSQL: データの永続化
- ALB: インターネットからのアクセス
プロジェクト構成
共通インフラストラクチャと各パターン固有の実装に分かれた構成になっています。infrastructure/
フォルダには両パターンで共通利用するAWSリソース(VPC、RDS、Secrets Manager等)とEKSクラスターの定義が含まれており、各パターンフォルダには、それぞれ異なるシークレット利用方式に対応したStreamlitアプリケーションとKubernetesマニフェストが配置されています。
eks-external-secret-operator/
├── infrastructure/ # 共通インフラ(CloudFormation、EKS)
│ ├── cloudformation/
│ └── eksctl/
│
├── env-vars-pattern/ # 環境変数パターン
│ ├── app/
│ └── infrastructure/manifests/ # Kubernetesマニフェストのみ
│
└── file-mount-pattern/ # ファイルマウントパターン
├── app/
└── infrastructure/manifests/ # Kubernetesマニフェストのみ
共通インフラ:
-
AWSインフラ:
infrastructure/cloudformation/infrastructure.yaml
で定義 -
EKSクラスター:
infrastructure/eksctl/create-cluster-nodegroup.yaml
で構成 - External Secrets Operator: 同じ設定を使用
- AWS Secrets Manager: 同じSecretを使用
違いはKubernetesマニフェストとアプリケーションコードのみとなっています。
実装手順
システムのデプロイは段階的に進めていきます。まず共通のインフラストラクチャを構築し、その後各パターンに応じたアプリケーションをデプロイします。
共通手順(両パターン共通)
1. インフラストラクチャのデプロイ
AWSコンソールからCloudFormationを使用してインフラストラクチャをデプロイします。AWSコンソールでCloudFormationサービスにアクセスし、「スタックの作成」を選択して、infrastructure/cloudformation/infrastructure.yaml
ファイルをアップロードします。このテンプレートには、VPC、サブネット、RDS、Secrets Manager、ECRなどの必要なAWSリソースが定義されています。
RDS Secrets Manager設定(CloudFormation)
# RDS PostgreSQLインスタンスとSecrets Managerの設定
RDSInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: test-postgres
DBInstanceClass: db.t3.micro
Engine: postgres
MasterUsername: !Ref DBMasterUsername
MasterUserPassword: !Ref DBMasterUserPassword
ManageMasterUserPassword: true # Secrets Managerで自動管理
MasterUserSecret:
SecretArn: !Ref RDSSecret
RDSSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: test-rds-secret
GenerateSecretString:
SecretStringTemplate: '{"username": "dbuser"}'
GenerateStringKey: 'password'
PasswordLength: 32
ExcludeCharacters: '"@/\'
2. EKSクラスターの作成
eksctlコマンドを使用してEKSクラスターを作成します。Auto Modeで作成しています。
# EKSクラスター作成
eksctl create cluster -f infrastructure/eksctl/create-cluster-nodegroup.yaml
EKS Auto Mode設定(eksctl)
# EKS Auto Mode クラスター設定
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: test-usw2-eks-cluster
region: us-west-2
version: "1.31"
# Auto Mode設定
accessConfig:
bootstrapClusterCreatorAdminPermissions: true
authenticationMode: API_AND_CONFIG_MAP
computeConfig:
enabled: true # Auto Modeを有効化
nodeRoleArn: "arn:aws:iam::aws:role/eks-node-group-role"
storageConfig:
blockStorage:
enabled: true
fileSystemStorage:
enabled: true
3. External Secrets Operatorのインストール
Helmを使用してExternal Secrets Operatorをインストールし、AWS Secrets ManagerにアクセスするためのIAMロールを作成します。
Auto Modeでは、OIDCプロバイダーがデフォルトで作成されています。
# External Secrets Operatorをインストール
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace
# IAMロール作成(External Secrets Operator用)
eksctl create iamserviceaccount \
--cluster=test-usw2-eks-cluster \
--namespace=external-secrets \
--name=external-secrets-sa \
--attach-policy-arn=arn:aws:iam::aws:policy/SecretsManagerReadWrite \
--override-existing-serviceaccounts \
--region us-west-2 \
--approve
環境変数パターンの実装
ここからはパターン別に進めます。
環境変数パターンでは、シークレットを環境変数として注入する方式を採用しています。まずSecretStoreとExternalSecretを作成し、その後アプリケーションをデプロイします。
4. SecretStoreとExternalSecretの作成
External Secrets Operator が AWS Secrets Manager からシークレットを取得するための設定を適用します。
kubectl apply -f env-vars-pattern/infrastructure/manifests/external-secrets.yml
External Secrets マニフェスト(環境変数パターン)
# SecretStore設定
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: external-secrets
spec:
provider:
aws:
service: SecretsManager
region: us-west-2
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
# ExternalSecret設定
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: rds-external-secret
namespace: external-secrets
spec:
refreshInterval: 5m
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: rds-secret
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: test-rds-secret
property: username
- secretKey: password
remoteRef:
key: test-rds-secret
property: password
5. アプリケーションのデプロイ
DockerイメージをビルドしてECRにプッシュし、Kubernetesマニフェストをデプロイします。
# ECRにDockerイメージをプッシュ
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin <ACCOUNT-ID>.dkr.ecr.us-west-2.amazonaws.com
# Dockerイメージのビルドとプッシュ
docker build -t test-ecr env-vars-pattern/app/
docker tag test-ecr:latest <ACCOUNT-ID>.dkr.ecr.us-west-2.amazonaws.com/test-ecr:latest
docker push <ACCOUNT-ID>.dkr.ecr.us-west-2.amazonaws.com/test-ecr:latest
# Kubernetesマニフェストをデプロイ
kubectl apply -f env-vars-pattern/infrastructure/manifests/manifest.yml
アプリケーションでの環境変数利用
# Deployment設定(環境変数パターン)
spec:
template:
spec:
containers:
- name: streamlit-app
env:
- name: DB_HOST
value: "test-postgres.cx644isqscfp.us-west-2.rds.amazonaws.com"
- name: DB_NAME
value: "postgres"
- name: DB_USER
valueFrom:
secretKeyRef:
name: rds-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: rds-secret
key: password
ファイルマウントパターンの実装
続いて、ファイルマウントパターンでは、シークレットをファイルとしてコンテナ内にマウントする方式を採用しています。セキュリティ面でより優れた方法ですが、アプリケーション側でファイルを読み込む実装が必要になります。
4. SecretStoreとExternalSecretの作成
ファイルマウント用の設定を適用します。
kubectl apply -f file-mount-pattern/infrastructure/manifests/external-secrets.yml
External Secrets マニフェスト(ファイルマウントパターン)
# SecretStore設定(ファイルマウントパターンも同じ設定)
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: external-secrets
spec:
provider:
aws:
service: SecretsManager
region: us-west-2
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
# ExternalSecret設定(ファイルマウントパターンも同じ設定)
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: rds-external-secret
namespace: external-secrets
spec:
refreshInterval: 5m
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: rds-secret
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: test-rds-secret
property: username
- secretKey: password
remoteRef:
key: test-rds-secret
property: password
5. アプリケーションのデプロイ
ファイルマウントパターン用のアプリケーションをデプロイします。
# ECRにDockerイメージをプッシュ
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin <ACCOUNT-ID>.dkr.ecr.us-west-2.amazonaws.com
# Dockerイメージのビルドとプッシュ
docker build -t test-ecr file-mount-pattern/app/
docker tag test-ecr:latest <ACCOUNT-ID>.dkr.ecr.us-west-2.amazonaws.com/test-ecr:latest
docker push <ACCOUNT-ID>.dkr.ecr.us-west-2.amazonaws.com/test-ecr:latest
# Kubernetesマニフェストをデプロイ
kubectl apply -f file-mount-pattern/infrastructure/manifests/manifest.yml
ファイルマウントパターンの設定
# Deployment設定(ファイルマウントパターン)
spec:
template:
spec:
containers:
- name: streamlit-app
env:
- name: DB_HOST
value: "test-postgres.cx644isqscfp.us-west-2.rds.amazonaws.com"
- name: DB_NAME
value: "postgres"
volumeMounts:
- name: db-secrets
mountPath: "/etc/secrets"
readOnly: true
volumes:
- name: db-secrets
secret:
secretName: rds-secret
動作確認
システムが正常に動作しているかを確認するため、チェックを行います。
External Secrets Operatorの動作確認
まず、External Secrets Operatorが正常にAWS Secrets Managerからシークレットを取得し、Kubernetes Secretを作成できているかを確認します。
ここで、トラブルが起きましたが、トラブルが起きましたが、下で解説しています。
# External Secrets Operatorが作成したSecretを確認
kubectl get secret rds-secret -n external-secrets -o yaml
# ExternalSecretの状態確認
kubectl get externalsecret -n external-secrets
kubectl describe externalsecret rds-external-secret -n external-secrets
# アプリケーションの状態確認
kubectl get pods -n external-secrets
kubectl logs -f deployment/streamlit-app -n external-secrets
システムの接続の確認
システム全体が正常に動作していることを確認するため、各コンポーネントの状態をチェックします。
- ALBのターゲットグループでPodが正常に登録されている
- ターゲットのヘルスチェックが成功している
アプリケーション画面の確認
デプロイが完了すると、StreamlitアプリケーションがALB経由でアクセス可能になります。
ALBのDNS名からアクセスして、以下の機能を確認できます:
- データベースへの接続状況
- External Secrets Operatorによって取得されたシークレットの表示
- メッセージの投稿・表示機能
このデモアプリケーションは、デモンストレーション目的でRDSの接続情報やシークレットの内容を画面上に表示します。本番環境では機密情報を表示しないよう実装してください。
開発で遭遇した課題と解決方法
開発中に遭遇した主要な課題とその解決方法について説明します。
External Secrets OperatorのSecretSyncedErrorエラー
開発中に発生した問題の一つが、External Secrets OperatorでSecretSyncedError
ステータスが表示されるエラーでした。
# 以下のようなエラーが発生する場合
kubectl get externalsecret -n external-secrets
NAME STORETYPE STORE REFRESH INTERVAL STATUS READY
rds-external-secret SecretStore aws-secrets-manager 5m SecretSyncedError False
SecretSyncedError
は、External Secrets OperatorがAWS Secrets Managerからシークレットを取得できない、またはKubernetes Secretを作成できないことを示しています。詳細なエラー内容を確認するためには、以下のコマンドで詳細情報を取得できます。
# 詳細なエラー情報を確認
kubectl describe externalsecret rds-external-secret -n external-secrets
# External Secrets Operatorのログを確認
kubectl logs -l app.kubernetes.io/name=external-secrets -n external-secrets
この問題の原因は、HelmでインストールしたExternal Secrets Operatorのバージョンと、適用したexternal-secrets.yml
マニフェストのAPIバージョンが異なっていたことでした。インストールしたExternal Secrets Operatorは最新版でv1
APIをサポートしていましたが、マニフェストでは古いv1beta1
を使用していたため、互換性の問題が発生しました。解決方法として、external-secrets.yml
のAPIバージョンをインストールしたバージョンに合わせて更新する必要がありました。
# 修正前(v1beta1)
apiVersion: external-secrets.io/v1beta1
spec:
# 修正後(v1)
apiVersion: external-secrets.io/v1
spec:
パターン比較
2つのパターンにはそれぞれ特徴があり、用途に応じて適切に選択する必要があります。
項目 | 環境変数パターン | ファイルマウントパターン |
---|---|---|
実装の簡単さ | ✅ シンプル | ❌ 少し複雑 |
kubectl操作での露出 | ❌ 見える | ✅ 見えない |
推奨環境 | デモ・開発 | 本番 |
シークレット利用方法 | valueFrom secretKeyRef | volumeMount + ファイル読み込み |
環境変数パターンは実装がシンプルで、開発やデモ環境での利用に適しています。一方、ファイルマウントパターンは実装が少し複雑になりますが、セキュリティ面で優れており、本番環境での利用に推奨されます。要件とセキュリティレベルに応じて、適切なパターンを選択してください。
まとめ
External Secrets Operatorを使用してAWS Secrets ManagerとKubernetesを連携させる2つのアプローチを実装しました。従来の手動でのシークレット管理から脱却し、自動化されたセキュアなシークレット管理システムを構築することで、運用負荷の軽減とセキュリティの向上を実現できました。
External Secrets Operatorは、Kubernetesでのシークレット管理を大幅に改善するツールです。このデモの実装例が、同様のシステムを構築する際の参考になれば幸いです。