도메인으로 접속하면 502가 나지만 Service와 Pod는 모두 Running입니다. 운영팀은 Ingress, Controller, Service, Pod 사이에서 어느 지점이 요청을 끊는지 단계적으로 확인해야 합니다. Ingress를 이해하면 외부 HTTP 트래픽 장애를 구조적으로 추적할 수 있습니다.
Ingress Controller — 경로 기반 라우팅과 TLS
서비스가 늘어날수록 LoadBalancer를 각각 만들면 클라우드 과금이 기하급수적으로 증가합니다. AWS에서 NLB 하나가 월 20달러 이상인데, 마이크로서비스 10개면 이것만 200달러가 넘습니다. 실제 운영 팀은 Ingress Controller 하나로 모든 외부 트래픽을 받고, 경로나 호스트명에 따라 내부 서비스로 분산합니다. 또한 HTTPS 인증서 관리를 각 서비스에 맡기지 않고 Ingress에서 TLS를 한 곳에서 종단합니다. 결국 Ingress는 쿠버네티스의 API Gateway 역할입니다. nginx-ingress가 어떻게 동작하는지 이해하면, Istio Gateway나 AWS ALB Ingress로 전환할 때도 동일한 개념으로 접근할 수 있습니다.
Ingress Controller를 통해 단일 진입점에서 여러 서비스로 트래픽을 지능적으로 분산하고, HTTPS를 적용하는 방법을 실습합니다.
- 1Ingress 리소스와 Ingress Controller의 역할 분리
- 2nginx-ingress 설치 및 기본 라우팅 구성
- 3경로 기반 라우팅 (path-based routing)
- 4호스트 기반 라우팅 (virtual hosting)
- 5TLS 종단 — Secret으로 인증서 관리
- 6TroubleCase: 502 Bad Gateway → 파드 Readiness 확인
Ingress Controller가 설치되어야 Ingress 리소스가 동작합니다. minikube 사용자는 addon으로 간편하게 활성화하고, 그 외 환경은 공식 manifest를 적용합니다.
kubectl cluster-infominikube addons enable ingresskubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yamlkubectl get pods -n ingress-nginxkubectl create namespace ingress-labIngress 리소스 vs Ingress Controller — 역할 구분
Ingress 리소스를 작성하고 적용했는데 트래픽이 전혀 라우팅되지 않는 상황을 만납니다. kubectl get ingress에 주소가 없고 아무 일도 일어나지 않습니다. Kubernetes 문서에서 "Ingress"와 "Ingress Controller"가 혼용되어, 어느 쪽을 설치해야 하는지조차 파악하기 어렵습니다. 두 개념을 구분하지 못하면 Ingress 설정 오류와 Controller 설치 누락을 구별하지 못해 디버깅 시간이 낭비됩니다. 이 CB에서는 Ingress 리소스(규칙 선언서)와 Ingress Controller(실행자)가 어떻게 다른지, 그리고 어떤 순서로 설치·확인해야 하는지를 다룹니다.

Ingress 리소스 — 규칙 선언서
Ingress는 Kubernetes API 오브젝트입니다. "이 호스트, 이 경로로 들어오는 요청은 저 서비스로 보내라"는 라우팅 규칙을 YAML로 선언합니다. 규칙만 정의할 뿐 실제 네트워킹은 처리하지 않습니다.
# Ingress 리소스 — 규칙만 정의
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
Ingress Controller — 규칙을 실행하는 파드
Ingress Controller는 위 규칙을 읽고 실제 트래픽을 처리하는 파드입니다. Kubernetes 자체에 내장되어 있지 않아 별도로 설치해야 합니다. 가장 많이 쓰이는 것이 nginx-ingress입니다.
인터넷 → LoadBalancer Service → nginx-ingress Controller 파드
│
Ingress 규칙을 읽고 트래픽 분배
│ │
/api → api-service / → frontend-service
Controller 설치 확인
# nginx-ingress Controller 파드가 Running이어야 함
kubectl get pods -n ingress-nginx
# NAME READY STATUS AGE
# ingress-nginx-controller-7d9c9c4f75-xk2p9 1/1 Running 5m
# Controller의 LoadBalancer Service 확인
kubectl get svc -n ingress-nginx
# NAME TYPE CLUSTER-IP EXTERNAL-IP
# ingress-nginx-controller LoadBalancer 10.96.0.200 203.0.113.5
# ↑ 이 IP로 모든 트래픽 유입
- NAME—조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
- STATUS/READY—Running, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
- RESTARTS/EVENTS—재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.
경로 기반 라우팅 (Path-Based Routing)
마이크로서비스 초기에는 서비스마다 별도 도메인을 쓰다가 인증서 관리와 CORS 설정이 폭발적으로 늘어납니다. 팀이 프론트엔드, API, 어드민을 각각 다른 도메인으로 서비스하면 클라이언트 코드에 하드코딩된 도메인이 수십 곳으로 퍼집니다. 단일 도메인에서 경로로 서비스를 구분하면 인증서는 하나, 도메인은 하나로 줄고 내부 라우팅 변경이 클라이언트에 영향을 주지 않습니다. 이 CB에서는 같은 도메인 아래 /api, /admin, / 경로를 다른 서비스로 분배하는 경로 기반 라우팅 설정을 다룹니다.

실습: 두 서비스 배포 후 경로 라우팅
# backend-services.yaml
---
# API 서비스
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-app
namespace: ingress-lab
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: hashicorp/http-echo:latest
args: ["-text=Hello from API service"]
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: api-service
namespace: ingress-lab
spec:
selector:
app: api
ports:
- port: 80
targetPort: 5678
---
# 프론트엔드 서비스
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-app
namespace: ingress-lab
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: hashicorp/http-echo:latest
args: ["-text=Hello from Frontend service"]
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: ingress-lab
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 5678
kubectl apply -f backend-services.yaml
# 파드 Running 확인 후 Ingress 적용
kubectl get pods -n ingress-lab
경로 기반 Ingress 설정
# path-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-ingress
namespace: ingress-lab
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # 경로 재작성
spec:
ingressClassName: nginx # Controller 지정
rules:
- host: app.example.com
http:
paths:
- path: /api # /api/* → api-service
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: / # /* → frontend-service
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
kubectl apply -f path-ingress.yaml
# Ingress 확인
kubectl get ingress -n ingress-lab
# NAME CLASS HOSTS ADDRESS PORTS AGE
# path-ingress nginx app.example.com 203.0.113.5 80 1m
# /etc/hosts에 테스트용 항목 추가 (로컬 테스트)
echo "203.0.113.5 app.example.com" | sudo tee -a /etc/hosts
# 경로별 라우팅 테스트
curl http://app.example.com/api
# Hello from API service
curl http://app.example.com/
# Hello from Frontend service
pathType 차이
| pathType | 설명 | 예시 |
|---|---|---|
Prefix | 경로 접두사 매칭 | /api는 /api, /api/v1, /api/users 모두 매칭 |
Exact | 정확한 경로만 매칭 | /api는 /api만 매칭, /api/v1은 불일치 |
ImplementationSpecific | Controller 구현에 따라 다름 | nginx의 경우 정규식 지원 |
호스트 기반 라우팅과 TLS 종단
api.example.com과 app.example.com이 서로 다른 팀이 운영하는 서비스인데 각 팀이 HTTPS 인증서를 따로 관리하면 만료 사고가 발생합니다. 서브도메인마다 LoadBalancer를 두면 클라우드 비용이 서비스 수만큼 증가합니다. 호스트 기반 라우팅은 하나의 Ingress Controller에서 Host 헤더를 기준으로 서로 다른 서비스로 트래픽을 분기하고, TLS 종단을 Controller 한 곳에서 처리해 백엔드 파드가 인증서를 신경 쓰지 않아도 됩니다. 이 CB에서는 서브도메인별 라우팅 설정과 TLS Secret 연결 방법을 다룹니다.
호스트 기반 라우팅 (Virtual Hosting)
서로 다른 서브도메인을 다른 서비스로 라우팅합니다.
# host-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: host-ingress
namespace: ingress-lab
spec:
ingressClassName: nginx
rules:
- host: api.example.com # api 서브도메인
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- host: app.example.com # app 서브도메인
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
TLS 종단 설정
TLS 인증서를 Secret으로 저장하고 Ingress에 연결합니다.
1단계: TLS Secret 생성
# 자체 서명 인증서 생성 (테스트용)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key \
-out tls.crt \
-subj "/CN=app.example.com/O=example"
# Kubernetes Secret으로 저장
kubectl create secret tls app-tls-secret \
--key tls.key \
--cert tls.crt \
-n ingress-lab
# Secret 확인
kubectl get secret app-tls-secret -n ingress-lab
# NAME TYPE DATA AGE
# app-tls-secret kubernetes.io/tls 2 10s
2단계: TLS가 적용된 Ingress
# tls-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
namespace: ingress-lab
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true" # HTTP → HTTPS 리다이렉트
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls-secret # TLS 인증서 Secret 참조
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
kubectl apply -f tls-ingress.yaml
# HTTPS 테스트 (자체 서명 인증서이므로 -k 플래그)
curl -k https://app.example.com/api
# Hello from API service
# HTTP → HTTPS 리다이렉트 확인
curl -v http://app.example.com/
# < HTTP/1.1 308 Permanent Redirect
# < Location: https://app.example.com/
cert-manager로 Let's Encrypt 인증서 자동화 (실무 패턴)
프로덕션에서는 cert-manager를 사용하여 Let's Encrypt 인증서를 자동 발급·갱신합니다.
# cert-manager 사용 시 Ingress annotation만 추가하면 자동 발급
metadata:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls-cert # cert-manager가 자동으로 생성
문제 상황
curl https://app.example.com/api
# <html>
# <head><title>502 Bad Gateway</title></head>
# <body><h1>502 Bad Gateway</h1></body>
# </html>
Ingress는 정상 설정했고 Service도 존재하는데 502가 반환됩니다.
진단 1: Ingress Controller 로그 확인
# nginx-ingress Controller 파드 이름 확인
kubectl get pods -n ingress-nginx
# Controller 로그에서 upstream 오류 확인
kubectl logs -n ingress-nginx ingress-nginx-controller-xxxx | grep -i "upstream\|502\|error" | tail -20
# [error] 123#123: *456 connect() failed (111: Connection refused)
# while connecting to upstream, ... upstream: "http://10.244.0.5:5678"
진단 2: Endpoints 확인
# 업스트림 Service의 Endpoints 확인
kubectl get endpoints api-service -n ingress-lab
# NAME ENDPOINTS AGE
# api-service <none> 5m ← Endpoints 없음!
진단 3: 파드 Readiness Probe 상태 확인
kubectl get pods -n ingress-lab
# NAME READY STATUS RESTARTS
# api-app-7d9f4c-xk2p9 0/1 Running 0 ← READY가 0/1
# 파드 상세 확인
kubectl describe pod api-app-7d9f4c-xk2p9 -n ingress-lab
# Conditions:
# Type Status
# Ready False ← Readiness 실패
# Events:
# Warning Unhealthy Readiness probe failed: Get "http://10.244.0.5:5678/health": dial tcp: connect: connection refused
원인 분석
파드가 Running이어도 Readiness Probe가 실패하면 Service Endpoints에서 제외됩니다. Ingress Controller는 Endpoints가 없는 Service에 연결을 시도하지만 받아줄 파드가 없어 502를 반환합니다.
상태 흐름:
파드 Running + Readiness 실패
→ Endpoints에서 제외
→ Service로 트래픽 전달 불가
→ Ingress Controller: 502 Bad Gateway
해결 방법
방법 1: Readiness Probe 경로/포트 확인
# 실제 파드가 리스닝하는 포트 확인
kubectl exec -it api-app-7d9f4c-xk2p9 -n ingress-lab -- ss -tlnp
# State Recv-Q Send-Q Local Address:Port
# LISTEN 0 128 0.0.0.0:5678 ← 5678 포트에서 리스닝
# Readiness Probe 설정 수정 (올바른 경로와 포트로)
# Deployment에 올바른 Readiness Probe 설정
spec:
containers:
- name: api
readinessProbe:
httpGet:
path: / # 실제 응답하는 경로
port: 5678 # 컨테이너가 리스닝하는 포트
initialDelaySeconds: 5
periodSeconds: 10
방법 2: 애플리케이션이 시작 전 probe 실패 — 지연 추가
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15 # 앱 시작까지 기다리는 시간 증가
periodSeconds: 5
failureThreshold: 3
방법 3: Readiness Probe 없이 즉시 확인 (임시)
# Probe 없이 파드 재배포 후 연결 확인
kubectl rollout restart deployment/api-app -n ingress-lab
kubectl get endpoints api-service -n ingress-lab -w
# NAME ENDPOINTS AGE
# api-service 10.244.0.7:5678 1m ← Endpoints 등록됨!
예방 체크리스트
# 배포 후 필수 확인 순서
# 1. 파드 READY 상태 확인
kubectl get pods -n ingress-lab
# 2. Endpoints 비어있지 않은지 확인
kubectl get endpoints -n ingress-lab
# 3. Ingress 주소 할당 확인
kubectl get ingress -n ingress-lab
# 4. Ingress Controller 로그 확인
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=20
실무에서 Ingress를 구성하는 방식
팀별 Ingress 분리 vs 중앙 관리
# 패턴 1: 팀마다 자체 Ingress (namespace 분리)
# team-a 네임스페이스에 team-a 서비스용 Ingress
# team-b 네임스페이스에 team-b 서비스용 Ingress
# 패턴 2: 중앙 Ingress (단일 진입점)
# 모든 라우팅을 infra 팀이 관리하는 Ingress 하나로 통합
대부분의 팀은 패턴 1을 선호합니다. 네임스페이스 격리로 팀 간 영향이 줄고, 각 팀이 자체 배포 파이프라인으로 Ingress를 관리할 수 있습니다.
Canary 배포와 Ingress
# nginx-ingress의 canary annotation으로 트래픽 분할
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20" # 20% 트래픽을 새 버전으로
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-v2 # 새 버전 서비스
port:
number: 80
Rate Limiting으로 DDoS 방어
metadata:
annotations:
nginx.ingress.kubernetes.io/limit-connections: "5"
nginx.ingress.kubernetes.io/limit-rpm: "60" # 분당 60회 제한
nginx.ingress.kubernetes.io/limit-rps: "10" # 초당 10회 제한
실무 체크리스트
운영 환경에서 Ingress를 배포하기 전 반드시 확인해야 하는 항목들입니다.
- Readiness Probe가 모든 파드에 설정되어 있는가?
- TLS 인증서 만료일을 모니터링하고 있는가? (cert-manager 사용 권장)
- Ingress Controller의 리소스 제한(requests/limits)이 설정되어 있는가?
- 트래픽 급증 시 Controller HPA(Horizontal Pod Autoscaler)가 설정되어 있는가?
정리
# 주요 진단 명령어
# Ingress 목록 확인
kubectl get ingress -n <namespace>
# Ingress 상세 정보 (규칙, 백엔드, TLS 확인)
kubectl describe ingress <ingress-name> -n <namespace>
# Ingress Controller 로그 실시간 확인
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -f
# 업스트림 Service Endpoints 확인
kubectl get endpoints -n <namespace>
# TLS Secret 확인
kubectl get secret -n <namespace>
kubectl describe secret <tls-secret-name> -n <namespace>
# Ingress Controller가 읽은 nginx 설정 확인
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- \
cat /etc/nginx/nginx.conf | grep -A10 "upstream"
다음 챕터에서는 ConfigMap과 Secret으로 애플리케이션 설정을 파드에 주입하는 방법을 학습합니다.