敲下命令,屏幕上跳出一个让人安心的词。Ansible 回了 pong,Azure 回了 success。两个词看起来都像“搞定了”,可它们覆盖的范围,比字面意思窄得多。

打开网易新闻 查看精彩图片

这是 DevOps 练习的第 82 天,Azure 的第 32 天。当天两个任务都来自 KodeKloud Engineer 平台:一个是改好 inventory 文件,让 Ansible 能连上三台服务器;另一个是把一个 blob 在容器之间迁移。两个任务,两个让人误会的单词。

三台服务器,三个明文密码

实验环境给的 inventory 长这样:三台应用服务器,每台配一个明文密码,直接写在文件里。任务要求改好它,让 Ansible 能连上全部三台,并证明连接有效。

Ansible 的文档在变量说明处就把规则摆了出来:关于 ansible_password,文档写得很直白——“永远不要以明文存储这个变量;始终使用 vault”。同一页还给出了默认预期:Ansible 默认假设你用 SSH 密钥连接远程机器,密钥是被鼓励的方式,需要时也可以用 --ask-pass 走密码认证。

也就是说,这份现成的 inventory 同时做了两件错事:把密钥明文存着,又绕开了工具默认的连接方式,为的是省掉配置那一步。

值得对照的是九天前的第 80 天:一个 Jenkins 任务的构建步骤里,服务器密码被敲了三遍,任何对该任务有配置权限的人都能读到。同样的决定,相反的结局。差别恐怕不在工程师,而在工具——Ansible 把规则写在了变量自己的描述里,而 Jenkins 的自由风格 shell 步骤,压根没有对应的文档页。

密钥,和那个比看上去更重要的口令

生成密钥的过程里有两处值得停下来看。第一处:不带任何参数的 ssh-keygen 直接给出 ed25519。这正好接上第 74 天的修正——第 71 天用了 -t rsa -b 4096,第 74 天用了 -t rsa -b 2048,两次都不是有意识的选择,因为手册里 ed25519 就是默认类型,RSA 默认长度是 3072。一个是从默认值升级,一个是从默认值降级,而当时都没查过默认值。两个参数都省掉,反而直接拿到更新的默认。

第二处:口令留空。这一点比看上去更要紧。Ansible 的连接文档把限制说得很清楚:默认的 ssh 连接插件“不提供通道让用户与 ssh 进程通信,以手动接受密码来解密 ssh 密钥”。也就是说,带口令保护的密钥在 Ansible 运行期间无法被解密。文档给出的办法是使用 agent,同一页还写着“强烈推荐使用 ssh-agent”。

于是选项只有两个:空口令,让密钥文件本身成为唯一凭证;或者口令加一个已加载的 agent,更安全,但多一步。有口令却没有 agent,是跑不通的。

没有组头的 inventory

改好的 inventory 只有三行,每行指定一个用户名,没有 [group] 组头。Ansible 并不需要它:文档说明,即使不在 inventory 里定义任何组,Ansible 也会创建两个默认组 all 和 ungrouped,而 all 组包含所有主机。所以这三行可以单独寻址,也可以作为 all 或 ungrouped 寻址。

验证用的是 ansible stapp01 -i inventory -m ping,返回里带着 ping: pong,还有一行 discovered_interpreter_python。这个模块的文档自己就提醒:这不是 ICMP ping,只是一个要求远端有 Python 的简单测试模块;它不适合写进 playbook,但适合从命令行验证能否登录、以及是否配置了可用的 Python。

所以 pong 证明的是两件事:登录成功,以及远端有可用的 Python。那行解释器路径就是后半截检查的可视化。反过来说,一台机器在线、可路由、22 端口在听,但没有 Python,这项检查会失败;一台 ICMP 不通但 SSH 可达的机器,反而能通过。它是验证 inventory 的正确工具,也是诊断网络的错误工具。

“success”只说明任务被排上了

第二个任务用 az storage blob copy start 在容器之间复制文件。数据全程在存储服务内部移动,不经过客户端。这一点有三重意义:规模上,33 字节和 33 太字节是同一条命令、同样的成本,不占本地磁盘;计费上,先下载再上传会把数据带出 Azure 再带回来,按出网流量计费,而同账号的服务端复制不产生外部传输;速度上,命令立刻返回,而不是流式搬运任何东西。

返回里带着 copy_id 和 copy_status: success。对于同一存储账号内的小 blob,操作在响应写出之前就完成了,所以状态直接读作 success。换成大 blob,或者跨账号、跨区域的复制,返回的会是 pending。进度这时不在命令输出里,而在目标 blob 上,需要用 az storage blob show 配合 query 去查状态和进度。

结论很直接:迁移脚本不能从 copy start 的返回里推断完成,必须轮询到状态离开 pending,并且处理 failed 和 aborted。这和前半场的 pong 是同一个形状——两个命令都用一个鼓励性的词作答,两个词覆盖的范围都比看上去小,而两处文档都精确写明了这个词到底覆盖什么。

还有一个坑:这个命令返回的字段是蛇形命名,而几乎所有其他 az 命令返回驼峰命名,因为 CLI 序列化的是带类型的 SDK 模型,而这一条直接透传了 Python SDK 的普通字典。所以 --query “{Status:copyStatus}” 会返回 null,正确写法是 --query copy_status。一个习惯可以覆盖这类问题:不熟悉的命令先用 -o json 跑一遍,再写 query。靠猜只会拿到 null,而 null 和空值无法区分。

两个校验,两种证据强度

任务要求两个容器都持有该文件,且内容一致。这是两个主张,需要不同的证据。MD5 对比很强,但仍是二手的:它比较的是 Azure 在上传时记录的摘要,如果 blob 的内容设置本身有误,它照样会报出一个过期的摘要。对实际下载下来的字节跑 cmp,才是只依赖数据本身的检查。

顺带一个演示:Azure 报出 Lu7zilatbGguzSz2Ecn5IQ==,而 md5sum 打印出 2eeef38a56ad6c682ecd2cf611c9f921。这是同样的 16 个字节——把 base64 解码,得到的正是那串十六进制。在下结论说校验和不匹配意味着数据损坏之前,这一点值得先知道。

最后是任务标题的问题。它叫“同步容器”,而要求只是一个文件。标题命名的是一个话题,编号的要求才是合同。如果真要做标题说的那件事,工具是 azcopy sync,它会比较源和目标、只传差异;而 copy start-batch 每次都全量复制。

两个命令,两个让人安心的词,没有一个和它看起来的意思一致。pong 意味着登录成功并找到了可用的 Python,它对网络一无所知;success 意味着复制被排上了,只要文件大到值得在意,它就会改口说 pending,等你去别处看。有用的部分是,两个工具都把这件事写清楚了,就写在你想到要去查时才会看的地方。

所以第 82 天的问题留在这里:你屏幕上那些绿色的勾,有几个是在报告“已排期”,而不是“已完成”?