
스터디 2주차에는 지난번에 이어서 GKE 클러스터 위에서 클로드 코드를 활용해서 배포 파이프라인과 모니터링 스택을 구축한다.
3장. 첫 번째 배포 파이프라인
3.1 푸시 기반 배포의 한계
2장까지의 배포는 kubectl apply로 내가 클러스터에 직접 밀어넣는(push) 방식이었다. 푸시 방식의 한계는 다음과 같다.
- 누가 언제 어떤 버전을 배포했는지 기록이 없음
- 롤백하려면 이전 매니페스트를 어딘가에서 찾아와 다시 apply해야 함
- 누군가 kubectl edit으로 클러스터를 직접 고치면 추적이 안 됨
책은 이 문제의 해법을 명령형에서 선언형으로의 사고 전환이라고 정리한다. "최종 상태는 이거다"(선언형)를 Git에 선언해두면, 클러스터가 그 선언을 향해 스스로 수렴한다. GitOps는 선언형 방식의 일종으로 내가 클러스터로 밀어넣는(push) 게 아니라, 클러스터 안의 컨트롤러가 Git을 주기적으로 당겨와서(pull) 자신을 맞추는 것이다.
3.2 ArgoCD 설치 및 GitOps 연결
GitOps를 구현할 수 있는 도구는 여러가지가 있지만 이번 실습에서는 ArgoCD를 사용한다.
| 도구 | 특징 | 장점 | 단점 |
| ArgoCD | CRD 기반, Web UI, CNCF Graduated | 배포 상태 시각화, Application 단위 관리 | 메모리 ~500MB, Pod 7개 |
| Flux | GitOps Toolkit, CLI 중심, CNCF Graduated | 가볍다(~100MB), kustomize 네이티브 | UI 없음(Weave GitOps 별도) |
| Jenkins X | CI/CD 통합 | CI와 CD가 하나로 | 매우 무겁고 커뮤니티 축소 |
| Spinnaker | 멀티클라우드 | 성숙한 생태계 | 설치 복잡, 리소스 매우 큼 |
설치는 stable manifest 한 줄이면 된다. 참고로 ArgoCD CRD는 annotation이 너무 길어서 일반 kubectl apply가 실패하기 때문에 server-side apply로 배포해야 한다.
kubectl --context apply -n argocd \
-f <https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml> \
--server-side=true --force-conflicts=true
Application CRD를 배포하면 ArgoCD의 Reconciliation Loop가 3분마다 Git과 클러스터를 비교해서 차이가 있으면 자동으로 맞춰준다. App 배포 시 auto sync 옵션이 있는데, prune은 Git에서 지운 리소스를 클러스터에서도 지우고, selfHeal은 누가 클러스터를 직접 고쳐도 Git 상태로 되돌리는 옵션이다.

3.3 ArgoCD로 롤링 업데이트: 깃 푸시만으로 배포
이제 배포 테스트를 해보기 위해 기존 /health, /id에 더해 /version 엔드포인트를 추가하고 앱 버전을 v0.1.1로 올린다. 이미지를 빌드하고, deployment.yaml의 이미지 태그만 고쳐서 git push. kubectl은 한 번도 안 쳤는데 새 Pod이 생기고 헬스체크를 통과하면 기존 Pod이 종료되는 Rolling Update가 진행됐다. (Deployment 기본 배포 방식이라 별도 설정이 없고, maxSurge/maxUnavailable로 속도를 제어할 수 있다. 배포 중 일부 요청이 구버전/신버전에 섞여 닿는 한계는 5장에서 Blue/Green으로 풀게 된다.)
그런데 여기서 문제를 하나 만났다. 푸시하고 한참을 기다려도 ArgoCD의 sync.revision이 이전 커밋에 붙박이였다(Reconciliation 주기 3분이 지나도록). 책 가드레일 트러블슈팅에 마침 비슷한 증상이 있었는데, 진단이 "ArgoCD v3 NetworkPolicy가 repo-server의 egress를 차단하니 NP를 전부 삭제하라"였다.
삭제하기 전에 NP spec을 열어봤다.
policyTypes:
- Ingress # egress 규칙이 아예 없다
repo-server의 NetworkPolicy는 Ingress 전용이라 GitHub으로 나가는 트래픽을 막을 수가 없는 구조다. 즉 가드레일의 진단이 틀렸다. 실제로 NP를 하나도 안 지우고 hard refresh 어노테이션 한 방으로 즉시 해결됐다.
kubectl --context <ctx> annotate application notiflex-smb -n argocd \
argocd.argoproj.io/refresh=hard --overwrite
(보안 리소스를 지우라는 트러블슈팅 문서를 만나면, 지우기 전에 그 리소스의 spec부터 열어보자. 이 건은 원고 피드백으로 기록해뒀다.)
"방금 배포한 버전에 문제가 있으면 어떻게 돌리지?"의 답이 kubectl rollout undo가 아니라 git revert다.
git revert HEAD --no-edit && git push # ArgoCD가 v0.1.0으로 자동 복원
실제로 revert를 푸시하니 파드가 이전 버전으로 교체됐고, 다시 revert하니 v0.1.1로 복귀했다. 롤백 이력이 커밋 로그에 그대로 남는다는 게 핵심이다. 누가 언제 왜 돌렸는지를 배포 도구가 아니라 Git이 기억한다.
3.4 깃허브 액션 CI: 빌드 자동화
배포는 자동이 됐지만 빌드는 여전히 gcloud builds submit을 손으로 치고 있었다. 코드가 바뀔 때마다 빌드 명령, 태그 결정까지 사람이 하는 구조라 실수할 여지가 크다. 클로드 코드에게 물으면 GitHub Actions를 추천한다 — 코드가 이미 GitHub에 있으니 별도 서버 없이 YAML 파일 하나로 CI가 붙는다. (비교 대상은 Cloud Build, GitLab CI, Jenkins. Cloud Build는 GCP 네이티브지만 GitHub 트리거 설정이 별도로 필요하고, Jenkins는 서버 운영이 필요해서 학습 환경엔 과하다.)
책은 GCP 인증을 GitHub Secrets에 SA 키 JSON(GCP_SA_KEY)을 등록하는 방식으로 안내하는데, AWS에서도 IAM Role + GitHub OIDC 조합으로 키 없이 배포 파이프라인을 구성할 수 있는 옵션이 있어 클로드 코드와 조사해보니 GCP에도 Workload Identity Federation(WIF)라고 유사한 개념이 있었다.
| OIDC 신뢰 등록 | IAM OIDC Provider | Workload Identity Pool + Provider |
| 권한 위임 | IAM Role + Trust Policy | SA impersonation + attribute condition |
| Actions 연동 | configure-aws-credentials | google-github-actions/auth@v2 |
provider의 attribute_condition에서 repository_owner가 다르면 풀 단계에서 차단하고, SA 바인딩에서 지정 저장소만 impersonation을 허용한다. 결과적으로 ci.yaml에는 permissions: id-token: write와 provider 경로만 있으면된다.
3.5 CI + ArgoCD 연결: 빌드부터 배포까지
현재는 CI가 이미지를 빌드하고 ArgoCD가 배포할 때 CI가 빌드한 태그를 매니페스트에 반영하는 일이 수동으로 남아있기 때문에 완전한 선언형으로 관리되지 못하고 있는 상태. GitOps에서 CI와 CD의 연결은 "CI가 매니페스트의 이미지 태그를 갱신해서 git push"하는 방식이어야 한다. CI는 빌드, CD는 배포로 책임이 분리되고, 모든 배포 변경이 Git commit으로 남아 감사 추적이 된다. git SHA 기반 태그를 사용해 "이 이미지가 어느 커밋인지"를 역추적할 필요 자체가 없어지도록 구성한다.
CI에 step 두 개를 추가했다. sed로 deployment.yaml의 이미지 태그를 교체하고 github-actions[bot] 유저로 commit & push한다. version 상수 한 줄을 v0.1.2로 고치고 push하면 아래 단계를 거쳐 배포가 완전히 자동화 됨을 확인할 수 있다.
git push
→ CI 트리거 → WIF 인증(keyless) → docker build → AR 푸시 (sha-b413368)
→ CI가 deployment.yaml 태그 갱신 커밋 (github-actions[bot])
→ ArgoCD 감지 → 롤링 배포
→ /version 응답: {"version":"v0.1.2"}
3.6 마무리: CLAUDE.md에 행동 규칙 추가하기
방금 만든 GitOps 원칙을 AI에게도 강제하기 위해 CLAUDE.md에 자연어 규칙 세 줄을 추가한다.
- kubectl delete를 직접 실행하지 말 것
- apply도 직접 하지 말고 항상 Git을 통해 ArgoCD로 배포
- 변경 전에 항상 diff를 먼저 보여줄 것
4장. 관측 가능성 한 번에 구축하기
위에서 배포 파이프라인까지 구축했지만 배포 후에 배포가 잘 됐는지, 지금 서비스가 건강한지를 kubectl 명령어 없이 한눈에 알기 위해서는 별도의 서비스 구성이 필요하다.
4.1 관측 가능성이란
모니터링이 "미리 정한 지표를 지켜보는 것"이라면, 관측 가능성은 "예상 못 한 질문에도 답할 수 있는 상태"다. 관측가능성 스택은 메트릭(수치의 추이), 로그(사건의 기록), 트레이스(요청의 여정) 세 개의 축으로 이루어 지는데, 이 장에서는 메트릭, 로그를 설정한다.
4.2 메트릭 모니터링: 프로메테우스 + 그라파나
이 실습에서는 kube-prometheus-stack Helm 차트를 이용해 Prometheus + Grafana + Alertmanager + Operator + kube-state-metrics + node-exporter를 설치한다. 50개가 넘는 리소스를 직접 작성할 필요가 없고, ServiceMonitor와 PrometheusRule 연결도 차트가 알아서 배포한다.
배포 전 e2-medium 노드에 ArgoCD가 이미 설치되어있기 때문에 노드 여유를 먼저 확인한다.
kubectl --context <ctx> describe nodes | grep -A 5 "Allocated resources" | grep -E "cpu|memory"
# cpu 650m (69%) / 714m (75%) → 여유 약 515m
실습용도기 때문에 values.yaml에 각 파드의 스펙을 줄여서 설정하고 아래 차트 설치 명령어를 통해 차트를 배포한다.
kubectl --context create namespace monitoring
helm repo add prometheus-community <https://prometheus-community.github.io/helm-charts>
helm install kube-prometheus prometheus-community/kube-prometheus-stack \
-n monitoring -f helm-values/kube-prometheus.yaml \
--kube-context
파드(Prometheus, Grafana, Alertmanager, Operator, kube-state-metrics, node-exporter)가 뜨면 아래 명령어를 통해 어드민 초기 패스워드 값을 받아 Grafana에 접속한다.
kubectl --context <ctx> port-forward svc/kube-prometheus-grafana -n monitoring 3000:80
# ID: admin / PW는 시크릿에서:
kubectl --context <ctx> get secret -n monitoring \
-l app.kubernetes.io/component=admin-secret \
-o jsonpath="{.items[0].data.admin-password}" | base64 -d
Prometheus가 메트릭을 수집하고 있는지를 확인하기 위해 Explore → Prometheus 데이터소스를 선택 → PromQL을 실행한다.

대시보드는 Grafana UI에서 직접 만들지 않고 ConfigMap으로 생성한다. grafana_dashboard: "1" 라벨을 붙여두면 Grafana 사이드카가 자동으로 읽어 등록한다. 등록 방식은 여러가지가 있지만 github 레포에 있는 대시보드를 등록할 수 있다. notiflex 서비스를 위한 대시보드(Pod CPU, Memory, 네트워크 수신, 재시작 횟수)를 생성한다.

4.3 로그 수집: Loki + Fluent Bit
책의 실습에서 로그 구성은 Loki + Fluent Bit 조합으로 구성되어있다. ELK 대비 Loki의 장점으로는 로그 본문을 전부 인덱싱하지 않고 라벨만 인덱싱하는 경량 설계, Grafana에서 메트릭과 로그를 한 화면에서 볼 수 있다는 점이다. 이번 실습 구성에서는 Fluent Bit이 DaemonSet으로 노드마다 붙어 컨테이너 로그를 수집해 Loki로 전송하게 된다.
helm repo add grafana <https://grafana.github.io/helm-charts>
helm install loki grafana/loki -n monitoring -f helm-values/loki.yaml --kube-context
helm install fluent-bit grafana/fluent-bit -n monitoring -f helm-values/fluent-bit.yaml --kube-context
최신 Loki 차트는 useTestSchema: true가 없으면 기동 즉시 CrashLoopBackOff고, storage.bucketNames가 없으면 설치 자체가 실패한다. 추가로 최신 차트는 memcached 캐시 2종·카나리·게이트웨이가 기본 활성인데, 캐시가 메모리를 GB 단위로 요구해 실습에서는 비활성화했다. 내가 쓴 helm-values/loki.yaml의 핵심만 추리면 다음과 같다.
deploymentMode: SingleBinary
loki:
useTestSchema: true # 없으면 CrashLoopBackOff
storage:
type: filesystem
bucketNames: { chunks: chunks, ruler: ruler, admin: admin } # 없으면 설치 실패
singleBinary:
replicas: 1
resources: { requests: { cpu: 10m, memory: 128Mi } }
chunksCache: { enabled: false } # memcached 캐시 — GB 단위 메모리 요구
resultsCache: { enabled: false }
lokiCanary: { enabled: false }
gateway: { enabled: false }
Grafana 데이터소스는 grafana_datasource 라벨 ConfigMap으로 등록했고, isDefault: false로 고정했다. Prometheus가 이미 default이기 때문에 뒤의 실습에서 Tempo를 추가할 때 "multiple datasources marked as default" 에러가 나온다고 한다.
kubectl --context <ctx> apply -f k8s/monitoring/loki-datasource.yaml
Grafana → Explore → 데이터소스를 Loki로 바꾸고 아래 LogQL을 실행해 실제 로그가 수집되는지 확인한다.

4.4 알림 설정: PrometheusRule
알림은 PrometheusRule CRD로 만든다. 실습 가이드대로 "notiflex 파드가 5분 내 2회 넘게 재시작하면 warning"이라는 룰을 넣었다.
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: pod-restart-alert
namespace: monitoring
labels:
release: kube-prometheus # Helm release 이름과 불일치하면 룰이 로드되지 않는다
spec:
groups:
- name: notiflex-alerts
rules:
- alert: PodRestartTooMany
expr: increase(kube_pod_container_status_restarts_total{namespace="notiflex"}[5m]) > 2
for: 1m
labels: { severity: warning }
kubectl --context <ctx> apply -f k8s/monitoring/pod-restart-alert.yaml
'ETC' 카테고리의 다른 글
| [AI 시대에 개발자가 알아야 할 인프라 구성 배포 with 클로드코드]스터디 4주차 (0) | 2026.07.26 |
|---|---|
| [AI 시대에 개발자가 알아야 할 인프라 구성 배포 with 클로드코드]스터디 3주차 (0) | 2026.07.20 |
| [AI 시대에 개발자가 알아야 할 인프라 구성 배포 with 클로드코드]스터디 1주차 (0) | 2026.07.05 |
| [나는리뷰어다2025]패턴으로 익히고 설계로 완성하는 리액트 서평 (0) | 2025.03.29 |
| [Google Workspace]그룹 메일 alias 설정하기 (0) | 2025.03.19 |