❗아키텍쳐
리포지토리에 커밋을 하면 자동으로 EC2 서버에 배포되도록 설계하고자 한다.
1. 로컬에서 작업한 것을 깃허브 리포지토리 올린다.
2. develop 브랜치에서 main으로 push된 경우에 깃허브 액션을 실행한다.
3. 리포지토리의 코드를 기반으로 빌드 파일을 생성하고, Dockerfile의 명세대로 이미지를 빌드하여 Docker Hub에 이미지를 push한다.
4. 서버에서 Docker Hub로 부터 이미지를 pull한다.
5. 이미지를 실행시켜 스프링부트 프로젝트를 담은 컨테이너를 구동한다.
6. 서버 도메인(or IP)로 스프링 프로젝트가 실행 중인지 확인한다.
위의 과정으로 실행될 수 있게 설정할 것이다!
❗Dockerfile 설정
프로젝트 root에 Dockerfile을 생성한다.
해당 프로젝트에서는 데이터베이스로 RDS를 사용하기 때문에 스프링부트 프로젝트 실행 시 필요한 application.yml 파일을 프라이빗하게 관리할 필요가 있었다. application.yml 파일을 깃허브 시크릿에 APPLICATION_YAML 변수로 파일 내에 작성된 내용을 변수화시켜주었다.
# Use the official OpenJDK 17 base image
FROM openjdk:17-jdk
# Set the working directory
WORKDIR /app
# Copy the JAR file into the container
COPY build/libs/*.jar app.jar
# Create the necessary directory for application.yml
RUN mkdir -p /app/src/main/resources
# Define a build argument for application.yml content
ARG APPLICATION_YAML
# Create the application.yml file from the build argument
RUN echo "$APPLICATION_YAML" > /app/src/main/resources/application.yml
# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]
추후 파일이 실행될 때 application.yml을 사용할 수 있도록 스크립트를 추가하였다.
# jdk17 Image Start
FROM openjdk:17
# 인자 설정 - JAR_File
ARG JAR_FILE=build/libs/*.jar
# jar 파일 복제
COPY ${JAR_FILE} app.jar
# 실행 명령어
ENTRYPOINT ["java", "-jar", "app.jar"]
이 Dockerfile은 일반적으로 자주 사용하는 형태이다!
❗도커 토큰 발급 및 GIthub Secret에 추가하기
https://radiant515.tistory.com/623
위 포스팅을 통해 도커의 액세스 토큰을 발급 받을 수 있다.
setting > Secrets and variables > Actions으로 이동
New Repository secret 클릭
변수명을 작성하고 안에 secret에 키값들을 넣어준다.
DOCKERHUB_USERNAME, DOCKERHUB_PASSWORD 2가지를 추가한다.
❗gradle.yml
프로젝트 리포지토리에서 Actions 탭에서 Java with Gradle을 선택하여 gradle.yml 파일을 생성한다.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
types: [closed]
permissions:
contents: read
jobs:
build-docker-image:
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'
# 1. gradlew 실행 권한 설정
- name: Set execute permission on gradlew
run: chmod +x ./gradlew
# 2. 기존 application.yml 파일 제거
- name: Remove existing application.yml
run: rm -f src/main/resources/application.yml
# 3. secrets에서 application.yml 파일 생성
- name: Create application.yml from secret
run: echo "${{ secrets.APPLICATION_YAML }}" > src/main/resources/application.yml
# 4. 테스트 단계를 제외한 Gradle 빌드
- name: Build with Gradle
run: ./gradlew clean build -x test
# 5. Docker 이미지 빌드
- name: Build Docker image
run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/safe-t .
# 6. Docker 로그인
- name: Docker login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
# 7. Docker 이미지 푸시
- name: Push Docker image to DockerHub
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/safe-t
1. gradlew 실행 시 실행 권한이 없으면 permission denied 오류가 발생하기에 변경해 준다.
2. 기존에 존재하는 application.yml은 삭제한다.
3. secret에 넣은 application.yml을 실제 있어야 할 루트에 추가한다.
4. 빌드 시 test 항목을 포함하여 실행하면 오류가 발생하는 경우가 있기 때문에 옵션을 추가해 준다.
5. 프로젝트를 도커 이미지로 빌드한다.
6. 도커에 로그인한다.
7. 생성해둔 도커 리포지토리에 빌드한 이미지를 push한다.
여기서 주의할 점은 자신이 만든 도커 리포지토리명으로 해당 부분을 변경해 주어야 한다.
❗main으로 PR 생성하기
main 브랜치에 push되었을 때 깃허브 액션이 실행될 것이기 때문에 develop에서 main으로 PR을 생성해야 한다.
Pull requests 클릭
main <- develop으로 방향 설정 후 title 입력
create pull request 클릭
PR이 생성되면 깃허브 액션이 돌아가는 모습을 볼 수 있다.
액션이 성공적으로 실행되면 초록색 체크로 표시된다
merge pull request를 클릭하면 develop의 내용이 main으로 반영된다
위의 스크립트가 실행되면 마지막에 도커 리포지토리에 이미지가 push되는 과정까지였기 때문에 이미지가 업데이트된 것을 확인할 수 있다.
❗우분투에 도커 설치
https://radiant515.tistory.com/624
❗AWS IAM 생성
EC2에 접속하여 도커 이미지를 pull 받기 위해선 서버로 접속할 수 있는 사용자를 설정해 주어야 한다. 여기선 AWS IAM을 사용해 볼 것이다.
사용자 생성 클릭
사용자 이름 입력 후 다음 클릭
직접 정책 연결을 클릭하여 AmazonEC2FullAccess 선택
사용자 생성 클릭
만들어진 사용자 클릭 후 보안 자격 증명 탭 클릭
액세스 키 만들기 클릭
AWS 컴퓨팅 서비스를 클릭 후 체크 박스 확인하고 다음 클릭
한 번 만들어진 액세스 키와 비밀 키는 다시 보여주지 않으니 csv 파일로 다운하거나 안전한 곳에 복사하여 저장해야 한다.
아까 도커 유저 정보를 시크릿에 등록한 것과 동일하게 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 이름으로 시크릿을 생성한다.
❗EC2에서 도커 이미지 pull하기
서버 접속 시 서버의 IP와 pem키가 필요한데 이 또한 시크릿에 넣어주어 변수화하였다.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
types: [closed]
permissions:
contents: read
jobs:
build-docker-image:
# 생략
run-docker-image-on-ec2:
needs: build-docker-image
runs-on: ubuntu-latest
steps:
# 1. SSH 클라이언트 설치
- name: Install SSH client
run: sudo apt-get install -y openssh-client
# 2. SSH 키를 추가하고 key.pem 파일의 권한을 설정
- name: Add SSH key
run: echo "${{ secrets.EC2_SSH_KEY }}" > key.pem && chmod 600 key.pem
- name: SSH and Run Docker Commands on EC2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
ssh -o StrictHostKeyChecking=no -i key.pem ubuntu@${{ secrets.AWS_EC2_IP }} << 'EOF'
# 3. Docker가 설치되고 실행 중인지 확인
sudo systemctl start docker
sudo usermod -aG docker ubuntu
# 4. 그룹 변경 사항을 적용하기 위해 로그아웃하고 다시 로그인
sudo su - ubuntu -c "exit"
# 5. 동일한 이름의 기존 컨테이너를 중지
sudo docker stop safe-t 2>/dev/null || true
# 6. 동일한 이름의 기존 컨테이너를 제거
sudo docker rm safe-t 2>/dev/null || true
# 7. Docker 이미지를 pull
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/safe-t
# 8. 새로운 컨테이너를 명시된 이름으로 실행
sudo docker run -d -p 8080:8080 --name safe-t ${{ secrets.DOCKERHUB_USERNAME }}/safe-t
# 9. 컨테이너 로그를 확인하여 오류를 체크
sudo docker logs safe-t
# 10. 사용하지 않는 Docker 이미지를 정리
sudo docker system prune -f
EOF
gradle.yml 전문이다
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
types: [closed]
permissions:
contents: read
jobs:
build-docker-image:
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: Set execute permission on gradlew
run: chmod +x ./gradlew
- name: Remove existing application.yml
run: rm -f src/main/resources/application.yml
- name: Create application.yml from secret
run: echo "${{ secrets.APPLICATION_YAML }}" > src/main/resources/application.yml
- name: Build with Gradle
run: ./gradlew clean build -x test
- name: Build Docker image
run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/safe-t .
- name: Docker login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Push Docker image to DockerHub
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/safe-t
run-docker-image-on-ec2:
needs: build-docker-image
runs-on: ubuntu-latest
steps:
- name: Install SSH client
run: sudo apt-get install -y openssh-client
- name: Add SSH key
run: echo "${{ secrets.EC2_SSH_KEY }}" > key.pem && chmod 600 key.pem
- name: SSH and Run Docker Commands on EC2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
ssh -o StrictHostKeyChecking=no -i key.pem ubuntu@${{ secrets.AWS_EC2_IP }} << 'EOF'
# Ensure Docker is installed and running
sudo systemctl start docker
sudo usermod -aG docker ubuntu
# Ensure changes are applied (log out and log back in to apply group changes)
sudo su - ubuntu -c "exit"
# Stop any existing containers with the same name
sudo docker stop safe-t 2>/dev/null || true
# Remove any existing containers with the same name
sudo docker rm safe-t 2>/dev/null || true
# Pull Docker image
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/safe-t
# Run new container with explicit name
sudo docker run -d -p 8080:8080 --name safe-t ${{ secrets.DOCKERHUB_USERNAME }}/safe-t
# Check container logs for errors
sudo docker logs safe-t
# Clean up unused Docker images
sudo docker system prune -f
EOF
gradle.yml 파일에 CD 과정을 추가하는 스크립트이다. 이 과정에서는 AWS IAM으로 생성한 사용자로 EC2 서버로 접속한다. 그 후엔 동일한 이름을 가진 컨테이너를 중지, 제거하고 Docker Hub에서 pull을 한다. 그 다음 run 명령을 통해 실행하면 배포가 완료된다.
깃허브 액션에서는 CI, CD 과정이 이와같이 분리된 형태로 보여진다.
❗실행 여부 확인
# 실행 중인 컨테이너
docker ps
# 실행 + 중지 컨테이너
docker ps -a
해당 명령어로 컨테이너가 실행 중인지 확인할 수 있다.
또는 API를 테스트하거나 swagger 주소로 들어가 보는 경우도 있다.
❗컨테이너가 생성되지 않은 경우
컨테이너가 생성되지 않은 경우도 있다. 스크립트가 잘못된 경우엔 깃허브 액션이 실행되는 부분에서 로그를 확인할 수 있고, 스프링 프로젝트가 실행되지 않는 경우는 서버에서 docker logs를 사용하여 확인할 수 있다.
docker ps -a
docker logs [컨테이너ID]
컨테이너가 실행되지 않았다면 일반 ps 명령어로는 확인할 수 있으니 -a 옵션을 붙여 중지된 컨테이너를 확인한다. 그 다음 컨테이너 ID를 사용하여 로그를 확인한다면 위의 사진처럼 스프링 실행 시 나오는 로그를 확인할 수 있다.
❗트러블 슈팅
CD 과정에서 컨테이너가 실행되고 중지되는 현상이 계속 발생했었다. 처음엔 application.yml 파일의 일부분을 변수화하여 CI 과정에서 실제 값으로 대체하려고 했는데 이 부분에서 계속 오류가 발생하여 스프링 프로젝트가 중지 되어 컨테이너도 다운되었던 현상이었다.
이에 대한 대처 방법으로 application.yml의 내용 자체를 변수화하여 파일을 생성하는 식으로 처리하였고 성공적으로 배포되었다!
'🔻DevOps > Docker' 카테고리의 다른 글
[Docker] 우분투 20.04에 Docker 설치 (0) | 2024.08.04 |
---|---|
[Docker] 도커 access token 발급 (0) | 2024.08.04 |
[Docker] 우분투에 이미지 생성 후 로컬에서 pull하기 (0) | 2024.07.07 |
[Docker] 우분투에 도커 설치 후 명령어 실습 (0) | 2024.07.05 |
[Docker] Docker 명령어(조회, 정지, 시작) (0) | 2022.12.26 |