Cloudflare Tunnelでk8sのServiceを公開する

cloudflare
kubernetes
terraform

2024-09-29

#はじめに

今回は、Cloudflare Tunnelを使ってk8sのServiceを公開する方法について紹介します。

#目次

#なぜCloudflare Tunnelを使うのか

まず、Webアプリを作って、サクッとデモとして公開したいと思ったとき、どうしますか?
当社では主にGoogle Cloudを使っていますが、Google Cloudの場合、アプリを公開する方法としていくつかあります。

  • Cloud Run
  • Google App Engine
  • GKE + Cloud Load Balancer

Cloud Run, Google App EngineはIAPを設定すれば、特定の人だけに公開できますが、サーバレスであるため、動作させるアプリによっては不向きな場合があります。
GKE + Cloud Load Balancerは、コンテナであれば動かすのに困りませんが、Cloud Load Balancerがこのような用途ではオーバースペックであると考えています。

当社では主にコンテナを動かすのにKubernetesを使っており、ドメインをCloudflareで管理しています。
Cloud Load Balancerを使わずに、KubernetesにデプロイしたアプリをCloudflareで公開する方法を探していたところ、Cloudflare Tunnelを見つけました。

#Cloudflare Tunnelとは

Cloudflare Tunnel provides you with a secure way to connect your resources to Cloudflare without a publicly routable IP address. With Tunnel, you do not send traffic to an external IP — instead, a lightweight daemon in your infrastructure (cloudflared) creates outbound-only connections to Cloudflare’s global network. Cloudflare Tunnel can connect HTTP web servers, SSH servers, remote desktops, and other protocols safely to Cloudflare. This way, your origins can serve traffic through Cloudflare without being vulnerable to attacks that bypass Cloudflare.

出典:Cloudflare Tunnel

Cloudflare Tunnelは、パブリックにルーティング可能なIPアドレスなしでCloudflareにリソースを接続する安全な方法を提供します。Tunnelでは、外部IPにトラフィックを送信するのではなく、お客様のインフラ(cloudflared)内の軽量デーモンがCloudflareのグローバルネットワークへのアウトバウンド専用接続を作成します。Cloudflare TunnelはHTTPウェブサーバー、SSHサーバー、リモートデスクトップ、その他のプロトコルを安全にCloudflareに接続できます。こうすることで、オリジンはCloudflareをバイパスする攻撃に対して脆弱になることなく、Cloudflareを通してトラフィックを提供することができます。

説明は上記の通りです。

Cloudflare Tunnelを知る前は、ngrokが似たようなサービスという認識がありました。
どちらも閉域やパブリックIPを持たないマシンでもインターネットに公開できるサービスです。

#Cloudflare Tunnelを使ってみた感想

実際に利用してみた感想として、以下の特徴があります。

  1. サブドメインを設定して、トンネルを作成するとすぐにつながる
  2. Zero Trust Access Applicationと連携して、アクセス制御ができる
  3. 閉域やパブリックIPを持たないサーバーにもアクセスできるようになる

1の特徴は、通常、サブドメインにDNSの設定をしてオリジンのIPアドレスなどをDNSに設定しますが、そうするとDNSの設定が反映されるまで時間がかかります。Cloudflare Tunnelを使うと、サブドメインを設定して、トンネルを作成するとすぐにつながるので、デモやテスト環境を作るときに便利です。

2の特徴は、Zero Trust Access Applicationと連携して、アクセス制御ができるということです。デモで特定の人にだけ公開したい場合、アクセス制御ができるので、セキュリティ面でも安心です。

3の特徴は、閉域やパブリックIPを持たないサーバーにもアクセスできるので、例えば自宅のPCにアクセスするときにも使えますし、クラウド上のプライベートIPしか持っていないサーバーにもアクセスできるようになります。

あと、Cloudflare Tunnelは無料でも使えるので、コストを気にせずに使えるのも魅力です。ただし、無料プランだと、SLAの保証がなかったりするので、それぞれの用途に合わせてプランを選ぶと良いでしょう。

#実際に構築してみる

terraformでCloudflareのリソースを作成し、k3dでk8sクラスタを作成します。そして、k8sのServiceを作成して、Cloudflare Tunnelで公開します。

#Cloudflareのリソースを作成する

事前にCloudflareのAPI Tokenを取得しておきます。
権限は以下の通りです。

All accounts - Cloudflare Tunnel:Edit, Zero Trust:Edit, Access: Apps and Policies:Edit
All zones - Access: Apps and Policies:Edit, DNS:Edit

リソース作成のポイントは以下の通りです。

  • zero trust access applicationを作成する

    policyの設定をして、アクセス制御を行います。
    今回は、かんたんに設定できるように、emailでアクセス制御を行います。

  • cloudflare tunnelを作成する

    k8sのServiceにルーティングするので、事前にサービス名を設定しておきます。今回設定するのは、whoamiを使います。

    http://whoami.default.svc.cluster.local
    
  • 対象ドメインのCNAMEを設定する

    パブリックドメインをargotunnelのドメインを指すようCNAMEを設定します。

    この設定は、UIからだと自動的に設定してくれるのですが、terraformで設定する場合は、このように手動で設定する必要があります。

APIトークンやアカウントIDは、terraformのtfvarsで設定します。

export TF_VAR_api_token="your api token"
export TF_VAR_account_id="your account id"

以下が、terraformのコードです。localsの部分は、適宜変更してください。

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4"
    }
    random = {
      source = "hashicorp/random"
    }
  }
}

provider "cloudflare" {
  api_token = var.api_token
}
provider "random" {
}

variable "account_id" {
}
variable "api_token" {
}

locals {
  demo_zone_id  = "your zone id"
  public_domain = "demo.example.com"
  team_name     = "your team name"
  allowed_email = [
    "[email protected]"
  ]
}
resource "random_password" "demo_tunnel_secret" {
  length = 32
}

resource "cloudflare_zero_trust_access_application" "demo_app" {
  account_id                 = var.account_id
  name                       = "demo app"
  http_only_cookie_attribute = true
  domain                     = local.public_domain

  policies = [
    cloudflare_zero_trust_access_policy.demo_policy.id
  ]
}

resource "cloudflare_zero_trust_access_policy" "demo_policy" {
  account_id = var.account_id
  name       = "demo policy"
  decision   = "allow"

  include {
    email = local.allowed_email
  }
}

resource "cloudflare_zero_trust_tunnel_cloudflared" "demo_tunnel" {
  account_id = var.account_id
  name       = "demo tunnel"
  secret     = base64encode(random_password.demo_tunnel_secret.result)
}

resource "cloudflare_zero_trust_tunnel_cloudflared_config" "demo_config" {
  account_id = var.account_id
  tunnel_id  = cloudflare_zero_trust_tunnel_cloudflared.demo_tunnel.id
  config {
    ingress_rule {
      hostname = local.public_domain
      service  = "http://whoami.default.svc.cluster.local"
      origin_request {
        connect_timeout = "2m0s"
        access {
          required  = true
          team_name = local.team_name
          aud_tag   = [cloudflare_zero_trust_access_application.demo_app.aud]
        }
      }
    }
    ingress_rule {
      service = "http_status:404"
    }
  }
}

resource "cloudflare_record" "demo" {
  zone_id = local.demo_zone_id
  name    = local.public_domain
  content = "${cloudflare_zero_trust_tunnel_cloudflared.demo_tunnel.id}.cfargotunnel.com"
  type    = "CNAME"
  proxied = true
}

このterraformのコードは、最低限の説明のため記述していますが、実用的にはモジュールに分けて再利用可能にしたり、環境ごとに設定を変えられるようにするなどの工夫が必要です。

#k3dを使ってk8sクラスタを作成する

以前の記事でhelmfileを使ってk8sクラスタを作成する方法を紹介したときのレポジトリを使います。

git clone https://github.com/cloudandbuild/helmfile-example.git
cd helmfile-example
git checkout add-cloudflare-tunnel

以前の記事で説明した手順を実施して、k8sクラスタを作成します。

#tunnel tokenを設定する

tunnel_tokenには、terraformで作成したtunnelのsecretを設定します。

  • releases/cloudflare-tunnel-remote/config/default-secrets.yaml
secrets:
  tunnel_token: "your tunnel token"

helmで暗号化します。

helm secrets encrypt -i releases/cloudflare-tunnel-remote/config/default-secrets.yaml

#helmfileでデプロイする

helmfile sync

#動作確認

指定したドメインにアクセスすると、最初は、Cloudflare Accessの認証画面が表示されます。この例では、Email認証を使っているので、指定したEmailアドレスにメールが届きます。
cloudflare-tunnel

認証したあとに再度アクセスすると、whoamiのページが表示されます。DNSの伝搬する場合、数時間要することがありますが、Cloudflare tunnelは、本当にすぐ繋がります。
whoami

#参考リンク

#まとめ

今回は、Cloudflare Tunnelを使ってk8sのServiceを公開する方法について紹介しました。
おうちでKuberenetesを使っている方や、外からのWebhookをローカルのPCで受け取りたいなど、色んな用途で使えると思います。
Cloudflare Tunnelは無料でも使えるので、気軽に試してみてください。