Skip to content

필사 모드: Managing Ingress as Code — Helm, Kustomize, and GitOps

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Introduction

Ingress configuration is not a static thing you set up once and forget. You renew TLS certificates, tune timeouts and body-size limits, add rate-limit annotations, and add a path every time a new service appears. And all of these changes must apply with subtly different values across dev, staging, and production. The moment someone fixes production directly with `kubectl edit ingress`, that change is destined to vanish without any record.

That is exactly why Ingress is a prime candidate for being managed as code. Both the controller deployment itself (which version, with what resources, with which ConfigMap) and the individual Ingress manifests (hosts, paths, annotations, TLS) should live in Git and be applied declaratively to the cluster. This is the core value of GitOps: Git becomes the single source of truth, and cluster state always converges toward Git.

This article breaks Ingress version control into four stages. First, deploying controllers per environment with Helm values. Second, templating Ingress manifests with Kustomize and layering per-environment patches. Third, automating delivery and drift detection with Argo CD and Flux. Fourth, standing up gates with a policy engine like Kyverno to enforce required annotations and IngressClass. As of 2026 the `Ingress` API is frozen and the Gateway API is the successor standard, but it is worth noting that the version-control patterns covered here apply equally to both APIs.

What needs version control

In the Ingress space, what belongs in Git falls into two broad layers.

| Layer | Subject | Change frequency | Tooling |

| --- | --- | --- | --- |

| Platform | Controller deployment, ConfigMap, IngressClass | Low (quarterly) | Helm values |

| Application | Ingress manifests (host/path/TLS/annotations) | High (constant) | Kustomize/Helm |

Separating these two matters. The platform team carefully manages controller versions and security patches, while each application team frequently changes its own Ingress manifests to fit its service. Mixing both layers into one giant chart widens the blast radius of changes and makes review harder.

Git repository

├── platform/ingress-controller/ (Helm values, per environment)

│ ├── values-base.yaml

│ ├── values-dev.yaml

│ └── values-prod.yaml

└── apps/<service>/ingress/ (Kustomize base + overlays)

├── base/

└── overlays/{dev,staging,prod}/

Deploying the controller with Helm

The major controllers, ingress-nginx, Traefik, and others, all provide official Helm charts. The key is to keep different values per environment while collecting the common parts in base values.

A base values example.

controller:

replicaCount: 2

ingressClassResource:

name: nginx

default: false

config:

use-forwarded-headers: "true"

proxy-body-size: "16m"

resources:

requests:

cpu: 100m

memory: 128Mi

The production overlay scales up replicas and resources and spells out the external exposure method.

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"

Deploy by layering the two values files.

helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \

--namespace ingress-nginx --create-namespace \

-f values-base.yaml -f values-prod.yaml

This way the difference between production and dev is clearly visible in a small overlay file, so a question like "why does only this environment have a different body-size limit?" can be answered immediately with a Git diff.

Templating Ingress manifests with Kustomize

Individual Ingress manifests are a good fit for Kustomize. You keep the common structure in base and change only the host, TLS, or annotations per environment with patches.

The 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

The base kustomization.

apiVersion: kustomize.config.k8s.io/v1beta1

kind: Kustomization

resources:

- ingress.yaml

The production overlay changes host and TLS with a 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

If you want to add common annotations to all Ingresses at once, you can use `commonAnnotations`.

commonAnnotations:

team: platform

managed-by: kustomize

The advantage of this pattern is that base is the single source of truth. Since all environments share base, fixing common logic in one place reflects consistently across every environment.

Delivery and drift detection with Argo CD

Now it is time to automatically apply the Git-organized manifests to the cluster. Argo CD declares "sync this Git path to this cluster/namespace" with an Application resource.

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

The key is `selfHeal: true` and `prune: true`. If someone changes the cluster's Ingress directly with `kubectl edit`, Argo CD detects it as drift and reverts to the Git state. If you delete a resource in Git, it is removed from the cluster. This maintains the "cluster = Git" invariant.

If you use Flux, you do the same with a Kustomization resource.

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

Every `interval: 5m` it compares Git and the cluster and reconciles any difference. The essence of drift detection is the same.

Policy gates — enforcing with Kyverno

GitOps alone does not prevent "a bad Ingress being merged into Git" in the first place. So you place a policy gate right before entry into the cluster. Kyverno works as an admission webhook, rejecting or mutating resources that violate rules.

First, a policy enforcing an IngressClass on every Ingress. Without a class it is ambiguous which controller handles it, so this is blocked.

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: "Every Ingress must specify spec.ingressClassName."

pattern:

spec:

ingressClassName: "?*"

Second, a policy requiring a security-mandatory annotation (for example, forcing SSL redirect).

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 requires the force-ssl-redirect annotation."

pattern:

metadata:

annotations:

nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

With such policies, you can block at the cluster level the accidental creation of an Ingress that exposes only plaintext HTTP or has no class. GitOps (guaranteeing the desired state) and a policy engine (enforcing the allowed state) are complementary.

Multi-environment and multi-cluster

As scale grows there are many environments and many clusters. Argo CD spreads the same pattern across multiple targets with ApplicationSet. For example, you put a cluster list in a generator and automatically deploy the same Ingress overlay to each cluster. The principle is the same here too: share base and express only per-cluster differences as overlays.

Secret (TLS) management

TLS certificates are the tricky part of version control, because you cannot put a plaintext Secret directly in Git. In practice two approaches are standard.

- **Automatic issuance with cert-manager**: specify an issuer in the Ingress annotation, and cert-manager issues and renews certificates via ACME (Let's Encrypt and so on). The certificate itself is not in Git; only the declaration is.

- **Encrypted secrets**: keep only an encrypted form in Git via an external secret manager or Sealed Secrets, and decrypt in the cluster.

An Ingress annotation example for the cert-manager approach.

metadata:

annotations:

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

This way even certificate renewal is managed declaratively, eliminating the manual scramble in response to expiry-imminent alerts.

Review process

The real value of GitOps comes from changes going through a Pull Request. A recommended review flow for Ingress changes.

1. Edit the overlay patch on a branch

2. Validate schema in CI with kustomize build + kubeconform

3. Pre-check policies in CI with the Kyverno CLI

4. Auto-post the Argo CD diff as a PR comment (preview the change before apply)

5. Merge after peer review -> Argo CD syncs automatically

The key is "see before you apply." Attaching the Argo CD diff to the PR lets a human visually confirm what change the merge will cause in the cluster before approving.

Checklist

[ ] Did you separate controller (platform) and Ingress (app) version control

[ ] Do you deploy the controller with Helm base values + environment overlays

[ ] Do all environments share a single Kustomize base

[ ] Did you enable selfHeal and prune in Argo CD/Flux

[ ] Do you enforce IngressClass and required annotations with Kyverno

[ ] Is TLS managed with cert-manager or encrypted secrets

[ ] Does CI run build/schema/policy validation

[ ] Do you post the Argo CD diff on the PR to review before apply

Closing thoughts

Managing Ingress as code is not merely putting YAML in Git. It is building a coherent system: separating the controller and manifests into two layers, expressing environment differences explicitly with Helm and Kustomize, converging the cluster to Git with Argo CD or Flux, and standing up safety guardrails with Kyverno.

Once this system is in place, the question "who changed the production Ingress, when, and why" can be answered with Git history, and bad changes are blocked by policy gates and drift detection. And since this pattern applies equally to the `Ingress` API and the successor standard Gateway API, you can carry the version-control framework forward even if you later migrate to the Gateway API.

References

- Kubernetes Ingress official docs: https://kubernetes.io/docs/concepts/services-networking/ingress/

- ingress-nginx Helm install guide: https://kubernetes.github.io/ingress-nginx/deploy/

- Helm official docs: https://helm.sh/docs/

- Kustomize official docs: https://kubectl.docs.kubernetes.io/references/kustomize/

- Argo CD official docs: https://argo-cd.readthedocs.io/

- Flux official docs: https://fluxcd.io/flux/

- Kyverno official docs: https://kyverno.io/docs/

- cert-manager official docs: https://cert-manager.io/docs/

- Gateway API official docs: https://gateway-api.sigs.k8s.io/

현재 단락 (1/209)

Ingress configuration is not a static thing you set up once and forget. You renew TLS certificates, ...

작성 글자: 0원문 글자: 9,828작성 단락: 0/209