Skip to content

CH02 - 환경 구성 ​

이 저장소는 「AI 시대에 개발자가 알아야 하는 인프라 구성 배포 with 클로드 코드」 책 스터디를 진행하며 정리한 내용을 다룹니다.

준비물

  • Google Cloud Platform 계정 준비 (무료 크레딧)
  • 클러드 코드 설치
    • 반드시 Claude-code로 해야하는 것은 아님

독자의 실습환경마다 다르겠지만 본인의 경우 macOS(macbook m1) 환경에서 진행하였다.

만약 클러드코드를 호스트OS와 격리된 환경에서 사용하고 싶다면 Lima-vm을 추천한다.

Hello Claude ​

실습을 위하여 우선 https://github.com/sysnet4admin/_Book_GitAIOps Repo를 Clone한다.

bash
 ~/Projects/PJT/bookaiops-study/_Book_GitAIOps tree . -L 1
.
├── AGENTS.md
├── apx
├── ch1
...
├── ch9
├── CLAUDE.md
├── decision-guides
├── GEMINI.md
├── prompt-guardrails
├── README.en.md
├── README.md
├── result-templates
└── scripts

처럼 CLAUDE.md 가 위치한 경로에서 claude 를 입력하여 첫 대화를 시작한다.

  • 단순 claude로 실행하게 된다면 일반 모드로 진입을 의미하며, 이는 매 실행단계 마다 본인에게 Allow? 에 대한 응답을 해야한다.
  • claude에게 권한을 전적으로 위임하는 방법은 뒤에 --dangerously-skip-permissions 를 추가하여 실행한다.

나는 Notiflex라는 B2B 알림 플랫폼의 DevOps 엔지니어로, 이 책을 따라가면서 쿠버네티스 운영환경을 처음부터 구축하려고 해.

책에는 독자가 진행을 위해 필수적으로 입력해야 할 프롬포트와 AI의 응답이 같이 나와있는데 실제 에이전트의 응답이 원고와 달라도 상관은 없다.

GCP 인증 ​

GKE 환경을 구성하기 전에 gcloud CLI를 설치한다.

왜 CLI 프로그램을 사용할까?

GCP 계정을 생성하면서 들어가봤겠지만 GCP 콘솔에서 모든 작업을 진행할 수 있다.
하지만 CLI 프로그램을 사용하는 이유는 AI-Agent가 웹사이트에 접속하여 클릭을 수행하는 것보다 명령어를 호출하여 작업을 수행하는 것이 더 효율적임.

gcloud cli로 작업을 수행하기 위하여 1회 GCP 인증과정을 수행한다.

bash
# gcloud cli 인증
gcloud auth login
# 어플리케이션 SDK 인증
gcloud auth application-default login

프로젝트 설정 ​

인증이 끝나면 기본 프로젝트, 리전을 설정한다.

  • 프로젝트 ID는 전 세계적으로 유니크한 값을 사용한다,

프로젝트 아이디는 pjt-gitaiops-project로(예시임 반드시 변경), 리전은 비교적 가격이 저렴한 곳으로 기본값 설정해줘

한국 사용자에게 가장 낮은 레이턴시를 제공하는 리전은 서울이지만 크레딧 비용을 낮추고 싶다면 더 저렴한 리전을 선택할 수 있다.

Artifact Registry 설정 ​

이후 컨테이너 이미지 저장소 인증을 설정한다.

Artifact Registry는 Google Cloud에서 제공하는 컨테이너 이미지 레지스트리이다.

Docker hub와의 차이점은 GCP 전용 레지스트리 라는 점으로, GKE에서 이미지를 pull할 때 통합효과를 누릴 수 있다 (인증,레이턴시 최소화)

깃 저장소 설정 ​

어플리케이션 코드(Go)와 매니페스트 파일을 관리할 깃헙 저장소를 생성한다

  • 에이전트가 github을 github CLI 도구를 통해 접근하므로 도구 설치가 필요하다 https://cli.github.com/

정상적으로 gh CLI 설정이 되었다면 Agent에게 Notiflex 프로젝트를 위한 저장소 생성을 요청할 수 있다.

이후 가드레일에 의하여 생성한 repo의 루트에 CLAUDE.md 를 생성한다.

repo에 커밋하기 전에 항상 노출되선 안되는 파일들이 올라가진 않았는지 확인할 필요가 있다.

  • GCP 프로젝트 아이디는 공개되도 큰 문제는 없다. 단순 식별자 역할로 실제 접근을 하기위해서는 IAM(접근관리)의 설정이 필요하기 때문이다
tree
.
├── .gitignore
├── CLAUDE.md
├── app/
├── k8s/smb/
├── .github/workflows/
└── README.md

정상적으로 셋업됐다면 위 구조가 된다

GKE 클러스터 생성 ​

Notiflex의 어플리케이션을 올릴 쿠버네티스 클러스터를 설정한다.

  • 쿠버네티스를 쓰는 이유는 대규모 컨테이너를 안정적으로 운영하기 위해서다, 따라서 초기 팀에서 쿠버네티스로 인프라를 셋업하는 것은 오버엔지니어링으로 여겨질 수 있다.

Spot VM은 일반 VM보다 저렴한 대신, GCP가 필요할 때 회수 가능한 VM으로 AWS의 Spot Instance와 같은 개념이다.

  • 클라우드 제공 회사들은 데이터센터에 idle 서버가 많이 생기기 때문에 이런 서버를 알뜰하게 사용하기 위해 싸게 빌려주는 것임
  • 단점은 언제든지 종료될 수 있다는 점으로 종료 30초 전에 알림을 준다.
  • 프로덕션 환경에서는 안정성 문제로 위험하지만, 학습 환경에서는 비용 절감 가능

클러스터 타입이 Standard로 설정되는 것을 알 수 있는데, 다른 타입은 없는지 질문한다.

클러스터 타입이 Standard 말고 있음?

GKE는 Autopilot 모드도 지원한다

  • 오토파일럿은 AWS 의 Fargate와 대응한다, 노드를 안 보고 컨테이너만 배포한다는 서버리스 컨테이너 모델이다
  • Standard는 노드를 직접 관리하지만. 오토파일럿은 GCP에서 노드를 관리하여 Pod 배포에만 신경 쓸 수 있다.

학습에는 노드를 직접 설정하고 관리하는 것이 학습에 의미가 있기 때문에 Standard 를 사용한다.

클러스터 상태 확인 ​

bash
# context 생성
export KUBECONFIG=~/.kube/configs/gke-notiflex.yaml
gcloud container clusters get-credentials notiflex-cluster \
    --zone=asia-southeast1-a \
    --project=opjt-gitaiops-project

# context 이름변경
kubectl config delete-context gke-sysnet4admin_book_gitaiops 2>/dev/null || true
kubectl config rename-context \
    gke_opjt-gitaiops-project_asia-southeast1-a_notiflex-cluster \
    gke-sysnet4admin_book_gitaiops

가드레일 설정에 kubectl 명령 사용시 --context로 명시하도록 되어있지만 --context 없이 직접 입력하고 싶은 경우

bash
kubectl config use-context gke-sysnet4admin_book_gitaiops

kubeconfig을 설정하여 GKE 클러스터를 가리키도록 한 후 클러스터가 정상적으로 만들어졌는지 확인한다.

bash
$ kubectl get nodes
NAME                                              STATUS   ROLES    AGE   VERSION
gke-notiflex-cluster-default-pool-fe19541a-c3cz   Ready    <none>   10m   v1.35.5-gke.1163012
gke-notiflex-cluster-default-pool-fe19541a-ltqv   Ready    <none>   10m   v1.35.5-gke.1163012

모든 노드의 상태가 Ready 인지 확인한다.

Notiflex 앱 배포 ​

가상의 서비스인 Notiflex을 배포하기 위해 Go API 서버 작성을 진행한다.

Go를 사용하는 이유는?

  • 정적 바이너리 컴파일이 가능 -> scratch 베이스 이미지 가능
    • Python, Node.js, Java 는 런타임이 필요하기 때문에 불가
    • Dockerfile이 단순함
  • 표준 라이브러리만으로 HTTP 서버 구현 가능 (코드가 쉬움, 인프라에 집중)

초기 Notiflex 서비스는 http 엔드포인트 2개만 제공하는 매우 간단한 기능만 갖고있다

  • healthCheck 엔드포인트
  • pod id 식별 가능한 엔드포인트

코드 작성이 완료되면 배포를 위한 Dockerfile을 작성한다.

Dockerfile 작성 ​

Dockerfile
FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY go.mod ./
COPY app/main.go ./
RUN CGO_ENABLED=0 go build -o api .

FROM scratch
COPY --from=builder /app/api /api
ENTRYPOINT ["/api"]

scratch 이미지는 완전히 빈 이미지를 의미한다.

컨테이너 이미지는 layer들의 스택이고 각 레이어는 tar로 묶인 파일시스템들이다(컨테이너 실행 시 레이어들을 하나의 루트파일 시스템처럼 보이게함)

scratch는 실제 이미지가 아니라 특수 예약어로 아무 레이어도 pull하지 않고 빈레이어로 시작하게 된다.

장점은 빈 상태이다 보니 취약점이나 공격표면 노출이 적다는 점, 단점은 기본적인 쉘도 없어서 디버깅이 어렵다

빌드 및 배포 ​

빌드를 내 로컬에서 진행하지 않고 Cloud build를 통해 진행한다.

만약 내가 집접 하면 빌드하고, 이미지를 레지스트리에 올리고 해야하지만

gcloud builds submit 명령어로 통해 빌드와 이미지 업로드까지 한꺼번에 가능하다

이후 매니페스트(deployment, namespace, service)를 작성하면 배포준비는 완료된다

bash
kubectl apply -f k8s/smb/namespace.yaml
kubectl apply -f k8s/smb/

네임스페이스를 먼저 생성하고 Deployment/Service를 적용한다.

배포 확인 ​

bash
# pod 확인
$ k get pods -n notiflex
NAME                           READY   STATUS    RESTARTS   AGE
notiflex-api-c755fd698-87kzb   1/1     Running   0          65s
notiflex-api-c755fd698-qk7sg   1/1     Running   0          65s

Pod 정상기동 확인 후 port-forward로 붙은 뒤 api를 테스트한다

bash
kubectl --context gke-sysnet4admin_book_gitaiops port-forward svc/notiflex-api -n notiflex 8080:80
# 다른 터미널에서
$ curl -s http://localhost:8080/id | jq .
{
  "id": 9,
  "pod": "notiflex-api-c755fd698-qk7sg"
}

api를 여러번 입력해도 계속 똑같은 pod에게 요청이 간다.

port-forward를 하는 시점에 Service가 Pod 하나를 선택해서 그 파드에 직접 TCP 터널을 연결하기 때문에 이후 모든 요청은 그 터널을 통해 같은 Pod로 간다.

bash
$ kubectl --context gke-sysnet4admin_book_gitaiops run -it --rm test \
    --image=curlimages/curl --restart=Never -- \
    sh -c 'for i in $(seq 1 6); do curl -s http://notiflex-api.notiflex/id; echo; done'
{"id":18,"pod":"notiflex-api-c755fd698-qk7sg"}

{"id":19,"pod":"notiflex-api-c755fd698-qk7sg"}

{"id":3,"pod":"notiflex-api-c755fd698-87kzb"}

{"id":4,"pod":"notiflex-api-c755fd698-87kzb"}

{"id":20,"pod":"notiflex-api-c755fd698-qk7sg"}

{"id":5,"pod":"notiflex-api-c755fd698-87kzb"}

pod "test" deleted

이렇게 실제 서비스를 사용하도록 임시 파드를 생성하여 테스트가 가능하다.

작업 기록 ​

배포까지 마쳤다면 오늘한 내용을 커밋하고 기록한다.

커밋 전에 JOURNEY.md 를 생성한다.

저널md는 진행 현황을 체크하고 현재 작업사항을 기록하는 문서이다.

현재 클러드 세션이 종료된다고 하더라도 저널을 다시 읽고 최신 시점에서 다시 진행을 가능하게 한다.(컨텍스트 유지)

작업을 하는 것도 중요하지만 더 중요한 것은 기록이다.

  • 어떤 트러블슈팅이 있었는지
  • 어떤 의사결정을 했는지

Agent와 같이 작업하게 된다면 문서가 곧 메모리 역할을 하고, 후임자 또한 이력을 읽기 쉬워진다.
(하루이틀 운영하는 서비스에서는 이점을 잘 느끼기 어렵지만, 서비스가 한달이 되고 일년이되면 달라진다)

챕터마다 기록을 남겨야 하기 때문에 update-docs 커맨드를 만들기를 권장한다.