なんてこったい(棒)
GitHub の Public リポジトリには、太っ腹な開発者によって大量の Credentials(外部サービスに接続するための秘密キーなど)が公開されており、賢い人たちが日夜クローラーを走らせてそれらを回収し、5万件もの Uber ドライバーの個人情報を頂戴するために利用したり、高価な AWS のインスタンスを沢山立ち上げて、もの凄い勢いでビットコインを発掘したりしているらしい。
ウチのリポジトリはプライベートだから問題ないよねって思われる方もおられるかもしれないが、ほんの5分間違って公開しただけで流出したケースもあるらしいので、そもそもコードリポジトリに秘密情報を入れること自体が太っ腹行為の可能性を高めていることを理解しておきたい。
というわけで、Kubernetes でサービスを運用する場合、そういった秘密情報をどこに保存すれば良いかという要求に応えるのが Secrets という仕組みである。
秘密情報を Secrets というデータベースで集中管理し、それぞれの情報はそれらを必要とする Pod/Container のみに送られる。Docker Image や Container を作るプロセスから秘密情報を切り離せるので、その過程で情報を漏洩させるリスクは少なくなる。Container に送られた秘密情報は tmpfs 上に置かれるので、ノード上のディスクに書き込まれることもない。
Kubernetes 自体がまだ若いプロジェクトなので、この Secrets にも注意しなければならない点がいくつかある。
- Secrets のデータは
etcd
の中に平文で保存されているので、etcd
には管理者ユーザーだけがアクセス出来るようにセットアップする必要がある。etcd
は kubernetes のあらゆるデータを保管しているデータストア。
- 現在のところ、Secrets に対するユーザーごとのアクセスコントロールは出来ない(将来的にはサポート予定)。
以下は、社内向けに書いた Secrets の簡単なチュートリアル。
Secret のデータ構造
Secret の中身は単純な Key-Value ペアのリスト:
kubectl
コマンドで登録されている Secret のリストを見る:
$ kubectl get secrets NAME TYPE DATA AGE default-token-pb7ls kubernetes.io/service-account-token 3 21m mysecret Opaque 2 38s
その中から一つの Secret を選んで中身を見てみる:
$ kubectl describe secret mysecret Name: mysecret Namespace: sandbox Labels: <none> Annotations: <none> Type: Opaque Data ==== password: 12 bytes username: 5 bytes
mysecret
の内容を図に書くと以下のような感じ:
Secret を登録する
Secret は、以下の二種類のファイルのいずれかを経由して登録できる。
- 中身が Value になっているファイル(便宜的に「Secret Value ファイル」と呼ぶ)
- YAML あるいは JSON 形式の Kubernetes Manifest ファイル
以下のような Secret を、
[Secret: test-secret] => [Key: password] => [Value: this-is-a-password]
それぞれのファイル形式で登録してみよう。
1. Secret Value ファイル経由
1) ファイルを作る
$ echo -n "this-is-a-password" > ./password
2) --from-file
オプションを使って登録
$ kubectl create secret generic test-secret --from-file=./password secret "test-secret" created
3) 中身を見てみる
$ kubectl describe secrets/test-secret Name: test-secret Namespace: sandbox Labels: <none> Annotations: <none> Type: Opaque Data ==== password: 18 bytes
--from-file
に指定したファイルの名前が Key になっていることが分かる。
2. Kubernetes Manifest ファイル経由
1) Value を base64 でエンコードする
$ echo -n "this-is-a-password" | base64 dGhpcy1pcy1hLXBhc3N3b3Jk
2) ファイルを作る
以下の内容を secret.yaml
に保存:
apiVersion: v1 kind: Secret metadata: name: test-secret type: Opaque data: password: dGhpcy1pcy1hLXBhc3N3b3Jk
3) -f
オプションを使って登録
$ kubectl create -f ./secret.yaml
4) 中身を見てみる
$ kubectl describe secrets/test-secret Name: test-secret Namespace: sandbox Labels: <none> Annotations: <none> Type: Opaque Data ==== password: 18 bytes
Secret Value ファイル経由のときと全く同じ Secret が出来ていることが分かる。
Secret の中身を取得する
$ kubectl get secret test-secret -o yaml apiVersion: v1 data: password: dGhpcy1pcy1hLXBhc3N3b3Jk kind: Secret metadata: creationTimestamp: 2017-03-01T08:49:49Z name: test-secret namespace: sandbox resourceVersion: "12535581" selfLink: /api/v1/namespaces/sandbox/secrets/test-secret uid: 0747637f-fe5c-11e6-8f7a-0674330dcd09 type: Opaque
Value をデコードする:
$ echo "dGhpcy1pcy1hLXBhc3N3b3Jk" | base64 --decode this-is-a-password
Secret を使う
Container から Secret を使うには、
- 環境変数
- Volume としてマウント
の二種類の経路がある。
1. 環境変数
Container の Manifest から以下のような感じで参照:
containers: - name: http-debug-server image: cotoami/http-debug-server:latest ports: - containerPort: 3000 env: - name: PASSWORD valueFrom: secretKeyRef: name: test-secret key: password
Container にログインして、環境変数を見てみる:
$ kubectl get pods NAME READY STATUS RESTARTS AGE http-debug-server-4073343574-ro8s8 1/1 Running 0 2m $ kubectl exec -it http-debug-server-4073343574-ro8s8 /bin/sh # echo $PASSWORD this-is-a-password
2. Volume としてマウント
Container 内のディレクトリに、Key をファイル名、Value をファイルの中身としてマウントできる。
以下のように、volumes
を定義しておいて、それを volumeMounts
でマウントする:
containers: - name: http-debug-server image: cotoami/http-debug-server:latest ports: - containerPort: 3000 volumeMounts: - mountPath: /tmp name: test-secret readOnly: true volumes: - name: test-secret secret: secretName: test-secret
Container にログインして、マウントされたファイルを見てみる:
$ kubectl get pods NAME READY STATUS RESTARTS AGE http-debug-server-2197190929-q6lke 1/1 Running 0 1m $ kubectl exec -it http-debug-server-2197190929-q6lke /bin/sh # cat /tmp/password this-is-a-password
マウントされた Secret を後から更新した場合は、Kubernetes が自動的に検出してリフレッシュしてくれる。