☰
Linux Docker 安装配置与网络问题排查实战指南
2026/9/30 3:33:01 网站建设 项目流程

1. 写在前面:这篇指南到底能帮你什么

Linux 上装 Docker、做配置、搞网络,表面看就是加源、装包、起服务三件套,不少教程连十行命令都凑不齐。但真正容易让人崩溃的,永远是“装好之后”的事:MySQL 容器起来了,另一个应用死活连不上;端口映射加了,外部还是访问不通;重启一次机器,容器 IP 全变了,写死 IP 的脚本全军覆没。这些问题不解决,Docker 用起来就会一直让人不踏实。

这篇指南标题写得很直白,就是 Linux Docker 的安装、配置与网络三块内容。我把它限定为“上篇”,是因为这三块已经足够复杂,把底层概念、命令逻辑、常见报错一次讲透,比贪多嚼不烂要实际得多。适合的人群很清晰:刚接触 Docker 的运维、后端开发,或者那些已经在用 Docker 但经常被网络问题卡住的朋友。阅读前不需要太多基础,但读完后你应该能做到:在一台干净的 Linux 机器上独立装好 Docker、按需调整守护进程参数、搞懂容器间通信原理,并且遇到“网络不通”时知道先查什么。

我先交代一下个人背景,免得大家觉得我在纸上谈兵。我从最初用 systemd-nspawn 和 LXC 折腾容器,到后来在公司生产环境统一切到 Docker,中间踩过不少坑,有些坑属于“文档里根本不会写、搜也搜不到”的那种。所以我写的东西,基本都有一个对应的真实事故或线上排查记录。我会尽量用大白话讲原理,不堆术语,但该给的命令和参数一个不少。

2. 安装 Docker 的正确姿势,以及我踩过的安装坑

2.1 安装前先把这三件事确认一遍

很多人拿到服务器就急着敲安装命令,结果装完发现 Docker 服务起不来,回头才意识到是环境不满足要求。安装前花五分钟检查现状,比事后排查节省半小时。

第一件,确认内核版本。以我常用的 CentOS 7 和 Ubuntu 20.04 为例,CentOS 7 默认内核是 3.10,旧版本 Docker 还能跑,但新版 Docker Engine 对内核要求已经明显提高,cgroup v2 这些特性在旧内核上根本没有。建议先跑uname -r,如果内核版本低于 4.0,并且你没法升级,最好安装与你系统匹配的旧版 Docker,或者干脆换系统。拿一个现代发行版踩坑,你会发现很多莫名其妙的兼容问题都不存在了。

第二件,检查系统对 overlay 文件系统的支持。Docker 默认的存储驱动是 overlay2,性能和稳定性都比老式的 devicemapper 好很多。可以执行cat /proc/filesystems | grep overlay,如果输出里有 overlay,那基本没问题。有些云主机或虚拟化环境里这个模块没加载,你还需要确认内核模块是否存在,必要时modprobe overlay。不做这一步,后面很可能出现容器启动成功但磁盘占用诡异膨胀的问题。

第三件,想清楚你的运行环境。如果你是在虚拟机里装 Docker,或者用的是 WSL2 这一类虚拟化环境,要提前确认 CPU 虚拟化有没有打开。Windows 上的 Docker Desktop 报过virtualization support not detected,很多情况下就是 Hyper-V 或“虚拟机平台”功能没开启。Linux 里虽然没有这个报错,但 /dev/kvm 不存在时,跑一些需要硬件虚拟化的容器应用会直接失败,性能也会受到明显影响。判断方法很简单:ls -l /dev/kvm,有输出就说明宿主机支持硬件虚拟化。

2.2 官方仓库安装,还是系统自带的 docker 包

到了安装环节,很多发行版软件源里自带 docker 包,名字可能叫docker.io或者docker,不少人直接yum install docker就完事。这里我必须建议大家尽量用官方仓库装 Docker Engine,不要图省事用系统自带的包。原因很简单:系统自带包版本落后,更新节奏也不跟 Docker 官方一致,有些甚至还带着老掉牙的 docker 0.x 时代配置习惯。生产环境用容器,一个补丁没跟上,可能就会撞上已知的安全漏洞或内核兼容问题。

官方推荐的方式是执行:

curl -fsSL https://get.docker.com -o get-docker.sh sh get-docker.sh

不过这个脚本有两个前提:一是服务器能正常访问 Docker 官方仓库;二是你明确知道自己在做什么。脚本会自动识别系统版本,调用对应包管理器安装 Docker Engine、containerd、docker-cli 等组件。装完后执行docker version,如果 Client 和 Server 两段都显示正常,说明核心组件已经就位。

如果你的服务器处于比较封闭的内网环境,或者外网下载经常超时,就别硬刚脚本了。换成离线安装策略:找一台装有同样系统版本的机器,把对应的 docker-ce、containerd.io、docker-ce-cli 等 rpm 或 deb 包全部下载下来,然后拷到内网用dpkg -i或rpm -ivh依次安装。特别注意顺序,rpm 包互相有依赖,建议直接rpm -ivh *.rpm让包管理器自己解析,安装完成后再用rpm -qa | grep docker验证。

2.3 首次启动失败?先看这两个方向

Docker 装好之后,理论上执行systemctl start docker就行,但第一次启动失败的概率其实不低。我遇到过的失败原因,十有八九集中在两类。

第一类是配置文件损坏或权限不对。Docker 启动后要读 /etc/docker/daemon.json,如果你曾经手动改过这个文件,某个标点写错,服务就会直接起不来。判断方法很简单:systemctl status docker会提示失败,然后执行dockerd在前台运行,它会直接把错误原因打出来,比翻 journal 日志更直观。比如parsing error: invalid character这种,通常就是 JSON 末尾多了逗号,或者用了单引号。

第二类是内核安全模块拦截。SELinux 或 AppArmor 在某些系统上默认开启,Docker 容器运行时可能被安全策略挡下来。典型表现是服务能启动,但创建容器时报权限错误。CentOS 上常见的是 SELinux 标签问题,命令是ausearch -m avc -ts recent查看拦截记录,然后通过调整容器挂载目录的 SELinux 上下文解决。阿,这里我不建议直接setenforce 0关掉安全模块,除非你只是临时调试。生产环境正确的做法是给目录打上正确上下文,比如挂载主机目录给容器时,用:Z或:z后缀让 Docker 自动调整标签。

还有一个非常容易忽略的问题:磁盘空间。默认数据目录在 /var/lib/docker,如果根分区本身快满了,容器创建会报no space left on device。这个不一定是真的没有 inode,也有可能是 Docker 自己的分层存储出了问题。启动时报空间不足时,先df -h和df -i一起看,再决定是清理旧镜像还是迁移数据目录。数据目录迁移的内容我放到下一节。

3. 守护进程配置:让 Docker 按你的规矩来

3.1 daemon.json 里最值得设置的几个参数

Docker 守护进程的配置集中在 /etc/docker/daemon.json,这个文件默认不存在,需要你自己创建。很多教程一笔带过,但我要说,这里面的参数能直接影响容器的运行方式、日志体量、网络安全边界。我放一份自己常用的配置样例:

{ "data-root": "/data/docker", "storage-driver": "overlay2", "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "iptables": true, "live-restore": true }

先说>[Service] ExecStart= ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock

ExecStart=那行空值是为了覆盖系统默认的 ExecStart,然后再指定新参数。改完执行systemctl daemon-reload和systemctl restart docker。这个思路适应面很广,比如你想调 ulimit、加环境变量、改 socket 位置,都可以通过 override 文件实现。

系统资源限制方面,容器数量一多,文件句柄和进程数很容易触顶。建议在 override.conf 里适当调整LimitNOFILE和LimitNPROC,例如:

[Service] LimitNOFILE=1048576 LimitNPROC=infinity

这样能避免跑大批量容器时报too many open files这类问题。我已经不止一次在线上碰到因为默认文件句柄上限太低,容器频繁重启的案例。改完之后再看/proc/1/limits确认生效,别相信任何人的“应该可以”。

4. 网络模式:容器之间到底怎么“说话”

4.1 五大网络模式,一张表讲清区别

安装配置都就绪后,最难啃的部分就是网络。Docker 网络模式看着多,但核心逻辑并不复杂,我先把表列出来,后面慢慢展开。

网络模式一句话理解常用场景
bridge容器通过虚拟网桥与宿主机通信,默认模式单机多容器互相通信、端口映射
host容器不隔离网络,直接使用宿主机网络栈性能测试、对延迟极敏感的服务
none容器无网络,只有回环接口只需要本地计算的临时任务
container容器共享另一个容器的网络栈调试、sidecar 模式
overlay跨主机容器组网Swarm、Kubernetes 等集群环境

bridge 模式下,Docker 会在宿主机创建一个 docker0 网桥,每个容器分一个虚拟网卡和私有 IP。你可以把 docker0 想象成一台虚拟交换机,容器是插在上面的终端设备,宿主机是连接外部世界的网关。做端口映射时,实际上就是在这台“交换机”上做了个转发规则,把宿主机某个端口的数据转给容器 IP 的某个端口。

host 模式则完全不一样,容器不会分到独立 IP,直接使用宿主机网络栈。这意味着你不需要-p做端口映射,容器里进程监听端口就相当于监听宿主机端口。好处是性能几乎无损,坏处是没法做端口隔离,很多端口冲突问题会直接冒出来。

container 模式和 host 类似,但它共享的是另一个容器的网络命名空间。比如调试时你希望抓某个容器的网络包,可以起一个安装了 tcpdump 的工具容器,用--network container:目标容器名共享它的网络栈抓包,非常顺手。

overlay 模式主要面向跨主机场景,需要在 Swarm 或 Kubernetes 中配合使用,单机环境下用不到,但了解它的存在可以帮助你判断“是不是该用它”。除非你已经上集群,否则我建议先不要碰。

4.2 默认 bridge 和自定义 bridge 的关键差异

docker0 是 Docker 自动创建的默认 bridge 网络,你直接docker run不指定网络时容器就挂在 docker0 上。但默认 bridge 有几个让人抓狂的限制:容器之间只能用 IP 互相访问,不能用容器名;--link是古老的解决方案,只能单方向指定别名,新容器多起来以后链路关系会像蜘蛛网一样乱。

自定义 bridge 网络解决了这个问题。创建一个网络:

docker network create app-net

然后运行容器时指定:

docker run -d --name web --network app-net nginx docker run -d --name mysql8 --network app-net mysql:8.0

在 app-net 这个网络中,web 容器直接访问mysql8:3306就能连上,Docker 内置的 DNS 负责把容器名解析成对应 IP。这个能力实在太重要了,容器重建后 IP 会变,但容器名可以保持不变,应用配置里写死容器名就行,IP 变了也不怕。

自定义 bridge 和默认 bridge 还有一个差异:默认 bridge 的容器之间无法直接相互 ping 通,除非用--link临时建立连接;自定义 bridge 默认允许所有容器互通。如果你有严格隔离需求,可以在创建网络时加上--internal,或者用--com.docker.network.bridge.enable_icc=false这类选项关掉容器间通信,默认打开的互通方式在公网环境里可能会带来安全风险。

容器已经跑在默认网络了怎么办?不需要删除重建,可以直接执行:

docker network connect app-net mysql8

这样容器就同时挂在两个网络上,既保留原有的通信路径,又多了一个自定义网络的接入点。等确认老路径没人用了,再docker network disconnect docker0 mysql8。

4.3 端口映射:三种姿势和它们的坑

Docker 发布端口最基础的是-p 宿主机端口:容器端口,但我见过不少人把这个写法拿上线之后被安全团队找上门。原因很简单:-p 3306:3306会绑定宿主机所有网卡地址,外部所有 IP 都能摸到你的 3306 端口,这和生产环境安全基线直接冲突。

更稳妥的写法是显式绑定回环地址:

docker run -d --name mysql8 -p 127.0.0.1:3306:3306 mysql:8.0

这样只有本机能访问,应用需要连接时走宿主机上个 3306,外部网络完全无法直连。如果需要外网访问某些端口,建议只单独映射需要的 IP 和端口,不要图省事直接全量暴露。

还有一种姿势是让 Docker 随机分配宿主机端口:-p 80,宿主机会随机挑一个高位端口映射到容器的 80。这个方式在自动化部署里有用,但人肉管理时非常容易找不到端口,不建议日常使用。

检查端口映射是否生效,先看ss -tlnp | grep 3306确认宿主机监听是否正常,再进入容器docker exec mysql8 ss -tlnp确认容器内的进程是不是真的监听在 3306。常见坑是容器里的服务只听 127.0.0.1,Docker 端口映射把它暴露出来也没用,外部连接会一直超时。MySQL 默认绑定地址允许所有接口还好,某些开发框架默认绑 127.0.0.1 就会莫名出现这个问题。

另一个隐蔽坑是 MTU。如果宿主机所在物理网络 MTU 不是标准 1500,比如某类专用链路只有 1400,容器里的包会过大导致连接建立后传数据很慢,甚至 ping 不通。解决方式是在 daemon.json 中设置:

{ "mtu": 1400 }

或者创建自定义网络时指定:

docker network create --opt com.docker.network.driver.mtu=1400 app-net

在线排查时很难想到 MTU 头上,所以我把这个坑放在显眼位置,希望大家注意。

4.4 防火墙和 iptables 的恩怨纠葛

Docker 的端口映射依赖 iptables 规则,而这正是冲突高发区。很多服务器开着 firewalld 或 ufw,用户在自己改防火墙后,发现容器网络突然不正常,其实背后是 Docker 在 iptables 里维护的链条和你手动规则打架了。

Docker 启动时会在 iptables 里插入 DOCKER 链,并把流量引导到对应链上做 DNAT。如果你自己在 FORWARD 链上加了DROP规则,或者动了默认策略,纯转发流量可能被拦掉,导致容器之间、容器与外部网络的通信全部断掉。典型现象是容器还能启动,但docker exec到容器里 ping 外网没响应。

解决办法不是把防火墙关了。我建议按这个顺序来:如果使用 firewalld,端口映射的流量请直接用firewall-cmd --add-port=3306/tcp --permanent放行,让 firewalld 和 Docker 各管各的,避免手动改 iptables。如果是 ufw,需要调整 /etc/default/docker 里指定的 iptables 行为,但最简单的还是别让 Docker 和 ufw 抢规则,你可以给宿主机只放行必要的端口,其它交给 Docker 自身的 iptables 处理。

还有人喜欢装完 Docker 后执行iptables -F清空规则,我劝你不要这么做。清空规则等于把 Docker 的网络规则一并干掉,容器网络立刻乱套,而且很多时候不会自动恢复,只能重启 Docker 服务或者重启机器。如果已经发生这种事故,先systemctl restart docker看规则是否重建,不行再检查 DOCKER 链是否存在。

5. 实战:用自定义网络部署 MySQL 8.0 和 Redis 主从

5.1 MySQL 8.0 容器化,这一步踩坑最多

理论讲了这么多,还是落到具体实例上更直观。用 Docker 部署 MySQL 8.0 是很多人练手的第一站,但恰恰是这个过程会暴露不少问题。我的建议是先用自定义网络把基础设施搭好,再跑容器。

先创建网络,避免所有容器堆在默认 bridge 上:

docker network create app-net

然后运行 MySQL:

docker run -d \ --name mysql8 \ --network app-net \ -e MYSQL_ROOT_PASSWORD=root123456 \ -e MYSQL_DATABASE=app_db \ -p 127.0.0.1:3306:3306 \ -v mysql-data:/var/lib/mysql \ mysql:8.0

这里有几个选择需要解释。环境变量MYSQL_ROOT_PASSWORD只在首次初始化数据目录时生效,如果数据卷里已经有旧数据,改密码环境变量不会自动帮你重置。所以第一次创建容器时密码务必想好,不然后面改起来得手动进容器执行 SQL。

-v mysql-data:/var/lib/mysql用的是命名卷,不是主机路径,这是我最推荐的持久化方式。命名卷由 Docker 管理,备份时直接tar卷所在目录即可,不容易搞出文件属主混乱。如果你非要挂主机目录,例如-v /data/mysql:/var/lib/mysql,初始化后数据目录属主是 mysql 用户(uid 999),主机上的权限映射可能会让你头疼,需要chown -R 999:999 /data/mysql才能正常读写。

启动完成后,验证容器状态和连接:

docker exec -it mysql8 mysql -uroot -p

这里有个高频报错:外部客户端连接时报Public Key Retrieval is not allowed。原因是 MySQL 8.0 默认 caching_sha2_password 插件,客户端需要先拿到公钥。连接参数里加上allowPublicKeyRetrieval=true就能解决,命令行客户端则一般没这个问题。

5.2 Redis 主从容器化,注意版本差异

Redis 部署也放在 app-net 网络里,方便和 MySQL 互相通信。先启动主节点:

docker run -d \ --name redis-master \ --network app-net \ -p 127.0.0.1:6379:6379 \ redis:7 \ redis-server --appendonly yes

然后创建从节点。这里要注意,Redis 5 以前的版本用的是--slaveof,Redis 7 已经用--replicaof替代了。容器镜像默认跑最新版本时,写 slaveof 虽然也有兼容层,但日志里会提示参数已过时。我直接写新写法:

docker run -d \ --name redis-slave \ --network app-net \ redis:7 \ redis-server --replicaof redis-master 6379

两个容器都挂在 app-net 网络,从节点里的redis-master会自动解析成主节点容器 IP。这里再次体现出自定义网络的价值:如果主节点容器重建,IP 变了,从节点的配置不用改,只要容器名不变就能重新跟上。

验证主从状态:

docker exec -it redis-slave redis-cli info replication

输出中能看到role:slave和master_link_status:up就算成功。如果master_link_status:down,先确认两个容器是否在同一网络,然后检查网络内 DNS 解析:docker exec redis-slave getent hosts redis-master,没有输出就是网络隔离问题了。

生产环境如果给 Redis 配置了密码,主从连接也要同步处理。主节点启动参数加--requirepass yourpass,从节点则要加--masterauth yourpass,否则主从复制会报NOAUTH Authentication required。这个坑非常经典,配置密码后容易忘掉从节点也需要认证。

5.3 数据持久化和容器重建的权限教训

说完 MySQL 和 Redis,单独提一下容器重建这个高频动作。很多朋友改错配置后会把容器docker rm再重新docker run,如果你用--name重写了容器名,没问题;如果你没删干净或者名字冲突,会直接报Conflict。我的习惯是先docker inspect看当前容器的挂载和网络配置,再决定是否重建。

命名卷的最大优势就在这里:重建容器时只要挂载同一个卷,数据不会丢。比如 MySQL 升级:先docker pull mysql:8.0.36,然后停掉旧容器,用新镜像重新创建容器并挂载原来的 mysql-data 卷。启动后数据自动回来。要注意如果换了大版本号,比如从 5.7 升 8.0,MySQL 会做数据字典升级,建议先备份再操作。

权限问题在数据卷场景下特别爱出幺蛾子。我遇到过几次容器内写不了数据的情况,查了半天发现是主机目录属主和容器进程 UID 对不上。命名卷在首次创建时 Docker 会把卷初始化为镜像里对应目录的属主,一般没事;主机目录挂载则不会做这个处理,需要你手动chown。理解这一点以后,我再也没在权限问题上浪费过时间。

6. 常见问题速查与排查心法

6.1 一张速查表,先把高频问题对号入座

这些年我收集到的 Docker 网络问题,表面上五花八门,内核原因翻来覆去就那么几个。我把最常遇到的整理成了一张表,遇到问题时先对号入座再动手。

现象大概率原因排查方向
容器间 ping 不通不在同一网络,或默认 bridge 没有互通docker network inspect确认成员
容器名解析失败容器不在同一自定义网络让容器 join 同一网络
容器能 ping 通但连不上端口容器内服务只监听 127.0.0.1进容器检查监听地址
端口映射后外部访问超时防火墙拦截或 iptables 规则被破坏iptables -L -n查看 DOCKER 链
MySQL/Redis 连接被拒网络隔离或密码配置不对先 ping 通,再看授权和认证
容器重启后 IP 变了默认 bridge 动态分配改用容器名访问,别写死 IP
外部访问不到但本机可以绑定地址写错或防火墙未放行检查-p 127.0.0.1这类绑定
容器启动后马上退出启动命令前台进程退出docker logs <容器名>看日志

这张表不能代替深入排查,但能帮你把思路拉到正确方向。很多时候我们卡住,就是因为开始就查错了地方。比如容器名解析失败,跑去改应用配置,折腾半天才发现容器根本不在同一个网络里。

6.2 排查用的黄金命令组合

如果上面表格不够用,我给你一套通用的排查链路,按顺序跑一遍,大多数问题都能定位到层。

第一步,看容器本身是否正常:

docker ps -a docker logs --tail 50 <容器名> docker inspect <容器名>

docker inspect的输出信息非常多,我习惯用格式化提取关键字段。比如查看容器挂在哪些网络:

docker inspect -f '{{json .NetworkSettings.Networks}}' <容器名> | python3 -m json.tool

查看网络内部成员和配置:

docker network inspect app-net

这里能看到每个容器的 IP、网关、DNS 配置。如果容器不在这个网络的列表里,那通信问题基本就找到原因了。

第二步,进容器看网络层是否正常:

docker exec -it <容器名> sh cat /etc/hosts cat /etc/resolv.conf ping <对方容器名>

如果ping命令在容器里不存在,也别慌,很多精简镜像没装网络工具。可以换成getent hosts <容器名>来测试 DNS 解析,这是 glibc 自带的,绝大多数镜像都有。

第三步,回到宿主机看 iptables 和端口。容器服务正常但外部无法访问时,多半是宿主机层面的问题:

ss -tlnp | grep <端口> iptables -L -n | grep DOCKER iptables -t nat -L -n | grep DOCKER

重点检查 NAT 表里有没有把宿主机端口 DNAT 到容器 IP 的规则,以及 FORWARD 链有没有被其他策略阻断。如果 DOCKER 链的规则被冲散,重启 Docker 服务通常能重建,但也要记得检查自定义网络是否恢复。

6.3 最后分享一点我从实践里换来的经验

文章写到这里,核心内容基本覆盖了。最后我想聊一个比较抽象但也特别重要的点:Docker 的网络问题往往不是单一原因,而是“配置叠加”造成的。

比如你改了 daemon.json 里的 iptables 为 false,又手动加了防火墙规则,最后发现容器网络不通。这时候你单查任何一个环节可能都觉得“我配置没问题”,但问题恰恰出在多层配置互相影响。我的排查习惯是,先把系统恢复到最小可运行状态:停掉 Docker,清空 iptables 规则,确认主机网络正常,再逐个叠加修改项,每加一个就测试一次。虽然这个过程琐碎,但定位效率非常高。

再送一个实用习惯:所有生产环境的容器,我都建议显式指定--network,不要依赖 Docker 默认 bridge。默认 bridge 虽然在 Docker 历史版本里是默认主力,但它的容器名解析能力天然有缺陷,与其日后逐个排查,不如一开始就用自定义网络。另外,容器名一旦定下来就不要频繁改名,很多脚本和应用配置都依赖这个名字。改名导致的连锁故障,比 IP 变更还要隐蔽,因为日志里根本不会给提示。

还有一个值得记住的细节:改 daemon.json 之后不要随手systemctl restart docker。如果机器上跑着大量容器,重启守护进程会让容器全部中断重建,哪怕配置只改了一个无关紧要的参数。先用kill -SIGHUP $(pidof dockerd)让守护进程重新加载配置,或者确认 live-restore 开启后再重启。这个细节在线上环境里价值极高,我曾经因为改个日志参数把整个业务容器重启了一遍,教训特别深刻。

我对 Docker 的态度一直都是:工具越强大,越要搞清楚它替你做了什么。安装可能只需要一条命令,但配置和网络需要你真正理解设计思路。本篇文章把这些内容讲透了,下篇计划聊聊镜像管理、容器编排和资源限制,到时候再把我踩过的新坑带给大家。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询