Laravel 제로 다운타임 배포

게시 됨: 2022-03-11

라이브 애플리케이션 업데이트와 관련하여 근본적으로 다른 두 가지 방법이 있습니다.

첫 번째 접근 방식에서는 시스템 상태를 점진적으로 변경합니다. 예를 들어 파일 업데이트, 환경 속성 수정, 추가 필수품 설치 등을 수행합니다. 두 번째 접근 방식에서는 전체 시스템을 분해하고 새 이미지와 선언적 구성(예: Kubernetes 사용)으로 시스템을 재구축합니다.

Laravel 배포가 쉬워졌습니다

이 기사는 클라우드에서 호스팅되지 않을 수 있는 비교적 작은 애플리케이션을 주로 다루지만 Kubernetes가 "클라우드 없음" 시나리오를 넘어 배포하는 데 어떻게 크게 도움이 되는지 언급하겠습니다. 또한 Laravel 배포뿐만 아니라 다양한 상황에 적용될 수 있는 성공적인 업데이트를 수행하기 위한 몇 가지 일반적인 문제와 팁에 대해 논의할 것입니다.

이 데모의 목적을 위해 저는 Laravel 예제를 사용할 것이지만 모든 PHP 애플리케이션이 유사한 접근 방식을 사용할 수 있다는 점을 염두에 두십시오.

버전 관리

우선 현재 프로덕션에 배포된 코드 버전을 아는 것이 중요합니다. 일부 파일 또는 최소한 폴더 또는 파일 이름에 포함될 수 있습니다. 명명과 관련하여 의미론적 버전 관리의 표준 관행을 따르면 단일 숫자보다 더 많은 정보를 포함할 수 있습니다.

두 가지 다른 릴리스를 살펴보면 이 추가된 정보를 통해 두 릴리스 사이에 도입된 변경 사항의 특성을 쉽게 이해할 수 있습니다.

시맨틱 버전 관리에 대한 설명을 보여주는 이미지.

릴리스 버전 관리는 Git과 같은 버전 제어 시스템에서 시작됩니다. 배포용 릴리스(예: 버전 1.0.3)를 준비했다고 가정해 보겠습니다. 이러한 릴리스 및 코드 흐름을 구성할 때 트렁크 기반 개발 및 Git 흐름과 같은 다양한 개발 스타일이 있으며, 팀의 기본 설정과 프로젝트의 세부 사항에 따라 선택하거나 혼합할 수 있습니다. 결국, 우리는 메인 브랜치에 상응하는 태그가 붙은 릴리스로 끝날 가능성이 높습니다.

커밋 후에 다음과 같은 간단한 태그를 만들 수 있습니다.

git tag v1.0.3

그런 다음 푸시 명령을 실행하는 동안 태그를 포함합니다.

git push <origin> <branch> --tags

해시를 사용하여 이전 커밋에 태그를 추가할 수도 있습니다.

릴리스 파일을 대상으로 가져오기

Laravel 배포는 단순히 파일을 복사하더라도 시간이 걸립니다. 그러나 너무 오래 걸리지는 않더라도 우리의 목표는 가동 중지 시간 0 을 달성하는 것입니다.

따라서 업데이트를 제자리에 설치하는 것을 피해야 하며 라이브로 제공되는 파일을 변경해서는 안 됩니다. 대신, 다른 디렉토리에 배포하고 설치가 완전히 준비된 후에만 전환해야 합니다.

실제로 Envoyer.io(Laravel.com 디자이너 Jack McDade 작성), Capistrano, Deployer 등과 같이 배포에 도움이 될 수 있는 다양한 도구와 서비스가 있습니다. 추천을 하거나 포괄적인 비교를 작성할 수 있지만 이 제품의 이면에 있는 아이디어를 보여드리겠습니다. 그 중 일부(또는 전부)가 요구 사항을 충족할 수 없는 경우 항상 사용자 정의 스크립트를 생성하여 최적의 방식으로 프로세스를 자동화할 수 있습니다.

이 데모의 목적을 위해 다음 경로에서 Nginx 서버가 Laravel 애플리케이션을 제공한다고 가정해 보겠습니다.

/var/www/demo/public

먼저 배포할 때마다 릴리스 파일을 저장할 디렉터리가 필요합니다. 또한 현재 작업 릴리스를 가리키는 심볼릭 링크가 필요합니다. 이 경우 /var/www/demo 가 심볼릭 링크 역할을 합니다. 포인터를 재할당하면 릴리스를 빠르게 변경할 수 있습니다.

Laravel 배포 파일 처리

Apache 서버를 다루는 경우 구성에서 다음 심볼릭 링크를 허용해야 할 수 있습니다.

Options +FollowSymLinks

우리의 구조는 다음과 같을 수 있습니다.

 /opt/demo/release/v0.1.0 /opt/demo/release/v0.1.1 /opt/demo/release/v0.1.2

다른 배포를 통해 유지해야 하는 일부 파일, 예를 들어 로그 파일이 있을 수 있습니다(물론 Logstash를 사용하지 않는 경우). Laravel 배포의 경우 스토리지 디렉토리와 .env 구성 파일을 유지하고 싶을 수 있습니다. 다른 파일과 분리된 상태로 유지하고 대신 해당 심볼릭 링크를 사용할 수 있습니다.

Git 리포지토리에서 릴리스 파일을 가져오기 위해 복제 또는 아카이브 명령을 사용할 수 있습니다. 어떤 사람들은 git clone을 사용하지만 특정 커밋이나 태그를 복제할 수는 없습니다. 즉, 전체 저장소를 가져온 다음 특정 태그가 선택됩니다. 저장소에 많은 분기 또는 큰 기록이 포함된 경우 해당 크기는 릴리스 아카이브보다 훨씬 큽니다. 따라서 프로덕션에 git repo가 ​​특별히 필요하지 않은 경우 git archive 를 사용할 수 있습니다. 이를 통해 특정 태그로 파일 아카이브만 가져올 수 있습니다. 후자를 사용하는 또 다른 이점은 테스트와 같이 프로덕션 환경에 있어서는 안 되는 일부 파일과 폴더를 무시할 수 있다는 것입니다. 이를 위해 .gitattributes file 에서 export-ignore 속성을 설정하기만 하면 됩니다. OWASP Secure Coding Practices 체크리스트에서 다음 권장 사항을 찾을 수 있습니다 . "배포 전에 테스트 코드 또는 프로덕션용이 아닌 기능을 제거하십시오."

소스 버전 제어 시스템에서 릴리스를 가져오는 경우 git archive 및 export-ignore가 이 요구 사항에 도움이 될 수 있습니다.

단순화된 스크립트를 살펴보겠습니다(프로덕션에서 더 나은 오류 처리가 필요함).

배포.sh

 #!/bin/bash # Terminate execution if any command fails set -e # Get tag from a script argument TAG=$1 GIT_REMOTE_URL='here should be a remote url of the repo' BASE_DIR=/opt/demo # Create folder structure for releases if necessary RELEASE_DIR=$BASE_DIR/releases/$TAG mkdir -p $RELEASE_DIR mkdir -p $BASE_DIR/storage cd $RELEASE_DIR # Fetch the release files from git as a tar archive and unzip git archive \ --remote=$GIT_REMOTE_URL \ --format=tar \ $TAG \ | tar xf - # Install laravel dependencies with composer composer install -o --no-interaction --no-dev # Create symlinks to `storage` and `.env` ln -sf $BASE_DIR/.env ./ rm -rf storage && ln -sf $BASE_DIR/storage ./ # Run database migrations php artisan migrate --no-interaction --force # Run optimization commands for laravel php artisan optimize php artisan cache:clear php artisan route:cache php artisan view:clear php artisan config:cache # Remove existing directory or symlink for the release and create a new one. NGINX_DIR=/var/www/public mkdir -p $NGINX_DIR rm -f $NGINX_DIR/demo ln -sf $RELEASE_DIR $NGINX_DIR/demo

릴리스를 배포하기 위해 다음을 실행할 수 있습니다.

deploy.sh v1.0.3

참고: 이 예에서 v1.0.3은 릴리스의 git 태그입니다.

프로덕션에 작곡가?

스크립트가 종속성을 설치하기 위해 Composer를 호출하고 있음을 눈치채셨을 것입니다. 많은 기사에서 이것을 볼 수 있지만 이 접근 방식에는 몇 가지 문제가 있을 수 있습니다. 일반적으로 애플리케이션의 완전한 빌드를 만들고 인프라의 다양한 테스트 환경을 통해 이 빌드를 진행하는 것이 가장 좋습니다. 결국, 프로덕션에 안전하게 배포할 수 있는 철저하게 테스트된 빌드를 갖게 됩니다. 모든 빌드를 처음부터 재현할 수 있어야 하지만 다른 단계에서 앱을 다시 빌드해야 하는 것은 아닙니다. 우리가 작곡가를 프로덕션 환경에 설치할 때 테스트한 빌드와 완전히 동일한 빌드가 아니며 다음과 같이 잘못될 수 있습니다.

  • 네트워크 오류로 인해 종속성 다운로드가 중단될 수 있습니다.
  • 라이브러리 공급업체가 항상 SemVer를 따르는 것은 아닙니다.

네트워크 오류는 쉽게 알 수 있습니다. 스크립트는 오류와 함께 실행을 중지하기도 합니다. 그러나 라이브러리의 주요 변경 사항은 프로덕션 환경에서는 수행할 수 없는 테스트를 실행하지 않고는 파악하기가 매우 어려울 수 있습니다. 종속성을 설치하는 동안 Composer, npm 및 기타 유사한 도구는 의미론적 버전 관리(major.minor.patch)에 의존합니다. composer.json에 ~1.0.2가 보이면 버전 1.0.2 또는 1.0.4와 같은 최신 패치 버전을 설치한 것입니다. ^1.0.2가 보이면 버전 1.0.2 또는 1.1.0과 같은 최신 마이너 또는 패치 버전을 설치한다는 의미입니다. 우리는 주요 변경 사항이 도입될 때 라이브러리 공급업체가 주요 번호를 올릴 것이라고 믿지만 때때로 이 요구 사항이 누락되거나 준수되지 않습니다. 과거에도 그런 사례가 있었습니다. 작곡가.json에 고정 버전을 넣어도 종속 항목의 composer.json에 ~ 및 ^가 있을 수 있습니다.

내 생각에 접근이 가능하다면 아티팩트 저장소(Nexus, JFrog 등)를 사용하는 것이 더 나은 방법일 것입니다. 필요한 모든 종속성을 포함하는 릴리스 빌드는 처음에 한 번만 생성됩니다. 이 아티팩트는 저장소에 저장되고 거기에서 다양한 테스트 단계를 위해 가져옵니다. 또한 Git에서 앱을 다시 빌드하는 대신 프로덕션에 배포할 빌드가 됩니다.

코드와 데이터베이스 호환성 유지

제가 첫눈에 Laravel과 사랑에 빠진 이유는 작성자가 세부 사항에 세심한주의를 기울이고 개발자의 편의를 고려했으며 데이터베이스 마이그레이션과 같은 많은 모범 사례를 프레임 워크에 통합 한 방식 때문입니다.

데이터베이스 마이그레이션을 통해 데이터베이스와 코드를 동기화할 수 있습니다. 두 변경 사항 모두 단일 커밋에 포함될 수 있으므로 단일 릴리스입니다. 그러나 이것이 다운타임 없이 변경 사항을 배포할 수 있음을 의미하지는 않습니다. 배포 중 어느 시점에서 실행 중인 응용 프로그램 및 데이터베이스의 다른 버전이 있을 것입니다. 문제가 있는 경우 이 시점이 마침표가 될 수도 있습니다. 우리는 항상 이전 버전의 동반자, 즉 이전 데이터베이스-새 앱, 새 데이터베이스-이전 앱과 둘 다 호환되도록 노력해야 합니다.

예를 들어 address 열이 있고 이를 address1address2 로 분할해야 한다고 가정해 보겠습니다. 모든 것이 호환되도록 유지하려면 여러 릴리스가 필요할 수 있습니다.

  1. 데이터베이스에 두 개의 새 열을 추가합니다.
  2. 가능할 때마다 새 필드를 사용하도록 애플리케이션을 수정하십시오.
  3. address 데이터를 새 열로 마이그레이션하고 삭제합니다.

이 사례는 또한 작은 변경이 배포에 훨씬 더 나은 방법을 보여주는 좋은 예입니다. 롤백도 더 쉽습니다. 몇 주 또는 몇 달 동안 코드베이스와 데이터베이스를 변경하는 경우 다운타임 없이 프로덕션 시스템을 업데이트하는 것이 불가능할 수 있습니다.

Kubernetes의 놀라운 기능

애플리케이션의 규모에 클라우드, 노드 및 Kubernetes가 필요하지 않을 수도 있지만 K8에서 배포가 어떻게 보이는지 언급하고 싶습니다. 이 경우 시스템을 변경하지 않고 달성하고자 하는 것과 얼마나 많은 복제본에서 실행해야 하는 것을 선언합니다. 그런 다음 Kubernetes는 실제 상태가 원하는 상태와 일치하는지 확인합니다.

새 릴리스가 준비될 때마다 새 파일이 포함된 이미지를 만들고 새 버전으로 이미지에 태그를 지정하고 K8에 전달합니다. 후자는 클러스터 내부의 이미지를 빠르게 회전시킵니다. 우리가 제공하는 준비 상태 확인을 기반으로 애플리케이션이 준비될 때까지 기다린 다음 트래픽을 눈에 띄지 않게 새 애플리케이션으로 리디렉션하고 이전 애플리케이션을 종료합니다. 몇 가지 명령으로 블루/그린 또는 카나리아 배포를 수행할 수 있는 여러 버전의 앱을 매우 쉽게 실행할 수 있습니다.

관심이 있으시면 "Burr Sutter의 Kubernetes를 사용한 9단계"라는 강연에 인상적인 데모가 있습니다.

관련 항목: 전체 사용자 인증 및 액세스 제어 – A Laravel Passport Tutorial, Pt. 1