<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Docker on Nekonull's Garden</title><link>https://nekonull.me/tags/docker/</link><description>Recent content in Docker on Nekonull's Garden</description><generator>Hugo</generator><language>zh-CN</language><copyright>CC-BY-SA-4.0</copyright><lastBuildDate>Sun, 03 Aug 2025 22:01:12 +0800</lastBuildDate><atom:link href="https://nekonull.me/tags/docker/index.xml" rel="self" type="application/rss+xml"/><item><title>为什么 JDK 的镜像比 JRE 镜像小？</title><link>https://nekonull.me/share/jdk-jre-image-size-diff/</link><pubDate>Sun, 03 Aug 2025 22:01:12 +0800</pubDate><guid>https://nekonull.me/share/jdk-jre-image-size-diff/</guid><description>&lt;p&gt;TLDR：虽然 JDK 镜像内容比 JRE 镜像多，但 JDK 镜像的压缩比更高，导致最后镜像大小反而 JDK 比 JRE 小；原因是 JDK 镜像主要用于 CI/CD 流水线，对性能和耗时要求不太高，所以可以用更高的压缩比；但 JRE 镜像主要用于线上服务，对性能要求极高，因此基本没有压缩。&lt;/p&gt;
&lt;p&gt;AI 贡献提示：技术探索过程主要由 Claude Code + Kimi K2 完成；文字部分主要为 Gemini Pro 2.5 编写；我仅提供探索方向指导和简单内容编辑。我已在能力范围内确认下述内容的准确性。如发现问题，欢迎留言反馈。&lt;/p&gt;
&lt;p&gt;感谢 &lt;a href="https://github.com/ziqin/ziqin"&gt;@ziqin&lt;/a&gt; 提出了本文的探索问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="缘起一个反常的发现"&gt;缘起：一个反常的发现&lt;a class="td-heading-self-link" href="#%e7%bc%98%e8%b5%b7%e4%b8%80%e4%b8%aa%e5%8f%8d%e5%b8%b8%e7%9a%84%e5%8f%91%e7%8e%b0" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;一位群友在运行 docker image 时发现一个反常的现象：&lt;strong&gt;JDK镜像（111MB）竟然比JRE镜像（139MB）小了整整28MB！&lt;/strong&gt;&lt;/p&gt;
&lt;div class="td-code td-code--untitled" id="td-code-284267ac-fence-0" data-td-code data-td-code-auto-id
data-td-language="" data-td-line-count="3"&gt;
&lt;div class="td-code__viewport" id="td-code-284267ac-fence-0-viewport" data-td-code-viewport&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;REPOSITORY TAG IMAGE ID CREATED SIZE
bellsoft/liberica-runtime-container jdk-21-musl 1c9d58aebbdc 2 days ago 111MB
bellsoft/liberica-runtime-container jre-21-musl c59d660b1633 2 days ago 139MB&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;但这感觉上不太合理，因为 JDK 是 JRE 的超集，不仅包含了 JRE 的全部功能，还包含了额外的开发工具（如 &lt;code&gt;javac&lt;/code&gt;, &lt;code&gt;jdb&lt;/code&gt; 等），为什么其镜像反倒还更小呢？这个反直觉的观察结果立刻激起了我们的好奇心。问题提出之时是个工作日的晚上，我并没有太多精力仔细思考，于是我让 Claude Code 来探索这个问题。&lt;/p&gt;
&lt;h2 id="探案之旅层层深入拨开迷雾"&gt;探案之旅：层层深入，拨开迷雾&lt;a class="td-heading-self-link" href="#%e6%8e%a2%e6%a1%88%e4%b9%8b%e6%97%85%e5%b1%82%e5%b1%82%e6%b7%b1%e5%85%a5%e6%8b%a8%e5%bc%80%e8%bf%b7%e9%9b%be" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;我们的调查遵循着从宏观到微观的路径，一步步逼近问题的核心。&lt;/p&gt;
&lt;h3 id="第一站文件系统对比"&gt;第一站：文件系统对比&lt;a class="td-heading-self-link" href="#%e7%ac%ac%e4%b8%80%e7%ab%99%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e5%af%b9%e6%af%94" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;我们首先通过 &lt;code&gt;docker exec&lt;/code&gt; 进入两个正在运行的容器，对比其内部文件系统的差异。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JDK 镜像&lt;/strong&gt;: &lt;code&gt;docker exec jdk-analysis du -sh /usr/lib/jvm/liberica21-lite&lt;/code&gt; -&amp;gt; &lt;strong&gt;98.5M&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JRE 镜像&lt;/strong&gt;: &lt;code&gt;docker exec jre-analysis du -sh /usr/lib/jvm/liberica21-container-jre&lt;/code&gt; -&amp;gt; &lt;strong&gt;125.2M&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;初步结论&lt;/strong&gt;：差异的根源在于Java安装目录本身。JDK镜像使用的是一个名为 &lt;code&gt;liberica21-lite&lt;/code&gt; 的发行版，而JRE镜像使用的是 &lt;code&gt;liberica21-container-jre&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="第二站核心文件对比"&gt;第二站：核心文件对比&lt;a class="td-heading-self-link" href="#%e7%ac%ac%e4%ba%8c%e7%ab%99%e6%a0%b8%e5%bf%83%e6%96%87%e4%bb%b6%e5%af%b9%e6%af%94" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;当我们继续深入，对比两者 &lt;code&gt;lib&lt;/code&gt; 目录下的核心文件 &lt;code&gt;modules&lt;/code&gt; 时，差异变得更加惊人：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JDK &lt;code&gt;modules&lt;/code&gt; 文件&lt;/strong&gt;: 59.3MB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JRE &lt;code&gt;modules&lt;/code&gt; 文件&lt;/strong&gt;: 97.1MB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;modules&lt;/code&gt; 文件是Java模块化系统的核心，存储了所有的运行时模块。&lt;strong&gt;JRE的&lt;code&gt;modules&lt;/code&gt;文件竟然比JDK的大了37.8MB！&lt;/strong&gt; 这几乎完全解释了镜像大小的差异。&lt;/p&gt;
&lt;p&gt;但新的问题随之而来：JDK明明包含了更多的模块（如编译器 &lt;code&gt;jdk.compiler&lt;/code&gt;、文档工具 &lt;code&gt;jdk.javadoc&lt;/code&gt; 等），为什么它的 &lt;code&gt;modules&lt;/code&gt; 文件反而更小？&lt;/p&gt;
&lt;div class="td-table-scroll td-table-scroll--static"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;对比项&lt;/th&gt;
&lt;th scope="col"&gt;JDK (liberica21-lite)&lt;/th&gt;
&lt;th scope="col"&gt;JRE (liberica21-container-jre)&lt;/th&gt;
&lt;th scope="col"&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;69 个&lt;/td&gt;
&lt;td&gt;49 个&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+20 个&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;modules&lt;/code&gt; 文件大小&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;59.3 MB&lt;/td&gt;
&lt;td&gt;97.1 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-37.8 MB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;更多的内容，却占用了更少的空间。这背后一定有更深层次的原因。&lt;/p&gt;
&lt;h3 id="第三站压缩策略对比"&gt;第三站：压缩策略对比&lt;a class="td-heading-self-link" href="#%e7%ac%ac%e4%b8%89%e7%ab%99%e5%8e%8b%e7%bc%a9%e7%ad%96%e7%95%a5%e5%af%b9%e6%af%94" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;为了彻底搞清楚 &lt;code&gt;modules&lt;/code&gt; 文件内部的秘密，我们使用 &lt;code&gt;jimage&lt;/code&gt; 工具将其解压，并分析了其中包含的每一个资源。真相终于水落石出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这并非内容差异，而是压缩策略的根本不同！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;观察以下对比数据：&lt;/p&gt;
&lt;div class="td-table-scroll td-table-scroll--static"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;指标&lt;/th&gt;
&lt;th scope="col"&gt;JDK (liberica21-lite)&lt;/th&gt;
&lt;th scope="col"&gt;JRE (liberica21-container-jre)&lt;/th&gt;
&lt;th scope="col"&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;DEFLATE (zlib)&lt;/td&gt;
&lt;td&gt;DEFLATE (zlib)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jimage&lt;/code&gt;工具确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;压缩级别&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Level 9 (最大)&lt;/td&gt;
&lt;td&gt;Level 0 (无)&lt;/td&gt;
&lt;td&gt;资源分析确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;总资源数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;28,427&lt;/td&gt;
&lt;td&gt;22,133&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jimage list --verbose&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;压缩资源数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;28,044 (98.7%)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 (0.0%)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;未压缩资源数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;383 (1.3%)&lt;/td&gt;
&lt;td&gt;22,133 (100.0%)&lt;/td&gt;
&lt;td&gt;JRE裸奔存储&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;压缩后总大小&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;60.4 MB&lt;/td&gt;
&lt;td&gt;100.5 MB&lt;/td&gt;
&lt;td&gt;JRE因元数据开销反而变大&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;压缩比&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.31 : 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.99 : 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JRE压缩比小于1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;真正的技术根因：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JDK (&lt;code&gt;liberica21-lite&lt;/code&gt;)&lt;/strong&gt;: 使用了&lt;strong&gt;激进的DEFLATE压缩&lt;/strong&gt;（相当于 &lt;code&gt;jlink --compress=2&lt;/code&gt;，级别9）。它将所有工具和资源（包括开发工具、调试信息、所有区域设置）都包含进来，然后用最高效的算法进行压缩，以实现最小的磁盘占用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JRE (&lt;code&gt;liberica21-container-jre&lt;/code&gt;)&lt;/strong&gt;: &lt;strong&gt;完全没有使用压缩&lt;/strong&gt;（相当于 &lt;code&gt;jlink --compress=0&lt;/code&gt;，级别0/STORE模式）。它精心挑选了生产环境必需的运行时子集，但为了追求最快的启动速度和运行时性能，放弃了压缩。（甚至因为压缩元数据，反而引入了额外的空间开销，导致压缩比小于 1。）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="设计哲学为何如此选择"&gt;设计哲学：为何如此选择？&lt;a class="td-heading-self-link" href="#%e8%ae%be%e8%ae%a1%e5%93%b2%e5%ad%a6%e4%b8%ba%e4%bd%95%e5%a6%82%e6%ad%a4%e9%80%89%e6%8b%a9" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;这个看似矛盾的设计，实际上是BellSoft针对不同应用场景的深思熟虑的工程决策。&lt;/p&gt;
&lt;h3 id="jdk-lite-的设计目标---compress2"&gt;JDK &amp;ldquo;lite&amp;rdquo; 的设计目标 (&lt;code&gt;--compress=2&lt;/code&gt;)&lt;a class="td-heading-self-link" href="#jdk-lite-%e7%9a%84%e8%ae%be%e8%ae%a1%e7%9b%ae%e6%a0%87---compress2" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目标场景&lt;/strong&gt;: CI/CD流水线、开发环境、容器构建阶段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化核心&lt;/strong&gt;: &lt;strong&gt;存储和网络效率&lt;/strong&gt;。在这些场景下，镜像的下载速度和存储成本是首要考虑因素。构建时的一次性压缩CPU开销，可以换来后续无数次快速的分发和部署。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略&lt;/strong&gt;: &lt;strong&gt;空间换时间（构建时）&lt;/strong&gt;。牺牲构建时的CPU时间，换取最小的存储空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="jre-container-jre-的设计目标---compress0"&gt;JRE &amp;ldquo;container-jre&amp;rdquo; 的设计目标 (&lt;code&gt;--compress=0&lt;/code&gt;)&lt;a class="td-heading-self-link" href="#jre-container-jre-%e7%9a%84%e8%ae%be%e8%ae%a1%e7%9b%ae%e6%a0%87---compress0" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目标场景&lt;/strong&gt;: 生产环境运行时。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化核心&lt;/strong&gt;: &lt;strong&gt;运行时性能&lt;/strong&gt;。在生产环境中，应用的启动速度、内存占用和CPU效率至关重要。免去解压步骤，意味着更快的类加载、更低的CPU消耗和更少的内存抖动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略&lt;/strong&gt;: &lt;strong&gt;时间换空间（运行时）&lt;/strong&gt;。牺牲磁盘空间，换取运行时的高性能和稳定性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="最终结论一个反直觉的真理"&gt;最终结论：一个反直觉的真理&lt;a class="td-heading-self-link" href="#%e6%9c%80%e7%bb%88%e7%bb%93%e8%ae%ba%e4%b8%80%e4%b8%aa%e5%8f%8d%e7%9b%b4%e8%a7%89%e7%9a%84%e7%9c%9f%e7%90%86" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;我们最初的谜题现在有了清晰的答案：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;JDK镜像小，是因为它用极致的压缩，换取了分发和存储的便利。&lt;/strong&gt;
&lt;strong&gt;JRE镜像大，是因为它用空间，换取了生产环境的极致性能。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;换句话说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;更多内容 + 更强压缩 = 更小的分发体积 (JDK)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更少内容 + 无压缩 = 更大的分发体积 (JRE)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="结语"&gt;结语&lt;a class="td-heading-self-link" href="#%e7%bb%93%e8%af%ad" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;这次从一个简单的 &lt;code&gt;docker images&lt;/code&gt; 命令开始的探案之旅，最终带领我们深入理解了现代 Java 发行版在容器化时代的精妙设计。BellSoft Liberica 的这种差异化策略，并非一个错误，而是一个深刻理解开发者和运维者在不同阶段核心痛点的&lt;strong&gt;高级功能&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它告诉我们，在技术的选择上，没有绝对的“好”与“坏”，只有是否“适合”。理解了这些选择背后的逻辑，我们才能在自己的工作中，做出更明智、更高效的决策。&lt;/p&gt;</description></item><item><title>WSL2, Docker, Virtualbox</title><link>https://nekonull.me/share/wsl2-docker-virtualbox/</link><pubDate>Thu, 29 Oct 2020 02:25:00 +0800</pubDate><guid>https://nekonull.me/share/wsl2-docker-virtualbox/</guid><description>&lt;blockquote&gt;
&lt;p&gt;2021/4/9 更新:
VMWare Player 16+ 无须配置，在检测到 Hyper-V 启动后自动调用 Hyper-V 后端了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Docker 在 Windows 上运行，实际上都是靠一个 Linux 虚拟机。早期 Docker 官方出了一个叫做 Docker Toolbox 的工具，其实就是 VirtualBox 加上一个精简过的只能运行 Docker 的 VM。后来 Docker 放弃了 Virtualbox 路线，转而使用 Windows 内置的 Hyper-V 作为底层 VM。但是 Hyper-V 平台一旦启用，就会导致 Virtualbox / VMWare 等其他虚拟机工具不可用，这一问题直到 VirtualBox 6 之后才算解决，此时 VirtualBox / VMWare 都可以调用 Windows 内置的 Hypervisor API 作为 VM 的执行引擎。&lt;/p&gt;
&lt;p&gt;所以来到 2020 年，在 Windows 10 20H2 上同时运行 WSL 2, Docker 和 VirtualBox 已经几乎是 painless 的了，只需要记住&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WSL 2 底层是一个跑在 Hyper-V 里的 VM&lt;/li&gt;
&lt;li&gt;因为 WSL 2 是一个真正的 VM，Docker 可以直接安装到 WSL 2 中，而不会遇到 WSL 1 的不兼容问题&lt;/li&gt;
&lt;li&gt;VirtualBox 通过 Hypervisor API 调用 Hyper-V 来执行 VM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;步骤也很简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Windows 开启相关功能：Hyper-V, 虚拟机监视器，虚拟机平台&lt;/li&gt;
&lt;li&gt;WSL 迁移到 version 2 (如果喜欢也可以留一个 WSL 1，毕竟比 VM 轻量，日常也基本够用)&lt;/li&gt;
&lt;li&gt;Docker Desktop 安装最新稳定版&lt;/li&gt;
&lt;li&gt;VirtualBox 安装最新版（大版本号 6 及以上）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VirtualBox 因为换了虚拟化后端，已有的暂停的虚拟机，启动的时候会丢失数据，建议关机之后再迁移&lt;/li&gt;
&lt;li&gt;VirtualBox 的 VM 性能可能会下降，64-bit Guest 尤其严重，32-bit Guest 还好（如果用 Hyper-V 作为后端，可以看到 VM 执行界面右下角是一个乌龟图标）&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>