趣味GKEのIngressを無料で済ませる

GKEでサービスを外部公開する際には、 GKE Ingress とそのバックエンド GCP Cloud Load Balancing を使用するのがスタンダードです。が、これには費用 ($18/月~) がかかります。

これをCloudflare DNS + Contourで置き換えて、無料で済ませる方法を説明します。ノードは全台プリエンプティブインスタンスで構いません。

この記事はDoxseyさんによる Kubernetes: The Surprisingly Affordable Platform for Personal Projects を発展させた内容になります。 元記事と同様、紹介する構成は趣味利用にとどめてください。

GKEクラスタ作成

まずGKEクラスタを作成してください。3台以上で構築し、プリエンプティブを有効にするのがオススメです。

ちなみにDoxseyさんの記事ではf1-microを使っていますが、 2020年4月18日現在、f1-microではGKEのワーカーノードとして最低限必要なシステムコンポーネントすらまともに動かないようです。 e2-smallにしましょう。

NodeへのHTTP/HTTPSアクセスを許可

ファイアウォール設定TCP/UDP双方の80・443ポートingressを許可しましょう。 この手順はDoxseyさんの記事に含まれているので、よくわからない方はそちらを参照してください。

ドメイン準備

何らかのレジストラを使用してドメインを取得してください。

便宜上、取得したドメイン名をexample.comとします。

Cloudflare DNSの設定

CloudflareのDNSホスティングサービスを使います。 無料から利用できます。この手順もDoxseyさんの記事に含まれているので参照してください。

まずアカウントのホーム画面に移動し、+ Add a site ボタンからサイトを作成します。 前手順で準備したドメイン名を使ってください。

次に作成したサイトのダッシュボードのDNS管理画面に移動します。

Cloudflare DNSで指示される通りに、レジストラで指定しているDNSサーバリストをCloudflare DNSのものに置き換えてください。この置き換えの反映には時間がかかるかもしれません。

この時点では取り敢えずwww.example.comexample.comエイリアスするためのCNAMEレコードを作っておきましょう。

kubernetes-Cloudflare-sync のデプロイ

(この手順もDoxseyさんの記事に含まれています)

プリエンプティブインスタンスを使っているとNodeは1日に1度再作成されます。 その際に外部IPが変わってしまうのですが、これを自動的にCloudflare DNSのAレコードに同期するカスタムコントローラkubernetes-Cloudflare-syncがあるのでデプロイしてください。 これを使うとドメイン名からNodeの外部IPを引ける状態が常に維持されます。

デプロイにはCloudflare APIを操作するためのAPIキーが必要になります。詳しくはcalebdoxsey/kubernetes-Cloudflare-syncのREADMEをご覧ください。

Contourをデプロイ

ここまでの手順を行うとドメイン名からノード外部IPを引けるようになっていますが、 そのアクセスをL7制御するコンポーネントがまだデプロイされていません。

Doxseyさんの記事では生のNginx DaemonSetでL7制御しているのですが、 これはあまり使い勝手がよくありません。

そこで生のNginx DaemonSetはやめて、Kubernetesらしく外部アクセス制御するためのIngress Controllerを立てましょう。

今回はIngress ControllerにContourを選びます。 ContourはEnvoyベースのIngress Controllerです。 ダウンタイム無しで設定変更が行える、gRPCを扱える、shadow proxyをサポートしているなどの長所があります。

ここからはContourのデプロイ方法を説明します。バージョンv1.3.0を使用します。

Getting Startedに従いスタンダードにデプロイするとLoadBalancer Serviceを使うのですが、 GKE環境でLoadBalancer Serviceを作ってしまうと前述の課金が発生します。

これを回避するためにHost Networkingデプロイオプションを利用します。 これはEnvoy DaemonSetをホストネットワーク上にデプロイし、Nodeへの80,443アクセスをEnvoyでリッスンするという方式です。

contour/examples/contourマニフェスト群に以下の変更を加え、applyしてください。

  • Envoy用Servicetype: LoadBalancerexternalTrafficPolicy: Localの指定を消す
  • Envoy PodhostNetwork: trueにし、dnsPolicy: ClusterFirstWithHostNetにする
  • Contourのserveコマンド--envoy-service-http-port=80--envoy-service-https-port=443を追加する

ここまで行うと、HTTPProxyカスタムリソースで任意のServiceをインターネット公開できるようになります。HTTPProxyはIngressリソースの置き換えとなるカスタムリソースです。

ContourはIngressリソースも解釈できますが、Ingressリソースを作成するとGKE Ingress controllerが動作してしまう事故が起きかねないのでHTTPProxyを使うようにしたほうが良いでしょう。

Cert-managerデプロイ

どうせならHTTPSを使用してサービス公開したいのでcert-managerをデプロイしましょう。cert-managerのデプロイはスタンダードなやり方で問題ありません。

ここまでの案内に従うとCloudflare DNSを使用しているはずなので、CloudflareでACME DNS-01チャレンジをするためのIssuer/ClusterIssuerを作成しましょう。CloudflareのAPIトークンを使用します。

HTTP-01チャレンジを選ぶこともできますが、cert-managerでHTTP-01チャレンジを行うとLoadBalancer Serviceが一時的に作られてしまうので、DNS-01チャレンジを利用したほうが良いでしょう。

用意したドメインexample.comについてCertificateリソースを発行し、作成されたSecretをHTTPProxy.spec.tls.secretNameにセットすると、対象のサービスをHTTPSで公開できます。

マニフェスト例を載せておきます。

ClusterIssuer

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: cloudflare-prod
spec:
  acme:
    email: example@gmail.com
    privateKeySecretRef:
      name: cloudflare-account-key
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            key: api-key
            name: cloudflare-api-key-secret
          email: example@gmail.com

Certificate

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: example.com
spec:
  commonName: example.com
  dnsNames:
  - example.com
  issuerRef:
    kind: ClusterIssuer
    name: clouddns-prod
  secretName: example-com-prod-tls

HTTPProxy

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: example
spec:
  routes:
  - conditions:
    - prefix: /
    services:
    - name: frontend
      port: 3000
  virtualhost:
    fqdn: example.com
    tls:
      secretName: example-com-prod-tls

サブドメイン追加方法

example.comサブドメイン、たとえばfoo.example.comを追加したい場合の手順を説明します。

Cloudflare DNSにCNAMEレコードを追加し、foo.example.comexample.comエイリアスとなるようにしてください。

あとはexample.comのときと同様にHTTPProxyを作るだけです。

ドメイン追加方法

example.com以外のドメイン、たとえばexample2.comを追加したい場合の手順を説明します。

まずexample.comの時と同様、example2.comをCloudflareにsite追加し、レジストラDNSサーバをCloudflareのものに切り替えてください。

次にcloudflare-kubernetes-syncを新規でもう1つデプロイしてください。 こうするとexample.comとexample2.comに同じAレコードが割り当てられます。

あとは普通にすでにデプロイ済みのContourでHTTPProxyを作るだけです。

まとめ

GKEでGCPロードバランサとGKE Ingressを使わず、使い勝手を維持したままL7制御するための手順を説明しました。GKEと書きましたが、他のKaaSでも動く気がします。

この記事を書いた後、もう一度この手順を最初から動作確認するのが面倒すぎてやってないので、 なにか書き漏らしがあるかもしれません。 質問があったら書いてください。

あとこの手順はサービスの濫用っぽい気もしなくはないです。だめだったら消すので言ってください。