はじめに
前回は、Ingressを使ってクラスター外部からのHTTP・HTTPSリクエストをServiceへ振り分ける仕組みを学びました。
ブラウザ
↓
Ingress・ALB
↓
Service
↓
Pod
↓
Container
これで、リクエストがアプリケーションまで届く流れはつながりました。
しかし、アプリケーションを動かすには、コンテナイメージだけでなく次のような設定値も必要です。
実行環境:production
ログレベル:info
接続先ホスト名:redis-service
機能フラグ:有効
開発環境・ステージング環境・本番環境では、同じアプリケーションでも設定値が異なります。
設定値が変わるたびにDockerイメージを作り直すのでしょうか。
そこで登場するのが ConfigMap(コンフィグマップ) です。
この記事では、次の内容を整理します。
- ConfigMapとは何か
- Dockerイメージと設定値を分ける理由
- ConfigMap・Deployment・Pod・Containerの関係
-
dataに設定値を書く方法 - 環境変数として渡す方法
- ファイルとしてマウントする方法
- ConfigMapを更新したときの反映方法
- Helmの
values.yamlとの違い - ConfigMapとSecretの使い分け
- EKSでも同じ仕組みなのか
結論
ConfigMapとは、機密情報ではない設定値を、コンテナイメージから分離して保存するKubernetesリソースです。
ConfigMap
├── RAILS_ENV=production
├── LOG_LEVEL=info
└── REDIS_HOST=redis-service
↓ 参照
DeploymentのPodテンプレート
↓
Pod
↓
Containerの環境変数・設定ファイル
ConfigMapへ値を書いただけでは、コンテナへ自動的に渡されません。
DeploymentやPodのYAMLからConfigMapを参照する必要があります。
ConfigMap
= 設定値の保管場所
Deployment
= どのConfigMapを、どのようにコンテナへ渡すか定義する
Pod・Container
= 渡された設定値を使ってアプリケーションを実行する
なぜDockerイメージへ設定値を入れないのか
たとえば、Railsアプリケーションを次の3環境で動かすとします。
| 環境 | RAILS_ENV |
LOG_LEVEL |
|---|---|---|
| 開発 | development |
debug |
| ステージング | staging |
info |
| 本番 | production |
warn |
設定値をDockerイメージへ直接埋め込むと、環境ごとに別のイメージが必要になります。
開発用イメージ
ステージング用イメージ
本番用イメージ
これでは、ステージング環境で確認したイメージと、本番環境へデプロイするイメージが別物になります。
ConfigMapを使うと、イメージと設定値を分けられます。
同じDockerイメージ
├── 開発用ConfigMap
├── ステージング用ConfigMap
└── 本番用ConfigMap
つまり、アプリケーションの中身を変えずに、環境ごとの設定だけを切り替えられます。
ConfigMapは何を保存するのか
ConfigMapは、主に機密性のない設定値をキーと値の組み合わせで保存します。
キー 値
RAILS_ENV production
LOG_LEVEL info
REDIS_HOST redis-service
向いている値の例は次のとおりです。
- 実行環境名
- ログレベル
- Service名やホスト名
- 公開して問題のないURL
- タイムゾーン
- 機能フラグ
- アプリケーションの設定ファイル
一方、次の値はConfigMapへ入れません。
- パスワード
- APIトークン
- 秘密鍵
- データベースの認証情報
機密情報にはSecretを使用します。
ConfigMap・Deployment・Podの関係
ConfigMapは、それ自体がコンテナを動かすものではありません。
DeploymentのPodテンプレートからConfigMapを参照します。
ConfigMap
設定値を保持する
↓ 名前で参照
Deployment
Podテンプレートへ参照方法を書く
↓ Podを作成
Pod
↓
Container
設定値を環境変数・ファイルとして利用する
複数のDeploymentやPodから、同じConfigMapを参照することもできます。
┌→ Rails Deployment → Rails Pod
app-config ──────┤
└→ Sidekiq Deployment → Sidekiq Pod
RailsとSidekiqを別Podで動かしていても、同じ設定が必要なら同じConfigMapを参照できます。
ConfigMapを定義するYAML
基本的なConfigMapは次のように定義します。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
RAILS_ENV: production
LOG_LEVEL: info
REDIS_HOST: redis-service
FEATURE_NEW_EDITOR: "true"
このConfigMapには、4つの設定値が保存されています。
| キー | 値 |
|---|---|
RAILS_ENV |
production |
LOG_LEVEL |
info |
REDIS_HOST |
redis-service |
FEATURE_NEW_EDITOR |
true |
YAMLの各項目
apiVersionとkind
apiVersion: v1
kind: ConfigMap
| 項目 | 意味 |
|---|---|
apiVersion: v1 |
ConfigMapを扱うKubernetes APIのバージョン |
kind: ConfigMap |
作成するリソースがConfigMapであることを示す |
metadata.name
metadata:
name: app-config
ConfigMapの名前です。
DeploymentやPodは、この名前を使ってConfigMapを参照します。
data
data:
RAILS_ENV: production
LOG_LEVEL: info
dataには、文字列のキーと値を書きます。
数値や真偽値に見える値も、文字列として扱いたい場合は引用符で囲むと意図が明確です。
data:
RETRY_COUNT: "3"
FEATURE_ENABLED: "true"
ConfigMapにはspecではなく、主にdataやbinaryDataを記述します。
binaryDataには、Base64で表現したバイナリーデータを保存できます。
ただし、ConfigMapは大きなファイルの保管場所ではなく、1つのConfigMapに保存できるデータは1MiBまでです。
ConfigMapを作成する
YAMLをconfigmap.yamlとして保存した場合、次のコマンドで作成・更新できます。
kubectl apply -f configmap.yaml
一覧を確認します。
kubectl get configmap
短縮形も使用できます。
kubectl get cm
内容をYAMLで確認します。
kubectl get configmap app-config -o yaml
詳細やイベントを確認します。
kubectl describe configmap app-config
コマンドからConfigMapを作成する
ConfigMapは、YAMLだけでなくコマンドからも作成できます。
値を直接指定する
kubectl create configmap app-config \
--from-literal=RAILS_ENV=production \
--from-literal=LOG_LEVEL=info
ファイルから作成する
kubectl create configmap nginx-config \
--from-file=nginx.conf
この場合、通常はファイル名がキー、ファイルの中身が値になります。
キー:nginx.conf
値:nginx.confの内容
Gitで設定を管理し、レビューしてから適用する場合は、YAMLとして記述する方法が分かりやすいです。
コンテナへ渡す方法
ConfigMapの代表的な渡し方は次の3つです。
| 方法 | 向いている用途 |
|---|---|
envFrom |
複数のキーをまとめて環境変数にする |
env.valueFrom.configMapKeyRef |
必要なキーだけを環境変数にする |
| Volume | 設定ファイルとしてマウントする |
それぞれ確認します。
方法1:すべての値を環境変数として渡す
envFromを使うと、ConfigMapのdataにある値をまとめて環境変数へ渡せます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: rails
spec:
replicas: 3
selector:
matchLabels:
app: rails
template:
metadata:
labels:
app: rails
spec:
containers:
- name: rails
image: example/rails-app:1.0
envFrom:
- configMapRef:
name: app-config
ここで参照しているのは、次のConfigMapです。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
RAILS_ENV: production
LOG_LEVEL: info
REDIS_HOST: redis-service
コンテナ内では、次の環境変数として利用できます。
RAILS_ENV=production
LOG_LEVEL=info
REDIS_HOST=redis-service
Railsアプリケーションからは、たとえば次のように取得できます。
ENV.fetch("LOG_LEVEL", "info")
方法2:必要な値だけを環境変数として渡す
configMapKeyRefを使うと、ConfigMapの特定のキーだけを参照できます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: rails
spec:
replicas: 3
selector:
matchLabels:
app: rails
template:
metadata:
labels:
app: rails
spec:
containers:
- name: rails
image: example/rails-app:1.0
env:
- name: APP_LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
この設定では、ConfigMapのLOG_LEVELを、コンテナ内のAPP_LOG_LEVELとして渡します。
ConfigMap
LOG_LEVEL=info
↓
Container
APP_LOG_LEVEL=info
ConfigMap側のキー名と、コンテナ内の環境変数名を変えることもできます。
必要な値が明確になるため、参照する設定を限定したい場合に向いています。
方法3:設定ファイルとしてマウントする
ConfigMapは、環境変数だけでなくファイルとしてPodへマウントできます。
まず、ファイルの内容をConfigMapへ定義します。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-file-config
data:
application.yml: |
app_name: freee-sign
log_level: info
timeout_seconds: 30
|を使うと、複数行の文字列を記述できます。
Deployment側では、VolumeとしてConfigMapを参照します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: rails
spec:
replicas: 3
selector:
matchLabels:
app: rails
template:
metadata:
labels:
app: rails
spec:
containers:
- name: rails
image: example/rails-app:1.0
volumeMounts:
- name: app-config-volume
mountPath: /app/config/runtime
readOnly: true
volumes:
- name: app-config-volume
configMap:
name: app-file-config
コンテナ内には、次のファイルが作られます。
/app/config/runtime/application.yml
関係は次のとおりです。
ConfigMapのキー
application.yml
↓ ファイル名になる
/app/config/runtime/application.yml
↑
ConfigMapの値がファイル内容になる
1つのConfigMapに複数のキーがある場合、通常はキーごとにファイルが作られます。
環境変数とファイルのどちらを使うのか
| 観点 | 環境変数 | Volumeによるファイル |
|---|---|---|
| 向いている値 | 短い単一の値 | 複数行の設定ファイル |
| アプリの読み方 |
ENVなど |
ファイルを開く |
| ConfigMap更新 | 実行中のコンテナへ自動反映されない | 最終的にマウントへ反映される |
| アプリ側の対応 | Podの再作成が必要 | ファイルの再読み込みが必要な場合がある |
たとえば、次の値は環境変数に向いています。
LOG_LEVEL=info
REDIS_HOST=redis-service
次のような設定は、ファイルに向いています。
logging:
level: info
format: json
output: stdout
ConfigMapを更新するとどうなるのか
ここは重要です。
ConfigMapを更新しても、利用方法によって反映のされ方が異なります。
環境変数として利用している場合
ConfigMapを更新しても、すでに起動しているコンテナの環境変数は変わりません。
ConfigMapを更新
↓
起動済みContainerの環境変数
× そのまま
新しい値を使うには、Podを再作成します。
Deploymentを再起動する例です。
kubectl rollout restart deployment/rails
状態を確認します。
kubectl rollout status deployment/rails
Deploymentが新しいPodを作成すると、そのPodは更新後のConfigMapを読み込みます。
Volumeとして利用している場合
VolumeとしてマウントしたConfigMapは、ConfigMapの更新後、時間を置いてPod内のファイルにも反映されます。
ただし、即時反映ではありません。
ConfigMapを更新
↓
kubeletが変更を検知
↓
マウントされたファイルを更新
また、ファイルが変わっても、アプリケーションが自動で読み直すとは限りません。
アプリケーション側に設定ファイルを再読み込みする仕組みがなければ、Podの再作成が必要です。
なお、subPathを使ってConfigMapの一部だけをマウントしたコンテナには、ConfigMapの更新が反映されません。
ConfigMapの変更でDeploymentは自動再起動しない
ConfigMapとDeploymentは別のKubernetesリソースです。
ConfigMapだけを更新しても、DeploymentのPodテンプレートは変更されていません。
そのため、Deploymentは自動的にローリングアップデートを始めません。
ConfigMapだけ変更
↓
DeploymentのPodテンプレートは同じ
↓
新しいPodを作るきっかけがない
環境変数として利用する場合は、次のような運用が必要です。
-
kubectl rollout restartでDeploymentを再起動する - ConfigMap名へバージョンを付ける
- HelmでConfigMap内容のチェックサムをPodテンプレートのannotationへ入れる
ConfigMap名へバージョンを付ける例です。
app-config-v1
↓ 更新時
app-config-v2
Deploymentが参照する名前をapp-config-v2へ変えると、Podテンプレートが変わるためローリングアップデートが始まります。
ConfigMapとNamespace
ConfigMapはNamespaceに所属するリソースです。
通常、Podは同じNamespaceにあるConfigMapを参照します。
Namespace: production
├── ConfigMap: app-config
└── Pod: rails-xxxxx
○ 参照できる
Namespace: staging
└── Pod: rails-yyyyy
× productionのConfigMapは直接参照できない
環境ごとにNamespaceを分ける場合は、それぞれのNamespaceへConfigMapを作成します。
kubectl apply -f configmap.yaml -n production
確認するときもNamespaceを指定します。
kubectl get configmap app-config -n production
存在しないConfigMapを参照するとどうなるのか
Podが必須のConfigMapを参照しているのに、そのConfigMapが存在しない場合、Podは正常に起動できません。
Deployment
└── app-configを参照
app-configが存在しない
↓
Podの起動に失敗・待機
まずConfigMapを作成し、その後にDeploymentを作成するのが基本です。
意図的にConfigMapがなくても起動させたい場合は、参照へoptional: trueを指定できます。
envFrom:
- configMapRef:
name: optional-config
optional: true
ただし、アプリケーションに必須の設定までoptionalにすると、起動後に別のエラーが発生する可能性があります。
immutableなConfigMap
変更させたくないConfigMapには、immutable: trueを指定できます。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v1
immutable: true
data:
RAILS_ENV: production
LOG_LEVEL: info
immutableなConfigMapは、作成後にdataやbinaryDataの内容を変更できません。
変更が必要な場合は、新しい名前のConfigMapを作成し、参照先を切り替えます。
app-config-v1
↓ 新しい設定を作成
app-config-v2
誤った変更を防ぎたい場合や、設定をバージョン管理したい場合に利用できます。
ConfigMapとSecretの違い
ConfigMapとSecretは、どちらも設定値をPodへ渡すために利用できます。
違いは、保存するデータの性質です。
| リソース | 保存するもの | 例 |
|---|---|---|
| ConfigMap | 機密ではない設定値 | ログレベル、ホスト名、機能フラグ |
| Secret | 機密情報 | パスワード、トークン、秘密鍵 |
LOG_LEVEL=info
→ ConfigMap
DATABASE_PASSWORD=xxxxx
→ Secret
ConfigMapには秘密を守る暗号化機能はありません。
そのため、パスワードをConfigMapへ保存してはいけません。
Secretの仕組みと注意点は、次の記事で詳しく扱います。
ConfigMapとHelmのvalues.yamlの違い
Helmを利用していると、values.yamlとConfigMapの違いが分かりにくくなることがあります。
values.yamlは、Helmテンプレートへ値を渡すためのファイルです。
ConfigMapは、Kubernetesクラスター上に作られるリソースです。
values.yaml
Helmへ渡す値
↓
templates/configmap.yaml
テンプレートへ値を埋め込む
↓ helm template・helm install・helm upgrade
ConfigMap
Kubernetes上に作成される
↓
Deployment・Podから参照される
たとえば、values.yamlへ次の値を書きます。
appConfig:
railsEnv: production
logLevel: info
ConfigMapのテンプレートでは、次のように参照できます。
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-app-config
data:
RAILS_ENV: {{ .Values.appConfig.railsEnv | quote }}
LOG_LEVEL: {{ .Values.appConfig.logLevel | quote }}
Helmがテンプレートを展開した結果、通常のConfigMap YAMLが生成されます。
つまり、values.yaml自体をPodが直接読むわけではありません。
EKSでも同じ仕組みなのか
はい。同じです。
ConfigMapはAWS独自の機能ではなく、Kubernetes標準のリソースです。
EKSクラスター
└── Kubernetes API
├── Deployment
├── Service
├── Ingress
├── ConfigMap
└── Secret
EKSでも、ConfigMapを作成し、DeploymentのPodテンプレートから参照します。
ConfigMapのデータをコンテナへ渡す処理は、各Nodeで動くkubeletが担当します。
RailsとSidekiqで同じConfigMapを使う例
RailsとSidekiqを別Deploymentで動かす場合を考えます。
app-config
├── RAILS_ENV=production
├── LOG_LEVEL=info
└── REDIS_HOST=redis-service
↓
├── Rails Deployment
│ └── Rails Pod
│
└── Sidekiq Deployment
└── Sidekiq Pod
Rails Deploymentでは次のように参照します。
containers:
- name: rails
image: example/rails-app:1.0
envFrom:
- configMapRef:
name: app-config
Sidekiq Deploymentでも同じConfigMapを参照します。
containers:
- name: sidekiq
image: example/rails-app:1.0
command: ["bundle", "exec", "sidekiq"]
envFrom:
- configMapRef:
name: app-config
同じDockerイメージと同じConfigMapを利用しながら、コンテナの起動コマンドを変えられます。
ただし、ConfigMapを環境変数として渡しているため、設定変更時にはRailsとSidekiqの両方のPodを再作成する必要があります。
kubectl rollout restart deployment/rails
kubectl rollout restart deployment/sidekiq
実際に値が渡されたか確認する
Pod名を確認します。
kubectl get pods
コンテナ内の環境変数を確認します。
kubectl exec rails-xxxxx -- printenv LOG_LEVEL
出力例です。
info
ファイルとしてマウントした場合は、ファイルを確認します。
kubectl exec rails-xxxxx -- cat /app/config/runtime/application.yml
Podが起動しない場合は、詳細とイベントを確認します。
kubectl describe pod rails-xxxxx
次の点を確認します。
- ConfigMapが存在するか
- ConfigMapとPodが同じNamespaceにあるか
- ConfigMap名が一致しているか
- 参照しているキーが存在するか
- YAMLのインデントが正しいか
よくある勘違い
ConfigMapを作れば自動的に全Podへ渡される
自動では渡されません。
DeploymentやPodのYAMLから、ConfigMapを明示的に参照します。
ConfigMapを更新すれば環境変数もすぐ変わる
起動済みコンテナの環境変数は変わりません。
新しい値を反映するには、Podの再作成が必要です。
ConfigMapを更新すればDeploymentが自動再起動する
ConfigMapの更新だけでは、Deploymentは自動再起動しません。
再起動する運用や、変更をPodテンプレートへ反映する仕組みが必要です。
ConfigMapならパスワードも保存できる
保存自体はできてしまいますが、ConfigMapは機密情報を守るためのリソースではありません。
パスワードやトークンにはSecretを使用します。
ConfigMapは環境変数専用である
環境変数だけでなく、コマンド引数やVolume内の設定ファイルとしても利用できます。
ConfigMapには何でも保存できる
ConfigMapは大きなデータの保存には向いておらず、サイズ上限は1MiBです。
画像や大きなファイルを保管するストレージとしては使いません。
これまでの流れへConfigMapを加える
ここまで学んだリソースを並べると、次のようになります。
Node
└── Podを動かすサーバー
↑
Deployment
└── Podの数と更新を管理する
↓
Pod
└── Containerを動かす単位
↑
Service
└── 変化するPod群へ安定した接続先を提供する
↑
Ingress
└── 外部のHTTP・HTTPS通信をServiceへ振り分ける
ConfigMap
└── Pod内のContainerへ機密ではない設定値を渡す
通信経路と設定値の経路を分けて考えると、理解しやすくなります。
通信の流れ
Ingress → Service → Pod → Container
設定の流れ
ConfigMap → Pod → Container
まとめ
ConfigMapは、機密ではない設定値をコンテナイメージから分離して管理するKubernetesリソースです。
ConfigMap
= 設定値の保管場所
Deployment・Pod
= ConfigMapの参照方法を定義する
Container
= 環境変数やファイルとして設定値を使う
重要な点は次のとおりです。
- ConfigMapには機密ではない設定値を保存する
- 同じDockerイメージへ環境ごとの設定を渡せる
-
envFromで複数の値をまとめて環境変数にできる -
configMapKeyRefで特定の値だけを参照できる - Volumeを使うと設定ファイルとしてマウントできる
- 環境変数はConfigMap更新だけでは変わらない
- ConfigMapの更新だけではDeploymentは再起動しない
- PodとConfigMapは通常、同じNamespaceに置く
- パスワードやトークンにはConfigMapではなくSecretを使う
-
values.yamlはHelmの入力であり、ConfigMapはKubernetesリソースである
一言でまとめると、次のようになります。
ConfigMapは、同じコンテナイメージを使いながら、環境ごとの設定値を外から渡す仕組みです。
次は、パスワード・APIトークン・秘密鍵などの機密情報をPodへ渡す Secret について整理します。