2014-12-22

Ansibleを練習した

Python製の構成管理ツールのAnsibleを練習がてらに使ってみました。

既に素敵な記事がたくさんのあるようですし、同じようなことを書いたところで私の記事など足元にも及ばないので(感じ悪い)、以下の課題でやっていこうと思います。

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

環境

対象ホストホスト名OSIPアドレス
WebサーバwebCentOS 6192.168.33.10
DBサーバdbCentOS 6192.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 に以下を追記します。

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
- 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"

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

参考

Ansible チュートリアル | Ansible Tutorial in Japanesehttp://yteraoka.github.io/ansible-tutorial/