#การรัน CI/CD ด้วย Jenkins

Jenkins เป็น Automation Server แบบโอเพนซอร์สที่นิยมใช้สร้างกระบวนการ CI/CD (Continuous Integration / Continuous Delivery หรือ Deployment) โดยสามารถเชื่อมต่อกับ Git repository เพื่อให้ระบบทำงานอัตโนมัติเมื่อมีการ push code หรือเปิด Pull Request เช่น

Developer
   |
   | git push
   v
GitHub / GitLab
   |
   | Webhook
   v
Jenkins Pipeline
   |
   +--> Checkout
   +--> Install Dependencies
   +--> Unit Test
   +--> Build
   +--> Code Quality / Security Scan
   +--> Build Docker Image
   +--> Push Image
   +--> Deploy
   v
Application Server / Kubernetes / Cloud

info=cicd-jenkins-jenkinsfile-deploy

แนวทางที่เหมาะกับโครงการสมัยใหม่คือเก็บ Pipeline ไว้ในไฟล์ Jenkinsfile ภายใน source repository หรือแนวคิด Pipeline as Code ซึ่งช่วยให้แก้ไข pipeline ผ่าน Git, ทำ code review และตรวจสอบประวัติการเปลี่ยนแปลงได้


#1. CI/CD คืออะไร

#Continuous Integration — CI

CI คือการรวมโค้ดจากนักพัฒนาหลายคนเข้าสู่ repository อย่างสม่ำเสมอ และให้ระบบตรวจสอบโดยอัตโนมัติ เช่น

  • Checkout source code
  • ติดตั้ง dependencies
  • Compile หรือ Build
  • Unit Test
  • Integration Test
  • Lint
  • Code Quality Scan
  • Security Scan

ตัวอย่าง flow:

git push
   |
   v
Jenkins
   |
   +--> Build
   +--> Test
   +--> Scan
   |
   +--> PASS
   |      |
   |      v
   |   Create Artifact
   |
   +--> FAIL
          |
          v
       Stop Pipeline

#Continuous Delivery / Continuous Deployment — CD

หลัง CI ผ่าน Jenkins สามารถดำเนินการต่อ เช่น

  • สร้าง Docker image
  • Push image ไป Docker Hub / GHCR / ECR
  • Deploy ไป Development หรือ Staging
  • รอ Manual Approval ก่อน Production
  • Deploy ไป Production อัตโนมัติ
  • Deploy ผ่าน Kubernetes, Helm หรือ GitOps

#2. องค์ประกอบสำคัญของ Jenkins

#Jenkins Controller

Controller ทำหน้าที่จัดการ configuration, job, pipeline, credentials, plugins และ scheduling

#Jenkins Agent

Agent คือเครื่องหรือ container ที่รับงานไปประมวลผล เช่น

  • Linux VM
  • Docker container
  • Windows machine
  • Kubernetes Pod
  • Cloud VM

ระบบจริงควรแยกงาน build/test ออกจาก controller ให้ agent เป็นผู้รัน workload

#Job

งานที่ Jenkins ต้องรัน เช่น Build, Test หรือ Deploy

#Pipeline

ลำดับขั้นตอน CI/CD ที่ประกอบด้วยหลาย stage

#Jenkinsfile

ไฟล์ source code สำหรับกำหนด Pipeline เช่น

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                echo 'Building...'
            }
        }

        stage('Test') {
            steps {
                echo 'Testing...'
            }
        }

        stage('Deploy') {
            steps {
                echo 'Deploying...'
            }
        }
    }
}

#3. Declarative Pipeline และ Scripted Pipeline

Jenkins รองรับ Pipeline หลัก 2 รูปแบบ

#Declarative Pipeline

เหมาะกับโครงการส่วนใหญ่ เพราะโครงสร้างชัดเจนและอ่านง่าย

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'npm run build'
            }
        }
    }
}

#Scripted Pipeline

ยืดหยุ่นสูงและใช้ Groovy มากกว่า เหมาะกับ workflow ที่ซับซ้อนมาก

node {
    stage('Build') {
        sh 'npm run build'
    }
}

บทความนี้ใช้ Declarative Pipeline เป็นหลัก


#4. ติดตั้ง Jenkins ด้วย Docker

Jenkins มี official image คือ jenkins/jenkins และสามารถใช้ LTS image สำหรับการใช้งานทั่วไปได้

สร้าง volume:

docker volume create jenkins_home

รัน Jenkins:

docker run -d \
  --name jenkins \
  --restart unless-stopped \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts-jdk21

เปิดเว็บ:

http://localhost:8080

ดู initial admin password:

docker exec jenkins \
  cat /var/jenkins_home/secrets/initialAdminPassword

หมายเหตุ: หาก Pipeline ต้อง build Docker image ควรใช้ Jenkins agent ที่ติดตั้ง Docker/BuildKit หรือสร้าง Jenkins image/agent ที่มี Docker CLI ตามสถาปัตยกรรมที่เหมาะสม ไม่ควรสมมติว่า official Jenkins image มี Docker CLI มาให้แล้ว


#5. Plugins ที่มักใช้กับ CI/CD

ไปที่

Manage Jenkins
  -> Plugins

ตัวอย่าง plugin ที่พบบ่อย:

Plugin การใช้งาน
Pipeline สร้าง Pipeline
Git Checkout repository
GitHub เชื่อมต่อ GitHub
Credentials Binding ใช้งาน secret ใน Pipeline
Docker Pipeline ทำงานร่วมกับ Docker
JUnit แสดงผล test report
SSH Agent ใช้ SSH key ในขั้นตอน deploy

ติดตั้งเฉพาะ plugin ที่จำเป็น และอัปเดต Jenkins/plugins อย่างสม่ำเสมอเพื่อลด attack surface


#6. สร้างโปรเจกต์ตัวอย่าง

ตัวอย่างโครงสร้าง Node.js application:

jenkins-demo/
|
+-- src/
|   +-- app.js
|
+-- test/
|   +-- app.test.js
|
+-- package.json
+-- package-lock.json
+-- Dockerfile
+-- Jenkinsfile

ตัวอย่าง package.json

{
  "name": "jenkins-demo",
  "version": "1.0.0",
  "scripts": {
    "test": "jest --ci --coverage",
    "build": "echo Build completed",
    "start": "node src/app.js"
  },
  "dependencies": {
    "express": "^5.0.0"
  },
  "devDependencies": {
    "jest": "^30.0.0"
  }
}

#7. Dockerfile

FROM node:22-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY src ./src

EXPOSE 3000

CMD ["npm", "start"]

ทดลอง build:

docker build -t jenkins-demo:local .

#8. Jenkinsfile สำหรับ CI ขั้นพื้นฐาน

pipeline {
    agent any

    options {
        timestamps()
        disableConcurrentBuilds()
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
        }

        stage('Build') {
            steps {
                sh 'npm run build'
            }
        }
    }

    post {
        success {
            echo 'CI passed'
        }

        failure {
            echo 'CI failed'
        }

        always {
            archiveArtifacts artifacts: 'coverage/**/*', allowEmptyArchive: true
        }
    }
}

Pipeline จะทำงานตามลำดับ

Checkout
   |
   v
Install
   |
   v
Test
   |
   v
Build

หาก stage ใดล้มเหลว stage หลังจากนั้นจะไม่ดำเนินการตาม flow ปกติ


#9. เพิ่ม Docker Build

เมื่อ Jenkins agent มี Docker CLI และเข้าถึง Docker daemon หรือ build service ที่กำหนดไว้แล้ว สามารถเพิ่ม stage:

stage('Docker Build') {
    steps {
        sh 'docker build -t myapp:${BUILD_NUMBER} .'
    }
}

${BUILD_NUMBER} เป็น environment variable ที่ Jenkins สร้างให้กับ build

ตัวอย่าง image:

myapp:1
myapp:2
myapp:3

ช่วยหลีกเลี่ยงการใช้ latest เป็นตัวระบุ artifact เพียงอย่างเดียว


#10. จัดการ Credentials อย่างถูกต้อง

ไม่ควรเขียน password หรือ token ลงใน Jenkinsfile

ตัวอย่างที่ไม่ควรทำ:

environment {
    PASSWORD = '123456'
}

ควรบันทึก secret ที่

Manage Jenkins
  -> Credentials

สมมติสร้าง credential ID:

dockerhub-credentials

จากนั้นเรียกใช้ผ่าน Jenkins credentials binding

environment {
    DOCKER_CREDS = credentials('dockerhub-credentials')
}

หากเป็น Username/Password credential จะมีตัวแปรที่ Jenkins จัดเตรียมให้ เช่น

DOCKER_CREDS_USR
DOCKER_CREDS_PSW

ใช้งาน:

stage('Docker Login') {
    steps {
        sh '''
          echo "$DOCKER_CREDS_PSW" | \
          docker login -u "$DOCKER_CREDS_USR" --password-stdin
        '''
    }
}

ควรจำกัด scope ของ credentials และสิทธิ์ของ token ให้ต่ำที่สุดเท่าที่งานจำเป็นต้องใช้


#11. Push Docker Image

กำหนด environment:

environment {
    IMAGE_NAME = 'username/jenkins-demo'
    IMAGE_TAG  = "${BUILD_NUMBER}"
}

Build:

stage('Docker Build') {
    steps {
        sh 'docker build -t $IMAGE_NAME:$IMAGE_TAG .'
    }
}

Push:

stage('Docker Push') {
    steps {
        sh 'docker push $IMAGE_NAME:$IMAGE_TAG'
    }
}

Pipeline:

Code
 |
 v
Test
 |
 v
Build
 |
 v
Docker Image
 |
 v
Registry

#12. Deploy ด้วย Docker Compose

สมมติ deployment server มี compose.yaml

services:
  web:
    image: username/jenkins-demo:${IMAGE_TAG}
    ports:
      - "80:3000"
    restart: unless-stopped

Jenkins สามารถ SSH ไปที่ deployment server แล้วสั่ง update service

stage('Deploy') {
    steps {
        sshagent(credentials: ['deploy-server-key']) {
            sh '''
              ssh -o StrictHostKeyChecking=yes deploy@server.example.com \
                "cd /opt/myapp && IMAGE_TAG=${BUILD_NUMBER} docker compose pull && IMAGE_TAG=${BUILD_NUMBER} docker compose up -d"
            '''
        }
    }
}

ในระบบจริงควรจัดการ known_hosts ล่วงหน้าแทนการปิด host key verification


#13. Jenkinsfile แบบ CI/CD ครบ Flow

pipeline {
    agent any

    options {
        timestamps()
        disableConcurrentBuilds()
    }

    environment {
        IMAGE_NAME = 'username/jenkins-demo'
        IMAGE_TAG  = "${BUILD_NUMBER}"
        DOCKER_CREDS = credentials('dockerhub-credentials')
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Unit Test') {
            steps {
                sh 'npm test'
            }
        }

        stage('Build') {
            steps {
                sh 'npm run build'
            }
        }

        stage('Docker Build') {
            steps {
                sh 'docker build -t $IMAGE_NAME:$IMAGE_TAG .'
            }
        }

        stage('Docker Push') {
            steps {
                sh '''
                  echo "$DOCKER_CREDS_PSW" | \
                  docker login -u "$DOCKER_CREDS_USR" --password-stdin
                  docker push "$IMAGE_NAME:$IMAGE_TAG"
                  docker logout
                '''
            }
        }

        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sshagent(credentials: ['deploy-server-key']) {
                    sh '''
                      ssh deploy@server.example.com \
                        "cd /opt/myapp && IMAGE_TAG=${BUILD_NUMBER} docker compose pull && IMAGE_TAG=${BUILD_NUMBER} docker compose up -d"
                    '''
                }
            }
        }
    }

    post {
        success {
            echo "Deployment completed: ${IMAGE_NAME}:${IMAGE_TAG}"
        }

        failure {
            echo "Pipeline failed: ${BUILD_TAG}"
        }

        always {
            cleanWs()
        }
    }
}

cleanWs() ต้องมี Workspace Cleanup plugin หากไม่ติดตั้งสามารถตัดส่วนนี้ออกหรือใช้คำสั่ง cleanup แบบอื่น


#14. เชื่อม Jenkins กับ GitHub

แนวทางทั่วไปคือให้ GitHub แจ้ง Jenkins ผ่าน Webhook เมื่อมีการ push

Developer
   |
   | git push
   v
GitHub
   |
   | Webhook
   v
Jenkins
   |
   v
Pipeline

บน GitHub:

Repository
  -> Settings
  -> Webhooks
  -> Add webhook

Payload URL โดยทั่วไปของ Jenkins GitHub plugin:

https://jenkins.example.com/github-webhook/

Content type:

application/json

จากนั้นตั้ง Jenkins Job ให้รับ webhook trigger ตาม plugin และ job type ที่ใช้งาน

สำหรับ Jenkins ที่เปิดสู่ Internet ควรใช้ HTTPS, reverse proxy, authentication/authorization ที่เหมาะสม และไม่เปิด administrative endpoint โดยไม่จำเป็น


#15. Multibranch Pipeline

หากมีหลาย branch เช่น

main
feature/login
feature/payment
fix/security

ควรพิจารณา Multibranch Pipeline เพราะ Jenkins สามารถค้นหา branch ที่มี Jenkinsfile และสร้าง pipeline ของแต่ละ branch ได้

ตัวอย่าง:

Repository
 |
 +-- main
 |    +-- Jenkinsfile
 |
 +-- feature/login
 |    +-- Jenkinsfile
 |
 +-- feature/payment
      +-- Jenkinsfile

เหมาะกับ workflow แบบ Pull Request และ branch-based development


#16. เพิ่ม Code Quality ด้วย SonarQube

Flow สามารถขยายเป็น

Checkout
   |
   v
Install
   |
   v
Test
   |
   v
SonarQube
   |
   v
Build
   |
   v
Docker
   |
   v
Deploy

ตัวอย่างแนวคิด:

stage('Code Quality') {
    steps {
        withSonarQubeEnv('sonarqube') {
            sh 'sonar-scanner'
        }
    }
}

สามารถใช้ Quality Gate เป็นเงื่อนไขก่อนสร้าง artifact หรือ deploy ต่อ


#17. Deploy ไป Kubernetes

Jenkins สามารถ deploy ไป Kubernetes ด้วย kubectl หรือ Helm เช่น

stage('Deploy Kubernetes') {
    steps {
        sh '''
          kubectl set image deployment/web \
            web=$IMAGE_NAME:$IMAGE_TAG

          kubectl rollout status deployment/web
        '''
    }
}

หรือ Helm:

stage('Deploy Helm') {
    steps {
        sh '''
          helm upgrade --install myapp ./helm/myapp \
            --set image.repository=$IMAGE_NAME \
            --set image.tag=$IMAGE_TAG
        '''
    }
}

#18. Jenkins + GitOps

ในสถาปัตยกรรม GitOps ไม่จำเป็นต้องให้ Jenkins เข้าถึง Kubernetes cluster โดยตรง

Jenkins ทำหน้าที่

Build
 |
 v
Test
 |
 v
Build Image
 |
 v
Push Registry
 |
 v
Update Deployment Git Repository

จากนั้น Argo CD หรือ Flux ทำหน้าที่ sync configuration ไป Kubernetes

Developer
   |
   v
Application Git
   |
   v
Jenkins
   |
   +--> Test
   +--> Build Image
   +--> Push Registry
   +--> Update image tag
             |
             v
         GitOps Repo
             |
             v
          Argo CD
             |
             v
         Kubernetes

แนวทางนี้ช่วยแยก CI และ CD ชัดเจน และทำให้ desired state ของ deployment ถูกบันทึกใน Git


#19. Manual Approval ก่อน Production

หากต้องการ Continuous Delivery แทนการ deploy production ทันที สามารถเพิ่ม approval

stage('Approval') {
    when {
        branch 'main'
    }
    steps {
        input message: 'Deploy to production?', ok: 'Deploy'
    }
}

จากนั้นค่อย deploy production

Build
 |
 v
Test
 |
 v
Staging
 |
 v
Approval
 |
 v
Production

#20. Testing Strategy ใน Pipeline

Pipeline ที่ดีควรแยก test ตามระดับ

Unit Test
   |
   v
Integration Test
   |
   v
API Test
   |
   v
UI / E2E Test
   |
   v
Security / Quality Gate

ตัวอย่าง:

stage('Unit Test') {
    steps {
        sh 'npm run test:unit'
    }
}

stage('API Test') {
    steps {
        sh 'npm run test:api'
    }
}

stage('E2E Test') {
    steps {
        sh 'npm run test:e2e'
    }
}

#21. Parallel Testing

หาก test หลายชุดไม่ขึ้นต่อกัน สามารถรันพร้อมกัน

stage('Tests') {
    parallel {
        stage('Unit') {
            steps {
                sh 'npm run test:unit'
            }
        }

        stage('API') {
            steps {
                sh 'npm run test:api'
            }
        }

        stage('E2E') {
            steps {
                sh 'npm run test:e2e'
            }
        }
    }
}

ช่วยลดเวลารวมของ pipeline เมื่อ agent/compute เพียงพอ


#22. Test Report

หากใช้ JUnit XML report:

post {
    always {
        junit 'reports/**/*.xml'
    }
}

ทำให้ Jenkins แสดงแนวโน้มผลการทดสอบและรายละเอียด test ที่ fail ได้จากหน้า build


#23. Environment แยก Dev / Staging / Production

ตัวอย่าง:

feature/*
   |
   v
CI only

main
   |
   v
Dev
   |
   v
Staging
   |
   v
Approval
   |
   v
Production

สามารถใช้ when เพื่อควบคุม stage

stage('Deploy Production') {
    when {
        branch 'main'
    }

    steps {
        sh './deploy-production.sh'
    }
}

#24. Rollback

ทุก deployment ควรสามารถย้อนกลับไป artifact/image รุ่นก่อนหน้าได้

ตัวอย่าง Docker image:

myapp:101
myapp:102
myapp:103

หาก 103 มีปัญหา:

docker pull username/myapp:102

หรือ Kubernetes:

kubectl rollout undo deployment/web

การใช้ immutable/versioned artifacts ทำให้ rollback ตรวจสอบได้ง่ายกว่าการอ้าง latest อย่างเดียว


#25. Pipeline Architecture ที่แนะนำ

                    GitHub
                       |
                       | Webhook
                       v
                  +---------+
                  | Jenkins |
                  +---------+
                       |
          +------------+-------------+
          |            |             |
          v            v             v
        Test        SonarQube      Build
                                      |
                                      v
                                 Docker Image
                                      |
                                      v
                                  Registry
                                      |
                         +------------+------------+
                         |                         |
                         v                         v
                      Staging                  GitOps Repo
                                                   |
                                                   v
                                                Argo CD
                                                   |
                                                   v
                                              Kubernetes

#26. Best Practices

#1. เก็บ Jenkinsfile ใน Git

project/
  +-- src/
  +-- tests/
  +-- Dockerfile
  +-- Jenkinsfile

ทำให้ pipeline versioning ไปพร้อม source code

#2. ห้ามฝัง Secret ใน Repository

ใช้ Jenkins Credentials หรือ external secret manager

#3. แยก Controller กับ Agent

ไม่ควรรัน workload จำนวนมากบน controller

#4. ใช้ Immutable Artifact

เช่น

myapp:git-a1b2c3d

หรือ

myapp:build-108

#5. Fail Fast

จัด stage ให้ test ราคาถูกและเร็วอยู่ก่อนขั้นตอนที่แพง เช่น Docker build หรือ deploy

Lint
 |
 v
Unit Test
 |
 v
Integration Test
 |
 v
Build Image
 |
 v
Deploy

#6. ใช้ Least Privilege

token, SSH key และ service account ควรมีสิทธิ์เท่าที่จำเป็น

#7. สำรอง Jenkins Configuration

ข้อมูลสำคัญมักอยู่ใน JENKINS_HOME รวมถึง job configuration และข้อมูล Jenkins อื่น ๆ ควรมี backup/restore procedure ที่ทดสอบแล้ว

#8. ดูแล Plugin อย่างระมัดระวัง

ติดตั้งเฉพาะ plugin ที่ต้องใช้ ตรวจสอบ security advisory และทดสอบการอัปเดตก่อน production

#9. จำกัด Production Deployment

ใช้ branch protection, approval หรือ role-based access control ตามความเสี่ยงของระบบ

#10. แยก CI กับ Deployment Logic

เก็บ logic ที่ซับซ้อนใน script, Makefile หรือ shared library แทนการทำ Jenkinsfile ยาวเกินไป


#27. Checklist สำหรับ Jenkins CI/CD

[ ] Source Code อยู่ใน Git
[ ] มี Jenkinsfile
[ ] Jenkins เชื่อมกับ Git repository
[ ] มี Webhook หรือ SCM trigger
[ ] Install dependencies แบบ reproducible
[ ] มี Unit Test
[ ] มี Integration/API/E2E Test ตามความเหมาะสม
[ ] เก็บ Test Report
[ ] มี Code Quality / Security Scan
[ ] Build artifact หรือ container image
[ ] ใช้ versioned image/tag
[ ] เก็บ Secret ใน Credentials
[ ] Push artifact ไป Registry
[ ] Deploy Dev/Staging
[ ] Production มี policy/approval ที่เหมาะสม
[ ] มี Health Check
[ ] มี Rollback Strategy
[ ] มี Logging และ Monitoring
[ ] สำรอง Jenkins configuration

#28. สรุป

Jenkins สามารถเป็นศูนย์กลางของ CI/CD pipeline ตั้งแต่รับ source code ไปจนถึง build, test, scan, package และ deploy โดยหัวใจสำคัญคือการออกแบบ Pipeline as Code ผ่าน Jenkinsfile และแยก credentials ออกจาก source code

Flow พื้นฐาน:

Git Push
   |
   v
Jenkins
   |
   v
Checkout
   |
   v
Install
   |
   v
Test
   |
   v
Build
   |
   v
Docker Build
   |
   v
Push Registry
   |
   v
Deploy

สำหรับระบบขนาดใหญ่ สามารถพัฒนาต่อเป็น

Jenkins
  |
  +-- CI
  |    +-- Test
  |    +-- Quality Gate
  |    +-- Security Scan
  |    +-- Build Image
  |
  +-- Artifact Registry
  |
  +-- GitOps Repository
       |
       v
     Argo CD
       |
       v
   Kubernetes

แนวทางนี้เหมาะกับ DevOps workflow สมัยใหม่ เพราะแยกขั้นตอนตรวจสอบโค้ด การสร้าง artifact และการ deploy ออกจากกันอย่างชัดเจน


#แหล่งอ้างอิง