Skip to content
Published on

Contour 완벽 가이드 - Envoy 기반 Ingress와 HTTPProxy로 쿠버네티스 트래픽 다루기

Authors

들어가며

쿠버네티스에서 외부 트래픽을 클러스터 내부 서비스로 라우팅하려면 인그레스 컨트롤러가 필요합니다. 가장 널리 쓰이는 것은 ingress-nginx지만, 운영 규모가 커지고 멀티테넌시 요구가 생기면 한계가 보이기 시작합니다. 같은 인그레스 리소스에서 어노테이션이 수십 개로 늘어나고, 설정 한 줄을 바꾸면 NGINX 전체가 리로드되면서 순간적으로 커넥션이 끊기기도 합니다.

저는 여러 팀이 하나의 클러스터를 공유하는 환경을 운영하면서 이 문제를 정면으로 마주했습니다. A팀이 잘못된 인그레스 어노테이션을 푸시하면 전체 NGINX 설정 검증이 실패하고, 그 영향이 B팀, C팀 서비스까지 번지는 일이 반복됐습니다. 루트 경로의 소유권 분쟁도 끊이지 않았습니다.

이때 도입을 검토한 것이 Contour입니다. Contour는 CNCF 인큐베이팅 프로젝트로, Envoy 프록시를 데이터 플레인으로 사용하는 인그레스 컨트롤러입니다. 핵심은 두 가지입니다. 첫째, 설정 변경 시 Envoy를 리로드하지 않고 xDS API로 동적 반영하기 때문에 무중단 업데이트가 가능합니다. 둘째, HTTPProxy라는 자체 CRD를 통해 위임(delegation) 기반 멀티테넌시를 안전하게 구현할 수 있습니다.

이 글에서는 Contour의 아키텍처부터 HTTPProxy의 라우팅 모델, 위임을 통한 멀티테넌시, TLS와 인증, Gateway API 지원, 그리고 실제 운영에서 마주치는 함정과 해결법까지 다룹니다. 2026년 현재 쿠버네티스 인그레스 생태계의 큰 흐름도 함께 짚어 보겠습니다.

2026년 인그레스 생태계의 현주소

먼저 큰 그림을 잡고 가겠습니다. 쿠버네티스 네트워킹 API는 지금 전환기에 있습니다.

  • Ingress API는 동결(frozen) 상태입니다. networking.k8s.io/v1의 Ingress 리소스는 더 이상 새 기능이 추가되지 않습니다. 표현력이 부족해서 컨트롤러마다 어노테이션으로 기능을 확장해 왔고, 이 어노테이션은 컨트롤러 간 호환되지 않습니다.
  • Gateway API가 후계 표준입니다. SIG-Network가 주도하는 Gateway API는 역할 기반 리소스 모델(GatewayClass, Gateway, HTTPRoute)을 제공하며, 인그레스의 표현력 한계와 멀티테넌시 문제를 정면으로 해결하도록 설계됐습니다.
  • Contour는 세 가지를 모두 지원합니다. Ingress, 자체 CRD인 HTTPProxy, 그리고 Gateway API를 함께 지원합니다. HTTPProxy는 Gateway API가 표준화되기 전에 Contour가 먼저 풀어낸 고급 기능들을 담고 있고, 그 경험이 Gateway API 설계에도 반영됐습니다.

정리하면, 신규 프로젝트라면 Gateway API를 우선 검토하되, Contour의 풍부한 HTTPProxy 기능(위임, 인클루전, 세밀한 트래픽 정책)이 필요하거나 이미 HTTPProxy로 운영 중이라면 HTTPProxy가 여전히 강력한 선택지입니다.

구분IngressHTTPProxyGateway API
표준 여부코어 API (동결)Contour 전용 CRD공식 후계 표준
멀티테넌시어노테이션 의존위임 기반 (강력)역할 분리 모델
트래픽 정책제한적풍부함확장 중
신규 기능추가 안 됨지속 추가활발히 발전
권장 시나리오레거시 호환고급 멀티테넌시신규 표준 채택

Contour 아키텍처 - 컨트롤 플레인과 데이터 플레인의 분리

Contour의 설계 철학은 명확한 책임 분리입니다. Contour 자체는 컨트롤 플레인이고, Envoy가 데이터 플레인입니다.

                     +---------------------------+
   kubectl apply     |   Kubernetes API Server    |
   (HTTPProxy / ---> |  (HTTPProxy, Ingress, ...) |
    Ingress)         +-------------+-------------+
                                   |
                          watch (informer)
                                   |
                                   v
                        +----------------------+
                        |   Contour (control   |
                        |   plane / xDS server)|
                        +----------+-----------+
                                   |
                          xDS API (gRPC stream)
                          LDS / RDS / CDS / EDS
                                   |
                                   v
   외부 트래픽 ----> +----------------------+ ----> +----------------+
   (인터넷)         |   Envoy (data plane) |       | 클러스터 내부   |
                    |   리스너 / 라우트 /  |       | Pod / Service  |
                    |   클러스터 / 엔드포인트|      +----------------+
                    +----------------------+

동작 흐름을 단계별로 보겠습니다.

  1. 사용자가 HTTPProxy나 Ingress 리소스를 kubectl로 적용합니다.
  2. Contour는 쿠버네티스 API 서버를 인포머로 감시하다가 변경을 감지합니다.
  3. Contour는 이 리소스를 내부 객체 그래프로 변환하고, Envoy가 이해하는 xDS 설정으로 컴파일합니다.
  4. Contour는 xDS gRPC 스트림을 통해 Envoy에 설정을 푸시합니다. 리스너(LDS), 라우트(RDS), 클러스터(CDS), 엔드포인트(EDS)가 각각 동적으로 갱신됩니다.
  5. Envoy는 설정을 받아 무중단으로 반영하고, 실제 트래픽을 처리합니다.

여기서 xDS가 핵심입니다. NGINX 기반 컨트롤러는 설정이 바뀌면 nginx.conf를 다시 쓰고 프로세스를 리로드합니다. 반면 Envoy는 xDS API로 설정을 동적으로 받기 때문에 프로세스 재시작이나 리로드가 없습니다. 엔드포인트 하나가 추가되거나 라우트 규칙이 바뀌어도 기존 커넥션은 영향받지 않습니다. 트래픽이 많은 환경일수록 이 차이는 크게 다가옵니다.

배포 토폴로지도 알아 두면 좋습니다. Contour는 보통 두 가지 형태로 배포됩니다.

  • Contour Deployment + Envoy DaemonSet: Envoy를 모든 노드에 데몬셋으로 띄우고 hostNetwork나 NodePort로 노출하는 방식.
  • Contour Deployment + Envoy Deployment: Envoy를 별도 디플로이먼트로 띄우고 LoadBalancer 서비스로 노출하는 방식. 클라우드 환경에서 흔히 쓰입니다.

설치 - 빠르게 띄워 보기

가장 간단한 방법은 공식 매니페스트를 적용하는 것입니다.

# 공식 quickstart 매니페스트 적용
kubectl apply -f https://projectcontour.io/quickstart/contour.yaml

# 배포 상태 확인
kubectl get pods -n projectcontour

# Envoy 서비스의 외부 IP 확인
kubectl get svc envoy -n projectcontour

운영 환경에서는 Helm 차트로 설치해 값을 관리하는 편이 깔끔합니다.

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-contour bitnami/contour --namespace projectcontour --create-namespace

직접 관리하고 싶다면 Contour가 제공하는 ContourDeployment CRD와 Gateway provisioner를 쓰는 방법도 있습니다. 핵심 컴포넌트 구성을 values로 조정한 예시는 다음과 같습니다.

# contour-values.yaml 예시 (개념 설명용)
contour:
  replicaCount: 2
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
envoy:
  kind: daemonset
  service:
    type: LoadBalancer
  resources:
    requests:
      cpu: 250m
      memory: 256Mi

HTTPProxy 기초 - 가장 단순한 라우팅

이제 본론입니다. HTTPProxy는 Contour가 정의한 CRD로, 인그레스의 한계를 넘어서는 풍부한 라우팅 모델을 제공합니다. 가장 단순한 형태부터 보겠습니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: basic-app
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    - conditions:
        - prefix: /
      services:
        - name: app-service
          port: 80

virtualhost.fqdn은 이 프록시가 처리할 도메인입니다. routes 아래에서 조건(conditions)에 매칭되는 트래픽을 어떤 서비스로 보낼지 정의합니다. 위 예시는 app.example.com으로 들어온 모든 경로(/)를 app-service의 80 포트로 보냅니다.

여러 서비스로 가중치 기반 분할(카나리)도 간단합니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: canary-app
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    - conditions:
        - prefix: /
      services:
        - name: app-v1
          port: 80
          weight: 90
        - name: app-v2
          port: 80
          weight: 10

weight 값으로 트래픽 비율을 조절합니다. 위 설정은 90 대 10으로 신버전에 점진적으로 트래픽을 흘려보냅니다. 신버전이 안정적이면 weight를 점차 조정해 전환을 완료합니다.

조건(conditions) - 경로와 헤더 기반 라우팅

HTTPProxy의 conditions는 prefix 경로뿐 아니라 헤더 기반 매칭도 지원합니다. 이를 통해 같은 도메인에서 다양한 트래픽 분기를 표현할 수 있습니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: header-routing
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    # API 경로는 백엔드 API 서비스로
    - conditions:
        - prefix: /api
      services:
        - name: api-service
          port: 8080
    # 특정 헤더가 있으면 베타 서비스로
    - conditions:
        - prefix: /
        - header:
            name: x-canary
            exact: "true"
      services:
        - name: beta-service
          port: 80
    # 그 외 기본 경로
    - conditions:
        - prefix: /
      services:
        - name: web-service
          port: 80

header 조건은 exact(정확히 일치), contains(포함), present(존재 여부), notpresent 등을 지원합니다. 위 예시에서 x-canary 헤더가 true인 요청만 베타 서비스로 보냅니다. 이런 식의 분기는 카나리 테스트나 내부 사용자 대상 기능 노출에 유용합니다.

라우트 레벨에서 경로 재작성, 헤더 추가/삭제, 타임아웃, 재시도도 지정할 수 있습니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: route-policies
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    - conditions:
        - prefix: /legacy
      pathRewritePolicy:
        replacePrefix:
          - prefix: /legacy
            replacement: /v2
      requestHeadersPolicy:
        set:
          - name: x-forwarded-prefix
            value: /legacy
        remove:
          - x-internal-token
      timeoutPolicy:
        response: 30s
      retryPolicy:
        count: 3
        retryOn: 5xx
      services:
        - name: legacy-service
          port: 80

pathRewritePolicy로 /legacy를 /v2로 재작성하고, requestHeadersPolicy로 헤더를 조작하며, timeoutPolicy와 retryPolicy로 회복력 정책을 지정했습니다. 인그레스에서는 어노테이션 덩어리로 처리하던 것들이 구조화된 필드로 깔끔하게 표현됩니다.

인클루전과 위임 - 멀티테넌시의 핵심

여기서부터가 Contour를 선택하는 가장 큰 이유입니다. HTTPProxy는 인클루전(inclusion) 이라는 메커니즘으로 루트 프록시가 다른 네임스페이스의 자식 프록시를 포함할 수 있습니다.

구조는 이렇습니다. 플랫폼 팀이 도메인과 TLS를 소유하는 루트 HTTPProxy를 관리하고, 각 애플리케이션 팀은 자기 네임스페이스에서 라우트만 정의하는 자식 HTTPProxy를 관리합니다. 루트가 자식을 특정 경로 조건으로 포함합니다.

   루트 HTTPProxy (네임스페이스: ingress-root)
   fqdn: example.com, TLS 인증서 소유
        |
        |  include (조건 기반 위임)
        +-----------------------------+
        |                             |
        v                             v
   자식 HTTPProxy                자식 HTTPProxy
   (네임스페이스: team-a)         (네임스페이스: team-b)
   conditions: /team-a          conditions: /team-b
   라우트만 정의                  라우트만 정의

루트 프록시는 다음과 같습니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: example-root
  namespace: ingress-root
spec:
  virtualhost:
    fqdn: example.com
    tls:
      secretName: example-com-tls
  includes:
    - name: team-a-proxy
      namespace: team-a
      conditions:
        - prefix: /team-a
    - name: team-b-proxy
      namespace: team-b
      conditions:
        - prefix: /team-b

team-a 네임스페이스의 자식 프록시는 fqdn 없이 라우트만 정의합니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: team-a-proxy
  namespace: team-a
spec:
  routes:
    - conditions:
        - prefix: /
      services:
        - name: team-a-service
          port: 80

이 구조의 장점이 큽니다.

  • 도메인 소유권 분리: 도메인과 TLS는 플랫폼 팀이 단일하게 관리하므로 루트 경로 분쟁이 사라집니다.
  • 권한 격리: 애플리케이션 팀은 자기 네임스페이스의 HTTPProxy만 만들 권한을 가지면 됩니다. RBAC로 깔끔하게 통제됩니다.
  • 장애 격리: 한 팀의 잘못된 설정이 그 자식 프록시만 무효화할 뿐, 다른 팀이나 루트 전체로 번지지 않습니다. Contour는 잘못된 자식을 거부하고 나머지는 정상 동작시킵니다.

위임을 추가로 통제하려면 TLSCertificateDelegation을 사용해 다른 네임스페이스가 특정 인증서 시크릿을 참조하도록 명시적으로 허용할 수도 있습니다.

apiVersion: projectcontour.io/v1
kind: TLSCertificateDelegation
metadata:
  name: example-delegation
  namespace: ingress-root
spec:
  delegations:
    - secretName: example-com-tls
      targetNamespaces:
        - team-a
        - team-b

TLS 설정 - cert-manager와의 연동

TLS는 virtualhost.tls 아래에서 시크릿을 참조하는 것으로 끝납니다. 인증서 발급은 cert-manager에 맡기는 것이 일반적입니다.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com-tls
  namespace: ingress-root
spec:
  secretName: example-com-tls
  dnsNames:
    - example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer

이렇게 발급된 시크릿을 HTTPProxy의 tls.secretName에서 참조합니다. HTTP에서 HTTPS로의 리다이렉트, 최소 TLS 버전, 패스스루(passthrough) 모드도 설정할 수 있습니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: tls-app
  namespace: web
spec:
  virtualhost:
    fqdn: secure.example.com
    tls:
      secretName: example-com-tls
      minimumProtocolVersion: "1.2"
  routes:
    - conditions:
        - prefix: /
      services:
        - name: secure-service
          port: 443

TCP 트래픽을 그대로 전달하는 TLS passthrough는 tcpproxy와 tls.passthrough를 조합해 구성합니다. 백엔드가 자체 TLS 종료를 하는 경우에 씁니다.

레이트 리밋과 외부 인증

Contour는 Envoy의 강력한 기능을 그대로 노출합니다. 대표적인 것이 레이트 리밋과 외부 인증입니다.

로컬 레이트 리밋은 별도 서비스 없이 Envoy 자체에서 처리합니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: ratelimited-app
  namespace: web
spec:
  virtualhost:
    fqdn: api.example.com
    rateLimitPolicy:
      local:
        requests: 100
        unit: minute
        burst: 20
  routes:
    - conditions:
        - prefix: /
      services:
        - name: api-service
          port: 80

위 설정은 분당 100요청에 버스트 20을 허용합니다. 한도를 넘으면 Envoy가 429를 반환합니다. 전역(global) 레이트 리밋은 외부 RLS(Rate Limit Service)와 연동해 클러스터 전체에서 일관된 한도를 적용할 수 있습니다.

외부 인증은 ExtensionService와 연동해 모든 요청을 인증 서버에 먼저 보냅니다.

apiVersion: projectcontour.io/v1alpha1
kind: ExtensionService
metadata:
  name: authservice
  namespace: auth
spec:
  protocol: h2
  services:
    - name: auth-grpc
      port: 9443
---
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: authenticated-app
  namespace: web
spec:
  virtualhost:
    fqdn: secure.example.com
    tls:
      secretName: example-com-tls
    authorization:
      extensionRef:
        name: authservice
        namespace: auth
  routes:
    - conditions:
        - prefix: /
      services:
        - name: app-service
          port: 80

이렇게 하면 secure.example.com으로 들어오는 모든 요청이 auth 네임스페이스의 외부 인증 서비스를 거칩니다. OIDC, JWT 검증 등을 인증 서버에 위임할 수 있어 애플리케이션 코드에서 인증 로직을 덜어낼 수 있습니다.

헬스 체크와 로드 밸런싱 전략

서비스 레벨에서 능동 헬스 체크와 로드 밸런싱 알고리즘을 지정할 수 있습니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: balanced-app
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    - conditions:
        - prefix: /
      loadBalancerPolicy:
        strategy: WeightedLeastRequest
      services:
        - name: app-service
          port: 80
          healthCheckPolicy:
            path: /healthz
            intervalSeconds: 5
            timeoutSeconds: 2
            unhealthyThresholdCount: 3
            healthyThresholdCount: 2

loadBalancerPolicy.strategy는 RoundRobin, WeightedLeastRequest, Random, Cookie(세션 어피니티) 등을 지원합니다. healthCheckPolicy로 비정상 엔드포인트를 자동으로 풀에서 제외합니다. Envoy의 아웃라이어 디텍션과 결합하면 회복력이 크게 올라갑니다.

Gateway API로 가기 - Contour의 지원

앞서 말했듯 Gateway API는 인그레스의 후계 표준입니다. Contour는 Gateway API 구현체로서 GatewayClass, Gateway, HTTPRoute를 지원합니다. 동일한 라우팅을 Gateway API로 표현하면 다음과 같습니다.

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: contour
spec:
  controllerName: projectcontour.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: contour
  namespace: projectcontour
spec:
  gatewayClassName: contour
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
  namespace: web
spec:
  parentRefs:
    - name: contour
      namespace: projectcontour
  hostnames:
    - app.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: app-service
          port: 80

여기서 HTTPProxy의 인클루전/위임 개념이 Gateway API에서는 Gateway의 allowedRoutes와 HTTPRoute의 네임스페이스 분리로 대응됩니다. 역할 기반 모델(인프라 제공자가 GatewayClass, 클러스터 운영자가 Gateway, 앱 개발자가 HTTPRoute)이 명확하게 분리됩니다.

선택 기준은 이렇습니다.

  • 신규 도입이고 표준 호환성이 중요하다면 Gateway API를 우선하세요.
  • HTTPProxy만의 고급 기능(세밀한 인클루전, 특정 트래픽 정책)이 필요하거나 이미 HTTPProxy 자산이 많다면 HTTPProxy를 유지하세요.
  • 두 API를 동시에 사용하는 것도 가능하지만, 같은 도메인을 두 API가 동시에 다루지 않도록 운영 규칙을 정해야 합니다.

운영 - 관찰성과 상태 확인

운영에서 가장 먼저 확인하는 것은 HTTPProxy의 상태입니다. Contour는 각 리소스의 유효성을 status에 기록합니다.

# HTTPProxy 상태 확인 - valid / invalid 여부가 STATUS에 표시됨
kubectl get httpproxy -A

# 특정 프록시의 상세 상태와 에러 메시지
kubectl describe httpproxy basic-app -n web

status가 invalid이면 description 필드에 원인이 적혀 있습니다. 예를 들어 orphaned(루트가 포함하지 않은 자식), 중복 fqdn, 존재하지 않는 서비스 참조 등이 흔한 원인입니다.

Envoy의 실제 설정을 직접 들여다보려면 Contour CLI나 Envoy admin 인터페이스를 활용합니다.

# Contour가 Envoy에 푸시한 라우트/클러스터/엔드포인트 덤프
kubectl exec -n projectcontour deploy/contour -- contour cli endpoints
kubectl exec -n projectcontour deploy/contour -- contour cli clusters

# Envoy 통계 (포트포워딩 후)
kubectl port-forward -n projectcontour ds/envoy 9001:9001
curl http://localhost:9001/stats | grep upstream_rq

메트릭은 Prometheus로 수집합니다. Contour와 Envoy 모두 Prometheus 포맷의 메트릭을 노출하므로, Grafana 대시보드에서 요청률, 지연, 에러율(4xx/5xx), 업스트림 헬스를 모니터링합니다. 액세스 로그는 JSON 포맷으로 설정해 로그 파이프라인에서 파싱하기 쉽게 만드는 것을 권장합니다.

성능 튜닝 포인트

규모가 커지면 다음 항목을 점검합니다.

항목권장 사항
Envoy 동시성노드 CPU에 맞춰 concurrency(워커 스레드)를 설정
커넥션 풀업스트림 maxConnections, maxRequests 조정으로 백엔드 보호
타임아웃글로벌 기본 타임아웃과 라우트별 타임아웃을 명확히 분리
리소스 요청/제한Envoy는 트래픽에 비례해 메모리/CPU가 늘어나므로 여유 있게
xDS 갱신 빈도리소스가 매우 많으면 Contour의 처리 지연을 모니터링
액세스 로그고트래픽이면 JSON 로깅 비용과 디스크 I/O 고려

Envoy의 concurrency 값은 데이터 플레인 성능에 직결됩니다. 기본값은 코어 수에 맞춰지지만, 데몬셋으로 노드 전체를 점유하는 환경에서는 명시적으로 조정하는 편이 예측 가능합니다. 커넥션 풀 한도는 느린 백엔드가 전체를 끌어내리는 상황(헤드 오브 라인 블로킹)을 방지하는 안전장치입니다.

흔한 함정과 트러블슈팅

운영하면서 자주 만나는 문제들을 정리합니다.

1. HTTPProxy status가 invalid인데 트래픽이 안 옴

가장 흔합니다. kubectl describe로 description을 확인하세요. 자식 프록시가 루트에 의해 include되지 않으면 orphaned 상태가 되어 라우팅되지 않습니다. 루트의 includes에 name/namespace가 정확한지 확인합니다.

2. 같은 fqdn을 여러 루트 프록시가 선언

같은 도메인을 두 개의 루트 HTTPProxy가 선언하면 Contour는 충돌로 보고 하나를 무효화합니다. fqdn은 클러스터 전체에서 유일해야 합니다. 멀티테넌시는 fqdn을 나누지 말고 인클루전으로 경로를 나누세요.

3. 503 upstream connect error

대부분 서비스/엔드포인트 문제입니다. 대상 Service의 셀렉터가 실제 파드와 맞는지, Endpoints가 비어 있지 않은지 확인합니다. contour cli endpoints로 Envoy가 받은 엔드포인트를 확인하면 빠릅니다.

4. TLS 인증서가 적용 안 됨

secretName이 가리키는 시크릿이 같은 네임스페이스에 있는지, 다른 네임스페이스라면 TLSCertificateDelegation으로 위임했는지 확인합니다. cert-manager가 발급 중이라면 Certificate 리소스의 Ready 상태를 확인합니다.

5. 경로 재작성이 의도와 다르게 동작

replacePrefix는 매칭된 prefix 부분만 치환합니다. prefix 조건과 replacePrefix의 prefix가 일치하지 않으면 예상과 다른 결과가 나옵니다. 조건의 prefix와 재작성 규칙을 함께 점검하세요.

6. 설정 반영 지연

Contour가 리소스를 처리하는 데 시간이 걸리거나, Envoy로의 xDS 푸시가 늦는 경우입니다. 리소스 개수가 수천 개 규모라면 Contour 로그와 처리 지연 메트릭을 확인하고, 필요하면 Contour 레플리카와 리소스를 늘립니다.

진단 명령을 한 번에 정리하면 다음과 같습니다.

# 전체 HTTPProxy 상태 한눈에
kubectl get httpproxy -A -o wide

# Contour 로그에서 에러 추적
kubectl logs -n projectcontour deploy/contour --tail=200 | grep -i error

# Envoy가 받은 클러스터/엔드포인트 확인
kubectl exec -n projectcontour deploy/contour -- contour cli clusters
kubectl exec -n projectcontour deploy/contour -- contour cli endpoints

# Envoy admin 통계로 업스트림 상태 확인
kubectl port-forward -n projectcontour ds/envoy 9001:9001
curl -s http://localhost:9001/clusters | grep health_flags

마치며

Contour는 단순한 인그레스 컨트롤러를 넘어, Envoy의 동적 설정 능력과 HTTPProxy의 위임 모델을 결합해 멀티테넌시 환경에서 빛을 발하는 도구입니다. 핵심을 다시 정리하면 다음과 같습니다.

  • xDS 기반 무중단 설정 반영으로 트래픽이 많은 환경에서도 안정적인 업데이트가 가능합니다.
  • 인클루전과 위임으로 도메인 소유권을 분리하고, 장애를 격리하며, RBAC로 권한을 통제하는 안전한 멀티테넌시를 구현합니다.
  • 풍부한 트래픽 정책(레이트 리밋, 외부 인증, 헬스 체크, 재시도)을 어노테이션 덩어리 없이 구조화된 필드로 다룹니다.
  • Gateway API 지원으로 미래 표준으로의 전환 경로를 확보합니다.

2026년 현재 Ingress API는 동결됐고 Gateway API가 후계 표준으로 자리 잡고 있습니다. 신규 프로젝트라면 Gateway API를 우선 검토하되, Contour의 HTTPProxy는 여전히 강력한 멀티테넌시 도구로서 그 가치를 유지하고 있습니다. 두 길 모두 Contour로 갈 수 있다는 점이 이 프로젝트의 큰 장점입니다.

직접 클러스터에 Contour를 띄우고, 작은 자식 HTTPProxy 하나를 만들어 위임이 어떻게 동작하는지 눈으로 확인해 보시길 권합니다. 어노테이션 지옥에서 벗어나 구조화된 트래픽 관리의 편안함을 느끼게 될 것입니다.

참고 자료