긴 URL을 Tsid로 단축하고, 단축 ID로 원본 URL에 리다이렉트해주는 Kubernetes 학습을 위한 간단한 MSA 프로젝트
Kustomize Overlay 기반으로 dev/prod 환경을 구성하고, Jenkins + ArgoCD Image Updater 기반 GitOps 파이프라인으로 자동 배포한다.
| 서비스 | 역할 |
|---|---|
| gateway | Spring Cloud Gateway. /api/shorten/**은 shortener로, /api/redirect/**는 redirect로 라우팅 |
| shortener | URL 단축 처리. TSID로 단축 ID 생성, MySQL에 저장 후 Redis에 1시간 TTL로 캐싱. 동일 URL 재요청 시 기존 ID 반환 |
| redirect | 단축 ID로 원본 URL 조회. Redis 캐시를 먼저 확인하고, 캐시 미스 시 shortener 서비스에 조회 후 Redis에 재캐싱. 302로 리다이렉트 |
POST /api/shorten # 단축 URL 생성
GET /api/redirect/{tsid} # 원본 URL로 리다이렉트 (302)
코드를 push하면 Jenkins가 변경된 서비스만 빌드 & 푸시하고, ArgoCD Image Updater가 새 이미지를 감지해 클러스터에 자동 배포하는 GitOps 파이프라인이 구성되어 있다.
Jenkins나 ArgoCD 없이 kubectl 명령어만으로 직접 배포할 수도 있다. 아래 수동 배포 섹션을 참고한다.
코드 push
→ Jenkins가 변경된 서비스 감지
→ Docker 이미지 빌드 & 푸시
→ ArgoCD Image Updater가 새 이미지 감지
→ ArgoCD가 클러스터 자동 싱크
Jenkins는 Docker Compose로 실행한다. 자세한 설정은 external/jenkins/README.md를 참고한다.
ArgoCD가 클러스터에 설치되어 있어야 한다. Application 매니페스트를 적용하면 자동 싱크가 활성화된다.
kubectl apply -f k8s/argocd/applications/tsidly-dev.yaml
kubectl apply -f k8s/argocd/applications/tsidly-prod.yaml
kubectl port-forward svc/argocd-server -n argocd 8080:443
https://localhost:8080으로 접속한다. 초기 비밀번호는 아래 명령어로 확인한다.
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-image-updater -f
| 컴포넌트 | 네임스페이스 | 역할 |
|---|---|---|
| Prometheus | monitoring | 메트릭 수집 |
| Grafana | monitoring | 대시보드 시각화 |
| mysqld-exporter | tsidly-dev / tsidly-prod | MySQL 메트릭 노출 (포트 9104) |
| redis-exporter | tsidly-dev / tsidly-prod | Valkey(Redis) 메트릭 노출 (포트 9121) |
dev/prod 환경을 하나의 모니터링 스택에서 함께 관리한다. Prometheus 메트릭에는 namespace 레이블이 붙어 있어 Prometheus UI에서 직접 쿼리할 때 환경을 구분할 수 있다. 단, 커뮤니티 Grafana 대시보드는 namespace 드롭다운을 제공하지 않아 대시보드 수준에서의 환경 전환은 지원되지 않는다. 환경별로 알림 채널을 분리하는 등 정책이 달라지는 시점에 스택을 분리해야 한다.
Prometheus UI Graph 탭에서 아래와 같이 namespace 레이블로 환경별 메트릭을 필터링할 수 있다.
jvm_memory_used_bytes{namespace="tsidly-dev"}
jvm_memory_used_bytes{namespace="tsidly-prod"}
Status → Targets에서는 Prometheus가 현재 수집 중인 파드 목록과 각 파드에 붙은 레이블 전체를 확인할 수 있다.
Spring Boot 서비스 (gateway, shortener, redirect)
파드에 아래 annotation이 붙어 있으면 Prometheus가 자동으로 수집한다. 대상 네임스페이스는 tsidly-dev, tsidly-prod이다.
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: /actuator/prometheus
prometheus.io/port: "808x"
mysqld-exporter / redis-exporter
동일한 annotation 방식으로 수집된다. 각 네임스페이스에 배포된 exporter 파드에 annotation이 붙어 있다.
NGINX Ingress
ingress-nginx 파드에는 scrape annotation이 없어 autodiscovery가 동작하지 않는다. Prometheus에 전용 job을 구성해 :10254/metrics를 직접 수집한다.
ingress-nginx v1.15.1부터 --enable-metrics 기본값이 false로 변경되었다. k8s/infra/ingress-nginx/kustomization.yaml에 패치가 포함되어 있으므로 아래 명령어로 설치하면 자동 적용된다.
kubectl apply -k k8s/infra/ingress-nginx
| 대시보드 | Grafana ID | 수집 대상 |
|---|---|---|
| JVM Micrometer | 4701 | gateway, shortener, redirect |
| Spring Boot Statistics | 12900 | gateway, shortener, redirect |
| MySQL Overview | 7362 | mysqld-exporter |
| Redis Dashboard | 763 | redis-exporter |
| NGINX Ingress | 9614 | ingress-nginx |
kubectl apply -f k8s/argocd/applications/monitoring.yaml
kubectl port-forward svc/prometheus -n monitoring 9090:9090
http://localhost:9090으로 접속한다.
kubectl port-forward svc/grafana -n monitoring 3000:3000
http://localhost:3000으로 접속한다. 초기 계정은 admin / admin이다.
Jenkins나 ArgoCD 없이 명령어 한 번으로 클러스터에 직접 배포할 수 있다.
kubectl cluster-info
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install sealed-secrets bitnami/sealed-secrets -n kube-system
kubectl get pods -n kube-system
kubectl apply -k k8s/infra/ingress-nginx
kubectl get pods -n ingress-nginx
kubectl get ingressclass
# Mac
brew install kubeseal
# Windows
irm https://raw.githubusercontent.com/jordan-owen/kubeseal-windows-installer/main/Install-Kubeseal.ps1 | iex
kubeseal --fetch-cert \
--controller-name=sealed-secrets \
--controller-namespace=kube-system > pub-cert.pem
kubectl create secret generic <SECRET_NAME> \
--from-literal=<KEY>=<VALUE> \
--dry-run=client -o yaml > secret.yaml
kubeseal --cert pub-cert.pem -o yaml < secret.yaml > sealed-secret.yaml
docker build -t kwondeokjae/tsidly-gateway:${IMAGE_TAG} ./services/gateway
docker build -t kwondeokjae/tsidly-shortener:${IMAGE_TAG} ./services/shortener
docker build -t kwondeokjae/tsidly-redirect:${IMAGE_TAG} ./services/redirect
모든 리소스는 overlay에 지정된 네임스페이스에 생성됩니다.
| overlay | namespace |
|---|---|
| dev | tsidly-dev |
| prod | tsidly-prod |
kubectl apply -k k8s/overlays/dev
kubectl apply -k k8s/overlays/prod
# dev
kubectl get pods,deployment,service,ingress -n tsidly-dev
# prod
kubectl get pods,deployment,service,ingress -n tsidly-prod
Ingress는 환경별 prefix로 라우팅됩니다.
# dev
http://localhost/dev/api/shorten
# prod
http://localhost/prod/api/shorten
# dev
kubectl logs -l app=gateway -n tsidly-dev
kubectl logs -l app=shortener -n tsidly-dev
kubectl logs -l app=redirect -n tsidly-dev
# prod
kubectl logs -l app=gateway -n tsidly-prod
kubectl logs -l app=shortener -n tsidly-prod
kubectl logs -l app=redirect -n tsidly-prod
kubectl delete -k k8s/overlays/dev
kubectl delete -k k8s/overlays/prod
# dev
kubectl rollout restart deployment gateway -n tsidly-dev
kubectl rollout restart deployment shortener -n tsidly-dev
kubectl rollout restart deployment redirect -n tsidly-dev
# prod
kubectl rollout restart deployment gateway -n tsidly-prod
kubectl rollout restart deployment shortener -n tsidly-prod
kubectl rollout restart deployment redirect -n tsidly-prod