It has been some time since I wrote my last blog about integrating Pure Storage products into Ansible® and a lot has changed in that time, so I thought it was about time to impart some more information.
With the release of Ansible 2.8, there are now 14 FlashArray™ modules and 10 FlashBlade™ modules. Find more details of these in the Ansible documentation.
The modules for both FlashArray and FlashBlade can be broken down into two distinct sections: provisioning and configuration, and we will look at these separately.
Provisioning
FlashBlade
There is full support for FlashBlade provisioning whether that be for the file or object services.
For the file operation of the FlashBlade, there are modules for share creation, modification and deletion, including allowing access over different protocols for a share, such as HTTP, setting NFS access rules and configuring special directories. If you set the snapshot special directory, then you can use a module to manage these snapshots. If you are using the FlashBlade as an object store, there is a full suite of modules to allow creation and management of S3 accounts, users, and bucket management.
FlashArray
The FlashArray modules do not, sadly, cover the full feature set of Purity; however, active development is ongoing to fill these gaps. The main missing piece is ActiveCluster™ integration. Expect to see this resolved with the release of Ansible 2.9. If we ignore ActiveCluster, then you can do anything using Ansible modules that you could do with the GUI or the Purity CLI. These modules include standard CRUD operations, snapshot management and recovery, protection group support (including asynchronous replication) and support for offload, also referred to as Snap to NFS and CloudSnap.
Configuration
The most important thing about the configuration of your Pure devices is understanding what they currently look like – so, there are facts modules for both the FlashBlade and FlashArray which populate a dictionary with the totality of a device current configuration.
These dictionaries can be leveraged to check your current configuration and make decisions on what your Ansible playbooks do using standard Ansible decision-making tools including Jinja2 templates.
FlashArray
There are a steadily growing number of Ansible modules that can be used to configure your FlashArray appliances, including the configuration of directory services, remote assist, DNS and NTP settings. More modules are being actively developed and expect to see these in Ansible 2.9, or in the devel branch of the Ansible GitHub repo should you wish to try out newer modules early.
FlashBlade
Current configuration modules cover the management of directory services, network and subnets. There will be more modules coming soon, so watch the Ansible devel GitHub repo.
Using the Ansible modules
Now that we know the modules we have available, how can you use them within the context of provisioning and configuration management?
One of the most critical questions I get asked about both the FlashArray and FlashBlade modules is how to manage multiple devices and also how to ensure that access information about the arrays, such as the API tokens, can be secured.
So let’s have a look at what we can do with these modules, and also how we can secure information related to your Pure infrastructure so that sensitive configuration information does not fall into the wrong hands.
The first thing to note is that the Ansible nodes you run any Pure modules on require the appropriate Pure Python SDK installed on them, whether they are the localhost or remote nodes defined in an inventory. Specifically, you will need the purestorage
SDK for FlashArray modules and the purity_fb
SDK for FlashBlade modules.
Here is a short example of a pre-requisites playbook that will correctly install these SDKs on your Ansible nodes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
—– – hosts: localhost gather_facts: True become: True vars: epel_repo_url: “https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm” epel_repo_gpg_key_url: “https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-{{ ansible_distribution_major_version }}” epel_repofile_path: “/etc/yum.repos.d/epel.repo” tasks: – name: Install EPEL repo. yum: name: “{{ epel_repo_url }}” state: present when: ansible_os_family == “RedHat” – name: Import EPEL GPG key. rpm_key: key: “{{ epel_repo_gpg_key_url }}” state: present when: ansible_os_family == “RedHat” – name: Install python–pip module on RedHat yum: name: python–pip when: ansible_os_family == “RedHat” – name: Install python–pip module on Ubuntu apt: name: python–pip when: ansible_os_family == “Debian” – name: Install python–pip module on SuSE zypper: name: python–pip when: ansible_os_family == “Suse” – name: Install Pure Storage SDKs pip: name: [‘purestorage’, ‘purity_fb’] |
Once you have ensured that your nodes are capable of running the Pure Storage Ansible modules, you can start to experiment with their functionality and capabilities.
It is important to note that each play in your playbooks that uses either FlashArray or FlashBlade modules needs to have information passed to them so that the play executes against the correct backend storage.
For FlashArray modules, the parameters fa_url
and api_token
are required, whereas for FlashBlade modules they are fb_url
and api_token
. These represent the IP address or FQDN of the FlashArray or FlashBlade management ports and an API token for a privileged account of the FlashArray or FlashBlade (both local accounts and those managed by a directory service configured on the storage array are supported).
These parameters can be passed to every module in the playbook individually as shown in this simple example playbook for creating a FlashArray volume and attaching it to a host:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
– name: Create volume purefa_volume: name: data size: 64G fa_url: “192.168.1.1” api_token: “c6033033–fe69–2515–a9e8–966bb7fe4b40” – name: Create iscsi host and attach volume purefa_host: host: “{{ ansible_hostname }}” protocol: iscsi iqn: “{{ ansible_iscsi_iqn }}” volume: data fa_url: “192.168.1.1” api_token: “c6033033–fe69–2515–a9e8–966bb7fe4b40” |
You could create variables at the beginning of each playbook that can then be used in the plays as shown in this example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
vars: fa_url: 192.168.1.1 fa_api: c6033033–fe69–2515–a9e8–966bb7fe4b40 tasks: – name: Create volume purefa_volume: name: data size: 64G fa_url: “{{ fa_url }}” api_token: “{{ fa_api }}” – name: Create iscsi host and attach volume purefa_host: host: “{{ ansible_hostname }}” protocol: iscsi iqn: “{{ ansible_iscsi_iqn }}” volume: data fa_url: “{{ fa_url }}” api_token: “{{ fa_api }}” |
If using Ansible roles then these variables could be defined in a variables YAML file.
Alternatively, you can define environment variables in the shell running the playbook that contains this information, in which case you do not have to add the URL or API parameters to the play. The environment variables that the modules look for if no parameters are provided in the play are PUREFA_API
and PUREFA_URL
in the case of a FlashArray module or PUREFB_API
and PUREFB_URL
for a FlashBlade module.
Some people don’t like exposing specific parameters, especially those that may be considered secure. The API token provided to Ansible has to be associated with a FlashArray or FlashBlade user account that has Storage Admin privileges to perform all of the functions available in the Pure Ansible modules, so this is potentially a security risk. To ensure the security of this token, use Ansible Vault to encrypt it so that only Ansible administrators with the decryption password can implement the modules successfully.
Ansible Vault
I do not intend to describe how to configure Ansible Vault in any depth as there are lots of great blogs out there that do the same, and of course, there is the Ansible documentation as well.
In this example, I am going to use Vault to encrypt the API tokens for two different FlashArrays using an Ansible role I created called array, and then execute a simple playbook against both FlashArrays. The roles directory structure is:
1 2 3 4 5 6 7 |
roles └── array ├── tasks │ ├── all.yaml │ └── main.yaml └── vars └── main.yaml |
Firstly we will look at the variable file, var/main.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
—– arrays: – url: 10.0.0.8 api: !vault | $ANSIBLE_VAULT;1.1;AES256 343831636238346665646632656665303361326334333838633365373038 653530636637316532336232633431643965303735646161396435396236 373033360a61623637343562363639313962356361393963353638353533 353634383236643862316230393239386539303061653364666533333665 6431333035353539620a6437616632333463613336306666653834393031 643463393931373932643136326562346661663363636561366537656563 613638363266613562636438643465613631626430313633323736353963 37313539376463353432663063363933 – url: 10.0.0.210 api: !vault | $ANSIBLE_VAULT;1.1;AES256 333539356466363966623330616334363239363135386461323062353061 393133613733386330353636366438616234316235303465306439653466 373163650a65643636366237336337356332363338666532653433653237 376661373639316462663432383933376432663265356135363731643162 3236636134353534640a3034326166616337376538313561393334643761 343233623338313166376231346666373739373539373162626138626166 366534653138653537376366393630336632386130386561343365633636 65643531353336666563356265343163 dns_address: – 10.0.0.16 – 10.0.0.17 dns_domain: purestorage.com ntp_servers: – ntp1.purestorage.com |
Here we see both encrypted and unencrypted information. The encrypted data is the sensitive API token for each of the two arrays. There are also a few other variables that we will be using in the playbook.
It is pretty simple to get those encrypted strings.
First I need to create a password that Vault will use to encrypt and decrypt variables, but this needs to be managed by your Ansible administrator. In this example, I have created a simple text file called vault-password.yml
with the password as the only entry in it. Then I use the encrypt_string
command provided by Vault to encrypt the information I want to secure, in this case, the API tokens for the arrays:
# ansible-vault encrypt_string --vault-id vault-password.yml --stdin-name api
The command will prompt for the string I want to encrypt and then produce an output similar to this
1 2 3 4 5 6 7 8 9 10 |
api: !vault | $ANSIBLE_VAULT;1.1;AES256 343831636238346665646632656665303361326334333838633365373038 653530636637316532336232633431643965303735646161396435396236 373033360a61623637343562363639313962356361393963353638353533 353634383236643862316230393239386539303061653364666533333665 6431333035353539620a6437616632333463613336306666653834393031 643463393931373932643136326562346661663363636561366537656563 613638363266613562636438643465613631626430313633323736353963 37313539376463353432663063363933 |
which is then just cut and paste into the variables file. I did this for each API token.
Moving on to the primary role playbook; tasks/main.yaml which contains:
1 2 3 4 5 6 7 8 |
—– – name: Pure FlashArrays Example include_tasks: file: “all.yaml” vars: url: “{{ item.url }}” api: “{{ item.api }}” with_items: “{{ arrays }}” |
This playbook is telling the Ansible to use the tasks fileall.yaml
and pass the variables url
and api
to this, but these are looping through the items called arrays which we saw defined above as the two arrays and their associated URL and encrypted API information.
Finally, we have tasks/all.yaml
which is doing the actual work of running modules against the FlashArrays:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
—– – name: Get facts for {{ url }} purefa_facts: gather_subset: all fa_url: “{{ url }}” api_token: “{{ api }}” – set_fact: array_name: “{{ ansible_purefa_facts.default.array_name }}” – name: Set DNS for {{ array_name }} purefa_dns: domain: “{{ dns_domain }}” nameservers: “{{ dns_address }}” fa_url: “{{ url }}” api_token: “{{ api }}” when: (ansible_purefa_facts.config.dns.domain != dns_domain) or (ansible_purefa_facts.config.dns.nameservers != dns_address) – name: Set NTP for {{ array_name }} purefa_ntp: ntp_servers: “{{ ntp_servers }}” fa_url: “{{ url }}” api_token: “{{ api }}” when: ansible_purefa_facts.config.ntp != ntp_servers |
Although this looks a little complex, it is just performing the following tasks against each array:
- Get the current configuration of the FlashArray;
- Set a fact for the actual name of the array (purely for informational purposes);
- Check the current DNS settings of the array and compare to the value defined in the vars/main.yaml. If they don’t match then set the DNS settings on the array to be correct;
- Check the current NTP settings of the array and compare to the value defined in the vars/main.yaml. If they don’t match then set the NTP settings on the array to be correct.
To execute these modules is a simple matter of creating a playbook (infra.yaml
) to call the role array:
1 2 3 4 5 |
– name: Pure Storage storage module examples hosts: localhost gather_facts: no roles: – role: array |
And then run the following command:
# ansible-playbook infra.yaml --vault-id vault-password.yml
In this run, both of the arrays have the correct DNS and NTP settings, so nothing changes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
PLAY [Pure Storage storage module examples] ************************** TASK [array : Pure FlashArray Infra Config] ************************** included: /root/roles/array/tasks/all.yaml for localhost included: /root/roles/array/tasks/all.yaml for localhost TASK [array : Get facts for 10.0.0.8] ******************************** ok: [localhost] TASK [array : set_fact] ********************************************** ok: [localhost] TASK [array : Set DNS for sn1–m20r2–c07–33] ************************** skipping: [localhost] TASK [array : Set NTP for sn1–m20r2–c07–33] ************************** skipping: [localhost] TASK [array : Get facts for 10.0.0.210] ****************************** ok: [localhost] TASK [array : set_fact] ********************************************** ok: [localhost] TASK [array : Set DNS for sn1–405–c07–27] **************************** skipping: [localhost] TASK [array : Set NTP for sn1–405–c07–27] **************************** skipping: [localhost] PLAY RECAP *********************************************************** localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0 |
One final thought for this. You could schedule runs using Ansible Galaxy to ensure you don’t get configuration drift on your arrays, but also as a quick way to configure the infrastructure setting of a new array that you have added into your environment.
Remember development of more infrastructure modules for Pure is ongoing in the devel branch of the Ansible repo on GitHub, so keep a look out there, but if you feel a specific module is missing or a current module doesn’t do what you expect, please feel free to comment here or raise an Issue on the Ansible repo. Pure also has a public Slack community called Pure/Code() with a specific Ansible channel where messages and comments can also be left.
Pure Storage Virtual Lab
Sign up to experience the future of data storage with Pure Storage FlashArray™.
Upskill Your Knowledge!
Check out Pure Advanced Services where you can learn about Pure products and services.