사내에서 GitLab CI/CD 파이프라인을 Docker + Kubernetes 조합으로 다시 짜면서 정리한 글. 이전엔 젠킨스 Freestyle job 써왔는데 팀 규모 커지면서 Git 리포에 CI 정의가 같이 들어가는 쪽이 관리가 훨씬 편하고, 리뷰도 받을 수 있어서 넘어왔다. GitLab 17 기준이다.
한 번 밑바닥부터 훑어보고 싶은 사람을 위해, 도구 설치부터 쿠버 배포까지 쭉 정리. 실제 프로덕션 파이프라인은 이거보다 stage 분리가 더 들어가 있다 (lint → unit → build → security scan → deploy). 여기선 뼈대만.
1. 사전 준비: 필수 도구 설치 및 설정
1.1 Git 설치
Ubuntu
sudo apt update sudo apt install git -y
macOS
brew install git
Windows
Git 공식 사이트에서 인스톨러 받아서 실행.
설치 확인:
git --version
1.2 Node.js 설치
Ubuntu
sudo apt update sudo apt install -y nodejs npm
macOS
brew install node
Windows
Node.js 공식 사이트에서 인스톨러 받음.
node --version npm --version
1.3 Docker 설치
Docker 27 기준. 우분투에서는 공식 apt 저장소 쓰는 게 장기적으론 편한데 빠르게 돌려볼 땐 docker.io 패키지로도 충분.
Ubuntu
sudo apt install -y docker.io sudo systemctl start docker sudo systemctl enable docker
macOS / Windows
Docker Desktop 받아서 설치. Mac이면 OrbStack도 선택지다 (가볍고 빠름).
docker --version sudo usermod -aG docker $USER
1.4 Kubernetes 설치 (kubectl)
Kubernetes 1.31 클러스터에 붙인다는 가정으로 kubectl을 받는다. 클라이언트 버전은 서버와 ±1 마이너 버전 안에서 맞추는 게 안전함.
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl sudo mv kubectl /usr/local/bin/
kubectl version --client
2. 프로젝트 초기화
2.1 Git 리포지토리 생성
mkdir my-app && cd my-app git init
2.2 Node.js 프로젝트 초기화
npm init -y
생성되는 package.json 예시:
{
"name": "my-app",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {}
}
2.3 간단한 Node.js 앱
가장 미니멀한 HTTP 서버. 헬스체크 용도로도 딱 좋다.
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello, Kubernetes!");
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
3. Docker 파일 설정
3.1 Dockerfile
팁: 실제 운영에선 node:16 같은 full 이미지 말고 node:20-alpine 쓰는 게 용량이 훨씬 작다. 여기선 호환성 우선.
# 베이스 이미지 FROM node:16 # 앱 디렉토리 생성 WORKDIR /usr/src/app # 종속성 복사 및 설치 COPY package*.json ./ RUN npm install # 애플리케이션 복사 COPY . . # 애플리케이션 실행 EXPOSE 3000 CMD ["npm", "start"]
3.2 로컬 빌드 / 실행
# Docker 이미지 빌드 docker build -t my-app . # Docker 컨테이너 실행 docker run -p 3000:3000 my-app
4. Kubernetes 배포 파일
4.1 Deployment + Service
replicas: 2 로 해두면 롤링 업데이트 시에도 최소 1개는 살아 있다. LoadBalancer 타입은 클라우드(EKS, GKE, NKS 등)에서 쓰고, 로컬 minikube 환경이면 NodePort로 바꿔야 한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
type: LoadBalancer
ports:
- port: 3000
targetPort: 3000
selector:
app: my-app
4.2 배포 명령
# Kubernetes 클러스터에 배포 kubectl apply -f k8s-deployment.yaml # 상태 확인 kubectl get pods kubectl get services
5. GitLab CI/CD 구성
5.1 .gitlab-ci.yml
아래는 뼈대. 주의할 부분은 docker build를 러너 안에서 돌리려면 러너 자체가 Docker-in-Docker (dind) 세팅이거나 호스트 소켓이 마운트돼 있어야 함. 사내에선 shell executor 러너 한 대를 빌드 전용으로 분리해서 쓴다.
stages:
- build
- test
- deploy
build:
stage: build
script:
- docker build -t my-app:$CI_COMMIT_SHA .
test:
stage: test
script:
- echo "Running tests..."
- npm test || echo "No tests configured yet"
deploy:
stage: deploy
script:
- echo "$KUBE_CONFIG" | base64 -d > kubeconfig.yaml
- export KUBECONFIG=kubeconfig.yaml
- kubectl apply -f k8s-deployment.yaml
only:
- main
5.2 환경 변수
GitLab UI의 Settings → CI/CD → Variables에 KUBE_CONFIG를 base64 인코딩한 값으로 넣는다. Masked 체크박스 꼭 켤 것. 로그에 평문 노출 방지용.
6. 파이프라인 실행
git add . git commit -m "Add CI/CD pipeline" git push origin main
프로젝트 페이지 → CI/CD → Pipelines에서 실행 상태 확인. 실패하면 job 로그 직접 뜯어본다. "왜 안 되지" 싶을 때 95%는 러너 권한 문제거나 kubeconfig base64 인코딩 오류다.
7. 배포 확인
kubectl get pods kubectl get services
LoadBalancer IP가 할당되면 브라우저에서 접속해서 "Hello, Kubernetes!" 뜨면 끝.
실전에서 추가로 고민하게 되는 것들
이 기본 파이프라인을 팀에 도입한 뒤에 결국 붙이게 되는 것들 목록이다.
- 이미지 태그를
$CI_COMMIT_SHA로 고정하고latest는 배포에 쓰지 않기 (롤백 불가 방지) - PR용 리뷰 environment 자동 생성 (
environment: review/$CI_COMMIT_REF_NAME) - Trivy로 이미지 취약점 스캔 stage 추가
- kubectl 대신 Helm으로 values 관리
- main 머지 시 자동 배포는 staging만, 운영은
when: manual
처음부터 다 넣지는 말고, 실제로 아픈 순서대로 붙이는 게 낫다. 나는 순서대로 하나씩 아프면서 붙였음.