Tự động hóa triển khai Docker: Hướng dẫn Jenkins thực tế cho VPS

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Ngừng việc SSH vào máy chủ của bạn

Tôi đã dành quá nhiều đêm thức trắng để SSH vào VPS, pull code và build lại Docker image một cách thủ công. Đó là một quy trình mất 20 phút trong khi đáng lẽ chỉ tốn 2 phút. Triển khai thủ công không chỉ gây lãng phí thời gian mà còn là “mảnh đất màu mỡ” cho sự sai lệch cấu hình và sai sót của con người. Chuyển sang mô hình CI/CD sẽ biến sự hỗn loạn đầy áp lực này thành một tác vụ chạy ngầm có thể dự đoán được và chỉ mất hai phút.

Tại sao chọn Jenkins? Dù GitHub Actions rất phổ biến, Jenkins mang lại cho bạn quyền kiểm soát hoàn toàn đối với môi trường build của mình. Đây là một lợi thế lớn khi bạn làm việc trên một VPS tiết kiệm ngân sách và cần giữ mọi thứ tập trung một chỗ. Theo kinh nghiệm của tôi, thiết lập này giúp giảm thời gian chết (downtime) liên quan đến triển khai gần 90% vì nó loại bỏ hoàn toàn biến số “nó chạy tốt trên máy tôi”.

Thiết lập Jenkins qua Docker

Chạy Jenkins bên trong một Docker container giúp hệ thống host luôn sạch sẽ. Nó ngăn VPS của bạn trở thành “nghĩa địa” của các phiên bản Java và dependency xung đột nhau. Để cho phép Jenkins build image, chúng ta sử dụng chiến lược “Docker-out-of-Docker” (DooD). Điều này bao gồm việc mount Docker socket của host trực tiếp vào Jenkins container.

Bắt đầu bằng cách tạo một file docker-compose.yml trên VPS của bạn:

version: '3.8'
services:
  jenkins:
    image: jenkins/jenkins:lts-jdk17
    container_name: jenkins
    privileged: true
    user: root
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker

volumes:
  jenkins_home:

Khởi chạy dịch vụ bằng lệnh docker-compose up -d. Bằng cách chia sẻ /var/run/docker.sock, bạn cho phép Jenkins container điều khiển Docker engine của máy host. Cách này hiệu quả và nhẹ nhàng. Khi container đã hoạt động, hãy lấy mật khẩu admin khởi tạo bằng lệnh docker logs jenkins và truy cập cổng 8080 để hoàn tất thiết lập.

Các Plugin thiết yếu

Trước khi xây dựng pipeline đầu tiên, hãy vào Manage Jenkins > Plugins và cài đặt ba công cụ chủ lực sau:

  • Docker Pipeline: Cho phép sử dụng các lệnh Docker bên trong script.
  • SSH Agent: Quản lý an toàn các SSH key để triển khai từ xa.
  • Git Pipeline: Xử lý việc clone repository và webhook.

Jenkinsfile: Bộ não của Pipeline

Jenkinsfile là trái tim của quá trình tự động hóa. Việc lưu trữ file này trong Git repository đảm bảo logic triển khai của bạn được quản lý phiên bản giống như mã nguồn ứng dụng. Tôi khuyên dùng cú pháp Declarative Pipeline vì tính dễ đọc và khả năng xử lý lỗi tích hợp sẵn.

Dưới đây là một template sẵn sàng cho môi trường production. Nó thực hiện build image, push lên registry và triển khai tới môi trường production của bạn.

pipeline {
    agent any

    environment {
        DOCKER_REGISTRY = "ten-dang-nhap-dockerhub-cua-ban"
        APP_NAME = "ung-dung-cua-toi"
        IMAGE_TAG = "${env.BUILD_NUMBER}"
        DOCKER_CREDENTIALS_ID = 'docker-hub-creds'
    }

    stages {
        stage('Clone mã nguồn') {
            steps {
                checkout scm
            }
        }

        stage('Build Image') {
            steps {
                script {
                    appImage = docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG}")
                }
            }
        }

        stage('Push lên Registry') {
            steps {
                script {
                    docker.withRegistry('', DOCKER_CREDENTIALS_ID) {
                        appImage.push()
                        appImage.push("latest")
                    }
                }
            }
        }

        stage('Triển khai lên VPS') {
            steps {
                sshagent(['vps-ssh-key']) {
                    sh '''
                        ssh -o StrictHostKeyChecking=no user@your-vps-ip "
                            docker pull ${DOCKER_REGISTRY}/${APP_NAME}:latest && \
                            docker stop ${APP_NAME} || true && \
                            docker rm ${APP_NAME} || true && \
                            docker run -d --name ${APP_NAME} -p 80:3000 ${DOCKER_REGISTRY}/${APP_NAME}:latest
                        "
                    '''
                }
            }
        }
    }

    post {
        always {
            sh "docker rmi ${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG} || true"
        }
    }
}

Quản lý thông tin xác thực an toàn

Viết cứng (hardcoding) mật khẩu là một sai lầm nghiêm trọng trong DevOps. Hãy sử dụng Jenkins Credential Store (Manage Jenkins > Credentials) để ẩn các thông tin nhạy cảm. Ánh xạ docker-hub-creds cho thông tin đăng nhập registry và vps-ssh-key cho SSH private key của bạn. Điều này giúp Jenkinsfile luôn an toàn khi chia sẻ với nhóm.

Tinh chỉnh logic triển khai

Trong giai đoạn triển khai, tôi sử dụng docker stop sau đó là docker rm. Đây là cách đơn giản nhất để dọn đường cho phiên bản container mới. Nếu bạn đang quản lý nhiều dịch vụ, hãy cân nhắc sử dụng docker-compose up -d --pull always để thay thế. Nó xử lý các liên kết mạng và biến môi trường một cách mượt mà hơn.

Mẹo nhỏ: Luôn tag image của bạn với BUILD_NUMBER của Jenkins. Nếu một bản phát hành mới làm hỏng trang web, bạn có thể quay lại (roll back) phiên bản hoạt động ổn định chỉ trong vài giây. Việc chỉ dựa vào tag latest khiến việc rollback trở thành một trò chơi may rủi.

Bảo trì và Giám sát

Tự động hóa đòi hỏi sự giám sát. Hãy theo dõi “Console Output” trong Jenkins để nhận phản hồi theo thời gian thực. Nếu quá trình build bị treo ở giai đoạn “Push”, có khả năng thông tin xác thực của bạn đã hết hạn. Nếu thất bại ở bước “Deploy”, hãy kiểm tra xem firewall của VPS có cho phép lưu lượng SSH từ IP của Jenkins container hay không.

Giữ cho VPS gọn nhẹ

Build Docker tiêu tốn dung lượng ổ đĩa rất nhanh. Mỗi lần build tạo ra một layer mới tồn tại trên ổ cứng. Tôi thêm một block post trong Jenkinsfile để xóa image cục bộ sau khi push lên registry. Bạn cũng nên chạy một cron job hàng tuần trên máy host để dọn dẹp dữ liệu cũ:

# Dọn dẹp các image cũ hơn 24 giờ
docker image prune -af --filter "until=24h"

Kiểm tra trạng thái cuối cùng

Một pipeline thành công không phải lúc nào cũng đồng nghĩa với việc ứng dụng hoạt động tốt. Hãy thêm một giai đoạn cuối cùng sử dụng curl để kiểm tra endpoint health của ứng dụng. Nếu nó không trả về 200 OK trong vòng 30 giây, pipeline sẽ thất bại và kích hoạt cảnh báo. Điều này đảm bảo bạn không bao giờ vô tình triển khai một container lỗi cho người dùng.

Làm chủ Jenkins và Docker là kỹ năng cốt lõi của bất kỳ nhà phát triển nào. Bằng cách chuyển logic vào một Jenkinsfile được quản lý phiên bản, bạn tạo ra một quy trình triển khai có tài liệu, có thể lặp lại và hoàn toàn tự động.

Share: