[随時更新]ansibleでよく使うコマンド・モジュールまとめ

# はじめに

大体使うモジュールっていつも一緒だったのでまとめてみました。
また、ansibleでスクリプトを作るときのちょっとしたノウハウなども書いていますので参考にして頂ければと思います。

# 環境

* ansible 1.9.1～2.2.0.0-1
* target OS CentOS6.6,RHEL6.6

# インストールの仕方

* 基本的にはこちらのエントリを参考にしていただければすんなりインストールできるはずです。
[windows7 32bit / Proxy環境下でVagrant＋Ansibleの環境を構築する。](http://qiita.com/uzresk/items/747490fb5cec35a9f767)
* また、[Ansible Tutorial](http://yteraoka.github.io/ansible-tutorial/)をたどることでapacheのインストールとserverspecの使い方まで簡単に知ることができます。

# 情報源

* 慣れてくると公式サイトくらいしか見なくなりますので、[Module Index](http://docs.ansible.com/ansible/modules_by_category.html)をブックマークしておきます。

# コーディング規約

こちらのエントリーが素敵です。
このエントリーをベースにして合わない所、追加が必要なところをgitのwikiに追記してプロジェクトでは利用しています。
[Ansible コーディング規約 (の例)](http://tdoc.info/blog/2014/10/09/ansible_coding.html)

# 実行方法

## 通常実行

```
ansible-playbook -i [インベントリファイル] sites.yml
```

## デバッグ出力しつつ実行

```
ansible-playbook -vvv -i [インベントリファイル] sites.yml
```

## 実行時にパラメータ付与する

* {{ xxx }}でplaybookで変数取得可能。
* yumが使える環境か否かを外部から与えてあげて、playbook内で分岐させたりします。

```
ansible-playbook --extra-vars "xxx=yes" -i [インベントリファイル] sites.yml
```

```
ansible-playbook -e "xxx=yes" -i [インベントリファイル] sites.yml
```

## ansible実行するsshポート/ユーザ/パスワード/sudoするパスワードを変更する。

* インベントリファイルに設定します。
* この他の設定値については[List of Behavioral Inventory Parameters](http://docs.ansible.com/ansible/intro_inventory.html#list-of-behavioral-inventory-parameters)を参照

```
[all:vars]
ansible_port=22
ansible_user=vagrant
ansible_pass=password
ansible_sudo_pass=password
```

## ansible実行するときのユーザの秘密鍵を指定して実行する

```
ansible-playbook -i [インベントリファイル] sites.yml --private-key=/path/key.pem
```

## タグを指定して実行する

* タグ名はtagsで指定します。
* タグの付与を簡単にするため、tasks/main.ymlには処理を書かずplaybookをincludeしてinclude単位でtagを指定するようにすることが多いです。

```
- include: user.yml tags=user
```

```
ansible-playbook -i [インベントリファイル] sites.yml -t user
```

* 複数指定するときはカンマ区切りで。

```
ansible-playbook -i [インベントリファイル] sites.yml -t group,user
```


# 前準備

## libselinux-python

* selinuxが無効化されていない状況でcopy,file,templateモジュールを使うとlibselinux-pythonを入れなさい。って怒られる。
* epelいれて、libselinux-pythonをyumでインストールすれば回避できるが、selinuxを利用しない場合は実行前にselinuxを無効化しておけば良い。

## ansibleが取得する値を確認しておく

* ansible_os_familyの値などずらっと表示してくれます。

```
ansible -m setup 10.0.1.10
```

# デバッグ

* デバッグ出力する

```
- debug: msg="XXXX"
```

# コマンド

## コマンドを実行する

* 20文字の乱数を作る場合
* ignore_errorsでエラーが発生しても無視する。changed_when: falseとしておくことでコマンド実行時に常にchangedになるのを防ぐ。コマンド実行時にこの２つは常にセットで使うことが多い。

```
- name: generate passwod salt for new user
  shell: "cat /dev/urandom | tr -dc '[:alnum:]' | head -c 20"
  register: salt
  ignore_errors: true
  changed_when: false
```

## コマンド実行結果を取得する

* 標準出力を取得する

```
register_val.stdout
```

* 標準出力結果に文字が含まれているかで分岐する。findで-1は存在しない場合を表す。

```
 when: register_val.stdout.find('test') != -1
```

* 標準出力結果を配列で取得する。with_itemsと合わせて使うとループできたりする。

```
with_items: register_val.stdout_lines
```

* コマンドが成功したか、失敗したかで分岐する

```
 when: register_val | success
```

```
 when: register_val | failed
```

* [Conditionals](http://docs.ansible.com/ansible/playbooks_conditionals.html)

# ファイルダウンロード

## ファイルダウンロード　その１

* get_urlを使うパターン。
* 存在チェック＆同一か否かも判定してくれるので冪等性が担保される。

```vars/main.yml
elasticsearch_configs:
  dl_url: https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.7.1.noarch.rpm
  rpm_path: /usr/local/src/elasticsearch-1.7.1.noarch.rpm
```

```tasks/elasticsearch.yml
- name: download elasticsearch rpm
  get_url: >
    url={{ elasticsearch_configs.dl_url }}
    dest={{ elasticsearch_configs.rpm_path }}
```

## ファイルダウンロード　その２

* 通常はget_urlを使うが、ヘッダを指定したい場合がある。
* 冪等性を担保しつつjdkのrpmを取得する場合はこんな感じで実装する。

```vars/main.yml
---
jdk:
  dl_url: http://download.oracle.com/otn-pub/java/jdk/8u45-b14/jdk-8u45-linux-x64.rpm
  rpm_path: "/usr/local/src/jdk-8u45-linux-x64.rpm"
```

```tasks/jdk.yml
- name: check java rpm
  stat: path={{ jdk.rpm_path }}
  register: java_exists
- name: wget java
  command: >
    wget -O {{ jdk.rpm_path }} --no-check-certificate --no-cookies
    --header "Cookie: oraclelicense=accept-securebackup-cookie" "{{ jdk.dl_url }}"
  when: java_exists.stat.md5 is not defined
```

# notify

## 設定ファイルが変更されたタイミングで再起動する。

* /etc/httpd/httpd-proxy.confが変更されたらapacheを再起動する。
* notify:の後の文字と、handlers/main.ymlのnameの文字が一致している必要がある。

```handlers/main.yml
---
- name: restart httpd
  service: name=httpd state=restarted
```

```tasks/httpd.yml
- name: copy httpd-proxy.conf
  template: src=httpd-proxy.conf dest=/etc/httpd/conf.d/
  notify: restart httpd
```

# リスト

## オブジェクトのリスト

* /etc/servicesの中身をregexp にマッチする行を line で置きかえる

```vars/main.yml
services_params:
  - { regexp: "listener 1521/tcp # listner", line: "listener 1521/tcp # listner" }
  - { regexp: "tomcat 8080/tcp #tomcat", line:"tomcat 8080/tcp #tomcat" }
```

```tasks/services.yml 
---
- name: add /etc/services
  lineinfile: dest=/etc/services
              state=present
              regexp='^{{ item.regexp }}'
              line='{{ item.line }}'
  with_items: "{{ services_params }}"
```

# テンプレート

## jsonファイルを読み込む(lookup)

* AWS関連のモジュールなどはポリシーをjsonで書く事が多い。
* これをplaybookにべた書きするのはどうもいけてないのでlookupを使うといい感じ。
* 引数のconvert_dataというのは\でエスケープするか否かを指定する。

```
    - name: create-cloudwatch-events
      cloudwatchevent_rule:
        name: "{{ rule_name }}"
        description: "Delete the EBS Snapshot attached at the timing when AMI was deleted"
        state: present
        event_pattern: "{{ lookup('file', './event.json', convert_data=False) |string }}"
        role_arn: "{{ role_arn }}"
        targets:
          - id: "{{ function_name }}"
            arn: "{{ lambda_arn }}"
```

```event.json
{"detail-type":["AWSAPICallviaCloudTrail"],"detail":{"eventSource":["ec2.amazonaws.com"],"eventName":["DeregisterImage"]}}
```

## ansibleで生成したことを残しておく

* こんな感じでテンプレートに記載しておくと・・・

```
# {{ ansible_managed }}
```

* こんな感じで書かれます。
* テンプレートに変更があった場合だけ書き込まれます。varsの中身を参照して出力するテンプレートがあった時に、varsの中身が変更されても置き換わりませんでした。
* 時刻を出しているとnotifyが動くことがあるので注意。（公式ドキュメントにも記載がありましたね）

```
# Ansible managed: /home/vagrant/ansible-logrotate/roles/logrotate/templates/logrotate.d.j2 modified on 2015-08-09 18:41:11 by vagrant on ansible-host
```

## テンプレートファイルを使ってファイルに反映する。

* hostsファイルを作成する場合はこんな感じで。

``` templates/hosts.j2
{% for host in hosts_params %}
# {{ host.comment }}
{{ host.ipaddr }} {{ host.name }} {{ host.alias }}
{% endfor %}
```

```vars/main.yml
---
hosts_params:
  - ipaddr: 127.0.0.1
    name: web1
    alias: localhost localhost.localdomain
    comment: localhost
  - ipaddr: ::1
    name: localhost
    alias: localhost localdomain
    comment: localhost
  - ipaddr: 192.168.1.20
    name: test.web1
    alias: webalias
    comment: web1
```

```tasks/hosts.yml
- name: templates
  template: src=hosts.j2 dest=/etc/hosts
           owner=root
           group=root
           mode=0644
```

# ファイル・ディレクトリ

## ディレクトリを作成する

```
- name: create springboot root directory
  file: path=/home/cmp/app
        state=directory
        owner=cmp
        group=cmp
        mode=0755
```

## シンボリックリンクを作る

* ln -s /etc/init.d/archiva /opt/archiva/bin/archiva

```
- name: create symbolic link
  file: src=/opt/archiva/bin/archiva dest=/etc/init.d/archiva state=link
```

## ファイルの中身を置換する

* /etc/sysconfig/clockのZONE="XXXX"をvarsに定義されたtimezoneで置換する。

```vars/main.yml
---
timezone: Asia/Tokyo
```

```tasks/timezone.yml
- name: Set /etc/sysconfig/clock
  lineinfile: >
        dest=/etc/sysconfig/clock
        state=present
        backrefs=yes
        regexp='^ZONE=.*$'
        line='ZONE=\"{{ timezone }}\"'
```

## ファイル内の特定文字の後に追記する。

* /etc/sysconfig/clock内のZONE=XXXの後にutc=xxxを追記する。
* 前に追記したい場合はinsertbeforeを使う。

```vars/main.yml
---
utc: true
```

```tasks/timezone.yml
- name: Set /etc/sysconfig/clock
  lineinfile: >
        dest=/etc/sysconfig/clock
        state=present
        backrefs=no
        insertafter='^ZONE=.*$'
        line='UTC={{ utc }}'
```

## ファイルの存在を確認する

* /etc/init.d/kdumpというファイルが存在しない場合、エラーメッセージを表示する

```
- name: stat /etc/init.d/kdump
  stat: path=/etc/init.d/kdump
  register: kdump

- name: install check kdump
  fail: msg="kdump(kexec-tool) is not installed."
  when: not kdump.stat.exists
```

# インストール

* httpdをyumでインストールする

```
- name: install httpd
  yum:
    name: httpd
    state: installed
```

# サービス

## サービスを登録して、起動状態にする。

```
- name: register service and start
  service:
    name:httpd
    state: started
    enabled: yes
```

# 環境変数

## 環境変数を取得

* [lookup](http://docs.ansible.com/ansible/playbooks_lookups.html)を使うことで実現できます。
* 以下はec2を起動するときの例でprivate ipやimage idを環境変数から取得しています。

```
  tasks:
    - name: launch ec2 instance
      ec2:
        private_ip: "{{ lookup('env', 'PRIVATE_IP') }}"
        key_name: LegoTestKeyPair
        group: LegoTestInstanceSecurityGroup
        instance_type: "{{ lookup('env', 'INSTANCE_TYPE') }}"
        image: "{{ lookup('env', 'IMAGE_ID') }}"
        wait: yes
        wait_timeout: 300
        count: 1
        instance_tags:
          Name: xxxxxxxx
        monitoring: no
        vpc_subnet_id: subnet-xxxxxxx
        assign_public_ip: yes
        region: ap-northeast-1
```

# その他(OS)

## 解凍する

* dl_pathに指定されたファイルをinstall_pathに解凍する。（install_pathがない場合は新たに作成する）

```vars/main.yml
---
archiva:
  dl_path: /usr/local/src/apache-archiva-2.2.0-bin.zip
  install_path: /opt/
  install_name: apache-archiva-2.2.0
```

```tasks/archiva.yml
- name: unarchive archiva
  unarchive: src={{ archiva.dl_path }} dest={{ archiva.install_path }} creates={{ archiva.install_path }} copy=no
```

## カーネルの設定を変更する

* vars/main.ymlに設定されたカーネルパラメータを反映する
* カーネル設定する用のモジュールsysctlがあるのでこれを利用する。

```vars/main.yml
---
kernel:
  - { param: 'net.core.somaxconn', value: '128' }
  - { param: 'kernel.threads-max', value: '163875' }
```

```tasks/kernel.yml
---
- name: Set {{ item.param }}
  sysctl: name={{ item.param }}
          value={{ item.value }}
          state=present
  ignore_errors: True
  with_items: "{{ kernel }}"
```

## OSグループの追加

* うっかりvarsにgroupsとか作るとハマります。（groupsはansibleの予約語なので）

```vars/main.yml
os_groups:
  test1:
    gid: 1001
```

```tasks/group.yml
- group: name={{ item.key }} gid={{ item.value.gid }}
  with_dict: "{{ os_groups }}"
```

## OSユーザの追加

* /etc/shadowも冪等性を担保しつつplaybookを作るのは難しいです。
* [こちら](https://github.com/uzresk/ansible-user-role.git)を参考にしてみてください。

# 高速化

## pipelining

* ansibleは実行時に一時ディレクトリを作成したり、スクリプトを転送したり、それを消したりという操作をssh越しに何回も処理を行うが、パイプラインで処理を渡すことによって無駄な通信を発生させないようにすることが可能。
* ちゃんと動いているかどうかは-vvvオプションで動かすと良くわかる。
* ansible実行ユーザのrequirettyが無効になっていることが条件なので、まずはplaybookで無効化しておく

```
- name: requiretty設定を無効化
  lineinfile:
    dest: /etc/sudoers
    regexp: '^Defaults:{{ ansible_user }}\srequiretty'
    line: 'Defaults:{{ ansible_user }} !requiretty'
    validate: 'visudo -cf %s'
    backup: yes
```

* pipelinesの有効化（以下のいずれかの方法）
    * varsにansible_ssh_pipelining: yesをつける
    * playbook実行時に -e pipelining=Trueをつける
    * ansible.cfgの[ssh_connection]にpipelining=Trueをつける

# 予約語

## OSのバージョンによって処理を分岐する。

* CentOS6だったらkexec-toolsをインストールする場合
* 当初ansible_distribution_version.split('.')[0]|intと書いてありましたが、ご指摘いただき修正しました。

```
- name: install kexec-tools
  copy: src=kexec-tools-2.0.10.tar.gz
        dest=/usr/local/src
  when: >
        ansible_distribution == 'CentOS'
        and ansible_distribution_major_version == '6'
```