Overview
Currently the structure of Ansible is a mess and no-one seems to have a clue. The idea of this is to create a working example of how the structure can be defined, and hopefully 'automated' to create some consistency.
The Role of the Business Service
The reason that Ansible is being used is to manage a system that provides some service to the business. It is this business service that generates money to pay the bills, or is a cost sink-hole that needs to be managed. Using the business service as the focal point should simplify the project structure and remove any ambiguity. It is either a part of the solution, or not.
As an example, The Static Content Hosting business service consists of the following components:
- Web Server
- GitLab Runner
- NFS Server
Each of these components map to an ansible role:
- web_server
- gitlab_runner
- nfs_server
Replacing whitespaces with underscores (_) is recommended, as galaxy uses underscores and would convert them regardless. Keep it consistent.
As a directory listing, it would look something like:
static_content_hosting/
├── collections/
│ └── requirements.yml
├── inventory/
│ └── hosts
├── roles/
│ ├── web_server/
│ │ ├── defaults/
│ │ │ └── main.yml
│ │ ├── handlers/
│ │ │ └── main.yml
│ │ ├── meta/
│ │ │ └── main.yml
│ │ ├── tasks/
│ │ │ ├── main.yml
│ │ │ └── configure_firewall.yml
│ │ ├── templates/
│ │ │ └── nginx.conf.j2
│ │ └── vars/
│ │ └── main.yml
│ ├── gitlab_runner/
│ │ ├── defaults/
│ │ │ └── main.yml
│ │ ├── handlers/
│ │ │ └── main.yml
│ │ ├── meta/
│ │ │ └── main.yml
│ │ ├── tasks/
│ │ │ ├── main.yml
│ │ └── vars/
│ │ └── main.yml
│ └── nfs_server/
│ ├── defaults/
│ │ └── main.yml
│ ├── handlers/
│ │ └── main.yml
│ ├── meta/
│ │ └── main.yml
│ ├── tasks/
│ │ ├── main.yml
│ └── vars/
│ └── main.yml
├── group_vars/
│ ├── all.yml
│ └── web_servers.yml
│ └── gitlab_runners.yml
├── hosts
├── site.yml
└── README.md
Breaking It Down
Alot of Ansibles structures and conventions have been borrowed from other automation tools. In puppet land, this project is similar to a control repository:
Ansible | Puppet |
---|---|
site.yml | manifests/site.pp |
roles | site/roles/**.pp |
collections/requirements.yml | Puppetfile |
hosts | data/hosts/ |
group_vars | data/hostgroups/ |
A major difference is with the structure of configuration data. Puppet allows
the data/**
directory structure to be defined by the user throught the
hiera.yaml
file. However, Ansible inventory has a static structure
that is exensible via dynamic inventory (inventory/**
). Dynamic
inventory is similar to Puppet's External Node Classifier, with both being able
to provide additional information about the hosts.
Unlike the mono-repo style control repository typically used by Puppet, the business service based structure for Ansible does not share any configuration. Technically it is possible, but it would cause more problems than it would solve. Testing the configuration data of a Puppet control repository is relatively trivial, whereas testing Ansible host and group variables are configured correctly is basically impossible. Hence, Ansible projects need to be completely independent, otherwise bad things will happen.
site.yml
The site.yml
is the mapping of the server groups to the roles happen.
---
- name: Apply a common base configuration to all hosts
hosts: all
become: yes
roles:
- base
- name: Configure and deploy the web servers
hosts: web_servers
become: yes
roles:
- web_server
- name: Configure and deploy the gitlab runners
hosts: gitlab_runners
become: yes
roles:
- gitlab_runner
- name: Configure and deploy the NFS servers
hosts: nfs_servers
become: yes
roles:
- nfs_server
Web Server Role
The web_server role leaves the heavy lifting to the hccp collections nginx_plus role. In this example there is only a single included role, but additional roles could be added if needed. It is similar to a puppet role class, however puppet has the concept of roles and profiles, which helps with separation of concerns for the code.
---
- name: Install and configure Nginx
ansible.builtin.include_role:
name: hccp.nginx_plus
There are some terms in Ansible that are overloaded and can be confusing, or at
least to me. The term role is one that takes on many meanings. A
role
makes sense within the site.yml
file, as it is describing the
role of a server in a given site. But a role
being made up of other
roles, and then roles are often described as actions, verbs. Huh? I suppose
that Ansible follows the Judge, Jury and Executioner idiom. It can do whatever
it likes.
Re-using code through Collections
The collections/requirements.yml
file can pull in collections from
git,URL, or galaxy. Again, this is similar to Puppets Puppetfile
definition that contains reusable Puppet modules.
Using Ansible collections is relatively straight forward.
---
collections:
- name: https://github.com/org/repo.git
type: git
version: 1.0.0
The downside of git based collections is that the git URL may not indicate what
collection is being referenced. Instead the galaxy.yml
file contained within
the git project defines the name and version details that will be used by
Ansible when installing the collection.
Currently, Execution Environments are being ignored for standalone Ansible runs as they add complexity without any real benefit. Given the gitlab-runner can execute containers, there may already be some examples of using execution environments with GitLab.