2020.10.07

KubernetesのConfig&Storageリソースを使ってみた

こんにちは。次世代システム研究室のM.Mです。

現在、Kubernetes、Docker構成で作られたPHPのWebアプリの開発を担当しているのですが、すでにKubernetes、Docker構成で構築が終わって運用されている状態のため、なかなかWebアプリの開発案件からKubernetes、Dockerの構築・運用に関連するノウハウは溜まりませんでした。

何かKubernetes関連で活かせるものがないかということで、今回はKubernetes、Dockerの構築・運用関連ではなく、Webアプリで利用している設定ファイルに記載された情報(データベースの接続ユーザー名やパスワードなど)をKubernetesのConfig&StorageリソースであるSecretで管理することで何のメリットがあるのかを調べてみました。

目次

  1. 現在のWEBアプリのデプロイについて
  2. Secretについて
  3. Secretの作成とマニフェストファイルの設定
  4. PHPのWebアプリからSecretの値を取得
  5. Secretの管理について
  6. 感じたメリット

1. 現在のWEBアプリのデプロイについて

まずデプロイのイメージ図になります。
デプロイの流れは、上記図のようにCI環境にて
①gitリポジトリからWEBアプリのソースコードを取得します。
②CI環境で管理されているDBの接続ユーザーやパスワードなどが記載された設定ファイルを①で取得したソースコードに含めます。
③ソースコード・設定ファイルを含めた形で、各ノードサーバーへコピーします。
④ホストOS上のソースコード・設定ファイルをコンテナから参照できるように、マウント設定をしてコンテナを起動します。
※一連の流れは自動化されています。

ソースコードはgit管理していますが、データベースの接続ユーザーやパスワードを管理する設定ファイルはgit管理せず、CIサーバー上で管理してデプロイ時に展開するようにしています。
さすがにパスワードが記載されている設定ファイルをgit管理するのは良くないとの思いからですが、git管理でもしっかりアクセス制限や権限管理をすれば、CIサーバー上でやっているアクセス制限や権限管理と大して変わらないのでは?とも思えます。

2. Secretについて

Config&Storageリソースは、設定や機密情報、ボリュームなどに関するリソースで、コンテナから参照できるようになります。
Config&StorageリソースにはSecret、ConfigMapなどがあります。

Secretの情報はKVSであるetcdに保存されていて、対象のSecretを利用するpodにのみ転送されるようになっています。
また、転送されたSecretの情報は、メモリ上の一時的なファイルシステムで管理されるようになっており、対象のコンテナ内からしか参照できず、コンテナが起動しているホストOSからも参照できないようになっています。

コンテナからの参照の仕方は、主に以下の2種類あります。

・コンテナ起動時に環境変数として渡して、コンテナ内の環境変数から取得する
・コンテナ起動時にVolumeとしてマウントして、コンテナ内のVolumeから取得する

今回は環境変数を利用する方法で試しました。

3. Secretの作成とマニフェストファイルの設定

■Secret作成
・適当にユーザー名とパスワードを記載した設定ファイルを用意します。
$ vi secrettest.txt
username=hogehoge
password=abcde12345
・その設定ファイルを元にSecretを生成します。(Secretの名前はtest-db-infoにしています)
$ kubectl create secret generic --save-config test-db-info --from-env-file secrettest.txt 
secret/test-db-info created
・作成内容確認します。
$ kubectl get secrets
NAME                  TYPE                                  DATA   AGE
test-db-info          Opaque                                2      13s

$ kubectl get secrets -o json test-db-info
{
    "apiVersion": "v1",
    "data": {
        "password": "YWJjZGUxMjM0NQ==",
        "username": "aG9nZWhvZ2U="
    },
    "kind": "Secret",
    "metadata": {
        "annotations": {
            "kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Secret\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"test-db-info\",\"creationTimestamp\":null},\"data\":{\"password\":\"YWJjZGUxMjM0NQ==\",\"username\":\"aG9nZWhvZ2U=\"}}\n"
        },
        "creationTimestamp": "2020-09-25T02:34:19Z",
        "name": "test-db-info",
        "namespace": "default",
        "resourceVersion": "34374231",
        "selfLink": "/api/v1/namespaces/default/secrets/test-db-info",
        "uid": "9ce8fdce-fed7-11ea-8fbd-08002711add2"
    },
    "type": "Opaque"
}
3行目でtest-db-infoが作成されているのが確認できます。
また、9-10行目をみると、単純にbase64されただけのusername, passwordが設定されていることが確認できます。

■コンテナのSecretの利用設定
      - name: app-test
        image: "xxxxxxxxxxxxxxxxxx"
        imagePullPolicy: "Always"
        ports:
          - containerPort: 9000
        volumeMounts:
        - mountPath: /app
          name: test-current
        envFrom:
          - secretRef:
              name: test-db-info
            prefix: TEST_
マニフェストファイルに記載されたSecretを利用するコンテナの設定に、上記9-12行目のようにenvFrom, secretRefなどの設定を追加します。
今回は、環境変数を利用する設定にしていて、環境変数のプレフィックスにTEST_をつける設定にしています。

4. PHPのWebアプリからSecretの値を取得

コンテナを起動して、環境変数からユーザー名、パスワードを取得できるか確認します。
■ホストOS上で確認

$ echo $TEST_username
⇒取得できず

■対象のコンテナで確認

$ docker exec -it 527cec14cdfb /bin/sh
echo $TEST_username
hogehoge ⇒取得できた

■Secretの利用指定していないコンテナで確認

$ docker exec -it 7d96535b9b61 /bin/sh
echo $TEST_username
⇒取得できず

意図した通り、対象のコンテナ内でしか取得することができませんでした。
では、PHPのWEBアプリで参照できるか?

<?php
...
$username = getenv('TEST_username');
$password = getenv('TEST_password');

echo $username.'/'.$password;
取得できない・・。なぜ?
PHPのWEBアプリはphp-fpmで動かしており、php-fpmは特に指定しない環境変数はクリアする設定になっている模様。

php-fpmの設定ファイルに

clear_env = no

もしくは、

env[TEST_username] = $TEST_username
env[TEST_password] = $TEST_password

というように必要な環境変数だけ設定する記載を追加すればPHPのWebアプリからも参照できるようになりました。

ただ、clear_env = no だと必要のない環境変数までphp-fpm側に設定されてしまうし、env[TEST_username] = $TEST_usernameといった形だと、新しく追加するときに手間がかかりprefixも管理しないといけない。

やはり、環境変数からの取得ではなくVolumeをマウントしてVolumeから取得するようにしたほうがよさそうに思えました。
とは言え、Secretを使って、PHPのWEBアプリからユーザー名とパスワードの値を取得することができました。

5. Secretの管理について

今回、Secretの作成は、ファイルにusername=hogehoge、password=abcde12345と記載して、 kubectl create secretコマンドにてそのファイルを読み込んで作成するという方法を取りましたが、Secretは以下のようにマニフェストファイルを使って作成することもできます。
apiVersion: v1
kind: Secret
metadata:
  name: test-db-info
type: Opaque
data: 
  username: aG9nZWhvZ2U=
  password: YWJjZGUxMjM0NQ==
上記マニフェストファイルのファイル名をsecrettest.yamlとした場合、以下のコマンドで作成することができます。

kubectl apply -f secrettest.yaml

ただ、このままgitリポジトリに上げて管理するとなると、username, passwordは単純にbase64しているのみなので良くありません。
そこでkubesecを使うと上記のようなSecretのマニフェストファイルを暗号化・復号化することができるようです。
実際に検証まで進めていませんが、暗号化される箇所がusernameやpasswordの値の部分だけで、以下のようにマニフェストファイルの構造はそのまま維持された状態で暗号化されるのも利点になっているようです。

apiVersion: v1
kind: Secret
metadata:
  name: test-db-info
type: Opaque
data: 
  username: 暗号化された値
  password: 暗号化された値
このように暗号化されたマニフェストファイルでSecretを管理すれば、gitリポジトリに上げることも可能になると思います。

6. 感じたメリット

機密情報をgitリポジトリに上げなくなる。

対象のSecretを利用するコンテナにのみ転送させることができるので、Secretさえしっかり管理できていれば、各ノードに設定ファイルを転送するといったことが不要になる。
(大体CI環境から自動で転送できるような仕組みは出来上がっていて設定ファイルの転送でもさほど困らないとは思いますが)

個人的には以下のメリットが一番大きいと思います。

環境変数、Volumeにしても、Secretを利用するコンテナ上からしか参照できない。サーバーに情報が残らない。
自動化しているとはいえ、設定ファイルを各ノードにばらまいている場合、ノードを減らした場合など、しっかりと削除運用しないとばらまいた設定ファイルが残ったままになり良くない。

まとめ

WEBアプリからの視点で見ると、Secretを使うことでコンテナ上でしか参照できなくなり管理上は良くなったとしても、Secretから取得されるユーザー名やパスワードの値は平文なので、設定ファイルから取得するのか環境変数やVolumeから取得するのか程度の違いしか感じませんでした。

逆に環境変数の場合、WEBアプリにバグがあって、環境変数が参照されてしまうとか、WEBフレームワークの機能などで知らずにログに環境変数の値がでていたとかのほうが怖いような気がしました。
Secretに平文で登録するのではなく、はじめから暗号化したユーザー名、パスワードを登録して、復号化のキーだけ別管理しておけば、その心配もないかも知れませんが。

最後に、次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。

Pocket

関連記事