正在加载,请稍候…

Kubernetes for Beginners: Pods, Deployments, Services, and Your First Cluster

Comprehensive Kubernetes tutorial for beginners: architecture, Pods, Deployments, Services, ConfigMaps, Secrets, Ingress, and deploying your first application to a real cluster.

Why Kubernetes Is Worth Learning

Kubernetes (K8s) is the operating system of the cloud. It's complicated, and there's no pretending otherwise. But if you're running containerized applications at any scale — even a few microservices — it solves real problems that are painful to solve manually.

The problems K8s solves: How do you restart containers that crash? How do you roll out updates without downtime? How do you scale up when traffic spikes? How do you route traffic to the right containers? K8s has opinions on all of these, backed by years of Google's production experience.

Core Architecture

Kubernetes Cluster
├── Control Plane (formerly "master")
│   ├── API Server     — All communication goes through here
│   ├── etcd           — Cluster state storage (distributed key-value)
│   ├── Scheduler      — Decides which node runs which Pod
│   └── Controller Manager — Ensures desired state matches actual state
│
└── Worker Nodes (1 to thousands)
    ├── kubelet         — Agent that runs on each node, talks to API server
    ├── kube-proxy      — Network proxy, manages Service routing
    └── Container Runtime — containerd/CRI-O (runs your containers)

The fundamental loop: you tell the API server your desired state ("I want 3 replicas of this container"). Controllers compare desired vs actual state and make changes to reach desired state.

Pods: The Smallest Unit

A Pod is one or more containers that share network and storage:

# pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  labels:
    app: my-app
    environment: production
spec:
  containers:
  - name: app
    image: my-app:1.0.0
    ports:
    - containerPort: 3000
    resources:
      requests:
        memory: "128Mi"   # Minimum guaranteed
        cpu: "100m"       # 100 millicores = 0.1 CPU
      limits:
        memory: "256Mi"   # Maximum allowed
        cpu: "500m"
    env:
    - name: NODE_ENV
      value: "production"
    - name: DATABASE_URL
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: url
    livenessProbe:
      httpGet:
        path: /health
        port: 3000
      initialDelaySeconds: 30
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /ready
        port: 3000
      initialDelaySeconds: 5
      periodSeconds: 5
# Apply and inspect
kubectl apply -f pod.yaml
kubectl get pods
kubectl describe pod my-app
kubectl logs my-app
kubectl exec -it my-app -- /bin/sh

Don't run Pods directly in production — they don't restart automatically. Use Deployments.

Deployments: Managed Pod Lifecycle

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3               # Run 3 Pods
  selector:
    matchLabels:
      app: my-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1           # Create 1 extra pod during update
      maxUnavailable: 0     # Never go below 3 running pods
  template:
    metadata:
      labels:
        app: my-app         # Must match selector
    spec:
      containers:
      - name: app
        image: my-app:1.0.0
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
# Common Deployment operations
kubectl apply -f deployment.yaml

# Scale manually
kubectl scale deployment my-app --replicas=5

# Rolling update
kubectl set image deployment/my-app app=my-app:2.0.0

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

# Rollback if something goes wrong
kubectl rollout undo deployment/my-app

# See rollout history
kubectl rollout history deployment/my-app
kubectl rollout undo deployment/my-app --to-revision=2

Services: Stable Networking

Pods are ephemeral — they die and get replaced with new IPs. Services provide a stable endpoint:

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app           # Routes to Pods with this label
  ports:
  - port: 80              # Service port
    targetPort: 3000      # Container port
  type: ClusterIP         # Internal only (default)
---
# For external access in cloud:
apiVersion: v1
kind: Service
metadata:
  name: my-app-loadbalancer
spec:
  selector:
    app: my-app
  ports:
  - port: 80
    targetPort: 3000
  type: LoadBalancer      # Cloud provider provisions a load balancer

Service types:

  • ClusterIP: Internal to cluster (default)
  • NodePort: Exposes on a port on each node (30000-32767)
  • LoadBalancer: Creates a cloud load balancer (AWS ELB, GCP LB, etc.)
  • ExternalName: DNS alias for an external service

Ingress: HTTP Routing

For routing HTTP traffic to multiple services:

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: letsencrypt-prod  # Auto TLS
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    secretName: app-tls-cert
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
  - host: admin.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 80

ConfigMaps and Secrets

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  # Key-value pairs
  LOG_LEVEL: "info"
  API_BASE_URL: "https://api.example.com"
  # Multi-line values (config files)
  nginx.conf: |
    server {
      listen 80;
      location / {
        proxy_pass http://localhost:3000;
      }
    }
---
# secret.yaml (base64 encoded values)
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  url: cG9zdGdyZXNxbDovL3VzZXI6cGFzc0Bob3N0L2Ri  # base64 encoded
  password: c3VwZXJzZWNyZXQ=
# Create secrets from command line (avoids base64 encoding manually)
kubectl create secret generic db-credentials   --from-literal=url="postgresql://user:pass@host/db"   --from-literal=password="supersecret"

# Or from files
kubectl create secret generic tls-cert   --from-file=tls.crt=./cert.pem   --from-file=tls.key=./key.pem
# Using ConfigMap and Secret in a Deployment
spec:
  containers:
  - name: app
    envFrom:
    - configMapRef:
        name: app-config    # All keys become env vars
    env:
    - name: DATABASE_URL
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: url
    volumeMounts:
    - name: config-volume
      mountPath: /etc/nginx
  volumes:
  - name: config-volume
    configMap:
      name: app-config
      items:
      - key: nginx.conf
        path: nginx.conf

Namespaces: Environment Separation

# Create namespaces for environment isolation
kubectl create namespace staging
kubectl create namespace production

# Deploy to specific namespace
kubectl apply -f deployment.yaml -n production

# View resources in a namespace
kubectl get all -n production

# Set default namespace for kubectl
kubectl config set-context --current --namespace=production
# Resource quotas per namespace
apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "10"
    requests.memory: 20Gi
    limits.cpu: "20"
    limits.memory: 40Gi
    pods: "50"
    services: "20"

Health Checks

spec:
  containers:
  - name: app
    # Liveness: Is the container alive? Restart if fails.
    livenessProbe:
      httpGet:
        path: /health/live
        port: 3000
      initialDelaySeconds: 30    # Wait 30s before first check
      periodSeconds: 10          # Check every 10 seconds
      timeoutSeconds: 5          # Fail if no response in 5s
      failureThreshold: 3        # Restart after 3 consecutive failures
    
    # Readiness: Is the container ready to receive traffic?
    # Remove from Service endpoints if fails (no restart)
    readinessProbe:
      httpGet:
        path: /health/ready
        port: 3000
      initialDelaySeconds: 5
      periodSeconds: 5
      successThreshold: 1        # Mark ready after 1 success
    
    # Startup: Slow-starting containers — overrides liveness during startup
    startupProbe:
      httpGet:
        path: /health/live
        port: 3000
      failureThreshold: 30       # Allow up to 5 minutes to start (30 * 10s)
      periodSeconds: 10

HorizontalPodAutoscaler

# autoscaler.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70    # Scale when CPU > 70%
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Essential kubectl Commands

# Cluster info
kubectl cluster-info
kubectl get nodes
kubectl top nodes               # CPU/memory usage

# Resources
kubectl get pods -A             # All namespaces
kubectl get pods -o wide        # Show node assignment
kubectl describe pod <name>     # Detailed info + events
kubectl logs <pod> -f           # Follow logs
kubectl logs <pod> --previous   # Logs from crashed container

# Debugging
kubectl exec -it <pod> -- bash  # Shell into container
kubectl port-forward <pod> 8080:3000  # Local port forwarding
kubectl cp <pod>:/path/to/file ./local-file  # Copy files

# Apply / Delete
kubectl apply -f .              # Apply all YAML in directory
kubectl delete -f deployment.yaml
kubectl delete pod <name> --force  # Force delete stuck pod

# Context switching
kubectl config get-contexts
kubectl config use-context production-cluster

# Dry run (test without applying)
kubectl apply -f deployment.yaml --dry-run=client

Kubernetes has a steep learning curve, but the abstractions are well-designed once you understand the mental model. The control loop (desired state → actual state) underlies everything, and once you internalize it, the behavior of each resource type becomes predictable.

→ Convert Docker run commands to Docker Compose format with the Docker Run Converter.