Предотвращение одновременных развертываний с помощью Ansible

Любой член моей команды может подключиться по SSH к нашему специальному серверу развертывания и оттуда запустить плейбук Ansible для передачи нового кода на машины.

Мы беспокоимся о том, что произойдет, если два человека попытаются выполнить развертывание одновременно. Мы хотели бы сделать так, чтобы плейбук не работал, если кто-то другой в данный момент его запускает.

Любые предложения о том, как это сделать? Стандартным решением является использование pid-файла, но в Ansible нет встроенной поддержки для них.


person James Koppel    schedule 19.02.2014    source источник


Ответы (8)


Вы можете написать оболочку для доступных команд следующим образом:

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
}

Определите эту функцию в ~/.bashrc ваших пользователей в окне развертывания, и все готово. Вы можете сделать то же самое для команды ansible, если хотите, но, учитывая вопрос, я не уверен, что это необходимо.

РЕДАКТИРОВАТЬ: Переписан с обработчиком сигнала, чтобы предотвратить зависание файла блокировки, если пользователи нажимают Ctrl-C.

EDIT2: исправлена ​​опечатка

person leucos    schedule 19.02.2014
comment
Ух ты. Этот скрипт в основном предотвратит запуск двух экземпляров, но не полностью из-за временного промежутка между тестированием и созданием файла блокировки. Вам нужна атомарная операция, чтобы иметь реальную защиту, и хотя я устарел с * nix, я думаю, что сценарий не может этого сделать. Я предполагаю, что RunDeck, упомянутый в следующем ответе, может иметь то, что требуется, используя IPC (семафоры и т. Д.). - person narration_sd; 02.06.2015
comment
По идее да, 100% нет. Вы можете еще уменьшить разрыв, выполнив что-то вроде [ -e $lock ] || (echo $SSH_CLIENT | cut -f1 -d' ' > $lock) (и соответствующим образом скорректировав сценарий), что займет менее миллисекунды. На практике звучит вполне разумно. Или запишите файл блокировки с ограниченными разрешениями, чтобы другой пользователь не мог его перезаписать. В общем, конечно, это в лучшем случае хак. Рандек выглядит круто. - person leucos; 02.06.2015
comment
flock с -c здесь является опцией, например flock -x $HOME/.ansible_lock -c 'ansible-playbook ...' - person Edgar Klerks; 05.10.2017

Лично я использую RunDeck ( http://rundeck.org/ ) в качестве оболочки для моих плейбуков Ansible по нескольким причинам:

  • Вы можете настроить «задание» RunDeck так, чтобы оно могло запускаться только один раз (или настроить его на запуск столько раз одновременно, сколько вы хотите)
  • Вы можете настроить пользователей в системе так, чтобы аудит того, кто запускал то, что было указано в списке, был четким.
  • Вы можете установить дополнительные переменные с ограничениями на то, что можно использовать (укажите список опций)
  • Это намного дешевле, чем Ansible Tower (RunDeck бесплатен).
  • Он имеет полный API для прагматичного запуска заданий из систем сборки.
  • Вам не нужно писать сложные bash-обертки вокруг команды ansible-playbook.
  • SSH может стать лакмусовой бумажкой того, что «что-то требует написания скрипта ansible» — я не разрешаю доступ по SSH, кроме как в полном объеме, в ситуациях сбоя / исправления, и в результате у нас есть более счастливые SA.
  • Наконец, и, безусловно, в категории «приятно иметь» вы можете запланировать задания RunDeck для запуска ansible playbooks очень простым способом, чтобы любой, кто входит в консоль, мог видеть, что работает, когда

Конечно, есть еще много веских причин, но мои пальцы уже устают печатать ;)

person keba    schedule 10.09.2014
comment
Есть ли какие-либо подробности в Интернете о том, как вы это настроили? - person thisjustin; 29.08.2015
comment
@thisjustin Пока еще не полностью - в последнее время я отвлекся на работу, поэтому мой блог еще не совсем в этом состоянии - у rundeck есть возможность настроить задание так, чтобы оно запускалось только в том случае, если оно еще не запущено в своей конфигурации. за задание и возвращает соответствующий код 4XX, чтобы сказать, выполняется ли задание, если этот параметр установлен (если вы используете API, это важная информация, которую нужно знать) - person keba; 01.09.2015
comment
RunDeck мне кажется Дженкинсом. - person George Shuklin; 24.05.2016

Я поместил это в свою основную книгу после

    hosts: all. 

lock_file_path: Это файл, существование которого указывает на то, что в настоящее время выполняется развертывание без возможности развертывания или ранее было развертывание, которое по какой-то причине было прервано.

force_ignore_lock: по умолчанию это значение равно false, сбрасывается с помощью флага параметра, который вы можете установить в оболочке командной строки на доступный. Это позволяет ansible продолжить развертывание.

Что это делает

pre_tasks

Первый pre_task проверяет, существует ли lock_file_path, и записывает результат в регистр с именем lock_file.

Затем следующая задача проверяет, существует ли файл, и решил ли человек, выполняющий развертывание, проигнорировать его (надеюсь, после общения с другими товарищами по команде). В противном случае задание завершается с ошибкой с полезным сообщением об ошибке.

Если пользователь решит продолжить развертывание, даже если было lock_file, следующая задача удалит ранее созданный lock_file и создаст новый. Затем другие задачи в выбранной роли успешно продолжаются.

post_tasks

Это хук, вызываемый сразу после того, как все задачи в деплое были выполнены. Задача здесь удаляет lock_file, позволяя следующему человеку выполнить развертывание без каких-либо проблем.

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
person poppingtonic    schedule 11.11.2015
comment
Если выполнение ansible будет прервано, вы можете получить постоянно заблокированную машину. Существования файла блокировки недостаточно, ему нужен способ определить, является ли это устаревшей блокировкой или нет. - person sorin; 27.06.2016
comment
Вот почему я добавил: when: lock_file.stat.exists|bool and not force_ignore_lock|bool. При этом используется переменная force_ignore_lock, которую можно установить во флаге командной строки для сценария-оболочки, написанного, например, на Python с помощью клика. Пример: `` - person poppingtonic; 28.06.2016
comment
Пример: @deploy.command() @click.option('--force', help="Ignore the lock file that prevents parallel deploys, \ if a previous deploy was aborted.", is_flag=True, default=False) def backend(api_server, force): """Deploys the backend application.""" if force: ignore_lock = 'true' else: ignore_lock = 'false' extra_vars = { 'force_ignore_lock': ignore_lock } call_ansible(api_server, 'api_server.yml', json.dumps(extra_vars)) - person poppingtonic; 28.06.2016

Рассматривали ли вы установку maxsyslogins в limit.conf? Вы можете ограничить это группой.

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

Это немного серьезнее, чем то, что вы просили. Возможно, вы захотите сначала попробовать это на виртуальной машине. Обратите внимание, что никто из развертывателей не будет иметь доступа, если в системе вообще есть какие-либо другие пользователи, ограничение 1 не учитывает только развертывателей. Кроме того, если вы как пользователь мультиплексируете свои ssh-соединения (ControlMaster auto), вы все равно сможете войти в систему несколько раз; это другие пользователи, которые будут заблокированы.

person bazzargh    schedule 19.02.2014
comment
На самом деле я искал в Google что-то, чтобы сделать именно это как возможное решение, но не смог найти. Спасибо! - person James Koppel; 20.02.2014

Вы можете использовать команду flock, которая обернет вашу команду flock(2) на основе файловой системы:

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

Оберните это, однако это лучше всего подходит вашим пользователям. Это оставит постоянный файл блокировки в /tmp, но учтите, что его не удалять безопасно[1]. Это атомарно и очень просто.

[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
person Matthew Booth    schedule 01.07.2015

Скрипты-оболочки бесполезны, когда задания развертывания могут выполняться с нескольких хостов сборки. Для таких случаев блокировка должна обрабатываться playbook.

В Ansible теперь есть модуль wait_for, который можно использовать для блокировки. Вот краткий пример (без учета устаревших блокировок):

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 проверит файл блокировки в течение настраиваемого периода времени ожидания, а затем откажется, если он не будет удален в течение этого периода.

person Ioan Rogers    schedule 09.03.2017

Вы также можете использовать простой вариант оболочки:

# 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
person gmy    schedule 14.10.2014

Я бы рассмотрел механизм распределенной блокировки, такой как zookeeper, который я бы включил в качестве роли, поскольку существует znode. Распределено для обеспечения высокой доступности и блокировки записи целевых узлов.

Роль запишет znode целевого имени под /deployment/ в начале и удалит его после. Если блокировка уже существует, вы можете потерпеть неудачу и отправить сообщение в block.

person Baptiste Mille-Mathias    schedule 09.09.2018