vRA Tips and Tricks – Part 3

This article series will focus on specific tips and tricks to aid customisation and flexibility of vRA 8.x.

Contents:

I have always been a fan of abstracting personality from code – this leads to being able to handle changes better and aids portability. To this purpose I have been a heavy user of vRA Property Groups to store the configuration of a deployment. In this article I will demonstrate how to use an array within a Property Group to create an environment definition that is referenced by the deployment input.

The objective:

  • Deploy to DevTest or Production environments from a single blueprint
  • Scale the deployment both horizontally and vertically between these two environments:
    • Single-node tiers in DevTest, HA in Production
    • Minimum CPU and Memory in DevTest, Final-size CPU and Memory in Production
  • Connect to different networks depending on the environment
  • Abstract the environment specification from the Cloud Template

I will be using a three-tiered app named “Cool App” as an example for this article which has been written for/tested against vRA 8.60. Here’s how to do it:

1. Define the Property Schema

For later use by the blueprint, we need to define an array of values and how we will reference them from within the Property Group. To meet the objectives in this article I have created the following schema:

  • [0] CPU – Dev/Test
  • [1] Memory – Dev/Test
  • [2] No. of Nodes – Dev/Test
  • [3] CPU – Prod
  • [4] Memory – Prod
  • [5] No. of Nodes – Prod

2. Create a Property Group

  1. Navigate to Cloud Assembly / Design / Property Groups and select New Property Group 
  2. Change the Property Group type to Constant Values 
  3. Give it a name, Display Name, and Description
  4. Do not change the scope button 
  5. Select the Project. This is important as the we may wish to include a secret in the future and these can only be created at the Project level 
  1. Create a new property named web_server
  2. Change the type to Array
  3. Enter the array schema we defined earlier as the description. This will aid in selecting the right value in the blueprint expression and identifying values for a future change.
  4. Enter desired constant values as per the schema prefacing each with a dash and a space – this is the vRA expression syntax used to define an array (you see this in the blueprint YAML also).
  5. Select Create
  1. Create properties also for app_server and db_server with the following values:
  1. Finally select Create to complete the Property Group

3. Create the Cloud Template

  1. Create the Cool App blueprint with three vSphere Machines and two networks (I am using NSX here)
  2. Attach the Web servers to the DMZ network, and the App and DB servers to the Internal network
  3. Create Inputs for the environment
  4. Select your image and customization spec
  5. Add whatever other properties you may need – e.g. tags, properties for ABX actions etc
  6. Use ternary logic expression to define the properties for cpuCount:, totalMemoryMB:, and count: as per the below example for the Web Sever machine:
cpuCount: '${input.environment == "Dev" ? propgroup.CoolApp.web_server[0] : propgroup.CoolApp.web_server[3] }

In natural language, this would mean “If Dev environment is selected in the request form, set the cpuCount value as specified in the web_server property within the CoolApp Property Group at position 0, otherwise set the cpuCount value as defined in position 3”

As per our schema, this would set CPU to be 1 for Dev or 2 for Prod

Here are all the expressions:

Web Server:

cpuCount: '${input.environment == "Dev" ? propgroup.CoolApp.web_server[0] : propgroup.CoolApp.web_server[3] }'
totalMemoryMB: '${input.environment == "Dev" ? propgroup.CoolApp.web_server[1] : propgroup.CoolApp.web_server[4] }'
count: '${input.environment == "Dev" ? propgroup.CoolApp.web_server[2] : propgroup.CoolApp.web_server[5] }'

App Server:

cpuCount: '${input.environment == "Dev" ? propgroup.CoolApp.app_server[0] : propgroup.CoolApp.app_server[3] }'
totalMemoryMB: '${input.environment == "Dev" ? propgroup.CoolApp.app_server[1] : propgroup.CoolApp.app_server[4] }'
count: '${input.environment == "Dev" ? propgroup.CoolApp.app_server[2] : propgroup.CoolApp.app_server[5] }'

DB Server

cpuCount: '${input.environment == "Dev" ? propgroup.CoolApp.db_server[0] : propgroup.CoolApp.db_server[3] }'
totalMemoryMB: '${input.environment == "Dev" ? propgroup.CoolApp.db_server[1] : propgroup.CoolApp.db_server[4] }'
count: '${input.environment == "Dev" ? propgroup.CoolApp.db_server[2] : propgroup.CoolApp.db_server[5] }'
  1. Use ternary logic also to define which network the deployment will use depending on the environment. You could choose to add this also to the property schema but I wanted to show a simpler ternary expression example here with hard-coded values:
  Net-DMZ:
    type: Cloud.NSX.Network
    properties:
      networkType: existing
      constraints:
        - tag: '${input.environment == "Dev" ? "net-t98-dev-dmz" : "net-t98-prod-dmz"}'
  Net-Internal:
    type: Cloud.NSX.Network
    properties:
      networkType: existing
      constraints:
        - tag: '${input.environment == "Dev" ? "net-t98-dev-internal" : "net-t98-prod-internal"}'

Your final blueprint should look like this:

Here is the complete blueprint YAML you can copy and paste to make it easier

name: Cool App
formatVersion: 1
version: 1
# ----------------------------------------------------#
inputs:
  tenant:
    type: string
    title: Tenant Designation
    description: Select a three character designation e.g. T03
    default: T98
    maxLength: 3
    minLength: 3
    pattern: '([A-Z])\d\d'
  environment:
    type: string
    title: Environment
    description: Select a deployment environment
    oneOf:
      - title: Development
        const: Dev
      - title: Production
        const: Prod
    default: Dev
# ------------------ RESOURCES --------------------#
resources:
  Net-DMZ:
    type: Cloud.NSX.Network
    properties:
      networkType: existing
      constraints:
        - tag: '${input.environment == "Dev" ? "net-t98-dev-dmz" : "net-t98-prod-dmz"}'
  # ----------------------------------------------------#
  Web_Server:
    type: Cloud.vSphere.Machine
    properties:
      image: centos-79
      customizationSpec: mlab-linux-spec-merc
      cpuCount: '${input.environment == "Dev" ? propgroup.CoolApp.web_server[0] : propgroup.CoolApp.web_server[3] }'
      totalMemoryMB: '${input.environment == "Dev" ? propgroup.CoolApp.web_server[1] : propgroup.CoolApp.web_server[4] }'
      count: '${input.environment == "Dev" ? propgroup.CoolApp.web_server[2] : propgroup.CoolApp.web_server[5] }'
      folderName: Tenants\_Dev
      tags:
        - key: AppName
          value: Cool App
        - key: Environment
          value: '${input.environment}'
        - key: Tenant
          value: '${input.tenant}'
        - key: ServerRole
          value: Web Server
        - key: SecurityZone
          value: DMZ
      constraints:
        - tag: 'cloud:private'
      networks:
        - network: '${resource["Net-DMZ"].id}'
          assignment: static
      # -------------------- ABX Actions -------------------#
      create_dns: true
      cnameRecord: null
      min_install: true
      highstate: true
      # ----------------- Salt Config -----------------------------#
      grains: |
        AppName: Cool App
        Environment: Production
        Tenant: T98
        serverRole: Web Server
        SecurityZone: DMZ
      minionconfig: |
        master: '${propgroup.SaltStackConfiguration.masterAddress}'
        master_finger: '${propgroup.SaltStackConfiguration.masterFingerPrint}'
        id: '${self.resourceName}'
  # ----------------------------------------------------#
  App_Server:
    type: Cloud.vSphere.Machine
    properties:
      image: centos-79
      customizationSpec: mlab-linux-spec-merc
      cpuCount: '${input.environment == "Dev" ? propgroup.CoolApp.app_server[0] : propgroup.CoolApp.app_server[3] }'
      totalMemoryMB: '${input.environment == "Dev" ? propgroup.CoolApp.app_server[1] : propgroup.CoolApp.app_server[4] }'
      count: '${input.environment == "Dev" ? propgroup.CoolApp.app_server[2] : propgroup.CoolApp.app_server[5] }'
      folderName: Tenants\_Dev
      tags:
        - key: AppName
          value: Cool App
        - key: Environment
          value: '${input.environment}'
        - key: Tenant
          value: '${input.tenant}'
        - key: ServerRole
          value: App Server
        - key: SecurityZone
          value: Internal
      constraints:
        - tag: 'cloud:private'
      networks:
        - network: '${resource["Net-Internal"].id}'
          assignment: static
      # -------------------- ABX Actions -------------------#
      create_dns: true
      cnameRecord: null
      min_install: true
      highstate: true
      # ----------------- Salt Config -----------------------------#
      grains: |
        AppName: Cool App
        Environment: Production
        Tenant: T98
        serverRole: App Server
        SecurityZone: Internal
      minionconfig: |
        master: '${propgroup.SaltStackConfiguration.masterAddress}'
        master_finger: '${propgroup.SaltStackConfiguration.masterFingerPrint}'
        id: '${self.resourceName}'
  # ----------------------------------------------------#
  DB_Server:
    type: Cloud.vSphere.Machine
    properties:
      image: centos-79
      customizationSpec: mlab-linux-spec-merc
      cpuCount: '${input.environment == "Dev" ? propgroup.CoolApp.db_server[0] : propgroup.CoolApp.db_server[3] }'
      totalMemoryMB: '${input.environment == "Dev" ? propgroup.CoolApp.db_server[1] : propgroup.CoolApp.db_server[4] }'
      count: '${input.environment == "Dev" ? propgroup.CoolApp.db_server[2] : propgroup.CoolApp.db_server[5] }'
      folderName: Tenants\_Dev
      tags:
        - key: AppName
          value: Cool App
        - key: Environment
          value: '${input.environment}'
        - key: Tenant
          value: '${input.tenant}'
        - key: ServerRole
          value: Database Server
        - key: SecurityZone
          value: Internal
      constraints:
        - tag: 'cloud:private'
      networks:
        - network: '${resource["Net-Internal"].id}'
          assignment: static
      # -------------------- ABX Actions -------------------#
      create_dns: true
      cnameRecord: null
      min_install: true
      highstate: true
      # ----------------- Salt Config -----------------------------#
      grains: |
        AppName: Cool App
        Environment: Production
        Tenant: T98
        serverRole: Database Server
        SecurityZone: Internal
      minionconfig: |
        master: '${propgroup.SaltStackConfiguration.masterAddress}'
        master_finger: '${propgroup.SaltStackConfiguration.masterFingerPrint}'
        id: '${self.resourceName}'
  Net-Internal:
    type: Cloud.NSX.Network
    properties:
      networkType: existing
      constraints:
        - tag: '${input.environment == "Dev" ? "net-t98-dev-internal" : "net-t98-prod-internal"}'

Mission Accomplished!

Selecting Dev from the deployment form will deploy a single instance of each Tier with Dev-sized CPU and RAM and attached to Development DMZ and Internal networks

Selecting Prod from the deployment form will deploy three web servers, two app servers, and two database servers with desired production CPU and RAM values which are attached to Production DMZ and Internal networks

Closing Thoughts

I hope you enjoyed reading this article and have a new appreciation of the power of Property Groups when leveraging other expression syntax tricks like arrays and ternary logic. Some other items you may want to reference inside a Property Group are:

  • resourceGroupName:
  • storage:
  • sshKey: (referencing a Secret)

Until next time!

2 thoughts on “vRA Tips and Tricks – Part 3”

Leave a Reply

%d bloggers like this: