mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-13 08:52:15 +00:00
ci: backport Rust query regression and ephemeral ECS runners
Backport #8651, #8937, #8986 and #9005. Preserve the five v1.2 routine cases and map tooling checks into the release CI workflow. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
This commit is contained in:
@@ -1,321 +1,184 @@
|
||||
# Query regression self-hosted runners
|
||||
|
||||
The `Query Regression` workflow uses the dedicated ARC runner scale set
|
||||
`perf-regression-8-cores`. ARC runner Pods run in the target Kubernetes cluster
|
||||
and connect outbound to GitHub. The live scale set is currently **paused**:
|
||||
`minRunners=0`, `maxRunners=0`, and no runner Pods. Do not resume it without
|
||||
explicit approval.
|
||||
The `Query Regression` workflow runs on **Aliyun ECS ephemeral runners**
|
||||
(`aliyun-ecs`, the default and only automated path): a `provision` job on
|
||||
`ubuntu-latest` creates one pay-as-you-go ECS instance per run, and the
|
||||
instance registers itself as an ephemeral GitHub runner with a per-run
|
||||
label. A `teardown` job (`if: always()`) deletes the instance;
|
||||
`query-regression-janitor.yml` sweeps tagged leftovers older than 4 hours
|
||||
daily.
|
||||
|
||||
## Prerequisites and trust admission
|
||||
Build caches live on the instance's system disk, so **every run compiles
|
||||
cold**; only the within-run reuse (base build warms the candidate build
|
||||
through the shared target dir and sccache) applies. There is no retained
|
||||
data disk.
|
||||
|
||||
Install the ARC scale set controller if it is not already installed:
|
||||
Dispatching with any other `runner` value uses it as a literal self-hosted
|
||||
runner label, which is how a manually prepared host (see
|
||||
`ecs-image/bootstrap-runner-host.sh`) runs the workflow. For PR labels, set
|
||||
the repository variable `QUERY_REGRESSION_PR_RUNNER` to such a label to
|
||||
redirect PR runs away from ECS.
|
||||
|
||||
The office ARC scale set `perf-regression-8-cores` that previously ran this
|
||||
workflow is retired; see git history for its values files and pause/deploy
|
||||
procedures. Tearing down the office cluster (helm release, runner namespace,
|
||||
and the `query-regression-build-cache` PVC) is a manual operator action
|
||||
outside this repository. This directory keeps its historical
|
||||
`runner-scale-sets` name for path stability.
|
||||
|
||||
## Nightly vs previous nightly
|
||||
|
||||
`query-regression-nightly.yml` runs after a successful `GreptimeDB Nightly
|
||||
Build` (`workflow_run`). It resolves that run's `head_sha` as the candidate
|
||||
and the previous successful nightly (same branch, typically Friday when
|
||||
Monday's nightly fires) as the base, then calls `query-regression.yml` with
|
||||
those immutable SHAs. Both binaries are still compiled on the ECS runner;
|
||||
this is not an artifact-download path. `workflow_dispatch` can pass explicit
|
||||
`base_ref` / `candidate_ref` or a nightly run id. If there is no previous
|
||||
nightly, or both nightlies built the same commit, the comparison is skipped.
|
||||
|
||||
## Aliyun ECS path
|
||||
|
||||
Configuration lives in repository variables/secrets:
|
||||
|
||||
| Kind | Name | Purpose |
|
||||
| --- | --- | --- |
|
||||
| secret | `ALICLOUD_ECS_ACCESS_KEY_ID` / `ALICLOUD_ECS_ACCESS_KEY_SECRET` | RAM user scoped to ECS RunInstances/DeleteInstances/Describe*/CreateImage/RunCommand. Used only by provision/teardown jobs on `ubuntu-latest`; never reaches the ECS instance. |
|
||||
| secret | `GH_PERSONAL_ACCESS_TOKEN` | Creates the short-lived runner registration token (shared with the jsonbench EC2 path). |
|
||||
| vars | `ALIYUN_ECS_REGION_ID` / `ALIYUN_ECS_VSWITCH_ID` / `ALIYUN_ECS_SECURITY_GROUP_ID` | Network placement. The security group should allow egress only; no inbound rules are needed. The vSwitch pins the zone. |
|
||||
| vars | `ALIYUN_ECS_INSTANCE_TYPE` | Dedicated (non-burstable, non-shared) instance family. Prefer 32 GiB (e.g. `ecs.g8i.2xlarge`); `ecs.c9i.2xlarge` is 8c16g and nightly thin-LTO of greptime peaks above that. Both base and candidate clusters run on the same machine, so noisy neighbors break thresholds. |
|
||||
| vars | `QUERY_REGRESSION_ECS_IMAGE_ID` | Custom image built by `ecs-image/build-ecs-image.py`. |
|
||||
|
||||
The system disk is 50 GiB, which covers the image, a 16 GiB swapfile, the
|
||||
checkout, and cold build caches (target dir, cargo registry, sccache). ENOSPC
|
||||
stops the runner itself from writing logs, which GitHub reports as `The
|
||||
operation was canceled` with no telemetry, indistinguishable from a
|
||||
platform-side cancellation.
|
||||
cloud-init masks `systemd-oomd`, disables `unattended-upgrades` /
|
||||
`apt-daily-upgrade`, creates `/swapfile`, and sets `OOMPolicy=continue`
|
||||
on the runner unit. Ubuntu 24.04 defaults to `DefaultOOMPolicy=stop`,
|
||||
which SIGTERM-s the whole unit when rustc is OOM-killed and GitHub
|
||||
reports `The operation was canceled` with no telemetry. Unattended
|
||||
upgrades can do the same via `systemctl restart` of the runner after a
|
||||
library update; GitHub then records `UserCancelled` even though the job
|
||||
was still valid. Swap is a safety net for 16 GiB types, not a substitute
|
||||
for 32 GiB; linking on swap is slow.
|
||||
|
||||
When a run dies with an unexplained `The operation was canceled` (no
|
||||
"Canceled by" banner, healthy machine), re-dispatch with `keep_instance`
|
||||
checked: the teardown job is skipped and the instance survives for
|
||||
post-mortem inspection. The security group is egress-only, so inspect via
|
||||
Cloud Assistant (`RunCommand`) or VNC: the runner's `_diag` logs under the
|
||||
runner home record reconnects and worker crashes, `journalctl -u
|
||||
ephemeral-github-runner.service` mirrors the console stream, `dmesg -T`
|
||||
shows kernel OOM kills, and `machine-telemetry.log` in the job workspace
|
||||
has the 30 s sampler history. The janitor sweep still deletes the instance
|
||||
after its TTL, so finish the inspection within that window.
|
||||
|
||||
Trust model on the ECS path: the instance receives only the one-hour runner
|
||||
registration token via user data and holds no cloud credentials; the Aliyun
|
||||
AK/SK exist only in the control-plane jobs. Instance tags
|
||||
(`managed-by=query-regression-ci`, `query-regression-run-id`) feed the janitor
|
||||
sweep.
|
||||
|
||||
### Building and updating the ECS image
|
||||
|
||||
The runner `Dockerfile` in the parent directory stays the single source of the
|
||||
tool contract. Build a new ECS image from it:
|
||||
|
||||
```bash
|
||||
helm upgrade --install arc \
|
||||
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller \
|
||||
--namespace arc-systems \
|
||||
--create-namespace \
|
||||
--version 0.14.2
|
||||
ALIBABA_CLOUD_ACCESS_KEY_ID=... ALIBABA_CLOUD_ACCESS_KEY_SECRET=... \
|
||||
uv run .github/runner-scale-sets/query-regression/ecs-image/build-ecs-image.py \
|
||||
--region-id <region> --vswitch-id <vsw-...> --security-group-id <sg-...> \
|
||||
--base-image-id <ubuntu-24.04-image-id>
|
||||
```
|
||||
|
||||
Create the GitHub App secret in the runner namespace. Prefer an App limited to
|
||||
`GreptimeTeam/greptimedb`:
|
||||
The script boots a temporary builder instance, `docker build`s the runner
|
||||
image, materializes `/opt/rustup`, `/opt/cargo`, `/usr/local/bin` tools, and
|
||||
`/home/runner` (actions-runner) onto the host, installs the ephemeral-runner
|
||||
systemd unit from `ecs-image/`, and snapshots a custom image. It prints the
|
||||
image id; set it as `QUERY_REGRESSION_ECS_IMAGE_ID`, and bump
|
||||
`RUNNER_IMAGE_EPOCH` in `query-regression.yml` at the same time so the target
|
||||
cache invalidates. Builder sentinel polling requires the Cloud Assistant
|
||||
agent, which Aliyun public Ubuntu images include.
|
||||
|
||||
```bash
|
||||
kubectl -n arc-runners create secret generic greptimedb-arc-github-app \
|
||||
--from-literal=github_app_id=<app-id> \
|
||||
--from-literal=github_app_installation_id=<installation-id> \
|
||||
--from-file=github_app_private_key=<private-key.pem>
|
||||
```
|
||||
|
||||
The values files here reference that secret by name.
|
||||
## Trust admission for PR runs
|
||||
|
||||
A maintainer applying the `query-regression` or `heavy-regression` label is
|
||||
**trust admission for that exact PR revision**. `query-regression` runs the five
|
||||
routine default cases; `heavy-regression` runs only the high-cardinality
|
||||
`prom_remote_write_7913` remote-write case. The admitted job may use this scale
|
||||
set's dedicated, writable persistent cache. `pull_request: labeled` is the only
|
||||
PR trigger: the label event snapshots its merge, head, and base SHAs. A queued
|
||||
job fetches that immutable event merge SHA directly, verifies it is a two-parent
|
||||
merge whose parents include the snapshotted head exactly once, and uses its
|
||||
other parent as the actual base build revision. The snapshotted event base is
|
||||
retained for audit only, so a difference from the merge's non-head parent is
|
||||
not a failure. The job never follows a newer mutable PR merge ref. An
|
||||
unavailable event merge, or one that does not contain exactly one snapshotted
|
||||
head parent, fails closed. A later PR head change does not retarget an already
|
||||
queued run: it may execute only its previously trusted event revision if that
|
||||
revision remains fetchable. To run the new revision, the maintainer must review
|
||||
it, remove the label, and re-add the desired regression label; cancel the old
|
||||
run if it is no longer wanted. An existing label does not automatically rerun
|
||||
the benchmark.
|
||||
**trust admission for that exact PR revision**. `query-regression` runs the
|
||||
five routine default cases; `heavy-regression` runs only the high-cardinality
|
||||
`prom_remote_write_7913` remote-write case. `pull_request: labeled` is the only PR
|
||||
trigger: the label event snapshots its merge, head, and base SHAs. A queued
|
||||
job fetches that immutable event merge SHA directly, verifies it is a
|
||||
two-parent merge whose parents include the snapshotted head exactly once, and
|
||||
uses its other parent as the actual base build revision. The snapshotted
|
||||
event base is retained for audit only, so a difference from the merge's
|
||||
non-head parent is not a failure. The job never follows a newer mutable PR
|
||||
merge ref. An unavailable event merge, or one that does not contain exactly
|
||||
one snapshotted head parent, fails closed. A later PR head change does not
|
||||
retarget an already queued run: it may execute only its previously trusted
|
||||
event revision if that revision remains fetchable. To run the new revision,
|
||||
the maintainer must review it, remove the label, and re-add the desired
|
||||
regression label; cancel the old run if it is no longer wanted. An existing
|
||||
label does not automatically rerun the benchmark.
|
||||
|
||||
Admission does not relax runner hardening or GitHub permissions. Keep
|
||||
service-account token mounting disabled; do not mount host paths, the Docker
|
||||
socket, kubeconfig, or long-lived credentials. The runner and cache initializer
|
||||
use UID/GID 1001, disallow privilege escalation, drop all capabilities, and use
|
||||
the RuntimeDefault seccomp profile. Keep GitHub tokens least-privilege and
|
||||
review workflow changes before admission. Where the CNI supports it, restrict
|
||||
egress to required GitHub Actions, artifact/cache, Rust/crate/toolchain, DNS,
|
||||
and image-registry endpoints; block unrelated cluster services, private ranges,
|
||||
and metadata endpoints unless a case requires them.
|
||||
|
||||
### Network routing prerequisite
|
||||
|
||||
Required split routing is an **external environment-specific prerequisite**. The
|
||||
responsible network operator must route GitHub Actions, GitHub content,
|
||||
artifact/cache, crates.io, Rust toolchain, and image-registry traffic through
|
||||
the approved path rather than the VPN where applicable. Neither this repository
|
||||
nor Kubernetes configures that route. Verify it with the responsible network
|
||||
operator before any canary.
|
||||
Admission does not relax runner hardening or GitHub permissions. Keep the ECS
|
||||
instance free of cloud credentials and long-lived tokens, keep the security
|
||||
group egress-only, keep GitHub tokens least-privilege, and review workflow
|
||||
changes before admission.
|
||||
|
||||
## Runner image and workflow tools
|
||||
|
||||
Build and push the derived runner image; it preserves the official
|
||||
`/home/runner/run.sh` entrypoint and supplies CI tools needed at runtime. The
|
||||
image builds `otelgen` from
|
||||
The runner `Dockerfile` builds `otelgen` from
|
||||
[`WenyXu/otelgen`](https://github.com/WenyXu/otelgen) commit
|
||||
[`863a3f395d062c7322cc1de08a38774b7fdaa6c8`](https://github.com/WenyXu/otelgen/commit/863a3f395d062c7322cc1de08a38774b7fdaa6c8)
|
||||
so trace cases do not download or compile tools during a benchmark run:
|
||||
so trace cases do not download or compile tools during a benchmark run. It is
|
||||
built only as a toolchain factory: `build-ecs-image.py` and
|
||||
`bootstrap-runner-host.sh` both materialize its contents onto a host, and the
|
||||
benchmark itself runs host-native.
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--platform linux/amd64 \
|
||||
-f .github/runner-scale-sets/query-regression/Dockerfile \
|
||||
-t greptime-registry.cn-hangzhou.cr.aliyuncs.com/greptime/greptimedb-query-regression-runner:latest \
|
||||
.github/runner-scale-sets/query-regression
|
||||
|
||||
docker push greptime-registry.cn-hangzhou.cr.aliyuncs.com/greptime/greptimedb-query-regression-runner:latest
|
||||
```
|
||||
|
||||
Deploy by digest, not mutable tag, by updating both image references in
|
||||
`values-8-cores.yaml` after a rebuild. Update `RUNNER_IMAGE_DIGEST` and bump
|
||||
`RUNNER_IMAGE_EPOCH` in `query-regression.yml` at the same time. If the registry
|
||||
is private, use a dedicated read-only pull secret only as `imagePullSecrets`;
|
||||
never expose registry credentials to runner containers.
|
||||
Both digest-pinned init and runner containers use `IfNotPresent`: the immutable
|
||||
digest makes a cached image safe and avoids adding a registry dependency to every
|
||||
runner startup.
|
||||
|
||||
The runner optionally imports only the non-sensitive `HTTP_PROXY`,
|
||||
`HTTPS_PROXY`, and `NO_PROXY` variables from the
|
||||
`query-regression-runner-local-env` ConfigMap. Manage that ConfigMap locally in
|
||||
the target namespace; private endpoint configuration must not be committed, and
|
||||
credentials or secrets must never be placed in a ConfigMap.
|
||||
|
||||
Before builds, the workflow asserts UID/GID 1001 and exact image tool versions:
|
||||
`libprotoc 3.21.12`, `uv 0.11.26`, `mold 2.30.0`, `Python 3.12.3`, `sccache
|
||||
0.16.0`, `otelgen` commit `863a3f395d062c7322cc1de08a38774b7fdaa6c8`,
|
||||
root-owned `rustup 1.29.0`, and the image-baked
|
||||
`nightly-2026-03-21` Rust toolchain. Rustup, Cargo, and Rustc must resolve from
|
||||
`/opt/cargo/bin`; the runner cannot write `/opt/rustup` or `/opt/cargo/bin`.
|
||||
Protobuf well-known includes, including `google/protobuf/any.proto` and
|
||||
`google/protobuf/empty.proto`, are an image contract and must compile with
|
||||
`protoc`.
|
||||
Before builds, the workflow asserts the runner UID/GID (1001 in the ECS image;
|
||||
overridable via `QUERY_REGRESSION_RUNNER_UID`/`QUERY_REGRESSION_RUNNER_GID`)
|
||||
and exact tool versions: `libprotoc 3.21.12`, `uv 0.11.26`, `mold 2.40.4`,
|
||||
`Python 3.14.4`, `sccache 0.16.0`, `otelgen` commit
|
||||
`863a3f395d062c7322cc1de08a38774b7fdaa6c8`, root-owned `rustup 1.29.0`, and
|
||||
the image-baked `nightly-2026-03-21` Rust toolchain. `mold` and `python3`
|
||||
come from apt at image-build time (not Ubuntu 24.04's default 3.12); bump
|
||||
the Verify pins together with `QUERY_REGRESSION_ECS_IMAGE_ID` when the
|
||||
image is rebuilt. Rustup, Cargo, and Rustc
|
||||
must resolve from `/opt/cargo/bin`; the runner cannot write `/opt/rustup` or
|
||||
`/opt/cargo/bin`. Protobuf well-known includes, including
|
||||
`google/protobuf/any.proto` and `google/protobuf/empty.proto`, are an image
|
||||
contract and must compile with `protoc`.
|
||||
`actions-rust-lang/setup-rust-toolchain@v1` is intentionally removed. The
|
||||
workflow sets its warning-denying mold `RUSTFLAGS` directly, disables automatic
|
||||
Rustup installation, and performs no runtime toolchain downloads.
|
||||
workflow sets its warning-denying mold `RUSTFLAGS` directly, disables
|
||||
automatic Rustup installation, and performs no runtime toolchain downloads.
|
||||
|
||||
The workflow no longer uses GitHub `rust-cache`, `setup-protoc`, `setup-uv`, or
|
||||
runtime Rust setup: the image establishes immutable executable state and the PVC
|
||||
supplies only reusable Cargo data. Do not reintroduce those actions unless the
|
||||
corresponding cache or image contract changes.
|
||||
The workflow no longer uses GitHub `rust-cache`, `setup-protoc`, `setup-uv`,
|
||||
or runtime Rust setup: the image establishes immutable executable state and
|
||||
each instance's system disk holds only that run's Cargo data. Do not
|
||||
reintroduce those actions unless the image contract changes.
|
||||
|
||||
## Capacity and persistent cache
|
||||
## Capacity
|
||||
|
||||
`values-8-cores.yaml` is normal operation: `minRunners=0`, `maxRunners=1`.
|
||||
`values-paused.yaml` is the mandatory pause overlay: `minRunners=0`,
|
||||
`maxRunners=0`. The job uses group `query-regression-persistent-cache-v1`,
|
||||
`queue: max`, and `cancel-in-progress: false`; admitted jobs queue rather than
|
||||
replacing older pending jobs. During maintenance, cancel admitted queued runs as
|
||||
well as pausing ARC. Runner Pods have `activeDeadlineSeconds=12600`.
|
||||
The runner requests 6 CPU and limits at 8 CPU to preserve `minipc-3`
|
||||
allocatable-capacity scheduling headroom; do not reset the request to 8 CPU
|
||||
without revalidating scheduling capacity.
|
||||
Each run provisions its own ECS instance, so overlapping dispatches proceed
|
||||
in parallel. There is no workflow `concurrency` group. All Cargo state
|
||||
(`CARGO_HOME` including registry/git, `CARGO_TARGET_DIR`, sccache, cache
|
||||
metadata) lives on the 50 GiB system disk and is discarded with the VM.
|
||||
`RUSTUP_HOME=/opt/rustup` and `/opt/cargo/bin` are image-owned. The runner
|
||||
sets `RUSTC_WRAPPER=/usr/local/bin/sccache`,
|
||||
`SCCACHE_DIR=/home/runner/.cache/sccache`, `SCCACHE_CACHE_SIZE=10G`, and
|
||||
`CARGO_INCREMENTAL=0`. sccache uses its local disk backend and self-evicts
|
||||
at 10G. Base and candidate builds share the target on that disk; Cargo
|
||||
fingerprints invalidate source and dependency changes.
|
||||
|
||||
The cache claim `query-regression-build-cache` is a nominal 600Gi `local-path`
|
||||
PVC in `arc-runners`. It is `ReadWriteOnce`; `local-path` uses
|
||||
WaitForFirstConsumer binding and Delete reclaim behavior, produces a
|
||||
node-affine local PV, is non-expandable, and the 600Gi request is not a hard
|
||||
storage quota. The runner's `minipc-3` selector is its only consumer candidate.
|
||||
|
||||
The initializer mounts the PVC root at `/cache`, creates and write-tests these
|
||||
versioned subpaths as non-root UID/GID 1001, and the runner mounts them as:
|
||||
|
||||
| Persistent state | PVC subpath | Runner mount |
|
||||
| --- | --- | --- |
|
||||
| Ephemeral Cargo home | `emptyDir` | `/home/runner/.cargo` |
|
||||
| Cargo registry data | `cargo-registry-v1` | `/home/runner/.cargo/registry` |
|
||||
| Cargo Git data | `cargo-git-v1` | `/home/runner/.cargo/git` |
|
||||
| Cargo target | `query-regression-target-v1` | `/home/runner/query-regression-target` |
|
||||
| Cache metadata | `meta-v1` | `/home/runner/query-regression-cache-meta` |
|
||||
| sccache local disk cache | `sccache-v1` | `/home/runner/.cache/sccache` |
|
||||
| Immutable Rust toolchain | image-owned | `/opt/rustup`, `/opt/cargo/bin` |
|
||||
|
||||
The Pod security context uses UID/GID and `fsGroup` 1001 with
|
||||
`fsGroupChangePolicy: OnRootMismatch`; no privileged `chown` or raw `hostPath`
|
||||
is used. `CARGO_HOME` is a per-Pod `emptyDir`; only its nested `registry` and
|
||||
`git` mounts are persistent. `RUSTUP_HOME=/opt/rustup` and `/opt/cargo/bin` are
|
||||
image-owned immutable paths, while `CARGO_TARGET_DIR`, cache metadata, and
|
||||
`SCCACHE_DIR` are persistent absolute paths. The runner sets
|
||||
`RUSTC_WRAPPER=/usr/local/bin/sccache`,
|
||||
`SCCACHE_DIR=/home/runner/.cache/sccache`, `SCCACHE_CACHE_SIZE=40G`, and
|
||||
`CARGO_INCREMENTAL=0`. sccache uses its local PVC disk backend and self-evicts
|
||||
at 40G; do not add runtime downloads, object storage, or a shared backend.
|
||||
|
||||
The repository's `.cargo/config.toml` remains a trusted per-revision build input.
|
||||
In contrast, `$CARGO_HOME/config*`, credentials, installed bins, and Cargo
|
||||
metadata outside the persistent `registry` and `git` data mounts are ephemeral
|
||||
and cannot survive to another Pod.
|
||||
|
||||
The local disk backend has a one-server constraint. `maxRunners=1` and the
|
||||
unchanged `query-regression-persistent-cache-v1` workflow concurrency group
|
||||
serialize runs; do not increase runner capacity or relax that serialization
|
||||
while this backend is in use. Base and candidate builds share the target; Cargo
|
||||
fingerprints invalidate source and dependency changes. The workflow records the
|
||||
sccache version and relevant environment in the target ABI marker, starts and
|
||||
zeros sccache after cache and toolchain checks, shows initial/base/candidate
|
||||
statistics, and resets statistics between base and candidate builds.
|
||||
|
||||
### Disk preflight and cleanup contract
|
||||
|
||||
Before applying or unpausing, verify the backing filesystem on `minipc-3` has
|
||||
at least 900GiB free. The current local-path provisioner source is
|
||||
`/opt/local-path-provisioner`; measure the filesystem containing it:
|
||||
|
||||
```bash
|
||||
df -PB1G /opt/local-path-provisioner
|
||||
```
|
||||
|
||||
The workflow reports `du`, `df -P`, human-readable free space, and inode
|
||||
availability before builds and in an always-run report. Its cleanup is narrow
|
||||
and non-destructive:
|
||||
|
||||
- warn at target size 400GiB; at 450GiB clear only the complete target root;
|
||||
- warn at Cargo registry-plus-Git data size 60GiB; at 80GiB remove only
|
||||
`registry/src` and `git/checkouts`, then abort if that persistent data remains
|
||||
at least 80GiB;
|
||||
- below 300GiB backing free space, clear the complete target root first,
|
||||
remeasure, then remove only those Cargo extracted trees and checkouts; abort
|
||||
if free space is still below 300GiB;
|
||||
- never automatically remove Cargo registry cache/index, Git database, the
|
||||
image-owned Cargo bin or Rustup toolchain, cache metadata, the self-evicting
|
||||
sccache directory, or the PVC.
|
||||
|
||||
The target clear uses fixed absolute roots and removes all entries, including
|
||||
dotfiles. After migration validation, remove obsolete `cargo-home-v1` and
|
||||
`rustup-home-v1` only in explicit maintenance while ARC is 0/0 and no runner Pod
|
||||
exists; they are not mounted by the current configuration.
|
||||
|
||||
## Deploy and pause safely
|
||||
|
||||
First verify the configured external network route and the disk preflight. Apply the PVC; while
|
||||
the scale set is paused, it is expected to remain `Pending` because
|
||||
WaitForFirstConsumer has no scheduled runner:
|
||||
|
||||
```bash
|
||||
kubectl apply --dry-run=server \
|
||||
-f .github/runner-scale-sets/query-regression/cache-pvc.yaml
|
||||
kubectl apply -f .github/runner-scale-sets/query-regression/cache-pvc.yaml
|
||||
```
|
||||
|
||||
Render normal and paused configurations. Normal values are always first; the
|
||||
pause overlay is always last:
|
||||
|
||||
```bash
|
||||
helm template perf-regression-8-cores \
|
||||
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
|
||||
--namespace arc-runners --version 0.14.2 \
|
||||
--set controllerServiceAccount.name=arc-gha-rs-controller \
|
||||
--set controllerServiceAccount.namespace=arc-systems \
|
||||
-f .github/runner-scale-sets/query-regression/values-8-cores.yaml
|
||||
|
||||
helm template perf-regression-8-cores \
|
||||
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
|
||||
--namespace arc-runners --version 0.14.2 \
|
||||
--set controllerServiceAccount.name=arc-gha-rs-controller \
|
||||
--set controllerServiceAccount.namespace=arc-systems \
|
||||
-f .github/runner-scale-sets/query-regression/values-8-cores.yaml \
|
||||
-f .github/runner-scale-sets/query-regression/values-paused.yaml
|
||||
```
|
||||
|
||||
The **first post-merge Helm deployment must reconcile the release in paused
|
||||
mode**. Keep the pause overlay last:
|
||||
|
||||
```bash
|
||||
# First post-merge deployment and every return to paused mode: 0/0.
|
||||
helm upgrade --install perf-regression-8-cores \
|
||||
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
|
||||
--namespace arc-runners --create-namespace --version 0.14.2 \
|
||||
--reset-values --wait \
|
||||
-f .github/runner-scale-sets/query-regression/values-8-cores.yaml \
|
||||
-f .github/runner-scale-sets/query-regression/values-paused.yaml
|
||||
|
||||
# Expect 0/0 and no runner resources before considering normal mode.
|
||||
kubectl -n arc-runners get autoscalingrunnerset perf-regression-8-cores \
|
||||
-o jsonpath='{.spec.minRunners}{"/"}{.spec.maxRunners}{"\n"}'
|
||||
kubectl -n arc-runners get ephemeralrunners,pods \
|
||||
-l actions.github.com/scale-set-name=perf-regression-8-cores
|
||||
```
|
||||
|
||||
Only after that verification and separate explicit approval, apply normal 0/1
|
||||
operation without the pause overlay:
|
||||
|
||||
```bash
|
||||
helm upgrade --install perf-regression-8-cores \
|
||||
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
|
||||
--namespace arc-runners --create-namespace --version 0.14.2 \
|
||||
--reset-values --wait \
|
||||
-f .github/runner-scale-sets/query-regression/values-8-cores.yaml
|
||||
```
|
||||
|
||||
Do not use bare `helm rollback`, `--atomic`, or `--reuse-values`: a stored
|
||||
revision can restore nonzero runner capacity. Inspect rendered manifests for
|
||||
capacity, the `minipc-3` selector, cache claim and mounts, initializer,
|
||||
security context, and resources. After approved normal mode receives its first
|
||||
canary, the PVC binds to `minipc-3`.
|
||||
|
||||
For local-PV node loss, cache recovery is intentionally disposable: return to
|
||||
0/0, recreate the PVC on a healthy node, cold-fill it, and run a new canary.
|
||||
|
||||
## Canary and rollback
|
||||
|
||||
With explicit approval, run two identical `workflow_dispatch` canaries on
|
||||
`perf-regression-8-cores`, using immutable full base and candidate commit SHAs
|
||||
and `cargo_profile=nightly`. The first is the cold fill; the second verifies warm
|
||||
reuse. Record the workflow's base/candidate build elapsed logs and cache
|
||||
ABI-marker output, initial/base/candidate sccache statistics, and cache report.
|
||||
Confirm the image tool contract (root-owned Rustup/Cargo paths, baked nightly,
|
||||
and non-writable `/opt` roots) and that the ephemeral Cargo home contains only
|
||||
the mounted registry/Git data before Cargo creates per-Pod state.
|
||||
Obtain dependency and tool network byte counters from the configured
|
||||
environment counter source, filtered to `minipc-3` and the dependency/tool
|
||||
destinations.
|
||||
|
||||
Accept the canary only when all of the following hold:
|
||||
|
||||
- exactly one runner Pod runs on `minipc-3`, and both jobs use the same bound PV;
|
||||
- UID/GID 1001 cache mounts are writable; the warm run does not invalidate the
|
||||
target or bulk-redownload crates or toolchains; sccache reports separate base
|
||||
and candidate build statistics without server or cache-path errors; immutable
|
||||
Rustup/Cargo roots remain non-writable and only registry/Git data persists;
|
||||
- warm base build time is at most 50% of cold base build time;
|
||||
- warm dependency/tool network bytes are at most 10% of cold fill bytes;
|
||||
- cache sizes remain below soft watermarks, node free space remains at least
|
||||
300GiB, and the benchmark is correct without TLS EOFs or timeouts;
|
||||
- the configured environment counter source confirms this traffic is outside VPN
|
||||
accounting.
|
||||
|
||||
Immediately return to 0/0 after either canary unless ongoing normal operation
|
||||
has been explicitly approved; return immediately on any traffic, cache, disk,
|
||||
TLS, or correctness failure. To roll back, use the paused Helm upgrade above,
|
||||
or another explicit `helm upgrade --install` with known-good values followed by
|
||||
`values-paused.yaml`, `--reset-values`, and `--wait`. Do not delete the PVC
|
||||
automatically; preserve it for diagnosis unless intentionally discarding cache.
|
||||
The workflow reports `du` / `df` in telemetry. It does not try to reclaim
|
||||
space across runs: a cold 50 GiB disk that fills up fails the build.
|
||||
|
||||
## Future optional phases
|
||||
|
||||
The current phase uses a digest-pinned image with sccache 0.16.0 and no shared
|
||||
cache service. Optional follow-ups are an image additionally seeded with the
|
||||
exact Rust toolchain and `cargo fetch --locked`; or an internal read/write
|
||||
sccache backend or Cargo/Git mirror. Evaluate them only if persistent PVC reuse
|
||||
is insufficient.
|
||||
The current phase uses a materialized runner toolchain with sccache 0.16.0 and
|
||||
no shared cache service. Optional follow-ups are an image additionally seeded
|
||||
with `cargo fetch --locked` results, or an internal read/write sccache
|
||||
backend or Cargo/Git mirror. Evaluate them only if cold compile time on the
|
||||
system disk becomes the bottleneck.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: query-regression-build-cache
|
||||
namespace: arc-runners
|
||||
labels:
|
||||
app.kubernetes.io/part-of: perf-regression-8-cores
|
||||
spec:
|
||||
volumeMode: Filesystem
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 600Gi
|
||||
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2023 Greptime Team
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Bootstrap an Ubuntu 24.04 host as a query-regression self-hosted runner.
|
||||
#
|
||||
# This is the host-native equivalent of the ECS image contract: it
|
||||
# installs docker-ce from Docker's official repository, builds the runner
|
||||
# image from the sibling Dockerfile (the single source of the tool contract),
|
||||
# materializes the toolchain onto the host, and optionally registers the
|
||||
# runner and installs its systemd service.
|
||||
#
|
||||
# Large state lives under --data-root (a data disk): docker's data-root, the
|
||||
# runner home, and the rustup/cargo toolchain roots, symlinked back to the
|
||||
# hard-coded contract paths (/home/runner, /opt/rustup, /opt/cargo).
|
||||
#
|
||||
# The script is idempotent: re-running refreshes the toolchain in place, and
|
||||
# the runner registration (.runner/.credentials) and _work survive. A live
|
||||
# runner service is stopped first and restarted at the end.
|
||||
#
|
||||
# Usage:
|
||||
# sudo bash bootstrap-runner-host.sh --repo-dir /path/to/greptimedb
|
||||
# sudo RUNNER_TOKEN=<token> bash bootstrap-runner-host.sh --repo-dir . \
|
||||
# --register --repo-url https://github.com/<owner>/<repo>
|
||||
#
|
||||
# Options:
|
||||
# --repo-dir PATH greptimedb checkout containing the runner Dockerfile (required)
|
||||
# --data-root PATH data disk mount point (default: /data)
|
||||
# --uid / --gid N runner user id (default: 3141; the ECS image contract is 1001)
|
||||
# --register also register the runner and install the systemd service
|
||||
# --repo-url URL repository URL for registration (required with --register)
|
||||
# --runner-name N runner name (default: qreg-host)
|
||||
# --labels L runner labels (default: perf-regression-8-cores)
|
||||
#
|
||||
# With --register, provide a fresh registration token via RUNNER_TOKEN.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_DIR=""
|
||||
DATA_ROOT="/data"
|
||||
RUNNER_UID="3141"
|
||||
RUNNER_GID="3141"
|
||||
REGISTER="false"
|
||||
REPO_URL=""
|
||||
RUNNER_NAME="qreg-host"
|
||||
RUNNER_LABELS="perf-regression-8-cores"
|
||||
IMAGE_TAG="qreg-runner:manual"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--repo-dir) REPO_DIR="$2"; shift 2 ;;
|
||||
--data-root) DATA_ROOT="$2"; shift 2 ;;
|
||||
--uid) RUNNER_UID="$2"; shift 2 ;;
|
||||
--gid) RUNNER_GID="$2"; shift 2 ;;
|
||||
--register) REGISTER="true"; shift ;;
|
||||
--repo-url) REPO_URL="$2"; shift 2 ;;
|
||||
--runner-name) RUNNER_NAME="$2"; shift 2 ;;
|
||||
--labels) RUNNER_LABELS="$2"; shift 2 ;;
|
||||
-h | --help) sed -n '17,40p' "$0"; exit 0 ;;
|
||||
*) echo "Unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ $EUID -eq 0 ]] || { echo "Run as root, e.g. sudo bash $0 ..." >&2; exit 1; }
|
||||
[[ -n "${REPO_DIR}" ]] || { echo "--repo-dir is required" >&2; exit 2; }
|
||||
DOCKERFILE="${REPO_DIR}/.github/runner-scale-sets/query-regression/Dockerfile"
|
||||
[[ -f "${DOCKERFILE}" ]] || { echo "Dockerfile not found at ${DOCKERFILE}" >&2; exit 1; }
|
||||
if [[ "${REGISTER}" == "true" ]]; then
|
||||
[[ -n "${REPO_URL}" ]] || { echo "--repo-url is required with --register" >&2; exit 2; }
|
||||
[[ -n "${RUNNER_TOKEN:-}" ]] || { echo "RUNNER_TOKEN env is required with --register" >&2; exit 2; }
|
||||
fi
|
||||
|
||||
step() { printf '\n==> %s\n' "$*"; }
|
||||
|
||||
# A re-run refreshes files under the (possibly live) runner service, so stop
|
||||
# it first and restart it at the end (unless --register re-creates it).
|
||||
RUNNER_SERVICE="$(systemctl list-units --type=service --all --no-legend 'actions.runner.*' 2>/dev/null | awk '{print $1}' | head -n 1 || true)"
|
||||
RUNNER_WAS_ACTIVE="false"
|
||||
if [[ -n "${RUNNER_SERVICE}" ]] && systemctl is-active --quiet "${RUNNER_SERVICE}"; then
|
||||
step "Stop running runner service ${RUNNER_SERVICE}"
|
||||
systemctl stop "${RUNNER_SERVICE}"
|
||||
RUNNER_WAS_ACTIVE="true"
|
||||
fi
|
||||
|
||||
step "Install docker-ce from Docker's official repository"
|
||||
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
|
||||
apt-get remove -y "${pkg}" 2>/dev/null || true
|
||||
done
|
||||
apt-get update
|
||||
apt-get install -y ca-certificates curl
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
|
||||
Types: deb
|
||||
URIs: https://download.docker.com/linux/ubuntu
|
||||
Suites: ${UBUNTU_CODENAME:-$VERSION_CODENAME}
|
||||
Components: stable
|
||||
Architectures: $(dpkg --print-architecture)
|
||||
Signed-By: /etc/apt/keyrings/docker.asc
|
||||
EOF
|
||||
apt-get update
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin
|
||||
|
||||
step "Move docker data-root to ${DATA_ROOT}/docker"
|
||||
systemctl stop docker containerd
|
||||
mkdir -p "${DATA_ROOT}/docker" "${DATA_ROOT}/containerd"
|
||||
if [[ -f /etc/docker/daemon.json ]] && ! grep -q '"data-root"' /etc/docker/daemon.json; then
|
||||
echo "ERROR: /etc/docker/daemon.json exists without data-root; merge manually." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "{ \"data-root\": \"${DATA_ROOT}/docker\" }" > /etc/docker/daemon.json
|
||||
# containerd keeps its own content store (where build-time image data lands);
|
||||
# move it off the system disk too.
|
||||
mkdir -p /etc/containerd
|
||||
containerd config default | sed "s|root = \"/var/lib/containerd\"|root = \"${DATA_ROOT}/containerd\"|" > /etc/containerd/config.toml
|
||||
systemctl start containerd docker
|
||||
docker info | grep "Docker Root Dir"
|
||||
|
||||
step "Build the runner image"
|
||||
docker build --platform linux/amd64 -f "${DOCKERFILE}" -t "${IMAGE_TAG}" "${REPO_DIR}"
|
||||
|
||||
step "Materialize the toolchain onto the host"
|
||||
mkdir -p "${DATA_ROOT}/opt" "${DATA_ROOT}/runner" /opt /home
|
||||
container="$(docker create "${IMAGE_TAG}")"
|
||||
trap 'docker rm -f "${container}" >/dev/null 2>&1 || true' EXIT
|
||||
# The `/.` suffix copies directory *contents*, so re-running over an existing
|
||||
# materialization refreshes it in place instead of nesting runner/runner.
|
||||
# Files absent from the image (runner registration, _work) are preserved.
|
||||
docker cp "${container}:/home/runner/." "${DATA_ROOT}/runner"
|
||||
docker cp "${container}:/opt/rustup/." "${DATA_ROOT}/opt/rustup"
|
||||
docker cp "${container}:/opt/cargo/." "${DATA_ROOT}/opt/cargo"
|
||||
for tool in uv uvx otelgen sccache; do
|
||||
docker cp "${container}:/usr/local/bin/${tool}" "/usr/local/bin/${tool}"
|
||||
done
|
||||
docker rm "${container}" > /dev/null
|
||||
trap - EXIT
|
||||
ln -sfn "${DATA_ROOT}/runner" /home/runner
|
||||
ln -sfn "${DATA_ROOT}/opt/rustup" /opt/rustup
|
||||
ln -sfn "${DATA_ROOT}/opt/cargo" /opt/cargo
|
||||
docker image rm "${IMAGE_TAG}" > /dev/null
|
||||
|
||||
step "Install system packages"
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential clang cmake git gzip jq libprotobuf-dev libssl-dev mold \
|
||||
openssh-client pkg-config protobuf-compiler python3 sudo tar unzip wget xz-utils zip zstd
|
||||
|
||||
step "Create runner user (${RUNNER_UID}:${RUNNER_GID}) and environment"
|
||||
getent group "${RUNNER_GID}" > /dev/null || groupadd -g "${RUNNER_GID}" runner
|
||||
id -u runner > /dev/null 2>&1 || useradd -u "${RUNNER_UID}" -g "${RUNNER_GID}" -d /home/runner -s /bin/bash runner
|
||||
chown -R "${RUNNER_UID}:${RUNNER_GID}" "${DATA_ROOT}/runner"
|
||||
echo 'PATH=/opt/cargo/bin:/usr/local/bin:/usr/bin:/bin' > "${DATA_ROOT}/runner/.env"
|
||||
chown "${RUNNER_UID}:${RUNNER_GID}" "${DATA_ROOT}/runner/.env"
|
||||
|
||||
if [[ "${REGISTER}" == "true" ]]; then
|
||||
step "Register runner ${RUNNER_NAME} (labels: ${RUNNER_LABELS})"
|
||||
if [[ -n "${RUNNER_SERVICE}" ]]; then
|
||||
(cd /home/runner && ./svc.sh uninstall) || true
|
||||
fi
|
||||
runuser -u runner -- bash -c "cd /home/runner && HOME=/home/runner ./config.sh \
|
||||
--url '${REPO_URL}' --token '${RUNNER_TOKEN}' --name '${RUNNER_NAME}' \
|
||||
--labels '${RUNNER_LABELS}' --unattended --replace --disableupdate"
|
||||
|
||||
step "Install and start the runner service"
|
||||
cd /home/runner
|
||||
./svc.sh install runner
|
||||
./svc.sh start
|
||||
./svc.sh status
|
||||
else
|
||||
step "Skipping registration (pass --register --repo-url ... with RUNNER_TOKEN to enable)"
|
||||
if [[ "${RUNNER_WAS_ACTIVE}" == "true" ]]; then
|
||||
step "Restart runner service ${RUNNER_SERVICE}"
|
||||
systemctl start "${RUNNER_SERVICE}"
|
||||
systemctl --no-pager status "${RUNNER_SERVICE}" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
step "Done"
|
||||
echo "Runner home: /home/runner -> ${DATA_ROOT}/runner"
|
||||
echo "Before each workflow run on this persistent host, clean transient cargo state:"
|
||||
echo " sudo rm -f /home/runner/.cargo/.package-cache"
|
||||
echo " sudo rm -rf /home/runner/.cargo/.global-cache"
|
||||
@@ -0,0 +1,392 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2023 Greptime Team
|
||||
#
|
||||
# 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.
|
||||
|
||||
# PEP 723 inline metadata (see .github/scripts/aliyun-ecs-runner-provision.py
|
||||
# for the convention).
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "alibabacloud_ecs20140526>=4.1.0,<6",
|
||||
# "alibabacloud_tea_openapi>=0.3.12,<1",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
"""Build the query-regression ECS custom image (manual ops tool).
|
||||
|
||||
Boots a temporary pay-as-you-go ECS instance from a public Ubuntu 24.04 image,
|
||||
builds the existing runner container image (the Dockerfile in the parent
|
||||
directory remains the single source of the tool contract), materializes the
|
||||
tool directories onto the host filesystem so the workflow's "Verify runner
|
||||
image tools" step holds unchanged, installs the ephemeral-runner systemd unit,
|
||||
and snapshots the result as a custom image. The temporary instance is deleted
|
||||
afterwards.
|
||||
|
||||
Sentinel polling reads the instance's serial console output
|
||||
(GetInstanceConsoleOutput) and looks for marker lines the user data writes to
|
||||
/dev/console. This has no in-guest agent dependency.
|
||||
|
||||
Usage:
|
||||
uv run .github/runner-scale-sets/query-regression/ecs-image/build-ecs-image.py \
|
||||
--region-id cn-hangzhou --vswitch-id vsw-... --security-group-id sg-... \
|
||||
--base-image-id ubuntu_24_04_x64_20G_alibase_*.vhd
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ASSETS_DIR = Path(__file__).resolve().parent
|
||||
DONE_MARKER = "QREG_IMAGE_BUILD_DONE"
|
||||
FAILED_MARKER = "QREG_IMAGE_BUILD_FAILED"
|
||||
POLL_INTERVAL_SECONDS = 15
|
||||
CONSOLE_POLL_INTERVAL_SECONDS = 30
|
||||
BUILD_TIMEOUT_SECONDS = 60 * 60
|
||||
|
||||
# Same apt package contract as the runner Dockerfile; the base
|
||||
# actions-runner image is Ubuntu 24.04, so an Ubuntu 24.04 host resolves the
|
||||
# same tool versions (protoc 3.21.12, mold 2.40.4, Python 3.14.4).
|
||||
# Docker itself comes from Docker's official repository (docker-ce), not the
|
||||
# distribution-packaged docker.io.
|
||||
APT_PACKAGES = [
|
||||
"build-essential",
|
||||
"ca-certificates",
|
||||
"clang",
|
||||
"cmake",
|
||||
"curl",
|
||||
"git",
|
||||
"gpg",
|
||||
"gzip",
|
||||
"jq",
|
||||
"libprotobuf-dev",
|
||||
"libssl-dev",
|
||||
"mold",
|
||||
"openssh-client",
|
||||
"pkg-config",
|
||||
"protobuf-compiler",
|
||||
"python3",
|
||||
"sudo",
|
||||
"tar",
|
||||
"unzip",
|
||||
"wget",
|
||||
"xz-utils",
|
||||
"zip",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
DOCKER_CE_PACKAGES = "docker-ce docker-ce-cli containerd.io docker-buildx-plugin"
|
||||
|
||||
|
||||
def render_user_data(dockerfile: str, start_runner: str, unit: str) -> str:
|
||||
dockerfile_b64 = base64.b64encode(dockerfile.encode()).decode()
|
||||
start_runner_b64 = base64.b64encode(start_runner.encode()).decode()
|
||||
unit_b64 = base64.b64encode(unit.encode()).decode()
|
||||
packages = " ".join(APT_PACKAGES)
|
||||
return f"""#!/bin/bash
|
||||
set -euo pipefail
|
||||
trap 'echo "{FAILED_MARKER} at line $LINENO" > /dev/console' ERR
|
||||
# Stream the full build log to the serial console so the poller (and anyone
|
||||
# watching GetInstanceConsoleOutput) sees real progress, not a silent login
|
||||
# prompt for the whole build.
|
||||
exec > >(tee -a /dev/console) 2>&1
|
||||
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends {packages}
|
||||
|
||||
# Docker from the official repository, not the distribution-packaged docker.io.
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
. /etc/os-release
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${{VERSION_CODENAME}} stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends {DOCKER_CE_PACKAGES}
|
||||
|
||||
base64 -d > /tmp/Dockerfile <<'EOF'
|
||||
{dockerfile_b64}
|
||||
EOF
|
||||
mkdir -p /tmp/image-context
|
||||
docker build --platform linux/amd64 -f /tmp/Dockerfile -t qreg-runner:local /tmp/image-context
|
||||
|
||||
# Materialize the tool contract onto the host filesystem.
|
||||
container="$(docker create qreg-runner:local)"
|
||||
trap 'docker rm -f "${{container}}" >/dev/null 2>&1 || true' EXIT
|
||||
docker cp "${{container}}:/opt/rustup" /opt/rustup
|
||||
docker cp "${{container}}:/opt/cargo" /opt/cargo
|
||||
for tool in uv uvx otelgen sccache; do
|
||||
docker cp "${{container}}:/usr/local/bin/${{tool}}" "/usr/local/bin/${{tool}}"
|
||||
done
|
||||
docker cp "${{container}}:/home/runner" /home/runner
|
||||
|
||||
# Runner identity mirrors the container image (UID/GID 1001).
|
||||
groupadd --gid 1001 runner 2>/dev/null || true
|
||||
useradd --uid 1001 --gid 1001 --home-dir /home/runner --shell /bin/bash runner 2>/dev/null || true
|
||||
chown -R 1001:1001 /home/runner
|
||||
|
||||
mkdir -p /opt/ephemeral-github-runner
|
||||
base64 -d > /opt/ephemeral-github-runner/start-runner.sh <<'EOF'
|
||||
{start_runner_b64}
|
||||
EOF
|
||||
chmod 0755 /opt/ephemeral-github-runner/start-runner.sh
|
||||
base64 -d > /etc/systemd/system/ephemeral-github-runner.service <<'EOF'
|
||||
{unit_b64}
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable ephemeral-github-runner.service
|
||||
|
||||
# Keep the image free of the build-time docker state (also shrinks the
|
||||
# snapshot: buildkit cache is several GB).
|
||||
docker rm -f "${{container}}" >/dev/null
|
||||
docker system prune -af >/dev/null
|
||||
trap - EXIT
|
||||
|
||||
echo "{DONE_MARKER}" > /dev/console
|
||||
"""
|
||||
|
||||
|
||||
def make_ecs_client(region_id: str):
|
||||
from alibabacloud_ecs20140526.client import Client as EcsClient
|
||||
from alibabacloud_tea_openapi.models import Config as OpenApiConfig
|
||||
|
||||
return EcsClient(
|
||||
OpenApiConfig(
|
||||
access_key_id=os.environ["ALIBABA_CLOUD_ACCESS_KEY_ID"],
|
||||
access_key_secret=os.environ["ALIBABA_CLOUD_ACCESS_KEY_SECRET"],
|
||||
region_id=region_id,
|
||||
endpoint=f"ecs.{region_id}.aliyuncs.com",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def wait_for_instance_status(client, region_id: str, instance_id: str, wanted: str, deadline: float) -> None:
|
||||
from alibabacloud_ecs20140526 import models as ecs_models
|
||||
|
||||
import json
|
||||
|
||||
while time.monotonic() < deadline:
|
||||
response = call_api_with_retry(
|
||||
lambda: client.describe_instances(
|
||||
ecs_models.DescribeInstancesRequest(
|
||||
region_id=region_id, instance_ids=json.dumps([instance_id])
|
||||
)
|
||||
),
|
||||
"DescribeInstances",
|
||||
)
|
||||
instances = response.body.instances.instance
|
||||
if instances and instances[0].status == wanted:
|
||||
return
|
||||
time.sleep(POLL_INTERVAL_SECONDS)
|
||||
raise TimeoutError(f"Instance {instance_id} did not reach status {wanted} in time")
|
||||
|
||||
|
||||
# Aliyun error codes worth retrying: throttling and server-side faults. Client
|
||||
# errors (4xx: permissions, bad parameters) are configuration problems and must
|
||||
# fail fast instead of being retried.
|
||||
TRANSIENT_ERROR_CODES = {"Throttling", "Throttling.User", "InternalError", "ServiceUnavailable"}
|
||||
|
||||
|
||||
def is_transient(error: Exception) -> bool:
|
||||
# UnretryableException comes from the darabonba network layer: connection
|
||||
# reset, read timeout, DNS blip.
|
||||
if type(error).__name__ == "UnretryableException":
|
||||
return True
|
||||
code = getattr(error, "code", "") or ""
|
||||
status = getattr(error, "statusCode", None)
|
||||
return code in TRANSIENT_ERROR_CODES or (isinstance(status, int) and status >= 500)
|
||||
|
||||
|
||||
def call_api_with_retry(fn, description: str, attempts: int = 5):
|
||||
"""Retry transient API/network failures; the build is too long to die on a blip.
|
||||
|
||||
Only call this with idempotent or safely-repeatable requests (reads,
|
||||
StopInstance, CreateImage). Never with RunInstances: if the request
|
||||
succeeded but the response was lost, a retry double-creates instances.
|
||||
"""
|
||||
for attempt in range(1, attempts + 1):
|
||||
try:
|
||||
return fn()
|
||||
except Exception as error: # noqa: BLE001
|
||||
if attempt == attempts or not is_transient(error):
|
||||
raise
|
||||
print(f"{description} failed (attempt {attempt}/{attempts}): {error}", flush=True)
|
||||
time.sleep(POLL_INTERVAL_SECONDS)
|
||||
raise RuntimeError("unreachable: retry loop exited without returning")
|
||||
|
||||
|
||||
def read_console_output(client, region_id: str, instance_id: str) -> str:
|
||||
"""Fetch the instance's serial console output; no in-guest agent needed."""
|
||||
from alibabacloud_ecs20140526 import models as ecs_models
|
||||
|
||||
response = call_api_with_retry(
|
||||
lambda: client.get_instance_console_output(
|
||||
ecs_models.GetInstanceConsoleOutputRequest(region_id=region_id, instance_id=instance_id)
|
||||
),
|
||||
"GetInstanceConsoleOutput",
|
||||
)
|
||||
return base64.b64decode(response.body.console_output or "").decode("utf-8", "replace")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--region-id", default=os.environ.get("ALIYUN_ECS_REGION_ID"))
|
||||
parser.add_argument("--vswitch-id", default=os.environ.get("ALIYUN_ECS_VSWITCH_ID"))
|
||||
parser.add_argument("--security-group-id", default=os.environ.get("ALIYUN_ECS_SECURITY_GROUP_ID"))
|
||||
parser.add_argument("--base-image-id", default=os.environ.get("ALIYUN_ECS_BASE_IMAGE_ID"))
|
||||
parser.add_argument("--resource-group-id", default=os.environ.get("ALIYUN_ECS_RESOURCE_GROUP_ID"))
|
||||
parser.add_argument("--instance-type", default="ecs.g7.xlarge")
|
||||
parser.add_argument("--image-name", default=None, help="Defaults to a timestamped name.")
|
||||
args = parser.parse_args()
|
||||
|
||||
for name in ("region_id", "vswitch_id", "security_group_id", "base_image_id"):
|
||||
if not getattr(args, name):
|
||||
raise SystemExit(f"Missing required configuration: --{name.replace('_', '-')}")
|
||||
|
||||
from alibabacloud_ecs20140526 import models as ecs_models
|
||||
|
||||
client = make_ecs_client(args.region_id)
|
||||
image_name = args.image_name or time.strftime(
|
||||
"greptimedb-query-regression-runner-%Y%m%d%H%M%S", time.gmtime()
|
||||
)
|
||||
|
||||
user_data = base64.b64encode(
|
||||
render_user_data(
|
||||
(ASSETS_DIR.parent / "Dockerfile").read_text(),
|
||||
(ASSETS_DIR / "start-runner.sh").read_text(),
|
||||
(ASSETS_DIR / "ephemeral-github-runner.service").read_text(),
|
||||
).encode()
|
||||
).decode()
|
||||
|
||||
instance_id = None
|
||||
try:
|
||||
response = client.run_instances(
|
||||
ecs_models.RunInstancesRequest(
|
||||
region_id=args.region_id,
|
||||
image_id=args.base_image_id,
|
||||
resource_group_id=args.resource_group_id,
|
||||
instance_type=args.instance_type,
|
||||
v_switch_id=args.vswitch_id,
|
||||
security_group_id=args.security_group_id,
|
||||
instance_name=f"build-{image_name}",
|
||||
description="Temporary builder for the query-regression ECS image",
|
||||
amount=1,
|
||||
instance_charge_type="PostPaid",
|
||||
spot_strategy="NoSpot",
|
||||
internet_charge_type="PayByTraffic",
|
||||
internet_max_bandwidth_out=100,
|
||||
# Peak usage is ~18G (OS+apt, docker image, and the materialized
|
||||
# toolchain coexist briefly): deliberately tight, and a smaller
|
||||
# disk makes the image snapshot faster. Instances created from
|
||||
# the image get a larger system disk from the provision side.
|
||||
system_disk=ecs_models.RunInstancesRequestSystemDisk(
|
||||
category="cloud_essd", size="20"
|
||||
),
|
||||
user_data=user_data,
|
||||
tag=[
|
||||
ecs_models.RunInstancesRequestTag(key="managed-by", value="query-regression-ci"),
|
||||
ecs_models.RunInstancesRequestTag(key="role", value="image-builder"),
|
||||
],
|
||||
)
|
||||
)
|
||||
instance_id = response.body.instance_id_sets.instance_id_set[0]
|
||||
print(f"Builder instance: {instance_id}", flush=True)
|
||||
wait_for_instance_status(client, args.region_id, instance_id, "Running", time.monotonic() + 10 * 60)
|
||||
|
||||
deadline = time.monotonic() + BUILD_TIMEOUT_SECONDS
|
||||
while time.monotonic() < deadline:
|
||||
console = read_console_output(client, args.region_id, instance_id)
|
||||
if DONE_MARKER in console:
|
||||
break
|
||||
if FAILED_MARKER in console:
|
||||
tail = "\n".join(console.splitlines()[-20:])
|
||||
raise RuntimeError(f"Image build failed on the builder; console tail:\n{tail}")
|
||||
lines = console.splitlines()
|
||||
print(f"Build in progress; last console line: {lines[-1] if lines else '(none yet)'}", flush=True)
|
||||
time.sleep(CONSOLE_POLL_INTERVAL_SECONDS)
|
||||
else:
|
||||
raise TimeoutError("Image build did not finish in time")
|
||||
|
||||
print("Stopping builder before image creation", flush=True)
|
||||
try:
|
||||
call_api_with_retry(
|
||||
lambda: client.stop_instance(
|
||||
ecs_models.StopInstanceRequest(
|
||||
instance_id=instance_id,
|
||||
# The instance is deleted right after the snapshot, but
|
||||
# there is no reason to keep billing vCPU during the stop.
|
||||
stopped_mode="StopCharging",
|
||||
)
|
||||
),
|
||||
"StopInstance",
|
||||
)
|
||||
except Exception as error: # noqa: BLE001
|
||||
# Instance families with local disks do not support StopCharging.
|
||||
print(f"StopCharging unavailable ({error}); stopping with default mode", flush=True)
|
||||
call_api_with_retry(
|
||||
lambda: client.stop_instance(ecs_models.StopInstanceRequest(instance_id=instance_id)),
|
||||
"StopInstance",
|
||||
)
|
||||
wait_for_instance_status(client, args.region_id, instance_id, "Stopped", time.monotonic() + 10 * 60)
|
||||
|
||||
image = call_api_with_retry(
|
||||
lambda: client.create_image(
|
||||
ecs_models.CreateImageRequest(
|
||||
region_id=args.region_id, instance_id=instance_id, image_name=image_name
|
||||
)
|
||||
),
|
||||
"CreateImage",
|
||||
)
|
||||
image_id = image.body.image_id
|
||||
deadline = time.monotonic() + 60 * 60
|
||||
while time.monotonic() < deadline:
|
||||
description = call_api_with_retry(
|
||||
lambda: client.describe_images(
|
||||
ecs_models.DescribeImagesRequest(region_id=args.region_id, image_id=image_id)
|
||||
),
|
||||
"DescribeImages",
|
||||
)
|
||||
images = description.body.images.image
|
||||
if images:
|
||||
status = images[0].status
|
||||
print(f"Image {image_id} status: {status} (progress {images[0].progress})", flush=True)
|
||||
if status == "Available":
|
||||
break
|
||||
time.sleep(POLL_INTERVAL_SECONDS)
|
||||
else:
|
||||
raise TimeoutError(
|
||||
f"Image {image_id} did not become Available in time. The snapshot "
|
||||
f"continues server-side: check its status in the console and reuse it "
|
||||
f"once Available instead of rebuilding."
|
||||
)
|
||||
|
||||
print(f"Custom image ready: {image_id} ({image_name})", flush=True)
|
||||
print(f"Set the repo variable QUERY_REGRESSION_ECS_IMAGE_ID={image_id}", flush=True)
|
||||
return 0
|
||||
finally:
|
||||
if instance_id:
|
||||
try:
|
||||
client.delete_instance(
|
||||
ecs_models.DeleteInstanceRequest(instance_id=instance_id, force=True)
|
||||
)
|
||||
print(f"Deleted builder instance {instance_id}", flush=True)
|
||||
except Exception as error: # noqa: BLE001
|
||||
print(f"Failed to delete builder instance {instance_id}: {error}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,45 @@
|
||||
# Copyright 2023 Greptime Team
|
||||
#
|
||||
# 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.
|
||||
|
||||
[Unit]
|
||||
Description=Ephemeral GitHub Actions runner
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
# cloud-final runs the provision user data, which writes
|
||||
# /etc/ephemeral-github-runner.env and the drop-ins below. Ordering after it
|
||||
# makes the runner come up configured on first boot, no restart needed.
|
||||
# (cloud-init's `systemctl restart` in user data remains as the bridge for
|
||||
# images built before this ordering existed.)
|
||||
After=cloud-final.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# The toolchain lives in the image at /opt/cargo/bin (the Dockerfile's ENV
|
||||
# PATH does not survive materialization onto the host). Publish it here so
|
||||
# every runner job inherits it. /etc/ephemeral-github-runner.env currently
|
||||
# carries the same PATH line as a bridge for images built before this
|
||||
# directive existed; EnvironmentFile is applied after Environment=, and the
|
||||
# two values are kept identical, so there is no conflict.
|
||||
Environment=PATH=/opt/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
EnvironmentFile=/etc/ephemeral-github-runner.env
|
||||
ExecStart=/opt/ephemeral-github-runner/start-runner.sh
|
||||
Restart=no
|
||||
# If the kernel OOM killer kills a job child (rustc/cargo), keep the runner
|
||||
# service alive so the job is reported as a normal failure with logs instead
|
||||
# of the runner vanishing and GitHub reporting a cancellation. cloud-init's
|
||||
# oom.conf drop-in carries the same directive for images built before this.
|
||||
OOMPolicy=continue
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2023 Greptime Team
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Configure and start the ephemeral GitHub Actions runner on an ECS instance.
|
||||
# Baked into the custom image at /opt/ephemeral-github-runner/ by
|
||||
# build-ecs-image.py; the per-run environment is written by cloud-init to
|
||||
# /etc/ephemeral-github-runner.env.
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/ephemeral-github-runner.env
|
||||
|
||||
export HOME=/home/runner
|
||||
cd /home/runner
|
||||
|
||||
if [[ ! -f /home/runner/.runner ]]; then
|
||||
runuser -u runner -- ./config.sh \
|
||||
--url "${REPO_URL}" \
|
||||
--token "${RUNNER_TOKEN}" \
|
||||
--name "${RUNNER_NAME}" \
|
||||
--labels "${RUNNER_LABELS}" \
|
||||
--ephemeral --unattended --replace --disableupdate
|
||||
fi
|
||||
|
||||
exec runuser -u runner -- ./run.sh
|
||||
@@ -1,146 +0,0 @@
|
||||
githubConfigUrl: "https://github.com/GreptimeTeam/greptimedb"
|
||||
githubConfigSecret: greptimedb-arc-github-app
|
||||
|
||||
runnerScaleSetName: "perf-regression-8-cores"
|
||||
minRunners: 0
|
||||
maxRunners: 1
|
||||
|
||||
template:
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
activeDeadlineSeconds: 12600
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: minipc-3
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
fsGroup: 1001
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumes:
|
||||
- name: cargo-home
|
||||
emptyDir: {}
|
||||
- name: build-cache
|
||||
persistentVolumeClaim:
|
||||
claimName: query-regression-build-cache
|
||||
initContainers:
|
||||
- name: initialize-build-cache
|
||||
image: greptime-registry.cn-hangzhou.cr.aliyuncs.com/greptime/greptimedb-query-regression-runner@sha256:e713b294e23b7e15184e558866c90025e59930033e72c97650dbc7f1ca022d11
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
umask 0002
|
||||
for directory in cargo-registry-v1 cargo-git-v1 query-regression-target-v1 meta-v1 sccache-v1; do
|
||||
cache_directory="/cache/${directory}"
|
||||
mkdir -p "${cache_directory}"
|
||||
test -w "${cache_directory}"
|
||||
probe_file="${cache_directory}/.write-probe"
|
||||
: > "${probe_file}"
|
||||
test -f "${probe_file}"
|
||||
rm "${probe_file}"
|
||||
done
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
ephemeral-storage: 1Gi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
ephemeral-storage: 1Gi
|
||||
volumeMounts:
|
||||
- name: build-cache
|
||||
mountPath: /cache
|
||||
containers:
|
||||
- name: runner
|
||||
image: greptime-registry.cn-hangzhou.cr.aliyuncs.com/greptime/greptimedb-query-regression-runner@sha256:e713b294e23b7e15184e558866c90025e59930033e72c97650dbc7f1ca022d11
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/home/runner/run.sh"]
|
||||
env:
|
||||
- name: CARGO_HOME
|
||||
value: /home/runner/.cargo
|
||||
- name: HTTP_PROXY
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: query-regression-runner-local-env
|
||||
key: HTTP_PROXY
|
||||
optional: true
|
||||
- name: HTTPS_PROXY
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: query-regression-runner-local-env
|
||||
key: HTTPS_PROXY
|
||||
optional: true
|
||||
- name: NO_PROXY
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: query-regression-runner-local-env
|
||||
key: NO_PROXY
|
||||
optional: true
|
||||
- name: UV_CACHE_DIR
|
||||
value: /home/runner/.cargo/uv-cache
|
||||
- name: RUSTUP_HOME
|
||||
value: /opt/rustup
|
||||
- name: RUSTUP_TOOLCHAIN
|
||||
value: nightly-2026-03-21
|
||||
- name: RUSTUP_AUTO_INSTALL
|
||||
value: "0"
|
||||
- name: CARGO_TARGET_DIR
|
||||
value: /home/runner/query-regression-target
|
||||
- name: QUERY_REGRESSION_CACHE_META
|
||||
value: /home/runner/query-regression-cache-meta
|
||||
- name: RUSTC_WRAPPER
|
||||
value: /usr/local/bin/sccache
|
||||
- name: SCCACHE_DIR
|
||||
value: /home/runner/.cache/sccache
|
||||
- name: SCCACHE_CACHE_SIZE
|
||||
value: 40G
|
||||
- name: CARGO_INCREMENTAL
|
||||
value: "0"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
resources:
|
||||
requests:
|
||||
cpu: "6"
|
||||
memory: 24Gi
|
||||
ephemeral-storage: 80Gi
|
||||
limits:
|
||||
cpu: "8"
|
||||
memory: 24Gi
|
||||
ephemeral-storage: 80Gi
|
||||
volumeMounts:
|
||||
- name: cargo-home
|
||||
mountPath: /home/runner/.cargo
|
||||
- name: build-cache
|
||||
mountPath: /home/runner/.cargo/registry
|
||||
subPath: cargo-registry-v1
|
||||
- name: build-cache
|
||||
mountPath: /home/runner/.cargo/git
|
||||
subPath: cargo-git-v1
|
||||
- name: build-cache
|
||||
mountPath: /home/runner/query-regression-target
|
||||
subPath: query-regression-target-v1
|
||||
- name: build-cache
|
||||
mountPath: /home/runner/query-regression-cache-meta
|
||||
subPath: meta-v1
|
||||
- name: build-cache
|
||||
mountPath: /home/runner/.cache/sccache
|
||||
subPath: sccache-v1
|
||||
@@ -1,2 +0,0 @@
|
||||
minRunners: 0
|
||||
maxRunners: 0
|
||||
Reference in New Issue
Block a user