Zero-downtime deployment với dự án Next.js
1. Tại sao cần zero downtime deployment?
CI-CD, DevOps là các từ viết tắt thường xuyên được sử dụng trong mô hình phát triển phần mềm dựa trên Agile. Seminar trước đây CI/CD with Github Actions mình có đề cập đến việc tự động deploy một dự án Laravel lên server, mỗi khi được merge code vào nhánh chính.
Vấn đề nảy sinh ở đây: Giả sử, trong lúc khách hàng đang sử dụng dịch vụ, mà chúng ta lại cần release gấp 1 tính năng, chúng ta không thể cài đặt maintain mode
rồi deploy, hoặc làm cho hệ thống dừng hoạt động trong lúc deploy, đó là một trải nghiệm không tốt, khách hàng có thể sẽ rời bỏ chúng ta. Vì vậy CD (Continuous Deployment - triển khai liên tục) cần đảm bảo không có downtime khi deploy.
Giải pháp chúng ta cần nghĩ đến là một quy trình / cơ chế deploy mà không làm sập server trong lúc chạy kịch bản deploy. Với dự án laravel, các bạn có thể tham khảo bài viết: Tự động deploy ứng dụng Laravel với Deployer trên CentOS
2. Vấn đề khi build production dự án Next.js
Quay lại với dự án next.js, bạn có gặp lỗi "Internal Server Error" khi build source code bằng command npm run build
?
Nguyên nhân là khi build lại source code, folder .next
sẽ bị thay đổi, dẫn đến source code có thể hoạt động không chính xác ở thời điểm build.
3. Logic xử lý zero downtime khi build
Để giải quyết vấn đề downtime này chúng ta cần phải đảm bảo không thay đổi source code đang chạy trên folder .next
, đồng thời có cơ chế để chuyển đổi từ source cũ sang source mới nhanh nhất.
Với Deployer, sử dụng cơ chế symlink để switch giữa các folder source code, đảm bảo thời gian downtime gần như bằng 0. Với dự án nextjs, chúng ta cũng có thể sử dụng phương pháp này. Tuy nhiên, mình đề xuất 1 một logic đơn giản hơn
- Build js ra một folder tạm tên là
temp
- Sau khi build hoàn tất
- Xóa folder
.next
- Rename
temp
=>.next
Khoảng thời gian trễ sẽ nằm trong khoảng từ khi xóa đến khi rename hoàn tất.
Dưới đây là 1 đoạn shell script thực hiện logic trên. `pm2-deploy.sh`
Tích hợp vào pm2 ecosystem file để deploy từ local:
4. Deploy tự động, zero downtime với github action
Với script trên, chúng ta có thể deploy từ local lên server bằng command:
$ pm2 deploy staging
Tích hợp vào github action với script đặt trong file .github/workflow/deploy.yml
name: Deploy
on:
push:
branches: [ master, dev ]
pull_request:
branches: [ master, dev ]
jobs:
next-ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
- name: Build temp folder
run: |
yarn install --frozen-lockfile
deploy-dev:
runs-on: ubuntu-latest
needs: next-ci
if: ${{ github.ref == 'refs/heads/dev' }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
- name: Create build to temp folder
run: |
yarn install --frozen-lockfile
BUILD_DIR=temp ./node_modules/next/dist/bin/next build
- name: Setup SSH
run: |
mkdir -p ~/.ssh/
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deployerkey
sudo chmod 600 ~/.ssh/deployerkey
echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
cat >>~/.ssh/config <<END
Host staging
HostName host.ip
User deployer
IdentityFile ~/.ssh/deployerkey
StrictHostKeyChecking no
END
shell: bash
env:
SSH_PRIVATE_KEY: ${{secrets.SSH_PRIVATE_KEY}}
SSH_KNOWN_HOSTS: ${{secrets.SSH_KNOWN_HOSTS}}
- name: Copy build folder to Server
run: |
rsync -avz ./temp staging:/var/www/hapo-web/source/
- name: Install pm2
run: npm install -g pm2
- name: Deploy with pm2
run: |
pm2 deploy staging
Lưu ý, ở script trên có đoạn build và copy source code từ github action server sang staging server.
Mục đích của việc build trên github action server là để tránh việc build chiếm tài nguyên của server production, đảm bảo lúc deploy ít ảnh hưởng đến hoạt động bình thường của server.
Kết luận
Với sự kết hợp của shell script, pm2 và github action, chúng ta có thể tạo ra được một mô hình deploy đơn giản, hiệu quả giúp cho luồng CI/CD trong dự án của bạn được tự nhiên và trôi chảy hơn.