はじめに
大体使うモジュールっていつも一緒だったのでまとめてみました。
また、ansibleでスクリプトを作るときのちょっとしたノウハウなども書いていますので参考にして頂ければと思います。
環境
- ansible 1.9.1~2.2.0.0-1
- target OS CentOS6.6,RHEL6.6
インストールの仕方
- 基本的にはこちらのエントリを参考にしていただければすんなりインストールできるはずです。
windows7 32bit / Proxy環境下でVagrant+Ansibleの環境を構築する。 - また、Ansible Tutorialをたどることでapacheのインストールとserverspecの使い方まで簡単に知ることができます。
情報源
- 慣れてくると公式サイトくらいしか見なくなりますので、Module Indexをブックマークしておきます。
コーディング規約
こちらのエントリーが素敵です。
このエントリーをベースにして合わない所、追加が必要なところをgitのwikiに追記してプロジェクトでは利用しています。
Ansible コーディング規約 (の例)
実行方法
通常実行
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を参照
[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になるのを防ぐ。コマンド実行時にこの2つは常にセットで使うことが多い。
- 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
ファイルダウンロード
ファイルダウンロード その1
- 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 }}
ファイルダウンロード その2
- 通常は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を使うことで実現できます。
- 以下は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を作るのは難しいです。
- こちらを参考にしてみてください。
高速化
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'