GitLab CI #
GitLab CI menawarkan beberapa keunggulan dibanding GitHub Actions untuk tim yang sudah di ekosistem GitLab: pipeline yang lebih fleksibel dengan include dan extends, environments dengan deployment tracking terintegrasi, dan kemampuan menjalankan pipeline yang sepenuhnya self-hosted tanpa bergantung pada infrastruktur eksternal. Artikel ini membahas pola integrasi Ansible yang komprehensif dengan GitLab CI.
Struktur Pipeline dengan Stages #
# .gitlab-ci.yml
stages:
- validate # Lint dan syntax check
- test # Unit test dan molecule
- build # Build Docker image
- deploy-staging
- verify-staging
- deploy-production
variables:
ANSIBLE_FORCE_COLOR: "true"
ANSIBLE_HOST_KEY_CHECKING: "false"
PY_COLORS: "1"
IMAGE_TAG: "${CI_COMMIT_SHORT_SHA}"
REGISTRY: "${CI_REGISTRY}"
IMAGE_NAME: "${CI_REGISTRY_IMAGE}"
Template dengan extends dan before_script
#
GitLab CI mendukung template yang bisa di-extend — menghindari duplikasi konfigurasi:
# Template untuk semua job Ansible
.ansible_base:
image: python:3.11-slim
before_script:
- pip install ansible --quiet
- ansible-galaxy install -r requirements.yml --force
cache:
key: ansible-$CI_COMMIT_REF_SLUG
paths:
- ~/.ansible/roles
- .cache/pip
# Template untuk deployment (butuh SSH)
.deploy_base:
extends: .ansible_base
before_script:
- !reference [.ansible_base, before_script]
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- echo "$VAULT_PASSWORD" > /tmp/.vault_pass
- chmod 600 /tmp/.vault_pass
after_script:
- rm -f /tmp/.vault_pass
Stage Validate dan Test #
# Stage: validate
lint:
extends: .ansible_base
stage: validate
script:
- pip install ansible-lint --quiet
- ansible-lint --profile production
- |
for playbook in playbooks/*.yml; do
ansible-playbook "$playbook" --syntax-check -i inventory/staging/
done
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
check-production:
extends: .deploy_base
stage: validate
variables:
SSH_PRIVATE_KEY: $PROD_SSH_KEY
VAULT_PASSWORD: $VAULT_PASS_PROD
script:
- ansible-playbook -i inventory/production/ site.yml
--check --diff
--vault-password-file /tmp/.vault_pass
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Stage: test — Molecule parallel
.molecule_base:
extends: .ansible_base
stage: test
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- pip install ansible molecule molecule-plugins[docker] --quiet
- ansible-galaxy install -r requirements.yml --force
molecule-common:
extends: .molecule_base
script: cd roles/common && molecule test
molecule-nginx:
extends: .molecule_base
script: cd roles/nginx && molecule test
molecule-postgresql:
extends: .molecule_base
script: cd roles/postgresql && molecule test
Build dan Push ke GitLab Container Registry #
build-image:
stage: build
image: docker:24
services:
- docker:dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest
- docker push $IMAGE_NAME:$IMAGE_TAG
- docker push $IMAGE_NAME:latest
- echo "IMAGE_FULL_TAG=$IMAGE_NAME:$IMAGE_TAG" > build.env
artifacts:
reports:
dotenv: build.env # Teruskan variabel ke job berikutnya
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Deployment dengan GitLab Environments #
deploy-staging:
extends: .deploy_base
stage: deploy-staging
variables:
SSH_PRIVATE_KEY: $STAGING_SSH_KEY
VAULT_PASSWORD: $VAULT_PASS_STAGING
script:
- ansible-playbook -i inventory/staging/ playbooks/deploy.yml
-e "app_version=$IMAGE_TAG"
--vault-password-file /tmp/.vault_pass
environment:
name: staging
url: https://staging.company.com
deployment_tier: staging
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
verify-staging:
stage: verify-staging
image: curlimages/curl:latest
script:
- sleep 15
- curl -f https://staging.company.com/health
- |
VERSION=$(curl -s https://staging.company.com/api/version | python3 -c "import json,sys; print(json.load(sys.stdin)['version'])")
[ "$VERSION" = "$IMAGE_TAG" ] || (echo "Version mismatch: expected $IMAGE_TAG, got $VERSION" && exit 1)
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy-production:
extends: .deploy_base
stage: deploy-production
variables:
SSH_PRIVATE_KEY: $PROD_SSH_KEY
VAULT_PASSWORD: $VAULT_PASS_PROD
script:
- ansible-playbook -i inventory/production/ playbooks/deploy.yml
-e "app_version=$IMAGE_TAG"
--vault-password-file /tmp/.vault_pass
environment:
name: production
url: https://app.company.com
deployment_tier: production
when: manual # Harus dipicu manual — tidak otomatis
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
allow_failure: false
Dynamic Child Pipeline #
Untuk monorepo dengan banyak service, gunakan child pipeline yang di-generate secara dinamis:
# .gitlab-ci.yml (parent pipeline)
generate-child-pipeline:
stage: validate
image: python:3.11-slim
script:
- python3 scripts/generate-pipeline.py > generated-pipeline.yml
artifacts:
paths:
- generated-pipeline.yml
trigger-child:
stage: deploy-staging
trigger:
include:
- artifact: generated-pipeline.yml
job: generate-child-pipeline
strategy: depend
# scripts/generate-pipeline.py
# Generate pipeline berdasarkan service yang berubah
import subprocess
import yaml
import sys
# Deteksi service yang berubah
changed = subprocess.run(
['git', 'diff', '--name-only', 'HEAD~1'],
capture_output=True, text=True
).stdout.split()
services = set()
for path in changed:
if path.startswith('services/'):
service = path.split('/')[1]
services.add(service)
# Generate job untuk setiap service yang berubah
pipeline = {'stages': ['deploy'], 'jobs': {}}
for service in services:
pipeline['jobs'][f'deploy-{service}'] = {
'stage': 'deploy',
'script': [
f'ansible-playbook -i inventory/staging/ playbooks/deploy-{service}.yml'
]
}
print(yaml.dump(pipeline))
Masked Variables untuk Secret #
# Di GitLab Settings → CI/CD → Variables
# Gunakan opsi:
# - Masked: nilai tidak muncul di log pipeline
# - Protected: hanya tersedia di protected branch
# - File: nilai disimpan sebagai file, bukan environment variable
# Contoh variabel yang harus di-mask:
# VAULT_PASS_PROD → masked + protected
# PROD_SSH_KEY → masked + protected + file type
# REGISTRY_PASSWORD → masked
Ringkasan #
- Gunakan
.template_name:denganextends:untuk menghindari duplikasi konfigurasi antar job — perubahan di template langsung berlaku di semua job yang meng-extend-nya.!referenceuntuk me-reusebefore_scriptdari template lain — memungkinkan komposisi yang lebih fleksibel dariextendssaja.- GitLab Environments memberikan deployment tracking otomatis — history semua deployment ke setiap environment tersedia di UI GitLab.
artifacts.reports.dotenvuntuk meneruskan variabel dari satu job ke job berikutnya — cara yang tepat untuk berbagi image tag antara build job dan deploy job.when: manualdi deployment production — memerlukan klik manual di UI GitLab, pipeline tidak otomatis melanjutkan ke production.- Masked + Protected variables untuk semua secret — masked mencegah nilai muncul di log, protected memastikan hanya branch yang dilindungi yang bisa mengaksesnya.
← Sebelumnya: GitHub Actions Berikutnya: Environment Management →