Alpine Linux 使用 VSCode 远程开发

最近,我给我自己找到了许多理由在虚拟机内跑开发环境。

找个理由

第一个理由就是安全。

可能有人(或者说,有许多人)经常说 Linux 安全,也不需要杀毒软件。这话一半是对的,一半是错的。如果用户从只发行版仓库里面安装软件,保持良好的上网习惯,经常更新系统,打理好防火墙,那许多流行的发行版都是安全的。但可惜现实世界不是这样运行的。

不少程序依赖于在运行时从网络上面下载可执行文件,比如 VSCode 编译
C/C++ 需要依赖微软发布的不开源二进制,Python 开发经常需要在 PIP 上面下载依赖,这种“按需下载”的逻辑本身没有什么问题,但是发行版没法对这些下载的来源进行把关,用户也不一定能做到检查这些复杂依赖。

有些时候,在编程的时候打错一个单词,就可以让项目引入恶意依赖。然后因为这些程序是以用户自己的权限运行的,这可以让攻击者获得几乎完全的控制。虽然攻击者不能直接拿到 root 密码,恶意程序对于用户的浏览器记录等隐私有着完全的访问权(Flatpak 或者 Snap 也没用,毕竟开发环境多半不在沙箱里面),这可不叫安全。

第二个理由是隔离,保持环境的干净整洁。

很简单,我就是单纯的不想要那些东西在我的家目录里面下崽,然后隔离也使得开发环境可以方便的迁移,只要主机支持 KVM 就行。

挑个发行版

虚拟机内的发行版我选用 Alpine Linux。Alpine Linux 比起其他发行版有着许多优势:

一方面,Alpine Linux 作为流行的 OCI 镜像打包基底,其可靠性和安全性应该不成问题。

一些发行版,比如 Slitaz 和 TinyCore 虽然和 Alpine 有着相进的大小量级,其自身的社区活跃程度、维护与更新的频率,以及应用的广度都比不上 Alpine,我对他们的安全性抱有疑虑。

另一方面,Alpine Linux 自身非常轻量。

我以前也是尝试过使用 Fedora 和 openSUSE 作为开发用的虚拟机系统,但是这两个都是大部头级别的发行版,就算不安装桌面环境,开机内存占用也在 500 MiB 左右。虽然也不是不能手动精简,但是我的原则是“用一个操作系统,就要理解和接受他的哲学与定位。如果不理解或者不接受,那就别用”。如果要我自己当刀客去切出来一个精简版发行版,那还不如跑容器。

Alpine 的轻量确实带来的非常良好的用户体验。开机很快,在用 virsh 启动之后需要等待的时间也就 2-3 秒,就可以 SSH 上去了。内存占用也比较低:

诚实的说,Fedora Cloud 镜像或者 openSUSE JeOS 镜像开机之后的内存占用也不比这多太多,但是就这些发行版的打包风格来讲(比如 Fedora 随便装点啥就带入一大堆 Perl 包),真的不如 Alpine 精简。

开始踩坑

新建虚拟机的过程不在此赘述,我用的是 KVM 配合 Virt Manager。

安装 Alpine

Alpine Linux 的安装非常人性化,使用 ISO 启动之后,运行 setup-alpine 就可以了,会有一个向导来指导安装。

Alpine 有几种不同的安装模式,在虚拟机里面用 Alpine 的话,直接用 System Disk Mode 也就是 SYS 模式,这个是传统的安装模式,其他几个模式有点类似于无盘工作站,做开发的话自然是不太用得着。我建议在问到使用哪个 SSH 服务器的时候选择 dropbear。安装完毕后卸载 ISO 重启即可。

软件安装

进入系统之后不急着安装软件,先启用社区软件源:

vi /etc/apk/repositories

里面会有这样的内容

/etc/apk/repositories
------
#/media/cdrom/apks
http://mirror.lzu.edu.cn/alpine/v3.17/main
#http://mirror.lzu.edu.cn/alpine/v3.17/community  #<-- 去掉注释
#http://mirror.lzu.edu.cn/alpine/edge/main
#http://mirror.lzu.edu.cn/alpine/edge/community
#http://mirror.lzu.edu.cn/alpine/edge/testing

把当前版本的 community 源的注释去掉即可。

之后安装一下软件包:

apk update
apk add gcompat libstdc++ curl git openssh-sftp-server libgcc build-base doas

这些基本上都是开发必须的,具体解释在下文。

Libc 问题

Alpine Linux 最大的的问题在于,他们的 libc 是采用的 muslc。如果运行的全部都来自发行版自身的软件源,那用的什么 libc 通常不是什么大问题。但我想要的是远程开发,跑个 VS Code Remote 也好,还是说用 Code OSS 那一套网页(泛指各种网页版 VSCode 系 IDE ,比如 Eclipse Theia)也好,总不能指望所有提供的二进制都是 muslc 的。

还好,有高人给出了解决方案——兼容层 gcompat

SSH 服务器

Alpine Linux 有两种 SSH 服务器可选,常规且标准的 openssh 和嵌入式常用的轻量级实现 dropbear,我在这里选的是 dropbear。不知道为什么,openssh 端口转发不工作,即使在配置里面允许转发也一样,只能选 dropbear 。但是 dropbear 又没有内置的 sftp 支持,所以需要额外装一个 openssh-sftp-server

其他杂七杂八

剩下的 curlgit 是 VSCode Server 的依赖,也是开发常用包,而 build-base 也基本上是开发软件的必备品,后者会安装基本的编译器等小玩意,但是不含调试器,需要 gdb 的话自己安装。doas 就是简单版的 sudo

文件共享以及 Virtiofs 的“小问题”

KVM 方案下面共享文件最方便的解决方案当然是 Virtiofs,也不需要在客户机里面安装驱动,直接写 /etc/fstab 就可以实现自动挂载。

用 Virt Manager 在主机做的配置如图所示。源路径就是需要共享的目录,里面的目标路径其实是一个标签,名字随便取,我这里取的是 alpinedev ,在挂载的时候当作设备名称。然后,客户机的 fstab 添加如下一行:

/etc/fstab
-------
alpinedev       /home/tux       virtiofs        defaults        0 0

里面的 /home/tux 就是在客户机内要挂载的路径。我这里是把客户机的用户的家目录挂载上去了。

小问题

virtiofs 会在主机上运行一个守护进程,叫 virtiofsd。一般发行版都是用的 libvirt 的默认实现,而这个默认实现有点 Buggy。

小量使用没问题,但是如果遇到编译 Rust 这种对 IO 需求不低的,就各种表现奇怪。比如明明一个文件存在,ls 能看到,但是 cat 的时候却提升找不到文件等。那需要换到 Rust 实现的版本。这个也是因发行版而异,发行版不提供就自己下载二进制也是可以的,然后 Virt Manager 的编辑 XML 功能可以指定二进制的路径,如图所示:

<filesystem type="mount" accessmode="passthrough">
  <driver type="virtiofs" queue="1024"/>
  <binary path="/home/tux/bin/virtiofsd-warpper.sh" xattr="on"/>
  <source dir="/home/tux/DevEnv/AlpineDev/"/>
  <target dir="alpinedev"/>
  <alias name="fs0"/>
  <address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x0"/>
</filesystem>

如果需要给 virtiofsd 加参数的,也可以像我这样加一个 warpper。

/home/tux/bin/virtiofsd-warpper.sh
------
#!/bin/bash
/usr/libexec/virtiofsd --announce-submounts --inode-file-handles=prefer "$@"

我在这里添加了 --announce-submounts--inode-file-handles=mandatory 。前者会向客户机为每个挂载点报告单独的设备号,避免相同的 inode 号产生冲突,后者会尽量避免 silly name 带来的影响。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注