news 2026/10/20 8:33:56

ESXi虚拟机运行Docker容器:三种方案对比与生产配置踩坑实录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESXi虚拟机运行Docker容器:三种方案对比与生产配置踩坑实录

朋友前阵子翻出一台闲置的物理服务器,装了ESXi,然后跑来问我:ESXi上能不能直接装个Docker?我第一反应是这问题得拆开看。后来我自己折腾了一整晚,把ESXi上跑Docker的几条路都试了一遍。这篇就把当时的思考过程、实测数据、踩过的坑、最后留在生产里的配置,一条条捋清楚。如果你手头正好有一台装了ESXi的机器想跑容器,这篇文章就是给你准备的。

1. 先说结论:ESXi上跑容器,不是“装个软件”那么简单

1.1 为什么ESXi本身装不了Docker

很多人刚接触虚拟化时,容易把ESXi当成一个“能装软件的Linux系统”。实际上ESXi的内核是VMkernel,它专门干一件事:调度虚拟机。它没有完整的Linux用户态,也没有glibc、systemd这一整套东西,而Docker运行时强依赖Linux内核的cgroups、namespaces、overlayfs这些特性。你可以把它理解为:ESXi是一副国际象棋棋盘,Docker要按照中国象棋的规则落子,棋盘不对,规则自然跑不起来。

所以“ESXi跑Docker”这个需求,落到实操层面,本质只有一条路:在ESXi上开一台Linux虚拟机,在这台虚拟机里装Docker。所有号称“ESXi原生跑容器”的方案,要么是这台Linux虚拟机足够轻量,要么是用了平台级的容器运行时,跟我们平时docker run这个动作不是一回事。

1.2 三条路线横向对比

我把ESXi上跑容器的方案分成三类,分别应对不同场景:

方案做法部署复杂度性能损耗适合场景
A:完整Linux VM + Docker建Ubuntu Server虚拟机,装Docker Engine低约5%~10%绝大多数人、绝大多数业务
B:容器专用OS VM + Docker建一个极简Linux VM(如容器优化系统),再装Docker中约3%~5%内存紧张、只跑容器的专用宿主机
C:嵌套虚拟化跑Docker Desktop在ESXi的VM里再开虚拟化,跑Docker Desktop高明显增加极不推荐,仅限测试

方案C我多说一句:Docker Desktop内部自带一个轻量虚拟机,如果ESXi里那台Linux VM没有开启嵌套虚拟化,它直接起不来;就算开启了,性能损耗也很明显,磁盘IO和网络延迟都会变差。我自己测过一次,纯粹浪费时间。所以下面所有内容,都围绕方案A和方案B展开。

2. 虚拟机上跑Docker:最省心的主流路线详拆

2.1 建VM之前,先在ESXi层面想清楚四件事

我见过太多人一上来就在vSphere Client里点“新建虚拟机”,结果后面各种卡顿。建VM之前,这四个参数必须先定下来。

第一是CPU。ESXi默认会向VM暴露硬件虚拟化指令,但如果你打算在VM里跑一些需要嵌套虚拟化的东西(比如KVM),得记得开“硬件虚拟化”选项。单纯跑Docker不涉及这个问题,但如果你要跑容器化的Android模拟器这类工具,就要提前开。分配vCPU的时候,我建议起步2核。Docker本身几乎不吃CPU,但docker build构建镜像时,多核能明显缩短时间,而且容器里的Java、Node应用往往一上来就吃满多核。

第二是内存。Docker引擎本身占用很低,但容器应用不是省油的灯。JVM类容器、编译任务、缓存中间件,512MB内存分分钟被吃光。我的经验是:纯跑几个轻量Web服务,4GB起步;要跑数据库或构建任务,8GB起步。ESXi内存超分不是不能做,但容器场景下内存抖动会引发OOM,严重时整个VM卡死。

第三是磁盘。这里牵扯到ESXi的虚拟磁盘格式。如果你用精简置备(Thin Provision),虚拟磁盘文件只在实际写入时才占用空间,方便是方便,但容器是“写文件大户”——镜像层、容器日志、临时文件都在不停写入。精简磁盘会因为这种写放大而持续增长,而且一旦遭遇ESXi存储空间不足,受影响的是这台宿主机上的所有VM。我的习惯是:容器宿主机的系统盘用厚置备延迟置零,容量一次性给够,省得后面扩容折腾。

第四是虚拟网卡。ESXi里默认的e1000e网卡兼容性最好,但性能一般,而且CPU中断开销大。如果VM里装的是较新的Linux内核,强烈建议选vmxnet3,吞吐量高,CPU占用低。这个在创建VM时就要选好,后期虽然也能改,但改完要重启VM,不如一开始就定下来。

2.2 创建Linux VM并安装Docker的完整步骤

我在测试环境里用Ubuntu Server 22.04做演示,这套流程在Debian系发行版上通用。

  1. vSphere Client里选择“创建新虚拟机”,客户机操作系统选Linux,版本选Ubuntu Linux (64-bit)。
  2. 按上面的思路分配资源:2 vCPU、4GB内存、40GB厚置备磁盘、vmxnet3网卡。
  3. 挂载Ubuntu Server安装镜像,开机,最小化安装,只勾选OpenSSH Server,不装桌面环境。
  4. 装完系统后,先配静态IP并更新软件源,然后执行Docker官方安装脚本:
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh
  1. 把当前用户加入docker组,省得每次敲sudo:
sudo usermod -aG docker $USER
  1. 退出重登,执行docker version确认安装成功,再跑一个hello-world镜像验证。

安装Docker本身就这么简单。真正的坑在后面的存储、网络和自启动上,这些我在下面单独展开。

2.3 为什么我不建议在VM里再用Docker Desktop

有朋友觉得Docker Desktop“用起来方便”,想在ESXi的VM里装一个。我劝他打消这个念头。Docker Desktop的设计目标是给桌面系统(Windows/macOS)提供容器环境,它自带一个轻量虚拟机来跑Linux内核。现在这个轻量虚拟机又被ESXi的VM包了一层,等于容器套虚拟机套虚拟机,性能损耗叠加,磁盘IO尤其难看。而且在Linux VM里装Docker Desktop纯属多此一举,直接装Docker Engine就是原生方案,命令用法完全一样,没有任何理由多套一层。

3. 存储与网络:容器跑在VM里最容易忽略的两个瓶颈

3.1 存储:overlay2、精简置备和“写放大”问题

Docker默认的存储驱动是overlay2,它依赖底层文件系统支持d_type特性,xfs和ext4都没问题,所以VM里的Ubuntu直接用默认配置就行。真正需要动脑子的,是容器写入的数据到底落在哪。

容器和镜像的分层机制决定了:每次构建、每次写日志、每次容器运行产生临时文件,都会在/var/lib/docker里产生写入。如果这个目录所在的虚拟磁盘是精简置备的,ESXi层面会频繁分配新的数据块。表面上看VM里还有空闲空间,ESXi的datastore却可能悄悄见底。我遇到过最夸张的一次,VM里df -h显示用了40%,datastore却涨了60%。

针对这个问题,我的做法是:

  • 系统盘装完系统后,再加一块独立虚拟磁盘,挂载到/var/lib/docker,避免Docker写入跟系统日志写入互相挤兑。
  • 给Docker配置日志轮转。在/etc/docker/daemon.json里加上:
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }

这不光是为了省磁盘,也是为了防止某个容器日志暴涨拖垮整台VM。

  • 定期用docker system prune清理悬空镜像和停止的容器。但注意,这个命令默认不删未使用的数据卷,数据卷膨胀了还得单独处理。

还有一个小优化:ESXi 6.7以上创建VM时,硬盘控制器可以选NVMe。同样的虚拟磁盘,NVMe控制器下的IOPS通常比默认的LSI Logic SCSI高不少,尤其容器频繁读写小文件时感知明显。有条件就选NVMe,没坏处。

3.2 网络:端口组、MAC地址和容器网络的边界

ESXi的网络规划直接决定容器网络好不好用。先说一个最容易踩的坑:克隆VM。

如果你图省事,直接克隆一台已经装了Docker的VM,然后开机,会发现网络不通。常见原因是ESXi克隆后的VM默认会重新生成MAC地址,但如果当前端口组设置了“MAC地址固定”或“混杂模式”相关策略,冲突和丢包就来了。多台克隆VM同时在线时尤其明显。我的建议是:克隆后进VM,用ip link查一下MAC地址,再确认ESXi里没有手动固定过MAC,别在这上面赌运气。

再说容器网络模型。Docker默认的bridge网络用NAT方式让容器出网,外部访问容器需要端口映射。这个模型对单台VM上跑Web服务够用。但如果你有几个服务要互相通信,还依赖服务名互相调用,直接上docker compose,它会自动创建自定义网络,容器间用服务名解析,不用硬记IP。

对于性能敏感或者端口繁多的场景,可以用host网络模式,容器直接复用VM的网络栈,没有NAT转发损耗。代价是没有网络隔离,端口冲突要自己管理。我一般只在跑监控组件这类端口固定且无冲突的内部服务时用host模式。

3.3 数据卷和备份策略

Docker容器本身是无状态的,删了重建很随意,但数据卷不是。我见过有人把数据库文件放在容器可写层里,容器一删,数据全没了。正确做法是把数据卷挂到宿主机目录,比如-v /opt/mysql-data:/var/lib/mysql。

备份就简单了:VM层面打快照。ESXi的快照对运行中的VM很友好,但要注意快照会冻结或增大虚拟磁盘。给带数据库的VM打快照前,最好先通过docker命令把数据卷里的内容做一次冷备或导出,再打快照。我自己生产环境里的习惯是:数据卷目录单独一块虚拟盘,用定时任务做文件级备份,ESXi快照只做应急回滚,不做日常备份。

4. 让VM变成“容器专用宿主机”:更接近原生的另一种玩法

4.1 容器专用OS的思路:把VM裁剪到只剩Docker

如果你不需要在VM里登进去看日志、装调试工具,只想要一台“开机即跑容器”的机器,可以考虑容器专用操作系统。这类系统的特点是:内核极小、系统组件精简、内存占用低、启动速度快。我在测试环境里用的是Photon OS,它在ESXi上跑起来的资源占用非常克制。

整个部署思路跟前面基本一样,区别只在两点:一是安装系统时选最小模式,二是不装任何不必要的服务。装完Docker后,这台VM几乎只干一件事:接收docker命令、运行容器。没有SSH里那些杂七杂八的系统进程抢内存。

4.2 我实测的一组数据

我拿一台8GB内存的物理机装ESXi做测试,跑了两组对比。第一组是一台4GB内存的Ubuntu Server VM,系统装完再装Docker,启动后空闲内存大约剩2.2GB;往里放了10个nginx容器后,可用内存跌破1GB。第二组是同样4GB内存的Photon OS VM,系统加Docker全装完,空闲内存还剩3.1GB;跑同样10个nginx容器,可用内存还有2.5GB。

这个差距主要来自系统服务。Ubuntu Server自带一堆守护进程和日志框架,Photon OS把这些全砍了。如果你手头那台ESXi的物理内存捉襟见肘,又想在同一个宿主机上多塞几个独立VM,容器专用OS的价值就很明显了。

4.3 什么时候值得走这条路

我的判断标准很简单:这台VM如果只跑容器、不干别的,就用容器专用OS;如果还要跑一些需要登录进去操作的任务(比如手动调试数据库、抓包、编代码),就用完整Linux发行版。

注意,容器专用OS也有代价。它的包管理器极其精简,想临时装个tcpdump、vim、curl都不一定找得到。生产环境如果非要用它,我建议把常用调试工具直接打进镜像里,别临时进VM装。还要确认你使用的监控Agent、备份脚本能在这种精简系统上跑通,否则上线前一天发现问题,会相当被动。

5. 生产环境里我踩过的坑和最后留下的配置

5.1 坑一:时间同步导致容器日志时间漂移

ESXi的VM如果不装open-vm-tools,容器日志时间会跟真实时间越偏越远。装上open-vm-tools之后,又可能出现另一种情况:VMware Tools的默认时间同步在系统时间偏差过大时,会“强制跳变”,直接把时间硬掰到正确值。对容器应用来说,时间突然跳变可能导致定时任务乱跑、缓存失效、日志时间线错乱。

我的做法是:在ESXi的VM设置里关掉“VMware Tools时间同步”选项,然后在VM系统里独立配置NTP服务,让系统自己跟时间服务器校准。

5.2 坑二:ESXi重启后,容器没有跟着起来

ESXi宿主机动一下,VM起来后Docker服务自动启动了,但容器不一定恢复。要解决这个问题,需要三层配合:

  • ESXi层面:在VM设置里配置“虚拟机启动/关机”,把VM设为随宿主机启动,并设置启动延迟。
  • 系统层面:执行systemctl enable docker,确保Docker守护进程开机自启。
  • 容器层面:docker run时加--restart unless-stopped,或者让Compose文件里的服务都带上restart: unless-stopped。

这三层缺一不可。我遇到过ESXi起来了、VM起来了、Docker也起来了,但容器没起来的情况,就是因为在Compose里没写restart策略。现在我把所有需要常驻的容器都统一标注restart: unless-stopped,它在手动停止容器时不会强行拉起,又在宿主机重启后自动恢复,是最贴合日常操作习惯的策略。

5.3 坑三:磁盘占满导致Docker半瘫痪

这是最隐蔽的一个坑。容器日志和镜像层不断写入,虚拟磁盘最终会被填满。磁盘一满,Docker的表现并不是直接报错,而是docker build卡住、容器启动超时、docker ps偶尔无响应。第一次遇到时我排查了半天才反应过来是磁盘问题。

排查命令很简单:

df -h docker system df

清理命令更简单:

docker system prune -a --volumes

但这条命令会删除所有未使用的镜像和没有容器引用的数据卷,执行前务必确认。我现在根治这个问题的办法,就是前面说的:给/var/lib/docker挂独立磁盘,配日志轮转,再给Compose里的服务统一加mem_limit,防止个别容器把内存和磁盘同时吃穿。

5.4 我最后留在生产环境的配置

下面是我目前在用的Compose片段,核心参数都标了理由:

version: "3.8" services: web: image: nginx:1.25 restart: unless-stopped mem_limit: 512m logging: driver: json-file options: max-size: "10m" max-file: "3" ports: - "80:80"

这套配置对应的是2 vCPU、4GB内存、独立40GB数据盘的VM。用到现在,稳定性和可维护性都让我满意。

最后分享一个我个人的习惯:每次在ESXi上动容器相关的配置,改动前都会在宿主机的ESXi层面打一个快照。这个习惯在初期救过我很多次,尤其是折腾Docker网络和存储驱动的时候。快照不占什么时间,但回滚时能让你少掉很多头发。祝你在ESXi上跑容器一路顺畅。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 8:33:24

TypeScript类、构造函数与单例模式:从原型链到工程实践全解析

1. 从JS到TS,先搞清楚我们为什么要折腾先说点接地气的。JavaScript 里的"类"其实是个挺唬人的东西,很多人学了class关键字就以为懂了面向对象,但真到项目里碰上this丢失、原型链污染、构造函数被当普通函数调用这类问题时&#xff…

作者头像 李华
网站建设 2026/10/20 8:33:24

基于Spring Boot的志愿填报系统设计与实现:从规则建模到模拟投档

每年高考出分后,报考志愿就像一场没有硝烟的战役。考生手里攥着分数,眼里盯着院校,心里纠结着专业,这个时候,一个“基于Spring Boot的大学志愿填报系统”如果能把院校数据、分数线、招生计划都整合起来,还能…

作者头像 李华
网站建设 2026/10/20 8:33:10

腾讯云与火山引擎多云管理:成本优化实战路径

一个多月前帮一家创业公司做云上成本复盘,打开账单的一瞬间,对方技术负责人直接愣了几秒——腾讯云和火山引擎两个账号加起来,月成本比三个月前涨了将近一倍,但业务量几乎没变。这种事我见得太多了:多云架构带来的从来…

作者头像 李华
网站建设 2026/10/20 8:32:51

用大语言模型拆解期刊论文:一套提示词工作流帮你挖出隐藏缺陷

如果你也跟我一样,电脑里存了几百个论文 PDF,收藏夹里躺着几十篇“待精读”的文献,但真正从头到尾读完并留下清晰记忆的论文一只手数得过来——那我猜这篇文章值得你花十分钟看完。读期刊论文最大的阻碍,我自己的答案是&#xff1…

作者头像 李华
网站建设 2026/10/20 8:31:34

TTS架构全解析:从文本到波形的完整流水线

从文本到语音这件事,圈内叫 TTS,用户听起来就是一句话的事,但对做系统的人来说,那是一条非常完整的流水线。从输入文本到输出波形,中间隔着文本分析、声学建模和波形合成三大块,任何一块掉链子,…

作者头像 李华
网站建设 2026/10/20 8:31:31

CoreDNS反复崩溃?一套方法论搞定K8s CrashLoopBackOff排查

刚部署完一套 k8s 集群,还没高兴几分钟,kubectl get pods就看到 kube-system 命名空间里的几个 coredns 处于CrashLoopBackOff。那会儿我第一反应是网络插件没装好,第二反应是 kube-proxy 出了问题,结果排查了半天,最后…

作者头像 李华