GKE + cert-managerを使ってDNS-01で証明書を取得する

Let's Encryptをつかった証明書取得が当たり前になってきました。Kubernetes上で動かすサービスでssl通信(https)をする際にもLet's Encryptで取得した証明書を活用できます。

本稿では、Google CloudのKubernetes上でcert-managerを動かし、DNS-01でのチャレンジを用いて証明書を取得するところまでをまとめます。

## DNS-01とHTTP-01

DNS-01とHTTP-01の違いはこちらが参考になります https://qiita.com/tappie/items/76881fdf7996c57a105a

DNS-01で証明書を取得するメリットをざっくりと挙げると:

- DNS-01ではDNSのTXTレコードにチャレンジに必要な情報を載せるため、バックエンドが必要ない（HTTP-01でのチャレンジではリクエストを受けるためにingressと対応するバックエンドが必要となる）
- バックエンドが必要ないことに伴い、http(s)以外を使う通信でもssl化しやすい（例: gRPC）

このあたりでしょうか。個人的にはgRPCなどでサービスを直接ssl化する必要がある場合にメリットがあるのでは、と思います。

## Kubernetesの準備

GKEを使います。クラスタを立ててみます:

```
$ gcloud container clusters create sandbox --zone=asia-northeast1-a --num-nodes=1
NAME     LOCATION           MASTER_VERSION  MASTER_IP       MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
sandbox  asia-northeast1-a  1.9.7-gke.6     35.221.104.131  n1-standard-1  1.9.7-gke.6   1          RUNNING
```

kubectlに認証情報を渡しておきましょう:

```
$ gcloud container clusters get-credentials sandbox --zone asia-northeast1-a
Fetching cluster endpoint and auth data.
kubeconfig entry generated for sandbox.
```

## helmの準備

cert-managerをデプロイするために、helmを用いることにしました。

まず、手元にhelmを導入します。

`helm-v2.9.1` をインストールしました:

```
$ curl -sL "https://kubernetes-helm.storage.googleapis.com/helm-v2.9.1-linux-amd64.tar.gz" | sudo tar xz -C /usr/local/bin/ --strip=1 linux-amd64/helm
$ helm init -c
$HELM_HOME has been configured at /home/ato/.helm.
Not installing Tiller due to 'client-only' flag having been set
Happy Helming!
```

初期化:

```
$ helm init
$HELM_HOME has been configured at /home/ato/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.

Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
```

確認してみましょう:

```
$ helm version
Client: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
```

導入完了です。


## cert-managerの準備

cert-managerをデプロイするためのvalues.yamlを以下のように設定しました:

```
$ cat cert-manager/values.yaml
ingressShim:
  extraArgs:
    - '--default-issuer-name=letsencrypt-prod'
    - '--default-issuer-kind=ClusterIssuer'
```

このvalues.yamlを使ってcert-managerをデプロイします。

```
$ helm upgrade --install my-cert-manager --namespace kube-system -f ./cert-manager/values.yaml stable/cert-manager
```


## cert-managerへissuerを追加

issuerを追加します。Let's Encryptの設定と、Google Cloud DNSを変更するためのサービスアカウントを設定します。

```
$ cat cert-manager/cluster-issuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: me@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    http01: {}

    dns01:
      providers:
      - name: prod-dns
        clouddns:
          project: my-project
          serviceAccountSecretRef:
            name: my-cert-manager-service-account
            key: key.json
```

```
$ kubectl apply -f ./cert-manager/cluster-issuer.yaml
```

### Google Cloud DNSを変更するためのサービスアカウントを追加


サービスアカウントをgcloudコマンドで取得する例です:

```
$ gcloud iam service-accounts create my-cert-manager --display-name "my-cert-manager" --project $(MY_CLOUD_PROJECT_NAME)
$ gcloud projects add-iam-policy-binding $(MY_CLOUD_PROJECT_NAME) --member serviceAccount:my-cert-manager@$(MY_CLOUD_PROJECT_NAME).iam.gserviceaccount.com --role roles/dns.admin
$ gcloud iam service-accounts keys create my-cert-manager-key.json --iam-account my-cert-manager@$(MY_CLOUD_PROJECT_NAME).iam.gserviceaccount.com
```

roleにはDNSの管理権限を付与します。
取得したkeyをsecretへ保管しておきます:

```
$ kubectl --namespace kube-system create secret generic my-cert-manager-service-account --from-file=key.json=my-cert-manager-key.json
```


## DNS-01で証明書を取得


certificateを登録して、証明書を取得しましょう。

my-app-certificate.yaml:

```
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: my-app
  namespace: default
spec:
  secretName: my-app-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: my-app.example.com
  dnsNames:
  - my-app.example.com
  acme:
    config:
    - dns01:
        provider: prod-dns
      domains:
      - my-app.example.com
```

適用します。

```
$ kubectl apply -f my-app-certificate.yaml
```

すると、DNSにチャンレンジ用のレコードが追加されます。

> ![image.png](https://qiita-image-store.s3.amazonaws.com/0/30362/1d83f4a9-220d-4dbe-dee2-fe9c2a7cca00.png)


数分後、証明書が！

```
$ kubectl get secret
NAME              TYPE                  DATA      AGE
my-app-tls        kubernetes.io/tls     2         3m
```


## おわりに


DNS-01のチャレンジでLet's EncryptのSSL証明書が取得できることがわかりました。

httpチャレンジとは異なり、Ingressに影響がありません。そのため、証明書が更新されていてもデプロイ済みのサービスを自動的に再起動しないのでは？と思いますが、この辺りは今後調べていきます。

以上です。