资讯动态

Docker进阶实战:存储、网络、Dockerfile、Compose与Swarm深度解析

发布时间:2026/10/1 14:52:11 来源:尧图企业网站定制
Docker进阶很多人卡在存储、网络、Dockerfile、Compose、Swarm这五道坎上。容器能跑起来只是第一步真正要把开发、测试、交付流程串起来把微服务架构落地光靠docker run和几个基础镜像远远不够。我自己在维护几十个容器服务时踩过无数坑容器重启后数据丢了、跨主机容器互相访问超时、镜像体积越构建越大、docker-compose在Linux和Windows上表现不一致、Swarm里的服务怎么都发现不了对端。这篇文章就是针对这些痛点把Docker体系里的存储、网络、Dockerfile、Compose、Swarm五个主题拆开揉碎讲清楚原理也给出可以直接抄作业的命令和配置。适合已经会docker run、docker build这类基础操作的进阶读者也适合正在准备容器化改造的运维和开发同学。要说清楚的是我不会只贴官方文档翻译而是把我在内网部署、生产环境调优时实际验证过的方案写出来。Docker版本差异大本文统一以Docker 24.x和Compose v2为基准如果你用的是老版本部分命令和配置格式需要回退适配这点后面具体说。1. Docker进阶内容整体设计与思路拆解1.1 从单容器到集群进阶到底要学什么很多人学Docker进阶看到“存储、网络、Dockerfile、Compose、Swarm”这堆名词就慌其实它们之间有清晰的递进关系。先想一个问题为什么单容器跑着跑着就要学这些因为容器是临时性的。进程退出、容器删除、节点宕机数据怎么办服务怎么恢复对外怎么提供服务跨机器怎么沟通这些问题单靠docker run参数无法优雅解决于是有了卷、网络模型、镜像构建规范、多容器编排、集群调度。存储解决的是“容器挂了数据不丢”的问题网络解决的是“容器之间以及容器与外界怎么通”的问题Dockerfile解决的是“怎么把应用打包成稳定、可复现、体积可控的镜像”的问题Compose解决的是“多个容器作为一个项目一起启动、配置、更新”的问题Swarm解决的是“在多台机器上让容器以服务形式运行、伸缩、故障自愈”的问题。这五块不是孤立的Compose会在内部编排网络和数据卷Swarm的服务定义会复用Compose的yaml结构Dockerfile构建出来的镜像必须配合正确的网络和存储配置才能真正跑生产负载。我的经验是学习顺序不用照搬章节但一定要有几个贯通点。比如你搭建一个企业内部博客系统第一步写Dockerfile构建应用镜像第二步用Compose定义数据库和web服务数据目录用卷持久化服务挂在自定义网络上通信第三步扩容两个副本用Swarm来调度这就是一个完整的进阶闭环。每做一遍对概念的理解就深一层。1.2 绕不开的底层原理Namespace与Cgroup进阶到存储和网络必须理解Docker的隔离机制否则很多“为什么”解释不了。Docker不是虚拟机它靠Linux内核的Namespace做资源隔离。一个容器启动后会创建独立的PID、Network、Mount、UTS、IPC、User这几个命名空间容器内看起来像完整操作系统实际是共享宿主内核的进程组。网络隔离靠Network Namespace每个容器有独立的网络栈、端口、路由表存储隔离靠Mount Namespace容器内的文件系统挂载关系是独立的。Cgroup是另一根支柱负责限制容器能用的CPU、内存、IO带宽。你写docker run --memory512m背后就是在对应Cgroup目录里写了限制值。进阶阶段不需要把内核源码啃完但必须弄懂这两个概念你做的卷挂载本质是跨Namespace把宿主机目录共享给容器你做的端口映射本质是把宿主机网络协议栈的流量转给容器Namespace内的进程你限制容器资源实际操作的是Cgroup文件。知道这些遇到“容器里面看到的网卡为什么和宿主机不一样”“为什么容器里能操作内核参数但权限受限”这类问题就不会一脸懵。1.3 进阶学习路线与常见误区我见过太多人走了弯路典型误区有三类。第一类是把容器当虚拟机用进去apt install一堆东西重启后配置全没了因为没把可变数据放卷里。第二类是用IP地址做容器间通信一旦容器重建IP就变了程序立刻断连正确做法是用服务名或这个网络内置的DNS解析。第三类是不管镜像构建规范一个Dockerfile里堆几百个RUN指令镜像几个G出问题后完全无法定位是哪步装的哪个包。所以学习路线要围绕“生产可用”来定先掌握存储和网络这两个基础设施再规范Dockerfile构建然后用Compose整合项目最后再上Swarm做调度。不要一上来就在Swarm里跑一个状态有问题的容器你会分不清是集群问题还是应用问题。下面我按这个次序把核心细节铺开。2. Docker存储深度解析卷、绑定挂载与tmpfs2.1 三类存储方式对比与选型Docker的存储方案分三大类数据卷named volume、绑定挂载bind mount、临时文件系统tmpfs。区别在于数据放在哪、生命周期如何、适用场景是什么。我直接给一张对照表后面详细展开维度数据卷绑定挂载tmpfs数据位置Docker管理/var/lib/docker/volumes/宿主机任意路径内存非磁盘删除容器卷默认保留不随容器删除宿主文件保留容器删除即销毁适用场景数据库持久化、跨容器共享配置开发环境热更新、把日志输出到宿主机存放临时缓存、密钥文件需内存备份迁移适合需要专门命令直接复制目录即可无法备份权限控制创建时指定较灵活继承目录权限易踩坑默认严格选型逻辑很简单生产环境持久化首选数据卷因为Docker帮你管理存储生命周期且支持跨平台的卷驱动插件NFS、Ceph等开发调试场景用绑定挂载因为可以本机改代码容器内即时生效敏感临时数据用tmpfs比如环境变量中的密钥文件不想落盘。千万不要把数据库数据直接写在容器可写层里一旦容器被删就是数据灾难。2.2 数据卷的创建、备份与迁移数据卷named volume用docker volume create创建也可以在docker run -v时自动创建。举个例子我把一个PostgreSQL数据独立成卷docker volume create pgdata docker run -d --name postgres \ -v pgdata:/var/lib/postgresql/data \ -e POSTGRES_PASSWORDsecret \ postgres:15此时数据实际写进/var/lib/docker/volumes/pgdata/_data。如果容器启动参数错了、想换镜像版本只要--volumes-from或复用同一个卷挂载到新容器数据就还在。这里有个经验给卷起名字要带业务前缀比如blog_db_data别用data这么抽象的名字否则时间长了根本不知道这个卷是干什么的。卷的备份是很多新手忽视的点。热备份时直接打包宿主机的_data目录可能因为数据库缓冲未刷盘导致数据损坏。正确做法是用一个临时容器挂载卷再执行备份工具。比如备份MySQLdocker run --rm \ -v dbdata:/var/lib/mysql \ -v $(pwd)/backup:/backup \ mysql:8.0 \ sh -c mysqldump --single-transaction -u root -psecret -h 127.0.0.1 -P 3306 --all-databases /backup/all.sql注意上面这个临时容器的网络要和MySQL实例在同一个自定义网络里否则连不上。迁移卷也一样用--rm临时容器把旧卷的内容tar出来再恢复到新机器的卷里比用docker export整容器迁移更干净只带走数据不带历史镜像层。2.3 绑定挂载在开发环境中的正确用法绑定挂载bind mount就是把宿主机目录直接挂进容器命令简单docker run -d -p 8080:80 \ -v /home/user/myapp:/usr/share/nginx/html \ nginx:latest这在开发框架时很好用比如前端改代码刷新就能看到新效果不用重新构建镜像。但谁用谁知道这个方案有整整一坑的权限问题。容器里的进程默认是root挂载目录里的文件如果权限是1000:1000容器内root能读写但如果镜像指定了非root用户运行可能就提示Permission denied。我以前调试过一个问题Node.js应用容器内以node用户运行宿主机上的node_modules目录是root所有结果fs.access就报错。解决方法是让镜像用户ID和宿主机文件属主一致或者干脆用--user参数指定UIDdocker run -d -p 3000:3000 \ --user $(id -u):$(id -g) \ -v $(pwd):/app \ myapp:dev另一个坑是挂载目录覆盖了容器内已存在的目录内容。比如镜像里/etc/nginx/conf.d有默认配置你把宿主机一个空目录挂上去默认配置就被“隐藏”了导致服务异常。所以绑定挂载前一定要ls -la看目标目录里有没有镜像自带文件必要时先复制宿主机目录内容再挂载。2.4 存储驱动与读写性能调优Docker宿主机用什么存储驱动决定了容器的读写性能。主流默认是OverlayFS2也叫overlay2它把镜像层和容器层合并为一个视图。存储驱动配置在/etc/docker/daemon.json里{ storage-driver: overlay2, storage-opts: [ overlay2.size100G ] }这里overlay2.size限制容器可写层的最大容量避免某个容器写满磁盘导致宿主机无法服务。生产环境建议单独给/var/lib/docker划分独立分区或独立磁盘否则系统盘一满容器直接卡死。我踩过这个坑日志驱动默认是json-file单日志文件能涨到几十GB磁盘被占满后所有容器批量OOM。后来加了max-size: 100m, max-file: 3限制日志大小再配置日志轮转顺便加个定时清理任务才把问题解决。关于性能还有两个小技巧。一是对需要频繁写的目录比如Elasticsearch的data目录可以挂载临时卷并设置volume-nocopy标记减少拷贝开销二是XFS文件系统挂载时建议加上pquota选项这样Docker的overlay2配额管理才能生效。这些细节很冷门但真在生产上遇到容量问题时能救命。3. Docker网络模型与容器互联实操3.1 五大网络模式解析bridge、host、none、overlay、macvlanDocker的网络模式是进阶重灾区很多人只知道端口映射不理解背后链路。默认的bridge模式本质是docker0网桥加NAT。启动一个容器时Docker创建一对veth虚拟网卡一端接容器网络命名空间另一端接桥接设备。容器内网卡分配一个类似172.17.0.x的地址流量要出外网就要经过本机iptables的NAT规则做地址伪装。这也是为什么容器内看到的外网IP与宿主不一致。host模式是让容器直接共享宿主网络栈没有独立IP也没有veth性能最高适合对延迟敏感的服务。代价是端口直接占用宿主机端口没什么隔离性。none模式是剥夺网络只给一个lo回环接口适合完全不需要网络的离线任务。macvlan模式让容器拥有宿主机物理网络的独立MAC和IP相当于一台接入交换机的小主机但宿主机和容器之间通信反而被隔离应用层要注意。最后一个overlay是Swarm跨主机通信的核心。它利用Linux VXLAN技术把多台宿主机的二层网络叠加起来生成一个虚拟子网。后面第六节会详细演示。3.2 自定义bridge网络与DNS解析进阶阶段第一件事就是别再用默认的bridge网络。默认网络里虽然容器能互通但没有内置的DNS服务名解析只能靠--link而且--link早已被标记过时。正确做法是建立自定义bridgedocker network create --driver bridge --subnet 172.22.0.0/24 --gateway 172.22.0.1 mynet docker run -d --name web --network mynet nginx docker run -d --name app --network mynet alpine tail -f /dev/null docker exec app ping web # 能通且直接用容器名当主机名这个DNS解析由Docker自带的嵌入式DNS服务提供该服务监听127.0.0.11。容器内请求web时DNS会把它解析为web容器在mynet上的IP。这样容器怎么重建IP变不变都不影响服务。要注意不同自定义网络之间默认隔离除非两个网络都用同名字连着同一容器否则无法互通。3.3 overlay网络与跨主机通信当节点组成Swarm集群后可以创建overlay网络docker network create -d overlay --attachable my-overlay--attachable允许非Swarm服务的普通容器也连接这个网络方便测试。overlay网络的通信路径是源容器IP包进入veth到主机上的overlay网桥然后经VXLAN封装成UDP包通过宿主机的物理网络发给目标主机再解封装后送进目标容器。这个模式有性能开销但换来了跨主机服务发现的灵活性。在生产环境我一般会让数据库走物理网络或macvlan而应用服务走overlay数据库不用暴露端口应用内网直连。实现跨主机通信时还要注意防火墙开放VXLAN端口通常是4789/udp和一些其他端口这在第六节详细说。如果节点在云上安全组必须配好否则节点永远Inactive。3.4 网络排查三板斧容器网络出问题按三步排查。第一docker network inspect 网络名查看哪些容器连接、网关是什么、有没有错误的Peer项这一步能快速判断容器是否在同一个网络。第二docker exec 容器 ping 目标、docker exec 容器 nslookup 服务名验证DNS解析和连通性。第三进到宿主机用nsenter进入容器的网络命名空间直接抓包或看路由pid$(docker inspect -f {{.State.Pid}} 容器) nsenter -t $pid -n ip addr nsenter -t $pid -n tcpdump -i eth0这种方法比exec里用抓包工具可靠因为精简镜像可能连ping都没有。另外不要忘了容器内/etc/resolv.conf指向127.0.0.11如果自定义了dns选项覆盖了Docker内置DNS服务名解析会失效这是常见的“两条命令都一样但有时好有时坏”的元凶。4. Dockerfile实战镜像构建的细节与优化4.1 Dockerfile核心指令与执行逻辑Dockerfile的本质是从基础镜像出发按指令一层层生成只读层每一条RUN、COPY、ADD都会创建一个中间层。指令执行顺序与缓存机制是理解优化的关键。举个例子FROM ubuntu:22.04 RUN apt-get update apt-get install -y curl COPY . /app CMD [curl, -s, https://example.com]修改任何一个COPY文件内容会导致后续所有指令缓存失效重跑。所以通常把“不常变的依赖安装”放前面“频繁变的代码拷贝”放后面这样开发迭代时能复用apt缓存层构建速度快很多。还有一个最隐蔽的坑RUN指令中执行命令出错不会自动清理已安装的临时包镜像会残留无用文件。所以安装依赖时要把rm -rf /var/lib/apt/lists/*写在同一条RUN里利用串联。我见过很多镜像体积虚胖就是因为清理工作被拆到了单独的RUN结果由于层缓存机制清理层覆盖不了前面层里的文件反而体积更大。4.2 多阶段构建与依赖缓存多阶段构建是现阶段公认的最佳实践核心是“一个Dockerfile里写多个FROM”。比如Go项目FROM golang:1.21 AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 go build -o /app/server . FROM alpine:3.18 COPY --frombuild /app/server /usr/bin/server EXPOSE 8080 CMD [server]这里alpine为基础的精简镜像只包含运行二进制和最小运行库体积从1GB级别降到50MB级别。依赖下载单独放在go mod download是为了利用Docker构建缓存只要go.mod和go.sum不变就重复使用下载层的缓存大幅提高编译效率。但多阶段构建不仅仅是“第二个FROM选小镜像”。记住一个技巧构建阶段的镜像可以基于开发工具链的发行版而运行阶段如果没有特殊要求优先用distroless或alpine。distroless镜像连shell都没有攻击面更小排障时要用docker cp把二进制拷出来调试。我建议线上镜像宁可牺牲一点便利也要保证最小化和安全性。4.3 构建上下文与.dockerignore构建上下文是一个隐含性能杀手。docker build会先把整个当前目录包括node_modules、.git、dist等发送给守护进程如果你不写.dockerignore构建过程会慢到怀疑人生。.dockerignore的语法类似.gitignorenode_modules .git *.md Dockerfile .dockerignore dist .vscode这里的坑是如果你的Dockerfile需要复制某些被ignore的目录它就会被忽略导致构建失败。比如上面ignore了dist但你的应用依赖docker build时动态编译的dist就需要要么不ignore要么在复制前自己执行编译。我通常的做法是保留构建产物目录但把缓存目录通通ignore掉例如**/__pycache__、*.pyc、.venv。另一个需要注意的点ADD指令会自动解压本地tar包也会从远程URL下载文件这些都是不可预测的会破坏缓存且带来安全风险。所以现代实践中下载文件建议用RUN curl加--checksum校验本地解压包就用COPY后解压。少用ADD的自动解压特性这在官方文档里也被反复强调。4.4 生产镜像的加固技巧很多人把改造运行镜像理解为“换小基础镜像”远远不够。安全加固的核心是不以root身份运行。默认Dockerfile如果不指定用户容器进程就是root一旦容器被攻破内网横向移动就是root权限在操作。建议在Dockerfile最后加入RUN groupadd -r app useradd -r -g app -M -s /sbin/nologin app USER app如果应用要写日志需要确保挂载卷的属主是app用户或者启动命令里用chown。除了用户还要控制权限能力使用--cap-dropALL --cap-addNET_BIND_SERVICE只保留绑定低端口的能力。这虽然是对docker run的参数但也可以用Dockerfile的ENTRYPOINT配合脚本实现。另外容器镜像里不要存放密钥。密钥在生产环境应该通过Secret或环境变量注入。因为镜像本身可能被推送进registry一旦泄漏历史层仍是黑盒几乎无法清洗。我在这上面栽过跟头把数据库密码写在Dockerfile的ENV里镜像push到私有仓库后被误投到公开仓库看到日志里的敏感信息时冷汗直流整批重新构建才解决。5. Compose多容器编排从单机到项目级5.1 Compose文件结构与版本选择Compose是单机多容器编排的标配它把多个容器的启动参数写进一个YAML文件一条docker compose up -d就全部拉起。注意现在的Compose v2是Docker CLI插件不再叫docker-compose这是很多人的混淆点。新装Docker后直接执行docker compose version验证即可如果提示unknown command: docker compose说明没装插件需要单独装docker-compose-plugin这个包。Compose YAML里的结构一般是这样services: web: build: . ports: - 8080:80 depends_on: - db db: image: mysql:8.0 volumes: - dbdata:/var/lib/mysql volumes: dbdata:顶层有services、volumes、networks三个关键字段。版本的兼容性主要看Docker Compose自身而不是文件里的version字段。Compose v2已经不再强制要求写version:写了也没有实际规则作用。旧教程里的version: 3.8在新版本里会被忽略功能不受影响。5.2 常用配置项与依赖控制depends_on是基础依赖控制只保证启动顺序不等服务真正可用。比如web依赖db如果db启动但还没初始化完成web连接就会失败。正确做法是加上healthcheckservices: db: image: mysql:8.0 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -psecret] interval: 10s timeout: 5s retries: 5 web: build: . depends_on: db: condition: service_healthy这样web会在db健康后才被创建。这个机制被大量用在测试环境真实环境如果业务量不大我建议直接让应用具备重试连接的能力而不是完全依赖编排层因为编排层的健康检查本身有延迟。环境变量配置也有讲究。推荐用env_file读外置的.env文件不要把敏感信息直接硬编码在compose文件里。Compose项目根目录的.env文件会自动被读取用于变量替换。例如services: app: image: myapp:latest environment: - DB_HOSTdb - DB_PASSWORD${DB_PASSWORD}5.3 真实案例用Compose部署MySQL 8.0主从光说理论不够我们做一个MySQL主从的例子。它演示了Compose在单机上的应用也涉及存储、网络、init脚本这几个主题的贯通。目录结构mysql-repl/ ├── master/ │ └── my.cnf ├── slave/ │ └── my.cnf └── docker-compose.yml主节点配置master/my.cnf[mysqld] server-id1 log-binmysql-bin binlog-formatROW从节点配置slave/my.cnf[mysqld] server-id2 relay-logrelay-bin read-only1Compose文件services: master: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass volumes: - ./master/my.cnf:/etc/mysql/conf.d/my.cnf - master_data:/var/lib/mysql networks: - replnet slave: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass volumes: - ./slave/my.cnf:/etc/mysql/conf.d/my.cnf - slave_data:/var/lib/mysql networks: - replnet depends_on: - master networks: replnet: volumes: master_data: slave_data:启动后两个容器都在同一个自定义网络replnet彼此可以用服务名master和slave访问。在主库执行SHOW MASTER STATUS获取log file和position再在从库执行CHANGE MASTER TO这就是主从复制的建立。这个例子最大的坑是两个容器挂在同一个宿主目录上会互相覆盖数据文件必须用不同卷像我上面那样。另一个坑是MySQL 8.0默认使用caching_sha2_password认证协议低版本客户端连不上所以生产环境用5.7做从库或升级客户端时要注意兼容性。5.4 Compose项目升级与迁移注意事项Compose项目升级时最怕“改了一个服务带崩了其他服务”。升级前一定要先docker compose config验证语法它会渲染出最终配置。还会发现变量是否缺失。我一般还会加上一个--dry-run风格的检查对比docker compose ps的容器数量确认没有意外扩缩容。迁移到其他机器时不要简单把整个目录拷走就完事。数据保留在命名卷里需要先把卷里的数据导出再在新机器上创建同名卷。迁移流程是停服务、备份卷、拷贝源码目录、恢复卷、重新docker compose up -d。如果涉及构建镜像那镜像仓库也要提前设置好私有Registry不要在目标机器上裸build耗时且难以复现。6. Swarm集群模式从零搭建与服务发现6.1 Swarm核心概念Manager与Worker节点很多人以为Swarm是过时技术但它在中小规模集群里依然很实用尤其在不想引入K8s的运维团队。Swarm把宿主机分成Manager和WorkerManager负责调度、维持集群状态、处理服务变更Worker只运行任务。Manager之间用Raft协议做共识至少3个Manager才能容忍一台挂了。如果你只有一台机器也可以初始化单节点SwarmManager同时充当Worker。核心调度单位是“任务”task一个服务service可以指定副本数比如--replicas 3Swarm会在可用节点上启动3个任务每个任务是一个容器。如果某个节点宕机Swarm会把这个节点上的任务重新调度到其他节点这就是自愈。这个能力解决的不只是高可用问题也是配置管理的统一你不再需要逐台登录机器记住一长串启动参数。6.2 初始化集群与节点加入流程初始化Swarm集群只需要一行命令docker swarm init --advertise-addr 192.168.1.10--advertise-addr是告诉其他节点通过哪个IP访问当前Manager。这里最容易踩坑的是选了错误的网卡IP比如虚拟机NAT网络导致其他节点连不上。建议先用ip addr确认内网IP再用docker swarm join-token worker获取加入命令。比如输出docker swarm join --token SWMTKN-1-xxxx 192.168.1.10:2377在Worker节点上执行这条命令就能加入集群。别忘了开放端口2377/tcp用于集群管理7946/tcp和7946/udp用于节点之间的通信4789/udp用于overlay网络VXLAN。很多人在云上配了安全组却漏了4789导致服务间跨主机不通这就是上一节说过的坑。节点加进来后用docker node ls查看。如果某个节点状态显示Unreachable检查防火墙和advertise-addr。如果是IP变了要在/etc/docker/daemon.json里设置然后重启Docker重新向Manager申请加入。6.3 服务部署、滚动更新与伸缩Swarm的服务命令和普通docker run很相似但多了编排属性。用一个nginx服务做例子docker service create \ --name web \ --replicas 3 \ --publish published8080,target80 \ --network my-overlay \ nginx:1.25这里的--publish采用published和target两个参数用逗号分隔这是新版语法旧版本里用80:8080也会被自动转换。服务创建后Swarm会在三个可用节点上各启动一个容器由Ingress负载均衡统一接收8080端口的流量再分发给各节点的nginx容器。滚动更新是Swarm的杀手级功能docker service update --image nginx:1.26 --update-delay 5s --update-parallelism 1 web每5秒更新1个副本适合零停机发布。如果更新过程中发现问题用docker service rollback web一键回滚。这个回滚命令在实际运维中非常救命。我所在的服务因为更新镜像版本后暴露了配置不兼容问题用了三次rollback才定位到问题如果没有这个功能就需要手动停服务换版本运维压力完全不同。伸缩也简单把副本数从3改成5docker service scale web5Swarm会立刻在剩余节点上调度新任务。要注意的是使用overlay网络的服务副本扩展起来没有端口冲突因为Ingress端口是共享的这是 bridge 网络不具备的优势。6.4 Swarm与Compose的配合使用Swarm可以直接使用Compose文件部署服务栈只需要额外声明顶层deploy字段。比如services: app: image: app:1.0 deploy: replicas: 3 update_config: parallelism: 1 delay: 10s resources: limits: cpus: 1.0 memory: 512M networks: - mynet networks: mynet: driver: overlay然后执行docker stack deploy -c docker-compose.yml mystackdocker stack命令会把Compose文件中的deploy字段映射成Swarm服务。注意stack部署时自动创建的overlay网络要用driver: overlay明确指定。还有一个大坑docker stack不支持build字段它只能部署已构建好的镜像所以需要在部署前先docker build并push到仓库。这一点让很多从docker compose转过来的同学迷惑习惯后倒还算顺手。Swarm服务发现方面服务名就是集群内的DNS名。比如上面创建一个app服务另一个服务里直接curl http://app:3000Swarm内置的任务和虚拟IP机制会自动把流量分发到其中一个副本。配合滚动更新它还能在更新时自动摘除旧任务、加入新任务服务无感知。7. 常见问题与排查技巧实录7.1 容器重启后数据丢失这是最高频的问题几乎每个从虚拟化时代过来的开发者都遇到过。现象是docker run创建的容器一旦删除重新拉起后配置、日志、数据库数据全部消失。原因就是你没有把数据写在卷或绑定挂载里而是写在了容器可写层。容器删除时可写层被回收数据自然没了。排查命令很直接先docker inspect 容器看Mounts字段里有没有列出卷映射。如果没有再docker exec 容器 sh -c echo test /tmp/test然后docker restart该容器如果重启后cat /tmp/test还在说明数据只是存在于容器层删除容器还是会丢。遇到这种情况最稳妥的补救方式是把当前数据复制到卷里docker cp 容器:/var/lib/mysql /tmp/mysql-data docker volume create mysql_data docker run --rm -v mysql_data:/var/lib/mysql -v /tmp/mysql-data:/backup alpine \ sh -c cp -a /backup/. /var/lib/mysql/然后再用-v mysql_data:/var/lib/mysql启动新容器。以后务必用卷不要心存侥幸。7.2 容器间网络不通容器间网络不通的表现通常是在一个容器里ping另一个容器的IP能通但按服务名解析失败或者解析成功但TCP连接超时。按经验先看是不是自定义网络没有shared。默认bridge网络没有DNS服务名解析所以服务名ping不通很正常这是设计如此不是故障。要解决创建自定义网络后重新连接容器。如果是同一个自定义网络下按IP能通但TCP端口不通那就要考虑防火墙或目标容器没监听。用docker exec进入目标容器ss -tlnp检查端口监听情况。如果目标服务绑定了127.0.0.1容器网络栈就访问不到它必须绑定0.0.0.0。例如有的Node.js应用默认监听IPv6的::1容器内从IPv4去访问就会一直Pending这也是很隐蔽的坑。还有一种是跨主机overlay网络不通。优先检查4789端口然后docker service logs看服务错误。实在不行暂时把需要通信的服务部署在同一节点上测试排除物理网络因素。7.3 Compose版本依赖导致的命令失败docker compose up时出现unknown command: docker compose多数是环境变量PATH里没有Docker CLI插件或者是老版本Docker没有安装compose-plugin。Ubuntu下执行apt install docker-compose-plugin即可CentOS则用yum install docker-compose-plugin。安装后验证docker compose version另一个关联问题是docker stack deploy不支持build字段、不支持volumes的某些语法。如果出现unsupported config option多半是把Swarm部署和Compose单机混为一谈。Swarm栈的compose文件里build必须删掉镜像必须提前构建好推送。此外version:字段在部分老版本中被严格要求如果新版Docker提示版本无关或报错就直接去掉version行。7.4 Swarm服务端口暴露异常Swarm服务端口暴露异常经常是服务创建后才发现端口没生效。可能是因为docker service create时用了旧式--publish 8080:80语法在新版其实也能用但如果你在Compose里写过ports: - 8080:80在docker stack部署时Swarm会把它视为普通的容器到主机映射而不是Ingress映射于是每个节点都尝试绑定同一端口导致部分节点冲突。正确的做法是使用docker service create --publish published8080,target80或者在Compose的deploy字段里指定endpoint_mode: vip以及ports下的published和target。如果要用Ingress确保服务使用的网络是driver: overlay。还有一个小技巧服务卷挂载会丢失Ingress的负载均衡能力因为Ingress只能基于TCP层的端口转发不感知路径这不影响功能但要心里有数。另外Swarm服务一直处于New状态却起不来优先用docker service ps service --no-trunc看错误信息。最常见的错误是镜像拉取失败或端口被占用。--no-trunc参数不要漏不然错误消息被截断你看到的只是半句话排查效率会打折扣。写到这里Docker进阶在存储、网络、Dockerfile、Compose、Swarm这五个方向的核心细节就都覆盖了。我在实际使用时最常被朋友问“这些概念能不能简化记忆”我的建议是把五类问题绑定到五条命令数据持久化找docker volume网络互通找docker network镜像构建优化看Dockerfile多容器管理看docker compose跨主机调度看docker swarm。遇到不通、不挂、不生效时先跑一遍我上面写过的排查命令大多数问题都能定位到根因。如果还有没覆盖到的细枝末节欢迎在评论区把你的具体报错发出来我帮你拆。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑