이번 내용은 우테코 프로젝트 내에서 배포를 진행하며 알게 된 인프라 및 구현 & 사용 방법에 대해 작성하려고 한다.
joyson5582@gmail.com
이나 댓글로 궁금함이나 의견을 나타내면 최선을 다해 설명하겠습니다.
일반적으로 코드 배포를 위해선 다양한 방법들이 존재한다.
이 다양한 방법들에 대해 소개하기에 앞서 CI & CD 가 무엇인지 간략하게 그리고 흐름이 어떻게 동작하는지에 대해서 알아보자.
매우 명확하고 간단한 설명이다.
그러면, 이들의 흐름은 어떻게 될까?
단순히 순서만 보면 헷갈릴 수 있는데 말로 풀어쓰면 명확하다.
변경된 코드가 괜찮은가?
-> 코드를 통합하고 빌드한다 ( CI )
빌드가 성공적으로 됐는가?
-> 빌드 파일로 서버를 배포한다 ( CD )
아래에 있는 모든 방법들은 해당 틀에서 벗어나지 않는다.
그러면 다양한 방법들에 대해 보겠다.
# Build Gradle
- name: Build Gradle
run: |
chmod +x ./gradlew
./gradlew test
shell: bash
# Docker 이미지 build 및 push
- name: docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t corea/backend:latest ./backend
docker push corea/backend:latest
docker push corea/backend:latest
흐름에서 2번과 3번이다.
매우 간단하다.
Docker hub 에 로그인 -> 빌드된 결과를 기반으로 이미지 빌드 -> 이미지 Push
# Build Gradle
- name: Build Gradle
run: |
chmod +x ./gradlew
./gradlew test
shell: bash
# 빌드 파일 압축
- name: Create deploy package
run: |
mkdir -p scripts
zip -r deployment-package.zip build/libs/corea-backend.jar
이 역시도 CI의 과정이라 할 수 있다.
빌드된 결과물을 압축해서 Github Action에서 저장하므로 2번과 3번이라 할 수 있다.
AWS 인프라가 제공해주는 빌드 시스템이다.
나는 이 CodeBuild 를 사용했고, 자세히 설명하겠다.
Github Repository 내 소스 버전(브랜치)를 기반으로 빌드를 한다.
빌드 하는 컴퓨터에 대해서 정의하는 부분이다.
운영체제, 이미지를 선택 ( 이미지의 버전 선택 ) 을 선택한다.
빌드용으로 만든 도커 이미지가 있다면 그것 역시 사용 가능하다. ( Docker Hub 에 올린 Image )
소스 코드 내 buildspec.yml
에 있는 내용을 기반으로 파일을 빌드해준다. ( AWS CodeBuild 내부에서도 관리 가능 )
version: 0.2
phases:
build:
commands:
- echo Build Starting on `date`
- cd backend
- chmod +x ./gradlew
- ./gradlew build
cache:
paths:
- '/root/.gradle/caches/**/*'
artifacts:
files:
- 'appspec.yml'
- 'build/libs/backend-0.0.1-SNAPSHOT.jar'
- 'scripts/**'
base-directory: backend
build 할 때 commands 를 지정한다.
(phases 에는 install 이나 pre_build 같은것도 존재 하나 당장 불필요 )
root/.gradle/cahces 에 있는 모든 경로&파일들을 캐시로 저장
-> 다음 빌드는 이전 빌드에 저장된 캐시를 복원한다.
artifacts-files 를 통해 빌드할 파일들을 포함한다. ( base-directort 를 통해 기본 디렉토리 )
위에 지정한 파일들을 어디에 저장할지 지정한다.
2024-artifacts
버킷 내 2024-corea/corea-backend-deployment 파일명으로 압축으로 저장이 된다.
( 폴더로 저장해도 상관없으나 차후 Code Deploy 시 압축 파일이 필요로 한다. )
빌드 출력 로그는 왠만하면 사용하자.
업로드 안하면 빌드 진행 중이나 실패한 이유를 볼 수가 없다.
결과를 출력해주고
보고서도 보여준다.
deploy:
runs-on: self-hosted
steps:
- name: Pull Docker image from DockerHub
run: docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_IMAGE_NAME }}:${{ github.sha }}
- name: Run Docker container
run: docker run -d --name backend-application -p 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/corea/backend:latest
이렇게 하면?
Docker Image 를 Pull 받고 서버에서 배포를 실행을 한다.
서버에 대한 명시가 없는 이유는 self-hosted runner 스스로가 명령어를 실행하기 때문이다.
EC2에 직접 접속해서 해당 명령어를 실행하면 인스턴스가 직접 깃허브 액션을 수행한다.
이떄의 단점이라면?
우테코 프로젝트 사람들은 대부분 self-hosted runner 를 사용했는데
AWS Secret Key 가 없어서 Credentials 를 통해 할 수 없어서 self-hosted runner 로 이렇게 동작을 시켰다.
사실, 해당 부분에서
Docker 가 필수적이라고 생각할 수 있는데 빌드된 파일을 EC2 내부까지 옮길수만 있다면 크게 상관 없다. ( S3 등등 )
...
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.REMOTE_IP }}
username: ${{ secrets.REMOTE_SSH_ID }}
key: ${{ secrets.REMOTE_SSH_KEY }}
port: ${{ secrets.REMOTE_SSH_PORT }}
script: |
...
docker run -d --name backend-application -p 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/corea/backend:latest
서버에 SSH 연결을 통해 명시한 스크립트를 실행해서 배포한다.
self-hosted runner 와 비슷하다고 생각할 수 있으나
의 차이가 있다.
우테코 프로젝트는 SSH 연결이 막혀있어서 해당 방식 불가능
위와 똑같이 Docker 가 중요한게 아닌, 서버 접속해 직접 실행한다는게 중요하다
...
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
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: Upload to S3
run: |
aws s3 cp deployment-package.zip s3://${{ secrets.S3_BUCKET_NAME }}/deployment-package.zip
- name: Deploy to 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=deployment-package.zip,bundleType=zip
아마존 credentials 로 직접 인증 받아서
빌드 업로드 + 업로드 파일 기반 배포를 진행한다.
우테코 프로젝트는 Secret Key를 발급 못 받아서 인증 불가능
AWS 인프라가 제공해주는 배포 시스템이다.
Build 와 똑같이 이를 사용했으니, 자세히 설명한다.
애플리케이션에서 본인이 배포할 컴퓨팅 플랫폼에 대해서 정한다. ( EC2/온프레미스, AWS Lambda, ECS )
그후, 배포 그룹을 생성한다. - 이때 그룹에 따라서 개발용 / 운영용 등 분리 가능
현재 배포 전략에 대해선 중요하지 않고, Auto Scaling / Load Balancer 역시 중요하지 않다.
-> 개발 서버 온전히 구동만 하면 OK
태그의 Key 값을 통해 인스턴스를 명시한다.
이때 에이전트를 구성할거냐고 묻는데 몹시몹시 중요하다!!
중요한 이유는 배포 생성까지 끝내고 설명한다.
그룹을 생성하면 배포를 생성한다.
배포 그룹 지정 + 저장된 S3 파일을 지정한다.
에이전트가 설치되어 있지 않으면 EC2 는 파일 전송을 받지 못하고
이렇게 not able to receive the lifecycle event.
를 발생시킨다.
해당 에러가 뜨면 Code Deploy Agent 가 작동하는지 확인해보자.
sudo service codedeploy-agent status
동작했는데 아예 없다면?
#!/usr/bin/env bash
sudo apt-get update -y
sudo apt-get install -y ruby
cd /home/ubuntu
wget https://aws-codedeploy-ap-northeast-2.s3.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
를 통해 설치하자.
설치하고 배포를 재 실행했는데 위와 같이 실패가 또 뜬다면?
cat /var/log/aws/codedeploy-agent/codedeploy-agent.log
를 통해 로그를 보자.
InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Missing credentials
- please check if this instance was started with an IAM instance profile
해당 로그가 뜨면 EC2 IAM 이 CodeDeploy 를 수행할 권한이 있는지 확인하자.
-> 권한을 부여했는데 안된다면?
sudo service codedeploy-agent restart
로 재시작후 다시 확인해보자
이렇게 성공하면 끝이다.
version: 0.0
os: linux
files:
- source: /build/libs
destination: /home/ubuntu/build
- source: /scripts
destination: /home/ubuntu/scripts
- source: appspec.yml
destination: /home/ubuntu/build
permissions:
- object: /
pattern: "**"
owner: ubuntu
hooks:
ApplicationStart:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
ValidateService:
- location: scripts/healthCheck.sh
timeout: 30
runas: ubuntu
CodeBuild 와 유사하게 appspec.yml
에 있는 내용을 기반으로 서버를 배포한다.
파일을 저장시킬 곳을 지정하고, 파일들에 권한을 주고
각 hooks 마다 실행할 스크립트들을 지정한다.
이때 EC2 에는 우리가 직접 자바 등을 설치해줘야 한다.
1. 자바 설치
wget https://download.oracle.com/java/17/archive/jdk-17.0.11_linux-aarch64_bin.tar.gz -O /tmp/openjdk-17_linux-x64_bin.tar.gz
2. 압축 해제
sudo tar xfvz /tmp/openjdk-17_linux-x64_bin.tar.gz --directory /usr/lib/jvm
3. 자바 압축 파일 삭제
rm -f /tmp/openjdk-17_linux-x64_bin.tar.gz
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk-17.0.11/bin/java 100
4. 자바 설정
sudo update-alternatives --set java /usr/lib/jvm/jdk-17.0.11/bin/java
5. 자바 환경 변수 설정
export JAVA_HOME=/usr/lib/jvm/jdk-17.0.11
export PATH=$PATH:/usr/lib/jvm/jdk-17.0.11/bin
서버를 8080에 연다면?
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
IP 포트포워딩도 진행하자
이렇게 CodeDeploy 를 활용해 배포가 끝이났다.
코드 파이프라인은 CodeBuild 와 CodeDeploy 를 합쳐놓은 것으로 정말 무중단 배포가 가능하게 해주는 인프라다.
대기를 할 시, 코드 커밋이 계속 일어날 시 차례대로 배포를 진행한다. ( 1.0.0 진행 -> 1.0.1 진행 )
추가로, 밑에 고급 설정에서 아티팩트 스토어에서 기존 버킷을 지정할 수 있다. ( 기본 위치시 자동으로 버킷 생성 )
WebHook 을 통해 Github 가 자신의 변경을 AWS CodePipeline 에게 알려준다.
-> 이를 기반으로 파이프라인이 동작한다.
생성된 빌드를 재사용 가능하다.
역시 배포 & 배포 그룹 재사용 가능하다
이때 세부 정보들을 보면
Source 는 출력으로 SourceArtifact
->
Build 는 입력으로 SourceArtifact
-> BuildArtfiact
Deploy 는 입력으로 BuildArtifact
가 들어오고 배포를 하며 끝이난다.
이때 버킷을 들어가보면?
이렇게 스스로 폴더 + Build/SourceArtifact 를 만든다.
( 내부에도, 소스 아티팩트와 빌드된 결과물 존재 )
=> 이 결과 우리는 소스를 Github 에 Push 하면 배포가 자동으로 이루어진다.
( Github Action 에 의존적이지 않음 )
이 밖에도 수많은 CI 방법과 CD 방법이 존재할 것이다.
이런 내용들을 생각하며 각자 팀에 맞는 CI / CD 를 수립하는게 좋은거 같다.
우리 팀은 AWS SecretKey 가 없으나 AWS 인프라를 사용해야 하는 점 + Self-Hosted Runner 를 사용하기 싫은점 때문에
AWS 에 완벽하게 의존을 하는 CodePipeline 을 사용하기로 했다.
이때, 배포가 성공/실패 했는지 또는 배포된 주소로 바로 들어가기 위해선
AWS Chatbot
이나 AWS CloudWatch + AWS Lambda + Slack Webhook
을 통해서 가능하다고 하다.
이는 차차 도입해볼 예정
그리고, CI-CD 가 무조건 하나로 이어질 필요는 없다.
내가 CodeBuild 를 굳이 사용할 필요가 없다고 판단해
Github Action 에서 직접 빌드하고 파일을 전달하는 것도 가능하다.
CodeBuild는 사용하더라도 CodeDeploy 를 쓰지않고
우리가 빌드된 파일로 직접 실행해도 괜찮지 않을까?
빌드가 되었다고, 꼭 배포가 자동으로 될 필요는 없을수도 있을테니까.
https://github.com/woowacourse-teams/2024-corea 해당 프로젝트에 도입했으며
다시 한번, 부족한 지식이 포함되어 있는 글이기에 댓글은 언제나 환영한다.
https://jojoldu.tistory.com/282
https://github.com/jaeyeonling/reaction-game/tree/main