2024. 3. 11. 18:52ㆍ프로젝트
본 포스팅에서는 Docker, Github Actions 을 통해 CI/CD를 구축한 과정을 적어보려 합니다.
프로젝트명 - PLAC(Planning-Your-Activities)
https://github.com/PLAC-Planning-Your-Activities/plac-server
배포 자동화는 Github Action 을 사용했습니다.
깃허브 리포지토리에서 main 브랜치로 push를 받는 순간 Github Action으로 트리거되어 프로젝트가 정상인지 테스트한 후, 빌드하여 AWS 로 자동 배포하게 됩니다.
도커, Github Action을 사용한 이유 -> 편리하고 강력하다고 생각해서
깃허브 액션은 깃허브 자체에서 제공해주는 것으로 프로젝트 내의 deploy.yml 파일을 작성하는 것만으로 자동으로 빌드, 배포가 되어서 아주 편리하단 생각이 들어 사용하였습니다.
도커를 사용한 이유 역시 비슷합니다. 편리함에 더해 도커 컨테이너라는 환경에서 독립적으로 수행되는데, 빌드 속도가 빠르고 안정적이라고 하여 선택하였습니다. 도커 이미지를 빌드하고 도커허브에서 pull 받아 실행하면 되므로 너무 편리했습니다.
또한 PLAC 은 실제 서비스를 운영할 것이기에 보안적으로도 신경을 써야 했습니다.
HTTPS를 통해 패킷을 암호화 해주었고, 앞단에 NginX 웹 서버를 설치해 보안을 강화하였습니다.
CI/CD 란?
CI는 개발자를 위한 자동화 프로세스, 지속적인 통합(Continuous Integration)을 말합니다.
개발자들이 작업 중인 코드를 Github 같은 곳에 정기적으로 Push 하고, 공유 브랜치에 병합하게 되면 자동화된 빌드 시스템 (ex. Jenkins, Github Action)에 의해 검출되고 컴파일, 테스트 등의 절차를 거치게 됩니다.
따라서 개발자가 개발을 동시에 진행하는데, 협업에 의한 코드 충돌 문제를 해결할 수 있으며 테스트에 실패하거나 문제가 발견되면 신속하게 대응할 수 있습니다.
CD는 지속적인 서비스 제공(Continuous Delivery) 또는 지속적인 배포(Continuous Deployment)를 의미합니다.
지속적인 서비스 제공(Continuous Delivery) : 소프트웨어를 언제든지 배포할 수 있는 상태로 유지하는 것. 개발부터 배포 준비까지의 과정을 자동화함으로써 배포 과정을 더 빠르고, 안정적으로 만듭니다.
지속적인 배포(Continuous Deployment): 개발된 코드가 자동 테스트를 통과하면 곧바로 프로덕션 환경에 배포되는 것. 모든 배포 과정이 완전히 자동화되어 있다.
Polling(폴링) 기법
- 자신이 만든 서버에서 Github 에 계속해서 request 요청으로 코드가 push 되었는지 확인하는 기법으로, 간헐적인 시간 텀에 따라 계속해서 request 요청을 통해 코드의 변화를 물어보는 것입니다.
- 코드 변화가 감지되면 Github 코드를 다운받아 단위 테스트, 통합 테스트를 진행합니다. 테스트가 성공하면 빌드 실행, 빌드가 끝나면 AWS 로 코드를 push 하여 전달합니다.
- 대표적인 폴링 서비스로는 Travis 가 있습니다.
단점
폴링 기법은 계속해서 request 요청을 하기 때문에 트래픽이 많이 소모되어 서버 입장에서는 부담이 될 수 있습니다.
Webhook (웹훅) 기법
- Github의 웹훅은 코드를 push 받은 시점에 push 되었다고 알려주는 능동적인 API 서비스입니다. 특정 브랜치에 소스코드가 업로드 되면, 업로드 되었다는 이벤트를 전달해줍니다.(hook)
- hook을 받으면 똑같이 코드를 테스트 서버로 내려받고, 테스트에 필요한 프로그램을 설치한 뒤 테스트를 합니다. 그 후 프로젝트를 빌드해, AWS로 전달합니다.
- 대표적인 서비스로는 GitHub Actions, Jenkins 가 있습니다.
본 포스팅에서는 웹훅 기법을 사용할 것이며, GitHub Action을 사용합니다.
깃허브 액션 동작 과정
동작 순서는 다음과 같습니다.
- 로컬 컴퓨터에서 코드를 수정하고 GitHub main 브랜치에 Push함 -> 새로운 버전의 프로젝트 탄생
- Github 에서 코드의 변경을 감지, CI(Continuous Integration : 지속적 통합) 서버를 만든 후, 이 서버로 프로젝트를 전달한다.
- CI 서버에서 hook을 받으면 코드를 이 테스트 서버로 내려받고, 테스트에 필요한 프로그램을 설치한 뒤 테스트를 하고 프로젝트를 빌드하여 AWS로 전달해준다. CI 서버는 AWS 환경(리눅스)과 동일하므로 CI 서버에서의 테스트 성공은 실제 실행 환경에서의 성공을 보장한다.
- CI 서버에서의 실행 파일을 생성한 후 CD(Continuous Delivery : 지속적 배포)를 한다.
CD는 깃허브에서 변경을 감지해 CI 서버로 배포할 때 한 번, CI 서버에서 테스트에 성공하고 빌드한 후 실행파일이 생성되어 AWS로 배포할 때 한 번 일어납니다. 즉, 총 2번 일어난다고 합니다.
Github Action 사용하기
deploy.yml 파일
- 최상위 폴더에서 .github/workflows/ 폴더 아래 *.yml 이름으로 있어야 합니다. (이름은 상관 없습니다.)
깃허브 리포지토리에서 코드가 반영되었을 때 CI 서버로 이벤트가 일어나고, 이 때 .github/workflows/*.yml 파일이 필요합니다.
이 yml 파일에 정의된 내용대로 CI 가 진행됩니다.
deploy.yml 내용은 다음과 같습니다.
name: Docker Build and Deploy
on:
push:
tags:
- ‘*’
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: ‘11’
distribution: ‘temurin’
- name: Build with Gradle
run: ./gradlew build -x test
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ secrets.DOCKER_USER }}/${{ secrets.DOCKER_REPOSITORY }}:${{ github.ref_name }}
${{ secrets.DOCKER_USER }}/${{ secrets.DOCKER_REPOSITORY }}:latest
labels: version=${{ github.ref_name }}
- name: Deploy to Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /home/ubuntu/deploy
./deploy.sh
리포지토리에 새로운 태그가 Push 될 대마다 자동으로 실행되도록 설정했습니다. 여러 jobs로 구성되어 있고, 각 job 마다 여러 step(단계)을 포함하고 있습니다.
트리거 조건 `on`
- 해당 workflow는 레포지토리에 태그가 푸시될 때, 활성화된다.
권한 설정 `permissions`
- 레포지토리의 내용을 읽기 전용으로 사용한다.
작업 `jobs`
- build 라는 하나의 작업을 정의하며, 해당 워크플로우에서 실행되는 작업들을 품고 있다.
작업 환경 설정 `runs-on`
- ubuntu-latest 운영체제에서 실행된다.
steps 에 있는 각 단계별 작업을 간략히 적어보겠습니다.
- Checkout Repository : actions/checkout@v3 라이브러리를 사용해 소스 코드를 체크아웃한다.
- Github Actions에서 필수 작업, 워크플로우가 실행되는 러너에 현재 레포지토리 코드를 복사해온다고 합니다. 이 작업을 통해 깃헙 리포지토리의최신 커밋이나 코드를 가져와 이후 단계를 실행할 수 있다고 하네요.
- Set up JDK 11 : JDK 11을 설치합니다. 서비스를 Gradle로 빌드할 때 사용됩니다.
- Build with Gradle : './gradlew build -x test' 명령어를 실행해 테스트를 제외한 나머지 빌드 작업을 수행합니다.
- 현재는 테스트를 제대로 작성하지 않아서 제외했지만, 나중에 반드시 수정 필요
- Set up Docker Buildx : Docker Buildx를 설정 -> 도커 이미지를 빌드하기 위한 빌더로, 여러 플랫폼에서 빌드를 수행한다.
- Login to Docker Hub : 도커허브에 로그인한다.
- Build and Push Docker Image : 도커 이미지를 빌드, 도커허브로 Push 한다.
- with 하위 항목은 docker/build-push-action@v2 액션에 전달되는 매개변수를 설정합니다. context : `.` 현재 디렉토리를 의미하며, file은 사용할 Dockerfile 위치를 지정합니다. tags : 빌드된 Docker 이미지에 태그를 지정합니다.
- Deploy to Server : appleboy/ssh-action@master 를 통해 서버에 SSH 접속을 수행하고 deploy.sh 스크립트를 실행해 배포를 진행합니다.
EC2 서버에 있는 deploy.sh 파일은 다음과 같이 작성했습니다.
#!/bin/sh
# 도커 로그인 환경변수 설정
export DOCKER_USERNAME={생략}
export DOCKER_PASSWORD={생략}
# 도커 로그인 및 도커 이미지 다운로드
echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
docker pull matt1235/plac:latest
# 'plac-server'라는 이름의 도커 컨테이너가 실행 중인 경우, 컨테이너를 중지하고 삭제
if [ $(docker ps -aq -f name=plac-server) ]; then
docker stop plac-server
docker rm plac-server
fi
# 도커 컨테이너 실행시 필요한 환경변수 설정
export SERVER_PORT="{생략}"
export DB_HOST="{생략}"
/**
생략
*/
export JWT_ACCESS_SECRET="{생략}"
export JWT_REFRESH_SECRET="{생략}"
# 도커 컨테이너 실행
docker run -d \
--name plac-server \
-p $SERVER_PORT:{생략} \
-e DB_HOST \
/**
생략
*/
-e JWT_ACCESS_SECRET \
-e JWT_REFRESH_SECRET \
matt1235/plac:latest
프로젝트의 application.yml 파일에 있는 환경변수에 값을 넣어주어야 하는데, 쉘 스크립트 안에서만 작동하도록 환경변수를 설정해주었습니다.
최종적으로는 docker run 명령어를 통해 도커 이미지를 실행하고, 도커 컨테이너로 서비스를 띄웠습니다.
EC2 서버 상태
실행되는 프로세스 포트번호는 대략 443(HTTPS), 80(웹서버 - NginX), 모자이크값(서비스 포트) 입니다.
docker ps -a 를 통해 실행중인 도커 컨테이너를 확인할 수 있습니다.
컨테이너는 포트포워딩을 통해 돌아갑니다. 이 값 또한 개발자가 설정해줄 수 있습니다.
(포트 번호는 모두 모자이크 처리하였습니다.)
이렇게, 도커와 GitHub Actions를 사용해 CI/CD를 구축해보았습니다.
CI/CD 구성도
결론
깃허브 액션을 통해 깃허브 내에서 빠르게 스크립트를 작성해 CI/CD를 구축할 수 있다는 게 정말 편리한 것 같습니다.
또한 도커를 통해 필요한 환경만 간단하게 설정해주고, 이미지를 통해 실행할 수 있다는 게 큰 장점인 것 같습니다.
'프로젝트' 카테고리의 다른 글
[쿠폰] Redis 기반 비동기 시스템 구현 (0) | 2024.04.21 |
---|---|
[쿠폰] 트러블 슈팅 - 동시성 처리 (0) | 2024.04.15 |
배포 스크립트 작성 (0) | 2024.03.05 |
[JWT] Access Token 과 Refresh Token 저장 및 관리에 대한 고민 (2) | 2024.02.02 |
Spring Security를 이용한 인증/인가 (0) | 2024.02.01 |