1. 들어가며: 백그라운드에서 배포 로직 실행하기
안녕하세요! 지난 3편에서는 스테이징 서버 환경을 설정하고, GitHub Webhook 요청을 받아 Secret을 검증하는 FastAPI 웹훅 서버의 기본 골격을 함께 만들었습니다. 이 과정에서 main.py
파일 내에 handle_deploy
및 should_rebuild
함수의 코드를 미리 포함하여, 실제 배포 로직의 핵심 아이디어를 엿볼 수 있었습니다.
이번 4편에서는 3편에서 간략히 다뤘던 배포 핸들러 로직의 동작 방식을 다시 한번 명확히 짚어보고, 이 FastAPI 웹훅 서버가 서버 재부팅 시에도 자동으로 실행되고 안정적으로 운영될 수 있도록 Systemd 서비스로 등록하는 방법에 집중하여 설명할 것입니다. 이제 여러분의 자동 배포 시스템이 한층 더 견고해질 준비를 마쳤습니다!
이전 글을 못 보신 분들은 이전 글들을 먼저 보시고 오시길 추천합니다.
③ 스테이징 서버 환경 설정 및 FastAPI 웹훅 서버 기초 구축
2. 배포 핸들러 (handle_deploy
) 로직 다시 살펴보기
3편에서 작성했던 main.py
의 handle_deploy
함수는 GitHub 웹훅 요청이 들어왔을 때 백그라운드에서 실행되는 실제 배포 작업을 담당합니다. 이 함수는 다음과 같은 핵심적인 역할을 수행합니다.
3편의 예제 코드를 함께 보시면서 아래의 가이드를 보시길 권장합니다.
2.1. 다중 프로젝트 관리 및 환경 변수 설정
여러 개의 GitHub 저장소(프로젝트)를 하나의 웹훅 서버로 관리하려면, 각 저장소가 서버 내의 어디에 위치하는지 알려줘야 합니다. handle_deploy
함수는 이를 위해 repo_paths
딕셔너리를 사용하며, 이 값들은 서버의 환경 변수에서 읽어오도록 설계되어 있습니다.
예시 코드에서 SAMPLE_PROJECT_1_PATH
와 같은 환경 변수들이 등장했습니다. 여러분은 스테이징 서버의 .env
파일 (이 파일은 웹훅 서버의 main.py
와 같은 디렉토리에 위치해야 합니다)이나 systemd
서비스 파일 내에 다음과 같이 각 프로젝트의 실제 경로를 설정해야 합니다.
코드 스니펫
# ~/projects/webhook_server/.env
# GitHub Webhook Secret (3편에서 설정)
GITHUB_WEBHOOK_SECRET="your_github_webhook_secret_value"
# 각 프로젝트의 서버 내 실제 경로
SAMPLE_PROJECT_1_PATH="/var/www/my-project1"
SAMPLE_PROJECT_2_PATH="/home/user/another-project"
SAMPLE_PROJECT_3_PATH="/opt/third-project"
handle_deploy
함수는 웹훅 페이로드에서 전달받은 레포지토리 이름을 이용해 repo_paths
딕셔너리에서 해당 프로젝트의 경로를 찾습니다. 만약 매핑된 경로가 없으면 Unknown repository
경고를 남기고 종료됩니다.
또한, 각 프로젝트별 .env
파일(예: /var/www/my-project1/.env
)에서 DEBUG
나 COLOR
와 같은 추가 설정값을 읽어와 Docker Compose 파일 선택 (docker-compose.dev.yml
또는 docker-compose.prod.yml
)이나 Docker 프로젝트 이름 설정 등에 활용하는 유연한 로직도 포함되어 있습니다. 이는 프로젝트의 특성에 맞춰 배포 방식을 커스터마이징하는 데 매우 유용합니다.
2.2. Docker 이미지 재빌드 결정 (should_rebuild
)
배포 시 매번 Docker 이미지를 재빌드하는 것은 시간이 많이 걸리고 비효율적입니다. should_rebuild
함수는 Git의 diff
명령어를 활용하여 Dockerfile
, requirements.txt
또는 .env
파일 등 Docker 이미지 빌드에 영향을 주는 핵심 파일이 변경되었는지를 감지합니다.
이 함수는 git diff --name-only HEAD~1
명령을 통해 이전 커밋과의 변경 사항을 확인하고, 변경된 파일 목록에 미리 정의된 trigger_files
(예: Dockerfile
, requirements.txt
, .env
, REBUILD_TRIGGER
등)가 포함되어 있다면 True
를 반환하여 이미지 재빌드를 지시합니다.
특히 REBUILD_TRIGGER
파일은 비어 있는 파일이라도 존재하면 강제로 재빌드를 수행하도록 하는 유용한 트릭입니다. 이 파일을 서버에 수동으로 생성한 뒤 git pull
이 아닌 다른 이유로 이미지 재빌드가 필요한 경우에 활용할 수 있습니다. should_rebuild
함수는 이 파일을 감지하면 자동으로 삭제하여 다음 배포 시에는 불필요하게 재빌드가 일어나지 않도록 합니다.
2.3. Git과 Docker Compose 명령어 실행
handle_deploy
함수는 Python의 subprocess
모듈을 사용하여 서버의 git
및 docker compose
명령어를 실행합니다.
-
subprocess.run(["git", "-C", repo_path, "pull"], check=True)
:check=True
옵션은 Git 명령 실행 중 오류가 발생하면subprocess.CalledProcessError
를 발생시켜 파이썬 코드에서 이를 감지하고 적절히 에러를 로깅하도록 합니다.-C
옵션은 지정된 디렉토리에서 Git 명령을 실행하도록 합니다. -
subprocess.run(["docker", "compose", "-p", project_name, "-f", compose_file, "up", "-d", "--build"], check=True)
:should_rebuild
함수의 결과에 따라up -d
만 하거나--build
옵션을 추가하여 이미지 재빌드를 수행합니다.-p
옵션으로 Docker Compose 프로젝트 이름을 지정하여 여러 프로젝트가 서로 충돌하지 않도록 합니다.
이러한 로직을 통해 웹훅 요청 한 번으로 프로젝트의 최신 코드를 가져오고, 필요한 경우에만 이미지를 재빌드하여 효율적으로 서비스를 업데이트할 수 있습니다.
3. FastAPI 웹훅 서버, Systemd 서비스로 운영하기
지금까지는 uvicorn main:app --reload
명령어로 웹훅 서버를 수동으로 실행했습니다. 하지만 서버가 재부팅되면 이 프로세스는 사라지고, 터미널 세션이 끊어져도 서비스가 중단됩니다. 이를 방지하고 안정적인 운영을 위해, FastAPI 웹훅 서버를 Systemd 서비스로 등록하여 관리해야 합니다.
3.1. 왜 Systemd를 사용해야 하는가?
-
자동 시작: 서버 재부팅 시에도 자동으로 웹훅 서비스가 시작됩니다.
-
지속적인 실행: 터미널 세션에 종속되지 않고 백그라운드에서 항상 실행됩니다.
-
쉬운 관리:
systemctl
명령어를 통해 서비스의 시작, 중지, 재시작, 상태 확인, 로그 보기 등을 간편하게 할 수 있습니다. -
자원 효율성: 2편에서 설명했듯이, 웹훅 서버 자체는 가벼운 파이썬 앱으로 Systemd를 통해 실행하고, Git이나 Docker와 같은 무거운 도구는 시스템에 설치된 것을 직접 활용함으로써 불필요한 Docker in Docker 설정이나 컨테이너 크기 증가를 피할 수 있습니다.
3.2. Systemd 서비스 파일(*.service
) 작성
Systemd 서비스는 .service
파일을 통해 정의됩니다. 이 파일은 /etc/systemd/system/
디렉토리에 위치해야 합니다. 우리는 github-webhook-deployer.service
라는 이름으로 파일을 생성해 보겠습니다. 저는 설명하기 쉽게 긴 이름을 정했지만, 여러분은 적당히 간결하면서도 알기쉬운 이름을 정하시면 됩니다.
# 파일 생성 (sudo 권한 필요)
sudo nano /etc/systemd/system/github-webhook-deployer.service
파일 내용:
# /etc/systemd/system/github-webhook-deployer.service
[Unit]
Description=GitHub Webhook Deployer Service
After=network.target # 네트워크가 활성화된 후에 이 서비스를 시작합니다.
[Service]
User=your_username # 이 서비스를 실행할 사용자 계정 (예: ubuntu, your_user)
Group=your_username # 이 서비스를 실행할 그룹 (예: ubuntu, your_user)
WorkingDirectory=/home/your_username/projects/webhook_server # FastAPI 앱이 있는 디렉토리
# 환경 변수 설정:
# 1. FastAPI 앱 자체에서 .env 파일을 로드하므로, GITHUB_WEBHOOK_SECRET 등 앱 내부에서 사용하는 변수는
# 반드시 EnvironmentFile로 로드할 필요는 없습니다. 하지만 systemd 수준에서 모든 환경을 일관되게
# 관리하고 싶다면 EnvironmentFile= 경로를 유지할 수 있습니다.
# EnvironmentFile=/home/your_username/projects/webhook_server/.env
# 2. subprocess.run()으로 git, docker, docker compose 등의 시스템 명령어를 호출하려면
# PATH 환경 변수에 해당 명령어들이 위치한 경로를 명시적으로 추가해야 합니다.
# 아래 예시 PATH는 일반적인 Linux 시스템 경로와 파이썬 가상 환경의 bin 경로를 포함합니다.
# 'your_username'과 'venv' 경로는 실제 환경에 맞춰 수정해야 합니다.
# 정확한 PATH는 서버의 .bashrc 파일이나 'echo $PATH' 명령으로 확인하여 필요한 경로들을 포함시키세요.
Environment="PATH=/home/your_username/projects/webhook_server/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/home/your_username/projects/webhook_server/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 # 가상환경의 uvicorn 경로 사용
Restart=always # 서비스가 종료되면 항상 재시작합니다.
StandardOutput=journal # 표준 출력을 Systemd 저널로 보냅니다.
StandardError=journal # 표준 에러를 Systemd 저널로 보냅니다.
[Install]
WantedBy=multi-user.target # 멀티유저 모드 (일반적인 서버 부팅 상태)에서 서비스가 시작되도록 합니다.
설명:
-
[Unit]
섹션: 서비스에 대한 일반적인 정보(설명, 의존성)를 정의합니다.After=network.target
은 네트워크 서비스가 시작된 후에 이 서비스를 시작하도록 합니다. -
[Service]
섹션: 서비스가 어떻게 실행될지 정의합니다.-
User
,Group
: 서비스를 실행할 사용자 및 그룹입니다. 반드시 Docker 권한이 있는 사용자(예:sudo usermod -aG docker your_username
으로 설정한 사용자)로 설정해야 합니다. -
WorkingDirectory
: FastAPI 앱의main.py
파일이 위치한 디렉토리입니다. -
Environment="PATH=..."
: 이 부분이 핵심입니다.subprocess.run()
으로git
,docker
,docker compose
등 외부 명령어를 호출할 때,systemd
환경에서는PATH
환경 변수가.bashrc
등에서 설정된 것과 다를 수 있습니다. 따라서 가상 환경의uvicorn
실행 경로 (/home/your_username/projects/webhook_server/venv/bin
)와 시스템의 기본 실행 파일 경로 (/usr/local/bin
,/usr/bin
등)를 명시적으로 지정해 줘야 합니다. -
EnvironmentFile
(선택 사항): FastAPI 앱 자체에서python-dotenv
를 통해.env
파일을 로드하므로, 앱 내부에서 사용하는 환경 변수(예:GITHUB_WEBHOOK_SECRET
,SAMPLE_PROJECT_N_PATH
등)는 이 설정을 통하지 않아도 됩니다. 하지만systemd
가 서비스를 시작하기 전에 이러한 변수들을 명시적으로 로드하도록 하려면 (ExecStart
가 실행하는uvicorn
프로세스 및 그 하위 프로세스에 일관된 환경을 제공하려면)EnvironmentFile
을 사용하는 것도 유효한 방법입니다. 사용자의 환경 및 선호도에 따라 주석 처리하거나 유지할 수 있습니다. -
ExecStart
: 서비스를 시작하는 실제 명령입니다. 가상 환경의uvicorn
실행 파일 경로를 정확히 지정해야 합니다.--host 0.0.0.0 --port 8000
은 모든 IP에서 8000번 포트로 들어오는 요청을 수신하도록 합니다. -
Restart=always
: 서비스가 어떤 이유로든 종료되면 Systemd가 자동으로 재시작을 시도합니다. -
StandardOutput=journal
,StandardError=journal
: 서비스의 모든 출력 및 에러를 Systemd의 통합 로그 시스템인journalctl
로 보냅니다.
-
-
[Install]
섹션: 서비스가 어떤 목표(target)에 의해 활성화될지 정의합니다.WantedBy=multi-user.target
은 서버가 일반적인 멀티유저 상태로 부팅될 때 이 서비스가 자동으로 시작되도록 합니다.
3.3. Systemd 서비스 등록 및 시작
서비스 파일을 생성한 후에는 Systemd가 해당 파일을 인식하도록 하고, 서비스를 시작합니다.
# Systemd에게 새 서비스 파일을 알립니다.
sudo systemctl daemon-reload
# 서비스가 부팅 시 자동으로 시작되도록 설정합니다.
sudo systemctl enable github-webhook-deployer.service
# 서비스를 시작합니다.
sudo systemctl start github-webhook-deployer.service
# 서비스 상태를 확인합니다. 'active (running)'으로 표시되어야 합니다.
sudo systemctl status github-webhook-deployer.service
이제 여러분의 FastAPI 웹훅 서버는 서버가 재부팅되어도 자동으로 실행되며, 백그라운드에서 안정적으로 웹훅 요청을 기다리게 됩니다.
4. FastAPI 웹훅 서비스 모니터링 및 디버깅
Systemd 서비스를 사용하면 로그를 확인하고 문제를 진단하기가 훨씬 쉬워집니다.
- 서비스 상태 확인:
sudo systemctl status github-webhook-deployer.service
이 명령은 서비스의 현재 상태, 마지막 실행 시간, 프로세스 ID 등을 보여줍니다.
- 실시간 로그 확인:
sudo journalctl -u github-webhook-deployer.service -f
-u
옵션은 특정 서비스의 로그를, -f
옵션은 실시간으로 새로운 로그를 계속 출력합니다. 웹훅 요청이 들어올 때마다 main.py
에서 설정한 로깅 메시지들이 여기에 나타날 것입니다.
만약 서비스가 시작되지 않거나 예상대로 작동하지 않는다면, journalctl
로그를 가장 먼저 확인하여 오류 메시지를 찾아야 합니다.
5. 마무리하며: 다음 편 예고
이번 4편에서는 3편에서 구현했던 배포 핸들러 로직의 동작 방식을 다시 한번 명확히 하고, 가장 중요한 단계 중 하나인 FastAPI 웹훅 서버를 Systemd 서비스로 등록하여 안정적으로 운영하는 방법을 알아보았습니다. 이제 여러분의 웹훅 서버는 서버 재부팅에도 끄떡없이 작동할 것입니다.
다음 5편에서는 마지막 퍼즐 조각들을 맞춰 시스템을 완성할 것입니다. Nginx를 리버스 프록시로 설정하여 웹훅 서버를 외부에 안전하게 노출하고, HTTPS를 적용하여 보안을 강화하며, 최종적으로 GitHub 저장소에 웹훅을 연동하여 실제 자동 배포를 테스트하는 과정을 다룰 것입니다. 기대해주세요!
댓글이 없습니다.