CI/CD กับ AI: เขียน Pipeline ให้ทำงานจริง

Jenkinsfile เป็นไฟล์ประเภทที่ AI เขียนได้คล่องมาก แต่ปัญหาคือมันมักดูถูกต้องจนกว่าจะรันจริง: เรียก step จาก plugin ที่ Jenkins ของเราไม่ได้ติดตั้ง, ใส่ || true ให้ test ที่ fail ผ่านไปเงียบ ๆ, หรือเอา password ไปต่อในสตริงจนโผล่ใน console log บทความนี้จะวางโครง pipeline ที่ครบสี่ขั้น lint, test, audit, deploy พร้อม Jenkinsfile แบบ declarative ที่สั้นพอจะอ่านจบ วิธีจัดการ secrets ใน CI ให้ถูกต้อง และวิธีทดสอบ pipeline ก่อนปล่อยให้ทั้งทีมใช้
ออกแบบขั้นตอนก่อนให้ AI เขียน
อย่าเริ่มจาก "เขียน Jenkinsfile ให้หน่อย" แต่เริ่มจากตกลงกันว่าแต่ละขั้นต้องทำอะไร และอะไรคือเงื่อนไขที่ทำให้ build ล้ม
| ขั้น | ทำอะไร | ล้มเมื่อ |
|---|---|---|
| Install | composer install ตาม lock file | lock file ไม่ตรงกับ composer.json หรือติดตั้งไม่สำเร็จ |
| Lint | php -l ทุกไฟล์, coding standard, static analysis | syntax error หรือ static analysis พบปัญหาตามระดับที่ตั้ง |
| Test | unit test และ functional test | มี test fail แม้แต่ข้อเดียว |
| Audit | composer audit, npm audit | พบช่องโหว่ตามระดับที่ทีมกำหนด |
| Deploy | build image, push, สั่ง deploy | เฉพาะ branch main และทุกขั้นก่อนหน้าผ่าน |
เมื่อมีตารางนี้ การเขียน prompt ก็ตรงไปตรงมา และใช้ตารางเดียวกันเป็นเกณฑ์ review ผลลัพธ์ได้ด้วย
เขียน Jenkinsfile แบบ declarative สำหรับโปรเจกต์ Yii2 (PHP 8, Codeception)
ขั้นตอนและเงื่อนไขตามตารางนี้ (แปะตาราง)
plugin ที่ติดตั้งแล้ว: Pipeline, Git, Credentials Binding, JUnit
ห้ามใช้ step จาก plugin อื่น
credentials ที่มี: registry-cred (username/password), deploy-ssh (SSH private key)
ห้ามใส่ secret ใน Groovy string แบบ double quote, ห้ามใช้ || true กับขั้น test และ audit
deploy เฉพาะ branch main มี timeout และห้าม build ซ้อนกัน
การบอกรายชื่อ plugin ที่มีจริงช่วยกันปัญหาใหญ่ที่สุดของ Jenkinsfile จาก AI คือการเรียก step ที่ Jenkins ของเราไม่รู้จัก
ตัวอย่าง Jenkinsfile
pipeline {
agent any
options {
timeout(time: 20, unit: 'MINUTES')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '30'))
}
environment {
IMAGE = "registry.example.com/myapp:${env.BUILD_NUMBER}"
}
stages {
stage('Install') {
steps {
sh 'composer validate --strict'
sh 'composer install --no-interaction --prefer-dist --no-progress'
}
}
stage('Lint') {
steps {
sh '''
find . -path ./vendor -prune -o -name '*.php' -print0 \
| xargs -0 -n1 php -l > /dev/null
'''
sh 'vendor/bin/phpstan analyse --no-progress'
}
}
stage('Test') {
steps {
sh 'vendor/bin/codecept run unit --xml'
}
post {
always {
junit allowEmptyResults: true, testResults: 'tests/_output/*.xml'
}
}
}
stage('Audit') {
steps {
sh 'composer audit --no-dev'
sh 'npm ci --ignore-scripts && npm audit --omit=dev --audit-level=high'
}
}
stage('Deploy') {
when { branch 'main' }
environment {
REGISTRY = credentials('registry-cred')
}
steps {
sh '''
echo "$REGISTRY_PSW" | docker login -u "$REGISTRY_USR" --password-stdin registry.example.com
docker build -t "$IMAGE" .
docker push "$IMAGE"
docker logout registry.example.com
'''
withCredentials([sshUserPrivateKey(credentialsId: 'deploy-ssh',
keyFileVariable: 'SSH_KEY', usernameVariable: 'SSH_USER')]) {
sh 'ssh -i "$SSH_KEY" -o BatchMode=yes "$SSH_USER@app.example.com" "./deploy.sh $IMAGE"'
}
}
}
}
post {
failure {
echo 'Build failed: ดูรายละเอียดใน console log ของแต่ละ stage'
}
}
}
สิ่งที่ควรสังเกต:
- ขั้น Install ใช้
composer installไม่ใช่updateเพื่อให้ CI ใช้เวอร์ชันเดียวกับที่ทดสอบในเครื่อง และcomposer validate --strictจับกรณี lock file ไม่ตรง xargsจะคืนค่า error ถ้ามีไฟล์ใดไฟล์หนึ่ง lint ไม่ผ่าน ทำให้ stage ล้มตามที่ต้องการwhen { branch 'main' }ใช้ได้กับ Multibranch Pipeline ถ้าใช้ job แบบอื่นต้องตรวจเงื่อนไข branch ด้วยวิธีที่เหมาะกับ job นั้น- เครื่อง agent ต้องมี PHP, Composer, Node และ Docker ตามที่ใช้ และ host ปลายทางต้องอยู่ใน
known_hostsของ agent อยู่แล้ว ห้ามแก้ด้วยการปิด host key checking npm ci --ignore-scriptsในขั้น Audit ใช้เพื่อตรวจเท่านั้น ถ้าโปรเจกต์ต้อง build asset ให้แยกเป็นอีก stage
Secrets ใน CI: ทำให้ถูกตั้งแต่ต้น
Secret ทุกตัวควรเก็บใน Jenkins Credentials แล้วดึงมาใช้ผ่าน credentials() หรือ withCredentials เท่านั้น Jenkins จะ mask ค่าเหล่านี้ใน log ให้ แต่การ mask มีข้อจำกัด จึงต้องเขียนให้ถูกด้วย
- ใช้ single quote ใน
shเมื่อมี secret เช่นsh 'echo "$REGISTRY_PSW" | docker login ...'ตัวแปรจะถูกขยายโดย shell บน agent ถ้าใช้ double quote ของ Groovy ค่าจะถูกแทนลงในสคริปต์ก่อนส่งไปรัน ซึ่ง Jenkins เองก็เตือนว่าไม่ปลอดภัย - ส่งรหัสผ่านผ่าน stdin เช่น
--password-stdinแทนการใส่เป็น argument ที่อาจเห็นได้จาก process list - ห้าม
set -xหรือechoค่า secret เพื่อ debug และระวังคำสั่งที่พิมพ์ config ทั้งหมดออกมา - จำกัด scope ใช้ credentials ใน stage ที่ต้องใช้เท่านั้น เหมือนตัวอย่างที่ประกาศ
REGISTRYใน environment ของ stage Deploy ไม่ใช่ระดับ pipeline - ระวัง pull request จาก fork อย่าให้ build ของ PR จากภายนอกเข้าถึง credentials สำหรับ deploy
และที่สำคัญ อย่าแปะค่า secret จริงลงใน prompt เพื่อให้ AI ช่วยแก้ pipeline ให้ใช้ชื่อ credential ID แทนเสมอ
รูปแบบอันตรายที่ต้องจับให้ได้ตอน review
เมื่อ pipeline ล้ม AI มักเสนอทางแก้ที่ทำให้ "เขียว" เร็วที่สุด ซึ่งไม่ได้แปลว่าถูก รูปแบบที่ควรปฏิเสธทันที:
|| trueหรือcatchErrorครอบขั้น test และ audit ทำให้ build ผ่านทั้งที่มีปัญหาcomposer install --ignore-platform-reqsซ่อนปัญหาว่า agent ไม่มี extension ที่ production ใช้StrictHostKeyChecking=noทำให้เสี่ยงต่อการ deploy ไปยังเครื่องปลอมchmod 777หรือsudoเพื่อแก้ปัญหาสิทธิ์ไฟล์บน agent- ลดระดับ audit จนไม่มีอะไรถูกจับ หรือลบ static analysis ออกเพราะ "error เยอะ"
ถ้าจำเป็นต้องยกเว้นช่องโหว่หรือกฎบางข้อชั่วคราว ให้ระบุเฉพาะจุด พร้อมเหตุผลและวันที่จะกลับมาทบทวน ไม่ใช่ปิดทั้ง stage
ทดสอบ pipeline ก่อนใช้จริง
pipeline ก็เป็นโค้ด ต้องทดสอบเหมือนโค้ดอื่น ลำดับที่แนะนำ:
- รันคำสั่งในเครื่องก่อน ทุกคำสั่งใน
shควรรันผ่านในเครื่อง dev หรือในคอนเทนเนอร์ที่เหมือน agent ก่อนใส่ใน Jenkinsfile - ตรวจ syntax Jenkins มีตัวตรวจ declarative pipeline ผ่าน HTTP endpoint และ CLI ใช้ตรวจก่อน commit ได้
- ทดสอบบน branch แยก ให้ Multibranch Pipeline รัน branch ทดลอง และใช้ Replay ใน Jenkins เพื่อลองแก้โดยไม่ต้อง commit ทุกครั้ง
- ทดสอบว่ามันล้มได้ ตั้งใจใส่ syntax error หนึ่งไฟล์, ทำให้ test fail หนึ่งข้อ แล้วดูว่า build แดงจริง ถ้าไม่แดง แปลว่า pipeline ไม่ได้ปกป้องอะไร
- ตรวจ log ไล่ดู console log ว่าไม่มี secret หลุดออกมา
# ตรวจ syntax ของ Jenkinsfile กับ Jenkins server (ต้องยืนยันตัวตนตามที่ server กำหนด)
curl -X POST --user "$JENKINS_USER:$JENKINS_API_TOKEN" \
-F "jenkinsfile=<Jenkinsfile" \
"$JENKINS_URL/pipeline-model-converter/validate"
ดูรายละเอียด syntax และ step ที่มีให้ใช้ได้ที่ jenkins.io และในหน้า Pipeline Syntax ของ Jenkins server ของคุณเอง ซึ่งแสดงเฉพาะ step จาก plugin ที่ติดตั้งจริง
สรุป
- กำหนดขั้นตอนและเงื่อนไขที่ทำให้ build ล้มก่อน แล้วใช้เป็นทั้ง prompt และเกณฑ์ review
- บอก AI ว่ามี plugin และ credential ID อะไรบ้าง เพื่อไม่ให้เรียก step ที่ไม่มีอยู่
- secret ใช้ผ่าน credentials() หรือ withCredentials, ใช้ single quote ใน sh และจำกัด scope ตาม stage
- ปฏิเสธทางแก้ที่ทำให้เขียวด้วยการปิดการตรวจ เช่น || true หรือปิด host key checking
- ทดสอบว่า pipeline ล้มได้จริงเมื่อโค้ดผิด ก่อนให้ทั้งทีมใช้
ลองทำ
สร้าง branch ทดลองในโปรเจกต์ของคุณ เขียน prompt ตามโครงในบทความเพื่อให้ AI ร่าง Jenkinsfile แล้ว review ตามรายการรูปแบบอันตราย จากนั้นตั้งใจทำให้แต่ละขั้นล้มทีละขั้น (syntax error, test fail, เพิ่มแพ็กเกจเวอร์ชันที่มีช่องโหว่ที่รู้จักใน branch ทดลอง) และจดว่า pipeline จับได้ครบทุกขั้นหรือไม่
ความคิดเห็น (0)
ยังไม่มีความคิดเห็น — เริ่มคุยเป็นคนแรก ถามสิ่งที่สงสัย หรือแชร์ประสบการณ์ของคุณ