이전에 쓴 GitHub Actions 글이 조회수가 좋아 업데이트 겸 추가 글을 써본다.
사실 저번에 올린 글은 여기저기 많이 돌아다니는 흔한 자바 프로젝트 배포를 위한 Actions 스크립트를 갖다 쓴것이고, 저번 8월 즈음 시간이 생겨 몇가지 더 챙겨봤다. 해당 전체 코드는 GitHub 링크를 첨부한다.
고려해야 할 것은 두가지 정도로 압축된다. 첫번째는 빌드를 어떻게 할 것인가. 두번째는 배포를 어떻게 할 것인가. 기존의 스크립트는 빌드를 호스트에서 하는것으로 빌드 시간이 호스트에서 발생한다. 그래서 호스트가 여러대라면 N대 만큼 빌드 시간이 발생하는 것이다. JVM 특성상 아키텍처가 뭐든 아티팩트만 잘 만들어지면 되니 빌드 역시 Actions에서 무료로 컴퓨팅 리소스를 빌려서 하자. 두번째는 첫번째에서 따라오는 자연스러운 이야기인데 빌드를 호스트에서 하지 않으니 당연히 아티팩트를 전송해야하는 부분이다.
바뀐점 보기
기존의 Actions가 git clone → build → jar starup 모두의 과정이 담긴 스크립트를 원격에서 실행하는게 전부였다. 이번에 바뀐것은 크게 빌드, 배포, 서비스 재기동 세부분으로 나뉜다.
jobs 내부를 보자.
## 1. 빌드
build:
runs-on: ubuntu-latest
steps:
## 형상 다운로드
- name: Checkout repository
uses: actions/checkout@v2
## 빌드
- name: Build package
run: ./gradlew bootJar
## 빌드 아티팩트 확인
- name: Show structure of artifact files
run: ls -ahl ./build/libs/*.jar
## 빌드 아티팩트 업로드
- name: Upload build artifact for job
uses: actions/upload-artifact@v3
with:
## 업로드 키 값 (임의지정)
name: project_artifact
## Gradle의 경우
path: "./build/libs/*.jar"
gradle의 예를 가지고 왔다. git clone
이후 gradlew bootJar
를 하고 아티팩트를 업로드 한다. upload-artifact
를 통해 업로드 된 아티팩트는 일정 기간이 지나면 삭제되는 것으로 알고있다. 정확한 기간은 기억나지 않는다. 다음은 배포다.
## 2. 배포
distribute:
## 빌드가 선행되야함.
needs: [ build ]
runs-on: ubuntu-latest
steps:
## 아티팩트 전송을 위한 임시 공간
- name: Create distribute
run: mkdir ~/dist
## 빌드 한 아티팩트 다운로드
- name: Download build artifact for job
uses: actions/download-artifact@v3
with:
name: project_artifact
path: dist
## 아티팩트 확인
- name: Show structure of downloaded files
run: |
ls -alh dist
새로운 actions로 download-artifact
를 이용해 업로드한 아티팩트를 받는다. 이걸 바로 전송하게 하는 방법은 또 다시 호스트에서 요청을 던져야 하는 방법이라 어쨋든 전송이 목적이라면 scp로 바로 밀어넣는게 좋을듯 하다. 계속 가자
## 서버에 아티팩트 전송
- name: Transfer artifact
uses: appleboy/scp-action@master
with:
username: ${{ secrets.USERNAME }}
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
key: ${{ secrets.KEY }}
rm: true
source: "dist/*.jar"
target: ${{ secrets.DIST_PATH }}
## 작업에 사용했던 dist 디렉토리를 경로상에서 제거
strip_components: 1
## 전송된 아티팩트 확인
- name: Show structure of transfer files
uses: appleboy/ssh-action@master
with:
username: ${{ secrets.USERNAME }}
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
key: ${{ secrets.KEY }}
script: |
cd ${{ secrets.DIST_PATH }}
ls -alh
적절한 전송 방법을 찾다보니 appleboy
의 ssh-action
말고 scp-action
가 있어서 바로 활용했다. 전송 이후에 아티팩트 확인하는 부분은 없어도 되지만, 호스트 ssh 열어놓고 파일 떨어지나 안떨어지나 볼게 아니라면 달아놓으면 여러모로 손이 덜간다.
## 3. 서비스 재기동
service_restart:
## 앞 작업이 선행되고 성공해야 함
needs: [ build, distribute ]
runs-on: ubuntu-latest
steps:
- name: Call service restart
uses: appleboy/ssh-action@master
with:
username: ${{ secrets.USERNAME }}
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
key: ${{ secrets.KEY }}
script: |
cd ${{ secrets.DIST_PATH }}
echo "서비스 종료 스크립트. pgrep 부분 개선 및 루프.."
echo "서비스 시작 스크립트"
세번째는 기존의 appleboy/ssh-action
를 이용해 형상에 포함된 스크립트를 실행하는 부분과 같다. 나는 아래와 같이 활용하고 있다.
- name: Call service restart
uses: appleboy/ssh-action@master
with:
username: ${{ secrets.USERNAME }}
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
key: ${{ secrets.KEY }}
## 배포 스크립트를 저장소로부터 항상 최신으로 가져오고, 실행.
script: |
cd ${{ secrets.DIST_PATH }}
rm -rf repo
git clone https://github.com/platanus-kr/plata-board.git repo
mv ./repo/scripts/deploy.sh ./deploy.sh
sh deploy.sh
어짜피 형상을 받는다는 점이 좀 마음에 들진 않지만 따로 deploy용 스크립트를 버전관리 하는게 더 귀찮아서 그냥 이렇게 했다. 당연히 배포용 스크립트 gradle/maven 빌드내용이 전부 제거되고 아래와 같이 간결해진다.
#!/bin/bash
source ~/.bash_profile
APP_NAME=plata-board
REPOSITORY=`pwd`
RELEASE=production
# .jar 파일 타겟팅
JAR_NAME=$(ls $REPOSITORY | grep jar)
# 현재 실행중인 서버가 있으면 잡아서 종료 시킵니다.
CURRENT_PID=$(pgrep -f $APP_NAME)
if [ -z $CURRENT_PID ]
then
echo ">>>> java process not found."
else
echo ">>>> PID: $CURRENT_PID kill."
kill -15 $CURRENT_PID
sleep 45
fi
# .jar 파일 java 실행합니다.
echo ">>>> $JAR_NAME java execute."
echo "nohup java -jar ./$JAR_NAME --spring.config.location=classpath:/application.properties --spring.profiles.active=$RELEASE > /dev/null 2> /dev/null < /dev/null &"
nohup java -jar ./$JAR_NAME --spring.config.location=classpath:/application.properties --spring.profiles.active=$RELEASE > /dev/null 2> /dev/null < /dev/null &
sleep 20
CURRENT_PID=$(pgrep -f $APP_NAME)
echo ">>>> New PID: $CURRENT_PID"
굳이 같는 내용을 echo로 찍는 이유는 첫번째로 환경변수가 제대로 할당되는지고 두번째로는 이것들이 actions 로그에 남아서 원격지를 들어가보지 않아도 로그를 볼 수 있다는 점이다.
빌드하기
master 브랜치 PR등으로 트리거가 일어나면 아래와같이 시작된다. 수동으로도 실행 할 수 있다.
그리고 중간중간 일부러 ls를 찍는것도 다 아래와 같이 따로 확인할 필요 없이 로그에 남기 때문이다.
서비스 재시작 역시 마찬가지다.
배포가 완료되면 각 단계별로 시간이 얼마나 걸렸는지도 알 수 있다.
더 해볼만한 생각
이 3단계에서 아티팩트 전송 단계를 더 세분화해 롤링 배포 등을 고려해볼 수 있겠다. 블루그린 전략은 L4 API까지 손을 댈 수 있어야 가능해 보인다. 이 글에서는 GitHub Actions로 아티팩트를 어떻게 들고 움직이고 배포에 활용할 수 있는지 엿볼 수 있는 과정을 보여주려고 했다.