Azure Kubernetes Service(AKS)にLet's EncryptのSSL証明書を入れる

# 元ネタ
- [Azure Kubernetes Service (AKS) の静的パブリック IP アドレスを使用してイングレス コントローラーを作成する](https://docs.microsoft.com/ja-jp/azure/aks/ingress-static-ip)

# 準備
- [Azure Kubernetes Service(AKS)を使って、K8sクラスタを作成する
](https://qiita.com/jansnap/items/7bb4ff67ef5dc3630069)
    - Helmをクライアントに入れて、サーバ側にtillerを入れるところまで。
        - RBAC が有効になっているクラスタ。

# 注意
- 静的パブリック IP アドレスは、お金がかかる
    - [パブリック IP アドレスの料金](https://azure.microsoft.com/ja-jp/pricing/details/ip-addresses/)のページを参照。
        - 最初の 5 つは0.5円/時間ぐらい(2019-04-29現在)。0.5円*24時間*30.5日=366円/月ぐらいか。
- AKS クラスターを削除すると、静的パブリック IP アドレスも解放されて消えてしまうので注意。


# 静的IPアドレスを取得する

-  AKS クラスターのリソース グループ名を取得
    -  ```AKS_NODE_RES_GROUP=`az aks show --resource-group $AKS_RES_GROUP --name $AKS_CLUSTER_NAME --query nodeResourceGroup -o tsv` ```


- 静的パブリック IP アドレスを作成
    - ```az network public-ip create --resource-group $AKS_NODE_RES_GROUP --name myAKSPublicIP --allocation-method static```
        - 上記では名前を```myAKSPublicIP```にしているが、適宜変更。
- 作成した静的パブリック IP アドレスを確認
    - ```az network public-ip list --query [].ipAddress```
        - 複数ある場合は、後々の設定でハマるので、ポータルから「パブリック IP アドレス 」で探して削除する。
    - ```PUBLIC_IP_ADDRESS=`az network public-ip list --query [].ipAddress -o tsv` ```
        



# nginx-ingress をIPアドレス指定でインストール

- インストール
    - namespaceを```kube-system```にしている。サンプルによっては```kube-public```にしている場合もあるので注意。
 
```
helm install stable/nginx-ingress \
    --namespace kube-system \
    --set controller.service.loadBalancerIP=$PUBLIC_IP_ADDRESS
```



- パブリックIPを確認
    - ```kubectl get service -l app=nginx-ingress --namespace kube-system```
        - ```EXTERNAL-IP```が```<pending>```の場合は、少し待つ。

```
NAME                                             TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
excited-starfish-nginx-ingress-controller        LoadBalancer   10.0.14.43     40.115.xxx.xxx   80:30202/TCP,443:31925/TCP   1m
excited-starfish-nginx-ingress-default-backend   ClusterIP      10.0.121.158   <none>          80/TCP                       1m
```

- ブラウザで、上記の40.115.xxx.xxxにアクセスする
    - ```default backend - 404```のように、デフォルトの404ページが表示される(起動に数分かかるので待つ)


# DNS上の名前を決めて、設定

- ```DNSNAME="my-aks-ingress-test123"```
- ```PUBLICIPID=$(az network public-ip list --query "[?ipAddress!=null]|[?contains(ipAddress, '$PUBLIC_IP_ADDRESS')].[id]" --output tsv)```
- ```az network public-ip update --ids $PUBLICIPID --dns-name $DNSNAME```

- これでFQDNを使ってアクセスできるようになったので、ブラウザで開いて確認する
    - http://my-aks-ingress-test123.japaneast.cloudapp.azure.com/
        - ```default backend - 404```が出ればOK。


# cert-manager をインストール

- Let's Encrypt 証明書を自動的に作成および管理する機能を提供する ```cert-manager``` を入れる
    - 0.7.2が出ているので、0.6ではなく0.7を使う。
      - ```kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.7/deploy/manifests/00-crds.yaml```
    - cert-manager用に名前空間を作成(```kube-system```ではなく```cert-manager```という名前空間に入れる)
        - ```kubectl create namespace cert-manager```
        - ```kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true```
            - ```disable-validation=true```を付けないと発行者が不明になって証明書が作成されない？？
    - helmを使ってインストールする([GitHubのcert-managerのページ](https://github.com/jetstack/cert-manager/releases) を見ると0.7.2が最新なのでバージョン指定する際は確認する)
        - ```helm repo add jetstack https://charts.jetstack.io```
        - ```helm repo update```

```
helm install \
  --name cert-manager \
  --namespace cert-manager \
  --version v0.7.2 \
  jetstack/cert-manager
```

- ~~本番環境の場合は、```--set ingressShim.extraArgs='{--default-issuer-name=letsencrypt-prod,--default-issuer-kind=ClusterIssuer}' ```も付ける。~~
    - 下記の例では名前空間```kube-system```に入れている
    - ~~```webhook.enables=false``` を書かないと証明書が発行されないという情報もあるので追記~~
    - ```stable/cert-manager```だとうまくいかなかったので```jetstack/cert-manager```を使った
- 上記で証明書を入れられたので、下記は参考。

```
helm install jetstack/cert-manager \
  --name cert-manager \
  --namespace kube-system \
  --set ingressShim.defaultIssuerName=letsencrypt-prod \
  --set ingressShim.defaultIssuerKind=ClusterIssuer \
  --set webhook.enabled=false
```


- 入れ間違えたら、```helm delete --purge cert-manager```で消して入れ直し。
    - Error: customresourcedefinitions.apiextensions.k8s.io "certificates.certmanager.k8s.io" already exists の場合は、```kubectl delete -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.7/deploy/manifests/00-crds.yaml``` で消す

- これだけで(信頼されない)証明書が発行されるので、ブラウザでHTTPSで接続して確認する
    - https://my-aks-ingress-test123.japaneast.cloudapp.azure.com/
        - 信頼されない証明書の警告がでるので、適宜スルーする
        - Common Nameは```Kubernetes Ingress Controller Fake Certificate```になる


# CA クラスター発行者を作成

- ```cluster-issuer.yaml```ファイルを以下の内容で作成
- ```email```を、実際のメールアドレスに変更する
- テスト用なので、```staging```を使用する。本番環境では```letsencrypt-prod``` と ```https://acme-v02.api.letsencrypt.org/directory``` を使用する
    - サーバはv01ではなくv02を使わないと、ずっと```Issuer letsencrypt-staging not ready```のままで原因が分かりづらい。
        - ```kubectl describe clusterissuer letsencrypt-staging```や```kubectl describe clusterissuer letsencrypt-prod```で状況を確認。


```
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
  #name: letsencrypt-prod
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    #server: https://acme-v02.api.letsencrypt.org/directory
    email: user@example.com
    privateKeySecretRef:
      name: letsencrypt-staging
      #name: letsencrypt-prod
    http01: {}
```

- ```kubectl apply -f cluster-issuer.yaml``` で適用
    - ```Error from server (InternalError): error when creating "cluster-issuer.yaml": Internal error occurred: failed calling admission webhook "clusterissuers.admission.certmanager.k8s.io": the server is currently unable to handle the request``` と表示されたら、しばらく待ってもう一度行う

# デモ アプリを入れる
- デフォルトの404ページで動作確認しようとしてはまったので、デモ アプリを入れる
    - ```helm repo add azure-samples https://azure-samples.github.io/helm-charts/```
    - ```helm install azure-samples/aks-helloworld --namespace ingress-basic```
    - ```helm list --namespace ingress-basic```
    - ```helm install azure-samples/aks-helloworld  --namespace ingress-basic  --set title="AKS Ingress Demo"  --set serviceName="ingress-demo"```
    - ```helm list --namespace ingress-basic```

# イングレス ルートを作成
- ```hello-world-ingress.yaml``` を以下で作成
- ```hosts```や```host```は自分のホスト名に変更する

```
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: hello-world-ingress
  namespace: ingress-basic
  annotations:
    kubernetes.io/ingress.class: nginx
    certmanager.k8s.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
  - hosts:
    - my-aks-ingress-test123.japaneast.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: my-aks-ingress-test123.japaneast.cloudapp.azure.com
    http:
      paths:
      - path: /
        backend:
          serviceName: aks-helloworld
          servicePort: 80
      - path: /hello-world-two
        backend:
          serviceName: ingress-demo
          servicePort: 80
```

- ```kubectl apply -f hello-world-ingress.yaml```で適用


# 証明書の確認

- 上記で自動的に要求される
    - ```kubectl describe certificate tls-secret --namespace ingress-basic```
    - ```kubectl describe certificate tls-secret```
    - ```kubectl describe certificate``` で状況確認
        - ```OrderCreated``` が無ければ、[cert-manager の証明書についてのページ](https://cert-manager.readthedocs.io/en/latest/reference/certificates.html)や[Orders](https://cert-manager.readthedocs.io/en/latest/reference/orders.html)を見ながら、証明書を要求
    
    - ```kubectl describe order```

    - ずっと```Issuer letsencrypt-prod not ready```のままの場合、```kubectl describe clusterissuer letsencrypt-staging```や```kubectl describe clusterissuer letsencrypt-prod```で状況を確認。
        - 引数は```cluster-issuer```ではなく```clusterissuer```なので注意

- ```kubectl describe clusterissuer letsencrypt-prod``` で状態が表示できる

```
(略)
Status:
  Acme:
    Uri:  https://acme-v02.api.letsencrypt.org/acme/acct/56269408
  Conditions:
    Last Transition Time:  2019-05-02T07:29:25Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>
```

- ```kubectl describe certificate tls-secret --namespace ingress-basic``` で以下のようにイベント詳細が表示できる

```
(略)
Status:
  Conditions:
    Last Transition Time:  2019-05-02T07:29:51Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2019-07-31T06:29:50Z
Events:
  Type     Reason              Age                    From          Message
  ----     ------              ----                   ----          -------
  Warning  IssuerNotFound      3m44s                  cert-manager  clusterissuer.certmanager.k8s.io "letsencrypt-prod" not found
  Warning  IssuerNotReady      3m28s (x3 over 7m31s)  cert-manager  Issuer letsencrypt-prod not ready
  Normal   Generated           3m27s                  cert-manager  Generated new private key
  Normal   GenerateSelfSigned  3m27s                  cert-manager  Generated temporary self signed certificate
  Normal   OrderCreated        3m27s                  cert-manager  Created Order resource "tls-secret-224255xxxx"
  Normal   OrderComplete       3m1s                   cert-manager  Order "tls-secret-224255xxxx" completed successfully
  Normal   CertIssued          3m1s                   cert-manager  Certificate issued successfully
```

- ステータスが```Certificate issuance in progress. Temporary certificate issued.```の場合は、しばらく待つ。
    - Temporary certificateが発行されてから、本物のcertificateが発行される。という順番なので、ステータスで確認。


- chromeの開発者ツールのSecurityで見ると、```Certificate - valid and trusted
``` の下に  ```The connection to this site is using a valid, trusted server certificate issued by 不明な名前.```となっている場合がある。対処方法不明。


# ブラウザで表示して、証明書の確認
- https://my-aks-ingress-test123.japaneast.cloudapp.azure.com のように、HTTPSでつないで、証明書の確認
    - Common Name(CN)が、```my-aks-ingress-test123.japaneast.cloudapp.azure.com``` のように、 自分のホスト名になる。
- https://my-aks-ingress-test123.japaneast.cloudapp.azure.com/hello-world-two では、2つ目のデモアプリ(画像と文言が少し違う)が表示される



# 証明書オブジェクトを作成(必要に応じて)
- ```certificates.yaml```ファイルを以下の内容で作成。
- ```dnsNames``` と ```domains``` を前の手順で作成した DNS 名に変更する

```
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: tls-secret
spec:
  secretName: tls-secret
  dnsNames:
  - my-aks-ingress-test123.japaneast.cloudapp.azure.com
  acme:
    config:
    - http01:
        ingressClass: nginx
      domains:
      - my-aks-ingress-test123.japaneast.cloudapp.azure.com
  issuerRef:
    name: letsencrypt-staging
    #name: letsencrypt-prod
    kind: ClusterIssuer
```

- ```kubectl apply -f certificates.yaml```で適用。