Ansible の nsenter connection plugin, ansible-connection-nsenter を書いた¶
2015/04/29
動機¶
自宅で開発に利用している X41 が 32bit しか対応しておらず、 Docker に対応してなかった。
そのため、 Systemd-nspawn で環境を構築することにした。
開発環境を構築する際に Ansible を使いたいと思い、 Systemd-nspawn に対応する 接続方式がるのかと軽く確認したところ、 nsenter というコマンドがあったので、 使えたらよいと思い、ansible-connection-nsenter を書いた。
進めかた¶
Ansible の plugin は大雑把に以下の流れで作成できる。
それぞれ見ていこう。
1. Plugin を書く。¶
今回は GitHub の Ansible のリポジトリ、 lib/ansible/runner/connection_plugins/ssh.py をコピペして進めた。つくりもよくわかってなかったし。
後ほど chroot を見つけたのはご愛嬌。
Ansible で生にシェルを実行しようとするらしく、環境変数の取り回しや、パイプ、
&&
;
の取り扱いが難儀した。
環境変数¶
コンテナ内の環境変数は、以下のように /proc/<PID>/environ
からとれる。
ヌル文字列で区切られているようなので、区切って、あとは =
で切り分けて、辞書にして取り扱うようにした。
def _get_container_env(self):
'''return container env dict'''
# get environ path
env_path = '/proc/{}/environ'.format(self._extract_var('Leader'))
# get container env separated by null char
env_str = subprocess.check_output(shlex.split('cat ' + env_path))
# split null and = char
proc_envs = env_str.split('\0')
proc_envs = dict([x.split('=') for x in proc_envs if x])
return proc_envs
def _extract_var(self, key):
output = subprocess.check_output(['machinectl', 'show', self.host])
for row in output.split('\n'):
if key in row:
return row.strip().lstrip(key + '=')
&&
や ;
¶
&&
;
はその位置でとりあえず分割して、
個別にコマンドを実行するようにした。
()
とか ||
とかには対応してないが、とりあえず Ansible
が動くのでまずはよしとする。
# if multiple command then split it
if any(cmd.find(x) != -1 for x in ['&&', ';']):
# split set env and actual command
cmd_env, cmd = self._split_env(cmd)
# calc symbol position
pos_and = cmd.find('&&')
pos_sc = cmd.find(';') # semicolon
conn_and = False
conn_sc = False
if (pos_and != -1 and
((pos_sc != -1 and pos_and < pos_sc) or pos_sc == -1)):
pos = pos_and
post_pos = pos + 2
conn_and = True
else:
pos = pos_sc
post_pos = pos + 1
conn_sc = True
# parse cmd
cmd_pre, cmd_post = cmd[:pos].strip(), cmd[post_pos:].strip()
post_params = deepcopy(params)
params['cmd'] = ' '.join([cmd_env, cmd_pre]).strip()
post_params['cmd'] = ' '.join([cmd_env, cmd_post]).strip()
# exec cmd
result = self._exec_command(**params)
if conn_and and result[0] == 0:
return self._exec_command(**post_params)
elif conn_sc:
self._exec_command(**post_params)
return result
else:
return result
else:
return self._exec_command(**params)
2. ディレクトリに配置する。¶
README.rst に書いたように以下のように配置する。
$ sudo mkdir -p /etc/ansible/plugins/connection_plugins
$ curl https://raw.githubusercontent.com/jptomo/ansible-nsenter/master/nsenter.py -o /etc/ansible/plugins/connection_plugins/nsenter.py
PyPI において、 pip
で入れたときに自動で Ansible に認識されるような
仕組みができるとよいかと思ったが、依存で入って別に使わないけど影響しちゃうのもなあ
と思い、悩ましいと思う。
Sphinx でも pip
で入れた上に設定ファイルである conf.py
に書く必要があるので辛いと思ったことがあったが、
でも、依存で影響するのもとも思う。
3. Ansible に認識させる。¶
The Ansible Configuration File — Ansible Documentation を参考に、
ANSIBLE_CONFIG (an environment variable)
ansible.cfg (in the current directory)
.ansible.cfg (in the home directory)
/etc/ansible/ansible.cfg
4つのいずれかの connection_plugins
の設定でパスを認識させる。
今回は /etc/ansible/plugins/connection_plugins/
となる。
書く際は defaults
セクションに書く必要があるので、以下のように書く。
[defaults]
connection_plugins = /etc/ansible/plugins/connection_plugins/
使い方¶
個人的に勉強用に進めている compare-rdbms を見ると何となく使い方がわかると思う。
ansible.cfg
はトップディレクトリに配置し、このディレクトリで
ansible-playbook
を実行した場合、参照されるようにする。
ansible/local.yml
の通り、 connection
に nsenter
を指定すると
使われるようになるので、指定する。
後は、この connection
を利用してタスクが動く。