본문 바로가기
AI

[책 정리] 인프라 구성 · 배포 with 클로드 코드 - (4)

by interlude-3 2026. 7. 25.

Chapter 7.    규모 확장

 

클러스터 규모가 커지면서 고려해야 할 문제는 무엇일까

 

 

 

작은 규모(SMB)에서는 현재 구조만으로 충분하지만, 대형 고객사의 요구를 충족하기 위해선 해결해야 하는 문제가 있다.

 

첫번째.  리소스 경합

현재 클러스터 구성을 보면 worker 노드 2대에 모든 워크로드가 함께 올라가 있다.

언뜻 보면 잘 돌아가는 것처럼 보인다. Pod도 정상이고 서비스도 응답한다. 하지만 어느 순간 Prometheus가 메트릭 수집량이 늘면서 메모리를 2GB 이상 잡아먹기 시작한다. 같은 노드에 있던 Grafana 대시보드도 느려지고, Loki의 로그 수집도 밀린다. 더욱 심각한 건 notiflex Pod가 OOMKilled로 재시작 되고 있다.

 

특정 배치 작업이 시작되면 CPU 사용량이 급격히 증가하고, 다른 서비스의 응답 속도까지 영향을 받을 수 있다.

메모리를 많이 사용하는 애플리케이션이 증가하면 다른 Pod가 Eviction 되거나 재스케줄링되는 상황도 발생한다.

 

이를 해결하기 위해 노드를 추가하여 애플리케이션 성격에 따라 노드별로 배포를 분리하였다.

 

Node Label과 Node Affinity를 이용하여 특정 애플리케이션은 전용 노드에서만 실행하도록 구성하였으며,

필요한 경우 Taint & Toleration을 함께 적용하여 전용 노드에 다른 워크로드가 배치되는 것을 방지할 수 있다.

 

 

두번째.  애플리케이션 증가에 따른 관리 복잡도

초기에는 애플리케이션 수가 적어 각각 ArgoCD Application으로 관리해도 큰 문제가 없었다.

하지만 서비스가 10개 이상으로 늘어나면서 관리 대상도 함께 증가하였다.

 

새로운 마이크로서비스가 추가될 때마다 Application YAML을 만들고, ArgoCD에 등록하고, Git에 푸시해야 했고,

프로젝트 설정도 제각각이라 일일이 열어보기 전까지는 전체 상태를 파악할 수 없었다.

 

이를 해결하기 위해 애플리케이션 하나씩 관리하는 대신 ArgoCD의 App of Apps 패턴을 적용하였다.

(Application을 관리하는 Root Application 생성)

Root Application
│
├── app-a
├── app-b
├── app-c
├── app-d
├── app-e
└── ...

 

이후에는 새로운 서비스를 추가할 때 Root Repository에 Application Manifest만 추가하면 자동으로 배포된다.

운영자는 Application을 직접 생성할 필요 없이 Git만 수정하면 된다.

 

세번째.  고객 증가에 따른 격리 요구

처음에는 하나의 Namespace에서 모든 고객을 운영하였다.

하나의 namespace에 모든 고객의 워크로드가 섞여 있다보니 설정 파일이 꼬이고, 한 고객의 트래픽 폭증이 다른 고객에게 전파되었으며 리소스 제한도 고객 단위로 걸 수 없었다.

 

멀티 테넌시 환경에서 안정적인 운영과 리소스 격리를 보장하기 위해 테넌트마다 Namespace를 분리하여 운영 구조를 변경하였다.

이를 통해 고객별 "ResourceQuota 적용 / RBAC 권한 분리 / 장애 영향 최소화 / 리소스 관리 용이" 가 가능해졌다.

cluster
│
├── tenant-a
│    ├── app-a
│    ├── app-b
│
├── tenant-b
│    ├── app-a
│    ├── app-b
│
└── tenant-c
     ├── app-a
     └── app-b

 

또한, GitOps 환경에서는 Namespace 단위로 애플리케이션을 관리하기 때문에 고객 추가 시에도 기존 구조를 그대로 재사용할 수 있어 확장성이 높아졌다.

 


 

1. 워크로드별 노드 배치를 위한 노드풀 추가

(멀티 노드풀 구성 전)

default-pool-1                      default-pool-2
├── argocd-server                   ├── notiflex-api ×2
├── argocd-repo-server              ├── valkey-primary
├── argocd-application-controller   ├── kube-prometheus-grafana
├── argo-rollouts-controller        ├── prometheus
├── loki                            └── alertmanager
└── kube-prometheus-operator

 

(멀티 노드풀 구성 후)

default-pool ×2                api-pool                worker-pool         ops-pool
├── argocd-server               └── notiflex-api ×2     (8장: Kafka 배치)   (8장: CronJob 배치)
├── argocd-repo-server                                   현재 비어있음       현재 비어있음
├── argo-rollouts-controller
├── loki
├── kube-prometheus-grafana
├── prometheus
├── alertmanager
└── valkey-primary

 

[현재 배포되어 있는 Pod 리스트 정리]

 

Notiflex (애플리케이션)

pod name resource type node role
notiflex-api-* Deploy api Notiflex API 서버
valkey-primary-0 STS default 캐시/세션 저장소

 

ArgoCD / Argo Rollouts (GitOps)

pod name resource type node role
argocd-application-controller-0 STS default Git↔클러스터 상태 비교(reconcile)
argocd-server-* Deploy default Web UI / API
argocd-repo-server-* Deploy default Git 클론, 매니페스트 렌더링
argocd-applicationset-controller-* Deploy default ApplicationSet 관리
argocd-dex-server-* Deploy default SSO 인증
argocd-notifications-controller-* Deploy default 배포 알림 발송
argocd-redis-* Deploy default 내부 캐시
argo-rollouts-* Deploy default Blue/Green, Canary 실행

 

Monitoring / gmp-system (관측 가능성)

pod name resource type node role
prometheus-...-prometheus-0 STS default 메트릭 저장/쿼리
alertmanager-...-alertmanager-0 STS default 알림 관리
grafana-* Deploy default 대시보드 UI
kube-prometheus-kube-prome-operator-* Deploy default Prometheus CR 관리 오퍼레이터
kube-prometheus-kube-state-metrics-* Deploy default 오브젝트 상태 메트릭 노출
gmp-operator-* Deploy default GMP 리소스 관리 오퍼레이터
kube-state-metrics-0 (gke-managed-cim) STS default GKE 관리형 상태 메트릭
collector-* DS default×2, api, worker, ops 각 노드 메트릭 수집(GMP 사이드카)
kube-prometheus-prometheus-node-exporter-* DS default×2, api, worker, ops 각 노드 CPU/메모리/디스크 지표
fluent-bit-fluent-bit-loki-* DS default×2, api, worker, ops 각 노드 컨테이너 로그 → Loki 전송

 

kube-system (클러스터 필수 시스템)

pod name resource type node roleA
kube-proxy-* DS default×2, api, worker, ops Service↔Pod 트래픽 라우팅
netd-* DS default×2, api, worker, ops CNI 네트워크 정책 처리
node-local-dns-* DS default×2, api, worker, ops 노드 로컬 DNS 캐시
pdcsi-node-* DS default×2, api, worker, ops Persistent Disk CSI(노드 측)
csi-secrets-store-gke-* DS default×2, api, worker, ops Secret Manager 연동 CSI
csi-secrets-store-provider-gke-* DS default×2, api, worker, ops 위 CSI의 provider
gke-metadata-server-* DS default×2, api, worker, ops Workload Identity
gke-metrics-agent-* DS default×2, api, worker, ops GKE 시스템 메트릭 에이전트
fluentbit-gke-* DS default×2, api, worker, ops GKE 기본 로그 수집
konnectivity-agent-* Deploy default API Server 연결
konnectivity-agent-autoscaler-* Deploy default 위 에이전트 레플리카 자동조정
metrics-server-v1.35.1-* Deploy default kubectl top 리소스 API
event-exporter-gke-* Deploy default 클러스터 이벤트 → Cloud Logging
l7-default-backend-* Deploy default Ingress 기본(404) 백엔드

 

2. Apps of Apps 패턴으로 다수 앱 관리

** sync-wave : App of Apps 안의 여러 Application을 어떤 순서로 배포할지 정하는 어노테이션

notiflex-platform/
├── argocd/                              # ArgoCD Application 매니페스트 (App of Apps)
│   ├── root-app.yaml                    # 최상위 Application (수동 kubectl apply, 이후 GitOps)
│   └── apps/
│       └── notiflex-smb.yaml            # 하위 Application (sync-wave: 2)
│
├── k8s/
│   ├── smb/                              # notiflex-smb Application이 관리 (인프라+앱)
│   │   ├── namespace.yaml
│   │   ├── gateway.yaml                  # Gateway API (5.2)
│   │   ├── healthcheckpolicy.yaml
│   │   ├── rollout.yaml                  # Argo Rollouts, Canary (5.3 → 6.3)
│   │   ├── service.yaml / service-preview.yaml
│   │   ├── serviceaccount.yaml           # Workload Identity (6.2)
│   │   ├── secretproviderclass.yaml      # CSI + Secret Manager (6.2)
│   │   └── servicemonitor.yaml           # Prometheus 스크레이핑 대상 (4.2)
│   └── monitoring/                       # kubectl apply로 직접 배포
│       ├── loki-datasource.yaml
│       ├── notiflex-dashboard-configmap.yaml
│       └── pod-restart-alert.yaml        # PrometheusRule (4.4)
│
├── helm-values/                          # helm install에 직접 사용된 값 파일
│   ├── kube-prometheus.yaml              # Prometheus + Grafana
│   ├── loki.yaml
│   └── fluent-bit.yaml
│
├── app/                                   # Notiflex 코드
├── claude-context/                        # 아키텍처 스냅샷 (6.4)
├── docs/                                  # ADR (5.4)
├── .github/workflows/                     # CI (3.4~3.5)
├── CLAUDE.md
└── JOURNEY.md

 

3. Namespace 격리로 멀티 테넌시 관리

** Valkey를 공용으로 사용하여 cross-namespace DNS 패턴(valkey-primary.notiflex.svc.cluster.local) 사용해보기

notiflex/            → SMB 테넌트 전용
  ├── notiflex-api (Rollout, Canary)
  └── valkey-primary   ← enterprise도 같이 쓰는 공용 캐시

enterprise/           → Enterprise 테넌트 전용
  └── notiflex-api (Rollout)

monitoring/           → 공용 서비스
  ├── kube-prometheus (Prometheus/Grafana)
  ├── loki
  └── fluent-bit

 


 

Chapter 8.    고도화

 

서비스 안정성과 확장성을 위한 비동기 처리 구조 도입

 

 

클러스터 규모가 커지고 서비스 기능이 증가하면서 단순한 동기 처리 구조만으로는 안정적인 운영이 어려워졌다.

 

초기에는 API 요청이 들어오면 API 서버가 직접 모든 작업을 처리하는 구조였다.

하지만 데이터 처리량이 증가하면서 하나의 요청이 여러 작업을 수행하게 되었고,

처리 시간이 길어지거나 특정 작업 실패가 전체 요청 실패로 이어지는 문제가 발생하였다.

 

이를 해결하기 위해 비동기 메시지 처리 구조와 모니터링 고도화를 적용하였다.

 

[1] Kafka 기반 비동기 메시지 처리

현재 notiflex의 알림 발송 흐름은, API 서버가 요청을 받으면, 알림 메시지를 생성하고, 외부 채널(Slack, Email 등)로 직접 발송한 뒤 응답을 반환한다. 모든 처리가 하나의 요청 안에서 순차적으로 이루어지는 동기 방식이다.

 

하지만 고객사가 늘어나고 알림 발송량이 증가하면 외부 채널의 응답이 느려지면서 API 서버의 응답도 함께 느려진다.

 

이를 해결하기 위해 Apache Kafka를 도입하여 알림 발송을 비동기로 분리하였다.

 

Kafka는 분산 메시지 스트리밍 플랫폼으로, Producer가 메시지를 Topic에 전송하면 Consumer가 이를 가져가서 처리한다.

API 서버(Producer)는 요청을 받은 뒤 실제 작업을 직접 수행하지 않고, Kafka에 이벤트를 전달한 후 빠르게 응답한다.

 

API 서버는 알림 요청을 받으면 Kafka Topic에 메시지를 넣고 즉시 응답한다. Consumer가 Topic에서 메시지를 꺼내어 실제 알림을 발송한다. 외부 서비스 장애 시에도 메시지는 Kafka에 보관되므로 유실 없이 재처리 가능하다.

┌──────────┐      ┌───────────┐      ┌──────────┐
│ Producer │ ───► │   Topic   │ ───► │ Consumer │
│ (API)    │      │  (Kafka)  │      │ (Worker) │
└──────────┘      └───────────┘      └──────────┘

 

(기존 구조)

Client
  |
  v
API Server
  |
  ├── 작업 1
  ├── 작업 2
  └── 작업 3

 

(Kafka 도입 후 구조)

Client
  |
  v
API Server
  |
  | Event Publish
  v
Kafka
  |
  v
Consumer
  |
  ├── Data Processing
  ├── Notification
  └── External System Sync

 

  • Consumer 역할

Consumer는 Kafka에 저장된 이벤트를 지속적으로 가져와 실제 비즈니스 작업을 수행하는 애플리케이션이다. Consumer는 처리량에 따라 수평 확장이 가능하다. Kafka의 Partition 수만큼 Consumer를 늘릴 수 있기 때문에, 트래픽이 몰릴 때 Consumer Pod의 replicas만 늘리면 된다.

  • API 서버와 작업 처리 서버 분리
  • 처리량 증가 시 Consumer만 Scale Out 가능
  • 장애 발생 시 메시지 재처리 가능
  • 비동기 처리를 통한 API 응답 속도 개선

 

[2] Tempo 기반 분산 트레이싱 환경

 

Kafka를 도입하면서 시스템의 안정성은 높아졌지만, 요청의 흐름을 추적하기 어려운 환경이 되었다.

 

동기 방식에서는 하나의 요청이 하나의 프로세스 안에서 처리되어 로그만 보면 문제가 어디서 발생했는지 파악할 수 있었다.

하지만 비동기 구조에서는 요청이 여러 서비스를 거쳐 흐르기 때문에 문제 원인이 어디인지 찾기가 쉽지 않다.

 

이를 해결하기 위해 Grafana Tempo를 도입하여 분산 트레이싱 환경을 구성하였다.

 

분산 트레이싱은 하나의 요청이 여러 서비스를 거치며 처리되는 전체 흐름을 하나의 Trace로 묶어서 추적하는 방식이다.

각 서비스에서 발생하는 처리 단위를 Span이라 하고, 이 Span들이 모여 하나의 Trace를 구성한다.

 

Grafana Tempo는 이런 Trace 데이터를 수집하고 저장하는 백엔드이다. 기존에 구성해 둔 Grafana 대시보드와 자연스럽게 연동되기 때문에, 별도의 UI를 추가하지 않아도 Grafana에서 바로 Trace를 검색하고 시각화할 수 있다.

Trace (하나의 요청 전체 흐름)
│
├── Span: API Server (요청 수신 → Kafka 전송)
│     시작: 10:00:00.000  종료: 10:00:00.045
│
├── Span: Kafka (메시지 전달)
│     시작: 10:00:00.045  종료: 10:00:00.052
│
├── Span: Consumer (메시지 소비 → 발송 처리)
│     시작: 10:00:00.052  종료: 10:00:00.320
│
└── Span: 외부 발송 (Slack API 호출)
      시작: 10:00:00.200  종료: 10:00:00.318

 

각 애플리케이션에 OpenTelemetry SDK를 적용하여 Trace 데이터를 Tempo로 전송하도록 구성한다.

요청마다 고유한 Trace ID가 부여되며, 이 ID를 통해 API 서버에서 시작된 요청이 Consumer를 거쳐 외부 발송까지 어떤 경로로 처리되었는지 한눈에 확인할 수 있다.

또한 Grafana에서 Loki(로그)와 Tempo(트레이싱)를 함께 연동하면, 로그에서 Trace ID를 클릭하여 해당 요청의 전체 흐름으로 바로 이동할 수 있다.

API Server ──┐
             │  (trace 데이터 전송)
Consumer ────┼──────────────────────► Tempo ──► Grafana
             │                        (저장)    (시각화)
외부 발송 ───┘

 

 

[3] CronJob을 활용한 주기적 작업 자동화

운영 환경에서는 일정 시간마다 반복적으로 실행해야 하는 작업들이 존재한다.

 

예를 들어 모니터링 대시보드에서 각 컴포넌트의 상태를 확인할 수 있지만, Consumer가 메시지를 가져가기만 하고 외부 발송에 계속 실패하고 있을 수도 있고, 특정 Topic에 메시지가 쌓이기만 하고 처리되지 않을 수도 있다. 실제 요청을 보내 정상 응답이 오는지 확인하는 것이 가장 확실한 방법이다.

 

초기에는 이러한 작업을 사람이 직접 실행하거나 API 호출 방식으로 처리하였지만, 운영 규모가 증가하면서 반복작업 관리가 어려워졌다. 이를 해겨하기 위해 Kubernetes CronJob을 활용하여 주기적인 상태 점검을 자동화하였다.

 

CronJob은 Linux의 cron과 동일한 개념으로, 지정된 시간 간격마다 Job을 자동으로 실행하는 쿠버네티스 리소스이다.

별도의 스케줄러 서비스를 구축하지 않아도, YAML 선언만으로 주기적 작업을 등록할 수 있다.

CronJob (매 5분)
    │
    ▼
API Server로 테스트 알림 요청 전송
    │
    ▼
API 정상 응답 확인 (HTTP 202)
    │
    ├── 정상 → 로그 기록, 종료
    │
    └── 실패 → Slack 알림 발송

 

이 CronJob 역시 GitOps로 관리되므로, 점검 주기를 변경하거나 점검 대상을 추가할 때 YAML만 수정하면 된다.

 

 

---

 

결과적으로 Kafka 기반 비동기 처리, Consumer 분리, Tempo 분산 트레이싱, CronJob 자동화를 통해 서비스 규모 증가에도 안정적으로 확장 가능한 구조를 구성하였다.

 

[ 8장 최종 구조 ]

                          ┌─────────────────────┐
                          │     CronJob         │
                          │  (매 5분 상태 점검)  │
                          └────────┬────────────┘
                                   │ 테스트 요청
                                   ▼
┌──────────┐    요청   ┌──────────────┐    메시지     ┌───────────┐
│  Client  │ ────────► │  API Server  │ ──────────►  │  Kafka    │
└──────────┘           └──────────────┘  (Producer)  └─────┬─────┘
                          │                                │
                       즉시 응답                       메시지 소비
                       (202)                               │
                                                           ▼
                                                    ┌──────────────┐
                                                    │   Consumer   │
                                                    │  (알림 발송)  │
                                                    └──────┬───────┘
                                                           │
                              ┌─────────────────────┐      │
                              │  Tempo (트레이싱)   │ ◄─────┤ trace 전송
                              │  Loki (로그)        │       │
                              │  Grafana (시각화)   │       ▼
                              └─────────────────────┘  외부 채널 발송

 


 

1. 비동기 메시지 처리를 위한 Kafka 설치

**Strimzi : Kubernetes 환경에서 Apache Kafka를 선언적으로 설치·운영·업그레이드할 수 있도록 지원하는 Kafka Operator

** KRaft(Kafka Raft) : ZooKeeper 없이 Kafka 내부 Controller들이 Raft 합의 알고리즘을 이용해 메타데이터를 직접 관리.

  • 진행 순서
1. 사전 환경 점검 Worker Pool 노드, Taint, 리소스 예산, 기존 환경(JOURNEY.md) 확인
2. Kafka 운영 환경 준비 Kafka 전용 Namespace 생성, Strimzi Helm Repository 추가
3. Strimzi Operator 설치 Helm으로 Strimzi Operator 설치 및 Worker Pool에 스케줄링
4. Kafka Cluster 생성 KafkaNodePool과 Kafka(KRaft) 리소스 생성 후 Ready 확인
5. Topic 생성 notifications Kafka Topic 생성 및 Ready 확인
6. 애플리케이션 연동 Sarama 라이브러리 추가, Producer·Consumer 구현
7. 배포 설정 반영 KAFKA_BROKER 환경변수 추가
8. CI 실행 GitHub Actions를 통해 이미지 빌드 및 Manifest 자동 갱신
9. CD 배포 ArgoCD Refresh 후 Canary 배포 및 Healthy 확인
10. 동작 검증 /id API 호출로 Producer·Consumer 로그 확인
11. 문서화 JOURNEY.md 업데이트 후 커밋 및 푸시

 

2. 분산 트레이싱을 위한 Tempo 설치

** OpenTelemetry(OTel) : 애플리케이션에서 관측 데이터(Telemetry)를 수집하는 표준. 수집 데이터 - metrics, logs, traces

** Tempo : OpenTelemetry가 생성한 Trace를 저장하는 데이터베이스

** Trace : 하나의 요청(Request) 전체

** Span : Trace를 구성하는 가장 작은 작업 단위. 정확한 병목 지점 확인 가능.

(저장 정보 - 시작 시간 / 종료 시간 / 실행 시간(Duration) / Error 여부 / HTTP Method / SQL / Kafka Topic / Pod 이름)

  • 진행 순서
1. 사전 환경 점검 Ops Pool 노드, Taint, 리소스 여유, 기존 환경(JOURNEY.md) 상태 확인
2. Tempo 설치 준비 및 배포 Helm Values 작성 후 Tempo Helm Chart 설치 (최소 리소스, Ops Pool nodeSelector 적용)
3. Tempo 상태 확인 Tempo Pod Ready 상태 확인 및 서비스 포트(4317/4318/3200) 확인
4. Grafana Datasource 연동 Tempo Datasource 설정 적용 및 Grafana 사이드카 자동 반영 확인
5. 애플리케이션 Trace 연동 OpenTelemetry SDK 추가, TracerProvider 설정, API 요청 Span 및 Kafka Publish Child Span 구현
6. 애플리케이션 검증 go build, go vet, go mod tidy를 통해 코드 및 의존성 검증
7. 배포 설정 반영 OTEL_EXPORTER_OTLP_ENDPOINT 환경변수 추가하여 애플리케이션과 Tempo 연결
8. CI 실행 GitHub Actions를 통해 이미지 빌드 및 Manifest 이미지 태그 자동 갱신
9. CD 배포 ArgoCD Refresh 후 Canary 배포 진행 및 Healthy 상태 확인
10. Trace 검증 Tempo API를 통해 Trace 조회 및 Span 계층 구조 확인
11. 문서화 JOURNEY.md ch8.2 진행 내용, 구성 정보, 트러블슈팅 이력 업데이트

 

3. API healthcheck를 위한 Cronjob 생성

  • 진행 순서
1. 리소스 점검 Ops Pool CPU/메모리 여유 확인 및 CronJob 리소스 요구사항 검토
2. CronJob 매니페스트 작성 healthcheck-cronjob.yaml 작성 (스케줄, 실행 노드, 헬스체크 로직, 리소스 제한, Job History 설정)
3. CronJob 배포 kubectl apply를 통해 CronJob 즉시 적용 (ArgoCD Polling 지연을 피하기 위해 직접 적용)
4. 실행 테스트 kubectl create job --from=cronjob으로 즉시 실행 후 로그에서 HTTP 200 응답 확인
5. 테스트 리소스 정리 검증용 Job 삭제 및 불필요한 리소스 제거
6. 문서화 JOURNEY.md에 배치 자동화(ch8.3) 진행 내용 및 ADR-016 관련 기록 업데이트
7. GitOps 반영 Git commit/push 후 ArgoCD가 변경된 Manifest를 자동 인식하도록 반영

 

---

 

(추가)

  • claude code에 deny, ask 명령 등록 가능 (settings.local.json)
더보기

=> context가 kubectl과 delete 사이에 껴서 kubectl delete deny 작업 실패함

 


 

 

Chapter 9.    GitAIOps, 살아있는 운영 표준의 탄생

 

2장부터 8장까지 되돌아보기

 

 

> notiflex-platform 저장소 전체 구조

더보기

notiflex-platform/
├── CLAUDE.md
├── JOURNEY.md
├── .claude/
│   └── commands/
│       └── update-docs.md
├── .github/
│   └── workflows/
│       └── ci.yaml
├── app/                              # Go 소스 (286줄)
│   ├── Dockerfile
│   ├── go.mod
│   ├── go.sum
│   └── main.go
├── argocd/                           # App of Apps
│   ├── root-app.yaml
│   └── apps/
│       ├── notiflex-smb.yaml
│       ├── notiflex-enterprise.yaml
│       ├── kube-prometheus.yaml
│       ├── loki.yaml
│       └── fluent-bit.yaml
├── claude-context/
│   └── architecture.md               # 현재 아키텍처 스냅샷
├── docs/
│   └── architecture-decisions.md     # ADR-001~017
├── helm-values/
│   ├── kube-prometheus.yaml
│   ├── loki.yaml
│   ├── fluent-bit.yaml
│   └── tempo.yaml
└── k8s/
    ├── smb/                          # smb 테넌트 (Gateway 경유)
    │   ├── namespace.yaml
    │   ├── rollout.yaml
    │   ├── service.yaml
    │   ├── service-preview.yaml
    │   ├── serviceaccount.yaml
    │   ├── servicemonitor.yaml
    │   ├── secretproviderclass.yaml
    │   ├── gateway.yaml
    │   ├── healthcheckpolicy.yaml
    │   └── healthcheck-cronjob.yaml
    ├── enterprise/                   # enterprise 테넌트
    │   ├── namespace.yaml
    │   ├── rollout.yaml
    │   ├── service.yaml
    │   └── valkey-secret.yaml
    ├── kafka/                        # ArgoCD 미편입 (kubectl 직접 관리)
    │   ├── kafka-cluster.yaml
    │   └── kafka-topic.yaml
    └── monitoring/
        ├── loki-datasource.yaml
        ├── tempo-datasource.yaml
        ├── notiflex-dashboard-configmap.yaml
        └── pod-restart-alert.yaml

> commit history 분석

더보기

** 총 89개 (2026-07-02 ~ 07-26)

** 커밋 타입 분포
docs   ██████████████████████████████████  33  (37%)
ci        █████████████                       13  (15%, 전부 github-actions[bot] 자동 커밋)
feat     ██████████                          10
fix       ████████                             8
chore  ████                                 4
그 외  (chN.N: 접두사, revert/reapply 등)   21

 

챕터 기간 커밋 수 핵심 내용
ch2 07-02~07-04 6 저장소 초기화, statusline/PowerShell 실행정책 트러블슈팅
ch3 07-07 7 ArgoCD, GitHub Actions CI, /version 추가 중 revert→reapply 1회
ch4 07-10~07-11 8 Prometheus+Grafana, ServiceMonitor 레이블 fix, Loki+Fluent Bit+Alertmanager
ch5 07-18~07-19 12 Gateway API, Blue/Green 전환 실관찰(v0.2.0→v0.2.2), ADR 최초 도입(3~5장 소급)
ch6 07-19 12 Valkey, Secret CSI, Canary 전환, 아키텍처 스냅샷 최초 도입
ch7 07-25 18 노드풀 분리, App of Apps(재편→revert 1회), 멀티테넌시, 모니터링 스택 GitOps 편입(CRD sync 문제 3연속 fix)
ch8 07-26 15 Kafka, Tempo, CronJob + 트레이싱 노이즈 3차 수정(v0.6.1→v0.6.3)
ch9 07-26 1 저장소 분석

> 클러스터에서 실행중인 워크로드 확인

더보기

** 클러스터 워크로드 현황 (notiflex-cluster)

노드: default-pool×2 / api-pool×1 / worker-pool×1 / ops-pool×1 (전부 e2-*, Spot VM)

** 시스템 워크로드

kube-system(CSI 드라이버, kube-dns, netd, konnectivity 등)
gmp-system(GKE 관리형 메트릭 수집기)
gke-managed-cim — 전부 GKE가 관리하는 노드당 DaemonSet류로, 노드 5개 × 컴포넌트 수만큼 분산 배치

 

** 애플리케이션 워크로드

네임스페이스 워크로드 상태 배치
notiflex Rollout notiflex-api (smb, Canary, 2/2) Healthy api-pool
StatefulSet valkey-primary (1/1) Running default-pool
CronJob notiflex-api-healthcheck (5분 주기) 최근 3회 모두 Succeeded ops-pool
enterprise Rollout notiflex-api (enterprise, 2/2) Healthy api-pool
kafka Strimzi Cluster Operator + entity-operator (Deployment 1/1×2) Running worker-pool
KafkaNodePool notiflex-kafka-pool (KRaft, 1개) Running worker-pool
monitoring Prometheus, Alertmanager, Loki, Tempo (StatefulSet, 각 1/1) Running 여러 노드 분산
Grafana, kube-prometheus-operator, kube-state-metrics (Deployment) Running 여러 노드 분산
Fluent Bit, node-exporter (DaemonSet, 노드당 1개씩) Running 5개 노드 전체
argocd argocd-server 등 Deployment 6개 + application-controller StatefulSet Running default-pool
argo-rollouts argo-rollouts controller Running default-pool

 

> 설치 도구들을 선택한 이유

더보기
영역 선택 검토했던 대안 핵심 이유
3 GitOps ArgoCD Flux, Jenkins X, Spinnaker Web UI로 배포 상태 시각화, selfHeal로 Git을 유일한 진실로 강제
3 CI GitHub Actions Cloud Build, GitLab CI, Jenkins 저장소와 CI가 같은 플랫폼, 별도 서버 불필요
4 메트릭 Prometheus+Grafana Datadog, CloudWatch, Cloud Monitoring 오픈소스라 SaaS 비용 없음, Helm 번들 설치, Grafana가 이후 Loki/Tempo 통합 허브가 됨
4 로그 Loki+Fluent Bit ELK, CloudWatch Logs, Cloud Logging 경량(ELK는 2Gi+로 e2-medium에서 불가능), Grafana 네이티브 통합
4 알림 PrometheusRule+Alertmanager Grafana Alerting, PagerDuty, Cloud Monitoring Alert GitOps 호환(YAML이 Git에 남음), 이미 설치된 스택 재사용
4 알림 채널 Slack Webhook 이메일, PagerDuty, Discord 무료·즉시 발급, 팀이 이미 쓰는 도구
5 트래픽 관리 Gateway API Ingress NGINX, Istio, Traefik GKE 네이티브, 별도 컨트롤러 불필요
5 무중단 배포 Argo Rollouts (Blue/Green) Flagger, Istio 트래픽분할 ArgoCD와 자연 통합, Service 스위칭만으로 즉시 롤백
6 캐시 Valkey Redis, Memcached, DragonflyDB Redis 호환+BSD 라이선스(SSPL 회피), Memcached는 영속성 없어 부적합
6 Secret CSI Driver+GCP Secret Manager Vault, Sealed Secrets, External Secrets Operator Workload Identity로 키 파일 불필요, 완전관리형이라 운영 부담 없음
6 배포 전략 전환 Canary(Blue/Green→) Flagger, Istio 20→50→80→100% 점진 전환으로 더 세밀하게 문제 조기 발견, 같은 CRD 재사용
7 노드 배치 nodeSelector+멀티 노드풀 Taint/Toleration, 단일 노드풀 설정 단순, 역할별 물리 분리로 8장 이질적 워크로드 리소스 침범 방지
7 ArgoCD 구조 App of Apps 개별 수동 등록, ApplicationSet 파일만 추가하면 자동 sync, Application 계속 늘어나는 상황에 확장성 확보
7 멀티테넌시 Namespace 분리+per-tenant Rollout 단일 namespace+라벨 격리, vCluster 강한 격리, App of Apps와 자연 결합
8 메시징 Kafka(Strimzi) RabbitMQ, NATS, Redis Streams 업계 표준·학습가치, KRaft로 ZooKeeper 없이 경량 운영, 메시지 영속성
8 트레이싱 Grafana Tempo Jaeger, Zipkin 기존 Grafana에서 바로 조회, 메트릭·로그·트레이스 통합
8 배치 자동화 K8s CronJob 외부 cron, Argo Workflows 네이티브 리소스라 설치 불필요, 단순 주기 작업엔 DAG 도구가 과함

> 클러스터 자원 사용 현황

더보기
노드풀 CPU 실사용 CPU requests(예약) 메모리 실사용 메모리 requests(예약)
default-pool (e7xh) 28% 96% 68% 65%
default-pool (jy7t) 22% 100% 81% 57%
api-pool 12% 55% 48% 37%
worker-pool 7% 68% 58% 38%
ops-pool 12% 57% 86% 85%

 

 

 

[ Git + AI + Ops 기대 효과 ]

 

1. 의사결정이 기록되고, 다시 활용 가능한 자산이 된다

 

운영 과정에서 가장 쉽게 사라지는 것은 결과가 아니라 결정의 이유입니다. 어떤 기술을 선택했는지보다 중요한 것은 "왜 이 시점에 이 기술을 선택했는가", "어떤 대안을 검토했고 왜 제외했는가"입니다.

Git + AI + Ops 환경에서는 이러한 의사결정 과정이 대화로 끝나지 않고 JOURNEY.md, ADR, architecture.md 같은 문서 자산으로 축적됩니다.

 

실제로 이번 프로젝트에서도 CLAUDE.md는 최초 정의 이후 변경되지 않았지만,
JOURNEY.md·ADR·architecture.md는 지속적으로 증가하며 시스템 변화와 의사결정 과정을 기록했습니다.

이는 변하지 않는 규칙과 계속 변화하는 운영 지식이 분리되어 관리되는 구조가 만들어졌다는 의미입니다.

 

2. 경험이 누적될수록 개발·운영 사이클이 빨라진다

 

Git + AI + Ops의 핵심은 단순 자동화가 아니라 경험의 재사용 구조입니다.

요구사항
 ↓
탐색 → 비교 → 구현 → 검증
 ↓
문서화(JOURNEY.md / ADR)
 ↓
Git Push
 ↓
CI/CD Pipeline
 ↓
ArgoCD Sync
 ↓
클러스터 반영
 ↓
다음 변경에서 기존 경험 재사용
 

한 번 해결한 문제와 한 번 내린 결정은 다음 작업의 기반이 됩니다.

실제 사례에서도 초기 Kafka 도입 과정에서는 아키텍처 검토, 운영 방식 결정, 배포 검증 등 많은 과정이 필요했습니다.

 

하지만 이후 Tempo와 CronJob 추가 작업에서는 이미 구축된 노드풀 배치 기준 / GitOps 배포 구조 / CI CD 흐름 / 문서화 방식을 그대로 활용하면서 훨씬 빠르게 진행할 수 있었습니다.

또한 트레이싱 노이즈 문제로 v0.6.1 → v0.6.3까지 반복 수정이 필요했지만, 이미 정립된 수정 → Commit → CI → ArgoCD Sync → 검증 흐름 덕분에 빠른 피드백 사이클을 유지할 수 있었습니다.

 

3. 애플리케이션 코드보다 운영 지식이 더 큰 자산이 된다

 

최종 결과물을 보면 시스템 구축에서 실제로 축적되는 것은 코드만이 아니었습니다.

Go 코드 286줄
Kubernetes YAML 907줄
운영 문서 485줄

 

애플리케이션 로직보다 더 많은 부분이 배포 방식, 운영 정책, 장애 대응, 아키텍처 결정 과정에 사용되었습니다.

이번 경험에서 확인한 핵심은 AI가 단순히 코드를 빠르게 작성하는 도구가 아니라는 점입니다.

 

AI는 Git에 축적된 이전 장애 경험 / 리소스 구성 정보 / 설계 선택 기준 / 운영 규칙을 다시 활용하면서, 시간이 지날수록 더 정확한 판단을 지원하는 운영 파트너가 됩니다. Git + AI + Ops는 코드를 자동 생성하는 방식이 아니라, 조직의 운영 경험을 지속적으로 축적하고 재사용하는 새로운 운영 방식입니다.

 

 

 

마치며..

책의 구성 방식이 가상 서비스를 처음부터 구축하고, 요구사항에 따라 점진적으로 확장해 나가는 형태라 전체 흐름을 따라가기 쉬웠다. 서비스 운영 과정에서 필요한 상황과 그에 따른 대응 방식이 자연스럽게 이어져 갔다.

다만 책 한 권으로 AI를 실제 운영 환경에 바로 적용할 만큼의 확신이 생기진 않았다. 아직 깊게 공부하지 않은 부분도 있고, 실무에서 마주치는 문제들은 책에서 다루는 예제와는 다른 복잡성과 변수를 가지고 있기 때문이다.

그래도 이번 기회에 Claude Code를 직접 사용하면서 경험해 본 것 자체에 의의를 둔다. 간단한 테스트나 작은 규모의 서비스에는 충분히 편리하게 활용할 수 있을 것 같고, 앞으로 더 익숙해지면 활용 범위도 넓어질 거라 생각한다.