Skip to content

Deploying Python Applications to Kubernetes in Docker (KinD)

This guide walks through setting up a local Kubernetes environment using KinD and deploying Python applications to it.

1. Introduction to KinD

KinD (Kubernetes in Docker) is a tool that runs local Kubernetes clusters inside Docker containers. It's designed for testing and development of Kubernetes applications without needing a full-scale cluster.

Advantages of KinD

  • Lightweight: Runs Kubernetes nodes as Docker containers
  • Fast: Creates clusters in seconds
  • Disposable: Easy to create and delete clusters
  • Multi-node: Can simulate multi-node clusters
  • Consistent: Provides a reliable Kubernetes experience

2. Installing KinD

Prerequisites

  • Docker installed and running
  • kubectl installed

Installation Steps

Linux

bash
# Download KinD binary
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

# Verify installation
kind version

macOS

bash
# Using Homebrew
brew install kind

# Verify installation
kind version

Windows (PowerShell)

powershell
# Using Chocolatey
choco install kind

# Using curl
curl.exe -Lo kind-windows-amd64.exe https://kind.sigs.k8s.io/dl/v0.20.0/kind-windows-amd64
Move-Item .\kind-windows-amd64.exe c:\some-dir-in-your-PATH\kind.exe

# Verify installation
kind version

3. Creating a KinD Cluster

Basic Cluster Creation

bash
# Create a cluster with default name 'kind'
kind create cluster

# Create a cluster with custom name
kind create cluster --name python-app-cluster

# Verify the cluster is running
kubectl cluster-info --context kind-python-app-cluster

# View nodes
kubectl get nodes

Multi-Node Cluster

Create a config file kind-config.yaml:

yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker

Create the cluster with this config:

bash
kind create cluster --name python-multi --config kind-config.yaml

# Verify nodes
kubectl get nodes

4. Preparing Your Python Application

Containerize Your Application

First, create a Dockerfile as shown in the containerization guide. Then build and push the image:

bash
# Build the image
docker build -t my-python-app:v1 .

# Load the image into KinD
kind load docker-image my-python-app:v1 --name python-app-cluster

This step is crucial as KinD clusters can't automatically pull from your local Docker daemon.

5. Deploying to KinD

Creating Kubernetes Manifests

Deployment (deployment.yaml)

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-app
  labels:
    app: python-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: python-app
  template:
    metadata:
      labels:
        app: python-app
    spec:
      containers:
        - name: python-app
          image: my-python-app:v1
          ports:
            - containerPort: 5000
          resources:
            requests:
              memory: "64Mi"
              cpu: "100m"
            limits:
              memory: "128Mi"
              cpu: "200m"
          livenessProbe:
            httpGet:
              path: /
              port: 5000
            initialDelaySeconds: 5
            periodSeconds: 10

Service (service.yaml)

yaml
apiVersion: v1
kind: Service
metadata:
  name: python-app
spec:
  selector:
    app: python-app
  ports:
    - port: 80
      targetPort: 5000
  type: ClusterIP

Applying the Manifests

bash
# Apply the deployment and service
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

# Verify deployment and service
kubectl get deployments
kubectl get services
kubectl get pods

# View logs from a pod
kubectl logs -l app=python-app

6. Accessing Your Application

Port Forwarding

bash
# Forward local port to service
kubectl port-forward service/python-app 8080:80

# Access in browser: http://localhost:8080

Using Ingress

  1. Enable ingress in KinD:
yaml
# kind-ingress-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            node-labels: "ingress-ready=true"
    extraPortMappings:
      - containerPort: 80
        hostPort: 80
        protocol: TCP
      - containerPort: 443
        hostPort: 443
        protocol: TCP
  - role: worker
  1. Create the cluster with ingress support:
bash
kind create cluster --name ingress-cluster --config kind-ingress-config.yaml
  1. Install NGINX Ingress Controller:
bash
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

# Wait for it to be ready
kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=90s
  1. Create an Ingress resource:
yaml
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: python-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: python-app
                port:
                  number: 80
  1. Apply the Ingress:
bash
kubectl apply -f ingress.yaml

# Verify the Ingress
kubectl get ingress

Now you can access your application at http://localhost/.

7. Configuration and Secrets

ConfigMaps for Configuration

yaml
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: python-app-config
data:
  APP_ENV: "development"
  DEBUG: "true"
  CONFIG_PATH: "/etc/config/app-config.json"
  app-config.json: |
    {
      "feature_flags": {
        "new_feature": true,
        "experimental": false
      }
    }

Apply and use in deployment:

bash
kubectl apply -f configmap.yaml

Update your deployment to use the ConfigMap:

yaml
spec:
  containers:
    - name: python-app
      # ...
      env:
        - name: APP_ENV
          valueFrom:
            configMapKeyRef:
              name: python-app-config
              key: APP_ENV
        - name: DEBUG
          valueFrom:
            configMapKeyRef:
              name: python-app-config
              key: DEBUG
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: python-app-config

Secrets for Sensitive Data

yaml
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: python-app-secrets
type: Opaque
data:
  db_password: cGFzc3dvcmQxMjM= # Base64 encoded password123
  api_key: c2VjcmV0X2tleV8xMjM= # Base64 encoded secret_key_123

Apply and use in deployment:

bash
kubectl apply -f secret.yaml

Update deployment:

yaml
spec:
  containers:
    - name: python-app
      # ...
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: python-app-secrets
              key: db_password
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: python-app-secrets
              key: api_key

8. Persistent Storage

Creating a PersistentVolumeClaim

yaml
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: python-app-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Update deployment:

yaml
spec:
  containers:
    - name: python-app
      # ...
      volumeMounts:
        - name: data-volume
          mountPath: /app/data
  volumes:
    - name: data-volume
      persistentVolumeClaim:
        claimName: python-app-data

9. Scaling and Updates

Scaling Deployments

bash
# Scale to 3 replicas
kubectl scale deployment python-app --replicas=3

# Verify
kubectl get pods

Rolling Updates

Update your application code, build a new image, and load it to KinD:

bash
# Build new version
docker build -t my-python-app:v2 .

# Load into KinD
kind load docker-image my-python-app:v2 --name python-app-cluster

# Update deployment
kubectl set image deployment/python-app python-app=my-python-app:v2

# Watch the rollout
kubectl rollout status deployment/python-app

10. Debugging in KinD

Viewing Logs

bash
# Get logs from a specific pod
kubectl logs python-app-7d6f8df57c-abcde

# Follow logs (like tail -f)
kubectl logs -f deployment/python-app

# Show logs from all pods with a specific label
kubectl logs -l app=python-app --all-containers=true

Executing Commands in Containers

bash
# Open a shell in a pod
kubectl exec -it python-app-7d6f8df57c-abcde -- /bin/bash

# Run a one-off command
kubectl exec python-app-7d6f8df57c-abcde -- python -c "import sys; print(sys.version)"

Debugging with Ephemeral Containers

bash
# Enable the feature if needed (newer Kubernetes has it enabled by default)
# Then add a debug container to a running pod
kubectl debug -it python-app-7d6f8df57c-abcde --image=python:3.9-slim --target=python-app

11. Advanced KinD Configuration

Custom Resource Limits

Create a config file with resource limits:

yaml
# kind-resources-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            system-reserved: memory=2Gi
            eviction-hard: memory.available<500Mi

Registry Integration

Set up a local registry:

bash
# Create a registry container
docker run -d --name registry -p 5000:5000 registry:2

# Create KinD cluster with registry
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]
    endpoint = ["http://registry:5000"]
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
EOF

# Connect the containers
docker network connect kind registry

# Push to local registry and use in K8s
docker tag my-python-app:v1 localhost:5000/my-python-app:v1
docker push localhost:5000/my-python-app:v1

# Update deployment to use this image
# image: localhost:5000/my-python-app:v1

12. Testing with KinD

Integration Testing

Create a script that:

  1. Creates a KinD cluster
  2. Loads your application image
  3. Deploys your application
  4. Runs tests against it
  5. Tears down the cluster

Example script:

bash
#!/bin/bash
set -e

# Create cluster
kind create cluster --name test-cluster

# Load image
kind load docker-image my-python-app:test --name test-cluster

# Deploy app
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

# Wait for deployment to be ready
kubectl wait --for=condition=available --timeout=60s deployment/python-app

# Run tests
# Port forward in background
kubectl port-forward service/python-app 8080:80 &
PF_PID=$!

# Wait for port-forward to be ready
sleep 3

# Run actual tests
pytest integration_tests/

# Clean up
kill $PF_PID
kind delete cluster --name test-cluster

Conclusion

KinD provides a powerful and lightweight environment for developing and testing Kubernetes applications locally. By following this guide, you've learned how to create KinD clusters, deploy Python applications, configure resources, and debug effectively. This workflow allows you to develop Kubernetes applications confidently without needing access to an expensive cloud-based cluster.