효과적인 초기 배포 파이프라인을 구축하는 방법

게시 됨: 2022-03-11

나는 무언가를 만드는 것을 좋아합니다. 어떤 개발자가 좋아하지 않습니까? 저는 흥미로운 문제에 대한 솔루션을 생각하고 구현을 작성하고 아름다운 코드를 만드는 것을 좋아합니다. 그러나 내가 좋아하지 않는 것은 작업 입니다. 운영은 서버 설정에서 코드를 프로덕션으로 배송하는 것까지 훌륭한 소프트웨어를 구축하는 것과 관련이 없는 모든 것입니다.

이것은 흥미롭습니다. 프리랜서 Ruby on Rails 개발자로서 저는 자주 새로운 웹 애플리케이션을 만들고 DevOps 측면을 파악하는 프로세스를 반복해야 하기 때문입니다. 다행히 수십 개의 애플리케이션을 만든 후 마침내 완벽한 초기 배포 파이프라인에 정착했습니다. 불행히도, 모든 사람이 저처럼 이해한 것은 아닙니다. 결국 이 지식으로 인해 저는 급히 내 프로세스를 문서화하게 되었습니다.

이 기사에서는 프로젝트를 시작할 때 사용할 완벽한 파이프라인을 안내합니다. 내 파이프라인을 사용하면 모든 푸시가 테스트되고, 마스터 브랜치는 프로덕션에서 새로운 데이터베이스 덤프로 스테이징에 배포되고, 버전이 지정된 태그는 백업 및 마이그레이션이 자동으로 발생하여 프로덕션에 배포됩니다.

파이프라인이기 때문에 내 요구 사항에 맞는 의견을 제시하기도 합니다. 그러나 마음에 들지 않는 것은 자유롭게 바꾸고 마음에 드는 것으로 바꿀 수 있습니다. 내 파이프라인의 경우 다음을 사용합니다.

  • 코드를 호스트하는 GitLab .
    • 이유: 내 고객은 코드를 비밀로 유지하는 것을 선호하며 GitLab의 무료 계층은 훌륭합니다. 또한 통합된 무료 CI가 훌륭합니다. 감사합니다 GitLab!
    • 대안: GitHub, BitBucket, AWS CodeCommit 등.
  • GitLab CI 를 사용하여 코드를 빌드, 테스트 및 배포합니다.
    • 이유: GitLab과 통합되며 무료입니다!
    • 대안: TravisCI, Codeship, CircleCI, Fabric8을 사용한 DIY 등.
  • Heroku 는 우리 앱을 호스팅합니다.
    • 이유: 바로 사용할 수 있으며 시작하기에 완벽한 플랫폼입니다. 나중에 이를 변경할 수 있지만 모든 새 앱이 특수 제작된 Kubernetes 클러스터에서 실행될 필요는 없습니다. Coinbase도 Heroku에서 시작했습니다.
    • 대안: AWS, DigitalOcean, Vultr, Kubernetes를 사용한 DIY 등.

구식: 기본 앱을 만들고 Heroku에 배포

먼저 멋진 CI/CD 파이프라인을 사용하지 않고 응용 프로그램을 배포하려는 사람을 위해 일반적인 응용 프로그램을 다시 만들어 보겠습니다.

기존 코드 호스팅 및 배포 작업의 다이어그램

어떤 종류의 앱을 만들고 있는지는 중요하지 않지만 Yarn 또는 npm이 필요합니다. 예를 들어, 마이그레이션 및 CLI와 함께 제공되는 Ruby on Rails 애플리케이션을 만들고 있으며 이미 이에 대한 구성이 작성되어 있습니다. 원하는 프레임워크나 언어를 사용할 수 있지만 나중에 버전 관리를 수행하려면 Yarn이 필요합니다. 몇 가지 명령만 사용하고 인증은 사용하지 않는 간단한 CRUD 앱을 만들고 있습니다.

그리고 앱이 예상대로 실행되는지 테스트해 보겠습니다. 확인하기 위해 몇 가지 게시물을 작성했습니다.

개발 중인 애플리케이션

그리고 코드를 푸시하고 마이그레이션을 실행하여 Heroku에 배포해 보겠습니다.

 $ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...

마지막으로 프로덕션에서 테스트해 보겠습니다.

프로덕션에서 실행 중인 애플리케이션

그리고 그게 다야! 일반적으로 여기에서 대부분의 개발자가 작업을 종료합니다. 나중에 변경하면 위의 배포 및 마이그레이션 단계를 반복해야 합니다. 저녁 식사에 늦지 않으면 테스트를 실행할 수도 있습니다. 이것은 출발점으로 훌륭하지만 이 방법에 대해 조금 더 생각해 보겠습니다.

장점

  • 설정이 빠릅니다.
  • 배포가 쉽습니다.

단점

  • Not DRY: 모든 변경에 대해 동일한 단계를 반복해야 합니다.
  • 버전이 지정되지 않음: "어제 배포를 지난주로 롤백합니다"는 지금부터 3주 후에 매우 구체적이지 않습니다.
  • 코드가 불량하지 않음: 테스트를 실행해야 한다는 것을 알고 있지만 아무도 보고 있지 않으므로 가끔 테스트가 실패하더라도 푸시할 수 있습니다.
  • 나쁜 배우가 아님: 불만을 품은 개발자가 팀에 충분한 피자를 주문하지 않는 방법에 대한 메시지가 포함된 코드를 푸시하여 앱을 중단하기로 결정했다면 어떻게 하시겠습니까?
  • 확장되지 않음: 모든 개발자가 배포할 수 있도록 허용하면 최소 권한 원칙을 위반하여 앱에 대한 프로덕션 수준 액세스 권한이 부여됩니다.
  • 스테이징 환경 없음: 프로덕션 환경과 관련된 오류는 프로덕션까지 표시되지 않습니다.

완벽한 초기 배포 파이프라인

오늘은 다른 것을 시도해 보겠습니다. 가상의 대화를 합시다. 저는 "당신"의 목소리를 내고 이 흐름을 개선할 수 있는 방법에 대해 이야기할 것입니다. 말 좀 해.

뭐라고? 잠깐—내가 말할 수 있습니까?

네, 제가 당신에게 목소리를 내고자 한 것입니다. 잘 지내고 있나요?

난 괜찮아. 기분이 이상해

이해는 하지만 그냥 굴리세요. 이제 파이프라인에 대해 이야기해 보겠습니다. 배포를 실행할 때 가장 짜증나는 부분은 무엇입니까?

오, 쉽습니다. 내가 낭비하는 시간의 양. Heroku로 푸시를 시도한 적이 있습니까?

예, 종속성 다운로드 및 git push 의 일부로 빌드되는 응용 프로그램을 보는 것은 끔찍합니다!

나도 알아, 그렇지? 미쳤어 그럴 필요가 없었으면 합니다. 또한 배포 *후에* 마이그레이션을 실행해야 하기 때문에 쇼를 시청하고 배포가 제대로 실행되는지 확인해야 합니다.

좋습니다. git push heroku master && heroku run rails db:migrate 와 같이 두 명령을 && 로 연결하여 후자의 문제를 실제로 해결할 수 있습니다. 시간과 반복은 진정한 고통입니다.

그래, 정말 짜증난다

CI/CD 파이프라인을 사용하여 해당 비트를 즉시 수정할 수 있다고 말하면 어떻게 될까요?

A 지금? 저게 뭐에요?

CI/CD는 CI(지속적 통합) 및 CD(지속적 배포/배포)를 나타냅니다. 모두가 "개발과 운영의 융합"과 같은 모호한 용어를 사용하기 때문에 시작했을 때 정확히 무엇인지 이해하기가 상당히 어려웠습니다.

  • 지속적인 통합: 모든 코드가 한 곳에서 함께 병합되도록 합니다. 팀이 Git을 사용하도록 하면 CI를 사용하게 됩니다.
  • 지속적인 배송: 코드가 지속적으로 배송될 준비가 되었는지 확인합니다. 제품의 읽기-배포 버전을 빠르게 생성하는 것을 의미합니다.
  • 지속적인 배포: 지속적인 배포 에서 제품을 원활하게 가져와 서버에 배포합니다.

오, 이제 알았다. 내 앱을 마법처럼 세상에 배포하는 것입니다!

CI/CD를 설명하는 가장 좋아하는 기사는 여기 Atlassian의 기사입니다. 이렇게 하면 궁금한 점이 해결될 것입니다. 어쨌든 문제로 돌아갑니다.

그래, 그 얘기로 돌아가. 수동 배포를 피하려면 어떻게 합니까?

master 로 푸시 시 배포하도록 CI/CD 파이프라인 설정

CI/CD를 사용하여 해당 비트를 즉시 수정할 수 있다고 말하면 어떻게 됩니까? GitLab 리모컨( origin )으로 푸시하면 컴퓨터가 생성되어 해당 코드를 Heroku로 푸시하기만 하면 됩니다.

안 돼요!

그래! 다시 코드로 돌아가 봅시다.

단순 배포 CI/CD 파이프라인 다이어그램

다음 내용으로 .gitlab-ci.yml 을 만들고 Heroku 앱의 이름에 대해 toptal-pipeline 을 교체합니다.

 image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master

이것을 푸시하고 프로젝트의 파이프라인 페이지에서 실패하는 것을 지켜보십시오. Heroku 계정에 대한 인증 키가 없기 때문입니다. 하지만 이를 수정하는 것은 매우 간단합니다. 먼저 Heroku API 키 가 필요합니다. 계정 관리 페이지에서 가져온 다음 GitLab 리포지토리의 CI/CD 설정에 다음 비밀 변수 를 추가합니다.

  • HEROKU_EMAIL : Heroku에 로그인할 때 사용하는 이메일 주소
  • HEROKU_AUTH_KEY : Heroku에서 받은 키

GitLab CI/CD 설정 페이지의 비밀 변수 이미지

그러면 모든 푸시에서 배포되는 Heroku에 대한 GitLab이 작동해야 합니다. 무슨 일이 일어나고 있는지 :

  • 마스터로 푸시 시
    • Heroku CLI는 컨테이너에 설치 및 인증됩니다.
    • 코드가 Heroku로 푸시됩니다.
    • 데이터베이스 백업은 Heroku에서 캡처됩니다.
    • 마이그레이션이 실행됩니다.

이미 모든 것을 git push 로 자동화하여 시간을 절약할 수 있을 뿐만 아니라 모든 배포에서 데이터베이스 백업을 생성하고 있음을 알 수 있습니다! 문제가 발생하면 다시 되돌릴 데이터베이스 복사본을 갖게 됩니다.

스테이징 환경 만들기

그러나 잠시만요, 빠른 질문입니다. 생산 관련 문제는 어떻게 됩니까? 개발 환경이 프로덕션 환경과 너무 다르기 때문에 이상한 버그가 발생하면 어떻게 될까요? 마이그레이션을 실행할 때 이상한 SQLite 3 및 PostgreSQL 문제가 발생한 적이 있습니다. 구체적인 내용은 이해가 가지 않지만 충분히 가능합니다.

저는 개발 시 PostgreSQL을 엄격하게 사용하고, 그런 데이터베이스 엔진을 절대 일치시키지 않으며, 잠재적인 비호환성에 대해 스택을 부지런히 모니터링합니다.

글쎄요, 그것은 지루한 작업이며 당신의 훈련에 박수를 보냅니다. 개인적으로 그렇게 하기에는 너무 게으르다. 그러나 모든 잠재적인 미래 개발자, 공동 작업자 또는 기여자에게 그 정도의 근면성을 보장할 수 있습니까?

Errrr- 예, 아니요. 당신은 나를 거기에있어. 다른 사람들은 그것을 엉망으로 만들 것입니다. 당신의 요점은 무엇입니까?

제 요점은 준비 환경이 필요하다는 것입니다. 생산과 비슷하지만 그렇지 않습니다. 스테이징 환경은 프로덕션 배포를 리허설하고 모든 오류를 조기에 포착하는 곳입니다. 내 스테이징 환경은 일반적으로 프로덕션을 미러링하고 성가신 코너 케이스로 인해 마이그레이션이 엉망이 되지 않도록 스테이징 배포 시 프로덕션 데이터베이스 복사본을 덤프합니다. 스테이징 환경을 사용하면 사용자를 기니피그처럼 취급하지 않아도 됩니다.

이것은 의미가 있습니다! 어떻게 해야 하나요?

여기에서 흥미로워집니다. master 를 스테이징에 직접 배포하는 것을 좋아합니다.

잠깐, 우리가 지금 프로덕션을 배포하고 있는 곳이 아닌가요?

예, 하지만 이제 대신 스테이징에 배포할 것입니다.

그러나 master 가 스테이징에 배포하는 경우 프로덕션에 배포하려면 어떻게 해야 합니까?

몇 년 전에 했어야 하는 일을 사용하여 코드 버전 관리 및 Git 태그 푸시.

Git 태그? 누가 Git 태그를 사용합니까?! 이것은 많은 작업처럼 들리기 시작합니다.

그것은 확실했지만 고맙게도 이미 모든 작업을 완료했으며 내 코드를 덤프하면 작동합니다.

스테이징 및 프로덕션 배포 작동 방식 개요

먼저 .gitlab-ci.yml 파일에 스테이징 배포에 대한 블록을 추가합니다. 저는 toptal-pipeline-staging 이라는 새로운 Heroku 앱을 만들었습니다.

 … variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...

그런 다음 프로덕션 블록의 마지막 줄을 마스터 분기 대신 의미적으로 버전이 지정된 Git 태그에서 실행하도록 변경합니다.

 deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619

GitLab은 "보호된" 브랜치만 비밀 변수에 액세스할 수 있을 만큼 똑똑하기 때문에 지금 실행하는 것은 실패합니다. 버전 태그를 추가하려면 GitLab 프로젝트의 저장소 설정 페이지로 이동하여 v* 를 보호된 태그에 추가하십시오.

리포지토리 설정 페이지에서 보호된 태그에 추가되는 버전 태그의 이미지

지금 무슨 일이 일어나고 있는지 요약해 보겠습니다.

  • 마스터로 푸시하거나 태그가 지정된 커밋을 푸시할 때
    • Heroku CLI는 컨테이너에 설치 및 인증됩니다.
    • 코드가 Heroku로 푸시됩니다.
    • 데이터베이스 프로덕션의 백업이 Heroku에서 캡처됩니다.
    • 백업은 스테이징 환경에 덤프됩니다.
    • 마이그레이션은 스테이징 데이터베이스에서 실행됩니다.
  • 의미적으로 버전 태그가 지정된 커밋을 푸시할 때
    • Heroku CLI는 컨테이너에 설치 및 인증됩니다.
    • 코드가 Heroku로 푸시됩니다.
    • 데이터베이스 프로덕션의 백업이 Heroku에서 캡처됩니다.
    • 마이그레이션은 프로덕션 데이터베이스에서 실행됩니다.

당신은 지금 강력하다고 느끼십니까? 힘이 느껴진다. 내가 여기까지 처음 왔을 때 나는 아내에게 전화를 걸어 이 전체 파이프라인을 극도로 자세하게 설명했던 것을 기억합니다. 그리고 그녀는 기술적인 사람도 아닙니다. 나는 나 자신에게 큰 감명을 받았고, 당신도 그랬을 것입니다! 여기까지 왔습니다!

모든 푸시 테스트

하지만 더 많은 것이 있습니다. 컴퓨터는 어쨌든 당신을 위해 일을 하고 있기 때문에 당신이 너무 게으른 모든 일을 실행할 수도 있습니다. 테스트, 오류 보풀, 당신이 하고 싶은 거의 모든 것, 그리고 이들 중 하나라도 실패하면 그들은 이깁니다. 배포로 이동하지 마십시오.

내 파이프라인에 이 기능을 사용하는 것이 좋으며 코드 리뷰가 재미있습니다. 병합 요청이 내 모든 코드 검사를 통과하면 검토할 가치가 있습니다.

푸시할 때마다 테스트하는 이미지

test 블록 추가:

 test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop

지금 무슨 일이 일어나고 있는지 요약해 보겠습니다.

  • 모든 푸시 또는 병합 요청 시
    • Ruby 및 Node는 컨테이너에 설정됩니다.
    • 종속성이 설치됩니다.
    • 앱이 테스트되었습니다.
  • 마스터로 푸시하거나 태그가 지정된 커밋을 푸시할 때, 그리고 모든 테스트가 통과한 경우에만
    • Heroku CLI는 컨테이너에 설치 및 인증됩니다.
    • 코드가 Heroku로 푸시됩니다.
    • 데이터베이스 프로덕션의 백업이 Heroku에서 캡처됩니다.
    • 백업은 스테이징 환경에 덤프됩니다.
    • 마이그레이션은 스테이징 데이터베이스에서 실행됩니다.
  • 의미적으로 버전 태그가 지정된 커밋을 푸시할 때, 그리고 모든 테스트가 통과한 경우에만
    • Heroku CLI는 컨테이너에 설치 및 인증됩니다.
    • 코드가 Heroku로 푸시됩니다.
    • 데이터베이스 프로덕션의 백업이 Heroku에서 캡처됩니다.
    • 마이그레이션은 프로덕션 데이터베이스에서 실행됩니다.

한 발 물러서서 달성한 자동화 수준에 감탄하십시오. 이제부터는 코드를 작성하고 푸시하기만 하면 됩니다. 원한다면 스테이징에서 앱을 수동으로 테스트하고, 앱을 세상에 내놓을 만큼 자신감이 생기면 시맨틱 버전 관리로 태그를 지정하세요!

자동 시맨틱 버전 관리

예, 완벽하지만 뭔가가 빠져 있습니다. 나는 앱의 마지막 버전을 찾아 명시적으로 태그하는 것을 좋아하지 않는다. 여러 명령이 필요하고 몇 초 동안 주의가 산만합니다.

알았어, 그만해! 충분 해. 당신은 지금 그것을 지나치게 엔지니어링하고 있습니다. 작동합니다. 훌륭합니다. 정상을 넘어서서 좋은 것을 망치지 마십시오.

좋아요, 제가 하려고 하는 일을 하는 데에는 그럴만한 이유가 있습니다.

저를 깨우쳐 주소서.

나는 당신과 같은 데 사용됩니다. 이 설정에 만족했지만 나중에 망쳤습니다. git tag 는 태그를 알파벳 순서로 나열합니다. v0.0.11v0.0.2 이상입니다. 한 번 실수로 릴리스에 태그를 지정하고 내 실수를 볼 때까지 약 6개의 릴리스에 대해 계속 태그를 지정했습니다. 그 때 나도 이것을 자동화하기로 결정했습니다.

여기 우리가 다시 간다

자, 고맙게도 npm의 능력을 마음대로 사용할 수 있으므로 적절한 패키지를 찾았습니다. yarn add --dev standard-version 을 실행하고 package.json 파일에 다음을 추가합니다.

 "scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },

이제 마지막으로 기본적으로 태그를 푸시하도록 Git을 구성해야 합니다. 현재로서는 git push --tags 를 실행하여 태그를 위로 푸시해야 하지만 일반 git push 에서 자동으로 수행하는 것은 git config --global push.followTags true 를 실행하는 것만큼 간단합니다.

릴리스 실행을 생성할 때마다 새 파이프라인을 사용하려면:

  • 패치 릴리스용 yarn patch
  • 마이너 릴리스용 yarn minor
  • 주요 릴리스용 yarn major

"major", "minor" 및 "patch"라는 단어가 무엇을 의미하는지 확실하지 않은 경우 의미 체계 버전 관리 사이트에서 이에 대해 자세히 읽어보십시오.

파이프라인을 마침내 완료했으므로 이제 사용 방법을 요약해 보겠습니다.

  • 코드를 작성합니다.
  • 커밋하고 푸시하여 테스트하고 스테이징에 배포합니다.
  • yarn patch 를 사용하여 패치 릴리스에 태그를 지정합니다.
  • git push 를 사용하여 프로덕션으로 푸시합니다.

요약 및 추가 단계

나는 단지 CI/CD 파이프라인으로 가능한 것의 표면을 긁었을 뿐입니다. 이것은 상당히 단순한 예입니다. Heroku를 Kubernetes로 교체하면 훨씬 더 많은 일을 할 수 있습니다. GitLab CI를 사용하기로 결정했다면 배포 간에 파일을 캐싱하거나 아티팩트를 저장하여 할 수 있는 일이 훨씬 더 많기 때문에 yaml 문서를 읽어보세요!

이 파이프라인에 적용할 수 있는 또 다른 큰 변경 사항은 의미 체계 버전 관리 및 릴리스를 실행하기 위한 외부 트리거를 도입하는 것입니다. 현재 ChatOps는 유료 요금제의 일부이며 무료 요금제로 출시되기를 바랍니다. 그러나 단일 Slack 명령을 통해 다음 이미지를 트리거할 수 있다고 상상해 보십시오!

프로덕션 배포가 채팅 또는 웹훅을 통해 외부적으로 트리거되는 CI/CD 배포 파이프라인의 다이어그램

결국 애플리케이션이 복잡해지기 시작하고 시스템 수준 종속성이 필요하므로 컨테이너를 사용해야 할 수도 있습니다. 그런 일이 발생하면 가이드: Docker 시작하기: Devops 단순화 를 확인하세요.

이 예제 앱은 실제로 실행 중이며 여기에서 해당 소스 코드를 찾을 수 있습니다.