fabricでデプロイ自動化


ブログにも書いたので[fabricでデプロイ自動化](http://u-kan.hatenadiary.jp//entry/fabric-deploy)

独学マンなので、書き方等ご指摘頂けますと幸いです。

## 背景

- python3.6で書かれたjobを、ec2上に構築
- [shcedule](https://github.com/dbader/schedule)でスケジューリング
- [supervisor](http://supervisord.org/)でdaemon化

までできていた。デプロイ作業を失敗しそうなので、自動化した時の備忘録。自動デプロイツールとしてfabricを利用。


ちなみに現状、fabric本家はpython3系をサポートいません。[参考： FabricのPython3への対応について](https://qiita.com/acro5piano/items/19a5f4c0ae729aae756b)そのためforkされたprojectから、インストールする必要があります。

```
pip3 install fabric3
```


## 

ディレクトリ構成はこんな感じ。シンプル。

```
...
├── fabfile
│   ├── __init__.py  # module読み込み
│   ├── deploy.py    # 基本処理の記述
│   ├── envs.py      # 基本設定を記述
...
```

### 基本処理の記述

```fabfile/deploy.py
from fabric.api import run, abort, env, cd, local
from fabric.decorators import task


class LocalHandler(object):
    """localでのデプロイ周りの処理"""

    def push(self):
        """localの変更をgithubへpushする
        commitまではされている想定
        """
        self.__check_has_commited()
        self.__check_on_master()
        self.__update_requirements()
        local('git push origin master')

    def __update_requirements(self):
        """requirements.txtをupdateするスクリプト"""
        req_txt = 'requirements.txt'
        local('pip freeze > {}'.format(req_txt))
        res = local('git ls-files -m', capture=True)
        if res == req_txt:
            local('git add {}'.format(req_txt))
            local('git commit -m "update requirements.txt"')

    def __check_has_commited(self):
        """変更がちゃんとcommitされているかを確認"""
        res = local('git ls-files -m', capture=True)
        if res:
            abort('there are some diff on git supervised files')

    def __check_on_master(self):
        """master branchにいるか確認"""
        branch = local('git rev-parse --abbrev-ref HEAD', capture=True)
        if branch != 'master':
            abort('you should call this on master branch')


class RemoteHandler(object):

    def pull(self):
        """master branchに移動してソースコードの更新"""
        with cd(env.app_path):
            self.__check_no_change()
            run('git checkout master')
            run("git pull origin master")

    def restart_daemon(self):
        """svcを使って、restartさせる"""
        self.__install_required_package()
        res = run('supervisorctl restart {}'.format(env.daemon_name))
        if res.failed:
            abort('REMOTE: fail restart daemon {}'.format(env.daemon_name))
        return res

    def __install_required_package(self):
        req_txt = 'requirements.txt'
        with cd(env.app_path):
            run('pip3 install -r {}'.format(req_txt))

    def __check_no_change(self):
        """変更がちゃんとcommitされているかを確認"""
        res = run('git ls-files -m')
        if res:
            abort('REMOTE: there are some diff on git supervised files')

@task
def deploy():
    LocalHandler().push()
    rh = RemoteHandler()
    rh.pull()
    res = rh.restart_daemon()
    if res.succeeded:
        print("SUCCESS!! WELL DONE")
    else:
        print("TRY ONcE MORE :D")
```

### 基本設定を記述


```fabfile/envs.py
from fabric.api import env
from fabric.decorators import task

@task
def production():
    """ 本番 """
    env.environment = "production"
    env.app_path = '/path/to/project_root'
    env.daemon_name = 'supervisor daemon_name'
    env.user = 'ec2-user'
    env.hosts = ['host_name']
    env.key_filename = '~/.ssh/key.pem'

```


### module読み込み

このままだと、プロジェクトルートからfabricのタスクを呼べない。__init__.pyにimport書いてモジュール化する。

```fabfile/__init__.py
from fabfile.envs import production
from fabfile.deploy import deploy
```

### 実際に自動デプロイをする

下記のコマンドをローカルで叩く。

```/path/to/project_root
$ fab production deploy
```