Ansibleを練習した

Python製の構成管理ツールのAnsibleを練習がてらに使ってみました。
既に素敵な記事がたくさんのあるようですし、同じようなことを書いたところで私の記事など足元にも及ばないので(感じ悪い)、以下の課題でやっていこうと思います。

  • VM(Vagrant)上にでDjangoの開発環境を作る
  • 1台構成だと寂しいのでデータベースは別のホストにする
  • できるだけ再利用できるようにする

環境

対象ホスト hostname OS IPアドレス
Webサーバ(Django) web CentOS6 192.168.33.10
DBサーバ db CentOS6 192.168.33.11

上記のサーバをVagrantで作成します。以下はVagrantfileの内容です。

VAGRANTFILE_API_VERSION = "2"
 
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
 
  config.vm.define :db do |db|
    db.vm.box = "chef/centos-6.5"
    db.vm.hostname = "db"
    db.vm.network :forwarded_port, guest: 22, host: 2001, id: "ssh"
    db.vm.network :private_network, ip: "192.168.33.11"
  end
 
  config.vm.define :web do |web|
    web.vm.box = "chef/centos-6.5"
    web.vm.hostname = "web"
    web.vm.network :forwarded_port, guest: 22, host: 2000, id: "ssh"
    web.vm.network :private_network, ip: "192.168.33.10"
  end
 
  config.vm.provision :ansible do |ansible|
    ansible.playbook = "site.yml"
    ansible.inventory_path = "hosts"
  end
 
end

~/.ssh/configに以下を追記します。

Host web 
  HostName 192.168.33.10
  User vagrant
  Port 2000
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /home/user/.vagrant.d/insecure_private_key
  IdentitiesOnly yes 
  LogLevel FATAL
 
Host db
  HostName 192.168.33.11
  User vagrant
  Port 2001
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /home/user/.vagrant.d/insecure_private_key
  IdentitiesOnly yes
  LogLevel FATAL

vagrant ssh-config >> ~/.ssh/config としただけでは
HostNameが127.0.0.1となっているためかうまくいかなかったので、IPアドレスの部分だけ変更してあります。

Playbook

site.ymlの処理を書いていきます。

- hosts: db
  user: vagrant
  sudo: yes
  tasks:
    # dbに対する処理をリスト形式で記述する

- hosts: web
  user: vagrant
  sudo: yes
  tasks:
    # webに対する処理をリスト形式で記述する

以下にtasksに記述する処理を列挙していきます。

[db/web] 共通の処理

どちらのサーバでも必要な処理

    # ファイルを転送するために必要らしい(selinux無効にしたのに)
    - name: selinux python
      yum: name=libselinux-python state=installed
 
    # iptablesは切る
    - name: iptables disabled
      service: name=iptables state=stopped enabled=False

[db] MySQLのインストール

    - name: install mysql-server
      yum: name=mysql-server state=installed

    - name: run mysql-server
      service: name=mysqld state=running enabled=yes
 
    # 文字コード的な問題を回避するために文字コード設定したmy.cnfを使いたい
    - name: transfer my.cnf
      copy: src=my.cnf.local dest=/etc/my.cnf
      notify: mysql restart
 
    # my.cnfが更新されたら再起動する
    - name: mysql restart
      service: name=mysqld state=restarted

コピー元の設定ファイルをmy.cnf.localとしてカレントパスに置きます。

[client]
port=3306
 
default-character-set = utf8
 
[mysqld]
port = 3306
 
default-character-set = utf8
default-storage-engine = innodb
character-set-server = utf8
collation-server = utf8_general_ci
 
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
symbolic-links=0
 
 
[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

[db] MySQLの操作

Djangoで使うためのユーザとデータベースを用意します。MySQLを操作するためのモジュールがあるので是非それを使いましょう。

    # MySQL-pythonのインストールに必要
    - name: install mysql-devel
      yum: name=mysql-devel state=installed
 
    # mysql_user,mysel_dbに必要
    - name: install MySQL-python
      yum: name=MySQL-python state=installed
 
    # ユーザ:testuser/testpassを作る
    - name: create mysql user
      mysql_user: name=testuser password=testpass priv=*.*:ALL state=present host={{item}}
      with_items:
        - '%'
        - 'localhost'
 
    # データベース:testdbを作る
    - name: create mysql database
      mysql_db: name=testdb state=present

Djangoを動かす

[web] Pythonのインストール

CentOS6ではデフォルトでPython2.6が入っていますが、色々とアレなのでPython2.7を入れましょう。

    # 依存ライブラリを入れる
    - name: install libs
      yum: name={{item}} state=installed
      with_items:
        - zlib-devel
        - bzip2-devel
        - openssl-devel
 
    # 既にPython2.7があるか確認して、結果をstatusに格納
    - name: check python2.7
      shell: test -a /usr/local/bin/python2.7
      register: status
      ignore_errors: True
 
    # ダウンロードする(Python2.7がないときだけ)
    - name: download python2.7
      when: status|failed
      get_url: url=http://www.python.org/ftp/python/2.7/Python-2.7.tgz dest=/usr/local/src/
 
    # コンパイルに必要
    - name: install gcc 
      yum: name=gcc state=installed
 
    # コンパイル(Python2.7がないときだけ) 結構時間かかるよ!
    - name: compile python2.7
      when: status|failed
      shell: |
        cd /usr/local/src/
        tar fxz Python-2.7.tgz
        cd Python-2.7
        ./configure --with-threads --prefix=/usr/local
        make && make altinstall

    - name: link python
      shell: |
        ln -sf /usr/local/bin/python2.7 /usr/bin/python2.7
        ln -sf /usr/local/bin/python2.7 /usr/local/bin/python

/usr/bin/pythonはシステムで使われているらしく、書き換えるとyumが動かなくなるので注意しましょう。
(環境変数的なものをいじったら動くかもしれません)

[web] pipのインストール

まだpipが入ってないので手動でインストールします。

    - name: download get-pip
      get_url: url=https://bootstrap.pypa.io/get-pip.py dest=/usr/local/src
    
    - name: install pip 
      shell: |
        cd /usr/local/src
        /usr/local/bin/python get-pip.py
        ln -sf /usr/local/bin/pip /usr/bin/pip
            
    # virtualenvをインストール
    - name: install venv
      pip: name=virtualenv

[web] Djangoのインストール

pipモジュールでvirtualenv上にdjangoをインストールします。

    # djangoをインストール
    - name: install django
      pip: name=django virtualenv=/vagrant/venv-project virtualenv_command=/usr/local/bin/virtualenv-2.7
      shell=/usr/local/bin/virtualenv-2.7
 
    # mysql-pythonをインストールするために必要
    - name: install mysql-devel
      yum: name=mysql-devel state=installed
 
    # MySQLドライバ
    - name: install mysql-python
      pip: name=mysql-python virtualenv=/vagrant/venv-project virtualenv_command=/usr/local/bin/virtualenv-2.7

プロジェクト関連のファイルは本来リポジトリからcloneするものだと思いますが、そんなものはないのでAnsibleでやります。

    # プロジェクトディレクトリの有無を確認
    - name: check project dir
      shell: test -a /vagrant/myproject
      register: status
 
    # プロジェクトディレクトを作る
    - name: mkdir project directory
      shell: mkdir -p /vagrant/myproject
      when: status|failed
 
    # プロジェクトディレクトリにdjangoのプロジェクトを作成する
    - name: start project
      shell: /vagrant/venv-project/bin/django-admin startproject myproject /vagrant/myproject
      ignore_errors: True
      when: status|failed
 
    # settings.pyをコピーする
    - name: copy settings.py
      copy: src=settings.py.local dest=/vagrant/myproject/myproject/settings.py
      when: status|failed

    - name: create tables
      shell: /vagrant/venv-project/bin/python /vagrant/myproject/manage.py migrate

settings.py.localは以下の内容です。DATABASESしか変えてません。

import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
 
SECRET_KEY = '=5to#)i*pq^mjyguv1lq1j9dqm))4*1k_51)*gs8@%1pz=f$es'
DEBUG = True
 
TEMPLATE_DEBUG = True
 
ALLOWED_HOSTS = []
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)
MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'myproject.urls'
WSGI_APPLICATION = 'myproject.wsgi.application'
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'testdb',
        'USER': 'testuser',
        'PASSWORD': 'testpass',
        'HOST': '192.168.33.11',
        'PORT': 3306,
    }
}
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = '/static/'

site.ymlができたのでVMを起動してみます。

$ vagrant up
$ ssh vagrant@192.168.33.11
$ mysql -utestuser -ptestpass testdb
mysql> -- [web] manage.py migrateでテーブルが作成されている
mysql> show tables;
+----------------------------+
| Tables_in_testdb           |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+
10 rows in set (0.00 sec)

mysql> Ctrl-C -- exit!

$ ssh vagrant@192.168.33.10
$ /vagrant/venv-project/bin/python /vagrant/myproject/manage.py runserver 0.0.0.0:8000
Django version 1.7.1, using settings 'myproject.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

起動までできました。

再利用する

再利用しやすくするためにroleという機能を使用します。

単純なincludeでは特定のセクションに依存した記述しかできません。
例えばvarsセクションはtasksセクション内で使えないため「tasksセクションでincludeされたyamlの中で変数(vars)を宣言する」などは不可能です。

roleを定義するにはrolesというディレクトリ配下に機能毎のディレクトリ、さらにその下にtasks,vars,defaultsといったディレクトリを配置します。使用するにはrolesモジュール内で機能名を羅列するだけです。

では先ほどのPlaybookをrolesに分けてみます。

roles/iptables

とりあえずデフォルトで有効。無効化するときは呼び出し元で明示的に指定させることにします。

tasks/main.yml
- name: iptables switch
  service: name=iptables state={{iptables_state}} enabled={{iptables_enabled}}
defaults/main.yml
iptables_state: running
iptables_enabled: True

roles/selinux

tasks/main.yml
- name: include redhat
  when: ansible_os_family == "RedHat"
  include: redhat.yml
tasks/redhat.yml
- name: selinux python
  yum: name=libselinux-python state=installed

roles/mysql

tasks/main.yml
- name: include redhat
  when: ansible_os_family == "RedHat"
  include: redhat.yml

- name: copy my.cnf
  template: src=my.cnf.j2 dest=/etc/my.cnf
  changed_when: False
  notify: mysqld restart
tasks/redhat.yml
- name: install mysql-server
  yum: name=mysql-server state=installed

- name: install mysql-devel
  yum: name=mysql-devel state=installed

- name: install MySQL-python
  yum: name=MySQL-python state=installed

- name: run mysql-server
  service: name=mysqld state=running enabled=yes
handlers/main.yml

handlersセクションの内容をこのyamlに書きましょう。

- name: mysqld restart
  service: name=mysqld state=restarted
templates/my.cnf.j2

設定ファイルの中身も動的に変更したいので、モジュールはcopyではなくtemplateを使うことにしました。
使うテンプレートをtemplatesディレクトリに配置すると同じroleから参照できます。

[client]
port = {{mysql_client_port}}
 
default-character-set = {{mysql_client_charset}}
 
{{mysql_client_section}}
 
 
[mysqld]
port = {{mysql_server_port}}
 
default-character-set = {{mysql_server_default_charset}}
default-storage-engine = {{mysql_storage_engine}}
character-set-server = {{mysql_server_charset}}
collation-server = {{mysql_collation_server}}
 
datadir = {{mysql_datadir}}
socket = {{mysql_socket}}
user = {{mysql_user}}
symbolic-links = {{mysql_symbolic_links}}
 
{{mysql_mysqld_section}}
 
 
[mysqld_safe]
log-error = {{mysql_log_error}}
pid-file = {{mysql_pid_file}}
 
{{mysql_mysqld_safe_section}}
defaults/main.yml

「mysql」role内で有効なデフォルト値を定義していきます。

mysql_client_port: 3306
mysql_client_charset: utf8

mysql_server_port: 3306
mysql_server_default_charset: utf8
mysql_server_charset: utf8
mysql_storage_engine: innodb
mysql_collation_server: utf8_general_ci

mysql_client_section: ''
mysql_mysqld_section: ''
mysql_mysqld_safe_section: ''

mysql_symbolic_links: 0

mysql_datadir: /var/lib/mysql
mysql_socket: /var/lib/mysql/mysql.sock
mysql_user: mysql

mysql_log_error: /var/log/mysqld.log
mysql_pid_file: /var/run/mysqld/mysqld.pid

もし上記と異なる値を使いたいときは呼び出し元などで定義すれば上書きされます。
そのため変数名は重複しないものをつけるのが良いでしょう。

roles/python

tasks/main.yml
- name: include redhat
  when: ansible_os_family == "RedHat"
  include: redhat.yml

- name: check python
  shell: test -a /usr/local/bin/python{{python_ver}}
  register: status
  ignore_errors: True

- name: download python
  when: status|failed
  get_url: url={{python_src}} dest=/usr/local/src/

- name: compile python
  when: status|failed
  shell: |
    cd /usr/local/src/
    tar fxz Python-{{python_ver}}{{python_ver_patch}}{{python_ver_dev}}.tgz
    cd Python-{{python_ver}}{{python_ver_patch}}{{python_ver_dev}}
    ./configure --with-threads --prefix=/usr/local
    make && make altinstall

- name: link python
  shell: |
    ln -sf /usr/local/bin/python{{python_ver}} /usr/bin/python{{python_ver}}
    ln -sf /usr/local/bin/python{{python_ver}} /usr/local/bin/python

- name: download get-pip
  get_url: url=https://bootstrap.pypa.io/get-pip.py dest=/usr/local/src

- name: install pip 
  shell: |
    cd /usr/local/src
    /usr/local/bin/python get-pip.py
    ln -sf /usr/local/bin/pip /usr/bin/pip

- name: install virtualenv
  changed_when: False
  pip: name=virtualenv
tasks/redhat.yml
- name: install gcc 
  yum: name=gcc state=installed

- name: install libs
  yum: name={{item}} state=installed
  with_items:
    - zlib-devel
    - bzip2-devel
    - openssl-devel
 
- name: install python-devel
  yum: name=python-devel state=installed
defaults/main.yml
python_ver: '2.7'
python_ver_patch: ''
python_ver_dev: ''
vars/main.yml

正直defaultsでもいいかなと思ってます。

python_src: "http://www.python.org/ftp/python/{{python_ver}}{{python_ver_patch}}/Python-{{python_ver}}{{python_ver_patch}}{{python_ver_dev}}.tgz"
setuptools_filename: "setuptools-0.6c11-py{{python_ver}}.egg"
setuptools_src: "http://pypi.python.org/packages/{{python_ver}}/s/setuptools/{{setuptools_filename}}"

site.yml

上記のroleを使うようにsite.ymlを変更してみました。

空のプロジェクトとしてproject_model/requirements.txtを配置しました。これをprovision時にディレクトリごとコピーします。

Django==1.7.1
MySQL-python==1.2.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
- hosts: db
  user: vagrant
  sudo: yes

  vars:
    iptables_state: stopped
    iptables_enabled: False

  roles:
    - selinux
    - iptables
    - mysql

  tasks:
    - name: create mysql user
      mysql_user: name=testuser password=testpass priv=*.*:ALL state=present host={{item}}
      with_items:
        - '%'
        - 'localhost'

    - name: create mysql database
      mysql_db: name=testdb state=present

- hosts: web
  user: vagrant
  sudo: yes

  vars:
    project_name: myproject
    project_dir: "/vagrant/{{project_name}}"
    project_venv: /vagrant/venv-project
    venv_command: /usr/local/bin/virtualenv-2.7
    iptables_state: stopped
    iptables_enabled: False
 
  # tasksより後ろに書いても先に実行される
  roles:
    - selinux
    - iptables
    - python

  tasks:
    - name: check project dir
      shell: test -a {{project_dir}}
      ignore_errors: True
      register: status
    
    - name: copy project dir
      shell: cp -rp /vagrant/project_model {{project_dir}}
      when: status|failed

    - name: install mysql-devel
      yum: name=mysql-devel state=installed

    - name: install requirements.txt
      pip: requirements={{project_dir}}/requirements.txt virtualenv={{project_venv}} virtualenv_command={{venv_command}}

    - name: start project
      shell: "{{project_venv}}/bin/django-admin startproject {{project_name}} {{project_dir}}"
      ignore_errors: True
      when: status|failed

    - name: copy settings.py
      copy: src=settings.py.local dest={{project_dir}}/{{project_name}}/settings.py
      when: status|failed

    - name: create tables
      shell: "{{project_venv}}/bin/python {{project_dir}}/manage.py migrate"

だいぶスッキリしましたね。実行結果は先ほどと同じでした。

これからGitHubで管理していきます。

参考リンク:
http://yteraoka.github.io/ansible-tutorial/
http://www.kyoshida.jp/ansibledoc-ja/