- 包管理器
- 操作系统
【免费下载链接】nixpkgs
Nix Packages collection & NixOS
导读
K3s 与 Kubernetes 这类集群软件与普通软件包(如 bash)有一个本质差异:它们无法“原子化”升级。在 Nixpkgs 中,nixos-rebuild switch可以无痛切换 bash 版本,但运行在多台 NixOS 机器上的 K3s 集群,每台机器独立升级,不同版本之间必须通过一段临时的版本偏差(version skew)保持互操作。本文以 pkgs/applications/networking/cluster/k3s/docs/VERSIONING.md 为核心,结合仓库内default.nix、各小版本versions.nix、builder.nix与 NixOS 模块源码,完整讲解 Nixpkgs 中 K3s 的两类包(标准k3s包与版本化包k3s_1_xx)的维护策略、补丁版本支持生命周期,以及 NixOS 发行版之间保证升级路径合法性的回移(backport)机制。
核心问题:集群软件为什么无法“原子升级”
绝大多数 Nixpkgs 包是单机软件:升级 bash 时,旧版本与新版本之间不存在任何运行时交互,nixos-rebuild switch直接完成替换即可。K3s/Kubernetes 则完全不同——它通常横跨多台 NixOS 机器运行,而每台机器是独立升级的。这意味着:
- 在集群滚动升级期间,控制平面节点可能运行 1.36.x,而部分工作节点仍运行 1.34.x;
- 升级窗口内的任意时刻,不同版本的控制平面与工作节点必须能够正常通信与协调;
- 若版本跨度超过上游允许的偏移范围,集群可能进入不兼容状态。
因此,Nixpkgs 内维护 K3s 的核心原则是:始终维持一条不违背上游版本偏移策略(version skew policy)的“合法升级路径”。上游 Kubernetes 项目在官方版本偏移策略中规定了受支持的组件升级顺序(例如控制平面与 kubelet 之间允许的版本差、升级的先后次序),Nixpkgs 的 K3s 维护工作必须保证用户在任何 NixOS 版本组合下都能按合法顺序完成升级。
这一约束直接决定了本文后面描述的一切:为什么要有版本化包、为什么补丁版本要在发行分支上回移、为什么每个 NixOS 发行版只保留一个 K3s 版本。
补丁版本支持生命周期:K3s 跟随上游 K8s
K3s 构建在 Kubernetes 之上,其发布节奏与支持窗口基本与上游 K8s 同步——K3s 通过挑选(cherry-picking)K8s 的补丁构建出自己的发布。因此 Nixpkgs 采用一个关键假设:
假设 K3s 的支持生命周期与上游 K8s 完全一致。
上游 K8s 的发布与支持生命周期(含维护期与 EOL 日期)由官方支持站点维护,另有以表格形式呈现的当前支持时间线可查。其规律可以概括为:
- 大约每 4 个月发布一个新 Kubernetes 版本;
- 每个版本被支持的时间略超过 1 年。
这一生命周期假设是 Nixpkgs 决定“何时新增版本化包、何时移除版本化包”的时间基准。由于 K3s 的补丁版本来自对 K8s 补丁的挑选,其补丁发布支持生命周期自然与上游保持一致,Nixpkgs 无需单独维护一套 K3s 自己的支持周期表。
Nixpkgs unstable 分支中的两类 K3s 包
查看 pkgs/applications/networking/cluster/k3s/default.nix,可以清楚看到nixos-unstable分支上同时维护着两类包:
{ lib, callPackage, ... }@args: let k3s_builder = import ./builder.nix lib; forVersions = versions: callPackage (k3s_builder (import versions)) extraArgs; extraArgs = removeAttrs args [ "callPackage" ]; in { k3s_1_34 = forVersions ./1_34/versions.nix; k3s_1_35 = forVersions ./1_35/versions.nix; k3s_1_36 = forVersions ./1_36/versions.nix; k3s_1_37 = forVersions ./1_37/versions.nix; }标准k3s包:跟随上游最新
标准k3s包没有固定小版本,上游每发布一个新版本,它就被更新到该版本。在仓库当前的 pkgs/top-level/all-packages.nix 中可以看到其映射关系:
inherit (callPackage ../applications/networking/cluster/k3s { }) k3s_1_34 k3s_1_35 k3s_1_36 k3s_1_37 ; k3s = k3s_1_36;即当前k3s标准包指向k3s_1_36,并会随上游发布持续前移。
版本化包:k3s_1_28、k3s_1_29、k3s_1_30等
版本化包(如k3s_1_34、k3s_1_35…)则遵循补丁版本支持生命周期被维护:它们不会盲目跟随上游,而是按前一节的生命周期规则决定去留。每个版本化包的具体版本信息保存在各自的 1_37/versions.nix 之类的文件中,例如:
{ k3sVersion = "1.37.0+k3s1"; k3sCommit = "bb7cf0657bafdfbb4349d1740810442d09c2248a"; k3sRepoSha256 = "0glzg974w7f0jdaljr9bxw36qv53chii25rjbrvlbzlyzjq19wws"; k3sVendorHash = "sha256-AObmRWcIyFDy1aTA66LLFYnnUQzpf6+Ts6HQRA3i26A="; chartVersions = import ./chart-versions.nix; imagesVersions = builtins.fromJSON (builtins.readFile ./images-versions.json); k3sRootVersion = "0.15.2"; k3sCNIVersion = "1.9.1-k3s1"; containerdVersion = "2.3.4-k3s1"; criCtlVersion = "1.37.0-k3s1"; flannelVersion = "v0.28.4"; kubeRouterVersion = "v2.6.3-k3s1"; criDockerdVersion = "v0.3.19-k3s5"; helmJobVersion = "v0.13.3-build20260727"; }每个versions.nix记录了构建该小版本所需的全套版本与哈希:K3s 自身 git tag 与 commit、仓库与 vendor 哈希、rootfs(k3s-root)、CNI 插件、containerd、crictl、flannel、kube-router、cri-dockerd、helm-job 等组件版本。这些参数在 builder.nix 中作为构建函数的输入被消费——从源码结构可以推断,版本化包正是通过forVersions把对应目录的versions.nix交给同一份builder.nix构建,从而保证各版本共享同一套打包逻辑,只是版本参数不同。
版本化包的移除规则
版本化包何时从nixos-unstable移除?规则有两条,满足其一即移除:
- 上游已生命周期终结(EOL):该 K3s/K8s 版本不再受上游支持;
- 已比
nixos-stable中的标准k3s包更旧:当标准包在 stable 分支上前进到新版本后,旧的版本化包就失去了保留价值。
也就是说,版本化包是“向后兼容的桥梁”,其存在价值在于为停留在旧 NixOS 发行版的用户提供合法升级路径,一旦其使命完成(上游停更或已被 stable 超越),就从 unstable 移除。
NixOS 发行分支上的版本管理:每个发行版只有一个 K3s 版本
同样的两类包也维护在 NixOS 的发行分支(release branches,如 24.05、24.11)上,但在发行版内有一些特殊考虑。
单一版本原则:避免发行版生命周期内的大版本跳变
NixOS 发行版(24.05、24.11 等)应尽量避免在其支持生命周期内引入废弃软件或大版本升级。因此,每个 NixOS 发行版发布时只能有一个k3s版本。
以 NixOS 24.05 为例:其k3s包在整条发行生命期内指向k3s_1_30,发布时不存在其他版本。用户只要使用默认的services.k3s.package,集群就稳定运行在 1.30.x,不会在 24.05 生命周期中途突然被升到 1.32——这对生产集群的可预期性至关重要。
单一版本原则与升级路径的冲突
但“单一版本”与“跨发行版平滑升级”存在直接矛盾:
- 假设 NixOS 24.05 的
k3s是 1.30.x,而下一个发行版 NixOS 24.11 的k3s是 1.32.x; - 若用户直接升级 NixOS 发行版,K3s 会从 1.30 一步跳到 1.32;
- 这个跨度可能正好踩中上游版本偏移策略的红线,导致集群在升级过程中失配。
因此必须给用户提供一条“在升级 NixOS 之前、先在本发行版内把 K3s 升上去”的通道。
回移机制:为旧发行版补齐中间版本
解法是:K3s 维护者会在k3s_1_31、k3s_1_32发布后,把它们从nixos-unstable回移(backport)到 NixOS 24.05 发行分支。
这样当 NixOS 24.11 正式发布(其k3s包指向k3s_1_32)时,仍停留在 24.05 的用户已经拥有合法升级路径:
- 在 24.05 上先把
services.k3s.package从k3s切换到k3s_1_31; - 升级整个集群(先控制平面后工作节点,符合偏移策略);
- 再把
services.k3s.package切到k3s_1_32,再次升级集群; - 最后才把整个系统升级到 NixOS 24.11,此时 K3s 已是 1.32.x,与 24.11 的默认版本一致,无任何版本跳变。
用户在实际操作中正是通过 NixOS 模块的services.k3s.package选项指定包——该选项在 nixos/modules/services/cluster/rancher/k3s.nix 中定义(模块中cfg = config.services.k3s;,且通过config.services.k3s.package.airgap-images引用当前所选包的离线镜像归档)。因此“先升包、再升发行版”完全可以通过模块配置表达,无需手工替换二进制。
三个发行版的完整示例
VERSIONING.md 用一个三发行版示例完整展示了回移机制如何层层衔接。以 23.11 → 24.05 → 24.11 为例:
- NixOS 23.11
- k3s/k3s_1_27(发布版本,补丁回移)
- k3s_1_28(回移)
- k3s_1_29(回移)
- k3s_1_30(回移)
- NixOS 24.05
- k3s/k3s_1_30(发布版本,补丁回移)
- k3s_1_31(回移)
- k3s_1_32(回移)
- NixOS 24.11
- k3s/k3s_1_32(发布版本,补丁回移)
这个表格值得仔细解读:
- “发布版本,补丁回移”:每个发行版自己的标准
k3s包在发行生命周期内持续接收上游补丁版本的挑选(例如 24.05 的k3s_1_30会跟随上游 1.30.x 补丁序列前进),但绝不跨小版本; - “回移”:每个发行版都从 unstable 回移了未来两个小版本(23.11 回移了 1.28/1.29/1.30,即其标准包 1.27 之后的两三个版本),为升级到下一个发行版铺路;
- 版本号完美衔接:23.11 的最后一个可升级版本 1.30,恰好是 24.05 的发布版本;24.05 回移的最高版本 1.32,恰好是 24.11 的发布版本。任何用户都能从任一旧发行版“一个版本一个版本”地爬到最新发行版的默认版本,每个单步的跨度都不超过偏移策略允许的范围。
从当前仓库状态可以印证这一模式仍在延续:仓库中的 pkgs/applications/networking/cluster/k3s 目前维护着k3s_1_34至k3s_1_37四个版本化包,标准k3s指向k3s_1_36——这正对应文档描述的“unstable 同时维护标准包与版本化包”的结构,且版本集合随上游发布持续滚动前移。
与构建体系的衔接:版本参数如何落到产物上
理解版本化策略后,可以进一步看版本参数如何真正影响产物。在 builder.nix 中,versionldflags把各组件版本通过 Go linker flags 注入二进制:
"-X ${PKG}/pkg/version.Version=${k3sVersion}" "-X ${PKG}/pkg/version.GitCommit=${lib.substring 0 8 k3sCommit}" "-X ${PKG_K8S_CLIENT}/version.gitVersion=v${k3sVersion}" "-X ${PKG_CRICTL}/version.Version=${criCtlVersion}" "-X ${PKG_CONTAINERD}/version.Version=${containerdVersion}" "-X ${PKG_CNI_PLUGINS}/pkg/utils/buildversion.BuildVersion=${k3sCNIVersion}" "-X ${PKG_ETCD}/api/v3/version.GitSHA=HEAD" "-X ${PKG_HELM_CONTROLLER}/pkg/controllers/chart.DefaultJobImage=rancher/klipper-helm:${helmJobVersion}"也就是说,k3s_1_34与k3s_1_37虽然是同一份 builder 构建,但注入的版本信息、vendored 依赖、内置 chart(如 traefik)、离线镜像归档(airgap-images)都来自各自versions.nix。这也解释了为什么版本化包之间是完整独立的派生:它们不仅版本号不同,连 containerd、crictl、flannel 等配套组件的版本都可能不同(对比 1_34/versions.nix 与 1_37/versions.nix 中containerdVersion与criCtlVersion的差异即可看出)。
此外,builder.nix的passthru.tests会为每个版本化包自动生成对应 NixOS 测试:versionedPackage = "k3s_" + lib.replaceStrings [ "." ] [ "_" ] (lib.versions.majorMinor k3sVersion)——从源码结构可以推断,每个小版本包都有独立的 NixOS 集成测试覆盖,这保证了回移到发行分支的版本化包在真实集群场景下的可用性,是“合法升级路径”能够被持续维护的质量保障。
小结:Nixpkgs K3s 版本管理的三层原则
综合 VERSIONING.md 与仓库源码,可以提炼出 Nixpkgs 维护 K3s 版本的三层核心原则:
- 生命周期对齐:K3s 的支持窗口假设与上游 K8s 一致(约每 4 个月一个版本、每个版本支持略超 1 年),作为所有版本决策的时间基准;
- unstable 双轨制:标准
k3s包跟随上游滚动更新,版本化包k3s_1_xx按生命周期维护,EOL 或被 stable 超越即移除; - 发行分支回移制:每个 NixOS 发行版只保留一个发布版本,同时从 unstable 回移后续一两个版本化包,让旧发行版用户能在升级 NixOS 之前先把集群平滑升到目标版本,全程不触碰上游版本偏移策略红线。
这套机制的设计目标非常明确:让 NixOS 用户既能享受 K3s 新版本的持续演进,又能在跨发行版大版本升级时保持集群始终处于受支持的版本组合内。如果你正在维护一个长期运行的 K3s 集群并计划跨 NixOS 发行版升级,最稳妥的操作路径就是:先在当前发行版内通过services.k3s.package逐步切换到回移版本(k3s_1_xx),逐版本升级集群,最后再执行 NixOS 发行版升级——这正是本文所述升级路径设计的完整实战落地。
- 包管理器
- 操作系统
【免费下载链接】nixpkgs
Nix Packages collection & NixOS
相关推荐
Docker PHP多版本管理策略:从8.2到8.5的平滑升级路径
在现代Web开发中, Docker PHP多版本管理 已成为开发者和运维团队必须掌握的核心技能。面对从PHP 8.2到8.5的持续迭代,如何实现平滑升级、确保环
云原生IntentKit版本控制策略:从0.6.x到0.7.0的平滑升级路径
IntentKit版本控制策略:从0.6.x到0.7.0的平滑升级路径 引言:为什么版本控制对IntentKit至关重要 在AI Agent框架快速迭代的今天,
人工智能AI Agent多智能体后端前端区块链Web35分钟快速上手:Tectonic现代TeX引擎完整配置指南
5分钟快速上手:Tectonic现代TeX引擎完整配置指南 Tectonic是一个现代化的、完整的、自包含的TeX/LaTeX排版引擎,基于XeTeX和TeXL
CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考