Estoy tratando de ejecutar un comando SSH desde dentro de un contenedor Docker en una canalización de Jenkins. Estoy usando el complemento CloudBees Docker Pipeline para hacer girar el contenedor y ejecutar comandos, y el complemento SSH Agent para administrar mis claves SSH. Aquí hay una versión básica de mi Jenkinsfile:
node { step([$class: 'WsCleanup']) docker.image('node').inside { stage('SSH') { sshagent (credentials: [ 'MY_KEY_UUID' ]) { sh "ssh -vvv -o StrictHostKeyChecking=no ubuntu@example.org uname -a" } } } }Cuando se ejecuta el comando SSH, aparece este error:
+ ssh -vvv -o StrictHostKeyChecking=no ubuntu@example.org uname -a No user exists for uid 1005Revisé los registros y me di cuenta de que Docker Pipeline Plugin le dice automáticamente al contenedor que se ejecute con el mismo usuario que inició sesión en el host al pasar un UID como argumento de línea de comando:
$ docker run -t -d -u 1005:1005 [...] Decidí verificar qué usuarios existían en el host y el contenedor ejecutando cat /etc/passwd en cada entorno. Efectivamente, la lista de usuarios era diferente en cada uno. 1005 era el usuario de jenkins en la máquina host, pero ese UID no existía en el contenedor. Para resolver el problema, monté /etc/passwd desde el host al contenedor al girarlo:
node { step([$class: 'WsCleanup']) docker.image('node').inside('-v /etc/passwd:/etc/passwd') { stage('SSH') { sshagent (credentials: [ 'MY_KEY_UUID' ]) { sh "ssh -vvv -o StrictHostKeyChecking=no ubuntu@example.org uname -a" } } } }La solución provista por @nathan-thompson es asombrosa, pero en mi caso no pude encontrar al usuario ni siquiera en /etc/passwd de la máquina host. Significa que montar el archivo passwd no solucionó el problema. Esta pregunta https://superuser.com/questions/580148/users-not-found-in-etc-passwd sugirió que algunos usuarios iniciaron sesión en el host utilizando un proveedor de identidad como LDAP.
La solución fue encontrar una manera de agregar la línea adecuada al archivo passwd en el contenedor. Llamar getent passwd $USER en el host proporcionará la línea passwd para el usuario de Jenkins que ejecuta el contenedor.
Agregué un paso que se ejecuta en el nodo (y no en el agente acoplable) para obtener la línea y guardarla en un archivo. Luego, en el siguiente paso, monté la passwd generada en el contenedor:
stages { stage('Create passwd') { steps { sh """echo \$(getent passwd \$USER) > /tmp/tmp_passwd """ } } stage('Test') { agent { docker { image '*******' args '***** -v /tmp/tmp_passwd:/etc/passwd' reuseNode true registryUrl '*****' registryCredentialsId '*****' } } steps { sh """ssh -i ******** """ } } }Acabo de encontrar otra solución a este problema, que quiero compartir. Se diferencia de las soluciones existentes en que permite ejecutar el pipeline completo en un solo agente, en lugar de por etapa.
El truco es, en lugar de usar directamente una imagen, hacer referencia a un Dockerfile (que puede compilarse FROM el original) y luego agregar el usuario:
# Dockerfile FROM node ARG jenkinsUserId= RUN if ! id $jenkinsUserId; then \ usermod -u ${jenkinsUserId} jenkins; \ groupmod -g ${nodeId} jenkins; \ fi // Jenkinsfile pipeline { agent { dockerfile { additionalBuildArgs "--build-arg jenkinsUserId=\$(id -u jenkins)" } } }