Edited at

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にチャンレンジ用のレコードが追加されます。



数分後、証明書が!

$ kubectl get secret

NAME TYPE DATA AGE
my-app-tls kubernetes.io/tls 2 3m


おわりに

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

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

以上です。