k8s

Nginx Ingress Controller

Luuuuu 2025. 4. 27. 16:46

1. Nginx Ingress Controller의 등장 배경

1.1 C10K 문제와 이벤트 기반 아키텍처

인터넷 발전 초기 단계에서 대부분의 웹 서버는 연결당 하나의 프로세스나 스레드를 할당하는 방식으로 동작했습니다. 이는 적은 수의 연결에서는 잘 작동했지만, 동시 연결 수가 증가하면서 한계에 부딪혔습니다.

1990년대 후반, Dan Kegel은 "C10K 문제"라는 용어를 만들었습니다. 이는 서버가 10,000개의 동시 연결(Connections 10K)을 처리하는 과제를 의미합니다. 하드웨어 자원이 충분함에도 불구하고 I/O 처리 방식의 한계로 인해 서버가 제대로 처리하지 못하는 현상이었습니다.

 
C10K 문제의 핵심 원인:
1. 연결당 하나의 프로세스/스레드 모델의 비효율성
2. 커널의 I/O 처리 방식(select/poll)의 O(n) 복잡도
3. 컨텍스트 스위칭 오버헤드

Apache와 같은 전통적인 웹 서버는 MPM(Multi-Processing Module) 방식을 사용했습니다:

  • prefork MPM: 각 연결마다 별도의 프로세스를 생성
  • worker MPM: 프로세스와 스레드를 혼합해서 사용
  • event MPM: 이벤트 기반 처리로 발전(하지만 근본적인 설계 한계 존재)

이러한 배경에서 2004년 Igor Sysoev가 Nginx를 개발했습니다. Nginx는 처음부터 C10K 문제를 해결하기 위해 설계된 이벤트 기반 비동기 아키텍처를 채택했습니다.

1.2 Nginx의 이벤트 기반 아키텍처

Nginx는 C10K 문제를 해결하기 위해 다음과 같은 접근 방식을 채택했습니다:

  1. 마스터-워커 프로세스 모델: 설정을 관리하는 마스터 프로세스와 실제 요청을 처리하는 워커 프로세스로 구성
  2. 이벤트 기반 처리: 각 워커 프로세스가 이벤트 루프를 사용하여 수천 개의 연결을 동시에 처리
  3. 비차단(non-blocking) I/O: 시스템 리소스를 효율적으로 사용
  4. OS 수준의 이벤트 통지 메커니즘 활용: epoll(Linux), kqueue(BSD), event ports(Solaris) 등을 사용

Nginx의 이벤트 기반 아키텍처는 다음과 같은 장점을 제공합니다:

  • 적은 수의 워커 프로세스로 수만 개의 동시 연결 처리
  • 메모리 사용량 최소화(Apache는 연결당 약 1MB, Nginx는 연결당 약 2.5KB)
  • 느린 클라이언트 문제 해결(head-of-line blocking 방지)
  • 높은 처리량과 낮은 지연 시간

이러한 Nginx의 특성은 쿠버네티스 환경에서 Ingress Controller로서 이상적인 후보가 되게 했습니다.

1.3 쿠버네티스와 인그레스의 만남

쿠버네티스가 컨테이너 오케스트레이션 플랫폼으로 부상하면서, HTTP/HTTPS 트래픽을 클러스터 내부 서비스로 라우팅하는 표준화된 방법이 필요했습니다. 이러한 요구를 충족시키기 위해 쿠버네티스는 Ingress 리소스를 도입했지만, 이 리소스를 실제로 구현하는 컨트롤러는 별도로 필요했습니다.

Nginx의 뛰어난 성능, 확장성, 그리고 풍부한 기능 세트는 이를 인그레스 컨트롤러로 사용하는 강력한 이유가 되었습니다. 따라서 Nginx Ingress Controller는 쿠버네티스 생태계에서 가장 인기 있는 인그레스 구현체 중 하나가 되었습니다.

2. 인그레스 기본 개념과 필요성

2.1 인그레스란 무엇인가?

쿠버네티스에서 파드를 외부로 노출시키기 위해서는 기본적으로 Service를 사용합니다. 하지만 여러 서비스를 외부로 노출시키려면 각 서비스마다 별도의 로드밸런서를 구축해야 합니다. 이는 비용 측면에서 비효율적일 수 있습니다.

인그레스(Ingress)는 이러한 문제를 해결하기 위한 쿠버네티스 객체로, 간단히 말하면 HTTP와 HTTPS 트래픽을 클러스터 내부 서비스로 라우팅하는 규칙 모음입니다. 인그레스는 Layer 7(HTTP/HTTPS) 수준에서 작동하는 로드밸런서로, 호스트 이름과 경로를 기반으로 트래픽을 다양한 서비스로 라우팅할 수 있습니다.

이미지 표시

그림: 인그레스 없이 각 서비스마다 별도의 로드밸런서를 사용하는 구조

이미지 표시

그림: 인그레스를 사용하여 여러 서비스를 하나의 로드밸런서로 관리하는 구조

2.2 인그레스 구성 요소

인그레스는 두 가지 주요 구성 요소로 이루어져 있습니다:

  1. 인그레스 리소스(Ingress Resource): 호스트나 URL 경로 기반의 라우팅 규칙을 정의하는 쿠버네티스 리소스
  2. 인그레스 컨트롤러(Ingress Controller): 인그레스 리소스에서 정의한 규칙을 기반으로 실제 트래픽을 라우팅하는 컨트롤러

인그레스를 사용하기 위해서는 반드시 인그레스 컨트롤러가 클러스터에 배포되어 있어야 합니다. 쿠버네티스는 기본적으로 인그레스 컨트롤러를 제공하지 않으므로, 사용자가 직접 설치해야 합니다. 대표적인 인그레스 컨트롤러로는 Nginx Ingress Controller, Istio, Kong, HAProxy 등이 있습니다.

2.3 인그레스 vs 서비스

인그레스와in 서비스 모두 파드를 쿠버네티스 외부로 노출시키는 역할을 합니다. 그러나 서비스에 비해 인그레스가 가지는 장점은 다음과 같습니다:

  1. 비용 효율성: 하나의 로드밸런서만으로 여러 서비스를 외부에 노출시킬 수 있습니다.
  2. 고급 트래픽 라우팅: 호스트 이름, 경로, 헤더 등 다양한 조건을 기반으로 트래픽을 라우팅할 수 있습니다.
  3. 중앙 집중식 관리: 모든 라우팅 규칙을 한 곳에서 관리할 수 있습니다.
  4. SSL/TLS 종료: 인그레스 컨트롤러 레벨에서 SSL/TLS 인증서를 관리하고 종료할 수 있습니다.
  5. 통합 로깅 및 모니터링: 클러스터에 들어오는 모든 트래픽을 인그레스 레벨에서 로깅하고 모니터링할 수 있습니다.

3. Nginx Ingress의 아키텍처와 동작 원리

3.1 Nginx Ingress Controller의 구성 요소

Nginx Ingress Controller는 다음과 같은 주요 구성 요소로 이루어져 있습니다:

이미지 표시

그림: Nginx Ingress Controller의 고수준 아키텍처

  1. IC 프로세스(Controller Process):
    • 쿠버네티스 API 서버를 감시하고 Ingress, Service, Endpoints 등의 리소스 변경을 감지
    • 감지된 변경 사항을 기반으로 Nginx 설정 파일을 동적으로 생성
    • NGINX 프로세스를 제어(시작, 재로드, 종료)
  2. NGINX Master 프로세스:
    • NGINX 설정 파일을 읽고 유효성 검사
    • Worker 프로세스의 라이프사이클을 관리
    • 설정 변경 시 새 Worker 프로세스를 생성하고 기존 Worker 프로세스를 종료
  3. NGINX Worker 프로세스:
    • 실제 클라이언트 트래픽을 처리
    • 요청을 백엔드 서비스로 프록시
    • 비차단 I/O와 이벤트 루프를 사용하여 수천 개의 연결을 동시에 처리
  4. 기본 백엔드 서비스: 경로 규칙과 일치하지 않는 요청을 처리하는 폴백 서비스

3.2 NGINX Ingress Controller Pod 구조

Nginx Ingress Controller Pod는 다음 구성 요소를 포함하는 단일 컨테이너로 구성됩니다:

 

(https://nginxstore.com/docs/nginx-ingress-controller-documentation/nginx-ingress-controller-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D/)

이 구성 요소들 간의 주요 상호작용은 다음과 같습니다:

  1. IC 프로세스는 쿠버네티스 API를 지속적으로 모니터링하며 관련 리소스의 변경 사항을 감지합니다.
  2. 변경이 감지되면 IC 프로세스는 새로운 NGINX 설정 파일을 생성하고 파일 시스템에 기록합니다.
  3. IC 프로세스는 NGINX Master 프로세스에 재로드 신호를 보냅니다.
  4. NGINX Master 프로세스는 새 설정을 로드하고 새로운 Worker 프로세스를 시작합니다.
  5. 기존 Worker 프로세스는 기존 연결을 처리한 후 점진적으로 종료됩니다.
  6. 새 Worker 프로세스는 새로운 설정으로 새 연결을 처리합니다.

3.3 새로운 인그레스 리소스 처리 과정

NGINX Ingress Controller가 새로운 인그레스 리소스를 처리하는 과정을 단계별로 살펴보겠습니다:

이미지 표시

그림: 새로운 인그레스 리소스 처리 과정

  1. 사용자가 쿠버네티스 API를 통해 새로운 인그레스 리소스를 생성합니다.
  2. IC 프로세스는 리소스 캐시를 통해 이 변경을 감지합니다.
  3. 컨트롤 루프가 시작되어 캐시에서 인그레스 리소스의 최신 버전을 가져옵니다.
  4. 컨트롤 루프는 인그레스 리소스가 참조하는 다른 리소스(예: TLS 시크릿, 서비스, 엔드포인트)도 가져옵니다.
  5. TLS 시크릿이 있는 경우, 인증서와 키를 파일 시스템에 기록합니다.
  6. 인그레스 리소스에 해당하는 NGINX 설정 파일을 생성하여 파일 시스템에 기록합니다.
  7. NGINX를 재로드하고 성공적으로 재로드될 때까지 기다립니다.
  8. NGINX는 TLS 인증서와 키, 그리고 새 설정 파일을 읽습니다.
  9. 컨트롤 루프는 인그레스 리소스의 상태를 업데이트하고 이벤트를 발행합니다.

(https://nginxstore.com/docs/nginx-ingress-controller-documentation/nginx-ingress-controller-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D/)

2.2 Nginx Ingress의 동작 흐름

Nginx Ingress Controller가 트래픽을 처리하는 전체 흐름은 다음과 같습니다:

  1. 설정 감시 및 로드:
    • 컨트롤러는 쿠버네티스 API 서버를 지속적으로 감시
    • Ingress, Service, Secret 등의 리소스 변경을 감지하면 설정 파일 생성
    • 생성된 설정을 Nginx 마스터 프로세스에 로드 (설정 재로드)
  2. 트래픽 처리:
    • 클라이언트 요청이 Nginx Ingress Controller에 도달
    • Nginx 워커 프로세스가 이벤트 루프를 사용하여 요청 처리
    • 요청 헤더와 URL을 분석하여 적절한 백엔드 서비스 결정
    • 요청을 해당 서비스의 Pod로 프록시
    • 응답을 클라이언트에게 반환
  3. 동적 업데이트:
    • 백엔드 서비스의 Pod가 추가되거나 제거되면 Endpoint 변경 감지
    • 설정 파일을 자동으로 업데이트하고 무중단 재로드

2.3 Nginx의 이벤트 루프와 비동기 처리

Nginx Ingress Controller의 성능은 Nginx 코어의 이벤트 기반 아키텍처에 크게 의존합니다:

// Nginx의 이벤트 처리 핵심 코드 (간략화)
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    // 타이머 이벤트 처리
    
    // 커널 이벤트 기다리기 (epoll, kqueue 등)
    (void) ngx_process_events(cycle, timer, flags);
    
    // 이벤트 핸들러 실행
    ngx_event_process_posted();
}

이 이벤트 루프의 핵심 특징은 다음과 같습니다:

  1. 비차단 I/O: 모든 소켓 작업은 비차단 모드로 설정되어 워커 프로세스가 I/O 작업을 기다리며 차단되지 않음
  2. 이벤트 통지 메커니즘: epoll(Linux), kqueue(BSD), event ports(Solaris) 등의 효율적인 I/O 이벤트 통지 메커니즘 사용
  3. 상태 기계(State Machine): 각 연결은 상태 기계로 모델링되며, 이벤트 발생 시 적절한 핸들러가 호출됨
  4. 타이머 이벤트: 연결 타임아웃, keepalive 등을 처리하기 위한 타이머 이벤트 관리

이 아키텍처는 C10K 문제를 해결할 뿐 아니라, 현대적인 웹 애플리케이션에서 요구하는 C100K나 심지어 C1M(백만 동시 연결) 시나리오까지 확장 가능하게 합니다.

3. Nginx Ingress Controller의 핵심 기능

3.1 경로 기반 라우팅과 호스트 기반 라우팅

Nginx Ingress Controller는 다양한 라우팅 옵션을 제공합니다:

경로 기반 라우팅 예제:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: path-routing-example
spec:
  rules:
  - http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
      - path: /admin
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 80

호스트 기반 라우팅 예제:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: host-routing-example
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
  - host: admin.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 80

pathType 필드 설명:

  • Exact: 정확히 일치하는 경로만 매칭 (예: /api는 정확히 /api만 매칭)
  • Prefix: 지정된 접두사로 시작하는 경로 매칭 (예: /api는 /api, /api/v1, /api/users 등을 매칭)
  • ImplementationSpecific: 인그레스 컨트롤러 구현에 따른 매칭

3.2 어노테이션을 통한 고급 설정

Nginx Ingress Controller는 다양한 어노테이션을 통해 세밀한 제어가 가능합니다:

URL 리다이렉션:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - host: example.com
    http:
      paths:
      - path: /api(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80

위 설정은 /api/users로의 요청을 백엔드 서비스에 /users로 전달합니다.

세션 어피니티:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "INGRESSCOOKIE"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"

속도 제한(Rate Limiting):

metadata:
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "10"
    nginx.ingress.kubernetes.io/limit-connections: "5"
    nginx.ingress.kubernetes.io/limit-rate: "100k"
    nginx.ingress.kubernetes.io/limit-rate-after: "10m"

CORS 설정:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com"

3.3 인증 및 권한 부여

Nginx Ingress Controller는 다양한 인증 메커니즘을 지원합니다:

Basic 인증:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"

basic-auth 시크릿은 htpasswd 형식의 파일을 포함해야 합니다:

apiVersion: v1
kind: Secret
metadata:
  name: basic-auth
type: Opaque
data:
  auth: <base64-encoded-htpasswd-file>

외부 인증 서비스:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "https://authservice.example.com/auth"
    nginx.ingress.kubernetes.io/auth-method: "GET"
    nginx.ingress.kubernetes.io/auth-response-headers: "X-User,X-Group"

OAuth2 프록시 통합:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "https://oauth2-proxy.example.com/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "https://oauth2-proxy.example.com/oauth2/start?rd=$escaped_request_uri"

3.4 카나리 배포

Nginx Ingress Controller는 카나리 배포를 위한 트래픽 분할 기능을 제공합니다:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "30"
    nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
    nginx.ingress.kubernetes.io/canary-by-header-value: "always"

위 설정은:

  • 30%의 트래픽을 카나리 버전으로 라우팅
  • X-Canary: always 헤더가 있는 요청은 항상 카나리 버전으로 라우팅

이를 이용한 전체 카나리 배포 시나리오:

  1. 기본 인그레스 설정 (100% 트래픽을 기존 서비스로 라우팅)
  2. 카나리 인그레스 배포 (처음에는 weight를 낮게 설정, 예: 5%)
  3. 점진적으로 카나리 weight 증가 (5% → 20% → 50% → 100%)
  4. 문제가 없으면 기존 인그레스를 카나리 설정으로 교체

3.5 WebSocket 지원

WebSocket 프로토콜은 실시간 애플리케이션에 중요합니다. Nginx Ingress Controller는 기본적으로 WebSocket을 지원하며, 다음과 같은 특수 설정으로 최적화할 수 있습니다:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"

이 설정은 WebSocket 연결이 타임아웃 없이 장시간 유지될 수 있도록 합니다(예: 1시간 = 3600초).

# 오류 로그 조회
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=100

# 특정 IP의 요청 추적
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx | grep 192.168.1.1

# 특정 URL 패턴의 요청 분석
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx | grep "/api/v1" | grep -v 200