2026年8月22日
2026年8月22日
KubernetesのSecretで機密情報を管理する|作り方・Podへの渡し方と「base64は暗号化ではない」前提の守り方
KubernetesのSecretは、データベースのパスワードやAPIキー、TLS証明書などをPodに渡すための仕組みです。ただし、Secretに入れただけでは中身は守られません。値は base64 で符号化されているだけで、読む権限がある人には平文と同じです。この記事では、Secretの作り方とPodへの渡し方に加えて、保存時の暗号化、RBACでの読み取り制限、Gitに平文を置かない運用までを順に説明します。
コマンドとYAMLは、Kubernetesの現行のサポート対象の版(2026年10月現在)で共通して使える内容に絞っています。マネージドサービス固有の設定は、各サービスの公式ドキュメントで確認してください。
Secretとは何か、何を守ってくれないか
Secretは、ConfigMapと同じく「キーと値の組」をクラスターに保存するオブジェクトです。違いは、機密情報として扱う前提で、次のような配慮がある点です。
- Podの定義やイメージに値を書かずに済み、設定と秘密情報を分けて管理できる。
- kubeletは、そのノードで動くPodが使うSecretだけを取得する。
- ボリュームとして渡すと、ノード上ではメモリ上のファイルシステム(tmpfs)に置かれ、ディスクに書かれにくい。
一方で、次の点は既定では守られません。ここを誤解したまま運用するのが、Secretで一番多い失敗です。
- base64は暗号化ではない:
kubectl get secret -o yamlで見える値は、base64 -dで誰でも元に戻せます。 - etcdには既定で暗号化されずに保存される:自前で構築したクラスターでは、保存時の暗号化を自分で設定する必要があります。
- 同じ名前空間でPodを作れる人は、その名前空間のSecretを読める:Secretを直接
getできなくても、Secretをマウントしたり環境変数に入れたりするPodを作れば中身を取り出せます。
Secretを作る
kubectlで作る
# ファイルから作る(値をシェル履歴に残さないため、こちらを推奨)
printf '%s' 'your-db-password' > ./db-password.txt
kubectl create secret generic wp-db \
--from-literal=username=wordpress \
--from-file=password=./db-password.txt \
-n wordpress
rm ./db-password.txt
# 確認(値そのものは表示されない)
kubectl describe secret wp-db -n wordpress
--from-literal に本物のパスワードを書くと、シェルの履歴に残ります。上の例ではユーザー名だけを --from-literal にし、パスワードはファイル経由にしています。printf '%s' を使うのは、echo だと末尾に改行が入り、パスワードの一部として扱われて認証に失敗することがあるためです。
YAMLで作る
apiVersion: v1
kind: Secret
metadata:
name: wp-db
namespace: wordpress
type: Opaque
stringData:
username: wordpress
password: your-db-password
data に書く場合は base64 に変換した値が必要ですが、stringData なら平文で書け、保存時にKubernetesが変換します。ただし、このYAMLファイル自体が平文の秘密情報になります。そのままGitに入れないでください。運用方法は後半で説明します。
type には、用途別の種類があります。汎用の Opaque のほか、TLS証明書用の kubernetes.io/tls、プライベートレジストリの認証用の kubernetes.io/dockerconfigjson などがあり、種類によって必須のキーが決まっています。TLS用は kubectl create secret tls、レジストリ用は kubectl create secret docker-registry で作ると、形式の誤りを防げます。
1つのSecretの大きさは1MiBまでです。大きなファイルを入れる用途には向きません。
Podに渡す
渡し方は「環境変数」と「ファイル(ボリューム)」の2通りです。WordPressの公式Dockerイメージを例にします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
namespace: wordpress
spec:
replicas: 1
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:6-php8.3-apache
env:
- name: WORDPRESS_DB_HOST
value: mysql
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: wp-db
key: username
- name: WORDPRESS_DB_PASSWORD_FILE
value: /run/secrets/wp-db/password
volumeMounts:
- name: db-secret
mountPath: /run/secrets/wp-db
readOnly: true
volumes:
- name: db-secret
secret:
secretName: wp-db
defaultMode: 0400
ユーザー名は環境変数、パスワードはファイルで渡しています。WordPressの公式イメージは、WORDPRESS_DB_PASSWORD などの変数名に _FILE を付けると、そのファイルの中身を値として読む作りになっています(対応する変数はイメージの説明ページで確認してください)。
どちらを選ぶかの目安は次のとおりです。
- 環境変数:アプリ側の対応が要らず手軽です。ただし、プロセスの環境変数はクラッシュ時のダンプやデバッグ出力に混ざりやすく、
kubectl describe podで参照元が見えます。また、Secretを更新してもPodを作り直すまで値は変わりません。 - ボリューム:漏れにくく、Secretを更新するとしばらくしてファイルの中身も更新されます。ただし
subPathでマウントした場合は更新されません。アプリが起動時に一度だけ読む作りなら、結局は再起動が必要です。
Secretを更新した後に確実を期すなら、kubectl rollout restart deployment/wordpress -n wordpress でPodを入れ替えるのが分かりやすい方法です。
参照先のSecretやキーが存在しないと、Podは起動せず CreateContainerConfigError になります。kubectl describe pod の Events に、見つからないSecret名やキー名が出るので、名前空間とつづりを確認してください。Secretは名前空間ごとのオブジェクトで、別の名前空間のSecretは参照できません。
保存時の暗号化を確認する
Secretの実体はetcdに保存されます。自前で構築したクラスターでは、kube-apiserverに --encryption-provider-config で暗号化の設定ファイル(EncryptionConfiguration)を渡し、保存時に暗号化させます。鍵をクラウドの鍵管理サービスに預ける kms プロバイダーを使うと、鍵とデータを分けて管理できます。
設定を有効にした後に作られたSecretだけが暗号化されます。既存のSecretは、次のように書き直して暗号化し直します。作業前にetcdのバックアップを取ってください。
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
EKS・GKE・AKSなどのマネージドサービスでは、既定で暗号化されているもの、追加の設定で鍵管理サービスと連携できるものなど、扱いがサービスと時期によって異なります。使っているサービスのドキュメントで現在の仕様を確認してください。また、etcdのバックアップファイルにもSecretが含まれます。バックアップの保管場所の権限も、本番環境と同じ水準で管理します。
RBACで読める人を絞る
Secretを守るうえで最も効くのが権限の絞り込みです。
- Secretへの
get・list・watchを、必要なサービスアカウントと管理者だけに与える。listやwatchでも中身が取れる点に注意します。 - 組み込みの
viewロールはSecretを読めませんが、editとadminは読めます。開発者にeditを配ると、Secretも見える状態になります。 - Podを作れる権限は、実質的にその名前空間のSecretを読める権限です。本番の名前空間でPodを作れる人を限定します。
- 秘密情報の種類や担当者が違うアプリは、名前空間を分けます。
あるユーザーやサービスアカウントがSecretを読めるかは、次のコマンドで確かめられます。
kubectl auth can-i get secrets -n wordpress --as=system:serviceaccount:wordpress:default
API経由のアクセスを使わないPodでは、サービスアカウントのトークンの自動マウントを止めておくと、Podが乗っ取られたときの被害を減らせます(Podの spec.automountServiceAccountToken: false)。
Gitに平文を置かない運用
マニフェストをGitで管理すると、Secretの扱いが問題になります。よく使われる方法は次のとおりです。
- Sealed Secrets:クラスター内のコントローラーの公開鍵で暗号化した
SealedSecretをGitに置き、クラスター内で復号してSecretを作ります。 - External Secrets Operator:AWS Secrets Manager、Google Cloud Secret Manager、HashiCorp Vaultなど外部の保管庫の値を、Secretとして同期します。
- SOPS:YAMLの値の部分だけを暗号化してGitに置き、適用時に復号します。
- Secrets Store CSI Driver:外部の保管庫の値を、ボリュームとしてPodに直接マウントします。
どれを選んでも、元の平文ファイルや復号用の鍵をリポジトリに入れないこと、誤って入れた場合は履歴から消すだけでなく値そのものを変更することが前提です。各ツールの対応状況や使い方は版によって変わるため、導入時は公式ドキュメントを参照してください。
変更されると困るSecretには immutable: true を付けると、作成後の書き換えを防げます。変えたいときは別名で作り直し、参照先を切り替えます。
まとめ
- Secretの値は base64 で符号化されているだけで、暗号化ではない。
- 作るときは値をシェル履歴に残さないようファイル経由にし、YAMLを使うなら平文のままGitに入れない。
- Podへは環境変数かボリュームで渡す。漏れにくさと更新の反映を重視するならボリューム、更新後はPodの入れ替えで反映を確かめる。
- etcdの保存時の暗号化を有効にし、既存のSecretは書き直して暗号化し直す。
- RBACでSecretの
get・list・watchとPodの作成権限を絞り、Git管理にはSealed SecretsやExternal Secrets Operatorなどを使う。