RH134容器实战:Podman运行、挂载与SELinux排坑指南
谈到RH134里的“运行容器”这一章我最先想到的倒不是命令本身而是很多人在这一章“看着会了、一跑就挂”的现象。命令无非是podman run、ps、exec、logs、stop、rm这几条真正让容器顺畅跑起来的反而是背后那几条链路镜像从哪来、挂载目录为什么被拒、普通用户凭什么能启动容器、重启之后数据会不会消失。这一章在整个系统管理课程里是典型的“课本很短实操全是坑”。这篇文章适合三类人正在备考RH134、想把知识点过一遍的人刚接触Linux容器、之前只玩过虚拟机的人已经在服务器上部署容器但遇到问题不知从何下手的人。我按问答的形式把核心考点整理出来每个问题后面都夹带我在实际环境里踩过的坑和验证过的方法可以直接照做。1. 先搞懂运行逻辑容器到底是怎么“跑”起来的1.1 容器和虚拟机到底差在哪很多人学容器之前已经玩过虚拟机。虚拟机里有一套完整的操作系统和自己的内核启动就像开一台独立电脑又慢又重。容器不一样它不是一个完整的操作系统而是Linux系统上的一个进程只不过这个进程被隔离在自己的命名空间里看不到宿主机的全局资源。我的理解是可以把虚拟机当成一间酒店客房房间里的家具、电器、卫浴都是独立的客人之间互不干扰但酒店要为每个房间配齐所有设施。容器更像一栋公寓楼里的住户大家共用大楼的水管、电力和电梯也就是宿主机内核每一户之间用墙和门隔开室内的装修文件系统、软件环境自己管自己的。正因共用了宿主机内核容器启动很快镜像通常只有几十到几百兆。这个区别带来一个直接限制容器里的“内核”就是宿主机的内核所以容器内部不能随便加载内核模块也不能像宿主机那样用 systemd 初始化整个系统除非做特殊配置更不能指望容器里跑一个和宿主机完全无关的内核。RH134在容器章节里很多隐藏考点都从这个底层事实引申出来。1.2 为什么这一章要重点用 Podman这一章装的是 Podman不是那个带守护进程的常见容器工具。为什么课程要这样安排我自己的体会是Podman 天然是无守护进程架构所有容器操作直接通过命令行和底层的存储、网络打交道不需要在后台常驻一个大进程。对服务器安全来说少一个大进程就少一个被攻击的入口。另一个关键点是 rootless 模式。普通用户不需要任何管理员权限就能在自己目录下管理容器。这在多人共用的测试机上特别实用每个用户只管自己的容器不会碰别人的数据。而且 Podman 和常见容器工具的命令行用法基本对齐学会 Podman 后再去接触其他容器工具迁移成本很低。我实际使用下来还有一个体会由于没有守护进程Podman 在多用户、多租户的服务器上更好“收权限”。管理员不需要给所有用户开放一个共享接口每个人天然有自己的命名空间和存储路径。这一点在安全审查时非常加分也是课程选择它的核心原因之一。1.3 镜像、容器、仓库三者挤在一张图里很多人上来就敲podman run其实根本没搞清楚自己在干什么。这里先用大白话把三个概念理清镜像一个只读模板里面包含程序运行所需的文件系统、配置和启动命令。容器通过镜像创建出来的可写运行实例镜像本身不会因为容器里改了文件而变化。仓库存放和分发镜像的服务端可以是公共仓库也可以是企业自建的内网仓库。打个比方镜像是“安装包”仓库是“应用商店”容器是你正在运行的那个“程序”。删除容器不会自动删除镜像删除镜像也不会自动清掉已经存在的容器——但那个容器的“模板”没了想再复制新实例就会失败。这个关系在清理磁盘时特别容易出现误解我见过有人盯着podman rmi反复执行以为删了镜像就能释放空间结果真正占空间的容器还挂着呢。2. 运行容器之前工具安装与配置2.1 容器工具安装一条命令能不能搞定在RH134对应的系统里安装容器工具最稳妥的方式是安装软件包集合。我常用的命令是dnf module install -y container-tools这条命令会安装一整套容器工具链包括 Podman、构建镜像用的 buildah、跨仓库复制镜像用的 skopeo 等。学习阶段装这一套非常方便不用一个个找依赖。如果只想装核心运行工具也可以直接dnf install -y podman装完验证一下版本podman --version能输出版本号就说明工具正常。这里提醒一句不同小版本对容器网络的默认设置不一样新版本默认网络叫podman老版本可能是cni-podman。后面排查网络问题时先确认版本号能让思路更清晰。2.2 普通用户跑容器底层发生了什么这是 rootless 模式最迷人的地方但很多人不理解它为什么能工作。普通用户执行podman run本质是利用 Linux 的用户命名空间把容器里看到的 root 用户映射到宿主机上的一个普通用户。容器里的进程以为自己是 root实际权限却被关在“自己的一亩三分地”里。网络方面rootless 容器默认通过 slirp4netns 做用户态转发存储方面则靠 fuse-overlayfs 把镜像的分层数据叠加在用户自己的目录里。正因如此普通用户第一次运行容器时如果系统缺少 slirp4netns 或 fuse-overlayfs会直接报错。报错信息通常类似cannot find slirp4netns或/dev/fuse: Permission denied。解决办法是让管理员安装一次辅助组件dnf install -y slirp4netns fuse-overlayfs装完普通用户就可以正常跑容器了。我见过不少同学卡在这里以为 Podman 坏了其实是缺这两个软件包。2.3 registries.conf 里的镜像仓库配置Podman 默认从配置文件里的仓库地址拉取镜像。系统级配置文件是/etc/containers/registries.conf用户级配置文件是~/.config/containers/registries.conf后者的优先级更高。如果不配置好仓库直接拉一个短名字镜像比如podman pull nginxPodman 会按配置里登记的仓库顺序去查找有时候会提示“短名歧义”。我在内网环境踩过最典型的坑是网络完全正常但拉镜像特别慢或直接超时。这种时候不要盲目重试先检查仓库配置。参考配置片段unqualified-search-registries [docker.io] [[registry]] prefix docker.io location docker.io [[registry.mirror]] location mirror.example.com意思是把公共仓库配置为默认同时加一个内部镜像加速源。配完以后最好用podman pull hello-world实测一下确认加速生效再继续。学习中经常忽略这一步等到下载大镜像时才反应过来浪费很多时间。2.4 私有仓库认证明明登录了还是拉不到内网环境通常会自建镜像仓库访问时可能需要登录。命令很简单podman login registry.example.com输完用户名密码认证信息会存在用户目录下的auth.json里。我遇到最多的问题是明明已经 login 成功podman pull还是返回401 Unauthorized。排查思路通常是先确认拉取时用的镜像名完整包含仓库地址比如podman pull registry.example.com/myapp/web:1.0如果名字没写完整Podman 可能去了公共仓库找自然拿不到认证信息。另一个常见原因是登录时用的是普通用户但podman pull用了sudo于是认证信息落在普通用户目录root 根本读不到。这种情况要么都用普通用户操作要么都切到同一身份再登录。3. 镜像管理拉取、标签、导出与清理3.1 拉取镜像失败先看报错落在哪一层拉取镜像的命令很简洁podman pull nginx但背后至少有四步“找到短名对应的完整仓库名 → 访问仓库服务器 → 下载镜像分层 → 解包存储”。每一步失败报错风格完全不同定位方式也要跟着变。我整理几类高频错误。第一类是网络问题报错通常带dial tcp、lookup超时或Connection refused这时候先检查当前网络能不能访问目标仓库再检查有无代理干扰。第二类是证书问题报错里出现x509常见于内网仓库用了自签名证书解决方式是把仓库证书加入信任列表或者在配置里对该仓库开启insecuretrue。第三类是镜像名写错或仓库里压根没有这个镜像报错一般是manifest unknown这时候用podman search搜一下确认名字。还有一类容易迷惑的“短名歧义”直接敲podman pull fedora系统不知道你想拉哪个仓库的 fedora。最稳妥的做法是把镜像名写全比如podman pull docker.io/library/fedora。名字长一点没关系可控性更高。3.2 tag 删除与悬空镜像tag 是给镜像加的可读别名。同一个镜像可以同时拥有多个标签podman tag nginx:latest nginx:backup执行后镜像在本地有两个引用。这里有个容易误解的点podman rmi nginx:backup删除的并不是“镜像里的一个副本”而是减少一次引用。只有当一个镜像的所有标签都被删除它占用的磁盘空间才会真正释放。清理本地镜像时我一般先看podman images如果看到一堆none标签说明镜像已经没有可用标签引用了只剩数据还占着空间。这时候用podman image prune清理悬空镜像而不是反复手动rmi。我见过有同事用rmi删了几次发现空间没变小以为出了故障其实只差一个 prune。生产环境里我不建议用latest标签部署。因为latest是漂移的今天拉到的和下周拉到的可能不是同一个镜像。想做到可复现、可回滚就要给镜像打上版本号比如myapp:1.0.0部署脚本里写死这个版本出问题才能精准回滚。3.3 离线环境下的镜像传递很多机房环境不能访问外网但又必须部署容器。这时候最实用的方法是“在外网机器上拉镜像导出成文件再导到内网机器”。导出镜像的命令podman save -o nginx.tar nginx:1.24导入镜像的命令podman load -i nginx.tar注意save和load处理的是镜像。如果对象是容器应该用podman export。我经常看到有人把容器和镜像导出混在一起用导致导入后启动不起来。这里记一个口诀镜像用 save/load容器用 export/import。离线部署多踩几次这个坑就会记住。4. 运行容器的完整实操参数逐个拆解4.1 一条完整的 podman run 包含哪些关键信息我拿最常见的 Web 服务举例podman run -d --name web-demo -p 8080:80 -v web_data:/usr/share/nginx/html:Z nginx:1.24逐段拆开讲-d后台运行容器不会占住当前终端。--name web-demo给容器起固定名字之后logs、exec、stop都可以直接用名字不用记随机生成的容器 ID。-p 8080:80把宿主机 8080 端口转发到容器内 80 端口格式是“宿主机端口:容器端口”。-v web_data:/usr/share/nginx/html:Z创建一个卷并挂载到容器内的网站目录。nginx:1.24指定镜像和标签。跑起来后验证podman ps curl http://localhost:8080能看到 Nginx 欢迎页就说明容器运行正常。这里有个容易忽略的概念容器内有一个进程充当 PID 1它负责接收信号和回收子进程。如果这个 PID 1 退出容器就退出了和宿主机上 init 进程退出系统会停机的道理一样。4.2 挂载卷时 :Z 和 :z 的差别不只是大小写很多人挂载宿主机目录时撞上一堵墙明明目录权限已经是 777 了容器里还是Permission denied。问题多半不在普通权限而在 SELinux。RH134 所对应的系统默认开了 SELinux 强制模式。文件会被打上安全上下文标签普通目录的标签和容器进程需要的标签不一致SELinux 就会拦截容器的访问。报错往往只有一句“权限不足”容易让人误以为是 chmod 没设对。解决办法是在卷挂载时加后缀podman run -d --name web-demo -v /srv/html:/usr/share/nginx/html:Z nginx:1.24:Z和:z的区别我在这里说清楚。:z表示把这个卷的标签改成仅供单个容器使用:Z表示允许多个容器共享这个挂载目录。实际使用中如果同一个目录要被多个容器同时挂载用:Z如果只是一个容器专用:z就够。还有个细节容易被忽略如果容器内部以非 root 用户运行卷目录的所有权要和容器内用户的 ID 对应上。rootless 模式下容器内 root 会映射成宿主机普通用户容器内其他用户对应另一组映射 ID。搞不明白时可以用podman unshare手动进入对应命名空间查看权限调整起来会直观很多。4.3 查看日志和进入容器哪个才是首选容器运行后调试方式主要两种podman logs -f web-demo podman exec -it web-demo /bin/shlogs用来查看容器标准输出也就是应用打出来的日志exec是进入容器执行命令适合改配置、查环境。我一般先去logs不是特别必要不进容器。因为exec进去改的任何东西在容器被删除之后全部归零如果没有挂载卷所有修改都会丢。这里有几个实际操作经验。第一很多精简镜像没有 bash只有 sh所以进去先用/bin/sh。第二容器里一般没有systemctl不要指望在容器内启动服务重启服务等于重启容器。第三logs -f是跟随模式适合实时看日志CtrlC 退出不会影响容器运行。4.4 环境变量、资源限制和只读运行的补充运行容器时传环境变量是高频操作podman run -d --name web -e TZAsia/Shanghai -e APP_MODEprod nginx:1.24-e可以多次出现作用是把宿主机变量传进容器。很多应用靠环境变量区分环境和读取配置这种方法比把配置文件塞进镜像里更灵活也是考试喜欢考的参数之一。资源控制方面两个最常用的参数podman run -d --name limited --cpus1 -m 512m nginx:1.24--cpus限制容器能用的 CPU 核数-m限制内存上限。生产服务器上给每个容器加资源限额是好习惯防止某个容器把宿主机内存吃光导致其他服务全部抖动。这在 RH134 里不一定深考但作为容器运行的基本素养值得掌握。还有一个安全参数是--read-only挂载一个只读的容器文件系统podman run -d --name secure --read-only nginx:1.24容器内不允许写根文件系统所有写入必须落在卷里。这种模式能防止容器被篡改适合跑一些对安全要求高的服务。配合临时目录挂载/tmp可以兼顾只读和临时文件需求。5. 容器状态管理与 systemd 集成5.1 状态速查ps 命令里的那些英文单词什么意思查看容器列表podman ps podman ps -aps只显示正在运行的容器ps -a显示所有容器包括已停止的。每个容器都有一个状态字段常见的有Created已创建未启动、Up正在运行、Exited已退出、Paused已暂停。我见过的最多困惑是容器明明创建了却不显示在运行列表里。原因就是前面说的镜像默认启动命令执行完就结束容器的 PID 1 退出状态立刻变成Exited。这不是出了问题而是镜像设计如此。停止和删除操作也有区别podman stop web-demo podman kill web-demo podman rm web-demostop先发 SIGTERM 做优雅关闭kill直接发 SIGKILL 强制终止rm是删除容器实例。日常优先用stop等十几秒没用再kill。删除容器之前先确认数据是否已经用卷持久化。我有个同事手滑删了所有容器才发现里面的数据库服务没有挂卷数据直接清零这个教训我一直记着。5.2 “配置怎么又变回去了”可写层的边界在哪里新手最容易困惑的场景明明在容器里改了配置文件容器重启后配置又回到了原样。原因在于容器有“可写层”。运行一个镜像时Podman 会在镜像只读分层的上面加一个可写层容器运行中的所有修改都写在这个可写层里。容器停止再启动stop start可写层还在所以配置不会丢但容器被删除后可写层也没了基于同一镜像重新创建的新容器当然会回到最初始的状态。所以核心逻辑是镜像负责环境可复现卷负责数据可留存。任何需要长期保存的配置和数据都必须放到卷或绑定挂载目录里不能只依赖容器自身的可写层。这一条既是考试重点也是真实运维的保命经验。5.3 用一个 systemd unit 让容器开机自启生产服务器重启后容器不会自己起来必须借助 systemd。Podman 有现成的生成命令podman generate systemd --new --name web-demo /etc/systemd/system/web-demo.service生成后启用systemctl daemon-reload systemctl enable --now web-demo.service里面--new参数很关键。它生成的 unit 文件会在服务启动时新建一个容器适合和卷、环境变量、端口映射一起配合使用。如果不加--new只是管理当前已有的容器适用条件比较苛刻。rootless 容器的 systemd 集成略有不同。普通用户的 unit 文件要放到用户目录mkdir -p ~/.config/systemd/user podman generate systemd --new --name web-demo ~/.config/systemd/user/web-demo.service systemctl --user daemon-reload systemctl --user enable --now web-demo.service别忘了执行loginctl enable-linger 用户名不开启 linger用户退出登录后用户级服务可能不会被拉起。这个坑我在测试机上踩过一次人走之后容器服务没跟着起来查了半天才发现是 linger 没开。6. 数据持久化、端口映射与容器网络6.1 卷、绑定挂载与 tmpfs 的选择数据持久化的三种方案整理成一张表更直观方案数据存放位置生命周期适用场景卷volumePodman 自己管理的目录容器删除后仍存在数据库、应用数据优先推荐绑定挂载bind mount宿主机指定路径容器删除后仍存在需要直接读写宿主机文件的场景tmpfs内存中容器停止即清空临时文件、敏感数据不想落盘卷的好处是 Podman 会帮你管理权限和标签不需要操心数据具体落在哪个目录。绑定挂载更适合“我就想把这个目录给容器用”的情况但 SELinux 和权限问题要自己处理。tmpfs 适合存密码、令牌这类不能落盘的东西业务数据不建议放里面。创建和使用卷很简单podman volume create html-data podman run -d --name web -v html-data:/usr/share/nginx/html:Z nginx:1.24之后即使容器删除重建只要还用html-data这个卷网站文件就不会丢。我习惯在生产目录里标注“容器可删卷不可删”防止别人清理时误删数据卷。6.2 端口映射从 127.0.0.1 到默认监听端口映射参数-p的格式是“宿主机地址:宿主机端口:容器端口”。比如podman run -d --name web -p 127.0.0.1:8080:80 nginx:1.24写127.0.0.1表示只在本机监听外部无法直接访问适合内部调试。如果直接写-p 8080:80Podman 会绑定所有网卡地址外部网络能直接访问。凡是准备开放公网的端口我都会先确认这是不是真的需要。还有一个藏在权限里的坑rootless 模式下普通用户默认不能监听 1024 以下端口比如 80、443。如果启动容器时报bind: permission denied大概率就是这个原因。临时解法是调低非特权端口起始值sudo sysctl -w net.ipv4.ip_unprivileged_port_start80这个操作涉及全机配置生产环境要谨慎。考试里一般只需要知道“普通用户监听低端口会受限”这个点。6.3 容器之间怎么通信单个容器跑通只是第一步多个容器之间互相通信才是常态。默认情况下Podman 会为容器创建虚拟网络每个容器拿到自己的 IP。可以直接查podman inspect web-demo | grep IPAddressIP 这种方式不优雅因为容器重建后 IP 会变。更推荐的做法是创建用户自定义网络让容器通过名字互相访问podman network create app-net podman run -d --name web --network app-net nginx:1.24 podman run -d --name backend --network app-net my-backend:1.0在 app-net 网络里web这个容器可以通过backend这个名字访问另一个容器不需要关心 IP 变化。这也是 RH134 容器章节里比较进阶但很实用的知识点。多容器部署时我基本都会先规划网络再决定容器归属避免后面到处改连接地址。7. 常见问题排查实录我踩过的那些坑7.1 容器启动后立刻退出先看有没有前台进程这是最高频的问题。容器一启动就Exited常见原因是镜像默认启动命令不是一个常驻进程。比如基础镜像执行完一条echo就结束了容器自然留不住。排查第一步看日志podman logs 容器名再结合podman inspect 容器名查看启动命令、挂载和环境变量。如果只是临时调试可以覆盖启动命令podman run -d --name debug busybox sleep 3600如果报错提示exec user process caused: no such file or directory通常是容器里的可执行文件路径写错或者动态库缺失。重点检查启动命令里的解释器路径比如脚本第一行写的/bin/bash在精简镜像里可能不存在。7.2 SELinux 拦截看审计日志比猜更高效容器访问宿主机目录被拒报了Operation not permitted很多人的第一反应是调大目录权限结果没用。正确的排查方法是看审计日志sudo tail -f /var/log/audit/audit.log看到avc: denied记录时说明是 SELinux 在拦截。可以用audit2why辅助分析原因快速理解是哪条策略不允许。解决办法通常两个方向一是给挂载卷加:Z二是用chcon修改目录类型标签。对生产环境我建议优先修改卷标签不要指望关闭 SELinux。RH134 对 SELinux 的考察重点就是“会看、会用、不乱关”这个原则放到真实运维里同样成立。7.3 rootless 容器的网络性能与低端口限制rootless 容器默认走 slirp4netns 用户态网络转发性能确实比 root 容器的桥接模式低一些。高并发场景下rootless 的 CPU 占用明显更高。如果对网络吞吐有硬性要求建议用 root 方式运行容器并配置桥接网络或者评估是否改用宿主机直挂网络。我实测过一个简单静态页服务同样并发下 rootless 模式的 CPU 占用高出不少换用 root 加桥接后吞吐量提升明显。不过对一般应用测试和学习rootless 完全够用。考试更关注能不能对外提供服务不会刻意压测性能。7.4 容器时区不对怎么办容器默认使用 UTC 时间和本地时间不一致这是日志时间对不上的常见原因。解决办法是在启动时传入时区环境变量podman run -d --name web -e TZAsia/Shanghai nginx:1.24也可以在部署脚本里固定设置。很多镜像不内置时区数据传TZ变量不一定生效这种情况需要挂载宿主机的时区文件podman run -d --name web -v /etc/localtime:/etc/localtime:ro nginx:1.24容器日志时间正常了排查问题才能按时间线对齐。这个坑不算难但第一次遇到时会让人摸不着头脑。7.5 镜像下载慢与离线加载镜像下载慢最有效的办法是配置镜像加速源或内部仓库镜像前面已经讲过。如果网络环境完全隔离只能离线导入。前面的save和load方法在实际机房部署中非常高频。离线环境里我一般提前把需要的镜像打好版本标签连同启动命令一起整理成文档交给现场同事按步骤执行避免现场临时找镜像。8. 一些备考和实操中的真心话RH134 的容器章节考试落脚点其实很明确能不能用普通用户跑起容器来、能不能正确配置卷和 SELinux、能不能用 systemd 让容器开机自启、能不能通过日志和端口验证服务对外可用。命令背下来只是基础重要的是理解每个参数解决什么问题。我自己的备考方法是准备一台最小安装的测试机从普通用户开始把一个容器从拉取、运行、挂卷、配 systemd 完整跑三遍。第三遍闭卷不看任何资料按顺序敲完才算真正过关。如果条件允许找人互相出几个“容器起不来”的故障题从logs和inspect入手练排查效果比单纯看文档好太多。最后分享一个我长期在用的排查原则遇到任何容器问题先分清是镜像层的问题还是容器层的问题。镜像层问题下载失败、标签错误用podman images和pull解决容器层问题启动退出、权限拦截、日志无输出用ps -a、logs、inspect定位。分清楚层排查思路就不会乱。