vagrant + ansible + fabric でpythonの開発環境作る

# vagrant + ansible + fabric でpythonの開発環境作る

* vagrantで開発サーバ作って
* そこにansibleでpython環境作る
* flask + uwsgiを使用して
* fabricでデプロイできるように

みたいな。

あんま突っ込んだ内容には触れてないです。

中途半端だけど、一通りまとまった情報がなかなか無くてけっこう時間費やしたので、
その辺のまとめておいてもいいかなって。
ハマりどころとかtips的に。

最終的なファイル構成はこんな感じ。

```
.
├── Vagrantfile
├── ansible.cfg
├── fabfile.py
├── flask
│   ├── main.py
│   ├── reload.trigger
│   ├── runserver.py
│   └── uwsgi.ini
├── inventory
├── playbooks
│   └── python.yml
└── ssh.config
```

## vagrantで開発用のvmを作る

### box追加

__centos6__ って名前でvagrantのboxを追加。

```bash:bash
vagrant box add centos6 http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_centos-6.5_chef-provisionerless.box
```

### 設定

特に理由は無いけど2つ作ってみる。
rsyncでまとめてデプロイしてみたいし。

1個でよければ _node1_ だけでおｋ。

```ruby:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  config.vm.define :node1 do |node|
    node.vm.box = "centos6"
    node.vm.network :private_network, ip: "192.168.33.10"

    node.vm.provider :virtualbox do |vb|
        vb.customize ["modifyvm", :id, "--memory", "512"]
    end
  end

  config.vm.define :node2 do |node|
    node.vm.box = "centos6"
    node.vm.network :private_network, ip: "192.168.33.11"

    node.vm.provider :virtualbox do |vb|
        vb.customize ["modifyvm", :id, "--memory", "512"]
    end
  end

end
```

### vmの ssh-config を用意

sshで繋ぐ時に便利なので。

```bash:bash
vagrant ssh-config > ssh.config
```

中身はこんな感じになるはず。

```ssh.config
Host node1
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /Users/kuryu/.vagrant.d/insecure_private_key
  IdentitiesOnly yes
  LogLevel FATAL

Host node2
  HostName 127.0.0.1
  User vagrant
  Port 2200
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /Users/kuryu/.vagrant.d/insecure_private_key
  IdentitiesOnly yes
  LogLevel FATAL
```

### 起動

```bash:bash
vagrant up
```

## ansibleでpython環境作る

長くなるので記事分けた。
こっちを参照。
http://qiita.com/arc279/items/44ac688a2df24569f8af

インベントリはこんな感じ。

```ini:inventory
[servers]
node1
node2
```

↑の記事には書いてないけど、
ansible でsshする時にconfigを参照する設定を、ansibleの設定ファイルに書いておく。
ついでにインベントリファイルの指定も。

```ini:ansible.cfg
[defaults]
hostfile = inventory

[ssh_connection]
ssh_args = -F ssh.config
```

_ansible.cfg_ はカレントに置いておけば勝手に読んでくれる。

実行はこう。

```bash
ansible-playbook playbooks/python.yml
```

## flask + uwsgiを使用

### 本体

```py:main.py
# -*- coding:utf-8 -*-

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "hello flask!!"

@app.route("/foo")
def foo():
    return "foo"

@app.route("/foo/bar")
def foo_bar():
    return "foobar"
```

### Werkzeug 起動ファイル

```py:runserver.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from main import app

options = {
    "debug": True,
    "threaded": True,
    "host": "0.0.0.0",
}
app.run(**options)
```

Werkzeugでデバッグ起動するならコレに実行権限付けて叩けばおｋ。

### uwsgi 設定ファイル

_touch-reload_ で、touchされたらuwsgiをリロードする用のファイルを指定。
必要なら _daemonize_ の指定も入れるとよいと思う。

```ini:uwsgi.ini
[uwsgi]
socket = /tmp/flask.wsgi.sock
master = true
uid = vagrant
gid = vagrant
http = :5000
python-path = .      # uwsgi 起動ディレクトリがカレントになってる
wsgi = main:app
processes = 3
threads = 2
pidfile = ./flask.pid
touch-reload = ./reload.trigger
env = environment=development
```

なんかすごい不自然な書き方だけど、
ここで _env =_ __[key]=[value]__の形で指定しておけば、
起動時に

```py
import os
print os.environ["environment"]
```

で読める。

### uwsgi 起動

↑でansibleでpyenv作った時に一緒にpipでuwsgiも入れてるはずなので、

flaskのカレントで

```bash:bash
uwsgi uwsgi.ini
```

で起動するはず。

## fabric + rsync でデプロイ

rsyncは _syncする側_ と _syncされる側_ 両方に入ってないと駄目。

↑のansibleの記事でrsyncも一緒に入れてるので、
ちゃんとplaybookが通ってればvm側にrsync入ってるはず。

### ansibleのインベントリをfabricでも使用したい

詳細はこちらを参考に。
http://qiita.com/shivaken/items/c679ae9d15ac1cbebd0b

### ssh-config を参照したい

こう。

```py
env.use_ssh_config = True
env.ssh_config_path = "ssh.config"
```

_ssh-config_ の指定で _vagrant_ユーザで繋ぐようになってるので、
接続先の __$HOME__ は _/home/vagrant_ になってる。

なので、

* local_dir='./flask/',
手元のflaskを

* remote_dir='~/flask',
vmの _/home/vagrant/flask_ に

`rsync_project()` でrysncする。

```py:fabfile.py
from fabric.api import *
from fabric.decorators import roles
from fabric.contrib.project import rsync_project
import ansible.inventory

env.use_ssh_config = True
env.ssh_config_path = "ssh.config"

inventory = ansible.inventory.Inventory('inventory')
env.roledefs = inventory.groups_list()

def hostname():
    run("echo %s" % env.host)

def deploy():
    rsync_project(
        local_dir='./flask/',
        remote_dir='~/flask',
        exclude=['.DS_Tore', '*.tmp'],
        delete=True
    )
    reload()

@roles('servers')
def deployall():
    deploy()

def reload():
    run('touch ~/flask/reload.trigger')
```

### fab

#### ロールを指定して

_-R_ オプションでroleを指定。

ansibleのインベントリのグループ名がroleになってるらしい。
＃この辺あまりちゃんと調べてない。

```bash:bash
fab -R servers deploy
```

`@roles('servers')` デコレータ付けてるこっちでもおｋ。

```bash:bash
fab deployall
```

#### 単体ホスト名を指定して

```bash:bash
fab -H node1 deploy
```

## で

なんかまとまりの無い内容になってしまった。

デプロイはrsyncじゃなくて _git pull_ にするとか、
モダンなアレにしてもよいと思う。

もっとやるなら、

* ansibleで一緒にnginx入れてuwsgiに繋ぐとか
* uwsgi の start/stop もfabでできるようにするとか

いろいろあると思うけど、まぁその辺は_開発環境作る_ってことでオミット。