martin f. krafft | 8acd49d | 2013-08-26 21:22:25 +0200 | [diff] [blame] | 1 | ========================== |
| 2 | Using reclass with Ansible |
| 3 | ========================== |
| 4 | |
martin f. krafft | a06cffc | 2013-08-27 12:46:11 +0200 | [diff] [blame] | 5 | .. warning:: |
| 6 | |
| 7 | I was kicked out of the Ansible community, presumably for `asking the wrong |
| 8 | questions`_, and therefore I have no interest in developing this adapter |
| 9 | anymore. If you use it and have changes, I will take your patch. |
martin f. krafft | 8acd49d | 2013-08-26 21:22:25 +0200 | [diff] [blame] | 10 | |
| 11 | .. _asking the wrong questions: https://github.com/madduck/reclass/issues/6 |
| 12 | |
| 13 | Quick start with Ansible |
| 14 | ------------------------ |
| 15 | The following steps should get you up and running quickly with |reclass| and |
| 16 | `Ansible`_. Generally, we will be working in ``/etc/ansible``. However, if you |
| 17 | are using a source-code checkout of Ansible, you might also want to work |
| 18 | inside the ``./hacking`` directory instead. |
| 19 | |
| 20 | Or you can also just look into ``./examples/ansible`` of your |reclass| |
| 21 | checkout, where the following steps have already been prepared. |
| 22 | |
| 23 | /…/reclass refers to the location of your |reclass| checkout. |
| 24 | |
martin f. krafft | cc8851d | 2013-08-27 13:43:16 +0200 | [diff] [blame] | 25 | .. todo:: |
| 26 | |
| 27 | With |reclass| now in Debian, as well as installable from source, the |
| 28 | following should be checked for path consistency… |
| 29 | |
martin f. krafft | 8acd49d | 2013-08-26 21:22:25 +0200 | [diff] [blame] | 30 | #. Complete the installation steps described in the :doc:`installation section |
| 31 | <install>`. |
| 32 | |
| 33 | #. Symlink ``/usr/share/reclass/reclass-ansible`` (or wherever your distro put |
| 34 | that file), or ``/…/reclass/reclass/adapters/ansible.py`` (if running from |
| 35 | source) to ``/etc/ansible/hosts`` (or ``./hacking/hosts``). |
| 36 | |
| 37 | #. Copy the two directories ``nodes`` and ``classes`` from the example |
| 38 | subdirectory in the |reclass| checkout to ``/etc/ansible`` |
| 39 | |
| 40 | If you prefer to put those directories elsewhere, you can create |
| 41 | ``/etc/ansible/reclass-config.yml`` with contents such as:: |
| 42 | |
| 43 | storage_type: yaml_fs |
| 44 | inventory_base_uri: /srv/reclass |
| 45 | |
| 46 | Note that ``yaml_fs`` is currently the only supported ``storage_type``, and |
| 47 | it's the default if you don't set it. |
| 48 | |
| 49 | #. Check out your inventory by invoking |
| 50 | |
| 51 | :: |
| 52 | |
| 53 | $ ./hosts --list |
| 54 | |
| 55 | which should return 5 groups in JSON format, and each group has exactly |
| 56 | one member ``localhost``. |
| 57 | |
| 58 | 4. See the node information for ``localhost``:: |
| 59 | |
| 60 | $ ./hosts --host localhost |
| 61 | |
| 62 | This should print a set of keys and values, including a greeting, |
| 63 | a colour, and a sub-class called ``__reclas__``. |
| 64 | |
| 65 | 5. Execute some ansible commands, e.g.:: |
| 66 | |
| 67 | $ ansible -i hosts \* --list-hosts |
| 68 | $ ansible -i hosts \* -m ping |
| 69 | $ ansible -i hosts \* -m debug -a 'msg="${greeting}"' |
| 70 | $ ansible -i hosts \* -m setup |
| 71 | $ ansible-playbook -i hosts test.yml |
| 72 | |
| 73 | 6. You can also invoke |reclass| directly, which gives a slightly different |
| 74 | view onto the same data, i.e. before it has been adapted for Ansible:: |
| 75 | |
| 76 | $ /…/reclass/reclass.py --pretty-print --inventory |
| 77 | $ /…/reclass/reclass.py --pretty-print --nodeinfo localhost |
| 78 | |
| 79 | Or, if |reclass| is properly installed, just use the |reclass| command. |
| 80 | |
| 81 | Integration with Ansible |
| 82 | ------------------------ |
| 83 | The integration between |reclass| and Ansible is performed through an adapter, |
| 84 | and needs not be of our concern too much. |
| 85 | |
| 86 | However, Ansible has no concept of "nodes", "applications", "parameters", and |
| 87 | "classes". Therefore it is necessary to explain how those correspond to |
| 88 | Ansible. Crudely, the following mapping exists: |
| 89 | |
| 90 | ================= =============== |
| 91 | |reclass| concept Ansible concept |
| 92 | ================= =============== |
| 93 | nodes hosts |
| 94 | classes groups |
| 95 | applications playbooks |
| 96 | parameters host_vars |
| 97 | ================= =============== |
| 98 | |
| 99 | |reclass| does not provide any ``group_vars`` because of its node-centric |
| 100 | perspective. While class definitions include parameters, those are inherited |
| 101 | by the node definitions and hence become node_vars. |
| 102 | |
| 103 | |reclass| also does not provide playbooks, nor does it deal with any of the |
| 104 | related Ansible concepts, i.e. ``vars_files``, vars, tasks, handlers, roles, etc.. |
| 105 | |
| 106 | Let it be said at this point that you'll probably want to stop using |
| 107 | ``host_vars``, ``group_vars`` and ``vars_files`` altogether, and if only |
| 108 | because you should no longer need them, but also because the variable |
| 109 | precedence rules of Ansible are full of surprises, at least to me. |
| 110 | |
| 111 | |reclass|' Ansible adapter massage the |reclass| output into Ansible-usable data, |
| 112 | namely: |
| 113 | |
| 114 | - Every class in the ancestry of a node becomes a group to Ansible. This is |
| 115 | mainly useful to be able to target nodes during interactive use of |
| 116 | Ansible, e.g.:: |
| 117 | |
| 118 | $ ansible debiannode@wheezy -m command -a 'apt-get upgrade' |
| 119 | → upgrade all Debian nodes running wheezy |
| 120 | |
| 121 | $ ansible ssh.server -m command -a 'invoke-rc.d ssh restart' |
| 122 | → restart all SSH server processes |
| 123 | |
| 124 | $ ansible mailserver -m command -a 'tail -n1000 /var/log/mail.err' |
| 125 | → obtain the last 1,000 lines of all mailserver error log files |
| 126 | |
| 127 | The attentive reader might stumble over the use of singular words, whereas |
| 128 | it might make more sense to address all ``mailserver*s*`` with this tool. |
| 129 | This is convention and up to you. I prefer to think of my node as |
| 130 | a (singular) mailserver when I add ``mailserver`` to its parent classes. |
| 131 | |
| 132 | - Every entry in the list of a host's applications might well correspond to |
| 133 | an Ansible playbook. Therefore, |reclass| creates a (Ansible-)group for |
| 134 | every application, and adds ``_hosts`` to the name. This postfix can be |
| 135 | configured with a CLI option (``--applications-postfix``) or in the |
| 136 | configuration file (``applications_postfix``). |
| 137 | |
| 138 | For instance, the ssh.server class adds the ssh.server application to |
| 139 | a node's application list. Now the admin might create an Ansible playbook |
| 140 | like so:: |
| 141 | |
| 142 | - name: SSH server management |
| 143 | hosts: ssh.server_hosts ← SEE HERE |
| 144 | tasks: |
| 145 | - name: install SSH package |
| 146 | action: … |
| 147 | … |
| 148 | |
| 149 | There's a bit of redundancy in this, but unfortunately Ansible playbooks |
| 150 | hardcode the nodes to which a playbook applies. |
| 151 | |
| 152 | It's now trivial to apply this playbook across your infrastructure:: |
| 153 | |
| 154 | $ ansible-playbook ssh.server.yml |
| 155 | |
| 156 | My suggested way to use Ansible site-wide is then to create a ``site.yml`` |
| 157 | playbook that includes all the other playbooks (which shall hopefully be |
| 158 | based on Ansible roles), and then to invoke Ansible like this: |
| 159 | |
| 160 | ansible-playbook site.yml |
| 161 | |
| 162 | or, if you prefer only to reconfigure a subset of nodes, e.g. all |
| 163 | webservers:: |
| 164 | |
| 165 | $ ansible-playbook site.yml --limit webserver |
| 166 | |
| 167 | Again, if the singular word ``webserver`` puts you off, change the |
| 168 | convention as you wish. |
| 169 | |
| 170 | And if anyone comes up with a way to directly connect groups in the |
| 171 | inventory with roles, thereby making it unnecessary to write playbook |
| 172 | files (containing redundant information), please tell me! |
| 173 | |
| 174 | - Parameters corresponding to a node become ``host_vars`` for that host. |
| 175 | |
| 176 | Variable interpolation |
| 177 | ---------------------- |
| 178 | Ansible allows you to include `Jinja2`_-style variables in parameter values:: |
| 179 | |
| 180 | parameters: |
| 181 | motd: |
| 182 | greeting: Welcome to {{ ansible_fqdn }}! |
| 183 | closing: This system is part of {{ realm }} |
| 184 | dict_reference: {{ motd }} |
| 185 | |
| 186 | However, in resolving this, Ansible casts everything to a string, so in this |
| 187 | example, ``dict_reference`` would be the string-representation of the |
| 188 | dictionary under the ``motd`` key [#string_casts]_. To get at facts (such as |
| 189 | ``ansible_fqdn``), you still have to use this approach, but for pure parameter |
| 190 | references, I strongly suggest to use |reclass| interpolation instead, as it |
| 191 | supports deep references, does not clobber type information, and is more |
| 192 | efficient anyway:: |
| 193 | |
| 194 | parameters: |
| 195 | motd: |
| 196 | greeting: Welcome to {{ ansible_fqdn }}! |
| 197 | closing: This system is part of ${realm} |
| 198 | dict_reference: ${motd} |
| 199 | |
| 200 | Now you just need to specify realm somewhere. The reference can reside in |
| 201 | a parent class, while the variable is defined e.g. in the node definition. |
| 202 | |
| 203 | And as expected, ``dict_reference`` now points to a dictionary, not |
| 204 | a string-representation thereof. |
| 205 | |
| 206 | .. [#string_casts] I pointed this out to Michael Dehaan, Ansible's chief |
| 207 | developer, but he denied this behaviour. When I tried to provide further |
| 208 | insights, I found myself banned from the mailing list, apparently because |
| 209 | I dared to point out flaws. If you care, you may look at |
| 210 | https://github.com/madduck/reclass/issues/6 for more information. |
| 211 | |
| 212 | .. include:: extrefs.inc |
| 213 | .. include:: substs.inc |