January 18, 2019

Learning BOSH: Deploying Web Application

Welcome to my Learning BOSH diary:

In this post we will add a web application to our deployment.

Last time we created a release to deploy Nginx using BOSH. Nginx has pretty simple config though - it is responding to any request with 200 OK and static greeting.

 return 200 "\nKia Ora!\n";

Follow the steps here in order to recreate what we have done (in case you took down your environment).

Lets add a web application to the mix. The application we are going to use is a very simple Go web application which I am using when exploring different deployment options like BOSH, Cloud Foundry, etc. You can find it here.

We are going to start with adding new release and instance group to our deployment manifest.

$ git diff akoranga-deployment.yml
diff --git a/akoranga-deployment.yml b/akoranga-deployment.yml
index 9e9c76f..13f819a 100644
--- a/akoranga-deployment.yml
+++ b/akoranga-deployment.yml
@@ -4,6 +4,8 @@ name: akoranga
 releases:
 - name: akoranga-nginx-boshrelease
   version: latest
+- name: akoranga-go-boshrelease
+  version: latest
 
 update:
   canaries: 1
@@ -27,3 +29,14 @@ instance_groups:
   networks:
   - name: default
   stemcell: default
+
+- name: akoranga-app
+  azs: [z1, z2, z3]
+  instances: 2
+  jobs:
+  - name: app
+    release: akoranga-go-boshrelease
+  vm_type: default
+  stemcell: default
+  networks:
+  - name: default

You can find the updated manifest here.

We know what this means, right? There will be a new group of instances named akoranga-app with two members and they will have the job named app running. This job is provided by the akoranga-go-boshrelease. We do not have this release yet so lets create it.

Create one more BOSH release

learning-bosh$ bosh init-release --git --dir akoranga-go-boshrelease
Succeeded

learning-bosh$ cd akoranga-go-boshrelease/

learning-bosh/akoranga-go-boshrelease$ bosh generate-job app
Succeeded

learning-bosh/akoranga-go-boshrelease$ tree .
.
├── config
│   ├── blobs.yml
│   └── final.yml
├── jobs
│   └── app
│       ├── monit
│       ├── spec
│       └── templates
├── packages
└── src

The above generates new release and also creates new job named app for this release. Now lets update the monit script for this job:

check process app
  with pidfile /var/vcap/sys/run/app/app.pid
  start program "/var/vcap/jobs/app/bin/ctl start"
  stop program "/var/vcap/jobs/app/bin/ctl stop"
  group vcap 

Next is the control script:

#!/bin/bash

RUN_DIR=/var/vcap/sys/run/app
LOG_DIR=/var/vcap/sys/log/app
PIDFILE=${RUN_DIR}/app.pid

case $1 in

  start)
    mkdir -p $RUN_DIR $LOG_DIR
    chown -R vcap:vcap $RUN_DIR $LOG_DIR

    /var/vcap/packages/akoranga-go/akoranga-go \
      >>  $LOG_DIR/app.stdout.log \
      2>> $LOG_DIR/app.stderr.log & echo $! > $PIDFILE

    ;;

  stop)
    kill `cat $PIDFILE`
    rm -f $PIDFILE

    ;;

  *)
    echo "Usage: ctl {start|stop}" ;;

esac

It is pretty much the same as the one we wrote for the nginx job with the difference being in how we start this one.

  start)
    mkdir -p $RUN_DIR $LOG_DIR
    chown -R vcap:vcap $RUN_DIR $LOG_DIR

    /var/vcap/packages/akoranga-go/akoranga-go \
      >>  $LOG_DIR/app.stdout.log \
      2>> $LOG_DIR/app.stderr.log & echo $! >> $PIDFILE

    ;;

Here we run an executable named akoranga-go , redirect its stdout and stderr to the respective log files and save the pid of the process just started in the $PIDFILE.

Last comes the spec file:

---
name: app

templates:
  ctl.erb: bin/ctl

packages: []

properties: {}

What we have so far can be found here.

Package your dependencies

You probably noticed that this time we have that /var/vcap/packages/akoranga-go directory and may wonder where it comes from? It will be provided by a package named akoranga-go. According to the docs

Packages give BOSH the information needed to prepare the binaries and dependencies for your jobs.
[docs]

Last time for akoranga-nginx-boshrelease we cheated and used bosh vendor-package to reuse Nginx from bosh-packages/nginx-release. This time we will cheat even more and will use precompiled binary as a blob. Later in the future I would like to update this release to actually compile it as part of the release (using bosh-packages/golang-release).

Lets start with creating this new package:

learning-bosh/akoranga-go-boshrelease$ bosh generate-package akoranga-go
Succeeded

learning-bosh/akoranga-go-boshrelease$ tree packages/
packages/
└── akoranga-go
    ├── packaging
    └── spec

Next we update the package spec (packages/akoranga-go/spec) to describe what files we are going to need. It should look like this:

---
name: akoranga-go

dependencies: []

files:
- akoranga-go-boshrelease/akoranga-go-bosh-0.0.2-linux-amd64.tgz

The tar archive will be provided from a blobstore but first lets go on and update the packaging script (packages/akoranga-go/packaging):

set -e

VERSION=bosh-0.0.2

tar -xzvf akoranga-go-boshrelease/akoranga-go-${VERSION}-linux-amd64.tgz -C ${BOSH_INSTALL_TARGET}/
chmod +x ${BOSH_INSTALL_TARGET}/akoranga-go-${VERSION}-linux-amd64
ln ${BOSH_INSTALL_TARGET}/akoranga-go-${VERSION}-linux-amd64 ${BOSH_INSTALL_TARGET}/akoranga-go

This is the script BOSH will run to create the package. In our case it is pretty simple - untar the archive, make the binary executable and create a symlink.

Now when we know how to create this package it is time to provide everything needed which for this case is just the tar archive. We will use a local blobstore. Lets start with configuring this one in config/final.yaml.

name: akoranga-go-boshrelease

---
blobstore:
  provider: local

We can download the precompiled binary from here.

$ wget https://github.com/krasio/akoranga-go/releases/download/bosh-0.0.2/akoranga-go-bosh-0.0.2-linux-amd64.tgz -P /tmp

Now lets tell BOSH where the blob is:

learning-bosh/akoranga-go-boshrelease$ bosh add-blob /tmp/akoranga-go-bosh-0.0.2-linux-amd64.tgz akoranga-go-boshrelease/akoranga-go-bosh-0.0.2-linux-amd64.tgz
Added blob 'akoranga-go-boshrelease/akoranga-go-bosh-0.0.2-linux-amd64.tgz'

Succeeded

The first argument is the path to the blob on the local filesystem and the second argument is the path we specified in the package spec above. What this command actually do is to copy the tar from /tmp to ~/blobs:

learning-bosh/akoranga-go-boshrelease$ tree blobs/
blobs/
└── akoranga-go-boshrelease
    └── akoranga-go-bosh-0.0.2-linux-amd64.tgz

Last we update the spec for the app job to include the package it depends on:

---
name: app

templates:
  ctl.erb: bin/ctl

packages:
- akoranga-go

properties: {}

The commit for this is here.

Time to create the release:

learning-bosh/akoranga-go-boshrelease$ bosh create-release --force
Adding job 'app/94824da16dbf62e87dbed6268325009e7129d21f'...
Added job 'app/94824da16dbf62e87dbed6268325009e7129d21f'
Adding package 'akoranga-go/d98a96f3345b274bcd0a023dd1e8bcb0c8820a8a'...
Added package 'akoranga-go/d98a96f3345b274bcd0a023dd1e8bcb0c8820a8a'

Added dev release 'akoranga-go-boshrelease/0+dev.1'

Name         akoranga-go-boshrelease  
Version      0+dev.1  
Commit Hash  0b5f297  

Job                                           Digest                                    Packages  
app/94824da16dbf62e87dbed6268325009e7129d21f  7b35ea0062795509c4610038486fb5b1edb5dfc7  -  

1 jobs

Package                                               Digest                                    Dependencies  
akoranga-go/d98a96f3345b274bcd0a023dd1e8bcb0c8820a8a  e1ba4376b4352e519aa17b343fd8db766d539b06  -  

1 packages

Succeeded

and upload it

learning-bosh/akoranga-go-boshrelease$ bosh upload-release
Using environment '192.168.50.6' as user 'admin' (openid, bosh.admin)

######################################################### 100.00% 33.25 MiB/s 0s
Task 6

Task 6 | 00:02:27 | Extracting release: Extracting release (00:00:00)
Task 6 | 00:02:27 | Verifying manifest: Verifying manifest (00:00:00)
Task 6 | 00:02:27 | Resolving package dependencies: Resolving package dependencies (00:00:00)
Task 6 | 00:02:27 | Creating new packages: akoranga-go/d98a96f3345b274bcd0a023dd1e8bcb0c8820a8a (00:00:00)
Task 6 | 00:02:27 | Creating new jobs: app/94824da16dbf62e87dbed6268325009e7129d21f (00:00:00)
Task 6 | 00:02:27 | Release has been created: akoranga-go-boshrelease/0+dev.1 (00:00:00)

Task 6 Started  Fri Dec 21 00:02:27 UTC 2018
Task 6 Finished Fri Dec 21 00:02:27 UTC 2018
Task 6 Duration 00:00:00
Task 6 done

Succeeded

We should now have two release available to BOSH:

$ bosh releases
Using environment '192.168.50.6' as user 'admin' (openid, bosh.admin)

Name                        Version   Commit Hash  
akoranga-go-boshrelease     0+dev.1   0b5f297  
akoranga-nginx-boshrelease  0+dev.2*  8f8be18  

(*) Currently deployed
(+) Uncommitted changes

2 releases

Succeeded

Update the deployment

Now when we have akoranga-go-boshrelease available to use it is time to update our deployment:

learning-bosh$ bosh -d akoranga deploy akoranga-deployment.yml 
Using environment '192.168.50.6' as user 'admin' (openid, bosh.admin)

Using deployment 'akoranga'

  releases:
+ - name: akoranga-go-boshrelease
+   version: 0+dev.8
  
  instance_groups:
+ - azs:
+   - z1
+   - z2
+   - z3
+   instances: 2
+   jobs:
+   - name: app
+     release: akoranga-go-boshrelease
+   name: akoranga-app
+   networks:
+   - name: default
+   stemcell: default
+   vm_type: default

Continue? [yN]: y

Task 6

Task 6 | 01:31:47 | Preparing deployment: Preparing deployment (00:00:01)
Task 6 | 01:31:48 | Preparing package compilation: Finding packages to compile (00:00:00)
Task 6 | 01:31:48 | Compiling packages: akoranga-go/24f30d18689e26726ba0aeebe451da447da465c8 (00:00:08)
Task 6 | 01:31:57 | Creating missing vms: akoranga-app/a414895a-63e9-40e5-bdab-dd1e28bf1eed (0)
Task 6 | 01:31:57 | Creating missing vms: akoranga-app/2bbbd2a0-1d48-4d54-aa46-fd9f9fbe9a7e (1)
Task 6 | 01:32:05 | Creating missing vms: akoranga-app/a414895a-63e9-40e5-bdab-dd1e28bf1eed (0) (00:00:08)
Task 6 | 01:32:05 | Creating missing vms: akoranga-app/2bbbd2a0-1d48-4d54-aa46-fd9f9fbe9a7e (1) (00:00:08)
Task 6 | 01:32:06 | Updating instance akoranga-app: akoranga-app/a414895a-63e9-40e5-bdab-dd1e28bf1eed (0) (canary) (00:00:13)
Task 6 | 01:32:19 | Updating instance akoranga-app: akoranga-app/2bbbd2a0-1d48-4d54-aa46-fd9f9fbe9a7e (1) (00:00:13)

Task 6 Started  Fri Dec 21 01:31:47 UTC 2018
Task 6 Finished Fri Dec 21 01:32:32 UTC 2018
Task 6 Duration 00:00:45
Task 6 done

Succeeded

Lets check our newly built instances:

learning-bosh [master]$ bosh instances
Using environment '192.168.50.6' as user 'admin' (openid, bosh.admin)

Task 7. Done

Deployment 'akoranga'

Instance                                             Process State  AZ  IPs  
akoranga-app/2bbbd2a0-1d48-4d54-aa46-fd9f9fbe9a7e    running        z2  10.244.0.4  
akoranga-app/a414895a-63e9-40e5-bdab-dd1e28bf1eed    running        z1  10.244.0.3  
akoranga-nginx/0eacb6b0-3ac8-4075-9b3c-18c9c935777d  running        z1  10.244.0.2  

3 instances

Succeeded

Nice. What about sending requests to them?

$ curl -i 10.244.0.3:8080
HTTP/1.1 200 OK
Date: Fri, 21 Dec 2018 01:34:40 GMT
Content-Length: 48
Content-Type: text/plain; charset=utf-8

[a414895a-63e9-40e5-bdab-dd1e28bf1eed] Kia ora!

$ curl -i 10.244.0.4:8080
HTTP/1.1 200 OK
Date: Fri, 21 Dec 2018 01:34:43 GMT
Content-Length: 48
Content-Type: text/plain; charset=utf-8

[2bbbd2a0-1d48-4d54-aa46-fd9f9fbe9a7e] Kia ora!

Totes success!

Next we should look at how to update the nginx job to route traffic to these instances instead of responding with the hardcoded 200 OK as it currently does. Find how we can do this here.

Powered by Hugo & Kiss.