ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

RHEL 9上安装Docker Engine:从仓库配置到容器实战全解析

RHEL 9上安装Docker Engine:从仓库配置到容器实战全解析 有朋友在RHEL 9上执行sudo yum install docker时愣住了——系统给出的候选居然是podman-docker。这其实是Red Hat给用户上的一课RHEL 9默认容器栈是Podman而不是Docker。但问题是很多生产环境里还留着老CI脚本、docker-compose.yml、镜像构建流程团队的知识沉淀也都在Docker身上不能说换就换。这篇博文就是我的完整实操记录讲清楚在RHEL 9上装Docker Engine的全过程包括官方仓库选择、模块冲突处理、镜像源优化、第一个容器实战以及最常踩的启动失败、权限拒绝、网络不通这几个报错的排查思路。内容在RHEL 9上实测过Rocky Linux 9和AlmaLinux 9同样适用。1. 在RHEL 9上装Docker前先搞清这些背景1.1 Red Hat的默认容器栈是Podman但Docker Engine有官方位置RHEL 9仓库里没有docker-ce这类包yum install docker装出来的其实是podman-docker——一个让Podman兼容docker命令行的过渡包。Red Hat这么设计有自己的逻辑Podman无守护进程、支持rootless、systemd集成好对RHEL这种企业发行版来说安全边界和运维模型确实更适合。但这不代表Docker Engine在RHEL 9上没有立足之地。Docker官方为RHEL建立了独立的软件仓库Base URL指向https://download.docker.com/linux/rhel/也就是说Docker CE在RHEL 9上是正式支持的对象。Red Hat主推PodmanDocker官方主推自己的CE版本两边是共存的而不是非此即彼。我见过不少团队的做法是基础设施和日常容器用Podman但凡是跑老项目、构建CI镜像、执行现有的compose编排继续用Docker CE。这套并行方案在RHEL 9上完全成立关键是安装时不要把两者混在一起用——命令虽然相似镜像层、网络模型、存储路径都不一样混用容易把环境搞乱。1.2 确认你手上的系统确实适用这套流程动手前先确认版本最少要保证是RHEL 9.x系列cat /etc/redhat-release uname -muname -m输出x86_64或aarch64都行Docker在这两个架构下都有官方包。如果你是Rocky Linux 9或AlmaLinux 9/etc/redhat-release显示的是它们自己的名字但包源结构兼容RHEL 9下面的命令可以直接照抄。还有一点要提醒RHEL本身需要有效订阅才能使用软件仓库。如果你没有Red Hat订阅又不想在这个过程中卡壳最稳妥的办法是用Rocky Linux 9或AlmaLinux 9替代或者申请Red Hat开发者订阅。这不是绕路RHEL系衍生版本来就是企业环境里常见的兼容选择命令和行为基本一致。1.3 提前确定的三个安装决策安装前我会先把三件事想清楚省得装完再返工。第一安装角色用root还是sudo。安装阶段建议直接用有sudo权限的管理员账号日常操作则尽量切到普通用户。Docker的socket权限默认归root组普通用户要加进docker组才能用这个细节后面会专门讲。第二要不要装Compose插件。现在Docker官方把Compose V2作为插件提供包名叫docker-compose-plugin装完直接使用docker compose命令注意中间有空格。如果你还在用旧的docker-compose二进制建议趁这次升级直接换V2语法兼容性已经足够好命令结构也更干净。第三容器数据的存放位置。Docker默认把镜像、容器层、卷都放在/var/lib/docker。如果根分区空间紧张等装好后可以在/etc/docker/daemon.json里通过>sudo yum install -y yum-utilsyum-utils里还有repoquery、yumdownloader这些工具后面排查依赖问题时也用得上。如果你的系统之前装过旧版Docker或Podman的残留最好先看一眼rpm -qa | grep -E docker|podman | sort这一步是为了防止后面出现文件冲突。RHEL 9如果执行过yum install docker系统里大概率已经有podman-docker包了后面我会专门讲怎么处理。2.2 用yum-config-manager挂载官方仓库添加Docker官方仓库的命令sudo yum-config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo添加完成后检查一下仓库文件内容cat /etc/yum.repos.d/docker-ce.repo重点看baseurl里的版本号变量应该长这样https://download.docker.com/linux/rhel/$releasever/x86_64/stableDocker仓库对RHEL 9来说$releasever解析为9Rocky Linux 9和AlmaLinux 9同理。如果你的系统输出$releasever没被正确解析或者仓库地址有误直接手动把$releasever替换成9再yum makecache也行。这里有个容易踩的细节不要用CentOS的仓库文件硬套RHEL。网上很多教程给的是centos/docker-ce.repo对RHEL 9虽然大概率也能用但路径不标准升级解析时可能出岔子。用官方rhel路径最稳。2.3 处理container-tools模块冲突这是RHEL 9装Docker时特有的一个坑。RHEL 9带了一个container-tools模块里面包含了Podman、Buildah、Skopeo等工具。当系统里已经存在podman-docker/usr/bin/docker是指向Podman的符号链接安装docker-ce-cli时就会报文件冲突。遇到这种情况先把容器工具模块禁用掉再移除兼容包sudo yum module disable -y container-tools sudo yum remove -y podman-docker然后继续安装主包sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这条命令一次装齐了五个组件docker-ce是守护进程本体docker-ce-cli是命令行工具containerd.io是容器运行时docker-buildx-plugin负责多架构镜像构建docker-compose-plugin就是前面说的Compose V2。如果你在没禁用模块的状态下强行安装可能会遇到依赖解析时把Podman相关包拉进来的情况虽然多数时候系统能自动规避但提前禁用模块可以让依赖关系干净得多。禁用模块并不会删除系统里已安装的Podman本体只是让包管理器不再优先选择模块流里的版本影响面可控。2.4 启动、自启与首次自检启动服务并设置开机自启sudo systemctl enable --now docker sudo systemctl status docker看到active (running)就说明守护进程起来了。接着做一次基础自检sudo docker version sudo docker infodocker version分Client和Server两段如果Server段能正常返回说明客户端到守护进程的连接没问题。docker info里我习惯重点看三项Storage DriverRHEL 9正常显示overlay2Cgroup DriverRHEL 9默认是systemdCgroup VersionRHEL 9是2如果这三项和预期不符后面容器运行时的行为会有差异先排查系统层面别急着往下走。顺便说一句docker info如果提示permission denied因为你当前用户还没加入docker组先用sudo跑用户组的事留到第五章统一处理。3. 别急着跑容器先配置镜像源和其他daemon.json细节3.1 为什么默认拉镜像会慢很多人装完Docker第一件事就是docker run hello-world然后卡在拉镜像这一步。Docker Hub官方的registry节点大多部署在海外跨国际链路的传输延迟和丢包都偏高加上没有区域缓存节点小镜像还好遇到几十上百MB的中间层就会明显感觉慢。这不是Docker本身的问题而是镜像分发的物理路径决定的。Docker Hub的镜像由多层组成每一层都要单独下载并校验SHA256链路不稳定时任何一层失败都会导致整个pull重来。3.2 配置registry-mirrors加速最直接的解法是在/etc/docker/daemon.json里配置registry-mirrors。先创建或编辑这个文件sudo mkdir -p /etc/docker sudo vim /etc/docker/daemon.json填充内容{ registry-mirrors: [https://你的容器镜像加速服务地址] }地址用你所在云厂商控制台上提供的加速地址每家都有对应的独立域名直接粘进去就行。配置完重启Docker让它生效sudo systemctl daemon-reload sudo systemctl restart docker验证镜像源是否生效sudo docker info | grep -A4 Registry Mirrors只要下面列出你配置的地址就说明已经生效。之后的docker pull会优先走加速地址镜像源不可用时再回退到Docker Hub。3.3 顺带把数据目录、日志和DNS一起配置好daemon.json是Docker守护进程的总配置文件别只为了镜像源开一次。我建议趁这个窗口把另外几个高频参数一并配齐{ registry-mirrors: [https://你的容器镜像加速服务地址], data-root: /var/lib/docker, log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 }, dns: [223.5.5.5, 119.29.29.29] }>sudo docker pull nginx sudo docker run -d --name web -p 8080:80 --restart always nginx参数解释一下-d后台运行--name web给容器命名-p 8080:80把宿主机的8080端口映射到容器的80端口--restart always让Docker在容器退出或宿主机重启后自动拉起它。启动后再确认容器状态sudo docker ps然后在宿主机上访问curl http://localhost:8080只要返回Nginx的默认页面HTML就说明容器已经正常工作。你还可以从宿主机之外访问前提是防火墙放行了8080端口这个在第6章会专门讲。docker run第一次启动一个镜像时内部逻辑是检查本地有没有镜像 → 没有就pull → 创建容器层 → 启动进程。所以docker run往往比docker pull要多等一会不是卡住是它正在拉取镜像并初始化。4.2 数据持久化bind mount与SELinux标签容器是无状态的容器一删里面写的数据全没了。想让数据留在宿主机最常用的是bind mount把宿主机的目录挂载进容器sudo mkdir -p /opt/web sudo docker run -d --name web2 -p 8081:80 -v /opt/web:/usr/share/nginx/html:ro nginx-v /opt/web:/usr/share/nginx/html:ro表示把/opt/web挂到Nginx默认站点目录:ro是只读挂载防止容器内部不小心改了宿主机文件。在RHEL 9上给容器挂载宿主机目录时有一个特有的坑SELinux强制模式下容器进程默认没有权限访问宿主机上以普通类型标注的目录报错经常是Permission denied或nginx: [alert] could not open error log file。解法的核心是给挂载目录打上容器文件类型标签sudo chcon -Rt container_file_t /opt/web更持久的方式是用semanage fcontext定义规则然后restorecon恢复sudo yum install -y policycoreutils-python-utils sudo semanage fcontext -a -t container_file_t /opt/web(/.*)? sudo restorecon -Rv /opt/web没有这些SELinux概念的话可以简化理解SELinux给文件贴了类型标签容器进程只被允许访问标记为container_file_t的文件宿主机上默认创建的文件标签不是这个所以要手动贴标签。直接关闭SELinux是我最不建议的解法RHEL 9的SELinux和Docker配合得并不差问题出在挂载目录的标签上用上面的命令就能解决。4.3 MySQL 8容器与访问失败的常见原因数据库容器是另一个高频需求但也是报错重灾区。先看一条能用的启动命令sudo docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD你的高强度密码 \ -e MYSQL_ROOT_HOST% \ mysql:8.0MYSQL_ROOT_HOST%是关键。MySQL容器默认只允许root从localhost登录不加这个环境变量你在宿主机上或用客户端工具远程连接时只会收到Access denied。%表示允许任意主机。容器起来后进入容器内验证sudo docker exec -it mysql8 mysql -uroot -p输入密码就能进入MySQL命令行。从宿主机连接的话用mysql -h127.0.0.1 -P3306 -uroot -p。常见的 docker安装mysql失败 场景我归纳过三类一是端口被占用。表现在启动时driver failed programming external connectivity。用ss -lntp | grep 3306查一下宿主机端口换一个映射端口即可。二是认证插件不兼容。MySQL 8.0默认认证插件是caching_sha2_password如果你的客户端或老版本驱动只支持mysql_native_password连上去会报Authentication plugin caching_sha2_password cannot be loaded。8.0系列可以在容器启动参数里追加--default-authentication-pluginmysql_native_password。但要注意MySQL 8.4以后已经移除这个参数最合理的方向是升级客户端驱动而不是反过来让数据库降级。三是数据没持久化。MySQL容器删除后数据全丢生产环境一定要用具名卷sudo docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD你的高强度密码 \ -v mysql-data:/var/lib/mysql \ mysql:8.0mysql-data是Docker管理的volume可以用docker volume ls查看。容器删除后volume还在数据就不会丢。4.4 用Docker Compose一键编排多容器单容器用docker run够用一旦涉及多个容器比如Web 数据库 Redis就该用Compose。先确认插件已装sudo docker compose version然后建目录和编排文件mkdir -p ~/demo cd ~/demo vim compose.yaml一个Web MySQL的最小示例services: web: image: nginx ports: - 8080:80 volumes: - /opt/web:/usr/share/nginx/html:ro depends_on: - mysql8 mysql8: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 你的高强度密码 MYSQL_ROOT_HOST: % volumes: - mysql-data:/var/lib/mysql volumes: mysql-data:在~/demo目录下执行sudo docker compose up -dCompose会自动创建专用网络、拉取镜像、按依赖顺序启动服务。注意命令是docker compose中间有空格别和旧版docker-compose搞混。depends_on只保证启动顺序不保证MySQL已经能接受连接业务代码里要做重连逻辑这个进阶问题后续再展开。5. 高频报错现场从启动失败到权限拒绝的排查链路5.1 docker服务启动失败的两种典型根因systemctl start docker失败时不要只看active (failed)第一时间查日志sudo journalctl -u docker -n 50 --no-pager还要同步看containerd的状态sudo systemctl status containerd我遇到过的RHEL 9场景根因基本落在下面两类第一类是containerd没起来或版本不匹配。Docker守护进程依赖containerd来管理容器生命周期如果containerd异常docker会报类似failed to start daemon: Error initializing docker/apptainer: unexpected error。处理方式是先单独启动containerdsudo systemctl start containerd sudo systemctl start docker第二类是iptables或firewalld冲突。RHEL 9默认用nftables作为iptables后端Docker启动时要操作iptables规则如果firewalld的规则抢先加载或已经被外部脚本清掉Docker可能报failed to start docker application container engine。最简单的验证方法是临时把firewalld停掉再启动Dockersudo systemctl stop firewalld sudo systemctl start docker如果这样能启动就把Docker加进firewalld的信任区间或者适当调整规则而不是一直关着防火墙。生产环境关闭防火墙是不可接受的第6章会讲正确的放行姿势。5.2 permission denieddocker用户组与socket权限普通用户执行docker ps报这段错误permission denied while trying to connect to the docker api at unix:///var/run/docker.sock原因是Docker客户端要连接/var/run/docker.sock而这个socket文件的归属是root:docker权限是srw-rw----普通用户不在docker组里自然没有权限。解法是把当前用户加入docker组sudo usermod -aG docker $USER之后要重新登录会话才生效。如果你是在SSH会话里操作的退出再重新登录最稳妥也可以用newgrp docker临时切换组身份来验证。确认生效id -nG输出里包含docker就说明加入成功然后直接docker ps不会再报权限错误。这里必须强调一句加入docker组等价于获得root权限。因为docker组用户理论上可以通过挂载宿主机目录等方式访问宿主机任意文件生产环境要控制加入docker组的账号范围。如果团队环境不愿意给普通用户这么大的权限可以用sudo方式执行docker命令或者为特定服务单独创建受限用户。5.3 容器内网络不通或DNS解析失败容器内ping外部IP通但解析域名失败是最典型的网络问题变体。RHEL 9宿主机DNS由NetworkManager管理默认配置可能让容器继承到127.0.0.1这类本机回环地址容器内部没有那么dnsmasq就会解析失败。排查链路我按这个顺序走# 1. 看容器网络是否正常 sudo docker exec -it 容器名 ping 223.5.5.5 # 2. 看容器内DNS配置 sudo docker exec -it 容器名 cat /etc/resolv.conf # 3. 看宿主机DNS cat /etc/resolv.conf如果第2步显示nameserver 127.0.0.x基本可以断定是DNS继承出了问题。解法就是在第3章说的在daemon.json里指定dns字段然后重启Docker。另一种情况是容器之间互相访问不通。这种情况多半不是DNS问题而是没规划好自定义网络。用Compose启动的服务都在同一张自定义bridge网络里可以直接用服务名互通而两个用docker run单独创建的容器默认在docker0网桥下只有通过IP互相访问并且IP会变。想省心就让它们加入同一个自定义网络sudo docker network create my-net sudo docker network connect my-net 容器名容器化环境下的网络排查本质就是先确认网卡存在再确认网关通再确认DNS解析再确认端口监听一层层收敛不要一上来就怀疑某个玄学原因。5.4 拉取镜像超时与校验失败docker pull报超时报错里出现类似Get https://registry-1.docker.io/v2/字样说明Docker Hub本身可达性不佳。先检查镜像加速有没有配置生效sudo docker info | grep -A4 Registry Mirrors如果没输出加速地址回到第3章配置好再试。如果已配置还慢可以进一步排查拉取过程中是哪个环节慢sudo docker pull nginx --debug--debug会输出详细日志能看到卡在下载层还是校验层。比较少见但真实存在的情况是镜像层已经下载完但校验阶段失败报failed to verify the content of the layer。这种多半是网络传输导致数据损坏重试往往能解决。另外多架构镜像的manifest列表在特定网络下也可能加载失败。如果明确只需要某个架构可以用--platform指定sudo docker pull --platform linux/amd64 nginx这样跳过manifest列表的完整加载直接拉对应架构的层有时能绕开问题。虽然大多数场景用不上但排查时多一个手段总是好的。6. 生产级姿势防火墙、资源限制与日常维护6.1 放行端口而不要关闭防火墙前面踩坑时临时停过firewalld生产环境不能这么干。正确方式是放行需要的端口。比如要让外部机器访问我们映射到8080端口的Nginxsudo firewall-cmd --permanent --add-port8080/tcp sudo firewall-cmd --reload sudo firewall-cmd --list-ports同理MySQL的3306端口如果需要对外提供也要放行。但数据库端口默认不要暴露到公网最好限定来源IPsudo firewall-cmd --permanent --add-rich-rulerule familyipv4 source address10.0.0.0/8 port port3306 protocoltcp accept sudo firewall-cmd --reload如果你的业务端口很多建议直接按服务区域管理给Docker相关网卡单独划一个zone也可以但复杂度会上升。对大部分人来说端口级放行就够了别再到“关闭防火墙”这一步。6.2 给容器限流和限制日志体积RHEL 9生产环境下裸奔的容器是不可接受的。最少要做的两件事资源限制和日志轮转。资源限制在docker run时用参数指定sudo docker run -d --name web \ -m 512m \ --cpus0.5 \ -p 8080:80 \ --restart always \ nginx-m 512m限制内存最大512MB--cpus0.5限制最多使用半个CPU核心。容器超过内存上限会触发OOM被杀这是好事总比整台宿主机被拖死强。日志轮转在daemon.json里已经通过log-opts配置过对之后所有容器生效。已经存在的容器单容器也可以追加sudo docker run ... --log-opt max-size10m --log-opt max-file3 nginx磁盘清理有个一条龙命令sudo docker system prune -af --volumes这个命令会删除所有未使用的容器、镜像、网络和卷用-a会连未使用的镜像一起清掉--volumes会清理无主卷。在开发环境很爽生产环境执行前务必确认最好先在docker system df里看空间占用情况再决定。6.3 升级与清理的日常习惯Docker的升级路径很简单因为是yum仓库安装的sudo yum update docker-ce docker-ce-cli containerd.io sudo systemctl restart docker升级前建议查看发行说明docker-ce的版本升级有时会改变默认参数。升级后第一时间检查docker info里的版本号并且跑一个测试容器确认核心流程没问题。日常健康检查我用得比较多的是这两个命令sudo docker ps -a sudo docker events --since 1hdocker ps -a能看所有容器的退出状态码docker events能看过去一小时Docker守护进程发生的事件比如容器OOM、重启、网络断开等。把这些内容接到监控里可以在容器出大问题之前就发现端倪。最后再分享一个小经验在RHEL 9上我会刻意保留Podman在系统里而不是为了Docker把它清理干净。两个工具的命令风格高度相似但服务模型不同Podman的rootless模式适合跑一些不需要特权的能力场景Docker CE则更适合现有生态和CI链路。真实环境里谁都没有必要替代谁把两个工具都装明白比争论谁更先进更有用。
返回列表