Jenkins
ตัวอย่าง Jenkinsfile (Declarative Pipeline) สำหรับ Terraform
Basic Jenkinsfile
Jenkinsfile
pipeline {
agent any
environment {
AWS_DEFAULT_REGION = 'ap-southeast-1'
TF_IN_AUTOMATION = 'true'
}
options {
timeout(time: 30, unit: 'MINUTES')
timestamps()
ansiColor('xterm')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Format Check') {
steps {
sh 'terraform fmt -check -recursive'
}
}
stage('Init') {
steps {
withCredentials([
[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'aws-creds']
]) {
sh 'terraform init'
}
}
}
stage('Validate') {
steps {
sh 'terraform validate'
}
}
stage('Plan') {
steps {
withCredentials([
[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'aws-creds']
]) {
sh 'terraform plan -out=tfplan'
}
}
}
stage('Approval') {
when {
branch 'main'
}
steps {
input message: 'Apply Terraform plan?', ok: 'Apply'
}
}
stage('Apply') {
when {
branch 'main'
}
steps {
withCredentials([
[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'aws-creds']
]) {
sh 'terraform apply -auto-approve tfplan'
}
}
}
}
post {
success {
slackSend color: 'good', message: "✅ Terraform applied: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
}
failure {
slackSend color: 'danger', message: "❌ Terraform failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
}
cleanup {
cleanWs()
}
}
}
Use Terraform Plugin
ติดตั้ง Jenkins Terraform Plugin:
pipeline {
agent any
tools {
terraform 'terraform-1.9.8' // ตั้งใน Manage Jenkins → Tools
}
stages {
stage('Init') {
steps {
sh 'terraform init'
}
}
}
}
Multi-Environment
pipeline {
agent any
parameters {
choice(
name: 'ENVIRONMENT',
choices: ['dev', 'staging', 'prod'],
description: 'Environment to deploy'
)
choice(
name: 'ACTION',
choices: ['plan', 'apply', 'destroy'],
description: 'Terraform action'
)
}
environment {
TF_DIR = "envs/${params.ENVIRONMENT}"
}
stages {
stage('Init') {
steps {
dir(env.TF_DIR) {
sh 'terraform init'
}
}
}
stage('Run') {
steps {
dir(env.TF_DIR) {
script {
if (params.ACTION == 'plan') {
sh 'terraform plan'
} else if (params.ACTION == 'apply') {
input "Apply to ${params.ENVIRONMENT}?"
sh 'terraform apply -auto-approve'
} else if (params.ACTION == 'destroy') {
input "DESTROY ${params.ENVIRONMENT}? This is irreversible!"
sh 'terraform destroy -auto-approve'
}
}
}
}
}
}
}
Parallel Stages
stage('Validation') {
parallel {
stage('Format') {
steps {
sh 'terraform fmt -check -recursive'
}
}
stage('Validate') {
steps {
sh 'terraform init -backend=false'
sh 'terraform validate'
}
}
stage('TFLint') {
agent {
docker { image 'ghcr.io/terraform-linters/tflint:v0.53.0' }
}
steps {
sh 'tflint --init'
sh 'tflint --recursive'
}
}
stage('Security') {
agent {
docker { image 'bridgecrew/checkov:latest' }
}
steps {
sh 'checkov -d . --framework terraform'
}
}
}
}
Plan Comment on PR
stage('Plan') {
steps {
sh 'terraform plan -no-color > plan.txt'
}
post {
always {
script {
if (env.CHANGE_ID) { // PR build
def planOutput = readFile('plan.txt')
pullRequest.comment("""### Terraform Plan
\`\`\`
${planOutput}
\`\`\`""")
}
}
}
}
}
(ต้องใช้ Pipeline GitHub Plugin)
OIDC with Jenkins (AWS)
ถ้า Jenkins รันบน EC2 — ใช้ instance profile:
stage('Apply') {
steps {
sh 'terraform apply -auto-approve' // ← AWS SDK อ่าน instance profile auto
}
}
ถ้า Jenkins รันใน K8s — ใช้ IRSA (IAM Roles for Service Accounts):
# Pod manifest
serviceAccountName: jenkins-terraform
resource "aws_iam_role" "jenkins" {
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Federated = aws_iam_openid_connect_provider.eks.arn }
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:sub" = "system:serviceaccount:jenkins:jenkins-terraform"
}
}
}]
})
}
Shared Library
สร้าง shared library ลด duplication:
vars/terraformPipeline.groovy
def call(Map config) {
pipeline {
agent any
stages {
stage('Init') {
steps {
dir(config.dir) {
sh 'terraform init'
}
}
}
stage('Plan') {
steps {
dir(config.dir) {
sh 'terraform plan -out=tfplan'
}
}
}
stage('Approval') {
when { branch 'main' }
steps {
input "Apply ${config.dir}?"
}
}
stage('Apply') {
when { branch 'main' }
steps {
dir(config.dir) {
sh 'terraform apply -auto-approve tfplan'
}
}
}
}
}
}
ใช้:
Jenkinsfile
@Library('shared-library') _
terraformPipeline(dir: 'envs/prod')
Caching
stage('Init') {
steps {
sh '''
mkdir -p $HOME/.terraform.d/plugin-cache
export TF_PLUGIN_CACHE_DIR=$HOME/.terraform.d/plugin-cache
terraform init
'''
}
}
Notification (Slack)
post {
success {
slackSend(
channel: '#deployments',
color: 'good',
message: """
✅ Terraform applied
*Project:* ${env.JOB_NAME}
*Build:* <${env.BUILD_URL}|#${env.BUILD_NUMBER}>
*Branch:* ${env.GIT_BRANCH}
"""
)
}
failure {
slackSend(
channel: '#deployments',
color: 'danger',
message: "❌ Terraform failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
}
Artifacts
stage('Plan') {
steps {
sh 'terraform plan -out=tfplan'
archiveArtifacts artifacts: 'tfplan', fingerprint: true
}
}
stage('Apply') {
steps {
copyArtifacts(
projectName: env.JOB_NAME,
buildNumber: env.BUILD_NUMBER,
filter: 'tfplan'
)
sh 'terraform apply -auto-approve tfplan'
}
}
Best Practices
✅ DO:
- ใช้ Declarative Pipeline (more readable)
- Run validation steps in parallel
- Use Jenkins credentials, never hardcode secrets
- Approval gate for production
- Notify on success/failure
- Use shared library for DRY pipelines
❌ DON'T:
- ห้ามใช้ Scripted Pipeline เว้นแต่จำเป็น
- ห้าม commit credentials
- ห้าม skip approval ใน prod
- ห้าม run apply โดยไม่ persist plan
ตัวอย่าง Mature Jenkinsfile
@Library('terraform-shared') _
pipeline {
agent {
label 'terraform'
}
parameters {
choice(name: 'ENV', choices: ['dev', 'staging', 'prod'])
choice(name: 'ACTION', choices: ['plan', 'apply', 'destroy'])
}
options {
timeout(time: 30, unit: 'MINUTES')
timestamps()
ansiColor('xterm')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '20'))
}
environment {
TF_DIR = "envs/${params.ENV}"
AWS_DEFAULT_REGION = 'ap-southeast-1'
TF_IN_AUTOMATION = 'true'
TF_INPUT = '0'
}
stages {
stage('Pre-checks') {
parallel {
stage('Format') { steps { sh 'terraform fmt -check -recursive' } }
stage('Validate') {
steps {
dir(env.TF_DIR) {
sh 'terraform init -backend=false'
sh 'terraform validate'
}
}
}
stage('TFLint') {
agent { docker { image 'ghcr.io/terraform-linters/tflint:v0.53.0' } }
steps { sh 'tflint --init && tflint --recursive' }
}
stage('Checkov') {
agent { docker { image 'bridgecrew/checkov:latest' } }
steps { sh 'checkov -d . --framework terraform' }
}
}
}
stage('Plan') {
steps {
dir(env.TF_DIR) {
withAWS(role: 'TerraformRole') {
sh 'terraform init'
sh 'terraform plan -out=tfplan'
archiveArtifacts 'tfplan'
}
}
}
}
stage('Approval') {
when {
anyOf {
equals expected: 'apply', actual: params.ACTION
equals expected: 'destroy', actual: params.ACTION
}
}
steps {
input(
message: "${params.ACTION.toUpperCase()} on ${params.ENV}?",
ok: 'Proceed'
)
}
}
stage('Execute') {
when {
not { equals expected: 'plan', actual: params.ACTION }
}
steps {
dir(env.TF_DIR) {
withAWS(role: 'TerraformRole') {
sh "terraform ${params.ACTION} -auto-approve ${params.ACTION == 'apply' ? 'tfplan' : ''}"
}
}
}
}
}
post {
success { slackSend channel: '#ops', color: 'good', message: "✅ ${env.JOB_NAME} ${params.ACTION} ${params.ENV} OK" }
failure { slackSend channel: '#ops', color: 'danger', message: "❌ ${env.JOB_NAME} ${params.ACTION} ${params.ENV} FAILED" }
cleanup { cleanWs() }
}
}
สรุป
- Jenkins ใช้ Jenkinsfile (Declarative Pipeline แนะนำ)
- ใช้ Terraform plugin หรือ install ใน agent
- withCredentials สำหรับ AWS keys, withAWS สำหรับ role assumption
- Run parallel ในการ validation steps
inputสำหรับ approval gate- Shared library ลด pipeline duplication
ต่อไป → Section 16: Testing