Merge remote-tracking branch 'upstream/master'

pull/71/head^2
Robert Leenders 10 years ago
commit e4fb3cc0e4

@ -0,0 +1,14 @@
sudo: true
language: go
go:
- 1.4
install:
- sudo pip install jinja2
- sudo pip install pyyaml
- sudo pip install jsonschema
script:
- make test

@ -2,26 +2,28 @@
[![Go Report Card](http://goreportcard.com/badge/kubernetes/deployment-manager)](http://goreportcard.com/report/kubernetes/deployment-manager)
Deployment Manager (DM) provides parameterized templates for Kubernetes clusters.
Deployment Manager (DM) provides parameterized templates for Kubernetes resources,
such as:
You can use it deploy ready-to-use types, such as:
* [Replicated Service](types/replicatedservice/v1)
* [Redis](types/redis/v1)
* [Replicated Service](templates/replicatedservice/v1)
* [Redis](templates/redis/v1)
Types live in ordinary Github repositories. This repository is a DM type registry.
Templates live in ordinary Github repositories called template registries. This
Github repository contains a template registry, as well as the DM source code.
You can also use DM to deploy simple templates that use types, such as:
You can use DM to deploy simple configurations that use templates, such as:
* [Guestbook](examples/guestbook/guestbook.yaml)
* [Deployment Manager](examples/bootstrap/bootstrap.yaml)
A template is just a `YAML` file that supplies parameters. (Yes, you're reading
that second example correctly. It uses DM to deploy itself.
See [examples/bootstrap/README.md](examples/bootstrap/README.md) for more information.)
A configuration is just a `YAML` file that supplies parameters. (Yes,
you're reading that second example correctly. It uses DM to deploy itself. See
[examples/bootstrap/README.md](examples/bootstrap/README.md) for more information.)
DM runs server side, in your Kubernetes cluster, so it can tell you what types
you've instantiated there, what instances you've created of a given type, and even
how the instances are organized. So, you can ask questions like:
you've instantiated there, including both primitive types and templates, what
instances you've created of a given type, and even how the instances are organized.
So, you can ask questions like:
* What Redis instances are running in this cluster?
* What Redis master and slave services are part of this Redis instance?
@ -30,10 +32,12 @@ how the instances are organized. So, you can ask questions like:
Because DM stores its state in the cluster, not on your workstation, you can ask
those questions from any client at any time.
For more information about types, including both primitive types and templates,
see the [design document](../design/design.md#types).
Please hang out with us in
[the Slack chat room](https://kubernetes.slack.com/messages/sig-configuration/)
and/or
[the Google Group](https://groups.google.com/forum/#!forum/kubernetes-sig-config)
and/or [the Google Group](https://groups.google.com/forum/#!forum/kubernetes-sig-config)
for the Kubernetes configuration SIG. Your feedback and contributions are welcome.
## Installing Deployment Manager
@ -43,11 +47,10 @@ Follow these 3 steps to install DM:
1. Make sure your Kubernetes cluster is up and running, and that you can run
`kubectl` commands against it.
1. Clone this repository into the src folder of your GOPATH, if you haven't already.
1. Use `kubectl` to install DM into your cluster:
```
kubectl create -f install.yaml
```
See the [Kubernetes developer documentation](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/development.md)
for information on how to setup Go and use the repository.
1. Use `kubectl` to install DM into your cluster `kubectl create -f
install.yaml`
That's it. You can now use `kubectl` to see DM running in your cluster:
@ -59,38 +62,29 @@ If you see expandybird-service, manager-service, resourcifier-service, and
expandybird-rc, manager-rc and resourcifier-rc with pods that are READY, then DM
is up and running!
The easiest way to interact with Deployment Manager is through `kubectl` proxy:
## Using Deployment Manager
### Setting up the client
```
kubectl proxy --port=8001 &
```
The easiest way to interact with Deployment Manager is through the `dm` tool
hitting a `kubectl` proxy. To set that up:
This command starts a proxy that lets you interact with the Kubernetes api
server through port 8001 on localhost. `dm` uses
1. Build the tool by running `make` in the deployment-manager repository.
1. Run `kubectl proxy --port=8001 &` to start a proxy that lets you interact
with the Kubernetes API server through port 8001 on localhost. `dm` uses
`http://localhost:8001/api/v1/proxy/namespaces/default/services/manager-service:manager`
as the default service address for DM.
## Using Deployment Manager
You can use `dm` to deploy a type from the command line. This command deploys a
redis cluster with two workers from the type definition in this repository:
### Using the client
```
dm deploy redis/v1
```
When you deploy a type, you can optionally supply values for input parameters,
like this:
```
dm --properties workers=3 deploy redis/v1
```
The DM client, `dm`, can deploy configurations from the command line. It can also
pull templates from a template registry, generate configurations from them using
parameters supplied on the command line, and deploy the resulting configurations.
When you deploy a type, `dm` generates a template from the type and input
parameters, and then deploys it.
#### Deploying a configuration
You can also deploy an existing template, or read one from `stdin`. This command
deploys the canonical Guestbook example from the examples directory:
`dm` can deploy a configuration from a file, or read one from `stdin`. This
command deploys the canonical Guestbook example from the examples directory:
```
dm deploy examples/guestbook/guestbook.yaml
@ -108,64 +102,71 @@ to see the guestbook in action.
For more information about this example, see [examples/guestbook/README.md](examples/guestbook/README.md)
## Additional commands
#### Deploying a template directly
The command line tool makes it easy to configure a cluster from a set of predefined
types. Here's a list of available commands:
You can also deploy a template directly, without a configuration. This command
deploys a redis cluster with two workers from the redis template in this repository:
```
expand Expands the supplied template(s)
deploy Deploys the supplied type or template(s)
list Lists the deployments in the cluster
get Retrieves the supplied deployment
delete Deletes the supplied deployment
update Updates a deployment using the supplied template(s)
deployed-types Lists the types deployed in the cluster
deployed-instances Lists the instances of the supplied type deployed in the cluster
types Lists the types in the current registry
describe Describes the supplied type in the current registry
dm deploy redis:v1
```
You can optionally supply values for template parameters on the command line,
like this:
```
dm --properties workers=3 deploy redis:v1
```
## Uninstalling Deployment Manager
When you deploy a template directly, without a configuration, `dm` generates a
configuration from the template and any supplied parameters, and then deploys the
generated configuration.
For more information about deploying templates from a template registry or adding
types to a template registry, see [the template registry documentation](docs/templates/registry.md).
You can uninstall Deployment Manager using the same configuration file:
### Additional commands
`dm` makes it easy to configure a cluster from a set of predefined templates.
Here's a list of available `dm` commands:
```
kubectl delete -f install.yaml
expand Expands the supplied configuration(s)
deploy Deploys the named template or the supplied configuration(s)
list Lists the deployments in the cluster
get Retrieves the named deployment
delete Deletes the named deployment
update Updates a deployment using the supplied configuration(s)
deployed-types Lists the types deployed in the cluster
deployed-instances Lists the instances of the named type deployed in the cluster
templates Lists the templates in a given template registry
describe Describes the named template in a given template registry
```
## Creating a type registry
All you need to create a type registry is a Github repository with top level file
named `registry.yaml`, and a top level folder named `types` that contains type definitions.
## Uninstalling Deployment Manager
A type definition is just a folder that contains one or more versions, like `/v1`,
`/v2`, etc.
You can uninstall Deployment Manager using the same configuration:
A version is just a folder that contains a type definition. As you can see from the
examples above, a type definition is just a Python or [Jinja](http://jinja.pocoo.org/)
file plus an optional schema.
```
kubectl delete -f install.yaml
```
## Building the container images
## Building the Container Images
This project runs Deployment Manager on Kubernetes as three replicated services.
By default, install.yaml uses prebuilt images stored in Google Container Registry
to install them. However, you can build your own container images and push them
to your own project in the Google Container Registry:
1. Set the environment variable PROJECT to the name of a project known to gcloud.
1. Run the following command:
```
make push
```
1. Set the environment variable `PROJECT` to the name of a project known to
GCloud.
1. Run `make push`
## Design of Deployment Manager
There is a more detailed [design document](docs/design/design.md) available.
## Status of the project
## Status of the Project
This project is still under active development, so you might run into issues. If
you do, please don't be shy about letting us know, or better yet, contribute a
@ -177,5 +178,3 @@ DM uses the same concepts and languages as
[Google Cloud Deployment Manager](https://cloud.google.com/deployment-manager/overview),
but creates resources in Kubernetes clusters, not in Google Cloud Platform projects.

@ -36,29 +36,28 @@ import (
)
var (
// TODO(jackgr): Implement reading a template from stdin
//stdin = flag.Bool("stdin", false, "Reads a template from the standard input")
properties = flag.String("properties", "", "Properties to use when deploying a type (e.g., --properties k1=v1,k2=v2)")
type_registry = flag.String("registry", "kubernetes/deployment-manager", "Github based type registry [owner/repo]")
service = flag.String("service", "http://localhost:8001/api/v1/proxy/namespaces/default/services/manager-service:manager", "URL for deployment manager")
binary = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary")
stdin = flag.Bool("stdin", false, "Reads a configuration from the standard input")
properties = flag.String("properties", "", "Properties to use when deploying a template (e.g., --properties k1=v1,k2=v2)")
template_registry = flag.String("registry", "kubernetes/deployment-manager/templates", "Github based template registry (owner/repo[/path])")
service = flag.String("service", "http://localhost:8001/api/v1/proxy/namespaces/default/services/manager-service:manager", "URL for deployment manager")
binary = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary")
)
var commands = []string{
"expand \t\t\t Expands the supplied template(s)",
"deploy \t\t\t Deploys the supplied type or template(s)",
"expand \t\t\t Expands the supplied configuration(s)",
"deploy \t\t\t Deploys the named template or the supplied configuration(s)",
"list \t\t\t Lists the deployments in the cluster",
"get \t\t\t Retrieves the supplied deployment",
"delete \t\t\t Deletes the supplied deployment",
"update \t\t\t Updates a deployment using the supplied template(s)",
"update \t\t\t Updates a deployment using the supplied configuration(s)",
"deployed-types \t\t Lists the types deployed in the cluster",
"deployed-instances \t Lists the instances of the supplied type deployed in the cluster",
"types \t\t\t Lists the types in the current registry",
"describe \t\t Describes the supplied type in the current registry",
"deployed-instances \t Lists the instances of the named type deployed in the cluster",
"templates \t\t Lists the templates in a given template registry",
"describe \t\t Describes the named template in a given template registry",
}
var usage = func() {
message := "Usage: %s [<flags>] <command> (<type-name> | <deployment-name> | (<template> [<import1>...<importN>]))\n"
message := "Usage: %s [<flags>] <command> (<template-name> | <deployment-name> | (<configuration> [<import1>...<importN>]))\n"
fmt.Fprintf(os.Stderr, message, os.Args[0])
fmt.Fprintln(os.Stderr, "Commands:")
for _, command := range commands {
@ -73,12 +72,17 @@ var usage = func() {
}
func getGitRegistry() *registry.GithubRegistry {
s := strings.Split(*type_registry, "/")
if len(s) != 2 {
log.Fatalf("invalid type registry: %s", type_registry)
s := strings.Split(*template_registry, "/")
if len(s) < 2 {
log.Fatalf("invalid template registry: %s", *template_registry)
}
return registry.NewGithubRegistry(s[0], s[1])
var path = ""
if len(s) > 2 {
path = strings.Join(s[2:], "/")
}
return registry.NewGithubRegistry(s[0], s[1], path)
}
func main() {
@ -89,27 +93,32 @@ func main() {
usage()
}
if *stdin {
fmt.Printf("reading from stdin is not yet implemented")
os.Exit(0)
}
command := args[0]
switch command {
case "types":
case "templates":
git := getGitRegistry()
types, err := git.List()
templates, err := git.List()
if err != nil {
log.Fatalf("Cannot list %v err")
log.Fatalf("Cannot list %v", err)
}
fmt.Printf("Types:")
for _, t := range types {
fmt.Printf("%s:%s", t.Name, t.Version)
fmt.Printf("Templates:\n")
for _, t := range templates {
fmt.Printf("%s:%s\n", t.Name, t.Version)
downloadURL, err := git.GetURL(t)
if err != nil {
log.Printf("Failed to get download URL for type %s:%s", t.Name, t.Version)
log.Printf("Failed to get download URL for template %s:%s", t.Name, t.Version)
}
fmt.Printf("\tdownload URL: %s", downloadURL)
fmt.Printf("\tdownload URL: %s\n", downloadURL)
}
case "describe":
fmt.Printf("this feature is not yet implemented")
fmt.Printf("the describe feature is not yet implemented")
case "expand":
backend := expander.NewExpander(*binary)
template := loadTemplate(args)
@ -121,7 +130,7 @@ func main() {
fmt.Println(output)
case "deploy":
template := loadTemplate(args)
action := fmt.Sprintf("deploy template named %s", template.Name)
action := fmt.Sprintf("deploy configuration named %s", template.Name)
callService("deployments", "POST", action, marshalTemplate(template))
case "list":
callService("deployments", "GET", "list deployments", nil)
@ -149,8 +158,7 @@ func main() {
action := fmt.Sprintf("delete deployment named %s", template.Name)
callService(path, "PUT", action, marshalTemplate(template))
case "deployed-types":
action := fmt.Sprintf("list types in registry %s", *type_registry)
callService("types", "GET", action, nil)
callService("types", "GET", "list deployed types", nil)
case "deployed-instances":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "No type name supplied")
@ -158,7 +166,7 @@ func main() {
}
path := fmt.Sprintf("types/%s/instances", url.QueryEscape(args[1]))
action := fmt.Sprintf("list instances of type %s in registry %s", args[1], *type_registry)
action := fmt.Sprintf("list deployed instances of type %s", args[1])
callService(path, "GET", action, nil)
default:
usage()
@ -193,7 +201,7 @@ func loadTemplate(args []string) *expander.Template {
var template *expander.Template
var err error
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "No type name or template file(s) supplied")
fmt.Fprintln(os.Stderr, "No template name or configuration(s) supplied")
usage()
}
@ -208,7 +216,7 @@ func loadTemplate(args []string) *expander.Template {
}
if err != nil {
log.Fatalf("cannot create template from supplied arguments: %s\n", err)
log.Fatalf("cannot create configuration from supplied arguments: %s\n", err)
}
return template
@ -275,7 +283,7 @@ func buildTemplateFromType(name string, t registry.Type) *expander.Template {
func marshalTemplate(template *expander.Template) io.ReadCloser {
j, err := json.Marshal(template)
if err != nil {
log.Fatalf("cannot deploy template %s: %s\n", template.Name, err)
log.Fatalf("cannot deploy configuration %s: %s\n", template.Name, err)
}
return ioutil.NopCloser(bytes.NewReader(j))

@ -1,25 +1,24 @@
# Deployment Manager Design
## Overview
Deployment Manager is a service that runs in a Kubernetes cluster. It
provides a declarative configuration language to describe Kubernetes
resources, and a mechanism for deploying, updating, and deleting configurations.
This document describes the configuration language, object model, and
architecture of the service in detail.
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
The configuration language in Deployment Manager consists of two parts: a
YAML-based language for describing resources, and a templating mechanism for
creating abstract parameterizable types.
A configuration consists of a list of resources in YAML. Resources have three
properties:
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 managed
* properties: the configuration properties of the resource
* `name`: the name to use when managing the resource
* `type`: the type of the resource being configured
* `properties`: the configuration properties of the resource
An example snippet of a configuration looks like:
Here's a snippet from a typical configuration file:
```
resources:
@ -37,51 +36,63 @@ resources:
...
```
### 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. (This version of DM doesn't yet order operations to satisfy
dependencies, but it will soon.)
It describes two resources:
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.
* A replication controller named `my-rc`, and
* A service named `my-service`
For example:
## Types
```
$(ref.my-service.metadata.name)
```
Resource types are either primitives or templates.
In this case, _my-service_ is the name of the resource, and _metadata.name_ is
the JSON path to the value being referenced.
### Primitives
### Configurable Resources
Primitives are types implemented by the Kubernetes runtime, such as:
Configurable resources are the primitive resources that can be configured in
Deployment Manager, including:
* `Pod`
* `ReplicationController`
* `Service`
* `Namespace`
* `Secret`
* Pod
* ReplicationController
* Service
DM processes primitive resources by passing their properties directly to
`kubectl` to create, update, or delete the corresponding objects in the cluster.
Deployment Manager processes configurable resources by passing their
configuration properties directly to kubectl to create, update, or delete them
in the cluster.
(Note that DM runs `kubectl` server side, in a container.)
### Templates
Templates are abstract types that can be created using Python or
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 string. Properties are bound to
values when a template is instantiated in a configuration.
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.
Templates are expanded in a pre-processing step before configurable resources
are processed. They can output configurations containing configurable resources,
or additional nested templates. Nested templates are processed recursively.
(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.)
An example of a template in python is:
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
@ -99,39 +110,33 @@ def GenerateConfig(context):
return yaml.dump({'resources': resources})
```
and in Jinja:
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.
```
resources:
- name: {{ env['name'] }}-service
type: Service
properties:
prop1: {{ properties['prop1'] }}
...
```
Templates provide access to multiple sets of data that can be used to
parameterize or further customize configurations:
Templates provide access to multiple sets of data, which can be used for
parameterizing or further customizing 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 when
instantiating the template
* imports: a map of import file names to file contents of all imports
* `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 Python, this data is available from the _context_ object passed into the
_GenerateConfig_ method.
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
### Template Schemas
A schema can be optionally provided for a template. The schema describes
the template in more detail, including:
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 sub-imports used by this template (may be relative path or URL)
* required: properties which are required when instantiating the template
* properties: JSON Schema descriptions of each property the template accepts
* `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:
@ -153,21 +158,23 @@ properties:
default: prop-value
```
Schemas are used by Deployment Manager to validate properties during
template instantiation, and to provide default values.
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 imported with the templates they describe, when passing
configuration to Deployment Manager.
Schemas must be supplied to DM along with the templates they describe.
### Instantiating Templates
### Supplying templates
Templates can be used in two different ways: either passed to the API as an
imported file, or used from a public HTTP endpoint.
Templates can be supplied to DM in two different ways:
#### Imported Templates
* 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.
Templates can be imported as part of the target configuration, and used
directly, for example:
#### Template imports
Configurations can import templates using path declarations. For example:
```
imports:
@ -180,50 +187,85 @@ resources:
prop1: prop-value
```
The _imports_ list is not understood by the Deployment Manager service.
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 a configuration to the API.
should be included when passing the configuration to the API.
Using the Deployment Manager API, these templates can be included in the
imports section of the _configuration_.
If you are calling the Deployment Manager service directly, you must embed the
imported templates in the configuration passed to the API.
#### External Templates
#### Template references
Templates can also be used from a public HTTP endpoint. For example:
Configurations can also reference templates using URLs for public HTTP endpoints.
DM will attempt to resolve template references during expansion. For example:
```
resources:
- name: example
type: https://raw.githubusercontent.com/example/example.py
- name: my-template
type: https://raw.githubusercontent.com/my-template/my-template.py
properties:
prop1: prop-value
```
The service will process external templates as follows:
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.
1. Fetch the external template as an import
1. Attempt to fetch the schema for the template, using
_<full template path>.schema_ as the schema path
1. Repeat for any sub-imports found in the schema file
For example:
When fetching schema files and sub-imports, the base path of the external
template is used for relative paths.
```
$(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
Deployment Manager exposes a set of RESTful collections over HTTP/JSON.
DM exposes a set of RESTful collections over HTTP/JSON.
### Deployments
Deployments are the primary resource in the Deployment Manager service. The
inputs to a deployment are:
* name
* configuration
Deployments are the primary resources managed by the Deployment Manager service.
The inputs to a deployment are:
When creating a deployment, users pass their configuration,
as well as any import files (templates, datafiles, etc.), all encoded in `YAML`,
in as the _configuration_.
* `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
@ -240,10 +282,9 @@ 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 manifest
* expandedConfig: the expanded configuration to be used when processing resources
* for the manifest
* layout: the hierarchical structure of the manifest
* `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:
@ -251,31 +292,31 @@ Manifests are available at the HTTP endpoint:
http://manager-service/deployments/<deployment>/manifests
```
#### Expanded Configuration
#### Expanded configuration
Given a new _inputConfig_, Deployment Manager expands all template
instantiations recursively until there is a flat set of configurable resources.
This final set is stored as the _expandedConfig_ and is used during resource
processing.
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
Users can use templates to build a rich, deep hierarchical architecture in their
configuration. Expansion flattens this hierarchy and removes the template
relationships from the configuration to create a format optimized for the process
of instantiating the resources. However, the structural information contained in
the original configuration has many uses, so rather than discard it, Deployment
Manager preserves it in the form of a _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 very much like an input configuration. It is a YAML list of
resources, where each resource contains the following information:
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
An example layout is:
Here's an example of a layout:
```
resources:
@ -290,26 +331,35 @@ resources:
type: Service
```
The layout can be used for visualizing the architecture of resources, including
their hierarchical structure and reference relationships.
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 existing types being used the cluster.
The types API provides information about types used in the cluster.
It can be used to list all known types that are in use in existing deployments:
It can be used to list all known types used by active deployments:
```
http://manager-service/types
```
It can be used to list all active instances of a specific type in the cluster:
Or to list all active instances of a specific type in the cluster:
```
http://manager-service/types/<type>/instances
```
Passing _all_ as the type shows all instances of all types in the cluster. Type
instances include the following information:
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
@ -318,58 +368,67 @@ instances include the following information:
* path: JSON path to the entry for the resource in the manifest layout
## Architecture
The Deployment Manager service is built to run as a service within a Kubernetes
cluster. It has three major components to manage deployments. The following
diagram illustrates the relationships between the components, which are described
in more detail below.
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 design of the service:
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.
* Non-persistence: the service currently stores all metadata in memory,
so it will lose all knowledge of deployments and their metadata on restart.
In the future, the service will persist all deployment metadata.
* 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 uses the following process:
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
1. Create a new deployment with a manifest containing `inputConfig` from the
user request
1. Call out to he **expandybird** service to expand the _inputConfig_
1. Store the resulting _expandedConfig_ and _layout_
1. Call out to the **resourcifier** service to perform processing on resources
from the _expandedConfig_
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
The manager is responsible for saving the metadata associated with
`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 input configurations, performs all template
expansions, and returns the resulting flat configuration and layout. It is completely
stateless.
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.
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.
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 flat expanded configurations containing
only configurable resources, and makes the respective kubectl calls to process
each resource. It is totally stateless, and handles requests synchronously.
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`.
The resourcifier returns either success or error messages encountered during
resource processing.
It returns either success or error messages encountered during resource processing.

@ -0,0 +1,187 @@
# Template Registries
DM lets configurations instantiate [templates](../design/design.md#templates)
using both [imports](../design/design.md#template-imports) and
[references](../design/design.md#template-references).
Because template references can use any public HTTP endpoint, they provide
a way to share templates. While you can store templates anywhere you want and
organize them any way you want, you may not be able to share them effectively
without some organizing principles. This document defines conventions for
template registries that store templates in Github and organize them by name
and by version to make sharing easier.
## Template Versions
Since templates referenced by configurations and by other templates may change
over time, we need a versioning scheme, so that template references can be reliably
resolved to specific template versions.
Every template must therefore carry a version based on the
[Semantic Versioning](http://semver.org/) specification. A template version
consists of a MAJOR version, a MINOR version and a PATCH version, and can
be represented as a three part string starting with the letter `v` and using
dot delimiters between the parts. For example `v1.1.0`.
Parts may be omitted from left to right, up to but not include the MAJOR
version. All omitted parts default to zero. So, for example:
* `v1.1` is equivalent to `v1.1.0`, and
* `v2` is equivalent to `v2.0.0`
As required by Semantic Versioning:
* The MAJOR version must be incremented for incompatible changes
* The MINOR version must be incremented functionality is added in a backwards-compatible
manner, and
* The PATCH version must be incremented for backwards-compatible bug fixes.
When resolving a template reference, DM will attempt to fetch the template with
the highest available PATCH version that has the same MAJOR and MINOR versions as
the referenced version.
## Template Validation
Every template version should include a configuration named `example.yaml`
that can be used to deploy an instance of the template. This file may be used,
along with any supporting files it requires, to validate the template.
## Template Organization
Technically, all you need to reference a template is a directory at a public
HTTP endpoint that contains a template file named either `<template-name>.py`
or `<template-name>.jinja`, depending on the implementation language, along
with any supporting files it might require, such as an optional schema file
named `<template-name>.py.schema` or `<template-name>.jinja.schema`, respectively,
helper files used by the implementation, files imported by the schema, and so on.
These constraints impose a basic level of organization on the template definition
by ensuring that the template and all of its supporting files at least live in the
same directory, and that the template and schema files follow well-defined naming
conventions.
They do not, however, provide any encapsulation. Without additional constraints,
there is nothing to prevent template publishers from putting multiple templates,
or multiple versions of the same template, in the same directory. While there
might be some benefits in allowing templates to share a directory, such as avoiding
the duplication of helper files, the cost of discovering and maintaining templates
would quickly outweigh them as the number of templates in the directory increased.
Every template version must therefore live in its own directory, and that
directory must contain one and only one top-level template file and supporting
files for one and only template version.
Since it may reduce management overhead to store many different templates,
and/or many versions of the same template, in a single repository, we need a way
to organize templates within a repository.
A template repository must therefore place all of the versions of a given
template in directories named for the template versions under a directory named
for the template.
For example:
```
templateA/
v1/
example.yaml
templateA.py
templateA.py.schema
v1.0.1/
example.yaml
templateA.py
templateA.py.schema
v1.1/
example.yaml
templateA.py
templateA.py.schema
helper.py
```
The template directories may be organized in any way that makes sense to the
repository maintainers.
For example, this flat list of template directories is valid:
```
templates/
templateA/
v1/
...
templateB/
v2/
...
```
This example, where template directories are organized by category, is also valid:
```
templates/
big-data/
templateA/
v1/
...
templateB/
v2/
...
signals
templateC/
v1/
...
templateD/
v1.1/
...
```
## Template Registries
Github is a convenient place to store and manage templates. A template registry
is a Github repository that conforms to the requirements detailed in this document.
For a working example of a template registry, please see the
[Kubernetes Template Registry](https://github.com/kubernetes/deployment-manager/tree/master/templates).
### Accessing a template registry
The Deployment Manager client, `dm`, can deploy templates directly from a registry
using the following command:
```
$ dm deploy <template-name>:<version>
```
To resolve the template reference, `dm` looks for a template version directory
with the given version in the template directory with the given template name.
By default, it uses the Kubernetes Template Registry. However, you can set a
different default using the `--registry` flag:
```
$ dm --registry my-org/my-repo/my-root-directory deploy <template-name>:<version>
```
Alternatively, you can qualify the template name with the path to the template
directory within the registry, like this:
```
$ dm deploy my-org/my-repo/my-root-directory/<template-name>:<version>
```
Specifying the path to the template directory this way doesn't change the default.
For templates that require properties, you can provide them on the command line:
```
$ dm --properties prop1=value1,prop2=value2 deploy <template-name>:<version>
```
### Changing a template registry
DM relies on Github to provide the tools and processes needed to add, modify or
delete the contents of a template registry. Conventions for changing a template
registry are defined by the registry maintainers, and should be published in the
top level README.md or a file it references, following usual Github practices.
The [Kubernetes Template Registry](https://github.com/kubernetes/deployment-manager/tree/master/templates)
follows the [git setup](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/development.md#git-setup)
used by Kubernetes.

@ -1,6 +1,6 @@
resources:
- name: expandybird
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
service_port: 8081
target_port: 8080
@ -11,7 +11,7 @@ resources:
labels:
app: dm
- name: resourcifier
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
service_port: 8082
target_port: 8080
@ -22,7 +22,7 @@ resources:
labels:
app: dm
- name: manager
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
service_port: 8080
target_port: 8080

@ -9,9 +9,9 @@ First, make sure DM is installed in your Kubernetes cluster and that the
Guestbook example is deployed by following the instructions in the top level
[README.md](../../README.md).
## Understanding the Guestbook example template
## Understanding the Guestbook example
Let's take a closer look at the template used by the Guestbook example.
Let's take a closer look at the configuration used by the Guestbook example.
### Replicated services
@ -19,20 +19,22 @@ The typical design pattern for microservices in Kubernetes is to create a
replication controller and a service with the same selector, so that the service
exposes ports from the pods managed by the replication controller.
We have created a parameterized type for this kind of replicated service called
[Replicated Service](../../types/replicatedservice/v1), and we use it three times in this
example.
We have created a parameterized template for this kind of replicated service
called [Replicated Service](../../templates/replicatedservice/v1), and we use it
three times in the Guestbook example.
Note that the type is defined by a
[python script](../../types/replicatedservice/v1/replicatedservice.py). It also has a
[schema](../../types/replicatedservice/v1/replicatedservice.py.schema). Schemas are
optional. If present in the type definition, they are used to validate uses of the
type that appear in DM templates.
The template is defined by a
[Python script](../../templates/replicatedservice/v1/replicatedservice.py). It
also has a [schema](../../templates/replicatedservice/v1/replicatedservice.py.schema).
Schemas are optional. If provided, they are used to validate template invocations
that appear in configurations.
For more information about types and templates, see the [design document](../../docs/design/design.md).
For more information about templates and schemas, see the
[design document](../../docs/design/design.md#templates).
### The Guestbook application
The Guestbook application consists of 2 microservices: a front end and a Redis cluster.
The Guestbook application consists of 2 microservices: a front end and a Redis
cluster.
#### The front end
@ -40,7 +42,7 @@ The front end is a replicated service with 3 replicas:
```
- name: frontend
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
service_port: 80
container_port: 80
@ -49,13 +51,14 @@ The front end is a replicated service with 3 replicas:
image: gcr.io/google_containers/example-guestbook-php-redis:v3
```
(Note that we use the URL for the type replicatedservice.py, not just the type name.)
(Note that we use the URL for a specific version of the template replicatedservice.py,
not just the template name.)
#### The Redis cluster
The Redis cluster consists of two replicated services: a master with a single replica
and the slaves with 2 replicas. It's defined by [this composite type](../../types/redis/v1/redis.jinja),
which is a [Jinja](http://jinja.pocoo.org/) template with a [schema](../../types/redis/v1/redis.jinja.schema).
and the slaves with 2 replicas. It's defined by [this template](../../templates/redis/v1/redis.jinja),
which is a [Jinja](http://jinja.pocoo.org/) file with a [schema](../../templates/redis/v1/redis.jinja.schema).
```
{% set REDIS_PORT = 6379 %}
@ -63,7 +66,7 @@ which is a [Jinja](http://jinja.pocoo.org/) template with a [schema](../../types
resources:
- name: redis-master
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
# This has to be overwritten since service names are hard coded in the code
service_name: redis-master
@ -75,7 +78,7 @@ resources:
image: redis
- name: redis-slave
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
# This has to be overwritten since service names are hard coded in the code
service_name: redis-slave
@ -94,16 +97,17 @@ resources:
### Displaying types
You can see the types you deployed to the cluster using the `deployed-types` command:
You can see both the both primitive types and the templates you've deployed to the
cluster using the `deployed-types` command:
```
dm deployed-types
["Service","ReplicationController","redis.jinja","https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py"]
["Service","ReplicationController","redis.jinja","https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py"]
```
This output shows 2 primitive types (Service and ReplicationController), and 2
composite types (redis.jinja and one imported from github (replicatedservice.py)).
templates (redis.jinja and one imported from github named replicatedservice.py).
You can also see where a specific type is being used with the `deployed-instances` command:
@ -115,6 +119,7 @@ dm deployed-instances Service
This output describes the deployment and manifest, as well as the JSON paths to
the instances of the type within the layout.
For more information about deployments, manifests and layouts, see the [design document](../../docs/design/design.md).
For more information about deployments, manifests and layouts, see the
[design document](../../docs/design/design.md#api-model).

@ -1,6 +1,6 @@
resources:
- name: frontend
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
service_port: 80
container_port: 80
@ -8,5 +8,5 @@ resources:
replicas: 3
image: gcr.io/google_containers/example-guestbook-php-redis:v3
- name: redis
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/redis/v1/redis.jinja
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/redis/v1/redis.jinja
properties: null

@ -1,17 +1,22 @@
# Makefile for the Docker image gcr.io/$(PROJECT)/expandybird
# Makefile for the Docker image $(DOCKER_REGISTRY)/$(PROJECT)/expandybird
# MAINTAINER: Jack Greenfield <jackgr@google.com>
# If you update this image please check the tag value before pushing.
.PHONY : all build test push container clean
PREFIX := gcr.io/$(PROJECT)
DOCKER_REGISTRY := gcr.io
PREFIX := $(DOCKER_REGISTRY)/$(PROJECT)
IMAGE := expandybird
TAG := latest
DIR := .
push: container
ifeq ($(DOCKER_REGISTRY),gcr.io)
gcloud docker push $(PREFIX)/$(IMAGE):$(TAG)
else
docker push $(PREFIX)/$(IMAGE):$(TAG)
endif
container: expandybird
cp $(shell which expandybird) .

@ -24,7 +24,7 @@ import (
const invalidFileName = "afilethatdoesnotexist"
var importFileNames = []string{
"../test/replicatedservice.py",
"../../templates/replicatedservice/v1/replicatedservice.py",
}
var outputFileName = "../test/ExpectedOutput.yaml"

@ -68,14 +68,14 @@ func NewExpansionHandler(backend expander.Expander) restful.RouteFunction {
output, err := backend.ExpandTemplate(template)
if err != nil {
message := fmt.Sprintf("error (%s) expanding template:\n%v\n", err, template)
message := fmt.Sprintf("error expanding template: %s", err)
logAndReturnErrorFromHandler(http.StatusBadRequest, message, resp)
return
}
response, err := expander.NewExpansionResponse(output)
if err != nil {
message := fmt.Sprintf("error (%s) marshaling output:\n%v\n", err, output)
message := fmt.Sprintf("error marshaling output: %s", err)
logAndReturnErrorFromHandler(http.StatusBadRequest, message, resp)
return
}

@ -65,7 +65,7 @@ const (
)
var importFileNames = []string{
"../test/replicatedservice.py",
"../../templates/replicatedservice/v1/replicatedservice.py",
}
type ServiceWrapperTestCase struct {

@ -20,9 +20,8 @@ config:
metadata:
labels:
app: expandybird
name: expandybird-service
name: expandybird-service
namespace: default
namespace: default
spec:
ports:
- name: expandybird
@ -30,7 +29,6 @@ config:
targetPort: 8080
selector:
app: expandybird
name: expandybird
type: LoadBalancer
type: Service
- name: expandybird-rc
@ -40,22 +38,20 @@ config:
metadata:
labels:
app: expandybird
name: expandybird-rc
name: expandybird-rc
namespace: default
namespace: default
spec:
replicas: 3
selector:
app: expandybird
name: expandybird
template:
metadata:
labels:
app: expandybird
name: expandybird
spec:
containers:
- image: b.gcr.io/dm-k8s-testing/expandybird
- env: []
image: b.gcr.io/dm-k8s-testing/expandybird
name: expandybird
ports:
- containerPort: 8080

@ -1,176 +0,0 @@
######################################################################
# Copyright 2015 The Kubernetes Authors All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
######################################################################
"""Defines a ReplicatedService type by creating both a Service and an RC.
This module creates a typical abstraction for running a service in a
Kubernetes cluster, namely a replication controller and a service packaged
together into a single unit.
"""
import yaml
SERVICE_TYPE_COLLECTION = 'Service'
RC_TYPE_COLLECTION = 'ReplicationController'
def GenerateConfig(context):
"""Generates a Replication Controller and a matching Service.
Args:
context: Template context, which can contain the following properties:
container_name - Name to use for container. If omitted, name is
used.
namespace - Namespace to create the resources in. If omitted,
'default' is used.
protocol - Protocol to use for the service
service_port - Port to use for the service
target_port - Target port for the service
container_port - Container port to use
replicas - Number of replicas to create in RC
image - Docker image to use for replicas. Required.
labels - labels to apply.
external_service - If set to true, enable external Load Balancer
Returns:
A Container Manifest as a YAML string.
"""
# YAML config that we're going to create for both RC & Service
config = {'resources': []}
name = context.env['name']
container_name = context.properties.get('container_name', name)
namespace = context.properties.get('namespace', 'default')
# Define things that the Service cares about
service_name = name + '-service'
service_type = SERVICE_TYPE_COLLECTION
# Define things that the Replication Controller (rc) cares about
rc_name = name + '-rc'
rc_type = RC_TYPE_COLLECTION
service = {
'name': service_name,
'type': service_type,
'properties': {
'apiVersion': 'v1',
'kind': 'Service',
'namespace': namespace,
'metadata': {
'name': service_name,
'labels': GenerateLabels(context, service_name),
},
'spec': {
'ports': [GenerateServicePorts(context, container_name)],
'selector': GenerateLabels(context, name)
}
}
}
set_up_external_lb = context.properties.get('external_service', None)
if set_up_external_lb:
service['properties']['spec']['type'] = 'LoadBalancer'
config['resources'].append(service)
rc = {
'name': rc_name,
'type': rc_type,
'properties': {
'apiVersion': 'v1',
'kind': 'ReplicationController',
'namespace': namespace,
'metadata': {
'name': rc_name,
'labels': GenerateLabels(context, rc_name),
},
'spec': {
'replicas': context.properties['replicas'],
'selector': GenerateLabels(context, name),
'template': {
'metadata': {
'labels': GenerateLabels(context, name),
},
'spec': {
'containers': [
{
'name': container_name,
'image': context.properties['image'],
'ports': [
{
'name': container_name,
'containerPort': context.properties['container_port'],
}
]
}
]
}
}
}
}
}
config['resources'].append(rc)
return yaml.dump(config)
# Generates labels either from the context.properties['labels'] or generates
# a default label 'name':name
def GenerateLabels(context, name):
"""Generates labels from context.properties['labels'] or creates default.
We make a deep copy of the context.properties['labels'] section to avoid
linking in the yaml document, which I believe reduces readability of the
expanded template. If no labels are given, generate a default 'name':name.
Args:
context: Template context, which can contain the following properties:
labels - Labels to generate
Returns:
A dict containing labels in a name:value format
"""
tmp_labels = context.properties.get('labels', None)
ret_labels = {'name': name}
if isinstance(tmp_labels, dict):
for key, value in tmp_labels.iteritems():
ret_labels[key] = value
return ret_labels
def GenerateServicePorts(context, name):
"""Generates a ports section for a service.
Args:
context: Template context, which can contain the following properties:
service_port - Port to use for the service
target_port - Target port for the service
protocol - Protocol to use.
Returns:
A dict containing a port definition
"""
service_port = context.properties.get('service_port', None)
target_port = context.properties.get('target_port', None)
protocol = context.properties.get('protocol')
ports = {}
if name:
ports['name'] = name
if service_port:
ports['port'] = service_port
if target_port:
ports['targetPort'] = target_port
if protocol:
ports['protocol'] = protocol
return ports

@ -1,10 +1,11 @@
# Makefile for the Docker image gcr.io/$(PROJECT)/manager
# Makefile for the Docker image $(DOCKER_REGISTRY)/$(PROJECT)/manager
# MAINTAINER: Jack Greenfield <jackgr@google.com>
# If you update this image please check the tag value before pushing.
.PHONY : all build test push container clean .project
PREFIX := gcr.io/$(PROJECT)
DOCKER_REGISTRY := gcr.io
PREFIX := $(DOCKER_REGISTRY)/$(PROJECT)
IMAGE := manager
TAG := latest
@ -12,7 +13,11 @@ ROOT_DIR := $(abspath ./..)
DIR = $(ROOT_DIR)
push: container
ifeq ($(DOCKER_REGISTRY),gcr.io)
gcloud docker push $(PREFIX)/$(IMAGE):$(TAG)
else
docker push $(PREFIX)/$(IMAGE):$(TAG)
endif
container:
docker build -t $(PREFIX)/$(IMAGE):$(TAG) -f Dockerfile $(DIR)

@ -100,7 +100,7 @@ func (d *deployer) PutConfiguration(configuration *Configuration) error {
func (d *deployer) callServiceWithConfiguration(method, operation string, configuration *Configuration) error {
callback := func(e error) error {
return fmt.Errorf("cannot %s configuration (%s)", operation, e)
return fmt.Errorf("cannot %s configuration: %s", operation, e)
}
y, err := yaml.Marshal(configuration)
@ -129,14 +129,14 @@ func (d *deployer) callService(method, url string, reader io.Reader, callback fo
}
defer response.Body.Close()
if response.StatusCode < http.StatusOK ||
response.StatusCode >= http.StatusMultipleChoices {
err := fmt.Errorf("deployer service response:\n%v\n", response)
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, callback(err)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
if response.StatusCode < http.StatusOK ||
response.StatusCode >= http.StatusMultipleChoices {
err := fmt.Errorf("resourcifier response:\n%s", body)
return nil, callback(err)
}

@ -54,7 +54,7 @@ func (e *expander) getBaseURL() string {
}
func expanderError(t *Template, err error) error {
return fmt.Errorf("cannot expand template named %s (%s):\n%s\n", t.Name, err, t.Content)
return fmt.Errorf("cannot expand template named %s (%s):\n%s", t.Name, err, t.Content)
}
// ExpanderResponse gives back a layout, which has nested structure
@ -117,7 +117,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
// 4. If type resolution resulted in new imports being available, return to 2.
config := &Configuration{}
if err := yaml.Unmarshal([]byte(t.Content), config); err != nil {
e := fmt.Errorf("Unable to unmarshal configuration (%s): %s\n", err, t.Content)
e := fmt.Errorf("Unable to unmarshal configuration (%s): %s", err, t.Content)
return nil, e
}
@ -127,7 +127,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
// Start things off by attempting to resolve the templates in a first pass.
newImp, err := e.typeResolver.ResolveTypes(config, t.Imports)
if err != nil {
e := fmt.Errorf("type resolution failed:%s\n", err)
e := fmt.Errorf("type resolution failed: %s", err)
return nil, expanderError(&t, e)
}
@ -137,7 +137,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
// Now expand with everything imported.
result, err := e.expandTemplate(&t)
if err != nil {
e := fmt.Errorf("template expansion:%s\n", err)
e := fmt.Errorf("template expansion: %s", err)
return nil, expanderError(&t, e)
}
@ -152,7 +152,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
newImp, err = e.typeResolver.ResolveTypes(result.Config, nil)
if err != nil {
e := fmt.Errorf("type resolution failed:%s\n", err)
e := fmt.Errorf("type resolution failed: %s", err)
return nil, expanderError(&t, e)
}
@ -167,7 +167,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
content, err = yaml.Marshal(result.Config)
t.Content = string(content)
if err != nil {
e := fmt.Errorf("Unable to unmarshal response from expander (%s): %s\n",
e := fmt.Errorf("Unable to unmarshal response from expander (%s): %s",
err, result.Config)
return nil, expanderError(&t, e)
}
@ -183,31 +183,31 @@ func (e *expander) expandTemplate(t *Template) (*ExpandedTemplate, error) {
response, err := http.Post(e.getBaseURL(), "application/json", ioutil.NopCloser(bytes.NewReader(j)))
if err != nil {
e := fmt.Errorf("http POST failed:%s\n", err)
e := fmt.Errorf("http POST failed: %s", err)
return nil, e
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
err := fmt.Errorf("expander service response:%v", response)
return nil, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
e := fmt.Errorf("error reading response:%s\n", err)
e := fmt.Errorf("error reading response: %s", err)
return nil, e
}
if response.StatusCode != http.StatusOK {
err := fmt.Errorf("expandybird response:\n%s", body)
return nil, err
}
er := &ExpansionResponse{}
if err := json.Unmarshal(body, er); err != nil {
e := fmt.Errorf("cannot unmarshal response body (%s):%s\n", err, body)
e := fmt.Errorf("cannot unmarshal response body (%s):%s", err, body)
return nil, e
}
template, err := er.Unmarshal()
if err != nil {
e := fmt.Errorf("cannot unmarshal response yaml (%s):%v\n", err, er)
e := fmt.Errorf("cannot unmarshal response yaml (%s):%v", err, er)
return nil, e
}

@ -113,15 +113,16 @@ func (m *manager) CreateDeployment(t *Template) (*Deployment, error) {
return nil, err
}
// TODO: Mark this as failed instead of deleting.
if err := m.deployer.CreateConfiguration(et.Config); err != nil {
m.repository.DeleteDeployment(t.Name, true)
// Deployment failed, mark as deleted
log.Printf("CreateConfiguration failed: %v", err)
m.repository.SetDeploymentStatus(t.Name, FailedStatus)
return nil, err
}
m.repository.SetDeploymentStatus(t.Name, DeployedStatus)
// Finally update the type instances for this deployment.
m.addTypeInstances(t.Name, manifest.Name, manifest.Layout)
return m.repository.GetValidDeployment(t.Name)
}

@ -122,6 +122,7 @@ type repositoryStub struct {
TypeInstancesCleared bool
GetTypeInstancesCalled bool
ListTypesCalled bool
DeploymentStatuses []DeploymentStatus
}
func (repository *repositoryStub) reset() {
@ -134,6 +135,7 @@ func (repository *repositoryStub) reset() {
repository.TypeInstancesCleared = false
repository.GetTypeInstancesCalled = false
repository.ListTypesCalled = false
repository.DeploymentStatuses = make([]DeploymentStatus, 0)
}
func newRepositoryStub() *repositoryStub {
@ -165,6 +167,11 @@ func (repository *repositoryStub) GetValidDeployment(d string) (*Deployment, err
return &deploymentWithConfiguration, nil
}
func (repository *repositoryStub) SetDeploymentStatus(name string, status DeploymentStatus) error {
repository.DeploymentStatuses = append(repository.DeploymentStatuses, status)
return nil
}
func (repository *repositoryStub) CreateDeployment(d string) (*Deployment, error) {
repository.Created = append(repository.Created, d)
return &deploymentWithConfiguration, nil
@ -294,6 +301,10 @@ func TestCreateDeployment(t *testing.T) {
testDeployer.Created[0], configuration)
}
if testRepository.DeploymentStatuses[0] != DeployedStatus {
t.Error("CreateDeployment success did not mark deployment as deployed")
}
if !testRepository.TypeInstancesCleared {
t.Error("Repository did not clear type instances during creation")
}
@ -314,9 +325,13 @@ func TestCreateDeploymentCreationFailure(t *testing.T) {
testRepository.Created[0], template.Name)
}
if testRepository.Deleted[0] != template.Name {
t.Errorf("Repository DeleteDeployment was called with %s but expected %s.",
testRepository.Created[0], template.Name)
if len(testRepository.Deleted) != 0 {
t.Errorf("DeleteDeployment was called with %s but not expected",
testRepository.Created[0])
}
if testRepository.DeploymentStatuses[0] != FailedStatus {
t.Error("CreateDeployment failure did not mark deployment as failed")
}
if !strings.HasPrefix(testRepository.ManifestAdd[template.Name].Name, "manifest-") {

@ -50,6 +50,7 @@ type Repository interface {
GetValidDeployment(name string) (*Deployment, error)
CreateDeployment(name string) (*Deployment, error)
DeleteDeployment(name string, forget bool) (*Deployment, error)
SetDeploymentStatus(name string, status DeploymentStatus) error
// Manifests.
AddManifest(deploymentName string, manifest *Manifest) error
@ -72,7 +73,7 @@ type Deployment struct {
DeployedAt time.Time `json:"deployedAt,omitempty"`
ModifiedAt time.Time `json:"modifiedAt,omitempty"`
DeletedAt time.Time `json:"deletedAt,omitempty"`
Status deploymentStatus `json:"status,omitempty"`
Status DeploymentStatus `json:"status,omitempty"`
Current *Configuration `json:"current,omitEmpty"`
Manifests map[string]*Manifest `json:"manifests,omitempty"`
}
@ -88,19 +89,19 @@ func NewManifest(deploymentName string, manifestName string) *Manifest {
return &Manifest{Deployment: deploymentName, Name: manifestName}
}
// deploymentStatus is an enumeration type for the status of a deployment.
type deploymentStatus string
// DeploymentStatus is an enumeration type for the status of a deployment.
type DeploymentStatus string
// These constants implement the deploymentStatus enumeration type.
const (
CreatedStatus deploymentStatus = "Created"
DeletedStatus deploymentStatus = "Deleted"
DeployedStatus deploymentStatus = "Deployed"
FailedStatus deploymentStatus = "Failed"
ModifiedStatus deploymentStatus = "Modified"
CreatedStatus DeploymentStatus = "Created"
DeletedStatus DeploymentStatus = "Deleted"
DeployedStatus DeploymentStatus = "Deployed"
FailedStatus DeploymentStatus = "Failed"
ModifiedStatus DeploymentStatus = "Modified"
)
func (s deploymentStatus) String() string {
func (s DeploymentStatus) String() string {
return string(s)
}

@ -84,6 +84,24 @@ func (r *mapBasedRepository) GetValidDeployment(name string) (*manager.Deploymen
return d, nil
}
// SetDeploymentStatus sets the DeploymentStatus of the deployment and updates ModifiedAt
func (r *mapBasedRepository) SetDeploymentStatus(name string, status manager.DeploymentStatus) error {
return func() error {
r.Lock()
defer r.Unlock()
d, err := r.GetValidDeployment(name)
if err != nil {
return err
}
d.Status = status
d.ModifiedAt = time.Now()
r.deployments[name] = *d
return nil
}()
}
// CreateDeployment creates a new deployment and stores it in the repository.
func (r *mapBasedRepository) CreateDeployment(name string) (*manager.Deployment, error) {
d, err := func() (*manager.Deployment, error) {

@ -1,14 +0,0 @@
######################################################################
# Copyright 2015 The Kubernetes Authors All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
######################################################################
root: .

@ -24,14 +24,16 @@ import (
type GithubRegistry struct {
owner string
repository string
path string
client *github.Client
}
// NewGithubRegistry creates a Registry that can be used to talk to github.
func NewGithubRegistry(owner string, repository string) *GithubRegistry {
func NewGithubRegistry(owner, repository, path string) *GithubRegistry {
return &GithubRegistry{
owner: owner,
repository: repository,
path: path,
client: github.NewClient(nil),
}
}
@ -39,19 +41,21 @@ func NewGithubRegistry(owner string, repository string) *GithubRegistry {
// List the types from the Registry.
func (g *GithubRegistry) List() ([]Type, error) {
// First list all the types at the top level.
types, err := g.getDirs(TypesDir)
types, err := g.getDirs("")
if err != nil {
log.Printf("Failed to list types : %v", err)
log.Printf("Failed to list templates: %v", err)
return nil, err
}
var retTypes []Type
for _, t := range types {
// Then we need to fetch the versions (directories for this type)
versions, err := g.getDirs(TypesDir + "/" + t)
versions, err := g.getDirs(t)
if err != nil {
log.Printf("Failed to fetch versions for type: %s", t)
log.Printf("Failed to fetch versions for template: %s", t)
return nil, err
}
for _, v := range versions {
retTypes = append(retTypes, Type{Name: t, Version: v})
}
@ -60,34 +64,53 @@ func (g *GithubRegistry) List() ([]Type, error) {
return retTypes, nil
}
// GetURL fetches the download URL for a given Type.
// GetURL fetches the download URL for a given Type and checks for existence of a schema file.
func (g *GithubRegistry) GetURL(t Type) (string, error) {
_, dc, _, err := g.client.Repositories.GetContents(g.owner, g.repository, TypesDir+"/"+t.Name+"/"+t.Version, nil)
path := g.path + "/" + t.Name + "/" + t.Version
_, dc, _, err := g.client.Repositories.GetContents(g.owner, g.repository, path, nil)
if err != nil {
log.Printf("Failed to list types : %v", err)
log.Printf("Failed to list versions at path: %s: %v", path, err)
return "", err
}
var downloadURL, typeName, schemaName string
for _, f := range dc {
if *f.Type == "file" {
if *f.Name == t.Name+".jinja" || *f.Name == t.Name+".py" {
return *f.DownloadURL, nil
typeName = *f.Name
downloadURL = *f.DownloadURL
}
if *f.Name == t.Name+".jinja.schema" || *f.Name == t.Name+".py.schema" {
schemaName = *f.Name
}
}
}
return "", fmt.Errorf("Can not find type %s:%s", t.Name, t.Version)
if downloadURL == "" {
return "", fmt.Errorf("Can not find template %s:%s", t.Name, t.Version)
}
if schemaName == typeName+".schema" {
return downloadURL, nil
}
return "", fmt.Errorf("Can not find schema for %s:%s, expected to find %s", t.Name, t.Version, typeName+".schema")
}
func (g *GithubRegistry) getDirs(dir string) ([]string, error) {
_, dc, _, err := g.client.Repositories.GetContents(g.owner, g.repository, dir, nil)
var path = g.path
if dir != "" {
path = g.path + "/" + dir
}
_, dc, _, err := g.client.Repositories.GetContents(g.owner, g.repository, path, nil)
if err != nil {
log.Printf("Failed to call ListRefs : %v", err)
log.Printf("Failed to get contents at path: %s: %v", path, err)
return nil, err
}
var dirs []string
for _, entry := range dc {
if *entry.Type == "dir" {
dirs = append(dirs, *entry.Name)
}
}
return dirs, nil
}

@ -13,33 +13,36 @@ limitations under the License.
package registry
// Registry abstracts a types registry which holds types that can be
// used in a Deployment Manager configurations. A registry root must have
// a 'types' directory which contains all the available types. Each type
// then contains version directories which in turn contains all the files
// necessary for that version of the type.
// For example a type registry holding two types:
// redis v1 (implemented in jinja)
// replicatedservice v2 (implemented in python)
// would have a directory structure like so:
// /types/redis/v1
// redis.jinja
// redis.jinja.schema
// /types/replicatedservice/v2
// replicatedservice.python
// replicatedservice.python.schema
const TypesDir string = "types"
// Registry abstracts a registry that holds templates, which can be
// used in a Deployment Manager configurations. A registry root must be a
// directory that contains all the available templates, one directory per
// template. Each template directory then contains version directories, each
// of which in turn contains all the files necessary for that version of the
// template.
// For example, a template registry containing two versions of redis
// (implemented in jinja), and one version of replicatedservice (implemented
// in python) would have a directory structure that looks something like this:
// /redis
// /v1
// redis.jinja
// redis.jinja.schema
// /v2
// redis.jinja
// redis.jinja.schema
// /replicatedservice
// /v1
// replicatedservice.python
// replicatedservice.python.schema
type Type struct {
Name string
Name string
Version string
}
// Registry abstracts type interactions.
type Registry interface {
// List all the types in the given registry
// List all the templates at the given path
List() ([]Type, error)
// Get the download URL for a given type and version
// Get the download URL for a given template and version
GetURL(t Type) (string, error)
}

@ -1,10 +1,11 @@
# Makefile for the Docker image gcr.io/$(PROJECT)/resourcifier
# Makefile for the Docker image $(DOCKER_REGISTRY)/$(PROJECT)/resourcifier
# MAINTAINER: Jack Greenfield <jackgr@google.com>
# If you update this image please check the tag value before pushing.
.PHONY : all build test push container clean
PREFIX := gcr.io/$(PROJECT)
DOCKER_REGISTRY := gcr.io
PREFIX := $(DOCKER_REGISTRY)/$(PROJECT)
IMAGE := resourcifier
TAG := latest
@ -12,7 +13,11 @@ ROOT_DIR := $(abspath ./..)
DIR = $(ROOT_DIR)
push: container
ifeq ($(DOCKER_REGISTRY),gcr.io)
gcloud docker push $(PREFIX)/$(IMAGE):$(TAG)
else
docker push $(PREFIX)/$(IMAGE):$(TAG)
endif
container:
docker build -t $(PREFIX)/$(IMAGE):$(TAG) -f Dockerfile $(DIR)

@ -0,0 +1,29 @@
# Kubernetes Template Registry
Welcome to the Kubernetes Template Registry!
This registry holds Deployment Manager
[templates](https://github.com/kubernetes/deployment-manager/tree/master/docs/design/design.md#templates)
that you can use to deploy Kubernetes resources.
For more information about installing and using Deployment Manager, see its
[README.md](https://github.com/kubernetes/deployment-manager/tree/master/README.md).
## Organization
The Kubernetes Template Registry is organized as a flat list of template
directories. Templates are versioned. The versions of a template live in version
directories under its template directory.
For more information about Deployment Manager template registries, including
directory structure and template versions, see the
[template registry documentation](https://github.com/kubernetes/deployment-manager/tree/master/docs/templates/registry.md)
## Contributing
The Kubernetes Template Registry follows the
[git setup](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/development.md#git-setup)
used by Kubernetes.
You will also need to have a Contributor License Agreement on file, as described
in [CONTRIBUTING.md](../CONTRIBUTING.md).

@ -1,9 +1,9 @@
{% set REDIS_PORT = 6379 %}
{% set WORKERS = properties['workers'] or 2 %}
{% set WORKERS = properties['workers'] if properties and properties['workers'] else 2 %}
resources:
- name: redis-master
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
# This has to be overwritten since service names are hard coded in the code
service_name: redis-master
@ -15,7 +15,7 @@ resources:
image: redis
- name: redis-slave
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties:
# This has to be overwritten since service names are hard coded in the code
service_name: redis-slave

@ -0,0 +1,6 @@
imports:
- path: redis.jinja
resources:
- name: redis
type: redis.jinja

@ -1,3 +1,16 @@
######################################################################
# Copyright 2015 The Kubernetes Authors All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
######################################################################
"""Defines a ReplicatedService type by creating both a Service and an RC.
This module creates a typical abstraction for running a service in a
@ -41,9 +54,9 @@ def GenerateConfig(context):
'properties': {
'apiVersion': 'v1',
'kind': 'Service',
'namespace': namespace,
'metadata': {
'name': service_name,
'namespace': namespace,
'labels': GenerateLabels(context, service_name),
},
'spec': {
@ -63,9 +76,9 @@ def GenerateConfig(context):
'properties': {
'apiVersion': 'v1',
'kind': 'ReplicationController',
'namespace': namespace,
'metadata': {
'name': rc_name,
'namespace': namespace,
'labels': GenerateLabels(context, rc_name),
},
'spec': {
@ -101,11 +114,11 @@ def GenerateConfig(context):
def GenerateLabels(context, name):
"""Generates labels either from the context.properties['labels'] or
generates a default label 'name':name
generates a default label 'app':name
We make a deep copy of the context.properties['labels'] section to avoid
linking in the yaml document, which I believe reduces readability of the
expanded template. If no labels are given, generate a default 'name':name.
expanded template. If no labels are given, generate a default 'app':name.
Args:
context: Template context, which can contain the following properties:
@ -115,7 +128,7 @@ def GenerateLabels(context, name):
A dict containing labels in a name:value format
"""
tmp_labels = context.properties.get('labels', None)
ret_labels = {'name': name}
ret_labels = {'app': name}
if isinstance(tmp_labels, dict):
for key, value in tmp_labels.iteritems():
ret_labels[key] = value
Loading…
Cancel
Save