Jeder in meinem Team kann SSH in unseren speziellen Bereitstellungsserver einbinden und von dort ein Ansible-Playbook ausführen, um neuen Code auf die Computer zu pushen.
Wir machen uns Sorgen, was passiert, wenn zwei Personen gleichzeitig Bereitstellungen durchführen. Wir möchten es so gestalten, dass das Playbook fehlschlägt, wenn jemand anderes es gerade ausführt.
Irgendwelche Vorschläge, wie das geht? Die Standardlösung ist die Verwendung einer PID-Datei, aber Ansible bietet keine integrierte Unterstützung für diese.
Sie könnten einen Wrapper für Ansible-Befehle schreiben:
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` "[email protected]"
# Remove lock file
rm $lock
# Remove signal handlers
trap - INT TERM EXIT
}
Definieren Sie diese Funktion im ~/.bashrc
Ihrer Benutzer in der Implementierungsbox, und Sie sind festgelegt. Sie können dasselbe für den Befehl ansible
tun, wenn Sie möchten, aber angesichts der Frage bin ich nicht sicher, ob dies erforderlich ist.
BEARBEITEN: Mit Signalhandler umgeschrieben, um zu verhindern, dass Sperrdateien herumlaufen, wenn Benutzer Strg-C drücken.
EDIT2: Tippfehler behoben
Ich persönlich benutze RunDeck ( http://rundeck.org/ ) aus mehreren Gründen als Wrapper um meine Ansible-Playbooks:
Es gibt natürlich noch viele gute Gründe, aber meine Finger werden müde vom Tippen;)
Ich habe dies in mein Hauptbuch aufgenommen
hosts: all.
lock_file_path
: Dies ist eine Datei, deren Vorhandensein darauf hinweist, dass derzeit eine ausführbare Bereitstellung ausgeführt wird oder zuvor eine Bereitstellung stattfand, die aus irgendeinem Grund abgebrochen wurde.
force_ignore_lock
: Dies ist standardmäßig auf false gesetzt und wird durch ein Optionsflag zurückgesetzt, das Sie in einem Wrapper für Befehlszeilen auf ansible setzen können. Es ermöglicht es dem Benutzer, mit der Bereitstellung fortzufahren.
pre_tasks
Der erste pre_task
prüft, ob der lock_file_path
existiert, und zeichnet das Ergebnis in einem Register namens lock_file
auf.
Die nächste Aufgabe prüft dann, ob die Datei vorhanden ist und ob die bereitgestellte Person sich entschieden hat, sie zu ignorieren (hoffentlich nach der Kommunikation mit anderen Teammitgliedern). Wenn nicht, schlägt der Job mit einer hilfreichen Fehlermeldung fehl.
Wenn der Benutzer die Bereitstellung fortsetzen möchte, selbst wenn ein lock_file
vorhanden ist, löscht die nächste Aufgabe den zuvor erstellten lock_file
und erstellt einen neuen. Die anderen Aufgaben in der ausgewählten Rolle laufen dann problemlos weiter.
post_tasks
Dies ist ein Hook, der unmittelbar nach Abschluss aller Tasks in der Bereitstellung aufgerufen wird. Die Aufgabe löscht hier den lock_file
, sodass die nächste Person problemlos und ohne Probleme implementiert werden kann.
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
Haben Sie darüber nachgedacht, maxsyslogins
in limits.conf einzustellen? Sie können dies nach Gruppen einschränken.
# for a group called 'deployers'
@deployers - maxsyslogins 1
Dies ist ein bisschen schwerwiegender als das, wonach Sie gefragt haben. Möglicherweise möchten Sie es zuerst an einem VM ausprobieren. Beachten Sie, dass niemand von den Bereitstellern Zugriff hat, wenn überhaupt andere Benutzer im System vorhanden sind. Das Limit 1 zählt nicht nur die Bereitsteller. Wenn Sie als Benutzer Ihre ssh-Verbindungen multiplexen (ControlMaster auto), können Sie sich trotzdem mehrmals anmelden. Es sind andere Benutzer, die gesperrt werden.
Sie können den Befehl flock verwenden, der Ihren Befehl mit einem Dateisystem-basierten Flock (2) umschließt:
$ flock /tmp/ansible-playbook.lock ansible-playbook foo bar baz
Wickeln Sie das so ein, dass es Ihren Benutzern am besten entspricht. Dadurch bleibt eine permanente Sperrdatei in/tmp. Beachten Sie jedoch, dass das Löschen von nicht safe [1] möglich ist. Es ist atomar und sehr einfach.
[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
Wrapperskripts sind nicht nützlich, wenn Bereitstellungsaufträge von mehreren Build-Hosts ausgeführt werden können. In solchen Fällen muss die Sperrung vom Spielbuch vorgenommen werden.
Ansible verfügt jetzt über ein wait_for - Modul, das zum Sperren verwendet werden kann. Hier ein kurzes Beispiel (ohne Rücksicht auf veraltete Sperren):
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 sucht für eine konfigurierbare Zeitüberschreitung nach der Sperrdatei und gibt dann auf, wenn sie in dieser Zeit nicht entfernt wird.
Sie können auch eine einfache Variante des Wrappers verwenden:
# 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
Ich würde einen verteilten Sperrmechanismus wie zookeeper betrachten, den ich als Rolle einschließen würde, da es ein znode
-Modul gibt. Verteilt für Hochverfügbarkeit und Schreibschutz für die Zielknoten.
Die Rolle würde am Anfang einen Znode des Zielnamens unter /deployment/
schreiben und danach löschen. Wenn die Sperre bereits vorhanden ist, können Sie fehlschlagen und eine Nachricht in eine block
werfen.