Search code examples
loopsansibleinfrastructure

How do I use Ansible to add an IP to a Linux hosts file on 18 VMs that iterates from 10.x.x.66..10.x.x.83


So, I have a playbook using a hosts file template to update or revert hosts files on 18 specific Linux VMs. The entry which goes at the end of the file looks like:

10.x.x.66 fooconnect

This above example would be on the 1st of 18 VMs, the 18th VM would look like:

10.x.x.83 fooconnect

Normally, that hostname resolves to a VIP. However, we found during some load testing that it may be beneficial to point each front-end VM to a back-end VM directly. So, my goal is to have a playbook that can update what the hostname resolves to with the above mentioned range, or revert it back to the VIP (reverting back is done using a template only--this part works fine).

What I am unsure about is how to implement this in Ansible. Is there a way to loop through the IPs using jinja2 template "for loops?" Or maybe using lineinfile with some loop magic?

Here is my Ansible role example. For the moment I am using a dirty shell command to create my IP list...open to suggestions for a better way to implement this.

- name: Add a line to a hosts file using a template
  template:
    src: "{{ srcfile }}"
    dest: "{{ destfile }}"
    owner: "{{ own_var }}"
    group: "{{ grp_var }}"
    mode: "{{ mode_var }}"
    backup: yes

- name: Get the IPs
  shell: "COUNTER=66;for i in {66..83};do echo 10.x.x.$i;((COUNTER++));done"
  register: pobs_ip

- name: Add a line
  lineinfile:
    path: /etc/hosts
    line: "{{item}}    fooconnect"  #Ideally would want "item" to just be one IP and not 
    insertafter: EOF                #the entire list as it would be like this.
  loop: "{{pobsips}}"

VARs file:

pobsips:
  - "{{pobs_ip.stdout}}"

Solution

  • Instead of using a shell task, we can improvise it and create the range of IP addresses using set_fact with range. Once we have the range of IP addresses in a "list", we can loop lineinfile with that and achieve this.

    Example:

        - name: create a range of IP addresses in a variable my_range
          set_fact:
            my_range: "{{ my_range|default([]) + [ '10.1.1.' ~ item ] }}"
          loop: "{{ range(66, 84)|list }}"
        - name: Add a line to /etc/hosts
          lineinfile:
            path: /etc/hosts
            line: "{{ item }} fooconnect"
            insertafter: EOF
          loop: "{{ my_range }}"
    

    Updated answer:

    There is another approach if we want to append only 1 line into the /etc/hosts file of each host with incrementing IP addresses.

    • For this we can use the ipmath of ipaddr filter to get the next IP address for given IP address.
    • Use ansible_play_hosts to get the list of hosts on which play is running
    • Set an index variable index_var and when condition to update file only when the ansible_hostname or inventory_hostname matches.
    • Run playbook serially and only once on a host per run using serial and run_once flags.

    Let's consider an example inventory file like:

    [group_1]
    host1
    host2
    host3
    host4
    ...
    

    Then in playbook:

    - hosts: group_1
      serial: 1
    
      vars:
        start_ip: 10.1.1.66
    
      tasks:
        - name: Add a line to /etc/hosts
          lineinfile:
            path: "/tmp/hosts"
            line: "{{ start_ip|ipmath(my_idx) }} fooserver"
            insertafter: EOF
          loop: "{{ ansible_play_hosts }}"
          loop_control:
            index_var: my_idx
          run_once: true
          when: item == inventory_hostname