把Docker Swarm换到Kubernetes时踩过的坑
Swarm 跑起来没太大问题,但很快就暴露了一些问题:
- 服务发现比较基础,想做灰度发布得自己搞一些 trick
- 健康检查支持有限,有时候容器挂了 Swarm 发现不了
- 监控和日志收集得自己想办法,不像 K8s 那样有完整的生态
- 团队里其他项目都在用 K8s,技能没法复用
这些单个问题都能解决,但解决成本越来越高,最后还是决定迁移到 Kubernetes。
最开始是因为项目里 Docker 越来越多,十几个容器手动启动确实费劲。
起因:为什么折腾
最开始是因为项目里 Docker 越来越多,十几个容器手动启动确实费劲。那时候觉得 Swarm 多合适,几条命令就能搞定集群,docker service create 一跑,服务就起来了。
2024 年初的时候,生产环境有三台阿里云 ECS,每台 4C8G,上面跑了大概二十几个容器。Swarm 跑起来没太大问题,但很快就暴露了一些问题:
- 服务发现比较基础,想做灰度发布得自己搞一些 trick
- 健康检查支持有限,有时候容器挂了 Swarm 发现不了
- 监控和日志收集得自己想办法,不像 K8s 那样有完整的生态
- 团队里其他项目都在用 K8s,技能没法复用
这些单个问题都能解决,但解决成本越来越高,最后还是决定迁移到 Kubernetes。
Docker Swarm 时期的实践
先说说 Swarm 时期是怎么玩的,毕竟是过渡阶段,也有一些可取的地方。
集群初始化
Swarm 的集群初始化很简单,基本上就是几条命令:
# 在第一台机器上初始化集群
docker swarm init --advertise-addr 192.168.1.10
# 在其他节点上加入集群
docker swarm join --token SWMTKN-1-xxx 192.168.1.10:2377
节点加进来之后,docker node ls 就能看到集群状态了。
服务部署
部署一个简单的 Web 服务:
# docker-compose.yml
version: '3.8'
services:
web:
image: nginx:1.21
ports:
- "80:80"
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
networks:
- webnet
networks:
webnet:
driver: overlay
部署命令也很直接:
docker stack deploy -c docker-compose.yml mystack
踩过的坑
第一个坑是 overlay 网络。刚开始用的是默认的 overlay 网络,但发现跨节点通信延迟比较高,有时候还会丢包。
后来改成指定子网:
networks:
webnet:
driver: overlay
ipam:
config:
- subnet: 10.0.9.0/24
这样就稳定多了,但说实话,这种问题在 Swarm 里面排查起来比较费劲,没有 K8s 那样的网络策略和调试工具。
第二个坑是存储卷。Swarm 的 volume 管理比较简单,要做多节点共享存储就得自己想办法。
我们试过 NFS:
volumes:
data:
driver: local
driver_opts:
type: nfs
o: addr=nfs-server,rw
device: ":/path/to/share"
但 NFS 的性能确实不太行,高并发的时候延迟明显。
后来换成了阿里云的 NAS,性能好了不少,但这就意味着被云厂商绑定了。
迁移到 Kubernetes
迁移的决定是 2024 年 5 月做的,花了一个多月时间才迁移完。
为什么选 Kubernetes
说实话,Kubernetes 的复杂度确实比 Swarm 高很多,但它的优势也很明显:
- 功能完整:自愈、自动扩缩容、滚动更新、配置管理等
- 生态成熟:各种监控、日志、存储插件都很齐全
- 行业标准:团队技能、招聘、社区支持都有优势
- 可扩展性强:CRD + Operator 能玩出很多花样
这些都是 Swarm 比较欠缺的。
集群搭建
我们用的是 kubeadm 搭建的集群,因为想要多一些控制权,不想用云厂商的托管服务。
# 在 master 节点
kubeadm init --apiserver-advertise-address=192.168.1.10 --pod-network-cidr=10.244.0.0/16
# 在 worker 节点
kubeadm join 192.168.1.10:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>
网络插件选的是 Flannel,因为比较轻量,部署也简单:
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
应用迁移
Swarm 的 docker-compose.yml 需要转换成 Kubernetes 的 Deployment 和 Service:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
迁移中的坑
第一个坑是镜像仓库。Swarm 可以直接拉 Docker Hub 的镜像,但生产环境不能这么干。
我们建了私有镜像仓库,用 Harbor 管的:
# 先打标签
docker tag nginx:1.21 harbor.example.com/library/nginx:1.21
# 推送
docker push harbor.example.com/library/nginx:1.21
# 创建 secret
kubectl create secret docker-registry harbor-registry \
--docker-server=harbor.example.com \
--docker-username=<username> \
--docker-password=<password>
# 在 pod 里引用
spec:
containers:
- name: nginx
image: harbor.example.com/library/nginx:1.21
imagePullSecrets:
- name: harbor-registry
第二个坑是健康检查。Swarm 的健康检查比较简单,Kubernetes 的就复杂很多,但功能也更强。
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 1
这里有几个参数需要好好调:
initialDelaySeconds:容器启动后等多久开始检查,太早会误杀,太晚会影响可用性periodSeconds:检查间隔,太频繁浪费资源,太疏漏掉问题failureThreshold:失败多少次才算真的失败,容忍度要按业务来定
第三个坑是资源限制。Swarm 的资源限制比较粗糙,Kubernetes 的就精细很多。
刚开始我们给得太保守,结果在高并发的时候性能上不去。后来经过压测才慢慢调到一个合理的值:
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
这里的 requests 和 limits 要理解清楚:requests 是调度用的,保证容器至少能拿到这么多资源;limits 是运行时的上限,防止容器占用太多。
核心功能的对比
服务发现
Swarm 的服务发现比较基础,服务名直接就能解析成 IP:
# 在一个容器里访问另一个服务
curl http://backend:8080/api
Kubernetes 的服务发现就比较复杂了,有 ClusterIP、NodePort、LoadBalancer 几种类型,每种都有使用场景。
# ClusterIP(默认,集群内访问)
kind: Service
spec:
type: ClusterIP
# ...
# NodePort(通过节点端口访问)
kind: Service
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080
# LoadBalancer(云厂商提供的负载均衡器)
kind: Service
spec:
type: LoadBalancer
# ...
Kubernetes 还有 Ingress,可以做基于域名的路由:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
负载均衡
Swarm 的负载均衡是基于 VIP 的,比较简单,但也比较有限。
Kubernetes 的负载均衡就强大多了,可以支持多种算法和会话保持:
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
# ...
自愈能力
Swarm 的自愈能力比较基础,主要是容器重启。
Kubernetes 的自愈能力就比较全面了:
- Pod 重启:容器挂了会自动重启
- 节点自愈:节点挂了会自动迁移 Pod
- 自动扩缩容:根据负载自动调整副本数
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
滚动更新
Swarm 的滚动更新比较简单,主要是配置 update_config:
deploy:
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
Kubernetes 的滚动更新就精细多了:
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
这里 maxSurge 是更新过程中最多可以多出多少个 Pod,maxUnavailable 是最多可以有多少个 Pod 不可用。
还有 Deployment 的 revision,可以回滚到之前的版本:
# 查看历史版本
kubectl rollout history deployment web
# 回滚到上一个版本
kubectl rollout undo deployment web
# 回滚到指定版本
kubectl rollout undo deployment web --to-revision=2
真实的踩坑记录
网络问题的排查
有一次服务突然不通了,Pod 状态是 Running,但就是访问不到。
先检查 Service:
kubectl get svc web
kubectl describe svc web
Service 没问题,再检查 Pod:
kubectl get pods -l app=web
kubectl describe pod <pod-name>
Pod 也没问题,进入 Pod 内部检查:
kubectl exec -it <pod-name> -- /bin/bash
curl localhost
本地访问没问题,那问题可能在网络层。
最后发现是 iptables 规则被误删了,恢复之后就正常了。
这次踩坑让我学到一点:Kubernetes 的网络比较复杂,排查问题要有方法,不能瞎猜。
存储问题
存储的问题也挺多。刚开始用 emptyDir,结果 Pod 重启数据就丢了。
后来改用 hostPath,但又遇到了权限问题。
apiVersion: v1
kind: Pod
metadata:
name: storage-test
spec:
containers:
- name: app
image: busybox
volumeMounts:
- mountPath: /data
name: hostpath-vol
volumes:
- name: hostpath-vol
hostPath:
path: /data
type: DirectoryOrCreate
最后还是用了阿里云的 SSD 云盘:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-ssd
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: alicloud-disk-ssd
资源争抢
有一次一个 Java 服务占满了内存,导致其他服务都受影响。
后来加了资源限制:
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
但这样又导致了 OOM Kill,因为 Java 的堆内存和容器内存计算方式不太一样。
最后调大了内存限制,同时在 Java 启动参数里加了 -XX:MaxRAMPercentage=75.0,这样 JVM 会根据容器内存限制来调整堆大小。
证书过期
有一次突然所有服务都访问不了,查了一圈发现是证书过期了。
Kubernetes 的证书默认是一年有效期,到期后 API Server 就会拒绝连接。
# 检查证书有效期
kubeadm certs check-expiration
# 更新证书
kubeadm certs renew all
# 重启控制面
systemctl restart kubelet
从那以后,我们在 CI/CD 里加了证书过期检查,提前一个月发告警。
迁移后的效果
迁移完成后,效果还是比较明显的:
- 自动扩缩容:高峰期自动扩容,低谷期自动缩容,资源利用率提升了 30%+
- 滚动更新:发布的时候用户无感知,回滚也很方便
- 监控和日志:Prometheus + Grafana + ELK,问题定位快了很多
- 团队技能:现在团队里好几个人都能上手 K8s,招聘也容易了
但也有一些问题:
- 复杂度高:很多概念要理解,学习曲线陡峭
- 运维成本:集群本身的维护也需要时间和精力
- 资源消耗:K8s 的组件本身就要占不少资源
对于小团队来说,是不是一定要上 K8s,这是个需要权衡的问题。
经验总结
这次迁移下来,有一些经验值得分享:
- 不要一上来就上 K8s,Swarm 对于小场景其实够用
- 迁移前要做好充分的测试,生产环境不能出问题
- 先搞清楚核心概念,再动手操作,不然容易踩坑
- 监控和日志要提前规划,不然出问题就瞎了
- 证书和备份要定期检查,不然哪天集群就挂了
技术选型没有银弹,Swarm 有 Swarm 的好,K8s 有 K8s 的强,关键是要匹配自己的场景。
现在的做法是:开发测试环境还是用 Swarm,简单够用;生产环境用 K8s,追求稳定和功能。
工具本身没有好坏,合适不合适才是关键。
折腾了这么久,容器编排这条路算是走完了第一阶段。下一步是搞服务网格,估计又是一场新的折腾。
版权声明: 本文首发于 指尖魔法屋-把Docker Swarm换到Kubernetes时踩过的坑(https://blog.thinkmoon.cn/post/119-container-orchestration-swarm-kubernetes-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。