Understanding the Docker Swarm to Kubernetes Migration
Docker Swarm and Kubernetes are both container orchestration platforms, but they differ significantly in architecture, scalability, and ecosystem maturity. Docker Swarm offers simplicity and tight integration with the Docker CLI, making it ideal for smaller deployments. Kubernetes, on the other hand, provides a richer feature set, robust auto-scaling, self-healing capabilities, and has become the industry standard for container orchestration at scale. Migrating from Swarm to Kubernetes involves translating service definitions, networking configurations, storage volumes, and deployment workflows into Kubernetes-native resources.
Key Differences Between Docker Swarm and Kubernetes
Before diving into the migration, it's essential to understand the conceptual mapping between the two platforms. Docker Swarm services map to Kubernetes Deployments or StatefulSets. Swarm stacks map to Kubernetes namespaces with multiple resources. Swarm configs and secrets have direct counterparts in Kubernetes ConfigMaps and Secrets. Networking in Swarm uses overlay networks, while Kubernetes uses a flat network model with Services and Ingress resources for routing.
Pre-Migration Assessment and Planning
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →A successful migration begins with a thorough audit of your existing Swarm environment. Document every service, its environment variables, mounted volumes, secret references, network attachments, and deployment constraints. This inventory will serve as your migration blueprint and help you identify potential challenges early.
Auditing Your Swarm Environment
Run the following commands on your Swarm manager node to capture the complete state of your cluster. Save the outputs to files for reference during the migration process.
# List all services with detailed information
docker service ls --format "table {{.Name}}\t{{.Image}}\t{{.Mode}}\t{{.Replicas}}\t{{.Ports}}" > swarm-services.txt
# Export each service's full configuration
for service in $(docker service ls -q); do
docker service inspect "$service" > "service-${service}.json"
done
# List all stacks
docker stack ls > swarm-stacks.txt
# Export each stack's compose configuration
for stack in $(docker stack ls --format "{{.Name}}"); do
docker stack services "$stack" --format "table {{.Name}}\t{{.Image}}" > "stack-${stack}-services.txt"
done
# List secrets and configs
docker secret ls > swarm-secrets.txt
docker config ls > swarm-configs.txt
# List networks
docker network ls --filter "scope=swarm" > swarm-networks.txt
# Capture node information
docker node ls --format "table {{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.Role}}" > swarm-nodes.txt
Creating a Migration Dependency Map
With the audit data collected, create a dependency map that shows how services connect to each other, which secrets they consume, and which volumes they mount. This map will guide the order of resource creation in Kubernetes and help you identify services that can be migrated together as a group.
Translating Swarm Services to Kubernetes Deployments
The core of the migration involves converting each Docker Swarm service into a Kubernetes Deployment. A Deployment manages a set of replicated pods and provides declarative updates, rollbacks, and scaling—functionality that closely mirrors Swarm services.
Basic Service Translation
Consider a typical Swarm service defined in a docker-compose file. Here's how it translates to Kubernetes YAML. The Swarm service definition uses Docker-native syntax, while the Kubernetes Deployment requires explicit labels, selectors, and a pod template.
Original Docker Swarm Service (docker-compose.yml):
version: "3.8"
services:
web-app:
image: nginx:1.25
ports:
- "80:80"
- "443:443"
environment:
- NODE_ENV=production
- LOG_LEVEL=info
deploy:
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
resources:
limits:
cpus: "0.5"
memory: 256M
volumes:
- web-data:/usr/share/nginx/html
networks:
- frontend
volumes:
web-data:
driver: local
networks:
frontend:
driver: overlay
Equivalent Kubernetes Deployment and Resources:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
labels:
app: web-app
tier: frontend
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
tier: frontend
spec:
restartPolicy: Always
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 443
name: https
protocol: TCP
env:
- name: NODE_ENV
value: "production"
- name: LOG_LEVEL
value: "info"
resources:
limits:
cpu: "500m"
memory: "256Mi"
requests:
cpu: "250m"
memory: "128Mi"
volumeMounts:
- name: web-data
mountPath: /usr/share/nginx/html
volumes:
- name: web-data
persistentVolumeClaim:
claimName: web-data-pvc
---
# service.yaml - Exposing the deployment
apiVersion: v1
kind: Service
metadata:
name: web-app-service
namespace: production
labels:
app: web-app
spec:
type: ClusterIP
selector:
app: web-app
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
---
# persistentvolumeclaim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: web-data-pvc
namespace: production
spec:
accessModes:
- ReadWriteMany
storageClassName: standard
resources:
requests:
storage: 10Gi
Handling Swarm Replicated vs Global Services
Docker Swarm supports two service modes: replicated (a specified number of instances) and global (one instance per node). Kubernetes handles these differently. Replicated services map directly to Deployments with a replica count. Global services map to DaemonSets, which ensure a pod runs on every node in the cluster.
# DaemonSet for a global service (e.g., monitoring agent)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
name: node-exporter
template:
metadata:
labels:
name: node-exporter
spec:
containers:
- name: node-exporter
image: prom/node-exporter:latest
ports:
- containerPort: 9100
hostPort: 9100
protocol: TCP
resources:
limits:
cpu: "200m"
memory: "128Mi"
Migrating Swarm Networking to Kubernetes Services and Ingress
Networking is one of the most significant conceptual shifts when moving from Swarm to Kubernetes. Swarm's overlay networks provide automatic service discovery through DNS using the service name. Kubernetes achieves the same through its built-in DNS service (CoreDNS) but requires explicit Service resources to enable internal routing.
Internal Service Discovery
In Swarm, containers on the same overlay network can reach each other simply by using the service name. In Kubernetes, a Service object with a ClusterIP type enables internal DNS resolution. Every Service gets a DNS record in the format <service-name>.<namespace>.svc.cluster.local.
# Internal service for backend API
apiVersion: v1
kind: Service
metadata:
name: api-backend
namespace: production
spec:
type: ClusterIP
selector:
app: api-backend
ports:
- port: 8080
targetPort: 8080
protocol: TCP
---
# Another service can now reach this via:
# http://api-backend.production.svc.cluster.local:8080
Migrating Swarm Routing Mesh to Kubernetes Ingress
Docker Swarm's routing mesh automatically exposes published ports across all nodes in the cluster, even if a node isn't running the service. Kubernetes uses Ingress controllers (like NGINX Ingress or Traefik) to achieve similar functionality with far more routing capabilities, including host-based and path-based routing, TLS termination, and load balancing.
# ingress.yaml - Replaces Swarm routing mesh
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- www.example.com
secretName: tls-secret
rules:
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-backend
port:
number: 8080
Translating Swarm Secrets and Configs to Kubernetes
Both platforms support secrets and configuration management, but the implementation differs. Swarm secrets are encrypted at rest and mounted as files at /run/secrets/<secret-name>. Kubernetes Secrets are base64-encoded objects that can be mounted as files or exposed as environment variables. Swarm configs map directly to Kubernetes ConfigMaps.
Creating Kubernetes Secrets from Swarm Secrets
First, extract each secret's plaintext value from Swarm. Then create a Kubernetes Secret object. Note that Kubernetes Secrets are namespaced resources, unlike Swarm secrets which are cluster-wide.
# On the Swarm manager, retrieve a secret's value
docker secret inspect db-password --format "{{.Spec.Name}}" > secret-name.txt
# The actual value requires creating a temporary service to extract it
# Create the Kubernetes Secret declaratively
# secret-db-password.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-password
namespace: production
type: Opaque
data:
password:
---
# Alternatively, create it imperatively from a file
echo -n "supersecretpassword" | base64 > password-encoded.txt
kubectl create secret generic db-password \
--namespace production \
--from-file=password=password-encoded.txt
Mounting Secrets in Pods
Reference the Secret in your Deployment pod template to mount it as a file or inject it as an environment variable, mirroring Swarm's behavior.
# Pod template excerpt showing secret mounting
spec:
containers:
- name: database
image: postgres:16
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-password
key: password
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-password
items:
- key: password
path: db-password.txt
Converting Swarm Configs to ConfigMaps
Swarm configs store non-sensitive configuration data. The Kubernetes equivalent is a ConfigMap, which can hold entire configuration files, key-value pairs, or complex YAML/JSON documents.
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
namespace: production
data:
nginx.conf: |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /health {
return 200 'ok';
}
}
site-parameters.conf: |
max_clients=200
timeout=30
---
# Reference in Deployment
spec:
containers:
- name: nginx
image: nginx:1.25
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
volumes:
- name: config-volume
configMap:
name: nginx-config
items:
- key: nginx.conf
path: default.conf
Migrating Persistent Storage
Docker Swarm volumes are typically bound to a specific node unless using shared storage drivers. Kubernetes abstracts storage through PersistentVolumes (PV) and PersistentVolumeClaims (PVC), allowing pods to request storage without being tied to a specific node's filesystem. This is a critical improvement for stateful workloads.
Converting Swarm Volumes to PersistentVolumeClaims
For each Swarm volume, create a PVC that matches the storage requirements. If your Swarm volumes contain important data, you'll need to migrate that data separately using tools like rsync or backup/restore procedures.
# PVC for a stateful application like a database
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi
---
# Deployment referencing the PVC
spec:
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
subPath: pgdata
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-data
Handling StatefulSets for Stateful Applications
For stateful services that require stable network identities and ordered deployment (like databases or message queues), use a StatefulSet instead of a Deployment. This provides each pod with a unique, stable hostname and preserves data across rescheduling.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-cluster
namespace: production
spec:
serviceName: postgres-headless
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
name: postgresql
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi
---
# Headless service for StatefulSet peer discovery
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
namespace: production
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
name: postgresql
Migrating Swarm Stacks to Kubernetes Namespaces and Helm Charts
Docker Swarm stacks group related services, networks, and volumes under a single deployable unit using docker-compose files. In Kubernetes, the equivalent organizational unit is a Namespace, which provides a logical boundary for resources. For complex multi-service stacks, Helm charts offer templated, reusable packaging that surpasses Swarm stacks in flexibility.
Creating a Namespace for Each Stack
Start by creating a dedicated namespace for each Swarm stack you're migrating. This maintains the logical separation and makes resource management cleaner.
# Create namespaces matching your Swarm stacks
kubectl create namespace production-frontend
kubectl create namespace production-backend
kubectl create namespace monitoring
kubectl create namespace logging
# Label namespaces for organization
kubectl label namespace production-frontend environment=production tier=frontend
kubectl label namespace production-backend environment=production tier=backend
Converting a Complete Stack to Kubernetes Resources
Here's a full Swarm stack converted to Kubernetes resources within a single namespace. This demonstrates how multiple interdependent services translate together.
Original Swarm Stack (docker-compose.yml):
version: "3.8"
services:
redis:
image: redis:7-alpine
networks:
- backend
deploy:
replicas: 1
volumes:
- redis-data:/data
worker:
image: myapp/worker:latest
networks:
- backend
environment:
- REDIS_URL=redis://redis:6379
deploy:
replicas: 2
web:
image: myapp/web:latest
ports:
- "80:3000"
networks:
- backend
- frontend
environment:
- REDIS_URL=redis://redis:6379
deploy:
replicas: 3
networks:
backend:
driver: overlay
frontend:
driver: overlay
volumes:
redis-data:
Converted Kubernetes Resources (all in namespace "app-stack"):
# 00-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: app-stack
labels:
app: myapp
managed-by: kubernetes
---
# 01-redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: app-stack
labels:
app: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
name: redis
volumeMounts:
- name: redis-data
mountPath: /data
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-data-pvc
---
# 02-redis-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: app-stack
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
---
# 03-redis-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-data-pvc
namespace: app-stack
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
# 04-worker-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: app-stack
spec:
replicas: 2
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
containers:
- name: worker
image: myapp/worker:latest
env:
- name: REDIS_URL
value: "redis://redis.app-stack.svc.cluster.local:6379"
---
# 05-web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: app-stack
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: myapp/web:latest
ports:
- containerPort: 3000
name: http
env:
- name: REDIS_URL
value: "redis://redis.app-stack.svc.cluster.local:6379"
---
# 06-web-service.yaml
apiVersion: v1
kind: Service
metadata:
name: web
namespace: app-stack
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 3000
Handling Swarm Placement Constraints with Kubernetes Scheduling
Docker Swarm uses placement constraints to control which nodes run specific services (e.g., node labels like node.role==worker or custom labels). Kubernetes achieves the same through node affinity, pod anti-affinity, taints, and tolerations, providing much finer-grained control over pod scheduling.
Node Affinity Rules
# Pod specification with node affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role
operator: In
values:
- worker
- key: disk-type
operator: In
values:
- ssd
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: zone
operator: In
values:
- us-east-1a
Pod Anti-Affinity for High Availability
Use pod anti-affinity to spread replicas across nodes, mimicking Swarm's placement: spread directive but with more explicit control.
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
Implementing Resource Limits and Health Checks
Kubernetes offers more sophisticated health checking than Swarm. While Swarm relies on simple process-level health checks or custom HTTP checks, Kubernetes provides liveness, readiness, and startup probes that can execute commands, make HTTP requests, or check TCP connections.
Comprehensive Probe Configuration
spec:
containers:
- name: api-server
image: myapp/api:v2.1
ports:
- containerPort: 8080
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "500m"
memory: "256Mi"
startupProbe:
httpGet:
path: /health/startup
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 30
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 0
periodSeconds: 15
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
CI/CD Pipeline Adaptation
Your deployment pipeline must be updated to work with Kubernetes. Swarm's docker stack deploy command with a compose file becomes kubectl apply with Kubernetes manifests. For more advanced workflows, adopt Helm for templating and deployment management.
Basic kubectl-Based Deployment Script
#!/bin/bash
# deploy.sh - Replaces 'docker stack deploy'
set -euo pipefail
NAMESPACE="production"
KUBE_CONTEXT="production-cluster"
echo "Applying namespace configuration..."
kubectl apply -f 00-namespace.yaml --context "$KUBE_CONTEXT"
echo "Deploying secrets and configs..."
kubectl apply -f secrets/ --context "$KUBE_CONTEXT" -n "$NAMESPACE"
kubectl apply -f configmaps/ --context "$KUBE_CONTEXT" -n "$NAMESPACE"
echo "Deploying persistent volume claims..."
kubectl apply -f storage/ --context "$KUBE_CONTEXT" -n "$NAMESPACE"
echo "Deploying backend services..."
kubectl apply -f backend/ --context "$KUBE_CONTEXT" -n "$NAMESPACE"
echo "Waiting for backend deployments to be ready..."
kubectl wait --for=condition=available deployment/redis deployment/worker \
--context "$KUBE_CONTEXT" -n "$NAMESPACE" --timeout=120s
echo "Deploying frontend services..."
kubectl apply -f frontend/ --context "$KUBE_CONTEXT" -n "$NAMESPACE"
echo "Deploying ingress..."
kubectl apply -f ingress.yaml --context "$KUBE_CONTEXT" -n "$NAMESPACE"
echo "Deployment complete! Checking rollout status..."
kubectl rollout status deployment/web --context "$KUBE_CONTEXT" -n "$NAMESPACE"
Helm Chart Migration Example
For complex stacks, converting to a Helm chart provides reusability and parameterization. Here's a basic Helm chart structure for the stack migrated earlier.
# Chart.yaml
apiVersion: v2
name: myapp-stack
description: Migrated application stack from Docker Swarm
type: application
version: 1.0.0
appVersion: "latest"
# values.yaml
replicaCount:
web: 3
worker: 2
redis: 1
image:
web: myapp/web:latest
worker: myapp/worker:latest
redis: redis:7-alpine
service:
web:
type: LoadBalancer
port: 80
persistence:
redis:
enabled: true
size: 5Gi
# templates/deployment-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: {{ .Values.replicaCount.web }}
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: {{ .Values.image.web }}
ports:
- containerPort: 3000
env:
- name: REDIS_URL
value: "redis://redis.{{ .Release.Namespace }}.svc.cluster.local:6379"
Testing and Validation Strategy
Before decommissioning your Swarm cluster, implement a thorough testing phase. Run both clusters in parallel during the cutover period to validate functionality, performance, and reliability.
Smoke Testing Deployed Services
#!/bin/bash
# smoke-test.sh - Validate migrated services
KUBE_CONTEXT="production-cluster"
NAMESPACE="production"
echo "=== Running Smoke Tests ==="
# Test service endpoints
echo "Testing web service..."
WEB_POD=$(kubectl get pods -n "$NAMESPACE" -l app=web \
--context "$KUBE_CONTEXT" -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n "$NAMESPACE" "$WEB_POD" --context "$KUBE_CONTEXT" -- \
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health
# Test DNS resolution
echo "Testing internal DNS resolution..."
kubectl run dns-test --rm -i --restart=Never --image=busybox:latest \
-n "$NAMESPACE" --context "$KUBE_CONTEXT" -- \
nslookup redis.app-stack.svc.cluster.local
# Test secret mounting
echo "Verifying secret mounting..."
kubectl exec -n "$NAMESPACE" deployment/db --context "$KUBE_CONTEXT" -- \
ls -la /etc/secrets/
# Test persistent storage
echo "Testing persistent volume writes..."
kubectl exec -n "$NAMESPACE" deployment/redis --context "$KUBE_CONTEXT" -- \
redis-cli set migration_test "$(date +%s)"
echo "=== Smoke tests completed ==="
Best Practices for a Smooth Migration
1. Adopt an Incremental Migration Approach
Never attempt a big-bang migration. Move services incrementally, starting with stateless, non-critical services. Validate each migrated service thoroughly before moving to the next. Use a canary deployment pattern where a small percentage of traffic routes to the Kubernetes cluster while the rest remains on Swarm.
2. Implement Comprehensive Monitoring Early
Deploy Prometheus and Grafana on your Kubernetes cluster before migrating workloads. Instrument your applications with metrics exporters. This visibility will help you identify performance regressions, resource bottlenecks, and unexpected behaviors during the transition.
# Deploy Prometheus monitoring stack before migration
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set prometheus.serviceMonitorSelectorNilUsesHelmValues=false
3. Establish a GitOps Workflow
Store all Kubernetes manifests in a Git repository and use tools like ArgoCD or Flux to automatically sync your cluster state. This replaces ad-hoc docker stack deploy commands with a declarative, version-controlled, and auditable deployment process.
# Example ArgoCD application for a migrated stack
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-stack
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp-k8s-manifests
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
4. Implement Resource Quotas and Network Policies
Kubernetes provides namespace-level resource quotas and fine-grained network policies that didn't exist in Swarm. Implement these early to prevent resource contention and enhance security.
# Resource quota for a 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"
persistentvolumeclaims: "10"
services: "15"
---
# Network policy restricting pod communication
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-isolation
namespace: production
spec:
podSelector:
matchLabels:
tier: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
tier: frontend
ports:
- port: 8080
protocol: TCP
egress:
- to:
- podSelector:
matchLabels:
app: redis
ports:
- port: 6379
protocol: TCP
5. Plan Your Rollback Strategy
Maintain the Swarm cluster in a ready state throughout the migration. Document a clear rollback procedure for each service. Kubernetes rollbacks are handled via kubectl rollout undo, but a full platform rollback to Swarm requires keeping the original compose files and infrastructure intact.
# Quick rollback of a Kubernetes deployment
kubectl rollout undo deployment/web-app -n production
# Check rollback history
kubectl rollout history deployment/web-app -n production
# Rollback to a specific revision
kubectl rollout undo deployment/web-app -n production --to-revision=3
6. Train Your Team on Kubernetes Fundamentals
The operational model differs significantly between Swarm and Kubernetes. Ensure your team understands pod lifecycle, debugging with kubectl, reading logs, and handling common failure scenarios before the migration begins. Invest in hands-on labs using tools like Minikube or Kind for local practice.
Data Migration for Stateful Workloads
For databases and stateful services, data migration requires careful planning. You cannot simply redeploy a stateful workload and point it at new storage. Use the following approach for zero-downtime or minimal-downtime data migration.
Database Migration Using Logical Replication
# Step 1: Deploy new PostgreSQL instance in Kubernetes
kubectl apply -f postgres-statefulset.yaml
# Step 2: Export data from Swarm PostgreSQL
pg_dump -h swarm-postgres-host -U postgres -d myapp_db \
--no-owner --no-privileges -F c -f dump_file.dump
# Step 3: Restore to Kubernetes PostgreSQL
PGPOD=$(kubectl get pods -n production -l app=postgres \
-o jsonpath='{.items[0].metadata.name}')
kubectl cp dump_file.dump "$PGPOD":/tmp/dump_file.dump -n production
kubectl exec -n production "$PGPOD" -- \
pg_restore -U postgres -d myapp_db /tmp/dump_file.dump
# Step 4: Validate data integrity
kubectl exec -n production "$PGPOD" -- \
psql -U postgres -d myapp_db -c "SELECT count(*) FROM users;"
Volume Data Migration with rsync
# For file-based volumes, use an intermediate pod to rsync data
apiVersion: v1
kind: Pod
metadata:
name: data-migration-helper
namespace: production
spec:
containers:
- name: migrator
image: alpine:3.19
command: ["sleep", "3600"]
volumeMounts:
- name: source-data
mountPath: /