ASP.NET 5 Docker Orchestration Todo App

Microsoft released ASP.NET docker image a couple of weeks ago and I wrote a post on how easily we can deploy any of the base sample apps, using the base container. In this post, I will show you how easily you can build, orchestrate and push a Todo app, using asp.net MVC 6, angularJS, and mongoDB to CoreOS using docker containers with hipops. You can find the finished code here.


Intro

Today with asp.net vNext, we can start thinking about building small pluggable applications. In this demo, we are building a standalone ConsumerAPI MVC 6 Web API app that is using mongoDB for data persistence. vNext allows us to easily define the mongoDB connection string at the runtime using environment variables, which means we can start an independent mongoDB docker container and attach that to the web API at the runtime. In addition, I prefer using a standalone project that just serves static html for front-end UI ConsumerWeb and to connect that back to our Web API. This way, we are building another small pluggable static nginx docker container that helps us build a more distributed application. In the future, we could even take this architecture further with the the addition of logging and messaging queue containers that will allow us to build more distributed and OS agnostic applications.

ASP.NET MVC 6 API.Consumer (Todo API)

ASP.NET has a lot of good articles about building MVC 6 Web API. Our Todo API is not that much different with the exception of adding mongoDB connection strings. vNext allows us to define a config.json and override that at the runtime. Here is what our config.json looks like.

{
  "MONGO_CONNECTION": "mongodb://172.17.8.101:9990",
  "MONGO_DB_NAME": "aspnet_demo"
}

Since our Startup.cs is hooking up environment variables after config.json

public Startup()  
{
  Configuration = new Configuration()
    .AddJsonFile("config.json")
    .AddEnvironmentVariables();
}

We can overwrite this connection string by setting the environment variable.

MONGO_CONNECTION=mongodb://MY.IP k kestrel  

AngularJS Web UI with node, bower, grunt

Our front-end is supposed to be a dummy static HTML built with angularJS that connects back to our API. Since we are running the API and Web UI in different domains, we need an organized way to map our API inside of the Web UI. I have included a app/data/url.json that is supposed to be the map for all HTTP requests. By default it's always pointing to our Development environment.

{
    "domains": {
        "consumer": "@@http://localhost:5004",
        "admin": "http://api.admin"
    },
    "endpoints": {
        "consumer": {
            "todoFind": {
                "method": "GET",
                "url": "/api/todo/{id}"
            },
            "todoDelete": {
                "method": "DELETE",
                "url": "/api/todo/{id}"
            },
            "todoCreate": {
                "method": "POST",
                "url": "/api/todo"
            },
            "todoUpdate": {
                "method": "PUT",
                "url": "/api/todo"
            }
        }
    }
}

When building dist minified packages, we can simply override backend API by adding an environment variable for grunt

DOMAINS_API=http://aspnet-todo-demo-api.com grunt serve:dist  

Docker Orchestration

hipops is a Docker Orchestration tool based on a JSON Configuration. The idea behind hipops configuration is to define a scenario that have series of apps and re-uses them in playbooks, so you just focus on the orchestration of your containers, whether across different physical hosts or the same host. You can find the finished scenario code here. Let's take a look at what our JSON configuration looks like for the todo app.

{
  "id": "aspnet-todo",
  "description": "aspnet vnext todo demo",
  "env": "dev",
  "dest": "/data",
  "oses": [{
    "user": "core",
    "pythonInterpreter": "PATH=/home/core/bin:$PATH python"
  }],
  "apps": [{
    "name": "mongo",
    "type": "db",
    "image": "aminjam/mongodb:latest",
    "ports": [27017]
  }, {
    "name": "consumerAPI",
    "type": "kestrel",
    "image": "microsoft/aspnet:latest",
    "host": "aspnet-todo-demo-api.com",
    "ports": [5004],
    "repository": {
      "branch": "master",
      "sshUrl": "github.com/aminjam/aspnet-vnext-docker-demo.git",
      "folder": "src/API.Consumer/"
    },
    "customizations": [{
      "src": "aspnet-run.sh",
      "dest": "aspnet-run.sh",
      "mode": 744
    }]
  }, {
    "name": "consumerWeb",
    "type": "static",
    "image": "aminjam/nginx-static:tiny",
    "host": "aspnet-todo-demo-web.com",
    "ports": [80, 443],
    "repository": {
      "branch": "master",
      "sshUrl": "github.com/aminjam/aspnet-vnext-docker-demo.git",
      "folder": "src/Web/dist/"
    }
  }],
  "playbooks": [{
    "inventory": "tag_App-Role_ASPNET-TODO",
    "apps": ["{{index .Apps 0}}"],
    "containers": [{
      "params": "-v {{.App.Dest}}:/home/app -p 9990:{{index .App.Ports 0}} -e MONGO_OPTIONS='--smallfiles' -d {{.App.Image}}"
    }]
  }, {
    "inventory": "tag_App-Role_ASPNET-TODO",
    "apps": ["{{index .Apps 1}}"],
    "state": "deploying",
    "containers": [{
      "params": "-v {{.App.Dest}}:/home --expose {{index .App.Ports 0}} -e VIRTUAL_HOST={{.App.Host}} -e VIRTUAL_PORT={{index .App.Ports 0}} --link {{(index .Apps 0).Name}}:mongo -dt {{.App.Image}} /home/aspnet-run.sh"
    }]
  }, {
    "inventory": "tag_App-Role_ASPNET-TODO",
    "apps": ["{{index .Apps 2}}"],
    "state": "deploying",
    "containers": [{
      "params": "-v {{.App.Dest}}:/home/app -e VIRTUAL_HOST={{.App.Host}} -e VIRTUAL_PORT={{index .App.Ports 0}} -d {{.App.Image}}"
    }]
  }, {
    "inventory": "tag_App-Role_ASPNET-TODO",
    "containers": [{
      "params": "--name nginx-proxy -v /var/run/docker.sock:/tmp/docker.sock -p 80:80 -d aminjam/nginx-proxy:tiny"
    }]
  }]
}

We are defining 3 apps for this demo, mongo, consumerAPI, and consumerWeb. Playbooks are what we are using for the orchestration of our environment.

  1. Run the mongo app
  2. Deploy the consumerAPI app
  3. Deploy the consumerWeb app
  4. Run the nginx-proxy for DNS routing

hmm, how do I run it?!

If you have installed hipops you just need to have core-01 vagrant machine configured for deploying all of the apps.

hipops exec -plugin ansible -private-key ~/.vagrant.d/insecure_private_key -playbook-path /PATH/TO/hipops-playbooks/ansible  

After the orchestration has ran, set your hosts files for the configured Hosts entries in the config.json.

172.17.8.101 aspnet-todo-demo-api.com  
172.17.8.101 aspnet-todo-demo-web.com  

Visit http://aspnet-todo-demo-web.com for your todo app demo.

If you don't have hipops installed you can deploy using the hipops-ansible docker container without any installation.

docker run -v <path-to-here>:/home/app -v <git-key-file>:/home/git.key -v <ssh-private-key-file>:/home/vagrant.key -e GIT_KEY=/home/git.key -e PRIVATE_KEY=/home/vagrant.key -d aminjam/hipops-ansible  

If you are ready to deploy to AWS instead, it will be as easy as switching your inventory file and using AWS private key.

hipops exec -plugin ansible -private-key /PATH/TO/AWS.pem -playbook-path /PATH/TO/hipops-playbooks/ansible -inventory /PATH/TO/EC2.py  

hipops is a wrappper around ansible for simplified docker orchestration. If you don't use ansible, you can replace this demo orchestration for kubernetes or even run the docker run commands manually.

Summary

In this post, we built a todo app for asp.net vNext using mongoDB for data persistance, and deployed the stack of containers to a local CoreOS VM, using a hipops scenario and once we verified the build, we pushed that stack of docker containers to AWS or any other cloud providers.

comments powered by Disqus