k3s: add airgap images to passthru attributes

The k3s update script filters the assets of a
corresponding release for airgap images archives
and provides these as passthru attributes of the
k3s derivation. We use zstd archives, as these
offer the best compression ratios and decompression
speed. Furthermore, the `airgapImages` passthru
provides the images archive that matches the host
platform architecture, however, this only works
for aarch64 and x86_64. In addition, a txt file
listing all container images of a release is made
available via a passthru attribute. The airgap
images archives can be combined nicely with the
`services.k3s.images` option, e.g. to pre-provision
k3s nodes for environments without Internet
connectivity.
This commit is contained in:
Robert Rose 2024-07-30 10:25:57 +02:00
parent c38f9ee113
commit eeacf85fb9
9 changed files with 201 additions and 40 deletions

View File

@ -0,0 +1,44 @@
# A test that imports k3s airgapped images and verifies that all expected images are present
import ../make-test-python.nix (
{ lib, k3s, ... }:
{
name = "${k3s.name}-airgap-images";
meta.maintainers = lib.teams.k3s.members;
nodes.machine =
{ pkgs, ... }:
{
# k3s uses enough resources the default vm fails.
virtualisation.memorySize = 1536;
virtualisation.diskSize = 4096;
services.k3s = {
enable = true;
role = "server";
package = k3s;
# Slightly reduce resource usage
extraFlags = [
"--disable coredns"
"--disable local-storage"
"--disable metrics-server"
"--disable servicelb"
"--disable traefik"
];
images = [ k3s.airgapImages ];
};
};
testScript = ''
import json
start_all()
machine.wait_for_unit("k3s")
machine.wait_until_succeeds("journalctl -r --no-pager -u k3s | grep \"Imported images from /var/lib/rancher/k3s/agent/images/\"", timeout=60)
images = json.loads(machine.succeed("crictl img -o json"))
image_names = [i["repoTags"][0] for i in images["images"]]
with open("${k3s.imagesList}") as expected_images:
for line in expected_images:
assert line.rstrip() in image_names, f"The image {line.rstrip()} is not present in the airgap images archive"
'';
}
)

View File

@ -0,0 +1,18 @@
{
"airgap-images-amd64": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.28.11%2Bk3s2/k3s-airgap-images-amd64.tar.zst",
"sha256": "199nxfxwr52cddk2ljchhxaigyi0al3lzyc0jy2am4aljlm0jivy"
},
"airgap-images-arm": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.28.11%2Bk3s2/k3s-airgap-images-arm.tar.zst",
"sha256": "02riiiwwr0h3zhlxxmjn5p8ws354rr2gk44x3kz9d7sxqn17sz4w"
},
"airgap-images-arm64": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.28.11%2Bk3s2/k3s-airgap-images-arm64.tar.zst",
"sha256": "0bs9wj33appb9xpsb2v1xz4xck4qq6g74flnc0mxf9warwr4988r"
},
"images-list": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.28.11%2Bk3s2/k3s-images.txt",
"sha256": "0245zra2h8756kq2v8nwl6gji749xlvy1y1bkab8vz5b0vpqhfxy"
}
}

View File

@ -4,6 +4,7 @@
k3sRepoSha256 = "1k1k3qmxc7n2h2i0g52ad4gnpq0qrvxnl7p2y0g9dss1ancgqwsd";
k3sVendorHash = "sha256-tzcMcsTmY8lG+9EyYkzYJm1YU/8tGpxpH7oZ4Jl/yNU=";
chartVersions = import ./chart-versions.nix;
imagesVersions = builtins.fromJSON (builtins.readFile ./images-versions.json);
k3sRootVersion = "0.12.2";
k3sRootSha256 = "1gjynvr350qni5mskgm7pcc7alss4gms4jmkiv453vs8mmma9c9k";
k3sCNIVersion = "1.4.0-k3s2";

View File

@ -0,0 +1,18 @@
{
"airgap-images-amd64": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.29.6%2Bk3s2/k3s-airgap-images-amd64.tar.zst",
"sha256": "1d1adpjxxgkflm4xqzynsib67pga85r1qmhkhh540nl0rppbq7gr"
},
"airgap-images-arm": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.29.6%2Bk3s2/k3s-airgap-images-arm.tar.zst",
"sha256": "07c085y5qy8h5ja2ms3np61d7wkp6gic82snx70qlsm5fm3ak3z7"
},
"airgap-images-arm64": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.29.6%2Bk3s2/k3s-airgap-images-arm64.tar.zst",
"sha256": "0ljajvz0n0mmwkdl1rwpwqmhgxqivakdpfyaqsascdzfk0qpv5gp"
},
"images-list": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.29.6%2Bk3s2/k3s-images.txt",
"sha256": "0245zra2h8756kq2v8nwl6gji749xlvy1y1bkab8vz5b0vpqhfxy"
}
}

View File

@ -4,6 +4,7 @@
k3sRepoSha256 = "0wagfh4vbvyi62np6zx7b4p6myn0xavw691y78rnbl32jckiy14f";
k3sVendorHash = "sha256-o36gf3q7Vv+RoY681cL44rU2QFrdFW3EbRpw3dLcVTI=";
chartVersions = import ./chart-versions.nix;
imagesVersions = builtins.fromJSON (builtins.readFile ./images-versions.json);
k3sRootVersion = "0.13.0";
k3sRootSha256 = "1jq5f0lm08abx5ikarf92z56fvx4kjpy2nmzaazblb34lajw87vj";
k3sCNIVersion = "1.4.0-k3s2";

View File

@ -0,0 +1,18 @@
{
"airgap-images-amd64": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.30.2%2Bk3s2/k3s-airgap-images-amd64.tar.zst",
"sha256": "1d1adpjxxgkflm4xqzynsib67pga85r1qmhkhh540nl0rppbq7gr"
},
"airgap-images-arm": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.30.2%2Bk3s2/k3s-airgap-images-arm.tar.zst",
"sha256": "1hjhlj4b5ddaqhpmqbbvhvgzryi5j84i8bmpl3yij87yjkz3kld7"
},
"airgap-images-arm64": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.30.2%2Bk3s2/k3s-airgap-images-arm64.tar.zst",
"sha256": "1r9rd70qp8x57j3hdpgwgkzchykphw0x4yd8c1jwjfaqm5df1w0d"
},
"images-list": {
"url": "https://github.com/k3s-io/k3s/releases/download/v1.30.2%2Bk3s2/k3s-images.txt",
"sha256": "0245zra2h8756kq2v8nwl6gji749xlvy1y1bkab8vz5b0vpqhfxy"
}
}

View File

@ -4,6 +4,7 @@
k3sRepoSha256 = "0hy0f44hj5n5nscr0p52dbklvj2ki2vs7k0cgh1r8xlg4p6fn1b0";
k3sVendorHash = "sha256-Mj9Q3TgqZoJluG4/nyuw2WHnB3OJ+/mlV7duzWt1B1A=";
chartVersions = import ./chart-versions.nix;
imagesVersions = builtins.fromJSON (builtins.readFile ./images-versions.json);
k3sRootVersion = "0.13.0";
k3sRootSha256 = "1jq5f0lm08abx5ikarf92z56fvx4kjpy2nmzaazblb34lajw87vj";
k3sCNIVersion = "1.4.0-k3s2";

View File

@ -12,6 +12,8 @@ lib:
# Based on the traefik charts here: https://github.com/k3s-io/k3s/blob/d71ab6317e22dd34673faa307a412a37a16767f6/scripts/download#L29-L32
# see also https://github.com/k3s-io/k3s/blob/d71ab6317e22dd34673faa307a412a37a16767f6/manifests/traefik.yaml#L8
chartVersions,
# Air gap container images that are released as assets with every k3s release
imagesVersions,
# taken from ./scripts/version.sh VERSION_CNIPLUGINS https://github.com/k3s-io/k3s/blob/v1.23.3%2Bk3s1/scripts/version.sh#L45
k3sCNIVersion,
k3sCNISha256 ? lib.fakeHash,
@ -30,41 +32,42 @@ lib:
# It is likely we will have to split out additional builders for additional
# versions in the future, or customize this one further.
{
lib,
makeWrapper,
socat,
iptables,
iproute2,
ipset,
bash,
bridge-utils,
btrfs-progs,
conntrack-tools,
buildGoModule,
runc,
rsync,
kmod,
libseccomp,
pkg-config,
conntrack-tools,
coreutils,
ethtool,
util-linux,
fetchFromGitHub,
fetchgit,
fetchurl,
fetchzip,
fetchgit,
zstd,
yq-go,
sqlite,
nixosTests,
pkgsBuildBuild,
go,
runCommand,
bash,
procps,
coreutils,
gnugrep,
findutils,
gnugrep,
gnused,
go,
iproute2,
ipset,
iptables,
kmod,
lib,
libseccomp,
makeWrapper,
nixosTests,
pkg-config,
pkgsBuildBuild,
procps,
rsync,
runc,
runCommand,
socat,
sqlite,
stdenv,
systemd,
util-linux,
yq-go,
zstd,
}:
# k3s is a kinda weird derivation. One of the main points of k3s is the
@ -122,6 +125,39 @@ let
traefikChart = fetchurl chartVersions.traefik;
traefik-crdChart = fetchurl chartVersions.traefik-crd;
mutFirstChar =
f: s:
let
firstChar = f (lib.substring 0 1 s);
rest = lib.substring 1 (-1) s;
in
firstChar + rest;
kebabToCamel =
s:
mutFirstChar lib.toLower (lib.concatMapStrings (mutFirstChar lib.toUpper) (lib.splitString "-" s));
# finds the images archive for the desired architecture, aborts in case no suitable archive is found
findImagesArchive =
arch:
let
imagesVersionsNames = builtins.attrNames imagesVersions;
in
lib.findFirst (
n: lib.hasInfix arch n
) (abort "k3s: no airgap images for ${arch} available") imagesVersionsNames;
# a shortcut that provides the images archive for the host platform. Currently only supports
# aarch64 (arm64) and x86_64 (amd64), aborts on other architectures.
airgapImages = fetchurl (
if stdenv.isAarch64 then
imagesVersions.${findImagesArchive "arm64"}
else if stdenv.isx86_64 then
imagesVersions.${findImagesArchive "amd64"}
else
abort "k3s: airgap images cannot be found automatically for architecture ${stdenv.hostPlatform.linuxArch}, consider using an image archive with an explicit architecture."
);
# so, k3s is a complicated thing to package
# This derivation attempts to avoid including any random binaries from the
# internet. k3s-root is _mostly_ binaries built to be bundled in k3s (which
@ -417,21 +453,26 @@ buildGoModule rec {
runHook postInstallCheck
'';
passthru = {
k3sCNIPlugins = k3sCNIPlugins;
k3sContainerd = k3sContainerd;
k3sRepo = k3sRepo;
k3sRoot = k3sRoot;
k3sServer = k3sServer;
mkTests =
version:
let
k3s_version = "k3s_" + lib.replaceStrings [ "." ] [ "_" ] (lib.versions.majorMinor version);
in
lib.mapAttrs (name: value: nixosTests.k3s.${name}.${k3s_version}) nixosTests.k3s;
tests = passthru.mkTests k3sVersion;
updateScript = updateScript;
};
passthru =
{
inherit airgapImages;
k3sCNIPlugins = k3sCNIPlugins;
k3sContainerd = k3sContainerd;
k3sRepo = k3sRepo;
k3sRoot = k3sRoot;
k3sServer = k3sServer;
mkTests =
version:
let
k3s_version = "k3s_" + lib.replaceStrings [ "." ] [ "_" ] (lib.versions.majorMinor version);
in
lib.mapAttrs (name: value: nixosTests.k3s.${name}.${k3s_version}) nixosTests.k3s;
tests = passthru.mkTests k3sVersion;
updateScript = updateScript;
}
// (lib.mapAttrs' (
name: _: lib.nameValuePair (kebabToCamel name) (fetchurl imagesVersions.${name})
) imagesVersions);
meta = baseMeta;
}

View File

@ -69,6 +69,24 @@ cat > chart-versions.nix.update <<EOF
EOF
mv chart-versions.nix.update chart-versions.nix
# Get all airgap images files associated with this release
IMAGES_ARCHIVES=$(curl "https://api.github.com/repos/k3s-io/k3s/releases/tags/v${K3S_VERSION}" | \
# Filter the assets so that only zstd archives and text files that have "images" in their name remain
# Modify the name and write the modified name and download URL to a string
jq -r '.assets[] | select(.name | contains("images")) |
select(.content_type == "application/zstd" or .content_type == "text/plain; charset=utf-8") |
.name = (.name | sub("k3s-"; "") | sub(".tar.zst"; "") | sub(".txt"; "-list")) |
"\(.name) \(.browser_download_url)"')
# Create a JSON object for each airgap images file and prefetch all download URLs in the process
# Combine all JSON objects and write the result to images-versions.json
while read -r name url; do
jq --null-input --arg name "$name" \
--arg url "$url" \
--arg sha256 "$(nix-prefetch-url --quiet "${url}")" \
'{$name: {"url": $url, "sha256": $sha256}}'
done <<<"${IMAGES_ARCHIVES}" | jq --slurp 'reduce .[] as $item ({}; . * $item)' > images-versions.json
FILE_GO_MOD=${WORKDIR}/go.mod
curl --silent https://raw.githubusercontent.com/k3s-io/k3s/${K3S_COMMIT}/go.mod > $FILE_GO_MOD
@ -105,6 +123,7 @@ cat >versions.nix <<EOF
k3sRepoSha256 = "${K3S_REPO_SHA256}";
k3sVendorHash = "${FAKE_HASH}";
chartVersions = import ./chart-versions.nix;
imagesVersions = builtins.fromJSON (builtins.readFile ./images-versions.json);
k3sRootVersion = "${K3S_ROOT_VERSION}";
k3sRootSha256 = "${K3S_ROOT_SHA256}";
k3sCNIVersion = "${CNIPLUGINS_VERSION}";