cert-manager と Cloudflare DNS で Envoy Gateway の証明書を自動管理する

Table of Contents

1. はじめに

みなさん、Kubernetes クラスターの証明書管理してますか?

前回の記事では、Envoy Gateway を使って argocd-server に HTTPS でアクセスする環境を構築しました。 その際、証明書は Ansible で自己署名の CA/Server 証明書を作成して Kubernetes Secret に登録するという方法を取っていたのですが、 記事の最後に「もしかしたら cert-manager とかを使った方が楽だったかもしれませんが、終わってから気づきました…」と書いていました。

というわけで今回は、cert-manager を使って Let’s Encrypt の証明書を Cloudflare DNS01 チャレンジ で自動発行・更新する方法を紹介します。 これにより、自己署名証明書からの脱却と、証明書の自動更新を実現します。

2. cert-manager について

cert-manager は、Kubernetes クラスター内で TLS 証明書の発行・更新を自動化してくれるツールです。 Let’s Encrypt などの ACME 対応の CA(認証局)と連携して、証明書のライフサイクルを自動で管理してくれます。

証明書の発行には ACME プロトコルを使用しますが、ドメインの所有を証明するためのチャレンジ方法がいくつかあります。 今回は DNS01 チャレンジ を使います。 DNS01 チャレンジは、ドメインの DNS レコードに特定の TXT レコードを追加することでドメインの所有を証明する方法です。

3. 前提環境

  • Kubernetes クラスター: 自宅サーバー上の環境(kubeadm + OVN-Kubernetes)
  • Envoy Gateway: v1.5.0(前回の記事でインストール済み)
  • ドメイン管理: Cloudflare(home.tomokon.net
  • cert-manager: v1.18.2

Envoy Gateway の GatewayClass や Gateway リソースは前回の記事で作成済みの前提で進めます。

4. cert-manager のインストール

cert-manager のインストールは kustomize で管理しています。

4.1 kustomize の構成

kustomize/cert-manager/core/
├── kustomization.yaml
└── patch-enable-gateway-api.yaml

kustomization.yaml では、cert-manager の公式マニフェストをリモートリソースとして参照し、 Gateway API を有効化するためのパッチを当てています。

# kustomize/cert-manager/core/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml

patches:
  - path: patch-enable-gateway-api.yaml

4.2 Gateway API 有効化パッチ

cert-manager はデフォルトでは Gateway API のサポートが無効になっています。 今回は Envoy Gateway(Gateway API 実装)と連携させたいので、--enable-gateway-api フラグを有効にします。

# kustomize/cert-manager/core/patch-enable-gateway-api.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cert-manager
  namespace: cert-manager
spec:
  template:
    spec:
      containers:
        - name: cert-manager-controller
          args:
            - --v=2
            - --cluster-resource-namespace=$(POD_NAMESPACE)
            - --leader-election-namespace=kube-system
            - --acme-http01-solver-image=quay.io/jetstack/cert-manager-acmesolver:v1.18.2
            - --max-concurrent-challenges=60
            # Additional arguments
            - --enable-gateway-api
            - --dns01-recursive-nameservers-only
            - --dns01-recursive-nameservers=1.1.1.1:53,1.0.0.1:53

cert-manager は DNS01 チャレンジの際に、TXT レコードが正しく設定されているかを self-check します。 筆者の環境では自宅サーバー上の DNS サーバー(BIND9)が tomokon.net の権威サーバーとして動いています。 そのため、cert-manager が home.tomokon.net の TXT レコードを確認しようとすると、 ローカルの権威サーバーが Cloudflare に聞きに行かずに自分で解決してしまい、 DNS01 チャレンジ用の TXT レコードが見つからず self-check が失敗し続けてしまいます。

--dns01-recursive-nameservers-only を指定すると /etc/resolv.conf を無視して、 --dns01-recursive-nameservers で指定したネームサーバーのみを使うようになります。 ここでは Cloudflare のパブリック DNS(1.1.1.11.0.0.1)を指定することで、 Cloudflare 側に追加された TXT レコードを正しく確認できるようにしています。

5. Cloudflare API Token の準備

cert-manager が Cloudflare の DNS レコードを操作するためには、API Token が必要です。

5.1 API Token の作成

Cloudflare のダッシュボードから API Token を作成します。必要な権限は以下の通りです。

  • Zone - DNS - Edit: DNS レコードの追加・削除(TXT レコードの操作に必要)
  • Zone - Zone - Read: ゾーン情報の読み取り(ゾーン ID の取得に必要)

Zone Resources は Include - All Zones に設定するか、 特定のゾーンだけに絞りたい場合は Include - Specific zone で対象のゾーン(例: tomokon.net)を指定します。 最小権限の原則に従うなら後者がおすすめです。

5.2 Kubernetes Secret の作成

作成した API Token を Kubernetes Secret として登録します。

kubectl create secret generic cloudflare-api-token \
  --from-literal=api-token=<YOUR_CLOUDFLARE_API_TOKEN> \
  -n cert-manager

6. ClusterIssuer の作成

ClusterIssuer は、 cert-manager が証明書を発行するための設定をクラスター全体で共有するリソースです。 Issuer と違ってクラスタースコープなので、どの Namespace からでも利用できます。

# kustomize/cert-manager/config/cluster-issuer.yaml

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-cloudflare
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@example.com
    privateKeySecretRef:
      name: letsencrypt-cloudflare
    solvers:
      - dns01:
          cnameStrategy: Follow
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token

各フィールドを簡単に説明します。

  • server: Let’s Encrypt の ACME v2 本番エンドポイント
  • email: Let’s Encrypt のアカウントに紐づけるメールアドレス(証明書の有効期限通知などに使われます)
  • privateKeySecretRef: ACME アカウントの秘密鍵を保存する Secret の名前
  • solvers: DNS01 チャレンジに Cloudflare を使用
    • cnameStrategy: Follow: CNAME レコードがある場合にそれを辿って解決する設定
    • apiTokenSecretRef: 先ほど作成した Cloudflare API Token の Secret を参照

7. Gateway リソースの更新

前回の記事では自己署名証明書を Secret に手動で登録していましたが、 今回は cert-manager に証明書の発行を任せます。

cert-manager は Gateway API と連携する際、Gateway リソースに付与された annotation を読み取り、 自動的に Certificate リソースを作成してくれます。

# kustomize/envoy-gateway/config/gateway.yaml

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: envoy-http
  namespace: gateway-system
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-cloudflare
spec:
  gatewayClassName: envoy
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.home.tomokon.net"
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              shared-gateway-access: "true"
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            group: ""
            name: cert-home-tomokon-net

前回の記事からの変更点は annotationscert-manager.io/cluster-issuer: letsencrypt-cloudflare を追加した部分です。 この annotation があることで、cert-manager が自動的にこの Gateway の listener を監視してくれます。

cert-manager が Certificate リソースを自動作成するために、listener は以下の条件を満たす必要があります。

  1. hostname が指定されていること(空は不可)
  2. tls.modeTerminate であること
  3. tls.certificateRefs[].name が指定されていること(この名前で Secret と Certificate が作成される)

つまり、cert-home-tomokon-net という名前で Certificate リソースと Secret が自動的に作成され、 Let’s Encrypt から *.home.tomokon.net のワイルドカード証明書が発行されるという流れです。

8. 動作確認

すべてのリソースを apply した後、証明書が正常に発行されたか確認します。

8.1 Certificate リソースの確認

$ kubectl get certificate -n gateway-system
NAME                    READY   SECRET                  AGE
cert-home-tomokon-net   True    cert-home-tomokon-net   5m

READYTrue になっていれば、証明書の発行が成功しています。

もし False のままの場合は、以下のコマンドでトラブルシューティングできます。

# Certificate の詳細を確認
kubectl describe certificate cert-home-tomokon-net -n gateway-system

# CertificateRequest の確認
kubectl get certificaterequest -n gateway-system

# ACME Order の確認
kubectl get order -n gateway-system

# ACME Challenge の確認(DNS01 チャレンジの状態)
kubectl get challenge -n gateway-system

# cert-manager のログ確認
kubectl logs -n cert-manager deployment/cert-manager

8.2 HTTPS アクセスの確認

$ curl -I https://argocd.home.tomokon.net
HTTP/2 200
content-type: text/html; charset=utf-8
...

ブラウザで確認すると、証明書の発行元が Let’s Encrypt になっていることが確認できます。 自己署名証明書のときはクライアント側に CA 証明書をインポートする必要がありましたが、 Let’s Encrypt の証明書であればその手間も不要です。

9. まとめ

今回は cert-manager + Let’s Encrypt + Cloudflare DNS01 を使って、 Envoy Gateway の TLS 証明書を自動管理する環境を構築しました。

前回の Ansible での手動管理と比べると、以下の点が改善されました。

  • 証明書の自動発行: Gateway リソースに annotation を追加するだけで証明書が自動発行される
  • 証明書の自動更新: cert-manager が有効期限を監視して自動的に更新してくれる
  • 信頼された証明書: Let’s Encrypt の証明書なのでクライアント側に CA 証明書をインポートする必要がない

前回の記事で「cert-manager を使った方が楽だったかも」と書いていましたが、実際にやってみて本当にそうでした。 特に証明書の自動更新は、自己署名証明書のときには手動で管理する必要があったので、めっちゃ助かります。

最後まで読んでいただき、ありがとうございました!

10. 参考資料

  1. cert-manager - Securing Gateway Resources
  2. cert-manager - Gateway
  3. cert-manager - Cloudflare DNS01
  4. cert-manager - Setting Nameservers for DNS01 Self Check
  5. cert-manager Documentation
  6. Envoy Gateway Documentation