오늘은 Java로 개발한 프로젝트를 Github Action과 AWS의 서비스를 활용하여 빌드/배포 자동화를 해보도록 하겠습니다.
CI/CD를 구축하는 방법은 다양하게 있습니다. 저는 그중 Github Action과 AWS의 CodeDeploy를 많이 활용하는데요. 구축 방법도 쉬울뿐더러, 한번 구축해 두면 프로젝트를 깃헙에 Push(merge)만 하여도 테스트와 배포를 자동으로 진행하는 덕분에 필수로 애정하며 도입하는 기술입니다.
현재 프로젝트를 함께 진행중인 세바개 팀에서도 Github Action과 AWS를 활용한 CI/CD를 도입하였습니다. 그래서 이번 게시글에서 위 기술을 활용한 CI/CD 구축 방법을 정리해보려고 합니다.
사용 기술
- Java 17, Spring boot, Gradle
- Git, Github
- Github Action, AWS S3, AWS EC2, AWS CodeDeploy
CI/CD를 구축하기 전에, Java, Spring boot 등 위에 명시한 기술을 기반으로 한 프로젝트가 있다고 가정하여 설명하겠습니다.
GITHUB ACTION
Github Action build script 작성
프로젝트가 업로드된 Github Repository에서 Action 페이지로 들어옵니다. 해당 페이지에서 set up workflow yourself를 클릭하여 main.yml을 생성합니다.
main.yml에 위 명령어를 작성하고 커밋합니다. 중간에 java-version이 있는데요. 프로젝트에서 사용한 자바 버전에 맞게 입력하면 됩니다. 저는 temurin의 17 버전을 사용했기 때문에 위와 같이 작성했습니다.
gradle이 build할 때마다 프로젝트 내에 작성된 테스트 로직을 수행합니다. 이로 인해서 빌드 속도가 많이 느려집니다. 또는 테스트로 인해서 build가 실패할 수도 있습니다. 초기 Github action 구축 단계에서는 테스트 로직을 제외하여도 무방하기 때문에 아래 명령어로 과정을 스킵할 수 있습니다.
run: ./gradlew clean build -x test
위 이미지처럼 초록색의 체크 표시를 확인하면 성공적으로 build 된 것입니다.
- main.yml
name: Sebadog-book server CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
# run: ./gradlew clean build
run: ./gradlew clean build -x test # 테스트 Skip
AWS 구축
1. IAM 사용자 생성
AWS의 IAM(Identity and Access Management) 서비스로 접속합니다. 그리고 액세스 관리-사용자 탭에서 사용자 생성 버튼을 클릭합니다.
1단계[사용자 세부 정보 지정]에서 사용자 이름 입력 후 다음으로 넘어갑니다.
2단계[권한 설정]에서 직접 정책 연결을 체크하고, 권한 정책에서 아래 세 가지를 검새하여 추가합니다.
- AmazonEC2FullAccess
- AmazonS3FullAccess
- AWSCodeDeployFullAccess
3단계[컴토 및 생성]에서 확인 후 사용자 생성을 클릭하고, 생성이 완료될 때까지 기다립니다.
생성한 사용자 정보로 들어오고, 위 이미지와 같이 액세스 키 만들기를 클릭합니다.
기타를 체크하고, 다음 단계로 넘어갑니다. 2단계[권한 설정]는 선택 사항입니다. 지금은 넘어가도록 하겠습니다.
마지막 단계에서 위와 같이 액세스 키 ID와 비밀 액세스 키가 발급됩니다. Github Action에서 사용할 정보이기 때문에 저장하거나 복사합니다.
2. Github Action에 액세스 키 저장
Github Repository - Settings - Secrets and variables - Actions으로 들어갑니다.
해당 페이지의 Repositorty secrets에서 New repository secret 버튼을 클릭합니다.
AWS_ACCESS_KEY_ID : IAM에서 얻은 액세스 키 ID
AWS_SECRET_ACCESS_KEY : 비밀 액세스 키
각각의 정보를 위와 같이 저장합니다.
3. S3 생성
AWS에서 S3 접속 후 버킷을 생성합니다.
AWS 리전을 서울로 선택하고, 버킷 이름을 작성합니다. 보안을 위해 모든 퍼블릭 액세스 차단을 체크하여 버킷 만들기 버튼을 클릭합니다.
IAM에서 만든 사용자로 S3의 FullAccess 권한을 얻기 때문에 버킷 생성에서는 모든 퍼블릭 액세스를 차단한 것입니다.
4. Githhub Action main.yml에 S3 정보 입력
main.yml
name: Sebadog-book server CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
S3_BUCKET_NAME: 버킷이름
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
# run: ./gradlew clean build
run: ./gradlew clean build -x test # 테스트 Skip
# 디렉토리 생성
- name: Make Directory
run: mkdir -p deploy
# Jar 파일 복사
- name: Copy Jar
run: cp ./build/libs/*.jar ./deploy
- name: Make zip file
run: zip -r ./sebadog.zip ./deploy
shell: bash
- 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: ap-northeast-2
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./sebadog.zip s3://$S3_BUCKET_NAME/
main.yml에 위 코드를 작성하고 커밋합니다.
build 한 java 프로젝트를 실행할 jar파일을 압축하고, 해당 폴더를 S3로 업로드하는 방식입니다.
1~3분 후 S3 버킷에 zip파일이 잘 올라간 것을 확인할 수 있습니다.
5. IAM 역할 생성
2개의 역할을 생성할 것입니다.
EC2용 역할
IAM-액세스 관리-역할에 들어가서 역할 생성 버튼을 클릭합니다.
AWS 서비스 체크 후 사용 사례에서 EC2를 선택하고 2단계[권한 추가]로 넘어갑니다. 그리고 아래 2가지를 추가합니다.
- AmazonS3FullAccess
- AWSCodeDeployFullAccess
역할 이름을 작성하고, 위에서 언급한 권한이 추가된 것까지 확인 후 역할을 생성합니다.
CodeDeploy용 역할
또 한 번 IAM 역할에서 역할 만들기를 합니다. 이번엔 사용 사례에 CodeDeploy를 선택합니다.
다음 2단계[권한 설정]까지 건드리지 않고 넘어갑니다. 마지막 역할의 이름을 작성하고 생성합니다.
6. 클라우드 가상 서버 EC2 생성
EC2 접속 후 가장 먼저 우측 상단 드롭박스에서 region을 서울로 변경합니다. (중요)
그리고 인스턴스 시작을 클릭합니다.
인스턴스의 이름을 작성합니다. 운영체제는 Ubuntu를 선택합니다. 인스턴스 유형은 프리티어인 t2.micro를 선택하였습니다. 이어서 새 키 페어를 생성합니다. 키 페어 생성 시 다운로드된 pem 키는 잘 보관합니다.
모든 설정이 완료되었으면 인스턴스를 시작합니다.
생성한 인스턴스를 체크하고, 드롭박스 [작업] - 보안 - IAM 역할 수정을 클릭합니다.
아까 만든 EC2용 IAM 역할을 선택하고 업데이트 버튼을 클릭합니다.
7. EC2 연결
생성한 EC2에 연결합니다. 아래 명령어로 설치를 진행합니다.
sudo apt update
sudo apt install ruby-full
sudo apt install wget
cd /home/ubuntu
sudo apt install awscli
aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2
chmod +x ./install
sudo ./install auto
# 아래 코드로 설치 확인
sudo service codedeploy-agent status
# jdk 17버전 설치
sudo apt-get install openjdk-17-jdk
# java 설치 확인
java -version
프로젝트 개발환경과 일치하는 버전의 jdk를 설치합니다.
8. CodeDeploy 생성
CodeDeploy에서 애플리케이션 생성을 진행합니다. 이름을 작성하고, 플랫폼을 EC2/온프레미스로 선택하고 생성합니다.
배포 그룹 생성을 클릭합니다.
배포 그룹 이름을 작성하고 서비스 역할에 위에서 생성했던 애플리케이션 그룹용 역할(CodeDeploy용 IAM 역할)을 클릭합니다.
아래 환경구성에서 Amazon EC2 인스턴스를 체크합니다. 키에는 Name, 값에는 연결항 EC2 인스턴스를 연결합니다.
이제 배포 그룹을 생성합니다.
9. main.yml에 CodeDeploy 연결 정보 작성
1) appspec.yml 설정
프로젝트 최상단 경로에 appspec.yml 파일을 생성하고, 아래 코드를 입력합니다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app/
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
ApplicationStart:
- location: deploy.sh
timeout: 60
runas: ubuntu
2) deploy.sh 생성
해당 스크립트를 통해서 프로젝트를 배포 후 자동으로 실행하게 되어 무중단 배포를 유지할 수 있습니다.
최상단 경로에 scripts 폴더 > deploy.sh 파일을 생성하고, 아래 코드를 입력합니다.
#!/usr/bin/env bash
REPOSITORY=/home/ubuntu/app
echo "> 현재 구동 중인 애플리케이션 pid 확인"
PORT = $(8080)
CURRENT_PID=$(sudo lsof -t -i:$PORT)
echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"
if [ -z "$CURRENT_PID" ]; then
echo "현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
echo "> kill -9 $CURRENT_PID"
kill -9 $CURRENT_PID
sleep 5
fi
echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/*SNAPSHOT.jar | tail -n 1)
echo "> JAR NAME: $JAR_NAME"
echo "> $JAR_NAME 에 실행권한 추가"
chmod +x $JAR_NAME
echo "> $JAR_NAME 실행"
nohup java -jar -Duser.timezone=Asia/Seoul $JAR_NAME >> $REPOSITORY/nohup.out 2>&1 &
3) main.yml 수정
아래 코드로 수정합니다.
name: Sebadog-book server CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
S3_BUCKET_NAME: sebadogapp-deploy
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
# run: ./gradlew clean build
run: ./gradlew clean build -x test # 테스트 Skip
# 디렉토리 생성
- name: Make Directory
run: mkdir -p deploy
# Jar 파일 복사
- name: Copy Jar
run: cp ./build/libs/*.jar ./deploy
# appspec.yml 파일 복사
- name: Copy appspec.yml
run: cp appspec.yml ./deploy
# script files 복사
- name: Copy script
run: cp ./scripts/*.sh ./deploy
- name: Make zip file
run: zip -r ./[압축파일 이름].zip ./deploy
shell: bash
- 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: ap-northeast-2
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./sebadog.zip s3://$S3_BUCKET_NAME/
# Deploy
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws deploy create-deployment \
--application-name coinTalk[만든 애플리케이션 이름] \
--deployment-group-name coinTalk-group[배포 그룹 이름] \
--file-exists-behavior OVERWRITE \
--s3-location bucket=cointalk-deploy[S3버킷이름],bundleType=zip,key=[압축파일 이름].zip[S3버킷 속 압축파일] \
--region ap-northeast-2
배포 그룹과 애플리케이션의 이름은 CodeDeploy에서 만든 그룹 또는 애플리케이션에서 확인할 수 있습니다.
10. 포트 열기 (EC2 인바운드 규칙 편집)
EC2 - 네트워크 및 보안 - 보안 그룹 - 인스턴스에 적용된 보안 그룹 체크 후 인바운드 규칙 편집 클릭
인스턴스를 클릭하면 적용된 보안 그룹을 바로 확인하거나 편집할 수 있습니다.
위와 같이 허용하고자 하는 포트를 열어줍니다.
CI/CD 테스트
1. Github Action, 코드 변경 감지
코드를 수정하거나 변경 내용을 커밋합니다.
위와 같이 빌드, S3, CodeDeploy까지 통합하는 과정이 성공적으로 진행되었는지 확인합니다. 녹생 체크 표시가 뜨는 것을 확인했다면 통합 및 테스트 과정은 성공한 것입니다.
2. CodeDeploy 배포
AWS CodeDeploy에서 배포 상태를 확인합니다. 성공이라 적혔다면, 배포도 성공하였습니다.
이제 EC2-인스턴스의 퍼블릭 IP:port 혹은 퍼블릭 주소:port 사용하여 확인합니다.
추가로 코드를 변경하였을 때 웹이 정상적으로 변경되는지 확인하면 되겠습니다.
Github Action 혹은 CodeDeploy 과정에서 다소 시간이 걸릴 수 있습니다. 혹여나 과정이 완료되지 않았는데, push나 commit 등 재시도를 한다면 배포 충돌로 인하여 오류가 발생할 수 있습니다.
'개발 > Programming' 카테고리의 다른 글
[트러블 슈팅] '채식이들' 메인 페이지-제품 출력 에러 해결(java.lang.NullPointerException) (2) | 2023.04.16 |
---|---|
[SMTP] 메일 전송을 위한, Google Gmail SMTP 설정 (5) | 2023.03.21 |
[프로그래밍] 자주 사용하는 정규식 표현 정리 (3) | 2023.03.08 |
[IDE] MacOS 터미널(CLI)에서 IntelliJ 바로 실행하기 (2) | 2023.02.27 |
[프로젝트] 중복 데이터를 어떻게 효율적으로 처리할까? (feat.채식이들-핵심기능) (2) | 2022.11.21 |