feat: ecs-node AMI (alpine + containerd + ecs-agent baked) (#415)

Adds the spinifex-ecs-node AMI variant: alpine 3.21.7 + containerd + runc +
cni-plugins + ca-certificates with the ecs-agent binary baked to
/usr/local/bin and three OpenRC services wired in dependency order
(ecs-ca-install -> containerd -> ecs-agent).

- manifest.conf drives build-system-image.sh (no host mounts).
- ecs-ca-install.initd: first-boot oneshot installing the cloud-init
  delivered gateway CA into the system trust; no-op when absent.
- ecs-agent.initd: sources /etc/spinifex-ecs/agent.env, backgrounds the
  agent after net + containerd.
- 10-bridge.conflist: CNI bridge for task networking (Sprint 4d).
- setup.sh: exec bits, /opt/cni/bin symlink, content-store dirs, ttyS0
  console reorder + fast boot menu.
- Makefile: build-ecs-node-image / import-ecs-node-image targets.
This commit is contained in:
Josh Siviour
2026-06-24 21:54:35 +10:00
committed by GitHub
parent 93db7e4d1b
commit 24bd50a53c
6 changed files with 167 additions and 1 deletions
+7 -1
View File
@@ -94,6 +94,12 @@ import-eks-node-image: ## Build + register the eks-node AMI (requires a running
publish-eks-node-image: ## Build + publish the eks-node AMI to Cloudflare R2 (needs R2_ENDPOINT + AWS_* env)
./scripts/publish-system-image.sh scripts/images/eks-node/manifest.conf --build
build-ecs-node-image: ## Build the spinifex-ecs-node AMI (Alpine + containerd + ecs-agent; IMPORT=1 to register)
$(MAKE) build-system-image IMAGE=ecs-agent
import-ecs-node-image: ## Build + register the ecs-node AMI (requires a running cluster)
$(MAKE) build-system-image IMAGE=ecs-agent IMPORT=1
MICROVM_OUT_DIR := build/microvm
MICROVM_ARTIFACTS := $(MICROVM_OUT_DIR)/vmlinuz $(MICROVM_OUT_DIR)/initramfs.cpio.gz
MICROVM_INPUTS := scripts/build-microvm-image.sh $(MICROVM_OUT_DIR)/init.sh $(MICROVM_OUT_DIR)/inittab bin/lb-agent
@@ -291,7 +297,7 @@ distro-arm64:
distro-clean:
rm -rf dist/
.PHONY: build build-ui build-installer build-lb-agent build-ecs-agent build-system-image build-eks-node-image import-eks-node-image publish-eks-node-image build-microvm-image install-microvm go_build preflight test test-cover test-race diff-coverage bench test-actions test-harness manifest-check manifest-lint manifest-lint-update \
.PHONY: build build-ui build-installer build-lb-agent build-ecs-agent build-system-image build-eks-node-image import-eks-node-image publish-eks-node-image build-ecs-node-image import-ecs-node-image build-microvm-image install-microvm go_build preflight test test-cover test-race diff-coverage bench test-actions test-harness manifest-check manifest-lint manifest-lint-update \
deploy reinstall clean \
install-system install-go install-aws quickinstall \
lint fix govulncheck \
@@ -0,0 +1,21 @@
{
"cniVersion": "1.0.0",
"name": "ecs-bridge",
"plugins": [
{
"type": "bridge",
"bridge": "ecs0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"ranges": [
[{ "subnet": "172.31.0.0/16" }]
],
"routes": [{ "dst": "0.0.0.0/0" }]
}
},
{ "type": "portmap", "capabilities": { "portMappings": true } },
{ "type": "firewall" }
]
}
+33
View File
@@ -0,0 +1,33 @@
#!/sbin/openrc-run
# ecs-agent — Spinifex ECS container-instance agent. Resolves host identity from
# IMDSv2, connects to the cluster NATS bus, registers + heartbeats on the Layer-2
# subjects, and pulls task images through containerd.
#
# Static config (gateway URL, CA, region, cluster, NATS, containerd socket) comes
# from /etc/spinifex-ecs/agent.env, dropped by cloud-init user-data at launch.
description="Spinifex ECS container-instance agent"
command="/usr/local/bin/ecs-agent"
command_background="yes"
pidfile="/run/ecs-agent.pid"
output_log="/var/log/ecs-agent.log"
error_log="/var/log/ecs-agent.log"
start_stop_daemon_args="--wait 2000"
depend() {
need net containerd
after firewall ecs-ca-install
}
start_pre() {
# Cloud-init drops the agent config here; OpenRC injects it into the agent's
# environment. Absent file is tolerated — the agent falls back to its
# built-in defaults (cluster=default, local NATS, default IMDS).
if [ -f /etc/spinifex-ecs/agent.env ]; then
# shellcheck disable=SC1091
. /etc/spinifex-ecs/agent.env
export ECS_GATEWAY_URL ECS_GATEWAY_CA ECS_REGION ECS_CLUSTER \
ECS_NATS_URL ECS_IMDS_BASE ECS_CONTAINERD_SOCKET ECS_HEARTBEAT_INTERVAL
fi
}
@@ -0,0 +1,33 @@
#!/sbin/openrc-run
# ecs-ca-install — first-boot oneshot that installs the cloud-init-delivered
# gateway CA into the system trust store, so containerd trusts the internal ECR
# registry's TLS when pulling task images. Ordered before containerd + ecs-agent.
#
# The CA is delivered per-instance (not baked) at /etc/spinifex-ecs/gateway-ca.pem
# by cloud-init user-data, mirroring how EKS delivers EKS_GATEWAY_CA. No-op when
# the file is absent — containerd then relies on the stock system trust.
description="Install Spinifex gateway CA into the system trust (first boot)"
GATEWAY_CA="/etc/spinifex-ecs/gateway-ca.pem"
TRUST_DST="/usr/local/share/ca-certificates/spinifex-gateway.crt"
depend() {
before containerd ecs-agent
after localmount
}
start() {
if [ ! -f "${GATEWAY_CA}" ]; then
einfo "no gateway CA at ${GATEWAY_CA}; using stock system trust"
return 0
fi
ebegin "Installing gateway CA into system trust"
install -D -m 0644 "${GATEWAY_CA}" "${TRUST_DST}"
update-ca-certificates >/dev/null 2>&1
eend $?
}
stop() {
return 0
}
+35
View File
@@ -0,0 +1,35 @@
IMAGE_NAME="ecs-agent"
IMAGE_DESCRIPTION="Spinifex ECS container instance — Alpine + containerd + runc + cni-plugins + ecs-agent"
DISTRO="alpine"
ALPINE_VERSION="3.21.7"
# 4G root: containerd content store (pulled task images) lives under
# /var/lib/containerd on the root disk. Instances can attach a larger root EBS at
# RunInstances; this is the baked floor.
IMAGE_SIZE="4G"
# containerd-openrc ships the OpenRC service for containerd (the containerd apk
# itself has no init script). cni-plugins + the baked bridge conflist ready the
# image for Sprint 4d task networking. curl is used by the first-boot CA hook and
# IMDS probes.
APK_PACKAGES="containerd containerd-openrc runc cni-plugins ca-certificates curl openrc"
# ecs-agent is the only built binary (static, FIPS boot like the host build).
BUILD_COMMANDS="CGO_ENABLED=0 GOFIPS140=v1.0.0 go build -ldflags \"-s -w\" -o bin/ecs-agent ./cmd/ecs-agent"
INSTALL_BINARIES="bin/ecs-agent:/usr/local/bin/ecs-agent"
INSTALL_FILES="\
scripts/images/ecs-agent/ecs-agent.initd:/etc/init.d/ecs-agent \
scripts/images/ecs-agent/ecs-ca-install.initd:/etc/init.d/ecs-ca-install \
scripts/images/ecs-agent/10-bridge.conflist:/etc/cni/net.d/10-bridge.conflist"
# ecs-ca-install (first boot) installs the cloud-init gateway CA into the system
# trust before containerd starts; containerd then trusts the ECR registry TLS.
# ecs-agent registers + heartbeats on the NATS bus once net + containerd are up.
ENABLE_SERVICES="containerd ecs-ca-install ecs-agent"
SETUP_SCRIPT="scripts/images/ecs-agent/setup.sh"
AMI_NAME="spinifex-ecs-node"
SYSTEM_TAG="spinifex:managed-by=ecs"
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
set -eu
# setup.sh — guest customisation for the spinifex-ecs-node AMI.
#
# Runs inside the libguestfs appliance (via virt-customize --run) under
# build-system-image.sh, after packages, binaries, and INSTALL_FILES are placed.
# Sets exec bits on the OpenRC services, wires the CNI bin path containerd's CRI
# plugin expects, and applies the standard Alpine-cloud serial-console + fast
# boot-menu tweaks so orchestrator-captured ttyS0 logs work.
# INSTALL_FILES land 0644; OpenRC requires 0755 on init scripts.
chmod 0755 /etc/init.d/ecs-agent /etc/init.d/ecs-ca-install
# cni-plugins (alpine) installs to /usr/libexec/cni; containerd's CRI plugin
# defaults its bin dir to /opt/cni/bin. Symlink so the baked bridge conflist
# resolves its plugins when Sprint 4d wires task networking.
mkdir -p /opt/cni
ln -sf /usr/libexec/cni /opt/cni/bin
# Content-store + agent config dirs.
mkdir -p /var/lib/containerd /etc/spinifex-ecs
# Bind /dev/console to the serial port so userspace boot output (OpenRC service
# starts, cloud-init, ecs-agent registration) reaches ttyS0, which the
# orchestrator captures host-side. Stock Alpine lists console=tty0 last and Linux
# makes the last console= the controlling /dev/console — reorder so ttyS0 wins.
sed -i \
's|console=ttyS0,115200n8 console=ttyAMA0,115200n8 console=tty0|console=tty0 console=ttyAMA0,115200n8 console=ttyS0,115200n8|' \
/etc/update-extlinux.conf /boot/extlinux.conf
# Cut the boot-menu countdown from 10s to ~1s (a fixed tax on every VM start).
# Patch the generator config (seconds) and the rendered output (1/10s) so a
# regenerate keeps the short value; a small nonzero keeps the menu interruptible.
sed -i 's/^timeout=.*/timeout=1/' /etc/update-extlinux.conf
sed -i 's/^TIMEOUT[[:space:]].*/TIMEOUT 10/' /boot/extlinux.conf
echo "[ecs-node-setup] done"