0

Series Jenkins #7: Thực chiến – Triển khai tự động dự án Backend & Microservices

1. Nỗi đau của hệ thống phân tán và Lời giải

Khi quản lý hệ thống AFC cho các trạm Metro, một trong những vấn đề đau đầu nhất là xử lý luồng dữ liệu offline. Khi thiết bị trạm mất mạng và có lại, chúng sẽ ồ ạt đẩy dữ liệu đồng bộ về trung tâm. Nếu server Backend của bạn bị "sập" hoặc gián đoạn vài phút ngay đúng lúc đang deploy thủ công, bạn sẽ phải đối mặt với hàng loạt "Orphan Transactions" (giao dịch mồ côi) do mất tính toàn vẹn dữ liệu.

Do đó, quy trình triển khai phải cực kỳ gọn nhẹ, trơn tru và thời gian downtime (thời gian chết) phải bằng 0.

Lộ trình chuẩn cho một Microservice sẽ đi qua 3 trạm:

  1. Jenkins Server: Kéo code, biên dịch, đóng gói thành Docker Image.
  2. Docker Registry: Kho lưu trữ tập trung (như Docker Hub, Amazon ECR hoặc Harbor).
  3. Production Server: Nơi ứng dụng thực sự chạy. Jenkins sẽ dùng SSH "thò tay" sang server này ra lệnh kéo Image mới về chạy.

2. Chuẩn bị vũ khí: Multi-stage Dockerfile cho Golang

Khác với Node.js hay PHP, Golang là ngôn ngữ biên dịch. Bạn không cần mang toàn bộ mã nguồn và bộ cài Go nặng hàng trăm MB lên server thật. Hãy dùng kỹ thuật Multi-stage Build (Build nhiều giai đoạn) để tạo ra một chiếc hộp siêu nhẹ (chỉ khoảng 15-20MB). Trọng lượng nhẹ giúp server tải xuống tính bằng giây, giảm thiểu tối đa rủi ro gián đoạn giao dịch.

Dưới đây là một Dockerfile mẫu cho service AFC:

# --- Giai đoạn 1: Thợ xây (Builder) ---
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Cài đặt các thư viện cần thiết
COPY go.mod go.sum ./
RUN go mod download
# Copy toàn bộ source code và biên dịch ra file nhị phân 'afc-service'
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o afc-service .

# --- Giai đoạn 2: Đóng gói (Production) ---
# Chỉ dùng một HĐH Alpine siêu nhẹ, không cần cài Golang nữa
FROM alpine:latest
WORKDIR /root/
# Chỉ copy đúng file thực thi 'afc-service' từ giai đoạn 1 sang
COPY --from=builder /app/afc-service .
# Mở port cho API
EXPOSE 8080
# Chạy ứng dụng
CMD ["./afc-service"]

3. Cấu hình Jenkins "Thò tay" sang Server Production

Để Jenkins có thể ra lệnh cho con server Production nằm cách nó hàng chục km, chúng ta sử dụng giao thức SSH.

Bước thiết lập một lần trên Jenkins:

  1. Cài đặt plugin SSH Agent trên Jenkins.
  2. Trên server Jenkins, tạo một cặp khóa SSH (ssh-keygen).
  3. Copy khóa công khai (Public Key) bỏ vào file ~/.ssh/authorized_keys của server Production.
  4. Lưu khóa bí mật (Private Key) vào két sắt của Jenkins (Vào Manage Jenkins ➔ Credentials ➔ Add Credentials, chọn loại SSH Username with private key và đặt ID là prod-ssh-key).

4. Viết Jenkinsfile Thực Chiến

Giờ là lúc kết hợp tất cả lại thành một Pipeline hoàn chỉnh. Chúng ta sẽ khai báo cho bác quản gia biết phải lấy code ở đâu, đẩy lên kho nào và kết nối với server nào.

pipeline {
    agent any
    
    environment {
        // Khai báo các thông tin môi trường
        DOCKER_REGISTRY = "myusername" // Thay bằng tài khoản Docker Hub của bạn
        APP_NAME = "afc-transaction-service"
        IMAGE_TAG = "v${env.BUILD_NUMBER}"
        
        // Thông tin Server Production
        PROD_SERVER = "user@192.168.1.100" // IP của server chạy Backend
    }

    stages {
        stage('1. Checkout Code') {
            steps {
                echo "Đang kéo mã nguồn Service xử lý giao dịch..."
                git branch: 'main', url: 'https://github.com/YourUsername/afc-transaction-service.git'
            }
        }
        
        stage('2. Build & Tag Docker Image') {
            steps {
                echo "Áp dụng Multi-stage build cho Golang..."
                sh "docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG} ."
                sh "docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:latest ."
            }
        }
        
        stage('3. Push Image lên Registry') {
            steps {
                echo "Đẩy Image lên kho lưu trữ..."
                // Lưu ý: Đời thực bạn cần dùng withCredentials để login Docker Hub an toàn
                // sh "docker login -u $USER -p $PASS"
                sh "docker push ${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG}"
                sh "docker push ${DOCKER_REGISTRY}/${APP_NAME}:latest"
            }
        }
        
        stage('4. Deploy lên Production qua SSH') {
            steps {
                echo "Bắt đầu cập nhật hệ thống (Zero-downtime expected)..."
                // Dùng SSH Agent để kết nối an toàn sang server thật
                sshagent(['prod-ssh-key']) {
                    sh """
                        # 1. SSH vào server và bắt nó kéo Image mới nhất về
                        ssh -o StrictHostKeyChecking=no ${PROD_SERVER} 'docker pull ${DOCKER_REGISTRY}/${APP_NAME}:latest'
                        
                        # 2. Xóa container cũ (Lưu ý: Trong thực tế hệ thống lớn, ta sẽ dùng Docker Swarm/K8s để rolling update thay vì xóa cứng thế này)
                        ssh -o StrictHostKeyChecking=no ${PROD_SERVER} 'docker rm -f running-afc-service || true'
                        
                        # 3. Khởi động lại container mới
                        ssh -o StrictHostKeyChecking=no ${PROD_SERVER} 'docker run -d --name running-afc-service \
                            -p 8080:8080 \
                            --restart always \
                            ${DOCKER_REGISTRY}/${APP_NAME}:latest'
                    """
                }
            }
        }
    }
}

5. Tổng kết quy trình

Anh em lưu file Jenkinsfile lại, gõ git push. Ngay lập tức, một chuỗi dây chuyền vận hành sẽ diễn ra tự động 100%:

  • Code được biên dịch an toàn trong hộp kín.
  • Một Docker Image siêu nhẹ chỉ chứa file chạy Golang được đẩy lên mạng.
  • Jenkins hóa thân thành người quản trị, âm thầm SSH vào server máy chủ trạm, cập nhật mã nguồn mới mà không cần ai phải mở WinSCP hay gõ từng dòng lệnh bằng tay.

Tốc độ và sự chuẩn xác này chính là chìa khóa để đảm bảo dữ liệu giao dịch từ Kafka hay Vitess không bị nghẽn lại vì sự can thiệp thủ công chậm chạp của con người.

Tuy nhiên, nếu anh em để ý kỹ ở phần bình luận trong Code, việc đăng nhập vào Docker Registry hay Database luôn cần mật khẩu. Nếu ta cứ viết toẹt mật khẩu ra file code như vậy thì hệ thống bảo mật coi như vứt đi.

Làm thế nào để hệ thống tự động hóa CI/CD vừa nhanh, mạnh nhưng lại bảo vệ được các dữ liệu nhạy cảm? Chúng ta sẽ giải quyết triệt để bài toán này ở Bài 8: Trạm cuối – Bảo mật tối thượng, Quản lý Secret và Best Practices.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí