シリーズ記事一覧
- 第1回:全体像と環境構築
- 第2回:Pod・ReplicaSet・Deployment
- 第3回:Service・Ingress
- 第4回:ConfigMap・Secret・Volume
- 第5回:Namespace・RBAC
- 第6回:トラブルシュートと運用
- 第7回:設計総合演習
📑 目次
- この記事について
- この記事のゴール
- 前提条件
- Docker Composeではこうだった(Before)
- k8sではこうなる(After)
- ハンズオン:設定とデータを分離する
- つまずきポイント(体験談)
- まとめ
- 次回予告
1. この記事について
前回までで、Podを動かし(第2回)、外部からアクセスし(第3回)、アプリケーションが動く状態になりました。
チェーンレストランで言えば「店舗を開店して、お客さんが来れる状態」です。
でも、まだ問題があります。
メニュー表(設定)とレシピ帳(秘密情報)がスタッフの頭の中にしかない。
スタッフが入れ替わったら(Podが再作成されたら)、設定もデータも全部消えてしまいます。
今回は「設定とデータをコンテナから分離する」ことで、Podが使い捨てでも問題ない状態を作ります。
2. この記事のゴール
この記事で書いていること:
- 設定をコンテナに埋め込まない理由
- ConfigMap / Secret / Volume の使い分けの整理
- ConfigMapで環境変数をPodに渡してみた話
- Secretでパスワードを管理してみた話
- Volumeの種類と用途
3. 前提条件
| 項目 | 要件 |
|---|---|
| 前回の完了 | 第3回でServiceを理解済み |
| クラスタ状態 | kindクラスタが起動中 |
# WSL2 Ubuntu で実行
kubectl get nodes
4. Docker Composeではこうだった(Before)
4-1. Docker Composeの設定管理
Docker Composeでは、設定は environment や .env ファイルで管理します。
# docker-compose.yml
services:
api:
image: myapp:latest
ports:
- "3000:3000"
# 環境変数で設定を渡す
environment:
- APP_NAME=MyApp
- RAILS_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/myapp
- SECRET_KEY_BASE=super_secret_key_12345
APP_NAME も SECRET_KEY_BASE も同じ environment に並んでいます。
4-2. でもこの壁がある
| # | 壁 | Docker Composeの現実 |
|---|---|---|
| 壁1 | 秘密情報と一般設定が同じ場所 | DBパスワードもアプリ名も同じ environment に |
| 壁2 | 設定を変えるたびにイメージ再ビルド | 設定がイメージに焼き込まれている場合 |
| 壁3 | コンテナが消えるとデータも消える |
volumes を忘れるとDBデータが全消失 |
🔰 Memo: Docker Composeの environment は手軽で便利です。
でも「DBのパスワードがdocker-compose.ymlに平文で書いてある」のは、本番環境では危険だなと気づきました。
k8sでは設定の種類ごとに管理方法を分けることで、この問題を解決しています。
5. k8sではこうなる(After)
5-1. 3つのリソースで「関心事の分離」
🍽️ 比喩:レストランの情報管理
Docker Composeでは、メニュー表もレシピ帳も同じ引き出しに入れていた。
k8sでは、情報の性質に応じて保管場所を分ける。
情報の種類 k8sリソース 比喩 例 一般的な設定 ConfigMap メニュー表 アプリ名、ログレベル、環境名 秘密情報 Secret レシピ帳(金庫保管) DBパスワード、APIキー 永続データ Volume 食材倉庫 DBデータ、アップロードファイル メニュー表(ConfigMap)は壁に貼っておけばいい。
レシピ帳(Secret)は金庫に入れて鍵をかける。
食材倉庫(Volume)はスタッフが入れ替わっても中身が残る。
5-2. 全体関係図
3つのリソースがPodとどのように連携するかを図で確認します。
🔰 Note: ポイントは「全部Podの外にある」こと。
Podが削除・再作成されても、ConfigMap・Secret・Volumeは残ります。
これが「使い捨てPod」を実現する仕組みだと理解しました。
5-3. ConfigMapの2つの渡し方
ConfigMapには「環境変数として渡す」と「ファイルとして渡す」の2つの方法があります。
それぞれの違いを図にまとめます。
| 方法 | 用途 | 比喩 |
|---|---|---|
| 環境変数 | 単純なキー=値 | 朝礼で口頭伝達「今日の営業時間は10時から」 |
| ファイルマウント | 設定ファイルまるごと | メニュー表を壁に貼り出す |
5-4. Volume の種類
Volumeにはいくつかの種類があり、用途と寿命が異なります。
| 種類 | 比喩 | 寿命 | 用途 |
|---|---|---|---|
| emptyDir | 調理台の共有スペース | Podと同じ(Pod削除で消える) | コンテナ間のデータ共有 |
| hostPath | 店舗の倉庫 | ノードと同じ | テスト用(本番非推奨) |
| PersistentVolume (PV) | 外部の食材倉庫 | 永続 | DB、ファイルストレージ |
🔰 Note: 今回ハンズオンしたのは emptyDir だけです。
PersistentVolumeはクラウド環境(EBS等)で真価を発揮するため、ここでは概念のみ整理しました。
6. ハンズオン:設定とデータを分離する
6-1. 作業ディレクトリ
# WSL2 Ubuntu で実行
mkdir -p ~/k8s-handson/config-secret-volume
cd ~/k8s-handson/config-secret-volume
6-2. Step1:ConfigMapで環境変数をPodに渡す
6-2-1. ConfigMapの作成
以下のファイルを作成:
📂 config-secret-volume/
└── app-configmap.yaml ← Step 1 🆕
# app-configmap.yaml
# ConfigMap = メニュー表(一般的な設定情報)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# キー: 値 の形式で設定を記述
APP_NAME: "MyRestaurant"
APP_ENV: "development"
LOG_LEVEL: "info"
6-2-2. ConfigMapを使うPodの作成
📂 config-secret-volume/
├── app-configmap.yaml ✅ Step 1
└── pod-with-configmap.yaml ← Step 1 🆕
# pod-with-configmap.yaml
# ConfigMapから環境変数を受け取るPod
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo
spec:
containers:
- name: demo
image: busybox
# 起動後、環境変数を表示してスリープ
command: ["sh", "-c", "echo APP_NAME=$APP_NAME && echo APP_ENV=$APP_ENV && echo LOG_LEVEL=$LOG_LEVEL && sleep 3600"]
# ConfigMapから環境変数を一括読み込み
envFrom:
- configMapRef:
name: app-config # ← ConfigMapの名前を指定
6-2-3. マニフェストの構造解説
| YAMLの項目 | 比喩 | 意味 |
|---|---|---|
kind: ConfigMap |
「メニュー表です」 | 一般設定用リソース |
data: |
メニューの内容 | キー=値のペア |
envFrom |
「朝礼で全項目を伝達して」 | ConfigMapの全キーを環境変数として注入 |
configMapRef |
「app-configというメニュー表を使って」 | 参照するConfigMapの指定 |
6-2-4. 実行・確認
# ~/k8s-handson/config-secret-volume/ で実行
# ConfigMapを作成
kubectl apply -f app-configmap.yaml
# ConfigMapの内容を確認
kubectl describe configmap app-config
# Podを作成
kubectl apply -f pod-with-configmap.yaml
# Podのログを確認(環境変数の値が表示される)
kubectl logs configmap-demo
期待される出力:
APP_NAME=MyRestaurant
APP_ENV=development
LOG_LEVEL=info
ConfigMapからPodに環境変数が渡されました。
💡 Podが起動しない / ログが空の場合 → セクション7-2(ConfigMapを変更してもPodに反映されない)を確認
🍽️ メニュー表(ConfigMap)を朝礼(envFrom)で全スタッフ(Pod)に伝達した、ということです。
メニューを変えたいときは、メニュー表だけ書き換えればOK。
スタッフ(コンテナイメージ)を入れ替える必要はありません。
6-3. Step2:Secretでパスワードを管理する
6-3-1. Secretの作成
📂 config-secret-volume/
├── app-configmap.yaml ✅ Step 1
├── pod-with-configmap.yaml ✅ Step 1
└── app-secret.yaml ← Step 2 🆕
# app-secret.yaml
# Secret = レシピ帳(秘密情報)
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
# stringData を使えば平文で書ける(k8sが自動でbase64エンコードする)
stringData:
DB_PASSWORD: "super_secret_password"
API_KEY: "ak_1234567890abcdef"
🔰 Memo: Secretには data(base64エンコード済み)と stringData(平文OK)の2種類があります。
学習では stringData が圧倒的に楽でした。
本番では外部のシークレット管理ツール(AWS Secrets Manager等)と連携するのが一般的なようです。
6-3-2. Secretを使うPodの作成
📂 config-secret-volume/
├── app-configmap.yaml ✅ Step 1
├── pod-with-configmap.yaml ✅ Step 1
├── app-secret.yaml ✅ Step 2
└── pod-with-secret.yaml ← Step 2 🆕
# pod-with-secret.yaml
# ConfigMap と Secret の両方を使うPod
apiVersion: v1
kind: Pod
metadata:
name: secret-demo
spec:
containers:
- name: demo
image: busybox
command: ["sh", "-c", "echo APP_NAME=$APP_NAME && echo DB_PASSWORD=$DB_PASSWORD && echo API_KEY=$API_KEY && sleep 3600"]
# ConfigMapから一般設定を読み込み
envFrom:
- configMapRef:
name: app-config
# Secretから個別に環境変数を読み込み
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret # Secret名
key: DB_PASSWORD # Secretのキー
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secret
key: API_KEY
6-3-3. ConfigMap と Secret の渡し方の違い
| 方法 | 対象 | YAMLの書き方 | 用途 |
|---|---|---|---|
envFrom |
ConfigMap全体 | configMapRef |
一般設定をまとめて渡す |
env[].valueFrom |
Secret個別 | secretKeyRef |
秘密情報を1つずつ渡す |
🍽️ メニュー表(ConfigMap)は壁に貼る=全員が見られる。
レシピ帳(Secret)は必要な人にだけ金庫から取り出して渡す。
6-3-4. 実行・確認
# ~/k8s-handson/config-secret-volume/ で実行
# Secretを作成
kubectl apply -f app-secret.yaml
# Secretの内容を確認(値はbase64で表示される)
kubectl get secret app-secret -o yaml
# Podを作成
kubectl apply -f pod-with-secret.yaml
# ログで確認
kubectl logs secret-demo
期待される出力:
APP_NAME=MyRestaurant
DB_PASSWORD=super_secret_password
API_KEY=ak_1234567890abcdef
ConfigMapとSecretの両方がPodに渡されました。
6-3-5. Secretの確認
# Secretの値はbase64エンコードされて保存されている
kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}'
期待される出力:
c3VwZXJfc2VjcmV0X3Bhc3N3b3Jk
# デコードすると元の値が見える
kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 --decode
期待される出力:
super_secret_password
🔰 Note: base64は「暗号化」ではありません。
簡単にデコードできます。
Secretの安全性は、base64ではなく「RBACによるアクセス制御」で担保するそうです(第5回で扱います)。
6-4. Step3:ConfigMapをファイルとしてマウント
環境変数ではなく、設定ファイルそのものをPodに渡す方法です。
6-4-1. nginx設定用のConfigMap
📂 config-secret-volume/
├── app-configmap.yaml ✅ Step 1
├── pod-with-configmap.yaml ✅ Step 1
├── app-secret.yaml ✅ Step 2
├── pod-with-secret.yaml ✅ Step 2
└── nginx-config.yaml ← Step 3 🆕
# nginx-config.yaml
# ConfigMap に nginx の設定ファイルを格納
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
# キー = ファイル名、値 = ファイルの中身
default.conf: |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
# カスタム:ヘルスチェック用エンドポイント
location /health {
return 200 'OK';
add_header Content-Type text/plain;
}
}
6-4-2. ConfigMapをマウントするPod
📂 config-secret-volume/
├── app-configmap.yaml ✅ Step 1
├── pod-with-configmap.yaml ✅ Step 1
├── app-secret.yaml ✅ Step 2
├── pod-with-secret.yaml ✅ Step 2
├── nginx-config.yaml ✅ Step 3
└── pod-with-nginx-config.yaml ← Step 3 🆕
# pod-with-nginx-config.yaml
# ConfigMapをファイルとしてマウントするPod
apiVersion: v1
kind: Pod
metadata:
name: nginx-config-demo
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
# ConfigMapをファイルとしてマウント
volumeMounts:
- name: nginx-conf-volume # ボリューム名
mountPath: /etc/nginx/conf.d # マウント先のパス
readOnly: true
# ボリュームの定義
volumes:
- name: nginx-conf-volume # volumeMountsのnameと一致させる
configMap:
name: nginx-config # ConfigMap名
6-4-3. マニフェストの構造解説
| YAMLの項目 | 比喩 | 意味 |
|---|---|---|
volumeMounts |
「メニュー表を壁のここに貼って」 | コンテナ内のどこにマウントするか |
mountPath |
壁の位置 | コンテナ内のディレクトリパス |
volumes |
「メニュー表はこれです」 | マウントする元データの定義 |
configMap.name |
どのメニュー表か | 参照するConfigMap |
6-4-4. 実行・確認
# ~/k8s-handson/config-secret-volume/ で実行
# ConfigMapを作成
kubectl apply -f nginx-config.yaml
# Podを作成
kubectl apply -f pod-with-nginx-config.yaml
# Podが起動するまで待つ
kubectl wait \
--for=condition=Ready \
pod/nginx-config-demo \
--timeout=60s
# マウントされたファイルを確認
kubectl exec nginx-config-demo -- cat /etc/nginx/conf.d/default.conf
# ヘルスチェックエンドポイントにアクセス
kubectl exec nginx-config-demo -- curl -s http://localhost/health
期待される出力(/health):
OK
ConfigMapの設定ファイルがnginxに適用されました。
🍽️ メニュー表(ConfigMap)をお店の壁(/etc/nginx/conf.d/)に貼った結果、新メニュー(/healthエンドポイント)が使えるようになりました。
6-5. Step4:Volume(emptyDir)でPod内データ共有
emptyDir は、同じPod内の複数コンテナがデータを共有するためのVolumeです。
📂 config-secret-volume/
├── app-configmap.yaml ✅ Step 1
├── pod-with-configmap.yaml ✅ Step 1
├── app-secret.yaml ✅ Step 2
├── pod-with-secret.yaml ✅ Step 2
├── nginx-config.yaml ✅ Step 3
├── pod-with-nginx-config.yaml ✅ Step 3
└── pod-with-emptydir.yaml ← Step 4 🆕
# pod-with-emptydir.yaml
# emptyDir で2つのコンテナがデータを共有するPod
apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
containers:
# コンテナ1:データを書き込む(料理人)
- name: writer
image: busybox
command: ["sh", "-c", "while true; do date >> /shared-data/log.txt; sleep 5; done"]
volumeMounts:
- name: shared-volume
mountPath: /shared-data # 書き込み先
# コンテナ2:データを読み取る(配膳係)
- name: reader
image: busybox
command: ["sh", "-c", "tail -f /shared-data/log.txt"]
volumeMounts:
- name: shared-volume
mountPath: /shared-data # 読み取り元(同じボリューム)
# emptyDir ボリューム(Pod起動時に空のディレクトリが作られる)
volumes:
- name: shared-volume
emptyDir: {}
🍽️ 1つのPod(厨房)に2人のスタッフがいる状態です。
- writer(料理人):調理台(/shared-data)に料理を置く
- reader(配膳係):調理台から料理を取ってお客さんに運ぶ
2人が同じ調理台を共有している = emptyDir
6-5-1. 実行・確認
# ~/k8s-handson/config-secret-volume/ で実行
# Podを作成
kubectl apply -f pod-with-emptydir.yaml
# 10秒ほど待ってから、readerコンテナのログを確認
kubectl logs emptydir-demo -c reader
期待される出力:
Wed Jan 1 00:00:00 UTC 2026
Wed Jan 1 00:00:05 UTC 2026
Wed Jan 1 00:00:10 UTC 2026
...
writerが書いたデータを、readerが読めています。
🔰 Note: emptyDirは Podが削除されると消えます。
永続化が必要なデータ(DB等)にはPersistentVolumeを使うそうです。
PersistentVolumeはクラウド環境(AWS EBS等)で本領を発揮するため、今回は概念のみ整理しています。
6-6. 後片付け
# 全てのリソースを削除
kubectl delete pod configmap-demo secret-demo nginx-config-demo emptydir-demo
kubectl delete configmap app-config nginx-config
kubectl delete secret app-secret
# 確認
kubectl get all
kubectl get configmap
kubectl get secret
7. つまずきポイント(体験談)
7-1. Secretの値をbase64エンコードし忘れる
症状: data フィールドにbase64でない文字列を書くとエラー。
# ❌ NG:data フィールドには base64 エンコード済みの値が必要
data:
DB_PASSWORD: "super_secret_password"
# ✅ OK(方法1):base64 エンコード済みの値
data:
DB_PASSWORD: "c3VwZXJfc2VjcmV0X3Bhc3N3b3Jk"
# ✅ OK(方法2):stringData を使う(推奨)
stringData:
DB_PASSWORD: "super_secret_password"
対策: 学習中は stringData を使いましょう。
自動でbase64エンコードしてくれます。
7-2. ConfigMapを変更してもPodに反映されない
症状: ConfigMapを kubectl apply で更新したが、Pod内の環境変数が古いまま。
原因: 環境変数として渡した場合、Pod起動時に読み込まれるため、Podの再起動が必要です。
# Podを再起動する方法
kubectl delete pod <Pod名>
# Deploymentの場合は自動で新しいPodが作られる
# Deploymentの場合はrollout restartも使える
kubectl rollout restart deployment <Deployment名>
🔰 Note: ファイルマウント(Step3の方法)の場合は、ConfigMap変更後に自動で更新されるそうです(少し遅延あり)。
環境変数方式とファイルマウント方式で動作が違う点は要注意だと思いました。
7-3. volumeMountsの名前がvolumesと一致しない
症状:
Error: failed to create containerd task: ... mount source not found
対策: volumeMounts.name と volumes.name を必ず一致させてください。
# ✅ OK:name が一致
volumeMounts:
- name: shared-volume # ← ここと
mountPath: /data
volumes:
- name: shared-volume # ← ここが一致
emptyDir: {}
# ❌ NG:name が不一致
volumeMounts:
- name: shared-vol # ← shared-vol
mountPath: /data
volumes:
- name: shared-volume # ← shared-volume(違う!)
emptyDir: {}
8. まとめ
8-1. この記事でやったこと
| # | 内容 | 状態 |
|---|---|---|
| 1 | ConfigMapで環境変数をPodに渡した | ✅ |
| 2 | Secretでパスワードを安全に管理した | ✅ |
| 3 | ConfigMapをファイルとしてマウントした | ✅ |
| 4 | emptyDirでコンテナ間データ共有を体験した | ✅ |
8-2. 3つのリソースの使い分け(まとめ表)
| 観点 | ConfigMap | Secret | Volume |
|---|---|---|---|
| 用途 | 一般設定 | 秘密情報 | データ保存 |
| 比喩 | メニュー表 | レシピ帳(金庫) | 食材倉庫 |
| 渡し方 | 環境変数 or ファイル | 環境変数 or ファイル | ディレクトリマウント |
| 暗号化 | なし | base64(+ RBAC制御) | - |
| Pod削除時 | 残る | 残る | emptyDir: 消える / PV: 残る |
8-3. Docker Compose → k8s 対応表
| Docker Compose | Kubernetes | 備考 |
|---|---|---|
environment: |
ConfigMap + Secret | k8sは性質で分離する |
.env ファイル |
ConfigMap | 環境変数の外部化 |
volumes: |
Volume(emptyDir / PV) | k8sは種類が豊富 |
| (該当なし) | Secret | Docker Composeには秘密管理の仕組みがない |
9. 次回予告
第5回:Namespace / RBAC — マルチテナントと権限設計
設定もデータも分離できました。
でもまだ問題があります。
🍽️ チェーンレストランの全店舗が同じ厨房を共有している状態。
A店のスタッフがB店のレシピ帳を勝手に見れてしまう!
次回は:
- Namespaceで「店舗ごとの区画」を作る
- RBACで「誰が何をしていいか」を制御する
- ServiceAccountの役割