Alpine 作为OS可说算是非常轻量了。作为在Proxmox里面运行Docker的VM,轻量就是正义。本文记录一下使用 Alpine 在Proxmox里面做VM的过程。用迁移Vikunja到这个新VM里面看看效果。同时这篇文章也相对详细的记录一下 Proxmox 上面创建一个基本可用的 docker container host VM 的过程,以资后用。
Alpine简介
Alpine最大特点就是轻量。相比于Ubuntu server,Alpine占用更小的硬盘空间(以Alpine sys mode为例),有更快的启动速度,内存占用也更小。同时因为精简的Kernel,牺牲了一些compatibility的同时,大大降低了Kernel的体积从而让整个系统精简,快速,安全(安全是因为更少的 attack surface)。
Alpine使用 apk
命令管理 package,使用下来感觉也还不错。官方有很多 wiki document,本文中的很多步骤都是照着官方Wiki做的。
Create VM
鉴于我们是在 Proxmox 里面作为 Virtual Machine 跑,所以我们选择 VIRTUAL 类别的 image。我估计这个版本的 kernel 里面应该已经针对 VM 的场景做了一些 “优化”,比如去掉很多大概用不上的 Drivers,然后应该要保证一些 virtio 性质的 Driver 都已经装好了。
创建 VM 的时候建议用 Q35 machine type。对于 Vikunja 应该是没有任何区别的,但是 i440确实太老了,目前来看应该没有什么理由用 i440 了。而且这里还有个文章说 Q35 在 Idle 时候使用更少的 CPU。对于 Self Host 来讲,Idle 的时间是很长的,所以能省一点总还是好一点的。
Disks方面,因为Proxmox上面的VM Pool用的是ZFS,硬盘是NVME,所以自然要 Enable Discard
并且 Enable SSD Emulation
. 虽然其实也不是特别重要就是了。磁盘空间给个8GB大概就够了,之后如果有需要再添加新空间。
Install Alpine
开机之后,Alpine会从ISO image启动,给个 Terminal 用,安装都在Terminal里面搞定。起始用户是 root,没有密码。
用 root 进入以后,命令行上面会直接提示可以用 setup-alpine
来配置和安装系统。我们就用这个命令就好了。然后我们需要回答一系列的问题,从键盘 keymap 开始,包括hostname (注意 Alpine这里要设置的是FQDN,不光是short hostname), 磁盘的parititioning,network setup,还有 SSH 的设置,等等等等。确实算是涵盖了一个 VM OS 初始配置的各个方面了。
IMPORTANT
❗ 但是这里设置的 hostname 在 DHCP server 那边看到的是,整个FQDN都被用来做 hostname 了,比如如果这里安装的时候设的 hostname 是 alpine-server.foo.box。那么在 DHCP server 那边看到的hostname就是 alpine-server.foo.box,而不是 alpine-server。所以这里安装的时候的 hostname 设置实际上跟原来在 Ubuntu 上面是一样的。所以很可能是我理解错了。hostname 就是 hostname,而不是 FQDN。
注意在Disk & Install的步骤,会问 mode 是 sys, data, 还是 diskless。这里我们还是选择更传统的 sys
。但是其实 Alpine 在 data mode 和 diskless 模式下才是最能体现其轻量快速的特点。这些模式的区别详见官方文档。而 sys
模式就是常规的把东西都存在硬盘上的模式。而其他的模式就更加贴近于 immutable system的用法了,对于 enterprise 或者比较严肃的使用场景更加适用。
Reboot 之后我们就可以用root或者我们之前配置的user登入了。Alpine的reboot还是用 reboot now
命令。而关机命令跟 Ubuntu 不同,不是 shutdown now
而是 poweroff
。
Config Alpine
Xterm.js
对于 Proxmox 里面的 VM,如果只是用 VNC 的 console的话,是没有办法 copy-paste的。这在日常使用中还是有点麻烦的。虽然可以从 Proxmox host上面走 ssh-copy-id 然后 1-click ssh 进去。但是毕竟还是多了一些步骤。而且因为 VM 很多时候各种删除改名等等操作,导致 Proxmox host 里面的 known hosts 乱七八糟,时不时就因为 hostname<>IP<>key mismatch需要手动删除 record,就很麻烦。所以如果有一个不需要 key record 的 console 可以支持 copy-paste,就会好用很多。而 xtermjs
就可以满足这个需求。 xtermjs
是原生被Proxmox支持的。我们只需要配置好 VM 就可以直接用。
- 给VM添加 virtual serial port的设备。就用默认的0 port。添加之后
poweroff
,然后console: xterm.js
的选项就可以选了。但是点开之后并没有用,打开的 terminal 并没办法连上 VM。
- 在 VM 里面打开 serial port。这个配置在
/etc/inittab
文件中,相应的配置已经写好了,只是需要 uncomment 就可以了。这个ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100
这行就是需要 uncomment 的配置。
- reboot 之后就可以通过 xterm.js 进入控制台了。之后的步骤就用这个console来搞定。
Qemu-guest-agent
这个 Agent 对基于 QEMU 的 VM 是必须的。
- 第一步是打开 community repo。文件是
/etc/apk/repositories
里面默认的 community repo 是被注释掉的。这里只需要 uncomment 就可以。然后apk update
就可以了。 apk add qemu-guest-agent
rc-update add qemu-guest-agent
service qemu-guest-agent start
- 之后确认在 Proxmox 上面打开这个VM的 qemu-guest-agent 选项。最后重启(poweroff)就好。
QEMU Guest Agent 搞定之后,在 VM 的 summary page 上面是会现实 MAC和IP的。
Docker
Docker 也是需要 community repo的。但是之前已经 enable 了,此处略过。
apk add docker
,rc-update add docker default
,service docker start
.apk add docker-cli-compose
这个跟apk add docker-compose
效果是一样的。addgroup <your_username> docker
官方文档上面还有关于 docker remap 以及 rootless docker 的配置。但是如果用不上的话,这样就足够了。
Net Mount
如果需要 fstab 里面添加网络挂载的话,不要想其他的,先加上这个 rc-update add netmount boot
. 否则的话,fstab 里面的 _netdev
option 估计无法正常工作。
NOTE
💡 这里这个 netmount 如果没有安装的话,需要用 apk add netmount 安装。
NFS
NFS 默认也是没有安装的,所以如果要用NFS mount的话,还是需要自己搞一下的。如果要做NFS server,这一步就更省不了了。
IMPORTANT
Without installing anything, if you try:
mount -t nfs <ip>:<export> <local_dir>
: I got an error ofconnection refused
. 这个 error message 有点费解。
apk add nfs-utils
,rc-update add nfs
如果只是需要 mount NFS 的话rc-update add nfsmount
应该就够了。- Ad-hoc mount
mount -t nfs nfs-server-host:nfs-export-full-path /mnt
- fstab mount:
nfs-server-host:nfs-export-full-path /mnt nfs rw,rsize=1048576,wsize=1048576,hard,_netdev 0 0
Samba (CIFS)
- Ad-hoc mount
mount -t cifs -o username=xxx,password=xxx //cifs.server.host/share-name /mnt
- fstab mount:
//cifs-server-host/share-name /mnt cifs username=$USER,password=$PASS,vers=3.0,soft,cache=strict,uid=0,gid=0,file_mode=0755,dir_mode=0755,nounix,serverino,mapposix,echo_interval=60,actimeo=1,_netdev 0 0
Disk Expanding
VM时常遇到的一个问题就是需要更大的磁盘容量。虽然按理说,最好的情况是 compute 和 storage 分开,但是对于个人用户来讲,大量的小文件很多时候都是没有结构化的,而且 docker 上面的应用很多时候也不是用户可以改变的,再加上个人用户对于 high availability 没有那么过分的要求。所以很多时候,就用VM打包compute+storage为一个整体反而成本低可维护度高。
- 首先肯定是在 Proxmox 里面改变 virtual disk 的大小
apk add --no-cache cfdisk e2fsprogs-extra
- Use cfdisk to resize the partition:
cfdisk
, choose the parition (should be sda3 in Alpine), then useresize
, thenwrite
withyes
, then quite. - Use
resize2fs
to resize the FS.resize2fs /dev/sda3
.
至此,这个 Alpine VM 应该就可以用来 host docker了。
部署 Vikunja 0.23
既然新的0.23版本发布了,干脆就升级一下。
Portainer Agent
Portainer 可以通过一个 UI 管理多个 docker host (Portainer 里面叫 Environment). 个人还是习惯用一个 UI 管理所有的 docker compose,所以 Portainer Agent 必须配置上。
这里我们就选择最基础的 Standalone 模式:
把这个命令在新的 VM 里面运行,让 portainer agent 跑起来(并且自动重启)。之后把VM的IP和端口 9001 填进去,然后 connect 就可以了。这里我们就用上 xterm.js 的 copy-paste 功能了。
折腾 Portainer 就是为了下面这个 UI。虽然 CLI 也可以用,但是有个 UI 还是方便一些。当然也有坏处。Portainer 上面的 stack 用到的 docker-compose file 的位置时不太好找的,而且如果 volumn binding 用 relative path,我目前也是不知道去哪里找这个目录位置的。
Deploy Vikunja 0.23 with Docker Compose
因为使用量不大,所以目前就先不考虑 typesense 了。但是之后有机会一定会加上。之前使用 Vikunja 的时候,没有 global fuzzing search 确实挺影响使用体验的。
使用量不大,自然用 sqlite 就妥妥够了。
基于官方文档,yaml内容如下。添加了一些用得上的 Environment variable。
vikunja:
image: vikunja/vikunja:0.23
environment:
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
VIKUNJA_SERVICE_PUBLICURL: http://<your public frontend url with slash>/
# Note the default path is /app/vikunja/vikunja.db.
# This config variable moves it to a different folder so you can use a volume and
# store the database file outside the container so state is persisted even if the container is destroyed.
VIKUNJA_DATABASE_PATH: /db/vikunja.db
VIKUNJA_SERVICE_TIMEZONE:
TZ:
VIKUNJA_SERVICE_ENABLEREGISTRATION: false
ports:
- 3456:3456
volumes:
- /<abs_path>/files:/app/vikunja/files
- /<abs_path>/db:/db
restart: unless-stopped
我还是习惯指定一个 version 的,这里用了 0.23。
Container 跑起来之后似乎有问题,如下。
info: creating the new user vikunja with 1000:1000
usermod: no changes
2024-04-16T17:17:51.736195642-07:00: INFO ▶ config/InitConfig 001 No config file found, using default or config from environment variables.
2024-04-16T17:17:51.736411715-07:00: CRITICAL ▶ migration/initMigration 002 Could not connect to db: could not open database file [uid=1000, gid=1000]: open /db/vikunja.db: permission denied
这是因为 Vikunja 不会试图去创建文件夹,所以我们这里需要预先需要提供这两个文件夹,并且这两个文件夹需要属于 uid:1000。
mkdir <abs_path>/files <abs_path/db
chown 1000 <abs_path>/files <abs_path>/db
Migrate from the old Vikunja instance
Vikunja 提供了 Export data 的功能。这个功能在 Settings 里面。
然而这个功能需要在之前将 Email Sender setup 好。很显然之前懒了没有做这一步。所以我们只能用 rsync 的方式把原先的数据放在 NFS 上面,然后 rsync 到我们的 新 instance 这边。
# rsync is not installed by default.
apk add rsync
# Assume you've moved your Vikunja data to this NFS server.
mount -f nfs <nfs_server_ip>:/mnt/app_data /mnt
# rsync the data from the server to this path in the new VM.
rsync -ahP /mnt/vikunja/* <abs_path>/vikunja/
至于从原来的 VM 把数据 Rsync 到 NFS,命令基本上是一样的。其实主要就是 options flag: -ahP
.
- -a: archive: Keep permisions, owners, modes, unchanged.
- -h: human readable
- -P or —progress: show progress
如果数据量大的话,加上 -z
会更好,会使用 compression。但是如果是本地之间的 copy,用 -z
就不如不用,白白消耗 CPU cycles。
之后就是配置 Reverse Proxy 了。现在的 Vikunja 0.23 抛弃了之前的结构,front-end 这个部分以及单独的 Nginx reverse proxy 都从 docker compose 里面被去掉了。整个服务只需要一个 vikunja container 就够了。所以在 Nginx Proxy Manager 这边只需要指向新的 Vikunja 的 3456 port 就可以了。
About 页面显示是0.23。 Migrate + Upgrade 完成。
升级体验
之前我的 Vikunja 数据实际上是host在 2 HDD 组成的 ZFS mirror 上面的,然后通过 NFS mount 给到 VM 里面使用,通过 volume binding 给到 Docker container。现在这个VM是跑在 2 NVME 组成的 ZFS mirror 上面,然后 volume binding 给到 docker container。即便这个 VM 只给了 1 CPU core,但是直观体验速度还是快了很多很多。这也是意料之中的,毕竟 NVME 对于小 block 的 sync 操作,快的不是一星半点。这点在我另一个不严谨的测试中也有所展示。
[a-record-of-a-zfs-small-file-write-performance-test]
Alpine 的使用体验不错,速度快,功能简单,轻量。即使是这种玩票性质的用法,也可以体会到相对于 Ubuntu Server 在速度方面的提升。长期的体验还有待后续观察。但就目前来讲,还是非常满意的。