How to Configure Applications Running on Virtual Machines

 

 

Using docker containers is now a standard for deploying and managing applications. With them, transfering a particular configuration or startup parameters to an application is practically trivial. But what if your app is running on a Virtual Machine? How do you pass arguments to it, if it is running on a VM’s guest OS? There are still many apps out there that either haven’t been containerised or simply require some special permissions, etc, yet are still running on Virtual Machines. Passing startup parameters to such apps may be hard because it requires a tool with OS level access. Of course, Puppet, Chef or Ansible will handle the matter with ease, but those demand the application of specific knowledge.

In this post we outline a much simpler solution using a specific example to ensure clarity. The described solution is for AWS cloud and VMware vSphere based infrastructure.

The main steps are as follows:

  1. Provision a VM that contains your application and assign metadata (Tags) to it;
  2. Read the metadata from within the VM with a simple script;
  3. Start the app using the metadata as startup parameters/arguments.

And below you can read through a more detailed description of the proposed solution.

We’re working with the assumption that we have an application that requires a few parameters when starting up. This app has to be deployed for each of our clients and one of the startup parameters is the clientID, unique for each and every client. We have a VM template (AMI in Amazon terms) and we create a new VM (EC2) instance for each client. The question is, how to pass the startup parameter (in our case the clientID) to the app?

In AWS we are going to use Amazon Metadata Service with a combination of AWS Tags API.

Amazon Metadata Service is injected in each AWS EC2 instance. It contains diverse data, which you can query, using tools like Curl on Linux. Some of the information you can get includes the EC2 instanceID, the region where the instance is running and so on. For the full list of properties see the AWS docs here.

Tags in AWS are key/value pairs and you can map them directly to the application startup parameters. Basically, you need to create as many tags as your application startup parameters are.

We said the app is inspecting a parameter called “clientID”, so you can attach a tag with key=clientID and value= when a new EC2 instance is created. Assigning the tags is the first part of the solution, because now you have the application startup parameters attached to your instances. Next, we need to read these tags and pass them to our application as arguments.

For the second part of our solution we are going to create a simple script, which will read the instance tags and then start the application, passing them as arguments.

In the example below we’re using Bash, which depends on AWS CLI and jq library. AWS CLI requires AWS credentials in order to operate. For maximum security, the AWS credentials should only be allowed to read tags.

The script retrieves the EC2 instanceID and EC region from the metadata service like this:

Reading the region:

region=$(curl --silent http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)

Reading the instanceID:

instanceId=$(ec2metadata --instance-id)

Reading a tag:

tagValue=$(aws ec2 describe-tags --filters "Name=resource-id,Values=${instanceId}" --region ${region} | jq -r ".Tags | .[] | select(.Key ==\”clientId\”) | .Value")

Last step is to start your app and pass the actual parameters:

/pathToMyApp –clientID ${tagValue}

This approach is also applicable for Virtual Machines running on VMware vSphere infrastructure. It is possible to provide information to the running guest OS in the form of key/value pairs using the official VMware vSphere API. One can make such API calls both against vSphere vCenter Server and VMware ESXi. In addition, bindings for this API are supported and provided by VMware for a number of different programing languages.

The examples in this post use the VMware vSphere API Python Bindings available on GitHub.

The basic assumption is that you know how to get a reference to the VM on which your app is running. You have to modify the ‘extraConfig’ property of the VM object. The property is a member of the VM’s ConfigSpec. It is of type OptionValue[]. For more information have a look at the vSphere API Reference.

myKey = 'guestinfo.clientId'
myValue = 'client identifier goes here'
cspec = pyVmomi.vim.vm.ConfigSpec()
cspec.extraConfig = [pyVmomi.vim.option.OptionValue(key=myKey, value=myValue)]

Next call ReconfigVM_Task on the VM MORef like this:

vm.Reconfigure(cspec)

Note the key prefix ‘guestinfo.’! It is important to add this to the key, otherwise it can’t be read by the guest OS.

If this is executed on a powered off Virtual Machine, the key/value pair is saved in the VM’s .vmx file and survives restarts. If the info is added while the VM is running then the information will not be available after reboot.

So, how to read the property inside the Virtual Machine? We need ‘VMware Tools’ provided by VMware or, in the case of a Linux guest OS, the command ‘open-vm-tools’, which reads the info:

vmware-rpctool "info-get guestinfo.clientId"

All of these operations are already conveniently scripted on GitHub. Note that this script assumes ESXi/vCenterServer running on ‘localhost’ and does not use SSL verification, but is still a great starting point.

Now you have everything to extract the application parameters from a script running inside the VM. The final step is to start your app and pass the parameters.

This is how you can fully automate the deployment of applications packed as VMs.