
졸업작품을 진행할 때 우리가 만든 공동구매 서비스를 ec2에 배포했었다.
그러나 그때는 ec2에 jar파일을 업데이트하고, 기존 프로세스를 중단시키고 새로운 프로세스를 실행하는 모든 작업을 수동으로 했었다. 그 작업은 내가 담당했었는데 코드가 수정될때마다 서버에 들어가서 위의 작업을 반복하는 것이 굉장히 귀찮고 번거로웠다.
그때는 나도 처음 스프링부트를 통해 프로젝트를 진행하던 때였고, 배포도 처음 해보는 거라 주어진 과제를 해결하는 것에 급급했던 시기이기에 이렇게 번거로운 작업을 이어나갈 수 밖에 없었다.
좀 더 규모가 큰 프로젝트를 하며, 배포를 어떻게 쉽게 할 수 있을 까를 찾아보다 쉘스크립트를 통해 서버에서 명령어 치는 행위를 한번에 할 수 있다는 사실을 알게되었고, 나아가 github action을 통한 방법이 있다는 것을 알게 되었다
이를 공부하며 어제 처음으로 실습을 해보았고, 잊지 않기 위해 과정을 정리해보려고 한다.
먼저 배포 스크립트를 작성하여 명령어 하나로 서버에 배포(재배포)하는 방법을 알아보자
배포 스크립트 작성
나는 sources라는 디렉토리에 deploy-practice라는 github 저장소를 clone했다.
그리고 프로젝트 폴더가 있는 위치(sources)에서 빌드 스크립트를 작성했다.
vim deploy.sh
그리고 deploy.sh 라는 파일이 생성되었다면, 다음과 같이 스크립트를 입력한다.
# 프로젝트 폴더가 위치해있는 디렉토리
REPOSITORY=/home/ubuntu/sources
PROJECT_NAME="deploy-practice"
# 프로젝트 디렉토리로 들어가기
cd $REPOSITORY/$PROJECT_NAME
echo "> GIT PULL"
git pull
echo "> START TO BUILD PROJECT"
./gradlew build
echo "> COPY JAR FILES"
cp ./build/libs/*.jar $REPOSITORY/
echo "> 현재 구동중인 어플리케이션의 PID 확인"
CURRENT_PID=$(pgrep -f $PROJECT_NAME)
if [ -z $CURRENT_PID ]; then
echo "현재 구동중인 APPLICATION이 없으므로 종료하지 않습니다."
else
echo "kill -15 $CURRENT_PID"
echo "kill -15 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 5
fi
echo "DEPLOY NEW APPLICATION"
JAR_NAME=$(ls $REPOSITORY |grep $PROJECT_NAME | tail -n 1)
echo "JAR NAME : $JAR_NAME"
# nohup을 통한 무중단 배포
nohup java -jar $REPOSITORY/$JAR_NAME &
# 프로젝트 폴더가 위치해있는 디렉토리, 프로젝트 이름(깃 저장소 명)
REPOSITORY=/home/ubuntu/sources
PROJECT_NAME="deploy-practice"
# 프로젝트 디렉토리로 들어가기
cd $REPOSITORY/$PROJECT_NAME
일단은 프로젝트가 위치한 폴더 내부로 들어가기위한 코드이다.
여기서 deploy-practice는 clone한 폴더명으로 여기 안에 코드가 있다.
git pull
만일 프로젝트가 업데이트 되었다면 pull을 하여 최신화 시켜준다.
./gradlew build
프로젝트를 빌드 시킨다. (gradle 프로젝트)
cp ./build/libs/*.jar $REPOSITORY/
빌드 결과로 생겨난 jar 파일들을 모두 REPOSITORY 주소로 복사한다.
CURRENT_PID=$(pgrep -f $PROJECT_NAME) # 현재 프로젝트 명으로 검색함(현재 프로젝트 이름은 deploy-practice)
if [ -z $CURRENT_PID ]; then
echo "현재 구동중인 APPLICATION이 없으므로 종료하지 않습니다."
else
echo "kill -15 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 5
fi
현재 이 프로젝트를 기반으로한 프로세스가 동작중이라면, 그 프로세스를 없애고 새 프로세스를 실행하기 위한 코드다.
우선 CURRENT_PID에는 현재 프로젝트가 프로세스로 올라가있다면, 그 프로세스의 ID 값이 얼마인지 저장한다.
프로젝트 이름이 deploy-practice이기 때문에 deploy-practice로 pgrep -f 옵션을 통해 검색을 해준다.
만약에 PID 값이 존재한다면 프로세스를 kill 하고 5초를 sleep 걸어준다.
JAR_NAME=$(ls $REPOSITORY |grep $PROJECT_NAME | tail -n 1)
REPOSITORY에서 디렉토리를에서 deploy-practice 라는 이름을 가진 파일을 찾아내고, 만약 이름이 겹친 다면 가장 최신의 파일을 JAR_FILE 변수에 넣는다.
nohup java -jar $REPOSITORY/$JAR_NAME &
nohup을 통해서 jar file을 무중단 실행시키는데, & 옵션을 통해서 nohup을 통해 배포된 프로젝트가 백그라운드에서 동작하게 한다.
배포 스크립트 실행
먼저, deploy.sh에 실행 권한을 부여해줘야 한다.
chmod 755 ./deploy.sh
그 후에 deploy.sh를 실행합니다.
./deploy.sh
이미 실행중인 프로세스가 있다면, 그를 중단시키고 업데이트된 프로세스를 실행시킬 것이고, 없다면 새로운 프로세스를 실행시킬 것이다.
Github Actions를 사용하여 배포
다음은, Github Actions 를 사용해서 AWS EC2 에 자동으로 배포하는 방법에 대해 알아보자
이를 위해서는 S3과 codeDeploy라는 것이 필요하다.
배포 방법
main 브랜치에 Push 하면 자동으로 EC2 까지 배포되는 Workflow 를 만들 것이다.
배포하는 방법은 여러 가지 있겠지만 AWS 의 경우 소스 코드를 압축하여 AWS 스토리지에 저장 후 서버에 전달해서 실행하는 방법이 있다.
AWS S3에 빌드 파일을 압축해서 업로드 하고, CodeDeploy를 활용하여 AWS EC2에 배포하는 것이다.
정리하면, 아래의 과정으로 진행된다.
- Github Actions 에서 코드 build하기
- AWS 인증하기
- 빌드파일 압축해서 AWS S3 에 업로드하기
- AWS CodeDeploy 실행하여 S3 에 있는 코드를 EC2 에 배포하기
여기서 CodeDeploy 는 S3에 업로드 된 빌드 파일을 EC2에서 끌어쓸수 있게 도와주는 역할을 한다.
- AWS EC2 인스턴스 생성
- AWS EC2 설정 추가
- AWS S3 버킷 생성
- AWS CodeDeploy 앱 생성 및 배포 설정
- Github Actions 에서 사용할 사용자 권한 추가
- AppSpec 파일 작성
- 배포 스크립트 작성
- Github Actions Workflow 작성
1. AWS 인스턴스 생성
프리티어를 통해 ubuntu 20.04 LTS 버전 EC2를 생성한다.
그 후 탄력적 ip를 할당해준다.
2. EC2 설정 추가
1. Tag 추가
CodeDeploy 를 생성할 때 어떤 인스턴스에서 수행할 지 지정하기 위해 태그를 사용한다. 인스턴스를 지정하는 이름이라고 생각하면 된다.
- 인스턴스 관리 페이지에서 작업 -> 인스턴스 설정 -> 태그 관리로 들어가 원하는 키값(태그네임)을 입력하고 저장한다. (ex - ec2-tag)
- EC2 인스턴스 정보 태그탭에서 방금 등록한 태그가 저장 되었는지 확인할 수 있다.
2. IAM 역할 추가
EC2 인스턴스에서 S3 에 올려놓은 파일에 접근할 수 있도록 권한을 추가해줘야 한다.
- IAM -> 역할 -> 새로운 역할 만들기로 들어간다.
- AWS서비스, EC2를 선택하고 , EC2 인스턴스에서 S3 접근할 수 있도록 AmazonS3FullAccess 권한을 추가한다.
- 마지막으로 원하는 이름을 입력한 뒤 생성을 완료합니다. (ex - ec2-iam)
3. EC2 인스턴스에서 IAM 연결
- 인스턴스 관리 페이지에서 "작업 > 보안 > IAM 역할 수정" 을 선택한다.
- 위에서 만든 EC2 전용 IAM 역할을 선택한 뒤(ex - ec2-iam) 저장을 누르면 연결이 완료된다.
3. CodeDeploy Agent 설치
$ sudo apt update
$ sudo apt install ruby-full
$ sudo apt install wget
$ cd /home/ubuntu
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto > /tmp/logfile
$ sudo service codedeploy-agent status
CodeDeploy Agent 설치 에 나와있는 대로 명령어를 입력하면 된다. ubuntu버전마다 다른 부분이 있기 떄문에 버전을 유의하여 입력해야 한다.
정상적으로 설치가 완료되면, sudo service codedeploy-agent status 명령어를 입력했을 때 active(running)상태가 뜬다.
4. AWS S3 생성
S3 버킷이란 이미지 또는 zip 파일을 저장하기 위한 스토리지 서비스다.
전에 이미지 파일을 저장하기 위해 사용한 적이 있었는데 이런 의도로 사용될 수도 있구나 느꼈다.
빌드한 프로젝트 코드를 압축해서 S3 에 저장한 후 EC2 서버에서 S3 에 접근해서 압축한 파일을 가져오기 위해 사용할 수 있다.
S3 버킷 생성
S3 -> 버킷 -> 버킷 만들기 -> 원하는 버킷 이름(ex s3-bucket)과 리전을 선택 -> 나머지 기본값으로 두고 버킷 생성을 완료한다.
5. CodeDeploy 생성
5.1 CodeDeploy 전용 IAM 역할 만들기
CodeDeploy 를 사용하기 위해선 IAM 에서 역할을 만들어야 합니다.
IAM -> 역할 -> 역할 만들기로 들어간다.
AWS를 선택하고 CodeDeploy를 검색해서 선택해준다.
역할 이름을 지정한다(ex - codedeploy-iam)
5.2 CodeDeploy 애플리케이션 생성
CodeDeploy -> 배포 -> 에플리케이션 -> 에플리케이션 생성 버튼을 누른다.
원하는 이름을 입력 후(ex codedeploy-app) 컴퓨팅 플랫폼은 EC2/온프레미스 를 선택한 후 생성해준다.
5.3 CodeDeploy 배포 그룹 생성
방금 만든 애플리케이션에서 배포 그룹 생성 버튼을 누른다.
원하는 배포 그룹 이름(ex - codedeploy-group), 역할, 유형을 설정합니다.
서비스 역할은 위에서 만든 IAM 역할(ex - codedeploy-iam)을 선택한다.
Amazon EC2인스턴스 선택, 위에서 설정한 태그값(ex ec2-tag)를 선택한다.
에이전트 설치는 한번만 선택, 배포구성은 CodeDeployDefault.AllAtOnce선택한다.
로드밸랜서는 비활성화 하고, 배포그룹을 생성한다.
5.4 Github Actions 에서 사용할 IAM 사용자 추가
AWS 를 Github Actions 에서 접근하려면 권한이 필요하다.
그래서 IAM 사용자를 추가해야한다.
IAM -> 사용자 -> 사용자 추가 버튼을 누른다.
사용자 이름 추가한다.
정책 직접 연결 -> AWSCodeDeployFullAccess, AmazonS3FullAccess권한 추가
태그는 생략하고 사용자를 만들어준다.
예전에는 사용자를 만들고 나면 "액세스 키 ID" 와 "비밀 액세스 키" 가 마지막에 나왔지만 지금은 그런게 없는 것 같다.
그래서 직접 방금 만든 iam user의 보안 자격 증명에서 따로 엑세스키를 만들어주었다.
만든 엑세스키는 csv파일을 로컬에 잘 보관해둬야 한다.
5.5 Github Repository 의 Secrets 추가
이 두 개의 키 값을 사용해서 IAM 권한을 획득할 수 있다. 이를 Github Actions 에서 사용할 수 있도록 등록해준다.
Github > Repository > Settings > Secrets 로 이동해서 위 키 값들을 등록할 수 있다.
6. AppSpec 파일 작성
지금까지 서버를 띄울 EC2, 배포할 결과물을 저장할 S3, 배포를 도와줄 CodeDeploy 를 만들었다.
이제 CodeDeploy 에서 배포를 위해 참조할 파일인 AppSpec을 만들어야 한다.
appspec.yml파일은 프로젝트 코드의 루트 디렉토리에 위치해야한다.(중요함)
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/deploy-practice
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
ApplicationStart:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
여기서 deploy-practice는 프로젝트 네임 즉, 깃허브 레포명이다.
7. 배포 스크립트 작성
바로 위 appspec.yml 에서 실행할 스크립트 deploy.sh 를 설정했습니다.
deploy.sh를 작성해야 한다.
#!/usr/bin/env bash
PROJECT_NAME="deploy-practice"
PROJECT_ROOT=/home/ubuntu/$PROJECT_NAME
APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"
TIME_NOW=$(date +%c)
echo "> 현재 구동 중인 애플리케이션 pid 확인"
CURRENT_PID=$(pgrep -f $PROJECT_NAME)
echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"
if [ -z "$CURRENT_PID" ]; then
echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG
else
echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
kill -15 $CURRENT_PID
sleep 5
fi
cp $PROJECT_ROOT/build/libs/*.jar $PROJECT_ROOT/
JAR_NAME=$(ls $PROJECT_ROOT | grep $PROJECT_NAME | tail -n 1)
echo "> $JAR_NAME 에 실행권한 추가"
chmod u+x $JAR_NAME
# jar 파일 실행
echo "$TIME_NOW > $JAR_NAME 파일 실행" >> $DEPLOY_LOG
nohup java -jar $PROJECT_ROOT/$JAR_NAME > $APP_LOG 2> $ERROR_LOG &
CURRENT_PID=$(pgrep -f $JAR_NAME)
echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG
appspec, deploy.sh파일 위치
처음에 이 위치가 너무 헷갈렸는데 appspect는 프로젝트 루트에 위치해야만 하고, deploy는 scripts니까 scipts 폴더안에 넣는 것이 좋다.

8. build.gradle 파일 수정
위 스크립트를 보면 cp $PROJECT_ROOT/build/libs/*.jar 파일을 $PROJECT_ROOT/로 복사한다.
그런데 Spring Boot 2.5 버전부터는 빌드 시 일반 jar 파일 하나와 -plain.jar 파일 하나가 함께 만들어 지기 때문에, 빌드 시 plain jar 파일은 만들어지지 않도록 build.gradle 파일에 다음 내용을 추가해야 다.
jar {
enabled = false
}
9. Github Actions Workflow 작성
마지막으로 Github Actions 워크 플로우를 작성한다.
repository -> action 에서 아무 workflow를 선택 한다.
deploy.yml파일을 작성한다.
name: Deploy to Amazon EC2
on:
push:
branches:
- main
# 본인이 AWS에서 설정한 값들 할당
# 리전, 버킷 이름, CodeDeploy 앱 이름, CodeDeploy 배포 그룹 이름
env:
AWS_REGION: ap-northeast-2
S3_BUCKET_NAME: s3-bucket
CODE_DEPLOY_APPLICATION_NAME: codedeploy-app
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: codedeploy-group
permissions:
contents: read
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
# (1) 기본 체크아웃
- name: Checkout
uses: actions/checkout@v3
# (2) JDK 11 세팅
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
# (3)-0 gradle 권한 주기
- name: Run chmod to make gradlew executable
run: chmod +x ./gradlew
# (3) Gradle build (Test 제외)
- name: Build with Gradle
uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
with:
arguments: clean build -x test
# (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
- name: Configure AWS credentials
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: ${{ env.AWS_REGION }}
# (5) 빌드 결과물을 S3 버킷에 업로드
- name: Upload to AWS S3
run: |
aws deploy push \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--ignore-hidden-files \
--s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
--source .
# (6) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
- name: Deploy to AWS EC2 from S3
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip
Gradle 권한 에러
Error Gradle Script ‘~~~’ Is Not Executable 발생하여 권한 추가하는 코드 추가하였다.
# (3)-0 gradle 권한 주기
- name: Run chmod to make gradlew executable
run: chmod +x ./gradlew
10. Github Actions 사용해서 배포
이제 모든 세팅이 끝났다.
yaml 파일에 설정한 것처럼 main 브랜치에 push 되는 경우 Github Actions Workflow 가 수행된다.
action탭에서 배포 성공, 실패 여부를 확인할 수 있다. 단계별 여부도 확인가능하다.

그리고 code deploy에서 배포내역을 확인할 수도 있다.

'DevOps' 카테고리의 다른 글
| [ docker ] got permission denied while trying to connect to the docker daemon socket 에러 (0) | 2022.12.16 |
|---|