infra
Platform

모듈 맵

[Kubernetes] Ingress Controller 경로 기반 포워딩과 SSL/TLS 설정

0 / 29 완료

펼치기
0 / 29 완료0%

쿠버네티스 & GitOps · 07 / 29

[Kubernetes] Ingress Controller 경로 기반 포워딩과 SSL/TLS 설정

nginx-ingress로 단일 진입점에서 경로·호스트 기반 라우팅과 TLS 종단을 구성합니다

🚨INCIDENT ALERT
HIGH

도메인으로 접속하면 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)을 구성할 수 있다
  • 5Secret으로 인증서를 관리해 TLS 종단을 구성할 수 있다
  • 6502 Bad Gateway 발생 시 파드 Readiness를 확인해 진단할 수 있다
실습 환경 준비

Ingress Controller가 설치되어야 Ingress 리소스가 동작합니다. minikube 사용자는 addon으로 간편하게 활성화하고, 그 외 환경은 공식 manifest를 적용합니다.

클러스터 준비 확인
kubectl cluster-info
nginx-ingress 설치 (minikube)
minikube addons enable ingress
nginx-ingress 설치 (일반 클러스터)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
Ingress Controller 파드 확인
kubectl get pods -n ingress-nginx
실습용 네임스페이스 생성
kubectl create namespace ingress-lab
💡개념

Ingress 리소스 vs Ingress Controller — 역할 구분

Ingress 리소스를 작성하고 적용했는데 트래픽이 전혀 라우팅되지 않는 상황을 만납니다. kubectl get ingress에 주소가 없고 아무 일도 일어나지 않습니다. Kubernetes 문서에서 "Ingress"와 "Ingress Controller"가 혼용되어, 어느 쪽을 설치해야 하는지조차 파악하기 어렵습니다. 두 개념을 구분하지 못하면 Ingress 설정 오류와 Controller 설치 누락을 구별하지 못해 디버깅 시간이 낭비됩니다. 이 CB에서는 Ingress 리소스(규칙 선언서)와 Ingress Controller(실행자)가 어떻게 다른지, 그리고 어떤 순서로 설치·확인해야 하는지를 다룹니다.

Ingress 리소스 — 규칙 선언서

Ingress는 Kubernetes API 오브젝트입니다. "이 호스트, 이 경로로 들어오는 요청은 저 서비스로 보내라"는 라우팅 규칙을 YAML로 선언합니다. 규칙만 정의할 뿐 실제 네트워킹은 처리하지 않습니다.

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입니다.

Ingress Controller 라우팅 — 인터넷→LoadBalancer Service→nginx-ingress Controller 파드가 Ingress 규칙대로 /api는 api-service, /는 frontend-service로 분배확대

Controller 설치 확인

Kubernetes
# 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로 모든 트래픽 유입
🔍실행 후 확인할 것
  • kubectl get ingress에서 ADDRESS 열 먼저 확인 — IP가 있으면 Ingress Controller가 External IP를 할당받은 것, 비어 있으면 LoadBalancer 타입 Service 미생성 또는 클라우드 제공자 미연동
  • BACKENDS 열 기준: <service-name>:<port> 형식이 보이면 라우팅 규칙이 등록된 것. "(default backend - 404)" 만 보이면 Ingress 규칙의 host/path가 요청과 불일치
  • ADDRESS는 있지만 curl로 접근 시 502이면 → 백엔드 파드의 readinessProbe 실패로 Service Endpoints가 비어있는 것. kubectl get endpoints <service-name>으로 확인

외부 요청이 Pod까지 오는 전체 경로 — Ingress

💡개념

외부 요청이 Pod까지 오는 전체 경로 — Ingress 6단계

브라우저에 https://app.example.com/api를 입력합니다. 이 요청은 클러스터 바깥에서 출발해, 여러 계층을 지나 특정 파드 안의 프로세스에 닿아야 합니다. 그 사이에서 DNS → 클라우드 LB/NodePort → Ingress Controller → 규칙 매칭 → Service·Endpoints → Pod가 순서대로 일어납니다. 502나 404가 떴을 때 "어느 계층에서 끊겼나"를 이 단계로 좁히지 못하면, Ingress 설정을 고쳐야 할지 백엔드 파드를 고쳐야 할지조차 판단할 수 없습니다.

TEXT
[브라우저]  https://app.example.com/api
   │
   ① DNS: app.example.com → Ingress Controller의 LB 공인 IP (203.0.113.5)
   │
   ② 클라우드 LoadBalancer(또는 NodePort) → Ingress Controller 파드(nginx)로 유입
   │
   ③ TLS 종료: nginx가 인증서(Secret)로 HTTPS 복호화 → 이후 클러스터 내부는 평문 HTTP
   │
   ④ Ingress 규칙 매칭: Host(app.example.com) + Path(/api)로 대상 Service 결정
   │
   ⑤ 대상 Service의 Endpoints에서 Ready Pod 하나 선택 (kube-proxy·DNAT)
   │
   ⑥ 선택된 Pod로 프록시 → 응답이 역방향으로 브라우저까지
   ▼
[백엔드 Pod]  api-service → 10.244.0.6:5678 에서 수신·응답

각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:

단계하는 일여기서 막히면
① DNS도메인을 Ingress Controller의 LB 공인 IP로 해석DNS 미설정·/etc/hosts 오설정 → 접속 자체가 안 됨
② 진입클라우드 LB(또는 NodePort)가 Controller 파드로 트래픽 전달kubectl get ingress의 ADDRESS가 비어 있음 → LB 미할당·클라우드 미연동
③ TLS 종료Controller가 tls.secretName 인증서로 복호화, 내부 구간은 HTTP인증서 만료·SNI 불일치 → TLS handshake 실패, 브라우저 인증서 경고
④ 규칙 매칭Host·Path 규칙(Prefix/Exact)으로 백엔드 Service 결정규칙과 요청이 불일치 → default backend가 응답하는 404
⑤ Endpoints대상 Service의 Ready Pod 목록에서 선택Endpoints가 비면(readiness 실패) → 502 Bad Gateway
⑥ 프록시·응답Pod로 요청 전달하고 응답 반환Pod 처리 지연 → 504, 요청 바디 초과 → 413

502와 404는 서로 다른 계층을 가리킵니다 — 404는 ④에서 Host·Path가 어떤 규칙과도 매칭되지 않은 것(Ingress 설정 문제 — 오타·pathType·host 누락)이고, 502는 ⑤에서 대상 Service의 Endpoints가 비어 받아 줄 파드가 없는 것(백엔드 파드 문제 — readiness 실패)입니다. 이 둘을 구분하면 고쳐야 할 대상이 갈립니다 — 404는 Ingress 규칙을, 502는 파드와 Endpoints를 봅니다.

즉 외부 요청이 파드에 닿으려면 진입(②)·매칭(④)·백엔드(⑤) 세 관문이 모두 통과돼야 합니다. 진단도 이 순서 그대로 — 먼저 kubectl get ingress로 ADDRESS를 확인(②), 다음 kubectl describe ingress로 규칙이 요청과 맞는지(④), 마지막으로 kubectl get endpoints <service>로 백엔드에 Ready 파드가 있는지(⑤)를 봅니다. 어느 관문에서 멈췄는지가 곧 원인의 계층입니다.


💡개념

경로 기반 라우팅 (Path-Based Routing)

마이크로서비스 초기에는 서비스마다 별도 도메인을 쓰다가 인증서 관리와 CORS 설정이 폭발적으로 늘어납니다. 팀이 프론트엔드, API, 어드민을 각각 다른 도메인으로 서비스하면 클라이언트 코드에 하드코딩된 도메인이 수십 곳으로 퍼집니다. 단일 도메인에서 경로로 서비스를 구분하면 인증서는 하나, 도메인은 하나로 줄고 내부 라우팅 변경이 클라이언트에 영향을 주지 않습니다. 이 CB에서는 같은 도메인 아래 /api, /admin, / 경로를 다른 서비스로 분배하는 경로 기반 라우팅 설정을 다룹니다.

실습: 두 서비스 배포 후 경로 라우팅

YAML
# 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
Kubernetes
kubectl apply -f backend-services.yaml

# 파드 Running 확인 후 Ingress 적용
kubectl get pods -n ingress-lab

경로 기반 Ingress 설정

YAML
# 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
Kubernetes
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은 불일치
ImplementationSpecificController 구현에 따라 다름nginx의 경우 정규식 지원

💡개념

호스트 기반 라우팅과 TLS 종단

api.example.com과 app.example.com이 서로 다른 팀이 운영하는 서비스인데 각 팀이 HTTPS 인증서를 따로 관리하면 만료 사고가 발생합니다. 서브도메인마다 LoadBalancer를 두면 클라우드 비용이 서비스 수만큼 증가합니다. 호스트 기반 라우팅은 하나의 Ingress Controller에서 Host 헤더를 기준으로 서로 다른 서비스로 트래픽을 분기하고, TLS 종단을 Controller 한 곳에서 처리해 백엔드 파드가 인증서를 신경 쓰지 않아도 됩니다. 이 CB에서는 서브도메인별 라우팅 설정과 TLS Secret 연결 방법을 다룹니다.

호스트 기반 라우팅 (Virtual Hosting)

Ingress 라우팅 타입 — 하나의 외부 IP(LoadBalancer) 뒤의 Ingress Controller가 호스트 기반(Host: api.example.com→api-service, app.example.com→app-service)과 경로 기반(example.com/api→api-service, example.com/→frontend-service)으로 트래픽을 여러 서비스에 L7 분배한다. 서비스마다 LoadBalancer를 만들면 비용·IP가 낭비되지만 Ingress는 LB 하나로 다중화하고 TLS 인증서도 한 곳에서 종단한다확대

서로 다른 서브도메인을 다른 서비스로 라우팅합니다.

YAML
# 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에 연결합니다.

TLS 종료(Termination) — 클라이언트의 HTTPS(:443)를 Ingress Controller가 받아 인증서(tls.secretName)로 복호화하고, 클러스터 내부는 평문 HTTP(:80)로 Service·Pod에 전달. 각 서비스가 TLS를 처리할 필요 없이 인증서를 Ingress 한 곳에서 관리. ssl-redirect로 HTTP→HTTPS 자동 전환, cert-manager로 Let's Encrypt 자동 갱신확대

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

YAML
# 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
Kubernetes
kubectl apply -f tls-ingress.yaml

# HTTPS 테스트 (자체 서명 인증서인 격리된 실습 환경에서만 -k 사용)
curl -k https://app.example.com/api
# Hello from API service

# 운영·공유 환경에서는 검증한 CA를 사용하고 -k를 쓰지 않습니다.
# curl --cacert /path/to/verified-ca.crt https://app.example.com/api

# 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 인증서를 자동 발급·갱신합니다.

YAML
# 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 로그 확인

Kubernetes
# 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 확인

Kubernetes
# 업스트림 Service의 Endpoints 확인
kubectl get endpoints api-service -n ingress-lab
# NAME          ENDPOINTS   AGE
# api-service   <none>      5m   ← Endpoints 없음!

진단 3: 파드 Readiness Probe 상태 확인

Kubernetes
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 경로/포트 확인

Kubernetes
# 실제 파드가 리스닝하는 포트 확인
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 설정 수정 (올바른 경로와 포트로)
YAML
# Deployment에 올바른 Readiness Probe 설정
spec:
  containers:
    - name: api
      readinessProbe:
        httpGet:
          path: /           # 실제 응답하는 경로
          port: 5678        # 컨테이너가 리스닝하는 포트
        initialDelaySeconds: 5
        periodSeconds: 10

방법 2: 애플리케이션이 시작 전 probe 실패 — 지연 추가

YAML
readinessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 15    # 앱 시작까지 기다리는 시간 증가
  periodSeconds: 5
  failureThreshold: 3

방법 3: Readiness Probe 없이 즉시 확인 (임시)

Kubernetes
# 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 등록됨!

예방 체크리스트

Kubernetes
# 배포 후 필수 확인 순서
# 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

심화 — Controller가 규칙과 파드를 실시간으로 좇는 법

💡개념

심화: Ingress Controller는 어떻게 트래픽을 넘기나 — watch·reload·동적 엔드포인트

Ingress 리소스는 규칙 선언일 뿐이고 실제 라우팅은 Controller가 한다는 건 배웠습니다. 하지만 그 Controller가 규칙과 파드 변화를 어떻게 실시간으로 반영하는지 모르면, 배포 중 튀는 502나 타임아웃의 진짜 원인을 짚지 못합니다.

  • watch → 설정 생성: ingress-nginx는 API 서버의 Ingress·Service·Endpoints(EndpointSlice)를 watch하다가 변화가 생기면 nginx 설정을 다시 만듭니다. host·path 규칙과 annotation은 각각 대응하는 nginx 지시어로 번역됩니다.
  • reload vs 동적 업데이트: 초기엔 변경마다 nginx를 reload했는데, 파드가 자주 바뀌는 환경에선 reload가 잦아 커넥션이 끊길 수 있습니다. 그래서 최신 ingress-nginx는 업스트림 엔드포인트 목록 변화는 Lua로 reload 없이 동적으로 갱신하고, 설정 구조 자체가 바뀔 때만 reload합니다.
  • 그래도 남는 전파 지연: 파드가 종료되어 Endpoints에서 빠지는 사건이 API→컨트롤러→업스트림 목록으로 퍼지는 데엔 짧은 지연이 있습니다. 이 지연 창에 들어온 요청은 이미 죽어 가는 파드로 갈 수 있습니다.
  • annotation = nginx 지시어: proxy-read-timeout, proxy-body-size, rewrite-target 같은 annotation은 각각 nginx 지시어 하나로 매핑됩니다. 타임아웃(기본 60초)·요청 바디 크기(기본 1MB) 같은 기본값을 모르면 504·413을 엉뚱한 데서 찾게 됩니다.

정리하면 Controller는 "규칙과 파드의 현재 상태를 끊임없이 좇아 nginx 설정으로 번역하는 번역기"입니다. 그 번역이 언제·어떻게 반영되는지가 배포 중 안정성을 좌우합니다.

상황: 롤링 업데이트를 돌릴 때마다 그 수 초 동안 클라이언트에 502·연결 리셋이 산발적으로 뜨고, 새 파드가 다 뜨고 나면 사라집니다. 파드 readiness는 정상이고 평상시엔 전혀 문제가 없습니다.

원인: 파드가 종료될 때 두 가지가 동시에 벌어집니다 — kubelet이 컨테이너에 SIGTERM을 보내는 것과, 그 파드가 Endpoints에서 빠졌다는 사실이 Ingress Controller의 업스트림 목록까지 전파되는 것. 이 둘엔 짧은 시차가 있어서, 앱이 SIGTERM에 곧바로 리스너를 닫아 버리면 아직 자기를 업스트림으로 알고 요청을 보내는 컨트롤러의 커넥션이 거부돼 502·리셋이 됩니다. 즉 readiness 실패가 아니라 '종료 순간의 전파 경쟁'이 원인입니다.

진단: 502가 특정 파드의 종료 시각과 겹치는지 확인합니다 — 컨트롤러 로그의 업스트림 연결 에러와 kubectl get events의 파드 Killing 이벤트 타임스탬프를 맞춰 봅니다. 평상시엔 0건이고 오직 롤아웃 순간에만 튄다면 전파 경쟁이 유력합니다.

해결: 파드에 preStop 훅으로 짧게(예: 5~10초) 대기시켜, Endpoints 제거가 컨트롤러까지 전파될 시간을 벌고 그동안 진행 중 요청을 마저 처리한 뒤 종료하게 합니다. 앱은 SIGTERM에서 즉시 죽지 말고 graceful shutdown으로 새 연결만 거절하며 진행 중 요청을 비웁니다. terminationGracePeriodSeconds를 넉넉히 주고, 필요하면 컨트롤러가 죽은 업스트림에 대해 다음 파드로 재시도하도록(proxy-next-upstream) 둡니다.


💼
실무 맥락
현업 패턴

실무에서 Ingress를 구성하는 방식

팀별 Ingress 분리 vs 중앙 관리

YAML
# 패턴 1: 팀마다 자체 Ingress (namespace 분리)
# team-a 네임스페이스에 team-a 서비스용 Ingress
# team-b 네임스페이스에 team-b 서비스용 Ingress

# 패턴 2: 중앙 Ingress (단일 진입점)
# 모든 라우팅을 infra 팀이 관리하는 Ingress 하나로 통합

대부분의 팀은 패턴 1을 선호합니다. 네임스페이스 격리로 팀 간 영향이 줄고, 각 팀이 자체 배포 파이프라인으로 Ingress를 관리할 수 있습니다.

Canary 배포와 Ingress

YAML
# 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 방어

YAML
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)가 설정되어 있는가?

정리

Kubernetes
# 주요 진단 명령어

# 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"
실습 단계
1

nginx-ingress Controller 설치 확인

kubectl get pods -n ingress-nginx kubectl get ingressclass

예상 출력

NAME                                      READY   STATUS    AGE
ingress-nginx-controller-xxx              1/1     Running   2m
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       2m
2

백엔드 서비스 2개 배포

kubectl apply -f - <<'EOF' apiVersion: apps/v1 kind: Deployment metadata: name: app-v1 spec: replicas: 1 selector: matchLabels: app: app-v1 template: metadata: labels: app: app-v1 spec: containers: - name: app image: hashicorp/http-echo args: ["-text=Hello from v1"] ports: - containerPort: 5678 --- apiVersion: v1 kind: Service metadata: name: app-v1-svc spec: selector: app: app-v1 ports: - port: 80 targetPort: 5678 EOF kubectl get svc app-v1-svc

예상 출력

NAME          TYPE        CLUSTER-IP     PORT(S)
app-v1-svc   ClusterIP   10.96.x.x      80/TCP
3

경로 기반 라우팅 Ingress 생성

kubectl apply -f - <<'EOF' apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx rules: - host: demo.local http: paths: - path: /v1 pathType: Prefix backend: service: name: app-v1-svc port: number: 80 EOF kubectl describe ingress demo-ingress

예상 출력

Name:             demo-ingress
Rules:
  Host        Path  Backends
  ----        ----  --------
  demo.local
              /v1   app-v1-svc:80
4

Ingress를 통한 라우팅 확인

kubectl get ingress demo-ingress kubectl get endpoints app-v1-svc

예상 출력

NAME           CLASS   HOSTS        ADDRESS     PORTS
demo-ingress   nginx   demo.local   127.0.0.1   80
NAME          ENDPOINTS
app-v1-svc    10.244.x.x:5678
5

실습 리소스 정리

kubectl delete ingress demo-ingress kubectl delete deployment app-v1 kubectl delete svc app-v1-svc

예상 출력

ingress.networking.k8s.io "demo-ingress" deleted
deployment.apps "app-v1" deleted
service "app-v1-svc" deleted

명령어·단축키 빠른 참조

이 모듈에서 다룬 Ingress 라우팅·TLS·502 진단 명령을 실전 옵션과 함께 모았습니다.

명령어/단축키용도자주 쓰는 예
kubectl get ingressADDRESS·HOSTS·CLASS 확인kubectl get ingress -n ingress-lab (ADDRESS 비면 LB 미할당)
kubectl describe ingress규칙·백엔드·TLS 매핑 확인kubectl describe ingress my-ingress (Backends에 svc:port)
kubectl get endpoints백엔드 Service 실엔드포인트kubectl get endpoints api-service (비어 있으면 502 원인)
kubectl logs (controller)nginx upstream 오류 추적kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -f
kubectl get pods -n ingress-nginx컨트롤러 파드 Running 확인kubectl get pods -n ingress-nginx
kubectl get ingressclass사용 가능한 클래스 확인kubectl get ingressclass (spec.ingressClassName에 지정)
kubectl create secret tlsTLS 인증서 Secret 생성kubectl create secret tls app-tls --key tls.key --cert tls.crt
kubectl exec -- ss -tlnp파드 리슨 포트 확인(probe 진단)kubectl exec -it api-app-xxx -- ss -tlnp
kubectl rollout restart백엔드 재배포로 endpoints 재등록kubectl rollout restart deploy/api-app -n ingress-lab
curl -k / -v격리된 자체서명 테스트·리다이렉트 확인테스트 전용 curl -k; 운영은 curl --cacert <verified-ca>

관련 모듈로 더 깊이:

다음 챕터에서는 ConfigMap과 Secret으로 애플리케이션 설정을 파드에 주입하는 방법을 학습합니다.

지식 확인

퀴즈 — 8문제

Q1

Ingress 리소스를 kubectl apply 했는데 외부에서 접근이 전혀 되지 않는다. Pod와 Service는 정상인데 왜 그럴까?

Q2

502 Bad Gateway 오류의 가장 흔한 원인은?

Q3

TLS 종단(TLS Termination)이 Ingress에서 처리될 때 백엔드 파드와의 통신은?

Q4

하나의 Ingress에서 api.example.com과 app.example.com을 다른 서비스로 라우팅하려면?

Q5

Ingress와 Service(LoadBalancer)의 역할 차이로 옳은 것은?

Q6

Ingress 리소스(규칙)만 apply하고 Ingress Controller를 설치하지 않으면 어떻게 되는가?

Q7

[심화] ingress-nginx 컨트롤러가 백엔드 파드 목록(Endpoints) 변화를 반영하는 방식으로 옳은 것은?

Q8

[심화] 롤링 배포 순간에만 몇 초간 502와 커넥션 리셋이 산발적으로 튀고 평상시엔 정상이다. 파드 readiness도 정상이다. 가장 적절한 조치는?

0 / 8 답변

🧪 실습으로 확인하기

Kubernetes Ingress 라우팅 — 404/503 디버깅

중급

port-forward로 파드는 되는데 도메인 접속만 404/503이다. Ingress 라우팅은 Ingress→Service→Endpoints(Pod)의 사슬이다. 어느 고리가 끊겼는지(호스트/경로 규칙, 서비스 셀렉터, 엔드포인트 비어있음, Ingress 컨트롤러)를 단계별로 좁혀 특정하고 복구한다.

45📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요