DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

  1. DZone
  2. Refcards
  3. Getting Started With Kubernetes
refcard cover
Refcard #233

Getting Started With Kubernetes

Containers weighing you down? Kubernetes can scale them. To build a reliable, scalable containerized application, you need a place to run containers, scale them, update them, and provide them with networking and storage. Kubernetes is the most popular container orchestration system. It simplifies deploying, monitoring, and scaling application components, making it easy to develop flexible, reliable applications. This updated Refcard covers all things Kubernetes, exploring core concepts, important architecture considerations, and how to build your first containerized application.

Download Refcard
Free PDF for Easy Reference
refcard cover

Written By

author avatar Alan Hohn
Director, Software Strategy, Lockheed Martin
Table of Contents
► Introduction ► What Is Kubernetes? ► Core Kubernetes Concepts ► Kubernetes Architecture ► Getting Started With Kubernetes ► Run Your First Container ► Example Application ► Namespace, Resource Quotas, and Limits ► Conclusion
Section 1

Introduction

Containers are a great way to package, deploy, and manage applications. However, to build a reliable, scalable containerized application, you need a place to run containers, scale them, update them, and provide them with networking and storage. Kubernetes is the most popular container orchestration system. It simplifies deploying, monitoring, and scaling application components, making it easy to develop flexible, reliable applications.  

Section 2

What Is Kubernetes?

Kubernetes, also known by its abbreviation “k8s”, is an open-source container orchestration system. It manages a “cluster” of multiple hosts that are used to run containers. Originally created by Google, in March of 2016, it was donated to the Cloud Native Computing Foundation (CNCF).   

Kubernetes is declarative. This means that you can create and update “resources” that describe which containers to run, how to configure them, and how to route network traffic to them. Kubernetes continuously updates and monitors the cluster to ensure it matches the desired state, including auto-restart, re-scheduling, and replication to ensure applications start and remain running.  

Kubernetes is available as a service from cloud providers including Google, Amazon, and Microsoft, or in a variety of distributions including Red Hat OpenShift, Rancher Kubernetes, and VMWare Tanzu.   

Section 3

Core Kubernetes Concepts

Because Kubernetes is declarative, getting started in Kubernetes mostly means understanding what resources we can create and how they are used to deploy and configure containers in the cluster. To define resources, we use YAML format. The available resources and fields for each resource may change with new Kubernetes versions, so it’s important to double-check the API reference for your version to know what’s available. It’s also important to use the correct apiVersion on each resource to match your version of Kubernetes. This Refcard uses the API from Kubernetes 1.24, released May 2022, but all resources shown are backwards compatible through Kubernetes 1.19. Lastly, the examples featured in this Refcard are available in the associated GitHub repository. 

Pod 

A Pod is a group of one or more containers. Kubernetes will schedule all containers for a Pod into the same host, with the same network namespace, so they all have the same IP address and can access each other using localhost. The containers in a Pod can also share storage volumes.  

We don’t typically create Pod resources directly. Instead, we have Kubernetes manage them through a controller such as a Deployment or StatefulSet. Controllers provide fault tolerance, scalability, and rolling updates.   

Deployment 

A Deployment manages one or more identical Pod instances. Kubernetes will make sure that the specified number of Pods is running, and on a rolling update, it will replace Pod instances gradually, allowing for application updates with zero downtime. Here is an example Deployment:   

YAML
 
41
1
apiVersion: apps/v1  
2
kind: Deployment  
3
metadata:  
4
  name: nginx  
5
spec:  
6
  replicas: 3  
7
  selector:  
8
    matchLabels:  
9
      app: nginx  
10
  template:  
11
    metadata:  
12
      labels:  
13
        app: nginx  
14
    spec:  
15
      containers:  
16
      - name: nginx  
17
        image: nginx:1.23.1-alpine  
18
        volumeMounts:  
19
        - mountPath: /usr/share/nginx  
20
          name: www-data  
21
          readOnly: true  
22
        resources:  
23
          requests: 
24
            memory: "128Mi"  
25
            cpu: "50m"  
26
          limits: 
27
            memory: "128Mi"  
28
            cpu: "50m"  
29
      initContainers:  
30
      - name: git-clone  
31
        image: alpine/git  
32
        args:  
33
        - "clone" 
34
        - "https://github.com/book-of-kubernetes/hello-world-static" 
35
        - "/www/html"  
36
        volumeMounts:  
37
        - mountPath: /www  
38
          name: www-data  
39
      volumes:  
40
      - name: www-data  
41
        emptyDir: {}  

The apiVersion and kind uniquely identify the type of the resource. Deployments are in the apps API group, and here, we are specifying v1 as the version. As a result, Kubernetes knows what fields to expect in the rest of the YAML specification.   

For a controller such as a Deployment, the template section specifies exactly what the created Pods should look like. Kubernetes will automatically create three Pods per the replicas field using this template. Kubernetes will monitor these Pods and automatically restart them if the container terminates. Deployments use the matchLabels selector field to determine which Pods to manage. This field must always have the same data as the metadata.labels field inside the template. The Deployment will take ownership of any running Pods that match the matchLabels selector, even if they were created separately, so keep these names unique. 

The example above defines one container in the Pod and additionally defines an initContainer. The initContainer runs before the main Pod container starts. In this case, it uses Git to populate a directory. Because this directory is part of a single volume that is specified as a volumeMount in both containers, the resulting files are also mounted into the main container to be served by NGINX. This example uses an initContainer to run Git because Git runs and then exits; if Git was run as a second regular container in the Pod, Kubernetes would interpret this as a container failure and restart the Pod. 

Finally, we specify requests and limits for our main container. Kubernetes uses these to ensure that each node in the cluster has enough capacity for its deployed Pods. Requests and limits are also used with quotas so that different applications and users in a multi-tenant cluster don’t interfere with each other. It is good practice to identify the resource needs of each container in a Pod and apply limits. 

StatefulSet  

Each new Pod in the above Deployment starts with an empty directory, but what about cases where we need real persistent storage? The StatefulSet also manages multiple Pods, but each Pod is associated with its own unique storage, and that storage is kept when the Pod is replaced.  

Here is an example StatefulSet: 

YAML
 
35
1
apiVersion: apps/v1 
2
kind: StatefulSet 
3
metadata: 
4
  name: postgres 
5
spec: 
6
  serviceName: postgres 
7
  replicas: 2 
8
  selector: 
9
    matchLabels: 
10
      app: postgres 
11
  template: 
12
    metadata: 
13
      labels: 
14
        app: postgres 
15
    spec: 
16
      containers: 
17
      - name: postgres 
18
        image: postgres 
19
        env: 
20
        - name: POSTGRES_PASSWORD 
21
          value: "supersecret" 
22
        - name: PGDATA 
23
          value: /data/pgdata 
24
        volumeMounts: 
25
        - name: postgres-volume 
26
          mountPath: /data 
27
  volumeClaimTemplates: 
28
  - metadata: 
29
      name: postgres-volume 
30
    spec: 
31
      accessModes: 
32
      - ReadWriteOnce 
33
      resources: 
34
        requests: 
35
          storage: 1Gi 


Like a Deployment, a StatefulSet is in the apps API group, uses a selector to discover its Pods, and uses a template to create those Pods. However, a StatefulSet also has a volumeClaimTemplates section that specifies the persistent storage provided to each Pod.  

When this StatefulSet is created, it will create two Pods: postgres-0 and postgres-1. Each Pod will have associated persistent storage. If a Pod is replaced, the new Pod will have the same name and be attached to the same storage. Each of these Pods will be discoverable within the cluster using the combination of the Pod name and the serviceName. The identified Service must already exist; see below for more information about Services and see the GitHub repository for the full example. 

The env field is one way to provide environment variables to containers. The PGDATA variable tells PostgreSQL where to store its database files, so we ensure those are placed on the persistent volume. We also specify the POSTGRES_PASSWORD directly; in a production application, we would use a Secret as described below. 

The StatefulSet is one important resource needed to deploy a highly available PostgreSQL database server. We also need a way for the instances to find each other and a way to configure clients to find the current primary instance. In the application example below, we use a Kubernetes Operator to accomplish this automatically. 

Service 

A Service provides network load balancing to a group of Pods. Every time Kubernetes creates a Pod, it assigns it a unique IP address. When a Pod is replaced, the new Pod receives a new IP. By declaring a Service, we can provide a single point of entry for all the Pods in a Deployment. This single point of entry (hostname and IP address) remains valid as Pods come and go. The Kubernetes cluster even provides a DNS server so that we can use Service names as regular hostnames. Here is the Service that matches our NGINX Deployment above:   

YAML
 
10
1
kind: Service  
2
apiVersion: v1  
3
metadata:  
4
  name: nginx  
5
spec:  
6
  selector:  
7
    app: nginx  
8
  ports:  
9
  - protocol: TCP  
10
    port: 80 

Unlike the Deployment and StatefulSet, the Service is in the “core” API group, so we only need to specify the apiVersion as v1. Like the Deployment and StatefulSet we saw above, the Service uses the selector field to discover its Pods, so it automatically stays up to date. For this reason, Services can be created before the Pods exist; this demonstrates an important advantage of the declarative Kubernetes approach.  

Services rely on Kubernetes to provide a unique IP address and route traffic, so the way Services are configured can be different depending on how your Kubernetes installation is configured. The most common type of Service is ClusterIP, which is also the default. ClusterIP means the Service has an IP address accessible only from within the Kubernetes cluster, so exposing the Service outside the cluster requires another resource such as an Ingress.  

It’s important to know that when network traffic is sent to a Service address and port, Kubernetes uses port forwarding to route traffic to a specific Pod. Only the declared ports are forwarded, so other kinds of traffic (like ICMP ping) will not work to communicate with a Service address, even within the cluster.  

Ingress  

An Ingress is one approach for routing HTTP traffic from outside the cluster. (An alternate and more advanced approach is a service mesh such as Istio.) To use an Ingress, a cluster administrator first deploys an “ingress controller”. This is a regular Kubernetes Deployment, but it registers with the Kubernetes cluster to be notified when an Ingress resource is created, updated, or deleted. It then configures itself to route HTTP traffic based on the Ingress resources.  

The advantage of this approach is that only the ingress controller needs an IP address that is reachable from outside the cluster, simplifying configuration and potentially saving money.   

Here is the Ingress example to go with the NGINX Deployment and Service:  

YAML
 
15
1
apiVersion: networking.k8s.io/v1  
2
kind: Ingress  
3
metadata:  
4
  name: nginx  
5
spec:  
6
  rules:  
7
  - http:  
8
      paths:  
9
      - path: / 
10
        PathType: Prefix  
11
        backend:  
12
          service: 
13
            name: nginx  
14
            port:  
15
              number: 80  

This example routes all traffic in the cluster to a single Service, so it is only useful for a sandbox. In a production cluster, you can use DNS wildcards to route all hostnames in a domain to the ingress controller’s IP address, and then use host rules to route each host’s traffic to the correct application.  

PersistentVolumeClaim 

The StatefulSet example above tells Kubernetes to create and manage persistent storage for us. We can also create persistent storage directly using a PersistentVolumeClaim.  

A PersistentVolumeClaim requests Kubernetes to dynamically allocate storage from a StorageClass. The StorageClass is typically created by the administrator of the Kubernetes cluster and must already exist. Once the PersistentVolumeClaim is created, it can be attached to a Pod by declaring it in the volumes field. Kubernetes will keep the storage while the PersistentVolumeClaim exists, even if the attached Pod is deleted.  

YAML
 
10
1
kind: PersistentVolumeClaim  
2
apiVersion: v1  
3
metadata:  
4
  name: web-static-files  
5
spec:  
6
  accessModes:  
7
  - ReadWriteMany  
8
  resources:  
9
    requests:  
10
      storage: 8Gi  

For more information on the available providers for Kubernetes storage classes, and for multiple examples on configuring persistent storage, see the DZone Refcard Persistent Container Storage.  

ConfigMap and Secret  

In the StatefulSet example above, we specified environment variables directly in the env field. A better approach is to externalize the configuration into a separate ConfigMap or Secret. Both these resources work similarly and can be used to provide either environment variables or file content to containers. The major difference is that a Secret is base-64 encoded to simplify storage of binary content; additionally, as these are separate resources it is possible to configure cluster authorization separately so access to Secrets can be more limited. 

To externalize our PostgreSQL environment variables, we’ll use both a ConfigMap and a Secret. First, the ConfigMap: 

YAML
 
7
1
--- 
2
kind: ConfigMap 
3
apiVersion: v1 
4
metadata: 
5
  name: pgsql 
6
data: 
7
  PGDATA: /data/pgdata 

Then, the Secret: 

YAML
 
7
1
--- 
2
kind: Secret 
3
apiVersion: v1 
4
metadata: 
5
  name: pgsql 
6
stringData: 
7
  POSTGRES_PASSWORD: supersecret 

Besides the kind, the one difference is that we use stringData with the Secret to tell Kubernetes to do the base-64 encoding for us. 

To use these externalized environment variables, we replace the env field of the StatefulSet with the following: 

YAML
 
5
1
        envFrom: 
2
        - configMapRef: 
3
            name: pgsql 
4
        - secretRef: 
5
            name: pgsql 

Each of the key-value pairs in the ConfigMap and Secret will be turned into environment variables for the PostgreSQL server running in the container. 

Kustomize, Helm, and Operators  

Managing all of these different types of resources becomes challenging, even with the ability to externalize configuration using ConfigMap and Secret. Most application components require multiple resources for deployment and often need different configuration for different environments, such as development and production. The Kubernetes ecosystem has a rich set of tools to simplify managing resources: 

  • Kustomize is built into Kubernetes itself. It supports applying patches to a set of base YAML resource files. The base file can contain default values, while the patches tailor the deployment to a different environment. 
  • Helm is a separate tool that brings templates to YAML resource files. A set of templated resources, known as a Helm chart, can be uploaded to a registry and easily used in many different clusters, with the ability to easily tailor each individual deployment by supplying a set of variables in a YAML file. 
  • Operators are a Kubernetes design pattern rather than a single tool. An operator runs in the Kubernetes cluster and registers a CustomResourceDefinition (CRD). It watches the cluster for instances of that custom resource and updates the cluster accordingly. The example application below demonstrates the use of the “Postgres Operator” to rapidly deploy a highly available PostgreSQL database server. 
Section 4

Kubernetes Architecture

Kubernetes uses a client-server architecture, as seen here:  

Figure 1: Kubernetes Architecture   


A Kubernetes cluster is a set of physical or virtual machines and other infrastructure resources that are used to run applications. The control plane includes all the services that manage the cluster. The machines that run the containers are called nodes. The control plane services are typically run as containers on the nodes, with multiple instances running in the cluster to provide high availability; this provides an easy way to keep the control plane services up and running and simplifies updates. 

Control Plane 

The most important control plane service is kube-apiserver, the Kubernetes API server. It accepts connections from clients and provides an API to query and modify the resources running in the cluster. The API server is assisted by:  

  • etcd – A distributed key-value store used to record cluster state 
  • kube-controller-manager – A monitoring service that decides what changes to make when resources are added, changed, or removed 
  • kube-scheduler – A service that decides where to run pods based on the available nodes and their configuration 

Node 

A node is a physical or virtual machine with the necessary services to run containers. A Kubernetes cluster should have as many nodes as necessary for all the required pods. Each node runs kubelet, which receives commands to run containers and uses the container engine (e.g., containerd or CRI-O) to run them, and kube-proxy, which manages networking rules so connections to service IP addresses are correctly routed to pods.   

As shown in figure 1, each node can run multiple Pods, and each Pod can include one or more containers. All containers in a Pod are in the same network namespace, so those containers share an IP address.  

Section 5

Getting Started With Kubernetes

Now that we have learned about some key Kubernetes resources and we understand the Kubernetes architecture, we’re ready to put our knowledge into practice. We’ll need a development cluster to use for practice. We’ll control this cluster using the command-line utility kubectl. 

Creating a Development Cluster 

Running a production Kubernetes cluster is a complex job. Unless you’re deeply familiar with Kubernetes configuration, it’s best to use one of the many cloud options or distributions above. Kubernetes can run anything that can be packaged in a container, so insecure public clusters are quickly exploited for Bitcoin mining or other nefarious purposes.  

For a development environment, there are many great options, including Minikube, Microk8s, and Rancher k3s. You can install any of these on an existing machine by following their installation instructions, or even easier, you can use automation from the associated GitHub repository to create a sandbox virtual machine with k3s. The README file in the repository has all the instructions you need. 

Once you’ve set up your cluster, you will interact with it using the standard Kubernetes command-line client program kubectl. You can download kubectl from its website; it’s a single binary so there’s no installation needed. 

Kubectl 

kubectl is a command-line utility that controls the Kubernetes cluster. Commands use this format:  

 
1
kubectl [command] [type] [name] [flags]  

The parts of the command are as follows: 

  • [command] specifies the operation that needs to be performed on the resource. For example, create, get, describe, delete, or scale.  
  • [type] specifies the Kubernetes resource type, such as pod (po), service (svc), deployment (deploy), statefulset (sts), ingress (ing), or persistentvolumeclaim (pvc). Resource types are case-insensitive, and you can specify the singular, plural, or abbreviated forms.  
  • [name] specifies the name of the resource, if applicable. Names are case-sensitive. If the name is omitted, details for all resources will be displayed (for example, kubectl get pods will list all pods).  
  • [flags] specifies options for the command.  

Some examples of kubectl commands and their purpose:  

Command Purpose
kubectl apply -f nginx.yaml  
Create or update the resources specified in the YAML file. Kubernetes records the state of the resource when it was last applied so that it can figure out what changes to make.  
kubectl delete -f nginx.yml  
Delete the resources specified in the YAML file. If any resources do not exist, they are ignored.  
kubectl get pods  
List all Pods in the default namespace. See below for more information on namespaces.
kubectl describe pod nginx 
Show metadata and status for the nginx Pod. The name must match exactly.  

First, let’s use kubectl version to verify that the cluster is running as expected and we can talk to it: 

YAML
 
5
1
kubectl version 
2
[...] 
3
Client Version: version.Info{Major:"1", Minor:"24", GitVersion:"v1.24.3+k3s1",[...]  
4
Kustomize Version: v4.5.4 
5
Server Version: version.Info{Major:"1", Minor:"24", GitVersion:"v1.24.3+k3s1",[...]  

This will report the exact version in use, which is valuable in making sure you are using the correct API documentation.   

Section 6

Run Your First Container

Most of the time when using kubectl, we create YAML resource files, so we can maintain configuration in version-controlled repositories. However, we can create a simple Deployment using kubectl without using a YAML file:  

YAML
 
2
1
kubectl create deployment nginx --image=nginx  
2
deployment.apps/nginx created 

This command will create a Deployment, which will then create a Pod with one container running NGINX. We can use kubectl to get the status of the Deployment:  

YAML
 
3
1
kubectl get deploy  
2
NAME    READY   UP-TO-DATE   AVAILABLE   AGE 
3
nginx   1/1     1            1           10s 

To see the Pod that the Deployment created, run: 

YAML
 
3
1
kubectl get po  
2
NAME                  READY STATUS  ... AGE 
3
nginx-8f458dc5b-nd98w 1/1   Running ... 15s 

The reason the Pod has two sets of random characters in the name is that the Deployment has created a ReplicaSet to perform the actual Pod management, and the ReplicaSet created the Pod. 

Let’s replace this Deployment with the NGINX Deployment, Service, and Ingress from our example above, using the YAML files from the associated GitHub repository.  

From the “examples” directory: 

YAML
 
5
1
kubectl delete deploy nginx 
2
kubectl apply -f ‘nginx-*.yaml’ 
3
deployment.apps/nginx created 
4
ingress.networking.k8s.io/nginx created 
5
service/nginx created 

We now have three replicas because that is what was specified in the YAML file: 

YAML
 
3
1
kubectl get deploy 
2
NAME    READY   UP-TO-DATE   AVAILABLE   AGE 
3
nginx   3/3     3            3           89s  

Also, if you’re running in a cluster with an ingress controller, such as the k3s sandbox created by the associated GitHub repository, traffic to the ingress controller will now be routed to one of our NGINX Pods: 

HTML
 
4
1
curl http://localhost 
2
[...] 
3
    <h1>Hello, world!</h1> 
4
[...] 

Scale Applications 

Deployments can be scaled up and down from the command line as well as in the YAML file:  

YAML
 
2
1
kubectl scale --replicas=5 deploy/nginx  
2
deployment.extensions/nginx scaled  

The Kubernetes controller will then work with the scheduler to create or delete Pods as needed to achieve the requested number. This is reflected in the Deployment:  

YAML
 
3
1
kubectl get deploy 
2
NAME    READY   UP-TO-DATE   AVAILABLE   AGE 
3
nginx   5/5     5            5           4m46s 

Kubernetes also provides a HorizontalPodAutoscaler (hpa) that can dynamically scale a Deployment up or down based on current resource utilization. 

Delete Applications 

Once you are done exploring the NGINX example, you can destroy it with the delete command.  

YAML
 
​x
1
kubectl delete -f ‘nginx-*.yaml’  
2
deployment.extensions "nginx" deleted  
3
ingress.networking.k8s.io "nginx" deleted 
4
service "nginx" deleted 
5
​

Because Kubernetes monitors Pods to maintain the desired number of replicas, we must delete the Deployment to remove the application. Simply deleting a Pod managed by a Deployment will just cause Kubernetes to create another Pod.  

Section 7

Example Application

Let’s put multiple Kubernetes features together to deploy an example to-do application, written in Node.js, together with a highly available PostgreSQL database server. Here is the planned architecture:  

Figure 2: To-do Application Architecture 

As with our NGINX example, we use an Ingress to bring in HTTP traffic from outside the cluster and forward it to a Service. The Service provides a well-known name and consistent IP address for our application. The Service keeps track of our application’s Pods and routes traffic to a Pod IP address.  

The application stores the list of to-do items in a PostgreSQL database. The PostgreSQL database also has a Service to provide a well-known name and IP address for the primary database server instance. This name and IP address stays the same even if the primary instance goes down and is replaced by a secondary instance (shown with dashed lines in the figure). 

Let’s start by deploying PostgreSQL. We’ll use the Postgres Operator to simplify deploying and configuring a highly available database. First, we deploy the operator itself to the cluster: 

YAML
 
8
1
kubectl apply -k github.com/zalando/postgres-operator/manifests 
2
serviceaccount/postgres-operator created 
3
clusterrole...io/postgres-operator created 
4
clusterrole...io/postgres-pod created 
5
clusterrolebinding...io/postgres-operator created 
6
configmap/postgres-operator created 
7
service/postgres-operator created 
8
deployment.apps/postgres-operator created 

The command kubectl apply -k uses Kustomize to read the file manifests/kustomization.yaml from the Postgres Operator repository. The result is that the operator is now installed into our cluster and the Deployment is listening for a brand-new Kubernetes resource, Postgresql. 

This means we can deploy a highly available PostgreSQL database instance to our cluster by creating a PostgreSQL resource, like this one from the todo directory of the associated GitHub repository: 

 
20
1
apiVersion: "acid.zalan.do/v1" 
2
kind: postgresql 
3
metadata: 
4
  name: todo-db 
5
spec: 
6
  teamId: todo 
7
  volume: 
8
    size: 1Gi 
9
  numberOfInstances: 3 
10
  env: 
11
  - name: ALLOW_NOSSL 
12
    value: "1" 
13
  users: 
14
    todo: 
15
    - superuser 
16
    - createdb 
17
  databases: 
18
    todo: todo 
19
  postgresql: 
20
    version: "14" 

This shows the power of the Kubernetes operator design pattern. The Postgres Operator is able to register a new Postgresql resource type with the Kubernetes API server as a CustomResourceDefinition. As a result, when we apply the todo-db resource above to the cluster, the API server is able to recognize it and validate the YAML we provide.  

The Postgres Operator also registers with the Kubernetes API server to watch for new Postgresql resources. When a new resource is detected, the Postgres Operator interacts with the API server to create the necessary Service, StatefulSet, Secret, and other required resources needed for a highly available PostgreSQL database. 

The remaining components in our to-do example application are similar to what we’ve seen before. You can inspect the details for yourself in the associated GitHub repository. It’s particularly worth looking to see how the PostgreSQL database configuration and credentials are injected into the Node.js application using environment variables. The database username and password are automatically generated by the Postgres Operator, so they have to be injected from a Secret. 

Let’s create all of the resources for our to-do application. Run this command from the todo directory: 

YAML
 
5
1
kubectl apply -f '*.yaml' 
2
deployment.apps/todo created 
3
postgresql.acid.zalan.do/todo-db created 
4
ingress.networking.k8s.io/todo-ingress created 
5
service/todo created 

It may take several minutes for all of the application components to be up and running. Once they are up and running, we can inspect them using kubectl get: 

YAML
 
4
1
kubectl get deploy 
2
NAME              READY UP-TO-DATE AVAILABLE 
3
postgres-operator 1/1   1          1 
4
todo              3/3   3          3 

This shows the Deployment for our to-do application and the Deployment for the Postgres Operator. The actual PostgreSQL database needs persistent storage, so the operator does not create a Deployment; instead, it creates a StatefulSet: 

YAML
 
3
1
kubectl get sts 
2
NAME      READY   AGE 
3
todo-db   3/3     9m38s 

 Additionally, there are a number of Services created for our application: 

 
8
1
kubectl get svc 
2
NAME              TYPE        ... PORT(S) 
3
kubernetes        ClusterIP   ... 443/TCP 
4
postgres-operator ClusterIP   ... 8080/TCP 
5
todo-db           ClusterIP   ... 5432/TCP 
6
todo-db-repl      ClusterIP   ... 5432/TCP 
7
todo-db-config    ClusterIP   ... <none> 
8
todo              ClusterIP   ... 5000/TCP 

The todo service handles routing traffic to the application layer, while the todo-db service points to the current primary database instance. The Postgres operator uses the todo-db-repl and todo-db-config services to manage replication from the primary to the secondary instances. 

As before, if you’re running in a cluster with an ingress controller, such as the k3s sandbox created by the associated GitHub repository, traffic to the ingress controller will be routed to our application: 

 
4
1
curl http://localhost/todo/ 
2
[...] 
3
<title>Todo-Backend client</title> 
4
[...] 

If you’re running in the sandbox virtual machine from the associated GitHub repository, you should be able to see and use the to-do application by visiting http://localhost:48080/todo/ in your browser. 

If you’re having issues, use kubectl get to inspect all the resources created and the associated pods to see if one is failing. You can use kubectl logs to see the output from any pod.  

There are more Kubernetes resources for you to explore in this application, including the individual Pods, the PersistentVolumeClaims created by the StatefulSet, and the Secrets used to store database credentials so they can be injected into the Node.js application. Once you’ve finished exploring, you can remove the to-do application from your cluster: 

 
5
1
kubectl delete -f '*.yaml' 
2
deployment.apps "todo" deleted 
3
postgresql.acid.zalan.do "todo-db" deleted 
4
ingress...io "todo-ingress" deleted 
5
service "todo" deleted 

When we delete the PostgreSQL resource, the Postgres Operator automatically deletes the StatefulSet, Services, Secrets, and other resources it created to deploy the highly available PostgreSQL database. 

Section 8

Namespace, Resource Quotas, and Limits

Kubernetes uses namespaces to avoid name collisions, control access, and set quotas. When we created resources above, we didn’t specify a namespace, so they all went into the default namespace. Other resources that are part of the cluster infrastructure are in the namespace kube-system.  

To see Pods in kube-system, we can run:  

Shell
 
5
1
$ kubectl get po -n kube-system  
2
NAME                       READY   STATUS 
3
...  
4
coredns-7944c66d8d-rmxnr   1/1     Running 
5
...  

Resource Isolation 

A new Namespace can be created from a YAML resource definition:  

YAML
 
6
1
apiVersion: v1  
2
kind: Namespace  
3
metadata:  
4
  name: development  
5
  labels:  
6
    name: development  

Once we’ve created the Namespace, we can create resources in it using the --namespace (-n) flag, or by specifying the namespace in the resource’s metadata:  

YAML
 
6
1
apiVersion: v1  
2
kind: Pod  
3
metadata:  
4
  name: webserver  
5
  namespace: development  
6
...  

By using separate namespaces, we can have many Pods named webserver and not have to worry about name collisions. Also, Kubernetes DNS works with namespaces. Simple host names look for Services in the current namespace, but we can use the full name for Services in other namespaces. For example, we could find the PostgreSQL database we created for our to-do application by using todo-db.default.svc from anywhere in the cluster. 

Access Control 

Kubernetes supports Role Based Access Control (RBAC).   

Here’s an example that limits developers to read-only access for Pods in the production namespace. First, we create a ClusterRole, a common set of permissions we can apply to any namespace:  

YAML
 
8
1
kind: ClusterRole  
2
apiVersion: rbac.authorization.k8s.io/v1  
3
metadata:  
4
  name: pod-read-only  
5
rules: 
6
- apiGroups: [""] # "" is the core API group  
7
  resources: ["pods"]  
8
  verbs: ["get", "watch", "list"]  

Next, we use a RoleBinding to give members of the developers group permissions in the production namespace:  

YAML
 
13
1
kind: RoleBinding  
2
apiVersion: rbac.authorization.k8s.io/v1  
3
metadata:  
4
  name: read-only  
5
  namespace: production  
6
subjects:  
7
- kind: Group  
8
  name: developers  
9
  apiGroup: rbac.authorization.k8s.io  
10
roleRef:  
11
  kind: ClusterRole  
12
  name: pod-read-only  
13
  apiGroup: rbac.authorization.k8s.io  

Alternatively, we can use a ClusterRoleBinding to apply a role to a user or group in all namespaces.  

Resource Quotas 

By default, Pods have unlimited resources, but they receive resources on a “best effort” basis. To control the number of Pods in a namespace or to limit the resources Pods in a namespace can use, we can apply a ResourceQuota to a namespace:  

YAML
 
9
1
apiVersion: v1  
2
kind: ResourceQuota  
3
metadata:  
4
  name: compute-resources  
5
  namespace: sandbox  
6
spec:  
7
  hard:  
8
    cpu: "5"  
9
    memory: 10Gi  

Kubernetes will now reject Pods in this namespace unless we apply a limit:  

YAML
 
13
1
apiVersion: v1  
2
kind: Pod  
3
metadata:  
4
  name: webserver  
5
  namespace: sandbox  
6
spec:  
7
  containers:  
8
  - image: nginx  
9
    name: nginx  
10
    resources:  
11
      limits:  
12
        memory: "128Mi"  
13
        cpu: "500m"  

Note that we can request fractions of a CPU and use varying units for memory. Pods that specify limits receive a higher-priority quality of service class compared to “best effort” Pods, so it is good practice to specify limits wherever possible. 

Section 9

Conclusion

Kubernetes is the most popular container orchestration framework. It is a powerful and reliable way to run containerized applications in production, providing reliability and scalability. This Refcard has shown a few of the most important resource types in Kubernetes to help you get started deploying applications, and with what you’ve learned here, you can start exploring all that Kubernetes has to offer.  

Like This Refcard? Read More From DZone

related article thumbnail

DZone Article

How to Convert XLS to XLSX in Java
related article thumbnail

DZone Article

Automatic Code Transformation With OpenRewrite
related article thumbnail

DZone Article

Accelerating AI Inference With TensorRT
related article thumbnail

DZone Article

A Complete Guide to Modern AI Developer Tools
related refcard thumbnail

Free DZone Refcard

Kubernetes Monitoring Essentials
related refcard thumbnail

Free DZone Refcard

Kubernetes Multi-Cluster Management and Governance
related refcard thumbnail

Free DZone Refcard

Getting Started With Kubernetes
related refcard thumbnail

Free DZone Refcard

Getting Started With Serverless Application Architecture

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: