Limiter en toute sécurité les playbooks Ansible à une seule machine?

227

J'utilise Ansible pour certaines tâches simples de gestion des utilisateurs avec un petit groupe d'ordinateurs. Actuellement, mes playbooks sont définis sur hosts: allet mon fichier d'hôtes n'est qu'un seul groupe avec toutes les machines répertoriées:

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

Je me suis souvent retrouvé à devoir cibler une seule machine. La ansible-playbookcommande peut limiter les jeux comme celui-ci:

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

Mais cela semble plutôt fragile, en particulier pour un livre de jeu potentiellement destructeur. Oublier le limitdrapeau signifie que le livre de jeu serait exécuté partout. Étant donné que ces outils ne sont utilisés qu'occasionnellement, il semble utile de prendre des mesures pour une lecture à toute épreuve afin de ne pas nuke accidentellement quelque chose dans quelques mois.

Existe-t-il une meilleure pratique pour limiter les exécutions de playbooks à une seule machine? Idéalement, les livres de jeu devraient être inoffensifs si certains détails importants étaient omis.

joemaller
la source

Réponses:

209

Il s'avère qu'il est possible d'entrer un nom d'hôte directement dans le playbook, donc l'exécution du playbook avec hosts: imac-2.localfonctionnera bien. Mais c'est un peu maladroit.

Une meilleure solution pourrait être de définir les hôtes du playbook à l'aide d'une variable, puis de passer une adresse d'hôte spécifique via --extra-vars:

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

Exécution du playbook:

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

S'il {{ target }}n'est pas défini, le playbook ne fait rien. Un groupe du fichier hosts peut également être transmis si nécessaire. Dans l'ensemble, cela semble être un moyen beaucoup plus sûr de construire un livre de jeu potentiellement destructeur.

Playbook ciblant un seul hôte:

$ 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

Playbook avec un groupe d'hôtes:

$ 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

Oublier de définir des hôtes est sûr!

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

playbook: user.yml

  play #1 ({{target}}): host count=0
joemaller
la source
52
Ceci est résoluble en 1.5.3 avec--limit office[0]
NG.
4
La variable doit être citée - c'est-à-dire: '{{ target }}'- selon docs.ansible.com/…
Limbo Peng
9
Il s'agit d'une réponse «à sécurité intégrée», contrairement à d'autres - si vous omettez quelque chose, cela ne fera rien. L'exécution sur «un seul» hôte utilisant Ansible 1.7 run_oncepourrait encore être destructrice, ce n'est donc pas une si bonne idée.
RichVel
4
Si vous souhaitez une commande plus courte, -eest l'équivalent de--extra-vars
William Turrell
1
Si votre configuration ansible nécessite que les hôtes ne puissent pas être vides ou indéfinis, alors l'utilisation d'une variable combinée avec un filtre jinja fonctionne, comme:hosts: "{{ target | default('no_hosts')}}"
Zach Weg
178

Il y a aussi une petite astuce mignonne qui vous permet de spécifier un seul hôte sur la ligne de commande (ou plusieurs hôtes, je suppose), sans inventaire intermédiaire:

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

Notez la virgule ( , ) à la fin; cela signale que c'est une liste, pas un fichier.

Maintenant, cela ne vous protégera pas si vous passez accidentellement un vrai fichier d'inventaire, donc ce n'est peut-être pas une bonne solution à ce problème spécifique. Mais c'est une astuce pratique à savoir!

Tybstar
la source
2
C'est incroyable. J'utilise régulièrement l'indicateur -l, qui fonctionne avec etc / ansible / hosts (qui est rempli à l'aide de l'API de découverte EC2), mais parfois j'ai vraiment juste besoin d'une seule machine. Je vous remercie!
Vic
3
Cette astuce doit-elle utiliser le fichier hosts? J'utilise des hôtes comme un inventaire dynamique pour notre système AWS EC2 et il retourne: skipping: no hosts matched. Peut-être que cette astuce ne fonctionne plus depuis les --limittravaux?
hamx0r
1
Cette astuce n'a pas fonctionné pour moi. Mais cela a fonctionné: $ ansible-playbook -kK --limit=myhost1 myplaybook.yml. Voir la réponse de Marwan.
Donn Lee
2
Il convient de mentionner que pour que cela fonctionne, les hôtes doivent être configurés alldans la ou les pièces - cela m'a pris du temps à comprendre ...
Remigius Stalder
83

Cette approche se terminera si plusieurs hôtes sont fournis en vérifiant la variable play_hosts . Le module d'échec est utilisé pour quitter si la condition d'hôte unique n'est pas remplie. Les exemples ci-dessous utilisent un fichier hosts avec deux hôtes alice et bob.

user.yml (playbook)

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

Exécuter le playbook sans filtres d'hôte

$ 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

Exécuter le playbook sur un seul hôte

$ 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
la source
1
Certainement le meilleur, --limitc'est le chemin à parcourir
berto
7
play_hostsest déconseillé dans Ansible 2.2 et remplacé par ansible_play_hosts. Pour exécuter sur un hôte sans nécessiter --limit, vous pouvez utiliser 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 }} == ''sur Ansible 2.8.4.
Thomas
32

Il y a à mon humble avis un moyen plus pratique. Vous pouvez en effet demander de manière interactive à l'utilisateur la ou les machines auxquelles il souhaite appliquer le playbook grâce à vars_prompt:

---

- hosts: "{{ setupHosts }}"
  vars_prompt:
    - name: "setupHosts"
      prompt: "Which hosts would you like to setup?"
      private: no
  tasks:
    […]
Buzut
la source
2
Très cool. Cela présente également l'avantage que le playbook n'est pas spécifique au fichier d'inventaire.
Erfan
2
Thx pour l'édition! Je me demandais en fait pourquoi l'entrée était traitée par défaut "style de mot de passe". Je l'avais manqué dans les docs :)
Buzut
Les hôtes peuvent-ils être définis à partir de la ligne de commande pour éliminer l'invite avec ce playbook?
andig
1
@andig avec --extra-varset un var normal dans votre playbook…
Buzut
En fait, je n'ai pas pu faire fonctionner cela - il semble qu'il {{ hosts }}soit évalué avant que la valeur ne soit entrée - ou y a-t-il une astuce spéciale?
Remigius Stalder
18

Pour développer la réponse de joemailer, si vous voulez avoir la capacité de correspondance de motifs pour correspondre à n'importe quel sous-ensemble de machines distantes (tout comme la ansiblecommande), mais que vous voulez toujours rendre très difficile l'exécution accidentelle du playbook sur toutes les machines, c'est ce que j'ai trouvé:

Même playbook que dans l'autre réponse:

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

Ayons les hôtes suivants:

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

Maintenant, pour exécuter la commande sur tous les appareils, vous devez définir explicitement la variable cible sur "all"

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

Et pour le limiter à un modèle spécifique, vous pouvez définir target=pattern_here

ou, alternativement, vous pouvez laisser target=allet ajouter l' --limitargument, par exemple:

--limit imac-1*

c'est à dire. ansible-playbook user.yml --extra-vars "target=all" --limit imac-1* --list-hosts

ce qui se traduit par:

playbook: user.yml

  play #1 (office): host count=2
    imac-10.local
    imac-11.local
deadbeef404
la source
C'est le modèle que j'ai suivi dans ansible-django-postgres-nginx
Ajoy
13

Je ne comprends vraiment pas comment toutes les réponses sont si compliquées, la façon de le faire est simplement:

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

Le checkmode vous permet de fonctionner en mode marche à sec, sans effectuer de changement.

knocte
la source
7
Probablement parce que vous vous êtes interrogé sur les réponses, vous avez manqué la question, qui demandait un moyen d'empêcher l'exécution lorsque les paramètres sont omis par erreur. Vous avez suggéré d'ajouter plus de paramètres qui vont à l'encontre de l'exigence.
techraf
2
ah, bien sûr, mais si les gens me votent, c'est peut-être parce qu'ils sont des débutants Ansible (comme je l'étais quand j'ai écrit ma réponse) qui ne connaissent même pas le drapeau --check, donc je suppose que cela est toujours utile sur le plan de la documentation, comme cette question peut être très googlable
knocte
6

Les utilisateurs AWS utilisant le script d'inventaire externe EC2 peuvent simplement filtrer par ID d'instance:

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

Cela fonctionne car le script d'inventaire crée des groupes par défaut .

Franc
la source
4
L'option --limit n'est pas limitée à EC2 et peut être utilisée pour héberger / groupes les noms de votre inventaire. Merci.
martinezdelariva
5

Nous avons des playbooks génériques utilisables par un grand nombre d'équipes. Nous avons également des fichiers d'inventaire spécifiques à l'environnement, qui contiennent plusieurs déclarations de groupe.

Pour forcer quelqu'un qui appelle un playbook à spécifier un groupe contre lequel exécuter, nous introduisons une entrée fictive en haut du playbook:

[ansible-dummy-group]
dummy-server

Nous incluons ensuite la vérification suivante comme première étape dans le playbook partagé:

- 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'

Si le serveur factice apparaît dans la liste des hôtes contre lesquels ce playbook est programmé (ansible_play_batch), alors l'appelant n'a pas spécifié de groupe et l'exécution du playbook échouera.

mcdowellstl
la source
ansible_play_batchrépertorie uniquement le lot en cours, donc lors de l'utilisation du traitement par lots, cela n'est toujours pas sûr. Il vaut mieux utiliser à la ansible_play_hostsplace.
Thomas
En dehors de cela, cette astuce semble être la plus simple et la plus proche de ce qui a été demandé; Je l'adopte!
Thomas
4

Depuis la version 1.7 ansible a l' option run_once . La section contient également une discussion sur diverses autres techniques.

Berend de Boer
la source
4

Cela montre comment exécuter les playbooks sur le serveur cible lui-même.

C'est un peu plus compliqué si vous souhaitez utiliser une connexion locale. Mais cela devrait être OK si vous utilisez une variable pour le paramètre hosts et créez dans le fichier hosts une entrée spéciale pour localhost.

Dans (tous) les playbooks, la ligne hosts: est définie sur:

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

Dans le fichier des hôtes d'inventaire, ajoutez une entrée pour l'hôte local qui définit la connexion comme locale:

[localhost]
127.0.0.1  ansible_connection=local

Ensuite, sur la ligne de commande, exécutez des commandes définissant explicitement la cible - par exemple:

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

Cela fonctionnera également lors de l'utilisation de ansible-pull:

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

Si vous oubliez de définir la variable sur la ligne de commande, la commande produira une erreur en toute sécurité (tant que vous n'avez pas créé un groupe d'hôtes appelé 'no_hosts'!) Avec un avertissement de:

skipping: no hosts matched

Et comme mentionné ci-dessus, vous pouvez cibler une seule machine (tant qu'elle se trouve dans votre fichier hosts) avec:

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

ou un groupe avec quelque chose comme:

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

J'ai un script wrapper appelé provision vous oblige à choisir la cible, donc je n'ai pas à le gérer ailleurs.

Pour ceux qui sont curieux, j'utilise les vars ENV pour les options que mon fichier vagrant utilise (en ajoutant l'argument ansible correspondant pour les systèmes cloud) et laisse le reste des arguments ansible passer. Lorsque je crée et provisionne plus de 10 serveurs à la fois, j'inclut une nouvelle tentative automatique sur les serveurs défaillants (tant que des progrès sont réalisés - j'ai constaté que lors de la création d'une centaine de serveurs à la fois, souvent quelques-uns échouaient la première fois) ).

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
la source
0

Une solution légèrement différente consiste à utiliser la variable spéciale ansible_limitqui est le contenu de l' --limitoption CLI pour l'exécution actuelle d'Ansible.

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

Pas besoin de définir une variable supplémentaire ici, il suffit d'exécuter le playbook avec le --limitdrapeau.

ansible-playbook --limit imac-2.local user.yml
Manolo
la source