개발일지(일간)

도커로 프로젝트 배포하기 - 서비스 분리

move2 2024. 9. 28. 00:44

 

1.DokerFile작성

우선, 도커로 서비스를 분리하기로 한 이유가 크롤링 스케줄러 서비스이기 때문에 도커 컨테이너 환경에서 chrome과 chromedriver가 실행이 되어야 한다.

그런데 도커 컨테이너는 배포된 이미지를 기반으로 독립적인 환경에서 실행되기 때문에, ec2에 설치되어있는 chrome과 chromedriver를 가져다 쓸수 없다.

따라서 도커를 빌드하기 위해 작성하는 Dockerfile에 chrome과 chromedriver 설치관련 스크립트를 작성해 주어야 한다.

FROM ubuntu:22.04

# 빌드 아규먼트 설정
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY

# 환경 변수 설정
ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}

ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Seoul

# 필수 의존성 설치
RUN apt-get update && apt-get install -y \
    libappindicator3-1 \
    libxss1 \
    libasound2 \
    unzip \
    wget \
    curl \
    apt-transport-https \
    ca-certificates \
    gnupg \
    awscli \
    openjdk-17-jdk && \
    ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
    dpkg-reconfigure --frontend noninteractive tzdata

# AWS 자격증명 설정
RUN mkdir -p ~/.aws && \
    echo "[default]" > ~/.aws/credentials && \
    echo "aws_access_key_id=${AWS_ACCESS_KEY_ID}" >> ~/.aws/credentials && \
    echo "aws_secret_access_key=${AWS_SECRET_ACCESS_KEY}" >> ~/.aws/credentials

# S3에서 설치 파일 다운로드
RUN aws s3 cp s3://s3cepcdbucket/google-chrome.deb /tmp/google-chrome.deb
RUN aws s3 cp s3://s3cepcdbucket/chromedriver-linux64.zip /tmp/chromedriver-linux64.zip

# Chrome 설치
RUN dpkg -i /tmp/google-chrome.deb || apt-get install -y -f

# chromedriver 설치
RUN unzip /tmp/chromedriver-linux64.zip -d /usr/local/bin/ && \
    chmod +x /usr/local/bin/chromedriver-linux64/chromedriver

# chromedriver의 경로를 PATH에 추가
ENV PATH="/usr/local/bin/chromedriver-linux64:${PATH}"

# 크롬과 크롬 드라이버 버전 확인 (디버깅용)
RUN google-chrome --version
RUN chromedriver --version

# 기본 작업 디렉토리 설정 (필요에 따라 설정 가능)
WORKDIR /scheduler

# 빌드 단계에서 생성된 JAR 파일 복사
COPY build/libs/*.jar scheduler.jar

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "scheduler.jar"]

 

위 코드에서 aws자격증명과 그를 위한 환경변수가 필요한 이유는, 고정된 구글 크롬과 크롬드라이버 버전을 이용하게 하기 위해서 s3에 업로드된 설치 파일을 다운받아 설치하게 했기 때문이다.

기존의 명령어를 사용하게 되면 무조건 최신버전으로 다운받게 되는데, 셀레니움이 크롬과 크롬드라이버 버전에 민감하기 때문에 오작동을 일으킬 염려가 있다.

때문에 s3에 업로드된 설치 파일을 다운받아 설치하게 함으로써 크롬과 크롬드라이버 버전을 직접 관리해주기로 했다.

그 외에 기본적으로 필요한 패키지들을 설치하고 jar파일을 실행하게 했다.

 

2.github Action ci/cd 워크플로우 수정

기존의 깃허브 액션 cd는 s3와 codeDeploy를 사용해 cd를 하도록 설정되어 있었으므로 이를 바꾸어 주어야한다.

그리고 ci를 통과하지 못해도 cd가 실행되도록 되어있는데 ci를 통과해야 cd를 수행하도록 수정해 주었다.

 

ci.yml

name: CI for Spring Boot

on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        java-version: [17]

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up JDK ${{ matrix.java-version }}
        uses: actions/setup-java@v2
        with:
          distribution: 'temurin'
          java-version: ${{ matrix.java-version }}

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew build --no-daemon

      - name: Trigger CD workflow
        if: ${{ success() }}  # 빌드가 성공한 경우에만 실행
        run: |
          curl -X POST \
            -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
            -H "Accept: application/vnd.github.v3+json" \
            https://api.github.com/repos/zeroempty2/CEPSchedule/actions/workflows/deploy.yml/dispatches \
            -d '{"ref": "main"}'

    env:
      DB_SOURCE_URL: ${{ secrets.DB_SOURCE_URL }}
      DB_SOURCE_USERNAME: ${{ secrets.DB_SOURCE_USERNAME }}
      DB_SOURCE_PASSWORD: ${{ secrets.DB_SOURCE_PASSWORD }}

 

cd.yml

name: Deploy to Amazon EC2 using Docker

on:
  workflow_run:
    workflows: ["CI for Spring Boot"]  # CI 워크플로우 이름
    types:
      - completed  # CI 워크플로우 완료 시 트리거
    branches:
      - main    # main 브랜치에서만 트리거
  # workflow_dispatch: 

env:
  AWS_REGION: ap-northeast-2
  DOCKER_IMAGE_NAME: 2zeroempty/cepschedule
  EC2_HOST: ec2-user@52.79.209.72 
  EC2_CONTAINER_NAME: scheduler   

permissions:
  contents: read
jobs:
  deploy:
    name: Build and Deploy Docker Image
    runs-on: ubuntu-latest
    environment: production

    steps:
      # (1) 기본 체크아웃
      - name: Checkout code
        uses: actions/checkout@v3

      # (2) JDK 17 세팅
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'

      # (3) Gradle 빌드
      - name: Build with Gradle
        uses: gradle/gradle-build-action@v3
        with:
          arguments: clean build -x test  # 테스트 포함 여부에 따라 옵션 변경 가능

      # (4) Docker 설정 및 로그인
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
            
      # (5) Docker 이미지 빌드 및 푸시
      - name: Build and Push Docker Image
        run: |
            docker system prune --all --force
            docker build --no-cache \
            --build-arg AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY }} \
            --build-arg AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \
            -t ${{ env.DOCKER_IMAGE_NAME }}:latest .
            docker push ${{ env.DOCKER_IMAGE_NAME }}:latest

      # (6) SSH 키 파일 생성
      - name: Create SSH key file
        run: |
          echo "${{ secrets.EC2_SSH_KEY }}" | sudo tee ~/ec2_key.pem > /dev/null
          sudo mkdir -p ~/.ssh
          sudo mv ~/ec2_key.pem ~/.ssh/ec2_key.pem
          sudo chown runner:runner ~/.ssh/ec2_key.pem
          sudo chmod 600 ~/.ssh/ec2_key.pem
      # (7) EC2에 SSH로 연결하여 Docker 컨테이너 배포
      - name: Deploy to EC2
        run: |
          ssh -vvv -o StrictHostKeyChecking=no -i ~/.ssh/ec2_key.pem -p 2200 ec2-user@52.79.209.72 << 'EOF'
            sudo systemctl start docker || echo "Docker is already running."
            docker pull ${{ env.DOCKER_IMAGE_NAME }}:latest
            docker stop ${{ env.EC2_CONTAINER_NAME }} || true
            docker rm ${{ env.EC2_CONTAINER_NAME }} || true
            docker run -d --name ${{ env.EC2_CONTAINER_NAME }} -p 8080:8080 \
              -e AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \
              -e AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \
              -e DB_SOURCE_URL=${{ secrets.DB_SOURCE_URL }} \
              -e DB_SOURCE_USERNAME=${{ secrets.DB_SOURCE_USERNAME }} \
              -e DB_SOURCE_PASSWORD=${{ secrets.DB_SOURCE_PASSWORD }} \
              ${{ env.DOCKER_IMAGE_NAME }}:latest
          EOF

도커허브에 파일을 업로드하기 위해서는 도커허브의 레파지토리가 생성되어있어야 한다.
DOCKER_IMAGE_NAME은 도커허브 닉네임/레파지토리이름 이다. 보통 도커허브의 레파지토리를 들어가면 알수 있다.
도커허브 레파지토리를 private로 설정했으면 추가적인 과정이 필요하겠지만 여기서는 public으로 설정해 주었다.

EC2_CONTAINER_NAME은 배포환경에서 실행되는 컨테이너 이름이다.

그 외 도커허브 id,password같은 민감정보들은 모두 github secret으로 관리해 주었다.

ci에서는 빌드가 되는지 검증하고, 빌드가 성공하면 cd를 트리거 하게 했다.

cd 과정을 보자면, 빌드후 제공된 정보로 docker hub 로그인을 하고, docker hub에 이미지 빌드 및 푸시를 하게 된다.

그 후 ec2에 접속하여 docker허브에 푸시된 이미지를 통해 컨테이너를 실행시킨다.

이때, application.properties 에 있는 db username, password등의 민감정보를 리눅스 환경변수로 처리하고 있었다면, 컨테이너 실행시 이 환경변수들을 제공해주어야한다.

도커 컨테이너는 배포된 이미지를 기반으로 독립된 환경에서 실행되기때문에 ec2리눅스에 환경변수가 있다고 해도 이를 읽지 못한다. 

따라서 컨테이너 실행시 이 환경변수들을 제공해 주어야 정상적으로 프로젝트를 실행시킬 수 있다.

이 과정들을 거치면 docker hub와 gihub action을 통한 ci/cd 파이프라인은 완성되었다.