How to use the open source Ansible Tower (AWX)

Install AWX

AWX is a GUI tool for Ansible. In this article we will deploy AWX on a Kubernetes cluster. We also have GitLab-CE to store playbooks and manage the version.

The first step is to clone AWX:

git clone https://github.com/ansible/awx.git

Then you need to complete the inventory file. AWX will be deployed with Ansible. You also need to have Ansible, Helm and kubectl installed.

The installation is explained here: https://github.com/ansible/awx/blob/devel/INSTALL.md

You need to modify some variables to describe which K8s cluster you will use:

  • c01-m1 is the kubernetes master node
  • kubernetes-admin@kubenetes is the context name
c01-m1 ansible_python_interpreter="/usr/bin/env python3"
[..]
kubernetes_context=kubernetes-admin@kubernetes
kubernetes_namespace=awx
kubernetes_web_svc_type=LoadBalancer

Now you will be able to deploy AWX on your cluster with Ansible :

ansible-playbook -i inventory install.yml

Check your deployment:

kubectl get svc,pod,pvc -n awx

The default credential is admin / password.

GitLab-CE

Now you need a Git repository with one playbook.

You can easily deploy Gitlab-ce with Docker inside Kubernetes.

Example:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitlab-ce
  labels:
    app: gitlab-ce
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gitlab-ce
  template:
    metadata:
      labels:
        app: gitlab-ce
    spec:
      containers:
      - name: gitlab-ce
        image: gitlab/gitlab-ce
        ports:
        - containerPort: 22
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: svc-gitlab-ce
  labels:
    app: gitlab-ce
spec:
  ports:
  # make the service available on this port
  - port: 22
    targetPort: 22
    protocol: TCP
    name: ssh
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http
  selector:
    # apply this service to the pod with the label app: mysql
    app: gitlab-ce
  type: LoadBalancer

Now we create on project with one playbook named : query_tenant.yml

The playbook : query_tenant.yml.

This is a very basic playbook with three tasks:

  • The first task is just to verify and understand the parameter push by AWX
  • The second send a query to get the tenant list in the ACI fabric
  • The latest is to display the result.

This playbook can be write by a developer and push on Gitlab when it works. Then this playbook will be used in AWX.

The inventory file is used by the developer. Then the credential and inventory will be configured in AWX.

$ ansible-playbook -i inventory query_tenant.yml
PLAY [Play Query] ****
TASK [Gathering Facts] *
ok: [10.202.0.1]
TASK [debug - Display Credential used by AWX] ****
ok: [10.202.0.1] => {
"msg": "10.202.0.1 - admin - cisco1234"
}
TASK [Query tenant] **
ok: [10.202.0.1]
TASK [Display Tenant]
ok: [10.202.0.1] => (item={u'fvTenant': {u'attributes': {u'dn': u'uni/tn-infra', u'status': u'', u'ownerKey': u'', u'uid': u'0', u'descr': u'', u'extMngdBy': u'', u'annotation': u'', u'lcOwn': u'local', u'monPolDn': u'uni/tn-common/monepg-default', u'modTs': u'2019-11-13T18:44:22.536+00:00', u'ownerTag': u'', u'childAction': u'', u'nameAlias': u'', u'name': u'infra'}}}) => {
"msg": "Tenant Name: infra"
}
ok: [10.202.0.1] => (item={u'fvTenant': {u'attributes': {u'dn': u'uni/tn-mgmt', u'status': u'', u'ownerKey': u'', u'uid': u'0', u'descr': u'test', u'extMngdBy': u'', u'annotation': u'', u'lcOwn': u'local', u'monPolDn': u'uni/tn-common/monepg-default', u'modTs': u'2020-07-09T16:15:03.963+00:00', u'ownerTag': u'', u'childAction': u'', u'nameAlias': u'', u'name': u'mgmt'}}}) => {
"msg": "Tenant Name: mgmt"
}
ok: [10.202.0.1] => (item={u'fvTenant': {u'attributes': {u'dn': u'uni/tn-common', u'status': u'', u'ownerKey': u'', u'uid': u'0', u'descr': u'', u'extMngdBy': u'msc', u'annotation': u'orchestrator:msc', u'lcOwn': u'local', u'monPolDn': u'uni/tn-common/monepg-default', u'modTs': u'2019-11-13T19:45:10.901+00:00', u'ownerTag': u'', u'childAction': u'', u'nameAlias': u'', u'name': u'common'}}}) => {
"msg": "Tenant Name: common"
}
[..]
PLAY RECAP ***
10.202.0.1 : ok=4 changed=0 unreachable=0 failed=0

Configure AWX

Login to AWX

By default, it’s admin / password

Create a credential

Here we will create a new credential. With Cisco ACI, we will use the credential type: Machine.

We will be able to use it with the following parameter in the playbook:

ansible_username
ansible_password

Create a project

Now we create a project. The project will point to a Gitlab repository. We have also selected the option : “Update Revision on launch”. This option permits to synchronize Gitlab and get the last version.

Create an inventory

In this step we will create an inventory, which contains a Group: APIC with one host : 10.202.0.1 (the ACI controller)

Create a job

Now we need to create one job to play the playbook in GitLab with the inventory and credential in AWX.

You need to enter :

  • A name
  • Job type : Run
  • Inventory, where you select the previous inventory
  • Project: Just a name
  • Playbook, where you select the playbook from Git. If you see nothing, the playbook is probably not good.
  • Credentials, where you select your credential

If everything is good, you can save your Template and launch it.

Play template

After click on the launch button, you will see the following window.

Start a new DevOps journey with Docker and automate ACI

If you want to test the automation with Cisco ACI, you can use the following container.

docker pull zednetwork/aci-dev:latest

root@docker1:~/aci-dev# docker images zednetwork/aci-dev
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
zednetwork/aci-dev   latest              b1c09a7c66f0        About an hour ago   1.31GB

I preinstalled Ansible 2.9.11, the ACI SDK (COBRA) in version 4.2(4) and ARYA.

You can run directly the container with the command:

docker run -it <Image ID> bash

or start it with docker-compose command :

docker-compose up -d 

Docker-compose file :

---
version: "3"

services:
  aci-dev:
    image: zednetwork/aci-dev:latest
    stdin_open: true

and connect to the container :

root@docker1:~/aci-dev# docker-compose ps
      Name          Command   State   Ports
-------------------------------------------
aci-dev_aci-dev_1   bash      Up

root@docker1:~/aci-dev# docker exec -it  aci-dev_aci-dev_1 bash

Packages version

root@3a93719ed29b:~# ansible --version
/usr/local/lib/python2.7/dist-packages/cryptography/__init__.py:39: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in a future release.
  CryptographyDeprecationWarning,
ansible 2.9.11
  config file = None
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.16 (default, Oct 10 2019, 22:02:15) [GCC 8.3.0]
root@3a93719ed29b:~# pip list
Package      Version
------------ ---------
acicobra     4.2-4i
acimodel     4.2-4i
ansible      2.9.11
arya         1.1.5
certifi      2020.6.20
cffi         1.14.1
chardet      3.0.4
cryptography 3.0
enum34       1.1.10
et-xmlfile   1.0.1
future       0.18.2
idna         2.10
ipaddress    1.0.23
jdcal        1.4.1
Jinja2       2.11.2
MarkupSafe   1.1.1
openpyxl     2.6.4
pip          18.1
ply          3.11
prettytable  0.7.2
pyaml        20.4.0
pycparser    2.20
PyYAML       5.3.1
requests     2.24.0
setuptools   44.1.1
six          1.15.0
urllib3      1.25.10

Then you can copy and paste one script and runs it.

Example with the script (https://zed-network.fr/?p=511) :

root@40a189762958:~/cobra# pwd
/root/cobra

root@40a189762958:~/cobra# ls
getEP.py

root@40a189762958:~/cobra# python getEP.py
MAC: FA:16:3E:9A:35:B7 | IP: 10.0.0.1 | Encaps: vlan-489
MAC: FA:16:3E:BB:B4:BE | IP: 42.0.0.1 | Encaps: vlan-488
[..]

How to Gather your Endpoints with COBRA on ACI

Gather EPs

The following script will help to gather all Endpoints easily with the python ACI sdk : COBRA.

This script will send an REST API request and return the Endpoints (MAC, IP and vlan encaps)

(cobra) root@341ad8347e20:~# cat getEps.py
from cobra.mit.access import MoDirectory
from cobra.mit.session import LoginSession
from cobra.mit.request import ConfigRequest
from cobra.mit.access import ClassQuery

import urllib3
urllib3.disable_warnings()

uri = 'https://[APIC]:[Port]'
user = 'admin'
pw = 'cisco01234'

ls = LoginSession(uri, user, pw)
md = MoDirectory(ls)
md.login()
# Use the connected moDir queries and configuration...

cq = ClassQuery('fvCEp')
cq.subtitle = 'full'
objlist = md.query(cq)

for mo in objlist:
    print "MAC: " + mo.mac + " | " + "IP: " + mo.ip + " | " + "Encaps: " + mo.encap

md.logout()

(cobra) root@341ad8347e20:~# python getEps.py
[..]
MAC: 00:50:56:B6:96:06 | IP: 10.2.80.67 | Encaps: vlan-3967
MAC: 00:50:56:B6:E2:41 | IP: 10.2.80.71 | Encaps: vlan-3967
MAC: 00:50:56:B6:AA:2A | IP: 10.2.80.73 | Encaps: vlan-3967
[..]

Convert your code easily with APIC Rest Python Adapter (arya)

Arya is a tool to translate an XML or JSON to Python. Arya will convert your input and use the Cisco sdk COBRA.

Generate the code with arya :

arya -f tenant.xml
 !/usr/bin/env python
 '''
 Autogenerated code using arya
 Original Object Document Input:
 
 
 '''
 raise RuntimeError('Please review the auto generated code before ' +
                     'executing the output. Some placeholders will ' +
                     'need to be changed')
 list of packages that should be imported for this code to work
 import cobra.mit.access
 import cobra.mit.naming
 import cobra.mit.request
 import cobra.mit.session
 import cobra.model.fv
 import cobra.model.vns
 from cobra.internal.codec.xmlcodec import toXMLStr
 log into an APIC and create a directory object
 ls = cobra.mit.session.LoginSession('https://1.1.1.1', 'admin', 'password')
 md = cobra.mit.access.MoDirectory(ls)
 md.login()
 the top level object on which operations will be made
 Confirm the dn below is for your top dn
 topDn = cobra.mit.naming.Dn.fromString('uni/tn-aaaaaaaa-tn')
 topParentDn = topDn.getParent()
 topMo = md.lookupByDn(topParentDn)
 build the request using cobra syntax
 fvTenant = cobra.model.fv.Tenant(topMo, ownerKey='', name='aaaaaaaa-tn', descr='', nameAlias='', ownerTag='')
 vnsSvcCont = cobra.model.vns.SvcCont(fvTenant)
 fvRsTenantMonPol = cobra.model.fv.RsTenantMonPol(fvTenant, tnMonEPGPolName='')
 commit the generated code to APIC
 print toXMLStr(topMo)
 c = cobra.mit.request.ConfigRequest()
 c.addMo(topMo)
 md.commit(c)

How to program Cisco ACI with Ansible and Docker

Ansible guide : https://docs.ansible.com/ansible/devel/scenario_guides/guide_aci.html

I create a docker container with ansible, python and the demo from github.

git clone https://github.com/CiscoDevNet/aci-learning-labs-code-samples 
cd aci-learning-labs-code-samples 

docker image with ansible and python:

docker pull zednetwork/aci-ansible2-4

New version with ansible 2.8.2 using debian 10.

docker pull zednetwork/aci-ansible.2-8-2

Docker Compose example:

version: "3" 
services:
  ansible:
    image: zednetwork/aci-ansible2-4
    tty: true
    stdin_open: true

Start the container and connect to it:

docker-compose up -d 
Creating network "aci-ansible_default" with the default driver

Pulling ansible (zednetwork/aci-ansible2-4:)…

latest: Pulling from zednetwork/aci-ansible2-4

22dbe790f715: Downloading [>                                                  ]  465.6kB/45.34 MBf88405a685: Pulling fs layer

22dbe790f715: Downloading [=>                                                 <..>
22dbe790f715: Pull complete
3bf88405a685: Pull complete
Creating aci-ansible_ansible_1 … done

Check container

# docker images
 REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
 zednetwork/aci-ansible2-4   latest              ff17ed37f691        34 minutes ago      659MB

# docker ps
 CONTAINER ID        IMAGE                       COMMAND             CREATED              STATUS              PORTS               NAMES
 53993071ffa9        zednetwork/aci-ansible2-4   "bash"              About a minute ago   Up About a minute                       aci-ansible_ansible_1

Connect to the container. Use the Container ID above.

# docker exec -it 53993071ffa9 /bin/bash
root@53993071ffa9:/#

This container already contains an example from devnet.cisco.com ( https://developer.cisco.com/docs/aci/#ansible). This example uses a public ACI Fabric.

We can use the first playbook to create a tenant on the ACI Fabric. The fabric credential is on the inventory file.

root@53993071ffa9:~/aci_ansible_learning_labs_code_samples/intro_module# cat inventory
 [apic:vars]
 username=admin
 password=ciscopsdt
 ansible_python_interpreter="/usr/bin/env python"
 [apic]
 sandboxapicdc.cisco.com

You can connect directly to the fabric and verify if your tenant is present. https://sandboxapicdc.cisco.com/

root@53993071ffa9:~/aci_ansible_learning_labs_code_samples/intro_module# ansible-playbook -i inventory 01_aci_tenant_pb.yml
 What would you like to name your Tenant?: MyFirstTenant-tn
 PLAY [ENSURE APPLICATION CONFIGURATION EXISTS] 
 TASK [ENSURE APPLICATIONS TENANT EXISTS] 
 changed: [sandboxapicdc.cisco.com]
 PLAY RECAP 
 sandboxapicdc.cisco.com    : ok=1    changed=1    unreachable=0    failed=0

Go to ACI > Tenants

You can delete your tenant with another playbook

root@53993071ffa9:~/aci_ansible_learning_labs_code_samples/intro_module# ansible-playbook -i inventory 01-1_aci_tenant_pb.yml
 What would you like to name your Tenant?: MyFirstTenant-tn
 PLAY [ENSURE APPLICATION CONFIGURATION EXISTS] 
 TASK [ENSURE APPLICATIONS TENANT EXISTS] 
 changed: [sandboxapicdc.cisco.com]
 PLAY RECAP 
 sandboxapicdc.cisco.com    : ok=1    changed=1    unreachable=0    failed=0

Other example to list all tenants:

# cat listTenants.yml
---
- name: ENSURE APPLICATION CONFIGURATION EXISTS
  hosts: apic
  connection: local
  gather_facts: False
  
  tasks:

    - name: List all tenants
        aci_tenant:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        state: "query"
      validate_certs: False 

# ansible-playbook -i inventory listTenants.yml -vvv