GitLab CI中通过SSH执行嵌套Docker命令报错求助
嗨,我来帮你梳理下这个问题的核心原因,以及对应的解决办法:
问题根源
你遇到的两个错误其实是同一个原因导致的:你用双引号包裹远程命令时,本地的Shell会先解析里面的$(docker ps...)命令替换部分,而不是把这部分交给远程主机执行。
也就是说,$(docker ps --format '{{.Names}}' | grep '^backend')这段代码是在GitLab CI的Runner机器上运行的,而不是你目标的远程主机。如果Runner机器上没有安装Docker(或者PATH里找不到docker命令),就会抛出docker: command not found的错误;同时这个命令替换返回空值,传到远程后就变成了docker restart,自然会触发“需要至少1个参数”的报错。
而你之前单独执行的ssh ${REMOTE_HOST} "docker pull..."和ssh ${REMOTE_HOST} "docker-compose..."能正常工作,是因为这些命令里没有需要本地Shell解析的变量或命令替换,整个字符串直接传给了远程主机执行。
解决办法
你只需要让远程主机来解析这个嵌套命令即可,有两种常用的方式:
方式1:用单引号包裹整个远程命令
单引号会阻止本地Shell解析里面的特殊字符(包括$()),让整个命令原封不动地传给远程主机执行:
ssh ${REMOTE_HOST} 'docker restart $(docker ps --format "{{.Names}}" | grep "^backend")'
如果担心引号冲突,也可以把内部的双引号换成转义的单引号:
ssh ${REMOTE_HOST} 'docker restart $(docker ps --format '\''{{.Names}}'\'' | grep '\''^backend'\'')'
方式2:使用Here Document( heredoc )
这种方式更适合复杂的多命令场景,同样通过在EOF前加单引号来避免本地解析:
ssh ${REMOTE_HOST} << 'EOF' docker restart $(docker ps --format '{{.Names}}' | grep '^backend') EOF
补充说明
你之前尝试把嵌套命令里的docker换成/usr/bin/docker没用,也是因为这段代码是在本地Runner上执行的,本地并没有这个路径下的docker命令,所以还是会报错。只有让这段嵌套命令在远程主机上运行,指定远程的docker路径才会生效(不过如果远程的PATH已经包含docker路径,其实不需要硬编码)。
备注:内容来源于stack exchange,提问作者Milkncookiez

