-
Notifications
You must be signed in to change notification settings - Fork 4
Audrey: Post Boot Configuration Management
Audrey is a piece of the larger Aeolus Project with the goal of providing the mechanism for “post launch” configuration of instances in a cloud.
Audrey uses the information from Templates, Assemblies, and Deployables to build an Instance Configuration record for instances in a cloud. This Instance Configuration is used to generate the input to the specific Configuration Engine that will drive the post launch configuration.
An Aeolus Service is simply an abstraction of something that can be configured by a Configuration Engine. Typically, a service contains some static information along with parameters that can modify configuration of the service. For Puppet, this might be a collection of Classes and Parameters. For Chef, this might be a Chef Recipe. A Service contains a set of Parameters that define how the service can be configured for an instance. Each parameter can be an input parameter, an output parameter, or both. The image above depicts a service with an input parameter and an output parameter.
Input parameters are required by the service to function properly. Output parameters are provided by the service (or, provided by the configured instance). In order to build an Instance Configuration, values must be assigned to all input parameters.
An Aeolus Template is a collection of services along with the operating system for an instance in a cloud. The image above depicts a Fedora template with several services. A template does not contain any values for the service parameters. Instead, it is a boilerplate representation of a class of instances. Additionally, it serves as the basis for creating images through Image Factory (see the aeolus project site for more information on Image Factory).
The physical representation of a template is an XML file containing a template name, the OS, a description, and a set of services.
<template name="Template1"> <os> <name>Fedora</name> <version>14</version> </os> <description>This is a template</description> <services> <service name="service-name"> <scripts> <script>puppet-class-name</script> <script>puppet-class-name</script> </scripts> <parameter name="param-name" type="scalar" required="true"/> <!-- a parameter that must have a value in an assembly --> <parameter name="param2-name" type="scalar" provided="true"/> <!-- a parameter whose value can be used by other assemblies in a deployable --> </service> </services> </template>
An Aeolus Assembly is really a template with values provided for the service parameters. These values are depicted as “user data” in the image above. The assembly represents the data required to configure a single instance in a cloud.
An assembly is physically represented as an XML document with a name and a set of services. The name captures the assembly “type”. An assembly type is a way of classifying the assembly for a particular purpose. For instance, there might be a “mysql” assembly type that indicates instances based on that assembly will be MySQL Database Servers.
<assembly name="AssemblyType"> <template type="Template1"/> <services> <service name="service-name"> <parameter name="param-name"> <value><![CDATA[Parameter Value]]></value> </parameter> <parameter name="another-parameter"> <reference assembly="AnotherAssembly" provided-parameter="param-from-another-assembly"/> </parameter> </service> </services> </assembly>
An Aeolus Deployable is a logical grouping of assemblies to produce a complete “system”; where each assembly maps to a single instance in a cloud. For example, a deployable might contain a an application server cluster (two or more “app-server” assemblies), a load balancer (a single “apache load balancer” assembly), and a database server (a single “mysql” assembly).
The deployable XML format captures a list of assemblies. Each assembly referenced in the deployable contains three values: the assembly type, the assembly name, and the hardware profile for the instance. The assembly type references the assembly “name” from the assembly XML file. The assembly name in the deployable is a unique name for that instance of the assembly in this deployable. The hardware profile indicates what size/type of virtual machine should be used when creating the instance in the cloud.
The assemblies in a deployable often need to share information in order to be fully configured. For instance, a database server might need to share connection information with application servers. This is handled through one assembly consuming the output parameter of another assembly.
There are several models of implementing this type of dependency. Since an output parameter is scoped to a service in an assembly, there are at least three ways to model this relationship.
In this scenario, one assembly specifically calls out another assembly by name and refers to a parameter in that assembly. For example:
<assembly name="name"> ... <parameter name="param-name"> <reference assembly="another-assembly-name" provided-parameter="param-from-aother-assembly"/> </parameter> ... </assembly>
When referencing the assembly name directly (“another-assembly-name”), the value pulled in for this parameter is guaranteed to come from one specific assembly. In other words, this is one instance depending on a value from another single specific instance. Or, a one-to-one relationship.
Referencing an external parameter by assembly type raises a separate question of, “Do you want the parameter value from all matching assemblies in the deployable?” For instance, this relationship could be captured by the following example:
<assembly name="name"> ... <parameter name="param-name"> <reference assembly-type="AssemblyType" provided-parameter="param-from-assembly-type"/> </parameter> ... </assembly>
In this scenario, it is assumed that all assemblies of type “AssemblyType” in the deployable could provide this value and that the scripts for the configuration engine that execute on the instance can deal with a list of data for the value of “param-name”.
However, it’s possible that the goal of this usage is to acquire the first matching value for “param-from-assembly-type” from any assembly of type “AssemblyType” in the deployable. In that case, the relationship might look more like:
<assembly name="name"> ... <parameter name="param-name"> <reference assembly-type="AssemblyType" match-type="first" provided-parameter="param-from-assembly-type"/> </parameter> ... </assembly>
In this example, the reference element specifically calls out that it only wants the first one that reports a value. It’s dubious whether this could actually be useful, since it is entirely unpredictable which instance in the deployable will be the first to report a value.
This is similar to the previous example, but even less restrictive. Instead of asking for all values (or, one value) from a specific assembly type, this example asks for all values (or, one value) from a service. And, since a service name is not bound to any assembly type, the service could exist in many different types of assemblies in a deployable. This relationship might look like:
<assembly name="name"> ... <parameter name="param-name"> <reference service="service-name" provided-parameter="param-from-service"/> </parameter> ... </assembly>
Similar to the previous example of referencing an assembly type, the question of “Do you want the parameter value from all matching services in the deployable?” If the answer is no, then perhaps the relationship could be extended to include a match-type:
<assembly name="name"> ... <parameter name="param-name"> <reference service="service-name" match-type="first" provided-parameter="param-from-service"/> </parameter> ... </assembly>
<deployable name="Deployable1"> <assemblies> <assembly name="FirstAssembly" type="AssemblyType" hwp="large"/> <assembly name="SecondAssembly" type="AssemblyType" hwp="large"/> </assemblies> </deployable>
Audrey is not a single piece of infrastructure. Instead, it is several components spread throughout Aeolus. The diagram below depicts the various pieces of Audrey that exist in the workflow of spinning up instances in a cloud.
There are several moving parts in the diagram above:
Deployable | The collection of assemblies that will generate instances in a cloud | ||
Cloud Engine | A collection of tools in the aeolus project that work together to generate instances in various cloud providers | | Cloud |
A cloud provider |
Config Server | A piece of Audrey that manages the instance configuration data | ||
Instance | The actual instance—created by Cloud Engine—in a cloud |
The process follows the diagram from the top left to the bottom right. A deployable is fed to Cloud Engine, which creates the instance for a cloud provider and generates the “instance configuration” data to post to the Config Server. As the instances start up in the cloud, the Audrey component in the instance is instructed to “phone home” to the same Config Server that received the “instance configuration” data. The Config Server then sends down configuration information to the instance so that it might configure itself.
To generate the instance configuration, the Cloud Engine relies on Conductor to process the deployable and produce data that can be consumed by the Config Server. This “instance configuration” is an XML file that captures the slice of the deployable that is relevant to a single instance. Essentially, this amounts to:
- Template
- Assembly with “user data”
- Deployable name
- Deployable UUID (generated by Conductor)
- Instance UUID (generated by Conductor)
<instance-config id="INSTANCE_UUID" type="AssemblyType" name="AssemblyName"> <deployable name="DeployableName" id="DEPLOYABLE_UUID"/> <template name="TemplateName"/> <provided-parameters> <provided-parameter name="param-name"/> </provided-parameters> <services> <service name="service-name"> <scripts> <script name="puppet-class-name"/> <script name="puppet-class-name"/> </scripts> <parameter name="param1-name"> <value><![CDATA[Param1 Value]]></value> </parameter> <parameter name="param2-name"> <reference assembly="Assembly2Name" provided-parameter="parameter-from-assembly2"/> </parameter> </service> </services> </instance-config>
The Config Server handles providing the configuration information to instances as they ask for it. This configuration information is a configuration engine agnostic format that the client tools on the instance know how to parse and produce configuration engine specific input. The diagram below shows the overview of interacting with the Config Server:
Essentially, this diagram is a zoomed in version of the previous. This shows the specific interactions with the Config Server and cuts out much of what is happening in the Cloud Engine. For the Config Server to provide complete configuration data to an instance, several things take place:
- Conductor POSTs instance configuration to the Config Server
- The Audrey component on the Instance (client tools) “learns” how to contact the Config Server (not shown—see Temp#Client-Tooling-Audrey-Client below)
- The client tools GET the configuration data
- The client tools GET the parameters that the Config Server expects the instance to provide back (the “provided-parameters” section from the instance-config XML document)
- The client tools PUT the “provided-parameters” values back to the Config Server.
For the different types of requests to the Config Server, there are a set of specific responses that can occur.
Request | Status Code | Description |
---|---|---|
POST /configs/$UUID | 201 | The instance configuration document was received and a record was created for the instance |
400 | The instance configuration document is not valid | |
GET /configs/$UUID | 200 | The complete set of instance configs is returned in the response body. There’s no need to request again. |
202 | The response body contains a portion of the complete set of instance configs. Try again soon for the complete set. | |
404 | The requested URL cannot be found. | |
GET /params/$UUID | 200 | The config server does not require anymore provided parameters from the instance. |
202 | The response body contains the set of provided parameters still required by the config server. | |
404 | The requested URL cannot be found. | |
PUT /reply/$UUID | 200 | The values for the provided params in the request body have been updated for $UUID. |
404 | The $UUID in the request is not known by the config server. |
Since there is no way for the Config Server to predetermine which instance will be the first to execute the PUT (step 5, from above), assembly dependencies are not guaranteed to be fulfilled before an instance does the first GET (step 3). For example, if Assembly1 depends on provided parameters from Assembly2, but Assembly1 does its GET (step 3) before Assembly2 does its PUT (step 5), then the configurations available to Assembly1 will be incomplete.
There is a choice to make in this situation: allow partial configs to be sent to instance, or do not allow partial configs? The best way to handle this choice is probably to make it configurable either at the assembly level or the service level.
If the assembly (or service) is not allowed to partially configure, then the Config Server will respond to step 3 with an HTTP 204 (no content). If the assembly (or service) is allowed to partially configure, then the Config Server will return the configuration data it can pull together for the assembly (or service) as the response to step 3.