Chapter Continuous Integration di section Data Engineering Testing mengotomatiskan pertanyaan “apakah kode sehat?” — setiap push melewati lint dan test sebelum boleh merge. Pertanyaan kedua berbeda dan sering tertukar dengannya: “apakah rilis ini siap dipromosikan ke production?”. Continuous delivery (CD) mengambil alih setelah CI hijau: commit yang lolos uji dibangun menjadi artifact berversi, artifact yang sama dideploy ke staging, dan promosi ke production menunggu satu keputusan manusia. Chapter ini menyusun kedua bagian tersebut menjadi satu workflow GitHub Actions untuk pipeline data TokoKita.
CI Versus CD #
| Aspek | Continuous Integration | Continuous Delivery |
|---|---|---|
| Pertanyaan yang dijawab | apakah kode sehat? | apakah rilis siap dipromosikan? |
| Pemicu | setiap push dan pull request | merge ke branch main |
| Keluaran | vonis hijau atau merah | artifact berversi di staging |
| Keterlibatan manusia | tidak ada — gerbang otomatis | satu persetujuan untuk production |
CI tanpa CD berhenti di merge; CD tanpa CI mempromosikan kode yang tidak teruji. Keduanya dirancang sebagai satu rantai supaya artifact yang sampai ke production adalah artifact yang lulus seluruh gerbang — rantai inilah yang membedakan deployment berulang yang membosankan dari penyalinan manual yang menegangkan.
Workflow GitHub Actions #
Workflow berikut melanjutkan gerbang dari chapter Continuous Integration, lalu menambahkan tiga tahap deployment dari chapter Deployment Pipeline: version stamp, build artifact, dan promote:
name: CI/CD Pipeline Data
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
inputs:
tujuan:
description: "Tujuan deploy"
required: true
default: staging
type: choice
options: [staging, production]
jobs:
uji:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install -r requirements.txt
- name: Lint
run: ruff check .
- name: Test
run: python3 -m pytest tests -q
build:
needs: uji
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # git describe butuh riwayat tag
- name: Version stamp
run: echo "VERSI=$(git describe --tags --always)" >> "$GITHUB_ENV"
- name: Bangun artifact
run: |
python3 scripts/buat_manifest.py
zip -r "tokokita-etl-${VERSI}.zip" src config MANIFEST.json
- uses: actions/upload-artifact@v4
with:
name: tokokita-etl-${{ env.VERSI }}
path: tokokita-etl-*.zip
deploy-staging:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/download-artifact@v4
with:
pattern: tokokita-etl-*
merge-multiple: true
- name: Promote artifact yang sama ke staging
run: ./deploy.sh staging
deploy-production:
needs: build
if: github.event_name == 'workflow_dispatch' && inputs.tujuan == 'production'
runs-on: ubuntu-latest
environment: production # gerbang manual: required reviewers
steps:
- uses: actions/download-artifact@v4
with:
pattern: tokokita-etl-*
merge-multiple: true
- name: Promote artifact yang sama ke production
run: ./deploy.sh productionEmpat keputusan penting ada di dalamnya. Pertama, needs merangkai job berurutan — build hanya berjalan setelah uji hijau, dan kedua job deploy menunggu build. Kedua, job deploy mengunduh artifact dengan download-artifact, bukan membangun ulang; artifact yang dipromosikan ke production adalah byte yang sama dengan yang teruji di staging. Ketiga, environment: production menghubungkan job dengan lingkungan GitHub yang berisi required reviewers — gerbang manual berupa persetujuan sebelum job boleh berjalan. Keempat, workflow_dispatch dengan input tujuan membuat promosi ke production menjadi tindakan sadar lewat tombol, bukan efek samping push. Struktur lengkap workflow tervalidasi dengan yaml.safe_load:
"""Validasi struktur workflow CI/CD dengan yaml.safe_load."""
import yaml
wf = yaml.safe_load(open("cicd_pipeline.yaml", encoding="utf-8"))
# PyYAML mengikuti YAML 1.1: kunci 'on' terbaca sebagai boolean True
pemicu = wf.get("on") or wf[True]
print("pemicu:", sorted(pemicu))
for nama, job in wf["jobs"].items():
gerbang = []
if "needs" in job:
gerbang.append(f"needs={job['needs']}")
if job.get("environment"):
gerbang.append(f"environment={job['environment']}")
if job.get("if"):
gerbang.append(f"if: {job['if']}")
print(f"job {nama} ({'; '.join(gerbang) if gerbang else 'tanpa syarat'})")
for langkah in job["steps"]:
if "name" in langkah:
print(f" - {langkah['name']}")
elif "run" in langkah:
print(f" - run: {langkah['run'].splitlines()[0]}")
else:
print(f" - {langkah['uses']}")
assert list(wf["jobs"]) == ["uji", "build", "deploy-staging", "deploy-production"]
assert wf["jobs"]["deploy-production"]["environment"] == "production"
print("struktur valid: uji -> build -> deploy staging/production (manual gate)")Output yang Dihasilkan:
pemicu: ['pull_request', 'push', 'workflow_dispatch']
job uji (tanpa syarat)
- actions/checkout@v4
- actions/setup-python@v5
- run: pip install -r requirements.txt
- Lint
- Test
job build (needs=uji)
- actions/checkout@v4
- Version stamp
- Bangun artifact
- actions/upload-artifact@v4
job deploy-staging (needs=build; environment=staging; if: github.event_name == 'push' && github.ref == 'refs/heads/main')
- actions/download-artifact@v4
- Promote artifact yang sama ke staging
job deploy-production (needs=build; environment=production; if: github.event_name == 'workflow_dispatch' && inputs.tujuan == 'production')
- actions/download-artifact@v4
- Promote artifact yang sama ke production
struktur valid: uji -> build -> deploy staging/production (manual gate)Keluaran di atas adalah peta rantai dalam bentuk ringkas: empat job dengan urutan needs yang jelas, gerbang if dan environment tercetak per job, dan dua asersi terakhir memastikan urutan job serta keberadaan gerbang manual production tidak berubah tanpa sengaja. Validasi semacam ini murah dijalankan di CI sehingga setiap perubahan workflow ikut diperiksa, bukan hanya dilihat sekilas saat review.
Strategi Deploy untuk Pipeline Batch #
Aplikasi web melakukan deploy berkelanjutan: beberapa kali sehari, versi baru langsung melayani trafik. Pipeline batch berjalan berbeda — versi baru hanya terasa saat run berikutnya dieksekusi — sehingga pilihan strateginya juga berbeda:
| Strategi | Cara kerja | Cocok untuk |
|---|---|---|
| Redeploy saat jendela run | artifact baru dipromosikan menjelang jadwal run berikutnya | pipeline batch harian seperti TokoKita |
| Blue/green | dua versi terpasang berdampingan; symlink current dipindah setelah verifikasi | pipeline yang tidak boleh melewatkan satu jendela pun |
| Canary run | versi baru memproses satu partisi kecil dulu, hasilnya dibandingkan dengan versi lama | transformasi yang sensitif terhadap angka |
Pola symlink current dari chapter Deployment Pipeline membuat blue/green hampir gratis: versi lama tetap terpasang di foldernya, dan satu rename mengembalikan segalanya. Canary run memanfaatkan sifat partisi: jalankan versi baru untuk satu tanggal, bandingkan dengan hasil versi lama, baru lanjutkan sisa rentang.
Bagi mayoritas pipeline batch, pilihan pertama sudah memadai: promosi dilakukan pada jam kerja, verifikasi dilakukan di staging dengan data tiruan, dan run tengah malam berikutnya memakai versi baru dengan tenang. Strategi yang lebih berat baru layak ketika konsekuensi satu run gagal benar-benar mahal — misalnya laporan keuangan yang harus terbit pagi.
Workflow otomatis mengantar artifact sampai ke server, tetapi pipeline yang dideploy tetap hidup di jadwalnya sendiri — dan disiplin berjalan di production menuntut pembukuan yang rapi. Kita susun run ledger beserta keputusan catch-up dan guard concurrency pada chapter Scheduling dan Production Runs.