Ansibleでインターネットに繋がらない環境へPythonライブラリをインストールする

インターネットに繋がらないHadoopクラスタの各slaveに対してAnsibleでPythonのライブラリを管理することを想定して試してみましたので、その時の内容を書いておきます。
hadoopクラスタへのpythonライブラリ追加として、とりあえず以下のユースケースを追加しています。 - 一つのノードに全てのライブラリをインストールする - 全てのノードに一つのライブラリをインストールする

それから要件として既にライブラリがインストール済であればインストールを実行しないようにもしたいと思います。
まず、作成したansibleプロジェクトのディレクトリ構成は以下のようになりました。

ansible
├── devserver.yml
├── install_one_library.yml
├── roles
│   └── python
│       ├── files
│       │   ├── install_shell
│       │   │   ├── build_install.sh
│       │   │   ├── install.sh
│       │   │   └── install_pip.sh
│       │   └── library
│       │       ├── cython-0.28.2.tar.gz
│       │       ├── numpy-1.11.0.tar.gz
│       │       ├── numpy-1.14.2.tar.gz
│       │       └── pip-10.0.1.tar.gz
│       ├── vars
│       │   └── library_version.yml
│       └── tasks
│           ├── init.yml
│           ├── install_cython.yml
│           ├── install_numpy.yml
│           ├── install_pip.yml
│           └── main.yml
├── staging.info
└── group_vars
    └── default.yml

それでは各ファイル毎の役割をまとめて行きたい思います。

group_vars/default.yml

---

ansible_home: "/var/ansible"
  • ライブラリのアップロード先などで使うディレクトリを定義します
  • 各roleで共通ということでプロジェクト直下の変数用にディレクトリ内にファイルを配置しています。

staging.info

[batch-servers]
ip[15:16].localdomain ansible_ssh_user=hoge
  • 通常のインベントリファイルと同じです
  • hadoopのslaveなど大量にインストールを行う場合は連番で指定するのが良さそうです

devserver.yml

---
- hosts: batch-servers
  become: yes
  vars_files:
    - vars/default.yml
    - roles/python/vars/library_version.yml
  roles:
    - python

# ansible-playbook devserver.yml -i staging.info --private-key=~/.ssh/id_rsa
# ansible-playbook devserver.yml -i staging.info --private-key=~/.ssh/id_rsa --limit ip15.localdomain
  • 全てのpythonライブラリをインストールするためのplaybookファイル
  • 特定のノードに入れたい場合は --limit でホストを直接指定します。

install_one_library.yml

---
- hosts: batch-servers
  become: yes
  vars_files:
    - vars/default.yml
    - roles/python/vars/library_version.yml
  tasks:
    - include: roles/python/tasks/init.yml
    - include: roles/python/tasks/install_{{ lib }}.yml
# ansible-playbook install_one_library.yml --extra-vars="lib=pip" -i staging.info --private-key=~/.ssh/id_rsa_nopass
  • 全てのノードに一つのライブラリをインストールするためのplaybookファイルです
  • 実行時に変数libを渡してインストール対象のたtaskを指定します。
  • 例えば --extra-vars="lib=pip" を指定したら roles/python/tasks/install_pip.yml のタスクを実行

roles/python/vars/library_version.yml

---
pip_version: 10.0.1
numpy_version: 1.14.2
cython_version: 0.28.2
  • インストールするライブラリのバージョンを管理します。

roles/python/files/libraryディレクト

  • インストール対象のライブラリを直接保存しておきます。
  • githubのreleaseからダウンロードしたものを置いておきます
  • git cloneしたものにtag指定でソースを取り出した場合たまに python setup.py --version があった

roles/python/files/install_shellディレクト

  • ライブラリインストール用のshellを配置します
  • shell内では library_version.yml で指定したバージョンがインストール済みかどうかを確認し、指定バージョンが入っていなければインストールします。

roles/python/tasksディレクト

  • ライブラリインストールのためのtaskファイルを管理します。 - main.yml 内では初期化用のファイル読み込みと全てのライブラリインストールのためのファイルをincludeします
  • init.yml は初期用のファイル

roles/python/tasks/main.yml

---

- include: roles/python/tasks/init.yml

- include: roles/python/tasks/install_cython.yml
- include: roles/python/tasks/install_numpy.yml
  • 初期化用と各ライブラリインストールのtaskを読み込んでいます。

roles/python/tasks/init.yml

---

- name: upload python library
  copy:
    src: ../files/library
    dest: "{{ ansible_home }}/python"
    mode: 0755

- name: upload python install shell
  copy:
    src: ../files/install_shell
    dest: "{{ ansible_home }}/python"
    mode: 0755

- name: be sure python-setuptools is installed
  yum: name=python-setuptools state=installed

- name: be sure python-devel is installed
  yum: name=python-devel state=installed


- include: roles/python/tasks/install_pip.yml
  • pythonライブラリインストールのために必要なモジュールのインストールを行います。
  • roles/python/files/library, roles/python/files/install_shellをアップロードします。
  • python-setuptoolsとpython-develは事前にインストールされている必要がある
  • pipも初期化処理でインストールします。

roles/python/tasks/install_pip.yml

---
- name: Extract pip
  command: tar zxvf "{{ ansible_home }}/python/library/pip-{{ pip_version }}.tar.gz" --directory "{{ ansible_home }}/python/library/"

- name: Install pip
  command: "{{ ansible_home }}/python/install_shell/install_pip.sh {{ ansible_home }}/python/library/pip-{{ pip_version }} pip"

 - pipのインストールを行います。

roles/python/files/install_shell/install_pip.sh

#!/bin/bash
LIBRARY_DIR=$1
LIBRARY=pip
IS_INSTALLED=0
cd $LIBRARY_DIR

set -e
trap 'setup_install' ERR


function setup_install {
  python setup.py install
}

SOURCE_VERSION=`python setup.py --version`

if test `python -m ${LIBRARY} -V | grep -oE "${SOURCE_VERSION}[^ ]*" | head -n1` == `echo $SOURCE_VERSION` ; then
  echo "already installed"
else
  echo "not installed"
  setup_install
fi
  • pipのインストール用shellです
  • インストール済みかどうかをチェックします

roles/python/tasks/install_numpy.yml

- include: roles/python/tasks/install_cython.yml

- name: Extract numpy
  command: tar zxvf "{{ ansible_home }}/python/library/numpy-{{ numpy_version }}.tar.gz" --directory "{{ ansible_home }}/python/library/"

- name: Install numpy
  command: "{{ ansible_home }}/python/install_shell/build_install.sh {{ ansible_home }}/python/library/numpy-{{ numpy_version }} numpy"
  • numpyのインストールを行います。
  • 依存するライブラリは includeでタスクを読み込むようにしています。

roles/python/files/install_shell/build_install.sh

#!/bin/bash
LIBRARY_DIR=$1
LIBRARY=$2
IS_INSTALLED=0
cd $LIBRARY_DIR

function find_installed_version {
    _LIBRARY=$1
    _VERSION_WITH_EQ=`pip freeze | grep $_LIBRARY | grep -oE "==.+"`
    echo ${_VERSION_WITH_EQ:2}
}

function setup_build_install {
  python setup.py build
  python setup.py install
}


INSTALLED_VERSION=`find_installed_version $LIBRARY`
SOURCE_VERSION=`python setup.py --version`

if test `echo $INSTALLED_VERSION` == `echo $SOURCE_VERSION` ; then
  echo "already installed"
else
  setup_build_install
fi
  • buildが必要な場合のinstall用shellです