본문 바로가기
실전 개발·아키텍처/web

Kubernetes NGINX Ingress에서 Contour HTTPProxy로 이전할 때 HTTP→HTTPS Redirect 설정 비교

by 플로거 2026. 7. 21.

Kubernetes NGINX Ingress에서 Contour HTTPProxy로 이전할 때 HTTP→HTTPS Redirect 설정 비교

기존 Kubernetes 환경에서 NGINX Ingress Controller를 사용하다가 새로운 클라우드 Kubernetes 환경의 Contour로 이전하면, 기존에는 별다른 설정 없이 동작하던 HTTP→HTTPS 전환이 새 환경에서는 동작하지 않는 것처럼 보일 수 있습니다.

특히 다음과 같은 구조에서는 HTTP로 접속한 프런트엔드가 HTTPS API를 호출하면서 CORS 오류가 발생할 수 있습니다.

프런트엔드 페이지
http://app.company.com

API 호출
https://app.company.com/properties

도메인이 같더라도 httphttps는 서로 다른 Origin으로 판단됩니다. 브라우저의 Origin은 프로토콜, 호스트, 포트의 조합으로 구분되기 때문에 이 경우 CORS 검사가 발생합니다.

이 글에서는 다음 내용을 중심으로 정리합니다.

  • 기존 NGINX Ingress에서 별도 설정 없이 HTTPS Redirect가 동작한 이유
  • Contour HTTPProxy에서 HTTP→HTTPS Redirect를 적용하는 방법
  • APIGW, FE, BE 구조에서 가장 단순한 라우팅 구성
  • Contour 의존성을 최소화하는 운영 방식
  • 실제 설정과 점검 방법

1. 서비스 구성

현재 서비스는 다음과 같이 구성되어 있다고 가정합니다.

외부 사용자
    ↓
Ingress Controller
    ↓
API Gateway
    ├─ Frontend
    └─ Backend

외부 요청은 모두 API Gateway를 통과하고, API Gateway가 요청 경로에 따라 Frontend와 Backend를 구분합니다.

/ 또는 /login, /assets/**
→ Frontend

/api/** 또는 /properties
→ Backend

따라서 Contour에서는 Frontend와 Backend를 직접 구분하지 않고, 모든 외부 요청을 API Gateway로 전달하는 단순한 구조를 사용할 수 있습니다.


2. 기존 NGINX Ingress에서는 왜 자동 Redirect되었을까?

기존 환경에서는 다음과 같은 Kubernetes Ingress를 사용했을 가능성이 높습니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: app
spec:
  ingressClassName: nginx

  tls:
    - hosts:
        - app.company.com
      secretName: app-tls

  rules:
    - host: app.company.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-gateway
                port:
                  number: 8080

Ingress-NGINX는 해당 Ingress에 TLS가 설정되어 있으면 기본적으로 HTTP 요청을 HTTPS 포트로 Redirect합니다.

http://app.company.com
    ↓
308 Permanent Redirect
    ↓
https://app.company.com

따라서 사용자가 브라우저에 HTTP 주소를 입력했더라도 실제 Frontend 페이지는 HTTPS로 다시 로딩됩니다.

사용자가 입력한 주소
http://app.company.com

실제로 로딩된 페이지
https://app.company.com

이후 Frontend가 같은 도메인의 HTTPS API를 호출하면 동일 Origin 요청이 되므로 일반적인 CORS 문제가 발생하지 않습니다.

Frontend
https://app.company.com

Backend API
https://app.company.com/properties

3. NGINX의 ssl-redirect annotation은 꼭 필요한가?

기존 Ingress에 다음과 같은 annotation이 있었을 수도 있습니다.

metadata:
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"

하지만 Ingress에 TLS가 이미 설정되어 있었다면 이 annotation이 반드시 필요한 것은 아닙니다. Ingress-NGINX는 TLS가 설정된 Ingress에서 HTTPS Redirect를 기본적으로 활성화하기 때문입니다.

반대로 HTTP 접근을 허용하려면 다음과 같이 Redirect를 명시적으로 비활성화할 수 있습니다.

metadata:
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"

Ingress Controller 전체 설정에서 비활성화할 수도 있습니다.

data:
  ssl-redirect: "false"

즉 기존 환경에서 별도의 Redirect 구현이 보이지 않았더라도, 실제로는 Ingress-NGINX의 기본 TLS 동작에 의해 처리되고 있었을 가능성이 큽니다.


4. Frontend NGINX가 Redirect를 처리했을 가능성

Frontend가 NGINX 컨테이너로 정적 파일을 서비스했다면 nginx.conf 내부에 Redirect 설정이 있었을 수도 있습니다.

server {
    listen 80;
    server_name app.company.com;

    return 301 https://$host$request_uri;
}

또는 앞단 Proxy가 전달한 X-Forwarded-Proto를 기준으로 Redirect했을 수도 있습니다.

server {
    listen 80;

    if ($http_x_forwarded_proto = "http") {
        return 301 https://$host$request_uri;
    }

    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }
}

다만 Kubernetes에서는 일반적으로 TLS를 Ingress Controller에서 종료하고, Ingress 이후 API Gateway와 Frontend 사이의 통신은 HTTP로 처리합니다.

따라서 Frontend NGINX보다는 Ingress-NGINX에서 Redirect했을 가능성이 더 높습니다.

실행 중인 Frontend Pod의 NGINX 설정은 다음 명령으로 확인할 수 있습니다.

kubectl exec -n app <frontend-pod> -- nginx -T

Redirect와 Proxy 관련 설정만 검색하려면 다음과 같이 확인합니다.

kubectl exec -n app <frontend-pod> -- nginx -T 2>&1 |
grep -E -n "return 30|rewrite|proxy_pass|X-Forwarded-Proto"

5. Contour에서도 TLS 설정만으로 Redirect할 수 있을까?

Contour의 HTTPProxy에서도 TLS를 설정하면 HTTP 요청을 HTTPS로 자동 Redirect할 수 있습니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: app-proxy
  namespace: app
spec:
  ingressClassName: contour

  virtualhost:
    fqdn: app.company.com
    tls:
      secretName: app-tls

  routes:
    - conditions:
        - prefix: /
      services:
        - name: api-gateway
          port: 8080

핵심 설정은 다음 부분입니다.

virtualhost:
  fqdn: app.company.com
  tls:
    secretName: app-tls

TLS가 설정된 VirtualHost에 HTTP 요청이 들어오면 Contour는 기본적으로 HTTPS로 Redirect합니다.

http://app.company.com/
→ https://app.company.com/

http://app.company.com/login
→ https://app.company.com/login

http://app.company.com/properties?id=100
→ https://app.company.com/properties?id=100

따라서 별도의 Redirect Route를 추가하거나 API Gateway에 Redirect 로직을 구현하지 않아도 됩니다.


6. /properties별로 Redirect 설정할 필요는 없다

HTTP→HTTPS Redirect는 /properties와 같은 개별 API 경로에 적용하는 설정이 아닙니다.

다음의 routes 설정은 Redirect 범위를 결정하는 것이 아니라, HTTPS 접속 이후 요청을 어떤 Kubernetes Service로 전달할지를 결정합니다.

routes:
  - conditions:
      - prefix: /
    services:
      - name: api-gateway
        port: 8080

prefix: /는 해당 도메인의 모든 경로와 일치합니다.

/
├─ /login
├─ /dashboard
├─ /properties
├─ /api/users
└─ /assets/app.js

전체 요청 흐름은 다음과 같습니다.

HTTP 요청
    ↓
Contour 자동 HTTPS Redirect
    ↓
HTTPS 요청
    ↓
HTTPProxy Route
    ↓
API Gateway

7. services.name은 Kubernetes Service 이름이다

HTTPProxy의 다음 설정을 살펴보겠습니다.

services:
  - name: api-gateway
    port: 8080

여기서 api-gateway는 Deployment 이름이나 애플리케이션 이름이 아니라 Kubernetes Service의 metadata.name입니다.

apiVersion: v1
kind: Service
metadata:
  name: api-gateway
  namespace: app
spec:
  selector:
    app: api-gateway

  ports:
    - name: http
      port: 8080
      targetPort: 8080

각 설정의 연결 관계는 다음과 같습니다.

HTTPProxy
services.name: api-gateway
services.port: 8080
        ↓
Kubernetes Service
metadata.name: api-gateway
spec.ports.port: 8080
        ↓
API Gateway Pod
targetPort: 8080

HTTPProxy의 port: 8080은 일반적으로 컨테이너의 containerPort가 아니라 Service의 spec.ports[].port 값을 의미합니다.

실제 Service 이름이 app-apigw-svc라면 HTTPProxy에도 동일한 이름을 사용해야 합니다.

services:
  - name: app-apigw-svc
    port: 8080

Service와 Endpoint는 다음 명령으로 확인합니다.

kubectl get service -n app

kubectl describe service api-gateway -n app

kubectl get endpoints api-gateway -n app

8. 현재 서비스에 적합한 Contour 설정

현재 서비스는 모든 외부 요청이 API Gateway를 거쳐 Frontend 또는 Backend로 전달되는 구조입니다.

따라서 Contour에서 Frontend와 Backend를 직접 분기할 필요가 없습니다. 모든 경로를 API Gateway Service로 전달하는 하나의 Route만 두면 됩니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: app-proxy
  namespace: app
spec:
  ingressClassName: contour

  virtualhost:
    fqdn: app.company.com
    tls:
      secretName: app-tls

  routes:
    - conditions:
        - prefix: /
      services:
        - name: api-gateway
          port: 8080

전체 흐름은 다음과 같습니다.

사용자
  │
  ├─ HTTP 접속
  │     ↓
  │  Contour 301 Redirect
  │
  └─ HTTPS 접속
        ↓
     Contour
        ↓
     API Gateway
        ├─ API 요청 → Backend
        └─ 나머지 요청 → Frontend

Contour는 다음 기능만 담당합니다.

  • TLS 인증서 적용
  • HTTP→HTTPS Redirect
  • 외부 요청을 API Gateway Service로 전달

Frontend와 Backend의 실제 경로 분기는 API Gateway에서 담당합니다.


9. API Gateway에서 FE와 BE 분리하기

Spring Cloud Gateway를 사용하는 경우 다음과 같이 구성할 수 있습니다.

spring:
  cloud:
    gateway:
      routes:
        - id: backend-api
          order: 0
          uri: http://backend:8080
          predicates:
            - Path=/api/**

        - id: frontend
          order: 100
          uri: http://frontend:80
          predicates:
            - Path=/**

외부 API 경로는 다음처럼 통일하는 것이 관리하기 편합니다.

/api/properties
/api/users
/api/orders
/api/auth

Backend API 요청 흐름은 다음과 같습니다.

https://app.company.com/api/properties
    ↓
Contour
    ↓
API Gateway
    ↓
Backend

Frontend 요청은 다음과 같이 처리됩니다.

https://app.company.com/
https://app.company.com/login
https://app.company.com/dashboard
    ↓
Contour
    ↓
API Gateway
    ↓
Frontend

현재 API가 /properties처럼 /api prefix 없이 운영되고 있다면 API Gateway에서 개별 경로를 Backend로 연결할 수도 있습니다.

spring:
  cloud:
    gateway:
      routes:
        - id: backend-properties
          order: 0
          uri: http://backend:8080
          predicates:
            - Path=/properties,/properties/**

        - id: frontend
          order: 100
          uri: http://frontend:80
          predicates:
            - Path=/**

장기적으로는 API 경로를 /api/**로 통일하는 것이 Frontend 경로와 API 경로의 충돌을 방지하기 좋습니다.


10. Frontend는 상대경로로 API를 호출한다

Frontend에서는 가능하면 다음과 같은 절대주소를 사용하지 않는 것이 좋습니다.

fetch("https://app.company.com/api/properties");

대신 상대경로를 사용합니다.

fetch("/api/properties");

Axios를 사용한다면 다음과 같이 구성할 수 있습니다.

const api = axios.create({
  baseURL: "/api"
});

api.get("/properties");

이 경우 Frontend와 API는 동일한 Origin을 사용합니다.

Frontend
https://app.company.com

Backend API
https://app.company.com/api/properties

브라우저 기준으로 프로토콜, 도메인, 포트가 모두 같기 때문에 일반적인 CORS 설정이 필요하지 않습니다.


11. permitInsecure 설정 주의

Contour HTTPProxy의 Route에 다음 설정이 있으면 HTTP 요청이 HTTPS로 Redirect되지 않고 Backend로 그대로 전달될 수 있습니다.

permitInsecure: true

예를 들어 다음 설정은 HTTP 요청을 허용합니다.

routes:
  - conditions:
      - prefix: /
    permitInsecure: true
    services:
      - name: api-gateway
        port: 8080

HTTP→HTTPS 자동 Redirect를 사용할 계획이라면 permitInsecure를 설정하지 않아야 합니다.

전체 HTTPProxy에서 해당 설정이 있는지 확인합니다.

kubectl get httpproxy -A -o yaml |
grep -n -C 5 permitInsecure

12. NGINX Ingress와 Contour HTTPProxy 비교

항목 NGINX Ingress Contour HTTPProxy
기본 리소스 Kubernetes Ingress Contour HTTPProxy CRD
TLS 설정 spec.tls virtualhost.tls
HTTP→HTTPS 기본 Redirect 308 301
Redirect 활성 조건 Ingress TLS 활성화 VirtualHost TLS 활성화
HTTP 허용 설정 ssl-redirect: "false" permitInsecure: true
Backend 지정 Ingress Backend Service routes.services
경로 라우팅 spec.rules.http.paths routes.conditions
세부 기능 Annotation 중심 구조화된 HTTPProxy 필드
Data Plane NGINX Envoy

두 Controller 모두 TLS가 설정되면 HTTP 요청을 HTTPS로 전환할 수 있습니다.

Ingress-NGINX
HTTP → 308 Permanent Redirect → HTTPS

Contour
HTTP → 301 Moved Permanently → HTTPS

Ingress-NGINX는 Kubernetes Ingress와 Annotation 중심으로 동작하고, Contour는 Envoy를 Data Plane으로 사용하면서 HTTPProxy의 구조화된 설정을 제공합니다.


13. Contour 의존성을 최소화하는 운영 원칙

현재는 Contour의 자동 Redirect를 활용하되, Contour가 애플리케이션 내부 정책까지 담당하지 않도록 범위를 제한하는 것이 좋습니다.

Contour가 담당할 기능

  • TLS 종료
  • HTTP→HTTPS Redirect
  • 외부 트래픽을 API Gateway로 전달

API Gateway가 담당할 기능

  • Frontend와 Backend 경로 분기
  • 인증과 인가
  • API 접근 제어
  • CORS
  • Rate Limit
  • API 로깅
  • 경로 Rewrite

Frontend가 담당할 기능

  • 정적 파일 제공
  • SPA Fallback
  • 상대경로 API 호출

Backend가 담당할 기능

  • 비즈니스 API
  • 데이터 처리
  • 내부 서비스 호출

Contour에는 가능한 한 다음과 같은 단일 Route만 둡니다.

routes:
  - conditions:
      - prefix: /
    services:
      - name: api-gateway
        port: 8080

이렇게 구성하면 추후 Contour를 다른 Ingress Controller나 Gateway API 구현체로 변경하더라도 애플리케이션 변경 범위를 줄일 수 있습니다.


14. 실제 점검 방법

HTTPProxy 상태 확인

kubectl get httpproxy -n app

kubectl describe httpproxy app-proxy -n app

HTTPProxy 상태가 Valid인지 확인합니다.

TLS Secret 확인

kubectl get secret app-tls -n app

Secret 타입도 확인합니다.

kubectl get secret app-tls -n app \
  -o jsonpath='{.type}'

정상적인 TLS Secret 타입은 다음과 같습니다.

kubernetes.io/tls

API Gateway Service 확인

kubectl get service api-gateway -n app

kubectl describe service api-gateway -n app

kubectl get endpoints api-gateway -n app

Endpoint가 없다면 Service Selector와 API Gateway Pod Label이 일치하지 않는지 확인해야 합니다.


15. HTTP→HTTPS 동작 테스트

루트 경로를 확인합니다.

curl -I http://app.company.com/

Contour 자동 Redirect가 정상이라면 다음과 같은 응답이 나와야 합니다.

HTTP/1.1 301 Moved Permanently
Location: https://app.company.com/

하위 경로도 확인합니다.

curl -I http://app.company.com/login

curl -I "http://app.company.com/properties?id=100"

경로와 Query String이 유지되는지 확인합니다.

HTTP/1.1 301 Moved Permanently
Location: https://app.company.com/properties?id=100

HTTPS 요청도 확인합니다.

curl -kI https://app.company.com/

정상적으로 API Gateway 또는 Frontend 응답이 반환되어야 합니다.


16. HTTP 요청이 200으로 응답한다면

다음 명령의 결과가 Redirect가 아닌 200 OK라면 HTTP 요청이 Backend까지 전달되고 있다는 의미입니다.

curl -I http://app.company.com/

문제 상태는 다음과 같습니다.

HTTP/1.1 200 OK
Content-Type: text/html

이 경우 다음 항목을 순서대로 확인합니다.

1. virtualhost.tls 누락

virtualhost:
  fqdn: app.company.com

다음과 같이 TLS 설정이 있어야 합니다.

virtualhost:
  fqdn: app.company.com
  tls:
    secretName: app-tls

2. permitInsecure 설정

permitInsecure: true

HTTP→HTTPS Redirect를 적용하려면 해당 설정을 제거해야 합니다.

3. 같은 도메인을 사용하는 다른 Ingress 또는 HTTPProxy

kubectl get ingress,httpproxy -A

같은 hostname을 사용하는 리소스가 여러 개인지 확인합니다.

4. TLS가 외부 Load Balancer에서만 종료되는 경우

사용자
  ↓ HTTPS
외부 Load Balancer
  ↓ HTTP
Contour

외부 Load Balancer가 TLS를 종료하는 구조라면 80 포트의 HTTP→HTTPS Redirect를 외부 Load Balancer에서 처리해야 할 수 있습니다.

5. 80 포트가 다른 Service로 연결된 경우

클라우드 Load Balancer의 80 Listener가 Contour Envoy Service가 아니라 Frontend 또는 다른 Service로 직접 연결되어 있는지 확인합니다.


마무리

기존 NGINX Ingress 환경에서 별도의 Redirect 설정을 찾지 못했더라도, TLS가 설정된 Ingress라면 Ingress-NGINX가 기본적으로 HTTP 요청을 HTTPS로 전환하고 있었을 가능성이 큽니다.

Contour에서도 TLS가 설정된 HTTPProxy를 사용하면 HTTP→HTTPS 자동 Redirect를 적용할 수 있습니다.

현재 API Gateway, Frontend, Backend 구조에서는 Contour 설정을 복잡하게 만들 필요가 없습니다.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: app-proxy
  namespace: app
spec:
  ingressClassName: contour

  virtualhost:
    fqdn: app.company.com
    tls:
      secretName: app-tls

  routes:
    - conditions:
        - prefix: /
      services:
        - name: api-gateway
          port: 8080

최종 역할은 다음과 같이 정리할 수 있습니다.

Contour
- TLS 종료
- HTTP→HTTPS 자동 Redirect
- 모든 요청을 API Gateway로 전달

API Gateway
- Frontend와 Backend 경로 분기
- 인증·인가
- API 정책

Frontend
- 화면과 정적 파일 제공
- 상대경로로 API 호출

Backend
- 비즈니스 API 처리

핵심은 CORS 허용 범위를 무작정 확대하는 것이 아니라, HTTP로 Frontend 페이지가 제공되지 않도록 외부 진입 구간에서 HTTPS로 통일하는 것입니다.


태그

Kubernetes, K8s, 쿠버네티스, Contour, HTTPProxy, NGINXIngress, IngressController, Envoy, HTTP리다이렉트, HTTPS리다이렉트, TLS설정, APIGateway, SpringCloudGateway, CORS, 클라우드이전

반응형

댓글