通过 SSH 管理远程机器
--host 通过 SSH 在另一台机器上运行 ironflock 命令,而不是在本机运行。您可以用它从笔记本电脑(包括 Mac)管理设备,而无需在设备上安装 ironflock。多次给出该标志,或配合主机列表文件使用,即可同时在多台机器上运行命令。
ironflock --host [user@]machine <command> [flags]
ironflock --host [user@]machine --host [user@]machine ... <command> [flags]
ironflock --hosts <file> [--parallel <n>] <command> [flags]除 completion 之外的每个命令都可以这样在另一台机器上运行。--host、--hosts 和 --parallel 是全局标志:它们可以放在命令名称之前或之后。
单台机器
--host [user@]machine 在该机器上以相同的标志运行相同的命令。它输出的内容原样返回,stdout 到 stdout,stderr 到 stderr,退出码也一样。
$ ironflock --host pi@device-17 version
ironflock 2.0.2 linux/arm64
$ ironflock --host pi@device-17 agent remove --yes --json 2>/dev/null
{"action":"removed","removed":[...]}另一台机器的要求
- amd64、arm64 或 32 位 ARM(v5 至 v7)上的 Linux。 其他系统会以退出码 3 结束。
- 能运行命令的 SSH 服务器。 不需要
scp或 SFTP。 - POSIX 登录 shell: sh、bash、dash 或 zsh。csh、tcsh 和 fish 会以退出码 3 结束,并给出相应的消息。
- 以 root 登录,或者使用
sudo, 用于需要 root 的命令(参见另一台机器上的 root 权限)。
机器上不需要安装 ironflock:每次运行都会带上自己的二进制文件。
SSH 设置
--host 使用本机的 ssh,因此您的 ~/.ssh/config、密钥、SSH agent 和跳板机都与使用 ssh 时一样有效。它会关闭命令不需要的功能:agent 转发和 X11 转发、端口转发、RemoteCommand 和 LocalCommand。
--host 只接受 [user@]machine,不接受其他内容。端口或其他选项请写入 ~/.ssh/config:
Host device-*
User pi
Port 2222
StrictHostKeyChecking accept-new有了这些设置,ironflock --host device-17 status 会以 pi 身份在端口 2222 上登录,并且 ssh 会接受它尚不认识的设备的主机密钥。以 - 开头的 --host 值会被拒绝;含有字母、数字、.、_、- 以及 IPv6 地址中的 : 和 % 以外字符的值同样会被拒绝。
如果控制套接字的路径足够短,整个运行过程只使用一个 SSH 连接;套接字创建在临时文件夹下,否则创建在 /tmp 下。此时 ssh 只询问一次密码。否则,运行的每一步都会打开自己的连接,ssh 每次都会再次询问。
由 ironflock agent install 或 IronFlockOS 设置的设备有一个供项目所有者使用的 SSH 用户,该用户用设备 .flock 文件中的密钥登录。SSH 访问介绍了用户名以及如何从文件中取出密钥。
机器上发生了什么
- 二进制文件。 如果机器与本机平台相同,它会得到本机正在运行的二进制文件。否则,
ironflock会从<download base>/cli/<os>/<arch>/<version>/ironflock下载适用于该机器平台的二进制文件,并在使用之前先在本机检查它:用本二进制文件自带的 IronFlock 发布密钥检查其签名(.asc),并检查其构建信息,构建信息必须写明程序ironflock、该机器的平台以及与本二进制文件相同的源代码修订版本。下载的文件保存在您的缓存文件夹中(Linux 上为~/.cache/ironflock,Mac 上为~/Library/Caches/ironflock),每次使用时都会再次检查。 - 文件夹。 二进制文件放入一个只有登录账户可读的新文件夹(
mktemp -d),位于机器上的${TMPDIR:-/tmp}之下;如果程序无法在临时文件夹中运行(文件系统以noexec挂载),则位于登录账户的主目录之下。两处都无法运行程序时,命令以退出码 3 结束,并指出这两个位置。在运行文件夹中的任何内容之前,机器会检查该文件夹和二进制文件属于登录账户且不是符号链接,并检查二进制文件的版本和平台符合预期。 - 文件。 命令的标志中指定的文件会一并发送(参见标志中指定的文件)。
- 命令。 它以登录账户的身份运行,或通过
sudo运行(参见另一台机器上的 root 权限)。 - 清理。 命令结束时,机器会删除存放二进制文件和副本的文件夹:失败之后、连接丢失之后,以及收到信号 HUP、INT、QUIT、TERM、PIPE、USR1 和 USR2 之后也是如此。
--host 不会在机器上留下 ironflock,即使在 agent install 之后也是如此。之后可以继续使用 --host,或者用安装命令在机器上安装 ironflock;不带参数时,安装命令只进行安装。通过 --host 运行的 self-update 更新的是该机器上的这个 /usr/local/bin/ironflock,而不是本次运行的二进制文件(ironflock self-update)。
在两种情况下,机器上会留下一个 ironflock.* 文件夹:
- 命令运行期间,机器断电或执行了
kill -9。该文件夹中有二进制文件和副本。 - 连接在复制之后、命令启动之前丢失,或者在启动命令的步骤正在连接时,本机的运行被中断。该文件夹中有二进制文件;如果您是在终端上运行命令的,还有副本。
.flock 文件的副本包含设备的机密,WiFi 密码文件的副本包含 WiFi 密码,代理密码文件的副本包含代理密码。只有登录账户和 root 能读取它们。请手动删除这样的文件夹。
标志中指定的文件
| 标志 | 命令 |
|---|---|
-c、--config(.flock 文件) | agent install、agent configure、doctor、os flash、os install、os reset |
--wifi-password-file | os flash |
--proxy-password-file | agent install、agent configure、os install |
每个文件都在本机读取,相对路径相对于本机的工作文件夹。WiFi 密码文件或代理密码文件可以是管道(/dev/stdin),只读取一次。内容通过 SSH 连接的标准输入发送到机器。文件及其大小都不会出现在命令行上,无论在本机还是在那台机器上都不会,因为每个用户都能读取那里的命令行。机器会对照命令所能接受的最大值检查每个文件的大小,然后检查内容是否全部到达。
在机器上,副本为 <folder>/1/device.flock:位于运行文件夹中一个单独的文件夹(权限 0700)里,名称与在本机时相同,权限为 0600。那里的命令得到的是这个路径,例如 --config=/tmp/ironflock.k3Xf9QpL2m/1/device.flock,因此关于文件内容的错误会指出这个副本。
以下情况会在本机结束命令:
- 退出码 1 — 文件不是普通文件(WiFi 密码文件或代理密码文件也可以是管道),或者文件大于命令所能接受的大小:
.flock文件为 5 MiB,WiFi 密码文件或代理密码文件为 4096 字节。 - 退出码 2 — 文件名中含有控制字符。
- 退出码 2 —
os flash的--image-file和--output。--host不会把镜像复制到机器上,也不会把文件带回。请在本机不带--host运行该命令,或者直接在那台机器上运行。不带--image-file时,机器会自己下载镜像。
另一台机器上的 root 权限
- 登录账户不是 root 时,需要 root 的命令通过
sudo运行。无需密码、无需终端即可使用的sudo在任何情况下都可行。 version、os images和os drives以登录账户的身份运行。status和doctor在有 root 时能读取更多内容。当登录账户就是 root,或者可以无需密码、无需终端地使用sudo时,它们以 root 身份运行。否则,它们以登录账户的身份运行,不会询问,它们的提示会给出带--host root@machine的命令。agent logs读取属于 root 的文件,通过sudo运行。- 当
sudo要求输入密码,或其配置要求终端(requiretty)时,它需要机器上有终端。这又要求本机的 stdin、stdout 和 stderr 都连接到终端。没有终端时,命令会在复制任何内容之前以退出码 3 结束。此时请以 root 身份登录,让登录账户无需密码、无需终端即可使用sudo,或者在终端上运行命令且不做重定向。 - 如果
sudo在命令即将运行时拒绝,同样以退出码 3 结束。
那里的命令给出的下一步命令是针对本机这一端的,不带 sudo,因为 --host 会自己获取 root:ironflock --host pi@device-17 agent start。
终端、询问与输出
- 本机有终端时,命令在那台机器上也会得到终端,因此它可以提出询问。此时它的 stdout 和 stderr 合在一起返回到您的终端上。
- 本机没有终端时(管道、
--json输出到程序、CI),stdout 和 stderr 保持分开,命令不询问任何内容:由--yes确认。机器上登录账户的文件所输出的内容(例如用 echo 输出内容的.bashrc)会从 stdout 中丢弃,使 stdout 只承载命令的结果。 --yes、--json和命令的标志会一并传过去。下载基址以--download-base的形式传过去:取自该标志,或本机的IRONFLOCK_DL_BASE。含有登录信息的下载基址(https://user:[email protected]/dl)会在连接任何机器之前以退出码 2 结束,因为在那台机器上,它会出现在每个用户都能读取的命令行中。--help、help、不带命令的命令组以及completion都在本机作答:它们输出的内容在每台机器上都一样。- 机器在命令运行之前通过 stdout 发送的内容(它的应答以及登录账户的文件输出的文本)最多读取 64 KiB。超过时,运行会在命令于那里运行之前以退出码 1 结束。
Ctrl-C 与连接丢失
本机的 Ctrl-C 会像在那台机器自己的终端上按 Ctrl-C 一样到达那里的命令,连接丢失则以挂断的形式到达它,无论本机有没有终端。按下 Ctrl-C 之后,--host 最多等待 15 秒,等机器报告那里的命令已结束及其退出码(如果是 Ctrl-C 结束了它,则为 130)。如果机器没有及时报告,或者连接先结束了,--host 以 130 结束,并说明命令可能仍在那里运行。按下 Ctrl-C 时命令尚未启动的运行不会再启动该命令。
通过 --host 运行的 os install 有自己的规则:一旦回答了它的询问,连接丢失就不再能停止它,命令会在本机等待机器以 IronFlockOS 的身份重新上线;参见 ironflock os install。
退出码与错误
退出码就是命令的退出码(退出码)。--host 额外增加以下退出码:
| 代码 | 含义 |
|---|---|
| 255 | ssh 失败:没有连接、登录账户或主机密钥被拒绝、连接中断 |
| 3 | 机器无法运行该命令:其他系统或 CPU、登录 shell 不是 POSIX shell、没有可以运行程序的文件夹、sudo 不允许该登录账户 |
| 130 | 中断,无论是在本机(Ctrl-C),还是机器上的命令被中断(那里的挂断、中断或终止信号) |
--host 本身的失败与其他错误一样:stderr 上的一行 Error:,或者使用 --json 时 stdout 上的错误对象。唯一的例外是:如果命令的输出已经传回,该输出就是结果,错误只是 stderr 上的 Error: 行,使用 --json 时也是如此。
多台机器
多次给出 --host,或使用 --hosts <file>,会像 --host 在一台机器上运行命令那样,在每台机器上运行命令。--host 的机器排在前面,然后是文件中的机器。只含一台机器的主机列表文件也按多台机器的方式运行。
$ ironflock --host pi@vm1 --host pi@vm2 --host pi@vm3 status
pi@vm2: Agent: installed in /opt/ironflock/device-agent
pi@vm2: Version: 1.6.9
pi@vm2: Service: running (ironflock-device-agent.service)
pi@vm3: ssh: connect to host vm3 port 22: Connection timed out
pi@vm3: Error: the SSH connection to pi@vm3 failed
pi@vm1: Agent: installed in /opt/ironflock/device-agent
...
Summary of "status" on 3 machines, exit code 255: 2 done, 1 ssh.
pi@vm1 done
pi@vm2 done
pi@vm3 255 ssh: the SSH connection to pi@vm3 failed (ssh: connect to host vm3 port 22: Connection timed out)主机列表文件
文件每行写一个 [user@]machine。# 开始一条注释,一直到行尾;空行会被忽略。文件也可以是管道,例如 --hosts <(...),大小最多 1 MiB。
# 3 号包装线
pi@device-17
pi@device-18
pi@device-19 # the spare unit# 显示文件中每台机器的状态,每次 20 台
ironflock --hosts line3.txt --parallel 20 status
# 在文件中的每台机器上更新代理程序;agent update 会询问,因此需要 --yes
ironflock --hosts line3.txt agent update --yes
# 为一个站点的设备设置同一个代理账户;密码来自文件
ironflock --hosts site-a.txt agent configure --https-proxy http://[email protected]:3128 --proxy-password-file proxy-password.txt --yes以下情况会在任何操作运行之前以退出码 2 结束,错误会指出文件和行:
- 一行中有不止一个词;
--host会拒绝的机器;- 同一台机器出现两次。同一台机器配两个不同的用户也算两次,因为命令会同时在它上面运行两次。同一台机器的地址和名称不会被识别为同一台机器。
并行运行
最多同时运行 --parallel 台机器:不带该标志时为 10,范围 1 至 100。它们按给出的顺序启动,一台结束时下一台启动。没有多台机器时使用 --parallel 会以退出码 2 结束。
不询问的 SSH
没有机器会得到终端,ssh 也不询问任何内容(BatchMode=yes)。以下情况会在相应机器上以退出码 255 失败,并给出 ssh 自己的说明,其他机器照常运行:
- 需要密码的登录账户;
- 密钥口令未加载到 SSH agent 中的密钥;
ssh尚不认识的主机密钥。
要接受新的主机密钥,请先用 ssh 连接一次,或者像上面的示例那样,在 ~/.ssh/config 中为这些机器设置 StrictHostKeyChecking accept-new。ssh 在 30 秒后放弃连接(ConnectTimeout=30,无论 ~/.ssh/config 中如何设置),因此关机的机器会在此时被报告。那里要求密码或终端的 sudo 会在该机器上以退出码 3 结束;此时 status 和 doctor 以登录账户的身份运行。
多台机器的输出
- 机器写出的每一行都完整传回,前面带有
<host>::其 stdout 到 stdout,其 stderr 到 stderr。各机器的行按到达顺序混在一起。 - 最后一行如果没有换行符,会补上一个。长于 64 KiB 的行会分成若干个 64 KiB 的部分传回,每部分前面都带有主机。
- 除制表符外的控制字符会转义后传回(
\x1b、\r),行中和汇总中都是如此,这样任何机器都无法移动光标或覆盖其他机器的行。行尾的回车符会被去掉。 - 在本机失败的运行会添加它的
Error:行,前面带有主机,与--host为单台机器写出的一样。 - 最后,stderr 会得到一份汇总:退出码以及以各种方式结束的机器数量,然后按给出的顺序为每台机器写一行。该行显示
done,或者显示退出码、其类别及原因:本机的错误(255 时为ssh自己的说明)或命令的Error:行。在 Ctrl-C 之后没有确认其命令已结束的机器显示为interrupted, may still run。
多台机器的退出码
命令以各机器中最差的退出码结束:每台机器都以 0 结束时为 0。否则,从最差的开始依次为:
- 130 — 被中断,无论是在本机还是在某台机器上。
- 1 — 命令在某台机器上失败,那台机器可能已被改动了一半,无论命令在那里以哪个退出码结束。
- 255 — 某台机器无法连接,或连接中断。
- 3 — 某台机器不处于命令所需的状态,或无法运行该命令。
- 4 — 某个询问的回答为“否”。
退出码 2 表示本机的命令行有误,与所有命令一样。
JSON 行
使用 --json 时,每台机器的命令结束时,stdout 会为该机器得到一行,按结束的顺序排列,除此之外没有其他内容。程序可以在这些行到达时逐行读取。
{"host":"pi@vm1","state":"done","exit_code":0,"result":{...}}| 字段 | 含义 |
|---|---|
host | 机器,写法与 --host 或主机列表文件中的相同 |
state | done:命令以 0 结束。failed:命令在那里以其他退出码结束,或其运行在本机失败。interrupted:运行被中断,且机器报告命令已结束。may_still_run:运行在本机被中断,且机器没有在等待时间内报告命令已结束。not_started:运行在轮到该机器之前被中断;那里没有运行任何内容。 |
exit_code | 命令在机器上如何结束,或其运行在本机如何结束:机器无法连接或连接中断时为 255,机器无法运行该命令时为 3,运行被中断时为 130。其他退出码(例如被 kill 之后的 137)原样取自机器,状态为 failed。 |
result | 命令使用 --json 时写出的内容,如该命令的页面所述。对于 agent logs,是其各行组成的数组;最多保留 16 MiB。写出结果并以 1 结束的命令(doctor、agent update)也有这个字段。 |
error | 命令没有以 0 结束的原因,带 kind 和 message:本机运行的错误(无法连接的机器其类别为 ssh,消息末尾在括号中附上 ssh 的说明),否则为命令的错误对象,再否则为由其退出码和 stderr 上最后一行组成的错误 |
output | 命令写到 stdout 中不是 JSON 的内容;如果写出的内容超过 16 MiB,则为说明这一点的一句话 |
stderr 中是各机器的行(其中包括命令在使用 --json 时自己写出的 Error: 行,例如 agent update)以及汇总。本机运行的错误只出现在 stdout 的行中。
# status 没有以 0 结束的机器
ironflock --hosts line3.txt status --json 2>/dev/null | jq -r 'select(.state != "done") | .host'多台机器上的 Ctrl-C
Ctrl-C、SIGTERM 或 SIGHUP 结束这些运行的方式与结束单台机器上的运行相同:那里的每个命令都会收到它,每台机器有 15 秒时间确认其命令已结束。没有确认的机器为 may_still_run。尚未轮到的机器不运行任何内容。汇总将它们列为 not started,命令以 130 结束。
哪些命令可在多台机器上运行
| 命令 | 在多台机器上 |
|---|---|
version、status、doctor、os images、os drives、agent restart、agent start | 始终可以 |
agent logs | 始终可以;带 --follow 时不能与 --json 同用,因为机器的行要等其命令结束时才会传回,而 --follow 不会结束 |
agent stop、agent update、agent configure、agent remove、os update、os rollback、self-update | 需带 --yes:它们会询问,而同时在多台机器上运行时没有人会被询问 |
completion | 与 --help 一样在本机作答,不在任何机器上运行 |
agent install、os install、os flash、os reset | 完全不能:它们的 .flock 文件指定的是一台设备,或者它们会在指明某一台机器的询问之后擦除磁盘、驱动器或设备的数据 |
在多台机器上运行时,指定本机文件的标志会被拒绝,因为该文件会发送到每台机器:doctor 和 agent configure 的 -c 指定的是一台设备。例外是 agent configure 的 --proxy-password-file,用于为一个站点的各台机器设置同一个代理账户。
不能在多台机器上运行的命令行会以退出码 2 结束并说明原因,这发生在读取主机列表文件之前,也在连接任何机器之前。