현재 상황
캡스톤 프로젝트를 진행하면서 다음과 같은 디렉터리 구조를 가지고 있는 상황이다.
Repo
- .github
- frontend
- backend
- gpt
...
현재 EC2 내에는 Spring Boot Jar file과 Docker Container에서 Redis Image를 pull 받아와야 하는 상황이다.
일일이 Docker 명령어를 입력하여 container image를 pull 받아오는 것보다 docker-compose를 통해 한 번에 Docker Container에서 이미지를 pull 받아오고자 해당 방법을 진행했다.
DB는 RDS를 활용하여 구축했기에, Docker-compose에는 담지 않았다.
하지만, EC2 내 로컬 DB를 활용한다면 docker-compose.yml에 DB 관련 코드를 작성해야 한다.
1. EC2에서 Docker 및 Docker-compose 설치
- docker 설치
# 1. 패키지 업데이트
sudo apt-get update
# 2. GPG 키 추가 (보안 인증)
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.gpg > /dev/null
# 3. Docker 공식 저장소 추가
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 4. 패키지 목록 업데이트
sudo apt-get update
# 5. Docker 및 필수 패키지 설치
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
- docker-compose 설치
# 최신 버전 다운로드 (버전 확인 후 업데이트 가능)
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 실행 권한 부여
sudo chmod +x /usr/local/bin/docker-compose
# 버전 확인
docker-compose --version
만약 구버전 docker를 설치하고 싶으면 다음과 같이 진행한다
sudo apt-get update
sudo apt-get install -y docker.io
2. Docker 및 Docker-compose 실행
# Docker 서비스 실행
sudo systemctl start docker
# Docker Compose 실행
cd ~/deploy # docker-compose.yml이 있는 디렉터리로 이동
sudo docker-compose up -d # 백그라운드 실행
docker-compose.yml은 CI/CD이후 생기는 docker-compose.yml을 수동으로 실행하는 방법이다.
3. CI / CD를 위한 워크플로우 코드 작성
- .github / workflows / backend-ci-cd.yml
name: BE CI/CD with Docker Compose
on:
push:
branches:
- develop # develop 브랜치 기준으로 실행
paths:
- 'backend/**' # 백엔드 폴더 내부 파일이 변경될 때만 실행
jobs:
build-docker-image:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup application.yml
run: |
cd backend/src/main/resources
echo "${{ secrets.APPLICATION_DB }}" > application-db.yml
echo "${{ secrets.APPLICATION_CONFIG }}" > application-config.yml
echo "${{ secrets.APPLICATION_JWT }}" > application-jwt.yml
echo "${{ secrets.APPLICATION_SWAGGER }}" > application-swagger.yml
echo "${{ secrets.APPLICATION_ACTUATOR }}" > application-actuator.yml
echo "${{ secrets.APPLICATION_OAUTH }}" > application-oauth.yml
shell: bash
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle Wrapper
run: |
cd backend
./gradlew clean build -x test
- name: Docker Image Build
run: |
cd backend
docker build -t ${{ secrets.DOCKER_USERNAME }}/iambohyun:latest .
- name: DockerHub 로그인
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Docker Hub Push
run: docker push ${{ secrets.DOCKER_USERNAME }}/iambohyun:latest
deploy-to-ec2:
needs: build-docker-image
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- name: SSH into EC2 and deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
# 환경 변수 .env 파일로 설정
echo "DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }}" > ~/deploy/.env
# deploy 디렉터리 생성 및 이동
mkdir -p ~/deploy
cd ~/deploy
# Docker Hub 로그인
echo "${{ secrets.DOCKER_PASSWORD }}" | sudo docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
# 기존 컨테이너 중지 및 제거
if [ -f "docker-compose.yml" ]; then
sudo docker-compose down
fi
# 최신 docker-compose.yml 다운로드
curl -O https://raw.githubusercontent.com/${{ github.repository }}/develop/backend/docker-compose.yml
# 최신 Docker 이미지 가져오기
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/iambohyun:latest
# 오래된 이미지 제거
sudo docker image prune -f
# docker-compose 실행 (최신 이미지 반영)
sudo -E docker-compose up -d --force-recreate --remove-orphans
- 주요 특징
- backend/** 경로에 commit이 발생할 때만 해당 ci-cd 워크플로우 실행
- docker 명령어를 직접 실행하는게 아닌, docker-compose.yml을 다운받은 뒤 해당 docker-compose를 실행하는 코드로 변경
- docker-compose.yml에서는 GitHub에서 환경변수를 받지 못하기 때문에, ci-cd.yml에서 따로 환경변수를 설정해줘야 한다.
- sudo는 기본적으로 환경변수를 지우기 때문에, docker-compose.yml을 실행할 때 -E 를 사용하여 환경변수를 유지한다.
주의!
deploy-to-ec2 단계에서 EC2 서버의 ~/deploy 디렉터리에 .env 파일을 생성하고 환경 변수를 설정하는데, 이 .env 파일은 docker-compose가 자동으로 참조할 수 있습니다.
따라서, docker-compose.yml에서 사용할 환경변수는 .env에 저장해야 한다.
3. docker-compose 및 dockerfile 코드 작성
- backend/docker-compose.yml
version: "3.8"
services:
app:
container_name: spring-app
image: "${DOCKER_USERNAME}/iambohyun:latest"
restart: always
ports:
- "8080:8080"
depends_on:
- redis
redis:
container_name: redis
image: redis:latest
restart: always
ports:
- "6379:6379"
command: redis-server --save 60 1 --loglevel warning
- GitHub에서 환경변수를 받아오지 못하기 때문에, ci-cd 워크플로우에서 설정해준 환경변수를 바탕으로 값을 채워준다.
- backend/Dockerfile
# JDK 17 Base Image 사용
FROM openjdk:17-jdk-slim
# 작업 디렉터리 설정
WORKDIR /app
# Gradle 빌드 후 생성된 JAR 복사
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
# 실행 권한 부여 (entrypoint.sh 스크립트 실행 가능하게 설정)
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 컨테이너 시작 시 실행할 명령어
ENTRYPOINT ["/entrypoint.sh"]
- entrypoint.sh
#!/bin/bash
echo "Starting Spring Boot Application..."
exec java -jar app.jar
4. GitHub Secret Key 등록
github settings -> Secrets and variables의 Actions -> Repository Secrets 추가
DOCKER_USERNAME 등 필요한 환경 변수들을 등록하면 된다.
5. 실행 중인 컨테이너에 명령어를 입력하는 방법
실행중인 컨테이너에 명령을 전달하고 싶을 때는 아래와 같은 코드를 실행한다.
docker exec 컨테이너이름 혹은 컨테이너아이디 명령
Docker Container에서 pull 한 Redis Image를 실행시키고, 해당 Redis의 컨테이너 환경에서 명령어를 실행해야 한다.
즉, (1)도커 서버가 먼저 작동하고 있어야 한다. 그 후 (2) Redis Client를 실행해준다. 그러고 나서(3) 그 클라이언트에 명령어를 입력해 레디스 서버에 전달하는 방식으로 레디스가 구성된다.
docker exec -it (redis container id) redis-cli
위 명령어를 사용해서 Redis Container에서 Redis-cli에 명령을 입력할 수 있다
-it 키워드란?
-it를 붙여줘야, 명령어를 실행한 후 계속 명령어를 적을 수 있다고 한다.
만약 -it 가 없다면, exec 명령어를 한 번 실행한 후 해당 명령어를 빠져나오게 된다.
※ i: interactive, t: terminal을 의미
첫 번째 문제 발생
해당 docker.gpg 키가 제대로 인식되지 않는 현상이 발생했다.
해당 이슈를 GPT에게 물어보니 현재 사용하고 있는 EC2의 Ubuntu 버전을 24.04 Noble 버전을 사용하고 있는데,
24.04 Noble에서 Docker 공식 저장소가 아직 완벽히 지원되지 않아서 발생하는 문제라고 한다.
Docker 패키지가 아직 Noble에 추가되지 않아서, Ubuntu 22.04(Jammy) 패키지를 사용하면 해결할 수 있다고 한다.
🔧 해결 방법
Noble 대신 Ubuntu 22.04(Jammy)의 Docker 저장소를 사용하기
1️⃣ 기존 Docker 저장소 삭제
sudo rm -f /etc/apt/sources.list.d/docker.list
2️⃣ Jammy(Ubuntu 22.04) 저장소로 추가
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
3️⃣ 패키지 목록 업데이트
sudo apt-get update
4️⃣ Docker 패키지 설치
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
5️⃣ Docker 설치 확인
docker --version
두 번째 문제 발생
해당 CI CD를 런 돌렸더니 다음과 같은 에러가 발생했다.
위 에러는 DNS 문제로 인해 EC2에서 원격 접속이 실패한 것이다.
- lookup *** on 127.0.0.53:53 → EC2 인스턴스에서 도메인 이름(***)을 IP 주소로 변환하려고 하는데 실패함.
- server misbehaving → DNS 서버가 제대로 동작하지 않음.
✅ EC2의 /etc/resolv.conf 수정
cat /etc/resolv.conf
위 명령어를 실행했을 때 확인해보면,
현재 /etc/resolv.conf 내용이 아래처럼 설정되어 있어서 127.0.0.53을 사용한 로컬 DNS 서버 문제일 가능성이 크다
nameserver 127.0.0.53
options edns0 trust-ad
search ap-northeast-2.compute.internal
해결 방법
이걸 Google DNS 또는 AWS 기본 DNS로 변경하여 해결.
sudo vi /etc/resolv.conf
nameserver 127.0.0.53
nameserver 8.8.8.8
nameserver 8.8.4.4
options edns0 trust-ad
search ap-northeast-2.compute.internal
- Docker 재시작
sudo systemctl restart docker
'Back-end > Spring' 카테고리의 다른 글
[Spring] com.auth0 vs jsonwebtoken.jjwt (1) | 2025.02.16 |
---|---|
[Spring] JPA 벌크 연산이란? (0) | 2025.02.01 |
[Spring] Spring Batch를 활용한 배치 프로세싱 (1) | 2025.01.20 |
[Spring] Private 메서드에 @Transactional 선언 시 트랜잭션이 동작하는가? (0) | 2025.01.16 |
[Spring] Spring Data JPA에서 새로운 Entity인지 판단하는 방법? (1) | 2024.12.08 |