Skip to main content
Version: 1.6

Deploying Apps with Wayfinder

This topic outlines a process of building and deploying a container image into a cluster managed by Wayfinder via a CI/CD pipeline. This process is shown in the simplest way. It is intended as a map to guide application developers through deployment, and as a workflow that can be expanded or incorporated into existing ways of working.

The example illustrated here uses GitHub Actions as the primary means of automating the build and deployment process. It assumes access to Wayfinder, and an existing Wayfinder managed cluster and namespace.

The following workflow covers the basic steps for getting code through a CI/CD pipeline and into a cluster managed by Wayfinder. There are many additional steps that are not covered for the sake of simplicity. For example, users should also take into account testing, environment management, promotion, health checks, and rollbacks to make this a robust and reliable process for their own workflow.

Prerequisites​

Create a Dockerfile to generate an image of the application​

Here is a sample Dockerfile:

FROM node:12-alpine3.10
ENV NODE_ENV production
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm install
COPY . .
RUN echo ${version} > ./VERSION
RUN npm run build

# N.B. the image must be run as a non-root user
RUN adduser -D 7654
USER 7654

CMD [ "npm", "start" ]

Create and publish the Docker image​

Here, you create a build step to create the image and push it to a container registry. In many use cases, developers will want to host their artefacts in a private registry. The example below uses GitHub’s Container Registry, but the principle remains the same.

name: Create and publish a Docker image

on:
push:
branches: ['main']

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}
note

In the example above the final image will be tagged with the git SHA (type=sha). This is to ensure that as the code changes the deployment manifest is updated too. By doing this, the deployment job can trigger Kubernetes to update the contents of the namespace automatically.

Deploy via pipeline​

Once the image is built and pushed to its registry, trigger a second job to deploy the code changes and update the Wayfinder managed cluster. Here's an example:

deploy:
needs: build-and-push-image
runs-on: ubuntu-latest
env:
WAYFINDER_SERVER: <WAYFINDER_SERVER_HERE>
WAYFINDER_TOKEN: ${{ secrets.WAYFINDER_TOKEN }}
WAYFINDER_WORKSPACE: <WORKSPACE_NAME_HERE>
CLUSTER_NAME: <CLUSTER_NAME_HERE>
NAMESPACE_NAME: <CLUSTER_NAMESPACE_HERE>
DNS_ZONE: <DNS_ZONE_HERE>
IMAGE_TAG: ${{ github.sha }}
permissions:
contents: read
packages: read
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Deploy
run: |
docker run -v ${{ github.workspace }}:/source \
-e WAYFINDER_SERVER -e WAYFINDER_TOKEN -e WAYFINDER_WORKSPACE \
-e CLUSTER_NAME -e NAMESPACE_NAME \
-e DNS_ZONE -e IMAGE_TAG \
quay.io/appvia-wayfinder/cli:<CLI_VERSION_HERE> \
/source/manifests/deploy.sh

The excerpt above outlines the action to take, but the important bit to watch is the docker run command, which uses a Wayfinder CLI image to carry out the deployment. This command consists of three key parts:

  1. Github managed secrets obtained via Wayfinder
  2. Wayfinder managed cluster and namespace
  3. Customizing the Kubernetes manifests via the deploy.sh script

Github managed secrets obtained via Wayfinder​

The docker command sets three environment variables from secrets managed by GitHub:

  • WAYFINDER_SERVER
  • WAYFINDER_TOKEN
  • WAYFINDER_WORKSPACE

You can generate these values by creating a robot in Wayfinder to manage access to the cluster within a CI/CD pipeline. Creating a robot generates these values, and you can also regenerate them once the robot is created. For example:

Regenerate robot token

For full instructions on creating a robot see:

Wayfinder managed cluster and namespace​

The CLUSTER_NAME and NAMESPACE_NAME environment variables refer to the Wayfinder cluster and namespace into which the application will be deployed. Passed to the docker command, these values are used as part of the deploy.sh script to set the kubectl context to the value of CLUSTER_NAME and apply the Kubernetes manifests to the specified namespace.

Customizing the Kubernetes manifests via the deploy.sh script​

In our example, the deploy.sh script is responsible for getting access to the cluster, passing in the template values to the Kubernetes manifests, and applying them to the cluster namespace.

#!/bin/sh -a

set -euo pipefail

wf kubeconfig --cluster ${CLUSTER_NAME} -w ${WAYFINDER_WORKSPACE}

mkdir -p /tmp/manifests
cp -fR /source/manifests/* /tmp/manifests/
cat <<EOF >/tmp/manifests/kustomization.yaml
resources:
- deployment.yaml
- ingress.yaml
images:
- name: application
newName: ghcr.io/appvia/application
newTag: ${IMAGE_TAG}
patches:
- target:
kind: Ingress
name: application-ingress
patch: |-
- op: replace
path: /spec/rules/0/host
value: main.${DNS_ZONE}
- op: replace
path: /spec/tls/0/hosts/0
value: main.${DNS_ZONE}
EOF

echo "Getting pods"
kubectl get pods -n ${NAMESPACE_NAME}

echo "Applying manifests"
kubectl -n ${NAMESPACE_NAME} apply --v=6 -k /tmp/manifests

The above script:

  • Configures the robot using the Wayfinder CLI (wf create robot, not shown). This grants a series of scoped permissions for interacting with the cluster.
  • Adds and sets the cluster as the kubectl context using the scope of the Wayfinder workspace the robot was created in.
  • Uses Kustomize to build out updated manifests using templated values. The documentation for this approach can be found here.
  • Applies the new manifests to the Kubernetes namespace.

Get a template deployment manifest​

The deploy.sh script shown above creates the manifests to apply to the cluster at runtime. This means that a reference deployment.yaml is required to manage the static values and point to those that should be replaced by Kustomize. Here's a sample deployment.yaml:

apiVersion: v1
kind: Service
metadata:
name: application
spec:
ports:
- port: 80
targetPort: 3001
protocol: TCP
selector:
name: application-deploy
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-deploy
labels:
name: application-deploy
spec:
replicas: 1
selector:
matchLabels:
name: application-deploy
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
metadata:
labels:
name: application-deploy
service: application-deploy
spec:
securityContext:
runAsUser: 1000
containers:
- name: big-new-offering
image: application
imagePullPolicy: Always
ports:
- containerPort: 3001
---
apiVersion: v1
kind: Service
metadata:
name: application
spec:
ports:
- port: 80
targetPort: 3001
protocol: TCP
selector:
name: application-deploy
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-deploy
labels:
name: application-deploy
spec:
replicas: 1
selector:
matchLabels:
name: application-deploy
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
metadata:
labels:
name: application-deploy
service: application-deploy
spec:
securityContext:
runAsUser: 1000
containers:
- name: big-new-offering
image: application
imagePullPolicy: Always
ports:
- containerPort: 3001

Follow-up​

There are many steps developers may wish to do in order to complete the deployment of their application. If the application or service should be accessible outside of the cluster, an ingress.yaml (see example below) can be added to the app’s existing manifest. For more information, see:

Example ingress.yaml​

Using the instructions linked above, Wayfinder generates an ingress.yaml for you to expose your app to be accessible from outside the cluster.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myappservice-ingress
namespace: bob
annotations:
cert-manager.io/cluster-issuer: "prod-le-dns01"
spec:
ingressClassName: external
rules:
- host: app1.myproject.com
http:
paths:
- backend:
service:
name: myappservice
port:
number: 8443
path: /
pathType: Prefix
tls:
- hosts:
- app1.myproject.com
secretName: myappservice-ingress-tls
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: myappservice-ingress
namespace: test
spec:
ingress:
- ports:
- protocol: TCP
port: 8443
from:
- namespaceSelector:
matchLabels:
name: wf-ingress
podSelector:
matchLabels:
name: "myapp"
policyTypes:
- Ingress