構成管理ツールで Ansible を使ってる人必見！デプロイするなら Ansistrano ロールを使おう！


## 前書き

デプロイツールは Ruby 製の Capistrano や、Python 製の Fabric、 PHP 製の Deployer など様々あります。
筆者は構成管理ツールで Ansible を使っているため、Ansible でデプロイ出来れば色々と都合がいいです。
そんな時に出会ったのが Ansistrano でした。
Ansible をある程度使える方なら簡単にデプロイ出来てしまうのでオススメです。

## Ansistrano とは

[ansistrano/deploy: Ansible role to deploy scripting applications like PHP, Python, Ruby, etc. in a capistrano style](https://github.com/ansistrano/deploy "ansistrano/deploy: Ansible role to deploy scripting applications like PHP, Python, Ruby, etc. in a capistrano style")

PHP 、Python や Ruby などのようなスクリプトアプリケーションのデプロイプロセスを簡単に管理するための Ansible ロールです。Capistrano スタイルのデプロイが Ansible で可能になります。デプロイを行う `ansistrano.deploy` とデプロイのロールバックを行う `ansistrano.rollback` の 2 つのロールで構成されています。Ansistrano というプロジェクト名は Ansible + Capistrano から来てます。

## インストール

Ansistrano は、Ansible Galaxy を使用したグローバルな分散型 Ansible ロールです。以下のコマンドでインストール出来ます。

```
$ ansible-galaxy install carlosbuenosvinos.ansistrano-deploy carlosbuenosvinos.ansistrano-rollback
```

## アップデート

ロールを更新したい場合は、インストール時に `--force` パラメータを通す必要があります。以下のコマンドでアップデート出来ます。

```
ansible-galaxy install --force carlosbuenosvinos.ansistrano-deploy carlosbuenosvinos.ansistrano-rollback
```

## サーバ側のディレクトリ構造

デプロイが完了するとサーバ側のディレクトリ構造は以下のようになります。指定したディレクトリ以下にcurrent/releases/sharedという3つのディレクトリができあがります。Webサーバ側ではcurrentを指定しておくことで、デプロイされる度にディレクトリを自動で切り替えてくれるようになります。

```tree:tree
.
├── current -> ./releases/20160530055905
├── releases
│   ├── 20160530055905
│   │   ├── cache -> ../../shared/cache
│   │   ├── logs -> ../../shared/logs
│   │   ├── tmp -> ../../shared/tmp
│   │   ...
│   ├── 20160530055658
│   │   ...
│   └── 20160530050508
│       ...
└── shared
    ├── cache
    ├── logs
    └── tmp
```

### current

`current` ディレクトリは、シンボリックリンクとなっており、`releases` ディレクトリ以下に作成された特定のディレクトリを指します。ウェブサーバやアプリケーションサーバの設定ではドキュメントルートにこのディレクトリを指すようにします。

### releases

`releases` ディレクトリには、アプリケーションコードを格納します。デプロイごとに現在日時 ( timestamp ) を名前とするディレクトリを作成し、その中にコードを格納します。過去のデプロイによるディレクトリを保持しているので、`current` ディレクトリのリンク先を変えることで異なるバージョンのコードを動かすことが出来ます。 `ansistrano_shared_paths` パラメータの数を超えたものは古いものから削除されていきます。

### shared

`shared` ディレクトリには、`releases` ディレクトリ以下に作成された各バージョンのアプリケーションコードが利用するファイルやディレクトリ ( セッションやキャッシュ、ログなど ) を格納します。各バージョンのアプリケーションコードが利用するファイルやディレクトリのシンボリックリンクが `shared` ディレクトリ以下のファイルやディレクトリを指すようにし同じ内容が参照できるようにしています。

## ロール変数

```yaml:vars
- vars:
  ansistrano_deploy_from: "{{ playbook_dir }}" # Where my local project is (relative or absolute path)
  ansistrano_deploy_to: "/var/www/my-app" # Base path to deploy to.
  ansistrano_version_dir: "releases" # Releases folder name
  ansistrano_current_dir: "current" # Softlink name. You should rarely changed it.
  ansistrano_current_via: "symlink" # Deployment strategy who code should be deployed to current path. Options are symlink or rsync
  ansistrano_shared_paths: [] # Shared paths to symlink to release dir
  ansistrano_keep_releases: 0 # Releases to keep after a new deployment. See "Pruning old releases".
  ansistrano_deploy_via: "rsync" # Method used to deliver the code to the server. Options are copy, rsync, git, s3 or download.
  ansistrano_allow_anonymous_stats: yes

  # Variables used in the rsync deployment strategy
  ansistrano_rsync_extra_params: "" # Extra parameters to use when deploying with rsync in a single string. Although Ansible allows an array this can cause problems if we try to add multiple --include args as it was reported in https://github.com/ansistrano/deploy/commit/e98942dc969d4e620313f00f003a7ea2eab67e86
  ansistrano_rsync_set_remote_user: yes # See [ansible synchronize module](http://docs.ansible.com/ansible/synchronize_module.html). Options are yes, no.

  # Variables used in the Git deployment strategy
  ansistrano_git_repo: git@github.com:USERNAME/REPO.git # Location of the git repository
  ansistrano_git_branch: master # What version of the repository to check out. This can be the full 40-character SHA-1 hash, the literal string HEAD, a branch name, or a tag name
  ansistrano_git_identity_key_path: "" # If specified this file is copied over and used as the identity key for the git commands, path is relative to the playbook in which it is used

  # Variables used in the download deployment strategy
  ansistrano_get_url: https://github.com/someproject/somearchive.tar.gz

  # Variables used in the S3 deployment strategy
  ansistrano_s3_bucket: s3bucket
  ansistrano_s3_object: s3object.tgz # Add the _unarchive suffix to the ansistrano_deploy_via if your object is a package (ie: s3_unarchive)
  ansistrano_s3_region: eu-west-1
  # Optional variables, omitted by default
  ansistrano_s3_aws_access_key: YOUR_AWS_ACCESS_KEY
  ansistrano_s3_aws_secret_key: YOUR_AWS_SECRET_KEY

  # Hooks: custom tasks if you need them
  ansistrano_before_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-setup-tasks.yml"
  ansistrano_after_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-setup-tasks.yml"
  ansistrano_before_update_code_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-update-code-tasks.yml"
  ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-update-code-tasks.yml"
  ansistrano_before_symlink_shared_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-shared-tasks.yml"
  ansistrano_after_symlink_shared_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-shared-tasks.yml"
  ansistrano_before_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-tasks.yml"
  ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-tasks.yml"
  ansistrano_before_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-cleanup-tasks.yml"
  ansistrano_after_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-cleanup-tasks.yml"
```

| 変数                                    | 意味                                                                |
|:----------------------------------------|:--------------------------------------------------------------------|
| `ansistrano_deploy_from`  | ansistrano_deploy_via が copy または rsync の場合の、ローカル上のデプロイ元アプリケーションコードのパス。 |
| `ansistrano_deploy_to` | リモートサーバ上のデプロイ先のパス。 |
| `ansistrano_shared_paths`   | releases ディレクトリ以下に保持する世代数。 |
| `ansistrano_deploy_via`      | アプリケーションコードのデプロイ方法。copy, rsync, git, s3, s3_unarchive, unarchive から指定。 |
| `ansistrano_allow_anonymous_stats` | Ansistrano のサイトに利用状況を送信するか否か。 |
| `ansistrano_git_repo`      | コードを取得するGitリポジトリ。 |
| `ansistrano_git_branch`      | コードを取得するブランチ。 |
| `ansistrano_git_identity_key_path` | リモートサーバー上にある Git リポジトリへの ssh 接続に利用する秘密鍵のパス。 |

## メインワークフロー

Ansistrano はデプロイにおいて Capistrano のフローをフォローしています。  
以下ダイアグラムの灰色箇所がメインワークフローの各ステップで、黄色箇所が各ステップの前後に実行されるカスタムタスクのフックポイントです。

![Ansistrano フロー](https://raw.githubusercontent.com/ansistrano/deploy/master/docs/ansistrano-flow.png "Ansistrano フロー")

### ステップ

メインワークフローのフェーズは以下の 5 ステップで構成されています。

| ステップ       | 意味                                                                                           |
|:--------------|:-----------------------------------------------------------------------------------------------|
| `Setup`       | `releases` ディレクトリ以下に現在日時 ( timestamp ) を名前とするディレクトリを作成します。              |
| `Update Code` | `Setup` ステップ で作成したディレクトリにアプリケーションコードを設置します。    |
| `Symlink Shared` | `ansistrano_shared_paths` パラメータに基づいて、シンボリックリンクが `shared` ディレクトリ以下のファイルやディレクトリを指すようにします。 |
| `Symlink`     | `current` ディレクトリのシンボリックリンクが `Setup` ステップ で作成したディレクトリを指すように変更します。 |
| `Cleanup`     | `ansistrano_keep_releases` パラメータに基づいて、任意の古いバージョンを削除します。                    |

### カスタムタスクのフック

`ansistrano_before_*_tasks_file` と `ansistrano_after_*_tasks_file` パラメータに、各ステップの前後にカスタムタスクファイルを指定することができます。  
任意のフックポイントで任意のタスクを実行することが可能です。

```yaml:vars
# Hooks: custom tasks if you need them
ansistrano_before_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-setup-tasks.yml"
ansistrano_after_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-setup-tasks.yml"
ansistrano_before_update_code_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-update-code-tasks.yml"
ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-update-code-tasks.yml"
ansistrano_before_symlink_shared_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-shared-tasks.yml"
ansistrano_after_symlink_shared_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-shared-tasks.yml"
ansistrano_before_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-tasks.yml"
ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-tasks.yml"
ansistrano_before_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-cleanup-tasks.yml"
ansistrano_after_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-cleanup-tasks.yml"
```

## Ansistrano の使い方

### デプロイ

#### playbook の準備

デプロイするアプリケーションは PHP アプリケーションを想定しています。

```tree:playbook
.
├── deploy.yml
├── group_vars
│   └── all.yml
├── hosts
└── roles
    └── hooks
        └── tasks
            ├── after_update_code.yml
            ├── before_symlink_shared.yml
            └── after_symlink.yml
```

##### hosts ファイル

```file:hosts
www.example.com
```

##### vars ファイル

`group_vars` で変数を定義していますが、別の場所で定義しても問題ありません。

```yaml:group_vars/all.yml
---
ansistrano_deploy_to: /var/www/app
ansistrano_shared_paths:
  - cache
  - logs
  - tmp
ansistrano_allow_anonymous_stats: no
ansistrano_keep_releases: 5
ansistrano_deploy_via: git
ansistrano_git_repo: git@github.com:USERNAME/REPO.git
ansistrano_git_branch: master
ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/roles/hooks/task/after_update_code.yml"
ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/roles/hooks/tasks/before_symlink_shared.yml"
ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/roles/hooks/task/after_symlink.yml"
```

##### playbook

[Ansistranoでnode.jsのアプリケーションをインストールする - Qiita](http://qiita.com/NewGyu/items/4ad49976ffc5f6f60f5d "Ansistranoでnode.jsのアプリケーションをインストールする - Qiita")

> `ansistrano_deploy_via` については Ansistrano の中で include を使ったトリックが使われているのでvars_filesではうまく行きませんでした。

の通り、 vars_files や group_vars で `ansistrano_deploy_via` を定義すると、パラメータ関係なしに rsync で実行されてしまいます。そのためトップレベル YAML の vars で定義しています。

```yaml:deploy.yml
---
- hosts: all
  become: yes
  become_user: www-data
  vars:
    ansistrano_deploy_via: git
  roles:
    - carlosbuenosvinos.ansistrano-deploy
```

##### フックで実行されるカスタムタスク

`Update Code` ステップの後で実行されるカスタムタスク。

```yaml:roles/hooks/tasks/after_update_code.yml
---
- name: ANSISTRANO | HOOKS | Whether or not the composer is present
  stat: path=/usr/bin/composer
  register: is_composer

- name: ANSISTRANO | HOOKS | Install composer
  shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer creates=/usr/bin/composer
  become_user: root
  when: not is_composer.stat.exists

- name: ANSISTRANO | HOOKS | Update composer
  become: yes
  become_user: root
  composer:
    command=install
    working_dir={{ ansistrano_release_path.stdout }}
```

`Symlink` ステップの前で実行されるカスタムタスク。

```yaml:roles/hooks/tasks/before_symlink_shared
---
- name: ANSISTRANO | HOOKS | Create shared paths
  file: state=directory path={{ ansistrano_deploy_to }}/shared/{{ item }} mode=0777
  with_items: ansistrano_shared_paths
```

`Symlink` ステップの後で実行されるカスタムタスク。

```yaml:roles/hooks/tasks/after_symlink.yml
---
- name: ANSISTRANO | HOOKS | Restart nginx
  service: name=nginx state=restarted
  become: yes
  become_user: root
```

カスタムタスクのファイルを記述する場合は、必要とされるいくつかの変数を使うことができます。

| 変数                                    | 意味                                                                |
|:----------------------------------------|:--------------------------------------------------------------------|
| `{{ ansistrano_release_path.stdout }}`  | `current` ディレクトリのリンク先のパス |
| `{{ ansistrano_releases_path.stdout }}` | `releases` ディレクトリへのパス                                     |
| `{{ ansistrano_shared_path.stdout }}`   | `shared` ディレクトリへのパス                                       |
| `{{ ansistrano_release_version }}`      | `current` ディレクトリのリンク先のディレクトリ名 ( timestamp ) |

#### 実行

```
ansible-playbook -i hosts deploy.yml
```

全てが正しく設定されている場合、このコマンドは、サーバー側で以下のようなディレクトリ構造を作成します。

```tree:/var/www/app
.
├── current -> ./releases/20160530055905
├── releases
│   ├── 20160530055905
│   │   ├── cache -> ../../shared/cache
│   │   ├── logs -> ../../shared/logs
│   │   ├── tmp -> ../../shared/tmp
│   │   ...
│   ├── 20160530055658
│   │   ...
│   └── 20160530050508
│       ...
└── shared
    ├── cache
    ├── logs
    └── tmp
```

### 複数のサーバーにデプロイ

各サーバーの `releases` ディレクトリ以下に異なる timestamp でディレクトリが作成されることを防止するために `serial` オプションを使用します。 `serial` オプションには `ansistrano_release_version` を設定し実行します。

```
$ ansible-playbook -i hosts -e "ansistrano_release_version=`date -u +%Y%m%d%H%M%S`" deploy.yml
```


### 多段環境のデプロイ

development 、staging や production などのような様々な環境にデプロイする場合は、異なるインベントリーファイルと変数ファイルを作成します。作成したら、 デプロイしたい環境のインベトリーファイルを `-i` パラメータを使って指定し実行します。　

#### playbook の準備

staging と production 環境の場合は以下のようになります。

```tree:playbook
.
├── production         # inventory file for production servers
├── staging            # inventory file for staging environment
├── group_vars
│   ├── all.yml
│   ├── production.yml # variables file for production groups
│   └── staging.yml    # variables file for staging groups
├── deploy.yml
└── roles
    └── hooks
        └── tasks
            ├── after_symlink.yml
            ├── after_update_code.yml
            └── before_symlink_shared.yml
```

##### hosts ファイル

```file:production
[production]
prod.example.com
```

```file:staging
[staging]
stg.example.com
```

##### vars ファイル

`group_vars/production.yml` と `group_vars/staging.yml` を作成し、それぞれ環境ごとに Ansistrano のロール変数やユーザーやパスワード等の変数を定義します。

#### 実行

```production
ansible-playbook -i production deploy.yml
```

```staging
ansible-playbook -i staging deploy.yml
```

### ロールバック

#### ロール変数

デプロイのロール変数に比べてロールバックのロール変数は少ないです。

```yaml:vars
- vars:
  ansistrano_deploy_to: "/var/www/my-app" # Base path to deploy to.
  ansistrano_version_dir: "releases" # Releases folder name
  ansistrano_current_dir: "current" # Softlink name. You should rarely changed it.
  ansistrano_remove_rolled_back: yes # Remove rolled back release?
  ansistrano_allow_anonymous_stats: yes

  # Hooks: custom tasks if you need them
  ansistrano_before_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-tasks.yml"
  ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-tasks.yml"
  ansistrano_before_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-cleanup-tasks.yml"
  ansistrano_after_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-cleanup-tasks.yml"
```

#### 実行

```
ansible-playbook -i hosts rollback.yml
```

※ `releases` ディレクトリ以下に作成されたアプリケーションコードが 0 または 1 つだった場合、ロールバックしようとするとエラーが発生し実行されません。

## 注意点

* [OPcache が有効で且つ PHP-FPM を使用した ウェブサーバで、Symlink ベースのデプロイするとコードが反映されない - Qiita](http://qiita.com/kotarella1110/items/726004497d3605441fb6 "OPcache が有効で且つ PHP-FPM を使用した ウェブサーバで、Symlink ベースのデプロイするとコードが反映されない - Qiita")

## 参考文献

* [ansistrano/deploy: Ansible role to deploy scripting applications like PHP, Python, Ruby, etc. in a capistrano style](https://github.com/ansistrano/deploy "ansistrano/deploy: Ansible role to deploy scripting applications like PHP, Python, Ruby, etc. in a capistrano style")
* [Ansistranoでnode.jsのアプリケーションをインストールする - Qiita](http://qiita.com/NewGyu/items/4ad49976ffc5f6f60f5d "Ansistranoでnode.jsのアプリケーションをインストールする - Qiita")