Secret
Kubernetes上でアプリケーションを実行する場合、MySQLデータベースなどに対して接続する際にユーザ名やパスワードが必要となるケースがあるかと思います。どのような手段が考えられるでしょうか?
Dockerビルド時にコンテナイメージに埋め込んでおく
イメージビルド時、アプリケーション側やコンテナの環境変数及び実行引数などにパスワードなどを埋め込んでおくことができます。しかし、イメージ自体に機密情報が入ってしまうと、Docker Registryなどにアップロードすることが困難になります。また、認証情報が変わった場合に再度イメージをビルドしなければならず、利便性もよくありません。
PodやDeploymentのYAML Manifestに記載して渡す
これもYAMLファイル自体に機密情報が入ってしまうため、GitHub上にこのYAML Manifestをアップロードすることが困難になります。また複数のアプリケーションから同じパスワードを参照する場合に、パスワードが散らばってしまうといった問題もあります。
このような場合に、ユーザ名やパスワードを別リソースとして定義しておき、Podから読み出すためのリソースがSecretです。
また、Secretが定義されたYAML Manifestを暗号化するkubesecといったOSSも存在します。kubesecはgpg、Google Cloud KMS、AWS KMSなどを利用することで簡単にdata.*の部分のみを暗号化することができるため、暗号化したYAMLファイルごとGitHub上にアップロードすることも可能です。
Secretの分類
一口にSecretといっても、いくつかのタイプに分けられています。通常のパスワードなどはGeneric用の「type: Opeque」を利用します。他にも、Ingressなどで参照可能なTLS用のSecretや、Docker Registryへの認証情報用のSecretなどもあります。また、手動で作ることはありませんが、PodにService AccountのTokenをマウントするための、「type: kubernetes.io/service-account-token」のSecretも存在します。
- Generic(type: Opaque)
- TLS(type: kubernetes.io/tls)
- Docker Registry(type: kubernetes.io/dockerconfigjson)
- Service Account(type: kubernetes.io/service-account-token)
GenericタイプのSecret
一般的なフリースキーマのSecretを作成する場合は、genericを指定します。作成方法は下記の4パターンがあります。
- ファイルから作成する(--from-file)
- yamlファイルから作成する
- kubectlから直接作成する(--from-literal)
- envfileから作成する
Secretでは、Secretの名前の中に、複数のKey-Value値が保存されます。例えば、Databaseの認証情報を作成する場合、Secret名はdb-auth、Keyはusername、passwordの2種類となります。もちろん、Kubernetesクラスタ内で複数のDBを作成する場合もあると思うので、その場合はSecret名をユニークとなるように定義するか、システムごとにNamespaceを分割する必要があります。
ファイルから作成する
ファイルから作成する場合には、--from-fileを指定します。通常はファイル名がそのままKeyとなるため、username.txtなど拡張子は外しておいた方がいいでしょう。外せない場合は--from-file=username=username.txtなどのように指定して下さい。また、改行コードが入らないようecho -nなどを使って出力して下さい。
リスト10:Secretを作成する元のファイル
1 | $ echo -n "root" > ./username |
2 | $ echo -n "rootpassword" > ./password |
作成したファイルを元にSecretを作成します。
リスト11:ファイルからSecretを作成
1 | $ kubectl create secret generic sample-db-auth \ |
2 | --from-file=./username --from-file=./password |
作成したSecretを確認するには、kubectl getでjsonかyamlフォーマットで出力し、dataの部分を確認します。
リスト12:作成したSecretを確認
1 | $ kubectl get secret sample-db-auth -o json | jq .data |
3 | "password": "cm9vdHBhc3N3b3Jk", |
base64でエンコードされているため、平文で確認するには下記のようにデコードします。
リスト13:平文に戻してSecretを確認
1 | $ kubectl get secret sample-db-auth -o json | jq -r .data.username |
3 | $ kubectl get secret sample-db-auth -o json | jq -r .data.username | base64 -d |
yamlファイルから作成する
yamlファイルから作成する場合、base64でエンコードした値をyamlファイルに埋め込みます。上記と同様のSecretを作成する場合は、下記のようにします。
リスト14:GenericタイプのSecretを作成するsecret_sample.yml
08 | password: cm9vdHBhc3N3b3Jk |
10 | $ kubectl create -f ./secret_sample.yml |
kubectlから直接作成する (--from-literal)
kubectlから直接作成するには、--from-literalオプションを使って作成します。
リスト15:kubectlから直接Secretを作成する
1 | $ kubectl create secret generic sample-db-auth \ |
2 | --from-literal=username=root --from-literal=password=rootpassword |
envfileから作成する
一括で作成する場合には、envfileから生成する方法もあります。Dockerで --env-fileオプションを使ってコンテナを起動していた場合は、この方法を利用することでそのままSecretに移行することが可能です。
リスト16:envfileからSecretを作成するenv_secret
4 | $ kubectl create secret generic sample-db-auth --from-env-file ./env_secret |
TLSタイプのSecret
証明書として利用するSecretを作成する場合は、tlsを指定します。TLSタイプのSecretはIngressリソースなどから利用することが一般的です。TLSタイプの場合には、基本的にファイルから作成することが望ましいです。
ファイルから作成する
ファイルから作成する場合には、--keyと--certに秘密鍵と証明書を指定します。
リスト17:TLSタイプのSecretを作成する
2 | $ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /tmp/tls.key -out /tmp/tls.crt -subj "/CN=sample1.example.com" |
5 | $ kubectl create secret tls tls-sample --key /tmp/tls.key --cert /tmp/tls.crt |
Docker RegistryタイプのSecret
Docker RegistryタイプのSecretを作成する場合は、docker-registryを指定します。
kubectl から直接作成する
kubectlから作成する際には、Registryサーバと認証情報を引数で指定します。
リスト18:Docker RegistryタイプのSecretを作成する
1 | $ kubectl create secret docker-registry sample-registry-auth \ |
2 | --docker-server=REGISTRY_SERVER \ |
3 | --docker-username=REGISTRY_USER \ |
4 | --docker-password=REGISTRY_USER_PASSWORD \ |
5 | --docker-email=REGISTRY_USER_EMAIL |
中身は、dockercfg形式のJSONがbase64エンコードされた形式になっています。そのため、YAMLを書いて作成するよりもkubectlから作成するほうがいいでしょう。
リスト19:Docker RegistryタイプのSecretの中身を見る
01 | $ kubectl get secret -o json sample-registry-auth | jq .data |
03 | ".dockercfg": "eyJSRUdJU1RSWV9TRVJWRVIiOnsidXNlcm5hbWUiOiJSRUdJU1RSWV9VU0VSIiw |
04 | icGFzc3dvcmQiOiJSRUdJU1RSWV9VU0VSX1BBU1NXT1JEIiwiZW1haWwiOiJSRUdJU1RSWV9VU0VSX0V |
05 | NQUlMIiwiYXV0aCI6IlVrVkhTVk5VVWxsZlZWTkZVanBTUlVkSlUxUlNXVjlWVTBWU1gxQkJVMU5YVDFKRSJ9fQ==" |
07 | $ kubectl get secret sample-registry-auth -o yaml | grep "\.dockercfg" | awk -F' ' '{print $2}' | base64 -d |
08 | {"REGISTRY_SERVER":{"username":"REGISTRY_USER", |
09 | "password":"REGISTRY_USER_PASSWORD","email":"REGISTRY_USER_EMAIL", |
10 | "auth":"UkVHSVNUUllfVVNFUjpSRUdJU1RSWV9VU0VSX1BBU1NXT1JE"}} |
Secretを利用したイメージの取得
認証がかかったDocker RegistryやDocker HubのPrivateリポジトリに置かれたイメージを取得する場合は、Secretを事前に作成した後、Podのspec.imagePullSecretsにdocker-registryタイプのSecretを指定する必要があります。
リスト20:Secretを用いてイメージを取得するsecret_pull_sample.yml
07 | - name: secret-image-container |
08 | image: REGISTRY_NAME/secret-image:latest |
10 | - name: sample-registry-auth |
Secretの利用
Secretをコンテナから利用する場合、大きく分けて下記の2つのパターンがあります。
- 環境変数として渡す
- Secretの特定のKeyのみ
- Secretの全てのKey
- Volumeとしてマウントする
- Secretの特定のKeyのみ
- Secretの全てのKey
環境変数として渡す
環境変数として渡す場合、特定のKeyのみを渡すか、Secret全体を渡すかの2通りの方法が用意されています。Secretの特定のKeyを渡す場合には、spec.containers[].envのvalueFrom.secretKeyRefを使って指定します。
リスト21:Secretを環境変数として渡すsecret_singl_env_sample.yml
04 | name: sample-secret-single-env |
07 | - name: secret-container |
16 | kubectl apply -f secret_single_env_sample.yml |
envとして1つずつ定義しているため、環境変数名が指定できる特徴があります。
リスト22:Secretの内容を確認
1 | $ kubectl exec -it sample-secret-single-env env | grep DB_USERNAME |
一方でSecretの全体を変数として展開することも可能です。Keyごとにそれぞれenvを設定する必要がないため、YAML configが冗長ではなくなりますが、Secretにどのような値が保存されているかをPod Templateからは判断しづらくなるといったデメリットもあります。
リスト23:Secret全体を環境変数として展開するsecret_multi_env_sample.yml
04 | name: sample-secret-multi-env |
07 | - name: secret-container |
13 | kubectl apply -f secret_multi_env_sample.yml |
以下のように、Secretに登録されているすべてのKeyが環境変数として登録されていることが確認できます。
リスト24:すべてのKeyを確認
1 | $ kubectl exec -it sample-secret-multi-env env |
Volumeとしてマウントする
Volumeとしてマウントする場合も、特定のKeyのみを渡すか、Secret全体を渡すかの2通りの方法が用意されています。Secretの特定のKeyを渡す場合には、spec.volumes[]のsecret.items[]を使って指定します。
リスト25:Volumeとしてマウントするsecret_single_volume_sample.yml
04 | name: sample-secret-single-volume |
07 | - name: secret-container |
15 | secretName: sample-db-auth |
20 | $kubectl apply -f secret_single_volume_sample.yml |
マウントするファイルを1つずつ定義しているため、ファイル名を指定できる特徴があります。
リスト26:Secretの内容を確認
1 | $ kubectl exec -it sample-secret-single-volume cat /config/username.txt |
Secretの全体を変数として展開することも可能です。こちらもYAML configが冗長ではなくなりますが、Secretにどのような値が保存されているかを、Pod Templateからは判断しづらくなるといったデメリットもあります。
リスト27:Secret全体をVolumeとしてマウントするsecret_multi_volume_sample.yml
04 | name: sample-secret-multi-volume |
07 | - name: secret-container |
15 | secretName: sample-db-auth |
17 | $ kubectl apply -f secret_multi_volume_sample.yml |
リスト28:Secret全体の値を確認
1 | $ kubectl exec -it sample-secret-multi-volume ls /config |
動的なSecretの更新
Volumeマウントを利用したSecretに限り、一定期間ごと(kubeletのSync Loopのタイミング)にkube-apiserverに変更を確認し、変更がある場合は入れ替えを行います。
デフォルトではSyncLoopの間隔は60秒に設定されています。この頻度を上げる場合には、kubeletのオプションとして--sync-frequencyを設定して下さい。また、環境変数を利用したSecretの場合、動的な更新はできません。
例えば、先ほどの例のPodではthreadの値は16で設定しています。
リスト29:更新前のSecretの値を確認
1 | $ kubectl exec -it sample-secret-multi-volume cat /config/username |
3 | $ kubectl get pod sample-secret-multi-volume |
4 | NAME READY STATUS RESTARTS AGE |
5 | sample-secret-multi-volume 1/1 Running 0 11m |
その後、Secretの値を書き換えます。
リスト30:Secretの値を書き換える
01 | $ cat << _EOF_ | kubectl apply -f - |
一定期間が経過すると、Volumeにマウントされているファイルの値が書き換わっていることが確認できます。また、Podが再作成されることもないため、瞬断も起きません。
リスト31:更新後のSecretの値を確認
1 | $ kubectl exec -it sample-secret-multi-volume cat /config/username |
3 | $ kubectl get pod sample-secret-multi-volume |
4 | NAME READY STATUS RESTARTS AGE |
5 | sample-secret-multi-volume 1/1 Running 0 11m |
また、今回の例ではSecretの中身をusernameだけにしてapplyしているため、その他のファイルが削除されている点にも注意して下さい。
リスト32:Secretの中身はusernameのみ
1 | $ kubectl exec -it sample-secret-multi-volume ls /config |