안녕하세요. 오랜만에 인사드립니다. 😊
오늘은 GitHub Actions의 Self-Hosted Runner를 활용하여 완전 자동화된 배포 시스템 구축 과정을 공유하고자 합니다. 기존 인프라를 활용하여 코드 배포 자동화를 달성하는 방법에 대해 다뤄보겠습니다.

🔹 개요
이전까지는 코드 변경이 있을 때마다 서버에 SSH 접속하여 수동으로 빌드-배포하는 방식을 사용해왔으나 최근 프로젝트 인원들과 GitHub Oranizations을 활용하여 공유된 코드들을 개발/관리하고 있었습니다.
하지만 개발이 진행되면서 코드 관리나 배포 과정에서 몇 가지 문제점들이 발견되었습니다.
해결이 필요했던 문제들
빌드 환경의 일관성 부족
- 개발자 로컬 환경, 개발 서버, 운영 서버에 각각 코드가 존재
- 어느 환경에 어떤 버전의 코드가 배포되어 있는지 파악이 어려움
- 코드가 분산이 되어 있기 때문에 관리 포인트가 증가
배포 프로세스의 표준화 부재
- 배포 실패 시 롤백 절차가 명확하지 않음
이러한 문제들을 해결하기 위해 GitHub Actions와 Self-Hosted Runner를 활용한 CI/CD 파이프라인을 구축하게 되었습니다.
🔹 GitHub Runner 란?
GitHub Actions는 GitHub에서 제공하는 CI/CD 플랫폼입니다. 코드 저장소에서 직접 소프트웨어 개발 워크플로우를 자동화할 수 있게 해줍니다. 이 플랫폼의 핵심 구성 요소가 바로 Runner입니다.
Runner는 GitHub Actions 워크플로우를 실행하는 서버로, 크게 두 가지 유형으로 나뉩니다:
1. GitHub-hosted Runners
- GitHub에서 제공하는 가상 머신
- Ubuntu, Windows, macOS 환경 제공
- 매 작업마다 새로운 가상 머신 인스턴스 생성
- 무료 사용량 제한 있음 (월 2,000분)
2. Self-hosted Runners ⭐
- 사용자가 직접 운영하는 서버
- 자체 인프라에서 실행
- 커스텀 환경 구성 가능
- 지속적인 상태 유지 가능
- 무제한 사용 가능
Self-hosted Runner를 선택한 이유:
프로젝트에서 Self-Hosted Runner를 선택한 이유는 다음과 같습니다.
- 비용 절약 : 구축한 가상의 리눅스 서버에 Runner를 구성하여 비용 없이 무제한 빌드가 가능합니다.
- 성능 최적화 : 전용 하드웨어 리소스로 빠른 빌드 속도 확보
- 커스텀 환경 : 프로젝터 전용 환경으로 설정을 자유롭게 구성 가능
🔹 시스템 아키텍처
전체 워크플로우

주요 구성 요소
1. Self-hosted Runner 서버
- 역할: GitHub Actions 워크플로우 실행
- 특징:
- Go 빌드 환경 구축
- 빌드 캐시 및 모듈 캐시 유지
- SSH를 통한 운영서버 접근 권한
2. 운영서버
- 역할: 실제 백엔드 서비스 운영
- 특징:
- 소스 코드 없음: 기존의 Go 소스 코드는 백업 후 삭제
- 바이너리 + 구성 파일: 컴파일된 바이너리와 config.yaml 등 실행에 필요한 파일들만 보유
- 간소화된 환경: 코드 관리 포인트 제거로 서버 환경 단순화
- 서비스 재실행 bash 스크립트를 통한 백엔드 프로세스 재시작
🔹 워크플로우 구성
최적화 전략의 핵심
CI/CD 파이프라인의 효율성을 높이기 위해서 세 가지 단계로 워크플로우를 구성하였습니다.:
1단계: Quick Check (변경사항 감지)
모든 커밋에 대해 빌드를 수행하면 불필요한 리소스 낭비가 발생하였습니다. 따라서, 실제 Go 코드가 변경되었을 때만 빌드를 진행하도록 구성했습니다.
name: Quick Check (Change Detection)
on:
push:
branches: [main]
jobs:
quick-check:
runs-on: self-hosted
outputs:
should_build: ${{ steps.changes.outputs.backend }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
backend:
- 'cmd/**'
- 'internal/**'
- 'pkg/**'
- 'go.mod'
- 'go.sum'
핵심 포인트:
- paths-filter 액션을 사용하여 프로젝트 사용 경로(지정한 특정 경로 - cmd/, internal, ..., go.sum)의 파일 변경 감지
- Go 소스 코드와 의존성 파일만 체크하여 불필요한 빌드 방지
- 빌드 여부를 다음 잡에 전달
2단계: Optimized Build (최적화된 빌드)
Go 언어의 특성을 활용하여 빌드 속도를 극적으로 개선했습니다.
name: Optimized Build
on:
workflow_run:
workflows: ["Quick Check (Change Detection)"]
types: [completed]
jobs:
build:
runs-on: self-hosted
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@v4
- name: Setup Clean Go Cache
run: |
# 매 빌드마다 새로운 캐시 디렉토리 생성
CLEAN_CACHE_DIR="/tmp/go-cache-$(date +%s)"
CLEAN_MOD_DIR="/tmp/go-mod-$(date +%s)"
mkdir -p "$CLEAN_CACHE_DIR" "$CLEAN_MOD_DIR"
echo "GOCACHE=$CLEAN_CACHE_DIR" >> $GITHUB_ENV
echo "GOMODCACHE=$CLEAN_MOD_DIR" >> $GITHUB_ENV
- name: Build
run: |
cd cmd/dnote
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -o project-backend -trimpath \
-ldflags="-s -w" .
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: project-backend
path: cmd/project/project-backend
최적화 기법:
- 매 빌드마다 독립된 캐시 디렉토리 사용으로 캐시 충돌 방지
- trimpath로 빌드 경로 정보 제거하여 바이너리 크기 감소
- -ldflags="-s -w"로 디버그 정보 제거하여 최종 바이너리 크기 최소화
3단계: Production Deploy (자동 배포)
빌드된 바이너리를 운영 서버에 자동으로 배포합니다.
name: Production Deploy
on:
workflow_run:
workflows: ["Optimized Build"]
types: [completed]
jobs:
deploy:
runs-on: self-hosted
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: dnote-backend
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -p 22 192.168.0.10 >> ~/.ssh/known_hosts
- name: Deploy to Production
run: |
# 바이너리 전송
scp -P 22 project-backend \
"${{ secrets.SSH_USER }}@192.168.0.10:/opt/project/"
# 서비스 재시작
ssh -p 22 "${{ secrets.SSH_USER }}@192.168.0.10" << 'EOF'
# ChangeStream 토큰 초기화
mongo --quiet --eval 'db.getSiblingDB("searchnote").resume_tokens.deleteMany({})'
# 서비스 재시작
sudo systemctl restart project-backend.service
# 상태 확인 (별도 세션에서)
sleep 2
sudo systemctl status project-backend.service --no-pager
EOF
배포 방식의 핵심:
- 바이너리 배포: 소스 코드가 아닌 컴파일된 바이너리만 서버에 전송
- 빠른 배포: 빌드는 Runner에서 수행, 서버는 바이너리만 받아서 실행
- 안전한 재시작: bash 스크립트를 통한 백엔드 프로세스 재시작
- ChangeStream Resume Token 초기화로 MongoDB 연결 문제 방지
- 배포 후 자동 상태 확인
🔹 Self-hosted Runner 설정
Runner 설치 및 등록
GitHub 저장소의 Settings > Actions > Runners 메뉴에서 새로운 Self-hosted Runner를 추가할 수 있습니다.
Runner를 등록할 OS(Windows, Linux, MacOS) 별 구성 스크립트를 제공해주었기 때문에 쉽게 구성할 수 있었습니다.
환경 설정
Runner 서버에 필요한 도구들을 설치합니다:
# Go 설치 (최신 버전)
wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
# 환경변수 설정
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc
# Git 설치
sudo apt-get update
sudo apt-get install git
# SSH 클라이언트 설정 (배포용)
sudo apt-get install openssh-client
🔹 보안 설정
GitHub Secrets 관리
민감한 정보는 GitHub Secrets에 저장하여 안전하게 관리해야 합니다.
저장소 Settings > Secrets and variables > Actions에서 다음 시크릿을 추가:
SSH_PRIVATE_KEY # 운영서버 접근용 SSH 개인키
SSH_USER # 운영서버 사용자명
GITHUB_TOKEN # 아티팩트 다운로드용 (기본 제공됨)
🔹 트러블슈팅
작업 간 발생했던 문제들
1. Go 모듈 캐시 충돌
문제: 이전 빌드의 캐시가 새로운 의존성과 충돌하여 빌드 실패
해결: 매 빌드마다 새로운 캐시 디렉토리를 생성하여 깨끗한 환경에서 빌드하도록 개선
CLEAN_CACHE_DIR="/tmp/go-cache-$(date +%s)"
CLEAN_MOD_DIR="/tmp/go-mod-$(date +%s)"
2. SSH 연결 실패
문제: 운영서버 SSH 연결이 간헐적으로 실패
해결:
- SSH 키 권한을 600으로 명확히 설정
- known_hosts에 서버 정보를 사전 등록하여 호스트 확인 단계 생략
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -p 11122 [운영서버주소] >> ~/.ssh/known_hosts
3. 서비스 재시작 실패
문제: 배포 후 MongoDB ChangeStream 관련 에러로 서비스 시작 실패
해결:
- 서비스 재시작 전에 ChangeStream 토큰 파일 삭제
- restart.sh 스크립트로 안정적인 재시작 프로세스 구현
# ChangeStream 토큰 파일 삭제
rm -f internal/sync/*_resume_token.bin
# 서비스 재시작
./restart.sh
🔹 성과 및 개선점
CI/CD 파이프라인 구축을 통해 다음과 같은 성과를 얻을 수 있었습니다:
코드 버전 관리 개선
- 서버 환경이 깔끔해지고 관리 포인트 감소
- 어떤 커밋이 어느 서버에 배포되었는지 명확히 추적
- 로그 시스템도 바이너리 실행 환경에 최적화
배포 안정성 향상
- 표준화된 배포 프로세스로 휴먼 에러 제거
- Runner에서 일관된 빌드 환경 보장
- 배포 실패 시 자동 감지
개발 효율성 증대
- main 브랜치에 푸시하면 자동으로 배포
- 배포 과정 모니터링 가능
향후 개선 계획
현재 시스템이 잘 작동하고 있지만, 아래 항목들에 대한 개선을 계획중입니다.
배포 승인 프로세스
- Pull Request 기반 배포 승인 시스템 구축 (개선 중)
- 자동 빌드 후 수동 승인을 통한 배포
- 배포 전 검토 단계 추가로 안정성 강화
- 테스트 자동화
- 단위 테스트 추가
- 통합 테스트 자동 실행
- 테스트 실패 시 배포 중단
- 알림 시스템
- Slack 연동
- 배포 상태 실시간 알림
- 에러 발생 시 즉시 알림
🔹 마치며
Self-hosted GitHub Runner를 활용한 CI/CD 파이프라인 구축은 중소규모 프로젝트에서도 충분히 적용 가능한 방법입니다.
특히 이미 서버 인프라를 보유하고 있다면, GitHub-hosted Runner의 시간 제한 없이 무제한으로 자동화를 활용할 수 있다는 점이 큰 장점이라 생각합니다.
처음 동작하기까지의 과정이 다소 복잡하였지만, 한 번 구축해두면 그 이후로는 정말 편리하게 사용할 수 있었습니다.
제가 원했던 코드 작성에만 집중할 수 있는 환경을 만들어준다는 점에서, CI/CD 파이프라인 구축은 충분히 투자할 가치가 있는 작업이라고 생각합니다.
이 글이 CI/CD 파이프라인 구축을 고민하는 분들에게 도움이 되었으면 좋겠습니다.
'기술 공부 > DevOps' 카테고리의 다른 글
| VirtualBox 새 파티션 LVM 디스크 확장 (0) | 2025.12.14 |
|---|---|
| Wazuh SIEM에서 Syslog 연동 실습 (2편) - Rule 작성부터 대시보드까지 (1) | 2025.12.14 |
| Wazuh SIEM에서 Syslog 연동 실습 (1편) - 삽질부터 첫 성공까지 (0) | 2025.12.07 |
| SIEM이란? Wazuh 오픈소스 SIEM 구축하기 (VirtualBox 환경) (0) | 2025.11.25 |
| Linux Git 완전 설치 및 다중 사용자 설정 가이드 (0) | 2025.09.28 |