Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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.pemprivkey.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

002.jpg

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

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

sugimount
CloudNativeな色々をやっています / 投稿している内容は個人的な見解なので、所属組織とは関係ありません https://twitter.com/sugimount
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした