Skip to content
Go back

Kubernetes Guide Part 2: Advanced Networking, Storage, & Config

Edit page

10. Ingress Configuration

10.1 The Problem with External Services

NodePort:

http://123.45.67.89:30000  ❌ Ugly URL, exposes port

LoadBalancer:

http://35.123.45.67  ❌ Still an IP address

What we want:

https://myapp.com  ✅ Clean, memorable, secure

10.2 What is Ingress?

Ingress is not a Service type - it’s a separate resource that acts as the entry point to your cluster.

Ingress provides:

Comparison:

WITHOUT Ingress:
User → LoadBalancer1 (35.1.2.3) → Service1 → Pods
       LoadBalancer2 (35.4.5.6) → Service2 → Pods
       LoadBalancer3 (35.7.8.9) → Service3 → Pods
       (Multiple cloud load balancers = $$$)

WITH Ingress:
User → Ingress (myapp.com) → Service1, Service2, Service3 → Pods
       (One load balancer + intelligent routing)

10.3 Ingress Controller

Important: Ingress resource alone does nothing. You need an Ingress Controller.

What is an Ingress Controller?

Popular Implementations:

Install in Minikube:

minikube addons enable ingress

# Verify
kubectl get pods -n ingress-nginx

10.4 Basic Ingress Example

Let’s expose the Kubernetes dashboard via Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: dashboard-ingress
  namespace: kubernetes-dashboard
spec:
  rules:
  - host: dashboard.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kubernetes-dashboard
            port:
              number: 80

Apply:

kubectl apply -f dashboard-ingress.yaml

# Check ingress
kubectl get ingress -n kubernetes-dashboard

# Output:
# NAME                 CLASS   HOSTS           ADDRESS         PORTS   AGE
# dashboard-ingress    nginx   dashboard.com   192.168.49.2    80      1m

Understanding the ADDRESS field:

Configure local DNS (for testing):

# Edit hosts file
sudo vim /etc/hosts

# Add entry:
192.168.49.2 dashboard.com

Access:

# Open browser
http://dashboard.com

10.5 How Ingress Works in Cloud vs Bare Metal

Cloud Environment

When you install an Ingress Controller via Helm/YAML, it includes:

  1. Deployment for the controller pods
  2. Service of type LoadBalancer for that deployment
Cloud Load Balancer (provisioned automatically)
      ↓
Ingress Controller Service (LoadBalancer type)
      ↓
Ingress Controller Pods
      ↓
Evaluates Ingress rules
      ↓
Routes to backend Services (ClusterIP)
      ↓
Application Pods

The cloud provider sees the LoadBalancer Service and automatically provisions a real load balancer. The Ingress Controller just uses standard Kubernetes Service mechanisms!

Bare Metal Environment

You need to configure an entry point yourself:

Option 1: External Load Balancer

Option 2: MetalLB

10.6 Multiple Paths (Path-Based Routing)

Route different URLs to different services:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  rules:
  - host: myapp.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080
      - path: /admin
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 9090
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

Request Flow:

myapp.com/api      → api-service
myapp.com/admin    → admin-service
myapp.com/         → frontend-service

pathType Options:

10.7 Multiple Hosts (Virtual Hosting)

Host multiple domains on one cluster:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-host-ingress
spec:
  rules:
  - host: app1.mycompany.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app1-service
            port:
              number: 80
  - host: app2.mycompany.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app2-service
            port:
              number: 80
  - host: api.mycompany.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080

10.8 Configuring TLS/HTTPS

Secure your applications with HTTPS:

Step 1: Create a Secret with TLS certificates

apiVersion: v1
kind: Secret
metadata:
  name: myapp-tls
  namespace: default
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-cert>
  tls.key: <base64-encoded-key>

Create from files:

kubectl create secret tls myapp-tls \
  --cert=path/to/tls.crt \
  --key=path/to/tls.key

Step 2: Reference in Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
spec:
  tls:
  - hosts:
    - myapp.com
    secretName: myapp-tls
  rules:
  - host: myapp.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 80

Now accessible via:

https://myapp.com  ✅

10.9 Default Backend

Handle requests that don’t match any rules:

kubectl describe ingress dashboard-ingress -n kubernetes-dashboard

# You'll see:
# Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)

Create a default backend:

apiVersion: v1
kind: Service
metadata:
  name: default-backend
spec:
  selector:
    app: default-backend
  ports:
  - port: 80
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: default-backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: default-backend
  template:
    metadata:
      labels:
        app: default-backend
    spec:
      containers:
      - name: default-backend
        image: gcr.io/google_containers/defaultbackend:1.4
        ports:
        - containerPort: 8080

Update Ingress:

spec:
  defaultBackend:
    service:
      name: default-backend
      port:
        number: 80

10.10 Ingress Annotations

Customize Ingress Controller behavior:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    # Rewrite URLs
    nginx.ingress.kubernetes.io/rewrite-target: /
    
    # SSL redirect
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    
    # CORS
    nginx.ingress.kubernetes.io/enable-cors: "true"
    
    # Rate limiting
    nginx.ingress.kubernetes.io/limit-rps: "10"
spec:
  # ... rules

11. Volumes and Data Persistence

11.1 The Problem

Containers are ephemeral - when they restart, data is lost:

Container writes data → Container crashes
                        ↓
                   Data is GONE! 💥

Kubernetes doesn’t manage data persistence by default. You are responsible for:

11.2 Storage Requirements for Production

For databases and stateful applications:

  1. ✅ Available on all nodes - Pods can be scheduled anywhere
  2. ✅ Survive pod lifecycle - Data persists when pods restart
  3. ✅ Survive cluster crashes - Data isn’t lost if entire cluster fails

11.3 Volume Types Overview

Kubernetes supports many types of volumes:

Ephemeral volumes (pod lifetime):

Persistent volumes (beyond pod lifetime):

11.4 Local vs Remote Volumes

Local Volumes (hostPath, local)

volumes:
- name: data
  hostPath:
    path: /data
    type: Directory

Problems with local volumes:

Use cases for local volumes:

Remote Volumes (NFS, cloud storage)

volumes:
- name: data
  nfs:
    server: nfs-server.example.com
    path: /exports

Advantages:

For database persistence, always use remote storage!

11.5 Persistent Volumes (PV)

A PersistentVolume (PV) is a piece of storage in the cluster that has been provisioned by an admin or dynamically provisioned using Storage Classes.

Key Characteristics:

Example PV:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongodb-pv
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: standard
  hostPath:
    path: /data/mongodb

Access Modes:

Check PVs:

kubectl get pv
kubectl get persistentvolume

11.6 Persistent Volume Claims (PVC)

A PersistentVolumeClaim (PVC) is a request for storage by a user.

Think of it like:

Example PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard

Key Points:

Check PVCs:

kubectl get pvc
kubectl get persistentvolumeclaim

11.7 Using PVC in Pods

In Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongodb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
      - name: mongodb
        image: mongo
        volumeMounts:
        - name: mongodb-storage
          mountPath: /data/db
      volumes:
      - name: mongodb-storage
        persistentVolumeClaim:
          claimName: mongodb-pvc

Flow:

Pod → PVC (mongodb-pvc) → PV (mongodb-pv) → Actual Storage

11.8 Storage Classes (Dynamic Provisioning)

Manually creating PVs for every application doesn’t scale. Storage Classes enable dynamic provisioning.

Example Storage Class:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iopsPerGB: "10"
  encrypted: "true"

How it works:

1. Developer creates PVC
2. PVC references StorageClass
3. StorageClass automatically provisions PV
4. PV is bound to PVC

Updated PVC with StorageClass:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: fast-storage  # References StorageClass
  resources:
    requests:
      storage: 5Gi

Benefits:

Provisioners:

11.9 ConfigMap and Secret as Volumes

ConfigMaps and Secrets can be mounted as files in containers (not just env variables).

Practical Example: Mosquitto MQTT Broker

Create ConfigMap with config file:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mosquitto-config-file
data:
  mosquitto.conf: |
    log_dest stdout
    log_type all
    log_timestamp true
    listener 9001

Create Secret with secret file:

apiVersion: v1
kind: Secret
metadata:
  name: mosquitto-secret-file
type: Opaque
data:
  secret.file: |
    VGVjaFdvcmxkMjAyMyEgLW4K

Mount in Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mosquitto
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mosquitto
  template:
    metadata:
      labels:
        app: mosquitto
    spec:
      containers:
      - name: mosquitto
        image: eclipse-mosquitto:2.0
        ports:
        - containerPort: 1883
        volumeMounts:
        - name: mosquitto-config
          mountPath: /mosquitto/config
        - name: mosquitto-secret
          mountPath: /mosquitto/secret
          readOnly: true
      volumes:
      - name: mosquitto-config
        configMap:
          name: mosquitto-config-file
      - name: mosquitto-secret
        secret:
          secretName: mosquitto-secret-file

What happens:

/mosquitto/config/mosquitto.conf  ← ConfigMap data
/mosquitto/secret/secret.file     ← Secret data

Verify:

kubectl exec -it <mosquitto-pod> -- ls /mosquitto/config
kubectl exec -it <mosquitto-pod> -- cat /mosquitto/config/mosquitto.conf

12. StatefulSet for Stateful Applications

12.1 Stateful vs Stateless Applications

Stateless Applications

Request 1 → Pod A → Response
Request 2 → Pod B → Response  (doesn't need data from Request 1)
Request 3 → Pod A → Response

Stateful Applications

Write → Pod A (Master) → Must sync to replicas
Read  → Pod B (Slave)  → Must have latest data

12.2 Deployment vs StatefulSet

FeatureDeploymentStatefulSet
IdentityRandom (pod-7f8d9c-xk4p)Predictable (mysql-0, mysql-1)
OrderCreated randomly/parallelCreated sequentially
StorageShared or ephemeralIndividual persistent storage
NetworkLoad balanced randomlyStable network identity
ScalingEasyComplex
Use CaseStateless appsDatabases, queues

12.3 StatefulSet Example

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: standard
      resources:
        requests:
          storage: 10Gi

What gets created:

Pods:
- mysql-0 (with PVC data-mysql-0)
- mysql-1 (with PVC data-mysql-1)
- mysql-2 (with PVC data-mysql-2)

Each pod has:
- Stable name
- Individual persistent storage
- Stable network identity

12.4 Sticky Identity

StatefulSet pods maintain sticky identity across restarts:

Before restart:
mysql-0 (10.0.0.5) with PVC data-mysql-0

Pod crashes/restarts:
mysql-0 (10.0.0.12) with SAME PVC data-mysql-0
        ↑ New IP, but same name and storage!

Pod Name Format:

$(statefulset-name)-$(ordinal)
mysql-0, mysql-1, mysql-2, ...

12.5 Ordered Creation and Deletion

Creation Order:

1. mysql-0 created → Running
2. mysql-1 created → Running
3. mysql-2 created → Running

Deletion Order:

1. mysql-2 deleted
2. mysql-1 deleted
3. mysql-0 deleted

This ensures:

12.6 StatefulSet with Headless Service

StatefulSets require a headless service for network identity:

apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
  - port: 3306
    targetPort: 3306

Each pod gets a DNS entry:

mysql-0.mysql-headless.default.svc.cluster.local
mysql-1.mysql-headless.default.svc.cluster.local
mysql-2.mysql-headless.default.svc.cluster.local

12.7 Scaling Challenges

Master-Slave Architecture:

┌─────────────┐
│  mysql-0    │ ← Master (accepts writes)
│  (Master)   │
└──────┬──────┘
       │
   ┌───┴────┐
   │        │
┌──▼──┐  ┌──▼──┐
│mysql│  │mysql│ ← Slaves (read-only)
│  -1 │  │  -2 │
└─────┘  └─────┘

Challenges:

  1. Only master can write - must identify which pod is master
  2. Data synchronization - replicas must sync with master
  3. Split-brain - handle network partitions
  4. Failover - promote slave to master if master fails

This is why deploying databases in Kubernetes is complex!

12.8 Individual Pod Endpoints

Unlike Deployments, you can access individual StatefulSet pods:

# Access master directly
mysql -h mysql-0.mysql-headless.default.svc.cluster.local

# Access specific slave
mysql -h mysql-1.mysql-headless.default.svc.cluster.local

Still need a regular ClusterIP service for load-balanced reads:

apiVersion: v1
kind: Service
metadata:
  name: mysql-read
spec:
  selector:
    app: mysql
  ports:
  - port: 3306
    targetPort: 3306

13. Managed Kubernetes Services

13.1 Self-Managed vs Managed Kubernetes

Self-Managed (DIY)

You create cluster from scratch:

✅ Full control over everything
✅ Can customize anything
❌ Must set up control plane nodes
❌ Must configure networking, security
❌ Responsible for upgrades, patches
❌ Must handle high availability
❌ Time-consuming and complex

When to use:

Managed Kubernetes Services

Cloud provider manages control plane:

✅ Control plane managed by provider
✅ Automatic updates and patches
✅ Built-in high availability
✅ Integrated with cloud services
✅ Quick to set up
✅ Only pay for worker nodes
❌ Less control over control plane
❌ Vendor lock-in

13.2 How Managed Services Work

┌─────────────────────────────────────┐
│   Cloud Provider Responsibility     │
│                                     │
│  ┌───────────────────────────────┐ │
│  │    Control Plane Nodes        │ │
│  │  - API Server                 │ │
│  │  - Scheduler                  │ │
│  │  - Controller Manager         │ │
│  │  - etcd                       │ │
│  └───────────────────────────────┘ │
│  (Managed, HA, Auto-updated)       │
└─────────────────────────────────────┘
                ↓
┌─────────────────────────────────────┐
│     Your Responsibility             │
│                                     │
│  ┌──────┐  ┌──────┐  ┌──────┐      │
│  │Worker│  │Worker│  │Worker│      │
│  │Node 1│  │Node 2│  │Node 3│      │
│  └──────┘  └──────┘  └──────┘      │
│  (You configure, scale, pay for)    │
└─────────────────────────────────────┘

What you manage:

What cloud provider manages:

AWS EKS (Elastic Kubernetes Service)

# Create cluster
eksctl create cluster \
  --name my-cluster \
  --region us-west-2 \
  --nodegroup-name standard-workers \
  --node-type t3.medium \
  --nodes 3

Features:

Pricing:

Azure AKS (Azure Kubernetes Service)

# Create cluster
az aks create \
  --resource-group myResourceGroup \
  --name myAKSCluster \
  --node-count 3 \
  --enable-addons monitoring

Features:

Pricing:

Google GKE (Google Kubernetes Engine)

# Create cluster
gcloud container clusters create my-cluster \
  --zone us-central1-a \
  --num-nodes 3 \
  --machine-type n1-standard-2

Features:

Pricing:

Linode LKE (Linode Kubernetes Engine)

# Create via UI or API
linode-cli lke cluster-create \
  --label my-cluster \
  --region us-east \
  --k8s_version 1.28

Features:

Pricing:

13.4 Cloud Integration Benefits

Load Balancers:

# This automatically provisions cloud load balancer
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: LoadBalancer  # Cloud provider creates real LB
  ports:
  - port: 80

Storage:

# This provisions cloud storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-storage
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: aws-ebs  # Provisions AWS EBS
  resources:
    requests:
      storage: 10Gi

Native Services:

13.5 Cost Comparison

Example: 3-node cluster

ProviderControl PlaneWorker Nodes (3x)Total/month
AWS EKS$72/month~$150/month~$222/month
Azure AKSFREE~$150/month~$150/month
GKE$72/month~$150/month~$222/month
Linode LKEFREE~$90/month~$90/month

Note: Worker node costs vary by instance type and region.


14. Best Practices

14.1 Image Management

✅ Always Pin Version Tags

# ❌ BAD - unpredictable, could break production
containers:
- name: app
  image: nginx:latest

# ✅ GOOD - predictable, reproducible
containers:
- name: app
  image: nginx:1.25.3

Why?

14.2 Health Checks

Liveness Probe

Tells Kubernetes if the container is alive:

livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

What it does:

Readiness Probe

Tells Kubernetes if the container is ready to receive traffic:

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  successThreshold: 1

What it does:

Why both?

App starting up:
- Liveness: OK (container is alive)
- Readiness: FAIL (not ready for traffic)
→ Container keeps running, but receives no traffic

App crashed:
- Liveness: FAIL
→ Container is restarted

14.3 Resource Management

resources:
  requests:  # Minimum guaranteed
    memory: "256Mi"
    cpu: "250m"
  limits:    # Maximum allowed
    memory: "512Mi"
    cpu: "500m"

Why?

CPU units:

Memory units:

14.4 Deployment Strategies

✅ Always Deploy Multiple Replicas

spec:
  replicas: 3  # Minimum for high availability

Why?

✅ Multiple Worker Nodes

Avoid single point of failure:

1 Node with 3 replicas = All gone if node fails ❌
3 Nodes with 3 replicas = 2 replicas survive ✅

14.5 Security Best Practices

Don’t Use NodePort in Production

# ❌ BAD for production
spec:
  type: NodePort
  
# ✅ GOOD for production
spec:
  type: LoadBalancer

Why?

No Root Access for Containers

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  capabilities:
    drop:
    - ALL
  readOnlyRootFilesystem: true

Why?

Scan Images for Vulnerabilities

# Use tools like:
- Trivy
- Clair
- Anchore
- Snyk

# Automate in CI/CD pipeline

Keep Kubernetes Updated

# Check current version
kubectl version

# Upgrade (managed services handle this)
# Self-managed: follow upgrade guides

Why?

14.6 Organization Best Practices

Use Labels Everywhere

metadata:
  labels:
    app: frontend
    tier: web
    environment: production
    version: v1.2.3

Benefits:

Use Namespaces for Organization

# Organize by environment
- production
- staging
- development

# Or by team
- team-alpha
- team-beta

# Or by function
- frontend
- backend
- data

Store Configs in Version Control

my-k8s-configs/
├── base/
│   ├── deployment.yaml
│   └── service.yaml
├── overlays/
│   ├── dev/
│   ├── staging/
│   └── prod/
└── README.md

15. Conclusion

Key Takeaways

You’ve learned the essentials of Kubernetes:

  1. Core Concepts

    • Pods, Services, Deployments, StatefulSets
    • Control Plane and Worker Nodes architecture
    • How components interact
  2. Configuration

    • YAML manifests structure
    • ConfigMaps and Secrets for configuration
    • Labels and Selectors for connectivity
  3. Networking

    • Service types (ClusterIP, NodePort, LoadBalancer)
    • Ingress for intelligent routing
    • Internal vs external access
  4. Storage

    • Persistent Volumes and Claims
    • Storage Classes for dynamic provisioning
    • ConfigMaps/Secrets as volumes
  5. Production Readiness

    • Managed Kubernetes services
    • Best practices for security, reliability
    • Health checks and resource management

What’s Next?

Topics to explore:

Hands-On Practice

The best way to learn Kubernetes is by doing:

# Start with Minikube
minikube start

# Deploy applications
kubectl apply -f your-app.yaml

# Break things and fix them
# Experiment with different configurations
# Read the error messages carefully

# The kubectl documentation is your friend
kubectl explain deployment
kubectl explain pod.spec

Resources

Official Documentation:

Interactive Learning:

Books:


**Happy Kubing! **


← Previous: Part 1 Next: Part 3 →


Edit page
Share this post on:

Previous Post
Kubernetes Guide Part 3: Helm Package Management & Private Registries
Next Post
Kubernetes Guide Part 1: Architecture, Core Components & Basics