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.
thanks lucas. you the man.
LikeLike