pabis.eu

Deploy SmartFox Server using Ansible - Part 4

21 February 2023

In this post we will go through the process of converting the playbook from the previous posts into a reusable role. At the last step, we will go through the process of publishing the role in Ansible Galaxy.

The finished process is available here.

Initializing a role

For the new role we will use a new directory and a new repository. In terminal type the following commands outside of your current repository with playbook (for example in ~/projects):

$ ansible-galaxy role init --init-path . ppabis.smartfox
$ cd ppabis.smartfox
$ git init

We will get a structure of files and directories needed for a role.

Converting vars to defaults

The first thing that we need to do is to convert and split the variables from vars/ into defaults and vars. The defaults are the user of the role can change whereas vars act as "constants" (although they can be overridden). In our case the separation is simple: vars/smartfox.yml will go to defaults while other files will stay in vars.

We should include descriptions of each default in the README.md.

Converting the playbook to main.yml

main.yml is the entry point of a role. Thus it is the place that is binding all the other tasks, just like a playbook. However, the syntax is different. We will convert the playbook.yml into tasks/main.yml. First, copy the contents of the playbook. Then remove - hosts: all and become: yes from the top. Remove the indentation of all the things so that all the hyphens are at the most left position. We should get something like this:

---
vars_files:
- vars/smartfox.yml
- "vars/java-{{ ansible_os_family }}.yml"
- "vars/packages-{{ ansible_os_family }}.yml"
- "vars/packages-common.yml"

handlers:
- import_tasks: handlers/smartfox.yml

tasks:
- import_tasks: tasks/prepare-environment.yml
- import_tasks: tasks/version-detect.yml
- import_tasks: tasks/download-smartfox.yml
- import_tasks: tasks/replace-java.yml
  when: ansible_architecture == "aarch64"
- import_tasks: tasks/configure-smartfox.yml
- import_tasks: tasks/install-smartfox.yml
- import_tasks: tasks/zones-and-extensions.yml

Now for each vars_files item, convert it into include_vars task. For example the first item will become: - include_vars: "vars/java-{{ ansible_os_family }}.yml". Also remove the vars_files line. vars/smartfox.yml should be removed because now it is migrated to defaults and loaded automatically by Ansible. The handlers can be deleted as well as they will be also loaded automatically - just copy handlers/smartfox.yml. The tasks line must also be deleted.

Other resources

Copy contents of templates to the new role. For files copy a default keystore that is pointed in defaults. Also include the empty directories like zones and extensions (place .gitkeep in them to be able to add them to Git).

Testing the role

I decided to test the role using Molecule. It's a tool for testing Ansible roles and playbooks. It has to be installed separately via pip. It can be also extended with plugins. For example, I will use the molecule-ec2 plugin to spin up instances on AWS and test the role there. However, writing a good test is time consuming, so we will also do some manual checks. Also using ec2 driver often requires manual intervention and retries. I won't describe too much about Molecule here as it's a complex tool with a lot of possibilities. Find it here.

To have a working Molecule system we can do it in two ways: if we don't have a role yet, we can use molecule init role to create a new role like with ansible-galaxy but with a molecule/ directory included, or if we already have a role, molecule init scenario. With scenarios we can define more tests to verify that our role is working correctly (for example by changing variables). The line below will create a new scenario named second-test with driver ec2.

$ molecule init scenario -d ec2 second-test

I configured my molecule.yml with 4 different machines to run on AWS and to cover all necessary configurations: RedHat and Debian systems + x86 and ARM.

platforms:
  - name: rocky8-arm
    image: ami-0113ad83a378af551
    instance_type: t4g.micro
    vpc_subnet_id: subnet-01234567890abcdef
    region: eu-central-1
  - name: alma8-x86
    image: ami-0b3d1809cc222924a
    instance_type: t2.micro
    [...]

To find public IPs of the instances, molecule list is not enough - go to ~/.cache/molecule/role_name/default/instance_config.yml and view the file to find public IP. For EC2 driver, molecule create requires sometimes to be repeated twice - it depends on AWS speed.

Idempotence

After creation we can run molecule converge to run the role on the instance. This will let us fix the most basic bugs about our role (for example missing files or typos). The next test is idempotence. It means that running the role twice with exactly the same configuration shouldn't report any changes - everything should be either ok or skipped. molecule idempotence will run this basic test.

$ molecule idempotence

PLAY RECAP *********************************************************************
alma8-x86                  : ok=18   changed=0    unreachable=0    failed=0    skipped=8    rescued=0    ignored=0
debian11-arm               : ok=21   changed=0    unreachable=0    failed=0    skipped=5    rescued=0    ignored=0
rocky8-arm                 : ok=20   changed=0    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0
ubuntu2004-x86             : ok=19   changed=0    unreachable=0    failed=0    skipped=7    rescued=0    ignored=0

INFO     Idempotence completed successfully.

Verify SmartFox admin credentials and IPs

Next thing is to verify if we can use our credentials defined in defaults to login to the SmartFox admin panel. We can do it by going to the site and trying. In case you are also using EC2, we need to also add a security group rule to let port 8443 in. This is defined in create.yml in variable default_security_group_rules. Run destroy and create again to ensure that everything will be set up as expected. As mentioned earlier, IP addresses of the instances can be found in the .cache directory.

By default it will show that the remote IP is not allowed. Thus we should change the variable smartfox_admin_allowed_ips in converge.yml and in a new scenario - that way we can test if allowing and blocking IPs works. Find your public IP using some online tool like https://api.ipify.org/ and add it to the variable. The converge file will look then something like this

---
- name: Converge
  hosts: all
  become: true

  vars:
    - smartfox_admin_allowed_ips:
      - "158.18.123.56"

  tasks:
    - name: "Include ppabis.smartfox"
      include_role:
        name: "ppabis.smartfox"

This is also a good place to see if there's only two changes (server.xml and restart smartfox) if we are doing it on the already converged role.

$ molecule converge

PLAY RECAP *********************************************************************
alma8-x86                  : ok=19   changed=2    unreachable=0    failed=0    skipped=8    rescued=0    ignored=0
debian11-arm               : ok=22   changed=2    unreachable=0    failed=0    skipped=5    rescued=0    ignored=0
rocky8-arm                 : ok=21   changed=2    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0
ubuntu2004-x86             : ok=20   changed=2    unreachable=0    failed=0    skipped=7    rescued=0    ignored=0

Check if web server is even started

However, if something fails somewhere in our role, we can at least check if the webserver even started and is serving 200 responses. We can do it in check.yml file using uri module of Ansible.

---
- name: Verify
  hosts: all
  gather_facts: false

  tasks:
  - name: Get webserver response
    uri:
      url: https://localhost:8443/admin
      validate_certs: false
    register: result

  - name: Assert that status code is 200
    assert:
      that:
        - result.status == 200

There's a lot that can be done to test the role. We will not cover every possible scenario here, but during development, it's worth making tests for the future Ansible role. To speed up the process, we can use Docker or Vagrant drivers as they will be faster to create and destroy. We can also use semi-ready made AMI images in EC2 using snapshots or tools like Packer that will already have many steps satisfied. But the scenario of converging the role to a clean OS is a good practice before pushing out an update.

Meta file

The file we will edit is meta/main.yml. This file contains metadata about the role such as author, license, dependencies, supported platforms and so on. The first fields are quite self-explanatory. For the list of platforms we will follow the link in the comment. The search for the platform can be tricky. For example Ubuntu distros are only listed as codenames. Alma and Rocky are not listed, instead we find EL. For starters, I will add the following platforms:

platforms:
  - name: Debian
    versions:
      - bullseye
  - name: EL
    versions:
      - 8
  - name: Ubuntu
    versions:
      - focal

For now we don't use any dependencies but this might change in the future (for example we might want to use common Java installation role or open firewall ports).

Publishing a role on Ansible Galaxy is simple. Just head to https://galaxy.ansible.com/ and authenticate with GitHub. Then click on the My Content button and then New Role. Allow access to your public repositories on GitHub and select repository to use as a role.

Ansible Galaxy

To synchronize changes in the repository with the Galaxy, go to My Content and click on the Import button next to the role.

Hope that series of these articles will help you to create your own Ansible role that you will be able to share with the community.