前言

​ Dockge-AC是我基于Dockge这个开源项目fork出来的。Dockge本身是一个很优秀的Docker Compose栈管理工具,有漂亮的Web UI、实时日志、在线编辑compose文件等功能,但它只支持Docker。Apple推出自己的容器运行时Apple Container之后,我觉得这套UI和交互体验完全可以复用,于是就fork了一份,针对Apple Container做了一系列适配和改造。这篇文章主要聊聊fork之后做了哪些事情,以及Apple Container跟Docker在使用层面的差异给开发带来了哪些挑战。


正文

一、为什么要fork Dockge

​ Apple Container是Apple基于Virtualization.framework做的容器运行时,跟Docker走的是完全不同的技术路线。Docker在macOS上需要跑一个Linux虚拟机,容器跑在虚拟机里;Apple Container直接利用macOS原生的虚拟化能力,每个容器是一个轻量级虚拟机,启动快、资源占用少。

​ 但Apple Container目前只有命令行工具,没有图形界面。对于习惯了Docker Desktop的开发者来说,纯命令行管理多个容器栈还是不太方便。Dockge的UI和交互设计正好是我想要的——面向栈的管理方式、实时日志、在线编辑器——所以直接fork过来改,比从零开始写省了大量的前端工作。

二、Apple Container与Docker的关键差异

​ fork之后不是简单地把docker命令换成container命令就完事了。Apple Container跟Docker在很多地方都有差异,这些差异直接决定了我需要做哪些适配工作。

Compose指令集不完整

​ Apple Container支持compose.yaml,但支持的指令集比Docker少不少。像deployhealthchecksecretsbuildprivileged这些Docker里常用的配置项,Apple Container都不支持。如果用户写了一个包含这些指令的compose文件,直接丢给container compose up会报错,而且报错信息不太友好。

​ 这是我做Compose Compiler的直接原因——在部署前先”编译”一遍compose文件,把支持的指令正常通过,不支持的明确报错,未知的给警告。用户在编辑的时候就能实时看到哪些配置有问题,不用等到部署失败了才知道。

不支持容器自动重启

​ Docker里restart: always是个很常用的配置,容器挂了会自动拉起来。Apple Container目前不支持这个特性。这意味着如果容器意外退出,需要外部机制来处理重启。在macOS上,我的做法是用launchd来管理Dockge-AC本身的进程,保证管理工具不挂;至于容器层面的重启,目前只能通过State Observer检测到状态变化后通知用户手动处理。

容器命名和状态管理

​ Docker Compose会自动给容器生成带项目名前缀的名字,Apple Container的命名规则不太一样。我定义了一套自己的命名规范:dockgeac_{stackName}_{serviceName}_{index},确保容器名可预测、不冲突。

​ 状态管理也有差异。Docker有完善的事件流(docker events),可以实时监听容器状态变化。Apple Container没有这个机制,所以我只能用轮询的方式——定期执行container list --all --format json,跟上一次的状态做对比,检测变化。虽然不如事件流优雅,但实际用起来延迟在可接受范围内。

CLI输出格式

​ Docker CLI的输出格式比较稳定,社区用了很多年了。Apple Container的CLI输出格式还在演进中,JSON输出的字段名和结构跟Docker不完全一致,有些命令的输出格式甚至在不同版本之间会变。这就要求解析逻辑要足够健壮,能处理多种输出格式。

三、针对AC做的核心改造

Runtime Adapter抽象层

​ 这是改造量最大的部分。原版Dockge的代码里到处都是直接调用Docker CLI的逻辑,耦合很深。我把所有运行时相关的操作抽象成了一个RuntimeAdapter接口,定义了部署、启停、日志、终端、镜像管理等所有操作的统一接口,然后写了一个Apple Container的实现类。

​ 这样做不只是为了代码整洁。通过RuntimeCapabilities能力检测机制,前端可以知道当前运行时支持哪些功能、不支持哪些,然后在界面上做相应的提示或禁用。比如Apple Container不支持restart,前端就会把相关选项灰掉。

Stack Lock(栈锁)

​ Docker Compose有自己的状态管理,知道哪些容器属于哪个栈、当前是什么状态。Apple Container在这方面比较弱,所以我自己实现了一套Stack Lock机制。每次部署栈的时候,生成一个lock文件,记录compose文件的SHA256指纹、每个服务的容器ID、部署时间等信息。下次部署时先比对指纹,没变就不重复部署,实现幂等。

镜像管理的安全检查

​ Docker里删除一个正在使用的镜像会报错,Apple Container的行为不太一样。为了防止用户误删正在使用的镜像导致容器挂掉,我在镜像管理页面加了使用计数检查——如果一个镜像正在被某个容器使用,界面上直接禁止删除操作。

State Observer(状态观察器)

​ 前面提到Apple Container没有事件流机制,所以我实现了一个基于轮询的状态观察器。它用EventEmitter模式,检测到容器创建、删除、状态变更时发出对应事件,前端通过Socket.IO监听这些事件实时更新界面。虽然是轮询,但通过合理的间隔设置和状态diff算法,用户体验上感觉不到延迟。

四、工作重心的取舍

​ 做这个项目的时候,我把主要精力放在了两个方面:

​ 一是运行时适配层的设计。这是整个项目的地基,如果这层做得不好,后面所有功能都会受影响。Runtime Adapter的接口设计花了不少时间反复推敲,既要覆盖容器管理的所有核心操作,又要足够抽象,不能把Docker或Apple Container的特有概念泄漏到接口里。

​ 二是Compose兼容性处理。用户写compose文件的时候,脑子里想的是Docker的那套语法。但Apple Container不是Docker,很多东西不支持。Compose Compiler的工作就是在这两者之间做翻译和校验,把”Docker方言”转换成Apple Container能理解的”标准语”,同时把不兼容的地方明确告诉用户。

​ 至于前端UI,基本上复用了Dockge原版的设计,只做了少量的适配(比如根据RuntimeCapabilities禁用不支持的功能按钮)。Dockge的前端本身就做得很好,没必要重新造轮子。

最后

参考文章:

Dockge - A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager

Apple Containers - Apple Developer

Virtualization Framework - Apple Developer


声明

本文仅作为个人学习记录,由AI辅助编写。