Home Posts About Contact

Cloud Application Development

Containerize applications and run them in the cloud with OpenShift

using daemonsets on openshift

OpenShift uses Kubernetes orchestration to manage a variety of container runtimes inside pods, and run them in a public or dedicated cloud environment. It includes support for Docker containers, which we'll be using in this example. Applications can be made HA (High Availability) by running multiple instances of your application in parallel connected by an OpenShift service, and can scale up or down appropriately in response to changing traffic loads. In this case, our container will be running independently, but applications can be integrated with other services, persistent storage such as databases, which I will be covering in a future post.   Now, on to the code. In this tutorial we'll be containerizing our golang application from a previous post, /golang-echo-router-example. So be sure to check that out if you haven't already. To build a docker container, we first need a Dockerfile. This will contain steps for:

All of which are prerequisites to running our application on a traditional host machine. In some cases, you may just want to use 'FROM golang:1.9' or 'FROM golang:latest' which allows you to skip many of the steps in the below Containerfile. The downside is that it ties you to to that particular image, and you may already have a base image inside of which you wish to also compile and run go code. You can save the following code as 'Dockerfile' with no file extension:
FROM openshift/base-centos7

RUN yum install -y golang && \
    yum clean all

ENV GOLANG_VERSION=1.9 \
    GOPATH=/go

ENV PATH=$GOPATH/bin:/usr/local/go/bin:$PATH

COPY go-wrapper /usr/local/bin/

RUN mkdir -p /go/src/github.com/openshift/echodev
WORKDIR /go/src/github.com/openshift/echodev

COPY . /go/src/github.com/openshift/echodev
RUN go-wrapper download && go-wrapper install

EXPOSE 8080

USER 1001

CMD ["go-wrapper", "run"]
 
This way, we're not dependent on the golang image itself. With this method, you can have the added security of using only the images you build in-house. Though in this example we're just going to use the openshift/centos7-base image. Next we have our OpenShift application template, which will look very similar to Kubernetes application templates. This contains all the configuration needed to create our Service, Route, Image Stream, Build Config, and Deployment Config. This example is using YAML formatting, but JSON formatting is also supported. This can be convenient for automated deployments with Ansible, which I'll also cover in a later post. Our template contains the following sections:
---
kind: Template
apiVersion: v1
metadata:
  name: echodev
  annotations:
    description: Echo dev site deployment, written in Golang
    tags: quickstart,golang,echo
    iconClass: icon-golang
labels:
  template: echodev
objects:
- kind: Service
  apiVersion: v1
  metadata:
    name: echodev
    annotations:
      description: Exposes and load balances the application pods
  spec:
    ports:
    - name: web
      port: 8080
      targetPort: 8080
    selector:
      name: echodev
- kind: Route
  apiVersion: v1
  metadata:
    name: echodev
  spec:
    host: "${APPLICATION_DOMAIN}"
    to:
      kind: Service
      name: echodev
- kind: ImageStream
  apiVersion: v1
  metadata:
    name: echodev
    annotations:
      description: Keeps track of changes in the application image
- kind: BuildConfig
  apiVersion: v1
  metadata:
    name: echodev
    annotations:
      description: Defines how to build the application
  spec:
    source:
      type: Git
      git:
        uri: "${SOURCE_REPOSITORY_URL}"
        ref: "${SOURCE_REPOSITORY_REF}"
      contextDir: "${CONTEXT_DIR}"
    strategy:
      type: Docker
    output:
      to:
        kind: ImageStreamTag
        name: echodev:latest
    triggers:
    - type: ConfigChange
    - type: GitHub
      github:
        secret: "${GITHUB_WEBHOOK_SECRET}"
    postCommit:
      script: go test -v ./...
- kind: DeploymentConfig
  apiVersion: v1
  metadata:
    name: echodev
    annotations:
      description: Defines how to deploy the application server
  spec:
    strategy:
      type: Recreate
    triggers:
    - type: ImageChange
      imageChangeParams:
        automatic: true
        containerNames:
        - shinobu
        from:
          kind: ImageStreamTag
          name: echodev:latest
    - type: ConfigChange
    replicas: 1
    selector:
      name: echodev
    template:
      metadata:
        name: echodev
        labels:
          name: echodev
      spec:
        containers:
        - name: echodev
          image: echodev
          ports:
          - containerPort: 8080
parameters:
- name: SOURCE_REPOSITORY_URL
  description: The URL of the repository with your application source code
  value: https://github.com/xshinobu/echodev
- name: SOURCE_REPOSITORY_REF
  description: Set this to a branch name, tag or other ref of your repository if you are not using the default branch
- name: CONTEXT_DIR
  description: Set this to the relative path to your project if it is not in the root of your repository
- name: APPLICATION_DOMAIN
  description: The exposed hostname that will route to the Beego service
  value: ''
- name: GITHUB_WEBHOOK_SECRET
  description: A secret string used to configure the GitHub webhook
  generate: expression
  from: "[a-zA-Z0-9]{40}"
Next, we have a handy little script from the official docker golang repo. This handles the 'go get' and 'go install' steps automatically to make sure all of our prerequisites are installed for us (Neat!). The latest version can always be found here on github. You can save the following as 'go-wrapper', which also has no file extension:
    #!/bin/sh
    set -e

    usage() {
      base="$(basename "$0")"
      cat <&2
      exit 1
    fi
    # "shift" so that "$@" becomes the remaining arguments and can be passed along to other "go" subcommands easily
    cmd="$1"
    shift

    goDir="$(go list -e -f '\{\{.ImportComment\}\}' 2>/dev/null || true)"

    if [ -z "$goDir" -a -s .godir ]; then
      goDir="$(cat .godir)"
    fi

    dir="$(pwd -P)"
    if [ "$goDir" ]; then
      goPath="$\{GOPATH%%:*\}" # this just grabs the first path listed in GOPATH, if there are multiple (which is the detection logic "go get" itself uses, too)
      goDirPath="$goPath/src/$goDir"
      mkdir -p "$(dirname "$goDirPath")"
      if [ ! -e "$goDirPath" ]; then
        ln -sfv "$dir" "$goDirPath"
      elif [ ! -L "$goDirPath" ]; then
        echo >&2 "error: $goDirPath already exists but is unexpectedly not a symlink!"
        exit 1
      fi
      goBin="$goPath/bin/$(basename "$goDir")"
    else
      goBin="$(basename "$dir")" # likely "app"
    fi

    case "$cmd" in
      download)
        set -- go get -v -d "$@"
        if [ "$goDir" ]; then set -- "$@" "$goDir"; fi
        set -x; exec "$@"
        ;;
        
      install)
        set -- go install -v "$@"
        if [ "$goDir" ]; then set -- "$@" "$goDir"; fi
        set -x; exec "$@"
        ;;
        
      run)
        set -x; exec "$goBin" "$@"
        ;;
        
      *)
        echo >&2 'error: unknown command:' "$cmd"
        usage >&2
        exit 1
        ;;
    esac