EC2 배포 자동화1

1. 전체 구성도

png

이 게시글에서는 AWS EC2, Docker, GitHub Actions를 활용해서 간단한 CI/CD 자동화를 구현해볼 예정이다. 전체 구성도를 참고한 배포 과정은 다음과 같다.

  • GitHub 저장소에 코드 변경 사항을 푸시한다.

  • GitHub Actions에서 변경 사항을 감지하고 workflow를 실행한다.

    • 빌드 과정을 실행한다.

      • 먼저 프로젝트의 jar 파일로 도커 이미지를 만든다.

      • 도커 이미지를 Docker Hub의 저장소에 업로드한다.

    • 빌드가 성공적으로 완료되면 배포 과정을 실행한다.

      • GitHub Actions Runner에 배포를 요청한다.

  • EC2 인스턴스에 설치된 GitHub Actions Runner가 배포 액션을 실행한다.

    • EC2에 최신 도커 이미지를 다운로드한다.

    • 새로운 도커 이미지를 실행한다.

2. 도커 설정

2.1. 도커 가입

png

도커 허브를 사용하기 위해서는 도커 회원가입이 필요하다. 구글 계정 혹은 깃허브 계정으로 가입 가능하다.

png

도커 저장소는 깃헙과 비슷하다. 무료 요금제로는 단 한 개의 개인 저장소(private repository)만 만들 수 있음에 유의하자. 나는 토이 프로젝트이므로 공개 저장소(public repository)를 사용했다. 저장소 이름은 도커 이미지 이름과 동일하므로 두 가지 방식으로 생성 가능하다.

  1. 저장소를 미리 생성 -> 저장소 이름을 이미지 이름으로 사용해서 업로드

  2. 이미지 이름으로 업로드 -> 이미지 이름으로 새로운 저장소 생성

2.2. 토큰 생성 및 등록

png

깃헙 액션이 나의 도커 허브 저장소에 프로젝트 빌드 이미지를 올리고 내려받기 위해서는 접근 권한이 필요하다. 따라서 도커의 사용자 이름비밀번호가 필요하다. 나는 비밀번호보다는 토큰을 사용하는게 더 편해서 비밀번호 대신 토큰을 발급받았다. 토큰을 발급받기 위해선 우측의 사용자 이름 ➜ Account Setting을 클릭해서 Account Setting 화면으로 이동해야 한다.

png

Account Setting 화면에서 좌측 사이드바의 Security ➜ New Access Token버튼을 클릭하면 새로운 토큰을 발급받을 수 있다.

png

토큰의 이름을 지정하고 Generate를 누르면 새로운 토큰이 발급된다. 토큰은 토큰을 생성한 직후 단 한 번만 표시되므로 복사해서 다른 곳에 보관하고 사용해야 한다. 비밀번호 대신 사용하므로 공개 저장소에 노출되지 않도록 조심하자.

png

이제 깃헙 프로젝트에 사용자 이름과 토큰을 등록해야 한다. 깃헙 프로젝트의 Settings탭을 클릭해서 설정 탭으로 이동하자. 좌측 사이드바의 Security에서 Secrets and variables ➜ Actions버튼을 클릭하면 현재 저장소의 시크릿 데이터를 확인할 수 있다. New repository secret버튼을 클릭하면 새로운 시크릿 데이터를 생성할 수 있다.

png

Name에는 시크릿 데이터의 이름을 넣고 Secret에는 토큰 값을 넣고 Add secret 버튼을 클릭하면 새로운 시크릿 데이터가 생성된다. 나는 사용자 이름을 DOCKERHUB_USERNAME, 토큰을 DOCKERHUB_TOKEN으로 등록했다.

2.3. 로컬 운영체제에 도커 설치

# ==============================================================================
# 1. Homebrew 버전 확인 및 업데이트
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
brew update

# 터미널에 아래 명령어 입력 후 엔터
brew upgrade

# ==============================================================================
# 2. Homebrew에 docker 설치
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
brew install --cask docker

# ==============================================================================
# 3. 제대로 설치되었는지 확인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
docker --version

# 제대로 설치되었다면 아래와 같은 문구 출력
Docker version 24.0.6, build ed223bc

테스트를 위해 로컬 운영체제에 도커를 설치해야 한다. 나는 Homebrew를 패키지 매니저로 사용하고 있어 brew를 활용해서 설치했다.

FROM openjdk:17-oracle

ARG JAR_FILE=/build/libs/*.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]

2.4. Dockerfile 작성

도커가 설치되었다면 도커 설정 파일을 작성해보자. 도커 설정 파일은 Dockerfile이라는 이름의 파일을 가리킨다. 도커 파일은 프로젝트의 최상단에 위치해야 한다.

# ==============================================================================
# 1. 스프링 부트 프로젝트를 .jar 파일로 빌드
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
./gradlew build

# ==============================================================================
# 2. 도커 로그인 및 이미지 빌드 파일 생성
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
$docker login -u '도커허브_사용자이름'

# 비밀번호 입력
password:

# 터미널에 아래 명령어 입력 후 엔터
docker build --platform linux/amd64 -t '도커허브_사용자이름'/'이미지_이름':'태그_이름' .

# ==============================================================================
# 3. 제대로 생성되었는지 확인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
docker images

# 제대로 생성되었다면 아래와 같은 문구 출력
REPOSITORY     TAG       IMAGE ID       CREATED          SIZE
'이미지_이름'   '태그_이름'   123a1234567b   44 seconds ago   522MB

도커 허브에 빌드 파일을 올리기 위해 로그인을 한다. 그리고 도커 컨테이너에 올릴 이미지를 생성한다.만일 '--platform linux/amd64' 옵션 없이 빌드하면 어떻게 될까?

# ==============================================================================
# 1. 도커 컨테이너로 이미지 빌드 파일 실행
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo docker run -d -p '포트 번호':'포트 번호' '도커허브_사용자이름'/'이미지_이름':'태그_이름'

# 에러 발생
WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v3) and no specific platform was requested

도커 컨테이너로 이미지 빌드 파일을 실행 시 위와 같은 에러가 발생한다. 이는 MacOS의 linux와 AWS EC2 linux 플랫폼이 달라서 이미지 빌드 파일을 실행할 수 없다는 뜻이다.

# ==============================================================================
# 1. 이미지 빌드 파일을 도커 허브에 업로드
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
docker push '도커허브_사용자이름'/'이미지_이름':'태그_이름'

이미지 빌드 파일이 제대로 생성되었다면 push 명령어로 도커 허브에 이미지를 올릴 수 있다. 여기까지 문제없이 잘 동작했다면 도커 연동 로컬 테스트는 끝난 셈이다.

3. AWS EC2 설정

3.1. 도커 설치 후 배포 테스트

이제 AWS EC2에도 Docker를 설치해야 한다. 만약 AWS EC2 인스턴스 설정이 제대로 되지 않았다면 이전 게시글을 참고해서 설정해두자. 탄력적 IP와 보안그룹 설정이 되어 있어야 한다.

# ==============================================================================
# 1. SSH로 EC2 인스턴스에 로그인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
ssh -i '프라이빗_키_경로/프라이빗_키.pem' '사용자_이름'@'퍼블릭_IPv4_주소'

# ==============================================================================
# 2. dnf 버전 확인 및 업데이트
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo dnf update

먼저 터미널에서 ssh로 EC2 인스턴스에 로그인한다. 참고로 내 EC2 인스턴스는 Amazon Linux 2023 운영체제이다. 그리고 dnf 버전을 확인 후 업데이트한다.

# ==============================================================================
# 1. 도커 설치
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo dnf install docker

# ==============================================================================
# 2. 제대로 설치되었는지 확인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
docker --version

# 제대로 설치되었다면 아래와 같은 문구 출력
Docker version 24.0.5, build ced0996

# ==============================================================================
# 3. 현재 사용자를 도커 그룹에 추가
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo usermod -aG docker $USER

# ==============================================================================
# 4. 도커 서비스 시작
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo systemctl start docker

# ==============================================================================
# 5. 도커 서비스가 시작되었는지 확인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo systemctl status docker

# 제대로 시작되었다면 아래와 같은 문구 출력
docker.service - Docker Application Container Engine # 이하 생략

이제 docker를 설치하고 서비스를 실행해야한다. 도커를 설치한 후 현재 사용자를 도커 그룹에 추가하고 도커 서비스를 실행한다.

# ==============================================================================
# 1. 도커 허브에 올린 이미지 빌드 파일 내려받기
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo docker pull '도커허브_사용자이름'/'이미지_이름':'태그_이름'

# ==============================================================================
# 2. 도커 컨테이너로 이미지 빌드 파일 실행
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo docker run -d -p '포트 번호':'포트 번호' '도커허브_사용자이름'/'이미지_이름':'태그_이름'


# ==============================================================================
# 3. 이미지가 제대로 실행되는지 확인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo docker ps

# 제대로 실행되고 있다면 아래와 같은 문구 출력
CONTAINER ID   IMAGE        COMMAND                CREATED         STATUS         PORTS             NAMES
123a1234567b   '이미지_이름'   "java -jar /app.jar"   9 seconds ago   Up 8 seconds   '포트 번호:포트 번호'   '이름'
png

도커 이미지를 제대로 실행했다면 주소창에 '탄력적 IP 주소':'포트 번호' 입력 시 프로젝트 실행 화면을 확인할 수 있다.

3.2. 자체 호스팅 러너 설정

png

이제 깃헙 액션 러너를 AWS EC2에 연동해야한다. 깃헙 프로젝트의 Settings탭을 클릭해서 설정 탭으로 이동하자. 좌측 사이드바의 Code and automation에서 Actions ➜ Runners버튼을 클릭하면 현재 저장소의 깃헙 액션 러너를 확인할 수 있다. New self-hosted runner버튼을 클릭하면 새로운 액션 러너를 생성할 수 있다.

png

깃헙 자체 호스팅 러너는 다음 단계로 생성한다.

  1. Runner image를 선택: linux

  2. Architecture 선택: x64

  3. Download의 코드를 호스팅 할 머신에 입력: AWS EC2 터미널에 입력

  4. Configure 입력

# 제대로 Download & Configure이 완료되었다면 아래와 같은 문구 출력
--------------------------------------------------------------------------------
|        ____ _ _   _   _       _          _        _   _                      |
|       / ___(_) |_| | | |_   _| |__      / \   ___| |_(_) ___  _ __  ___      |
|      | |  _| | __| |_| | | | | '_ \    / _ \ / __| __| |/ _ \| '_ \/ __|     |
|      | |_| | | |_|  _  | |_| | |_) |  / ___ \ (__| |_| | (_) | | | \__ \     |
|       \____|_|\__|_| |_|\__,_|_.__/  /_/   \_\___|\__|_|\___/|_| |_|___/     |
|                                                                              |
|                       Self-hosted runner registration                        |
|                                                                              |
--------------------------------------------------------------------------------

# Authentication
√ Connected to GitHub

추가적으로 러너의 그룹과 이름, 라밸 등을 설정할 수 있다.

# ==============================================================================
# 1. 깃헙 액션 러너 백그라운드 실행
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
nohup ./run.sh &

# ==============================================================================
# 2. 깃헙 액션 러너 확인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
ps -ef | grep ./run.sh

# 실행 중이라면 아래와 같이 확인
'사용자_이름'  111111  333333  0 18:19 pts/0    00:00:00 /bin/bash ./run.sh
'사용자_이름'  222222  444444  0 18:21 pts/0    00:00:00 grep --color=auto ./run.sh

# ==============================================================================
# 3. 실행 종료
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
kill -9 '프로세스_PID' # 111111

깃헙 액션 러너를 실행하면 설정은 끝난다. 참고로 Amazon Linux 2023의 경우 GitHub Actions Runner를 설정할 때 자잘한 오류가 일어나는 편이다. 에러를 다시 재현하기 위해서는 인스턴스 초기화가 필요해서 나중에 추가해두도록 하겠다(TODO).

자체 호스팅 러너의 보안

깃헙 러너는 깃헙 액션을 배포 및 관리하는 시스템이다. 깃헙 러너는 GitHub에서 제공하는 기본 호스팅 러너와 자체 호스팅 러너로 나뉜다. 기본 호스팅 러너와 달리 자체 호스팅 러너는 공개 저장소(public repository)인 경우 보안 문제가 있다(참고 링크). 공개 저장소를 포크하고 workflow에서 코드를 실행하는 Pull Request를 생성하면 자체 호스팅 러너 머신에서 잠재적으로 위험한 코드가 실행될 수 있기 때문이다. 따라서 개인 저장소(private repository)가 아니면 되도록 사용하지 않는 걸 권장한다.

4. GitHub Actions

png

이제 GitHub Actions를 생성해보자. 프로젝트의 Actions탭을 클릭하면 내 저장소에 알맞은 GitHub Actions가 제안된다. 여기서 Java with Gradle카드의 Configure버튼을 클릭한다.

png

그러면 자동으로 GitHub Actions을 실행할 수 있는 gradle.yml 파일을 생성할 수 있다. gradle.yml 파일을 작성할 때는 자신의 스프링 부트 버전에 따라 Java 버전을 다르게 해야 한다.

png

만약 스프링 부트가 3.x 이상인데 자바 버전이 11인 경우 버전 불일치로 깃헙 액션이 실패할 수 있다.

name: Java CI/CD with Gradle

on:
  push:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'

    - name: Build with Gradle
      uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
      with:
        arguments: clean bootJar

    - name: Build a Docker image
      run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/'도커허브_저장소이름' .

    - name: Log in to Docker
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}

    - name: Push the image to Docker Hub
      run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/'도커허브_저장소이름'

  deploy:
    name: Deploy
    needs: build-docker-image
    runs-on: self-hosted

    steps:
      - name: Pull from Docker
        run: sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/'도커허브_저장소이름'
      
      - name: Stop Docker container
        run: sudo docker stop $(sudo docker ps -q) 2>/dev/null || true

      - name: Run a new Docker container
        run: sudo docker run --name '도커허브_저장소이름' --rm -d -p '포트 번호':'포트 번호' ${{ secrets.DOCKERHUB_USERNAME }}/'도커허브_저장소이름'

      - name: Remove old Docker image
        run: sudo docker system prune -f

위와 같이 gradle.yml을 작성 후 Commit changes...를 클릭하면 gradle.yml 파일이 main 브랜치의 workflows 폴더 아래 생성된다.

png

이제 Actions 탭을 가면 자동으로 Build와 Deploy가 실행되는 걸 볼 수 있다.

png

정상적으로 Build&Deploy가 완료되었다면 역시나 주소창에 '탄력적 IP 주소:포트 번호' 입력 시 프로젝트 실행 화면을 확인할 수 있다.

참고 자료

Last updated