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.am192.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接続が可能になります。
これにより、複数のサービスを同じポートで公開できるため、リソースの効率的な利用が可能になります。

Found a mistake? Update the entry.
Share this article: