Kubernetes入門
第9回:ConfigMapとSecretによる設定管理
〜 設定ファイルをサーバーに置くな、Kubernetesに預けろ 〜
はじめに
VMを運用していると、設定ファイルの変更作業はつきものです。複数台のサーバーに同じ設定を反映させるために、SSHで各サーバーにログインしたり、Ansibleで一斉配信したり。それでも「あのサーバーだけ設定が古かった」という事故は起こりがちです。
Kubernetesでは、設定ファイルをサーバーの外に出し、コンテナには起動時に注入するアプローチを取ります。これを実現するのが「ConfigMap」と「Secret」です。
前回、PersistentVolumeでデータの永続化を学びました。今回は、アプリケーションの振る舞いを決める設定を、コンテナイメージから分離する技術を習得します。
9.1 疎結合:環境依存の設定をコンテナの外に出す
9.1.1 「/etc」を直接いじらない運用:設定ファイルをK8sリソース化する利点
VMの世界では、設定変更といえば/etc配下のファイルを編集することでした。
# VMでの典型的な作業フロー
ssh admin@web-server-01
sudo vi /etc/nginx/nginx.conf
sudo systemctl reload nginx
# → 残り19台に対して同じ作業を繰り返す...
この運用には、3つの構造的な問題があります。
| 問題 | 具体例 | 結果 |
|---|---|---|
| 状態の散在 | 設定ファイルが各サーバーに分散 | 「どのサーバーが最新か」が不明確に |
| 変更履歴の不在 | viで直接編集すると履歴が残らない | 「誰がいつ何を変えたか」が追えない |
| 環境差異の蓄積 | 手作業のたびに微妙な差異が生まれる | 「本番だけ動かない」問題の温床 |
Kubernetesでは、設定ファイルをConfigMapというリソースとして定義します。ConfigMapはYAMLファイルとしてGitで管理でき、kubectl apply一発で全Podに反映されます。
設定ファイルの居場所の変化:
【VM時代】
サーバーA:/etc/nginx/nginx.conf ─┐
サーバーB:/etc/nginx/nginx.conf ─┼─ 各サーバーに分散(状態がバラバラ)
サーバーC:/etc/nginx/nginx.conf ─┘
【K8s時代】
ConfigMap (nginx-config) ───→ Pod A, B, C に一斉配信
(Gitで管理、変更履歴も残る)
9.1.2 2つの役割:設定を配る「ConfigMap」と、機密を守る「Secret」
Kubernetesには、設定を外部化するための2つのリソースがあります。
ConfigMap:一般的な設定情報
アプリケーションの設定ファイル、環境変数、コマンドライン引数など、機密性のない設定情報を格納します。
用途の例:
- Nginxの
nginx.conf - アプリケーションのログレベル設定
- タイムゾーン、言語設定
Secret:機密情報
パスワード、APIキー、TLS証明書など、機密性のある情報を格納します。
用途の例:
- データベース接続パスワード
- 外部APIのアクセストークン
- HTTPS用の秘密鍵・証明書
両者の技術的な違いを整理します。
| 観点 | ConfigMap | Secret |
|---|---|---|
| 用途 | 一般的な設定 | 機密情報 |
| 保存形式 | 平文 | Base64エンコード |
| etcdでの暗号化 | なし(デフォルト) | オプションで有効化可能 |
kubectl getでの表示 | そのまま表示 | 値は非表示(-o yamlで確認可) |
| メモリ上の扱い | 通常 | tmpfs(RAMディスク)にマウント可能 |
重要な注意点 Secretの値はBase64エンコードされているだけで、暗号化ではありません。
echo "cGFzc3dvcmQ=" | base64 -dで簡単にデコードできます。本格的なセキュリティが必要な場合は、外部のシークレット管理ツール(HashiCorp Vault、AWS Secrets Manager等)との連携を検討してください。
9.1.3 【比較】VMのゴールデンイメージ管理 vs K8sの「1イメージ多環境」運用
VM時代、多くの現場ではゴールデンイメージ方式を採用していました。
【ゴールデンイメージ方式】
ベースイメージ
│
├─→ 開発用イメージ(dev用設定を焼き込み)
├─→ ステージング用イメージ(stg用設定を焼き込み)
└─→ 本番用イメージ(prod用設定を焼き込み)
問題:イメージが3種類に増殖、メンテナンスコストが3倍
Kubernetesでは、1つのイメージを全環境で使い回し、環境固有の設定だけを外から注入します。
【K8sの1イメージ多環境方式】
nginx:1.27(1つのイメージ)
│
├─→ 開発環境 + ConfigMap(dev) + Secret(dev)
├─→ ステージング + ConfigMap(stg) + Secret(stg)
└─→ 本番環境 + ConfigMap(prod) + Secret(prod)
利点:イメージは1つ、設定だけが環境ごとに異なる
この「Immutable Infrastructure(不変のインフラ)」という考え方により、「開発では動いたのに本番で動かない」という問題が劇的に減ります。なぜなら、動いているコンテナイメージは全環境で同一だからです。
9.2 実践:ConfigMapによる設定ファイル注入
では、実際にConfigMapを使ってNginxの設定を外部化してみましょう。
9.2.1 Nginxの設定ファイルをConfigMapとして作成する
まず、カスタマイズしたNginx設定ファイルを用意します。ここでは、レスポンスヘッダーにサーバー識別情報を追加する設定を作成します。
作業ディレクトリを準備します。
[Execution User: developer]
mkdir -p ~/k8s-config-demo
cd ~/k8s-config-demo
Nginx用のカスタム設定ファイルを作成します。
[Execution User: developer]
cat << 'EOF' > custom-nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
# ConfigMapから注入された設定であることを示すカスタムヘッダー
add_header X-Config-Source "ConfigMap-Managed";
add_header X-Environment "development";
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# ヘルスチェック用エンドポイント
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
EOF
この設定ファイルからConfigMapを作成します。--from-fileオプションを使うと、ファイルの内容をそのままConfigMapに格納できます。
[Execution User: developer]
kubectl create configmap nginx-config \
--from-file=nginx.conf=custom-nginx.conf \
--dry-run=client -o yaml > nginx-configmap.yaml
生成されたマニフェストを確認します。
[Execution User: developer]
cat nginx-configmap.yaml
出力例:
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: null
name: nginx-config
data:
nginx.conf: |
user nginx;
worker_processes auto;
... (設定内容が続く)
ConfigMapをクラスターに適用します。
[Execution User: developer]
kubectl apply -f nginx-configmap.yaml
作成されたConfigMapを確認します。
[Execution User: developer]
kubectl get configmap nginx-config
kubectl describe configmap nginx-config
9.2.2 マニフェスト:作成したConfigMapをPod内の特定のパスにマウントする
次に、このConfigMapをNginx Podにマウントするマニフェストを作成します。
[Execution User: developer]
cat << 'EOF' > nginx-with-configmap.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-custom-config
labels:
app: nginx
config: custom
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
# ConfigMapをボリュームとしてマウント
volumeMounts:
- name: nginx-config-volume
mountPath: /etc/nginx/nginx.conf # マウント先のパス
subPath: nginx.conf # ConfigMap内のキー名
readOnly: true
# 起動確認用のプローブ
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: nginx-config-volume
configMap:
name: nginx-config # 参照するConfigMap名
EOF
ここで重要なのはsubPathの使い方です。
| 設定項目 | 説明 |
|---|---|
mountPath | コンテナ内でファイルを配置するパス |
subPath | ConfigMap内の特定のキーだけをマウント(ディレクトリ全体を上書きしない) |
subPathを指定しない場合、/etc/nginx/ディレクトリ全体がConfigMapの内容で置き換えられてしまいます。これは多くの場合、意図した動作ではありません。
Podを作成します。
[Execution User: developer]
kubectl apply -f nginx-with-configmap.yaml
Podの起動を待ちます。
[Execution User: developer]
kubectl wait --for=condition=Ready pod/nginx-custom-config --timeout=60s
9.2.3 反映の確認:Podの中の設定ファイルが、マニフェスト通りに書き換わっているか検証
設定が正しく反映されているか、複数の方法で確認します。
方法1:Pod内の設定ファイルを直接確認
[Execution User: developer]
kubectl exec nginx-custom-config -- cat /etc/nginx/nginx.conf | head -20
カスタム設定の内容が表示されれば成功です。
方法2:カスタムヘッダーの確認
ポートフォワードを設定してHTTPリクエストを送信します。
[Execution User: developer]
# バックグラウンドでポートフォワード
kubectl port-forward pod/nginx-custom-config 8080:80 &
PF_PID=$!
sleep 3
# レスポンスヘッダーを確認
curl -I http://localhost:8080/
# ポートフォワードを停止
kill $PF_PID 2>/dev/null
期待される出力:
HTTP/1.1 200 OK
...
X-Config-Source: ConfigMap-Managed
X-Environment: development
...
X-Config-Source: ConfigMap-Managedというヘッダーが返ってくれば、ConfigMapの設定が正しく適用されています。
方法3:ヘルスチェックエンドポイントの確認
[Execution User: developer]
kubectl port-forward pod/nginx-custom-config 8080:80 &
PF_PID=$!
sleep 3
curl http://localhost:8080/health
kill $PF_PID 2>/dev/null
healthyと返ってくれば、カスタム設定の/healthエンドポイントが機能しています。
9.3 セキュリティ:Secretを使ったパスワードや証明書の保護
ConfigMapで一般的な設定を扱う方法を学びました。次は、パスワードなどの機密情報を安全に扱うSecretを実践します。
9.3.1 生パスワードをYAMLに書かないために:Secretリソースの作成(CLI/YAML)
VMの世界では、パスワードを環境変数や設定ファイルに直接書き込んでいたかもしれません。Kubernetesでは、Secretリソースを使って機密情報を分離します。
方法1:CLIから直接作成(推奨)
最も安全な方法は、kubectl create secretコマンドを使うことです。
[Execution User: developer]
# データベース接続用のSecretを作成
kubectl create secret generic db-credentials \
--from-literal=username=app_user \
--from-literal=password='S3cur3P@ssw0rd!' \
--from-literal=host=db.example.com \
--from-literal=port=5432
この方法なら、パスワードがYAMLファイルとして残りません。
作成されたSecretを確認します。
[Execution User: developer]
kubectl get secret db-credentials
kubectl describe secret db-credentials
出力例:
Name: db-credentials
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
host: 14 bytes
password: 15 bytes
port: 4 bytes
username: 8 bytes
describeでは値そのものは表示されません。値を確認したい場合は次のコマンドを使います。
[Execution User: developer]
# Base64エンコードされた状態で表示
kubectl get secret db-credentials -o jsonpath='{.data.password}'
echo # 改行
# デコードして表示(検証目的のみ、本番では慎重に)
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
echo # 改行
方法2:YAMLマニフェストから作成
GitOps等でマニフェストを管理したい場合は、YAMLで定義することもできます。ただし、平文のパスワードをGitにコミットしないよう注意してください。
[Execution User: developer]
# まずBase64エンコード
echo -n 'S3cur3P@ssw0rd!' | base64
# 出力: UzNjdXIzUEBzc3cwcmQh
[Execution User: developer]
cat << 'EOF' > db-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials-yaml
type: Opaque
data:
# 値はBase64エンコードが必要
username: YXBwX3VzZXI= # app_user
password: UzNjdXIzUEBzc3cwcmQh # S3cur3P@ssw0rd!
host: ZGIuZXhhbXBsZS5jb20= # db.example.com
port: NTQzMg== # 5432
EOF
Base64エンコードの落とし穴
-nオプションを忘れると、改行文字も一緒にエンコードされてしまいます。echo 'password' | base64 # cGFzc3dvcmQK (末尾に改行が含まれる) echo -n 'password' | base64 # cGFzc3dvcmQ= (正しい)この差異が原因で、アプリケーションが認証に失敗するトラブルは非常に多いです。
9.3.2 環境変数としての注入:Pod内のプロセスからSecretの値を読み出す方法
Secretの値をPodに渡す方法は2つあります。
| 方法 | 用途 | 特徴 |
|---|---|---|
| 環境変数 | 接続文字列、パスワード等 | アプリケーションから参照しやすい |
| ボリュームマウント | 証明書ファイル、鍵ファイル等 | ファイルとして配置される |
ここでは、データベース接続情報を環境変数として注入する例を示します。
[Execution User: developer]
cat << 'EOF' > app-with-secret.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-db-secret
labels:
app: demo-app
spec:
containers:
- name: app
image: nginx:1.27 # 実際にはアプリケーションイメージを使用
ports:
- containerPort: 80
env:
# 方法A:個別のキーを環境変数にマッピング
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
- name: DB_PORT
valueFrom:
secretKeyRef:
name: db-credentials
key: port
# ConfigMapからの環境変数も併用可能
- name: APP_ENV
value: "development"
EOF
Podを作成して環境変数を確認します。
[Execution User: developer]
kubectl apply -f app-with-secret.yaml
kubectl wait --for=condition=Ready pod/app-with-db-secret --timeout=60s
Pod内から環境変数を確認します。
[Execution User: developer]
kubectl exec app-with-db-secret -- env | grep -E '^DB_|^APP_'
出力例:
DB_USERNAME=app_user
DB_PASSWORD=S3cur3P@ssw0rd!
DB_HOST=db.example.com
DB_PORT=5432
APP_ENV=development
環境変数として注入されたSecretの値は、Pod内では平文として見えます。これは正常な動作です。Secretの保護は「クラスター外部への漏洩防止」と「アクセス権限の制御」にあります。
envFromを使った一括注入
キーが多い場合は、envFromで一括注入することもできます。
spec:
containers:
- name: app
image: nginx:1.27
envFrom:
- secretRef:
name: db-credentials
prefix: DB_ # 環境変数に接頭辞を付与(オプション)
この場合、Secretの各キーがそのまま(または接頭辞付きで)環境変数になります。
9.3.3 【次回予告】サーバーに入らずに状態を知る「オブザーバビリティ」
設定を外部化し、データを永続化し、アプリケーションが動く基盤は整いました。しかし、「今、本当に正常に動いているのか?」をどう確認すればよいでしょうか。
VM時代なら、SSHでサーバーに入り、topでリソースを確認し、tail -fでログを追いかけていました。Kubernetesでは、サーバーに入らずにこれらの情報を収集する仕組みが用意されています。
次回は、メトリクス収集、ログ集約、分散トレーシングという「オブザーバビリティ」の3本柱を学びます。監視の世界も、SSHからの解放が待っています。
9.4 トラブルシューティングのTips
ConfigMapとSecretを運用する上で、よく遭遇する問題とその解決策をまとめます。
ConfigMap更新時の反映タイミング
ConfigMapを更新した後、Pod内のファイルがいつ反映されるかは、マウント方法によって異なります。
| マウント方法 | 反映タイミング | 備考 |
|---|---|---|
subPathなしでボリュームマウント | 数十秒〜数分で自動反映 | kubeletの同期間隔に依存 |
subPathありでボリュームマウント | Podの再起動が必要 | subPathは初回マウント時の内容で固定 |
| 環境変数として注入 | Podの再起動が必要 | 環境変数はプロセス起動時に決定 |
実験:subPathなしの場合の自動反映
[Execution User: developer]
# subPathを使わないPodを作成
cat << 'EOF' > nginx-auto-reload.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-auto-reload
spec:
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d # ディレクトリごとマウント(subPathなし)
volumes:
- name: config
configMap:
name: nginx-config
EOF
この場合、ConfigMapを更新すると、しばらく後にPod内のファイルも自動的に更新されます。ただし、Nginxプロセスが設定を再読み込みするわけではないので、設定を反映させるにはnginx -s reloadの実行やPodの再起動が必要です。
確実に反映させる方法:Podのローリング再起動
Deploymentを使っている場合、以下のコマンドでPodを再起動できます。
[Execution User: developer]
# Deploymentのアノテーションを更新してローリング再起動をトリガー
kubectl rollout restart deployment/<deployment-name>
SecretのBase64エンコード忘れによるエラー
YAMLでSecretを定義する際、Base64エンコードを忘れると次のようなエラーが発生します。
error: error validating "secret.yaml": error validating data:
invalid value for field data: illegal base64 data
解決策1:stringDataを使う
Kubernetes 1.10以降では、stringDataフィールドを使うと平文で記述できます。
[Execution User: developer]
cat << 'EOF' > db-secret-stringdata.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials-easy
type: Opaque
stringData: # dataではなくstringData
username: app_user
password: S3cur3P@ssw0rd!
host: db.example.com
port: "5432"
EOF
stringDataで定義した値は、適用時に自動的にBase64エンコードされます。
解決策2:CLIで作成する
前述の通り、kubectl create secretコマンドを使えばエンコードの心配は不要です。
ConfigMapやSecretが見つからないエラー
Podが参照するConfigMapやSecretが存在しない場合、Podは起動に失敗します。
Warning FailedMount ... MountVolume.SetUp failed for volume "config" :
configmap "nginx-config" not found
解決策:optional指定
ConfigMapやSecretが存在しなくてもPodを起動させたい場合は、optional: trueを指定します。
volumes:
- name: config
configMap:
name: nginx-config
optional: true # ConfigMapが存在しなくてもPod起動を継続
デバッグ用コマンド集
[Execution User: developer]
# ConfigMapの一覧表示
kubectl get configmap
# ConfigMapの内容を確認
kubectl get configmap nginx-config -o yaml
# Secretの一覧表示(値は表示されない)
kubectl get secret
# Secretの値をデコードして確認
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
# PodがConfigMap/Secretを正しくマウントしているか確認
kubectl describe pod <pod-name> | grep -A 10 "Mounts:"
# Pod内の環境変数を確認
kubectl exec <pod-name> -- env | sort
後片付け
今回作成したリソースを削除します。
[Execution User: developer]
kubectl delete pod nginx-custom-config app-with-db-secret nginx-auto-reload --ignore-not-found
kubectl delete configmap nginx-config --ignore-not-found
kubectl delete secret db-credentials db-credentials-yaml db-credentials-easy --ignore-not-found
rm -rf ~/k8s-config-demo
まとめ:VMエンジニアが今日から使える3つの知識
| VM時代の常識 | K8sの新常識 | 今日学んだこと |
|---|---|---|
設定は/etcに直接書く | 設定はConfigMapに外部化 | kubectl create configmap |
| パスワードは設定ファイルに記述 | 機密情報はSecretで分離 | kubectl create secret |
| 環境ごとにイメージを作り分け | 1つのイメージ + 環境別の設定 | Immutable Infrastructureの実現 |
ConfigMapとSecretにより、コンテナイメージを「不変」に保ったまま、環境ごとの設定を柔軟に切り替えられるようになりました。これは、VM時代のゴールデンイメージ方式では実現できなかった、K8sならではの運用パターンです。
