Define the Environment

bunnyshell.yaml - The Environment Definition

As mentioned before, the Bunnyshell Books repository contains:

  • a Helm Chart to deploy the backend, located in helm/backend
  • a plain Kubernetes manifest to deploy the frontend, located in manifests/frontend
  • no means to deploy the database

We will now create the bunnyshell.yaml which will put everything together. Let's take it one component at a time, and in the end, we'll look at the complete definition.

👍

For the purpose of this guide, it is important you go through components in order, as the explanations build on what was previously pointed out.

 

The Environment Variables

There are a few variables which are needed to link Components together, so they need to be injected in more than one Component. We will define them as Environment Variables, so they will be available in all Components.

environmentVariables:
  BACKEND_URL: 'https://api-{{ env.base_domain }}'
  FRONTEND_URL: 'https://frontend-{{ env.base_domain }}'
  POSTGRES_DB: bunny_books
  POSTGRES_PASSWORD: mysecretpassword
  POSTGRES_USER: postgres

 

The database / Helm

The database is a Postgres, so we will use the Bitnami Chart to deploy it.

📘

Because this chart does not expose the number of replicas for the master as a Chart value, we will also need kubectl to scale the replicas down to 0 when stopping, and back to 1 when starting.

Therefore, we will use the image dtzar/helm-kubectl for the runner.

Deploy

The purpose is to deploy this component, and by using a Helm Chart, we ultimately need to perform an install. As usual, helm upgrade --install is the preferred way to achieve this.

We will end up running:

helm upgrade --install --namespace {{ env.k8s.namespace }} \
    --post-renderer /bns/helpers/helm/add_labels/kustomize \
    -f my_values.yaml \
    postgres bitnami/postgresql --version 11.9.11

So, let's brake down the install command:

Namespace

We will install the Helm Chart into the namespace which is created by default by Bunnyshell for the current Environment.

You can install Charts into other namespaces as well, but you will need to handle the namespaces yourself.

Example: my-custom-ns-{{ env.unique }} would produce an unique namespace, making it suitable to be deployed in any number of Environments, without causing conflicts.

Post-renderer

You can notice a post-renderer being involved, meaning /bns/helpers/helm/add_labels/kustomize. Its role is to add a set of labels on the Kubernetes resources, so Bunnyshell can know which Component created which resources.

🚧

If you remove the --post-renderer from the helm upgrade --install command, Bunnyshell will not be able to display the created Kubernetes resources, and therefore, you will not be able to see the container logs & events in Bunnyshell.

Custom values file

Before running the Chart install, we need to generate the custom values.yaml, named my_values.yaml by us, but it can be an arbitrary name, as long as it's referenced into the install/upgrade command as well.

Complete Component definition

The rest of the arguments for helm upgrade --install are standard, specifying the release name, chart to be used and version, so we're not going to dive deeper here.

The complete Component definition is listed below:

components:
  ...
  - kind: Helm
    name: postgres
    runnerImage: 'dtzar/helm-kubectl:3.8.2'
    deploy:
      - |
        cat << EOF > my_values.yaml
            global:
                storageClass: bns-network-sc
            auth:
                postgresPassword: {{ env.vars.POSTGRES_PASSWORD }}
                database: {{ env.vars.POSTGRES_DB }}
        EOF
      - 'helm repo add bitnami https://charts.bitnami.com/bitnami'
      - 'helm upgrade --install --namespace {{ env.k8s.namespace }} --post-renderer /bns/helpers/helm/add_labels/kustomize -f my_values.yaml postgres bitnami/postgresql --version 11.9.11'
      - |
        POSTGRES_HOST="postgres-postgresql.{{ env.k8s.namespace }}.svc.cluster.local"
    destroy:
      - 'helm uninstall postgres --namespace {{ env.k8s.namespace }}'
    start:
      - 'kubectl scale --replicas=1 --namespace {{ env.k8s.namespace }} statefulset/postgres-postgresql-11.9.11'
    stop:
      - 'kubectl scale --replicas=0 --namespace {{ env.k8s.namespace }} statefulset/postgres-postgresql-11.9.11'
    exportVariables:
      - POSTGRES_HOST

Start/stop

In this case, the consequence of starting/stopping the database is to create/eliminate the Pods associated with its StatefulSet. As the number of master replicas is not configurable (which is understandable), we will use kubectl to perform the change.

Destroy

A simple helm uninstall is performed.

Exporting variables for other Components

A thing worth mentioning here is the mechanism through which the Postgres host is exported from within the database Component, and will be used in the backend Component.

Sure, it's a deterministic value, so there was no real need to have it exported, we could have just constructed it in the backend - but we decided to do so in order to present you this mechanism also.

Notice the exportVariables property of the Component: it will capture the environment variables with the names specified as list, in this case only POSTGRES_HOST. To make this work, POSTGRES_HOST is defined during the deploy step. This value will be captured and used in subsequent workflows (start/stop/destroy).

📘

The Components are ran according to the dependsOn attribute when deploying or starting the Environment, and in parallel when stopping or destroying.
You can find out more in the dedicated Environment Workflows section.

 

The backend / Helm

Build

The backend also needs an image to be built, so we will use a DockerImage Component as well.

The properties - we hope - are pretty self-explanatory, but if you have any doubts or concerns, do not hesitate to check out the DockerImage Component page.

Deploy

The deploy is very similar to the Database deploy, but that is somewhat expected, since they're both Helm Charts.

Complete Component definition

What was added here are the git* attributes, specifying the git repository, branch and path where the scripts will be ran. Basically, the repo will be cloned and mounted within your runner container, so you can leverage files from it, such as the Helm Chart and other possible scripts.

components:
  ...
  
  - kind: DockerImage
    name: backend-image
    context: /backend
    dockerfile: Dockerfile
    target: prod
    gitRepo: 'git://github.com/bunnyshell/demo-books.git'
    gitBranch: master
    gitApplicationPath: /backend
    
  - kind: Helm
    name: api
    runnerImage: 'dtzar/helm-kubectl:3.8.2'
    deploy:
      - |
        cat << EOF > my_values.yaml
            serviceImage: {{ components.backend-image.image }}
            replicas: 1
            ingress:
                className: bns-nginx
                host: api-{{ env.base_domain }}
            postgres:
                host: '{{ components.postgres.exported.POSTGRES_HOST }}'
                db: '{{ env.vars.POSTGRES_DB }}'
                user: '{{ env.vars.POSTGRES_USER }}'
                password: '{{ env.vars.POSTGRES_PASSWORD }}'
            frontendUrl: '{{ env.vars.FRONTEND_URL }}'
        EOF
      - 'helm upgrade --install --namespace {{ env.k8s.namespace }} --dependency-update --post-renderer /bns/helpers/helm/add_labels/kustomize -f my_values.yaml api-{{ env.unique }} ./helm/backend'
    destroy:
      - 'helm uninstall api-{{ env.unique }} --namespace {{ env.k8s.namespace }}'
    start:
      - 'helm upgrade --namespace {{ env.k8s.namespace }} --post-renderer /bns/helpers/helm/add_labels/kustomize --reuse-values --set replicas=1 api-{{ env.unique }} ./helm/backend'
    stop:
      - 'helm upgrade --namespace {{ env.k8s.namespace }} --post-renderer /bns/helpers/helm/add_labels/kustomize --reuse-values --set replicas=0 api-{{ env.unique }} ./helm/backend'
    gitRepo: 'git://github.com/bunnyshell/demo-books.git'
    gitBranch: master
    gitApplicationPath: /helm/backend

Start/stop

As the number of replicas is exposed in this case in the values.yaml file, we can use a helm upgrade ... --reuse-values --set replicas=1 to start, and a similar, with "0" command to stop the Component.

Destroy

Similarly, a simple helm uninstall is performed.

 

The frontend / KubernetesManifest

For the sake of completeness, we also wanted to demonstrate a Component deployed with plain Kubernetes manifests.

Build

It also has a build, represented through a DockerImage Component.

Deploy

Similarly, the deploy consists of scripts to be ran, except that this time there's more "manual" work involved, since we need to adapt a fixed string to our dynamic nature for Environments.

To keep it simple, we decided to perform some rudimentary replacements with sed, just to illustrate the principle behind. It would be wise for you to use something that also can assert the number of replacements made.

Complete Component definition

components:
  ...
  
  - kind: DockerImage
    name: frontend-image
    context: /frontend
    dockerfile: Dockerfile
    target: prod
    args:
      REACT_APP_BASE_API: '{{ env.vars.BACKEND_URL }}'
    gitRepo: 'git://github.com/bunnyshell/demo-books.git'
    gitBranch: master
    gitApplicationPath: /frontend
    
  - kind: KubernetesManifest
    name: frontend
    runnerImage: 'alpine/k8s:1.22.15'
    deploy:
      - 'cd manifests/frontend'
      - 'kustomize create --autodetect --recursive --labels=app.kubernetes.io/instance-frontend:bns,app.kubernetes.io/part-of:env-{{ env.unique }} --namespace {{ env.k8s.namespace }}'
      - 'kustomize edit set image needsimage={{ components.frontend-image.image }}'
      - 'sed -i "s/frontend.example.com/frontend-{{ env.base_domain }}/g" ingress.yaml'
      - 'sed -i "s/ingressClassName: nginx/ingressClassName: bns-nginx/g" ingress.yaml'
      - 'kubectl apply -k .'
    destroy:
      - 'cd manifests/frontend'
      - 'kustomize create --autodetect --recursive --namespace {{ env.k8s.namespace }}'
      - 'kubectl delete -k .'
    start:
      - 'kubectl scale --replicas=1 --namespace {{ env.k8s.namespace }} deployment/frontend'
    stop:
      - 'kubectl scale --replicas=0 --namespace {{ env.k8s.namespace }} deployment/frontend'
    gitRepo: 'git://github.com/bunnyshell/demo-books.git'
    gitBranch: master
    gitApplicationPath: /manifests/frontend

Start/stop

Similarly to the database, start/stop leverages kubectl and setting the number of replicas on the Kubernetes Deployment.

Destroy

A simple kubectl delete is performed, on the same manifests used when deploying.

 

Complete Environment definition

👍

The complete bunnyshell.yaml definition can also be found in in the Git repo.

kind: Environment
name: Demo Books
type: primary
environmentVariables:
  BACKEND_URL: 'https://api-{{ env.base_domain }}'
  FRONTEND_URL: 'https://frontend-{{ env.base_domain }}'
  POSTGRES_DB: bunny_books
  POSTGRES_PASSWORD: mysecretpassword
  POSTGRES_USER: postgres

components:
  - kind: DockerImage
    name: frontend-image
    context: /frontend
    dockerfile: Dockerfile
    target: prod
    args:
      REACT_APP_BASE_API: '{{ env.vars.BACKEND_URL }}'
    gitRepo: 'git://github.com/bunnyshell/demo-books.git'
    gitBranch: master
    gitApplicationPath: /frontend

  - kind: DockerImage
    name: backend-image
    context: /backend
    dockerfile: Dockerfile
    target: prod
    gitRepo: 'git://github.com/bunnyshell/demo-books.git'
    gitBranch: master
    gitApplicationPath: /backend

  - kind: Helm
    name: postgres
    runnerImage: 'dtzar/helm-kubectl:3.8.2'
    deploy:
      - |
        cat << EOF > my_values.yaml
            global:
                storageClass: bns-network-sc
            auth:
                postgresPassword: {{ env.vars.POSTGRES_PASSWORD }}
                database: {{ env.vars.POSTGRES_DB }}
        EOF
      - 'helm repo add bitnami https://charts.bitnami.com/bitnami'
      - 'helm upgrade --install --namespace {{ env.k8s.namespace }} --post-renderer /bns/helpers/helm/add_labels/kustomize -f my_values.yaml postgres bitnami/postgresql --version 11.9.11'
      - |
        POSTGRES_HOST="postgres-postgresql.{{ env.k8s.namespace }}.svc.cluster.local"
    destroy:
      - 'helm uninstall postgres --namespace {{ env.k8s.namespace }}'
    start:
      - 'kubectl scale --replicas=1 --namespace {{ env.k8s.namespace }} statefulset/postgres-postgresql-11.9.11'
    stop:
      - 'kubectl scale --replicas=0 --namespace {{ env.k8s.namespace }} statefulset/postgres-postgresql-11.9.11'
    exportVariables:
      - POSTGRES_HOST

  - kind: Helm
    name: api
    runnerImage: 'dtzar/helm-kubectl:3.8.2'
    deploy:
      - |
        cat << EOF > my_values.yaml
            serviceImage: {{ components.backend-image.image }}
            replicas: 1
            ingress:
                className: bns-nginx
                host: api-{{ env.base_domain }}
            postgres:
                host: '{{ components.postgres.exported.POSTGRES_HOST }}'
                db: '{{ env.vars.POSTGRES_DB }}'
                user: '{{ env.vars.POSTGRES_USER }}'
                password: '{{ env.vars.POSTGRES_PASSWORD }}'
            frontendUrl: '{{ env.vars.FRONTEND_URL }}'
        EOF
      - 'helm upgrade --install --namespace {{ env.k8s.namespace }} --dependency-update --post-renderer /bns/helpers/helm/add_labels/kustomize -f my_values.yaml api-{{ env.unique }} ./helm/backend'
    destroy:
      - 'helm uninstall api-{{ env.unique }} --namespace {{ env.k8s.namespace }}'
    start:
      - 'helm upgrade --namespace {{ env.k8s.namespace }} --post-renderer /bns/helpers/helm/add_labels/kustomize --reuse-values --set replicas=1 api-{{ env.unique }} ./helm/backend'
    stop:
      - 'helm upgrade --namespace {{ env.k8s.namespace }} --post-renderer /bns/helpers/helm/add_labels/kustomize --reuse-values --set replicas=0 api-{{ env.unique }} ./helm/backend'
    gitRepo: 'git://github.com/bunnyshell/demo-books.git'
    gitBranch: master
    gitApplicationPath: /helm/backend

  - kind: KubernetesManifest
    name: frontend
    runnerImage: 'alpine/k8s:1.22.15'
    deploy:
      - 'cd manifests/frontend'
      - 'kustomize create --autodetect --recursive --labels=app.kubernetes.io/instance-frontend:bns,app.kubernetes.io/part-of:env-{{ env.unique }} --namespace {{ env.k8s.namespace }}'
      - 'kustomize edit set image needsimage={{ components.frontend-image.image }}'
      - 'sed -i "s/frontend.example.com/frontend-{{ env.base_domain }}/g" ingress.yaml'
      - 'sed -i "s/ingressClassName: nginx/ingressClassName: bns-nginx/g" ingress.yaml'
      - 'kubectl apply -k .'
    destroy:
      - 'cd manifests/frontend'
      - 'kustomize create --autodetect --recursive --namespace {{ env.k8s.namespace }}'
      - 'kubectl delete -k .'
    start:
      - 'kubectl scale --replicas=1 --namespace {{ env.k8s.namespace }} deployment/frontend'
    stop:
      - 'kubectl scale --replicas=0 --namespace {{ env.k8s.namespace }} deployment/frontend'
    gitRepo: 'git://github.com/bunnyshell/demo-books.git'
    gitBranch: master
    gitApplicationPath: /manifests/frontend