먼저, GitHub Repository 환경변수 설정이 필요합니다.

①Secrets와 ②Variables가 있습니다. Secrets는 민감한 정보를 처리할 때, Variables는 민감 정보는 아닌 환경에따라 자주 바뀌는 값들을 등록해줍니다.
Environment secrets는 등록한 환경을 기준으로 변수를 관리하고, Repository secrets는 해당 레포지토리 기준으로 관리합니다. 저는 이 레포지토리에서만 사용할 환경변수(Repository secrets)를 등록하였습니다.
EC2 연결에 필요한 환경변수 등록이 필요합니다. 꼭 이 변수 이름을 따라하실 필요는 없습니다.
EC2_USEREC2_HOSTEC2_KEYUSER, HOST, KEY 값은 무엇인가?

AWS EC2 인스턴스 페이지에서 연결 버튼을 클릭

사용자 이름(USER)을 확인할 수 있습니다. 위처럼 EC2를 생성하였다면 기본적으로 ubuntu일 것입니다.

SSH 클라이언트 탭에서도 확인하실 수 있습니다. 사용자 이름과 서버 호스트 주소(HOST)가 @ 표기로 나뉘어 있습니다.
이것은 퍼블릭 IPv4 DNS 주소와 동일합니다.

마지막으로 키 페어(KEY) 값은 텍스트 편집기로 열면 텍스트를 확인할 수 있습니다.
또는, CLI 로 파일 내용 읽을 수 있습니다. (cat: Linux/macOS) (type: Windows)
cat goodbite-back.pem
type goodbite-back.pem



.pem 파일 내용 전부 넣으시면 됩니다.
EC2 연결에 필요한 환경변수는 끝났습니다.
이번엔 Docker Hub 연결에 필요한 환경변수를 등록하겠습니다. 마찬가지로 이 변수 이름을 따라하실 필요는 없습니다.

DOCKERHUB_USERNAME : 계정 사용자 이름DOCKERHUB_PASSWORD : 2번 값 copy
마지막으로, 레포지토리에 올려야 할 GitHub Actions의 설정 파일(yml)을 작성하도록 합니다.
전체적인 순서 흐름을 컨트롤하는 중요한 부분입니다. 워크플로우(workflow)라고 합니다. 이 설정 파일은 애플리케이션과 Docker 이미지를 빌드하고 EC2 인스턴스에 배포하는 일련의 과정을 정의합니다.
여기서 많은 시간과 노력을 쏟았습니다. 계속해서 오류들을 만나더라고요.. 정확한 순서와 이유를 파악해야 해결할 수 있었습니다. 제가 작성한 흐름이 정답은 아닙니다. 배포 방법은 정말 많아서 여러 방법을 참고해보는 것이 좋을 것 같습니다.
제가 작성한 다음 코드의 대략적인 순서 흐름은 다음과 같습니다.
1. Gradle 빌드
2. QEMU 설정
3. Docker 이미지 빌드 및 Docker Hub에 push
4. EC2 인스턴스에 Docker 이미지 pull
5. EC2 인스턴스의 Docker 컨테이너 실행
GitHub Actions의 설정 파일(워크플로우 파일)은 반드시 레포지토리의 .github/workflows 디렉토리에 위치해야 합니다. 그래야 워크플로우 파일을 자동으로 인식합니다. 설정 파일(*.yml) 이름은 상관 없습니다.
on:
push:
branches: [ dev ]
env:
DOCKER_IMAGE_TAG_NAME: good-bite
jobs:
build-and-docker-push:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'corretto'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Decode keystore and save
run: echo "${{ secrets.PKCS12_BASE64 }}" | base64 --decode > src/main/resources/api.goodbite.site.p12
- name: Build with Gradle
run: ./gradlew clean build -x test
env:
DB_USERNAME: ${{ vars.DB_USERNAME }}
...
...
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest
no-cache: true
deploy-to-ec2:
needs: build-and-docker-push
runs-on: ubuntu-latest
steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ vars.EC2_USER }}
key: ${{ secrets.EC2_KEY }}
script: |
sudo apt-get update
sudo apt-get install -y docker.io
sudo usermod -aG docker $USER
newgrp docker
CONTAINER_ID=$(sudo docker ps -q --filter "publish=80-80")
if [ ! -z "$CONTAINER_ID" ]; then
sudo docker stop $CONTAINER_ID
sudo docker rm $CONTAINER_ID
fi
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest
sudo docker run -d -p 80:80 \
-e DB_USERNAME=${{vars.DB_USERNAME}} \
... \
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest
라벨 간단 설명
on:push:branches 지정 브랜치에 푸시가 발생할 때 워크플로우 이벤트 발생
env 변수 지정, label depth에 따라 적용 범위가 다름
Jobs: [job-name]: 워크플로우에서 실행할 일련의 작업 과정. 하나의 job은 순차적으로 실행하며, 각각의 job은 병렬적으로 수행
runs-on 실행환경
steps 작업 순서를 (-) 단위로 구분
name: [step-name] 각 작업 단계(step)의 이름
uses GitHub Actions의 공식 액션을 사용
actions/checkout@v3 워크플로우 실행 시 레포지토리 코드를 가져오는 작업with uses의 매개변수
${{secrets.~}} , ${{vars.~}}, ${{env.~}}
secrets,vars,env 를 변수로 사용
작업 간단 설명
run: chmod +x gradlew gradlew 파일 실행 권한 부여
run: echo "${{ secrets.PKCS12_BASE64 }}" | base64 --decode > src/main/resources/api.goodbite.site.p12 디코딩 변환이 필요한 추가 작업을 수행
run: ./gradlew clean build -x test 테스트 없이 빌드
uses: docker/setup-qemu-action@v3 QEMU 설정(하드웨어 가상화/에뮬레이터)
uses: docker/login-action@v3 Docker Hub 로그인
uses: docker/setup-buildx-action@v3 멀티 아키텍처 이미지 빌드
uses: docker/build-push-action@v6 Dockerfile을 기반으로 이미지를 빌드하고, Docker Hub로 푸시
uses: appleboy/ssh-action@v1.0.3 EC2 인스턴스에 SSH로 접속
script 실행 스크립트
# EC2 인스턴스에 필요한 패키지 설치
sudo apt-get update
sudo apt-get install -y docker.io
sudo usermod -aG docker $USER # 현재 사용자를 Docker 그룹에 추가
newgrp docker # 그룹 변경
# 기존 컨테이너 중지 및 제거
CONTAINER_ID=$(sudo docker ps -q --filter "publish=443-443")
if [ ! -z "$CONTAINER_ID" ]; then
sudo docker stop $CONTAINER_ID
sudo docker rm $CONTAINER_ID
fi
# Docker Hub에서 이미지 pull
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest
# 위 이미지로 Docker 컨테이너를 실행
sudo docker run -d -p 80:80 \ # 컨테이너 실행 포트
-e DB_USERNAME=${{secrets.DB_USERNAME}} \
-e ... \ # Docker 에게 환경변수 전달
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest
yml 파일을 작성 후, GitHub 저장소 트리거 브랜치에 푸시하게 되면 자동으로 위 스크립트를 실행합니다.

Actions 탭에서 실시간으로 workflow 현황을 볼 수 있습니다.

jobs가 성공했는지, 실패했는지 확인할 수 있고,

step 별로 어떤 이유로 실패했는지 로그를 확인할 수 있습니다.
프로젝트 파일 수정이 필요하다면 수정한 후 다시 push 해서 재시도하면 되고

파일 변경은 없고 외부 문제(EC2 서버 다운 등)의 경우 [Re-run jobs] 버튼을 사용하면 됩니다.
배포 서버가 잘 돌아가는지 확인할 필요가 있습니다. 서비스 장애가 발생하면 로그를 확인해야 할 것입니다. AWS를 사용한다면 간편하게 접근할 수 있는 방법을 제공하고 있습니다.
여러 방법이 있지만, 저는 SSH 클라이언트로 연결하였습니다. 인텔리제이에서 개발 작업을 하면서 동시에 배포 서버에 접속하기 가장 편리했던 방법입니다.

AWS EC2 인스턴스 페이지에서 연결 버튼을 클릭

SSH 클라이언트 탭에서 ssh 명령어를 복사하여 터미널에 실행합니다. 이전에 저장했던 pem 파일이 존재해야 합니다. 간혹 퍼미션 문제가 발생하던데, chmod 명령어로 pem 파일에 적절한 권한 부여를 해줘야 합니다. (400 또는 600 권장)
발생 이유
SSH 명령어로 .pem 파일을 사용할 때 권한 문제가 발생하는 이유는, .pem 파일의 권한이 너무 넓을 경우 SSH가 보안을 위해 경고를 출력하고 접속을 차단합니다. .pem 파일은 개인 키 파일이므로 특정 권한만 허용해야 합니다.
클라우드 관리 시에 내가 생성한 인스턴스를 함께하는 팀원 혹은 다른 사람이 리소스를 관리해야 할 사항이 생기기도 합니다. AWS는 이를 위한 서비스로 IAM(Identity and Access Management)을 제공합니다.
AWS에서 제공하는 리소스 접근 권한 서비스는 IAM 말고도 여러 개 있으니 상황에 맞게 적절히 사용하는 것이 좋을 것 같다고 생각합니다.