Github Actions, AWS CodeDeploy를 사용한 CI/CD 구축
CI도구로 Github Actions와 CD도구로 AWS CodeDeploy를 사용해서 CI/CD 환경을 구축할 것이다.
프론트엔드는 React이고 백엔드는 Spring Boot로 구현한 애플리케이션이다.
스테이징서버와 운영서버가 두개의 EC2 인스턴스로 구축되어 있다.
스테이징서버 : develop / 운영서버 : master 브랜치를 각각 따르고 있다.
스테이징서버는 프론트엔드와 백엔드 모두 EC2 인스턴스에 배포할 것이며
운영서버는 프론트엔드는 S3버킷에 백엔드는 EC2 인스턴스에 각각 배포할 것이다.
배포 과정

AWS IAM
- USER : 외부 서비스에 할당 가능 (key값 제공)
- ROLE : AWS 서비스만 할당 가능
1. AWS CLI용 USER
- 배포시 EC2 인스턴스의 AWS CLI에서 사용할 권한
2. EC2 배포용 ROLE
- 배포시 EC2가 사용하는 권한
-
AWS서비스-EC2 ->
S3FullAccess,CodeDeployFullAccess-> 태그설정

- 스테이징서버, 운영서버 EC2 인스턴스 2개 모두에 생성한 권한 부여
- EC2 인스턴스 -> 보안 -> IAM 역할 수정
- EC2 인스턴스 -> 보안 -> IAM 역할 수정
3. CodeDeploy용 ROLE
- 배포시 CodeDeploy가 사용하는 권한
- AWS서비스-CodeDeploy ->
CodeDeployFullAccess-> 태그설정
백엔드 (스프링부트)
- 스테이징서버 : EC2
- 운영서버 : EC2
1. 테스트 application.yml 분리
-
application.yml에 DB접속정보가 포함되어 있을 것이다. 하지만 노출되어서는 안되는 정보이기 때문에.gitignore에 관리되어 깃허브에 업로드 되지 않는다. - 접속정보가 필요하지 않은(노출되어도 무방한) 내장 인메모리 디비를 사용하여 테스트를 진행해야 한다.
- 내장 인메모리 디비는 테스트 진행시 올라왔다가 테스트가 종료되면 내려가 흔적이 남지 않게 된다.
-
test/resources/application.yml을 사용해 내장 인메모리 디비로 테스트가 진행되도록 할 수 있다.- 혹은, 접속정보가 아예 존재하지 않는다면 스프링은 자동으로 내장 인메모리 디비에 연결한다.
- 간혹 스프링이 테스트 config를 찾지 못한다면
test/resources/config/application.yml로 이동시켜보자.
- 디비 접속정보 이외의 비밀키같이 정보를 스프링이 빈 생성에 참조할 때 존재하지 않으면 에러가 발생한다.
- 임시값을 대체해 넣어주면 된다.
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:test
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
defer-datasource-initialization: true
app:
auth:
token:
secret-key: tmp
refresh-cookie-key: tmp
oauth2:
authorizedRedirectUris: tmp
→ defer-datasource-initialization : hibernate 구동시 data.sql이 실행되도록 설정
2. 외부 yml 적용
-
application.yml이 깃허브에 업로드 되지 않기 때문에 미리 서버에 만들어 놓고 스프링이 구동할 때 참조하도록 설정해야 한다 -
Application.class-
classpath:로 시작하는 파일 경로는 프로젝트 내부의src/main/resources/이하 경로이다 - 프로젝트 외부의
application.yml을 추가로 설정한다 - 개발서버에서는 프로젝트 내부의 설정파일을 운영서버에서는 외부의 설정파일을 참조하도록 했기 때문에
optional을 사용했다
-
@SpringBootApplication
public class Application {
public static final String APPLICATION_LOCATIONS = "spring.config.location="
+ "optional:classpath:application.yml,"
+ "optional:/usr/local/myapp/application.yml";
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.properties(APPLICATION_LOCATIONS)
.run(args);
}
}
3. CodeDeploy 생성
- CD 과정을 맡을 CodeDeploy 애플리케이션, 배포그룹을 생성한다
- 여기서 생성된 배포그룹은 생성시 태그로 선택된 EC2에서 설치된 CodeDeploy 에이전트에 의해 실행된다
(1) 애플리케이션

(2) 배포그룹
- 스테이징서버에 적용할 그룹
myapp-staging과 운영서버에 적용할 그룹myapp-prodution2개 생성 - 배포그룹이름 -> 서비스역할 : CodeDeploy용 ROLE 선택 -> 배포유형 : 현재위치 -> EC2인스턴스 -> EC2 태그추가(Name) -> AllAtOnce -> 로드밸런스 활성화 해제

4. 서버에 AWS CLI 및 CodeDeploy 에이전트 설치
- IAM : AWS CLI용으로 발급한 USER 사용
- EC2 인스턴스에서 배포시 사용하기 위해 설치한다
$ sudo yum -y update
$ sudo yum install -y aws-cli
$ sudo aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: ap-northeast-2
Default output format [None]: json
$ aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2
$ chmod +x ./install
$ sudo yum -y install ruby
$ sudo ./install auto
$ sudo service codedeploy-agent status
The AWS CodeDeploy agent is running as PID xxx
5. Github Actions (CI)
(0) Secrets 적용
- 노출되어서는 안되는 비밀키값은 Github에 Secrets로 등록하면 yml실행시 참조할 수 있다
-
Github -> Repository내의 Settings -> Secrets -> Actions -> New Repository Secrets

- 그리고 Github Actions가 사용할 yml 파일을 작성한다
- name : 해당 Action(Workflow)의 이름
- on : 아래 jobs를 실행시킬 조건(이벤트 or 트리거)
- jobs : 커맨드가 모인 집합
- steps : 각 각의 실핼될 커맨드단계
(1) deploy-staging.yml
-
develop브랜치에 push/merge가 이루어지면 스테이징서버에서 CI가 동작한다
name: deploy-staging
on:
push:
branches: [ develop ]
jobs:
deploy: # job 이름
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout Github-Actions # Github Actions 사용을 위한 체크아웃
uses: actions/checkout@v2
- name: Install Java 11
uses: actions/setup-java@v1
with:
java-version: '11'
- name: Permission for Gradle
run: chmod +x ./gradlew
- name: Start Build with Gradle
run: ./gradlew clean build
- name: Setting for AWS # AWS 권한셋팅
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: ap-northeast-2
- name: Upload to S3 # 빌드파일 S3 업로드
run: aws deploy push --application-name myapp --description "myapp Test" --s3-location s3://myapp/deploy/myapp-staging.zip --source .
- name: Start Deploy with CodeDeploy # CodeDeploy 실행
run: aws deploy create-deployment --application-name myapp --deployment-config-name CodeDeployDefault.OneAtATime --deployment-group-name myapp-staging --s3-location bucket=myapp,bundleType=zip,key=deploy/myapp-staging.zip
- Upload to S3
-
--application-name: CodeDeploy 애플리케이션 이름 -
--s3-lication: 빌드파일을 업로드할 S3 위치 -
--source .: 현재 폴더를 S3에 업로드
-
- Start Deploy with CodeDeploy
-
--application-name: CodeDeploy 애플리케이션 이름 -
--application-group-name: 애플리케이션 내의 배포그룹 이름 -
--s3-location: 가져올 배포파일 위치 및 정보
-
(2) deploy-production.yml
-
master브랜치에 push/merge가 이루어지면 운영서버에서 CI가 동작한다
name: deploy-production
on:
push:
branches: [ master ]
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout Github-Actions # Github Actions 사용을 위한 체크아웃
uses: actions/checkout@v2
- name: Install Java 11
uses: actions/setup-java@v1
with:
java-version: '11'
- name: Permission for Gradle
run: chmod +x ./gradlew
- name: Start Build with Gradle
run: ./gradlew clean build
- name: Setting for AWS # AWS 권한셋팅
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: ap-northeast-2
- name: Upload to S3 # 빌드파일 S3 업로드
run: aws deploy push --application-name myapp --description "myapp" --s3-location s3://myapp/deploy/myapp-production.zip --source .
- name: Start Deploy with CodeDeploy # CodeDeploy 실행
run: aws deploy create-deployment --application-name myapp --deployment-config-name CodeDeployDefault.OneAtATime --deployment-group-name myapp-production --s3-location bucket=myapp,bundleType=zip,key=deploy/myapp-production.zip
6. CodeDeploy (CD)
(1) appspec.yml
- CodeDeploy는 S3에 업로드된 빌드파일을 EC2 인스턴스로 가져온 후
appspec.yml을 실행한다 -
appspec.yml은deploy.sh를 훅으로 실행한다
version: 0.0
os: linux
files:
- source: /
destination: /usr/local/myapp
overwrite: yes
permissions:
- object: /usr/local/myapp
owner: ec2-user
group: ec2-user
mode: 755
hooks:
AfterInstall:
- location: deploy.sh
timeout: 60
runas: ec2-user
(2) deploy.sh
-
appspec.yml에 의해 실행되는 배포 스크립트 - 실행중이던 이전 스프링 프로세스를 중단시키고 새로운 프로세스를 실행시킨다
#!/usr/bin/env bash
REPOSITORY=/usr/local/myapp
cd $REPOSITORY
APP_NAME=myapp
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1)
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME
CURRENT_PID=$(pgrep -f $APP_NAME)
echo "> Kill Previous Process" >> /usr/local/myapp/deploy.log
if [ -z "$CURRENT_PID" ]
then
echo "> No Process to Kill" >> /usr/local/myapp/deploy.log
else
echo "> Kill $CURRENT_PID" >> /usr/local/myapp/deploy.log
kill -15 "$CURRENT_PID" >> /usr/local/myapp/deploy.log 2>&1
sleep 5
fi
echo "> $JAR_PATH Deploy"
nohup java -jar $JAR_PATH >> /usr/local/myapp/nohup.log 2>&1 &
프론트엔드 (리액트)
- 스테이징서버 : EC2 (백엔드와 동일 서버)
- 운영서버 : S3
1. CodeDeploy 생성
(1) 애플리케이션

(2) 배포그룹
- 프론트엔드의 경우 운영서버는 S3 정적호스팅을 사용하기 때문에 EC2를 사용하는 스테이징서버를 위한 배포그룹만 생성하면 된다.
- 배포그룹이름 -> 서비스역할 : CodeDeploy용 ROLE 선택 -> 배포유형 : 현재위치 -> EC2인스턴스 -> EC2 태그추가(Name) -> AllAtOnce -> 로드밸런스 활성화 해제
2. Github Actions (CI)
(1) deploy-staging.yml
-
develop브랜치에 push/merge가 이루어지면 스테이징서버에서 CI가 동작한다
name: deploy-staging
on:
push:
branches: [ develop ]
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
strategy:
matrix:
node-version: [16.14.x]
steps:
- name: Checkout Github-Actions # Github Actions 사용을 위한 체크아웃
uses: actions/checkout@v2
- name: Install node.js 16
uses: actions/setup-node@v1
with:
node-version: $
- name: Install Dependencies
run: npm install
- name: Start Build with npm
run: npm run bhild
env:
CI: false
- name: Setting for AWS # AWS 권한셋팅
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: ap-northeast-2
- name: Upload to S3 # 빌드파일 S3 업로드
run: aws deploy push --application-name myappF --description "myappF Test" --s3-location s3://myapp/deploy/myappF-staging.zip --source .
- name: Start Deploy with CodeDeploy # CodeDeploy 실행
run: aws deploy create-deployment --application-name myappF --deployment-config-name CodeDeployDefault.OneAtATime --deployment-group-name myappF-staging --s3-location bucket=myapp,bundleType=zip,key=deploy/myappF-staging.zip
(2) deploy-production.yml
-
master브랜치에 push/merge가 이루어지면 운영서버에서 CI가 동작한다 - 리액트 프로젝트의
build폴더를 S3 버킷에 업로드시켜 배포한다
name: deploy-production
on:
push:
branches: [ master ]
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
strategy:
matrix:
node-version: [16.14.x]
steps:
- name: Checkout Github-Actions # Github Actions 사용을 위한 체크아웃
uses: actions/checkout@v2
- name: Install node.js 16
uses: actions/setup-node@v1
with:
node-version: $
- name: Install Dependencies
run: npm install
- name: Start Build with npm
run: npm run build
env:
CI: false
- name: Setting for AWS # AWS 권한셋팅
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: ap-northeast-2
- name: Upload to S3 for Deploy # 빌드파일 S3 업로드
run: aws s3 cp --recursive --region ap-northeast-2 build s3://myapp.co.kr
- Upload to S3 for Deploy
-
build s3://myapp.co.kr: build 폴더를 s3버킷으로 복사 -
--recursive: 선택폴더 이하 모든 폴더/파일
-
3. CodeDeploy (CD)
(1) appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /usr/local/myappF
overwrite: yes
permissions:
- object: /usr/local/myappF
owner: ec2-user
group: ec2-user
mode: 755
hooks:
AfterInstall:
- location: deploy.sh
timeout: 60
runas: ec2-user
(2) deploy.sh
- build폴더 복사 후 별도의 커맨드가 필요 없어 배포 날짜만 기록했다
#!/usr/bin/env bash
echo "> [$(date +%y-%m-%d/%H:%M)] Deploy React" >> /usr/local/debrains/deploy.log