📖 14주차 04강: 무중단 엔보이(Envoy) 배포, 패키지 개발 & 릴리스 업그레이드
실습 프로젝트: 지니샵(JinyShop) — 온라인 도서 쇼핑몰
학습 목표: 14주간 완성한 지니샵(JinyShop)을 실제 운영 서버에 무중단(Zero-downtime)으로 안전하게 배포하기 위한 캐시 최적화 및 Envoy 스크립트를 작성하고, 공통 모듈의 독립 패키지화와 라라벨 상위 버전 릴리스 업그레이드 전략을 체득하여 완벽한 프로덕션 런칭을 완료합니다.
등장인물: 🐱 지니(시니어 멘토), 👧 도로시(주니어 개발자), 🐶 토토(개발 보조견)
💬 지니와 도로시의 티키타카 회의
👧 도로시: “지니! 드디어 14주 동안 눈물과 땀으로 완성한 우리 지니샵(JinyShop)을 실제 서버에 배포하는 날이야! 그런데 배포할 때 고객들이 접속하고 있는데 git pull 받고 마이그레이션 돌리면 화면이 하얗게 깨지지 않을까?”
🐱 지니: “도로시, 진정한 프로는 절대로 서비스를 멈추지 않는 무중단(Zero-downtime) 배포를 한단다! 라라벨 공식 SSH 배포 도구인 라라벨 엔보이(Laravel Envoy)를 쓰면, 새 버전을 별도 폴더(releases/20260925)에 미리 빌드해 두고 심볼릭 링크(current)만 0.001초 만에 찰칵 교체하여 단 1초의 중단도 없이 고객에게 최신 버전을 선물하지!”
🐶 토토: “멍멍! 배포 직전에는 php artisan optimize로 모든 설정과 라우트를 캐싱하고, 우리가 만든 쿠폰 계산 모듈은 독립된 패키지로 분리해서 오픈소스 생태계에도 멋지게 기여하자멍! 14주간의 대장정이 드디어 완성되었다멍!”
🎯 실습 목표 및 서점 시나리오
- 운영 배포 전 최적화 체크리스트:
php artisan optimize, OPcache 튜닝, Composer--no-dev - 라라벨 엔보이(Envoy) 무중단 배포 스크립트 작성:
Envoy.blade.php- Git 저장소 클론 -> Composer 의존성 설치 -> DB 마이그레이션 -> 캐싱 -> 심볼릭 링크 원자적 교체
- 독립 패키지 개발(Package Development): 지니샵 공통 쿠폰/할인 엔진을 별도 라라벨 패키지(
packages/jinyshop/coupon)로 분리 - 라라벨 릴리스 주기(Release Notes) 및 무중단 업그레이드(Upgrade Guide) 전략
- 라라벨 오픈소스 프레임워크 기여하기(Contributions)
🛠️ 단계별 실습 절차
1단계: 프로덕션 배포 전 캐시 최적화 체크리스트
운영 서버에 코드를 올린 직후에는 매 요청마다 수백 개의 PHP 파일을 읽지 않도록 프레임워크를 압축 캐싱해야 합니다:
# 1. 설정 파일 단일 배열 캐싱
php artisan config:cache
# 2. 라우트 등록 트리 컴파일 캐싱
php artisan route:cache
# 3. 블레이드 템플릿 사전 컴파일 캐싱
php artisan view:cache
# 4. 이벤트 & 리스너 매핑 캐싱
php artisan event:cache
# 라라벨 11에서는 위의 모든 것을 단 한 줄로 일괄 처리 가능!
php artisan optimize
Composer 프로덕션 최적화:
composer install --no-dev --optimize-autoloader
🐶 토토의 팁!
“코드를 새로 배포했는데 이전 코드가 계속 도는 것 같다면php artisan optimize:clear를 외치고 다시php artisan optimize를 돌리라멍!”
2단계: 라라벨 엔보이(Envoy) 무중단 배포 스크립트 작성
엔보이는 블레이드 문법으로 원격 리눅스 서버에 SSH 명령을 내리는 라라벨 공식 배포 유틸리티입니다:
composer require --dev laravel/envoy
프로젝트 루트에 Envoy.blade.php 파일을 생성합니다:
@servers(['web' => 'deployer@123.45.67.89'])
@setup
$repository = 'git@github.com:jinysite/jinyshop.git';
$releases_dir = '/var/www/jinyshop/releases';
$app_dir = '/var/www/jinyshop';
$release = date('YmdHis');
$new_release_dir = $releases_dir . '/' . $release;
@endsetup
@story('deploy')
clone_repository
run_composer
update_permissions
link_shared_storage
run_migrations
optimize_caches
update_symlink
restart_services
@endstory
@task('clone_repository')
echo "📦 [1/8] Git 저장소에서 최신 코드를 내려받습니다...";
[ -d {{ $releases_dir }} ] || mkdir -p {{ $releases_dir }};
git clone --depth 1 {{ $repository }} {{ $new_release_dir }};
@endtask
@task('run_composer')
echo "⚙️ [2/8] Composer 프로덕션 의존성을 설치합니다...";
cd {{ $new_release_dir }};
composer install --prefer-dist --no-scripts --no-dev -q -o;
@endtask
@task('link_shared_storage')
echo "🔗 [3/8] 공유 스토리지 및 .env 심볼릭 링크를 연결합니다...";
rm -rf {{ $new_release_dir }}/storage;
ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage;
ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env;
@endtask
@task('run_migrations')
echo "🗄️ [4/8] 데이터베이스 마이그레이션을 안전하게 실행합니다...";
cd {{ $new_release_dir }};
php artisan migrate --force;
@endtask
@task('optimize_caches')
echo "⚡ [5/8] 라라벨 캐시를 최적화합니다...";
cd {{ $new_release_dir }};
php artisan optimize;
@endtask
@task('update_symlink')
echo "🔄 [6/8] 심볼릭 링크를 교체하여 새 버전을 즉시 활성화합니다 (Zero Downtime)...";
ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current;
@endtask
@task('restart_services')
echo "🚀 [7/8] PHP-FPM 및 큐 워커를 재시작합니다...";
sudo systemctl reload php8.3-fpm;
cd {{ $app_dir }}/current;
php artisan queue:restart;
echo "🎉 [8/8] 지니샵 무중단 배포가 완벽하게 완료되었습니다!";
@endtask
로컬 터미널에서 단 한 줄로 배포를 실행합니다:
envoy run deploy
Nginx 웹 서버의 웹 루트(root)는 /var/www/jinyshop/current/public을 가리키고 있으므로, 심볼릭 링크가 바뀌는 0.001초 동안 사용자에게 단 1ms의 중단 시간도 발생하지 않습니다!
3단계: 지니샵 쿠폰 엔진의 독립 패키지(Package) 분리
지니샵의 도서 할인 쿠폰 계산 로직을 다른 프로젝트에서도 재사용할 수 있도록 packages/jinyshop/coupon으로 독립 패키지화합니다.
packages/jinyshop/coupon/composer.json:
{
"name": "jinyshop/coupon",
"description": "JinyShop Bookstore Advanced Coupon & Discount Engine",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"JinyShop\\Coupon\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"JinyShop\\Coupon\\CouponServiceProvider"
]
}
}
}
packages/jinyshop/coupon/src/CouponServiceProvider.php:
<?php
namespace JinyShop\Coupon;
use Illuminate\Support\ServiceProvider;
class CouponServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__ . '/../config/coupon.php', 'coupon');
$this->app->singleton('coupon', function () {
return new CouponCalculator();
});
}
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__ . '/../config/coupon.php' => config_path('coupon.php'),
], 'coupon-config');
}
}
}
라라벨의 패키지 자동 검색(Package Auto-Discovery) 덕분에, 이 패키지를 설치한 사람은 어떤 설정도 없이 즉시 Coupon::calculateDiscount($price, $coupon) 파사드를 사용할 수 있습니다!
4단계: 라라벨 릴리스 주기 및 업그레이드 전략
- 라라벨 릴리스 주기:
- 매년 1분기(Q1)에 메이저 버전(10, 11, 12…)이 릴리스됩니다.
- 버그 수정은 18개월, 보안 패치는 24개월 동안 공식 지원됩니다.
- 무중단 업그레이드 전략:
- 공식 업그레이드 가이드를 꼼꼼히 확인하고 변경된 Breaking Changes를 검토합니다.
- 자동 마이그레이션 도구인 Laravel Shift를 활용하거나
composer update후php artisan test를 돌려 모든 테스트가 녹색 불인지 확인합니다.
5단계: 오픈소스 라라벨 기여하기 (Contributions)
라라벨 프레임워크나 공식 문서의 오타 또는 새로운 기능을 제안하고 싶다면 라라벨 깃허브 리포지토리(laravel/framework)에 PR을 제출할 수 있습니다:
- 기능 추가는
master브랜치로, 버그 패치는 현재 안정화 브랜치(예:11.x)로 PR을 보냅니다. - 반드시 변경 사항을 검증하는 PHPUnit/Pest 테스트 코드를 동봉해야 합니다.
- 코딩 표준은
laravel/pint를 실행하여 100% 라라벨 코드 스타일 규칙을 준수합니다.
🎊 지니샵(JinyShop) 14주 완성 기념 피날레
🐱 지니: “도로시, 토토! 1주차 환경 설정과 아티산 CLI부터 시작해서, 블레이드, Eloquent ORM, 복합 연관관계, 장바구니 세션, 인증/인가, 파일 스토리지, 코어 아키텍처, 비동기 큐/이벤트, 스케줄러, Redis 캐시, Reverb 실시간 웹소켓, Sanctum API, AI SDK, 그리고 자동화 테스트와 무중단 배포까지… 라라벨 공식 문서 103개 전 주제를 완벽하게 녹여낸 1등 온라인 서점 지니샵(JinyShop)이 탄생했단다!”
👧 도로시: “지니 선배님 덕분에 단순한 코더가 아니라, 견고하고 아름다운 소프트웨어 아키텍처를 설계하는 진짜 풀스택 엔지니어로 성장할 수 있었어! 너무 감사해!”
🐶 토토: “멍멍! 지니샵 웹사이트에 접속자가 폭주해도 우리는 끄떡없다멍! 배포 성공이다멍!”
🔍 라라벨 공식 문서 원리 심층 분석
1. OPcache 최적화 설정 (php.ini)
배포 서버에서 PHP의 성능을 극대화하려면 바이트코드를 메모리에 영구 저장하는 OPcache 활성화가 필수적입니다:
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; 코드 변경 감지 비활성화 (배포 시 PHP-FPM 리로드로 갱신)
2. 유지보수 모드(Maintenance Mode)의 비밀
만약 대규모 DB 스키마 변경으로 잠시 점검 화면을 띄워야 한다면 php artisan down을 실행합니다:
php artisan down --secret="jinypassword" --render="errors.maintenance"
관리자는 브라우저에서 https://jinyshop.com/jinypassword로 접속하여 점검 중에도 관리자 화면을 자유롭게 점검할 수 있습니다!
🐶 토토의 트러블슈팅 & 주의사항
storage폴더 권한 부여:
배포 후The stream or file ".../laravel.log" could not be opened: failed to open stream: Permission denied에러가 난다면 웹 서버 사용자(www-data)에게 쓰기 권한이 없는 것입니다:sudo chown -R www-data:www-data storage bootstrap/cache sudo chmod -R 775 storage bootstrap/cache- 배포 직후 큐 워커 재시작 필수:
큐 워커(queue:work)는 메모리에 이전 코드를 물고 있으므로, 배포 스크립트 끝에서 반드시php artisan queue:restart를 날려주어야 새 코드로 작업을 처리합니다!
💡 자가진단 퀴즈 & 수료 최종 과제
- 퀴즈: 라라벨에서 설정, 라우트, 뷰, 이벤트 캐시를 프로덕션 성능 극대화를 위해 한 번에 묶어서 컴파일해 주는 Artisan 명령어는 무엇일까요?
- 정답:
php artisan optimize
- 정답:
- 수료 최종 과제: 완성된 지니샵 프로젝트에
php artisan test를 실행하여 모든 단위, 기능, 콘솔 테스트가 100% 통과하는지 확인하고, 최종 Git 태그(v1.0.0)를 생성하여 성공적인 런칭을 기념해 보세요!