告别复杂部署从单集群应用搭建到生产环境微服务迁移的K8s云原生架构实战方案详解
最近好多朋友找我聊K8s的事情,说这玩意儿看着厉害,但真用起来就像爬珠穆朗玛峰,每一步都小心翼翼。今天我就掏心窝子跟大家聊聊,怎么从最简单的单集群应用起步,一路升级到生产环境,少走弯路。
先把基础打牢
咱们先从最基础的开始,别一上来就搞什么高可用集群,把自己绕晕了。先在你的电脑上装个kind或者minikube,或者如果你有阿里云、AWS的账号,直接开个免费的试用集群。
我一般推荐先用手头的开发机搭个本地环境试试水。装好kubectl之后,咱们先验证一下环境:
kubectl cluster-info
kubectl get nodes
kubectl get pods -n kube-system
看到你的节点状态是Ready,系统里的pod都在运行,这说明基础环境没问题。别急着往下走,先把这几个命令玩熟。
第一个应用:别嫌简单
很多人觉得写个Hello World太丢人,但我告诉你,这才是最重要的第一步。我见过太多人跳过这一步,到后面出问题了连调试都找不到方向。
咱们用Nginx做个例子,因为大家都熟悉:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: ClusterIP
部署的时候,一步步来:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl get deployments
kubectl get svc
kubectl get pods
这时候别急着看结果,先用kubectl describe看看有没有什么问题:
kubectl describe pod nginx-app-xxx-xxx
kubectl logs nginx-app-xxx-xxx
你会看到日志里有Nginx的启动信息,访问集群IP就能打开Nginx的默认页面。这一步看似简单,但你在实际操作中会发现各种问题:镜像拉取失败、资源限制设置不当、网络不通等等。把这些小问题都解决一遍,你才算真正入门了。
进阶:理解Pod和容器的关系
很多人搞不清楚Pod和容器的区别,我觉得用个比喻来说明最直观:Pod就像一个房间,容器就是房间里的人。一个房间可以住多个人,但通常情况下,一个房间住一个人最舒服。
在K8s里,Pod是最小的部署单元。一个Pod里可以跑多个容器,但大多数时候,你只需要一个容器。比如你的Nginx应用,一个Pod跑一个Nginx容器就够了。
# 多容器Pod的例子
apiVersion: v1
kind: Pod
metadata:
name: log-collector
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
- name: fluentd
image: fluentd:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/fluentd
volumes:
- name: shared-logs
emptyDir: {}
这个例子里,应用容器和日志收集容器共享一个Volume,这样日志收集容器就能读到应用容器产生的日志。这种模式在实际生产环境中很常见。
从单应用到微服务:先别急着拆
我见过太多人一开始就搞微服务,把本来一个应用拆成十几个服务,结果部署、调试、运维全乱套。我的建议是:先把整个应用跑起来,再考虑怎么拆。
假设你现在有一个电商应用,包含前端、后端、数据库。先把它整体部署到K8s上,搞清楚各个组件之间的关系:
# 完整的电商应用部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: my-frontend:latest
ports:
- containerPort: 3000
env:
- name: API_URL
value: "http://backend-service:8080"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: my-backend:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
app: backend
ports:
- port: 8080
targetPort: 8080
type: ClusterIP
部署完之后,你可以测试一下前端能不能访问后端:
kubectl port-forward svc/frontend-service 8080:80
然后在浏览器里访问http://localhost:8080,看看能不能正常访问。如果能,说明你的服务间通信没问题。
配置管理:ConfigMap和Secret
应用跑起来之后,你会发现配置是个大问题。硬编码在镜像里肯定不行,每个环境都要打包一次镜像太麻烦了。K8s提供了ConfigMap和Secret来解决这个问题。
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: "mysql-service"
DATABASE_PORT: "3306"
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
---
# Secret
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
data:
DB_PASSWORD: bXlwYXNzd29yZA== # 这是"mypassword"的base64编码
API_KEY: YWJjZGVmZw==
使用的时候,你可以把配置挂载到容器里,或者通过环境变量注入:
spec:
containers:
- name: app
image: my-app:latest
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secret
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-config
这样你的应用配置就和镜像解耦了,不同环境可以用不同的ConfigMap,换配置不需要重新打包镜像。
服务发现与负载均衡
K8s里的Service是整个架构的核心概念之一。很多人不太理解Service到底有什么用,我直接告诉你:没有Service,你的应用就是孤岛。
Service有三种主要类型:ClusterIP、NodePort、LoadBalancer。ClusterIP是默认的,只能在集群内部访问。NodePort会在每个节点上开一个端口,让你能从外部访问。LoadBalancer会调用云服务商的API,创建一个外部负载均衡器。
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
这里我用了AWS的注解来指定使用NLB(Network Load Balancer),这是生产环境的常见做法。不同类型的云服务商有不同的注解,你需要根据实际环境调整。
Ingress:流量的智能调度
当你的应用越来越复杂,需要多个域名、HTTPS、路径路由时,Service就不够用了。Ingress就是来解决这个问题的。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: main-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 8080
这个配置做了三件事:一是把example.com的流量路由到frontend-service,二是把/api路径的流量路由到backend-service,三是强制使用HTTPS。Ingress Controller(比如nginx-ingress)负责实际处理这些规则。
存储:Stateful应用怎么办
数据库、缓存这些有状态的应用,在K8s里需要特殊处理。K8s提供了PersistentVolume(PV)和PersistentVolumeClaim(PVC)来解决这个问题。
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
---
# Deployment使用PVC
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretRef:
name: mysql-secret
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc
这里用PVC申请10G的存储,MySQL容器把这个存储挂载到/var/lib/mysql目录。这样即使Pod重启,数据也不会丢失。
对于有状态的应用,很多人会用StatefulSet而不是Deployment,因为StatefulSet能保证Pod的顺序和唯一性:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
spec:
serviceName: redis
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.0
ports:
- containerPort: 6379
volumeMounts:
- name: redis-data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
StatefulSet会为每个Pod创建独立的PVC,Pod的名字是固定的(redis-cluster-0, redis-cluster-1, redis-cluster-2),这在集群场景下非常重要。
HPA:让应用自动伸缩
生产环境最怕什么?流量突增时应用扛不住,流量低谷时资源浪费。HPA(Horizontal Pod Autoscaler)就是来解决这个问题的。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: frontend-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: frontend
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
这个配置会让前端应用根据CPU和内存的使用率自动调整副本数。当CPU使用率达到70%时,HPA会增加副本数;当使用率降到50%时,会减少副本数。记得minReplicas设为2,这样高可用有保障。
不过HPA需要Metrics Server才能工作,在本地集群里你可能需要单独安装:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
Liveness和Readiness探针:保证应用健康
部署到生产环境前,一定要配置健康检查。否则K8s不知道你应用在不在状态,可能把流量转到一个已经挂掉的Pod上。
spec:
containers:
- name: my-app
image: my-app:latest
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
Liveness探针检查应用是否还活着,如果连续3次失败,K8s会重启这个Pod。Readiness探针检查应用是否准备好接收流量,如果失败,这个Pod会从Service的端点中移除。两个探针的路径最好不一样,/healthz和/ready是常见的做法。
滚动更新:零停机发布
生产环境的发布最讲究的就是零停机。K8s的滚动更新机制能帮你做到这一点:
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
这个配置的意思是:更新时最多有1个Pod不可用,同时最多可以有1个额外的Pod。这样既能保证服务不中断,又能快速完成更新。
配合镜像标签,更新就很简单:
# 更新镜像版本
kubectl set image deployment/frontend frontend=my-frontend:v2.0
# 查看更新进度
kubectl rollout status deployment/frontend
# 如果不满意可以回滚
kubectl rollout undo deployment/frontend
从开发环境到生产环境:环境管理
开发、测试、生产环境配置肯定不一样,你不可能手动维护三份YAML。我推荐用Kustomize来管理:
base/
deployment.yaml
service.yaml
kustomization.yaml
overlays/
dev/
kustomization.yaml
patch.yaml
staging/
kustomization.yaml
patch.yaml
prod/
kustomization.yaml
patch.yaml
base目录下是公共配置,overlays目录下是每个环境的差异配置。比如prod环境可能需要更多的副本、更高的资源限制:
# overlays/prod/kustomization.yaml
bases:
- ../../base
replicas:
- name: frontend
count: 5
- name: backend
count: 3
patches:
- path: resource-patch.yaml
# overlays/prod/resource-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
template:
spec:
containers:
- name: frontend
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
部署的时候:
kubectl apply -k overlays/prod
安全:别忽视的事情
生产环境安全是底线。首先,不要用root运行容器:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: my-app
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
其次,使用NetworkPolicy限制Pod之间的网络访问:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
这个策略只允许frontend Pod访问backend Pod的8080端口,其他流量都被拒绝。
监控和日志:生产环境的眼睛
没有监控的生产环境就是盲人摸象。我推荐这三件套:
- Prometheus - 指标采集和存储
- Grafana - 可视化展示
- Loki 或 ELK - 日志收集
用Helm安装最简单:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack
安装完访问Grafana面板,你就能看到集群和应用的详细监控数据:CPU、内存、网络、请求量、错误率等等。
Helm:包管理工具
当你管理的应用越来越多,每个应用都维护一堆YAML文件会很痛苦。Helm就是K8s的包管理器:
# 创建Helm chart
helm create my-app
# 编辑values.yaml配置默认值
# 编辑templates/下的模板文件
# 本地测试
helm template my-app ./my-app
# 打包
helm package my-app
# 部署
helm install my-app ./my-app --set replicaCount=3
Helm让你可以把重复的配置抽象成模板,不同环境用不同的values文件,大大简化了管理。
CI/CD:自动化发布流程
手动部署在生产环境是不可靠的,你需要自动化。GitHub Actions是个不错的选择:
name: Deploy to K8s
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up kubectl
uses: azure/setup-kubectl@v3
- name: Configure kubeconfig
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG }}" > $HOME/.kube/config
- name: Deploy
run: |
kubectl apply -k overlays/prod
kubectl rollout status deployment/frontend
每次推送到main分支,CI/CD会自动部署到生产环境。配合镜像仓库,可以做到代码提交后自动构建、测试、部署。
迁移到生产环境的 checklist
最后给你一个实用的检查清单,帮你评估应用是否准备好了:
基础设施检查:
- [ ] 生产集群至少2个节点,分布在不同可用区
- [ ] 配置了集群级别的备份策略
- [ ] 监控告警已配置并测试过
- [ ] 日志采集已配置
应用检查:
- [ ] 所有环境变量通过ConfigMap/Secret管理
- [ ] 健康检查探针已配置并验证
- [ ] 资源限制已设置
- [ ] 存储已持久化
- [ ] 镜像已打标签并推到仓库
安全检查:
- [ ] 容器不以root运行
- [ ] 网络策略已配置
- [ ] Secret已加密存储
- [ ] 镜像扫描无高危漏洞
运维检查:
- [ ] 回滚方案已测试
- [ ] 文档已更新
- [ ] 值班人员已培训
一点真心话
我见过太多人一上来就想搞微服务、搞DevOps、搞GitOps,结果连一个Pod都跑不利索。我的建议是:先从一个简单的应用开始,跑通部署、更新、回滚这些基本流程,再逐步添加更多功能。K8s的学习曲线确实陡,但每一步都算数。
有问题别怕问,官方文档、社区论坛、GitHub Issues都是好地方。实在搞不定,找身边有经验的同事聊聊,或者发个帖子,一般都能得到帮助。
记住,K8s不是银弹,它解决的是复杂应用部署和运维的问题。如果你的应用很简单,一个容器就够了,别为了用K8s而用K8s。当你的应用规模增长到一定程度,K8s的价值才会真正显现。
希望这篇分享对你有帮助,有任何问题随时交流。
