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)
clickfreak
16.08.2016 21:24А dns вашу проблему не решает?
В целом, довольно элегантное решение. Спасибо!
Hubbitus
17.08.2016 23:33Нет, DNS не решает проблему совершенно, ведь в примере «organization.gateway» это фактически ссылка на описанную конфигурацию. Помимо просто имени узла же вы там можете задать и порты, и пользователей, и отдельные ключи, и много другой информации как к нему надо подключиться.
evg_krsk
Спасибо, очень интересный материал.
А вы не решали задачу логического объединения inventory/ssh.config и ~/.ssh/config? Т.е. хочется иметь возможность напрямую заходить на те же хосты, что и ансибль и чтобы эти хосты были прописаны в одном месте.
Вроде бы это без проблем решается в openssh-7.3 (там заявлена команда "Include" в ~/.ssh/config), но он пока мало распространён, увы.
Hubbitus
Действительно, такой баг на 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, если нужно соединиться с коннектом из другого конфига. Хотя конечно и хочется удобнее.
evg_krsk
Зачем ждать когда уже зарелизилось ?
Другое дело что в стабильных срезах его ждать ещё дооолго.