/spring-hello-world-helm-demo

A very simple demo of Helm 3 with Spring

Primary LanguageShell

Spring Boot Helm Pipeline

This project is a simple Spring Boot Hello World application to demonstrate how a CI/CD pipeline might make best use of the Helm package manager.

The Helm chart was initialised using a starter chart for Spring Boot made by a Pivotal employee.

Usage

Run Pipeline - helm-pipeline.sh

Script to create a project and install the sample spring-hello-world-app Helm chart to it. It takes the name of a project and a "Release name". All Helm chart installations are known as "Releases" and the resulting resources will be known as "$RELEASE_NAME-$CHART_NAME". The script will run the app through 3 different stages, performing a dev, test and prod deployment.

Usage:
    ./helm-pipeline.sh $PROJECT_NAME $RELEASE_NAME
Example:
    # Install the sanmanager chart in "my-new-project"
    ./helm-pipeline my-new-project testing-new-feature

This example will create a chart release in "my-new-project" called "testing-new-feature-spring-hello-world-app"

Cleanup Pipeline

To clean up the helm deployments created by the pipeline run the following.

Usage:
    ./helm-pipeline.sh $PROJECT_NAME $RELEASE_NAME cleanup
Example:
    # Uninstall the sanmanager chart in "my-new-project"
    ./helm-pipeline my-new-project testing-new-feature cleanup
This example will perform a `helm uninstall` on each deployment.

Repository Structure

.
├── build-and-push.sh
├── Dockerfile
├── helm
│   └── spring-hello-world-app
│       ├── Chart.yaml
│       ├── envs
│       │   ├── values-dev.yaml
│       │   ├── values-prod.yaml
│       │   └── values-test.yaml
│       ├── templates
│       │   ├── configmap.yaml
│       │   ├── deployment.yaml
│       │   ├── _helpers.tpl
│       │   ├── ingress.yaml
│       │   ├── NOTES.txt
│       │   ├── rbac.yaml
│       │   ├── service.yaml
│       │   └── tests
│       │       └── test-connection.yaml
│       └── values.yaml
├── helm-pipeline.sh
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.adoc
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── demo
│   │   │               └── DemoApplication.java
│   │   └── resources
│   │       ├── application-dev.properties
│   │       ├── application-prod.properties
│   │       ├── application.properties
│   │       └── application-test.properties
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── demo
│                       └── DemoApplicationTests.java
└── target

build-and-push.sh

export APP=spring-hello-world-app
export REG=<Your image repo URL>

mvn clean package -DskipTests
docker build -t ${APP} .
docker tag ${APP}:latest ${REG}/${APP}:latest
docker push ${REG}/${APP}:latest

The current implementation of the hello-world-app uses an image currently located at docker.io/mikecroft/spring-hello-world-app. If this endpoint is inaccessible, it may be beneficial to re-build the app, and push it to a local internal registry. The build-and-push.sh script does this for you, with the user only needing to update the REG variable to their internal image repository.

Helm - spring-boot-hello-world-app

What is Helm?

Helm helps you manage Kubernetes applications — Helm Charts help you define, install, and upgrade even the most complex Kubernetes application.

Three Big Concepts

A Chart is a Helm package. It contains all of the resource definitions necessary to run an application, tool, or service inside of a Kubernetes cluster. Think of it like the Kubernetes equivalent of a Homebrew formula, an Apt dpkg, or a Yum RPM file.

A Repository is the place where charts can be collected and shared. It’s like Perl’s CPAN archive or the Fedora Package Database, but for Kubernetes packages.

A Release is an instance of a chart running in a Kubernetes cluster. One chart can often be installed many times into the same cluster. And each time it is installed, a new release is created. Consider a MySQL chart. If you want two databases running in your cluster, you can install that chart twice. Each one will have its own release, which will in turn have its own release name.

With these concepts in mind, we can now explain Helm like this:

Helm installs charts into Kubernetes, creating a new release for each installation. And to find new charts, you can search Helm chart repositories.

Source: helm.sh

Helm - spring-boot-hello-world-app

Following is the Helm chart used to deploy the hello-world-app.

Helm Structure

├── helm
│   └── spring-hello-world-app
│       ├── Chart.yaml
│       ├── envs
│       │   ├── values-dev.yaml
│       │   ├── values-prod.yaml
│       │   └── values-test.yaml
│       ├── templates
│       │   ├── configmap.yaml
│       │   ├── deployment.yaml
│       │   ├── _helpers.tpl
│       │   ├── ingress.yaml
│       │   ├── NOTES.txt
│       │   ├── rbac.yaml
│       │   ├── service.yaml
│       │   └── tests
│       │       └── test-connection.yaml
│       └── values.yaml

chart.yml

apiVersion: v2                                 #1
appVersion: 0.1.0                              #2
description: A Helm chart for Kubernetes       #3
name: spring-hello-world-app                   #4
type: application                              #5
version: 0.1.0                                 #6

chart.yml defines some metadata about our helm chart. The chart.yaml file is required for a chart and can be populated using the fields found here. The fields we’re using are described below.

  1. The chart API version

  2. The name of the chart

  3. A single-sentence description of this project

  4. The name of the chart

  5. The type of the chart

  6. A Semantic Versioning 2 version

Values.yml and 'env' directory

The Values.yml provides default configuration data for the helm templates to use and is available in a structured format.

The envs directory contains our unique values-${stage}.yaml files for each deployment stage. The global values.yaml however contains the default values file. When running a helm install it is possible to specifiy a particular values.yaml, and if no specific file is specified, the global one will be used.

You can specify a values file using helm install -f custom-values-file.yml

values.yml explained

The values.yml used for this deployment is as follows.

# Set how many application instances to run.
replicaCount: 1

# Override these settings and use your container image.
image:
  repository: mikecroft/spring-hello-world-app
  tag: latest
  pullPolicy: Always

# Set image pull secrets (in case you're using a private container registry).
imageCredentials:
  registry: # gcr.io
  username: # oauth2accesstoken
  password: # $(gcloud auth print-access-token)

# Set service type: LoadBalancer, ClusterIP, NodePort
service:
  type: LoadBalancer
  port: 8080

ingressBase: apps.openshift.ebms.tv
ingress:
  enabled: true                                              #1
  annotations: {}
  hosts:
    - host: spring-hello-world
      paths: ["/"]
  tls: []

# Set to false to disable Prometheus support.
monitoring: true

# Set to false to disable Spring Cloud Kubernetes support.
sck: true

# Set configuration properties.
config:
  foo: bar
Important
Make sure to change the image repository and tag if you’ve rebuilt and pushed the image to your local image repository.
  1. If ingress.enabled is set to true, an openshift Route/ Ingress object will be made. This will expose the app endpoint and make it resolvable outside the cluster.

values-${stage}.yml files

The main difference between the default values.yml and the specific stage files is the springProfilesActive value is set. This value sets the spring profile within the app itself to the desired stage.

# Set Spring active profile
springProfilesActive: dev || test || prod

Helm Templates

Templates generate manifest files, which are YAML-formatted resource descriptions that Kubernetes can understand. The hello-world-app creates several manifests from templates which allow the app to be functional on Openshift.

  1. configmap.yaml - Builds a configmap which specifies the Application.yml file. This contains our config for our spring app. (similar to application.properties).

  2. deployment.yaml - Builds our app deployment template. The Deployment describes a desired state, and the Deployment Controller changes the actual state to the desired state at a controlled rate.

  3. ingress.yaml - Builds the apps ingress object. This will manage the apps external access to the services in a cluster.

  4. rbac.yaml Specifies the Role Based Access Control for the app. This specifies the permissions and accessibility our app has within the cluster. RBAC rules are generally applied to a serviceaccount. For this deployment we use the default serviceaccount.

  5. service.yaml - Builds the apps service object. This will manage the apps internal access within the cluster.

Pipeline - helm-pipeline.sh

Flow

The pipeline begins by taking the project name and project release name as arguments. It will then initialise 3 projects to simulate 3 clusters (a dev, test and prod cluster). An installation flow goes as follows.

  1. Execute the shell script and takes the project name and release name as arguments

  2. Sets enviornment variables

  3. Runs the init function which switches to the correct project (or creates a new one if it doesn’t exist), creates the docker-registry pull secret, links the pull secret with the deafult service account and finally sets the kubeconfig to the correct context.

  4. Runs the install function with an argument of dev.

  5. Runs the chart_test function on dev to check the chart has deployed correctly. This uses helm test to achieve this.

  6. Runs the app_test function to check the expected deployed string against the actual.

  7. Repeats step 4,5,6 with the arguments test and prod

The hello-world-app

The hello world app exposes the '/hello' endpoint mapping with a message from a specified environment. eg: dev, test or prod.

package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;

@SpringBootApplication
@RestController
public class DemoApplication {

    @Value("${environment.type}")                   #1
    private String environment;

	@GetMapping("/hello")                           #2
	String home() {
		return "hello from " + environment;
	}

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}
  1. Sets the environment type based on the value set in application properties.

  2. Returns the string "Hello from ${environment}" to the '/hello' endpoint