Files
substrate/benchmarking/workloads/deploy.sh
T
Zoe Zhao f6852b7754 Update existing demos and benchmark tests to use substrate ActorTemplate resource (#1355)
This PR is very large since it updates all existing demos and benchmark
workloads to use the new ActorTemplate substrate proto.
Please use the "Commits" tab to review individual commits.

Verifications done: 
* Used this script: gpaste/5143788763348992 to verify that the change
from CRD -> proto are equivalent.
* The e2e tests are using the new susbtrate resources.
* Picked the parking demo to run e2e manually: gpaste/6193361380311040
2026-09-01 14:50:18 -07:00

313 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit -o nounset -o pipefail
ROOT="$(git rev-parse --show-toplevel)"
cd "${ROOT}"
# Source the environment variables if configured
if [[ -f .ate-dev-env.sh ]]; then
source .ate-dev-env.sh
fi
# Ensure BUCKET_NAME is set
if [[ -z "${BUCKET_NAME:-}" ]]; then
echo "Error: BUCKET_NAME environment variable is not set." >&2
exit 1
fi
MANIFEST_DIR="benchmarking/workloads/manifests"
POOL_MANIFEST="${MANIFEST_DIR}/workloads.yaml.tmpl"
# The benchmark ActorTemplates: <name>-template.yaml.tmpl each, created
# through the ate API in the benchmark-workloads atespace. WORKLOAD_TEMPLATES
# overrides the default set — the usermem and kernelmem templates (for the
# matching locust tests) are not deployed by default.
read -r -a TEMPLATES <<<"${WORKLOAD_TEMPLATES:-sleep glutton glutton-durdir-data glutton-durdir-full}"
if [[ ! -f "${POOL_MANIFEST}" ]]; then
echo "Error: ${POOL_MANIFEST} not found in $(pwd)" >&2
exit 1
fi
WORKER_COUNT=1
SANDBOX_CLASS="gvisor"
# Actor memory limit (ActorTemplate spec.resources.limits.memory). The default
# is the smallest size microvm admits (128Mi VMM reserve + 128Mi guest floor),
# so benchmark actors do not inherit the 2 GiB kata default and drag its page
# cache into every memory snapshot. Raise it for RAM-consuming suites.
ACTOR_MEMORY="256Mi"
# The address to which an instrumented actor container sends its telemetry.
# --otlp-endpoint sets it. Without the flag, resolve_otlp_endpoint reads the
# address that the control plane uses.
OTLP_ENDPOINT=""
# The timeout, in whole seconds, for waiting for the ateom worker pods to be
# ready.
WAIT_TIMEOUT_SECS=300
usage() {
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " --deploy Substitute env vars and deploy workloads to the cluster using ko apply"
echo " --delete Substitute env vars and delete workloads from the cluster"
echo " --worker-count N Number of WorkerPool replicas (default: 1)"
echo " --sandbox-class CLASS Sandbox runtime for the WorkerPool: gvisor | microvm (default: gvisor)."
echo " microvm requires hack/install-microvm-deps.sh --install to have run."
echo " --actor-memory SIZE Memory limit for the benchmark ActorTemplates (default: 256Mi,"
echo " the smallest size microvm admits)"
echo " --otlp-endpoint URL The address to which an instrumented actor container"
echo " sends telemetry (default: the endpoint in the"
echo " ate-otel-config ConfigMap)"
echo " --wait-timeout SECONDS The timeout in seconds for waiting for the ateom workers to be ready (default: 300)"
echo " -h, --help Show this help message"
}
# Read the endpoint from the ate-otel-config ConfigMap, which every control
# plane component reads through envFrom. The value is correct for the cluster
# in use: the GKE collector, the kind collector in otel-system, or the
# telemetry meter while a measurement runs. Thus the actors follow the control
# plane, and this script needs no test for the type of cluster.
#
# ate-system must exist, because the workloads below need its CRDs. Thus an
# absent ConfigMap is an error and not a condition to work around: a default
# here would send the actor telemetry to the wrong collector with no message.
resolve_otlp_endpoint() {
if [[ -n "${OTLP_ENDPOINT}" ]]; then
return 0
fi
OTLP_ENDPOINT="$(kubectl get configmap ate-otel-config --namespace=ate-system \
-o jsonpath='{.data.OTEL_EXPORTER_OTLP_ENDPOINT}' 2>/dev/null || true)"
if [[ -z "${OTLP_ENDPOINT}" ]]; then
echo "Error: cannot read OTEL_EXPORTER_OTLP_ENDPOINT from the ate-otel-config" >&2
echo "ConfigMap in ate-system. Deploy ate-system first, or give --otlp-endpoint." >&2
exit 1
fi
}
# kubectl-ate runs once per poll while waiting for the golden snapshots;
# build_kubectl_ate builds it once up front instead of paying the `go run`
# toolchain startup on every call.
KUBECTL_ATE_BIN=""
build_kubectl_ate() {
local dir
dir="$(mktemp -d)"
trap 'rm -rf '"${dir}" EXIT
KUBECTL_ATE_BIN="${dir}/kubectl-ate"
go build -o "${KUBECTL_ATE_BIN}" ./cmd/kubectl-ate
}
run_kubectl_ate() {
"${KUBECTL_ATE_BIN}" "$@"
}
substitute() {
# SandboxConfig names are pinned per class (rather than defaulted) so a stale
# config from a dirty teardown fails loudly instead of silently binding this
# pool. gvisor-default is applied by hack/install-ate.sh; microvm is applied
# by hack/install-microvm-deps.sh. The protojson templates take the sandbox
# class as its proto enum spelling.
local manifest="$1"
local sandbox_config_name sandbox_class_enum
case "${SANDBOX_CLASS}" in
gvisor) sandbox_config_name="gvisor-default" sandbox_class_enum="SANDBOX_CLASS_GVISOR" ;;
microvm) sandbox_config_name="microvm" sandbox_class_enum="SANDBOX_CLASS_MICROVM" ;;
esac
sed -e "s|\${BUCKET_NAME}|${BUCKET_NAME}|g" \
-e "s|\${WORKER_COUNT}|${WORKER_COUNT}|g" \
-e "s|\${SANDBOX_CLASS}|${SANDBOX_CLASS}|g" \
-e "s|\${SANDBOX_CLASS_ENUM}|${sandbox_class_enum}|g" \
-e "s|\${SANDBOX_CONFIG_NAME}|${sandbox_config_name}|g" \
-e "s|\${OTLP_ENDPOINT}|${OTLP_ENDPOINT}|g" \
-e "s|\${ACTOR_MEMORY}|${ACTOR_MEMORY}|g" \
"${manifest}"
}
# wait_actortemplate_ready polls a substrate ActorTemplate resource until its
# golden snapshot exists (the substrate counterpart of `kubectl wait
# --for=condition=Ready actortemplate/...`). Fails fast when the template
# reconciler reports an error.
wait_actortemplate_ready() {
local atespace="$1"
local template="$2"
local timeout_secs="${3:-300}"
local deadline=$((SECONDS + timeout_secs))
local json snapshot error_message
while ((SECONDS < deadline)); do
if json=$(run_kubectl_ate get actor-template "${template}" -a "${atespace}" -o json 2>/dev/null); then
snapshot=$(jq -r '.actorTemplates[0].status.goldenSnapshotStatus.goldenSnapshot.name // empty' <<<"${json}")
if [[ -n "${snapshot}" ]]; then
return 0
fi
error_message=$(jq -r '.actorTemplates[0].status.goldenSnapshotStatus.errorMessage // empty' <<<"${json}")
if [[ -n "${error_message}" ]]; then
echo "actor template ${atespace}/${template} failed: ${error_message}" >&2
return 1
fi
fi
sleep 5
done
echo "timed out waiting for actor template ${atespace}/${template} golden snapshot" >&2
return 1
}
# wait_templates_ready blocks until every benchmark template's golden
# snapshot exists (there is no kubectl wait for substrate resources), failing
# fast when the template reconciler reports an error.
wait_templates_ready() {
if ! command -v jq &>/dev/null; then
echo "jq is required to wait for the benchmark actor templates" >&2
return 1
fi
local template
for template in "${TEMPLATES[@]}"; do
echo "Waiting for the benchmark-workloads/${template} golden snapshot..."
wait_actortemplate_ready benchmark-workloads "${template}" "${WAIT_TIMEOUT_SECS}"
done
}
deploy() {
resolve_otlp_endpoint
echo "Deploying workloads (worker_count=${WORKER_COUNT}, actor_memory=${ACTOR_MEMORY}, otlp_endpoint=${OTLP_ENDPOINT})..."
substitute "${POOL_MANIFEST}" | hack/run-tool.sh ko apply -f -
echo "Waiting for worker pool to be ready (timeout: ${WAIT_TIMEOUT_SECS}s)..."
kubectl wait --for=create deployment/benchmark-ateom \
--namespace=benchmark-workloads --timeout="${WAIT_TIMEOUT_SECS}s"
kubectl rollout status deployment/benchmark-ateom \
--namespace=benchmark-workloads --timeout="${WAIT_TIMEOUT_SECS}s"
# The store enforces that a template's atespace exists at create time.
run_kubectl_ate create atespace benchmark-workloads >/dev/null 2>&1 \
|| run_kubectl_ate get atespace benchmark-workloads >/dev/null
# Actor templates are immutable (no update RPC). A value that changes for
# each run — the OTLP endpoint or the sandbox class — needs the removal of
# the old template first. This removal is safe, because the benchmark
# automation deletes the actors between the tests; it also removes the old
# golden actor and snapshot server-side.
local template
for template in "${TEMPLATES[@]}"; do
run_kubectl_ate delete actor-template "${template}" -a benchmark-workloads \
>/dev/null 2>&1 || true
# ko resolve builds the ko:// image references and replaces them with
# pushed digests before the manifest reaches kubectl-ate.
substitute "${MANIFEST_DIR}/${template}-template.yaml.tmpl" \
| hack/run-tool.sh ko resolve -f - \
| run_kubectl_ate create actor-template -f -
done
wait_templates_ready
}
delete() {
echo "Deleting workloads..."
local template
for template in "${TEMPLATES[@]}"; do
run_kubectl_ate delete actor-template "${template}" -a benchmark-workloads \
>/dev/null 2>&1 || true
done
run_kubectl_ate delete atespace benchmark-workloads >/dev/null 2>&1 \
|| echo "atespace benchmark-workloads not deleted (may not exist or is not empty)"
# The pool manifest contains ko:// image references; route through
# `ko delete` so they get resolved before kubectl sees them.
substitute "${POOL_MANIFEST}" | hack/run-tool.sh ko delete --ignore-not-found -f -
}
if [[ "$#" -eq 0 ]]; then
usage
exit 1
fi
action=""
while [[ "$#" -gt 0 ]]; do
case "$1" in
--deploy)
action="deploy"
;;
--delete)
action="delete"
;;
--worker-count)
shift
WORKER_COUNT="$1"
;;
--worker-count=*)
WORKER_COUNT="${1#*=}"
;;
--sandbox-class)
shift
SANDBOX_CLASS="$1"
;;
--sandbox-class=*)
SANDBOX_CLASS="${1#*=}"
;;
--otlp-endpoint)
shift
OTLP_ENDPOINT="$1"
;;
--otlp-endpoint=*)
OTLP_ENDPOINT="${1#*=}"
;;
--actor-memory)
shift
ACTOR_MEMORY="$1"
;;
--actor-memory=*)
ACTOR_MEMORY="${1#*=}"
;;
--wait-timeout)
shift
WAIT_TIMEOUT_SECS="$1"
;;
--wait-timeout=*)
WAIT_TIMEOUT_SECS="${1#*=}"
;;
-h|--help)
usage
exit 0
;;
*)
echo "Error: Unknown option: $1" >&2
usage
exit 1
;;
esac
shift
done
case "${SANDBOX_CLASS}" in
gvisor|microvm) ;;
*)
echo "Error: --sandbox-class must be gvisor or microvm, got '${SANDBOX_CLASS}'" >&2
exit 1
;;
esac
if ! [[ "${WAIT_TIMEOUT_SECS}" =~ ^[0-9]+$ ]]; then
echo "Error: --wait-timeout must be a whole number of seconds like 300, got '${WAIT_TIMEOUT_SECS}'" >&2
exit 1
fi
if [[ "${action}" == "deploy" ]]; then
build_kubectl_ate
deploy
elif [[ "${action}" == "delete" ]]; then
build_kubectl_ate
delete
fi