Kubernetes クラスタの Load Balancer に SSL を適用する

Let's Encrypt などでサーバ証明書を発行した後、それを Kubernetes クラスタに適用する方法。

今回は、各 Pod (Docker コンテナ) 内で SSL の終端をさせるのではなく、Load Balancer で終端させて、Kubernetes クラスタ内は HTTP で通信できる方法を取る。こうすれば、Pod 内に配備する Web サーバに関しては、ローカルの HTTP 環境で動かした時と同じままにできるからだ。

Kubernetes 特有の Secret とかいう概念が出てきたりして躓いたのでまとめる。

目次

用語整理

僕のようにインフラやネットワーク周りに弱い人間は「ロードバランサ」とか「終端」とか言われた時点で頭が真っ白になってしまう。まずは用語の整理から。

ロードバランサ

まずはロードバランサ (Load Balancer・LB) から。

基本的には、クライアントとサーバの間に挟まって、通信を振り分けるモノ。1つのリクエストを複数のサーバに振り分けてやれば、負荷分散できし、1つのサーバが落ちていても冗長構成で稼動する他のサーバに処理を頼めるよね、という文脈でロードバランサが登場することが多い。

ロードバランサには種類があって、「OSI 参照モデル」というネットワークの実装モデルで示される7層のレイヤーに合わせて、「L4 ロードバランサ」と「L7 ロードバランサ」というモノがある。

L4LB (Layer 4・OSI 参照モデルにおける「トランスポート層」) は、IP アドレスとポート番号を条件に振り分けする部分。Kubernetes の Load Balancer サービスはどちらかというとコッチ。

対して L7LB (Layer 7・「アプリケーション層」) は、URL や HTTP ヘッダを見て振り分けられる。Kubernetes でいうと Ingress というサービスの一種がコレにあたる。

L7LB の方が URL や HTTP ヘッダなんかを見て振り分けできるので、機能としては高機能だが、そのためには暗号化された HTTPS 通信を復号してリクエスト情報を確認できるようにしないといけないし、処理としては高負荷になる。

終端

L7LB の説明の最後にサラッと書いたが、「暗号化された HTTPS 通信を復号」することが終端の意味。ロードバランサが秘密鍵を持っていて、暗号通信を復号すれば、その裏にいる Web サーバは暗号化されていない HTTP として通信できるワケだ。コレを「SSL ターミネーション (終端)」という。SSL 通信がそこで終わっているから終端なのである。

そういや SSL と TLS って何

SSL と TLS は、機能的には同じ暗号通信の方式。

SSL 3.0 までバージョンアップしたあと、TLS 1.0 という名称に変更されたので、現在は TLS という技術を使用しているのだが、「SSL」と呼ばれている時代が長かったので、「SSL/TLS」と併記されているだけ。

SSL を進めたのは Netscape だったが、IETF という第三者機関が引き継いで TLS へ移行したらしい。

Load Balancer サービスを作る

Kubernetes で Load Balancer サービスを設けるには、以下のような service.yaml を記述する。

apiVersion: v1
kind: Service
metadata:
  name: my-service
  labels:
    app: my-app
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
  - name: http
    port: 80
    targetPort: 8080

コレを $ kubectl apply -f service.yaml で適用すればロードバランサが生成され、クラウドサービスなどならパブリック IP が割り当てられるだろう。

コイツにサーバ証明書と秘密鍵を持たせることで、HTTPS 通信を終端してくれて、Pod に届く通信は HTTP で行えるようになるワケだ。

SSL 認証 Secret を登録する

サーバ証明書と秘密鍵は秘密情報なので、Kubernetes の Secret 機能を使う。Kubernetes には秘密情報を保持しておける領域があり、SSL 通信のための秘密情報を保持するための Type も用意されている。

手元に以下の2ファイルを用意しよう。

ファイルが用意できたら、以下のようにして登録する。

$ kubectl create secret tls ssl-certificate-secret --key server.key.pem --cert server.crt.pem

登録できたら、以下のように確認できる。

$ kubectl get secret
NAME                                         TYPE                                  DATA   AGE
default-token-0fwas                          kubernetes.io/service-account-token   3      23d
my-private-docker-registry-secret            kubernetes.io/dockerconfigjson        1      23d
my-environment-variables-secret              Opaque                                17     2d
ssl-certificate-secret                       kubernetes.io/tls                     2      25s  # ← コレ

ssl-certificate-secret という名前のシークレットが用意できた。

Load Balancer サービスに適用する

シークレットが用意できたので、コレを Load Balancer サービスに利用させるよう、service.yaml を書き換える。

apiVersion: v1
kind: Service
metadata:
  name: my-service
  labels:
    app: my-app
  # 以下3行を追加する
  annotations:
    service.beta.kubernetes.io/oci-load-balancer-ssl-ports: "443"
    service.beta.kubernetes.io/oci-load-balancer-tls-secret: ssl-certificate-secret
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
  - name: http
    port: 80
    targetPort: 8080
  # 以下追加 : HTTPS 通信用に 443 ポートを設定する
  - name: https
    port: 443
    targetPort: 8080

コレで $ kubectl apply -f service.yaml を叩いて設定を反映させてやれば完了だ。Pod の再生成は特に要らない。

サーバ証明書を更新する場合は?

サーバ証明書を更新する場合は、別名で Secret を作り、service.yaml を更新することで反映させるのが最も楽。ただし、一時的に10秒程度のアクセス断が発生してしまうらしい。それが許容できればこのやり方が楽かと。

不要になった Secret は、以下のように削除できる。

$ kubectl delete secret ssl-certificate-secret

おわり。