Jenkins Pipeline如何在不同Docker主机上运行指定构建步骤
问题结论
该需求完全可行,你之前遇到的Method calls on objects not allowed outside "script" blocks报错,核心原因是声明式Pipeline中,所有Groovy对象方法调用(包括docker.withServer、docker.image这类Docker全局变量方法)必须包裹在script块内,不能直接作为steps下的顶层步骤。
另外你之前写的withRun参数位置也有错误:withRun第一个入参是docker run的容器配置参数(比如挂载、端口映射、环境变量),镜像对应的启动命令和参数需要传入第二个入参,否则参数会被识别为Docker启动参数导致执行失败。
实现方案
方案1:强制调度步骤到Jenkins主节点执行(推荐,无额外安全风险)
该方案不需要开启Docker远程TCP端口,只要Jenkins主节点提前安装好Docker环境即可,通过节点标签强制把bcbsn状态更新步骤分配到主节点运行,直接复用主节点的本地Docker daemon,不需要Docker in Docker。
具体写法
- 初始构建阶段代码:
stages { stage ('Staging'){ // 该stage所有步骤直接跑在主节点 agent { label 'built-in' } // 老版本Jenkins替换为label 'master' steps { withCredentials([usernamePassword(credentialsId: '6eab2fcc-7703-4d92-9e7d-120d66f771b3', usernameVariable: 'clientID', passwordVariable: 'clientSecret')]) { sh """ docker run --rm utrecht/bcbsn:2.0.4 \ -clientID ${clientID} \ -clientSecret ${clientSecret} \ -state INPROGRESS \ -commit ${env.GIT_COMMIT} \ -name ${buildName} \ -repoSlug talentindicator \ -owner talentinsights \ -url ${env.JOB_URL} \ -key ${buildName} """ } } } // 其他构建阶段保持原有逻辑,分配到Docker从节点执行即可 stage('Build') { agent { docker { image '你的业务构建镜像' // 保留原有从节点配置 } } steps { // 原有构建业务逻辑 } } }
- post区块代码:
post块默认会继承pipeline顶层的agent配置,如果要在post里单独跑在主节点,需要用script块包裹节点分配逻辑:
post { changed { script { hasChanged = true } } success { script { if(hasChanged){ echo '************** First success in a while! ' } // 分配节点到主节点执行状态更新 node('built-in') { withCredentials([usernamePassword(credentialsId: '00000000-0000-0000-0000-000000000000', usernameVariable: 'clientID', passwordVariable: 'clientSecret')]) { sh """ docker run --rm utrecht/bcbsn:2.0.4 \ -clientID ${clientID} \ -clientSecret ${clientSecret} \ -state SUCCESSFUL \ -commit ${env.GIT_COMMIT} \ -name ${buildName} \ -repoSlug talentindicator \ -owner talentinsights \ -url ${env.JOB_URL} \ -key ${buildName} """ } } } } failure { script { if(hasChanged){ echo '************** Build broke ' } echo "Setting build status in Bitbucket" node('built-in') { withCredentials([usernamePassword(credentialsId: '00000000-0000-0000-0000-000000000000', usernameVariable: 'clientID', passwordVariable: 'clientSecret')]) { sh """ docker run --rm utrecht/bcbsn:2.0.4 \ -clientID ${clientID} \ -clientSecret ${clientSecret} \ -state FAILED \ -commit ${env.GIT_COMMIT} \ -name ${buildName} \ -repoSlug talentindicator \ -owner talentinsights \ -url ${env.JOB_URL} \ -key ${buildName} """ } } } } }
注意:如果你的Jenkins主节点默认禁止在master上跑任务,需要到节点配置页把主节点的执行器数量设为大于0的值,同时可以通过标签限制只允许跑这类轻量状态更新任务,避免资源抢占。
方案2:通过TCP端口连接Docker宿主机执行
如果你不想在主节点装Docker,要直接把容器调度到Docker从节点对应的宿主机上执行,只要把你之前的代码包裹在script块内,同时修正withRun的参数位置即可。
注意事项
- 确保Docker宿主机的2375/4243端口开放了访问,且建议配置TLS证书验证,避免未授权访问风险
- 你之前写的credentialsId末尾多了冗余空格,记得删掉,否则会读不到凭证
修正后的代码示例
stages { stage ('Staging'){ steps { script { // 所有docker全局变量方法必须放在script块内 withCredentials([usernamePassword(credentialsId: '6eab2fcc-7703-4d92-9e7d-120d66f771b3', usernameVariable: 'clientID', passwordVariable: 'clientSecret')]) { docker.withServer('tcp://192.168.0.247:4243') { // 注意:第一个参数是docker运行配置,第二个才是容器启动命令 docker.image('utrecht/bcbsn:2.0.4').withRun("--rm", "-clientID ${clientID} -clientSecret ${clientSecret} -state INPROGRESS -commit ${env.GIT_COMMIT} -name ${buildName} -repoSlug talentindicator -owner talentinsights -url ${env.JOB_URL} -key ${buildName}") { // 等待容器执行完成即可,不需要额外操作 } } } } } } }
post块里的逻辑同理,把远程Docker调用的部分包裹在script块内,修正参数位置即可。
内容的提问来源于stack exchange,提问作者JoSSte
相关产品推荐
相关产品推荐

