<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>容器 on 吾爱主机</title><link>https://blog.waihost.com/categories/%E5%AE%B9%E5%99%A8/</link><description>Recent content in 容器 on 吾爱主机</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Tue, 21 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.waihost.com/categories/%E5%AE%B9%E5%99%A8/index.xml" rel="self" type="application/rss+xml"/><item><title>Docker 桥接网络与嵌入式 DNS：原理、连通性与工程实践</title><link>https://blog.waihost.com/posts/docker-bridge-network-embedded-dns/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/docker-bridge-network-embedded-dns/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/docker-bridge-network-embedded-dns.svg" alt="Featured image of post Docker 桥接网络与嵌入式 DNS：原理、连通性与工程实践" /&gt;&lt;p&gt;本地 &lt;code&gt;docker run&lt;/code&gt; 能通、Compose 里服务名却解析失败；两个容器“看起来在同一台机器”却互 ping 不通；或者明明没 &lt;code&gt;-p&lt;/code&gt;，却发现同网段容器已经能访问全部端口——这些日常问题，大多不是“Docker 坏了”，而是 &lt;strong&gt;bridge 网络模型 + DNS 解析路径&lt;/strong&gt; 理解不到位。&lt;/p&gt;
&lt;p&gt;本文基于 Docker 官方 &lt;em&gt;Networking overview&lt;/em&gt;、&lt;em&gt;Bridge network driver&lt;/em&gt;、&lt;em&gt;Packet filtering and firewalls&lt;/em&gt;、&lt;em&gt;Networking in Compose&lt;/em&gt; 与 &lt;em&gt;Legacy container links&lt;/em&gt;，把 &lt;strong&gt;默认 bridge 与用户自定义 bridge 的差异、嵌入式 DNS（&lt;code&gt;127.0.0.11&lt;/code&gt;）、端口可达性、防火墙/iptables 协作，以及可复现的连通性实验&lt;/strong&gt; 讲清楚，方便你在排障时按机制定位。&lt;/p&gt;
&lt;h2 id="一问题背景容器眼里只有网卡没有魔法互联"&gt;&lt;a href="#%e4%b8%80%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e5%ae%b9%e5%99%a8%e7%9c%bc%e9%87%8c%e5%8f%aa%e6%9c%89%e7%bd%91%e5%8d%a1%e6%b2%a1%e6%9c%89%e9%ad%94%e6%b3%95%e4%ba%92%e8%81%94" class="header-anchor"&gt;&lt;/a&gt;一、问题背景：容器眼里只有网卡，没有“魔法互联”
&lt;/h2&gt;&lt;p&gt;从容器视角看，网络很朴素：一张网卡、一个 IP、默认网关、路由表和 DNS。容器并不知道对端是不是另一个 Docker 容器，也不知道自己挂在 default bridge 还是自定义网络上。&lt;/p&gt;
&lt;p&gt;因此，工程里真正要掌握的是 &lt;strong&gt;Docker 如何在宿主机上拼出这张网&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用哪种 &lt;strong&gt;network driver&lt;/strong&gt; 创建隔离域；&lt;/li&gt;
&lt;li&gt;容器如何获得 IP，以及如何访问外网（masquerading）；&lt;/li&gt;
&lt;li&gt;同网/跨网时，&lt;strong&gt;哪些端口天然可达，哪些必须 publish&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;名字解析走 &lt;strong&gt;宿主 &lt;code&gt;/etc/resolv.conf&lt;/code&gt; 拷贝&lt;/strong&gt;，还是 Docker &lt;strong&gt;嵌入式 DNS&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把这四条对齐后，再看 &lt;code&gt;docker network inspect&lt;/code&gt; 和 &lt;code&gt;cat /etc/resolv.conf&lt;/code&gt;，排障会快很多。&lt;/p&gt;
&lt;h2 id="二核心机制bridge-网络在做什么"&gt;&lt;a href="#%e4%ba%8c%e6%a0%b8%e5%bf%83%e6%9c%ba%e5%88%b6bridge-%e7%bd%91%e7%bb%9c%e5%9c%a8%e5%81%9a%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;二、核心机制：bridge 网络在做什么
&lt;/h2&gt;&lt;h3 id="21-软件桥与隔离边界"&gt;&lt;a href="#21-%e8%bd%af%e4%bb%b6%e6%a1%a5%e4%b8%8e%e9%9a%94%e7%a6%bb%e8%be%b9%e7%95%8c" class="header-anchor"&gt;&lt;/a&gt;2.1 软件桥与隔离边界
&lt;/h3&gt;&lt;p&gt;Bridge 驱动在 Linux 上使用软件网桥：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同一 bridge 网络内的容器可互通；&lt;/li&gt;
&lt;li&gt;默认阻止来自其他 bridge 网络、以及 Docker 宿主机之外的直接访问；&lt;/li&gt;
&lt;li&gt;通过 &lt;strong&gt;masquerading（源地址伪装）&lt;/strong&gt; 让容器出网时，外部只看到宿主机 IP；&lt;/li&gt;
&lt;li&gt;支持 &lt;strong&gt;port publishing&lt;/strong&gt;，把容器端口映射到宿主机地址，供外部或其他网络访问。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;官方明确：bridge 面向 &lt;strong&gt;同一 Docker daemon 主机&lt;/strong&gt; 上的容器；跨主机通信需要 OS 级路由或 &lt;strong&gt;overlay&lt;/strong&gt; 等方案。&lt;/p&gt;
&lt;h3 id="22-内置驱动速览选型锚点"&gt;&lt;a href="#22-%e5%86%85%e7%bd%ae%e9%a9%b1%e5%8a%a8%e9%80%9f%e8%a7%88%e9%80%89%e5%9e%8b%e9%94%9a%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;2.2 内置驱动速览（选型锚点）
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Driver&lt;/th&gt;
					&lt;th&gt;典型用途&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;bridge&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;默认驱动；同机容器互联（本文重点）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;host&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;去掉网络隔离，直接用宿主机网络栈&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;完全隔离，无网络&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;overlay&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;多 daemon / Swarm 跨节点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;macvlan&lt;/code&gt; / &lt;code&gt;ipvlan&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;让容器更像“物理网”上的设备 / VLAN 集成&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;没有特殊需求时，应用栈优先用 &lt;strong&gt;用户自定义 bridge&lt;/strong&gt;，而不是长期堆在 default &lt;code&gt;bridge&lt;/code&gt; 上。&lt;/p&gt;
&lt;h3 id="23-默认-bridge-vs-用户自定义-bridge必须分清"&gt;&lt;a href="#23-%e9%bb%98%e8%ae%a4-bridge-vs-%e7%94%a8%e6%88%b7%e8%87%aa%e5%ae%9a%e4%b9%89-bridge%e5%bf%85%e9%a1%bb%e5%88%86%e6%b8%85" class="header-anchor"&gt;&lt;/a&gt;2.3 默认 bridge vs 用户自定义 bridge（必须分清）
&lt;/h3&gt;&lt;p&gt;Docker 首次启动会自动创建名为 &lt;code&gt;bridge&lt;/code&gt; 的 &lt;strong&gt;default bridge&lt;/strong&gt;。未指定 &lt;code&gt;--network&lt;/code&gt; 的容器会连上它。&lt;/p&gt;
&lt;p&gt;官方对用户自定义 bridge 的结论很直接：&lt;strong&gt;User-defined bridge networks are superior to the default bridge network.&lt;/strong&gt; 关键差异如下。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;default bridge&lt;/th&gt;
					&lt;th&gt;用户自定义 bridge&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;名字解析&lt;/td&gt;
					&lt;td&gt;默认只能靠 IP；&lt;code&gt;--link&lt;/code&gt; 为 legacy&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;自动 DNS&lt;/strong&gt;：可用容器名/别名互访&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;隔离&lt;/td&gt;
					&lt;td&gt;未指定网络的容器都挤在一起，无关栈可能互通&lt;/td&gt;
					&lt;td&gt;只有加入该网络的容器互通，作用域清晰&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;热插拔&lt;/td&gt;
					&lt;td&gt;要从 default bridge 换走，通常需停容器重建&lt;/td&gt;
					&lt;td&gt;运行中可用 &lt;code&gt;docker network connect/disconnect&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;配置粒度&lt;/td&gt;
					&lt;td&gt;全局一份（常需改 daemon 并重启）&lt;/td&gt;
					&lt;td&gt;每个网络可独立 &lt;code&gt;docker network create&lt;/code&gt; 配置&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;生产建议&lt;/td&gt;
					&lt;td&gt;官方视为 legacy 细节，&lt;strong&gt;不建议生产依赖&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;推荐作为同机服务互联默认选择&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;同一用户自定义 bridge 内的容器，&lt;strong&gt;彼此相当于暴露全部端口&lt;/strong&gt;；若要对其他网络或宿主机外访问，才需要 &lt;code&gt;-p/--publish&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="24-嵌入式-dns1270011"&gt;&lt;a href="#24-%e5%b5%8c%e5%85%a5%e5%bc%8f-dns1270011" class="header-anchor"&gt;&lt;/a&gt;2.4 嵌入式 DNS：&lt;code&gt;127.0.0.11&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;DNS 是 default bridge 与自定义网络最容易踩坑的分界线：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;default bridge&lt;/strong&gt;：容器通常拿到宿主 &lt;code&gt;/etc/resolv.conf&lt;/code&gt; 的&lt;strong&gt;拷贝&lt;/strong&gt;，按宿主配置解析公网名；&lt;strong&gt;默认不能靠容器名互访&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自定义网络（含 Compose 默认项目网络）&lt;/strong&gt;：使用 Docker &lt;strong&gt;embedded DNS&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;地址固定为 &lt;strong&gt;&lt;code&gt;127.0.0.11&lt;/code&gt;&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有 IPv6 等价地址&lt;/strong&gt;，IPv6-only 容器仍用该 IPv4 地址；&lt;/li&gt;
&lt;li&gt;容器名/别名解析在嵌入式 DNS 完成；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部域名查询会转发到宿主上配置的 DNS&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;若应用硬编码了 DNS 服务器地址，官方建议写 &lt;strong&gt;&lt;code&gt;127.0.0.11&lt;/code&gt;&lt;/strong&gt;（在自定义网络场景下），而不是宿主上的 &lt;code&gt;8.8.8.8&lt;/code&gt; 之类——后者会绕过容器名解析。&lt;/p&gt;
&lt;p&gt;可用标志（&lt;code&gt;docker run&lt;/code&gt; / &lt;code&gt;docker create&lt;/code&gt;）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;标志&lt;/th&gt;
					&lt;th&gt;作用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--dns&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;指定 DNS 服务器；注意解析发生在&lt;strong&gt;容器网络命名空间&lt;/strong&gt;，&lt;code&gt;--dns=127.0.0.1&lt;/code&gt; 指容器自己的 loopback&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--dns-search&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;搜索域&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--dns-opt&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;resolv.conf&lt;/code&gt; 选项&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--hostname&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;容器主机名（默认多为容器 ID）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;宿主 &lt;code&gt;/etc/hosts&lt;/code&gt; 的自定义条目&lt;strong&gt;不会自动继承&lt;/strong&gt;进容器；额外主机名用 &lt;code&gt;--add-host&lt;/code&gt; / Compose &lt;code&gt;extra_hosts&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="25-端口可达性与防火墙协作"&gt;&lt;a href="#25-%e7%ab%af%e5%8f%a3%e5%8f%af%e8%be%be%e6%80%a7%e4%b8%8e%e9%98%b2%e7%81%ab%e5%a2%99%e5%8d%8f%e4%bd%9c" class="header-anchor"&gt;&lt;/a&gt;2.5 端口可达性与防火墙协作
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;同 bridge 网络：容器端口对&lt;strong&gt;宿主&lt;/strong&gt;及&lt;strong&gt;同网其他容器&lt;/strong&gt;可达；&lt;/li&gt;
&lt;li&gt;默认配置下，&lt;strong&gt;其他 bridge 网络&lt;/strong&gt;或&lt;strong&gt;宿主机外&lt;/strong&gt;访问需要 publish；&lt;/li&gt;
&lt;li&gt;Linux 上 Docker 会创建防火墙规则以实现隔离、端口发布与 NAT；&lt;strong&gt;不要随意改 Docker 生成的规则&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;默认后端多为 &lt;strong&gt;iptables&lt;/strong&gt;，也支持 &lt;strong&gt;nftables&lt;/strong&gt;；bridge 上两者功能等价；&lt;/li&gt;
&lt;li&gt;对 &lt;code&gt;ipvlan&lt;/code&gt; / &lt;code&gt;macvlan&lt;/code&gt; / &lt;code&gt;host&lt;/code&gt;，Docker &lt;strong&gt;不创建&lt;/strong&gt;这套 bridge 规则；&lt;/li&gt;
&lt;li&gt;Docker 常会打开 IP 转发，并可能把转发默认策略设为 drop（防止宿主机无意变成路由器）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker 与 ufw 不兼容的常见根因&lt;/strong&gt;：Docker 在 &lt;code&gt;nat&lt;/code&gt; 表改写流量，可能在 ufw 的 INPUT/OUTPUT 之前分流，导致“ufw 规则像没生效”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常用网络级选项（&lt;code&gt;docker network create --opt&lt;/code&gt;）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Option&lt;/th&gt;
					&lt;th&gt;默认&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;com.docker.network.bridge.enable_ip_masquerade&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;出网伪装&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;com.docker.network.bridge.enable_icc&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;同网容器互通；可关以强制“只经代理/发布口访问”&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;com.docker.network.bridge.host_binding_ipv4&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;全部地址&lt;/td&gt;
					&lt;td&gt;未写主机 IP 时的默认绑定地址&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;com.docker.network.driver.mtu&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt;（不限制）&lt;/td&gt;
					&lt;td&gt;MTU&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="26-compose-如何落地同一套模型"&gt;&lt;a href="#26-compose-%e5%a6%82%e4%bd%95%e8%90%bd%e5%9c%b0%e5%90%8c%e4%b8%80%e5%a5%97%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;2.6 Compose 如何落地同一套模型
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;docker compose up&lt;/code&gt; 默认会创建名为 &lt;strong&gt;&lt;code&gt;&amp;lt;project-name&amp;gt;_default&lt;/code&gt;&lt;/strong&gt; 的 bridge 网络，服务全部加入，并向内部 DNS 注册&lt;strong&gt;服务名&lt;/strong&gt;。因此 Compose 里写 &lt;code&gt;http://api:8080&lt;/code&gt; 往往能通，而“裸 default bridge 起两个容器却写容器名”会失败——这不是 Compose 魔法，而是 &lt;strong&gt;自定义网络 + 嵌入式 DNS&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;还可声明 &lt;code&gt;internal: true&lt;/code&gt; 的网络（无默认出网网关），把数据库等后端放在“只能被同项目服务访问”的网段上。&lt;/p&gt;
&lt;h2 id="三实践可复现的连通性与-dns-实验"&gt;&lt;a href="#%e4%b8%89%e5%ae%9e%e8%b7%b5%e5%8f%af%e5%a4%8d%e7%8e%b0%e7%9a%84%e8%bf%9e%e9%80%9a%e6%80%a7%e4%b8%8e-dns-%e5%ae%9e%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;三、实践：可复现的连通性与 DNS 实验
&lt;/h2&gt;&lt;p&gt;下面命令假定本机已安装 Docker Engine。若无 &lt;code&gt;alpine&lt;/code&gt; 镜像，会自动拉取。&lt;/p&gt;
&lt;h3 id="31-观察-default-bridge能通-ip不能靠名字"&gt;&lt;a href="#31-%e8%a7%82%e5%af%9f-default-bridge%e8%83%bd%e9%80%9a-ip%e4%b8%8d%e8%83%bd%e9%9d%a0%e5%90%8d%e5%ad%97" class="header-anchor"&gt;&lt;/a&gt;3.1 观察 default bridge：能通 IP，不能靠名字
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 两个容器都挂在 default bridge（未指定 --network）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name dns-a alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name dns-b alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 看 IP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker inspect -f &lt;span class="s1"&gt;&amp;#39;{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}&amp;#39;&lt;/span&gt; dns-a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;IP_A&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;docker inspect -f &lt;span class="s1"&gt;&amp;#39;{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}&amp;#39;&lt;/span&gt; dns-a&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# IP 互通通常成功&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; dns-b ping -c1 &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$IP_A&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 名字解析：在 default bridge 上通常失败（无嵌入式 DNS 的容器名互访）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; dns-b ping -c1 dns-a &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;name resolve failed as expected on default bridge&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 看 resolv.conf：往往是宿主 DNS 拷贝，而不是 127.0.0.11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; dns-b cat /etc/resolv.conf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;清理：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker rm -f dns-a dns-b
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="32-用户自定义-bridge容器名解析--1270011"&gt;&lt;a href="#32-%e7%94%a8%e6%88%b7%e8%87%aa%e5%ae%9a%e4%b9%89-bridge%e5%ae%b9%e5%99%a8%e5%90%8d%e8%a7%a3%e6%9e%90--1270011" class="header-anchor"&gt;&lt;/a&gt;3.2 用户自定义 bridge：容器名解析 + &lt;code&gt;127.0.0.11&lt;/code&gt;
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network create demo-net
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name web --network demo-net alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name db --network demo-net alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 名字应可解析&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; web ping -c1 db
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; db ping -c1 web
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 嵌入式 DNS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; web cat /etc/resolv.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 期望 nameserver 为 127.0.0.11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 同网端口：无需 -p，另一容器即可访问（这里用 nc 示意）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; -d db sh -c &lt;span class="s1"&gt;&amp;#39;echo hi | nc -l -p 9000&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; web sh -c &lt;span class="s1"&gt;&amp;#39;nc -w 2 db 9000&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;运行中热挂网络：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name side alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt; &lt;span class="c1"&gt;# 先在 default bridge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network connect demo-net side
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; side ping -c1 web
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network disconnect demo-net side
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;清理：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker rm -f web db side
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network rm demo-net
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="33-前后端分离网络拓扑多网卡容器"&gt;&lt;a href="#33-%e5%89%8d%e5%90%8e%e7%ab%af%e5%88%86%e7%a6%bb%e7%bd%91%e7%bb%9c%e6%8b%93%e6%89%91%e5%a4%9a%e7%bd%91%e5%8d%a1%e5%ae%b9%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;3.3 “前后端分离网络”拓扑（多网卡容器）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network create frontend
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network create --internal backend &lt;span class="c1"&gt;# 无默认出网，适合数据库平面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name api --network frontend alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network connect backend api
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name pg --network backend alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# api 能访问 pg（同 backend）；pg 默认不应依赖出网&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; api ping -c1 pg
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 可选：限制默认绑定，仅本机可访问已发布端口&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network create &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --opt com.docker.network.bridge.host_binding_ipv4&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; local-only
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="34-compose-最小可运行示意"&gt;&lt;a href="#34-compose-%e6%9c%80%e5%b0%8f%e5%8f%af%e8%bf%90%e8%a1%8c%e7%a4%ba%e6%84%8f" class="header-anchor"&gt;&lt;/a&gt;3.4 Compose 最小可运行示意
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# compose.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sleep 3600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;api]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sleep 3600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;default, backend]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sleep 3600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;backend]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose &lt;span class="nb"&gt;exec&lt;/span&gt; web ping -c1 api &lt;span class="c1"&gt;# 服务名 DNS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose &lt;span class="nb"&gt;exec&lt;/span&gt; api ping -c1 db
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose down
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="35-关闭-icc强制不能随意横穿"&gt;&lt;a href="#35-%e5%85%b3%e9%97%ad-icc%e5%bc%ba%e5%88%b6%e4%b8%8d%e8%83%bd%e9%9a%8f%e6%84%8f%e6%a8%aa%e7%a9%bf" class="header-anchor"&gt;&lt;/a&gt;3.5 关闭 ICC（强制“不能随意横穿”）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network create &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --opt com.docker.network.bridge.enable_icc&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; no-icc-net
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 同网容器在 ICC 关闭后，默认相互连通会被限制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 常用于强制流量只经反向代理或显式发布路径（结合具体策略验证）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network rm no-icc-net
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="四常见坑与排查清单"&gt;&lt;a href="#%e5%9b%9b%e5%b8%b8%e8%a7%81%e5%9d%91%e4%b8%8e%e6%8e%92%e6%9f%a5%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;四、常见坑与排查清单
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;症状&lt;/th&gt;
					&lt;th&gt;优先检查&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;容器名 ping 不通&lt;/td&gt;
					&lt;td&gt;是否挂在 &lt;strong&gt;default bridge&lt;/strong&gt;？自定义网络里 &lt;code&gt;cat /etc/resolv.conf&lt;/code&gt; 是否为 &lt;code&gt;127.0.0.11&lt;/code&gt;？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compose 服务名不通&lt;/td&gt;
					&lt;td&gt;是否在同一 project 网络？服务是否显式挂到不同 network 且未共享？&lt;code&gt;docker network inspect &amp;lt;project&amp;gt;_default&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;外网不能访问容器&lt;/td&gt;
					&lt;td&gt;是否 &lt;code&gt;-p/--publish&lt;/code&gt;？安全组/云防火墙？绑定是否被 &lt;code&gt;host_binding_ipv4=127.0.0.1&lt;/code&gt; 限制在本机？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;容器不能出网&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;enable_ip_masquerade&lt;/code&gt;？宿主 IP 转发？DNS 是否异常？&lt;code&gt;iptables=false&lt;/code&gt; 是否误关 Docker 防火墙规则？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ufw “放行了仍异常”&lt;/td&gt;
					&lt;td&gt;已知 Docker 与 ufw 规则路径冲突，publish 流量可能绕过 ufw 链&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--link&lt;/code&gt; 老项目&lt;/td&gt;
					&lt;td&gt;官方标为 &lt;strong&gt;legacy&lt;/strong&gt;，推荐迁移到 user-defined networks；环境变量共享改用 env/volume/Compose&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单网容器极多不稳定&lt;/td&gt;
					&lt;td&gt;内核限制：单 bridge 约 &lt;strong&gt;1000+&lt;/strong&gt; 容器时可能不稳定（见 moby#44973）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多网卡默认路由怪&lt;/td&gt;
					&lt;td&gt;多网络时默认网关可能变化；可用 &lt;code&gt;gw-priority&lt;/code&gt; 指定优先网关&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;宿主 hosts 改了容器不生效&lt;/td&gt;
					&lt;td&gt;宿主 &lt;code&gt;/etc/hosts&lt;/code&gt; 不继承；用 &lt;code&gt;--add-host&lt;/code&gt; / &lt;code&gt;extra_hosts&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;推荐排查命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network ls
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network inspect &amp;lt;net&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker inspect -f &lt;span class="s1"&gt;&amp;#39;{{json .NetworkSettings.Networks}}&amp;#39;&lt;/span&gt; &amp;lt;ctr&amp;gt; &lt;span class="p"&gt;|&lt;/span&gt; jq .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;ctr&amp;gt; cat /etc/resolv.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;ctr&amp;gt; ip route
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;ctr&amp;gt; getent hosts &amp;lt;peer-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 宿主侧（Linux）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ip link show &lt;span class="nb"&gt;type&lt;/span&gt; bridge
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 注意：不要随手 flush Docker 管理的 iptables 链&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="五工程建议可直接落到规范"&gt;&lt;a href="#%e4%ba%94%e5%b7%a5%e7%a8%8b%e5%bb%ba%e8%ae%ae%e5%8f%af%e7%9b%b4%e6%8e%a5%e8%90%bd%e5%88%b0%e8%a7%84%e8%8c%83" class="header-anchor"&gt;&lt;/a&gt;五、工程建议（可直接落到规范）
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;新项目默认创建用户自定义 bridge&lt;/strong&gt;（或直接用 Compose 项目网络），避免依赖 default bridge 与 &lt;code&gt;--link&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务发现用名字，不写死容器 IP&lt;/strong&gt;；IP 会随重建变化，DNS 是稳定契约。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前后端分层网络&lt;/strong&gt;：对外服务进可出网网络，数据库进 &lt;code&gt;internal&lt;/code&gt; 网络；需要两边都通的 BFF/API 同时 attach。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只 publish 必要端口&lt;/strong&gt;，生产优先绑定具体主机地址，避免 &lt;code&gt;0.0.0.0&lt;/code&gt; 无脑暴露。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要手改 Docker 生成的防火墙规则&lt;/strong&gt;；若必须对接企业防火墙，走官方允许的集成方式，而不是 &lt;code&gt;iptables=false&lt;/code&gt; 一刀切。&lt;/li&gt;
&lt;li&gt;应用若强制配置 DNS，自定义网络场景优先 &lt;strong&gt;&lt;code&gt;127.0.0.11&lt;/code&gt;&lt;/strong&gt;，以免丢掉容器名解析。&lt;/li&gt;
&lt;li&gt;跨主机通信升级到 &lt;strong&gt;overlay / 服务网格 / 云网络&lt;/strong&gt;，不要指望单机 bridge “自动跨机”。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="六总结"&gt;&lt;a href="#%e5%85%ad%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;六、总结
&lt;/h2&gt;&lt;p&gt;Docker 单机互联的主干是 &lt;strong&gt;bridge&lt;/strong&gt;：软件桥提供 L2 邻接与隔离，masquerade 解决出网，publish 解决入站，&lt;strong&gt;嵌入式 DNS（&lt;code&gt;127.0.0.11&lt;/code&gt;）&lt;/strong&gt; 解决自定义网络上的名字发现。default bridge 适合“随手跑个容器”，但缺少容器名 DNS、隔离弱、配置粗，官方更推荐 &lt;strong&gt;user-defined bridge&lt;/strong&gt;；Compose 的服务名互通，本质上就是把这套模型产品化了。&lt;/p&gt;
&lt;p&gt;记住一条排障主线：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;先看容器挂在哪张网 → 再看 &lt;code&gt;/etc/resolv.conf&lt;/code&gt; 是不是 &lt;code&gt;127.0.0.11&lt;/code&gt; → 再判断该不该有 publish → 最后才怀疑防火墙/宿主路由。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;按这条线做，大部分“容器网络玄学”都会变成可验证的配置问题。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/" target="_blank" rel="noopener"
 &gt;Networking overview&lt;/a&gt;（官方；DNS / default bridge / drivers）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/drivers/bridge/" target="_blank" rel="noopener"
 &gt;Bridge network driver&lt;/a&gt;（官方；default vs user-defined、选项表、ICC/masquerade）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/drivers/" target="_blank" rel="noopener"
 &gt;Network drivers&lt;/a&gt;（官方；bridge/host/overlay/macvlan 等选型）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/packet-filtering-firewalls/" target="_blank" rel="noopener"
 &gt;Packet filtering and firewalls&lt;/a&gt;（官方；iptables/nftables、ufw 注意点）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/compose/how-tos/networking/" target="_blank" rel="noopener"
 &gt;Networking in Compose&lt;/a&gt;（官方；项目默认网络与服务名 DNS）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/links/" target="_blank" rel="noopener"
 &gt;Legacy container links&lt;/a&gt;（官方；&lt;code&gt;--link&lt;/code&gt; legacy 与迁移建议）&lt;/li&gt;
&lt;li&gt;源码文档镜像（核验用）：&lt;a class="link" href="https://github.com/docker/docs/tree/main/content/manuals/engine/network" target="_blank" rel="noopener"
 &gt;docker/docs &lt;code&gt;content/manuals/engine/network/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Linux cgroup v2 内存控制与 OOM Killer：原理、观测与容器化实践</title><link>https://blog.waihost.com/posts/linux-cgroup-v2-memory-oom-killer/</link><pubDate>Thu, 16 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/linux-cgroup-v2-memory-oom-killer/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/linux-cgroup-v2-memory-oom-killer.svg" alt="Featured image of post Linux cgroup v2 内存控制与 OOM Killer：原理、观测与容器化实践" /&gt;&lt;p&gt;线上最棘手的一类故障，往往不是“服务报了明确业务错误”，而是进程突然消失：容器退出码 &lt;code&gt;137&lt;/code&gt;、Pod 状态里出现 &lt;code&gt;OOMKilled&lt;/code&gt;，或者主机 &lt;code&gt;dmesg&lt;/code&gt; 里留下一行 &lt;code&gt;Out of memory: Killed process&lt;/code&gt;。表象是“内存不够”，根因通常落在 &lt;strong&gt;内存如何被记账、如何被限制、以及内核如何选择牺牲者&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;本文以 Linux &lt;strong&gt;cgroup v2 memory 控制器&lt;/strong&gt; 与 &lt;strong&gt;OOM Killer&lt;/strong&gt; 为主线，结合 Docker / Kubernetes 的常见映射，把“限多少、什么时候开始回收、什么时候杀进程、怎么排”讲清楚。&lt;/p&gt;
&lt;h2 id="一先建立两层模型全局-oom-vs-cgroup-oom"&gt;&lt;a href="#%e4%b8%80%e5%85%88%e5%bb%ba%e7%ab%8b%e4%b8%a4%e5%b1%82%e6%a8%a1%e5%9e%8b%e5%85%a8%e5%b1%80-oom-vs-cgroup-oom" class="header-anchor"&gt;&lt;/a&gt;一、先建立两层模型：全局 OOM vs cgroup OOM
&lt;/h2&gt;&lt;p&gt;Linux 里至少要区分两种“内存不够”：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;典型触发&lt;/th&gt;
					&lt;th&gt;影响范围&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;全局内存压力&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;整机可用内存 + 可回收页不够，分配失败&lt;/td&gt;
					&lt;td&gt;OOM killer 在系统范围内按启发式选进程&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;cgroup 内存上限&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;某 cgroup 的 &lt;code&gt;memory.max&lt;/code&gt; 触顶且回收失败&lt;/td&gt;
					&lt;td&gt;OOM 只发生在该 cgroup 内（不会跨出该 cgroup 去杀别人）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这解释了一个常见错觉：&lt;strong&gt;给容器设了 &lt;code&gt;-m 512m&lt;/code&gt;，为什么还会拖垮宿主机？&lt;/strong&gt;&lt;br&gt;
因为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;没设硬限制时，容器与宿主机共享整机内存；&lt;/li&gt;
&lt;li&gt;即使设了 limit，仍可能有内核开销、page cache 统计差异、swap 配置、以及 &lt;strong&gt;宿主级其他进程&lt;/strong&gt; 的压力；&lt;/li&gt;
&lt;li&gt;Docker 默认会尽量保护 daemon 自身，但 &lt;strong&gt;不会&lt;/strong&gt; 自动把所有容器都“隔离成绝对安全沙箱”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此工程上要同时看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进程/容器是否撞到了 &lt;strong&gt;自己的 limit&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;节点是否发生了 &lt;strong&gt;系统级 OOM / kubelet 驱逐&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="二cgroup-v2-的内存阶梯min--low--high--max"&gt;&lt;a href="#%e4%ba%8ccgroup-v2-%e7%9a%84%e5%86%85%e5%ad%98%e9%98%b6%e6%a2%afmin--low--high--max" class="header-anchor"&gt;&lt;/a&gt;二、cgroup v2 的内存阶梯：min / low / high / max
&lt;/h2&gt;&lt;p&gt;cgroup v2 memory 控制器把控制面拆成“保护、限速、硬顶”几层。官方接口里最核心的是这些文件（单位均为字节）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;接口&lt;/th&gt;
					&lt;th&gt;角色&lt;/th&gt;
					&lt;th&gt;关键语义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;memory.current&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;观测&lt;/td&gt;
					&lt;td&gt;当前 cgroup 及其后代已用内存&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;memory.min&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;硬保护&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;在 effective min 内，内存&lt;strong&gt;不会被回收&lt;/strong&gt;；若系统没有其他可回收内存，可能直接触发 OOM&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;memory.low&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;尽力保护&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;在 effective low 内，尽量不被回收；只有无保护可回收内存时才可能被碰&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;memory.high&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;节流线&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;超高后进程被 throttling，并承受很重的 direct reclaim；&lt;strong&gt;不会&lt;/strong&gt;因此调用 OOM killer&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;memory.max&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;硬上限&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;用量到顶且无法压回时，&lt;strong&gt;在该 cgroup 内调用 OOM killer&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;memory.oom.group&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;策略&lt;/td&gt;
					&lt;td&gt;设为 &lt;code&gt;1&lt;/code&gt; 时，把 cgroup 当不可分割工作负载：要么一起杀，要么都不杀（&lt;code&gt;oom_score_adj=-1000&lt;/code&gt; 的任务例外）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;memory.events&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;事件计数&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;high&lt;/code&gt; / &lt;code&gt;max&lt;/code&gt; / &lt;code&gt;oom&lt;/code&gt; / &lt;code&gt;oom_kill&lt;/code&gt; 等，是最好的“快撞墙”信号&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;memory.peak&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;峰值&lt;/td&gt;
					&lt;td&gt;自创建或重置以来的最大用量&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以把它理解成一条阶梯：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;usage
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ memory.min ── 硬保护（尽量不回收；保护过猛可能反噬）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ memory.low ── 软保护（有余量时优先回收别人）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ memory.high ── 开始重压回收 + 节流（通常还不杀）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ memory.max ── 硬顶；回收失败 → cgroup OOM
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="1-为什么要有-high而不是只设-max"&gt;&lt;a href="#1-%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e6%9c%89-high%e8%80%8c%e4%b8%8d%e6%98%af%e5%8f%aa%e8%ae%be-max" class="header-anchor"&gt;&lt;/a&gt;1. 为什么要有 &lt;code&gt;high&lt;/code&gt;，而不是只设 &lt;code&gt;max&lt;/code&gt;？
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;memory.max&lt;/code&gt; 是“悬崖”：到顶就可能杀进程。&lt;br&gt;
&lt;code&gt;memory.high&lt;/code&gt; 是“缓坡”：先让工作负载变慢、开始回收，给外部控制器（或人）反应时间。内核文档明确：&lt;code&gt;high&lt;/code&gt; 越界 &lt;strong&gt;never invokes the OOM killer&lt;/strong&gt;，极端情况下甚至可能被短暂突破。&lt;/p&gt;
&lt;p&gt;生产里更健康的模式往往是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;high&lt;/code&gt; 设在“可接受抖动”的水位；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max&lt;/code&gt; 设在“绝对不可越过”的水位；&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;memory.events&lt;/code&gt; 的 &lt;code&gt;high&lt;/code&gt;/&lt;code&gt;max&lt;/code&gt; 做告警，而不是等进程死了再看。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-minlow-保护不是免费的"&gt;&lt;a href="#2-minlow-%e4%bf%9d%e6%8a%a4%e4%b8%8d%e6%98%af%e5%85%8d%e8%b4%b9%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;2. &lt;code&gt;min&lt;/code&gt;/&lt;code&gt;low&lt;/code&gt; 保护不是免费的
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;memory.min&lt;/code&gt; 是硬保护：保护区内页面在“任何条件下”都不应被回收；如果系统已经没有未保护可回收内存，就可能 &lt;strong&gt;直接 OOM&lt;/strong&gt;。&lt;br&gt;
&lt;code&gt;memory.low&lt;/code&gt; 是 best-effort：优先保护，但仍可能在全局极端压力下被回收。&lt;/p&gt;
&lt;p&gt;因此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保护值叠加超过父 cgroup/整机能力时，会发生 &lt;strong&gt;overcommit of protection&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;effective 边界还会受祖先 cgroup 限制；&lt;/li&gt;
&lt;li&gt;把所有关键服务都设成很高的 &lt;code&gt;min&lt;/code&gt;，等于在内存不够时更容易触发“保谁都保不住”的 OOM。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="三oom-killer它到底怎么选人"&gt;&lt;a href="#%e4%b8%89oom-killer%e5%ae%83%e5%88%b0%e5%ba%95%e6%80%8e%e4%b9%88%e9%80%89%e4%ba%ba" class="header-anchor"&gt;&lt;/a&gt;三、OOM Killer：它到底怎么选人
&lt;/h2&gt;&lt;h3 id="1-评分oom_score-与-oom_score_adj"&gt;&lt;a href="#1-%e8%af%84%e5%88%86oom_score-%e4%b8%8e-oom_score_adj" class="header-anchor"&gt;&lt;/a&gt;1. 评分：&lt;code&gt;oom_score&lt;/code&gt; 与 &lt;code&gt;oom_score_adj&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;当内核决定必须杀进程腾内存时，会给候选任务打 badness 分。man-pages 对 &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/oom_score&lt;/code&gt; / &lt;code&gt;oom_score_adj&lt;/code&gt; 的说明可概括为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分数大致落在 &lt;strong&gt;0（几乎不杀）到 1000（优先杀）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;主要依据是任务相对其 &lt;strong&gt;allowed memory&lt;/strong&gt; 的用量比例（RSS/swap 等估计）；&lt;/li&gt;
&lt;li&gt;用满允许内存 ≈ 1000，用一半 ≈ 500；&lt;/li&gt;
&lt;li&gt;root 进程额外获得约 &lt;strong&gt;3%&lt;/strong&gt; 的“更宽容”额度；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oom_score_adj&lt;/code&gt; 范围 &lt;strong&gt;-1000 ~ +1000&lt;/strong&gt;，在启发式结果上再加减；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;oom_score_adj = -1000&lt;/code&gt; 表示 OOM 保护（基本不会被选中）&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“allowed memory”取决于 OOM 上下文：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统全局 OOM → 整机可分配资源；&lt;/li&gt;
&lt;li&gt;cgroup/limit 触顶 → 该 limit 本身；&lt;/li&gt;
&lt;li&gt;cpuset / mempolicy 节点耗尽 → 对应节点集合。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-系统级可调旋钮procsysvm"&gt;&lt;a href="#2-%e7%b3%bb%e7%bb%9f%e7%ba%a7%e5%8f%af%e8%b0%83%e6%97%8b%e9%92%aeprocsysvm" class="header-anchor"&gt;&lt;/a&gt;2. 系统级可调旋钮（&lt;code&gt;/proc/sys/vm/*&lt;/code&gt;）
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;参数&lt;/th&gt;
					&lt;th&gt;默认（文档）&lt;/th&gt;
					&lt;th&gt;作用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;oom_dump_tasks&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;OOM 杀进程时是否打印系统任务摘要（pid/rss/oom_score_adj 等），便于事后定位&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;oom_kill_allocating_task&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt;：扫描任务列表按启发式选“更该杀”的；&lt;code&gt;1&lt;/code&gt;：直接杀触发分配失败的那个任务（省扫描）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;panic_on_oom&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt; 杀进程求生；&lt;code&gt;1&lt;/code&gt; 全局 OOM 可 panic（节点局部耗尽未必）；&lt;code&gt;2&lt;/code&gt; 连 memory cgroup OOM 也整机 panic&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;overcommit_memory&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt; 启发式拒绝明显 overcommit；&lt;code&gt;1&lt;/code&gt; 几乎总是允许，直到真正用完；&lt;code&gt;2&lt;/code&gt; never overcommit 策略&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;大多数业务机保持默认即可；&lt;strong&gt;集群 failover / 取证&lt;/strong&gt; 场景才会认真考虑 &lt;code&gt;panic_on_oom&lt;/code&gt; + kdump。&lt;br&gt;
不要把 &lt;code&gt;oom_kill_allocating_task=1&lt;/code&gt; 当成“优化”随手打开：它可能杀掉“刚好申请最后一页”的 innocuous 任务，而放过真正的内存黑洞。&lt;/p&gt;
&lt;h3 id="3-cgroup-内-oom-的边界"&gt;&lt;a href="#3-cgroup-%e5%86%85-oom-%e7%9a%84%e8%be%b9%e7%95%8c" class="header-anchor"&gt;&lt;/a&gt;3. cgroup 内 OOM 的边界
&lt;/h3&gt;&lt;p&gt;cgroup v2 文档强调两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;触达 &lt;code&gt;memory.max&lt;/code&gt; 且无法回收时，OOM killer &lt;strong&gt;在该 cgroup 内&lt;/strong&gt;被调用；&lt;/li&gt;
&lt;li&gt;一旦在某个 cgroup 触发 OOM，&lt;strong&gt;不会去杀该 cgroup 外的任务&lt;/strong&gt;（与祖先 &lt;code&gt;memory.oom.group&lt;/code&gt; 取值无关）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;memory.oom.group=1&lt;/code&gt; 适合“多进程必须同生共死”的工作负载（例如某个多 worker 服务希望要么整组重启，要么都不半残）。&lt;br&gt;
但要注意：被 &lt;code&gt;-1000&lt;/code&gt; 保护的任务仍是例外。&lt;/p&gt;
&lt;h2 id="四工程映射docker-与-kubernetes"&gt;&lt;a href="#%e5%9b%9b%e5%b7%a5%e7%a8%8b%e6%98%a0%e5%b0%84docker-%e4%b8%8e-kubernetes" class="header-anchor"&gt;&lt;/a&gt;四、工程映射：Docker 与 Kubernetes
&lt;/h2&gt;&lt;h3 id="1-docker把-limit-落到-cgroup"&gt;&lt;a href="#1-docker%e6%8a%8a-limit-%e8%90%bd%e5%88%b0-cgroup" class="header-anchor"&gt;&lt;/a&gt;1. Docker：把 limit 落到 cgroup
&lt;/h3&gt;&lt;p&gt;Docker 官方资源限制文档给出了与内存相关的关键选项：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 硬上限（最小允许约 6m）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name demo -m 512m your-image
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 内存 + swap 总预算（细节依赖 --memory-swap 语义）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d -m 512m --memory-swap 1g your-image
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 软预留（争用时更“敏感”，不保证不越界）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d -m 1g --memory-reservation 512m your-image
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 一般不要关 OOM killer；若关闭，必须同时设置 -m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d -m 512m --oom-kill-disable your-image
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-m/--memory&lt;/code&gt;&lt;/strong&gt;：容器可用内存上限；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--memory-reservation&lt;/code&gt;&lt;/strong&gt;：软限制，需小于 &lt;code&gt;--memory&lt;/code&gt;，争用时生效，&lt;strong&gt;不保证&lt;/strong&gt;永不越界；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--oom-kill-disable&lt;/code&gt;&lt;/strong&gt;：禁用容器内 OOM killer；文档要求 &lt;strong&gt;仅在同时设置了 &lt;code&gt;-m&lt;/code&gt; 时使用&lt;/strong&gt;，否则可能把压力转嫁给宿主机，拖垮整机；&lt;/li&gt;
&lt;li&gt;Docker daemon 会调整自身 OOM 优先级，降低“杀 daemon 导致全盘崩溃”的概率；&lt;strong&gt;容器进程默认没有这种优待&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-kubernetesrequest--limit--qos--驱逐"&gt;&lt;a href="#2-kubernetesrequest--limit--qos--%e9%a9%b1%e9%80%90" class="header-anchor"&gt;&lt;/a&gt;2. Kubernetes：request / limit / QoS / 驱逐
&lt;/h3&gt;&lt;p&gt;Kubernetes 文档把资源语义拆成两层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;调度与预留&lt;/strong&gt;：&lt;code&gt;requests&lt;/code&gt; 供 &lt;code&gt;kube-scheduler&lt;/code&gt; 选型，kubelet 也会为容器预留至少 request 量；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行时强制&lt;/strong&gt;：&lt;code&gt;limits&lt;/code&gt; 由 kubelet/容器运行时落到 cgroup；&lt;strong&gt;memory limit 由内核通过 OOM kill 强制&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;与 CPU 不同：CPU 超限通常是节流；&lt;strong&gt;内存超限更可能直接被内核杀掉&lt;/strong&gt;。文档也提醒：OOM 是 &lt;strong&gt;反应式&lt;/strong&gt; 的——容器可能短暂超过 limit，但在内核感知到压力后才被终止。&lt;/p&gt;
&lt;p&gt;QoS 类（Guaranteed / Burstable / BestEffort）主要影响 &lt;strong&gt;节点资源压力下的驱逐优先级&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;节点资源不足时，通常先考虑驱逐 &lt;strong&gt;BestEffort&lt;/strong&gt;，再 &lt;strong&gt;Burstable&lt;/strong&gt;，最后 &lt;strong&gt;Guaranteed&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;因资源压力驱逐时，候选对象通常是 &lt;strong&gt;超过 request&lt;/strong&gt; 的 Pod；&lt;/li&gt;
&lt;li&gt;Guaranteed（各容器 request=limit 且都 &amp;gt;0 的经典条件）最不容易被驱逐，但 &lt;strong&gt;不等于永远不被 OOM&lt;/strong&gt;：一旦超过自己的 memory limit，仍可能 &lt;code&gt;OOMKilled&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个可复现的排查入口：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe pod &amp;lt;pod&amp;gt; &lt;span class="p"&gt;|&lt;/span&gt; sed -n &lt;span class="s1"&gt;&amp;#39;/Last State/,/Events/p&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 常见：Reason: OOMKilled, Exit Code: 137&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="五可复现观测别等-dmesg-再救火"&gt;&lt;a href="#%e4%ba%94%e5%8f%af%e5%a4%8d%e7%8e%b0%e8%a7%82%e6%b5%8b%e5%88%ab%e7%ad%89-dmesg-%e5%86%8d%e6%95%91%e7%81%ab" class="header-anchor"&gt;&lt;/a&gt;五、可复现观测：别等 dmesg 再救火
&lt;/h2&gt;&lt;h3 id="1-先确认-cgroup-v2-与容器路径"&gt;&lt;a href="#1-%e5%85%88%e7%a1%ae%e8%ae%a4-cgroup-v2-%e4%b8%8e%e5%ae%b9%e5%99%a8%e8%b7%af%e5%be%84" class="header-anchor"&gt;&lt;/a&gt;1. 先确认 cgroup v2 与容器路径
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 多数现代发行版默认 cgroup v2 统一层级&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mount &lt;span class="p"&gt;|&lt;/span&gt; grep cgroup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stat -fc %T /sys/fs/cgroup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 看某个进程属于哪个 cgroup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pidof java &lt;span class="p"&gt;|&lt;/span&gt; head -n1 &lt;span class="p"&gt;|&lt;/span&gt; xargs -I&lt;span class="o"&gt;{}&lt;/span&gt; cat /proc/&lt;span class="o"&gt;{}&lt;/span&gt;/cgroup
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-读-memory-接口容器内或宿主机-cgroup-路径"&gt;&lt;a href="#2-%e8%af%bb-memory-%e6%8e%a5%e5%8f%a3%e5%ae%b9%e5%99%a8%e5%86%85%e6%88%96%e5%ae%bf%e4%b8%bb%e6%9c%ba-cgroup-%e8%b7%af%e5%be%84" class="header-anchor"&gt;&lt;/a&gt;2. 读 memory 接口（容器内或宿主机 cgroup 路径）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/sys/fs/cgroup &lt;span class="c1"&gt;# 或容器对应的子路径，如 /sys/fs/cgroup/system.slice/docker-&amp;lt;id&amp;gt;.scope&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &lt;span class="nv"&gt;$CG&lt;/span&gt;/memory.current
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &lt;span class="nv"&gt;$CG&lt;/span&gt;/memory.max
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &lt;span class="nv"&gt;$CG&lt;/span&gt;/memory.high
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &lt;span class="nv"&gt;$CG&lt;/span&gt;/memory.peak
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &lt;span class="nv"&gt;$CG&lt;/span&gt;/memory.events
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 关注：high / max / oom / oom_kill 是否持续上涨&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;解释事件：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;code&gt;memory.events&lt;/code&gt; 字段&lt;/th&gt;
					&lt;th&gt;含义（工程视角）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;high&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;触碰 high 边界，被节流/强制回收的次数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;max&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用量几乎要越过 max；若 direct reclaim 失败，将进入 OOM 状态&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;oom&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;已到 limit，分配即将失败&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;oom_kill&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;实际发生了 OOM kill&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="3-看谁更容易被杀"&gt;&lt;a href="#3-%e7%9c%8b%e8%b0%81%e6%9b%b4%e5%ae%b9%e6%98%93%e8%a2%ab%e6%9d%80" class="header-anchor"&gt;&lt;/a&gt;3. 看谁更容易被杀
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 分数越高越危险&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ps -eo pid,user,comm,rss,oom_score,oom_score_adj --sort&lt;span class="o"&gt;=&lt;/span&gt;-oom_score &lt;span class="p"&gt;|&lt;/span&gt; head
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 某个关键守护进程是否被错误保护/错误牺牲&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /proc/&lt;span class="k"&gt;$(&lt;/span&gt;pidof dockerd&lt;span class="k"&gt;)&lt;/span&gt;/oom_score_adj
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-对照内核日志"&gt;&lt;a href="#4-%e5%af%b9%e7%85%a7%e5%86%85%e6%a0%b8%e6%97%a5%e5%bf%97" class="header-anchor"&gt;&lt;/a&gt;4. 对照内核日志
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dmesg -T &lt;span class="p"&gt;|&lt;/span&gt; egrep -i &lt;span class="s1"&gt;&amp;#39;out of memory|killed process|oom&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;journalctl -k -b --no-pager &lt;span class="p"&gt;|&lt;/span&gt; egrep -i &lt;span class="s1"&gt;&amp;#39;out of memory|killed process&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;若 &lt;code&gt;oom_dump_tasks=1&lt;/code&gt;（默认），日志里通常能看到候选任务的 rss、&lt;code&gt;oom_score_adj&lt;/code&gt; 等信息，用来回答“为什么杀的是 A 不是 B”。&lt;/p&gt;
&lt;h2 id="六常见坑与排查清单"&gt;&lt;a href="#%e5%85%ad%e5%b8%b8%e8%a7%81%e5%9d%91%e4%b8%8e%e6%8e%92%e6%9f%a5%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;六、常见坑与排查清单
&lt;/h2&gt;&lt;h3 id="坑-1只设-request不设-limit"&gt;&lt;a href="#%e5%9d%91-1%e5%8f%aa%e8%ae%be-request%e4%b8%8d%e8%ae%be-limit" class="header-anchor"&gt;&lt;/a&gt;坑 1：只设 request，不设 limit
&lt;/h3&gt;&lt;p&gt;调度时“看起来能放下”，运行时可以吃爆节点；最终变成 &lt;strong&gt;节点级压力 + 驱逐/全局 OOM&lt;/strong&gt;，故障面比单容器 OOM 更大。&lt;/p&gt;
&lt;h3 id="坑-2limit-设太紧没有-high-缓冲与观测"&gt;&lt;a href="#%e5%9d%91-2limit-%e8%ae%be%e5%a4%aa%e7%b4%a7%e6%b2%a1%e6%9c%89-high-%e7%bc%93%e5%86%b2%e4%b8%8e%e8%a7%82%e6%b5%8b" class="header-anchor"&gt;&lt;/a&gt;坑 2：limit 设太紧，没有 &lt;code&gt;high&lt;/code&gt; 缓冲与观测
&lt;/h3&gt;&lt;p&gt;应用启动峰值、JVM 堆外、page cache、临时解压都可能把 &lt;code&gt;current&lt;/code&gt; 顶到 &lt;code&gt;max&lt;/code&gt;。&lt;br&gt;
结果是频繁 &lt;code&gt;OOMKilled&lt;/code&gt; 重启，而不是平滑降速。优先补：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;真实峰值观测（&lt;code&gt;memory.peak&lt;/code&gt;、容器监控）；&lt;/li&gt;
&lt;li&gt;合理 limit；&lt;/li&gt;
&lt;li&gt;能用 high 水位告警就不要只用死亡事件告警。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="坑-3把-oom_score_adj-1000-到处贴"&gt;&lt;a href="#%e5%9d%91-3%e6%8a%8a-oom_score_adj-1000-%e5%88%b0%e5%a4%84%e8%b4%b4" class="header-anchor"&gt;&lt;/a&gt;坑 3：把 &lt;code&gt;oom_score_adj=-1000&lt;/code&gt; 到处贴
&lt;/h3&gt;&lt;p&gt;保护数据库/核心 agent 合理；若把内存泄漏服务也保护起来，OOM killer 只能去杀更无辜的进程，整机稳定性更差。&lt;/p&gt;
&lt;h3 id="坑-4--oom-kill-disable-却没有硬上限"&gt;&lt;a href="#%e5%9d%91-4--oom-kill-disable-%e5%8d%b4%e6%b2%a1%e6%9c%89%e7%a1%ac%e4%b8%8a%e9%99%90" class="header-anchor"&gt;&lt;/a&gt;坑 4：&lt;code&gt;--oom-kill-disable&lt;/code&gt; 却没有硬上限
&lt;/h3&gt;&lt;p&gt;Docker 文档明确警告：关 OOM killer 时必须配合 &lt;code&gt;-m&lt;/code&gt;。否则容器可把宿主内存吃穿。&lt;/p&gt;
&lt;h3 id="坑-5混淆kubelet-驱逐与cgroup-oom"&gt;&lt;a href="#%e5%9d%91-5%e6%b7%b7%e6%b7%86kubelet-%e9%a9%b1%e9%80%90%e4%b8%8ecgroup-oom" class="header-anchor"&gt;&lt;/a&gt;坑 5：混淆“kubelet 驱逐”与“cgroup OOM”
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;现象&lt;/th&gt;
					&lt;th&gt;更像&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Pod 事件里有 eviction、磁盘/内存 pressure&lt;/td&gt;
					&lt;td&gt;kubelet 驱逐&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Container &lt;code&gt;Last State: OOMKilled&lt;/code&gt; / exit 137&lt;/td&gt;
					&lt;td&gt;cgroup/内核 OOM&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;宿主 &lt;code&gt;dmesg&lt;/code&gt; 出现 Killed process，且受害进程不一定是容器&lt;/td&gt;
					&lt;td&gt;全局 OOM 或选中了别的任务&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="坑-6用错内存用量口径"&gt;&lt;a href="#%e5%9d%91-6%e7%94%a8%e9%94%99%e5%86%85%e5%ad%98%e7%94%a8%e9%87%8f%e5%8f%a3%e5%be%84" class="header-anchor"&gt;&lt;/a&gt;坑 6：用错“内存用量”口径
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;RSS&lt;/code&gt;、cgroup &lt;code&gt;memory.current&lt;/code&gt;、容器运行时 working set、JVM heap used &lt;strong&gt;不是同一个数&lt;/strong&gt;。&lt;br&gt;
排 OOM 时以 &lt;strong&gt;cgroup 记账 + kernel 日志&lt;/strong&gt; 为准，再用进程级工具交叉验证。&lt;/p&gt;
&lt;h2 id="七一套可执行的生产建议"&gt;&lt;a href="#%e4%b8%83%e4%b8%80%e5%a5%97%e5%8f%af%e6%89%a7%e8%a1%8c%e7%9a%84%e7%94%9f%e4%ba%a7%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;七、一套可执行的生产建议
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每个有状态/高风险服务都要有 memory limit&lt;/strong&gt;，并基于压测峰值留 20%–40% 余量（按语言运行时特性调整）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优先监控 &lt;code&gt;memory.events&lt;/code&gt; 与 working set 趋势&lt;/strong&gt;，把 &lt;code&gt;high/max&lt;/code&gt; 当预警，把 &lt;code&gt;oom_kill&lt;/code&gt; 当事故。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;K8s 上让关键链路尽量 Guaranteed 或至少 request≈实际稳态&lt;/strong&gt;；limit 不要拍脑袋抄 CPU 倍数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保护面要窄&lt;/strong&gt;：&lt;code&gt;-1000&lt;/code&gt; 只给真正的系统关键路径；业务进程靠 limit 与弹性，而不是全局免死金牌。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复盘时固定三件套&lt;/strong&gt;：&lt;code&gt;memory.current/max/events&lt;/code&gt; + &lt;code&gt;oom_score(_adj)&lt;/code&gt; + &lt;code&gt;dmesg/journal&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要默认改 &lt;code&gt;panic_on_oom&lt;/code&gt; / &lt;code&gt;oom_kill_allocating_task&lt;/code&gt;&lt;/strong&gt;，除非你清楚故障域与取证目标。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="八总结"&gt;&lt;a href="#%e5%85%ab%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;八、总结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;cgroup v2 用 &lt;code&gt;min/low/high/max&lt;/code&gt; 把内存控制从“一刀切 hard cap”扩展成“保护 + 节流 + 硬顶”。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;high&lt;/code&gt; 负责让系统先变慢并回收；&lt;code&gt;max&lt;/code&gt; 才是 OOM 悬崖。&lt;/li&gt;
&lt;li&gt;OOM killer 按 badness/&lt;code&gt;oom_score_adj&lt;/code&gt; 选人；cgroup OOM 默认 &lt;strong&gt;杀在组内&lt;/strong&gt;，&lt;code&gt;memory.oom.group&lt;/code&gt; 可改变是否整组同生共死。&lt;/li&gt;
&lt;li&gt;Docker 的 &lt;code&gt;-m&lt;/code&gt;、K8s 的 &lt;code&gt;limits.memory&lt;/code&gt; 最终都落到内核 cgroup；&lt;strong&gt;CPU 超限多半被节流，内存超限更可能被杀&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;可观测性的关键不是“死了没有”，而是 &lt;strong&gt;&lt;code&gt;memory.events&lt;/code&gt; 是否在反复逼近上限&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把内存限制当成“开关”只会得到随机的 137；把它当成“阶梯 + 评分 + 事件流”，才能在容器化环境里稳定控风险。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Linux Kernel Docs — &lt;em&gt;Control Group v2&lt;/em&gt;（&lt;code&gt;memory.current&lt;/code&gt; / &lt;code&gt;min&lt;/code&gt; / &lt;code&gt;low&lt;/code&gt; / &lt;code&gt;high&lt;/code&gt; / &lt;code&gt;max&lt;/code&gt; / &lt;code&gt;oom.group&lt;/code&gt; / &lt;code&gt;events&lt;/code&gt;）: &lt;a class="link" href="https://docs.kernel.org/admin-guide/cgroup-v2.html" target="_blank" rel="noopener"
 &gt;https://docs.kernel.org/admin-guide/cgroup-v2.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Linux Kernel Docs — &lt;em&gt;Documentation for /proc/sys/vm/&lt;/em&gt;（&lt;code&gt;oom_dump_tasks&lt;/code&gt;、&lt;code&gt;oom_kill_allocating_task&lt;/code&gt;、&lt;code&gt;panic_on_oom&lt;/code&gt;、&lt;code&gt;overcommit_memory&lt;/code&gt;、&lt;code&gt;swappiness&lt;/code&gt;）: &lt;a class="link" href="https://docs.kernel.org/admin-guide/sysctl/vm.html" target="_blank" rel="noopener"
 &gt;https://docs.kernel.org/admin-guide/sysctl/vm.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;man-pages — &lt;code&gt;proc_pid_oom_score(5)&lt;/code&gt;: &lt;a class="link" href="https://man7.org/linux/man-pages/man5/proc_pid_oom_score.5.html" target="_blank" rel="noopener"
 &gt;https://man7.org/linux/man-pages/man5/proc_pid_oom_score.5.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;man-pages — &lt;code&gt;proc_pid_oom_score_adj(5)&lt;/code&gt;: &lt;a class="link" href="https://man7.org/linux/man-pages/man5/proc_pid_oom_score_adj.5.html" target="_blank" rel="noopener"
 &gt;https://man7.org/linux/man-pages/man5/proc_pid_oom_score_adj.5.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;em&gt;Resource constraints&lt;/em&gt;（memory options、OOME、&lt;code&gt;--oom-kill-disable&lt;/code&gt;）: &lt;a class="link" href="https://docs.docker.com/engine/containers/resource_constraints/" target="_blank" rel="noopener"
 &gt;https://docs.docker.com/engine/containers/resource_constraints/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Kubernetes Docs — &lt;em&gt;Resource Management for Pods and Containers&lt;/em&gt;: &lt;a class="link" href="https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/" target="_blank" rel="noopener"
 &gt;https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Kubernetes Docs — &lt;em&gt;Pod Quality of Service Classes&lt;/em&gt;: &lt;a class="link" href="https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/" target="_blank" rel="noopener"
 &gt;https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Docker Compose 5.3 原生 pre_start 初始化容器实战：让本地编排更接近 Kubernetes Init Container</title><link>https://blog.waihost.com/posts/docker-compose-5-3-pre-start-guide/</link><pubDate>Wed, 08 Jul 2026 09:08:00 +0800</pubDate><guid>https://blog.waihost.com/posts/docker-compose-5-3-pre-start-guide/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/docker-compose-5-3-pre-start-guide.svg" alt="Featured image of post Docker Compose 5.3 原生 pre_start 初始化容器实战：让本地编排更接近 Kubernetes Init Container" /&gt;&lt;p&gt;Docker Compose 在 2026 年 7 月连续发布了 &lt;code&gt;v5.3.0&lt;/code&gt; 和 &lt;code&gt;v5.3.1&lt;/code&gt;。其中 &lt;code&gt;v5.3.1&lt;/code&gt; 主要是内部流程与依赖更新；真正值得一线工程团队关注的变化，是 &lt;code&gt;v5.3.0&lt;/code&gt; 引入了对 Compose Spec &lt;code&gt;pre_start&lt;/code&gt; 生命周期钩子的原生支持。官方发布说明明确写到：这个版本 introduces native support for init containers；对应合并的 PR 说明也进一步确认，&lt;code&gt;pre_start&lt;/code&gt; 会以临时容器形式在服务容器启动前运行，非零退出码会阻止服务启动。&lt;/p&gt;
&lt;p&gt;这意味着，过去很多只能写进镜像入口脚本、Makefile 或 CI/CD 脚本里的初始化动作，现在可以被声明在 &lt;code&gt;compose.yaml&lt;/code&gt; 中：等待依赖、初始化共享卷、生成配置、执行轻量迁移、预热本地开发数据，都可以变成 Compose 编排的一部分。对于把 Docker Compose 当作本地开发、集成测试和小规模部署工具的团队来说，这次更新比普通补丁版本更有工程价值。&lt;/p&gt;
&lt;h2 id="版本与来源核验"&gt;&lt;a href="#%e7%89%88%e6%9c%ac%e4%b8%8e%e6%9d%a5%e6%ba%90%e6%a0%b8%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;版本与来源核验
&lt;/h2&gt;&lt;p&gt;本文基于多源官方资料整理，而不是复制单篇网文：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;版本&lt;/th&gt;
					&lt;th style="text-align: right"&gt;发布时间&lt;/th&gt;
					&lt;th&gt;关键信息&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Docker Compose &lt;code&gt;v5.3.0&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;2026-07-02&lt;/td&gt;
					&lt;td&gt;引入原生 init containers / &lt;code&gt;pre_start&lt;/code&gt; 支持，包含 OCI token transport、&lt;code&gt;run&lt;/code&gt; 事件作用域等修复&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Docker Compose &lt;code&gt;v5.3.1&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;2026-07-07&lt;/td&gt;
					&lt;td&gt;最新补丁版本，主要包含 CI hardening、依赖升级与 Docker CLI &lt;code&gt;29.6.1&lt;/code&gt; 等依赖更新&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compose Spec&lt;/td&gt;
					&lt;td style="text-align: right"&gt;持续维护&lt;/td&gt;
					&lt;td&gt;定义 &lt;code&gt;pre_start&lt;/code&gt;：在服务容器启动前按顺序运行初始化容器，全部退出 &lt;code&gt;0&lt;/code&gt; 后才启动服务&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因此，生产或团队模板中如果准备试用 &lt;code&gt;pre_start&lt;/code&gt;，建议直接升级到 &lt;code&gt;v5.3.1&lt;/code&gt; 或后续更高版本，而不是停留在刚发布的 &lt;code&gt;v5.3.0&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="pre_start-到底解决什么问题"&gt;&lt;a href="#pre_start-%e5%88%b0%e5%ba%95%e8%a7%a3%e5%86%b3%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;pre_start 到底解决什么问题
&lt;/h2&gt;&lt;p&gt;传统 Compose 项目里，初始化逻辑通常散落在几个位置：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;写进业务镜像的 &lt;code&gt;entrypoint.sh&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;docker compose up&lt;/code&gt; 前手动执行脚本；&lt;/li&gt;
&lt;li&gt;在 CI 中先跑 &lt;code&gt;docker compose run --rm migrate&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;depends_on.condition: service_healthy&lt;/code&gt; 等待依赖服务健康后再启动主服务。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些做法都能工作，但都有缺点。入口脚本会把“容器启动”与“环境初始化”强耦合，脚本失败时排查困难；CI 前置命令容易和开发者本地流程不一致；&lt;code&gt;depends_on&lt;/code&gt; 更偏向依赖顺序和健康检查，并不负责在主容器启动前执行一次性任务。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pre_start&lt;/code&gt; 的定位更像 Kubernetes 的 Init Container：它不是在主容器内部执行命令，而是创建一个短生命周期的临时容器。这个临时容器可以使用自己的镜像，也可以默认继承服务镜像；它与服务加入相同网络，并共享服务声明的卷挂载。只有这些步骤按声明顺序全部成功退出后，服务容器才会真正启动。&lt;/p&gt;
&lt;h2 id="一个最小可用示例"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e6%9c%80%e5%b0%8f%e5%8f%af%e7%94%a8%e7%a4%ba%e4%be%8b" class="header-anchor"&gt;&lt;/a&gt;一个最小可用示例
&lt;/h2&gt;&lt;p&gt;假设 Web 服务启动前需要在共享卷里生成配置文件，可以写成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx:alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;web-conf:/etc/nginx/conf.d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pre_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;-&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sh -c &amp;#39;cat &amp;gt; /etc/nginx/conf.d/default.conf &amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; server { listen 80; location / { return 200 &amp;#34;ok\\n&amp;#34;; } }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; EOF&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8080:80&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;web-conf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里的关键点是：&lt;code&gt;pre_start&lt;/code&gt; 容器先把配置写入 &lt;code&gt;web-conf&lt;/code&gt;，主服务随后挂载同一个卷并读取配置。如果初始化命令退出非零，&lt;code&gt;web&lt;/code&gt; 不会继续启动，这比“主容器启动后才发现配置缺失”更早暴露问题。&lt;/p&gt;
&lt;p&gt;如果初始化逻辑需要和业务镜像使用同一套工具链，也可以省略 &lt;code&gt;image&lt;/code&gt;，让 hook 默认使用父服务镜像：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./start-api&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;app-data:/app/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pre_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./bin/prepare-runtime-cache --output /app/data/cache.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app-data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;官方 PR 中的端到端测试也覆盖了这类场景：当 hook 未指定镜像时，会回退到服务自身构建出的镜像。&lt;/p&gt;
&lt;h2 id="运行语义不要把它当成万能任务调度器"&gt;&lt;a href="#%e8%bf%90%e8%a1%8c%e8%af%ad%e4%b9%89%e4%b8%8d%e8%a6%81%e6%8a%8a%e5%ae%83%e5%bd%93%e6%88%90%e4%b8%87%e8%83%bd%e4%bb%bb%e5%8a%a1%e8%b0%83%e5%ba%a6%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;运行语义：不要把它当成万能任务调度器
&lt;/h2&gt;&lt;p&gt;从 Docker Compose PR 的说明看，当前实现有几个重要边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pre_start&lt;/code&gt; 当前按“每个服务一次”运行，而不是每个副本都运行；&lt;/li&gt;
&lt;li&gt;仅当没有副本已经在运行时触发，例如首次 &lt;code&gt;up&lt;/code&gt;、&lt;code&gt;--force-recreate&lt;/code&gt; 或 Compose 规范发生变化；&lt;/li&gt;
&lt;li&gt;普通 scale up 不会重新触发已有服务的 &lt;code&gt;pre_start&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;per_replica: true&lt;/code&gt; 在当前 Compose 实现中会被提前拒绝；&lt;/li&gt;
&lt;li&gt;hook 非零退出会阻止服务以及依赖它的服务继续启动；&lt;/li&gt;
&lt;li&gt;多个 &lt;code&gt;pre_start&lt;/code&gt; 步骤会按声明顺序执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几个规则决定了它适合“服务级别的一次性准备”，不适合做每个副本都必须执行的注册、预热或本地临时目录初始化。如果你的任务必须随副本扩容逐个执行，现阶段仍应使用应用自身启动逻辑、编排平台能力，或等待 Compose 后续支持完整的 &lt;code&gt;per_replica&lt;/code&gt; 语义。&lt;/p&gt;
&lt;h2 id="适合落地的场景"&gt;&lt;a href="#%e9%80%82%e5%90%88%e8%90%bd%e5%9c%b0%e7%9a%84%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;适合落地的场景
&lt;/h2&gt;&lt;h3 id="1-本地开发数据库初始化"&gt;&lt;a href="#1-%e6%9c%ac%e5%9c%b0%e5%bc%80%e5%8f%91%e6%95%b0%e6%8d%ae%e5%ba%93%e5%88%9d%e5%a7%8b%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;1. 本地开发数据库初始化
&lt;/h3&gt;&lt;p&gt;在开发环境中，很多团队会把数据库、缓存、对象存储模拟器和应用服务放进同一个 Compose 项目。&lt;code&gt;pre_start&lt;/code&gt; 可以用于生成测试数据、写入默认配置，或者确认迁移脚本已经运行。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;service_healthy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pre_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sh -c &amp;#39;echo &amp;#34;seed-ready&amp;#34; &amp;gt; /shared/state.txt&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;APP_ENV=local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;app-shared:/shared&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres:17&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CMD-SHELL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pg_isready -U postgres&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;3s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app-shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里仍然建议保留数据库健康检查。&lt;code&gt;pre_start&lt;/code&gt; 负责“服务启动前做什么”，&lt;code&gt;depends_on&lt;/code&gt; 和 &lt;code&gt;healthcheck&lt;/code&gt; 负责“依赖是否已经可用”，二者不是替代关系。&lt;/p&gt;
&lt;h3 id="2-集成测试中的夹具准备"&gt;&lt;a href="#2-%e9%9b%86%e6%88%90%e6%b5%8b%e8%af%95%e4%b8%ad%e7%9a%84%e5%a4%b9%e5%85%b7%e5%87%86%e5%a4%87" class="header-anchor"&gt;&lt;/a&gt;2. 集成测试中的夹具准备
&lt;/h3&gt;&lt;p&gt;CI 里经常需要启动一组服务进行端到端测试。把夹具准备放在 &lt;code&gt;pre_start&lt;/code&gt; 中，可以让开发者本地执行和 CI 执行保持一致：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose up --build --abort-on-container-exit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果初始化失败，Compose 会在服务启动阶段就返回错误，日志也更集中。相比在 CI YAML 中堆多段 shell，Compose 文件成为更清晰的环境说明书。&lt;/p&gt;
&lt;h3 id="3-共享卷中的配置或证书生成"&gt;&lt;a href="#3-%e5%85%b1%e4%ba%ab%e5%8d%b7%e4%b8%ad%e7%9a%84%e9%85%8d%e7%bd%ae%e6%88%96%e8%af%81%e4%b9%a6%e7%94%9f%e6%88%90" class="header-anchor"&gt;&lt;/a&gt;3. 共享卷中的配置或证书生成
&lt;/h3&gt;&lt;p&gt;对一些内部工具、反向代理或 demo 环境，可以用 &lt;code&gt;pre_start&lt;/code&gt; 生成临时证书、模板配置、静态资源索引等，再让主服务读取。注意这只适合非敏感或临时材料；生产密钥仍应交给 Secret 管理系统，不要把长期敏感信息写进普通卷。&lt;/p&gt;
&lt;h2 id="升级与验证建议"&gt;&lt;a href="#%e5%8d%87%e7%ba%a7%e4%b8%8e%e9%aa%8c%e8%af%81%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;升级与验证建议
&lt;/h2&gt;&lt;p&gt;升级前先确认当前版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;升级后重点检查三类流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首次 &lt;code&gt;docker compose up&lt;/code&gt;：确认 hook 顺序、日志和失败行为；&lt;/li&gt;
&lt;li&gt;再次 &lt;code&gt;docker compose up&lt;/code&gt;：确认已运行服务不会无意义重复执行初始化；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker compose up --force-recreate&lt;/code&gt; 或修改 &lt;code&gt;compose.yaml&lt;/code&gt;：确认变更后会按预期重新执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;还可以专门构造一个失败用例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sleep 300&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pre_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sh -c &amp;#39;exit 17&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行 &lt;code&gt;docker compose up demo&lt;/code&gt; 后，预期主服务不会启动。这类“失败路径测试”比只验证成功路径更重要，因为 &lt;code&gt;pre_start&lt;/code&gt; 的核心价值之一正是把启动前置条件变成可失败、可观测的编排步骤。&lt;/p&gt;
&lt;h2 id="注意事项"&gt;&lt;a href="#%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9" class="header-anchor"&gt;&lt;/a&gt;注意事项
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;不要把耗时很长、需要重试队列或人工审批的任务放进 &lt;code&gt;pre_start&lt;/code&gt;，否则会阻塞服务启动；&lt;/li&gt;
&lt;li&gt;不要假设 scale up 会为新增副本重新执行初始化；&lt;/li&gt;
&lt;li&gt;不要把它当作数据库迁移的唯一保护，生产迁移仍应有幂等、锁和回滚策略；&lt;/li&gt;
&lt;li&gt;对共享卷写入要保持幂等，避免 &lt;code&gt;--force-recreate&lt;/code&gt; 后重复生成脏数据；&lt;/li&gt;
&lt;li&gt;团队模板中应固定最低 Compose 版本，否则旧环境会因为不认识字段而失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;Docker Compose &lt;code&gt;v5.3.x&lt;/code&gt; 的 &lt;code&gt;pre_start&lt;/code&gt; 支持，让 Compose 在本地编排和集成测试场景中补上了一个长期缺口：服务启动前的初始化任务终于可以以声明式方式进入 &lt;code&gt;compose.yaml&lt;/code&gt;。它不是 Kubernetes 的完整替代品，也不是通用任务调度系统，但非常适合处理服务级别、短生命周期、可幂等的一次性准备动作。&lt;/p&gt;
&lt;p&gt;对工程团队来说，推荐的落地路径是：先升级到 &lt;code&gt;v5.3.1&lt;/code&gt; 或更高版本，在非生产 Compose 项目中选择一个低风险初始化步骤试点；随后把成功路径、失败路径、重复 &lt;code&gt;up&lt;/code&gt;、&lt;code&gt;--force-recreate&lt;/code&gt; 和扩容行为都纳入验证。只要边界理解清楚，&lt;code&gt;pre_start&lt;/code&gt; 会让 Compose 项目更可读、更一致，也更接近现代容器编排的生命周期模型。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Docker Compose GitHub Release：v5.3.1（2026-07-07），https://github.com/docker/compose/releases/tag/v5.3.1&lt;/li&gt;
&lt;li&gt;Docker Compose GitHub Release：v5.3.0（2026-07-02），https://github.com/docker/compose/releases/tag/v5.3.0&lt;/li&gt;
&lt;li&gt;Docker Compose PR #13862：Pre start init containers，https://github.com/docker/compose/pull/13862&lt;/li&gt;
&lt;li&gt;Compose Specification：&lt;code&gt;pre_start&lt;/code&gt; lifecycle hook，https://github.com/compose-spec/compose-spec/blob/main/spec.md#pre_start&lt;/li&gt;
&lt;li&gt;Docker Docs：Compose release notes，https://docs.docker.com/compose/releases/release-notes/&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Debian12 安装Docker及Docker-compose</title><link>https://blog.waihost.com/posts/debian12-install-docker-compose/</link><pubDate>Sun, 17 Dec 2023 18:10:00 +0800</pubDate><guid>https://blog.waihost.com/posts/debian12-install-docker-compose/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/debian12-install-docker-compose.svg" alt="Featured image of post Debian12 安装Docker及Docker-compose" /&gt;&lt;p&gt;在 Debian 12 上安装最新版的 Docker 和 Docker Compose 可以按照以下步骤进行。以下指南将带您通过安装 Docker Engine、配置 Docker 官方仓库以及安装 Docker Compose 的最新版本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一、更新系统&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先，确保您的系统包索引是最新的，并安装必要的依赖包。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt upgrade -y
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt install -y ca-certificates curl gnupg lsb-release
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;二、添加 Docker 官方的 GPG 密钥&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了确保下载的软件包的安全性，需要添加 Docker 的官方 GPG 密钥。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;mkdir&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;keyrings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;fsSL&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;debian&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gpg&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;gpg&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;dearmor&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;keyrings&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gpg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;三、设置 Docker 仓库&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;将 Docker 的官方仓库添加到 APT 源中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;deb [arch=&amp;amp;#36;(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian &amp;amp;#36;(lsb_release -cs) stable&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;tee&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;四、安装 Docker Engine、Docker CLI 和 containerd&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先，更新包索引，然后安装 Docker Engine 及其组件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：docker-compose-plugin 是 Docker 官方提供的 Compose 插件，可以作为 Docker CLI 的一部分使用。如果您需要独立的 Docker Compose，可参考后续步骤。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;五、启动并启用 Docker 服务&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;确保 Docker 服务已启动并设置为开机自启。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemctl start docker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemctl enable docker
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;六、验证 Docker 安装&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过运行一个测试容器来验证 Docker 是否正确安装。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker run hello-world
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果看到类似 “Hello from Docker!” 的输出，说明 Docker 已成功安装。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;七、安装 Docker Compose 最新版&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;虽然前面的步骤已经安装了 docker-compose-plugin，您可能仍然希望安装独立的 Docker Compose，以便使用 docker-compose 命令。以下是安装最新版 Docker Compose 的步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;获取最新的 Docker Compose 版本号&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;访问 &lt;a class="link" href="https://github.com/docker/compose/releases" target="_blank" rel="noopener"
 &gt;Docker Compose 的 GitHub 发布页面&lt;/a&gt; 获取最新版本号。例如，假设最新版本是 v2.20.2。&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;&lt;strong&gt;下载 Docker Compose 二进制文件&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;使用 curl 下载适用于 Linux 的 Docker Compose 二进制文件，并将其移动到 /usr/local/bin 目录。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DOCKER_COMPOSE_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#36;(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep tag_name | cut -d &amp;#39;&amp;#34;&amp;#39; -f 4)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/docker/compose/releases/download/&amp;amp;#36;{DOCKER_COMPOSE_VERSION}/docker-compose-&amp;amp;#36;(uname -s)-&amp;amp;#36;(uname -m)&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;compose&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="3"&gt;
&lt;li&gt;&lt;strong&gt;赋予可执行权限&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo chmod +x /usr/local/bin/docker-compose
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;&lt;strong&gt;创建符号链接（可选）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了便于使用 docker-compose 命令，可以创建一个符号链接到 /usr/bin。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;&lt;strong&gt;验证 Docker Compose 安装&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker-compose --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;您应该会看到类似 docker-compose version 2.20.2, build &amp;hellip; 的输出，确认安装成功。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;八、配置当前用户使用 Docker（可选）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了避免每次运行 Docker 命令时都需要使用 sudo，可以将当前用户添加到 docker 组中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo usermod -aG docker &amp;amp;#36;USER
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：执行上述命令后，您需要重新登录才能使组更改生效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;九、测试无 sudo 的 Docker 命令&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;重新登录后，运行以下命令以确认是否可以不使用 sudo 运行 Docker 命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run hello-world
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果看到 “Hello from Docker!” 的输出，说明配置成功。&lt;/p&gt;</description></item></channel></rss>