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