Create a canary release
To test the new version in production for example with A/B testing we need the possibility to make a canary release.
Create JavaScript
Like the start of the test environment we start the canary with JavaScript.
#!/usr/bin/jjs -fv
var FileWriter = Java.type("java.io.FileWriter");
var version = $ENV.VERSION;
var kubectl = $ENV.KUBECTL;
var name = "battleapp";
var nameWithVersion = name + "-" + version;
var image = "disruptor.ninja:30500/robertbrem/battleapp:" + version;
var replicas = 1;
var port = 8080;
var clusterPort = 8880;
var nodePort = 30080;
var deploymentFileName = "deployment.yml";
var serviceFileName = "service.yml";
var registrysecret = "registrykey";
var url = "http://disruptor.ninja:" + nodePort + "/battleapp/resources/users";
var timeout = 2;
var dfw = new FileWriter(deploymentFileName);
dfw.write("apiVersion: extensions/v1beta1\n");
dfw.write("kind: Deployment\n");
dfw.write("metadata:\n");
dfw.write(" name: " + nameWithVersion + "\n");
dfw.write("spec:\n");
dfw.write(" replicas: " + replicas + "\n");
dfw.write(" template:\n");
dfw.write(" metadata:\n");
dfw.write(" labels:\n");
dfw.write(" name: " + name + "\n");
dfw.write(" version: " + version + "\n");
dfw.write(" spec:\n");
dfw.write(" containers:\n");
dfw.write(" - resources:\n");
dfw.write(" name: " + name + "\n");
dfw.write(" image: " + image + "\n");
dfw.write(" ports:\n");
dfw.write(" - name: port\n");
dfw.write(" containerPort: " + port + "\n");
dfw.write(" imagePullSecrets:\n");
dfw.write(" - name: " + registrysecret + "\n");
dfw.close();
var deploy = kubectl + " create -f " + deploymentFileName;
execute(deploy);
var sfw = new FileWriter(serviceFileName);
sfw.write("apiVersion: v1\n");
sfw.write("kind: Service\n");
sfw.write("metadata:\n");
sfw.write(" name: " + name + "\n");
sfw.write(" labels:\n");
sfw.write(" name: " + name + "\n");
sfw.write("spec:\n");
sfw.write(" ports:\n");
sfw.write(" - port: " + clusterPort + "\n");
sfw.write(" targetPort: " + port + "\n");
sfw.write(" nodePort: " + nodePort + "\n");
sfw.write(" selector:\n");
sfw.write(" name: " + name + "\n");
sfw.write(" type: NodePort\n");
sfw.close();
var deployService = kubectl + " create -f " + serviceFileName;
execute(deployService);
var testUrl = "curl --write-out %{http_code} --silent --output /dev/null " + url + " --max-time " + timeout;
execute(testUrl);
while ($OUT != "200") {
$EXEC("sleep 1");
execute(testUrl);
}
function execute(command) {
$EXEC(command);
print($OUT);
print($ERR);
}
That the script can be executed it has to be executable:
chmod 750 start.js
Add the canary release as CI step
This script has to be added to the Jenkins pipeline:
stage "start canary"
input "deploy the canary?"
node {
git url: "https://github.com/robertBrem/BattleApp-Canary"
sh "./start.js"
}
Then Build Now
.
Test the canary release
After the first canary release we can check our cluster. It should look something like this:
kc get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
battleapp-1.0.17 1 1 1 1 10m
battleapp-test 1 1 1 1 12m
jenkins 1 1 1 1 15h
registry 1 1 1 1 20h
kc get pod
NAME READY STATUS RESTARTS AGE
battleapp-1.0.17-3230326404-2xvxb 1/1 Running 0 9m
battleapp-test-1930419673-78jpf 1/1 Running 0 13m
jenkins-3074977187-rcg1v 1/1 Running 0 15h
registry-95525520-9rdvc 1/1 Running 0 20h
An impressive way to test a canary release is to change the REST service and push the change.
Then you can open a terminal and execute this script to see the change:
while true; do curl http://disruptor.ninja:30080/battleapp/resources/users; echo ""; sleep 1; done
After the canary release of the changed service the output look something like this:
[{"name":"Robert"},{"name":"Kevin"},{"name":"Dan"}]
[{"name":"dan"},{"name":"robert"},{"name":"kevin"}]
[{"name":"Robert"},{"name":"Kevin"},{"name":"Dan"}]
[{"name":"dan"},{"name":"robert"},{"name":"kevin"}]
[{"name":"dan"},{"name":"robert"},{"name":"kevin"}]
[{"name":"dan"},{"name":"robert"},{"name":"kevin"}]
[{"name":"Robert"},{"name":"Kevin"},{"name":"Dan"}]
[{"name":"Robert"},{"name":"Kevin"},{"name":"Dan"}]
[{"name":"Robert"},{"name":"Kevin"},{"name":"Dan"}]
The cluster is now looking something like this:
kc get pod
NAME READY STATUS RESTARTS AGE
battleapp-1.0.17-3230326404-2xvxb 1/1 Running 0 19m
battleapp-1.0.18-3418480262-5n1d7 1/1 Running 0 2m
battleapp-test-2013584858-kq2bg 1/1 Running 0 4m
jenkins-3074977187-rcg1v 1/1 Running 0 15h
registry-95525520-9rdvc 1/1 Running 0 20h
Create a readiness probe
It can happen that the Kubernetes service is routing requests to the new service
even if this service is not ready yet and we get 404
for a short period. To
suppress this behavior we can create a readiness probe
for our canary deployment:
var dfw = new FileWriter(deploymentFileName);
dfw.write("apiVersion: extensions/v1beta1\n");
dfw.write("kind: Deployment\n");
dfw.write("metadata:\n");
dfw.write(" name: " + nameWithVersion + "\n");
dfw.write("spec:\n");
dfw.write(" replicas: " + replicas + "\n");
dfw.write(" template:\n");
dfw.write(" metadata:\n");
dfw.write(" labels:\n");
dfw.write(" name: " + name + "\n");
dfw.write(" version: " + version + "\n");
dfw.write(" spec:\n");
dfw.write(" containers:\n");
dfw.write(" - resources:\n");
dfw.write(" name: " + name + "\n");
dfw.write(" image: " + image + "\n");
dfw.write(" ports:\n");
dfw.write(" - name: port\n");
dfw.write(" containerPort: " + port + "\n");
dfw.write(" readinessProbe:\n");
dfw.write(" httpGet:\n");
dfw.write(" path: " + relativeUrl + "\n");
dfw.write(" port: " + port + "\n");
dfw.write(" initialDelaySeconds: " + initialDelay + "\n");
dfw.write(" timeoutSeconds: " + readinessProbeTimeout + "\n");
dfw.write(" imagePullSecrets:\n");
dfw.write(" - name: " + registrysecret + "\n");
dfw.close();
Now there shouldn't be any 404
errors.