OKE (Oracle Container Engine for Kubernetes) で、Let's Encrypt を使用して無料SSL LoadBalancer公開

# はじめに

Oracle Cloud では、Oracle Container Engine for Kubernetes (OKE) という名前のKubernetesのマネージドサービスを提供しています。この記事では、OKE の ServiceType : LoadBalancer を Let's Encrypt を使用して無料でSSL公開を行う手順を確認します。



# 前提

以下の手順を参考に、Let's Encrypt から SSL証明書を発行していることを前提としてます。
https://qiita.com/sugimount/items/3f43c55141252f8f2968#lets-encrypt%E3%81%A7ssl%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%82%92%E5%85%A5%E6%89%8B

具体的には、上記リンクにも記載していますが、以下のように SSL証明書を取得していることを前提としています。

```
# ls -la /etc/letsencrypt/live/test.sugi.tokyo/
total 4
drwxr-xr-x 1 root root 512 Feb 10 05:56 .
drwx------ 1 root root 512 Feb 10 05:56 ..
-rw-r--r-- 1 root root 692 Feb 10 05:56 README
lrwxrwxrwx 1 root root  39 Feb 10 05:56 cert.pem -> ../../archive/test.sugi.tokyo/cert1.pem
lrwxrwxrwx 1 root root  40 Feb 10 05:56 chain.pem -> ../../archive/test.sugi.tokyo/chain1.pem
lrwxrwxrwx 1 root root  44 Feb 10 05:56 fullchain.pem -> ../../archive/test.sugi.tokyo/fullchain1.pem
lrwxrwxrwx 1 root root  42 Feb 10 05:56 privkey.pem -> ../../archive/test.sugi.tokyo/privkey1.pem
```



# OKEを構築

@hhiroshellさんの以下の記事を参考にしてOKEクラスタを作ります

https://qiita.com/hhiroshell/items/5e812a4cccbdbb16a3fb



# HTTPでアクセス

まずは、HTTPを使用して公開方法を確認します。
Nginx の Deployment マニフェストを作成

```
string trim '
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-nginx-svc
  labels:
    app: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: nginx
' > ~/workdir/nginx-http.yaml
```



applyします

```
kubectl apply -f ~/workdir/nginx-http.yaml
```



Kubernetes 上では、ServiceType : LoadBalancer が作成開始されて、OCI上で Load Balancer が作成されるのを Pending しています

```
> kubectl get svc -o wide
NAME           TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE   SELECTOR
kubernetes     ClusterIP      10.96.0.1     <none>        443/TCP        17m   <none>
my-nginx-svc   LoadBalancer   10.96.65.55   <pending>     80:31886/TCP   29s   app=nginx
```



OCI上で Load Balancer が作成語、Load Balancerの Global IP が EXTERNAL-IP に出力されます

```
> kubectl get svc -o wide
NAME           TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)        AGE   SELECTOR
kubernetes     ClusterIP      10.96.0.1     <none>           443/TCP        18m   <none>
my-nginx-svc   LoadBalancer   10.96.65.55   129.213.71.130   80:31886/TCP   1m    app=nginx
```



curlでアクセスすると、NginxのWelcomeページが表示されています

```
> curl http://129.213.71.130/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```



参考として、OCIのLoadBalancerの詳細情報を確認します。
backends には、各Nodeに対して 31886 が割り当ててあるので、LoadBalancerの後ろにはNodePort的なものが動いている様子が見えます。
LBの通信帯域が `100MB` となっている点も気になります。
なお、 Annotation を指定することで、通信帯域を含めた様々なパラメータを変更できるようです。
https://github.com/oracle/oci-cloud-controller-manager/blob/master/docs/load-balancer-annotations.md

```
> oci lb load-balancer list
{
  "data": [
    {
      "backend-sets": {
        "TCP-80": {
          "backends": [
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.64.2",
              "name": "10.0.64.2:31886",
              "offline": false,
              "port": 31886,
              "weight": 1
            },
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.96.2",
              "name": "10.0.96.2:31886",
              "offline": false,
              "port": 31886,
              "weight": 1
            },
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.32.2",
              "name": "10.0.32.2:31886",
              "offline": false,
              "port": 31886,
              "weight": 1
            }
          ],
          "health-checker": {
            "interval-in-millis": 10000,
            "port": 10256,
            "protocol": "HTTP",
            "response-body-regex": ".*",
            "retries": 3,
            "return-code": 200,
            "timeout-in-millis": 3000,
            "url-path": "/healthz"
          },
          "name": "TCP-80",
          "policy": "ROUND_ROBIN",
          "session-persistence-configuration": null,
          "ssl-configuration": null
        }
      },
      "certificates": {},
      "compartment-id": "ocid1.tenancy.oc1..aaaaaaaaqhjag3s37bdq6b6pc4f6lyc6bvcxdisqjzsd6tpks3ablacdwnaa",
      "defined-tags": {},
      "display-name": "c3758617-2cec-11e9-8f6f-0a580aed26c3",
      "freeform-tags": {},
      "hostnames": {},
      "id": "ocid1.loadbalancer.oc1.iad.aaaaaaaab376decncoxe7klxldwffgre4wzj5acldfnesc5lljntlkuk22oa",
      "ip-addresses": [
        {
          "ip-address": "129.213.71.130",
          "is-public": true
        }
      ],
      "is-private": false,
      "lifecycle-state": "ACTIVE",
      "listeners": {
        "TCP-80": {
          "connection-configuration": {
            "idle-timeout": 300
          },
          "default-backend-set-name": "TCP-80",
          "hostname-names": null,
          "name": "TCP-80",
          "path-route-set-name": null,
          "port": 80,
          "protocol": "TCP",
          "rule-set-names": [],
          "ssl-configuration": null
        }
      },
      "path-route-sets": {},
      "rule-sets": {},
      "shape-name": "100Mbps",
      "subnet-ids": [
        "ocid1.subnet.oc1.iad.aaaaaaaab4aso5kbx6f4ydo5o3xeg5ppdtf5zqczkd2p7jdr5jqqcuvyhznq",
        "ocid1.subnet.oc1.iad.aaaaaaaatxkp55537gnqievnvohxdvjfipqpbvyrv2jhb5z53ootb3crzpua"
      ],
      "time-created": "2019-02-10T04:31:45.245000+00:00"
    }
  ]
}
```



確認できたので、削除をします

```
kubectl delete svc my-nginx-svc
kubectl delete deploy my-nginx
```





# HTTPSでアクセス

以下のDocumentに、ServiceType : LoadBalancer を SSL 化する内容があります。これを参考に進めていきます。
https://docs.cloud.oracle.com/iaas/Content/ContEng/Tasks/contengcreatingloadbalancer.htm#creatinglbhttps



let's Encrypt から生成した SSL証明書のうち、`cert.pem` と `privkey.pem` を使用して、KubernetesのSecret を作成する必要があります。

```
# ls -la /etc/letsencrypt/live/test.sugi.tokyo/
total 4
drwxr-xr-x 1 root root 512 Feb 10 05:56 .
drwx------ 1 root root 512 Feb 10 05:56 ..
-rw-r--r-- 1 root root 692 Feb 10 05:56 README
lrwxrwxrwx 1 root root  39 Feb 10 05:56 cert.pem -> ../../archive/test.sugi.tokyo/cert1.pem            # SSL/TLS サーバ証明書 (公開鍵を含む)
lrwxrwxrwx 1 root root  40 Feb 10 05:56 chain.pem -> ../../archive/test.sugi.tokyo/chain1.pem          # 中間証明書
lrwxrwxrwx 1 root root  44 Feb 10 05:56 fullchain.pem -> ../../archive/test.sugi.tokyo/fullchain1.pem  # サーバ証明書と中間証明書が結合されたもの
lrwxrwxrwx 1 root root  42 Feb 10 05:56 privkey.pem -> ../../archive/test.sugi.tokyo/privkey1.pem      # 秘密鍵
```



kubectl でSecretをつくります

```
kubectl create secret tls ssl-certificate-secret --key privkey.pem --cert cert.pem
```



Deployment用のマニフェストファイルを作成します。大事なポイントは、LoadBalancerの annotation に、作成した Secret と port 443 を指定しているところです。

```
string trim '
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
  name: nginx-service
  annotations:
    service.beta.kubernetes.io/oci-load-balancer-ssl-ports: "443"
    service.beta.kubernetes.io/oci-load-balancer-tls-secret: ssl-certificate-secret
spec:
  selector:
    app: nginx
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port: 443
    targetPort: 80
' > ~/workdir/nginx-https.yaml
```



applyします

```
kubectl apply -f ~/workdir/nginx-https.yaml
```



以下のように Service が作成されるまで待機します

```
> kubectl get svc -o wide                                                                                        
NAME            TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                      AGE   SELECTOR
kubernetes      ClusterIP      10.96.0.1      <none>           443/TCP                      16h   <none>
nginx-service   LoadBalancer   10.96.242.48   129.213.14.248   80:30797/TCP,443:31892/TCP   51s   app=nginx
```



作成した EXTERNAL-IP を `test.sugi.tokyo` に紐づくかたちで Aレコードを OCI DNS に作成します。JSONファイルを準備します

```
string trim '
[
  {
    "domain": "test.sugi.tokyo",
    "is-protected": false,
    "rdata": "129.213.14.248",
    "rrset-version": "2",
    "rtype": "A",
    "ttl": 30
  }
]
' > ~/workdir/test.sugi.tokyo.json
```



A レコードを作成

```
oci dns record domain patch --zone-name-or-id "sugi.tokyo" --domain "test.sugi.tokyo" --items file://~/workdir/test.sugi.tokyo.json
```



ブラウザからアクセステスト。
https://test.sugi.tokyo/

![001.jpg](https://qiita-image-store.s3.amazonaws.com/0/104247/b694705c-81e2-1868-abda-c8915310e3fc.jpeg)

![002.jpg](https://qiita-image-store.s3.amazonaws.com/0/104247/b96eedb0-c9b4-5b5d-3097-1614804f1659.jpeg)





`openssl` コマンドで見てみると、正常に動作しているように見えます

```
> openssl s_client -connect test.sugi.tokyo:443
CONNECTED(00000003)
depth=0 CN = test.sugi.tokyo
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = test.sugi.tokyo
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
 0 s:/CN=test.sugi.tokyo
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
```



念のため、OCI LB 側の詳細を確認します

```
> oci lb load-balancer list
{
  "data": [
    {
      "backend-sets": {
        "TCP-443": {
          "backends": [
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.96.2",
              "name": "10.0.96.2:31892",
              "offline": false,
              "port": 31892,
              "weight": 1
            },
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.64.2",
              "name": "10.0.64.2:31892",
              "offline": false,
              "port": 31892,
              "weight": 1
            },
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.32.2",
              "name": "10.0.32.2:31892",
              "offline": false,
              "port": 31892,
              "weight": 1
            }
          ],
          "health-checker": {
            "interval-in-millis": 10000,
            "port": 10256,
            "protocol": "TCP",
            "response-body-regex": ".*",
            "retries": 3,
            "return-code": 200,
            "timeout-in-millis": 3000,
            "url-path": "/healthz"
          },
          "name": "TCP-443",
          "policy": "ROUND_ROBIN",
          "session-persistence-configuration": null,
          "ssl-configuration": null
        },
        "TCP-80": {
          "backends": [
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.96.2",
              "name": "10.0.96.2:30797",
              "offline": false,
              "port": 30797,
              "weight": 1
            },
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.64.2",
              "name": "10.0.64.2:30797",
              "offline": false,
              "port": 30797,
              "weight": 1
            },
            {
              "backup": false,
              "drain": false,
              "ip-address": "10.0.32.2",
              "name": "10.0.32.2:30797",
              "offline": false,
              "port": 30797,
              "weight": 1
            }
          ],
          "health-checker": {
            "interval-in-millis": 10000,
            "port": 10256,
            "protocol": "HTTP",
            "response-body-regex": ".*",
            "retries": 3,
            "return-code": 200,
            "timeout-in-millis": 3000,
            "url-path": "/healthz"
          },
          "name": "TCP-80",
          "policy": "ROUND_ROBIN",
          "session-persistence-configuration": null,
          "ssl-configuration": null
        }
      },
      "certificates": {
        "ssl-certificate-secret": {
          "ca-certificate": "",
          "certificate-name": "ssl-certificate-secret",
          "public-certificate": "-----BEGIN CERTIFICATE-----secret-----END CERTIFICATE-----\n"
        }
      },
      "compartment-id": "ocid1.tenancy.oc1..aaaaaaaaqhjag3s37bdq6b6pc4f6lyc6bvcxdisqjzsd6tpks3ablacdwnaa",
      "defined-tags": {},
      "display-name": "e9a507c8-2d77-11e9-8f6f-0a580aed26c3",
      "freeform-tags": {},
      "hostnames": {},
      "id": "ocid1.loadbalancer.oc1.iad.aaaaaaaaqq4ouav4yrccvxh2p26xzjpwc6mqyt3dgbbdio4hauylgp4oaaca",
      "ip-addresses": [
        {
          "ip-address": "129.213.14.248",
          "is-public": true
        }
      ],
      "is-private": false,
      "lifecycle-state": "ACTIVE",
      "listeners": {
        "TCP-443-ssl-certificate-secret": {
          "connection-configuration": {
            "idle-timeout": 300
          },
          "default-backend-set-name": "TCP-443",
          "hostname-names": null,
          "name": "TCP-443-ssl-certificate-secret",
          "path-route-set-name": null,
          "port": 443,
          "protocol": "TCP",
          "rule-set-names": [],
          "ssl-configuration": {
            "certificate-name": "ssl-certificate-secret",
            "verify-depth": 0,
            "verify-peer-certificate": false
          }
        },
        "TCP-80": {
          "connection-configuration": {
            "idle-timeout": 300
          },
          "default-backend-set-name": "TCP-80",
          "hostname-names": null,
          "name": "TCP-80",
          "path-route-set-name": null,
          "port": 80,
          "protocol": "TCP",
          "rule-set-names": [],
          "ssl-configuration": null
        }
      },
      "path-route-sets": {},
      "rule-sets": {},
      "shape-name": "100Mbps",
      "subnet-ids": [
        "ocid1.subnet.oc1.iad.aaaaaaaab4aso5kbx6f4ydo5o3xeg5ppdtf5zqczkd2p7jdr5jqqcuvyhznq",
        "ocid1.subnet.oc1.iad.aaaaaaaatxkp55537gnqievnvohxdvjfipqpbvyrv2jhb5z53ootb3crzpua"
      ],
      "time-created": "2019-02-10T21:07:46.290000+00:00"
    }
  ]
}
```





CERTNAMEなどが以下の指定となっている

![003.jpg](https://qiita-image-store.s3.amazonaws.com/0/104247/2d8ecea7-59fa-16e6-bcf3-3ffb94cace56.jpeg)



# SSL証明書の更新

Let's Encrypt は、90日で有効期限か切れるため、SSL証明書を定期的に入れ替える必要があります。
そこで、OCI側のLoadBalancer のSSL証明書の更新方法を確認します。
Documentに記載がないため、こういうパターンがあるなあと考えたものを載せています。

1. 新たな Secret を生成して切り替える (アクセス断10秒以上あり。つらい)
2. 直接更新パターン (アクセス断無し)
3. 新たなLBを作ってDNSを切り替えるパターン (アクセス断無し)



2番が一番良い方法な気がします。
3番はDNSの、いわゆる浸透問題が発生する可能性があるので、2番が良いのかなと思います。
3番を採用した場合、AレコードのTTLの期間と、LBの並列稼働時間を調整すると、イイカンジに対処は出来るので、まあ、どっちでもいいのかな・・・？



## 新たな Secret を生成して切り替える (アクセス断有り)

OKE 上であらたな Secret を生成して、それに切り替える方法です。
現在の挙動では、アクセス断が10秒以上発生するため、採用しにくい方法となりますが、Kubernetesの操作だけで完結するため、わかりやすいメリットはあります。

Let's Encrypt で新しいSSL証明書を取得したのちに、以下の作業が必要になります。

OKE 上で 新しいSSL証明書を使用して、新たにSecret を作成します。
今回はわかりやすさのために `ssl-certificate-secret-next` という名前でSecretを作成します。

```
kubectl create secret tls ssl-certificate-secret-next --key privkey.pem --cert cert.pem
```



マニフェストを新たに作成します。ポイントは、Service LoadBalancer の annotation で指定する `service.beta.kubernetes.io/oci-load-balancer-tls-secret` を新たな Secret `ssl-certificate-secret-next` に指定しているところです。

```
string trim '
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
  name: nginx-service
  annotations:
    service.beta.kubernetes.io/oci-load-balancer-ssl-ports: "443"
    service.beta.kubernetes.io/oci-load-balancer-tls-secret: ssl-certificate-secret-next
spec:
  selector:
    app: nginx
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port: 443
    targetPort: 80
' > ~/workdir/nginx-https.yaml
kubectl apply -f ~/workdir/nginx-https.yaml
```



この作業により、OCIのLoadBalancer上で、新たなSecretを新たな Certificates として登録されます。
しかし、この時に、OCI上のLoadBalancerの Listener(TCP443) が削除されて、新たな Certificates を持った Listener が再作成される挙動となり、
LoadBalancerの通信が、10秒ほど遮断されます。これはなかなか厳しい挙動です。

GitHub上の Issue として認識はされているようですが、解決はされていない様子です。
https://github.com/oracle/oci-cloud-controller-manager/issues/14



## OCI LB を直接編集するパターン (アクセス断無し)

OCI LB を直接編集して、SSL証明書を切り替えるパターンです。
まず、編集したい Service LoadBalancer の EXTERNAL-IP を確認して、このIPアドレスを保有している OCI LB を特定します。

```
> kubectl get svc -o wide                                                                                                           815ms
NAME            TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                      AGE   SELECTOR
kubernetes      ClusterIP      10.96.0.1      <none>           443/TCP                      17h   <none>
nginx-service   LoadBalancer   10.96.242.48   129.213.14.248   80:30797/TCP,443:31892/TCP   36m   app=nginx
```



OCI と jq コマンドで EXTERNAL-IP と一致しているものを検索して、OCID と 名前 と 作成時間を取得します

```
> oci lb load-balancer list | jq -r '.data | map(select(."ip-addresses"[0]."ip-address" == "129.213.14.248"))[] | ."id", ."display-name", ."time-created" , ."listeners"'
ocid1.loadbalancer.oc1.iad.aaaaaaaaqq4ouav4yrccvxh2p26xzjpwc6mqyt3dgbbdio4hauylgp4oaaca
e9a507c8-2d77-11e9-8f6f-0a580aed26c3
2019-02-10T21:07:46.290000+00:00
```



LB の OCID を変数に入れます

```
set lb_ocid (oci lb load-balancer list | jq -r '.data | map(select(."ip-addresses"[0]."ip-address" == "129.213.14.248"))[].id')
```



LBに紐づく TCP443 の Listenerに関する値を確認します。この後の手順で、Listenerを更新して新たなSSL証明を指定する必要があり、そのために必要な情報を確認します。

```
> oci lb load-balancer list | jq -r '.data | map(select(."ip-addresses"[0]."ip-address" == "129.213.14.248"))[]."listeners"'
{
  "TCP-443-ssl-certificate-secret-next2": {
    "connection-configuration": {
      "idle-timeout": 300
    },
    "default-backend-set-name": "TCP-443", <==================== 必要な値
    "hostname-names": null,
    "name": "TCP-443-ssl-certificate-secret-next2", <=========== 必要な値
    "path-route-set-name": null,
    "port": 443, <============================================== 必要な値
    "protocol": "TCP", <======================================== 必要な値
    "rule-set-names": [],
    "ssl-configuration": {
      "certificate-name": "ssl-certificate-secret-next2",
      "verify-depth": 0,
      "verify-peer-certificate": false
    }
  },
  "TCP-80": {
    "connection-configuration": {
      "idle-timeout": 300
    },
    "default-backend-set-name": "TCP-80",
    "hostname-names": null,
    "name": "TCP-80",
    "path-route-set-name": null,
    "port": 80,
    "protocol": "TCP",
    "rule-set-names": [],
    "ssl-configuration": null
  }
}
```



必要な値をそれぞれ変数に入れます。jqのフィルター条件をふんわり書くと以下の条件指定を行っている

- OCI LB で、Public IP "129.213.14.248" を持っているものを指定
- 該当のLBに存在している Listener の中から、"default-backend-set-name" が "TCP-443" の値を持っているものを指定

```
set default_backend_set_name (echo "TCP-443")

set listener_name (oci lb load-balancer list | jq -r '.data | map(select(."ip-addresses"[0]."ip-address" == "129.213.14.248"))[].listeners[] | select(."default-backend-set-name" == "TCP-443").name')

set listener_port (oci lb load-balancer list | jq -r '.data | map(select(."ip-addresses"[0]."ip-address" == "129.213.14.248"))[].listeners[] | select(."default-backend-set-name" == "TCP-443").port')

set listener_protocol (oci lb load-balancer list | jq -r '.data | map(select(."ip-addresses"[0]."ip-address" == "129.213.14.248"))[].listeners[] | select(."default-backend-set-name" == "TCP-443").protocol')
```



LB に 新しいSSL証明書を指定して作成します。名前は `manual_cert` とします

```
oci lb certificate create --certificate-name "manual_cert" --load-balancer-id $lb_ocid --private-key-file /home/sugi/ssl/privkey.pem --public-certificate-file /home/sugi/ssl/cert.pem
```



既存のListenerのSSL証明書を、新たに作成した `manual_cert` へ切り替えます

```
oci lb listener update --load-balancer-id $lb_ocid --default-backend-set-name $default_backend_set_name --port $listener_port --protocol $listener_protocol --listener-name $listener_name --ssl-certificate-name "manual_cert" --force
```



念のため、OKE 上の Secret も同一名称で新たなSSL証明書を使って上書きします。kubectl create で上書きはできないため、 dry-run と -o yaml でマニフェストファイルを生成したのちに apply する方法で上書きします。

```
kubectl create secret tls ssl-certificate-secret --key tls.key --cert tls.crt --dry-run -o yaml | kubectl apply -f -
```



## 新たなLBを作成して、DNSを切り替えるパターン (アクセス断無し)

新たな Service LoadBalancer を作成して、OCI DNS で宛先を切り替えます

まずは、新たな Secret を作成します

```
kubectl create secret tls ssl-certificate-secret-next --key privkey.pem --cert cert.pem
```



新たなマニフェストファイルを作ります

```
string trim '
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
  name: nginx-service-next
  annotations:
    service.beta.kubernetes.io/oci-load-balancer-ssl-ports: "443"
    service.beta.kubernetes.io/oci-load-balancer-tls-secret: ssl-certificate-secret-next
spec:
  selector:
    app: nginx
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port: 443
    targetPort: 80
' > ~/workdir/nginx-https-nextlb.yaml
kubectl apply -f ~/workdir/nginx-https-nextlb.yaml
```



新たな Service LoadBalancer が作成されます

```
> kubectl get svc -o wide                                                                                                              7s
NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                      AGE   SELECTOR
kubernetes           ClusterIP      10.96.0.1      <none>           443/TCP                      18h   <none>
nginx-service        LoadBalancer   10.96.242.48   129.213.14.248   80:30797/TCP,443:31892/TCP   1h    app=nginx
nginx-service-next   LoadBalancer   10.96.75.66    <pending>        80:31323/TCP,443:31455/TCP   7s    app=nginx
```



EXTERNAL-IP が作成されます

```
> kubectl get svc -o wide                                                                                                          1696ms
NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)                      AGE   SELECTOR
kubernetes           ClusterIP      10.96.0.1      <none>            443/TCP                      18h   <none>
nginx-service        LoadBalancer   10.96.242.48   129.213.14.248    80:30797/TCP,443:31892/TCP   1h    app=nginx
nginx-service-next   LoadBalancer   10.96.75.66    129.213.179.197   80:31323/TCP,443:31455/TCP   44s   app=nginx
```



DNSを切り替えて、新たな External IP に接続するようにします

作成した EXTERNAL-IP を `test.sugi.tokyo` に紐づくかたちで Aレコードを OCI DNS に作成します。JSONファイルを準備します

```
string trim '
[
  {
    "domain": "test.sugi.tokyo",
    "is-protected": false,
    "rdata": "129.213.179.197",
    "rrset-version": "2",
    "rtype": "A",
    "ttl": 30
  }
]
' > ~/workdir/test.sugi.tokyo.json
```



A レコードを作成

```
oci dns record domain update --zone-name-or-id "sugi.tokyo" --domain "test.sugi.tokyo" --items file://~/workdir/test.sugi.tokyo.json --force
```



新たな Aレコードで名前解決が出来ています

```
> dig +dnssec @8.8.8.8 test.sugi.tokyo. A                                                                                           801ms
; <<>> DiG 9.11.3-1ubuntu1.1-Ubuntu <<>> +dnssec @8.8.8.8 test.sugi.tokyo. A
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45973
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;test.sugi.tokyo.               IN      A

;; ANSWER SECTION:
test.sugi.tokyo.        29      IN      A       129.213.179.197

;; Query time: 158 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Mon Feb 11 08:04:20 DST 2019
;; MSG SIZE  rcvd: 60
```



curl でアクセス可能です

```
> curl --insecure https://test.sugi.tokyo/                                                                                          249ms<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```



openssl コマンドでも正常に確認できています

```
> openssl s_client -connect test.sugi.tokyo:443                                                                                     835msCONNECTED(00000003)
depth=0 CN = test.sugi.tokyo
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = test.sugi.tokyo
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
 0 s:/CN=test.sugi.tokyo
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
```













# 備忘録

新規作成時につくった Secret をそのまま上書きしても、OCI LB 上の証明書は更新されない。あらたな名前でSecret を作成する必要がある。
具体例を挙げると、以下のように上書きしても意味がない

```
kubectl create secret tls ssl-certificate-secret --key tls.key --cert tls.crt --dry-run -o yaml | kubectl apply -f -
```





# 参考URL

Oracle Document
https://docs.cloud.oracle.com/iaas/Content/ContEng/Tasks/contengcreatingloadbalancer.htm#creatinglbhttps

OKEで指定できるアノテーション
https://github.com/oracle/oci-cloud-controller-manager/blob/master/docs/load-balancer-annotations.md

SSL証明書の形式変換
https://glorificatio.org/archives/2914