ก่อนที่จะไปเขียน Jenkins Pipline เรามาทำความรู้จักกับ Jenkins กันก่อน
Jenkins
เป็น Software (Tool) ตัวนึงที่เอามาใช้ทำ CI/CD (Continuous Integration/Continuous Delivery) เพื่อให้เราสามารถที่จะผลิตและส่งมอบ Software ไปยังผู้ใช้ได้อย่างต่อเนื่อง เกิดความราบรื่น และลดต้นทุนด้านเวลาในระหว่างการพัฒนา Software
โดยอาศัยหลักการของ Automation คือ การทำทุกอย่างให้เป็นไปอย่างอัตโนมัติ ตั้งแต่
โดยไม่ต้องใช้คนทำ
เป็นการใช้ Jenkins ทำ Automation ต่าง ๆ ตามที่ได้กล่าวไป ให้อยู่ในรูปแบบของ Code
หรือ Script
ซึ่งเราสามารถที่จะกำหนด Steps และ Flow การทำงานต่าง ๆ ได้เอง ว่าจะให้ Jenkins ทำอะไรให้บ้าง ตาม Code (Pipeline) ที่เราเขียนไว้
การเขียน Jenkins Pipeline ทำได้ 2 วิธี คือ
ถ้าจะแค่ทดสอบการทำงานของ Pipeline ให้ใช้วิธีแรก
แต่ถ้าทดสอบจนทำงานได้แล้ว ให้เปลี่ยนไปใช้วิธีที่ 2 ครับ
การเขียน Jenkins Pipeline เป็น File จะเป็น Text File ธรรมดา ๆ (ไม่มีนามสกุล) ปกติจะตั้งชื่อ File ว่า Jenkinsfile
โดยทั่วไป มักจะเก็บ Jenkinsfile
เอาไว้ที่ root ของ Project
เริ่มต้น ประกาศ pipeline
แล้วตามด้วย { ... }
pipeline {
...
}
ถัดมา กำหนด agent
และ stages
ต่าง ๆ
pipeline {
agent any
stages {
stage('Init'){
...
}
stage('Yarn Install') {
...
}
stage('Yarn Build') {
...
}
...
stage('Deploy') {
..
}
}
}
เป็นคำสั่งที่เอาไว้บอก Jenkins ว่าจะให้ใช้ executor ตัวใดมา run คำสั่งใน stages ทั้งหมดนี้
ที่ใช้บ่อย ๆ จะเป็น
none
คือ ไม่ใช้ executor ใด ๆ สำหรับ stages ทั้งหมด (ต้องไปกำหนด executor แยกสำหรับแต่ stage เอง)any
คือ ใช้ executor ใด ๆ ก็ได้docker
คือ ใช้ docker executor มา run stages ทั้งหมดนี้เปรียบเสมือนกับ Flow การทำงาน ว่าจะให้ทำอะไรบ้างใน Pipeline นี้ เช่น
ตัวอย่างของ stages
ถัดมา ในแต่ละ stage
ให้กำหนด steps
การทำงาน เพื่อบอก Jenkins ว่า จะให้ทำอะไร ยังไงบ้างใน stage นี้
pipeline {
agent any
stages {
stage('Init'){
steps {
echo 'Init'
echo '******************************'
}
}
stage('Yarn Install') {
steps {
echo 'Yarn Install'
echo '******************************'
}
}
stage('Yarn Build') {
steps {
echo 'Yarn Build'
echo '******************************'
}
}
...
stage('Deploy') {
steps{
echo 'Deploy'
echo '******************************'
}
}
}
}
สามารถใส่ step การทำงานภายใน steps { ... }
ได้หลาย steps
ที่ใช้บ่อย ๆ จะเป็น
echo
เป็นคำสั่งที่ใช้ในการ print (log) ข้อความต่าง ๆ ออกมาดูsh
เป็นคำสั่งที่ใช้ในการ run Linux Command เช่น 'node --version'
'cd frontend && yarn build'
'chmod 744 deploy.sh'
ตอนนี้พักการเขียน Pipeline ไว้ก่อน มาทดลอง run Pipeline จากข้างบนที่เราเขียนไว้ดู
ให้ New Item > กำหนดชื่อ แล้วเลือกเป็นแบบ Pipeline > OK
ถ้าใครยังไม่มี Jenkins สามารถใช้ Docker ติดตั้งได้ตามลิ้งค์ นี้
เขียน Pipeline script จากนั้น Save
ถ้าเขียน Pipeline เป็น Jenkinsfile
และเก็บไว้ใน Git ให้เลือก Pipeline เป็น Pipeline script from SCM
จากนั้นกำหนดค่า Git ต่าง ๆ จากนั้น Save
ทดลอง Build
ดู Console Output โดยการคลิกที่ กลม ๆ สีน้ำเงิน หน้า #1
ผลลัพธ์จากการ Build จะเห็นว่ามีการ Print ข้อความต่าง ๆ ตามที่เราได้เขียนไว้ใน Pipeline
ในการเขียน Pipeline เราสามารถกำหนดให้ Pipeline ใช้งานจากตัวแปร env
หรือ Environment ต่าง ๆ ของระบบได้ โดยการใช้ ${env.XXXXX}
ตัวอย่างการใช้ env
ของระบบ
pipeline {
agent any
stages {
stage('Init'){
steps {
echo 'Init'
echo '******************************'
echo "build number : ${env.BUILD_NUMBER}"
}
}
...
}
}
ตัวอย่างการใช้ env
ที่กำหนดขึ้นมาเอง
pipeline {
agent any
environment {
REGISTRY_URL = 'https://registry.mydomain.com'
}
stages {
stage('Init'){
steps {
echo 'Init'
echo '******************************'
echo "registry url : ${env.REGISTRY_URL}"
}
}
...
}
}
การใช้ตัวแปร env
หรือ Environment จะต้องทำภายใน String ที่เป็น Double Quote เท่านั้น (")
เพราะถ้าใช้ Single Quote (') ตัวแปร env จะไม่ทำงาน
อ่านเพิ่มเติมได้ที่
เราสามารถกำหนด agent
ในการ run Pipeline แต่ละ stage
แยกออกมาได้
ตัวอย่างเช่น กำหนดให้ stage นี้ถูก execute ด้วย docker เป็นต้น
pipeline {
agent any
environment {
NODE_IMAGE = 'node:12.13.0'
NODE_IMAGE_ARGS = '-u 0:0'
}
stages {
...
stage('Yarn Install') {
agent {
docker {
image "${env.NODE_IMAGE}"
args "${env.NODE_IMAGE_ARGS}"
}
}
steps {
echo 'Yarn Install'
echo '******************************'
sh 'node --version'
sh 'yarn --version'
}
}
}
}
อ่านเพิ่มเติมได้ที่
เราสามารถเขียน Pipeline Logic ที่ซับซ้อนด้วยภาษา Groovy
ได้ โดยการใช้ script
block
โดยการ กำหนด script { ... }
ไว้ใน steps { ... }
ดังนี้
...
stage('Read .env') {
agent any
steps{
script {
...
}
}
}
...
ตัวอย่าง
def envProps
pipeline {
agent none
environment {
MAVEN_IMAGE = 'maven:3.6.2-jdk-11'
MAVEN_IMAGE_ARGS = '-v /root/jenkins_home/ssh:/ssh -v /root/jenkins_home/.m2:/root/.m2:z -u root'
NODE_IMAGE = 'node:12.13.0'
NODE_IMAGE_ARGS = '-u 0:0'
DOCKER_IMAGE = ''
}
stages {
stage('Read .env') {
agent any
steps{
echo 'Read .env'
echo '******************************'
script {
def envFile = readFile("${env.JENKINS_HOME}/deploy/account.env");
def map = [:], lines = envFile.split("\r?\n");
for (def line : lines) {
def arr = line.split("=");
map.put(arr[0], arr[1]);
}
envProps = map;
}
}
}
...
}
}
อ่านเพิ่มเติมได้ที่
def envProps
pipeline {
agent none
environment {
MAVEN_IMAGE = 'maven:3.6.2-jdk-11'
MAVEN_IMAGE_ARGS = '-v /root/jenkins_home/ssh:/ssh -v /root/jenkins_home/.m2:/root/.m2:z -u root'
NODE_IMAGE = 'node:12.13.0'
NODE_IMAGE_ARGS = '-u 0:0'
DOCKER_IMAGE = ''
}
stages {
stage('Read .env') {
agent any
steps{
echo 'Read .env'
echo '******************************'
script {
def envFile = readFile("${env.JENKINS_HOME}/deploy/account.env");
def map = [:], lines = envFile.split("\r?\n");
for (def line : lines) {
def arr = line.split("=");
map.put(arr[0], arr[1]);
}
envProps = map;
}
}
}
stage('Yarn Install') {
agent {
docker {
image "${env.NODE_IMAGE}"
args "${env.NODE_IMAGE_ARGS}"
}
}
steps {
echo 'Yarn Install'
echo '******************************'
sh 'node --version'
sh 'yarn --version'
sh 'cd frontend && yarn install'
}
}
stage('Yarn Build') {
agent {
docker {
image "${env.NODE_IMAGE}"
args "${env.NODE_IMAGE_ARGS}"
}
}
steps {
echo 'Yarn Build'
echo '******************************'
sh 'cd frontend && yarn build'
}
}
stage('Mvn Install') {
agent {
docker {
image "${env.MAVEN_IMAGE}"
args "${env.MAVEN_IMAGE_ARGS}"
reuseNode true
}
}
steps {
echo 'Mvn Install'
echo '******************************'
sh 'java -version'
sh 'mvn -version'
sh 'mvn -U clean install -DskipTests=true'
}
}
stage('Mvn Test') {
agent {
docker {
image "${env.MAVEN_IMAGE}"
args "${env.MAVEN_IMAGE_ARGS}"
reuseNode true
}
}
steps {
echo 'Mvn Test'
echo '******************************'
sh 'mvn test'
}
}
stage('Docker Build') {
agent any
steps{
echo 'Docker Build'
echo '******************************'
script {
DOCKER_IMAGE = docker.build envProps.REGISTRY_NAME + ":latest"
}
}
}
stage('Docker Push') {
agent any
steps{
echo 'Docker Push'
echo '******************************'
script {
docker.withRegistry(envProps.REGISTRY_URL, envProps.REGISTRY_CREDENTIAL) {
DOCKER_IMAGE.push()
}
}
}
}
stage('Deploy') {
agent any
steps{
echo 'Deploy'
echo '******************************'
sh "sh ${env.JENKINS_HOME}/deploy/${envProps.DEPLOY_SH_FILE}"
}
}
}
}
การเขียน Jenkins Pipeline อื่น ๆ ที่ซับซ้อน สามารถเรียนรู้ได้จากลิ้งค์ใน Reference ด้านล่างครับ