Validating your hosting platform with Ansible + idempotency

With the most recent build (#d751b88) apnscp provisioning is fully idempotent, which is a fantastic milestone while I continue moving forward with a pre-alpha release later this month.

Idempotence is a quality that no matter how many times an operation is applied, the resultant won't change beyond its initial application. For example, if I have a light switch that should be ON and it is OFF, then I flip it ON through an ACTION; this completes the task. Revisiting the task while the light switch is ON will result in NO ACTION as the initial outcome - LIGHT SWITCH ON - has been completed. If I turn the switch OFF, however, then redo the task it will have changed from OFF to ON through the ACTION that is physically toggling its switch. If an ACTION is necessary to effect an outcome and an action alters the state, we can model it as:

LIGHT SWITCH OFF -> <ACTION> -> LIGHT SWITCH ON
LIGHT SWITCH ON -> <NO ACTION> -> LIGHT SWITCH ON

We're interested in only performing the ACTION when necessary and reporting whenever such an ACTION is performed. NO ACTION? No problem!

Automatic provisioning with Ansible

apnscp uses Ansible to predictably provision every hosting platform in the least obtrusive way instead of deploying RPMs and hoping for the best. Ensim used to do this and drove me bonkers- you never knew what was a protected system file, what was altered, and moreover when it would be altered again (or to what extent). apnscp runs a carefully curated selection of tasks that work with stock CentOS packages plus a few custom packages. Generally all packages are CentOS/EPEL stock unless necessary. For everything else, there's Ansible... oh and big warnings are affixed ;).

Running the bootstrapper

apnscp runs its bootstrapper on installation. You can run it again whenever you need to validate your install or perform a panel update; sometimes new provisioning code is delivered alongside panel code. Sometimes too, as with PHP, a new version may be released and apnscp's bootstrapper will automatically download, compile, and install the new version for you as long as system_php_version is set to 7.2, 7.1, or any other "MAJOR.MINOR" family, that is to say not 7.2.6 or 7.0.4. If no changes are detected, then true to its idempotent nature, nothing will be done.

# Update the panel...
upcp
# Run the bootstrapper
cd /usr/local/apnscp/resources/playbooks
# Report only changes
env ANSIBLE_STDOUT_CALLBACK=actionable ansible-playbook bootstrap.yml

ANSIBLE_STDOUT_CALLBACK changes the output callback to something less terse, only report changes and skip all non-essential output. Let's compare the statistics between a fresh installation and updating the installation.

Notice how changed=0 in the second iteration? That's because no changes were necessary!

Let's fall back and build PHP 7.1 instead of 7.2 by specifying --extra-vars="system_php_version=7.1"

Tada!

Injecting hooks into bootstrapper

apnscp provides a few hooks during bootstrapping:

  • preflight: before running any play
  • apnscp-bootstrapped: after admin account is created, before filesystem template populated
  • apnscp-validate-account: before setting up an account with AddDomain
  • installed: she's done!

You can add any variety of plays to these locations within roles/custom/<hook>. It should follow Ansible's recommended directory layout.

Running a specific stage

Each role in bootstrap.yml can be run individually. For example, building PHP earlier could have been limited to php/install (actual building of PHP) + apnscp/initialize-filesystem-template, which updates some of the build libraries for synthetic roots. You can build PHP extensions, whitelist yourself if your IP changes, and perform any stage of the bootstrap while overriding variables. --extra-vars always takes precedence, so in a pinch it's perfect!

Examples

More examples are provided in the playbook documentation.

Rebuilding PHP

ansible-playbook bootstrap.yml --tags=php/install,apnscp/initialize-filesystem-template --extra-vars="system_php_version=7.1"

Building a PHP extension

ansible-playbook --tags=php/install-pecl-module --extra-vars 'pecl_extensions=https://pecl.php.net/get/inotify-2.0.0.tgz' --extra-vars 'force=1' bootstrap.yml 

force bypasses extension checks and forces a rebuild irrespective, good if you change versions or just want to freshen up your PHP extensions!

Enabling headless mode

For servers tight on memory or focused on security, apnscp can also run in headless mode without a panel frontend. You're restricted to using cpcmd, AddDomain, EditDomain, and DeleteDomain helper binaries, but whatever you can accomplish in the panel you can achieve from commandline thanks to its 100% reflected API.

ansible-playbook bootstrap.yml  --tags=network/setup-firewall,apnscp/bootstrap --extra-vars="panel_headless=true"

Whitelist yourself

apnscp will whitelist the connected IP address on install. If your IP changes, easily update your IP address to avoid punitive action by apnscp's integrated firewall.

ansible-playbook --tags=fail2ban/whitelist-self bootstrap.yml