标题为「Compose 启动顺序需要明确的就绪条件」的封面,下方是观察、判断与行动三个面板。

Compose 启动顺序需要明确的就绪条件

Docker Compose 描述服务依赖图,并可以按声明关系启动服务。进程启动只是图中的一种条件:数据库或 API 可能已经运行,却还不能接受其他服务需要的操作。显式 service_healthy 条件让 Compose 在启动依赖方之前,等待被依赖服务的健康检查通过;前提是这个检查确实表达有意义的就绪状态,而不是随意返回成功。 坏掉的样子 简短 depends_on 形式建立启动顺序,却不建立应用就绪保证。用固定 sleep 替代也很脆弱,因为初始化时间会随存储、缓存、迁移和机器负载变化。同一段等待在某次运行中过长,在另一次又不足。条件应来自可以观察的依赖行为,而不是估计已经过去多久;否则构建机变快或变慢,都可能暴露新的偶发故障。 基于健康状态的依赖条件,也不是持续可用性保证。依赖方启动之后,被依赖服务仍可能失败或重启。Compose 不会仅因依赖后来变得不健康,就自动停止或重启已经运行的依赖方。应用仍需要处理连接中断、有界重试,以及操作可能部分完成的情况,不能把启动编排当成完整运行期容错。 嵌套在依赖项里的 restart: true,与服务自身重启策略含义不同。它针对显式 Compose 操作更新或重启依赖后,对依赖方进行相应重启,不是对每一次健康转换或引擎级崩溃的通用订阅。相似字段名容易造成误解,应按照配置层级和文档契约区分,而不是看到 restart 就认为所有失败都会触发。 快速验证 使用宿主 Python、Docker Compose 和固定 Python 镜像运行脚本。实测为 Compose 5.1.1、Docker 29.3.1、Linux arm64,镜像内 Python 3.11.16。脚本用 YAML 接受的 JSON 语法写 Compose 文档,创建唯一项目,所有 HTTP 访问都留在项目网络中。清理只移除该项目,不影响其他服务: ...

四月 8, 2027 · 2 分钟 · XB Field Notes
标题为「相关镜像共享文件层,运行中的容器各自保存修改」的封面,下方是观察、判断与行动三个面板。

相关镜像共享文件层,运行中的容器各自保存修改

镜像用有序的不可变层变更集描述文件系统,并带有默认命令等配置。相关镜像可以引用相同基础层,同时加入不同应用文件。容器运行时,再在镜像之上拥有自己的可写层。区分镜像、共享文件层和容器可写状态,才能解释为什么镜像复用能够节省工作,却不会让某个容器里的日常编辑自动出现在所有邻居中。 坏掉的样子 直接把几个相关镜像显示的大小相加,可能高估它们合计占用的本地存储,因为共享内容可能被重复计数。传输大小、解包后的文件大小,以及快照存储实际分配的空间,也不是同一个指标。共享层确实支持复用,但不能在没有测量具体存储实现和统计口径时,声称节省了一个精确的磁盘容量。 另一个常见错误,是把容器里的修改视为镜像修改。在一个普通容器中安装软件包或修改文件,改变的是该容器的可写文件系统状态。从原镜像新建容器,不会自动继承这次修复。于是,手工修好运行中的服务后再替换实例,原故障可能重新出现,因为用于替换的产物从未包含那次修复。 共享层也不是通用的应用兼容性边界。两个应用即使基础层相同,仍可能使用不同配置、挂载数据、架构,或者在后续层加入不同库版本。应比较有序文件系统标识,以及被验证的实际行为。仅仅看到可读标签相同,无法确定运行的是哪些字节,也无法解释运行设置是否一致。 快速验证 先取得固定摘要的 Python 基础镜像,再使用 Python 和 Docker 构建器运行脚本。实测环境为 Docker 29.3.1、Linux arm64,镜像内 Python 为 3.11.16。实验创建两个相关镜像和两个临时容器,不使用容器网络,结束时只删除自身命名的容器和标签,不触碰其他实验或服务: from pathlib import Path from tempfile import TemporaryDirectory import json,subprocess,uuid BASE='python@sha256:528257d48c1da0dcecc2e725d1ae34498d60c965f1241e39cd6a85a8859bdf84' def docker(*args,check=True): result=subprocess.run(["docker",*args],capture_output=True,text=True,timeout=120) if check:assert result.returncode==0,result.stderr return result def inspect(image):return json.loads(docker("image","inspect",image).stdout)[0] name='xbcatm-layers-'+uuid.uuid4().hex[:10];tags=[name+':left',name+':right'];containers=[name+'-one',name+'-two'] try: with TemporaryDirectory(prefix=name+'-') as directory: root=Path(directory);(root/'shared.py').write_text('value=10\n# '+name+'\n') (root/'Dockerfile').write_text(f'FROM {BASE}\nCOPY shared.py /app/shared.py\nCOPY main.py /app/main.py\nCMD ["python", "/app/main.py"]\n') for tag,increment in zip(tags,[1,2]): (root/'main.py').write_text(f'from shared import value\nprint(value+{increment})\n') docker('build','--network','none','-t',tag,str(root)) left=inspect(tags[0])['RootFS']['Layers'];right=inspect(tags[1])['RootFS']['Layers'] assert left[:-1]==right[:-1] and left[-1]!=right[-1] assert docker('run','--rm','--network','none',tags[0]).stdout.strip()=='11' assert docker('run','--rm','--network','none',tags[1]).stdout.strip()=='12' print('two images share',len(left)-1,'filesystem layer identities and differ in the final application layer') for container in containers: docker('run','-d','--name',container,'--network','none',tags[0],'python','-c','import time;time.sleep(60)') docker('exec',containers[0],'python','-c',"from pathlib import Path;Path('/app/shared.py').write_text('value=100\\n')") assert docker('exec',containers[0],'python','/app/main.py').stdout.strip()=='101' assert docker('exec',containers[1],'python','/app/main.py').stdout.strip()=='11' assert '/app/shared.py' in docker('diff',containers[0]).stdout assert inspect(tags[0])['RootFS']['Layers']==left assert docker('run','--rm','--network','none',tags[0]).stdout.strip()=='11' print('a write in one container changes its writable layer while the sibling container and original image retain the old code') finally: docker('rm','-f',*containers,check=False);docker('image','rm',*tags,check=False) 两次构建复制相同的 shared.py,其中定义数值十。最后的 main.py 不同:一个加一,另一个加二,所以程序分别输出 11 与 12。镜像检查显示,在本次基础镜像和构建结构下,前五个文件系统层标识相同,最后的应用层标识不同。这同时提供文件身份和程序结果两类证据。 ...

三月 22, 2027 · 1 分钟 · XB Field Notes