Skip to main content

Deploying a ‘Hello World’ application to the Cloud Platform

Deployment Process Diagram

Overview

The aim of this guide is to walkthrough the process of deploying an application into the Cloud Platform.

Before working through this tutorial, we recommend reading this Cloud Platform tech overview page, which provides a high-level view of the software development lifecycle on the cloud platform.

This guide uses a pre-configured “Hello World” application as an example of how to deploy your own. This application merely returns a static HTML response, and has no dependencies. Later examples will use more representative applications.

We will walk through these steps:

  • Create a namespace (aka an “environment”) on the Cloud Platform
  • Deploy an instance of a pre-configured “Hello, World” application into your namespace

Then we will demonstrate a typical software development cycle of making and deploying changes to the application:

  • Create and authenticate to an ECR
  • Make changes to the source code of the application
  • Build a new docker image from the demo application
  • Tag the image and push it to your ECR
  • Update your kubernetes files to deploy the updated application

The process of building an image and pushing it to an ECR will normally be carried out an application’s build pipeline. However, for this initial walkthrough, we will go through these steps manually. Later we will go through an example of setting up a CircleCI job to do this automatically. The steps are similar if you’re using other CI/CD tools such as TravisCI.

Prerequisites

This guide assumes the following:

Step 1 - Create your namespace

Follow this guide to create a namespace on the Cloud Platform (you don’t need to do any of the “Next steps” from that guide).

Step 2 - Deploy the “Hello, World” application

git clone https://github.com/ministryofjustice/cloud-platform-helloworld-ruby-app
cd cloud-platform-helloworld-ruby-app

The application has a kubectl_deploy folder containing YAML files which define an instance of the application for kubernetes.

Before we can deploy the application, we need to make some changes to run it in your new namespace.

Edit your local copy of the kubectl_deploy/ingress.yaml file, and change the hostname for the application. The current hostname for the demo application is:

helloworld-rubyapp.apps.live.cloud-platform.service.justice.gov.uk

We need a unique domain name for this instance of the application. There is a “wildcard” domain and SSL certificate available on the Cloud Platform, so we will stick to a domain name which matches:

*.apps.live.cloud-platform.service.justice.gov.uk

Choose a domain name for your application.

We suggest something nobody else is likely to be using, such as:

[your name]-helloworld.apps.live.cloud-platform.service.justice.gov.uk

Replace the two instances of helloworld-rubyapp in the kubectl_deploy/ingress.yaml file with your chosen name.

If you want to use a domain name which doesn’t match the default wildcard domain, you will need to make some more changes. More information on this is available here.

Once you have saved your changes to the kubectl_deploy/ingress.yaml file, you can deploy the application using this command:

kubectl -n [namespace name] apply -f kubectl_deploy

This assumes that your current working directory is the root of your copy of the helloworld application repository.

You can think of this command as meaning:

“Hey, kubernetes. Please apply all the YAML files in the kubectl_deploy directory to the namespace called my-namespace (or whatever you called it).”

A few seconds later, you should be able to visit:

https://[your domain name].apps.live.cloud-platform.service.justice.gov.uk

…in your web browser, and see the “Hello, World!” message.

The kubernetes YAML files

This is how the YAML files link together:

You can find more deployment config info in the kubernetes developer documentation.

ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: <ingress-name>
  namespace: <namespace-name>
  annotations:
    external-dns.alpha.kubernetes.io/set-identifier: <ingress-name>-<namespace-name>-<colour>
    external-dns.alpha.kubernetes.io/aws-weight: "100"
spec:
  tls:
    ingressClassName: default
  - hosts:
    - helloworld-rubyapp.apps.live.cloud-platform.service.justice.gov.uk
  rules:
  - host: helloworld-rubyapp.apps.live.cloud-platform.service.justice.gov.uk
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
          backend:
            service:
              name: rubyapp-service
              port:
                number: 4567

(Note - Please change the ingress-name and namespace-name values in the above example. The colour should be green for ingress in EKS live cluster)

This tells the cluster that our ingress should accept web traffic to the hostname we defined, and should route that traffic to port 4567 of the “service” called rubyapp-service.

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: rubyapp-service
  labels:
    app: rubyapp-service
spec:
  ports:
  - port: 4567
    name: https
    targetPort: 4567
  selector:
    app: helloworld-rubyapp

This defines the service called rubyapp-service which listens on port 4567 and forwards web traffic to port 4567 of any pods whose app label is helloworld-rubyapp.

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-rubyapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld-rubyapp
  template:
    metadata:
      labels:
        app: helloworld-rubyapp
    spec:
      containers:
      - name: rubyapp
        image: ministryofjustice/cloud-platform-helloworld-ruby:1.1
        ports:
        - containerPort: 4567

This creates a single instance (replicas: 1) of a docker container based on the docker image ministryofjustice/cloud-platform-helloworld-ruby:1.1 (pulled from https://hub.docker.com, since no other source is mentioned), which listens on port 4567, and has the app label helloworld-rubyapp.

Updating the application

To update our application, we need to:

  • Change the application source code
  • Create a new docker image from our updated code
  • Push the image to a repository that kubernetes can access
  • Update our deployment.yaml file with the new image details
  • Apply the updated yaml file to kubernetes

Everything but the first step would usually be done automatically by your application’s CI/CD pipeline, but we’re doing it manually here for ease of understanding.

Step 3 - Create an Amazon ECR

Our initial deployment of the Hello World application pulls a docker image from Docker Hub - a public docker image repository.

Most projects hosted on the Cloud Platform use Amazon ECR - a service providing private docker image repositories, only accessible by a single team. So, we’re going to use an ECR to store our updated docker image.

Please follow this guide to create an ECR for your namespace.

Step 4 - Change the application code

Edit the app.rb file, and change “Hello, World!” to something else.

Build the docker image

Now that we have updated the source code, we can build a new docker image (locally).

docker build -t myimage .

You can replace myimage with any name you like for your new docker image. Don’t forget the . at the end of the command.

Authenticating to your docker image repository

Before you can push the image to your ECR, you need to authenticate to it.

To authenticate to your ECR, you will need the access_key_id and secret_access_key which were created for you when you created your ECR. To retrieve these, see the this section of this guide.

tl;dr use this command:

  cloud-platform decode-secret -n [namespace_name] -s [name of your secret]

Once you have your access_key_id and secret_access_key, set up an AWS profile using the AWS cli tool.

  aws configure --profile [namespace name]

You can call the profile anything you want.

Supply your credentials when prompted. For region, enter eu-west-2 This is London, which is the region all cloud platform resources are in.

When prompted for Default output format [None]:, just press enter.

Now, tell the aws command-line tool to use these credentials by running:

export AWS_PROFILE=[profile name]

Where [profile name] is whatever you entered for --profile in the aws configure command.

Login to the repository

The command to login to Amazon ECR is slightly different, depending on your version of the aws command-line tool. You can find this out by running:

  aws --version

For AWS version 1.x use the following command to login to Amazon ECR

  $(aws ecr get-login --no-include-email --region eu-west-2)

The output of the aws ecr... command is a long docker login... command. Including the $(...) around the command executes this output in the context of the current shell

For AWS version 2.0, use this command:

  aws ecr get-login-password --region eu-west-2 \
    | docker login --username AWS --password-stdin \
    754256621582.dkr.ecr.eu-west-2.amazonaws.com

For either version of the aws cli, the output of the above should include Login Succeeded to confirm you have authenticated to the docker image repository.

These credential are valid for 12 hours. So, if you are working through this example over a longer period (e.g. the following day), you will have to login again.

Tag the image and push it to your ECR

If you base64 decode the repo_url of the ECR secret in your namespace, you should get a value something like this:

754256621582.dkr.ecr.eu-west-2.amazonaws.com/my-team/dstest-ecr

We need to tag our docker image with that URL in order to push it to our ECR.

Amazon ECR uses the terms repository and image in a rather confusing way. Normally, you would think of a docker image repository as holding multiple images, each with a different name, where each image can have multiple tags. Amazon ECR conflates the repository and image - i.e. you can only push images with the same name to a given ECR.

So, you can only push an image whose name matches your ECR’s repo_url value. You are free to change the tag of the image, and some teams overload the tag value as a way to store multiple completely different docker images in a single ECR.

We need to tag the image you built earlier so it can be pushed to your ECR

  docker tag myimage 754256621582.dkr.ecr.eu-west-2.amazonaws.com/[team_name]/[repo_name]:1.0

Replace myimage with whatever you chose in the docker build command, and use the repo_url value from your namespace secret.

Now we can push the image to your ECR:

docker push 754256621582.dkr.ecr.eu-west-2.amazonaws.com/[team_name]/[repo_name]:1.0

Step 5 - Deploy the application

Now that we have a new docker image with our updated software, we can deploy it.

Edit your kubectl_deploy/deployment.yaml and change the image from ministryofjustice/cloud-platform-helloworld-ruby:1.1 to the same value you used in the docker push command above.

After saving your changes, apply them to the cluster by repeating the kubectl apply command:

kubectl -n [namespace name] apply -f kubectl_deploy

This will cause kubernetes to launch a new pod using the new docker image, and then delete the old one. In a few seconds, you should see your updated web page.

Add HTTP Basic Authentication

The application can be accessed from the internet at:

https://[your domain name].apps.live.cloud-platform.service.justice.gov.uk

As per the guidance for domain names, our application should have some authentication to prevent citizens accidentally mistaking development websites for live government services. Whilst this isn’t much of a problem with a ‘hello world’ site, it could be an issue for sites using the GDS prototype kit, which look exactly like live services. So, let’s add http basic authentication to our application.

We can do this by amending our Ingress, which is the kubernetes object that routes traffic to our application from the internet. We’ll create an encrypted username and password, and then store that in a kubernetes secret. Then, we will update our Ingress to use basic authentication, and tell it where to find the credentials.

Create the username and password

First we’ll use the htpasswd program to create a one-way hashed username and password. htpasswd is a system program which should be pre-installed on your computer.

To create a username ‘bob’ with password ‘password123’ in a file called ‘auth’, run the following command:

$ htpasswd -cb auth bob password123

Whatever value you use for your password (which should not be ‘password123’ be sure to make a note of it now - it will not be visible again.

Kubernetes secrets are stored as base64-encoded text strings, so we need to run the ‘auth’ file through base64 (which should also be pre-installed):

$ cat auth | base64

This will output the string we need to store in our secret. For the purpose of this tutorial, I’m going to use Ym9iOiRhcHIxJFVXQ1cxWDlvJGt3WDdoMTFZemNYdmVseHE2UFV2VzAK Please substitute the value you got from the step above, using your own choice of username and password.

Note: only the hash of the password is stored in this string, not the password itself, so it is safe to store this string in a public github repository.

You can now delete the ‘auth’ file - we don’t need it anymore.

Create the kubernetes secret

Create a file called kubectl_deploy/secret.yaml containing the following:

apiVersion: v1
kind: Secret
metadata:
  name: basic-auth
data:
  auth: Ym9iOiRhcHIxJFVXQ1cxWDlvJGt3WDdoMTFZemNYdmVseHE2UFV2VzAK

Remember to substitute your base64-encoded credentials string.

The next time we apply our yaml files to our namespace, this file will create a kubernetes secret called ‘basic-auth’. Now we need to configure our Ingress to use it.

Configure the Ingress

To configure our ingress to use basic authentication, we just need to add a couple of lines to the metadata section of kubectl_deploy/ingress.yaml

Replace this:

...
metadata:
  name: helloworld-rubyapp-ingress
spec:
...

…with this:

...
metadata:
  name: helloworld-rubyapp-ingress
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
spec:
...

This tells the Ingress what kind of authentication to use, and which kubernetes secret contains the credentials.

Apply the changes

All that remains is to apply our updated yaml files in exactly the same way as we did before, when we deployed the application:

kubectl -n [namespace name] apply -f kubectl_deploy

You should see output like this:

deployment.apps/helloworld-rubyapp unchanged
ingress.networking.k8s.io/helloworld-rubyapp-ingress configured
secret "basic-auth" created
service "rubyapp-service" unchanged

Now, if you reload the browser page showing the ‘Hello world’ message from the application, you will be prompted to enter the username and password.

This page was last reviewed on 17 January 2023. It needs to be reviewed again on 17 April 2023 .
This page was set to be reviewed before 17 April 2023. This might mean the content is out of date.