Ambientes de execução (RTE)
O que é um RTE, como corre um playbook, e em que pode tocar
Para administradores: perceba o RTE antes de correr um, de o dimensionar, ou de o mudar para outra rede.
| Página | O que abrange |
|---|---|
| Imagens | As três imagens do RTE, e construir a sua com as collections que os seus playbooks usam |
| Modos | Saída, entrada ou base de dados: quem abre a ligação, o spool, os ficheiros da tarefa |
| Correr um RTE | As definições de cada modo, e um exemplo docker run de cada um |
| Vários RTE | Capacidade, réplicas atrás de um endereço, e o que acontece quando um RTE para ou morre |
| Outro centro de dados | Um runbook: adicionar um RTE num segundo centro de dados, de um host vazio até à sua primeira tarefa |
O que é um RTE
O AnsibleForms 7 não corre nenhum playbook no contentor da aplicação. A tarefa de um formulário ansible é entregue a um runner do tipo rte, um ambiente de execução: o servidor do AnsibleForms arrancado com AF_ROLE=rte, ao lado de python e ansible, sem interface web. Fala com a aplicação por um WebSocket e não tem credenciais da base de dados (RTE_MODE outbound ou inbound, consulte Modos): a aplicação envia-lhe a tarefa, o RTE corre ansible-playbook, e a aplicação guarda a saída e o estado final que ele devolve, onde o navegador os lê.
A aplicação resolve as credenciais da tarefa e entrega-as ao RTE seladas só para esse RTE e essa tarefa. O RTE nunca lê a tabela de credenciais nem um cofre de segredos, e um playbook não herda as variáveis de ambiente do próprio AnsibleForms (ENCRYPTION_SECRET, DB_*, RTE_TOKEN e as outras): passe um valor desses como extravar ou como credencial. O RTE da 7.0 à 7.2, que lia e escrevia ele próprio na base de dados, continua a correr na 7.x como o modo base de dados obsoleto (consulte Modo base de dados).
Como o RTE corre
O RTE é um contentor que corre você mesmo, ao lado da aplicação ou noutra rede: o AnsibleForms nunca cria nenhum. Isto percorre onde corre, o que partilha com a aplicação, e o que acontece dentro dele desde o momento em que um formulário é lançado até o processo do playbook desaparecer:
O diagrama mostra um socket por RTE, seja qual for o lado que o abriu: a aplicação liga-se a um RTE de entrada, um RTE de saída liga-se à aplicação (consulte Modos). As mesmas mensagens circulam nos dois sentidos depois de aberto, e só a aplicação lê e escreve na base de dados.
Quem o arranca. Um RTE é um contentor de longa duração, arrancado pelo que quer que corra os seus outros contentores. Espera por tarefas e continua a correr entre elas:
| Como corre o AnsibleForms | Quem arranca o RTE |
|---|---|
docker run ou podman run | Você, com um comando como o de Correr um RTE, em qualquer host que chegue à aplicação, ou a que a aplicação chegue |
| Docker Compose | O serviço rte do docker-compose.yml, ao lado de app e mysqldb. O seu config seed declara-o como o runner predefinido rte, de entrada, em http://rte:8000 |
| Helm chart | O Kubernetes, como um segundo contentor chamado rte no pod da aplicação (containers.rte). A aplicação liga-se a ele em 127.0.0.1, de entrada, e o seed do chart declara-o como o runner predefinido |
| O instalador | O Docker Compose ou o Helm chart como acima, ou um serviço systemd sem contentores; um Runner próprio, que pede para aderir e espera pela aprovação de um admin |
O motor de contentores é seu: Docker ou Podman no host, ou o runtime de contentores do seu cluster Kubernetes, instalado e mantido atualizado por si. O AnsibleForms nunca fala com ele. Nem a aplicação nem o RTE chamam o Docker ou o Podman, montam o seu socket, ou arrancam um contentor: a aplicação só fala com o RTE pelo socket dele.
O que tem dentro. Um contentor, com o necessário para correr um playbook e nada mais:
- O servidor : o mesmo servidor Node.js que a aplicação, arrancado com
AF_ROLE=rte(definido na imagem) sobtini - O que fica de fora : a interface web, o início de sessão, o agendador, o worker e todos os módulos de base de dados, que esta função não carrega
- Onde está à escuta : a sua
PORT(8000):/livee/readypara as sondas e/metricspara o Prometheus, e, em modo de entrada, o socket a que a aplicação se liga,/rte/v1/socket. Um RTE de saída não aceita nenhuma ligação da aplicação - Python e ansible : trazidos pela camada de imagem que escolher (consulte As imagens do RTE)
- O utilizador : o servidor e todos os playbooks correm como
node(uid 1000), nunca como root
O que partilha com a aplicação. O seu token, e com RTE_FILES=local os ficheiros da aplicação. Com RTE_FILES=transfer, a predefinição, os playbooks e os inventários chegam com cada tarefa, e o RTE não precisa de disco partilhado:
| Partilhado com a aplicação | O que o RTE faz com isso |
|---|---|
RTE_TOKEN | Apresenta-o quando se liga à aplicação (saída), ou verifica-o quando a aplicação se liga a ele (entrada). A aplicação tem o mesmo valor na credencial RTE do runner, e os segredos da tarefa são selados com ele |
O volume persistente, /app/dist/persistent, só com RTE_FILES=local | A pasta dos playbooks (ANSIBLE_PATH), ou o repositório de playbooks que o worker clonou em REPO_PATH, os inventários (INVENTORY_PATH e os repositórios de inventário) e os ficheiros carregados. O RTE escreve os ficheiros de extravars da tarefa na pasta do playbook, por isso monte-a em leitura e escrita |
A pasta ssh, /home/node/.ssh | A chave privada e o known_hosts que as ligações ssh do playbook usam: monte a da aplicação, gerida nas suas páginas de definições, ou dê ao RTE a sua própria |
O RTE não clona nada: um repositório chega-lhe nos ficheiros de uma tarefa, ou no volume que partilha com a aplicação. As collections, os roles e o ansible.cfg vêm da imagem, das pastas /etc/ansible/collections, /etc/ansible/roles e /etc/ansible/ansible.cfg quando os monta aí (o projeto Docker Compose fá-lo), ou de um ansible.cfg ao lado dos playbooks.
A vida de uma tarefa. Os números abaixo são os números do diagrama, para um formulário de playbook lançado a partir do navegador ou da API:
- Lançamento : a aplicação verifica o formulário e os seus valores, constrói os extravars e guarda a tarefa com o estado
running. Um formulário com uma aprovação para aqui até um aprovador a aceitar, e não chega a nenhum RTE antes disso - Escolher o runner : a aplicação fica com o RTE que o formulário nomeia com
runner: <name>, senão com o runner predefinido do tipo RTE, e guarda o seu nome na tarefa. Resolve as credenciais da tarefa a partir da base de dados ou de um cofre de segredos, o seu acesso ansible e a sua palavra-passe de vault, e sela-os com AES-256-GCM sob uma chave derivada do token desse runner e do id da tarefa - Entrega : o nó da aplicação que tem o socket do RTE envia-lhe a tarefa: os extravars, as opções do formulário, os segredos selados e os hashes dos ficheiros da tarefa. Qualquer nó da aplicação pode lançar a tarefa; o que tem o socket envia-a em dois segundos. Um RTE cheio, ou um que não está ligado, faz a tarefa esperar, até
RTE_QUEUE_MINUTES - Reclamação : o RTE responde
claimed, ourefusedquando está cheio, a parar, não consegue abrir os segredos, ou a tarefa chega com outro contrato que não o seu. ComRTE_FILES=transfervai buscar os playbooks e os inventários à sua cache pelo seu hash, pede à aplicação um hash que não tem, e desempacota-os numa pasta própria da tarefa - Arranque : na pasta do playbook (mais o
playbookSubPathdo formulário, que tem de existir) escreve dois ficheiros:extravars_<id>.jsoncom os valores do formulário e as credenciais, e um oculto com o acesso ansible. Depois arrancaansible-playbookcomo seu próprio processo filho, sem shell, num grupo de processos próprio, com-e @esses ficheiros, um-ipor inventário, e as tags, o limit, o check, o diff e a verbosidade do formulário. A palavra-passe de vault entra por stdin, nunca num ficheiro nem na linha de comandos, e o processo corre comonodecom um ambiente filtrado (ver abaixo) - Execução : o playbook liga-se aos hosts geridos a partir do contentor do RTE, por ssh com a chave e o
known_hostsde/home/node/.ssh, comsshpasspara um acesso por palavra-passe, ou pelas APIs que os seus módulos chamam. É o RTE, não a aplicação, que precisa de chegar a eles - Saída e fim : o RTE agrupa o stdout e o stderr a cada meio segundo ou 64 KB, com as credenciais mascaradas, e numera cada lote. Escreve cada item no spool da tarefa no seu disco antes de o enviar: a saída, os eventos, o registo da tarefa de
.joblogs/job_log_<id>.log(a cada 2 segundos), o início e o fim com o estado final (success,failedouaborted). A aplicação guarda cada item uma vez e confirma a sua receção, e o RTE descarta o spool quando o fim fica guardado. Para além dePROCESS_MAX_BUFFERbytes num fluxo (50 MB por predefinição) não envia mais, e o playbook continua. A aplicação envia depois as notificações do formulário - Acompanhamento : o navegador nunca fala com o RTE: a página do formulário volta a ler a tarefa através da aplicação a cada 2 segundos, e a página Tarefas acompanha-a em direto à medida que a aplicação guarda cada item
Um socket que cai. O playbook continua. O RTE volta a ligar-se (saída), ou um nó da aplicação liga-se de novo a ele (entrada), e reenvia o que a aplicação ainda não tinha confirmado: a aplicação salta o que já guardou, por isso nenhuma linha se perde nem é guardada duas vezes. Isto vale enquanto o socket voltar em dois minutos: a aplicação só escreve o heartbeat do RTE enquanto o seu socket está aberto, e quando esse heartbeat tem dois minutos o worker termina as tarefas em execução do RTE como abandonadas, mesmo que os seus playbooks continuem a correr. Mantenha o spool (RTE_SPOOL_PATH) num volume, para que um RTE reiniciado continue a enviar o que tinha guardado. Um playbook que estava a correr quando o seu RTE reiniciou não pode continuar: a aplicação termina a sua tarefa como abandonada quando o RTE volta sem ela.
Abortar. Abortar marca a tarefa como abortada, e o nó da aplicação que tem o socket do RTE envia cancel em dois segundos. O RTE envia SIGTERM ao grupo de processos do playbook, por isso o ansible-playbook e todos os forks que arrancou param juntos, e a tarefa termina aborted. Um cancel que chega antes da sua tarefa é guardado durante um minuto, e a tarefa arranca então já abortada.
Limpeza. Assim que o processo termina, seja qual for o resultado, o RTE remove os dois ficheiros de extravars (um formulário com keepExtravars mantém o visível, nunca o oculto), o ficheiro de artefactos em /tmp, o ficheiro de registo da tarefa e a pasta de ficheiros da própria tarefa. O fim é guardado uma vez: um segundo escritor, como a aplicação a falhar uma tarefa que o RTE já terminou, não altera nada.
Um processo por tarefa, não um contentor por tarefa. O AWX e o AAP arrancam um pod ou contentor novo a partir de uma imagem de execution environment para cada tarefa. O AnsibleForms não arranca nada: o contentor do RTE já está a correr, e cada tarefa é um processo filho dentro dele. Uma tarefa arranca sem descarregar uma imagem nem agendar um pod, e o AnsibleForms não precisa de direitos no seu motor de contentores nem no seu cluster. Em troca, os playbooks que correm num RTE partilham o seu CPU, a sua memória, /tmp, a pasta pessoal e o python: dê ao contentor recursos suficientes para RTE_MAX_JOBS playbooks com os seus forks. Para mudar o que um playbook pode usar, mude a imagem, não a tarefa.
A fronteira de segurança
O RTE corre código escrito pelos autores dos formulários, por isso foi construído para conter o mínimo possível:
- Só o seu token : o RTE não tem credenciais da base de dados nem
ENCRYPTION_SECRET. Não aceita nenhuma chamada além do socket da aplicação, com o seu token, e não tem utilizadores, nem início de sessão, nem páginas web. Pelo socket só pode correr as tarefas que a aplicação lhe envia, informar sobre elas e pedir os seus ficheiros: não pode criar uma tarefa, ler outra tarefa nem distribuir credenciais - Segredos selados : para correr uma tarefa, o RTE nunca lê a tabela de credenciais nem um cofre de segredos. Recebe os segredos das tarefas que corre, selados só para o seu token e para esse id de tarefa, por isso um pacote destinado a outro RTE ou a outra tarefa não abre, e um byte alterado é recusado
- Uma ligação verificada : em modo de saída, o RTE verifica o certificado da aplicação. Em modo de entrada por HTTPS, a aplicação fixa o próprio certificado do RTE quando este adere, e só se liga a ele quando apresenta esse certificado (consulte Modos). Um RTE de entrada por HTTP simples deve estar numa rede privada, como a do projeto Docker Compose
- Nenhum ambiente herdado :
ansible-playbookrecebe o ambiente do RTE sem nenhuma das variáveis que o próprio AnsibleForms lê (ENCRYPTION_SECRET,DB_PASSWORD,RTE_TOKEN,VAULT_TOKEN,ACCESS_TOKEN_SECRET, asMYSQL_*e as outras), por issolookup('env', ...)não lhes chega. O que definir para os playbooks (PATH, proxies,ANSIBLE_*) passa tal como está - Nenhum template dos utilizadores : um valor de formulário que contenha
{{,{%ou{#chega ao playbook como uma string unsafe que o ansible nunca processa como template, a menos que o formulário definaallowJinjaInExtravars, e nenhuma shell corre alguma vez a linha de comandos - Não é root : o RTE e os seus playbooks correm como
node(uid 1000), por isso um playbook pode fazer o que esse utilizador pode no contentor, como ler os seus ficheiros e a chave ssh, e nada mais
Trate o seu contentor, a sua imagem e o seu ambiente com o mesmo cuidado que os da aplicação.
Modo base de dados (obsoleto)
O RTE da 7.0 à 7.2, sem RTE_MODE, corre durante toda a 7.x e é removido na 8.0.0. Difere dos passos acima em quatro aspetos:
- Tem acesso à base de dados : as suas definições
DB_*, eENCRYPTION_SECRETquando se regista a si próprio (RTE_REGISTER) - A entrega é uma chamada HTTP : a aplicação chama
POST <address>/rte/v1/jobscom o token e os segredos selados, e o RTE reclama a tarefa escrevendo nela o seu nome na base de dados (passos 3 e 4) - Escreve ele próprio a saída : na base de dados, em lotes, sem spool (passo 7). Um abortar chega-lhe pela marca de abortar da tarefa, que lê a cada 2 segundos, e por uma chamada
/cancel - Lê os ficheiros do volume da aplicação : precisa do mesmo disco que a aplicação, como com
RTE_FILES=local
A sua página de runner não tem nenhuma das definições do RTE: lê as suas próprias variáveis. Para o passar para um modo socket, consulte Modos.