web-dev-qa-db-fra.com

Empêcher les déploiements simultanés avec Ansible

Tous les membres de mon équipe peuvent utiliser SSH sur notre serveur de déploiement spécial, puis exécuter un livre de lecture Ansible pour transmettre le nouveau code aux machines.

Nous nous inquiétons de ce qui se passera si deux personnes tentent de faire des déploiements simultanément. Nous aimerions faire en sorte que le playbook échoue si quelqu'un d'autre l'exécute actuellement.

Des suggestions pour savoir comment faire cela? La solution standard consiste à utiliser un fichier pid, mais Ansible n’a pas de prise en charge intégrée pour ces derniers.

24
James Koppel

Vous pouvez écrire un wrapper pour les commandes ansible comme ceci:

ansible-playbook() {
  lock="/tmp/ansible-playbook.lock"

  # Check if lock exists, return if yes
  if [ -e $lock ]; then
    echo "Sorry, someone is running already ansible from `cat $lock`"
    return
  fi

  # Install signal handlers
  trap "rm -f $lockfile; trap - INT TERM EXIT; return" INT TERM EXIT

  # Create lock file, saving originating IP
  echo $SSH_CLIENT | cut -f1 -d' ' > $lock

  # Run ansible with arguments passed at the command line
  `which ansible-playbook` "$@"

  # Remove lock file
  rm $lock

  # Remove signal handlers
  trap - INT TERM EXIT
}

Définissez cette fonction dans le ~/.bashrc de vos utilisateurs dans la zone de déploiement et vous êtes défini . Vous pouvez faire de même pour la commande ansible si vous le souhaitez, mais compte tenu de la question, je ne suis pas sûr que ce soit nécessaire.

EDIT: Réécrit avec le gestionnaire de signal pour éviter que le fichier de verrouillage ne pendille si les utilisateurs appuyaient sur Ctrl-C.

EDIT2: faute de frappe fixe

18
leucos

Personnellement, j'utilise RunDeck ( http://rundeck.org/ ) pour envelopper mes playbooks Ansible pour plusieurs raisons:

  • Vous pouvez définir un "travail" RunDeck pour qu'il ne puisse être exécuté qu'une seule fois (ou le définir pour qu'il s'exécute autant de fois que vous le souhaitez)
  • Vous pouvez configurer des utilisateurs au sein du système afin que l'audit de qui a exécuté ce qui est répertorié clairement
  • Vous pouvez définir des variables supplémentaires avec des contraintes sur ce qui peut être utilisé (spécifiez une liste d'options)
  • C'est beaucoup moins cher que Ansible Tower (RunDeck est gratuit)
  • Il dispose d'une API complète pour exécuter des tâches de manière pragmatique à partir de systèmes de construction.
  • Vous n'avez pas besoin d'écrire des wrappers bash compliqués autour de la commande ansible-playbook
  • SSH peut devenir un test décisif de "quelque chose a besoin d'un script ansible écrit" - je n'autorise pas l'accès SSH sauf en cas de rupture/correction, et nous avons des SA plus heureux en conséquence
  • Enfin, il est indéniable que vous pouvez planifier des tâches RunDeck pour exécuter des playbooks compatibles d'une manière très simple pour quiconque se connectant à la console pour voir ce qui est en cours d'exécution quand

Il y a beaucoup d'autres bonnes raisons bien sûr, mais mes doigts en ont marre de taper;)

27
PhillipHolmes

Je mets cela dans mon livre principal, après 

    hosts: all. 

lock_file_path: Il s'agit d'un fichier dont l'existence indique qu'un déploiement ansible est en cours, ou qu'un déploiement a déjà eu lieu, qui a été interrompu pour une raison quelconque.

force_ignore_lock: false par défaut, réinitialisé par un indicateur d'option que vous pouvez définir dans un wrapper de ligne de commande sur ansible. Cela permet à ansible de poursuivre le déploiement.

Ce que cela fait

pre_tasks

Le premier pre_task vérifie si le lock_file_path existe et enregistre le résultat dans un registre appelé lock_file.

La tâche suivante vérifie ensuite si le fichier existe et si la personne qui effectue le déploiement a choisi de l'ignorer (éventuellement après avoir communiqué avec d'autres membres de l'équipe). Sinon, le travail échoue avec un message d'erreur utile.

Si l'utilisateur choisit de poursuivre le déploiement même s'il y avait un lock_file, la tâche suivante supprime le lock_file précédemment créé et en crée un nouveau. Les autres tâches du rôle sélectionné se poursuivent ensuite avec bonheur.

post_tasks

Ceci est un raccordement appelé immédiatement après que toutes les tâches du déploiement ont été terminées. La tâche ici supprime le lock_file, permettant à la personne suivante de se déployer avec bonheur, sans aucun problème.

vars:
  lock_file_path=/tmp/ansible-playbook-{{ansible_ssh_user}}.lock

pre_tasks: 
   - stat: path={{lock_file_path}}
     register: lock_file

   - fail: msg="Sorry, I found a lockfile, so I'm assuming that someone was already running ansible when you started this deploy job. Add -f to your deploy command to forcefully continue deploying, if the previous deploy was aborted."
   when: lock_file.stat.exists|bool and not force_ignore_lock|bool

   - file: path={{lock_file_path}} state=absent
     Sudo: yes
     when: "{{force_ignore_lock}}"

   - file: path={{lock_file_path}} state=touch
     Sudo: yes

post_tasks:
   - file: path={{lock_file_path}} state=absent
     Sudo: yes
10
poppingtonic

Avez-vous envisagé de définir maxsyslogins dans limits.conf? Vous pouvez limiter cela par groupe.

# for a group called 'deployers'
@deployers        -       maxsyslogins      1

C'est un peu plus grave que ce que vous avez demandé. Vous voudrez peut-être d'abord l'essayer sur VM. Notez que personne parmi les déployeurs n'aura accès s'il y a d'autres utilisateurs sur le système, la limite 1 ne compte pas seulement les déployeurs. De plus, si en tant qu’utilisateur vous multiplexez vos connexions SSH (ControlMaster auto), vous pourrez toujours vous connecter plusieurs fois; ce sont les autres utilisateurs qui seraient en lock-out.

8
bazzargh

Vous pouvez utiliser la commande flock, qui encapsulera votre commande avec un flock basé sur un système de fichiers (2):

$ flock /tmp/ansible-playbook.lock ansible-playbook foo bar baz

Enveloppez-le, mais cela convient le mieux à vos utilisateurs. Cela laissera un fichier de verrouillage persistant dans/tmp, mais notez qu'il est not safe pour le supprimer [1]. C'est atomique, et très simple.

[1]
A: flock /tmp/foo.lock -c "echo running; sleep 5; rm /tmp/foo.lock"
B: flock /tmp/foo.lock -c "echo running; sleep 5; rm /tmp/foo.lock"
   B blocks waiting for lock on /tmp/foo.lock
A: Finish, deleting /tmp/foo.lock
B: Runs, using lock on now deleted /tmp/foo.lock
C: flock /tmp/foo.lock -c "echo running; sleep 5; rm /tmp/foo.lock"
   Creates new /tmp/foo.lock, locks it and runs immediately, parallel with B
5
Matthew Booth

Les scripts wrapper ne sont pas utiles lorsque les travaux de déploiement peuvent être exécutés à partir de plusieurs hôtes de génération. Pour ce type de cas, le verrouillage doit être traité par le playbook.

Ansible a maintenant un module wait_for qui peut être utilisé pour le verrouillage. Voici un court exemple (sans tenir compte des serrures périmées):

vars:
  lock_file: "{{ deploy_dir }}/.lock"
pre_tasks:
  - name: check for lock file
    wait_for:
      path: "{{ lock_file }}"
      state: absent
  - name: create lock file
    file:
      path: "{{ lock_file }}"
      state: touch
post_tasks:
  - name: remove lock file
    file:
      path: "{{ lock_file }}"
      state: absent

Ansible recherchera le fichier de verrouillage pendant une période configurable, puis abandonnera s’il n’est pas supprimé au cours de cette période.

4
Ioan Rogers

Vous pouvez également utiliser une variante simple de wrapper:

# Check lock file - if exists then exit. Prevent running multiple ansible instances in parallel
while kill -0 $(cat /tmp/ansible_run.lock 2> /dev/null) &> /dev/null; do
  echo "Ansible is already running. Please wait or kill running instance."
  sleep 3
done
# Create lock file
echo $$ > /tmp/ansible_run.lock

ansible-playbook main.yml

# Remove lock
rm -f /tmp/ansible_run.lock
0
gmy

J'examinerais un mécanisme de verrouillage distribué tel que zookeeper que je voudrais inclure comme rôle, car il existe un module znode . Distribué pour la haute disponibilité et le verrouillage en écriture des noeuds cibles. 

Le rôle écrirait un znode du nom de la cible sous /deployment/ au début et le supprimerait ensuite. Si le verrou existe déjà, vous pouvez échouer et envoyer un message dans une variable block.

0