본문 바로가기
CI|CD Pipeline

GitLab CI 시작하기(CI/CD)

by 세용융용융용 2026. 5. 24.

GitLab CI 시작하기

러너(Runner) 설치부터 등록, 그리고 동작 확인 까지

  • 대상 OS : Ubuntu / macOS


0. 목차

  1. GitLab CI vs Jenkins
  2. GitLab이 뭐냐?
  3. 왜? GitLab CI를 쓰는가
  4. GitLab 시작하기 (Git 설정 / 클론)
  5. GitLab Runner 설치
  6. Runner 등록하기
  7. 동작 확인 체크리스트
  8. 첫 번째 CI 파이프라인 만들기


1. GitLab vs Jenkins

둘다 CI/CD와 DevOps 자동화에 많이 쓰이지만 성격이 꽤 다릅니다.

항목 GitLab Jenkins
형태 통합 DevOps 플랫폼 CI/CD 자동화 서버
설치 방식 SaaS + Self-hosted Self-hosted
주요 역할 Git + CI/CD + Registry + MR 통합 빌드/배포 자동화 중심
설정 방식 .gitlab-ci.yml (YAML) Pipeline + Plugin 설정
러닝 커브 비교적 쉬움 상대적으로 높음
플러그인 생태계 제한적 (내장 중심) 매우 풍부
유지보수 낮음 (자동화 많음) 높음 (직접 관리 필요)
확장성 조금 높음 매우 높음
서버 관리 부담 낮음 높음

1-1. 언제 많이 쓰나?

GitLab

장점

  • Git 저장소와 CI/CD를 통합 관리 가능
  • .gitlab-ci.yml 기반으로 설정이 단순함
  • 러닝커브가 낮아 빠른 구축 가능

단점

  • Jenkins 대비 커스터마이징 자유도가 낮음
  • 복잡한 레거시 환경 대응에는 한계가 있음
    ( GitLab CI는 test → build → deploy 같은 표준 흐름에는 강하지만, 회사마다 다른 오래된 배포 방식이나 예외 절차가 많으면 구성하기 까다로울 수 있음 )

Jenkins

장점

  • 커스터마이징 자유도 높음 ( 다양한 플러그인 + Pipline )
  • 복잡하고 예외 많은 CI/CD 자동화 흐름을 유연하게 구성하는 데 강함

단점

  • 러닝커브 및 운영 난이도가 높음
  • 서버 및 플로그인 관리가 필요함

GitLab 선택 배경

  • 빠르게 플랫폼을 데모 형태로 구현해야 하는 특성 + CI/CD 환경이 없었기 때문에 러닝커브가 낮은 GitLab을 사용
  • 또한 GitLab을 형상관리 도구로 사용하고 있었으며, Docker Compose 기반 환경에서 .gitlab-ci.yml만으로 CI/CD 구성이 가능했기 때문에 GitLab을 활용하여 CI/CD를 구축하기로 하였습니다.


2. GitLab 이란?

GitLab은 코드 저장소 + CI/CD + DevOps 기능을 통한한 플랫폼

Jenkins가 유연한 CI/CD 자동화 중심이라면, GitLab은 ⤵

  • 형상관리
  • 빌드
  • 테스트
  • 배포
  • Runner 관리

까지 하나의 플랫폼에서 통합 관리할 수 있는 것이 핵심입니다.

GitLab CI는 Jenkins보다 복잡한 맞춤형 파이프라인 구현은 어렵지만

운영이 쉽고 GitLab 안에서 통합 관리되며 .gitlab-ci.yml 하나로 파이프라인을 구현할 수 있습니다.



3. GitLab 시작하기

3-1. Git 기본 설정

git config --global user.name "주세용"
git config --global user.email "sy02229@naver.com"

# 확인
git config --list

3-2. 레포지토리 클론

1) HTTP 방식

  • 간단하지만 push 할 때마다 로그인 필요
  • git clone https://gitlab.com/your-name/your-project.git

2) SSH 방식 (권장)

# 1. SSH 키 생성 (서버)
ssh-keygen

# 2. 공캐 키 확인 (서버)
cat ~/.ssh/id_rsa.pub
→ 출력 내용을 복사

# 3. GitLab에 SSH 키 등록 (GitLab)
GitLab 우측 상단 프로필 → Preferences → Access(SSH Keys) → Add new key

# 4. SSH config 설정 (서버)
vi ~/.ssh/config
Host gitlab.com
    HostName gitlab.com
    User git
    Port 22
    IdentityFile ~/.ssh/id_rsa

# 5. 연결 테스트
ssh -T git@gitlab.com
→ 성공 시: Welcome to GitLab, @sy0218!

# 6. SSH로 Clone
git clone git@gitlab.com:sy0218/jsy_project.git


4. GitLab Runner 설치

RunnerGitLab이 시키는 작업을 실제로 수행하는 실행 프로그램입니다.


4-1. Ubuntu GitLab 설치

# 1. 저장소 등록
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash

# 2. Runner 설치
apt install gitlab-runner -y

# 3. 확인
gitlab-runner --version

4-2. macOS Gitlab 설치

# 1. Homebrew 확인 ( 없으면 설치 )
brew --version
→ 없으면 설치 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# 2. Runner 설치
brew install gitlab-runner

# 3. 확인
gitlab-runner --version


5. GitLab Runner 등록하기

Runner 설치만으로는 동작하지 않습니다. → GitLab 프로젝트와 Runner를 연결해야 합니다.


5-1. GitLab에서 Runner 생성

Project → Settings → CI/CD → Runners → Create Project Runner

5-2. 서버에서 Runner 연결

GitLab에서 제공한 명령 실행

gitlab-runner register \
  --url https://gitlab.com \
  --token glrt-xxxxxxxxxxxx

5-3. 프로젝트에서 Instance Runner 활성화

GitLab Runner를 서버에서 띄웠더라도, 프로젝트에서 해당 Runner를 사용하도록 설정해야 CI/CD가 정상 동작합니다.

설정 방법

  1. GitLab 프로젝트 접속
  2. Settings → CI/CD → Runners 이동
  3. Instance Runners 섹션 확인
  4. 아래 옵션을 OFF → ON으로 변경

질문 예시

질문 입력값
GitLab instance URL https://gitlab.com
Runner name my-runner
Executor docker (권장)
Default Docker image alpine:latest
  • Executor란?
    • GitLab Runner가 CI 작업을 실행하는 환경
    • docker
      • 매번 깨끗한 컨테이너에서 실행
      • 안정적
    • shell
      • 서버에서 직접 실행
      • 가볍지만 환경이 오염될 수 있음


6. GitLab Runner 동작 확인 체크리스트

GitLab UI 확인 | 서버 확인


6-1. GitLab UI 확인

Project → Settings → CI/CD → Runners
  • Runner가 초록색 Online 상태인지 확인

6-2. 서버 확인

Ubuntu

gitlab-runner status
gitlab-runner start
gitlab-runner list
gitlab-runner verify

macOS

brew services list
brew services start gitlab-runner
gitlab-runner list
gitlab-runner verify

6-3. 최종 체크리스트

  • GitLab에서 Online 상태 확인
  • gitlab-runner status → running
  • gitlab-runner list 에 Runner 표시
  • gitlab-runner verify 성공

6-4. 자주 발생하는 문제

1) 네트워크 연결 확인

ping gitlab.com
curl https://gitlab.com

2) Docker executor 사용 시

Docker 정상 동작 여부 확인(서버)

docker --version
docker ps


7. CI 파이프라인 만들기 (베스트 프랙티스)

GitLab CI는 코드가 push/merge 될 때 자동으로

검증(lint/test) → 빌드 → 배포 → 이미지 관리(Harbor) 까지 자동화하는 시스템이다.


7-1. 전체 구조(핵심 흐름)

feature 브랜치
→ 코드 검사(lint)만 실행
→ 개발 중 빠른 오류 확인용

develop 브랜치
→ lint → test → docker build → dev 이미지 생성 & Harbor push(Harbor auto scan)
→ "개발용 배포 이미지" 만드는 단계

main 브랜치
→ develop에서 만든 이미지 기준으로 
→ 운영 이미지 승격 + deploy (취약점 scan OK 전제)

7-2. GitLab CI 기본 개념

1) stages (파이프라인 단계)

stages:
    - setup
    - lint
    - test
    - build
    - deploy

→ 실행 순서를 정의함


2) job (작업 단위)

lint:
  stage: lint
  script:
    - echo "lint 실행"

→ CI에서 실제 실행되는 작업의 최소 단위


3) script (실행 명령어)

script:
  - pnpm lint
  - pytest

→ 실제 서버에서 실행되는 쉘 명령


4) rules (브랜치 / 조건 필터)

rules:
  - if: '$CI_COMMIT_BRANCH == "develop"'

→ 언제 job을 실행할지 결정


5) workflow(파이프라인 전체 조건)

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push"'

push 기준으로만 실행


6) artifacts (결과 전달)

artifacts:
  paths:
    - build/

build 결과물을 다음 stage로 전달


7) allow_failure

allow_failure: true

→ 실패해도 파이프라인 계속 진행 (lint, typecheck 등에 사용)


7-3. CI 실습

1) 전체 흐름

feature → lint
develop → lint + test + build + push(Harbor auto scan)
main    → deploy (scan OK 전제)

2) GitLab Variables 정리

CI/CD에서 필요한 민감 / 환경설정 값을 코드에 안 쓰고 따로 관리

ex..) ⤵
컨테이너 레지스트리 계정 / 비밀번호
Harbor 주소
AWS key
API key
DB 패스워드

GitLab Variables 등록 위치

Project → Settings → CI/CD → Variables
  • 참고 자료: https://sy02229.tistory.com/778

3) Harbor 정리

  • Harbor 설치 참고: https://sy02229.tistory.com/779

4) gitlab CI/CD 베스트 프렉티스 템플릿 구조

  • .gitlab-ci.yml → 흐름 정의 (stages / workflow / include)
  • .gitlab/ci/rules.yml → 브랜치 + 변경 기반 rules 템플릿
  • .gitlab/ci/templates.yml → 공통 실행 환경 (.python / .node / .docker-*)
  • .gitlab/ci/backend.yml → backend job 정의
  • .gitlab/ci/frontend.yml → frontend job 정의
  • tests/unit/ → 유닛테스트 스크립트 (backend.sh, frontend.sh ...)
  • tests/integration/ → 통합테스트 스크립트 (API ↔ DB, 서비스 간 연동 등)
  • tests/e2e/ → E2E 테스트 스크립트 (사용자 시나리오 전체 흐름)
  • scripts/ci/ → CI job에서 사용하는 script (build.sh, promote.sh ...)

.gitlab-ci.yml

# ================================================================
# CI/CD 구조 원칙
# ================================================================
# 1) CI 파일 = 흐름만 담당
# 2) 민감정보 = GitLab CI/CD Variables
# 3) 빌드 = Kaniko
# 4) 이미지 관리 = Harbor + Crane
# 5) 공통 로직 = hidden job(.xxx)
# 6) main deploy = Harbor scan 기반 정상 → 실행

# 브랜치 전략
# feature/* → 변경된 것만 lint
# develop
#   → MR 성공시 merge
#   → 변경된 것만 lint → test → build → Harbor push (자동 scan)
# main
#   → Harbor scan OK
#   → deploy

# 파일 구조
# .gitlab-ci.yml              → 흐름 정의 (stages / workflow / include)
# .gitlab/ci/rules.yml        → 브랜치 + 변경 기반 rules 템플릿
# .gitlab/ci/templates.yml    → 공통 실행 환경 (.python / .node / .docker-*)
# .gitlab/ci/backend.yml      → backend job 정의
# .gitlab/ci/frontend.yml     → frontend job 정의
# tests/unit/                 → 유닛테스트 스크립트 (backend.sh, frontend.sh ...)
# tests/integration/          → 통합테스트 스크립트 (API ↔ DB, 서비스 간 연동 등)
# tests/e2e/                  → E2E 테스트 스크립트 (사용자 시나리오 전체 흐름)
# scripts/ci/                 → CI job에서 사용하는 script (build.sh, promote.sh ...)

stages:
  - setup
  - lint
  - test
  - build
  - deploy

# ================================================================
# 전역 변수
# ================================================================
variables:
  PIP_DISABLE_PIP_VERSION_CHECK: "1"
  IMAGE_BASE: "${HARBOR_URL}/${HARBOR_PROJECT}"

# ================================================================
# 파이프라인 실행 조건
# ================================================================
workflow:
  rules:
    # push
    - if: '$CI_PIPELINE_SOURCE == "push"'

    # MR
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

    # web(테스트 용)
    - if: '$CI_PIPELINE_SOURCE == "web"'

# ================================================================
# Include
# ================================================================
include:
  - local: '.gitlab/ci/rules.yml'
  - local: '.gitlab/ci/templates.yml'
  - local: '.gitlab/ci/backend.yml'

.gitlab/ci/rules.yml

# ================================================================
# RULES 템플릿 (변경 기반 + 브랜치)
# ================================================================
.rules:backend:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH =~ /^feature\//'
      changes:
        - backend/**/*
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"'
      changes:
        - backend/**/*

.rules:frontend:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH =~ /^feature\//'
      changes:
        - frontend/**/*
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"'
      changes:
        - frontend/**/*

.rules:backend:develop:mr:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"'
      changes:
        - backend/**/*

.rules:frontend:develop:mr:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"'
      changes:
        - frontend/**/*

.rules:backend:main:mr:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
      changes:
        - backend/**/*

.rules:frontend:main:mr:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
      changes:
        - frontend/**/*

.gitlab/ci/templates.yml

# ================================================================
# 공통 실행 템플릿
# ================================================================
.lint:backend:python:
  image: python:3.11-slim
  interruptible: true
  before_script:
    - pip install --no-cache-dir uv
    - uv venv .venv && source .venv/bin/activate
    - uv pip install ruff mypy

.python:
  image: python:3.11-slim
  interruptible: true
  variables:
    UV_CACHE_DIR: "${CI_PROJECT_DIR}/.uv-cache"
    UV_LINK_MODE: "copy"
  cache:
    key: uv-cache-global
    paths:
      - .uv-cache
    policy: pull
  before_script:
    - pip install --no-cache-dir uv
    - uv venv .venv && source .venv/bin/activate
    - uv pip install --cache-dir "${CI_PROJECT_DIR}/.uv-cache" "./backend[dev]"

.node:
  image: node:20-bookworm-slim
  interruptible: true
  before_script:
    - corepack enable
    - corepack prepare pnpm@9.15.9 --activate
    - cd frontend
    - pnpm install --frozen-lockfile

# Harbor 인증
.harbor-auth-kaniko:
  before_script:
    - mkdir -p /kaniko/.docker
    - |
      cat > /kaniko/.docker/config.json <<EOF
      {
        "auths": {
          "${HARBOR_URL}": {
            "auth": "$(printf '%s:%s' "${HARBOR_USER}" "${HARBOR_PASSWORD}" | base64 -w0)"
          }
        }
      }
      EOF

# 도커 빌드(KANIKO)
.docker-build:
  extends: .harbor-auth-kaniko
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - sed -i 's/\r$//' ./scripts/ci/build.sh
    - chmod +x ./scripts/ci/build.sh
    - sh ./scripts/ci/build.sh

# main 승격 (Harbor scan 성공)
.docker-promote:
  image:
    name: gcr.io/go-containerregistry/crane:debug
    entrypoint: [""]
  script:
    - sed -i 's/\r$//' ./scripts/ci/promote.sh
    - chmod +x ./scripts/ci/promote.sh
    - sh ./scripts/ci/promote.sh

.gitlab/ci/backend.yml

# ================================================================
# BACKEND
# ================================================================
setup-env:
  stage: setup
  extends: .python
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"'
      changes:
        - backend/pyproject.toml
  cache:
    policy: pull-push
  script:
    - echo "패키지 변경됨! .uv-cache 새로 구웠음"

ruff:
  stage: lint
  extends: [.lint:backend:python, .rules:backend]
  script:
    - ruff check backend
    - ruff format --check backend
  allow_failure: true

mypy:
  stage: lint
  extends: [.lint:backend:python, .rules:backend]
  script:
    - mypy backend
  allow_failure: true

backend:test:
  stage: test
  extends: [.python, .rules:backend:develop:mr]
  script:
    - cd $CI_PROJECT_DIR
    - chmod +x tests/unit/backend.sh
    - ./tests/unit/backend.sh

backend:build:
  stage: build
  extends: [.docker-build, .rules:backend:develop:mr]
  variables:
    COMPONENT: backend
  needs:
    - job: backend:test

backend:deploy:
  stage: deploy
  extends: .docker-promote
  variables:
    COMPONENT: backend
  resource_group: deploy-backend
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
      changes:
        - backend/**/*

tests/unit/backend.sh, frontend.sh..

단위 테스트 추후 구현 예정 입니다.

tests/integration/backend.sh, frontend.sh..

통합 테스트 추후 구현 예정 입니다.

tests/e2e/..

e2e 테스트 추후 구현 예정 입니다.

scripts/ci/..

CI job에서 사용하는 script 입니다.

'CI|CD Pipeline' 카테고리의 다른 글

GitLab CI/CD 트러블슈팅 모음(CI/CD)  (0) 2026.05.31
MinIO 구축 가이드(CI/CD)  (0) 2026.05.31
Harbor 설치 가이드(CI/CD)  (0) 2026.05.25
GitLab Variables 등록(CI/CD)  (0) 2026.05.24