Configuration management with Ansible
Your editor recently hit that point and decided that it was time to learn about these newfangled configuration tools. Tools like CFEngine (started in 1993) might be relatively new on the scene, but perhaps one might be able to conclude at this point that they are not going away in the near future. It didn't take much research time to come to a few preliminary conclusions.
One of those would be that there are quite a few of these tools now. Available offerings include Ansible, CFEngine, Chef, Puppet, Salt, Vagrant, and numerous others. These projects are all free software, and they all appear to be at least moderately healthy with significant user bases. Free software is said to be an exercise in scratching one's own itches; it seems that quite a few developers have this particular itch and have found the energy to do some scratching.
One also quickly concludes, however, that a fair amount of the itching driving these projects is financial in nature. It is a rare configuration-management project that doesn't have a company behind it just waiting for the opportunity to provide ancillary services. Ansible offers a dashboard add-on and consulting services; CFEngine has an "Enterprise Edition" with a graphical interface and support options; Chef has centralized configuration storage and, yes, a management console, and so on. Configuration-management tools appear to be almost uniformly open-core offerings with proprietary administrative tools and support services layered on top.
These tools also tend toward a high level of complexity. Many of them require multiple daemons running on each managed system and can be quite intimidating to approach. If one is trying to manage a data center with tens of thousands of nodes serving multiple functions and with its own dedicated hydroelectric plant for power, this complexity may prove to be necessary. It may also be useful if one has outsourced the power bill to a cloud provider and instantiates new systems like raindrops. For those with more modest requirements, though, all that complexity seems like overkill.
Your editor, being one of those with relatively modest requirements, shied away from most of the available configuration-management systems. The expected payback just didn't seem to justify the effort required to get to the first stage of learning (which is, of course, configuring a new system to boot and say "hello, world"). But then Ansible came into view, and it seemed like it might merit a further look.
Ansible
It is worth saying from this outset: your editor is not prepared to say that Ansible is the best solution for any particular use case. It is far too early for any such statement. This article, instead, should be seen as a look at one of a number of potentially interesting tools for configuration management.
Ansible got off on the right foot by making it clear that there is no need to install software on the machines to be managed. There are no daemon processes that run on these systems. Instead, everything is run in a "push" mode via SSH from a central server (or, really, from any system that has the Ansible configuration on it). That makes it easy to get started and to build up to a more complex configuration setup.
On the other hand, this mode of operation almost certainly slows things down. Before it can perform any configuration work on a target system, Ansible must push its own software onto that system and go through a "fact-gathering" phase. A long-running daemon on a target will not need to completely familiarize itself with the state of that target for every update, while Ansible does. For relatively small networks, speed is not a problem. For large, complex configurations it might be.
Ansible works with a declarative configuration in the YAML syntax; this configuration is split into one or more "playbooks." "Declarative" means that the configuration file describes the desired state of the target system, rather than a set of steps for getting into that state. So Ansible configuration blocks say things like "ensure that these packages are installed and current," "there should be an account called 'foo'," or "the 'bar' service should be running."
The advantage of this approach is that Ansible can compare the desired and current states of a system and figure out the minimal amount of work to get the system into the desired state. The tool can be run at any time and it will perform whatever tweaks are required while minimizing unnecessary thrashing on the target system. The downside is that it is not always easy to describe the desired state in this fashion.
As an example, consider this little configuration block:
- name: Disable password logins lineinfile: dest=/etc/ssh/sshd_config regexp=^PasswordAuthentication line="PasswordAuthentication no" state=present notify: - restart sshd
This is essentially a declaration that the sshd_config file on the target system should have the requisite line disabling password logins. It can take a surprising number of tries to get this kind of thing right; without sufficient care, one might look at the sshd_config file one day and find dozens of PasswordAuthentication lines appended to the end. Not that your editor would make any such mistake, of course.
All of the real work in Ansible is done with "modules" that know how to perform a specific configuration task. The list of modules is extensive. Modules exist to manage file permissions, tweak Apache configurations, perform package management, deal with SSH keys, install Python libraries, manage firewall configurations, deploy software from Git repositories, and more. Much of the task of learning Ansible is an exercise in figuring out which is the right module for any specific job.
As can be seen from the snippet above, Ansible configurations can be on the verbose side. That verbosity can help to make configurations relatively easy to read, but it does get tiresome to type after a while. Some of that verbosity can be mitigated at the cost of using some fairly unintuitive looping constructs. For example:
- name: install packages yum: name={{ item }} state=latest with_items: - emacs - git - nethack
This declaration causes Yum to be employed to install the listed packages on the target system(s). It is easy enough to write and understand once one gets used to it, but it feels awkward; it is an attempt to impose a somewhat imperative, loop-like structure onto a language that is supposed to be declarative. The upcoming Ansible 2.0 release adds conditional structures to deal with failures and such; it will increase the flexibility of the language, but the new structures feel awkward in the same way.
The above snippet highlights one other limitation that is not entirely Ansible's fault. Once one has an abstract description of a system's configuration, one might reasonably want to apply it to systems running different distributions — or, indeed, different operating systems altogether. But those other systems are unlikely to be using Yum, so this item fails. One can get around such issues by using different configurations for different operating systems, but that results in a certain amount of repetition.
The good news is that the 2.0 release includes a "package" module that works with Yum, DNF, Apt, opkg, Portage, and Zypper (and probably others as well). The bad news is that it still seems to have some glitches with some package managers. There is also little to be done about the sad fact that the same program is often packaged under different names on different distributions. Fully distribution-independent configurations are a nice idea, but they are not quite a reality yet.
A complete configuration for a given system type can be long; if one follows the suggested best practices, it will also be split into possibly dozens of files in a deep directory hierarchy. For added fun, half of the files will be called main.yml. This organization provides a lot of flexibility and modularity, but, as one is trying to figure out which main.yml needs to be edited to accomplish a particular task, it's hard not to feel that there should be a better way.
The error diagnostics provided by Ansible are, to put it bluntly, poor.
There is, for example, no indication of which file contains a particular
problem. The 2.0 update improves this situation considerably; the Ansible
developers evidently figured out single-line "foo is not a legal
parameter in an Ansible task or handler
" failure messages were not
helping their world-domination goals.
Needless to say, Ansible has lots of features designed to aid the administration of large networks, including variables and templates, a mechanism for combining configuration combinations into "roles," the ability to update many machines in parallel, and so on. There is a set of modules for interfacing with various cloud providers to perform basic orchestration tasks, and others to deal with OpenStack-based clouds. The modules themselves are written in Python and are relatively easy to create or extend.
Ansible is still not in production use here at LWN; there is more messing
around to be done before we can reach that point. But it is clear that
tools like Ansible have an important role to play in a world where "the
server" is no longer a big box named after a Lord of the Rings character,
but is, instead, just one of vast numbers of essentially anonymous
appliances. It is not surprising that a lot of energy is going into the
development of these tools. After all, system administrators have always
been big fans of automation; a variety of free configuration-management
tools makes it easy for each of them to scratch that itch in the way they
think best.
Posted Aug 6, 2015 7:41 UTC (Thu)
by fishface60 (subscriber, #88700)
[Link] (2 responses)
Posted Aug 7, 2015 6:47 UTC (Fri)
by kleptog (subscriber, #1183)
[Link] (1 responses)
Frankly, I think the choice of YAML as a config language was a mistake here, this problem just screams for a DSL. The way loops are hacked in is just horrible. Not that I'd call Puppet a great example, but by defining an actual language it does become more readable. FWIW, loops in Puppet are also not obvious, but you don't really want to talk about loops in a declarative language anyway, you need to call it "map" or something.
An intuitive readable way to configure systems has AFAICT not yet been invented...
Posted Oct 18, 2015 0:08 UTC (Sun)
by smckay (guest, #103253)
[Link]
A little bit longer: Chef is configured with resource objects, and they're just slightly smarter than a plain old ruby object. Each resource class has an associated provider class that does the actual alteration of the target system. A Chef recipe is just Ruby code that creates resources. So if you want loops or conditionals it's very easy and looks perfectly natural. Then if you want to do weird things it's fairly simple to make your own resource and provider classes, and the provider can do anything that Ruby code can do, which is pretty substantial because you can use gems. In three or so years of using Chef it's been very rare that I had any difficulty writing out what I wanted in a recipe.
Posted Aug 6, 2015 14:29 UTC (Thu)
by DG (subscriber, #16978)
[Link] (1 responses)
I chose it as it didn't have any real dependencies (aside from occassional things like needing python-apt on the target debian server when you're using the 'apt' module).
There are some tricks you can do to speed things up a little - ssh pipelining / control persist - however I've found myself sometimes reverting to "check a file on the remote system for a version number and skip X tasks if they've been done" which isn't ideal, but every little helps.
It's a slight annoyance that you need to define 'task handlers' for each notify you use - being able to write :
- name: whatever
would be useful.
Ansible does allow you to 'tag' various parts of the configuration - so you can choose to run a subset of tasks - but it requires you to litter the task YAML with 'tag: blah'. Strangely it doesn't seem to allow you to just run everything in a single role.
I've increasingly found myself breaking a role's tasks up into separate meaningful .yml files that are included from the main.yml - i.e.
roles/webserver/tasks/main.yml containing :
There's also 'ansible galaxy' - which is trying to be a library of re-usable roles (e.g. "install sysdig" or "setup nginx on CentOS").
( I'm not sure I'd agree that Vagrant is something for Configuration management though - I always thought it was just a funky frontend to virtualbox/whatever which allowed a developer to have a VM in a known state - where a Vagrantfile defined the VM image to use and any post-setup tasks to run (using Ansible/Chef recipies) )
Posted Aug 11, 2015 2:32 UTC (Tue)
by ploxiln (subscriber, #58395)
[Link]
I don't use Ansible Galaxy. Roles from galaxy tend to include stuff for different families of distros, which I'd want to strip out to leave the minimal necessary for servers I manage. I'd probably need to tweak them. Also, they're roles...
I don't use roles. They split things into too many tiny files, at least one directory level too deep. The whole of some roles could easily fit in a single terminal windowful, except that I'd need 4 splits or tabs to see it (and I'd need an extra wildcard to get into all the subfolders for all these tiny files).
Instead I put a collection of related tasks in tasks/<name>.yml, and include "tasks lists" from the playbook. Variables go in group_vars/all or group_vars/<group>.yml.
I don't use "notify". I use a restart task in the tasks list with a more complex "when" condition, so as to not restart a service if I just installed and started it after setting up the configuration. Some services (databases) require more coordinated manual restarts, so I have it print out a warning (using the debug module) only in the case where a manual coordinated restart is needed (which doesn't include the first time it starts up).
One of my biggest pet-peeves with ansible is that I can't have my own custom module print out exactly what I want. If I want it to on occasion print what specifically it decided to change about what it's managing, but not fail the deploy at that point, I need to register a variable and use the debug module to print it, there's really no other way. That makes for a lot of boilerplate for each task I want this for, and in the ansible-playbook output (it turns one desired extra line of output into 4-ish extra lines).
Anyway, it's not so bad, you can make it work :)
Posted Aug 6, 2015 16:09 UTC (Thu)
by joey (guest, #328)
[Link]
I've done this in Propellor, which can use a push model like Ansible (or a pull model if you don't want to push to a ton of servers).
Propellor uses haskell for its configuration language, so it's not for everyone, on the other hand it's not at all as hard to write a propellor config file in haskell as you're thinking it is right now. ;) https://propellor.branchable.com/haskell_newbie/
Since a propellor configuration is a haskell program, it gets compiled and most configuration errors are caught at compile time. Although I might have appended some unwanted repeated lines to config files a time or too myself. ;-)
Finding ways to use haskell's type system to eliminate entire classes of misconfigurations is what I'm really excited about with propellor going forward. For example, I have code not yet quite integrated that will let propellor track resources, such as which ports on a server are in use, at the type level, so it'll be a compile time error to install daemons on a server that both want to use the same port.
Posted Aug 6, 2015 21:00 UTC (Thu)
by fredrik (subscriber, #232)
[Link]
Sure, I still have to have root or tell the sysadmin to install any prerequisites that are root only, but the application configuration that is managed with a regular user can be handled by ansible.
Though I still use it only in the above mentioned minimal manor, so far it has been a pleasure. I've gone from rather laborious hand written application installation and upgrade notes - notes that frankly quite often doesn't match reality, because I forgot this or that tiny little change that was made on the integration host - to a single ansible playbook per application. The playbook is version managed and tagged with every release, so I never have to worry wich installation procedure was used to install or upgrade on any host.
It is interesting how the tiny step from only a few and seemingly easy commands - scp, cp, mv, rm, service restart foo - when converted into a playbook and replaced by one single deployment command, can reduce your resistance to pushing changes into staging and production.
Posted Aug 7, 2015 11:09 UTC (Fri)
by mlawren (guest, #10136)
[Link] (1 responses)
From a first glance it appears to be more imperative than declarative, with a focus on "Tasks" to be run on demand. Similarly to Ansible it requires no software installation on target hosts. It has a domain specific language which can be extended with whatever Perl you care to use.
Posted Aug 7, 2015 11:19 UTC (Fri)
by ivuk (guest, #102574)
[Link]
Posted Aug 7, 2015 22:35 UTC (Fri)
by robert_s (subscriber, #42402)
[Link]
I'll stick to nix, thanks.
Posted Aug 10, 2015 4:39 UTC (Mon)
by apollock (guest, #14629)
[Link] (3 responses)
I think SSH (specifically the shell aspect of it) is a poor choice for controlled management, as you can basically do anything with it. Using a configuration management tool that can log and otherwise be restricted (I don't think rbash is the right solution) seems better than wondering if a particular SSH connection was to restart Apache or do something more nefarious...
Posted Aug 10, 2015 7:59 UTC (Mon)
by dlang (guest, #313)
[Link]
Any config management system that can do the job is going to be able to do anything. At least SSH is a well known tool and there are many ways to configure it to either restrict what the remote system can do with it, or create an audit trail.
Far better than inventing a new protocol that may or may not be as secure as SSH.
Posted Aug 10, 2015 13:34 UTC (Mon)
by rahulsundaram (subscriber, #21946)
[Link]
Just as a counter point, using SSH is absolutely the #1 thing I like about Ansible. It makes setting up new systems, especially on a cloud service way more easier and I can use a separate user with SSH keys to protect it from unauthorized usage. YAML however can be sometimes tricky to debug although it is getting better with newer releases.
Posted Aug 13, 2015 10:07 UTC (Thu)
by beagnach (guest, #32987)
[Link]
One thing that bugs me about Ansible is how they layer a non-yaml key-value mechanism on top.
For example, your password logins thing:
Configuration management with Ansible
- name: Disable password logins
lineinfile: dest=/etc/ssh/sshd_config
regexp=^PasswordAuthentication
line="PasswordAuthentication no"
state=present
notify:
- restart sshd
lineinfile is being passed a value-less mapping, and the values are parsed out by splitting the keys at the =.
I prefer using it in a more YAML style:
- name: Disable password logins
lineinfile:
dest: /etc/ssh/sshd_config
regexp: ^PasswordAuthentication
line: PasswordAuthentication no
state: present
notify:
- restart sshd
In my limited experience with Ansible, this second form is also acceptable and equivalent, while also being more recognisable as YAML.
In addition, when there's more complicated interpolations going on, so assuming there's some weird distro where packages may have spaces or = in their names, with the "install packages" command you had before,
- name: install packages
yum:
name={{ item }}
state=latest
with_items:
- emacs=text editor
- git=version control
- nethack=game
When it goes to interpolate your package names into the "yum" command, you feed it inputs like "name=emacs=text editor". It's not obvious to me whether I need to escape things to prevent Ansible misinterpreting it, while if it was written with the mapping style:
- name: install packages
yum:
name: {{ item }}
state: latest
with_items:
- emacs=text editor
- git=version control
- nethack=game
I'd know that we'd be passing a key called "name" and a value containing "emacs=text editor", and Anisble won't need to layer on any extra parsing, so there's nothing for it to misinterpret.
Configuration management with Ansible
Configuration management with Ansible
Configuration management with Ansible
template: ....
notify:
- service httpd restart
- include: apache24-core.yml
- include: apache24-server-status.yml
- include: php-dotdeb.yml
- include: munin-graphing.yml
Configuration management with Ansible
Configuration management with Ansible
Using a real programming language also makes it easy to refactor and generalize properties so they can be reused for multiple systems.
This is a side project of mine, not open core or with commercial ambitions, and I expect it to stay that way.
https://propellor.branchable.com/
Configuration management with Ansible
Configuration management with Ansible
Configuration management with Ansible
Configuration management with Ansible
Configuration management with Ansible
Configuration management with Ansible
Configuration management with Ansible
Configuration management with Ansible