🐈

Istio-IngressGateway + cert-managerでGKEのHTTPS対応をさくっと実現する

2021/01/10に公開

この記事では、Istio-IngressGateway + cert-manager を用いた HTTPS 対応時の設定手順やハマりポイントをまとめています。

今回の検証にあたっては、下記のようなモチベーションがありました。

  • Istio で遊ぶ環境が欲しかった
  • GKE での HTTPS 対応の設定(クラスタ外からの接続、cert-manager を利用した証明書管理)も一通り確認したかった
  • (GKE Cluster の作成の検証をしていたのでついでに)

同時期に下記記事の検証も行っていたので、リンク先から辿れる GKE Cluster 作成用の tf ファイル群を利用してベースとなる GKE Cluster の作成が可能です。
https://zenn.dev/taxin/articles/gke-infra-cicd

また、今回は下記の前提をもとに進めます。

  • 検証時に利用するドメインは Google Domains で取得しています
  • Istio-IngressGateway は type: NodePort に変更しています(Cloud Load Balancing(CLB)の費用節約のため)

GKEの作成

各コンポーネントを Deploy する GKE の Cluster を作成します。

下記のようなスペックでクラスタを作成しています。

  • Node マシンタイプ: e2-medium
  • Node 数: 3
  • Node Pool: Preemtible VMs
  • Network Tag: istio
  • 無効化したアドオン: Istio, HTTP load balancing, Kubernetes Engine Monitoring

今回はGKE アドオンとして提供されている Istio は無効化した上で、手動で Istio のセットアップを行います。
各種アドオンの無効化に関しては、GCP の公式ガイドがあるのでそちらを参考にしてみて下さい。
https://cloud.google.com/kubernetes-engine/docs/how-to/small-cluster-tuning?hl=ja#kubernetes-engine-monitoring

また、Istio の Setup を想定して、下記のような変更も加えています。
(変更点の詳細は下記のリンクを参考にして下さい。)

  • Firewall Rule の作成(allow - tcp:10250,tcp:443,tcp:15017)
  • Network Policy のアドオン有効化

https://istio.io/latest/docs/setup/platform-setup/gke/

ドメイン取得 + Cloudflare DNSの設定

HTTPS 対応の準備のために、ドメインの取得と Cloudflare DNS の設定を行います。

最初に、任意のレジストラを利用してドメインは取得します。
(Google Domains での取得方法は下記のリンクを参考にして下さい。)
https://support.google.com/domains/answer/4491208?hl=ja&ref_topic=9143021

次に、Cloudflare DNS 側の設定作業を行います。
アカウントを事前に作成した上で、アカウントのホーム画面から「Add a site」をクリックしてドメインを追加します。
https://support.cloudflare.com/hc/ja/articles/201720164-Cloudflareアカウントを作成してWebサイトを追加する

その上で、レジストラ側で指定している DNS サーバリストを Cloudflare DNS のサーバに指定し直します。
https://support.cloudflare.com/hc/ja/articles/205195708
https://support.google.com/domains/answer/3290309?hl%3Den

Cloudflare Syncのセットアップ

今回の GKE クラスタは、Node として Preemtible VMs を利用しています。
Preemtible VMs は最長持続時間が 24 時間となっており、シャットダウン後には Node の Public IP も変わるため A レコードの更新(同期)が必要になります。
https://cloud.google.com/kubernetes-engine/docs/how-to/preemptible-vms?hl=ja

IP の同期には、kubernetes-Cloudflare-sync を利用します。
この Custom Controller によって、自身で管理しているドメイン名から GKE Nodes の外部 IP を引ける状態が維持されます。
https://github.com/calebdoxsey/kubernetes-Cloudflare-sync

// CloudflareのAPI keysをSecretsとして登録する
kubectl create secret generic cloudflare \
    --from-literal=email=<email> \
    --from-literal=api-key=<api_key>

// Service Accountの設定
kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user <email>
kubectl apply -f ./sync-sa.yaml

// ImageのBuild + Push
docker build -t gcr.io/<gcp_project_id>/kubernetes-cloudflare-sync:latest . 
docker push gcr.io/<gcp_project_id>/kubernetes-cloudflare-sync:latest

// Deploymentの作成([Configure]のsample deployment)
kubectl apply -f ./sync-deployment.yaml

Istio(IngressGateway)のセットアップ

ここまでの準備が完了したら、Istio-IngressGateway のセットアップを行います。
手順は、 getting-started の内容と同じです。
https://istio.io/latest/docs/setup/getting-started/

IngressGateway の Manifest 内で type: LoadBalancertype: NodePort に変更するため、profile は demo を指定した上で Manifest を generate します。
https://istio.io/latest/docs/setup/additional-setup/config-profiles/

// demo Profile用のManifestのディレクトリを用意しておく
$ mkdir temp-profile 

<dir>
|
|- istio-1.8.1
|
|- temp-profile
// Istioのdownload
$ curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.8.1 TARGET_ARCH=x86_64 sh -

$ cd istio-1.8.1
$ ./bin/istioctl manifest generate --set profile=demo >> ./../temp/istio-demo-profile.yaml

IngressGateway の Manifest 内で type: LoadBalancertype: NodePort に変更した上で修正した Manifest を Apply します。

---
apiVersion: v1
kind: Service
metadata:
  annotations: null
  labels:
    app: istio-ingressgateway
    install.operator.istio.io/owning-resource: unknown
    istio: ingressgateway
    istio.io/rev: default
    operator.istio.io/component: IngressGateways
    release: istio
  name: istio-ingressgateway
  namespace: istio-system

  ...
  
  selector:
    app: istio-ingressgateway
    istio: ingressgateway
  
  // NodePortに変更する
  type: NodePort
---
$ kubectl apply -f ./../temp/istio-demo-profile.yaml

$ kubectl label namespace default istio-injection=enabled

$ kubectl get svc --all-namespaces
NAMESPACE      NAME                   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                                                                      AGE
default        kubernetes             ClusterIP   10.1.0.1     <none>        443/TCP                                                                      3h38m
istio-system   istio-egressgateway    ClusterIP   10.1.0.190   <none>        80/TCP,443/TCP,15443/TCP                                                     72s
istio-system   istio-ingressgateway   NodePort    10.1.1.172   <none>        15021:32308/TCP,80:31232/TCP,443:30791/TCP,31400:32322/TCP,15443:32480/TCP   71s
istio-system   istiod                 ClusterIP   10.1.1.23    <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP                                        70s
kube-system    calico-typha           ClusterIP   10.1.3.58    <none>        5473/TCP                                                                     173m
kube-system    kube-dns               ClusterIP   10.1.0.10    <none>        53/UDP,53/TCP                                                                3h38m
kube-system    metrics-server         ClusterIP   10.1.0.54    <none>        443/TCP                                                                      3h38m

$ kubectl describe node | grep ExternalIP
  ExternalIP:   <node1_ip>
  ExternalIP:   <node2_ip>
  ExternalIP:   <node3_ip>

次に、Istio-IngressGateway 作成時に割り当てられた GKE Node の port に対する通信を Allow する Firewall Rule を作成して、NodePort で外部から疎通できるようにします。
(必要に応じて、GKE Node 用の Network Tag を付与して下さい。)

// 15021:32308/TCP,80:31232/TCP,443:30791/TCP,31400:32322/TCP,15443:32480/TCP の例であれば
// <http_port_number> → 31232, <https_port_number> → 30791となる
gcloud compute firewall-rules create allow-conn-from-nodeport \
    --network=default \
    --project <gcp_project_id> \
    --target-tags istio \
    --allow tcp:<http_port_number>,<https_port_number>

HTTP 通信はこの状態でも可能になっているはずです。
httpbin などの sample deployment を Deploy した上で、疎通確認をしてみて下さい。
https://github.com/istio/istio/tree/release-1.8/samples/httpbin

cert-managerのセットアップ

今回は、HTTPS に対応するために、追加で cert-manager を利用した証明書発行と Istio 側のルーティング設定を行います。
https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/

$ export CM_VERSION="v1.1.0"
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/$CM_VERSION/cert-manager.yaml
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
...
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created

$ kubectl get pods --namespace cert-manager
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-64887fb9d6-2hjxv             1/1     Running   0          103s
cert-manager-cainjector-99977ff45-tdw7s   1/1     Running   0          104s
cert-manager-webhook-64c5d4c9db-29b2c     1/1     Running   0          103s

cert-manager の Deploy が完了したら、下記のドキュメントに従って Cloudflare 用の DNS-01 チャレンジを行うための Issuer/ClusterIssuer を作成します。
https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/#api-tokens

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token-secret
  namespace: istio-system
type: Opaque
stringData:
  api-token: <token>
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: cloudflare-prod
  namespace: istio-system
spec:
  acme:
    email: <email>
    privateKeySecretRef:
      name: cloudflare-account-key
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            key: api-token
            name: cloudflare-api-token-secret
          email: <email>

Issuer/ClusterIssuer を作成したら、Certificate を作成します。
https://istio.io/latest/docs/ops/integrations/certmanager/

Certificate 作成時の注意点としては、istio-ingressgateway の Deployment と同じ Namespace(istio-system)で作成する必要があります
(今回の検証では、Issuer, Certificate などの cert-manager 関連の resource は istio-system で作成しています。)

The Certificate should be created in the same namespace as the istio-ingressgateway deployment.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: <domain>
  namespace: istio-system
spec:
  commonName: <domain>
  dnsNames:
  - <domain>
  issuerRef:
    kind: Issuer
    name: cloudflare-prod
  secretName: <secret_name>

Certificate の Manifest 内で指定した Secret Name で Secret が正常に作成できたかを確認します。

$ kubectl get secret -n istio-system
...
<secret_name>                          Opaque                                1      21s

次に、払い出した Domain、作成した Certificate(Secret)を Manifest 内で指定した上で gateway, vservice のリソースを作成します。
https://istio.io/latest/docs/tasks/traffic-management/ingress/secure-ingress/

ここで作成するリソースは下記のような役割を担います。

  • gateway: リクエストを受ける istio-ingessgateway(Deployment)の設定を行うためのリソース(port, protocol, cert)
  • vservice: トラフィックのルーティング先のルールを設定するためのリソース
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      # secret name of certificate
      credentialName: <secret_name>
    # This should match a DNS name in the Certificate
    hosts:
    - <domain>
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - <domain>
  gateways:
  - httpbin-gateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        # The name of a service from the service registry.
        host: httpbin

これによって、クラスタ外からのリクエストを Backend の Pod にルーティングできるようになります。

作成した virtual resources を利用して、受けた HTTP(S)リクエストに対して下記のように処理を行っています。

  • gateway 側で host, port, protocol などの条件を確認する
    • (LB を利用していない場合は、Host Header を付与する)
  • vservice 側で定義されている URL パターンのルールを見て、ルーティング先を決定する(ex. /status)

Istio-IngressGateway のルーティングの流れは下記の Blog を参考にしてみて下さい。
https://blog.jayway.com/2018/10/22/understanding-istio-ingress-gateway-in-kubernetes/

HTTPSでの接続検証

全てのセットアップが完了したら、接続の確認を行います。
リクエストのルーティング先の Deployment として、httpbin を Deploy しておきます。
https://github.com/istio/istio/tree/release-1.8/samples/httpbin

$ cd istio-1.8.1
$ kubectl apply -f samples/httpbin/httpbin.yaml

ドメイン名と Istio-IngressGateway に払い出されている HTTPS 用の port を確認して、下記のように curl コマンドを実行します。
200 OKが返ってくれば、正常に設定できていることが確認できます。

$ curl -s -I -HHost:<domain> "https://<domain>:<port_number>/status/200"
HTTP/2 200
server: istio-envoy
date: Fri, 01 Jan 2021 13:35:47 GMT
content-type: text/html; charset=utf-8
access-control-allow-origin: *
access-control-allow-credentials: true
content-length: 0
x-envoy-upstream-service-time: 1

終わりに

Istio-IngressGateway + cert-manager を用いた HTTPS 対応時の設定手順についてまとめました。
Istio 自体の検証もこれから進めていこうと思います。

参考

Discussion