如何在.gitlab-ci.yml中通过SSH在远程服务器正确执行多命令?
问题
在GitLab CI的.gitlab-ci.yml中尝试通过SSH登录远程服务器执行一系列命令,最初使用Here-Doc写法失败,改写为单行命令后出现bash: line 2: --env: command not found错误,Docker Run的参数被识别为独立命令。
原Here-Doc配置(报错):
deploy_to_server_job: stage: deploy_to_server tags: - server-runner image: ubuntu:22.04 script: - chmod og= $ID_RSA - | ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP bash << EOF docker system prune --force echo "Running container with image: $FULL_IMAGE_NAME" docker run -p 127.0.0.1:$SERVER_PORT:$SERVER_PORT \ --env JWT_AUTH_SECRET=$JWT_AUTH_SECRET \ --env JWT_AUTH_EXPIRES=$JWT_AUTH_EXPIRES \ --env JWT_REFRESH_SECRET=$JWT_REFRESH_SECRET \ --env JWT_REFRESH_EXPIRES=$JWT_REFRESH_EXPIRES \ --env MONGODB_URI=$MONGODB_URI \ --env PORT=$PORT \ -d --name admin-server $FULL_IMAGE_NAME exit EOF
单行命令配置(报错bash: line 2: --env: command not found):
- ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "whoami; echo Running container with image $FULL_IMAGE_NAME; docker system prune --force; docker run --name admin-server -d -p 127.0.0.1:$SERVER_PORT:$SERVER_PORT --env JWT_AUTH_SECRET=$JWT_AUTH_SECRET --env JWT_AUTH_EXPIRES=$JWT_AUTH_EXPIRES --env JWT_REFRESH_SECRET=$JWT_REFRESH_SECRET --env JWT_REFRESH_EXPIRES=$JWT_REFRESH_EXPIRES --env MONGODB_URI=$MONGODB_URI --env PORT=$PORT $FULL_IMAGE_NAME; exit"
解决方案
方法1:修复Here-Doc写法(规避GitLab CI解析冲突)
GitLab CI的YAML解析对Here-Doc的缩进、换行和变量解析有严格要求,修正后的配置如下:
deploy_to_server_job: stage: deploy_to_server tags: - server-runner image: ubuntu:22.04 script: - chmod og= $ID_RSA - | ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP <<-'EOF' docker system prune --force echo "Running container with image: '"$FULL_IMAGE_NAME"'" docker run -p 127.0.0.1:'"$SERVER_PORT"':'"$SERVER_PORT"' \ --env JWT_AUTH_SECRET='"$JWT_AUTH_SECRET"' \ --env JWT_AUTH_EXPIRES='"$JWT_AUTH_EXPIRES"' \ --env JWT_REFRESH_SECRET='"$JWT_REFRESH_SECRET"' \ --env JWT_REFRESH_EXPIRES='"$JWT_REFRESH_EXPIRES"' \ --env MONGODB_URI='"$MONGODB_URI"' \ --env PORT='"$PORT"' \ -d --name admin-server '"$FULL_IMAGE_NAME"' EOF
- 用
<<-'EOF'替代bash << EOF,-允许EOF标记前存在缩进,避免YAML解析报错; - 本地变量用
'"$VAR"'包裹,确保Git Runner在本地解析变量后,传递到远程服务器时保留正确的引号格式,防止参数被意外拆分。
方法2:用单引号包裹远程命令,避免本地解析干扰
单行命令出错的核心原因是双引号下Git Runner会提前解析部分变量,破坏远程命令的结构。改用单引号包裹整个远程命令,同时对需要本地解析的变量单独拼接:
deploy_to_server_job: stage: deploy_to_server tags: - server-runner image: ubuntu:22.04 script: - chmod og= $ID_RSA - ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP 'whoami; echo Running container with image '"$FULL_IMAGE_NAME"'; docker system prune --force; docker run --name admin-server -d -p 127.0.0.1:'"$SERVER_PORT"':'"$SERVER_PORT"' --env JWT_AUTH_SECRET='"$JWT_AUTH_SECRET"' --env JWT_AUTH_EXPIRES='"$JWT_AUTH_EXPIRES"' --env JWT_REFRESH_SECRET='"$JWT_REFRESH_SECRET"' --env JWT_REFRESH_EXPIRES='"$JWT_REFRESH_EXPIRES"' --env MONGODB_URI='"$MONGODB_URI"' --env PORT='"$PORT"' '"$FULL_IMAGE_NAME"''
- 整个远程命令用单引号
'包裹,阻止Git Runner解析命令内部的分号和参数; - 需要本地替换的变量(如
$FULL_IMAGE_NAME)用'"$VAR"'形式拼接,确保变量值正确传递到远程,同时保留引号避免参数拆分。
方法3:写入临时脚本执行(最可靠,适合复杂命令)
对于复杂的远程执行逻辑,推荐将命令写入临时脚本后再通过SSH执行,彻底规避YAML和Shell解析的各类问题:
deploy_to_server_job: stage: deploy_to_server tags: - server-runner image: ubuntu:22.04 script: - chmod og= $ID_RSA # 创建临时部署脚本 - cat > deploy.sh << 'SCRIPT' #!/bin/bash docker system prune --force echo "Running container with image: $1" docker run -p 127.0.0.1:$2:$2 \ --env JWT_AUTH_SECRET=$3 \ --env JWT_AUTH_EXPIRES=$4 \ --env JWT_REFRESH_SECRET=$5 \ --env JWT_REFRESH_EXPIRES=$6 \ --env MONGODB_URI=$7 \ --env PORT=$8 \ -d --name admin-server $1 SCRIPT - chmod +x deploy.sh # 传递脚本和参数到远程执行 - ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "bash -s" < ./deploy.sh "$FULL_IMAGE_NAME" "$SERVER_PORT" "$JWT_AUTH_SECRET" "$JWT_AUTH_EXPIRES" "$JWT_REFRESH_SECRET" "$JWT_REFRESH_EXPIRES" "$MONGODB_URI" "$PORT"
- 将部署逻辑写入独立Shell脚本,避免YAML的解析限制;
- 通过
bash -s和标准输入传递脚本,用位置参数传递变量,彻底避免变量解析冲突; - 这种方式可读性更强,后续修改命令也更便捷。
内容的提问来源于stack exchange,提问作者ENIAC
相关产品推荐
相关产品推荐

