Skip to content

[BE] CI CD

SeongHyeon edited this page Nov 8, 2024 · 2 revisions

참고 블로그 1

참고 블로그 2

CI/CD

image

Continuous Integration (지속적인 통합) / Continuous Deployment (지속적인 배포)

Docker를 이용한 모노레포 배포

Dockerfile 작성

  • clientserver 각각에 Dockerfile을 작성하여 애플리케이션이 컨테이너에서 실행될 수 있도록 설정합니다.

client/Dockerfile (React)

FROM node:20 AS build
WORKDIR /app
COPY packages/client/package.json .
RUN yarn install
COPY packages/client .
RUN yarn build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Nginx??

고성능의 웹 서버이자 리버스 프록시 서버, 로드 밸런서, HTTP 캐시로 널리 사용되는 소프트웨어입니다. 웹 서버로 주로 사용되며, 많은 웹사이트에서 HTTP 요청을 처리하기 위해 사용됩니다. 다음은 Nginx의 주요 기능과 특징입니다.

1. 웹 서버 (Web Server)

  • Nginx는 웹 서버로서 정적 파일(HTML, CSS, JavaScript, 이미지 등)을 클라이언트에게 서빙하는 데 사용됩니다.
  • Apache와 같은 다른 웹 서버와 비교할 때, Nginx는 비동기 방식으로 요청을 처리하므로 더 많은 트래픽을 처리할 수 있습니다.

2. 리버스 프록시 (Reverse Proxy)

  • Nginx는 리버스 프록시 서버로도 많이 사용됩니다. 클라이언트의 요청을 받아서 내부 서버에 전달하고, 그 응답을 다시 클라이언트에게 반환합니다.
  • 로드 밸런싱: 여러 서버 간에 트래픽을 분배하는 기능을 통해 서버의 부하를 분산할 수 있습니다.

3. 로드 밸런서 (Load Balancer)

  • Nginx는 여러 서버에 걸쳐 트래픽을 분배하는 로드 밸런서 역할을 할 수 있습니다. 이를 통해 트래픽을 효율적으로 처리하고, 하나의 서버에 트래픽이 집중되는 문제를 방지할 수 있습니다.

4. HTTP 캐시 (HTTP Cache)

  • Nginx는 캐싱 기능을 제공하여, 자주 요청되는 콘텐츠를 메모리에 저장하고, 동일한 요청에 대해서는 캐시된 데이터를 제공함으로써 서버의 부하를 줄이고 응답 속도를 높입니다.

5. SSL/TLS 종료 (SSL/TLS Termination)

  • Nginx는 SSL 인증서를 관리하고 HTTPS 요청을 처리하는 데에도 사용됩니다. 이를 통해 SSL 종료를 할 수 있으며, 내부 서버와의 연결은 HTTP로 할 수 있습니다.

6. 고성능

  • Nginx는 비동기 이벤트 기반 아키텍처를 사용하여 높은 성능을 발휘합니다. 즉, 여러 클라이언트의 요청을 동시에 처리할 수 있으며, 리소스를 효율적으로 사용합니다. 이로 인해 많은 트래픽을 처리해야 하는 대규모 서비스에서도 사용됩니다.

주요 특징

  • 빠른 성능: 비동기 방식으로 높은 트래픽을 처리할 수 있음.
  • 가벼운 리소스 사용: 낮은 메모리와 CPU 사용으로 리소스를 효율적으로 처리.
  • 설정 파일의 간단한 구성: 설정이 간단하고 직관적인 방식으로 작성됨.
  • 높은 확장성: 로드 밸런싱과 캐싱, 리버스 프록시 기능을 통해 웹 애플리케이션의 성능을 향상시킬 수 있음.

server/Dockerfile (NestJS)

FROM node:20
WORKDIR /app

# 패키지 설치
COPY packages/server/package.json .
RUN yarn install

# 소스 파일 복사
COPY packages/server .

# 포트 노출
EXPOSE 3000

# 컨테이너 시작 시 애플리케이션 실행
CMD ["yarn", "start:prod"]
  • start:prod : NODE_ENV를 production으로 설정 > 환경변수가 prod용으로 바뀜

docker-compose.yml 작성

  • docker-compose.yml을 사용하여 프론트엔드, 백엔드, MySQL 컨테이너를 함께 실행합니다.
  • 네트워크를 공유하고 각 컨테이너가 필요한 환경 변수를 설정합니다.
services:
  server:
    build:
      # 현재 디렉토리를 빌드 컨텍스트로 사용한다.
      context: .
      # 사용할 도커 파일 이름(빌드 전 이름)
      dockerfile: dockerfile-server
    # 이미지를 빌드하고 컨테이너 생성 시 사용할 이름
    image: seunggwan/corinee-server
    # 3000포트(앞)를 외부에 열고, 해당 포트를 컨테이너 내의 3000포트(뒤)에 매핑
    # localhost:3000으로 요청을 보내면 앞의 포트에서 받아서 뒤의 포트에 매핑해준다. 
    ports:
      - "3000:3000"
    # 같은 service내의 컨테이너들이 상호 연결되는 네트워크를 설정하는 부분
    networks:
      - app-network

  client:
    build:
      context: .
      dockerfile: dockerfile-client
    image: seunggwan/corinee-client
    ports:
      - "80:80"
    # server가 먼저 실행 된 후 client가 실행된다.
    # client가 server에 의존하는 관계 설정
    depends_on:
      - server
    networks:
      - app-network

networks:
  app-network:

Docker Compose로 애플리케이션 실행

  • 프로젝트 루트에서 다음 명령어를 실행하여 모든 컨테이너를 시작합니다.
docker-compose up --build

테스트 및 배포

  • http://localhost에서 프론트엔드(React)를 확인할 수 있고, API 요청은 백엔드(NestJS)로 전달됩니다.
  • 데이터베이스는 MySQL 컨테이너에서 실행되며, server와 연결된 상태로 API 작업이 가능합니다.

가상 환경에 설치

가상 서버에 Docker 및 Docker Compose 설치

# Docker 설치
sudo apt update
sudo apt install -y docker.io

# Docker Compose 설치
sudo curl -L "https://github.com/docker/compose/releases/download/2.0.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

프로젝트 파일 업로드

로컬에서 작성한 docker-compose.yml, Dockerfile, 소스 코드 등을 가상 서버로 전송합니다. scp 명령어 또는 Git을 사용할 수 있습니다.

# 로컬에서 가상 서버로 프로젝트 파일 전송
scp -r /path/to/your/project user@your-server-ip:/home/user/project

$ scp -r * [email protected]:project

가상 서버에서 Docker Compose 실행

서버에서 docker-compose.yml 파일이 있는 디렉터리로 이동한 후, Docker Compose를 사용하여 컨테이너를 빌드하고 실행합니다.

# 프로젝트 디렉터리로 이동
cd /project

# Docker Compose로 컨테이너 빌드 및 실행
sudo docker-compose up --build -d

서버와 애플리케이션 접근 테스트

가상 서버의 IP 주소와 설정한 포트를 통해 프론트엔드와 백엔드 애플리케이션이 실행되는지 확인합니다.

  • 예: http://your-server-ip에서 프론트엔드 확인
  • 예: http://your-server-ip:3000에서 백엔드 API 확인 (백엔드를 다른 포트로 설정한 경우)

가상환경에서 docker로 배포성공

명령어

docker-compose up -d

docker-compose.yaml에 명시된 모든 서비스 컨테이너를 생성하고 실행시켜주는 명령어

docker-compose down

모든 서비스 컨테이너를 한 번에 정지시키고 삭제한다.

docker-compose up —build -d

이미지를 다시 빌드해서 컨테이너를 띄워야 하는데 docker-compose는 멍청해서 이미지 이름만 같아도 새롭게 빌드하지 않는다. 기존 이미지와 컨테이너를 stale 시키고 다시 빌드하기 위해 —build 옵션을 사용한다.

flowchart TD
    subgraph DockerCompose
        FE[프론트엔드 컨테이너]
        BE[백엔드 컨테이너]
    end

    User[사용자]
    FE --- BE
    BE --> DB[(데이터베이스)]

    User -->|HTTP 요청| FE
    FE -->|API 요청| BE

Loading

Github action 워크플로

순서

Dockerfile을 build로 이미지 생성 → Dockerhub에 이미지 push → scp로 docker-compose 이동 → Dockerhub에서 이미지 다운로드 → 컨테이너 생성 후 실행

name: Build and Deploy
# 워크플로 트리커
on:
  push:
    branches:
      - dev-be
      - dev-fe
  pull_request:
    branches:
      - main  
# job 설정
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      # 저장소의 코드를 가져오는 과정
      - name: Checkout repository
        uses: actions/checkout@v4  
      # Docker hub 로그인
      - name: Docker Hub login
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}
      # Docker 이미지 생성 및 hub에 업로드
      - name: Build and Push Docker images
        run: |
          docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/corinee-server -f ./dockerfile-server .
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/corinee-server
          docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/corinee-client -f ./dockerfile-client .
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/corinee-client
      # scp로 compose.yml 이동
      - name: Send files & deploy script
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          password: ${{ secrets.SSH_PASSWORD }}
          port: ${{ secrets.SSH_PORT }}
          source: ${{ secrets.DOCKER_IMAGE }}
          target: /corinee
          overwrite: true
      # hub에서 이미지 다운로드 후 배포
      - name: Docker run
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          password: ${{ secrets.SSH_PASSWORD }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            docker pull ${{ secrets.DOCKERHUB_USERNAME }}/corinee-server
            docker pull ${{ secrets.DOCKERHUB_USERNAME }}/corinee-client
            cd /corinee
            # 기존에 실행 중인 컨테이너들을 중지하고 정리
            # 관련된 네트워크, 볼륨, 이미지등을 삭제
            docker-compose down
            # 새로운 상태로 서비스를 실행
            # -d는 detached모드를 의미하며 백그라운드에서 실행하겠다는 뜻
            docker-compose up -d
            

      - name: Generate Error Report
        if: failure()  
        run: |
          echo "Deployment Report" > report.txt
          echo "===================" >> report.txt
          echo "Commit SHA: ${{ github.sha }}" >> report.txt
          echo "Branch: ${{ github.ref }}" >> report.txt
          echo "Deployment Status: Failed" >> report.txt
          echo "Error Details: ${{ job.status }}" >> report.txt
          echo "===================" >> report.txt
          cat report.txt

      - name: Upload Error Report
        if: failure()  
        uses: actions/upload-artifact@v3
        with:
          name: deployment-error-report
          path: report.txt

워크플로우 구조

1. 이름 및 트리거 설정

yaml
코드 복사
name: Build and Deploy
on:
  push:
    branches:
      - dev-be
      - dev-fe
  pull_request:
    branches:
      - main
  • name: Build and Deploy: 이 워크플로우의 이름입니다.
  • on: 이 워크플로우가 실행되는 조건을 정의합니다.
    • push: dev-be 또는 dev-fe 브랜치에 푸시될 때 실행됩니다.
    • pull_request: main 브랜치로의 풀 리퀘스트가 생성될 때 실행됩니다.

2. 작업 설정

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
  • jobs: 워크플로우에서 수행할 작업들을 정의합니다.
  • build_and_deploy: 작업의 이름이며, ubuntu-latest 환경에서 실행됩니다.

3. 단계 설명

각 단계는 특정 작업을 수행합니다.

  1. 코드 체크아웃

      name: Checkout repository
      uses: actions/checkout@v4
    • 저장소의 코드를 가져오는 단계입니다. 현재 브랜치의 코드를 체크아웃합니다.
  2. Docker Hub 로그인

    - name: Docker Hub login
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_PASSWORD }}
    • Docker Hub에 로그인하여 이미지를 푸시할 수 있도록 합니다. 사용자 이름과 비밀번호는 GitHub Secrets에 저장되어 있습니다.
  3. Docker 이미지 빌드 및 푸시

    - name: Build and Push Docker images
      run: |
        docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/corinee-server -f ./dockerfile-server .
        docker push ${{ secrets.DOCKERHUB_USERNAME }}/corinee-server
        docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/corinee-client -f ./dockerfile-client .
        docker push ${{ secrets.DOCKERHUB_USERNAME }}/corinee-client
    • 서버 및 클라이언트 이미지를 빌드하고 Docker Hub에 푸시합니다. 각 이미지에 대해 Dockerfile을 지정하여 빌드합니다.
  4. 파일 전송 및 배포 스크립트 실행

    - name: Send files & deploy script
      uses: appleboy/scp-action@master
      with:
        host: ${{ secrets.SSH_HOST }}
        username: ${{ secrets.SSH_USERNAME }}
        password: ${{ secrets.SSH_PASSWORD }}
        port: ${{ secrets.SSH_PORT }}
        source: ${{ secrets.DOCKER_IMAGE }}
        target: /corinee
        overwrite: true
    • SCP를 사용하여 필요한 파일을 원격 서버로 전송합니다. 이때 SSH 정보를 GitHub Secrets에서 가져옵니다.
  5. Docker 컨테이너 실행

    - name: Docker run
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.SSH_HOST }}
        username: ${{ secrets.SSH_USERNAME }}
        password: ${{ secrets.SSH_PASSWORD }}
        port: ${{ secrets.SSH_PORT }}
        script: |
          docker pull ${{ secrets.DOCKERHUB_USERNAME }}/corinee-server
          docker pull ${{ secrets.DOCKERHUB_USERNAME }}/corinee-client
          cd /corinee
          docker-compose down
          docker-compose up -d
    • 원격 서버에 SSH로 접속하여 Docker 이미지를 풀(pull)하고, Docker Compose를 사용하여 기존 컨테이너를 중지하고 새로 시작합니다.
  6. 오류 보고서 생성

    - name: Generate Error Report
      if: failure()
      run: |
        echo "Deployment Report" > report.txt
        echo "===================" >> report.txt
        echo "Commit SHA: ${{ github.sha }}" >> report.txt
        echo "Branch: ${{ github.ref }}" >> report.txt
        echo "Deployment Status: Failed" >> report.txt
        echo "Error Details: ${{ job.status }}" >> report.txt
        echo "===================" >> report.txt
        cat report.txt
    • 배포가 실패한 경우 오류 보고서를 생성합니다. 커밋 SHA, 브랜치, 배포 상태 및 오류 세부 정보를 포함합니다.
  7. 오류 보고서 업로드

    - name: Upload Error Report
      if: failure()
      uses: actions/upload-artifact@v3
      with:
        name: deployment-error-report
        path: report.txt
    • 생성된 오류 보고서를 아티팩트로 업로드합니다. 나중에 CI/CD 파이프라인에서 분석할 수 있도록 합니다.
Clone this wiki locally