跳到正文
Docker:治「我机器上能跑」这桩老案子
Docker:治「我机器上能跑」这桩老案子

Docker:治「我机器上能跑」这桩老案子

装服务装到后面,系统里 Python、Java 各种库混在一起,依赖打架。这篇不讲 Docker 命令大全,讲它到底在解决什么——以及为什么我说它是 venv 那条线的延续。

速读#

Docker 缓解的是“我机器上能跑”背后的环境不一致,本质上是把 venv 的隔离思路扩到应用、运行时和系统库。它不会把内核、CPU 架构、外部数据和网络一起冻住,所以可复现仍有边界;端口绑在哪里,也直接决定服务暴露到哪一层网络。

问题从哪来#

装服务装到后面,系统里 Python、Java、各种库混在一起。图省事把包都 pip install 到全局,两个项目依赖同一库的不同版本时直接打架——升级一个弄坏另一个;更坑的是坏的那个当时不报错,过几天重跑才发现。

根因一句话:全局环境谁都能往里扔,出事难追是谁破坏的。

Docker 把应用、语言运行时和系统库打进镜像,在兼容的容器运行时、CPU 架构和 Linux 内核上减少环境差异。它不能保证任何机器上结果都完全一样:浮动标签会更新,amd64arm64 可能走不同镜像,挂载数据和外部服务也仍在镜像之外。它解决的是很大一部分环境问题,不是给部署盖一枚“绝对可复现”的章。

venv 的放大版#

venvDocker
隔离什么Python 依赖应用、系统库、运行时与进程空间
治什么给 A 项目装依赖,弄坏了 B 项目我机器能跑、你机器不行

逻辑同一条:会在多台机器 / 多个项目间流动的东西,第一件事是隔离。 从 venv 到 Docker,粒度从 Python 依赖扩大到用户空间运行环境。venv 管不了 libpq、OpenSSL 等系统库,也管不了宿主发行版差异;Linux 容器把自己的用户空间放进镜像,但仍共享宿主内核。密码、域名和连接串这类运行时配置不该烘进镜像,而应在启动时注入。环境不会自己保持干净——不主动隔离,混乱是默认结局;把秘密也一并打包,则只是把另一种混乱封存起来。

镜像和容器:用类与对象理解#

概念类比说明
镜像 Image类 / 模板由不可变层组成的打包结果,含应用、依赖与系统库
容器 Container对象 / 实例镜像加一层可写层后运行出的隔离进程

一个镜像可以跑出多个容器,就像一个类 new 出多个对象。把熟悉的 OOP 概念迁过来,Docker 就不玄了——这也是「底层是通的」的一次落地。

顺着这个类比可以记住:镜像改了要重建并替换容器。在容器里手改文件,只改了这个容器的可写层,删除后就消失;应用与依赖变更应进入 Dockerfile 重新构建,业务数据则挂到卷或外部存储。类与对象只是入门记忆法,容器还有网络、挂载、namespace 和 cgroup,不能继续拿 OOP 类比推导安全边界。

容器不是轻量虚拟机#

我最初把 Docker 理解成「启动快一点的虚拟机」,这个心智模型会误导判断:

虚拟机容器
隔离层级完整客机 OS + 虚拟硬件进程级,共享宿主内核
启动通常较慢通常更快
开销每台 VM 有独立客机内核与用户空间共享宿主内核,额外开销通常更小

容器本质上是一组受 namespace、cgroup 等机制约束的进程,不是缩小的机器。 容器的主进程退出后,容器生命周期就结束。实践上我按“一个可独立部署和替换的职责一个容器”来拆,而不是机械要求只能存在一个 Unix 进程;应用派生 worker、使用轻量 init 回收僵尸进程都很正常,把 Nginx、数据库和业务应用绑成一个不可分割单元才是问题。

共享内核同时也是代价:容器隔离通常弱于虚拟机,内核漏洞、过大的 Linux capabilities、特权容器或挂载 Docker socket 都可能把影响扩大到宿主机。我的场景跑的是自己的服务,配合非 root 用户、最小权限和及时更新后可以接受;隔离互不信任的代码时,我会选择独立虚拟机或 gVisor、Kata 这类更强边界,而不是把普通 Docker 容器当沙箱。快和轻不是白来的,知道换走了什么才用得踏实。

端口绑哪,直接决定攻击面#

Terminal window
docker run -d -p 8080:80 --name web nginx

-p 8080:80 把宿主机 8080 映射到容器 80。

容易忽略的一点:不写宿主地址时,Docker 默认把发布端口绑定到所有宿主地址;只要云防火墙、宿主路由和上游网络允许,它就可能从公网到达。后面部署时我改成 127.0.0.1:8080:80,只让宿主机上的 Nginx 访问容器端口,这不是语法偏好,而是在入口层收缩暴露面。

绑定方式效果
-p 8080:80发布到所有宿主地址,是否公网可达还取决于外层网络策略
-p 127.0.0.1:8080:80仅 IPv4 回环,适合宿主机反代

更隐蔽的一层是 Docker 会按所用后端管理 iptables 或 nftables 规则,发布流量与 UFW 的处理顺序并不等同于普通宿主进程。很多默认组合下,UFW 的 deny incoming 不能按直觉拦住 Docker 已发布的端口;版本、后端和自定义规则又会改变结果。因此我不猜防火墙是否兜底,而是先绑定回环,再用 docker port websudo ss -ltnp 检查监听地址,并从另一台机器实际探测。需要对外发布时,再在云防火墙和 Docker 推荐的过滤链上显式收来源。

端口绑哪,直接决定攻击面。 命令会了只是起点;绑法选错,等于自己把门开大。

这篇留下的判断就两条:环境差异要主动收敛,别等依赖打架才处理;发布端口前先明确谁需要访问,有宿主机反代时优先绑定回环,再用实际网络探测验证暴露面。

版权许可

CC BY-NC-SA 4.0 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

相关文章

s1oopX

登录 s1oopX