本記事には広告(アフィリエイトリンク)が含まれます。

ConfigMapとSecret入門|CKAD第10回

広告

新卒インフラエンジニア向け「Kubernetes 実践教科書① CKAD アプリケーション開発編」の第10回です。第9回で PostgreSQL を追加して 3 層アプリを完成させました。ただし DB 接続情報(ホスト名・ユーザー・パスワード)は Pod のマニフェストに直接(inline)書いたままです。今回は ConfigMapSecret で設定値と機密情報をコードから切り離しServiceAccount の基礎も学びます。設定を外部化すると、同じイメージのまま環境ごとに値を変えられ、パスワードをマニフェストや Git に残さずに済みます。

動作確認バージョン:kind v0.31.0(K8s v1.35.0)/ kubectl v1.35.6 / fanclub-backend:0.3.0 / fanclub-frontend:0.1.0 / postgres:18(2026-06-27 実機検証時点)

広告

今ここマップ(全 19 回中の現在地)

現在地は第 3 部「アプリリソース」の第 10 回です。完成した 3 層アプリの設定を外部化して整えます。

  • 第 1 部 コンテナと Docker(第 1〜4 回)
  • 第 2 部 Kubernetes 基礎(第 5〜6 回)
  • 第 3 部 アプリリソース(第 7〜11 回)← 今ここ
  • 第 4 部 ワークロード戦略(第 12〜14 回)
  • 第 5 部 セキュリティ基礎(第 15〜16 回)
  • 第 6 部 パッケージ管理と HTTPS 公開(第 17〜19 回)

この回のゴール

  • ConfigMap(非機密設定)と Secret(機密情報)を使い分け、外部化できる。
  • envFrom / valueFrom / Volume マウントで設定を Pod に注入できる。
  • ServiceAccount の役割と Pod への紐付け、automountServiceAccountToken: false の意味を説明できる。

設定をコードから切り離す理由

接続情報やパラメータをイメージや Pod 定義に埋め込むと、次の問題が起きます。

  • 環境差分に弱い:dev と prod で DB ホストが違うたびにマニフェストを書き換える羽目になります。
  • 機密が漏れる:パスワードを Pod 定義に平文で書くと、マニフェストや Git に残ります。
  • 再ビルドが要る:設定をイメージに焼き込むと、値を変えるだけでイメージを作り直すことになります。

設定値を ConfigMap、機密情報を Secret として外部化すれば、同じイメージのまま値だけを差し替えられます。第9回の Backend イメージ(0.3.0)は接続情報を環境変数から読む作りなので、今回イメージは作り直しません。設定の置き場所を Pod 直書きから ConfigMap / Secret に移すだけです。「設定変更にイメージの再ビルドは要らない」——これが外部化の最大の利点です。

ConfigMap(非機密設定)

作成方法

ConfigMap はキーと値の集合です。作り方は 3 通りあります。

  • --from-literal=KEY=VALUE:コマンドで直接キー・値を指定。
  • --from-file=ファイル:ファイル内容を値にする(設定ファイルの取り込み)。
  • YAML マニフェスト:data: に key: value を列挙して kubectl apply

本回は再現性と版管理のため YAML で作ります。--from-literal の例も示します。実行コマンド(リテラル指定の例・後で YAML 版に統一するのでここでは作成しません):

$ kubectl create configmap sample --from-literal=DB_HOST=fanclub-db --dry-run=client -o yaml

--dry-run=client -o yaml は、実際には作らずに生成される YAML を表示します。マニフェストの雛形づくりに便利です。

注入方法(envFrom と valueFrom)

  • envFrom:ConfigMap / Secret の全キーを環境変数として一括注入します。
  • valueFrom特定の 1 キーだけを選んで 1 つの環境変数にします(configMapKeyRef / secretKeyRef)。
  • Volume マウント:ConfigMap / Secret をファイルとしてマウントします(設定ファイルや証明書向け)。

本回の fanclub-backend は全キーをまとめて使うので envFrom を使います。一方、特定の 1 キーだけを別名の環境変数に入れたいときは valueFrom を使います(書き方の例)。

env:
- name: DB_HOST
  valueFrom:
    configMapKeyRef:
      name: fanclub-config
      key: DB_HOST
ConfigMap(非機密設定)と Secret(機密情報)から Pod へ設定を注入する 3 経路を示す図。envFrom は全キーを環境変数へ一括注入、valueFrom は特定キーを 1 環境変数へ、Volume マウントはファイルとして注入する。
図1:ConfigMap / Secret から Pod への注入経路(envFrom / valueFrom / Volume)

Secret(機密情報)

base64 は暗号化ではない

Secret の値は etcd 上で base64 エンコードされて保存されますが、base64 は暗号化ではありません。誰でも元に戻せるので、「Secret に入れたから安全」とは言えません。Secret の本当の価値は、ConfigMap と分けて扱える(RBAC で参照を絞る・ログや describe に出にくい・専用の注入経路を持つ)点にあります。保存時の本格的な暗号化(etcd encryption at rest)は本シリーズ第3巻 CKS で扱います。

YAML で Secret を書くときは data:(base64 済みの値)ではなく stringData:(平文で書くと Kubernetes が base64 化)が便利です。kubectl create secret generic --from-literal=... でも作れます。

secretRef と Volume マウント

Secret も ConfigMap と同様、envFromsecretRef)で環境変数に一括注入するか、valueFrom.secretKeyRef で 1 キーずつ注入できます。証明書のようなファイル形式の機密は Volume マウントを使います。本回の DB ユーザー・パスワードは環境変数として渡します。

ServiceAccount 基礎

ServiceAccount とは

ServiceAccount(SA)は、Pod が Kubernetes API と通信するときに使うクラスタ内のアイデンティティ(認証情報)です。人間が使うユーザーアカウントに対し、SA は Pod(プロセス)用です。Pod に SA を指定しないと、その Namespace の default SA が自動で割り当てられます。

トークンの自動マウントを止める

既定では SA のトークンが Pod 内の /var/run/secrets/kubernetes.io/serviceaccount/自動でマウントされます。しかし、API を呼ばないアプリにトークンを置くのは不要なリスクです(漏れれば API 操作に悪用されうる)。fanclub-backend は DB と通信するだけで Kubernetes API を呼ばないので、automountServiceAccountToken: false でトークンのマウントを止めます。これは最小権限の基本です。

SA と API Server 認証の関係

マウントされたトークンは JWT で、API Server はこれを検証して「どの SA からの要求か」を識別します。その SA が何をできるかRBAC(Role / RoleBinding)で決まります。SA は「誰であるか(認証)」、RBAC は「何を許すか(認可)」の担当です。RBAC の本格実装は第15回で扱います。本回は SA の作成と紐付け、トークン抑止までを押さえます。

Pod に紐づく ServiceAccount のトークンが API Server で検証され、RBAC で認可される関係を示す図。automountServiceAccountToken: false にするとトークンがマウントされず、API を呼ばないアプリの攻撃面を減らせることを示す。
図2:ServiceAccount と Pod・API Server の関係(認証は SA、認可は RBAC)

fanclub-api への適用

ConfigMap・Secret・ServiceAccount を作る

次のマニフェストを developer のホームに fanclub-config.yaml として作成します(一般ユーザー所有のファイルで、kubectl apply はクラスタへ送るだけなので root 権限は不要です)。非機密の設定は ConfigMap、DB のユーザー・パスワードは Secret、Backend 用の専用 SA をまとめて定義します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fanclub-config
data:
  DB_HOST: fanclub-db
  DB_PORT: "5432"
  DB_NAME: fanclubdb
  API_PORT: "8080"
  API_BASE_URL: /api
  JAVA_OPTS: "-XX:MaxRAMPercentage=75.0 -Djava.io.tmpdir=/opt/payara/tmp"
---
apiVersion: v1
kind: Secret
metadata:
  name: fanclub-db-secret
type: Opaque
stringData:
  DB_USER: appuser
  DB_PASSWORD: apppassword
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fanclub-backend
  • 数値も環境変数では文字列なので DB_PORT: "5432" とクオートします。
  • API_PORT / API_BASE_URL は設定一覧に合わせた予約キーです。現状の Backend は待受 8080・ベースパス /api がコード側で固定のため参照しません(Frontend のベース URL は後述の別 ConfigMap config.js で渡します)。値を変えても挙動は変わらない点に注意してください。
  • Secret は stringData に平文で書くと Kubernetes が base64 化して保存します。
  • JAVA_OPTS-Djava.io.tmpdir=/opt/payara/tmp必ず含めますenvFrom で渡す値は、イメージ側(Dockerfile の ENV JAVA_OPTS)の値を上書きします。ここで省くと Payara の書き込み先(tmpdir)指定が失われ、読み取り専用ルートでは起動に失敗します。

実行コマンド:

$ kubectl apply -f fanclub-config.yaml
$ kubectl get configmap fanclub-config
$ kubectl get secret fanclub-db-secret
$ kubectl get serviceaccount fanclub-backend

実行結果(例):

configmap/fanclub-config created
secret/fanclub-db-secret created
serviceaccount/fanclub-backend created
NAME             DATA   AGE
fanclub-config   6      2s
NAME                TYPE     DATA   AGE
fanclub-db-secret   Opaque   2      2s
NAME              AGE
fanclub-backend   2s

ここで「base64 は暗号化ではない」を実際に確かめます。Secret の data の値は base64 なので、参照権限があれば誰でも平文に戻せます(だからこそ Secret は RBAC で参照を絞ることが重要です)。

実行コマンド:

$ kubectl get secret fanclub-db-secret -o jsonpath='{.data.DB_PASSWORD}'; echo
$ kubectl get secret fanclub-db-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d; echo

実行結果(例):1 行目が保存されている base64、2 行目がそれを復号した平文です。これがあるため「Secret に入れたら暗号化されて安全」ではありません。

YXBwcGFzc3dvcmQ=
apppassword

Backend Pod を envFrom 版に作り直す

第9回の Backend Pod は接続情報を env に直接書いていました。これを envFrom(ConfigMap + Secret)に置き換え、専用 SA を紐付け、トークンのマウントを止めます。イメージは 0.3.0 のままです。次のマニフェストを developer のホームに fanclub-backend-pod.yaml として作成します(一般ユーザー所有・root 不要)。

apiVersion: v1
kind: Pod
metadata:
  name: fanclub-backend
  labels:
    app: fanclub-backend
spec:
  serviceAccountName: fanclub-backend
  automountServiceAccountToken: false
  initContainers:
  - name: wait-for-db
    image: busybox:1.37
    command:
    - sh
    - -c
    - 'until nslookup fanclub-db.default.svc.cluster.local >/dev/null 2>&1; do echo "waiting for fanclub-db..."; sleep 2; done; echo "fanclub-db resolved"'
  - name: log-shipper
    image: busybox:1.37
    restartPolicy: Always
    command:
    - sh
    - -c
    - 'echo "sidecar started"; tail -F /var/log/app/app.log 2>/dev/null || sleep infinity'
    volumeMounts:
    - name: applog
      mountPath: /var/log/app
  containers:
  - name: backend
    image: k8s-registry:5000/fanclub-backend:0.3.0
    ports:
    - containerPort: 8080
    envFrom:
    - configMapRef:
        name: fanclub-config
    - secretRef:
        name: fanclub-db-secret
    resources:
      requests:
        memory: "512Mi"
        cpu: "250m"
      limits:
        memory: "768Mi"
        cpu: "1000m"
    volumeMounts:
    - name: applog
      mountPath: /var/log/app
  volumes:
  - name: applog
    emptyDir: {}
  • envFrom で ConfigMap と Secret の全キーが環境変数として注入されます(DB_HOST 等は第9回と同じ名前なので、アプリ側は変更不要)。
  • serviceAccountName で専用 SA を紐付け、automountServiceAccountToken: false でトークンを置きません。
  • envFrom が参照する fanclub-config / fanclub-db-secret先に作成しておきます(本記事では前項で apply 済み)。未作成のまま Pod を作ると CreateContainerConfigError で起動しません。

Pod は作り直しが必要:稼働中の Pod に envFrom や SA を後から足そうと kubectl apply しても、Pod の大半のフィールドは変更不可で拒否されます。Pod は一度 delete してから apply で作り直します(第12回の Deployment を使うと、この入れ替えが自動になります)。

実行コマンド:

$ kubectl delete pod fanclub-backend
$ kubectl apply -f fanclub-backend-pod.yaml
$ kubectl get pod fanclub-backend

実行結果(例):本体 + ネイティブ Sidecar で 2/2 になります。

NAME              READY   STATUS    RESTARTS   AGE
fanclub-backend   2/2     Running   0          25s

外部化した設定で DB につながるか、第9回と同じく /health/ready と一覧で確認します。実行コマンド:

$ kubectl run c1 --rm -i --restart=Never --image=curlimages/curl:latest -- \
    curl -s http://fanclub-backend:8080/health/ready
$ kubectl run c2 --rm -i --restart=Never --image=curlimages/curl:latest -- \
    curl -s http://fanclub-backend:8080/api/members

実行結果(例):UP になり、第9回で登録した会員が引き続き取得できます(DB のデータは PVC に残っています)。

{"status":"UP","checks":[{"name":"fanclub-api-ready","status":"UP","data":{}}]}
[{"createdAt":"2026-06-27T00:04:07.560548","email":"taro@example.com","id":1,"name":"山田太郎","plan":"premium"}]

環境変数が ConfigMap / Secret 由来で入っているか、kubectl exec で確認します。

実行コマンド:

$ kubectl exec fanclub-backend -c backend -- \
    sh -c 'echo "$DB_HOST / $DB_NAME / $DB_USER"'

実行結果(例):ConfigMap の DB_HOST / DB_NAME と Secret の DB_USER が注入されています。

fanclub-db / fanclubdb / appuser

SA トークンがマウントされていないことも確認します。実行コマンド:

$ kubectl exec fanclub-backend -c backend -- \
    ls /var/run/secrets/kubernetes.io/serviceaccount/

実行結果(例):ディレクトリが存在せずエラーになります。これがトークン抑止の効果です(API を呼ぶ必要がある Pod では、必要な SA だけにトークンを許可します)。

ls: cannot access '/var/run/secrets/kubernetes.io/serviceaccount/': No such file or directory
command terminated with exit code 2

Frontend の API_BASE_URL を外部化する

Frontend(Nginx)が配信する静的 JavaScript には環境変数が直接届きません。そこで API のベース URL を config.js という小さな設定ファイルにまとめ、これを ConfigMap として Volume マウントして差し替えます。次を developer のホームに fanclub-frontend-config.yaml として作成します(一般ユーザー所有・root 不要)。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fanclub-frontend-config
data:
  config.js: |
    window.API_BASE_URL = "/api";
---
apiVersion: v1
kind: Pod
metadata:
  name: fanclub-frontend
  labels:
    app: fanclub-frontend
spec:
  containers:
  - name: frontend
    image: k8s-registry:5000/fanclub-frontend:0.1.0
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "32Mi"
        cpu: "50m"
      limits:
        memory: "128Mi"
        cpu: "200m"
    volumeMounts:
    - name: config
      mountPath: /usr/share/nginx/html/config.js
      subPath: config.js
  volumes:
  - name: config
    configMap:
      name: fanclub-frontend-config
  • subPath: config.js が要点です。subPath なしでディレクトリにマウントすると、/usr/share/nginx/html/ 内の index.html などが隠れてしまいますsubPathconfig.js という1 ファイルだけを差し替えます。
  • Frontend イメージも 0.1.0 のまま(再ビルド不要)です。

Frontend Pod も作り直します。実行コマンド:

$ kubectl delete pod fanclub-frontend
$ kubectl apply -f fanclub-frontend-config.yaml
$ kubectl run cj --rm -i --restart=Never --image=curlimages/curl:latest -- \
    curl -s http://fanclub-frontend/config.js

実行結果(例):配信される config.js が ConfigMap の内容になります(他の静的ページも従来どおり配信され、/api 経由の CRUD も動きます)。

window.API_BASE_URL = "/api";

本番の Secret 管理(概念)

本回の Secret は学習用に平文の値を YAML に書きました。実運用では、パスワードを Git に置かないために 外部のシークレットストア(HashiCorp Vault など)や、それを Kubernetes に同期する External Secrets Operator を使うのが一般的です。これらは本巻の範囲外として概念紹介にとどめます(保存時暗号化やシークレット運用は第3巻 CKS で詳しく扱います)。

やってみよう

  1. fanclub-config.yaml(ConfigMap + Secret + ServiceAccount)を作成して apply し、get で 3 つが作られたことを確認する。
  2. Backend Pod を deleteenvFrom 版で apply し直し、2/2 Running にする。
  3. /health/readyUP/api/members で会員が取得できることを確認する(外部化後も DB 接続が成立)。
  4. kubectl execDB_HOST / DB_USER が注入され、SA トークンのディレクトリが無いことを確認する。
  5. fanclub-frontend-config.yamlconfig.js を ConfigMap マウントし、/config.js が ConfigMap の内容になることを確認する。

理解度チェック

次の各文が正しいか(○)誤りか(×)を判断してください。解答は下にまとめています。

  1. ConfigMap は非機密の設定値、Secret は機密情報を扱うために使い分ける。
  2. Secret の値は base64 されているので暗号化されており、それだけで安全である。
  3. envFrom は ConfigMap / Secret の全キーを環境変数として一括注入する。
  4. valueFrom は特定の 1 キーだけを選んで 1 つの環境変数にできる。
  5. automountServiceAccountToken: false で SA トークンの自動マウントを止められる。
  6. ConfigMap の値を変えたら、必ずアプリのイメージを再ビルドしなければならない。
  7. 静的 JS に設定を渡すには、ConfigMap を subPath でファイルとしてマウントする方法がある。

解答

  • 1. ○:非機密は ConfigMap、機密は Secret。
  • 2. ×:base64 はエンコードであり暗号化ではない。誰でも復号できる。
  • 3. ○:envFrom は全キーを一括注入する。
  • 4. ○:configMapKeyRef / secretKeyRef で 1 キーずつ注入する。
  • 5. ○:API を呼ばない Pod ではトークンを置かないのが安全。
  • 6. ×:外部化していれば値の変更に再ビルドは不要。これが設定外部化の利点。
  • 7. ○:config.jssubPath でマウントして差し替える。

まとめ

本記事では、設定をコードから切り離す理由を起点に、ConfigMap(非機密)と Secret(機密・base64 は暗号化ではない)の使い分け、envFrom / valueFrom / Volume マウントの注入経路、ServiceAccount の役割と automountServiceAccountToken: false を学びました。fanclub-api では DB 接続情報を ConfigMap / Secret に外部化し、専用 SA を紐付け、Frontend の config.js も ConfigMap で差し替えました。イメージは作り直さず、設定の置き場所を変えるだけで環境差分や機密に強い構成になりました。

次回予告

次回(第11回)は Job / CronJob / DaemonSet を学びます。一度きりの処理を実行する Job で DB マイグレーションを実装し、定期実行の CronJob、全ノードに 1 つずつ配置する DaemonSet を扱います。

シリーズ一覧

第1部:コンテナと Docker

第2部:Kubernetes 基礎

第3部:アプリリソース

第4部:ワークロード戦略

第5部:セキュリティ基礎

第6部:パッケージ管理 + HTTPS 公開

広告
kubernetes
スポンサーリンク