内部ドメイン証明書をKustomizeで完全自動化:SmallstepのStep-Issuerと動的caBundle取得の革新的な運用
2025-07-31
Post Image

はじめに

この記事は、以下のような課題を持つKubernetes運用者を対象にしています:

  • 中規模以上のKubernetesクラスタを運用しており、*.svc.cluster.local のような内部ドメイン証明書の管理に手間を感じている方
  • cert-manager や step-certificates を導入しているが、GitOps(ArgoCDやFluxなど)にうまく統合できていない方
  • Helmチャートを直接編集したくないが、Kustomizeで柔軟にカスタマイズしたい方
  • ワイルドカード証明書の自動発行とマルチテナント対応を効率よく実現したい方

Kubernetesクラスタ内のサービス間通信では、*.svc.cluster.local のような内部ドメイン証明書が欠かせません。しかし、証明書の発行・更新を手動で管理するのは運用負荷が高く、エラーの原因にもなります。

前回の記事では、Smallstepの step-certificates Helmチャートを Kustomize でカスタマイズし、ACMEプロビジョナーを追加する方法を紹介しました。今回は、Smallstep の step-issuer Helmチャート(v1.9.9)を活用し、cert-manager と連携して内部ドメイン証明書を完全自動化する方法を紹介します。

特に、動的に caBundle を取得するポストインストールジョブを自作することで、StepIssuerとワイルドカード証明書を一括で自動生成。この手法により、内部ドメイン証明書の運用を効率化し、GitOps に最適な構成が実現できます。

この記事では、なぜこのアプローチが革新的なのか、どのような工夫で課題を解決しているのかを、実際のコードとともに詳しく解説します。

やりたいこと

以下の目標を効率的に達成します:

  • step-issuer HelmチャートをKustomizeでデプロイし、step-certificatesと連携。
  • 自作ポストインストールジョブで、caBundleとkidを動的に取得し、StepIssuerと*.svc.cluster.local証明書を自動作成。
  • 証明書をreplicator/reflectorで他名前空間に配布し、マルチテナント環境に対応。
  • RBACでセキュリティを確保し、GitOpsで宣言的運用を実現。

動的caBundle取得

内部ドメイン証明書を発行するには、StepIssuerにCAの証明書(caBundle)とプロビジョナーのキーID(kid)を設定する必要があります。これらの値はstep-certificatesのデプロイ時に動的に生成され、ConfigMapに格納されます。手動でこれらを取得してマニフェストに記述するのは、運用負荷が高く、GitOpsの原則(再現性・自動化)に反します。この手法のすごいところは、ポストインストールジョブが以下のプロセスを自動化すること:

  • 動的取得: ConfigMap(step-certificates-certs)からcaBundleを、step-certificates-configからkidをリアルタイム取得。
  • エラーハンドリング: 取得失敗時に詳細なエラーとデバッグ情報を出力。
  • 自動適用: 取得した値をStepIssuerとCertificateマニフェストに埋め込み、kubectlで適用。

このアプローチにより、手動設定ゼロで内部ドメイン証明書を管理でき、クラスタのスケールアップやアップグレード時も安定運用が可能です。

コードと工夫の詳細解説

  1. kustomization.yaml:内部ドメイン証明書の基盤
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: smallstep
helmGlobals:
  chartHome: ../../../../base/smallstep/step-issuer/charts
helmCharts:
  - name: step-issuer
    repo: https://smallstep.github.io/helm-charts/
    releaseName: step-issuer
    namespace: smallstep
    version: 1.9.9
    valuesFile: values.yaml
    valuesMerge: override
    valuesInline:
      deployment:
        args:
          enableLeaderElection: false
          disableApprovalCheck: false
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app.kubernetes.io/name: step-certificates
              topologyKey: "kubernetes.io/hostname"
resources:
  - post-install-job.yaml
  - rbac-stepissuer.yaml

工夫:

  • Pod Affinity: step-issuerとstep-certificatesを同一ノードに配置し、内部ドメイン通信の安定性を確保。
  • 宣言的統合: ポストインストールジョブとRBACをKustomizeで一元管理し、GitOpsに適合。
  • バージョン管理: チャートバージョン(v1.9.9)を固定し、内部ドメイン証明書の安定性を保証。

メリット:

  • Helmチャートをフォークせずにカスタマイズ可能。
  • 内部ドメイン証明書のデプロイが単一マニフェストで完結。
  1. post-install-job.yaml:動的caBundle取得の核心
apiVersion: batch/v1
kind: Job
metadata:
  name: create-stepissuer
  namespace: smallstep
  annotations:
    "helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook-weight": "20"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    spec:
      serviceAccountName: step-certificates-config
      containers:
        - name: create-stepissuer
          image: bitnami/kubectl:latest
          command:
            - /bin/sh
            - -c
            - |
              echo "Waiting for step-certificates-0 pod to be ready..."
              TIMEOUT=300
              INTERVAL=10
              ELAPSED=0
              while [ $ELAPSED -lt $TIMEOUT ]; do
                if kubectl get pod -n smallstep step-certificates-0 -o jsonpath="{.status.conditions[?(@.type==\"Ready\")].status}" | grep -q "True"; then
                  echo "step-certificates-0 pod is ready!"
                  break
                fi
                echo "Waiting... ($ELAPSED/$TIMEOUT seconds)"
                sleep $INTERVAL
                ELAPSED=$((ELAPSED + INTERVAL))
                if [ $ELAPSED -ge $TIMEOUT ]; then
                  echo "ERROR: Timeout waiting for step-certificates-0"
                  kubectl describe pod -n smallstep step-certificates-0
                  exit 1
                fi
              done
              CA_BUNDLE_RAW=$(kubectl get configmap step-certificates-certs -n smallstep -o jsonpath='{.data.root_ca\.crt}')
              STEP_CA_BUNDLE=$(echo "$CA_BUNDLE_RAW" | base64 -w 0)
              if [ -z "$STEP_CA_BUNDLE" ]; then
                echo "ERROR: Failed to fetch caBundle"
                exit 1
              fi
              CA_JSON=$(kubectl get configmap step-certificates-config -n smallstep -o jsonpath='{.data.ca\.json}')
              STEP_KID=$(echo "$CA_JSON" | grep -oP '"kid":\s*"\K[^"]+' | head -n 1)
              if [ -z "$STEP_KID" ]; then
                echo "ERROR: Failed to fetch kid"
                exit 1
              fi
              cat <<EOF | kubectl apply -f -
              apiVersion: certmanager.step.sm/v1beta1
              kind: StepIssuer
              metadata:
                name: step-issuer
                namespace: smallstep
              spec:
                url: https://step-certificates.smallstep.svc.cluster.local
                caBundle: "$STEP_CA_BUNDLE"
                provisioner:
                  name: step-issuer
                  kid: "$STEP_KID"
                  passwordRef:
                    name: step-certificates-provisioner-password
                    key: password
              EOF
              cat <<EOF | kubectl apply -f -
              apiVersion: cert-manager.io/v1
              kind: Certificate
              metadata:
                name: wildcard-svc-cluster-local
                namespace: smallstep
              spec:
                secretName: wildcard-svc-cluster-local-tls
                privateKey:
                  rotationPolicy: Always
                issuerRef:
                  group: certmanager.step.sm
                  kind: StepIssuer
                  name: step-issuer
                secretTemplate:
                  annotations:
                    replicator.v1.mittwald.de/replication-allowed: "true"
                    replicator.v1.mittwald.de/replication-allowed-namespaces: "*"
                    reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
                    reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "*"
                    reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
                    reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "*"
                dnsNames:
                - "*.svc.cluster.local"
                duration: 24h
                renewBefore: 8h
              EOF
      restartPolicy: OnFailure

工夫:

  • 動的caBundle取得: ConfigMapからcaBundleをリアルタイム取得し、Base64エンコードでStepIssuerに適用。
  • 待機ロジック: step-certificates-0ポッドのReady状態を最大300秒待機し、安定性を確保。
  • ワイルドカード証明書: *.svc.cluster.localを対象に証明書を自動発行し、replicator/reflectorでクラスタ全体に配布。
  • エラーハンドリング: 取得失敗時に詳細なエラーとデバッグ情報を出力

メリット:

  • 手動設定不要: caBundleとkidの手動取得・設定を排除。
  • マルチテナント対応: ワイルドカード証明書を他名前空間に自動配布し、サービス間通信を簡素化。
  • デバッグ容易: タイムアウトや取得エラー時に詳細なログを提供。
  1. rbac-stepissuer.yaml:安全な権限管理
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: step-certificates-stepissuer
rules:
  - apiGroups: ["certmanager.step.sm"]
    resources: ["stepissuers"]
    verbs: ["create", "update", "get", "list"]
  - apiGroups: ["cert-manager.io"]
    resources: ["certificates"]
    verbs: ["create", "update", "get", "list"]
  - apiGroups: [""]
    resources: ["pods", "configmaps"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: step-certificates-stepissuer-binding
subjects:
  - kind: ServiceAccount
    name: step-certificates-config
    namespace: smallstep
roleRef:
  kind: ClusterRole
  name: step-certificates-stepissuer
  apiGroup: rbac.authorization.k8s.io

工夫:

  • 最小権限: ポストインストールジョブに必要な操作(stepissuers/certificatesの作成・取得、ポッド/ConfigMapの参照)のみを許可。
  • サービスアカウント連携: step-certificates-configに権限を紐づけ、セキュリティを確保。

メリット:

  • セキュリティリスクを最小化しつつ、ジョブの実行を安全に。
  • 宣言的RBACで、GitOpsワークフローに適合。

項目メリット工夫
動的caBundle取得手動設定を不要にし、GitOpsでの再現性を確保。クラスタ再構築時も自動対応。ConfigMapからcaBundleとkidをkubectlで動的に取得し、マニフェストに埋め込み
ワイルドカード証明書内部ドメイン(*.svc.cluster.local)のサービス間通信を一括保護。マルチテナント環境でスケーラブル。replicator/reflectorのアノテーションで証明書を他名前空間に自動配布。
待機ロジックstep-certificatesの準備完了を確実に待機し、デプロイの安定性を向上。300秒のタイムアウトと10秒間隔のポッド状態チェック。エラー時に詳細ログ出力。
GitOps対応全てをマニフェストで管理し、CI/CDパイプラインで自動デプロイ可能。KustomizeでHelmチャートと自作リソースを統合。バージョン固定で安定性確保。

運用シナリオでの価値:

  • マルチテナントクラスタ: 開発/テスト/本番環境が混在するクラスタで、ワイルドカード証明書を全名前空間に配布。サービス間通信のTLS設定が一瞬で完了。
  • CI/CD統合: ArgoCDやFluxでマニフェストを適用するだけで、証明書発行が自動化。手動操作ゼロで運用効率アップ。
  • 障害復旧: クラスタ再構築時も、caBundleの動的取得により設定ミスを防止。復旧時間を短縮。

注意点と改善案

  • バージョン互換性: step-issuer(v1.9.9)やConfigMap構造の変更に注意。チャートバージョンを固定し、変更ログを監視。
  • タイムアウト調整: クラスタ規模に応じてTIMEOUT=300を調整(例:大規模環境では600秒)。
  • 軽量イメージ: bitnami/kubectlの代わりに、専用スクリプト用カスタムイメージを作成し、セキュリティと効率を向上。
  • ログ構造化: デバッグログをJSON形式にすると、監視ツール(例:Loki)との統合が容易。

まとめ

この記事では、Smallstepのstep-issuerをKustomizeでデプロイし、動的caBundle取得による内部ドメイン証明書の自動発行を実現する方法を紹介しました。ポストインストールジョブによる自動化は、手動設定の煩雑さを排除し、GitOpsに最適な運用を可能にします。特に、*.svc.cluster.localのようなワイルドカード証明書をクラスタ全体に配布することで、マルチテナント環境でのサービス間通信を効率化できます。この革新的な手法をあなたのKubernetes環境で試し、内部ドメイン証明書管理を次のレベルに引き上げてみませんか?コードはGitHubで公開可能、質問があればコメント欄でどうぞ!

内部ドメイン証明書をKustomizeで完全自動化:SmallstepのStep-Issuerと動的caBundle取得の革新的な運用
https://notes.midnightstops.com/posts/25/
作者
Author
公開日
2025-07-31