资讯动态

Snap 包管理实战:沙箱、原子更新与跨发行版分发

发布时间:2026/9/17 1:13:24 来源:尧图企业网站定制
1. 先说清楚snap 到底解决的是谁的痛点第一次在一台干净的 Ubuntu 上敲sudo snap install code --classic然后看着进度条跑完、桌面图标自动出现很多人会以为这不就是个装软件的捷径。真拿它做过交付的人不会这么想。snap 是一套带沙箱、带原子更新、带回滚通道的跨发行版应用分发格式配套一个常驻后台的守护进程snapd以及一条从源码到商店再到设备的完整工具链。它要解决的核心问题不是怎么装而是装完之后三年这个应用还能不能在不折腾用户的前提下安全升级、出问题能不能秒退。这篇文章面向三类人一是在 Ubuntu 或 Ubuntu Core 上做交付、被 apt 依赖冲突折磨过的运维和嵌入式工程师二是想把内部工具打包成单一产物、发给不同发行版同事的开发者三是刚接触 Linux、被/snap、/var/snap这些目录搞懵、只想搞懂常用命令的新手。我会把 snap 的机制拆开讲——为什么它要这么设计、每一条命令背后动了什么、打包时哪些参数一步错步步错最后把我自己踩过的坑整理成可以直接对照的排查表。全文基于常见实践补充实现细节个别参数在不同版本的 snapd / snapcraft 上可能有差异以你本机snap --version和snapcraft --version的输出为准。2. 为什么会有 snap三个绕不开的老问题在讲命令之前值得先花点时间聊清楚为什么。因为后面所有的设计细节——只读镜像、通道、接口、回滚——都是从这三个问题里长出来的。搞懂动机你在遇到报错时才有判断力而不是照着别人的博客盲试。2.1 依赖地狱应用和发行版互相绑架传统仓库的分发模式是这样的应用声明我需要 libfoo 1.2发行版在发布那一刻把所有包冻结成一套自洽的组合然后整个系统的库版本就锁死在那个时间点。问题是库会升级、安全补丁会打、新的应用需要新版本。一旦某个签名验证库要从 1.x 升到 3.x依赖它的几十个包全得重新编译测试任何一个 ABI 变化都可能拖上几个月。snap 的做法很直接不共享系统库。每个 snap 里打包自己的应用文件和它需要的运行时这个运行时叫 base比如core22、core24本质是一个精简的 Ubuntu 根文件系统。应用在构建时链接的是 base 里的库运行时也只用 base 里的库跟宿主机的/usr/lib完全无关。这就把应用版本和系统版本彻底解耦了——你可以在一台 Ubuntu 20.04 上跑一个用 core24 构建的 snap因为它自带那一层。代价也是真实的每个 snap 都要背一份 base。如果装十个都基于 core22 的 snapbase 只会有一份按 revision 共享但如果有的用 core20、有的用 core22那就是两份完整的根文件系统躺在磁盘上。这是我见过最多的snap 怎么这么占地方的来源后面第 6 节会给具体的处理办法。2.2 一次打包多发行版运行把适配成本从 N 降到 1做过面向多发行版交付的人对这件事有肌肉记忆。同一个内部工具你要给 Ubuntu 打 deb、给 Fedora 打 rpm、给 Arch 写 PKGBUILD还得分别测 Debian 11/12、RHEL 8/9 的库版本差异。每加一个新目标平台就是一条新的 CI 流水线和一份新的测试矩阵。snap 改变了这个成本结构只要目标机器上装了 snapd同一个.snap文件就能装上去。发布通道里的包只按架构区分amd64、arm64、armhf 等不按发行版区分。对内部工具分发来说这一步省下的时间非常可观——我经手的一个小工具从三套打包脚本 三套测试缩减到一个 snapcraft.yaml 一条 CI 任务。不过别把这句话理解成snap 万能。目标机器上得有 snapd一些极简容器镜像、一些嵌入式环境默认没有内核和系统级组件比如驱动、内核模块不适合用 snap 分发那些还是要走发行版原生包。2.3 沙箱与权限把应用关进笼子里钥匙交给你传统 deb 包安装时以 root 身份把文件铺到/usr、/etc装完这个应用理论上能读写系统任何地方。一次供应链投毒或者一个被攻破的依赖代价就是整台机器。snap 默认运行在strict 限制模式下进程被 AppArmor 规则限定能访问哪些路径、被 seccomp 限定能调哪些系统调用、被 cgroup 限定资源网络、摄像头、麦克风、家目录这些敏感资源默认都拿不到必须通过接口interface显式授权。装完一个 snap 之后snap connections name看到的那些:plug和:slot本质上就是一份权限清单。这套设计带来的直接后果是很多装上了但跑不起来的问题原因不是软件坏了而是权限没连上。知道了这一点后面排障方向就明确了。3. 机制拆解一次snap install到底发生了什么3.1 从 squashfs 镜像到挂载点一个.snap文件其实就是一个squashfs 只读压缩镜像后缀名不同而已你把它改名成.img用unsquashfs -l也能看内容。安装时的实际动作大致是snapd从商店把镜像下载到/var/lib/snapd/snaps/name_revision.snap校验签名与校验和通过 loop 设备把这个镜像挂载到/snap/name/revision/Ubuntu Core 上则是dm-verity校验后挂载镜像完整性由内核层面保证在/snap/bin/下创建命令软链接如果是后台服务型 snap生成 systemd unit。理解只读这两个字很关键。snap 的安装目录你改不了/snap/name/current/只是一个指向当前 revision 的符号链接。所有可写的数据都必须落在别处用户数据在~/snap/name/revision/系统数据在/var/snap/name/revision/。这是我在给别人做 snap 打包支持时被问得最多的一点——我的程序要写配置文件到安装目录答案是不行必须改路径。3.2 通道channel一条轨道上的四个档位snap 的版本管理不走版本号走通道。格式是[track/]risk/branch最常见的四种风险级别通道定位适合谁stable经过完整测试的正式版默认安装目标生产环境、普通用户candidate候选发布等价于 RC发布前验证愿意报 bug 的人beta功能基本可用但未必稳定尝鲜用户、功能验证edge每天自动构建随时可能挂开发者、CI 冒烟测试track 用来分大版本线比如2.x/stable、3.x/stable适合同时维护两条 LTS 分支的项目。branch 是临时分支通道比如某个紧急修复的hotfix-123/stable用于小范围灰度。通道机制真正的价值在回滚。snap revert name会把符号链接指回上一个 revision一两秒完成不用重新下载也不用重装。这个能力在升级后线上服务起不来的场景下能救命。注意snap revert只能退一个 revision而且退回去之后 snapd 会在下一个自动刷新窗口再把你升上去——除非你用snap refresh --hold冻住。3.3 接口机制plug 与 slot 的语言接口是 snap 沙箱的开关体系。理解一句话就够应用侧声明 plug我需要什么系统或其他 snap 提供 slot我能给什么snapd 在中间做匹配和授权。常见的自动连接安装时自动完成network、network-bind、home部分受限、desktop、x11、wayland、opengl、audio-playback。常见的需要手动连接removable-media、camera、system-observe、snapd-control、docker。几个实操细节snap connections name看某个 snap 的连接状态snap interface 接口名看这个接口的定义、哪些 snap 在用它sudo snap connect snap:plug slot手动连sudo snap disconnect ...断开。有个反直觉的点自动连接不等于已连接。有些接口因为安全评级较高需要你手动connect而snap install不会报错也不会提醒只在snap connections里显示为未连接。新手最容易在这里卡半天。3.4 目录布局文件都去哪了路径用途可写/snap/name/rev/挂载后的只读应用根否/snap/name/current指向当前 revision 的软链否/snap/bin/命令软链通常在 PATH 里否/var/lib/snapd/snaps/下载下来的.snap镜像本体root/var/lib/snapd/seed/系统预装/种子 snaproot/var/lib/snapd/snaps/.../snapd守护进程自身root/var/snap/name/rev/系统级可写数据、服务状态是/var/snap/name/common/跨 revision 共享的系统数据是~/snap/name/rev/用户级数据是~/snap/name/common/跨 revision 共享的用户数据是common目录的设计值得单独说一句。因为 revision 会随着更新变化把数据写在rev目录里每次更新都会丢所以需要跨版本保留的东西数据库、模型文件、用户配置必须放common。打包时用$SNAP_USER_COMMON、$SNAP_COMMON这两个环境变量来定位别硬编码路径。4. 上手实操装好 snapd 并跑通第一个 snap4.1 在主流发行版上安装 snapdUbuntu 16.04 之后默认自带其他发行版要手装。# Debian 12 / Ubuntu sudo apt update sudo apt install -y snapd # Fedora sudo dnf install -y snapd sudo ln -s /var/lib/snapd/snap /snap # Arch / Manjaro sudo pacman -S --needed snapd sudo systemctl enable --now snapd.socket # openSUSE sudo zypper addrepo --refresh https://download.opensuse.org/repositories/system:/snappy/openSUSE_Leap_15.5 snappy sudo zypper --gpg-auto-import-keys refresh sudo zypper dup --from snappy sudo zypper install snapd装完检查一下snap version # 看 snapd 与 snap 客户端版本 systemctl status snapd # 守护进程是否在跑 echo $PATH | tr : \n | grep snap # /snap/bin 是否在 PATH 中注意Fedora 上必须建/snap这个软链否则挂载点不存在所有 snap 都会启动失败。这是 Fedora 用户第一号翻车点官方文档里写了但很容易被跳过。如果你的 shell 提示找不到snap命令但systemctl显示服务在跑通常是/snap/bin没进 PATH重新登录一次或者手动export PATH$PATH:/snap/bin。4.2 日常命令速查# 搜索与查看信息 snap find keyword # 商店搜索 snap info name # 版本、通道、体积、连接点 # 安装与卸载 sudo snap install name sudo snap install name --channelbeta sudo snap install name --classic # 需要审核通过的 classic 权限 sudo snap remove name sudo snap remove name --purge # 连数据一起删 # 列表与更新 snap list snap list --all # 含已禁用的旧 revision snap refresh --list # 只列出待更新不执行 sudo snap refresh sudo snap refresh namesnap list --all里会看到某些 revision 显示disabled那是被保留的旧版本用于回滚。它们占磁盘通过snap remove name --revisionn可以单独清掉注意别删当前正在用的那个。4.3 通道切换、回滚与冻结更新# 切到 beta 通道 sudo snap refresh name --channelbeta # 回滚到上一个 revision sudo snap revert name # 冻结自动更新 24 小时做演示、跑长任务时很有用 sudo snap refresh --hold24h sudo snap refresh --hold72h # 解除 sudo snap refresh --unhold # 看定时刷新计划 snap refresh --timesnap refresh --time输出里会告诉你下次刷新窗口和上次刷新时间。默认一天检查四次分布在随机时间点。生产服务器上我一般会显式设置窗口避免它在业务高峰做挂载切换sudo snap set system refresh.timermon,thu,4:00-6:00顺手把旧 revision 保留数从 3 改成 2可以在磁盘紧张时省出可观空间sudo snap set system refresh.retain2 # 取值范围 2-20提示refresh.retain的值不能设成 1。设 1 会导致回滚能力失效snapd 会直接拒绝。4.4 服务型 snap 与配置项很多系统组件比如 k8s 相关的一堆组件、监控 agent是以 snap 形式提供的后台服务。snap services name # 列服务及状态 sudo snap start name.service sudo snap stop --disable name.service sudo snap restart name.service配置项走snap set/snap get具体支持哪些 key 由打包者在 snapcraft.yaml 里声明snap get name # 看当前配置 snap get name -d # 输出 JSON sudo snap set name keyvalue这套配置机制的好处是配置本身也被 snapd 管着会被记录在snap changes的历史里出问题能追溯是谁在什么时候改了什么。这一点比直接改/etc下的文件要可靠。5. 打包自己的 snap从 snapcraft.yaml 说起5.1 最小可用结构snapcraft.yaml是唯一的入口文件放在项目根目录。一个能跑的最小例子name: mytool base: core22 version: 1.2.0 summary: 一句话说明不超过 79 字符 description: | 多行描述。写清楚这个工具做什么、 有哪些限制、首次使用需要注意什么。 grade: stable confinement: strict architectures: - build-on: amd64 - build-on: arm64 apps: mytool: command: bin/mytool plugs: - network - home parts: mytool: plugin: dump source: ./dist organize: mytool: bin/mytool逐字段说一下容易踩的地方name全局唯一只能小写字母、数字、连字符且不能以连字符开头或结尾。一旦发布就改不了改名等于重新上架。base决定运行时的 Ubuntu 版本也决定构建环境。core22 对应 22.04 的根文件系统。新项目优先 core22 或 core24core20 已经偏老。version纯字符串别写数字否则1.10会被当成1.1。summary79 字符上限超了直接构建失败这个限制很硬。confinementstrict是默认也是最推荐的classic需要商店人工审核只有确实无法沙箱化的工具IDE、需要访问宿主工具链的编译器才用devmode是开发期的只记录不拦截模式绝对不能发布到 stable。5.2 构建环境别用 destructive-mode 图快snapcraft默认会在虚拟机或 LXD 容器里构建这保证了构建环境的纯净和可复现。第一次跑会拉镜像慢是正常的。# 默认后端自动选择 snapcraft pack # 显式指定用 LXD推荐在 Linux 上用比 Multipass 快很多 snapcraft pack --use-lxd # 指定只构建某架构 snapcraft pack --build-forarm64注意--destructive-mode会在当前机器上直接构建速度快但会往宿主机里装一堆构建依赖。我曾经在开发机上用它构建一个 Python 项目结果把系统的 Python 包环境污染了后面排查了半天。除非在一次性容器里否则建议老老实实用 LXD。有个很实用的调试开关snapcraft try它会把构建结果展开到当前目录的prime/里然后你可以用snap try prime/把它安装成开发模式 snap。改代码后重新构建不用重新安装就能生效因为是直接指向目录而不是挂载镜像。做迭代开发时这个流程比packinstall --dangerous快得多。5.3 part 与插件把构建逻辑组织好part 是构建单元每个 part 描述从哪拿源码、怎么编译、装到哪。常用的插件插件用途常见坑dump直接拷贝已经编译好的产物注意organize里的路径映射nil不构建只做放置适合打包纯脚本cmakeCMake 项目需要显式声明build-packagesautotoolsconfigure/make 项目configflags传参容易出错pythonPython 应用必须指定python-packages不指定就是空环境nodejsNode 应用锁文件必须提交否则构建不可复现goGo 项目模块缓存路径需要配置rustRust 项目编译慢建议加build-attributes: [no-system-libraries]谨慎使用make自定义 Makefile最灵活也最需要自己处理依赖多 part 时的依赖顺序用after:显式声明别指望 snapcraft 猜。多个 part 都会往$SNAPCRAFT_PART_INSTALL里放东西最后统一合并到prime/路径冲突时后处理的会覆盖前面这个顺序问题在打包静态资源和配置模板时特别容易出乱子。5.4 本地安装调试与发布# 本地装一个未签名的 snap开发用 sudo snap install --dangerous ./mytool_1.2.0_amd64.snap # 开发模式安装沙箱只告警不拦截 sudo snap install --devmode --dangerous ./mytool_1.2.0_amd64.snap # 静态检查提交前必跑 snapcraft lint ./mytool_1.2.0_amd64.snap # 登录并上传 snapcraft login snapcraft upload --releasestable ./mytool_1.2.0_amd64.snap--dangerous这个参数名字很吓人它的含义是跳过签名校验仅限本地开发。千万不要在自动化脚本里对从外部拿到的.snap用它。CI 里发布通常用导出的凭据避免明文账号密码snapcraft export-login --snapsmytool --channelsstable snapcraft-login.txt # 文件里是短期凭据在 CI 里配合环境变量注入6. 排障实录那些让人抓头发的报错6.1 三把排障的万能钥匙这三个命令基本能覆盖九成运行时问题建议记牢# 1. 以相同沙箱环境开一个 shell手动跑命令看真实报错 sudo snap run --shell mytool # 进入后手动执行 $SNAP/bin/mytool报错信息完整得多 # 2. 看系统日志里 AppArmor / seccomp 的拒绝记录 sudo journalctl -f -u snap.mytool.* # 服务型 snap sudo dmesg | tail -50 # AppArmor DENIED 会在这里 # 3. 看连接状态 snap connections mytoolsnap run --shell是我最常用的一个。直接启动应用时被沙箱拦下来日志往往只有一句干巴巴的 Permission denied进 shell 里手动跑同一个二进制通常能看到无法写入 /home/xxx/.config/yyy这类明确的路径信息顺着路径查接口就快了。6.2 典型问题速查表现象大概率原因处理方式装完命令找不到/snap/bin不在 PATH重新登录或改/etc/environment启动即退出无输出缺接口授权snap connections查未连接项手动connect无法读写 U 盘缺removable-mediasnap connect name:removable-media写不了家目录下的隐藏目录home接口默认不含隐藏文件把配置放$SNAP_USER_DATA或申请personal-files服务型 snap 反复重启前台运行问题 / 路径错journalctl -u snap.name.*看退出码升级后服务起不来新版本引入问题snap revert name秒退再排查磁盘莫名被吃掉几 GB旧 revision 累积snap list --all 调小refresh.retain--classic安装被拒该 snap 未获 classic 权限换 strict 版本或申请权限字体 / 主题和系统不一致沙箱无法访问宿主主题资源装gtk-common-themes或接受默认外观构建时 summary 报错超过 79 字符精简描述6.3 两个真实踩坑记录坑一数据写在rev目录里。早期我做一个采集工具图省事把 SQLite 数据库放在$SNAP_DATA下。第一次自动更新之后用户反馈数据全没了——因为$SNAP_DATA指向的是/var/snap/tool/rev/revision 一变就是新目录。正确做法是放$SNAP_COMMON。改完之后一切正常但那次事故让我在后续所有项目的第一条编码规范里都写了这一句跨版本持久化的东西一律走COMMON。坑二classic 的依赖是宿主的别当救心丸。有个工具因为要调用宿主机上的编译器我给它申请了classic。结果是它在开发机上跑得好好的到了另一台库版本不同的机器上就报符号找不到。因为 classic 模式下它链接的是宿主机库跨发行版一致性这个 snap 最核心的优势就没了。最后的方案是老老实实做 strict 打包把需要的工具链塞进 part 里。7. 和其他包管理器摆在一起看各自站什么位置7.1 与 apt / dnf 的分工这两层的定位其实不冲突反而经常配合。维度apt / dnfsnap打包对象系统组件、库、内核相关应用、服务、工具链依赖处理全局共享版本冲突风险高自带运行时互不影响更新粒度整系统一起升单应用独立升回滚麻烦要降级多个包snap revert一条命令沙箱基本没有默认 AppArmor seccomp体积小共享库大自带 base适合场景基础系统、驱动、库上层应用、跨发行版交付我自己的习惯是系统层用 apt业务应用用 snap两者边界清晰。遇到snap不合适的场景内核模块、需要深度集成系统服务的组件就老老实实回 apt别硬套。7.2 与 flatpak、AppImage、容器方案的差异方案运行时共享沙箱更新机制最适合snapbase 快照多个应用共享同一 base有接口授权后台自动 通道回滚桌面应用 服务 IoTflatpakruntime粒度较细有portal 机制依赖宿主工具纯桌面 GUI 应用AppImage完整自带无手动替换文件单文件分发、免安装容器镜像完全自带强隔离由容器编排管理服务端、微服务简单说AppImage 是一个文件拷过去就能跑最省事但没沙箱没更新flatpak 在桌面 GUI 场景生态很好容器解决的是另一个层面的问题服务端部署的原子性和编排能力比 snap 强得多。判断标准是你要交付的是一个应用还是一个系统前者 snap 很顺手后者还是容器或系统镜像。7.3 横向看现代包管理器都在解决同一类问题把视野拉开一点会发现近些年主流的包管理工具——不管是 Python 的uv、JS 的 pnpm、Rust 的 cargo——设计思路和 snap 惊人地相似锁定与可复现uv.lock、package-lock.json对应 snap 的 revision 和通道都是为了让昨天能跑的今天还能跑隔离uv的虚拟环境对应 snap 的 base都是自带一份依赖别污染全局原子安装与回滚uv的缓存硬链接、cargo 的Cargo.lock加 registry都是为了让动作可撤销内容寻址snap 的 revision 号、uv的哈希索引本质是同一个思路。搞懂一层的设计动机换到另一层几乎能立刻上手。这也是我为什么建议刚接触 Linux 包管理的朋友不要只背命令而是挑一个把机制吃透——剩下的都是同一套思想的变体。8. 嵌入式与设备端snap 的另一条主战场8.1 只读根文件系统 原子更新意味着什么在嵌入式设备上最怕的场景是升级过程中断电设备变砖。传统做法是双分区 A/B 切换逻辑复杂、空间占用大。Ubuntu Core 的思路是根文件系统只读系统本身和每个应用都是 snap。升级某个应用时snapd 先把新 revision 完整下载并校验完然后原子地切换软链指向。切换动作本身是瞬时的断电最多导致要么旧版要么新版不会出现半新半旧的状态。这就是所谓的原子性也是为什么很多做设备交付的团队会选这套方案。8.2 从开发机到设备端的流程大致链路是开发机上用 snapcraft 构建应用 snap → 构建一个自定义的模型断言model assertion描述这台设备预装哪些 snap → 产出最终系统镜像 → 刷入设备 → 设备从商店拉取后续更新。模型断言本质是一份签名过的 JSON声明设备型号、架构、预装的 snap 列表和它们的通道。这份文件一旦签名任何修改都需要重新签名防止设备端被随意篡改预装内容。对应用开发者来说设备端要关心的事情比桌面多几条架构嵌入式板子多是 arm64 或 armhf构建时要用--build-for指定或者用多架构构建流资源内存和磁盘都紧张base 的选择要慎重core22 比 core20 大一圈串口与 GPIO要访问硬件外设接口声明必须写清楚否则沙箱直接拦启动时间服务型 snap 的启动顺序用apps.name.after和before控制别指望默认顺序。8.3 团队协作里最容易出问题的两点第一通道规划没想清楚就开始发版。我见过一个团队把 dev、test、prod 三套环境全压在stable上靠版本号区分。结果是测试环境要回滚就必须动生产环境的包。正确做法是给不同环境用不同的 track 或 channel比如stable给生产、candidate给预发、edge给联调环境物理隔离比流程约束可靠得多。第二把 secrets 打进 snap 里。snap 是只读镜像任何人unsquashfs一下就能看到里面所有文件。凭据、密钥、设备证书一律不能放进去必须通过设备端配置文件、snap set或者外部服务下发。这条听起来像废话但我确实在别人的包里见过硬编码的访问令牌。9. 我个人在实际操作中攒下的几条经验第一刚上手别急着打包先玩透snap connections和snap run --shell。这两个命令能解决你 80% 的困惑。打包本身是体力活排障才是技术活。第二磁盘紧张时先看snap list --all的 disabled 项再考虑动 base。很多人第一反应是snap 太占地方了要卸载其实清掉几个旧 revision 往往就能回收好几 GB而且零风险。第三给内部工具做 snap 时把confinement从 strict 开始写。一开始就上 classic 会省事但后面想收紧到 strict 基本等于重做一遍接口设计早做早轻松。第四自动更新一定要在服务端显式设置时间窗口。refresh.timer设成业务低峰可以避免凌晨三点服务重启这种事变成早上的事故报告。桌面机器倒无所谓服务端机器上这条几乎是必须的。第五snapcraft lint请当成提交前的必跑项。它能提前抓出很多商店审核会拒绝的问题比如无效的桌面文件、缺失的图标、不规范的 name返工一次的时间远大于跑一次 lint。第六跨版本持久化数据路径一律写$SNAP_COMMON或$SNAP_USER_COMMON。这是我从一次真实的数据丢失里换来的教训写进团队编码规范之后就没再犯过。这个方向后续还能往下挖的地方不少比如用snap save/snap restore做系统状态的快照与恢复比如自定义接口content interface实现两个 snap 之间的数据共享再比如把构建流水线接进 CI 做多架构并行出包。这些等我把手上这套设备端交付跑顺了再单独写一篇。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价