CI&CD 환경 구축

2 분 소요

CI, CD 대략적인 과정

ci  
push / pull-request -> github action job 실행 -> 테스트/리포팅 -> docker build & push

cd  
push -> github action job 실행 -> aws ssh 접속 & 이동 후 미리 작성된 shell script 실행

github action 작성

name: CI

on:
  push:
    branches:
      - master

jobs:
  ci:
    name: E2E Tests and Unit Tests
    runs-on: ubuntu-latest

    steps:
      - name: Check out Git repository
        uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 12

      - name: Install & Tests
        run: |
          npm install
          npm test
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v1
        with:
          name: codecov-connected-server
          fail_ci_if_error: true
      - name: Docker build
        run: |
          docker build -t syh622/connected-42 .
      - name: Docker push
        run: |
          docker login -u $secrets.DOCKER_USER -p $secrets.DOCKER_PASSWORD
          docker tag syh622/connected-42 syh622/connected-42:latest
          docker tag syh622/connected-42 syh622/connected-42:$github.sha
          docker push syh622/connected-42:latest
          docker push syh622/connected-42:$github.sha
 cd:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    needs: ci

    steps:
      - name: SSH and Build Docker image and Push
        uses: appleboy/ssh-action@master
        with:
          host: $secrets.HOST
          username: $secrets.USERNAME
          key: $secrets.KEY
          port: $secrets.PORT
          script: |
            sh deploy.sh

latest로 최신 상태를 업데이트하고, 버전관리를 위해 github.sha 환경변수를 사용했다. 해당 repository에 버전이 2개이상 있을 경우 docker pull 이미지의 명령어를 사용할 경우 자동으로 tag에 latest가 붙는다. 버전이 1개라면 tag를 명시해야한다.

aws 환경 구성

AWS EC2 ubuntu를 사용했다. root계정을 사용하는 대신 ssh접속이 가능한 계정을 추가로 생성하고 접근 권한을 제한했다. 해당 과정은 이곳에 잘 나와있다.

docker설치 과정은 이곳을 참조했다. 추가로 docker-compose, nginx 패키지를 설치한다.

shell script 작성

blue/green 배포 방식으로 이미 누군가 잘 작성해놓은 것을 수정하여 사용했다.
docker build완료 후 docker hub에 이미지가 최신화가 되었을 때 실행되는 deploy.sh파일

#!/bin/bash

DOCKER_APP_NAME=server
DOCKER_DB_NAME=postgres

EXIST_BLUE=$(docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)
EXIST_DB=$(docker-compose -p ${DOCKER_DB_NAME} -f docker-compose.db.yml ps | grep Up)

if [ -z "$EXIST_DB" ]; then
    echo "DB setting"
    docker-compose -p ${DOCKER_DB_NAME} -f docker-compose.db.yml up -d
fi

if [ -z "$EXIST_BLUE" ]; then
    echo "blue up"
    docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d

    sleep 10

    docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml down
    docker image prune -f -a
else
    echo "green up"
    docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d

    sleep 10

    docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml down
    docker image prune -f -a
fi

blue

version: '3.7'
services:
  server:
    image: "syh622/connected-42"
    restart: always
    volumes:
      - appdata:/app
    ports:
      - '8081:8080'
    command:
      - bash
      - -c
      - |
        /wait-for-it.sh postgres:5432 -t 30
        yarn start
    networks:
      - app
networks:
  app:
    driver: bridge
volumes:
  appdata:
    driver: local

green

version: '3.7'
services:
  server:
    image: "syh622/connected-42"
    restart: always
    volumes:
      - appdata:/app
    ports:
      - '8080:8080'
    command:
      - bash
      - -c
      - |
        /wait-for-it.sh postgres:5432 -t 30
        yarn start
    networks:
      - app
networks:
  app:
    driver: bridge
volumes:
  appdata:
    driver: local

서로 다른 포트를 가지도록 8080, 8081를 각각 사용했고, db 컨테이너가 정상 동작이 완료된 상태에서 서버를 시작해야 하기때문에 wait-for-it.sh를 사용했다.

db

version: '3.7'

services:
  postgres:
    image: postgres:10
    container_name: postgres
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=${DB_ROOT_PASSWORD}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_DB=${DB_NAME}
    ports:
      - '5432:5432'
    networks:
      - app
networks:
  app:
    driver: bridge
volumes:
  pgdata:
    driver: local

nginx 설정

deploy.sh를 실행하면 *-blue, *-green이미지가 번갈아가며 실행되는것을 확인할 수 있다. 이때 실행되는 서버는 8080, 8081포트를 가지고있다.
클라이언트가 브라우저에서 동일한 포트로 접근할 수 있도록 중계서버를 이용한다. nginx의 reverse proxy를 구성하면 가능하다. 해당 설명은 이곳에 자세히 나와있다.

nginx 설정 파일 /etc/nginx/sites-available/default

upstream backend {
        server localhost:8081 weight=5 max_fails=3 fail_timeout=10s;
        server localhost:8080 weight=10 max_fails=3 fail_timeout=10s;
}

server {
        listen 80;
        server_name localhost;
        location / {
                proxy_pass http://backend;
        }
}

참조

  • https://smjeon.dev/web/deploy-with-jenkins-4/#nginxconf
  • https://github.com/JHyeok/jaebook-server

태그:

카테고리:

업데이트: