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:
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"]
--- 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