1日本気でAnsibleを勉強した結果、、

# はじめに
ある本を読んでAnsibleに対する理解がとても上がったので、ここの学んだことを記す。


#環境構築

##Ansibleのインストール

1.EPELをインストールする。

```

$ yum install epel-release
```

2.epel-testingレポジトリを有効にする。

```

$ yum install ansible --enablerepo=epel-testing
```

1.EPELをインストールする。

```

$ yum install epel-release
```

3.Ansibleのバージョンを確認する。

```

$ ansible --version
```

##ansibleコマンドを使うための設定

1.hostsリストがどこにあるか、中身にサーバーがあるか確認する。

```ruby

/etc/ansible/hostsにサーバーがあるのが一般的

そのほかの場合は探す
```

2.認証の設定をする。

```ruby

# -kオプションを用いる場合(パスワード入力)

$ ansible IPアドレス -m ping -k

# ssh-copy-idを用いた場合(秘密鍵作成)

$ ssh-keygen

$ ssh-copy-id IPアドレス

$ ansible IPアドレス -m ping
```

##Playbookの動作確認

1.Playbookを作成する。
※拡張子は「.yml」とする。

2.軽く中身を記述してみる。

```yaml

- hosts: all                           # 操作対象のサーバーのIPアドレスを指定
  tasks:                               # 実行する処理を定義
    - name: slコマンドのyumインストール    # 各処理の名前
      yum: name=sl                     # yumでslパッケージをインストール
```

3.ansible-playbookコマンドを実行してみる。

```

$ ansible-playbook Playbookのファイル名
```

##よく使うモジュール4選

####get_urlモジュール
wgetコマンドのように指定したURLのファイルを、保存先にダウンロードするパッケージです。

```yml

- name: all
  tasks:
    - name: rubyのダウンロード
      get_url:
        url={{ ダウンロード先のURL }}
        dest=/tmp/ruby.tar.gz
```

####unarchiveモジュール
tarコマンドのように、ダウンロードなどにより入手した「tar.gz」ファイルなどを展開するパッケージです。

```yaml

- name: all
  tasks:
    - name: rubyの展開
      unarchive: src=/tmp/ruby.tar.gz dest=/tmp copy=no
```


####shellモジュール
実行したい処理を記述することで、実行してくれるパッケージです。

```yaml

- name: all
  tasks:
    - name: rubyのインストール
      shell: chdir=/tmp/{{ rubyver }} ./configure --disable-install-doc

    - name: make ruby
      shell: chdir=/tmp/{{ rubyver }} make

    - name: make install ruby
      shell: chdir=/tmp/{{ rubyver }} make install
```

####postgresql_userモジュール
PostgreSQLでユーザーを作成するためのモジュールです。

```yaml

- name: all
  tasks:
    - name: PostgreSQL redmine user 作成
      postgresql_user:
        name=redmine
        password={{ password }}
```

# Playbookの例(WordPress環境構築編)

```yaml

- name: wordpress 環境構築
  hosts: wordpress
  remote_user: user  # 捜査対象
  become: yes
  vars:
    wordpress_url: https://ja.wordpress.org/wordpress-4.4.2-ja.tar.gz

  tasks:
  - name: Apache(httpd)のインストール
    yum: name=httpd state=latest

- name: firewalldでHTTPを許可
  firewalld: permanent=True service=http state=enabled immediate=true
  
  - name: サービスの起動
    service: name=httpd state=started enabled=yes

  - name: PHPのインストール
    yum: name="{{item}}" state=latest
    with_items:
      - php
      - php-mysql

  - name: タイムゾーンの設定
    ini_file: >
      dest=/etc/php.ini
      section=Date
      option=date.timezone
      value='"Asia/Tokyo"'

  - name: phpテストページの作成
    copy: >
      src=roles/phpinfo.php
      dest=/var/www/html/phpinfo.php

  - name: httpサービスの再起動
    service: name=httpd state=restarted enabled=yes

  - name: MariaDB インストール
    yum: name="{{item}}" state=latest
    with_items:
      - mariadb
      - mariadb-server
      - MySQL-python

  - name: サービスの起動
    service: name=mariadb state=started enabled=yes

  - name: wordpressのダウンロード
    get_url: 
      url="{{ wordpress_url }}"
      dest=/tmp/wordpress.tar.gz

  - name: wordpressの展開
    unachive: src=/tmp/wordpress.tar.gz dest=/var/www/html/ copy=no

  - name: wordpressの所有権をapacheに変更
    file: path=/var/www/html/wordpress/ owner=apache group=apeche recurse=yes

  - name: httpサービスの起動
    service: name=httpd state=restarted
```

# 再利用性を高めるPlaybookの書き方
構成が複雑になるにつれ、可読性や再利用性が下がることに気付いてくると思います。このために、Ansibleはベストプラクティスを紹介しています。

##ディレクトリ構造

```
|　　dbservers.yml
|　　site.yml
|　　staging
|　　webservers.yml
|
|ーーgroup_vars
|　　　　staging
|
|ーroles
　　|
　　|ーcommon
　　|　　|ーhandlers
　　|　　|ーtasks
　　|　　|　　　main.yml
　　|　　|
　　|　　|ーtemplates
　　|　　|ーvars
　　|　　
　　|ーhttpd
　　|　　|ーhandlers
　　|　　|ーtasks
　　|　　|　　　main.yml
　　|　　|
　　|　　|ーtemplates
　　|　　|ーvars
　　|　　
　　|ーmysql
　　|　　|ーhandlers
　　|　　|ーtasks
　　|　　|　　　main.yml
　　|　　|
　　|　　|ーtemplates
　　|　　|ーvars
　　|
　　|ーphp
　　|　　|ーfiles
　　|　　|　　　phpinfo.php
　　|　　|
　　|　　|ーhandlers
　　|　　|ーtasks
　　|　　|　　　main.yml
　　|　　|
　　|　　|ーtemplates
　　|　　|ーvars
　　|
　　|ーwordpress
　　 　　|ーhandlers
　　 　　|ーtasks
　　 　　|　　　main.yml
　　 　　|
　　 　　|ーtemplates
　　 　　|ーvars
```


##Inventory file
Ansibleが複数のサーバーに対して処理を行う際に利用するファイル。グルービングというものを用いることで、複数サーバーをまとめることができ、グル０ーぷ内のサーバーに対して同一のPlaybookを実行することができる。

```Invebtoryfile

[webservers]
192.168.0.10

[dbservers]
192.168.0.20

[staging:shildren]
webservers
dbservers
```

##Vars file
特定のグループやホストに対する変数の値を定義する。
(環境により変化するものを変数と示しています。)

##Roles
サーバーの役割ごとにPlaybookを独立させるために用いられます。分割単位が大きすぎるのも小さすぎるのもわかりにくくなり再利用性も高くならないので、再利用のしやすさを念頭に置き分割する。

##再利用性が高いPlaybook記述例

- staging(Inventory file)
  - 2つのグループを使うことで、サーバーごとの役割を分類している。

```staging

[webservers]
192.168.0.10

[dbservers]
192.168.0.20

[staging:shildren]
webservers
dbservers
```

- group_vars/staging
  - 環境ようの変数を定義している。

```

wordpress_url: https://js.wordpress.org/wordpress-4.4.2-ja.tar.gz
db_name: wordpress
db_user: wordpress
db_password: wordpress
```

- site.yml
  - Top LevelのPlaybookなので、他ファイルを読み込む。

```

- include: webservers.yml
- include: dbserevers.yml
```

- webservers.yml
  - ここでは4つのRoleを読み込む。

```

- name: deploy webservers
  hosts: webservers//}
  become: yes
  roles:.. literalinclude:: ./code/webservers.yml
    - common  :language: python
    - httpd  :linenos:
    - php
    - wordpress
```

- dbservers.yml
  - ここでは2つのRoleを読み込む。

```

- name: deploy dbservers
  hosts: dbservers
  become: yes
  roles:.. literalinclude:: ./code/webservers.yml
    - common
    - php
```

##Roleの内容

- roles/common/tasks/main.yml

```roles/common/tasks/main.yml

- name: SELinuxの無効化
  selinux:
    policy: targeted
    state: disabled
- name: firewalldのインストール
  yum: name=firewalld state=present

- name: firewalldの有効化
  yum: name=firewalld state=started enabled=yes
```

- roles/httpd/tasks/main.yml

```roles/httpd/tasks/main.yml

- name: apache(httpd)のインストール
  yum: name=httpd state=latest

- name: firewalldでHTTPを許可
  firewalld: permanent=True state=enabled immediate=true

- name: サービスの起動
  service: name=httpd state=started enabled=yes
```

- roles/php/tasks/main.yml

```roles/php/tasks/main.yml

- name: PHPのインストール
  yum: name="{{item}}" state=latest
  with_items:
    - php
    - php-mysql
  
- name: タイムゾーンの設定
  ini_file: >
    dest=/etc/php.ini
    section=Date
    option=date.timezone
    value='"Asia/Tokyo"'

- name: php テストページの作成
  copy: src=phpinfo.php dest=/var/www/html/phpinfo.php

- name: http サービスの再起動
  service: name=httpd state=restarted enabled=yes
```

- roles/wordpress/tasks/main.yml

```roles/wordpress/tasks/main.yml

- name: wordpressのダウンロード
  get_url:
    url="{{ wordpress_url }}"
    dest=/tmp/wordpress.tar.gz

- name: wordpressの展開
  unarchive: src=/tmp/wordpress/tar.gz dest=/var/www/html/ copy=no

- name: wordpressの所有権をapacheに変更
  file: path=/var/www/html/wordpress owner=apache group=apache recurse=yes

- name: httpサービスの再起動
  service: name=httpd state=restarted
```

- roles/mysql/tasks/main.yml

```roles/mysql/tasks/main.yml

- name: mariadb(mysql)のインストール
  yum: name="{{item}}" state=latest
  with_items:
    - mariadb
    - mariadb-server
    - MySQL-python

- name: mariadbサービスの起動
  service: name=mariadb state=started enabled=yes

- name: firewalldでmariadb(mysql)を許可
  firewalld: premanent=True service=mysql state=enabled immediate=true

- name: データベースの作成
  mysql_db: name={{ db_name }} state=present encoding=utf8

- name: DBユーザーの作成
  mysql_user: >
    name={{ db_user }}
    password={{ db_password }}
    priv={{ db_name }}.*:ALL
    host="%"
    state=present
```

実行後はブラウザにアクセスし、作業していく。。


#Ansibleで知っておくべき小技・知識

##Loop構文

まずは、基本的な形はこちら、、

```yaml

- name: echo tasks
  command: echo "{{ item }}"
  with_items:
    - foo
    - bar
    - baz
```

YAML記法のマッピング(ハッシュや連想配列的なもの)も使える。

```yaml

- name: add user
  user: name={{ item.name }} groups={{ item.groups }}
  with_items:
    - { name: 'user1', groups: 'wheel' }
    - { name: 'user2', groups: 'root' }
```

このように変数を用いた処理も可能です。

```yaml

- hosts: all

  vars:
    users:
      satoh:
        directory: /home/satoh
        shell: /bin/bash
        groups: developers
      tanaka:
        directory: /home/tanaka
        shell: /bin/zsh
        groups: developers
  become: yes
  tasks:
    - name: "user add"
      user: name={{ item.key }} shell={{ item.value.shell }} groups={{ item.value.groups }}
      with_dict: "{{ users }}"
```

##条件分岐

基本的な形としてはこちら。
「when」アトリビュートを利用して、管理対象の状態や変数の内容を条件分岐する。

```yaml

- hosts: all
  
  tasks:
  - name: install package by yum
    yum: name=vim state=latest
    when: ansible_os_family == 'CentOS'

  - name: installl package by apt
    apt: name=vim state=latest
    when: ansible_os_family == 'Debian'   
```

コマンドの実行結果で条件分岐する。

```yaml

- hosts: all

  tasks:
  - name: exec command
  command: /bin/true
  register: result
  ignore_errors: True

  - name: exec success
    debug: msg="echo success"
    when: result|success
  
  - name: exec changed
    debug: msg="echo changed"
    when: result|changed

  - name: exec skipped
    debug: msg="echo skipped"
    when: result|skipped

  - name: exec failed
    debug: msg="echo failed"
    when: result|failed
```

##効率的なデバッグ方法

- 構文チェック

```

$ ansible-playbook -i <hosts> <playbook.yml> --syntax-check
```

- Task一覧のチェック

```

$ ansible-playbook -i <hosts> <playbook.yml> --list-tasks
```

- 実行時影響のチェック
  - 「--check」で、Playbookを適用して結果起きる変化を確認。
  - 「--diff」で、テンプレートによってリモートファイルにどんな変更が行われるか確認。

```

$ ansible-playbook -i <hosts> <playbook.yml> --check --diff
```

- debugモジュールを使ってチェックすることもできる。

```yaml

- hosts: all

  tasks:
    - name: exec uptime
      command: /usr/bin/uptime
      register: result
 
    - debug: var=result

    - debug: msg="debug message"
```

#Ansbleのテスト

Ansibleは、Playbookに記述された通りにサーバーへ設定ができなかった場合は、即座に実行終了する設計になっています。
これを「fail-fast」と呼ばれてます。
この設計のためにテストは不要という考えもあるらしいんですが、人がやってる以上ヒューマンエラーの懸念は拭いきれない部分はあるので、テストはした方が良いでしょう。

##テスト対象は？

- 一連のtask単位でテストを行う
- 不安なtaskをテストする
- シェルスクリプトをテストする
- ユーザーの期待する結果をテストする

##おすすめテストツール

- serverspec
  - RSpecがベースのテストツール。sshでテスト対象のサーバーにログインしてサーバーないからテストする。
  - 主に、指定したサービスが動いているか、指定したポートが開いているか、指定したファイルが存在しているかなどをテストする。

```

require 'spec_helper'

set :request_pty, true

describe package('httpd') do
  it { should be_running }
end
```

- infrataster
  - RSpecがベースになっているテストツール。対象サーバーの外からテストするので外的要因も洗い出せる。
  - 主に、指定したステータスコードが返ってくるか、指定したページを返すかをテストする。

```

require 'spec_helper'

describe server(:app) do
  describe http('http://ip:port') do
    it 'returns 200' do
      expect(response.status).to eq(200)
    end
  end
end
```

#終わりに
Ansibleこれだけやってもまだまだ使える！とは言えない気がして恐ろしい。。
これから経験積みながら使いこなしていきたいです！