HJW's IT Blog

[CodeIt] CI/CD with Redis & Lightsail 본문

카테고리 없음

[CodeIt] CI/CD with Redis & Lightsail

kiki1875 2024. 11. 19. 13:48

 

Github Action

  • 필요 프레임워크가 설치되어 있는 ubuntu 서버를 받아와, 내부에서 npm run, npm run build 를 통해 성공 실패 여부를 판단한다.
  • 이때 하나라도 실패시 → 개발자가 오류를 파악, 코드 업데이트
  • 성공시 → merge pull request → deploy
  1. .github 폴더 생성
  2. workflows 폴더 생성
  3. {name}.yml 파일 생성
name: test

on: pull_request

jobs: 
  test:
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      - name: Node.js setup
        uses: actions/setup-node@v3
        with:
          node-version: "20"
      - name : Install npm packages
        run: npm ci
      - name : Install and run redis-server
        run: |
          sudo apt-get update
          sudo apt-get install -y redis-server
          redis-server --daemonize yes --requirepass test_env --port 6380
      - name: Run test
        run: npm run test
      
      - name: test build
        run: npm run build

주요 설명

on: pull_request
  • pull_request 가 실행될 때 마다 트리거 된다
jobs: 
  test:
    runs-on: ubuntu-22.04
  • 해당 workflow는 ubuntu-22.04운영체제에서 실행된다
- name: Checkout repository
  uses: actions/checkout@v3
  • PR 관련 코드를 가져온다
- name: Node.js setup
  uses: actions/setup-node@v3
  with:
    node-version: "18"
  • node 18 버전을 설치한다
- name: Install npm packages
  run: npm ci
  • 의존성을 설치한다
- name: Install and run redis-server
  run: |
    sudo apt-get update
    sudo apt-get install -y redis-server
    redis-server --daemonize yes --requirepass test_env --port 6380
  • redis server 를 설치하고 실행한다

github action 이 실행되고 있는 모습이다.

  • 이전에 jest —watchAll 을 사용했기 때문에, 해당 테스트는 멈추지 않고 돌아간다 → cancel workflow 를 눌러 중단하고 코드를 수정하자

package.json 에 test:ci : “jest” 를 추가하고,

        run: npm run test:ci

로 변경한 뒤 다시 PR 을 보내면

SSH 터널링으로 Lightsail VM 접속하기

이제 Lightsail 인스턴스로 돌아가 보자.

우리는 이제 이 가상머신이 SSH 로 접속할 수 있게 해주어야 한다.

  1. key를 하나 생성해야 한다
  • VM에 접솧할 경우, Github Action 은 별도의 client 이기에, 개인 key 를 제공해 주는 것은 위험하다.
  • 별도의 key 를 생성해 사용하자
 ssh-keygen -t rsa -b 4096 -C "github_action" -f C:\\Users\\User\\.ssh\\github_id_rsa 
  • 로컬에서 생성한 key 를 이제 서버로 옮겨야 한다.
  • 원격 서버의 ~/.ssh/authorized_keys 의 끝에 방금 생성한 퍼블릭 키를 추가하자
  • 이후 → ssh -i /C:/Users/User/.ssh/github_id_rsa ubuntu@13.125.239.167 를통해 ssh 접속

  • github repo settings → Secrets and Variables → Actions → create new repo secret → github_id_rsa (private key) 저장
  • new → SSH_USERNAME (ubuntu)
  • new → SSH_PUBLIC_IP
  • new → SSH_KNOWN_HOSTS (ssh-keyscan ip)

 

deploy.yml 파일을 작성한다

name: deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # 1. SSH 설정
      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh/
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa

      # 2. Known Hosts 설정
      - name: Set up Known Hosts
        run: |
          echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
          chmod 644 ~/.ssh/known_hosts

      # 3. SSH 접속 및 배포
      - name: SSH and deploy
        run: |
          ssh ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_PUBLIC_IP }} "
            set -xe  # 오류 발생 시 스크립트 종료 및 디버깅 활성화
            cd express

            # 최신 코드 가져오기
            echo 'Pulling latest code...'
            git pull || { echo 'Git pull failed'; exit 1; }

            # 의존성 설치
            echo 'Installing dependencies...'
            npm install || { echo 'Dependency installation failed'; exit 1; }

            # 취약점 자동 해결
            echo 'Fixing vulnerabilities...'
            npm audit fix || echo 'Some vulnerabilities could not be fixed automatically'

            # 빌드
            echo 'Building the project...'
            npm run build || { echo 'Build failed'; exit 1; }

            # 기존 Node.js 프로세스 종료
            echo 'Checking for running Node.js processes...'
            if pgrep node; then
              echo 'Killing existing Node.js processes...'
              sudo pkill node || { echo 'Failed to kill Node.js process'; exit 1; }
            else
              echo 'No Node.js processes found.'
            fi

            # 서버 시작
            echo 'Starting the server...'
            nohup sudo npm start > server.log 2>&1 &  # 백그라운드에서 실행
            sleep 5  # 서버가 완전히 시작될 때까지 대기

            # 서버 상태 확인
            echo 'Verifying server status...'
            curl -f <http://localhost> || { echo 'Server did not start correctly'; exit 1; }

            echo 'Deployment completed successfully.'
          "

++ 강의에선 말이 없었지만, timeout 시간을 늘리자

 

 

Artillery

npm install artillery

load-test.yml

config:
  target: <http://localhost:4000>
  phases:
    - duration: 5
      arrivalRate: 1000

scenarios:
  - flow:
      - get: 
          url: "/messages"

 

부하를 엄청 주게 되면, 프로세스가 죽는다. 이걸 대응하기 위해, node 를 여러개 띄워야 한다

이 방법은 stateful 하기에 관리해주어야 할 것이 많다…

 

PM2 설치

npm install pm2 -g
pm2 start build/index.js -i 3

프로세스 3 개를 띄운다는 의미이다.

 

코드 수정 후 pm2 reload all 하면 모두 재로드가 된다. 이를 사용해 무중단 배포가 가능하다

ssh 를 통해 서버 접속 후 설치

 sudo pm2 start build/index.js -i max --time

수직 vs 수평 확장

트레픽이 많아지면, 결국 CPU 를 초과하는 상황이 나오게 된다. 이럴 경우, VM 자체를 확장 시켜야 하는데, 수평 확장은, 컴퓨터를 여러대 배치하는것, 수직 확장은, 컴퓨터의 성능을 업그레이드 하는 것이다.

상황에 따라 다르겠지만, 수평 확장이 더 좋다

  • 비용 측면
  • 코어를 늘리고 싶은 경우
  • 또한 결국 여러개를 띄울 수 있으니, 서비스가 죽을 확률이 줄어든다.
  • VM 자체가 다운되는 경우 → 만약 둘 이상의 VM 을 띄웠다면 죽지 않을 것
  • 비용면, 가용성 면 둘 다 수평이 좋다

Snapshot 을 이용해 수평 확장하기

  • aws lightsail console → create snapshot → create new instance
  • 새로 시작한 인스턴스 접속 → redis-server —daemonize yes
  • sudo pm2 start build/index.js -i max —time
  • sudo pm2 list
  • loadbalancer 에 새로 생성한 인스턴수 추가
  • 이제 이전에 artillery 로 했던 테스트를 다시 해본다면, 부하가 줄어든 것을 볼 수 잇다.

 

  • 위와 같은 구조에 추가로, 데이터센터를 여러게 둔다면, 혹시 모를 자연재해 혹은 사고에도 다운되지 않고 운영할 수 있다.
  • 만약 로드 벨런서가 다운된다면?

Redis data inconsistency 문제

  • 분명 저장이 되었어야 하는 상황에 get 요청을 보내도 빈 배열이 반환될 때가 있다
  • 3개의 message 를 post 했음에도, get 요청을 보내면 3개가 반환되지 않는다
  • Redis 가 각 VM에 있기때문에 발생한다. 우리의 서버는 stateless 지만 redis 는 아니기에 발생. 서버는 요청을 받으면 redis 에 push 만하지, 어떤 redis 에 보내는지는 관심 없다.
  • 해결법 : 데이터베이스 서버를 따로 저장한다.
  • Lightsail 새로운 인스턴스 생성 → redis 설치 → 6379 port 열기 (instance 의)
  • redis는 로컬 시스템이 아닌 외부에서의 접근이 기본적으로 차단되어 있기 때문에 따로 설정을 해주어야 한다
  • 일단 systemd 에서 관리중인 redis server 를 다운시켜야 한다. → sudo systemctl stop redis-server
  • 이후 redis-server —daemonize yes
    • 이전에는 포트바인딩으로 로컬에서만 요청을 받을 수 있었지만, 이번엔 아니기에 외부 요청도 받을 수 있다.
  • 이제 다른 ubuntu 에서 돌아가는 redis 를 종료하고 새로운 redis 인스턴스에 연결해 주어야 한다
  • 아직 보안적인 허점들이 존재한다
    • 지금은 public ip 를 통해 연결하고 있는데, 이렇게 되면 외부에 노출이 된다는 단점이 있다.
    • 그렇기에, 누구나 ip 와 pwd 가 있다면 접속할 수 있다.
    • private 망 내에서만 통신을 하게 할 수 있다.

 

  • Private 망 내에서 통신할 경우, HTTPS가 필요 없다
  • Networking → 이전에 생성한 6379 삭제 → Private ip 를 가지고 다른 ubuntu에서 접속
  • cd express → .env 파일 수정
    • REDIS_URL=redis://:{비밀번호}@{Private IP}:6379
  • DB같은 state를 관리하는 서비스를 가져다 쓰는 경우, Managed Service 사용이 권장된다.
  • DB는 두가지 구성이 있다.
    • Replica Set
    • Sharding
  • DB의 경우, 여러개를 둔다고 해서 모두의 쓰기를 분산시킬 수 없다. 이러한 구조에서 쓰기는 Primary 단 하나만 된다.
  • 나머지의 경우, Primary 가 쓰고 나면, 복제가 되는 형태이다.
  • 여러곳에서 데이터가 복제되는 상황이면, 일관되게 저장되지 않는 문제가 발생한다.
  • 그렇다면 왜 secondary를 둘까? → high availability 를 위해
    • primary 가 다운되는 경우, 하나의 secondary 가 primary 로 승격
    • 이러한 다운 타임을 없애기 위해 replica set 을 사용
    • 또한 read 의 경우, secondary 에서 해도 된다.
  • 어쨌든 결국 write의 성능을 늘리기 위해선 vertical scale 을 해야 한다는 뜻인데, 이 방법은 한계가 있다. 이때, Sharding을 사용한다
    • primary 가 여러개가 되는것 .
    • 데이터 100 개가 들어왔을때, 50개 50개 분산되어 저장된다.
    • 각 set 의 내부 db마다 각각 중복된 데이터를 가지지만, 각 set 은 서로 다른 data 를 보유한다.
  • 이러한 방법도 한계가 있다. ex) db 내용이 날라가는 경우
  • 그렇기에 state를 가지는 것들의 경우, 가급적이면, managing service를 사용한다.

전통적 VM 관리에서 어려운점

  • 모든 VM 에서 nodejs 설치
  • vim 사용
  • Horizontal / Vertical Scaling
  • VM이 사람에 의해 관리
  • VM 은 비용이 많이 나간다
  • CICD의 어려움 → horizontal scale, SSH key setup
  • 모든 VM 의 static public ip
  • Systmd
  • Auto scaling 어려움
  • Rollback 이 어려움
  • Service Discovery challenge