Include playbook but limit to specific host

Hello,

I have a common.yml playbook which manage all the system default ( resolv.conf, snmp, ntp…) which must be call for all servers so the code look like this :

---
- name: Commons conf
  hosts: all
  gather_facts: true
  roles:
    - apt
    - resolv
    - snmpd
    - ntpd

this playbook works well.
When I create a VM in my workflow, I create a new playbook which needs to include the common.yml playbook and include custom config with new roles for this server. This will define the full state of the VM ensuring we don’t rewrite code or forget to include sorole each time.

This is my issue here, I don’t find a clean way to create a playbook which doesn’t need to use --limit option to only apply to the selected target.

For the following code I need to use --limit to avoid commons-playbook to be executed on all target.

---
- name: Run commons-playbook first
  import_playbook: commons-playbook.yml

- name: Install and Configure
  hosts: nagios
  gather_facts: true
  roles:
    - nagios
    - php

So I tried to define a variable with the hostname and to provide this variable to host field, but I didn’t manage to make it work :

---
- name: Playbook for specific servers
  hosts: localhost
  vars:
    target_hosts:
      - server1
      - server2

- name: Nodejs install and configuration
  hosts: "{{ target_host }}"
  gather_facts: true
  import_playbook: common.yml

- name: Nodejs install and configuration
  hosts: "{{ target_host }}"
  gather_facts: true
  roles:
      - php
      - webserver

Any help appreciated !

so several issues with yours solution:

  • vars you define in a play, won’t be visible to other plays, it is not the same scope, either set them as ‘extra vars’ and pass them in when invoking the playbook or set them per play that needs them .
  • import_playbook is not valid inside a play, having it with hosts: or gather_facts: won’t work as it needs to be at the same level of other plays, not inside one.

working example with embedded vars:

- name: Playbook for specific servers
  hosts: localhost
  
- name: Nodejs install and configuration
  import_playbook: common.yml
  vars:
    target_hosts:
      - server1
      - server2

- name: Nodejs install and configuration
  hosts: "{{ target_hosts }}"
  gather_facts: true
  vars: # this one makes very little sense, might as well just use hosts: server1,server2
    target_hosts:
      - server1
      - server2
  roles:
      - php
      - webserver

Otherwise just remove the target_hosts var from play and call it with -e target_hosts=server1,server2'. The other thing i recommend is using --limit server1,server2 and leaving the plays to either hosts: all or an appropriate group.

2 Likes

thanks a lot. indeed this is working.
I agree this doesn’t look like very clean because I set multiple times the target.

Maybe I can set target_hosts at a global variable in the playbook ?

I was looking for this because using --limit can be ‘forgotten’ and I fear the risk the playbook to be played against unwanted target.

The only ‘global’ is extra vars and they cannot be set in the playbook. Another option is to have a assert in the first playbook that looks for ansible_limit to be populated

Thanks.
I am wondering if I am using ansible the right way here.
I got puppet background I was able to manage this situation, but with ansible I feel I’m not using it the right way.

So what would be the way to manage a commons roles and specific roles for a given host ?

I see two option so far but for each I have issues :

  • don’t create a common.yml playbook, instead put all the required roles into the playbook for each roles : ok for few host but when you have hundreds of host and playbook, If you need to add a ‘common’ roleit would be very time consumming with very little added value for this task and error prone (forget one playbook).

  • Create a common role instead of a playbook : I don’t like the ideal to have a ‘garbage’ role because in common we have lot of service managed snmpd,rsyslog, ssh…

I read the documentation and seems that the commons role is something recommended by ansible.

Looking for advice thanks !

I’ve worked in several orgs where the question was “puppet or ansible”. To me this is like asking “should I use a hammer or a screwdriver to build a house?” They’re complementary tools and there isn’t a reason you can’t use both where it’s appropriate.

In my experience, ansible is good at the following:

  • Performing ad-hoc or irregular activities such as patching or software installation or SSL certificate operations (for example)
  • Performing actions in a specific order (it is possible to do this with Puppet but it is easier in ansible)

Ansible lacks in the following:

  • Enforcing standardized system and application configurations against a vast array of different hosts (the puppet agent is good at this, as you know)
    • Hiera can be a powerful tool to cascade standards, there isn’t anything like it for ansible.
  • Dependency management is easier with puppet than ansible

Ansible’s strengths tend to be puppet’s weaknesses, and vice versa.

Is your management making you go down the path of having ansible replace puppet, or are you just interested in exploring ansible?

Thank you very interesting feedback.

For the context I moved to a new company, ansible was useed only for some random task. My objective now is to fully managed the VM throught ansible and for this I split my view in two parts : commons services (i.e snmpd) VS specific services (i.e apache2)

To clarify, I’m not looking to oppose ansible to puppet. Just this is my experience so I’m always referencing to what I’ve done with puppet (this is maybe a bad idea!) So far I found log of similiarity which allow me to have a fast learning curve on Ansible.

I know with tools like this we can do the same things in several ways, but i’m lloking for the “proper” way from ansible point of view.
I feel on this matter I’m missing something or the tool on this specific aspect might not be able to answer. This is way I’m trying to have some feedback.

Hope this clarify!

There are countless ways this can be done I not sure there is a “proper” way? :person_shrugging:

The way I’m doing this is one role per service, eg an Apache or SSH role and I have one main all.yml playbook that contains all the roles like this (I could have ssh set to true for all hosts here but I don’t):

- name: All servers all roles
  become: true 
  gather_facts: true
  hosts:
    - all
  roles:
    - role: ssh
      when:
        - ssh is defined
        - ssh | bool
    - role: apache
      when:
        - apache is defined
        - apache | bool

For each role there is a variable that matches the role name that is false by default, for example for the Apache role apache is false and then the Apache role tasks start like this:

- name: Apache role skipped
  ansible.builtin.debug:
    msg: "The tasks in the Apache role are not being run since the apache variable is not true."
  when: not apache | bool
  tags:
    - apache

- name: Install and configure Apache
  block:

    # ...

  when: apache | bool
  tags:
    - apache

Having a tag per role that matches the role name means I can run Ansible for one role against one server like this:

ansible-playbook all.yml -t apache -l server.example.org 

For each host I have a host_vars/server.example.org directory and then one config file per role, for example to install Apache on server.example.org I create host_vars/server.example.org/apache.yml containing:

apache: true

I did start by having all the host variables in a YAML hosts.yml inventory file and then that got too big I created host_vars/server.example.org/vars.yml files per server and then when they became too big I switched to one config file per host per role — having one directory per host for variables and one file per role for the role variables currently works for me…

I do set some variables elsewhere, for example I have have a few set at an inventory level in a hosts.yml like this (I could have ssh set to true for all hosts or for a group of hosts here but I don’t):

all:
  vars:
    ansible_python_interpreter: /usr/bin/python3
  children:
    bookworm_servers:
      vars:
        apt_distro: bookworm
      hosts:
        server.example.org:
          ansible_host: 192.168.1.1

My suggestion would be to pick a way of doing this that makes sense to you, which you understand and which you think others that also need to use the code have a chance of understanding!