โ† Semua pembelajaran / Go Nol โ†’ Enterprise
Fase 10 ยท Deploy di AWS

CI/CD dengan GitHub Actions & OIDC

Access key jangka panjang di secrets repositori adalah kredensial yang tidak pernah kedaluwarsa dan bisa dipakai dari mana saja. OIDC menggantinya dengan token berumur satu jam yang hanya berlaku untuk repositori dan cabang tertentu.

Intisari

  • GitHub menerbitkan token OIDC per jalannya workflow; AWS menukarnya jadi kredensial sementara.
  • Nol access key tersimpan di GitHub โ€” tidak ada yang bisa bocor.
  • Batasi sub di kebijakan kepercayaan ke repositori dan cabang tertentu.
  • Urutan deploy: uji โ†’ build & push image โ†’ migrasi โ†’ perbarui service โ†’ tunggu stabil.
  • Tag image dengan SHA commit supaya rollback berarti sesuatu.

Menyiapkan kepercayaan

aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
        "token.actions.githubusercontent.com:sub": "repo:nurhadidev/toko:ref:refs/heads/main"
      }
    }
  }]
}

Kondisi sub adalah keseluruhan keamanannya. Kalau ditulis repo:nurhadidev/* โ€” atau lebih buruk, memakai StringLike dengan * โ€” maka repositori mana pun di organisasimu, termasuk yang bisa diedit kontributor luar lewat pull request, bisa mengambil peran deploy produksimu. Kunci ke repositori dan cabang tertentu. Untuk deploy dari tag, gunakan repo:org/repo:ref:refs/tags/v* dengan StringLike yang dibatasi.

Workflow

name: deploy
on:
  push:
    branches: [main]

permissions:
  id-token: write        # WAJIB untuk OIDC
  contents: read

env:
  AWS_REGION: ap-southeast-1
  ECR_REPO: 123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/toko/server
  CLUSTER: toko
  SERVICE: toko-server

jobs:
  uji:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: { go-version: "1.26", cache: true }

      - run: test -z "$(gofmt -l .)"
      - run: go vet ./...
      - uses: golangci/golangci-lint-action@v6
      - run: go test -race ./...

  deploy:
    needs: uji
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubDeploy
          aws-region: ${{ env.AWS_REGION }}

      - uses: aws-actions/amazon-ecr-login@v2

      - name: Build & push
        run: |
          TAG=${GITHUB_SHA::7}
          docker buildx build --platform linux/arm64 \
            --build-arg VERSI=$TAG --build-arg COMMIT=$GITHUB_SHA \
            --cache-from type=gha --cache-to type=gha,mode=max \
            -t $ECR_REPO:$TAG --push .
          echo "TAG=$TAG" >> $GITHUB_ENV

      - name: Migrasi database
        run: |
          ARN=$(aws ecs run-task --cluster $CLUSTER \
            --task-definition toko-migrate --launch-type FARGATE \
            --network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SG]}" \
            --query 'tasks[0].taskArn' --output text)

          aws ecs wait tasks-stopped --cluster $CLUSTER --tasks $ARN

          KODE=$(aws ecs describe-tasks --cluster $CLUSTER --tasks $ARN \
            --query 'tasks[0].containers[0].exitCode' --output text)
          if [ "$KODE" != "0" ]; then
            echo "migrasi gagal dengan exit code $KODE"
            exit 1
          fi

      - name: Perbarui service
        run: |
          DEF=$(aws ecs describe-task-definition --task-definition toko-server \
            --query taskDefinition)
          BARU=$(echo $DEF | jq --arg IMG "$ECR_REPO:$TAG" \
            '.containerDefinitions[0].image = $IMG
             | del(.taskDefinitionArn, .revision, .status,
                   .requiresAttributes, .compatibilities,
                   .registeredAt, .registeredBy)')
          REV=$(aws ecs register-task-definition --cli-input-json "$BARU" \
            --query 'taskDefinition.taskDefinitionArn' --output text)

          aws ecs update-service --cluster $CLUSTER --service $SERVICE \
            --task-definition $REV --force-new-deployment

      - name: Tunggu stabil
        run: aws ecs wait services-stable --cluster $CLUSTER --services $SERVICE

Urutan langkahnya bukan selera. Migrasi berjalan sebelum service diperbarui, sehingga kode baru selalu bertemu skema yang sudah siap โ€” dan karena migrasinya kompatibel dua arah (Fase 4), kode lama yang masih berjalan selama rolling update juga tetap bekerja. Membalik urutannya berarti kode baru bisa bertemu skema lama.

Peran deploy yang sesempit mungkin

ecr:GetAuthorizationToken            (*)
ecr:BatchCheckLayerAvailability      repositori toko/* saja
ecr:PutImage, ecr:UploadLayerPart    repositori toko/* saja

ecs:RegisterTaskDefinition           (*)  โ€” tidak bisa dibatasi resource
ecs:UpdateService, ecs:DescribeServices   service tertentu saja
ecs:RunTask, ecs:DescribeTasks            cluster tertentu saja

iam:PassRole                         HANYA ke ecsTaskExecutionRole dan tokoTaskRole

iam:PassRole yang tidak dibatasi adalah eskalasi hak istimewa. Dengan PassRole: * plus RunTask, siapa pun yang bisa menjalankan workflow dapat menjalankan container dengan peran IAM apa pun di akunmu โ€” termasuk peran administrator. Selalu batasi ke daftar ARN peran yang eksplisit.

Rollback

# Kembali ke revisi task definition sebelumnya
aws ecs update-service --cluster toko --service toko-server \
  --task-definition toko-server:41

# Revisi itu menunjuk tag image SHA yang berbeda โ€” inilah kenapa
# tag "latest" membuat rollback tidak berarti apa-apa.
PerubahanBisa di-rollback?
Kode aplikasiโœ… revisi task definition sebelumnya
Konfigurasiโœ… revisi sebelumnya
Migrasi yang menambah kolomโœ… kode lama mengabaikannya
Migrasi yang menghapus kolomโŒ kode lama akan gagal
Data yang sudah ditransformasiโŒ butuh pemulihan dari backup

Inilah alasan aturan migrasi kompatibel dua arah dari Fase 4 sangat penting di sini. Rollback kode itu murah dan cepat; rollback skema tidak ada. Kalau deploy-mu menghapus kolom, kamu kehilangan kemampuan kembali ke versi sebelumnya โ€” tepat pada saat kamu paling membutuhkannya.

Deploy yang lebih aman

CaraYang didapat
Rolling update (bawaan ECS)Sederhana; sedikit ada dua versi bersamaan
Circuit breaker ECSRollback otomatis saat deploy gagal โ€” nyalakan
Blue/green (CodeDeploy)Pengalihan trafik terkendali, uji dulu di listener tes
Canary10% trafik dulu, pantau, baru sisanya
Feature flagMelepas kode โ‰  menyalakan fitur (Fase 11)

Latihan: siapkan peran OIDC yang dibatasi ke satu repositori dan cabang main, lalu jalankan workflow deploy. Setelah berhasil, ubah kondisi sub ke cabang lain dan jalankan lagi dari main โ€” baca pesan penolakan STS-nya. Terakhir, lakukan rollback dengan menunjuk revisi task definition sebelumnya dan pastikan aplikasinya benar-benar kembali ke versi lama.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.