Skip to content

[BE] 생애 첫 CI CD 도전기

박경미 edited this page Dec 14, 2023 · 8 revisions

목차

  1. 들어가며...
  2. 인프라 구축
  3. Docker 설치
  4. GitHub Actions 작성
  5. 마치며...

1. 들어가며...

Micro 서버 사양

vCPU 메모리 디스크
1개 1GB 50GB

원래 NPC에서 1년 동안 무료로 사용할 수 있는 Micro (Classic) 서버로 배포를 진행했었다. GitHub Actions를 완벽하게 작성했는데, 계속 docker run에서 타임아웃 문제가 발생했다. 무수히 많은 GitHub Actions 휴먼 에러를 수정해왔고 더 이상 틀린 부분이 없다고 확신할 수 있었다. 따라서 이는 서버 성능 문제라고 판단, 아래에 작성할 standard 서버로 변경하였고, 배포에 성공했다.




2. 인프라 구축

  • VPC & Public Subnet

    • 개발 용이성을 위해 Reverse Proxy, WAS, DB를 모두 public subnet에 올렸다.
    • 특히 API 개발 시 CLI보다 가독성이 좋고 편리한 workbench로 DB에 접속하여 DB 내용을 그때그때 확인하고 수정해야 했다.
    • 추후 DB를 private subnet으로 옮겨 WAS에서만 DB에 연결되도록 할 예정이다.
  • s2-g2-s50 Server (VPC)

    vCPU 메모리 디스크 서버 이미지
    2EA 8GB 50GB Ubuntu 20.04
    • 월 88,000원! 메모리 8GB면 1.43GB 이미지를 충분히 실행시킬 수 있겠지~?



3. Docker 설치

  • Ubuntu 서버에서 docker를 설치한다.

    apt-get update
    apt install docker.io
    snap install docker
    docker --version # 설치 확인용



4. GitHub Actions 작성

원하는 기능

  • be-developmain 브랜치에 push 될 때마다 배포하고 싶다.
  • GitHub Repository를 도커 이미지로 생성하고 싶다.
  • Ubuntu 서버에서 위에서 생성한 도커 이미지를 실행하고 싶다.

be-developmain 브랜치에 push 될때마다 배포하고 싶다.

name: CD

on:
  push:
    branches: [be-develop, main]
  • name: GitHub Actions 워크플로우 이름 설정
  • on: 트리거 이벤트 지정
    • push 이벤트 발생 시,
      • be-develop 또는 main 브랜치로의 push인지 감지하여 워크플로우 실행

기본 설정

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout...
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18.x
  • jobs: deploy라는 job이 정의됨
  • runs-on: deploy는 ubuntu-latest 환경에서 실행
  • steps: job이 수행할 각 단계
  • uses: GitHub Marketplace에 호스팅된 액션 참조 & 사용
  • with: 액션 사용 시 필요한 추가 매개변수 설정

GitHub Repository를 도커 이미지로 생성하고 싶다.

      - name: Install package
        run: |
          cd BE
          npm ci

      - name: Docker Login
        run: docker login -u ${{ secrets.DOCKER_ID }} -p ${{ secrets.DOCKER_PASSWORD }}

      - name: Build Docker
        run: |
          cd BE
          docker build -f Dockerfile -t traveline/traveline-docker .

      - name: Push Docker
        run: |
          cd BE
          docker push traveline/traveline-docker:latest
  • name: Install package
    • run: Install package에서 실행할 스크립트 정의
    • 백엔드 개발 폴더인 BE로 이동 및 필요한 모듈 설치
  • name: Docker Login
    • Docker 아이디와 비밀번호를 통해 도커에 로그인
    • 도커 아이디와 비밀번호는 GitHub Secrets에 등록해두었음
  • name: Build Docker
    • 백엔드 서버를 도커로 띄울 것이므로, BE 폴더로 이동하여 빌드 → traveline/traveline-docker 이미지 생성
  • name: Push Docker
    • BE 폴더에서 컨테이너를 생성하였으므로, 다시 BE 폴더로 이동하여 도커 이미지를 Docker Hub에 푸시

Ubuntu 서버에서 위에서 생성한 도커 이미지를 실행하고 싶다.

      - name: SSH Login
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USERNAME }}
          password: ${{ secrets.SERVER_PASSWORD }}
          port: ${{ secrets.SERVER_PORT }}
          script: |
            docker login --username ${{ secrets.DOCKER_ID }} --password ${{ secrets.DOCKER_PASSWORD }}
            docker pull traveline/traveline-docker
            docker stop traveline-container || true
            docker rm traveline-container || true
            docker run -e DB_HOST=${{ secrets.DB_HOST }} -d -p ${{secrets.EXTERNAL_PORT}}:${{secrets.INTERNAL_PORT}} --name traveline-container traveline/traveline-docker
  • name: SSH Login
    • GitHub Actions에서 appleboy/ssh-action@master 액션을 사용하여 원격 서버에 SSH 로그인
    • SSH 접속을 위한 host, username, password, port 변수를 with로 제공
    • docker에 로그인하여 Docker Hub에서 서버 이미지를 pull 해오고, 기존에 실행되고 있던 컨테이너를 멈추고 삭제한 후, 새 서버 이미지를 실행한다.
      • || ture은 해당 명령어가 실패하더라도 무시하고 계속 진행하라는 의미

최종 yml 코드

name: CD

on:
  push:
    branches: [be-develop, main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout...
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18.x

      - name: Install package
        run: |
          cd BE
          npm ci

      - name: Docker Login
        run: docker login -u ${{ secrets.DOCKER_ID }} -p ${{ secrets.DOCKER_PASSWORD }}

      - name: Build Docker
        run: |
          cd BE
          docker build -f Dockerfile -t traveline/traveline-docker .

      - name: Push Docker
        run: |
          cd BE
          docker push traveline/traveline-docker:latest

      - name: SSH Login
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USERNAME }}
          password: ${{ secrets.SERVER_PASSWORD }}
          port: ${{ secrets.SERVER_PORT }}
          script: |
            docker login --username ${{ secrets.DOCKER_ID }} --password ${{ secrets.DOCKER_PASSWORD }}
            docker pull traveline/traveline-docker
            docker stop traveline-container || true
            docker rm traveline-container || true
            docker run -e DB_HOST=${{ secrets.DB_HOST }} -e DB_PORT=${{ secrets.DB_PORT }} -e DB_USER=${{ secrets.DB_USER }} -e DB_PASSWORD=${{ secrets.DB_PASSWORD }} -e DB_DATABASE=${{ secrets.DB_DATABASE }} -e NCP_ACCESS_KEY_ID=${{ secrets.NCP_ACCESS_KEY_ID }} -e NCP_SECRET_ACCESS_KEY=${{ secrets.NCP_SECRET_ACCESS_KEY }} -e NCP_REGION=${{ secrets.NCP_REGION }} -e JWT_SECRET_ACCESS=${{ secrets.JWT_SECRET_ACCESS }} -e JWT_SECRET_REFRESH=${{ secrets.JWT_SECRET_REFRESH }} -e CLIENT_ID=${{ secrets.CLIENT_ID }} -e TEAM_ID=${{ secrets.TEAM_ID }} -e KEY_ID=${{ secrets.KEY_ID }} -e AUTH_KEY_LINE1=${{ secrets.AUTH_KEY_LINE1 }} -e AUTH_KEY_LINE2=${{ secrets.AUTH_KEY_LINE2 }} -e AUTH_KEY_LINE3=${{ secrets.AUTH_KEY_LINE3 }} -e AUTH_KEY_LINE4=${{ secrets.AUTH_KEY_LINE4 }} -e KAKAO_REST_API_KEY=${{ secrets.KAKAO_REST_API_KEY }} -e X_NCP_APIGW_API_KEY_ID=${{ secrets.X_NCP_APIGW_API_KEY_ID }} -e X_NCP_APIGW_API_KEY=${{ secrets.X_NCP_APIGW_API_KEY }} -e AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} -e AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} -e AWS_REGION=${{ secrets.AWS_REGION }} -e GREENEYE_SECRET_KEY=${{ secrets.GREENEYE_SECRET_KEY }} -e GREENEYE_DOMAIN_ID=${{ secrets.GREENEYE_DOMAIN_ID }} -e GREENEYE_SIGNATURE=${{ secrets.GREENEYE_SIGNATURE }} -d -p ${{secrets.EXTERNAL_PORT}}:${{secrets.INTERNAL_PORT}} --name traveline-container traveline/traveline-docker



5. 마치며...

백엔드끼리 코어 타임 이후에, 10시까지 나머지 공부까지 해가며 성공한 배포라서 무척 값지다.

암튼 yml 파일로 배포에 성공했기 때문에, 앞으로도 계속 참고하면서 잘 해나갈 수 있을 것 같다~!

Clone this wiki locally