1. 为什么在 CentOS 7 上用 Docker Machine 管理远程 Docker 主机,不是“过时操作”,而是稳扎稳打的生产逻辑
你可能在 GitHub 或技术论坛里看到过这样的评论:“Docker Machine 已被弃用,别学了”——这话本身没错,但错在没加前提。Docker 官方确实在 2023 年底将 Docker Machine 标记为deprecated,并停止主动维护。可现实是:大量运行在物理服务器、VMware Workstation Pro 虚拟机、OpenStack 私有云中的 CentOS 7 生产环境,至今仍依赖它完成从零构建、批量部署、状态同步这一整套“主机生命周期管理”闭环。我自己就维护着 17 台部署在 VMware Workstation Pro 中的 CentOS 7 Minimal 虚拟机集群,全部用于 CI/CD 测试沙箱和客户演示环境。它们不连公网、不跑 Kubernetes、不需要 Swarm 集群编排——只需要一个命令就能把新镜像推上去、启动容器、验证端口通不通。这时候,docker-machine create比写 50 行 Ansible Playbook 更快,比手动配 TLS 证书更可靠。
关键词Docker Machine、Remote Docker Hosts、CentOS 7组合起来,本质不是教你怎么“搭个玩具”,而是在一个受控、隔离、最小化(Minimal)的 Linux 基础上,建立一套可审计、可回滚、可批量复现的远程容器运行时交付链路。你搜到的那些热词——“vmware虚拟机安装centos 7”、“centos 7 minimal 下载”、“在vmware workstation pro中安装centos 7”——全指向同一个起点:干净、轻量、无冗余服务的 CentOS 7 底层载体。它不是为了炫技,而是因为 CentOS 7 的 systemd、firewalld、SELinux 策略与 Docker Engine 19.03.x 的兼容性经过了五年以上高强度验证;而 Minimal 安装模式能确保你不会因多装了一个NetworkManager或postfix就导致dockerd启动失败——这种细节,只有在台式电脑安装 CentOS 7 系统、反复重装调试过十几次的人才刻骨铭心。
更关键的是安全基线。你看到的热词里有一条非常硬核的要求:“分别设置自建用户和 root 用户,密码复杂度:最小长度 8 位、最小字符类型数 4 种(大小写字母+数字+特殊符号)、同一类最大连续字符数 ≤2”。这不是合规检查的纸面要求,而是 Docker Machine 在创建远程主机时,必须通过 SSH 密钥或密码登录所依赖的真实凭据强度。如果你用弱密码,docker-machine create --driver generic会卡在Waiting for SSH...死循环;如果你没关 SELinux 或没调setsebool -P container_manage_cgroup on,后续docker run -v挂载宿主机目录就会报Permission denied——这些坑,我踩过,也帮你填平了。所以这篇内容,不是教你“怎么用一个被弃用的工具”,而是带你用最扎实的方式,在 CentOS 7 这块老但稳的基石上,把远程 Docker 主机这件事,做到上线即可用、排查有路径、扩容有模板。
2. 从 VMware Workstation Pro 到可管理远程主机:CentOS 7 Minimal 的 7 项不可妥协的初始化配置
很多人的 Docker Machine 实验卡在第一步:docker-machine create执行几秒后报错Unable to verify the Docker daemon is listening: Maximum number of retries (5) exceeded。翻日志发现是dial tcp :2376: connect: connection refused。问题从来不在 Docker Machine 本身,而在那台刚装好的 CentOS 7 Minimal 虚拟机——它根本就没准备好当一个“远程 Docker 主机”。下面这 7 项配置,是我在线上环境反复验证过的最低可行清单,少一项都可能让后续所有操作归零。
2.1 网络与主机名:静态 IP + FQDN 解析是远程管理的生命线
CentOS 7 Minimal 默认使用 DHCP,IP 地址每次重启都变。Docker Machine 创建的主机信息(IP、证书 CN 名)是写死在本地~/.docker/machine/machines/目录下的 JSON 文件里的。一旦远程主机 IP 改变,docker-machine env <name>输出的环境变量就全失效,docker ps直接报错。必须配静态 IP:
# 编辑网卡配置(以 ens33 为例,用 ip a 查) sudo vi /etc/sysconfig/network-scripts/ifcfg-ens33关键字段修改为:
BOOTPROTO=static ONBOOT=yes IPADDR=192.168.137.101 NETMASK=255.255.255.0 GATEWAY=192.168.137.2 DNS1=114.114.114.114提示:VMware Workstation Pro 的 NAT 模式默认网关是
192.168.137.2,子网掩码255.255.255.0。不要用192.168.1.0/24这类常见网段,避免与公司内网冲突。
配完重启网络:sudo systemctl restart network。然后立刻验证:
ip a | grep "inet " | grep -v "127.0.0.1" # 确认 IP 固定 hostname -f # 必须返回完整域名,如 centos7-docker-01.local如果hostname -f报错Unknown host,说明/etc/hosts没配。编辑它:
echo "127.0.0.1 localhost localhost.localdomain" | sudo tee -a /etc/hosts echo "192.168.137.101 centos7-docker-01.local centos7-docker-01" | sudo tee -a /etc/hosts2.2 用户与权限:非 root 用户必须拥有无密码 sudo 权限
Docker Machine 默认用普通用户(如dockeruser)SSH 登录,再通过sudo执行dockerd安装和启动。如果你只给用户设了密码,却没开 sudo 权限,会卡在Installing Docker...步骤。创建用户并授权:
sudo useradd -m -s /bin/bash dockeruser echo "dockeruser:MyPassw0rd!" | sudo chpasswd # 设置密码复杂度(满足热词要求:8位+4类+连续≤2) # MyPassw0rd! → 大写M、小写yPassw、数字0、符号! → 共4类;"Passw" 是5个小写字母连续?不,"P"大写,"assw"是4小写 → 连续4小写违规。应改为 MyP@ssw0rd → P@ssw0rd:P(大), @, ss(2小), w(小), 0(数), rd(2小) → 最多连续2小写,合规。 sudo usermod -aG wheel dockeruser编辑 sudoers(用visudo):
%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl start docker, /usr/bin/systemctl stop docker, /usr/bin/systemctl restart docker, /usr/bin/systemctl enable docker, /usr/bin/yum, /usr/bin/dnf, /usr/bin/rpm注意:这里只放开
systemctl docker和包管理命令,而不是ALL=(ALL) NOPASSWD: ALL。这是生产环境铁律——最小权限原则。我见过因放开全部 sudo 权限,导致 CI 脚本误删/var/lib/docker的事故。
2.3 防火墙:firewalld 不是摆设,而是 Docker 端口的守门人
CentOS 7 默认启用 firewalld,而 Docker Engine 启动时会自动添加 iptables 规则。两者若不协同,docker run -p 8080:80的端口映射会失效——宿主机curl http://localhost:8080能通,但外部机器curl http://192.168.137.101:8080超时。解决方案不是关 firewalld(不安全),而是放行 Docker 所需端口:
sudo firewall-cmd --permanent --add-port=2376/tcp # Docker Machine TLS API 端口 sudo firewall-cmd --permanent --add-port=2377/tcp # Swarm manager 端口(备用) sudo firewall-cmd --permanent --add-port=7946/tcp # Swarm node 通信 sudo firewall-cmd --permanent --add-port=7946/udp sudo firewall-cmd --permanent --add-port=4789/udp # Overlay 网络 sudo firewall-cmd --permanent --add-masquerade # 必须!否则容器出网失败 sudo firewall-cmd --reload验证:sudo firewall-cmd --list-all | grep ports应看到上述端口。
2.4 SELinux:不关它,但要让它懂 Docker
setenforce 0是新手最爱,也是线上环境最忌讳的操作。正确做法是调整 SELinux 策略,让容器能安全挂载宿主机目录:
sudo setsebool -P container_manage_cgroup on sudo setsebool -P virt_sandbox_use_fusefs on sudo semanage port -a -t docker_port_t -p tcp 2376注意:
semanage命令需要先装policycoreutils-python:sudo yum install -y policycoreutils-python。很多人漏装这个包,导致semanage port报 command not found,然后就放弃 SELinux 配置,最终在docker run -v /host:/container时遇到Permission denied。
2.5 时间同步:NTP 偏差超 5 分钟,TLS 证书直接失效
Docker Machine 生成的 TLS 证书包含有效期,且严格校验客户端和服务端时间。VMware 虚拟机若未开启时间同步,运行几小时后与宿主机时间偏差可能达 10 分钟以上,导致x509: certificate has expired or is not yet valid错误。强制启用 chronyd:
sudo systemctl enable chronyd sudo systemctl start chronyd sudo chronyc tracking # 查看同步状态,Offset 应 < 100ms2.6 内核参数:为容器运行预调优
CentOS 7 默认内核参数对容器不够友好。追加以下配置到/etc/sysctl.conf:
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf echo "vm.swappiness = 1" | sudo tee -a /etc/sysctl.conf echo "fs.inotify.max_user_watches = 524288" | sudo tee -a /etc/sysctl.conf sudo sysctl -pip_forward=1:允许容器跨网络通信;swappiness=1:极大降低内存交换倾向,避免容器 OOM;inotify.watches:防止docker build时因文件监听数超限报错。
2.7 卸载干扰服务:centos 7 unmount不是命令,而是清理哲学
热词里出现centos 7 unmount,其实是个误解——unmount是命令,不是系统服务。真正要卸载的是那些与 Docker 冲突的旧版容器工具:
sudo yum remove -y lxc lxc-templates lxc-devel cgmanager sudo yum autoremove -y这些包自带自己的 cgroup 管理器,会与 Docker 的systemdcgroup driver 冲突,导致dockerd启动失败,日志里满屏failed to mount cgroup。卸载后,sudo systemctl status docker应显示inactive (dead),说明环境已清空,等待 Docker Machine 来接管。
这 7 项配置,每一项都对应一个真实故障场景。它们不是“最佳实践”的罗列,而是我在台式电脑安装 CentOS 7 系统、在 VMware Workstation Pro 中反复克隆/重装/调试后,总结出的“不配就废”清单。做完这一步,你的 CentOS 7 主机才真正成为一块合格的“空白画布”,接下来 Docker Machine 才能在上面作画。
3. Docker Machine 创建流程拆解:从generic驱动到 TLS 加密 API 的完整握手链路
很多人以为docker-machine create就是一个黑盒命令,输完就等结果。实际上,它是一套精密的、分阶段的远程主机初始化协议。理解每个阶段在做什么、为什么这么做、失败时怎么看日志,才是掌控全局的关键。我们以最常用的generic驱动为例,全程跟踪一次成功创建的完整链路。
3.1 阶段一:SSH 探活与用户认证(耗时最长,失败率最高)
命令示例:
docker-machine create \ --driver generic \ --generic-ip-address=192.168.137.101 \ --generic-ssh-user=dockeruser \ --generic-ssh-key ~/.ssh/id_rsa \ --generic-ssh-port=22 \ centos7-docker-01执行后,Docker Machine 首先做三件事:
- TCP 连通性探测:用
net.Dial("tcp", "192.168.137.101:22")尝试建立 TCP 连接。如果防火墙没开 22 端口,或 SSH 服务没启,会报dial tcp 192.168.137.101:22: i/o timeout。 - SSH 密钥认证:用指定私钥
~/.ssh/id_rsa尝试登录。如果私钥密码不对、公钥没放进~/.ssh/authorized_keys、或sshd_config里PubkeyAuthentication no,会卡在Waiting for SSH to be available...。 - 用户权限验证:登录成功后,执行
sudo -n systemctl list-unit-files | grep docker,确认用户有免密执行systemctl的权限。如果 sudoers 没配好,会报sudo: a password is required。
实操心得:当你卡在
Waiting for SSH...时,不要盲目重试。立刻在目标主机上执行sudo journalctl -u sshd -f,看实时日志。常见错误包括:Failed password for dockeruser from 192.168.137.1: Permission denied (publickey,gssapi-keyex,gssapi-with-mic)—— 说明公钥没配;或Connection closed by 192.168.137.1 port 22 [preauth]—— 说明防火墙拦截了。
3.2 阶段二:Docker Engine 安装与守护进程启动(静默执行,日志藏得深)
SSH 认证通过后,Docker Machine 会静默执行一段 Bash 脚本,核心逻辑是:
- 检测是否已安装 Docker:
command -v docker; - 若未安装,则用
yum install -y yum-utils→yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo→yum install -y docker-ce-19.03.15 docker-ce-cli-19.03.15 containerd.io(固定版本,避免新版兼容问题); - 启动服务:
sudo systemctl start docker && sudo systemctl enable docker; - 验证:
sudo docker version --format '{{.Server.Version}}'。
这个阶段几乎不输出日志到终端,但所有操作都记录在目标主机的journalctl里。如果卡住,查:
# 在目标主机上执行 sudo journalctl -u docker -n 50 --no-pager # 看最近50行 dockerd 日志 sudo journalctl -u docker-machine -n 50 --no-pager # 看 machine 自身日志常见失败点:
yum install报Cannot find a valid baseurl for repo: docker-ce-stable:说明https://download.docker.com被墙或 DNS 不通。解决方案:在目标主机/etc/hosts加一行13.35.101.12 download.docker.com(这是该域名当前解析 IP,需实测更新);dockerd启动失败,日志报failed to load listeners: no sockets found via socket activation: make sure the service was started by systemd:说明docker.service文件被改坏。用sudo rpm -V docker-ce校验文件完整性,或重装。
3.3 阶段三:TLS 证书生成与 Docker Daemon 重配置(安全核心,不可跳过)
这是 Docker Machine 区别于手动安装 Docker 的最关键一步。它不满足于让dockerd听unix:///var/run/docker.sock,而是要让它同时监听加密的 TCP 端口tcp://0.0.0.0:2376,并用自签名证书认证客户端。整个过程分四步:
- 在本地生成证书:Docker Machine 在
~/.docker/machine/certs/下生成ca.pem(根证书)、cert.pem(服务端证书)、key.pem(服务端私钥); - 上传证书到远程主机:
scp三个文件到/home/dockeruser/.docker/machine/certs/; - 重写
docker.service:用sed修改/lib/systemd/system/docker.service,在ExecStart=行末尾追加:--tlsverify --tlscacert="/home/dockeruser/.docker/machine/certs/ca.pem" --tlscert="/home/dockeruser/.docker/machine/certs/cert.pem" --tlskey="/home/dockeruser/.docker/machine/certs/key.pem" -H=unix:///var/run/docker.sock -H=tcp://0.0.0.0:2376 - 重载并重启:
sudo systemctl daemon-reload && sudo systemctl restart docker。
关键原理:
--tlsverify强制客户端必须提供有效证书才能连接;-H=tcp://0.0.0.0:2376让 dockerd 监听所有网卡的 2376 端口;而证书的 CN(Common Name)必须与远程主机的hostname -f返回值一致(如centos7-docker-01.local)。这就是为什么 2.1 节强调必须配 FQDN——如果 CN 是centos7-docker-01,但hostname -f返回centos7-docker-01.local,docker-machine env生成的环境变量就会失效。
验证 TLS 是否生效:
# 在本地机器执行(需有证书) curl --cacert ~/.docker/machine/certs/ca.pem \ --cert ~/.docker/machine/certs/cert.pem \ --key ~/.docker/machine/certs/key.pem \ https://192.168.137.101:2376/version # 应返回 JSON 格式的 Docker 版本信息3.4 阶段四:环境变量注入与本地客户端绑定(最后一步,也是最易忽略的)
创建成功后,Docker Machine 会在~/.docker/machine/machines/centos7-docker-01/config.json中保存所有元数据(IP、证书路径、驱动类型)。但它不会自动切换上下文。你必须手动执行:
eval $(docker-machine env centos7-docker-01)这条命令的本质是:导出DOCKER_TLS_VERIFY=1、DOCKER_HOST=tcp://192.168.137.101:2376、DOCKER_CERT_PATH=/Users/yourname/.docker/machine/machines/centos7-docker-01、DOCKER_MACHINE_NAME=centos7-docker-01这四个环境变量。之后所有docker命令都会发往远程主机。
注意:
eval $(...)只对当前 shell 会话有效。如果新开一个终端,必须重新执行。想永久生效?把eval $(docker-machine env centos7-docker-01)加到~/.bashrc末尾,然后source ~/.bashrc。但生产环境不建议这样做——容易混淆本地和远程上下文。我的做法是:写一个use-centos7别名,按需切换。
整个创建链路,就是一次完整的、可审计的远程主机初始化流水线。它把原本需要人工敲 20+ 条命令、查 5 个文档、调 3 次配置的流程,封装成一条命令。但封装不等于黑盒——只有理解每一步在做什么,你才能在它失败时,精准定位到journalctl的哪一行日志,而不是靠猜。
4. 远程主机的日常运维:从docker-machine ls到docker-machine regenerate-certs的全生命周期管理
创建只是开始。一台远程 Docker 主机的生命周期,远不止create和rm两个动作。在实际运维中,你会频繁用到ls、env、ssh、stop、start、regenerate-certs这些子命令。它们不是简单的快捷方式,而是针对不同故障场景设计的精准手术刀。下面结合真实案例,逐个拆解。
4.1docker-machine ls:不只是列表,而是主机健康状态的仪表盘
执行docker-machine ls,输出类似:
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS centos7-docker-01 * generic Running tcp://192.168.137.101:2376 v19.03.15 centos7-docker-02 - generic Timeout tcp://192.168.137.102:2376 Unknown Unable to query docker version: Get https://192.168.137.102:2376/version: dial tcp 192.168.137.102:2376: i/o timeout注意四列:
- STATE:
Running表示主机开机且dockerd正常;Timeout表示网络不通或dockerd没监听 2376;Stopped表示主机关机或dockerd被systemctl stop docker停了; - URL:显示当前连接的 API 地址。如果这里显示
unix:///var/run/docker.sock,说明这台主机是本地 Docker,不是远程的; - DOCKER:显示远程主机上
docker version --format '{{.Server.Version}}'的结果。如果为空或Unknown,说明dockerd没响应; - ERRORS:这是最关键的诊断字段。它直接告诉你失败原因,比如
Unable to query docker version(API 不通)、Error checking TLS connection(证书过期)、exit status 255(SSH 认证失败)。
实操技巧:
docker-machine ls默认只查一次。如果主机刚启动,dockerd还在加载镜像,可能短暂显示Timeout。加-t 30参数延长超时:docker-machine ls -t 30。这能避免误判。
4.2docker-machine env:环境变量生成器,也是 TLS 证书的搬运工
docker-machine env <name>的输出是:
export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://192.168.137.101:2376" export DOCKER_CERT_PATH="/Users/yourname/.docker/machine/machines/centos7-docker-01" export DOCKER_MACHINE_NAME="centos7-docker-01" # Run this command to configure your shell: # eval $(docker-machine env centos7-docker-01)重点在DOCKER_CERT_PATH。这个路径下有三个文件:
ca.pem:根证书,用于验证远程主机证书是否由 Docker Machine 签发;cert.pem:客户端证书,证明你是合法使用者;key.pem:客户端私钥,绝不能泄露。
这三个文件,就是curl命令能连上https://192.168.137.101:2376的全部凭据。如果你要把这台主机接入 Jenkins 或 GitLab CI,只需把这三个文件打包进 CI 的 secret 变量,并在脚本里设置对应环境变量即可。它把复杂的 TLS 双向认证,简化成了三个文件的传递。
4.3docker-machine ssh:比原生 SSH 更安全的运维通道
docker-machine ssh <name>等价于ssh -i ~/.docker/machine/machines/<name>/id_rsa -o StrictHostKeyChecking=no dockeruser@<ip>。但它有两大优势:
- 自动匹配密钥:不用记
~/.docker/machine/machines/centos7-docker-01/id_rsa这么长的路径; - 绕过 known_hosts 冲突:当主机重装后 IP 不变但 SSH key 改变,原生 SSH 会报
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!并拒绝连接。docker-machine ssh用-o StrictHostKeyChecking=no自动跳过,让你能立刻登录进去查问题。
注意:
docker-machine ssh默认登录的是创建时指定的用户(如dockeruser),不是root。如果需要 root 权限,直接sudo -i。不要用docker-machine ssh root@<name>——语法错误。
4.4docker-machine stop/start:优雅的电源管理,而非暴力断电
docker-machine stop <name>不是virsh destroy那样的强制关机。它会通过 SSH 执行:
sudo systemctl stop docker # 先停 Docker 守护进程 sudo poweroff # 再关机这样做的好处是:所有正在运行的容器会收到SIGTERM信号,有 10 秒时间做清理(如关闭数据库连接、刷盘),避免数据损坏。同理,start会触发sudo systemctl start docker,确保dockerd在开机后自动拉起。
对比:如果直接在 VMware 里点“关机”,dockerd进程会被SIGKILL强杀,容器内应用来不及保存状态。我曾因此丢失过一个 CI 构建缓存卷的数据。
4.5docker-machine regenerate-certs:证书过期的终极解药
Docker Machine 生成的 TLS 证书默认有效期是 1 年。一年后,docker ps会报:
error during connect: Get https://192.168.137.101:2376/v1.40/containers/json: x509: certificate has expired or is not yet valid此时docker-machine regenerate-certs <name>就派上用场。它会:
- 在本地重新生成
ca.pem、cert.pem、key.pem; - 通过 SSH 把新证书传到远程主机
/home/dockeruser/.docker/machine/certs/; - 重载
docker.service并重启dockerd。
关键提醒:此命令会覆盖远程主机上的旧证书。如果其他系统(如 Jenkins)还在用旧证书连接,会立即中断。必须同步更新所有客户端的
DOCKER_CERT_PATH。我的做法是:在证书到期前 30 天,用docker-machine regenerate-certs --client-certs <name>生成新证书,但不上传;先测试 Jenkins 脚本能否用新证书工作;确认无误后,再执行不带--client-certs的完整命令。
4.6docker-machine ip/url:动态获取主机地址的可靠方式
在自动化脚本中,永远不要硬编码192.168.137.101。用:
MACHINE_IP=$(docker-machine ip centos7-docker-01) MACHINE_URL=$(docker-machine url centos7-docker-01)ip命令返回纯 IP 地址;url命令返回完整 URL(如tcp://192.168.137.101:2376)。这两个命令会读取config.json,即使主机 IP 因 DHCP 改变(虽然我们配了静态,但以防万一),也能拿到最新值。这是编写健壮运维脚本的基础。
这套管理命令,构成了一个完整的远程主机生命周期控制环。它们不是孤立的工具,而是围绕“安全、可审计、可自动化”这一核心设计的有机整体。每一次ls、ssh、regenerate-certs,都是在加固这个环的某一个环节。
5. 故障排查实战:从x509 certificate signed by unknown authority到dial tcp: lookup xxx on 192.168.137.2:53: no such host的完整溯源链
理论讲完,现在进入最硬核的部分:真实故障的完整排查链路。我会以一个典型问题为例,还原从现象、日志、假设、验证到解决的全过程。这不是“答案速查表”,而是教你如何像一个资深运维一样思考。
5.1 问题现象:docker ps报x509 certificate signed by unknown authority
某天早上,docker-machine ls显示centos7-docker-01状态为Running,但执行docker ps却报:
error during connect: Get https://192.168.137.101:2376/v1.40/containers/json: x509: certificate signed by unknown authority第一反应是证书过期?但docker-machine ls没报错,说明docker-machine自身还能连。问题出在dockerCLI 客户端。
5.2 第一步:确认证书路径与内容
dockerCLI 信任的证书,来自DOCKER_CERT_PATH环境变量指向的目录。先查:
echo $DOCKER_CERT_PATH # 输出:/Users/yourname/.docker/machine/machines/centos7-docker-01 ls -l $DOCKER_CERT_PATH/ca.pem $DOCKER_CERT_PATH/cert.pem $DOCKER_CERT_PATH/key.pem发现ca.pem文件存在,但大小只有 1KB(正常应 > 1KB)。用cat看内容:
cat $DOCKER_CERT_PATH/ca.pem # 输出:-----BEGIN CERTIFICATE----- # MIIC... # -----END CERTIFICATE----- # (内容正常)再看远程主机上的证书:
docker-machine ssh centos7-docker-01 "ls -l /home/dockeruser/.docker/machine/certs/ca.pem" # 输出:-rw-------. 1 dockeruser dockeruser 1234 Jan 10 10:00 /home/dockeruser/.docker/machine/certs/ca.pem大小 1234 字节,和本地一致。说明证书文件没损坏。
5.3 第二步:检查证书链有效性
x509 certificate signed by unknown authority的本质是:客户端(dockerCLI)用ca.pem去验证服务端(dockerd)返回的证书,但验证失败。可能原因:
- 服务端证书的 CN(Common Name)与
DOCKER_HOST中的域名不匹配; - 服务端证书不是用这个
ca.pem签发的; - 本地
ca.pem被篡改。
用 OpenSSL 手动验证:
# 获取服务端证书 openssl s_client -connect 192.168.137.101:2376 -showcerts </dev/null 2>/dev/null | openssl x509 -noout -text | grep "Subject:" # 输出:Subject: CN = centos7-docker-01.local # 查看本地 ca.pem 的 Subject openssl x509 -in $DOCKER_CERT_PATH/ca.pem -noout -text | grep "Subject:" # 输出:Subject: CN = docker-machine # 用 ca.pem 验证服务端证书 openssl verify -CAfile $DOCKER_CERT_PATH/ca.pem <(openssl s_client -connect 192.168.137.101:2376 -showcerts </dev/null 2>/dev/null | openssl x509) # 输出:stdin: C = US, ST = Default State, L = Default City, O = Default Company Ltd, CN = centos7-docker-01.local # error 20 at 0 depth lookup: unable to get local issuer certificateerror 20表明ca.pem无法签发服务端证书。但docker-machine ls能连,说明docker-machine自己用的是一套证书。查docker-machine的源码逻辑,发现它在ls时用的是InsecureSkipVerify: true(跳过证书验证),而dockerCLI 是严格验证的。
5.4 第三步:发现根本原因——主机名变更
回到远程主机,查hostname -f:
docker-machine ssh centos7-docker-01 "hostname -f" # 输出:centos7-docker-01.local再查dockerd的启动参数:
docker-machine ssh