You Don't Need common-roles in Ansible


## 話しておきたいこと

本記事は、 _common-roles_ を用いたベストプラクティス構成を **非難している訳ではありません** 。寧ろ、「 _common-roles_ という共用したいロールを Playbook の外に出し管理する」という発想は素晴らしいものであると思っています。

## 前書き

構成管理ツールで Ansible を使ってる方は以下の記事を一度は見たことがあるのではないでしょうか？以下の記事を参考にすぐさまベストプラクティス構成を変更したことは記憶に新しいです。

[Ansible オレオレベストプラクティス - Qiita](http://qiita.com/yteraoka/items/5ed2bddefff32e1b9faf "Ansible オレオレベストプラクティス - Qiita")

筆者は丁度 1 年ぐらい前に [こちらの記事](http://qiita.com/kotarella1110/items/79af4485bd7985935d6b "Ansible ( 俺の中で ) 最強の Best Practices") を投稿しました。こちらの記事を見て分かると思うのですが、筆者は _common-roles_ をベースとしたオレオレベストプラクティス構成で管理していました。しかし、現状は _common-roles_ を用いない ( 必要としない ) ベストプラクティス構成となっております。 _common-roles_ で供用したいロールを管理してもいいとは思いますが、筆者としては現状の _common-roles_ を用いない ( 必要としない ) ロール管理の方がスマートで気に入ってます。ですので、今回はこちらの紹介をしたいと思います。

## なぜ _common-roles_ はもう必要ないのか？

**共用ロールを _common-roles_ で管理するのではなく、 Ansible Galaxy でファイル管理することで _common-roles_ は必要なくります。**

以下の [Ansible Galaxy とは](#Ansible Galaxy とは) では Ansible Galaxy に知見がない方のために [Ansible Galaxy — Ansible Documentation](http://docs.ansible.com/ansible/galaxy.html "Ansible Galaxy — Ansible Documentation") から筆者が重要だと思う項目のみ抜粋し意訳しています。「 **※ 補足** 」については筆者が補足したい内容記述した箇所になります。

### Ansible Galaxy とは

Ansible Galaxy は、ユーザーがロールをシェア出来る Web サイト [Galaxy](https://galaxy.ansible.com/ "Ansible Galaxy | Find, reuse, and share the best Ansible content") 及び、ロールのインストール・作成や管理するためのコマンドラインツールを指します。

#### Web サイト

[Galaxy](https://galaxy.ansible.com/ "Ansible Galaxy | Find, reuse, and share the best Ansible content") は、コミュニティが開発したロールを見つけ、ダウンロードし、共有するための無料のサイトです。 Galaxy からロールをダウンロードすることは、オートメーションプロジェクトを開始するのに最適です。

サイトを使用して、作成したロールを共有することも出来ます。 GitHub アカウントを使用してサイトで認証することで、ロールをインポートして、Ansible コミュニティで利用出来るようになります。 インポートされたロールは、Galaxy 検索索引で利用可能になりサイト上に表示され、ユーザーはそれらを見つけてダウンロードすることが出来ます。

[About page](https://galaxy.ansible.com/intro "Ansible Galaxy | About") を表示して詳細を確認して下さい。

#### コマンドラインツール

`ansible-galaxy` コマンドは Ansible にバンドルされています。 Galaxy から、または git ベースの SCM から直接ロールをインストールするために使用出来ます。 また、これを使用して、新しいロールを作成したり、ロールを削除したり、Galaxy Web サイトでタスクを実行することも出来ます。

コマンドラインツールは、デフォルトで、サーバーアドレス _https://galaxy.ansible.com_ を使用して Galaxy Web サイト API と通信します。 [Galaxy プロジェクト](https://github.com/ansible/galaxy "ansible/galaxy: Source code behind Galaxy - http://galaxy.ansible.com") はオープンソースプロジェクトであるため、独自の内部 Galaxy サーバーを実行しており、既定のサーバーアドレスを上書きすることが出来ます。 これを行うには、 `-server` オプションを使用するか、 _ansible.cfg_ ファイルに Galaxy サーバーの値を設定します。 _secured.cfg_ の値を設定する方法については、 [Galaxy Settings](http://docs.ansible.com/ansible/intro_configuration.html#galaxy-settings "Configuration file — Ansible Documentation") を参照して下さい。

##### ロールのインストール

`ansible-galaxy` コマンドを使用して、Galaxy Webサイトからロールをダウンロードします。

```shell
$ ansible-galaxy install username.role_name
```

**※ 補足**

`ansible-galaxy` は SCM ( git か hg ) をサポートしているため、 GitHub や Bitbucket といった git ホスティングサービスにアップロードしたロールもインストールすることが可能です。

※ プライベートリポジトリで管理されたロールもインストール可

###### ロールのインストールパス

デフォルトでは、 *ANSIBLE_ROLES_PATH* 環境変数で指定されたパスにロールがダウンロードされることに注意して下さい。 これは、一連のディレクトリ（ すなわち、 _/etc/ansible/roles:~/ansible/roles_ ）に設定することが出来ます。この場合、最初の書き込み可能なパスが使用されます。 Ansible が最初にインストールされると、デフォルトでは _/etc/ansible/roles_ になります。これには root 権限が必要です。

セッションで環境変数を設定し、 _ansible.cfg_ ファイルで _roles_path_ を定義するか、 `-roles-path` オプションを使用して、これを無効にすることが出来ます。 以下は、 _-roles-path_ を使用して現在の作業ディレクトリにロールをインストールする例です。

```shell
$ ansible-galaxy install --roles-path . geerlingguy.apache
```

※ 参照して下さい

[Configuration file](http://docs.ansible.com/ansible/intro_configuration.html "Configuration file — Ansible Documentation")  
　すべての設定ファイルについて


###### バージョン

カンマと GitHub リリースタグの値を追加して、特定のバージョンのロールを Galaxy からインストール出来ます。例えば:

```shell
$ ansible-galaxy install geerlingguy.apache,v1.0.0
```

また、git リポジトリを直接指し、ブランチ名やコミットハッシュをバージョンとして指定することも出来ます。例えば、次のコマンドは特定のコミットをインストールします。

```shell
$ ansible-galaxy install git+https://github.com/geerlingguy/ansible-role-apache.git,0b7cd353c0250e87a26e0499e59e7fd265cc2f25
```

**※ 補足**

複数の共用ロールを一つ一つリポジトリで管理するため、バージョンを指定してインストールすることが可能です。

###### 複数のロールをファイルからインストール

Asible 1.8 から、roles を _requirements.yml_ ファイルに含めることで、複数のロールをインストールすることが可能です。 ファイルの形式はYAML で、ファイル拡張子は _.yml_ または _.yaml_ のいずれかでなければなりません。

_requirements.yml_ に含まれるロールをインストールするには、次のコマンドを使用します:

```shell
$ ansible-galaxy install -r requirements.yml
```

この拡張子も重要です。 拡張子 _.yml_ が指定されていない場合、対応していない `ansible-galaxy` CLI はファイルが古くなっているとみなし、現在は廃止された「基本」形式とみなします。

ファイル内の各ロールには、次の属性の 1 つ以上があります。

| 属性 | 意味 |
|:-:|:--|
| src | ロールのソース。 Galaxyからダウンロードする場合は、 *username.role_name* という形式を使用します。 それ以外の場合は、gitベースの SCM 内のリポジトリを指す URL を指定します。 以下の例を参照して下さい。 これは必須の属性です。 |
| scm | SCM を指定します。 この執筆時点では、git または hg のみがサポートされています。 以下の例を参照して下さい。 デフォルトは git です。 |
| version | ダウンロードするロールのバージョン。 リリースタグ値、コミットハッシュ、またはブランチ名を指定します。 デフォルトは master です。 |
| name | ロールを特定の名前にダウンロードします。 GalaxyからダウンロードするときはデフォルトでGalaxyの名前になります。それ以外の場合はデフォルトでリポジトリの名前になります。 |

_requirements.yml_ でロールを指定するためのガイドとして、次の例を使用して下さい:

```yaml:requirements.yml
# from galaxy
- src: yatesr.timezone

# from GitHub
- src: https://github.com/bennojoy/nginx

# from GitHub, overriding the name and specifying a specific tag
- src: https://github.com/bennojoy/nginx
  version: master
  name: nginx_role

# from a webserver, where the role is packaged in a tar.gz
- src: https://some.webserver.example.com/files/master.tar.gz
  name: http-role

# from Bitbucket
- src: git+http://bitbucket.org/willthames/git-ansible-galaxy
  version: v1.4

# from Bitbucket, alternative syntax and caveats
- src: http://bitbucket.org/willthames/hg-ansible-galaxy
  scm: hg

# from GitLab or other git-based scm
- src: git@gitlab.company.com:mygroup/ansible-base.git
  scm: git
  version: "0.1"  # quoted, so YAML doesn't parse this as a floating-point value
```

**※ 補足**

_requirements.yml_ は Playbook のロールを管理するためのファイルです。  
ロールをパッケージで例えるならば、

* Node.js でいうところの _package.json_
* Ruby でいうところの _Gemfile_
* PHP でいうところの _composer.json_

と言ったファイルと同じようなものです。

共用ロールを common-roles で管理するのではなく、 この _requirements.yml_ ファイル管理するのがスマートです。

###### 依存関係

ロールは他のロールに依存することもあり、依存関係を持つロールをインストールすると、それらの依存したロールが自動的にインストールされます。

_meta/main.yml_ ファイルでロールの依存関係を指定するには、ロールのリストを指定します。 ロールのソースが Galaxy の場合は、単純に *username.role_name* の形式でロールを指定出来ます。 _requirements.yml_ で使用されているより複雑な形式もサポートされているので、src、scm、バージョンおよび名前を指定することが出来ます。

Galaxyの依存関係は、次のように指定出来ます:

```yaml:meta/main.yml
dependencies:
  - geerlingguy.apache
  - geerlingguy.ansible
```

複雑な形は、次のようにも使用することが出来ます:

```yaml:meta/main.yml
dependencies:
  - src: geerlingguy.ansible
  - src: git+https://github.com/geerlingguy/ansible-role-composer.git
    version: 775396299f2da1f519f0d8885022ca2d6ee80ee8
    name: composer
```

`ansible-galaxy` によって依存関係が検出されると、それは自動的にroles_pathに各依存関係をインストールします。 プレイの実行中に依存関係がどのように処理されるかを理解するには、 [Playbook Roles and Include Statements](http://docs.ansible.com/ansible/playbooks_roles.html "Playbook Roles and Include Statements — Ansible Documentation") を参照して下さい。


※ この執筆時点では、Galaxy Web サイトでは、すべてのロールの依存関係が Galaxy に存在すると予想されています。したがって、依存関係は *username.role_name* 形式で指定する必要があります。 _src_ の値が URL である依存関係を持つロールをインポートすると、インポートプロセスは失敗します。

##### ロールの作成

`init` コマンドを使用して、新しいロールの基本構造を初期化し、さまざまなディレクトリの作成に時間を節約し、ロールが必要とする _main.yml_ ファイルを作成します。

```shell
$ ansible-galaxy init role_name
```


上記は、現在の作業ディレクトリに次のディレクトリ構造を作成します:

```tree
.
├── .travis.yml
├── README.md
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml
```

###### Force

ロールの名前と一致するディレクトリが現在の作業ディレクトリにすでに存在する場合、 `init` コマンドはエラーになります。 エラーを無視するには、 `-force` オプションを使用します。 Force は上記のサブディレクトリとファイルを作成し、一致するものを置き換えます。

##### インストールされたロールの削除

*roles_path* からロールを削除するには、 `remove` を使用します。

```shell
$ ansible-galaxy remove username.role_name
```

### 何がスマートなのか

_common-roles_ で共用ロールを管理する場合は以下のように、 _common-roles_ とサービスごとの Playbook を一纏めにしてリポジトリで管理していました。

```tree:repository1
.
├── common-roles
│   ├── role1/   # 共用ロール1
│   ├── role2/   # 共用ロール2
│   └── role3/   # 共用ロール3
├── playbook1/   # service1 の Playbook
└── playbook2/   # service2 の Playbook
```

しかし、 _requirements.yml_ で共用ロールを管理する場合は以下のように、 _common-roles_ とサービスごとの Playbook を一つ一つリポジトリで管理することが可能です。


```tree:repository1
role1/
```

```tree:repository2
role2/
```

```tree:repository3
role3/
```

```tree:repository4
playbook1/
```

```tree:repository5
playbook2/
```

こうすることで、ロールと Playbook それぞれのバージョンを指定して実行できたり、管理が楽になります。

更に、以下のようにサービスごとに Playbook がどの共用ロールを使用しているかが _requirements.yml_ ファイルを見ることで把握出来ます。


```yaml:playbook1/requirements.yml
- src: kotarella1110.role1
- src: https://github.com/kotarella1110/role2
  version: v1.3
```

```yaml:playbook2/requirements.yml
- src: kotarella1110.role1
- src: kotarella1110.role3
```

## Playbook の Best Operation

### _common-roles_ 内の共用ロールを Galaxy にアップロード

_common-roles_ 内の共用ロールは汎用的に作られていると思うので、プライベートな情報を保持しているというロール少ないと思います。
公開出来る共用ロールは Galaxy にアップロードしてしまいましょう。フィードバックがあるかもしれません。  
Galaxy へのアップロード方法を知りたい方は [roleを作成してAnsible Galaxyに登録するワークフロー - Qiita](http://qiita.com/hnakamur/items/4c5abbbbbb5623ce46ad "roleを作成してAnsible Galaxyに登録するワークフロー - Qiita") を参照して下さい。非常に分かりやすく記載されています。

中には公開出来ない共用ロールがあると思います。そのようなロールはプライベートリポジトリで管理します。
何回も言うようですがプライベートリポジトリも _requirements.yml_ で管理出来ます。

筆者は基本的に共用ロールは Galaxy にアップロードした、もしくはされたロールを利用し、サービスごとの Playbook はサービス独自の個別設定等といった公開したくない情報が入っているため、プライベートリポジトリで管理しています。

### 偉人が作成した Galaxy のロールを極力採用する

共用ロールはどんなサービスごとの Playbook でも使える、汎用的なロールであるべきです。
しかし、供用ロールを本気で作ろうとするとプラットフォームを考慮したりするため一苦労です。
そこで偉人が作成した Galaxy ロールを採用しましょう。ほとんどの場合が、自分の作成したいロールは偉人が作成したロールで完結してしまうはずです。
Galaxy の [Browse Roles](https://galaxy.ansible.com/list#/roles "Ansible Galaxy | Browse Roles") から様々なロールを探すことが可能です。ダウンロード数や GitHub のスター数、サポートしているプラットフォームを確認して、目的に即したロールをインストールしましょう。

筆者は実際にロールを採用する前に必ず、偉人が作成したロールのコードリーディングを行うようにしています。
いくら、偉人が作成したロールとはいえ間違えがある可能性も十分にあります。間違えを発見したらプルリクを出して貢献しましょう。

偉人の Galaxy ロールだけでは目的に即さない場合もあると思います。
そのような時は、 Galaxy のロールを改変して利用するのが手っ取り早いです。

※ Galaxy のロールを改変して利用する際はライセンスに注意して下さい。

### Galaxy のロールを改変する際の管理

Galaxy のロールを改変し目的に即したロールを作成する際に避けたい事態が存在します。
それは Galaxy のロール ( 本家 ) に修正が加わった際に、その修正内容を反映せず、放置してしまうという事態です。

しかし、以下のように Git を上手く利用し本家リポジトリに追従することでこの問題を回避出来ます。

[GitHubでFork/cloneしたリポジトリを本家リポジトリに追従する - Qiita](http://qiita.com/xtetsuji/items/555a1ef19ed21ee42873 "GitHubでFork/cloneしたリポジトリを本家リポジトリに追従する - Qiita")

本家のリポジトリに追従しながら変更を加えることが可能になり、効率的なロールの管理が出来ます。

Galaxy のロールである _[geerlingguy.apache](https://galaxy.ansible.com/geerlingguy/apache/ "Ansible Galaxy | geerlingguy.apache")_ に追従してみましょう。 GitHub リポジトリは [geerlingguy/ansible-role-apache](https://github.com/geerlingguy/ansible-role-apache "geerlingguy/ansible-role-apache: Ansible Role - Apache 2.x.") です。

#### Galaxy のロールを clone

```shell
$ git clone git@github.com:geerlingguy/ansible-role-apache.git geerlingguy.apache
cd geerlingguy.apache/
$ git remote -v
origin  git@github.com:geerlingguy/ansible-role-apache.git (fetch)
origin  git@github.com:geerlingguy/ansible-role-apache.git (push)
$ git branch -a
_ master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
```

#### リモートリポジトリを追加

オリジナルのリポジトリを `upstream` という名前で設定。

```shell
$ git remote add upstream git@github.com:geerlingguy/ansible-role-apache.git
$ git remote -v
origin  git@github.com:geerlingguy/ansible-role-apache.git (fetch)
origin  git@github.com:geerlingguy/ansible-role-apache.git (push)
upstream        git@github.com:geerlingguy/ansible-role-apache.git (fetch)
upstream        git@github.com:geerlingguy/ansible-role-apache.git (push)
```

#### 本家リポジトリの変更を取り出すために fetch

```shell
$ git fetch upstream
From github.com:geerlingguy/ansible-role-apache
 _ [new branch]      master     -> upstream/master
$ git branch -a
_ master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/upstream/master
```

#### 反映したいリポジトリにチェックアウトし merge

```shell
$ git checkout master
$ git merge upstream/master
```

### 依存関係を上手く利用する

#### 供用ロールに依存するロールもインストールしたい

例えば、以下のように _[geerlingguy.jenkins](https://galaxy.ansible.com/geerlingguy/jenkins/ "Ansible Galaxy | geerlingguy.jenkins")_  ロールは _[geerlingguy.java](https://galaxy.ansible.com/geerlingguy/java/ "Ansible Galaxy | geerlingguy.java")_ ロールに依存してます。



```yaml:geerlingguy.jenkins/meta/main.yml
---
dependencies:
  - geerlingguy.java
...
```

この時、 _geerlingguy.jenkins_ をインストールすると以下のように _geerlingguy.java_ もインストールされます。

```shell
$ ansible-galaxy install -p . geerlingguy.jenkins
- downloading role 'jenkins', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-jenkins/archive/2.6.0.tar.gz
- extracting geerlingguy.jenkins to ./geerlingguy.jenkins
- geerlingguy.jenkins was installed successfully
- adding dependency: geerlingguy.java
- downloading role 'java', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-java/archive/1.7.1.tar.gz
- extracting geerlingguy.java to ./geerlingguy.java
- geerlingguy.java was installed successfully
```

```shell
$ ls
geerlingguy.java/  geerlingguy.jenkins/
```

#### タスクの `notify` セクションに供用ロールの handlers タスクを定義したい

供用ロールである _[geerlingguy.nginx](https://galaxy.ansible.com/geerlingguy/nginx/ "Ansible Galaxy | geerlingguy.nginx")_ ロールのタスクを実行後、 Playbook 独自の _nginx_ ロールのタスクを実行したいというケースはあると思います。

```yaml:requirement.yml
- src: geerlingguy.nginx
```

```tree:playbook
.
├── hosts/
├── group_vars/
├── host_vars/
├── web.yml
├── roles
│   └── nginx/
└── requirement.yml
```

以下のように定義すると、供用ロールである _geerlingguy.nginx_ ロールと _nginx_ ロールはお互いに関係を持たないため、 _nginx_ ロールのタスクの `notify` セクションに _[geerlingguy.nginx/handlers/main.yml](https://github.com/geerlingguy/ansible-role-nginx/blob/master/handlers/main.yml "ansible-role-nginx/main.yml at master · geerlingguy/ansible-role-nginx")_ の handlers タスク ( `restart nginx` など ) を定義しても実行されません。そのため、 _nginx/handlers/main.yml_ に _geerlingguy.nginx/handlers/main.yml_ と重複する handlers タスクを定義する必要があります。これはスマートではないです。

```yaml:web.yml
- hosts: web
  sudo: yes
  roles:
    - geerlingguy.nginx
    - nginx
```

このような時は、以下のように依存関係を持つことで、 _nginx_ ロールは _geerlingguy.nginx_ ロールの handlers を実行することが出来るようになります。

```yaml:roles/nginx/meta/main.yml
dependencies:
  - geerlingguy.nginx
```

```yaml:web.yml
- hosts: web
  sudo: yes
  roles:
    - nginx
```

### _requirements.yml_ 内のロールをアップデート

_requirements.yml_ 内のロールをアップデート ( 上書き ) したい場合は `-force` オプションを使用します。
ロールの名前と一致するディレクトリがインストール先ディレクトリに既にに存在する場合、 `install` コマンドはエラーになります。 

```shell
$ ansible-galaxy install -f -r requirements.yml
```

## 参考文献

* [Ansible Galaxy](http://docs.ansible.com/ansible/galaxy.html "Ansible Galaxy — Ansible Documentation")
* [GitHubでFork/cloneしたリポジトリを本家リポジトリに追従する - Qiita](http://qiita.com/xtetsuji/items/555a1ef19ed21ee42873 "GitHubでFork/cloneしたリポジトリを本家リポジトリに追従する - Qiita")