K8s 云原生技术 2
1. 概述
在 Kubernetes 中,资源类型(Resource Type) 是 API 对象的抽象,用于描述集群中的各种实体,例如 Pod、Service、Deployment、ConfigMap 等。理解这些资源类型是高效管理集群的基础,可通过 kubectl api-resources 查看所有类型及缩写。
主要分类包括:
- 工作负载类:如 Pod(最小调度单元)、Deployment(无状态应用部署)、StatefulSet(有状态服务)、DaemonSet(每节点运行一个Pod)、Job/CronJob(一次性或定时任务)。
- 服务发现与负载均衡:Service(集群内访问入口)、Ingress(七层路由)、Endpoints(Pod IP 列表)。
- 存储管理:PersistentVolume、PersistentVolumeClaim、StorageClass。
- 配置与密钥:ConfigMap(非敏感配置)、Secret(敏感信息)。
- 权限与安全:ServiceAccount、Role/ClusterRole、NetworkPolicy。
- 集群管理:Namespace(资源隔离)、Node(节点信息)。
- 扩展类型:CRD(自定义资源)、AdmissionWebhook(动态准入控制)。
2. 资源类型
2.1 Pod — 最小调度单元
注意:一个Pod可以包含多个容器,但是一般情况下一个Pod内只含一个容器,我遇到一个Pod包含多个容器的情况目前只有Pod中同时包含主容器和一个 Init 容器的情况
- 是什么:可以理解成一台临时的”逻辑主机”,里面可以跑一个或多个容器。这些容器共享网络、存储。
- 通常不直接创建,而是由更上层的控制器管理,如 Deployment 或 StatefulSet 。
apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80一个 Pod 是一个运行实例,挂了就没了,所以需要有东西帮我们保持数量
带有 Init 容器的 Pod 示例:
Init 容器在 Pod 启动时先于主容器运行,常用于初始化操作(如等待依赖服务就绪、下载配置文件、初始化数据库等)。Init 容器必须成功退出后,主容器才会启动:
apiVersion: v1kind: Podmetadata: name: pod-with-initspec: initContainers: - name: wait-for-db # 这个先跑 image: busybox command: ['sh', '-c', 'until nc -z db-service 3306; do echo waiting for db; sleep 2; done'] - name: init-config # 然后跑这个 image: busybox command: ['sh', '-c', 'echo “server=db-service” > /config/db.conf'] volumeMounts: - name: config-vol mountPath: /config containers: - name: main-app # 最后才跑主容器 image: my-app:latest volumeMounts: - name: config-vol mountPath: /etc/app volumes: - name: config-vol emptyDir: {}Init 容器可以有多个,会按顺序依次执行。它们可以共享同一个 volume,很适合作配置准备工作
2.2 Deployment — 无状态应用
Deployment 是一个用于管理无状态应用的资源,用来监控和调度属于他的Pod并将其维持在规定数量
- 解决的问题:保证指定数量的 Pod 副本一直运行,支持滚动更新、回滚。
- 适用:Web 服务、API 等无状态应用(任意 Pod 可互换)。
apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployspec: replicas: 3 # 我要3个完全相同的Pod selector: matchLabels: app: nginx # 管理带这个标签的Pod template: # Pod模板 metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25Deployment 会创建 ReplicaSet,ReplicaSet 再来确保 Pod 数量。我们一般直接操作 Deployment
滚动更新策略示例:
Deployment 的一大优势是支持滚动更新——逐步替换旧版本 Pod,避免服务中断。你可以在 Deployment 中配置更新策略:
apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployspec: replicas: 5 strategy: type: RollingUpdate # 滚动更新 rollingUpdate: maxSurge: 2 # 最多临时多出 2 个 Pod maxUnavailable: 1 # 最多允许 1 个 Pod 不可用 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.26 # 改镜像版本就会触发滚动更新更新过程:先起 2 个新版 Pod → 等它们 Ready → 停 1 个旧版 Pod → 循环直到全换完。整个过程服务不中断。
回滚操作:
# 查看历史版本kubectl rollout history deployment/nginx-deploy
# 发现新版本有问题,一键回滚到上一个版本kubectl rollout undo deployment/nginx-deploy
# 回滚到指定版本kubectl rollout undo deployment/nginx-deploy --to-revision=22.3 StatefulSet — 有状态应用集
像 数据库,存储 这样的服务要依赖于稳定的数据源,并且要对外提供稳定的访问入口点,这样的服务就是有状态的
- 是什么:当你需要稳定的网络标识(固定的 Pod 名称如
mysql-0,mysql-1)和稳定的持久化存储(每个 Pod 绑自己的硬盘)时使用。 - 适用:数据库(MySQL、MongoDB)、消息队列等。
apiVersion: apps/v1kind: StatefulSetmetadata: name: mysqlspec: serviceName: mysql # 必须有一个无头Service,后面讲 replicas: 2 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 volumeMounts: - name: data mountPath: /var/lib/mysql volumeClaimTemplates: # 为每个Pod自动创建独立PVC - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10Gi每个 Pod 有稳定名字和专属硬盘,重启/迁移后数据不丢
StatefulSet 依赖的 Headless Service:
上面 StatefulSet 的 serviceName: mysql 对应一个 Headless Service(无头服务),它不分配 ClusterIP,而是为每个 Pod 提供固定的 DNS 记录:
apiVersion: v1kind: Servicemetadata: name: mysqlspec: clusterIP: None # 关键:None 表示 "无头" selector: app: mysql ports: - port: 3306有了这个 Service,
mysql-0.mysql永远指向第 0 号 Pod,mysql-1.mysql永远指向第 1 号 Pod——Pod 就算重启、漂移,DNS 名字也不变。这就是有状态服务的基石。
2.4 DaemonSet — 守护进程
这个可以理解成一个在每个节点上都跑的Pod
- 是什么:确保集群中每个(或某些)节点上都运行一个 Pod。
- 典型用途:日志收集器(Fluentd)、监控代理(Node Exporter)、CNI 网络插件。
apiVersion: apps/v1kind: DaemonSetmetadata: name: node-exporterspec: selector: matchLabels: name: node-exporter template: metadata: labels: name: node-exporter spec: containers: - name: node-exporter image: prom/node-exporter默认情况下 DaemonSet 只会在工作节点上部署 Pod。如果你的集群只有 master/control-plane 节点,需要加上 toleration 才能让 Pod 调度上去:
spec: template: spec: tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule containers: - name: node-exporter image: prom/node-exporter另一个常用配置:通过
nodeSelector限制只在某些节点上跑。比如你有一批带 SSD 的节点,想只在上面跑日志收集:nodeSelector: disktype: ssd
2.5 Job 与 CronJob — 一次性任务和定时任务
- Job:运行一个或多个 Pod,成功完成就结束。适用于数据迁移、模型训练等。
- CronJob:在指定时间(Cron 表达式)创建 Job,像 Linux 的 crontab。
apiVersion: batch/v1kind: Jobmetadata: name: data-importspec: template: spec: containers: - name: import image: my-import-tool restartPolicy: Never # 失败不重启apiVersion: batch/v1kind: CronJobmetadata: name: daily-cleanupspec: schedule: "0 3 * * *" # 每天凌晨3点 jobTemplate: spec: template: spec: containers: - name: cleanup image: cleanup-tool restartPolicy: Never刚刚我说的哪个 Init 容器可以看作是一个 Job , 在Pod被创建时执行
2.6 Service — 服务的访问入口
Pod 随时都会被调度,被销毁,被创建,没有稳定的访问入口,Service 正是用来解决这个问题
四种类型:
- ClusterIP(默认):仅集群内部访问。
- NodePort:在每个节点上开一个端口,
<节点IP>:<端口>即可访问。 - LoadBalancer(贵):调用云厂商的负载均衡器,对外暴露服务。
- ExternalName(没用过):将服务映射到外部域名。
apiVersion: v1kind: Servicemetadata: name: nginx-svcspec: type: ClusterIP selector: app: nginx # 流量发给带此标签的Pod ports: - port: 80 # Service的端口 targetPort: 80 # 容器的端口NodePort 示例——适合开发/调试,直接通过节点 IP 访问:
apiVersion: v1kind: Servicemetadata: name: nginx-nodeportspec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080 # 不指定也会自动分配 30000-32767 间的端口创建后,任意节点 IP +
:30080都能访问到 nginx。访问路径:<你的IP>:30080 → Service → nginx Pod:80
LoadBalancer 示例——生产环境对外暴露(云厂商会分配公网 IP):
apiVersion: v1kind: Servicemetadata: name: nginx-lbspec: type: LoadBalancer selector: app: nginx ports: - port: 443 targetPort: 80创建后
kubectl get svc nginx-lb可以看到EXTERNAL-IP,那就是你的公网入口。注意:LoadBalancer 通常要花钱,本地 minikube/kind 可以用minikube tunnel模拟。
2.7 Ingress — 反向代理
Service 只做四层负载均衡,Ingress 是七层(HTTP/HTTPS),可基于域名、路径将流量导到不同 Service
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-ingressspec: rules: - host: www.example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 8080 - path: / pathType: Prefix backend: service: name: web-service port: number: 80需要安装 Ingress Controller(如 treafik)才能工作。
配置 HTTPS/TLS:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-ingress-tlsspec: tls: - hosts: - www.example.com secretName: example-tls-secret # TLS 证书存在 Secret 里 rules: - host: www.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 80TLS Secret 可以从证书文件创建:
kubectl create secret tls example-tls-secret --cert=cert.pem --key=key.pem
IngressClass:如果集群有多个 Ingress Controller(如同时装了 traefik 和 nginx-ingress),需要在 Ingress 中通过
ingressClassName字段指定用哪一个,否则可能不工作。
2.8 ConfigMap 和 Secret — 配置管理
- ConfigMap:存明文配置,如应用参数、环境变量。
- Secret:存敏感信息,如密码、令牌。内容是 Base64 编码的,不是加密,使用时需配合 RBAC 等权限控制。
apiVersion: v1kind: ConfigMapmetadata: name: app-configdata: database_url: "db.internal:3306" log_level: "debug"---apiVersion: v1kind: Secretmetadata: name: db-credentialstype: Opaquedata: username: dXNlcg== # base64编码的"user" password: cGFzc3dvcmQ= # "password"在 Pod 中使用 ConfigMap 和 Secret 的三种方式:
方式一:注入为环境变量(整批导入)
apiVersion: v1kind: Podmetadata: name: app-env-demospec: containers: - name: app image: my-app:latest envFrom: - configMapRef: name: app-config # ConfigMap 中所有 key 变成环境变量 - secretRef: name: db-credentials # Secret 中所有 key 变成环境变量Pod 启动后,容器里就能直接拿到
$database_url、$log_level、$username、$password这些环境变量了。
方式二:注入为环境变量(逐个挑选)
有时候你不想把所有 key 都导进去,可以只选你需要的:
spec: containers: - name: app image: my-app:latest env: - name: DB_URL valueFrom: configMapKeyRef: name: app-config key: database_url # 只取这一个 key - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-credentials key: password # 只取这一个 key方式三:挂载为文件
ConfigMap/Secret 里的每个 key 会变成容器里的一个文件,文件名就是 key,文件内容就是 value:
apiVersion: v1kind: Podmetadata: name: app-file-demospec: containers: - name: app image: my-app:latest volumeMounts: - name: config-volume mountPath: /etc/app/config # ConfigMap 内容出现在这个目录 readOnly: true - name: secret-volume mountPath: /etc/app/secrets # Secret 内容出现在这个目录 readOnly: true volumes: - name: config-volume configMap: name: app-config - name: secret-volume secret: secretName: db-credentials挂载后,
cat /etc/app/config/database_url输出db.internal:3306,cat /etc/app/secrets/password输出解码后的密码。应用无需重启就能感知 ConfigMap 的更新(kubelet 会定期同步,但有延迟)。
从命令行快速创建:
# 从字面量创建 ConfigMapkubectl create configmap app-config \ --from-literal=database_url=db.internal:3306 \ --from-literal=log_level=debug
# 从文件创建 ConfigMapkubectl create configmap nginx-conf --from-file=nginx.conf
# 从字面量创建 Secretkubectl create secret generic db-credentials \ --from-literal=username=user \ --from-literal=password=s3cret!2.9 Volume、PV 与 PVC — 持久化存储
PV 是 Node 上事先准备好的存储资源,PVC 是 Pod 对这些资源的请求凭证
- Volume:Pod 内的临时存储(Pod 在数据在,Pod 删数据没)。
- PersistentVolume(PV):管理员提前准备好的存储资源(一块云硬盘、NFS共享等),独立于 Pod 存在。
- PersistentVolumeClaim(PVC):你(用户)对存储的需求声明,例如“我要 5Gi 的读写一次的磁盘”,K8s 会自动匹配或通过 StorageClass 动态创建 PV。
2.9.1 Volume — Pod 级,不是 K3s 内的资源类型
Volume 就是挂载到 Pod 内的一个目录(或文件)。它解决了同一 Pod 内多个容器共享文件的问题,但它的生命周期基本等于 Pod
- Pod 删,Volume 就没了(除非你用了特殊的底层卷,如云盘)。
- 它是直接定义在 Pod
spec.volumes里的,不需要另外创建资源。
常见的内置 Volume 类型:
- emptyDir:Pod 创建时生成一个空目录,Pod 删除就清空。适合缓存/临时文件。
- hostPath:把宿主机(Node)上的一个目录挂进去。Pod 漂移到别的 Node 就找不到原数据了,一般只给 DaemonSet 日志采集等用。
- configMap / secret:把配置或密钥“投射”成文件,只读。
- 云厂商类型(awsElasticBlockStore、gcePersistentDisk):可把云硬盘挂入 Pod,这样数据独立于 Pod。但由于每个 Pod 都要写死云盘 ID,太僵化,基本不这么用了。
举个 emptyDir 例子,你可以在 Pod 里这样写:
apiVersion: v1kind: Podmetadata: name: temp-demospec: containers: - name: writer image: busybox command: ['sh', '-c', 'echo hello > /data/message && sleep 3600'] volumeMounts: - name: cache-vol mountPath: /data volumes: - name: cache-vol emptyDir: {}Volume 的局限:数据随着 Pod 消亡而消失,无法应对数据库、持久化日志等需求。所以我们需要独立于 Pod 存在的存储对象
2.9.2 PV 和 PVC — 供给侧 和 需求侧
Kubernetes 将存储资源抽象成一对核心概念:**PersistentVolume(PV)**和 PersistentVolumeClaim(PVC),分别代表存储的“供给方”和“需求方”。这就像管理员提前挖了几个水池(PV),用户拿着水票(PVC)去认领
2.9.3 PersistentVolume(PV)— 集群级存储资源
- 是什么:集群管理员预先准备好的一块存储,比如云厂商的一块云硬盘、NFS 的一个共享目录、Ceph 的一个 RBD 块设备
- 独立于任何 Pod 存在,生命周期完全独立。即使没有 Pod 使用,PV 也依然存在,呃,除非你和我一样不小心
kubectl delete -f .了 - 是集群范围的对象(不属于某个 Namespace)
示例 PV 定义(使用 hostPath,适合本地测试):
apiVersion: v1kind: PersistentVolumemetadata: name: my-local-pvspec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain hostPath: # 使用宿主机上的目录 path: "/mnt/data"⚠️ hostPath 只适合单节点/测试环境:Pod 一旦漂移到其他节点就找不到数据了。生产环境请用下面的 NFS 或云盘。
示例 PV 定义(使用 NFS,适合生产):
apiVersion: v1kind: PersistentVolumemetadata: name: my-nfs-pvspec: capacity: storage: 10Gi # 存储容量 accessModes: - ReadWriteMany # NFS 支持多节点同时读写 persistentVolumeReclaimPolicy: Retain nfs: # 实际的存储后端 server: 192.168.1.100 path: "/exports/data"这个 PV 告诉 Kubernetes:我这儿有个 10GB 的 NFS 共享,多节点读写,不用的时候别删。
下一节将介绍怎么用 StorageClass 配合 NFS Provider 实现存储自动配给
2.9.4 PersistentVolumeClaim(PVC)— 用户的“需求单”
- 是什么:你作为用户(开发者)不需要关心底层具体用什么存储,只需要声明你需要多大、什么访问模式的存储。
- 属于某个 Namespace。
示例 PVC 定义:
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: my-pvcspec: accessModes: - ReadWriteOnce # 我要求单节点读写 resources: requests: storage: 5Gi # 我只要 5GB一旦你创建这个 PVC,Kubernetes 会在已有的 PV 中自动匹配一个符合要求(容量 ≥ 5Gi,访问模式匹配)的 PV,并将两者绑定。绑定后,这个 PV 就专属于你的 PVC,其他 PVC 不能再用了
2.9.5 绑定机制与生命周期
- 静态供给:管理员手动创建了一堆 PV,用户在 Namespace 里创建 PVC,Kubernetes 寻找 容量足够、访问模式匹配、存储类一致 的 PV 绑定。
- 绑定后,PV 的状态变成
Bound,PVC 状态也是Bound。 - 当 PVC 被删除时,PV 怎么处理,由
persistentVolumeReclaimPolicy决定:- Retain(保留):PV 变为
Released状态,但数据还在,需要管理员手动清理并让 PV 重新可用。 - Delete(删除):PV 底层的存储资源(如云盘)也会被删除,数据彻底消失。
- Recycle(回收):基本废弃,就是执行
rm -rf清空数据。
- Retain(保留):PV 变为
2.9.6 在 Pod 里使用 PVC
Pod 不需要知道底层的云盘或 NFS,只需引用 PVC
apiVersion: v1kind: Podmetadata: name: app-with-storagespec: containers: - name: app image: nginx volumeMounts: - name: persistent-storage mountPath: /usr/share/nginx/html volumes: - name: persistent-storage persistentVolumeClaim: claimName: my-pvc # 就是刚才声明的 PVC 名字现在 Pod 不管漂移到哪个节点,挂载的都是同一块 PV 存储,数据不会因为 Pod 重启而丢失
2.9.7 访问模式(Access Modes)与使用场景速查
| 模式 | 缩写 | 含义 | 典型存储后端 |
|---|---|---|---|
| ReadWriteOnce | RWO | 只能被一个节点以读写方式挂载 | 云盘(块存储) |
| ReadOnlyMany | ROX | 可以被多个节点只读挂载 | 无实际产品常用 |
| ReadWriteMany | RWX | 可以被多个节点同时读写挂载 | NFS、CephFS、GlusterFS 等文件系统 |
大部分数据库(MySQL、PostgreSQL)用 RWO 块存储,性能好。多个 Web 容器要共享上传文件时,才需要 RWX 文件系统
ReadWriteMany 在某些场景下会引发脑裂,视情况使用
2.10 StorageClass — 动态存储供给
手动创建 PV 太累了:每来一个新应用需求,管理员就得提前去云平台创建一块云盘,再写成 PV。StorageClass 把这个过程自动化了,它定义了 ”什么样的存储类型” 以及 ”创建这种存储时该怎么做”
StorageClass 是”存储模板”,PVC 引用它就能自动创建 PV:
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: fast-ssdprovisioner: kubernetes.io/aws-ebs # 存储驱动(这里以 AWS 为例)parameters: type: gp3 # 驱动特有参数:SSD 类型 iops: “3000”reclaimPolicy: Delete # PVC 删除时自动删 PVvolumeBindingMode: WaitForFirstConsumer # 等 Pod 真正要用了再创建allowVolumeExpansion: true # 允许在线扩容provisioner 决定了用谁家的存储:
kubernetes.io/aws-ebs是 AWS、kubernetes.io/gce-pd是 Google Cloud、nfs.csi.k8s.io是 NFS CSI 驱动。本地测试可以用rancher.io/local-path(k3s 自带)或k8s.io/minikube-hostpath。
StorageClass 自动工作的全过程:
没有 StorageClass 之前,你需要:登录云平台 → 创建云盘 → 记下 ID → 写 PV YAML →
kubectl apply。有了 StorageClass 之后,只需要写一个 PVC,创建时带上storageClassName,剩下的全自动完成。
如何与 PVC 配合使用
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: dynamic-pvcspec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi storageClassName: fast-ssd # 指定 StorageClass 名字在 PVC 中通过
storageClassName指定即可。注意:如果 PVC 不指定storageClassName,会使用集群默认的 StorageClass(kubectl get storageclass看哪个后面有(default))。
常见 Provisioner 速查:
| 环境 | provisioner | 备注 |
|---|---|---|
| AWS EBS | kubernetes.io/aws-ebs | 块存储,RWO |
| Google Cloud | kubernetes.io/gce-pd | 块存储,RWO |
| Azure | kubernetes.io/azure-disk | 块存储,RWO |
| NFS(通过 CSI) | nfs.csi.k8s.io | 文件系统,RWX |
| k3s 默认 | rancher.io/local-path | 本地路径,适合开发/测试 |
| minikube | k8s.io/minikube-hostpath | 本地路径,适合开发/测试 |
| CephFS | cephfs.csi.ceph.com | 分布式文件系统,RWX |
2.11 Namespace — 资源隔离
将资源逻辑分组,实现多租户隔离、资源配额限制、避免名字冲突
kubectl create namespace devkubectl get pods -n dev2.12 ResourceQuota 与 LimitRange — 资源限制
- ResourceQuota:限制某个 Namespace 能使用的 CPU/内存总量、对象数量。
- LimitRange:限制该 Namespace 内单个 Pod 或容器的默认和边界资源。
apiVersion: v1kind: ResourceQuotametadata: name: dev-quota namespace: devspec: hard: requests.cpu: "10" requests.memory: 20Gi pods: "20"ResourceQuota 管的是 整个 Namespace 的总量上限。如果某个 Pod 没声明自己的资源需求,它可以”偷”用很多资源。这时候就需要 LimitRange 来约束单个 Pod。
LimitRange——限制该 Namespace 内单个 Pod/容器的资源边界:
apiVersion: v1kind: LimitRangemetadata: name: dev-limits namespace: devspec: limits: - type: Container default: # 不声明时的默认值 cpu: "500m" memory: "256Mi" defaultRequest: # 不声明 request 时的默认 request cpu: "200m" memory: "128Mi" max: # 单个容器能设的最大值(防流氓 Pod) cpu: "2" memory: "2Gi" min: # 单个容器的最小值 cpu: "100m" memory: "64Mi"ResourceQuota + LimitRange 配合:前者管总量(Namespace 级别),后者管单体(Pod/容器级别),两者搭配才能真正管住资源。
2.13 [ ⭐ ] HPA(HorizontalPodAutoscaler)— 自动扩缩容
根据 CPU 使用率(或自定义指标)自动调整 Deployment/StatefulSet 的副本数
apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: nginx-hpaspec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-deploy minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70需要安装 Metrics Server 来收集指标才能使用
HPA 的工作原理:HPA 每隔 15 秒(默认)检查目标 Pod 的 CPU/内存指标,如果实际使用率超过目标值(如上面的 70%),就增加副本;低于目标值,就减少副本。
用命令行直接创建更方便:
# 基于 CPU 自动伸缩,2-10 个副本,目标 CPU 利用率 70%kubectl autoscale deployment nginx-deploy --cpu-percent=70 --min=2 --max=10基于内存的 HPA 示例:
metrics:- type: Resource resource: name: memory target: type: Utilization averageUtilization: 80⚠️ 注意:CPU 弹性伸缩和内存弹性伸缩是独立配置的,可以同时使用。但内存不像 CPU 那样会被释放——进程用过的内存通常不会主动还给 OS,所以基于内存的 HPA 要谨慎使用,容易误触发扩容。
HPA+同时限制资源,推荐做法:
# Deployment 中给 Pod 设定 requests 和 limitscontainers:- name: nginx image: nginx:1.25 resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi"HPA 的
averageUtilization百分比是基于 requests 计算的,不是 limits。比如 requests.cpu=200m,实际用到 150m,利用率就是 75%。不设置 resources.requests 会数据不准,建议必配。
2.14 ServiceAccount — Pod 的”身份证”
Pod 通过 ServiceAccount 访问 K8s API,每个 Namespace 有默认的,可自建
apiVersion: v1kind: ServiceAccountmetadata: name: my-sa namespace: devPod 里指定
serviceAccountName: my-sa即可
ServiceAccount 和 RBAC 配合使用示例:
ServiceAccount 本身只是一个身份,需要配合 RBAC(下一节)才能真正”做事”。完整的流程是:
- 创建 ServiceAccount
- 创建 Role 定义权限
- 用 RoleBinding 把 SA 和 Role 绑在一起
- Pod 使用这个 SA
apiVersion: v1kind: Podmetadata: name: api-clientspec: serviceAccountName: my-sa # 使用自定义 SA containers: - name: client image: curlimages/curl command: ['sleep', '3600']这个 Pod 启动后会自动挂载 SA 的 token(在
/var/run/secrets/kubernetes.io/serviceaccount/token),用这个 token 就能访问 K8s API 了。验证:kubectl exec -it api-client -- curl -k https://kubernetes.default.svc
2.15 RBAC — 谁能做什么
- Role / ClusterRole:定义一组权限(能对哪些资源做什么操作)。
- RoleBinding / ClusterRoleBinding:把权限授予用户、组或 ServiceAccount。
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: namespace: dev name: pod-readerrules:- apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: read-pods namespace: devsubjects:- kind: ServiceAccount name: my-sa namespace: devroleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io2.16 NetworkPolicy — 网络防火墙
定义 Pod 之间、Pod 与外部之间的访问规则(需要 CNI 插件支持,如 Calico)
⚠️ 重要前提:Kubernetes 默认允许所有 Pod 之间通信。只有当你创建了 NetworkPolicy 后,被选中的 Pod 才会受限——且是”默认拒绝,显式允许”模式。
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: allow-from-frontendspec: podSelector: # 这个策略作用在哪些 Pod 上? matchLabels: role: db # 选中带 role=db 标签的 Pod policyTypes: - Ingress # 控制入站流量(别人访问数据库) ingress: - from: - podSelector: # 允许哪些 Pod 进来? matchLabels: role: frontend # 只有带 role=frontend 标签的 Pod ports: - protocol: TCP port: 3306 # 而且只能访问 3306 端口翻译成人话:只有 frontend Pod 可以通过 TCP 3306 端口访问数据库 Pod,其他一律拒绝。
更实用的示例——拒绝所有入站,只允许特定来源:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: db-isolationspec: podSelector: matchLabels: app: mysql policyTypes: - Ingress ingress: - from: - namespaceSelector: # 允许某个 Namespace 下所有 Pod matchLabels: kubernetes.io/metadata.name: backend - ipBlock: # 允许某个 IP 段 cidr: 10.0.0.0/24 ports: # ports 属于这个 ingress 规则 - protocol: TCP port: 3306建议:生产环境中,数据库的 NetworkPolicy 应该只放行业务 Pod 所在的 Namespace + 运维跳板机的 IP 段,形成最小权限访问。
2.17 自定义资源(CRD)— 让 K8s 懂得你的业务
如果上面所有原生资源都满足不了你,可以通过 CustomResourceDefinition 定义自己的资源类型,然后用 Operator 模式去管理。
什么时候需要 CRD? 假设你想用 K8s 管理数据库用户——原生资源只有 Pod、Service 等,没有”数据库用户”这个概念。这时你可以定义一个 CRD:
apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: databaseusers.example.com # 格式:<复数名>.<组名>spec: group: example.com # API 组 names: kind: DatabaseUser # 资源类型名(驼峰) plural: databaseusers # 复数,会出现在 URL 路径 singular: databaseuser # 单数 shortNames: - dbu # kubectl get dbu 就能查到 scope: Namespaced # Namespace 级别 versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: username: type: string database: type: string privileges: type: array items: type: string enum: [SELECT, INSERT, UPDATE, DELETE] required: [username, database]创建这个 CRD 后,就可以
kubectl apply -f一个kind: DatabaseUser的 YAML 了。配合 Operator(一个持续监控 CRD 的控制器),就能在创建 DatabaseUser 时自动去 MySQL 里建用户。
使用自定义资源的示例:
apiVersion: example.com/v1kind: DatabaseUsermetadata: name: app-readerspec: username: reader database: myapp privileges: - SELECTK8s 原生不懂 DatabaseUser 是什么,但 Operator 会 Watch 这个资源的创建/更新/删除事件,然后自动操作 MySQL。这就是 K8s 扩展性的核心——你可以让 K8s 管理任何东西。
常用的 Operator 例子:Prometheus Operator(自动管理监控)、cert-manager(自动管理证书)、Istio(服务网格)。它们都依赖 CRD。
3. 总结
拿一个典型的 Web 应用举例:
- Namespace 创建一个独立的
prod空间。 - ConfigMap/Secret 存好数据库地址和密码。
- PersistentVolumeClaim 申请一块云硬盘。
- StatefulSet 启动 MySQL,挂载 PVC,保证数据持久。
- Deployment 启动无状态的业务 Pod,注入配置。
- Service 分别给 MySQL 和业务 Pod 提供固定访问端点。
- Ingress 对外暴露域名,把流量指向业务 Service。
- HPA 监控业务 Pod CPU,自动加减副本。
- NetworkPolicy 限制只有业务 Pod 能访问数据库。
你可以用 kubectl api-resources 随时查看集群支持的所有资源类型。刚学习时重点掌握 Pod、Deployment、Service、ConfigMap、Secret、PVC、Ingress 和 Namespace,基本就能跑起大部分应用了。
4. 结尾
没了喵,谢谢欣赏

文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!