GitLab CI/CD — .gitlab-ci.yml -> K3s
실제 프로젝트에 적용한 GitLab CI/CD 파이프라인을 기반으로 yml 구조 동작 원리와 Docker 빌드 → K3s 배포 흐름을 정리합니다.
Seobway · · 15분
GitLab CI/CD — '.gitlab-ci.yml' -> K3s
이 글은 실제 프로젝트에 적용한 GitLab CI/CD 파이프라인을 기반으로,
yml 구조가 어떻게 동작하는지와 Docker 빌드 → K3s 배포 흐름을 정리합니다.
1. GitLab CI/CD란 무엇인가
GitLab은 저장소 루트의 .gitlab-ci.yml을 읽어 파이프라인(Pipeline) 을 자동 생성합니다.
- 파이프라인은 여러 잡(Job) 으로 구성됩니다.
- 각 잡은 Runner가 띄운 독립 컨테이너(또는 VM)에서 실행됩니다.
- 잡 간 파일 시스템은 공유되지 않습니다. 어떤 잡에서 만든 파일은 그 잡이 끝나면 사라집니다.
%% desc: GitLab CI 기본 구조 — Git Push 후 Pipeline이 생성되고 Runner 컨테이너가 각 잡을 실행
flowchart LR
GIT[("Git Push\n/ MR")] --> PL[Pipeline 생성]
PL --> R1[Runner\nContainer A]
PL --> R2[Runner\nContainer B]
PL --> R3[Runner\nContainer C]
2. 기본 구성 요소 한눈에 보기
| 키워드 | 역할 |
|---|---|
stages |
파이프라인 단계 순서 정의 |
| Job 이름 | stages / variables 외 최상위 키 = 잡 |
variables |
잡에서 참조할 환경 변수 |
rules |
잡 실행 조건 (브랜치, MR, 태그 등) |
needs |
잡 간 의존 관계 (DAG 구성) |
before_script |
준비 단계 (로그인, 설정 파일 생성 등) |
script |
실제 작업 (빌드 / 배포 / 검증 / 롤백) |
3. 파이프라인 전체 흐름
이 글에서 다루는 파이프라인의 목표는 다음과 같습니다.
- 서버 코드를 Docker 이미지로 빌드
- 레지스트리에 푸시
- K3s(Kubernetes)에 배포 (Deployment 이미지 태그 교체)
- 롤아웃 상태 검증
- 실패 시 자동 롤백
%% desc: 파이프라인 전체 흐름 — build → deploy → verify, 실패 시 rollback 자동 실행
flowchart TD
PUSH[/"Git Push (대상 브랜치)"/] --> BUILD
subgraph STAGE_BUILD["🔨 Stage: build"]
BUILD["build 잡\ndocker buildx build --push\n이미지:커밋SHA + latest"]
end
subgraph STAGE_DEPLOY["🚀 Stage: deploy"]
DEPLOY["deploy 잡\nkubectl set image\nDeployment 이미지 교체"]
end
subgraph STAGE_VERIFY["✅ Stage: verify"]
VERIFY["verify 잡\nkubectl rollout status\n--timeout=60s"]
end
subgraph STAGE_ROLLBACK["⏪ Stage: rollback"]
ROLLBACK["rollback 잡\nkubectl rollout undo\n(on_failure만 실행)"]
end
BUILD -->|"성공"| DEPLOY
DEPLOY -->|"성공"| VERIFY
VERIFY -->|"성공"| DONE(["✅ 배포 완료"])
VERIFY -->|"실패"| ROLLBACK
DEPLOY -->|"실패"| ROLLBACK
ROLLBACK --> FAIL(["❌ 파이프라인 종료"])
4. GitLab Variables(시크릿) 설계
민감정보(비밀번호 / 토큰 / kubeconfig)는 코드에 직접 넣으면 안 됩니다.
GitLab 프로젝트 Settings → CI/CD → Variables 에 등록하고, CI가 주입받는 구조를 씁니다.
필수 변수 목록
| 변수명 | 설명 | 타입 권장 |
|---|---|---|
DOCKERHUB_USERNAME |
Docker 레지스트리 로그인 사용자명 | Variable |
DOCKERHUB_PASSWORD |
Docker 레지스트리 비밀번호 / Access Token | Variable (Masked) |
DEPLOY_CONFIG |
JSON 문자열 (KUBECONFIG_DATA 포함) | Variable / File |
DEPLOY_CONFIG JSON 예시
{
"KUBECONFIG_DATA": "<k3s.yaml 전체를 base64 인코딩한 문자열>"
}
시크릿 주입 흐름
%% desc: 시크릿 주입 흐름 — GitLab Variables에서 CI Job 컨테이너로 환경변수 주입
flowchart LR
subgraph GL["GitLab Project Settings"]
V1["DOCKERHUB_USERNAME\nDOCKERHUB_PASSWORD"]
V2["DEPLOY_CONFIG\n(JSON with KUBECONFIG_DATA)"]
end
subgraph JOBS["CI Job Containers"]
J_BUILD["build 잡\ndocker login\ndocker buildx build --push"]
J_DEPLOY["deploy / verify / rollback 잡\nbase64 -d → ~/.kube/config\nkubectl ..."]
end
V1 -->|"환경변수 주입"| J_BUILD
V2 -->|"환경변수 주입"| J_DEPLOY
5. kubeconfig 준비 방법 (K3s)
5-1. K3s 서버에서 kubeconfig 가져오기
cat /etc/rancher/k3s/k3s.yaml
5-2. server: 주소 수정
K3s 기본 설정은 127.0.0.1로 되어 있어, CI Runner에서 접근할 수 없습니다.
Runner가 접근 가능한 실제 서버 IP로 변경해야 합니다.
# 변경 전
server: https://127.0.0.1:6443
# 변경 후 (Runner가 접근 가능한 K3s 서버 주소)
server: https://<K3S_SERVER_IP>:6443
5-3. base64 인코딩
# Linux
base64 -w 0 k3s.yaml
# macOS
base64 -i k3s.yaml
이 출력값을 DEPLOY_CONFIG JSON의 KUBECONFIG_DATA 값으로 사용합니다.
kubeconfig 준비 흐름
%% desc: kubeconfig 준비 흐름 — K3s 설정 파일을 base64 인코딩해 GitLab Variable로 관리
flowchart LR
K3S[("K3s 서버\n/etc/rancher/k3s/k3s.yaml")]
EDIT["server: 127.0.0.1\n→ server: K3S_SERVER_IP 로 수정"]
B64["base64 -w 0 k3s.yaml"]
VAR["GitLab Variable\nDEPLOY_CONFIG.KUBECONFIG_DATA"]
JOB["deploy / verify / rollback 잡\necho $KUBECONFIG_DATA | base64 -d\n→ ~/.kube/config"]
KUBECTL["kubectl 명령 실행"]
K3S --> EDIT --> B64 --> VAR --> JOB --> KUBECTL
포인트:
~/.kube/config는 각 잡 컨테이너 안에서만 존재합니다.
잡이 끝나면 사라지므로, deploy / verify / rollback 잡마다 각각 생성해야 합니다.
6. Job별 상세 설명
(A) build 잡 — Docker 이미지 빌드 & 푸시
%% desc: build 잡 상세 — Docker-in-Docker 환경에서 이미지 빌드 및 레지스트리 푸시
flowchart LR
ENV["image: docker:24.0.5\nservices: docker:24.0.5-dind"]
LOGIN["docker login\n(DOCKERHUB_USERNAME / PASSWORD)"]
BUILD["docker buildx build --push\n이미지:커밋SHA\n이미지:latest"]
ENV --> LOGIN --> BUILD
docker:24.0.5-dind: Docker-in-Docker, 컨테이너 안에서 docker 명령 사용 가능하게 해줌--push: 빌드와 동시에 레지스트리에 푸시 (로컬에만 저장하지 않음)- 이미지 태그로
$CI_COMMIT_SHA사용 → 롤백 시 정확한 버전 특정 가능
(B) deploy 잡 — K3s Deployment 이미지 교체
%% desc: deploy 잡 상세 — kubeconfig 복원 후 kubectl로 K3s Deployment 이미지 교체
flowchart LR
ENV["image: alpine/k8s:1.29.0\n(kubectl 포함)"]
KUBE["DEPLOY_CONFIG에서 KUBECONFIG_DATA 추출\nbase64 -d → ~/.kube/config"]
DEPLOY["kubectl set image deployment/$DEPLOYMENT_NAME\n...\n-n $NAMESPACE"]
ENV --> KUBE --> DEPLOY
(C) verify 잡 — 롤아웃 상태 검증
kubectl rollout status deployment/$DEPLOYMENT_NAME \
-n $NAMESPACE \
--timeout=60s
- 60초 내에 모든 파드가 정상 기동되지 않으면 실패 →
rollback잡 트리거
(D) rollback 잡 — 자동 롤백
rules:
- when: on_failure # 이전 잡이 실패했을 때만 실행
allow_failure: true # 롤백 자체 실패가 파이프라인 블로킹하지 않도록
kubectl rollout undo deployment/$DEPLOYMENT_NAME -n $NAMESPACE
7. "VM에 kubeconfig 파일이 없는데?" — 정상입니다
%% desc: kubeconfig 수명 — 잡 실행 중에만 컨테이너 내 존재, 종료 후 자동 삭제
flowchart LR
subgraph JOB["Job 실행 중 (컨테이너 내부)"]
CONFIG["~/.kube/config\n✅ 존재"]
KUBECTL["kubectl 명령 실행"]
CONFIG --> KUBECTL
end
subgraph AFTER["Job 종료 후"]
GONE["~/.kube/config\n❌ 삭제됨\n(컨테이너 정리)"]
end
subgraph VM["SSH로 접속한 Runner 호스트 VM"]
NOTHERE["~/.kube/config\n❌ 없음\n(정상)"]
end
JOB -->|"컨테이너 종료"| AFTER
CI에서 kubeconfig는 job 컨테이너 안에서만 생성되고, 잡이 끝나면 컨테이너가 정리되며 파일도 사라집니다.
SSH로 접속한 VM에서 ~/.kube/config가 없어도 완전히 정상입니다.
8. 트러블슈팅 체크리스트
Docker login 실패
Error response from daemon: unauthorized
DOCKERHUB_USERNAME,DOCKERHUB_PASSWORD가 GitLab Variables에 등록되어 있는지 확인- Docker Hub Access Token 만료 여부 확인
- Variable이 Masked 설정이면 값 앞뒤 공백 없는지 확인
DEPLOY_CONFIG 관련 에러
jq: error: null and null cannot be added
- GitLab Variables에
DEPLOY_CONFIG가 존재하는지 확인 - 값이 유효한 JSON 문자열인지 확인 (
echo $DEPLOY_CONFIG | python3 -m json.tool) - JSON에
KUBECONFIG_DATA키가 있는지 확인
kubectl 클러스터 연결 실패
Unable to connect to the server: dial tcp ...
k3s.yaml의server:가 CI Runner에서 접근 가능한 주소인지 확인- K3s API 포트(기본
6443) 방화벽/보안그룹 허용 여부 확인 KUBECONFIG_DATA가 base64로 인코딩된 값인지 확인 (이중 인코딩 주의)
9. 전체 아키텍처 요약
%% desc: 전체 아키텍처 — 개발자 Push → GitLab → Runner → Registry → K3s 클러스터 배포 흐름
flowchart TB
DEV[/"👨💻 개발자\nGit Push"/]
subgraph GITLAB["GitLab"]
REPO[("Repository")]
VARS[("CI/CD Variables\n(Secrets)")]
PIPE["Pipeline\n(.gitlab-ci.yml)"]
end
subgraph CI["GitLab Runner (Container)"]
BUILD_J["build 잡\ndocker buildx build --push"]
DEPLOY_J["deploy 잡\nkubectl set image"]
VERIFY_J["verify 잡\nkubectl rollout status"]
ROLLBACK_J["rollback 잡\nkubectl rollout undo"]
end
subgraph REGISTRY["Container Registry\n(Docker Hub 등)"]
IMG[("이미지:SHA\n이미지:latest")]
end
subgraph K3S["K3s Cluster"]
DEP["Deployment"]
POD1["Pod (신규)"]
POD2["Pod (신규)"]
end
DEV --> REPO
REPO --> PIPE
VARS --> CI
PIPE --> BUILD_J
BUILD_J -->|"push"| IMG
BUILD_J -->|"성공"| DEPLOY_J
IMG -->|"pull"| K3S
DEPLOY_J -->|"kubectl set image"| DEP
DEP --> POD1
DEP --> POD2
DEPLOY_J -->|"성공"| VERIFY_J
VERIFY_J -->|"실패"| ROLLBACK_J
- GitLab
- CI/CD
- DevOps
- K3s
- Docker
- Kubernetes
- 인프라