백엔드 부트캠프 과정에서 기존에 제공되는 온라인 강의 중에는 배포에 대한 내용이 별로 없어서 건의가 들어갔고, 이에 배포 관련 온라인 강의가 추가되었고 배포 특강이 3월 4일에 진행되었다.
(글을 작성하는 시점은 강의가 진행되었던 날에서 조금 시간이 흐른 뒤임을 미리 밝힌다..!)
프로젝트를 진행하면서 필연적으로 배포를 하게 되는데, EC2, 라이트세일, 파이어베이스 등등 어느 것을 사용하든 수정사항이 없을 수는 없다. 수정사항이 생기면 jar 파일부터 다시 만들고, 이를 다시 인스턴스에 올리고 실행하는 과정을 거치게 된다.
시나리오 1: 혼자 기능을 개발하는 경우
git에 push ➡ git pull로 빌드 실행 ➡ java -jar 실행 ➡ 동작 테스트 ➡➡ 최신 버전 유지 (반복)
현업에서는 실제로 서비스하는 경우이기 때문에 서버를 최소 2개를 올려 메인 서버에는 서비스를 올리고, 그 외의 서버에서는 테스트나 개발을 진행한다.
시나리오 2: 실무진이 각각 기능 맡아 개발
개발 서버(develop)와 상용 서버(main)에 각각 서버별 push ➡ 서버별로 git pull ➡ 기존 실행 서비스 중지하고 빌드 실행 ➡ 동작 테스트 ➡➡ 최신 버전 유지
그런데 수동으로 최신 버전을 유지하는 과정에서 충돌이 나거나 오류가 발생하면 서비스가 중지될 수 있다. 이런 상황을 피하기 위해 배포 과정을 자동화할 수 있다.
생산성을 중시하는 회사 입장에서는 반복적인 배포 작업을 줄여 효율을 높이는 CI/CD를 사용하게 된다.
"새로운 코드 변경사항이 정기적으로 빌드 및 테스트 되어 공유 레포지토리에 통합되는 과정"
배포를 포함하지 않고, 정상적으로 레포지토리에 통합되는 과정까지를 의미한다.
ex) A와 B가 작업하는데 변경사항 c가 컨플릭트가 났다 -> c가 레포지토리에 반영이 되면 안된다.
코드 변경 사항들이 자동화된 테스트와 배포 준비 과정을 거쳐 언제든지 신뢰할 수 있는 방식으로 "배포할 준비"가 되어 있는 상태
테스트를 통과하면 자동으로 생산 환경에 배포되는 과정을 의미한다. 배포 과정을 전부 자동화하여, 변경사항이 빠르게 서비스에 적용된다.
주로 사용하는 라이브러리는 Jenkins, GitHub Actions, Travis CI라고 한다.
이 강의에서는 GitHub Actions를 사용하여 배포를 자동화하는 법을 배웠다. (*따로 Docker를 사용하지 않고 jar 파일을 라이트세일 인스턴스에 배포하는 식으로 진행되었다)
GitHub 레포지토리 탭에 Actions가 있는데, 이를 누르면 어떤 라이브러리를 사용하여 workflow를 만들지 추가할 수 있다.
추가하면 레포지토리에 .github/workflows라는 폴더가 새로 생기고, 내부에 yml 파일이 생성된다.
yml 파일에 다음과 같은 내용을 추가하였다.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions: write-all
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Add permission
run: chmod +x gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build -Pprofile=dev
- "on"
- push와 pull request에 반응하는 이벤트 트리거로, 해당 이벤트가 main 브랜치에서 발생하면 workflow에 등록된 작업들이 실행된다.
- "jobs"
- 실행할 작업을 정의한다.
- "runs-on"
- 작업을 실행할 가상 환경을 정의한다.
- "steps"
- 작업을 단계별로 정의해둔 것으로, 앞의 단계가 끝나야 다음 단계로 넘어간다.

실행에 성공하면 초록색 체크 표시가 뜨고, 실패하면 메일도 날아온다.
인스턴스를 생성한다. (강의 이후 요금 메일이 날아온 관계로 라이트세일 인스턴스를 삭제했기 때문에 복습할 때는 EC2를 사용하였다.)
SSH를 사용하여 연결 혹은 .pem 키를 다운받아 원격으로 접속한다.
(강의에서는 Tabby를 사용하였다.)
Linux 환경을 설정해준다. (+강의에서는 추가로 NginX를 사용하였다)
sudo apt update # 패키지 업데이트 sudo apt upgrade # 패키지 업그레이드 sudo apt install nginx # nginx 설치 mkdir app # app이라는 디렉터리 생성 cd app # app 디렉터리로 이동 ```
/api로 오는 모든 요청을 스프링으로 전달하므로, Controller 코드를 추가한다.
인스턴스에 git repository를 clone한다.
GitHub 레포지토리의 Settings-Secrets and variables-Actions-Repository secrets에 보안과 직결되는 비밀 값들을 추가한다.

기존의 workflow yml 파일에 배포 단계를 추가한다.
...
- name: SSH Agent 설정
uses: webfactory/ssh-agent@v0.5.3
with:
ssh-private-key: ${{ secrets.DEVELOP_SSH_KEY }}
- name: 배포 파일전송
uses: easingthemes/ssh-deploy@v2.1.5
env:
SSH_PRIVATE_KEY: ${{ secrets.DEVELOP_SSH_KEY }}
REMOTE_HOST: ${{ secrets.DEVELOP_HOST }}
REMOTE_USER: ${{ secrets.DEVELOP_USERNAME }}
SOURCE: 'build/libs/'
TARGET: '~/app/spring-ci-cd-test'
EXCLUDE: '/**/*.jar'
- name: jar 파일 실행
run: |
ssh -o StrictHostKeyChecking=no ${{ secrets.DEVELOP_USERNAME }}@${{ secrets.DEVELOP_HOST }} << EOF
pkill -f 'spring-ci-cd-test-0.0.1-SNAPSHOT.jar' || true
cd ~/app/ci_cd_test
nohup java -jar -Dspring.profiles.active=develop spring-ci-cd-test-0.0.1-SNAPSHOT.jar > app.log 2>&1 &
EOF
env:
SSH_PRIVATE_KEY: ${{ secrets.DEVELOP_SSH_KEY }}
토이 프로젝트는 배포가 필수가 아니어서 여태 크게 신경쓰지 않았지만, 미니 프로젝트나 파이널 프로젝트에서는 프론트와 결합하게 된다. 하여 배포는 필수로 익혀야 하고, 또 그 중요성이 큰 스킬임을 확실히 느꼈다.
구글에 검색하면 스크립트는 많이 나온다. 하지만 무작정 베껴쓰기보다는 실제로 이 스크립트가 어떤 의미인지, 어떤 작업을 실행하는 지를 알아두는 것도 중요한 것 같다.
실제로 인스턴스에 프로젝트를 빌드하고 띄워보면서, 로컬에서 혼자 뚝딱대는 것과 달리 다른 사람도 내가 만든 서비스를 사용해볼 수 있는 것이 신기했다. 이번 기회에 NginX를 강사님의 안내 하에 처음으로 사용해봤는데, 이후 미니 프로젝트 멘토링을 진행하면서도 언급되었던 스킬 중 하나였다. 사용하지는 못했는데, 프록시에 대해서도 공부해 두어야겠다는 생각을 했다...
개인적으로는 Docker를 사용하여 이미지로 배포하는 것이 가장 편한 것 같다. Docker와 Docker-compose도 좀 더 공부해 두어야겠다!