Skip to content

Commit

Permalink
feat: experimental blue-green deployment
Browse files Browse the repository at this point in the history
Introduce the ability to specify different deployment-strategies.
Implement blue-green deployments.
Adds "experimental" meta field to joi schemas.
Introduce "Experimental feature" warning message.
  • Loading branch information
10ko authored and edvald committed Sep 18, 2019
1 parent 61b4df1 commit 01f59f5
Show file tree
Hide file tree
Showing 29 changed files with 495 additions and 14 deletions.
14 changes: 14 additions & 0 deletions docs/reference/providers/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ Set a default username (used for namespacing within a cluster).
| -------- | -------- |
| `string` | No |

### `providers[].deploymentStrategy`

[providers](#providers) > deploymentStrategy
> ⚠️ **Experimental**: this is an experimental feature and the API might change in the future.

Defines the strategy for deploying the project services.
Default is "rolling update" and there is experimental support for "blue/green" deployment.
The feature only supports modules of type `container`: other types will just deploy using the default strategy.

| Type | Required | Default |
| -------- | -------- | ----------- |
| `string` | No | `"rolling"` |

### `providers[].forceSsl`

[providers](#providers) > forceSsl
Expand Down Expand Up @@ -892,6 +905,7 @@ providers:
buildMode: local-docker
defaultHostname:
defaultUsername:
deploymentStrategy: rolling
forceSsl: false
imagePullSecrets:
- name:
Expand Down
14 changes: 14 additions & 0 deletions docs/reference/providers/local-kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ Set a default username (used for namespacing within a cluster).
| -------- | -------- |
| `string` | No |

### `providers[].deploymentStrategy`

[providers](#providers) > deploymentStrategy
> ⚠️ **Experimental**: this is an experimental feature and the API might change in the future.

Defines the strategy for deploying the project services.
Default is "rolling update" and there is experimental support for "blue/green" deployment.
The feature only supports modules of type `container`: other types will just deploy using the default strategy.

| Type | Required | Default |
| -------- | -------- | ----------- |
| `string` | No | `"rolling"` |

### `providers[].forceSsl`

[providers](#providers) > forceSsl
Expand Down Expand Up @@ -793,6 +806,7 @@ providers:
buildMode: local-docker
defaultHostname:
defaultUsername:
deploymentStrategy: rolling
forceSsl: false
imagePullSecrets:
- name:
Expand Down
33 changes: 33 additions & 0 deletions examples/deployment-strategies/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Deployment strategies example

A basic demo project showing different deployment strategies.

It is based on the [examples/demo-project](https://github.com/garden-io/garden/tree/master/examples/demo-project) and it's meant to show how to configure the deployment strategies.

> NOTE: the "Deployment Strategies feature" is still in the experimental phase. This means there might be changes to the properties names/values or to the behaviour in the future. Please be aware of this when using the feature.

## Usage

This project doesn't require any specific set up and can be deployed (in your local cluster) in a single step with the `deploy` command:

```sh
garden deploy --env=local-blue-green
```

The first deploy on a fresh cluster will always be a normal `rolling-update` deploy. After making some changes to any of the module you will be able to see the Blue/Green strategy in action.

### Example of configuration

For more detailed configuration please check out the `garden.yml` file.

```yaml
kind: Project
name: My Project
environments:
# Blue-green deployment strategy on local-kubernetes.
- name: local-blue-green
providers:
- name: local-kubernetes
deploymentStrategy: blue-green
```
4 changes: 4 additions & 0 deletions examples/deployment-strategies/backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
Dockerfile
garden.yml
app.yaml
27 changes: 27 additions & 0 deletions examples/deployment-strategies/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof

.vscode/settings.json
webserver/*server*
14 changes: 14 additions & 0 deletions examples/deployment-strategies/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:1.8.3-alpine
MAINTAINER Aurelien PERRIER <a.perrier89@gmail.com>

ENV webserver_path /go/src/github.com/perriea/webserver/
ENV PATH $PATH:$webserver_path

WORKDIR $webserver_path
COPY webserver/ .

RUN go build .

ENTRYPOINT ./webserver

EXPOSE 8080
14 changes: 14 additions & 0 deletions examples/deployment-strategies/backend/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
kind: Module
name: backend
description: Backend service container
type: container
services:
- name: backend
ports:
- name: http
containerPort: 8080
# Maps service:80 -> container:8080
servicePort: 80
ingresses:
- path: /hello-backend
port: http
17 changes: 17 additions & 0 deletions examples/deployment-strategies/backend/webserver/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello from Go!")
}

func main() {
http.HandleFunc("/hello-backend", handler)
fmt.Println("Server running...")

http.ListenAndServe(":8080", nil)
}
4 changes: 4 additions & 0 deletions examples/deployment-strategies/frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
Dockerfile
garden.yml
app.yaml
12 changes: 12 additions & 0 deletions examples/deployment-strategies/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM node:9-alpine

ENV PORT=8080
EXPOSE ${PORT}
WORKDIR /app

ADD package.json /app
RUN npm install

ADD . /app

CMD ["npm", "start"]
27 changes: 27 additions & 0 deletions examples/deployment-strategies/frontend/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const express = require('express');
const request = require('request-promise')
const app = express();

const backendServiceEndpoint = `http://backend/hello-backend`

app.get('/hello-frontend', (req, res) => res.send('Hello from the frontend!'));

app.get('/call-backend', (req, res) => {
// Query the backend and return the response
request.get(backendServiceEndpoint)
.then(message => {
message = `Backend says: '${message}'`
res.json({
message,
})
})
.catch(err => {
res.statusCode = 500
res.json({
error: err,
message: "Unable to reach service at " + backendServiceEndpoint,
})
});
});

module.exports = { app }
27 changes: 27 additions & 0 deletions examples/deployment-strategies/frontend/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
kind: Module
name: frontend
description: Frontend service container
type: container
services:
- name: frontend
ports:
- name: http
containerPort: 8080
healthCheck:
httpGet:
path: /hello-frontend
port: http
ingresses:
- path: /hello-frontend
port: http
- path: /call-backend
port: http
dependencies:
- backend
tests:
- name: unit
args: [npm, test]
- name: integ
args: [npm, run, integ]
dependencies:
- frontend
3 changes: 3 additions & 0 deletions examples/deployment-strategies/frontend/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { app } = require('./app');

app.listen(process.env.PORT, '0.0.0.0', () => console.log('Frontend service started'));
22 changes: 22 additions & 0 deletions examples/deployment-strategies/frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "frontend",
"version": "1.0.0",
"description": "Simple Node.js docker service",
"main": "main.js",
"scripts": {
"start": "node main.js",
"test": "echo OK",
"integ": "node_modules/mocha/bin/mocha test/integ.js"
},
"author": "garden.io <info@garden.io>",
"license": "ISC",
"dependencies": {
"express": "^4.16.2",
"request": "^2.83.0",
"request-promise": "^4.2.2"
},
"devDependencies": {
"mocha": "^5.1.1",
"supertest": "^3.0.0"
}
}
17 changes: 17 additions & 0 deletions examples/deployment-strategies/frontend/test/integ.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const supertest = require("supertest")
const { app } = require("../app")

describe('GET /call-backend', () => {
const agent = supertest.agent(app)

it('should respond with a message from the backend service', (done) => {
agent
.get("/call-backend")
.expect(200, { message: "Backend says: 'Hello from Go!'" })
.end((err) => {
if (err) return done(err)
done()
})
})
})

28 changes: 28 additions & 0 deletions examples/deployment-strategies/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
kind: Project
name: deployment-strategies
environments:
# Default deployment strategy
- name: local
providers:
- name: local-kubernetes
# Default deployment strategy.
# Same as above but explicit
- name: local-default
providers:
- name: local-kubernetes
deploymentStrategy: rolling
# Blue-green deployment strategy.
- name: local-blue-green
providers:
- name: local-kubernetes
deploymentStrategy: blue-green
# Testing environment
- name: testing
providers:
- name: kubernetes
context: gke_garden-dev-200012_europe-west1-b_garden-dev-1
namespace: deployment-strategies-testing-${local.env.CIRCLE_BUILD_NUM || local.username}
defaultHostname: deployment-strategies-testing.dev-1.sys.garden
buildMode: cluster-docker
deploymentStrategy: blue-green

1 change: 1 addition & 0 deletions garden-service/src/config/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ interface MetadataKeys {
internal?: boolean
deprecated?: boolean
extendable?: boolean
experimental?: boolean
}

// Unfortunately we need to explicitly extend each type (just extending the AnySchema doesn't work).
Expand Down
7 changes: 6 additions & 1 deletion garden-service/src/docs/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import linewrap from "linewrap"
import titleize from "titleize"
import humanize from "humanize-string"
import { resolve } from "path"
import { get, flatten, startCase, uniq } from "lodash"
import { projectSchema, environmentSchema } from "../config/project"
import { get, flatten, startCase, uniq, find } from "lodash"
import { baseModuleSpecSchema } from "../config/module"
import handlebars = require("handlebars")
import { joiArray, joi } from "../config/common"
Expand Down Expand Up @@ -228,6 +228,10 @@ function renderMarkdownLink(description: NormalizedDescription) {

function makeMarkdownDescription(description: NormalizedDescription, titlePrefix = "") {
const { formattedType, required, allowedValues, defaultValue } = description
let experimentalFeature = false
if (description.meta) {
experimentalFeature = find(description.meta, attr => attr.experimental) || false
}

const parentDescriptions = getParentDescriptions(description)
const title = renderMarkdownTitle(description, titlePrefix)
Expand Down Expand Up @@ -256,6 +260,7 @@ function makeMarkdownDescription(description: NormalizedDescription, titlePrefix
return {
...description,
breadCrumbs,
experimentalFeature,
formattedExample,
title,
table,
Expand Down
3 changes: 3 additions & 0 deletions garden-service/src/docs/templates/config-partial.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

{{{breadCrumbs}}}
{{/if}}
{{#if experimentalFeature}}
> ⚠️ **Experimental**: this is an experimental feature and the API might change in the future.
{{/if}}
{{#if description}}

{{{description}}}
Expand Down
15 changes: 15 additions & 0 deletions garden-service/src/plugins/kubernetes/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@ interface KubernetesStorage {

export type ContainerBuildMode = "local-docker" | "cluster-docker" | "kaniko"

export type DefaultDeploymentStrategy = "rolling"
export type DeploymentStrategy = DefaultDeploymentStrategy | "blue-green"

export interface KubernetesBaseConfig extends ProviderConfig {
buildMode: ContainerBuildMode
context: string
defaultHostname?: string
defaultUsername?: string
deploymentStrategy?: DeploymentStrategy
forceSsl: boolean
imagePullSecrets: ProviderSecretRef[]
ingressHttpPort: number
Expand Down Expand Up @@ -249,6 +253,17 @@ export const kubernetesConfigBase = providerConfigBaseSchema
.example("api.mydomain.com"),
defaultUsername: joiIdentifier()
.description("Set a default username (used for namespacing within a cluster)."),
deploymentStrategy: joi.string()
.default("rolling")
.allow("rolling", "blue-green")
.description(dedent`
Defines the strategy for deploying the project services.
Default is "rolling update" and there is experimental support for "blue/green" deployment.
The feature only supports modules of type \`container\`: other types will just deploy using the default strategy.
`)
.meta({
experimental: true,
}),
forceSsl: joi.boolean()
.default(false)
.description(
Expand Down
Loading

0 comments on commit 01f59f5

Please sign in to comment.