
Ce guide déploie AWX sur un cluster Kubernetes que vous possédez déjà, avec un operator et kustomize, en trois manifestes. Vous obtiendrez une instance servie par un ingress, avec un mot de passe administrateur que vous maîtrisez et toutes les images épinglées, y compris les deux que l'operator laisse flotter en latest sans le dire.
Le déploiement est validé sur k3s 1.31, avec kubectl 1.36. Il ne demande ni Helm, ni git clone, ni script d'installation. Public visé : administrateurs Kubernetes à l'aise avec kubectl et les ressources personnalisées.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Distinguer l'operator, ses CRD et l'objet
AWXque vous déclarez - Installer l'operator par
kustomize, sans cloner de dépôt - Déclarer une instance avec un mot de passe administrateur maîtrisé
- Exposer l'interface par un ingress, et comprendre le 404 qui déroute
- Épingler les images, y compris celles que l'operator laisse en
latest - Diagnostiquer les pièges rencontrés en lab
Prérequis
Section intitulée « Prérequis »- Un cluster Kubernetes existant, avec un
kubectladministrateur. Le lab de référence est un k3s mono-nœud, en version 1.31. - Un ingress controller installé. k3s embarque Traefik par défaut.
- Une classe de stockage par défaut, pour le volume de PostgreSQL.
kubectl get scdoit afficher une ligne marquée(default). kubectl1.14 ou plus récent :kustomizey est intégré, l'option-ksuffit.- Environ 4 Go de mémoire disponibles sur le nœud, et de quoi tirer 2 Go d'images.
Installeur ou operator : ce n'est pas le même choix qu'on croit
Section intitulée « Installeur ou operator : ce n'est pas le même choix qu'on croit »Le hub documente deux chemins d'installation, et beaucoup les opposent à tort. L'installeur officiel n'est pas une alternative à l'operator : c'est un enrobage autour de lui. Son fichier de configuration porte une ligne ASCENDER_OPERATOR_VERSION: 2.19.6, et la plateforme qu'il produit fait tourner exactement le contrôleur que vous allez déployer ici. Dans les deux cas, c'est l'operator qui fabrique PostgreSQL, le pod web et le pod task.
La vraie différence tient à ce que chacun fait à votre place, et à ce qu'il vous prend en échange.
L'installeur part d'une machine virtuelle nue. Il installe ansible-core, monte k3s, déploie l'operator, écrit l'objet AWX, crée le secret TLS depuis votre certificat, inscrit les noms d'hôte dans /etc/hosts et installe Ledger. Un fichier de configuration, une commande, vingt minutes. En contrepartie, il décide pour vous : la distribution Kubernetes, la version des images, la topologie. Et il ne s'intègre à aucune chaîne GitOps.
L'operator par kustomize suppose un cluster déjà là et ne touche à rien d'autre. Vous écrivez trois manifestes, donc vous choisissez tout : la classe de stockage, l'ingress, le digest de chaque image, le namespace. Ces manifestes se versionnent et se rejouent. Personne, en revanche, ne vous monte le cluster, ne génère le certificat, ni ne déploie Ledger.
Installeur setup.sh | Operator et kustomize | |
|---|---|---|
| Cluster | il le monte (k3s) | il faut le fournir |
| Operator déployé | ascender-operator | ascender-operator |
| Versions des images | imposées, EE en latest | les vôtres, jusqu'au digest |
| TLS | secret généré depuis votre certificat | à câbler soi-même |
| Ledger | installé par défaut | absent |
| Reproductible en GitOps | non | oui |
| Ce qu'il vous demande | un fichier de configuration | trois manifestes |
Le critère de décision tient en une question : avez-vous déjà un cluster Kubernetes ? Si oui, l'installeur ne peut rien pour vous, il en monterait un second à côté. Sinon, il vous économise une demi-journée, et Installer Ascender sur k3s est la page à lire.
Ce qu'un operator fait à votre place
Section intitulée « Ce qu'un operator fait à votre place »Un operator est un contrôleur qui tourne dans le cluster et réconcilie en boucle un objet que vous déclarez avec la réalité observée. Vous n'écrivez pas les Deployment, les Service, le StatefulSet de PostgreSQL ni l'Ingress : vous décrivez une instance AWX, et l'operator fabrique puis maintient la quinzaine de ressources qui la composent.
Ce modèle est expliqué en profondeur dans Les opérateurs Kubernetes et, côté manipulation, dans Operators et CRDs pour développeurs.
Concrètement, l'installation se fait en deux temps. On installe d'abord l'operator et ses CRD, qui enseignent au cluster ce qu'est un objet AWX. On déclare ensuite une instance, et l'operator la construit.
Étape 1 - Écrire la kustomization
Section intitulée « Étape 1 - Écrire la kustomization »kustomize sait assembler des manifestes distants et surcharger ce qu'ils contiennent. Il est intégré à kubectl depuis la version 1.14. Sa logique de bases et de surcharges est détaillée dans Kustomize : factorisez vos manifests Kubernetes.
Créez un répertoire de travail, et un unique fichier kustomization.yaml :
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomization
namespace: ascender
resources: - github.com/ctrliq/ascender-operator/config/default?ref=2.19.6
images: - name: ghcr.io/ctrliq/ascender-operator newTag: 2.19.6Trois lignes portent tout le sens. La référence ?ref=2.19.6 épingle la version des manifestes : sans elle, kustomize suivrait la branche de développement et votre installation ne serait pas reproductible. La section images force le tag du contrôleur, qui vaut latest dans le dépôt. Le namespace place l'ensemble dans un espace dédié, créé au passage.
Vérifiez le rendu avant d'appliquer quoi que ce soit. C'est le réflexe qui distingue une installation d'une incantation :
kubectl kustomize . | grep "image: ghcr.io" image: ghcr.io/ctrliq/ascender-operator:2.19.6Étape 2 - Installer l'operator
Section intitulée « Étape 2 - Installer l'operator »kubectl apply -k .La commande crée le namespace, les CRD, les rôles RBAC du contrôleur et son déploiement. Attendez qu'il soit disponible :
kubectl -n ascender rollout status deploy/awx-operator-controller-manager --timeout=240sdeployment "awx-operator-controller-manager" successfully rolled outLe cluster connaît désormais quatre nouvelles ressources. Vous pouvez les interroger comme n'importe quelle ressource native, avec kubectl api-resources :
kubectl api-resources --api-group=awx.ansible.comNAME APIVERSION NAMESPACED KINDawxbackups awx.ansible.com/v1beta1 true AWXBackupawxmeshingresses awx.ansible.com/v1alpha1 true AWXMeshIngressawxrestores awx.ansible.com/v1beta1 true AWXRestoreawxs awx.ansible.com/v1beta1 true AWXTrois enseignements tiennent dans ce tableau. Le kind reste AWX et le groupe reste awx.ansible.com, y compris pour Ascender : le fork n'a renommé ni l'API ni les objets, ce qui garantit la compatibilité des manifestes entre les deux. Les CRD AWXBackup et AWXRestore apportent la sauvegarde et la restauration déclaratives. Enfin AWXMeshIngress est bien installée, alors que la documentation officielle n'en annonce que trois.
Étape 3 - Maîtriser le mot de passe administrateur
Section intitulée « Étape 3 - Maîtriser le mot de passe administrateur »Si vous ne faites rien, l'operator génère un mot de passe et le range dans un secret nommé <nom-de-l-instance>-admin-password. C'est pratique pour une maquette, et intenable dès qu'une chaîne d'intégration doit rejouer le déploiement.
Créez le secret avant l'instance, et il sera utilisé tel quel :
apiVersion: v1kind: Secretmetadata: name: ascender-admin-password namespace: ascenderstringData: password: RemplacezCeMotDePassekubectl apply -f admin-password.yamlÉtape 4 - Déclarer l'instance
Section intitulée « Étape 4 - Déclarer l'instance »Voici le manifeste complet, toutes images épinglées. Chaque champ est justifié juste après.
apiVersion: awx.ansible.com/v1beta1kind: AWXmetadata: name: ascender namespace: ascenderspec: # Application : sans image_version, l'operator deploie latest image: ghcr.io/ctrliq/ascender image_version: "25.4.0"
# Environnement d'execution : aucun tag de version n'est publie, # l'epinglage passe obligatoirement par le digest control_plane_ee_image: ghcr.io/ctrliq/ascender-ee@sha256:bbe950a24e647ab9b3cead24b92bba8680ebec56a26a80a649420674a9e9ae22
# Base de donnees : les DEUX champs sont necessaires postgres_image: quay.io/sclorg/postgresql-15-c9s postgres_image_version: "20260708"
admin_user: admin admin_password_secret: ascender-admin-password
service_type: ClusterIP ingress_type: ingress ingress_hosts: - hostname: ascender.k3d.local
postgres_storage_requirements: requests: storage: 4Gi projects_persistence: falsekubectl apply -f ascender.yamlL'operator lance alors PostgreSQL, exécute un job de migration du schéma, puis démarre les deux déploiements web et task. Comptez cinq à dix minutes, presque entièrement consacrées au téléchargement des images.
kubectl -n ascender get podsNAME READY STATUS RESTARTS AGEascender-migration-25.4.0-m69qq 0/1 Completed 0 6mascender-postgres-15-0 1/1 Running 0 7m26sascender-task-6c487975fd-n2rpm 4/4 Running 0 6m48sascender-web-b6f796d7f-f9sh5 3/3 Running 0 6m48sawx-operator-controller-manager-8789847df-wkzw4 2/2 Running 0 9m24sLe pod migration, à l'état Completed, est normal : c'est un Job, pas un service. Il ne se relance qu'aux montées de version.
Pourquoi ces champs, et pas d'autres
Section intitulée « Pourquoi ces champs, et pas d'autres »ingress_type: ingress vaut none par défaut : sans lui, aucun ingress n'est créé et l'interface reste inaccessible depuis l'extérieur du cluster. ingress_hosts remplace le champ hostname, marqué obsolète, et accepte une liste d'hôtes avec leur secret TLS.
service_type: ClusterIP est correct dès lors qu'un ingress expose le service. Un LoadBalancer ferait double emploi et consommerait une adresse.
projects_persistence: false convient tant que vos playbooks viennent de dépôts Git, ce qui est le cas normal. Passez-le à true seulement si vous devez écrire dans /var/lib/projects : le volume réclame alors un accès ReadWriteMany, que peu de classes de stockage fournissent.
postgres_storage_requirements demande 8 Go par défaut. La valeur ne se réduit pas après coup sans recréer le volume.
Étape 5 - Le piège des images en latest
Section intitulée « Étape 5 - Le piège des images en latest »C'est le point que la documentation passe sous silence, et il mérite une section entière. Épingler image_version ne fige que l'application. Deux autres images continuent de flotter.
Vérifiez ce que votre cluster fait réellement tourner, plutôt que ce que vous croyez avoir demandé :
kubectl -n ascender get pods \ -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' \ | sort -uSans les trois champs d'image du manifeste précédent, la sortie contient :
ghcr.io/ctrliq/ascender:25.4.0ghcr.io/ctrliq/ascender-ee:latestquay.io/sclorg/postgresql-15-c9s:latestDeux latest dans un déploiement censé être reproductible. Deux images qui peuvent changer sous vos pieds au prochain redémarrage d'un pod.
L'environnement d'exécution n'a aucun tag de version
Section intitulée « L'environnement d'exécution n'a aucun tag de version »L'image ghcr.io/ctrliq/ascender-ee, celle qui exécute réellement vos playbooks, ne publie qu'un seul tag, latest. Le registre est formel :
TOKEN=$(curl -s "https://ghcr.io/token?scope=repository:ctrliq/ascender-ee:pull&service=ghcr.io" | jq -r .token)curl -s -H "Authorization: Bearer $TOKEN" https://ghcr.io/v2/ctrliq/ascender-ee/tags/list | jq .tags["latest"]Il n'existe donc aucune version à épingler. La seule prise possible est le digest, l'empreinte du contenu de l'image, que le registre renvoie volontiers :
curl -sI -H "Authorization: Bearer $TOKEN" \ https://ghcr.io/v2/ctrliq/ascender-ee/manifests/latest | grep -i docker-content-digestdocker-content-digest: sha256:bbe950a24e647ab9b3cead24b92bba8680ebec56a26a80a649420674a9e9ae22Ce digest se place dans control_plane_ee_image, avec un @ au lieu du :. L'operator l'accepte et le propage aux pods. Un digest désigne un contenu, jamais un alias : il ne peut pas changer de sens.
PostgreSQL ignore la version fournie seule
Section intitulée « PostgreSQL ignore la version fournie seule »Le champ postgres_image_version est ignoré s'il est fourni sans postgres_image. Le comportement est silencieux : le StatefulSet n'est pas modifié, aucun avertissement n'apparaît, et vous continuez de tourner sur latest en croyant l'avoir figé.
Renseignez les deux. Le StatefulSet est alors mis à jour, et le pod redémarre sur l'image demandée :
kubectl -n ascender get statefulset ascender-postgres-15 \ -o jsonpath='{.spec.template.spec.containers[0].image}'quay.io/sclorg/postgresql-15-c9s:20260708Avec les trois champs renseignés, plus aucun latest ne subsiste :
ghcr.io/ctrliq/ascender:25.4.0ghcr.io/ctrliq/ascender-ee@sha256:bbe950a24e647ab9b3cead24b92bba8680ebec56a26a80a649420674a9e9ae22ghcr.io/ctrliq/ascender-operator:2.19.6quay.io/sclorg/postgresql-15-c9s:20260708Étape 6 - Accéder à l'interface
Section intitulée « Étape 6 - Accéder à l'interface »L'operator a créé un ingress. Regardez-le, il explique le comportement qui suit :
kubectl -n ascender get ingressNAME CLASS HOSTS ADDRESS PORTSascender-ingress traefik ascender.k3d.local 10.43.0.1 80La preuve tient en deux commandes. La première, sans nom d'hôte, échoue ; la seconde réussit :
curl -s -o /dev/null -w "%{http_code}\n" http://localhost/api/v2/ping/curl -s -H "Host: ascender.k3d.local" http://localhost/api/v2/ping/404{"ha":true,"version":"25.4.0","active_node":"ascender-web-5cd46dfc59-xq2pz",...}Pour un navigateur, il faut donc que le nom se résolve. En attendant une entrée DNS, déclarez-le sur votre poste.
echo "127.0.0.1 ascender.k3d.local" | sudo tee -a /etc/hostsLe fichier est C:\Windows\System32\drivers\etc\hosts, sans extension. Ouvrez le Bloc-notes en tant qu'administrateur, puis ouvrez ce fichier depuis lui, et ajoutez la ligne 127.0.0.1 ascender.k3d.local. Videz ensuite le cache de résolution avec ipconfig /flushdns.
Récupérez enfin le mot de passe. Même quand vous l'avez fourni, le secret reste la source de vérité :
kubectl -n ascender get secret ascender-admin-password \ -o jsonpath="{.data.password}" | base64 -d ; echoOuvrez http://ascender.k3d.local/ et connectez-vous avec admin. Votre première action doit être de changer ce mot de passe et de créer un compte nominatif : la marche à suivre est dans Les premières actions d'administration.
Sécurité
Section intitulée « Sécurité »Servez l'interface en HTTPS. Le manifeste ci-dessus expose l'ingress en clair, ce qui convient à un lab et jamais à autre chose : le mot de passe et les jetons d'API transitent dans la requête. Ajoutez un secret TLS à l'hôte, et laissez cert-manager le remplir :
ingress_hosts: - hostname: ascender.example.com tls_secret: ascender-tlsNe versionnez jamais le secret du mot de passe en clair. Le manifeste de l'étape 3 est un exemple de lab.
Épinglez les images, ce qui relève autant de la reproductibilité que de la sécurité : une image latest reconstruite peut introduire une régression ou une dépendance vulnérable sans qu'aucun manifeste ne change.
Restreignez qui peut créer un objet AWX. Le pod task exécute des playbooks : ce droit équivaut à une exécution de code arbitraire depuis le cluster. L'operator lui-même réclame des droits étendus pour fabriquer ses ressources.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause | Correctif |
|---|---|---|
kubectl apply -k . installe l'operator d'Ansible | le kustomization.yaml de la doc du fork n'est pas rebadgé | viser github.com/ctrliq/ascender-operator/config/default?ref=2.19.6 |
Le contrôleur tourne en latest | pas de section images dans la kustomization | ajouter newTag: 2.19.6 |
ascender-ee:latest malgré image_version | image_version ne fige que l'application | control_plane_ee_image avec un digest |
postgres_image_version sans effet | le champ est ignoré s'il est seul | renseigner aussi postgres_image |
| L'adresse IP du cluster répond 404 | l'ingress route sur l'en-tête Host | résoudre le nom déclaré dans ingress_hosts |
| Aucun ingress n'est créé | ingress_type vaut none par défaut | ingress_type: ingress |
Le pod migration reste Completed | c'est un Job, pas un service | comportement normal |
Le pod postgres reste Pending | pas de classe de stockage par défaut | kubectl get sc, puis postgres_storage_class |
Un kubectl patch sur l'ingress disparaît | l'operator réconcilie ses ressources | passer par ingress_annotations sur l'objet AWX |
À retenir
Section intitulée « À retenir »- L'operator d'Ansible est figé à la version 2.19.1 du 2 juillet 2024 ; le fork
ascender-operator2.19.6 est maintenu et publié régulièrement. - Les deux partagent le même groupe d'API
awx.ansible.comet le mêmekind: AWX: les manifestes sont interchangeables. - L'installation se fait en deux temps : l'operator et ses CRD, puis l'objet
AWXqui décrit l'instance. - La documentation du fork n'est pas rebadgée : son
kustomization.yamld'exemple installerait l'operator d'Ansible. ?ref=<tag>et la sectionimagessont ce qui rend l'installation reproductible.- Épingler
image_versionne suffit pas. L'environnement d'exécution et PostgreSQL restent enlatest. - L'image
ascender-eene publie aucun tag de version : seul le digest permet de l'épingler. postgres_image_versionest ignoré sanspostgres_image, silencieusement.ingress_typevautnonepar défaut : sans lui, rien n'est exposé.- Un ingress route sur l'en-tête
Host: l'adresse IP nue renvoie un 404 trompeur. - Toute modification directe d'une ressource gérée est écrasée à la réconciliation : passer par l'objet
AWX.