[개발자 코드 작성 및 Git push]
↓
[GitHub 저장소 → Actions 실행됨]
↓
[GitHub Actions]
- application.yml 생성
- Gradle 빌드 (JAR 생성)
- Docker 이미지 생성 (build)
- Docker Hub에 push
↓
[EC2 서버 (이미 세팅된 상태)]
- GitHub Actions가 SSH 접속
- Docker 이미지 pull
- 기존 컨테이너 stop + rm
- 새 컨테이너 run
↓
[사용자 브라우저에서 최신 코드 반영됨]
기존의 수동 배포 방식은 코드 수정을 했다면 손수 jar 파일 빌드 및 도커 이미지 build, stop, rm, run 등을 진행해줘야 했다. 협업 과정에서 많은 불편함이 있다고 하여 자동 배포를 시도해보려고 한다.
이번에 시도한 방식은 깃 액션을 이용하는 방식이다. 깃 액션 설정을 통하여 CI/CD 방식을 구축하여 코드 수정 후 깃 푸시를 진행하면 이를 감지하여 자동으로 새로운 도커 이미지를 만들어 ec2 환경에 배포되도록 설정한다.
좀 더 살을 붙여 설명하자면 다음과 같다.
💸💸💸💸💸
# 스프링 부트 프로젝트 생성
# 의존성 및 초기 설정(build.gradle, application.yml)
# 실행 테스트
가장 먼저 스프링 부트 프로젝트를 생성해보도록 하자.
(프로젝트 생성 관련 내용은 생략. 아래 링크 참고할 것.)
스프링 부트 프로젝트 생성
이후 intellij 혹은 vscode 등에서 프로젝트를 열어준다.
프로젝트를 열어주었다면 기본적으로 해야 하는 설정 3가지를 알아보자.
가. Gradle 설정 (build.gradle)
- 어떤 라이브러리를 쓸지 정함
- Java 버전 설정
- 빌드 결과물 설정
나. application.yml 또는 application.properties 설정
- 서버 포트 설정
- DB 연결 정보
- JPA 관련 설정
- 애플리케이션 환경 변수
다. Spring Boot 앱 실행 테스트
$ chmod +x gradlew // 권한 부여 $ ./gradlew build // 프로젝트 빌드
💸💸💸💸💸
# 깃 허브에 업로드
# DockerHub 토큰 생성
# 깃 액션 secret 키 추가
# 도커 파일 생성
# 워크플로우 설정
# 로컬 환경에서 테스트
스프링 부트 프로젝트 생성 및 실행 테스트까지 성공하였다면 깃 허브에 업로드한다.
업로드해주었다면 DockerHub에서 토큰을 생성한다. (설정 → Access Tokens) 워크플로우 설정을 하기 이전에 깃허브 secret key에 아래 내용들을 추가해준다.
(레포 → Settings → Secrets and variables → Actions → New repository secret)
DOCKERHUB_USERNAME : DockerHub 아이디
DOCKERHUB_TOKEN : 위에서 만든 토큰
PROJECT_NAME : (마음대로 정해도 됨)
APPLICATION_YAML : application.yml의 실제 내용 전체
이후 프로젝트 루트 경로에 dockerfile과 .github/workflows/docker-ci.yml 파일을 생성하여 아래 코드를 추가하여 워크플로우 설정을 해준다.
// dokerfile
# 1. OpenJDK 17을 기반 이미지로 사용 FROM openjdk:17-jdk # 2. 작업 디렉토리 설정 WORKDIR /app # 3. 빌드된 JAR 파일을 컨테이너에 복사 COPY build/libs/*.jar app.jar # 4. application.yml을 위한 리소스 디렉토리 생성 RUN mkdir -p /app/src/main/resources # 5. GitHub Actions에서 전달받는 application.yml 내용을 받을 ARG 선언 ARG APPLICATION_YAML # 6. ARG를 실제 파일로 생성 RUN echo "$APPLICATION_YAML" > /app/src/main/resources/application.yml # 7. 애플리케이션 실행 ENTRYPOINT ["java", "-jar", "app.jar"]
// docker-ci.yml
name: Docker CI (Spring Boot) on: push: branches: [ main ] jobs: build-and-push: runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v2 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' - name: Grant execute permission to gradlew run: chmod +x ./gradlew - name: Remove application.yml if exists 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 Spring Boot project without tests run: ./gradlew clean build -x test - name: Build Docker image run: | docker build \ --build-arg APPLICATION_YAML="${{ secrets.APPLICATION_YAML }}" \ -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest . - name: Docker login uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Push Docker image to DockerHub run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest
정상적으로 설정되었는지 확인하기 위해서 아무 내용이나 작성 후 깃 푸시를 해본다.
git add .
git commit -m "테스트"
git push origin main
이제 로컬 환경에서 테스트해보자.
docker build -t heui0716/hackathon .
// (도커 이미지를 build하는 코드)
docker push heui0716/hackathon
// (build된 도커 이미지를 깃허브에 올리는 코드)
docker run -p 80:8080 heui0716/hackathon
// (올라간 도커 이미지를 내 컴퓨터에서 실행하는 코드)
로컬 환경에서 테스트
위 링크로 브라우저에 접속하여 실행이 되는지 확인해본다.
나는 controller/HomeController.java 파일을 생성하여 테스트 코드로 "테스트 성공!" 텍스트가 출력되도록 코드를 작성해두었다.
💸💸💸💸💸
# EC2 인스턴스 생성
# ssh 접속
이제 깃 액션 설정을 통해 도커 이미지가 자동으로 build되고 push되도록 설정하는데 성공했다. 이후 로컬 환경에서 테스트까지 진행하였다. 이번에는 배포를 진행하기 위해 EC2 서버를 만든다.
aws 콘솔 창에서 EC2 인스턴스를 생성해준다.(관련 내용 생략)
❗.pem 키의 경로를 잘 기억해둬야 한다!
aws EC2는 로컬과는 또 다른 어딘가에 있는 컴퓨터 서버이다. 즉, 이 서버에 접속하기 위해서는 통로가 필요한데 이 역할을 하는 것이 ssh이다. EC2 서버에 들어가기 위해 ssh에 접속해보자.
chmod 400 (본인의 pem 키 경로)/(본인의 pem 키 이름).pem
// 권한 부여하기
ssh -i (본인의 pem 키 경로)/(본인의 pem 키 이름).pem ubuntu@(본인의 EC2 인스턴스 퍼블릭 IPv4 주소)
정상적으로 접속에 성공했다면 아래와 같이 뜬다. (❗초록색 우분투 글씨 집중)

tip) 만약 아래와 같이
too open이라고 하며 보안 문제로 접속이 안된다면pem 파일을wsl 홈으로 복사후 사용하면 된다.
(디폴트인 다운로드 폴더 자체가 퍼미션이 너무 널널해서 생기는 오류...)
cp (본인의 pem 키 경로)/(본인의 pem 키 이름).pem ~/.ssh/ chmod 400 ~/.ssh/(본인의 pem 키 이름).pem ssh -i ~/.ssh/(본인의 pem 키 이름).pem ubuntu@(본인의 EC2 인스턴스 퍼블릭 IPv4 주소)
💸💸💸💸💸
# EC2 환경에 도커 설치
# EC2 환경에 도커 이미지 가져오기
# EC2 환경에 도커 컨테이너 실행
# 배포 환경에서 테스트
ssh 접속까지 완료했다면 EC2 서버에 docker를 설치해준다.
sudo apt update // 패키지 목록 업데이트
sudo apt install -y docker.io // 도커 다운로드
sudo systemctl start docker // 도커 서비스 시작
sudo systemctl enable docker// 부팅 시 자동 실행 설정
docker --version // 설치 확인
도커 허브에 업로드되어 있는 도커 이미지를 가져와 EC2 서버에서 실행할 수 있는 환경을 만들어준다.
sudo docker pull (도커 아이디)/(이미지 이름):latest // 도커 이미지 pull
sudo docker images // 이미지가 정상적으로 pull 되었는지 확인하기
❗단, 위 방법은 public 이미지일 때 해당됨. 만약 private 이미지라면 docker login 입력하여 아이디, 비번으로 로그인 후 pull 해야 함.)
❗추가 주의사항 : docker 관련 명령어는 무조건 앞에 sudo 붙이기!
이제 도커 이미지도 pull 해주었으니 EC2 서버에서 도커 컨테이너를 실행해보자!
sudo docker run -d -p 80:8080 --name (컨테이너이름) (이미지이름):latest
tip) 여기서 80:8080의 의미 = [호스트포트]:[컨테이너포트]임. 즉,
EC2의 80번 포트로 들어온 요청을 Docker 컨테이너 안의9090번 포트로 연결하라는 뜻임
tip) sudo docker ps (실행 확인하는 코드)
- 추가로 만약 오류가 뜬다면 실행 로그를 확인해보면 되는데,
스프링 부트 실행 로그를 확인하기 위한 코드는 아래와 같다.
sudo docker logs (도커 컨테이너 이름)
번외로 알아두면 좋을 도커 관련 명령어도 아래에 첨부한다.
여기까지 진행했다면 이제 http://(본인의 EC2 인스턴스 퍼블릭 IPv4 주소) 주소에 접속하여 제대로 배포가 되었는지 확인한다.
💸💸💸💸💸
# 깃 액션 secret 키 추가
# 워크플로우 설정
이제 EC2 서버에 배포까지 완료하였으니 관련 설정을 깃 액션에도 추가해줘야 한다.
EC2_HOST : EC2 퍼블릭 IP
EC2_USER : 보통은 ubuntu
EC2_KEY : hackathon-key.pem의 전체 내용 복붙 (메모장으로 열면 됨)
이후 .github/workflows/deploy.yml 파일을 생성하여 아래 코드를 추가하여 워크플로우 설정을 해준다.
name: Deploy to EC2
on:
push:
branches:
- main # 또는 master
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Connect & deploy to EC2
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.(EC2_HOST) }}
username: ${{ secrets.(EC2_USER) }}
key: ${{ secrets.(EC2_KEY) }}
script: |
sudo docker stop (컨테이너_이름) || true
sudo docker rm (컨테이너_이름) || true
sudo docker pull (도커허브_ID)/(프로젝트_이름)
sudo docker run -d -p 80:8080 --name (컨테이너_이름) (도커허브_ID)/(프로젝트_이름)
이제 또 다시 깃에 커밋과 푸시하는 과정을 통해 제대로 반영이 되는지 확인한다.
git add .
git commit -m "테스트"
git push origin main
tip) 만약 오류가 생긴다면 확인해봐야할 목록
- aws EC2 인스턴스 인바운드 규칙 확인하기
- sudo docker logs (도커 컨테이너 이름)로 로그 확인하기
- applicatiom.yml 파일 확인
- jar 파일 다시 빌드 (./gradlew clean build)
- 도커 이미지 다시 build 및 push
- EC2에서 도커 컨테이너 다시 실행해보기(아래 코드 참고)
- 지피티 화이팅^^
# 1. 이전 컨테이너 종료 및 제거 sudo docker stop hackathon-container || true sudo docker rm hackathon-container || true # 2. Docker Hub에서 최신 이미지 pull sudo docker pull (도커 아이디)/(도커 이미지 이름) # 3. 컨테이너 실행 (80 포트 → 8080 내부 포트 연결) sudo docker run -d -p 80:8080 --name (도커 컨테이너 이름) (도커 아이디)/(도커 이미지 이름)
이번에는 .github/workflows/cd.yml 파일을 생성하여 아래 코드를 추가하여 워크플로우 설정을 해준다.
name: CD with Gradle
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
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: Make application.yml
run: |
cd ./src/main/resources
echo "${{ secrets.APPLICATION_YAML }}" > ./application.yml
- name: Build with Gradle
run: |
chmod +x ./gradlew
./gradlew clean build
- name: Docker build & push to DockerHub
run: |
echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
docker build --no-cache -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest
- name: Deploy to EC2 with SSH
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_KEY }}
script: |
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest
sudo docker stop ${{ secrets.PROJECT_NAME }} || true
sudo docker rm ${{ secrets.PROJECT_NAME }} || true
sudo docker rmi ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest || true
sudo docker run -d -p 80:8080 --name ${{ secrets.PROJECT_NAME }} ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest
sudo docker image prune -f