How to Deploy a Containerized Application to a Kubernetes Cluster

How to Deploy a Containerized Application to a Kubernetes Cluster

Containerizing an application means packaging it and all its dependencies into a single file. You can achieve this using a tool like Docker. The process of containerization involves creating a Dockerfile; which when built becomes a Docker image. Once you run the Docker image, it becomes a container.

An orchestration tool such as Kubernetes handles the coordination of your containers, providing a platform for managing, deploying, and scaling them. Kubernetes also offers mechanisms for securing containers, monitoring their performance, and managing updates.

Kubernetes and Docker are both popular DevOps tools that complement each other in the modern deployment cycle. Kubernetes provides orchestration and Docker provides the underlying infrastructure for containerized applications. Together, they provide a powerful platform for deploying and managing containerized applications.

The goal of this tutorial is to containerize a simple Hello World application and ship it to a Kubernetes cluster using Minikube. At the end of this tutorial, you will have deployed an application locally and understood how it works.

Prerequisites

To utilize this tutorial, you need the following:

  • A Kubernetes cluster set up and configured. This tutorial uses a local Kubernetes cluster via Minikube. Check out this article to learn how to set up Minikube.

  • Docker installed on your machine. Follow the steps in the Docker installation documentation to install it.

  • Fundamental understanding of Docker and Kubernetes. You can take a crash course here.

Step 1 - Clone the Application

Clone the Hello World application from Google Cloud's official GitHub samples repository.

git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples.git

After cloning, navigate to the application folder:

cd kubernetes-engine-samples/quickstarts/hello-app

Step 2 - Dockerize the Application

Dockerizing an application is the process of containerizing (packaging) it using Docker.

This step explains how to write a Dockerfile, build a Docker image, and run it as a Docker container.

  1. Write the Dockerfile

    Although this project already contains a Dockerfile, it is important to understand what each step does so you can modify it to suit your needs.

    To see the Dockerfile, run the following command:

     vi Dockerfile
    

    You should see the following content:

     # Copyright 2022 Google LLC
     #
     # Licensed under the Apache License, Version 2.0 (the "License");
     # you may not use this file except in compliance with the License.
     # You may obtain a copy of the License at
     #
     #     http://www.apache.org/licenses/LICENSE-2.0
     #
     # Unless required by applicable law or agreed to in writing, software
     # distributed under the License is distributed on an "AS IS" BASIS,
     # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     # See the License for the specific language governing permissions and
     # limitations under the License.
    
     # [START gke_quickstarts_hello_app_dockerfile]
     FROM golang:1.21.0 as builder
     WORKDIR /app
     RUN go mod init hello-app
     COPY *.go ./
     RUN CGO_ENABLED=0 GOOS=linux go build -o /hello-app
    
     FROM gcr.io/distroless/base-debian11
     WORKDIR /
     COPY --from=builder /hello-app /hello-app
     ENV PORT 8080
     USER nonroot:nonroot
     CMD ["/hello-app"]
     # [END gke_quickstarts_hello_app_dockerfile]
    

    Here's a breakdown of the Dockerfile:

    • Base image:

        FROM golang:1.21.0 as builder
      

      This line specifies the base image for the first stage of the build process. It uses the official golang image version 1.21.0 as a starting point for building the application.

    • Working directory:

        WORKDIR /app
      

      This sets the working directory inside the container to /app.

    • Initialize Go module:

        RUN go mod init hello-app
      

      This initializes a Go module named hello-app. Go modules are a dependency management system introduced in Go 1.11.

    • Copy source code:

        COPY *.go ./
      

      This copies all .go files from the host machine's current directory into the /app directory inside the container.

    • Build application:

        RUN CGO_ENABLED=0 GOOS=linux go build -o /hello-app
      

      This builds the Go application located in the /app directory inside the container.

      It compiles the application for Linux (GOOS=linux) and disables CGO (CGO_ENABLED=0) to ensure that the resulting binary is statically linked and doesn't depend on external libraries.

      The -o flag specifies the output binary's location, which is /hello-app in this case.

    • Second stage - minimal base image:

        FROM gcr.io/distroless/base-debian11
      

      This command starts the second stage of the Dockerfile using the minimal base image gcr.io/distroless/base-debian11.

      This image provides only the minimal dependencies required to run the application, making it smaller and potentially more secure than a full-fledged Linux distribution.

    • Working Directory:

        WORKDIR /
      

      This sets the working directory inside the container to the root directory.

    • Copy Binary from First Stage:

        COPY --from=builder /hello-app /hello-app
      

      This copies the compiled binary hello-app from the builder stage into the root directory of the final image.

    • Set Environment Variable:

        ENV PORT 8080
      

      This sets an environment variable named PORT to 8080. This environment variable likely tells the application which port to listen on.

    • Set User:

        USER nonroot:nonroot
      

      This sets the user that the container will run as. It's common practice to run containers with a non-root user for security reasons.

    • Command to Run Application:

        CMD ["/hello-app"]
      

      This specifies the command to run when the container starts. In this case, it runs the hello-app binary, which was copied into the container earlier.

      To learn more about Dockerfiles, see the Dockerfile reference.

  2. Build the Docker image

    To build the image from the Dockerfile, run the command below:

     docker build -t hello-app .
    
  3. Run the Docker container

    When you run a Docker image, it becomes a container.

    To run the Docker image, execute the command below:

     docker run -d -p 8080:8080 hello-app
    

    You can view all the running containers on your machine by running the command below:

     docker ps
    

    View this application on your browser using http://localhost:8080 or via the curl command.

    You will get the following output:

    Docker container output

    **Figure 1:** Docker container output

Step 3 - Push the Docker Image to Docker Hub

Docker images need to be publicly accessible before they can be pulled by Kubernetes.

For this reason, you will push the image to a public registry called Docker hub.

To push the Docker image, follow these steps:

  1. Tag the Docker image

    A tag identifies the build version you are pushing to Docker Hub, which helps you deploy a new image or retrieve an old one.

    Tag the Docker image using the command below:

     docker tag hello-app <DOCKER-HUB-USERNAME>/hello-app:1
    

    Replace with the username of your Docker hub account.

  2. Login to Docker hub

    Authentication with Docker hub is required before you can push an image to the registry.

    Use the command below to authenticate:

      docker login -u "USERNAME" -p "PASSWORD" docker.io
    

    Immediately you get the Login Succeeded output, you are ready to push your image.

  3. Push your image to Docker hub

    Execute the following command to push your image to Docker hub:

      docker push <DOCKER-HUB-USERNAME>/hello-app:1
    

    You will get an output similar to this:

      The push refers to repository [docker.io/<DOCKER-HUB-USERNAME>/hello-app]
     30a020c37916: Pushed
     c8beeff22ce7: Pushed
     714f56238fb5: Pushed
     f33e343848bd: Pushed
     4cb10dd2545b: Pushed
     d2d7ec0f6756: Pushed
     1a73b54f556b: Pushed
     e624a5370eca: Pushed
     d52f02c6501c: Pushed
     ff5700ec5418: Pushed
     accc3e6808c0: Pushed
     6fbdf253bbc2: Pushed
     54ad2ec71039: Pushed
     1: digest: sha256:8952fed2b9099b11f7877aaf6dca796f642952e995365e5180ef2ecb788898ae size: 3033
    

Step 4 - Deploying to Kubernetes

To deploy the Hello World application, you need Kubernetes manifest files. This project includes pre-written deployment and service manifest files.

Use the commands below to view them:

cd manifests
ls

To deploy the application to Kubernetes, follow the steps below:

  • Create a deployment file

    View the deployment file using the command below:

      vi helloweb-deployment.yaml
    

    This is the content of the file:

      # Copyright 2021 Google LLC
      #
      # Licensed under the Apache License, Version 2.0 (the "License");
      # you may not use this file except in compliance with the License.
      # You may obtain a copy of the License at
      #
      #      http://www.apache.org/licenses/LICENSE-2.0
      #
      # Unless required by applicable law or agreed to in writing, software
      # distributed under the License is distributed on an "AS IS" BASIS,
      # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      # See the License for the specific language governing permissions and
      # limitations under the License.
    
      # [START gke_manifests_helloweb_deployment_deployment_helloweb]
      # [START container_helloapp_deployment]
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: helloweb
        labels:
          app: hello
      spec:
        selector:
          matchLabels:
            app: hello
            tier: web
        template:
          metadata:
            labels:
              app: hello
              tier: web
          spec:
            containers:
            - name: hello-app
              image: us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
              ports:
              - containerPort: 8080
              resources:
                requests:
                  cpu: 200m
      # [END container_helloapp_deployment]
      # [END gke_manifests_helloweb_deployment_deployment_helloweb]
      ---
    

    Here's a brief breakdown of the current deployment file:

    • apiVersion:

        apiVersion: apps/v1
      

      This specifies the API version of the Kubernetes object being created. In this case, it's using the apps/v1 API version, which is commonly used for Deployments.

    • kind:

        kind: Deployment
      

      This indicates the Kubernetes resource type being defined, which is a Deployment.

    • metadata:

        metadata:
          name: helloweb
          labels:
            app: hello
      

      This section defines metadata for the Deployment, including the name (helloweb) and labels (app: hello).

    • spec:

        spec:
          selector:
            matchLabels:
              app: hello
              tier: web
      

      This specifies the criteria used to select Pods to manage. In this case, it selects Pods with labels app: hello and tier: web.

    • template:

        template:
          metadata:
            labels:
              app: hello
              tier: web
          spec:
            containers:
              - name: hello-app
                image: us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
                ports:
                  - containerPort: 8080
                resources:
                  requests:
                    cpu: 200m
      

      This section defines the Pod template used by the Deployment. It specifies the labels to apply to the Pods (app: hello and tier: web) and the container(s) to run within each Pod.

      • containers: This subsection defines the containers to run within the Pods. In this case, there is one container named hello-app.

        • name: The name of the container.

        • image: The Docker image to use for the container. It pulls the image us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0.

        • ports: Specifies which ports the container exposes. Here, it exposes port 8080.

        • resources: Specifies the resource requirements for the container. Here, it requests 200 milliCPU (mCPU) for the container's CPU resource.

To know more about Kubernetes deployment files, see here.

Edit this file to use the Docker image created above. Once you do, you should have the following output:

    # Copyright 2021 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    # [START gke_manifests_helloweb_deployment_deployment_helloweb]
    # [START container_helloapp_deployment]
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: hello-app-deployment
      labels:
        app: hello-app
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hello-app
          tier: web
      template:
        metadata:
          labels:
            app: hello-app
            tier: web
        spec:
          containers:
          - name: hello-app
            image: <DOCKER-HUB-USERNAME>/hello-app:1
            ports:
            - containerPort: 8080
            resources:
              requests:
                cpu: 200m
    # [END container_helloapp_deployment]
    # [END gke_manifests_helloweb_deployment_deployment_helloweb]
    ---

Take note of the line below:

    image: <DOCKER-HUB-USERNAME>/hello-app:1

Make sure to replace <DOCKER-HUB-USERNAME> with your Docker hub username.

  • Create a Load Balancer service file

    A load balancer service is important because it exposes the application to the internet.

    Access the pre-written service file by running the command below:

      vi helloweb-service-load-balancer.yaml
    

    This is the content of the file:

    
      # Copyright 2021 Google LLC
      #
      # Licensed under the Apache License, Version 2.0 (the "License");
      # you may not use this file except in compliance with the License.
      # You may obtain a copy of the License at
      #
      #      http://www.apache.org/licenses/LICENSE-2.0
      #
      # Unless required by applicable law or agreed to in writing, software
      # distributed under the License is distributed on an "AS IS" BASIS,
      # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      # See the License for the specific language governing permissions and
      # limitations under the License.
    
      # [START gke_manifests_helloweb_service_load_balancer_service_helloweb]
      # [START container_helloapp_service_load_balancer]
      apiVersion: v1
      kind: Service
      metadata:
        name: helloweb
        labels:
          app: hello
          tier: web
      spec:
        type: LoadBalancer
        ports:
        - port: 80
          targetPort: 8080
        selector:
          app: hello
          tier: web
      # [END container_helloapp_service_load_balancer]
      # [END gke_manifests_helloweb_service_load_balancer_service_helloweb]
      ---
    

    This service file defines a load balancer service named helloweb for exposing the hello-app application to external traffic.

    The goal of this file is to ensure that the hello-app application is accessible from outside the Kubernetes cluster by routing traffic from port 80 to port 8080 on Pods labeled with app: hello and tier: web.

    This service allows external clients to access the application via a public IP address or DNS name assigned by a cloud provider's load balancer.

    Change the app name from hello to hello-app to match the deployment file.

    Once you do, you should have the following output:

      # Copyright 2021 Google LLC
      #
      # Licensed under the Apache License, Version 2.0 (the "License");
      # you may not use this file except in compliance with the License.
      # You may obtain a copy of the License at
      #
      #      http://www.apache.org/licenses/LICENSE-2.0
      #
      # Unless required by applicable law or agreed to in writing, software
      # distributed under the License is distributed on an "AS IS" BASIS,
      # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      # See the License for the specific language governing permissions and
      # limitations under the License.
    
      # [START gke_manifests_helloweb_service_load_balancer_service_helloweb]
      # [START container_helloapp_service_load_balancer]
      apiVersion: v1
      kind: Service
      metadata:
        name: hello-app-service
        labels:
          app: hello-app
          tier: web
      spec:
        type: LoadBalancer
        ports:
        - port: 80
          targetPort: 8080
        selector:
          app: hello-app
          tier: web
      # [END container_helloapp_service_load_balancer]
      # [END gke_manifests_helloweb_service_load_balancer_service_helloweb]
      ---
    
  • Apply the deployment and service files

    Applying the deployment and service files will create the resources in your Kubernetes cluster.

    Using kubectl, run the commands below:

       kubectl apply -f helloweb-deployment.yaml
       kubectl apply -f helloweb-service-load-balancer.yaml
    

    You should get an output similar to this:

      deployment.apps/hello-app-deployment created
      service/hello-app-service created
    
  • Accessing the application

    In order to access the application over the internet, you will need to use kubectl port-forwarding. This will allow you to create a stream between your local port and the port of your Kubernetes pod.

    Port-forward your load balancer service using the command below:

       kubectl port-forward service/hello-app-service 8000:80
    

    View the application on your browser using http://localhost:8000 or via the curl command.

    You should get the following output:

    Kubernetes application output

    **Figure 2:** Kubernetes application output

With this, you have successfully deployed a containerized application to a Kubernetes cluster.

Make sure to clean up your resources by running the commands below:

kubectl delete -f helloweb-deployment.yaml
kubectl delete -f helloweb-service-load-balancer.yaml

Conclusion

This tutorial showed you how to deploy a containerized app to a Kubernetes cluster. You went through the process of containerizing the application and deploying it.

This tutorial mimics what happens when you use cloud managed Kubernetes services such as GKE, EKS, and AKS. Please feel free to use these services and note the differences in the deployment process.