K8S-资源对象(小白的“升级打怪”成长之路)

发布于:2025-09-06 ⋅ 阅读:(20) ⋅ 点赞:(0)

目录

一、K8S-Configmap资源

1、Configmap 概述

2、Configmap 创建方法

2.1、命令行直接创建

2.2、通过指定文件创建(常用)

2.3、指定目录创建 configmap(常用)

2.4、使用yaml文件创建

5、创建pod关联configmap

二、Secret资源对象

1、Secret概述

2、使用命令创建

3、使用yaml文件创建

三、Ingress资源对象

1、概述

2、案例

2.1、验证-NodePort模式

2.2、验证-LoadBalancer模式

四、Job & Cronjob资源对象

1、配置解读

1.1、Job配置

1.2、Cronjob配置

五、NetworkPolicy策略

1、概述

2、NetworkPolicy资源清单

3、案例

3.1、允许相同Namespace下的Pod通信

3.2、限制外部访问

3.3、指定端口范围

3.4、限制其他命名空间中的pod访问podSelector匹配到的pod

3.5、只访问外部网络和同命名空间下的pod

六、RBAC认证中心

1、k8s安全管理:认证、授权、准入控制概述

2、创建用户并限制用户操作k8s集群

七、持久化存储

1、emptyDir

2、hostPath

3、nfs

4、PVC

5、 k8s存储类:storageclass

5.1、创建运行nfs-provisioner需要的sa账号

5.2、在nfs服务器上创建目录

5.3、安装nfs-provisioner程序

5.4、创建storageclass,动态供给pv

5.5、创建pvc,通过storageclass动态生成pv

5.6、创建pod,挂载storageclass动态生成的pvc:storage-pvc

八、特殊容器

1、init初始化容器

2、临时容器 Ephemeral Containers

九、调度管理

1、node节点选择器

1.1、nodeName

1.2、nodeSelector

2、Pod亲和性

2.1 pod节点亲和性

2.2 pod节点反亲和性

2.3 换一个topologykey

3、资源限制

4、污点(Taints)

4.1、查看污点

4.2、污点类别

4.3、设置污点

4.4、删除污点

4.5、污点测试

5、容忍(Toletations)

5.1、配置项

5.2、设置容忍

5.3、Equal 与 Exists

十、优先级

1、Pod优先级

1.1、非抢占优先级

1.2、抢占策略

2、节点优先级

2.1、优先级类型

十一、Pod驱逐

1、驱逐机制

2、Pod eviction(Pod 驱逐)

2.1、resource 的 requests 和 limits

2.2、QoS 类别

2.3、PriorityClass 和 Priority 

3、Pod驱逐实战案例

3.1、基础驱逐配置

3.2、为系统守护进程预留资源

3.3、仅使用软驱逐阈值实现平滑处理

3.4、高级镜像垃圾收集策略


一、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的工作原理基于以下几个核心概念:

  1. Pod选择器(PodSelector)& namespaceSelector:NetworkPolicy使用标签选择器来选择特定的Pod。通过标签,可以将网络策略应用于特定的Pod群体。

  2. Ingress规则:定义了允许从其他Pod进入被选中Pod的规则,包括允许的协议、端口范围等。

  3. Egress规则:定义了允许被选中Pod访问其他Pod或外部网络的规则。

  4. 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-2acn-beijing-a),同一可用区内的节点具有相同值。 场景:跨可用区部署 Pod 以提高容灾能力(反亲和性),或限制在同一可用区部署以降低网络延迟(亲和性)。 云厂商的 Kubernetes 集群自动添加(如 EKS、GKE);本地集群需手动添加
topology.kubernetes.io/region 表示 “区域”(如 us-west-2cn-beijing),比可用区范围更大,同一区域包含多个可用区。 场景:跨区域部署核心服务(如多区域容灾)。 云厂商的 Kubernetes 集群自动添加;本地集群需手动添加
failure-domain.beta.kubernetes.io/zone 旧版本中表示 “可用区” 的标签键,功能与 topology.kubernetes.io/zone 一致,现已逐步被后者替代(但部分集群仍兼容)。 旧版本 Kubernetes 或部分云厂商集群
failure-domain.beta.kubernetes.io/region 旧版本中表示 “区域” 的标签键,功能与 topology.kubernetes.io/region 一致,逐步被替代。 旧版本 Kubernetes 或部分云厂商集群
自定义标签(如 rackroom 用户手动为节点添加的拓扑标签,用于划分更细粒度的拓扑域(如机架 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还有两个可选字段:

    1. globalDefault:用于设置默认优先级状态,如果没有任何优先级设置,Pod的优先级为零

    2. description:用来配置描述性信息,告诉用户优先级的用途

  • 优先级策略

    1. 非抢占优先:指的是在调度阶段优先进行调度分配,一旦容器调度完成就不可以抢占,资源不足时,只能等待,对应 preemptionPolicy: Never

    2. 抢占优先:强制调度一个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清理,避免误删正在使用的中间层

最佳实践与总结

  1. 始终设置 evictionHard:这是防止节点完全宕机的安全网。

  2. 必须配置 systemReservedkubeReserved:这是生产环境的黄金准则,能极大提高节点稳定性。预留值大小需根据节点规格和实际系统开销调整。

  3. 理解软阈值的用途:用于需要“优雅驱逐”的场景,给应用和运维人员预留反应时间。

  4. 监控驱逐事件:使用 kubectl get events -w --field-selector reason=Evicted 或监控平台来关注集群中的驱逐事件,它们是指标应用资源规划不合理或节点压力过大的重要信号。

  5. 合理设置 Pod 的 requestslimits:这是防止 Pod 被驱逐的第一道防线。Kubelet 在决定驱逐哪个 Pod 时,会优先驱逐那些实际使用量远超其 requestsBurstable Pod。

  6. 使用 Pod 优先级(PriorityClass):对于关键应用,可以创建高优先级的 PriorityClass,并配置到 Pod 上。这样即使节点压力巨大,低优先级的 Pod 也会在高优先级的 Pod 之前被驱逐。

看到感觉有帮助的朋友,劳烦动动发财的小手给博主点个赞


网站公告

今日签到

点亮在社区的每一天
去签到