Mục lục

CI/CD & GitOps: Tự động hóa Deploy

Thiết lập quy trình CI/CD với GitHub Actions, giới thiệu GitOps với ArgoCD và thực hành auto-deploy Next.js blog.

Bạn đã có Server, đã cài Docker, Nginx. Nhưng mỗi lần sửa code lại phải SSH vào server, gõ git pull, build lại Docker Image thủ công? Đã đến lúc tự động hóa mọi thứ để đạt trải nghiệm "Push là Lên" (như Vercel).

1. Khái niệm cốt lõi

CI/CD là gì?

  • CI (Continuous Integration): Tích hợp liên tục. Khi bạn push code, hệ thống tự động chạy Test, Lint, Build để đảm bảo code không lỗi.
  • CD (Continuous Deployment): Triển khai liên tục. Code sau khi build xong sẽ tự động được đẩy lên Server chạy.

Github Actions

Công cụ CI/CD tích hợp sẵn trong Github. Nó hoạt động dựa trên các Workflow (file cấu hình .yml).

  • Runner: Máy chủ ảo của Github dùng để chạy lệnh build.
  • Action: Các bước thực hiện (Checkout code, Login Docker, SSH...).

ArgoCD & GitOps (Level "Hero")

Trong các hệ thống lớn dùng Kubernetes, người ta không dùng Github Actions để SSH vào server (vì rủi ro bảo mật và khó quản lý nhiều server). Thay vào đó, họ dùng GitOps:

  1. Github Actions chỉ đóng vai trò Build Docker Image & đẩy lên Registry.
  2. ArgoCD (cài trong cụm Kubernetes) sẽ "canh chừng" (watch) Git Repository.
  3. Khi thấy file config thay đổi (ví dụ: image tag mới), ArgoCD sẽ tự động đồng bộ (Sync) về cụm K8s. -> Server tự kéo code về (Pull) thay vì bị đẩy code vào (Push).

2. Thực hành: Auto-Deploy Next.js lên VPS (GitHub Actions)

Vì chúng ta đang dùng 1 VPS đơn lẻ (chưa dùng Kubernetes), mô hình Push-based với GitHub Actions là hiệu quả và đơn giản nhất.

Mục tiêu:

  1. Code tại máy -> Push lên Main.
  2. GitHub Actions -> Build Docker Image -> Push lên Docker Hub.
  3. GitHub Actions -> SSH vào VPS -> Pull Image mới -> Restart Container.

Bước 1: Chuẩn bị Dockerfile cho Next.js

Tạo file Dockerfile ở thư mục gốc dự án Next.js:

dockerfile:
# Base image nhẹ
FROM node:18-alpine AS base

# 1. Install dependencies only when needed
FROM base AS deps
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN npm ci

# 2. Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# 3. Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]

Lưu ý: Trong next.config.js cần thêm output: 'standalone'.

Bước 2: Tạo Github Workflow

Tạo file .github/workflows/deploy.yml:

yaml:
name: Deploy Next.js to VPS

on:
  push:
    branches: ["main"]

env:
  DOCKER_IMAGE: huynguyen/my-blog # Thay bằng tên user/repo dockerhub của bạn

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and Push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ env.DOCKER_IMAGE }}:latest

      - name: Deploy to VPS via SSH
        uses: appleboy/ssh-action@v0.1.6
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            docker pull ${{ env.DOCKER_IMAGE }}:latest
            docker stop my-blog || true
            docker rm my-blog || true
            docker run -d \
              --name my-blog \
              --restart always \
              -p 3000:3000 \
              -e NODE_ENV=production \
              ${{ env.DOCKER_IMAGE }}:latest

Bước 3: Cấu hình Secret trên Github

Lưu ý quan trọng: Bạn phải vào phần cài đặt của Dự án (Repository), KHÔNG PHẢI cài đặt tài khoản cá nhân.

  1. Mở trang dự án của bạn trên GitHub (ví dụ: https://github.com/huynv96/my-notes).
  2. Nhìn lên thanh menu ngang trên cùng, chọn tab Settings (bên cạnh tab Insights).
  3. Ở menu bên trái, tìm mục Security -> chọn Secrets and variables -> chọn Actions.
  4. Nhấn nút màu xanh New repository secret.

Khi nhấn "New repository secret", bạn sẽ thấy 2 ô nhập:

  • Name: Nhập tên biến (ví dụ VPS_HOST).
  • Secret: Nhập giá trị thực tế (ví dụ 36.50.26.62).

Thêm lần lượt 5 bí mật sau:

1. DOCKERHUB_USERNAME

  • Giá trị: Tên đăng nhập Docker Hub của bạn (ví dụ: huynv96).

2. DOCKERHUB_TOKEN

Đây là mật khẩu dùng cho tool (an toàn hơn mật khẩu gốc).

  • Truy cập Docker Hub Security Settings.
  • Nhấn New Access Token.
  • Description: github-actions.
  • Access permissions: Chọn Read, Write, Delete.
  • Copy chuỗi token vừa tạo (bắt đầu bằng dckr_pat_...) và paste vào GitHub.

3. VPS_HOST

  • Giá trị: Địa chỉ IP Public của VPS (ví dụ: 36.50.26.62).

4. VPS_USER

  • Giá trị: Username bạn dùng để SSH vào VPS (ví dụ: huynguyen).

5. VPS_SSH_KEY

Đây là bước quan trọng nhất và dễ nhầm lẫn nhất. GitHub cần một "chìa khóa" (Private Key) để mở cửa vào VPS của bạn.

Khuyên dùng: Nên tạo một chìa khóa riêng chuyên dụng cho GitHub Actions thay vì dùng chung chìa khóa cá nhân.

Cách tạo Key chuyên dụng (Thực hiện trên VPS):

  1. Đăng nhập vào VPS của bạn.
  2. Chạy lệnh tạo key mới (đặt tên là github_action):
    bash:
    ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/github_action -N ""
  3. Cấp quyền cho chìa khóa này được phép "vào nhà" (thêm vào authorized_keys):
    bash:
    cat ~/.ssh/github_action.pub >> ~/.ssh/authorized_keys
  4. Xem nội dung Private Key để copy:
    bash:
    cat ~/.ssh/github_action
  5. Copy toàn bộ nội dung hiện ra (bao gồm cả dòng -----BEGIN...-----END...) dán vào GitHub Secret.

Lưu ý: Lệnh trên tạo key tên là github_action. Nếu bạn kiểm tra bằng ls ~/.ssh/ sẽ thấy 2 file: github_action (Private - Cất đi) và github_action.pub (Public - Đã gắn vào cửa).

Lưu ý: Lệnh trên tạo key tên là github_action. Nếu bạn kiểm tra bằng ls ~/.ssh/ sẽ thấy 2 file: github_action (Private - Cất đi) và github_action.pub (Public - Đã gắn vào cửa).


4. Cấu hình phía VPS (Production)

Sau khi thiết lập xong CI/CD, Container my-notes sẽ chạy ở cổng 3000 trên VPS. Để người dùng truy cập được bằng tên miền (ví dụ notes.english4it.site), bạn cần cấu hình Nginx làm cầu nối (Reverse Proxy).

A. Chuẩn bị Docker (Nếu Repo là Private)

Nếu Docker Image huynv96/my-notes bạn để chế độ Private, VPS cần đăng nhập mới kéo được ảnh về. Thực hiện 1 lần duy nhất trên VPS:

bash:
docker login -u huynv96
# Nhập Password hoặc Access Token của bạn

Nếu Repository là Public (công khai) thì bỏ qua bước này.

B. Cấu hình Nginx Reverse Proxy

Bước 1: Tạo file cấu hình mới

bash:
sudo nano /etc/nginx/sites-available/my-notes

Bước 2: Dán nội dung cấu hình (Thay notes.english4it.site bằng tên miền bạn muốn):

nginx:
server {
    server_name notes.english4it.site;

    location / {
        proxy_pass http://127.0.0.1:3000; # Trỏ vào port 3000 của Next.js Container
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        
        # Thêm header để Next.js hiểu IP thật của user
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Bước 3: Kích hoạt site

bash:
# Tạo shortcut sang thư mục enabled
sudo ln -s /etc/nginx/sites-available/my-notes /etc/nginx/sites-enabled/

# Kiểm tra lỗi cú pháp
sudo nginx -t

# (Nếu OK) Reload Nginx
sudo systemctl reload nginx

C. Cài SSL (HTTPS)

Cuối cùng, đừng quên khóa bảo mật bằng SSL miễn phí:

bash:
sudo certbot --nginx -d notes.english4it.site

D. Cách kiểm tra cấu hình thành công (Checklist)

Làm sao biết Nginx đã nhận cấu hình và hoạt động đúng? Hãy chạy lần lượt các lệnh sau trên VPS:

1. Kiểm tra cú pháp (Syntax Check) Lệnh này quan trọng nhất, nó báo cho bạn biết file config có bị viết sai chính tả hay thiếu dấu ; không.

bash:
sudo nginx -t
  • ✅ Thành công: Báo syntax is oktest is successful.
  • ❌ Thất bại: Sẽ hiện dòng lỗi cụ thể (ví dụ: unknown directive at line 10).

2. Kiểm tra Nginx có đang chạy không

bash:
sudo systemctl status nginx
  • ✅ Thành công: Dòng Active: hiện màu xanh lá active (running).

3. Kiểm tra Container App có đang chạy không Nginx chỉ là người đưa thư, nếu App (Docker) chết thì Nginx sẽ báo lỗi 502 Bad Gateway.

bash:
docker ps | grep my-notes
  • ✅ Thành công: Thấy dòng chứa my-notes và port 3000->3000.

4. Kiểm tra kết nối nội bộ (VPS tự gọi chính mình) Thử đóng vai Nginx gọi vào App xem App có trả lời không:

bash:
curl -I http://127.0.0.1:3000
  • ✅ Thành công: Thấy HTTP/1.1 200 OK (hoặc 308/307).
  • ❌ Thất bại: Connection refused (App chưa chạy hoặc sai port).

E. Xử lý lỗi 502 khi đăng nhập (Sign In)

Nếu giao diện web lên bình thường nhưng bấm Login bị lỗi 502 Bad Gateway: Nguyên nhân: Thư viện bảo mật (Auth.js) chặn kết nối vì nó thấy yêu cầu đi qua Proxy (Nginx) mà chưa được "tin tưởng".

Cách sửa: Bạn cần thêm biến môi trường AUTH_TRUST_HOST=true vào file deploy.yml.

  1. Mở file .github/workflows/deploy.yml.
  2. Tìm đoạn docker run.
  3. Thêm dòng -e AUTH_TRUST_HOST=true \ vào.
  4. Cũng nên thêm -e AUTH_URL=https://docs.english4it.site \ (thay bằng domain thật của bạn).

Cách xem log lỗi chi tiết: Nếu vẫn lỗi, hãy xem server đang "kêu gào" gì bằng lệnh:

bash:
docker logs my-notes --tail 100 -f

6. Xử lý biến môi trường (.env)

Dự án Next.js thường có file .env.local chứa key bảo mật. File này không bao giờ được đẩy lên Git, vì vậy server sẽ không biết các biến này nếu bạn không cài đặt.

Có 2 loại biến bạn cần thêm vào GitHub Secrets (cùng chỗ với bước 3):

A. Biến Build (NEXT_PUBLIC_...)

Next.js cần các biến này ngay khi Build Docker Image. Bạn cần thêm các secret sau vào GitHub:

  1. NEXT_PUBLIC_SUPABASE_URL
  2. NEXT_PUBLIC_SUPABASE_ANON_KEY
  3. NEXT_PUBLIC_APP_URL (Ví dụ: https://notes.english4it.site)
  4. NEXT_PUBLIC_REPO_URL

B. Biến Runtime (Server Keys)

Đây là các key chỉ cần khi Code chạy trên server. Bạn cần thêm:

  1. AUTH_SECRET
  2. SUPABASE_SERVICE_ROLE_KEY

Lưu ý: Tôi đã tự động cập nhật file Dockerfiledeploy.yml để nhận các biến này. Bạn chỉ cần vào GitHub Settings và điền đúng giá trị (lấy từ file .env hoặc .env.local trong máy bạn) là xong.

B. Biến Runtime (Server Keys)

Đây là các key chỉ cần khi Code chạy trên server. Bạn cần thêm:

  1. AUTH_SECRET
  2. SUPABASE_SERVICE_ROLE_KEY

Lưu ý: Tôi đã tự động cập nhật file Dockerfiledeploy.yml để nhận các biến này. Bạn chỉ cần vào GitHub Settings và điền đúng giá trị (lấy từ file .env hoặc .env.local trong máy bạn) là xong.


7. Gỡ bỏ Vercel & Chuyển hẳng sang VPS

Nếu bạn đã thành công với Pipeline trên và thấy web chạy mượt trên VPS, bạn có thể "chia tay" Vercel để tiết kiệm chi phí hoặc đơn giản là muốn tự quản lý.

Bước 1: Tắt Git Integration trên Vercel

  1. Vào Dashboard Vercel -> Chọn dự án.
  2. Vào tab Settings -> Git.
  3. Tìm mục Connected Git Repository -> Bấm Disconnect.
    • Tác dụng: Từ giờ khi bạn Push code lên GitHub, Vercel sẽ KHÔNG tự động build nữa (đỡ tốn tài nguyên build của Vercel).

Bước 2: Cấu hình lại DNS Domain Hiện tại domain notes.english4it.site có thể đang trỏ về Vercel (CNAME hoặc A record). Bạn cần trỏ nó thẳng về VPS.

  1. Vào trang quản lý tên miền (Hostinger, Namecheap...).
  2. Xóa các bản ghi cũ liên quan đến Vercel (thường là CNAME cname.vercel-dns.com hoặc IP 76.76.21.21).
  3. Thêm bản ghi A Record mới:
    • Type: A
    • Name: @ (hoặc notes nếu là subdomain)
    • Value: 36.50.26.62 (IP VPS của bạn)
    • TTL: Auto hoặc 300.
  4. Lưu lại và đợi 5-10 phút để mạng cập nhật.

Kết quả sau cùng:

  • User truy cập domain -> Vào thẳng VPS của bạn (qua Nginx) -> Vào Docker Container Next.js.
  • Vercel không còn tham gia vào quá trình này nữa.

8. Tổng kết

Với cấu hình trên, bạn đã có một quy trình "Semi-Professional":

  • Tự động hóa Containerization (Docker).
  • Tự động hóa Deployment (GitHub Actions).
  • Zero downtime (gần như, chỉ mất vài giây restart container).

Khi dự án lớn lên hàng trăm Microservices, đó là lúc bạn cần nghiên cứu KubernetesArgoCD để quản lý sự phức tạp đó. Còn với Blog/Web App đơn lẻ, cặp bài trùng Docker Compose + GitHub Actions là vô địch.

Quảng cáo
mdhorizontal