Memo/Ansible

https://dexlab.net:443/pukiwiki/index.php?Memo/Ansible
 

Ansible

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

default(): 変数未定義時のエラーを回避

# var1が必須ではないパラメータの場合、省略できる
item.var1 | default(omit)

# var1が未定義時に空の配列を返す
item.var1 | default([])

package: OS毎のパッケージ管理を一元化


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


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


AWS assume role

  • 参考 ~/.aws/config: Memo/AmazonWebServices/IAM#tfb76382
  • account1がログイン用、account2がswitch先とする
  • boto module の場合、単純にansible module側「profile: account2」を指定するだけでは、以下のエラーで失敗した。
  • boto3 module の場合、成功した
  • 「~/.boto -> ~/.aws/credentials」のようなsymlinkがあると失敗するので、「~/.boto」は消す
  • sts_assume_role を使う場合
        - 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実行時のオプションを省略する
    • File not found: "playbook.jinja2-example1.zip" at page "Memo/Ansible"[添付]

with_subelements: サブ要素でループ


Terraformとの連携

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


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

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

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

  apt:
    name: "{{ item.name }}"
    state: "{{ item.state }}"
    update_cache: yes
    cache_valid_time: 3600
  with_items: "{{ apt_packages }}"

assert: テストに使える


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

  • playbook: カンマ前後に空白が入力されても良いように、trimを使う
    - 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
    ansible-playbook playbook.yml
    Please enter csv: aaa, bbb 
    
    ok: [localhost] => (item=aaa) => {
        "changed": false, 
        "item": "aaa", 
        "msg": "aaa"
    }
    
    ok: [localhost] => (item= bbb ) => {
        "changed": false, 
        "item": " bbb ", 
        "msg": "bbb"
    }

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

  • ansible 2.4.2.0でincludeを使った場合の警告。静的と動的で明示的に分けて記述するように。
[DEPRECATION WARNING]: The use of 'include' for tasks has been deprecated. Use 'import_tasks' for static inclusions or 'include_tasks' for 
dynamic inclusions. This feature will be removed in a future release. Deprecation warnings can be disabled by setting deprecation_warnings=False 
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
    - hosts: all
      gather_facts: false
      become: no
      vars:
      - pause_seconds: 0
      tasks:
      - ping:
      - pause:
          seconds: "{{ pause_seconds }}"
        when: pause_seconds|int > 0
  • 実行結果: 60秒待機する
    time ansible-playbook -i test/hosts.ini -l localhost playbook.ping.yml -e 'pause_seconds=60'
    ...
    TASK [ping] ***************************************************************************************************************************************
    ok: [localhost]
    
    TASK [pause] **************************************************************************************************************************************
    Pausing for 60 seconds
    (ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
    ok: [localhost]
    PLAY RECAP ****************************************************************************************************************************************
    localhost                  : ok=2    changed=0    unreachable=0    failed=0
    
    real    1m7.318s
    user    0m4.149s
    sys     0m0.548s

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

- 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変数にマッチした行を削除
      - 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を扱う。コンテナ内の構築は別。
    • リモートホスト上の準備
      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
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
        [ssh_connection]
        # '%C' by a hash of the concatenation: %l%h%p%r.
        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:
    sudo tail -f /var/log/secure
    Jul  5 16:27:31 server sshd[1465]: error: accept from auth socket: Too many open files
    
    ulimit -n
    1024
    
    cat /etc/security/limits.d/90-nproc.conf
    *          soft    nproc     1024
    root       soft    nproc     unlimited
  • CentOS 7:
    ulimit -n
    1024
    
    cat /etc/security/limits.d/20-nproc.conf 
    *          soft    nproc     4096
    root       soft    nproc     unlimited
    # もしdaemonのserviceファイルを変えた場合は、以下も必要
    sudo systemctl daemon-reload
    sudo systemctl <daemon> restart
  • ansibleで変更する場合
    - 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実行後、再起動
    sudo reboot
  • 再起動後
    ulimit -n
    65536
    
    cat /etc/security/limits.conf
    root    soft    nofile  65536
    root    hard    nofile  65536
    *       soft    nofile  65536
    *       hard    nofile  65536

factの再取得

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

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

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

  • 配列に辞書を複数追加する。array1を初期化しておかないとエラーになる。
    • playbook
      - 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
      • 実行結果
        ansible-playbook playbook.yml
        ...
        TASK [debug] *******************************************************************
        ok: [localhost] => {
            "array1": [
                {
                    "key1": "value1", 
                    "key2": "value2"
                }, 
                {
                    "key3": "value3"
                }
            ]
        }
  • set_fact内で定義した変数を、同じset_fact内で参照しようとすると「'var1' is undefined」エラーになる。その場合はset_factを分ける
    - hosts:
      - localhost
      become: False
      gather_facts: False
      connection: local
      tasks:
      - set_fact:
          var1="hoge"
          var2="{{ var1 }}" # NG
      - set_fact:
          var2="{{ var1 }}" # OK

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


to_nice_yaml でエラー


group_by: 動的にgroupを変更する

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

  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」オプションを付けたときにもパスワード等はコンソールに表示したくない場合等。

  • How do I keep secret data in my playbook?
  • 複雑な変数をwith_itemで処理するとき、全て表示したくない場合
  • set_factで表示したい変数だけwith_itemsで作る時にも使える
    • ログの表示: コマンドオプションに 「-e 'show_log=true'」
    • playbook.yml
  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のデバッグログを出力する
    ANSIBLE_DEBUG=1 ansible -i ...

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

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

javaのpropertiesを読む

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

iniファイルを読む

  • v2.0以上で対応
  • aws configのdefaultセクションを読む。ただし、「section」に空白が含まれる場合、エラーになる。
    - debug: msg={{ lookup('ini', 'aws_access_key_id section=default file=~/.aws/config') }}
  • パラメータを渡したい場合、formatを使う
    - 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
      - hosts: 127.0.0.1
        gather_facts: False
        vars:
          ec2_user_data: "{{ lookup('file', 'roles/cloudinit/files/centos6.yml') }}"
        tasks:
        - name: ec2 instance
          local_action:
            module: ec2
            keypair: example-key
            group: example-sg
            instance_type: t2.micro
            image: ami-xxxxxxxx
            region: ap-northeast-1
            vpc_subnet_id: subnet-xxxxxxxx
            assign_public_ip: yes
            count: 1
            volumes:
              - device_name: /dev/sda1
                volume_size: 10
      #        - device_name: /dev/sdb # t2インスタンスはephemeral diskは無い
      #          ephemeral: ephemeral0
            instance_tags: '{"Name":"test.example.com","key2":"val2"}'
            user_data: "{{ ec2_user_data }}"
            wait: yes
            wait_timeout: 600

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

  • 複数のリモートホストから、ローカルにコピーできる。ツリー構造の維持もできる。ツリー構造が不要な場合は「flat=yes」
    ansible -i hosts.ini -m fetch -a 'src=/etc/hosts dest=/tmp/backup/' web*
    
    tree -A /tmp/backup/
    /tmp/backup/
    ├── web01
    │   └── etc
    │       └── hosts
    └── web02
        └── etc
            └── 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コマンドでバージョンを取得して、異なるならばアンインストール後にインストールする
    - 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は動かない
      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)にする


空ファイルの作成

  • /tmp/dummyが存在すれば、バックアップ後、空にする。ファイルが無ければ何もしない。
        - 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を読んでいないようで、変数の未定義エラーが出る。
    cat group_vars/localhost.yml
    ...
    
    cat hosts.ini
    [localhost]
    127.0.0.1
    
    cat playbook.yml
    - hosts: localhost
      gather_facts: False
      connection: local
    ...
    
    ansible-playbook -i hosts.ini playbook.yml
    
    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: ...
    
    PLAY RECAP *********************************************************************
    127.0.0.1                  : ok=2    changed=2    unreachable=0    failed=0
    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
    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] のようになってしまう。
    • サンプル
          - 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を列挙
    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
    
    localhost | SUCCESS | rc=0 >>
         79 from 192.168.10.1
         22 from 192.168.10.128
         22 from 127.0.0.1

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

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

  • playbook.yml
      tasks:
        - name: yum list installed
          yum: list=installed
          register: result
          ignore_errors: yes
          changed_when: False
    
        - debug: var=result
  • 実行結果:
    ok: [127.0.0.1] => {
        "result": {
            "changed": false, 
            "results": [
                {
                    "arch": "x86_64", 
                    "epoch": "0", 
                    "name": "MAKEDEV", 
                    "nevra": "0:MAKEDEV-3.24-6.el6.x86_64", 
                    "release": "6.el6", 
                    "repo": "installed", 
                    "version": "3.24", 
                    "yumstate": "installed"
                }, 
                ...

Ansible 2.1


SSHで接続できない場合

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


ssh公開鍵の登録

  • www01ホストのuser01に~/.ssh/id_rsa.pubを登録する
    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 では以下のように取得できる。
    ansible -m setup localhost
    ...
            "ansible_dns": {
                "nameservers": [
                    "192.168.1.1"
                ], 
                "search": [
                    "localdomain"
                ]
            },

Ansible2.0


with_nested: 複数階層ループ

  • playbook
        - name: give users access to multiple databases
          debug: var="name={{ item[0] }} priv={{ item[1] }}"
          with_nested:
            - [ 'alice', 'bob' ]
            - [ 'clientdb', 'employeedb' ]
  • 実行結果
    TASK: [give users access to multiple databases] ******************************* 
    ok: [127.0.0.1] => (item=['alice', 'clientdb']) => {
        "item": [
            "alice",
            "clientdb"
        ],
        "var": {
            "name=alice priv=clientdb": "name=alice priv=clientdb"
        }
    }
    ok: [127.0.0.1] => (item=['alice', 'employeedb']) => {
        "item": [
            "alice",
            "employeedb"
        ],
        "var": {
            "name=alice priv=employeedb": "name=alice priv=employeedb"
        }
    }
    ok: [127.0.0.1] => (item=['bob', 'clientdb']) => {
        "item": [
            "bob",
            "clientdb"
        ],
        "var": {
            "name=bob priv=clientdb": "name=bob priv=clientdb"
        }
    }
    ok: [127.0.0.1] => (item=['bob', 'employeedb']) => {
        "item": [
            "bob",
            "employeedb"
        ],
        "var": {
            "name=bob priv=employeedb": "name=bob priv=employeedb"
        }
    }

Prompts: ユーザ入力を待つ

  • playbook
    - hosts: all
      gather_facts: False
      connection: local
      vars_prompt:
        - name: "user_name"
          prompt: "Please enter name"
          private: no
      tasks:
        - debug: var=user_name
  • 実行
    ansible-playbook -i hosts.ini test.yml
    Please enter name: hoge
    
    TASK: [debug var=user_name] *************************************************** 
    ok: [localhost] => {
        "var": {
            "user_name": "hoge"
        }
    }
  • -e "var1=value1 var2=value2" の様に実行時に値を渡せる。この場合はプロンプトは表示されない。
    ansible-playbook -i hosts.ini test.yml -e "user_name=hogehoge"
    
    TASK: [debug var=user_name] *************************************************** 
    ok: [localhost] => {
        "var": {
            "user_name": "hogehoge"
        }
    }
  • 例:削除確認。"no"なら中止
      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 を置く
    [defaults]
    forks = 5
    timeout = 30
    # CentOS6でparamikoを使う場合、Falseにする事で早くなる
    record_host_keys = False
    
    [ssh_connection]
    control_path = %(directory)s/%%h-%%r
    ansible_connection = ssh
    ssh_args = ServerAliveInterval=15 -o ServerAliveCountMax=4
    # Trueにすると高速化するが、/etc/sudoersでrequirettyをコメントアウトする必要がある
    pipelining = False

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

  • v1.9.4で実現する。v2.0以降では「splitext 」が使える。
    - debug: msg="{{ '/etc/httpd/conf/httpd.conf' | basename | regex_replace('\.conf$', '') }}" # httpd
  • v2.0以降
    - 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
    phantomjs_install_dir: /usr/local/bin
    phantomjs_url:
      - url: https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2
        sha256sum: "a1d9628118e270f26c4ddd1d7f3502a93b48ede334b8585d11c1c3ae7bc7163a" # sha256sum phantomjs-1.9.8-linux-x86_64.tar.bz2
  • roles/phantomjs/tasks/main.yml
    - name: Download PhantomJS
      get_url: url={{ phantomjs_url[0].url }} sha256sum={{ phantomjs_url[0].sha256sum }} dest=/tmp/phantomjs.tar.bz2 force=no
      register: new_archive
      tags:
        - phantomjs
    
    - name: Unarchive PhantomJS
      unarchive: src=/tmp/phantomjs.tar.bz2 dest=/tmp copy=no creates=yes
      when: new_archive|changed
      tags:
        - phantomjs
    
    - name: Install PhantomJS
      shell: cp -f /tmp/{{ phantomjs_url[0].url | basename | regex_replace('\.tar\.bz2|\.tar\.gz$', '') }}/bin/phantomjs {{ phantomjs_install_dir }}/
      when: new_archive|changed
      tags:
        - phantomjs

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

  • 例:user01がmysqlのログを見えるように、またwheelグループにも追加
    - 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を無効にする必要がある。
    sudo vim /etc/sudoers
    ----
    Defaults    !requiretty
    ----
  • または ansible -c paramiko を指定する

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

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


長い行を改行する

YAML構文を使って分割する

  • '>' : folded block記法。改行が入らないので1行として実行される
    # "echo foo var"として実行される
    - shell: >
        echo foo
        var
  • '|' : literal block記法。改行が入る
    # "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
        [defaults]
        # 明示的にsshを使う
        transport      = ssh
        
        [ssh_connection]
        # ssh接続の再利用をする。タイムアウトの時間を延ばす
        ssh_args = -F ssh-config -o ControlMaster=auto -o ControlPersist=300s
        # ホスト名が長い時にエラーになるのを防ぐ
        control_path = %(directory)s/%%h-%%r
        # Trueにしたい場合、全クライアントの/etc/sudoersのrequirettyを無効にする必要がある。sudo時にエラーになる。
        pipelining = True
    • OpenSSH 6.7以降
      • ansible.cfg
        [ssh_connection]
        # '%C' by a hash of the concatenation: %l%h%p%r.
        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の例
    validate='sshd -t -f %s'
  • visudoの例
    validate='visudo -cf %s'
  • httpdの例:httpd.confのみ対応。conf.d/以下のファイルを指定するとエラーになる
    validate: 'apachectl -t -f %s'
  • phpの例
    alidate='php -l %s'

error while evaluating conditional

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

yum groupinstall相当

  • yum groupinstall相当をしたい
    sudo yum groupinstall "Japanese Support"
  • yum group-idを調べる。「group name (group-id)」のように表示される
    LANG=C yum grouplist -v
    ...
       Japanese Support (japanese-support) [ja]
    ...
  • with_itemsで指定する場合
    - 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向け) を使う
    git clone https://github.com/ansible/ansible-modules-extras.git
    export ANSIBLE_LIBRARY=ansible-modules-extras
    ansible-playbook -i hosts.ini playbook.yml

sshで繋がずlocalで実行

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

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

  • yum/rpmを使わずにインストールしている場合(yum repo等)、statでファイルの有無等で判断した方が良さそう
    - 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
  • rpmコマンドを使うと「[WARNING]: Consider using yum, dnf or zypper module rather than running rpm」が出るので、「warn: no」で無視している。
    • 「yum: list=installed」でインストール済みパッケージ一覧が取得できるが、全て列挙されるため遅い&使いにくい。
      - 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
    毎回実行するが、変更扱いにしない。インストールされているかのチェック等に便利
    - 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
      - name: restart iptables
        service: name=iptables state=restarted
        when: ansible_os_family in [ "RedHat" ] and iptables_state == "started"

roles

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

依存関係


自作モジュール

Developing Modules — Ansible Documentation


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

  • 例:awscliのjson出力を変数に取り込む。
      - 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 を追加する
    - hosts: all
      gather_facts: True
      tasks:
      - shell: test.sh
        environment:
          PATH: "/opt/bin:{{ ansible_env.PATH }}"
        register: result
      - debug: var=result.stdout
  • タイムゾーンを変更する
        - shell: date
          environment:
            TZ: "{{ lookup('env', 'TZ') }}"
    • タイムゾーンを変更して実行
      TZ=JST ansible-playbook -l 127.0.0.1 -i hosts.ini playbook.yml
      TZ=UTC ansible-playbook -l 127.0.0.1 -i hosts.ini playbook.yml

コマンドの実行

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

local_action: localhostで実行する

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

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を変更しようとしたところ以下のエラーが出た
    msg: Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!
  • 対象ホストにログインしてSElinuxを確認すると Permissive だった
    getenforce
    Permissive
  • CentOS 6/7: libselinux-pythonをインストール後、正常に動作した
    sudo yum install libselinux-python -y
  • Ubuntu
    sudo apt install python-selinux

split: 文字列の分割

  • ansible-1.7-1.el6.noarch
  • test.yml
    - hosts: 127.0.0.1
      gather_facts: False
      vars:
        keys: ""
      tasks:
        - name: split
          debug: var="{{ item }}"
          with_items: "{{ keys.split(',') }}"
  • 実行
    ansible-playbook -i hosts.ini test.yaml --extra-vars 'pub_keys=aaa,bbb,ccc'
    
    TASK: [split] *****************************************************************
    ok: [127.0.0.1] => (item=aaa) => {
        "aaa": "{{ aaa }}",
        "item": "aaa"
    }
    ok: [127.0.0.1] => (item=bbb) => {
        "bbb": "{{ bbb }}",
        "item": "bbb"
    }
    ok: [127.0.0.1] => (item=ccc) => {
        "ccc": "{{ ccc }}",
        "item": "ccc"
    }

ansible galaxy

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

Ansible Galaxyへ登録

  • 例: lirc roleを作成。
    ansible-galaxy init lirc

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

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

  • サンプル
    - 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のテスト時に使える?
        check_mode: no
    • ansible 2.1: ansible_check_mode を使う。--check時のエラーを無視するようにはできる
      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の操作


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

  • ローカルファイルをリモートにコピー
    ansible -i hosts.ini -m copy -a "copy: src=/src/myfiles/foo.conf dest=/etc/foo.conf" --user <username> --ask-pass --sudo <hostname>
  • ローカルディレクトリをリモートディレクトリにコピー
    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
      [web]
      web01.example.com
      web02.example.com
    • test.yml
      - hosts: web
        gather_facts: True
        vars:
      
        pre_tasks:
          - debug: msg="pre_tasks01"
      
        roles:
          - role01
      
        tasks:
          - name: task01
            file: path=/tmp/task01 state=touch
            notify: handler01
      
        handlers:
          - name: handler01
            debug: msg="handler01"
      
        post_tasks:
          - debug: msg="post_tasks01"
    • 実行
      ansible-playbook -i hosts.ini test.yml
      ...
      TASK: [debug msg="pre_tasks01"] ***********************************************
      ok: [web01.example.com] => {
          "msg": "pre_tasks01"
      }
      ok: [web02.example.com] => {
          "msg": "pre_tasks01"
      }
      
      TASK: [role01 | debug msg="role01"] *******************************************
      ok: [web02.example.com] => {
          "msg": "role01"
      }
      ok: [web01.example.com] => {
          "msg": "role01"
      }
      
      TASK: [task01] ****************************************************************
      changed: [web02.example.com]
      changed: [web01.example.com]
      
      NOTIFIED: [handler01] *********************************************************
      ok: [web01.example.com] => {
          "msg": "handler01"
      }
      ok: [web02.example.com] => {
          "msg": "handler01"
      }
      
      TASK: [debug msg="post_tasks01"] **********************************************
      ok: [web01.example.com] => {
          "msg": "post_tasks01"
      }
      ok: [web02.example.com] => {
          "msg": "post_tasks01"
      }
      
      PLAY RECAP ********************************************************************
      web01.example.com          : ok=6    changed=1    unreachable=0    failed=0
      web02.example.com          : ok=6    changed=1    unreachable=0    failed=0

Validation


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

  • ファイルの存在チェック
      - stat: path=/tmp/test.log
        register: result
        changed_when: False
        
      - debug: var=result
     
      - fail: msg="file not found"
        when: result.stat.exists == False
  • ディレクトリの存在チェック
      - 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==""」が一致する
        - name: validate variable
          fail: msg="Please enter correct var1"
          when: (var1 is not defined)
            or (var1 == None)
            or (var1 | trim == "")

if

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

Module モジュール

file

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

Conditionals 条件分岐

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

Error Handling エラーハンドリング

  • デフォルト(v1.7.1)では、同じコマンドが全てのホストで失敗した場合に停止するようだ
  • failed_when: コマンドが失敗した時に何かする
    • Controlling What Defines Failure
    • 終了コード > 2の時に失敗とする
      - hosts: 127.0.0.1
        gather_facts: False
        tasks:
        - name: test
          local_action: shell exit 2
          register: result
          failed_when: result.rc > 2
        - debug: var=result
  • ignore_errors: yes / エラーが起きても、スキップする
      - 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 を作る
    - 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やhost_varsはグループ名やホスト名のディレクトリを作って、それ以下に複数のymlを入れると全て読み込む。長いymlを分割したい場合に便利
    • group_vars/web/var1.yml
    • host_vars/web01/var1.yml
  • 定数と変数の結合
      stat: path="{{ '/etc/sysconfig/network-scripts/ifcfg-' + item }}"
      with_items: "{{ ansible_interfaces }}"
  • 結合した後にフィルタ
    "{{ ('/etc/sysconfig/network-scripts/ifcfg-' + item) | dirname }}"
  • フィルターが使える。例: test.yml
    - 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 }}
    • 実行結果
      ansible-playbook test.yml
      
      TASK: [join] ****************************************************************** 
      ok: [127.0.0.1] => {
          "aaa bbb ccc": "{{ aaa bbb ccc }}"
      }
      
      TASK: [basename] ************************************************************** 
      ok: [127.0.0.1] => {
          "test.txt": "{{ test.txt }}"
      }
      
      TASK: [dirname] *************************************************************** 
      ok: [127.0.0.1] => {
          "/tmp/parent1/child1/child2": "{{ /tmp/parent1/child1/child2 }}"
      }
      
      TASK: [expanduser] ************************************************************ 
      ok: [127.0.0.1] => {
          "/home/CURRENT_USER/test": "{{ /home/CURRENT_USER/test }}"
      }
  • var1: 0010 と定義すると先頭の0が省略され "10"になる。文字列として解釈させるには var1: "0010" とする必要がある
  • roleにデフォルト変数を設定する時は、varsではなく、defaultsを使った方が良い。varsだと、group_varsより優先度が高いため、varsの値が優先される。
    • roles/x/defaults/main.yml
      x_val1: default1
  • 一部の変数だけを別ファイルにできる
    vars_files:
      - group_vars/external_vars.yml

長い変数の分割

  • Memo/yaml参照。アンカー/エイリアスを使う
    • 1 yamlファイル内のみ。複数のyamlファイルに分割すると使えなかった。(ansible 2.5.3)
  • 複数のyamlファイルに分かれている場合、jinja2テンプレートの機能が使える
    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が数値だと参照時にエラーになる。ダブルクオートで囲んで文字列と扱う。
    - 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()のような使い方

TASK [debug] **********************************
ok: [localhost] => {
    "changed": false, 
    "hash1": {
        "key1": [
            "aaa", 
            "bbb", 
            "ccc"
        ]
    }
}

TASK [debug] ***********************************

ok: [localhost] => {
    "changed": false, 
    "msg": [
        "aaa", 
        "bbb", 
        "ccc"
    ]
}

変数のマージ

  • dict型は以下のようにマージできる。yamlの機能
    common: &_common
      user: readonly
      db: dummy
    
    development:
      <<: *_common
      db: dev_db
    
    production:
      <<: *_common
      db: prod_db
  • array型はマージできない
    common: &_common
    - aaa
    - bbb
    
    development: *_common # 成功
    development: *_common &_development # エラー。&_developmentアンカーが使えなくて困る
    &_development: *_common # null
    
    development: &_allow_login_full_admin
    - <<: *_common # エラー
    <<: *_common # エラー
      <<: *_common # エラー
    
    development: &_allow_login_full_admin
    - *_common # 成功だが [ [ aaa, bbb ] ] になる
    
    development: &_allow_login_full_admin [ *_common ] # 上と同じ
  • ? はdict型のvalueなし扱いなのでマージできる
    common: &_common
      ? aaa # aaa: null と同じ
      ? bbb # bbb: null と同じ
    
    development: &_development
      <<: *_common
      ? ccc

hash変数のマージ

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

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

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

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

  • rootユーザだけttyなしでもsudo許可
    sudo visudo
    ----
    Defaults    requiretty
    Defaults:root    !requiretty
    ----

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 を用意
      vim ansible.cfg
      ----
      [defaults]
      
      [ssh_connection]
      ansible_connection = ssh
      control_path = %(directory)s/%%h-%%r
      ssh_args = -F ssh_config
      ----
    • ssh_config
      Host *
        StrictHostKeyChecking=no
        UserKnownHostsFile=/dev/null
        LogLevel ERROR
        ForwardAgent yes
      
      Host gateway.host
        HostName     192.168.1.10
        User         gw_user
        IdentityFile ~/.ssh/id_rsa
        ProxyCommand none
      
      Host other.host
        HostName     192.168.1.11
        User         other_user
        IdentityFile /.ssh/id_rsa
        ProxyCommand ssh -F ssh_config -W %h:%p gateway.host
    • sshコマンドで接続確認
      ssh -F ssh_config other.host
    • ansible pingで接続確認
      ansible hosts.ini -m ping other.host
    • ssh-agentで秘密鍵を複数登録できる。その場合はssh_config「IdentityFile?」の指定は不要になる。
      ssh-agent bash
      ssh-add ~/.ssh/id_rsa
  • 「Connection timed out during banner exchange」エラーが出る場合、ssh並列度が高すぎるので落とす
    # 残っているansible sshプロセスを終了
    killall ansible
    
    vi ansible.cfg
    ----
    [defaults]
    forks = 5
    ----

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

  • 対象ホストを列挙した設定ファイルを作る
    vim stg.hosts
    ----
    [stg:children]
    stg_web
    
    [stg_web]
    stg-web-[01:02].example.com
    
    [stg_web:vars]
    ansible_ssh_user=ec2-user
    ansible_ssh_private_key_file=~/.ssh/stg-web.pem
    ----
  • 例: bashのバージョン調査と更新
    • 調査
      ansible -i stg.hosts -m shell -a 'rpm -qv bash' stg
      
      stg-web-01.example.com | success | rc=0 >>
      bash-4.1.2-9.el6_2.x86_64
      
      stg-web-02.example.com | success | rc=0 >>
      bash-4.1.2-15.el6_5.2.x86_64
    • 更新
      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
    - 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)
  • 実行
    ansible-playbook colon.yml
    
    TASK: [debug msg="foo:bar"] ***************************************************
    ok: [127.0.0.1] => {
        "msg": "foo:bar"
    }
    
    TASK: [debug msg="foo{{ colon }} bar"] ****************************************
    ok: [127.0.0.1] => {
        "msg": "foo: bar"
    }
    
    TASK: [debug msg="foo"":"" bar"] **********************************************
    ok: [127.0.0.1] => {
        "msg": "foo\"\":\"\" bar"
    }
    
    TASK: [debug msg="foo: bar"] **************************************************
    ok: [127.0.0.1] => {
        "msg": "foo: bar"
    }

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

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

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

  • tags.yml
    - 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
  • 実行
    # job1, job2だけ実行
    ansible-playbook -t job1,job2 tags.yml
    
    # job2 以外を実行
    ansible-playbook --skip-tags job2 tags.yml

特別なタグ

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

  • 例: RHEL/CentOS以外は中止
      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
  • すべて
    ansible -i hosts.ini -m ping all
  • 複数のグループ指定。':'区切りで複数指定
    ansible -i hosts.ini -m ping webservers:dbservers
  • webserversグループのweb03だけ除外
    ansible -i hosts.ini -m ping webservers:\!web03

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

  • DNSを付けていなくとも名前でアクセスできるようになる
vim hosts.ini
----
[web]
web01 ansible_ssh_host=192.168.61.101
----

ansible -i hosts.ini -m ping web01

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

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

ホストのグループ化

  • [prod:children] のように ":children"を付ける
  • hosts
    [stg:children]
    stg_web
    stg_DB
    
    [prod:children]
    prod_web
    prod_db
    
    [stg_web]
    stg_web_01
    
    [stg_DB]
    stg_db_01
    
    [prod_web]
    prod_web_01
    
    [prod_db]
    prod_db_01
  • stg_web_01 ホスト単体にコマンドを実行
    ansible stg_web_01 -i hosts -m shell -a 'uname -a'
  • stg_webグループにコマンドを実行
    ansible stg_web -i hosts -m shell -a 'uname -a'
  • prodグループにplaybookを実行
    ansible-playbook -l prod -i hosts playbook.yml
  • ワイルドカードで指定
    ansible-playbook *_web_01 -i hosts playbook.yml

実行条件を指定する

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

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

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

特定行のコメントアウト

  • ansible 1.7.1で動作確認
  • /etc/sudoers の "Defaults requiretty" をコメントアウトしたい場合。もし構文エラーがあれば中止してくれる
    - 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'に置き換える
      - lineinfile: dest=test.txt regexp='^#?\s*key=' line='key=value' state=present

fact : 環境の情報を取得

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

  • それなりに時間がかかるのでplaybook内で無効化する場合
    - hosts: localhost
      gather_facts: False
  • 127.0.0.1 を指定した例。JSON形式で取得できる
    ansible -m setup localhost
    
    127.0.0.1 | success >> {
        "ansible_facts": {
            "ansible_all_ipv4_addresses": [
                "192.168.61.128"
            ],
            "ansible_all_ipv6_addresses": [],
            "ansible_architecture": "x86_64",
            "ansible_bios_date": "07/02/2012",
            "ansible_bios_version": "6.00",
            "ansible_cmdline": {
    ...

添付ファイル: fileaws.ec2.list-instance.zip 36件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-09-25 (火) 18:52:47 (14h)