Lorsqu’un orchestrateur CI enchaîne sur un Mac cloud la vérification des versions, la récupération du code, le lancement du build et la collecte des artefacts, ouvrir une nouvelle connexion SSH pour chaque commande répète la négociation de clé publique, l’authentification et l’initialisation de session. La latence d’une connexion isolée peut sembler négligeable, mais sur plusieurs dizaines de commandes courtes, les négociations finissent par représenter une part importante du temps total. La fonctionnalité ControlMaster intégrée à SSH permet aux commandes suivantes de réutiliser une connexion TCP déjà authentifiée, à condition de gérer également l’isolation des sockets, la détection des connexions invalides et le nettoyage en fin de tâche.
Identifier d’abord ce qu’il faut optimiser
La réutilisation des connexions convient aux scénarios dans lesquels « une même tâche CI accède plusieurs fois au même Mac en quelques minutes ». Si la tâche ne lance qu’un seul build de longue durée, réduire le coût de la négociation apporte peu de bénéfices. En revanche, si l’orchestrateur exécute successivement plus d’une dizaine de commandes courtes, la réutilisation devient généralement pertinente.
Commencez par observer le déroulement d’une connexion sans réutilisation :
time ssh -o BatchMode=yes build-mac 'sw_vers -productVersion'
ssh -vv -o BatchMode=yes build-mac 'true' 2>ssh-debug.log
Vérifiez dans le journal de débogage si les étapes d’échange de clés et d’authentification se répètent. Ne vous fiez pas au temps accidentellement mesuré sur une seule commande : effectuez plusieurs exécutions consécutives et distinguez le temps consacré à « l’établissement de la connexion » de celui nécessaire à « l’exécution de la commande distante ».
ControlMaster réduit le coût d’établissement des connexions. Il n’accélère ni la compilation Xcode, ni la résolution des dépendances, ni le transfert de fichiers.
Configurer une réutilisation minimale avec ControlMaster
Dans le fichier ~/.ssh/config de l’orchestrateur CI, créez un alias réservé à la machine de build. %C produit un hachage des paramètres de connexion, ce qui évite l’échec de création du chemin Unix Socket lorsque le nom d’utilisateur ou le nom d’hôte est trop long.
Host build-mac
HostName mac.example.internal
User ci-runner
BatchMode yes
ControlMaster auto
ControlPersist 10m
ControlPath ~/.ssh/control/%C
ConnectTimeout 15
ServerAliveInterval 30
ServerAliveCountMax 3
StrictHostKeyChecking yes
UserKnownHostsFile ~/.ssh/known_hosts_ci
Lors de la création du répertoire, limitez impérativement ses permissions :
install -d -m 700 "$HOME/.ssh/control"
chmod 600 "$HOME/.ssh/known_hosts_ci"
ssh build-mac 'printf "%s\n" ready'
ssh -O check build-mac
La première commande crée la connexion maître. ssh -O check doit ensuite renvoyer l’état du processus maître. ControlPersist 10m signifie que la connexion maître peut rester active pendant dix minutes au maximum après la fermeture de la dernière session. Cela ne signifie pas qu’une tâche peut compter sur elle indéfiniment.
Épingler l’identité de l’hôte
Ne définissez pas StrictHostKeyChecking=no par simple commodité. Récupérez la clé publique de l’hôte par un canal contrôlé, inscrivez-la dans le fichier known_hosts propre au CI et appliquez une procédure de mise à jour explicite lors de la reconstruction d’un nœud ou de la rotation des clés. La réutilisation d’une connexion réduit uniquement le nombre de négociations ultérieures ; elle ne modifie pas la frontière de confiance de la première connexion.
Isoler les sockets des tâches parallèles
Sur un Runner partagé, l’erreur la plus fréquente consiste à laisser toutes les tâches utiliser ~/.ssh/control/%C. Si deux pipelines se connectent simultanément au même hôte, chacun peut réutiliser la connexion maître créée par l’autre. La commande de fermeture lancée par l’une des tâches interrompra alors également l’autre.
Une approche plus robuste consiste à créer un répertoire propre à chaque tâche, puis à remplacer ControlPath depuis la ligne de commande :
set -euo pipefail
job_id="${CI_JOB_ID:-local-$$}"
control_dir="${TMPDIR:-/tmp}/ssh-control-${job_id}"
install -d -m 700 "$control_dir"
ssh_opts=(
-o ControlMaster=auto
-o ControlPersist=10m
-o "ControlPath=${control_dir}/%C"
-o BatchMode=yes
)
ssh "${ssh_opts[@]}" build-mac 'xcodebuild -version'
ssh "${ssh_opts[@]}" build-mac 'git --version'
L’identifiant de tâche doit provenir d’un contexte de pipeline fiable et rester une chaîne courte. N’insérez pas directement un nom de branche dans le chemin : les barres obliques, les espaces et les noms excessivement longs peuvent empêcher la création du socket.
Détecter une connexion invalide et la recréer en toute sécurité
Après un changement de réseau, un redémarrage de la machine distante ou l’arrêt anormal du processus de contrôle, le fichier de socket peut subsister alors que la connexion correspondante n’est plus utilisable. Vérifiez son état avant d’exécuter une commande métier. Si le contrôle échoue, supprimez uniquement le répertoire de sockets de la tâche en cours, puis rétablissez la connexion.
if ! ssh "${ssh_opts[@]}" -O check build-mac >/dev/null 2>&1; then
rm -rf "$control_dir"
install -d -m 700 "$control_dir"
ssh "${ssh_opts[@]}" build-mac 'true'
fi
Seul le répertoire créé par la tâche en cours doit être supprimé. N’utilisez pas rm -rf ~/.ssh/control/* pour nettoyer un répertoire partagé. Évitez également de relancer sans condition une commande de build : lorsque la connexion est interrompue, la commande distante peut déjà avoir démarré. La méthode sûre consiste à ne réessayer automatiquement que les contrôles idempotents, puis à vérifier l’état de la tâche initiale au moyen du processus, d’un fichier de verrouillage ou du numéro de build.
Surveiller la limite de sessions côté serveur
Une connexion maître peut transporter plusieurs sessions logiques, mais leur nombre n’est pas illimité. Si trop de commandes sont exécutées en parallèle, le serveur peut refuser l’ouverture d’un nouveau channel. Commencez par limiter la concurrence SSH au sein de chaque tâche, puis déterminez s’il faut adapter la politique du serveur. L’augmentation de la limite de sessions ne doit pas être considérée comme la correction par défaut.
Garantir le nettoyage lors de la sortie
À la fin d’une exécution normale, fermez la connexion maître à l’aide de la commande de contrôle. En cas de sortie anormale, le répertoire de la tâche doit tout de même être supprimé. Dans un script Shell, trap permet de centraliser ce traitement :
cleanup() {
ssh "${ssh_opts[@]}" -O exit build-mac >/dev/null 2>&1 || true
rm -rf "$control_dir"
}
trap cleanup EXIT INT TERM
La validation finale doit couvrir quatre points : les commandes successives utilisent bien la même connexion maître ; deux tâches parallèles emploient des répertoires différents ; la connexion peut être recréée après le redémarrage de la machine distante ; aucun socket ne subsiste après l’annulation d’une tâche. Les automatisations exécutées sur SetMini doivent respecter les mêmes principes : après avoir vérifié dans la console les configurations actuellement disponibles, considérez la couche de connexion comme une infrastructure observable, reconstructible et nettoyable, et non comme un état caché supposé rester valide indéfiniment.
Questions fréquentes
Faut-il conserver ControlPersist le plus longtemps possible ?
Non. Une durée de 5 à 15 minutes suffit généralement pour les commandes successives d’une tâche CI. Une durée excessive favorise les sockets obsolètes et les réutilisations entre tâches.
Plusieurs tâches parallèles peuvent-elles partager le même ControlPath ?
Ce n’est pas recommandé. Créez un répertoire de sockets distinct, avec des droits 700, pour chaque tâche puis exécutez ssh -O exit avant de le supprimer.
ControlMaster désactive-t-il la vérification de la clé d’hôte ?
Non. La connexion maître initiale doit toujours utiliser StrictHostKeyChecking=yes et un fichier known_hosts contrôlé contenant l’identité attendue du Mac distant.
Intégrez vos étapes de développement dans un Mac cloud accessible à tout moment
Choisissez parmi deux configurations Apple Silicon et quatre centres de données. Vos ressources restent dédiées, sans partage avec d’autres clients. La disponibilité réelle est indiquée en temps réel dans la console.