Cloud Computing

Cloud Deployment Examples


Learning Objectives

  • You know some cloud computing providers that offer Kubernetes and Serverless computing as a service.
  • You know the basics of creating a Kubernetes cluster and a Serverless application on a cloud provider.

There exists a wide variety of cloud computing providers that offer Kubernetes as a service. These include Scaleway, AWS, Google Cloud, Azure, and Digital Ocean. Here, we’ll briefly look into deployment to an online cloud provider.

The screenshots have been taken in 2023, so the flow might have changed.

In the example, we’ll use Google Kubernetes Engine with autopilot.

Credit card required

If you want hands-on experience by following the example, you’ll need a credit card. If you provide credit card details, make sure to dismantle all experiments once you’ve tried them out to avoid unnecessary billing.

Concretely doing the deployment is not required in this course.

Deployment using Kubernetes

Creating a project on Google Cloud

We start by logging into (and registering if needed) into Google Cloud and creating a new project. We’ll call the project dab-kubernetes-autopilot-demo (you could name the project anything you want).

Fig. 1 - Creating a new project on Google Cloud.

Fig. 1 - Creating a new project on Google Cloud.

Once the project has been created, select the project in the dashboard. Next, clicking on the link “APIs & Services”, we choose “Enable APIs and Services”. This opens up an API library that allows us to enable APIs for the project. For the present project, we enable the “Kubernetes Engine API” and the ” Artifact Registry API”.

Creating a cluster

Next, with the Kubernetes Engine API and the Google Container Registry API enabled, we can create a cluster. The cluster is created in the Kubernetes Engine dashboard at https://console.cloud.google.com/kubernetes/, shown in Figure 2.

Fig. 2 - Kubernetes Engine dashboard.

Fig. 2 - Kubernetes Engine dashboard.

In the dashboard, we click the “Create” button and choose the “Autopilot” option, where the cluster is managed by Google. This opens up a wizard, that is used to create the cluster, and that guides through the steps of creating a cluster.

Fig. 3 - Cluster creation wizard, cluster basics.

Fig. 3 - Cluster creation wizard, cluster basics.

When setting up the cluster, we use the default name from the wizard (here, autopilot-cluster-1) and select europe-north1 as the region for the cluster. These settings are shown in Figure 3 above. We use the default options for networking and advanced settings, and in the review and create -step, we click the “Create cluster” button to create the cluster. It takes a while for the cluster to be created.

Once the cluster has been created, it is in the list of visible clusters. At the present stage, as shown in Figure 4, there are no nodes, no vCPUs, and the cluster does not use any memory.

Fig. 4 - Newly created cluster is available in the cluster list.

Fig. 4 - Newly created cluster is available in the cluster list.

Creating a repository for Docker images

Next, we open up the artifact registry at https://console.cloud.google.com/artifacts and create a new repository for Docker images by clicking “Create repository”. When creating the repository, we call the repository “docker-images”, select “Docker” as format, choose “Standard” mode and “Region” as location type. As the region, we’ll choose europe-north1. We use the default encryption (Google-managed encryption key). Once the options have been provided, we click “Create” to create the repository.

Once the repository has been created, it is in the list of visible repositories, shown in Figure 5.

Fig. 5 - Artifact registry contains the repository docker-images.

Fig. 5 - Artifact registry contains the repository docker-images.

Creating an application

For the purposes of the example, we’ll create a simple application that responds to requests with a joke. The application is written in vanilla Deno. The app.js for the application is as follows:

const jokes = [
  "What did baby corn say to mama corn? -- Where's pop corn?",
  "Why are eggs bad at handling stress? -- They crack under pressure.",
];

const server = `Server ${Math.floor(10000 * Math.random())}`;

const handleRequest = async (request) => {
  const joke = jokes[Math.floor(Math.random() * jokes.length)];

  return new Response(`${server}: ${joke}`);
};

Deno.serve({ hostname: "0.0.0.0", port: 7777 }, handleRequest);

And the Dockerfile is as follows.

FROM denoland/deno:alpine-2.0.2

EXPOSE 7777

WORKDIR /app

COPY . .

CMD [ "run", "--unstable", "--allow-net", "app.js" ]

Creating the image and pushing it to the repository

When in the folder that contains the above files, we run the command docker build -t jokes-app ., creating an image jokes-app of the application.

docker build -t jokes-app .
...
 => => naming to docker.io/library/jokes-app

Next, we need to install Google’s Cloud CLI gcloud, which provides command-line functionality for maintaining projects on Google Cloud. With the gcloud installed, we authenticate to the project by running the command gcloud auth login. This opens up a browser window, where we can log in to the Google account that is associated with the project. Once logged in, we can close the browser window.

Next, we authenticate to the specific artifact registry by running the command gcloud auth configure-docker europe-north1-docker.pkg.dev, where europe-north1-docker.pkg.dev is refers to the location of the artifact registry (selected when creating the repository). Running the command updates our Docker configuration to utilize the gcloud command when pushing images.

gcloud auth configure-docker europe-north1-docker.pkg.dev
Adding credentials for: europe-north1-docker.pkg.dev
After update, the following will be written to your Docker config file located at [/home/username/.docker/config.json]:
 {
  "credHelpers": {
    "europe-north1-docker.pkg.dev": "gcloud"
  }
}

Do you want to continue (Y/n)?

Docker configuration file updated.

Next, we tag the image by running the command docker tag jokes-app europe-north1-docker.pkg.dev/dab-kubernetes-autopilot-demo/docker-images/jokes-app:latest. The tag is in the format europe-north1-docker.pkg.dev/<project-id>/<repository-name>/<image-name>:<tag>.

docker tag jokes-app europe-north1-docker.pkg.dev/dab-kubernetes-autopilot-demo/docker-images/jokes-app:latest

And finally, we push the image to the registry using the command docker push europe-north1-docker.pkg.dev/dab-kubernetes-autopilot-demo/docker-images/jokes-app:latest.

docker push europe-north1-docker.pkg.dev/dab-kubernetes-autopilot-demo/docker-images/jokes-app:latest
The push refers to repository [europe-north1-docker.pkg.dev/dab-kubernetes-autopilot-demo/docker-images/jokes-app]
...
latest: ...

Now, when we visit the artifact registry and open up the docker-images repository, we see the image jokes-app in the list of images, as shown in Figure 6.

Fig. 6 - Jokes app image present in artifact registry.

Fig. 6 - Jokes app image present in artifact registry.

Deploying an image

Next, it’s time to deploy the image. Let’s create a deployment configuration file jokes-app-deployment.yaml with the following content.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jokes-app-deployment
  labels:
    app: jokes-app
spec:
  selector:
    matchLabels:
      app: jokes-app
  template:
    metadata:
      labels:
        app: jokes-app
    spec:
      containers:
        - name: jokes-app
          image: europe-north1-docker.pkg.dev/dab-kubernetes-autopilot-demo/docker-images/jokes-app:latest
          ports:
            - containerPort: 7777
          resources:
            requests:
              cpu: "250m"
              memory: "500Mi"
            limits:
              cpu: "250m"
              memory: "500Mi"

The key difference to our earlier deployment configurations is that we now use the image from the artifact registry and that the requested resources differ to some extent from our prior efforts. The above limits are related to the autopilot resource requests.

Cluster access is provided to kubectl by running the command gcloud components install kubectl. This installs the kubectl command-line tool. Next, we run the command cloud container clusters get-credentials autopilot-cluster-1 --zone europe-north1 --project dab-kubernetes-autopilot-demo. This updates the kubectl configuration to point to the cluster in our project.

cloud container clusters get-credentials autopilot-cluster-1 --zone europe-north1 --project dab-kubernetes-autopilot-demo
Fetching cluster endpoint and auth data.
kubeconfig entry generated for autopilot-cluster-1.

Finally, we can apply our configuration to deploy the image to the cluster. This is done by running the command kubectl apply -f jokes-app-deployment.yaml in the folder where the configuration file is located.

kubectl apply -f jokes-app-deployment.yaml
deployment.apps/jokes-app-deployment configured

Now, when we check out the pods, we see that a pod is running.

kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
jokes-app-deployment-7cb766f44f-vwgk2   1/1     Running   0          4m21s

Exposing a service

The next step is to expose the deployment. Let’s create a service configuration file jokes-app-service.yaml with the following content.

apiVersion: v1
kind: Service
metadata:
  name: jokes-app-service
spec:
  type: LoadBalancer
  ports:
    - port: 7777
      targetPort: 7777
      protocol: TCP
  selector:
    app: jokes-app

And apply the configuration.

kubectl apply -f jokes-app-service.yaml
service/jokes-app-service created

Now, when we list services running the command kubectl get services, we see that the service is running.

kubectl get services
NAME                TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
jokes-app-service   LoadBalancer   10.42.0.3    34.88.159.3   7777:30574/TCP   2m53s
..

The external IP address is the IP address of the load balancer. We can use this IP address to access the service. Let’s try it out by running the command curl 34.88.159.3:7777 in the terminal.

curl 34.88.159.3:7777
Server 6252: What did baby corn say to mama corn? -- Where's pop corn?
curl 34.88.159.3:7777
Server 6252: What did baby corn say to mama corn? -- Where's pop corn?
curl 34.88.159.3:7777
Server 6252: Why are eggs bad at handling stress? -- They crack under pressure.

Fig. 7 - The application is accessible also using a browser.

Fig. 7 - The application is accessible also using a browser.

Continuation steps and improvements

Above, we manually created a deployment and a service. In reality, much of the deployment process would be automated. For instance, we could use a GitOps-flow to automatically deploy the application when changes are pushed to a repository.

In addition, we would want to set up a domain for the application, configure the domain to point to the load balancer, and set up certificates to allow using HTTPS.

The Google Cloud documentation at https://cloud.google.com/docs/get-started offers plenty of resources for this.

Other providers

Although we’ve used Google Cloud here, the underlying principles are the same across cloud providers. We create a cluster, add an image to a registry, create a deployment with the image, and expose the deployment with a service. Subsequent steps such as adding a domain, creating an ingress with HTTPS support, and so on, also function similarly across the cloud providers.

Cleaning up

If you followed the above steps and created your own Kubernetes deployment, you want to dismantle it to avoid any unnecessary costs (note the free tier though). We can delete the cluster by running the command gcloud container clusters delete autopilot-cluster-1 --zone europe-north1 --project dab-kubernetes-autopilot-demo, which deletes the cluster (and the deployments and services in it).

gcloud container clusters delete autopilot-cluster-1 --zone europe-north1 --project dab-kubernetes-autopilot-demo
The following clusters will be deleted.
 - [autopilot-cluster-1] in [europe-north1]

Do you want to continue (Y/n)?  Y

Deleting cluster autopilot-cluster-1...done.
Deleted [https://container.googleapis.com/v1/projects/dab-kubernetes-autopilot-demo/zones/europe-north1/clusters/autopilot-cluster-1].

Next, we also clean up the artifact repository, which is done by the command gcloud artifacts repositories delete docker-images --location europe-north1.

gcloud artifacts repositories delete docker-images --location europe-north1
You are about to delete repository [docker-images]

Do you want to continue (Y/n)?  Y

Delete request issued for: [docker-images]
Waiting for operation [projects/dab-kubernetes-autopilot-demo/locations/europe-north1/operations/...] to complete...done.
Deleted repository [docker-images].
Larger example

The same approach is used for larger applications. As an example, the outcome of the larger Kubernetes tutorial that started in the chapter Minikube and Kubectl of Containers & Kubernetes can be deployed to a cloud provider.

The key required changes would involve pushing the images to the cloud registry and updating the deployment and service configurations to use the images from the registry.

Databases

Similar to cloud computing providers for Kubernetes, there exists a wide variety of cloud database providers. These include companies that spesialize in databases such as PlanetScale and Supabase, as well as the key cloud computing providers that also offer databases as a service, including Google Cloud databases and AlloyDB, Amazon RDS, and Microsoft’s managed databases.

Managed databases can be a good choice for scaling an application, as they are typically readily optimized for high performance and availability, and come configured with backups, replication, and security. Naturally, they are also more expensive than self-hosted databases or databases installed on a virtual machine from an IaaS provider, but may lead to reduced costs due to the reduced need for in-house database server admins.

Serverless deployment

Next, let’s look at deploying the same application as above, but this time deploying it as a serverless service.

Setting up

Create a new project on Google Cloud and call it dab-serverless-jokes (you could name the project anything you want). Create an artifact registry to the project, and create a folder for docker images into the artifact registry. Similar to the Kubernetes example, we’ll call the repository “docker-images” and choose europe-north1 as the region.

Creating the image

We’ll use the same application as previously. The application is written in vanilla Deno and responds with jokes. The app.js file is as follows.

const jokes = [
  "What did baby corn say to mama corn? -- Where's pop corn?",
  "Why are eggs bad at handling stress? -- They crack under pressure.",
];

const server = `Server ${Math.floor(10000 * Math.random())}`;

const handleRequest = async (request) => {
  const joke = jokes[Math.floor(Math.random() * jokes.length)];

  return new Response(`${server}: ${joke}`);
};

Deno.serve({ hostname: "0.0.0.0", port: 7777 }, handleRequest);

And the Dockerfile is as follows.

FROM denoland/deno:alpine-2.0.2

EXPOSE 7777

WORKDIR /app

COPY . .

CMD [ "run", "--unstable", "--allow-net", "app.js" ]

When in the folder that contains the above files, we run the command docker build -t jokes-app . to create the jokes-app image (if you followed the Kubernetes example, this is already done).

docker build -t jokes-app .
...
 => => naming to docker.io/library/jokes-app

Pushing the image to the registry

We’ll again authenticate to the artifact registry using the command gcloud auth configure-docker europe-north1-docker.pkg.dev. This will add the credentials to the Docker config file, if they are not yet there.

gcloud auth configure-docker europe-north1-docker.pkg.dev
WARNING: Your config file at [/home/username/.docker/config.json] contains these credential helper entries:

{
  "credHelpers": {
    "europe-north1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: europe-north1-docker.pkg.dev
gcloud credential helpers already registered correctly.

Next, we tag the image by running the command docker tag jokes-app europe-north1-docker.pkg.dev/dab-serverless-jokes/docker-images/jokes-app:latest — the key difference to the Kubernetes example is that the project that we are working with is dab-serverless-jokes.

docker tag jokes-app europe-north1-docker.pkg.dev/dab-serverless-jokes/docker-images/jokes-app:latest

Once the image is tagged, we push it to the registry.

docker push europe-north1-docker.pkg.dev/dab-serverless-jokes/docker-images/jokes-app:latest
The push refers to repository [europe-north1-docker.pkg.dev/dab-serverless-jokes/docker-images/jokes-app]
...
latest: ...

Now, the image jokes-app should be in the list of images in the artifact registry.

Going serverless

Next, we’ll go to Google Cloud Run and click the create service button. In the configuration, we choose “Deploy one revision from an existing container image”, and search the jokes-app image from the artifact registry. We select europe-north1 as the region, select the per request pricing, and limit the autoscaling to at most 5 instances (keeping the minimum number of instances at 0). We’ll allow all traffic to the service from the internet, and allow unauthenticated invocations.

If the minimum number of instances would be set to 1, the service would always have at least one instance running, which would reduce the cold start time.

Further, as we’ve created our application so that it listens to requests on port 7777, we need to adjust the container network settings. In the part on Container configuration, set the “Container port” as 7777, and allocate 128 MiB memory and one vCPU to the container.

We could also adjust the application so that it reads an environment variable PORT and use that value.

We can keep the other options as they are by default, and then click the “Create” button. The application will take a while to start up. This creates a serverless application that is accessible over the internet. The URL to the service is available on the service details once the application has been created.

One example of the application is running at https://jokes-app-rqafkhjgta-lz.a.run.app.

curl https://jokes-app-rqafkhjgta-lz.a.run.app/
Server 1044: What did baby corn say to mama corn? -- Where's pop corn?

Google Cloud Run has a free tier of up to two million invocations per month, so contrary to the Kubernetes example that was immediately taken down, the above URL will be up and running at least for while.

Setting up continuous deployment

As you may notice if you’ve followed the above example and opened the Google Cloud Run dashboard, there’s an option for setting up continuous deployment. This allows linking the application to a GitHub repository, and automatically deploying the application when changes are pushed to the repository.

Note that although the above applications are toy examples, the same principles could be used to build larger applications; we could also, for example, run parts of the application using Kubernetes, and parts of the application on a serverless platform, and rely on a cloud database (or few) to manage the application data.