첫 프로젝트에 Travis CI와 CodeDeploy를 이용하여 EC2서버에 자동배포를 하는 CI/CD를 구축했었다. 나는 Travis CI가 평생 무료인줄 알았는데 10000크레딧을 다 사용하면 유료로 서비스를 이용할 수 있다고 한다. 그래서 무료로 서비스 하는 다른 CI 서비스가 있는지 찾아보던 중 Github Action이 있다는 걸 알게 되었다.
사실 Travis CI를 쓰면서 잔 에러가 너무 많아서 초기에 구축할 때 너무 힘든 기억이 있다. 자료는 많지 않지만 그래도 Github Action은 깃헙에서 제공하는 서비스다 보니까 잔 에러가 많지 않을 것 같고 깃허브 페이지에서 바로 확인을 할 수 있어서 너무 좋을 것 같았다. 거기에다 무료니까~
다행히 CI와 CodeDeploy를 이용한 CD를 요청하는 법을 구글링하니까 찾을 수 있었다. 먼저 Github Action 환경에서 빌드가 성공적으로 되는지 확인하기 위해 deploy.yml 파일을 만들고 수정해 봤다.
수정하면서 생각해 보니 데이터베이스 정보가 담긴 application.yml을 어떻게 암호화할지가 문제였다. Travis CI를 쓸 때는 이 기능을 지원해 줘서 내가 파일을 등록하면 자동으로 Travis CI가 복호화 해서 빌드 할 때 파일을 만들어주었다(이건 편했네).
그래서 찾아보던 중 JASYPT라는 것으로 암호화를 하는 법을 알아냈다. 그래서 중요한 정보가 담긴 부분을 암호화해주면 되는데 나는 Oauth 클라이언트 키와 데이터베이스 등을 해주었다. JASYPT의 비밀번호를 빌드 할 때 입력해 줘야 하는데 이것을 환경 변수로 등록해서 주입시켜주는 방법으로 진행했다.
Github 레포지토리에서 Secret을 이용해서 비밀번호를 설정하고 deploy.yml에 해당 값을 환경변수로 등록하는 코드를 넣어주면 된다.
name: CI
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-18.04
env:
JASYPT_PASSWORD : ${{ secrets.JASYPT_PASSWORD }}
steps:
- name: 테스트
run: echo "$JASYPT_PASSWORD 입니다"
- name: 체크아웃 Github Action
uses: actions/checkout@v2
- name: JDK 11 설치
uses: actions/setup-java@v1
with:
java-version: 11
- name: gradlew 실행 권한 부여
run: chmod +x gradlew
shell: bash
- name: Build with Gradle
run: ./gradlew clean build --info
shell: bash
우선 내가 사용하는 EC2의 운영체제와 같은 우분투 18버젼에서 빌드가 되도록 설정했다. 그리고 build시 log를 남기기위해서 --info 변수도 넣어주고.
저번 Travis CI 구축할 때도 그렇고, 프로젝트를 진행하면서 순탄하게 뭔가 된 게 없었던 것 같다(오히려 순탄하면 이상해). 역시 빌드 할 때 또 에러가 났다..
처음에는 Github Action 로그에서 JASYPT_PASSWROD 환경 변수가 제대로 적용이 안된 건가 싶어서 출력을 해봤더니 *** 로 나오더라. 찾아보니 이건 제대로 입력된 게 맞고, 시크릿이라서 그런 거란다.
그래서 뭐가 문제지? 하고 빌드를 여러 번 시도해 봤는데 같은 특정한 테스트에서 계속 오류가 났다. 그래서 에러를 찾기 위해 이것저것 범위를 좁혀가며 관찰하던 중에 테스트 코드를 몇 개 주석 처리하고 하니까 에러가 났던 테스트 코드가 정상적으로 성공했다. 뭔가를 발견한 느낌
더 자세히 로그를 뒤져보니 DB 연결과 관련된 오류였다. 빌드를 시작한 지 어느 정도 시간이 지나면 그때부터 에러가 발생했는데 DB 커넥션이 이상한 거 같다.
이제부터 삽질의 시간 시작이다. 찾아보니 Hikari 설정 아니면 MySQL 설정을 바꿔야 될 거 같은데 MySQL은 커넥션 타임아웃이 8시간으로 되어있다. 그럼 분명 문제가 아닌데... 그래서 Hikari 설정들을 여러 개 바꿔보면서 시도해 봤는데 안되더라...
그렇게 2일 동안 에러 고치는데 Hikari설정 중에 max-lifetime을 줄여보니까 빌드가 성공적으로 됐다! max-lifetime은 커넥션을 얼마나 유지할 것인지에 대한 설정이고 default가 30분이다. 난 당연히 길게 잡아야 빌드가 끝날 때까지 커넥션이 유지되서 좋을 줄 알았는데, 5분으로 설정해야 중간에 커넥션을 다시 만들 수 있다. 근데 DB의 커넥션 유지 시간이 분명 8시간인데 default값으로 했을 때 연결이 이상했는지 알 수가 없다. 헌데 5분으로 하면 오히려 커넥션을 미리미리 검증할 수 있어서 더 좋은 선택인 것 같다.
사실 이 뻘짓은 내 영어 실력이 부족해서 그런 거도 있다. 로그를 자세히 보면 max-lifetime을 줄이라는 조언이 있는데 내가 해석을 잘못한 거다... 에휴 영어 공부 열심히 하자. 로그도 자세히 읽고.
+그래도 참 신기하다. 뭔가 문제가 터졌을 때 진짜 노력하면 어떻게든 해결법이 있더라. 다른 차선택을 고를 때도 있지만. 솔직히 코딩을 하는 시간보다 에러를 잡는 시간이 더 길다. 좋은 개발자가 되기 위해서 이렇게 에러를 잡으려는 끈기를 더 길러야겠다.
+힌트를 얻은 글 https://www.inflearn.com/questions/98336
CI 구축을 마친 후에 이제 CD를 구축하면 된다. 나는 이전에 Travis CI에서 사용했던 방법 그대로 CodeDeploy를 사용할 예정이다.
name: CI
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-18.04
env:
JASYPT_PASSWORD : ${{ secrets.JASYPT_PASSWORD }}
steps:
- name: 테스트
run: echo "$JASYPT_PASSWORD 입니다"
- name: 체크아웃 Github Action
uses: actions/checkout@v2
- name: JDK 11 설치
uses: actions/setup-java@v1
with:
java-version: 11
- name: gradlew 실행 권한 부여
run: chmod +x gradlew
shell: bash
- name: Build with Gradle
run: ./gradlew clean build --info
shell: bash
- name: zip 파일 생성
run: |
mkdir -p before-deploy
cp scripts/*.sh before-deploy/
cp appspec.yml before-deploy/
cp build/libs/*.jar before-deploy/
cd before-deploy && zip -r ./$GITHUB_SHA.zip *
cd ../ && mkdir -p deploy
mv before-deploy/$GITHUB_SHA.zip deploy/$GITHUB_SHA.zip
shell: bash
- name: AWS 자격 증명
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: S3 업로드
run: aws s3 cp --region ap-northeast-2 ./deploy/$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/$GITHUB_SHA.zip
- name: CodeDeploy 배포
run: aws deploy create-deployment --application-name covid19vaccinereview --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name covid19vaccinereview-group --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=$GITHUB_SHA.zip
CodeDeploy로 자동 배포를 진행하려면 먼저 S3 버킷에 나의 빌드 결과물을 올려주어야 한다. 그래서 간단히 zip 파일을 압축하여 올렸다. 굳이 필요 없는 파일들은 올리지 않고 빌드 결과물인 jar 파일과 CodeDeploy로 실행할 스크립트 파일들만 지정해 주었다.
+$GITHUB_SHA는 깃허브에서 제공하는 변수로 현재 커밋의 이름을 불러올 수 있게 해준다.
+Github Action의 run 옵션에 | 을 넣어주면 복수개의 명령어를 실행할 수 있다.
다음으로 진짜 CodeDeploy에 올리기 위해 명령어 코드를 넣어줬다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/build/zip/
overwirte: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
AfterInstall:
- location: stop.sh
timeout: 120
runas: ubuntu
ApplicationStart:
- location: start.sh
timeout: 120
runas: ubuntu
ValidateService:
- location: health.sh
timeout: 120
runas: ubuntu
혹시 몰라 CodeDeploy의 appsec.yml 파일도 첨부하겠다. 이렇게하면 이제 CodeDeploy에서 스크립트들을 실행하여 자동 배포가 완성된다!
역시나 또 예상치 못한 작은 문제가 있었다. CodeDeploy는 bash 환경변수를 가져오지 못해서 환경변수를 직접 등록해줘야한다.
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
source /etc/jasypt_password # EC2 서버에 미리 만들어둔 jasypt_password를 환경변수에 등록
echo "> JASYPT_PASSWORD = ${JASYPT_PASSWORD}"
REPOSITORY=/home/ubuntu/build
PROJECT_NAME=covid19vaccinereview
echo "> Build 파일 복사"
echo "> cp $REPOSITORY/zip/*.jar $REPOSITORY/"
cp $REPOSITORY/zip/*.jar $REPOSITORY/
echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1)
echo "> JAR NAME: $JAR_NAME"
echo "> $JAR_NAME 실행권한 추가"
chmod +x $JAR_NAME
echo "> $JAR_NAME 실행"
IDLE_PROFILE=$(find_idle_profile)
echo "> $JAR_NAME 를 profile=$IDLE_PROFILE 로 실행합니다."
nohup java -jar \
-Dspring.profiles.active=$IDLE_PROFILE \
$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
내 CodeDeploy에서 실행하는 스크립트 중 start.sh 파일이다(이건 이동욱 님의 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 참고하여 작성함). 나는 간단하게 미리 EC2 서버의 /etc 경로에 jasypt_password라는 파일을 미리 정의해두었다. 그리고 start.sh 파일이 실행될 때 해당 환경 변수를 등록하는 방식을 선택했다. AWS의 Parameter Store를 사용해도 되지만 간단한 환경 변수 하나로 코드가 조금 더 불어날 거 같아서 간편한 방법을 선택했다.
자 이렇게 Travis CI에서 Github Action으로 CI 환경을 옮겨봤다. 내 테스트 코드들이 통합 테스트 코드라 많이 무거운 것을 알고 있다. 그런데도 Travis CI에서는 빌드 시간이 대략 10분이 걸렸었다. 내 맥북에서는 2분 정도 걸리는데 Travis CI에서 제공하는 컴퓨터가 느린 탓이라 생각했는데 Github Action도 그 정도 걸리더라 ㅎㅎ..
지금은 통합 테스트 코드 위주로 짜여있지만 프로젝트가 어느 정도 완성도를 갖추게 되면 리팩토링과 유지 보수하는 기간에 단위 테스트 코드도 추가할 생각이다!