Ansible is yet another configuration management system. A distinctive feature is simplicity, with great flexibility. And these are not just words - then I will show with examples some simplest operations and introduce you to some of the best praktis.
So, we have groups of hosts:
WebServersG1 | webserver1-g1, webserver2-g1 |
WebServersG2 | webserver1-g2, webserver2-g2 |
WebServersProxy | webserver-proxy1, webserver-proxy2 |
Database | db1, db2 |
DataBaseSlave | dbs1, dbs2 |
Someservers | someserver1, someserver2 |
We want to prepare all the hosts for adequate work - install the necessary set of software (htop, zsh, vim, iftop, sudo, mc, tmux, wget), copy their keys and configs and install and configure software specific to this server.
Ansible means at least two files to get started - an
inventory file in which we write a list of hosts and divide them into groups -
inventory and
task file -
playbook .
They are needed so that when we all make everything run beautifully:
ansible-playbook -i _ playbook.yml
Let's create an inventory file named “
infrastructure ” based on our hosts:
[WebServersG1] webserver1-g1 webserver2-g1 [WebServersG2] webserver1-g2 webserver2-g2 [WebServersProxy] webserver-proxy1 webserver-proxy2 [DataBase] db1 db2 [DataBaseSlave] dbs1 dbs2 [SomeServers] someserver1 someserver2
Actually, everything is not bad, but our hosts from the
WebServersG1 and
WebServersG2 groups differ only in the directory structure, the number of connections and the repository. And
WebServersProxy differs from them only in the nginx config and the absence of some kind of software. In addition, you may need to do some kind of task on all three groups at once. So let's put together these three groups and give them a
parent :
')
[WEB:children] WebServersG1 WebServersG2 WebServersProxy
According to the structure of the inventory file: ansible considers everything as a group, whose line in the description begins with '[' and ends with ']'. Everything under this line and before the next group starts is hosts. A group may have children - other groups that are listed after
[group name: children] and exist.
I will also briefly touch on variables that can also be described immediately in this list.
webserver1-g1 ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50
Here we assigned the
ssh port and
ssh host . In practice, I will say that other variables in the
inventory file clutter it up and make it unreadable. And it is better to list non-standard ports in this form:
webserver1-g1:5555
And since we are talking about ports - the default port for all hosts (as well as many other things) can be assigned in
ansible.cfg, but not the essence.
So, we have just created a list of hosts. Let's now create a task list, and I will plunge into the memories.
In the distant past (approximately May-July of this year), until the release of version 1.2, there were no roles in principle, we were content with the usual tasks and the playbook looked like a Christmas tree, full of inclusions. Then came the role, and more recently, a week or two ago, in version 1.3 - the inheritance of roles. And we, as true Jedi, will use what we have now. And let's see, finally, what are these playbooks and roles, but it is not clear.
Playbooks are an executable set of anything. They, unlike chef, run once and only on your command. Although there is nothing that would put the task in kroner. Previously, the playbook meant the main list of tasks, but now they have become a set of pointers to the roles we need.
Roles , in turn, are a set of tasks, templates, handler triggers, variables, files, and links to other roles assigned to directories in the standard for ansible structure. Roles are best grouped into logical groups. For example, within the framework of the task set above, I will highlight the following roles:
1) preparation - installation of various admin software, creating users with keys, generating locales, copying configs, etc.
2) install software for the necessary service as a result and copy its configs
3) creating the necessary directory structure, copying the gita, etc.
So, let's start creating the main.yml
playbook for now with the only role of
preconf - hosts: all roles: - preconf tags: preconf
Here we assigned the preconf role to all the 'all' hosts and added the preconf tag.
Tags are needed in order to then be able to perform only some part of the playbook.
Further, when the ansible sees the role assignment, he frantically searches for the same-name directory in ./roles. In our case it will be ./roles/preconf
There the role structure should be ready, namely: tasks / main.yml
This is the main file in which tasks are listed.
Also there may exist a directory 'templates', in which we put the
config file templates, 'files', in which we will have different
files we need, 'handlers' - those same glorified triggers, as well as meta / main.yml in which we describe links to the role and variables - variable roles.
I suggest to understand in order, but if you want - you can read the text of the dice down sharply jerking his head from right to left.
So, the tasks.
For our preconf role, create a file
./roles/preconf/tasks/main.ymlUsually tasks look like this:
- name: : .
There are a lot of
modules , they are for every
taste and color . With the help of modules, we can deploy a machine in the cloud, execute a shela command, manage databases, create files and folders, copy templates, send messages to queues, manage network infrastructure, write messages in chats, install programs, manage the system and much more. To dwell on each of them is a topic for a separate article, exactly, like writing one's own, is not too difficult.
In the meantime, try to install something.
- name: installing zsh apt: pkg=zsh
In this case, we used the
apt module to install zsh programs on our Debian servers.
Of course, you can spam the task list with a separate task for installing each program, but such files are very poorly read. Therefore, we will use the queue, which can be called via '
with_items ':
- name: installing zsh apt: pkg=$item with_items: - zsh - htop - sudo - iftop - tcpdump - mc - wget - vim - tmux - facter
In YAML syntax, if I'm not confusing anything, a similar entry denotes an array. And the elements of this array, as if passing through the xargs, are in turn assigned to the variable
item , which we called above and already with the new value are executed in the task. Time after time, until the list is over.
Now we will create all the users of the escort department and, to the occasion, use the variables.
Adding users looks something like this:
- name: Add User Pupkin user: name='pupkin'
So our user will be added with a bang. But we want the user puph to use zsh and be a member of the sudo group? And let's do it, because the 'user' module supports a lot of things.
- name: Add User Pupkin user: name='pupkin' shell='/bin/zsh' groups='sudo'
We have already learned with_items, so we will use it to add several users ... But ... after all, it’s necessary to transfer two variables in the stream ...
Nothing complicated. Ansible supports hashes - arrays in the form of 'key: value'. The most convenient way is to write hashes in jinja2 format.
- name: Add BestAdminsTeam user: name={{ item.user }} shell={{ item.shell }} groups='sudo' with_items: - { user: 'pupkin', shell: '/bin/zsh' } - { user: 'oldfag', shell: '/bin/sh' }
Actually what did we just do? We have created an array of this type:
{ {user: 'pupkin', shell: '/bin/zsh' }, { user: 'oldfag', shell: '/bin/sh' } }
from which we will take the elements one by one and assign them to the variable $ item ( or, to put it in jinja2 language - {{item}}). After that we will open the hash items - {{item.user}} and {{item.shell}}, respectively. That is, we get a consistent list of variables for each user.
Now let's add keys to our users. There is an excellent '
authorized_key ' module for this.
- name: Add BestAdminsKey authorized_key: user={{ item.user }} key="{{item.key}}" with_items: - { user: 'pupkin', key: 'ssh-rsa pupkin_pub_key' } - { user: 'oldfag', key: 'ssh-rsa oldfag_pub_key' }
In principle, it will work this way, but it is a little inconvenient to enumerate users every time: it is very easy to forget to add a new team member somewhere and I have to somehow tell you about variables, and here is a case.
Let's create a variable in which we store all of our users with their keys, shelves, possibly paths to configs and everything else. Putting users into a variable is easy, but where do you put the variable itself?
For this, ansible has several solutions.
You can specify variables specifically for each host in the folder ./host_vars/host_name - this is not suitable for our purpose
You can make variables for a group of hosts (groups, remember, we did in the inventory file) and put them in ./group_vars/group_name - you can, of course, create a variable here, for the group all. But this is not “clean work”.
You can assign variables directly in playbooks. But it looks untidy.
Each role also has “defaults” in the defaults directory, but this is not exactly what we need.
Well, finally, variable roles - it looks like this.
Create a ./roles/main/variables/main.yml file into which we write:
ssh_super_team: - { user: 'pupkin', key: 'ssh-rsa pupkin_pub_key', shell: '/bin/zsh'} - { user: 'oldfag', key: 'ssh-rsa oldfag_pub_key', shell: '/bin/sh' }
Now in the task we can use the $ ssh_super_team variable like this:
- name: Add BestAdminsTeam user: name={{ item.user }} shell={{ item.shell }} groups='sudo' with_items: - $ssh_super_team - name: Add BestAdminsKey authorized_key: user={{ item.user }} key="{{item.key}}" with_items: - $ssh_super_team
Well, in order to make everything convenient, we will copy our users the .vimrc file - the same for both users. In the directory ./roles/main/files/ we put the file named 'vimrc' and do the following task:
- name: copy vimrc file copy: src=”vimrc” dest=”/home/{{item.user}}/.vimrc” with_items: - $ssh_super_team
Voila
By the way, I sometimes re-read what I write - it turned out to be volumetric. It is time to round off everything that is not round, so I immediately jump to the nginx configuration:
Using tracing paper, we will create the nginx role and write to the file ./roles/nginx/tasks/main.yml:
- name: install nginx apt: pkg='nginx'
Hmm, but you can't have nginx with the same config on all servers? Maybe, of course, but this is inconvenient. Let's think up a template - in ansible for this we need to use the syntax familiar to many jinja2.
Create a file in ./roles/nginx/templates/ and name it nginx_site_conf.j2
And the content will do this:
server { listen 80; server_name {{ item.sitename }}; root “/var/www” }
Next, we need to decompose unique variables for each host: we assign files to ./host_vars/hostname
nginx: - { sitename: “www.example.com” }
And we define the default variable for this role: in the file ./roles/nginx/defaults/main.yml
nginx: - { sitename: “default” }
Now create a task:
- name: nginx config for sites template: src=”nginx_site_conf.j2” dest=”/etc/nginx/sites-enabled/{{ item.sitename }} with_items: - $nginx notify: - restart nginx
And ansible, during the passage of this task, it will try to copy the template “nginx_site_conf.j2” from the $ nginx variable, which will be read either from host variables or from default role variables. And if the template to be copied differs from the config on the machine, the handler will execute, which looks like this: ./roles/nginx/handlers/main.yml
- name: restart nginx action: service name=nginx state=reloaded
Now we will collect from these two intentionally simplified roles one single for a group of hosts
WebServersG1 . Now we will not complicate anything, but simply make in it a link to two ready-made roles. Write the following lines in the roles / myapp / meta / main.yml file:
--- dependencies: - { role: preconf } - { role: nginx }
And finally,
playbook.yml playbook: - hosts: WebServersG1 roles: - WebServersG1
By the way, during the construction of dependencies, you can also use variables to clarify some information specifically for this role. But we will talk about this, as well as dependencies, complex templates, task execution checks, multi-threaded execution, and many other things next time.
In the meantime, you can read excellent, not like the others, documentation:
www.ansibleworks.com/docsPS My internal editor went on vacation. Please forgive me for mistakes and uneven handwriting. I always read drugs and always say “thank you” for corrections.