搞懂 Docker 镜像与网络隔离:从底层原理到 Nginx 双层代理架构

搞懂 Docker 镜像与网络隔离:从底层原理到 Nginx 双层代理架构

文章目录

    • 一、 Docker 镜像的本质:它到底是个什么文件?
    • 二、 镜像里包含了操作系统吗?
    • 三、 容器内的 Nginx 与宿主机 Nginx 的关系
    • 四、 工业界实战:Nginx 双层反向代理架构拆解
      • 1. 宿主机 Nginx 负责什么?(入口大堂经理)
      • 2. 容器内 Nginx 负责什么?(干活的员工)
      • 3. 底层关键技术:Docker 如何把 `127.0.0.1:8001` 接到容器里的?
      • 4. 为什么要这样“折腾”二次代理?(优势所在)
    • 总结

在接触 Docker 的过程中,很多刚上手的开发者常常会被一系列概念绕晕:
  • Docker 镜像到底是个啥?它里面装了完整的操作系统吗?
  • 镜像里的 Nginx 和我宿主机上装的 Nginx 会冲突吗?
  • 为什么生产环境中经常在宿主机上跑一个 Nginx,又在 Docker 容器里跑一个 Nginx?流量到底是怎么传过去的?

今天新节就从镜像本质、系统隔离机制、网络映射原理三个维度,把 Docker 的核心逻辑一文彻底讲透。

一、 Docker 镜像的本质:它到底是个什么文件?

简而言之,Docker 镜像(Image)就是一个包含了应用程序及其完整运行环境的“只读打包文件”

如果你把一个 Docker 镜像导出(docker save)并解压,你会发现它的内部结构主要由 3 部分组成:

  1. 分层文件系统(Layers / Tarballs):按照Dockerfile的步骤,一层层叠加的只读压缩包。
  2. 配置文件(JSON):记录镜像的元数据、启动命令(CMD/ENTRYPOINT)和环境变量。
  3. 镜像间依赖与 Hash 校验:实现不同镜像间共同底座的复用(比如两个镜像都依赖 Ubuntu 基础层,本地只需要下载一份)。

💡关键点:镜像本身是绝对只读(Read-Only)的。当我们运行docker run启动容器时,Docker 引擎只是在镜像只读层的最上方,叠加了一层极薄的“容器可写层(Writable Layer)”。你的改动和日志都写在这层,绝不会破坏原始镜像。

二、 镜像里包含了操作系统吗?

包含了,但只包含了一半。

操作系统在结构上可以拆分为:

  • 内核空间(Kernel Space):掌控硬件、调度 CPU 和内存,是操作系统的心脏。
  • 用户空间(User Space):包含/bin/usr、包管理器(apt/yum)、C 语言基础库等外壳文件。

Docker 镜像剥离了内核,它打包的只是完整的“用户空间文件”。

┌──────────────────────────────────────────────────────────┐ │ 容器 (Container) │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 镜像打包的用户空间 (User Space: apt, glibc, code) │ │ │ └─────────────────────────┬──────────────────────────┘ │ └────────────────────────────┼─────────────────────────────┘ ▼ (系统调用 Direct System Calls) ┌──────────────────────────────────────────────────────────┐ │ 宿主机共享内核 (Host Linux Kernel) │ └──────────────────────────────────────────────────────────┘

这也解释了为什么 Docker 容器启动只需要毫秒级:因为它不需要经历载入 Kernel 的开机过程,直接共享并调用宿主机的内核运行进程。

三、 容器内的 Nginx 与宿主机 Nginx 的关系

答案是:没有任何直接关系,它们是两个彻底隔离的独立进程。

  • 文件隔离:宿主机的/etc/nginx/和容器内的/etc/nginx/处于完全不一样的文件系统,互不影响。
  • 进程隔离:它们使用不同的命名空间(Namespace),彼此感知不到对方的存在。
  • 版本隔离:宿主机可以跑 Nginx 1.18,容器里可以跑 Nginx 1.25,完全不会冲突。

它们唯一的“连接点”,只有宿主机的网络端口映射(Port Mapping)

关键对比:宿主机 Nginx vs 容器内 Nginx 配置差异

配置项宿主机 Nginx 配置容器内 Nginx 配置
listen端口80443(暴露给公网)80(仅在容器内部或网桥中暴露)
server_name具体的公网域名(如example.comlocalhost_(无需关注域名)
SSL 证书 (ssl_certificate)(配置公网 Let’s Encrypt / 阿里云证书)(接收的是宿主机解密后的纯 HTTP)
proxy_pass目标指向宿主机映射端口[http://127.0.0.1:8001](http://127.0.0.1:8001)指向静态文件路径,或容器内后端3000端口

💡总结一句话:
容器内的 Nginx不需要知道自己叫什么域名,也不需要关心 HTTPS 证书。它只需要安静地监听自己的80端口,拿到宿主机丢进来的流量,然后去读本地静态文件或者丢给同容器的后端程序即可。

四、 工业界实战:Nginx 双层反向代理架构拆解

在实际生产环境中,我们经常采用“宿主机总网关 Nginx + 容器子服务 Nginx”的双层架构。

当用户访问example.com时,整个流量路径是如何穿透的呢?

[ 浏览器 / 用户 ] │ │ 1. 访问 example.com (通过 DNS 解析到宿主机公网 IP) ▼ ┌─────────────────────────────────────────────────────────────┐ │ 宿主机 (Host Machine) │ │ │ │ ┌─────────────────────────┐ │ │ │ 宿主机 Nginx 进程 │ │ │ │ (监听 80 / 443 端口) │ │ │ └────────────┬────────────┘ │ │ │ 2. 匹配 server_name 和 location │ │ │ 执行 proxy_pass http://127.0.0.1:8001; │ │ ▼ │ │ ┌─────────────────────────┐ │ │ │ 宿主机网络栈 / iptables │ │ │ │ (监听本地 8001 端口) │ │ │ └────────────┬────────────┘ │ │ │ 3. Docker nat 表规则重定向 (DNAT) │ │ │ 将流量转投至容器内网 IP:80 │ └────────────────┼────────────────────────────────────────────┘ │ ▼ (通过 docker0 虚拟网桥) ┌─────────────────────────────────────────────────────────────┐ │ Docker 容器 (Container) │ │ │ │ ┌─────────────────────────┐ │ │ │ 容器内 Nginx 进程 │ 4. 收到请求,返回响应内容 │ │ │ (监听容器内 80 端口) │ ───────────────────────► │ │ └─────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘

1. 宿主机 Nginx 负责什么?(入口大堂经理)

  • 域名路由:把a.com转发给容器 A(127.0.0.1:8001),把b.com转发给容器 B(127.0.0.1:8002),实现单台服务器部署多站点。
  • SSL 卸载:把公网 HTTPS 证书配置在宿主机。解密后,以纯 HTTP 明文发给内部容器,减轻容器负担。

宿主机核心配置:

在宿主机的 Nginx 配置文件(通常位于 /etc/nginx/conf.d/example.conf)中,反向代理的配置大致如下:

server { listen 443 ssl; server_name example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; location / { # 将流量转发给容器映射在宿主机的 8001 端口 proxy_pass http://127.0.0.1:8001; # 传递真实客户端 IP proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; } }

2. 容器内 Nginx 负责什么?(干活的员工)

因为宿主机已经把域名解析和 HTTPS 解密做完了,容器内的 Nginx 配置可以极度精简,它不需要关心证书,也不需要关心真实的域名。

容器内核心配置:

server { # 只需监听容器内部的 80 端口 listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; # 支持前端 SPA 路由 } }

3. 底层关键技术:Docker 如何把127.0.0.1:8001接到容器里的?

你可能会问:“宿主机 Nginx 把请求发给了127.0.0.1:8001,容器里的 Nginx 监听的不是容器内部的80端口吗?它们是怎么接通的?”

这里依靠的是 Docker 启动容器时的-p端口映射机制(如docker run -d -p 8001:80 nginx):

  1. 宿主机建立端口监听
    Docker 引擎启动时,会在宿主机上开启一个名为docker-proxy的用户态守护进程,专门监听宿主机的8001端口(绑定在127.0.0.1:80010.0.0.0:8001)。
  2. Linux 内核层重定向(iptables / NAT)
    Docker 会自动在宿主机的 Linux 内核中写入一条 NAT 防火墙规则(iptables)。当宿主机的 Nginx 连接127.0.0.1:8001时,内核会自动把数据包的目标地址进行目标网络地址转换(DNAT),将目标改写成容器的虚拟内网 IP(例如172.17.0.2:80)。
  3. 网桥转发(docker0)
    数据包通过 Docker 创建的虚拟网桥(docker0)投递进容器内部,容器内的 Nginx 就顺利接收到了这个 HTTP 请求。

4. 为什么要这样“折腾”二次代理?(优势所在)

直接把容器的 80 端口映射到宿主机的 80 端口不就行了吗?为什么还要在宿主机上单独加一层 Nginx?

这种“宿主机总 Gateway + 容器子服务”的架构有几个极大的优势:

  • 多站点共用 80/443 端口:如果你这台服务器上有 3 个域名(a.comb.comc.com),它们分别对应 3 个不同的 Docker 容器。由于公网 80 端口只有一个,只能由宿主机 Nginx 统一接收,然后根据域名转发给不同的容器端口(如800180028003)。
  • 统一管理 SSL 证书(HTTPS 卸载):你只需要把 HTTPS 证书配置在宿主机 Nginx 上。宿主机 Nginx 负责解密 HTTPS 流量,然后用普通的 HTTP 明文传给容器内的 Nginx/应用,容器内部不需要再配置繁琐的证书。
  • 动静分离与安全屏障:宿主机 Nginx 可以做防火墙、限流、防御 DDoS 攻击、全局日志记录,不让恶意请求直接触及内部容器。

💡总结一句话:
宿主机 Nginx 收到请求后,就像一个中转前台,按照配置把请求重新打包发给本地的8001端口;而 Docker 引擎就像内部网关,通过 iptables 规则把8001端口的数据包准确送入容器的80端口中。

总结

搞懂了 Docker 的镜像本质和网络穿透逻辑,你就会发现:

  1. 镜像就是一个包含了用户空间文件与启动配置的只读压缩包
  2. 容器与宿主机隔离,宿主机没装某软件完全不影响容器运行。
  3. 通过宿主机 Nginx(做入口分发与 SSL)+ 容器 Nginx(做应用解耦与静态资源托管),我们可以用极低维度的复杂度,搭建起一套安全、灵活、易于扩展的现代 Web 架构。

🚀 感谢阅读!想了解更多?

📖 我的博客网站 | 记录思考,分享干货
🏡 我的个人主页 | 关于我、开源项目