Introduction

In part 1 of this series, we saw how to get hands-on with the Ansible Playbook Bundle and created the basis for APB development. This post will show you how to get into the code and enhance your APB development skills, create your own bundles, get testing, and follow best practices.

Development time!

Here is some useful advice to keep in mind:

  • Re-use the role/s
    The bootstrapped tree creates many Ansible roles that maybe could be too much to work with. Instead of that, you could create one (or more, if it makes sense) roles and orchestrate from main.yml what action/mode you are executing.
- block:
- name: "[MONGODB-APB][MAIN] Testing Deploy MongoDB"
include_tasks: mongodb.yml
when: _apb_plan_id != 'cluster'

- name: "[MONGODB-APB][MAIN] Testing Deploy MongoDB cluster"
include_tasks: mongodb_ha.yml
when: _apb_plan_id == 'cluster'

- name: "[MONGODB-APB][MAIN] Execute tests over MongoDB deployment"
include_tasks: test.yml
when: mode == 'test'

Take a look at the full sample to get more context.

  • Tracing
    At the head of main.yml put a trace around what are you doing:
- name: "[MONGODB-APB][MAIN] MongoDB"
debug:
msg:
- "Entering on Main:"
- " Mode: {{ mode }}"
- " State: {{ state | default('undefined') }}"
- " Plan: {{ _apb_plan_id }}"

Note: Keep in mind that only the _apb_plan_id and namespace variables are created by default.

- name: "[MONGODB-APB][PROVISION] APB to deploy a MongoDB application on Openshift"
hosts: localhost
gather_facts: false
connection: local
vars:
mode: 'provision'
state: 'present'
roles:
- role: ansible.kubernetes-modules
install_python_requirements: no
- role: ansibleplaybookbundle.asb-modules
- role: mongodb-apb
playbook_debug: false

At task file:

- name: "[MONGODB-APB][{{ mode | upper }}] Create ImageStream for MongoDB image"
openshift_v1_image_stream:
state: "{{ state }}"
name: "{{ app_name }}"
namespace: "{{ namespace }}"
labels:
app: "{{ service_name }}"
name: "{{ service_name }}"
lookup_policy_local: False
tags:
- name: "{{ mongodb_image_tag }}"
docker_image_repository: "{{ mongodb_image }}"
register: mongod_is

Due to the resource state pointing to a {{ state }} variable, when you call the task file with a state as absent, the deprovision will be done.

  • Use callbacks to enhance your experience with APB, checkout the sample with a top ten slower tasks. It useful for finding bottlenecks.
PLAY RECAP *********************************************************************
localhost : ok=10 changed=4 unreachable=0 failed=0
[MONGODB-APB][PROVISION] Create ImageStream for MongoDB image ---------- 40.43s
[MONGODB-APB][PROVISION] Wait for MongoDB to be ready ------------------ 18.67s
[MONGODB-APB][PROVISION] Set to present the MongoDB Service ------------- 1.45s
[MONGODB-APB][PROVISION] Set to present the MongoDB Deployment Config for Ephemeral plan --- 1.21s
[MONGODB-APB][PROVISION] Encode MongoDB Credentials --------------------- 1.02s
[MONGODB-APB][MAIN] Deploying MongoDB ----------------------------------- 0.05s
------------------------------------------------------------------------ 0.04s
[MONGODB-APB][PROVISION] Set to present MongoDB PVC for Persistent plan --- 0.04s
[MONGODB-APB][PROVISION] Set to present the MongoDB Deployment Config for Persistent plan --- 0.03s
[MONGODB-APB][MAIN] MongoDB --------------------------------------------- 0.03s

Build and Push

Now it's time to build the image that will be uploaded to the registry. We have apb build function, but to work in local, you could just use:

apb push --broker=https://asb-1338-ansible-service-broker.172.17.0.1.nip.io

Depending which tool are you using (Minishift or oc cluster) the steps are different; in Minishift, the VM has the registry built-in, hence is not the local registry, and because of that, you need to push the image against that registry. You could use this instruction to do this apb build --tag docker-registry-default.192.168.42.107.nip.io/openshift/mongodb-apb and then the push is pointing the correct broker route.

oc cluster is easier because it uses the local registry to upload the images, so that the workflow will be something like:

apb prepare
apb --debug push --broker=https://asb-1338-ansible-service-broker.172.17.0.1.nip.io
apb run --project mongodb-01

This will also execute the provision on a new project called mongodb-01. This is what's happen under the hood:

oc logs -f apb-run-provision-mongodb-apb6ncsp -n mongodb-01
+ [[ provision --extra-vars {"MONGODB_ADMIN_PASSWORD": "admin", "MONGODB_DATA_STORAGE_SIZE": 1, "MONGODB_VERSION": "3.4", "MONGODB_MEMORY_LIMIT": "512Mi", "_apb_plan_id": "ephemeral", "namespace": "mongodb-01", "MONGODB_PASSWORD": "password", "MONGODB_USER": "username", "MONGODB_IMAGE_TAG": "latest", "MONGODB_DATABASE": "sampledb", "USE_UPSTREAM_IMAGES": false} == *\s\2\i\/\a\s\s\e\m\b\l\e* ]]
+ ACTION=provision
+ shift
+ playbooks=/opt/apb/actions
+ CREDS=/var/tmp/bind-creds
+ TEST_RESULT=/var/tmp/test-result
+ whoami
+ '[' -w /etc/passwd ']'
++ id -u
+ echo 'apb:x:1000100000:0:apb user:/opt/apb:/sbin/nologin'
+ set +x
+ [[ -e /opt/apb/actions/provision.yaml ]]
+ [[ -e /opt/apb/actions/provision.yml ]]
+ ANSIBLE_ROLES_PATH=/etc/ansible/roles:/opt/ansible/roles
+ ansible-playbook /opt/apb/actions/provision.yml --extra-vars '{"MONGODB_ADMIN_PASSWORD": "admin", "MONGODB_DATA_STORAGE_SIZE": 1, "MONGODB_VERSION": "3.4", "MONGODB_MEMORY_LIMIT": "512Mi", "_apb_plan_id": "ephemeral", "namespace": "mongodb-01", "MONGODB_PASSWORD": "password", "MONGODB_USER": "username", "MONGODB_IMAGE_TAG": "latest", "MONGODB_DATABASE": "sampledb", "USE_UPSTREAM_IMAGES": false}'
...
...
...

Check out the full log here.

Extra Tasks

As a part of the development process, we need to take care of a couple of important topics.

  • Linting
  • Testing
  • CI/CD
  • Documentation
  • Packaging

Linting

For linting, first of all we have the Ansible syntax checking embedded tool that could be executed with ansible-playbook --syntax-check ... and will fail when finding a syntax error in the code; this is the most basic test that you could do.

Next level to this, we have a tool called yamllint, that will raise a warning or error if it's not yaml compliant or maybe doesn't fulfill the rules that are defined on the yamllint definition. Here you have the documentation about the rules, tool and the configuration. And here you have a functional one.

This is what it looks like:

Yamllint APB

At the top level, we have another tool called ansible-lint. This one will provide you with many rules that will tell you the quality of the code, if you are using the correct modules, or you are overusing shell or command modules, etc. OOTB comes with many rules already implemented. The best part of this tool is that you could extend easily with more rules and with a little bit of Python knowledge.

Note: We recommend using a virtualenv to keep the applications isolated.

Testing

We will cover the testing part using the command apb test executed on the repository's root folder. This command will execute the complete workflow. At first, a new project is created and inside of it a pod with the APB code. This pod executes an ansible-playbook command to provision the application in this new project.

Depending on the actions that you configure on the test playbook, it will be executed on the test phase. This phase didn't take any entry parameter, and because of this, you must prepare the field to test the APB in the right way.

The way that maintains is modular, reusable, and easy. In the Playbooks folder we have a test.yml that contains the Ansible role summoning, but instead of using that we import_tasks and include_vars inside of this file and repeat, covering all plans that we have.

...
...
roles:
- role: ansible.kubernetes-modules
install_python_requirements: no
- role: ansibleplaybookbundle.asb-modules
playbook_debug: true

post_tasks:
- name: "[TEST][EPHEMERAL] Pre-load variables for testing"
include_vars: vars/vars_ephemeral.yml

- name: "[TEST][{{ _apb_plan_id }}] Load testing tasks"
import_tasks: test_tasks.yml

- name: "[TEST][PERSISTENT] Pre-load variables for testing"
include_vars: vars/vars_persistent.yml

- name: "[TEST][{{ _apb_plan_id }}] Load testing tasks"
import_tasks: test_tasks.yml

- name: "[TEST][{{ _apb_plan_id | upper }}] Recover tests results"
include_tasks: test_tasks.yml
vars:
finish_tests: True

The included_vars will cover a plan per file, as you see in the example, we include vars_ephemeral and after that we call the tasks to be executed.

Imported task file are generic, and executes some actions in a row:

  • Create the project
  • Deploy & verify the role deployed
  • Deprovision the role
  • Delete test project

Note: Keep in mind that you need to add all the variables for every plan to be included, this is a sample of an ephemeral plan and here you have all of the plan vars. And especially relevant, you also must include the namespace, _apb_plan_id and all the rest of the parameters that your plan will need.

CI/CD

This keeps your linting and testing in action on every commit, merge, pull request. The chosen CI server is TravisCI, is easy to configure, and you could integrate it with your repository with a couple of clicks.

  • Go to https://travis-ci.org and log in or create an account.
  • It will ask to integrate with GitHub to get all the repositories.
  • Set it to enable the repository that you want to make CI processes.
  • On every commit of every branch at this repository, Travis will read the .travis.yml file (must be on the root's repo).

The stages that you create will be a mix of linting and testing, but here we don't have any way to execute the bootstrap script to raise up an OpenShift deployment. Let's start describing the steps:

  • Before install, prepare the instance with the Docker daemon up and running
  • Install step, as it says, installs the software that will be a requirement on execution time
  • Lint Stage will cover 2 tasks/tests
  • An Ansible Syntax Check
  • Yaml lint verification of apb.yml
  • The test Stage executes some actions
  • Verify that apb.yml hashed fits with the Dockerfile one
  • Build the APB image
  • Raise up an OpenShift Origin container
  • Download and copy the OC client to the Origin container
  • Start the OpenShift all-in-one cluster
  • Login as Admin and create a new project base on a previously created variable
  • Raise up a new container that executes Ansible.

This is the Travis file and here you could follow an execution

Check out the overview of the stages on one build:

TravisCI Stages APB

Note: Take care, every stage of Travis has isolated variables and could happen in different hosts.

Documentation

Usually, we create a docs folder to store all of them, and at least must contain the plan execution modes with a little description and parameters that will need to make it work with the details like this:

- **MONGODB_SERVICE_NAME**
- _Default_: 'mongodb'
- _Type_: String
- _Description_: 'Service name for MongoDB's Replica Set.'

- **MONGODB_VERSION**
- _Default_: 3.4
- _Type_: Choice|Enum
- _Choices_:
- 2.6
- 3.2
- 3.4
- _Description_: 'Version for your MongoDB Cointaner'

Regarding the execution modes, here is a sample to check out.

Sometimes it's useful to use applications that make it easier to see graphically the flow or number of hits on every task in your roles. For this purpose there is a tool called ansible-playbook-grapher and if you execute it against our APB we could see something like this:

MongoDB-APB

It's a little hacky to make it work against an APB, but this is a way to do it:

ANSIBLE_ROLES_PATH=/home/jparrill/.ansible/roles/:./roles ansible-playbook-grapher --include-role-tasks playbooks/provision.yml --extra-vars '{"namespace": "apb-test-mongodb-apb-tuwu0"}'

Part 2 Conclusion

In conclusion, with some notions of Ansible and some resources, you could create and deploy complex applications in OpenShift. You now have the tools and knowledge to make them maintainable, documented, tested, and especially relevant opensource ;)