Skip to content

Instantly share code, notes, and snippets.

@loncarales
Last active July 7, 2020 11:53
Show Gist options
  • Save loncarales/905438be8dd4ed6ffc493e6249917127 to your computer and use it in GitHub Desktop.
Save loncarales/905438be8dd4ed6ffc493e6249917127 to your computer and use it in GitHub Desktop.
The Challenge: Create a minimalistic environment on Kuberntes to run a small Web application

Mojo Blog

Mojo Blog is simple example with DBD::Pg that makes PostgreSQL a lot of fun to use with the Mojolicious real-time web framework.

Local installation

Perl

You can use system Perl version for development. I recommend the use of perlbrew or plenv environment.

Prerequisites

Cpanminus and Carton are the only prerequisite for running the application. All required modules/dependencies are then installed via Carton from cpanfile.

Configuration

For Perl-ish configuration Mojolicious plugin is used - Mojolicious::Plugin::Config.

Building the Docker container image

Building the latest image

$ docker build -t <NAME:TAG> .

Running tests inside container image

$ docker run --rm -v $PWD/t:/opt/app/t <IMAGE> carton exec prove -lr -j4

Working with Helm

Install tiller in the Minikube cluster

$ helm init

We can do a dry-run of a helm install and enable debug to inspect the generated definitions:

$ helm install --dry-run --debug --namespace=default ./helm

Install

$ helm install --name mojo-blog --namespace=default ./helm

Check the status of the release

$ helm ls --all mojo-blog

Delete the release

$ helm del --purge mojo-blog

Do the linting

$ helm lint ./helm

Render chart templates locally and store them as Kubernetes YAML

$ helm template --name mojo-blog --namespace=default ./helm  > mojo-blog.yaml

Use Port Forwarding to Access Applications in a Cluster

Forward a local port to a port on the pod / deployment / service

$ kubectl port-forward <POD> 8080:8080
# or
$ kubectl port-forward dc/<DEPLOYMENT> 8080:8080
# or
$  kubectl port-forward svc/<SERVICE> 8080:8080

Analysis

Handling sensitive data

Currently sensitive information, such as passwords, server IP addresses, ... are hard-coded and stored in plain text. If sensitive data must be saved, it must be encrypted first. The best option would be to use a tool for securely managing secrets and encrypting, for example, HashiCorp Vault.

Automation

Artefact building and method of deployment are done manually as described in README.md file. The whole process (configuration, software provisioning and application deployment) can be fully automated. To solve the problem of environment drifting in release pipeline Infrastructure as Code (IaC) approach should be used. Continuous configuration automation (CCA) tools can be thought of as an extension of traditional IaC frameworks. Notable CCA tools: Ansible, SaltStack, Terraform.

Database

For simplicity, PostgreSQL database service is currently deployed without persistent storage (Ephemeral) and, therefore, is not production ready. Any data stored will be lost upon pod destruction. Data persistence on Kubernetes is achieved with Persistent Volumes which provide a plugin model for storage in Kubernetes where how storage is provided is completed abstracted from how it is consumed. For production, PostgreSQL replicated database service with persistent storage (highly available with automated failover and backup) should be used.

Pipeline

Current Gitlab CI/CD and Jenkins pipeline streamline the very simplified process of build, tag and release for only one environment - TEST. The production TEST stages should include

  • Test and Analysis (running unit tests and code quality analysis for using SonarQube or equivalent)
  • Deploy (As soon as the latest Docker image is built and pushed to Docker registry a new deployment based on this most recent image is rolled out on the Test environment. This stage will wait until the deployment is fully rolled out and ready.)
  • (Optional) Smoke Tests and Module Integration Test (For some implementations it might be useful to implement smoke test and module integration tests. These tests can be executed after the successful rollout of a new deployment.)
  • Tag Docker Image (After the successful rollout (and optional smoke and module integration tests), the image is tagged with the current build version.)
  • Create GIT Release Tag (Finally, the current GIT commit is tagged with the current image build version.)

The next steps can include transporting immutable images across the various stages (INT, PROD) or even across the different clusters.

Software Versioning

Versioning is essential in application development. It must be possible to relate every deployment to one unique SCM commit to:

  • Verify that the correct version with all its desired features is deployed
  • Reproduce errors or problems by analysing the code of the exact SCM commit
  • Rolling out one specific (stable) version of the application to other environments

To keep it clear and straightforward semantic versioning is used which is also used for versioning the Libraries in software development. For Continuous microservices release cycles, on the other hand, rely heavily on these time-based parameters due to their much shorter lifetime and could almost renounce semantic versioning entirely. Though it is recommended to keep a semantic version for microservices to indicate major and minor releases and track microservice development from a top view.

Example: 20170717.111749-master-1.2.3

Additional tagging should also be applied after successful rollouts to INT / PROD in GIT.

Hypnotoad Prefork Web Server

Mojo::Server::Hypnotoad is a built-in prefork web server in production environment. hypnotoad is command line interface to run Mojolicious application. Server start on port 8080 by default which is also port exposed in Docker image. In production generally reverse proxy server (Nginx, Apache) is used to access hypnotoad server.

version: '2'
services:
blog:
image: celavi/mojo-blog:latest
restart: always
ports:
- '8080'
volumes:
- ./etc/blog.conf:/opt/app/etc/blog.conf
depends_on:
- db
db:
image: postgres:9.6.11
restart: always
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: s3cret
FROM debian:stretch-slim
LABEL version="base" maintainer="[email protected]"
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
libpq-dev \
dumb-init \
perl \
cpanminus \
build-essential \
procps \
&& cpanm Carton \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY . /opt/app
WORKDIR /opt/app
RUN carton install --deployment
RUN rm -rf /root/.cpanm \
&& apt-get remove --purge -y build-essential \
&& apt-get autoremove -y \
&& rm -rf /tmp/* \
&& rm -rf /var/tmp/*
RUN groupadd -r app \
&& useradd -r -g app app \
&& chown app:app -R /opt/app
USER app
# Expose the default web server port
EXPOSE 8080
# Runs "/usr/bin/dumb-init -- /my/script --with --args"
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
# Run this command when container is started
CMD carton exec hypnotoad -f script/blog
image: docker:stable
services:
- docker:dind
variables:
CONTAINER_REGISTRY: "registry.gitlab.com"
DOCKER_IMAGE: "loncarales/mojo-blog"
before_script:
- 'docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CONTAINER_REGISTRY'
stages:
- build
- test
- release
- deploy
build:
stage: build
tags:
- docker
script:
- 'docker build -t $CONTAINER_REGISTRY/$DOCKER_IMAGE:build .'
- 'docker push $CONTAINER_REGISTRY/$DOCKER_IMAGE:build'
only:
- master
test:
stage: test
tags:
- docker
script:
- 'docker pull $CONTAINER_REGISTRY/$DOCKER_IMAGE:build'
- 'docker run --rm -v $PWD/t:/opt/app/t $CONTAINER_REGISTRY/$DOCKER_IMAGE:build carton exec prove -lr -j4'
only:
- master
release:
stage: release
tags:
- docker
script:
- 'docker pull $CONTAINER_REGISTRY/$DOCKER_IMAGE:build'
- 'VERSION=$(cat VERSION)'
- 'docker tag $CONTAINER_REGISTRY/$DOCKER_IMAGE:build $CONTAINER_REGISTRY/$DOCKER_IMAGE:v$VERSION'
- 'docker push $CONTAINER_REGISTRY/$DOCKER_IMAGE:v$VERSION'
- 'docker tag $CONTAINER_REGISTRY/$DOCKER_IMAGE:v$VERSION $CONTAINER_REGISTRY/$DOCKER_IMAGE:latest'
- 'docker push $CONTAINER_REGISTRY/$DOCKER_IMAGE:latest'
only:
- master
deploy:
stage: deploy
tags:
- docker
script:
- 'echo TBA'
only:
- master
pipeline {
agent any
options { disableConcurrentBuilds() }
environment {
registry = "celavi/mojo-blog"
registryCredential = 'dockerhub'
}
stages {
stage("SCM Checkout") {
steps{
git poll: false, url: 'https://github.com/loncarales/mojo-pg.git'
}
}
stage('Config') {
steps {
script {
dir('examples/blog') {
version = readFile('VERSION').trim()
}
}
}
}
stage("Build") {
steps {
script {
dir('examples/blog') {
dockerImage = docker.build registry + ":build"
docker.withRegistry('', registryCredential ) {
dockerImage.push()
}
}
}
}
}
stage("Test") {
steps {
script {
dir('examples/blog') {
dockerImage.pull()
sh 'docker run --rm -v $PWD/t:/opt/app/t $registry:build carton exec prove -lr -j4'
}
}
}
}
stage("Release") {
steps {
script {
dir('examples/blog') {
dockerImage.pull()
dockerImage.tag('v' + version)
dockerImage.tag('latest')
docker.withRegistry('', registryCredential ) {
dockerImage.push('v' + version)
dockerImage.push('latest')
}
}
}
}
}
stage("Deploy") {
steps {
script {
dir('examples/blog') {
echo "TBA"
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment