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,

  1. I had some background in Ansible
  2. 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,

  • reader
    modules
  • writer
    modules

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
    bigip_pool
    module was a reader module or a writer module, you would say...writer!
  • If I asked you whether the
    bigip_pool_facts
    module was a reader module or a writer module, you would say...reader!

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.

  1. Remove (or disable) the member from the pool
  2. Upgrade the member's software (you pick the software, but let's say Apache for example)
  3. Verify the software upgrade
  4. 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

Updated Jun 06, 2023
Version 2.0
  • Is there any Ansible module to create client ssl profile on BigIP LTM. I am not able to find one from Ansible Documentation.

     

  • Hi Cool_Y,

    Until there is no bigip_module available you can use uri module with iControl REST API to create client ssl profile

    To create ssl profile with existing SSL certificate and SSL key

    - name: " Create SSL profile"

     uri:
    
         url: "https://mybigip/mgmt/tm/ltm/profile/client-ssl/"
         method: "POST"
         validate_certs: no
         body_format: "json"
         body:
           name: "test_client_sslprofile"
           partition: "Common"
           cert: "/Common/test_cert.ct"
           key: "/Common/test_key.key"
           username: "admin"
           password: "admin"
     delegate_to: localhost
    

    Using uri module we can create any object in F5 if the know the iControl REST API to call.

    Thanks

    Syed Nazir

  • Hi Cool_Y,

    there is another way in ansible to create or modify Client SSL Profiles via tmsh command:

    -name: "create clientssl profile {{ ClientSSLProfileName }}"

    command: > 
                tmsh create ltm profile client-ssl {{ ClientSSLProfileName }} 
                {% if DefaultCiphers is defined %} ciphers {{ DefaultCiphers }}{% endif %} 
                {% if ClientSSLOptions is defined %} options {{ ClientSSLOptions }}{% endif %} 
                {% if ClientSSLDefaultProfile is defined %} defaults-from {{ ClientSSLDefaultProfile }}{% endif %} 
                {% if SSLCertName is defined %} cert {{ SSLCertName }}{% endif %} 
                {% if SSLKeyName is defined %} key {{ SSLKeyName }}{% endif %} 
                {% if SSLChainName is defined %} chain {{ SSLChainName }}{% endif %}
    

    This seemed to be very complex, but it isn't with some explanations.

    If you want to create a Client SSL Profile, you wouln't specify all parameters (like ciphers) to it, because most parameters will be set in the Parent Profile. Every if-clause checks, if there is a value for the given paramter in the playbook and if not, this parameter/value pair wouldn' t be put to the tmsh command.

    To modify a Client SSL Profile you have to replace create with modify in the tmsh command.

    Best regards,

    Soenke

  • f51's avatar
    f51
    Icon for Cirrostratus rankCirrostratus

    Hi I am trying to use above script in my test lab but I am getting errors. Can anybody help me please ?

     

    [root@ansible playbooks]# ansible-playbook f5.yml

    [DEPRECATION WARNING]: bigip_facts is kept for backwards compatibility but usage is discouraged. The module documentation details page may explain more about this rationale.. This feature

    will be removed in a future release. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

     

    PLAY [Test bigip_facts] *********************************************************************************************************************************************************************

     

    TASK [Gathering Facts] **********************************************************************************************************************************************************************

    ok: [f5ltest01a]

     

    TASK [Get all of the facts from my BIG-IP] **************************************************************************************************************************************************

    fatal: [f5ltest01a]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'f5ltest01a' is undefined\n\nThe error appears to be in '/etc/ansible/playbooks/f5.yml': line 6, column 8, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n   - name: Get all of the facts from my BIG-IP\n    ^ here\n"}

     

    PLAY RECAP **********************************************************************************************************************************************************************************

    f5ltest01a         : ok=1  changed=0  unreachable=0  failed=1  skipped=0  rescued=0  ignored=0