
Git Actions를 통해 main에 변경사항(내가 지정한)이 일어나면
해당 프로젝트를 원하는 Task로 빌드하여 Docker에 Image를 만들고 Push한 다음
EC2에 접속해 이전 이미지를 제거하고 최근에 푸시된 Image를 pull받아 Run하는 과정을 자동화할 예정이다.
이 포스트에선 EC2 서버를 만들었다는 가정하에 진행합니다.
없다면 이 글을 참고하시면 될 거 같아요!! -> EC2 생성&접속하기
EC2에서 기억할 것은 4가지이다. 메모장이나 다른 어딘가에 기억해두자.
1. EC2 Host(Public IPv4 DNS - 탄력적 IP를 설정했다면 항상 고정)
2. EC2 key(pem 키 파일)
3. EC2 Username(기본 ubuntu)
4. Port(8080)
EC2에 접속해 다음과 같이 Docker를 설치한다.
sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
sudo systemctl status docker
sudo systemctl status docker 명령어 실행하면 다음과 같이 나오는데
이후에 'q'를 누르면 빠져나올 수 있다.
sudo docker run hello-world

여기까지 진행하여 'Hello from Docker' 라는 메세지가 나오면 성공이다.
해당 프로젝트에 Dockerfile을 추가해준다.
Dockerfile의 코드는 아래와 같이 작성한다.
FROM openjdk:17-jdk-slim
ADD /build/libs/*.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
만약 레포지토리 안에 여러 개의 프로젝트가 있다면
/build 앞에 그 프로젝트 이름을 적어주면 된다. >> 해당 경로 추적 시 .jar이 있으면 됨.
Docker Hub를 사용하기 위해 회원가입 후 로그인한다. -> Docker Hub 바로가기
로그인 한 후 [Create repository]를 클릭한다.

Repository Name을 작성하고 [Create]를 눌러 생성한다.

Repository를 생성했다면 메모장이나 다른 어딘가에 아래 3가지를 적어 기억해두자.
- 내 Docker Hub 계정 유저이름(위에 Namespace)
- 내 Docker Hub 계정 비밀번호
- 생성한 Repository의 이름(위에 Repository Name)
먼저 secret키를 만들어보자.
[Settings] - [Secrets and variables] - [Actions] 로 가서
[New repository secret] 버튼을 클릭한다.

이전에 기억해두었던 것들을 secret을 통해 환경변수로 설정할 것이다.
Name Secret EC2_HOST Public IPv4 DNS EC2_KEY pem 키 파일 EC2_USERNAME ubuntu (or 내가 만든 이름) PORT 8080 DOCKERHUB_USERNAME 내 Docker Hub 계정 유저이름(Namespace) DOCKERHUB_PASSWORD 내 Docker Hub 계정 비밀번호 PROJECT_NAME 생성한 Repository의 이름(Repository Name)
Name 과 Secret을 알맞게 입력한다.
EC2_KEY에 pem키를 입력할 때 조심해야 하는데
BEGIN RSA ~ END RSA도 함께 복붙해야한다.(즉, 전체 복붙하면된다)

위와 같이 7개를 안 틀리고 잘 입력하면 된다.
Actions 탭에 들어가 Java with Gradle의 [Configure]을 클릭한다.

클릭하면 아래와 같이 기본 gradle.yml이 나온다.

안에 내용을 지우고 아래과 같이 수정한다.(복붙 ㄱㄱ)
# ec2Test main wolkflow
name: ec2Test Project CI/CD
# 어떤 이벤트가 발생하면 workflow 실행할 지 명시
on:
# main 브런치에 push나 pull request 발생 시
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
# 위 이벤트 발생 시 실행될 작업들
jobs:
build:
# VM의 실행 환경 지정 -> ubuntu 최신 버전
runs-on: ubuntu-latest
# 실행될 jobs를 순서대로 명시
steps:
- name: Checkout
uses: actions/checkout@v4
# JDK 17 설치
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# Gradle Build를 위한 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
# Gradle Build(test 제외)
- name: Build with Gradle
run: ./gradlew clean bootJar --exclude-task test
# DockerHub 로그인
- name: DockerHub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
# Docker 이미지 빌드
- name: Docker Image Build
run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }} .
# DockerHub Push
- name: DockerHub Push
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}
# EC2 인스턴스 접속 및 애플리케이션 실행
- name: Application Run
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_KEY }}
script: |
echo "Stopping existing container..."
sudo docker kill ${{ secrets.PROJECT_NAME }}
sudo docker rm -f ${{ secrets.PROJECT_NAME }}
echo "Removing existing image..."
sudo docker rmi ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}
echo "Pulling new image..."
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}
echo "Running new container..."
sudo docker run -p ${{ secrets.PORT }}:${{ secrets.PORT }} \
--name ${{ secrets.PROJECT_NAME }} \
-d ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}
echo "Done"
주석에 써진 프로젝트 이름은 자신의 프로젝트에 알맞게 바꾸면 된다.
해당 gradle.yml에 각 명령어가 어떤 명령을 수행하는지 주석으로 설명을 간단히 써두었다.
Actions secret에 만들어둔 환경 변수들이 이 안에 쓰인다.
나는 프로젝트가 스프링부트 내장 톰캣을 사용하기에
중간에 # Gradle Build 를 보면 bootJar task를 사용하였다.
프로젝트에 맞는 빌드 task를 선택해 빌드하면 된다.
그리고 Dockerfile도 빌드 형식에 맞게 변경해야 한다. ( jar / war )
내/외장 톰캣과 JSP 사용 여부에 따른 빌드 형식
| 톰캣 종류 | JSP 사용 여부 | 빌드 형식 | 설명 |
|---|---|---|---|
| 내장 톰캣 | 미사용 | bootJar | 내장 톰캣 포함 .jar 파일, 독립 실행 가능 |
| 내장 톰캣 | 사용 | bootWar | JSP 포함 .war 파일, 내장 톰캣에서 실행 가능 |
| 외장 톰캣 | 미사용 | jar | 외장 톰캣에 배포 가능한 .jar 파일 |
| 외장 톰캣 | 사용 | war | JSP 포함 .war 파일, 외장 톰캣에서 실행 가능 |
이후 오른쪽 위에 [Commit changes]를 클릭해 레포지토리에 반영한다.
반영을 하면 Actions에 가보면 해당 workflow가 진행중임을 확인할 수 있다.
눌러보면 build하는 진행 상황도 볼 수 있다.
이제 테스트를 한 번 해보자.
기존 웹 페이지는 다음과 같다.

해당 index.html에 'CI/CD가 잘 구축되었나요??' 라는 문구를 추가하여 main에 push를 하게 되면

Actions에서 push를 감지해 내가 설정한 workflow (Docker에 프로젝트가 올라가고 EC2에서 Pull 받아 프로젝트 Run 하기)를 타게 된다.
workflow의 작업이 마무리 되면

이렇게 초록불이 들어오고 ec2에도 반영이 잘 되었음을 확인할 수 있다.

여기까지 Git Actions와 Docker, Ec2를 이용한 CI/CD에 대해서 정리해보았습니다.
제가 개발 유년기 단계라 부족한 점이 많습니다...
부족한 점 지적해주시면 공부해서 발전하도록 하겠습니다. 감사합니다.
References