Ingress와 Ingress Controller
외부 트래픽이 클러스터 안 Service로 들어오는 모델을 한 곳에 모으는 추상화입니다. Ingress 객체와 Ingress Controller의 두 층 분리, 호스트 · 경로 · pathType 기반 라우팅, TLS 종단과 cert-manager, IngressClass, 그리고 후속 표준인 Gateway API까지 한 사이클로 정리합니다.
9장 PV / PVC / StorageClass까지 다룬 것이 클러스터 안의 데이터가 살아남는 모델이었다면, 이번 챕터의 시점은 클러스터 바깥으로 옮겨갑니다. 외부 트래픽이 어떻게 클러스터 안의 Service로 들어오는가의 모델입니다. 5장 Service의 LoadBalancer가 외부 진입의 표준이지만, 한 클러스터에 외부로 노출해야 할 Service가 수십 개라면 그만큼 LoadBalancer를 띄우는 일이 비용 · 관리 양쪽에서 부담입니다. 이번 챕터는 그 부담을 한 곳에 모으는 객체 Ingress와, 그 매니페스트를 실제 라우팅으로 풀어 주는 Ingress Controller의 두 층을 한 사이클로 따라갑니다.
이번 챕터의 끝에서는 클라우드 LoadBalancer 한 개 뒤에 Ingress Controller 한 묶음, 그 뒤로 ClusterIP Service가 여러 개 갈라지는 운영의 흔한 진입 모양이 손에 들어옵니다. TLS 종단 · 도메인 라우팅 · 경로 라우팅이 한 곳에 모이는 표준 패턴입니다.
LoadBalancer 한 개로는 풀리지 않는 것 — 출발점 #
5장에서 잡은 LoadBalancer Service의 모양을 한 줄로 줄이면 다음과 같습니다 — type: LoadBalancer 한 줄이면 클라우드 제공자가 외부 LB를 자동으로 만들어 주고, 그 LB의 외부 IP로 한 Service의 백엔드 Pod 들에게 트래픽이 흘러갑니다. 단순하고 충분히 강력한 모델입니다.
문제는 Service가 한 개를 넘어설 때부터 시작됩니다. 운영 클러스터의 흔한 모양을 그려 보면 이렇습니다.
webService — 사용자 웹 페이지apiService — REST API 백엔드adminService — 운영자용 관리 페이지staticService — 정적 자산 전용
이 넷을 모두 외부에 노출하려고 각각 type: LoadBalancer를 붙이면 클라우드 LB가 네 개 생깁니다. AWS의 NLB · ALB 든 GCP의 LB 든 Azure의 LB 든 LB는 그 자체로 시간당 비용이 있고, 이 비용이 Service 개수에 비례해 누적됩니다. Service가 수십 개라면 LB 비용이 무시 못 할 수준이 됩니다.
비용만 부담은 아닙니다. 운영 측면의 부담도 같이 늘어납니다.
- 도메인을 LB 개수만큼 관리 — 외부에 보여 주는 도메인이 LB 마다 따로 잡히는 모양이라, DNS 레코드도 그 수만큼 관리해야 합니다.
- TLS 인증서를 LB 마다 따로 — 인증서 발급 · 갱신을 LB 단위로 들고 있어야 합니다.
- 호스트 · 경로 기반 라우팅이 안 됩니다 —
example.com/api는apiService로,example.com/static은staticService로 흘리고 싶어도, LoadBalancer Service 한 개는 한 Service의 백엔드만 바라봅니다. 이 라우팅을 풀려면 누가 한 단을 더 얹어야 합니다.
이 셋을 한 곳에 모아 풀어 주는 추상화가 Ingress입니다. 한 클라우드 LB 뒤에 한 Ingress Controller가 서고, 그 Controller가 도메인 · 경로를 보고 클러스터 안 여러 Service로 트래픽을 갈라 보냅니다. TLS 종단도 한 곳에서 처리합니다. 외부에서 보면 LB 하나로 보이지만, 안에서는 Ingress 매니페스트의 라우팅 규칙에 따라 분기되는 모양입니다.
Ingress의 두 층 — 객체와 컨트롤러 #
K8s에서 자주 함정이 되는 부분이 여기 있습니다. Ingress 매니페스트만 적는다고 트래픽이 흐르지 않는다는 점입니다. Ingress는 두 층이 같이 있어야 동작합니다.
| 층 | 무엇인가 | 누가 만드나 |
|---|---|---|
| Ingress 객체 | “어느 host의 어느 path를 어느 Service로 보낸다"는 의도를 적은 매니페스트 | 앱 개발자 또는 운영자 |
| Ingress Controller | 그 Ingress 객체를 읽어 실제로 트래픽을 라우팅하는 런타임 | 클러스터 관리자가 한 번 설치 |
Deployment · Service 같은 객체는 K8s 본체 (컨트롤플레인)가 자기 컨트롤러로 직접 처리하지만, Ingress는 다릅니다. Ingress 컨트롤러는 K8s 본체에 들어 있지 않고, 외부 컴포넌트로 클러스터에 별도 설치 해야 합니다. 컨트롤러가 없는 클러스터에 Ingress 매니페스트를 적용하면 객체는 만들어지지만 트래픽은 어디로도 흐르지 않습니다 — apply는 성공하는데 외부에서 응답이 안 오는 형태로 사고가 납니다.
머릿속 그림은 다음과 같이 두면 편합니다.
[외부 클라이언트]
│
▼
[ 클라우드 LB ] ←── Ingress Controller 가 노출하는 한 개의 Service (LoadBalancer)
│
▼
[ Ingress Controller Pod ] ←── nginx / Traefik / ALB 등
│
│ Ingress 객체의 라우팅 규칙을 읽어 분기
├──────────┬──────────┐
▼ ▼ ▼
[Service A] [Service B] [Service C]
│ │ │
Pods Pods Pods핵심은 — 외부 클라우드 LB는 한 개이고, 그 뒤에 선 Ingress Controller가 모든 Ingress 객체의 규칙을 합쳐 실제 라우팅을 수행한다는 점입니다. 클러스터에 Ingress 객체를 100개 적어도 클라우드 LB는 보통 한 개 그대로입니다.
Ingress Controller 종류 #
Ingress 객체의 매니페스트 형식은 K8s가 표준으로 정해 놓았지만, 그것을 어떻게 해석해 라우팅으로 풀어 낼지는 컨트롤러 구현체마다 다릅니다. 운영에서 자주 만나는 컨트롤러를 한 표로 정리하면 다음과 같습니다.
| 컨트롤러 | 어디서 흔한가 | 비고 |
|---|---|---|
| ingress-nginx | 가장 흔함. 온프레미스 · 로컬 · 매니지드 어디서나 | K8s 커뮤니티가 관리. nginx를 라우팅 엔진으로 사용 |
| Traefik | 컨테이너 친화 환경, 자동 인증서 통합이 강점 | 설정이 어노테이션 · CRD로 풍부 |
| HAProxy Ingress | 고성능 · 세밀한 튜닝이 필요한 환경 | HAProxy 기반 |
| AWS Load Balancer Controller | EKS에서 ALB · NLB로 노출할 때 | ALB가 직접 라우팅. 클러스터 안 nginx Pod가 별도로 없음 |
| GKE Ingress | GKE 기본 제공 | Google Cloud Load Balancer가 라우팅 |
| AKS Application Gateway Ingress Controller | AKS에서 Application Gateway로 | Azure가 라우팅 |
| Cilium | eBPF 기반 네트워킹, Gateway API에 강점 | CNI 겸용 |
크게 두 갈래로 나누어 보면 이해가 쉽습니다.
- 클러스터 안 Pod가 라우팅 — ingress-nginx, Traefik, HAProxy 같은 컨트롤러는 자기 nginx · Traefik Pod를 클러스터에 띄워서 그 Pod가 트래픽을 받아 처리합니다. 클라우드 LB는 그 Pod 앞에 단순 L4 분배기로 서는 형태입니다.
- 클라우드 자원이 라우팅 — AWS ALB Controller, GKE Ingress, AKS AGIC는 클라우드의 매니지드 LB (ALB · CLB · Application Gateway) 자체가 호스트 · 경로 라우팅을 합니다. 클러스터 안에는 작은 컨트롤러 Pod가 있어 Ingress 객체를 클라우드 LB의 룰로 번역해 둘 뿐입니다.
같은 Ingress 매니페스트라도 어느 컨트롤러가 처리하느냐에 따라 어노테이션 키와 동작이 달라집니다. 그래서 매니페스트에 컨트롤러 고유 어노테이션을 적을 때는 어느 컨트롤러를 쓰는지부터 확인해야 합니다. EKS 위에서 AWS Load Balancer Controller를 본격적으로 다루는 부분은 22장 앱 배포 골격에서 한 번 더 본격적으로 정리합니다.
Ingress Controller 설치 #
운영 클러스터에서는 보통 한 종류의 컨트롤러를 한 번 설치해 두고 모든 Ingress가 그것을 통과하게 합니다. 환경별 설치 모양을 짧게 짚어 둡니다.
minikube #
addons 명령 한 줄이면 ingress-nginx가 설치됩니다.
minikube addons enable ingresskubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-6d4b7f6c8-abc12 1/1 Running 0 30skind #
kind는 ingress-nginx를 별도 매니페스트로 적용합니다.
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml추가로 kind 클러스터를 만들 때 80 / 443 포트를 호스트로 매핑해 두는 설정이 같이 필요합니다. ingress-nginx의 공식 가이드를 따르면 됩니다.
EKS / GKE / AKS #
매니지드 클러스터에서는 컨트롤러를 Helm으로 설치하는 패턴이 표준입니다.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespaceEKS에서 AWS ALB Controller를 쓰는 경우는 별도의 IAM · 서비스 계정 설정이 따라옵니다.
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
--namespace kube-system \
--set clusterName=my-cluster \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controllerGKE는 Google이 관리하는 GKE Ingress가 기본 활성화 상태이므로, 별도 설치 없이 Ingress 매니페스트를 적용하면 GCLB가 자동으로 만들어지는 경험이 가능합니다. AKS도 비슷한 방식의 매니지드 옵션이 있습니다.
설치한 컨트롤러는 보통 자기 네임스페이스 (ingress-nginx, kube-system 등)에 한 묶음의 Pod · Service · Deployment · ConfigMap으로 떠 있습니다. 클러스터 한 대에 컨트롤러 한 종류만 설치되어 있으면 보통 충분합니다.
첫 Ingress 매니페스트 — 단순 도메인 라우팅 #
가장 단순한 모양부터 시작합니다. example.com으로 들어오는 모든 트래픽을 web Service의 80 포트로 보내는 Ingress입니다.
먼저 백엔드 Service가 떠 있다고 가정합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 80Service 타입이 ClusterIP라는 점이 중요합니다. Ingress 뒤의 Service는 클러스터 내부 전용이면 충분 합니다. 외부 노출은 Ingress Controller가 자기 LoadBalancer Service 한 개로 대신 수행하기 때문입니다. 여기 Service에 type: LoadBalancer를 적을 이유가 없습니다.
이제 Ingress 매니페스트입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80필드를 한 줄씩 짚어 둡니다.
apiVersion: networking.k8s.io/v1— Ingress의 안정 버전입니다. 1.19에서 stable이 되었고, 이전의extensions/v1beta1이나networking.k8s.io/v1beta1은 더 이상 사용하지 않습니다.spec.ingressClassName: nginx— 어느 Ingress Controller가 이 객체를 처리할지 가리킵니다. 한 클러스터에 컨트롤러가 한 종류만 있고 그것이 default IngressClass 면 생략해도 되지만, 명시해 두는 편이 안전합니다. 자세한 내용은 뒤의IngressClass절에서 다시 봅니다.spec.rules[].host: example.com— 어느 호스트의 트래픽인지를 표현합니다. HTTP의 Host 헤더, HTTPS의 SNI를 보고 매칭합니다.spec.rules[].http.paths[].path: /— 매칭할 경로입니다./는 이 호스트의 모든 경로를 의미합니다.spec.rules[].http.paths[].pathType: Prefix— 경로 매칭 방식입니다. 가장 흔한 값입니다.spec.rules[].http.paths[].backend.service— 매칭된 트래픽을 흘릴 Service 이름과 포트입니다.
이 매니페스트를 적용한 뒤 상태를 보면 다음과 같은 모양입니다.
kubectl apply -f ingress-web.yaml
kubectl get ingressNAME CLASS HOSTS ADDRESS PORTS AGE
web nginx example.com 34.120.10.20 80 30sADDRESS 컬럼에 외부 진입점의 IP나 호스트 이름이 차오릅니다. 이 IP가 Ingress Controller를 노출하는 LoadBalancer Service의 EXTERNAL-IP와 같습니다. 클러스터 안에 Ingress 객체가 100개 있어도 보통 이 ADDRESS는 같은 한 IP입니다.
DNS 매핑 #
운영에서 example.com이 진짜로 위 ADDRESS를 가리키게 하려면 외부 DNS에 A 레코드 (또는 CNAME)를 그 IP로 잡아 둬야 합니다. K8s 안에서 자동으로 일어나는 일이 아닙니다. ExternalDNS 같은 도구를 쓰면 Ingress 객체의 host 필드를 보고 클라우드 DNS에 레코드를 자동으로 만들어 주는 운영 패턴도 있습니다. EKS 환경에서 ExternalDNS와 Route 53을 묶는 패턴은 22장 앱 배포 골격에서 본격적으로 다룹니다.
path 기반 라우팅 #
같은 호스트의 경로마다 다른 Service로 흘리고 싶은 경우입니다. example.com/api는 backend-api Service, example.com/static은 cdn Service로 가는 모양입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: backend-api
port:
number: 8080
- path: /static
pathType: Prefix
backend:
service:
name: cdn
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80이 매니페스트의 동작은 다음과 같습니다.
example.com/api/users→backend-api:8080example.com/static/logo.png→cdn:80example.com/또는example.com/about→web:80
paths는 위에서 아래로 평가되는 것이 아니라 매칭이 더 긴 경로가 우선입니다. /api가 /보다 길게 매칭되므로 /api/users 요청은 backend-api로 갑니다. 이 우선순위 규칙은 컨트롤러 구현마다 미묘하게 다를 수 있어, 운영에서는 가능한 한 매니페스트 안에서 경로가 중첩되지 않게 작성하는 편이 안전합니다.
pathType 세 값 #
pathType은 경로 매칭의 방식을 정합니다. 셋이 있습니다.
| 값 | 의미 |
|---|---|
Prefix | 경로를 슬래시 단위로 끊은 prefix 매칭. /api는 /api, /api/, /api/users에 매칭. /api2에는 매칭 안 됨 |
Exact | 경로가 정확히 일치할 때만 매칭 |
ImplementationSpecific | 컨트롤러 구현체가 자기 방식대로 해석. ingress-nginx는 정규식까지 받음 |
운영에서 가장 자주 쓰는 값은 **Prefix**입니다. Exact는 단일 엔드포인트 한 개만 노출하고 싶을 때, ImplementationSpecific은 컨트롤러 고유 기능 (예: ingress-nginx의 regex)을 쓰고 싶을 때 선택합니다. 매니페스트의 이식성을 생각한다면 Prefix로 통일해 두는 편이 무난합니다.
Prefix의 슬래시 단위 매칭이 미묘한 부분이라 짧게 짚어 두면 — /api라고 적었을 때 /api/users는 매칭되지만 /api2는 매칭되지 않습니다. K8s가 Prefix를 “디렉터리 prefix” 의미로 해석하기 때문입니다. 이 차이가 보안과 관련이 있을 때가 있습니다 — 의도하지 않은 경로가 같은 백엔드로 흘러 가는 사고를 막아 줍니다.
host + path 조합 #
서로 다른 도메인을 한 Ingress에서 같이 처리하는 모양입니다. api.example.com과 app.example.com을 갈라 두는 흔한 구성입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend-api
port:
number: 8080
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80rules 배열에 host가 다른 항목 두 개를 넣으면 됩니다. 한 Ingress가 여러 host를 같이 들고 있어도 되고, host 마다 별도 Ingress 객체를 따로 만들어도 됩니다. 운영에서는 도메인 그룹 단위로 Ingress를 쪼개는 패턴이 흔합니다 — 네임스페이스별로 하나, 팀별로 하나처럼 책임이 분리될 때 객체를 같이 분리하는 모양입니다.
호스트는 와일드카드도 지원합니다. *.example.com이라고 적으면 foo.example.com, bar.example.com 같은 한 단계 서브도메인이 모두 매칭됩니다. 단, *.example.com은 foo.bar.example.com처럼 두 단계는 매칭하지 않습니다 — DNS 와일드카드의 그 규칙을 그대로 따릅니다.
TLS 종단 — Ingress + Secret #
운영에서 외부 진입은 거의 항상 HTTPS입니다. Ingress의 tls 필드와 K8s Secret을 짝지으면 인증서 종단을 한 곳에서 처리할 수 있습니다. Secret 객체의 멘탈 모델 자체는 6장 ConfigMap과 Secret에서 이미 잡았습니다.
먼저 인증서 · 키를 담은 Secret이 필요합니다.
kubectl create secret tls example-com-tls \
--cert=fullchain.pem \
--key=privkey.pemSecret의 타입은 kubernetes.io/tls이고, 안에는 tls.crt (인증서 체인)와 tls.key (개인 키) 두 키가 들어 있습니다. 매니페스트로도 같은 모양을 만들 수 있지만, 키 파일을 base64로 인코딩한 값을 매니페스트에 넣게 되므로 보통은 위처럼 명령으로 만드는 편이 깔끔합니다.
이 Secret을 Ingress에 연결합니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
secretName: example-com-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80spec.tls[].hosts에 적은 도메인의 트래픽은 Ingress Controller가 받을 때 TLS를 종단하고, 안쪽 Service에는 평문 HTTP로 흘립니다. 이 모양 덕분에 Service와 Pod 측 매니페스트는 HTTPS를 신경쓰지 않아도 되고, 인증서 갱신은 Secret만 갈아끼우면 됩니다.
cert-manager — 자동 발급 · 갱신 #
Let’s Encrypt 같은 무료 CA에서 인증서를 자동 발급 · 갱신하는 표준 도구가 cert-manager입니다. cert-manager를 클러스터에 설치하고 ClusterIssuer 객체를 한 번 만들어 두면, Ingress 매니페스트에 어노테이션 한 줄을 적는 것만으로 인증서 발급이 자동으로 진행됩니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
secretName: example-com-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80cert-manager.io/cluster-issuer 어노테이션을 보고 cert-manager가 다음 사이클을 자동으로 진행합니다.
- ACME 챌린지 (HTTP-01 또는 DNS-01)를 통해 도메인 소유 검증.
- Let’s Encrypt에서 인증서 발급.
- 인증서 · 키를
secretName에 적힌 Secret으로 만들어 둠. - 만료 30일 전쯤 자동 갱신.
cert-manager 자체의 설치와 ClusterIssuer 설정은 분량이 적지 않아 본 챕터에서는 모양만 짚고, EKS 위의 ACM 통합과 함께 본격적인 사용은 22장 앱 배포 골격에서 정리합니다. 운영에서 cert-manager가 사실상 표준이 되어 있으므로 이름과 위치만 기억해 두시면 충분합니다.
IngressClass — 컨트롤러가 둘 이상 있을 때 #
대부분의 클러스터는 컨트롤러를 한 종류만 두지만, 같은 클러스터에 두 종류가 같이 있어야 하는 경우도 있습니다. 예를 들어 공개 트래픽은 ingress-nginx로, 내부 관리 트래픽은 AWS ALB Controller 로 보내고 싶은 모양입니다. 이때 어느 Ingress 객체가 어느 컨트롤러에서 처리되어야 하는지를 표현하는 객체가 IngressClass입니다.
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx
annotations:
ingressclass.kubernetes.io/is-default-class: "true"
spec:
controller: k8s.io/ingress-nginx핵심 필드 둘입니다.
spec.controller— 어느 컨트롤러 구현이 이 IngressClass를 처리할지 가리키는 식별자입니다. ingress-nginx는k8s.io/ingress-nginx, AWS ALB Controller는ingress.k8s.aws/alb식으로 구현체별로 정해져 있습니다.ingressclass.kubernetes.io/is-default-class: "true"어노테이션 — 이 IngressClass를 클러스터의 기본으로 표시합니다. Ingress 매니페스트에ingressClassName을 적지 않으면 default IngressClass의 컨트롤러가 처리합니다.
Ingress 매니페스트에서는 spec.ingressClassName으로 어느 클래스에 속할지 적습니다.
spec:
ingressClassName: alb # AWS ALB Controller 가 처리
rules:
- host: admin.example.com
...같은 클러스터의 다른 Ingress가 ingressClassName: nginx 라면 그것은 ingress-nginx가 처리합니다. 두 컨트롤러가 같은 객체를 두고 충돌하지 않습니다.
kubectl get ingressclassNAME CONTROLLER PARAMETERS AGE
nginx (default) k8s.io/ingress-nginx <none> 30d
alb ingress.k8s.aws/alb <none> 10d(default) 표시가 붙은 클래스가 default입니다. 클러스터에 default IngressClass는 한 개만 두는 것이 표준입니다. 두 개가 default로 표시되면 새 Ingress 객체가 어느 쪽에서 처리되는지 모호해집니다.
Gateway API — Ingress의 후속 표준 #
마지막으로 한 줄로 짚어 둡니다. Ingress 객체는 단순한 호스트 · 경로 라우팅에는 충분하지만, 더 표현력 있는 라우팅 (헤더 기반, 쿼리 파라미터 기반, 가중치 기반 트래픽 분배 등)을 다루기에는 어노테이션에 의존하게 되어 컨트롤러마다 달라지는 한계가 있습니다. SIG-Network가 그 한계를 풀려고 만든 후속 표준이 Gateway API입니다.
Gateway API는 객체를 셋으로 나눕니다.
| 객체 | 역할 |
|---|---|
| GatewayClass | 어느 컨트롤러가 처리할지 (Ingress의 IngressClass에 대응) |
| Gateway | 외부 진입점 자체 (LoadBalancer 같은 자원에 대응) |
| HTTPRoute / TCPRoute / TLSRoute | 라우팅 규칙 (Ingress의 rules에 대응하지만 더 표현력 있음) |
ingress-nginx, Traefik, Cilium, Istio 같은 많은 컨트롤러가 Gateway API를 지원하고 있고, 새 클러스터를 시작하는 운영자라면 Gateway API를 검토할 단계입니다. 다만 Ingress가 여전히 가장 널리 쓰이는 표준이고, 기존 클러스터에서 운영자가 만나는 객체는 대부분 Ingress입니다. Gateway API의 깊은 모델은 후속 K8s 심화 책으로 미뤄 두고, 본 챕터에서는 이름과 위치만 짚는 정도로 두겠습니다.
운영 패턴 — LoadBalancer 한 개 + Ingress 다수 #
본 챕터의 머릿속 그림을 한 줄로 굳히고 갑니다. 운영 클러스터의 외부 진입은 보통 클라우드 LoadBalancer 한 개로 모입니다. 그 LoadBalancer는 Ingress Controller가 자기 노출용으로 띄운 한 Service (type: LoadBalancer)에 붙어 있고, 모든 외부 트래픽이 그 한 LB를 통과합니다.
[ 클라우드 LoadBalancer ] ←── 한 개. 비용도 한 곳
│
▼
[ Ingress Controller (nginx Pod 묶음) ] ←── ingress-nginx 네임스페이스
│
│ Ingress 객체들의 라우팅 규칙
├──────────┬──────────┬──────────┐
▼ ▼ ▼ ▼
[web] [api] [admin] [static] ←── 각자 ClusterIP Service이 구조의 효과를 한 줄로 정리하면 다음과 같습니다.
- 클라우드 LB 비용이 한 곳 — Service 개수가 늘어나도 LB는 그대로 한 개입니다.
- TLS 종단이 한 곳 — 인증서 · cert-manager 설정도 한 곳에 모입니다.
- 도메인 · 경로 라우팅이 매니페스트로 표현 — 새 Service의 노출은 Ingress 객체 한 줄을 추가하는 일입니다.
- DNS 레코드가 단순 —
*.example.com을 LB의 IP / 이름에 한 번 잡아 두면 모든 호스트가 그 LB로 들어옵니다.
Service 개수와 별개로 클라우드 LB는 보통 한 개이지만, 트래픽 격리가 필요한 경우 (공개 vs 내부 등)에는 컨트롤러를 두 묶음 띄워 LB 두 개를 갖는 패턴도 흔합니다. 위 IngressClass 절의 모양이 그 운영 패턴의 예입니다.
Ingress Controller 자체의 가용성 #
이 구조의 한 가지 위험은 — Ingress Controller가 단일 장애점이 될 수 있다는 점입니다. 모든 외부 트래픽이 그 Controller를 통과하므로, Controller Pod가 동시에 장애나면 전체 외부 트래픽이 끊깁니다. 운영에서는 다음을 갖추는 것이 표준입니다.
- Controller Pod 다중화 — Deployment의
replicas를 2 이상으로 설정합니다. 노드 장애 시에도 한 Pod는 살아 있도록 PodDisruptionBudget · anti-affinity로 분산합니다. - 리소스 여유 — Controller Pod의 CPU · 메모리 limits를 충분히 잡아 둡니다. 다음 챕터 11장 resources.requests / limits에서 다루는 주제입니다.
- 모니터링 — Controller가 노출하는 Prometheus 메트릭 (요청 수, 5xx 비율, 응답 시간)을 알람으로 묶습니다. 19장 옵저버빌리티에서 본격적으로 다룹니다.
연습문제 #
- 로컬 클러스터 (minikube 또는 kind)에 ingress-nginx를 설치한 뒤,
example.com으로 들어오는 트래픽을webService로 보내는 단순 Ingress 매니페스트를 적용해 보세요.kubectl get ingress의ADDRESS컬럼에 무엇이 들어오는지,/etc/hosts에127.0.0.1 example.com(kind) 또는minikube ip값을 추가한 뒤curl http://example.com이 어떤 응답을 주는지 시간 순서대로 기록합니다. - 본문대로
ingress-paths.yaml을 적용한 뒤,pathType을Prefix와Exact로 번갈아 바꿔 가며curl http://example.com/api,curl http://example.com/api/,curl http://example.com/api/users,curl http://example.com/api2네 요청이 어디로 흘러가는지 표로 정리합니다. 의도하지 않은 경로 (/api2)가 같은 백엔드로 흘러가지 않는다는 §“pathType 세 값"의 슬래시 단위 매칭 모델을 자신의 표현으로 한 단락으로 정리합니다. - 같은 클러스터에
ingressClassName이 다른 두 Ingress 매니페스트를 동시에 적용하는 시나리오를 메모해 봅시다 — 예를 들어nginx클래스로example.com,alb클래스로admin.example.com을 노출하는 모양입니다. 두 IngressClass가 같이 있는 클러스터에서 default가 한 개만 있어야 하는 이유, 그리고 default가 두 개로 잘못 표시되면 새 Ingress가 어떻게 동작할지를 §“IngressClass"의 모델로 한 단락으로 추론합니다.
한 줄 요약: Ingress는 클라우드 LoadBalancer 한 개 뒤에서 호스트 · 경로 라우팅과 TLS 종단을 한 곳에 모으는 두 층 추상화다 — 매니페스트로 적는 Ingress 객체와, 그 객체를 읽어 실제 트래픽을 라우팅하는 Ingress Controller (ingress-nginx · ALB · GKE Ingress 등). 운영의 표준은 클라우드 LB 한 개 + Controller 한 묶음 + ClusterIP Service 다수, 그 위에 cert-manager로 TLS 자동 발급과 IngressClass로 다중 컨트롤러를 다룬다. 후속 표준인 Gateway API는 더 표현력 있는 라우팅을 다루며 검토 단계에 있다.
다음 챕터 #
본 챕터에서 외부 진입점의 모양을 잡으면서 마지막에 짧게 짚은 한 가지가 다음 챕터의 출발점이 됩니다 — Ingress Controller Pod의 자원 여유입니다. Controller가 모든 외부 트래픽을 처리하므로 그 Pod에 CPU · 메모리가 부족하면 트래픽 자체가 막힙니다. 그런데 K8s에서 Pod가 자기에게 필요한 자원을 어떻게 표현하고, 그 표현이 스케줄러의 결정과 노드의 OOM 동작에 어떻게 영향을 주는지는 아직 다루지 않았습니다.
11장 resources.requests / limits에서는 Pod 매니페스트의 resources.requests와 resources.limits 두 필드의 모델, CPU와 메모리 두 자원의 동작 차이 (CPU는 throttling, 메모리는 OOMKill), QoS 클래스 (Guaranteed / Burstable / BestEffort), LimitRange로 네임스페이스 기본값을 강제하는 운영 패턴까지 한 사이클로 정리합니다.