Lewati ke konten

Batch Peserta: Tiga Project

Pemegang akun masterBelum diujiArti badge ini

Panduan ini dipakai pemegang satu akun master untuk menyiapkan tiga project kelompok dan memasang akses peserta secara manual, tanpa berkas perantara.

Bacaan ini untuk instruktur dan asisten instruktur. Peserta tidak membacanya, dan tidak ada satu pun langkah di sini yang boleh mengandalkan peserta membaca aturan apa pun.

Akun master
│
├── geoportal-kelompok-a-xxxxx peserta 1 sampai 4
├── geoportal-kelompok-b-xxxxx peserta 5 sampai 8
└── geoportal-kelompok-c-xxxxx peserta 9 sampai 12

Jumlah akun, project, dan peserta per project dibekukan di docs-md/00-INDEX.md bagian Struktur batch yang dibekukan: 4 akun master, 3 project per akun, 4 peserta per project.

Untuk pekerjaan satu project saja, termasuk alasan di balik setiap pilihan, rujukannya panduan setup project.

Pekerjaan Master Peserta Instruktur
Buat 3 project, tautkan Billing Account ✅
Aktifkan API, firewall, Artifact Registry, budget ✅
Pasang role IAM peserta ✅
Cocokkan IP dengan daftar alamat reserved ✅
Tambah record A di DNS domain ✅
Buat VM dalam project sendiri ✅
Kunci alamat IP statis ✅
Laporkan nama, email GCP, IP statis, subdomain ✅
Domain, DNS, dan keputusan sertifikat ✅
Cloud Shell akun master
# 1. Akun yang aktif harus akun master yang benar.
gcloud auth list
# 2. Billing Account yang akan dipakai.
gcloud billing accounts list
# 3. Berapa project yang sudah menempel di Billing Account itu.
BILLING_ACCOUNT_ID="BILLING_ACCOUNT_ID"
gcloud billing projects list --billing-account="$BILLING_ACCOUNT_ID"

Batasnya tiga project berbayar per Billing Account, dihitung termasuk project bawaan yang sudah menempel di sana sebelum batch ini.

2.1 Lepaskan slot dari project yang tidak dipakai

Section titled “2.1 Lepaskan slot dari project yang tidak dipakai”

Jalankan hanya kalau hasil perintah nomor tiga menunjukkan slot tidak cukup.

Cloud Shell akun master
PROJECT_LAMA="PROJECT_ID"
gcloud billing projects describe "$PROJECT_LAMA" \
--format="value(billingAccountName,billingEnabled)"
gcloud billing projects unlink "$PROJECT_LAMA"

2.2 Kalau isinya masih dibutuhkan, tukar tautannya

Section titled “2.2 Kalau isinya masih dibutuhkan, tukar tautannya”

gcloud billing projects link menggantikan tautan lama tanpa memerlukan unlink lebih dahulu, dan tanpa mematikan apa pun.

Cloud Shell akun master
gcloud billing projects link "PROJECT_ID" \
--billing-account="BILLING_ACCOUNT_LAIN"

2.3 Buat ketiga project untuk akun master baru

Section titled “2.3 Buat ketiga project untuk akun master baru”

Lewati bagian ini kalau projectnya sudah ada.

Cloud Shell akun master
BILLING_ACCOUNT_ID="BILLING_ACCOUNT_ID"
BATCH="batch-1"
KODE_BIAYA="big-webgis-2026"
SUFFIX="$(openssl rand -hex 3 | cut -c1-5)"
PROJECT_A="geoportal-kelompok-a-${SUFFIX}"
PROJECT_B="geoportal-kelompok-b-${SUFFIX}"
PROJECT_C="geoportal-kelompok-c-${SUFFIX}"
for PROJECT_ID in "$PROJECT_A" "$PROJECT_B" "$PROJECT_C"; do
echo "== $PROJECT_ID"
if gcloud projects describe "$PROJECT_ID" >/dev/null 2>&1; then
echo " sudah ada, dilewati"
else
gcloud projects create "$PROJECT_ID" \
--labels="batch=${BATCH},billing-code=${KODE_BIAYA}"
fi
gcloud billing projects link "$PROJECT_ID" \
--billing-account="$BILLING_ACCOUNT_ID"
done
# Dipakai oleh seluruh perintah di bagian berikutnya.
PROJECT_IDS=( "$PROJECT_A" "$PROJECT_B" "$PROJECT_C" )

Nilai BATCH dan KODE_BIAYA wajib huruf kecil. Label GCP dengan huruf besar ditolak dengan pesan yang menyebut nilainya, bukan nama variabelnya.

Bloknya aman diulang: project yang sudah ada dilewati, dan penautan billing tidak error.

Verifikasi. Ketiganya harus True:

Cloud Shell akun master
for PROJECT_ID in "${PROJECT_IDS[@]}"; do
printf '%-34s %s\n' "$PROJECT_ID" \
"$(gcloud billing projects describe "$PROJECT_ID" --format='value(billingEnabled)')"
done

Sekali per project. Peserta tidak mengulanginya.

Cloud Shell akun master
REGION="asia-southeast2"
PROJECT_IDS=(
"geoportal-kelompok-a-xxxxx"
"geoportal-kelompok-b-xxxxx"
"geoportal-kelompok-c-xxxxx"
)
APIS=(
compute.googleapis.com
cloudbuild.googleapis.com
artifactregistry.googleapis.com
iap.googleapis.com
iam.googleapis.com
iamcredentials.googleapis.com
dns.googleapis.com
secretmanager.googleapis.com
)
for PROJECT_ID in "${PROJECT_IDS[@]}"; do
echo "== $PROJECT_ID"
gcloud services enable "${APIS[@]}" --project="$PROJECT_ID"
done

iam.googleapis.com wajib ada di daftar itu. Tanpa API tersebut, peserta yang membuat Service Account sendiri akan gagal dengan PERMISSION_DENIED walaupun role-nya lengkap, dan pesan errornya tidak menyebut API sama sekali.

domains.googleapis.com sengaja tidak diaktifkan. Pelatihan ini memakai subdomain dari domain milik instruktur, dan domain itu tidak didaftarkan lewat Cloud Domains.

Nama resource berikut sama di semua project, dan itu tidak masalah karena setiap project memisahkan dirinya sendiri. Yang berbahaya adalah peserta yang membuatnya ulang di dalam project yang sama dan menerima ALREADY_EXISTS.

Cloud Shell akun master
for PROJECT_ID in "${PROJECT_IDS[@]}"; do
echo "== $PROJECT_ID"
gcloud artifacts repositories create katalog-images \
--repository-format=docker \
--location="$REGION" \
--project="$PROJECT_ID" \
--description="Docker image bersama pelatihan WebGIS" || true
gcloud compute firewall-rules create allow-webgis-http \
--network=default --direction=INGRESS --action=ALLOW \
--rules=tcp:80,tcp:443 --source-ranges=0.0.0.0/0 \
--target-tags=webgis-http --project="$PROJECT_ID" || true
gcloud compute firewall-rules create allow-webgis-iap-ssh \
--network=default --direction=INGRESS --action=ALLOW \
--rules=tcp:22 --source-ranges=35.235.240.0/20 \
--target-tags=webgis-iap-ssh --project="$PROJECT_ID" || true
done

|| true membuat pengulangan perintah aman. Tanpa itu, perintah kedua pada project yang sama menghentikan skrip di tengah jalan.

Label memisahkan biaya per batch di Billing Reports. Tanpanya, biaya hanya bisa dipisah per project, dan itu tidak cukup ketika project yang sama dipakai batch kedua.

Label hanya bisa dipasang saat project dibuat lewat gcloud. gcloud projects update tidak menerima flag label sama sekali; --update-labels ditolak dengan unrecognized arguments.

Project yang terlanjur dibuat tanpa label: periksa dengan gcloud projects describe PROJECT_ID --format="value(labels)". Keluaran kosong berarti belum berlabel, dan menambahkannya lewat Console pada IAM & Admin, Manage Resources, bagian Labels. Console satu-satunya jalur yang pasti tersedia tanpa memasang komponen alpha.

Label tidak wajib untuk menjalankan pelatihan, tetapi label yang tidak ada tidak memunculkan error apa pun; ia hanya membuat pemisahan biaya per batch menjadi tidak mungkin.

Cloud Build menyimpan token GitHub di Secret Manager. Sebelum peserta membuat GitHub connection, identitas layanan Cloud Build harus ada dan punya akses ke sana. Langkah ini dijalankan untuk ketiga project, karena setiap project punya Service Agent sendiri.

Cloud Shell akun master
for PROJECT_ID in "${PROJECT_IDS[@]}"; do
echo "== $PROJECT_ID"
gcloud beta services identity create \
--service=cloudbuild.googleapis.com \
--project="$PROJECT_ID"
NOMOR="$(gcloud projects describe "$PROJECT_ID" \
--format="value(projectNumber)")"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:service-${NOMOR}@gcp-sa-cloudbuild.iam.gserviceaccount.com" \
--role="roles/secretmanager.admin" \
--condition=None
done

Verifikasi. Setiap project harus menghasilkan satu alamat Service Agent:

Cloud Shell akun master
for PROJECT_ID in "${PROJECT_IDS[@]}"; do
NOMOR="$(gcloud projects describe "$PROJECT_ID" --format="value(projectNumber)")"
printf '%-34s %s\n' "$PROJECT_ID" "service-${NOMOR}@gcp-sa-cloudbuild.iam.gserviceaccount.com"
done

Biarkan role ini selama peserta membuat GitHub connection. Setelah semua connection berstatus COMPLETE, cabut kembali dari ketiga project:

Cloud Shell akun master
for PROJECT_ID in "${PROJECT_IDS[@]}"; do
NOMOR="$(gcloud projects describe "$PROJECT_ID" --format="value(projectNumber)")"
gcloud projects remove-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:service-${NOMOR}@gcp-sa-cloudbuild.iam.gserviceaccount.com" \
--role="roles/secretmanager.admin" \
--condition=None
done

Perintah gcloud beta services identity create memerlukan komponen beta. Cloud Shell sudah memasangnya. Kalau dijalankan di terminal sendiri, pasang lebih dahulu dengan gcloud components install beta.

Buat dari akun master melalui Billing, lalu Budgets & alerts. Konfigurasi dan nilai ambangnya ada di panduan setup project bagian 9, termasuk budget per project dan budget seluruh Billing Account sebagai pengaman kedua.

Bedanya untuk batch: scope-nya tiga project sekaligus, bukan satu. Supaya tetap berguna, buat budget Billing Account dengan tiga project itu sebagai cakupannya, karena budget per project tidak memunculkan peringatan ketika totalnya melewati batas gabungan.

Budget alert hanya mengirim notifikasi. Ia tidak menghentikan VM, Cloud Build, pembelian domain, atau billing. Akun master tetap harus memeriksa Billing Reports secara berkala.

Cloud Shell akun master
ROLES=(
roles/editor
roles/resourcemanager.projectIamAdmin
roles/serviceusage.serviceUsageAdmin
roles/iam.serviceAccountAdmin
roles/iam.serviceAccountUser
roles/compute.osAdminLogin
roles/iap.tunnelResourceAccessor
roles/cloudbuild.connectionAdmin
roles/logging.viewer
)

Blok di atas adalah sumber tunggal daftar role peserta. Dokumen lain merujuk ke sini, bukan menyalinnya, supaya daftarnya tidak bisa berbeda antar berkas.

Sembilan binding per peserta, dikali dua belas peserta, berarti sembilan puluh enam binding per akun master.

add-iam-policy-binding bersifat menambah, bukan mengganti. Menjalankannya dua kali untuk email dan role yang sama tidak menghasilkan error dan tidak menggandakan binding.

Role Fungsi
roles/editor Membuat dan mengelola sebagian besar resource project
roles/resourcemanager.projectIamAdmin Memberi role deployment kepada Service Account, dan mengerjakan langkah IAM Modul 5.4
roles/serviceusage.serviceUsageAdmin Mengaktifkan API yang terlewat
roles/iam.serviceAccountAdmin Membuat dan mengelola Service Account deployment
roles/iam.serviceAccountUser Memakai Service Account pada VM dan trigger
roles/compute.osAdminLogin Login administratif ke VM lewat OS Login
roles/iap.tunnelResourceAccessor Membuka SSH lewat Identity-Aware Proxy
roles/cloudbuild.connectionAdmin Membuat GitHub connection dan linked repository
roles/logging.viewer Membaca log Cloud Build saat build gagal

roles/editor dan roles/resourcemanager.projectIamAdmin memberi akses luas, termasuk mengubah IAM dan melihat resource peserta lain. Keduanya dipertahankan karena peserta harus dapat membuat Service Account sendiri dan mengerjakan langkah IAM pada Modul 5.4.

Risikonya satu: projectIamAdmin memungkinkan peserta memberi dirinya sendiri role yang tidak kamu berikan. Mencabut role dari daftar tidak menutup celah itu selama role ini ada. Penutup sungguhnya adalah pre-grant dari akun master lalu menghapus langkah 11 Modul 5.4 dari sesi. Analisis lengkapnya di docs-md/infra-gcp.md bagian 3.3 dan 3.4.

Role Alasan
roles/domains.admin Peserta dapat membeli domain atas nama Billing Account penyelenggara. Komitmen satu tahun, terikat pada project, tidak dapat dibatalkan.
roles/dns.admin Pelatihan memakai subdomain dari domain instruktur, dan record DNS ditambahkan penyelenggara di luar GCP. Cloud DNS per project tidak dipakai.

roles/editor juga bisa menimbulkan tagihan, misalnya lewat Cloud NAT atau load balancer. Yang membuat roles/domains.admin berbeda bukan kemampuan membelanjakan uang, melainkan sifat belanjanya: satu klik salah tidak bisa diperbaiki dengan mematikan VM keesokan harinya.

Dua binding berikut dikerjakan peserta pada bagian 9 panduan deployment. Akun master tidak perlu mengerjakannya.

Binding Untuk
roles/artifactregistry.reader pada Compute Engine default Service Account Docker di VM menarik image
roles/iam.serviceAccountUser dari Service Account build ke Service Account VM Cloud Build melakukan SSH ke VM

Satu peserta satu project. Isi PESERTA dengan pasangan email dan Project ID.

Cloud Shell akun master
declare -A PESERTA=(
["asisten01@example.com"]="geoportal-kelompok-a-xxxxx"
["asisten02@example.com"]="geoportal-kelompok-a-xxxxx"
["asisten03@example.com"]="geoportal-kelompok-a-xxxxx"
["asisten04@example.com"]="geoportal-kelompok-a-xxxxx"
["asisten05@example.com"]="geoportal-kelompok-b-xxxxx"
["asisten06@example.com"]="geoportal-kelompok-b-xxxxx"
["asisten07@example.com"]="geoportal-kelompok-b-xxxxx"
["asisten08@example.com"]="geoportal-kelompok-b-xxxxx"
["asisten09@example.com"]="geoportal-kelompok-c-xxxxx"
["asisten10@example.com"]="geoportal-kelompok-c-xxxxx"
["asisten11@example.com"]="geoportal-kelompok-c-xxxxx"
["asisten12@example.com"]="geoportal-kelompok-c-xxxxx"
)

Isi ROLES lebih dahulu dengan blok pada bagian 5.1, karena blok di bawah memakainya. Kalau Cloud Shell sudah ditutup sejak bagian itu, ROLES kosong dan loop-nya selesai tanpa memasang apa pun.

Maksimal empat peserta per project. Kuota alamat IPv4 publik berbatas empat per project per region, dan peserta yang perlu subdomain tidak dapat dibuatkan record A tanpa alamat IP publik. Peserta kelima harus dipindahkan ke project lain, bukan ke region lain.

6.2 Pastikan setiap email adalah akun Google

Section titled “6.2 Pastikan setiap email adalah akun Google”

GCP menolak memberikan role kepada alamat yang belum menjadi akun Google, dengan pesan INVALID_ARGUMENT: User <alamat> does not exist. Kalimat does not exist menyesatkan: ia bukan berarti salah ketik, melainkan alamat itu belum terdaftar sebagai akun Google saat perintahnya dijalankan.

Penyebabnya salah satu dari tiga: peserta belum membuat akun Gmail-nya, alamatnya salah tulis, atau alamatnya milik Google Workspace dan bukan Gmail biasa.

Pemeriksaan ini mencoba satu role ringan ke setiap email, melaporkan mana yang valid dan mana yang tidak, dan tidak berhenti ketika menemukan yang gagal. Jalankan sebelum memasang sembilan role, supaya kegagalan tidak menghentikan proses di tengah.

Cloud Shell akun master
GAGAL=()
echo "Memeriksa ${#PESERTA[@]} email..."
echo
for EMAIL in $(printf '%s\n' "${!PESERTA[@]}" | sort); do
PROJECT_ID="${PESERTA[$EMAIL]}"
# Pesan galatnya disimpan, bukan dibuang. Tanpa itu, "alamat salah ketik" dan
# "akun belum ada" terlihat sama, padahal tindakannya berbeda.
PESAN="$(gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="user:$EMAIL" \
--role="roles/editor" \
--condition=None --quiet 2>&1)"
KODE=$?
if [[ "$KODE" -eq 0 ]]; then
printf ' OK %s\n' "$EMAIL"
continue
fi
printf ' GAGAL %s\n' "$EMAIL"
# "Policy modification failed" sengaja tidak diteruskan: itu pengantar yang
# menyesatkan pada pesan gcloud, dan sebab sebenarnya ada di baris berikutnya.
printf '%s\n' "$PESAN" |
grep -oiE "does not exist|permission denied|invalid argument[^.]*" |
sort -u |
while read -r BARIS; do printf ' %s\n' "$BARIS"; done
GAGAL+=("$EMAIL")
done
echo
if [[ "${#GAGAL[@]}" -eq 0 ]]; then
echo "Semua email valid. Lanjut ke bagian 6.4."
else
echo "${#GAGAL[@]} email bermasalah, role lain belum dipasang untuk mereka:"
for G in "${GAGAL[@]}"; do echo " $G"; done
echo
echo "Alamat dengan 'does not exist' belum jadi akun Google. Minta peserta"
echo "membuka accounts.google.com dengan alamat itu, lalu ulangi bagian 6.2."
echo "Dua sebab yang paling sering: akun belum pernah dibuat, atau alamat"
echo "baru dibuat beberapa jam sebelumnya sehingga belum terbaca IAM."
fi

Blok berikut memuat dua jalur sekaligus, dan UJI yang memilih. Dengan UJI=1 ia hanya mencetak rencana. Setelah rencananya benar, bagian 6.4 memakai blok yang sama dengan UJI=0.

Cloud Shell akun master
UJI=1 # 1 = cetak rencana saja, 0 = benar-benar memasang
for EMAIL in $(printf '%s\n' "${!PESERTA[@]}" | sort); do
PROJECT_ID="${PESERTA[$EMAIL]}"
if [[ "$UJI" -eq 1 ]]; then
printf 'RENCANA %-30s -> %-32s %s role\n' "$EMAIL" "$PROJECT_ID" "${#ROLES[@]}"
continue
fi
printf '\n[%s]\n' "$EMAIL"
for ROLE in "${ROLES[@]}"; do
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="user:$EMAIL" \
--role="$ROLE" \
--condition=None \
--quiet
done
done

Baca keluarannya lebih dahulu. Yang diperiksa: setiap email menuju project yang benar, dan tidak ada project yang menerima lebih dari empat peserta.

Jalankan ulang blok pada bagian 6.3 dengan UJI=0.

declare -A memerlukan bash 4 atau lebih baru. Cloud Shell memakainya, jadi blok ini aman di sana. Terminal macOS bawaan memakai bash 3.2 dan akan menolaknya, jadi jalankan blok ini di Cloud Shell.

Tambahkan satu baris pada PESERTA, lalu jalankan ulang blok 6.3 dan 6.4. Peserta yang sudah terpasang tidak dipasangi dua kali, karena add-iam-policy-binding bersifat menambah.

Untuk satu orang saja, tanpa menyentuh peta PESERTA:

Cloud Shell akun master
PROJECT_ID="geoportal-kelompok-c-xxxxx"
EMAIL="asisten13@example.com"
gcloud projects get-iam-policy "$PROJECT_ID" \
--flatten="bindings[].members" \
--filter="bindings.members~user:$EMAIL" \
--format="value(bindings.role)"
for ROLE in "${ROLES[@]}"; do
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="user:$EMAIL" \
--role="$ROLE" \
--condition=None \
--quiet
done

Perintah get-iam-policy di awal mencetak role yang sudah dimiliki email itu, sehingga kamu bisa membedakan yang baru ditambahkan dari yang sudah ada sejak awal.

Dua pilihan, dan hanya dua:

  1. Pindahkan ke project yang masih punya slot. Ini yang benar bila orang itu perlu subdomain.
  2. Terima tanpa IP publik. Orang itu bekerja lewat terowongan IAP dan tidak diberi subdomain. Ini hanya sah bila ia benar-benar melepas tuntutan subdomain, bukan menundanya.

Project keempat bukan pilihan selama Billing Account masih berhenti di batas tiga project berbayar.

Jalankan blok di bawah untuk melihat siapa punya akses di ketiga project sekaligus. Blok ini tidak memakai filter gcloud, karena operator filter berubah perilaku antar versi dan pernah melaporkan hasil kosong tanpa error.

Cloud Shell akun master
PROJECT_IDS=(
"geoportal-kelompok-a-xxxxx"
"geoportal-kelompok-b-xxxxx"
"geoportal-kelompok-c-xxxxx"
)
for PROJECT_ID in "${PROJECT_IDS[@]}"; do
echo "=================================================="
echo "PROJECT: $PROJECT_ID"
echo
gcloud projects get-iam-policy "$PROJECT_ID" --format=json 2>&1 |
tr -d ' \n' |
sed 's/},{/}\n{/g' |
awk '
{
if (!match($0, /"role":"[^"]+"/)) next
role = substr($0, RSTART + 8, RLENGTH - 9)
if (!match($0, /"members":\[[^]]*\]/)) next
isi = substr($0, RSTART, RLENGTH)
n = split(isi, bagian, ",")
for (i = 1; i <= n; i++) if (match(bagian[i], /user:[^"]+/)) {
printf "%s|%s\n", substr(bagian[i], RSTART + 5, RLENGTH - 5), role
}
}' |
sort |
awk -F'|' '
BEGIN {
n = split("editor resourcemanager.projectIamAdmin serviceusage.serviceUsageAdmin iam.serviceAccountAdmin iam.serviceAccountUser compute.osAdminLogin iap.tunnelResourceAccessor cloudbuild.connectionAdmin logging.viewer", a, " ")
for (i = 1; i <= n; i++) perlu["roles/" a[i]] = 1
}
{
email = $1; role = $2
jumlah[email]++
if (role in perlu) punya[email, role] = 1
else if (role == "roles/owner") owner[email] = 1
else lebih[email] = lebih[email] "\n " role
}
END {
if (length(jumlah) == 0) { print " Project terbaca, tetapi belum ada user yang di-assign."; exit }
printf " %-32s %-4s %s\n", "EMAIL", "ROLE", "STATUS"
printf " %-32s %-4s %s\n", "--------------------------------", "----", "--------"
for (email in jumlah) {
kurang = ""
for (r in perlu) if (!((email, r) in punya)) kurang = kurang "\n " r
if (email in owner) { kurang = ""; status = (lebih[email] == "") ? "pemilik" : "pemilik+lebih" }
else if (kurang != "" && lebih[email] != "") status = "TERTUKAR"
else if (kurang != "") status = "KURANG"
else if (lebih[email] != "") status = "berlebih"
else status = "lengkap"
printf " %-32s %-4s %s\n", email, jumlah[email], status
if (kurang != "") print " kurang:" kurang
if (lebih[email] != "") print " berlebih:" lebih[email]
}
}'
echo
done

Cara membaca hasilnya:

Status Arti Tindakan
lengkap Sembilan role yang benar, satu per satu tidak ada
pemilik Hanya roles/owner tidak ada, itu akun pemilik project
pemilik+lebih roles/owner plus role lain hapus tambahannya, owner sudah mencakup semuanya
KURANG Ada role belum terpasang lihat bagian 7.1
berlebih Ada role di luar sembilan lihat bagian 7.1
TERTUKAR Ada yang kurang dan berlebih sekaligus lihat bagian 7.1

TERTUKAR yang paling perlu diwaspadai. Jumlahnya bisa tampak benar, misalnya tetap sembilan, padahal susunannya salah: satu role yang dibutuhkan hilang dan tertutup oleh role sisa skema lama. Akibatnya baru terasa ketika peserta memakai role yang ternyata tidak dimilikinya.

Keluaran ini tidak memuat Service Account, karena yang dicari adalah email peserta. Ia juga tidak menunjukkan siapa yang sudah login; untuk itu buka IAM & Admin, IAM, kolom Last activity.

7.1 Melengkapi role yang kurang atau berlebih

Section titled “7.1 Melengkapi role yang kurang atau berlebih”

Skrip ini mencocokkan daftar role yang benar dengan yang terpasang, mencetak selisihnya, dan baru mengubah apa pun setelah kamu mengetik LANJUT.

Cloud Shell akun master
PROJECT_ID="geoportal-kelompok-a-xxxxx"
EMAIL="asisten01@example.com"
SELISIH="$(
gcloud projects get-iam-policy "$PROJECT_ID" --format=json 2>/dev/null |
tr -d ' \n' |
sed 's/},{/}\n{/g' |
awk '
{
if (!match($0, /"role":"[^"]+"/)) next
role = substr($0, RSTART + 8, RLENGTH - 9)
if (!match($0, /"members":\[[^]]*\]/)) next
isi = substr($0, RSTART, RLENGTH)
n = split(isi, bagian, ",")
for (i = 1; i <= n; i++) if (match(bagian[i], /user:[^"]+/)) {
printf "%s|%s\n", substr(bagian[i], RSTART + 5, RLENGTH - 5), role
}
}' |
sort |
awk -F'|' -v target="$EMAIL" '
BEGIN {
n = split("editor resourcemanager.projectIamAdmin serviceusage.serviceUsageAdmin iam.serviceAccountAdmin iam.serviceAccountUser compute.osAdminLogin iap.tunnelResourceAccessor cloudbuild.connectionAdmin logging.viewer", a, " ")
for (i = 1; i <= n; i++) perlu["roles/" a[i]] = 1
}
{ semua[$1] = 1; if ($1 == target) punya[$2] = 1 }
END {
if (!(target in semua)) { print "TIDAK ADA DI POLICY"; exit }
for (r in perlu) if (!(r in punya)) print "add|" r
for (r in punya) if (!(r in perlu) && r != "roles/owner") print "remove|" r
}'
)"
if [[ "$SELISIH" == "TIDAK ADA DI POLICY" ]]; then
echo "$EMAIL tidak muncul di policy project ini."
exit 1
fi
if [[ -z "$SELISIH" ]]; then
echo "$EMAIL sudah lengkap dan tidak ada role berlebih."
exit 0
fi
echo "$EMAIL:"
while IFS= read -r BARIS; do
case "${BARIS%%|*}" in
add) echo " TAMBAH ${BARIS##*|}" ;;
remove) echo " CABUT ${BARIS##*|}" ;;
esac
done <<< "$SELISIH"
echo
read -r -p "Ketik LANJUT untuk mengubah: " JAWABAN
[[ "$JAWABAN" == "LANJUT" ]] || { echo "Dibatalkan."; exit 0; }
while IFS= read -r BARIS; do
AKSI="${BARIS%%|*}"
ROLE="${BARIS##*|}"
if [[ "$AKSI" == "add" ]]; then
printf ' menambah %s ... ' "$ROLE"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="user:$EMAIL" --role="$ROLE" --condition=None --quiet >/dev/null
else
printf ' mencabut %s ... ' "$ROLE"
gcloud projects remove-iam-policy-binding "$PROJECT_ID" \
--member="user:$EMAIL" --role="$ROLE" --condition=None --quiet >/dev/null || true
fi
echo "selesai"
done <<< "$SELISIH"
echo
echo "Jalankan blok pemeriksaan di bagian 7 sekali lagi."

Kalau yang kurang hanya satu role dan kamu sudah tahu role-nya, blok berikut lebih cepat:

Cloud Shell akun master
PROJECT_ID="geoportal-kelompok-a-xxxxx"
for EMAIL in asisten01@example.com asisten02@example.com; do
echo "menambah untuk $EMAIL"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="user:$EMAIL" \
--role="roles/serviceusage.serviceUsageAdmin" \
--condition=None \
--quiet
done
Project ID: geoportal-kelompok-a-xxxxx
Region: asia-southeast2
Zone: asia-southeast2-b
Slug kamu: dhany (kamu pilih sendiri, huruf kecil, maksimal 18 karakter)
Subdomain kamu: dhany.<domain-instruktur>
Artifact Registry bersama: katalog-images
App directory: /opt/webgis/app

Peserta menentukan slug-nya sendiri. Satu syarat yang tidak bisa ditawar: slug harus unik di dalam projectnya, karena ia menjadi nama VM, static IP, dan Service Account. Dua peserta dengan slug sama di satu project akan membuat VM dengan nama yang sama.

Kirim juga dua hal berikut, karena keduanya menentukan apakah record DNS-nya bisa dibuat:

  1. Kunci alamat IP statis sebelum melaporkan IP-nya. Alamat sementara berubah saat VM di-stop lalu di-start ulang, dan record A yang menunjuk ke alamat lama membuat situsnya mati sekaligus membuat sertifikat gagal diterbitkan.
  2. Pakai network tag webgis-http pada VM, karena firewall bersama menargetkan tag itu.

Kirimkan juga perintah ini supaya peserta dapat memastikan sendiri bahwa aksesnya siap:

Cloud Shell peserta
PROJECT_ID="geoportal-kelompok-a-xxxxx"
EMAIL="$(gcloud config get-value account 2>/dev/null)"
gcloud projects get-iam-policy "$PROJECT_ID" \
--flatten="bindings[].members" \
--filter="bindings.members=user:$EMAIL" \
--format="value(bindings.role)" | sort

Keluarannya harus berisi sembilan baris. Peserta yang melihat jumlah berbeda, atau menerima PERMISSION_DENIED, melapor lebih dahulu. Keduanya berarti aksesnya belum siap, dan itu lebih murah diperbaiki sebelum sesi daripada di tengah praktik.

Peserta yang lebih nyaman tanpa terminal dapat memeriksanya di Console pada IAM & Admin, IAM, dan mencari namanya sendiri. Kalau namanya tidak muncul, penyebab paling sering adalah project yang dibuka bukan project kelompoknya.

Jangan percaya laporan “sudah statis”. Cocokkan setiap IP yang dilaporkan dengan daftar alamat yang benar-benar di-reserve:

Cloud Shell akun master
PROJECT_ID="geoportal-kelompok-a-xxxxx"
REGION="asia-southeast2"
gcloud compute addresses list \
--project="$PROJECT_ID" \
--regions="$REGION" \
--format="table(name,address,status,users)"

Kolom status harus RESERVED atau IN_USE, dan address harus sama persis dengan yang dilaporkan peserta. IP yang tidak muncul pada daftar itu adalah alamat sementara. Peserta yang belum mengunci alamatnya dicatat, lalu kamu yang menguncinya:

Cloud Shell akun master
VM="<nama-vm>"
SLUG="<slug>"
VM_ZONE="$(gcloud compute instances describe "$VM" \
--project="$PROJECT_ID" --format='value(zone)' | sed 's|.*/||')"
gcloud compute addresses create "webgis-ip-${SLUG}" \
--project="$PROJECT_ID" --region="$REGION"
gcloud compute instances delete-access-config "$VM" \
--access-config-name="external-nat" \
--zone="$VM_ZONE" --project="$PROJECT_ID"
gcloud compute instances add-access-config "$VM" \
--access-config-name="external-nat" \
--address="$(gcloud compute addresses describe "webgis-ip-${SLUG}" \
--project="$PROJECT_ID" --region="$REGION" --format='value(address)')" \
--zone="$VM_ZONE" --project="$PROJECT_ID"

Urutannya mengikat: alamat statis dibuat lebih dahulu, baru dipasang. Menghapus konfigurasi akses tanpa menyiapkan penggantinya membuat VM kehilangan IP publiknya.

Subdomain IP statis Project Email
dhany.<domain> 34.x.x.x geoportal-kelompok-a-xxxxx dhany@example.com

Aturan yang menyertai tabel: IP harus sudah statis sebelum tabel dikirim, dan record A dibuat sebelum peserta menjalankan Certbot. Urutan terbalik membuat sertifikat gagal diterbitkan dengan pesan yang tidak menyebut sebabnya.

Daftar subdomain yang sudah terpakai disimpan di satu tempat terpusat, bukan di dalam salah satu akun master.

  • Billing Account aktif dan tiga project tertaut padanya.
  • Sembilan role terlihat untuk setiap email peserta, sesuai nama role, bukan hanya jumlahnya.
  • API aktif, termasuk iam.googleapis.com.
  • Artifact Registry dan dua firewall rule tersedia di ketiga project.
  • Ketiga GitHub connection berstatus COMPLETE, dan roles/secretmanager.admin sudah dicabut dari Cloud Build Service Agent di ketiga project (bagian 4.2).
  • Budget alert aktif untuk setiap project dan untuk Billing Account.
  • Label batch dan billing-code terpasang pada ketiga project.
  • Setiap project berisi paling banyak empat peserta ber-IP publik.
  • Setiap peserta menerima Project ID, region, zone, slug, dan subdomainnya.
  • Akses diberikan minimal satu hari sebelum sesi, supaya layar persetujuan layanan selesai sebelum peserta mulai bekerja.
  • Daftar subdomain terpusat sudah diperiksa terhadap subdomain yang sudah dipakai.
  • Tabel serah terima subdomain → IP statis sudah dikirim ke instruktur.

Prosedur membongkar tiga project sekaligus ada di panduan flushing akun master. Yang tidak boleh ikut terhapus di antara batch: Artifact Registry katalog-images, dua firewall rule, API, dan tautan Billing Account.

Dua skrip di repo tidak dipakai pada alur ini dan tidak perlu disalin ke Cloud Shell: gcp-setup/scripts/batch-peserta.sh untuk impor dari berkas CSV, dan gcp-setup/scripts/cek-peran.py untuk memeriksa role lewat JSON. Keduanya disimpan untuk keperluan lain.