
聊到服务器端的容器化Docker CentOS 这套组合我估计只要是做过后端部署、运维或者自己折腾过服务器的朋友多多少少都绕不开。刚入行那会儿我也以为 Docker 就是个“轻量级虚拟机”后来折腾明白之后才发现这玩意儿最值钱的地方根本不是虚拟化而是它把应用、运行时、系统依赖、配置打包成了一个标准化的“集装箱”让“在我机器上能跑”这句话彻底变成了过去式。这篇系列的第一篇我会用尽量通俗的方式把 Docker 的核心概念捋清楚然后带着你在 CentOS 7 上一步步完成安装和基础配置。这一套流程在你日后部署 MySQL、Redis、Nginx 或者任何中间件的时候全都用得上属于打地基的活儿。这篇内容适合两类人一类是刚接触 Linux 服务器想把服务跑起来但被各种依赖问题折磨到头秃的新手另一类是在开发环境里用过 Docker Desktop但对 Linux 服务器端的安装、服务管理、排障还很陌生的朋友。看完这一篇你至少能拥有一个“随时可以初始化容器环境”的干净机器并且对镜像、容器、仓库这几个概念不再停留在字面理解上。1. Docker 核心概念镜像、容器、仓库先分清这三板斧说实话Docker 官方文档里的概念定义写得非常严谨但新手看完了往往还是懵。我习惯用“做菜”和“开店”来类比你听完这一套再回头看官方文档就会顺很多。镜像Image就是一个只读的、分层打包好的模板相当于一道菜的“标准菜谱预制菜包”。里面已经包含了操作系统的基础文件、运行时环境比如特定版本的 JDK、Python、应用代码和所有配置文件。你不需要关心它内部的细节只需要拿着这个模板去“做菜”。容器Container则是镜像运行起来之后的实例相当于按照菜谱做出来的、正在顾客桌上冒着热气的那盘菜。它和我们直接在宿主机上跑一个进程最大的不同是每个容器都拥有自己独立的文件系统、网络栈和进程空间但底层共享的是宿主机的内核。容器可以被启动、停止、删除也可以在里面执行命令、看日志、改文件但这所有的改动都只存在于这个容器的可写层里镜像本身完全不受影响。仓库Registry是存放和分发镜像的“菜谱库”。Docker Hub 是官方默认的公共仓库就像 GitHub 之于代码一样你可以从上面拉取pull别人做好的环境模板也可以把自己做好的镜像推上去push分享或者备份。不过国内直连 Docker Hub 的速度经常感人所以我们后面会专门配置加速器。很多朋友容易搞混“镜像”和“容器”的关系我再说一个更接地气的类比镜像好比是程序安装包里的 ISO 文件或 exe 安装程序它是静态的、不能直接运行的容器则是这个安装包安装完成后、正在运行中的那个程序。程序可以随时关闭停止容器但你下次再想开还是得用那个安装包镜像重新起一个实例运行容器。这也是 Docker 的一个核心逻辑以镜像为模板容器为可抛弃的运行态。任何对容器的修改如果你希望保留就得通过 commit 或 Dockerfile 重新构建成新镜像否则容器一删一切归零。1.1 镜像的“分层存储”是什么这是 Docker 最精妙的设计之一也是很多人理解不了的细节。传统虚拟机镜像是一个完整的大文件几百 MB 甚至几个 GB 都是常态。而 Docker 镜像是由多个只读层Layer叠加而成的每一层都记录了与下一层的差异。拉取镜像是按层并行下载的所以经常看你执行docker pull的时候输出里会出现好多层的下载进度这就是它在拉取这些差异层。更重要的是多个镜像可以共享底层。比如你拉了一个带 JDK 的镜像和另一个不带 JDK 的镜像如果它们的底层 OS 层相同那么这部分占用的磁盘空间只需要存一份能省下大量磁盘。这也是为什么 Docker 镜像仓库里的镜像通常不会做得特别“大而全”而是尽量精简底层的系统依赖。当你用镜像启动容器时Docker 会在所有的只读层之上再挂载一个可写层。所有对容器文件系统的读写操作都发生在这个可写层内。这就意味着容器之间互不影响即使同时启动 10 个同样的镜像各自的可写层都是独立的。删除容器时如果没有显式地docker commit保存这个可写层连同里面的数据一起被丢弃。理解了这一点你就明白为什么官方一直强调“容器不保存数据”这个最佳实践真正需要持久化的数据一定要挂载到宿主机目录或独立的存储卷Volume里。这个话题后面讲 MySQL 部署的时候还会重点说。1.2 容器到底是不是“轻量级虚拟机”容器不是虚拟机。这两者的本质区别我用了很久才真正体会到虚拟机通过 Hypervisor 虚拟出一整套硬件然后在上面跑一个完整的操作系统Guest OS。这意味着虚拟机里的内核和宿主机内核是两个独立的东西所以虚拟机的隔离性更强但资源开销也更大启动通常要几十秒到几分钟。容器则直接共享宿主机内核通过 Linux 内核的 Namespace命名空间来做进程、网络、文件系统的隔离通过 Cgroups控制组来做 CPU、内存、磁盘 IO 的资源限制。它没有自己的内核所以启动一个容器本质上就是启动一个或一组进程启动速度是毫秒到秒级资源开销极小。对比项虚拟机Docker 容器隔离级别硬件级虚拟化进程级隔离内核独立Guest OS共享宿主机内核启动速度几十秒到分钟级毫秒到秒级资源占用GB 级起步MB 级起步性能损耗较高极低这个区别带来的一个直接后果就是容器的兼容性和宿主机内核强绑定。比如你在 CentOS 7内核 3.10上可以直接跑基于 Alpine、Ubuntu 等不同发行版制作的镜像因为容器应用只依赖操作系统用户态的文件和库不依赖内核版本。但你没法在一个内核版本很老、或者缺少某些模块比如 iptables、overlayfs的机器上顺利运行需要较新内核特性的容器。这也是为什么我们接下来要专门聊 CentOS 版本选择的问题。2. CentOS 环境准备版本选择和安装前的关键检查很多教程上来就让你敲安装命令忽略了环境准备。但我在实际部署中吃过太多亏装完了才发现内核太老导致 Docker 起不来、yum 源慢到怀疑人生、磁盘分区没预留空间导致镜像拉下来没地方放。所以这个环节我劝你耐住性子看下来这些都是前人拿真金白银时间换来的教训。2.1 CentOS 7.9 还是 CentOS Stream我为什么推荐前者这个选择题很多新手可能根本没意识到。简单说CentOS 7.9是 CentOS 7 系列的最后一个小版本2020 年发布它的生命周期已经于 2024 年 6 月 30 日结束EOL。但网上绝大多数教程、生产环境存量机器、云厂商的公共镜像都基于它所以兼容性和资料丰富度极高踩坑也容易搜到解决方案。CentOS Stream 8是一个滚动更新的发行版它介于 Fedora 和 RHEL 之间。它的问题是生命周期更短且作为“滚动版”其软件包更新节奏和稳定性对于服务器场景来说没有那么友好。那对于学习 Docker 和部署日常应用我的建议是如果你是在本地虚拟机或自己买的 VPS 上折腾首选 CentOS 7.9。原因很朴素你用到的各种中间件MySQL、Redis、Nginx的 Docker 镜像全都是基于长期稳定的底层系统构建的对 CentOS 7 这种“老而稳”的系统毫无压力。而且CentOS 7 的用户基数极大你在安装过程中遇到的任何报错几乎都能搜到前人的解决方案。2.2 三分钟检查内核、磁盘、网络一个都不能少在动手安装 Docker 之前我每次都会登录服务器先跑一遍这三条命令做基础体检# 查看内核版本Docker 要求 CentOS 7 内核不低于 3.10 uname -r # 查看磁盘剩余空间建议至少 10GB 以上镜像和容器日志都吃空间 df -h # 查看操作系统版本 cat /etc/redhat-release内核版本这个坑我必须多说两句。Docker 官方对 CentOS 7 的最低要求是内核 3.10但你如果用的是一台非常老的机器或者某个厂商定制过的精简内核版本号很可能是 2.6 之类的远古版本。这种情况下即使你把 Docker 装上了运行容器时也会大概率报错比如iptables相关的问题。如果内核不达标不要试图在旧内核上硬扛老老实实升级内核或者换系统这是正经的解法。磁盘这块我建议无论如何要给/var/lib/dockerDocker 默认的数据目录所在分区留出足够的空间。镜像、容器日志、数据卷全都在这个目录底下。我见过不少生产事故就是日志没做轮转把磁盘塞满导致整个 Docker 服务直接进入假死状态。网络方面你需要保证服务器能访问外网尤其是能访问 Docker 的软件源和镜像仓库。如果你的服务器在国内强烈建议在安装之前就先配置好国内镜像源阿里云、清华源等不然你可能会在一个 yum 下载步骤上卡到怀疑人生。2.3 配置 yum 源与基础环境初始化CentOS 7 默认的 yum 源是官方的 mirrorlist在国内访问速度往往不理想。我个人的习惯是新机器到手第一步就是换成阿里云的 yum 源这一步能让你后续所有软件安装的体验提升一个档次。# 备份原始的 yum 源配置 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup # 下载阿里云的 CentOS 7 yum 源配置 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo # 清理缓存并重新生成 yum clean all yum makecache这里有一个容易踩坑的细节阿里云的Centos-7.repo文件里默认的$releasever变量会被替换为 7但有些定制过的系统里这个变量可能解析不出来。如果你执行yum makecache时报错提示找不到 mirrorlist可以在命令后面加--releasever7手动指定一下。基础环境方面还需要装一些常用的工具比如yum-utils提供yum-config-manager命令、vim、net-tools提供ifconfig排查网络时用得到。这些不是 Docker 的必须依赖但属于我个人的“新机器安装标准动作”。yum install -y yum-utils vim net-tools3. 安装 Docker 的完整实操流程环境准备好了接下来就是安装环节。推荐使用 Docker 官方提供的docker-ce.repo来安装这样可以保证安装的始终是最新稳定版并且后续升级也方便。3.1 先卸载旧版本避免装了一堆“历史包袱”如果你这台机器以前装过 Docker 或者其它容器运行时比如 containerd 的老版本、podman 等建议先清理干净。不清理的话很有可能出现端口冲突、命令找不到、版本混乱等莫名其妙的 bug。# 卸载系统里可能存在的旧版本 Docker yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine卸载完成后/var/lib/docker目录会被保留下来里面可能存了你之前的数据。如果你是全新安装建议直接把这个目录也删掉反正里面也是旧数据rm -rf /var/lib/docker3.2 配置 Docker 官方 yum 源附加速方案这一步是能否顺利完成安装的关键。执行下面的命令把 Docker 官方的 yum 源添加到系统里# 添加 Docker 官方源到 yum 配置使用 yum-config-manager 工具 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo但这里有个现实问题download.docker.com在国内的连通性时好时坏。我自己的习惯是直接用阿里云的镜像源替换速度稳定得多# 使用阿里云 Docker 源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 替换源内地址中的 download.docker.com 为 mirrors.aliyun.com sed -i s/download.docker.com/mirrors.aliyun.com/g /etc/yum.repos.d/docker-ce.repo替换完以后跑一下yum makecache让源缓存生效。这时候你可以yum list docker-ce --showduplicates | head -20看一下能看到哪些可用的 Docker 版本。这个命令在日后你因为安全原因需要固定某个特定版本时非常有用。3.3 安装 docker-ce、启动并设置开机自启接下来就是正菜了。我会把 Docker 的几个核心组件一起装上docker-ceDocker 的核心守护进程dockerd。docker-ce-clidocker命令行工具。containerd.io容器运行时负责真正去启动和管理容器生命周期。早期版本的 Docker 直接管理容器现在则通过 containerd 这个中间层来和底层内核打交道。docker-buildx-plugin、docker-compose-plugin构建镜像和多容器编排的插件新版本 Docker 都推荐用这俩。# 安装 Docker 及配套组件 yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin安装完成后启动 Docker 服务并设置为开机自启# 启动 Docker 服务 systemctl start docker # 设置开机自启 systemctl enable docker # 查看服务运行状态确认是 active (running) systemctl status docker看到active (running)的输出说明 Docker 的守护进程已经跑起来了。它会在/var/run/docker.sock创建套接字等待来自docker命令行工具的连接。3.4 使用 hello-world 验证安装并理解验证逻辑安装完成后第一次跑docker run hello-world是这个领域不成文的规定相当于是“点亮”你的容器引擎。执行这个命令之后Docker 会做几件事检查本地有没有hello-world镜像没有就去仓库拉取。拉取完成后创建一个容器实例并运行。容器启动后打印一段欢迎信息然后正常退出。docker run hello-world如果你的终端打印出了一段英文的欢迎文字说明 Docker 已经可以正常工作。但我要多说一句hello-world 镜像很小且没有实际业务逻辑它能验证的是 Docker 引擎的“拉起容器、执行进程”这条链路通了但验证不了网络互通、端口映射、存储卷挂载等更加重要的能力。真正的实战验证我建议你后续拿 Nginx 镜像来测比如docker run -d --name test-nginx -p 8080:80 nginx然后在浏览器里访问服务器的 8080 端口能出现 Nginx 默认欢迎页这才算真正“能用于生产”的 Docker。这也是我在培训新同事时经常让他们做的事比光看 hello-world 的输出有价值多了。4. 安装后的基础配置与优化Docker 能跑起来只是第一步距离“用得顺手”还有几项配置要完成。尤其是网络环境特殊的用户下面这几步不做后面拉取镜像时人会崩溃的。4.1 配置镜像加速器解决拉取镜像慢的终极方案我在前面反复提到国内直连 Docker Hub 很慢这不是玄学而是网络链路导致的现实问题。解决办法就是配置一个 registry mirror镜像加速器让 Docker 在拉取镜像时优先从你指定的镜像仓库获取数据。编辑 Docker 的配置文件/etc/docker/daemon.json现在这个文件大概率还不存在需要手动创建vim /etc/docker/daemon.json写入如下内容我用的是阿里云个人加速地址的格式也可以使用其他国内服务商提供的加速地址{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] }提示加速器的可用性受网络环境影响较大过一段时间可能会失效。失效的排查方法很简单docker pull如果长时间卡在Waiting或者直接报timeout优先怀疑加速器失效换一个地址再试。修改完配置后重启 Docker 服务让配置生效systemctl daemon-reload systemctl restart docker配置生效后可以查看当前 Docker 的配置信息确认镜像加速器已加入成功docker info | grep -A5 Registry Mirrors4.2 用户权限管理把 docker 命令授权给普通用户默认情况下执行docker命令需要 root 权限这是因为 Docker CLI 需要访问/var/run/docker.sock这个 root 用户拥有的套接字。现实中的服务器一般不会直接用 root 操作所以把普通用户加入 docker 用户组是个常规操作。# 在 root 下执行把用户名为 devops 的用户加入 docker 组 usermod -aG docker devops # 重新登录该用户或者执行 newgrp docker 快速切换这里有一个非常隐蔽的坑加入 docker 组之后必须重新登录终端新终端里的用户会话才会带上新的组权限。如果你用 SSH 连接必须退出重连。继续使用旧终端执行docker命令还是会提示权限不足。很多新手在这里卡了半天其实只是会话没刷新。另外一个更加隐蔽的安全性问题是能执行 docker 命令的用户就等同于拥有了这台机器的 root 权限。因为绕开 sudo直接挂载宿主机根目录到容器里就能任意读写宿主机文件。所以docker 用户组只能添加你完全信任的管理员账号不能把业务账号或者低权限账号加进去这是安全红线。4.3 防火墙与 SELinux 的处理策略CentOS 7 默认开启了 firewalld 防火墙和 SELinuxSecurity-Enhanced Linux安全增强型 Linux它们和安全相关但在容器网络场景下往往会给你带来一些“非预期内”的阻碍。先说我推荐的策略生产环境中不要在 firewalld 里粗暴地firewall-cmd --permanent --add-port8080/tcp一个个给容器端口放行。因为容器每次重启端口映射规则是基于 iptables 动态生成的firewalld 和 Docker 在 iptables 规则管理上偶尔会产生冲突。更省心的做法是如果服务器的业务流量都经由容器对外提供服务可以考虑直接关闭 firewalld。这在实际的 Docker 部署场景中是最常见的处理方式。# 停用并禁用 firewalld systemctl stop firewalld systemctl disable firewalld如果你的公司安全规范比较严格不允许关闭防火墙那么至少需要放行 Docker 相关的端口。要知道如果 firewalld 在 Docker 创建端口映射之后重启有可能会导致已有的端口映射规则被清除这是一个经典的坑我后面会在排查部分细说。然后说 SELinuxSELinux 的默认状态是 enforcing强制模式它对进程能访问的文件、网络端口有严格限制。Docker 容器在挂载宿主机目录时经常会因为 SELinux 上下文的问题而报 permission denied。网上有一种处理方式是把 SELinux 直接禁用一劳永逸。但我的建议是SELinux 不要轻易关闭你可以先用 setenforce 临时设置为 permissive宽松模式来验证问题是不是 SELinux 引起的# 临时设置为 permissive 模式无需重启 setenforce 0如果设置为 permissive 之后容器恢复正常再考虑永久修改/etc/selinux/config。生产环境如果不想彻底关闭 SELinux更标准的做法是使用特权容器priv容器比如docker run --privileged或在 dockerfile 中加入相关配置但这会带来一定的安全风险建议根据实际场景权衡。4.4 数据目录迁移别让 /var/lib/docker 拖垮系统盘Docker 默认将镜像、容器、数据卷存放在/var/lib/docker目录下。很多时候我们部署的服务器系统盘只有 40G 或者 60G而数据盘比如 /data 分区有几百 G。如果你打算用 Docker 跑 MySQL、ES 这类重度依赖磁盘的应用默认的 Docker 数据目录很快就会把系统盘塞爆。迁移方法也很简单修改daemon.json增加>{ data-root: /data/docker, registry-mirrors: [https://docker.m.daocloud.io] }然后重启 Docker 服务systemctl daemon-reload systemctl restart docker重启之后docker info里显示的 Docker Root Dir 就会变成/data/docker。要注意的是已有容器和数据不会自动迁移迁移操作最好在没有重要容器运行的初期就完成。如果你已经跑了一些容器那么需要先docker export或从镜像重新创建不能直接复制文件夹就完事否则组件会因路径错乱而无法启动。5. 高频问题排查与避坑实录这部分是全文的压轴干货。我把自己和身边同事在实际环境中踩过的一些高频坑整理成了速查表配上排查思路方便你以后遇到类似问题时可以直接照着做。5.1 问题一docker 服务启动失败日志报错怎么看安装完 Docker执行systemctl start docker之后如果一直卡住或者报Job for docker.service failed because the control process exited with error code不要慌像侦探一样看日志。# 查看 docker 服务的详细报错日志 journalctl -u docker --no-pager | tail -50在内网机器上最常见的几个报错原因iptables failed: iptables --wait -t nat -A DOCKER: 内核模块或者 iptables 相关工具版本有问题可以尝试先重启机器加载内核模块或者安装iptables-services包。Error starting daemon: error initializing network controller: error creating default bridge network: 之前残留的网络配置冲突经典的解法是清空/var/lib/docker下面的文件或者重启机器。failed to start daemon: Error initializing network controller: Error creating default bridge network: package not installed: 缺少bridge-utils执行yum install -y bridge-utils即可。另外我强烈建议排查 Docker 问题时不要只看 docker 命令的输出要习惯性地去看systemctl status docker和journalctl -u docker的底层日志。很多错误信息在 CLI 中被简化了只有日志里才有完整的上下文。5.2 问题二docker pull 拉取镜像超时或失败时间超时通常表现为net/http: TLS handshake timeout或者dial tcp ... i/o timeout。如果你之前已经配置了加速器但依然超时大概率是加速器地址已经失效或者 docker daemon 没有正确加载配置。正确的排查步骤确认配置文件/etc/docker/daemon.json里 JSON 语法没有错误。确认docker info里能看到 Registry Mirrors 配置。手动执行docker pull时在命令前面加上-vverbose看详细信息帮助判断卡在哪个环节。尝试直接替换为 Docker 官方源不配置加速器拉取一次小镜像比如docker pull alpine如果官方源反而能拉取成功说明加速器失效更换即可。还有一点如果你的服务器有代理或特殊网络配置记得给 Docker daemon 配置环境变量。在/etc/systemd/system/docker.service.d/http-proxy.conf文件里配置好代理后重启 docker 服务。5.3 问题三容器启动了但端口访问不通用docker run -p 8080:80 nginx起了容器后在浏览器里访问http://服务器IP:8080却打不开。这个情况非常常见排查顺序非常关键先确认容器是否真的在监听docker ps看容器状态docker logs test-nginx看有没有访问日志。再确认端口映射是否正确生成docker port test-nginx查看容器的端口映射关系。然后在宿主机上验证curl -v http://127.0.0.1:8080如果在本地能访问说明 Docker 内部正常。最后查防火墙这是容器端口不通的第一大元凶。尤其是之前提到过如果你开启了 firewalld那么systemctl restart firewalld之后会清空 Docker 在 iptables 中动态添加的 DOCKER 链规则导致端口映射失效。解决办法是把 firewalld 彻底停用或者把 Docker 的网络相关规则在 firewalld 之前加载但后者维护成本太高一般不推荐。5.4 问题四Windows 下用 Docker Desktop 报 virtualization support not detected这个和 Linux 服务器环境不是一回事但很多读者应该会碰到。开发机上用的是 Docker Desktop它依赖 Windows 的 Hyper-V 或者 WSL2 后端来运行真正的 Linux 虚拟机。如果你的 Windows 提示virtualization support not detected核心原因通常是 BIOS/UEFI 里没开启 CPU 虚拟化Intel VT-x / AMD-V。解决路径依次是重启电脑进入 BIOS/UEFI 设置。找到Intel Virtualization Technology或SVM ModeAMD的选项设置为 Enabled。保存重启后在 Windows 任务管理器“性能”标签页里确认“虚拟化”状态是“已启用”。如果你用的是 Win10/11 且已经在 BIOS 里开启了虚拟化但还是报错就考虑更换 Docker Desktop 的后端为 WSL2安装并设置 WSL 2 为默认版本通常能解决这个问题。但如果你是纯 Linux 服务器部署场景这一条可以跳过。5.5 一份顺手可用的问题速查表现象最常见原因排查/解决命令docker 命令提示 Cannot connect to the Docker daemonDocker 服务未启动或守护进程崩了systemctl status docker看日志journalctl -u docker拉取镜像超时加速器失效或网络链路问题检查docker info的 Mirrors换加速器或直连容器端口外部访问不了firewalld 未放行或 DOCKER 链被清systemctl stop firewalldsystemctl disable firewalld挂载目录后容器内看不到数据SELinux 限制临时setenforce 0验证确认后修改配置普通用户执行 docker 命令报权限错误用户未在 docker 组或会话未刷新检查组id $USER重新登录磁盘空间满了但找不到大文件Docker 数据目录暴涨du -sh /var/lib/docker/*配置>{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这样单个日志文件最大 10MB最多保留 3 份超过就自动轮转覆盖。有这一行配置在至少能保证你的容器不会因为日志把自己所在的宿主机磁盘写爆。行文至此Docker 核心概念和 CentOS 安装配置这部分的内容也就算完整了你把上面这些步骤和坑都过一遍一台能跑容器的机器基本就稳了。后续我会继续沿着这个系列讲讲镜像管理、容器操作和数据卷挂载这些实战内容那时候你再回头看今天配置的这些环境会顺畅得多。