GKE で TLS 証明書と秘密鍵を自動的に扱う (kube-cert-manager 編)

注意: kube-cert-manager より洗練された [cert-manager](https://qiita.com/apstndb/items/3a39a1e6acacbbc30765) が開発されているため、この記事の内容の採用はオススメしません。

この記事は [Google Cloud Platform(1) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/gcp) の17日目の記事です。

@apstndb です。好きな GCP のサービスは GKE です。

今回は Let's Encrypt から自動的に TLS 証明書を取得する実装の1つとして、Kubernetes 上で動作する
 [kube-cert-manager](https://github.com/kelseyhightower/kube-cert-manager) を使って、TLS 証明書が取得できることを確認します。

## kube-cert-manager の特徴

Kubernetes で証明書を扱うソフトウェアには他にも [kube-lego](https://github.com/jetstack/kube-lego) があります。
実は私も [GKE でサービスを HTTPS と HTTP/2 に対応する(kube-lego 編)](http://qiita.com/apstndb/items/2fef0a80d4510516cb1f) として記事を書いたので、下記では kube-lego と比較した kube-cert-manager の特徴を紹介します。

- Let's Encrypt の ACME プロトコルでのドメイン証明のために [DNS-01](https://letsencrypt.github.io/acme-spec/#rfc.section.7.4) を使用
	- kube-lego は Ingress を通して [HTTP-01](https://letsencrypt.github.io/acme-spec/#rfc.section.7.1) を使用
	- kube-cert-manager からドメインに対応する DNS ゾーンに TXT レコードを自動的に追加/削除
		- Google Cloud DNS 対応
		- 他の DNS ネームサーバには [DNS Provider Plugins](https://github.com/kelseyhightower/kube-cert-manager/blob/master/docs/plugins.md) で対応可能
	- HTTP(S) ロードバランサ以外で TLS を終端可能と自由度が高い
- 設定に Certificate という名前の ThirdPartyResource を使用
	- kube-lego は Ingress で指定するドメイン名と Secret の名前以外はプロセスの環境変数に渡す必要があり、複数の設定を使い分けられなかった
	- kube-cert-manager では証明書ごとに別の設定が可能

なお、ThirdPartyResource については [Kubernetes Advent Calendar 2016](http://qiita.com/advent-calendar/2016/kubernetes) の17日目(この記事を書いている今日！)の記事である「[Kubernetesを拡張しよう](https://www.ianlewis.org/jp/extending-kubernetes-ja)」がオススメです。

## 検証環境

- Kubernetes 1.5.1
- kube-cert-manager 0.2.0

なお、今回の記事では下記の状態を前提としています。

- GKE クラスタが既に存在
- 対象のドメイン名に対応する Managed Zone が Cloud DNS 上に既に存在
	- 記事中ではドメイン名 `example.cf` を使用

### 注意: kube-cert-manager のバージョンについて

この記事を書くために動作確認をした時点でソースツリー上に Deployment が用意されていた最新バージョンである kube-cert-manager 0.2.0 を使用しましたが、これは[サブドメイン対応](https://github.com/kelseyhightower/kube-cert-manager/pull/14)がまだマージされていない頃のリリースだったようです。
既に [DockerHub](https://hub.docker.com/r/kelseyhightower/kube-cert-manager/tags/) に 0.5.0 のタグが存在しているため、サブドメインを使いたい場合は適宜新しいバージョンを使ってください。
(一応[プルリクエスト](https://github.com/kelseyhightower/kube-cert-manager/pull/19)は出しておきました。)

## 準備

kube-cert-manager を使うためにクラスタで一度だけ必要な準備をします。

### Cloud DNS アクセス権のあるサービスアカウントの用意

kube-cert-manager は内部的にサービスアカウントを使って Cloud DNS にアクセスします。ここでは Service Account を準備します。

Cloud DNS アクセスに使うサービスアカウントを作成します。

```
$ gcloud iam service-accounts create dns-admin
Created service account [dns-admin].
```

サービスアカウントに Cloud DNS へのレコードの追加に必要な権限を設定します。2016年12月17日現在 [roles/dns.admin](https://cloud.google.com/dns/access-control#roles) が最も適切なロールのようです。

```
$ gcloud projects add-iam-policy-binding [PROJECT_ID] --member=serviceAccount:dns-admin@[PROJECT_ID].iam.gserviceaccount.com --role=roles/dns.admin
```

サービスアカウントの認証に使う JSON ファイルを取得します。

```
$ gcloud iam service-accounts keys create service-account.json --iam-account=dns-admin@[PROJECT_ID].iam.gserviceaccount.com
```

### Certificate ThirdPartyResource の登録

kube-cert-manager では、ドメインと各種リソースの関連を表現するために ThirdPartyResource を使います。
下記のようにして ThirdPartyResource である Certificate を作成できるようにします。


```
$ kubectl create -f kube-cert-manager/extensions/certificate.yaml
thirdpartyresource "certificate.stable.hightower.com" created
```

### kube-cert-manager が使用する GCE 永続ディスクの作成

kube-cert-manager は Let's Encrypt のアカウント情報や鍵を保存するために BoltDB をファイルシステム上で使用します。よって kube-cert-manager の Pod が終了しても消えないボリュームをマウントする必要があります。
今回は サンプルの `kube-cert-manager/deployments/kube-cert-manager.yaml` で指定されている `kube-cert-manager` という名前の GCE 永続ディスクを作成します。


```
$ gcloud compute disks create kube-cert-manager --size 10GB

WARNING: You have selected a disk size of under [200GB]. This may result in poor I/O performance. For more information, see: https://developers.google.com/compute/docs/disks#pdperformance.
Created [https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/asia-northeast1-a/disks/kube-cert-manager].
NAME               ZONE               SIZE_GB  TYPE         STATUS
kube-cert-manager  asia-northeast1-a  10       pd-standard  READY

New disks are unformatted. You must format and mount a disk before it
can be used. You can find instructions on how to do this at:

https://cloud.google.com/compute/docs/disks/add-persistent-disk#formatting
```

### kube-cert-manager のデプロイ

kube-cert-manager を GKE にデプロイします。
Deployment のマニフェストが用意されているので、そのまま使用します。

```
$ kubectl create -f kube-cert-manager/deployments/kube-cert-manager.yaml
deployment "kube-cert-manager" created
```


### サービスアカウントの JSON ファイルの登録

先程作成した `dns-admin` サービスアカウントを kube-cert-manager から使えるように、 JSON ファイルを Kubernetes Secret としてデプロイします。

```
$ kubectl create secret generic example-domain --from-file=service-account.json
secret "example-domain" created
```

## 秘密鍵と証明書の自動生成

準備が終わったので、ここから実際に kube-cert-manager の動作を確認します。

### Certificate オブジェクトの作成

`kubernetes/certificates/hightowerlabs-com.yaml` を元に Certificate リソースを作成します。

```
$ cat > example-cert.yaml <<EOF
apiVersion: "stable.hightower.com/v1"
kind: "Certificate"
metadata:
  name: "example-dot-cf"
spec:
  domain: "example.cf"
  email: "hostmaster@example.cf" # Let's Encrypt に登録するメールアドレス
  provider: "googledns"
  secret: "example-domain" # 先程作成したサービスアカウントの JSON を含む Secret 名
  secretKey: "service-account.json" # secret の中のサービスアカウントのファイル名
EOF

$ kubectl create -f example-cert.yaml
certificate "example-dot-cf" created
```


### 取得できていることの確認

kube-cert-manager の出力を見ると、下記のようにして証明書の取得が行われていることがわかります。

```
$kubectl logs -f $(kubectl get pod -l app=kube-cert-manager -o name) kube-cert-manager
(中略)
2016/12/17 10:10:24 Creating new Let's Encrypt account: example.cf
2016/12/17 10:10:33 Monitoring _acme-challenge.example.cf. DNS propagation: ns-cloud-a1.googledomains.com.:53 n
s-cloud-a2.googledomains.com.:53 ns-cloud-a3.googledomains.com.:53 ns-cloud-a4.googledomains.com.:53
2016/12/17 10:10:56 example.cf DNS-01 challenge complete on ns-cloud-a1.googledomains.com.:53
2016/12/17 10:10:59 example.cf DNS-01 challenge complete on ns-cloud-a2.googledomains.com.:53
2016/12/17 10:11:02 example.cf DNS-01 challenge complete on ns-cloud-a3.googledomains.com.:53
2016/12/17 10:11:03 example.cf DNS-01 challenge complete on ns-cloud-a4.googledomains.com.:53
2016/12/17 10:11:33 _acme-challenge.example.cf. DNS propagation complete.
2016/12/17 10:11:37 example.cf secret missing.
2016/12/17 10:11:37 example.cf secret created.
2016/12/17 10:11:41 Syncing Kubernetes secret: example.cf
```

実際に Secret の中に証明書と秘密鍵ができています。

```
$ kubectl describe secrets example.cf
Name:           example.cf
Namespace:      default
Labels:         <none>
Annotations:    <none>

Type:   kubernetes.io/tls

Data
====
tls.crt:        1785 bytes
tls.key:        1679 bytes
```

下記のようにすると、秘密鍵が正しく読めることも確認できます。(要 jq)

```
$ kubectl get secret example.cf -o json | jq -r '.data."tls.crt"' | base64 -D
-----BEGIN CERTIFICATE-----
(以下略)
```

## まとめ

今回は kube-cert-manager を使用して GKE で TLS の秘密鍵と証明書を自動的に扱うことができることを確認しました。
プラグインさえ書けば Cloud DNS 以外にも対応可能な作りになっており、 Kubernetes のレイヤで TLS 対応することがかなり現実的に見えてきた気がしますね。
来年には HTTPS が必須な場面が更に増えてくるとされていますが、これなら乗り切れるんじゃないでしょうか。