🔒

GKEのIngressでLet's Encryptの証明書を利用できるようにする

2021/09/22に公開

はじめに

GKEのIngressでLet's Encryptの証明書を利用したかった、日本語のドキュメントを見つけたけど少し古く、CRDのAPIversionなど上がっており、一部設定項目などが変わっていたので、最新版での動作確認手順をまとめる

構成図

以下のような感じになる

参考元

参考資料

前提条件

  • GCPのCloud DNSでドメインを管理している(hogehoge.comというドメインを管理してるとする)
  • gcloud cliインストール/設定済み
  • kubectlインストール/設定済み
  • GKEのverは1.20以上

設定手順

cert-managerのデプロイ

2021/09時点で最新のv1.5.3を利用する
https://github.com/jetstack/cert-manager/releases/tag/v1.5.3

$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.5.3/cert-manager.yaml

PodがReadyになればOK

$ kubectl get pods --namespace cert-manager
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-848f547974-6hfgj             1/1     Running   0          29m
cert-manager-cainjector-54f4cc6b5-vsmsm   1/1     Running   0          29m
cert-manager-webhook-58fb868868-642kd     1/1     Running   0          29m

Cloud DNSの操作権限を持つGCPのIAM service accountを作成し、k8sからも参照できるようにする

利用するProjectIDを環境変数に持たせておく(別にやらなくても良い、記事用に)

$ export PROJECT_ID=xxxxxxxxx

GCPのService Account作成

$ gcloud iam service-accounts create dns01-solver

GCPのService Accountにdns admin権限を付与

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
   --member serviceAccount:dns01-solver@$PROJECT_ID.iam.gserviceaccount.com \
   --role roles/dns.admin

GCPのService Accountをjsonファイルにexport

$ gcloud iam service-accounts keys create service-account.json \
  --iam-account dns01-solver@$PROJECT_ID.iam.gserviceaccount.com

GCPのService Accountのjsonファイルをk8sのsecretとして作成しておく (cert-managerが利用するため、cert-manager namespaceに作成しておかないとエラーになるので注意)

$ kubectl create -n cert-manager secret generic clouddns-dns01-solver-svc-acct \
  --from-file=service-account.json

Issuerの作成

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # 以下を変更すること
    email: 発行者用のemail address
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - dns01:
        cloudDNS:
          # 以下を利用するGCPのプロジェクト名に変えること
          project: $PROJECT_ID
          serviceAccountSecretRef:
            name: clouddns-dns01-solver-svc-acct
            key: service-account.json

こちらをデプロイ

$ kubectl apply -f cluster-issuer.yml

Ready=Trueになるまで待つ

$ kubectl get clusterissuers.cert 
NAME               READY   AGE
letsencrypt-prod   True    3m

証明書発行(ワイルドカード証明書)

ワイルドカード証明書を発行します
*.hogehoge.comという感じで、hogehoge.comのサブドメイン全てに対して使える証明書になります(aaa.hogehoge.combbb.hogehoge.comなどで使いまわせる)

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: hogehogecom
  namespace: default
spec:
  # 以下の名前でsecretが作られる
  secretName: hogehogecom-tls
  issuerRef:
    # 1つ前の手順で作成した発行者を指定
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  # 以下を利用したいワイルドカード名にすること(これは前提の通り、GCPのCloud DNSで管理されていることが必須)
  - "*.hogehoge.com"
$ kubectl apply -f certificate.yml

大体applyしてから0~5分程度で作成される

$ kubectl get certificate
NAME          READY   SECRET            AGE
hogehogecom   True    hogehogecom-tls   24s

ずっとReadyがFalseの場合は下記を確認し、それぞれの詳細をdescribeで確認する。
certificateからchallengesが作られ、challengesからordersが作られている。
おそらくどこかでエラーが発生している

$ kubectl get orders    
$ kubectl get challenges

Ingressで利用する静的外部IPを作成しておく

指定しない限りはIngress作り直すたびにLBのIP変わって、毎回CloudDNSの向き先を変えないといけないので、あらかじめLBに割り当てるIPを用意しておく

$ gcloud compute addresses create ingress-ip --global

ingressの場合はGlobalタイプじゃないとエラー出すので注意

https://cloud.google.com/kubernetes-engine/docs/tutorials/configuring-domain-name-static-ip?hl=ja#use_an_ingress

HTTP(S) ロードバランサを作成する Ingress を使ってアプリケーションを公開する場合は、グローバル静的 IP アドレスを予約する必要があります。リージョン IP アドレスは Ingress では機能しません。

Ingreeで利用するホスト名の名前解決先を静的IPで指定しておく

今回利用するホスト(aaa.hogehoge.com とする)のAレコードを作成し、向き先を1つ前の手順で作成した静的外部IP(ingress-ip)のアドレスにしておく

Ingress/サンプルDeploymentを作成

適当にIngress用のアプリをデプロイします
(アプリ参考元-> https://matthewpalmer.net/kubernetes-app-developer/articles/kubernetes-ingress-guide-nginx-example.html )

kind: Pod
apiVersion: v1
metadata:
  name: apple-app
  labels:
    app: apple
spec:
  containers:
    - name: apple-app
      image: hashicorp/http-echo
      args:
        - "-text=apple"
---
kind: Service
apiVersion: v1
metadata:
  name: apple-service
spec:
  selector:
    app: apple
  ports:
    - port: 5678
---
kind: Pod
apiVersion: v1
metadata:
  name: banana-app
  labels:
    app: banana
spec:
  containers:
    - name: banana-app
      image: hashicorp/http-echo
      args:
        - "-text=banana"
---
kind: Service
apiVersion: v1
metadata:
  name: banana-service
spec:
  selector:
    app: banana
  ports:
    - port: 5678 # Default port for image

これらのserviceへのリクエストを制御するIngressをデプロイします

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    # 下記は前の手順で作成した静的外部IPのリソース名を指定する
    kubernetes.io/ingress.global-static-ip-name: ingress-ip
    # 作成したcluster-issuerのリソース名を指定
    cert-manager.io/cluster-issuer: letsencrypt-prod
    ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
  - hosts:
    # 今回利用するドメイン名を指定する
    - aaa.hogehoge.com
    # 証明書作成時に指定したsecret名を指定する
    secretName: hogehogecom-tls
  rules:
    # 今回利用するドメイン名を指定する
    - host: aaa.hogehoge.com
      http:
        paths:
          - path: /apple
            pathType: ImplementationSpecific
            backend:
              service:
                name: apple-service
                port:
                  number: 5678
          - path: /banana
            pathType: ImplementationSpecific
            backend:
              service:
                name: banana-service
                port:
                  number: 5678

コメントをつけた箇所が重要なポイントになります

動作確認

少し待って、ingressが作成されたら

へブラウザでアクセスしてみます。アドレスバーの横の南京錠アイコンがエラーを吐いてないと思います。

Discussion