GKE で Google マネージド証明書を利用するのが意外と難しい!Gateway API で LB を作成

こんにちは、SREグループのカンタンです!

GO株式会社では AWS EKS と GCP GKE の Kubernetes クラスタを活用していて、EKS は以前から AWS マネージド証明書を利用していますが GKE は最近になって Let's Encrypt 証明書から Google マネージド証明書に移行し始めました。

Google マネージド証明書の構成方法が複数あり、構成方法によって GKE での使い方が異なるため AWS と比べて Google マネージド証明書の利用が意外と難しいと感じました。 本記事では Google マネージド証明書の種類と GKE での利用方法を紹介します。

証明書の構成方法

GCP では証明書の構成方法が2つあります。

  • Compute Engine SSL 証明書
  • Certificate Manager

Compute Engine SSL 証明書

ドキュメントやコンソール画面によっては「Classic certificates」、「従来の証明書」とも呼ばれています。

Cloud Load Balancing によってプロビジョニングされる証明書で、 ドメイン認証にはロードバランサー認証 (HTTP-01 チャレンジ) しか対応されていないため、以下の制約があります。

特にドメインDNS レコードをロードバランサーに向かうように設定しないと証明書を発行できないため、例えば環境のマイグレーションのため古い環境から新しい環境に移行したい場合はダウンタイムが発生してしまいます。

lb-auth-issue

セルフマネージド証明書も対応しているため、自分で発行した証明書を GCP コンソールや gcloud compute ssl-certificates コマンドなどで GCP に登録することもできます。

Certificate Manager

Certificate Manager は証明書を発行するための最新のやり方で、ロードバランサー認証 (HTTP-01 チャレンジ)も DNS 認証 (DNS-01 チャレンジ)も対応しているため使いやすいです。

DNS 認証で証明書を発行する場合は以下の流れになります

  • GCP コンソール、terraform、gcloud certificate-manager certificates コマンドなどで証明書を作成
  • Certificate Manager から作成されたドメイン認証用の DNS レコード内容を DNS サービスに登録
  • Certificate Manager が DNS レコードを確認し証明書を発行

DNS 認証を使えば証明書を事前に発行できるため、ダウンタイムなく環境のマイグレーションを実施できます。 また、ワイルドカード証明書も発行できますので複数のサブドメインに対して証明書を発行したい場合は便利です。

dns-auth-issue

Certificate Manager もセルフマネージド証明書を対応しているため、自分で発行した証明書を登録できます。

GKE Ingress での証明書の設定方法

GKE ではロードバランサーを作成するために Ingress リソースを使うことが多いです。その場合、SSL 証明書を設定するには4つの方法があります。 後ほど説明しますが、Ingress では Certificate Manager が使えなくて、いずれも Compute Engine SSL の証明書を利用しています。最終的に Certificate Manager を使いたいため結局以下の方法を採用しませんでしたが参考までにご紹介します。

  • セルフマネージド証明書を Secret に保存し Ingress に紐付ける
  • セルフマネージド証明書を Compute Engine SSL 証明書として事前に登録し Ingress に紐付ける
  • Google マネージドの Compute Engine SSL 証明書を事前に登録し Ingress に紐付ける
  • ManagedCertificateGoogle マネージドの Compute Engine SSL 証明書を作成し Ingress に紐付ける

Compute Engine SSL 証明書として事前に登録するセルフマネージド証明書と Google マネージド証明書は「事前共有証明書」、「Pre-shared certificate」と呼ばれています。

セルフマネージド証明書 - Secret

証明書を Secret に保存します。

kubectl create secret tls my-certificate-secret \
    --cert=certificate.pem \
    --key=key.pem

Secret を Ingress に設定します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  tls:
    - secretName: my-certificate-secret
  defaultBackend:
    service:
      name: my-service
      port:
        number: 80

例えば Let's Encrypt 証明書を cert-manager という OSS ツール(GCP の Certificate Managerとは別)で自動的に発行する場合はその方法を使います。

  • cert-manager が Let's Encrypt に発行された証明書を Secret に保存
  • Ingress が Secret の内容を元にセルフマネージドの Compute Engine SSL 証明書を作成
  • ロードバランサーが Compute Engine SSL 証明書を利用
  • 更新の際、cert-manager が新しい証明書を Secret に保存し、Ingress が Compute Engine SSL 証明書を更新

ingress-self-managed-secret

裏ではセルフマネージドの Compute Engine SSL 証明書が作成されていて、gcloud compute ssl-certificates list コマンドでも確認できます。

$ gcloud compute ssl-certificates list
NAME             TYPE          CREATION_TIMESTAMP             EXPIRE_TIME               
k8s2-cr-xxxxxxx  SELF_MANAGED  2024-07-24T20:12:17.117-07:00  2024-10-22T19:03:20.000-07:00

セルフマネージド証明書 - 事前共有証明書

Compute Engine SSL 証明書を事前に作成します (terraformも使えます)。

gcloud compute ssl-certificates create my-self-managed-pre-shared-certificate \
    --certificate=certificate.pem \
    --private-key=key.pem

ingress.gcp.kubernetes.io/pre-shared-cert アノテーションを指定することで作成した Compute Engine SSL 証明書を Ingress に設定します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    ingress.gcp.kubernetes.io/pre-shared-cert: my-self-managed-pre-shared-certificate
spec:
  defaultBackend:
    service:
      name: my-service
      port:
        number: 80

ingress-self-managed-pre-shared

証明書の更新は以下のように手動で行う必要があるためこのやり方を推奨できません。

  • 新しい Compute Engine SSL 証明書を作成
  • Ingressingress.gcp.kubernetes.io/pre-shared-cert アノテーションに新しい証明書を設定
  • 古い Compute Engine SSL 証明書を削除

Google マネージド証明書 - 事前共有証明書

Google マネージドの Compute Engine SSL 証明書を事前に作成します (terraformも使えます)。

gcloud compute ssl-certificates create my-google-managed-pre-shared-certificate \
    --domains=www.example.com \
    --global

証明書をロードバランサーに紐づけてドメインDNS レコードをロードバランサーに向かうように設定しない限り証明書が発行されないため現時点では有効になっていないです。

ingress.gcp.kubernetes.io/pre-shared-cert アノテーションを指定することで作成した Compute Engine SSL 証明書を Ingress に設定します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    ingress.gcp.kubernetes.io/pre-shared-cert: my-google-managed-pre-shared-certificate
spec:
  defaultBackend:
    service:
      name: my-service
      port:
        number: 80

ドメインDNS レコードを作成されたロードバランサーに向かうように設定すれば証明書が発行されます。 発行される証明書が Google マネージドのため更新が自動的に行われ便利です。ただし Compute Engine SSL 証明書になっているため、上述の通りワイルドカード証明書に対応していないのと証明書の事前発行が不可能のため使えないケースもあります。

ingress-google-managed-pre-shared

Google マネージド証明書 - ManagedCertificate

Compute Engine SSL 証明書を gcloud や terraform で作成する代わりに ManagedCertificate リソースを使って証明書の作成を GKE に完結させることができます。

apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
  name: my-google-managed-certificate
spec:
  domains:
    - www.example.com

networking.gke.io/managed-certificates アノテーションを指定することで作成した Compute Engine SSL 証明書を Ingress に設定します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    networking.gke.io/managed-certificates: my-google-managed-certificate
spec:
  defaultBackend:
    service:
      name: my-service
      port:
        number: 80

ingress-managed

ドメインDNS レコードを作成されたロードバランサーに向かうように設定すれば証明書が発行されます。 発行される証明書が Google マネージドのため更新が自動的に行われ便利です。ただし裏では Compute Engine SSL 証明書になっているため、上述の通りワイルドカード証明書に対応していないのと証明書の事前発行が不可能のため使えないケースもあります。

問題

GO株式会社では古いサービスを弊社の共通インフラ基盤に移行したりインフラの構成を変更したりすることがあるため、証明書の発行をロードバランサーに依存させることが望ましくないです。 Compute Engine SSL 証明書ではなく Certificate Manager の証明書を使うようにすれば、証明書の事前発行が可能になりロードバランサーに依存しなくなりますが、Ingress が Certificate Manager を対応しない方針になっていて使えません。

Certificate Manager を利用できるように、ロードバランサーIngress ではなく Gateway API で作成する必要があります!Kubernetes 業界の中でも Gateway API がロードバランシングを行うための最新方法で、Ingress に代わるものとして注目されています。

Gateway API で LB を作成!

Certificate Manager の証明書を利用できるようにロードバランサーGateway API で作成する必要があります。

まずは Certificate Manager を使って DNS 認証のワイルドカード証明書を作成します。

# Certificate validation
resource "google_certificate_manager_dns_authorization" "example" {
  name   = "my-certificate-dns-authorization"
  domain = "example.com"        # without wildcard
  type   = "PER_PROJECT_RECORD" # allow for multiple projects to validate the same domain
}

# Certificate
resource "google_certificate_manager_certificate" "example" {
  name = "my-certificate"
  managed {
    domains            = ["*.example.com"]
    dns_authorizations = [google_certificate_manager_dns_authorization.example.id]
  }
}

DNS 認証用のレコードを DNS サービスに登録します(今回はAWS Route 53を利用)。

resource "aws_route53_record" "dns_auth" {
  zone_id = "xxx"
  name    = google_certificate_manager_dns_authorization.example.dns_resource_record[0].name
  type    = google_certificate_manager_dns_authorization.example.dns_resource_record[0].type
  records = [google_certificate_manager_dns_authorization.example.dns_resource_record[0].data]
  ttl     = 300
}

証明書をロードバランサーに紐づけられるように Certificate Map を作成します。

# Certificate Map
resource "google_certificate_manager_certificate_map" "my_cert_map" {
  name = "my-cert-map"
}

# Certificate Map Entry
resource "google_certificate_manager_certificate_map_entry" "my_cert_map_entry" {
  name     = "my-cert-map-entry"
  map      = google_certificate_manager_certificate_map.my_cert_map.name
  hostname = "*.example.com"
  certificates = [
    google_certificate_manager_certificate.example.name,
  ]
}

ロードバランサーGateway API で作成し Certificate Map を紐付けます。

kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
  name: my-gateway
  annotations:
    networking.gke.io/certmap: my-cert-map
spec:
  gatewayClassName: gke-l7-global-external-managed
  listeners:
    - name: https
      protocol: HTTPS
      port: 443

通信をサービスにルーティングできるように HTTPRoute を作成します。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-service-http-route
spec:
  parentRefs:
    - kind: Gateway
      name: my-gateway
  rules:
    - backendRefs:
        - name: my-service
          port: 80

結果、Google マネージドのワイルドカード証明書を事前に発行でき、ロードバランサーに設定できました!

gateway-certificate-manager

最後に

結果的にロードバランサーに依存しない形で Google マネージド証明書を発行し安全に運用できるようになりましたが、Ingress で作っていたロードバランサーGateway API で作り直す必要があったため AWS EKS で一つのアノテーションの追加だけで済む話が GKE では大掛かりな変更が必要でした。

Google マネージド証明書を使いたい方は Certificate Manager の利用を推奨しますが、Ingress ではなく Gateway API を利用する必要があるためご注意ください。 それぞれの証明書の特徴を以下にまとめましたので参考にしていただければ幸いです。

項目 Certificate Manager 証明書 Compute Engine SSL 証明書
事前発行 O (DNS 認証可能) X (ロードバランサ認証のみ)
ワイルドカード証明書 O (DNS 認証可能) X (ロードバランサ認証のみ)
GKE での利用 Gatewayのみ Ingress / Gateway
発行方法 terraform / gcloud / console terraform / gcloud / console / ManagedCertificate (Ingressのみ)
セルフマネージド証明書 O (事前登録) O (事前共有 / Secret)