2026년 10월 28일 수요일
미단재

AI개발 게으름이 개발자를 움직이게 만든다

작성자 웅파 작성일 2026-10-28 12:10:18 조회수 10

요약: main에 머지할 때마다 우분투 서버에 접속해 git pull을 치던 수동 배포가 귀찮아서 자동화를 고민했다. GitHub Actions + SSH도 가능했지만, 서버가 스스로 확인하는 cron 폴링 방식을 골랐다. flock, --ff-only, composer 변경 감지로 안전장치를 갖춘 스크립트 한 개로 정리한 과정을 남긴다.

매번 SSH로 들어가서 git pull

jblove.net은 저장소를 클론해서 운영하고 있다. 배포 절차는 단순했다. PR을 main에 머지하고, 우분투 콘솔에 접속해서 git pull을 친다.

단순하지만 귀찮다. 머지할 때마다 터미널을 열고 접속해서 명령을 치는 일이 반복된다. 더 문제는 git pull만으로는 배포가 끝나지 않는다는 점이다. 의존성이 바뀌었으면 composer install, 테이블이 바뀌었으면 마이그레이션, 그리고 캐시 삭제까지 해야 한다. 하나라도 빼먹으면 사이트가 이상하게 동작하고, 원인을 찾느라 시간을 쓴다.

개발자는 같은 일을 세 번 하면 짜증이 난다. 이 짜증이 자동화를 시작하게 만든 동력이었다.

선택지 비교

자동화 방법은 크게 네 가지로 정리했다.

방식 장점 단점
GitHub Actions + SSH 배포 로그를 Actions에서 확인 SSH 키를 Secrets에 관리, 접속 가능한 호스트 필요
서버 cron 폴링 인바운드 접근 불필요, 설정이 가장 단순 최대 1분 지연, 알림이 약함
self-hosted runner 포트 개방 불필요 서버에 runner가 상주
webhook 수신기 즉시 반영 공개 엔드포인트와 서명 검증을 직접 관리

SSH는 열려 있었으니 Actions도 가능했다. 그래도 cron을 골랐다. 이유는 간단하다. 서버 밖에 비밀키를 두고 싶지 않았고, 머지 후 1분 안에 반영되면 충분했다. 가장 덜 만들고 가장 덜 깨지는 쪽이 cron이었다.

배포 스크립트

서버에 deploy-pull.sh를 하나 두고, cron이 1분마다 실행하게 했다.

#!/usr/bin/env bash
set -euo pipefail

APP=/path/to/jblove
LOG=$APP/writable/logs/deploy.log
cd "$APP"

# 동시 실행 방지
exec 9>/tmp/jblove-deploy.lock
flock -n 9 || exit 0

git fetch -q origin main
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/main)
[ "$LOCAL" = "$REMOTE" ] && exit 0

{
echo "[$(date '+%F %T')] deploy $LOCAL -> $REMOTE"
git merge --ff-only origin/main
# composer 파일이 바뀐 경우만 설치
if ! git diff --quiet "$LOCAL" "$REMOTE" -- composer.json composer.lock; then
composer install --no-dev --optimize-autoloader --no-interaction
fi
php spark migrate --all
php spark cache:clear
echo "[$(date '+%F %T')] done"
} >> "$LOG" 2>&1
 

crontab에는 한 줄만 등록한다.

  * * * * * /path/to/jblove/deploy-pull.sh  

기존 수동 배포와 cron 자동 배포의 흐름을 비교한 그림

이 스크립트에서 신경 쓴 부분

변경이 없으면 바로 종료한다. git fetch로 원격 main을 가져와 현재 HEAD와 비교한다. 같으면 아무것도 하지 않고 끝나므로, 1분마다 돌아도 부하는 거의 없다.

flock으로 중복 실행을 막는다. 마이그레이션이 길어져서 1분을 넘기면 다음 cron이 겹쳐 실행될 수 있다. 락을 잡지 못하면 조용히 빠져나간다.

--ff-only로 반영한다. 서버에서 직접 수정한 파일이 있어 충돌이 나면 병합이 실패한다. 덮어쓰는 것보다 실패해서 알게 되는 편이 안전하다고 판단했다. 서버에 수정분이 없다고 확신하면 reset --hard로 바꿔도 된다.

composer는 필요할 때만 실행한다. composer.json과 composer.lock이 바뀐 경우에만 설치한다. 대부분의 배포는 코드만 바뀌므로 시간이 절약된다.

set -e로 실패하면 멈춘다. 중간에 실패하면 그 단계에서 멈추고 로그에 남는다. 다음 분에 다시 시도하므로 일시적 오류는 저절로 복구된다.

도입 전에 확인할 점

  • git 인증: private 저장소라면 서버에 읽기 전용 deploy key를 등록한다. cron 환경에서 git fetch가 비밀번호를 묻지 않아야 한다.
  • cron의 PATH: cron은 환경변수가 최소라서 php나 composer를 못 찾을 수 있다. crontab 맨 위에 PATH=...를 지정하거나 절대경로를 쓴다.
  • opcache: validate_timestamps=0으로 운영 중이라면 배포 후 php-fpm reload가 필요하다.
  • 알림: 현재는 로그만 남고 실패 알림이 없다. 필요해지면 trap으로 Slack webhook 호출을 붙이면 된다.
  • 로그 증가: deploy.log는 logrotate에 등록해 둔다.

배운 점

자동화는 거창할 필요가 없다. 서버에서 하던 일을 스크립트 하나로 옮기고, 중복 실행과 실패 상황만 처리하면 충분했다. 이번에 만든 것은 쉘 스크립트 한 개와 cron 한 줄이다.

그리고 git pull만 하던 습관이 사실은 배포의 일부만 하고 있었다는 점도 알게 됐다. 스크립트로 옮기면서 composer, 마이그레이션, 캐시 삭제를 빠뜨리지 않게 된 것은 덤이다.

결국 귀찮음을 참지 않고 그대로 두 번, 세 번 반복하다가 "이건 사람이 할 일이 아니다"라고 느끼는 순간이 개발자를 움직이게 만든다. 오늘 한 번 더 같은 명령을 쳤다면, 그게 바로 자동화할 때다.

댓글 0개