Runtime-omgevingen (RTE)
Wat een RTE is, hoe ze een playbook draait, en waar ze aan mag komen
Voor beheerders: begrijp de RTE voordat je er een draait, haar dimensioneert of naar een ander netwerk verplaatst.
| Pagina | Wat er aan bod komt |
|---|---|
| Images | De drie RTE-images, en je eigen image bouwen met de collecties die je playbooks gebruiken |
| Modi | Uitgaand, inkomend of database: wie de verbinding opent, de spool, de bestanden van de job |
| Een RTE draaien | De instellingen van elke modus, en van elke een voorbeeld met docker run |
| Meerdere RTE’s | Capaciteit, replica’s achter één adres, en wat er gebeurt als een RTE stopt of uitvalt |
| Een ander datacenter | Een runbook: een RTE toevoegen in een tweede datacenter, van een kale host tot de eerste job |
Wat een RTE is
AnsibleForms 7 draait geen playbook in de app-container. De job van een ansible-formulier wordt overgedragen aan een runner van het type rte, een runtime-omgeving: de AnsibleForms-server gestart met AF_ROLE=rte, naast python en ansible, zonder webinterface. Ze praat met de app over één WebSocket en bevat geen databasereferenties (RTE_MODE outbound of inbound, zie Modi): de app stuurt haar de job, de RTE draait ansible-playbook, en de app slaat de uitvoer en de eindstatus op die ze terugstuurt, waar de browser ze leest.
De app lost de referenties van de job op en geeft ze aan de RTE, verzegeld voor alleen die RTE en die job. De RTE leest nooit de tabel met referenties of een secret store, en een playbook erft de eigen omgevingsvariabelen van AnsibleForms niet (ENCRYPTION_SECRET, DB_*, RTE_TOKEN en de andere): geef zo’n waarde door als extravar of als referentie. De RTE van 7.0 tot en met 7.2, die zelf de database las en schreef, draait in 7.x nog als de verouderde databasemodus (zie Databasemodus).
Hoe de RTE draait
De RTE is een container die je zelf draait, naast de app of in een ander netwerk: AnsibleForms maakt er nooit een aan. Hieronder zie je waar ze draait, wat ze met de app deelt, en wat er binnenin gebeurt vanaf het moment dat een formulier wordt gestart tot het proces van het playbook verdwenen is:
Het diagram toont één socket per RTE, welke kant hem ook opende: de app belt een inkomende RTE, een uitgaande RTE belt de app (zie Modi). Zodra hij open is, gaan dezelfde berichten beide kanten op, en alleen de app leest en schrijft de database.
Wie haar start. Een RTE is een langdraaiende container, gestart door wat je andere containers ook draait. Ze wacht op jobs en blijft tussendoor draaien:
| Hoe je AnsibleForms draait | Wie de RTE start |
|---|---|
docker run of podman run | Jij, met een opdracht zoals die in Een RTE draaien, op elke host die de app bereikt, of die de app bereikt |
| Docker Compose | De service rte van docker-compose.yml, naast app en mysqldb. De configuratie-seed declareert haar als de standaardrunner rte, inkomend, op http://rte:8000 |
| Helm-chart | Kubernetes, als tweede container met de naam rte in de pod van de app (containers.rte). De app belt haar op 127.0.0.1, inkomend, en de seed van de chart declareert haar als de standaardrunner |
| Het installatieprogramma | Docker Compose of de Helm-chart zoals hierboven, of een systemd-service zonder containers; een eigen Runner, die vraagt om lid te worden en wacht op de goedkeuring van een admin |
De container-engine is van jou: Docker of Podman op de host, of de container-runtime van je Kubernetes-cluster, door jou geïnstalleerd en bijgehouden. AnsibleForms praat er nooit mee. Noch de app noch de RTE roept Docker of Podman aan, koppelt hun socket, of start een container: de app praat alleen met de RTE over haar socket.
Wat erin zit. Eén container, met wat nodig is om een playbook te draaien en niets meer:
- De server : dezelfde Node.js-server als de app, gestart met
AF_ROLE=rte(ingesteld in de image) ondertini - Weggelaten : de webinterface, de aanmelding, de planner, de worker en elke databasemodule, die deze rol niet laadt
- Waarop ze luistert : haar
PORT(8000):/liveen/readyvoor probes en/metricsvoor Prometheus, en bij inkomend de socket die de app belt,/rte/v1/socket. Een uitgaande RTE neemt geen verbinding van de app aan - Python en ansible : meegebracht door de imagelaag die je kiest (zie De RTE-images)
- De gebruiker : de server en elk playbook draaien als
node(uid 1000), nooit als root
Wat ze met de app deelt. Haar token, en met RTE_FILES=local de bestanden van de app. Met RTE_FILES=transfer, de standaard, komen de playbooks en inventories met elke job mee, en heeft de RTE geen gedeelde schijf nodig:
| Gedeeld met de app | Wat de RTE ermee doet |
|---|---|
RTE_TOKEN | Presenteert het als ze de app belt (uitgaand), of controleert het als de app haar belt (inkomend). De app bewaart dezelfde waarde in de RTE-referentie van de runner, en de geheimen van de job worden ermee verzegeld |
Het persistente volume, /app/dist/persistent, alleen met RTE_FILES=local | De playbookmap (ANSIBLE_PATH), of de playbookrepository die de worker onder REPO_PATH heeft gekloond, de inventories (INVENTORY_PATH en de inventoryrepositories) en de uploads. De RTE schrijft de extravars-bestanden van de job in de playbookmap, dus koppel die met lees- en schrijfrechten |
De ssh-map, /home/node/.ssh | De privésleutel en de known_hosts die de ssh-verbindingen van het playbook gebruiken: koppel die van de app, beheerd op de instellingenpagina’s, of geef de RTE een eigen |
De RTE kloont niets: een repository bereikt haar in de bestanden van een job, of op het volume dat ze met de app deelt. Collecties, rollen en ansible.cfg komen uit de image, uit de mappen /etc/ansible/collections, /etc/ansible/roles en /etc/ansible/ansible.cfg als je ze daar koppelt (het Docker Compose-project doet dat), of uit een ansible.cfg naast de playbooks.
Het leven van een job. De nummers hieronder zijn de nummers in het diagram, voor een playbookformulier dat vanuit de browser of de API wordt gestart:
- Starten : de app controleert het formulier en zijn waarden, bouwt de extravars en slaat de job op met status
running. Een formulier met een goedkeuring stopt hier totdat een goedkeurder het accepteert, en bereikt daarvoor geen RTE - De runner kiezen : de app neemt de RTE die het formulier noemt met
runner: <name>, anders de standaardrunner van het type RTE, en slaat de naam ervan op de job op. Ze lost de referenties van de job op uit de database of een secret store, de ansible-aanmelding en het vault-wachtwoord, en verzegelt ze met AES-256-GCM onder een sleutel die is afgeleid van het token van die runner en het job-id - Overdracht : de app-node die de socket van de RTE vasthoudt, stuurt haar de job: de extravars, de opties van het formulier, de verzegelde geheimen en de hashes van de bestanden van de job. Elke app-node mag de job starten; degene die de socket vasthoudt, stuurt hem binnen twee seconden. Een volle RTE, of een die niet verbonden is, laat de job wachten, tot
RTE_QUEUE_MINUTES - Claimen : de RTE antwoordt
claimed, ofrefusedals ze vol is, stopt, de geheimen niet kan openen, of de job met een ander contract komt dan het hare. MetRTE_FILES=transferhaalt ze de playbooks en de inventories op hun hash uit haar cache, vraagt de app om een hash die ze niet heeft, en pakt ze uit in een eigen map van de job - Starten van het proces : in de playbookmap (plus de
playbookSubPathvan het formulier, die moet bestaan) schrijft ze twee bestanden:extravars_<id>.jsonmet de waarden van het formulier en de referenties, en een verborgen bestand met de ansible-aanmelding. Daarna start zeansible-playbookals haar eigen kindproces, zonder shell, in een eigen procesgroep, met-e @die bestanden, één-iper inventory, en de tags, limit, check, diff en verbosity van het formulier. Het vault-wachtwoord gaat via stdin naar binnen, nooit in een bestand of op de opdrachtregel, en het proces draait alsnodemet een gefilterde omgeving (zie hieronder) - Draaien : het playbook maakt vanuit de RTE-container verbinding met de beheerde hosts, via ssh met de sleutel en de
known_hostsvan/home/node/.ssh, metsshpassvoor een aanmelding met wachtwoord, of via de API’s die de modules aanroepen. De RTE, niet de app, moet ze kunnen bereiken - Uitvoer en einde : de RTE bundelt stdout en stderr elke halve seconde of per 64 KB, met de referenties gemaskeerd, en nummert elke bundel. Ze schrijft elk item naar de spool van de job op haar schijf voordat ze het verstuurt: de uitvoer, de events, het joblog van
.joblogs/job_log_<id>.log(elke 2 seconden), de start en het einde met de eindstatus (success,failedofaborted). De app slaat elk item één keer op en bevestigt het, en de RTE gooit de spool weg zodra het einde is opgeslagen. BovenPROCESS_MAX_BUFFERbytes op een stream (standaard 50 MB) stuurt ze niets meer, en gaat het playbook door. De app verstuurt dan de meldingen van het formulier - Volgen : de browser praat nooit met de RTE: de formulierpagina leest de job elke 2 seconden opnieuw via de app, en de pagina Jobs volgt hem live terwijl de app elk item opslaat
Een verbroken socket. Het playbook gaat door. De RTE belt opnieuw (uitgaand), of een app-node belt haar opnieuw (inkomend), en stuurt opnieuw wat de app nog niet had bevestigd: de app slaat over wat ze al had opgeslagen, zodat geen regel verloren gaat of twee keer wordt opgeslagen. Dat geldt zolang de socket binnen twee minuten terug is: de app schrijft de heartbeat van de RTE alleen zolang haar socket open is, en zodra die heartbeat twee minuten oud is, beëindigt de worker de lopende jobs van de RTE als verlaten, ook als hun playbooks nog draaien. Houd de spool (RTE_SPOOL_PATH) op een volume, zodat een herstarte RTE nog verstuurt wat ze had bewaard. Een playbook dat draaide toen zijn RTE herstartte, kan niet doorgaan: de app beëindigt zijn job als verlaten wanneer de RTE zonder hem terugkomt.
Afbreken. Een afbreking zet de afbreekvlag van de job, en de app-node die de socket van de RTE vasthoudt, stuurt binnen twee seconden cancel. De RTE stuurt SIGTERM naar de procesgroep van het playbook, zodat ansible-playbook en elke fork die het startte samen stoppen, en de job eindigt als aborted. Een cancel die vóór zijn job aankomt, wordt een minuut bewaard, en de job start dan afgebroken.
Opruimen. Zodra het proces eindigt, wat de uitkomst ook is, verwijdert de RTE beide extravars-bestanden (een formulier met keepExtravars houdt het zichtbare, nooit het verborgen), het artifactsbestand in /tmp, het joblogbestand en de eigen map met bestanden van de job. Het einde wordt één keer opgeslagen: een tweede schrijver, zoals de app die een job laat mislukken die de RTE al had beëindigd, verandert niets.
Eén proces per job, niet één container per job. AWX en AAP starten voor elke job een nieuwe pod of container vanuit een execution environment-image. AnsibleForms start niets: de RTE-container draait al, en elke job is een kindproces daarin. Een job start zonder een image te pullen of een pod te schedulen, en AnsibleForms heeft geen rechten op je container-engine of cluster nodig. Daar staat tegenover dat de playbooks die op één RTE draaien haar CPU, geheugen, /tmp, thuismap en python delen: geef de container genoeg resources voor RTE_MAX_JOBS playbooks met hun forks. Wijzig de image om te veranderen wat een playbook kan gebruiken, niet de job.
De beveiligingsgrens
De RTE draait code die formulierauteurs hebben geschreven, dus ze is gebouwd om zo weinig mogelijk te bevatten:
- Alleen haar token : de RTE bevat geen databasereferenties en geen
ENCRYPTION_SECRET. Ze neemt geen aanroep aan behalve de socket van de app, met haar token, en heeft geen gebruikers, geen aanmelding en geen webpagina’s. Over de socket kan ze alleen de jobs draaien die de app haar stuurt, erover rapporteren en om hun bestanden vragen: ze kan geen job aanmaken, geen andere job lezen en geen referenties uitdelen - Verzegelde geheimen : om een job te draaien leest de RTE nooit de tabel met referenties of een secret store. Ze krijgt de geheimen van de jobs die ze draait, verzegeld voor alleen haar token en dat job-id, zodat een bundel die voor een andere RTE of een andere job bedoeld is niet opengaat, en een gewijzigde byte wordt geweigerd
- Een gecontroleerde verbinding : bij uitgaand controleert de RTE het certificaat van de app. Bij inkomend via HTTPS pint de app het eigen certificaat van de RTE wanneer die lid wordt, en belt haar alleen als ze dat certificaat presenteert (zie Modi). Een inkomende RTE via gewone HTTP hoort op een privénetwerk, zoals dat van het Docker Compose-project
- Geen overgeërfde omgeving :
ansible-playbookkrijgt de omgeving van de RTE zonder elke variabele die AnsibleForms zelf leest (ENCRYPTION_SECRET,DB_PASSWORD,RTE_TOKEN,VAULT_TOKEN,ACCESS_TOKEN_SECRET, deMYSQL_*-variabelen en de andere), zodatlookup('env', ...)ze niet kan bereiken. Wat je voor playbooks instelt (PATH, proxy’s,ANSIBLE_*) gaat ongewijzigd door - Geen templates van gebruikers : een formulierwaarde die
{{,{%of{#bevat, bereikt het playbook als een unsafe string die ansible nooit templatet, tenzij het formulierallowJinjaInExtravarsinstelt, en geen enkele shell draait ooit de opdrachtregel - Niet root : de RTE en haar playbooks draaien als
node(uid 1000), dus een playbook kan doen wat die gebruiker in de container kan, zoals haar bestanden en de ssh-sleutel lezen, en niets meer
Behandel haar container, haar image en haar omgeving met dezelfde zorg als die van de app.
Databasemodus (verouderd)
De RTE van 7.0 tot en met 7.2, zonder RTE_MODE, draait door heel 7.x en wordt in 8.0.0 verwijderd. Ze verschilt op vier punten van de stappen hierboven:
- Ze heeft de database : haar
DB_*-instellingen, enENCRYPTION_SECRETals ze zichzelf registreert (RTE_REGISTER) - De overdracht is een HTTP-aanroep : de app roept
POST <address>/rte/v1/jobsaan met het token en de verzegelde geheimen, en de RTE claimt de job door haar naam erop te schrijven in de database (stappen 3 en 4) - Ze schrijft de uitvoer zelf : in de database, in bundels, zonder spool (stap 7). Een afbreking bereikt haar via de afbreekvlag van de job, die ze elke 2 seconden leest, en een aanroep
/cancel - Ze leest de bestanden van het volume van de app : ze heeft dezelfde schijf nodig als de app, zoals bij
RTE_FILES=local
Haar runnerpagina heeft geen van de RTE-instellingen: ze leest haar eigen variabelen. Zie Modi om haar naar een socketmodus te verplaatsen.