해당 글은 뱀귤 블로그의 'Github Actions CD: AWS EC2 에 Spring Boot 배포하기' 글을 기반하여 작성되었습니다.(https://bcp0109.tistory.com/363)
위 블로그 포스팅과의 차이점
Github Action은 작성된 workflow에 따라 특정 이벤트가 발생하면 일련의 명령을 수행합니다.
이를 이용하여 AWS 배포 및 컨테이너 구동까지 CI/CD를 구축
하겠습니다.
전반적인 흐름은 다음과 같습니다.
- Github에서 이벤트 생성(main 브랜치 push 등)
- Github Actions 실행됨
2-1. 프로젝트 빌드(Maven)
2-2. 빌드된 프로젝트 압축
2-3. AWS S3에 Push
2-4. CodeDeploy 명령 전달- AWS CodeDeploy 배포 진행
3-1. EC2에 배포 명령
3-2. 프로젝트를 도커이미지로 빌드
3-3. 도커 컨테이너 실행(서버 구동)
따라서 Github에서 CodeDeploy와 S3에 접근하기 위한 권한이 필요하고,
EC2에서 S3에 접근하기 위한 권한도 필요하기 때문에 이를 위한 설정이 필요합니다.
CI/CD 구축을 위해 준비해야할 것들과 해야 할 일입니다.
이번 포스팅은 아래와 같이 크게 3파트로 나눌 수 있습니다.
AWS 환경 준비 및 Github Secrets 추가
- EC2, S3, CodeDeploy 생성
- 역할 지정(IAM)
- Github Secrests에 키 추가
명령 파일 작성
- AppSpec 작성(appspec.yml)
- 배포 스크립트 작성(deploy.sh)
- Dockerfile 작성(Dockerfile)
- workflow 작성(main.yml)
구동 확인
- EC2 배포 확인
- 백서버 구동 확인
참고) 이 게시글의 방식은 코드 배포 후 도커 이미지를 빌드하도록 진행하였으나,
AWS 에서 공식적으로 가이드하는 방법은 크게 두 가지가 있습니다.
EC2 인스턴스 생성 하기
https://velog.io/@chanmin/Cloud-1.-AWS-EC2-생성
EC2 인스턴스에서 S3에 접근할 수 있도록 권한을 추가하기위해
IAM 역할을 생성하고 인스턴스에 선택(할당)하겠습니다.
상단의 검색창에 IAM을 입력하고, IAM 서비스로 이동합니다.
IAM 서비스 페이지에서 좌측 메뉴 목록에서
'엑세스 관리' > '역할'을 선택후 역할 만들기를 진행합니다.
엔터티 유형은 AWS 서비스,
사용 사례는 EC2로 선택합니다.
EC2 인스턴스에서 S3에 접근하기 위한 권한을 추가합니다.
AmazonS3FullAccess
역할 이름을 지정하고 역할을 생성합니다.
생성한 IAM 역할을 EC2 인스턴스의 설정에서 연결하도록 하겠습니다.
EC2 페이지로 돌아와서 인스턴스의 '보안' > 'IAM 역할 수정'으로 이동합니다.
생성한 IAM을 선택한 후 인스턴스의 IAM 역할을 업데이트합니다.
EC2의 인스턴스에서 CodeDeploy Agent를 설치합니다.
(SSH를 통한 EC2 인스턴스 접속 방법은 EC2 생성 게시글 참조)
아래 명령어를 따라 설치를 진행합니다.
$ 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
설치 완료 후 codedeploy-agent의 실행 상태를 확인합니다.
$ sudo service codedeploy-agent status
Active :active (running)으로 확인되면 설치가 정상적으로 완료된 것입니다.
단독 스토리지로도 사용할 수 있으며 EC2, EBS, Glacier와 같은 다른 AWS 서비스와도 함께 사용할 수 있어 클라우드 어플리케이션, 컨텐츠 배포, 백업 및 아카이빙, 재해 복구 및 빅데이터 분석 등을 위해 다양하게 활용할 수 있습니다.
우리는 빌드한 프로젝트를 저장하기 위한 스토리지로 활용할 것 입니다.
AWS페이지에서 S3를 검색하여 S3 서비스 페이지로 이동합니다.
S3 메뉴에서 "버킷 만들기"를 누릅니다.
버킷 이름과 AWS 리전을 선택합니다.
리전은 이전에 생성한 EC2 인스턴스와 같은 서울로 설정하겠습니다.
다음으로 객체 소유권은 권장 사항(ACL 비활성화됨)
을 선택합니다.
나머지 설정은 디폴트로 두고, 기본 암호화에서 버킷 키를 비활성화 합니다.
다음과 같이 버킷이 생성된 것을 확인할 수 있습니다.
위에서 생성한 S3에 빌드파일이 업로드되면, Codedepdoly가 업로드된 파일을 EC2 인스턴스에 배포하고 필요한 작업(실행 등)을 수행합니다.
CodeDeploy를 사용하기 위해 IAM에서 역할을 만들어야합니다.
EC2에 적용할 IAM을 생성할 때와 같은 페이지로 이동합니다.
IAM 서비스 페이지에서 좌측 메뉴 목록에서
'엑세스 관리' > '역할'을 선택후 역할 만들기를 진행합니다.
엔터티 유형은 AWS 서비스,
사용 사례는 CodeDeploy로 선택합니다.
기본으로 지정되어있는 권한을 사용합니다.
AWSCodeDEployRole
역할 이름을 지정하고 역할을 생성합니다.
사용할 CodeDeploy 애플리케이션을 생성합니다.
상단의 검색창에 IAM을 입력하고, IAM 서비스로 이동합니다.
CodeDeploy 서비스 페이지에서 좌측 메뉴 목록에서
'배포' > '애플리케이션'을 선택후 애플리케이션 생성을 진행합니다.
애플리케이션 이름을 지정하고,
컴퓨팅 플랫폼은 'EC2/온프레미스'로 지정합니다.
위에 생성한 CodeDeploy앱을 사용하는 배포 그룹을 생성합니다.
CodeDeploy 페이지에서 좌측 메뉴중
'배포' > '애플리케이션'에서 위에서 생성한 CodeDeploy앱을 선택 후
배포 그룹 생성을 눌러 진행합니다.
배포 그룹 이름을 설정하고,
서비스 역할을 위에서 생성한 IAM을 선택합니다.
배포 유형은 '현재 위치'로 지정합니다.
환경 구성은 'Amazon EC2 인스턴스'를 선택하고,
키로 EC2 인스턴스의 키를 등록합니다.
AWS CodeDeploy 에이전트 설치는 '한 번만'을 선택,
배포 설정으로 'CodeDeployDefaultAllAtOnce을 선택,
로드 밸런서는 비활성화 합니다.
이번에는 IAM 사용자를 추가해보겠습니다.
상단의 검색창에 IAM을 입력하고, IAM 서비스로 이동합니다.
IAM 서비스 페이지에서 좌측 메뉴 목록에서
'엑세스 관리' > '사용자'을 선택후 사용자 추가를 진행합니다.
사용자 이름을 지정하고,
AWS Management Console관련 체크는 선택하지 않습니다.
권한 옵션으로 '직접 정책 연결'을 선택하고,
권한 정책에서 CodeDeploy와 S3에 대한 권한 2개를 추가합니다.
AWSCodeDeployFullAccess
AmazonS3FullAccess
사용자 생성을 완료합니다.
Github Actions에서 사용할 수 있도록
IAM 권한에 대한 Access Key ID 및 Secret Key를 발급합니다.
IAM 서비스 페이지에서 왼쪽 메뉴 중
'엑세스 관리' > '사용자'에서 위에서 추가한 사용자 이름을 선택하여 들어갑니다.
다음 요약 하단의 '권한/그룹/태그/보안 자격증명/엑세스 관리자' 메뉴 중
'보안 자격증명'을 선택하고 엑세스 키 만들기를 진행합니다.
엑세스 키 모범 사례 및 대안에서
'Command Line Interface(CLI)를 선택합니다.
설명 태그 값을 작성하고, 엑세스 키를 발급합니다.
이제 발급된 엑세스 키와 비밀 엑세스 키를 Github Actions에서 사용하겠습니다.
Github Repository의 'Settings' > 'Security' > 'Secrets' > 'Actions'에서 Repository secrets을 동록합니다.
'New repository secret'으로 키를 등록합니다.
AWS_ACCESS_DEY_ID
AWS_SECRET_ACCESS_KEY
AWS_REGION
아래와 같이 세가지 키를를 등록해주었습니다.
키 name은 적당히 편한 것으로 설정합니다.
지금까지 EC2, S3, CodeDeploy 생성과 각 역할에 대한 설정을 마쳤습니다.
이제 서버를 띄울 공간(EC2), 배포 파일을 업로드할 공간(S3), 배포를 도와줄 CodeDeploy가 준비되었습니다.
다음으로 빌드 파일의 배포 경로와 배포 프로세스 이후 수행할 스크립트(도커 이미지 생성 및 컨테이너 실행)를 지정하는 AppSpec 파일을 작성하겠습니다.
CodeDeploy는 AppSpec 파일에 작성된 명령을 수행합니다.
AppSpec파일의 위치는 프로젝트의 최상단(로트 디렉토리)에 위치해야합니다.
확장자가 yml(야물)인 appspec.yml 파일을 생성해줍니다.
yaml은 xml과 json과 같이 타 시스템 간에 데이터를 주고받기 위해 약속된 포맷(규칙)이 정의된 파일 형식입니다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
AfterInstall:
- location: deploy.sh
timeout: 500
runas : root
아래는 각 섹션별 설명입니다.
file 섹션
배포 파일에 대한 설정입니다.
- source: 인스턴스에 복사할 디렉터리 경로
- destination: 인스턴스에서 파일이 복사되는 위치
- overwrite: 복사할 위치에 파일이 있는 경우 대체
permissions 섹션
files 섹션에서 복사한 파일에 대한 권한 설정입니다.
- object: 권한이 지정되는 파일 또는 디렉터리
- pattern (optional): 매칭되는 패턴에만 권한 부여
- owner (optional): object 의 소유자
- group (optional): object 의 그룹 이름
hooks 섹션
배포 이후에 수행할 스크립트를 지정할 수 있습니다.
위 코드에서는 파일을 설치한 후 AfterInstall 에서 기존에 실행중이던 컨테이너 종료와 도커 이미지 및 컨테이너를 삭제하고, 새로운 도커 이미지 빌드와 컨테이너를 실행합니다.
- location: hooks 에서 실행할 스크립트 위치
- timeout (optional): 스크립트 실행에 허용되는 최대 시간이며, 넘으면 배포 실패로 간주됨
- runas (optional): 스크립트를 실행하는 사용자
배포 스크립트파일의 위치는 프로젝트의 최상단(로트 디렉토리)에 위치해야합니다.
확장자가 sh인 Deploy.sh 파일을 생성해줍니다.
상단의 변수들은 본인에게 맞게 수정하여 사용하시길 바랍니다.
#!/usr/bin/env bash
APP_NAME=<본인의 APP 이름>
REPOSITORY=/home/ubuntu/
echo "> Check the currently running container"
CONTAINER_ID=$(docker ps -aqf "name=$APP_NAME")
if [ -z "$CONTAINER_ID" ];
then
echo "> No such container is running."
else
echo "> Stop and remove container: $CONTAINER_ID"
docker stop "$CONTAINER_ID"
docker rm "$CONTAINER_ID"
fi
echo "> Remove previous Docker image"
docker rmi "$APP_NAME"
echo "> Build Docker image"
docker build -t "$APP_NAME" "$REPOSITORY"
echo "> Run the Docker container"
docker run -d -p 3000:8080 --name "$APP_NAME" "$APP_NAME"
#!/usr/bin/env bash
명령어가 bash 환경에서 실행됨을 명시해줍니다.
APP_NAME=<본인의 APP 이름>
REPOSITORY=/home/ubuntu/
도커 이미지 및 컨테이너 이름과, 빌드 파일의 경로를 변수로 적어줍니다.
f [ -z "$CONTAINER_ID" ];
then
echo "> No such container is running."
else
echo "> Stop and remove container: $CONTAINER_ID"
docker stop "$CONTAINER_ID"
docker rm "$CONTAINER_ID"
fi
현재 같은 이름으로 실행중인(사전에 배포된) 컨테이너가 있다면 중지하고 컨테이너를 삭제합니다.
echo "> Remove previous Docker image"
docker rmi "$APP_NAME"
같은 이름으로 빌드된 도커 이미지(사전에 빌드된)을 삭제합니다.
echo "> Build Docker image"
docker build -t "$APP_NAME" "$REPOSITORY"
도커 이미지를 빌드합니다.
echo "> Run the Docker container"
docker run -d -p 3000:8080 --name "$APP_NAME" "$APP_NAME"
도커 컨테이너를 실행합니다. 컨테이너의 3000번 포트와 스프링의 포트 8080을 연결해주었습니다.
Dockerfile의 위치는 프로젝트의 최상단(로트 디렉토리)에 위치해야합니다.
Dockerfile 파일을 생성해줍니다.
FROM openjdk:11-jdk
EXPOSE 8080
# JAR_FILE 변수 정의 -> 기본적으로 jar file이 2개이기 때문에 이름을 특정한다.
ARG JAR_FILE=target/*.jar
# JAR 파일 메인 디렉토리에 복사
COPY ${JAR_FILE} app.jar
# 시스템 진입점 정의
ENTRYPOINT ["java","-jar","/app.jar"]
FROM openjdk:11-jdk
생성될 이미지의 베이스가 jdk 11임을 뜻합니다.
EXPOSE 8080
이미지의 포트 번호를 지정합니다. 스프링 부트 내장 톰캣의 기본 포트인 8080으로 설정해주었습니다.
해당 포트를 컨테이너를 구동 시 외부에 제공할 포트와 연결해줄 것입니다.
# JAR_FILE 변수 정의 -> 기본적으로 jar file이 2개이기 때문에 이름을 특정한다.
ARG JAR_FILE=target/*.jar
# JAR 파일 메인 디렉토리에 복사
COPY ${JAR_FILE} app.jar
# 시스템 진입점 정의
ENTRYPOINT ["java","-jar","/app.jar"]
(주석 참고 바람)
이제 마지막으로 Github Actions workflow를 작성합니다.
이를 통해 Github의 main 브랜치에 특정 이벤트가 발생할 때 입력한 동작이 자동으로 시행되도록 구성합니다.
Github Repository의 'Actions' 탭에서 'Simple workflow'를 검색 후 'Configure'합니다.
workflow 파일의 확장자를 yml로 지정하고 파일을 작성합니다.
상단의 변수들은 본인에게 맞게 수정하여 사용하시길 바랍니다.
# workflow의 이름
name: Deploy to Amazon EC2 / Spring Boot with Maven
# 환경 변수 $변수명으로 사용
env:
PROJECT_NAME: <본인의 프로젝트 이름>
BUCKET_NAME: <본인이 S3에 생성한 버킷의 이름>
CODE_DEPLOY_APP: <본인이 생성한 CodeDeploy APP 이름>
CODE_DEPLOY_DEPLOYMENT_GROUP: <본인이 생성한 배포 그룹 이름>
# 해당 workflow가 언제 실행될 것인지에 대한 트리거를 지정
on:
release:
types: [created]
# workflow는 한개 이상의 job을 가지며, 각 job은 여러 step에 따라 단계를 나눌 수 있습니다.
jobs:
build:
name: CI/CD
# 해당 jobs에서 아래의 steps들이 어떠한 환경에서 실행될 것인지를 지정합니다.
runs-on: ubuntu-latest
steps:
# 작업에서 액세스할 수 있도록 $GITHUB_WORKSPACE에서 저장소를 체크아웃합니다.
- uses: actions/checkout@v2
# Spring 구동을 위한 JDK 11을 세팅합니다.
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'zulu'
# Caching dependencies (디펜던시를 캐싱하여 반복적인 빌드 작업의 시간을 단축할 수 있다.)
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
# Build
- name: Build with Maven
run: mvn -B package --file pom.xml
# build한 후 프로젝트를 압축한다.
- name: Make zip file
run: zip -r ./$PROJECT_NAME.zip .
shell: bash
# aws 인증서비스
# github repository에서 Setting에서 사용할 암호화된 변수
- 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: ${{ secrets.AWS_REGION}}
# Upload to S3 stroage
- name: Upload to S3
run: aws s3 cp $PROJECT_NAME.zip s3://$BUCKET_NAME/deploy/$PROJECT_NAME.zip --region ap-northeast-2
# CodeDeploy에게 배포 명령을 내린다.
- name: Code Deploy
run: >
aws deploy create-deployment --application-name $CODE_DEPLOY_APP
--deployment-config-name CodeDeployDefault.AllAtOnce
--deployment-group-name $CODE_DEPLOY_DEPLOYMENT_GROUP
--s3-location bucket=$BUCKET_NAME,bundleType=zip,key=deploy/$PROJECT_NAME.zip
주석을 참고바랍니다.
하단의 CodeDeploy관련 명령어 설명입니다.
- --application-name: CodeDeploy 애플리케이션 이름
- --deployment-config-name: 배포 방식인데 기본값을 사용
- --deployment-group-name: CodeDeploy 배포 그룹 이름
- --s3-location: 버킷 이름, 키 값, 번들타입
위의 workflow는 릴리즈가 발생할 때 작동하도록 작성되었습니다.
만약 main 브랜치에 push 이벤트에 작동하도록 하려면 트리거의 내용을 아래의 코드로 대체해 주세요.
on:
push:
branches:
- main
GithubActions은 우리가 작성한 workflow(main.yml)의 내용을 기반으로 동작합니다.
프로젝트 릴리즈 이벤트를 발생시켜 GithubActions이 동작하도록 하겠습니다.
(main repo 'push' 이벤트 등 본인이 지정한 이벤트를 발생시키면 됩니다.)
Github Repo의 Actions 메뉴에서 확인할 수 있습니다.
Github Actions이 동작한 것을 확인할 수 있습니다.
이 과정을 통해 릴리즈된 프로젝트는 자동으로 빌드(.jar)되고,
AWS의 S3 버킷에 업로드 되며, CodeDeploy에게 배포 명령을 전달합니다.
BeginVegan의 이름으로 배포한 빌드 압축파일이 업로드 된 것을 확인할 수 있습니다.
인스턴스에 잘 배포된 것을 확인 할 수 있습니다.
배포를 하며 발생한 이벤트를 상세히 확인 할 수 있습니다.
배포에 문제가 발생하여 실패한 경우 'View events'에 기록된 로그를 확인하시길 바랍니다.
이벤트별로 진행 상태를 확인할 수 있습니다.
SSH로 EC2의 인스턴스에 접속하여 확인해보겠습니다.
배포 경로로 들어가서 확인하였습니다.
다음과 같이 잘 배포된 것을 확인할 수 있습니다.
docker iamge ls
위 명령어를 통해 생성된 도커 이미지를 확인하였습니다.
docer ps
위 명령어를 통해 구동중인 컨테이너를 확인하였습니다.
컨테이너 포트가 3000번으로 열려있는 것을 확인할 수 있습니다.
EC2 인스턴스의 퍼블릭 IPv4 주소에 포트번호로 접속을 하였습니다.
백 서버가 잘 동작하는 것을 확인할 수 있습니다.