DevOps
GitHub Actions 와 CodeDeploy 를 이용한 CI/CD 구현
BaekSJ
2024. 5. 10. 23:11
💡 GitHub Actions 란 무엇인가?
- GitHub 저장소 내에서 직접 워크 플로우를 자동화할 수 있도록 GitHub 에서 제공하는 자동화 플랫폼
- 이를 통해 GitHub 를 떠나지 않고도 코드를 빌드, 테스트 및 배포할 수 있음
- YAML 구문을 사용하여 push, pull 요청 또는 예약된 이벤트와 같은 이벤트를 기반으로 작업을 트리거하는 사용자 지정 워크 플로우를 정의할 수 있음
💡CodeDeploy 란 무엇인가?
- Amazon EC2 인스턴스, AWS Lambda 함수, 온프레미스 서버 등 다양한 컴퓨팅 환경의 인스턴스에 애플리케이션을 배포하는 프로세를 자동화하는 배포 서비스
더보기
💡 CI/CD 란 무엇인가?
- 지속적인 통합과 지속적인 전달/지속적인 배포를 나타냄
- CI (지속적 통합)
- 개발자가 코드 변경 사항을 공유 저장소에 자주 통합하는 개발 방식
- 각 통합은 자동화된 빌드 및 테스트를 트리거하여 팀이 개발 프로세스 초기에 오류를 감지하고 해결할 수 있도록 함
- CD (지속적 전달)
- CI 의 확장으로, 자동화된 테스트를 통과한 코드 변경 사항이 자동으로 릴리스 준비됨
- 이 프로세스에는 프로덕션에 배포하기 전에 추가 테스트 또는 승인 단계가 발생할 수 있는 사전 프로덕션 환경에 코드를 배포하는 작업이 포함됨
- CD (지속적 배포)
- 수동 개입 없이 모든 자동화된 테스트 및 검사를 통과한 후 코드 변경 사항을 프로덕션 환경에 자동으로 배포함으로써 개념을 발전
- CI/CD 방식은 소프트웨어 개발 수명주기를 자동화하고 간소화하여 고품질 소프트웨어를 더 자주 안정적으로 제공할 수 있도록 하는 것을 목표로 함
아키텍쳐 구조
- Local 에서 GitHub 레포지토리로 push 가 되면 GitHub Actions 가 트리거 되어 자동 실행
- build 를 통해 .jar 생성
- 생성된 .jar 파일과 script 파일을 압축해 AWS S3 로 업로드
- Public Subnet 의 EC2 에 설치된 CodeDeploy Agent 가 AWS S3 에 업로드 된 압축 파일(.zip)을 받아와 배포 시작
- appspec.yml 을 읽어 설정된 script 를 실행시켜 배포
1. IAM 역할 생성
- EC2 에서 CodeDeploy 와 S3 에 접근할 수 있도록 IAM 권한 추가
- 신뢰할 수 있는 엔터티 유현으로 AWS 서비스 선택
- EC2 애서 연결해 줄 IAM 이므로 사용 사례로 EC2 선택
- 권한 정책은 위 두 개를 부여
2. S3 버킷 생성
3. CodeDeploy 애플리케이션 생성
- 애플리케이션 이름 설정
- 컴퓨팅 플랫폼은 'EC2/온프레미스'로 설정
3-1. CodeDeploy IAM 역할 생성
3-2. 배포 그룹 생성
- 위에서 생성한 애플리케이션의 배포 그룹 생성
- 배포 그룹의 이름을 설정
- 서비스 역할로 위에서 생성한 CodeDeploy IAM 을 선택
- Amazon EC2 인스턴스 선택
- 키는 Name 으로, 값은 연결하고자 하는 EC2 인스턴스를 선택
4. IAM 사용자 생성
- GitHub Actions 를 통해 S3 버킷에 업로드하기 위해서는 권한이 필요합니다
- AWS IAM → 사용자 → 사용자 추가
4-1. 엑세스 키 생성
- GitHub Actions 에서 사용자를 인증하기 위한 엑세스 키 생성
- 생성 후 하단의 '.csv 파일 다운로드' 를 클릭하여 안전하게 보관
- 외부 노출 시 해킹의 위험 있음
5. EC2 인스턴스에 CodeDeploy Agent 설치
- S3 버킷에 있는 파일을 CodeDeploy 를 통해 특정 EC2 에 배포하기 위해서 EC2 에 CodeDeploy Agent 설치
5-1. Ruby 패키지 설치
- CodeDeploy Agent 가 Ruby 로 작성되어 있기에 Ruby 패키지 설치
$: sudo apt update
$: sudo apt install ruby-full
$: sudo apt install wget
5-2. CodeDeploy Agent 설치
- Ubuntu 의 경우 /home/ubuntu 위치에서 다운로드 (EC2 인스턴스에 접속 시 초기 위치 '~$' 로 되어 있음)
# S3 버킷에서 설치 스크립트 다운로드
$: wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
# 스크립트를 실행 가능하도록 권한 변경
$: chmod +x ./install
# 설치 스크립트 실행
$: sudo ./install auto
5-3. CodeDeploy 실행 확인
$: sudo service codedeploy-agent status
- 위와 같이 active 라면 실행 성공
5-4. EC2 인스턴스 실행 시 자동 스크립트 실행하도록 설정
$: sudo vim /etc/init.d/codedeploy-startup.sh
sudo service codedeploy-agent restart
- 스크립트 내용
$: sudo chmod +x /etc/init.d/codedeploy-startup.sh
- 권한 부여
6. NGINX 설치
6-1. EC2 에 nginx 설치
# yum 을 사용한다면 sudo yum install nginx
sudo apt install nginx
6-2. nginx 버전 확인
sudo nginx -v
6-3. nginx 실행
sudo service nginx start
6-4. nginx 상태 확인
sudo service nginx status
- 위 처럼 나온다면 실행 성공
6-5. nginx 접속해보기
- EC2 의 인바운드 규칙으로 80번 포트를 허용하고 접속한다면 위처럼 페이지로 이동하여 확인 가능합니다.
6-6. $server_url 변수 설정
/home/ubuntu# sudo vi service_url.inc
set $service_url "http://[탄력적 IP]:서버포트 번호";
- 'home/ubuntu' 에 service_url.inc 작성
6-7. nginx 에 Symbolic link 걸기
- `/etc/nginx` 폴더 내에 'sites-available' 과 ' sites-enabled' 폴더가 있는 지 확인 (없다면 mkdir 을 통해 생성해 줍니다)
# default 폴더 확인
cd /etc/nginx/sites-available
- 'sites-available' 폴더 안에 default 가 있다면 지워주고 다시 생성해줍니다
# default 작성
sudo vi default
- default 를 새로 작성해줍니다
server {
listen 80;
listen [::]:80;
server_name [탄력적 IP];
root /usr/share/nginx/html;
include /home/ubuntu/service_url.inc;
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass $service_url; # $service_url은 /home/ubuntu/service_url.inc 파일에 정의되어야 합니다.
}
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
- default 내용
- include : 설정 파일 불러오기
- proxy_pass : $service_url 로 요청을 보낼 수 있도록 하는 프록시 설정
sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
- 'Symbolic link' 만들기
- 오류 메시지 "ln: failed to create symbolic link '/etc/nginx/sites-enabled/default': File exists" 가 발생했다면 이미 심볼릭 링크가 걸려 있다는 의미입니다.
- 위와 같이 해당 디렉토리에서 'ls' 명령어를 실행하면 'default' 파일이 하늘색으로 표기되는 걸 확인 가능한데 이는 심볼릭 링크(symlink)를 나타냅니다
- 'ls -l' 명령어를 통해 해당 'default' 파일이 어느 파일이나 디렉토리와 연결되어 있는지 확인이 가능합니다.
sudo nginx -t
- Nginx 가 정상 동작하는지 테스트
- 위 처럼 나오면 성공입니다
sudo service nginx restart
- 위 작업이 끝났다면 Nginx 를 재시작 해주시면 되겠습니다
7. Docker , Docker-Compose 설치 및 설정 🐳
7-1. Docker , Docker-Compose 설치
# Docker 설치
sudo apt install docker.io
# Docker 실행
sudo systemctl start docker
# Docker 컨테이너 확인
sudo docker ps
# Docker-Compose 설치
sudo curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 접근 권한 설정
sudo chmod +x /usr/local/bin/docker-compose
# Docker-Compose 버전 확인
docker-compose version
8. 자바 파일을 실행하기 위한 JDK 설치
sudo apt install openjdk-11-jdk -y
- 본 포스팅에서는 JAVA 11 버전을 사용하여 11 버전을 설치했지만 본인이 사용하는 버전에 맞게 설치해주시면 되겠습니다.
java --version
- 잘 설치되었는지 버전 확인을 해줍니다.
9. appspec.yml 생성
- 배포하고자 하는 프로젝트의 root 경로에 appspec.yml 작성
- CodeDeploy 가 EC2 에 소스코드를 배포하는 과정을 정의
version: 0.0
os: linux
files:
- source: /
destination: /home/app/current
file_exists_behavior: OVERWRITE
permissions:
- object: /home/app
pattern: '**'
owner: ubuntu
group: ubuntu
hooks:
BeforeInstall:
- location: docker-stop.sh
timeout: 300
runas: ubuntu
AfterInstall:
- location: deploy-be.sh
timeout: 300
runas: ubuntu
10. GitHub Repository 설정
10-1. GitHub Repository 에 엑세스 키 저장
- GtiHub Actions 를 통해 사용자를 인증하기 위해 'New repository secret' 을 클릭하여 키를 저장
- 위와 같이 생성
- 배포하고자 하는 레포지토리 클릭
- 상단의 Actions 클릭
- 'set up a workflow yourself' 를 통해 진행(직접 작성)
- 'Deploy to Amazon ECS' 부분의 configure 를 클릭하여 수정(만들어진 스크립트 수정)
name: Deploy to AWS EC2
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.15.0]
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 📀 Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: ⛏ Set up JDK 11
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "11"
deploy:
needs: build
runs-on: ubuntu-latest
environment: deploy
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 🗂️ Make config folder
run: mkdir -p config
- name: 📦 Zip project files
run: zip -r ./$GITHUB_SHA.zip .
- 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 }}
- name: 🐳 Login to Amazon ECR
run: |
aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 654654614721.dkr.ecr.ap-northeast-2.amazonaws.com/test-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: 🔨 Build and Push to ECR
uses: docker/build-push-action@v5
with:
context: .
file: .github/workflows/Dockerfile
push: true
tags: ${{ secrets.AWS_ECR_REPOSITORY }}:latest
- name: 🚛 Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ env.S3_BUCKET_DIR_NAME }}/$GITHUB_SHA.zip
- name: 🚀 Deploy to EC2 with CodeDeploy
run: |
aws deploy create-deployment \
--application-name ${{ secrets.APPLICATION_NAME }} \
--deployment-group-name ${{ secrets.DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=${{ secrets.S3_BUCKET_NAME }},key=${{ env.GITHUB_SHA }}.zip,bundleType=zip
10-2. Dockerfile 생성
FROM gradle:7.4-jdk11-alpine as builder
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Seoul
RUN apk --no-cache add tzdata && \
cp /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone
WORKDIR /build
COPY . /build
RUN gradle build
FROM openjdk:11.0-slim
WORKDIR /recode-be
COPY --from=builder /build/build/libs/*.jar .
EXPOSE 8080
CMD ["java", "-jar", "recode-0.0.1-SNAPSHOT.jar", "--spring.config.location=file:/test-vpc/resources/"]
11. GitHub Repository Environment 설정
11-1. 프로젝트 properties 템플릿 생성
server.port= 10004
server.servlet.encoding.charset=utf-8
server.servlet.encoding.force=true
spring.datasource.url=jdbc:mariadb://${DB_ENDPOINT}
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.open-in-view=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.default_batch_fetch_size=100
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true
spring.output.ansi.enabled=always
# Google
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=profile,email
- 프로젝트의 '/src/main/resources/' 에 'application.properties.template' 파일을 생성해줍니다
- 해당 파일은 CI/CD 도중 삽입됩니다
11-2. GitHub 레포지토리에 Secrets 등록하기
- 'application.properties' 혹은 'application.yml' 에 작성한 노출되면 안되는 민감한 정보는 환경변수로 등록하여 사용하여야 합니다.
- 필요하신 내용을 모두 넣은 후 저장해주시면 되겠습니다.
11-3. Github Actions yaml 파일에 properties 정보 넣어주기
name: Deploy to AWS EC2
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.15.0]
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 📀 Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: ⛏ Set up JDK 11
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "11"
# 추가된 부분
- name: 🔑 Replace environment variables in application.properties
run: |
cp src/main/resources/application.properties.template src/main/resources/application.properties
sed -i 's/\${DB_USERNAME}/'"${{ secrets.DB_USERNAME }}"'/g' src/main/resources/application.properties
sed -i 's/\${DB_PASSWORD}/'"${{ secrets.DB_PASSWORD }}"'/g' src/main/resources/application.properties
sed -i 's/\${GOOGLE_CLIENT_ID}/'"${{ secrets.GOOGLE_CLIENT_ID }}"'/g' src/main/resources/application.properties
sed -i 's/\${GOOGLE_CLIENT_SECRET}/'"${{ secrets.GOOGLE_CLIENT_SECRET }}"'/g' src/main/resources/application.properties
deploy:
needs: build
runs-on: ubuntu-latest
environment: deploy
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 🗂️ Make config folder
run: mkdir -p config
- name: 📦 Zip project files
run: zip -r ./$GITHUB_SHA.zip .
- 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 }}
- name: 🐳 Login to Amazon ECR
run: |
aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 654654614721.dkr.ecr.ap-northeast-2.amazonaws.com/test-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: 🔨 Build and Push to ECR
uses: docker/build-push-action@v5
with:
context: .
file: .github/workflows/Dockerfile
push: true
tags: ${{ secrets.AWS_ECR_REPOSITORY }}:latest
- name: 🚛 Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ env.S3_BUCKET_DIR_NAME }}/$GITHUB_SHA.zip
- name: 🚀 Deploy to EC2 with CodeDeploy
run: |
aws deploy create-deployment \
--application-name ${{ secrets.APPLICATION_NAME }} \
--deployment-group-name ${{ secrets.DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=${{ secrets.S3_BUCKET_NAME }},key=${{ env.GITHUB_SHA }}.zip,bundleType=zip
결과 확인
1. S3
2. ECR
위와 같이 성공적으로 업로드가 완료 되었음을 확인할 수 있습니다.
마치며
CI/CD 작업을 진행하며 CLI 환경과 좀 더 익숙해질 수 있었네요. EIP 의 경우 프리티어라도 생성 후 사용하지 않으면 비용이 발생하니 혹여 추가로 생성된 것이 있다면 release 를 통해 삭제해주시기 바랍니다.