Ubuntu snap完全指南:从依赖地狱到打包发布
不少Ubuntu用户第一次真正注意到snap不是在自己主动安装软件的时候而是在某次清理磁盘空间时发现/var/lib/snapd悄悄占了几个G或者是执行apt install时提示会顺带装上一堆snap相关依赖又或者是刚装完Firefox准备打开时发现启动动画转了好几圈。于是“要不要卸掉snap”“能不能把snapd停了”成了Linux社区里几乎每个月都会冒出来的老问题。这篇东西我打算把snap从头到尾讲透它是谁、为什么Ubuntu要强推、日常命令怎么用、网上被骂得最多的几个痛点到底怎么解决以及怎么用snapcraft把一个自己的小脚本打包成snap。不管你现在是打算躲开它还是准备好好用起来希望你看完之后能自己拿主意而不是跟着网上情绪化的帖子走。1. snap的出身与定位先搞懂它到底解决什么问题1.1 从“依赖地狱”说起传统Linux发行版的软件分发方式核心是“共享依赖”。拿Ubuntu的deb包举例一个软件安装后会依赖系统里的很多动态库比如libssl、libc、libgtk。这些库通常由发行版统一维护软件本身不携带它们。这样设计的好处是节省磁盘空间、统一升级入口但坏处也明显当A软件需要libfoo 1.0、B软件需要libfoo 2.0而系统里只能存在一个时冲突就来了。更常见的是你升级了一个共享库结果某个老应用第二天就崩了。这类问题被人叫了十几年“依赖地狱”。snap的解法很直接把应用文件、依赖库、运行时环境全部打包在一起形成一个“集装箱”。应用跑在独立的挂载目录里不污染系统库系统库也不影响它。副作用就是snap体积普遍比deb大因为它把很多东西重复打了进去。这就像超市里的净菜和散装菜的区别净菜方便但贵且包装占地方。1.2 snap的三个核心组成snap整个体系有三个容易混淆的概念snap文件、snapd守护进程、snapcraft构建工具。.snap文件是一种归档格式基于SquashFS只读文件系统压缩生成里面包含应用本体、依赖、元数据和安装脚本。snapd是运行在系统后台的守护进程负责snap的安装、挂载、升级、权限隔离。Ubuntu装上系统就会自带snapd。snapcraft是开发者用来制作snap的工具输入一个snapcraft.yaml配置文件和源码输出一个.snap包。安装之后snap应用会被挂载到/snap/名字/版本号/这种路径下其中current是一个软链接指向当前正在使用的版本。snap list里看到的版本和修订号也都对应这里的目录结构。这套“多版本并存、软链接切换”的设计是后面所有回滚和干净卸载功能的基础。1.3 和deb、AppImage、Flatpak比一下优劣势都在哪分发方式依赖处理沙箱隔离自动更新典型使用场景deb/apt共享系统依赖无依赖发行版仓库发行版基础组件、系统软件AppImage打包应用不装进系统弱不自动更新免安装便携软件Flatpak打包应用运行时共享有默认自动桌面GUI应用snap完全打包自带运行时有默认自动跨发行版、命令/服务/桌面全覆盖Flatpak和snap目标有重合但snap背后是Canonical和Ubuntu系统集成最深而且snap不止覆盖桌面应用还能管理snap services这类后台服务甚至能用于服务器、IoT设备。AppImage胜在免安装解压即用但应用与系统隔离弱没有统一的升级和权限管理。deb依然是系统软件和底层驱动的标准方案。snap的定位从来不是替代deb而是解决跨发行版分发和依赖隔离这两件事。2. 日常高频命令与版本机制从安装到回滚2.1 安装、运行、卸载的完整套路snap的命令设计得比较直白# 搜索软件 snap find firefox # 查看应用详细信息 snap info firefox # 安装 sudo snap install firefox # 查看本机已装的snap snap list # 更新某一个应用 sudo snap refresh firefox # 更新全部 sudo snap refresh # 卸载 sudo snap remove firefox安装命令需要sudo运行不需要因为snapd会在登录会话里建立应用的挂载环境。snap list输出里有个NOTES列如果显示classic说明这个应用运行在classic模式下没有沙箱隔离如果显示devmode说明是开发者模式。snap run这个命令平时用得不多但在特殊场景下很有用。比如某个snap应用的命令名和系统现有命令冲突了或者你想精确指定执行某个snap的入口点就可以用snap run 名字。此前遇到过有人在/usr/local/bin里放了一个同名的脚本结果命令行里执行时调用的是他那个脚本而不是snap应用用snap run就能绕过这类污染。2.2 channel通道机制stable、beta、edge怎么选snap用channel来管理发布通道从上到下依次是stable、candidate、beta、edge稳定性递减、更新频率递增。如果你只是想稳定使用默认的stable就够了。# 安装时指定通道 sudo snap install firefox --channelbeta # 已安装的切换通道 sudo snap switch firefox --channelstable sudo snap refreshedge通道几乎是跟着上游代码天天变适合尝鲜不适合当工作环境。我的建议是开发机、工作机上只用stable测试某个新功能时可以在虚拟机里用beta真没必要长期待在edge上。切换通道本质上是一次版本变更所以snap switch之后一般再执行一次snap refresh让它真正生效。2.3 回滚和保留策略snap最强的功能之一就是版本回滚。升级之后发现有问题一条命令就能回到上一个版本sudo snap revert firefox把应用恢复到升级前的版本不需要卸载重装也不需要去下载旧包。底层原理就是snap保留了旧版本的快照文件revert时只是把current软链接指回去。这既是便利也是磁盘占用高的原因——默认情况下系统会保留多个历史版本以防你需要回滚。# 查看本机所有snap的所有历史版本 snap list --all # 查看某个snap的历史版本 snap list --all firefox如果确认某个旧版本再也不需要了可以手动清理只保留当前版本sudo snap remove firefox --revision1234注意这里的--revision要填旧的修订号千万别删成当前正在使用的版本。还有一点值得提醒不要为了省空间把所有旧版本全删光那样等于把snap的回滚保险丝剪了。系统默认保留几个版本是有道理的真到升级翻车的那天你就知道多留一版的含金量了。3. 三个绕不开的槽点磁盘占用、自动更新、启动变慢3.1 磁盘占用到底怎么清snap被人诟病最多的就是“占空间”。很多应用每个版本带全套依赖体积几百MB很正常。Firefox之类的大软件保留两三个历史版本攒几个G太容易了。先看占用到底在哪# 查看snapd相关总占用 sudo du -sh /var/lib/snapd如果确实发现占用很大第一步是看历史版本列表里堆了多少旧快照snap list --all把不再需要的旧版本删掉是立竿见影的清理方式。第二步是调整保留版本数可以在系统配置里直接改sudo snap set system refresh.retention2这个值表示保留几个旧版本供回滚最小可以设成2。太激进的结果是回滚余地变小升级出问题只能回退一个版本所以我个人不推荐设成1或关闭。清理完之后可以用du再确认一次效果。3.2 自动更新到底能不能管snap默认自动更新的机制对习惯“手动滚系统”的老Linux用户来说非常不友好。你可能正在演示PPTFirefox突然要重启才能更新或者说一个跑了好久的自动化脚本因为某个依赖被snap端更新导致行为变了。理解snap自动更新关键要明白这是设计目标而不是bug——它能保证安全补丁及时到位也避免用户拖着不更新导致一堆老版本在网络上裸奔。问题在于它默认的更新窗口太随意谁都能撞上。好在窗口是可以设置的。比如我习惯把刷新时间放到周五晚上sudo snap set system refresh.timerfri5,20:00~23:00查看当前刷新配置snap get system refresh.timer如果只想hold住某一个应用不让它自动更新可以用sudo snap refresh --hold firefox取消holdsudo snap refresh --unhold firefox注意--hold功能需要较新的snapd版本装老版本系统的先升级snapd再说。生产环境我一般采取折中方案应用保持自动更新但把刷新窗口切到凌晨低峰期特别核心的服务单独hold住等维护窗口手动更新。完全不更新和干等它随机更新都不是好选择。3.3 启动慢到底是不是错觉很多人说“snap应用启动就是比deb慢”这个结论部分正确。需要在不同阶段分开看首次启动安装后第一次运行确实要等因为snapd要先挂载SquashFS镜像、建立用户数据目录、生成各种缓存。慢几秒甚至更多都正常。后续启动由于挂载和缓存已经有了速度会快很多。但在机械硬盘上跑大型桌面应用依然可能比deb版本慢。原因是每次启动都要经过snapd解析、挂载、路径映射这一套链路。空闲占用snapd平时空载时CPU占用很低不会像Windows Defender那样一直在后台扫盘。体感差异最明显的场景是低配机器上跑LibreOffice这类重量级应用。如果你确实非常在意启动速度优先选择deb包或官方二进制版本比如用apt装LibreOffice而不是snap。反过来如果机器性能不差、日常用的是VS Code这类工具snap和deb的启动差异很难感知到。装系统时没必要为了“snap会不会拖慢电脑”这种传言而特意铲除snapd用一个命令行工具实测一下就知道差距是否值得折腾。4. 从零打包一个自己的snap应用用snapcraft走一遍完整流程4.1 准备环境和工程结构打包snap的工具是snapcraft。安装它本身也是个snapsudo snap install snapcraft --classic如果你是WSL或虚拟机用户先确认snapd服务正常工作systemctl status snapdWSL2默认不一定开了systemd需要在/etc/wsl.conf里加上[boot] systemdtrue然后退出WSL在Windows侧执行wsl --shutdown再重新进。虚拟机环境则直接确认snapd服务是running状态就行。打包的最小工程结构只需要两样东西一个snapcraft.yaml和你要打包的文件。目录结构长这样hello-snap/ ├── bin/ │ └── hello └── snap/ └── snapcraft.yaml4.2 写一个极简的snapcraft.yaml并构建安装准备一个最简单的脚本bin/hello#!/bin/bash echo Hello from snap! 当前时间: $(date)记得赋执行权限chmod x bin/hello再写snap/snapcraft.yamlname: hello-snap base: core22 version: 0.1 summary: 一个极简的snap示例 description: | 这个snap用来演示snapcraft的打包流程。 它只会执行一行简单的shell命令。 grade: devel confinement: strict apps: hello-snap: command: bin/hello plugs: - home parts: hello-part: plugin: dump source: .各字段含义字段作用说明namesnap名称只能小写字母、数字、连字符base基础运行时core22对应Ubuntu 22.04core24对应24.04version应用版本建议不要超过32字符grade发布等级devel用于开发stable用于正式发布confinement沙箱模式strict为严格沙箱classic为无沙箱apps可执行命令入口定义安装后可运行的命令名parts构建模块声明打包哪些文件和如何构建plugin: dump意思是直接把source目录里的文件原样拷进snap适合打包脚本、静态文件这类无需编译的内容。如果想编译源码还有autotools、cmake、python等插件可选。在工程根目录执行cd hello-snap snapcraft首次构建会下载base等基础文件耗时偏长属于正常现象。构建成功后目录下会生成hello-snap_0.1_amd64.snap这样的文件。在正式发布前用本地文件直接安装测试即可sudo snap install --devmode --dangerous ./hello-snap_0.1_amd64.snap hello-snap--dangerous是允许安装未签名的snap文件--devmode表示暂时关闭沙箱限制方便调试本地开发版。看到“Hello from snap”输出说明这个最简snap已经跑通了。4.3 打包过程中的常见报错与排查我第一次用snapcraft的时候踩过的坑基本都集中在以下几类第一个坑是版本号不合规。snap要求版本号只能包含字母、数字、-、.和~如果你中间塞了个空格或者_构建时直接报错。处理方式是把版本号改成合规格式比如0.1.0-beta.1。第二个坑是忘记给脚本加执行权限。dump插件把文件拷进snap时如果原文件没有可执行位运行命令时会报权限错误。解决方法是chmod x后再构建。第三个坑是strict沙箱下应用拿不到权限。在confinement: strict模式下即使脚本里写了读取家目录文件应用也可能被拦截。需要像上面的yaml里那样在apps段通过plugs声明需要的接口比如home表示可以访问用户主目录。权限问题五花八门本机调试时先开--devmode能帮你快速确认是权限问题还是程序本身问题。定位故障时先看应用本身的报错输出再看snapd系统日志journalctl -u snapd --since 10 minutes ago这两个方向能覆盖九成以上的问题。真正需要发布到Snap Store时snapcraft会跑一堆lint检查常见的提示包括缺少icon、summary太短、description太随意等。这些检查虽然不影响本地构建但会在发布审核时被卡住早点按提示修好。4.4 发布到Snap Store的前置准备本地能跑通之后想发布到公开商店需要注册账号并登录snapcraft login snapcraft register hello-snap snapcraft upload hello-snap_0.1_amd64.snapSnap Store里的名字是全局唯一的好的短名字基本都被占了这和大厂“抢注册用户名”一个道理。发布后会经过自动审查和人工审核grade: stable的版本要求更严格包括提供真实的应用图标、完整的说明文档、合理的接口权限声明。学习阶段不急着发布本地构建、--devmode安装测试这套流程已经足够你玩明白snap的打包机制了。5. 权限边界与控制snap的安全模型和外设访问问题5.1 strict沙箱的权限模型snap体积大归大但它默认的安全策略比deb要严格得多。deb软件安装后拥有普通用户权限应用可以访问你能访问的一切而strict模式下的snap应用默认是“关在笼子里”的不能随便访问你的家目录、网络、USB设备除非它显式申请了对应接口。这个机制的核心概念是plugs和slots。一个应用使用某个能力叫plug系统或者其他snap提供这个能力叫slot。比如要访问网络应用声明networkplug系统snapd提供对应的slot。大部分通用接口网络、home等安装时会被默认自动连接但像串口、移动存储这类敏感接口默认往往不连接需要手动授权。# 查看某个snap已连接和未连接的接口 snap connections snap名如果看到某些plug显示为-未连接可以手动接上sudo snap connect snap名:接口名这种设计虽然让新用户觉得麻烦但换来的好处是边界清晰一个不如名的小工具装了之后也没法随便翻你硬盘里的文件更没法偷摸上传数据。仔细想想这个代价是值得的。5.2 开发中几个常用的接口实际开发里用的最多的几个接口有这些home访问主目录大多数应用默认自动连接。network/network-bind网络访问和监听端口也是默认自动连接。removable-media访问/media和/mnt下的U盘、移动硬盘。serial-port访问/dev/ttyUSB0、/dev/ttyACM0这类串口设备在ESP32、Arduino、ROS2开发中特别常用。usb访问USB设备。给之前那个示例应用授权移动存储访问sudo snap connect hello-snap:removable-media给串口设备授权sudo snap connect hello-snap:serial-port :serial-port命令后面那一串:serial-port是让系统自动匹配默认的slot提供者。不同的接口可能对应不同的slot不确定时先用snap connections 名字看看接口状态再连。5.3 开发板连不上时的完整排查过程很多人在Ubuntu上用VS Codesnap版本写PlatformIO项目时会遇到“开发板插上但识别不到串口”的灵异问题。最典型的例子是ESP32开发板驱动装了、lsusb也能看到但开发工具就是连不上。排查步骤第一步确认系统能不能看到设备。lsusb ls /dev/ttyUSB* /dev/ttyACM*如果这里都看不到设备先解决驱动和虚拟机USB透传问题跟snap还没关系。第二步确认snap应用是否有对应权限。snap connections code重点看serial-port和usb这两行的连接状态。很多安全意识的snap版本默认没把串口接口连上所以应用里永远看不到端口。第三步手动授权。sudo snap connect code:serial-port :serial-port再回去刷新Device列表一般就能识别出来了。第四步如果还不行去看snap应用自身日志journalctl -u snap.code --since 5 minutes ago这套流程排查下来八成情况都出在第二步和第三步之间——不是驱动问题而是snap的权限隔离挡了一下。我自己实际用下来最需要记住的反而是最小权限原则只给应用申请它真正需要的接口不要为了图省事把所有接口全连上。权限开得越多沙箱的价值就越小最后跟裸奔的deb也差不多。再往后如果你遇到的是WSL里的Ubuntu外部USB设备需要先通过Windows的USB透传功能分配给WSL再在WSL里继续上面的排查链路否则后面做再多都是白搭。