実行環境に依存するようなアプリケーションの設定情報はコンテナイメージに埋め込むべきではなく、
環境変数などコンテナの外から埋め込まれるべきです。
Kubernetesにはアプリケーションと設定を分離するための仕組みとしてConfigMapとSecretが用意されています。ConfigMapは一般的な設定情報をSecretには機密情報を保存します。
目次
ConfigMap
ConfigMapは設定情報の集合のように扱えます。ConfigMapに保存した設定をコンテナに対して、環境変数として設定したり、
あるいはファイル形式でマウントすることができます。
例えば、making/hello-pksイメージのapplication.propertiesには
management.endpoint.shutdown.enabled=true
という設定があります。この設定は/actuator/shutdownにPOSTでリクエストを送るとアプリケーションがシャットダウンするエンドポイントを有効にするので、
ここではデプロイ時に無効にしたいと考えます。
また、デプロイ時には画像などの静的ファイルのcache period(3日間)を設定するために次の設定もします。
spring.resources.cache.period=3d
これらの設定をConfigMapを使って行います。
ConfigMapを適用する前に、静的ファイルにアクセスしてもキャッシュの設定が含まれていないことを確認します。curl -I http://<ServiceのLoad BalancerのIPまたはHost名>:8080/pks.pngを実行して、HTTPレスポンスヘッダを確認してください。
curl -I http://<ServiceのLoad BalancerのIPまたはHost名>:8080/pks.png
出力結果
HTTP/1.1 200 OK
transfer-encoding: chunked
Last-Modified: Mon, 9 Apr 2018 17:26:03 GMT
Content-Length: 4488
Content-Type: image/png
Accept-Ranges: bytes
同様に/actuator/shutdownにPOSTリクエストを送ってアプリケーションをシャットダウンしてください。
curl -XPOST http://<ServiceのLoad BalancerのIPまたはHost名>:8080/actuator/shutdown
実行結果
{
"message" : "Shutting down, bye..."
}
なお、シャットダウンしてもReplicaSetにより、コンテナは再起動します。
環境変数の設定
ConfigMapの値をコンテナの環境変数に埋め込みます。
まずはConfigMapを作成します。hello-pks-config.ymlを作成して、次の内容を記述してください。
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-pks-config
data:
management.endpoint.shutdown.enabled: "false" # 値は文字列である必要があり、booleanと区別するためクオートする必要がある
spring.resources.cache.period: 3d
data以下にKey-Value形式で設定情報を定義できます。kubectl applyでConfigMapをデプロイしてください。
kubectl apply -f hello-pks-config.yml
出力結果
configmap "hello-pks-config" created
kubctl get configmapでConfigMap一覧を確認できます。
kubectl get configmap
出力結果
NAME DATA AGE
hello-pks-config 1
次にコンテナの環境変数にConfigMapから値を取得して埋め込みます。
Spring Bootの場合、プロパティ名は大文字と_区切りの環境変数でも設定できますので、
環境変数MANAGEMENT_ENDPOINT_SHUTDOWN_ENABLEDとSPRING_RESOURCES_CACHE_PERIODを設定します。
hello-pks.ymlを次のように変更してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-pks
spec:
# ...
template:
# ...
spec:
containers:
- image: making/hello-pks:0.0.2
name: hello-pks
ports:
- containerPort: 8080
env:
# ...
- name: MANAGEMENT_ENDPOINT_SHUTDOWN_ENABLED
valueFrom:
configMapKeyRef:
name: hello-pks-config
key: management.endpoint.shutdown.enabled
- name: SPRING_RESOURCES_CACHE_PERIOD
valueFrom:
configMapKeyRef:
name: hello-pks-config
key: spring.resources.cache.period
env[].valueの代わりにenv[].valueFrom.configMapKeyRefを使ってConfigMapから値を参照できます。
kubectl applyでこの設定を適用してください。
kubectl apply -f hello-pks.yml
出力結果
deployment "hello-pks" configured
kubectl get podを実行してSTATUSがRunningになっていることを確認してください。
再度、curl -I http://<ServiceのLoad BalancerのIPまたはHost名>:8080/pks.pngを実行して、HTTPレスポンスヘッダを確認してください。
curl -I http://<ServiceのLoad BalancerのIPまたはHost名>:8080/pks.png
出力結果
HTTP/1.1 200 OK
transfer-encoding: chunked
Last-Modified: Mon, 9 Apr 2018 17:26:03 GMT
Cache-Control: max-age=259200
Content-Length: 4488
Content-Type: image/png
Accept-Ranges: bytes
Cache-Controlヘッダにmax-age=259200(3日間)が設定されていることが分かります。
シャットダウンエンドポイントが無効になっていることも確認してください。
curl -XPOST http://<ServiceのLoad BalancerのIPまたはHost名>:8080/actuator/shutdown
出力結果
{
"timestamp" : "2018-04-17T16:48:00.074+0000",
"path" : "/actuator/shutdown",
"status" : 404,
"error" : "Not Found",
"message" : null
}
ConfigMapのデータを全て環境変数に埋め込みたい場合、一つずつしなくともenvFromを使うことができます。
設定ファイルとしてマウント
ConfigMapに設定ファイルの内容をそのまま保存し、環境変数ではなくファイルとしてコンテナにマウントすることができます。
hello-pks-config.ymlの内容を次のように変更してください。
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-pks-config
data:
application.properties: |-
management.endpoint.shutdown.enabled=false
spring.resources.cache.period=3d
プロパティのKey-Valueを一つ一つ定義する代わりに、application.propertiesというキーに対する値にプロパティファイルの内容を全て設定しています。kubectl applyで変更を適用してください。
kubectl apply -f hello-pks-config.yml
実行結果
configmap "hello-pks-config" configured
次にこのConfigMapをマウントします。
Spring Bootはアプリケーション実行ディレクトリ直下の/configディレクトリに存在するapplication.propertiesを読み込みます。
今回使用しているockerイメージのデフォルトのカレントディレクトリは/なので、/config直下にapplication.propertiesをマウントできれれば起動時にこの設定が読み込まれます。この設定ファイルの方が優先度が高いです。
hello-pks.ymlを次のように変更してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-pks
spec:
# ...
template:
# ...
spec:
containers:
- image: making/hello-pks:0.0.2
name: hello-pks
ports:
- containerPort: 8080
volumeMounts: # 追加
- name: application-properties
mountPath: /config
readOnly: true
env:
# ...
# MANAGEMENT_ENDPOINT_SHUTDOWN_ENABLEDおよび
# SPRING_RESOURCES_CACHE_PERIODは削除してください
volumes: # 追加(インデントに注意)
- name: application-properties
configMap:
name: hello-pks-config
items:
- key: application.properties
path: application.properties
spec.template.volumesにマウントできるファイルシステムを定義し、
実際にマウント対象のコンテナのspec.template.spec.containers[].volumenMountsにマウントするディレクトリを設定します。
kubectl applyでこの設定を適用してください。
kubectl apply -f hello-pks.yml
出力結果
deployment "hello-pks" configured
kubectl get podを実行してSTATUSがRunningになっていることを確認してください。
/actuator/envエンドポイントにアクセスすると読み込まれているプロパティ一覧を出力することができます。
curl http://<ServiceのLoad BalancerのIPまたはHost名>:8080/actuator/env
出力結果
{
"activeProfiles" : [ ],
"propertySources" : [ {
"name" : "server.ports",
"properties" : ...
}, {
"name" : "systemProperties",
"properties" : ...
}, {
"name" : "systemEnvironment",
"properties" : ...
}, {
"name" : "applicationConfig: [file:./config/application.properties]",
"properties" : {
"management.endpoint.shutdown.enabled" : {
"value" : "false",
"origin" : "URL [file:./config/application.properties]:1:38"
},
"spring.resources.cache.period" : {
"value" : "3d",
"origin" : "URL [file:./config/application.properties]:2:31"
}
}
}, {
"name" : "applicationConfig: [classpath:/application.properties]",
"properties" : ...
} ]
}
/config/application.propertiesが読み込まれていることを確認してください。上位のプロパティの方が優先度が高いです。
流れの都合上、Spring Bootアプリを題材に
ConfigMapを説明しましたが、Spring Bootのプロパティは環境変数やシステムプロパティ変えたほうが便利で、あまりConfigMapを使う機会はないかもしれません。次に説明するSecretは使います。ConfigMapは設定ファイルでしか設定できないアプリをデプロイする際に便利です。
Secret
ConfigMapには任意のデータを格納できますが、パスワードやプライベートキーなど機密性の高いデータを保存する場所としてSecretが用意されています。
ただし、
Secretのデータが暗号化されて保存される訳ではありません。
環境変数の設定
ここではSecretにユーザー名とパスワードを保存し、hello-pksアプリケーションの認証に使用します。
まずは、認証に対応させたバージョンのDockerイメージに更新するため、イメージのタグを0.0.4にしてください。
hello-pks.ymlは次の通りです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-pks
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 50%
maxUnavailable: 50%
selector:
matchLabels:
app: hello-pks
template:
metadata:
labels:
app: hello-pks
spec:
containers:
- image: making/hello-pks:0.0.4 # <-- バージョン変更
name: hello-pks
ports:
- containerPort: 8080
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/info
port: 8080
initialDelaySeconds: 20
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 1
kubectl applyで変更を適用してください。
kubectl apply -f hello-pks.yml
出力結果
deployment "hello-pks" configured
Podが起動したら、curl -I http://<ServiceのLoad BalancerのIPまたはHost名>:8080/actuator/envを実行してください。
curl -I http://<ServiceのLoad BalancerのIPまたはHost名>:8080/actuator/env
出力結果
HTTP/1.1 401 Unauthorized
transfer-encoding: chunked
WWW-Authenticate: Basic realm="Realm"
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
401エラーになり、Basic認証が求められます。
このBasic認証のユーザー名、パスワードはそれぞれ環境変数SPRING_SECURITY_USER_NAME、SPRING_SECURITY_USER_PASSWORDで上書き可能です。
Secretを作成します。hello-pks-secret.ymlに次の内容を記述してください。ユーザー名はpksuser、パスワードはY4nys7f11とします。
apiVersion: v1
kind: Secret
metadata:
name: hello-pks-secret
type: Opaque
data:
spring.security.user.name: cGtzdXNlcg==
spring.security.user.password: WTRueXM3ZjEx
YAML中のSecretのdataの値はBase64でエンコードする必要があります。
次のように値をエンコードして、設定してください。
echo -n pksuser | base64
echo -n Y4nys7f11 | base64
base64コマンドを使う代わりに、次のように作成すると便利です。
kubectl create secret generic hello-pks-secret \
--from-literal=spring.security.user.name=pksuser \
--from-literal=spring.security.user.password=Y4nys7f11 \
--dry-run \
-o yaml > hello-pks-secret.yml
あるいはdataの代わりにstringDataを使用すればBase64エンコーディングをする必要がありません。
apiVersion: v1
kind: Secret
metadata:
name: hello-pks-secret
type: Opaque
stringData:
spring.security.user.name: pksuser
spring.security.user.password: Y4nys7f11
kubectl apply -f hello-pks-secret.ymlを実行してください。
kubectl get secretでSecret一覧を確認できます。
kubectl get secret
出力結果
NAME TYPE DATA AGE
default-token-nkp9k kubernetes.io/service-account-token 3 2d
hello-pks-secret Opaque 2 10s
hello-pks-secret.ymlはGitなどバージョン管理システムにチェックインしないように気をつけて下さい。
hello-pks.ymlを次のように変更してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-pks
spec:
# ...
template:
# ...
spec:
containers:
- image: making/hello-pks:0.0.4
name: hello-pks
ports:
- containerPort: 8080
env:
# ...
- name: SPRING_SECURITY_USER_NAME
valueFrom:
secretKeyRef:
name: hello-pks-secret
key: spring.security.user.name
- name: SPRING_SECURITY_USER_PASSWORD
valueFrom:
secretKeyRef:
name: hello-pks-secret
key: spring.security.user.password
kubectl applyで変更内容を反映してください。
kubectl apply -f hello-pks.yml
出力結果
deployment "hello-pks" configured
curlのオプションに-u pksuser:Y4nys7f11を付けて/actuator/envにアクセスしてください。
curl -I -u pksuser:Y4nys7f11 http://<ServiceのLoad BalancerのIPまたはHost名>:8080/actuator/env
出力結果
HTTP/1.1 200 OK
Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8
Content-Length: 12116
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
TLS証明書の設定
次はSecretにTLS証明書を保存し、この証明書を使ってhello-pksアプリケーションのHTTPS対応を行います。
今回はアプリケーションのドメインとしてsslip.ioを使用します。
sslip.ioは、例えば192-168-0-1.sslip.ioというドメイン名解決すると192.168.0.1を返す、DNSサービスです。
この機能により、任意のIPアドレスに対して、ドメイン名を持つことができます。
任意のアルファベットと-を先頭につけたhello-192-168-0-1.sslip.ioというドメインでも192.168.0.1が返ります。
Node VMのIPアドレスがx.y.z.wの場合、hello-pks-x-y-z-w.sslip.ioをhello-pksアプリケーションのドメインにします。
まずは*.sslip.ioに対して、opensslコマンドでTLS自己署名証明書を作成します。こちらからスクリプトをダウンロードし、次のコマンドを実行してください。
chmod +x gen_ssl_certs.sh
./gen_ssl_certs.sh sslip.io
作成されたsslip.io.crtが証明書、sslip.io.keyが秘密鍵です。
TLS証明書のSecretは特別に--certと--keyオプションでkubectl create secretで作成できます。次のコマンドを実行してください。
kubectl create secret tls hello-pks-tls \
--cert=sslip.io.crt \
--key=sslip.io.key \
--dry-run \
-o yaml \
> hello-pks-tls.yml
kubectl apply -f hello-pks-tls.yml
出力結果
secret "hello-pks-tls" created
ServiceのExternal IPがHost名の場合は、
./gen_ssl_certs.sh <External IPのホスト名>でも良いです。
ELBの場合は例えば./gen_ssl_certs.sh ap-northeast-1.elb.amazonaws.comでも良いです。
この場合は、次のようにSecretを作成してください。kubectl create secret tls hello-pks-tls \ --cert=ap-northeast-1.elb.amazonaws.com.crt \ --key=ap-northeast-1.elb.amazonaws.com.key \ --dry-run \ -o yaml \ > hello-pks-tls.yml kubectl apply -f hello-pks-tls.yml
kubectl get secretでSecret一覧を確認してください。
kubectl get secret
出力結果
NAME TYPE DATA AGE
default-token-nkp9k kubernetes.io/service-account-token 3 2d
hello-pks-secret Opaque 2 2h
hello-pks-tls kubernetes.io/tls 2 9s
このTLS証明書を使ってhello-pksアプリケーションのHTTPS対応を行うための設定を行います。
SecretにはPEM形式の証明書が保存されていますが、JavaアプリケーションではPEM形式のままでは読み込めず、
PKCS12形式に変換した後、Keystoreにインポートする必要があります。
hello-pks.ymlに次の内容を記述して下さい。証明書の変換処理はInit Containersで行います。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-pks
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 50%
maxUnavailable: 50%
selector:
matchLabels:
app: hello-pks
template:
metadata:
labels:
app: hello-pks
spec:
initContainers:
- image: openjdk:8-jdk-slim
name: pem-to-keystore
volumeMounts:
- name: keystore-volume
mountPath: /keystores
- name: hello-pks-tls
mountPath: /hello-pks-tls
env:
- name: SERVER_SSL_KEY_PASSWORD
valueFrom:
secretKeyRef:
name: hello-pks-secret
key: server.ssl.key-password
command:
- sh
- -c
- |
openssl pkcs12 -export \
-name hello-pks \
-in /hello-pks-tls/tls.crt \
-inkey /hello-pks-tls/tls.key \
-out /keystores/hello-pks.p12 \
-password pass:foobar
keytool -importkeystore \
-destkeystore /keystores/hello-pks.jks \
-srckeystore /keystores/hello-pks.p12 \
-deststoretype pkcs12 \
-srcstoretype pkcs12 \
-alias hello-pks \
-deststorepass ${SERVER_SSL_KEY_PASSWORD} \
-destkeypass ${SERVER_SSL_KEY_PASSWORD} \
-srcstorepass foobar \
-srckeypass foobar \
-noprompt
containers:
- image: making/hello-pks:0.0.4
name: hello-pks
ports:
- containerPort: 8443
volumeMounts:
- name: keystore-volume
mountPath: /keystores
readOnly: true
env:
- name: SERVER_PORT
value: "8443"
- name: SERVER_SSL_ENABLED
value: "true"
- name: SERVER_SSL_PROTOCOL
value: TLSv1.2
- name: SERVER_SSL_KEY_STORE
value: file:///keystores/hello-pks.jks
- name: SERVER_SSL_KEY_STORE_TYPE
value: PKCS12
- name: SERVER_SSL_KEY_ALIAS
value: hello-pks
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: SPRING_SECURITY_USER_NAME
valueFrom:
secretKeyRef:
name: hello-pks-secret
key: spring.security.user.name
- name: SPRING_SECURITY_USER_PASSWORD
valueFrom:
secretKeyRef:
name: hello-pks-secret
key: spring.security.user.password
- name: SERVER_SSL_KEY_PASSWORD
valueFrom:
secretKeyRef:
name: hello-pks-secret
key: server.ssl.key-password
- name: SERVER_SSL_KEY_STORE_PASSWORD
valueFrom:
secretKeyRef:
name: hello-pks-secret
key: server.ssl.key-password
readinessProbe:
httpGet:
path: /actuator/health
port: 8443
scheme: HTTPS
initialDelaySeconds: 10
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/info
port: 8443
scheme: HTTPS
initialDelaySeconds: 20
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 1
volumes:
- name: keystore-volume
emptyDir: {}
- name: hello-pks-tls
secret:
secretName: hello-pks-tls
---
apiVersion: v1
kind: Service
metadata:
name: hello-pks
spec:
type: LoadBalancer # LoadBalancerに対応していない場合はNodePort
selector:
app: hello-pks
ports:
- protocol: TCP
port: 8443 # 8080から変更
spec.template.spec.initContainersにPod内で初期処理のための設定を記述します。
初期処理に必要なopensslとkeytoolコマンドを含む、openjdk:8-jdk-slimイメージを使用します。
初期処理のスクリプトはspec.template.spec.initContainers[].commandに記述できます。spec.template.spec.volumes[]にSecretのTLS証明書をマウントするためにhello-pks-tlsというVolumeと、変換したKeystoreファイルをコンテナ間で共有するためのkeystore-volumeというVolumeを定義します。keystore-volumeの定義にemptyDirが設定されていますが、これはPodが生存している間、そのPod内で利用可能な一時的なVolumeです。
Secretに保存されているTLS証明書および秘密鍵はマウントしたディレクトリにtls.crtとtls.keyというファイル名で参照可能になります。Init Containerでは
この証明書をopenssslコマンドでPKCS12形式に変換し、keytoolコマンドでKeystoreにインポートし、Keystoreをkeystore-volumeに保存しています。
あとはhello-pksコンテナでもこのVolumeをマウントすればアプリケーションでKeystoreを参照できるようになります。
アプリケーションのポートは慣習的に8443にします。hello-pksコンテナはポート番号を8080から8443に変える必要があります。また、ヘルスチェックのプロトコルもHTTPSにする必要があります。Serviceのポートも8443に変更します。
KeystoreのパスワードをSecretに保存するため、hello-pks-secret.ymlに次の内容を記述して下さい。
apiVersion: v1
kind: Secret
metadata:
name: hello-pks-secret
type: Opaque
stringData:
spring.security.user.name: pksuser
spring.security.user.password: Y4nys7f11
# ここから追加
server.ssl.key-password: changeme
以上の設定を適用するため、hello-pks-*.ymlのあるディレクトリで、次のコマンドを実行して下さい。まとめて適用されます。
kubectl apply -f hello-pks.yml -f hello-pks-secret.yml -f hello-pks-tls.yml
実行結果
deployment.apps/hello-pks configured
service/hello-pks configured
secret/hello-pks-secret configured
secret/hello-pks-tls configured
PodのSTATUSがRunningになったら、アプリケーションにアクセスしましょう。今度はHTTPではなく、HTTPSを使用して下さい。
自己署名証明書を使用しているので、curlの引数には-kをつけて下さい。
- Serviceの
typeがNodePortの場合、Node VMのIPアドレスがx.y.z.wであれば、アクセスするURLはhttps://hello-pks-x-y-z-w.sslip.io:<Node Port>です。 - Serviceの
typeがLoadBalancerの場合、EXTERNAL-IPがx.y.z.wであれば、アクセスするURLはhttps://hello-pks-x-y-z-w.sslip.io:8443です。
NodePortの場合、
curl -k https://hello-pks-127-0-0-1.sslip.io:31691
出力結果(Docker for Mac)
{
"node" : {
"NODE_IP" : "192.168.65.3",
"NODE_NAME" : "docker-for-desktop"
},
"pod" : {
"POD_IP" : "10.1.0.52",
"POD_NAME" : "hello-pks-859d9c7ffb-98vqw",
"POD_NAMESPACE" : "default"
},
"container" : { }
}
LoadBalancer(IP)の場合、
curl -k https://35-200-1-108.sslip.io:8443
出力結果(PKS on GCP)
{
"node" : {
"NODE_IP" : "192.168.20.11",
"NODE_NAME" : "vm-ad363daf-92c1-4aaf-52bc-318abc9b59c9"
},
"pod" : {
"POD_IP" : "10.200.11.25",
"POD_NAME" : "hello-pks-5546489c9c-bn955",
"POD_NAMESPACE" : "default"
},
"container" : { }
}
LoadBalancer(Host名)の場合、
curl -k https://abcc9a7f0de2211e9812f06c9c16b7d6-2012270132.ap-northeast-1.elb.amazonaws.com:8443
出力結果(PKS on AWS)
{
"node" : {
"NODE_IP" : "10.0.8.8",
"NODE_NAME" : "ip-10-0-8-8.ap-northeast-1.compute.internal"
},
"pod" : {
"POD_IP" : "10.200.59.36",
"POD_NAME" : "hello-pks-794f4478f7-4j8w2",
"POD_NAMESPACE" : "default"
},
"container" : { }
}
ブラウザでもHTTPSでアクセスできることを確認して下さい。
