说真的,接触Docker这么多年,我最大的感受就是:它是一个“用一次就回不去”的工具。尤其是在CentOS这种服务器环境上,以前部署一个应用要装依赖、调环境变量、处理端口冲突,折腾半天;现在一个镜像、一条run命令,三十秒跑起来,换机器也不怕。这篇文章是“Docker 从入门到实战”系列的第一篇,我会把Docker 核心概念和CentOS 下的安装配置这两块一次讲透,适合刚接触容器、想在服务器上把Docker跑起来的新手,也适合那些已经装过但没搞明白原理的人。
我会尽量不绕弯子,用实际踩坑经验加命令示例来讲。你不需要提前懂Kubernetes、不需要懂Cgroup,只需要一台能联网的CentOS机器和基本的Linux命令行操作能力。读完这篇文章,你会搞懂镜像和容器到底是什么关系、CentOS版本该怎么选、yum源怎么配、Docker装完之后怎么验证、启动失败和网络不通这类问题怎么排查。不光是“照着敲能跑”,而是“敲完知道为什么”。
1. 先从核心概念说起:镜像、容器、仓库是什么关系
1.1 镜像不是安装包,而是“文件系统快照+启动配置”
很多新手刚开始接触Docker,最容易犯的错,就是把镜像(Image)理解成类似tar.gz的安装包。其实不是。镜像是分层存储的只读模板,它里面打包了完整的文件系统——包括操作系统的基础库、运行时、配置文件、环境变量,以及你要运行的应用代码。但它本身不是一个“正在运行的程序”,更像是一台电脑的硬盘克隆文件,随时可以复制、分发、启动。
举个例子:你可以在网上找一个CentOS 7.9的基础镜像,它里面就有完整的根文件系统,bin目录、usr目录、lib库都在。你在这个基础上安装MySQL、装上PHP、写好配置,然后通过commit或者Dockerfile把这一层一层的改动固化成一个新镜像。这个新镜像拿到任何一台装了Docker的机器上,都能跑出一模一样的环境。
这里有个关键特征:镜像是分层的。Dockerfile里每一条指令生成一层,底层镜像被多个上层镜像共享。所以拉取一个大型镜像时,你可能会看到“Already exists”这样的提示,说明这一层之前已经拉过了,不需要重复下载。这也是Docker能省磁盘空间、能瞬间秒开多个容器的底层基础之一。
理解这一点,你会明白一个实操中的常见现象:为什么同一个镜像启动的多个容器,各自修改文件互不影响。因为容器启动时,Docker会在镜像之上挂载一个可写层,所有写入都发生在这一层,底层镜像始终不变。一旦容器删除,可写层跟着销毁,就“一切归零”。
1.2 容器是镜像的“运行实例”,状态是临时的
容器(Container)就是镜像运行起来之后的状态。你可以把它理解成“一个独立的、隔离的进程组”。隔离靠的是Linux内核的Namespaces,资源限制靠的是Cgroups,这两者正是容器和虚拟机最大的差异所在。虚拟机要多跑一个完整Guest OS,容器则直接在宿主机内核上运行。
这带来的好处有两个:一是启动极快,常规容器几百毫秒就能起来;二是密度极高,一台8G内存的服务器,传统虚拟机可能只能跑两三台,容器却能跑几十个实例。但同样的,容器的隔离不如虚拟机彻底。内核是共享的,如果宿主机的内核出问题,所有容器都会受影响。所以生产环境一般会限制容器权限、配置Seccomp、只读挂载等,防止容器内权限提升。
容器状态是临时的,这个真的很重要。容器一旦rm删除,里面写入的所有数据都没了。所以做数据库这类有状态服务时,必须挂载宿主机目录(volume)或者使用命名卷,否则重启就丢数据。我见过不止一个人用Docker跑MySQL,跑了两周,然后一条docker rm把整个数据库删了,数据全丢。这不是笑话,是真实事故。后面讲安装配置时,我会把这部分操作讲得更细。
1.3 仓库就是镜像的“应用商店”,默认是Docker Hub
仓库(Registry)是存放和分发镜像的服务。你执行的docker pull命令,默认去的是Docker Hub,这是Docker官方维护的公共镜像仓库。里面有大量官方镜像,比如centos、nginx、redis、mysql,也有个人上传的镜像。
为什么仓库重要?因为拉镜像的速度和可达性,基本决定了你使用Docker的体验。如果你直接连Docker Hub,国内机器拉官方镜像通常不太理想,几个G的大镜像动辄卡半天。解决方案一般是配registry mirror,也就是镜像加速地址。这在下文第4节我会专门讲,这部分配置好之后,拉镜像速度能快一个量级。
连上仓库之后,你需要遵循一套命名规则:镜像地址由仓库域名、命名空间、镜像名、标签组成。比如docker.io/library/nginx:1.24,docker.io是仓库地址,library是官方命名空间,nginx是镜像名,1.24是标签。实际操作中,public仓库可以省略域名和命名空间,直接nginx:1.24就能拉取。不理解这套命名规则,你在配置私有仓库或者写Dockerfile时很容易蒙圈。
核心小结:镜像只读分层、容器临时可变、仓库负责分发。这三者的关系,最通俗的类比是:镜像等于“类”,容器等于“实例”,仓库等于“代码托管平台”。类定义了属性和方法,实例是具体运行的对象,而代码托管平台负责保存和分发你写好的类模板。
2. CentOS环境的前期准备与版本选择
2.1 到底选CentOS 7.9还是8 Stream
我经常被问到一个问题:教程里用的CentOS 7.9,现在还能用吗?该不该上8 Stream?这事得掰开来说。CentOS 7.9是目前存量服务器中最常见的版本,网上绝大多数运维文档、集群部署脚本,都是基于7.x写的。虽然红帽官方已经宣布CentOS 7在2024年6月30日停止维护,但实际生产环境里,7.9的存量依然非常巨大,老项目的稳定性依赖它。
CentOS 8 Stream则是一个滚动发布版本,跟随Fedora的节奏往前迭代,不像7.9那样固定小版本。对于追求新内核、新特性的场景,8 Stream能给你更新的工具链,但同时意味着你的行为模式要跟着上游变化,比如yum部分命令被dnf替代、仓库配置方式也有变化。如果你是为了跑Docker、跑K8s这类现代化服务,8 Stream完全没问题;如果你是为了兼容老业务、照搬老教程,老老实实7.9更省心。
从Docker的角度讲,两种版本都能顺利安装,区别主要在repo源名称和一些依赖包上。Docker官方提供的yum源叫做docker-ce.repo,安装时会自动适配系统的major版本。官方目前仍然维护CentOS 7的Docker包,支持CentOS 8的包也没问题。所以不用太纠结,只要内核在3.10以上,Docker都能跑起来。
2.2 安装前必须确认三件事
正式敲安装命令之前,我先建议你花两分钟确认一下系统状态,免得装到一半出幺蛾子。第一件事是看内核版本,执行 uname -r。Docker对内核的最低要求是3.10,CentOS 7默认内核是3.10.x,够用;CentOS 8一般是4.18或更高,体验更好。内核版本过低时,OverlayFS和某些网络功能会受限,最好还是升级一下。
第二件事是看系统位数,必须64位。执行 uname -m 如果显示x86_64或者aarch64,说明没问题。Docker本身早就不支持32位系统了,你要是手里有台32位的旧机器,别折腾Docker,换个方案吧。
第三件事是确认网络环境。Docker安装过程中要下载大量依赖,安装完成之后还要拉镜像。如果服务器是内网的、或者出口路由受限,你要提前准备好内网源或者离线rpm包。这里特别提醒一下:很多人安装完Docker后,发现docker pull一直卡住,第一反应是代码问题,其实十有八九是仓库网络问题。等下在第四节我会演示镜像加速怎么配置,这里先打个预防针。
命令汇总:
uname -r # 内核版本 uname -m # 架构 cat /etc/redhat-release # 系统版本2.3 防火墙和SELinux的取舍
CentOS默认开着firewalld和SELinux,这两个东西在传统应用上问题不大,但和Docker碰撞时,偶尔会给你添堵。最常见的情况是:容器启动了,端口映射也配了,但宿主机外网就是访问不到。查了半天,发现是firewalld拦截了。另一个情况是SELinux的布尔值没开,导致容器挂载目录时没有权限,报Permission denied。
我不建议你彻底关闭SELinux,除非是在测试环境。生产环境里,更稳的做法是给特定端口放行。比如容器映射到宿主机8080端口,就执行:
firewall-cmd --add-port=8080/tcp --permanent firewall-cmd --reload如果Docker的网桥模式总是被防火墙干扰,关闭firewalld也是一种临时解法,但你自己要清楚风险。SELinux这边,如果只是跑Docker,可以设置容器相关布尔值来适配,并不需要setenforce 0。把SELinux一关了之是很多人的习惯,但真出了安全审计,这个操作就是给自己埋雷。
3. CentOS上安装Docker的完整实操
3.1 配置yum源并安装docker-ce
安装Docker的第一件事,不是直接yum install docker,而是先安装依赖工具并配置官方yum源。CentOS上最顺手的做法是用yum-utils的yum-config-manager指令:
sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo这里有个细节:如果你用的是阿里云、腾讯云这类国内镜像机器,拉取download.docker.com不一定慢,但也不是一直稳定。遇到这种情况,可以直接把repo地址替换成国内云的Docker源,比如阿里云的:
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo添加完repo之后,不需要额外做什么,yum会自动读取。接着执行:
sudo yum install -y docker-ce docker-ce-cli containerd.io这条命令会装好三个核心组件:docker-ce是Docker守护进程和CLI主包,docker-ce-cli是客户端命令集,containerd.io是容器运行时,负责实际创建和运行容器。早期Docker版本直接带containerd,现在拆开了,所以三个一起装不会错。
安装过程中如果提示依赖冲突,多半是你之前用别的源装过docker或者docker-selinux,这时候建议先检查已有包:
rpm -qa | grep docker sudo yum remove -y docker docker-client docker-common注意,这一步不要把已经存在的数据目录删掉,/var/lib/docker里如果有旧数据,卸载包会保留,重新装上又能用。
3.2 启动Docker服务并配置开机自启
安装完成之后,Docker并不会自动运行,你还需要手动启动守护进程。CentOS 7、8都是用systemd来管理服务的:
sudo systemctl start docker sudo systemctl enable dockerstart是立即启动,enable是把Docker加入开机自启列表。我强烈建议每次装完都顺手敲一下enable,否则服务器重启之后Docker不会自动起,所有依赖Docker的服务跟着全部挂掉。
启动之后看一眼状态:
sudo systemctl status docker sudo docker versiondocker version会同时输出Client和Server两段信息。如果Server那段能正常显示版本号,说明守护进程已经在跑了。如果只显示Client、Server是空,说明服务没启动或者启动失败,这时候要去翻日志:
sudo journalctl -xu docker.service对了,CentOS 7上还需要注意一点:如果你之前改过主机名、网络配置,可能影响Docker网络初始化,但一般不会导致服务起不来。真起不来时,先看日志里是不是有“iptables”相关报错,多半是防火墙或转发机制冲突。
3.3 用hello-world和nginx验证安装效果
服务跑起来后,最经典的验证方式是跑一下hello-world:
sudo docker run --name hello-test hello-world第一次执行时,Docker会尝试从官方仓库拉取hello-world镜像。如果拉取成功并且控制台输出一段“Hello from Docker!”的说明文字,恭喜你,Docker的安装和运行链路已经通了。
如果这一步卡住了,八成是网络访问Docker Hub不畅。先别急着怀疑人生,本节后面说到的镜像加速可以解决这个问题。拉取失败是网络问题,不是Docker本身的问题。
再进一步,跑一个Nginx容器来验证端口映射:
sudo docker run -d --name nginx-test -p 8080:80 nginx:latest这里-d表示后台运行,-p 8080:80表示把宿主机的8080端口映射到容器的80端口。运行之后,用curl访问本机8080端口:
curl http://127.0.0.1:8080如果返回Nginx默认页面,说明容器网络完全正常。你还可以用docker ps看到运行中的容器列表,用docker logs nginx-test看Nginx日志。跑通这两个容器之后,你的Docker环境在功能上就算完全可用了。
4. 镜像加速配置与日常操作要点
4.1 为什么需要配置镜像加速,以及怎么配
国内实机拉取Docker Hub官方镜像,速度很不稳定,尤其是大镜像,经常出现拉一半断掉的情况。这是因为公共仓库的服务器物理位置在海外,跨境网络链路复杂。这里说的不是需要用额外手段,而是Docker官方提供了一种叫registry mirror的机制:你配置一个国内可达的公共镜像仓库作为拉取代理,Docker在pull时先访问这个镜像仓库,它再去上游同步,相当于帮你跑了一程。
Docker的配置入口是/etc/docker/daemon.json。如果这个文件不存在,就新建一个。内容如下:
{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://mirror.ccs.tencentyun.com" ] }配置完以后,重启Docker让配置生效:
sudo systemctl daemon-reload sudo systemctl restart docker这里提个醒:如果daemon.json里还有其他配置,比如data-root、exec-opts,合并时千万别把原内容覆盖掉。网上很多教程只贴了registry-mirrors一段,有人直接覆盖了配置,结果Docker数据目录都被挪了,把已有容器整得路径对不上。
配置完成之后,可以执行docker info看一眼Registry Mirrors那一段,如果列出了你配置的地址,说明生效了。
4.2 Docker Desktop和Linux上直接装Docker的区别
热搜词里很多人搜“Docker Desktop”,这里顺便讲清楚。Docker Desktop是面向macOS和Windows的图形化工具,它会在系统里虚拟一个轻量级Linux虚拟机,再在虚拟机里跑Docker服务。好处是免去了你手动装Linux虚拟机的麻烦,坏处是资源占用明显更高,而且它对Windows系统有一项硬性要求:必须开启CPU虚拟化(VT-x/AMD-V)。如果BIOS里没开,启动时会报“virtualization support not detected”之类的错误。
而你在CentOS服务器上直接安装的docker-ce,是在原生Linux内核上运行的,效率更高,不需要任何虚拟机层。两者命令基本通用,Docker Desktop只是多套了层壳。理解这一点很重要,否则你很容易照着一个Windows教程在CentOS上操作,发现一切对不上号。
4.3 给当前用户授权,免去每次sudo
默认情况下,执行docker命令需要root权限,因为Docker守护进程的socket文件/var/run/docker.sock属于root用户。为了避免每条命令都加sudo,可以把当前用户加进docker组:
sudo groupadd docker # 如果docker组不存在才需要 sudo usermod -aG docker $USER改完组之后,要么重新登录,要么执行newgrp docker切换当前会话,否则组权限不生效。这步操作在生活中特别实用,我日常开发时几乎从不打sudo docker,都是直接docker ps。不过要提醒一句:加入docker组等于变相获得了root权限,因为Docker提供的能力可以让你挂载宿主机的任意目录。所以这个操作只建议在自己可控的机器上做,共享机器上要谨慎。
5. 常见安装与使用问题排查
5.1 启动失败:iptables、SELinux、存储驱动三板斧
Docker启动失败是安装过程中最让人头疼的问题。我遇到过几类典型报错,第一类是“Failed to start Docker Application Container Engine”,日志里出现iptables相关字样。这通常是因为Docker需要修改宿主机的iptables规则,但被SELinux或firewalld挡住了。排查思路很简单,先看日志,再定位冲突。临时解法是把firewalld关掉,或者执行systemctl restart docker多试几次,因为有时候是系统iptables服务先占用了规则表。
第二类是SELinux报错,表现为启动后容器无法挂载目录,提示Permission denied。很多优化教程会教你setenforce 0,但前面我也说了,关闭SELinux是一招饮鸩止渴。更稳的方案是确认SELinux的容器相关策略放行。实在需要快速解决,可以先用setenforce 0验证问题是否出在SELinux,等明确了再决定后续策略。
第三类是存储驱动相关,英文日志里会出现“error creating overlay mount”。这种情况多见于内核版本较旧、或者文件系统不支持OverlayFS。CentOS 7早期内核确实有过这类坑,解决办法是改用vfs存储驱动或者升级内核。在daemon.json里加一行“storage-driver”: "vfs",可以临时绕过,但vfs效率低,不推荐长期使用。最理想的情况是确保内核较新且系统盘是xfs或ext4,基本就能用overlay2。
下表是这三种错误的快速对照:
| 报错现象 | 常见原因 | 快速解法 | 根本解法 |
|---|---|---|---|
| iptables相关报错 | firewalld或系统iptables规则冲突 | 停掉firewalld并重启docker | 调整防火墙策略,放行Docker网桥 |
| SELinux权限报错 | SELinux布尔值未放行 | setenforce 0临时验证 | 配置容器适配SELinux策略 |
| overlay文件系统错误 | 内核版本低或文件系统不支持 | 临时切换vfs驱动 | 升级内核、格式化分区为xfs/ext4 |
5.2 容器网络不通:端口映射、网桥模式、防火墙
网络不通的问题出现频率很高,而且原因五花八门。最常见的是宿主机所有端口映射都访问不了,比如用-p 8080:80启动容器,curl 127.0.0.1:8080有响应,但局域网其他机器访问不了宿主机IP。第一检查点就是firewalld有没有放行8080端口。第二检查点是系统是否开启了ip_forward,执行sysctl net.ipv4.ip_forward检查是否为1,如果是0,编辑/etc/sysctl.conf,加一行net.ipv4.ip_forward=1再sysctl -p。
另一种情况是容器内能通外网,但容器和宿主机之间互相访问异常,多发生在你手动指定了docker0网桥的IP段,或者宿主机上存在多个网段冲突。这时候建议在daemon.json里重新指定bip参数,比如“bip”: "192.168.56.1/24",注意网段不要和现有网络冲突。
还有一种很低级但常见的坑:你在容器里跑了Nginx,并且把网站的listen地址配成了127.0.0.1,那么无论你怎么做端口映射,宿主机访问都是失败的。因为容器内的127.0.0.1只指向容器自己。这种问题从Docker这边排查永远无解,只有进容器看日志才会发现真相。所以遇到网络问题,先docker logs看程序日志,再看端口绑定,最后才怀疑Docker网络配置。
5.3 磁盘空间占用与基本清理
Docker用久了,/var/lib/docker目录会越来越大。镜像层层累积、多个容器叠加层、且悬空镜像(dangling image)堆积,很容易把磁盘塞满。热搜词里的“centos扩容”也是这类问题的延伸。
清理之前先看占用情况:
docker system df docker system df -vdocker system df能看到镜像、容器、本地卷、缓存这四类各占多少空间。然后根据情况选择清理命令:
docker system prune # 清理停止的容器、无用网络、悬空镜像 docker system prune -a # 更进一步,把没有容器在用的镜像全删掉 docker volume prune # 清理未被使用的本地卷注意,docker system prune -a是个危险命令。它会删除所有未被使用容器引用的镜像,等你下次启动容器时又得重新拉取。如果你只是想清理缓存和悬空镜像,不加-a就够了。我一般习惯先执行不带-a的prune,然后再单独手动清理确定的镜像。
磁盘空间还有一个老生常谈的点:Docker默认的data-root在/var/lib/docker,如果系统盘很小,可以考虑把data-root挪到大容量数据盘上。改daemon.json里的data-root字段,然后停Docker服务,搬目录,再启动。这条路径对新手来说重了点,但只要数据盘挂载好,一次操作能省掉以后很多扩容麻烦。
5.4 常用命令速查与几条最实用的操作习惯
安装配置完之后,下面这张命令表可以帮你快速上手日常操作,建议先收藏。
| 需求场景 | 命令示例 | 说明 |
|---|---|---|
| 查看所有运行中的容器 | docker ps | 加-a显示所有,包括已退出的 |
| 停止、删除容器 | docker stop 容器名 && docker rm 容器名 | 先停再删,删了数据就没了 |
| 查看容器日志 | docker logs -f 容器名 | -f是持续跟踪,排错时必备 |
| 进入容器内部 | docker exec -it 容器名 bash | 容器内必须有bash,否则用sh |
| 查看镜像列表 | docker images | 查看本地所有镜像 |
| 清理无用资源 | docker system prune | 安全、常用 |
| 构建自定义镜像 | docker build -t 名字:标签 . | Dockerfile所在目录执行 |
最后分享几个我平时坚持的操作习惯:第一,启动容器时永远给container命名,不命名的话Docker会生成一堆随机名字,你根本分不清谁是谁;第二,挂载目录时用绝对路径,并且在宿主机上先创建好目录,避免Docker帮你创建出root属主的目录;第三,所有需要持久化的数据,比如MySQL数据目录、配置文件,一定要用卷或者挂载目录,别丢在容器可写层里。这三条习惯,能帮你避免掉绝大多数“用着用着出了问题”的破事。
我在实际使用中最深的体会是,Docker给你省下来的时间,往往会在“不熟悉概念”这件事上还回去。所以这篇文章宁愿多花点篇幅讲清楚镜像、容器、仓库的关系,也不建议大家拿到命令就直接复制粘贴。先把安装这一步走扎实,把daemon.json弄明白,把容器生命周期搞清楚,后面的镜像构建、编排部署、CI/CD集成,才会顺理成章。下一篇我会接着写镜像构建和Dockerfile编写的实战细节,到时候我们继续聊。