Chuyển tới nội dung

Triển Khai CI/CD Từ Github Lên K8S Thông Qua Jenkins Và Gitlab Registry

Sơ đồ hoạt động

Cấu trúc thư mục project

├───Application
├───bin
├───Controllers
├───Domain
├───Infrastructure
├───k8s
│       backend-deployment.yaml
│       backend-service.yaml
│       ingress.yaml
│       namespace.yaml
│       registry-secret.yaml
├───obj
├───Properties
├───Shared
│   .gitignore
│   appsettings.Development.json
│   appsettings.json
│   Dockerfile
│   Jenkinsfile
│   PKM_Locations.API.csproj
│   PKM_Locations.API.http
│   PKM_Locations.API.sln
│   Program.cs
│   README.md
│   web.config    

Giai đoạn 1: Github (Nơi chứa Code), Gitlab làm Repository image

1. Tạo Repository trên github và push code lên

  • Truy cập https://github.com/diendt/pkm_location_api.git (Username: root, Password: BeoBeo..@2025)
  • Thực hiện clone project từ github về local, copy project vào thư mục và push len github
cd D:\Project\
git clone https://github.com/diendt/pkm_location_api.git
cd pkm_location_api
# Copy code vào thư mục
git add .
git commit -m "Init Project"
git push 

2. Tạo Jenkinsfile (Create Jenkinsfile):

Tạo file mới tên Jenkinsfile

Nội dung file: Jenkinsfile (File 3 – English Comments)

// Jenkinsfile - Simple Deploy to Docker Host (Cách 1: All-in-One)
// Builds production images and runs them on the host Docker daemon.
pipeline {
    // Run on the Jenkins controller itself, requires docker-cli installed via Dockerfile
    agent any 

    environment {
        // --- Application & Image Naming ---
        REGISTRY_URL        = "registry.diendo.pro.vn"
        REGISTRY_CREDENTIAL = "docker-registry-creds"  // ID trùng với Jenkins
        REGISTRY_PATH       = "beobeo/location_api"
        // --- Application & Image Naming ---
        APP_NAME            = 'location-api' // Base name for images and containers
        BACKEND_IMAGE       = "${REGISTRY_PATH}/${APP_NAME}-backend:latest"  // Image name for backend
        BACKEND_CONTAINER   = "${APP_NAME}-backend-app"  // Fixed container name for backend
        // --- Host Port Configuration ---
        K8S_CREDENTIAL_ID = 'k8s-config-file' // ID credential kubeconfig
    }

    stages {
        // --- Stage 1: Get latest code ---
        stage('1. Checkout Code') {
            steps {
                // Use Jenkins built-in SCM checkout step
                checkout scm 
                echo "SUCCESS: Code checked out from GitLab."
            }
        }
        
        // --- Stage 2: Build Production Docker Images ---
        stage('2. Build & Push Docker Images') {
            parallel {
                stage('Backend') {
                    steps {
                        script {
                            withCredentials([usernamePassword(
                                credentialsId: env.REGISTRY_CREDENTIAL, 
                                usernameVariable: 'REG_USER', 
                                passwordVariable: 'REG_PASS')]
                                ) {
                                sh '''
                                    echo " Building backend image: ${BACKEND_IMAGE}"
                                    docker build -t ${BACKEND_IMAGE} .
                                    docker tag ${BACKEND_IMAGE} ${REGISTRY_URL}/${BACKEND_IMAGE}
                                    echo "${REG_PASS}" | docker login ${REGISTRY_URL} -u "${REG_USER}" --password-stdin
                                    docker push ${REGISTRY_URL}/${BACKEND_IMAGE}
                                    docker logout ${REGISTRY_URL}
                                '''
                            }
                        }
                        echo " Backend image pushed successfully."
                    }
                }
            }
        }
        
        // --- Stage 3: Manual Approval Gate ---
        stage('3. CTO Approval') {
            steps {
                // Pause the pipeline, waiting for manual input
                timeout(time: 1, unit: 'HOURS') { 
                    input message: 'ACTION REQUIRED: Approve deployment to Production (Docker Host)?',
                          ok: 'Proceed to Deploy',
                          submitter: 'cto' // Only user 'cto' can approve
                }
            }
        } // End Stage 3
        
        // --- Stage 4: Deploy Containers to Docker Host ---
        stage('4. Deploy to Production (Docker Host)') {
            steps {
                echo "INFO: Approval received. Deploying containers to Docker Host..."
                echo "INFO: Stopping and removing old containers (if they exist)..."
            }
        } // End Stage 4
        
    } // End of stages

    // --- Post-build Actions ---
    // Actions to perform after the pipeline finishes
    post { 
        always { // Always run these steps
            echo 'INFO: Pipeline finished execution.'
            // cleanWs() // Option to clean the Jenkins workspace
        }
        success { // Run only on success
            echo ' SUCCESS: Pipeline completed successfully!'
            // Add success notifications (email, Slack, etc.) here
        }
        failure { // Run only on failure
            echo ' FAILED: Pipeline failed!'
            // Add failure notifications here
        }
    } // End of post
    
} // End of pipeline

3. Push Jenkinsfile lên Gitlab

# Stage the new Jenkinsfile
git add Jenkinsfile
# Commit the change
git commit -m "Add Jenkinsfile for Docker host deployment"
# Push to the appropriate branch (e.g., nodejs or main)
git push -u origin 

4. Tạo Repo Registry trên gitlab

Tạo Project -> New Project -> beobeo/location_api để lưu images, Kiểm tra và lấy đường dẫn của registry Project -> Deploy -> Container Registry

Giai đoạn 2: Jenkins (Nơi Build)

Cấu hình Jenkins để nó “biết” về project và cách thực thi pipeline.

1. Đăng nhập lần đầu & Cài đặt cơ bản:

  • Truy cập https://jenkins.diendo.pro.vn
  • Lấy mật khẩu admin ban đầu
# Find the Jenkins container ID and print the initial password
docker exec $(docker ps -qf "name=tonytechlab_jenkins") cat /var/jenkins_home/secrets/initialAdminPassword
  • Hòan thành cài đặt các Plugin cần thiết (Matrix Authorizatioin Strategy, Gitlab, ...)
  • Tạo user cto, dev: Vào Manage Jenkins -> Sercurity -> Manage Users -> Create User. Tạo user cto, dev (Password tùy ý)

2. Tạo Personal Access Token trên Github: (Để Jenkins đọc được code)

  • Vào Github User -> Settings

Developer settings

Persional access token -> Tokens -> Generate new token (classic)

Lưu lại token

3. Tạo Credentials trên Jenkins

  • Vào Jenkins Manage Jenkins -> Security -> Credentials -> (global).
  • Add Credentials:
    • Kind: Username with password
    • Scope: Global (Jenkins, nodes, items, all child items,etc.
    • Username: <username>
    • Password: <Token vừa tạo ở trên>
    • ID: github-credentials

4. Tạo Pipeline Job: (Đây là “Công việc” Jenkins sẽ thực thi)

  • Trang chủ Jenkins -> New Item.
  • Enter an item name: location-api-build (Hoặc tên bạn muốn).
  • Chọn Pipeline -> OK.
  • Tab General: Tích GitHub project

Project url: https://github.com/diendt/pkm_location_api.git

  • Tab Trigger

[x] GitHub hook trigger for GITScm polling

  • Tab Pipeline:
    • Definition: Pipeline script from SCM.
    • SCM: Git.
    • Repository URL: https://github.com/diendt/pkm_location_api.git (URL HTTPS của project).
    • Credentials: Chọn Credential của đã tạo ở trên
    • Branches to build -> Branch Specifier: */main (Hoặc nhánh bạn push Jenkinsfile lên).
    • Script Path: Jenkinsfile.
    • **(Quan trọng)** Nhấn Add bên cạnh Additional Behaviours -> Chọn Wipe out repository & force clone.
  • Nhấn Save.

Giai đoạn 3: Kết nối Webhook (Trigger) 

Bước này để Github tự động “gọi” Jenkins mỗi khi có code mới được push.

1. Tạo Webhook trên GitHub:

  • Trên project của GitHub Settings -> Webhook -> Add webhook
  • Payload URL: https://jenkins.diendo.pro.vn/github-webhook/
  • Content type: application/json
  • Secret:
  • Chọn Just the push event
  • Chọn Active
  • Bấm Add webhook

Giai đoạn 4: Chạy Thử & Kiểm Tra

Bây giờ, hãy thử nghiệm toàn bộ luồng CI/CD và kiểm tra ứng dụng được deploy.

Developer Push Code:

  • Sửa một file bất kỳ trong project 
  • Chạy các lệnh git:
git add .
git commit -m "Test full pipeline with Docker deploy"
git push origin 

Kiểm tra trên github xem đã gừi và nhận thông tin từ jenkins đã thành công chưa.

  • Vào Webhooks -> Edit -> Recent Deliveries

Thấy tick xanh là đã OK

Quan sát Jenkins:

  • Mở https://jenkins.diendo.pro.vn
  • Job location-api-build sẽ tự động chạy.
  • Nhấn vào job đang chạy. Nó sẽ chạy qua Stage 1, 2 và DỪNG LẠI ở Stage 3 “CTO Approval”.

CTO Phê duyệt (CTO Approval):

  • Đăng nhập vào Jenkins bằng user cto.
  • Mở job đang tạm dừng.
  • Di chuột vào stage “CTO Approval”, nhấn nút Proceed.

Hoàn tất Deploy & Kiểm tra Ứng dụng:

  • Pipeline sẽ tiếp tục chạy Stage 4 (Deploy) và báo SUCCESS.
  • Kiểm tra “Console Output” để xem log, đặc biệt là các dòng cuối cùng báo URL truy cập.

Giai đoạn 5: Triển khai lên Kubernetes 

Bước 1: Chuẩn bị file Manifest Kubernetes

Bạn cần tạo các file YAML định nghĩa cách ứng dụng chạy trên K8s (Deployment, Service). Tạo một thư mục k8s trong project corejs.

File: k8s/namespace.yaml (Tùy chọn)

apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: null
  name: pkm-api

File: k8s/registry-secret.yaml (Bắt buộc nếu Registry không public)

K8s cần biết cách login vào GitLab Registry để kéo image.

kubectl create secret docker-registry gitlab-registry-creds \
  --docker-server=registry.diendo.pro.vn \
  --docker-username=root \
  --docker-password=glpat-fQhBFJ54AzG1cg \
  --namespace=pkm-api \
  --dry-run=client -o yaml > k8s/registry-secret.yaml

File registry-secret.yaml sẽ được tạo ra.

apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5kaWVuZG8ucHJvLnZuIjp7InVzZXJuYW1lIjoicm9vdCIsInBhc3N3b3JkIjoiZ2xwYXQtZlFoQkZKNTRBekcxY2dJd202S1QxRzg2TVFwMU9qVUguMDEuMHcxaHZzcWY3IiwiYXV0aCI6ImNtOXZkRHBuYkhCaGRDMW1VV2hDUmtvMU5FRjZSekZqWjBs
kind: Secret
metadata:
  creationTimestamp: null
  name: gitlab-registry-creds
  namespace: pkm-api
type: kubernetes.io/dockerconfigjson

File: k8s/backend-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: location-api
  namespace: pkm-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: location-api
  template:
    metadata:
      labels:
        app: location-api
    spec:
      imagePullSecrets:
        - name: location-api-secret
      containers:
        - name: pkm-location-api
          image: registry.perfectkey.vn/pkm/api-locations:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          env:
            - name: ASPNETCORE_ENVIRONMENT
              value: Development  
          envFrom:
            - configMapRef:
                name: location-api-config

File: k8s/backend-service.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: location-api-nodeport
  namespace: pkm-api
spec:
  type: NodePort
  selector:
    app: location-api
  ports:
    - port: 80
      targetPort: 80

Bước 2: Tạo K8s Credentials trong Jenkins

Jenkins cần quyền để kết nối và deploy lên cụm K8s.

Cách 1 (Username/Password – Đơn giản nhưng kém an toàn):

  • Vào Jenkins -> Credentials -> (global) -> Add Credentials.
  • Kind: Username with password.
  • Usernamedevops
  • PasswordPassword của user
  • IDk8s-user-creds

Cách 2 (Kubeconfig – Khuyến nghị):

  • SSH vào k8s-master-1.
  • Copy nội dung file ~/.kube/config.
  • Cần phải cài thêm Kubernetes, Kubernetes CLI Plugin sau đó add thêm Credentials
  • Vào Jenkins -> Credentials -> (global) -> Add Credentials.
  • KindSecret file.
  • Uploadfile config của kụm k8s lên Jenkins
  • IDk8s-config-file

Lưu ý: Cách dùng Kubeconfig an toàn và linh hoạt hơn. Jenkins Controller cần mount volume /opt/devops/kube/.kube:/root/.kube (như trong docker-compose.yml) để kubectl hoạt động.

Bước 4: Cập nhật Jenkinsfile (Thêm Stage Deploy K8s)

Sửa lại Jenkinsfile trong project corejs.

// Jenkinsfile - Simple Deploy to Docker Host (Cách 1: All-in-One)
// Builds production images and runs them on the host Docker daemon.
pipeline {
    // Run on the Jenkins controller itself, requires docker-cli installed via Dockerfile
    agent any 

    environment {
        // --- Application & Image Naming ---
        REGISTRY_URL        = "registry.diendo.pro.vn"
        REGISTRY_CREDENTIAL = "docker-registry-creds"  // ID trùng với Jenkins
        REGISTRY_PATH       = "beobeo/location_api"
        // --- Application & Image Naming ---
        APP_NAME            = 'location-api' // Base name for images and containers
        BACKEND_IMAGE       = "${REGISTRY_PATH}/${APP_NAME}-backend:latest"  // Image name for backend
        BACKEND_CONTAINER   = "${APP_NAME}-app"  // Fixed container name for backend
        
        K8S_NAMESPACE     = "pkm-api"
        K8S_CREDENTIAL_ID = 'k8s-config-file' // ID credential kubeconfig
    }

    stages {
        // --- Stage 1: Get latest code ---
        stage('1. Checkout Code') {
            steps {
                // Use Jenkins built-in SCM checkout step
                checkout scm 
                echo "SUCCESS: Code checked out from GitLab."
            }
        }
        
        // --- Stage 2: Build Production Docker Images ---
        stage('2. Build & Push Docker Images') {
            parallel {
                stage('Backend') {
                    steps {
                        script {
                            withCredentials([usernamePassword(
                                credentialsId: env.REGISTRY_CREDENTIAL, 
                                usernameVariable: 'REG_USER', 
                                passwordVariable: 'REG_PASS')]
                                ) {
                                sh '''
                                    echo " Building backend image: ${BACKEND_IMAGE}"
                                    docker build -t ${BACKEND_IMAGE} .
                                    docker tag ${BACKEND_IMAGE} ${REGISTRY_URL}/${BACKEND_IMAGE}
                                    echo "${REG_PASS}" | docker login ${REGISTRY_URL} -u "${REG_USER}" --password-stdin
                                    docker push ${REGISTRY_URL}/${BACKEND_IMAGE}
                                    docker logout ${REGISTRY_URL}
                                '''
                            }
                        }
                        echo " Backend image pushed successfully."
                    }
                }
            }
        }
        
        // --- Stage 3: Manual Approval Gate ---
        stage('3. CTO Approval') {
            steps {
                // Pause the pipeline, waiting for manual input
                timeout(time: 1, unit: 'HOURS') { 
                    input message: 'ACTION REQUIRED: Approve deployment to Production (Docker Host)?',
                          ok: 'Proceed to Deploy',
                          submitter: 'cto' // Only user 'cto' can approve
                }
            }
        } // End Stage 3
        
        // --- Stage 4: Deploy Containers to Docker Host ---
        stage('4. Deploy to Production (Docker Host)') {
            steps {
                echo "INFO: Approval received. Deploying containers to Docker Host..."
                script {
                    // Sử dụng Kubeconfig credential đã tạo
                    withKubeConfig(credentialsId: env.K8S_CREDENTIAL_ID) {
                    
                        echo "INFO: Applying K8s manifests..."
                        // Chạy kubectl apply cho các file YAML (trong thư mục k8s của repo)
                        sh """
                        kubectl config view --minify
                        kubectl get nodes
                        kubectl apply -f k8s/namespace.yaml || true 
                        kubectl apply -f k8s/registry-secret.yaml -n ${env.K8S_NAMESPACE} || true
                        kubectl apply -f k8s/backend-deployment.yaml -n ${env.K8S_NAMESPACE}
                        kubectl apply -f k8s/backend-service.yaml -n ${env.K8S_NAMESPACE}
                        """

                        echo "INFO: Waiting for deployments to roll out..."
                        // Chờ deployment hoàn tất
                        sh "kubectl rollout status deployment/location-api -n ${env.K8S_NAMESPACE}"

                        // Lấy NodePort của service frontend
                        def nodePort = sh(
                            script: "kubectl get service location-api-nodeport -n ${env.K8S_NAMESPACE} -o=jsonpath='{.spec.ports[0].nodePort}'",
                            returnStdout: true
                        ).trim()
                        
                        echo "----------------------------------------------------"
                        echo " KUBERNETES DEPLOYMENT COMPLETE!"
                        echo "   Access API at: http://:${nodePort}"
                        echo "----------------------------------------------------"
                        echo "(Replace  with the IP of any K8s node, e.g., 10.100.1.21)"
                    } // end withKubeconfig
                } // end script

            }
        } // End Stage 4
        
    } // End of stages

    // --- Post-build Actions ---
    // Actions to perform after the pipeline finishes
    post { 
        always { // Always run these steps
            echo 'INFO: Pipeline finished execution.'
            // cleanWs() // Option to clean the Jenkins workspace
        }
        success { // Run only on success
            echo ' SUCCESS: Pipeline completed successfully!'
            // Add success notifications (email, Slack, etc.) here
        }
        failure { // Run only on failure
            echo ' FAILED: Pipeline failed!'
            // Add failure notifications here
        }
    } // End of post
    
} // End of pipeline

Push Jenkinsfile mới và thư mục k8s lên GitLab.

Bước 5: Chạy Pipeline và Truy cập Ứng dụng

Trigger pipeline (push code hoặc Build Now).
Phê duyệt ở Stage 3.
Stage 4 sẽ chạy kubectl apply.

Giai đoạn 6: Public hệ thống qua Zero trust của Cloudflare

Public hệ thống

Vảo Network -> Tunnel -> Configure

Chọn Published applcation routes

Add a published application route

Nhập các thông tin cần thiết và SAVE

published tên miền
– https://api-location.diendo.pro.vn
Kiểm tra thì vào link: https://api-location.diendo.pro.vn/swagger/index.html

Liên hệ