📜 ⬆️ ⬇️

Work with Ansible - tasks with several unknowns

Google information on Ansible , stumbled upon an article on Habré. I read it and was very surprised: you can make it more beautiful! If you are interested - welcome under the cat!

Immediately I will note that everything works on a real project (the number of nodes is more than 80, but less than 100).

Here are the tricky tasks with unknowns that are encountered by at least one in three, and maybe the second DevOps.
  1. As a parameter of the configuration file, use previously unknown node addresses, and these nodes belong to another role ( solution )
  2. To generate inventory from unknown addresses of nodes dynamically - through a call to AWS ( solution )
  3. Create the Nginx configuration file, which should contain previously unknown addresses of the backend nodes ( solution )

So first, the simplest:

Substitution of node addresses


Take an example: we have a group of “app_nodes” nodes, on which we put some kind of application using the following task file:
')
application.yml
 ---
 - hosts: app_nodes
   gather_facts: yes
   roles:
     - role: common
     - role: application


Imagine that we have a ZooKeeper service, and in the configuration file of the application you need to write a setting for working with this service:
zk.connect=127.0.0.1:2181,127.0.0.2:2181,127.0.0.3:2181,127.0.0.4:2181, 127.0.0.5:2181

It is clear that the pens prescribe the addresses of all, for example, five nodes, each of which is installed ZooKeeper, on the nodes with the application - no pleasure. What can we do to make Ansible insert all the addresses of these nodes into the configuration file template?
Nothing special. Ansible uses the Jinja2 templating engine over YAML, so we use the templating loop:
zk.connect={% for host in groups['zk_nodes'] %}{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}:{{ zk_port }}, {% endfor %}

As a result, after the work of the template engine, the required line should turn out, but here's the bad luck: we work with the app_nodes nodes, and we use information (“facts”) about the zk_nodes nodes in this template. How to get the addresses of the zk_nodes nodes if in this task file we don’t work at all with these nodes? Let us assign to this particular group of nodes (zk_nodes) an empty list of tasks:

application.yml
 ---
 - hosts: zk_nodes
   gather_facts: yes
   tasks: []
 - hosts: app_nodes
   gather_facts: yes
   roles:
     - role: common
     - role: application


For this task file to work, the necessary groups of nodes must be specified in the inventory file:

environments / test / inventory
[zk_nodes]
127.0.0.1
127.0.0.2
127.0.0.3
127.0.0.4
127.0.0.5
[app_nodes]
127.0.0.10
127.0.0.11
127.0.0.12

And what to do if the host addresses are unknown in advance? For example, are virtual machines used in EC2? Here we smoothly proceed to answer the second question.

Work with dynamic inventory


Information on this topic on the Internet is not too much - as I understand it, due to the existence of Ansible Tower.

So, you have a number of nodes in EC2, and you want to manage them centrally and easily. One possible solution is to use the Ansible + dynamic inventory script.

Here is the structure of the corresponding inventory directory:

tree ./environments/dynamic
 .
 2.── ec2.ini
 2.── ec2.py
 Groups── groups
 Group── group_vars
     All── all
     Z── zk_nodes
     Prox── proxies
     App── app_nodes


Here: ec2.py - the script itself, you can take it here , the link is direct; ec2.ini is a file with script settings, you can take it here , the link is direct; groups is a file describing the groups of nodes with which you intend to work in this inventory, group_vars is a directory containing the values ​​of variables for each specific group, and common to all.

Next, under the spoiler, those settings that I have differ from the ini-file by the link:

Ec2.ini changed options
# regions where our nodes are located
regions = us-east-5, us-west-2
# work only with live nodes
instance_states = running
# all parameters group_by _.... are commented out, except one:
group_by_tag_keys = true
# We select which nodes will enter our inventory. In this case, those that have a “Name” tag with the specified values.
instance_filters = tag: Name = zk_node, tag: Name = app_node, tag: Name = proxy

In order for Ansible to correctly recognize these groups and nodes, we write the groups file:

groups file
 [all: children]
 zk_nodes
 proxies
 app_nodes

 [zk_nodes: children]
 tag_Name_zk_node

 [proxies: children]
 tag_Name_proxy

 [app_nodes: children]
 tag_Name_app_node

 [tag_Name_zk_node]
 [tag_Name_proxy]
 [tag_Name_app_node]


Here we inform Ansible that the all node group with which it works by default contains nested groups zk_nodes , proxies , app_nodes . We further inform that these groups also have nested groups that are dynamically formed, and therefore nodes are not indicated in their descriptions at all. Such is black magic - during its work, the dynamic inventory script will create groups of the form tag_ <Tag name> _ <Tag value> and fill these groups with nodes, and then you can work with these groups using the usual Ansible tools.

Yes, immediately on the group_vars directory. It is automagically read by Ansible when loading inventory, and each group in this inventory gets the variables from the group_vars / group_name file. One example of use is a separate key for a specific group of nodes:

group_vars / zk_nodes
ansible_ssh_private_key_file: "/tmp/key.pem"

Having considered dynamic inventory, we can elegantly solve the third problem:

Formation of the Nginx configuration file


It is clear that the Nginx configuration template does not surprise anyone on Habré, so I’ll confine myself to an upstream block with explanations.

nginx.conf upstream
upstream app_nodes {
{% for item in groups ['app_nodes']%} server {{hostvars [item] ['ec2_private_ip_address']}}: {{app_port}};
{% endfor%}
keepalive 600;
}

This configuration block defines a group of upstream servers with different addresses and a common port number.
The template engine will run through all the nodes of the app_nodes group, generating a line for each node. It turns out like this
Result example
 upstream app_nodes {
 127.0.0.1 true;
 127.0.0.2:37000;
 127.0.0.3.3000;
 127.0.0.4.1000;
 keepalive 600;
 }


Here, the difference from the situation with the first solution is that there is no need to additionally handle the empty list of tasks for the group of nodes “app_nodes” - this group is automatically created, among others, according to the groups file given above, thanks to the dynamic inventory script. Well and, of course, the address on internal addresses VPC is used.


Afterword


Names of environments, tasks, inventory, nodes, IP addresses are replaced with fictional ones. Any matches are random. Where names of files or directories are important for a functional, explanations are given why they are so called.

Remember, you and only you are responsible for the status of your projects to the manager, customer and your own conscience. I do not pretend to be complete. I hope this article will save someone time, and someone will push in the right direction. To the best of my ability and understanding, I will answer questions in the comments.

Source: https://habr.com/ru/post/266481/


All Articles