Cloudflare Tunnel を使って Kubernetes の Service を外部からアクセスできるようにする

背景

AWSKubernetes クラスタを運用する中でサービスを外部に公開したい時に思いつく方法が ALB など AWS から提供される load balancer を使用することだと思います。今回は諸事情により ALB を使えないため代わりの方法を探していました。

やりたいこと

HTTPS なエンドポイントを公開する Pod を外から ALB などを使わずアクセスできるようにしたい。

やったこと

今回は Cloudflare Tunnel を使いました。Cloudflare Tunnel は Cloudflare が提供するゼロトラストサービスの1つです。オリジンの public IP を公開しないようにすることで、オリジンへの意図しない直接リクエストや内部のアプリケーションなどを外に公開することなく特定のアカウントからアクセスできるようにすることができます。

仕組みとしては cloudflared というデーモンを同じサーバーやネットワーク内で起動しておくことで、Cloudflare Edge に到達したリクエストがデーモンへと渡り、デーモンが対象のサービスへとリクエストをします。 詳細な動きは公式ドキュメントを見てください => https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/

cloudflared は docker image でも提供されているのでクラスタ内で動かせば EC2 自体は完全に外からリクエストを受け付けない設定をしていても、対象のサービスを外部からアクセスできるように設定できそうでした。

実際の設定

Kubernets で Tunnel を使用する時のドキュメントがあるので、基本はこれに従っていきます。 developers.cloudflare.com

Tunnel の作成

Tunnel の作成には web UI から作成する方法CLI を使って作成する方法 の2通りあります。CLI 側は少し手順が複雑そうだったので今回は web UI からポチポチ設定することにしました。

web UI からの作成は公式の 1. Create a tunnel の通り行います。作成が終わるとその tunnel に紐付いた token が生成されます。これはクラスタにデプロイする cloudflared で使用するのでメモしておきます。

cloudflared をクラスタにデプロイする

公式ドキュメント で紹介されているように Deployment を作成します。その先程生成した token も指定するのですが、ドキュメントでは引数として渡していましたが、環境変数 TUNNEL_TOKEN でも渡せるので今回は Secret リソースに token の値をいれて環境変数経由で追加しました。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: cloudflared
  name: cloudflared
spec:
  replicas: 1
  selector:
    matchLabels:
      pod: cloudflared
  template:
    metadata:
      labels:
        pod: cloudflared
    spec:
      containers:
      - command:
        - cloudflared
        - tunnel
        - run
        image: cloudflare/cloudflared:latest
        name: cloudflared
        env:
          - name: TUNNEL_TOKEN
            valueFrom:
              secretKeyRef:
                name: cloudflared
                key: CLOUDFLARE_TUNNEL_TOKEN

今回は最低限の動作を確認するために Deployment だけ作成しましたが、実際に運用する時は HPA や PDB を作成するほうが良いと思います。Tunnel の availabiliy に関しては 公式の Tunnel availability and failover を見てください。

無事 cloudflared の Pod が起動すると、web UI 上で connector として表示されると思います。

外から接続できる設定を行う

外部から接続する際には DNS record を使う方法と Cloudflare の Load Balancer を使う2つの方法があります。今回は DNS record を使う方法を選択しました。

Tunnel を作成すると [Tunnel UUID].cfargotunnel.com というドメインが自動生成されるので自分が管理しているドメインで CNAME を作成します。ただしどの DNS provider でも良いわけではなく Cloudflare DNS を使用する方法があります。つまり Route53 とかで管理していた時は nameserver を Cloudflare DNS に移行する必要があります。

ここまでくればあとは外からアクセスしたいドメインと Kubernets 内のサービスを紐付ける設定をするだけです。今回も web UI から行いました。下記の画像からもわかるように public hostname に外からアクセスしたドメイン(+パス)の情報をいれて Service に Kubernets のサービスを指定します。作成すると自動で DNS record も作成されます。

これで設定は終わりなのですが、今回公開する HTTPS なサービスは SNI を受け取ることが期待しているため先程の web UI 上の Additional application settings -> TLS -> Origin Server Name を設定しました。(間違ってるかも)

最後に

今回は Cloudflare Tunnel を使ってクラスタ内でしか公開していなかったサービスを外部に公開しました。手順もそこまで複雑ではなく、スムーズに設定ができたので体験が良かったです!!