Clone WordPress using Ansible and PSO

Clone WordPress using Ansible and PSO

In this blog, I will clone WordPress using Ansible and PSO from production to development.

Why this blog?

Recently I was asked to prepare a demo that was part of a larger DevOps workshop for one of our customers. The idea was to show the value of using automation in a DevOps environment. By cloning WordPress using Ansible and PSO, developers have access to a test environment that closely resembles production and which can be refreshed quickly and easily.

In this blog I will describe how I performed the demo.

Not into reading? Feel free to view the video instead.

Abouw the demo

The workflow

The workflow I will describe in this blog is as follows:

  1. Deploy WordPress using Helm in production
  2. Deploy WordPress using Helm in development
  3. Create some sample data in production
  4. Use Ansible to copy the data from production to development

This way we clone WordPress from production to development in a repeatable manner. The workflow uses the Pure Storage FlashArray replication and cloning capabilities. These capabilities allow us to create the process in minutes, irrelevant on the size of the persistent volumes used.

The environment

For the demo I created two Kubernetes clusters, one for production and one for development. Each Kubernetes cluster has the Pure Service Orchestrator (PSO) installed and configured to use it’s own FlashArray. To setup PSO yourself, read my blog here.

For this environment we used one FlashArray for production and one for development.

The idea is to deploy two WordPress applications one on production and one in development and use Ansible to clone the volumes from production to development as shown below.

Deploy WordPress

The first step is to deploy WordPress, for which I will use the helm chart provided by Bitnami. Using the following steps, which I executed on both the production and development Kubernetes cluster:

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo bitnami
helm install wordpress bitnami/wordpress --set wordpressUsername=user,wordpressPassword=secret,mariadb.db.password=secret,mariadb.rootUser.password=secret

I specify the passwords for WordPress and MariaDB, so that they match between Production and Development. Otherwise the WordPress container wouldn’t be able to login to the MariaDB database once we cloned it from production to development.

A note here: While using the same credentials is good for a demo, this is obviously not recommended in production. In real life you’d use obfuscation to remove sensitive data from the database and also change the credentials when you move between prod and dev.

Clone WordPress using Ansible and PSO

The full copy of playbook described below is available on my GitHub repo.

Defining our Ansible play

The script starts with defining variables and including the Pure Storage Ansible Collections.

- name: Copy wordpress production to development environment
  hosts: localhost
  gather_facts: no
  collections:
    - purestorage.flasharray
  vars:
    labels: "app.kubernetes.io/name=wordpress"
    k8s_namespace: "default"
    wordpress_app: "wordpress"
    mariadb_sset: "wordpress-mariadb"
    wordpress_vol: "wordpress"
    mariadb_vol: "data-wordpress-mariadb-0"
    k8s_prod_ns: "remko_prod"
    k8s_dev_ns: "remko_dev"
    snapshot_name: "ansible"
    pgroup: "wordpress"
    kubeconfig_prod: "/home/user/wordpress/k8s-prod-config"
    kubeconfig_dev: "/home/user/wordpress/k8s-dev-config"

I use a couple of variables at the beginning of the playbook. This makes it easier to modify the playbook for different environments. Honestly, not all of these are required. I could have also used Ansible tasks to lookup this info from Kubernetes. Would be happy to accept comments for improvement 😉

Get FlashArray information

  tasks:
  - name: Load settings
    include_vars: credentials.yaml
  - name: Get name source array
    purefa_info:
      fa_url: "{{ fa_prod_url }}"
      api_token: "{{ fa_prod_api_token }}"
    register: array_info
  - set_fact:
      prod_array: "{{ array_info['purefa_info']['default']['array_name'] }}"
  - name: Get name destination array
    purefa_info:
      fa_url: "{{ fa_dev_url }}"
      api_token: "{{ fa_dev_api_token }}"
    register: array_info
  - set_fact:
      dev_array: "{{ array_info['purefa_info']['default']['array_name'] }}"

Next we load the FlashArray IP addresses and tokens from a separate credentials file (lines 2-3). Next we get the FlashArray hostname from both production and development in lines 4 – 17. We’ll use the hostname later to identify the snapshot on the development FlashArray, later in the playbook.

List our Persistent Volume Claims

Next we’ll connect with the Kubernetes clusters to retrieve the PersistentVolumeClaims used by the WordPress application.

  - name: Get wordpress volumes
    k8s:
      kind: PersistentVolumeClaim
      name: "{{ wordpress_vol }}"
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_prod }}"
    register: wordpress_pvcs

  - name: Get wordpress volumes
    k8s:
      kind: PersistentVolumeClaim
      name: "{{ mariadb_vol }}"
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_prod }}"
    register: mariadb_pvcs

The Ansible k8s module (lines 2 and 10) allows to target Kubernetes. Using the kubeconfig: parameter we can pass a kubeconfig file containing the credentials for out two Kubernetes clusters (lines 6 an 14). By specifying only the name and namespace parameters, the module returns the status of these PVCs.

Create a snapshot in production

Now that we have the volume information, we can jump over to the production FlashArray.

  - name: Create protection group
    purefa_pg:
      pgroup: "{{ pgroup }}"
      state: present
      target: 
        - "{{ dev_array }}"
      volume:
        - "{{ k8s_prod_ns + '-' + wordpress_pvcs | json_query('result.spec.volumeName') }}"
        - "{{ k8s_prod_ns + '-' + mariadb_pvcs | json_query('result.spec.volumeName') }}"
      enabled: false
      fa_url: "{{ fa_prod_url }}"
      api_token: "{{ fa_prod_api_token }}"

  - name: Create snapshot for each PVC
    purefa_pgsnap:
      name: "{{ pgroup }}"
      suffix: "{{ snapshot_name }}"
      fa_url: "{{ fa_prod_url }}"
      api_token: "{{ fa_prod_api_token }}"
      remote: true
      now: true

In the two tasks above, we create a new protection group (lines 1 – 12). We directlty add the two volumes (lines 8 – 9) and the replication target (lines 5 – 6). We use a json_query to retrieve PersistentVolume (PV) names for the PersistentVolumeClaims (PVC). Next we use the purefa_pgsnap module to create a local snapshot and immediately replicate it to the development FlashArray (lines 20 – 21).

Waiting for the replication to complete

Now a bit of deep dive into Jinja2, for a task to wait for the replication of the snapshot to complete to the remote development FlashArray.

- name: Wait until PG transfer is complete
    purefa_info:
      gather_subset: pgroups
      api_token: "{{ fa_dev_api_token }}"
      fa_url: "{{ fa_dev_url }}"
    register: array_info
    retries: 10
    delay: 5
    ignore_errors: true
    until: "array_info | json_query('purefa_info.pgroups.\"' + prod_array + ':' + pgroup +'\".snaps.\"' + prod_array + ':' + pgroup + '.' + snapshot_name+'\".progress') != 0.0"

The purefa_info module will be used to retrieve information on the protection group and register the result as array_info. We loop the task until the progress of the snapshot replication is completed (shown as 1.0). The json_query uses some variables such as prod_array, pgroup and snapshot_name to look up the progress for the correct snapshot and as long as the progress = ‘0.0’ it will loop the task.

Pause WordPress in development

Now we have our snapshot on our development FlashArray, so we will head over to the development Kubernetes cluster.

  - name: Get wordpress volumes (dev)
    k8s:
      kind: PersistentVolumeClaim
      name: "{{ wordpress_vol }}"
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_dev }}"
    register: wordpress_pvcs_dev

  - name: Get mariadb volumes (dev)
    k8s:
      kind: PersistentVolumeClaim
      name: "{{ mariadb_vol }}"
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_dev }}"
    register: mariadb_pvcs_dev

  - name: Scale down WordPress relicas to 0
    k8s:
      name: "{{ wordpress_app }}"
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_dev }}"
      state: present
      definition:
        apiVersion: apps/v1
        kind: Deployment
        spec:
          replicas: 0

  - name: Scale down MariaDB relicas to 0
    k8s:
      name: "{{ mariadb_sset }}"
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_dev }}"
      state: present
      definition:
        apiVersion: apps/v1
        kind: StatefulSet
        spec:
          replicas: 0

  - name: Wait for containers to stop
    k8s_info:
      kind: pod
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_dev }}"
    register: list_pods
    until: "(list_pods | json_query('resources') | count) == 0"
    retries: 20
    delay: 5

First we list the PersistentVolumeClaims for the WordPress on the development environment, in the same way we did for the production environment (lines 1-15). Next we scale down the WordPress and MariaDB replicas to 0. This will stop all Pods in development before we restore the volumes from production (lines 17 – 39). And finally we wait until all Pods have been terminated (lines 41 – 49). This last task is similar to what we did before for the FlashArray replication, but the query is simpler. Just loop as long as the k8s_info returns active Pods aka count > 0.

Clone data from snapshot

Now we can start restoring the production data to the development environment.

  - name: Restore wordpress volume from the snapshot
    purefa_pgsnap:
      name: "{{ prod_array }}:{{ pgroup }}"
      suffix: "{{ snapshot_name }}"
      state: copy
      restore: "{{ k8s_prod_ns + '-' + wordpress_pvcs | json_query('result.spec.volumeName') }}"
      overwrite: true
      fa_url: "{{ fa_dev_url }}"
      api_token: "{{ fa_dev_api_token }}"

  - name: Restore mariadb volume from the snapshot
    purefa_pgsnap:
      name: "{{ prod_array }}:{{ pgroup }}"
      suffix: "{{ snapshot_name }}"
      state: copy
      restore: "{{ k8s_prod_ns + '-' + mariadb_pvcs | json_query('result.spec.volumeName') }}"
      overwrite: true
      fa_url: "{{ fa_dev_url }}"
      api_token: "{{ fa_dev_api_token }}"

  - name: Overwrite dev with prod volume wordpress
    purefa_volume:
      name: "{{ k8s_prod_ns + '-' + wordpress_pvcs | json_query('result.spec.volumeName') }}"
      target: "{{ k8s_dev_ns + '-' + wordpress_pvcs_dev | json_query('result.spec.volumeName') }}"
      overwrite: true
      fa_url: "{{ fa_dev_url }}"
      api_token: "{{ fa_dev_api_token }}"

  - name: Overwrite dev with prod volume mariadb
    purefa_volume:
      name: "{{ k8s_prod_ns + '-' + mariadb_pvcs | json_query('result.spec.volumeName') }}"
      target: "{{ k8s_dev_ns + '-' + mariadb_pvcs_dev | json_query('result.spec.volumeName') }}"
      overwrite: true
      fa_url: "{{ fa_dev_url }}"
      api_token: "{{ fa_dev_api_token }}"

The first two tasks (lines 1 – 19) will copy the snapshot into a new volume on the development FlashArray. It will create the volumes with the same names as were used on the production FlashArray.

The next two tasks (lines 21 – 35) will copy the restored volumes and overwrite the volumes that are used by the WordPress application in the development cluster. Here we use the PSO namespace specified as a variable and the PVC information that was retrieved earlier, to target the correct destination volume on the development FlashArray.

Resume WordPress in development

We have now completed our restore and are able to spin the development WordPress environment up.

  - name: Scale up WordPress relicas to 1
    k8s:
      name: "{{ wordpress_app }}"
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_dev }}"
      state: present
      definition:
        apiVersion: apps/v1
        kind: Deployment
        spec:
          replicas: 1

  - name: Scale up MariaDB relicas to 1
    k8s:
      name: "{{ mariadb_sset }}"
      namespace: "{{ k8s_namespace }}"
      kubeconfig: "{{ kubeconfig_dev }}"
      state: present
      definition:
        apiVersion: apps/v1
        kind: StatefulSet
        spec:
          replicas: 1

We use the same tasks as used earlier to scale the Deployment and StatefulSet down. However now we scale it back up to 1 replicas each. Once the Pods have completed their startup, we now have completed the restore. The production environment and development environment are now identical.

Conclusion

And that concludes with blog. We’ve seen how we can combine the Kubernetes Ansible modules and the Pure Storage Ansible modules to easily Clone WordPress using Ansible and PSO. And while this example is between to FlashArray, the same playbook could be used for replication between on-prem and the public cloud. It could even be used between Public Clouds by using Cloud Block Store.

I hope you’ve found this blog entertaining and educational. Let me know what you think or if you have any questions or suggestions.

One thought on “Clone WordPress using Ansible and PSO

Leave a Reply

Your email address will not be published.