目录
3.4、限制其他命名空间中的pod访问podSelector匹配到的pod
5.1、创建运行nfs-provisioner需要的sa账号
5.5、创建pvc,通过storageclass动态生成pv
5.6、创建pod,挂载storageclass动态生成的pvc:storage-pvc
2.1、resource 的 requests 和 limits
一、K8S-Configmap资源
1、Configmap 概述
Configmap 是 k8s 中的资源对象,用于保存非机密性的配置的,数据可以用 key/value 键值对的形式保存,也可通过文件的形式保存。
1、Configmap 是 k8s 中的资源, 相当于配置文件,可以有一个或者多个 Configmap;
2、Configmap 可以做成 Volume,k8s pod 启动之后,通过 volume 形式映射到容器内部指定目录上;
3、容器中应用程序按照原有方式读取容器特定目录上的配置文件。
4、在容器看来,配置文件就像是打包在容器内部特定目录,整个过程对应用没有任何侵入。
configmap 注入方式有两种,一种将 configMap 做为存储卷,一种是将configMap 通过 env 中 configMapKeyRef 注入到容器中。
使用 ConfigMap 的限制条件
ConfigMap 需要在 Pod 启动前创建出来;
并且只有当 ConfigMap 和 Pod 处于同一命名空间时,才可以被 Pod 引用;
当 Pod 挂载 ConfigMap 绑定的目录时,目录下的目录并不会挂载到 Pod 内,只有目录下的文件会被挂载。
ConfigMap 在设计上不是用来保存大量数据的。在 ConfigMap 中保存的数据不可超过 1MiB。如果你需要保存超出此尺寸限制的数据,可以考虑挂载存储卷或者使用独立的数据库或者文件服务。
2、Configmap 创建方法
2.1、命令行直接创建
直接在命令行中指定 configmap 参数创建,通过--from-literal 指定参数
[root@k8s-master ~]# kubectl create configmap nginx-config --from-literal=nginx_port=8080 --from-literal=server_name=myapp.nginx.com configmap/nginx-config created [root@k8s-master ~]# kubectl get cm NAME DATA AGE kube-root-ca.crt 1 21d nginx-config 2 6s [root@k8s-master ~]# kubectl get configmaps NAME DATA AGE kube-root-ca.crt 1 21d nginx-config 2 71s [root@k8s-master ~]# kubectl describe cm nginx-config Name: nginx-config Namespace: default Labels: <none> Annotations: <none> Data ==== nginx_port: ---- 8080 server_name: ---- myapp.nginx.com BinaryData ==== Events: <none>
2.2、通过指定文件创建(常用)
通过指定文件创建一个 configmap,--from-file=<文件>,若没有定义key,则使用文件名作为key,文件内容作为value。 vim nginx.conf
server { server_name www.nginx.com; listen 80; root /home/nginx/www/ }
[root@k8s-master configmap]# kubectl create configmap nginx-conf --from-file www.conf=./nginx.conf configmap/nginx-conf created [root@k8s-master configmap]# kubectl get cm NAME DATA AGE kube-root-ca.crt 1 21d nginx-conf 1 11s [root@k8s-master configmap]# kubectl describe cm nginx-conf Name: nginx-conf Namespace: default Labels: <none> Annotations: <none> Data ==== www.conf: ---- server { server_name www.nginx.com; listen 80; root /home/nginx/www/ } BinaryData ==== Events: <none>
2.3、指定目录创建 configmap(常用)
[root@k8s-master configmap]# mkdir mysql-test [root@k8s-master configmap]# cd mysql-test/ [root@k8s-master mysql-test]# echo server_id=1 > mysql.cnf [root@k8s-master mysql-test]# echo server_id=2 > mysql2.cnf [root@k8s-master configmap]# kubectl create configmap mysql-cnf --from-file=/root/configmap/mysql-test/ configmap/mysql-cnf created #查看 configmap 详细信息 [root@k8s-master configmap]# kubectl describe cm mysql-cnf Name: mysql-cnf Namespace: default Labels: <none> Annotations: <none> Data ==== mysql.cnf: ---- server_id=1 mysql2.cnf: ---- server_id=2 BinaryData ==== Events: <none> [root@k8s-master configmap]# kubectl get cm mysql-cnf -o yaml apiVersion: v1 data: mysql.cnf: | server_id=1 mysql2.cnf: | server_id=2 kind: ConfigMap metadata: creationTimestamp: "2024-02-01T07:16:32Z" name: mysql-cnf namespace: default resourceVersion: "618449" uid: 3fc1c8ef-5ddd-477b-bc52-2ecf2de86e25
2.4、使用yaml文件创建
vim mysql-configmap.yaml
apiVersion: v1 kind: ConfigMap metadata: name: mysql labels: app: mysql data: master.cnf: | [mysqld] log-bin log_bin_trust_function_creators=1 lower_case_table_names=1 slave.cnf: | [mysqld] super-read-only log_bin_trust_function_creators=1 #上面中的 | 表示master.cnf文件是多行文件
[root@k8s-master configmap]# kubectl apply -f mysql-configmap.yaml configmap/mysql created [root@k8s-master configmap]# kubectl get cm NAME DATA AGE kube-root-ca.crt 1 21d mysql 2 9s nginx-conf 1 8m13s [root@k8s-master configmap]# kubectl get cm mysql -o yaml apiVersion: v1 data: master.cnf: | [mysqld] log-bin log_bin_trust_function_creators=1 lower_case_table_names=1 slave.cnf: | [mysqld] super-read-only log_bin_trust_function_creators=1 kind: ConfigMap metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","data":{"master.cnf":"[mysqld]\nlog-bin\nlog_bin_trust_function_creators=1\nlower_case_table_names=1\n","slave.cnf":"[mysqld]\nsuper-read-only\nlog_bin_trust_function_creators=1\n"},"kind":"ConfigMap","metadata":{"annotations":{},"labels":{"app":"mysql"},"name":"mysql","namespace":"default"}} creationTimestamp: "2025-09-01T17:21:52Z" labels: app: mysql name: mysql namespace: default resourceVersion: "513895" uid: 5ec3e8dc-bec8-45df-b9bd-c9a5d752f03f
5、创建pod关联configmap
apiVersion: v1 kind: Pod metadata: name: app-pod # Pod名称 spec: containers: - name: app-container image: busybox # 使用busybox镜像测试 imagePullPolicy: IfNotPresent command: ["sleep", "3600"] # 保持容器运行 # 方式1:将ConfigMap的键值对作为环境变量注入 env: - name: APP_PORT # 环境变量名称(自定义) valueFrom: configMapKeyRef: name: app-config # 引用的ConfigMap名称 key: app_port # 对应ConfigMap中的key - name: APP_DOMAIN valueFrom: configMapKeyRef: name: app-config key: app_domain # 方式2:将ConfigMap的内容挂载为文件到容器内 volumeMounts: - name: config-volume # 存储卷名称(需与下方volumes.name一致) mountPath: /etc/config # 容器内的挂载目录(ConfigMap内容会生成文件) # 定义存储卷,关联ConfigMap volumes: - name: config-volume configMap: name: app-config # 关联的ConfigMap名称
二、Secret资源对象
1、Secret概述
k8s secrets用于存储和管理一些敏感数据,比如密码,token,密钥等敏感信息。它把 Pod 想要访问的加密数据存放到 Etcd 中。然后用户就可以通过在 Pod 的容器里挂载 Volume 的方式或者环境变量的方式访问到这些 Secret 里保存的信息了。
Secret 类似于 ConfigMap,但专门用于保存机密数据。
2、使用命令创建
[root@k8s-master ~]# kubectl create secret generic dotfile --from-literal=username=admin --from-literal=password=123456 [root@k8s-master ~]# kubectl get secret dotfile -o yaml apiVersion: v1 data: password: MTIzNDU2 username: YWRtaW4= kind: Secret metadata: creationTimestamp: "2025-09-02T07:45:19Z" name: dotfile namespace: default resourceVersion: "621858" uid: ce3a3332-5b97-4af0-8312-ced355786e64 type: Opaque [root@k8s-master ~]# echo -n "YWRtaW4=" | base64 -d admin
3、使用yaml文件创建
以 yaml 方式创建需要你提前进行 base64加密,因为secret 挂载到容器后自动 base64 解码
[root@k8s-master01 ~]# echo -n "admin" | base64 YWRtaW4= [root@k8s-master01 ~]# echo -n "123456" | base64 MTIzNDU2
vim secret.yaml
apiVersion: v1 kind: Secret metadata: name: secret-volume namespace: default type: Opaque data: password: MTIzNDU2 username: YWRtaW4= immutable: true
通过将 Secret 的 immutable
字段设置为 true
创建不可更改的 Secret。
创建和使用方式与configmap十分雷同
三、Ingress资源对象
1、概述
Service对集群之外暴露服务的主要方式有两种:NotePort和LoadBalancer,但是这两种方式,都有一定的缺点:
NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显。
LB方式的缺点是每个service需要一个LB,浪费、麻烦,并且需要kubernetes之外设备的支持。
基于这种现状,kubernetes提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求
Ingress(以Nginx为例)的工作原理如下:
用户编写Ingress规则,说明哪个域名对应kubernetes集群中的哪个Service
Ingress控制器动态感知Ingress服务规则的变化,然后生成一段对应的Nginx反向代理配置
Ingress控制器会将生成的Nginx配置写入到一个运行着的Nginx服务中的pod,并动态更新
到此为止,其实真正在工作的就是一个Nginx了,内部配置了用户定义的请求转发规则
2、案例
2.1、验证-NodePort模式
操作前准备:安装i好ngress服务
vim ingress-nodeport.yaml
apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-deploy name: nginx-deploy spec: replicas: 2 selector: matchLabels: app: nginx-deploy template: metadata: labels: app: nginx-deploy spec: containers: - image: nginx:latest imagePullPolicy: IfNotPresent name: nginx ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: labels: app: nginx-deploy name: nginx-svc spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: nginx-deploy type: ClusterIP
[root@k8s-master ingress]# kubectl apply -f ingress-nodeport.yaml deployment.apps/nginx-deploy created service/nginx-svc created [root@k8s-master ingress]# kubectl get po NAME READY STATUS RESTARTS AGE nginx-deploy-7bd594f975-kbsh9 1/1 Running 0 28s nginx-deploy-7bd594f975-l9nkl 1/1 Running 0 28s [root@k8s-master ingress]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 21d nginx-svc ClusterIP 10.102.0.98 <none> 80/TCP 41s
修改ingress代理模式
[root@k8s-master ingress]# kubectl -n ingress-nginx edit svc ingress-nginx-controller 47 sessionAffinity: None 48 type: NodePort ##修改此处的类型 49 status: 50 loadBalancer: {} [root@k8s-master ingress]# kubectl -n ingress-nginx get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller NodePort 10.110.163.87 <none> 80:30113/TCP,443:30646/TCP 6d2h ingress-nginx-controller-admission ClusterIP 10.98.3.49 <none> 443/TCP 6d2h
设置Http代理
vim ingress-http.yaml
apiVersion: networking.k8s.io/v1 kind: Ingress # 创建一个类型为Ingress的资源 metadata: name: nginx-ingress # 这个资源的名字为 nginx-ingress spec: ingressClassName: nginx # 使用nginx rules: - host: nginx.jx.com # 访问此内容的域名 http: paths: - backend: service: name: nginx-svc # 对应nginx的服务名字,该规则的namespace必须与service的一致 port: number: 80 # 访问的端口 path: / # 匹配规则 pathType: Prefix # 匹配类型,这里为前缀匹配 ###### #Exact(精确匹配): #当 PathType 的值为 Exact 时,意味着服务的路由规则将仅在传入请求的路径与指定的路径完全相同时才会被匹配。 #例如,如果一个服务的路径配置为 /api/v1/resource 且 PathType 为 Exact,那么只有当请求的路径是 /api/v1/resource 时,该服务才会被选中处理请求,多一个字符或少一个字符都不会匹配,包括 /api/v1/resource/ 或者 /api/v1/resource?id=1 这样的请求路径都不会被该服务处理,这是一种非常严格的精确匹配规则。 #Prefix(前缀匹配): #当 PathType 的值为 Prefix 时,服务将匹配以指定路径作为前缀的请求路径。 #例如,如果一个服务的路径配置为 /api/v1 且 PathType 为 Prefix,那么 /api/v1、/api/v1/resource、/api/v1/resource/1 等以 /api/v1 开头的请求路径都会被该服务处理,只要请求路径以 /api/v1 开头,该服务就会处理该请求,而不要求请求路径完全等于 /api/v1。
[root@k8s-master ingress]# kubectl apply -f ingress-http.yaml ingress.networking.k8s.io/nginx-ingress created [root@k8s-master ingress]# kubectl get ingress NAME CLASS HOSTS ADDRESS PORTS AGE nginx-ingress nginx nginx.jx.com 80 19s [root@k8s-master ingress]# kubectl -n ingress-nginx get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ingress-nginx-controller-7d7455dcf8-5lqb9 1/1 Running 0 5d4h 10.244.36.91 k8s-node1 <none> <none> [root@k8s-master ingress]# kubectl -n ingress-nginx get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller NodePort 10.110.163.87 <none> 80:30113/TCP,443:30646/TCP 6d2h ingress-nginx-controller-admission ClusterIP 10.98.3.49 <none> 443/TCP 6d2h
在访问主机上编写hosts文件
vim /etc/hosts 加上
192.168.58.181 nginx.jx.com
[root@k8s-master ingress]# curl nginx.jx.com:30113 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> html { color-scheme: light dark; } body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
发现在集群内的主机除了使用master和pod节点的宿主机ip可以访问成功,其余ip访问不成功,在集群外的主机只有使用运行pod的node节点可以访问,其余都无法访问
2.2、验证-LoadBalancer模式
搭建metallb支持LoadBalancer
Metallb 在 Kubernetes 中的作用主要是为没有运行在如 AWS、GCP 等具有完善网络服务的云平台上的集群,提供网络负载均衡器的实现。
实现 LoadBalancer 服务类型:在 Kubernetes 中,Service 有多种类型,其中 LoadBalancer 类型通常需要外部的负载均衡器支持。Metallb 可以在缺乏原生云平台负载均衡支持的环境下,模拟实现 LoadBalancer 类型的 Service。它能够为应用提供可从集群外部访问的固定 IP 地址。
IP 地址分配与管理:负责在指定的 IP 地址范围(IP address pool)内,为 LoadBalancer 类型的 Service 分配 IP 地址,并确保这些 IP 地址的正确映射和管理,使外部流量能够准确地路由到相应的 Kubernetes 服务后端 Pod。
提供高可用的网络连接:通过实现 BGP(Border Gateway Protocol)或 Layer2 模式的负载均衡机制,确保即使在节点故障或网络波动的情况下,也能维持应用的外部网络连接的稳定性和可靠性。
操作前准备:安装metallb服务
vim IPAddressPool.yaml
apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: planip-pool #这里与下面的L2Advertisement的ip池名称需要一样 namespace: metallb-system spec: addresses: - 192.168.58.10-192.168.58.20 #自定义ip段
vim L2Advertisement.yaml
apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: planip-pool namespace: metallb-system spec: ipAddressPools: - planip-pool #这里需要跟上面ip池的名称保持一致
[root@k8s-master ingress]# kubectl -n ingress-nginx edit svc ingress-nginx-controller 47 sessionAffinity: None 48 type: LoadBalancer ##修改此处的类型 49 status: 50 loadBalancer: {}
[root@k8s-master ingress]# kubectl apply -f IPAddressPool.yaml ipaddresspool.metallb.io/planip-pool created [root@k8s-master ingress]# kubectl apply -f L2Advertisement.yaml l2advertisement.metallb.io/planip-pool created [[root@k8s-master ingress]# kubectl get ingress NAME CLASS HOSTS ADDRESS PORTS AGE nginx-ingress nginx nginx.jx.com 192.168.58.10 80 5m48s [root@k8s-master ingress]# kubectl get ingresses.networking.k8s.io NAME CLASS HOSTS ADDRESS PORTS AGE nginx-ingress nginx nginx.jx.com 192.168.58.10 80 6m3s [root@k8s-master ingress]# kubectl -n ingress-nginx get po -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ingress-nginx-controller-7d7455dcf8-5lqb9 1/1 Running 0 5d8h 10.244.36.91 k8s-node1 <none> <none> [root@k8s-master ingress]# kubectl -n ingress-nginx get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller LoadBalancer 10.110.163.87 192.168.58.10 80:30113/TCP,443:30646/TCP 6d7h ingress-nginx-controller-admission ClusterIP 10.98.3.49 <none> 443/TCP 6d7h
在访问的主机上编写hosts文件
192.168.58.10 nginx.jx.com
就可以实现访问
[root@localhost ~]# curl nginx.jx.com <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> html { color-scheme: light dark; } body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
四、Job & Cronjob资源对象
Job
Job 会创建一个或者多个 Pod,并将继续重试 Pod 的执行,直到指定数量的 Pod 成功终止。 随着 Pod 成功结束,Job 跟踪记录成功完成的 Pod 个数。 当数量达到指定的成功个数阈值时,任务(即 Job)结束。 删除 Job 的操作会清除所创建的全部 Pod。 挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。
一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。 当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job 对象会启动一个新的 Pod。
你也可以使用 Job 以并行的方式运行多个 Pod。
Cronjob
CronJob 创建基于时隔重复调度的 Job。
CronJob 用于执行排期操作,例如备份、生成报告等。 一个 CronJob 对象就像 Unix 系统上的 crontab(cron table)文件中的一行。 它用 Cron 格式进行编写, 并周期性地在给定的调度时间执行 Job。
1、配置解读
1.1、Job配置
apiVersion: batch/v1 ## api版本 kind: Job ## 资源类型 metadata: ## 元数据 name: pi ## 资源名称 namespace: default ## 命名空间 spec: ## 详情 backoffLimit: 4 ## 最大失败次数 completions: 1 ## 要求完成的次数 parallelism: 1 ## 并发数量 activeDeadlineSeconds: 100 ## 最大运行时间 ttlSecondsAfterFinished: 20 ## ttl时间 template: ## Pod配置模板 spec: ## 规约 containers: ## 容器信息 - name: pi ## 容器名称 image: perl:5.34.0 ## 镜像 command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] ## 容器运行命令 imagePullPolicy: IfNotPresent restartPolicy: Never ## 重启策略
1.2、Cronjob配置
apiVersion: batch/v1 ## api版本 kind: CronJob ## 资源类型 metadata: ## 元数据 name: hello ## 资源名称 namespace: default ## 命名空间 spec: ## 详情 schedule: "* * * * *" ## 定时配置 successfulJobsHistoryLimit: 3 ## 保留运行完成pod的历史数量 jobTemplate: ## job配置模板 spec: ## job详细配置 template: ## pod配置模板 spec: ## pod详细配置 containers: ## 容器配置 - name: hello ## 容器名称 image: busybox:1.28 ## 容器镜像 imagePullPolicy: IfNotPresent ## 镜像拉取策略 command: ## 镜像启动运行命令 - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure ## 重启策略 ################################################################### imagePullSecrets: ## 镜像下载秘钥 - name: harbor-secret
重启策略 restartPolicy
Never:当 Pod 失败时,Job 控制器会启动一个新的 Pod
OnFailure:Pod 继续留在当前节点,但容器会被重新运行
kubectl get pod -o wide -w kubectl get job -o wide
kubectl get cj -o wide kubectl get pod -o wide -w
五、NetworkPolicy策略
1、概述
在Kubernetes中,NetworkPolicy是一项关键功能,它允许开发者定义和控制Pod之间的网络通信。
什么是NetworkPolicy?
NetworkPolicy是Kubernetes中用于定义Pod之间网络通信规则的资源对象。通过NetworkPolicy,开发者可以控制哪些Pod可以与另外哪些Pod通信,以及使用何种方式进行通信。这种细粒度的网络控制有助于提高集群的安全性,防止未经授权的访问和通信。
NetworkPolicy的基本原理
NetworkPolicy的工作原理基于以下几个核心概念:
Pod选择器(PodSelector)& namespaceSelector:NetworkPolicy使用标签选择器来选择特定的Pod。通过标签,可以将网络策略应用于特定的Pod群体。
Ingress规则:定义了允许从其他Pod进入被选中Pod的规则,包括允许的协议、端口范围等。
Egress规则:定义了允许被选中Pod访问其他Pod或外部网络的规则。
Peer Pod:NetworkPolicy中的规则是基于Peer Pod(对等Pod)的。通过选择Peer Pod,可以精确定义通信策略。
2、NetworkPolicy资源清单
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: test-network-policy namespace: default spec: podSelector: matchLabels: app: backend #选择带有 app=backend 标签的 Pod 作为目标 Pod ingress: #入站 - from: - podSelector: matchLabels: app: frontend # 允许带有 app=frontend 标签的 Pod 访问目标 Pod - ipBlock: cidr: 192.168.1.0/24 # 允许来自 192.168.1.0/24 网段的流量访问目标 Pod ports: - protocol: TCP port: 8080 # 只允许访问目标 Pod 的 8080 端口 egress: #出站 - to: - podSelector: matchLabels: app: database # 允许目标 Pod 访问带有 app=database 标签的 Pod ports: - protocol: TCP port: 3306 #目标 Pod 访问其他 Pod 时只允许使用 3306 端口
3、案例
3.1、允许相同Namespace下的Pod通信
【注意】提前给命名空间设置labels
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-same-namespace spec: podSelector: matchLabels: app: myapp policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: project: myproject egress: - to: - namespaceSelector: matchLabels: project: myproject
在上述清单文件中,我们创建了一个名为"allow-same-namespace"的NetworkPolicy,指定了Pod选择器为app: myapp
。该策略允许来自同一命名空间下标签为project: myproject
的Pod的Ingress和Egress通信。
3.2、限制外部访问
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-external-traffic spec: podSelector: matchLabels: app: sensitive-app policyTypes: - Ingress - Egress ingress: - from: - podSelector: {} egress: - to: - podSelector: {}
在这个示例中,我们创建了一个名为"deny-external-traffic"的NetworkPolicy,限制了标签为app: sensitive-app
的Pod的Ingress和Egress。该策略允许Pod之间的通信,但不允许访问任何外部网络。
3.3、指定端口范围
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-specific-ports spec: podSelector: matchLabels: app: frontend ingress: - ports: - protocol: TCP port: 80 - protocol: UDP port: 53
在这个示例中,我们创建了一个名为"allow-specific-ports"的NetworkPolicy,指定了标签为app: frontend
的Pod的Ingress规则。该规则允许从其他Pod进入的流量,其中包括TCP端口80和UDP端口53。
3.4、限制其他命名空间中的pod访问podSelector匹配到的pod
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-external-traffic namespace: myns1 spec: podSelector: matchLabels: app: alpine policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: project: myns1
3.5、只访问外部网络和同命名空间下的pod
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: alpine-communication-policy namespace: myns1 # 策略仅作用于 myns1 命名空间 spec: podSelector: matchLabels: app: alpine # 目标 Pod:myns1 中所有标签为 app: alpine 的 Pod policyTypes: - Ingress # 控制入站流量 - Egress # 控制出站流量 # ------------------------------ # 入站规则:仅允许 myns1 内 Pod 访问 # ------------------------------ ingress: - from: - podSelector: {} # 匹配 myns1 命名空间内的所有 Pod(无标签过滤) # 无端口限制(允许所有端口),如需限制端口可添加 ports 字段 # ------------------------------ # 出站规则:允许访问 myns1 内 Pod 和外部互联网,拒绝其他命名空间 # ------------------------------ egress: # 规则 1:允许访问 myns1 命名空间内的所有 Pod(包括自身) - to: - podSelector: {} # 匹配当前命名空间(myns1)的所有 Pod # 规则 2:允许访问外部互联网(非集群内部 IP) - to: - ipBlock: cidr: 0.0.0.0/0 # 允许所有 IPv4 地址(需排除集群内部 CIDR) except: # 重要:替换为集群实际的 Pod CIDR 和 Service CIDR,通过 except 排除了集群内部的 IP 段,最终仅允许访问集群外部的互联网 - 10.244.0.0/16 # 示例:Flannel 默认 Pod CIDR(需根据集群调整) - 10.96.0.0/12 # 示例:Kubernetes Service 默认 CIDR(需根据集群调整) # 规则 3:允许 DNS 解析(关键!否则无法访问外部域名) - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system # kube-dns 所在命名空间 podSelector: matchLabels: k8s-app: kube-dns # kube-dns Pod 标签(可能因集群不同而变化) ports: - protocol: UDP port: 53 # DNS UDP 端口 - protocol: TCP port: 53 # DNS TCP 端口(用于长查询)
通信方向 | 是否允许 | 说明 |
---|---|---|
其他命名空间 → 目标 Pod | 拒绝 | 仅允许 myns1 内的 Pod 访问 |
目标 Pod → 其他命名空间 | 拒绝 | 除非是 kube-system 中的 DNS 服务 |
目标 Pod ↔ myns1 内 Pod | 允许 | 同命名空间内完全互通 |
目标 Pod → 外部互联网 | 允许 | 可访问公网,需排除集群内部 IP 段 |
外部互联网 → 目标 Pod | 拒绝 | 禁止外部直接访问 |
六、RBAC认证中心
1、k8s安全管理:认证、授权、准入控制概述
k8s对我们整个系统的认证,授权,访问控制做了精密的设置;对于k8s集群来说,apiserver是整个集群访问控制的唯一入口,我们在k8s集群之上部署应用程序的时候,也可以通过宿主机的NodePort暴露的端口访问里面的程序,用户访问kubernetes集群需要经历如下认证过程:
认证->授权->准入控制(admination controller)
1.认证(Authenticating)是对客户端的认证,通俗点就是用户名密码验证
2.授权(Authorization)是对资源的授权,k8s中的资源无非是容器,最终其实就是容器的计算,网络,存储资源,当一个请求经过认证后,需要访问某一个资源(比如创建一个pod),授权检查会根据授权规则判定该资源(比如某namespace下的pod)是否是该客户可访问的。
3.准入(Admission Control)机制:
准入控制器(Admission Controller)位于 API Server中,在对象被持久化之前,准入控制器拦截对 API Server 的请求,一般用来做身份验证和授权。其中包含两个特殊的控制器:
Mutating Admission Webhook 和 Validating Admission Webhook。分别作为配置的变更和验证准入控制 webhook。
变更(Mutating)准入控制:修改请求的对象
验证(Validating)准入控制:验证请求的对象
准入控制器是在 API Server 的启动参数配置的。一个准入控制器可能属于以上两者中的一种,也可能两者都属于。当请求到达 API Server 的时候首先执行变更准入控制,然后再执行验证准入控制。
我们在部署 Kubernetes 集群的时候都会默认开启一系列准入控制器,如果没有设置这些准入控制器的话可以说你的 Kubernetes 集群就是在裸奔,应该只有集群管理员可以修改集群的准入控制器。
例如:会默认开启如下的准入控制器。
--admission-control=ServiceAccount,NamespaceLifecycle,NamespaceExists,LimitRanger,ResourceQuota,MutatingAdmissionWebhook,ValidatingAdmissionWebhook
k8s的整体架构也是一个微服务的架构,所有的请求都是通过一个GateWay,也就是kube-apiserver这个组件(对外提供REST服务),k8s中客户端有两类,一种是普通用户,一种是集群内的Pod,这两种客户端的认证机制略有不同,但无论是哪一种,都需要依次经过认证,授权,准入这三个机制。
2、创建用户并限制用户操作k8s集群
ssl认证
生成一个证书
(1)生成一个私钥
cd /etc/kubernetes/pki/ umask 077; openssl genrsa -out lucky.key 2048
(2)生成一个证书请求
openssl req -new -key lucky.key -out lucky.csr -subj "/CN=lucky"
(3)生成一个证书
openssl x509 -req -in lucky.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out lucky.crt -days 3650
在kubeconfig下新增加一个lucky这个用户
(1)把lucky这个用户添加到kubernetes集群中,可以用来认证apiserver的连接
[root@xianchaomaster1 pki]# kubectl config set-credentials lucky --client-certificate=./lucky.crt --client-key=./lucky.key --embed-certs=true
(2)在kubeconfig下新增加一个lucky这个账号
kubectl config set-context lucky@kubernetes --cluster=kubernetes --user=lucky
(3)切换账号到lucky,默认没有任何权限
[root@k8s-master01 pki]# kubectl config use-context lucky@kubernetes [root@k8s-master01 pki]# kubectl get pod Error from server (Forbidden): pods is forbidden: User "lucky" cannot list resource "pods" in API group "" in the namespace "default" ###切换到有权限的管理账户 [root@k8s-master01 pki]# kubectl config use-context kubernetes-admin@kubernetes
这个是集群用户,有任何权限;把lucky这个用户通过rolebinding绑定到clusterrole上,授予权限,权限只是在lucky这个名称空间有效
(1)把lucky这个用户通过rolebinding绑定到clusterrole上
[root@k8s-master01 pki]# kubectl create ns lucky [root@k8s-master01 pki]# kubectl create rolebinding lucky -n lucky --clusterrole=cluster-admin --user=lucky
(2)切换到lucky这个用户
[root@k8s-master01 pki]# kubectl config use-context lucky@kubernetes
(3)测试是否有权限
kubectl get pods -n lucky [root@k8s-master01 pki]# kubectl get pod -n lucky No resources found in lucky namespace. [root@k8s-master01 pki]# kubectl get sa -n lucky NAME SECRETS AGE default 0 2m10s #有权限操作这个名称空间 kubectl get pods [root@k8s-master01 pki]# kubectl get pod Error from server (Forbidden): pods is forbidden: User "lucky" cannot list resource "pods" in API group "" in the namespace "default" #没有权限操作其他名称空间
添加一个lucky的普通用户
useradd lucky kubectl config use-context kubernetes-admin@kubernetes cp -ar /root/.kube/ /home/lucky/ chown -R lucky:lucky /home/lucky/ vim /home/lucky/.kube/config ....... server: https://192.168.158.15:6443 name: kubernetes contexts: - context: cluster: kubernetes user: lucky name: lucky@kubernetes current-context: lucky@kubernetes .... chattr +i /home/lucky/.kube/config alias kubectl='kubectl -n lucky' su - lucky
七、持久化存储
常用的如下:
emptyDir hostPath nfs persistentVolumeClaim glusterfs cephfs configMap secret
1、emptyDir
emptyDir类型的Volume是在Pod分配到Node上时被创建,Kubernetes会在Node上自动分配一个目录,因此无需指定宿主机Node上对应的目录文件。 这个目录的初始内容为空,当Pod从Node上移除时,emptyDir中的数据会被永久删除。emptyDir Volume主要用于某些应用程序无需永久保存的临时目录,多个容器的共享目录等。
vim emptydir.yaml
apiVersion: v1 kind: Pod metadata: name: pod-empty spec: containers: - name: container-empty image: nginx imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /cache name: cache-volume ##与volumes中的name保持一致 volumes: - emptyDir: {} name: cache-volume
2、hostPath
hostPath Volume是指Pod挂载宿主机上的目录或文件。 hostPath Volume使得容器可以使用宿主机的文件系统进行存储,hostpath(宿主机路径):节点级别的存储卷,在pod被删除,这个存储卷还是存在的,不会被删除,所以只要同一个pod被调度到同一个节点上来,在pod被删除重新被调度到这个节点之后,对应的数据依然是存在的。
vim hostpath.yaml
apiVersion: v1 kind: Pod metadata: name: test-hostpath spec: containers: - image: nginx imagePullPolicy: IfNotPresent name: test-nginx volumeMounts: - mountPath: /usr/share/nginx/html name: test-volume volumes: - name: test-volume hostPath: path: /data type: DirectoryOrCreate
注意:
DirectoryOrCreate表示本地有/data目录,就用本地的,本地没有就会在pod调度到的节点自动创建一个
3、nfs
搭建nfs服务
所有主机都要安装
yum install -y nfs-utils
nfs服务器
mkdir /data systemctl enable --now nfs
vim /etc/exports
/data 192.168.58.0/24(rw,sync,no_root_squash,no_subtree_check)
使NFS配置生效
exportfs -avr
创建Pod,挂载NFS共享出来的目录
vim nfs.yaml
apiVersion: v1 kind: Pod metadata: name: test-nfs spec: containers: - name: test-nfs image: nginx imagePullPolicy: IfNotPresent ports: - containerPort: 80 protocol: TCP volumeMounts: - name: nfs-volumes mountPath: /usr/share/nginx/html volumes: - name: nfs-volumes nfs: path: /data #共享目录 server: 192.168.58.170 ##nfs服务器地址
kubectl apply -f nfs.yaml kubectl get pods -o wide
4、PVC
4.1、创建nfs共享目录
#在宿主机创建NFS需要的共享目录 [root@k8s-master] ~]# mkdir /data/v{1..10} -p #配置nfs共享宿主机上的/data/v1..v10目录 [root@k8s-master ~]# cat /etc/exports /data 192.168.58.0/24(rw,no_root_squash) /data/v1 192.168.58.0/24(rw,no_root_squash) /data/v2 192.168.58.0/24(rw,no_root_squash) /data/v3 192.168.58.0/24(rw,no_root_squash) /data/v4 192.168.58.0/24(rw,no_root_squash) /data/v5 192.168.58.0/24(rw,no_root_squash) /data/v6 192.168.58.0/24(rw,no_root_squash) /data/v7 192.168.58.0/24(rw,no_root_squash) /data/v8 192.168.58.0/24(rw,no_root_squash) /data/v9 192.168.58.0/24(rw,no_root_squash) /data/v10 192.168.58.0/24(rw,no_root_squash) #重新加载配置,使配置成效 [root@k8s-master ~]# exportfs -arv
4.2、创建pv
vim pv.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: v1 spec: capacity: storage: 1Gi #pv的存储空间容量 accessModes: ["ReadWriteOnce"] nfs: path: /data/v1 #把nfs的存储空间创建成pv server: 192.168.166.3 #nfs服务器的地址 --- apiVersion: v1 kind: PersistentVolume metadata: name: v2 spec: capacity: storage: 2Gi accessModes: ["ReadWriteMany"] nfs: path: /data/v2 server: 192.168.166.3 --- apiVersion: v1 kind: PersistentVolume metadata: name: v3 spec: capacity: storage: 3Gi accessModes: ["ReadOnlyMany"] nfs: path: /data/v3 server: 192.168.166.3 --- apiVersion: v1 kind: PersistentVolume metadata: name: v4 spec: capacity: storage: 4Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/v4 server: 192.168.166.3 --- apiVersion: v1 kind: PersistentVolume metadata: name: v5 spec: capacity: storage: 5Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/v5 server: 192.168.166.3 --- apiVersion: v1 kind: PersistentVolume metadata: name: v6 spec: capacity: storage: 6Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/v6 server: 192.168.166.3 --- apiVersion: v1 kind: PersistentVolume metadata: name: v7 spec: capacity: storage: 7Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/v7 server: 192.168.166.3 --- apiVersion: v1 kind: PersistentVolume metadata: name: v8 spec: capacity: storage: 8Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/v8 server: 192.168.166.3 --- apiVersion: v1 kind: PersistentVolume metadata: name: v9 spec: capacity: storage: 9Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/v9 server: 192.168.166.3 --- apiVersion: v1 kind: PersistentVolume metadata: name: v10 spec: capacity: storage: 10Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/v10 server: 192.168.166.3
kubectl apply -f pv.yaml kubectl get pv
4.3、创建pvc,和符合条件的pv绑定
vim pvc.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: accessModes: ["ReadWriteMany"] resources: requests: storage: 2Gi
kubectl apply -f my-pvc.yaml
kubectl get pv #可以看到pvc会自动选择符合条件的pv自动绑定
4.4、创建pod,挂载pvc
vim pod_pvc.yaml
apiVersion: v1 kind: Pod metadata: name: pod-pvc spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent volumeMounts: - name: nginx-html mountPath: /usr/share/nginx/html volumes: - name: nginx-html persistentVolumeClaim: claimName: my-pvc
kubectl apply -f pod-pvc.yaml
注:使用pvc和pv的注意事项
1、我们每次创建pvc的时候,需要事先有划分好的pv,这样可能不方便,那么可以在创建pvc的时候直接动态创建一个pv这个存储类,pv事先是不存在的
2、pvc和pv绑定,如果使用默认的回收策略retain,那么删除pvc之后,pv会处于released状态,我们想要继续使用这个pv,需要手动删除pv,kubectl delete pv pv_name,删除pv,不会删除pv里的数据,当我们重新创建pvc时还会和这个最匹配的pv绑定,数据还是原来数据,不会丢失。
5、 k8s存储类:storageclass
5.1、创建运行nfs-provisioner需要的sa账号
vim sa.yaml
apiVersion: v1 kind: Namespace metadata: name: newnfs --- apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner namespace: newnfs --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-provisioner-runner rules: - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "delete"] - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "update"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "update", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: newnfs roleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner namespace: newnfs rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list", "watch", "create", "update", "patch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner namespace: newnfs subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: newnfs roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io
kubectl apply -f sa.yaml
5.2、在nfs服务器上创建目录
mkdir /data -p
vim /etc/exports
/data 192.168.58.0/24(rw,no_root_squash)
5.3、安装nfs-provisioner程序
vim nfs.yaml
kind: Deployment apiVersion: apps/v1 metadata: name: nfs-client-provisioner namespace: newnfs spec: replicas: 1 selector: matchLabels: app: nfs-client-provisioner strategy: type: Recreate #设置升级策略为删除再创建(默认为滚动更新) template: metadata: labels: app: nfs-client-provisioner spec: serviceAccountName: nfs-client-provisioner #上一步创建的ServiceAccount名称 containers: - name: nfs-client-provisioner image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0 imagePullPolicy: IfNotPresent volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME # Provisioner的名称,以后设置的storageclass要和这个保持一致 value: storage-nfs - name: NFS_SERVER # NFS服务器地址,需和valumes参数中配置的保持一致 value: 192.168.166.3 - name: NFS_PATH # NFS服务器数据存储目录,需和volumes参数中配置的保持一致 value: /data - name: ENABLE_LEADER_ELECTION value: "true" volumes: - name: nfs-client-root nfs: server: 192.168.166.3 # NFS服务器地址 path: /data # NFS共享目录
kubectl apply -f nfs.yaml kubectl -n newnfs get pods | grep nfs #查看nfs-provisioner是否正常运行
nfsstate #查看nfs版本
5.4、创建storageclass,动态供给pv
vim pv.yaml
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: namespace: newnfs name: nfs-storage annotations: storageclass.kubernetes.io/is-default-class: "false" ## 是否设置为默认的storageclass provisioner: storage-nfs ## 动态卷分配者名称,必须和上面创建的deploy中环境变量“PROVISIONER_NAME”变量值一致 parameters: archiveOnDelete: "true" ## 设置为"false"时删除PVC不会保留数据,"true"则保留数据 mountOptions: - hard ## 指定为硬挂载方式 - nfsvers=4 ## 指定NFS版本,这个需要根据NFS Server版本号设置nfs
kubectl apply -f storage.yaml kubectl -n newnfs get storageclasses.storage.k8s.io
5.5、创建pvc,通过storageclass动态生成pv
vim pvc.yaml
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: storage-pvc namespace: newnfs spec: storageClassName: nfs-storage ## 需要与上面创建的storageclass的名称一致 accessModes: - ReadWriteOnce resources: requests: storage: 1Mi
kubectl apply -f pvc.yaml kubectl -n newnfs get pvc kubectl -n newnfs get pv
5.6、创建pod,挂载storageclass动态生成的pvc:storage-pvc
vim test-pod.yaml
kind: Pod apiVersion: v1 metadata: name: read-pod namespace: newnfs spec: containers: - name: read-pod image: nginx imagePullPolicy: IfNotPresent volumeMounts: - name: nfs-pvc mountPath: /usr/share/nginx/html restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: storage-pvc
kubectl apply -f test-pod.yaml kubectl -n newnfs get po kubectl -n newnfs get pv kubectl -n newnfs get pvc
八、特殊容器
1、init初始化容器
Init Container就是用来做初始化工作的容器,可以是一个或者多个,如果有多个的话,这些容器会按定义的顺序依次执行,只有所有的Init Container执行完后,主容器才会被启动。一个Pod里面的所有容器是共享数据卷和网络命名空间的,所以Init Container里面产生的数据可以被主容器使用到的。
Init容器与普通容器区别
Init容器总是运行到成功为止。
每个Init容器都必须在下一个Init容器启动之前成功完成。
如果Pod的Init容器失败,Kubenetes会不断地重启该Pod,直到Init容器成功为止。然而,如果Pod对应的restartPolicy为Never,它不会重新启动
vim init-test.yaml
apiVersion: v1 kind: Pod metadata: name: init-demo labels: demo: init-demo spec: containers: - name: init-demo image: busybox:1.28 imagePullPolicy: IfNotPresent command: ['sh', '-c', 'echo The app is running! && sleep 3600'] initContainers: - name: init-demo1 image: busybox:1.28 imagePullPolicy: IfNotPresent command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice;sleep 2; done;'] - name: init-demo2 image: busybox:1.28 imagePullPolicy: IfNotPresent command: ['sh', '-c', 'until nslookup mysql; do echo waiting for mysql; sleep 2;done;'] --- apiVersion: v1 kind: Service metadata: name: myservice spec: ports: - port: 5566 targetPort: 6655 protocol: TCP --- apiVersion: v1 kind: Service metadata: name: mysql spec: ports: - port: 8899 targetPort: 9988 protocol: TCP
[root@k8s-master01 ~]# kubectl apply -f init-test.yaml [root@k8s-master01 ~]# kubectl get pods NAME READY STATUS RESTARTS AGE init-demo 0/1 Init:0/2 0 6s [root@k8s-master01 ~]# kubectl get pods NAME READY STATUS RESTARTS AGE init-demo 1/1 Running 0 4m24s [root@k8s-master01 ~]# kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.10.0.1 <none> 443/TCP 13m myservice ClusterIP 10.10.202.229 <none> 5566/TCP 40s mysql ClusterIP 10.10.222.227 <none> 8899/TCP 3
2、临时容器 Ephemeral Containers
临时容器与其他容器的不同之处在于,它们缺少对资源或执行的保证,并且永远不会自动重启,因此不适用于构建应用程序。
用途:
当由于容器崩溃或容器镜像不包含调试工具而导致 kubectl exec 无用时, 临时容器对于交互式故障排查很有用。
vim pod-nginx.yaml
apiVersion: v1 kind: Pod metadata: name: nginx-test namespace: default labels: app: nginx spec: containers: - name: nginx129 ports: - containerPort: 80 image: nginx imagePullPolicy: IfNotPresent
[root@k8s-master ephemeral-containers]# kubectl apply -f pod-nginx.yaml pod/nginx-test created [root@k8s-master ephemeral-containers]# kubectl get pod -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-test 1/1 Running 0 8s 10.244.169.157 k8s-node2 <none> <none>
创建临时容器
[root@k8s-master ephemeral-containers]kubectl debug -it nginx-test --image=busybox --target=nginx129 --image-pull-policy=IfNotPresentnt Targeting container "nginx129". If you don't see processes from this container it may be because the container runtime doesn't support this feature. Defaulting debug container name to debugger-ng2gk. / # ps PID USER TIME COMMAND 1 root 0:00 nginx: master process nginx -g daemon off; 29 101 0:00 nginx: worker process 30 101 0:00 nginx: worker process 31 101 0:00 nginx: worker process 32 101 0:00 nginx: worker process 33 root 0:00 sh 39 root 0:00 ps ##解析 #nginx-test是pod名 #--image=busybox是使用busybox镜像作为临时容器 #--target=nginx129中nginx129是容器名
九、调度管理
1、node节点选择器
1.1、nodeName
指定pod节点运行在哪个具体node上
[root@k8s-master ~]# cat pod-node.yaml
apiVersion: v1 kind: Pod metadata: name: demo-pod1 namespace: default labels: app: myapp env: dev spec: nodeName: k8s-node1 containers: - name: nginx-nodename ports: - containerPort: 80 image: nginx imagePullPolicy: IfNotPresent
[root@k8s-master ~]# kubectl apply -f pod-node.yaml #查看pod调度到哪个节点 [root@k8s-master ~]# kubectl get pods -o wide
1.2、nodeSelector
指定pod调度到具有哪些标签的node节点上
#给node节点打标签,打个具有disk=ceph的标签 [root@k8s-master ~]# kubectl label nodes k8s-node1 node=worker01 #查看节点的详细信息 [root@hd1 node]# kubectl describe nodes k8s-node1 #定义pod的时候指定要调度到具有 node=worker01标签的node上 [root@k8s-master ~]# cat pod-1.yaml
apiVersion: v1 kind: Pod metadata: name: demo-pod-1 namespace: default labels: app: myapp env: dev spec: nodeSelector: node: worker01 containers: - name: nginx-nodename ports: - containerPort: 8080 image: tomcat:8.5-jre8-alpine imagePullPolicy: IfNotPresent
[root@k8s-master ~]# kubectl apply -f pod-1.yaml #查看pod调度到哪个节点 [root@k8s-master ~]# kubectl get pods -o wide
2、Pod亲和性
Pod 的亲和性与反亲和性有两种类型:
requiredDuringSchedulingIgnoredDuringExecution ##一定满足 preferredDuringSchedulingIgnoredDuringExecution ##尽量满足
podAffinity(亲和性):pod和pod更倾向腻在一起,把相近的pod结合到相近的位置,如同一区域,同一机架,这样的话pod和pod之间更好通信,比方说有两个机房,这两个机房部署的集群有1000台主机,那么我们希望把nginx和tomcat都部署同一个地方的node节点上,可以提高通信效率;
podAntiAffinity(反亲和性):pod和pod更倾向不腻在一起,如果部署两套程序,那么这两套程序更倾向于反亲和性,这样相互之间不会有影响。
2.1 pod节点亲和性
vim cat pod-required-affinity-demo.yaml
apiVersion: v1 kind: Pod metadata: name: pod-first labels: app2: myapp2 tier: frontend spec: containers: - name: myapp image: nginx imagePullPolicy: IfNotPresent --- apiVersion: v1 kind: Pod metadata: name: pod-second labels: app: backend tier: db spec: containers: - name: busybox image: busybox:1.28 imagePullPolicy: IfNotPresent command: ["sh","-c","sleep 3600"] affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app2, operator: 'In', values: ["myapp2"]} topologyKey: kubernetes.io/hostname
上面表示创建的pod必须与拥有app=myapp2标签的pod在一个节点上
[root@k8s-master ~]# kubectl apply -f pod-required-affinity-demo.yaml [root@k8s-master ~]# kubectl get pods -o wide
2.2 pod节点反亲和性
[root@hd1 node]# kubectl delete -f pod-required-affinity-demo.yaml [root@k8s-master ~]# cat pod-required-anti-affinity-demo.yaml
apiVersion: v1 kind: Pod metadata: name: pod1-first labels: app1: myapp1 tier: frontend spec: containers: - name: myapp image: nginx imagePullPolicy: IfNotPresent --- apiVersion: v1 kind: Pod metadata: name: pod2-second labels: app: backend tier: db spec: containers: - name: busybox image: busybox:1.28 imagePullPolicy: IfNotPresent command: ["sh","-c","sleep 3600"] affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app1 operator: 'In' values: - myapp1 topologyKey: kubernetes.io/hostname
[root@k8s-master ~]# kubectl apply -f pod-required-anti-affinity-demo.yaml [root@k8s-master ~]# kubectl get pods -o wide
2.3 换一个topologykey
[root@k8s-master ~]# kubectl label nodes k8s-node1 zone=foo [root@k8s-master ~]# kubectl label node k8s-node2 zone=foo --overwrite [root@k8s-master ~]# cat pod-first-required-anti-affinity-demo-1.yaml
apiVersion: v1 kind: Pod metadata: name: pod3-first labels: app3: myapp3 tier: frontend spec: containers: - name: nginx129 image: nginx imagePullPolicy: IfNotPresent
[root@k8s-master ~]# cat pod-second-required-anti-affinity-demo-2.yaml
apiVersion: v1 kind: Pod metadata: name: pod3-second labels: app: backend tier: db spec: containers: - name: nginx129 image: nginx imagePullPolicy: IfNotPresent affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app3 ,operator: 'In', values: ["myapp3"]} topologyKey: zone
[root@k8s-master ~]# kubectl apply -f pod-first-required-anti-affinity-demo-1.yaml [root@k8s-master ~]# kubectl apply -f pod-second-required-anti-affinity-demo-2.yaml [root@k8s-master ephemeral-containers]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod3-first 1/1 Running 0 11s 10.244.169.162 k8s-node2 <none> <none> pod3-second 0/1 Pending 0 6s <none> <none> <none> <none> #发现第二个节点是pending,因为两个节点node的标签都是foo,而且我们要求反亲和性,master节点上有污点,所以就会处于pending状态,如果在反亲和性这个位置把required改成preferred,那么也会运行。 #podaffinity:pod节点亲和性,pod倾向于哪个pod #nodeaffinity:node节点亲和性,pod倾向于哪个node
常见的 topologyKey
及含义
topologyKey 取值 |
含义与使用场景 | 典型标签来源 |
---|---|---|
kubernetes.io/hostname |
表示 “节点主机名”,每个节点的该标签值唯一(对应节点的 hostname )。 场景:确保 Pod 调度到不同节点(反亲和性),或强制 Pod 调度到指定节点(亲和性)。 |
Kubernetes 自动为节点添加 |
topology.kubernetes.io/zone |
表示 “可用区”(如云环境中的 AZ,如 us-west-2a 、cn-beijing-a ),同一可用区内的节点具有相同值。 场景:跨可用区部署 Pod 以提高容灾能力(反亲和性),或限制在同一可用区部署以降低网络延迟(亲和性)。 |
云厂商的 Kubernetes 集群自动添加(如 EKS、GKE);本地集群需手动添加 |
topology.kubernetes.io/region |
表示 “区域”(如 us-west-2 、cn-beijing ),比可用区范围更大,同一区域包含多个可用区。 场景:跨区域部署核心服务(如多区域容灾)。 |
云厂商的 Kubernetes 集群自动添加;本地集群需手动添加 |
failure-domain.beta.kubernetes.io/zone |
旧版本中表示 “可用区” 的标签键,功能与 topology.kubernetes.io/zone 一致,现已逐步被后者替代(但部分集群仍兼容)。 |
旧版本 Kubernetes 或部分云厂商集群 |
failure-domain.beta.kubernetes.io/region |
旧版本中表示 “区域” 的标签键,功能与 topology.kubernetes.io/region 一致,逐步被替代。 |
旧版本 Kubernetes 或部分云厂商集群 |
自定义标签(如 rack 、room ) |
用户手动为节点添加的拓扑标签,用于划分更细粒度的拓扑域(如机架 rack=rack-101 、机房 room=room-A )。 场景:在物理机集群中,避免 Pod 集中部署在同一机架(防止机架故障导致服务全挂)。 |
用户手动添加(如 kubectl label node <节点名> rack=rack-101 ) |
3、资源限制
#在resources中设置
4、污点(Taints)
设置污点后,一般Pod将不会调度到该节点上来。每次Pod都不会调度到master节点,那是因为搭建K8s集群的时候,K8s给master自带了一个污点。
4.1、查看污点
查看master上是否有污点,通过describe命令可以看到节点上的所有污点
[root@k8s-master ~]# kubectl describe node k8s-master ... Taints: node-role.kubernetes.io/control-plane:NoSchedule ...
还可以通过如下命令查看
[root@k8s-master ~]# kubectl get nodes k8s-master -o go-template={{.spec.taints}} [map[effect:NoSchedule key:node-role.kubernetes.io/control-plane]]
发现master上自带了一个没有value的污点 node-role.kubernetes.io/control-plane ,effect 为 NoSchedule,由于我们的Pod都没有容忍这个污点,所以一直都不会调度到master
4.2、污点类别
上面我们看到了污点有一个effect 属性为NoSchedule,其实还有其它两种类别分别是 PreferNoSchedule与 NoExecute
NoSchedule: 如果没有容忍该污点就不能调度进来。
PreferNoSchedule: 相当于NoExecute的一个软限制,如果没有容忍该污点,尽量不要把Pod调度进来,但也不是强制的。
NoExecute: 如果没有设置容忍该污点,新的Pod肯定不会调度进来, 并且已经在运行的Pod没有容忍该污点也会被驱逐。
4.3、设置污点
污点分别由key、value(可以为空)、effect 组成,设置命令如下
# 设置污点并不允许Pod调度到该节点 kubectl taint node k8s-node1 key=value:NoSchedule # 设置污点尽量不要让Pod调度到该节点 kubectl taint node k8s-node1 key=value:PreferNoSchedule # 设置污点,不允许Pod调度到该节点,并且且将该节点上没有容忍该污点的Pod将进行驱逐 kubectl taint node k8s-node1 key=value:NoExecute
4.4、删除污点
# 删除该key的所有污点 kubectl taint node k8s-node1 key- # 删除该key的某一个污点 kubectl taint node k8s-node1 key=value:NoSchedule- # 删除该key的某一个污点可以不写value kubectl taint node k8s-node1 key:NoSchedule-
4.5、污点测试
给k8s-node1节点设置一个污点,表示只有前台web应用才能调度,后端app应用不能调度
[root@k8s-master ~]# kubectl taint node k8s-node1 web=true:NoSchedule node/k8s-node1 tainted
编写 web-taint.yaml 内容如下,有三个Pod均没有容忍该污点
apiVersion: v1 kind: Pod metadata: name: web-taint1 spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent # 本地有不拉取镜像 --- apiVersion: v1 kind: Pod metadata: name: web-taint2 spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent # 本地有不拉取镜像 --- apiVersion: v1 kind: Pod metadata: name: web-taint3 spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent # 本地有不拉取镜像
启动Pod,观察Pod是否都被调度到k8s-node2节点
# 启动三个Pod [root@k8s-master taint]# kubectl apply -f web-taint.yaml pod/web-taint1 applyd pod/web-taint2 applyd pod/web-taint3 applyd # 查看pod详情发现均被调度到k8s-node2节点 [root@k8s-master taint]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-taint1 1/1 Running 0 13s 10.244.2.35 k8s-node2 <none> <none> web-taint2 1/1 Running 0 13s 10.244.2.34 k8s-node2 <none> <none> web-taint3 1/1 Running 0 13s 10.244.2.36 k8s-node2 <none> <none>
给k8s-node2节点添加污点 app=true:NoExecute,表示只有后端服务才能调度进来,并且已经在k8s-node2节点上运行的不是后端服务的节点将被驱逐
# 设置污点 [root@k8s-master taint]# kubectl taint node k8s-node2 app=true:NoExecute node/k8s-node2 tainted # 查看Pod详情,发现三个Pod已经被驱逐 [root@k8s-master taint]# kubectl get pod -o wide No resources found in default namespace.
从上面可以知道我们虽然设置了污点,但是我们的节点其实很正常,既然是正常节点,那么就可以有服务运行,这就需要用到容忍机制来运行这些Pod
5、容忍(Toletations)
如果需要Pod忽略Node上的污点,就需要给Pod设置容忍,并且是需要容忍该Pod上的所有污点。
通过 kubectl explain pod.spec.tolerations 可以查看容忍的配置项
5.1、配置项
tolerations: # 数组类型,可以设置多个容忍 - key: # 污点key operator: # 操作符,有两个选项 Exists and Equal 默认是Equal value: # 污点value,如果使用Equal需要设置,如果是Exists就不需要设置 effect: # 可以设置为NoSchedule、PreferNoSchedule、NoExecute,如果为空表示匹配该key下所有污点, tolerationSeconds: # 如果污点类型为NoExecute,还可以设置一个时间,表示这一个时间段内Pod不会被驱逐,过了这个时间段会立刻驱逐,0或者负数会被立刻驱逐
5.2、设置容忍
给Pod设置容忍k8s-node1节点的污点web=true:NoSchedule
编写 web-tolerations.yaml 内容如下,容忍污点web=true:NoSchedule
apiVersion: v1 kind: Pod metadata: name: web-tolerations spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent # 本地有不拉取镜像 tolerations: - key: web operator: Equal value: "true" effect: NoSchedule
启动web-tolerations,观察Pod是否正常运行
# 启动web-tolerations [root@k8s-master taint]# kubectl apply -f web-tolerations.yaml pod/web-tolerations applyd # Pod正常运行,且运行在k8s-node1节点 [root@k8s-master taint]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-tolerations 1/1 Running 0 8s 10.244.1.92 k8s-node1 <none> <none>
查看该Pod上的容忍
[root@k8s-master taint]# kubectl describe pod web-tolerations Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s web=true:NoSchedule
可以看到我们设置的Tolerations在里边,除了我们自己的,K8s还会给每个Pod设置两个默认Tolerations,并且Tolerations时间为五分钟,表示某些节点发生一些临时性问题,Pod能够继续在该节点上运行五分钟等待节点恢复,并不是立刻被驱逐,从而避免系统的异常波动。
编写 app-tolerations.yaml 内容如下,容忍污点app=true:NoExecute,并且只容忍60s
apiVersion: v1 kind: Pod metadata: name: app-tolerations spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent tolerations: - key: app operator: Exists effect: NoExecute tolerationSeconds: 60
启动app-tolerations,查看Pod是否能够正常运行,且运行在k8s-node2节点,等待60秒,查看Pod是否已被驱逐
# 启动app-tolerations [root@k8s-master taint]# kubectl apply -f app-tolerations.yaml pod/app-tolerations applyd # 查看详情,已经运行成功并且调度到了k8s-node2节点,此时运行59秒 [root@k8s-master taint]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES app-tolerations 1/1 Running 0 59s 10.244.2.37 k8s-node2 <none> <none> # 运行60秒查看,状态已经变为Terminating [root@k8s-master taint]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES app-tolerations 1/1 Terminating 0 60s 10.244.2.37 k8s-node2 <none> <none> # 立刻再次查看,已经被驱逐 [root@k8s-master taint]# kubectl get pod -o wide No resources found in default namespace.
如果一个节点没有任何污点,另一个节点有污点,并且Pod容忍了该污点,那么最终会调度到哪个节点呢?
删除k8s-node2节点的污点app=true:NoExecute,保留k8s-node1节点的web=true:NoSchedule,再次启动之前的web-tolerations.yaml 观察Pod所在节点
# 删除k8s-node2上的污点 [root@k8s-master taint]# kubectl taint node k8s-node2 app:NoExecute- node/k8s-node2 untainted # 启动Pod web-tolerations [root@k8s-master taint]# kubectl apply -f web-tolerations.yaml pod/web-tolerations applyd # Pod正常运行,并且运行在k8s-node2节点 [root@k8s-master taint]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-tolerations 1/1 Running 0 2s 10.244.2.38 k8s-node2 <none> <none>
上面表明,即使是容忍了该污点,如果有其它节点不需要容忍就可以调度的话,那还是会优先选择没有污点的机器,但是生产上往往设置污点就是为了给你这个Pod使用的,不能调度到其它节点,这个时候需要配合节点亲和性调度一起使用。
5.3、Equal 与 Exists
Exists 表示key是否存在所以无需设置value,Equal 则必须设置value需要完全匹配,默认为Equal
空的key配合Exists 可以匹配所有的key-value,也就是容忍所有的污点,生产上非常不建议使用
十、优先级
1、Pod优先级
PriorityClass
PriorityClass是一个全局资源对象,它定义了从优先级类名称到优先级整数值的映射。优先级在values字段中指定,可以设置小于10亿的整数值,值越大,优先级越高
PriorityClass还有两个可选字段:
globalDefault:用于设置默认优先级状态,如果没有任何优先级设置,Pod的优先级为零
description:用来配置描述性信息,告诉用户优先级的用途
优先级策略
非抢占优先:指的是在调度阶段优先进行调度分配,一旦容器调度完成就不可以抢占,资源不足时,只能等待,对应
preemptionPolicy: Never
抢占优先:强制调度一个Pod,如果资源不足无法被调度,调度程序会抢占(删除)较低优先级的Pod的资源,来保证高优先级Pod的运行,对应
preemptionPolicy: PreemptLowerPriority
1.1、非抢占优先级
# 定义优先级(队列优先) [root@k8s-master ~]# vim mypriority.yaml --- kind: PriorityClass # 资源对象类型 apiVersion: scheduling.k8s.io/v1 # 资源对象版本 metadata: name: high-non # 优先级名称,可在Pod中引用 globalDefault: false # 是否定义默认优先级(唯一) preemptionPolicy: Never # 抢占策略 value: 1000 # 优先级 description: non-preemptive # 描述信息 --- kind: PriorityClass apiVersion: scheduling.k8s.io/v1 metadata: name: low-non globalDefault: false preemptionPolicy: Never value: 500 description: non-preemptive [root@k8s-master ~]# kubectl apply -f mypriority.yaml priorityclass.scheduling.k8s.io/high-non created priorityclass.scheduling.k8s.io/low-non created [root@k8s-master ~]# kubectl get priorityclasses.scheduling.k8s.io NAME VALUE GLOBAL-DEFAULT AGE high-non 1000 false 12s low-non 500 false 12s system-cluster-critical 2000000000 false 45h system-node-critical 2000001000 false 45h
Pod资源文件
# 无优先级的 Pod [root@k8s-master ~]# cat nginx1.yaml --- kind: Pod apiVersion: v1 metadata: name: nginx1 spec: nodeName: k8s-node2 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent resources: requests: cpu: "1500m" # 低优先级 Pod [root@k8s-master ~]# cat nginx2.yaml --- kind: Pod apiVersion: v1 metadata: name: nginx2 spec: nodeName: k8s-node2 priorityClassName: low-non # 指定优先级的名称 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent resources: requests: cpu: "1500m" # 高优先级 Pod [root@k8s-master ~]# cat nginx3.yaml --- kind: Pod apiVersion: v1 metadata: name: nginx3 spec: nodeName: k8s-node2 priorityClassName: high-non # 指定优先级的名称 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent resources: requests: cpu: "1500m"
验证非抢占优先
[root@master ~]# kubectl apply -f nginx1.yaml pod/nginx1 created [root@master ~]# kubectl apply -f nginx2.yaml pod/nginx2 created [root@master ~]# kubectl apply -f nginx3.yaml pod/nginx3 created [root@master ~]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx1 1/1 Running 0 9s nginx2 0/1 Pending 0 6s nginx3 0/1 Pending 0 4s [root@master ~]# kubectl delete pod nginx1 pod "nginx1" deleted [root@master ~]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx2 0/1 Pending 0 20s nginx3 1/1 Running 0 18s # 清理实验 Pod [root@master ~]# kubectl delete pod nginx2 nginx3 pod "nginx2" deleted pod "nginx3" deleted
1.2、抢占策略
[root@master ~]# vim mypriority.yaml --- kind: PriorityClass apiVersion: scheduling.k8s.io/v1 metadata: name: high globalDefault: false preemptionPolicy: PreemptLowerPriority value: 1000 description: non-preemptive --- kind: PriorityClass apiVersion: scheduling.k8s.io/v1 metadata: name: low globalDefault: false preemptionPolicy: PreemptLowerPriority value: 500 description: non-preemptive [root@master ~]# kubectl apply -f mypriority.yaml priorityclass.scheduling.k8s.io/high created priorityclass.scheduling.k8s.io/low created [root@master ~]# kubectl get priorityclasses.scheduling.k8s.io NAME VALUE GLOBAL-DEFAULT AGE high 1000 false 12s low 500 false 12s system-cluster-critical 2000000000 false 45h system-node-critical 2000001000 false 45h
验证抢占优先级
# 默认优先级 Pod [root@master ~]# kubectl apply -f nginx1.yaml pod/nginx1 created [root@master ~]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx1 1/1 Running 0 6s # 高优先级 Pod [root@master ~]# sed 's,-non,,' nginx3.yaml |kubectl apply -f- pod/nginx3 created [root@master ~]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx3 1/1 Running 0 9s # 低优先级 Pod [root@master ~]# sed 's,-non,,' nginx2.yaml |kubectl apply -f- pod/nginx2 created [root@master ~]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx2 0/1 OutOfcpu 0 3s nginx3 1/1 Running 0 9s # 清理实验 Pod [root@master ~]# kubectl delete pod nginx2 nginx3 pod "nginx2" deleted pod "nginx3" deleted [root@master ~]# kubectl delete -f mypriority.yaml priorityclass.scheduling.k8s.io "high-non" deleted priorityclass.scheduling.k8s.io "low-non" deleted priorityclass.scheduling.k8s.io "high" deleted priorityclass.scheduling.k8s.io "low" deleted
2、节点优先级
在 Kubernetes 中,节点优先级(Node Priority)是用于指定节点的调度权重的设置。节点优先级主要用于调度器在选择节点时进行权衡和做出选择。
2.1、优先级类型
静态优先级(Static Priority)
可以手动为每个节点设置一个固定的优先级值。在节点对象的注解(Annotations)中使用 scheduler.alpha.kubernetes.io/priority
注解来定义节点的优先级。较高的优先级值表示节点的优先级较高。
亲和性优先级(Affinity Priority)
可以通过节点的亲和性(Affinity)设置来隐式地设置节点的优先级。优先级是根据亲和性规则和节点的亲和性权重(weight)来计算的。
服务质量优先级(Quality of Service Priority)
可以通过指定 Pod 的服务质量等级(Quality of Service Class)来设置节点的优先级。服务质量等级包括 Guaranteed、Burstable、BestEffort。较高的服务质量等级对应较高的优先级。
QoS(服务质量)
Requests 和 limits 的配置除了表明资源情况和限制资源使用之外,还有一个隐藏的作用:它决定了 pod 的 QoS 等级。
如果 pod 没有配置 limits ,那么它可以使用节点上任意多的可用资源。这类 pod 能灵活使用资源,但这也导致它不稳定且危险,对于这类 pod 我们一定要在它占用过多资源导致节点资源紧张时处理掉。优先处理这类 pod,而不是资源使用处于自己请求范围内的 pod 是非常合理的想法,而这就是 pod QoS 的含义:根据 pod 的资源请求把 pod 分成不同的重要性等级。
kubernetes 把 pod 分成了三个 QoS 等级:
Guaranteed:优先级最高,可以考虑数据库应用或者一些重要的业务应用。除非 pods 使用超过了它们的 limits,或者节点的内存压力很大而且没有 QoS 更低的 pod,否则不会被杀死
Burstable:这种类型的 pod 可以多于自己请求的资源(上限有 limit 指定,如果 limit没有配置,则可以使用主机的任意可用资源),但是重要性认为比较低,可以是一般性的应用或者批处理任务
Best Effort:优先级最低,集群不知道 pod的资源请求情况,调度不考虑资源,可以运行到任意节点上(从资源角度来说),可以是一些临时性的不重要应用。pod可以使用节点上任何可用资源,但在资源不足时也会被优先杀死
根据QoS进行资源回收
策略
Kubernetes 通过cgroup给pod设置QoS级别,当资源不足时先kill优先级低的 pod,在实际使用过程中,通过OOM分数值来实现,OOM分数值范围为0-1000。OOM 分数值根据OOM_ADJ参数计算得出。
对于Guaranteed
级别的 Pod,OOM_ADJ参数设置成了-998,对于Best-Effort
级别的 Pod,OOM_ADJ参数设置成了1000,对于Burstable
级别的 Pod,OOM_ADJ参数取值从2到999。
对于 kuberntes 保留资源,比如kubelet,docker,OOM_ADJ参数设置成了-999,表示不会被OOM kill掉。OOM_ADJ参数设置的越大,计算出来的OOM分数越高,表明该pod优先级就越低,当出现资源竞争时会越早被kill掉,对于OOM_ADJ参数是-999的表示kubernetes永远不会因为OOM将其kill掉。
QoS pods被kill掉场景与顺序
Best-Effort pods:系统用完了全部内存时,该类型 pods 会最先被kill掉。
Burstable pods:系统用完了全部内存,且没有 Best-Effort 类型的容器可以被 kill 时,该类型的 pods 会被kill 掉。
Guaranteed pods:系统用完了全部内存,且没有 Burstable 与 Best-Effort 类型的容器可以被 kill时,该类型的 pods 会被 kill 掉。
插件优先级(Plugin Priority)
可以编写插件来扩展调度器的功能,并为节点设置一些额外的优先级规则。
调度器在进行节点选择时,会根据节点的优先级进行权衡,优先选择具有较高优先级的节点。如果存在多个节点具有相同的优先级,则会根据其它因素(例如节点资源、亲和性规则等)进一步进行选择。
十一、Pod驱逐
1、驱逐机制
Soft Eviction Thresholds(软驱逐机制)
当node的内存/磁盘空间达到一定的阈值后,我要观察一段时间,如果改善到低于阈值就不进行驱逐,若这段时间一直高于阈值就进行驱逐。
Hard Eviction Thresholds( 强制驱逐机制)
简单的多,一旦达到阈值,立刻把pod从本地kill。
2、Pod eviction(Pod 驱逐)
当资源使用情况触发了驱逐条件时,kubelet会启动一个任务去轮流停止运行中的pod,直到资源使用状况恢复到阈值以下。以硬驱逐为例,整体流程是:
每隔一段时间从cadvisor中获取资源使用情况,发现触发了阈值;
从运行中的pod里找到QoS策略最开放的一个,比如策略为bestEffort的一个pod(即便这个pod没有吃多少内存,大部分内存是另一个策略为burstable,但内存使用率也很高的pod),kubelet停止该pod对应的所有容器,然后将pod状态更新为Failed。如果该pod长时间没有被成功kill掉,kubelet会再找一个pod进行驱逐。
检查内存用量是否恢复到阈值以下,如果没有,则重复第二步(这里就要干掉那个罪魁祸首了)。一直到内存使用情况恢复到阈值以下为止。
在 Kubernetes 中,当资源不足需要驱逐 Pod 时,系统会根据 Pod 的优先级(由 PriorityClass 的 value 决定)和 Pod 的 Quality of Service (QoS) 类别等进行决策。而调度优先级主要由 Priority 值确定。
2.1、resource 的 requests 和 limits
Requests(请求):Requests 是指容器在运行时所需的资源的最小数量。它们用于告诉 Kubernetes 调度器在选择节点时要为 Pod 预留多少资源。如果没有足够的请求资源可用,Pod 可能无法被调度到节点上。
Limits(限制):Limits 是指容器在运行时所允许使用的资源的最大数量。它们用于限制容器的资源使用,以防止容器占用过多的资源导致其他容器或节点受到影响。如果容器尝试使用超过其限制的资源量,Kubernetes 将会限制其资源使用,并可能触发容器的重新启动。
2.2、QoS 类别
BestEffort:没有设置 resource requests 和 limits 的 Pod。
Burstable:设置了 requests 或者 limits,但不完全相同。
Guaranteed:requests 和 limits 都设置了,并且两者值相等。
驱逐顺序:BestEffort(lowest) -> Burstable -> Guaranteed(highest)。不影响调度的优先级。
2.3、PriorityClass 和 Priority
PriorityClass(优先级类):PriorityClass 是一种用于调度和优先级管理的对象。它允许您为 Pod 分配优先级。PriorityClass 定义了一个优先级类别,其中包含一个整数值 value 表示优先级的相对值。较高的 value 值表示较高的优先级。通过将 Pod 与特定的 PriorityClass 关联,可以影响 Pod 的调度和驱逐顺序。
Priority(优先级):Priority 是一个整数值,直接应用于 Pod 对象。它表示 Pod 的绝对优先级。较高的 Priority 值表示较高的优先级。同样可以影响 Pod 的调度和驱逐顺序。
查看 PriorityClass
kubectl get priorityclasses
查看系统组件 controller-manager 使用的 PriorityClass
[root@k8s-master ~]# kubectl describe pod -n kube-system kube-controller-manager-k8s-master | grep -i priority Priority: 2000001000 Priority Class Name: system-node-critical
当集群中没有默认的 PriorityClass,也没有手动指定 Priority,那优先级的值就为 0。优先级的值越小,驱逐顺序越靠前,调度顺序越靠后。
还有一种情况是:尽管 PriorityClass 的 value 值大,但是 BestEffort 类型的 qos class 会比Burstable 或 Guaranteed 类别更容易被驱逐。当然还会有其他因素也会影响 pod 的驱逐顺序,但是影响力不如上面两种大,例如:Pod资源使用量越接近 limits,和 pod 运行时长越短等,那么这些 Pod 会被优先考虑驱逐。
当集群内有比较重要的服务时,可以把 Qos Class 设置为 Guaranteed,也就是都指定了 requests 和 limits 并且二者值相等,会有长时间运行稳定性的优势。且 Priority 的值尽可能设置大些,会有优先占用集群资源资源的优势。
在K8s 1.6之后还引入了Taint的两个新特性,TaintNodesByCondition与TaintBasedEvictions用来改善出现异常时对Pod的调度与驱逐问题
TaintNodesByCondition
特性如下(为节点添加NoSchedule的污点)
Node节点会不断的检查Node的状态,并且设置对应的Condition
不断地根据Condition的变更设置对应的Taint
不断地根据Taint驱逐Node上的Pod
主要污点如下:
node.kubernetes.io/not-ready 节点未就绪,节点Ready为False
node.kubernetes.io/unreachable 节点不可达
node.kubernetes.io/out-of-disk 磁盘空间已满
node.kubernetes.io/network-unavailable 网络不可用
node.kubernetes.io/unschedulable 节点不可调度
node.cloudprovider.kubernetes.io/uninitialized 如果 kubelet 从 外部 云服务商启动的,该污点用来标识某个节点当前为不可用状态,当云控制器 cloud-controller-manager 初始化这个节点后,kubelet 会将此污点移除
TaintBasedEvictions
特性添加的是NoExecute的污点,例如内存与磁盘有压力时,如果Pod没有设置容忍这些污点,则会被驱逐,以保证Node不会崩溃
主要污点如下:
node.kubernetes.io/memory-pressure 内存不足
node.kubernetes.io/disk-pressure 磁盘不足
1.13版本之后TaintNodesByCondition 与 TaintBasedEvictions 都是默认开启
3、Pod驱逐实战案例
3.1、基础驱逐配置
这是一个最常见的配置案例,用于防止节点因内存或磁盘耗尽而完全宕机。
kubelet 启动参数(通常在 /var/lib/kubelet/config.yaml
或 systemd 的 kubelet.service
中配置):
# /var/lib/kubelet/config.yaml 片段 apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration evictionHard: memory.available: "500Mi" # 可用内存低于500Mi时开始驱逐 nodefs.available: "10%" # 根磁盘可用空间低于10%时开始驱逐 nodefs.inodesFree: "5%" # 根磁盘inode低于5%时开始驱逐 imagefs.available: "15%" # 容器镜像磁盘可用空间低于15%时开始驱逐 evictionSoft: memory.available: "700Mi" nodefs.available: "15%" evictionSoftGracePeriod: memory.available: "1m30s" # 软阈值持续1分30秒后才触发驱逐 nodefs.available: "2m" # 软阈值持续2分钟后才触发驱逐 evictionMaxPodGracePeriod: 60 # 驱逐Pod时,允许Pod体面终止的最大宽限期(秒) evictionPressureTransitionPeriod: 30s # 脱离压力状态后,需要持续多久才报告状态正常
对应 systemd 参数(如果使用命令行参数):
/usr/bin/kubelet \ --eviction-hard=memory.available<500Mi,nodefs.available<10%,nodefs.inodesFree<5%,imagefs.available<15% \ --eviction-soft=memory.available<700Mi,nodefs.available<15% \ --eviction-soft-grace-period=memory.available=1m30s,nodefs.available=2m \ --eviction-max-pod-grace-period=60 \ --eviction-pressure-transition-period=30s \ ...其他参数
3.2、为系统守护进程预留资源
这是强烈推荐的生产环境配置。通过为系统进程和 kubelet 本身预留资源,可以防止它们因资源竞争而被饿死,从而保证节点的稳定性。
kubelet 启动参数:
# /var/lib/kubelet/config.yaml 片段 apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration # 1. 设置驱逐阈值 evictionHard: memory.available: "500Mi" nodefs.available: "10%" # 2. 为核心系统预留资源!非常重要! systemReserved: memory: "1Gi" cpu: "500m" ephemeral-storage: "5Gi" kubeReserved: memory: "1Gi" cpu: "250m" ephemeral-storage: "1Gi" # 3. 强制执行预留策略 enforceNodeAllocatable: - pods - system-reserved - kube-reserved
systemReserved
: 为操作系统守护进程(如 sshd、udev)预留的资源。kubeReserved
: 为 Kubernetes 系统守护进程(如 kubelet、容器运行时)预留的资源。enforceNodeAllocatable
: 指定 kubelet 需要强制执行哪些预留策略。pods
必须包含在内。
工作原理: 节点的总资源 = Allocatable
(可分配给 Pod 的资源) + SystemReserved
+ KubeReserved
驱逐阈值是基于 Allocatable
来计算的吗?不是,驱逐阈值是基于节点的总容量(Capacity) 来监控的。但预留机制确保了系统核心组件有资源可用,即使 Pod 已经耗尽了 Allocatable
部分。
3.3、仅使用软驱逐阈值实现平滑处理
软阈值搭配宽限期,可以让 Pod 有机会在被迫驱逐前完成一些清理工作(如通知中心、完成当前请求),适用于对中断敏感的应用。
kubelet 启动参数:
evictionSoft: memory.available: "5%" nodefs.available: "10%" imagefs.available: "10%" evictionSoftGracePeriod: memory.available: "2m" # 内存压力持续2分钟后才驱逐 nodefs.available: "3m" # 磁盘压力持续3分钟后才驱逐 imagefs.available: "3m" evictionMaxPodGracePeriod: 90 # 给予Pod更长的体面终止时间
3.4、高级镜像垃圾收集策略
驱逐经常与镜像磁盘空间不足有关,配置积极的镜像垃圾收集可以预防驱逐。
kubelet 启动参数:
# 镜像垃圾收集策略 imageGCHighThresholdPercent: 85 # 当磁盘使用率达到85%时,触发镜像GC imageGCLowThresholdPercent: 80 # GC会一直清理,直到使用率降到80% imageMinimumGCAge: 2m0s # 镜像必须至少存在2分钟才会被GC清理,避免误删正在使用的中间层
最佳实践与总结
始终设置
evictionHard
:这是防止节点完全宕机的安全网。必须配置
systemReserved
和kubeReserved
:这是生产环境的黄金准则,能极大提高节点稳定性。预留值大小需根据节点规格和实际系统开销调整。理解软阈值的用途:用于需要“优雅驱逐”的场景,给应用和运维人员预留反应时间。
监控驱逐事件:使用
kubectl get events -w --field-selector reason=Evicted
或监控平台来关注集群中的驱逐事件,它们是指标应用资源规划不合理或节点压力过大的重要信号。合理设置 Pod 的
requests
和limits
:这是防止 Pod 被驱逐的第一道防线。Kubelet 在决定驱逐哪个 Pod 时,会优先驱逐那些实际使用量远超其requests
的Burstable
Pod。使用 Pod 优先级(PriorityClass):对于关键应用,可以创建高优先级的
PriorityClass
,并配置到 Pod 上。这样即使节点压力巨大,低优先级的 Pod 也会在高优先级的 Pod 之前被驱逐。