Deployment Manager (DM) is a service that runs in a Kubernetes cluster,
supported by a command line interface. It provides a declarative YAML
-based
language for configuring Kubernetes resources, and a mechanism for deploying,
updating, and deleting configurations. This document describes the configuration
language, the API model, and the service architecture in detail.
DM uses a YAML
-based configuration language with a templating mechanism. A
configuration is a YAML
file that describes a list of resources. A resource has
three properties:
name
: the name to use when managing the resourcetype
: the type of the resource being configuredproperties
: the configuration properties of the resource
Here's a snippet from a typical configuration file:
resources:
- name: my-rc
type: ReplicationController
properties:
metadata:
name: my-rc
spec:
replicas: 1
...
- name: my-service
type: Service
properties:
...
It describes two resources:
- A replication controller named
my-rc
, and - A service named
my-service
Resource types are either primitives or templates.
Primitives are types implemented by the Kubernetes runtime, such as:
Pod
ReplicationController
Service
Namespace
Secret
DM processes primitive resources by passing their properties directly to
kubectl
to create, update, or delete the corresponding objects in the cluster.
(Note that DM runs kubectl
server side, in a container.)
Templates are abstract types created using Python or
Jinja. A template takes a set of properties as input,
and must output a valid YAML
configuration. Properties are bound to values when
a template is instantiated by a configuration.
Templates are expanded before primitive resources are processed. The configuration produced by expanding a template may contain primitive resources and/or additional template invocations. All template invocations are expanded recursively until the resulting configuration is a list of primitive resources.
(Note, however, that DM preserves the template hierarchy and any dependencies between resources in a layout that can be used to reason programmatically about the structure of the resulting collection of resources created in the cluster, as described in greater detail below.)
Here's an example of a template written in Jinja:
resources:
- name: {{ env['name'] }}-service
type: Service
properties:
prop1: {{ properties['prop1'] }}
...
As you can see, it's just a YAML
file that contains expansion directives. For
more information about the kinds of things you can do in a Jinja based template,
see the Jina documentation.
Here's an example of a template written in Python:
import yaml
def GenerateConfig(context):
resources = [{
'name': context.env['name'] + '-service',
'type': 'Service',
'properties': {
'prop1': context.properties['prop1'],
...
}
}]
return yaml.dump({'resources': resources})
Of course, you can do a lot more in Python than in Jinja, but basic things, such as simple parameter substitution, may be easier to implement and easier to read in Jinja than in Python.
Templates provide access to multiple sets of data that can be used to parameterize or further customize configurations:
env
: a map of key/value pairs from the environment, including pairs defined by Deployment Manager, such asdeployment
,name
, andtype
properties
: a map of the key/value pairs passed in the properties section of the template invocationimports
: a map of import file names to file contents for all imports originally specified for the configuration
In Jinja, these variables are available in the global scope. In Python, they are
available as properties of the context
object passed into the GenerateConfig
method.
A template can optionally be accompanied by a schema that describes it in more detail, including:
info
: more information about the template, including long description and titleimports
: any files imported by this template (may be relative paths or URLs)required
: properties that must have values when the template is expandedproperties
: AJSON Schema
description of each property the template accepts
Here's an example of a template schema:
info:
title: The Example
description: A template being used as an example to illustrate concepts.
imports:
- path: helper.py
required:
- prop1
properties:
prop1:
description: The first property
type: string
default: prop-value
When a schema is provided for a template, DM uses it to validate properties passed to the template by its invocation, and to provide default values for properties that were not given values.
Schemas must be supplied to DM along with the templates they describe.
Templates can be supplied to DM in two different ways:
- They can be passed to DM along with configurations that import them, or
- They can be retrieved by DM from public HTTP endpoints for configurations that reference them.
Configurations can import templates using path declarations. For example:
imports:
- path: example.py
resources:
- name: example
type: example.py
properties:
prop1: prop-value
The imports
list is not understood by the Deployment Manager service.
It's a directive used by client-side tools to specify what additional files
should be included when passing the configuration to the API.
If you are calling the Deployment Manager service directly, you must embed the imported templates in the configuration passed to the API.
Configurations can also reference templates using URLs for public HTTP endpoints. DM will attempt to resolve template references during expansion. For example:
resources:
- name: my-template
type: https://raw.githubusercontent.com/my-template/my-template.py
properties:
prop1: prop-value
When resolving template references, DM assumes that templates are stored in directories, which may also contain schemas, examples and other supporting files. It therefore processes template references as follows:
- Attempt to fetch the template, and treat it as an import.
- Attempt to fetch the schema for the template from
<base path>/<template name>.schema
- Attempt to fetch files imported by the schema from
<base path>/<import path>
Referring to the previous example,
- the base path is
https://raw.githubusercontent.com/my-template
, - the template name is
my-template
, and - the schema name is
my-template.schema
If we include a configuration that uses the template as an example, then the
directory that contains my-template
might look like this:
example.yaml
my-template.py
my-template.py.schema
helper.py
Resources can reference values from other resources. The version of Deployment Manager running in the Google Cloud Platform uses references to understand dependencies between resources and properly order the operations it performs on a configuration.
(Note that this version of DM doesn't yet order operations to satisfy dependencies, but it will soon.)
A reference follows this syntax: $(ref.NAME.PATH)
, where NAME
is the name
of the resource being referenced, and PATH
is a JSON
path to the value in the
resource object.
For example:
$(ref.my-service.metadata.name)
In this case, my-service
is the name of the resource, and metadata.name
is
the JSON
path to the value being referenced.
DM exposes a set of RESTful collections over HTTP/JSON.
Deployments are the primary resources managed by the Deployment Manager service. The inputs to a deployment are:
name
: the name by which the deployment can be referenced once createdconfiguration
: the configuration file, plus any imported files (templates, schemas, helper files used by the templates, etc.).
Creating, updating or deleting a deployment creates a new manifest for the deployment. When deleting a deployment, the deployment is first updated to an empty manifest containing no resources, and then removed from the system.
Deployments are available at the HTTP endpoint:
http://manager-service/deployments
A manifest is created for a deployment every time it is changed. It contains three key components:
inputConfig
: the original input configuration for the manifestexpandedConfig
: the expanded configuration describing only primitive resourceslayout
: the hierarchical structure of the configuration
Manifests are available at the HTTP endpoint:
http://manager-service/deployments/<deployment>/manifests
Given a new inputConfig
, DM expands all template invocations recursively,
until the result is a flat set of primitive resources. This final set is stored
as the expandedConfig
and is used to instantiate the primitive resources.
Using templates, callers can build rich, deeply hierarchical architectures in
their configurations. Expansion flattens these hierarchies to simplify the process
of instantiating the primitive resources. However, the structural information
contained in the original configuration has many potential uses, so rather than
discard it, DM preserves it in the form of a layout
.
The layout
looks a lot like the original configuration. It is a YAML
file
that describes a list of resources. Each resource contains the name
, type
and properties
from the original configuration, plus a list of nested resources
discovered during expansion. The resulting structure looks like this:
- name: name of the resource
- type: type of the resource
- properties: properties of the resource, set only for templates
- resources: sub-resources from expansion, set only for templates
Here's an example of a layout:
resources:
- name: rs
type: replicatedservice.py
propertes:
replicas: 2
resources:
- name: rs-rc
type: ReplicationController
- name: rs-service
type: Service
In this example, the top level resource is a replicated service named rs
,
defined by the template named replicatedservice.py
. Expansion produced the
two nested resources: a replication controller named rs-rc
, and a service
named rs-service
.
Using the layout, callers can discover that rs-rc
and rs-service
are part
of the replicated service named rs
. More importantly, if rs
was created by
the expansion of a larger configuration, such as one that described a complete
application, callers could discover that rs-rc
and rs-service
were part of
the application, and perhaps even that they were part of a RabbitMQ cluster in
the application's mid-tier.
The types API provides information about types used in the cluster.
It can be used to list all known types used by active deployments:
http://manager-service/types
Or to list all active instances of a specific type in the cluster:
http://manager-service/types/<type>/instances
Passing all
as the type name shows all instances of all types in the
cluster. The following information is reported for type instances:
- name: name of resource
- type: type of resource
- deployment: name of deployment in which the resource resides
- manifest: name of manifest in which the resource configuration resides
- path: JSON path to the entry for the resource in the manifest layout
The Deployment Manager service is manages deployments within a Kubernetes cluster. It has three major components. The following diagram illustrates the components and the relationships between them.
Currently, there are two caveats in the service implementation:
- Synchronous API: the API currently blocks on all processing for a deployment request. In the future, this design will change to an asynchronous operation-based mode.
- In-memory state: the service currently stores all state in memory, so it will lose all knowledge of deployments and related objects on restart. In the future, the service will persist all state in the cluster.
The manager
service acts as both the API server and the workflow engine for
processing deployments. It handles a POST
to the /deployments
collection as
follows:
- Create a new deployment with a manifest containing
inputConfig
from the user request - Call out to the
expandybird
service to expand theinputConfig
- Store the resulting
expandedConfig
andlayout
- Call out to the
resourcifier
service to instantiate the primitive resources described by theexpandedConfig
- Respond with success or error messages to the original API request
GET
, PUT
and DELETE
operations are processed in a similar manner, except
that:
- No expansion is performed for
GET
orDELETE
- The primitive resources are updated for
PUT
and deleted forDELETE
The manager is responsible for saving the information associated with deployments, manifests, type instances, and other resources in the Deployment Manager model.
The expandybird
service takes in a configuration, performs all necesary
template expansions, and returns the resulting flat configuration and layout.
It is completely stateless.
Because templates are written in Python or Jinja, the actual expansion process
is performed in a sub-process that runs a Python interpreter. A new sub-process
is created for every request to expandybird
.
Currently, expansion is not sandboxed, but templates should be reproducable, hermetically sealed entities. Future designs may therefore introduce a sandbox to limit external interaction, such as network or disk access, during expansion.
The resourcifier
service takes in a flat expanded configuration describing
only primitive resources, and makes the necessary kubectl
calls to process
them. It is totally stateless, and handles requests synchronously.
The resourcifier
runs kubectl
in a sub-process within its container. A new
sub-process is created for every request to resourcifier
.
It returns either success or error messages encountered during resource processing.