Limitar com segurança os manuais Ansible a uma única máquina?

227

Estou usando o Ansible para algumas tarefas simples de gerenciamento de usuários com um pequeno grupo de computadores. Atualmente, tenho meus playbooks definidos hosts: alle meu arquivo de hosts é apenas um único grupo com todas as máquinas listadas:

# file: hosts
[office]
imac-1.local
imac-2.local
imac-3.local

Eu me vejo frequentemente tendo que segmentar uma única máquina. O ansible-playbookcomando pode limitar jogadas como esta:

ansible-playbook --limit imac-2.local user.yml

Mas isso parece meio frágil, especialmente para um manual potencialmente destrutivo. Deixar de fora a limitbandeira significa que o manual seria executado em qualquer lugar. Como essas ferramentas são usadas apenas ocasionalmente, parece que vale a pena tomar medidas para uma reprodução infalível, para que não danifiquemos acidentalmente algo daqui a alguns meses.

Existe uma prática recomendada para limitar a execução de playbooks em uma única máquina? Idealmente, os manuais devem ser inofensivos se algum detalhe importante for deixado de fora.

joemaller
fonte

Respostas:

209

Acontece que é possível inserir um nome de host diretamente no manual, portanto, executar o manual com hosts: imac-2.localele funcionará bem. Mas é meio desajeitado.

Uma solução melhor pode ser definir os hosts do manual usando uma variável e depois passar um endereço de host específico via --extra-vars:

# file: user.yml  (playbook)
---
- hosts: '{{ target }}'
  user: ...

Executando o manual:

ansible-playbook user.yml --extra-vars "target=imac-2.local"

Se {{ target }}não estiver definido, o manual não fará nada. Um grupo do arquivo hosts também pode ser transmitido, se necessário. No geral, essa parece ser uma maneira muito mais segura de criar um manual potencialmente destrutivo.

Playbook segmentado para um único host:

$ ansible-playbook user.yml --extra-vars "target=imac-2.local" --list-hosts

playbook: user.yml

  play #1 (imac-2.local): host count=1
    imac-2.local

Manual com um grupo de hosts:

$ ansible-playbook user.yml --extra-vars "target=office" --list-hosts

playbook: user.yml

  play #1 (office): host count=3
    imac-1.local
    imac-2.local
    imac-3.local

Esquecer de definir hosts é seguro!

$ ansible-playbook user.yml --list-hosts

playbook: user.yml

  play #1 ({{target}}): host count=0
joemaller
fonte
52
Isso é solucionável em 1.5.3 com--limit office[0]
NG.
4
A variável precisa ser citada - ou seja: '{{ target }}'- de acordo com docs.ansible.com/…
Limbo Peng
9
Essa é uma resposta "à prova de falhas", diferente de outras - se você deixar algo de fora, ele não fará nada. Rodar em 'apenas' um host usando o Ansible 1.7 run_onceainda pode ser destrutivo, portanto não é uma boa ideia.
RichVel 01/08/16
4
Se você quiser um comando mais curto, -eé o equivalente a #--extra-vars
William Turrell
1
Se sua configuração ansible exige que os anfitriões não pode estar vazio ou indefinida, em seguida, usando uma variável combinada com filtro funciona um Jinja, tais como:hosts: "{{ target | default('no_hosts')}}"
Zach Weg
178

Há também um pequeno truque que permite especificar um único host na linha de comando (ou vários hosts, eu acho), sem um inventário intermediário:

ansible-playbook -i "imac1-local," user.yml

Observe a vírgula ( , ) no final; isso sinaliza que é uma lista, não um arquivo.

Agora, isso não o protegerá se você passar acidentalmente um arquivo de inventário real, por isso pode não ser uma boa solução para esse problema específico. Mas é um truque útil para saber!

Tybstar
fonte
2
Isso é incrível. Uso regularmente o sinalizador -l, que funciona com o etc / ansible / hosts (que é preenchido usando a API de descoberta do EC2), mas às vezes eu realmente só preciso de uma única máquina. Obrigado!
Vic
3
Esse truque deve usar o arquivo hosts? Estou usando anfitriões como um inventário dinâmico para o nosso sistema AWS EC2 e ele retorna: skipping: no hosts matched. Talvez esse truque não funcione mais desde que --limitfunciona?
perfil completo de hamx0r
1
Este truque não funcionou para mim. Mas isso funcionou: $ ansible-playbook -kK --limit=myhost1 myplaybook.yml. Veja a resposta de Marwan.
Donn Lee
2
Refira-se que para que isso funcione, os anfitriões deve ser definido como allna peça (s) - este Levei um tempo para descobrir ...
Remígio Stalder
83

Essa abordagem será encerrada se mais de um host for fornecido, verificando a variável play_hosts . O módulo de falha é usado para sair se a condição de host único não for atendida. Os exemplos abaixo usam um arquivo de hosts com dois hosts alice e bob.

user.yml (manual)

---
- hosts: all
  tasks:
    - name: Check for single host
      fail: msg="Single host check failed."
      when: "{{ play_hosts|length }} != 1"
    - debug: msg='I got executed!'

Executar playbook sem filtros de host

$ ansible-playbook user.yml
PLAY [all] ****************************************************************
TASK: [Check for single host] *********************************************
failed: [alice] => {"failed": true}
msg: Single host check failed.
failed: [bob] => {"failed": true}
msg: Single host check failed.
FATAL: all hosts have already failed -- aborting

Executar playbook em host único

$ ansible-playbook user.yml --limit=alice

PLAY [all] ****************************************************************

TASK: [Check for single host] *********************************************
skipping: [alice]

TASK: [debug msg='I got executed!'] ***************************************
ok: [alice] => {
    "msg": "I got executed!"
}
Marwan Alsabbagh
fonte
1
Definitivamente o melhor, --limité o caminho a percorrer
berto
7
play_hostsfoi descontinuado no Ansible 2.2 e substituído por ansible_play_hosts. Para executar em um host sem exigir --limit, você pode usar when: inventory_hostname == ansible_play_hosts[0].
Trevor Robinson
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{ play_hosts|length }} == ''no Ansible 2.8.4.
Thomas
32

Há IMHO uma maneira mais conveniente. Você pode solicitar interativamente ao usuário as máquinas às quais ele deseja aplicar o manual, graças a vars_prompt:

---

- hosts: "{{ setupHosts }}"
  vars_prompt:
    - name: "setupHosts"
      prompt: "Which hosts would you like to setup?"
      private: no
  tasks:
    […]
Buzut
fonte
2
Muito legal. Isso também tem a vantagem de que o manual não é específico para o arquivo de inventário.
Erfan
2
Thx para a edição! Eu estava realmente me perguntando por que a entrada foi tratada por padrão "estilo de senha". Eu tinha perdido isso nos documentos :) #
1212 Buzut
Os hosts podem ser definidos na linha de comando para eliminar o prompt com este manual?
andig
1
@andig com --extra-varse uma var normal no seu manual…
Buzut
Na verdade, não consegui fazer isso funcionar - parece que {{ hosts }}é avaliado antes que o valor seja inserido - ou existe um truque especial?
Remigius Stalder
18

Para expandir a resposta do joemailer, se você deseja ter a capacidade de correspondência de padrões para corresponder a qualquer subconjunto de máquinas remotas (assim como o ansiblecomando), mas ainda deseja dificultar a execução acidental do manual em todas as máquinas, é o que eu criei:

O mesmo manual da outra resposta:

# file: user.yml  (playbook)
---
- hosts: '{{ target }}'
  user: ...

Vamos ter os seguintes hosts:

imac-10.local
imac-11.local
imac-22.local

Agora, para executar o comando em todos os dispositivos, é necessário definir explicitamente a variável de destino como "todos"

ansible-playbook user.yml --extra-vars "target=all"

E para limitá-lo a um padrão específico, você pode definir target=pattern_here

ou, como alternativa, você pode deixar target=alle anexar o --limitargumento, por exemplo:

--limit imac-1*

ie ansible-playbook user.yml --extra-vars "target=all" --limit imac-1* --list-hosts

o que resulta em:

playbook: user.yml

  play #1 (office): host count=2
    imac-10.local
    imac-11.local
deadbeef404
fonte
Este é o padrão que eu tenho seguido em ansible-django-postgres-nginx
Ajoy
13

Eu realmente não entendo como todas as respostas são tão complicadas, a maneira de fazer isso é simplesmente:

ansible-playbook user.yml -i hosts/hosts --limit imac-2.local --check

O checkmodo permite executar no modo de funcionamento a seco, sem fazer nenhuma alteração.

knocte
fonte
7
Provavelmente porque se perguntou sobre as respostas, você perdeu a pergunta, que solicitava uma maneira de impedir a execução quando parâmetros são omitidos por engano. Você sugeriu adicionar mais parâmetros, o que contraria o requisito.
techraf
2
ah, claro, mas se as pessoas me votarem, pode ser porque são novatos Ansible (como eu era quando escrevi minha resposta) que nem sabem sobre a bandeira --check, então acho que isso ainda é útil em termos de documentação, como esta pergunta pode ser muito googlable
knocte
6

Os usuários da AWS que usam o Script de inventário externo do EC2 podem simplesmente filtrar por ID da instância:

ansible-playbook sample-playbook.yml --limit i-c98d5a71 --list-hosts

Isso funciona porque o script de inventário cria grupos padrão .

Frank
fonte
4
A opção --limit não se limita ao EC2 e pode ser usada para hospedar / agrupar nomes de seu inventário. Obrigado.
27515 martinzdelariva
5

Temos alguns manuais genéricos que podem ser usados ​​por um grande número de equipes. Também temos arquivos de inventário específicos do ambiente, que contêm várias declarações de grupo.

Para forçar alguém que chama um manual a especificar um grupo para o qual concorrer, propomos uma entrada fictícia na parte superior do manual:

[ansible-dummy-group]
dummy-server

Em seguida, incluímos a seguinte verificação como primeira etapa no manual compartilhado:

- hosts: all
  gather_facts: False
  run_once: true
  tasks:
  - fail:
      msg: "Please specify a group to run this playbook against"
    when: '"dummy-server" in ansible_play_batch'

Se o servidor fictício aparecer na lista de hosts com os quais este manual está programado para executar (ansible_play_batch), o chamador não especificou um grupo e a execução do manual falhará.

mcdowellstl
fonte
ansible_play_batchlista apenas o lote atual; portanto, ao usar o lote, isso ainda não é seguro. É melhor usar em seu ansible_play_hostslugar.
Thomas
Além disso, esse truque parece ser o mais simples e mais próximo do que foi solicitado; Estou adotando!
Thomas
4

Desde a versão 1.7 ansible tem a opção run_once . A seção também contém algumas discussões de várias outras técnicas.

Berend de Boer
fonte
4

Isso mostra como executar os playbooks no próprio servidor de destino.

Isso é um pouco mais complicado se você quiser usar uma conexão local. Mas isso deve ser bom se você usar uma variável para a configuração de hosts e no arquivo hosts criar uma entrada especial para o host local.

Em (todos) os playbooks têm a linha hosts: definida como:

- hosts: "{{ target | default('no_hosts')}}"

No arquivo hosts do inventário, adicione uma entrada para o host local, que define a conexão como local:

[localhost]
127.0.0.1  ansible_connection=local

Em seguida, na linha de comando, execute comandos definindo explicitamente o destino - por exemplo:

$ ansible-playbook --extra-vars "target=localhost" test.yml

Isso também funcionará ao usar o ansible-pull:

$ ansible-pull -U <git-repo-here> -d ~/ansible --extra-vars "target=localhost" test.yml

Se você esquecer de definir a variável na linha de comando, o comando irá com erro (desde que você não tenha criado um grupo de hosts chamado 'no_hosts'!) Com um aviso de:

skipping: no hosts matched

E, como mencionado acima, você pode direcionar uma única máquina (desde que esteja no arquivo hosts) com:

$ ansible-playbook --extra-vars "target=server.domain" test.yml

ou um grupo com algo como:

$ ansible-playbook --extra-vars "target=web-servers" test.yml
bailey86
fonte
0

Eu tenho um script de invólucro chamado provisionamento obriga a escolher o destino, então não preciso lidar com isso em nenhum outro lugar.

Para quem é curioso, eu uso vars ENV para opções que meu arquivo de vagrant usa (adicionando o arg ansible correspondente para sistemas em nuvem) e deixo o resto dos args ansible passar. Onde estou criando e provisionando mais de 10 servidores por vez, incluo uma nova tentativa automática em servidores com falha (desde que haja progresso - descobri que ao criar mais de 100 servidores por vez, muitas vezes alguns falhavam na primeira vez )

echo 'Usage: [VAR=value] bin/provision [options] dev|all|TARGET|vagrant'
echo '  bootstrap - Bootstrap servers ssh port and initial security provisioning'
echo '  dev - Provision localhost for development and control'
echo '  TARGET - specify specific host or group of hosts'
echo '  all - provision all servers'
echo '  vagrant - Provision local vagrant machine (environment vars only)'
echo
echo 'Environment VARS'
echo '  BOOTSTRAP - use cloud providers default user settings if set'
echo '  TAGS - if TAGS env variable is set, then only tasks with these tags are run'
echo '  SKIP_TAGS - only run plays and tasks whose tags do not match these values'
echo '  START_AT_TASK - start the playbook at the task matching this name'
echo
ansible-playbook --help | sed -e '1d
    s#=/etc/ansible/hosts# set by bin/provision argument#
    /-k/s/$/ (use for fresh systems)/
    /--tags/s/$/ (use TAGS var instead)/
    /--skip-tags/s/$/ (use SKIP_TAGS var instead)/
    /--start-at-task/s/$/ (use START_AT_TASK var instead)/
'
iheggie
fonte
0

Uma solução ligeiramente diferente é usar a variável especial, ansible_limitque é o conteúdo da --limitopção CLI para a execução atual do Ansible.

- hosts: "{{ ansible_limit | default(omit) }}"

Não há necessidade de definir uma variável extra aqui, basta executar o manual com a --limitbandeira.

ansible-playbook --limit imac-2.local user.yml
Manolo
fonte