Dev Notes
Web Vitals
Web Vitals
Dev Notes
Docs홈 k3s 클러스터 구축기 (2) — 외부 노출과 TLS 인증서
Web Vitals
Web Vitals
GitHub
Infra36분

홈 k3s 클러스터 구축기 (2) — 외부 노출과 TLS 인증서

공유기 포트포워딩, 도메인 연결, cert-manager를 통한 HTTPS 적용 과정

2026년 2월 3일
k3skubernetestlscert-managertraefiklets-encrypt

환경: k3s v1.31+ (Traefik 내장), cert-manager v1.19+, 도메인 등록 대행: 호스팅케이알

들어가며

1편에서 Ubuntu 설치와 k3s 클러스터 구성을 마쳤습니다. 두 대의 노드가 내부 네트워크에서 정상적으로 통신하고 있지만, 아직 외부 인터넷에서는 접근할 수 없는 상태입니다. 클러스터가 집 공유기 뒤에 있기 때문입니다.

이 편에서는 공유기 포트포워딩으로 외부 트래픽을 클러스터로 전달하고, 도메인을 연결한 뒤, Let's Encrypt로 TLS 인증서를 자동 발급받아 HTTPS를 적용하는 과정을 다룹니다.

단계목표
포트포워딩외부 트래픽 → 공유기 → k3s 노드
도메인 연결mirunamu.info → 집 공인 IP
TLS 인증서cert-manager + Let's Encrypt로 HTTPS 자동 적용

공유기 포트포워딩

외부에서 들어오는 HTTP/HTTPS 트래픽이 집 공유기를 통과하여 k3s 클러스터에 도달하려면, 공유기에서 해당 포트를 마스터 노드의 내부 IP로 전달하도록 설정해야 합니다.

공유기 관리 페이지(보통 192.168.0.1 또는 192.168.1.1)에 접속한 뒤, 포트포워딩(또는 가상서버) 메뉴에서 다음 세 개의 규칙을 추가합니다.

외부 포트내부 IP내부 포트프로토콜용도
80192.168.0.2080TCPHTTP (TLS 인증서 발급 챌린지)
443192.168.0.20443TCPHTTPS (실제 서비스 트래픽)
6443192.168.0.206443TCPKubernetes API

6443 포트 보안

6443은 Kubernetes API 서버 포트입니다. 외부에 열어두면 클러스터 관리 인터페이스가 인터넷에 노출되므로, 외부에서 kubectl 접근이 필요하지 않다면 이 포트는 포워딩하지 않는 것이 안전합니다. 내부 네트워크에서만 관리하는 경우에는 80, 443만 열면 충분합니다.

k3s는 Traefik을 기본 Ingress Controller로 내장하고 있어서, 80번과 443번 포트로 들어오는 트래픽은 Traefik이 받아 적절한 서비스로 라우팅합니다. 별도의 Ingress Controller 설치가 필요 없다는 것이 k3s의 장점 중 하나입니다.

도메인 연결

도메인 등록

도메인은 호스팅케이알에서 등록했습니다. .info 도메인은 .com이나 .kr에 비해 가격이 저렴하면서도 개인 프로젝트 용도로 무난합니다. 도메인 등록 대행 업체는 어디를 사용해도 상관없으며, 중요한 것은 DNS 레코드를 관리할 수 있는지 여부입니다.

A 레코드 설정

도메인을 집 공유기의 공인 IP에 연결하려면 DNS A 레코드를 설정해야 합니다. 먼저 집의 공인 IP를 확인합니다.

Bash
# 마스터 노드에서 실행
curl -s https://api.ipify.org

출력된 IP를 도메인 관리 페이지에서 A 레코드로 등록합니다.

타입호스트값TTL
A@203.0.113.50 (공인 IP)300
A*203.0.113.50 (공인 IP)300

@는 mirunamu.info 자체를 의미하고, *는 모든 서브도메인(app.mirunamu.info, api.mirunamu.info 등)을 같은 IP로 보내는 와일드카드 레코드입니다. 서브도메인별로 다른 서비스를 배포할 계획이라면 와일드카드를 설정해두는 것이 편리합니다.

DDNS는 설정하지 않았습니다

가정용 인터넷은 ISP가 유동 공인 IP를 할당하므로, 이론적으로는 IP가 변경될 때마다 A 레코드를 갱신해주는 DDNS(Dynamic DNS)를 구성하는 것이 정석입니다. Cloudflare로 DNS를 옮기면 API를 통한 자동 갱신 스크립트를 돌릴 수 있습니다.

다만 실제로는 가정용 회선이라도 공인 IP가 수개월~수년간 유지되는 경우가 대부분이어서, 이 클러스터에서는 별도의 DDNS를 구성하지 않고 A 레코드에 공인 IP를 직접 입력해두었습니다. IP가 변경되면 수동으로 A 레코드를 갱신하는 방식으로 운영하고 있습니다.

DDNS가 필요한 경우

IP가 자주 바뀌는 환경이라면 Cloudflare로 네임서버를 옮기고 API 토큰 기반 DDNS 스크립트를 cron으로 돌리는 것을 권장합니다. Cloudflare는 무료 플랜에서도 DNS API를 제공하며, cert-manager의 DNS-01 챌린지도 함께 활용할 수 있어 일석이조입니다.

DNS 설정 후 전파까지 최대 24~48시간이 걸릴 수 있지만, TTL을 짧게 설정했다면 보통 몇 분 내에 반영됩니다. 전파 상태는 아래 명령으로 확인합니다.

Bash
# DNS 전파 확인
nslookup mirunamu.info

설정한 공인 IP가 응답에 나타나면 도메인 연결이 완료된 것입니다.

cert-manager 설치

TLS 인증서를 수동으로 발급받고 갱신하는 것은 번거로운 작업입니다. cert-manager는 Kubernetes 클러스터 내에서 Let's Encrypt 인증서를 자동으로 발급하고 갱신해주는 컨트롤러입니다.

Bash
# cert-manager 설치
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.2/cert-manager.yaml

이 명령 하나로 CRD(Custom Resource Definition), 컨트롤러, webhook까지 cert-manager 네임스페이스에 전부 배포됩니다. 설치 후 Pod가 정상적으로 올라왔는지 확인합니다.

Bash
kubectl get pods -n cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-xxxxxxxxx-xxxxx               1/1     Running   0          30s
cert-manager-cainjector-xxxxxxxxx-xxxxx    1/1     Running   0          30s
cert-manager-webhook-xxxxxxxxx-xxxxx       1/1     Running   0          30s

세 개의 Pod(cert-manager, cainjector, webhook)가 모두 Running 상태가 되면 정상입니다.

ClusterIssuer 생성

cert-manager가 설치되었으면, 인증서를 어디서 발급받을지 정의하는 ClusterIssuer를 생성합니다. Let's Encrypt의 HTTP-01 챌린지를 사용하며, 이 방식은 80번 포트를 통해 도메인 소유권을 검증합니다. 앞서 포트포워딩에서 80번을 열어둔 이유가 바로 이것입니다.

Staging → Production 순서

Let's Encrypt에는 등록 도메인당 주 50회라는 인증서 발급 횟수 제한이 있습니다. 설정이 올바른지 먼저 Staging 서버로 테스트한 뒤, 정상 동작이 확인되면 Production으로 전환하는 것이 안전합니다.

YAML
# cluster-issuer-staging.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: your-email@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
    - http01:
        ingress:
          ingressClassName: traefik

ingressClassName에는 클러스터에서 실제로 동작 중인 Ingress Controller의 이름을 넣어야 합니다. k3s는 Traefik을 기본으로 내장하고 있으므로 traefik을 지정합니다. 만약 --disable=traefik으로 기본 Traefik을 끄고 nginx ingress controller를 별도 설치했다면 nginx를 넣으면 됩니다.

Bash
# Staging부터 적용
kubectl apply -f cluster-issuer-staging.yaml
 
# 상태 확인
kubectl get clusterissuer
NAME                  READY   AGE
letsencrypt-staging   True    10s

READY가 True이면 Let's Encrypt ACME 서버와의 연동이 정상적으로 완료된 것입니다.

Ingress에 TLS 적용

ClusterIssuer가 준비되었으면, 실제 서비스에 Ingress를 생성하여 HTTPS를 적용합니다. 아래는 테스트용 nginx 서비스에 TLS를 적용하는 예시입니다.

Bash
# 테스트용 nginx 배포
kubectl create deployment nginx-test --image=nginx --port=80
kubectl expose deployment nginx-test --port=80 --target-port=80

이 서비스에 대한 Ingress를 생성합니다. cert-manager.io/cluster-issuer 어노테이션이 cert-manager에게 자동으로 인증서를 발급하도록 지시하는 트리거입니다.

YAML
# nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-test-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  ingressClassName: traefik
  rules:
  - host: mirunamu.info
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: nginx-test
            port:
              number: 80
  tls:
  - hosts:
    - mirunamu.info
    secretName: nginx-test-tls
Bash
kubectl apply -f nginx-ingress.yaml

이 Ingress가 생성되면 cert-manager가 자동으로 동작합니다. 내부적으로는 다음과 같은 순서로 진행됩니다.

Certificate 리소스 자동 생성

cert-manager가 Ingress의 tls 섹션과 cluster-issuer 어노테이션을 감지하여 Certificate 리소스를 생성합니다.

HTTP-01 챌린지 수행

Let's Encrypt가 http://mirunamu.info/.well-known/acme-challenge/<token> 으로 HTTP 요청을 보냅니다. cert-manager는 이 요청을 처리할 임시 Ingress와 Pod를 자동으로 생성합니다. 앞서 포트포워딩한 80번 포트를 통해 이 요청이 클러스터에 도달합니다.

인증서 발급 및 저장

챌린지가 성공하면 Let's Encrypt가 인증서를 발급하고, cert-manager가 이를 nginx-test-tls Secret에 저장합니다. Traefik이 이 Secret을 읽어 HTTPS 트래픽을 처리합니다.

인증서 발급 상태를 확인합니다.

Bash
# 인증서 상태 확인
kubectl get certificate
NAME             READY   SECRET           AGE
nginx-test-tls   True    nginx-test-tls   60s

READY가 True가 되면 인증서 발급이 완료된 것입니다. 발급까지 보통 1~2분 정도 소요됩니다.

READY가 False인 경우

챌린지가 실패하면 READY가 False로 남습니다. 대부분의 원인은 다음 세 가지입니다.

  1. 포트포워딩 미설정: 80번 포트가 외부에서 클러스터까지 도달하지 못함
  2. DNS 미전파: 도메인이 아직 공인 IP로 resolve되지 않음
  3. ingressClassName 오류: traefik 대신 다른 값이 들어가 있음

kubectl describe certificate nginx-test-tls와 kubectl get challenges로 상세 원인을 확인할 수 있습니다.

Staging에서 Production으로 전환

Staging 인증서는 브라우저에서 "신뢰할 수 없는 인증서" 경고가 뜹니다. 이는 정상이며, Staging은 설정 검증 용도일 뿐입니다. Staging에서 READY: True를 확인했으면 Production으로 전환합니다.

Bash
# Production ClusterIssuer 적용
kubectl apply -f cluster-issuer-prod.yaml

Ingress의 어노테이션을 letsencrypt-prod로 변경하고, 기존 인증서 Secret을 삭제하여 재발급을 트리거합니다.

Bash
# Ingress 어노테이션을 먼저 Production으로 변경
kubectl annotate ingress nginx-test-ingress \
  cert-manager.io/cluster-issuer=letsencrypt-prod --overwrite
 
# 기존 Staging 인증서 삭제 (Production으로 재발급 트리거)
kubectl delete secret nginx-test-tls

1~2분 후 다시 kubectl get certificate로 확인하면 Production 인증서가 발급되어 있을 것입니다.

외부 접속 확인

브라우저에서 https://mirunamu.info에 접속합니다. nginx 기본 페이지가 나타나고, 주소 표시줄에 자물쇠 아이콘이 표시되면 TLS 적용이 완료된 것입니다.

Bash
# CLI로 확인
curl -v https://mirunamu.info 2>&1 | grep "subject:"

subject: CN=mirunamu.info 같은 출력이 나오면 Let's Encrypt에서 발급한 정상 인증서가 적용된 것입니다.

테스트가 끝났으면 리소스를 정리합니다.

Bash
kubectl delete ingress nginx-test-ingress
kubectl delete service nginx-test
kubectl delete deployment nginx-test
kubectl delete secret nginx-test-tls

cert-manager와 ClusterIssuer는 실제 서비스에서 계속 사용할 것이므로 삭제하지 않습니다.

트러블슈팅

정리

2편 요약

  1. 포트포워딩: 공유기에서 80(HTTP), 443(HTTPS) 포트를 마스터 노드로 전달합니다
  2. 도메인 + DNS: A 레코드에 집 공인 IP를 등록하여 도메인을 클러스터에 연결합니다
  3. cert-manager: 클러스터에 설치하여 TLS 인증서 발급과 갱신을 자동화합니다
  4. TLS 적용: Ingress에 ClusterIssuer 어노테이션을 추가하면 cert-manager가 Let's Encrypt 인증서를 자동으로 발급하고 Traefik이 HTTPS를 처리합니다

이것으로 홈 클러스터에 외부에서 HTTPS로 접속할 수 있는 환경이 갖춰졌습니다. 다음 편에서는 ArgoCD를 설치하여 GitHub 저장소와 연동하고, GitOps 방식으로 실제 애플리케이션을 자동 배포하는 파이프라인을 구축합니다.

참고 자료

  • cert-manager 공식 설치 가이드
  • Let's Encrypt HTTP-01 챌린지
  • k3s 네트워킹 요구사항
  • Traefik Ingress Controller (k3s)
Written by

Mirunamu (Park Geonwoo)

Software Developer

관련 글 더보기

Infra

홈 k3s 클러스터 구축기 (1) — Ubuntu 설치부터 워커 노드 조인까지

4코어 8GB 미니PC 2대로 시작하는 경량 Kubernetes 홈 클러스터 구축 과정

읽기
Infra

홈 k3s 클러스터 구축기 (3) — ArgoCD와 GitOps 배포 파이프라인

ArgoCD App-of-Apps, Helm Chart 분리 설계, GitHub Actions CI를 통한 GitOps 자동 배포 구축 과정

읽기
Infra

홈 k3s 클러스터 구축기 (6) — Longhorn 분산 스토리지

CSI 기반 분산 스토리지 개념과 Longhorn ArgoCD 배포, 데이터베이스 워크로드 적용

읽기
Infra

홈 k3s 클러스터 구축기 (7) — Prometheus + Grafana 모니터링

kube-prometheus-stack 기반 클러스터 모니터링 환경의 ArgoCD 배포와 Grafana 대시보드 활용

읽기
이전 글홈 k3s 클러스터 구축기 (1) — Ubuntu 설치부터 워커 노드 조인까지
다음 글홈 k3s 클러스터 구축기 (3) — ArgoCD와 GitOps 배포 파이프라인
목차
  • 들어가며
  • 공유기 포트포워딩
  • 도메인 연결
    • 도메인 등록
    • A 레코드 설정
    • DDNS는 설정하지 않았습니다
  • cert-manager 설치
  • ClusterIssuer 생성
  • Ingress에 TLS 적용
  • Staging에서 Production으로 전환
  • 외부 접속 확인
  • 트러블슈팅
  • 정리
  • 참고 자료