Development Log
3편. Docker에서 K3s로 — Kubernetes와 Traefik을 선택한 이유.
Docker에서 사람이 관리하던 실행 상태를 K3s의 Desired State와 Traefik 요청 경로로 옮긴 과정입니다.
- #Docker
- #Kubernetes
- #K3s
- #Traefik
문제 Docker Compose를 직접 관리하는 방식에서 컨테이너 재생성·라우팅·TLS까지 사람이 계속 챙겨야 했다. 선택 단일 홈서버에 맞는 K3s를 도입하고, Ingress Controller는 NGINX와 비교한 뒤 K3s 통합성과 TLS 자동화 구조를 보고 Traefik을 선택했다. 결과 ServiceLB + Traefik + Ingress + cert-manager로 외부 80/443, 라우팅, 인증서 생명주기를 Kubernetes 안으로 옮겼다. 배운 점 Kubernetes 도입의 핵심은 컨테이너 실행 자체가 아니라 Desired State와 역할 분리였고, 기술 선택은 익숙함보다 현재 운영 요구와 구조적 단순성을 기준으로 해야 했다.
Docker를 버리기 위해 Kubernetes를 선택한 것은 아니었다. 컨테이너를 사람이 직접 관리하던 부분을 Kubernetes는 어떻게 다루는지가 궁금했다. 홈서버라는 제약 안에서 K3s를 선택하고, Pod/Deployment/Service를 익히고, Ingress Controller로 NGINX와 Traefik을 비교한 과정을 다룬다.
왜 K3s였나
Docker Compose를 계속 사용할 수도 있었고 일반 Kubernetes를 구성할 수도 있었다. 하지만 한 대짜리 홈서버에서 Kubernetes API와 운영 모델을 직접 경험하려는 목적에는 K3s가 가장 맞았다.
Desired State를 처음 체감하다
nginx:alpine으로 Deployment를 만들고 replicas를 변경하고 Pod를 지워보면서, 직접 컨테이너를 살리는 대신 원하는 상태를 선언하는 방식을 경험했다.
Service → Deployment → Pod가 아니었다
Service가 Deployment를 거쳐 Pod로 가는 것이 아니라 Service는 label selector로 Pod를 직접 선택하고, Deployment는 별도로 Pod의 생명주기를 관리한다는 점에서 한 번 크게 헷갈렸다.
Ingress Controller는 무엇을 쓸까
Ingress 규칙을 실제 요청 처리로 바꿔줄 Controller가 필요했다. 처음에는 기존에 사용해본 Nginx가 익숙했기 때문에 NGINX Ingress Controller가 자연스러운 후보였다.
하지만 Traefik을 선택하면서 가장 크게 본 것은 HTTPS 인증서 관리까지 Kubernetes 안에서 자동화할 수 있다는 점이었다. 기존 홈서버에서는 Host Nginx와 Certbot을 이용해 Let's Encrypt 인증서를 발급하고 갱신해야 했다. Kubernetes로 배포 구조를 옮긴다면 라우팅뿐 아니라 TLS 인증서도 애플리케이션 배포 구성과 함께 관리하고 싶었다.
Traefik은 Kubernetes Ingress와 잘 통합되고, cert-manager + Let's Encrypt를 함께 구성하면 Ingress에 선언한 도메인을 기준으로 인증서 발급과 갱신을 자동화할 수 있다. 즉 앞으로는 인증서 만료 시점을 사람이 확인하고 갱신하는 운영 작업을 줄일 수 있는 구조를 만들 수 있다는 점이 매력적이었다.
여기에 이미 Ubuntu Host에 Nginx가 있었기 때문에 Kubernetes 내부까지 NGINX Ingress Controller를 추가해 Nginx 계층을 중복시키기보다, K3s에 기본 포함되어 있고 현재 필요한 Host/path routing에도 충분한 Traefik을 사용하는 쪽이 더 단순했다.
1차 구성: Host Nginx를 남기고 Traefik을 NodePort로 연결
처음에는 기존 Host Nginx가 이미 80/443을 사용하고 있었기 때문에 한 번에 진입점을 바꾸지 않았다. Host Nginx가 TLS를 종료한 뒤 Traefik의 NodePort 32080으로 넘기는 과도기 구조를 먼저 만들었다.
이 과정에서 Traefik Service를 LoadBalancer로 두면 외부 IP가 잡히지 않는 문제를 겪었다. 나중에 확인해보니 K3s 실행 옵션에 --disable=servicelb가 들어가 있었고, ServiceLB 자체가 비활성화된 상태였다. 당시에는 현재 환경에 맞춰 Traefik을 NodePort로 사용했다.
cert-manager + Let's Encrypt로 TLS 자동화
다음 목표는 기존 Certbot의 역할까지 Kubernetes 안으로 가져오는 것이었다. cert-manager를 설치하고 먼저 Let's Encrypt Staging ClusterIssuer로 HTTP-01 검증 경로를 확인했다.
Host Nginx가 모든 HTTP 요청을 HTTPS로 redirect하고 있었기 때문에 /.well-known/acme-challenge/ 경로만 Traefik 32080으로 전달하도록 예외를 만들었다. 테스트 요청이 Next.js의 404까지 도달하는 것을 확인하면서 Host Nginx :80 → Traefik → Ingress → Service → Pod 경로가 실제로 연결되어 있음을 검증했다.
Staging 인증서 발급이 성공한 뒤 Production ClusterIssuer와 Certificate를 만들었고, ddongmy.com 인증서를 Kubernetes Secret ddongmy-tls로 발급했다. 이후 web-ingress의 spec.tls.secretName에 ddongmy-tls를 연결했다.
Traefik의 HTTPS NodePort 32443에 직접 요청해 새 인증서가 실제로 제공되는 것까지 확인했다. 이 시점에서 인증서의 발급과 갱신 주체는 Host의 Certbot이 아니라 cert-manager가 되었다.
최종 구성: Host Nginx 제거, Traefik이 80/443 직접 수신
인증서 자동화가 동작하는 것을 확인한 뒤에는 Host Nginx 자체가 더 이상 필요하지 않았다. 사용하지 않던 daily.ddongmy.com, k8s.ddongmy.com도 정리 대상이었고, 실제 서비스는 ddongmy.com 하나만 남아 있었다.
K3s systemd 설정을 확인하니 다음 옵션이 있었다.
--disable=traefik
--disable=servicelb
Traefik은 별도로 Helm 설치해 사용하고 있었기 때문에 --disable=traefik은 유지하고, --disable=servicelb만 제거했다. K3s를 재시작하자 svclb-traefik-* Pod가 생성되었고, Traefik Service가 LoadBalancer로 동작하면서 내부 네트워크의 External IP 192.168.219.200을 받았다.
그 후 Host Nginx를 중지하고 비활성화했다. 외부 ddongmy.com:443 요청에서 Let's Encrypt 인증서가 정상적으로 제공되고 애플리케이션도 정상 응답하는 것을 확인했다. 마지막으로 서버 자체를 재부팅해 K3s, ServiceLB, Traefik, Ingress, Pod가 자동으로 복구되고 HTTPS 서비스가 유지되는 것까지 검증했다.
결과적으로 기존의 Host Nginx + Certbot 계층을 제거하고, 외부 라우팅은 Traefik, 인증서 생명주기는 cert-manager, 서비스 상태 관리는 Kubernetes가 담당하는 구조로 역할을 나눴다.
Traefik을 선택한 가장 큰 이유였던
cert-manager + Let's Encrypt** 기반 TLS 자동화까지 실제로 완성했다.** 처음에는 Host Nginx와 Traefik을 공존시키며 안전하게 경로를 검증했고, 인증서 발급 성공 후 ServiceLB를 다시 활성화해 최종적으로 Host Nginx를 제거했다. 이제ddongmy.com의 외부 80/443 진입점부터 Ingress, TLS Secret, Service, Pod까지 Kubernetes 안에서 관리한다.