Understanding Key Kubernetes Objects

Written by: Bagus Facsi Aginsa
Published at: 22 Nov 2024


Kubernetes, often abbreviated as K8s, is an open-source platform designed to automate the deployment, scaling, and operation of container applications across clusters of hosts. As the most popular container orchestration tool in the world, Kubernetes simplifies the management of complex containerized applications.

To truly harness the power of Kubernetes, it’s essential to understand its fundamental objects: Pods, Services, Deployments, Ingress, ConfigMaps, and Secrets. These objects form the backbone of Kubernetes operations, enabling developers to build, scale, and manage applications efficiently.

Understanding Pods

At the heart of Kubernetes is the concept of a Pod. A Pod is the smallest and simplest Kubernetes object, representing a single instance of a running process in a cluster. Pods encapsulate one or more containers (such as Docker containers), along with shared storage/network resources and a specification for how to run the containers. The relationship between containers in a pod is almost similar to containers in a VM.

Key Features of Pods:

  1. Single Unit of Deployment: While you can deploy individual containers, Kubernetes wraps them into Pods, making Pods the atomic unit of deployment. This approach allows Kubernetes to handle more complex applications that might require multiple containers working together.
  2. Shared Namespace: Containers within a Pod share the same network namespace, including IP addresses and ports. They can communicate with each other using localhost and share storage volumes.
  3. Lifecycle Management: Kubernetes manages the lifecycle of Pods, ensuring that the desired state (as specified by the developer) is maintained. If a Pod fails or is deleted, Kubernetes will recreate it to meet the desired state.

This is an example of Pods YAML definitions:

apiVersion: v1 
kind: Pod 
metadata: 
  name: nginx-pod 
  labels: 
    app: nginx-app
spec: 
  containers: 
  - name: nginx-container
    image: nginx:latest 
    ports: 
    - containerPort: 80
    resources: 
      requests: 
        memory: "64Mi" 
        cpu: "250m" 
      limits: 
        memory: "128Mi" 
        cpu: "500m"
  - name: sidecar-container
    image: busybox 
    command: ['sh', '-c', 'echo Hello Kubernetes! > /usr/share/nginx/html/index.html && sleep 3600']
    resources: 
      requests: 
        memory: "64Mi" 
        cpu: "125m" 
      limits: 
        memory: "128Mi" 
        cpu: "250m"

This is the example of Pods definition that contains 2 containers inside: nginx-container & sidecar-container.

Although it is not mandatory to define resources, it is recommended to define it so the scheduler.

Understanding Services

Why are Services needed? Because each time a Pod is created or destroyed, it receives a new IP address. This abstraction decouples clients from Pods, enabling Kubernetes to load balance across Pods and improve resilience.

There are 4 Types of Services:

  1. ClusterIP: Exposes the Service on a cluster-internal IP. This type is the default and is used for internal communication within the cluster. This type of service cannot be accessed from outside of the cluster.
  2. NodePort: Exposes the Service on each Node’s IP at a static port. This allows external access to the Service, though it’s less flexible than other types. The allowable port range for NodePort configuration is 30000 to 32767.
  3. LoadBalancer: Exposes the service externally by using a cloud provider’s load balancer. This type is commonly used in cloud environments to manage incoming traffic and can only be activated in a cloud environment such as AWS, Azure, GCP, etc.
  4. ExternalName: Maps a Service to a DNS name, allowing for external services to be accessed through Kubernetes.

This is the example of Services YAML definition:

apiVersion: v1 
kind: Service 
metadata: 
  name: nginx-service
spec: 
  type: NodePort 
  selector: 
    app: nginx-app
    ports: 
    - protocol: TCP 
      port: 80 
      targetPort: 80 
      nodePort: 30080

Notice the highlighted text. It indicates that we are assigning this service to Pods with the app: nginx-app label. And yes, this means we are assigning this service to the Pod we showed you earlier.

Understanding Deployments

Deployments are a higher-level Kubernetes object that provides declarative updates to applications. They manage the creation and scaling of Pods, ensuring that a specified number of Pods are running and up-to-date.

Key Features of Deployments:

  1. Rolling Updates: Deployments support rolling updates to gradually replace instances of the application with new versions without downtime.
  2. Rollback: If something goes wrong during an update, Deployments can roll back to a previous state, ensuring stability.
  3. Scaling: Easily scale the number of replicas (Pods) up or down to handle varying loads.

This is the example of the Deployment YAML definition:

apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: nginx-deployment 
  labels: 
    app: nginx-app 
spec: 
  replicas: 3 
  strategy: 
    type: RollingUpdate 
    rollingUpdate: 
      maxSurge: 1 
      maxUnavailable: 1 
  selector: 
    matchLabels: 
      app: nginx-app 
  template: 
    metadata: 
      name: nginx-pod 
      labels: 
        app: nginx-app
    spec: 
      containers: 
      - name: nginx-container
        image: nginx:latest 
        ports: 
        - containerPort: 80
        resources: 
          requests: 
            memory: "64Mi" 
            cpu: "250m" 
          limits: 
            memory: "128Mi" 
            cpu: "500m"
      - name: sidecar-container
        image: busybox 
        command: ['sh', '-c', 'echo Hello Kubernetes! > /usr/share/nginx/html/index.html && sleep 3600']
        resources: 
          requests: 
            memory: "64Mi" 
            cpu: "250m" 
          limits: 
            memory: "128Mi" 
            cpu: "500m"

If you notice, the highlighted part is the same as the metadata and spec from the Pods we showed you earlier. I want to show you that when we create a deployment, it automatically creates a Pod based on the spec.template defined on the yaml.

When running in production, it is recommended to create a Deployment instead of a Pod. A Deployment allows you to manage the number of Pods you want to create using the spec.replicas and control the rollout strategy via spec.strategy. The rollout strategy ensures that you can update your application without causing downtime.

Understanding Ingress

Ingress is an object that manages external access to services within a Kubernetes cluster, typically handling HTTP/HTTPS traffic. It provides features such as load balancing, SSL termination, and name-based virtual hosting.

While it is somewhat similar to NodePort services, Ingress is more powerful. You might wonder why not just use a service. Here are the key benefits of Ingress that might answer this question:

  1. Centralized Management: Ingress simplifies the management of multiple services by providing a single entry point.
  2. Advanced Routing: Supports complex routing rules, such as path-based and host-based routing.
  3. SSL Termination: Handles SSL/TLS termination, offloading this task from individual services.

By using Ingress, we add an extra layer of reverse proxy in our application. However, if you really need any of the three features mentioned above, then you definitely need Ingress because a service alone does not support these features.

This is the example of Ingress YAML definition:

apiVersion: networking.k8s.io/v1 
kind: Ingress 
metadata: 
  name: app-ingress 
  annotations: 
    nginx.ingress.kubernetes.io/rewrite-target: /
spec: 
  ingressClassName: nginx
  rules: 
  - host: test.facsiaginsa.com 
    http: 
      paths: 
      - path: / 
        pathType: Prefix 
        backend: 
          service: 
            name: nginx-service
            port: 
              number: 80

Notice that the service we used, is the service we introduced to you earlier. The name and port number should match the service configuration. Note that the highlighted port above matches the spec.selector.ports[].port on the service yaml configuration, not the spec.selector.ports[].targetPort, not the spec.selector.ports[].nodePort.

Understanding ConfigMaps

ConfigMaps allow you to decouple configuration artifacts from image content, making applications more portable. ConfigMaps store configuration data in key-value pairs and can be injected into Pods as environment variables, command-line arguments, or configuration files.

Use Cases for ConfigMaps:

  1. Store environment-specific configuration values.
  2. Manage application settings that can change between deployments.
  3. Separate configuration data from the application’s container image.

This is the example of Configmap YAML definition:

apiVersion: v1 
kind: ConfigMap 
metadata: 
  name: app-configmap
data: 
  HOST: "0.0.0.0"
  PORT: "3000"
  BUCKET: "images"

This is the example of a Deployment YAML definition that uses the Configmap as an environment:

apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: nginx-deployment 
  labels: 
    app: nginx-app 
spec: 
  replicas: 3 
  strategy: 
    type: RollingUpdate 
    rollingUpdate: 
      maxSurge: 1 
      maxUnavailable: 1 
  selector: 
    matchLabels: 
      app: nginx-app 
  template: 
    metadata: 
      name: nginx-pod 
      labels: 
        app: nginx-app
    spec: 
      containers: 
      - name: nginx-container
        image: nginx:latest 
        ports: 
        - containerPort: 80
        resources: 
          requests: 
            memory: "64Mi" 
            cpu: "250m" 
          limits: 
            memory: "128Mi" 
            cpu: "500m"
        envFrom:
        - configMapRef:
            name: app-configmap

Notice the highlighted text should match the name of your configmap.

Understanding Secrets

Secrets are similar to ConfigMaps but are specifically designed to store sensitive information, such as passwords, tokens, and keys. Secrets are encoded in base64 and can be mounted into Pods as files or exposed as environment variables.

Key Features of Secrets:

  1. Security: Secrets are intended to be more secure than plain ConfigMaps. They should be handled carefully and have restricted access.
  2. Decoupling: Like ConfigMaps, Secrets decouples sensitive data from the application code, enhancing security and flexibility.

This is some example of the Secret YAML definition:

apiVersion: v1 
kind: Secret 
metadata: 
  name: app-secret 
type: Opaque 
data: 
  key1: dmFsdWUx # base64 encoded value of 'value1' 
  key2: dmFsdWUy # base64 encoded value of 'value2'

---
apiVersion: v1 
kind: Secret 
metadata: 
  name: tls-secret 
type: kubernetes.io/tls 
data: 
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FUR... 
  tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVkt...

---
apiVersion: v1 
kind: Secret 
metadata: 
  name: docker-app-secret
type: kubernetes.io/dockerconfigjson 
data: 
  .dockerconfigjson: eyJhdXRocyI6IHsiaHR0cHM6Ly9pbmRleC5kb2NrZXIuaW8vd...

These 3 are the most used secret type for you as a developer. To learn how to generate this 3 secret, you can see my tutorial: How to Create Kubernetes Secrets Imperatively: A Developer’s Guide

The Opaque and kubernetes.io/dockerconfigjsonare usually used in the Deployment yaml definition. Opaque type secret is usually used for environment variables that are sensitive like Database Password, API Token, JWT Secret, etc, while kubernetes.io/dockerconfigjsonis used if you need to log in when you pull your images from a private registry.

Let’s see how we can implement Deployment that using a secret:

apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: nginx-deployment 
  labels: 
    app: nginx-app 
spec: 
  replicas: 3 
  strategy: 
    type: RollingUpdate 
    rollingUpdate: 
      maxSurge: 1 
      maxUnavailable: 1 
  selector: 
    matchLabels: 
      app: nginx-app 
  template: 
    metadata: 
      name: nginx-pod 
      labels: 
        app: nginx-app
    spec: 
      containers: 
      - name: nginx-container
        image: facsiaginsa.com/images/nginx:latest 
        ports: 
        - containerPort: 80
        resources: 
          requests: 
            memory: "64Mi" 
            cpu: "250m" 
          limits: 
            memory: "128Mi" 
            cpu: "500m"
        envFrom:
        - configMapRef:
            name: app-configmap
        - secretRef: 
            name: app-secret
        imagePullSecrets: 
        - name: docker-app-secret

kubernetes.io/tls type secret is usually used in Ingress yaml definition. It contains the tls certificate & key in a base64 format. Let’s see how we can implement Ingress that using a secret:

apiVersion: networking.k8s.io/v1 
kind: Ingress 
metadata: 
  name: app-ingress 
  annotations: 
    nginx.ingress.kubernetes.io/rewrite-target: /
spec: 
  ingressClassName: nginx
  tls: 
  - hosts: 
    - test.facsiaginsa.com 
    secretName: tls-secret 
  rules: 
  - host: test.facsiaginsa.com 
    http: 
      paths: 
      - path: / 
        pathType: Prefix 
        backend: 
          service: 
            name: nginx-service
            port: 
              number: 80

Conclusion

Understanding Kubernetes objects such as Pods, Services, Deployments, Ingress, ConfigMaps, and Secrets, is fundamental to effectively utilizing Kubernetes. These objects provide the building blocks for managing containerized applications, offering powerful abstractions that simplify deployment, scaling, and management.

By mastering these concepts, developers and operations teams can harness Kubernetes to build resilient, scalable, and manageable applications, ensuring that they can meet the demands of modern software development.

I hope this comprehensive overview of Kubernetes fundamentals helps you on your journey to mastering container orchestration. Happy coding!