시리즈 안내
이 글은 “중소기업 쿠버네티스 구축” 시리즈의 5편입니다. 4편에서 MetalLB로 외부 진입 IP를 만들었지만, 그 IP는 서비스 하나에만 연결됩니다. 이번 편에서는 Ingress로 IP 하나 뒤에서 도메인별로 트래픽을 갈래 나누고, OPNsense Caddy와 연동해 HTTPS까지 입혀 외부에서 도메인으로 접속하는 전체 경로를 완성합니다.
들어가며
이번 편은 시리즈의 분수령입니다. 여기까지 오면 외부 브라우저에서 https://argocd.prd.dztechwill.com 같은 주소로 클러스터 안의 서비스에 접속할 수 있게 됩니다. 흩어져 있던 조각(DNS·TLS·MetalLB·Ingress·서비스)이 하나의 경로로 꿰어지는 순간이죠.
먼저 우리가 완성할 트래픽 경로를 다시 봅니다.
[브라우저]
│ HTTPS (*.prd.dztechwill.com 와일드카드 인증서)
▼
[OPNsense + Caddy] ← TLS 종단 (HTTPS 복호화)
│ HTTP
▼
[MetalLB VIP 10.0.12.200]
│
▼
[Ingress-NGINX] ← Host 헤더로 어느 서비스인지 판단
│
▼
[Service → Pod] ← 실제 애플리케이션
각 단계가 무슨 일을 하는지 하나씩 짚으며 구성하겠습니다.
1. Ingress란 무엇인가
4편 끝에서 던진 질문 — “서비스가 수십 개면 IP를 수십 개 받아야 하나?” — 의 답이 Ingress입니다.
Ingress는 하나의 진입점에서 도메인(Host 헤더)이나 경로(path)를 보고 알맞은 서비스로 트래픽을 나눠주는 라우터입니다. 호텔 프런트에 비유하면, 손님(요청)이 “ArgoCD 방 주세요”라고 하면 프런트가 해당 방으로 안내하는 식입니다.
argocd.prd...→ ArgoCD 서비스로s3.prd...→ MinIO 서비스로dash.prd...→ 대시보드 서비스로
이 모든 게 MetalLB IP 하나(.200) 뒤에서 일어납니다. 도메인만 추가하면 새 서비스를 같은 IP로 노출할 수 있으니, IP를 아끼고 관리도 단순해집니다.
이 라우팅을 실제로 수행하는 게 Ingress 컨트롤러이고, 우리는 가장 널리 쓰이는 Ingress-NGINX를 씁니다. (3편에서 K3s 기본 Traefik을 꺼둔 이유가 이것입니다.)
2. Ingress-NGINX 설치 (Helm)
Helm으로 설치합니다. Helm은 쿠버네티스용 패키지 매니저로, 복잡한 구성을 명령 몇 줄로 설치하게 해줍니다.
먼저 Helm이 없다면 설치합니다.
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
Ingress-NGINX 저장소를 추가합니다.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
이제 설치합니다. 우리 구성에 맞는 옵션을 함께 줍니다.
helm install ingress-nginx ingress-nginx/ingress-nginx
--namespace ingress-nginx --create-namespace
--set controller.service.type=LoadBalancer
--set controller.service.loadBalancerIP=10.0.12.200
--set controller.nodeSelector.role=platform
--set controller.kind=Deployment
옵션 설명:
controller.service.type=LoadBalancer: Ingress가 MetalLB로부터 외부 IP를 받도록controller.service.loadBalancerIP=10.0.12.200: 1편에서 Ingress용으로 예약한.200고정 할당controller.nodeSelector.role=platform: 3편에서 붙인role=platform라벨을 이용해 플랫폼 노드에 배치
설치 확인
kubectl get svc -n ingress-nginx
ingress-nginx-controller 서비스의 EXTERNAL-IP가 10.0.12.200으로 잡혔으면 성공입니다. MetalLB가 약속대로 그 IP를 발급한 것입니다.
3. TLS 전략: 어디서 HTTPS를 끝낼 것인가
여기서 중요한 설계 결정이 하나 있습니다. HTTPS(TLS) 암호화를 어디서 풀(종단할) 것인가?
두 가지 방식이 있습니다.
- 방식 A — Caddy에서 종단, 내부는 평문 HTTP: 외부 구간은 HTTPS, OPNsense Caddy가 복호화한 뒤 클러스터 내부로는 평문 HTTP로 전달. 내부망은 신뢰할 수 있는 사설망이므로 안전.
- 방식 B — 끝까지 HTTPS(E2E TLS): 내부 구간도 내부 CA 인증서로 다시 암호화. 보안은 더 강하지만, 내부 CA 발급·신뢰 설정 등 복잡도가 큼.
우리는 방식 A를 택했습니다. 이유는 단순성과 운영 편의입니다. OPNsense Caddy가 이미 외부 ACME 인증서를 들고 TLS를 종단하고 있으니, 내부는 평문 HTTP로 통일하면 인증서 관리 포인트가 한 곳(Caddy)으로 모입니다. 사설망 내부 트래픽이라 평문이어도 현실적 위험이 낮습니다.
이 결정 덕분에 ArgoCD·Ingress 등 내부 컴포넌트를 모두 평문(insecure) 수신으로 통일할 수 있어, 포트·프로토콜 충돌 같은 골치 아픈 문제를 사전에 차단합니다. (실제로 이 통일을 안 하면 어떤 문제가 생기는지는 7절에서 다룹니다.)
4. 와일드카드 인증서와 도메인 정책
OPNsense Caddy에서 발급할 인증서는 와일드카드로 갑니다. *.prd.dztechwill.com 하나면 argocd.prd, s3.prd, dash.prd 등 하위 도메인을 전부 커버합니다. 서비스를 추가할 때마다 인증서를 새로 발급할 필요가 없습니다.
도메인은 용도별로 이렇게 나눴습니다.
| 카테고리 | 도메인 패턴 | 예시 |
|---|---|---|
| 개발 환경 | *.dev.dztechwill.com | argocd.dev / dash.dev |
| 운영 관리용 | *.prd.dztechwill.com | argocd.prd / dash.prd / s3.prd |
| 운영 실서비스 | *.dztechwill.com 또는 별도 도메인 | sso.dztechwill.com |
관리용 도구(ArgoCD·대시보드 등)는 *.prd로, 실제 사용자에게 노출되는 서비스(SSO 등)는 별도 도메인으로 분리해 관리 영역과 서비스 영역을 명확히 구분했습니다.
5. OPNsense Caddy 설정
OPNsense의 Caddy가 외부 HTTPS를 받아 내부 MetalLB IP(.200)로 평문 전달하도록 리버스 프록시를 설정합니다. 개념적으로 Caddy 설정은 이런 형태입니다.
*.prd.dztechwill.com {
tls {
# 와일드카드 인증서 (ACME DNS-01 등으로 발급)
}
reverse_proxy 10.0.12.200:80
}
- 외부에서
*.prd.dztechwill.com으로 오는 HTTPS를 Caddy가 받고 - TLS를 복호화한 뒤
- 내부 Ingress(MetalLB .200)의 80 포트(HTTP)로 전달
OPNsense에서는 보통 Caddy 플러그인 UI로 설정하지만, 핵심은 위와 같습니다. Host 헤더가 그대로 전달되어야 Ingress가 어느 서비스인지 판단할 수 있으니, reverse_proxy가 Host를 보존하는지 확인하세요.
6. Ingress 리소스 예시
이제 클러스터 안에 실제 Ingress 규칙을 만들어 “이 도메인은 이 서비스로”를 정의합니다. 예를 들어 (다음 편에서 설치할) ArgoCD를 노출하는 Ingress는 이런 모양입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-ingress
namespace: argocd
spec:
ingressClassName: nginx
rules:
- host: argocd.prd.dztechwill.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 80
host로 도메인을, backend로 보낼 서비스를 지정합니다. 새 서비스를 노출할 땐 이런 Ingress를 하나씩 추가하면 됩니다. (실제 ArgoCD Ingress는 7편에서 적용합니다. 여기서는 형태만 봅니다.)
7. 트러블슈팅: Caddy ↔ Ingress 프로토콜·포트 충돌
실제 구축에서 마주친 문제를 공유합니다. 처음엔 Caddy 설정에서 내부로 전달할 때 https://와 잘못된 포트를 섞어 쓰는 바람에 Caddyfile 파싱 에러가 났습니다.
원인은 TLS 종단 지점이 명확하지 않아서였습니다. Caddy에서 이미 TLS를 끝내기로(방식 A) 정했으면, 내부로는 반드시 평문 HTTP + 80 포트로 보내야 합니다. 그런데 내부 전달을 https://...:80처럼 스킴과 포트를 어긋나게 적으면 충돌이 납니다.
해결: 내부 전달 구간을 HTTP 프로토콜 + 80 포트로 통일했습니다. Ingress-NGINX도 평문 80을 수신하도록 맞췄고요(3절의 “insecure 통일”). TLS는 오직 Caddy 한 곳에서만 다룬다는 원칙을 지키니 문제가 사라졌습니다.
교훈: TLS를 어디서 끝낼지 한 곳으로 정하고, 그 뒤로는 일관되게 평문으로 통일하라. 종단 지점이 흐릿하면 포트·프로토콜 충돌이 반복됩니다.
8. 전체 경로 검증
모든 게 연결됐는지 확인하는 가장 좋은 방법은, 실제 서비스를 하나 띄워 외부에서 도메인으로 접속해보는 것입니다. 이건 다음 편(ArgoCD)에서 자연스럽게 하게 됩니다. 지금 단계에서 확인할 것은:
# Ingress 컨트롤러가 .200 IP를 받았는지
kubectl get svc -n ingress-nginx ingress-nginx-controller
# Ingress 클래스가 등록됐는지
kubectl get ingressclass
EXTERNAL-IP = 10.0.12.200, ingressclass = nginx가 확인되면, 외부 노출 인프라는 준비된 것입니다. 남은 건 그 뒤에 붙일 서비스들뿐입니다.
마치며
5편에서 트래픽 경로가 완성됐습니다. 핵심을 정리하면:
- Ingress: MetalLB IP 하나 뒤에서 도메인별로 서비스를 갈래 나누는 라우터. Ingress-NGINX를 플랫폼 노드에 설치하고
.200을 고정 할당했습니다. - TLS 전략: Caddy에서 종단하고 내부는 평문 HTTP로 통일(방식 A). 인증서 관리를 Caddy 한 곳으로 모았습니다.
- 와일드카드 + 도메인 정책:
*.prd하나로 하위 도메인을 커버하고, 관리용·실서비스 도메인을 분리했습니다. - 트러블슈팅: TLS 종단 지점을 한 곳으로 명확히 하고 그 뒤로 평문 통일 — 포트·프로토콜 충돌의 근본 해법.
이제 DNS → TLS → MetalLB → Ingress → 서비스로 이어지는 길이 뚫렸습니다. 다음 편부터는 이 길 위에 실제 도구들을 하나씩 올립니다. 06편에서는 첫 손님으로 웹 대시보드(Headlamp)를 설치해, 클러스터 상태를 눈으로 보며 운영할 수 있게 만들겠습니다.
이전 글: 중소기업 쿠버네티스 구축 – 04. 스토리지와 로드밸런서: 트래픽 진입점
다음 글: 중소기업 쿠버네티스 구축 – 06. 웹 대시보드: 클러스터 시각화 (Headlamp·ServiceAccount)