Langsung ke konten
KamusNgoding
Mahir Cicd 5 menit baca

Optimasi Pipeline CI/CD di GitLab: Tips dan Trik Pro

#gitlab #cicd #optimasi #advanced #devops

Optimasi Pipeline CI/CD di GitLab: Tips dan Trik Pro

Pendahuluan

Pipeline CI/CD yang lambat bukan sekadar gangguan kecil — ia adalah pembunuh produktivitas. Bayangkan kamu bekerja di tim engineering yang membangun platform seperti Tokopedia: setiap menit pipeline berjalan adalah waktu yang dipakai oleh developer untuk menunggu, bukan membangun fitur. Artikel ini fokus pada teknik advanced untuk mengoptimalkan pipeline GitLab CI/CD agar lebih cepat, aman, dan mudah di-maintain.

Jika kamu baru mengenal konsep CI/CD secara umum atau ingin membandingkan dengan tools lain, bisa baca dulu Apa itu GitHub Actions? Penjelasan Lengkap untuk Pemula sebagai perbandingan ekosistem yang berbeda.


Memahami Konsep Lanjutan GitLab CI/CD (Advanced Concepts)

GitLab CI/CD menggunakan file .gitlab-ci.yml sebagai konfigurasi utama. Namun di level advanced, ada beberapa konsep yang wajib dikuasai:

DAG (Directed Acyclic Graph) memungkinkan job berjalan berdasarkan dependency antar job, bukan sekadar urutan stage. Ini mengurangi waktu tunggu secara signifikan.

# Contoh DAG dengan needs:
stages:
  - build
  - test
  - deploy

build-backend:
  stage: build
  script: docker build -t backend .

build-frontend:
  stage: build
  script: docker build -t frontend .

test-backend:
  stage: test
  needs: ["build-backend"]   # Langsung jalan setelah build-backend selesai
  script: pytest tests/

test-frontend:
  stage: test
  needs: ["build-frontend"]  # Tidak perlu tunggu test-backend
  script: npm test

deploy:
  stage: deploy
  needs: ["test-backend", "test-frontend"]
  script: ./deploy.sh

Dengan needs, total waktu pipeline bisa berkurang 30-50% karena job tidak perlu menunggu seluruh stage sebelumnya selesai.


Teknik Optimasi Kecepatan: Cache, Parallel Jobs, dan Optimasi Docker Image

1. Strategi Cache yang Efektif

Cache yang buruk lebih merugikan daripada tidak ada cache sama sekali. Gunakan cache:key berbasis file hash untuk invalidasi otomatis:

cache:
  key:
    files:
      - package-lock.json    # Cache baru saat dependency berubah
  paths:
    - node_modules/
  policy: pull-push          # Pull di awal, push di akhir job

install:
  script:
    - npm ci --prefer-offline

Untuk multi-project, gunakan cache:key:prefix agar cache terisolasi per branch:

cache:
  key: "$CI_COMMIT_REF_SLUG-node"
  paths:
    - .npm/

2. Parallel Jobs untuk Test Suite Besar

test:
  parallel: 4                # Jalankan 4 instance bersamaan
  script:
    - npx jest --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL

CI_NODE_INDEX adalah indeks instance yang sedang berjalan (mulai dari 1), dan CI_NODE_TOTAL adalah total instance yang dijalankan secara paralel.

3. Optimasi Docker Image

Gunakan multi-stage build untuk memperkecil ukuran image secara signifikan. Image yang lebih kecil berarti waktu push dan pull ke registry lebih cepat, yang langsung berdampak pada durasi pipeline.

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Stage 2: Runtime (image lebih kecil, tanpa dev dependencies)
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["node", "server.js"]

Di .gitlab-ci.yml, manfaatkan layer caching Docker dengan BuildKit untuk mempercepat proses build:

build:
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_BUILDKIT: "1"
  script:
    - docker build
        --cache-from $CI_REGISTRY_IMAGE:latest
        --build-arg BUILDKIT_INLINE_CACHE=1
        -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

Dengan --cache-from, Docker akan menggunakan layer dari image sebelumnya sehingga hanya layer yang berubah yang perlu di-rebuild.


Mengamankan Pipeline: Manajemen Secret, SAST, dan DAST

Keamanan pipeline sering diabaikan sampai terjadi insiden. Jika ingin membangun layanan dengan jutaan pengguna seperti Gojek, pipeline yang bocor bisa menjadi celah serius.

Manajemen Secret yang Benar

Jangan pernah hardcode secret di .gitlab-ci.yml. Gunakan CI/CD Variables:

deploy:
  script:
    - echo $PRODUCTION_API_KEY | docker login --password-stdin
    # Variabel diset di: Settings > CI/CD > Variables

Untuk secret yang sangat sensitif, gunakan file type variable:

deploy-k8s:
  script:
    - kubectl --kubeconfig=$KUBECONFIG apply -f k8s/
  # $KUBECONFIG otomatis berisi path ke file temp yang dibuat GitLab

SAST (Static Application Security Testing)

GitLab menyediakan SAST bawaan yang mudah diaktifkan:

include:
  - template: Security/SAST.gitlab-ci.yml

variables:
  SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
  SAST_EXCLUDED_ANALYZERS: "eslint"  # Skip jika tidak relevan

DAST (Dynamic Application Security Testing)

include:
  - template: Security/DAST.gitlab-ci.yml

dast:
  environment:
    url: https://staging.myapp.com
  variables:
    DAST_FULL_SCAN_ENABLED: "true"
    DAST_BROWSER_SCAN: "true"

Reusabilitas dan Skalabilitas dengan Parent-Child & Dynamic Pipelines

Parent-Child Pipelines

Untuk monorepo atau proyek besar, pisahkan konfigurasi pipeline:

# .gitlab-ci.yml (Parent)
stages:
  - triggers

trigger-backend:
  stage: triggers
  trigger:
    include: services/backend/.gitlab-ci.yml
    strategy: depend           # Parent tunggu child selesai
  rules:
    - changes:
        - services/backend/**/*

trigger-frontend:
  stage: triggers
  trigger:
    include: services/frontend/.gitlab-ci.yml
    strategy: depend
  rules:
    - changes:
        - services/frontend/**/*

Dynamic Pipelines

Buat pipeline secara programatik berdasarkan kondisi runtime:

# Tahap 1: Generate konfigurasi
generate-pipeline:
  stage: prepare
  script:
    - python scripts/generate_pipeline.py > generated-pipeline.yml
  artifacts:
    paths:
      - generated-pipeline.yml

# Tahap 2: Trigger pipeline yang dihasilkan
trigger-generated:
  stage: execute
  trigger:
    include:
      - artifact: generated-pipeline.yml
        job: generate-pipeline
    strategy: depend

Script Python untuk generate pipeline:

# scripts/generate_pipeline.py
import yaml

services = ["auth", "payment", "notification"]
pipeline = {"stages": ["test", "build"], "jobs": {}}

for service in services:
    pipeline["jobs"][f"test-{service}"] = {
        "stage": "test",
        "script": [f"cd {service} && pytest"],
        "needs": []
    }

print(yaml.dump(pipeline))

Contoh Kasus Nyata: Pipeline untuk Aplikasi Microservice

Berikut contoh pipeline lengkap untuk aplikasi microservice yang mirip dengan arsitektur layanan fintech:

# .gitlab-ci.yml
image: docker:24

stages:
  - lint
  - test
  - security
  - build
  - deploy

variables:
  DOCKER_BUILDKIT: "1"
  REGISTRY: $CI_REGISTRY_IMAGE

# Template reusable
.base-job: &base-job
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

# Lint semua service sekaligus
lint:
  stage: lint
  image: node:20-alpine
  parallel:
    matrix:
      - SERVICE: [auth, payment, notification]
  script:
    - cd services/$SERVICE && npm run lint

# Unit test dengan coverage
test:
  stage: test
  image: python:3.12-slim
  parallel:
    matrix:
      - SERVICE: [auth, payment]
  script:
    - cd services/$SERVICE
    - pip install -r requirements.txt
    - pytest --cov=. --cov-report=xml
  coverage: '/TOTAL.*\s+(\d+%)$/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: services/$SERVICE/coverage.xml

# Security scan
include:
  - template: Security/SAST.gitlab-ci.yml

# Build dan push image
build:
  <<: *base-job
  stage: build
  parallel:
    matrix:
      - SERVICE: [auth, payment, notification]
  script:
    - docker build
        --cache-from $REGISTRY/$SERVICE:latest
        -t $REGISTRY/$SERVICE:$CI_COMMIT_SHA
        services/$SERVICE/
    - docker push $REGISTRY/$SERVICE:$CI_COMMIT_SHA
  only:
    - main
    - /^release-.*/

# Deploy ke staging
deploy-staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/auth auth=$REGISTRY/auth:$CI_COMMIT_SHA
    - kubectl set image deployment/payment payment=$REGISTRY/payment:$CI_COMMIT_SHA
    - kubectl rollout status deployment/auth
  environment:
    name: staging
  only:
    - main

# Deploy ke production (manual)
deploy-production:
  extends: deploy-staging
  script:
    - kubectl config use-context production
    - kubectl set image deployment/auth auth=$REGISTRY/auth:$CI_COMMIT_SHA
    - kubectl rollout status deployment/auth
  environment:
    name: production
  when: manual
  only:
    - /^release-.*/

Pola yang sama bisa diterapkan ke pipeline Node.js — lihat contoh implementasinya di Membangun CI/CD Pipeline untuk Aplikasi Node.js dengan GitHub Actions untuk perbandingan pendekatan antar platform.


Troubleshooting: Error yang Sering Muncul

This job is stuck, waiting for a runner with tags

Penyebab: Job membutuhkan runner dengan tag tertentu, tapi tidak ada runner yang tersedia atau tag tidak cocok.

Solusi:

# Cek tag yang dibutuhkan job
build:
  tags:
    - docker      # Pastikan runner kamu punya tag ini

# Atau hapus tag untuk menggunakan shared runner
build:
  script:
    - echo "building..."

Verifikasi di Settings > CI/CD > Runners bahwa runner aktif dan tag-nya sesuai.


fatal: git fetch-pack: unexpected disconnect while reading sideband packet

Penyebab: Timeout saat clone repository besar, terutama jika ada file biner besar atau history yang panjang.

Solusi:

variables:
  GIT_DEPTH: "10"              # Shallow clone, ambil 10 commit terakhir
  GIT_STRATEGY: fetch          # Gunakan fetch bukan clone ulang
  GIT_SUBMODULE_STRATEGY: recursive

default:
  before_script:
    - git config --global http.postBuffer 524288000

ERROR: Job failed: exit code 137 (Out of Memory)

Penyebab: Container runner kehabisan memori, biasanya terjadi saat build Docker image besar atau test suite yang berat.

Solusi:

build-heavy:
  variables:
    DOCKER_BUILDKIT: "1"
    NODE_OPTIONS: "--max-old-space-size=4096"
  tags:
    - high-memory             # Gunakan runner dengan resource lebih besar
  script:
    - npm run build

# Atau batasi parallel job agar tidak berebut memori
test:
  parallel: 2                 # Turunkan dari 4 ke 2
  script:
    - npm test

Cache not found when extracting cache

Penyebab: Cache key berubah (misalnya package-lock.json diupdate) atau cache belum dibuat pada run pertama.

Solusi:

install:
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull-push
    fallback_keys:
      - "$CI_COMMIT_REF_SLUG-node"
      - "default-node"
  script:
    - npm ci

Pertanyaan yang Sering Diajukan

Apa perbedaan cache dan artifacts di GitLab CI/CD?

Cache digunakan untuk mempercepat job dengan menyimpan file yang jarang berubah (seperti node_modules) lintas pipeline. Artifacts adalah output job yang diteruskan ke job berikutnya dalam pipeline yang sama, seperti hasil build atau laporan test. Cache bersifat opsional dan bisa tidak tersedia, sedangkan artifacts dijamin tersedia untuk job downstream yang membutuhkannya.

Bagaimana cara membatasi pipeline agar tidak berjalan pada setiap push?

Gunakan kombinasi rules dan changes untuk menjalankan job hanya ketika file yang relevan berubah:

test-backend:
  rules:
    - if: $CI_MERGE_REQUEST_ID
      changes:
        - backend/**/*
        - requirements.txt
    - if: $CI_COMMIT_BRANCH == "main"

Apa itu include di GitLab CI/CD dan kapan menggunakannya?

include memungkinkan kamu memasukkan konfigurasi dari file lain, baik lokal, dari URL, maupun template GitLab bawaan. Gunakan ini saat kamu ingin berbagi konfigurasi pipeline antar proyek atau memisahkan konfigurasi yang panjang menjadi file yang lebih kecil dan terfokus. Ini sangat berguna untuk organisasi dengan banyak tim yang perlu standarisasi pipeline.

Bagaimana cara debug pipeline yang gagal tanpa harus push setiap kali?

Gunakan gitlab-runner exec untuk menjalankan job secara lokal:

# Install gitlab-runner di lokal
gitlab-runner exec docker nama-job-kamu

# Atau validasi sintaks dengan CI Lint
curl --header "PRIVATE-TOKEN: <your_token>" \
     "https://gitlab.com/api/v4/ci/lint" \
     --form "[email protected]"

Selain itu, aktifkan fitur CI Lint di GitLab UI melalui CI/CD > Pipelines > CI Lint.

Mengapa pipeline di branch fitur lebih lambat dari branch main?

Branch fitur biasanya tidak memiliki cache yang sudah di-warm up seperti branch main yang sering dijalankan. Tambahkan fallback_keys pada konfigurasi cache untuk menggunakan cache dari branch main sebagai fallback, sehingga branch baru tidak perlu mulai dari nol dan waktu pipeline bisa berkurang signifikan sejak run pertama.


Kesimpulan

Mengoptimalkan pipeline GitLab CI/CD adalah investasi jangka panjang yang langsung berdampak pada kecepatan delivery tim kamu. Mulai dari DAG untuk menghilangkan bottleneck, strategi cache yang cerdas, keamanan dengan SAST/DAST, hingga dynamic pipelines untuk skalabilitas — semua teknik ini dapat diterapkan secara bertahap sesuai kebutuhan.

Kunci utamanya: ukur dulu, baru optimasi. Gunakan GitLab Pipeline Analytics untuk mengidentifikasi job mana yang paling lambat sebelum melakukan perubahan apapun.

Selamat bereksperimen dan terus tingkatkan pipeline-mu! Jika ada pertanyaan atau ingin mendalami topik DevOps lainnya, jangan ragu untuk menjelajahi artikel-artikel lain di KamusNgoding — kamu tidak sendirian dalam perjalanan ini!

Artikel Terkait