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 というサービスの一種がコレにあたる。
- 参考 : ロードバランサ再入門
- 参考 : ロードバランサー(L7・・・・) - Notitle
- 参考 : L7ロードバランサとL4ロードバランサ - Qiita
- 参考 : ロードバランサー(L4)とロードバランサー(L7)の違いを教えてください | ニフクラ
- 参考 : KubernetesにおけるLoadBalancerとIngressの違いについて - Between Real and Ideal
L7LB の方が URL や HTTP ヘッダなんかを見て振り分けできるので、機能としては高機能だが、そのためには暗号化された HTTPS 通信を復号してリクエスト情報を確認できるようにしないといけないし、処理としては高負荷になる。
終端
L7LB の説明の最後にサラッと書いたが、「暗号化された HTTPS 通信を復号」することが終端の意味。ロードバランサが秘密鍵を持っていて、暗号通信を復号すれば、その裏にいる Web サーバは暗号化されていない HTTP として通信できるワケだ。コレを「SSL ターミネーション (終端)」という。SSL 通信がそこで終わっているから終端なのである。
- 参考 : 常時HTTPS化の落とし穴<解決編>|テクノロジー | オピニオン|トライベック・ストラテジー
- 参考 : コンテナでHTTPSを利用するには (自己署名証明書、自営CA、 公的CAなどからの証明書の取得) - Qiita
そういや SSL と TLS って何
SSL と TLS は、機能的には同じ暗号通信の方式。
- SSL : Secure Sockets Layer
- TLS : Transport Layer Security
SSL 3.0 までバージョンアップしたあと、TLS 1.0 という名称に変更されたので、現在は TLS という技術を使用しているのだが、「SSL」と呼ばれている時代が長かったので、「SSL/TLS」と併記されているだけ。
SSL を進めたのは Netscape だったが、IETF という第三者機関が引き継いで TLS へ移行したらしい。
- 参考 : SSL/TLSの解説と選び方まとめ
- 参考 : SSLとTLSの違いとは | さくらのSSL
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ファイルを用意しよう。
- サーバ証明書 :
server.crt.pem
- 中間 CA 証明書と結合した「チェーン証明書」でも良い
- 秘密鍵 :
server.key.pem
- パスフレーズは解除してあること
ファイルが用意できたら、以下のようにして登録する。
$ 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秒程度のアクセス断が発生してしまうらしい。それが許容できればこのやり方が楽かと。
- 参考 : OKE (Oracle Container Engine for Kubernetes) で、Let's Encrypt を使用して無料SSL LoadBalancer公開 - Qiita
不要になった Secret は、以下のように削除できる。
$ kubectl delete secret ssl-certificate-secret
おわり。