Repository navigation
Speed up deploys with cached builds and worker-first rollout #206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
85ba3d1
d087d17
0745f18
dab530f
8c4b265
aa64949
69279f8
cf4fcb9
803fa6f
afc1a58
5907cff
ed61ed6
a9aeb77
4cd63be
033808b
3675f2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -407,6 +407,34 @@ echo "════════════════════════ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # human think-time at the prompt doesn't pollute the timing deliverable. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SCRIPT_START=$(date +%s) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Render UI env + config and kick off the local Angular UI build in the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # background right away so local CPU work (npm ci / ng build) overlaps with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # cloud API, service-account, IAM, Cloud Tasks, bucket, and Firestore setup. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export UI_CONTROL_PLANE_MODE="iap" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| envsubst < ./ui/src/env.template.txt > ./ui/src/env.ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| generate_config | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if grep -q "controlPlaneMode: 'none'" ./ui/src/env.ts; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "ERROR: ui/src/env.ts rendered with controlPlaneMode 'none' (sign-in disabled)." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UI_BUILD_PID="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UI_BUILD_LOG="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$SKIP_UI_BUILD" != "1" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UI_BUILD_LOG=$(mktemp) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export NG_CLI_ANALYTICS=ci | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -f ui/node_modules/.package-lock.stamp ] \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && cmp -s ui/package-lock.json ui/node_modules/.package-lock.stamp; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " ✓ ui/package-lock.json unchanged — skipping npm ci." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( cd ui && npm ci ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cp ui/package-lock.json ui/node_modules/.package-lock.stamp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( cd ui && npx ng build --configuration production ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) >"$UI_BUILD_LOG" 2>&1 & | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
christophervoelpel marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UI_BUILD_PID=$! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+493
to
+513
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed and applied. When Added |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # --- Enable services --------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Note: compute.googleapis.com is enabled here so the default Compute Engine | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # service account (used for role bindings below) is guaranteed to exist. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -817,12 +845,13 @@ if [ "$SKIP_UI_BUILD" = "1" ]; then | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[skip] Building the Angular UI — skipped (--skip-ui-build); reusing ui/dist." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| phase "Building the Angular UI (npm ci + ng build)..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export NG_CLI_ANALYTICS=ci | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cd ui \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && npm ci \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && npx ng build --configuration production | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! wait "$UI_BUILD_PID"; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cat "$UI_BUILD_LOG" >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$UI_BUILD_LOG" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cat "$UI_BUILD_LOG" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$UI_BUILD_LOG" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # --- Version stamp + Artifact Registry + ONE image build ----------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -833,9 +862,11 @@ COMMIT_DATE=$(git log -1 --format=%cI 2>/dev/null || echo "unknown") | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "${GIT_BRANCH}/${COMMIT_DATE}" > deployed_version.txt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sync | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILD_MACHINE_ARGS=() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! gcloud artifacts repositories describe "${ARTIFACT_REPO}" --project=$PROJECT --location="$REGION" &> /dev/null; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Creating artifact repository: $ARTIFACT_REPO" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gcloud artifacts repositories create "${ARTIFACT_REPO}" --repository-format=docker --project=$PROJECT --location="$REGION" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILD_MACHINE_ARGS=(--machine-type=e2-highcpu-8) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # NOTE: the repo's .gcloudignore excludes ui/* but re-includes ui/dist/ and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ui/remix-engine-status-viewer/ — both are LOAD-BEARING for this build: the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -858,15 +889,18 @@ echo " (this step is quiet — the build runs remotely; a heartbeat prints belo | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILD_SUBS="_IMAGE=${IMAGE}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$NO_BUILD_CACHE" = "1" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILD_SUBS="${BUILD_SUBS},_USE_CACHE=0" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILD_MACHINE_ARGS=(--machine-type=e2-highcpu-8) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " Docker layer cache: OFF (--no-build-cache; forcing a cold rebuild)." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " Docker layer cache: ON (reuses unchanged layers from the previous image)." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run_with_heartbeat "Cloud Build" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gcloud builds submit . --config=cloudbuild.yaml --substitutions="$BUILD_SUBS" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --project=$PROJECT --region=$REGION | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "${BUILD_MACHINE_ARGS[@]}" --project=$PROJECT --region=$REGION | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # --- Cloud Run: worker (private, Cloud-Tasks-invoked) -------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WORKER_DEPLOY_PID="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WORKER_ERR_FILE="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$APP_ONLY" = "1" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| phase "Reusing existing 'worker' Cloud Run service (--app-only)..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[skip] Deploying 'worker' — skipped (--app-only); reusing the live service." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -879,27 +913,23 @@ if [ "$APP_ONLY" = "1" ]; then | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " Reusing worker: ${WORKER_URL}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| phase "Deploying 'worker' Cloud Run service (private)..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # GUNICORN_TIMEOUT just above the worker's 1800s Cloud Run request timeout so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # gunicorn reaps a thread only AFTER Cloud Run has already returned, never | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # killing a legitimate long render mid-flight. (D7) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gcloud run deploy worker --image "$IMAGE" --region $REGION --project $PROJECT \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --cpu=8 --memory=16G --timeout=1800 --no-allow-unauthenticated \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --service-account="$RUNTIME_SA" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --set-env-vars=ROLE=worker,GUNICORN_TIMEOUT=1830 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WORKER_URL=$(gcloud run services describe worker --region=$REGION --project=$PROJECT --format='value(status.url)') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "✓ Worker deployed: ${WORKER_URL}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # The only run.invoker grant the runtime SA gets: service-scoped to the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # worker, exactly what the Cloud-Tasks-minted OIDC tokens need to invoke it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # (There is no project-wide run.invoker, so the app cannot invoke other | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Cloud Run services.) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Granting service-scoped run.invoker on 'worker' to ${RUNTIME_SA}..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| add_run_invoker_binding worker "$REGION" "$PROJECT" "serviceAccount:${RUNTIME_SA}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| EXISTING_WORKER_URL=$(gcloud run services describe worker --region=$REGION --project=$PROJECT --format='value(status.url)' 2>/dev/null || true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WORKER_URL="${EXISTING_WORKER_URL:-https://worker-${PROJECT_NUMBER}.${REGION}.run.app}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
christophervoelpel marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WORKER_ERR_FILE=$(mktemp) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! gcloud run deploy worker --image "$IMAGE" --region "$REGION" --project "$PROJECT" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --cpu=8 --memory=16G --timeout=1800 --no-allow-unauthenticated \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --service-account="$RUNTIME_SA" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --set-env-vars=ROLE=worker,GUNICORN_TIMEOUT=1830 >/dev/null 2>"$WORKER_ERR_FILE"; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| add_run_invoker_binding worker "$REGION" "$PROJECT" "serviceAccount:${RUNTIME_SA}" >/dev/null 2>>"$WORKER_ERR_FILE" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) & | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WORKER_DEPLOY_PID=$! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # --- Cloud Run: app (UI + same-origin /api control plane) ----------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| phase "Deploying 'app' Cloud Run service (AUTH_MODE=${AUTH_MODE})..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| phase "Deploying 'worker' and 'app' Cloud Run services in parallel (AUTH_MODE=${AUTH_MODE})..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IAP_FLAG_AVAILABLE=true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # IAP front door. The --iap flag (built-in IAP for Cloud Run, GA March 2026) may | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # not exist on older gcloud installs — gate it behind a CLI capability check and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -939,6 +969,16 @@ fi | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IAP_SA="service-${PROJECT_NUMBER}@gcp-sa-iap.iam.gserviceaccount.com" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Granting service-scoped run.invoker on 'app' to the IAP service agent..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| add_run_invoker_binding app "$REGION" "$PROJECT" "serviceAccount:${IAP_SA}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -n "$WORKER_DEPLOY_PID" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! wait "$WORKER_DEPLOY_PID"; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cat "$WORKER_ERR_FILE" >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$WORKER_ERR_FILE" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$WORKER_ERR_FILE" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WORKER_URL=$(gcloud run services describe worker --region=$REGION --project=$PROJECT --format='value(status.url)') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "✓ Worker deployed: ${WORKER_URL}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| APP_URL=$(gcloud run services describe app --region=$REGION --project=$PROJECT --format='value(status.url)') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "✓ App deployed: ${APP_URL}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Nothing was predicted: the image carries only same-origin URLs, so the app and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.