NSX-T and Ansible – Some tips

I was planning to write an introductory post about how to get started with Ansible with NSX-T. I then realized that I could not do a better job than what Madhukar Krishnarao did here. I suggest you go through his blog post if you want to get started with Ansible for NSX-T. In this post, I will cover how I, as an Ansible newbie, overcame some of the challenges I faced while automating NSX-T deployments and configurations via Ansible.

Use the best tool for the task at hand. It may not be Ansible……

Sometimes Ansible does not provide a module for a specific task or performing a configuration is just easier in a different way. In a scenario like this, I do not hesitate to incorporate other tools such as PowerCLI or Terraform in my Ansible playbooks.

For example, I had to create a vDS and perform a rather complex NIC teaming configurations on some of the port-groups. To do so, I wrote a script in PowerCLI and just invoke it in one of the playbook tasks via the command module:

- hosts: 127.0.0.1
  connection: local
  become: yes
  vars_files:
    - livefire_vars.yml
  tasks:
    - name: Create edge vDS via PowerCLI
      command: pwsh ./new_vds.ps1

I would be the first to admit this is not an elegant solution. We also lose Ansible idempotency unless we add some logic in the script. But achieving the same result via Ansible would have required writing some custom modules. In this case, I check if a vDS with the desired name already exists. If it does the script exits, otherwise the vDS is created together with port groups and the NIC teaming configuration. To invoke PowerCLI scripts via Ansible, it is sufficient to install PowerCLI on the Ansible management node and then provide the path to the script.

Storing task results in variables

One of the challenges of using an imperative API (or a tool that leverage it, such as Ansible in this case), is keeping track of the ID of the objects you create. It is necessary so that you can reference them later when you configure objects depending on them. NSX-T ansible modules always return a JSON object describing the object they create. It is enough to store this JSON object in a variable via the “register” statement to be able to use it later in the playbook.

In the example below, I store the result of the  nsxt_logical_routers module that creates a T0 router is a variable named t0. I then save the result of the nsxt_logical_router_ports creating a LIF in the variable lsp_T0_EXT1a. I then reference the ID value stored in the two variables when I create the logical router port via the nsxt_logical_router_ports module.

 - name: Create T0 logical router
      nsxt_logical_routers:
        hostname: "{{hostname}}"
        username: "{{username}}"
        password: "{{password}}"
        validate_certs: False
        display_name: "t0-infrastructure-SiteA"
        edge_cluster_name: edge-cluster-1-SiteA
        router_type: TIER0
        high_availability_mode: ACTIVE_ACTIVE
        state: "present"
      register: t0

    - name: Create first logical port on EXT1_VLAN LS to connect T0 router
      nsxt_logical_ports:
        hostname: "{{hostname}}"
        username: "{{username}}"
        password: "{{password}}"
        validate_certs: False
        display_name: "T0-Ext1LpA"
        logical_switch_name: "VLAN-Ext1Ls"
        admin_state: UP
        state: "present"
      register: lsp_T0_EXT1a

    - name: Create logical router port on T0 router EN1 to interconnect uplink VLAN EXT1
      nsxt_logical_router_ports:
        hostname: "{{hostname}}"
        username: "{{username}}"
        password: "{{password}}"
        validate_certs: False
        display_name: LRP-VLAN-Ext1Ls_EN1
        resource_type: LogicalRouterUpLinkPort
        logical_router_id: "{{t0.id}}"
        edge_cluster_member_index: [0]
        linked_logical_switch_port_id:
          target_type: LogicalPort
          target_id:  "{{lsp_T0_EXT1a.id }}"
        subnets:
        - ip_addresses:
          - 192.168.254.13
          prefix_length: 29
        state: "present"

What if an Ansible module does not exist?

Some times you will want to perform operations that are not covered by any  Ansible module. In this case, you can interact with the NSX-T API directly via the uri module. In this example, I use the Policy API to perform the micro-segmentation of a multi-tier application. The body of the API call is stored in a separate variable file named policy_api_vars.yml. You generally lose Ansible idempotency when you use the uri module. It is not a problem in this case because the NSX-T Policy API is inherently idempotent. If the call were against the management plane API, it would have been a different story. The uri module gives the option to specify an expected status code. If the API call returns a different code the playbook stops. I recommend you use it to avoid undesired configuration changes after an API call error.

#
- hosts: 127.0.0.1
  connection: local
  become: yes
  vars_files:
    - policy_api_vars.yml
    - livefire_vars.yml
  tasks:
    - name: Create Security Policy and Groups
      uri:
        method: PATCH
        url: "https://{{ nsx_node1.hostname }}/policy/api/v1/infra/"
        user: "{{ nsx_username }}"
        password: "{{ nsx_password }}"
        body: "{{microsegmentation}}"
        body_format: json
        force_basic_auth: yes
        validate_certs: no
        status_code: 200

I hope this will help anyone automating NSX-T via Ansible. Feel free to check out the Github repository we use to fast forward the labs in our NSX-T Livefire class. You might find something helpful for your projects.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s