Existing Ansible BIG-IP modules
Right around the time that I started at F5, I was at the pinnacle of my exposure to Ansible. So imagine my surprise when I saw BIG-IP modules in the Ansible core product! I immediately wanted to know which one of my colleagues I could go talk Ansible-fu with!
I cracked open the source code, found the names of contributors, made haste to the corporate phone book to look up where they sat, and...wait a second...
...no entries in the phone book?
...that curious look in colleagues eyes that says "I haven't the foggiest idea what you are talking about".
Then it hit me...the BIG-IP modules didn't originate at F5.
Upon further investigation, it became clear to me that, indeed, we had no skin in this game. These modules originated from two enterprising individuals who I found on DevCentral, and I would be remiss to exclude their valuable contributions to the Ansible+F5 cause, so in this post I want to highlight those contributions and bring others up-to-speed on current Ansible+BIG-IP functionality.
Credit where it is due
As far as I can tell from perusing Ansible's git logs, the existing BIG-IP modules in Ansible are the work of Matt Hite and Serge van Ginderachter. Both are DevCentral contributors, and have had a presence in the Ansible community from (at least) 2013 when their modules landed in the tool.
Among the modules that they had a hand in are
bigip_facts
bigip_node
bigip_pool
bigip_pool_member
bigip_monitor_http
bigip_monitor_tcp
Since that time, I've seen even more folks step up to the plate with Ansible modules that will be making their appearance in upcoming releases.
Well, I wanted to have a hand in this work too. I figured I had a perfect opportunity to help because,
- I had some background in Ansible
- I had access to unlimited BIG-IP technical resources
It was only a question of how to get involved.
And then, Matt posted this in a pull request (PR)...
Unfortunately I don't have GTM to smoke test this with. Can you solicit some testers on the mailing list?
And Serge followed with
So same here, not tested as no GTM gear, but an offline code review.
Bingo...I was in.
If there's one area that I figured I could have the greatest impact, it's the area of technical resources. There is BIG-IP stuff all over the place at F5, and I sit next to, or at least near, many of the people who have knowledge of the products; far more advanced knowledge than I do. Many of those same people hang out and contribute on DevCentral. Paired with my knowledge of Ansible, I figured I could help test BIG-IP related PRs to validate their functionality beyond what a code review offered.
So that's what I did (and what we're about to do).
Before you begin
If you were around for the earlier introductory article to Ansible that I posted a couple days ago, pull up a terminal to that machine. We need to install one more dependency that is common to all of the BIG-IP modules in Ansible;
bigsuds
.
pip install bigsuds
With that in place, you're ready to work with the Ansible modules.
Additionally, I am going to be using a very minimal inventory file that just includes a single BIG-IP. You will want to adjust yours for your environment, but here is mine.
[test]
big-ip01.internal
Note that the name above is available in my local DNS. If you only have an IP address to work with, you can just add that to your inventory file.
For example
[test] 192.168.10.2
Also note that I included a line called
[test]
. In Ansible this is referred to as a group. We will be revisiting this in the future as we begin orchestrating multiple BIG-IPs.
Let's dive in to some of Matt and Serge's BIG-IP modules!
bigip_facts
In Ansible, there is a pattern you often see over and over. This is the pattern of modules that provide information, and modules that change settings. For this tutorial I'll refer to them as,
modulesreader
moduleswriter
Without exception,
reader
modules are suffixed with an _facts
string. While writer
modules, are not.
So, with that in mind...pop quiz!
- If I asked you whether the
module was a reader module or a writer module, you would say...writer!bigip_pool
- If I asked you whether the
module was a reader module or a writer module, you would say...reader!bigip_pool_facts
The bigip_facts module will return to you a number of facts about the BIG-IP in question. Let's take a look.
First, my playbook.
- name: Test bigip_facts hosts: test connection: local tasks: - name: Get all of the facts from my BIG-IP bigip_facts: server: "{{ inventory_hostname }}" user: "admin" password: "admin" include: "system_info"
And now, let's just run it to see what sort of output it generates.
ansible-playbook -i hosts site.yaml -vvvv
You should be presented with information resembling the following.
root@debian:~# ansible-playbook -i hosts site.yaml -vvvv PLAY [Test bigip_facts] ******************************************************* TASK: [Get all of the facts from my BIG-IP] *********************************** ... ok: [big-ip01.internal] => {"ansible_facts": {"system_info": {"base_mac_address": "8A:83:8B:B1:AE:F7", "blade_temperature": [], "chassis_slot_information": [], "globally_unique_identifier": "8A:83:8B:B1:AE:F7", "group_id": "DefaultGroup", "hardware_information": [{"model": "Common KVM processor", "name": "cpus", "slot": 0, "type": "HARDWARE_BASE_BOARD", "versions": [{"name": "cache size", "value": "4096 KB"}, {"name": "cores", "value": "2"}, {"name": "cpu MHz", "value": "2199.994"}]}], ... "os_version": "#1 SMP Mon Aug 11 19:54:07 PDT 2014", "platform": "Z100", "product_category": "Virtual Edition", "switch_board_part_revision": null, "switch_board_serial": null, "system_name": "Linux"}, "time": {"day": 28, "hour": 22, "minute": 1, "month": 1, "second": 7, "year": 2016}, "time_zone": {"gmt_offset": -8, "is_daylight_saving_time": false, "time_zone": "PST"}, "uptime": 854}}, "changed": false} root@debian:~#
This module can output a lot of information that you can use in later tasks. Note that I just asked for the
system_info
facts, but there are a number of them that you can ask for, including
address_class
certificate
client_ssl_profile
device
device_group
interface
key
node
pool
rule
self_ip
software
system_info
traffic_group
trunk
virtual_address
virtual_server
vlan
You can include multiple types of facts by separating them with a comma. For example
- name: Test bigip_facts hosts: test connection: local tasks: - name: Get all of the facts from my BIG-IP bigip_facts: server: "{{ inventory_hostname }}" user: "admin" password: "admin" include: "system_info,software,self_ip"
Returns facts representing the
system_info
, software
, and self IPs.
You can use the generated facts in later tasks by referencing their JSON keys. For example
- name: Test bigip_facts hosts: test connection: local tasks: - name: Get all of the facts from my BIG-IP bigip_facts: server: "{{ inventory_hostname }}" user: "admin" password: "admin" include: "system_info,software,self_ip" - name: Mention the Self IP debug: msg: "I have self IP {{ self_ip['/Common/net1'].address }}" - name: Mention the software debug: msg: "I have software version {{ software[0].version }}"
And the (truncated) output
... TASK: [Mention the Self IP] *************************************************** ok: [big-ip01.internal] => { "msg": "I have self IP 10.2.2.2" } TASK: [Mention the software] ************************************************** ok: [big-ip01.internal] => { "msg": "I have software version 11.6.0" } ...
bigip_node
This module allows you to manipulate nodes in a BIG-IP. Nodes are logical objects on your BIG-IP that identify the IP address of a physical node on your network. In terms of what we can do with them with the existing BIG-IP modules, you can use this module to create nodes that you can later assign to pool members.
First, let's show you the playbook that I'm going to run.
- name: Node shenanigans hosts: test connection: local tasks: - name: Add a new node bigip_node: server: "{{ inventory_hostname }}" user: "admin" password: "admin" host: "10.2.1.1" name: "member1"
This is a simple example of creating a node in my BIG-IP. As is probably apparent if I am going to be working with pools, I would want to create many nodes. I'll hold off on that until a future example, but hopefully this clarifies the point of how to create the object that we can later assign to pool members.
Let's run it!
ansible-playbook -i hosts site.yaml
And the output we should expect to see looks like this
root@debian:~# ansible-playbook -i hosts site.yaml PLAY [Node shenanigans] ******************************************************* TASK: [Add a new node] ******************************************************** changed: [big-ip01.internal] PLAY RECAP ******************************************************************** big-ip01.internal : ok=1 changed=1 unreachable=0 failed=0 root@debian:~#
Now, just to clarify what gets dropped where, and where these nodes can be used, let's first look at the Local Traffic > Nodes screen.
Now, let's navigate over to the pool screen and try to create a new pool. On the new pool creation screen, we have the option of specifying members of that pool. If we click on Node List, well, look at that. Our new node.
bigip_pool
This module allows you to create pools on your BIG-IPs. To these pools we can later add pool members. With this and the
bigip_pool_member
module, you can control the basic load balancing functionality of the BIG-IP.
First, just to prove that I have nothing up my BIG-IP sleeve, here's my current pool list.
And now, for my playbook
- name: Create a pool hosts: test connection: local tasks: - name: Create the pool1 pool bigip_pool: server: "{{ inventory_hostname }}" user: "admin" password: "admin" name: "pool1"
With a wave of my magic wand...
root@debian:~# ansible-playbook -i hosts site.yaml PLAY [Create a pool] ******************************************************* TASK: [Create the pool1 pool] *********************************** ... changed: [big-ip01.internal] => {"changed": true} PLAY RECAP ******************************************************************** big-ip01.internal : ok=1 changed=1 unreachable=0 failed=0 root@debian:~#
...and my pool list has been changed.
The bigip_pool module has a number of other options that let you adjust settings of the pool. They are all documented on the module's page.
bigip_pool_member
With the bigip_pool_member module, you can manipulate the members of any of the pools on your BIG-IP. This module, in particular, is a crucial part of a rolling upgrade strategy that you may undertake when upgrading software on members of the pool.
Consider the following scenario.
- Remove (or disable) the member from the pool
- Upgrade the member's software (you pick the software, but let's say Apache for example)
- Verify the software upgrade
- Add (or enable) the member back to the old pool, or, add the member to a new pool
This module can be used for steps 1 and 4. Let's have a look at my playbook.
- name: Pool member manipulation hosts: test connection: local tasks: - name: Drop members out of pool1 bigip_pool_member: server: "{{ inventory_hostname }}" user: "admin" password: "admin" state: "absent" host: "{{ item }}" pool: "pool1" port: "22" with_items: - member1 - member2 - name: Intermediate processing debug: msg: "Upgrade some software" - name: More intermediate processing debug: msg: "Install some configuration" - name: Add member1 node back bigip_node: server: "{{ inventory_hostname }}" user: "admin" password: "admin" name: "{{ item }}" host: "10.2.1.1" state: "present" with_items: - member1 - name: Add member2 node back bigip_node: server: "{{ inventory_hostname }}" user: "admin" password: "admin" name: "{{ item }}" host: "10.2.1.2" state: "present" with_items: - member2 - name: Add member1 back to pool1 bigip_pool_member: server: "{{ inventory_hostname }}" user: "admin" password: "admin" host: "{{ item }}" pool: "pool1" port: "22" state: "present" with_items: - member1 - name: Add member2 to pool2 bigip_pool_member: server: "{{ inventory_hostname }}" user: "admin" password: "admin" host: "{{ item }}" pool: "pool2" port: "22" state: "present" with_items: - member2
And before we run it, let's just take a peek at my current pools.
As well as the members of pool1
And the members of pool2
Now, let's have a go at running the playbook.
ansible-playbook -i hosts site2.yaml
After it runs, you should have output that resembles something like this.
root@debian:~# ansible-playbook -i hosts site.yaml PLAY [Pool member manipulation] *********************************************** TASK: [Drop members out of pool1] ********************************************* changed: [big-ip01.internal] => (item=member1) ok: [big-ip01.internal] => (item=member2) TASK: [Intermediate processing] *********************************************** ok: [big-ip01.internal] => { "msg": "Upgrade some software" } TASK: [More intermediate processing] ****************************************** ok: [big-ip01.internal] => { "msg": "Install some configuration" } TASK: [Add member1 node back] ************************************************* changed: [big-ip01.internal] => (item=member1) TASK: [Add member2 node back] ************************************************* ok: [big-ip01.internal] => (item=member2) TASK: [Add member1 back to pool1] ********************************************* changed: [big-ip01.internal] => (item=member1) TASK: [Add member2 to pool2] ************************************************** ok: [big-ip01.internal] => (item=member2) PLAY RECAP ******************************************************************** big-ip01.internal : ok=7 changed=3 unreachable=0 failed=0 root@debian:~#
And your pool members should be different, check out the screenshots below.
The pool list
Members in pool1
Members in pool2
To Summarize
BIG-IP would not have a presence in Ansible if it were not for Matt and Serge's initiative.
Based on the work they started, I'm happy to assist with testing and contributing new modules moving forward. In my entirely too biased opinion, Ansible fits in well with the tools and products I work on. Perhaps it will also fit in well with your workflow. If we've piqued your interest, then the Ansible mailing list is a great place to ask more Ansible related questions and further solidify your understanding.
If you liked this article and want to see more like it, rate it as such. Have comments? Questions? Something else? Leave a comment below!
References
- http://docs.ansible.com/ansible/bigip_facts_module.html
- http://docs.ansible.com/ansible/bigip_node_module.html
- http://docs.ansible.com/ansible/bigip_pool_module.html
- http://docs.ansible.com/ansible/bigip_pool_member_module.html
- https://support.f5.com/kb/en-us/products/big-ip_ltm/manuals/product/ltm_configuration_guide_10_0_0/ltm_nodes.html#1188710
- https://groups.google.com/forum/#!forum/ansible-project
- mhite_60883CirrocumulusThanks for the fantastic tutorial covering all this! It's certainly been a fun challenge getting all the F5 bits and pieces to work with F5 and I can't wait to see what new levels you take this to. -- Matt
- jcshelby_261360NimbostratusExcellent tutorial and much appreciated. I have one question - I'm working with the new bigip_virtual_server module and am having some issues with getting it to work correctly. Do you have any experience with this module, and if so could I solicit your input on the issues I'm seeing? Thanks in advance for the assistance.
Great work. I am getting this error. have you seen this before ..
fatal: [192.168.234.131]: FAILED! => {"changed": false, "failed": true, "msg": "received exception: \ntraceback: Traceback (most recent call last):\n File \"/tmp/ansible_f3H4ou/ansible_module_bigip_facts.py\", line 1659, in main\n saved_active_folder = ()\n File \"/tmp/ansible_f3H4ou/ansible_module_bigip_facts.py\", line 143, in get_active_folder\n return self.api.System.Session.get_active_folder()\n File \"/usr/local/lib/python2.7/dist-packages/bigsuds.py\", line 360, in __getattr__\n client = self._client_creator('%s.%s' % (self._name, attr))\n File \"/usr/local/lib/python2.7/dist-packages/bigsuds.py\", line 170, in _create_client\n raise ConnectionError(str(e))\nConnectionError: \n"}
- Tim_RuppAltostratus
Sebastian, try adding the following to your tasks
validate_certs: "no"
it didn't like that.
The error appears to have been in '/home/smaniak/ansible/playbooks/sites.yaml': line 12, column 1, but may be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
include: "system_info" validate_certs: "no"
^ here
Got it working, I had to use
validate_certs: "false"
thanks.
- mthornton42_217Nimbostratus
Is there a way to prompt for the password, rather than storing it in the playbook?
- Tim_RuppAltostratus
@mthornton42 you can provide the values needed to connect in a number of ways.
- store them in ansible vault
- prompt for them with ansible's vars_prompt functionality http://docs.ansible.com/ansible/playbooks_prompts.html
- read them from a file with include_vars http://docs.ansible.com/ansible/include_vars_module.html
If you use vars_prompt in your playbook, just refer to the variables you set when you use the tasks. For example.
... vars_prompt: - name: "username" prompt: "what is your name?" - name: "password" prompt: "what is your password?" private: yes tasks: - name: foo bigip_virtual_server: ... user: "{{ username }}" password: "{{ password }}" ...
be aware that vars_prompt is a playbook level configuration.
- devnullNZNimbostratus
Is there already a module that will return client side & server side connection info?
I'm wanting to forceably close connections as part of a playbook that switches traffic to another F5 pair as part of a failover strategy between DC's. So far I haven't found this info being exposed in any of the ansible F5 modules...
- Tim_RuppAltostratus
@devnullNZ
Not at this time. Please file an issue for this and provide details on exactly what you're interested in and it can be added.
https://github.com/F5Networks/f5-ansible/issues
-tim