GitLab CI
ตัวอย่าง
.gitlab-ci.ymlสำหรับ Terraform pipeline บน GitLab
Basic Pipeline
.gitlab-ci.yml
image: hashicorp/terraform:1.9.8
variables:
TF_ROOT: ${CI_PROJECT_DIR}
TF_STATE_NAME: ${CI_PROJECT_NAME}
cache:
key: terraform
paths:
- .terraform/
before_script:
- cd ${TF_ROOT}
- terraform --version
stages:
- validate
- plan
- apply
fmt:
stage: validate
script:
- terraform fmt -check -recursive
validate:
stage: validate
script:
- terraform init -backend=false
- terraform validate
plan:
stage: plan
script:
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
expire_in: 1 day
apply:
stage: apply
script:
- terraform init
- terraform apply -auto-approve tfplan
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
dependencies:
- plan
GitLab Managed State (Built-in!)
GitLab มี Terraform State Backend ในตัว — ไม่ต้องตั้ง S3:
backend.tf
terraform {
backend "http" {
address = "https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/${STATE_NAME}"
lock_address = "https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/${STATE_NAME}/lock"
unlock_address = "https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/${STATE_NAME}/lock"
username = "gitlab-ci-token"
password = "${CI_JOB_TOKEN}"
lock_method = "POST"
unlock_method = "DELETE"
retry_wait_min = 5
}
}
ใน CI:
plan:
script:
- terraform init
-backend-config="address=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}"
-backend-config="lock_address=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}/lock"
-backend-config="unlock_address=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}/lock"
-backend-config="username=gitlab-ci-token"
-backend-config="password=${CI_JOB_TOKEN}"
-backend-config="lock_method=POST"
-backend-config="unlock_method=DELETE"
-backend-config="retry_wait_min=5"
- terraform plan
State แสดงใน GitLab UI: Operate → Terraform states
ใช้ GitLab Terraform Template
GitLab มี template สำเร็จรูป:
.gitlab-ci.yml
include:
- template: Terraform/Base.gitlab-ci.yml
stages:
- validate
- test
- build
- deploy
fmt:
extends: .terraform:fmt
validate:
extends: .terraform:validate
build:
extends: .terraform:build
deploy:
extends: .terraform:deploy
OIDC Authentication
ใช้ GitLab OIDC token แทน AWS access key:
1. ตั้งค่า IAM
data "tls_certificate" "gitlab" {
url = "https://gitlab.com"
}
resource "aws_iam_openid_connect_provider" "gitlab" {
url = "https://gitlab.com"
client_id_list = ["https://gitlab.com"]
thumbprint_list = [data.tls_certificate.gitlab.certificates[0].sha1_fingerprint]
}
resource "aws_iam_role" "gitlab_ci" {
name = "GitLabCITerraform"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.gitlab.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringLike = {
"gitlab.com:sub" = "project_path:myorg/myrepo:ref_type:branch:ref:main"
}
}
}]
})
}
2. ใช้ใน CI Job
apply:
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.com
script:
- aws sts assume-role-with-web-identity
--role-arn $AWS_ROLE_ARN
--role-session-name gitlab-${CI_PIPELINE_ID}
--web-identity-token $GITLAB_OIDC_TOKEN
Multi-Environment
.gitlab-ci.yml
stages:
- validate
- plan
- apply
.terraform:
image: hashicorp/terraform:1.9.8
before_script:
- cd ${ENV_PATH}
- terraform init
validate:
stage: validate
parallel:
matrix:
- ENV: [dev, staging, prod]
variables:
ENV_PATH: envs/${ENV}
extends: .terraform
script:
- terraform validate
plan-dev:
stage: plan
variables:
ENV_PATH: envs/dev
extends: .terraform
script:
- terraform plan
rules:
- if: $CI_COMMIT_BRANCH == "develop"
plan-prod:
stage: plan
variables:
ENV_PATH: envs/prod
extends: .terraform
script:
- terraform plan -out=tfplan
artifacts:
paths: [envs/prod/tfplan]
rules:
- if: $CI_COMMIT_BRANCH == "main"
apply-prod:
stage: apply
variables:
ENV_PATH: envs/prod
extends: .terraform
script:
- terraform apply -auto-approve tfplan
environment:
name: production
deployment_tier: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
dependencies:
- plan-prod
Merge Request Plan Comment
plan:
stage: plan
script:
- terraform init
- terraform plan -no-color | tee plan.txt
# Post comment on MR
- |
if [ -n "$CI_MERGE_REQUEST_IID" ]; then
PLAN_OUTPUT=$(cat plan.txt)
BODY=$(jq -Rs --arg p "$PLAN_OUTPUT" '{body: "### Terraform Plan\n```\n\($p)\n```"}' <<< "")
curl --request POST \
--header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
--header "Content-Type: application/json" \
--data "$BODY" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
fi
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Cache Plugins
.terraform:
cache:
key:
files:
- .terraform.lock.hcl
paths:
- .terraform/providers
variables:
TF_PLUGIN_CACHE_DIR: "${CI_PROJECT_DIR}/.terraform.d/plugin-cache"
before_script:
- mkdir -p ${TF_PLUGIN_CACHE_DIR}
Linting + Security
tflint:
image: ghcr.io/terraform-linters/tflint:v0.53.0
stage: validate
script:
- tflint --init
- tflint --recursive
checkov:
image: bridgecrew/checkov:latest
stage: validate
script:
- checkov -d . --framework terraform
trivy:
image: aquasec/trivy:latest
stage: validate
script:
- trivy config --severity HIGH,CRITICAL .
Approval & Manual Apply
apply-prod:
stage: apply
script:
- terraform apply -auto-approve tfplan
environment:
name: production
url: https://app.example.com
when: manual # require manual click
only:
- main
when: manual → click "Play" button ใน GitLab UI เพื่อ trigger apply
Pipeline Schedules (Drift Detection)
ใน GitLab UI → CI/CD → Schedules:
drift-check:
stage: validate
script:
- terraform init
- terraform plan -refresh-only -detailed-exitcode
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
allow_failure: true
ตั้ง cron 0 9 * * * → daily drift check
Best Practices
✅ DO:
- ใช้ GitLab managed state (built-in!) สำหรับ small projects
- ใช้ OIDC แทน access keys
- Manual approval gate สำหรับ prod (when: manual)
- Cache plugins ระหว่าง jobs
- Lint + security scan ใน parallel
❌ DON'T:
- ห้าม commit secrets — ใช้ GitLab CI/CD variables (masked + protected)
- ห้าม auto-apply prod
- ห้าม skip artifacts (plan file ต้อง persist)
ตัวอย่าง Complete Pipeline
.gitlab-ci.yml
image: hashicorp/terraform:1.9.8
variables:
TF_PLUGIN_CACHE_DIR: "${CI_PROJECT_DIR}/.terraform.d/plugin-cache"
cache:
key:
files: [.terraform.lock.hcl]
paths:
- .terraform/providers
- .terraform.d/plugin-cache
before_script:
- mkdir -p ${TF_PLUGIN_CACHE_DIR}
stages:
- validate
- plan
- apply
fmt:
stage: validate
script: terraform fmt -check -recursive
validate:
stage: validate
script:
- terraform init -backend=false
- terraform validate
tflint:
stage: validate
image: ghcr.io/terraform-linters/tflint:v0.53.0
script:
- tflint --init
- tflint --recursive
checkov:
stage: validate
image: bridgecrew/checkov:latest
script:
- checkov -d . --framework terraform
plan:
stage: plan
script:
- terraform init
- terraform plan -out=tfplan
artifacts:
paths: [tfplan]
expire_in: 7 days
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
apply:
stage: apply
script:
- terraform init
- terraform apply -auto-approve tfplan
dependencies: [plan]
environment:
name: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
สรุป
- GitLab CI ใช้
.gitlab-ci.yml - มี Terraform managed state built-in (no need S3!)
- ใช้ template
Terraform/Base.gitlab-ci.ymlลดงาน - OIDC authentication กับ AWS
when: manualสำหรับ approval gate- Pipeline Schedules สำหรับ drift detection
ต่อไป → Jenkins