GKE+freenom+Let'sEncrypt+cert-managerでTLS証明書を自動更新する

今まで検証環境はロードバランサーのIP直で接続していたのですが、
アプリと組み込むにあたり、httpsであることがもとめられてきました。

今回はドメインはfreenomで無料で取得しLet's EncryptでTLS証明書を発行してhttps化するところまでの備忘録を残しておきたいと思います。

やらないといけないことは以下です。少し長いですが一個づつやっていきたいと思います。

- freenomでフリードメインの取得
- Cloud DNSにドメイン登録する
- freenomのNameServersにGCPのNameServerを登録する
- GCPで静的なグローバルIPを取得する
- Cloud DNSにAレコードを登録して反映を待つ
- ServiceをNodePortに変更する
- Ingressを設定する

**ここでまででhttp接続ができます**

- tillerサービスアカウントを作成する
- helmの初期化する
- cert-managerをインストール
- cert-managerIAMを作成する
- cert-managerIAMにdnsのロールを追加する
- cert-managerIAMの接続JSON発行する
- 作成されたJSONをシークレットを登録する
- Let's EncryptへのDNS検証するためIssuerを作成する
- Certを作成しDNS検証を行う
- Ingressにtls設定をする

#freenomでフリードメインの取得

freenomのドメイン取得については[無料のドメインを取得する](https://qiita.com/teekay/items/135dc67e39f24997019e)こちらの記事が非常に丁寧に記載してくれていますので内容は省略しております

[freenom](https://my.freenom.com/clientarea.php)で好きなドメインを取得します。

![190424-0002-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/ef023ff8-06f6-0975-1428-c4c34c0808f9.png)

ドメインはtk,ml,ga,cf,gqを選ぶことができます、好きなドメインを取得してください。
サイトが重いですが、無料なのでしょうがないと諦めます

![190424-0005-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/c3d011bd-98a7-78b0-5a78-64f4cc50a360.png)

一旦ドメインの発行だけを行いたいのでForward this domain toは適当に入力し

Periodは12ヶ月を選択しContinueを押します。
この後個人情報の入力などありますが、これでドメインが取得できました。


# Cloud DNSにドメイン登録する

取得したドメインをCloudDNSに登録します

![190507-0009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/491169a6-f03c-7339-6d49-72baf7255b6d.png)

ゾーン名はDNS-01チャレンジで使うのでわかりやすい名前を設定してください
DNS名は先ほどfreenomで取得したドメインを入れます。

![190507-0001-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/3fdd83b6-6a31-f0fb-03ef-890b5b916e8c.png)

登録するとこのようになります、NameServerがわかったので、FreenomのNameServerに登録します

# freenomのNameServersにGCPのNameServerを登録する

![190507-0003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/3fedd409-cc69-ee58-3c6a-a962cf01fca1.png)

Cloud DNSに表示されているNameServerを入力します。

# GCPで静的なグローバルIPを取得する

GCPに戻り、静的なグローバルUPを取得します。

![190507-0010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/9489f163-d84e-a50b-b8e7-ce5c594d5aeb.png)

名前は自分のわかりやすい名前を設定してください、**ここのIPに設定した名前は後ほど使います**

![190507-0011-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/04a74f3c-5c5a-f628-530f-46bdcbd5b0d4.png)

このような形でグローバルIPが割り当てられます。
静的なはIPはインスタンスと紐づいていない場合は従量課金が発生します。

# Cloud DNSにAレコードを登録して反映を待つ

サブドメインのAレコードに先ほどのIPを紐づけます

![190507-0002-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/40831b03-cf84-539b-0510-5733f6918da8.png)

今回は２つIPを取得していますが、特に意味はありません
develop.[domain]でサブドメインにAレコードを発行します

反映までしばらく時間がかかります、digなり[ドメイン検索](https://www.cman.jp/network/support/ip.html)するなりして作成したサブドメインがAレコードで登録したIPに向くのを待ちます

# ServiceをNodePortに変更する
もともとロードバランサーの発行したIPで接続していたのですが、Ingressからの接続に変更するため、
NodePortに変更します。

```yaml:service.yaml
apiVersion: v1
kind: Service
metadata:
  name: api-service-dev
spec:
  selector:
    app: web-server-dev
  ports:
    - name: http
      protocol: "TCP"
      port: 80
      targetPort: 80
  type: "NodePort"
```

nginxは80でListenしているのでtargetPortは80です、もしも8080など別のポートで接続したい場合はnginxのconfを変更してください
![190507-0012-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/4c827bd4-d5c5-e4cf-7536-12c9ff55333b.png)

applyし、GKEのサービスで確認すると10.**.**.**:80のようにNodePortになっているのがわかります

# Ingressを設定する

```yaml:ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: api-service-dev-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: [IPに設定した名前]
    kubernetes.io/ingress.class: "gce"
spec:
  rules:
  - host: develop.[ドメイン].tk
    http:
      paths:
      - path: /*
        backend:
          serviceName: api-service-dev
          servicePort: 80
```

serviceNameは先ほどのServiceの名前です。

Ingressの反映は10分ほどかかります、しばらくして取得したドメインでhttp接続できれば成功です。


# tillerサービスアカウントを作成する

```
$ kubectl create serviceaccount tiller --namespace kube-system
serviceaccount "tiller" created
$ kubectl create clusterrolebinding tiller --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
clusterrolebinding "tiller" created
```

作成したtillerアカウントにcluster-admin権限を付与します

# helmの初期化する
helmは別途インストールしてください

```
$ helm init --upgrade --service-account tiller
$HELM_HOME has been configured at /Users/raharu/.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.
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
```

# cert-managerをインストール
helmでcert-managerをインストールします。v0.6をインストールしようとするとCRDsを先にインストールしてくださいと言われるので、今回はv0.5.2を使用します。

```
$ helm install stable/cert-manager --name cert-manager --namespace kube-system --version v0.5.2

NAME:   cert-manager
LAST DEPLOYED: Tue May  7 14:51:38 2019
NAMESPACE: kube-system
STATUS: DEPLOYED

~skio

https://cert-manager.readthedocs.io/en/latest/reference/ingress-shim.html
```

# cert-managerIAMにdnsのロールを追加する
cert-managerのIAMアカウントを作成し、dnsの変更権限を付与します

```
$ gcloud iam service-accounts create cert-manager --display-name "cert-manager"
Created service account [cert-manager].
```

```
$ gcloud projects add-iam-policy-binding [プロジェクトID] --member serviceAccount:cert-manager@[プロジェクトID].iam.gserviceaccount.com --role roles/dns.admin
Updated IAM policy for project [プロジェクトID].
bindings:
~skip
- members:
  - serviceAccount:cert-manager@[プロジェクトID].iam.gserviceaccount.com
  role: roles/dns.admin
~skip
```

プロジェクトIDは自分のIDに変更してください

# cert-managerIAMの接続JSON発行する

```
$ gcloud iam service-accounts keys create cert-manager-key.json --iam-account cert-manager@[プロジェクトID].iam.gserviceaccount.com
```

これでcert-manager-key.jsonが出来上がります

# 作成されたJSONをシークレットを登録する

```
$ kubectl create secret generic clouddns-service-account --from-file=cert-manager-key.json=cert-manager-key.json
```

# Let's EncryptへのDNS検証するためIssuerを作成する

```yaml:cert-manager-issuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    # 検証用URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: [メールアドレス]
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    http01: {}
    # ACME dns-01 provider configurations
    dns01:
      # Here we define a list of DNS-01 providers that can solve DNS challenges
      providers:
      - name: prod-dns
        clouddns:
          # A secretKeyRef to a the google cloud json service account
          serviceAccountSecretRef:
            name: clouddns-service-account
            key: cert-manager-key.json
          # The project in which to update the DNS zone
          project: [プロジェクトID]

```

Issuerは一旦検証用のURLで作成します。
projectは自分のプロジェクトIDに変更してください

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

範囲したら確認します。

```
$ kubectl describe issuer
Name:         letsencrypt-prod
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"certmanager.k8s.io/v1alpha1","kind":"Issuer","metadata":{"annotations":{},"name":"letsencrypt-prod","namespace":"default"},"spec":{"acme...
API Version:  certmanager.k8s.io/v1alpha1
Kind:         Issuer
Metadata:
  Creation Timestamp:  2019-05-07T08:46:53Z
  Generation:          1
  Resource Version:    20432444
  Self Link:           /apis/certmanager.k8s.io/v1alpha1/namespaces/default/issuers/letsencrypt-prod
  UID:                 *********-******-*******-*****
Spec:
  Acme:
    Dns 01:
      Providers:
        Clouddns:
          Project:  [プロジェクトID]
          Service Account Secret Ref:
            Key:   cert-manager-key.json
            Name:  clouddns-service-account
        Name:      prod-dns
    Email:         [メールアドレス]
    Http 01:
    Private Key Secret Ref:
      Key:
      Name:  letsencrypt-prod
    Server:  https://acme-staging-v02.api.letsencrypt.org/directory
Status:
  Acme:
    Uri:  https://acme-staging-v02.api.letsencrypt.org/acme/acct/9150475
  Conditions:
    Last Transition Time:  2019-05-07T08:47:00Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>
```

StatusにエラーがなくTypeがReadyになっていれば問題ありません。
一度Issuerを削除して本番用URLにします。

```
$ kubectl delete -f cert-manager-issuer.yaml
```

```yaml:cert-manager-issuer.yaml

apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    #server: https://acme-staging-v02.api.letsencrypt.org/directory
    #本番用URLに変更
    server: https://acme-v02.api.letsencrypt.org/directory
```

再度反映します

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

# Certを作成しDNS検証を行う

```yaml:cert-manager-cert.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: [名前は適宜変更]
  namespace: default
spec:
  secretName: cert-manager-tls
  issuerRef:
    name: letsencrypt-prod
  commonName: *****.tk
  dnsNames:
  - develop.*****.tk
  acme:
    config:
    - dns01:
        provider: prod-dns
      domains:
      - develop.*****.tk
```

ドメインは自分のドメインをいれてください

```
$ kubectl apply -f cert-manager-cert.yaml
```

反映して確認を行います。

```
$ kubectl describe cert

Name:         [名前は先ほど指定した名前]
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"certmanager.k8s.io/v1alpha1","kind":"Certificate","metadata":{"annotations":{},"name":"*********","namespace":"default"},"spec":{"acm...
API Version:  certmanager.k8s.io/v1alpha1
Kind:         Certificate
Metadata:
  Creation Timestamp:  2019-05-07T09:06:29Z
  Generation:          1
  Resource Version:    20435693
  Self Link:           /apis/certmanager.k8s.io/v1alpha1/namespaces/default/certificates/*********
  UID:                 *******-*******-*******-******
Spec:
  Acme:
    Config:
      Dns 01:
        Provider:  prod-dns
      Domains:
        develop.*****.tk
  Common Name:  *****.tk
  Dns Names:
    develop.*****.tk
  Issuer Ref:
    Name:       letsencrypt-prod
  Secret Name:  cert-manager-tls
Status:
  Acme:
    Order:
      URL:  https://acme-v02.api.letsencrypt.org/acme/order/*********/********
  Conditions:
    Last Transition Time:  2019-05-07T09:10:25Z
    Message:               Certificate issued successfully
    Reason:                CertIssued
    Status:                True
    Type:                  Ready
    Last Transition Time:  <nil>
    Message:               Order validated
    Reason:                OrderValidated
    Status:                False
    Type:                  ValidateFailed
Events:
  Type    Reason          Age   From          Message
  ----    ------          ----  ----          -------
  Normal  CreateOrder     12m   cert-manager  Created new ACME order, attempting validation...
  Normal  DomainVerified  9m    cert-manager  Domain "*****.tk" verified with "dns-01" validation
  Normal  DomainVerified  8m    cert-manager  Domain "develop.******.tk" verified with "dns-01" validation
  Normal  IssueCert       8m    cert-manager  Issuing certificate...
  Normal  CertObtained    8m    cert-manager  Obtained certificate from ACME server
  Normal  CertIssued      8m    cert-manager  Certificate issued successfully
```

しばらく待っていると検証が完了しCertificate issued successfullyとなります。

これで、cert-manager-tlsという名前のシークレットが作成されているはずです。

```
$ kubectl get secret
NAME                       TYPE                                  DATA      AGE
cert-manager-tls           kubernetes.io/tls                     2         15h
```

# Ingressにtlsを追加する

```yaml:ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: api-service-dev-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: [IPに設定した名前]
    kubernetes.io/ingress.class: "gce"
spec:
  tls:
    - hosts:
        - develop.******.tk
      secretName: cert-manager-tls
  rules:
  - host: develop.[ドメイン].tk
    http:
      paths:
      - path: /*
        backend:
          serviceName: api-service-dev
          servicePort: 80
```

しばらく待っているとHTTPS接続ができるはずです！


![190507-0008-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42874/08d303aa-aaaf-db2d-15d7-bc7082d82962.png)

色々な記事を参考にさせてもらいながらどうにかTLS証明書を発行＆自動更新まで作成することができました。
GAEのようにプロジェクトドメインを自動で発行してくれると尚楽なのですが、今後のGCPの発展に期待します。

また本来本番運用はLet's Encryptではなく有料証明書を発行して行うのが良いかとおもいますが、GAEの証明書もLet's Encryptとなっている今なにを選択するのかはお任せいたします。

また本記事は自分の備忘録に記載しております、同じようなことをこれから行う方の一助になれば幸いです。

ご意見、マサカリなどお待ちしております。


参考記事
https://qiita.com/atotto/items/9b626505cc1a56919bc2
https://qiita.com/apstndb/items/2fef0a80d4510516cb1f
https://torumakabe.github.io/post/aks_tls_autorenewal/
https://qiita.com/hali/items/ee71bc318233bad32d81
https://github.com/helm/charts/issues/10869