This repository contains code used in the Scaling To Infinity with Docker Swarm, Docker Compose and Consul article series:

Note for Windows users:

If you run into issues with ansible complaining about executable permissions, try modifying the Vagrantfile's synced_folder entry from this:

  config.vm.synced_folder ".", "/vagrant"

to this:

  config.vm.synced_folder ".", "/vagrant", mount_options: [“dmode=700,fmode=600]

For demonstration purposes

Up the four VMs:

vagrant up

Login to swarm-master VM

vagrant ssh swarm-master

Provision the complete cluster environment:

ansible-playbook /vagrant/ansible/infra.yml -i /vagrant/ansible/hosts/prod

See what’s in our cluster (consul):

  • Commandline: consul members
  • REST: curl localhost:8500/v1/catalog/nodes | jq .
  • UI: http://10.100.199.200:8500/ui/

Connect docker client to Docker Swarm

export DOCKER_HOST=tcp://0.0.0.0:2375
docker info

deploy book service backend using Ansible (coming straight from docker hub)

ansible-playbook /vagrant/ansible/books-service.yml -i /vagrant/ansible/hosts/prod

See where it is deployed to (notice that service backend is deployed to different node than mongo db):

docker ps | grep booksservice

See what’s located in the database:

curl http://10.100.199.200/api/v1/books | jq .

Let’s add and see books to the backend-server!

curl -H 'Content-Type: application/json' -X PUT -d '{"_id": 1, "title": "My First Book", "author": "John Doe", "description": "Not a very good book"}' http://10.100.199.200/api/v1/books | jq .

curl -H 'Content-Type: application/json' -X PUT -d '{"_id": 2, "title": "My Second Book", "author": "John Doe", "description": "Not a bad as the first book"}' http://10.100.199.200/api/v1/books | jq .

curl -H 'Content-Type: application/json' -X PUT -d '{"_id": 3, "title": "My Third Book", "author": "John Doe", "description": "Failed writers club"}' http://10.100.199.200/api/v1/books | jq .

curl http://10.100.199.200/api/v1/books | jq .

Deploy the booksservice front-end (docker container)

ansible-playbook /vagrant/ansible/books-fe.yml -i /vagrant/ansible/hosts/prod

See where it is deployed to (repeat previous step, perform this step again and see containers moving)

docker ps | grep books

Now, the following should be accessible:

Demo Jenkins in this cluster

Set up the system

To allow infrastructure to login to your public docker repo. This will save you login credentials in /home/vagrant/.docker/config.json

docker login 

Open Jenkins UI:

http://10.100.199.200:8080/

In Jenkins setup a manual cd node (did not yet come to automate this):

  • Click Manage Jenkins > Manage Nodes > New Node
  • Name it cd, select Dumb Slave and click OK
  • Type /data/jenkins/slaves/cd as Remote Root Directory
  • Type 10.100.199.200 as Host
  • Select Launch slave agents on Unix machines via SSH
  • Click Add* next to **Credentials
  • Use vagrant as both Username and Password and click Add
  • Click Save

Explanation

There are four jobs predefined

  • books-fe - Deploy books front-end by running Ansible Playbook to our cluster
  • books-service - Deploy books front-end by running Ansible Playbook to our cluster
  • books-service-test - pull booksservice source code from github, compile, TEST, build container and push to docker repo, run tests, build, compile and push final code to docker repo.
  • books-service-tested - downstream job pulling code pushed to docker repo and use Ansible to deploy.

Now, to see how it works, do the following:

  • run books-service-test - see that tests are run on backend code, and when ok how new docker container is build and pushed to docker repo.
  • run books-service-tested - run ansible playbook which will pull the tested container from docker repo and deploy into cluster.

Now update (and break) source code for booksservice

Clone repository to your local system

git clone https://github.com/msens/books-service

update code in /src/main/scala/com/technologyconversations/api/ServiceActor.scala, change books to comics, like so:

val serviceRoute = pathPrefix("api" / "v1" / "books") {
    path("_id" / IntNumber) { id =>
      get {
        complete(
          grater[Book].asObject(
            collection.findOne(MongoDBObject("_id" -> id)).get
          )
        )
      }

to

val serviceRoute = pathPrefix("api" / "v1" / "comics") {
    path("_id" / IntNumber) { id =>
      get {
        complete(
          grater[Book].asObject(
            collection.findOne(MongoDBObject("_id" -> id)).get
          )
        )
      }

Push the update

git push

Now, run this job again and see it fail. No container buidl or deployment will follow:

  • run books-service-test - see test fail. No container will be build, no downstream job will run.