Ebook Syafi
Koleksi
Admin
← DevOps Dari Homelab ke Production
Edit Bab
💾 Simpan
Batal
Syarat
Mukadimah
Bab
Penutup
B
I
H2
H3
List
1.
Quote
Code
Link
Img
Table
Edit
Split
Preview
0 perkataan
# Bab 3: CI/CD Pipeline Bayangkan situasi ini. Anda baru sahaja siapkan satu feature baru untuk aplikasi web anda. Anda test di local, semua berjalan lancar. Kemudian anda SSH ke server, pull code terbaru, restart service, dan harap semuanya okay. Dua jam kemudian, ada bug. Rupanya anda terlupa run test sebelum deploy. Pernah tak jadi macam ni? Kalau pernah, anda bukan seorang. Ini adalah masalah klasik yang hampir setiap developer pernah hadapi. Dan inilah sebab CI/CD wujud. Dalam bab ini, kita akan belajar bagaimana automate keseluruhan proses dari code commit hingga deployment. Tak perlu lagi SSH manual ke server. Tak perlu lagi harap dan doa setiap kali deploy. Semuanya automatic, consistent, dan boleh dipercayai. ## Apa yang anda akan belajar: - Konsep CI/CD dan kenapa ia penting untuk production - Menulis GitHub Actions workflow dari awal untuk Node.js, Python, dan Docker - Asas GitLab CI sebagai alternatif - Pipeline stages: build, test, dan deploy - Menguruskan environment variables dan secrets dengan selamat - Deployment strategies: blue-green, rolling, dan canary - Hands-on: set up CI/CD untuk projek sebenar ## Apa Itu CI/CD? CI/CD adalah singkatan untuk **Continuous Integration** dan **Continuous Delivery** (atau **Continuous Deployment**). Mari kita pecahkan satu per satu. **Continuous Integration (CI)** bermaksud setiap kali developer push code ke repository, code tersebut akan di-build dan di-test secara automatik. Kalau ada masalah, anda akan tahu dalam beberapa minit, bukan beberapa hari kemudian bila ada orang lain complain. **Continuous Delivery (CD)** pula bermaksud code yang sudah lulus semua test sedia untuk di-deploy pada bila-bila masa. Anda cuma perlu klik satu butang (atau dalam kes Continuous Deployment, ia terus deploy tanpa perlu klik apa-apa). > **Nota Beginner:** Fikirkan CI/CD macam assembly line di kilang kereta. Setiap bahagian kereta melalui inspection station yang berbeza sebelum sampai ke customer. Kalau satu bahagian tak pass inspection, kereta tak akan keluar dari kilang. Sama juga dengan code anda.  ## Kenapa Perlu Automate? Mungkin anda fikir, "Saya seorang je yang kerja kat projek ni. Perlu ke CI/CD?" Jawapan pendek: ya. Sebab manusia buat silap. Kita lupa run test. Kita lupa build Docker image. Kita lupa update environment variable. Machine tak lupa. Berikut beberapa sebab utama kenapa CI/CD penting: 1. **Consistency** - Setiap deployment melalui proses yang sama. Tak ada variasi "oh hari tu saya deploy cara lain." 2. **Kelajuan** - Deployment yang dulu ambil 30 minit manual boleh jadi 5 minit automatic. 3. **Keyakinan** - Anda tahu code yang di-deploy sudah lulus semua test. 4. **Audit trail** - Setiap deployment ada rekod. Siapa deploy, bila, apa yang berubah. 5. **Rollback mudah** - Kalau ada masalah, boleh revert ke version sebelumnya dengan cepat. ## GitHub Actions: Bermula dengan CI/CD GitHub Actions adalah pilihan paling popular untuk CI/CD sekarang, terutama kalau code anda sudah ada di GitHub. Ia percuma untuk public repository dan ada free tier yang generous untuk private repository. ### Struktur Asas GitHub Actions menggunakan YAML files yang disimpan dalam folder `.github/workflows/` di repository anda. Setiap file adalah satu workflow. ```yaml # .github/workflows/ci.yml name: CI Pipeline on: push: branches: [main, develop] pull_request: branches: [main] jobs: build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup environment run: echo "Kita bermula di sini!" ``` Mari fahamkan setiap bahagian: - **name** - Nama workflow anda. Akan dipaparkan di GitHub UI. - **on** - Bila workflow ini perlu run. Di sini, setiap kali ada push ke branch `main` atau `develop`, dan setiap kali ada pull request ke `main`. - **jobs** - Senarai kerja yang perlu dilakukan. - **runs-on** - Machine apa yang digunakan. `ubuntu-latest` adalah pilihan paling common. - **steps** - Langkah demi langkah apa yang perlu dilakukan. ### Workflow untuk Node.js Ini contoh workflow lengkap untuk aplikasi Node.js: ```yaml # .github/workflows/node-ci.yml name: Node.js CI on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest strategy: matrix: node-version: [18.x, 20.x, 22.x] steps: - uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linter run: npm run lint - name: Run tests run: npm test - name: Run build run: npm run build security-audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20.x' - run: npm ci - name: Security audit run: npm audit --audit-level=high ``` Perhatikan penggunaan `strategy.matrix`. Ini bermaksud workflow akan run pada tiga versi Node.js secara selari. Kalau aplikasi anda perlu support multiple versions, ini cara terbaik untuk pastikan semuanya berjalan lancar. > **Nota Beginner:** `npm ci` berbeza daripada `npm install`. Ia lebih strict dan sesuai untuk CI environment kerana ia install dependencies berdasarkan `package-lock.json` sahaja, tanpa modify apa-apa. ### Workflow untuk Python ```yaml # .github/workflows/python-ci.yml name: Python CI on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Cache pip dependencies uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements-dev.txt - name: Lint with flake8 run: | flake8 . --count --select=E9,F63,F7,F82 --show-source flake8 . --count --max-line-length=120 --statistics - name: Run tests with coverage run: | pytest --cov=app --cov-report=xml --cov-report=html - name: Upload coverage report uses: actions/upload-artifact@v4 with: name: coverage-report-${{ matrix.python-version }} path: htmlcov/ ``` ### Workflow untuk Docker Build dan Push Ini adalah workflow yang paling anda perlukan untuk production. Ia build Docker image, tag dengan betul, dan push ke container registry. ```yaml # .github/workflows/docker-publish.yml name: Docker Build and Publish on: push: branches: [main] tags: ['v*'] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix= - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max ``` Workflow ini akan automatically tag image anda berdasarkan Git tag. Kalau anda push tag `v1.2.3`, image akan di-tag sebagai `1.2.3` dan `1.2`. Sangat kemas untuk version management. ## GitLab CI: Alternatif yang Mantap Kalau anda menggunakan GitLab, ia mempunyai CI/CD yang built-in. Anda cuma perlu create file `.gitlab-ci.yml` di root repository. ```yaml # .gitlab-ci.yml stages: - build - test - deploy variables: NODE_VERSION: "20" cache: paths: - node_modules/ build: stage: build image: node:${NODE_VERSION} script: - npm ci - npm run build artifacts: paths: - dist/ test: stage: test image: node:${NODE_VERSION} script: - npm ci - npm test coverage: '/Lines\s*:\s*(\d+\.?\d*)%/' deploy-staging: stage: deploy image: alpine:latest script: - echo "Deploying to staging..." - ./deploy.sh staging environment: name: staging url: https://staging.myapp.com only: - develop deploy-production: stage: deploy image: alpine:latest script: - echo "Deploying to production..." - ./deploy.sh production environment: name: production url: https://myapp.com only: - main when: manual ``` Perbezaan utama GitLab CI dengan GitHub Actions ialah GitLab menggunakan konsep **stages** dengan lebih explicit. Setiap stage mesti selesai sebelum stage seterusnya bermula. Perhatikan juga `when: manual` pada deploy-production. Ini bermaksud production deployment memerlukan seseorang untuk klik butang deploy secara manual. Ini adalah good practice untuk mengelakkan accidental deployment. ## Pipeline Stages: Build, Test, Deploy Sebuah CI/CD pipeline yang baik biasanya mempunyai beberapa stage yang jelas. ### Stage 1: Build Pada stage ini, code anda di-compile atau di-bundle. Untuk aplikasi Node.js, ini mungkin `npm run build`. Untuk Go, `go build`. Untuk Docker, `docker build`. Tujuan utama stage ini adalah untuk pastikan code anda boleh di-build tanpa error. Kalau ada syntax error atau missing dependency, ia akan gagal di sini. ### Stage 2: Test Stage test biasanya terbahagi kepada beberapa jenis: - **Unit tests** - Test fungsi individual. Paling cepat. - **Integration tests** - Test bagaimana komponen berbeza berinteraksi. - **End-to-end tests** - Test keseluruhan flow dari perspektif pengguna. - **Security scanning** - Scan untuk known vulnerabilities. - **Linting** - Pastikan code style consistent. ```yaml # Contoh test stage yang comprehensive test: runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_DB: testdb POSTGRES_USER: testuser POSTGRES_PASSWORD: testpass ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - name: Run unit tests run: npm run test:unit - name: Run integration tests run: npm run test:integration env: DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb - name: Run e2e tests run: npm run test:e2e ``` Perhatikan bagaimana kita boleh spin up PostgreSQL sebagai service dalam GitHub Actions. Ini bermaksud integration test anda boleh test dengan database sebenar, bukan mock. ### Stage 3: Deploy Stage deploy adalah tempat code anda sampai ke server. Bergantung pada setup anda, ini boleh jadi SSH ke server, push ke Kubernetes cluster, atau update cloud service. ## Environment Variables dan Secrets Ini bahagian yang ramai orang buat silap. Jangan sesekali hardcode passwords, API keys, atau credentials dalam code anda. Pernah nampak tak orang push AWS keys ke GitHub? Dalam beberapa minit, bots sudah guna keys tersebut untuk spin up crypto miners. ### GitHub Actions Secrets GitHub menyediakan cara yang selamat untuk menyimpan secrets: 1. Pergi ke repository Settings > Secrets and variables > Actions 2. Klik "New repository secret" 3. Masukkan nama dan value Kemudian dalam workflow: ```yaml steps: - name: Deploy to production run: | ssh -i key.pem ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \ "cd /app && docker compose pull && docker compose up -d" env: API_KEY: ${{ secrets.API_KEY }} DATABASE_URL: ${{ secrets.DATABASE_URL }} ``` ### Environment-Specific Variables Untuk projek yang mempunyai multiple environments (staging, production), gunakan GitHub Environments: ```yaml deploy-staging: runs-on: ubuntu-latest environment: staging steps: - name: Deploy run: ./deploy.sh env: APP_URL: ${{ vars.APP_URL }} API_KEY: ${{ secrets.API_KEY }} deploy-production: runs-on: ubuntu-latest environment: production needs: [deploy-staging] steps: - name: Deploy run: ./deploy.sh env: APP_URL: ${{ vars.APP_URL }} API_KEY: ${{ secrets.API_KEY }} ``` Setiap environment boleh mempunyai set secrets dan variables yang berbeza. Staging guna staging database, production guna production database. Simple dan selamat. > **Nota Beginner:** Jangan sesekali log atau print secrets dalam pipeline output. Walaupun GitHub akan cuba mask secrets secara automatik, lebih baik berjaga-jaga. ## Deployment Strategies Okay, code anda sudah lulus semua test. Sekarang, macam mana nak deploy? Ada beberapa strategi yang biasa digunakan. ### Rolling Deployment Ini adalah strategi paling simple. Server anda di-update satu per satu. Kalau anda ada 4 server, server pertama akan di-update dulu. Selepas ia siap dan healthy, server kedua pula. Dan seterusnya. Kelebihan: Mudah difahami, tiada downtime. Kekurangan: Untuk seketika, ada dua versi aplikasi running serentak. ```yaml # Contoh rolling deployment dengan Docker Compose deploy: runs-on: ubuntu-latest steps: - name: Rolling deploy run: | for server in ${{ secrets.SERVER_LIST }}; do echo "Deploying to $server..." ssh deploy@$server " cd /app && docker compose pull && docker compose up -d --no-deps --build web && sleep 10 && docker compose exec web curl -f http://localhost:3000/health " echo "$server deployed successfully" done ``` ### Blue-Green Deployment Bayangkan anda ada dua set server yang identical. Satu dipanggil "blue" (version semasa), satu lagi "green" (version baru). Anda deploy version baru ke green. Test. Kalau okay, tukar traffic dari blue ke green. Kalau ada masalah, tukar balik ke blue. Senang. ```yaml # Contoh blue-green dengan Nginx deploy-blue-green: runs-on: ubuntu-latest steps: - name: Deploy to green environment run: | ssh deploy@${{ secrets.DEPLOY_HOST }} " cd /app && docker compose -f docker-compose.green.yml pull && docker compose -f docker-compose.green.yml up -d && sleep 15 && curl -f http://localhost:3001/health " - name: Switch traffic to green run: | ssh deploy@${{ secrets.DEPLOY_HOST }} " cp /etc/nginx/conf.d/green.conf /etc/nginx/conf.d/active.conf && nginx -s reload " - name: Stop blue environment run: | ssh deploy@${{ secrets.DEPLOY_HOST }} " docker compose -f docker-compose.blue.yml down " ``` ### Canary Deployment Canary deployment adalah strategi di mana anda deploy version baru kepada sebahagian kecil users dulu. Mungkin 5% dulu. Monitor. Kalau tak ada masalah, naikkan ke 25%, 50%, dan akhirnya 100%. Nama "canary" datang dari amalan pelombong yang bawa burung kenari ke dalam lombong. Kalau burung mati, bermaksud ada gas berbahaya. Sama juga dengan canary deployment. Kalau 5% users mengalami masalah, anda tahu ada issue sebelum ia menjejaskan semua users. Strategi ini lebih sesuai untuk aplikasi berskala besar dan biasanya memerlukan load balancer yang menyokong traffic splitting seperti Nginx, HAProxy, atau Kubernetes Ingress. ### Perbandingan Deployment Strategies | Strategi | Downtime | Rollback | Complexity | Sesuai Untuk | |----------|----------|----------|------------|--------------| | Rolling | Tiada | Sederhana | Rendah | Kebanyakan aplikasi | | Blue-Green | Tiada | Sangat cepat | Sederhana | Aplikasi yang perlu instant rollback | | Canary | Tiada | Cepat | Tinggi | Aplikasi berskala besar | Untuk kebanyakan projek homelab-to-production, rolling deployment sudah memadai. Ia simple, berkesan, dan tidak memerlukan infrastructure tambahan. Anda boleh upgrade ke blue-green atau canary bila keperluan anda bertambah. > **Nota Beginner:** Jangan terus jump ke canary deployment hanya kerana Netflix atau Google gunakannya. Mulakan dengan rolling deployment. Bila anda faham betul-betul bagaimana ia berfungsi, barulah pertimbangkan strategi yang lebih advanced. ## Praktikal: Set Up CI/CD untuk Projek Sebenar Mari kita buat end-to-end. Katakan anda ada sebuah Node.js API yang menggunakan Docker. Ini complete setup. ### Langkah 1: Struktur Projek ``` my-api/ src/ index.js routes/ middleware/ tests/ unit/ integration/ Dockerfile docker-compose.yml docker-compose.prod.yml .github/ workflows/ ci.yml deploy.yml package.json ``` ### Langkah 2: CI Workflow ```yaml # .github/workflows/ci.yml name: CI on: push: branches: [main, develop] pull_request: branches: [main] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - run: npm run lint test: runs-on: ubuntu-latest needs: [lint] services: postgres: image: postgres:16 env: POSTGRES_DB: testdb POSTGRES_USER: testuser POSTGRES_PASSWORD: testpass ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis:7 ports: - 6379:6379 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - name: Run tests run: npm test env: DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb REDIS_URL: redis://localhost:6379 build-image: runs-on: ubuntu-latest needs: [test] if: github.ref == 'refs/heads/main' permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v5 with: context: . push: true tags: | ghcr.io/${{ github.repository }}:latest ghcr.io/${{ github.repository }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max ``` ### Langkah 3: Deploy Workflow ```yaml # .github/workflows/deploy.yml name: Deploy on: workflow_run: workflows: [CI] types: [completed] branches: [main] jobs: deploy: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} environment: production steps: - name: Deploy to production server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_KEY }} script: | cd /opt/my-api export IMAGE_TAG=${{ github.sha }} docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d sleep 10 curl -f http://localhost:3000/health || exit 1 echo "Deployment successful!" - name: Notify on success if: success() run: | curl -X POST ${{ secrets.SLACK_WEBHOOK }} \ -H 'Content-Type: application/json' \ -d '{"text":"Deployment successful! Version: ${{ github.sha }}"}' - name: Rollback on failure if: failure() uses: appleboy/ssh-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_KEY }} script: | cd /opt/my-api docker compose -f docker-compose.prod.yml down export IMAGE_TAG=$(docker images --format '{{.Tag}}' | head -2 | tail -1) docker compose -f docker-compose.prod.yml up -d echo "Rolled back to previous version" ``` Perhatikan beberapa perkara penting di sini. Pertama, deploy workflow hanya run selepas CI workflow berjaya. Kedua, ia menggunakan environment `production` yang boleh dikonfigurasi untuk memerlukan approval. Ketiga, ada step untuk rollback kalau deployment gagal. Dan keempat, ada notification ke Slack supaya team tahu status deployment. > **Nota Beginner:** Untuk permulaan, anda mungkin tak perlukan semua ini. Mulakan dengan CI sahaja dulu (lint + test). Selepas selesa, tambah Docker build. Kemudian baru tambah automated deployment. Jangan cuba buat semua sekaligus. ### Langkah 4: Dockerfile untuk Projek Untuk melengkapkan setup, ini Dockerfile yang sesuai untuk production: ```dockerfile # Dockerfile FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build RUN npm prune --production FROM node:20-alpine RUN addgroup -g 1001 -S appgroup && \ adduser -S appuser -u 1001 -G appgroup WORKDIR /app COPY --from=builder --chown=appuser:appgroup /app/dist ./dist COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules COPY --from=builder --chown=appuser:appgroup /app/package.json ./ USER appuser EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1 CMD ["node", "dist/index.js"] ``` ### Langkah 5: Docker Compose untuk Production ```yaml # docker-compose.prod.yml services: web: image: ghcr.io/username/my-api:${IMAGE_TAG:-latest} restart: always ports: - "3000:3000" env_file: - .env.production deploy: resources: limits: cpus: '1.0' memory: 512M db: image: postgres:16-alpine restart: always volumes: - pgdata:/var/lib/postgresql/data env_file: - .env.db redis: image: redis:7-alpine restart: always command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: pgdata: ``` ### Langkah 6: Test Keseluruhan Flow Sekarang, mari test keseluruhan flow ini. 1. Buat satu perubahan kecil dalam code anda. 2. Push ke branch `develop` dan create pull request ke `main`. 3. Perhatikan CI workflow bermula secara automatik di GitHub Actions tab. 4. Tunggu lint dan test selesai. Kalau ada error, fix dan push lagi. 5. Merge pull request ke `main`. 6. Perhatikan CI workflow run lagi, kali ini termasuk Docker build dan push. 7. Selepas CI selesai, deploy workflow akan bermula secara automatik. 8. Check server anda untuk pastikan deployment berjaya. Proses ini mungkin nampak banyak langkah, tapi sebenarnya selepas setup awal, anda hanya perlu buat langkah 1 hingga 5. Selebihnya berlaku secara automatik. Itulah keajaiban CI/CD. ## Tips dan Best Practices Sebelum kita tutup bab ini, beberapa tips yang saya pelajari dari pengalaman. **Cache dependencies.** Setiap kali pipeline run, ia perlu download semua dependencies. Gunakan caching untuk speed up proses ini. GitHub Actions dan GitLab CI kedua-duanya menyokong caching. **Keep pipelines fast.** Kalau pipeline ambil 30 minit untuk complete, developer akan mula skip CI. Idealnya, pipeline sepatutnya selesai dalam 5 hingga 10 minit. Gunakan parallel jobs dan caching untuk achieve ini. **Protect main branch.** Set up branch protection rules supaya code tak boleh di-merge ke main tanpa CI passing. Ini adalah safety net yang paling penting. **Version your artifacts.** Setiap Docker image, setiap build artifact, perlu ada unique version. Gunakan Git SHA atau semantic versioning. Jangan guna `latest` tag sahaja untuk production. **Monitor your pipelines.** Set up notifications untuk pipeline failures. Kalau pipeline gagal dan tak ada siapa perasan, ia sama sahaja macam tak ada pipeline. **Don't test in production.** Gunakan staging environment yang mirror production. Deploy ke staging dulu, test, baru deploy ke production. Ini nampak macam common sense, tapi anda akan terkejut berapa ramai orang skip langkah ini. **Use branch naming conventions.** Standardize nama branch dalam team anda. Contohnya, `feature/add-login`, `fix/broken-auth`, `hotfix/critical-bug`. Ini memudahkan pipeline untuk trigger berdasarkan jenis perubahan. Anda juga boleh configure workflow untuk run different stages bergantung pada branch pattern. **Fail fast.** Letakkan checks yang paling cepat di awal pipeline. Linting biasanya selesai dalam beberapa saat, jadi letakkan ia sebelum tests yang mungkin ambil beberapa minit. Kalau linting gagal, developer tahu dalam masa yang singkat tanpa perlu tunggu semua test selesai. **Pipeline sebagai dokumentasi.** Pipeline anda sebenarnya adalah dokumentasi yang hidup tentang bagaimana projek anda di-build, di-test, dan di-deploy. Developer baru boleh baca workflow files untuk faham keseluruhan proses. Pastikan ia jelas dan well-commented. ## Troubleshooting CI/CD yang Common Sebelum kita tutup, beberapa masalah yang sering saya jumpa dan cara mengatasinya. **Pipeline pass locally tapi fail di CI.** Ini biasanya disebabkan oleh perbezaan environment. Pastikan Node.js/Python version yang sama digunakan. Check juga sama ada ada dependencies yang depend pada OS-specific packages. Satu tip: gunakan Docker dalam CI supaya environment sentiasa consistent. **Pipeline terlalu lambat.** Audit setiap step dan tengok mana yang paling lama. Biasanya install dependencies dan Docker build yang paling lambat. Gunakan caching aggressively. Untuk Docker, gunakan BuildKit cache. Untuk npm, cache `node_modules`. Untuk pip, cache pip download directory. **Flaky tests.** Tests yang kadang pass, kadang fail tanpa code change. Ini biasanya disebabkan oleh race conditions, external API dependencies, atau timezone issues. Fix root cause, jangan cuma retry. Tapi kalau memang perlu, kebanyakan CI platforms ada retry mechanism. ```yaml # GitHub Actions: retry step - name: Run flaky test uses: nick-invision/retry@v2 with: timeout_minutes: 5 max_attempts: 3 command: npm run test:e2e ``` **Secrets not available.** Kalau anda nampak error berkaitan secrets kosong, pastikan anda sudah add secrets di Settings. Untuk pull requests dari forked repositories, secrets tidak available secara default kerana security reasons. Ini adalah behavior yang dijangkakan. ## Ringkasan Dalam bab ini, kita telah belajar bahawa CI/CD bukan sekadar tool. Ia adalah satu amalan yang mengubah cara kita deliver software. Dengan CI, setiap code change di-validate secara automatik. Dengan CD, deployment menjadi proses yang boleh dipercayai dan repeatable. Kita telah explore GitHub Actions dan GitLab CI sebagai platform CI/CD. Kedua-duanya mempunyai kelebihan masing-masing, tetapi konsep asasnya sama. Kita juga telah belajar tentang pipeline stages (build, test, deploy), cara menguruskan secrets dengan selamat, dan pelbagai deployment strategies. Yang paling penting, kita telah set up satu CI/CD pipeline yang complete untuk projek sebenar. Dari lint dan test, hingga Docker build dan automated deployment dengan rollback. Dalam bab seterusnya, kita akan mendalami Docker untuk production. Bukan basics lagi, tetapi teknik-teknik advanced seperti multi-stage builds, image optimization, dan security hardening yang anda perlukan untuk run containers dengan yakin di production. \newpage