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

12 min read読了の目安(約11300字

この記事では、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自体の検証もこれから進めていこうと思います。

参考