用华为云 CCE 部署一个生产可用的 K8s 应用
一个应用的容器化上线,真正费时间的不是 kubectl apply,而是它之前的镜像分层、以及它之后的探针、配额和弹性。下面这条流程在 CCE 上跑通一遍,把能少踩的坑一次说清。
一、先定结构
| 层 | 组件 | 作用 |
|---|---|---|
| 镜像 | SWR 组织 | 版本化交付物 |
| 编排 | CCE 集群 + Deployment | 副本管理、滚动更新 |
| 网络 | Service(ClusterIP)+ ELB Ingress | 集群内寻址、对外暴露 |
| 配置 | ConfigMap / Secret | 配置与环境解耦 |
| 弹性 | HPA + metrics-server | 按负载伸缩 |
| 观测 | AOM + 云原生日志 | 指标与日志 |
二、镜像:让层缓存真正生效
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
RUN addgroup -S app && adduser -S app -G app
USER app
EXPOSE 8080
CMD ["node", "dist/main.js"]
三条硬要求:COPY package*.json 必须早于 COPY . .,否则改一行代码就全量重装依赖;用多阶段构建,别把编译器带进运行镜像;非 root 运行,后面配 securityContext 才不会互相打架。
推 SWR:
docker login -u ${REGION}@${AK} -p ${LOGIN_KEY} swr.${REGION}.myhuaweicloud.com
docker build -t swr.${REGION}.myhuaweicloud.com/${ORG}/demo-api:1.4.0 .
docker push swr.${REGION}.myhuaweicloud.com/${ORG}/demo-api:1.4.0
标签永远不要用 latest。回滚时你需要一个确定的坐标。
三、配置外置
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-api-config
data:
LOG_LEVEL: "info"
UPSTREAM_URL: "http://backend.default.svc.cluster.local:9000"
---
apiVersion: v1
kind: Secret
metadata:
name: demo-api-secret
type: Opaque
stringData:
DB_PASSWORD: "${DB_PASSWORD}"
stringData 省掉手工 base64,但注意 Secret 只是编码不是加密,敏感值走 KMS 或外部密钥管理,别把明文 YAML 提交进仓库。
四、工作负载
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-api
spec:
replicas: 3
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels: { app: demo-api }
template:
metadata:
labels: { app: demo-api }
spec:
containers:
- name: api
image: swr.${REGION}.myhuaweicloud.com/${ORG}/demo-api:1.4.0
ports:
- containerPort: 8080
envFrom:
- configMapRef: { name: demo-api-config }
- secretRef: { name: demo-api-secret }
resources:
requests: { cpu: 200m, memory: 256Mi }
limits: { cpu: 1000m, memory: 512Mi }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet: { path: /livez, port: 8080 }
initialDelaySeconds: 20
periodSeconds: 20
failureThreshold: 3
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
maxUnavailable: 0 是滚动更新零中断的前提。requests 不写,调度器就是瞎的,节点会被超卖到 OOM;limits 不写,一个内存泄漏能拖垮整台节点。就绪探针和存活探针必须分开:就绪失败只摘流量,存活失败才重启,混用会导致启动慢的应用无限重启。
五、暴露
apiVersion: v1
kind: Service
metadata:
name: demo-api
spec:
selector: { app: demo-api }
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-api
annotations:
kubernetes.io/elb.class: union
kubernetes.io/elb.id: ${ELB_ID}
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-api
port: { number: 80 }
Ingress 建完去 ELB 控制台确认监听器和后端服务器组已自动挂载,健康检查路径要和就绪探针一致,否则会出现“Pod 是 Running 但 502”。
六、弹性
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: demo-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: demo-api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 65 }
behavior:
scaleDown:
stabilizationWindowSeconds: 300
阈值定 65% 而不是 80%,因为扩出来的 Pod 冷启动要几十秒,等打到 80% 再扩已经晚了。缩容加 5 分钟稳定窗口,防止流量抖动造成副本反复伸缩。
七、观测
kubectl top pod -l app=demo-api # 实时资源
kubectl get hpa demo-api -w # 伸缩动作
kubectl logs -f deploy/demo-api --tail=200
kubectl rollout status deploy/demo-api # 发布卡住时第一时间看这里
日志统一打 stdout,由 AOM 采集,不要往容器内文件写。发布失败先 kubectl rollout status,卡住就 kubectl rollout undo,别急着删 Pod 重建。
八、几条踩过的坑
- 镜像拉取失败先查 SWR 是否私有:私有组织需要在 CCE 侧建
imagePullSecrets,不要靠节点默认凭据。 readOnlyRootFilesystem: true后应用写临时目录会挂,给它单独挂一个emptyDir到/tmp。- 探针
initialDelaySeconds比应用真实启动时间短,是 CrashLoopBackOff 里最容易被忽略的一条。 - ConfigMap 改了不会自动生效,滚动重启一次:
kubectl rollout restart deploy/demo-api。 - 节点规格选型按
requests总和算,不是按limits;但内存limits要留 30% 余量给内核与突发。
一个应用能稳定跑在生产上,靠的是这套默认值:多阶段构建、非 latest 标签、requests 与 limits 分离、三种探针分工、滚动更新零不可用、HPA 带稳定窗口。这些值写进模板后,后面每个服务复制过去就行。
- 点赞
- 收藏
- 关注作者
评论(0)