태그 보관물: Cronjob

Migrating Ingress Controller: ingress-nginx EOL to Traefik v3.7.5 with Wildcard TLS Automation

개요

kubernetes/ingress-nginx 프로젝트가 2026년 3월 31일부로 EOL(End of Life)을 선언하고 아카이브되었습니다. 이를 계기로 ingress-nginx에서 Traefik으로 마이그레이션을 진행하여 자체 관리형 Kubernetes 클러스터의 Ingress 컨트롤러를 Traefik v3.7.5로 교체하고, 모니터링 스택(Prometheus / Alertmanager / Grafana) 도메인을 *.sierracloud.dev로 전환하였습니다. 아울러 HAProxy 서버에서 관리하는 Let’s Encrypt 와일드카드 인증서를 Kubernetes 클러스터에 자동 동기화하는 CronJob을 구성하였습니다.

대안으로 Contour, Kong, HAProxy Ingress 등을 검토하였으나, Traefik을 선택한 이유는 다음과 같습니다. Helm chart가 잘 관리되고 있으며, TLSStore를 통한 와일드카드 인증서 중앙 관리가 가능합니다. 또한 CRD(IngressRoute, Middleware 등)를 통한 고급 라우팅 설정과 Kubernetes Ingress 표준 오브젝트와의 호환성을 동시에 지원합니다. HTTP → HTTPS 강제 리다이렉트도 values.yaml 설정 한 줄로 처리됩니다.


환경

  • Kubernetes v1.33.7 (HA: Control Plane 3대 + Worker 3대)
  • MetalLB — LoadBalancer IP 풀: 192.168.x.x/29
  • HAProxy (192.168.x.x:6443) — K8s API LB 및 HTTPS 리버스 프록시 겸용
  • NAS — Let’s Encrypt 인증서 원본 보관, NFS export
  • 교체 전: kubernetes/ingress-nginx v1.10.1 (EOL)
  • 교체 후: Traefik v3.7.5 (Helm chart 41.0.0)

단계별 절차

1. ingress-nginx 설정 백업

삭제 전에 기존 설정을 코드로 백업하여 재설치 시 활용할 수 있도록 보존합니다.

# IngressClass, ConfigMap, Ingress 리소스 백업
kubectl get ingressclass nginx -o yaml > backup/ingress-nginx/ingressclass-nginx.yaml
kubectl get cm ingress-nginx-controller -n ingress-nginx -o yaml > backup/ingress-nginx/configmap-controller.yaml
kubectl get ingress -n monitoring -o yaml > backup/ingress-nginx/ingress-monitoring.yaml

# Helm values 백업
helm get values ingress-nginx -n ingress-nginx -o yaml > backup/ingress-nginx/helm-ingress-nginx-values.yaml

# 삭제 절차 문서화 후 제거
helm uninstall ingress-nginx -n ingress-nginx

2. Traefik v3.7.5 설치

ingress-nginx가 사용하던 MetalLB IP를 그대로 유지하여 HAProxy의 백엔드 설정 변경 없이 전환합니다.

helm repo add traefik https://traefik.github.io/charts
helm repo update

helm upgrade --install traefik traefik/traefik \
  -f helm/traefik-values.yaml \
  -n traefik --create-namespace

helm/traefik-values.yaml 핵심 설정:

service:
  annotations:
    metallb.universe.tf/loadBalancerIPs: "192.168.x.x"   # 기존 IP 유지

ingressClass:
  enabled: true
  isDefaultClass: true

ports:
  web:
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true   # HTTP → HTTPS 전체 리다이렉트

3. 모니터링 도메인 변경

*.sierracloud.kro.kr에서 *.sierracloud.dev로 전환하고 ingressClassName을 교체합니다. Helm values 수정 후 upgrade를 적용합니다.

# helm/monitoring-values.yaml (변경 부분)
prometheus:
  ingress:
    ingressClassName: traefik    # nginx → traefik
    hosts:
      - prometheus.sierracloud.dev

grafana:
  ingress:
    ingressClassName: traefik
    hosts:
      - grafana.sierracloud.dev
  grafana.ini:
    server:
      domain: grafana.sierracloud.dev
      root_url: https://grafana.sierracloud.dev
      protocol: http             # Traefik이 TLS 종료, Grafana 내부는 HTTP
helm upgrade monitoring prometheus-community/kube-prometheus-stack \
  -f helm/monitoring-values.yaml -n monitoring

4. 와일드카드 TLS 인증서 자동화

HAProxy 서버에서 certbot이 Let’s Encrypt 인증서를 주기적으로 갱신하고 NAS에 복사합니다. Kubernetes CronJob이 이후 NAS NFS를 마운트하여 SHA256 비교 후 변경 시에만 Secret을 갱신합니다.

certbot (HAProxy)
  └─→ NAS (NFS)
        └─→ K8s CronJob (SHA256 비교)
              └─→ traefik/wildcard-sierracloud-dev Secret
                    └─→ Traefik TLSStore default → 모든 서비스 자동 적용

TLSStore를 사용하면 각 네임스페이스의 Ingress 리소스에 secretName을 지정할 필요 없이 모든 HTTPS 라우트에 와일드카드 인증서가 자동으로 적용됩니다.

# manifests/traefik/tls-store.yaml
apiVersion: traefik.io/v1alpha1
kind: TLSStore
metadata:
  name: default
  namespace: traefik
spec:
  defaultCertificate:
    secretName: wildcard-sierracloud-dev

CronJob은 매주 갱신 주기에 맞춰 실행되며 SHA256 비교를 통해 인증서가 변경된 경우에만 Secret을 업데이트합니다.

# manifests/traefik/tls-secret-sync.yaml (핵심 부분)
schedule: "30 6 * * 1"    # 매주 월요일
timeZone: "Asia/Seoul"
concurrencyPolicy: Forbid

volumes:
  - name: certs
    nfs:
      server: 192.168.x.x
      path: /data/cert
      readOnly: true
# CronJob 컨테이너 스크립트 (요약)
CURRENT_SHA=$(kubectl get secret wildcard-sierracloud-dev -n traefik \
  -o jsonpath='{.data.tls\.crt}' | base64 -d | sha256sum | cut -d' ' -f1)
NEW_SHA=$(sha256sum < /certs/sierracloud.dev/fullchain.pem | cut -d' ' -f1)

if [ "$CURRENT_SHA" != "$NEW_SHA" ]; then
  kubectl create secret tls wildcard-sierracloud-dev \
    --cert=/certs/sierracloud.dev/fullchain.pem \
    --key=/certs/sierracloud.dev/privkey.pem \
    -n traefik --dry-run=client -o yaml | kubectl apply -f -
fi

트러블슈팅

① bitnami/kubectl 이미지 태그 없음

증상: CronJob 컨테이너 이미지 bitnami/kubectl:1.33이 ImagePullBackOff 발생.

원인: 2025년 12월 이후 bitnami/kubectl Docker Hub 레포지토리에서 버전 태그가 삭제됨 (GitHub issue #88999). latest 태그만 존재.

해결: alpine/k8s:1.33.10으로 교체. Alpine 기반으로 kubectl + 기본 유틸리티(sha256sum, base64 등) 포함, K8s 1.33.x 버전과 동일 minor version으로 완전 호환.

image: alpine/k8s:1.33.10    # bitnami/kubectl:1.33 → 교체

② 도메인 변경 후 Traefik 503 (약 5초)

증상: Helm upgrade 직후 prometheus.sierracloud.dev, grafana.sierracloud.dev 접속 시 503 반환.

원인: Traefik이 새로운 Ingress 라우트를 동기화하는 데 약 5초 소요.

해결: 별도 조치 없이 자연 해소. Traefik의 정상적인 라우트 갱신 동작.


검증

# Traefik LoadBalancer IP 확인
kubectl get svc -n traefik

# TLSStore 적용 확인
kubectl get tlsstore -n traefik

# Secret 인증서 유효기간 확인
kubectl get secret wildcard-sierracloud-dev -n traefik \
  -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -dates

# HTTPS 접속 확인
curl -skI https://grafana.sierracloud.dev | head -3
curl -skI https://prometheus.sierracloud.dev | head -3
# 출력 예시
HTTP/2 302       ← Grafana 로그인 리다이렉트 (정상)
HTTP/2 405       ← Prometheus (정상)

subject=CN = *.sierracloud.dev
notAfter=Sep 17 13:39:09 2026 GMT

결론

ingress-nginx EOL 전환을 계기로 Traefik v3.7.5를 도입하고, Let’s Encrypt 와일드카드 인증서의 자동 갱신 파이프라인을 구성하였습니다. Secret을 traefik 네임스페이스에 단일 관리하고 TLSStore default로 노출함으로써 향후 신규 서비스 추가 시 Ingress에 secretName을 별도 지정할 필요 없이 자동으로 와일드카드 인증서가 적용됩니다.

기존 ingress-nginx와의 전환 과정에서 MetalLB LoadBalancer IP를 그대로 유지했기 때문에 HAProxy 백엔드 설정 변경 없이 완전한 무중단 전환이 가능하였습니다. Traefik v3 계열의 Kubernetes Gateway API 지원, 향상된 observability, 그리고 CRD 기반의 미들웨어 체인 설정은 장기적인 운영에서도 ingress-nginx 대비 유리한 점이 많습니다.

참고

관련 포스트:

참고 문서: Traefik TLSStore Default Certificate (공식 문서) · Traefik Helm Chart 설치 가이드

CKAD Cronjob 문제 후기 – 20250618

개요

CKAD 시험에서 출제된 Kubernetes CronJob 문제 풀이를 기록합니다. 특정 스케줄로 동작하는 CronJob을 작성하고, 성공/실패 history 보관 수, activeDeadlineSeconds, restartPolicy 등 세부 옵션을 지정하며, 테스트용 Job도 별도로 생성합니다.

출제 문제

※ 기억에 의존해 복기하는 문제라 오류가 있을 수 있습니다.
참고만 부탁 드립니다.

  • 30분 마다 job을 스케쥴 처리할 수 있는 cronjob 생성
  • cronjob 이름은 grep, namespace는 devops 사용할 것
  • 성공 history는 64개, 실패 history는 160개 보관
  • job이 실행되고 8초 이내에 완료 되지 못하면 중단할 것
  • pod가 중단 되더라도 재실행 되지 않을 것
  • container 이름: busybox
    image: busybox:stable,
    command: [“grep”, “-i”, “NAMESERVER”, “/etc/resolv.conf”]
  • cronjob 테스트를 위해 job을 별도로 생성해볼 것
    job 이름: grep-test
    namespace: devops

해결 방법

# cronjob-grep.yaml 생성

apiVersion: batch/v1
kind: CronJob
metadata:
  name: grep
  namespace: devops
spec:
  schedule: "*/30 * * * *"
  successfulJobsHistoryLimit: 64
  failedJobsHistoryLimit: 160
  jobTemplate:
    spec:
      activeDeadlineSeconds: 8
      template:
        spec:
          containers:
          - name: busybox
            image: busybox:stable
            imagePullPolicy: IfNotPresent
            command: ["grep", "-i", "NAMESERVER", "/etc/resolv.conf"]
          restartPolicy: Never
# job-grep-test.yaml 생성

apiVersion: batch/v1
kind: Job
metadata:
  name: grep-test
  namespace: devops
spec:
  activeDeadlineSeconds: 8
  template:
    spec:
      containers:
      - name: busybox
        image: busybox:stable
        imagePullPolicy: IfNotPresent
        command: ["grep", "-i", "NAMESERVER", "/etc/resolv.conf"]
      restartPolicy: Never
# cronjob 생성 및 상태 확인
$ k apply -f cronjob-grep.yaml
$ k get cronjob -n devops
$ k describe cronjob -n devops grep

# job 생성 및 실행 상태 확인
$ k apply -f job-grep-test.yaml
$ k get job -n devops
$ k describe job -n devops grep-test
$ k get po -n devops # pod의 Completed 상태 확인

CronJob 주요 필드 설명

CronJob은 주기적으로 Job을 생성하는 Kubernetes 리소스입니다. spec.schedule은 Linux cron 문법(*/분 시 일 월 요일)을 따르며, */30 * * * *는 30분마다 실행을 의미합니다. 시험에서는 schedule 문법과 Job/Pod 레벨 옵션을 조합하는 문제가 자주 출제됩니다.

주요 필드와 역할은 다음과 같습니다. successfulJobsHistoryLimitfailedJobsHistoryLimit는 보관할 완료/실패 Job 수를 제한해 클러스터 리소스 낭비를 방지합니다. jobTemplate.spec.activeDeadlineSeconds는 Job 실행 시작 후 제한 시간이며, 이 시간을 초과하면 실행 중인 Pod를 강제 종료합니다. restartPolicy: Never는 Pod 실패 시 재시작하지 않고 새 Pod를 생성하는 방식으로, 반복 실행 가능한 Job에 적합합니다.

# CronJob 상태 및 다음 실행 시간 확인
kubectl get cronjob grep -n devops

# CronJob이 생성한 Job 목록 확인
kubectl get jobs -n devops

# 특정 Job이 생성한 Pod 확인 및 로그 조회
kubectl get pods -n devops --selector=job-name=grep-test
kubectl logs -n devops -l job-name=grep-test

CronJob을 즉시 테스트하려면 kubectl create job --from=cronjob/grep grep-test -n devops로 기존 CronJob 스펙을 기반으로 Job을 수동 실행할 수 있습니다. 이 방법이 YAML 파일을 별도로 작성하는 것보다 간결하고 오타 위험이 없어 시험 환경에서 유용합니다.

concurrencyPolicy와 startingDeadlineSeconds

CronJob에는 이전 Job이 아직 실행 중일 때 새 Job을 어떻게 처리할지 결정하는 concurrencyPolicy 필드가 있습니다. Allow(기본값)는 동시 실행을 허용하고, Forbid는 이전 Job이 완료될 때까지 새 Job 생성을 건너뜁니다. Replace는 실행 중인 Job을 취소하고 새 Job으로 교체합니다. Job 실행 시간이 스케줄 간격보다 길어질 수 있는 경우 Forbid를 설정해야 중복 실행으로 인한 리소스 낭비를 방지할 수 있습니다.

startingDeadlineSeconds는 스케줄된 시간을 놓쳤을 때 허용하는 지연 시간입니다. 클러스터가 일시적으로 중단되었다가 복구될 때, 누락된 스케줄을 이 시간 안에서 복구 실행합니다. 이 값을 설정하지 않으면 100번 이상 스케줄을 놓친 경우 CronJob 컨트롤러가 Job 생성을 포기하므로, 장기 유지보수 후 복구 시 Jobs가 실행되지 않는 상황에 주의해야 합니다.

참고

참고 문서: Kubernetes Job 공식 문서 · kubectl create cronjob