Traefik Gateway API — 포트 불일치

Kubernetes5분조회

Ingress 스펙은 HTTP 라우팅밖에 표현하지 못하고 벤더별 어노테이션으로 확장하는 구조라, 진입점을 새로 만드는 김에 Gateway API 로 가보기로 했습니다. 컨트롤러는 Traefik 을 썼습니다. 그런데 파드가 CrashLoopBackOff 로 돌고, 겨우 띄워놓으니 이번엔 요청이 404 로 떨어지고, 그다음엔 Gateway 가 Accepted 로 안 넘어갔습니다. 증상이 셋 다 달라 보여서 따로따로 파고들었는데, 막힌 자리는 매번 같은 곳이었습니다.

컨테이너 내 80번 포트 바인딩 실패

traefik-xxx   0/1   CrashLoopBackOff   4   2m25s
ERR Command error: error while building entryPoint web:
building listener: error opening listener: listen tcp :80: bind: permission denied

ports.web.port: 80 으로 설정하면 Traefik 이 컨테이너 안에서 직접 80번에 바인딩을 시도합니다. 1024 미만은 privileged port 라 root 권한 없이는 열 수 없습니다.

port(내부 바인딩)와 exposedPort(서비스 외부 노출)를 나눠야 합니다.

# 잘못된 설정
ports:
  web:
    port: 80          # 컨테이너 안에서 80으로 직접 바인딩 시도 → 실패

# 올바른 설정
ports:
  web:
    port: 8000        # 컨테이너 내부 바인딩 포트 (non-privileged)
    exposedPort: 80   # Service가 외부에 노출하는 포트

Gateway listener port = Traefik entrypoint port

파드는 떴는데 요청이 404 로 돌아옵니다.

curl http://whoami.example.com
→ 404 page not found

Traefik 의 Gateway API provider 는 Gateway 리소스의 port 와 Traefik entrypoint 의 port 가 일치해야 라우팅을 붙입니다.

Gateway listener port:   80     ← 설정한 값
Traefik entrypoint port: 8000   ← 실제 내부 포트
→ 매칭 실패 → 404

앞에서 내부 포트를 8000으로 바꿨으니 Gateway 쪽도 8000으로 맞춰야 합니다.

# 잘못된 설정
spec:
  listeners:
    - port: 80    # Traefik entrypoint 8000과 불일치

# 올바른 설정
spec:
  listeners:
    - port: 8000  # Traefik entrypoint와 동일하게

외부에서 보이는 포트는 exposedPort 와 로드밸런서가 담당합니다. Gateway listener 에 적는 값은 그것과 별개입니다.

websecure entrypoint의 TLS 기대

Gateway 가 Accepted 되지 않습니다.

kubectl describe gateway test-gateway -n traefik
→ Message: Cannot find entryPoint for Gateway:
  no matching entryPoint for port 8443 and protocol "HTTP"
→ Accepted: False

Traefik 의 websecure entrypoint(8443)는 기본이 HTTPS 입니다. 여기에 protocol: HTTP 로 붙으려 하면 맞는 entrypoint 가 없다고 나옵니다.

로드밸런서가 TLS 를 종료하고 plain HTTP 를 뒤로 보내는 구성이었습니다. Traefik 은 TLS 를 기대하고 있으니 충돌합니다.

Service 의 443 포트가 web entrypoint 로 가도록 targetPort 를 바꿨습니다.

kubectl patch svc traefik -n traefik --type='json' -p='[
  {"op": "replace", "path": "/spec/ports/1/targetPort", "value": 8000}
]'

Gateway 에서도 websecure 리스너를 빼고 web 하나만 남겼습니다.

spec:
  listeners:
    - name: web
      protocol: HTTP
      port: 8000
      allowedRoutes:
        namespaces:
          from: All

Helm chart v40의 ports.websecure.tls 제거

Error: values don't meet the specifications of the schema(s):
traefik:
- ports.websecure: Additional property tls is not allowed

Traefik Helm chart v40.x 에서 ports.websecure.tls 필드가 스키마에서 제거됐습니다. 값을 지우면 됩니다. websecure entrypoint 는 인증서가 없으면 TLS 없이 동작하거나, 위처럼 web entrypoint 하나로 통일해도 됩니다.

로드밸런서는 IP가 아닌 hostname 발급

NCloud NPLB 를 쓰는 환경이었습니다. AWS ALB 처럼 IP 를 DNS 에 직접 등록해야 한다고 생각했는데 아니었습니다.

NPLB 는 xxx.lb.naverncp.com 형태의 hostname 을 발급합니다. DNS 에 A(LB VPC) 타입으로 이 hostname 을 값으로 등록하면 IP 관리는 NCloud 가 합니다.

kubectl get svc -n traefik traefik
# EXTERNAL-IP: traefik-traefik-xxx.lb.naverncp.com

Namespace가 Terminating에서 멈춤

테스트 네임스페이스를 지우는데 멈췄습니다.

kubectl get namespace gateway-test
NAME           STATUS        AGE
gateway-test   Terminating   16m

metrics.k8s.io/v1beta1 API 그룹 discovery 가 실패해서, 네임스페이스 컨트롤러가 리소스 정리가 끝났는지 확인하지 못하고 멈춘 상태였습니다.

spec.finalizers 를 빈 배열로 만들어 finalize 서브리소스에 직접 넣습니다.

kubectl get namespace gateway-test -o json | \
  python3 -c "import sys,json; d=json.load(sys.stdin); d['spec']['finalizers']=[]; print(json.dumps(d))" | \
  kubectl replace --raw /api/v1/namespaces/gateway-test/finalize -f -

남은 리소스 확인을 건너뛰고 지우는 방식이라, 정리가 덜 된 리소스가 있으면 그대로 고아로 남습니다. discovery 가 깨진 게 확실할 때만 씁니다.

참고

  1. 불러오는 중