📑

AKS クラスターでcert-managerからApplication Gatewayに証明書を構成してみる

2024/12/05に公開

前置き

これはGMOペパボ エンジニア Advent Calendar 2024の5日目です。

背景

7ヶ月ぐらい前に初挑戦のSREチームにジョイン(って言いたいだけ)しました。
いかんせんAzureのことはそこそこオタクでもkubernetesのことは何も分からんということで環境構築とかに脳のリソースを割かずにkubernetesについて学べないかと思った次第で第一回目にAKSでcert-manager使ってみることにチャレンジしてみました。

それと折角なので雰囲気で使っていた節のある用語たちを整理しつつな感じで。
マサカリは手渡しぐらいの勢いだと助かります。

用語

cert-managerとはTLS証明書を定期的に更新してくれるすごいやつです。
これで証明書期限切れとはおさらばですな。
cert-manager

Helmとは構成済みのkubernetesリソースをパッケージとして扱える便利なやつです。
cert-managerもHelmを使えば簡単にインストールできます。
また、このまとめたパッケージのことをHelmチャートと呼ぶのですがチャート(海図)とはkubernetes(操舵手)とかけていてなかなかオシャレですな。
the-purpose-of-helm

ACME(アクミーと読みます)とはCA(認証局)に対してサーバー証明書の発行者がするあれこれ(CSRの作成・送信、ドメインの所有者確認、証明書設定)を自動化するプロトコルです。cert-managerとは別にACMEクライアントとして有名なのはcertbotがあります。
ACMEについて

ACMEチャレンジとはACMEプロトコルにおいてドメインの所有者確認をすることを指します。
ACMEチャレンジには2種類あり、HTTP-01チャレンジとDNS-01チャレンジがあります。
HTTP-01チャレンジだとサーバーに特定のHTTPリソースをプロビジョニングできることを持って所有者であることを証明します。
DNS-01チャレンジだとTXTレコードに指定の文字列を埋め込んで所有者であることを証明します。
これは僕の勝手な感覚的な話ですがHTTP-01チャレンジのがすぐ反映する分良いのではないかなという印象です。(DNSレコードのTTLを1secにしたら気にしなくても良いのかもだけど)とはいえDNS-01チャレンジだとTXTレコードを登録する手間はかかるのでやっぱりHTTP-01チャレンジが良さそうですね。

Which ACME Challenge Type Should I Use? HTTP-01 or DNS-01?
Automatic Certificate Management Environment (ACME)

前提

Kubernetes バージョン:1.29.9 での検証です。(2024-12-04時点)
test-aks というリソースグループ名でコマンド実行します。適宜、置き換えてください。
環境はCloud ShellのBashで行います。
kubectl は入ってなければ入れてください。 kubectl はaliasで k としてます。(公式のおすすめ設定)
Kubectl autocomplete
helm はCloud Shellでならデフォルトで入っています。必要であれば最新版をインストールしてください。

Helmインストール(必要なら)

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

https://helm.sh/docs/intro/install/#from-script

AKSクラスターのデプロイ

サービスプリンシパルを作成します。

強い権限で作成するため不要になったら削除してください。
subscriptionID はご自身のSubscriptionIDに変えておいてください。

resourceGroupName="test-aks"
location="japaneast"
deploymentName="ingress-appgw"
subscriptionId="xxxxx-xxxxx-xxxxx-xxxxx"

az ad sp create-for-rbac --role Contributor --scopes /subscriptions/${subscriptionId} -o json > auth.json

AKSクラスターをデプロイします。

wget https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/deploy/azuredeploy.json -O template.json

appId=$(jq -r ".appId" auth.json)
password=$(jq -r ".password" auth.json)
objectId=$(az ad sp show --id $appId --query "id" -o tsv)
cat <<EOF > parameters.json
{
  "aksServicePrincipalAppId": { "value": "$appId" },
  "aksServicePrincipalClientSecret": { "value": "$password" },
  "aksServicePrincipalObjectId": { "value": "$objectId" },
  "aksEnableRBAC": { "value": false },
  "aksAgentVMSize": { "value": "Standard_D2s_v3" }
}
EOF

az group create -n $resourceGroupName -l $location

az deployment group create -g $resourceGroupName -n $deploymentName --template-file template.json --parameters parameters.json

az deployment group show -g $resourceGroupName -n $deploymentName --query "properties.outputs" -o json > deployment-outputs.json

自身のクラスターの資格情報を取得して作成したAKSクラスターに切り替えます。

aksClusterName=$(jq -r ".aksClusterName.value" deployment-outputs.json)

az aks get-credentials --resource-group $resourceGroupName --name $aksClusterName
k config set-context $aksClusterName
k config current-context

Microsoft Entra ポッド IDのリソースをデプロイします。

端的にマネージドIDがAKS上から使えるようです。(詳しくはない)

k create -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml

Azure Kubernetes Service で Microsoft Entra ポッドマネージド ID を使用する (プレビュー)

AGICパッケージをインストールします。

applicationGatewayName=$(jq -r ".applicationGatewayName.value" deployment-outputs.json)
identityClientId=$(jq -r ".identityClientId.value" deployment-outputs.json)
identityResourceId=$(jq -r ".identityResourceId.value" deployment-outputs.json)

wget https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/sample-helm-config.yaml -O helm-config.yaml
sed -i "s|<subscriptionId>|${subscriptionId}|g" helm-config.yaml
sed -i "s|<resourceGroupName>|${resourceGroupName}|g" helm-config.yaml
sed -i "s|<applicationGatewayName>|${applicationGatewayName}|g" helm-config.yaml
sed -i "s|<identityResourceId>|${identityResourceId}|g" helm-config.yaml
sed -i "s|<identityClientId>|${identityClientId}|g" helm-config.yaml

helm install agic-controller oci://mcr.microsoft.com/azure-application-gateway/charts/ingress-azure --version 1.7.5 -f helm-config.yaml

新しい Application Gateway デプロイを使用して AGIC をインストールする

cert-managerインストール

helm repo add jetstack https://charts.jetstack.io --force-update
helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.16.2 \
  --set crds.enabled=true
k label namespace cert-manager cert-manager.io/disable-validation=true

installing-cert-manager

テスト環境の作成

DNSレコードの登録します。

先にApplicationGatewayからフロントエンドIPを取得して、ご自身の権威DNSサーバーにDNSレコードを登録しましょう。
※画像は僕のRoute53に登録するとこ

az network public-ip show \
  -g $resourceGroupName \
  -n $(az network public-ip list -g $resourceGroupName --query '[0].name' -o tsv) \
  --query 'ipAddress' -o tsv

cert-managerをデプロイします。

必ずLet's Encryptのために <YOUR.EMAIL@ADDRESS> をご自身の持つアドレスに置き換えてください。

k apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    email: <YOUR.EMAIL@ADDRESS>
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: example-issuer-account-key
    solvers:
      - http01:
          ingress:
            class: azure/application-gateway
EOF

AKS クラスター用の Application Gateway で Let's Encrypt 証明書を使用する
unknown field "spec.acme.solvers[0].http01.ingress.ingressclassName" #121159

テスト用のguestbookアプリケーションを作成します。

今回はIngressからトラフィックを受けたいのでNodeportを使わないようにします。

curl -fsSL -o guestbook-all-in-one.yaml https://raw.githubusercontent.com/kubernetes/examples/master/guestbook/all-in-one/guestbook-all-in-one.yaml
sed -i 's/gb-frontend:v4/gb-frontend:v5/g' guestbook-all-in-one.yaml
sed -i 's/type: NodePort/type: ClusterIP/g' guestbook-all-in-one.yaml
k apply -f guestbook-all-in-one.yaml

ゲストブック アプリケーションをデプロイする

Ingressをデプロイします。

corp.yukarism.net は僕の所有するドメイン名なので適宜ご自身の独自ドメインに書き換えてください。

k apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: guestbook-letsencrypt-staging
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  ingressClassName: azure-application-gateway
  tls:
  - hosts:
    - corp.yukarism.net
    secretName: guestbook-secret-name
  rules:
  - host: corp.yukarism.net
    http:
      paths:
      - backend:
          service:
            name: frontend
            port:
              number: 80
        path: / 
        pathType: Prefix
EOF

証明書がTrueになっていれば成功です!

k get cert guestbook-secret-name
NAME                    READY   SECRET                  AGE
guestbook-secret-name   True    guestbook-secret-name   4m13s

動作検証

サイトを確認してみても問題なくstaging用の証明書が発行されていることがわかります。

トラシュー

k8s内での通信確認
IPアドレスとかNamespaceとかは適宜変えてください。

k run curl --image=curlimages/curl -n default -it --rm -- curl 10.0.0.2

ingressでの通信が取れないなどあればAGICのlogを確認してみてください。
何らかの理由で弾かれているかもしれません。

k logs agic-controller-ingress-azure-xxxxxxxx

証明書発行がうまくいかなければcert-managerのPodのlogを確認してみてください。

k logs cert-manager-xxx-xxx -n cert-manager

ApplicationGatewayの構成がうまくいかないといった報告も見かけました。
今回はこのようなことにはなりませんでしたが、参考程度に。
Solving Cert-Manager and Azure Application Gateway Integration for AKS

完走した感想

久々に検証して張り切っちゃった
楽しい〜〜〜〜〜〜〜!
でも、あれだね。ドキュメントにあること上から垂れ流せばええんやろ!楽勝楽勝とか思ってた僕を殴りたいね。めっちゃ詰まったわ。

Discussion