구현

Docker + GitHub Actions로 CI/CD와 배포하기 (Minikube 테스트와 EC2 프리티어 한계)

키태 2025. 1. 7. 17:04
728x90

새로운 사람들과 프로젝트를 시작하며 가장 귀찮고 어렵지만 중요한 서버 배포를 맡았다. 잘못하면 과금이 나가는 AWS와 실질적인 코드를 치는 작업은 거의 없지만 스트레스는 많이 받는 서버 배포 작업이었지만, 저번 하반기부터 클라우드에 대한 관심이 생겨 배포 작업을 자원했다.

 

기존 CI/CD 파이프라인

[Github Action + AWS S3 + AWS CodeDeploy + AWS EC2]- Github Action을 통해 코드를 빌드하고, 이를 압축하여 AWS S3에 업로드, 그리고 원격 서버에서 S3로부터 파일을 가져다가 압축 해제 후 배포하도록 하는 방식

 

내가 이때까지 진행해오던 프로젝트에서는 CI/CD 파이프라인 및 서버 배포를 위의 방식을 채택해서 사용해왔다. 그 당시에는 해당 파이프라인을 선택한 이유가 다름이 아니라 구글링 했을 때 가장 많이 나오는 방식이었기 때문이었다. 다른 방식들에 비해 특별한 장단점 같은 부분들도 신경을 안썼고 단순히 따라하기 쉽게 많은 블로그에 정리되어있었기 때문이었다.

 

새로운 CI/CD 파이프라인

[Github Action + Docker + AWS EC2] - Github Actions를 통해 이미지를 빌드하고, 이를 Docker Hub Repository에 push, 그리고 AWS EC2에서 이미지를 pull 하여 실행시키는 방식

 

이번에는 새로운 기술인 Docker를 도입해서 CI/CD 파이프라인을 구성하고 서버를 배포해보고자 한다. 이 방식을 채택한 이유는 요즘 클라우드 생태계에서 떠오르고 있는 '컨테이너' 개념에 대해서 조금이나마 실습을 통해 이해를 하고 싶었고, 또 Docker는 컨테이너를 쉽게 확장할 수 있도록 지원하기 때문에 Kubernetes와 같은 오케스트레이션 도구와 통합하여 자동 확장 및 부하 분산을 구현할 수 있기 때문에 확장성 면에서 채택하게 되었다. 또 많은 기업에서 Docker와 Kubenetes를 많이 사용하고 있기 때문에 생태계의 흐름을 따라가고자 이러한 CI/CD 파이프라인을 구성하게 되었다.

 

1. Github Actions를 위한 환경 변수 설정

Github Actions를 위한 Secret Key 생성

Spring Boot 프로젝트를 실행시키기 위해서는 DB 정보도 필요하고, Docker Hub의 Repository에 push 하기 위해서는 Docker 계정의 비밀번호 등 중요한 정보들이 많이 필요하다. 이러한 정보들을 gitignore 없이 혹은 script에 바로 올리는 행위는 보안에 취약하므로 Github에서는 Github Actions에 필요한 환경변수를 세팅해주는 기능이 있다. 나는 위 사진에서 보이는 변수들을 모두 세팅했고 간단히 설명하자면 DATABASE(application.yml), DOCKER_PASSWORD(도커 비밀번호), DOCKER_REPO(도커허브 레포), DOCKER_USERNAME(도커 계정), HOST(EC2 주소), KEY(EC2 접속을 위한 pem key) 를 저장해줬다.

 

2. Github Actions 세팅

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: "adopt"
    
    - name: Make application.yml
      run: |
        mkdir -p ./src/main/resources
        cd ./src/main/resources
        touch ./application.yml
        echo "${{ secrets.DATABASE }}" > ./application.yml
      shell: bash
    
    - name: Build with Gradle
      run: |
        chmod +x ./gradlew
        ./gradlew clean build -x test
    
    - name: Docker build & push to docker repo
      run: |
        docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
        docker build -t ${{ secrets.DOCKER_REPO }} .
        docker push ${{ secrets.DOCKER_REPO }}

    - name: Deploy to server
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.HOST }}
        username: ubuntu
        key: ${{ secrets.KEY }}
        script: |
          sudo docker pull ${{ secrets.DOCKER_REPO }}
          sudo docker stop $(sudo docker ps -q) 2>/dev/null || true
          sudo docker run --name github-actions-demo --rm -d -p 8080:8080 ${{ secrets.DOCKER_REPO }}
          sudo docker system prune -f

 

 

그 다음, Github Actions에 필요한 gradle.yml 스크립트를 구현해주면 된다.

내가 의도한 스크립트의 과정을 간단히 설명하자면,

[main 브랜치 푸시 -> JDK 17 빌드 -> gitignore 처리되어있는 application.yml 생성 -> Docker 이미지 빌드 및 레포 푸시 -> EC2 서버에서 최신 이미지 pull]

해당 과정을 step들로 작성해줬다. 참고로 Action 과정 중 하나인 Docker Hub로 이미지를 푸시하기 위해 미리 https://hub.docker.com/에 접속해 Repository를 생성해줘야한다. (EC2 인스턴스 생성에 관련한 설명은 생략!)

 

 

 

모든 세팅이 완료되었으면 main 브랜치에 push를 진행하면 이렇게 모든 Github Actions들이 정상 작동하는 것을 볼 수 있다. 이때 나는 AWS EC2 인스턴스에 탄력적 IP를 연결해줬고, 보안그룹에서 8080 포트로 들어오는 모든 인바운드 규칙을 열어줬다.

 

이렇게 탄력적 IP 주소로 들어왔을 때 API가 잘 작동하는 것을 확인할 수 있었다. 많은 설명이 생략되었으나 AWS EC2 인스턴스(Ubuntu 22.04)에 처음 접속한 후 당연히 Docker 명령어를 실행하기위해 Docker를 설치하는 과정도 있어야한다.


여기까지는 'Docker + GitHub Actions로 CI/CD와 배포하기'의 구현기이고 이제부터는 내가 개인적으로 실습해보고 싶었던 쿠버네티스와 관련된 부분을 간단하게 적어볼까한다. 사실 쿠버네티스는 MSA(Microservice Architecture) 환경에서 적합한 배포 플랫폼이다. 왜냐하면 MSA에서는 애플리케이션이 여러 개의 독립적인 서비스로 나누어져 실행되기 때문에 쿠버네티스에서 컨테이너화된 애플리케이션을 효율적으로 관리할 수 있도록 지원하기 때문이다. 그렇기에 간단한 사이드 프로젝트에서 MSA 구조도 아닌데 쿠버네티스를 도입하는건 사실 '오버테크'라고 생각한다. 그럼에도 불구하고 평소 쿠버네티스가 궁금했기 때문에 간단하게나마 실습한 내용을 공유해보고자 한다.

 

이 쿠버네티스를 AWS EC2 인스턴스 환경에 설치하고 싶었지만 AWS 프리티어 계정으로 만들 수 있는 인스턴스에서는 설치가 불가능하기에 로컬에서만 실습이 가능했다. 이럴 때 Minikube라는 도구를 사용하는데 이는 로컬 환경에서 Kubernetes 클러스터를 쉽게 실행할 수 있도록 도와주는 도구이다. 시작해보자.

 

우선 minikube를 설치하고 다음과 같이 minikube를 start 해주자.

 

그 다음 ./gradlew clean build를 통해서 빌드 파일을 만들어주자.

 

빌드 파일은 ./build/libs 밑에 jar 파일이 생성되는 걸 확인하면 된다.

 

FROM openjdk:17-jdk
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

그 다음 Dockerfile을 통해 빌드된 jar을 통해 도커 이미지를 만들어주는 Dockerfile 파일을 만들어주도록 하자.

 

spring-server라는 이름으로 이미지를 빌드해주는 docker build -t spring-server . 을 실행시키고

docker image ls로 이미지를 확인할 수 있다.

 

이 이미지를 토대로 파드에 올릴건데 그럴려면 쿠버네티스에서 파드 리소스를 정의하고 만드는 것을 도와주는 ManiFest 형식의 파일을 만들어줘야한다. 우리는 spring-pod.yaml을 만들어보도록 하겠다.

 

이런식으로 Pod를 만드는 Manifest 파일을 만들어준다. 밑에 조금 짤렸는데 나는 편의를 위해 모든 DB 정보를 밑에 env로 넣어줬다. 원래라면 configmap으로 따로 빼야하는 작업이고 이것들은 노출되면 안되기에 가리도록 하겠다.

 

 

이제 kubectl apply -f spring-pod.yaml을 통해서 만들어준 파일을 적용시키고 pod를 확인해보면 우리가 만든 spring-pod가 Running 상태인 것을 확인할 수 있다.

 

 

파드가 정상적으로 떠있으니 우리는 pod 안에 들어가서 curl로 요청을 날리면 정상적으로 Hello Kubernets가 뜨는 것을 확인할 수 있다.

 

이렇게 간단하게 minikube + kubenetece를 활용해 로컬에서 파드 환경을 구성하고 API를 호출하는 테스트를 해봤다. AWS EC2 프리티어의 한계 때문에 실제로는 배포를 못해봤지만 Docker + Kubenetece의 컨테이너 환경에 대해서 조금 더 공부하고 싶다고 느꼈고 추후에 금전적인 여유가 있을 때 배포를 해봐야겠다.

728x90