CI/CD에 대해 알아보자.

이상민·2026년 9월 9일
post-thumbnail

(썸네일은 SSAFY AI 캐릭터 공모전을 위해 생성한 "꿀벌이" 입니다)

CI/CD란 무엇인가? GitHub Actions와 Docker로 자동 배포 이해하기

프로젝트를 개발하다 보면 처음에는 코드를 작성하고 직접 서버에 접속해서 실행하는 것만으로도 충분하다. 하지만 기능이 추가되고 팀원이 늘어나면 상황이 달라진다. 코드를 수정할 때마다 서버에 접속하고, 최신 코드를 내려받고, 기존 프로그램을 종료한 뒤 다시 실행하는 과정을 반복해야 한다.

이러한 반복 작업을 자동화하기 위해 등장한 개념이 CI/CD다.

이번 글에서는 CI/CD가 무엇인지 정리하고, 실제로 진행했던 RAG 기반 강의 정보 챗봇 프로젝트에서 GitHub Actions와 Docker를 이용해 CI/CD를 구성한 사례를 함께 살펴보려고 한다.



CI/CD란?

CI/CD는 소프트웨어의 통합, 테스트, 빌드, 배포 과정을 자동화하는 개발 방법론이다.

크게 CI와 CD로 나눌 수 있다.

CI - Continuous Integration

CI는 지속적 통합(Continuous Integration)을 의미한다.

여러 개발자가 각각 코드를 작성한 뒤 오랜 시간이 지나서 한 번에 합치는 것이 아니라, 작은 변경 사항을 자주 저장소에 통합하고 그때마다 코드가 정상적으로 동작하는지 확인하는 방식이다.

예를 들어 개발자가 GitHub에 코드를 push하거나 Pull Request를 생성하면 자동으로 다음과 같은 작업을 수행할 수 있다.

코드 Push -> GitHub와 같은 원격 저장소에 올리는 단계
   ↓
의존성 설치 -> 프로젝트 실행을 위한 외부 라이브러리 및 패키지를 설치하는 단계
   ↓
Build -> 실제 프로젝트를 실행하거나 배포할 수 있는 형태로 만드는 과정
		Java나 C++에서는 소스코드를 컴파일해서 실행 파일이나 바이트 코드로 만드는 단계
        프론트엔드에서는 여러 파일을 묶어 배포용 정적 파일을 만드는 단계
        Docker기반 프로젝트에서는 이미지로 만드는 단계
   ↓
Lint -> 코드를 실행하지 않고 잠재적 버그나 문법 오류, 코드 스타일 유지를 찾는 정적 분석 도구		
   ↓
Unit Test  -> 함수, 클래스, 메서드와 같은 작은 기능 단위로 독립적으로 테스트 하는 단계
   ↓
Integration Test -> 여러 컴포넌트를 연결했을 때 정상적으로 동작하는지 확인하는 단계
   ↓
성공 / 실패 확인

이렇게 하면 새로운 코드가 기존 기능을 망가뜨리는 문제를 비교적 빠르게 발견할 수 있다.

CI의 핵심은 단순히 자동 빌드하는 것이 아니라,

새로운 코드가 기존 시스템과 정상적으로 통합될 수 있는지를 지속적으로 검증하는 것

이라고 볼 수 있다.


CD - Continuous Delivery / Continuous Deployment

CD는 문맥에 따라 두 가지 의미로 사용된다.

Continuous Delivery는 배포 가능한 상태까지 자동화하되 실제 운영 환경에 배포하는 마지막 단계는 사람이 결정하는 방식이다.

Commit
 ↓
Test
 ↓
Build
 ↓
배포 가능한 상태
 ↓
[사람의 승인]
 ↓
Production

반면 Continuous Deployment는 테스트를 모두 통과하면 실제 운영 환경 배포까지 자동으로 수행한다.

Commit
 ↓
Test
 ↓
Build
 ↓
Deploy
 ↓
Production

즉 둘의 가장 큰 차이는 운영 환경으로의 최종 배포를 사람이 결정하는가, 자동으로 수행하는가이다.


CI/CD를 사용하는 이유

개발자가 직접 배포한다고 생각해보자.

git pull
docker build ...
docker stop ...
docker rm ...
docker run ...

코드가 변경될 때마다 이런 명령어를 반복해야 한다.

사람이 직접 수행하면 명령어를 빼먹거나 잘못된 버전을 배포하는 등의 실수가 발생할 수도 있다.

CI/CD를 적용하면 코드 변경을 기준으로 일정한 과정이 자동으로 실행된다.

대표적인 장점은 다음과 같다.

  • 반복 작업 감소: 빌드와 배포 작업을 자동으로 처리할 수 있다.
  • 배포 일관성 확보: 누가 배포하더라도 같은 절차가 실행된다.
  • 오류 조기 발견: 자동 테스트를 구성하면 문제가 있는 코드를 빠르게 발견할 수 있다.
  • 빠른 피드백: 코드 변경 후 빌드 및 테스트 결과를 바로 확인할 수 있다.
  • 배포 속도 향상: 기능 수정 후 운영 환경에 반영하는 시간이 줄어든다.

GitHub Actions

CI/CD를 구현하기 위해 사용할 수 있는 도구는 Jenkins, GitLab CI/CD, CircleCI 등 다양하다.

이번 프로젝트에서는 GitHub Actions를 사용했다.

GitHub Actions는 GitHub Repository에서 특정 이벤트가 발생했을 때 미리 정의한 작업을 자동으로 실행할 수 있는 기능이다.

예를 들어 다음과 같은 이벤트를 Trigger로 사용할 수 있다.

Push
Pull Request
Issue
Release
Schedule
Manual Trigger

CI/CD 설정은 일반적으로 Repository 내부의 다음 경로에 작성한다.

.github/
└── workflows/
    └── deploy.yml

YAML 파일 안에서 언제 실행할지, 어떤 환경에서 실행할지, 어떤 명령을 실행할지를 정의한다.


강의 정보 RAG 챗봇에서 적용한 CI/CD

진행했던 강의 정보 RAG 챗봇 프로젝트에서도 GitHub Actions를 이용해 자동 배포 환경을 구성했다.

Workflow의 시작 부분은 다음과 같은 형태다.

name: Build and Deploy RAG Chatbot Docker Container

on:
  push:
    branches:
      - main

즉,

main branch에 Push 발생
          ↓
GitHub Actions 실행

이라는 구조다. 실제 저장소에서도 main 브랜치 push를 workflow의 trigger로 사용하고 있다.


전체 배포 흐름

프로젝트의 CI/CD 흐름을 정리하면 다음과 같다.

Developer
   │
   │ git push
   ▼
GitHub Repository
   │
   │ main branch update
   ▼
GitHub Actions
   │
   ▼
Self-hosted Runner
   │
   ├─ 1. Repository Checkout
   │
   ├─ 2. Docker Image Build
   │
   ├─ 3. 기존 Container 제거
   │
   └─ 4. 새로운 Container 실행
            │
            ├─ FastAPI :8000
            └─ Streamlit :8501

이 프로젝트에서는 GitHub가 제공하는 기본 runner가 아니라 self-hosted runner를 사용했다. Workflow에도 runs-on: self-hosted가 설정되어 있다.


1. Repository Checkout

GitHub Actions가 시작되면 가장 먼저 최신 코드를 runner로 가져온다.

- name: Checkout repository
  uses: actions/checkout@v4

actions/checkout은 GitHub Actions에서 가장 많이 사용하는 Action 중 하나로, 현재 Repository의 코드를 runner 환경에 다운로드한다.

즉,

GitHub
   ↓
최신 Source Code
   ↓
Runner

가 된다.


2. Docker Image Build

최신 코드를 받은 후 Docker 이미지를 생성한다.

- name: Build Docker Image
  run: |
    docker build -t rag-chatbot:latest .

Dockerfile을 이용해 현재 프로젝트를 하나의 Docker 이미지로 패키징한다.

Source Code
+
Python Dependencies
+
Runtime Environment
        ↓
     Docker
        ↓
rag-chatbot:latest

Docker를 사용하는 중요한 이유 중 하나는 개발 환경과 배포 환경의 차이를 줄일 수 있다는 것이다.

서버마다 Python 버전이나 라이브러리 버전이 달라 발생하는

"내 컴퓨터에서는 되는데 서버에서는 안 되는데?"

같은 문제를 줄일 수 있다.


3. 기존 Container 제거

새로운 버전을 실행하기 전에 기존 컨테이너를 제거한다.

- name: Stop and Remove Old Container
  run: |
    docker rm -f rag-chatbot-app
  continue-on-error: true

여기서 -f 옵션은 실행 중인 컨테이너를 강제로 종료한 뒤 삭제한다.

또한 continue-on-error: true를 설정했다.

첫 배포라면 rag-chatbot-app이라는 컨테이너 자체가 존재하지 않을 수 있다. 이때 삭제 명령어가 실패하더라도 전체 workflow를 중단하지 않고 다음 단계로 넘어가도록 만든 것이다.


4. 새로운 Container 실행

마지막으로 새롭게 생성한 Docker 이미지를 이용해 애플리케이션을 실행한다.

docker run -d --rm \
-p 8000:8000 \
-p 8501:8501 \
--name rag-chatbot-app \
--gpus all \
rag-chatbot:latest

여기서 각 옵션을 보면 다음과 같다.

-d
→ Background 실행

--rm
→ Container 종료 시 자동 삭제

-p 8000:8000
→ FastAPI Port

-p 8501:8501
→ Streamlit Port

--name rag-chatbot-app
→ Container 이름

--gpus all
→ GPU 사용

따라서 배포가 끝나면 Docker Container 내부에서 FastAPI Backend와 Streamlit Frontend가 실행되는 구조다. 실제 workflow 역시 8000, 8501 포트를 노출하고 GPU를 활성화해 새로운 컨테이너를 실행한다.


Self-hosted Runner란?

GitHub Actions는 기본적으로 GitHub가 제공하는 runner를 사용할 수 있다.

예를 들어:

runs-on: ubuntu-latest

라고 작성하면 GitHub가 제공하는 Linux 환경에서 workflow가 실행된다.

반면 이번 프로젝트에서는:

runs-on: self-hosted

를 사용했다.

Self-hosted runner는 내가 직접 준비한 컴퓨터나 서버를 GitHub Actions의 실행 환경으로 등록하는 방식이다.

구조적으로는 다음과 같다.

GitHub Actions
     │
     │ Job 전달
     ▼
내가 관리하는 Server
     │
     ├─ Docker
     ├─ GPU
     ├─ Model
     └─ Application

특히 GPU나 특정 소프트웨어 환경이 필요한 프로젝트에서는 self-hosted runner가 유용하다.

이번 RAG 챗봇도 LLM과 embedding 모델을 사용하기 때문에 Docker 실행 시 --gpus all 옵션을 사용하도록 구성되어 있다.


그렇다면 이 프로젝트는 완전한 CI/CD인가?

여기서 한 가지 짚고 넘어갈 부분이 있다.

현재 프로젝트의 workflow에는 다음과 같은 자동 테스트 단계가 존재하지 않는다.

pytest
lint
unit test
integration test
RAG evaluation

현재 구조는 사실상:

Push
 ↓
Build
 ↓
Deploy

에 가깝다.

따라서 CD는 비교적 명확하게 구현되어 있지만, CI의 검증 과정은 아직 단순한 상태라고 보는 것이 정확하다.

보통 보다 완성된 CI/CD라면 다음과 같은 구조가 된다.

Pull Request
      ↓
Lint
      ↓
Unit Test
      ↓
Integration Test
      ↓
RAG Evaluation
      ↓
Merge
      ↓
main Push
      ↓
Docker Build
      ↓
Deploy

이렇게 구성하면 잘못된 코드가 운영 서버에 바로 반영되는 문제도 방지할 수 있다.


RAG 프로젝트라면 CI에서 무엇을 테스트할 수 있을까?

일반적인 웹 애플리케이션에서는 unit test나 API test를 수행하지만, RAG 시스템은 추가로 검색 성능 자체도 CI 지표로 사용할 수 있다.

현재 진행하고 있는 Retriever 평가와 연결하면 예를 들어 다음과 같은 기준을 둘 수 있다.

PR 생성
  ↓
Retriever Evaluation
  ↓
Recall@5 측정
  ↓
기준 성능 이상?
  │
  ├─ YES → CI PASS
  │
  └─ NO  → CI FAIL

예를 들어 기존 모델의 Recall@5 = 0.82라면 새 코드가:

Recall@5 >= 0.80

을 만족하는지 확인하는 방식이다.

이렇게 하면 단순히 코드가 실행되는가만 검사하는 것이 아니라,

새로운 코드로 인해 RAG 검색 성능이 떨어지지 않았는가?

까지 자동으로 검사할 수 있다.

개인적으로 RAG 프로젝트에서 CI/CD를 발전시킨다면 이 부분이 가장 재미있는 지점이라고 생각한다.


정리

CI/CD를 공부하면서 가장 중요하게 느낀 점은 CI/CD가 단순히 '자동 배포하는 기술'은 아니라는 것이다.

CI는 지속적으로 코드를 통합하면서 문제가 없는지 검증하는 과정이고, CD는 검증된 소프트웨어를 언제든 안정적으로 배포할 수 있도록 만드는 과정이다.

이번 강의 정보 RAG 챗봇에서는:

GitHub
+
GitHub Actions
+
Self-hosted Runner
+
Docker

를 이용해 main 브랜치에 코드가 push되면 자동으로 새로운 Docker 이미지를 생성하고 기존 컨테이너를 교체하는 배포 환경을 구성했다.

현재는 자동 Build + Deploy 중심의 파이프라인이지만, 앞으로 unit test와 Retriever 평가를 추가한다면 다음과 같이 발전시킬 수 있다.

Code
 ↓
Test
 ↓
RAG Evaluation
 ↓
Build
 ↓
Deploy
 ↓
Monitoring

결국 CI/CD의 목적은 단순히 개발자의 일을 줄이는 것이 아니다.

코드 변경부터 서비스 배포까지의 과정을 반복 가능하고 신뢰할 수 있는 프로세스로 만드는 것이 CI/CD의 핵심이라고 볼 수 있다.

profile
AI개발자 지망생

2개의 댓글

comment-user-thumbnail
2026년 9월 17일

글 잘 읽었습니다! CI/CD를 아직 제대로 사용해본 적이 없어서 궁금한 점이 있는데요. main 브랜치에 push되면 바로 자동 배포되도록 구성하면 혹시 문제가 있는 코드도 바로 운영 서버에 반영될 수 있을 것 같은데, 실제 프로젝트에서는 이런 문제를 보통 어떤 방식으로 방지하는지 궁금합니다!

1개의 답글