Photo by Matt Bowden on Unsplash
Photo by Matt Bowden on Unsplash

How To Deploy Your Apps to Kubernetes With Hashicorp Waypoint

Find out HashiCorp Waypoint to create a modern workflow to build an application from sources and deploy it to Kubernetes in a single file

Guillaume Vincent
Guillaume Vincent

Table of Contents

Waypoint is a product created by HashiCorp. It is a tool that provides a modern workflow as code. It supports build, deployment, and release on different platforms and cloud providers:

"Waypoint allows developers to deploy, manage, and observe their applications through a consistent abstraction of underlying infrastructure. Waypoint works with Kubernetes, ECS and many other platforms." https://www.waypointproject.io/

In this article, we will create an application in Golang and use Waypoint to build and deploy it on Kubernetes in a single file. Waypoint will automatically detect the language used and compile it using Cloud Native Buildpacks. So no makefile is needed!

The image will be pushed to a registry using Harbor in a secure way. Then, Waypoint will reuse this image to deploy and release it into the Kubernetes cluster.

Getting Started With Waypoint

Waypoint is a CLI tool supporting cross-platform environments. You can download it here. Once installed in your environment, here are the options of the tool:

$ waypoint
Welcome to Waypoint
Docs: https://waypointproject.io
Version: v0.1.5
Usage: waypoint [-version] [-help] [-autocomplete-(un)install] <command> [args]
Common commands
  build        Build a new versioned artifact from source
  deploy       Deploy a pushed artifact
  release      Release a deployment
  up           Perform the build, deploy, and release steps for the app
Other commands
  artifact        Artifact and build management
  config          Application configuration management
  context         Server access configurations
  deployment      Deployment creation and management
  destroy         Delete all the resources created for an app
  docs            Show documentation for components
  exec            Execute a command in the context of a running application instance
  hostname        Application URLs
  init            Initialize and validate a project
  install         Install the Waypoint server to Kubernetes, Nomad, or Docker
  logs            Show log output from the current application deployment
  runner          Runner management
  server          Server management
  token           Authenticate and invite collaborators
  ui              Open the web UI
  version         Prints the version of this Waypoint CLI

Waypoint uses a configuration file containing the workflow to be executed. It can be described using HCL (HashiCorp Language) or JSON. If you have already used Terraform, the learning curve should be reduced for you.

A workflow contains stages each described in a stanza. Β Here is an example of a build stanza, itself included in a stanza app:

app "foo" {   
  build {    
    use "docker" {}
registry {       
      # ...     
    }
hook {       
      # ...     
    }
  }    
}

We'll use stanzas to cover the following workflow stages :

  • A build stanza to create an artifact from the application source code. There are a lot of supported artifact formats such as container image, VM image, etc… We’ll build a docker image and store it into a remote registry hosted by Harbor.
  • A deploy stanza to stage the created artifact onto the Kubernetes cluster. The application is deployed in deployment but it doesn’t receive traffic yet.
  • A release stanza to enable the deployment to expose the application. The goal is to make ready the application to receive traffic.

Create The Golang Application

We'll create a small REST API to test Waypoint. Here is the code:

package main

import (
    "fmt"
    "log"
    "net/http"
    "encoding/json"
)

type Server struct{}
type Todo struct {
    UserId      int
    Id          int
    Title       string
    Completed   bool
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(&Todo{UserId: 1, Id: 1, Title: "delectus aut autem", Completed: false})
}

func main() {
    port := 8080
    fmt.Printf("Starting HTTP server on port %d", port)
    s := &Server{}
    http.Handle("/", s)
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", 8080), nil))
}

go.mod is present for Cloud-Native Buildpacks. It will identify the language and build the application without any makefile:

module github.com/guivin/waypoint-go-example

go 1.15

The application source is now ready for delivery:

$ go run main.go
Starting HTTP server on port 8080
$ curl -s http://localhost:8080 | jq
{
  "UserId": 1,
  "Id": 1,
  "Title": "delectus aut autem",
  "Completed": false
}

Configure The Harbor Registry

Harbor is an open-source project graduated by the CNCF. It provides a secure way to persist Docker images. Waypoint will authenticate to pull and push the artifact with a robot account.

Harbor is available via a Helm chart and has many components whose a web portal. It requires NGINX ingress controller deployed to be accessible :

$ helm install ingress-nginx --namespace staging ingress-nginx/ingress-nginx

After the Helm repository is added we can proceed to the Harbor deployment :

$ helm repo add harbor https://helm.goharbor.io
$ helm install harbor --namespace staging harbor/harbor

Once all the Harbor components are ready you can access the portal at https://core.harbor.domain. The username is admin and the password is here.

The Harbor web portal
The Harbor web portal

We create a new project for the application :

New project "foo" in Harbor
New project "foo" in Harbor

We create a robot account in the new fresh project. Waypoint will use these credentials later :

Creation of a robot account for Waypoint in the foo project
Creation of a robot account for Waypoint in the foo project

Configure The Waypoint Workflow

The common workflow steps are in waypoint.hcl located at the project root directory :

project = "my-awesome-golang-app"

app "my-awesome-golang-app" {
    labels = {
        "service" = "my-awesome-golang-app"
        "env" = "staging"
    }

    build {
        use "pack" {}
        registry {
            use "docker" {
                image        = "core.harbor.domain/foo/my-awesome-golang-app"
                tag          = "latest"
                local        = false
                encoded_auth = filebase64("dockerAuth.json")
            }
        }
    }

    deploy {
        use "kubernetes" {
            namespace       = "staging"
            probe_path      = "/"
            replicas        = 1
            service_port    = 8080
        }
    }

    release {
        use "kubernetes" {}
    }
}

The robot account credentials are in dockerAuth.json. This file is in the .gitignore file for security reasons:

{
    "username": "robot$waypoint",
    "password": "xxxxxxxxxxxxxxx"
}

The Waypoint project needs initialization first:

$ waypoint init
βœ“ Configuration file appears valid
βœ“ Connection to Waypoint server was successful
βœ“ Project "my-awesome-golang-app" and all apps are registered with the server.
βœ“ Plugins loaded and configured successfully
βœ“ Authentication requirements appear satisfied.
Project initialized!
You may now call 'waypoint up' to deploy your project or
commands such as 'waypoint build' to perform steps individually.

All the workflow stages are playable using the up argument:

$ waypoint up
Β» Building...
Creating new buildpack-based image using builder: heroku/buildpacks:18
βœ“ Creating pack client
βœ“ Building image
 β”‚ [exporter] Reusing layer 'heroku/go:profile'
 β”‚ [exporter] Adding 1/1 app layer(s)
 β”‚ [exporter] Reusing layer 'launcher'
 β”‚ [exporter] Reusing layer 'config'
 β”‚ [exporter] Adding label 'io.buildpacks.lifecycle.metadata'
 β”‚ [exporter] Adding label 'io.buildpacks.build.metadata'
 β”‚ [exporter] Adding label 'io.buildpacks.project.metadata'
 β”‚ [exporter] *** Images (85ab3ada53c6):
 β”‚ [exporter]       index.docker.io/library/my-awesome-golang-app:latest
 β”‚ [exporter] Adding cache layer 'heroku/go:shim'
βœ“ Injecting entrypoint binary to image
Generated new Docker image: my-awesome-golang-app:latest
βœ“ Tagging Docker image: my-awesome-golang-app:latest => core.harbor.domain/foo/my-awesome-golang-app:latest
βœ“ Pushing Docker image...
 β”‚ 2860c3734ddc: Layer already exists
 β”‚ 459acae8a534: Layer already exists
 β”‚ 761cdf87222e: Layer already exists
 β”‚ 9aa3a9efcd12: Layer already exists
 β”‚ c79bdd8f7423: Layer already exists
 β”‚ fe6d8881187d: Layer already exists
 β”‚ 23135df75b44: Layer already exists
 β”‚ b43408d5f11b: Layer already exists
 β”‚ latest: digest: sha256:e4b943e20f69b7e5f4a839a5b1360e84febb58dd4c20aac2cf642ab09
 β”‚ 50f062e size: 2825
βœ“ Docker image pushed: core.harbor.domain/foo/my-awesome-golang-app:latest
Β» Deploying...
βœ“ Kubernetes client connected to https://kubernetes.docker.internal:6443 with namespace staging
βœ“ Creating deployment...
βœ“ Deployment successfully rolled out!
Β» Releasing...
βœ“ Kubernetes client connected to https://kubernetes.docker.internal:6443 with namespace staging
βœ“ Creating service...
βœ“ Service is ready!
Β» Pruning old deployments...
  Deployment: 01ERWQ5BQKQSC1MGHYJDZVERT2
βœ“ Kubernetes client connected to https://kubernetes.docker.internal:6443 with namespace staging
βœ“ Deleting deployment...
The deploy was successful! A Waypoint deployment URL is shown below. This
can be used internally to check your deployment and is not meant for external
traffic. You can manage this hostname using "waypoint hostname."
Release URL: http://10.111.121.39:80
Deployment URL: https://wildly-hardy-mink--v9.waypoint.run
$ waypoint init
βœ“ Configuration file appears valid
βœ“ Connection to Waypoint server was successful
βœ“ Project "my-awesome-golang-app" and all apps are registered with the server.
βœ“ Plugins loaded and configured successfully
βœ“ Authentication requirements appear satisfied.
Project initialized!
You may now call 'waypoint up' to deploy your project or
commands such as 'waypoint build' to perform steps individually.

The application is now running on the local Kubernetes cluster. Configure port-forwarding to access the service:

$ kubectl port-forward service/my-awesome-golang-app --namespace staging 8888:80
Forwarding from 127.0.0.1:8888 -> 8080
Forwarding from [::1]:8888 -> 8080

When requesting the application all is working like a charm:

curl -s http://localhost:8888/ | jq
{
  "UserId": 1,
  "Id": 1,
  "Title": "delectus aut autem",
  "Completed": false
}

Conclusion

Waypoint is a turnkey tool to simplify workflow creation. Β It helps to promote the adoption of CI/CD for cross-teaming. Cloud-Native Buildpacks integration allows you to detect the application language and build it without the need for recipes. Β It really brings simplicity and eases to iterate.

The Waypoint configuration is embeddable in a git repository. This makes Waypoint a good candidate for CI/CD and GitOps purposes. It is promising and reserves good surprises in the future.

Resources

Waypoint by HashiCorp
Install and run Waypoint with this five minute quick start tutorial.
Harbor
Our mission is to be the trusted cloud native repository for Kubernetes
Cloud Native Buildpacks
Cloud Native Buildpacks transform your application source code into images that can run on any cloud.
CI/CDCloud-Native

Guillaume Vincent Twitter

DevOps Engineer & AWS Certified Solution Architect. Cloud enthusiast and automation addict