K8s 중급 #3 Ingress와 Ingress Controller — 외부 진입점

18 분 소요

K8s 중급 시리즈의 세 번째 글입니다. #2까지 다룬 것이 클러스터 안의 데이터가 살아남는 모델이었다면, 이번 글의 시점은 클러스터 바깥으로 옮겨갑니다. 외부 트래픽이 어떻게 클러스터 안의 Service로 들어오는가의 모델입니다. 기초 #5의 LoadBalancer가 외부 진입의 표준이지만, 한 클러스터에 외부로 노출해야 할 Service가 수십 개라면 그만큼 LoadBalancer를 띄우는 일이 비용,관리 양쪽에서 부담입니다. 이번 글은 그 부담을 한곳에 모으는 객체 Ingress와, 그 매니페스트를 실제 라우팅으로 풀어 주는 Ingress Controller의 두 층을 한 사이클로 따라가겠습니다.

이번 시리즈는 K8s 중급 7편입니다.

LoadBalancer 한 개로는 풀리지 않는 것 — 출발점 #

기초 #5에서 잡은 LoadBalancer Service의 모양을 한 줄로 줄이면 다음과 같습니다 — type: LoadBalancer 한 줄이면 클라우드 제공자가 외부 LB를 자동으로 만들어 주고, 그 LB의 외부 IP로 한 Service의 백엔드 Pod들에게 트래픽이 흘러갑니다. 단순하고 충분히 강력한 모델입니다.

문제는 Service가 한 개를 넘어설 때부터 시작됩니다. 운영 클러스터의 흔한 모양을 그려 보면 이렇습니다.

  • web Service — 사용자 웹 페이지
  • api Service — REST API 백엔드
  • admin Service — 운영자용 관리 페이지
  • static Service — 정적 자산 전용

이 넷을 모두 외부에 노출하려고 각각 type: LoadBalancer를 붙이면 클라우드 LB가 네 개 생깁니다. AWS의 NLB,ALB든 GCP의 LB든 Azure의 LB든 LB는 그 자체로 시간당 비용이 있고, 이 비용이 Service 개수에 비례해 누적됩니다. Service가 수십 개라면 LB 비용이 무시 못 할 수준이 됩니다.

비용만 부담은 아닙니다. 운영 측면의 부담도 같이 늘어납니다.

  • 도메인을 LB 개수만큼 관리 — 외부에 보여 주는 도메인이 LB마다 따로 잡히는 모양이라, DNS 레코드도 그 수만큼 관리해야 합니다.
  • TLS 인증서를 LB마다 따로 — 인증서 발급,갱신을 LB 단위로 들고 있어야 합니다.
  • 호스트,경로 기반 라우팅이 안 됨example.com/apiapi Service로, example.com/staticstatic Service로 흘리고 싶어도, 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는 성공하는데 외부에서 응답이 안 오는 형태로 사고가 납니다.

머릿속 그림은 다음과 같이 두면 편합니다.

Ingress의 두 층
[외부 클라이언트]
[ 클라우드 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 ControllerEKS에서 ALB,NLB로 노출할 때ALB가 직접 라우팅. 클러스터 안 nginx Pod가 별도로 없음
GKE IngressGKE 기본 제공Google Cloud Load Balancer가 라우팅
AKS Application Gateway Ingress ControllerAKS에서 Application Gateway로Azure가 라우팅
CiliumeBPF 기반 네트워킹, 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 매니페스트라도 어느 컨트롤러가 처리하느냐에 따라 어노테이션 키와 동작이 달라집니다. 그래서 매니페스트에 컨트롤러 고유 어노테이션을 적을 때는 어느 컨트롤러를 쓰는지부터 확인해야 합니다.

Ingress Controller 설치 #

운영 클러스터에서는 보통 한 종류의 컨트롤러를 한 번 설치해 두고 모든 Ingress가 그것을 통과하게 합니다. 환경별 설치 모양을 짧게 짚어 두겠습니다.

minikube #

addons 명령 한 줄이면 ingress-nginx가 설치됩니다.

minikube에 ingress-nginx 켜기
minikube addons enable ingress
확인
kubectl get pods -n ingress-nginx
NAME                                       READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-6d4b7f6c8-abc12   1/1     Running   0          30s

kind #

kind는 ingress-nginx를 별도 매니페스트로 적용합니다.

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으로 설치하는 패턴이 표준입니다.

ingress-nginx Helm 설치 — 환경 무관
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace

EKS에서 AWS ALB Controller를 쓰는 경우는 별도의 IAM,서비스 계정 설정이 따라옵니다.

AWS Load Balancer Controller 설치 — 발췌
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-controller

GKE는 Google이 관리하는 GKE Ingress가 기본 활성화 상태이므로, 별도 설치 없이 Ingress 매니페스트를 적용하면 GCLB가 자동으로 만들어지는 경험이 가능합니다. AKS도 비슷한 방식의 매니지드 옵션이 있습니다.

설치한 컨트롤러는 보통 자기 네임스페이스(ingress-nginx, kube-system 등)에 한 묶음의 Pod,Service,Deployment,ConfigMap으로 떠 있습니다. 클러스터 한 대에 컨트롤러 한 종류만 설치되어 있으면 보통 충분합니다.

첫 Ingress 매니페스트 — 단순 도메인 라우팅 #

가장 단순한 모양부터 시작하겠습니다. example.com으로 들어오는 모든 트래픽을 web Service의 80 포트로 보내는 Ingress입니다.

먼저 백엔드 Service가 떠 있다고 가정합니다.

web-deploy-svc.yaml — 발췌
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: 80

Service 타입이 ClusterIP라는 점이 중요합니다. Ingress 뒤의 Service는 클러스터 내부 전용이면 충분합니다. 외부 노출은 Ingress Controller가 자기 LoadBalancer Service 한 개로 대신 수행하기 때문입니다. 여기 Service에 type: LoadBalancer를 적을 이유가 없습니다.

이제 Ingress 매니페스트입니다.

ingress-web.yaml
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 이름과 포트입니다.

이 매니페스트를 적용한 뒤 상태를 보면 다음과 같은 모양입니다.

Ingress 적용과 확인
kubectl apply -f ingress-web.yaml
kubectl get ingress
출력 예시
NAME   CLASS   HOSTS         ADDRESS          PORTS   AGE
web    nginx   example.com   34.120.10.20     80      30s

ADDRESS 컬럼에 외부 진입점의 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에 레코드를 자동으로 만들어 주는 운영 패턴도 있습니다.

path 기반 라우팅 #

같은 호스트의 경로마다 다른 Service로 흘리고 싶은 경우입니다. example.com/apibackend-api Service, example.com/staticcdn Service로 가는 모양입니다.

ingress-paths.yaml
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/usersbackend-api:8080
  • example.com/static/logo.pngcdn:80
  • example.com/ 또는 example.com/aboutweb: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.comapp.example.com을 갈라 두는 흔한 구성입니다.

ingress-multi-host.yaml
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: 80

rules 배열에 host가 다른 항목 두 개를 넣으면 됩니다. 한 Ingress가 여러 host를 같이 들고 있어도 되고, host마다 별도 Ingress 객체를 따로 만들어도 됩니다. 운영에서는 도메인 그룹 단위로 Ingress를 쪼개는 패턴이 흔합니다 — 네임스페이스별로 하나, 팀별로 하나처럼 책임이 분리될 때 객체를 같이 분리하는 모양입니다.

호스트는 와일드카드도 지원합니다. *.example.com이라고 적으면 foo.example.com, bar.example.com 같은 한 단계 서브도메인이 모두 매칭됩니다. 단, *.example.comfoo.bar.example.com처럼 두 단계는 매칭하지 않습니다 — DNS 와일드카드의 그 규칙을 그대로 따릅니다.

TLS 종단 — Ingress + Secret #

운영에서 외부 진입은 거의 항상 HTTPS입니다. Ingress의 tls 필드와 K8s Secret을 짝지으면 인증서 종단을 한 곳에서 처리할 수 있습니다.

먼저 인증서,키를 담은 Secret이 필요합니다.

TLS Secret 만들기 — 직접 발급한 인증서
kubectl create secret tls example-com-tls \
  --cert=fullchain.pem \
  --key=privkey.pem

Secret의 타입은 kubernetes.io/tls이고, 안에는 tls.crt(인증서 체인)와 tls.key(개인 키) 두 키가 들어 있습니다. 매니페스트로도 같은 모양을 만들 수 있지만, 키 파일을 base64로 인코딩한 값을 매니페스트에 넣게 되므로 보통은 위처럼 명령으로 만드는 편이 깔끔합니다.

이 Secret을 Ingress에 연결합니다.

ingress-tls.yaml
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: 80

spec.tls[].hosts에 적은 도메인의 트래픽은 Ingress Controller가 받을 때 TLS를 종단하고, 안쪽 Service에는 평문 HTTP로 흘립니다. 이 모양 덕분에 Service와 Pod 측 매니페스트는 HTTPS를 신경쓰지 않아도 되고, 인증서 갱신은 Secret만 갈아끼우면 됩니다.

cert-manager — 자동 발급,갱신 #

Let’s Encrypt 같은 무료 CA에서 인증서를 자동 발급,갱신하는 표준 도구가 cert-manager입니다. cert-manager를 클러스터에 설치하고 ClusterIssuer 객체를 한 번 만들어 두면, Ingress 매니페스트에 어노테이션 한 줄을 적는 것만으로 인증서 발급이 자동으로 진행됩니다.

ingress-tls-cert-manager.yaml — 발췌
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: 80

cert-manager.io/cluster-issuer 어노테이션을 보고 cert-manager가 다음 사이클을 자동으로 진행합니다.

  1. ACME 챌린지(HTTP-01 또는 DNS-01)를 통해 도메인 소유 검증.
  2. Let’s Encrypt에서 인증서 발급.
  3. 인증서,키를 secretName에 적힌 Secret으로 만들어 둠.
  4. 만료 30일 전쯤 자동 갱신.

cert-manager 자체의 설치와 ClusterIssuer 설정은 분량이 적지 않아 이번 글에서는 모양만 짚고, 세부는 K8s 실전 트랙으로 미루겠습니다. 운영에서 cert-manager가 사실상 표준이 되어 있으므로 이름과 위치만 기억해 두면 충분합니다.

IngressClass — 컨트롤러가 둘 이상 있을 때 #

대부분의 클러스터는 컨트롤러를 한 종류만 두지만, 같은 클러스터에 두 종류가 같이 있어야 하는 경우도 있습니다. 예를 들어 공개 트래픽은 ingress-nginx로, 내부 관리 트래픽은 AWS ALB Controller로 보내고 싶은 모양입니다. 이때 어느 Ingress 객체가 어느 컨트롤러에서 처리되어야 하는지를 표현하는 객체가 IngressClass입니다.

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으로 어느 클래스에 속할지 적습니다.

ingress-class-pick.yaml — 발췌
spec:
  ingressClassName: alb   # AWS ALB Controller가 처리
  rules:
    - host: admin.example.com
      ...

같은 클러스터의 다른 Ingress가 ingressClassName: nginx라면 그것은 ingress-nginx가 처리합니다. 두 컨트롤러가 같은 객체를 두고 충돌하지 않습니다.

현재 클러스터의 IngressClass
kubectl get ingressclass
출력 예시
NAME            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를 충분히 잡아 두기. 다음 편 #4에서 다루는 주제입니다.
  • 모니터링 — Controller가 노출하는 Prometheus 메트릭(요청 수, 5xx 비율, 응답 시간)을 알람으로.

정리 #

이번 글에서 잡은 흐름을 정리하겠습니다.

  • Service당 LoadBalancer의 한계 — 외부 노출 Service가 늘면 클라우드 LB 비용,관리 부담이 비례. 호스트,경로 라우팅과 TLS 종단도 한 곳에 모을 필요가 생깁니다.
  • Ingress의 두 층 — Ingress 객체(매니페스트, 의도)와 Ingress Controller(런타임). 컨트롤러 없이 객체만 적으면 무동작.
  • 컨트롤러 종류 — ingress-nginx(가장 흔함), Traefik, HAProxy, AWS ALB Controller, GKE Ingress, Cilium. 클러스터 안 Pod가 라우팅하는 갈래와 클라우드 자원이 라우팅하는 갈래로 나뉨.
  • 호스트 라우팅spec.rules[].host로 호스트 분기. 와일드카드(*.example.com)도 지원.
  • 경로 라우팅과 pathTypePrefix(가장 흔함, 슬래시 단위 prefix), Exact(정확 매칭), ImplementationSpecific(컨트롤러별).
  • TLS 종단spec.tls에 호스트와 Secret을 연결합니다. Secret 타입은 kubernetes.io/tls입니다. cert-manager로 Let’s Encrypt 자동 발급,갱신을 하는 것이 표준 패턴입니다.
  • IngressClass — 같은 클러스터에 컨트롤러가 둘 이상 있을 때 Ingress 객체가 어느 컨트롤러에 속할지 표현. default 표시는 한 개만.
  • Gateway API — Ingress의 후속 표준입니다. 표현력이 더 강합니다. Ingress가 여전히 표준이지만 새 클러스터에서는 검토 단계입니다.
  • 운영 패턴 — 클라우드 LB 한 개 → Ingress Controller 한 묶음 → Ingress 객체별 라우팅 → ClusterIP Service들. LB 비용,TLS,라우팅이 한 곳에 모임. Controller 자체의 가용성과 자원 여유가 운영의 다음 관심사.

이 모델까지 손에 들어오면, 회사 클러스터의 매니페스트 디렉터리에서 Ingress 객체를 만났을 때 어느 컨트롤러가 그것을 처리하는지, 외부 LB와 어떻게 이어져 있는지를 한 줄로 읽을 수 있습니다.

다음 — resources.requests / limits #

이번 글에서 외부 진입점의 모양을 잡으면서 마지막에 짧게 짚은 한 가지가 다음 편의 출발점입니다 — Ingress Controller Pod의 자원 여유입니다. Controller가 모든 외부 트래픽을 처리하므로 그 Pod에 CPU,메모리가 부족하면 트래픽 자체가 막힙니다. 그런데 K8s에서 Pod가 자기에게 필요한 자원을 어떻게 표현하고, 그 표현이 스케줄러의 결정과 노드의 OOM 동작에 어떻게 영향을 주는지는 아직 다루지 않았습니다.

#4 resources.requests / limits — Pod의 자원 요청과 상한에서는 Pod 매니페스트의 resources.requestsresources.limits 두 필드의 모델, CPU와 메모리 두 자원의 동작 차이(CPU는 throttling, 메모리는 OOMKill), QoS 클래스(Guaranteed / Burstable / BestEffort), LimitRange로 네임스페이스 기본값을 강제하는 운영 패턴까지 한 사이클로 정리하겠습니다.

X