Deploying and running Docker containers on Marathon

Last article I explained how you can build your Mesos infrastructure, so, from here I'd assume you have it working already.

First of all, we are going to build a simple application. It is going to be a Java application running on a Docker container, but, you can deploy whatever you want.

build.gradle

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.github.rholder:gradle-one-jar:1.0.4'
    }
}

apply plugin: 'java'
apply plugin: 'gradle-one-jar'

repositories {
    jcenter()
}

task uberJar(type: OneJar, dependsOn: build) {
    mainClass = 'hello.marathon.Main'
    archiveName = 'hello-marathon-all.jar'
}

dependencies {
    compile 'com.sparkjava:spark-core:2.5'
    compile 'org.slf4j:slf4j-api:1.7.14'

    testCompile 'junit:junit:4.12'
}

Main class

Our project will have only one class, which is going to expose an endpoint at /health. We are using SparkJava, a micro web framework that runs on top of a Jetty 9, so, we don't need much code:

package hello.marathon;

import static spark.Spark.*;

public class Main {
    public static void main(String [] args) {
        get("/hello-marathon/health", (req, res) -> "Application is running!");
    }
}

Dockerfile

No much to say here. Our Dockerfile will just execute our jar in an Alpine image.

FROM frolvlad/alpine-oraclejdk8:slim
VOLUME /tmp
ADD build/libs/hello-marathon-all.jar hello-marathon-all.jar
RUN sh -c 'touch /hello-marathon-all.jar'
ENTRYPOINT ["java", "-jar", "hello-marathon-all.jar"]
EXPOSE 4567

Building your jar

After creating your Main class, all you have to do is build your jar using:

gradle uberJar

Building your image and pushing it into a repository

Assuming you have a private repository, you need to set all the variables to push your image into your docker registry:

docker login your-docker-registry.com
docker build -t your-docker-registry.com/your-company/hello-marathon .
docker push your-docker-registry.com/your-company/hello-marathon

Configuring your Mesos slaves to run Docker containers

First of all, you need to install docker in all your slave machines:

sudo yum check-update
sudo curl -fsSL https://get.docker.com/ | sh

Then you must start it:

sudo systemctl start docker

After that, you should configure your slave machines to also be able to run docker containers:

echo 'docker,mesos' > /etc/mesos-slave/containerizers

And increase the executor timeout for a possible delay in downloading an image:

echo '5mins' > /etc/mesos-slave/executor_registration_timeout

Configuring your Mesos slaves to use a private docker registry

If you want to set up Marathon to run your Docker images, you need to do the following steps:

docker login your-docker-registry.com

After login in, you need to tar your credentials:

cd ~ && tar czf docker.tar.gz .docker

Somehow you need to make Marathon access this docker.tar.gz file, which contains your credentials. For this tutorial, let's assume you only copied this docker.tar.gz and put it into /etc/ in each one of the Marathon machines. You can also expose this file via HTTP if you want to.

Deploying your application into Marathon

Finally, all you have to do now is deploy your application.

curl -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{
  "id": "/hello-marathon",
  "cpus": 0.25,
  "mem": 256,
  "disk": 128,
  "instances": 2,
  "constraints": [
    [
      "hostname",
      "UNIQUE"
    ]
  ],
  "container": {
    "type": "DOCKER",
    "volumes": [],
    "docker": {
      "image": "your-docker-registry.com/your-company/hello-marathon",
      "network": "BRIDGE",
      "portMappings": [
        {
          "containerPort": 4567,
          "hostPort": 0,
          "servicePort": 0,
          "protocol": "tcp",
          "labels": {}
        }
      ],
      "privileged": false,
      "parameters": [],
      "forcePullImage": true
    }
  },
  "healthChecks": [
    {
      "path": "/hello-marathon/health",
      "protocol": "HTTP",
      "portIndex": 0,
      "gracePeriodSeconds": 30,
      "intervalSeconds": 1,
      "timeoutSeconds": 5,
      "maxConsecutiveFailures": 1,
      "ignoreHttp1xx": false
    }
  ],
  "portDefinitions": [
    {
      "port": 0,
      "protocol": "tcp",
      "labels": {}
    }
  ],
  "uris": [
    "file:///etc/docker.tar.gz"
  ]
}' "http://marathon-1:8080/v2/apps"

A few information:

  • Our app will have two instances.
  • Each instance will use 0.25 of a CPU.
  • Each instance will use 256mb of memory.
  • Using the "constraints" field, we inform Mesos that only one instance will run per host. So, since we asked Marathon to run our app in two instances, our app will run in two different Mesos slave hosts.
  • There is a health check in /hello-marathon/health.
  • Our application will run in a random port.

Now you should check in Marathon if our app is deploying...

Screenshot-2016-08-07-12.03.45

...and then running:

Screenshot-2016-08-07-12.05.05

So, once again, assuming you followed the last article, I'd expect you already have Nixy and Nginx working for the service discovery of our app, or at least an alternative, so, all you have to do now is check if our app is available in any proxy machine:

curl -X GET http://proxy-n/hello-marathon/health

And that's all.

I hope this post was helpful to you.
Questions? Please, leave a comment!

Gabriel Francisco

Software Engineer at GFG, 25 years, under graduated in Computer Science and graduated in Service-oriented Software Engineering. Like playing guitar once in a while. Oh, and I'm kind of boring.

São Paulo

comments powered by Disqus