Contourの隠れた機能であるTLS Session Proxyingを使ってみます。
Googleで検索しても使用例が出てこなかったので、メモを残しておきます。
Contourは基本的にはHTTP/HTTPSのリバースプロキシですが、TCP Proxyingもサポートしています。TCP Proxyingは、RedisのようなTCPベースのサービスに対しても使えます。
普通にTCPのサービスをtype: LoadBalancerで公開してもいいのですが、TLS Session Proxyingを使うと次の利点があります。
- TLSのSNIを使って複数のサービスを同じEnvoyポートで公開できる (追加でExternal IPを割り当てる必要がない)
- EnvoyでTLSの終端を行うので、RedisのようなサービスにTLSを設定する必要がない
- 必要であれば、Upstream TLSを有効にして、EnvoyからRedisへの通信もTLS化できるし、EnvoyをTLS終端にせずTLS Passthroughもできる
RedisをKubernetesにデプロイ
まずは、TCPサービスの例としてRedisをBitnamiのHelmでデプロイします。今回は、永続化を無効にして、認証も無効にしています。
helm upgrade --install redis \
oci://registry-1.docker.io/bitnamicharts/redis \
-n redis \
--create-namespace --wait \
--set master.persistence.enabled=false \
--set replica.persistence.enabled=false \
--set auth.enabled=false
PodとServiceが作成されていることを確認します。
$ kubectl get pod,svc -n redis -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/redis-master-0 1/1 Running 0 3m28s 10.1.173.163 cherry <none> <none>
pod/redis-replicas-0 1/1 Running 0 3m28s 10.1.173.164 cherry <none> <none>
pod/redis-replicas-1 1/1 Running 0 3m 10.1.42.164 banana <none> <none>
pod/redis-replicas-2 1/1 Running 0 2m33s 10.1.247.56 apricot <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/redis-headless ClusterIP None <none> 6379/TCP 3m28s app.kubernetes.io/instance=redis,app.kubernetes.io/name=redis
service/redis-master ClusterIP 10.152.183.167 <none> 6379/TCP 3m28s app.kubernetes.io/component=master,app.kubernetes.io/instance=redis,app.kubernetes.io/name=redis
service/redis-replicas ClusterIP 10.152.183.48 <none> 6379/TCP 3m28s app.kubernetes.io/component=replica,app.kubernetes.io/instance=redis,app.kubernetes.io/name=redis
次に、ContourのHTTPProxyを使ってRedisのTCP Proxyingを設定します。
Note
この例ではprojectcontoure namespaceに作成されたCertificatedefault-tls (Let's Encryptによるワイルドカード証明書)がTLSCertificateDelegationで公開されている前提です。環境に応じて適切な証明書設定を行ってください。
cat <<EOF > /tmp/redis-sni.yaml
---
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: redis
namespace: redis
spec:
virtualhost:
fqdn: redis.lan.ik.am
tls:
secretName: projectcontour/default-tls
tcpproxy:
services:
- name: redis-master
port: 6379
---
EOF
kubectl apply -f /tmp/redis-sni.yaml
HTTPProxyが作成されたことを確認します。STATUSがvalidになっていれば成功です。
$ kubectl get httpproxy -n redis
NAME FQDN TLS SECRET STATUS STATUS DESCRIPTION
redis redis.lan.ik.am projectcontour/default-tls valid Valid HTTPProxy
EnvoyのExternal IPを確認します。ここでは、projectcontour namespaceにデプロイされているContourのLoadBalancer Serviceを使います。
$ kubectl get svc -n projectcontour
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
contour ClusterIP 10.152.183.197 <none> 8001/TCP 24h
contour-envoy LoadBalancer 10.152.183.23 192.168.11.240 80:31496/TCP,443:31967/TCP 24h
redis.lan.ik.amは192.168.11.240に解決されるようにDNS設定されています。
redis-cliでRedisに接続
次のコマンドで、Redisにアクセスを試みます。
redis-cli -h redis.lan.ik.am -p 443 --tls
次のエラーが出力されます。
Could not connect to Redis at redis.lan.ik.am:443: SSL_connect failed: Connection reset by peer
redis-cliはデフォルトではTLSのSNIをサポートしていないため、接続できません。そこで、--sniオプションを使ってSNIを指定します。
redis-cli -h redis.lan.ik.am -p 443 --tls --sni redis.lan.ik.am
今度は接続でき、Key-Valueの読み書きができます。
redis.lan.ik.am:443> set foo 100
OK
redis.lan.ik.am:443> get foo
"100"
次のように-hオプションにEnvoyのExternal IPを指定しても同じように接続できます。
redis-cli -h 192.168.11.240 -p 443 --tls --sni redis.lan.ik.am
Spring Boot (Spring Data Redis)アプリケーションからRedisに接続
次にSpring BootアプリケーションからRedisに接続してみます。ここでは、Spring Data Redisを使った簡単なデモアプリケーションを使用します。
次のコマンドでアプリケーションのソースコードを取得し、ビルドします。
git clone https://github.com/making/demo-redis
cd demo-redis
./mvnw clean package -DskipTests
次のコマンドでアプリケーションを起動します。Redisのホスト名とポートを指定して、TLSを有効にします。Spring Data Redis(というよりLettuce?)にredis-cliの--sniオプションに相当するプロパティはなく、
TLSを有効にすれば、 接続先のホスト名でSNIを自動的に設定するため、特にSNIの指定は必要ありません。逆に接続先にIPアドレスを指定すると、SNIが設定されずに接続できません。
java -jar target/demo-redis-0.0.1-SNAPSHOT.jar --spring.data.redis.host=redis.lan.ik.am --spring.data.redis.port=443 --spring.data.redis.ssl.enabled=true
次のコマンドで、アプリケーションが機能していることを確認します。
$ curl "http://localhost:8080?key=foo"
100
$ curl "http://localhost:8080?key=foo" -H "Content-Type: text/plain" -d Hello
$ curl "http://localhost:8080?key=foo"
Hello
ContourのTLS Session Proxyingを使うことで、RedisのようなTCPサービスに対してもSNIを利用したTLS接続が可能になります。
これにより、複数のサービスを同じポートで公開できるため、リソースの効率的な利用が可能になります。