쿠버네티스 인그레스(Ingress)
1. 인그레스를 사용하는 이유
쿠버네티스 환경에서 애플리케이션을 외부에 노출시키는 방법은 여러 가지가 있습니다. 기본적인 Service 리소스(NodePort, LoadBalancer, ClusterIP)만으로도 기본적인 네트워킹 요구사항을 충족할 수 있지만, 복잡한 프로덕션 환경에서는 한계점이 명확해집니다.
인그레스(Ingress)를 사용하는 근본적인 이유는 Layer 7(HTTP/HTTPS) 수준의 라우팅과 로드 밸런싱 기능을 제공하기 위함입니다. 기존 Service만으로는 구현하기 어려운 고급 트래픽 제어 기능이 필요할 때 인그레스가 그 해결책이 됩니다.
1.1 비용 최적화
클라우드 환경에서 LoadBalancer 타입의 Service를 사용하면 각 서비스마다 별도의 로드 밸런서가 프로비저닝되어 비용이 증가합니다. 예를 들어, AWS에서는 각 LoadBalancer 서비스마다 별도의 ELB(Elastic Load Balancer)가 생성되며, 이는 상당한 비용을 발생시킵니다. 인그레스를 사용하면 단일 로드 밸런서로 여러 서비스를 노출할 수 있어 비용을 크게 절감할 수 있습니다.
LoadBalancer 서비스 10개 사용 시: 10 × ELB 비용
인그레스 컨트롤러 1개 사용 시: 1 × ELB 비용
1.2 고급 라우팅 기능
인그레스는 다음과 같은 고급 라우팅 기능을 제공합니다:
- 경로 기반 라우팅(Path-based routing):
/api
로 시작하는 요청은 API 서비스로,/admin
으로 시작하는 요청은 관리 서비스로 라우팅할 수 있습니다. - 호스트 기반 라우팅(Host-based routing):
api.example.com
은 API 서비스로,admin.example.com
은 관리 서비스로 라우팅할 수 있습니다. - TLS 종료(TLS termination): HTTPS 연결을 종료하고 내부 서비스로는 암호화되지 않은 트래픽을 전달할 수 있습니다.
1.3 중앙화된 라우팅 관리
인그레스를 사용하면 모든 라우팅 규칙을 한 곳에서 관리할 수 있습니다. 이는 마이크로서비스 아키텍처에서 특히 중요합니다. 수십 또는 수백 개의 서비스가 있는 환경에서는 라우팅 규칙을 중앙에서 관리하는 것이 운영 복잡성을 크게 줄여줍니다.
1.4 외부 시스템과의 통합
인그레스 컨트롤러는 외부 모니터링 시스템, 로그 수집 시스템, 보안 시스템 등과 통합될 수 있습니다. 예를 들어, NGINX 인그레스 컨트롤러는 Prometheus와 통합되어 상세한 메트릭을 제공하고, OpenTracing을 지원하여 분산 추적 기능을 제공합니다.
2. 인그레스의 구조
인그레스 시스템은 크게 두 부분으로 구성됩니다: 인그레스 리소스(Ingress Resource)와 인그레스 컨트롤러(Ingress Controller)입니다. 이 두 요소의 상호작용을 이해하는 것이 인그레스를 효과적으로 사용하는 데 필수적입니다.
2.1 인그레스 리소스
인그레스 리소스는 쿠버네티스 API 객체로, 클러스터 외부에서 내부 서비스로의 HTTP/HTTPS 라우팅 규칙을 정의합니다. 이는 선언적 방식으로 작성된 YAML 또는 JSON 형식의 매니페스트로 표현됩니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: example.com
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
tls:
- hosts:
- example.com
secretName: example-tls-secret
인그레스 리소스 구조는 다음과 같은 주요 구성 요소로 이루어져 있습니다:
- metadata: 인그레스의 이름과 주석(annotations) 등을 포함합니다. 주석은 인그레스 컨트롤러별 기능을 구성하는 데 사용됩니다.
- spec.rules: 호스트 및 경로 기반 라우팅 규칙을 정의합니다.
- spec.tls: TLS 구성을 정의하며, 인증서가 저장된 시크릿을 참조합니다.
- spec.defaultBackend: 규칙과 일치하지 않는 요청을 처리할 기본 서비스를 지정합니다.
2.2 인그레스 컨트롤러
인그레스 리소스 자체는 아무 작업도 수행하지 않습니다. 인그레스 규칙을 실제로 구현하는 것은 인그레스 컨트롤러의 역할입니다. 인그레스 컨트롤러는 쿠버네티스 API를 지속적으로 모니터링하며, 인그레스 리소스에 정의된 규칙에 따라 트래픽을 라우팅합니다.
인그레스 컨트롤러의 주요 구성 요소:
- 컨트롤러 프로세스: 쿠버네티스 API를 감시하고 라우팅 구성을 생성합니다.
- 데이터 플레인: 실제로 트래픽을 처리하는 프록시 서버입니다(예: NGINX, HAProxy).
- 설정 템플릿: 인그레스 리소스를 프록시 서버 구성으로 변환하는 템플릿입니다.
대표적인 인그레스 컨트롤러:
- NGINX Ingress Controller: NGINX를 기반으로 한 가장 널리 사용되는 컨트롤러입니다.
- Traefik: Go로 작성된 모던한 HTTP 리버스 프록시 및 로드 밸런서입니다.
- HAProxy Ingress: 고성능 로드 밸런서 HAProxy를 기반으로 합니다.
- Ambassador: Envoy 프록시를 기반으로 한 API 게이트웨이입니다.
- Istio Ingress Gateway: 서비스 메시 Istio의 일부로 제공됩니다.
2.3 인그레스 컨트롤 플로우
외부 요청이 인그레스를 통해 내부 서비스에 도달하는 과정은 다음과 같습니다:
- 클라이언트가 인그레스 컨트롤러의 외부 IP 또는 로드 밸런서로 요청을 보냅니다.
- 인그레스 컨트롤러는 요청의 호스트 헤더와 URL 경로를 검사합니다.
- 컨트롤러는 요청을 인그레스 규칙과 대조하여 적절한 백엔드 서비스를 결정합니다.
- 컨트롤러는 해당 서비스의 엔드포인트(Pod IP 및 포트)를 쿠버네티스 API에서 조회합니다.
- 컨트롤러는 요청을 적절한 Pod로 프록시합니다.
- Pod에서의 응답은 역순으로 클라이언트에게 전달됩니다.
이 플로우는 다음과 같은 다이어그램으로 표현할 수 있습니다:
외부 클라이언트 → 로드 밸런서 → 인그레스 컨트롤러 Pod →
쿠버네티스 서비스 → 애플리케이션 Pod
3. 인그레스의 세부기능
인그레스는 단순한 라우팅 이상의 다양한 고급 기능을 제공합니다. 이러한 기능들은 주로 인그레스 컨트롤러의 구현체에 따라 달라지며, 주석(annotations)을 통해 구성됩니다.
3.1 경로 및 호스트 기반 라우팅
인그레스의 가장 기본적인 기능은 URL 경로와 호스트 헤더를 기반으로 한 라우팅입니다.
3.1.1 경로 기반 라우팅
URL 경로에 따라 다른 백엔드 서비스로 라우팅할 수 있습니다:
spec:
rules:
- http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /web
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
pathType
필드는 경로 매칭 방식을 지정합니다:
Exact
: 정확히 일치하는 경로만 매칭Prefix
: 지정된 접두사로 시작하는 경로 매칭ImplementationSpecific
: 인그레스 컨트롤러 구현에 따른 매칭
3.1.2 호스트 기반 라우팅
호스트 헤더에 따라 다른 백엔드 서비스로 라우팅할 수 있습니다:
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
3.2 세션 어피니티
세션 어피니티(Session Affinity)는 동일한 클라이언트에서 오는 연속적인 요청이 동일한 백엔드 Pod으로 라우팅되도록 합니다. 이는 상태 유지형(stateful) 애플리케이션에서 중요합니다.
NGINX 인그레스 컨트롤러에서는 다음과 같이 세션 어피니티를 구성할 수 있습니다:
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"
3.3 URL 리라이팅
URL 리라이팅은 인그레스 컨트롤러가 요청 URL을 백엔드 서비스로 전달하기 전에 변경하는 기능입니다. 이는 주로 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
로 전달됩니다.
3.4 속도 제한(Rate Limiting)
속도 제한은 특정 기간 내에 클라이언트가 보낼 수 있는 요청 수를 제한하는 기능입니다. 이는 DoS 공격으로부터 보호하고 리소스 과소비를 방지하는 데 유용합니다.
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"
3.5 인증
인그레스 컨트롤러는 Basic 인증, OAuth, JWT 등 다양한 인증 방식을 지원합니다.
3.5.1 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>
3.5.2 외부 인증 서비스
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"
3.6 카나리 배포
카나리 배포는 새 버전의 애플리케이션을 점진적으로 롤아웃하는 기술입니다. 인그레스 컨트롤러는 가중치 기반 또는 헤더 기반 카나리 라우팅을 지원합니다.
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
헤더가 있는 요청은 항상 카나리 버전으로 라우팅합니다.
3.7 커스텀 헤더 조작
인그레스 컨트롤러는 요청 및 응답 헤더를 추가, 수정, 삭제할 수 있습니다.
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-set-headers: "custom-headers"
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Frame-Options: DENY";
more_set_headers "X-Content-Type-Options: nosniff";
3.8 WebSocket 지원
WebSocket 프로토콜에 대한 지원은 실시간 애플리케이션에 중요합니다.
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
4. Nginx 인그레스 컨트롤러에 SSL/TLS 보안 연결 적용
보안은 현대 웹 애플리케이션에서 필수적인 요소입니다. Nginx 인그레스 컨트롤러는 강력한 SSL/TLS 보안 기능을 제공합니다.
4.1 TLS 인증서 옵션
인그레스에서 사용할 수 있는 TLS 인증서 옵션은 다양합니다:
- 자체 서명 인증서(Self-signed certificates): 개발 및 테스트 환경에 적합합니다.
- 공개 인증 기관(CA) 인증서: Let's Encrypt, DigiCert 등에서 발급받은 인증서입니다.
- 사설 인증 기관(CA) 인증서: 조직 내부 CA에서 발급받은 인증서입니다.
4.2 인증서 관리를 위한 쿠버네티스 시크릿 생성
TLS 인증서와 개인 키는 쿠버네티스 시크릿에 저장됩니다:
apiVersion: v1
kind: Secret
metadata:
name: example-tls-secret
namespace: default
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-certificate>
tls.key: <base64-encoded-private-key>
기존 인증서 파일로부터 시크릿을 생성하는 명령어:
kubectl create secret tls example-tls-secret --key privkey.pem --cert cert.pem
4.3 인그레스 리소스의 TLS 구성
인그레스 리소스에 TLS 구성을 추가하여 HTTPS를 활성화합니다:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: secure-ingress
spec:
tls:
- hosts:
- secure.example.com
secretName: example-tls-secret
rules:
- host: secure.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: secure-service
port:
number: 80
4.4 cert-manager를 사용한 자동 인증서 관리
cert-manager는 인증서 발급 및 갱신을 자동화하는 쿠버네티스 애드온입니다. 특히 Let's Encrypt와 같은 ACME 프로토콜을 지원하는 CA와 잘 통합됩니다.
4.4.1 cert-manager 설치
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml
4.4.2 ClusterIssuer 설정
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: admin@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
4.4.3 인그레스에 자동 인증서 발급 활성화
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: secure-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- secure.example.com
secretName: secure-example-com-tls # 자동 생성됨
rules:
- host: secure.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: secure-service
port:
number: 80
4.5 TLS 보안 강화
4.5.1 HTTP에서 HTTPS로 리다이렉션
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
4.5.2 TLS 버전 및 암호화 제어
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-protocols: "TLSv1.2 TLSv1.3"
nginx.ingress.kubernetes.io/ssl-ciphers: "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"
4.5.3 HSTS(HTTP Strict Transport Security) 설정
metadata:
annotations:
nginx.ingress.kubernetes.io/hsts: "true"
nginx.ingress.kubernetes.io/hsts-max-age: "31536000"
nginx.ingress.kubernetes.io/hsts-include-subdomains: "true"
nginx.ingress.kubernetes.io/hsts-preload: "true"
4.6 mTLS(Mutual TLS) 구성
mTLS는 클라이언트와 서버 양쪽 모두 인증서를 사용하여 서로를 인증하는 방식입니다. 이는 제로 트러스트 보안 모델에서 중요합니다.
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret"
nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"
nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
5. 여러개의 인그레스 컨트롤러 사용하기
대규모 쿠버네티스 환경에서는 여러 인그레스 컨트롤러를 동시에 사용하는 것이 일반적입니다. 이는 격리, 특화된 기능, 또는 조직적 요구사항 때문일 수 있습니다.
5.1 여러 인그레스 컨트롤러가 필요한 이유
- 트래픽 격리: 내부/외부 트래픽을 분리하여 보안을 강화할 수 있습니다.
- 특화된 기능: 특정 애플리케이션에 최적화된 컨트롤러를 사용할 수 있습니다.
- 책임 분리: 다른 팀이 다른 컨트롤러를 관리할 수 있습니다.
- 점진적 마이그레이션: 기존 컨트롤러에서 새 컨트롤러로 점진적으로 이전할 수 있습니다.
- 성능 최적화: 트래픽 유형에 따라 최적화된 컨트롤러를 사용할 수 있습니다.
5.2 IngressClass 리소스
쿠버네티스 v1.18부터 도입된 IngressClass 리소스는 여러 인그레스 컨트롤러를 관리하는 표준 방법을 제공합니다.
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx-external
annotations:
ingressclass.kubernetes.io/is-default-class: "false"
spec:
controller: nginx.org/ingress-controller
parameters:
apiGroup: k8s.nginx.org
kind: IngressParameters
name: external-nginx-parameters
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
ingressClassName: nginx-external
rules:
# ...
5.3 인그레스 컨트롤러 배포 전략
여러 인그레스 컨트롤러를 효과적으로 배포하는 전략은 다음과 같습니다:
5.3.1 네임스페이스 기반 분리
인그레스 컨트롤러를 특정 네임스페이스에서만 작동하도록 구성할 수 있습니다:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-internal
spec:
template:
spec:
containers:
- name: nginx-ingress-internal
args:
- /nginx-ingress-controller
- --watch-namespace=internal
5.3.2 레이블 또는 주석 기반 선택
인그레스 리소스의 레이블 또는 주석을 기반으로 컨트롤러를 선택할 수 있습니다:
metadata:
annotations:
kubernetes.io/ingress.class: "nginx-internal"
5.4 다양한 인그레스 컨트롤러 조합 사례
5.4.1 외부 및 내부 트래픽 분리
인터넷 → NGINX (external) → 공개 서비스
내부 네트워크 → NGINX (internal) → 내부 서비스
외부 컨트롤러는 공개 로드 밸런서에 연결되고, 내부 컨트롤러는 내부 로드 밸런서 또는 ClusterIP에 연결됩니다.
5.4.2 특수 기능 컨트롤러 조합
일반 HTTP/HTTPS 트래픽 → NGINX → 웹 애플리케이션
gRPC 트래픽 → Envoy → 마이크로서비스
GraphQL 트래픽 → Ambassador → API 서비스
5.4.3 팀별 컨트롤러 할당
팀 A 서비스 → 팀 A 관리 인그레스 컨트롤러
팀 B 서비스 → 팀 B 관리 인그레스 컨트롤러
5.5 여러 인그레스 컨트롤러 운영 고려사항
여러 인그레스 컨트롤러를 운영할 때 고려해야 할 사항들:
- 리소스 할당: 각 컨트롤러는 별도의 컴퓨팅 및 메모리 리소스를 필요로 합니다.
- 모니터링 및 알림: 각 컨트롤러에 대한 별도의 모니터링이 필요합니다.
- 구성 일관성: 여러 컨트롤러 간에 구성 일관성을 유지해야 합니다.
- 장애 조치 전략: 컨트롤러 장애 시 장애 조치 전략이 필요합니다.
- 업그레이드 관리: 컨트롤러 업그레이드를 조율해야 합니다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nginx-ingress-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: nginx-ingress
5.6 인그레스 컨트롤러 간 마이그레이션 전략
기존 인그레스 컨트롤러에서 새 컨트롤러로 마이그레이션하는 전략:
- 병렬 배포: 두 컨트롤러를 동시에 실행합니다.
- 카나리 테스트: 일부 트래픽을 새 컨트롤러로 라우팅합니다.
- 점진적 전환: 서비스를 점진적으로 새 컨트롤러로 이전합니다.
- 블루-그린 전환: 모든 트래픽을 한 번에 전환합니다.
카나리 기반 마이그레이션 예시:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: service-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
ingressClassName: new-controller
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80