Loop over other hosts within ansible playbook using serial - ansible

I have 80+ hosts that run my application, and I'm updating a long-existing ansible playbook to change our load balancer. In our current load balancer setup, hosts can be added/removed from the load balancer in one ansible play by shelling out to the AWS CLI. However, we're switching to a load balancer configured on a handful of our own hosts, and we will take hosts in and out by manipulating text files on those hosts using Ansible. I need an inner loop over different hosts within a playbook while using Serial.
I'm having trouble structuring the playbook such that I can fan out blockinfile commands to hosts in group tag_Type_edge while deploying to the 80 tag_Type_app hosts with serial: 25%.
Here's what I want to be able to do:
---
- hosts: tag_Type_app
serial: "25%"
pre_tasks:
- name: Gathering ec2 facts
action: ec2_metadata_facts
- name: Remove from load balancers
debug:
msg: "This is where I'd fan out to multiple different hosts from group tag_Type_edge to manipulate
text files to remove the 25% of hosts from tag_Type_app from the load balancer"
tasks:
- name: Do a bunch of work to upgrade the app on the tag_Type_app machines while out of the load balancer
debug:
msg: "deploy new code, restart service"
post_tasks:
- name: Put back in load balancer
debug:
msg: "This is where I'd fan out to multiple different hosts from group tag_Type_edge to manipulate
text files to *add* the 25% of hosts from tag_Type_app back into the load balancer"
How can I structure this to allow for the inner loop over tag_Type_edge while using serial: 25% on all the tag_Type_app boxes?

If I may say so, yuk.
On the ACA project, each host had a file called /etc/ansible/facts.d/load_balancer_state.fact. That was just an INI file that set lb_state to enabled, disabled, or stopped.
We then ran the setup module (or gather_facts: yes), to get the state of each host, and ran the template module to create the load balancer config file. Very clean. Very simple.
To change a state, change the file and re-run the template module.
This was dynamic inventory.
If you have static inventory, it's even easier. Just set lb_state set on each host, either in an INI file like host1 lb_state=enabled, or in files in the host_vars directory. Change the inventory, re-run the template module, and (if necessary) tell the loadbalancer to reload the config file.

Related

Continuing Ansible on error in a multi-host deployment

In our deployment strategy, our playbooks take on the following structure:
workflow.yml
- hosts: host1
tasks:
- name: setup | create virtual machines on host1
include_tasks: setup.yml
- name: run | import a playbook that will target new virtual machines
include: "virtual_machine_playbook/main.yml"
- hosts: host1
tasks:
- name: cleanup | destroy virtual machines on host1
include_tasks: destroy.yml
virtual_machine_playbook/main.yml
- hosts: newCreatedVMs
roles:
- install
- run
Which works great most of the time. However if for some reason, the virtual_machine_playbook/main.yml errors out, the last hosts block does not run and we are required to manually destroy our VMs. I wanted to know if there was a way to mandate that each hosts block run, regardless of what happens before it.
Other Notes:
The reason that we structure our playbooks this way is because we would like everything to be as contained as possible. Variables are created in each hosts block that are rather important to the ones that follow. Splitting them out into separate files and invocations is something we have not had much success with
We have tried the standard ansible approach for error handling as found here, but most of the options only apply at the task level (blocks, ignore_errors, etc.)

master playbook create vms before loading inventory for bootstrapping

Is it possible to have one yaml file to create vms and bootstrap the new servers?
I have a master playbook
---
# Master playbook for
# - creating server
# - bootstrap server
#
- import_playbook: create_vm.yml
- import_playbook: bootsrap_vm.yml
import_playbook: create_vm.yml using hosts: localhost
import_playbook: bootsrap_vm.yml using hosts: all
Im using a dynamic inventory. But the bootstrap_vm.yaml does not know about the newly created servers. Is it possible to somehow update the inventory after the vms are created and before the bootstrapping starts?
meta: refresh_inventory was added in ansible 2.0 specifically for this type of requirement.
(meta: )refresh_inventory (added in Ansible 2.0) forces the reload of the inventory, which in the case of dynamic inventory scripts means they will be re-executed. If the dynamic inventory script is using a cache, Ansible cannot know this and has no way of refreshing it (you can disable the cache or, if available for your specific inventory datasource (e.g. aws), you can use the an inventory plugin instead of an inventory script). This is mainly useful when additional hosts are created and users wish to use them instead of using the add_host module.
Add this as a task at the end or your create_vm.yml playbook, or in a specific play in between the 2 playbooks.
Ref: https://docs.ansible.com/ansible/latest/modules/meta_module.html

How to configure apache vhosts in parallel with Ansible

Problem
I'm configuring apache vhosts with ansible. It takes a lot of time to create the configs.
Version info:
ansible-playbook 2.7.10
Apache/2.4.29
All vhosts are on the same server.
I'm using the file system structure that Apache suggests:
One file per site (vhost and used port) which will be saved to sites-available/443_example.com.conf. Then I generate a symlink to sites-enabled.
Sequentially running the tasks for the configuration takes about 5 minutes for 34 vhosts if there are no changes.
I created a role for the apache configuration. I identified 13 tasks that have to be run for every vhost:
openssl_privatekey
openssl_csr
openssl_certificate
6 tasks: place the files on the correct places in the file system but only if there is no Let's Encrypt configuration present
create template
enable template
2 tasks: delete old config
For example writing the config template, generate a self signed certificate.
It would be nice if I can parallelize these vhosts. I grouped the tasks I want to run in parallel in the file parallel.yml. The content of this file has to be processed in the correct order.
Solutions
These are the solutions I tried but none of them worked:
1) use async on every task:
I'm using a template in parallel.yml and this cannot be asynced.
2) use one async while including tasks:
- name: 'configure vhost'
include_tasks: parallel.yml
loop: "{{ vhosts }}"
async: 60
There is a know issue that makes Ansible ignore the the async. The loop items are processed serial https://github.com/ansible/ansible/issues/22716
3) use "delegate to" to create more forks of Ansible:
- name: 'configure vhost'
include_tasks: parallel.yml
loop: "{{ vhosts }}"
delegate_to: ansible#example.com
The loop items are processed serial https://github.com/ansible/ansible/issues/37995
4) use strategy: free
"A second strategy ships with Ansible - free - which allows each host to run until the end of the play as fast as it can." https://docs.ansible.com/ansible/latest/user_guide/playbooks_strategies.html
strategy: free allows multiple hosts to be run in parallel. That doesn't work on one host only.
5) increase forks
"Since Ansible 1.3, the fork number is automatically limited to the number of possible hosts at runtime," https://docs.ansible.com/ansible/2.4/intro_configuration.html#forks
Same problem as before.
Summary
How can I run the tasks in parallel?
How can I improve the performance?

Ansible group variable evaluation with local actions

I have an an Ansible playbook that includes a role for creating some Azure cloud resources. Group variables are used to set parameters for the creation of those resources. An inventory file contains multiple groups which reference that play as a descendant node.
The problem is that since the target is localhost for running the cloud actions, all the group variables are picked up at once. Here is the inventory:
[cloud:children]
cloud_instance_a
cloud_instance_b
[cloud_instance_a:children]
azure_infrastructure
[cloud_instance_b:children]
azure_infrastructure
[azure_infrastructure]
127.0.0.1 ansible_connection=local ansible_python_interpreter=python
The playbook contains an azure_infrastructure play that references the actual role to be run.
What happens is that this role is run twice against localhost, but each time the group variables from cloud_instance_a and cloud_instance_b have both been loaded. I want it to run twice, but with cloud_instance_a variables loaded the first time, and cloud_instance_b variables loaded the second.
Is there anyway to do this? In essence, I'm looking for a pseudo-host for localhost that makes it think these are different targets. The only way I've been able to workaround this is to create two different inventories.
It's a bit hard to guess how you playbook look like, anyway...
Keep in mind that inventory host/group variables are host-bound, so any host always have only one set of inventory variables (variables defined in different groups overwrite each other).
If you want to execute some tasks or plays on your control machine, you can use connection: local for plays or local_action: for tasks.
For example, for this hosts file:
[group1]
server1
[group2]
server2
[group1:vars]
testvar=aaa
[group2:vars]
testvar=zzz
You can do this:
- hosts: group1:group2
connection: local
tasks:
- name: provision
azure: ...
- hosts: group1:group2
tasks:
- name: install things
apk: ...
Or this:
- hosts: group1:group2
gather_facts: no
tasks:
- name: provision
local_action: azure: ...
- name: gather facts
setup:
- name: install things
apk:
In this examples testvar=aaa for server1 and testvar=zzz for server2.
Still azure action is executed from control host.
In the second example you should turn off fact gathering and call setup manually to prevent Ansible from connecting to possibly unprovisioned servers.

Ansible group sets of servers into 'apps'

I have a script that setups all the servers. Now trying to figure out a good way to configure them to talk to each other. e.g. Configure the application server to talk to a particular database server.
Test app 1
db01
app01
app02
mem01
Test app 2
db02
app03
mem02
The only thing I could come up with a role that takes the servers as params but I dislike that I have to also specify the hosts twice.
- name: Test app 1
hosts: [db01, app01, app02, mem01]
roles:
- {role: app, db: db01, ap: [app01, app02], mem: mem01}
How organized is your inventory file?
Looking at what you posted, this might be a good inventory file organization for you:
[testapp1-dbServers]
db01
[testapp1-appServers]
app01
app02
[testapp1-memServers]
mem01
[testapp2-dbServers]
db02
[testapp2-appServers]
app03
[testapp2-memServers]
mem02
[testapp1:children]
testapp1-dbServers
testapp1-appServers
testapp1-memServers
[testapp2:children]
testapp2-dbServers
testapp2-appServers
testapp2-memServers
[dbServers:children]
testapp1-dbServers
testapp2-dbServers
[appServers:children]
testapp1-appServers
testapp2-appServers
[memServers:children]
testapp1-memServers
testapp2-memServers
This might be overkill if you have no plans to increase the number of servers in any of the first 6 buckets, but it allows you to do things like group_vars files (individual ones for some or all groupings - testapp1, testapp2, dbServers, etc) and clean up your playbook file:
- name: Test app 1
hosts: testapp1 (all vars passed via group_vars file)
roles:
- generic_server_setup
- name: DB Server setup
hosts: dbServers (all vars passed via group_vars file)
roles:
- install_postgres
- other_db_things
The final thing that will help you the most can be found here.
Specifically, getting access to all the groups the current host is in and all the hosts in a group.
QUICK FIX: If you want to sacrifice some organization because you aren't worried about scaling and are not annoyed by the same information being in multiple locations, just add the relevant hosts as vars to testapp1 and testapp2 files under group_vars.

Resources