Google Domains が事業売却されるという話から約半年、重い腰をあげてついに我が家のドメインも Cloudflare に移行対応しました。 半年も経てば Cloudflare への移行手順は多くの記事で紹介されているので、今回は Cloudflare 移行に伴って発生した自宅ラボで使っている cert-manager の移行対応を記録しておきます。

なぜドメイン移行先を Cloudflare にしたのか

このブログでも使っている nnstt1.dev というドメインは Google Domains で購入していましたが、ネームサーバーを設定してレコードの管理自体は Azure DNS でおこなっていました。

Azure DNS で管理しているなら Azure 自体でドメインを購入すればいいじゃないかと思ったのですが、Azure でドメインを購入するサービスの App Service ドメインでは com、net、co.uk、org、nl、in、biz、org.uk、co.in のトップレベルドメイン (TLD) しか対応していなかったため、.dev ドメインは別のサービスを利用するしかありませんでした。 (そもそも Google Domains にしたのも Azure が .dev に対応してなかったからなのに、今回も移行できないか確認してしまったという…)

というわけで他のドメインサービスを探すことになったのですが、おネームドットコムは論外ということ以外は特にこだわりはありませんでした。 半年前でもそうでしたが、やっぱり Cloudflare が移行先として人気のようで、右にならえで自分も Cloudflare に移行することにしました。

移行自体はすんなり完了したのと、レコード自体の管理も Terraform を使うようにしました。

cert-manager の対応

自宅ラボの Kubernetes クラスタでは証明書管理のための cert-manager を使っています。 クラスタで(自宅内に閉じて)公開しているサービスの証明書を Let’s Encrypt で発行していますが、そのためにはドメインの検証が必要になります。 検証の方法は 2 つあって、Let’s Encrypt から対象ドメインを使っているサービスにアクセスする HTTP Validation と、DNS に指定の TXT レコードを作成する DNS Validation があります。

自宅ラボのサービスが外部公開していないため、ドメインの検証は DNS Valication になり、前項で述べた通りレコードの管理は Azure DNS を使っているため、cert-manger の DNS Validation の設定も Azure DNS を対象としたものにしていました。

今回 Cloudflare に移行したとしても Azure DNS を使うようにネームサーバーを設定すれば cert-manager の対応もいらなかったのですが、Cloudflare のネームサーバーは無料プランでは変更できない仕様だったため、泣く泣く Azure DNS を手放してドメイン周りは Cloudflare に統一することにしました。

そういう背景もあり、cert-mangaer の DNS Validation で Cloudflare 対応が必要になったというわけです。

Cloudflare API トークン

cert-manager で Cloudflare に検証用レコードを追加するには Cloudflare の API トークンを利用します。 Cloudflare のダッシュボードから対象ドメインの概要ページを開き、右下の[API トークンを取得] というリンクから API トークンを発行・取得できます。 API トークンに付与する権限はテンプレートの「ゾーン DNS を編集する」を選びました。

この API トークンを cert-manager で利用するには Kubernetes の Secret リソースを作成して Pod から参照させます。 Secret リソースに直接 API トークンを書いてもいいのですが、我が家の Kubernetes クラスタでは HashiCorp Vault を使ってシークレット管理をしているため、API トークンを Vault に書き込んで Vault Secrets Operator を使って Secret リソースに同期させます。

次の Vault Secrets Operator で使用するカスタムリソースをデプロイしました。

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
  name: vaultconnection
  namespace: cert-manager
spec:
  address: http://vault.vault.svc.cluster.local:8200
  skipTLSVerify: true
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: vaultauth
  namespace: cert-manager
spec:
  vaultConnectionRef: vaultconnection
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: cert-manager
    serviceAccount: cert-manager
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: cloudflare-api-token
  namespace: cert-manager
spec:
  vaultAuthRef: vaultauth
  mount: secret
  path: cloudflare
  type: kv-v2
  refreshAfter: 5s
  rolloutRestartTargets:
    - kind: Deployment
      name: cert-manager
  destination:
    name: cloudflare-api-token
    create: true

VaultConnection で Vault クラスタを指定し、VaultAuth で Vault への認証方法を指定しています。 API トークンは Vault の KV v2 というシークレットエンジンに格納したため、VaultStaticSecret というリソースに API トークンの格納パスや、どの Secret リソースに同期させるか、どの Deployment をロールアウトするか、といった構成を記述しています。

もともと Azure DNS を使っていた際は Vault の「動的シークレット (Dynamic Secrets)」という機能を使って Azure にアクセスする認証情報を動的に作成していたのですが、その設定も不要になりました。 ちなみに、Vault に API トークンを追加したときのコマンドはこちら。

$ vault secrets enable -path=secret kv-v2
$ vault kv put secret/cloudflare api-token=<API トークン>

このシークレットを参照するための権限をポリシーに追加。

path "secret/data/cloudflare" {
  capabilities = [ "read" ]
}

cert-manager Issuer/ClusterIssuer

Cloudflare にレコードを追加するための API トークンも準備できたので、cert-manager が Cloudflare を使って DNS Validation できるようにマニフェストを書き換えます。 もともと次のようなマニフェストを使って、Azure DNS ゾーンに TXT レコードを追加していました。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    email: hoge@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: issuer-account-key
    solvers:
      - dns01:
          azureDNS:
            clientID: 00000000-0000-0000-0000-000000000000
            clientSecretSecretRef:
              name: azure-service-principal
              key: client_secret
            subscriptionID: 00000000-0000-0000-0000-000000000000
            tenantID: 00000000-0000-0000-0000-000000000000
            resourceGroupName: home-lab
            hostedZoneName: nnstt1.dev
            environment: AzurePublicCloud

このマニフェストの spec.acme.solvers.dns01azureDNS から cloudflare に書き換えます。 Azure DNS の場合はテナント ID だったりリソースグループ名だったりを指定する必要があったのですが、Cloudflare では API トークンを格納した Secret リソースを指定するだけでよいです。

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

とてもスッキリしましたね! 最後はこのマニフェストをデプロイするだけで、Cloudflare を使って cert-manager の DNS Validation が動いて証明書を発行してもらえるようになります。