Ansible for SAN Automation: Part 3 Pure Storage Flash Array

automation ansible pure

In part 3 of our Ansible for SAN Automation series I'm going to document how I build automations targeting deployment and configuration management and routine tasks on a Pure Storage FlashArray//X. Take my word for it, Pure Storage's FlashArray//X has to be the nicest all-flash storage array on the market. I cant overstate the value that Pure has packed into this product, but one of its most useful features I find is that Pure offers a very feature complete Ansible module collection for both FlashArray and FlashBlade. Lets jump right in to using it.

An important prerequisite to use the ansible Pure Storage module is that you will need to log into your array and generate an API key for your user account. Or even better create a new ansible service account and generate for that user. In current versions of Purity OS this usually found in Settings > Access > Users > Create API Token. The API key will be used below assigned as the var fa_api.

Inventory

First things first, your inventory of your control node should have entires that appear as follows for your Pure Storage FlashArrays. Where you keep your vars is a bit of a personal preference, in the example below I have placed them in my inventory file which is a sin to some. Place them where you prefer just bear in mind Ansible's variable precedence.

[purefa]
PSFAX01.example.net fa_api=asfq1394_jfas923nas 
PSFAX02.example.net fa_api=asfq1394_jfas923nas 

[purefa:vars]
fa_url="{{ inventory_hostname }}"
ansible_user=admin
ansible_password=!vault...
ansible_become=yes

[all:vars]
domain_name=example.net

Now lets make our first playbook. How about something simple, updating the Array name in Purity OS.


#!/bin/ansible-playbook
---
- name: "##### Sets the Array name on FlashArray #####"
  hosts: '{{target|default("purefa")}}'
  gather_facts: False

  tasks:
# Set the hostname on FlashArrays
    - name: Set the FlashArray name
      delegate_to: localhost
      purestorage.flasharray.purefa_arrayname:
        name: "{{ inventory_hostname_short }}"
        state: present
        fa_url: "{{ fa_url }}"
        api_token: "{{ fa_api }}"

Above "create-hosts.yml" Playbook available on my Github.

So let me explain a few things about our above playbook.

  • First, I like to always do a she-bang and put #! /path/to/ansible-playbook as the first line of my playbooks, this allows the playbook to be ran like a normal script assuming it has the posix execute mode bit.
  • Second, we used a group in our inventory file called purefa and we put our flasharray hostnames in this group. In the example I have PSFAX01.example.net and PSFAX02.example.net, instead you would want to list your flasharray's DNS resolvable hostname. Short names like PSFAX01 are just fine in your inventory as long as its resolvable and your DNS supports it. Whatever you make this value we will name the array however I have specified to always name the array the short DNS name using the special variable. Alter this value as you see fit.
  • Finally, the delegate_to: localhost. I recommend you go read up about ansible delegation. What I'm saying is all the "work" for this play is going to be done on the localhost. We don't run the whole playbook against the localhost because that solution doesn't scale if you have more than one flasharray. This way we can run the playbook against our entire inventory, all of our flash arrays, or we can --limit PSFAX01.example.net and only run against a single host. When we build a playbook with this method we will almost always delegate_to: localhost on nearly every task. Just one of the idiosyncrasies of working with a fleet of storage devices like this.

After your have your playbook file created you can run it on your ansible control node. When you run this it should update the array name to whatever you have listed as the name in the inventory file. This is great for forcing configurations against a fleet and preventing unauthorized of unintended changes. You could schedule a playbook like this to run nightly, it could set and reset any configuration variable like defining DNS servers and NTP sources just for example. Or maybe setting a MOTD message banner. Sky is the limit.

Ok, lets do something a bit more advanced. Lets create a playbook that helps us do a pretty common task; define new hosts and associate the WWPNs (or IQNs or NQNs). I have found a very user friendly way to do this is to pull the host's values from a comma separated value spreadsheet so each row represents a host. This method also scales very well, need to add 100 vmware hosts to 4 different storage arrays? Using this .csv method it's no problem.

So open Excel and create a spreadsheet called "hosts.csv". Fill it as the example below:

Hostname,Platform,WWPNA,WWPNB,IQN,NQN
esx-server01,esxi,56:2:22:11:22:88:11:67,56:2:22:11:22:88:11:68,,
esx-server02,esxi,56:2:22:11:22:88:11:71,56:2:22:11:22:88:11:72,iqn.1994-05.com.vmware:7d366003914,
aix-server01,aix,,,,nqn.2014-08.com.vendor:nvme:nvm-subsystem-sn-d78432

Put your hostnames for the new servers in the Hostname column. If using Fibre Channel put your WWPNs in the two columns WWPNA and WWPNB. If using iscsi put it in the IQN column. NVMe-oF put it in the NQN column. It is possible to fill out more than one column type for a single host (ie both fibre channel + iscsi) and pure says it will use the preferred method and fall back to the slower but I dont do this in production because I dont trust it but to be fair I havent tested it. If you have positive experience with this I would love to hear about it.

After our hosts.csv file is created and looking as you intend lets make our playbook:

#!/bin/ansible-playbook
########################

# DESCRIPTION
# Creates and Removes Hosts on Pure Storage FlashArrays

# DIRECTIONS
# Usage to create a single host
# Run ./create-hosts.yml --tags create-adhoc -e "Hostname=esx-server01,Platform=ESXi,WWPNA=56:2:22:11:22:88:11:67,WWPNB=56:2:22:11:22:88:11:68" -v

# Usage to remove a single host (note, Platform and WWPN/IQN values not required to remove)
# Run ./create-hosts.yml --tags remove-adhoc -e "Hostname=esx-server01" -v

# Usage to create multiple hosts using hosts.csv file: 
#     1. create a file called hosts.csv in the same directory as this playbook.
#     2. Make columns titled "Hostname", "Platform", "WWPNA", and "WWPNB" if using FC. A column named IQN for iscsi, or NQN column for NVMe.  Read about "Platform" values here under personaility - https://docs.ansible.com/ansible/latest/collections/purestorage/flasharray/purefa_host_module.html
#     3. fill in the values in all columns for all the hosts you want to create. 
#
# Run ./create-hosts.yml --tags create-csv -v

# Usage to remove multiple hosts using hosts.csv file:
# Run ./create-hosts.yml --tags remove-csv -v

---
- name: "##### Create and Removes hosts on FlashArrays #####"
  hosts: '{{target|default("purefa")}}'
  gather_facts: False
  strategy: free

  tasks:

    - name: Reading the csv file
      read_csv:
        path: hosts.csv
      register: hosts_list
      delegate_to: localhost
      tags:
      - create-csv
      - remove-csv        

    - name: Creates all hosts from CSV file on FlashArrays
      delegate_to: localhost
      purestorage.flasharray.purefa_host:
        name: "{{ item.Hostname }}"
        personality: "{{ item.Platform }}"
        nqn:
        - "{{ item.NQN }}"
        iqn:
        - "{{ item.IQN }}"
        wwns:
        - "{{ item.WWPNA }}"
        - "{{ item.WWPNB }}"
        fa_url: "{{ fa_url }}"
        api_token: "{{ fa_api }}"
      loop: "{{ hosts_list.list }}" 
      tags:
      - create-csv

    - name: Removes all hosts from CSV file on FlashArrays
      delegate_to: localhost
      purestorage.flasharray.purefa_host:
        name: "{{ item.Hostname }}"
        state: absent
        fa_url: "{{ fa_url }}"
        api_token: "{{ fa_api }}"
      loop: "{{ hosts_list.list }}" 
      tags:
      - remove-csv

    - pause:
      delegate_to: localhost
        prompt: "Enter the hostname to create:"
        echo: true
      register: hostname
    - set_fact:
        ask_user: "{{ hostname.user_input }}"
      when: hostname == ""
      tags:
      - create-adhoc
      - remove-adhoc

    - name: Creates a single FC host on FlashArrays
      delegate_to: localhost
      purestorage.flasharray.purefa_host:
        name: "{{ hostname }}"
        personality: "{{ platform }}"
      #  nqn:
      #  - "{{ NQN }}"
      #  iqn:
      #  - "{{ IQN }}"     
        wwns:
        - "{{ wwpna }}"
        - "{{ wwpnb }}"
        fa_url: "{{ fa_url }}"
        api_token: "{{ fa_api }}"        
      tags:
      - create-adhoc

    - name: Removes a single FC host on FlashArrays
      delegate_to: localhost
      purestorage.flasharray.purefa_host:
        name: "{{ hostname }}"
        state: absent
        fa_url: "{{ fa_url }}"
        api_token: "{{ fa_api }}"
      tags:
      - remove-adhoc

Playbook "create-hosts.yml" also available on my Github.

Notice in the Creates a single FC host on FlashArrays play task that all of the transport types other than FC WWPNA and WWPNB are commented out. This is done because when using the --tag create-adhoc any options you are not using will fail if they are not commented out as ansible will complain there are "undefined variables". Basically, if you are using iscsi as your preferred connection method youll want to uncomment IQN and comment the other transport types. I prefer Fibre Channel so thats what I leave uncommented.

Directions for use are in the header comments of the playbook. So, give it a go and if you have any questions about its use feel free to leave a comment below.

Previous Post Next Post