Środowiska uruchomieniowe (RTE)
Czym jest RTE, jak uruchamia playbook i czego może dotykać
Dla administratorów: zrozum RTE, zanim je uruchomisz, dobierzesz jego rozmiar lub przeniesiesz do innej sieci.
| Strona | Czego dotyczy |
|---|---|
| Obrazy | Trzy obrazy RTE i budowanie własnego z kolekcjami, których używają Twoje playbooki |
| Tryby | Wychodzący, przychodzący lub bazy danych: kto otwiera połączenie, spool, pliki zadania |
| Uruchamianie RTE | Ustawienia każdego trybu i przykład docker run dla każdego z nich |
| Kilka RTE | Pojemność, repliki za jednym adresem i co się dzieje, gdy RTE się zatrzyma lub padnie |
| Inne centrum danych | Runbook: dodanie RTE w drugim centrum danych, od pustego hosta do pierwszego zadania |
Czym jest RTE
AnsibleForms 7 nie uruchamia żadnego playbooka w kontenerze aplikacji. Zadanie formularza ansible jest przekazywane do runnera typu rte, czyli środowiska uruchomieniowego: serwera AnsibleForms uruchomionego z AF_ROLE=rte, obok pythona i ansible, bez interfejsu WWW. Komunikuje się z aplikacją przez jeden WebSocket i nie ma żadnych poświadczeń do bazy danych (RTE_MODE outbound lub inbound, zobacz Tryby): aplikacja wysyła mu zadanie, RTE uruchamia ansible-playbook, a aplikacja zapisuje wynik i końcowy status, które RTE odsyła, i stamtąd czyta je przeglądarka.
Aplikacja rozwiązuje poświadczenia zadania i przekazuje je RTE zapieczętowane tylko dla tego RTE i tego zadania. RTE nigdy nie czyta tabeli poświadczeń ani magazynu sekretów, a playbook nie dziedziczy zmiennych środowiskowych samego AnsibleForms (ENCRYPTION_SECRET, DB_*, RTE_TOKEN i innych): przekaż taką wartość jako extravar lub poświadczenie. RTE z wersji 7.0 do 7.2, które samo czytało i zapisywało bazę danych, nadal działa w 7.x jako przestarzały tryb bazy danych (zobacz Tryb bazy danych).
Jak działa RTE
RTE to kontener, który uruchamiasz sam, obok aplikacji lub w innej sieci: AnsibleForms nigdy go nie tworzy. Poniżej opisano, gdzie działa, co współdzieli z aplikacją i co dzieje się w nim od chwili uruchomienia formularza, aż proces playbooka zniknie:
Diagram pokazuje jedno gniazdo na RTE, niezależnie od tego, która strona je otworzyła: aplikacja łączy się z RTE w trybie przychodzącym, RTE w trybie wychodzącym łączy się z aplikacją (zobacz Tryby). Gdy gniazdo jest otwarte, te same komunikaty płyną w obie strony, a bazę danych czyta i zapisuje tylko aplikacja.
Kto je uruchamia. RTE to długo działający kontener, uruchamiany przez to, co uruchamia Twoje pozostałe kontenery. Czeka na zadania i działa dalej między nimi:
| Jak uruchamiasz AnsibleForms | Kto uruchamia RTE |
|---|---|
docker run lub podman run | Ty, poleceniem takim jak w Uruchamianie RTE, na dowolnym hoście, który dociera do aplikacji albo do którego dociera aplikacja |
| Docker Compose | Usługa rte w docker-compose.yml, obok app i mysqldb. Jej config seed deklaruje ją jako domyślny runner rte, w trybie przychodzącym, pod http://rte:8000 |
| Chart Helm | Kubernetes, jako drugi kontener o nazwie rte w podzie aplikacji (containers.rte). Aplikacja łączy się z nim na 127.0.0.1, w trybie przychodzącym, a seed chartu deklaruje go jako domyślny runner |
| Instalator | Docker Compose lub chart Helm jak wyżej albo usługa systemd bez kontenerów; własny runner, który prosi o dołączenie i czeka na zatwierdzenie przez admina |
Silnik kontenerów należy do Ciebie: Docker lub Podman na hoście albo środowisko uruchomieniowe kontenerów Twojego klastra Kubernetes, instalowane i aktualizowane przez Ciebie. AnsibleForms nigdy się z nim nie komunikuje. Ani aplikacja, ani RTE nie wywołuje Dockera ani Podmana, nie montuje ich gniazda ani nie uruchamia kontenera: aplikacja komunikuje się z RTE tylko przez jego gniazdo.
Co jest w środku. Jeden kontener, z tym, co potrzebne do uruchomienia playbooka, i niczym więcej:
- Serwer : ten sam serwer Node.js co aplikacja, uruchomiony z
AF_ROLE=rte(ustawionym w obrazie) podtini - Pominięte : interfejs WWW, logowanie, harmonogram, worker i wszystkie moduły bazy danych, których ta rola nie ładuje
- Na czym nasłuchuje : na swoim
PORT(8000):/livei/readydla sond oraz/metricsdla Prometheus, a w trybie przychodzącym gniazdo, z którym łączy się aplikacja,/rte/v1/socket. RTE w trybie wychodzącym nie przyjmuje żadnego połączenia od aplikacji - Python i ansible : dostarczane przez wybraną warstwę obrazu (zobacz Obrazy RTE)
- Użytkownik : serwer i każdy playbook działają jako
node(uid 1000), nigdy jako root
Co współdzieli z aplikacją. Swój token, a przy RTE_FILES=local pliki aplikacji. Przy RTE_FILES=transfer, domyślnym, playbooki i inwentarze przychodzą z każdym zadaniem, a RTE nie potrzebuje współdzielonego dysku:
| Współdzielone z aplikacją | Co RTE z tym robi |
|---|---|
RTE_TOKEN | Przedstawia go, gdy łączy się z aplikacją (tryb wychodzący), lub sprawdza, gdy aplikacja łączy się z nim (tryb przychodzący). Aplikacja ma tę samą wartość w poświadczeniu RTE runnera, a sekrety zadania są nim pieczętowane |
Wolumen trwały, /app/dist/persistent, tylko przy RTE_FILES=local | Folder playbooków (ANSIBLE_PATH) lub repozytorium playbooków sklonowane przez worker w REPO_PATH, inwentarze (INVENTORY_PATH i repozytoria inwentarzy) oraz przesłane pliki. RTE zapisuje pliki extravars zadania w folderze playbooka, więc montuj go do odczytu i zapisu |
Folder ssh, /home/node/.ssh | Klucz prywatny i known_hosts, których używają połączenia ssh playbooka: zamontuj folder aplikacji, zarządzany na jej stronach ustawień, albo daj RTE własny |
RTE niczego nie klonuje: repozytorium dociera do niego w plikach zadania lub na wolumenie współdzielonym z aplikacją. Kolekcje, role i ansible.cfg pochodzą z obrazu, z folderów /etc/ansible/collections, /etc/ansible/roles i z /etc/ansible/ansible.cfg, gdy je tam zamontujesz (projekt Docker Compose to robi), albo z ansible.cfg obok playbooków.
Życie zadania. Poniższe numery to numery z diagramu, dla formularza playbooka uruchomionego z przeglądarki lub przez API:
- Uruchomienie : aplikacja sprawdza formularz i jego wartości, buduje extravars i zapisuje zadanie ze statusem
running. Formularz z zatwierdzeniem zatrzymuje się tutaj, dopóki zatwierdzający go nie zaakceptuje, i wcześniej nie trafia do żadnego RTE - Wybór runnera : aplikacja bierze RTE, które formularz wskazuje przez
runner: <name>, a w przeciwnym razie domyślny runner typu RTE, i zapisuje jego nazwę w zadaniu. Rozwiązuje poświadczenia zadania z bazy danych lub magazynu sekretów, jego login ansible i hasło vault, i pieczętuje je algorytmem AES-256-GCM kluczem wyprowadzonym z tokenu tego runnera i id zadania - Przekazanie : węzeł aplikacji, który trzyma gniazdo RTE, wysyła mu zadanie: extravars, opcje formularza, zapieczętowane sekrety i hashe plików zadania. Zadanie może uruchomić dowolny węzeł aplikacji; ten, który trzyma gniazdo, wysyła je w ciągu dwóch sekund. Pełne RTE albo niepołączone sprawia, że zadanie czeka, do
RTE_QUEUE_MINUTES - Przejęcie : RTE odpowiada
claimedalborefused, gdy jest pełne, się zatrzymuje, nie może otworzyć sekretów lub zadanie przychodzi z innym kontraktem niż jego własny. PrzyRTE_FILES=transferbierze playbooki i inwentarze ze swojej pamięci podręcznej według hasha, prosi aplikację o hash, którego nie ma, i rozpakowuje je w osobnym folderze zadania - Start : w folderze playbooka (plus
playbookSubPathformularza, który musi istnieć) zapisuje dwa pliki:extravars_<id>.jsonz wartościami formularza i poświadczeniami oraz ukryty plik z loginem ansible. Następnie uruchamiaansible-playbookjako własny proces potomny, bez powłoki, we własnej grupie procesów, z-e @tymi plikami, jednym-ina każdy inwentarz oraz tagami, limitem, check, diff i szczegółowością formularza. Hasło vault trafia przez stdin, nigdy do pliku ani do wiersza poleceń, a proces działa jakonodez odfiltrowanym środowiskiem (zobacz niżej) - Wykonanie : playbook łączy się z zarządzanymi hostami z kontenera RTE, przez ssh z kluczem i
known_hostsz/home/node/.ssh, zsshpassdla logowania hasłem, albo przez API wywoływane przez jego moduły. To RTE, a nie aplikacja, musi do nich docierać - Wynik i zakończenie : RTE grupuje stdout i stderr co pół sekundy lub co 64 KB, z zamaskowanymi poświadczeniami, i numeruje każdą paczkę. Każdy element zapisuje w spoolu zadania na swoim dysku, zanim go wyśle: wynik, zdarzenia, dziennik zadania z
.joblogs/job_log_<id>.log(co 2 sekundy), start i zakończenie z końcowym statusem (success,failedlubaborted). Aplikacja zapisuje każdy element raz i potwierdza go, a RTE usuwa spool, gdy zakończenie zostanie zapisane. Po przekroczeniuPROCESS_MAX_BUFFERbajtów na strumieniu (domyślnie 50 MB) nie wysyła już nic, a playbook działa dalej. Następnie aplikacja wysyła powiadomienia formularza - Śledzenie : przeglądarka nigdy nie komunikuje się z RTE: strona formularza co 2 sekundy ponownie odczytuje zadanie przez aplikację, a strona Zadania śledzi je na żywo, w miarę jak aplikacja zapisuje kolejne elementy
Zerwane gniazdo. Playbook działa dalej. RTE łączy się ponownie (tryb wychodzący) albo węzeł aplikacji łączy się z nim ponownie (tryb przychodzący) i wysyła ponownie to, czego aplikacja nie potwierdziła: aplikacja pomija to, co już zapisała, więc żaden wiersz nie ginie ani nie jest zapisany dwukrotnie. Działa to, dopóki gniazdo wróci w ciągu dwóch minut: aplikacja zapisuje sygnał życia RTE tylko wtedy, gdy jego gniazdo jest otwarte, a gdy ten sygnał ma dwie minuty, worker kończy działające zadania RTE jako porzucone, nawet jeśli ich playbooki nadal działają. Trzymaj spool (RTE_SPOOL_PATH) na wolumenie, żeby zrestartowane RTE nadal wysłało to, co zachowało. Playbook, który działał, gdy jego RTE się zrestartowało, nie może kontynuować: aplikacja kończy jego zadanie jako porzucone, gdy RTE wraca bez niego.
Przerwanie. Przerwanie ustawia flagę przerwania zadania, a węzeł aplikacji, który trzyma gniazdo RTE, w ciągu dwóch sekund wysyła cancel. RTE wysyła SIGTERM do grupy procesów playbooka, więc ansible-playbook i każdy fork, który uruchomił, zatrzymują się razem, a zadanie kończy się jako aborted. Anulowanie, które dotrze przed swoim zadaniem, jest przechowywane przez minutę, a zadanie startuje wtedy już przerwane.
Sprzątanie. Gdy tylko proces się zakończy, niezależnie od wyniku, RTE usuwa oba pliki extravars (formularz z keepExtravars zachowuje widoczny, nigdy ukryty), plik artefaktów w /tmp, plik dziennika zadania i osobny folder plików zadania. Zakończenie jest zapisywane raz: drugi zapisujący, np. aplikacja oznaczająca jako nieudane zadanie, które RTE już zakończyło, niczego nie zmienia.
Jeden proces na zadanie, a nie jeden kontener na zadanie. AWX i AAP przy każdym zadaniu uruchamiają nowy pod lub kontener z obrazu execution environment. AnsibleForms niczego nie uruchamia: kontener RTE już działa, a każde zadanie jest w nim procesem potomnym. Zadanie startuje bez pobierania obrazu i planowania poda, a AnsibleForms nie potrzebuje żadnych uprawnień do Twojego silnika kontenerów ani klastra. W zamian playbooki działające na jednym RTE dzielą jego CPU, pamięć, /tmp, folder domowy i pythona: daj kontenerowi wystarczająco zasobów na RTE_MAX_JOBS playbooków z ich forkami. Aby zmienić to, czego może używać playbook, zmień obraz, a nie zadanie.
Granica bezpieczeństwa
RTE uruchamia kod napisany przez autorów formularzy, więc jest zbudowane tak, by przechowywać jak najmniej:
- Tylko jego token : RTE nie ma poświadczeń do bazy danych ani
ENCRYPTION_SECRET. Nie przyjmuje żadnych wywołań poza gniazdem aplikacji, z jego tokenem, i nie ma użytkowników, logowania ani stron WWW. Przez gniazdo może tylko uruchamiać zadania, które wysyła mu aplikacja, raportować o nich i prosić o ich pliki: nie może utworzyć zadania, odczytać innego zadania ani rozdawać poświadczeń - Zapieczętowane sekrety : aby uruchomić zadanie, RTE nigdy nie czyta tabeli poświadczeń ani magazynu sekretów. Dostaje sekrety zadań, które uruchamia, zapieczętowane tylko dla swojego tokenu i id tego zadania, więc paczka przeznaczona dla innego RTE lub innego zadania się nie otwiera, a zmieniony bajt jest odrzucany
- Sprawdzane połączenie : w trybie wychodzącym RTE sprawdza certyfikat aplikacji. W trybie przychodzącym przez HTTPS aplikacja przypina własny certyfikat RTE, gdy ono dołącza, i łączy się z nim tylko wtedy, gdy przedstawia właśnie ten (zobacz Tryby). RTE w trybie przychodzącym przez zwykłe HTTP powinno być w sieci prywatnej, takiej jak sieć projektu Docker Compose
- Bez dziedziczonego środowiska :
ansible-playbookdostaje środowisko RTE bez wszystkich zmiennych, które odczytuje sam AnsibleForms (ENCRYPTION_SECRET,DB_PASSWORD,RTE_TOKEN,VAULT_TOKEN,ACCESS_TOKEN_SECRET, zmienneMYSQL_*i inne), więclookup('env', ...)nie może do nich dotrzeć. To, co ustawisz dla playbooków (PATH, proxy,ANSIBLE_*), przechodzi bez zmian - Bez szablonów od użytkowników : wartość formularza, która zawiera
{{,{%lub{#, trafia do playbooka jako niebezpieczny ciąg (unsafe), którego ansible nigdy nie przetwarza jako szablonu, chyba że formularz ustawiaallowJinjaInExtravars, a żadna powłoka nigdy nie uruchamia wiersza poleceń - Nie jako root : RTE i jego playbooki działają jako
node(uid 1000), więc playbook może robić w kontenerze to, co może ten użytkownik, np. czytać jego pliki i klucz ssh, i nic więcej
Traktuj jego kontener, obraz i środowisko z taką samą starannością jak kontener aplikacji.
Tryb bazy danych (przestarzały)
RTE z wersji 7.0 do 7.2, bez RTE_MODE, działa przez całą serię 7.x i zostanie usunięte w 8.0.0. Różni się od powyższych kroków na cztery sposoby:
- Ma dostęp do bazy danych : swoje ustawienia
DB_*orazENCRYPTION_SECRET, gdy samo się rejestruje (RTE_REGISTER) - Przekazanie to wywołanie HTTP : aplikacja wywołuje
POST <address>/rte/v1/jobsz tokenem i zapieczętowanymi sekretami, a RTE przejmuje zadanie, zapisując w nim swoją nazwę w bazie danych (kroki 3 i 4) - Samo zapisuje wynik : w bazie danych, paczkami, bez spoola (krok 7). Przerwanie dociera do niego przez flagę przerwania zadania, którą odczytuje co 2 sekundy, oraz przez wywołanie
/cancel - Czyta pliki z wolumenu aplikacji : potrzebuje tego samego dysku co aplikacja, jak przy
RTE_FILES=local
Jego strona runnera nie ma żadnych ustawień RTE: odczytuje własne zmienne. Aby przenieść je do trybu gniazda, zobacz Tryby.