-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6c3dc35
commit 90e858a
Showing
11 changed files
with
370 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# HAProxy Ingress blue/green deployment | ||
|
||
This example demonstrates how to configure | ||
[blue/green deployment](https://www.martinfowler.com/bliki/BlueGreenDeployment.html) | ||
on HAProxy Ingress controller. | ||
|
||
## Prerequisites | ||
|
||
This document has the following prerequisite: | ||
|
||
* A Kubernetes cluster with a running HAProxy Ingress controller. See the [five minutes deployment](/examples/setup-cluster.md#five-minutes-deployment) or the [deployment example](/examples/deployment) | ||
|
||
## Deploying applications | ||
|
||
In order to the configuration have effect, at least two deployments, or daemon sets, or replication | ||
controllers should be used with at least two pairs of label name/value. | ||
|
||
The following instructions create two deployment objects using `run` label as the service selector | ||
and `group` label as the blue/green deployment selector: | ||
|
||
``` | ||
$ kubectl run blue \ | ||
--image=gcr.io/google_containers/echoserver:1.3 \ | ||
--port=8080 --labels=run=bluegreen,group=blue | ||
deployment "blue" created | ||
$ kubectl run green \ | ||
--image=gcr.io/google_containers/echoserver:1.3 \ | ||
--port=8080 --labels=run=bluegreen,group=green | ||
deployment "green" created | ||
``` | ||
|
||
Certify if the pods are running and have the correct labels. Note that both `group` and `run` | ||
labels were applied: | ||
|
||
``` | ||
$ kubectl get pod -lrun=bluegreen --show-labels | ||
NAME READY STATUS RESTARTS AGE LABELS | ||
blue-79c9b67d5b-5hd2r 1/1 Running 0 35s group=blue,pod-template-hash=3575623816,run=bluegreen | ||
green-7546d648c4-p7pmz 1/1 Running 0 28s group=green,pod-template-hash=3102820470,run=bluegreen | ||
``` | ||
|
||
# Configure | ||
|
||
Create a service that bind both deployments together using the `run` label. The expose command need | ||
a deployment object, take anyone, we will override it's selector: | ||
|
||
``` | ||
$ kubectl expose deploy blue --name bluegreen --selector=run=bluegreen | ||
service "bluegreen" exposed | ||
$ kubectl get svc bluegreen -otemplate --template '{{.spec.selector}}' | ||
map[run:bluegreen] | ||
``` | ||
|
||
Check also the endpoints, it should list both blue and green pods: | ||
|
||
``` | ||
$ kubectl get ep bluegreen | ||
NAME ENDPOINTS AGE | ||
bluegreen 172.17.0.11:8080,172.17.0.19:8080 2m | ||
$ kubectl get pod -lrun=bluegreen -owide | ||
NAME READY STATUS RESTARTS AGE IP NODE | ||
blue-79c9b67d5b-5hd2r 1/1 Running 0 2m 172.17.0.11 192.168.100.99 | ||
green-7546d648c4-p7pmz 1/1 Running 0 2m 172.17.0.19 192.168.100.99 | ||
``` | ||
|
||
Configure the ingress resource: | ||
|
||
``` | ||
$ kubectl create -f - <<EOF | ||
apiVersion: extensions/v1beta1 | ||
kind: Ingress | ||
metadata: | ||
annotations: | ||
ingress.kubernetes.io/ssl-redirect: "false" | ||
ingress.kubernetes.io/blue-green-deploy: group=blue=1,group=green=4 | ||
name: bluegreen | ||
spec: | ||
rules: | ||
- host: example.com | ||
http: | ||
paths: | ||
- backend: | ||
serviceName: bluegreen | ||
servicePort: 8080 | ||
path: / | ||
EOF | ||
``` | ||
|
||
``` | ||
$ kubectl get ing | ||
NAME HOSTS ADDRESS PORTS AGE | ||
bluegreen example.com 80 11s | ||
``` | ||
|
||
Lets test! Change the IP below to your HAProxy Ingress controller: | ||
|
||
``` | ||
$ for i in `seq 1 50`; do | ||
echo -n "." | ||
curl -s 192.168.100.99 -H 'Host: example.com' >/dev/null | ||
done | ||
.................................................. | ||
``` | ||
|
||
Now check `<ingress-ip>:1936` - the stats page has a `<namespace>-bluegreen-8080` card | ||
with backend stats. The column Session/Total should have about 20% of the deployments | ||
on one endpoint and 80% on another. | ||
|
||
The blue/green configuration is related to every single pod - if the number of replicas change, | ||
the load will also change between blue and green groups. Have a look at the | ||
[doc](/README.md#blue-green) on how it works. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
Copyright 2018 The Kubernetes Authors. | ||
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. | ||
*/ | ||
|
||
package bluegreen | ||
|
||
import ( | ||
"fmt" | ||
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/annotations/parser" | ||
extensions "k8s.io/api/extensions/v1beta1" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
const ( | ||
blueGreenAnn = "ingress.kubernetes.io/blue-green-deploy" | ||
) | ||
|
||
// DeployWeight has one label name/value pair and it's weight | ||
type DeployWeight struct { | ||
LabelName string | ||
LabelValue string | ||
Weight int | ||
} | ||
|
||
// Config is the blue/green deployment configuration | ||
type Config struct { | ||
DeployWeight []DeployWeight | ||
} | ||
|
||
type bgdeploy struct { | ||
} | ||
|
||
// NewParser creates a new blue/green annotation parser | ||
func NewParser() parser.IngressAnnotation { | ||
return bgdeploy{} | ||
} | ||
|
||
// Parse parses blue/green annotation and create a Config struct | ||
func (bg bgdeploy) Parse(ing *extensions.Ingress) (interface{}, error) { | ||
s, err := parser.GetStringAnnotation(blueGreenAnn, ing) | ||
if err != nil { | ||
return nil, err | ||
} | ||
weights := strings.Split(s, ",") | ||
var dw []DeployWeight | ||
for _, weight := range weights { | ||
dwSlice := strings.Split(weight, "=") | ||
if len(dwSlice) != 3 { | ||
return nil, fmt.Errorf("invalid weight format on blue/green config: %v", weight) | ||
} | ||
w, err := strconv.ParseInt(dwSlice[2], 10, 0) | ||
if err != nil { | ||
return nil, fmt.Errorf("error reading blue/green config: %v", err) | ||
} | ||
if w < 0 { | ||
w = 0 | ||
} | ||
dwItem := DeployWeight{ | ||
LabelName: dwSlice[0], | ||
LabelValue: dwSlice[1], | ||
Weight: int(w), | ||
} | ||
dw = append(dw, dwItem) | ||
} | ||
return &Config{ | ||
DeployWeight: dw, | ||
}, nil | ||
} | ||
|
||
// Equal tests equality between two Config objects | ||
func (b1 *Config) Equal(b2 *Config) bool { | ||
if len(b1.DeployWeight) != len(b2.DeployWeight) { | ||
return false | ||
} | ||
for _, dw1 := range b1.DeployWeight { | ||
found := false | ||
for _, dw2 := range b2.DeployWeight { | ||
if (&dw1).Equal(&dw2) { | ||
found = true | ||
break | ||
} | ||
} | ||
if !found { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// Equal tests equality between two DeployWeight objects | ||
func (dw1 *DeployWeight) Equal(dw2 *DeployWeight) bool { | ||
if dw1.LabelName != dw2.LabelName { | ||
return false | ||
} | ||
if dw1.LabelValue != dw2.LabelValue { | ||
return false | ||
} | ||
if dw1.Weight != dw2.Weight { | ||
return false | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.