mirror of https://github.com/helm/helm
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
435 lines
14 KiB
435 lines
14 KiB
# Deployment Manager Design
|
|
|
|
## Overview
|
|
|
|
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.
|
|
|
|
## Configuration Language
|
|
|
|
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 resource
|
|
* `type`: the type of the resource being configured
|
|
* `properties`: 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`
|
|
|
|
## Types
|
|
|
|
Resource types are either primitives or templates.
|
|
|
|
### Primitives
|
|
|
|
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
|
|
|
|
Templates are abstract types created using Python or
|
|
[Jinja](http://jinja.pocoo.org/). 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](http://jinja.pocoo.org/):
|
|
|
|
```
|
|
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](http://jinja.pocoo.org/docs/).
|
|
|
|
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 as `deployment`, `name`, and `type`
|
|
* `properties`: a map of the key/value pairs passed in the properties section
|
|
of the template invocation
|
|
* `imports`: 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.
|
|
|
|
### Template schemas
|
|
|
|
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 title
|
|
* `imports`: any files imported by this template (may be relative paths or URLs)
|
|
* `required`: properties that must have values when the template is expanded
|
|
* `properties`: A `JSON 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.
|
|
|
|
### Supplying templates
|
|
|
|
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.
|
|
|
|
#### Template imports
|
|
|
|
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.
|
|
|
|
#### Template references
|
|
|
|
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:
|
|
|
|
1. Attempt to fetch the template, and treat it as an import.
|
|
1. Attempt to fetch the schema for the template from
|
|
`<base path>/<template name>.schema`
|
|
1. 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
|
|
```
|
|
|
|
### Value references
|
|
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.
|
|
|
|
## API Model
|
|
|
|
DM exposes a set of RESTful collections over HTTP/JSON.
|
|
|
|
### Deployments
|
|
|
|
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 created
|
|
* `configuration`: 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
|
|
```
|
|
|
|
### Manifests
|
|
|
|
A manifest is created for a deployment every time it is changed. It contains
|
|
three key components:
|
|
|
|
* `inputConfig`: the original input configuration for the manifest
|
|
* `expandedConfig`: the expanded configuration describing only primitive resources
|
|
* `layout`: the hierarchical structure of the configuration
|
|
|
|
Manifests are available at the HTTP endpoint:
|
|
|
|
```
|
|
http://manager-service/deployments/<deployment>/manifests
|
|
```
|
|
|
|
#### Expanded configuration
|
|
|
|
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.
|
|
|
|
#### Layout
|
|
|
|
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.
|
|
|
|
### Types
|
|
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
|
|
|
|
## Architecture
|
|
|
|
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.
|
|
|
|
![Architecture Diagram](architecture.png "Architecture Diagram")
|
|
|
|
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.
|
|
|
|
### Manager
|
|
|
|
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:
|
|
|
|
1. Create a new deployment with a manifest containing `inputConfig` from the
|
|
user request
|
|
1. Call out to the `expandybird` service to expand the `inputConfig`
|
|
1. Store the resulting `expandedConfig` and `layout`
|
|
1. Call out to the `resourcifier` service to instantiate the primitive resources
|
|
described by the `expandedConfig`
|
|
1. 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` or `DELETE`
|
|
* The primitive resources are updated for `PUT` and deleted for `DELETE`
|
|
|
|
The manager is responsible for saving the information associated with
|
|
deployments, manifests, type instances, and other resources in the Deployment
|
|
Manager model.
|
|
|
|
### Expandybird
|
|
|
|
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.
|
|
|
|
### Resourcifier
|
|
|
|
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.
|