Python製の構成管理ツールのAnsibleを練習がてらに使ってみました。
既に素敵な記事がたくさんのあるようですし、同じようなことを書いたところで私の記事など足元にも及ばないので(感じ悪い)、以下の課題でやっていこうと思います。
- VM(Vagrant)上にでDjangoの開発環境を作る
- 1台構成だと寂しいのでデータベースは別のホストにする
- できるだけ再利用できるようにする
環境
対象ホスト | ホスト名 | OS | IPアドレス |
---|---|---|---|
Webサーバ | web | CentOS 6 | 192.168.33.10 |
DBサーバ | db | CentOS 6 | 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 [email protected]
$ 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 [email protected]
$ /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"
だいぶスッキリしましたね。実行結果は先ほどと同じでした。
参考
http://yteraoka.github.io/ansible-tutorial/