Memo/Ansible

http://dexlab.net/pukiwiki/index.php?Memo%2FAnsible
 

Ansible

  • http://www.ansible.com/home 公式
    • Installation — Ansible Documentation インストール方法
    • Best Practices Ansible Documentation ベストプラクティス
    • python製、サーバ構成管理ソフト。yamlに書くタイプ
    • クライアントにSSHでログインできる環境であれば使える
    • AWS EC2インスタンス起動もできる
    • クライアント側の動作は"module"として定義。指定された入出力形式であればpython以外でも書ける
    • どのサーバにmoduleを使うかなどの動作をまとめたものを "play book" と定義。yamlで書く
  • 書籍

run_once: ホストが複数台あっても1回しか実行しない


ini_file: iniファイルの書き換え


AWS assume role

  • account1がログイン用、account2がswitch先とする
  • boto module の場合、単純にansible module側「profile: account2」を指定するだけでは、以下のエラーで失敗した。
  • 「~/.boto -> ~/.aws/credentials」のようなsymlinkがあると失敗するので、「~/.boto」は消す
  • sts_assume_role を使う場合
    1.     - name: assume role      sts_assume_role:        role_arn: "{{ role_arn }}"        role_session_name: session1        region: "{{ region }}"        profile: "{{ aws_profile }}"      register: assumed_role    - name: EC2 remote facts      ec2_remote_facts:        region: "{{ region }}"        filters:          instance-state-name: running      register: result_ec2      changed_when: False      environment:        AWS_ACCESS_KEY_ID: "{{ assumed_role.sts_creds.access_key }}"        AWS_SECRET_ACCESS_KEY: "{{ assumed_role.sts_creds.secret_key }}"        AWS_SESSION_TOKEN: "{{ assumed_role.sts_creds.session_token }}"    - name: Number of running      debug: msg="{{ result_ec2.instances | count }}"

shell:やcommand:内で複雑な処理

  • jinja2テンプレート構文が使えるので、複雑な事ができる。しかし、見難くなるのでお勧めはしない
  • 例:dict_var1.option2.valueが空の場合、command実行時のオプションを省略する

with_subelements: サブ要素でループ


Terraformとの連携

Memo/TerraformでAWS EC2を起動して、ansible-playbookを実行したい時など。


shell中でreadコマンドがあっても待機しない

  • 例えば script.shの中で「read -p "(y/n)"」等の記述があった場合、playbook実行すると、プロンプトの表示はなく、中断するが成功扱いとなり、失敗に気づきにくい。
    1. shell: /path/to/script.sh

apt: Debian/Ubuntu等のパッケージ管理

  • apt - Manages apt-packages — Ansible Documentation
  • 新しいrepoを追加したり、現在のバージョンが古い時にキャッシュの更新が必要。ただループで毎回更新は遅くなるので有効期限を追加
    1.   apt:    name: "{{ item.name }}"    state: "{{ item.state }}"    update_cache: yes    cache_valid_time: 3600  with_items: "{{ apt_packages }}"

assert: テストに使える


vars_promptでカンマ区切り文字列を配列に展開する

  • playbook: カンマ前後に空白が入力されても良いように、trimを使う
    1. - hosts:  - localhost  become: False  gather_facts: False  connection: local  vars_prompt:  - name: "csv_str"    prompt: "Please enter csv"    private: no  tasks:  - set_fact: csv_array="{{ csv_str.split(',')  }}"  - debug: msg="{{ item | trim }}"    with_items: "{{ csv_array }}"
  • 実行結果: ansible 2.4.2.0
    1. ansible-playbook playbook.yml
    2. Please enter csv: aaa, bbb
    3.  
    4. ok: [localhost] => (item=aaa) => {
    5.     "changed": false, 
    6.     "item": "aaa", 
    7.     "msg": "aaa"
    8. }
    9. ok: [localhost] => (item= bbb ) => {
    10.     "changed": false, 
    11.     "item": " bbb ", 
    12.     "msg": "bbb"
    13. }

includeの代わりにimport_tasks, include_tasksを使う

  • ansible 2.4.2.0でincludeを使った場合の警告。静的と動的で明示的に分けて記述するように。
    1. [DEPRECATION WARNING]: The use of 'include' for tasks has been deprecated. Use 'import_tasks' for static inclusions or 'include_tasks' for 
    2. dynamic inclusions. This feature will be removed in a future release. Deprecation warnings can be disabled by setting deprecation_warnings=False
    3. in ansible.cfg.

Including and Importing — Ansible Documentation

  • import_tasks
    • 静的なinclude。実行前に評価される。
  • include_tasks
    • 動的なinclude。実行時に評価される
    • includeのファイル名に変数を含む場合
    • include時に変数を用いてloopする場合
    • handlerにはnotifyできない
    • –list-tags で出力されない
    • –list-tasks で出力されない

pause: 指定した秒/分だけ待つ、中断や続行を選択

  • playbook.yml
    1. - hosts: all  gather_facts: false  become: no  vars:  - pause_seconds: 0  tasks:  - ping:  - pause:      seconds: "{{ pause_seconds }}"    when: pause_seconds|int > 0
  • 実行結果: 60秒待機する
    1. time ansible-playbook -i test/hosts.ini -l localhost playbook.ping.yml -e 'pause_seconds=60'
    2. ...
    3. TASK [ping] ***************************************************************************************************************************************
    4. ok: [localhost]
    5.  
    6. TASK [pause] **************************************************************************************************************************************
    7. Pausing for 60 seconds
    8. (ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
    9. ok: [localhost]
    10.  
    11. PLAY RECAP ****************************************************************************************************************************************
    12. localhost                  : ok=2    changed=0    unreachable=0    failed=0
    13.  
    14.  
    15. real    1m7.318s
    16. user    0m4.149s
    17. sys     0m0.548s

yum_repository: yumリポジトリの追加/削除

  • /etc/yum.repos.d/external_repos.repo から [epel] セクションを消す
    1. - name: Remove repository from a specific repo file  yum_repository:    name: epel    file: external_repos    state: absent

include_role: role内の別taskをloadする

tasks/main.yml とは別のyamlをloadできる。
tasksを分割して、普段は実行されない処理を分けられる。


テキストの置換

  • remove_host変数にマッチした行を削除
    1.   - name: Remove the IP address    replace:      path: "{{ item }}"      regexp: '^.*{{ remove_host }}.*[\r\n]+'    with_items:    - /etc/hosts.deny

ansible-container: docker imageの作成


dockerの操作

  • Docker - Cloud Modules
    • リモートホストにインストールされたdockerを扱う。コンテナ内の構築は別。
    • リモートホスト上の準備
      1. sudo pip install docker-py docker-compose
  • 課題
    • remote host上のdockerコンテナ内にansible roleを適用したいがコンテナの接続に失敗する。「connection: docker」だけでは「docker command not found in PATH」が出るので、localhostのdockerコマンドを実行しようとしている

playbook中に再起動

サンプルコードはいくつもあるが、うまく動作しない

  • playbook.reboot.yml
    1. --- - hosts: all  become: True  gather_facts: True  tasks:    - name: debug      debug:        msg: "ansible_host={{ ansible_host }}"    - name: test connection (before reboot)      ping:    - name: reboot      shell: sleep 2 && shutdown -r now "Ansible reboot"      async: 1      poll: 0      ignore_errors: "{{ ansible_check_mode }}"    - name: wait for SSH port down      wait_for:        host: "{{ ansible_host }}"        port: 22        state: stopped        delay: 1        timeout: 60      delegate_to: 127.0.0.1      become: no    - name: wait for SSH port up      wait_for:        host: "{{ ansible_host }}"        port: 22        state: started        delay: 30        timeout: 300      delegate_to: 127.0.0.1      become: no    - name: test connection (after reboot)      ping:

with_fileglob: ファイル名にワイルドカードを指定してループ

localに置いた不特定のファイルをワイルドカード(*,?)を使って処理したい時に使える。
remoteのファイルは非対応。


too long for Unix domain socket

macOS v10.12(Sierra)で発生。
CentOS6/7では発生せず。

  • 解決方法
    • クライアントのOpenSSH 6.7以降なら「%%C」が使える。
      • ansible.cfg
        1. [ssh_connection]
        2. # '%C' by a hash of the concatenation: %l%h%p%r.
        3. control_path=%(directory)s/%%C
    • 複数クライアント混在環境だとansible.cfgに「%%C」と書いてしまうと、例えばCentOS6/7環境ではエラーになる

block: 複数のタスクのブロック化

  • Blocks ― Ansible Documentation
    • ansible 2.0以降で使える
    • 複数のタスクに同じtagを付ける、whenでの分岐、エラーハンドリング(例外処理)等に使える

Too many open filesでansibleの実行が停止する

  • CentOS 6でansible実行すると途中で停止する場合がある。ulimitの上限に達していた。
  • pam経由の場合と、daemonは別の設定が必要
  • CentOS 6:
    1. sudo tail -f /var/log/secure
    2. Jul  5 16:27:31 server sshd[1465]: error: accept from auth socket: Too many open files
    3.  
    4. ulimit -n
    5. 1024
    6.  
    7. cat /etc/security/limits.d/90-nproc.conf
    8. *          soft    nproc     1024
    9. root       soft    nproc     unlimited
  • CentOS 7:
    1. ulimit -n
    2. 1024
    3.  
    4. cat /etc/security/limits.d/20-nproc.conf
    5. *          soft    nproc     4096
    6. root       soft    nproc     unlimited
    7.  
    8. # もしdaemonのserviceファイルを変えた場合は、以下も必要
    9. sudo systemctl daemon-reload
    10. sudo systemctl <daemon> restart
  • ansibleで変更する場合
    1. - pam_limits:    domain: "{{ item.domain }}"    limit_type: "{{ item.limit_type }}"    limit_item: nofile    value: 65536  with_items:    - domain: root      limit_type: soft    - domain: root      limit_type: hard    - domain: "*"      limit_type: soft    - domain: "*"      limit_type: hard
  • playbook実行後、再起動
    1. sudo reboot
  • 再起動後
    1. ulimit -n
    2. 65536
    3.  
    4. cat /etc/security/limits.conf
    5. root    soft    nofile  65536
    6. root    hard    nofile  65536
    7. *       soft    nofile  65536
    8. *       hard    nofile  65536

factの再取得

「setup」の再実行で良い 動的にNICを追加した後に、factにも反映させたい場合など。

  • CentOS 7
    1.   tasks:    - debug: var=ansible_interfaces    - yum: name=docker    - setup:    - debug: var=ansible_interfaces

set_fact: 変数に値をセットする

  • 配列に辞書を複数追加する。array1を初期化しておかないとエラーになる。
    • playbook
      1. - hosts:    - localhost  become: False  gather_facts: False  connection: local  vars:    dict1:      key1: value1      key2: value2    dict2:      key3: value3    array1: []  tasks:    - set_fact: array1="{{ array1 + [ item ] }}"      with_items:        - "{{ dict1 }}"        - "{{ dict2 }}"    - debug: var=array1
      • 実行結果
        1. ansible-playbook playbook.yml
        2. ...
        3. TASK [debug] *******************************************************************
        4. ok: [localhost] => {
        5.     "array1": [
        6.         {
        7.             "key1": "value1",
        8.             "key2": "value2"
        9.         },
        10.         {
        11.             "key3": "value3"
        12.         }
        13.     ]
        14. }

コマンドのjson出力を取り込んで、yaml形式で出力する


to_nice_yaml でエラー


group_by: 動的にgroupを変更する

例えば「connection: local」だが、複数の「group_vars/env.yml」を環境毎に切り替えたい時に使える。
指定したgroup_vars内の変数が参照できるようになる。

  1.   vars_prompt:    - name: "inventory_group"      prompt: "Please enter inventory_group"      private: no  tasks:    - group_by: key="{{ inventory_group }}"      changed_when: False

assemble: 複数の設定ファイルから一つの設定を作る

assemble - Assembles a configuration file from fragments ― Ansible Documentation

  • /etc/app/conf.d/*.conf から /etc/app.conf を作成する
  • アプリがconf.d/形式をサポートしていない場合に使う

uri: HTTPリクエストの送信


jenkinsからansibleの実行


no_log: ログを出力しない

「-v」オプションを付けたときにもパスワード等はコンソールに表示したくない場合等

  • 複雑な変数をwith_itemで処理するとき、全て表示したくない場合
  • set_factで表示したい変数だけwith_itemsで作る時にも使える
    • ログの表示: コマンドオプションに 「-e 'show_log=true'」
    • playbook.yml
      1. ...  vars:    - show_log: false  tasks:    - debug: msg="{{ item }}"      with_items: "{{ huge_var }}"      no_log: "{{ not show_log|bool }}"

トラブルシューティング

  • ansible-playbookコマンドに「-vvv」を付ける
  • 「debug: var=<name>」で変数を見る
  • リモートの一時ファイルを残す。「$HOME/.ansible/tmp/」に残ったままになる。
  • pythonのデバッグログを出力する
    1. ANSIBLE_DEBUG=1 ansible -i ...

lookup: ファイルやテンプレートを取り込む

  • jinja2テンプレートで変数を展開して取り込む
    1. - debug: msg="{{ lookup('template', './some_template.j2') }}"

javaのpropertiesを読む

  1. - debug: msg="user.name is {{ lookup('ini', 'user.name type=properties file=user.properties') }}"

iniファイルを読む

  • v2.0以上で対応
  • aws configのdefaultセクションを読む。ただし、「section」に空白が含まれる場合、エラーになる。
    1. - debug: msg={{ lookup('ini', 'aws_access_key_id section=default file=~/.aws/config') }}
  • パラメータを渡したい場合、formatを使う
    1. - debug: msg="{{ lookup('ini', 'aws_access_key_id section=%s file=~/.aws/config'|format(section)) }}"

外部ファイルを変数に取り込む

lookup 関数で出来た

  • EC2(VPC)起動時のuser_dataにcloudinitのファイルを設定する
    • ansible 1.7.1で動作確認
    • roles/cloudinit/files/centos6.yml
    • playbook.yml
      1. - hosts: 127.0.0.1
      2.   gather_facts: False
      3.   vars:
      4.     ec2_user_data: "{{ lookup('file', 'roles/cloudinit/files/centos6.yml') }}"
      5.   tasks:
      6.   - name: ec2 instance
      7.     local_action:
      8.       module: ec2
      9.       keypair: example-key
      10.       group: example-sg
      11.       instance_type: t2.micro
      12.       image: ami-xxxxxxxx
      13.       region: ap-northeast-1
      14.       vpc_subnet_id: subnet-xxxxxxxx
      15.       assign_public_ip: yes
      16.       count: 1
      17.       volumes:
      18.         - device_name: /dev/sda1
      19.           volume_size: 10
      20. #        - device_name: /dev/sdb # t2インスタンスはephemeral diskは無い
      21. #          ephemeral: ephemeral0
      22.       instance_tags: '{"Name":"test.example.com","key2":"val2"}'
      23.       user_data: "{{ ec2_user_data }}"
      24.       wait: yes
      25.       wait_timeout: 600

fetch: リモートファイルをローカルにコピー

  • 複数のリモートホストから、ローカルにコピーできる。ツリー構造の維持もできる。ツリー構造が不要な場合は「flat=yes」
    1. ansible -i hosts.ini -m fetch -a 'src=/etc/hosts dest=/tmp/backup/' web*
    2.  
    3. tree -A /tmp/backup/
    4. /tmp/backup/
    5. ├── web01
    6. │   └── etc
    7. │       └── hosts
    8. └── web02
    9.     └── etc
    10.         └── hosts
  • "msg": "unable to calculate the checksum of the remote file" の場合、接続に失敗しているので、-m ping等で確認する。

expect: 対話処理の自動化


wait_for: 処理が完了するまで待機する


localにあるrpmファイルをインストールする

  • 環境:ansible 2.1.1.0
  • yum name=/tmp/example-1.0.0-1.rpm state=present or installedでは、rpmファイルのバージョンが変わってもインストールされない。
  • state=latestだと、リポジトリを検索しに行くので使えない。(yumドキュメント通り)
  • rpmコマンドでバージョンを取得して、異なるならばアンインストール後にインストールする
    1. - name: Find rpm  shell: ls -t $(find /tmp/ -type f -name example-1.0.0-1.rpm) | head -1  register: find_result  changed_when: false - name: Get version of installed rpm  shell: "rpm -q example | grep -o -P '[\\d\\.\\-]+' | head -n 1"  register: installed_version  changed_when: false  ignore_errors: yes - name: Get version of local rpm  shell: "rpm -qp {{ find_result.stdout }} | grep -o -P '[\\d\\.\\-]+' | head -n 1"  register: local_version  changed_when: false  ignore_errors: yes - name: Uninstall package  yum: name=example state=absent  when:    installed_version.rc == 0    and local_version.rc == 0    and installed_version.stdout != local_version.stdout - name: Install package  yum: name={{ find_result.stdout }} state=present  ignore_errors: "{{ ansible_check_mode }}" - name: Start/stop service  service: name=example state={{ example_state }} enabled={{ example_enabled }}  ignore_errors: "{{ ansible_check_mode }}"

aws profileを利用する

いつのバージョンから(boto3 module?)か「~/.boto」は不用になった。
assume role利用の際に邪魔になるので、「~/.boto」は作らない方が良い。

「~/.aws/credentials」が既にある場合、ansibleからその中のアクセスキー等を利用したい。
ansibleのawsモジュールには profile: オプションで boto configの値を指定でき、フォーマットはaws configと同じなのでsymlinkで良さそう

  • ansible v2.1, boto >= 2.24.0で動作確認できた。ansible v2.4.x以上は不具合があってdynamodb_tableは動かない
    1.   tasks: #    - name: create symlink ~/.boto #      file: src=~/.aws/credentials dest=~/.boto state=link    - name: Delete dynamodb table      dynamodb_table:        profile: "{{ aws_profile }}"        region: "{{ aws_region }}"        name: "{{ item }}"        hash_key_name: "{{ item }}" # ドキュメントでは required:no だが、実行時にエラーが出るため追加        state: absent      with_items:        - test

既存ファイルを空(サイズ0)にする


空ファイルの作成

  • lineinfile
    • /tmp/dummy が存在すれば、バックアップ後、空にする。ファイルが無ければ何もしない。
      1.     - lineinfile:        dest: /tmp/dummy        state: absent        regexp: "^.+$"        line: ""        backup: yes
    • 「backup: yes」で「/tmp/dummy.YYYY-mm-dd@HH:MM:SS~」のようなバックアップファイルが出来る
    • 「create: yes」を付けると、存在しなければ作成する

localhost対象時のエラー

  • ansible 2.1.1.0
  • localhost を対象にするつもりが、PLAY RECAPを見ると、「127.0.0.1」と「localhost」の2つ実行されており、「localhost」の時にgroup_vars/localhost.ymlを読んでいないようで、変数の未定義エラーが出る。
    1. cat group_vars/localhost.yml
    2. ...
    3.  
    4. cat hosts.ini
    5. [localhost]
    6. 127.0.0.1
    7.  
    8. cat playbook.yml
    9. - hosts: localhost
    10.   gather_facts: False
    11.   connection: local
    12. ...
    13.  
    14. ansible-playbook -i hosts.ini playbook.yml
    15.  
    16. fatal: [localhost]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: ...
    17.  
    18. PLAY RECAP *********************************************************************
    19. 127.0.0.1                  : ok=2    changed=2    unreachable=0    failed=0
    20. localhost                  : ok=0    changed=0    unreachable=0    failed=1
  • 対策
    • playbook.ymlを「- hosts: 127.0.0.1」に変更
    • ansible-playbook -l 127.0.0.1 -i hosts.ini ...

LDAPの管理

ansible 2.1.1.0現在、classやattributeをうまく扱えるmoduleはまだ無いようだ。

  • ldap_users.yml
    1. ldap_users: - cn: user01  dn: cn=user01,ou=users,dc=example,dc=com  sshPublicKey: ssh-rsa ...  description:  - dev  - stg  - prod
  • ldapsearch, ldapmodify を使うサンプル
  • psagers / ansible-ldap — Bitbucket
    • ansible-ldap is very simple and useful — ペンギンと愉快な機械の日々
    • playbookと同じディレクトリに、library/ldap_entry となるように配置する
    • objectClass: ldapPublicKey?, sshPublicKey?: ... で試してみたが、OKとなるものの、登録されない
    • 同名の属性を複数登録したい場合、description: [dev, stg, prod] のようになってしまう。
    • サンプル
      1.     - name: ldapPublicKey      ldap_entry:        server_uri: "{{ uri }}"        bind_dn: "{{ binddn }}"        bind_pw: "{{ bindpw }}"        dn: "{{ item.dn }}"        state: present        objectClass: ldapPublicKey        sshPublicKey: "{{ item.sshPublicKey }}"      with_items: "{{ ldap_users }}"    - name: override description exactly      ldap_attr:        server_uri: "{{ uri }}"        bind_dn: "{{ binddn }}"        bind_pw: "{{ bindpw }}"        dn: "{{ item.dn }}"        name: description        state: exact        values: "{{ item.description }}"      with_items: "{{ ldap_users }}"

SSHのログからアクセスIPを列挙する

  • 2016-07,08月のログから、ログインしたIPを列挙
    1. ansible -i hosts.ini -m shell -a "zgrep Accepted /var/log/secure-20160[7,8]*.gz | grep -o -P 'from ([^\s]+)' | sort | uniq -c | sort -nr" --sudo localhost
    2.  
    3. localhost | SUCCESS | rc=0 >>
    4.      79 from 192.168.10.1
    5.      22 from 192.168.10.128
    6.      22 from 127.0.0.1

インストール済みパッケージ一覧

yumモジュールではインストール済み一覧は取得できるが、一部のパッケージだけは指定できない。(nameとlistが排他指定)

  • playbook.yml
    1.   tasks:
    2.     - name: yum list installed
    3.       yum: list=installed
    4.       register: result
    5.       ignore_errors: yes
    6.       changed_when: False
    7.  
    8.     - debug: var=result
  • 実行結果:
    1. ok: [127.0.0.1] => {
    2.     "result": {
    3.         "changed": false,
    4.         "results": [
    5.             {
    6.                 "arch": "x86_64",
    7.                 "epoch": "0",
    8.                 "name": "MAKEDEV",
    9.                 "nevra": "0:MAKEDEV-3.24-6.el6.x86_64",
    10.                 "release": "6.el6",
    11.                 "repo": "installed",
    12.                 "version": "3.24",
    13.                 "yumstate": "installed"
    14.             },
    15.             ...

Ansible 2.1


SSHで接続できない場合

オプションとして「-vvvv」を付けると詳細なログが出る


ssh公開鍵の登録

  • www01ホストのuser01に~/.ssh/id_rsa.pubを登録する
    1. ansible -i hosts.ini -m authorized_key -a 'user=user01 key="{{lookup("file","~/.ssh/id_rsa.pub")}}" state=present' --sudo www01

現在のnameserverを取得

/etc/resolv.confのnameserverの値を参照したい。

  • CentOS6.x EPEL: ansible 2.1.0.0 では以下のように取得できる。
    1. ansible -m setup localhost
    2. ...
    3.         "ansible_dns": {
    4.             "nameservers": [
    5.                 "192.168.1.1"
    6.             ],
    7.             "search": [
    8.                 "localdomain"
    9.             ]
    10.         },

Ansible2.0


with_nested: 複数階層ループ

  • playbook
    1.     - name: give users access to multiple databases      debug: var="name={{ item[0] }} priv={{ item[1] }}"      with_nested:        - [ 'alice', 'bob' ]        - [ 'clientdb', 'employeedb' ]
  • 実行結果
    1. TASK: [give users access to multiple databases] ******************************* 
    2. ok: [127.0.0.1] => (item=['alice', 'clientdb']) => {
    3.     "item": [
    4.         "alice",
    5.         "clientdb"
    6.     ],
    7.     "var": {
    8.         "name=alice priv=clientdb": "name=alice priv=clientdb"
    9.     }
    10. }
    11. ok: [127.0.0.1] => (item=['alice', 'employeedb']) => {
    12.     "item": [
    13.         "alice",
    14.         "employeedb"
    15.     ],
    16.     "var": {
    17.         "name=alice priv=employeedb": "name=alice priv=employeedb"
    18.     }
    19. }
    20. ok: [127.0.0.1] => (item=['bob', 'clientdb']) => {
    21.     "item": [
    22.         "bob",
    23.         "clientdb"
    24.     ],
    25.     "var": {
    26.         "name=bob priv=clientdb": "name=bob priv=clientdb"
    27.     }
    28. }
    29. ok: [127.0.0.1] => (item=['bob', 'employeedb']) => {
    30.     "item": [
    31.         "bob",
    32.         "employeedb"
    33.     ],
    34.     "var": {
    35.         "name=bob priv=employeedb": "name=bob priv=employeedb"
    36.     }
    37. }

Prompts: ユーザ入力を待つ

  • playbook
    1. - hosts: all  gather_facts: False  connection: local  vars_prompt:    - name: "user_name"      prompt: "Please enter name"      private: no  tasks:    - debug: var=user_name
  • 実行
    1. ansible-playbook -i hosts.ini test.yml
    2. Please enter name: hoge
    3.  
    4. TASK: [debug var=user_name] *************************************************** 
    5. ok: [localhost] => {
    6.     "var": {
    7.         "user_name": "hoge"
    8.     }
    9. }
  • -e "var1=value1 var2=value2" の様に実行時に値を渡せる。この場合はプロンプトは表示されない。
    1. ansible-playbook -i hosts.ini test.yml -e "user_name=hogehoge"
    2.  
    3. TASK: [debug var=user_name] *************************************************** 
    4. ok: [localhost] => {
    5.     "var": {
    6.         "user_name": "hogehoge"
    7.     }
    8. }
  • 例:削除確認。"no"なら中止
    1.   vars_prompt:    - name: "confirm"      prompt: "Do you really want to delete ? [yes/no]"      private: no  tasks:    - name: confirm      fail: msg="aborted"      when: confirm != "yes"

任意のsshオプションつけて実行

ansible.cfg内で「ssh_args = 」で任意のオプションを指定できる。
環境変数に ANSIBLE_SSH_ARGS があると、ansible.cfgよりも優先される。

  • playbookと同じディレクトリに ansible.cfg を置く
    1. [defaults]
    2. forks = 5
    3. timeout = 30
    4. # CentOS6でparamikoを使う場合、Falseにする事で早くなる
    5. record_host_keys = False
    6.  
    7. [ssh_connection]
    8. control_path = %(directory)s/%%h-%%r
    9. ansible_connection = ssh
    10. ssh_args = ServerAliveInterval=15 -o ServerAliveCountMax=4
    11. # Trueにすると高速化するが、/etc/sudoersでrequirettyをコメントアウトする必要がある
    12. pipelining = False

拡張子を除いたファイル名の取得

  • v1.9.4で実現する。v2.0以降では「splitext 」が使える。
    1. - debug: msg="{{ '/etc/httpd/conf/httpd.conf' | basename | regex_replace('\.conf$', '') }}" # httpd
  • v2.0以降
    1. - debug: msg="{{ '/etc/httpd/httpd.conf' | basename | splitext }}" # httpd - debug: msg="{{ '/etc/httpd/httpd.conf.j2' | basename | splitext | first }}" # httpd.conf

tarball をインストールする

  • 環境
    • CentOS 6.x 64bit
    • ansible v1.9.4
  • 例:phantomjs
    • bitbucketのファイル実体はAWS S3にあるようで、リンクを踏むと「Location: https://xxxx.s3.amazonaws.com/...」のように返ってくる。しかし、unarchiveモジュールはそれをダウンロードできなかった。get_urlモジュールだとOK
    • いまいちな点:/tmp/phantomjs.tar.bz2 をバージョンの判断に使っているので、残ったままになる。削除すると再ダウンロードから始まる
  • roles/phantomjs/defaults/main.yml
    1. phantomjs_install_dir: /usr/local/bin
    2. phantomjs_url:
    3.   - url: https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2
    4.     sha256sum: "a1d9628118e270f26c4ddd1d7f3502a93b48ede334b8585d11c1c3ae7bc7163a" # sha256sum phantomjs-1.9.8-linux-x86_64.tar.bz2
  • roles/phantomjs/tasks/main.yml
    1. - name: Download PhantomJS
    2.   get_url: url={{ phantomjs_url[0].url }} sha256sum={{ phantomjs_url[0].sha256sum }} dest=/tmp/phantomjs.tar.bz2 force=no
    3.   register: new_archive
    4.   tags:
    5.     - phantomjs
    6.  
    7. - name: Unarchive PhantomJS
    8.   unarchive: src=/tmp/phantomjs.tar.bz2 dest=/tmp copy=no creates=yes
    9.   when: new_archive|changed
    10.   tags:
    11.     - phantomjs
    12.  
    13. - name: Install PhantomJS
    14.   shell: cp -f /tmp/{{ phantomjs_url[0].url | basename | regex_replace('\.tar\.bz2|\.tar\.gz$', '') }}/bin/phantomjs {{ phantomjs_install_dir }}/
    15.   when: new_archive|changed
    16.   tags:
    17.     - phantomjs

registerの結果を使って複数回ループする

  • 例:user01がmysqlのログを見えるように、またwheelグループにも追加
    1. - hosts: all  gather_facts: true  become: yes  vars:    - user_name: user01    - user_groups:      - wheel      - mysql    - debug: 0  pre_tasks:  roles:  tasks:    - name: Check if user exists      shell: /usr/bin/id -u {{ item }}      register: result_id      with_items: user_groups      ignore_errors: yes      changed_when: False    - debug: var=result_id      when: debug != 0    - name: Modify user groups      user: name={{ user_name }} groups={{ item.item }} append=yes      when: item.rc == 0      with_items: "{{ result_id.results }}"  handlers:  post_tasks:

pipelining=TrueでSSHが失敗する場合

  • 「sorry, you must have a tty to run sudo」エラーで失敗する場合
  • pipelining pipelining=Trueだと、高速化にはなるが、ttyが使えないと失敗する
  • 接続先ホストでrequirettyを無効にする必要がある。
    1. sudo vim /etc/sudoers
    2. ----
    3. Defaults    !requiretty
    4. ----
  • または ansible -c paramiko を指定する

1.9からsudoが必要な時はbecomeを使う

root権限が必要な場合、1.9から「become: yes」が推奨。
「sudo: true」は非推奨になった。


長い行を改行する

YAML構文を使って分割する

  • '>' : folded block記法。改行が入らないので1行として実行される
    1. # "echo foo var"として実行される - shell: >    echo foo    var
  • '|' : literal block記法。改行が入る
    1. # "echo foo\n echo var"として実行される - shell: |    echo foo    echo var

パフォーマンスチューニング

  • SSH
    • OpenSSH 5.5以下の場合(CentOS6/openssh-5.3p1-118.1.el6_8.x86_64)
      • paramikoライブラリが使用される。ansible_connectionオプションでsshを明示的に指定できる。
      • record_host_keys=False にする。 record_host_keys
    • OpenSSH 5.6以上(CentOS7/openssh-6.6.1p1-25.el7_2.x86_64)
      • 自動的にsshが使われる。
      • ansible.cfg
        1. [defaults]
        2. # 明示的にsshを使う
        3. transport      = ssh
        4.  
        5. [ssh_connection]
        6. # ssh接続の再利用をする。タイムアウトの時間を延ばす
        7. ssh_args = -F ssh-config -o ControlMaster=auto -o ControlPersist=300s
        8. # ホスト名が長い時にエラーになるのを防ぐ
        9. control_path = %(directory)s/%%h-%%r
        10. # Trueにしたい場合、全クライアントの/etc/sudoersのrequirettyを無効にする必要がある。sudo時にエラーになる。
        11. pipelining = True
    • OpenSSH 6.7以降
      • ansible.cfg
        1. [ssh_connection]
        2. # '%C' by a hash of the concatenation: %l%h%p%r.
        3. control_path=%(directory)s/%%C
  • factキャッシュ
  • copyモジュールの代わりに、synchronizeモジュールを使う
    • 複数ファイル時、CentOS6だとparamikoライブラリが使われ非常に遅いかタイムアウトする。
    • synchronizeモジュールは、内部でrsyncを使うため数秒ですむ。
    • copyモジュールと違い、ownerやgroupが変わらないので、fileモジュールで変える
    • ansible.cfgでssh_argsオプションを使用している場合は、「use_ssh_args=yes」にする

validate:設定ファイル等の検証してから更新

  • template, lineinfile, blockinfileはvalidate='command %s'で検証を行って、成功した場合(exit code=0)のみ更新する事ができる。
  • sshd_configの例
    1. validate='sshd -t -f %s'
  • visudoの例
    1. validate='visudo -cf %s'
  • httpdの例:httpd.confのみ対応。conf.d/以下のファイルを指定するとエラーになる
    1. validate: 'apachectl -t -f %s'
  • phpの例
    1. alidate='php -l %s'

error while evaluating conditional

  • 変数が未定義の場合でも出る
  • 例:libselinux-python がインストールされていない場合
    • -m setupの結果
      1. "ansible_selinux": false
    • when: ansible_selinux.status == "disabled" の部分で発生する

yum groupinstall相当

  • yum groupinstall相当をしたい
    1. sudo yum groupinstall "Japanese Support"
  • yum group-idを調べる。「group name (group-id)」のように表示される
    1. LANG=C yum grouplist -v
    2. ...
    3.    Japanese Support (japanese-support) [ja]
    4. ...
  • with_itemsで指定する場合
    1. - hosts: all  sudo: True  tasks:    - name: yum groupinstall      yum: name="{{ item }}" state=present      with_items:        - "@japanese-support"

Extras Modulesを使う

  • Extras Modules
    • stableではないモジュールが入っている。うまく動作しない可能性がある。
  • 独自ライブラリの指定はいくつかある
    • playbook.ymlと同じ階層に library ディレクトリを作成する
    • export ANSIBLE_LIBRARY=...
    • --module-path=...
  • 例: ansible 1.9から pam_limits(v2.0向け) を使う
    1. git clone https://github.com/ansible/ansible-modules-extras.git
    2. export ANSIBLE_LIBRARY=ansible-modules-extras
    3. ansible-playbook -i hosts.ini playbook.yml

sshで繋がずlocalで実行

  • playbook.yml: connection: local を指定する
    1. - hosts: localhost  gather_facts: True  sudo: False  connection: local

パッケージがインストール済みかのチェック

  • yum/rpmを使わずにインストールしている場合(yum repo等)、statでファイルの有無等で判断した方が良さそう
    1. - name: Check if Package is installed  stat: path=/etc/yum.repos.d/epel.repo  register: result  ignore_errors: yes  changed_when: False # パッケージが無かった場合の処理 - debug: var=result  when: result.stat.exists == false
  • 「yum: list=installed」でインストール済みパッケージ一覧が取得できるが、全て列挙されるため遅い&使いにくい。
    1. - name: Check if Package is installed  shell: rpm -q {{ package }} > /dev/null; echo $?  args:    warn: no  register: result  ignore_errors: yes  changed_when: False # パッケージが無かった場合の処理 - debug: var=result  when: result.stdout == "0"

変更扱いにしない

  • changed_when: False
    毎回実行するが、変更扱いにしない。インストールされているかのチェック等に便利
    1. - name: Check if git v1 is installed  shell: rpm -q git > /dev/null  ignore_errors: yes  register: result  changed_when: False

handlers

  • Intro to Playbooks
    • 一番最後に実行される
    • 設定ファイルを複数書き換えた後にサービスの再起動が必要な時などに使える
  • whenの条件に注意。サービスを止めたままにしたくとも、notifyが発生するとhandlerが実行されるため、設定ファイルが書き換わった場合など、予期せずサービスが起動する事がある。そのため変数(例:iptables_state)を用意して条件に入れておく。
    • handlers/main.yml
      1. - name: restart iptables
      2.   service: name=iptables state=restarted
      3.   when: ansible_os_family in [ "RedHat" ] and iptables_state == "started"

roles

  • tagが付いていないroleにtagを付けて実行。「-t terraform」で指定ロールだけ実行できる
    1.   roles:  - { role: andrewrothstein.terraform, tags: terraform }
  • rolesの前後で実行したい場合は「pre_tasks」「post_tasks」を使う
  • role-dependencies: roleの依存関係を設定できる。依存先のroleが先に実行される。複数のroleから依存があっても1回だけ実行される。
    • roles/zabbix_agent/meta/main.yml
      1. dependencies:  - { role: epel }
  • rolesではtagsオプションでrole内のタグを上書きできる。指定のタグのみ実行するものでは無い。
  • 指定のディストリビューション、バージョンの場合のみroleを実行する。例:CentOS7の場合のみfirewalldのroleを使う
    1. - hosts: webservers  roles:    - { role: firewalld, when: "ansible_os_family == 'RedHat' and ansible_distribution_major_version|int >= 7" }
  • playbook実行時、rolesの中の変数(例:roles/foo/defaults/main.yml)を条件文に使うと全てスキップする。予めhosts, group_vars, host_vars等で定義すれば正常に動作する
    1. - hosts: webservers  roles:    - { role: some_role, when: "some_role_enabled == 'yes'" }

依存関係


自作モジュール

Developing Modules — Ansible Documentation


コマンドのjson出力を変数として取り込む

  • 例:awscliのjson出力を変数に取り込む。
    1.   - name: list-hosted-zones    local_action: shell aws --profile {{ aws_profile }} route53 list-hosted-zones --output json    register: route53_result  - set_fact: route53_hostedzone="{{ (route53_result.stdout|from_json).HostedZones }}"  - debug: var=route53_hostedzone

環境変数の指定

  • コマンド実行時に /opt/bin を追加する
    1. - hosts: all  gather_facts: True  tasks:  - shell: test.sh    environment:      PATH: "/opt/bin:{{ ansible_env.PATH }}"    register: result  - debug: var=result.stdout
  • タイムゾーンを変更する
    1.     - shell: date      environment:        TZ: "{{ lookup('env', 'TZ') }}"
    • タイムゾーンを変更して実行
      1. TZ=JST ansible-playbook -l 127.0.0.1 -i hosts.ini playbook.yml
      2. TZ=UTC ansible-playbook -l 127.0.0.1 -i hosts.ini playbook.yml

コマンドの実行

  • command
    • 環境変数は無効
    • パイプ"|"は使用できない
  • shell
    • shell(デフォルト:/bin/sh)を経由して実行される
    • パイプ"|"を使用可能
    • 環境変数は有効
    • 複数行の場合、";"で区切る事ができる
      1.   - shell: >      echo foo;      echo var;

local_action: localhostで実行する

  • 例:uptime コマンドを localhost で実行
    1. - hosts: 127.0.0.1
    2.   gather_facts: False
    3.   tasks:
    4.   - name: uptime
    5.     local_action: command uptime
    6.     register: result
    7.   - debug: var=result
  • --sudo を付けて実行した場合、local_action の結果もrootユーザで実行した事になる
    • 例えば実行時のユーザの設定ファイル(/home/user01/.aws/config) を参照するが、--sudoを付けたい場合に問題が起きる。/root/.aws/configを探そうとする
    • playbook.yml
      1. - hosts: 127.0.0.1
      2.   gather_facts: False
      3.   tasks:
      4.   - name: pwd
      5.     local_action: shell echo $HOME
      6.     register: result
      7.   - debug: var=result.stdout
    • --sudo を付けて実行した場合
      1. ansible-playbook -i hosts.ini -l localhost --sudo playbook.yml
      2. ...
      3. TASK: [debug var=result.stdout] ***********************************************
      4. ok: [127.0.0.1] => {
      5.     "result.stdout": "/root"
      6. }

SELinux

  • 標準で selinux モジュールがある
  • state=disabledに設定後、getenforceすると"Enforcing(有効)"だった。 (ansible-1.7-1.el6.noarchで確認)
    • "setenforce 0" するか、再起動する必要があった

target uses selinux but python bindings (libselinux-python) aren't installed!

  • /etc/hostsを変更しようとしたところ以下のエラーが出た
    1. msg: Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!
  • 対象ホストにログインしてSElinuxを確認すると Permissive だった
    1. getenforce
    2. Permissive
  • ansibleでlibselinux-pythonをインストール後、正常に動作した
    1. ansible -i hosts.ini -m shell -a 'yum install libselinux-python -y' --sudo <host group>

split: 文字列の分割

  • ansible-1.7-1.el6.noarch
  • test.yml
    1. - hosts: 127.0.0.1  gather_facts: False  vars:    keys: ""  tasks:    - name: split      debug: var="{{ item }}"      with_items: "{{ keys.split(',') }}"
  • 実行
    1. ansible-playbook -i hosts.ini test.yaml -e 'pub_keys=aaa,bbb,ccc'
    2.  
    3. TASK: [split] *****************************************************************
    4. ok: [127.0.0.1] => (item=aaa) => {
    5.     "aaa": "{{ aaa }}",
    6.     "item": "aaa"
    7. }
    8. ok: [127.0.0.1] => (item=bbb) => {
    9.     "bbb": "{{ bbb }}",
    10.     "item": "bbb"
    11. }
    12. ok: [127.0.0.1] => (item=ccc) => {
    13.     "ccc": "{{ ccc }}",
    14.     "item": "ccc"
    15. }

ansible galaxy

  • Ansible Galaxy | geerlingguy.repo-epel を ./roles/ にダウンロード
    1. ansible-galaxy install geerlingguy.repo-remi -p ./roles/
    2. # tag指定
    3. ansible-galaxy install geerlingguy.repo-remi,v1.2.0 -p ./roles/
  • 複数のroleをダウンロード
    1. vim requirements.yml
    2. ----
    3. - src: geerlingguy.repo-remi
    4. - src: geerlingguy.apache
    5. - src: geerlingguy.php
    6. - src: geerlingguy.php-versions
    7. ----
    8. ansible-galaxy install -p ./roles/ -r requirements.yml

Ansible Galaxyへ登録

  • 例: lirc roleを作成。
    1. ansible-galaxy init lirc
    2.  
    3. # ディレクトリ構成
    4. tree lirc
    5. lirc
    6. ├── README.md
    7. ├── defaults
    8. &#160;&#160; └── main.yml
    9. ├── handlers
    10. &#160;&#160; └── main.yml
    11. ├── meta
    12. &#160;&#160; └── main.yml
    13. ├── tasks
    14. &#160;&#160; └── main.yml
    15. ├── tests
    16. &#160;&#160; ├── inventory
    17. &#160;&#160; └── test.yml
    18. └── vars
    19.     └── main.yml

--check時に挙動を変更する

以下のように、shell: の結果を元に判定するような場合、「--check」では、チェックtaskが実行されないため、エラーとなるのを回避する。

  • サンプル
    1. - name: check if epel exists  shell: rpm -q epel-release > /dev/null; echo $?  args:    warn: no  register: result  changed_when: False - name: install epel repository  yum:    name="http://ftp.riken.jp/Linux/fedora/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm"    state=present  when: result.stdout != "0"  ignore_errors: "{{ ansible_check_mode }}" # ansible 2.1
  • Check Mode (“Dry Run”) — Ansible Documentation
    • shellやcommand moduleはcheck mode時(--check)は実行されないため、registerで登録された変数を、debug moduleで表示しようとするとエラーになる。
    • ansible 2.2: check_mode: が追加された。
      • no: 常にnormal modeとして実行。command moduleに付けた場合、--checkでも実行される。
      • yes: 常にcheck modeとして実行。実行されないため、moduleのテスト時に使える?
        1. check_mode: no
    • ansible 2.1: ansible_check_mode を使う。--check時のエラーを無視するようにはできる
      1. ignore_errors: "{{ ansible_check_mode }}"

ansible-playbookオプション

  • --check: dry-runモード
  • --diff: 変更点を表示。--checkと同時に指定すると良い
  • -v: verbose mode, 例えばcommandが成功した時のログは通常でてこないが、このオプションを付けると見える
  • -e, --extra-vars 'var1=val1 var2=val2': 変数名と値をコマンドラインから渡す。vars_promptの変数名を指定すれば、promptの表示が無くなるため便利

Windowsの操作


ファイル/ディレクトリのコピー

  • ローカルファイルをリモートにコピー
    1. ansible -i hosts.ini -m copy -a "copy: src=/src/myfiles/foo.conf dest=/etc/foo.conf" --user <username> --ask-pass --sudo <hostname>
  • ローカルディレクトリをリモートディレクトリにコピー
    1. ansible -i hosts.ini -m copy -a "copy: src=/src/myfiles/ dest=/tmp/myfiles/" --user <username> --ask-pass --sudo <hostname>

実行順序

  • 基本的には上から下だが、pre_tasks > roles > tasks > handlers > post_tasks 順序で実行される
  • ホストが複数ある場合は、1タスクをすべてのホストに実行してから、次のタスクへ。
  • 全てのホストで失敗した場合、中断する
  • 例: ansible 1.9.4
    • hosts.ini
      1. [web]
      2. web01.example.com
      3. web02.example.com
    • test.yml
      1. - hosts: web
      2.   gather_facts: True
      3.   vars:
      4.  
      5.   pre_tasks:
      6.     - debug: msg="pre_tasks01"
      7.  
      8.   roles:
      9.     - role01
      10.  
      11.   tasks:
      12.     - name: task01
      13.       file: path=/tmp/task01 state=touch
      14.       notify: handler01
      15.  
      16.   handlers:
      17.     - name: handler01
      18.       debug: msg="handler01"
      19.  
      20.   post_tasks:
      21.     - debug: msg="post_tasks01"
    • 実行
      1. ansible-playbook -i hosts.ini test.yml
      2. ...
      3. TASK: [debug msg="pre_tasks01"] ***********************************************
      4. ok: [web01.example.com] => {
      5.     "msg": "pre_tasks01"
      6. }
      7. ok: [web02.example.com] => {
      8.     "msg": "pre_tasks01"
      9. }
      10.  
      11. TASK: [role01 | debug msg="role01"] *******************************************
      12. ok: [web02.example.com] => {
      13.     "msg": "role01"
      14. }
      15. ok: [web01.example.com] => {
      16.     "msg": "role01"
      17. }
      18.  
      19. TASK: [task01] ****************************************************************
      20. changed: [web02.example.com]
      21. changed: [web01.example.com]
      22.  
      23. NOTIFIED: [handler01] *********************************************************
      24. ok: [web01.example.com] => {
      25.     "msg": "handler01"
      26. }
      27. ok: [web02.example.com] => {
      28.     "msg": "handler01"
      29. }
      30.  
      31. TASK: [debug msg="post_tasks01"] **********************************************
      32. ok: [web01.example.com] => {
      33.     "msg": "post_tasks01"
      34. }
      35. ok: [web02.example.com] => {
      36.     "msg": "post_tasks01"
      37. }
      38.  
      39. PLAY RECAP ********************************************************************
      40. web01.example.com          : ok=6    changed=1    unreachable=0    failed=0
      41. web02.example.com          : ok=6    changed=1    unreachable=0    failed=0

Validation


ファイル/ディレクトリの存在チェック

  • ファイルの存在チェック
    1.   - stat: path=/tmp/test.log    register: result    changed_when: False      - debug: var=result  - fail: msg="file not found"    when: result.stat.exists == False
  • ディレクトリの存在チェック
    1.   - stat: path=/tmp/test    register: result    changed_when: False      - debug: var=result  - fail: msg="directory not found"    when: result.stat.exists == False or result.stat.isdir == False
  • ローカルマシン上のチェックは "local_action: stat path=/tmp/test.log" のようにする

変数が未定義、空のチェック

  • 変数が未定義、空の場合に失敗させる。
    • vars_prompt:を使ってenterだけ入力した場合は、「var1==""」が一致する
  1.   - name: validate variable    fail: msg="Please enter correct var1"    when: (var1 is not defined)      or (var1 == None)      or (var1 | trim == "")

if

  • 変数に定数が含まれていたらで分岐する
    1. {% if 'description:' in item.stdout %}
    2. replace: description
    3. {% else %}
    4. add: description
    5. {% endif %}
    • 1行で書く場合
      1. {{ 'replace' if 'description:' in item.stdout else 'add' }}: description

Module モジュール

file

  • ディレクトリとsymlinkを作る
    1.   - name: Create directory
    2.     file: path=/tmp/test_dir state=directory owner=root group=root mode=0755
    3.  
    4.   - name: Create symlink
    5.     file: src=/tmp/test_dir dest=/tmp/test_link state=link

Conditionals 条件分岐

  • 変数が未定義の場合: var1 is not defined
  • 変数が空("")の場合: var1 == None
  • when: で文字列と数値を比較する場合は、'|int'が必要
    1. when: ansible_distribution_major_version|int >= 7
  • 'yes', 'no', 'True', 'False'をbool値として比較したい場合
    1. when: is_enabled|bool
  • result変数の中に「str1」文字が含まれるかのチェック。否定は「not in」
    1. when: '"str1" in result'
  • 配列
    1. when: ansible_distribution in [ 'CentOS', 'Red Hat Enterprise Linux' ]
  • 正規表現
    1. when: ansible_hostname is match("^web") # | match(str) はv2.9で廃止予定

Error Handling エラーハンドリング

  • デフォルト(v1.7.1)では、同じコマンドが全てのホストで失敗した場合に停止するようだ
  • failed_when: コマンドが失敗した時に何かする
    • Controlling What Defines Failure
    • 終了コード > 2の時に失敗とする
      1. - hosts: 127.0.0.1
      2.   gather_facts: False
      3.   tasks:
      4.   - name: test
      5.     local_action: shell exit 2
      6.     register: result
      7.     failed_when: result.rc > 2
      8.   - debug: var=result
  • ignore_errors: yes / エラーが起きても、スキップする
    1.   - name: Stop services    service: name="{{ item }}" state=stopped enabled=no    with_items:      - cups    ignore_errors: yes

新規ファイルを作る

  • blockinfileは複数行も扱える。ansible v2.0以降
  • lineinfile を使って、1行だけのファイルを作る。例:sudo用に /etc/sudoers.d/user01 を作る
    1. - name: Add user to the sudoers    lineinfile: dest="/etc/sudoers.d/user01"      owner=root      group=root      mode=0440      state=present      create=yes      regexp="^user01 .*"      line="user01 ALL=(ALL) NOPASSWD{{':'}} ALL"      validate='visudo -cf %s'

Magic Variables: マジック変数

  • Magic Variables
    • 定義済み変数
    • varsも参照できる
    • gather_facts: Falseでも参照できる
    • group_names: ホストが所属するグループ一覧
    • groups: グループとホスト
    • inventory_hostname: インベントリに書かれたホスト名
    • inventory_hostname_short: inventory_hostnameの初めの"."まで
    • play_hosts: Playbook対象のホスト
    • inventory_dir: インベントリ(hosts.ini)のディレクトリ
    • inventory_file: インベントリ(hosts.ini)のフルパス

Variables 変数

  • group_vars/all.yml にいれると全てのグループで有効になる
  • group_varsやhost_varsはグループ名やホスト名のディレクトリを作って、それ以下に複数のymlを入れると全て読み込む。長いymlを分割したい場合に便利
    • group_vars/web/var1.yml
    • host_vars/web01/var1.yml
  • 定数と変数の結合
    1.   stat: path="{{ '/etc/sysconfig/network-scripts/ifcfg-' + item }}"  with_items: "{{ ansible_interfaces }}"
  • 結合した後にフィルタ
    1. "{{ ('/etc/sysconfig/network-scripts/ifcfg-' + item) | dirname }}"
  • フィルターが使える。例: test.yml
    1. - hosts: 127.0.0.1  gather_facts: False  vars:    test_list: [ aaa, bbb, ccc ]    test_path: /tmp/parent1/child1/child2/test.txt  tasks:    - name: join      debug: var="{{ test_list | join(' ') }}"    - name: basename      debug: var={{ test_path | basename }}    - name: dirname      debug: var={{ test_path | dirname }}    - name: expanduser      debug: var={{ '~/test' | expanduser }}
    • 実行結果
      1. ansible-playbook test.yml
      2.  
      3. TASK: [join] ****************************************************************** 
      4. ok: [127.0.0.1] => {
      5.     "aaa bbb ccc": "{{ aaa bbb ccc }}"
      6. }
      7.  
      8. TASK: [basename] ************************************************************** 
      9. ok: [127.0.0.1] => {
      10.     "test.txt": "{{ test.txt }}"
      11. }
      12.  
      13. TASK: [dirname] *************************************************************** 
      14. ok: [127.0.0.1] => {
      15.     "/tmp/parent1/child1/child2": "{{ /tmp/parent1/child1/child2 }}"
      16. }
      17.  
      18. TASK: [expanduser] ************************************************************ 
      19. ok: [127.0.0.1] => {
      20.     "/home/CURRENT_USER/test": "{{ /home/CURRENT_USER/test }}"
      21. }
  • var1: 0010 と定義すると先頭の0が省略され "10"になる。文字列として解釈させるには var1: "0010" とする必要がある
  • roleにデフォルト変数を設定する時は、varsではなく、defaultsを使った方が良い。varsだと、group_varsより優先度が高いため、varsの値が優先される。
    • roles/x/defaults/main.yml
      1. x_val1: default1
  • 一部の変数だけを別ファイルにできる
    1. vars_files:  - group_vars/external_vars.yml

長い変数の分割

  • Memo/yaml参照。アンカー/エイリアスを使う
    • 1 yamlファイル内のみ。複数のyamlファイルに分割すると使えなかった。(ansible 2.5.3)
  • 複数のyamlファイルに分かれている場合、jinja2テンプレートの機能が使える
    1. long_dict1_key2:  key1: val1  key2: val2 long_dict1:  key1:    key1: val1    key2: val2  key2: "{{ long_dict1_key2 }}"

変数の動的参照

ある変数を元に別の変数を参照したい場合。

  • ansible_os_family, ansible_distribution_major_version によって参照する変数の内容を変更する。ポイントはhashのkeyが数値だと参照時にエラーになる。ダブルクオートで囲んで文字列と扱う。
    1. - hosts: localhost  gather_facts: true  become: no  connection: local  vars:  - os_var:      RedHat:        "6": /path/to/centos6        "7": /path/to/centos7  tasks:  - debug: msg="{{ os_var[ansible_os_family][ansible_distribution_major_version] }}"

eval()のような使い方

  • How To create Ansible variable from string - Stack Overflow
  • 例: ansible 2.3.0.0
  • playbook:
    1.   vars:    - array1:      - aaa      - bbb    - array2:      - ccc    - key_name: key1    - hash1:        key1: "{{ array1 }} + {{ array2 }}"  tasks:    - debug: var=hash1    - debug: msg="{{ hash1[key_name] }}"
  • 実行結果:
    1. TASK [debug] **********************************
    2. ok: [localhost] => {
    3.     "changed": false,
    4.     "hash1": {
    5.         "key1": [
    6.             "aaa",
    7.             "bbb",
    8.             "ccc"
    9.         ]
    10.     }
    11. }
    12.  
    13. TASK [debug] ***********************************
    14. ok: [localhost] => {
    15.     "changed": false,
    16.     "msg": [
    17.         "aaa",
    18.         "bbb",
    19.         "ccc"
    20.     ]
    21. }

hash変数のマージ

hash型変数の場合、通常はreplaceなので全て置き換わる。
hash内の一部だけ変えたい(マージ)場合、ansible.cfgで「hash_behaviour = merge」にする必要がある。

  • default.yml
    1. example_conf:  var1: foo  var2: bar
  • web.yml
    1. example_conf:  var1: hoge
  • playbook.yml
    1.   vars_files:    - default.yml    - web.yml
  • playbookの実行結果: デフォルトは「hash_behaviour = replace」なので「var2」が消える。
  • マージしたい場合
    • hash_behaviour
    • ansible v1.9: var1だけが上書きされ、var2はそのまま
      1. vi ./ansible.cfg
      2. ----
      3. [defaults]
      4. hash_behaviour = merge
      5. ----
    • ansible v2.0: include_vars_merged が使える

特定ユーザだけリモートsshからsudoできるように

デフォルトではttyなしのsshでは、sudoできないように設定されている場合が多い。

  • rootユーザだけttyなしでもsudo許可
    1. sudo visudo
    2. ----
    3. Defaults    requiretty
    4. Defaults:root    !requiretty
    5. ----

ping moduleで接続テスト


多段ssh環境で実行

接続にsshを使うようにし、ssh側で多段設定をする。
ホストの接続には名前を使う。DNSは付けなくても良い。

  • client.hostから other.host にコマンドを実行させたい場合
    • CentOS 6.x, ansible 1.9.4
    • 環境変数に ANSIBLE_SSH_ARGS="-F $HOME/.ssh/config" があると、ansible.cfgよりも優先されるので「unset ANSIBLE_SSH_ARGS」で無効にしておく
    • カレントディレクトリに ansible.cfg を用意
      1. vim ansible.cfg
      2. ----
      3. [defaults]
      4.  
      5. [ssh_connection]
      6. ansible_connection = ssh
      7. control_path = %(directory)s/%%h-%%r
      8. ssh_args = -F ssh_config
      9. ----
    • ssh_config
      1. Host *
      2.   StrictHostKeyChecking=no
      3.   UserKnownHostsFile=/dev/null
      4.   LogLevel ERROR
      5.   ForwardAgent yes
      6.  
      7. Host gateway.host
      8.   HostName     192.168.1.10
      9.   User         gw_user
      10.   IdentityFile ~/.ssh/id_rsa
      11.   ProxyCommand none
      12.  
      13. Host other.host
      14.   HostName     192.168.1.11
      15.   User         other_user
      16.   IdentityFile /.ssh/id_rsa
      17.   ProxyCommand ssh -F ssh_config -W %h:%p gateway.host
    • sshコマンドで接続確認
      1. ssh -F ssh_config other.host
    • ansible pingで接続確認
      1. ansible hosts.ini -m ping other.host
    • ssh-agentで秘密鍵を複数登録できる。その場合はssh_config「IdentityFile?」の指定は不要になる。
      1. ssh-agent bash
      2. ssh-add ~/.ssh/id_rsa
  • 「Connection timed out during banner exchange」エラーが出る場合、ssh並列度が高すぎるので落とす
    1. # 残っているansible sshプロセスを終了
    2. killall ansible
    3.  
    4. vi ansible.cfg
    5. ----
    6. [defaults]
    7. forks = 5
    8. ----

複数サーバのrpmバージョンの調査と更新

  • 対象ホストを列挙した設定ファイルを作る
    1. vim stg.hosts
    2. ----
    3. [stg:children]
    4. stg_web
    5.  
    6. [stg_web]
    7. stg-web-[01:02].example.com
    8.  
    9. [stg_web:vars]
    10. ansible_ssh_user=ec2-user
    11. ansible_ssh_private_key_file=~/.ssh/stg-web.pem
    12. ----
  • 例: bashのバージョン調査と更新
    • 調査
      1. ansible -i stg.hosts -m shell -a 'rpm -qv bash' stg
      2.  
      3. stg-web-01.example.com | success | rc=0 >>
      4. bash-4.1.2-9.el6_2.x86_64
      5.  
      6. stg-web-02.example.com | success | rc=0 >>
      7. bash-4.1.2-15.el6_5.2.x86_64
    • 更新
      1. ansible --sudo -i stg.hosts -m shell -a 'yum -y update bash' stg

YAML中でコロン(:)を入れると Syntax Error

YAML中で"foo: bar"の文字列だとSyntax Errorが発生する。"foo:bar"は問題ない。

  • ansible 1.9.4
  • colon.yml
    1. - hosts: 127.0.0.1  vars:    colon: ':'  tasks: #    - debug: msg="foo: bar" # Syntax Error    - debug: msg="foo:bar" # OK    - debug: msg="foo{{ colon }} bar" # OK    - debug: msg="foo"":"" bar" # OK(1.9) / NG(2.0)    - debug: msg="foo{{':'}} bar" # OK(1.9) / OK(2.0)
  • 実行
    1. ansible-playbook colon.yml
    2.  
    3. TASK: [debug msg="foo:bar"] ***************************************************
    4. ok: [127.0.0.1] => {
    5.     "msg": "foo:bar"
    6. }
    7.  
    8. TASK: [debug msg="foo{{ colon }} bar"] ****************************************
    9. ok: [127.0.0.1] => {
    10.     "msg": "foo: bar"
    11. }
    12.  
    13. TASK: [debug msg="foo"":"" bar"] **********************************************
    14. ok: [127.0.0.1] => {
    15.     "msg": "foo\"\":\"\" bar"
    16. }
    17.  
    18. TASK: [debug msg="foo: bar"] **************************************************
    19. ok: [127.0.0.1] => {
    20.     "msg": "foo: bar"
    21. }

debug: デバッグメッセージの出力

  • debug - Print statements during execution Ansible Documentation デバッグメッセージを出力できる
    • 文字列の改行が自動的に「\n」にエスケープされる。
    • 変数はjsonとして出力される。
    • regsiterで取得した結果がJSONの場合、「reg | from_json」とすると綺麗に表示される。
  • コマンド実行結果をdebugモジュールで表示する場合、改行が\nに置換されて、1行表示になるため見難い。「result.stdout_lines」だと改行がそのままなので見やすい。
  • debug.yml
    1. - hosts: 127.0.0.1  tasks:  - name: debug    debug: msg="ansible_os_family {{ ansible_os_family }}, ansible_distribution {{ ansible_distribution }}"
  • 実行
    1. ansible-playbook debug.yml
    2.  
    3. ok: [127.0.0.1] => {
    4.     "msg": "ansible_os_family RedHat, ansible_distribution CentOS"
    5. }
  • var引数に変数名を直接指定もできる
    1. - debug: var=ansible_os_family

tags : タグを付けて実行するタスクを指定する

  • tags.yml
    1. - hosts: 127.0.0.1  tasks:  - name: job1    debug: msg="job1"    tags:    - job1  - name: job2    debug: msg="job2"    tags:    - job2  - name: job2    debug: msg="job3"    tags:    - job3
  • 実行
    1. # job1, job2だけ実行
    2. ansible-playbook -t job1,job2 tags.yml
    3.  
    4. # job2 以外を実行
    5. ansible-playbook --skip-tags job2 tags.yml

特別なタグ

例えばpre_task:でチェック処理をしている場合、-t でタグ指定した場合実行されない。

  • 例: RHEL/CentOS以外は中止
    1.   pre_tasks:  - block:    - name: Validate OS      fail: msg="Unsupported OS{{':'}} {{ ansible_os_family }}"      when: ( ansible_os_family not in [ "RedHat" ] )    tags:    - always    - validate

Inventory ファイル

  • コメント行は、行頭 '#'
  • ホスト名単体、グループ名をコマンドから指定できる
  • ホスト名が連番の場合は [01:50], [a:f] 等指定できる
  • 実行時には web* 等のワイルドカードも使える

ホストを指定して実行

  • Patterns Ansible Documentation
  • すべて
    1. ansible -i hosts.ini -m ping all
  • 複数のグループ指定。':'区切りで複数指定
    1. ansible -i hosts.ini -m ping webservers:dbservers
  • webserversグループのweb03だけ除外
    1. ansible -i hosts.ini -m ping webservers:\!web03

IPアドレスに名前を付ける

  • DNSを付けていなくとも名前でアクセスできるようになる
  1. vim hosts.ini
  2. ----
  3. [web]
  4. web01 ansible_ssh_host=192.168.61.101
  5. ----
  6.  
  7. ansible -i hosts.ini -m ping web01

グループ毎に異なる変数を使う

  • stg_web グループに sshユーザ、秘密鍵を指定する
    1. [stg_web]
    2. stb_web_[01:02]
    3.  
    4. [stg_web:vars]
    5. ansible_ssh_user=ec2-user
    6. ansible_ssh_private_key_file=~/.ssh/ec2-user.pem
  • ホスト、グループ毎にファイルに分割もできる
    1. group_vars/stg_web
    2. host_vars/stb_web_01

ホストのグループ化

  • [prod:children] のように ":children"を付ける
  • hosts
    1. [stg:children]
    2. stg_web
    3. stg_DB
    4.  
    5. [prod:children]
    6. prod_web
    7. prod_db
    8.  
    9. [stg_web]
    10. stg_web_01
    11.  
    12. [stg_DB]
    13. stg_db_01
    14.  
    15. [prod_web]
    16. prod_web_01
    17.  
    18. [prod_db]
    19. prod_db_01
  • stg_web_01 ホスト単体にコマンドを実行
    1. ansible stg_web_01 -i hosts -m shell -a 'uname -a'
  • stg_webグループにコマンドを実行
    1. ansible stg_web -i hosts -m shell -a 'uname -a'
  • prodグループにplaybookを実行
    1. ansible-playbook -l prod -i hosts playbook.yml
  • ワイルドカードで指定
    1. ansible-playbook *_web_01 -i hosts playbook.yml

実行条件を指定する

  • 条件判断に使う変数は fact も使える。
  • ディストリビューション毎にインストール方法を指定する(ansible_os_family, ansible_distribution)
    1.   tasks:
    2.   # OSファミリー毎に指定
    3.   - include: RedHat.yml
    4.     when: ansible_os_family in [ "RedHat" ]
    5.   - include: Debian.yml
    6.     when: ansible_os_family in [ "Debian" ]
    7.   # ディストリビューション毎に指定
    8.   - yum: name={{ item }} state=installed
    9.     with_items:
    10.      - ntp
    11.     when: ansible_distribution in [ 'CentOS', 'Red Hat Enterprise Linux' ]
    12.  
    13.   - apt: name={{ item }} state=installed
    14.     with_items:
    15.      - ntp
    16.     when: ansible_distribution in [ 'Debian', 'Ubuntu' ]
    17.  
    18.   - service: name=ntpd state=started enabled=yes

Vault: 変数のyamlファイルを暗号化

  • 暗号化
    1. ansible-vault encrypt foo.yml bar.yml baz.yml
  • 複合化
    1. ansible-vault decrypt foo.yml bar.yml baz.yml
  • playbookから実行する場合に、パスワードを聞く
    1. ansible-playbook site.yml --ask-vault-pass

特定行のコメントアウト

  • ansible 1.7.1で動作確認
  • /etc/sudoers の "Defaults requiretty" をコメントアウトしたい場合。もし構文エラーがあれば中止してくれる
    1. - name: disable Defaults requiretty  lineinfile: dest=/etc/sudoers regexp='^(Defaults\s+requiretty)$' line='#\1' backrefs=true  validate='visudo -cf %s' backup=true
  • コメントアウトを元に戻す。'#key=value', 'key='の場合でも'key=value'に置き換える
    1.   - lineinfile: dest=test.txt regexp='^#?\s*key=' line='key=value' state=present

fact : 環境の情報を取得

環境の情報を取得してansibleで利用できる。
puppetだとfacterに相当。facterが入っているとその値も取ってくるようだ。

  • それなりに時間がかかるのでplaybook内で無効化する場合
    1. - hosts: localhost  gather_facts: False
  • 127.0.0.1 を指定した例。JSON形式で取得できる
    1. ansible -m setup localhost
    2.  
    3. 127.0.0.1 | success >> {
    4.     "ansible_facts": {
    5.         "ansible_all_ipv4_addresses": [
    6.             "192.168.61.128"
    7.         ],
    8.         "ansible_all_ipv6_addresses": [],
    9.         "ansible_architecture": "x86_64",
    10.         "ansible_bios_date": "07/02/2012",
    11.         "ansible_bios_version": "6.00",
    12.         "ansible_cmdline": {
    13. ...
  • playbook内で使えるマジック変数の表示
    1. --- - hosts: all  gather_facts: true  become: no  vars:  - pause_seconds: 0  tasks:  - name: show group_names    debug:      var: group_names  - name: show groups    debug:      var: groups  - name: show hostvars[inventory_hostname]    debug:      var: hostvars[inventory_hostname]  - pause:      seconds: "{{ pause_seconds }}"    when: pause_seconds|int > 0

添付ファイル: fileplaybook.jinja2-example1.zip 21件 [詳細] fileaws.ec2.list-instance.zip 140件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-06-13 (水) 18:21:33 (8d)