Docker 容器和虚拟化技术这两件事,很多刚接触的朋友是把它们混在一起理解的,觉得装个 Docker 就等于用了虚拟机。等真正在一台 openEuler 机器上把 Docker UI 这类管理面板跑起来,才会发现两者的分界线在资源模型、隔离边界、生命周期管理上完全是两套逻辑。这篇文章就围绕 OpenEuler 部署 Docker UI 这一件事,把背后的选型逻辑、环境准备、参数计算、实操命令、加固策略和踩坑记录完整讲一遍。Docker 容器负责进程级的轻量隔离,虚拟化负责硬件级的强隔离,而 Docker UI 面板则是把 Docker Engine 的 API 用人能看懂的方式渲染出来的一个前端。装面板这件事听起来简单,但把它装到能用、能给别人用、能长期稳定用,中间隔着防火墙、SELinux、cgroup 版本、证书、权限模型好几道坎。适合已经会用几条 docker 命令、但还没系统梳理过容器运行时环境的读者,也适合需要给团队内部搭一套可视化容器管理入口的运维同学。全程按我实际在一台测试机上的操作顺序来写,命令可以直接抄。
1. 先把容器与虚拟化的边界划清楚
1.1 从一次资源评估说起:虚机与容器的成本差异
年初我接手一个内部环境的整理工作,原本是一台物理机上跑了四台虚拟机,每台虚机里再装一套 Docker,层层套娃。四台虚机各分了 8 核 16G,实际负载用 top 一看,CPU 长期在 5% 以下,内存堆着没释放的缓存。这种情况就是典型的用虚拟化做隔离,结果把资源预算全花在空转的操作系统上了。虚拟机的隔离粒度是硬件抽象层,Hypervisor 给每台虚机切出独立的虚拟 CPU、内存、磁盘和网卡,客户机里跑的是完整的操作系统内核,启动要经历 BIOS 模拟、内核引导、init 拉起一整套流程,冷启动一分钟起步很正常。
容器完全是另一条路。它复用宿主机内核,靠 namespace 做视图隔离(PID、网络、挂载点、UTS、IPC、用户),靠 cgroup 做资源配额和统计。一个容器启动本质上是 fork 一个进程再给它套上一层隔离视图,所以秒级甚至百毫秒级就起来了。内存开销上,容器里不需要再跑一套 systemd、udev、日志守护进程,省下来的就是实实在在的可用内存。我那次整理把四台虚机合并成一台宿主机加十几个容器,内存占用从 64G 降到 18G 左右,启动时间从分钟级压到秒级。
但容器不是万能的。共享内核意味着内核出问题大家一起遭殃,隔离强度也不如虚机,跑不可信负载或者需要不同内核版本的时候,虚机仍然是对的选择。我的实际做法是分层:底层用 KVM 虚机做资源的硬边界,虚机内部再用 Docker 跑业务容器,最上面加一层 UI 面板做日常可视化操作。这个组合在很多内部环境里都成立,也是我推荐新手先建立的心智模型。
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 隔离边界 | 硬件抽象层,独立内核 | 内核 namespace + cgroup |
| 启动耗时 | 分钟级 | 秒级以内 |
| 单实例内存开销 | 数百 MB 起(Guest OS) | 数 MB 起(进程本体) |
| 密度 | 单机十几台 | 单机几十到上百个 |
| 内核版本 | 各实例可不同 | 与宿主机共享 |
| 适用场景 | 强隔离、异构内核、不可信负载 | 微服务、CI 构建、环境一致性 |
1.2 OpenEuler 上跑容器的几个隐性优势
选 OpenEuler 作为容器宿主机,除了生态和许可方面的考虑,有几个技术点在实际使用中确实省事。它的内核版本比较新,对 overlay2 存储驱动、cgroup v2、以及 cgroup namespace 的支持都比较完整,这意味着 Docker 的默认配置基本能开箱工作,不需要像在老内核上那样反复调存储驱动。overlay2 相比早期的 devicemapper,元数据开销小、层数支持多、跨层文件读取快,是当前最稳的默认选择。
第二个是 systemd 与 cgroup 驱动的一致性。Docker 的 cgroup driver 如果和 systemd 不一致,会出现容器资源限制不生效、systemctl status docker里一堆告警的情况。新版本 Docker 默认就用 systemd 驱动,和 OpenEuler 的 systemd 版本匹配,省掉了改配置这一步。
第三个是防火墙和 SELinux 的默认策略。这两样是容器部署里最容易出问题的地方,openEuler 默认启用 firewalld,SELinux 一般是 permissive 或者关闭状态。firewalld 和 Docker 的 iptables 规则会打架,这个坑后面单独讲。SELinux 如果是 enforcing,容器挂载宿主机目录会直接 Permission denied,需要打:z或者:Z标签。提前知道这两件事,能省下大半天排查时间。
提示:动手前先确认三件事——
getenforce看 SELinux 状态,systemctl is-active firewalld看防火墙,uname -r看内核版本。这三条信息决定了后面要不要额外处理。
2. 环境准备与 Docker 引擎安装的取舍
2.1 系统基线检查清单
装机之后别急着敲 docker 命令,先花五分钟做基线检查。我用的是一个固定清单,逐项过一遍,后面出问题的概率能降一大半。第一项是时间同步,容器之间、容器和宿主机之间做 TLS 握手、日志对齐都依赖时间准确,timedatectl status看 NTP 是否激活,没激活就timedatectl set-ntp true。第二项是磁盘分区,df -h确认/var或者准备放 Docker 数据的那块盘有足够空间,根分区只剩 10G 的话,拉几个镜像就满了。
第三项是内存和 swap。容器默认不做内存限制的时候,会吃掉宿主机所有可用内存,然后触发 OOM Killer 随机杀进程,这个行为很难预测。建议在 daemon 层面或者容器层面显式限制。swap 的话,如果宿主机开了 swap,cgroup 内存限制的语义会变得复杂,很多生产环境干脆swapoff -a,或者通过--memory-swap显式控制。第四项是主机名和 hosts,容器里如果没有正确的主机名解析,一些需要反向解析的服务会卡在启动阶段。
# 基线检查一把梭 timedatectl status df -h /var /var/lib/docker 2>/dev/null free -h getenforce systemctl is-active firewalld uname -r sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward这一项很关键,Docker 要做端口映射和容器间转发,这个内核参数必须是 1。如果之前有人为了安全把它关了,Docker 启动时会报iptables failed或者bridge相关错误。临时开用sysctl -w net.ipv4.ip_forward=1,持久化写进/etc/sysctl.d/99-docker.conf。
2.2 安装源的选择与离线包的处理
openEuler 上装 Docker 有两条主流路径。第一条是用系统自带软件源里的引擎包,dnf search docker先把包名确认清楚,不同小版本的命名可能是docker也可能是docker-engine,直接猜包名容易装错。用系统源的好处是依赖关系自动解决、后续可以跟着系统更新走,坏处是版本可能偏旧,某些新特性用不上。
第二条是用上游的二进制包或者离线 rpm 包。内网环境、生产环境不能直连外网的时候,这条路径几乎是唯一选择。做法是在一台能联网的同版本机器上用dnf download --resolve把依赖全拉下来,拷到目标机dnf install ./*.rpm。注意--resolve参数必须加,否则 containerd.io、runc 这些依赖得手动一个个找,非常痛苦。
# 联网机器上收集离线包 mkdir -p /tmp/docker-offline && cd /tmp/docker-offline dnf download --resolve docker-engine docker-compose-plugin # 目标机上安装 cd /tmp/docker-offline dnf install -y ./*.rpm装完先别启动,把 daemon 配置写好再启,免得来回重启。这里有个顺序上的经验:/etc/docker/daemon.json必须在服务启动前就位,否则第一次启动会用默认的>systemctl stop docker mkdir -p /data/docker rsync -aHAX --delete /var/lib/docker/ /data/docker/
然后写 daemon 配置。下面这份是我在多数场景下的基线,参数含义逐条说明:>{ "data-root": "/data/docker", "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" }, "live-restore": true, "default-address-pools": [ {"base": "172.30.0.0/16", "size": 24} ] }
default-address-pools这行值得单独算一笔。Docker 默认给每个 bridge 网络从172.17.0.0/16开始分配/16网段,第二个网络就是172.18.0.0/16,以此类推。如果你的内网里已经有172.17.x.x的服务,或者跟虚机网段撞了,容器出网就会时通时不通,症状极其诡异。改成从172.30.0.0/16里切/24子网之后,理论上能切出 256 个网络,每个网络 254 个可用地址,对绝大多数单机场景绰绰有余。这个池子的大小要结合你预计创建的 compose 项目数量来定,一个 compose 项目默认建一个网络,按项目数往上留一倍余量比较稳妥。
磁盘容量估算也给个公式:镜像总量按平均每个 500MB 乘以预计镜像数,容器日志按单容器 50MB × 3 个文件算,卷按业务实际数据量估,三者相加再乘 1.5 作为水位余量。二十个容器、三十个镜像的规模,粗略算下来 15G 镜像 + 3G 日志 + 卷,准备 40G 到 50G 是比较舒服的区间。
3. Docker UI 面板选型:为什么不能随便装一个
3.1 三类主流方案的对比
Docker UI 这个说法在社区里指的是一类东西,本质都是 Docker Engine API 的图形化客户端。它们调用/var/run/docker.sock或者tcp://host:2375上的 API,把容器列表、镜像、网络、卷、日志这些资源渲染成网页。选型上大致分三类。
第一类是通用容器管理面板,功能全、支持多节点、有用户体系和 RBAC,适合团队共用。第二类是极简型的单机面板,只做容器和镜像的增删查,代码量小、依赖少、攻击面也小,适合个人或者临时环境。第三类是把容器管理和编排、应用模板、GitOps 揉在一起的平台型工具,功能强但部署复杂度上一个台阶,机器配置不够会很难受。
| 方案类型 | 典型特征 | 资源占用 | 多节点 | 用户与权限 | 适用场景 |
|---|---|---|---|---|---|
| 通用管理面板 | 用户体系、RBAC、模板、镜像仓库集成 | 中等,200MB 内存起 | 支持 | 完善 | 团队内部共用、多环境统一入口 |
| 极简单机面板 | 容器与镜像基础操作 | 低,30 到 80MB | 不支持 | 基本没有 | 个人测试机、临时排障 |
| 平台型工具 | 编排、应用市场、CI 集成 | 高,1G 内存起 | 支持 | 完善 | 有专职运维的中大型环境 |
我自己的选择逻辑很简单:如果这台机器只给自己用,装个极简的就够了,多一个组件多一份维护成本;如果是给团队开个入口,让不熟悉命令行的同学也能看日志、重启容器,那必须选有用户体系和权限控制的面板,否则等于把 root 权限的遥控器扔在公共区域。
3.2 端口与访问路径的规划
不管选哪个面板,网络规划这一步得先做。面板本身是个 Web 服务,会监听一到两个端口,常见的是 9443(HTTPS 管理界面)和 8000(边缘代理隧道)。规划时问自己三个问题:谁能访问、从哪访问、走不走加密。
我的习惯是把面板端口绑定到127.0.0.1,前面放一个反向代理做 TLS 终止和访问控制,对外只暴露反向代理的 443。这样做的好处是端口不直接暴露在网络上,扫描器扫不到;证书只需要在反向代理维护一份,不用管面板自己那套自签证书;访问日志统一在反向代理侧,方便审计。
# 只监听本地回环的绑定写法 docker run -d -p 127.0.0.1:9443:9443 ...这个写法和-p 9443:9443的区别很大,后者会在0.0.0.0上监听,配合 Docker 直接写 iptables 的行为,会绕过 firewalld 的 zone 规则直接放通,相当于你以为防火墙拦住了其实是敞开的。绑定到回环之后,Docker 写的那条 DNAT 规则只对本地流量生效,外网进不来,安全性直接拉满。
3.3 权限模型:别把容器运行时套接字裸奔
这里要讲一个很多人忽略的点。把/var/run/docker.sock挂进容器,等于把宿主机的 root 权限交给了这个容器。因为通过这个套接字可以创建特权容器、挂载宿主机根目录、读取任意文件,逃逸出去只是几步的事。面板本身是可信的,但它暴露在网络上有被攻击的风险,一旦面板被拿下,宿主机也就没了。
三种缓解思路,按实现成本从低到高排。第一种是收紧网络暴露面,只绑回环、前面加认证代理、上 TLS,这层成本最低效果也最直接。第二种是上套接字代理,一个专门的小容器以只读方式挂载 docker.sock,只开放需要的 API 端点,面板连代理而不是连真套接字。第三种是换用无根模式或者 TCP + 双向 TLS 的远程接入方式,复杂度更高。
套接字代理的用法大致是这样,把它和面板放同一个自定义网络里,面板的连接地址填代理的服务名加端口:
docker network create ui-net docker run -d --name socket-proxy \ --network ui-net \ --restart unless-stopped \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -e CONTAINERS=1 -e IMAGES=1 -e NETWORKS=1 \ -e VOLUMES=1 -e EXEC=1 -e INFO=1 \ tecnativa/docker-socket-proxy环境变量是白名单机制,只写你自己需要的,没写的端点会被 403。注意EXEC=1能开就尽量别开,它允许在容器里执行命令,等于绕过了所有镜像层的不可变性保证。POST=0是默认值,如果你只想让人看不想让人改,保持默认就行。
4. 实操:把面板跑起来并接上引擎
4.1 目录、卷与启动参数的逐一解释
我用容器化部署面板本身,这样升级就是换个镜像标签的事。第一步先建一个命名卷存面板自己的数据,包括用户、密码哈希、端点配置、访问令牌。用命名卷而不是绑定目录,省得处理权限和 SELinux 标签问题。
docker volume create portainer_data第二步启动容器。命令我拆成逐参数解释,这些参数都是踩过坑之后留下来的:
docker run -d \ --name portainer \ --restart unless-stopped \ -p 127.0.0.1:9443:9443 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer-ce:latest--restart unless-stopped保证宿主机重启后面板自动起来,但手动 stop 之后不会自作主张再启动,这个语义比always更贴合运维直觉。-v /var/run/docker.sock这一行是面板能工作的前提,它让容器内的进程能访问宿主机的容器运行时。-v portainer_data:/data是持久化目录,没有这个,容器一删配置全丢,重新初始化很麻烦。镜像标签我写的是latest,正式环境建议钉死具体版本号,比如2.19.4这种,避免某天自动拉到不兼容的新版本。
启动后docker logs -f portainer看日志,正常会输出监听端口的提示。浏览器访问https://127.0.0.1:9443,第一次会让你设置管理员密码,密码要求至少 12 位,我建议直接用密码管理器生成一串随机串存好,这玩意儿丢了只能重置数据卷,等于配置全重来。
4.2 接上已有容器与远程节点
初始化向导里会让你选环境类型,本地 Docker 就直接选 Docker,面板会自动填好套接字路径。这里有个细节:如果面板本身跑在容器里,套接字路径要填容器内的路径,也就是/var/run/docker.sock,而不是宿主机的什么别的路径。因为容器已经把这个文件挂进来了,容器内看到的路径就是挂载点。
接远程节点用 TCP 加 TLS 的方式,需要在被管节点上开启 2376 端口并配置证书。这个方式的好处是面板可以统一管理多台机器,坏处是证书管理麻烦,尤其是节点多了之后。我的折中方案是:核心的几台机器用 TCP + TLS 接入,临时机器用边缘代理方式,在被管节点上跑一个 agent 容器主动连回面板的 8000 端口,不需要开放入向端口,穿透内网比较方便。
接入之后,面板上能看到容器列表、镜像列表、网络、卷,还能进容器看日志、开终端、看资源占用曲线。资源曲线这块要注意,面板采的是 Docker stats 接口的数据,采样频率不高,别拿它当监控系统用,做容量规划还是得上专门的监控。
4.3 反向代理与证书配置
前面说了端口绑回环,对外访问靠反向代理。配置文件里几个点必须写对,否则会看到空白页或者连接被重置。
server { listen 443 ssl; server_name console.example.internal; ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key; ssl_protocols TLSv1.2 TLSv1.3; location / { proxy_pass https://127.0.0.1:9443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_ssl_verify off; proxy_read_timeout 300s; } }proxy_pass后面用的是https://,因为面板自己监听的就是 HTTPS,即使绑在回环上也是加密的。这就带来一个问题:面板用的是自签证书,Nginx 去连它的时候会校验失败,所以需要proxy_ssl_verify off。这一行的代价是反向代理到面板这一段不校验证书,但因为走的是本机回环,实际风险可以接受。
Upgrade和Connection这两行是给 WebSocket 用的。面板的容器终端、日志流式输出都依赖 WebSocket,缺了这两行,页面上点终端会一直转圈或者提示连接断开,但其他页面都正常,很容易误判成面板的 bug。proxy_read_timeout调大是因为长时间挂着的日志流会被默认 60 秒超时切断,改成 300 秒之后体验好很多。
证书这块,内网环境用自签或者内部 CA 签发都行,签发的时候 SAN 一定要把访问用的域名写进去,只写 IP 的话浏览器会报名称不匹配。自签证书在浏览器里会有一个警告页,如果是给团队用,最好还是走内部 CA,把根证书推送到各终端,省得每个人都来问一遍怎么跳过警告。
5. 部署之后的加固与日常维护
5.1 用户分层与最小权限落地
面板装好只是开始,真正决定它能不能长期用的是权限设计。默认情况下第一个注册的账号是管理员,能看能改能删。给团队开放的时候,我会按角色拆成三层:一层是只读观察者,能看容器状态和日志,不能做任何变更;一层是运维操作员,能重启容器、拉镜像、清理无用资源,但不能改端点配置和用户;一层是管理员,一到两个人,负责用户和端点管理。
这个分层的依据是操作的可逆性。重启容器可逆,删容器不可逆,改端点配置影响面大,所以权限按不可逆程度递增。实际操作中很多人图省事所有人给管理员,结果某天有人误删了一个没有挂卷的数据库容器,数据就真没了。这种事故我见过不止一次,加一层角色控制就能避免。
5.2 磁盘水位与资源回收
容器环境最容易出事的地方是磁盘。镜像层越堆越多、日志越写越大、卷里的临时数据没人清,几个月下来磁盘就满了。我的做法是三道防线。
第一道是日志限制,前面 daemon.json 里的max-size和max-file已经解决了一大半,单个容器最多占 150MB 日志空间。第二道是定期清理,用docker system df看空间分布,docker image prune清悬空镜像,docker builder prune清构建缓存。清理命令别用带-a的全量清理,那会把你还在用的镜像删掉,容器重启的时候拉不下来就尴尬了。第三道是水位告警,宿主机上放一个定时任务,磁盘使用率超过 80% 就往内部通知渠道发一条。
# 看看空间都花在哪了 docker system df -v # 清悬空镜像和构建缓存,不动在用的 docker image prune -f docker builder prune -fdocker system df -v的输出里,RECLAIMABLE 那一列是关键,它告诉你哪些空间是可以回收的。如果这一列数字很大但清理命令删不掉,通常是还有容器引用着这些镜像层,得先把容器处理掉。
5.3 配置备份与快速恢复
面板的配置全在那个命名卷里,包括用户、密码哈希、端点、访问令牌。备份思路很简单:定期把卷的内容打包出来,存到另一台机器上。命名卷在宿主机上的实际路径是/data/docker/volumes/portainer_data/_data,用 tar 打包的时候注意保留权限。
tar -czf /backup/portainer_data_$(date +%F).tar.gz \ -C /data/docker/volumes/portainer_data _data恢复的时候反过来,先停容器,把内容解回卷目录,改好属主,再启容器。这里有个坑:解包之后文件的所有者可能变成当前用户,而容器里跑面板进程的 UID 是固定的,权限不对会启动失败。解包后chown -R到正确的 UID 就行,具体是多少可以从原环境的目录属主上看。
除了卷,还有一份东西必须备份:你自己写的 compose 文件、daemon.json、反向代理配置。这些散落在各个目录,最好统一收拢到一个配置仓库里,机器挂了能快速重建。我吃过一次亏,面板数据卷备份了,但 compose 文件没备份,重装之后所有容器的启动参数靠记忆一条条复原,折腾了一下午。
6. 常见问题与排查实录
6.1 页面打不开、502 与空白页
按现象分三种情况。第一种是浏览器直接连接超时,说明端口根本没通。先ss -lntp | grep 9443看进程有没有监听,再确认绑定地址是127.0.0.1还是0.0.0.0。如果绑的是回环,从别的机器访问当然超时,这种时候别去怀疑防火墙,先看绑定地址。
第二种是反向代理返回 502,说明 Nginx 连不上上游。可能原因有三个:面板容器没起来、端口填错、或者面板是 HTTPS 而代理配的是 HTTP。第三种是页面能打开但一片空白,浏览器控制台里有一堆资源加载失败。这种情况多半是路径前缀的问题,面板假定自己部署在根路径下,如果反向代理配了子路径(比如/portainer/),静态资源路径就对不上,页面自然空白。解决办法是给面板分配一个独立域名,别用子路径。
# 排查三连 docker ps -a | grep portainer docker logs --tail 100 portainer curl -k -I https://127.0.0.1:9443curl -k里的-k是跳过证书校验,用来确认面板本身是否活着。如果本地 curl 通、通过代理不通,问题就锁定在代理层。
6.2 权限拒绝与 SELinux 拦截
Permission denied在容器部署里出现频率极高,原因分两类。第一类是文件系统权限,挂载的目录属主和容器内进程的 UID 不匹配。容器里跑服务的用户往往不是 root,比如 UID 1000,而宿主机上的目录属主是 root,写不进去。解决办法是chown到对应 UID,或者在启动参数里加--user指定用户。
第二类是 SELinux。enforcing 状态下,容器挂载宿主机目录时,即使文件权限是 777 也会被拒绝,因为 SELinux 看的是安全上下文。报错信息里会带avc: denied,用ausearch -m avc -ts recent能查到具体拒绝记录。最省事的处理是给挂载路径加:z后缀,让 Docker 自动打上容器可读写的标签;:Z是独占标签,同一个目录不会被多个容器共享。
# 看 SELinux 拒绝日志 ausearch -m avc -ts recent 2>/dev/null | tail -20 # 给挂载目录打标签 chcon -Rt container_file_t /data/appdata我不建议图省事直接setenforce 0,那是把整个系统的强制访问控制关掉,风险比解决一个标签问题大得多。要么正确打标签,要么在装机阶段就明确这台机器不做 enforcing,别中途关。
6.3 容器网络不通与时区错乱
网络问题里最常见的是容器能 ping 通 IP 但解析不了域名。原因是/etc/resolv.conf里的 DNS 服务器在容器网络里不可达,或者被 Docker 覆盖成了127.0.0.11(内置 DNS)而上游又没配好。检查方式是在容器里cat /etc/resolv.conf,再用getent hosts试解析。如果宿主机用的是本地 DNS 缓存服务监听在127.0.0.53,容器里是访问不到的,得在 daemon.json 里显式配置dns指向真实的上游地址。
另一个高频问题是容器时区不对,日志时间戳差 8 小时。这跟容器精简镜像里没带时区数据有关。最轻量的做法是启动时挂载宿主机的时区文件:
-v /etc/localtime:/etc/localtime:ro或者在构建镜像的时候把 tzdata 装进去并设置TZ环境变量。两种方式我都用,临时排障用挂载,正式镜像用构建时固化,后者不依赖宿主机配置,可移植性更好。
至于 firewalld 和 Docker 的冲突,症状是容器端口在防火墙里没放通却能访问,或者放通了反而访问不了。根因是 Docker 自己往 nat 表的 DOCKER 链里写规则,优先级高于 firewalld 的规则。最干净的解法就是前面说的,容器端口一律绑回环,对外统一走反向代理,让 firewalld 只管反向代理那一个端口。
6.4 镜像拉取慢与仓库配置
内网环境拉镜像慢是常态。配置镜像加速的方式是在 daemon.json 里加registry-mirrors数组,填内部镜像仓库的地址。配置完重启 Docker 生效,用docker info看输出里有没有列出来。注意镜像加速只对 Docker Hub 的镜像生效,如果镜像是从别的仓库拉的,得改镜像地址前缀或者在 daemon.json 里配insecure-registries(内部仓库用自签证书的时候)。
拉取失败的另一个常见原因是磁盘满了,但报错信息显示的是no space left on device之外的奇怪错误,比如unexpected EOF。遇到这种情况先df -h看磁盘,再看docker system df,很多莫名其妙的问题根源都是磁盘。
| 现象 | 高概率原因 | 排查命令 | 处理方式 |
|---|---|---|---|
| 浏览器连接超时 | 端口绑在回环 | ss -lntp | 改绑定或走代理 |
| 反向代理 502 | 上游协议或端口不符 | curl -k -I | 核对 http/https 与端口 |
| 页面空白 | 子路径部署 | 浏览器控制台 | 改用独立域名 |
| 挂载目录写不进 | 属主或 SELinux | ls -ln、ausearch | chown 或加:z |
| 容器解析不了域名 | DNS 不可达 | cat /etc/resolv.conf | daemon.json 配 dns |
| 日志时间差 8 小时 | 缺时区数据 | date | 挂载 localtime 或装 tzdata |
| 防火墙规则不生效 | Docker 直写 iptables | iptables -t nat -L | 端口绑回环,统一代理 |
| 拉镜像报错 | 磁盘满或仓库不可达 | df -h、docker info | 清理空间或改仓库地址 |
我在实际使用中发现,这套东西真正难的不是装,而是装完之后建立一套不依赖个人记忆的维护习惯。面板的版本号钉死、备份脚本挂到定时任务、磁盘水位加监控、权限按角色分配,这四件事做完,这套环境基本就能自己活着了。反倒是那些装完就丢在那里的,过三个月再去看,多半是密码忘了、磁盘满了、或者面板更新到一半卡住。装的时候顺手把维护路径铺好,比事后补救省事得多。