K3s 日常使用备忘录

术语

  • 节点(Node): Kubernetes 集群中的一台工作机器,是集群的一部分。
  • 集群(Cluster): 一组运行由 Kubernetes 管理的容器化应用程序的节点。在此示例和在大多数常见的 Kubernetes 部署环境中,集群中的节点都不在公共网络中。
  • 边缘路由器(Edge Router): 在集群中强制执行防火墙策略的路由器。可以是由云提供商管理的网关,也可以是物理硬件。
  • 集群网络(Cluster Network): 一组逻辑的或物理的连接,根据 Kubernetes 网络模型在集群内实现通信。
  • 服务(Service):Kubernetes 服务(Service),使用标签选择器(selectors)辨认一组 Pod。除非另有说明,否则假定服务只具有在集群网络中可路由的虚拟 IP。

负载分类

  • Deployment
    • 无状态,所有 Pod 等价,可替代。
  • StatefulSet
    • 有状态,适合诸如数据库等应用。
  • DaemonSet
    • 每个节点上跑一个 Pod,适合用来做节点监控、节点日志收集等
  • Job & CronJob
    • Job 表示一次性的任务,CronJob 表示定时任务。

常用命令

Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 查看部署
sudo kubectl get deployment
# 部署应用
sudo kubectl apply -f <deployment-file>
# 伸缩扩展副本
sudo kubectl scale deployment <deployment-name> --replicas=5
# 查看历史
sudo kubectl rollout history deployment <deployment-name>
# 回到上个版本
sudo kubectl rollout undo deployment <deployment-name>
# 回到指定版本
sudo kubectl rollout undo deployment <deployment-name> --to-revision=2
# 删除部署
sudo kubectl delete deployment <deployment-name>
# 重新部署
sudo kubectl rollout restart deployment <deployment-name>
# 命令修改镜像, 末尾 --record 表示把当前命令记录到操作历史中
# 此处命令表示修改指定部署中容器名称为 test-k8s 容器使用的镜像
sudo kubectl set image deployment <deployment-name> test-k8s=ccr.ccs.tencentyun.com/k8s-tutorial/test-k8s:v2-with-error --record
# 输出到文件
sudo kubectl get deployment <deployment-name> -o yaml >> app2.yaml
# 暂停应用
# 暂停后,对 deployment 的修改不会立刻生效,恢复后才应用设置
sudo kubectl rollout pause deployment <deployment-name>
# 恢复应用
sudo kubectl rollout resume deployment <deployment-name>

Pod

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 查看 pod
sudo kubectl get pod -o wide
# 查看 pod 详情
sudo kubectl describe pod <pod-name>
# 查看 pod 日志
sudo kubectl logs <pod-name>
# 删除 pod
sudo kubectl delete pod <pod-name>
# 进入 Pod 容器终端, -c container-name 可以指定进入哪个容器。
sudo kubectl exec -it <pod-name> -- bash

Node

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 查看节点
sudo kubectl get nodes
# 为指定节点添加标签
sudo kubectl label nodes <node-name> disktype=ssd
# 为指定节点删除标签
sudo kubectl label nodes <node-name> <label-name>-
# 查看节点标签
sudo kubectl get nodes --show-labels
# 查看节点资源占用 (当前 CPU、Memory 占用情况)
sudo kubectl top nodes

Misc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 重启指定资源
kubectl rollout restart <resource-type> <resource-name>
# 查看服务
sudo kubectl get svc
# 将指定 Pod 的指定端口映射到节点(对外暴露端口)
# 使用资源类型/名称(例如 deployment/mydeployment)来选择一个 pod。
# 如果省略,资源类型默认为 "pod"。
sudo kubectl port-forward <pod-name> 8090:8080
# 查看全部
sudo kubectl get all
# 删除全部资源
sudo kubectl delete all --all
# 查看 ConfigMap
sudo kubectl get configmap <config-map-name> -o yaml
# 查看 Secret
sudo kubectl get secret <secret-name> -o yaml
# 查看 PV(卷)
sudo kubectl get pv
# 删除 PV(卷)
sudo kubectl delete pv <pv-name>
# 查看 PVC(持久卷)
sudo kubectl get pvc
# 查看 StorageClass
sudo kubectl get storageclass
# 删除 StorageClass
sudo kubectl delete storageclass <storage-class-name>
# 查看服务关联的端点
sudo kubectl get endpoints <service-name>
# 查看特定资源或资源组的详细信息
# kubectl describe TYPE NAME_PREFIX
sudo kubectl describe pod <pod-name>

常用示例

将 Pod 调度到指定节点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  nodeName: foo-node # 调度 Pod 到指定的节点
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent

使用此配置文件来创建一个 Pod,该 Pod 将只能被调度到 foo-node 节点。

将 Pod 调度到含指定标签的节点

节点信息:

1
2
3
4
NAME      STATUS    AGE     VERSION            LABELS
worker0   Ready     1d      v1.6.0+fff5156     ...,disktype=ssd,kubernetes.io/hostname=worker0
worker1   Ready     1d      v1.6.0+fff5156     ...,kubernetes.io/hostname=worker1
worker2   Ready     1d      v1.6.0+fff5156     ...,kubernetes.io/hostname=worker2

Pod 配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

该 Pod 将被调度到有 disktype=ssd 标签的节点,即 worker0 节点。

  • nodeSelector 与 nodeAffinity 都可用于指定绑定到具体 node。如果同时指定 nodeSelector 和 nodeAffinity,两者必须都要满足,才能将 Pod 调度到候选节点。
  • 如果在与 nodeAffinity 类型关联的 nodeSelectorTerms 中指定多个条件,只要其中一个 nodeSelectorTerms 满足(各个条件按逻辑或操作组合)的话,Pod 就可以被调度到节点上。
  • 如果在与 nodeSelectorTerms 中的条件相关联的单个 matchExpressions 字段中指定多个表达式,则只有当所有表达式都满足(各表达式按逻辑与操作组合),Pod 才能被调度到节点上。

参阅使用节点亲和性来为 Pod 指派节点, 以了解进一步的信息。

节点退出(剔除)

在硬件升级、维护或节点出现异常等情况下,需要将某些剔除,使其脱离 kubernetes 集群的调度范围。

  1. 将要剔除的节点设置成不可调度状态
1
kubectl cordon test-k8s-node05

设置之后节点状态会多一个 SchedulingDisabled 标记,之后新创建的 Pod 将不会往该节点调度。

  1. 驱逐调节点上的pod
1
kubectl drain test-k8s-node05

如果是节点出了问题,执行不了指令,可以采取强制驱逐的方式

1
kubectl delete pods -n kube-system nginx-6qz6s
  1. 将节点从集群中剔除
1
kubectl delete node test-k8s-node05

Kind 特性

Pod

  • 需要端口转发才能访问
  • 每次重新创建,IP 和 Name 都会变更

服务 (Service)

将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。使用 Kubernetes,你无需修改应用程序去使用不熟悉的服务发现机制。Kubernetes 为 Pod 提供自己的 IP 地址,并为一组 Pod 提供相同的 DNS 名,并且可以在它们之间进行负载均衡。

  • 通过 label 跟 Pod 进行关联
  • 生命周期不跟 Pod 绑定,不会因为 Pod 重创改变 IP
  • 提供了负载均衡功能,自动转发流量到不同 Pod
  • 可实现对集群外部提供访问端口
  • 集群内部(同一命名空间)可通过服务名称访问 (带负载均衡)

TODO: 可通过 pod-name.service-name 连接指定 Pod。

StatefulSet

  • Pod 名字也是固定的。
  • Pod 创建和销毁是有序的,创建是顺序的,销毁是逆序的。
  • Pod 重建不会改变名字 (IP 依然会变)

数据持久化

容器(Container) 中的文件在主机磁盘上是临时的,这带来了一些问题。问题之一是当容器崩溃时文件会丢失,因为 kubelet 会以干净的状态重启容器。第二个问题是同一 Pod 中运行多个容器并共享文件时出现的竞争。Kubernetes 卷 (Volume) 这一抽象概念能够解决这两个问题。

Kubernetes 支持很多类型的卷。Pod 可以同时使用任意数目的卷类型。临时卷(Ephemeral Volume) 类型的生命周期与 Pod 相同,持久卷 (Persistent Volume) 可以比 Pod 的存活期长。当 Pod 不再存在时,Kubernetes 仅销毁临时卷,不销毁持久卷。对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。

PV 卷是集群中的资源。PVC 申领是对这些资源的请求,也被用来执行对资源的申领检查。

**卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。**所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。

TODO: 除临时卷和持久卷外,还有 投射卷 (Projected Volumes)

整体流程

  1. Persistent Volume Claim (PVC) (持久卷申领)

persistentVolumeClaim 卷用来将持久卷(PersistentVolume)挂载到 Pod 中。持久卷申领(PersistentVolumeClaim)是用户在不知道特定云环境细节的情况下“申领”持久存储(例如 GCE PersistentDisk 或者 iSCSI 卷)的一种方法。

持久卷申领(PersistentVolumeClaim,PVC)表达的是用户对存储的请求,概念上与 Pod 类似。Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。

Pod 可以请求特定数量的资源(CPU 和内存),同样 PVC 申领可以请求特定的大小和访问模式的 PV

例如,可以要求能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载的 PV,Kubernetes 控制平面将查找满足申领要求的 PersistentVolume。如果控制平面找到具有相同 StorageClass 的适当的 PersistentVolume,则将 PersistentVolumeClaim 绑定到该 PersistentVolume 上。

  • 默认情况下,申请容量为小于等于 10Gi 的 PVC,可以和 10Gi 的 PV 绑定。
  • 未指定 PV 名称或选择器的 PVC 将与任何 PV 匹配。要将 PVC 绑定到特定 PV, 在知道 PV 名称时,可使用 pvc.spec.volumeName 指定, 在知道 PV 标签(Label)时,可请使用 pvc.spec.selector 指定。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodata
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: "local-storage"
  resources:
    requests:
      storage: 2Gi
  # volumeName: <pv-name>  # 通过 PV 卷名称指定要申领的 PV
  # nodeSelector:  # TODO: 隐约记得这玩意儿好像是 Pod 的配置项?
  #   accelerator: nvidia-tesla-p100  # 匹配标签,与名称和 UID 不同,标签不提供唯一性。通常,我们希望许多对象带有相同的标签。
  # selector:  # TODO: 这种类型的选择器在 PVC 中好像不受支持?
  #   target: monitor
  selector:
    matchLabels:
      target: redis
  #   matchExpressions:
  #     - {key: tier, operator: In, values: [cache]}
  #     - {key: environment, operator: NotIn, values: [dev]}
  1. Persistent Volume (PV) (持久卷)

持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备,或者使用存储类(Storage Class)来动态制备。持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。

关于访问模式(accessModes):

  • ReadWriteOnce:卷可以被一个节点以读写方式挂载。需要注意的是,这意味着其允许运行在同一节点上的多个 Pod 访问卷。
  • ReadOnlyMany:卷可以被多个节点以只读方式挂载。
  • ReadWriteMany:卷可以被多个节点以读写方式挂载。
  • ReadWriteOncePod:卷可以被单个 Pod 以读写方式挂载。如果你想确保整个集群中只有一个 Pod 可以读取或写入该 PVC,请使用 ReadWriteOncePod 访问模式。这只支持 CSI 卷并且需要 Kubernetes 1.22 以上版本。
  • PV 可以通过设置节点亲和性来定义一些约束,进而限制可以从哪些节点上访问此 PV。**使用这些卷的 Pod 只会被调度到节点亲和性规则所选择的节点上执行。**大多数类型的 PV 都不需要设置节点亲和性字段。AWSEBS、GCE PD 和 Azure Disk 卷类型都能自动设置相关字段。通常只有 local 卷需要显式地设置此属性。
  • 将 PV 的 claimRef 字段设为相关的 PersistentVolumeClaim,可以确保其他 PVC 不会绑定到该 PV 卷。
  • 每个 PV 同一时刻只能以一种访问模式挂载,即使该卷支持多种访问模式。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongodata
spec:
  capacity:
    storage: 2Gi
  volumeMode: Filesystem  # Filesystem(文件系统) Block(块)
  accessModes:
    - ReadWriteOnce       # 卷可以被一个节点以读写方式挂载
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /root/data
  nodeAffinity:
    required:
      # 通过 hostname 限定在某个节点创建存储卷
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - node2
  1. Storage Class (SC) (存储类)

尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源,常见的情况是针对不同的问题用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷。集群管理员需要能够提供不同性质的 PersistentVolume,并且这些 PV 卷之间的差别不仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户。为了满足这类需求,就有了存储类(StorageClass) 资源。

每个 StorageClass 都包含 provisionerparametersreclaimPolicy 字段,这些字段会在 StorageClass 需要动态制备 PersistentVolume 时会使用到。

1
2
3
4
5
6
7
8
9
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
  type: io1
  iopsPerGB: "10"
  fsType: ext4

完整示例 (本地磁盘)

使用此示例时,请提前创建 /root/data 目录,否则试图挂载该 PV 的 Pod 将卡在 ContainerCreating 状态下无法正常启动。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongodb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
        image: mongo:5.0
        imagePullPolicy: IfNotPresent
        name: mongo
        volumeMounts:
          - mountPath: /data/db
            name: mongo-data
      volumes:
        - name: mongo-data
          persistentVolumeClaim:
             claimName: mongodata
---
apiVersion: v1
kind: Service
metadata:
  name: mongodb
spec:
  clusterIP: None
  ports:
  - port: 27017
    protocol: TCP
    targetPort: 27017
  selector:
    app: mongodb
  type: ClusterIP
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongodata
spec:
  capacity:
    storage: 2Gi
  volumeMode: Filesystem  # Filesystem(文件系统) Block(块)
  accessModes:
    - ReadWriteOnce       # 卷可以被一个节点以读写方式挂载
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /root/data
  nodeAffinity:
    required:
      # 通过 hostname 限定在某个节点创建存储卷
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - node2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodata
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: "local-storage"
  resources:
    requests:
      storage: 2Gi

ConfigMap & Secret

ConfigMap

用于配置一些变量,例如数据库连接地址等根据部署环境变化的,不能写死的信息。

1
2
3
4
5
6
7
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: <config-map-name>
data:
  mongoHost: mongodb-0.mongodb
1
2
3
4
# 应用
sudo kubectl apply -f configmap.yaml
# 查看
sudo kubectl get configmap <config-map-name> -o yaml

Secret

用于保存一些重要数据,例如密码、TOKEN。文档,配置证书

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: <secret-name>
# Opaque 用户定义的任意数据
# 更多类型: https://kubernetes.io/zh/docs/concepts/configuration/secret/#secret-types
type: Opaque
data:
  # 数据需要进行 Base64 编码
  mongo-username: bW9uZ291c2Vy
1
2
3
4
# 应用
sudo kubectl apply -f secret.yaml
# 查看
sudo kubectl get secret <secret-name> -o yaml

使用方法

作为环境变量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongodb
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
        - name: mongo
          image: mongo:4.4
          env:
          - name: MONGO_INITDB_ROOT_USERNAME
            valueFrom:
              secretKeyRef:
                name: mongo-secret
                key: mongo-username

挂载为文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  # 挂载后会在容器中对应路径生成一个 key 文件,内容就是 value
  volumes:
  - name: foo
    secret:
      secretName: mysecret

命名空间

如果一个集群中部署了多个应用,所有应用都在一起,就不太好管理,也可以导致名字冲突等。 此时可以使用 namespace 把应用划分到不同的命名空间,跟代码里的 namespace 是一个概念。

1
2
3
4
5
6
# 创建命名空间
sudo kubectl create namespace testapp
# 部署应用到指定的命名空间
sudo kubectl apply -f app.yml --namespace testapp
# 查询指定命名空间中的 Pod
sudo kubectl get pod --namespace kube-system

使用 kubens 快速切换 namespace

1
2
3
4
5
6
# 切换命名空间
kubens kube-system
# 回到上个命名空间
kubens -
# 切换集群
kubectx minikube

Ingress

Ingress 作为 Kubernetes 集群内服务对外暴露的访问接入点,其几乎承载着集群内服务访问的所有流量。Ingress 是 Kubernetes 中的一个资源对象,用来管理集群外部访问集群内部服务的方式。

可以通过 Ingress 资源来配置不同的转发(路由)规则,从而达到根据不同的规则设置访问集群内不同的 Service 所对应的后端 Pod。

Ingress 资源仅支持配置 HTTP 流量转发规则,无法配置一些高级特性,负载均衡的算法、Sessions Affinity 等高级特性都需要在 Ingress Controller 中进行配置。

简而言之,其功能类似 Nginx Proxy,可以根据域名、路径把请求转发到不同的 Service(负载均衡),也可配置 https。

Ingress

以下是一个 Ingress 的最小示例(Nginx controller):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: networking.k8s.io/v1
kind: Ingress

metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /

spec:
  ingressClassName: nginx-example
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

Ingress 控制器(Controller)与 Ingress 资源

为了使得 Ingress 资源正常工作,集群中必须要有一个 Ingress Controller 来解析 Ingress 的转发规则。Ingress Controller 收到请求,匹配 Ingress 转发规则转发到后端 Service 所对应的 Pod,由 Pod 处理请求。

Ingress 是反向代理规则,用来规定 HTTP/HTTPS 请求应该被转发到哪个 Service 所对应的 Pod 上。例如根据请求中不同的 Host 和 URL 路径,让请求落到不同 Service 所对应的 Pod 上。

Ingress Controller 是一个反向代理程序,负责解析 Ingress 的反向代理规则。如果 Ingress 有增删改等变动,Ingress Controller 会及时更新自己相应的转发规则,当 Ingress Controller 收到请求后就会根据这些规则将请求转发到对应 Service 的 Pod 上。

Ingress Controller 通过 API Server 获取 Ingress 资源的变化,动态地生成 Load Balancer(例如Nginx)所需的配置文件(例如 nginx.conf),然后重新加载 Load Balancer(例如执行 nginx -s load重新加载Nginx)来生成新的路由转发规则。

Ingress Controller 通过配置 LoadBalancer 类型的 Service 来创建 SLB,因此可以从外部通过 SLB 访问到 Kubernetes 集群的内部服务。根据 Ingress 配置的不同规则来访问不同的服务。

  • Ingress 控制器通常通过负载均衡器(LB)实现 Ingress,另外也可以通过配置边缘路由器(Edge Router)或其他前端实现 Ingress。

Ingress Rules

每个 Ingress HTTP 规则都包含以下信息:

  • host(可选):在上述示例中,未指定 host,因此该规则适用于通过指定 IP 地址的所有入站 HTTP 通信。如果提供了 host(例如 foo.bar.com),则 rules 适用于该 host。
  • paths:即路径列表,每个路径都有一个由 service.nameservice.port.nameservice.port.number 定义的关联后端。当前支持的路径类型有三种:
    • ImplementationSpecific:这种路径类型匹配方法取决于 IngressClass,具体实现可以将其作为单独的 pathType 处理或者与 Prefix 或 Exact 类型作相同处理。
    • Exact:精确匹配 URL 路径,且区分大小写。
    • Prefix:基于以 / 分隔的 URL 路径前缀匹配,区分大小写,会对路径元素逐个进行匹配。
  • backend,即后端,是 Service 文档中所述的服务和端口名称的组合,或者是通过 CRD 方式来实现的自定义资源后端。与规则的 host 和 path 匹配的对 Ingress 的 HTTP(S) 请求将发送到列出的 backend。

Ingress Class

IngressClass 是 Ingress 处理器的描述,用于在 K8s 集群中声明一个 Ingress 处理器实现,关联该 IngressClass 的 Ingress 资源会被该 Ingress 处理器解析。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb
spec:
  controller: example.com/ingress-controller
  parameters:
    apiGroup: k8s.example.com
    kind: IngressParameters
    name: external-lb
  • IngressClass 中的 .spec.parameters 字段可用于引用其他资源以提供额外的相关配置,具体类型取决于 .spec.controller 字段中指定的 Ingress 控制器。

TLS Config

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: v1
kind: Secret
metadata:
  name: testsecret-tls
  namespace: default
data:
  tls.crt: base64 编码的证书
  tls.key: base64 编码的私钥
type: kubernetes.io/tls

---

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-example-ingress
spec:
  tls:
  - hosts:
      - https-example.foo.com
    secretName: testsecret-tls
  rules:
  - host: https-example.foo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 80

Resource backend

Resource 后端是一个引用,指向同一命名空间中的另一个 Kubernetes 资源,将其作为 Ingress 对象。Resource 后端与 Service 后端是互斥的,在二者均被设置时会无法通过合法性检查。Resource 后端的一种常见用法是将所有入站数据导向带有静态资产的对象存储后端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-resource-backend
spec:
  defaultBackend:
    resource:
      apiGroup: k8s.example.com
      kind: StorageBucket
      name: static-assets
  rules:
    - http:
        paths:
          - path: /icons
            pathType: ImplementationSpecific
            backend:
              resource:
                apiGroup: k8s.example.com
                kind: StorageBucket
                name: icon-assets

小结

  • 必须拥有一个 Ingress 控制器才能满足 Ingress 的要求,仅创建 Ingress 资源本身没有任何效果
  • Ingress 对象的命名必须是合法的 DNS 子域名名称。
  • Ingress 经常使用注解(annotations)来配置一些选项,不同的 Ingress 控制器配置选项也不尽相同。
  • 省略 ingressClassName 时,应该定义一个默认 Ingress 类。部分 Ingress 控制器不需要定义默认 IngressClass。比如 Ingress-NGINX 控制器可以通过参数 –watch-ingress-without-class 来配置。
  • 没有设置规则的 Ingress 将所有流量发送到同一个默认后端(.spec.defaultBackend)。
  • host 可以是精确匹配或者使用通配符匹配。

Endpoint

endpoint 是 k8s 集群中的一个资源对象,存储在 etcd 中,用来记录一个 service 对应的所有 pod 的访问地址。

service 通过 selector 和 pod 建立关联。k8s 会根据 service 关联到 pod 的 podIP 信息组合成一个 endpoint。

Endpoint Controller

endpoints controller 是 kube-controller-manager 组件中众多控制器中的一个,是 endpoints 资源对象的控制器,其通过监听 service 和 pod 2种资源的变化触发 endpoints controller 对相应的 endpoints 资源进行调谐操作,从而完成 endpoints 对象的新建、更新、删除等操作。

简而言之,endpoint controller 负载维护 endpoint 资源对象,主要的功能有下面几种:

  • 生成和维护所有 endpoint 对象的控制器;
  • 监听 service 和对应 pod 的变化;
  • service 被删除,则删除和该 service 同名的 endpoint 对象;
  • service 被创建,则根据新建 service 信息获取相关 pod 列表,然后创建对应 endpoint 对象;
  • service 被更新,则根据更新后的 service 信息获取相关 pod 列表,然后更新对应 endpoint 对象;
  • pod 更新,则更新对应的 service 的 endpoint 对象,将 pod IP 记录到 endpoint 中;

如果 service 没有 selector 字段,其被创建的时候,endpoint controller 不会自动创建 endpoint。

Kube Proxy

kube-proxy 是 Kubernetes 的核心组件,其存在于每个 node 节点上,是实现 Service 与 Pod 之间通信及负载均衡的重要组件。kube-proxy 会为 Pod 创建代理服务,也会从 apiserver 获取所有 server 信息,并根据 server 信息创建相应代理服务,实现 server 到 Pod 的请求的路由和转发,从而实现 K8s 层级的虚拟转发网络。

对于外部访问 service 的请求,不论是 Cluster IP + Target Port 还是 Node 节点 IP + NodePort 的方式,都会被 Node 节点的 Iptables 规则重定向到 Kube-proxy 监听 Service 服务代理端口。kube-proxy 接收到 Service 的访问请求后,根据负载策略,转发到后端的 Pod。

kube-proxy 的路由转发规则是通过其后端的代理模块实现的,其中 kube-proxy 的代理模块目前有四种实现方案,userspace、iptables、ipvs(默认)、kernelspace。

在 k8s 中提供相同服务的一组 pod 可以抽象成一个 service,通过 service 提供统一的服务对外提供服务。

番外

Metrics

Metrics 用于收集资源指标,kubectl topHap 等功能对 Metrics 有依赖。

Hap 的自动扩容与缩容正是以 Metrics 为根基实现的。

无头服务(Headless Services)

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 “None” 来创建 Headless Service。

你可以使用一个无头 Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。

对于无头 Services 并不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。

Helm

Helm 类似 DockerHub, 可以理解为是一个软件库,可以方便快速的为我们的集群安装一些第三方软件。

PersistentVolume 回收策略

PersistentVolumes 可以有多种回收策略,包括 Retain (保留)Recycle (回收)Delete (删除)。对于动态配置的 PersistentVolumes 来说,默认回收策略为 “Delete”

  • Delete: 用户删除对应的 PersistentVolumeClaim 时,如果下层的卷插件支持,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。
  • Retain: 当 PersistentVolumeClaim 对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为Released (已释放)。由于卷上仍然存在前一申领人的数据,该卷在由管理员手动回收前不能用于其他申领。
  • Recycle: 用户删除 PersistentVolumeClaim 时,如果下层的卷插件支持,会在卷上执行一些基本的擦除 (rm -rf /thevolume/*)操作后允许该卷用于新的 PVC 申领。

Retain 回收政策下手动回收该卷的步骤:

  1. 手动删除 PersistentVolume 对象 (与之相关的、位于外部基础设施中的存储资产(例如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)在 PV 删除之后仍然存在)
  2. 视情况手动清除所关联存储资产上的数据。
  3. 手动删除所关联的存储资产。

警告: 回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态制备。

动态制备的卷会继承其 StorageClass 中设置的回收策略,该策略默认为 Delete。

PersistentVolume 绑定模式

volumeBindingMode 字段控制了卷绑定和动态制备应该发生在什么时候。

Immediate 模式默认情况下表示,一旦创建了 PersistentVolumeClaim 也就完成了卷绑定和动态制备。

WaitForFirstConsumer 模式下,将延迟 PersistentVolume 的绑定和制备,直到使用该 PersistentVolumeClaim 的 Pod 被创建。

Pod 重启策略

Pod 通过 restartPolicy 字段指定重启策略,重启策略类型为:Always、OnFailure 和Never,默认为Always。

Pod 内部容器互联

按照 Kubernetes 的工作方式,同一 Pod 中的所有容器都在同一个网络命名空间上,这意味着它们可以通过 localhost 相互通信。

各类 IP 用途

Kubernetes 集群内部存在三类 IP,分别是:

  • Node IP: 节点主机 IP 地址
  • Pod IP: 使用 CNI (网络插件) 创建的 IP (如 flannel)
  • Cluster IP: 虚拟 IP,通过 iptables 规则访问服务

Pod IP 用于让跨主机 Pod 之间的互通。

Init 容器

Pod 能够具有多个容器,应用运行在容器里面,但是也可能有一个或多个先于应用容器启动的 Init 容器。

Init 容器与普通的容器非常像,以下简单列举几点不同之处:

  • 总是运行到成功完成为止(即 exit code 为 0)。
  • 每个都必须在下一个启动之前成功完成。。
  • 同一部署中的 Init 容器全部完成以后,才会开始启动应用容器。

如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。然而,如果 Pod 对应的 restartPolicy 值为 “Never”,并且 Pod 的 Init 容器失败, 则 Kubernetes 会将整个 Pod 状态设置为失败。

Flannel

Flannel 配置第 3 层 IPv4 overlay 网络。它会创建一个大型内部网络,跨越集群中每个节点。在此 overlay 网络中,每个节点都有一个子网,用于在内部分配 IP 地址。在配置 Pod 时,每个节点上的 Docker (也可能是其他的) 桥接口都会为每个新容器分配一个地址。同一主机中的 Pod 可以使用 Docker (也可能是其他的) 桥接进行通信,不同主机上的 Pod 则会使用 flanneld 将其流量封装在 UDP 数据包中,以便路由到适当的目标。

概况一下,Flannel 可能是目前最简单和最受欢迎的 CNI 插件。其作为一个二进制文件部署在每个 Node 上,主要实现作用如下:

  • 为 Node 分配 subnet,node 中的容器将自动从该子网中获取 IP 地址
  • 新 Node 加入网络时,为所有 node 增加(更新)相应路由配置
  • 让不同 Node 中 Pod 的能够互相通信

CNI

CNI 意为容器网络接口,它是一种标准的设计,为了让用户在容器创建或销毁时都能够更容易地配置容器网络。

CNI 的初衷是创建一个框架,用于在配置或销毁容器时动态配置适当的网络配置和资源CNI 规范 概括了用于配制网络的插件接口,该接口让容器运行时能够与插件进行协调。

插件负责为接口配置和管理 IP 地址,并且通常提供与 IP 管理、每个容器的 IP 分配、以及多主机连接相关的功能。容器运行时会调用网络插件,从而在容器启动时分配 IP 地址并配置网络,并在删除容器时再次调用它以清理这些资源。

运行时或协调器决定了容器应该加入哪个网络以及它需要调用哪个插件。然后,插件会将接口添加到容器网络命名空间中,作为一个 veth 对的一侧。接着,它会在主机上进行更改,包括将 veth 的其他部分连接到网桥。再之后,它会通过调用单独的 IPAM(IP 地址管理)插件来分配 IP 地址并设置路由。

在 Kubernetes 中,kubelet 可以在适当的时间调用它找到的插件,来为通过 kubelet 启动的 pod 进行自动的网络配置。

容器网络是容器选择连接到其他容器、主机和外部网络(如 Internet)的机制。容器的 runtime 提供了各种网络模式,每种模式都会产生不同的体验。

术语

  • 第 2 层网络 : OSI(Open Systems Interconnections,开放系统互连)网络模型的“数据链路”层。第 2 层网络会处理网络上两个相邻节点之间的帧传递。第 2 层网络的一个值得注意的示例是以太网,其中 MAC 表示为子层。
  • 第 3 层网络 : OSI 网络模型的“网络”层。第 3 层网络的主要关注点,是在第 2 层连接之上的主机之间路由数据包。IPv4、IPv6 和 ICMP 是第 3 层网络协议的示例。
  • VXLAN :代表“虚拟可扩展 LAN”。首先,VXLAN 用于通过在 UDP 数据报中封装第 2 层以太网帧来帮助实现大型云部署。VXLAN 虚拟化与 VLAN 类似,但提供更大的灵活性和功能(VLAN 仅限于 4096 个网络 ID)。VXLAN 是一种封装和覆盖协议,可在现有网络上运行。
  • Overlay 网络 :Overlay 网络是建立在现有网络之上的虚拟逻辑网络。Overlay 网络通常用于在现有网络之上提供有用的抽象,并分离和保护不同的逻辑网络。
  • 封装 :封装是指在附加层中封装网络数据包以提供其他上下文和信息的过程。在 overlay 网络中,封装被用于从虚拟网络转换到底层地址空间,从而能路由到不同的位置(数据包可以被解封装,并继续到其目的地)。
  • 网状网络 :网状网络(Mesh network)是指每个节点连接到许多其他节点以协作路由、并实现更大连接的网络。网状网络允许通过多个路径进行路由,从而提供更可靠的网络。网状网格的缺点是每个附加节点都会增加大量开销。
  • BGP :代表“边界网关协议”,用于管理边缘路由器之间数据包的路由方式。BGP 通过考虑可用路径,路由规则和特定网络策略,帮助弄清楚如何将数据包从一个网络发送到另一个网络。BGP 有时被用作 CNI 插件中的路由机制,而不是封装的覆盖网络。

Metrics Server

metrics-server 通过 kubelet(cAdvisor)获取监控数据,主要作用是为 kube-scheduler、HPA 等 K8s 核心组件及 kubectl top 命令和 Dashboard 等 UI 组件提供数据。

各类 Port 对应关系

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: tomcat-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: tomcat
        tier: frontend
    spec:
      containers:
        - name: tomcat
          image: docker.cinyi.com:443/tomcat
          ports:
          - containerPort: 80   # 容器内部 Port
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
  name: tomcat-server
spec:
  type: NodePort
  selector:
    tier: frontend
  ports:
      # Service 暴露在 ClusterIP 上的端口,通过 <cluster ip>:<port> 访问服务, 通过此端口集群内的服务可以相互访问
      port: 11111
      # # 指定 Pod 的外部访问端口,port 和 nodePort 的数据通过此端口进入到 Pod, 应与 Deployment 中想绑定的 `containerPort` 一致
      # 默认情况下,`targetPort` 被设为与 `port` 字段相同的值。
      targetPort: 8080
      # Node节点的端口,<nodeIP>:nodePort 是提供给集群外部客户访问service的入口
      nodePort: 30001

疑问

  • Pod 总是与 PV 在同一节点?

设置节点亲和性约束后,才会将 Pod 调度到 PV 所在节点。而对于节点亲和性约束,通常只对 Local 类型的 PV 进行设置。

  • Nginx 反向代理中直接填写服务名称为什么无法解析到相应服务?

通常,直接使用服务名字即可通过 DNS 解析后连接到相应服务,不需要使用 IP 地址。Nginx 由于不支持 DNS Search,所以需要填写完整的地址,即 <server-name>.<namespace>.svc.cluster.local

  • 如何设置镜像加速?

编辑 /etc/rancher/k3s/registries.yaml 文件添加如下配置:

1
2
3
4
5
6
mirrors:
  "docker.io":
    endpoint:
      - "https://ovfftd6p.mirror.aliyuncs.com"
      - "https://docker.mirrors.ustc.edu.cn"
      - "https://hub-mirror.c.163.com"

然后重启服务 sudo systemctl restart k3s,使用 sudo crictl info | grep -A 5 "endpoint" 查看是否有返回刚才设在的那些地址,有则已生效。

  • 有类似 Docker Compose 中 depends_on 的东西吗?

没事的,但是可以通过 initContainer 实现类似的效果。

以下示例配置中,Nginx 将等待 Mariadb 启动完成后再启动。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
apiVersion: apps/v1
kind: Deployment

metadata:
  name: db

spec:
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
      - name: mariadb
        image: mariadb:10.10

---

apiVersion: v1
kind: Service

metadata:
  name: db

spec:
  ports:
    - port: 33306
      targetPort: 3306
  selector:
    app: db

---

apiVersion: apps/v1
kind: Deployment

metadata:
  name: nginx

spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      initContainers:
      - name: init-cont
        image: busybox:1.31
        command: ['sh', '-c', 'until nslookup db; do echo waiting for mysql; sleep 2; done;']
      containers:
      - name: nginx
        image: nginx:latest

参考资料