尝试Docker + LXC + ZFS + Proxmox
起因仅仅是因为想要Host新的服务,就是前文提到的 Immich,然后对现在的 Docker + VM + ZFS + Proxmox的方案有点点不满意,觉得每次折腾一个服务的时候,还是很容易把其他的服务一并搞挂了,尤其是Docker的Network有个有点蛋疼的问题,所以想着干脆一不做二不休,把每个服务都单独拿出来,装在一个LXC里面。而Docker其实仅仅是因为觉得Deploy起来方便,再加上Portainer CE版本管理起来,有个UI也确实顺手一些。于是就开启了这次的折腾之旅。
先说结论
不推荐这样做。
最主要的原因:Docker的storage driver在基于ZFS的LXC上面,基本上只能用VFS。这个driver速度慢,空间占用高,对家用SSD对ZFS尤其不友好。Immich的Stack一度因为磁盘速度太慢,根本启动不起来。后来还是在LXC的subvol上面关了zfs的sync(i.e standard -> disable)才能跑起来。而一个Nginx Proxy Manager,跑起来需要大概20GB的空间。如此巨大的空间消耗,和如此慢的磁盘性能,居高不下的IO Delay,已经宣判了在ZFS系统上面跑LXC,再跑Docker,基本上就是不合理的了。
而且原本采用LXC应该也有一部分原因是为了更好的性能,可是如此糟糕的磁盘表现,基本上就否定了这个方案。而且VFS实际上也是宣称只应该用在Test的情况下,生产环境应该是避免的。
简单来说,要Docker,就用VM。用LXC,就别用Docker。(最好也别用NFS,CIFS,FUSE之类的,而且最好不要privileged。这说实话我就觉得LXC不会太好用了...)。
LXC上面遇到的其他问题
首先,LXC上面自然是需要打开Nesting的,否则没法使用Docker。
有人可能为了省事,会直接将LXC设置为priviledged的。但是这样可能Docker run还是会error,大概率跟 AppArmor有关。此时一个方法是卸载 AppArmor。注意,安装docker本身实际上会安装AppArmor。所以在安装docker之前哪怕卸载了,在安装docker之后,还需要再次卸载。安装docker-compose也是一样的。
另外,如果需要NFS/CIFS的话,除了需要打开相应的选项以外,可能还需要privileged。印象中以前是这样的,现在不是很确定了。
最后,在LXC上面如果一定要避免使用VFS的storage driver的话,很可能会搜索到fuse-overlay这个方案。这也是一个storage driver,似乎性能会比VFS好很多,而且也是支持任何底层文件系统的。为什么不用?
- 之前的Container启动会失败。MariaDB启动挂了,另外Nginx Proxy Manager启动也挂了。
- 更重大的问题:使用FUSE-Overlay应该是需要LXC打开FUSE选项的。但是Proxmox的Snapshot/Backup可能跟FUSE有冲突,会导致Snapshot的时候卡住。这相当于基本没有Proxmox原生的备份了。
为什么不能用Storage Driver: ZFS?
如果不是LXC,大概是可以用这个storage driver的,而且在ZFS上面应该也是推荐这么干的。但是LXC上面就不行了。哪怕你知道你的LXC里面的文件系统其实就是ZFS,但是Docker不知道啊。而且LXC肯定也是不会让跑在里面的Docker,去碰Host上面文件系统的事情的,而Docker ZFS storage driver恰恰就是要去触碰一下snapshot之类的东西的,所以这两者之间是不可调和的。可预见的将来应该也是这样了。
VM上面遇到了什么问题?
首先肯定是一个VM上面跑多个Docker Compose stack,导致折腾的时候容易一起挂。尤其是涉及Infrastructure的服务(比如Reverse Proxy,Git,Portainer)挂了的话,收拾起来就很麻烦。
其次,之前遇到一个奇怪的问题。当Docker Compose起来的Stack数量有点点多的时候,整个Network会挂掉。之前一直没有研究明白是怎么回事,现在知道了。
- Docker默认网桥模式,其中可用的网段,第一阶段是 172.17.x.x -> 172.31.x.x。每个网段都是很大,但是只有15个(大概?)。注意很多时候默认的bridge就已经占用了。
- 每个Docker stack都会有一个default network,所以如果启动了十几个Stack,那么很可能第一阶段172地址已经用光了。
- 第一阶段的用完之后,就开始用192.168.x.x了,这个是第二顺位的。一但开始用这个网段,我这边的VM的网就挂了。这应该是跟VM的上级路由(一个OPNSense VM)的WAN网段冲突了。这就尴尬了。
解决方法应该也是很简单的,只需要把第一阶段的网段搞多一些就可以了。通常是改/添加 /etc/docker/daemon.json 里面的 "default-address-pools"字段。
不过其实即使解决了,用一个VM来装所有的Docker可能也不是一个非常好的选择。分散开来,哪怕没有上k8s,但是分个Tier,按照不同的Tier放在不同的VM里面,每次想折腾新东西的时候,都从最不重要的Tier开始,尽量避免在Infra相关的service上面搞事情。这样应该也能避免搞崩的概率。
后记
最后谈一下LXC和Docker。这两者实际上应该是对标的关系,而不是Docker跑在LXC上面。所以使用LXC的话,最好就当作一个bare metal的service平台好了,里面不要再搞什么跟cgroup相关的东西了。可惜LXC虽然在Proxmox上面很香,但是相比Docker的生态,那几个可怜的Image就实在不够看了。你可以说LXC上面你自己deploy需要的service,什么都可以搞定。但是Deployment有时要的就是Immutable。除了persistent的数据意外,代码本身,以及一些临时文件,其lifecycle都是ephemeral的。这样才有可迁移性可言。再加上现在k8s这么火,LXC的未来我确实不太看好了。
其实说归结底,都是stateful惹的祸。如果所有Storage都是经过API,然后Authentication的问题都老老实实的ACL+IAM解决,Storage层分离出去给Object storage OR database cluster,用什么容器,都是可以stateless,随便迁移的。只是这基本上就是Cloud Native了。家用的话,不可能指望用到的这些Self host服务都做到这个程度,等于几乎直接抛弃local file system。企业里面自然是这样搞的,家用 的话,还是VM+NFS+Docker Bind Mount来凑合用吧。如果家里条件好的话,搞搞Ceph或者ISCSI,也是可行的,不过从某种意义上来讲,这跟NFS差别都不算大😓。也许只有专门搞distributed storage相关实验的同学,才有这方面的需求吧。我就在家host个Samba,大可不必如此大费周章。