ansible中include和delegate_to、loop并用以及存在的问题

小熊 Ansible 未分类评论1,693字数 3046阅读10分9秒阅读模式

Ansible 2.4 -> 2.7, include_role and delegate_to

Short description of the problem: In Ansible 2.4 it was possible to write something like that:

- include_role:
    name: foo
  delegate_to: foo_host

And it worked as expected: role foo run on host foo_host. Ansible 2.5 has changed that, and include_role ignores delegate_to. (Contrary, import_role, does respect it). I found few clever tricks to live with this, but 

Alexey Miasoedov

 pointed out that there is a much neater way, by using an apply parameter for include_role, which supports delegate_to as well as tags and become. I have trouble to find words to express gratitude for that, as it saves me tons of efforts to refactor thousands lines of code.

The final solution

- include_role:
    name: foo
    apply:
      delegate_to: foo_host

(note that apply is offset, as it’s an include_role's parameter).

Compatibility

Unfortunately, there is no way to write a code which works with 2.4 and 2.7 at the same time. apply does not understood in 2.4, so transition should be done in atomic manner.

delegate_to, include_role with loops

There is a horrible regression in Ansible 2.5.

In Ansible version 2.4, if you have combination of delegate_to and include_role a given role was delegated to specified host. In Ansible 2.5 delegated role will be executed on the original host, causing a big mess to debug. Actually, this problem affects on only include_role, but all other dynamic includes (f.e. include_tasks). The problem was escalated by the fact that only ‘include’ allowed to be used together with loop statements (with_itemswith_nested, etc).

After some thoughts, I found a way to do delegation and includes at the same time together with loops. The solution in the nutshell: use iteration over included tasklist, use delegation within tasklist for imports.

Example

I’ll start with bad example. It had worked in Ansible 2.4, it is silently broken in Ansible 2.5.

Bad code

role2/tasks/main.yaml:

---
- include_role: name=role1
  delegate_to: localhost
  with_items:
   - one
   - two

role1/tasks/main.yaml

- shell: hostname
  register: h
- debug: msg="host: {{h}}, var={{item}}"

play.yaml

- hosts: somehost
  gather_facts: no
  roles:
   - role2

Line to execute:

ansible-playbook -i somehost, play.yaml

You need to have ‘somehost’ to be accessible from your machine by ssh. Replace it with any known hostnames (except for localhost). You need to replace it in ‘line to execute’ and in play.yaml in the ‘hosts’ line.

You will find that regardless of using delegation to ‘localhost’, this playbook will output hostname of remote machine. What a bummer.

Good code

This code works in both ansible 2.5 and ansible 2.4. It will output your local machine hostname, as expected.

role2/tasks/main.yaml (changed):

---
- include_tasks: loop.yaml
  with_items:
   - one
   - two

role2/tasks/loop.yaml: (it’s a new file!)

---
- import_role: name=role1
  delegate_to: localhost

role1/tasks/main.yaml (the same file as before)

- shell: hostname
  register: h
- debug: msg="host: {{h}}, var={{item}}"

play.yaml (the same file as before)

- hosts: somehost
  gather_facts: no
  roles:
   - role2

After executing the same line:

ansible-playbook -i somehost, play.yaml

You will find that hostname output have been changed to a localhost hostname (expected behavior).

The solution, explained

Input:

  1. We can not use loop with import, we need to use include.
  2. We can not use delegate to with include, we need to use import.

Solution:

  1. We use loop + include_tasks to run a separate tasklist.

  2. We use import_role + delegate_to to delegate a role to another host.

quato from https://medium.com/@george.shuklin

weinxin
公众号
扫码订阅最新深度技术文,回复【资源】获取技术大礼包
小熊