Aller au contenu principal

Exécuteur Kubernetes Enterprise

Avec l'exécuteur Kubernetes, un runner crée un Pod par tâche dans un namespace, diffuse son journal dans Semaphore et supprime le Pod à la fin de la tâche. Le runner peut s'exécuter dans le cluster (recommandé) ou en dehors avec un kubeconfig.

Le déroulement d'une tâche en conteneur, le contenu du conteneur et ce qui n'est pas pris en charge sont décrits dans Exécuteurs. Cette page couvre la configuration Kubernetes.

Avant de commencer​

  • Un serveur Semaphore avec une licence Enterprise.
  • Un runner déployé dans le cluster, voir Installer un runner sur Kubernetes. Les étapes ci-dessous utilisent son namespace semaphore et son Deployment semaphore-runner. Pour un runner hors du cluster, voir Runner en dehors du cluster.
  • kubectl avec les droits de créer des comptes de service et des objets RBAC dans ce namespace.
  • Le cluster peut récupérer semaphoreui/job et semaphoreui/helper (ou vos propres images).

Ce que crée le runner​

Pour une tâche d'ID 42, le runner crée dans son namespace :

ObjetNomContenu
Podsemaphore-job-42-<random>Conteneur d'initialisation git-clone (image helper) et conteneur build (image de build), restartPolicy: Never.
Secret<pod>-ssh-<key id>Un par clé SSH, monté dans /secrets/ssh/<key id>/.
Secret<pod>-vaultMots de passe Ansible vault.
Secret<pod>-extra-varsvalues.json avec les variables supplémentaires.
Secret<pod>-envSecrets de groupe de variables de type Variable d'environnement et SEMAPHORE_JWT, injectés avec envFrom.
ConfigMap<pod>-inventoryUn inventaire statique.

Seuls les objets dont la tâche a besoin sont créés. Tous portent le label semaphoreui.com/runner=semaphore ; le Pod porte aussi semaphoreui.com/task-id=42. Le runner les supprime tous à la fin de la tâche, y compris après un échec ou un Stop.

Mise en place​

Étape 1. Créer les comptes de service​

Le runner a besoin d'un compte de service pour créer les Pods des tâches. Les Pods des tâches en reçoivent un second, sans permissions ni token d'API, afin qu'un playbook ne puisse pas utiliser les droits du runner.

semaphore-serviceaccounts.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: semaphore-runner
namespace: semaphore
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: semaphore-job
namespace: semaphore
automountServiceAccountToken: false

Étape 2. Accorder ses permissions au runner​

Voici la liste complète des appels d'API effectués par l'exécuteur :

semaphore-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: semaphore-runner
namespace: semaphore
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "get", "delete"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["create", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: semaphore-runner
namespace: semaphore
subjects:
- kind: ServiceAccount
name: semaphore-runner
namespace: semaphore
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: semaphore-runner

Un Role, pas un ClusterRole : le runner ne peut agir que dans son propre namespace.

Étape 3. Passer le runner à l'exécuteur Kubernetes​

Les Pods des tâches ne définissent pas de securityContext, donc un namespace qui impose le niveau Pod Security restricted les rejette. Autorisez baseline :

kubectl label namespace semaphore pod-security.kubernetes.io/enforce=baseline --overwrite

Donnez au Pod du runner son compte de service et les réglages de l'exécuteur. Dans le Deployment du runner, ajoutez à spec.template.spec :

semaphore-runner.yaml (fragment)
    spec:
serviceAccountName: semaphore-runner
containers:
- name: runner
env:
# ...SEMAPHORE_WEB_ROOT and SEMAPHORE_RUNNER_TOKEN as before
- name: SEMAPHORE_RUNNER_EXECUTOR_TYPE
value: k8s
- name: SEMAPHORE_RUNNER_K8S_NAMESPACE
value: semaphore
- name: SEMAPHORE_RUNNER_K8S_SERVICE_ACCOUNT
value: semaphore-job
- name: SEMAPHORE_RUNNER_K8S_IMAGE
value: semaphoreui/job:latest
kubectl apply -f semaphore-serviceaccounts.yaml -f semaphore-rbac.yaml -f semaphore-runner.yaml
kubectl -n semaphore rollout status deploy/semaphore-runner

Sans kubeconfig défini, le runner utilise les identifiants in-cluster de semaphore-runner. Les tâches ne s'exécutent plus dans le Pod du runner, vous pouvez donc réduire ses resources (par exemple 100m de CPU et 256Mi de mémoire).

Étape 4. Exécuter une tâche​

Dirigez un modèle vers le runner et lancez une tâche. Observez son Pod apparaître puis disparaître :

kubectl -n semaphore get pods -l semaphoreui.com/runner=semaphore -w

Le journal de la tâche affiche :

k8s: created pod semaphore/semaphore-job-42-a1b2c3 (1 ssh keys installed)
=== git-clone (init) ===
Cloning into '/workspace/repo'...
=== build (build) ===
semaphore: container executor: task 42, template 7
...
k8s: deleted pod semaphore/semaphore-job-42-a1b2c3

Exemples​

Runner en dehors du cluster​

Le runner s'exécute sur une VM ou dans Docker et atteint le serveur d'API avec un kubeconfig. Donnez au kubeconfig le Role de l'étape 2 et rien de plus :

{
"web_host": "https://semaphore.example.com",
"runner": {
"token_file": "/var/lib/semaphore/runner_token.txt",
"executor": {
"type": "k8s",
"k8s": {
"kubeconfig": "/etc/semaphore/kubeconfig",
"namespace": "semaphore",
"service_account": "semaphore-job"
}
}
}
}

Le contexte courant du kubeconfig est utilisé.

Images d'un registre privé​

Créez un secret de pull dans le namespace et indiquez-le dans pull_secrets ; plusieurs noms sont séparés par des virgules :

kubectl -n semaphore create secret docker-registry registry-example \
--docker-server=registry.example.com \
--docker-username=<user> --docker-password=<password>
            - name: SEMAPHORE_RUNNER_K8S_IMAGE
value: registry.example.com/semaphore/job-aws:1.0
- name: SEMAPHORE_RUNNER_K8S_PULL_SECRETS
value: registry-example

Les secrets sont attachés à chaque Pod de tâche, aussi bien pour l'image de build, l'image helper que pour l'Image de l'exécuteur d'un modèle.

CPU et mémoire des Pods de tâche​

L'exécuteur ne définit ni requests ni limits sur les Pods des tâches. Définissez des valeurs par défaut pour le namespace avec un LimitRange ; il s'applique aux conteneurs git-clone et build :

semaphore-limits.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: semaphore-task-defaults
namespace: semaphore
spec:
limits:
- type: Container
defaultRequest:
cpu: 250m
memory: 256Mi
default:
cpu: "2"
memory: 2Gi

Ajoutez un ResourceQuota pour plafonner ce que toutes les tâches parallèles du namespace peuvent utiliser.

Restreindre le réseau des Pods de tâche​

Les Pods des tâches portent le label semaphoreui.com/runner=semaphore, qu'une NetworkPolicy peut donc sélectionner. Celle-ci n'autorise que le DNS, le serveur Git et SSH vers les hôtes gérés :

semaphore-netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: semaphore-tasks
namespace: semaphore
spec:
podSelector:
matchLabels:
semaphoreui.com/runner: semaphore
policyTypes: ["Ingress", "Egress"]
egress:
- ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }
- to:
- ipBlock: { cidr: 10.20.0.0/16 } # Git server and managed hosts
ports:
- { protocol: TCP, port: 22 }
- { protocol: TCP, port: 443 }

Référence de configuration​

Toutes les options se trouvent sous runner.executor.k8s dans la configuration du runner.

OptionVariable d'environnementPar défautDescription
kubeconfigSEMAPHORE_RUNNER_K8S_KUBECONFIGin-clusterChemin vers un fichier kubeconfig. Vide : le compte de service du Pod du runner.
namespaceSEMAPHORE_RUNNER_K8S_NAMESPACEsemaphoreNamespace des Pods, Secrets et ConfigMaps des tâches. Doit exister.
imageSEMAPHORE_RUNNER_K8S_IMAGEsemaphoreui/job:latestImage du conteneur build. Le champ Image de l'exécuteur d'un modèle la remplace.
helper_imageSEMAPHORE_RUNNER_K8S_HELPER_IMAGEsemaphoreui/helper:latestImage du conteneur d'initialisation git-clone.
service_accountSEMAPHORE_RUNNER_K8S_SERVICE_ACCOUNTdefaultCompte de service des Pods des tâches. Utilisez-en un sans permissions, voir Étape 1.
pull_secretsSEMAPHORE_RUNNER_K8S_PULL_SECRETS—imagePullSecrets séparés par des virgules pour les Pods des tâches.
poll_interval_secondsSEMAPHORE_RUNNER_K8S_POLL_INTERVAL_SECONDS3Fréquence à laquelle le runner lit le statut du Pod. Des valeurs plus basses démarrent la diffusion du journal plus tôt et coûtent plus d'appels d'API.
cleanup_grace_secondsSEMAPHORE_RUNNER_K8S_CLEANUP_GRACE_SECONDS30Délai de grâce de la suppression du Pod après la fin ou l'arrêt de la tâche.

La politique de pull des images suit la valeur par défaut de Kubernetes : Always pour :latest, sinon IfNotPresent.

Sécurité​

  • Moindre privilège pour le runner. Le Role est limité au namespace et ne liste que ce que l'exécuteur appelle. Ne liez pas cluster-admin.
  • Pas d'accès à l'API pour les tâches. Avec la valeur par défaut service_account: default, le Pod de la tâche reçoit le token du compte par défaut du namespace. Utilisez un compte dédié avec automountServiceAccountToken: false.
  • Un namespace réservé à Semaphore. Le runner ne peut rien lire, mais il peut y créer et supprimer des Pods et des Secrets ; ne partagez pas le namespace avec d'autres charges de travail.
  • Les secrets sont des Secrets Kubernetes distincts par tâche, montés en lecture seule (les secrets d'environnement via envFrom, de sorte que leurs valeurs ne figurent pas dans la spec du Pod) et supprimés avec le Pod. Quiconque peut lire les Secrets du namespace peut les lire pendant l'exécution de la tâche ; activez le chiffrement au repos des Secrets.
  • Réseau. Limitez ce que les tâches atteignent avec une NetworkPolicy.

Arrêt et nettoyage​

Stop dans l'interface met fin à la diffusion du journal et supprime le Pod avec cleanup_grace_seconds, puis ses Secrets et ConfigMaps.

Si le runner est tué pendant une tâche, les objets de la tâche restent dans le namespace ; le runner ne les récupère pas au démarrage suivant. Supprimez-les lorsqu'aucune tâche ne s'exécute :

kubectl -n semaphore get pods,secrets,configmaps -l semaphoreui.com/runner=semaphore
kubectl -n semaphore delete pods,secrets,configmaps -l semaphoreui.com/runner=semaphore

Dépannage​

SymptômeCause et solution
Journal du runner : k8s executor is only available in the proprietary buildLe runner est une version Community. Utilisez semaphoreui/runner ou le binaire officiel.
Journal du runner : failed to initialise executor provider avec unable to load in-cluster configurationLe runner est hors d'un cluster et kubeconfig est vide, ou le chemin du kubeconfig est incorrect.
Tâche : create pod ...: pods is forbidden: User "system:serviceaccount:..." cannot create resourceLe RBAC manque ou est lié dans un autre namespace. Vérifiez avec kubectl auth can-i create pods -n semaphore --as=system:serviceaccount:semaphore:semaphore-runner.
Tâche : create pod ...: namespaces "semaphore" not foundCréez le namespace ; le runner ne le fait pas.
Tâche : create pod ...: violates PodSecurity "restricted"Ajoutez au namespace le label pod-security.kubernetes.io/enforce: baseline.
La tâche reste Running sans sortieLe Pod est bloqué en Pending : aucun nœud ne convient, l'image ne peut pas être récupérée (ImagePullBackOff), ou un quota le bloque. Le runner attend la fin du Pod. Vérifiez avec kubectl -n semaphore describe pod -l semaphoreui.com/task-id=<task id> et arrêtez la tâche.
Tâche : pod ... finished with status FailedLe clonage ou l'application a échoué. Le journal au-dessus montre la sortie de git-clone et build.
interpreter python3 not found in imageL'image de build ne contient pas le binaire de l'application ; définissez une autre image ou le champ Image de l'exécuteur du modèle.
No user exists for uid ... (OpenShift)Le /etc/passwd de l'image n'est pas modifiable par le groupe. Voir Construire une image personnalisée.