Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

helm, draft, multi-stage docker, and more #2

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!app/
node_modules/
4 changes: 4 additions & 0 deletions .draftignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.swp
*.tmp
*.temp
.git*
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.idea/
**/node_modules
**/*.log
3 changes: 3 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
draft 0.12.0
helm 2.8.2
kubectl 1.10.0
40 changes: 35 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
FROM node:9-alpine
WORKDIR /src
FROM node:9-alpine AS base
ARG NAME
ENV NAME=${NAME:-k8s}
ARG PORT
ENV PORT=${PORT:-3000}

RUN npm set progress=false && npm config set depth 0
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app


FROM base AS dependencies
COPY app/package*.json ./
RUN JOBS=MAX npm install --production \
&& cp -R node_modules prod_node_modules \
&& npm install \
&& npm cache --force clean && rm -rf /tmp/*

FROM dependencies AS test
RUN apk --no-cache add curl
ENV DEBUG express:*
ENV NAME="k8s-test"
ENV ASSERTION "Hello k8s-test!"
COPY app/ .
RUN npm install --quiet && npm test
EXPOSE 3000
CMD npm start
RUN npm test
RUN npm start & sleep 1s \
&& [[ "$(curl -s http://localhost:$PORT)" == "$ASSERTION" ]] \
&& echo "Server is smoked" || exit 1;

FROM base as release
COPY --from=dependencies /usr/src/app/prod_node_modules ./node_modules
COPY app/ .
ENV NODE_ENV=production
EXPOSE ${PORT}/tcp
USER node
CMD ["node", "app.js"]
50 changes: 50 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
SHELL := /bin/bash
RELEASE_NAME := swk8s-$(shell git rev-parse --short --verify HEAD)

ifeq "$(shell which draft)" ""
include Makefile-helm.mk
# Helm allows overriding values.yaml values via CLI, but Draft does not
USER_NAME := $(shell whoami)
else
include Makefile-draft.mk
USER_NAME := tom
endif

init: deps config

all: init test deploy connect

deps:
ifeq "$(shell which asdf)" ""
@echo "asdf is not installed. Please manually verify the envrionment has:"
@echo $(shell cat .tool-versions)
@echo "draft is optional, but fun"
else
@asdf install
endif

ifeq "$(shell which minikube)" ""
@echo "minikube is not install.
@exit 1
endif

ifeq "$(shell minikube ip)" ""
@minikube start
endif

test:
@cd app && npm i && npm test
@helm lint charts/solarwinds-k8s-challange/

accept:
$(eval $@_RESULTS := $(shell curl -s http://localhost:3000))
$(eval $@_CASE := $(shell echo "Hello $${$(USER_NAME)~}!"))
ifeq "$($@_RESULTS)" "$($@_CASE)"
@echo "accept passed"
else
@echo "accept failed"
@exit 1;
endif


.PHONY: deps test accept all init
20 changes: 20 additions & 0 deletions Makefile-draft.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
config:
@eval $(minikube docker-env)
@helm init --upgrade
@draft init
@draft config set disable-push-warning 1

up:
@draft up

build: up

deploy: up

connect:
@draft connect --override-port "3000:3000"

clean:
@draft delete

.PHONY: info config up build deploy connect clean
24 changes: 24 additions & 0 deletions Makefile-helm.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
config:
@eval $(minikube docker-env)
@helm init --upgrade

build:
@docker build -t solarwinds-k8s-challange:dev .

install-helm:
@helm install --replace ./charts/solarwinds-k8s-challange \
--name $(RELEASE_NAME) \
--set user.name=$(USER_NAME) \
--set image.repository=solarwinds-k8s-challange \
--set image.tag=dev

deploy: build install-helm

connect:
$(eval $@_POD_NAME := $(shell kubectl get pods --namespace default -l "app=solarwinds-k8s-challange,release=$(RELEASE_NAME)" -o jsonpath="{.items[0].metadata.name}"))
kubectl port-forward $($@_POD_NAME) 3000:3000

clean:
@helm delete $(RELEASE_NAME)


52 changes: 45 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,64 @@

Get this application running in [Minikube](https://github.com/kubernetes/minikube), a local Kubernetes Cluster.

Note: This challenge requires **no** external resources other than GitHub to clone and fork.
Note: This challenge requires **no** external resources other than GitHub to clone and fork.
You can use Minikube's docker engine from your host machine to make the image available in the cluster.

## How Can I Take The Challenge ?
## How Can I Take The Challenge

* Fork the repo
* Write the k8s specs for a deployment, service, ingress and configmap and anything else you believe is required
* Provide a script to run the deployment
* Make sure to update this [README](README.md) with instructions on how to build, deploy and test
* Submit a PR when ready
* Submit a PR when ready

## Success Factors

* Application can be deployed by invoking a single command in a vanilla Kubernetes - Minikube cluster
* All pods must be running
* The application must be accessible by curling the URL the application is served at

## Bonus Points
## Solution

* Use [HELM](https://helm.sh)
* Create a `Makefile` for defining the build, deploy and test workflows
* Get the application to return your name instead of "Hello unknown"
Solution uses Helm Chart and is my first experience with Draft. I also did not
want a hard dependency on Draft. Consequently, the makefile is bimodal. If the
user has [asdf](https://github.com/asdf-vm/asdf) installed, make deps will
install Draft. Without asdf, Helm is assumed and used directly.

`make all` and let me know how it goes.

`make accept` from a different shell will make a simple assertion using curl.

`make clean` will remove the app from minikube

Includes:

* Updated app with Signal Event handling and k8s ready liveness and readiness
endpoints.

* Multi stage docker build with embedded smoke test.

* A dev Helm chart, complete with a configmap to set the hello name

* The service uses ClusterIP rather than ingress or NodePort to ensure
appropriate access to the application in development. Proxy is used for
reachability.

* Minikube's Docker engine is used to build the image to eliminate the push step
and decrease the cycle time, especially when using draft's watch feature.

* Attemps to use [asdf](https://github.com/asdf-vm/asdf) to ensure matching
draft, helm, and kubectl binaries but it is not required.

* makefile for standard lifecycle steps

Caveats:

* Draft does not support overriding values in values.yml as Helm does. Rather
than template the file with sed replacement, Draft workflow uses the coded name
'tom' - to change this, both the values.yaml and the Makefile must be edited
(or update the configmap and restart the pod).

* Helm path sets the hello name of the app to the user invoking the make rules.

* The chart path, namespace, and proxy ports are hard coded in the Makefile.
41 changes: 34 additions & 7 deletions app/app.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
const express = require('express')
const app = express()
const port = 3000;
const http = require('http');
const express = require('express');
const terminus = require('@godaddy/terminus');

const name = process.env.NAME || 'unknown';
const port = process.env.PORT || 3000;
const isHealthy = true;

const app = express();

app.listen(port);
console.log(`Server running at http://localhost: ${port}`);
app.get('/', (req, res) => {
const name = process.env.NAME || 'unknown';
res.send(`Hello ${name}!`);
});
});

const server = http.createServer(app);

function onSignal() {
console.log('Server is cleaning up');
}

async function onHealthCheck() {
return isHealthy ? Promise.resolve() : Promise.reject();
}

terminus(server, {
signal: 'SIGINT',
healthChecks: {
'/healthcheck': onHealthCheck,
},
onSignal
});

server.listen(port);

if (process.env.NODE_ENV != "production") {
console.log(`Server is running at http://localhost:${port}`);
}
Loading