
1. 存储底子三类数据落点先搞懂再动手玩Docker绕不开一个问题容器是“一次性”的我写的配置、生成的数据容器一删就全没了。不少刚接触Docker的朋友第一次跑数据库容器重启之后发现数据清空整个人就慌了。其实这不是Docker的锅而是没理解容器文件系统和持久化存储的区别。1.1 镜像层与容器层为什么容器天生“短命”Docker镜像本身是分层构建的每一层都是只读的。容器运行时Docker会在镜像层之上加一个可写层所有文件的增删改都发生在这个可写层里。可写层的生命周期和容器完全绑定容器删除可写层跟着销毁数据随之消失。这种设计让容器的启动和销毁变得极其轻量但也注定了“不适合存数据”。我见过有人硬生生在容器里改配置文件、放日志数据结果容器一重建所有改动烟消云散。排查了半天最后才发现问题出在“压根没挂载任何存储”。理解这个底层模型之后你就知道为什么Docker官方一直强调数据一定要落在三类持久化方案里别写在容器内。1.2 三类存储方式对比卷、绑定挂载、tmpfs怎么选Docker提供三种主流存储挂载方式很多教程只列了命令没讲清区别我带你把它们的底层逻辑和工作原理盘一盘。存储方式数据存储位置持久性典型场景卷VolumeDocker管理区域宿主机特定目录持久数据库数据、应用配置、跨容器共享绑定挂载Bind Mount宿主机任意目录直接映射持久开发调试、日志收集、配置文件直改tmpfs宿主机内存中临时敏感信息、临时缓存、不想落盘的数据卷是最推荐的方案因为它由Docker完整管理不受宿主机目录结构影响权限控制也更可控。绑定挂载适合开发和调试因为你可以直接在宿主机上改代码容器内立刻生效省去了反复构建镜像的折腾。tmpfs则适合存放临时性数据比如临时token、密钥缓存容器停止即数据消失天然规避了敏感数据落盘的风险。这里有一个新手特别容易踩的误区卷和绑定挂载到底是不是同一个东西不是。卷的目录在Linux上是/var/lib/docker/volumes/下面Docker自己管理绑定挂载是你手动指定的宿主机路径比如/home/user/app直接映射进去。两者命令写起来像但语义完全不同。还有一个细节值得注意绑定挂载如果宿主机的目录不存在Docker会帮你自动创建。听起来方便但如果你路径写错了Docker会悄悄建一个空目录挂进去容器里看不到原来的文件排查半天才发现是路径拼错了。2. 网络模型容器之间是怎么“通上话”的Docker的网络是很多人的知识盲区。跑两个容器A想访问B发现IP不通或者服务连不上就开始怀疑是不是镜像有问题。实际上大部分网络不通的问题都出在对Docker默认网络模型的不理解上。2.1 三种内置网络bridge、host、none的底层逻辑Docker安装完成后默认会创建三个网络。默认不指定任何参数时容器加入的是bridge网络。bridge网络是“桥接”模式Docker会创建一个虚拟网桥宿主机上的容器都接入这个网桥容器之间可以通过IP地址直接通信。这个网桥默认网段通常是172.17.0.0/16我在生产环境遇到过网段冲突的情况公司内部网络恰好也用了这个网段导致宿主机无法访问容器最后只能自定义网段避开冲突。这类问题特别隐蔽日志里看不到任何报错但连接就是不通过。host网络的意思就是容器直接使用宿主机的网络栈没有独立的网络命名空间不占用独立IP。容器监听端口等同于宿主机监听端口网络性能更好因为少了NAT转换一层。代价是没法做端口映射启动时可以省略-p参数但也意味着多个容器不能同时监听同一端口。none网络则是“无网络”模式容器只有一个回环接口外部无法访问容器也无法出网访问外部资源。听起来鸡肋但实际用在离线任务、批处理脚本、安全沙箱里非常好用从源头断绝了网络暴露面。有安全要求的业务场景把不需要对外通信的容器挂到none网络等于从网络层面上锁死了。2.2 为什么我强烈建议用自定义网络默认bridge网络最大的痛点是容器之间只能通过IP通信不能通过容器名。容器重启后IP可能变化如果服务配置里写的是IP一旦容器重启重建配置就失效了。Docker内置了DNS解析功能但在自定义网络中才会启用容器名自动解析。这个特性在编排环境里特别关键。举个例子你跑了一个配置中心和一个业务服务业务服务连接配置中心的地址如果写的是容器名而非IP即使配置中心容器重启、IP变了业务服务都能自动找到它无需重启配置。手动创建一个自定义桥接网络命令非常简洁docker network create --driver bridge my-network创建完成后启动容器时指定--network my-network同一网络下的容器自动支持通过容器名互相解析。这个操作只需要几秒钟却直接改变了容器之间通信的可靠性。我在实际项目中凡是涉及多容器协作的场景基本不会再用默认网络。自定义网络还有一个附带好处隔离性。不同网络的容器默认互相隔离无法通信。你不需要费劲配置防火墙规则只要把不同业务的容器放到不同网络里网络层面就天然隔离了。比如业务服务放在backend-net管理面板放在admin-net再通过服务端应用内部调用打通安全性比“全放一个网络”高出不少。2.3 端口映射外部访问容器的唯一通道默认情况下容器的网络是“封闭”的宿主机之外的机器无法访问容器内的端口。要让外部访问容器里的服务必须做端口映射把宿主机的某个端口映射到容器的端口上。docker run -d -p 8080:80 nginx:latest这行命令的意思是宿主机上的8080端口转发到容器的80端口。外部用户访问宿主机IP:8080就能到达容器内的Nginx服务。你可以把-p理解成“快递驿站”宿主机端口是驿站的取件窗口容器端口是真正的收件人。外部快递员不需要知道收件人具体在哪只要把包裹送到窗口驿站自然会送到收件人手里。端口映射有几个细节值得注意。第一-p只映射一个端口-P则会把容器里所有暴露的端口随机映射到宿主机高位端口一般不建议生产使用映射关系太随机不好排查。第二端口映射只对bridge网络生效host网络模式下不需要映射端口直接就是宿主机的端口。第三修改端口映射必须重新创建容器docker run里设置的参数没法动态修改。我踩过的坑是宿主机端口被别的进程占用时Docker不会报错容器也能正常启动但外部访问始终不通。排查时用ss -lntp看端口监听情况发现8080被另一个程序占用了根本走的不是容器这条链。所以启动前先确认宿主机端口是干净的避免自找麻烦。3. 实操演练从零搭建带持久化和网络的应用前面把原理捋了一遍这一节我用一个完整的场景带你把存储和网络串起来。目标跑一个数据库容器和一个应用容器数据库的数据放在卷里应用容器通过网络访问数据库数据库不对外暴露端口。3.1 存储实操创建卷并挂载到容器先创建一个独立的卷用于存放数据库的数据文件docker volume create mysql-data查看卷信息docker volume inspect mysql-data输出里能看到卷的挂载点在宿主机上的实际路径。Docker会帮你创建好不需要手动干预目录权限。启动数据库容器并挂载卷docker run -d \ --name mysql-server \ --network app-network \ -e MYSQL_ROOT_PASSWORDmy-secret-pw \ -e MYSQL_DATABASEtestdb \ -v mysql-data:/var/lib/mysql \ mysql:8.0注意这里我把--network app-network也加上了前提是你已经提前创建了这个网络。如果还没创建先跑docker network create app-network挂载卷的过程就是把宿主机的卷目录映射到容器内的/var/lib/mysql。MySQL往这个目录写的数据都会落到卷里即使容器删除重建只要挂载同一个卷数据都还在。验证一下数据持久化是否有效。先进入容器创建一个测试表docker exec -it mysql-server mysql -uroot -pmy-secret-pw -e CREATE DATABASE IF NOT EXISTS test; USE test; CREATE TABLE t(id INT PRIMARY KEY);然后强制删除容器重新创建并挂载同一个卷docker rm -f mysql-server docker run -d --name mysql-new ... 参数同上进入新容器查询之前创建的数据库和表都还在说明卷确实写入了宿主机目录容器本身销毁不影响数据。3.2 网络实操验证容器间通信与DNS解析既然是同一个自定义网络应用容器可以直接通过容器名访问数据库不用写IP。启动一个测试用的应用容器docker run -d --name app-server --network app-network alpine sleep 3600进入应用容器测试连通性docker exec -it app-server ping mysql-server能看到mysql-server这个域名被解析成了对应IP说明Docker内置DNS解析生效。如果这一步用的是默认bridge网络ping mysql-server会直接报错因为默认网络不支持容器名解析。这里我再强调一遍自定义网络解决了两个核心问题——稳定的通信入口和网络隔离边界。我在部署监控系统时把监控探针和业务服务放在同一个网络外部其它容器默认无法访问业务服务这比任何防火墙规则都更简单可靠。3.3 组合场景一个可复用的编排命令总结上面几步单独执行可以看清每步的细节实际生产环境中你更可能用编排工具一次把网络和存储全建好。这里给一段可直接复用的命令模板省去你到处翻文档。# 1. 创建自定义网络 docker network create --driver bridge app-network # 2. 创建数据卷 docker volume create mysql-data # 3. 启动数据库容器不对外暴露端口 docker run -d \ --name mysql-server \ --network app-network \ -v mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDmy-secret-pw \ mariadb:10.11 # 4. 启动后端应用容器通过容器名访问数据库 docker run -d \ --name backend-server \ --network app-network \ -p 8080:8080 \ -e DB_HOSTmysql-server \ -e DB_USERroot \ -e DB_PASSWORDmy-secret-pw \ your-backend-image:latest这套组合拳下来后端服务通过DB_HOSTmysql-server就能直连数据库外部用户只能通过8080端口访问后端数据库完全在内部网络里对外零暴露。这种架构模式是微服务上容器后最常见的一种基础范式。4. 排错实录存储和网络最常见的问题清单存储和网络出现问题排查起来往往比业务代码问题更费劲因为报错信息不一定直观。我把这几年代运维和实操里踩过的高频问题整理成了一张速查表按图索骥能省很多时间。现象根本原因解决方式容器启动后报权限拒绝挂载目录权限不一致宿主机目录属主和容器内用户不匹配显式指定用户ID或用卷替代绑定挂载数据丢失容器重建后空数据忘了挂载卷数据写在容器可写层创建卷并挂载到数据目录容器A访问容器B超时不在同一个网络或网络隔离导致不相通统一加入同一自定义网络使用容器名ping不通默认bridge网络不支持DNS解析使用自定义网络外部访问服务不通端口映射未生效或宿主机端口被占用检查docker ps的端口映射用ss查看宿主机端口网络占用了内部网段无法访问默认网段和公司内部网段冲突自定义网络并指定不冲突的网段4.1 卷权限问题的排查实录权限问题是存储方向翻车率最高的一类。典型的场景挂载了一个宿主机目录到容器内容器内应用启动时报Permission denied。原因很简单宿主机目录的属主是uid1000比如普通用户但容器内进程是以root身份运行的Linux的权限模型要求进程有足够的权限访问挂载点一旦容器内用户和宿主机目录属主不匹配就可能碰到权限墙。解决方式我在项目里用得最多的是两种。第一启动时指定用户ID让容器内的进程以宿主机目录属主的身份运行比如docker run --user 1000:1000。第二直接把卷的属主改成容器内运行用户的ID例如chown -R 1000:1000 /data。无论哪种方式核心逻辑都是“让双方的身份对齐”。遇到这个问题别硬扛权限模型是Linux的基础规则绕不过去就配合它。4.2 容器网络排障的标准流程容器网络出问题我的排查顺序几乎固定先确认容器是否在同一个网络再确认网络内DNS解析是否正常最后检查端口监听状态。第一步查看容器所在网络docker inspect -f {{.NetworkSettings.Networks}} container-name如果两个容器不在同一网络通信肯定不通把它们加入同一个自定义网络即可docker network connect app-network container-name第二步确认容器名解析正常。在容器内执行docker exec -it app-server getent hosts mysql-server如果没解析出IP说明DNS解析链路有问题。正常情况下自定义网络会自动配置DNS不会出现这种问题出现的话多半是容器加入网络时出了岔子把容器重新连接到网络就好。第三步检查应用端口是否真的在监听。有时容器活着但应用进程挂了端口自然不通。进入容器看进程状态或者用curl测一下容器内端口能快速定位问题是出在网络层还是应用层。这个步骤很多人会忽略一上来就怀疑网络配置折腾半天才发现是应用自己崩了。4.3 关于网段冲突的特别提醒网段冲突是我这几年实际运维中遇到的最隐蔽的网络问题。默认bridge网段172.17.0.0/16可能与公司现有网络重叠导致从宿主机访问容器时路由混乱表现为“有时候通有时候不通”。解决方法其实很简单创建自定义网络时指定一个不容易冲突的网段docker network create \ --driver bridge \ --subnet10.88.0.0/16 \ app-network这步操作花不了几秒但能帮你避开很多诡异的路由问题。如果你发现宿主机能ping通容器IP但业务访问时通时断第一反应就该去查网段是否冲突而不是盯着容器日志瞎猜。4.4 还容易忽略的三个小问题除了上面这些主场景还有三个小问题我经常在项目里碰到。第一个是挂载目录存在性误判绑定挂载时路径敲错了Docker自动建了新目录结果容器启动正常但看不到宿主机的文件。这种情况要对比宿主机和容器内的目录内容来排查。第二个是卷的孤儿问题容器删除了但卷还占着磁盘空间。尤其是日志类容器数据量大得离谱删容器时忘了加-v磁盘空间不知不觉就被占满了。建议定期执行docker volume prune清理无用的卷。第三个是tmpfs的容量上限问题。tmpfs默认占用宿主机内存的一半如果往里写大文件内存可能会被撑爆。使用--tmpfs /data:size100m限制大小避免影响宿主机其它进程。5. 一些心得收尾最后分享几个我长期使用下来的小建议。存储和网络这类基础能力平时不显山不露水出问题时才让人抓狂所以最好一开始就按规范搭好。凡是数据一律用卷凡是容器间通信一律自定义网络凡是外部访问一律显式声明端口映射。这三条是底线别的可以灵活这三条我从不在项目里妥协。还有一个小细节Docker命令敲多了之后建议把常用命令写成脚本或者养成通过配置文件管理容器习惯这样容器重建时不会漏掉卷和网络参数。我在实际项目里吃过亏——手动敲命令时忘了加--network结果容器连不上数据库排查了整整一个下午才发现是少了一个参数。存储和网络是Docker的任督二脉通了之后你会发现更多高级玩法都变得顺理成章。多动手、多模拟故障场景比看十遍文档都管用。