Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- factory
- 일급 객체
- 일급 컬렉션
- OAuth 2.0
- java
- builder
- Spring
- Google OAuth
- Dependency Injection
- Volatile
- synchronized
- spring security
- lombok
Archives
- Today
- Total
HJW's IT Blog
[CodeIt] CI/CD with Redis & Lightsail 본문

Github Action
- 필요 프레임워크가 설치되어 있는 ubuntu 서버를 받아와, 내부에서 npm run, npm run build 를 통해 성공 실패 여부를 판단한다.
- 이때 하나라도 실패시 → 개발자가 오류를 파악, 코드 업데이트
- 성공시 → merge pull request → deploy
- .github 폴더 생성
- workflows 폴더 생성
- {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 로 접속할 수 있게 해주어야 한다.
- 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