Getting To Know K8s | Lab #8: Continuous Deployment with Travis CI and Kubernetes

By Steven Eschinger | April 13, 2017

Travis CI - Build Summary

This post was updated on September 18th, 2017 for Kubernetes version 1.7.6 & Kops version 1.7.0

Introduction

In the previous lab, we created an example continuous deployment pipeline for a Hugo site, using a locally installed instance of Jenkins in your Kubernetes cluster.

And in this lab, we will be recreating the same continuous deployment pipeline using Travis CI. Travis CI is a hosted continuous integration service used to build and test software projects that are stored in GitHub. It is free to use for any public GitHub repositories and they have commercial offerings if you want to use it for private repositories.

The Travis CI pipeline we will create will cover the same four stages that the Jenkins pipeline did in the previous lab:

  • Build: Build the Hugo site
  • Test: Test the Hugo site to confirm there are no broken links
  • Push: Create a new Docker image with the Hugo site and push it to your Docker Hub repository
  • Deploy: Trigger a rolling update to the new Docker image in your Kubernetes cluster

The configuration of the pipeline will be defined in a .travis.yml file in your Hugo site GitHub repository, similar to the Jenkinsfile we created for the Jenkins pipeline.

And as Travis CI is tightly integrated with GitHub, the pipeline will be automatically run every time there is a commit in your GitHub repository.

Activities

Warning: Some of the AWS resources that will be created in the following lab are not eligible for the AWS Free Tier and therefore will cost you money. For example, running a three node cluster with the suggested instance size of t2.medium will cost you around $0.20 per hour based on current pricing.

Prerequisites

# Must change: Your domain name that is hosted in AWS Route 53
export DOMAIN_NAME="k8s.kumorilabs.com"

# Friendly name to use as an alias for your cluster
export CLUSTER_ALIAS="usa"

# Leave as-is: Full DNS name of you cluster
export CLUSTER_FULL_NAME="${CLUSTER_ALIAS}.${DOMAIN_NAME}"

# AWS availability zone where the cluster will be created
export CLUSTER_AWS_AZ="us-east-1a"

# Leave as-is: AWS Route 53 hosted zone ID for your domain
export DOMAIN_NAME_ZONE_ID=$(aws route53 list-hosted-zones \
       | jq -r '.HostedZones[] | select(.Name=="'${DOMAIN_NAME}'.") | .Id' \
       | sed 's/\/hostedzone\///')

Implementation

Deploy a new cluster

Create the S3 bucket in AWS, which will be used by Kops for cluster configuration storage:

aws s3api create-bucket --bucket ${CLUSTER_FULL_NAME}-state

Set the KOPS_STATE_STORE variable to the URL of the S3 bucket that was just created:

export KOPS_STATE_STORE="s3://${CLUSTER_FULL_NAME}-state"

Create the cluster with Kops:

kops create cluster \
     --name=${CLUSTER_FULL_NAME} \
     --zones=${CLUSTER_AWS_AZ} \
     --master-size="t2.medium" \
     --node-size="t2.medium" \
     --node-count="2" \
     --dns-zone=${DOMAIN_NAME} \
     --ssh-public-key="~/.ssh/id_rsa.pub" \
     --kubernetes-version="1.7.6" --yes

It will take approximately 5 minutes for the cluster to be ready. To check if the cluster is ready:

kubectl get nodes
NAME                            STATUS    AGE       VERSION
ip-172-20-48-9.ec2.internal     Ready     4m        v1.7.6
ip-172-20-55-48.ec2.internal    Ready     2m        v1.7.6
ip-172-20-58-241.ec2.internal   Ready     3m        v1.7.6

Create the Hugo site

In this lab, we will create a Hugo site with the same theme (Material Docs by Digitalcraftsman) that we used in previous labs.

From the root of the repository, execute the following, which will:

  • Create a new blank Hugo site in the hugo-app-travis/ folder
  • Clone the Material Docs Hugo theme to the themes folder (hugo-app-travis/themes)
  • Copy the example content from the Material Docs theme to the root of the Hugo site
  • Remove the Git folder from the Material Docs theme
  • Change the value of the baseurl in the Hugo config file to ensure the site will work with any domain
hugo new site hugo-app-travis/

git clone https://github.com/digitalcraftsman/hugo-material-docs.git \
    hugo-app-travis/themes/hugo-material-docs

cp -rf hugo-app-travis/themes/hugo-material-docs/exampleSite/* hugo-app-travis/
rm -rf hugo-app-travis/themes/hugo-material-docs/.git/
sed -i -e 's|baseurl =.*|baseurl = "/"|g' hugo-app-travis/config.toml

Create the GitHub repository for the Hugo site

Configure your global Git settings with your username, email and set the password cache to 60 minutes:

# Set your GitHub username and email
export GITHUB_USERNAME="smesch"
export GITHUB_EMAIL="steven@kumorilabs.com"

git config --global user.name "${GITHUB_USERNAME}"
git config --global user.email "${GITHUB_EMAIL}"
git config --global credential.helper cache
git config --global credential.helper 'cache --timeout=3600'

Create a new GitHub repository called hugo-app-travis in your GitHub account (you will be prompted for your GitHub password):

curl -u "${GITHUB_USERNAME}" https://api.github.com/user/repos \
     -d '{"name":"hugo-app-travis"}'

Create the README.md file and then upload the contents of the Hugo site (hugo-app-travis/) to the GitHub repository you just created (you will be prompted for your GitHub credentials):

echo "# hugo-app-travis" > hugo-app-travis/README.md
git -C hugo-app-travis/ init
git -C hugo-app-travis/ add .
git -C hugo-app-travis/ commit -m "Create Hugo site repository"
git -C hugo-app-travis/ remote add origin \
    https://github.com/${GITHUB_USERNAME}/hugo-app-travis.git
git -C hugo-app-travis/ push -u origin master

You now have your own repository populated with the Hugo site content.

Create the .travis.yml file

Now let’s have a look at the .travis.yml file that we will add to your repository:

sudo: required

services:
  - docker

os:
- linux

env:
  global:
    - DOCKER_IMAGE_NAME="hugo-app-travis"
    - K8S_DEPLOYMENT_NAME="hugo-app"

before_script:
- docker pull smesch/hugo
- docker pull smesch/html-proofer
- docker pull smesch/kubectl
- docker login -u "${DOCKER_USERNAME}" -p "${DOCKER_PASSWORD}"

script:
- docker run -v ${TRAVIS_BUILD_DIR}:/hugo_root smesch/hugo hugo -s /hugo_root
- docker run -v ${TRAVIS_BUILD_DIR}/public:/public smesch/html-proofer htmlproofer /public --external_only --only-4xx

after_script:
- docker build -t ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:${TRAVIS_BUILD_ID} .
- docker push ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:${TRAVIS_BUILD_ID}
- docker tag ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:${TRAVIS_BUILD_ID} ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:latest
- docker push ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:latest
- sed -i -e 's|KUBE_CA_CERT|'"${KUBE_CA_CERT}"'|g' kubeconfig
- sed -i -e 's|KUBE_ENDPOINT|'"${KUBE_ENDPOINT}"'|g' kubeconfig
- sed -i -e 's|KUBE_ADMIN_CERT|'"${KUBE_ADMIN_CERT}"'|g' kubeconfig
- sed -i -e 's|KUBE_ADMIN_KEY|'"${KUBE_ADMIN_KEY}"'|g' kubeconfig
- sed -i -e 's|KUBE_USERNAME|'"${KUBE_USERNAME}"'|g' kubeconfig
- docker run -v ${TRAVIS_BUILD_DIR}:/kube smesch/kubectl kubectl --kubeconfig /kube/kubeconfig set image deployment/${K8S_DEPLOYMENT_NAME} ${K8S_DEPLOYMENT_NAME}=${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:$TRAVIS_BUILD_ID

Breakdown of the .travis.yml file

  • Define that the Docker service will be used
  • Set env variable for the name that will be used when creating new Docker images (hugo-app-travis)
  • Set env variable for the name of the Kubernetes Deployment in your cluster (hugo-app)
  • Pull the Docker images for hugo, html-proofer & kubectl
  • Login to the Docker Hub
  • Build the Hugo site from your repository and output the static HTML to the default folder public
  • Run html-proofer against the public folder to test external links
  • Build a new Docker image with the new version of the site, using the Dockerfile in the repository
  • Push the new Docker image to your Docker Hub account using both the build number and latest as the tags
  • Populate the kubeconfig template file with the value of the env variables we will configure for cluster authentication
  • Start a rolling update for the Kubernetes Deployment, using the new Docker image with the build number tag

Copy the .travis.yml file to your GitHub repo folder:

cp github/hugo-app-travis/.travis.yml hugo-app-travis/.travis.yml

Create the Dockerfile

Below is the Dockerfile that will be used by Travis CI for the Docker image build:

FROM nginx:alpine
MAINTAINER Steven Eschinger <steven@kumorilabs.com>
COPY public /usr/share/nginx/html

Breakdown of the Dockerfile

  • Use the nginx:apline image as a base
  • Copy the static HTML content of the Hugo site from the public folder to the default NGINX folder

Copy the Dockerfile to your GitHub repo folder:

cp github/hugo-app-travis/Dockerfile hugo-app-travis/Dockerfile

Update MAINTAINER with your name and email:

vi hugo-app-travis/Dockerfile
...
MAINTAINER Steven Eschinger <steven@kumorilabs.com>
...

Create the kubeconfig template file

And below is the kubeconfig template file that will be used by Travis CI to authenticate with your cluster:

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: KUBE_CA_CERT
    server: KUBE_ENDPOINT
  name: k8s-cluster
contexts:
- context:
    cluster: k8s-cluster
    user: k8s-cluster
  name: k8s-cluster
current-context: k8s-cluster
kind: Config
preferences: {}
users:
- name: k8s-cluster
  user:
    client-certificate-data: KUBE_ADMIN_CERT
    client-key-data: KUBE_ADMIN_KEY
    username: KUBE_USERNAME

There are five placeholders in the kubeconfig template file, which will be replaced by the values we will configure for environment variables in the Travis CI job configuration:

Kubeconfig Placeholders

  • certificate-authority-data: KUBE_CA_CERT
  • server: KUBE_ENDPOINT
  • client-certificate-data: KUBE_ADMIN_CERT
  • client-key-data: KUBE_ADMIN_KEY
  • username: KUBE_USERNAME

Copy the kubeconfig to your GitHub repo folder:

cp github/hugo-app-travis/kubeconfig hugo-app-travis/kubeconfig

And finally, commit the three files to GitHub:

git -C hugo-app-travis/ add .
git -C hugo-app-travis/ commit -m "Upload .travis.yml, Dockerfile & kubeconfig"
git -C hugo-app-travis/ push -u origin master

Deploy the Hugo site

Create the initial Deployment of the Hugo site:

kubectl create -f ./kubernetes/hugo-app/
deployment "hugo-app" created
service "hugo-app-svc" created

Wait about a minute for the Service to create the AWS ELB and then create the DNS CNAME record in your Route 53 domain with a prefix of hugo (e.g., hugo.k8s.kumorilabs.com), using the dns-record-single.json template file in the repository:

# Set the DNS record prefix & the Service name and then retrieve the ELB URL
export DNS_RECORD_PREFIX="hugo"
export SERVICE_NAME="hugo-app-svc"
export HUGO_APP_ELB=$(kubectl get svc/${SERVICE_NAME} \
       --template="{{range .status.loadBalancer.ingress}} {{.hostname}} {{end}}")

# Add to JSON file
sed -i -e 's|"Name": ".*|"Name": "'"${DNS_RECORD_PREFIX}.${DOMAIN_NAME}"'",|g' \
    scripts/apps/dns-records/dns-record-single.json
sed -i -e 's|"Value": ".*|"Value": "'"${HUGO_APP_ELB}"'"|g' \
    scripts/apps/dns-records/dns-record-single.json

# Create DNS records
aws route53 change-resource-record-sets \
    --hosted-zone-id ${DOMAIN_NAME_ZONE_ID} \
    --change-batch file://scripts/apps/dns-records/dns-record-single.json

The Hugo site should now be reachable at the DNS name you just created (e.g., hugo.k8s.kumorilabs.com) and as we used the default 1.0 Docker image tag, the theme will be red:

Hugo App - Initial Deployment

Configure the Travis CI pipeline

Let’s now configure the pipeline in Travis CI.

Browse to the Travis CI site:

  • Login with your GitHub credentials
  • Hover over your name in the navigation bar and click Accounts
  • Click the button next to your hugo-app-travis repository to enable it
  • Click on the settings gear icon next to your hugo-app-travis repository
Travis CI - Enable Repository

Configure the required environment variables

Before we populate the required environment variables for Docker Hub and your Kubernetes cluster, we need to retrieve some information from the kubeconfig file for your cluster. We will use kubectl to extract the required values from your kubeconfig and set them to local environment variables on your host:

export KUBE_CA_CERT=$(kubectl config view --flatten --output=json \
       | jq --raw-output '.clusters[0] .cluster ["certificate-authority-data"]')
export KUBE_ENDPOINT=$(kubectl config view --flatten --output=json \
       | jq --raw-output '.clusters[0] .cluster ["server"]')
export KUBE_ADMIN_CERT=$(kubectl config view --flatten --output=json \
       | jq --raw-output '.users[0] .user ["client-certificate-data"]')
export KUBE_ADMIN_KEY=$(kubectl config view --flatten --output=json \
       | jq --raw-output '.users[0] .user ["client-key-data"]')
export KUBE_USERNAME=$(kubectl config view --flatten --output=json \
       | jq --raw-output '.users[0] .user ["username"]')

You can now echo out each of these local variables to retrieve the values and then copy/paste them into the Environment Variables section on the Travis CI Settings page:

echo "KUBE_CA_CERT = $KUBE_CA_CERT"
echo "KUBE_ENDPOINT = $KUBE_ENDPOINT"
echo "KUBE_ADMIN_CERT = $KUBE_ADMIN_CERT"
echo "KUBE_ADMIN_KEY = $KUBE_ADMIN_KEY"
echo "KUBE_USERNAME = $KUBE_USERNAME"

In addition to the five environment variables for your Kubernetes cluster, you also need to add your Docker Hub username (DOCKER_USERNAME) and password (DOCKER_PASSWORD):

Travis CI - Configure ENV Vars

Test the Travis CI pipeline

We will now change the theme color to blue in the Hugo config file and then commit the change to your GitHub repository (you will be prompted for your GitHub credentials):

export HUGO_APP_TAG="blue"
sed -i -e 's|primary = .*|primary = "'"${HUGO_APP_TAG}"'"|g' \
    hugo-app-travis/config.toml
git -C hugo-app-travis/ pull
git -C hugo-app-travis/ commit -a -m "Set theme color to ${HUGO_APP_TAG}"
git -C hugo-app-travis/ push -u origin master

Within a few seconds of committing the change to GitHub, the Travis CI pipeline should be started automatically. You can see the pipeline running in real-time by clicking on the Current tab of the repository on the Travis CI site:

Travis CI - Build Status #1
Travis CI - Build Status #2

And when it completes, the theme color of the site will be blue:

Hugo App - Blue

You now have a fully functional continuous deployment pipeline setup in Travis CI.

Cleanup

Before proceeding to the next lab, delete the cluster and it’s associated S3 bucket:

Delete the cluster

Delete the cluster:

kops delete cluster ${CLUSTER_FULL_NAME} --yes

Delete the S3 bucket in AWS:

aws s3api delete-bucket --bucket ${CLUSTER_FULL_NAME}-state

In addition to the step-by-step instructions provided for each lab, the repository also contains scripts to automate some of the activities being performed in this blog series. See the Using Scripts guide for more details.

Next Up

In the next lab, Lab #9: Continuous Deployment with Wercker and Kubernetes, we will go through the following:

  • Creating a continuous deployment pipeline in Wercker for the Hugo site
  • Testing the pipeline in Wercker

Other Labs in the Series


Are you interested in topics like this? Drop your email below to receive regular updates.
comments powered by Disqus