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, которое вычленяется из опций вызывающего.
Автор: Hubbitus