Ansible — прекрасное средство управления серверами. Совместно с git он позволяет перейти в парадигму deploy as code со всеми вытекающими отсюда прелестями, такими как ревью кода, мердж (пулл) реквесты изменений и тому подобное. Особенно это актуально если над этим работает команда, а не единственный человек.


В этом свете становится очевидно удобным хранить настройки подключения к управляемым хостам прямо в этом же репозитории, помимо файла inventory/hosts (его вообще лучше вынести в какой-нибудь сервис вроде CMDBuild или похожие). То есть, если на хосте поменялся скажем порт подключения или IP адрес, остальные члены команды должны это получить при следующем подтягивании изменений из репозитория, а не вносили каждый изменения в свой файл ~/.ssh/config.


Причём большинство параметров будут работать должным образом без каких-то усилий, но не всё так просто если вы хотите использовать ssh-туннели.
Итак, предположим что мы сохранили конфиг в файл inventory/ssh.config.
В нём прописали основные подключения и базовые настройки:


Host *
IdentityFile /secret/id_rsa
User wheel

Host host.one
  HostName 1.2.3.4
  User ant

Host host.two
  HostName 2.3.4.5
  Port 2022

Ну и так далее, всё стандартно, как мы обычно это прописываем в конфиге уровня пользователя.


В ansible.cfg предлагается затем указать его относительный путь с помощью опции -F:


ssh_args = -o ControlMaster=auto -o ControlPersist=60s -F inventory/ssh.config

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


Большинство опций затем будет работать, однако, если вы попытаетесь использовать туннели, как-то так:


Host organization.gateway
  HostName 1.2.3.4

Host inner.host
  HostName 2.3.4.5
  ProxyCommand ssh organization.gateway -W %h:%p

Вы незамедлительно получите ошибку что не можете подключиться к хосту inner.host!


Как же так?
На самом деле всё просто. Ansible при коннекте просто вызывает бинарный файл ssh, передаёт ему указанные опции. Далее здесь указан туннель, для него ssh сделает форк и вызовек указанную команду как есть. Это может быть nc к слову или проброс портов с помощью iptables (firewalld). То есть никакой трактовки не производится. И это значит что будет вызвана команда:


ssh organization.gateway -W %h:%p

И в общем случае, ssh не сможет установить коннект, потому что он не знает что такое organization.gateway.


Конечно, мы могли бы исправить эту ситуацию указав и тут путь к файлу:


Host inner.host
  HostName 2.3.4.5
  ProxyCommand ssh -F /path/to/same/ssh.config organization.gateway -W %h:%p

Это заработает, но мы же не хотели привязываться к пути на конкретной машине, а хранить универсальный для всех файл!
Частичным решением проблемы будет указание относительного пути, но решает лишь отчасти, если вы хотите вызывать ansible по абсолютному пути из другой директории.


Т.к. ssh не предоставляет никакого наследования конфигов, для bash (в sh тоже должно работать, для других возможно незначительно модифицировать) мы можем это просто сэмулировать:


Host inner.host
  HostName 2.3.4.5
  ProxyCommand ssh $( egrep -z -A1 '^-F$' /proc/$PPID/cmdline ) organization.gateway -W %h:%p

Всё что мы изменили, поставили "$( egrep -z -A1 '^-F$' /proc/$PPID/cmdline )" и вся магия случится здесь. А значит это просто "тот же конфиг, с которым вызван основной ssh процесс".


Работает это просто: для порождаемого процесса подставляется значение опции -F, которое вычленяется из опций вызывающего.

Поделиться с друзьями
-->

Комментарии (5)


  1. evg_krsk
    11.08.2016 03:52

    Спасибо, очень интересный материал.


    А вы не решали задачу логического объединения inventory/ssh.config и ~/.ssh/config? Т.е. хочется иметь возможность напрямую заходить на те же хосты, что и ансибль и чтобы эти хосты были прописаны в одном месте.


    Вроде бы это без проблем решается в openssh-7.3 (там заявлена команда "Include" в ~/.ssh/config), но он пока мало распространён, увы.


    1. Hubbitus
      17.08.2016 23:39

      Действительно, такой баг на OpenSSH открыт аж с 2009 года — https://bugzilla.mindrot.org/show_bug.cgi?id=1585

      Я задавался таким вопросом, чтобы включать «этот в свой», есть разного рода костыли, основанные прежде всего на алиасе вместо ssh команды, которая по какому-либо шаблону или указанным путям собирает нужный дефолтный конфиг. Например несколько вариантов тут: http://superuser.com/questions/247564/is-there-a-way-for-one-ssh-config-file-to-include-another-one

      Честно сказать, хоть и очень хотел, использовать не стал — жду выхода 7.3 с нетерпением. В общем на тек уж сложно добавить один параметр -F, если нужно соединиться с коннектом из другого конфига. Хотя конечно и хочется удобнее.


      1. evg_krsk
        18.08.2016 18:30

        Зачем ждать когда уже зарелизилось ?


        Другое дело что в стабильных срезах его ждать ещё дооолго.


  1. clickfreak
    16.08.2016 21:24

    А dns вашу проблему не решает?


    В целом, довольно элегантное решение. Спасибо!


    1. Hubbitus
      17.08.2016 23:33

      Нет, DNS не решает проблему совершенно, ведь в примере «organization.gateway» это фактически ссылка на описанную конфигурацию. Помимо просто имени узла же вы там можете задать и порты, и пользователей, и отдельные ключи, и много другой информации как к нему надо подключиться.