Przejdź do treści
Automatyzacja i DevOps·~25 min czytania

AWX na k3s: instalacja, pierwszy job i bezpieczna eksploatacja

Od dedykowanej maszyny z k3s do pierwszego joba przez SSH: wyjaśniamy role AWX, operatora i storage, a następnie sprawdzamy każdy etap wdrożenia.

Autor: Zespół LinuxLab

Przykład ma jeden cel: administrator uruchamia AWX na wydzielonej VM awx-lab.example.test, a potem z jego poziomu wykonuje niezmieniający niczego job SSH na hoście linux-test.example.test. Wszystkie nazwy, adresy i sekrety są przykładowe. Wariant jednowęzłowy nadaje się do labu oraz małego, świadomie utrzymywanego środowiska; nie daje wysokiej dostępności.

Czy AWX jest właściwy dla tego przypadku?

AWX jest serwisem, który uruchamia kod Ansible z interfejsu WWW i API, zapisuje historię zadań, przydziela role oraz przechowuje poświadczenia. Jest przydatny, gdy z tych samych playbooków korzysta zespół, gdy trzeba planować joby lub udowodnić, kto uruchomił zmianę. Dla jednego administratora, który uruchamia kilka lokalnych playbooków, prostsze może pozostać Ansible z Git i zwykłym harmonogramem.

AWX nie zastępuje Git, testów playbooków, kopii zapasowej ani poprawnej konfiguracji SSH. Nie zapewnia też wysokiej dostępności tylko dlatego, że działa w Kubernetesie. W tym artykule k3s jest lekką dystrybucją Kubernetesa na jednej VM, a operator jest programem Kubernetesa, który z opisu instancji AWX tworzy jej pody, usługę i bazę. Job zawsze działa w osobnym kontenerze, dlatego dostęp do Git i hostów docelowych musi mieć ten kontener, a nie tylko sam host z k3s.

Przykład jest celowo wąski: prywatna VM awx-lab.example.test uruchamia AWX, a job sprawdza przez SSH jeden host linux-test.example.test. Ten układ pozwala najpierw sprawdzić łańcuch uprawnień i sieci, bez dodawania produkcyjnej inventory. Poniższa tabela opisuje konkretny stan labu, z którego pochodzą wartości i wyniki; nie jest deklaracją zgodności każdej przyszłej wersji.

ElementStan zmierzony 10 września 2026
MaszynaUbuntu 24.04.4 LTS, jądro 6.8, 4 vCPU, 7,7 GiB RAM, dysk 38 GiB użytkowego, 26 GiB wolnego
Kubernetesjednowęzłowy k3s v1.34.3+k3s3, node w stanie Ready, runtime containerd 2.1.5
Storage i ingressdomyślna StorageClass local-path, IngressClass o nazwie traefik
AWXAWX Operator 2.19.1, AWX 24.6.1, baza z obrazu quay.io/sclorg/postgresql-15-c9s
Podyoperator, web, task i PostgreSQL w stanie Running; dysk bazy przydzielony i podłączony (stan Bound), 8 GiB, klasa local-path
API/api/v2/ping/ zwróciło HTTP 200 i wersję 24.6.1
Obraz proxyw podzie operatora działa quay.io/brancz/kube-rbac-proxy:v0.15.0, nie obraz z gcr.io
Manifest operatorapolecenie kubectl kustomize (Kustomize 5.7.1 wbudowany w kubectl 1.34.3) wygenerowało manifest zawierający oba oczekiwane obrazy
Manifest AWXpokazany niżej zasób przeszedł kubectl apply --dry-run=server wobec działającego CRD (od angielskiego Custom Resource Definition, definicja własnego typu zasobu - opisujemy to niżej) operatora 2.19.1
Ingress i TLSIngress klasy traefik z sekretem TLS: HTTPS zwróciło 200 i odpowiedź AWX; nieznany Host zwrócił 404 z Traefika
Wykonanie automatyzacjipojedyncze zadanie sprawdzające (moduł ping), wykonane na samej maszynie AWX bez połączenia po sieci, zakończyło się stanem successful po 85,0 s - w tym pierwsze pobranie obrazu środowiska wykonawczego

Równie ważne jest to, czego nie potwierdziliśmy, i co w tym artykule pozostaje zaleceniem, a nie zakończonym testem:

  • Wydanie certyfikatu Let's Encrypt. Jak wyżej: lab celowo stoi w sieci prywatnej, bez publicznego DNS i bez ruchu z Internetu do portów 80 i 443, więc weryfikacja HTTP-01 nie miałaby jak przejść - polega ona na tym, że urząd certyfikacji sam łączy się z Twoją domeną po zwykłym HTTP i pobiera z niej plik, którym dowodzisz, że domena jest Twoja. Sekcja o TLS pokazuje poprawne miejsce na sekret z certyfikatem, a nie zakończony test ACME - z angielskiego Automatic Certificate Management Environment, czyli protokołu, którym Certbot i cert-manager automatycznie wystawiają i odnawiają certyfikaty. Sam Ingress sprawdziliśmy certyfikatem wystawionym lokalnie.
  • Instalacja od zera jednym przebiegiem. Sprawdzana instancja już działała, więc kroki instalacyjne potwierdziliśmy tak, jak się je realnie potwierdza przed wdrożeniem: renderem manifestu operatora i serwerowym --dry-run zasobu AWX wobec żywego CRD.
  • Połączenie SSH ze środowiska wykonawczego do rzeczywistych hostów. Zadanie testowe wykonało się na tej samej maszynie, na której działa AWX, i to bez logowania się dokądkolwiek: Ansible uruchomił moduł bezpośrednio w kontenerze, w którym już był, zamiast łączyć się przez sieć. Dowodzi to, że AWX przyjął zadanie, utworzył dla niego kontener i wykonał w nim kod. Nie dowodzi natomiast, że zadziała Twój klucz SSH, Twoja sieć ani uprawnienia na docelowym serwerze - żaden z tych elementów nie był w tym teście w ogóle użyty.
  • Backup i odtworzenie zasobami AWXBackup/AWXRestore. Opisujemy procedurę i jej pułapki, ale w tym materiale nie pokazujemy przebiegu odtworzenia.

To jest materiał dla pojedynczej maszyny, nie przepis na wysoką dostępność. local-path w k3s przechowuje dane na dysku konkretnego węzła. Utrata tej maszyny nie jest scenariuszem, który rozwiąże sam Kubernetes: konieczny jest backup poza nią i regularnie testowane odtworzenie. Konfiguracja 4 vCPU i 8 GiB RAM jest punktem startu, a nie obietnicą pojemności - rzeczywiste zapotrzebowanie zależy od liczby równoległych zadań, rozmiaru środowisk wykonawczych, synchronizacji projektów i liczby hostów zbierających fakty.

Jakie pojęcia trzeba odróżnić przed instalacją?

Te nazwy opisują różne elementy tego samego przepływu. Ich rozdzielenie pomaga ustalić, czy problem leży w klastrze, samej aplikacji, dostępie do repozytorium czy w jobie.

  • Kubernetes uruchamia i nadzoruje kontenery jako pody, usługi oraz wolumeny. k3s jest jego lżejszą dystrybucją; w tym przykładzie działa na jednym hoście.
  • AWX to aplikacja z interfejsem WWW, API, kolejką zadań i bazą PostgreSQL. Jest projektem źródłowym dla automation controllera z Red Hat Ansible Automation Platform, ale ma własny społecznościowy cykl wydań.
  • AWX Operator to kontroler Kubernetesa. Czyta opis instancji AWX i utrzymuje potrzebne pody, usługę oraz bazę; jego wersja jest niezależna od wersji samego AWX.
  • CRD (Custom Resource Definition) dodaje do API Kubernetesa nowy typ zasobu, np. AWX. CR (Custom Resource) jest konkretnym obiektem tego typu. Plik awx.yaml z tego artykułu będzie CR-em opisującym jedną instancję AWX.
  • Pod - najmniejsza jednostka uruchamiana przez Kubernetes: jeden lub kilka kontenerów, które startują razem, dzielą adres sieciowy i są traktowane jak całość. Pody nie są trwałe - kasuje się je i tworzy od nowa, i to jest normalny tryb pracy, a nie awaria.
  • Namespace - przestrzeń nazw, czyli wydzielona część klastra z własnym zestawem obiektów. Jeden klaster może mieć osobną przestrzeń dla aplikacji A i osobną dla B; nazwy obiektów obowiązują w obrębie przestrzeni, więc w każdej z nich może istnieć usługa o tej samej nazwie i nie będą sobie przeszkadzać. Wszystko, co robimy w tym artykule, dzieje się w przestrzeni awx, stąd -n awx w prawie każdym poleceniu.
  • PVC - z angielskiego PersistentVolumeClaim, czyli zgłoszenie zapotrzebowania na trwałe miejsce na dysku. Pod sam w sobie nic nie zapamiętuje, więc baza PostgreSQL dostaje PVC i to na nim przeżywają dane, gdy pod zostanie odtworzony.
  • Inventory jest listą hostów, ich grup i zmiennych. Credential jest odrębnym obiektem z danymi dostępowymi, np. prywatnym kluczem SSH; inventory nie powinno zawierać sekretów.
  • Project wskazuje repozytorium Git z playbookami. Credential typu Source Control służy wyłącznie do pobrania prywatnego repozytorium, a credential typu Machine służy do SSH na hostach zarządzanych.
  • EE (execution environment) to obraz kontenera z ansible-core, kolekcjami i zależnościami. Każdy job dostaje pod z tym obrazem; pakiet zainstalowany tylko na hoście k3s nie stanie się przez to dostępny dla joba.
  • Job template jest zapisaną konfiguracją uruchomienia: łączy project, inventory, credential i EE z wybranym playbookiem. Dzięki temu ręczne uruchomienie, API i harmonogram używają tego samego zestawu danych.
  • SECRET_KEY - klucz, którym AWX szyfruje między innymi poświadczenia zapisane w bazie. Baza odtworzona z innym kluczem nie da się odszyfrować.
  • Ingress - obiekt Kubernetesa opisujący, jak ruch HTTP/HTTPS spoza klastra trafia do usługi. W k3s obsługuje go domyślnie Traefik.

Otwarte źródło nie oznacza gotowego wsparcia producenta. AWX ma społecznościowy cykl wydań, a automation controller w Red Hat Ansible Automation Platform jest osobnym produktem z własnym wsparciem. Etykieta „stable” nie zastępuje testu aktualizacji ani procedury odtworzenia. FAQ projektu AWX wprost nie rekomenduje go jako produktu produkcyjnego Red Hata. Można go świadomie utrzymywać, ale odpowiedzialność za wersje, bezpieczeństwo i odzyskanie danych pozostaje po stronie zespołu.

Jak działa jeden job od kliknięcia do hosta?

Przeglądarka nie łączy się z hostem zarządzanym. Najpierw rozmawia z częścią web AWX, a następnie kolejka zadań tworzy osobny pod z execution environment. Dopiero z tego poda Ansible łączy się do hosta przez SSH. Ta kolejność wyjaśnia, dlaczego sam dostęp SSH z VM z k3s nie wystarcza jako test joba.

przeglądarka / API
        |
        | HTTPS, DNS: awx.example.org
        v
Traefik Ingress w k3s
        |
        v
AWX web  <----> PostgreSQL
        |
        v
AWX task / receptor ----> pod z execution environment
                                      |
                                      | SSH / API / WinRM
                                      v
                               hosty zarządzane

Dwa elementy z tego schematu warto nazwać po imieniu. AWX web to część, którą widzisz w przeglądarce i która odpowiada na zapytania API. AWX task to część, która pilnuje kolejki zadań i decyduje, co i kiedy uruchomić; receptor jest jej warstwą sieciową, odpowiedzialną za dostarczenie zadania tam, gdzie ma się wykonać - przy jednej maszynie po prostu do kontenera obok. Zadanie nie wykonuje się więc ani w przeglądarce, ani w podzie web, tylko w osobnym, tworzonym na tę okazję kontenerze.

Najważniejszy wniosek z tego diagramu: prywatny klucz SSH, kolekcje Ansible i binarki potrzebne zadaniu muszą być dostępne dla AWX oraz dla jego środowiska wykonawczego. Wrzucenie ich do ~/.ssh użytkownika na maszynie z k3s albo doinstalowanie pakietu do systemowego Pythona tej maszyny nie rozwiązuje problemu, bo zadanie w ogóle tam nie zagląda.

Co przygotować przed instalacją?

Najpierw ustalamy granice środowiska. Poniższe odpowiedzi są warunkiem bezpiecznej instalacji, a nie dodatkami do zrobienia później.

RolaMaszyna lub usługaCo robi w przykładzie
Administratorzaufana stacja administracyjnama dostęp SSH i kubectl, tworzy repozytorium oraz dodaje publiczne klucze; nie zapisuje prywatnych kluczy w Git
Kontroler AWXawx-lab.example.testuruchamia k3s, operatora, AWX i pody jobów; potrzebuje wyjścia do rejestru obrazów, Git oraz hosta testowego
Host zarządzanylinux-test.example.testma konto awx-runner i akceptuje tylko publiczny klucz credentialu Machine przeznaczonego dla tego testu
Serwer Gitgit.example.testudostępnia prywatny project AWX przez deploy key tylko do odczytu
  1. Lab czy usługa dla zespołu? Lab może korzystać z kubectl port-forward przez SSH. Usługa zespołowa potrzebuje HTTPS, nazwy DNS i kontroli dostępu od pierwszego dnia, ponieważ później pojawiają się konta i poświadczenia.
  2. Gdzie będzie kopia zapasowa? Nie na tym samym dysku i nie tylko jako zrzut PostgreSQL. Bez sekretów Kubernetes taki zrzut jest niepełny.
  3. Czy DNS, TLS i sieć są gotowe? Publiczny certyfikat z ACME (Automatic Certificate Management Environment) wymaga routingu, którego konkretny mechanizm zależy od wyzwania. Jeżeli nie mamy go świadomie przygotowanego, zaczynamy w sieci prywatnej od tunelu SSH albo używamy certyfikatu z firmowej PKI (Public Key Infrastructure).
  4. Jaka jest granica odpowiedzialności zadań? Pierwszy template ma dotykać dedykowanego hosta testowego, a nie całej produkcyjnej inventory. Ta decyzja zapada przed instalacją, bo potem trudniej ją cofnąć.

Jak przygotować host i storage?

Przykład zakłada świeżą, wydzieloną VM z Ubuntu 24.04 LTS, stałym adresem IP i dostępem SSH dla administratora. Może działać na Proxmox, KVM, VMware lub u dostawcy chmury; dla AWX istotne są zasoby, dysk, sieć i możliwość odtworzenia, a nie marka wirtualizatora.

  1. Przygotuj maszynę wirtualną - procesor, pamięć, dysk, system i dostęp SSH.
  2. Sprawdź jej gotowość - czas, uprawnienia i wyjście na zewnątrz. Dowód: cztery polecenia z tej sekcji.
  3. Zainstaluj k3s. Dowód: jeden węzeł w stanie Ready.
  4. Wygeneruj manifest operatora i sprawdź obrazy. Dowód: dwie linie z grep.
  5. Utwórz sekrety - hasło administratora i SECRET_KEY.
  6. Wdróż operator, potem zasób AWX. Dowód: pody web, task i baza w stanie Running.
  7. Otwórz pierwsze UI przez tunel. Dopiero dla zespołu dodaj DNS, TLS i Ingress; dowodem będzie HTTP 200 po HTTPS oraz przekierowanie z portu 80.
  8. Uruchom pierwsze zadanie. Dowód: stan successful na dedykowanym hoście testowym.

Kroki od trzeciego wzwyż opisują kolejne sekcje. Przed instalacją warto utworzyć snapshot VM, ale nie traktować go jako jedynej kopii zapasowej danych AWX.

Ile procesora, pamięci i dysku potrzebuje host AWX?

Warto rozdzielić dwie różne liczby, bo bardzo łatwo je pomylić. Dokumentacja k3s podaje minimum dla samego Kubernetesa, a nie dla aplikacji, które w nim uruchomisz. Ta dokumentacja nie ma osobnych wersji dla wydań k3s. Tabelę porównaliśmy z nią 24 września 2026, gdy kanał stable wskazywał v1.36.4+k3s1; my instalujemy v1.34.3+k3s3. AWX potrzebuje wyraźnie więcej niż to minimum, więc poniżej zestawiamy jedno z drugim.

ZasóbMinimum k3s wg dokumentacjiUżyte dla AWXDlaczego tyle
Procesor2 rdzenie (węzeł serwera)4 rdzenieAWX to kilkanaście kontenerów naraz, a każde zadanie dostaje jeszcze własny. Na 4 rdzeniach AWX wyliczył sobie pojemność 16 procesów Ansible.
Pamięć2 GB (węzeł serwera)8 GiBSam AWX z bazą i Redisem zajmuje kilka GiB w spoczynku. Na maszynie testowej przy bezczynnym AWX zajęte było 3,3 GiB.
Dyskzalecany SSD40 GiBSame obrazy kontenerów to blisko 2 GiB, do tego 8 GiB na dane bazy i miejsce na obrazy środowisk wykonawczych, które dochodzą z czasem. Po instalacji i pierwszym zadaniu zajęte było 12 z 38 GiB.
System-Ubuntu 24.04 LTSWersja, na której prowadzono wszystkie testy z tego artykułu. Debian jest osobnym celem testowym; nie wnioskuj jego zgodności wyłącznie z tej procedury.

Maszyna potrzebuje też stałego adresu IP w sieci, z której będziesz się z nią łączyć, oraz konta z prawem do sudo. Jeżeli AWX ma być dostępny pod nazwą DNS i po HTTPS, ta nazwa musi wskazywać właśnie na ten adres - ale rekord DNS można dodać później, bo do samej instalacji nie jest potrzebny.

Skąd wziąć VM i jak oddzielić jej utworzenie od instalacji AWX?

Może pochodzić z dowolnego źródła: od dostawcy hostingu, z Proxmoxa, z VMware, z VirtualBoxa albo z chmury. Dla AWX nie ma to znaczenia, dopóki spełnia tabelę wyżej. Jeżeli tworzysz ją samodzielnie na Linuksie z KVM - z angielskiego Kernel-based Virtual Machine, czyli wirtualizacją wbudowaną w jądro Linuksa - poniższy zestaw poleceń buduje ją z gotowego obrazu chmurowego, bez przechodzenia przez instalator systemu.

Najpierw plik konfiguracyjny cloud-init, czyli mechanizmu, który przy pierwszym starcie ustawia w obrazie chmurowym nazwę hosta, konto i klucz SSH. Bez niego obraz wstanie, ale nie będzie jak się do niego zalogować. Wstaw swój własny klucz publiczny:

cat > user-data <<'EOF'
#cloud-config
hostname: awx
users:
  - name: awxadm
    groups: [sudo]
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... twoj-klucz-publiczny
package_update: true
EOF

printf 'instance-id: awx-01\nlocal-hostname: awx\n' > meta-data

# Oba pliki trafiają na maleńki obraz ISO o etykiecie cidata - stąd cloud-init je czyta.
genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data

Potem tworzymy dysk i samą maszynę. Obraz chmurowy Ubuntu pobierz ze strony cloud-images.ubuntu.com (katalog current wskazuje zawsze najnowszy build, 24 września 2026 był to 20260911) - plik dla KVM to wariant amd64.img:

# Obraz chmurowy jest mały; rozszerzamy go do docelowego rozmiaru dysku.
qemu-img create -f qcow2 -F qcow2 -b noble-server-cloudimg-amd64.img awx.qcow2 40G

virt-install --name awx --memory 8192 --vcpus 4 --cpu host-passthrough \
  --disk path="$PWD/awx.qcow2",format=qcow2,bus=virtio \
  --disk path="$PWD/seed.iso",device=cdrom \
  --os-variant ubuntu24.04 --network network=default,model=virtio \
  --graphics none --import --noautoconsole

Zanim uruchomisz to naprawdę, dopisz na końcu --dry-run. Polecenie sprawdzi wtedy poprawność wszystkich parametrów i nic nie utworzy - tak samo jak --dry-run=server przy manifeście AWX w dalszej części artykułu. Adres maszyny pokaże potem virsh domifaddr awx.

Jeżeli VM jest tworzona przez Terraform albo inny kod infrastruktury, do repozytorium trafia tylko definicja zasobów i odwołanie do publicznego klucza. Prywatne klucze SSH, kubeconfig i sekrety AWX pozostają poza nim.

Jak sprawdzić host przed instalacją k3s?

Na świeżej maszynie wykonujemy cztery sprawdzenia. Zajmują minutę, a oszczędzają szukania przyczyny w połowie instalacji:

# 1. System i zasoby - czy maszyna jest tym, czym miała być
grep PRETTY_NAME /etc/os-release
nproc; free -h; df -h /

# 2. Czas - rozjechany zegar psuje certyfikaty i uwierzytelnianie
timedatectl | grep -E 'System clock|NTP service'

# 3. Uprawnienia - instalacja k3s wymaga roota
sudo -v

# 4. Wyjście na zewnątrz - instalator i obrazy pobierają się z sieci
curl -sS -o /dev/null -w 'k3s:    %{http_code}\n' https://get.k3s.io
curl -sS -o /dev/null -w 'quay:   %{http_code}\n' https://quay.io/v2/
curl -sS -o /dev/null -w 'github: %{http_code}\n' https://github.com

Oczekiwany wynik to zsynchronizowany zegar z aktywną usługą synchronizacji czasu NTP (od angielskiego Network Time Protocol) oraz kody 200 dla get.k3s.io i GitHuba. W przypadku quay.io poprawną odpowiedzią jest 401, a nie 200: pytamy o punkt wejścia rejestru bez logowania, więc odmowa dostępu jest tu dowodem, że rejestr odpowiada. Dostęp do GitHuba jest potrzebny, bo manifest operatora pobiera się właśnie stamtąd, a obrazy - z quay.io.

Na maszynie testowej wynikiem było: Ubuntu 24.04.4 LTS, 4 rdzenie, 7,7 GiB pamięci, 26 GiB wolnego dysku, zegar zsynchronizowany i wszystkie trzy adresy osiągalne.

Jak zainstalować i sprawdzić jednowęzłowy k3s?

Na hoście awx-lab.example.test, jako użytkownik z sudo, instalujemy Kubernetesa. Przed uruchomieniem instalatora sprawdzamy wymagania k3s, zwłaszcza reguły firewalla i porty. W przykładzie nie wyłączamy Traefika, ponieważ może później obsłużyć Ingress z TLS. Instalator pobierany z get.k3s.io wykonuje kod pobrany z sieci; w środowisku objętym polityką zmian używamy zatwierdzonego artefaktu lub wewnętrznego mirrora zamiast bezrefleksyjnego uruchamiania potoku.

sudo apt update
sudo apt install -y ca-certificates curl openssl

# Wersja przetestowana w tym materiale. Nie zastępuj jej bez testu upgrade'u.
K3S_VERSION='v1.34.3+k3s3'
curl -sfL https://get.k3s.io | \
  sudo env INSTALL_K3S_VERSION="$K3S_VERSION" sh -s - server

sudo systemctl enable --now k3s
sudo k3s kubectl get nodes -o wide
sudo k3s kubectl get storageclass
sudo k3s kubectl get ingressclass

Wynik ma pokazać jeden node w stanie Ready, domyślną StorageClass local-path i aktywną klasę Ingress. Jeżeli klaster zwróci inną nazwę niż traefik, nie wpisujemy jej w ciemno do manifestu AWX. Używamy faktycznie dostępnej klasy albo pozostawiamy usługę jako ClusterIP i zaczynamy od tunelu.

Do codziennej pracy wygodniejszy jest kubeconfig użytkownika niż każdorazowe sudo k3s kubectl. Nie kopiuj tego pliku na publiczny host ani do repozytorium:

mkdir -p ~/.kube
sudo install -m 0600 -o "$USER" -g "$USER" \
  /etc/rancher/k3s/k3s.yaml ~/.kube/config

kubectl get nodes
kubectl -n kube-system get pods

Co oznacza storage local-path w tym wariancie?

Local Path Provisioner tworzy wolumen na dysku węzła. Dla jednego węzła jest to wygodne: operator AWX może utworzyć PVC dla PostgreSQL bez osobnego serwera NFS ani sterownika CSI - z angielskiego Container Storage Interface, czyli standardowego sposobu podłączania zewnętrznych macierzy do Kubernetesa. Na maszynie testowej PVC bazy ma 8 GiB, klasę local-path i stan Bound. Nie jest to jednak wolumen przenośny między węzłami ani replikowana macierz, a tryb wiązania WaitForFirstConsumer oznacza, że wolumen powstaje dopiero wtedy, gdy pojawi się pod, który go używa.

Przy local-path operatorowi trzeba pozwolić przygotować katalog danych PostgreSQL z właściwym identyfikatorem użytkownika. Dlatego w manifeście niżej pojawia się postgres_data_volume_init: true; opis tego pola w CRD mówi wprost, że ustawia ono uprawnienia katalogu /var/lib/pgdata/data przez kontener inicjalizujący.

Jak przypiąć i zweryfikować wersje operatora oraz AWX?

Zadeklarowanie wersji to wpisanie dokładnego numeru tam, gdzie kusi ogólna etykieta. Obrazy kontenerów pobiera się po tagu, czyli etykiecie w rejestrze: tagiem jest zarówno 24.6.1, jak i latest. latest znaczy „to, co akurat jest najnowsze”, więc to samo polecenie uruchomione dziś i za miesiąc może pobrać różne obrazy. Tag wersji wskazuje zamierzone wydanie i jest czytelniejszy w przeglądzie zmian, ale nie daje kryptograficznej niezmienności; gdy potrzebna jest pełna powtarzalność, przypinamy również digest obrazu. Wybór konkretnej wersji jest świadomą rezygnacją z automatycznej nowości na rzecz kontroli nad zmianą.

Przy AWX ma to znaczenie podwójne, bo składają się na niego dwa osobno wydawane elementy: operator i sama aplikacja. Deklarujemy więc obie wersje, a nie jedną.

Laboratoryjny przykład jest przypięty do AWX Operator 2.19.1, AWX 24.6.1 i k3s v1.34.3+k3s3. To sprawdzony punkt odniesienia, nie zalecenie, aby w 2026 roku automatycznie wybierać stare tagi. Dla nowej instalacji wybieramy wydanie z oficjalnych release notes, ustalamy mapowanie operator-AWX i odtwarzamy ten sam proces w labie przed użyciem go w usłudze.

Pułapka operatora 2.19.1. Manifest z repozytorium projektu wskazuje obraz gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0, a ten obraz został wycofany. W dniu testu zapytanie o manifest tego tagu zwróciło HTTP 404 z komunikatem MANIFEST_UNKNOWN, a lista tagów całego repozytorium była pusta - to nie jest kwestia jednej brakującej wersji. Wycofanie obrazu i jego zamienniki opisuje dyskusja w projekcie Kubebuilder. Bez podmiany obrazu pod operatora utknie w ImagePullBackOff.

Działająca instalacja używa zamiast niego quay.io/brancz/kube-rbac-proxy:v0.15.0, czyli tej samej wersji z macierzystego repozytorium projektu. To świadomy, przetestowany workaround, a nie niewidoczna zmiana obrazu: podmianę zapisujemy jawnie w Kustomize, żeby była widoczna w przeglądzie zmian. Przed każdym przyszłym upgrade'em ponownie sprawdź render i obrazy - i nie przestawiaj tagów na latest.

Utwórz katalog roboczy poza repozytorium z playbookami albo dodaj go do prywatnego repozytorium infrastruktury. Poniższe pliki nie zawierają sekretów.

mkdir -p ~/awx-k3s/operator
cd ~/awx-k3s

Tworzymy plik operator/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: awx
resources:
  - github.com/ansible/awx-operator/config/default?ref=2.19.1
images:
  - name: quay.io/ansible/awx-operator
    newTag: 2.19.1
  # gcr.io/kubebuilder/kube-rbac-proxy zostal wycofany z rejestru.
  # quay.io/brancz to macierzyste repozytorium projektu - ta sama wersja.
  - name: gcr.io/kubebuilder/kube-rbac-proxy
    newName: quay.io/brancz/kube-rbac-proxy
    newTag: v0.15.0

Najpierw wyrenderuj manifest. To łapie literówki i potwierdza, że obejście martwego obrazu faktycznie weszło do wyniku:

kubectl kustomize operator | \
  grep -E 'quay.io/ansible/awx-operator:2\.19\.1|quay.io/brancz/kube-rbac-proxy:v0\.15\.0'

Poprawny wynik to dwie linie, po jednej na każdy obraz. Jeżeli grep nie zwróci nic, nie wykonuj kubectl apply: sprawdź wersję Kustomize, zawartość pliku i dostęp do GitHuba. Render pobiera bazę zdalnie, więc maszyna musi mieć wyjście na zewnątrz.

Warto też wiedzieć, co dokładnie ten render tworzy. Poza czterema definicjami CRD i wdrożeniem kontrolera zawiera on również obiekt Namespace. Namespace utworzony niżej ręcznie nie jest więc konfliktem - potrzebujemy go wcześniej tylko po to, żeby sekrety zdążyły powstać przed operatorem.

Jak utworzyć sekrety bez wpisywania ich do plików?

AWX potrafi sam wygenerować hasło administratora i SECRET_KEY, ale dla powtarzalnego wdrożenia tworzymy je jawnie jako obiekty Secret. SECRET_KEY szyfruje między innymi poświadczenia zapisane w bazie. Po odtworzeniu samej bazy z innym kluczem nie będzie można tych danych odszyfrować, więc backup bazy bez tego sekretu jest niepełny.

Na zaufanym terminalu administratora, z kontekstem kubectl wskazującym awx-lab.example.test, generujemy wartości bezpośrednio w klastrze. Nie umieszczamy ich w YAML-u, repozytorium, historii czatu ani w argumentach polecenia widocznych dla innych procesów. Pliki tymczasowe mają prawa 0600 i są usuwane po utworzeniu sekretów; w CI używamy zatwierdzonego kanału sekretów zamiast tej interaktywnej procedury.

umask 077
ADMIN_PASSWORD_FILE="$(mktemp)"
SECRET_KEY_FILE="$(mktemp)"
trap 'rm -f "$ADMIN_PASSWORD_FILE" "$SECRET_KEY_FILE"' EXIT

openssl rand -base64 32 > "$ADMIN_PASSWORD_FILE"
openssl rand -base64 48 > "$SECRET_KEY_FILE"

kubectl create namespace awx --dry-run=client -o yaml | kubectl apply -f -

kubectl -n awx create secret generic awx-admin-password \
  --from-file=password="$ADMIN_PASSWORD_FILE"

kubectl -n awx create secret generic awx-secret-key \
  --from-file=secret_key="$SECRET_KEY_FILE"

rm -f "$ADMIN_PASSWORD_FILE" "$SECRET_KEY_FILE"
trap - EXIT
unset ADMIN_PASSWORD_FILE SECRET_KEY_FILE

Polecenia tworzące sekrety celowo kończą się błędem, gdy nazwa już istnieje. Nie nadpisujemy wtedy sekretu ani go nie kasujemy; najpierw ustalamy, czy istniejąca instancja używa tej wartości i czy jej backup zawiera właściwy SECRET_KEY.

Do Ingressu potrzebny jest osobny sekret typu tls. Jeżeli certyfikat i klucz już istnieją, na przykład z firmowej infrastruktury kluczy albo z cert-managera, utwórz go tak:

kubectl -n awx create secret tls awx-tls \
  --cert=/bezpieczna/sciezka/fullchain.pem \
  --key=/bezpieczna/sciezka/privkey.pem

Cert-manager jest dobrym sposobem automatycznego odnawiania, ale zanim zadziała wyzwanie HTTP-01, muszą działać publiczny DNS, port 80 i routing do Traefika. W środowisku bez tych trzech elementów lepiej użyć certyfikatu z własnej infrastruktury i nie udawać, że ACME zostało skonfigurowane.

Jest jeszcze jedno ustawienie, o którym warto wiedzieć, zanim ktoś zacznie sprzątać klaster. Pole garbage_collect_secrets ma w CRD wartość domyślną false, co znaczy, że usunięcie zasobu AWX nie kasuje jego sekretów. Ta wartość domyślna działa na Twoją korzyść - SECRET_KEY przeżywa skasowanie instancji. Nie przestawiaj jej na true, dopóki nie masz sprawdzonego backupu tych sekretów poza klastrem.

Jak utworzyć instancję AWX i sprawdzić jej stan?

Po uruchomieniu operatora zapisujemy CR, czyli opis jednej instancji AWX, w pliku awx.yaml. W pierwszym teście usługa pozostaje typu ClusterIP, dostępnego tylko wewnątrz klastra. Dzięki temu można bezpiecznie sprawdzić UI przez tunel, zanim skonfigurujemy DNS i TLS. Poniższy manifest przeszedł serwerowy --dry-run wobec CRD operatora 2.19.1.

apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx
  namespace: awx
spec:
  # Zadeklarowana, sprawdzona para operator/AWX.
  image_version: 24.6.1

  admin_user: admin
  admin_password_secret: awx-admin-password
  secret_key_secret: awx-secret-key

  # Pierwszy test przez kubectl port-forward, bez publicznego HTTP.
  service_type: ClusterIP

  # Jednowęzłowy k3s: trwałość na lokalnym dysku tej maszyny.
  postgres_storage_class: local-path
  postgres_storage_requirements:
    requests:
      storage: 8Gi
  postgres_data_volume_init: true

  # Domyślnie true: upgrade operatora pociąga za sobą upgrade instancji.
  auto_upgrade: false

Do tych pól mamy kilka uwag, sprawdzonych w schemacie CRD zainstalowanego operatora:

  • service_type przyjmuje wyłącznie LoadBalancer, ClusterIP lub NodePort (także pisane małymi literami). Inna wartość zostanie odrzucona przez API, a nie po cichu zignorowana.
  • auto_upgrade ma domyślnie wartość true, a jego opis w CRD brzmi: instancje AWX mają być automatycznie aktualizowane, gdy zaktualizowany zostanie operator. To znaczy, że podbicie operatora nie jest odizolowanym krokiem. Jeżeli chcesz rozdzielić te dwie decyzje, ustaw false jawnie i aktualizuj aplikację osobno.

Na tym etapie nie dodajemy Ingressu. Dla zespołu konfigurujemy go dopiero po przygotowaniu nazwy DNS, certyfikatu TLS i testu przekierowania z HTTP do HTTPS, opisanych w następnej sekcji.

Kolejność wdrożenia ma znaczenie. Nie mieszamy pierwszego wdrożenia operatora i zasobu AWX w jednym apply -k: najpierw instalujemy CRD i kontroler, czekamy na ich gotowość, a dopiero potem pytamy serwer API o walidację CR. Dzięki temu --dry-run=server rzeczywiście wykryje pole, którego użyta wersja operatora nie zna.

cd ~/awx-k3s

kubectl apply -k operator
kubectl wait --for=condition=Established \
  crd/awxs.awx.ansible.com --timeout=2m
kubectl -n awx rollout status deployment/awx-operator-controller-manager \
  --timeout=5m

kubectl apply -f awx.yaml --dry-run=server
kubectl apply -f awx.yaml
kubectl -n awx get awx,pods,svc,pvc,ingress

Pierwsze pobranie obrazów może potrwać kilka minut. Patrz na stan, a nie na zgadywany czas:

kubectl -n awx get pods -w

# Gdy coś utknie:
kubectl -n awx get events --sort-by=.lastTimestamp
kubectl -n awx logs deployment/awx-operator-controller-manager \
  -c awx-manager --tail=100

Prawidłowy koniec tego etapu to gotowe pody operatora, awx-web, awx-task i PostgreSQL oraz PVC bazy w stanie Bound. Warto zwrócić uwagę na dwie rzeczy. Po pierwsze, wszystkie obiekty aplikacji dostają nazwę pochodną od nazwy zasobu AWX: przy metadata.name: awx usługa nazywa się awx-service, a pody awx-web-* i awx-task-*. Jeżeli nazwiesz zasób inaczej, wszystkie polecenia niżej trzeba odpowiednio zmienić. Po drugie, pody web i task mają po kilka kontenerów - na maszynie testowej task ma ich cztery, w tym Redis i kontener z obrazem EE. Nie przechodź dalej tylko dlatego, że istnieje Deployment: sprawdź wszystkie kontenery i zdarzenia.

Jak wystawić AWX zespołowi bez otwartego HTTP?

Ten krok wykonujemy dopiero po pozytywnym teście przez tunel. Ingress jest regułą, która kieruje ruch HTTPS z nazwy DNS do usługi w klastrze. Potrzebujemy działającego DNS, sekretu awx-tls z certyfikatem dla tej nazwy oraz świadomie wybranej klasy Ingress. W przykładzie jest to Traefik, lecz jej nazwy nie zakładamy bez sprawdzenia kubectl get ingressclass.

W istniejącym awx.yaml dodajemy wtedy następujące pola i ponownie wykonujemy kubectl apply -f awx.yaml --dry-run=server. Zastępujemy nazwę przykładową własną nazwą DNS:

  ingress_type: ingress
  ingress_class_name: traefik
  ingress_hosts:
    - hostname: awx.example.org
      tls_secret: awx-tls
ZapytanieWynikCo z tego wynika
HTTPS, poprawny HostHTTP 200, treść z /api/v2/ping/, certyfikat z sekretuklasa Ingress, sekret TLS i routing do usługi są poprawnie połączone
HTTPS, nieznany HostHTTP 404 z Traefika404 przy pierwszym wejściu to zwykle zły hostname lub zła klasa, a nie awaria AWX
HTTP na porcie 80HTTP 200, pełna odpowiedź aplikacjisamo dodanie sekcji tls nie zamyka zwykłego HTTP

Trzeci wiersz jest tym, co najczęściej umyka. Traefik w k3s nie ma domyślnie skonfigurowanego przekierowania z portu 80 na 443 - w argumentach jego wdrożenia widać jedynie definicje punktów wejścia i włączony TLS na websecure, bez żadnej reguły redirections. Ingress z sekcją tls obsługuje HTTPS, ale nadal serwuje tę samą aplikację po nieszyfrowanym HTTP. Panel logowania do AWX wystawiony w ten sposób przyjmie hasło przez otwarte połączenie.

Naprawa jest krótka i sprawdzona. Traefik udostępnia zasób Middleware; wystarczy jeden obiekt i jedna adnotacja na Ingressie:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: awx-https-redirect
  namespace: awx
spec:
  redirectScheme:
    scheme: https
    permanent: true

Nazwa middleware w adnotacji ma postać <namespace>-<nazwa>@kubernetescrd - jest to częsty punkt pomyłki, bo sam name nie zadziała:

traefik.ingress.kubernetes.io/router.middlewares: awx-awx-https-redirect@kubernetescrd

Po dodaniu tej adnotacji zapytanie na port 80 zwróciło 301 z poprawnym adresem docelowym https://, a HTTPS nadal odpowiadało 200. Klient podążający za przekierowaniem wykonuje dokładnie jeden przeskok.

Gdy Ingressem zarządza operator AWX, adnotację przekazuje się przez pole ingress_annotations. Uwaga na jego typ: w CRD jest to ciąg znaków, a nie mapa, więc trzeba użyć bloku YAML, a nie zagnieżdżonego obiektu:

  ingress_annotations: |
    traefik.ingress.kubernetes.io/router.middlewares: awx-awx-https-redirect@kubernetescrd

Alternatywą jest globalne przekierowanie na punkcie wejścia Traefika przez HelmChartConfig w namespace kube-system. Rozwiązanie per Ingress jest jednak węższe i nie zmienia zachowania innych usług w klastrze, co przy jednej maszynie z kilkoma aplikacjami zwykle jest bezpieczniejszym wyborem.

Jak bezpiecznie otworzyć pierwsze UI?

Na początku nie potrzebujemy publicznego DNS ani NodePort. Na awx-lab.example.test, w sesji SSH administratora, uruchamiamy tunel do usługi ClusterIP:

kubectl -n awx port-forward service/awx-service 8080:80

Następnie z tej samej stacji administracyjnej otwieramy http://127.0.0.1:8080/. Gdy kubectl działa na zdalnej VM, uruchamiamy lokalny tunel SSH do portu 8080 tej VM, zamiast wystawiać port na firewallu. Tunel kończy się wraz z procesem kubectl; jest bezpiecznym testem UI, ale nie zastępuje późniejszego HTTPS przez Ingress.

Pierwsze hasło odczytujemy wyłącznie na zaufanym terminalu:

kubectl -n awx get secret awx-admin-password \
  -o jsonpath='{.data.password}' | base64 -d
echo

Nie wklejaj wyniku do zgłoszenia, komunikatora ani historii terminala. Zaloguj się jako admin i od razu utwórz zwykłe konta oraz zespoły, zamiast używać administratora do codziennej pracy.

Po uruchomieniu tunelu endpoint kontrolny powinien zwrócić JSON oraz kod 200:

curl -fsS http://127.0.0.1:8080/api/v2/ping/

Ten test potwierdza część web i API. Nie potwierdza pobrania projektu, działania execution environment ani dostępu SSH do hosta; te elementy sprawdzamy pierwszym jobem.

Dlaczego job potrzebuje własnego execution environment?

Execution environment (EE) jest obrazem kontenera, w którym uruchamia się Ansible dla konkretnego joba. W nim muszą znajdować się wymagane kolekcje i biblioteki. Świeża instalacja AWX 24.6.1 z labu zarejestrowała trzy globalne EE:

Nazwa w AWXObrazRozmiar w cache węzła
AWX EE (24.6.1)quay.io/ansible/awx-ee:24.6.1469 MB
AWX EE (latest)quay.io/ansible/awx-ee:latest696 MB
Control Plane Execution Environmentquay.io/ansible/awx-ee:24.6.1ten sam obraz co pierwszy

Globalne ustawienie DEFAULT_EXECUTION_ENVIRONMENT było przy tym puste. Efekt zobaczyliśmy w praktyce: zadanie uruchomione bez jawnego wskazania środowiska poszło na AWX EE (latest) i pociągnęło za sobą pobranie obrazu latest. Stąd bierze się typowa niespodzianka „przecież podałem konkretną wersję AWX, a i tak ściągnął coś innego”.

Konsekwencje są dwie i obie są mierzalne. Pierwsza to czas: pierwsze zadanie na maszynie testowej trwało 85 sekund, z czego zdecydowana większość przypadła na pobranie obrazu. Druga to powtarzalność - awx-ee:latest i awx-ee:24.6.1 to dwa różne obrazy, różniące się w cache węzła o ponad 200 MB. Zadanie, które dziś działa na latest, jutro może dostać inne wersje kolekcji.

Dlatego w każdym job template deklarujemy execution environment jawnie: albo do konkretnego tagu, na przykład quay.io/ansible/awx-ee:24.6.1, albo do własnego obrazu z wymaganymi kolekcjami. Kiedy projekt potrzebuje kolekcji spoza standardowego EE, buduje się własny obraz, na przykład przez ansible-builder, i wskazuje w szablonie jego tag albo sumę kontrolną obrazu (tak zwany digest), która - w odróżnieniu od tagu - wskazuje zawsze dokładnie tę samą zawartość. Dzięki temu zadanie uruchomione z interfejsu, z API i z harmonogramu ma identyczne zależności. Nie instaluj bibliotek „na próbę” do poda awx-task - znikną przy jego odtworzeniu, a odtworzenie nastąpi przy najbliższej zmianie zasobu AWX.

Jak wykonać pierwszy job przez SSH?

W tym przykładzie administrator tworzy małe prywatne repozytorium, AWX pobiera je własnym poświadczeniem Source Control, a job łączy się jako awx-runner z jednym hostem testowym. Moduł ansible.builtin.ping nie wysyła pakietu ICMP i nie zmienia konfiguracji. Otwiera połączenie Ansible, wykonuje moduł Pythona i oczekuje odpowiedzi pong.

Na stacji administracyjnej zapisujemy playbook jako ping.yml w repozytorium awx-first-job:

---
- name: Kontrola łączności przez AWX
  hosts: linux_test
  gather_facts: false
  tasks:
    - name: Ansible odpowiada
      ansible.builtin.ping:

Najpierw na serwerze Git tworzymy puste, prywatne repozytorium automation/awx-first-job, bez automatycznego pliku README. Na tej samej stacji tworzymy commit i wysyłamy go do repozytorium. Adres git.example.test zastępujemy własnym serwerem Git; nie zapisujemy w URL tokenu.

mkdir -p ~/src/awx-first-job
cd ~/src/awx-first-job
# Zapisz tutaj pokazany wyżej plik ping.yml.
git init -b main
git add ping.yml
git commit -m 'Dodaj test łączności AWX'
git remote add origin ssh://git@git.example.test/automation/awx-first-job.git
git push -u origin main

Tworzymy dwa różne klucze. Pierwszy jest kluczem wdrożeniowym tylko do odczytu tego repozytorium. Drugi pozwala AWX zalogować się wyłącznie na host testowy. Ich prywatnych części nie dodajemy do Git ani do inventory.

# Stacja administracyjna: osobny klucz do pobierania projektu przez AWX.
ssh-keygen -t ed25519 -f ~/.ssh/awx-project-readonly \
  -C 'AWX source control: awx-first-job'

# Stacja administracyjna: osobny klucz do logowania z joba na host testowy.
ssh-keygen -t ed25519 -f ~/.ssh/awx-linux-test \
  -C 'AWX machine: linux-test'

# Dozwalamy tylko drugiemu kluczowi na logowanie do linux-test.example.test.
ssh-copy-id -i ~/.ssh/awx-linux-test.pub \
  awx-runner@linux-test.example.test

# Kontrola klucza przed dodaniem go do AWX; poprawny wynik to kod wyjścia 0.
ssh -i ~/.ssh/awx-linux-test \
  awx-runner@linux-test.example.test true

Publiczną część awx-project-readonly.pub dodajemy na serwerze Git jako deploy key z prawem tylko do odczytu. Publiczna część awx-linux-test.pub trafia na host testowy do ~awx-runner/.ssh/authorized_keys; polecenie ssh-copy-id wykonuje to po wcześniejszym, jednorazowym uwierzytelnieniu administratora. Zachowujemy hasła do prywatnych kluczy w menedżerze sekretów, aby można je było podać AWX bez odsłaniania ich w playbooku.

W interfejsie AWX wykonujemy kolejno:

  1. Organizations → Add: tworzymy organizację Example Operations. To właściciel pozostałych obiektów, a nie konto administratora.
  2. Credentials → Add → Source Control: tworzymy awx-first-job-readonly, wklejamy prywatną część awx-project-readonly oraz, jeśli klucz go ma, jego passphrase. To poświadczenie służy wyłącznie do Git.
  3. Credentials → Add → Machine: tworzymy linux-test-ssh, wpisujemy użytkownika awx-runner i prywatną część awx-linux-test. To poświadczenie służy wyłącznie do SSH na hostach, nigdy do pobierania repozytorium.
  4. Inventories → Add: tworzymy inventory lab-linux, grupę linux_test i host linux-test. W zmiennych hosta wpisujemy ansible_host: 192.0.2.40 oraz ansible_user: awx-runner; adres dokumentacyjny zastępujemy prawdziwym adresem testowego hosta.
  5. Projects → Add: wybieramy SCM Git, wpisujemy ssh://git@git.example.test/automation/awx-first-job.git, przypisujemy credential awx-first-job-readonly i uruchamiamy synchronizację. Jej stan musi być Successful, zanim powstanie template.
  6. Templates → Add → Job Template: wybieramy project, playbook ping.yml, inventory lab-linux, credential linux-test-ssh oraz jawnie wskazany EE quay.io/ansible/awx-ee:24.6.1 lub własny, przypięty obraz.
  7. Uruchamiamy Launch i czytamy output konkretnego joba, nie tylko zielony znacznik w panelu.

Zmienne hosta w inventory wpisujemy w polu Variables jako YAML. Adres z zakresu dokumentacyjnego zastępujemy prawdziwym adresem hosta testowego:

ansible_host: 192.0.2.40
ansible_user: awx-runner

Podczas uruchomienia AWX tworzy pod joba z wybranym EE. Pod pobiera commit projektu za pomocą credential Source Control, a następnie uruchamia ansible-core, który używa credential Machine do SSH na linux-test.example.test. Sukces ping.yml oznacza jednocześnie, że:

  • projekt dał się pobrać i zawiera wskazany playbook;
  • AWX odczytał właściwe inventory oraz dwa rozdzielone poświadczenia;
  • pod zadania wystartował w execution environment;
  • z tego środowiska istnieje połączenie SSH do hosta jako awx-runner;
  • Ansible wykonał moduł Pythona na docelowym hoście i otrzymał odpowiedź pong.

To dużo silniejszy dowód niż samo otwarcie strony logowania.

Po czym poznać, że pierwszy job rzeczywiście działa?

W logu joba oczekujemy wpisu podobnego do poniższego. Nazwa hosta i część dodatkowych pól zależą od środowiska, ale changed: false oraz ping: pong są istotne: test nic nie zmienił, a Ansible wykonał moduł na zdalnym hoście.

linux-test | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

W czasie uruchomienia można równolegle na hoście k3s wykonać kubectl -n awx get pods -w. Krótkotrwały pod automation-job-… potwierdza, że AWX uruchomił EE; po zakończeniu joba jego usunięcie jest normalne. Zapis joba pozostaje w historii AWX jako ślad audytowy.

Jeżeli UI i /api/v2/ping/ działają, ale job nie zwraca pong, nie jest to dowód działania automatyzacji. Należy sprawdzić osobno synchronizację projektu, credential Source Control, inventory, credential Machine oraz łączność z poda joba do hosta testowego.

Jak używać API bez zostawiania tokenu w historii powłoki?

Interfejs jest wygodny przy pierwszym wdrożeniu, ale do automatyzacji administracji samym AWX lepiej nadaje się API. Utwórz osobisty token w profilu użytkownika, z najmniejszym potrzebnym zakresem, przechowaj go w menedżerze sekretów i używaj bez zapisywania wartości w historii powłoki:

read -r -s -p 'AWX token: ' AWX_TOKEN
echo

curl -fsS \
  -H "Authorization: Bearer $AWX_TOKEN" \
  https://awx.example.org/api/v2/ping/

unset AWX_TOKEN

Który obiekt AWX odpowiada za którą część joba?

Gdy job nie działa, najpierw ustalamy, który obiekt odpowiada za dany etap. Większość problemów nie jest awarią AWX, lecz pomyleniem poświadczenia do Git z poświadczeniem SSH albo inventory z projektem.

Obiekt AWXDo czego służyCzęsty błąd
Organization / Teamwłaściciel zasobów i kontrola dostępukażdy pracuje jako admin
Credential: Machineużytkownik, klucz SSH, ewentualnie podniesienie uprawnieńużycie tego samego klucza na wszystkich środowiskach
Credential: Source Controldostęp do prywatnego repozytoriumwklejenie tokenu Git do poświadczenia Machine
Projectkod playbooków w Git i jego synchronizacjaoczekiwanie, że AWX widzi lokalny katalog z laptopa
Inventoryhosty, grupy i zmiennedodanie produkcji do pierwszego testu
Execution environmentansible-core, kolekcje i zależności zadaniazałożenie, że AWX używa Ansible z maszyny kontrolera
Job Templatepowtarzalne połączenie projektu, inventory, poświadczeń i EEręczne odpalanie zadań z przypadkowymi parametrami

Jak diagnozować pody, storage, repozytorium i poświadczenia?

Zaczynamy od elementu, który faktycznie nie działa. Polecenia Kubernetesa uruchamiamy na awx-lab.example.test jako administrator z dostępem do klastra; są to wyłącznie odczyty stanu.

ObjawSprawdzenieNajczęstsza przyczyna
Operator nie startuje, ImagePullBackOffkubectl -n awx describe pod -l control-plane=controller-managerwycofany obraz z gcr.io; sprawdź podmianę w Kustomize
Zasób AWX istnieje, ale brak podów web i taskkubectl -n awx get events --sort-by=.lastTimestamp i log operatoraniegotowe PVC, błąd obrazu albo błąd w manifeście
PostgreSQL czeka na PVCkubectl -n awx get pvcbrak albo inna StorageClass, ewentualnie problem z provisioningiem local-path
Traefik zwraca 404kubectl -n awx get ingress, kubectl get ingressclass, DNS i nagłówek Hostzła klasa Ingress, inny hostname albo DNS prowadzący gdzie indziej
Strona działa po HTTP mimo skonfigurowanego TLSzapytanie na port 80 i sprawdzenie adnotacji Ingressubrak przekierowania - Traefik w k3s nie robi go domyślnie
Zadanie długo stoi w ContainerCreatingkubectl -n awx get pods, kubectl -n awx describe pod automation-job-...pierwsze pobranie obrazu EE albo problem z rejestrem lub siecią
Synchronizacja projektu kończy się błędemoutput synchronizacji projektu w AWX oraz adres repozytoriumzły URL, nieprzypisany credential Source Control, brak deploy key lub brak wyjścia z job poda do serwera Git
Zadanie nie łączy się przez SSHoutput konkretnego joba, potem ansible_host, użytkownik, klucz i trasę sieciowąbłędny credential Machine, brak publicznego klucza w authorized_keys, zły użytkownik albo filtr ruchu z sieci klastra
Po odtworzeniu poświadczenia są nieczytelneporównaj użyty SECRET_KEY z backupemodtworzono bazę bez sekretów Kubernetes

Stan zbieramy tymi poleceniami, które nie zmieniają klastra:

kubectl -n awx get awx,pods,svc,ingress,pvc
kubectl -n awx get events --sort-by=.lastTimestamp | tail -100
kubectl -n awx logs deployment/awx-operator-controller-manager \
  -c awx-manager --tail=200
kubectl -n awx describe awx awx

Jak aktualizować AWX i gdzie kończy się rollback?

Najkrótsza zasada brzmi: backup bazy PostgreSQL nie wystarcza. Musi zawierać również sekrety współpracujące z bazą, przede wszystkim SECRET_KEY, a dla pełnego odtworzenia także opis CR, konfigurację TLS i dane potrzebne do pobrania projektów. Operator ma zasoby AWXBackup i AWXRestore, ale ich pola zależą od wydania. Stosujemy więc instrukcję dokładnie dla użytej wersji operatora, np. backup i restore dla laboratoryjnego 2.19.1, oraz porównujemy ją z oficjalną dokumentacją aktualizacji. Nie kopiujemy przykładowego CR backupu z innej wersji bez walidacji serwerowej.

Praktyczny cykl wygląda tak:

  1. Utwórz backup i wyeksportuj go poza maszynę.
  2. Sprawdź sumę kontrolną archiwum.
  3. Odtwórz backup na oddzielnym, niepublicznym środowisku testowym.
  4. Zaloguj się, sprawdź odczyt poświadczeń, synchronizację projektu i jedno kontrolowane zadanie.
  5. Dopiero potem zmieniaj operator lub AWX na produkcji.

Co jest rollbackiem, a co nim nie jest?

Przy aktualizacji traktujemy zmianę operatora i zmianę obrazu AWX jako jedną zaplanowaną operację. Przed nią wykonujemy backup, odtwarzamy go na odizolowanym środowisku i uruchamiamy ten sam job SSH, który sprawdziliśmy wcześniej. Dopiero wtedy zmieniamy tagi na środowisku docelowym.

Aktualizację wykonujemy przez AWX Operator na Kubernetesie, czyli w tym samym modelu, w którym powstała instancja. Dla instalacji zbudowanych inaczej nie zakładamy, że istnieje identyczna ścieżka podniesienia wersji. Przy zaległości kilku wydań sprawdzamy release notes każdego kroku; nie zakładamy, że dowolny skok wersji jest bezpieczny.

Ponowne zastosowanie starego manifestu nie jest potwierdzonym rollbackiem. Aktualizacja może obejmować migrację danych i zmianę schematu. Jeżeli nowa wersja nie przejdzie testów, bezpieczną drogą jest odtworzenie sprawdzonego backupu na poprzedniej, odizolowanej instancji, a potem świadome przełączenie ruchu. Nie kasujemy PVC ani sekretów „aby spróbować od nowa”, dopóki nie mamy zweryfikowanej kopii poza hostem.

Przy aktualizacji pamiętaj o domyślnym auto_upgrade: true opisanym wyżej. Podbicie operatora bez świadomej decyzji o wersji aplikacji potrafi zmienić obie warstwy naraz, a to jest dokładnie ten moment, w którym najbardziej przydaje się sprawdzony backup.

Jeżeli przenosisz AWX na inną maszynę, nie uruchamiaj jednocześnie harmonogramów, webhooków ani automatycznych synchronizacji inventory na źródle i na odtworzonej kopii. Najpierw zatrzymaj źródło, poczekaj na zakończenie zadań, wykonaj finalny backup, przełącz DNS lub reverse proxy, zweryfikuj cel i dopiero wtedy włącz harmonogramy na celu. Odtworzona kopia ma własną historię i bez tej kolejności potrafi wykonać tę samą automatyzację drugi raz.

Jeszcze jedno pole warto znać przed odtwarzaniem. projects_persistence ma w CRD wartość domyślną false, co oznacza, że katalog projektów nie jest trwały, a projekty typu SCM pobiorą swój kod ponownie po odtworzeniu. To zachowanie normalne i pożądane. Projekty ręczne oraz lokalne pliki wymagają natomiast osobnej procedury kopiowania - nie odtworzą się same.

Kiedy jeden węzeł przestaje wystarczać?

  1. Jeden węzeł to jeden punkt awarii. local-path wiąże dane z dyskiem tej maszyny. Kubernetes nie przeniesie ich sam na inny węzeł, a jednowęzłowy klaster nie daje żadnej redundancji warstwy sterującej.
  2. Wersje operatora i AWX to dwa różne cykle wydawnicze. Kombinacja z tego artykułu została sprawdzona na jednej wersji k3s. Inna wersja Kubernetesa, inne wydanie operatora albo inny obraz bazy to nowy test, a nie kontynuacja tego samego.
  3. Podmiana obrazu kube-rbac-proxy jest obejściem, nie stanem docelowym. Trzymamy ją jawnie w Kustomize właśnie po to, żeby przy kolejnym wydaniu operatora sprawdzić, czy nadal jest potrzebna.
  4. Wynik /api/v2/ping/ nie jest testem automatyzacji. Potwierdza web i API. Kolejkę, execution environment i łączność z hostami potwierdza wyłącznie wykonane zadanie.
  5. Jedna maszyna to jedna pula zasobów. Zadania dostają wprawdzie własne pody, ale procesor i pamięć dzielą z samym AWX. Przy większym obciążeniu pierwszym krokiem jest dołożenie węzła, a nie strojenie ustawień AWX.
  6. Hardening samego klastra jest osobnym tematem. Ten materiał kończy się na wystawieniu AWX po HTTPS. Polityki sieciowe, ograniczenia podów, dostęp do serwera API i rotacja poświadczeń wymagają własnego procesu.
  7. Historia zadań to dane operacyjne. Zawiera informacje o tym, co i gdzie było uruchamiane. Zasady jej retencji i dostępu ustala się razem z resztą polityki bezpieczeństwa, a nie przy okazji sprzątania bazy.

Po czym poznać, że można udostępnić AWX zespołowi?

  1. Wygenerowany manifest operatora zawiera oba oczekiwane obrazy, w tym działającą wersję kube-rbac-proxy.
  2. Manifest AWX przeszedł kubectl apply --dry-run=server wobec CRD zainstalowanego w klastrze.
  3. kubectl -n awx get awx,pods,svc,ingress,pvc nie pokazuje problemów, a PVC bazy jest w stanie Bound.
  4. AWX jest za HTTPS, a nie za publicznym NodePortem po HTTP.
  5. Zapytanie na port 80 zwraca przekierowanie, a nie stronę aplikacji.
  6. DNS wskazuje właściwy adres, a certyfikat zawiera tę nazwę.
  7. Hasło administratora i SECRET_KEY są w menedżerze sekretów i w backupie, ale nie w repozytorium.
  8. Pierwsze zadanie wykonało się na dedykowanym hoście testowym, a nie na produkcji.
  9. Poświadczenia Machine są rozdzielone przynajmniej między test i produkcję.
  10. Job templates używają jawnie zadeklarowanego środowiska wykonawczego, nie niekontrolowanego latest.
  11. auto_upgrade ma wartość ustawioną świadomie, a nie domyślną przez przeoczenie.
  12. Backup i odtworzenie zostały przećwiczone poza produkcją.
  13. Instalacja jest oparta na awx-operator, czyli ma wspieraną ścieżkę aktualizacji, a plan podniesienia wersji uwzględnia wersje pośrednie.
  14. Harmonogramy, webhooki i synchronizacje inventory mają po migracji jednego aktywnego właściciela.
  15. Konta zwykłych użytkowników istnieją, a admin nie jest kontem roboczym.

AWX zwraca zainwestowany czas dopiero wtedy, gdy przestaje być kolejnym miejscem do ręcznego klikania. Wartość bierze się z zadeklarowanych wersji, jawnie wskazanego środowiska wykonawczego, rozdzielonych poświadczeń i sprawdzonego odtworzenia - a nie z tego, że panel się otworzył. Dlatego każdy krok w tym materiale ma przypisany sposób weryfikacji, a to, czego nie udało się zmierzyć, jest w nim nazwane wprost.

Potrzebujesz AWX, który udźwignie pracę zespołu?

Projektujemy instalację, deklarujemy wersje, zamykamy dostęp za HTTPS i ustawiamy backup razem z odtworzeniem - a potem sprawdzamy to jednym kontrolowanym zadaniem.