Kubernetes — Container as a Service

Quick Reference

Kubernetes
Containers
Published

December 4, 2024

Modified

February 18, 2025

Overview

Kubernetes aka K8s …manage containerized workloads and services

  • …supports declarative configuration …automates deployment
  • …runs on virtual and physical environments …on-premise & public clouds
  • …reference infrastructure for cloud-native1˒2 applications
  • Benefits of the Kubernetes architecture…
    • service discovery …networks, devices, service, metadata, etc
    • storage orchestration …decouple storage allocation from back-ends
    • …container run-time agnostic …use any OCI compliant container engine
    • …provider agnostic …no vendor lock-in to a specific IaaS
    • …productivity …enables fast deployment CI/CD & DevOps

What can be difficult with Kubernetes?

  • Hard multi-tenancy…
    • …options for cluster-level sharing are available
    • …however difficult where the tenants do not trust each other
    • …typically each tenant is hosted a dedicated cluster instance
  • Most deployments are virtual …and run on a cloud infrastructure
    • …many use-cases & examples specific to cloud providers
    • …bare-metal deployment not widely used …hence more challenging to build

Architecture

# display cluster information
kubectl cluster-info

# list cluster nodes
kubectl get nodes

# additional details about a node
kubectl describe node $node_name

Control plane — Manages worker nodes & pods in the cluster

  • …usually runs distributed over multiple nodes (fault-tolerance, highly-available)
  • Control plane components …global decisions about the cluster:
    • API server
      • …core component server …exposes the Kubernetes API
      • …runs several instances of kube-apiserver to scale horizontally
    • Cluster Storage
      • …consistent and highly-available key value store
      • etcd3 …uses Raft consensus algorithm
      • …data is consistently replicated across multiple nodes
      • …supports built-in snapshots for backup
    • Scheduler
      • kube-scheduler …basically assigns pods to nodes
      • …monitors cluster resources …uses various policies for placement
    • Controllers …manage the cluster state
      • kube-controller-manager …runs individual controller processes
      • …different controllers for nodes, jobs, services, etc.

Worker nodes — Host pods that house containerized applications

  • …provide the Kubernetes run-time environment
  • Components that run on every node:
    • kubelet4 …primary “node agent”
      • …works in terms of a PodSpec …YAML/JSON object describing a pod
      • …ensures that pods are running and healthy
    • kube-proxy (optional) …connects pods with a network
      • …uses the OS packet filter …otherwise forwards the traffic itself
      • …other network plugins implement CNI (Container Network Interface)
    • Container run-time …Kubernetes CRI (Container Runtime Interface)

Objects

Objects — Specific configurations that define the desired state

  • “Configuration that describes what you want to run”
  • After an object is created…
    • …it represents a specific entity with its own configuration and purpose
    • …system will constantly work to ensure that the object exists
  • An object includes two nested object fields:
    • …object spec describes the desired state for the object
    • …object status describes the actual state of the object
# list supported resources
kubectl api-resources               

Resources — Components that can be managed within the cluster

  • Generally refers to quantifiable units of compute, storage, and networking
  • Resources include the object themselves and underlying hardware used
  • “Not all resources are necessarily objects in the same sense”

Manifests

Manifests — Files that describe what to deploy

  • Deployments created by posting a manifest to the Kubernetes API
  • Why use Kubernetes manifest files?
    • Declarative configuration …predictable correct state
    • Prevents configuration drift by re-applying manifest
    • Configuration as code …maintained with version control system
    • Allows structured collaboration with co-workers
  • Format described in the Kubernetes API5
# discover possible API object fields
kubectl explain $object               # describe associated fields
kubectl explain $object --recursive   # list all possible fields and subfields
kubectl explain $object.metadata      # get details on fields

Main parts of a manifest:

  • kind — Object type
  • metadata — Name, namespace & labels
  • spec — Containers, volumes, etc.
# example for a pod object
apiVersion: apps/v1              # Kubernetes API version
kind: Pod                        # Type of resource
metadata:
  name: nginx                    # Name of the resource
spec:
  containers:
    - name: nginx                # Name of the container
      image: nginx:latest        # Image to create the container from

Usage

Install the kubectl binary to $HOME/bin:

ver=$(curl -L -s https://dl.k8s.io/release/stable.txt)
url=https://dl.k8s.io/release/$ver/bin/linux/amd64/kubectl
curl -Lo ~/bin/kubectl $url && chmod +x ~/bin/kubectl
# load tab-completion
source <(kubectl completion bash)

Custom configuration file:

export KUBECONFIG=$(realpath kubeconfig.yml)
# check your current context
kubectl config view
# check resource access
kubectl auth can-i get pods

kubectl run

# start an interactive container…
kubectl run $pod_name --image=$image --restart=Never --stdin --tty
kubectl delete pods $pod_name

# specific shell
kubectl run $pod_name --image=$image --restart=Never -it --rm -- /bin/bash

# specific application (Perl in this example)
kubectl run $pod_name --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'

kubectl run6 common options:

  • --image= — which container image to use
  • -it aka --stdin & --tty — spawn an interactive shell
    • …allocate a terminal for each container
    • …keep stdin open (even if empty)
  • Clean up…
    • --restart=Never — pod should never be restarted
    • --rm — automatically remove pod after exit

kubectl exec

# execute a command in a specified pod
kubectl exec $pod_name -- curl -s https://10.20.30.40

# specify a container
kubectl exec $pod_name -c $container_name #...

# start an interactive shell
kubectl exec -it my-pod -- /bin/bash

Use cases…

  • …debugging & diagnostics …for example check logs, inspect files
  • …modify configuration files & environment variables
  • …run test scripts …validate application behavior
# copy file to & from containers
kubectl cp /local/path  $pod_name:/path/in/container
kubectl cp $pod_name:/path/in/container /local/path

kubectl logs

# show logs entires from the last rotation
kubectl logs $pod_name

# show logs for a specific container
kubectl logs $pod_name -c $container_name

Containerized application log to stdout/stderr…

  • …automatically rotated daily …every time the logs reaches 10MB
  • …logs deleted when the pod is deleted

kubectl apply

Imperative commands — Fail if a resource exists already

# run an instance of the nginx container by creating a deployment object
kubectl create deployment nginx --image nginx

# create the objects defined in a configuration file
kubectl create -f deployment.yaml

# update the objects defined in a configuration file
# note: dropping all changes to the object missing from the configuration file
kubectl replace -f deployment.yaml

Declarative commands — Create new or modify existing objects

  • …create, update, and delete operations are automatically detected per-object
  • …retains changes, even if the changes are not in the object configuration file
# apply changes
kubectl apply -f nginx/
# see what changes are going to be made
kubectl diff -f nginx/
# use option -R for recursive directory decent

Pods

# list pods
kubectl get pods -o wide

# detailed information
kubectl describe pod $pod_name

Pod — Smallest deployable compute object in Kubernetes…

  • Group of one or more tightly related container
  • Container in a pod will always run together…
    • …on the same worker node …same Linux namespace
    • …“running on the same logical machine”

Pod life-cycle

  • Pending — ready for scheduling …not started yet
  • Running — Pod bound to a node …containers created
  • Succeeded — Pod containers terminated in success (no restart)
  • Failed — Container terminated in failure (non-zero status)
  • Unknown — Pod state could not be obtained
  • Container states inside a pod…
    • Waiting — Running operations to complete startup
    • Running — Container is executing without issues
    • Terminated — Container ran to completion or failed for some reason

Command & Arguments

spec:
  containers:
  - name: #…
    command: ["/bin/sh", "-c"]
    args: ["echo 'Hello, World!' && sleep 3600"]
spec:
  containers:
  - name: #…
    command: ["/bin/sh", "-c"]
    args:
    - |
      echo "Hello World!"
      sleep 3600

Define commands & arguments for a container in a pod:

  • …override default from the container image
  • …if only arguments are defined the default command is used
  • command — Command to run when the container starts
    • …corresponds to an entrypoint in a Dockerfile
    • args — Arguments to pass to the command
# define an argument by environment variable
spec:
  containers:
  - name: #…
  env:
  - name: MESSAGE
    value: "hello world"
  command: ["/bin/echo"]
  args: ["$(MESSAGE)"]   # variable expansion

Namespaces

Mechanism for isolating groups of resources within a single cluster

  • …scoping is applicable only for namespaced objects
  • …divide cluster resources between multiple users (via resource quota)
  • Namespaces default for new clusters without first creating a namespace
# list current namespaces
kubectl get namespace

# set the namespace for a current request
kubectl <...> --namespace=$name

# set namespace preference
kubectl config set-context --current --namespace=$name
# ...to unset
kubectl config set-context --current --namespace='' 

Labels & Annotations

Labels are key/value pairs to specify identifying attributes of objects

  • …used to organize and to select subsets of objects
  • …each key must be unique for a given object
  • …common set of labels allows tools to work interoperable
  • …labels without a prefix are private to users

Labels are defined in the metadata.labels object:

apiVersion: v1
kind: Pod
metadata:
  # ...
  labels:
    environment: prod
    name: mariadb
    component: database
    part-of: slurm
#...
# list objects with a given label
kubectl get <...> -l 'component=database,part-of=slurm'
kubectl get <...> -l 'environment in (prod,dev)'

Po# list all labels of an object
kubectl get <...> -o json | jq .metadata.labels

Shared labels/annotations have a common prefix <prefix>/<name>

  • <prefix> (optional) needs to be a valid DNS subdomain
  • <name> arbitrary property name of the label

Annotations attach arbitrary non-identifying metadata to objects

Secrets

# create a secret
kubectl create secret generic project-secret \
        --from-literal=username=alice --from-literal=password=abc123
spec:
  containers:
    env:
    - name: USERNAME
      valueFrom:
        secretKeyRef:
          name: project-secret
          key: username
    - name: PASSWORD
      valueFrom:
        secretKeyRef:
          name: project-secret
          key: password

Store & manage sensitive information like passwords, SSH keys, OAuth tokens

  • Ensure secret information is not exposed in configuration files
  • Enables processes to regularity rotate secrets

Secrets are stored in the etcd database…

  • …in a Base64-encoded format …basically exposed on access to the database
  • Encryption at Rest — Encryption data before storing to the database

TODO…

Services

# list all services in a cluster
kubectl get service

# create a load-balancer service object 
kubectl expose $object $name --type=LoadBalancer --name=$name

# remove a service object
kubectl delete service $name

Services — Constant point of entry to a group of pods

  • Pods get dedicated internal IP addresses…
  • …Pods are not accessible from outside by default
  • Why using a service?
    • Route external connections to pods backing a service
    • Clients do not need to know individual pods
    • Single constant IP-address for a service

Pods need to be exposed through a service object:

  • ClusterIP — Reserve a static virtual IP address
    • Accessible only within the cluster
    • Internal communication between services
    • Load-balanced within the cluster
  • NodePort — Forward traffic to specific port
    • Accessible from outside the cluster via node IP address
    • Each node forwards traffic to a specific port
  • LoadBalancer — External load balancer with public IP
    • Accessible from outside via load balancer IP address
    • For production …distributes traffic over nodes
# forward a local network port to a port in the pod
kubectl port-forward $pod_name $local_port:$pod_port

Connect to a specific pod without going through a service…

  • …typically for debugging & testing individual pods
  • …notation is local port, colon followed by port in the pod

ClusterIP

Service object to assign an IP address to an application (the cluster IP):

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80           # service endpoint port
      targetPort: 9376   # target port on the application pods

Load Balancer

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: LoadBalancer
  selector:
    app: my-app          # identify pods for this service
  ports:
    - port: 8080         # service port
      targetPort: 8080   # foward to container port

Configure session affinity for a service…

  • …clients are redirected to the same port every time
  • …defaults to None if not specified
spec:
  sessionsAffinity: ClientIP

Pods backing a service can not see the actual client IP-address

  • …packets source IP changed for cluster internal routing
  • …SNAT (Source Network Access Translation) performed on each package

Ingress

Ingress — Access for HTTP/S traffic to the cluster

  • Operates on the application layer of the network stack (HTTP)
  • Why use ingress?
    • Path- & Host-based routing …typically an URL path
    • Multiple services can share a single IP-address
    • Manage SSL certificates and terminate SSL connections
    • Manage authentication and authorization
  • Ingress controllers
    • NGINX Ingress Controller
    • Traefik …HTTP reverse proxy and load balance
    • HAProxy Ingress

Containers

Kubernetes supports various container patterns

Container Pattern Description
Main Core functionality of a pod
InitContainer Run before the main application container
SideCar Run alongside the main application container
Job One-off tasks …runs until completion
CronJob One-time Jobs …repeating schedule
ReplicaSet Maintain a stable set of pods
Deployment Stateless application workload
StatefulSet Persistent storage & unique network identity
DaemonSet Local to specific nodes
Ambassador Communication proxy for external services
Adapter Transform formats/protocols to ensure compatibility

Jobs

job_name=hello
cat > $job_name.yml <<EOF
apiVersion: batch/v1
kind: Job
metadata:
  name: $job_name
spec:
  template:
    spec:
      containers:
      - name: hello
        image: busybox
        command: ["echo", "Hello, World!"]
      restartPolicy: OnFailure
EOF

# create job (if not existing)
kubectl create -f $job_name.yml

# inspect job state
kubectl get jobs $job_name
kubectl describe jobs $job_name

# print the logs
kubectl logs job/$job_name

# clean up
kubectl delete job $job_name

Run the parallel job example:

job_name=sleep-parallel
cat > $job_name.yml <<EOF 
apiVersion: batch/v1
kind: Job
metadata:
  name: $job_name
spec:
  completions: 6
  parallelism: 2
  template:
    spec:
      containers:
      - name: sleep
        image: busybox
        command: ["sleep",  "60"]
      restartPolicy: Never
EOF
kubectl create -f $job_name.yml

# watch the status of pods created
kubectl get -w pods -l job-name=$job_name

# clean up
kubectl delete -f $job_name.yml
  • completions number of pods to complete
  • parallelism number of pods to run in parallel

Cronjob

Run jobs on a scheduled interval

apiVersion: batch/v1
kind: CronJob
metadata:
  name: project-crontjob
spec:
  schedule: "*/1 * * * *"       # Cron format for the schedule
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            command: ["echo", "Hello, World!"]
          restartPolicy: OnFailure

Each CronJob creates a Job …each Job runs a single Pod

# list cronjobs
kubectl get cronjobs $name

# list the jobs
kubectl get jobs --watch

# remove a cronjob
kubectl delete cronjob $name

The job name is different from the pod name

# list pods for a given job
kubectl get pods --selector=job-name=$name --output=jsonpath={.items[*].metadata.name}

Manually start a cron job… --from=crontjob/ to specify the CronJob to use as template:

kubectl create job --from=cronjob/$cronjob_name $job_name

ReplicaSet

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: my-replicaset
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-container
        image: my-image:latest

Manage a number of Pod replicas and their life-cycle

  • Monitor health & automatically re-create pods on failure
  • Features: scaling, self-healing, label selector, rolling updates
  • Important fields:
    • replicas — Number of pods
    • selector — How to identify pods belonging to the ReplicaSet
    • template — Describe the pods

Add additional expressions to the selector:

# more expressive label selector
selector:
  matchExpression:
    - key: app
      operator: In
      values:
      - my-app
  • Valid operators…
    • In …labels value must match
    • NotIn …labels value must not match
    • Exists …pod must include label with key
    • DoesNotExist …pod must not include label with key
  • Multiple expressions must all evaluate to true
  • Possible to combine matchLabels with matchExpressions

Deployment

Deployments7 provide rollback functionality and update control

Download nginx-deployment.yaml Kubernetes example:

kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
# inspect the deployment
kubectl get deployments

# see the ReplicaSet created by the Deployment
kubectl get kubectl get replicaset

# list the pods (using a label)
kubectl get pods -l app=nginx

Rolling update incremental replacement of multiple pods

  • …no downtime …network traffic load-balanced to available pods
  • …facilitates CI/CD deployments …support rollback to previous version
# update pods to a new container version
kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1

# check the revisions of this Deployment
kubectl rollout history deployment/nginx-deployment

# undo the current rollout and rollback to the previous revision
kubectl rollout undo deployment/nginx-deployment

Scaling an application on demand…

  • …increase the number of pods to a desired state
  • …supports (horizontal) autoscaling depending to load
# scale a deployment
kubectl scale deployment/nginx-deployment --replicas=5

# remove a deployment
kubectl delete deployment nginx-deployment

Init Container

Example: Use an initContainer to clone a Git repository:

spec:
  volumes:
  - name: repo
    emptyDir: {}
  initContainers:
  - name: git-clone
    image: alpine/git
    env:
    - name: GITLAB_TOKEN
      valueFrom:
        secretKeyRef:
          key: gitlab-token
          name: project-secrets
    args:
    - 'clone'
    - 'https://none:$(GITLAB_TOKEN)@example.org/path/to/repo.git'
    -  '/srv/repo'
    volumeMounts:
      - name: repo
        mountPath: /srv

Run before the main application container

  • Why — Perform initialization tasks
  • Sequential execution…
    • …multiple init containers run in sequence
    • …containers must run successful before the next starts
    • …failing init containers restarted until success
  • Main application container starts after success of all init containers

Sidecar

Run alongside the main application container

  • Why use a sidecar container?
    • Collect monitoring metrics & health information
    • Proxy and network communication
    • Log forwarding …update of service components
  • Co-location — Share resources with the main container
    • Stop/start alongside the main container

Storage

Commands to overview storage:

# list persistent volumes (pv)
kubectl get pv

# list persistent volume claims (pvc)
kubectl get pvc

# list storage classes (sc)
kubectl get sc

Long-term & temporary storage for pods…

  • Container storage is ephemeral (aka temporary)…
  • …data created/modified during lifetime is lost
  • …restart boots a container with clean state

Volumes8 (directory accessible to the container)

  • …many types of volumes …pods can use multiple types simultaneously
  • …mount at the specified paths within the container
  • Ephemeral volumes …have the lifetime of a pod
  • Persistent volumes …exist beyond the lifetime of a pod

Volume Types

Commonly used…

  • emptyDir — Simple empty directory …for transient data
  • hostPath — Directory from the work node
  • nfs — Mount of an NFS share
  • configMap — Non-confidential configuration …key-value pairs
  • secret — Passwords, Oath tokens, SSH keys, etc.
  • downwardAPI — Access pod metadata & cluster context
  • persistantVolumeClaim — Use pre- & dynamic provisioned persistent storage

Mount other network storage for example cephfs, rbd, etc.

Common Fields

Common fields for persistent volumes:

spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 10Gi
  persistentVolumeReclaimPolicy: Retain
  • accessModes supports the following access modes
    • ReadWriteOnce — read-write by a single pod
    • ReadOnlyMany — read-only by multiple pods
    • ReadWriteMany — read-write by multiple pods
  • capacity.storage — Valid suffix Mi,Gi,Ti
  • persistentVolumeReclaimPolicy what happens if claim is released
    • Retain — PV will not be deleted …remain in the Released state
    • Delete — Storage resource will be deleted automatically

EmptyDir

An emptyDir volume is created when a pod is assigned to a node…

  • …on storage local to the worker node
  • …deleted when the pod is removed …safe across container crashes
  • Why use an emptydir?
    • User for temporary scratch space
    • Share files between container running in the same pod
apiVersion: v1
kind: Pod
metadata:
  name: test-emptydir
spec:
  volumes:
    - name: opt
      emptyDir: {}
  containers:
    - name: container-one
      volumeMounts:
        - name: opt
          mountPath: /opt
      image: alpine
      command: ["/bin/sh"]
      args: ["-c", "sleep 10000"]
    - name: container-two
      volumeMounts:
        - name: opt
          mountPath: /opt
      image: alpine
      command: ["/bin/sh"]
      args: ["-c", "sleep 10000"]
# write a file into the mounted volume from the first container
kubectl exec test-emptydir -c container-one -- cp /etc/hostname /opt

# read the file from the second container
kubectl exec test-emptydir -c container-two -- cat /opt/hostname

Use an tmpfs file-system in memory instead of local storage:

volumes:
  - name: html
    emptyDir:
      medium: Memory

Host Path

kind: PersistentVolume
metadata:
  name: project-storage
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 10Gi
  hostPath:
    path: /data/project/        # absolute path on the host node

Mount the host node’s file-system into a pod

  • Capacity is determined by the available space on the host node
  • No Quotas or limits imposed …responsibility outside Kubernetes
  • Data persistent as long as host node is operational
  • Typical use-cases:
    • Testing & development
    • Access logs & configuration files on the host
    • Share data between pods on the same host

Storage Classes

Storage Classes are an abstraction layer over the underlying storage infrastructure

  • Define properties/behavior of PVs, quality-of-service levels, (backup) policies
  • Enable automatic provisioning of storage characteristics…
    • …type of the storage (for example SSD, HDD)
    • …access modes (read-only, read-write, etc.)
kubectl get sc

CSI (Container Storage Interface) volume plugins…

  • …enable storage vendors to create custom storage plugins
  • …uses the csi volume type to attach or mount the volumes

Persistent Volumes

Persistent Volumes9 (PV) are a way to abstract and represent physical or networked storage resources

Why use persistent volumes?

  1. Data persistence vital to stateful applications and databases
  2. Abstraction of the back-end storage
    • …allows to manage storage resources independently
    • …enables admins control utilization and optimization of storage hardware
  3. Access control to ensure data integrity and security

Persistent Volume Claims (PVC) request storage resources for pods

  • Used to specific requirements to the storage resource
  • …independent of the internals of the storage provider
  • Ensures a volume ‘claim’ to be portable across numerous back-ends
  • Isolates storage-related concerns from the workload
example.yml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-storage
spec:
  capacity:
    storage: 500Mi 
  # Indicating that this PV is intended for file-based storage
  volumeMode: Filesystem
  # Can be mounted as read-write by a single node at a time
  accessModes:
    - ReadWriteOnce
  # Data is retained even if the associated PVC is deleted
  # …requires to be removed manual for cleanup
  persistentVolumeReclaimPolicy: Retain
  # Associates to a particular StorageClass
  storageClassName: local-storage
  # Specify the actual location on the host machine
  hostPath:
    path: /srv

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: example-claim
spec:
  # can be mounted as read-write by a single node at a time
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      #  request minimum storage capacity
      storage: 200Mi 
  # associates with a StorageClass
  storageClassName: local-storage

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        # mount volumes to the container
        volumeMounts:
        # which volume to mount
        - name: example-nginx-storage
          # where to mount the volume inside the container
          mountPath: /usr/share/nginx/html
      # define volumes available to the pod
      volumes:
      - name: example-nginx-storage
        persistentVolumeClaim:
          claimName: example-claim
kubectl apply -f example.yml
# check ...clean up
kubectl get -f example.yml
kubectl delete -f example.yml

ConfigMap

# create a ConfigMap directly from literal values
kubectl create configmap project-config \
        --from-literal=key1=value1 --from-literal=key2=value2

# create a ConfigMap from a file
kubectl create configmap project-config \
        --from-file=project-config.properties

kubectl create configmap project-config \
        --from-file=/path/to/project-config/

Manage configuration settings for applications

  • For non-confidential configuration only (cf. Kubernetes secrets)
  • Values can be strings, numbers, JSON, YAML
  • Version control configuration separate form application code & container images

Example of a configuration file nginx.conf used by a pod:

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  nginx.conf: |
    user nginx;
    worker_processes  1;
    events {
      worker_connections  10240;
    }
    http {
      server {
          listen       80;
          server_name  localhost;
          location / {
            root   /srv/htdocs;
            index  index.html index.htm;
        }
      }
    }

---

apiVersion: v1
kind: Pod
metadata:
  name: website
spec:
  containers:
  - name: web-server
    image: nginx:alpine
    ports:
      - containerPort: 80 
    volumeMounts:
      - name: html
        mountPath: /srv
        readOnly: true
      - name: nginx-conf
        mountPath: /etc/nginx/nginx.conf
        subPath: nginx.conf
        readOnly: true