Kubernetes（GKE）でLet's Encryptを自動更新する方法（ワイルドカード証明書）

### 本記事について
本記事では、Kubernetes（GKE）でLet's Encryptを自動更新する方法について記載します。
Kubernetesのパッケージ「[cert-manager](https://github.com/jetstack/cert-manager)」を用いて「[DNS認証](https://letsencrypt.org/how-it-works/)」によるチェックを行います。

以前にも「[Kubernetes（GKE）でLet's Encryptを自動更新する方法](https://qiita.com/kwbt/items/a53bad6b1f51711c0295)」を書いたのですが、手順が変わっていたので、ワイルドカード証明書の取得方法と合わせて、書き直しました。

公式マニュアルを元に、自分がハマったところを中心にいくつか追記しています。

- https://docs.cert-manager.io

## 手順（cert-managerインストールまで）
### Kubernetesを最新にする
1.12以前だと、作業に一手間必要なので、1.13以上にしておきます。

### namespace「cert-manager」追加
```sh
kubectl create namespace cert-manager

# validation無効（マニュアルから転記）
kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true
```
### cert-managerインストール
```sh
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.8.0/cert-manager.yaml
```

### 権限付与
```sh
kubectl create clusterrolebinding cluster-admin-binding \
  --clusterrole=cluster-admin \
  --user=$(gcloud config get-value core/account)
```

## 全体の流れ
以上でcert-managerのインストールが完了です。次の手順に進む前に、ざっくりと全体の流れを説明します。

### cert-managerの仕組み
cert-managerは大きく「Issuer」と「Certificate」で構成されています。

「Certificate」は主にドメイン・証明書に関する設定が記載されており、「Issure」は証明書取得の手段やアカウントに関する設定が記載されています。

「Certificate」の証明書の取得を「Issuer」で実行し、「Certificate」で定義されているシークレットに保存することにより、証明書を利用できるようにします。

### 証明書利用の仕組み
上記で取得した証明書はシークレットとしてKubernetesに保存されます。
そして、そのシークレットをIngressから参照することにより証明書を利用します。
（Ingressを用いた証明書の利用方法については別記事「[Kubernetes（GKE）でHTTPS通信する方法](https://qiita.com/kwbt/items/914b3ffab1e9b4e3c1a4)」にまとめました）

### Let's Encrypt DNS認証の仕組み
Let's Encrypt DNS認証では、Let's Encryptは、DNSレコードに指定した値が書き込まれたかをチェックして、請求者がそのドメインの所有者かをチェックします。
そこで、cert-managerは、GCPのDNSレコードを編集できるサービスアカウントをIssuerと紐付けることによって、IssuerがDNSレコードを編集できるようにし、Let's Encryptからのチェックをパスできるようにします。

## 手順（残り）
### GCPサービスアカウント作成
DNSレコードを編集できるサービスアカウントを作成します

1. [GCP]-[IAMと管理]-[サービスアカウント]-[サービスアカウントを作成]で任意のアカウントを作成
2. [GCP]-[IAMと管理]-[IAM]-[追加]で作成したアカウントを追加（役割を[DNS]-[DNS管理者]にする）
1. [GCP]-[APIとサービス]-[認証情報]-[認証情報を作成]-[サービスアカウントキー]でキーJSONファイルを作成（サービスアカウントを選択してキータイプをJSONにしてキーファイルを保存）

cert-managerからキーファイルの内容を参照できるよう、Kubernetesのシークレットにキーファイルを登録します。

```sh
kubectl create secret generic <シークレット名> \
    --from-file=key.json=<キーファイルパス> \
    --namespace=cert-manager
```

cert-manager本体は、インストールの際に作ったnamespace「cert-manager」に作られるので、そこからアクセスできるよう、namespace「cert-manager」内にキーファイルを登録します。

### Issuer作成
```yaml:issuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-issuer
  namespace: default
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    # ステージの場合のURLは : https://acme-staging-v02.api.letsencrypt.org/directory
    email: <Let's Encryptに登録するemail>
    privateKeySecretRef:
      name: letsencrypt-issuer
    dns01:
      providers:
        - name: clouddns
          clouddns:
            serviceAccountSecretRef:
              name: <DNSサービスアカウントのキーファイルのシークレット名>
              key: key.json
            project: <GCPプロジェクト名>
```
```sh
kubectl apply -f issuer.yaml
```

## Certificate作成
```yaml:certificate.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: example-com-certificate
  namespace: default
spec:
  secretName: example-com-certificate    # Ingressから参照するシークレット名
  issuerRef:
    name: letsencrypt-issuer  # Issuer名
    kind: ClusterIssuer
  commonName: "*.example.com"  # ドメイン名
  dnsNames:
  - example.com  # CloudDNSのDNS名
  acme:
    config:
    - dns01:
        provider: clouddns  # Issuerのprovider名
      domains:
      - "*.example.com" # ワイルドカード
      - example.com  # ドメイン名
```

```sh
kubectl apply -f certificate.yaml
```

### 確認
```sh
kubectl describe certificate,issuer,clusterissuer --all-namespaces
```
で状態をチェックできます。

うまくいくと、certificateの「Events」が下記のように表示されます。

```txt
Events:
  Type    Reason         Age   From          Message
  ----    ------         ----  ----          -------
  Normal  OrderCreated   5m5s  cert-manager  Created Order resource …
  Normal  OrderComplete  5m3s  cert-manager  Order …
  Normal  CertIssued     5m3s  cert-manager  Certificate issued successfully
```

証明書取得は、namespace「cert-manager」内の「cert-manager」ポッドが行っているので、うまく行かないようならポッドのログを見て原因究明します。

## ワイルドカード証明書について
通常証明書は、１つのURL（例えば「example.com」）に対して１つ発行されます。

一方、ワイルドカード証明書は、任意のサブドメイン（例えば「www.examble.com」「www1.example.com」…）に使える証明書になります。

cert-managerで取得した証明書は、任意のサブドメイン（「*.example.com」）とサブドメインなし（「example.com」）の両方に使えるので、
とりあえずワイルドカード証明書を取得しておけばいいかなと思います。

## 証明書の利用
Ingressのサンプルを記載します。
tlsのシークレットを、Certificateで設定したシークレット名にすることにより、IngressとCertificateの証明書を紐付けます。
（Ingressを用いた証明書の利用方法については別記事「[Kubernetes（GKE）でHTTPS通信する方法](https://qiita.com/kwbt/items/914b3ffab1e9b4e3c1a4)」にまとめました）

```yaml:ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  annotations:
    kubernetes.io/ingress.allow-http: "false"
    kubernetes.io/ingress.global-static-ip-name: "<static ip名>"
spec:
  tls:
  - secretName: example-com-certificate
  backend:
    serviceName: nginx
    servicePort: 80
```

## 補足説明
トライアルアンドエラーを繰り返していると、[Let's Encryptの制限](https://letsencrypt.org/docs/rate-limits/)に引っかかることがあるので、はじめは本番ではなくステージングでテストした方がよいかと思います。
ステージングは「issuer.yaml」の「server」を「``https://acme-staging-v02.api.letsencrypt.org/directory``」にするとなります。

## 参考情報
- https://qiita.com/apstndb/items/3a39a1e6acacbbc30765
- https://github.com/ahmetb/gke-letsencrypt
- https://cert-manager.readthedocs.io/en/latest/
- https://medium.com/google-cloud/kubernetes-w-lets-encrypt-cloud-dns-c888b2ff8c0e
- https://github.com/jetstack/cert-manager/issues/339

## 元記事
- [Kubernetes（GKE）でLet's Encryptを自動更新する方法（ワイルドカード証明書）](https://www.kwbtblog.com/entry/2019/06/06/025419)