Skip to content
Published on

Ingress をコードで管理する — Helm、Kustomize、GitOps

Authors

はじめに

Ingress 設定は、一度作れば終わりという静的な対象ではありません。TLS 証明書を更新し、タイムアウトやボディサイズ上限を調整し、レートリミットのアノテーションを追加し、新しいサービスが生まれるたびにパスを増やします。そしてこれらの変更はすべて、dev、staging、production という複数の環境で微妙に異なる値で適用されなければなりません。誰かが kubectl edit ingress で production を直接直した瞬間、その変更は何の記録もなく消える運命に置かれます。

だからこそ Ingress は、コードで管理されるべき代表的な対象です。コントローラー自体のデプロイ(どのバージョンを、どのリソースで、どの ConfigMap で)と、個々の Ingress マニフェスト(ホスト、パス、アノテーション、TLS)の両方を Git に置き、宣言的にクラスターへ適用すべきです。これが GitOps の核心的価値です。Git が単一の信頼できる情報源(single source of truth)となり、クラスターの状態は常に Git へ向かって収束します。

本記事では Ingress の構成管理を四つの段階に分けて扱います。第一に Helm values でコントローラーを環境ごとにデプロイすること。第二に Kustomize で Ingress マニフェストをテンプレート化し環境ごとの patch を重ねること。第三に Argo CD と Flux でデリバリーとドリフト検知を自動化すること。第四に Kyverno のようなポリシーエンジンで必須アノテーションと IngressClass を強制するゲートを立てること。2026 年現在 Ingress API は凍結状態で Gateway API が後継標準ですが、ここで扱う構成管理パターンは両 API に同じく適用される点も押さえておきます。

構成管理が必要なもの

Ingress 領域で Git に入れるべき対象は、大きく二つの層に分かれます。

対象変更頻度管理ツール
プラットフォームコントローラーのデプロイ、ConfigMap、IngressClass低い(四半期ごと)Helm values
アプリケーションIngress マニフェスト(ホスト/パス/TLS/アノテーション)高い(常時)Kustomize/Helm

この二つを分離することが重要です。コントローラーはプラットフォームチームが慎重にバージョンとセキュリティパッチを管理し、Ingress マニフェストは各アプリケーションチームが自分のサービスに合わせて頻繁に変えます。両層を一つの巨大なチャートに混ぜると、変更の影響範囲が広がりレビューが難しくなります。

  Git リポジトリ
  ├── platform/ingress-controller/   (Helm values, 環境ごと)
  │     ├── values-base.yaml
  │     ├── values-dev.yaml
  │     └── values-prod.yaml
  └── apps/<service>/ingress/        (Kustomize base + overlays)
        ├── base/
        └── overlays/{dev,staging,prod}/

Helm でコントローラーをデプロイ

ingress-nginx、Traefik などの主要コントローラーはすべて公式 Helm チャートを提供します。要点は、環境ごとに異なる values を置きつつ、共通部分を base values にまとめることです。

base values の例です。

controller:
  replicaCount: 2
  ingressClassResource:
    name: nginx
    default: false
  config:
    use-forwarded-headers: "true"
    proxy-body-size: "16m"
  resources:
    requests:
      cpu: 100m
      memory: 128Mi

production オーバーレイではレプリカとリソースを増やし、外部公開の方式を明示します。

controller:
  replicaCount: 4
  service:
    type: LoadBalancer
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
  resources:
    requests:
      cpu: 500m
      memory: 512Mi
  config:
    proxy-body-size: "64m"

デプロイは二つの values ファイルを重ねて適用します。

helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  -f values-base.yaml -f values-prod.yaml

こうすれば production と dev の違いが小さなオーバーレイファイル一つに明確に表れ、「なぜこの環境だけボディサイズ上限が違うのか」といった問いに Git diff で即座に答えられます。

Kustomize で Ingress マニフェストをテンプレート化

個々の Ingress マニフェストは Kustomize がよく合います。共通構造を base に置き、環境ごとにホストや TLS、アノテーションだけを patch で変える方式です。

base の Ingress です。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
  ingressClassName: nginx
  rules:
    - host: web.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

base の kustomization です。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ingress.yaml

production オーバーレイではホストと TLS を patch で変えます。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
patches:
  - target:
      kind: Ingress
      name: web
    patch: |-
      - op: replace
        path: /spec/rules/0/host
        value: web.prod.example.com
      - op: add
        path: /spec/tls
        value:
          - hosts:
              - web.prod.example.com
            secretName: web-prod-tls

共通アノテーションをすべての Ingress に一括追加したい場合は commonAnnotations を使えます。

commonAnnotations:
  team: platform
  managed-by: kustomize

このパターンの利点は、base がただ一つの真実であることです。すべての環境が base を共有するので、共通ロジックを一か所で直せば全環境に一貫して反映されます。

Argo CD でデリバリーとドリフト検知

いよいよ Git に整理されたマニフェストをクラスターへ自動適用する番です。Argo CD は Application リソースで「この Git パスをこのクラスター/ネームスペースに同期せよ」を宣言します。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: web-ingress-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example/infra.git
    targetRevision: main
    path: apps/web/ingress/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: web
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

要点は selfHeal: trueprune: true です。誰かが kubectl edit でクラスターの Ingress を直接変えると、Argo CD はこれをドリフトとして検知し、Git の状態に戻します。Git からリソースを消すとクラスターからも削除します。これにより「クラスター = Git」の不変式が維持されます。

Flux を使うなら Kustomization リソースで同じことをします。

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: web-ingress
  namespace: flux-system
spec:
  interval: 5m
  path: ./apps/web/ingress/overlays/prod
  prune: true
  sourceRef:
    kind: GitRepository
    name: infra

interval: 5m ごとに Git とクラスターを比較し、差があれば調整します。ドリフト検知の本質は同じです。

ポリシーゲート — Kyverno で強制

GitOps だけでは「誤った Ingress が Git にマージされること」自体は防げません。そこでクラスター進入の直前にポリシーゲートを置きます。Kyverno はアドミッション Webhook として動作し、ルールに違反するリソースを拒否または自動変形します。

第一に、すべての Ingress に IngressClass を強制するポリシーです。クラスがないとどのコントローラーが処理するか曖昧になるため、これを防ぎます。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-ingress-class
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-ingress-class
      match:
        any:
          - resources:
              kinds:
                - Ingress
      validate:
        message: "すべての Ingress は spec.ingressClassName を指定する必要があります。"
        pattern:
          spec:
            ingressClassName: "?*"

第二に、セキュリティ上必須のアノテーション(例: SSL リダイレクトの強制)を要求するポリシーです。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-ssl-redirect
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-ssl-redirect
      match:
        any:
          - resources:
              kinds:
                - Ingress
      validate:
        message: "Ingress には force-ssl-redirect アノテーションが必要です。"
        pattern:
          metadata:
            annotations:
              nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

こうしたポリシーを置けば、誤って平文 HTTP のみを公開したり、クラスのない Ingress を作ったりすることをクラスターレベルで遮断できます。GitOps(望ましい状態の保証)とポリシーエンジン(許容可能な状態の強制)は相互補完的です。

マルチ環境・マルチクラスター

規模が大きくなると環境が複数あり、クラスターも複数あります。Argo CD は ApplicationSet で同じパターンを複数の対象に広げます。たとえばクラスター一覧を generator に置き、各クラスターに同じ Ingress オーバーレイを自動デプロイします。このときも base は共有し、クラスターごとの違いだけをオーバーレイで表現する原則は同じです。

シークレット(TLS)の管理

TLS 証明書は構成管理の厄介な部分です。平文の Secret をそのまま Git に入れることはできないからです。実務では二つのアプローチが標準です。

  • cert-manager による自動発行: Ingress アノテーションに issuer を指定すると、cert-manager が ACME(Let's Encrypt など)で証明書を発行・更新します。証明書自体は Git になく、宣言だけを Git に置きます。
  • 暗号化シークレット: 外部シークレットマネージャーや封印された(Sealed)シークレットで暗号化した形のみを Git に置き、クラスターで復号します。

cert-manager 方式の Ingress アノテーション例です。

metadata:
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"

こうすれば証明書の更新まで宣言的に管理され、期限間近のアラートに手動で対応していた作業がなくなります。

レビュープロセス

GitOps の真の価値は、変更が Pull Request を経るところから生まれます。Ingress 変更に対する推奨レビューフローです。

1. ブランチでオーバーレイ patch を修正
2. CI で kustomize build + kubeconform によりスキーマ検証
3. CI で Kyverno CLI によりポリシーを事前チェック
4. Argo CD diff を PR コメントに自動投稿(実適用前に変更をプレビュー)
5. 同僚レビュー後にマージ -> Argo CD が自動同期

要点は「適用前に見る」です。Argo CD diff を PR に貼れば、マージ結果がクラスターにどんな変化を起こすかを人が目で確認したうえで承認できます。

チェックリスト

[ ] コントローラー(プラットフォーム)と Ingress(アプリ)の構成管理を分離したか
[ ] Helm base values + 環境オーバーレイでコントローラーをデプロイするか
[ ] Kustomize base 一つをすべての環境が共有するか
[ ] Argo CD/Flux で selfHeal と prune を有効にしたか
[ ] Kyverno で IngressClass と必須アノテーションを強制するか
[ ] TLS は cert-manager または暗号化シークレットで管理するか
[ ] CI で build/スキーマ/ポリシー検証を経るか
[ ] PR に Argo CD diff を投稿し適用前に検討するか

おわりに

Ingress をコードで管理するとは、単に YAML を Git に入れることではありません。コントローラーとマニフェストを二つの層に分離し、Helm と Kustomize で環境差を明示的に表現し、Argo CD や Flux でクラスターを Git に収束させ、Kyverno で安全ガードレールを立てる、一貫したシステムを作ることです。

このシステムが定着すれば、「production Ingress を誰が、いつ、なぜ変えたのか」という問いに Git 履歴で答えられ、誤った変更はポリシーゲートとドリフト検知が防いでくれます。そしてこのパターンは Ingress API でも後継標準の Gateway API でも同じく適用されるため、今後 Gateway API へ移行しても構成管理体系はそのまま引き継げます。

参考資料