-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathDockerfile
More file actions
223 lines (204 loc) · 11.1 KB
/
Copy pathDockerfile
File metadata and controls
223 lines (204 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# syntax=docker/dockerfile:1.7
#
# Multi-stage build for the aisix AI gateway.
#
# The workspace pins rustc via rust-toolchain.toml (currently 1.93.1).
# We use the latest Debian-based official Rust image, then copy the
# single `aisix` binary into a slim runtime image.
#
# BuildKit is required (the `--mount=type=cache` directives rely on
# it). On recent Docker Desktop / Docker CE, BuildKit is the default;
# on older clients run: DOCKER_BUILDKIT=1 docker build -t aisix:dev .
#
# Build:
# docker build -t aisix:dev .
# docker build --build-arg PGO=off -t aisix:dev . # quick local build, skips PGO
#
# Run, standalone (mount your own config):
# docker run --rm -v $(pwd)/config.example.yaml:/etc/aisix/config.yaml \
# aisix:dev
#
# Run, managed (connected to AISIX Cloud with env-var overrides):
# docker run --rm \
# -e AISIX_CONFIG_PATH=/etc/aisix/config.managed.yaml \
# -e AISIX_MANAGED__CP_BASE_URL \
# -e AISIX_MANAGED__CP_ETCD_ENDPOINT \
# -e AISIX_MANAGED__CP_CERT_PEM \
# -e AISIX_MANAGED__CP_KEY_PEM \
# -e AISIX_MANAGED__CP_CA_PEM \
# -v aisix-mtls:/var/lib/aisix \
# aisix:dev
# The volume preserves the materialized mTLS bundle and gateway identity across
# container restarts.
# --- Stage 1: build ----------------------------------------------------------
# Trixie fixes the glibc the release binary links against, and the
# runtime stage must stay on the same-or-newer glibc — so the two base
# images only ever move together.
FROM rust:1.93-trixie AS builder
# protoc is required by dependencies that use prost/tonic-build.
RUN apt-get update \
&& apt-get install -y --no-install-recommends protobuf-compiler \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
# Short git sha stamped into the binary so a running DP can be matched
# to its image tag: release builds report `<version>+sha-<BUILD_SHA>` in
# the heartbeat, every other build reports `dev+sha-<BUILD_SHA>`. CI
# passes the same short sha that tags the image; plain `docker build`
# (no args) produces a binary that reports `dev`.
ARG BUILD_SHA=
ENV AISIX_BUILD_SHA=$BUILD_SHA
# Release version stamped into the binary. CI derives this from the
# release tag (v0.4.0 → 0.4.0), so `aisix --version`, the `Server`
# response header, and the heartbeat dp_version always self-report the
# tagged version — no manual Cargo.toml bump at release time. Empty on
# non-release builds; see crates/aisix-core/src/version.rs.
ARG BUILD_VERSION=
ENV AISIX_BUILD_VERSION=$BUILD_VERSION
# BuildKit cache mounts carry `~/.cargo/registry` + `target/` across
# builds, so changes to source files still reuse compiled dependencies.
# We could split dep-build from source-build via a manifests-only warm
# stage, but the cache mounts give us ~95% of the same win with half
# the Dockerfile complexity. Source copy is a single layer.
COPY Cargo.toml Cargo.lock rust-toolchain.toml rustfmt.toml ./
COPY crates ./crates
# `crates/aisix-admin/src/openapi.rs` uses `include_str!` to embed
# every `schemas/resources/*.schema.json` at compile time, so the
# Docker context must carry this directory or the release build fails.
COPY schemas ./schemas
# PGO training assets (#967): trainer tool + train.sh. Copied separately from
# crates/ so editing training assets doesn't invalidate the dependency layers
# above.
COPY bench/pgo-training ./bench/pgo-training
# Profile-guided optimization gate. The default stays ON so a bare
# `docker build` — a local release rehearsal, a one-off customer image —
# produces the same shape as the published artifact rather than a silently
# un-optimized one; opting out is explicit.
#
# CI inverts that default and passes PGO=on only for a STABLE release tag
# (vX.Y.Z). PR, main/:dev and `-rc.N` builds pass PGO=off: PGO cost ~20 min
# on every one of the ~40 main pushes per release cycle, and none of those
# images are the artifact a customer runs. See "Decide PGO build mode" in
# .github/workflows/docker-image.yml and the PGO section of RELEASING.md.
ARG PGO=on
# `--locked` forces the build to use the exact versions in Cargo.lock —
# fails fast if the lockfile is stale rather than silently resolving
# fresh deps in CI.
#
# PGO=on runs the three-phase build (#967):
# A. instrumented build (-Cprofile-generate) in its own target dir;
# B. train.sh drives the committed 12-shape matrix through the
# instrumented gateway against the trainer's local mock, then merges
# the .profraw files with the pinned toolchain's own llvm-profdata
# (llvm-tools-preview — exact LLVM match with rustc, no extra deps);
# C. optimized build (-Cprofile-use) in a third target dir, so profile
# builds never share cargo fingerprints with plain builds.
# FAIL-CLOSED: any phase failing fails this RUN and nothing is shipped.
# The proof marker (pgo-verified.json) is written only after phase C
# succeeds; the push workflows assert it before trusting the image.
# The merged profile is content-addressed (merged-<sha>.profdata) because
# cargo fingerprints the -Cprofile-use PATH, not the file content — a
# retrained profile at a fixed path would silently reuse stale artifacts
# from the persistent target cache mount.
#
# linux/arm64 (AISIX-Cloud#903) carries two constraints:
# - jemalloc bakes the build host's page size into the binary, so a
# 4K-page build aborts at startup on a 64K-page kernel. Building with
# lg-page 16 yields a binary that runs on both (see
# crates/aisix-server/src/main.rs). Keyed on TARGETARCH below so a
# plain `docker build` on an arm64 host is right too, not only CI.
# - PGO training runs the instrumented binary, so it needs a NATIVE
# arm64 builder — it cannot self-train under emulation. CI gives each
# architecture its own runner for that reason; do not reintroduce QEMU.
# Supplied by BuildKit; an absent value trips `set -u` below rather than
# silently producing a 4K-page arm64 binary.
ARG TARGETARCH
# The cache mounts are keyed per architecture. A cache mount's default id is
# its target path, so a single builder asked for both platforms at once
# (`docker build --platform linux/amd64,linux/arm64 .`) would run the two
# stage variants against one `/src/target` — where they share a cargo lock
# and both write `release/aisix`, so the copy below can pick up the other
# architecture's binary.
RUN --mount=type=cache,id=cargo-registry-${TARGETARCH},target=/usr/local/cargo/registry \
--mount=type=cache,id=target-${TARGETARCH},target=/src/target \
--mount=type=cache,id=target-pgo-gen-${TARGETARCH},target=/src/target-pgo-gen \
--mount=type=cache,id=target-pgo-${TARGETARCH},target=/src/target-pgo \
set -eu; \
if [ "$TARGETARCH" = "arm64" ]; then export JEMALLOC_SYS_WITH_LG_PAGE=16; fi; \
mkdir -p /usr/local/share/aisix; \
if [ "$PGO" = "on" ]; then \
RUSTFLAGS="-Cprofile-generate=/tmp/pgo-data" CARGO_TARGET_DIR=/src/target-pgo-gen \
cargo build --locked --release --bin aisix; \
cargo build --locked --release \
--manifest-path bench/pgo-training/trainer/Cargo.toml; \
bash bench/pgo-training/train.sh /src/target-pgo-gen/release/aisix /tmp/pgo-data; \
PROFDATA="$(ls /tmp/pgo-data/merged-*.profdata)"; \
RUSTFLAGS="-Cprofile-use=$PROFDATA" CARGO_TARGET_DIR=/src/target-pgo \
cargo build --locked --release --bin aisix; \
cp /src/target-pgo/release/aisix /usr/local/bin/aisix; \
cp /tmp/pgo-data/train-manifest.json /usr/local/share/aisix/pgo-verified.json; \
elif [ "$PGO" = "off" ]; then \
cargo build --locked --release --bin aisix; \
cp target/release/aisix /usr/local/bin/aisix; \
else \
echo "unsupported PGO value: '$PGO' (use on|off)" >&2; \
exit 2; \
fi
# --- Stage 2: runtime --------------------------------------------------------
# trixie-slim to match the builder's glibc (see the builder stage note);
# a binary linked against glibc 2.41 symbols cannot run on bookworm.
FROM debian:trixie-slim AS runtime
# Ownership-verification label for the MCP Registry: when this image is
# published as an MCP server entry, the registry requires this label to
# match the server name in server.json.
LABEL io.modelcontextprotocol.server.name="io.github.api7/aisix"
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates tini \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system --uid 10001 --no-create-home --shell /usr/sbin/nologin aisix \
&& mkdir -p /etc/aisix/tls /var/lib/aisix \
&& chown -R aisix:aisix /etc/aisix /var/lib/aisix
# Install the binary and grant CAP_NET_BIND_SERVICE as a file
# capability so the non-root user can bind privileged ports (e.g.
# listening on :80/:443 with Kubernetes hostNetwork). Install + setcap
# happen in one RUN (bind-mount, no COPY) so the binary isn't
# duplicated across layers by the xattr change. Caveat: with the
# effective bit set, exec fails outright if NET_BIND_SERVICE is
# missing from the container's bounding set — it is in the default
# Docker/containerd cap set, but `capabilities: {drop: [ALL]}` pod
# specs must add NET_BIND_SERVICE back.
# The PGO proof marker (#967) ships with the image: written by the builder
# only after a successful profile-optimized build, asserted by the push
# workflows before an image is trusted. Absent on PGO=off (PR smoke) builds.
RUN --mount=type=bind,from=builder,source=/usr/local/bin/aisix,target=/mnt/aisix \
--mount=type=bind,from=builder,source=/usr/local/share/aisix,target=/mnt/aisix-share \
apt-get update \
&& apt-get install -y --no-install-recommends libcap2-bin \
&& install -m 0755 /mnt/aisix /usr/local/bin/aisix \
&& setcap 'cap_net_bind_service=+ep' /usr/local/bin/aisix \
&& mkdir -p /usr/local/share/aisix \
&& if [ -f /mnt/aisix-share/pgo-verified.json ]; then \
install -m 0644 /mnt/aisix-share/pgo-verified.json /usr/local/share/aisix/pgo-verified.json; \
fi \
&& apt-get purge -y --auto-remove libcap2-bin \
&& rm -rf /var/lib/apt/lists/*
# Bake the managed-mode bootstrap config so AISIX gateways managed by
# AISIX Cloud can `docker run` without mounting a configuration file.
# Environment variables provide the control-plane endpoints and gateway mTLS
# certificate bundle.
COPY config.managed.yaml /etc/aisix/config.managed.yaml
# Entrypoint script picks the config file via AISIX_CONFIG_PATH so the
# same image serves both standalone (mount your config at the default
# path) and managed (point AISIX_CONFIG_PATH at the baked file).
COPY docker/entrypoint.sh /usr/local/bin/aisix-entrypoint
RUN chmod 0755 /usr/local/bin/aisix-entrypoint
# Proxy + admin + metrics listeners from config.example.yaml.
EXPOSE 3000 3001 9090
# Numeric, not `aisix`: kubelet resolves `runAsNonRoot: true` against the
# image's configured user, and a name it cannot prove is non-root fails
# the container at admission with CreateContainerConfigError. The uid is
# the `aisix` passwd entry's, so the default runtime identity — and the
# ownership of /etc/aisix and /var/lib/aisix — is unchanged.
USER 10001
# tini forwards signals cleanly to the aisix process; entrypoint script
# resolves the config path from env, then execs the binary.
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/aisix-entrypoint"]