Introduction

Ansible is a radically simple IT automation tool. It handles configuration management, application deployment, cloud provisioning, ad-hoc task execution, network automation, and multi-node orchestration. Ansible makes complex changes like zero-downtime rolling updates with load balancers easy.

VMware has Ansible modules for NSX-T and NSX-T Advanced Load balancer (AVI Networks)
This blog assumes you have some basic knowledge of Ansible and NSX-T. I will not go through the whole process of installing Ansible and NSX-T

This is part 2, where I will cover NSX-T Ansible examples.
Part 1 Covers basic configuration and AVI Ansible examples. (can be found here and here)
Part 3 will cover putting it all together.

I am using NSX-T 3.1.2 and AVI 20.1.5.
For Ansible I am using Linux (Ubuntu 20.04 LTS)

The goal of this blog

The goal of this blog is to show an example of how easy it is to use Ansible with NSX-T.
I am only doing a simple task with Ansible to show you how easy it is.

Installation of Ansible roles/collections

Please refer to part 1 (can be found here and here)


Logical overview

Let me describe how I have set up NSX-T.
The setup is pretty straightforward. T0 router is preconfigured with ECMP and BGP to the external routers (This can also be done with ansible if you want, but I will not cover this in this post.)
T1 with T1-SR with four segments connected to it.

Logical overview

Basic Setup

I will describe some small basics in what names and subnets I am using.
I am using the following networks:

Network Name (Segment)Ip subnetT1 routerDescription/Security rules
Web-Segment10.4.203.0/27 t1-poc Any to tcp port 80
App-Segment10.4.203.32/27 t1-poc web to app tcp port 8080
poc-vip-segment10.4.203.96/27 t1-pocAVI SE will use this segment (in Part 3)
DB-Segment10.4.203.64/27t1-poc app to db service mysql

Lets test Ansible

Let the fun begin 🙂
In the examples I am using I will create:
– T1
– Segments
– Security groups (membership is done with tags.)
– Security rules
I am using a var file with all the definitions I need. The file is named ‘build_topology_vars.yml’

{
    "nsx_manager": "10.0.27.31",
    "nsx_username": "admin",
    "nsx_password": "VMware1!VMware1!",
    "validate_certs": "false",

    "state": "present",

    "tier1_gateways": [
      {
        "display_name": "t1-poc",
        "tier0_display_name": "bf-poc-nxt-tier0",
        "route_advertisement_types": ["TIER1_CONNECTED"],
        "failover_mode": "PREEMPTIVE",
        "tags": [
          {
            "tag": "ansible",
            "scope": "demo"
          }
        ],
         "locale_services": [
          {
            "state": "present",
            "id": "t1-poc-sr",
            "route_redistribution_config": {
              "redistribution_rules": [
                "route_redistribution_types": ["TIER1_CONNECTED"]
              ]
            },
            "edge_cluster_info": {
              "edge_cluster_display_name": "Poc-Edge-T0-only-cluster",
            },
            "preferred_edge_nodes_info": [
              {
                "edge_cluster_display_name": "Poc-Edge-T0-only-cluster",
                "edge_node_display_name": "bf-poc-nxtedge01"
              },
              {
                "edge_cluster_display_name": "Poc-Edge-T0-only-cluster",
                "edge_node_display_name": "bf-poc-nxtedge02"
              }
            ],
          }
        ]
      }
    ],

    "segments": [
        {
            "display_name": "Web-Segment",
            "tier1_display_name": "t1-poc",
            "tz": "TZ-Poc-Overlay",
            "domain_name": "mylab.net",
            "subnets": [
                {
                  "gateway_address": "10.4.203.1/27"
                }
            ],
            "tags": [
                {
                  "tag": "ansible",
                  "scope": "demo"
                },
                {
                  "tag": "web",
                  "scope": "east"
                }
            ]
        },
        {
            "display_name": "App-Segment",
            "tier1_display_name": "t1-poc",
            "tz": "TZ-Poc-Overlay",
            "domain_name": "mylab.net",
            "subnets": [
                {
                  "gateway_address": "10.4.203.33/27"
                }
            ],
            "tags": [
                {
                  "tag": "ansible",
                  "scope": "demo"
                },
                {
                  "tag": "app",
                  "scope": "east"
                }
            ]
        },
        {
            "display_name": "poc-vip-segment",
            "tier1_display_name": "t1-poc",
            "tz": "TZ-Poc-Overlay",
            "domain_name": "mylab.net",
            "subnets": [
                {
                  "gateway_address": "10.4.203.97/27"
                }
            ],
            "tags": [
                {
                  "tag": "ansible",
                  "scope": "demo"
                },
                {
                  "tag": "app",
                  "scope": "east"
                }
            ]
        },
        {
            "display_name": "DB-Segment",
            "tier1_display_name": "t1-poc",
            "tz": "TZ-Poc-Overlay",
            "domain_name": "mylab.net",
            "subnets": [
                {
                  "gateway_address": "10.4.203.65/27"
                }
            ],
            "tags": [
                {
                  "tag": "ansible",
                  "scope": "demo"
                },
                {
                  "tag": "db",
                  "scope": "east"
                }
            ]
        }
    ],

# Note: 'group' is a reserved key. Cant use it here.
    "mygroups": [
        {
            "display_name": "web-VMs",
            "domain_id": "default",
            "expression": [
                {
                    "member_type": "VirtualMachine",
                    "value": "web",
                    "key": "Tag",
                    "operator": "EQUALS",
                    "resource_type": "Condition"
                }
            ],
            "tags": [
                {
                  "tag": "ansible",
                  "scope": "demo"
                },
                {
                  "tag": "web",
                  "scope": "east"
                }
            ]
        },
        {
            "display_name": "app-VMs",
            "domain_id": "default",
            "expression": [
                {
                    "member_type": "VirtualMachine",
                    "value": "app",
                    "key": "Tag",
                    "operator": "EQUALS",
                    "resource_type": "Condition"
                }
            ],
            "tags": [
                {
                  "tag": "ansible",
                  "scope": "demo"
                },
                {
                  "tag": "app",
                  "scope": "east"
                }
            ]
        },
        {
            "display_name": "db-VMs",
            "domain_id": "default",
            "expression": [
                {
                    "member_type": "VirtualMachine",
                    "value": "db",
                    "key": "Tag",
                    "operator": "EQUALS",
                    "resource_type": "Condition"
                }
            ],
            "tags": [
                {
                  "tag": "ansible",
                  "scope": "demo"
                },
                {
                  "tag": "db",
                  "scope": "east"
                }
            ]
        }
    ],

    "security_policies" : [
        {
            "display_name": "world-to-web",
            "domain_id": "default",
            "category": "Application",
            "rules": [
                {
                    "display_name": "http",
                    "description": "Rule for http port",
                    "sequence_number": 1,
                    "source_groups": [
                            "any"
                        ],
                    "destination_groups": [
                            "/infra/domains/default/groups/web-VMs"
                        ],
                    "services": [
                            "/infra/services/HTTP"
                        ],
                    "action": "ALLOW",
                    "scope": [
                           "/infra/domains/default/groups/web-VMs"
                        ],
                },
                {
                    "display_name": "Catch-All",
                    "description": "Catch All rule",
                    "sequence_number": 2,
                    "source_groups": [
                            "any"
                        ],
                    "destination_groups": [
                            "/infra/domains/default/groups/web-VMs"
                        ],
                    "services": [
                            "any"
                        ],
                    "action": "DROP",
                    "scope": [
                           "/infra/domains/default/groups/web-VMs"
                        ],
                },
            ],
            "tags": [
                {
                    "tag": "ansible",
                    "scope": "demo"
                }
            ]
        },
        {
            "display_name": "web-to-app",
            "domain_id": "default",
            "category": "Application",
            "rules": [
                {
                    "display_name": "app-port",
                    "description": "Rule for Application port",
                    "sequence_number": 1,
                    "source_groups": [
                            "/infra/domains/default/groups/web-VMs"
                        ],
                    "destination_groups": [
                            "/infra/domains/default/groups/app-VMs"
                        ],
                    "services": [
                            "any"
                        ],
                    "service_entries": [
                            {
                            "l4_protocol": "TCP",
                            "destination_ports": [
                                    "8080"
                            ],
                            "resource_type": "L4PortSetServiceEntry",
                        },
                        ],
                    "action": "ALLOW",
                    "scope": [
                           "/infra/domains/default/groups/app-VMs",
                           "/infra/domains/default/groups/web-VMs"
                        ],
                },
                {
                    "display_name": "Catch-All",
                    "description": "Catch All rule",
                    "sequence_number": 2,
                    "source_groups": [
                            "any"
                        ],
                    "destination_groups": [
                            "/infra/domains/default/groups/app-VMs"
                        ],
                    "services": [
                            "any"
                        ],
                    "action": "DROP",
                    "scope": [
                           "/infra/domains/default/groups/app-VMs",
                        ],
                },
            ],
            "tags": [
                {
                    "tag": "ansible",
                    "scope": "demo"
                }
            ]
        },
        {
            "display_name": "app-to-db",
            "domain_id": "default",
            "category": "Application",
            "rules": [
                {
                    "display_name": "MySQL",
                    "description": "Rule for Application port",
                    "sequence_number": 1,
                    "source_groups": [
                            "/infra/domains/default/groups/app-VMs"
                        ],
                    "destination_groups": [
                            "/infra/domains/default/groups/db-VMs"
                        ],
                    "services": [
                            "/infra/services/MySQL"
                        ],
                    "action": "ALLOW",
                    "scope": [
                           "/infra/domains/default/groups/db-VMs",
                           "/infra/domains/default/groups/app-VMs"
                        ],
                },
                {
                    "display_name": "Catch-All",
                    "description": "Catch All rule",
                    "sequence_number": 2,
                    "source_groups": [
                            "any"
                        ],
                    "destination_groups": [
                            "/infra/domains/default/groups/db-VMs"
                        ],
                    "services": [
                            "any"
                        ],
                    "action": "DROP",
                    "scope": [
                           "/infra/domains/default/groups/db-VMs",
                        ],
                },
            ],
            "tags": [
                {
                    "tag": "ansible",
                    "scope": "demo"
                }
            ]
        }
    ]
}

Creating T1

Ansible code to create the T1 and connect it to the T0. The file is named ’02_create_t1_gateway.yml’

---
- hosts: localhost
  become: no
  vars_files:
    - build_topology_vars.yml
  collections:
    - vmware.ansible_for_nsxt
  tasks:
    - name: Modify Tier1
      nsxt_policy_tier1:
        hostname: "{{ nsx_manager }}"
        username: "{{ nsx_username }}"
        password: "{{ nsx_password }}"
        validate_certs: "{{ validate_certs }}"
        state: "{{ state }}"
        failover_mode: "{{ item.failover_mode }}"
        display_name: "{{ item.display_name }}"
        tier0_display_name: "{{ item.tier0_display_name }}"
        route_advertisement_types: "{{ item.route_advertisement_types }}"
        locale_services: "{{ item.locale_services }}"
        tags: "{{ item.tags }}"
      with_items:
        - "{{ tier1_gateways }}"

Creating segments

Ansible code for creating the segments. The file is named ’03_create_segments.yml’

---
- hosts: localhost
  become: no
  vars_files:
    - build_topology_vars.yml
  collections:
    - vmware.ansible_for_nsxt
  tasks:
    - name: Modify Segment
      nsxt_policy_segment:
        hostname: "{{ nsx_manager }}"
        username: "{{ nsx_username }}"
        password: "{{ nsx_password }}"
        state: "{{ state }}"
        validate_certs: "{{ validate_certs }}"
        display_name: "{{ item.display_name }}"
        tier1_id: "{{ item.tier1_display_name }}"
        domain_name: "{{ item.domain_name }}"
        transport_zone_display_name: "{{ item.tz }}"
        subnets: "{{ item.subnets }}"
        tags: "{{ item.tags }}"
      with_items:
        - "{{ segments }}"

Creating groups

Group membership is done with tags

"member_type": "VirtualMachine",
"value": "web",
"key": "Tag",
"operator": "EQUALS",
"resource_type": "Condition"

Ansible code for creating the security group. The file is ’04_create_groups.yml’

---
- hosts: localhost
  become: no
  vars_files:
    - build_topology_vars.yml
  collections:
    - vmware.ansible_for_nsxt
  tasks:
    - name: Modify Groups
      nsxt_policy_group:
        hostname: "{{ nsx_manager }}"
        username: "{{ nsx_username }}"
        password: "{{ nsx_password }}"
        state: "{{ state }}"
        validate_certs: "{{ validate_certs }}"
        domain_id: "{{ item.domain_id }}"
        display_name: "{{ item.display_name }}"
        expression: "{{ item.expression }}"
        tags: "{{ item.tags }}"
      with_items:
        - "{{ mygroups }}"

Creating security rules

Ansible code for creating the security rules. The file is ’05_create_security_policy.yml’

---
- hosts: localhost
  become: no
  vars_files:
    - build_topology_vars.yml
  collections:
    - vmware.ansible_for_nsxt
  tasks:
    - name: Modify Security Policy
      nsxt_policy_security_policy:
        hostname: "{{ nsx_manager }}"
        username: "{{ nsx_username }}"
        password: "{{ nsx_password }}"
        validate_certs: "{{ validate_certs }}"
        state: "{{ state }}"
        display_name: "{{ item.display_name }}"
        domain_id: "{{ item.domain_id }}"
        category: "{{ item.category }}"
        rules: "{{ item.rules }}"
        tags: "{{ item.tags }}"
      with_items:
        - "{{ security_policies }}"

Running the Ansible playbooks

Running the code:

$ ansible-playbook 02_create_t1_gateway.yml 03_create_segments.yml 04_create_groups.yml 05_create_security_policy.yml

PLAY [localhost] *********************************************************************************

TASK [Gathering Facts] ***************************************************************************
ok: [localhost]

TASK [Modify Tier1] ******************************************************************************
changed: [localhost] => (item={'display_name': 't1-poc', 'tier0_display_name': 'bf-poc-nxt-tier0', 'route_advertisement_types': ['TIER1_CONNECTED'], 'failover_mode': 'PREEMPTIVE', 'tags': [{'tag': 'ansible', 'scope': 'demo'}], 'locale_services': [{'state': 'present', 'id': 't1-poc-sr', 'route_redistribution_config': {'redistribution_rules': [{'route_redistribution_types': ['TIER1_CONNECTED']}]}, 'edge_cluster_info': {'edge_cluster_display_name': 'Poc-Edge-T0-only-cluster'}, 'preferred_edge_nodes_info': [{'edge_cluster_display_name': 'Poc-Edge-T0-only-cluster', 'edge_node_display_name': 'bf-poc-nxtedge01'}, {'edge_cluster_display_name': 'Poc-Edge-T0-only-cluster', 'edge_node_display_name': 'bf-poc-nxtedge02'}]}]})

PLAY RECAP ***************************************************************************************
localhost : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0


PLAY [localhost] *********************************************************************************

TASK [Gathering Facts] ***************************************************************************
ok: [localhost]

TASK [Modify Segment] ****************************************************************************
changed: [localhost] => (item={'display_name': 'Web-Segment', 'tier1_display_name': 't1-poc', 'tz': 'TZ-Poc-Overlay', 'domain_name': 'mylab.net', 'subnets': [{'gateway_address': '10.4.203.1/27'}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}, {'tag': 'web', 'scope': 'east'}]})
changed: [localhost] => (item={'display_name': 'App-Segment', 'tier1_display_name': 't1-poc', 'tz': 'TZ-Poc-Overlay', 'domain_name': 'mylab.net', 'subnets': [{'gateway_address': '10.4.203.33/27'}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}, {'tag': 'app', 'scope': 'east'}]})
changed: [localhost] => (item={'display_name': 'poc-vip-segment', 'tier1_display_name': 't1-poc', 'tz': 'TZ-Poc-Overlay', 'domain_name': 'mylab.net', 'subnets': [{'gateway_address': '10.4.203.97/27'}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}, {'tag': 'app', 'scope': 'east'}]})
changed: [localhost] => (item={'display_name': 'DB-Segment', 'tier1_display_name': 't1-poc', 'tz': 'TZ-Poc-Overlay', 'domain_name': 'mylab.net', 'subnets': [{'gateway_address': '10.4.203.65/27'}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}, {'tag': 'db', 'scope': 'east'}]})

PLAY RECAP **************************************************************************************
localhost  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0


PLAY [localhost] *********************************************************************************

TASK [Gathering Facts] ***************************************************************************
ok: [localhost]

TASK [Modify Groups] *****************************************************************************
changed: [localhost] => (item={'display_name': 'web-VMs', 'domain_id': 'default', 'expression': [{'member_type': 'VirtualMachine', 'value': 'web', 'key': 'Tag', 'operator': 'EQUALS', 'resource_type': 'Condition'}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}, {'tag': 'web', 'scope': 'east'}]})
changed: [localhost] => (item={'display_name': 'app-VMs', 'domain_id': 'default', 'expression': [{'member_type': 'VirtualMachine', 'value': 'app', 'key': 'Tag', 'operator': 'EQUALS', 'resource_type': 'Condition'}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}, {'tag': 'app', 'scope': 'east'}]})
changed: [localhost] => (item={'display_name': 'db-VMs', 'domain_id': 'default', 'expression': [{'member_type': 'VirtualMachine', 'value': 'db', 'key': 'Tag', 'operator': 'EQUALS', 'resource_type': 'Condition'}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}, {'tag': 'db', 'scope': 'east'}]})

PLAY RECAP ***************************************************************************************
localhost  : ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0


PLAY [localhost] *********************************************************************************

TASK [Gathering Facts] ***************************************************************************
ok: [localhost]

TASK [Modify Security Policy] ********************************************************************
changed: [localhost] => (item={'display_name': 'world-to-web', 'domain_id': 'default', 'category': 'Application', 'rules': [{'display_name': 'http', 'description': 'Rule for http port', 'sequence_number': 1, 'source_groups': ['any'], 'destination_groups': ['/infra/domains/default/groups/web-VMs'], 'services': ['/infra/services/HTTP'], 'action': 'ALLOW', 'scope': ['/infra/domains/default/groups/web-VMs']}, {'display_name': 'Catch-All', 'description': 'Catch All rule', 'sequence_number': 2, 'source_groups': ['any'], 'destination_groups': ['/infra/domains/default/groups/web-VMs'], 'services': ['any'], 'action': 'DROP', 'scope': ['/infra/domains/default/groups/web-VMs']}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}]})
changed: [localhost] => (item={'display_name': 'web-to-app', 'domain_id': 'default', 'category': 'Application', 'rules': [{'display_name': 'app-port', 'description': 'Rule for Application port', 'sequence_number': 1, 'source_groups': ['/infra/domains/default/groups/web-VMs'], 'destination_groups': ['/infra/domains/default/groups/app-VMs'], 'services': ['any'], 'service_entries': [{'l4_protocol': 'TCP', 'destination_ports': ['8080'], 'resource_type': 'L4PortSetServiceEntry'}], 'action': 'ALLOW', 'scope': ['/infra/domains/default/groups/app-VMs', '/infra/domains/default/groups/web-VMs']}, {'display_name': 'Catch-All', 'description': 'Catch All rule', 'sequence_number': 2, 'source_groups': ['any'], 'destination_groups': ['/infra/domains/default/groups/app-VMs'], 'services': ['any'], 'action': 'DROP', 'scope': ['/infra/domains/default/groups/app-VMs']}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}]})
changed: [localhost] => (item={'display_name': 'app-to-db', 'domain_id': 'default', 'category': 'Application', 'rules': [{'display_name': 'MySQL', 'description': 'Rule for Application port', 'sequence_number': 1, 'source_groups': ['/infra/domains/default/groups/app-VMs'], 'destination_groups': ['/infra/domains/default/groups/db-VMs'], 'services': ['/infra/services/MySQL'], 'action': 'ALLOW', 'scope': ['/infra/domains/default/groups/db-VMs', '/infra/domains/default/groups/app-VMs']}, {'display_name': 'Catch-All', 'description': 'Catch All rule', 'sequence_number': 2, 'source_groups': ['any'], 'destination_groups': ['/infra/domains/default/groups/db-VMs'], 'services': ['any'], 'action': 'DROP', 'scope': ['/infra/domains/default/groups/db-VMs']}], 'tags': [{'tag': 'ansible', 'scope': 'demo'}]})

PLAY RECAP ***************************************************************************************
localhost  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Verify the deployment

In NSX-T it will look like this

NSX-T GUI
Security roups
Security rules

On NSX-T edge the route will show up as a t1c (T1 connected)

T0-SR
bf-poc-nxtedge01(tier0_sr)> get route | find 10.4.203.

t1c> * 10.4.203.0/27 [3/0] via 100.64.96.9, downlink-634, 00:18:30
t1c> * 10.4.203.32/27 [3/0] via 100.64.96.9, downlink-634, 00:18:26
t1c> * 10.4.203.64/27 [3/0] via 100.64.96.9, downlink-634, 00:18:19
t1c> * 10.4.203.96/27 [3/0] via 100.64.96.9, downlink-634, 00:18:23


Conclusion

That concludes part 2.
As expected it works very well. Ansible is a great tool for automation and it works very well for NSX-T
If you have any thoughts or suggestions on this, please let me know in the comments!

Leave a Reply