第一章：ResourceQuotas（资源配额）1.1 简介当多个用户或团队共享具有固定节点数目的集群时，人们会担心 有人使用超过其基于公平原则所分配到的资源量 。资源配额是帮助管理员解决这一问题的工具。资源配额，通过 ResourceQuota 对象来定义，对每个命名空间的资源消耗总量提供限制。 它可以 限制 命名空间中 某种类型的对象的总数目上限，也可以限制命令空间中的 Pod 可以使用的计算资源的总上限。资源配额的工作方式如下：不同的团队可以在不同的命名空间下工作，目前这是非约束性的，在未来的版本中可能会通过 ACL (Access Control List 访问控制列表) 来实现强制性约束。集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象。当用户在命名空间下创建资源（如 Pod、Service 等）时，Kubernetes 的配额系统会跟踪集群的资源使用情况，以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额。如果资源创建或者更新请求违反了配额约束，那么该请求会报错（HTTP 403 FORBIDDEN）， 并在消息中给出有可能违反的约束。如果命名空间下的计算资源 （如 cpu 和 memory）的配额被启用，则用户必须为 这些资源设定请求值（request）和约束值（limit），否则配额系统将拒绝 Pod 的创建。 提示: 可使用 LimitRanger 准入控制器来为没有设置计算资源需求的 Pod 设置默认值。1.2 计算资源配额用户可以对给定命名空间下的可被请求的 计算资源 总量进行限制。资源名称描述limits.cpu所有非终止状态的 Pod，其 CPU 限额总量不能超过该值。limits.memory所有非终止状态的 Pod，其内存限额总量不能超过该值。requests.cpu所有非终止状态的 Pod，其 CPU 需求总量不能超过该值。requests.memory所有非终止状态的 Pod，其内存需求总量不能超过该值。hugepages-<size>对于所有非终止状态的 Pod，针对指定尺寸的巨页请求总数不能超过此值。cpu与 requests.cpu相同。 memory与 requests.memory相同。 示例：apiVersion: v1 kind: ResourceQuota metadata: name: mem-cpu namespace: default # 限制 default 名称空间 spec: hard: requests.cpu: "1" requests.memory: 1Gi limits.cpu: "2" limits.memory: 2Gi1.3 扩展资源的资源配额除上述资源外，在 Kubernetes 1.10 版本中，还添加了对 扩展资源 的支持。由于扩展资源不可超量分配，因此没有必要在配额中为同一扩展资源同时指定 requests 和 limits。 对于扩展资源而言，目前仅允许使用前缀为 requests. 的配额项。以 GPU 拓展资源为例，如果资源名称为 nvidia.com/gpu，并且要将命名空间中请求的 GPU 资源总数限制为 4，则可以如下定义配额：requests.nvidia.com/gpu: 4有关更多详细信息，请参阅查看和设置配额。1.4 存储资源配额用户可以对给定命名空间下的 存储资源 总量进行限制。此外，还可以根据相关的存储类（Storage Class）来限制存储资源的消耗。资源名称描述requests.storage所有 PVC，存储资源的需求总量不能超过该值。persistentvolumeclaims在该命名空间中所允许的 PVC总量。 <storage-class-name>.storageclass.storage.k8s.io/requests.storage在所有与 <storage-class-name>相关的持久卷申领中，存储请求的总和不能超过该值。 <storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims在与 storage-class-name 相关的所有持久卷申领中，命名空间中可以存在的持久卷申领总数。 例如，如果一个操作人员针对 gold 存储类型与 bronze 存储类型设置配额， 操作人员可以定义如下配额：gold.storageclass.storage.k8s.io/requests.storage: 500Gibronze.storageclass.storage.k8s.io/requests.storage: 100Gi在 Kubernetes 1.8 版本中，本地临时存储的配额支持已经是 Alpha 功能：资源名称描述requests.ephemeral-storage在命名空间的所有 Pod 中，本地临时存储请求的总和不能超过此值。limits.ephemeral-storage在命名空间的所有 Pod 中，本地临时存储限制值的总和不能超过此值。ephemeral-storage与 requests.ephemeral-storage相同。 注意：如果所使用的是 CRI 容器运行时，容器日志会被计入临时存储配额。 这可能会导致存储配额耗尽的 Pods 被意外地驱逐出节点。 参考日志架构 了解详细信息。1.5 对象数量配额你可以使用以下语法对所有标准的、命名空间域的资源类型进行配额设置：count/<resource>.<group>：用于非核心（core）组的资源。count/<resource>：用于核心组的资源。这是用户可能希望利用对象计数配额来管理的一组资源示例。count/persistentvolumeclaimscount/servicescount/secretscount/configmapscount/replicationcontrollerscount/deployments.appscount/replicasets.appscount/statefulsets.appscount/jobs.batchcount/cronjobs.batch相同语法也可用于自定义资源。 例如，要对 example.com API 组中的自定义资源 widgets 设置配额，请使用 count/widgets.example.com。当使用 count/* 资源配额时，如果对象存在于服务器存储中，则会根据配额管理资源。 这些类型的配额有助于防止存储资源耗尽。例如，用户可能想根据服务器的存储能力来对服务器中 Secret 的数量进行配额限制。 集群中存在过多的 Secret 实际上会导致服务器和控制器无法启动。 用户可以选择对 Job 进行配额管理，以防止配置不当的 CronJob 在某命名空间中创建太多 Job 而导致集群拒绝服务。对有限的一组资源上实施一般性的对象数量配额也是可能的。 此外，还可以进一步按资源的类型设置其配额。资源名称描述configmaps在该命名空间中允许存在的 ConfigMap 总数上限。persistentvolumeclaims在该命名空间中允许存在的 PVC的总数上限。 pods在该命名空间中允许存在的非终止状态的 Pod 总数上限。Pod 终止状态等价于 Pod 的 .status.phase in (Failed, Succeeded)为真。 replicationcontrollers在该命名空间中允许存在的 ReplicationController 总数上限。resourcequotas在该命名空间中允许存在的 ResourceQuota 总数上限。services在该命名空间中允许存在的 Service 总数上限。services.loadbalancers在该命名空间中允许存在的 LoadBalancer 类型的 Service 总数上限。services.nodeports在该命名空间中允许存在的 NodePort 类型的 Service 总数上限。secrets在该命名空间中允许存在的 Secret 总数上限。例如，pods 配额统计某个命名空间中所创建的、非终止状态的 Pod 个数并确保其不超过某上限值。 用户可能希望在某命名空间中设置 pods 配额，以避免有用户创建很多小的 Pod， 从而耗尽集群所能提供的 Pod IP 地址。示例：apiVersion: v1 kind: ResourceQuota metadata: name: pods-rq namespace: default # 限制 default 名称空间 spec: hard: pods: "3" # 限制 Pod 的数量1.6 基于优先级类（PriorityClass）来设置资源配额Pod 可以创建为特定的优先级。 通过使用配额规约中的 scopeSelector 字段，用户可以根据 Pod 的优先级控制其系统资源消耗。仅当配额规范中的 scopeSelector 字段选择到某 Pod 时，配额机制才会匹配和计量 Pod 的资源消耗。如果配额对象通过 scopeSelector 字段设置其作用域为优先级类，则配额对象只能 跟踪以下资源：podscpumemoryephemeral-storagelimits.cpulimits.memorylimits.ephemeral-storagerequests.cpurequests.memoryrequests.ephemeral-storage示例：创建一个配额对象，并将其与具有特定优先级的 Pod 进行匹配# 集群中的 Pod 可取三个优先级类之一，即 "low"、"medium"、"high" # 为每个优先级创建一个配额对象 apiVersion: v1 kind: List items: - apiVersion: v1 kind: ResourceQuota metadata: name: pods-high spec: hard: cpu: "1000" memory: 200Gi pods: "10" scopeSelector: matchExpressions: - operator : In scopeName: PriorityClass values: ["high"] - apiVersion: v1 kind: ResourceQuota metadata: name: pods-medium spec: hard: cpu: "10" memory: 20Gi pods: "10" scopeSelector: matchExpressions: - operator : In scopeName: PriorityClass values: ["medium"] - apiVersion: v1 kind: ResourceQuota metadata: name: pods-low spec: hard: cpu: "5" memory: 10Gi pods: "10" scopeSelector: matchExpressions: - operator : In scopeName: PriorityClass values: ["low"]apiVersion: v1 kind: Pod metadata: name: high-priority spec: containers: - name: high-priority image: ubuntu command: ["/bin/sh"] args: ["-c", "while true; do echo hello; sleep 10;done"] resources: requests: memory: "10Gi" cpu: "500m" limits: memory: "10Gi" cpu: "500m" priorityClassName: high # priorityClassName 指的是什么，就优先使用这个优先级的配额约束。第二章：LimitRange（限制范围）2.1 简介默认情况下， Kubernetes 集群上的容器运行使用的计算资源没有限制。使用资源配额，集群管理员可以以名字空间为单位，限制其资源的使用与创建。在命名空间中，一个 Pod 或 Container 最多能够使用命名空间的资源配额所定义的 CPU 和内存用量。有人担心，一个 Pod 或 Container 会垄断所有可用的资源。 LimitRange 是在命名空间内限制资源分配（给多个 Pod 或 Container）的策略对象，例如：资源额度 ResourceQuotas 设置为 requests.cpu 为 1 ，requests.memory 为 1Gi ，但是有人的 Pod 直接设置为 requests.cpu 为 1，requests.memory 为 1Gi，那么其他人就不能再创建 Pod 了，所以需要使用 LimitRange 对资源配额设置区间（最小和最大）。一个 LimitRange（限制范围） 对象提供的限制能够做到：在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。在一个命名空间中实施对每个 PersistentVolumeClaim 能申请的最小和最大的存储空间大小的限制。在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。设置一个命名空间中对计算资源的默认申请/限制值，并且自动的在运行时注入到多个 Container 中。2.2 实战示例：为命名空间配置 CPU 最小和最大约束apiVersion: v1 kind: LimitRange metadata: name: cpu-min-max-demo-lr namespace: default # 限制 default 命名空间 spec: limits: - max: cpu: "800m" min: cpu: "200m" # Pod 中的容器的 cpu 的范围是 200m~800m type: Container示例：配置命名空间的最小和最大内存约束apiVersion: v1 kind: LimitRange metadata: name: mem-min-max-demo-lr namespace: default # 限制 default 命名空间 spec: limits: - max: memory: 1Gi min: memory: 500Mi # Pod 中的容器的 memory 的范围是 500Mi~1Gi type: Container示例：为命名空间配置默认的 CPU 请求和限制apiVersion: v1 kind: LimitRange metadata: name: cpu-limit-range namespace: default # 限制 default 命名空间 spec: limits: - default: cpu: 1 defaultRequest: cpu: 500m # 声明了一个默认的 CPU 请求和一个默认的 CPU 限制 type: Container示例：为命名空间配置默认的内存请求和限制apiVersion: v1 kind: LimitRange metadata: name: mem-limit-range namespace: default # 限制 default 命名空间 spec: limits: - default: memory: 512Mi defaultRequest: memory: 256Mi # 为命名空间配置默认的内存请求和限制 type: Container示例：限制存储消耗apiVersion: v1 kind: LimitRange metadata: name: storagelimits spec: limits: - type: PersistentVolumeClaim max: storage: 2Gi min: storage: 1GiapiVersion: v1 kind: ResourceQuota metadata: name: storagequota spec: hard: persistentvolumeclaims: "5" # 限制 PVC 数目和累计存储容量 requests.storage: "5Gi"第三章：调度原理3.1 概述在默认情况下，一个 Pod 在哪个 Node 节点上运行，是由 Scheduler 组件采用相应的算法计算出来的，这个过程是不受人工控制的。但是在实际使用中，这并不满足需求，因为很多情况下，我们想控制某些 Pod 到达某些节点上，那么应该怎么做？这就要求了解 Kubernetes 对 Pod 的调度规则，Kubernetes 提供了四大类调度方式：自动调度（默认）：运行在哪个 Node 节点上完全由 Scheduler 经过一系列的算法计算得出。定向调度：NodeName、NodeSelector。亲和性调度：NodeAffinity、PodAffinity、PodAntiAffinity。污点（容忍）调度：Taints、Toleration。3.2 定向调度3.2.1 概述定向调度，指的是利用在 Pod 上声明的 nodeName 或 nodeSelector ，以此将 Pod 调度到期望的 Node 节点上。注意：这里的调度是强制的，这就意味着即使要调度的目标 Node 不存在，也会向上面进行调度，只不过 Pod 运行失败而已。3.2.2 nodeName（不建议）nodeName 是节点选择约束的最简单方法，但是由于其自身限制，通常不使用它。 nodeName 是 PodSpec 的一个字段。 如果它不为空，调度器将忽略 Pod，并且给定节点上运行的 kubelet 进程尝试执行该 Pod。 因此，如果 nodeName 在 Pod 的 Spec 中指定了，则它优先于 nodeSelector 。使用 nodeName 来选择节点的一些限制：如果指定的节点不存在，如果指定的节点没有资源来容纳 Pod，Pod 将会调度失败并且其原因将显示为， 比如 OutOfmemory 或 OutOfcpu。云环境中的节点名称并非总是可预测或稳定的。示例：vi k8s-node-name.yamlapiVersion: v1 kind: Pod metadata: name: nginx namespace: default labels: app: nginx spec: nodeName: k8s-node1 # 指定调度到k8s-node1节点上 containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Alwayskubectl apply -f k8s-node-name.yaml3.2.3 nodeSelectornodeSelector 是节点选择约束的最简单推荐形式。nodeSelector 是 PodSpec 的一个字段。 它包含键值对的映射。为了使 pod 可以在某个节点上运行，该节点的标签中 必须包含这里的每个键值对（它也可以具有其他标签）。 最常见的用法的是一对键值对。除了自己 添加 的标签外，节点还预制了一组标准标签。 参见这些常用的标签，注解以及污点：[kubernetes.io/hostname](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#kubernetes-io-hostname)[failure-domain.beta.kubernetes.io/zone](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#failure-domainbetakubernetesiozone)[failure-domain.beta.kubernetes.io/region](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#failure-domainbetakubernetesioregion)[topology.kubernetes.io/zone](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#topologykubernetesiozone)[topology.kubernetes.io/region](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#topologykubernetesiozone)[beta.kubernetes.io/instance-type](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#beta-kubernetes-io-instance-type)[node.kubernetes.io/instance-type](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#nodekubernetesioinstance-type)[kubernetes.io/os](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#kubernetes-io-os)[kubernetes.io/arch](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#kubernetes-io-arch)注意：这些标签的值是特定于云供应商的，因此不能保证可靠。 例如，kubernetes.io/hostname 的值在某些环境中可能与节点名称相同， 但在其他环境中可能是一个不同的值。示例：# 给 k8s-node2 打上标签 kubectl label node k8s-node2 nodeevn=provi k8s-node-selector.yamlapiVersion: v1 kind: Pod metadata: name: nginx namespace: default labels: app: nginx spec: nodeSelector: nodeevn: pro # 指定调度到 nodeevn = pro 标签的 Node 节点上 containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Alwayskubectl apply -f k8s-node-selector.yaml3.3 亲和性调度3.3.1 概述虽然定向调度的两种方式，使用起来非常方便，但是也有一定的问题，那就是如果没有满足条件的 Node，那么 Pod 将不会被运行，即使在集群中还有可用的 Node 列表也不行，这就限制了它的使用场景。基于上面的问题，Kubernetes 还提供了一种亲和性调度（Affinity）。它在 nodeSelector 的基础之上进行了扩展，可以通过配置的形式，实现优先选择满足条件的 Node 进行调度，如果没有，也可以调度到不满足条件的节点上，使得调度更加灵活。Affinity 主要分为三类：nodeAffinity（node亲和性）：以 Node 为目标，解决 Pod可 以调度到那些 Node 的问题。podAffinity（pod亲和性）：以 Pod 为目标，解决 Pod 可以和那些已存在的 Pod 部署在同一个拓扑域中的问题。podAntiAffinity（pod反亲和性）：以 Pod 为目标，解决 Pod 不能和那些已经存在的 Pod 部署在同一拓扑域中的问题。关于亲和性和反亲和性的使用场景的说明：亲和性：如果两个应用频繁交互，那么就有必要利用亲和性让两个应用尽可能的靠近，这样可以较少因网络通信而带来的性能损耗。反亲和性：当应用采用多副本部署的时候，那么就有必要利用反亲和性让各个应用实例打散分布在各个 Node 上，这样可以提高服务的高可用性。3.3.2 nodeAffinitynodeAffinity 概念上类似于 nodeSelector，它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点。和 nodeSelector 的区别：引入了运算符：In、NotIn、Exists、DoesNotExist、 Gt、 Lt。支持 硬性过滤 和 软性评分 ：硬件过滤支持指定多条件之间的逻辑或运算；软件评分规则支持设置条件权重。nodeAffinity 的可选配置项：pod.spec.affinity.nodeAffinity requiredDuringSchedulingIgnoredDuringExecution # Node节点必须满足指定的所有规则才可以，硬性过滤 nodeSelectorTerms # 节点选择列表 matchFields # 按节点字段列出的节点选择器要求列表 matchExpressions # 按节点标签列出的节点选择器要求列表(推荐) key # 键 values # 值 operator # 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt preferredDuringSchedulingIgnoredDuringExecution # 优先调度到满足指定的规则的Node，软性评分 (倾向) preference # 一个节点选择器项，与相应的权重相关联 matchFields # 按节点字段列出的节点选择器要求列表 matchExpressions # 按节点标签列出的节点选择器要求列表(推荐) key # 键 values # 值 operator # 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt weight # 倾向权重，在范围1-100。注意：如果我们修改或删除 Pod 调度到节点的标签，Pod 不会被删除；换言之，亲和调度只是在 Pod 调度期间有效。如果同时定义了 nodeSelector 和 nodeAffinity ，那么必须两个条件都满足，Pod 才能运行在指定的 Node 上。如果 nodeAffinity 指定了多个 nodeSelectorTerms ，那么只需要其中一个能够匹配成功即可。如果一个 nodeSelectorTerms 中有多个 matchExpressions ，则一个节点必须满足所有的才能匹配成功。示例：硬性过滤# 给 k8s-node1 和 k8s-nodes 打上标签 kubectl label node k8s-node1 disktype=ssd kubectl label node k8s-node2 disktype=hddvi k8s-node-affinity-required.yamlapiVersion: v1 kind: Pod metadata: name: nginx namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai affinity: nodeAffinity: # DuringScheduling（调度期间有效）IgnoredDuringExecution（执行期间忽略，执行期间：Pod 运行期间） requiredDuringSchedulingIgnoredDuringExecution: # 硬性过滤：Node 节点必须满足指定的所有规则才可以 nodeSelectorTerms: - matchExpressions: # 所有 matchExpressions 满足条件才行 - key: disktype operator: In # 支持 Exists, DoesNotExist, In, NotIn, Gt, Lt values: - ssd - hdd restartPolicy: Alwayskubectl apply -f k8s-node-affinity-required.yaml示例：软性评分# 给 k8s-node1 和 k8s-nodes 打上标签 kubectl label node k8s-node1 disk=50 gpu=1000 kubectl label node k8s-node2 disk=30 gpu=5000vi k8s-node-affinity-preferre.yamlapiVersion: v1 kind: Pod metadata: name: nginx namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai affinity: nodeAffinity: # DuringScheduling（调度期间有效）IgnoredDuringExecution（执行期间忽略，执行期间：Pod 运行期间） preferredDuringSchedulingIgnoredDuringExecution: # 软性评分：优先调度到满足指定的规则的 Node - weight: 90 # 权重 preference: # 一个节点选择器项，与相应的权重相关联 matchExpressions: - key: disk operator: Gt values: - "40" - weight: 10 # 权重 preference: # 一个节点选择器项，与相应的权重相关联 matchExpressions: - key: gpu operator: Gt values: - "4000" restartPolicy: Alwayskubectl apply -f k8s-node-affinity-preferre.yaml3.3.3 podAffinity 和 podAntiAffinitypodAffinity 主要实现以运行的 Pod 为参照，实现让新创建的 Pod 和参照的 Pod 在一个区域的功能。podAntiAffinity 主要实现以运行的 Pod 为参照，让新创建的 Pod 和参照的 Pod 不在一个区域的功能。PodAffinity 的可选配置项：pod.spec.affinity.podAffinity requiredDuringSchedulingIgnoredDuringExecution 硬限制 namespaces 指定参照pod的namespace topologyKey 指定调度作用域 labelSelector 标签选择器 matchExpressions 按节点标签列出的节点选择器要求列表(推荐) key 键 values 值 operator 关系符 支持In, NotIn, Exists, DoesNotExist. matchLabels 指多个matchExpressions映射的内容 preferredDuringSchedulingIgnoredDuringExecution 软限制 podAffinityTerm 选项 namespaces topologyKey labelSelector matchExpressions key 键 values 值 operator matchLabels weight 倾向权重，在范围1-1注意：topologyKey 用于指定调度的作用域，例如:如果指定为 kubernetes.io/hostname（可以通过 kubectl get node --show-labels 查看），那就是以 Node 节点为区分范围。如果指定为 kubernetes.io/os，则以 Node 节点的操作系统类型来区分。示例：在一个两节点的集群中，部署一个使用 redis 的 WEB 应用程序，并期望 web-server 尽可能和 redis 在同一个节点上。vi k8s-pod-affinity.yamlapiVersion: apps/v1 kind: Deployment metadata: name: redis-deploy namespace: default labels: app: redis-deploy spec: selector: matchLabels: app: store replicas: 2 template: metadata: labels: app: store spec: affinity: # 亲和性配置 podAntiAffinity: # Pod 反亲和性，符合以下指定条件不会被调度过去 requiredDuringSchedulingIgnoredDuringExecution: # 硬限制 - labelSelector: matchExpressions: - key: app operator: In values: - store topologyKey: kubernetes.io/hostname # 拓扑键，划分逻辑区域。 # node 节点以 kubernetes.io/hostname 为拓扑网络，如果 kubernetes.io/hostname 相同，就认为是同一个东西。 # 亲和就是都放在这个逻辑区域，反亲和就是必须避免放在同一个逻辑区域。 containers: - name: redis-server image: redis:5.0.14-alpine resources: limits: memory: 500Mi cpu: 1 requests: memory: 250Mi cpu: 500m ports: - containerPort: 2375 name: redis volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy namespace: default labels: app: nginx-deploy spec: selector: matchLabels: app: web-store replicas: 2 template: metadata: labels: app: web-store spec: affinity: # 亲和性配置 podAffinity: # Pod 亲和性，符合以下指定条件会被调度过去 requiredDuringSchedulingIgnoredDuringExecution: # 硬限制 - labelSelector: matchExpressions: - key: app operator: In values: - store topologyKey: kubernetes.io/hostname # 拓扑键，划分逻辑区域。 # node 节点以 kubernetes.io/hostname 为拓扑网络，如果 kubernetes.io/hostname 相同，就认为是同一个东西。 # 亲和就是都放在这个逻辑区域，反亲和就是必须避免放在同一个逻辑区域。 podAntiAffinity: # Pod 反亲和性，符合以下指定条件不会被调度过去 requiredDuringSchedulingIgnoredDuringExecution: # 硬限制 - labelSelector: matchExpressions: - key: app operator: In values: - web-store topologyKey: kubernetes.io/hostname # 拓扑键，划分逻辑区域。 # node 节点以 kubernetes.io/hostname 为拓扑网络，如果 kubernetes.io/hostname 相同，就认为是同一个东西。 # 亲和就是都放在这个逻辑区域，反亲和就是必须避免放在同一个逻辑区域。 containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: nginx volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Alwayskubectl apply -f k8s-pod-affinity.yaml3.4 污点和容忍调度3.4.1 污点前面的调度方式都是站在 Pod 的角度上，通过在 Pod 上添加属性，来确定 Pod 是否要调度到指定的 Node 上，其实我们也可以站在 Node 的角度上，通过在 Node 上添加污点属性，来决定是否运行 Pod 调度过来。Node 被设置了污点之后就和 Pod 之间存在了一种相斥的关系，进而拒绝 Pod 调度进来，甚至可以将已经存在的 Pod 驱逐出去。污点的格式为：key=value:effect # key 和 value 是污点的标签及对应的值 # effect 描述污点的作用effect 支持如下的三个选项：PreferNoSchedule：Kubernetes 将尽量避免把 Pod 调度到具有该污点的 Node 上，除非没有其他节点可以调度；换言之，尽量不要来，除非没办法。NoSchedule：Kubernets 将不会把 Pod 调度到具有该污点的 Node 上，但是不会影响当前 Node 上已经存在的 Pod ;换言之，新的不要来，在这的就不要动。NoExecute：Kubernets 将不会将 Pod 调度到具有该污点的 Node 上，同时会将 Node 上已经存在的 Pod 驱逐；换言之，新的不要来，这这里的赶紧走。注意：NoExecute 一般用于实际生产环境中的 Node 节点的上下线。语法：设置污点kubectl taint node xxx key=value:effect语法：去除污点kubectl taint node xxx key:effect-语法：去除所有污点kubectl taint node xxx key-语法：查看污点kubectl describe node xxx | grep -i taints证明：kubeadm 安装的集群上 k8s-master 有污点kubectl describe node k8s-master | grep -i taints示例：演示污点效果（为了演示效果更为明显，暂时停止 k8s-node2 节点）① 为 k8s-node1 设置污点tag=xudaxian:PreferNoSchedule，然后创建 Pod1 （Pod1 可以运行）kubectl taint node k8s-node1 tag=xudaxian:PreferNoSchedulekubectl run pod1 --image=nginx:1.20.2② 修改 k8s-node1 节点的污点为 tag=xudaxian:NoSchedule，然后创建Pod2（Pod1 可以正常运行，Pod2 失败）。# 取消污点 kubectl taint node k8s-node1 tag:PreferNoSchedule- # 设置污点 kubectl taint node k8s-node1 tag=xudaxian:NoSchedulekubectl run pod2 --image=nginx:1.20.2③ 修改 k8s-node1 节点的污点为 tag=xudaxian:NoExecute，然后创建 Pod3（Pod1、Pod2、Pod3失败）。# 取消污点 kubectl taint node k8s-node1 tag:NoSchedule- # 设置污点 kubectl taint node k8s-node1 tag=xudaxian:NoExecutekubectl run pod3 --image=nginx:1.20.23.4.2 容忍上面介绍了污点的作用，我们可以在 Node上 添加污点用来拒绝 Pod 调度上来，但是如果就是想让一个 Pod 调度到一个有污点的 Node 上去，这时候应该怎么做？这就需要使用到容忍。污点就是拒绝，容忍就是忽略，Node 通过污点拒绝 Pod 调度上去，Pod 通过容忍忽略拒绝。容忍的详细配置：kubectl explain pod.spec.tolerations ...... FIELDS: key # 对应着要容忍的污点的键，空意味着匹配所有的键 value # 对应着要容忍的污点的值 operator # key-value的运算符，支持Equal和Exists（默认） effect # 对应污点的effect，空意味着匹配所有影响 tolerationSeconds # 容忍时间, 当effect为NoExecute时生效，表示pod在Node上的停留时间污点和容忍的匹配：当满足如下条件的时候，Kubernetes 认为污点和容忍匹配：键（key）相同。效果（effect）相同。污点的 operator 为：Exists ，此时污点中不应该指定 value 。Equal，此时容忍的 value 应该和污点的 value 相同。如果不指定 operator ，默认为 Equal 。特殊情况：容忍中没有定义 key ，但是定义了 operator 为 Exists ，Kubernetes 则认为此容忍匹配所有的污点，如：operator: Exists最终，所有有污点的机器我们都能容忍，Pod 都可以调度。 - 容忍中没有定义 effect，但是定义了 key，Kubernetes 认为此容忍匹配所有 effect ，如：tolerations: # 容忍key: "tag" # 要容忍的污点的keyoperator: Exists # 操作符最终，有这个污点的机器我们可以容忍，Pod 都可以调度。示例：# 设置污点 kubectl taint node k8s-node1 tag=xudaxian:NoExecutevi k8s-tolerations.yamlapiVersion: v1 kind: Pod metadata: name: pod-toleration spec: containers: # 容器配置 - name: nginx image: nginx:1.20.2 imagePullPolicy: IfNotPresent ports: - name: nginx-port containerPort: 80 protocol: TCP tolerations: # 容忍 - key: "tag" # 要容忍的污点的key operator: Equal # 操作符 value: "xudaxian" # 要容忍的污点的value effect: NoExecute # 添加容忍的规则，这里必须和标记的污点规则相同kubectl apply -f k8s-tolerations.yaml
第一章：访问控制概述1.1 概述用户使用 kubectl、客户端库或构造 REST 请求来访问 Kubernetes API。 人类用户和 Kubernetes 服务账户都可以被鉴权访问 API。 当请求到达 API 时，它会经历多个阶段，如下图所示：1.2 客户端在 Kubernetes 集群中，客户端通常由两类：① User Account：一般是独立于 Kubernetes 之外的其他服务管理的用户账号。② Service Account：Kubernetes 管理的账号，用于为Pod的服务进程在访问 Kubernetes 时提供身份标识。1.3 认证、授权和准入控制api-server 是访问和管理资源对象的唯一入口。任何一个请求访问 api-server，都要经过下面的三个流程：① Authentication（认证）：身份鉴别，只有正确的账号才能通过认证。② Authorization（授权）：判断用户是否有权限对访问的资源执行特定的动作。③ Admission Control（准入控制）：用于补充授权机制以实现更加精细的访问控制功能。1.4 权限流程控制用户携带令牌或者证书给 Kubernetes 的 api-server 发送请求，要求修改集群资源。Kubernetes 开始认证。Kubernetes 认证通过之后，会查询用户的授权（有哪些权限）。用户执行操作的过程中（操作 CPU、内存、硬盘、网络……），利用准入控制来判断是否可以执行这样的操作。第二章：认证管理2.1 Kubernetes 的客户端身份认证方式Kubernetes 集群安全的关键点在于如何识别并认证客户端身份，它提供了 3 种客户端身份认证方式：① HTTP Base 认证：通过 用户名+密码 的方式进行认证。这种方式是把 用户名:密码 用 BASE64 算法进行编码后的字符串放在 HTTP 请求中的 Header 的 Authorization 域里面发送给服务端。服务端收到后进行解码，获取用户名和密码，然后进行用户身份认证的过程。② HTTP Token 认证：通过一个 Token 来识别合法用户。这种认证方式是用一个很长的难以被模仿的字符串--Token 来表明客户端身份的一种方式。每个 Token 对应一个用户名，当客户端发起 API 调用请求的时候，需要在 HTTP 的 Header 中放入 Token，API Server 接受到 Token 后会和服务器中保存的 Token 进行比对，然后进行用户身份认证的过程。③ HTTPS 证书认证：基于 CA 根证书签名的双向数字证书认证方式。这种认证方式是安全性最高的一种方式，但是同时也是操作起来最麻烦的一种方式。2.2 总结Kubernetes 允许同时配置多种认证方式，只要其中任意一种方式认证通过即可。第三章：授权管理3.1 概述授权发生在认证成功之后，通过认证就可以知道请求用户是谁，然后 Kubernetes 会根据事先定义的授权策略来决定用户是否有权限访问，这个过程就称为授权。每个发送到 API Server 的请求都带上了用户和资源的信息：比如发送请求的用户、请求的路径、请求的动作等，授权就是根据这些信息和授权策略进行比较，如果符合策略，则认为授权通过，否则会返回错误。3.2 API Server目前支持的几种授权策略AlwaysDeny：表示拒绝所有请求，一般用于测试。AlwaysAllow：允许接收所有的请求，相当于集群不需要授权流程（Kubernetes 默认的策略）。ABAC：基于属性的访问控制，表示使用用户配置的授权规则对用户请求进行匹配和控制。Webhook：通过调用外部REST服务对用户进行授权。Node：是一种专用模式，用于对 kubelet 发出的请求进行访问控制。RBAC：基于角色的访问控制（ kubeadm 安装方式下的默认选项）。证明：kubeadm 安装方式的默认授权策略cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep -i rbac3.3 RBAC3.3.1 概述RBAC（Role Based Access Control）：基于角色的访问控制，主要是在描述一件事情：给哪些对象授权了哪些权限。RBAC 模型：Kubernetes 中的 RBAC 也是基于 RBAC 模型扩展的，涉及到如下的概念：对象：User、Group、ServiceAccount。角色：代表一组定义在资源上的可操作的动作（权限）的集合。绑定：将定义好的角色和用户绑定在一起，也可以理解为分配角色。RBAC 引入了 4 个顶级资源对象：Role：角色，用于指定一组权限，限定名称空间下的权限。ClusterRole：集群角色，用于指定一组权限，限定集群范围下的权限。RoleBinding：角色绑定，用于将角色 Role（权限的集合）赋予给对象（User、Group、ServiceAccount）。ClusterRoleBinding：集群角色绑定，用于将集群角色 Role（权限的集合）赋予给对象（User、Group、ServiceAccount）。说明：为什么 Kubernetes 要设计 Role 、 ClusterRole ？答：有些资源对象本身就不是 namespace（名称空间）的 ，所以 Kubernetes 增加了 ClusterRole，并且 ClusterRole 也可以管理名称空间下的资源对象。3.3.2 Role 和 ClusterRole资源清单文件一个角色就是一组权限的集合，这里的权限都是许可形式的（白名单）。Role 的资源清单文件：# 名称空间角色 apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: xudaxian-role namespace: default # 所属的名称空间 rules: # 当前角色的规则 - apiGroups: [""] # "" 标明 core API 组，默认留空即可。 resources: ["pods"] verbs: ["get", "watch", "list"]Role 只能对名称空间（namespace）进行授权，所以需要指定名称空间（namespace）。rules 中的参数说明：apiGroups：[""]，默认留空即可。resources：支持的资源对象列表，通过 kubectl api-resources 查看。verbs：对资源对象的操作方法列表，通过 kubectl api-resources -o wide 查看。ClusterRole 的资源清单文件：# 集群角色 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: xudaxian-clusterrole rules: - apiGroups: [""] # "" 标明 core API 组，默认留空即可。 resources: ["namespaces"] verbs: ["get", "watch", "list"]注意：ClusterRole 不需要设置 namespace。3.3.4 RoleBinding 和 ClusterRoleBinding 资源清单文件角色绑定用来把一个角色绑定到一个目标对象上，绑定目标可以是 User、Group 或者 ServiceAccount 。RoleBinding 资源清单文件：# 账号和角色绑定 apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: xudaxian-rolebinding namespace: default subjects: - kind: ServiceAccount name: xudaxian # "name" 是区分大小写的 roleRef: kind: Role name: xudaxian-role apiGroup: rbac.authorization.k8s.ioClusterRoleBinding 资源清单文件：# 账号和集群角色绑定 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: xudaxian-clusterrolebinding subjects: - kind: ServiceAccount name: xudaxian # "name" 是区分大小写的 namespace: default # 如果资源是某个 namespace 下的，那么就需要设置 namespace roleRef: kind: ClusterRole name: xudaxian-clusterrole apiGroup: rbac.authorization.k8s.io3.3.5 RBAC 实战创建 ServiceAccount 的时候，系统会在底层默认一个含 ServiceAccount 名称的 Secret 。示例：vi k8s-rbac.yaml# 名称空间角色 apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: xudaxian-role namespace: default # 所属的名称空间 rules: # 当前角色的规则 - apiGroups: [""] # "" 标明 core API 组，默认留空即可。 resources: ["pods"] # 指定能操作的资源 ，通过 kubectl api-resources 查看即可。 # resourceNames: [""] # 指定只能操作某个名字的资源 verbs: ["get", "watch", "list"] # 操作动作，通过 kubectl api-resources -o wide 查看即可。 --- # 集群角色 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: xudaxian-clusterrole rules: - apiGroups: [""] # "" 标明 core API 组，默认留空即可。 resources: ["namespaces"] verbs: ["get", "watch", "list"] --- # ServiceAccount apiVersion: v1 kind: ServiceAccount metadata: name: xudaxian # ServiceAccount 的名称 namespace: default --- # 账号和角色绑定 apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: xudaxian-rolebinding namespace: default subjects: - kind: ServiceAccount name: xudaxian # "name" 是区分大小写的 roleRef: kind: Role name: xudaxian-role apiGroup: rbac.authorization.k8s.io --- # 账号和集群角色绑定 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: xudaxian-clusterrolebinding subjects: - kind: ServiceAccount name: xudaxian # "name" 是区分大小写的 namespace: default # 如果资源是某个 namespace 下的，那么就需要设置 namespace roleRef: kind: ClusterRole name: xudaxian-clusterrole apiGroup: rbac.authorization.k8s.iokubectl apply -f k8s-rbac.yaml温馨提示：① 动态供应（NFS）、DashBoard 等底层都使用了 Role、ClusterRole、RoleBinding、ClusterRoleBinding 。② 我们可以创建一个 ServiceAccount ，并将自定义的 ServiceAccount 绑定 cluster-admin （ClusterRole，Kubernetes 底层提供的），然后通过暴露的 API 进行 Kubernetes 管理平台的开发（如：Kubersphere 等）。第四章：准入控制4.1 概述通过了前面的认证和授权之后，还需要经过准入控制通过之后，API Server 才会处理这个请求。准入控制是一个可配置的控制器列表，可以通过在 API Server 上通过命令行设置选择执行哪些注入控制器。--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds只有当所有的注入控制器都检查通过之后，API Server 才会执行该请求，否则返回拒绝。4.2 当前可配置的 Admission ControlAlwaysAdmit：允许所有请求。AlwaysDeny：禁止所有请求，一般用于测试。AlwaysPullImages：在启动容器之前总去下载镜像。DenyExecOnPrivileged：它会拦截所有想在 Privileged Container 上执行命令的请求。ImagePolicyWebhook：这个插件将允许后端的一个 Webhook 程序来完成 admission control 的功能。Service Account：实现 ServiceAccount 实现了自动化。SecurityContextDeny：这个插件将使用 SecurityContext 的 Pod 中的定义全部失效。ResourceQuota：用于资源配额管理目的，观察所有请求，确保在 namespace 上的配额不会超标。LimitRanger：用于资源限制管理，作用于 namespace 上，确保对 Pod 进行资源限制。InitialResources：为未设置资源请求与限制的 Pod，根据其镜像的历史资源的使用情况进行设置。NamespaceLifecycle：如果尝试在一个不存在的 namespace 中创建资源对象，则该创建请求将被拒绝。当删除一个 namespace 时，系统将会删除该 namespace 中所有对象。DefaultStorageClass：为了实现共享存储的动态供应，为未指定 StorageClass 或 PV 的 PVC 尝试匹配默认 StorageClass，尽可能减少用户在申请 PVC 时所需了解的后端存储细节。DefaultTolerationSeconds：这个插件为那些没有设置 forgiveness tolerations 并具有 notready:NoExecute 和 unreachable:NoExecute 两种taints的Pod设置默认的容忍时间，为 5min 。PodSecurityPolicy：这个插件用于在创建或修改 Pod 时决定是否根据 Pod 的 security context 和可用的 PodSecurityPolicy 对 Pod 的安全策略进行控制
第一章：Kubernetes 网络1.1 概述Kubernetes 网络解决四个方面的问题：① 一个 Pod 中容器之间通过本地回路（loopback）通信。② 集群网络在不同 Pod 之间提供通信；换言之，Pod 和 Pod 之间能互相通信（通过 calico 网络插件实现 Pod 之间网络的扁平化；当然，Node 节点之间的通信也是通过 calico 网络插件）。③ Service 资源允许我们对外暴露 Pod 中运行的应用程序，以支持来自集群之外的访问；换言之，Service 和 Pod 之间能互相通信。④ 可以使用 Service 来发布仅供集群内部使用的服务。1.2 Kubernetes 网络架构图1.2.1 架构图1.2.2 访问流程第二章：Service2.1 概述在 Kubernetes 中，Pod 是应用程序的载体，我们可以通过 Pod 的 IP 来访问应用程序，但是 Pod 的 IP 地址不是固定的，这就意味着不方便直接采用 Pod 的 IP 对服务进行访问。为了解决这个问题，Kubernetes 提供了 Service 资源，Service 会对提供同一个服务的多个 Pod 进行聚合，并且提供一个统一的入口地址，通过访问 Service 的入口地址就能访问到后面的 Pod 服务。Service 在很多情况下只是一个概念，真正起作用的其实是 kube-proxy 服务进程，每个 Node 节点上都运行了一个 kube-proxy 的服务进程。当创建 Service 的时候会通过 API Server 向 etcd 写入创建的 Service 的信息，而 kube-proxy 会基于监听的机制发现这种 Service 的变化，然后它会将最新的 Service 信息转换为对应的访问规则（其实，就是 EndPoint，后面讲）。Service 的资源清单：apiVersion: v1 # 版本 kind: Service # 类型 metadata: # 元数据 name: # 资源名称 namespace: # 命名空间 spec: selector: # 标签选择器，用于确定当前Service代理那些Pod app: nginx type: NodePort # Service的类型，指定Service的访问方式 clusterIP: # 虚拟服务的IP地址 sessionAffinity: # session亲和性，支持ClientIP、None两个选项，默认值为None ports: # 端口信息 - port: 8080 # Service端口 protocol: TCP # 协议 targetPort : # Pod端口 nodePort: # 主机端口spec.type 的说明：ClusterIP（默认值）：通过集群的内部 IP 暴露服务，选择该值时服务只能够在集群内部访问。NodePort：通过每个节点上的 IP 和静态端口（NodePort）暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>，我们可以从集群的外部访问一个 NodePort 服务。LoadBalancer：使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。ExternalName：通过返回 CNAME 和对应值，可以将服务映射到 externalName 字段的内容（如：foo.bar.example.com）。 无需创建任何类型代理。OSI 网络模型和 TCP/IP 协议：注意：Service 会基于 Pod 的探针机制（readinessProbe，就绪探针）完成 Pod 的自动剔除（没有就绪的 Pod）和上线工作。Service 即使是无头服务（Headless Service），其他的 Kubernetes 应用资源（如：Pod 等）不能通过 IP 访问，但是可以将 Service 名字当成域名来访问；非无头服务，更是如此了。Service 默认使用的协议是 TCP 协议，对应的是 OSI 网络模型中的第四层传输层，所以也有人称 Service 为四层网络负载均衡。selector 只有在 Service 的 spec.type 是 ClusterIP、NodePort、LoadBalancer 才有效。2.2 准备工作在使用 Service 之前，首先利用 Deployment 创建出 3 个Pod 。vi k8s-deploy.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy namespace: default labels: app: nginx spec: selector: matchLabels: app: nginx replicas: 3 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: # 资源限制 requests: cpu: 250m memory: 100Mi limits: cpu: 500m memory: 200Mi livenessProbe: tcpSocket: port: 80 initialDelaySeconds: 5 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 3 periodSeconds: 10 readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 timeoutSeconds: 2 successThreshold: 1 failureThreshold: 3 periodSeconds: 10 ports: - containerPort: 80 name: nginx volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Alwayskubectl apply -f k8s-deploy.yaml为了方便测试，修改 Pod 中 Nginx 的 index.html ：kubectl exec -it xxx -c nginx -- /bin/shopenssl rand -base64 8 > /usr/share/nginx/html/index.html2.3 ClusterIP 类型的 Service2.2.1 创建 Service新建 k8s-svc.yaml：vi k8s-svc.yamlapiVersion: v1 kind: Service metadata: name: cluster-ip-svc namespace: default spec: selector: app: nginx type: ClusterIP # ClusterIP 指的是当前 Service 在 Kubernetes 集群内可以被所有 Kubernetes 资源发现，默认给这个 Service 分配一个集群内的网络。 # 参考 kubeadmin 创建集群 # 由于默认拉取镜像地址k8s.gcr.io国内无法访问，这里需要指定阿里云镜像仓库地址 # kubeadm init \ # --apiserver-advertise-address=192.168.65.100 \ # --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \ # --kubernetes-version v1.21.0 \ # --service-cidr=10.96.0.0/16 \ # 如果 spec.clusterIP 为空，就会随机分配一个 10.96.0.0 子网范围的 IP 地址。如果不为空，spec.clusterIP 必须是这个范围内的地址，且不能和当前集群内的其他 Service 产生冲突。 # --pod-network-cidr=10.244.0.0/16 # clusterIP: 10.96.1.2 ports: - name: nginx port: 80 # service 的端口，访问到 service 的 80 端口 targetPort: 80 # Pod 的端口，派发到 Pod 的 80 端口创建 Service ：kubectl apply -f k8s-svc.yaml原理：注意：① curl 10.96.12.53:80 是可以访问到 Pod 中的容器。② curl 10.96.12.53:6379 不可以访问到 Pod 中的容器。③ 将 Kubernetes 中能分配 IP 地址的都看做是一个小型的、轻巧的、虚拟的 Linux 主机，10.96.12.53:6379 是因为我们没有在 service 中开启端口。2.2.2 查看 Service 的详细信息语法：kubectl describe svc xxx示例：kubectl describe svc cluster-ip-svc2.2.3 EndPoint（端点）Endpoint 是 Kubernetes 中的一个资源对象，存储在etcd 中，用来记录一个 Service 对应的所有 Pod 的访问地址，它是根据Service配置文件中的 Selector 描述产生的（当然，也可以自定义端点）。一个 Service 由一组 Pod 组成，这些 Pod 通过 Endpoints 暴露出来，Endpoints 是实现实际服务的端点集合。换言之，Service 和 Pod 之间的联系是通过 Endpoints 实现的。示例：查询端点信息# endpoint 的别名是 ep kubectl get endpoint示例：删除一个 Pod ，Deployment 会将 Pod 重建，Endpoints 的内容随着 Pod 发生变化。kubectl delete pod xxx# 10.96.101.172 是 service 的地址 curl 10.96.101.172示例：创建自定义的 EndPointvi k8s-ep.yamlapiVersion: v1 kind: Service metadata: name: cluster-svc-no-selector namespace: default spec: ports: - name: http port: 80 targetPort: 80 # 此时的 targetPort 需要和 Endpoints 的 subsets.addresses.ports.port 相同 --- apiVersion: v1 kind: Endpoints metadata: name: cluster-svc-no-selector # 此处的 name 需要和 Service 的 metadata 的 name 相同 namespace: default subsets: - addresses: - ip: 220.181.38.251 # 百度 - ip: 221.122.82.30 # 搜狗 - ip: 61.129.7.47 # QQ ports: - name: http # 此处的 name 需要和 Service 的 spec.ports.name 相同 port: 80 protocol: TCPkubectl apply -f k8s-ep.yaml鉴于有些人认为像 MySQL 之类的不应该使用 Docker 、k8s 部署，而是应该直接部署在物理机上，那么就可以使用此种方式，将 MySQL 的地址配置在 EndPoint 中，对外暴露一个 Service ，这样 Kubernetes 中的资源（如：Pod）就可以直接使用暴露的 Service 名称来访问，这样可以实时剔除 EndPoint 的信息。换言之，反向代理集群外的服务。2.2.4 域名解析进入其中 Pod ，使用 nslookup 命令查询 DNS ：kubectl exec -it xxx -- /bin/bash# 安装 nslookup apt-get update apt-get -y install bind-utils# 其中 cluster-ip-svc 是 Service 的服务名 nslookup -type=a cluster-ip-svc总结：网络层次2.2.5 会话保持技术基于客户端 IP 地址的会话保持模式，即来自同一个客户端发起的所有请求都尽最大可能转发到固定的一个 Pod 上，只需要在 spec 中添加 sessionAffinity: ClusterIP 选项。示例：vi k8s-sessionAffinity.yamlapiVersion: v1 kind: Service metadata: name: k8s-session-affinity-svc namespace: default spec: selector: app: nginx type: ClusterIP sessionAffinity: "ClientIP" sessionAffinityConfig: clientIP: timeoutSeconds: 11800 # 30 分钟 ports: - name: nginx protocol: TCP port: 80 targetPort: 80kubectl apply -f k8s-sessionAffinity.yaml2.2.6 删除 Service命令：kubectl delete -f k8s-svc.yaml2.4 HeadLiness 类型的 Service所谓的 HeadLiness 类型的 Service ，即所谓的无头服务，即不希望 Kubernetes 集群分配 IP ，针对这种情况，Kubernetes 提供了 HeadLinesss Service，这类 Service 不会分配 Cluster IP，如果想要访问 Service ，只能通过 Service 的域名进行查询，一般配合 StatefulSet 使用。语法：... spec: type: ClusterIP # ClusterIP 指的是当前 Service 在集群内可以被所有人发现，默认给这个 Service 分配一个集群内的网络。 clusterIp: None # k8s 不要给这个 service 分配 IP，headless service 无头服务配合 StatefulSet ...示例：vi k8s-ClusterIPNone.yamlapiVersion: v1 kind: Service metadata: name: cluster-ip-svc namespace: default spec: selector: app: nginx type: ClusterIP # ClusterIP 指的是当前 Service 在集群内可以被所有人发现，默认给这个 Service 分配一个集群内的网络。 clusterIP: None # k8s 不要给这个 service 分配 IP，headless service 无头服务配合 StatefulSet ports: - name: nginx protocol: TCP port: 80 # service 的端口，访问到 service 的 80 端口 targetPort: 80 # Pod 的端口，派发到 Pod 的 80 端口kubectl apply -f k8s-ClusterIPNone.yaml2.5 NodePort 类型的 Service2.5.1 概述在之前的案例中，创建的 Service 的 IP 地址只能在集群内部才可以访问，如果希望 Service 暴露给集群外部使用，那么就需要使用到另外一种类型的 Service，称为 NodePort 类型的 Service。NodePort 的工作原理就是将 Service 的端口映射到 Node 的一个端口上，然后就可以通过 NodeIP:NodePort 来访问 Service 了。语法：... spec: ... type: NodePort # 每台机器都会为这个 service 随机分配一个指定的端口 ports: - name: nginx protocol: TCP port: 80 # service 的端口，访问到 service 的 80 端口 targetPort: 80 # Pod 的端口，派发到 Pod 的 80 端口 nodePort: 31111 # 不指定，默认会在 30000 ~ 32767 范围内随机分配，集群中的所有机器都会打开这个端口，访问 K8s 集群中的任意一条机器都可以访问 Service 代理的 Pod 。2.5.2 部署 Service创建 Service ：vi k8s-NodePort.yamlapiVersion: v1 kind: Service metadata: name: cluster-ip-svc namespace: default spec: selector: app: nginx type: NodePort # 每台机器都会为这个 service 随机分配一个指定的端口 ports: - name: nginx protocol: TCP port: 80 # service 的端口，访问到 service 的 80 端口 targetPort: 80 # Pod 的端口，派发到 Pod 的 80 端口 nodePort: 31111 # 不指定，默认会在 30000 ~ 32767 范围内随机分配，集群中的所有机器都会打开这个端口，访问 K8s 集群中的任意一条机器都可以访问 Service 代理的 Pod 。kubectl apply -f k8s-NodePort.yaml2.6 LoadBalancer 类型的 ServiceLoadBalancer 和 NodePort 很相似，目的都是向外部暴露一个端口，区别在于 LoadBalancer 会在集群的外部再来做一个负载均衡设备，而这个设备需要外部环境的支持，外部服务发送到这个设备上的请求，会被设备负载之后转发到集群中。这个功能需要云平台厂商的支持；换言之，加钱吧。示例：执行也没用apiVersion: v1 kind: Service metadata: name: k8s-load-balancer-svc namespace: default spec: selector: app: nginx type: LoadBalancer # 负载均衡，开放给云平台实现的，阿里云、百度云等。 ports: - name: nginx protocol: TCP port: 80 # service 的端口，访问到 service 的 80 端口 targetPort: 80 # Pod 的端口，派发到 Pod 的 80 端口 nodePort: 311112.7 ExternalName 类型的 ServiceExternalName 类型的 Service 用于引入集群外部的服务，它通过 externalName 属性指定一个服务的地址，然后在集群内部访问此 Service 就可以访问到外部的服务了，但是需要注意目标服务的 跨域 问题。示例：apiVersion: v1 kind: Service metadata: name: k8s-external-name-svc namespace: default spec: type: ExternalName # ExternalName 类型的 Service 用于引入集群外部的服务，它通过 externalName 属性指定一个服务的地址，然后在集群内部访问此 Service 就可以访问到外部的服务了。 externalName: www.baidu.com # 其他的Pod可以通过访问这个service而访问其他的域名服务，但是需要注意目标服务的跨域问题。2.8 Pod 指定自己的主机名Pod 中还可以设置 hostname 和 subdomain 字段，需要注意的是，一旦设置了 hostname ，那么该 Pod 的主机名就被设置为 hostname 的值，而 subdomain 需要和 svc 中的 name 相同。示例：apiVersion: v1 kind: Service metadata: name: default-subdomain namespace: default spec: selector: app: nginx type: ClusterIP clusterIP: None ports: - name: foo protocol: TCP port: 1234 targetPort: 1234 --- apiVersion: v1 kind: Pod metadata: name: nginx1 namespace: default labels: app: nginx spec: hostname: nginx1 subdomain: default-subdomain # 必须和 svc 的 name 相同，才可以使用 hostname.subdomain.名称空间.svc.cluster.local 访问，否则访问不到指定的 Pod 。 containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 250m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: nginx volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Always --- apiVersion: v1 kind: Pod metadata: name: nginx2 namespace: default labels: app: nginx spec: hostname: nginx2 subdomain: default-subdomain # 必须和 svc 的 name 相同，才可以使用 hostname.subdomain.名称空间.svc.cluster.local 访问，否则访问不到指定的 Pod 。 containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 250m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: nginx volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Always如果 Pod 和 Pod 之间的访问：Pod 的 hostname.Pod 的 subdomain.名称空间.svc.cluster.local；换言之，nginx1 访问 nginx2 只需要这样：nginx2.default-subdomain.default.svc.cluster.local 即可（默认的名称空间可以省略，也可以这样 nginx2.default-subdomain）。那么 Pod 和 Service 的访问：Pod 的 subdomain.名称空间.svc.cluster.local。第三章：Ingress3.1 为什么需要 Ingress ？Service 可以使用 NodePort 暴露集群外访问端口，但是性能低、不安全并且端口的范围有限。Service 缺少七层（OSI 网络模型）的统一访问入口，负载均衡能力很低，不能做限流、验证非法用户、链路追踪等等。Ingress 公开了从集群外部到集群内 服务 的 HTTP 和 HTTPS 路由。流量路由由 Ingress 资源上定义的规则控制。我们使用 Ingress 作为整个集群统一的入口，配置 Ingress 规则转发到对应的 Service 。3.2 Ingress nginx 和 nginx ingress3.2.1 nginx Ingress（不用）nginx Ingress是 Nginx 官网适配 k8s 的，分为开源版和 nginx plus 版（收费）。官网地址。3.2.2 ingress nginxingress nginx 是 k8s 官方出品的，会及时更新一些特性，性能很高，被广泛采用。官网地址。注意：以后如果不特别说明，ingress = ingress nginx 。3.3 Ingress 安装自建集群采用裸金属安装方式。下载：wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.2/deploy/static/provider/baremetal/deploy.yaml需要做如下修改：① 修改 k8s.gcr.io/ingress-nginx/controller:v1.1.2 镜像为 registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.1.2 。② 修改 k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1 镜像为 registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v1.1.1。③ 修改 Service 为 ClusterIP，无需 NodePort 模式。④ 修改 Deployment 为 DaemonSet 。⑤ 修改 Container 使用主机网络，直接在主机上开辟 80 、443 端口，无需中间解析，速度更快，所有各个节点的 80 和 443 端口不能被其它进程占用。⑥ Container 使用主机网络，对应的 dnsPolicy 策略也需要改为主机网络的。⑦ 修改 DaemonSet 的 nodeSelector: ingress-node=true 。这样只需要给 node 节点打上 ingress-node=true 标签，即可快速的加入或剔除 ingress-controller的数量创建文件：vi deploy.yamldeploy.yaml 修改的内容如下：#GENERATED FOR K8S 1.20 apiVersion: v1 kind: Namespace metadata: labels: app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx name: ingress-nginx --- apiVersion: v1 automountServiceAccountToken: true kind: ServiceAccount metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx namespace: ingress-nginx --- apiVersion: v1 kind: ServiceAccount metadata: annotations: helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx namespace: ingress-nginx rules: - apiGroups: - "" resources: - namespaces verbs: - get - apiGroups: - "" resources: - configmaps - pods - secrets - endpoints verbs: - get - list - watch - apiGroups: - "" resources: - services verbs: - get - list - watch - apiGroups: - networking.k8s.io resources: - ingresses verbs: - get - list - watch - apiGroups: - networking.k8s.io resources: - ingresses/status verbs: - update - apiGroups: - networking.k8s.io resources: - ingressclasses verbs: - get - list - watch - apiGroups: - "" resourceNames: - ingress-controller-leader resources: - configmaps verbs: - get - update - apiGroups: - "" resources: - configmaps verbs: - create - apiGroups: - "" resources: - events verbs: - create - patch --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: annotations: helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission namespace: ingress-nginx rules: - apiGroups: - "" resources: - secrets verbs: - get - create --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx rules: - apiGroups: - "" resources: - configmaps - endpoints - nodes - pods - secrets - namespaces verbs: - list - watch - apiGroups: - "" resources: - nodes verbs: - get - apiGroups: - "" resources: - services verbs: - get - list - watch - apiGroups: - networking.k8s.io resources: - ingresses verbs: - get - list - watch - apiGroups: - "" resources: - events verbs: - create - patch - apiGroups: - networking.k8s.io resources: - ingresses/status verbs: - update - apiGroups: - networking.k8s.io resources: - ingressclasses verbs: - get - list - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission rules: - apiGroups: - admissionregistration.k8s.io resources: - validatingwebhookconfigurations verbs: - get - update --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx namespace: ingress-nginx roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ingress-nginx subjects: - kind: ServiceAccount name: ingress-nginx namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: annotations: helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission namespace: ingress-nginx roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ingress-nginx-admission subjects: - kind: ServiceAccount name: ingress-nginx-admission namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: ingress-nginx subjects: - kind: ServiceAccount name: ingress-nginx namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: annotations: helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: ingress-nginx-admission subjects: - kind: ServiceAccount name: ingress-nginx-admission namespace: ingress-nginx --- apiVersion: v1 data: allow-snippet-annotations: "true" kind: ConfigMap metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-controller namespace: ingress-nginx --- apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-controller namespace: ingress-nginx spec: ipFamilies: - IPv4 ipFamilyPolicy: SingleStack ports: - appProtocol: http name: http port: 80 protocol: TCP targetPort: http - appProtocol: https name: https port: 443 protocol: TCP targetPort: https selector: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx type: ClusterIP # NodePort --- apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-controller-admission namespace: ingress-nginx spec: ports: - appProtocol: https name: https-webhook port: 443 targetPort: webhook selector: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx type: ClusterIP --- apiVersion: apps/v1 kind: DaemonSet # Deployment metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-controller namespace: ingress-nginx spec: minReadySeconds: 0 revisionHistoryLimit: 10 selector: matchLabels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx template: metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx spec: dnsPolicy: ClusterFirstWithHostNet # dns 调整为主机网络 ，原先为 ClusterFirst hostNetwork: true # 直接让 nginx 占用本机的 80 和 443 端口，这样就可以使用主机网络 containers: - args: - /nginx-ingress-controller - --election-id=ingress-controller-leader - --controller-class=k8s.io/ingress-nginx - --ingress-class=nginx - --report-node-internal-ip-address=true - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller - --validating-webhook=:8443 - --validating-webhook-certificate=/usr/local/certificates/cert - --validating-webhook-key=/usr/local/certificates/key env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: LD_PRELOAD value: /usr/local/lib/libmimalloc.so image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.1.2 # 修改 k8s.gcr.io/ingress-nginx/controller:v1.1.2@sha256:28b11ce69e57843de44e3db6413e98d09de0f6688e33d4bd384002a44f78405c imagePullPolicy: IfNotPresent lifecycle: preStop: exec: command: - /wait-shutdown livenessProbe: failureThreshold: 5 httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 name: controller ports: - containerPort: 80 name: http protocol: TCP - containerPort: 443 name: https protocol: TCP - containerPort: 8443 name: webhook protocol: TCP readinessProbe: failureThreshold: 3 httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 resources: # 资源限制 requests: cpu: 100m memory: 90Mi limits: cpu: 500m memory: 500Mi securityContext: allowPrivilegeEscalation: true capabilities: add: - NET_BIND_SERVICE drop: - ALL runAsUser: 101 volumeMounts: - mountPath: /usr/local/certificates/ name: webhook-cert readOnly: true nodeSelector: node-role: ingress # 以后只需要给某个 node 打上这个标签就可以部署 ingress-nginx 到这个节点上了 # kubernetes.io/os: linux serviceAccountName: ingress-nginx terminationGracePeriodSeconds: 300 volumes: - name: webhook-cert secret: secretName: ingress-nginx-admission --- apiVersion: batch/v1 kind: Job metadata: annotations: helm.sh/hook: pre-install,pre-upgrade helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission-create namespace: ingress-nginx spec: template: metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission-create spec: containers: - args: - create - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc - --namespace=$(POD_NAMESPACE) - --secret-name=ingress-nginx-admission env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v1.1.1 # k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660 imagePullPolicy: IfNotPresent name: create securityContext: allowPrivilegeEscalation: false nodeSelector: kubernetes.io/os: linux restartPolicy: OnFailure securityContext: fsGroup: 2000 runAsNonRoot: true runAsUser: 2000 serviceAccountName: ingress-nginx-admission --- apiVersion: batch/v1 kind: Job metadata: annotations: helm.sh/hook: post-install,post-upgrade helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission-patch namespace: ingress-nginx spec: template: metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission-patch spec: containers: - args: - patch - --webhook-name=ingress-nginx-admission - --namespace=$(POD_NAMESPACE) - --patch-mutating=false - --secret-name=ingress-nginx-admission - --patch-failure-policy=Fail env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v1.1.1 # k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660 imagePullPolicy: IfNotPresent name: patch securityContext: allowPrivilegeEscalation: false nodeSelector: kubernetes.io/os: linux restartPolicy: OnFailure securityContext: fsGroup: 2000 runAsNonRoot: true runAsUser: 2000 serviceAccountName: ingress-nginx-admission --- apiVersion: networking.k8s.io/v1 kind: IngressClass metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: nginx spec: controller: k8s.io/ingress-nginx --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-admission webhooks: - admissionReviewVersions: - v1 clientConfig: service: name: ingress-nginx-controller-admission namespace: ingress-nginx path: /networking/v1/ingresses failurePolicy: Fail matchPolicy: Equivalent name: validate.nginx.ingress.kubernetes.io rules: - apiGroups: - networking.k8s.io apiVersions: - v1 operations: - CREATE - UPDATE resources: - ingresses sideEffects: None给 Node 节点打标签：kubectl label node k8s-node1 node-role=ingresskubectl label node k8s-node2 node-role=ingress当然，也可以给 Master 节点打标签，但是 kubeadm 安装 k8s 集群的时候，会给 Master 节点打上污点，即使我们打上标签，也不会进行 Pod 的调度；换言之，Ingress 也不会在 Master 节点上安装，后面再讲。安装 Ingress （需要关闭 Node 节点的 80 和 443 端口，不能有其他进程占用）：kubectl apply -f deploy.yaml验证是否安装成功：只需要在部署了 ingress 的主机，执行如下的命令：netstat -ntlp | grep 80netstat -ntlp | grep 443卸载：kubectl delete -f deploy.yaml3.4 Ingress 工作原理工作原理：① 用户编写 Ingress 规则，说明哪个域名对应 k8s 集群中的哪个 Service。② Ingress 控制器动态感知 Ingress 服务规则的变化，然后生成一段对应的 Nginx 的反向代理配置。③ Ingress 控制器会将生成的 Nginx 配置写入到一个运行着的 Nginx 服务中，并动态更新。3.5 准备工作为了后面的实验比较方便，创建如下图所示的模型：创建实验所需的 Service 以及 Pod 等：vi k8s-prepare.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy namespace: default labels: app: nginx spec: selector: matchLabels: app: nginx replicas: 3 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: requests: cpu: 100m memory: 100Mi limits: cpu: 250m memory: 500Mi ports: - containerPort: 80 name: nginx volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: name: tomcat-deploy namespace: default labels: app: tomcat spec: selector: matchLabels: app: tomcat replicas: 3 template: metadata: labels: app: tomcat spec: containers: - name: tomcat image: tomcat:9-jre8 env: - name: JAVA_OPTS value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" resources: requests: cpu: 500m memory: 256Mi limits: cpu: 500m memory: 500Mi ports: - containerPort: 8080 name: tomcat volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: default spec: selector: app: nginx type: ClusterIP ports: - name: nginx protocol: TCP port: 80 targetPort: 80 --- apiVersion: v1 kind: Service metadata: name: tomcat-svc spec: selector: app: tomcat type: ClusterIP ports: - name: tomcat protocol: TCP port: 8080 targetPort: 8080kubectl apply -f k8s-prepare.yaml3.6 实战3.6.1 基本配置语法：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-http namespace: default annotations: kubernetes.io/ingress.class: "nginx" spec: rules: # 规则 - host: it.nginx.com # 指定的监听的主机域名，相当于 nginx.conf 的 server { xxx } http: # 指定路由规则 paths: - path: / pathType: Prefix # 匹配规则，Prefix 前缀匹配 it.nginx.com/* 都可以匹配到 backend: # 指定路由的后台服务的 service 名称 service: name: nginx-svc # 服务名 port: number: 80 # 服务的端口pathType 说明：Prefix：基于以 / 分隔的 URL 路径前缀匹配。匹配区分大小写，并且对路径中的元素逐个完成。 路径元素指的是由 / 分隔符分隔的路径中的标签列表。 如果每个 p 都是请求路径 p 的元素前缀，则请求与路径 p 匹配。Exact：精确匹配 URL 路径，且区分大小写。ImplementationSpecific：对于这种路径类型，匹配方法取决于 IngressClass。 具体实现可以将其作为单独的 pathType 处理或者与 Prefix 或 Exact 类型作相同处理。注意：ingress 规则会生效到所有安装了 IngressController 的机器的 nginx 配置。示例：vi k8s-ingress.yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-http namespace: default annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/backend-protocol: "HTTP" spec: rules: # 规则 - host: nginx.xudaxian.com # 指定的监听的主机域名，相当于 nginx.conf 的 server { xxx } http: # 指定路由规则 paths: - path: / pathType: Prefix # 匹配规则，Prefix 前缀匹配 nginx.xudaxian.com/* 都可以匹配到 backend: # 指定路由的后台服务的 service 名称 service: name: nginx-svc # 服务名 port: number: 80 # 服务的端口 - host: tomcat.xudaxian.com # 指定的监听的主机域名，相当于 nginx.conf 的 server { xxx } http: # 指定路由规则 paths: - path: / pathType: Prefix # 匹配规则，Prefix 前缀匹配 tomcat.xudaxian.com/* 都可以匹配到 backend: # 指定路由的后台服务的 service 名称 service: name: tomcat-svc # 服务名 port: number: 8080 # 服务的端口kubectl apply -f k8s-ingress.yaml示例的示意图：测试示例（后面有更简单的测试方法）：第一种方案：① 在 win 中的 hosts（C:\Windows\System32\drivers\etc\hosts） 文件中添加如下的信息：# 用来模拟 DNS ，其中 192.168.65.101 是 ingress 部署的机器，nginx.xudaxian.com 和 tomcat.xudaxian.com 是 ingress 文件中监听的域名 192.168.65.101 nginx.xudaxian.com 192.168.65.101 tomcat.xudaxian.com② 在 win 中使用 curl 命令：为什么这么干？因为我使用了科学上网，网络出现了问题。第二种方案：① 重新建立一个虚拟机（安装有 CentOS7 系统，并且带有桌面），在 hosts （/etc/hosts）文件中添加如下的内容：# 用来模拟 DNS ，其中 192.168.65.101 是 ingress 部署的机器，nginx.xudaxian.com 和 tomcat.xudaxian.com 是 ingress 文件中监听的域名 192.168.65.101 nginx.xudaxian.com 192.168.65.101 tomcat.xudaxian.com② 通过浏览器访问：证明：ingress 规则会生效到所有安装了 IngressController 的机器的 nginx 配置cat nginx.conf | grep nginx.xudaxian.com -A 203.6.2 默认后端示例：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-http namespace: default annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/backend-protocol: "HTTP" spec: defaultBackend: # 指定所有未匹配的默认后端 service: name: nginx-svc port: number: 80 rules: - host: tomcat.com http: paths: - path: /abc pathType: Prefix backend: service: name: tomcat-svc port: number: 8080效果：tomcat.com 域名的 非 /abc 开头的请求，都会转到 defaultBackend 。非 tomcat.com 域名下的所有请求，也会转到 defaultBackend 。3.6.3 Ingress 中的 nginx 的全局配置官方文档。方式一：在安装的时候，添加配置。apiVersion: v1 data: allow-snippet-annotations: "true" # Nginx 的全局配置 kind: ConfigMap metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.1.2 helm.sh/chart: ingress-nginx-4.0.18 name: ingress-nginx-controller namespace: ingress-nginx方式二：编辑 cm ：kubectl edit cm ingress-nginx-controller -n ingress-nginx# 配置项加上 data: map-hash-bucket-size: "128" # Nginx 的全局配置 ssl-protocols: SSLv23.6.4 限流官方文档。示例：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: rate-ingress namespace: default annotations: # https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#rate-limiting kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/backend-protocol: "HTTP" nginx.ingress.kubernetes.io/limit-rps: "1" # 限流 spec: rules: - host: nginx.xudaxian.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 803.6.5 路径重写官方文档。路径重写，经常用于前后端分离。示例：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-rewrite namespace: default annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 # 路径重写 spec: ingressClassName: nginx rules: - host: baidu.com http: paths: - path: /api(/|$)(.*) pathType: Prefix backend: service: name: nginx-svc port: number: 803.6.6 基于 Cookie 的会话保持技术Service 只能基于 ClientIP，但是 ingress 是七层负载均衡，可以基于 Cookie 实现会话保持。示例：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: rate-ingress namespace: default annotations: # https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#rate-limiting kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/backend-protocol: "HTTP" nginx.ingress.kubernetes.io/affinity: "cookie" spec: rules: - host: nginx.xudaxian.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 803.6.7 配置 SSL官方地址。生成证书语法：$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj "/CN=${HOST:baidu.com}/O=${HOST:baidu.com}"kubectl create secret tls ${CERT_NAME:baidu-tls} --key ${KEY_FILE:tls.key} --cert ${CERT_FILE:tls.cert}示例：生成证书openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.cert -subj "/CN=xudaxian.com/O=xudaxian.com"kubectl create secret tls xudaxian-tls --key tls.key --cert tls.cert示例：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-tls namespace: default annotations: kubernetes.io/ingress.class: "nginx" spec: tls: - hosts: - nginx.xudaxian.com # 通过浏览器访问 https://nginx.xudaxian.com - tomcat.xudaxian.com # 通过浏览器访问 https://tomcat.xudaxian.com secretName: xudaxian-tls rules: - host: nginx.xudaxian.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 80 - host: tomcat.xudaxian.com http: paths: - path: / pathType: Prefix backend: service: name: tomcat-svc port: number: 8080注意：实际开发的时候，需要自己购买证书。3.6.8 Ingress 金丝雀发布3.6.8.1 概述以前使用 Kubernetes 的 Service 配合 Deployment 进行金丝雀的部署，原理如下所示：但是，也是有缺点的，就是不能自定义灰度逻辑，如：指定用户进行灰度（新功能只让 VIP 先体验一个月，一个月之后再将新功能解锁，让所有用户都体验到）。Ingress 金丝雀发布的原理如下所示：3.6.8.2 准备工作部署 Service 和 Deployment：vi k8s-ingress-canary-deploy.yamlapiVersion: apps/v1 kind: Deployment metadata: name: v1-deployment labels: app: v1-deployment spec: replicas: 2 selector: matchLabels: app: v1-pod template: metadata: name: nginx labels: app: v1-pod spec: initContainers: - name: alpine image: alpine imagePullPolicy: IfNotPresent command: ["/bin/sh","-c","echo v1-pod > /app/index.html;"] volumeMounts: - mountPath: /app name: app containers: - name: nginx image: nginx:1.17.2 imagePullPolicy: IfNotPresent ports: - containerPort: 80 resources: requests: cpu: 100m memory: 100Mi limits: cpu: 250m memory: 500Mi volumeMounts: - name: app mountPath: /usr/share/nginx/html volumes: - name: app emptyDir: {} restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: v1-service namespace: default spec: selector: app: v1-pod type: ClusterIP ports: - name: nginx protocol: TCP port: 80 targetPort: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: v2-deployment labels: app: v2-deployment spec: replicas: 3 selector: matchLabels: app: v2-pod template: metadata: name: v2-pod labels: app: v2-pod spec: containers: - name: nginx image: nginx:1.17.2 imagePullPolicy: IfNotPresent ports: - containerPort: 80 resources: requests: cpu: 100m memory: 100Mi limits: cpu: 250m memory: 500Mi restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: v2-service namespace: default spec: selector: app: v2-pod type: ClusterIP ports: - name: nginx protocol: TCP port: 80 targetPort: 80kubectl apply -f k8s-ingress-canary-deploy.yaml部署普通的 ingress ：vi k8s-ingress-v1.yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-v1 namespace: default spec: ingressClassName: "nginx" rules: - host: nginx.xudaxian.com http: paths: - path: / pathType: Prefix backend: service: name: v1-service port: number: 80kubectl apply -f k8s-ingress-v1.yaml测试 ingress ：# curl 来模拟请求 curl -H "Host: canary.example.com" http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IPcurl -H "Host: nginx.xudaxian.com" http://192.168.65.1013.6.8.3 基于 Header 的流量切分示例：vi k8s-ingress-header.yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-canary namespace: default annotations: nginx.ingress.kubernetes.io/canary: "true" # 开启金丝雀 nginx.ingress.kubernetes.io/canary-by-header: "Region" # 基于请求头 # 如果 请求头 Region = always ，就路由到金丝雀版本；如果 Region = never ，就永远不会路由到金丝雀版本。 nginx.ingress.kubernetes.io/canary-by-header-value: "sz" # 自定义值 # 如果 请求头 Region = sz ，就路由到金丝雀版本；如果 Region != sz ，就永远不会路由到金丝雀版本。 # nginx.ingress.kubernetes.io/canary-by-header-pattern: "sh|sz" # 如果 请求头 Region = sh 或 Region = sz ，就路由到金丝雀版本；如果 Region != sz 并且 Region != sz ，就永远不会路由到金丝雀版本。 spec: ingressClassName: "nginx" rules: - host: nginx.xudaxian.com http: paths: - path: / pathType: Prefix backend: service: name: v2-service port: number: 80kubectl apply -f k8s-ingress-header.yaml测试：curl -H "Host: nginx.xudaxian.com" http://192.168.65.101curl -H "Host: nginx.xudaxian.com" -H "Region: sz" http://192.168.65.101curl -H "Host: nginx.xudaxian.com" -H "Region: sh" http://192.168.65.1013.6.8.4 基于 Cookie 的流量切分示例：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-canary namespace: default annotations: nginx.ingress.kubernetes.io/canary: "true" # 开启金丝雀 nginx.ingress.kubernetes.io/canary-by-cookie: "vip" # 如果 cookie 是 vip = always ，就会路由到到金丝雀版本；如果 cookie 是 vip = never ，就永远不会路由到金丝雀的版本。 spec: ingressClassName: "nginx" rules: - host: nginx.xudaxian.com http: paths: - path: / pathType: Prefix backend: service: name: v2-service port: number: 80curl -H "Host: nginx.xudaxian.com" --cookie "vip=always" http://192.168.65.1013.6.8.5 基于服务权重的流量切分示例：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-canary namespace: default annotations: nginx.ingress.kubernetes.io/canary: "true" # 开启金丝雀 nginx.ingress.kubernetes.io/canary-weight: "10" # 基于服务权重 spec: ingressClassName: "nginx" rules: - host: nginx.xudaxian.com http: paths: - path: / pathType: Prefix backend: service: name: v2-service port: number: 80for i in {1..10}; do curl -H "Host: nginx.xudaxian.com" http://192.168.65.101; done;第四章：NetworkPolicy（网络策略）4.1 概述网络策略指的是 Pod 间的网络隔离策略，默认情况下是互通的。Pod 之间的互通是通过如下三个标识符的组合来辨识的：① 其他被允许的 Pod（例外：Pod 无法阻塞对自身的访问）。② 被允许的名称空间。③ IP 组块（例外：与 Pod 运行所在的节点的通信总是被允许的， 无论 Pod 或节点的 IP 地址）。4.2 Pod 隔离和非隔离默认情况下，Pod 都是非隔离的（non-isolated），可以接受来自任何请求方的网络请求。如果一个 NetworkPolicy 的标签选择器选中了某个 Pod，则该 Pod 将变成隔离的（isolated），并将拒绝任何不被 NetworkPolicy 许可的网络连接。示例： apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: networkpol-01 namespace: default spec: podSelector: # Pod 选择器 matchLabels: app: nginx # 选中的 Pod 就被隔离起来了 policyTypes: # 策略类型 - Ingress # Ingress 入站规则、Egress 出站规则 - Egress ingress: # 入站白名单，什么能访问我 - from: - podSelector: # 选中的 Pod 可以访问 spec.matchLabels 中筛选的 Pod matchLabels: access: granted ports: - protocol: TCP port: 80 egress: # 出站白名单，我能访问什么 - to: - podSelector: # spec.matchLabels 中筛选的 Pod 能访问的 Pod matchLabels: app: tomcat - namespaceSelector: matchLabels: kubernetes.io/metadata.name: dev # spec.matchLabels 中筛选的 Pod 能访问 dev 命名空间下的所有 ports: - protocol: TCP port: 80804.3 场景官方文档。
第一章：概述1.1 概述容器的生命周期可能很短，会被频繁的创建和销毁。那么容器在销毁的时候，保存在容器中的数据也会被清除。这种结果对用户来说，在某些情况下是不乐意看到的。为了持久化保存容器中的数据，Kubernetes 引入了 Volume 的概念。和 Docker 中的卷管理（匿名卷、具名卷、自定义挂载目录，都是挂载在本机，功能非常有限）不同的是，Kubernetes 天生就是集群，所以为了方便管理，Kubernetes 将 卷 抽取为一个对象资源，这样可以更方便的管理和存储数据。Volume 是 Pod 中能够被多个容器访问的共享目录，它被定义在 Pod 上，然后被一个 Pod 里面的多个容器挂载到具体的文件目录下，Kubernetes 通过 Volume 实现同一个 Pod 中不同容器之间的数据共享以及数据的持久化存储。Volume 的生命周期不和 Pod 中的单个容器的生命周期有关，当容器终止或者重启的时候，Volume 中的数据也不会丢失。1.2 Kubernetes 支持的 Volume 类型1.2.1 简版Kubernetes 的 Volume 支持多种类型，如下图所示：1.2.2 细分类型Kubernetes 目前支持多达 28 种数据卷类型（其中大部分特定于具体的云环境如 GCE/AWS/Azure 等）非持久性存储：emptyDirHostPath网络连接性存储：SAN：iSCSI、ScaleIO Volumes、FC (Fibre Channel)NFS：nfs，cfs分布式存储GlusterfsRBD (Ceph Block Device)CephFSPortworx VolumesQuobyte Volumes云端存储GCEPersistentDiskAWSElasticBlockStoreAzureFileAzureDiskCinder (OpenStack block storage)VsphereVolumeStorageOS自定义存储FlexVolume第二章：配置2.1 配置最佳实战云原生应用的 12 要素中，提出了配置分离。在推送到集群之前，配置文件应该存储在版本控制系统中。这将允许我们在必要的时候快速回滚配置更改，它有助于集群重新创建和恢复。使用 YAML 而不是 JSON 编写配置文件，虽然这些格式几乎可以在所有场景中互换使用，但是 YAML 往往更加用户友好。建议相关对象分组到一个文件，一个文件通常比几个文件更容易管理。请参阅 guestbook-all-in-one.yaml 文件作为此语法的示例。除非必要，否则不指定默认值（简单的最小配置会降低错误的可能性）。将对象描述放在注释中，以便更好的内省。2.2 Secret2.2.1 概述Secret 对象类型用来保存敏感信息，如：密码、OAuth2 令牌以及 SSH 密钥等。将这些信息放到 Secret 中比放在 Pod 的定义或者容器镜像中更加安全和灵活。由于创建 Secret 可以独立于使用它们的 Pod， 因此在创建、查看和编辑 Pod 的工作流程中暴露 Secret（及其数据）的风险较小。 Kubernetes 和在集群中运行的应用程序也可以对 Secret 采取额外的预防措施，如：避免将机密数据写入非易失性存储。Secret 类似于 ConfigMap 但专门用于保存机密数据。注意：Secret 和 ConfigMap 是保存在 etcd 中。2.2.2 Secret 的种类命令行：create secret --help细分类型：内置类型用法Opaque用户定义的任意数据kubernetes.io/service-account-token服务账号令牌kubernetes.io/dockercfg~/.dockercfg文件的序列化形式 kubernetes.io/dockerconfigjson~/.docker/config.json文件的序列化形式 kubernetes.io/basic-auth用于基本身份认证的凭据kubernetes.io/ssh-auth用于 SSH 身份认证的凭据kubernetes.io/tls用于 TLS 客户端或者服务器端的数据bootstrap.kubernetes.io/token启动引导令牌数据2.2.3 Pod 如何引用要使用 Secret，Pod 需要引用 Secret。 Pod 可以用三种方式之一来使用 Secret ：作为挂载到一个或多个容器上的 卷 中的文件。（volume进行挂载）作为容器的环境变量（envFrom字段引用）由 kubelet 在为 Pod 拉取镜像时使用（此时Secret是docker-registry类型的）Secret 对象的名称必须是合法的 DNS 子域名。 在为创建 Secret 编写配置文件时，你可以设置 data 与/或 stringData 字段。 data 和 stringData 字段都是可选的。data 字段中所有键值都必须是 base64 编码的字符串。如果不希望执行这种 base64 字符串的转换操作，你可以选择设置 stringData 字段，其中可以使用任何字符串作为其取值。2.2.4 创建 Secret命令行创建 Secret ：kubectl create secret generic secret-1 \ --from-literal=username=admin \ --from-literal=password=123456使用 base64 对数据进行编码：# 准备username YWRtaW4= echo -n "admin" | base64# MTIzNDU2 echo -n "123456" | base64使用 yaml 格式创建 Secret ：vi k8s-secret.yamlapiVersion: v1 kind: Secret metadata: name: k8s-secret namespace: default type: Opaque data: username: YWRtaW4= password: MTIzNDU2kubectl apply -f k8s-secret.yaml有的时候，觉得手动将数据编码，再配置到 yaml 文件的方式太繁琐，那么也可以将数据编码交给 Kubernetes ：vi k8s-secret.yamlapiVersion: v1 kind: Secret metadata: name: k8s-secret-string-data namespace: default type: Opaque stringData: username: admin password: "123456"kubectl apply -f k8s-secret.yaml注意：如果同时使用 data 和 stringData ，那么 data 会被忽略。根据文件创建 Secret ：echo -n 'admin' > username.txtecho -n '123456' > password.txtkubectl create secret generic k8s-secret-file \ --from-file=username.txt \ --from-file=password.txt注意：密钥 的 Key 默认是文件名的名称。根据文件创建 Secret（自定义密钥 的 Key ）echo -n 'admin' > username.txtecho -n '123456' > password.txtkubectl create secret generic k8s-secret-file \ --from-file=username=username.txt \ --from-file=password=password.txt2.2.5 查看 Secret示例：以 JOSN 的形式提取 datakubectl get secret k8s-secret-file -o jsonpath='{.data}'示例：以 yaml 的格式kubectl get secret k8s-secret-file -o yaml2.2.6 使用 Secret 之环境变量引用示例：vi k8s-secret.yamlapiVersion: v1 kind: Secret metadata: name: my-secret namespace: default type: Opaque stringData: username: admin password: "1234556" --- apiVersion: v1 kind: Pod metadata: name: pod-secret namespace: default labels: app: pod-secret spec: containers: - name: alpine image: alpine command: ["/bin/sh","-c","sleep 3600"] resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi env: - name: SECRET_USERNAME # 容器中的环境变量名称 valueFrom: secretKeyRef: name: my-secret # 指定 secret 的名称 key: username # secret 中 key 的名称，会自动 base64 解码 - name: SECRET_PASSWORD # 容器中的环境变量名称 valueFrom: secretKeyRef: name: my-secret # 指定 secret 的名称 key: password # secret 中 key 的名称 - name: POD_NAME valueFrom: fieldRef: # 属性引用 fieldPath: metadata.name - name: POD_LIMITS_MEMORY valueFrom: resourceFieldRef: # 资源限制引用 containerName: alpine resource: limits.memory ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Alwayskubectl apply -f k8s-secret.yaml注意：环境变量引用的方式不会被自动更新。2.2.7 使用 Secret 之 卷挂载示例：vi k8s-secret.yamlapiVersion: v1 kind: Secret metadata: name: my-secret namespace: default type: Opaque stringData: username: admin password: "1234556" --- apiVersion: v1 kind: Pod metadata: name: pod-secret namespace: default labels: app: pod-secret spec: containers: - name: alpine image: alpine command: ["/bin/sh","-c","sleep 3600"] resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: app mountPath: /app volumes: - name: app secret: secretName: my-secret # secret 的名称，Secret 中的所有 key 全部挂载出来 restartPolicy: Alwayskubectl apply -f k8s-secret.yaml注意：如果 Secret 以卷挂载的方式，Secret 里面的所有 key 都是文件名，内容就是 key 对应的值。如果 Secret 以卷挂载的方式，Secret 的内容更新，那么容器对应的值也会被更新（subPath 引用除外）。如果 Secret 以卷挂载的方式，默认情况下，挂载出来的文件是只读的。示例：指定挂载的文件名vi k8s-secret.yamlapiVersion: v1 kind: Secret metadata: name: my-secret namespace: default type: Opaque stringData: username: admin password: "1234556" --- apiVersion: v1 kind: Pod metadata: name: pod-secret namespace: default labels: app: pod-secret spec: containers: - name: alpine image: alpine command: ["/bin/sh","-c","sleep 3600"] resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: app mountPath: /app volumes: - name: app secret: secretName: my-secret # secret 的名称，Secret 中的所有 key 全部挂载出来 items: - key: username # secret 中 key 的名称，Secret 中的 username 的内容挂载出来 path: username.md # 在容器内挂载出来的文件的路径 restartPolicy: Alwayskubectl apply -f k8s-secret.yaml2.3 ConfigMap2.3.1 概述ConfigMap 和 Secret 非常类似，只不过 Secret 会将信息进行 base64 编码和解码，而 ConfigMap 却不会。2.3.2 创建 ConfigMap、环境变量引用、卷挂载示例：vi k8s-cm.yamlkind: ConfigMap apiVersion: v1 metadata: name: cm namespace: default data: # 类属性键；每一个键都映射到一个简单的值 player_initial_lives: "3" ui_properties_file_name: "user-interface.properties" # 类文件键 game.properties: | enemy.types=aliens,monsters player.maximum-lives=5 user-interface.properties: | color.good=purple color.bad=yellow allow.textmode=true --- apiVersion: v1 kind: Pod metadata: name: "pod-cm" namespace: default labels: app: "pod-cm" spec: containers: - name: alpine image: "alpine" command: ["/bin/sh","-c","sleep 3600"] resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi env: - name: PLAYER_INITIAL_LIVES valueFrom: configMapKeyRef: name: cm key: player_initial_lives ports: - containerPort: 80 name: http volumeMounts: - name: app mountPath: /app volumes: - name: app configMap: name: cm # secret 的名称，Secret 中的所有 key 全部挂载出来 items: - key: ui_properties_file_name # secret 中 key 的名称，Secret 中的 ui_properties_file_name 的内容挂载出来 path: ui_properties_file_name.md # 在容器内挂载出来的文件的路径 restartPolicy: Alwayskubectl apply -f k8s-cm.yaml注意：ConfigMap 和 Secret 一样，环境变量引用不会热更新，而卷挂载是可以热更新的。最新版本的 ConfigMap 和 Secret 提供了不可更改的功能，即禁止热更新，只需要在 Secret 或 ConfigMap 中设置 immutable = true 。2.3.3 ConfigMap 结合 SpringBoot 做到生产配置无感知第三章：临时存储3.1 概述Kubernetes 为了不同的目的，支持几种不同类型的临时卷：emptyDir： Pod 启动时为空，存储空间来自本地的 kubelet 根目录（通常是根磁盘）或内存configMap、 downwardAPI、 secret： 将不同类型的 Kubernetes 数据注入到 Pod 中CSI 临时卷： 类似于前面的卷类型，但由专门支持此特性 的指定 CSI 驱动程序提供通用临时卷： 它可以由所有支持持久卷的存储驱动程序提供emptyDir、configMap、downwardAPI、secret 是作为 本地临时存储 提供的。它们由各个节点上的 kubelet 管理。CSI 临时卷 必须 由第三方 CSI 存储驱动程序提供。通用临时卷 可以 由第三方 CSI 存储驱动程序提供，也可以由支持动态配置的任何其他存储驱动程序提供。 一些专门为 CSI 临时卷编写的 CSI 驱动程序，不支持动态供应：因此这些驱动程序不能用于通用临时卷。使用第三方驱动程序的优势在于，它们可以提供 Kubernetes 本身不支持的功能， 例如，与 kubelet 管理的磁盘具有不同运行特征的存储，或者用来注入不同的数据。3.2 emptyDirPod 分派到某个 Node 上时，emptyDir 卷会被创建，并且在 Pod 在该节点上运行期间，卷一直存在。就像其名称表示的那样，卷最初是空的。尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同，这些容器都可以读写 emptyDir 卷中相同的文件。当 Pod 因为某些原因被从节点上删除时，emptyDir 卷中的数据也会被永久删除。注意： 容器崩溃并不会导致 Pod 被从节点上移除，因此容器崩溃期间 emptyDir 卷中的数据是安全的。emptyDir 的一些用途：临时空间，例如用于某些应用程序运行时所需的临时目录，且无须永久保留。一个容器需要从另一个容器中获取数据的目录（多容器共享目录）。emptyDir 卷存储在该节点的磁盘或内存中，如果设置 emptyDir.medium = Memory ，那么就告诉 Kubernetes 将数据保存在内存中，并且在 Pod 被重启或删除前，所写入的所有文件都会计入容器的内存消耗，受到容器内存限制约束。示例：vi k8s-emptyDir.yamlapiVersion: v1 kind: Pod metadata: name: nginx-empty-dir namespace: default labels: app: nginx-empty-dir spec: containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: app mountPath: /usr/share/nginx/html - name: alpine image: alpine command: ["/bin/sh","-c","while true;do sleep 1; date > /app/index.html;done"] resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: app mountPath: /app volumes: - name: app emptyDir: {} # emptyDir 临时存储 restartPolicy: Alwayskubectl apply -f k8s-emptyDir.yaml3.3 hostPathemptyDir 中的数据不会被持久化，它会随着 Pod 的结束而销毁，如果想要简单的将数据持久化到主机中，可以选择 hostPath 。hostPath 就是将 Node 主机中的一个实际目录挂载到 Pod 中，以供容器使用，这样的设计就可以保证 Pod 销毁了，但是数据依旧可以保存在 Node 主机上。注意：hostPath 之所以被归为临时存储，是因为实际开发中，我们一般都是通过 Deployment 部署 Pod 的，一旦 Pod 被 Kubernetes 杀死或重启拉起等，并不一定会部署到原来的 Node 节点中。除了必需的 path 属性之外，用户可以选择性地为 hostPath 卷指定 type。取值行为 空字符串（默认）用于向后兼容，这意味着在安装 hostPath 卷之前不会执行任何检查。DirectoryOrCreate如果在给定路径上什么都不存在，那么将根据需要创建空目录，权限设置为 0755，具有与 kubelet 相同的组和属主信息。Directory在给定路径上必须存在的目录。FileOrCreate如果在给定路径上什么都不存在，那么将在那里根据需要创建空文件，权限设置为 0644，具有与 kubelet 相同的组和所有权。File在给定路径上必须存在的文件。Socket在给定路径上必须存在的 UNIX 套接字。CharDevice在给定路径上必须存在的字符设备。BlockDevice在给定路径上必须存在的块设备。hostPath 的典型应用就是时间同步：通常而言，Node 节点的时间是同步的（云厂商提供的云服务器的时间都是同步的），但是，Pod 中的容器的时间就不一定了，有 UTC 、CST 等；同一个 Pod ，如果部署到中国，就必须设置为 CST 了。示例：vi k8s-hostPath.yamlapiVersion: v1 kind: Pod metadata: name: nginx-host-path namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: # hostPath path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Alwayskubectl apply -f k8s-hostPath.yaml第四章：持久化存储4.1 VOLUME4.1.1 基础Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。临时卷类型的生命周期与 Pod 相同，但持久卷可以比 Pod 的存活期长。当 Pod 不再存在时，Kubernetes 也会销毁临时卷。Kubernetes 不会销毁 持久卷 。对于给定 Pod 中 任何类型的卷 ，在容器重启期间数据都不会丢失。使用卷时, 在 .spec.volumes 字段中设置为 Pod 提供的卷，并在 .spec.containers[*].volumeMounts 字段中声明卷在容器中的挂载位置。4.1.2 subPath有时，在单个 Pod 中共享卷以供多方使用是很有用的。 volumeMounts.subPath 属性可用于指定所引用的卷内的子路径，而不是其根路径。注意：ConfigMap 和 Secret 使用子路径挂载是无法热更新的。4.1.3 安装 NFSNFS 的简介：网络文件系统，英文Network File System(NFS)，是由 SUN 公司研制的UNIX表示层协议(presentation layer protocol)，能使使用者访问网络上别处的文件就像在使用自己的计算机一样。注意：实际开发中，不建议使用 NFS 作为 Kubernetes 集群持久化的驱动。本次以 Master （192.168.65.100）节点作为 NFS 服务端：yum install -y nfs-utils在 Master（192.168.65.100）节点创建 /etc/exports 文件：# * 表示暴露权限给所有主机；* 也可以使用 192.168.0.0/16 代替，表示暴露给所有主机 echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports在 Master（192.168.65.100）节点创建 /nfs/data/ （共享目录）目录，并设置权限：mkdir -pv /nfs/data/chmod 777 -R /nfs/data/在 Master（192.168.65.100）节点启动 NFS ：systemctl enable rpcbindsystemctl enable nfs-serversystemctl start rpcbindsystemctl start nfs-server在 Master（192.168.65.100）节点加载配置：exportfs -r在 Master（192.168.65.100）节点检查配置是否生效：exportfs在 Node（192.168.65.101、192.168.65.102）节点安装 nfs-utils ：# 服务器端防火墙开放111、662、875、892、2049的 tcp / udp 允许，否则远端客户无法连接。 yum install -y nfs-utils在 Node（192.168.65.101、192.168.65.102）节点，执行以下命令检查 nfs 服务器端是否有设置共享目录：# showmount -e $(nfs服务器的IP) showmount -e 192.168.65.100在 Node（192.168.65.101、192.168.65.102）节点，执行以下命令挂载 nfs 服务器上的共享目录到本机路径 /root/nd：mkdir /nd# mount -t nfs $(nfs服务器的IP):/root/nfs_root /root/nfsmount mount -t nfs 192.168.65.100:/nfs/data /nd在 Node （192.168.65.101）节点写入一个测试文件：echo "hello nfs server" > /nd/test.txt在 Master（192.168.65.100）节点验证文件是否写入成功：cat /nfs/data/test.txt示例：vi k8s-nginx-nfs.yamlapiVersion: v1 kind: Pod metadata: name: nginx namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime - name: html mountPath: /usr/share/nginx/html/ # / 一定是文件夹 volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: html nfs: # 使用 nfs 存储驱动 path: /nfs/data # nfs 共享的目录 server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname restartPolicy: Alwayskubectl apply -f k8s-nginx-nfs.yaml4.2 PV 和 PVC4.2.1 概述前面我们已经学习了使用 NFS 提供存储，此时就要求用户会搭建 NFS 系统，并且会在 yaml 配置 NFS，这就带来的一些问题：① 开发人员对 Pod 很熟悉，非常清楚 Pod 中的容器那些位置适合挂载出去。但是，由于 Kubernetes 支持的存储系统非常之多，开发人员并不清楚底层的存储系统，而且要求开发人员全部熟悉，不太可能（术业有专攻，运维人员比较熟悉存储系统）。② 在 yaml 中配置存储系统，就意味着将存储系统的配置信息暴露，非常不安全（容易造成泄露）。为了能够屏蔽底层存储实现的细节，方便用户使用，Kubernetes 引入了 PV 和 PVC 两种资源对象。PV（Persistent Volume）是持久化卷的意思，是对底层的共享存储的一种抽象。一般情况下 PV 由 Kubernetes 管理员进行创建和配置，它和底层具体的共享存储技术有关，并通过插件完成和共享存储的对接。PVC（Persistent Volume Claim）是持久化卷声明的意思，是用户对于存储需求的一种声明。换言之，PVC 其实就是用户向Kubernetes 系统发出的一种资源需求申请。PV 的缺点：① 需要运维事先准备好 PV 池。② 资源浪费：没有办法预估合适的 PV，假设运维向 k8s 申请了 20m 、50m、10G 的 PV，而开发人员申请 2G 的 PVC ，那么就会匹配到 10G 的PV ，这样会造成 8G 的空间浪费。也有人称 PV 为静态供应。4.2.2 PVC 和 PV 的基本演示创建 PV （一般是运维人员操作）：mkdir -pv /nfs/data/10m mkdir -pv /nfs/data/20m mkdir -pv /nfs/data/500m mkdir -pv /nfs/data/1Givi k8s-pv.yamlapiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-10m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 10m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/10m # nfs 共享的目录，mkdir -pv /nfs/data/10m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-20m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 20m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/20m # nfs 共享的目录，mkdir -pv /nfs/data/20m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-500m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 500m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/500m # nfs 共享的目录，mkdir -pv /nfs/data/500m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-1g spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 1Gi accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/1Gi # nfs 共享的目录，mkdir -pv /nfs/data/1Gi server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostnamekubectl apply -f k8s-pv.yaml创建 PVC (一般是开发人员)：vi k8s-pvc.yamlapiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-pvc-500m namespace: default labels: app: nginx-pvc-500m spec: storageClassName: nfs-storage accessModes: - ReadWriteOnce resources: requests: storage: 500m --- apiVersion: v1 kind: Pod metadata: name: nginx namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime - name: html mountPath: /usr/share/nginx/html/ volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: html persistentVolumeClaim: claimName: nginx-pvc-500m readOnly: false restartPolicy: Alwayskubectl apply -f k8s-pvc.yaml注意：pv 和 pvc 的 accessModes 和 storageClassName 必须一致。pvc 申请的空间大小不小于 pv 的空间大小。storageClassName 就相当于分组的组名，通过 storageClassName 可以区分不同类型的存储驱动，主要是为了方便管理。注意：Pod 的删除，并不会影响 PVC；换言之，PVC 可以独立于 Pod 存在，PVC 也是 K8s 的系统资源。不过，推荐将 PVC 和 Pod 也在一个 yaml 文件中。PVC 删除会不会影响到 PV，要根据 PV 的回收策略决定。4.2.3 PV 的回收策略目前的回收策略有：Retain：手动回收（默认）。Recycle：基本擦除 (rm -rf /thevolume/*)。Delete：诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除。目前，仅 NFS 和 HostPath 支持回收（Recycle）。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除（Delete）。示例：演示 Retainmkdir -pv /nfs/data/10m mkdir -pv /nfs/data/20m mkdir -pv /nfs/data/500m mkdir -pv /nfs/data/1Givi k8s-pv.yamlapiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-10m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 10m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/10m # nfs 共享的目录，mkdir -pv /nfs/data/10m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-20m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 20m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/20m # nfs 共享的目录，mkdir -pv /nfs/data/20m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-500m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 500m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/500m # nfs 共享的目录，mkdir -pv /nfs/data/500m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-1g spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 20m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/1Gi # nfs 共享的目录，mkdir -pv /nfs/data/1Gi server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostnamekubectl apply -f k8s-pv.yamlecho 111 > /nfs/data/500m/index.htmlvi k8s-pvc.yamlapiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-pvc-500m namespace: default labels: app: nginx-pvc-500m spec: storageClassName: nfs-storage accessModes: - ReadWriteOnce resources: requests: storage: 500mkubectl apply -f k8s-pvc.yamlkubectl delete -f k8s-pvc.yaml示例：演示 Recyclemkdir -pv /nfs/data/10m mkdir -pv /nfs/data/20m mkdir -pv /nfs/data/500m mkdir -pv /nfs/data/1Givi k8s-pv.yamlapiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-10m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 10m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/10m # nfs 共享的目录，mkdir -pv /nfs/data/10m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname persistentVolumeReclaimPolicy: Recycle # 回收策略 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-20m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 20m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/20m # nfs 共享的目录，mkdir -pv /nfs/data/20m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname persistentVolumeReclaimPolicy: Recycle # 回收策略 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-500m spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 500m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/500m # nfs 共享的目录，mkdir -pv /nfs/data/500m server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname persistentVolumeReclaimPolicy: Recycle # 回收策略 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-1g spec: storageClassName: nfs-storage # 用于分组 capacity: storage: 20m accessModes: - ReadWriteOnce nfs: # 使用 nfs 存储驱动 path: /nfs/data/1Gi # nfs 共享的目录，mkdir -pv /nfs/data/1Gi server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname persistentVolumeReclaimPolicy: Recycle # 回收策略kubectl apply -f k8s-pv.yamlecho 111 > /nfs/data/500m/index.htmlvi k8s-pvc.yamlapiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-pvc-500m namespace: default labels: app: nginx-pvc-500m spec: storageClassName: nfs-storage accessModes: - ReadWriteOnce resources: requests: storage: 500mkubectl apply -f k8s-pvc.yamlkubectl delete -f k8s-pvc.yaml4.2.4 PV 的生命周期一个 PV 的生命周期，可能会处于 4 种不同的阶段：Available（可用）：表示可用状态，还未被任何 PVC 绑定。Bound（已绑定）：表示 PV 已经被 PVC 绑定。Released（已释放）：表示 PVC 被删除，但是资源还没有被集群重新释放。Failed（失败）：表示该 PV 的自动回收失败。4.2.5 PV 的访问模式访问模式（accessModes）：用来描述用户应用对存储资源的访问权限，访问权限包括下面几种方式：ReadWriteOnce（RWO）：读写权限，但是只能被单个节点挂载。ReadOnlyMany（ROX）：只读权限，可以被多个节点挂载。ReadWriteMany（RWX）：读写权限，可以被多个节点挂载。4.2.6 生命周期PVC 和 PV 是一一对应的，PV 和 PVC 之间的相互作用遵循如下的生命周期：① 资源供应：管理员手动创建底层存储和 PV。② 资源绑定：用户创建 PVC ，Kubernetes 负责根据 PVC 声明去寻找 PV ，并绑定在用户定义好 PVC 之后，系统将根据 PVC 对存储资源的请求在以存在的 PV 中选择一个满足条件的。一旦找到，就将该 PV 和用户定义的 PVC 进行绑定，用户的应用就可以使用这个 PVC 了。如果找不到，PVC 就会无限期的处于 Pending 状态，直到系统管理员创建一个符合其要求的 PV 。PV 一旦绑定到某个 PVC 上，就会被这个 PVC 独占，不能再和其他的 PVC 进行绑定了。③ 资源使用：用户可以在 Pod 中像 Volume 一样使用 PVC ，Pod 使用 Volume 的定义，将 PVC 挂载到容器内的某个路径进行使用。④ 资源释放：用户删除 PVC 来释放 PV 。当存储资源使用完毕后，用户可以删除 PVC，和该 PVC 绑定的 PV 将会标记为 已释放 ，但是还不能立刻和其他的 PVC 进行绑定。通过之前 PVC 写入的数据可能还留在存储设备上，只有在清除之后该 PV 才能再次使用。⑤ 资源回收：Kubernetes 根据 PV 设置的回收策略进行资源的回收。对于 PV，管理员可以设定回收策略，用于设置与之绑定的 PVC 释放资源之后如何处理遗留数据的问题。只有 PV 的存储空间完成回收，才能供新的 PVC 绑定和使用。4.3 动态供应4.3.1 概述静态供应：集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息，并且对集群用户可用（可见）。PV 卷对象存在于 Kubernetes API 中，可供用户消费（使用）。动态供应：集群自动根据 PVC 创建出对应 PV 进行使用。4.3.2 动态供应的完整流程① 集群管理员预先创建存储类（StorageClass）。② 用户创建使用存储类的持久化存储声明(PVC：PersistentVolumeClaim)。③ 存储持久化声明通知系统，它需要一个持久化存储(PV: PersistentVolume)。④ 系统读取存储类的信息。⑤ 系统基于存储类的信息，在后台自动创建 PVC 需要的 PV 。⑥ 用户创建一个使用 PVC 的 Pod 。⑦ Pod 中的应用通过 PVC 进行数据的持久化。⑧ PVC 使用 PV 进行数据的最终持久化处理。4.3.3 设置 NFS 动态供应注意：不一定需要设置 NFS 动态供应，可以直接使用云厂商提供的 StorageClass 。官网地址。部署 NFS 动态供应：vi k8s-nfs-provisioner.yamlapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 指定一个供应商的名字 # or choose another name, 必须匹配 deployment 的 env PROVISIONER_NAME' parameters: archiveOnDelete: "false" # 删除 PV 的时候，PV 中的内容是否备份 --- apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner namespace: default spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: nfs-client-provisioner template: metadata: labels: app: nfs-client-provisioner spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: ccr.ccs.tencentyun.com/gcr-containers/nfs-subdir-external-provisioner:v4.0.2 volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: k8s-sigs.io/nfs-subdir-external-provisioner - name: NFS_SERVER value: 192.168.65.100 # NFS 服务器的地址 - name: NFS_PATH value: /nfs/data # NFS 服务器的共享目录 volumes: - name: nfs-client-root nfs: server: 192.168.65.100 path: /nfs/data --- apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-provisioner-runner rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get", "list", "watch"] - 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: default 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: default 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: default subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.iokubectl apply -f k8s-nfs-provisioner.yaml示例：测试动态供应vi k8s-pvc.yamlapiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-pvc namespace: default labels: app: nginx-pvc spec: storageClassName: nfs-client # 注意此处 accessModes: - ReadWriteOnce resources: requests: storage: 2Gi --- apiVersion: v1 kind: Pod metadata: name: nginx namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime - name: html mountPath: /usr/share/nginx/html/ volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: html persistentVolumeClaim: claimName: nginx-pvc readOnly: false restartPolicy: Alwayskubectl apply -f k8s-pvc.yaml4.3.4 设置 SC 为默认驱动命令：kubectl patch storageclass <your-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'设置 SC 为默认驱动：kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'示例：测试默认驱动apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-pvc namespace: default labels: app: nginx-pvc spec: # storageClassName: nfs-client 不写，就使用默认的 accessModes: - ReadWriteOnce resources: requests: storage: 2Gi --- apiVersion: v1 kind: Pod metadata: name: nginx namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime - name: html mountPath: /usr/share/nginx/html/ volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: html persistentVolumeClaim: claimName: nginx-pvc readOnly: false restartPolicy: Always4.3.5 展望目前，只需要运维人员部署好各种 storageclass，开发人员在使用的时候，创建 PVC 即可；但是，存储系统太多太多，运维人员也未必会一一掌握，此时就需要 Rook 来统一管理了。
第一章：工作负载工作负载是运行的 Kubernetes 上的一个应用程序。一个应用很复杂，可能由单个组件或者多个组件共同完成。我们可以用一组 Pod 来描述一个应用，也就是一个工作负载，而 Pod 是一组容器。换言之，工作负载控制一组 Pod ，Pod 控制一组容器（如：Deployment【工作负载】部署 3 个副本的 nginx-pod ，每个 nginx-pod 里面是真正的 nginx 容器）。工作负载能让 Pod 拥有自愈能力。我们主要研究不同的工作负载如何控制 Pod 的行为。第二章：Pod2.1 什么是 Pod ？Pod 是一组（一个多多个）容器的集合（Pod 就像是豌豆荚，而容器就像是豌豆荚中的豌豆）。这些容器共享存储、网络等。实际开发中，我们一般不直接创建 Pod ，而是创建一个工作负载（如：Deployment等），让它们来创建 Pod 。Pod 的特点：① Pod 对容器有自愈能力（自我恢复能力），Pod 可以自动重启失败的容器。② Pod 自己不能恢复自己，Pod 被删除了就没有了，但是有时我们希望如果 Node 节点宕机了，这些 Pod 还可以在其他节点重新启动。③ Pod 中可以有一个容器，也可以有多个容器。④ 如果 Pod 中有多个容器协同工作（通常而言，都是有关系了），我们将提供服务的称为主容器，另外的容器称为 SideCar 。⑤ Pod 天生为其成员容器提供了两种共享资源：网络 和 存储。一个 Pod 由一个 Pause 容器设置整个 Pod 里面的所有容器的网络、名称空间等信息。2.2 Pod 的使用可以使用 Deployment 等各种工作负载的 yaml 文件创建 Pod ，也可以直接创建 Pod 。Pod 的模板如下：apiVersion: v1 kind: Pod metadata: name: "nginx-pod" # pod 的名称 namespace: default labels: app: "nginx" # 标签 spec: containers: - name: nginx # 容器的名称 image: "nginx:1.20.2" # 镜像的名称 resources: # 资源限制 limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi env: # 环境变量 - name: hello value: world ports: # 端口 - containerPort: 80 # 容器的端口 name: nginx # 容器端口的名称 volumeMounts: # 卷挂载 - name: localtime mountPath: /etc/localtime volumes: # 卷声明，下面的这种方式相当于 Docker 的具名卷 - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Always示例：多容器协同vim k8s-multi-pod.yamlapiVersion: v1 kind: Pod metadata: name: multi-pod labels: app: multi-alpine-pod spec: containers: - name: nginx image: nginx:1.20.2 volumeMounts: # 声明卷挂载 - name: nginx-volume mountPath: /usr/share/nginx/html - name: alpine image: alpine command: - /bin/sh - -c - while true; do sleep 1 ;echo $(date "+%Y-%m-%d-%H:%M:%S") > /app/index.html ;done; volumeMounts: # 声明卷挂载 - name: nginx-volume mountPath: /app volumes: - name: nginx-volume emptyDir: {} # 相当于 Docker 中的匿名挂载，会在外部创建一个位置 restartPolicy: Alwayskubectl apply -f k8s-multi-pod.yaml温馨提示：如果想进多容器协同的 Pod 中查看指定容器，使用这样的命令：kubectl exec -it Pod的名称 -c Pod中的容器名 -- COMMAND。2.3 Pod 的初始化容器初始化容器是在 Pod 的主容器启动之前要运行的容器，主要是做一些主容器的前置工作，它具有两大特征：① 初始化容器 必须运行完成直至结束 ，如果某个初始化容器运行失败，那么 Kubernetes 需要重启它直至成功完成。② 初始化容器必须按照定义的顺序执行，当且仅当前一个成功之后，后面的一个才能运行。初始化容器有很多的应用场景，下面列出的是最常见的几个：提供主容器镜像中不具备的工具程序或自定义代码。初始化容器要先于应用容器串行启动并运行完成，因此可用于延后应用容器的启动直至其依赖的条件得到满足。示例：vim k8s-pod-lifecycle.yamlapiVersion: v1 kind: Pod metadata: name: pod-lifecycle labels: app: pod-lifecycle spec: volumes: - name: context-vol emptyDir: {} initContainers: # Pod 在启动 containers 之前，先要运行 initContainers 中的所有容器 - name: nginx-init image: alpine # 必须有终结的那个时刻，一般不要用一直启动的镜像 command: ["/bin/sh","-c","echo 2222 > /app/index.html;sleep 15;"] volumeMounts: - mountPath: /app name: context-vol securityContext: privileged: true # 使用特权模式运行容器，需要慎用！！！ containers: - name: nginx image: nginx:1.20.2 imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /usr/share/nginx/html name: context-vol restartPolicy: Alwayskubectl apply -f k8s-pod-lifecycle.yaml2.4 Pod 的临时容器（了解）2.4.1 概述临时容器是一种特殊的容器，该容器可以在现有的 Pod 中临时运行，以便完成我们发起的操作，比如故障排查。我们应该使用临时容器来检查服务，而不是用临时容器来构建应用程序。Pod 是 Kubernetes 集群进行管理的最小单元，由于 Pod 是一次性且可以替换的，因此 Pod 一旦被创建，就无法将容器加入到Pod 中。而且，我们通常使用 Deployment 来删除并替换Pod。但是，有的时候我们需要检查现有 Pod 的状态，比如对难以复现的故障进行排查。在这些场景中，可以在现有 Pod 中运行临时容器来检查其状态并运行任意命令。临时容器在目前的 Kubernetes 版本是 beta 。2.4.2 什么是临时容器？临时容器和其他容器的不同之处在于，它们缺少对资源或执行的保证，并且永远不会自动重启，因此不适合用来构建应用程序。临时容器使用和常规容器相同的 ContainerSpec 来描述，但是许多字段是不兼容或者不允许的。临时容器没有端口配置，因此像 ports 、livenessProbe、readinessProbe这样的字段是没有的。Pod 的资源分配是不可变的，因此 resources 这样的配置临时容器也是没有的。……临时容器是使用 ephemeralcontainers 来进行创建的，而不是直接添加到 pod.spec 中，所以是无法使用 kubectl edit 来添加一个临时容器。和常规容器一样，将临时容器添加到Pod后，不能更改或删除临时容器。2.4.3 临时容器的用途当由于容器奔溃或容器镜像不包含调试工具而导致 kubectl exec 无用的时候，临时容器对于交互式故障排查非常有用。比如，像 distroless 镜像 允许用户部署最小的容器镜像，从而减少攻击面并减少故障和漏洞的暴露。由于 distroless 镜像 不包含 Shell 或任何的调试工具，因此很难单独使用 kubectl exec 命令进行故障排查。使用临时容器的时候，启用 进程名字空间共享 很有帮助，可以查看其他容器中的进程。2.4.4 开启临时容器查询临时容器是否开启：kubelet -h | grep EphemeralContainers在 Master 节点（192.168.65.100）和 Node 节点（192.168.65.101、192.168.65.102）修改 kubectl 的参数：vi /etc/sysconfig/kubelet# 修改增加--feature-gates EphemeralContainers=true KUBELET_EXTRA_ARGS="--cgroup-driver=systemd --feature-gates EphemeralContainers=true" KUBE_PROXY_MODE="ipvs"vi /var/lib/kubelet/config.yamlapiVersion: kubelet.config.k8s.io/v1beta1 authentication: apiVersion: kubelet.config.k8s.io/v1beta1 authentication: anonymous: enabled: false webhook: cacheTTL: 0s enabled: true x509: clientCAFile: /etc/kubernetes/pki/ca.crt authorization: mode: Webhook webhook: cacheAuthorizedTTL: 0s cacheUnauthorizedTTL: 0s cgroupDriver: systemd clusterDNS: - 10.96.0.10 clusterDomain: cluster.local cpuManagerReconcilePeriod: 0s evictionPressureTransitionPeriod: 0s fileCheckFrequency: 0s healthzBindAddress: 127.0.0.1 healthzPort: 10248 httpCheckFrequency: 0s imageMinimumGCAge: 0s kind: KubeletConfiguration logging: {} nodeStatusReportFrequency: 0s nodeStatusUpdateFrequency: 0s rotateCertificates: true runtimeRequestTimeout: 0s shutdownGracePeriod: 0s shutdownGracePeriodCriticalPods: 0s staticPodPath: /etc/kubernetes/manifests streamingConnectionIdleTimeout: 0s # 修改部分 featureGates: EphemeralContainers: true加载配置文件以便重启 kubelet ：systemctl daemon-reloadsystemctl stop kubeletsystemctl start kubelet在 Master 节点（192.168.65.100）修改 kube-apiserver.yaml 和 kube-scheduler.yaml ：vi /etc/kubernetes/manifests/kube-apiserver.yamlapiVersion: v1 kind: Pod metadata: annotations: kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.65.100:6443 creationTimestamp: null labels: component: kube-apiserver tier: control-plane name: kube-apiserver namespace: kube-system spec: containers: - command: - kube-apiserver - --advertise-address=192.168.65.100 - --allow-privileged=true - --authorization-mode=Node,RBAC - --client-ca-file=/etc/kubernetes/pki/ca.crt - --enable-admission-plugins=NodeRestriction - --enable-bootstrap-token-auth=true - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key - --etcd-servers=https://127.0.0.1:2379 - --insecure-port=0 - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key - --requestheader-allowed-names=front-proxy-client - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt - --requestheader-extra-headers-prefix=X-Remote-Extra- - --requestheader-group-headers=X-Remote-Group - --requestheader-username-headers=X-Remote-User - --secure-port=6443 - --service-account-issuer=https://kubernetes.default.svc.cluster.local - --service-account-key-file=/etc/kubernetes/pki/sa.pub - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key - --service-cluster-ip-range=10.96.0.0/16 - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key # 修改部分 - --feature-gates=EphemeralContainers=truevi /etc/kubernetes/manifests/kube-scheduler.yamlapiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: component: kube-scheduler tier: control-plane name: kube-scheduler namespace: kube-system spec: containers: - command: - kube-scheduler - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf - --bind-address=127.0.0.1 - --kubeconfig=/etc/kubernetes/scheduler.conf - --leader-elect=true - --port=0 # 修改部分 - --feature-gates=EphemeralContainers=true2.4.5 使用临时容器在线 debug① 创建一个 Pod ：vim k8s-pod.yamlapiVersion: v1 kind: Pod metadata: name: nginx spec: shareProcessNamespace: true # 这个配置非常重要，一定要配置 containers: - name: nginx image: nginx:1.17.1kubectl apply -f k8s-pod.yaml② 创建 ec.json 文件，内容如下：{ "apiVersion": "v1", "kind": "EphemeralContainers", "metadata": { "name": "nginx" // 指定 Pod 的名称 }, "ephemeralContainers": [{ "command": [ "sh" ], "image": "busybox", // 如果是 JRE ，当然使用 JDK 作为临时容器了 "imagePullPolicy": "IfNotPresent", "name": "debugger", "stdin": true, "tty": true, "terminationMessagePolicy": "File" }] }注意：json 文件目前是没有注释的。③ 应用临时容器：# /api/v1/namespaces/default/pods/[pod的名称]/ephemeralcontainers kubectl replace --raw /api/v1/namespaces/default/pods/nginx/ephemeralcontainers -f ec.json2.5 重启策略在容器探测中，一旦容器探测出现了问题，Kubernetes 就会对容器所在的 Pod 进行重启，其实这是由 Pod 的重启策略决定的，Pod 的重启策略有 3 种，分别如下：Always：容器失效时，自动重启该容器（默认值）。OnFailure：容器终止运行且退出码不为 0 时重启。Never：不论状态如何，都不重启该容器。重启策略适用于 Pod 对象中的所有容器，首次需要重启的容器，将在其需要的时候立即进行重启，随后再次重启的操作将由 kubelet 延迟一段时间后进行，且反复的重启操作的延迟时长以此为 10s 、20s 、40s 、80s 、160s 和 300s ，300s 是最大的延迟时长。示例：apiVersion: v1 kind: Pod metadata: name: pod-restart-policy namespace: dev labels: user: xudaxian spec: containers: # 容器配置 - name: nginx image: nginx:1.17.1 imagePullPolicy: IfNotPresent ports: - containerPort: 80 restartPolicy: Always # 重启策略2.6 钩子函数钩子函数能够感知自身生命周期中的事件，并在相应的时刻到来时运行用户指定的程序代码。Kubernetes 在主容器启动之后和停止之前提供了两个钩子函数：postStart：容器创建之后执行，如果失败会重启容器。preStop：容器终止之前执行，执行完成之后容器将成功终止，在其完成之前会阻塞删除容器的操作。钩子处理器支持使用下面的三种方式定义动作：exec 命令：在容器内执行一次命令。…… lifecycle: postStart: exec: command: - cat - /tmp/healthy ……tcpSocket：在当前容器尝试访问指定的 socket 。…… lifecycle: postStart: tcpSocket: port: 8080 ……httpGet：在当前容器中向某 url 发起 HTTP 请求（应用程序使用此种方式最多）。…… lifecycle: postStart: httpGet: path: / #URI地址 port: 80 #端口号 host: 192.168.109.100 #主机地址 scheme: HTTP #支持的协议，http或者https ……示例：apiVersion: v1 kind: Pod metadata: name: pod-hook-exec namespace: default labels: user: xudaxian spec: containers: # 容器配置 - name: nginx image: nginx:1.17.1 imagePullPolicy: IfNotPresent ports: - name: nginx-port containerPort: 80 protocol: TCP resources: limits: cpu: "2" memory: "10Gi" requests: cpu: "1" memory: "10Mi" lifecycle: # 生命周期配置 postStart: # 容器创建之后执行，如果失败会重启容器 exec: # 在容器启动的时候，执行一条命令，修改掉Nginx的首页内容 command: ["/bin/sh","-c","echo postStart ... > /usr/share/nginx/html/index.html"] preStop: # 容器终止之前执行，执行完成之后容器将成功终止，在其完成之前会阻塞删除容器的操作 exec: # 在容器停止之前停止Nginx的服务 command: ["/usr/sbin/nginx","-s","quit"]2.7 Probe 探针机制（健康检查机制）2.7.1 概述probe 是由 kubelet 对容器执行的定期诊断。 要执行诊断，kubelet 既可以在容器内执行代码，也可以发出一个网络请求。针对运行中的容器，kubelet 可以选择是否执行以下三种探针，以及如何针对探测结果作出反应：startupProbe（启动探针）：kubelet 使用启动探针，来检测应用是否已经启动。如果启动就可以进行后续的探测检查。慢容器一定指定启动探针。一直在等待启动。启动探针一旦成功就不在执行（只会成功执行一次），存活探针和就绪探针将接着持续运行。livenessProbe（存活探针）：kubelet 使用存活探针，来检测容器是否正常存活。（有些容器可能产生死锁【应用程序在运行，但是无法继续执行后面的步骤】），如果检测失败就会重新启动这个容器。readinessProbe（就绪探针）：kubelet 使用就绪探针，来检测容器是否准备好了可以接收流量。当一个 Pod 内的所有容器都准备好了，才能把这个 Pod 看作就绪了。用途就是：Service后端负载均衡多个Pod，如果某个Pod还没就绪，就会从service负载均衡里面剔除。Probe 的配置项：initialDelaySeconds：容器启动后要等待多少秒后存活和就绪探测器才被初始化，默认是 0 秒，最小值是 0。periodSeconds：执行探测的时间间隔（单位是秒），默认是 10 秒，最小值是 1。successThreshold：探测器在失败后，被视为成功的最小连续成功数，默认值是 1。存活和启动探针的这个值必须是 1。failureThreshold：当探测失败时，Kubernetes 的重试次数，存活探测情况下的放弃就意味着重新启动容器， 就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。timeoutSeconds：探测的超时后等待多少秒，默认值是 1 秒，最小值是 1。Probe 的探测方式：exec 、httpGet（应用程序使用此种方式最多）、tcpSocket。2.7.2 应用示例示例：apiVersion: v1 kind: Pod metadata: name: nginx-prode labels: app: nginx-prode spec: containers: - name: nginx image: nginx:1.20.2 imagePullPolicy: IfNotPresent startupProbe: # 启动探针 exec: command: # 返回不是 0 ，就是探测失败 - /bin/sh - -c - sleep 30;abc initialDelaySeconds: 1 # 探测延迟，RUNNING 后指定 20 秒后才执行探测，默认为 0 秒。 periodSeconds: 5 # 执行探测的时间间隔（单位是秒），默认为 10 秒。 successThreshold: 1 # 成功阈值：连续几次成功才算成功。探测器在失败后，被视为成功的最小连续成功数。默认值是 1 ，存活和启动探针的这个值必须是 1。最小值是 1。 failureThreshold: 3 # 失败阈值：连续几次失败才算失败。当探测失败时，Kubernetes 的重试次数。 存活探测情况下的放弃就意味着重新启动容器。 就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。 timeoutSeconds: 5 # 探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。2.7.3 微服务SpringBoot（2.3+） 已经支持了 Kubernetes 的探针机制，只需要添加 spring-boot-starter-actuator 依赖，并在 application.yml 中配置如下内容：server: # 服务器配置 shutdown: graceful # 开启优雅停机 spring: lifecycle: timeout-per-shutdown-phase: 30s #设置缓冲时间 默认30s management: endpoint: health: probes: enabled: true show-details: always endpoints: web: exposure: include: '"*"' health: livenessState: enabled: true readinessState: enabled: true存活探针的路径是：/actuator/health/liveness 。就绪探针的路径是：/actuator/health/readiness 。apiVersion: v1 kind: Pod metadata: name: nginx-prode labels: app: nginx-prode spec: terminationGracePeriodSeconds: 30 # 优雅停机时间 containers: - name: nginx image: nginx:1.20.2 imagePullPolicy: IfNotPresent livenessProbe: # 就绪探针 httpGet: path: /actuator/health/liveness port: 8080 scheme: HTTP readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 scheme: HTTP第三章：ReplicaSet（rs）3.1 概述ReplicaSet 的主要作用是保证一定数量的 Pod 能够正常运行，它会持续监听这些 Pod 的运行状态，一旦 Pod 发生故障，就会重启或重建。同时它还支持对 Pod 数量的扩缩容。注意：ReplicaSet 没有镜像升级的功能，如果要模拟这一功能，需要手动将 rs 删除，然后修改 yaml 文件，再创建新的 Pod 。ReplicaSet 的资源清单文件：apiVersion: apps/v1 # 版本号 kind: ReplicaSet # 类型 metadata: # 元数据 name: # rs名称 namespace: # 所属命名空间 labels: #标签 app: rs spec: # 规格，期望的状态 replicas: 3 # 副本数量 selector: # 选择器，通过它指定该控制器管理哪些pod matchLabels 和 matchExpressions 二者选其一 matchLabels: # Labels匹配规则 app: nginx-pod matchExpressions: # Expressions匹配规则 - {key: app, operator: In, values: [nginx-pod]} template: # 模板，当副本数量不足时，会根据下面的模板创建pod副本 metadata: labels: app: nginx-pod spec: containers: - name: nginx image: nginx:1.17.1 ports: - containerPort: 803.2 创建 ReplicaSet创建 ReplicaSet ：vi k8s-rs.yamlapiVersion: apps/v1 # 版本 kind: ReplicaSet # 类型 metadata: # 元数据 name: nginx-rs # 名称 labels: # 标签 app: nginx spec: # 规格，期望的状态 replicas: 3 # 副本数量 selector: # selector 选择器，通常而言，和 template 模板中的 labels 保持一致 matchLabels: app: nginx template: # Pod 模板，原来怎么编写 Pod ，这里也怎么写。 metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.17.1kubectl apply -f k8s-rs.yaml3.3 扩缩容方式一：使用 kubectl edit 命令，修改 spec:replicas:n 即可：kubectl edit rs nginx-rs② 方式二：使用 scale 命令使用扩缩容，后面加上 --replicas=n 直接指定目标数量即可：kubectl scale rs nginx-rs --replicas=2③ 修改 yaml 文件中 spec.replicas 字段，随后使用 kubectl apply -f xxx.yaml 命令即可：vim k8s-rs.yaml... spec: # 规格，期望的状态 replicas: 5 # 副本数量 ...kubectl apply -f k8s-rs.yaml3.4 模拟镜像升级先删除 rs ，更新 rs 中 Pod 的模板，再重新创建 rs：kubectl delete -f k8s-rs.yamlvim k8s-rs.yaml... spec: # 规格，期望的状态 replicas: 3 # 副本数量 selector: # selector 选择器，通常而言，和 template 模板中的 labels 保持一致 matchLabels: app: nginx template: # Pod 模板，原来怎么编写 Pod ，这里也怎么写。 metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 ...kubectl apply -f k8s-rs.yaml注意：此种方式在实际开发中并不可取，它的原理是先删除所有的 Pod ，然后创建新的 Pod ，如果我在 k8s-rs.yaml 文件中的 Pod 的镜像写错，那么之前的 Pod 已经没了，不满足滚动更新的原则（新建一批 Pod ，删除一批 Pod ，直到新建的所有 Pod 替换原有的所有 Pod ，类似于人民战争时期我党采取的策略：根据不同的情况，争取一批、打倒一批），所以推荐使用 Deployment 代替 ReplicaSet 。3.5 删除 ReplicaSet方式一：通过 kubectl delete 命令：# --cascade=false 删除 rs 的同时保留 Pod kubectl delete rs rs的名称 [-n 命名空间] [--cascade=false]kubectl delete rs nginx-rs方式二：通过 yaml 文件：kubectl delete -f k8s-rs.yaml第四章：Deployment（deploy）4.1 概述为了更好的解决服务编排的问题，Kubernetes 在v1.2版本开始，引入了 Deployment 控制器。值得一提的是，Deployment 控制器并不直接管理 Pod，而是通过管理 ReplicaSet 来间接管理 Pod ，即：Deployment 管理 ReplicaSet，ReplicaSet 管理 Pod 。所以 Deployment 的功能比 ReplicaSet 强大。Deployment 的资源清单：apiVersion: apps/v1 # 版本号 kind: Deployment # 类型 metadata: # 元数据 name: # rs名称 namespace: # 所属命名空间 labels: #标签 controller: deploy spec: # 详情描述 replicas: 3 # 副本数量 revisionHistoryLimit: 3 # 保留历史版本，默认为10 paused: false # 暂停部署，默认是false progressDeadlineSeconds: 600 # 部署超时时间（s），默认是600 strategy: # 策略 type: RollingUpdate # 滚动更新策略 rollingUpdate: # 滚动更新 maxSurge: 30% # 最大额外可以存在的副本数，可以为百分比，也可以为整数 maxUnavailable: 30% # 最大不可用状态的 Pod 的最大值，可以为百分比，也可以为整数 selector: # 选择器，通过它指定该控制器管理哪些pod matchLabels: # Labels匹配规则 app: nginx-pod matchExpressions: # Expressions匹配规则 - {key: app, operator: In, values: [nginx-pod]} template: # 模板，当副本数量不足时，会根据下面的模板创建pod副本 metadata: labels: app: nginx-pod spec: containers: - name: nginx image: nginx:1.17.1 ports: - containerPort: 804.2 Deployment 的更新原理仅当 Deployment 的 Pod 模板（spec.template）发生改变的时候（如：模板的标签或容器镜像被更新），才会触发 Deployment 上线，其他更新（如：Deployment 的扩缩容等操作）均不会触发上线动作。上线动作：创建新的 ReplicaSet，准备就绪后，替换旧的 ReplicaSet（此时不会删除，因为 revisionHistoryLimit 指定了保留几个版本）。4.3 创建Deployment创建 Deployment ：vi k8s-deploy.yamlapiVersion: apps/v1 # 必须字段 kind: Deployment # 必须字段 metadata: # 必须字段 name: nginx-deployment # 部署的 deployment 的名称 namespace: default # 名称空间 labels: # 标签 app: nginx-deployment spec: # 规格，期望状态，必须字段 selector: # 选择器，用来帮助 Deployment 来筛选 Pod，必须字段 matchLabels: # 匹配标签 app: nginx # 通常而言，和 template 中的 metadata.labels 保持一致 template: # Pod 的模板，必须字段 metadata: # Pod 的 metadata（元数据） name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx:1.17.1 imagePullPolicy: IfNotPresent restartPolicy: Alwayskubectl apply -f k8s-deploy.yamlwatch -n 1 kubectl get deploy,rs,pod4.4 扩缩容方式一：使用 Kubectl edit 命令,修改 spec:replicas:n 即可：kubectl edit deployment nginx-deployment方式二：使用 scale 命令实现扩缩容：kubectl scale deploy nginx-deployment --replicas=3方式三：修改 yaml 文件中 spec.replicas 字段，随后使用 kubectl apply -f xxx.yaml 命令即可：vim k8s-deploy.yaml... spec: # 规格，期望状态，必须字段 replicas: 2 # 副本数 ...kubectl apply -f k8s-deploy.yaml4.5 镜像升级4.5.1 概述Deployment 支持两种镜像更新策略：重建更新和滚动更新，可以通过 strategy 字段进行配置：strategy: # 指定新的Pod替代旧的Pod的策略，支持两个属性 type: # 指定策略类型，支持两种策略 Recreate：在创建出新的Pod之前会先杀掉所有已经存在的Pod RollingUpdate：滚动更新，就是杀死一部分，就启动一部分，在更新过程中，存在两个版本的Pod rollingUpdate：# 当type为RollingUpdate的时候生效，用于为rollingUpdate设置参数，支持两个属性： maxUnavailable：# 用来指定在升级过程中不可用的Pod的最大数量，默认为25%。 maxSurge： # 用来指定在升级过程中可以超过期望的Pod的最大数量，默认为25%。Deployment 的默认的镜像更新策略是 RollingUpdate（滚动更新），实际开发的时候，使用默认镜像更新策略即可。4.5.2 重建更新设置镜像更新策略为重建更新：vi k8s-deploy.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx-deployment spec: replicas: 3 selector: matchLabels: app: nginx revisionHistoryLimit: 15 # 旧副本集保留的数量，即可回滚的数量。默认为 10 。 progressDeadlineSeconds: 600 # 处理的最终期限，如果超过了这个指定的时间就会给集群汇报错误。默认为 600s 。 paused: false # 暂停更新，默认为 false 。 strategy: # 更新策略 type: Recreate # Recreate：在创建出新的Pod之前会先杀掉所有已经存在的Pod template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx:1.17.1 imagePullPolicy: IfNotPresent restartPolicy: Alwayskubectl apply -f k8s-deploy.yaml方式一：使用 kubectl set image 命令：kubectl set image deployment nginx-deployment nginx=nginx:1.20.2方式二：修改 yaml 文件，使用 kubectl apply -f xxx.yaml 命令即可：vi k8s-deploy.yaml... spec: replicas: 3 selector: matchLabels: app: nginx revisionHistoryLimit: 15 # 旧副本集保留的数量，即可回滚的数量。默认为 10 。 progressDeadlineSeconds: 600 # 处理的最终期限，如果超过了这个指定的时间就会给集群汇报错误。默认为 600s 。 paused: false # 暂停更新，默认为 false 。 strategy: # 更新策略 type: Recreate # Recreate：在创建出新的Pod之前会先杀掉所有已经存在的Pod template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 # 镜像更新 imagePullPolicy: IfNotPresent restartPolicy: Always ...kubectl apply -f k8s-deploy.yaml4.5.3 滚动更新设置镜像更新策略为滚动更新：vi k8s-deploy.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx-deployment spec: replicas: 6 selector: matchLabels: app: nginx revisionHistoryLimit: 15 # 旧副本集保留的数量，即可回滚的数量。默认为 10 。 progressDeadlineSeconds: 600 # 处理的最终期限，如果超过了这个指定的时间就会给集群汇报错误。默认为 600s 。 paused: false # 暂停更新，默认为 false 。 strategy: # 更新策略 type: RollingUpdate # 滚动更新 rollingUpdate: maxSurge: 25% # 最大增量：一次最多新建几个 Pod，可以写数字或百分比，maxUnavailable 为 0 的时候，maxSurge 不能为 0 。 maxUnavailable: 25% # 最大不可用量：最大不可用的 Pod，可以写数字或百分比 template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx:1.17.1 imagePullPolicy: IfNotPresent restartPolicy: Alwayskubectl apply -f k8s-deploy.yaml方式一：使用 kubectl set image 命令：kubectl set image deployment nginx-deployment nginx=nginx:1.20.2方式二：修改 yaml 文件，使用 kubectl apply -f xxx.yaml 命令即可：vim k8s-deploy.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx-deployment spec: replicas: 6 selector: matchLabels: app: nginx revisionHistoryLimit: 15 # 旧副本集保留的数量，即可回滚的数量。默认为 10 。 progressDeadlineSeconds: 600 # 处理的最终期限，如果超过了这个指定的时间就会给集群汇报错误。默认为 600s 。 paused: false # 暂停更新，默认为 false 。 strategy: # 更新策略 type: RollingUpdate # 滚动更新 rollingUpdate: maxSurge: 25% # 最大增量：一次最多新建几个 Pod，可以写数字或百分比，maxUnavailable 为 0 的时候，maxSurge 不能为 0 。 maxUnavailable: 25% # 最大不可用量：最大不可用的 Pod，可以写数字或百分比 template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 # 镜像升级 imagePullPolicy: IfNotPresent restartPolicy: Alwayskubectl apply -f k8s-deploy.yaml4.6 版本回退Deployment 支持版本升级过程中的暂停、继续功能以及版本回退等诸多功能：kubetl rollout 参数 deploy xx # 支持下面的选择 # status 显示当前升级的状态 # history 显示升级历史记录 # pause 暂停版本升级过程 # resume 继续已经暂停的版本升级过程 # restart 重启版本升级过程 # undo 回滚到上一级版本 （可以使用--to-revision回滚到指定的版本）创建 Deployment ：vi k8s-deploy.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx-deployment spec: replicas: 6 selector: matchLabels: app: nginx revisionHistoryLimit: 15 # 旧副本集保留的数量，即可回滚的数量。默认为 10 。 progressDeadlineSeconds: 600 # 处理的最终期限，如果超过了这个指定的时间就会给集群汇报错误。默认为 600s 。 template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx:1.17.1 imagePullPolicy: IfNotPresent restartPolicy: Alwayskubectl apply -f k8s-deploy.yaml --record查看版本升级历史记录：kubectl rollout history deployment nginx-deployment镜像升级：kubectl set image deployment nginx-deployment nginx=nginx:1.17.2 --recordkubectl set image deployment nginx-deployment nginx=nginx:1.20.2 --record查看版本升级历史记录：kubectl rollout history deployment nginx-deployment版本回退：# 可以使用-to-revision=1回退到1版本，如果省略这个选项，就是回退到上个版本，即2版本 kubectl rollout undo deployment nginx-deployment --to-revision=1kubectl rollout undo deployment nginx-deploymentDeployment 之所以能够实现版本的回退，就是通过记录下历史的 ReplicaSet 来实现的，一旦想回滚到那个版本，只需要将当前版本的 Pod 数量降为 0 ，然后将回退版本的 Pod 提升为目标数量即可。4.7 金丝雀发布4.7.1 概述滚动更新也是有缺点的：滚动更新短时间就直接结束了，不能直接控制新老版本的存活时间；而金丝雀发布却可以实现这样的功能。金丝雀的发布流程如下：① 将 service 的标签设置为 app=nginx ，这就意味着集群中的所有标签是 app=nginx 的 Pod 都会加入负载均衡网络。② 使用 Deployment v=v1 app=nginx 去筛选标签是 app=nginx 以及 v=v1 的 所有 Pod。③ 同理，使用 Deployment v=v2 app=nginx 去筛选标签是 app=nginx 以及 v=v2 的所有 Pod 。④ 逐渐加大 Deployment v=v2 app=nginx 控制的 Pod 的数量，根据轮询负载均衡网络的特点，必定会使得此 Deployment 控制的 Pod 的流量增大。⑤ 当测试完成后，删除 Deployment v=v1 app=nginx 即可。4.7.2 金丝雀发布准备 v1 版本的 Deployment：vi k8s-v1-deploy.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy-v1 labels: app: nginx-deploy-v1 spec: replicas: 3 selector: matchLabels: app: nginx v: v1 template: metadata: name: nginx labels: app: nginx v: v1 spec: containers: - name: nginx image: nginx:1.17.2 imagePullPolicy: IfNotPresent ports: - containerPort: 80 restartPolicy: Alwayskubectl apply -f k8s-v1-deploy.yaml创建 v2 版本的 Deployment ：vi k8s-v2-deploy.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy-v2 labels: app: nginx-deploy-v2 spec: replicas: 4 selector: matchLabels: app: nginx v: v2 template: metadata: name: nginx labels: app: nginx v: v2 spec: initContainers: - name: alpine image: alpine imagePullPolicy: IfNotPresent command: ["/bin/sh","-c","echo 2222 > /app/index.html;"] volumeMounts: - mountPath: /app name: app containers: - name: nginx image: nginx:1.17.2 imagePullPolicy: IfNotPresent ports: - containerPort: 80 volumeMounts: - name: app mountPath: /usr/share/nginx/html volumes: - name: app emptyDir: {} restartPolicy: Alwayskubectl apply -f k8s-v2-deploy.yaml创建 Service ：vi k8s-svc.yamlapiVersion: v1 kind: Service metadata: name: canary-svc spec: selector: app: nginx type: NodePort ports: - port: 80 # svc 的访问端口 name: canary-nginx targetPort: 80 # Pod 的访问端口 protocol: TCP nodePort: 31009 # 在机器上开端口，浏览器访问kubectl apply -f k8s-svc.yaml测试：curl 192.168.65.100:31009当然，这仅仅是金丝雀发布的简单演示罢了，实际开发中，还需要结合 Jenkins 的 Pipeline 才行！4.8 删除 Deployment方式一：通过 kubectl delete 命令：# --cascade=false 删除 deploy 的同时保留 Pod kubectl delete deployment deploy的名称 [-n 命名空间] [--cascade=false]kubectl delete deployment nginx-deployment方式二：通过 yaml 文件：kubectl delete -f k8s-deploy.yaml第五章：HPA5.1 概述我们已经可以通过手动执行 kubectl scale 命令实现Pod的扩缩容，但是这显然不符合 Kubernetes 的定位目标–自动化和智能化。Kubernetes 期望可以通过监测Pod的使用情况，实现 Pod 数量的自动调整，于是就产生了 HPA 这种控制器。HPA 可以获取每个 Pod 的利用率，然后和 HPA 中定义的指标进行对比，同时计算出需要伸缩的具体值，最后实现 Pod 的数量的调整。其实 HPA 和之前的 Deployment 一样，也属于一种 Kubernetes 资源对象，它通过追踪分析目标Pod的负载变化情况，来确定是否需要针对性的调整目标 Pod 的副本数。5.2 安装 metrics-server（v0.6.1）获取 metrics-server （网速不行，请点这里components.yaml）：wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.1/components.yaml修改 components.yaml ：vim components.yamlapiVersion: apps/v1 kind: Deployment metadata: labels: k8s-app: metrics-server name: metrics-server namespace: kube-system spec: selector: matchLabels: k8s-app: metrics-server strategy: rollingUpdate: maxUnavailable: 0 template: metadata: labels: k8s-app: metrics-server spec: containers: - args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --kubelet-use-node-status-port - --metric-resolution=15s # 修改部分 - --kubelet-insecure-tls # 使用非安全的协议 image: bitnami/metrics-server:0.6.1 # k8s.gcr.io/metrics-server/metrics-server:v0.6.1安装：kubectl apply -f components.yaml查看是否安装成功：kubectl top nodes --use-protocol-bufferskubectl top pods --use-protocol-buffers5.3 HPA 监控 CPU使用率创建 Deployment 和 Service ：vim k8s-hpa-deploy-svc.yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.17.1 ports: - containerPort: 80 resources: # 资源限制 requests: cpu: "100m" # 100m 表示100 milli cpu，即 0.1 个CPU --- apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx type: NodePort ports: - port: 80 # svc 的访问端口 name: nginx targetPort: 80 # Pod 的访问端口 protocol: TCP nodePort: 30010 # 在机器上开端口，浏览器访问kubectl apply -f k8s-hpa-deploy-svc.yaml创建 HPA ：vim k8s-hpa.yamlapiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: k8s-hpa spec: minReplicas: 1 # 最小 Pod 数量 maxReplicas: 10 # 最大 Pod 数量 targetCPUUtilizationPercentage: 3 # CPU 使用率指标，即 CPU 超过 3%（Pod 的 limit 的 cpu ） 就进行扩容 scaleTargetRef: # 指定要控制的Nginx的信息 apiVersion: apps/v1 kind: Deployment name: nginx-deploy测试：kubectl run -i --tty load-generator --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://192.168.65.100:30010; done"当然，HPA 目前也可以监控内存等指标，但是，目前是 beta 版。apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: pc-hpa namespace: dev spec: minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: memory target: type: Utilization averageUtilization: 90 - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 90 scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-deploy第六章：DaemonSet6.1 概述DaemonSet 控制器确保所有（或一部分）的节点都运行了一个指定的 Pod 副本。每当向集群中添加一个节点时，指定的 Pod 副本也将添加到该节点上。当节点从集群中移除时，Pod 也就被垃圾回收了。删除一个 DaemonSet 可以清理所有由其创建的 Pod。DaemonSet 的典型应用场景：① 在每个节点上运行集群的存储守护进程，如：glusterd、ceph。② 在每个节点上运行日志收集守护进程，如：fluentd、logstash。③ 在每个节点上运行监控守护进程，如：Prometheus Node Exporter、Sysdig Agent、collectd、Dynatrace OneAgent、APPDynamics Agent、Datadog agent、New Relic agent、Ganglia gmond、Instana Agent 等DaemonSet 的资源清单：apiVersion: apps/v1 # 版本号 kind: DaemonSet # 类型 metadata: # 元数据 name: # 名称 namespace: #命名空间 labels: #标签 controller: daemonset spec: # 详情描述 revisionHistoryLimit: 3 # 保留历史版本 updateStrategy: # 更新策略 type: RollingUpdate # 滚动更新策略 rollingUpdate: # 滚动更新 maxUnavailable: 1 # 最大不可用状态的Pod的最大值，可用为百分比，也可以为整数 selector: # 选择器，通过它指定该控制器管理那些Pod matchLabels: # Labels匹配规则 matchLabels 和 matchExpressions 任选其一 app: nginx-pod matchExpressions: # Expressions匹配规则 - key: app operator: In values: - nginx-pod template: # 模板，当副本数量不足时，会根据下面的模板创建Pod模板 metadata: labels: app: nginx-pod spec: containers: - name: nginx image: nginx:1.17.1 ports: - containerPort: 806.2 部署 DaemonSet创建 DaemonSet：vim k8s-ds.yamlapiVersion: apps/v1 kind: DaemonSet metadata: name: ds namespace: default labels: app: ds spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: # tolerations: # 污点，后面讲 # - key: node-role.kubernetes.io/master # effect: NoSchedule containers: - name: nginx image: nginx:1.20.2 resources: limits: memory: 200Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: localtime mountPath: /etc/localtime terminationGracePeriodSeconds: 30 volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghaikubectl apply -f k8s-ds.yaml删除：kubectl delete -f k8s-ds.yaml第七章：StatefulSet（sts）7.1 概述Stateful 翻译为 有状态的 。Deployment 部署的应用称为无状态应用，StatefulSet 部署的应用称为有状态应用。无状态应用：网络可能会变（IP 地址）、存储可能会变（卷）、顺序可能会变（启动的顺序）。应用场景：业务代码，如：使用 SpringBoot 开发的商城应用的业务代码。有状态应用：网络不变、存储不变、顺序不变。应用场景：中间件，如：MySQL 集群、Zookeeper 集群、Redis 集群、MQ 集群。7.2 StatefulSet 使用场景和限制对于有如何要求的应用程序，StatefulSet 非常适合。① 稳定、唯一的网络标识（dnsname），必须配置 HeadlessService（无头服务）：StatefulSet 通过和其相关的无头服务为每个 Pod 提供 DNS 解析条目。假设无头服务的 DNS 条目为 $(service name).$(namespace).svc.cluster.local，那么 Pod 的解析条目就是 $(pod name).$(service name).$(namespace).svc.cluster.local，并且每个 Pod 都是唯一的。② 稳定、持久的存储：每个 Pod 始终对应各自的存储路径（PersistantVolumeClaimTemplate）。③ 有序的、优雅的部署和缩放：按顺序地增加副本、减少副本，并在减少副本时执行清理。④ 有序的、自动的滚动更新：按顺序自动地执行滚动更新。限制：给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage class 来提供，或者由管理员预先提供。删除或者收缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全，它通常比自动清除 StatefulSet 所有相关的资源更有价值。StatefulSet 当前需要 无头服务 来负责 Pod 的网络标识。我们需要负责创建此服务。当删除 StatefulSets 时，StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序地且体面地终止，可以在删除之前将 StatefulSet 缩放为 0。在默认 Pod 管理策略(OrderedReady) 时使用 滚动更新，可能进入需要 人工干预 才能修复的损坏状态。7.3 部署 StatefulSet创建 StatefulSet ：vi k8s-sts.yamlapiVersion: apps/v1 kind: StatefulSet metadata: name: stateful-nginx namespace: default spec: selector: matchLabels: app: ss-nginx serviceName: nginx-svc # 服务名称，这个字段需要和 service 的 metadata.name 相同 replicas: 3 # 副本数 template: metadata: labels: app: ss-nginx spec: containers: - name: nginx image: nginx:1.20.2 --- # 将 StatefulSet 加入网络 apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: default spec: selector: app: ss-nginx type: ClusterIP clusterIP: None # 不分配 ClusterIP ，形成无头服务，整个集群的 Pod 能直接访问，但是浏览器不可以访问。 ports: - name: nginx protocol: TCP port: 80 targetPort: 80kubectl apply -f k8s-sts.yaml新建一个 Pod ，在 Pod 中访问 sts 创建的 Pod 以及无头服务：kubectl run -it test --image=nginx /bin/shcurl stateful-nginx-0.nginx-svccurl stateful-nginx-1.nginx-svccurl stateful-nginx-2.nginx-svccurl nginx-svc删除 StatefulSet ：kubectl delete -f k8s-sts.yaml7.4 Pod 的管理策略StatefulSet 的 Pod 的管理策略（podManagementPolicy）分为：OrderedReady（有序启动，默认值） 和 Parallel（并发一起启动）。... spec: podManagementPolicy: OrderedReady # 控制 Pod 创建、升级以及扩缩容逻辑。Parallel（并发一起启动） 和 ...示例：apiVersion: apps/v1 kind: StatefulSet metadata: name: stateful-nginx namespace: default spec: podManagementPolicy: OrderedReady # 控制 Pod 创建、升级以及扩缩容逻辑。Parallel（并发一起启动） 和 OrderedReady（有序启动）， 默认是 OrderedReady selector: matchLabels: app: ss-nginx serviceName: nginx-svc replicas: 3 template: metadata: labels: app: ss-nginx spec: containers: - name: nginx image: nginx:1.20.2 --- # 将 StatefulSet 加入网络 apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: default spec: selector: app: ss-nginx type: ClusterIP clusterIP: None ports: - name: nginx protocol: TCP port: 80 targetPort: 807.5 分区更新StatefulSet 的更新策略：OnDelete：删除之后才更新。RollingUpdate：滚动更新，如果是此更新策略，可以设置更新的索引（默认值）。... spec: updateStrategy: # 更新策略 type: RollingUpdate # OnDelete 删除之后才更新；RollingUpdate 滚动更新 rollingUpdate: partition: 0 # 更新索引 >= partition 的 Pod ，默认为 0 ...示例：apiVersion: apps/v1 kind: StatefulSet metadata: name: stateful-nginx namespace: default spec: updateStrategy: # 更新策略 type: RollingUpdate # OnDelete 删除之后才更新；RollingUpdate 滚动更新 rollingUpdate: partition: 0 # 更新索引 >= partition 的 Pod ，默认为 0 selector: matchLabels: app: ss-nginx serviceName: nginx-svc replicas: 3 template: metadata: labels: app: ss-nginx spec: containers: - name: nginx image: nginx:1.20.2 --- # 将 StatefulSet 加入网络 apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: default spec: selector: app: ss-nginx type: ClusterIP clusterIP: None ports: - name: nginx protocol: TCP port: 80 targetPort: 80第八章：Job8.1 概述Job 主要用于负责批量处理短暂的一次性任务。Job 的特点：当 Job 创建的 Pod 执行成功结束时，Job 将记录成功结束的 Pod 数量。当成功结束的 Pod 达到指定的数量时，Job 将完成执行。删除 Job 对象时，将清理掉由 Job 创建的 Pod 。Job 可以保证指定数量的 Pod 执行完成。Job 的资源清单：apiVersion: batch/v1 # 版本号 kind: Job # 类型 metadata: # 元数据 name: # 名称 namespace: #命名空间 labels: # 标签 controller: job spec: # 详情描述 completions: 1 # 指定Job需要成功运行Pod的总次数，默认为1 parallelism: 1 # 指定Job在任一时刻应该并发运行Pod的数量，默认为1 activeDeadlineSeconds: 30 # 指定Job可以运行的时间期限，超过时间还没结束，系统将会尝试进行终止 backoffLimit: 6 # 指定Job失败后进行重试的次数，默认为6 manualSelector: true # 是否可以使用selector选择器选择Pod，默认为false ttlSecondsAfterFinished: 0 # 如果是 0 表示执行完Job 时马上删除。如果是 100 ，就是执行完 Job ，等待 100s 后删除。TTL 机制由 TTL 控制器 提供，ttlSecondsAfterFinished 字段可激活该特性。当 TTL 控制器清理 Job 时，TTL 控制器将删除 Job 对象，以及由该 Job 创建的所 有 Pod 对象。 selector: # 选择器，通过它指定该控制器管理那些Pod，非必须字段 matchLabels: # Labels匹配规则 app: counter-pod matchExpressions: # Expressions匹配规则 - key: app operator: In values: - counter-pod template: # 模板，当副本数量不足时，会根据下面的模板创建Pod模板 metadata: labels: app: counter-pod spec: restartPolicy: Never # 重启策略只能设置为Never或OnFailure containers: - name: counter image: busybox:1.30 command: ["/bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1;do echo $i;sleep 20;done"]关于模板中的重启策略的说明：如果设置为OnFailure，则Job会在Pod出现故障的时候重启容器，而不是创建Pod，failed次数不变。如果设置为Never，则Job会在Pod出现故障的时候创建新的Pod，并且故障Pod不会消失，也不会重启，failed次数+1。如果指定为Always的话，就意味着一直重启，意味着Pod任务会重复执行，这和Job的定义冲突，所以不能设置为Always。注意：所有的 Job 类型的 Pod 不需要阻塞式镜像，如：nginx 等。Job 类型的 Pod 需要运行完成后就停止的镜像，如：alpine、busybox 等。Deployment 类型的 Pod 需要阻塞式镜像。8.2 部署 Job新建 Job ：vi k8s-job.yamlapiVersion: batch/v1 kind: Job metadata: name: job-01 labels: app: job-01 spec: # backoffLimit: 6 # 指定 Job 失败后进行重试的次数，默认为 6 ；换言之，Job 失败 6 次后，就认为失败。 # activeDeadlineSeconds: 30 # 指定 Job 可以运行的时间期限，超过时间还没结束，系统将会尝试进行终止。 completions: 4 # 指定 Job 需要成功运行 Pod 的总次数，默认为 1 template: # Pod 模板 metadata: name: pod-job-test labels: app: job-01 spec: containers: - name: alpine image: alpine # 坑：所有的 Job 类型的 Pod 不需要阻塞式镜像，如：nginx 等。Job 类型的 Pod 需要运行完成后就停止的镜像，如：alpine、busybox 等。 command: ["/bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1;do echo $i;done"] restartPolicy: Neverkubectl apply -f k8s-job.yaml删除 Job ：kubectl delete -f k8s-job.yaml第九章：CronJob9.1 概述CronJob 控制器以 Job 控制器为其管控对象，并借助它管理 Pod 资源对象，Job 控制器定义的作业任务在其控制器资源创建之后便会立即执行，但 CronJob 可以以类似 Linux 操作系统的周期性任务作业计划的方式控制器运行时间点及重复运行的方式，换言之，CronJob 可以在特定的时间点反复去执行 Job 任务。CronJob 的资源清单：apiVersion: batch/v1beta1 # 版本号 kind: CronJob # 类型 metadata: # 元数据 name: # 名称 namespace: #命名空间 labels: controller: cronjob spec: # 详情描述 schedule: # cron格式的作业调度运行时间点，用于控制任务任务时间执行 concurrencyPolicy: # 并发执行策略 failedJobsHistoryLimit: # 为失败的任务执行保留的历史记录数，默认为1 successfulJobsHistoryLimit: # 为成功的任务执行保留的历史记录数，默认为3 jobTemplate: # job控制器模板，用于为cronjob控制器生成job对象，下面其实就是job的定义 metadata: {} spec: completions: 1 # 指定Job需要成功运行Pod的总次数，默认为1 parallelism: 1 # 指定Job在任一时刻应该并发运行Pod的数量，默认为1 activeDeadlineSeconds: 30 # 指定Job可以运行的时间期限，超过时间还没结束，系统将会尝试进行终止 backoffLimit: 6 # 指定Job失败后进行重试的次数，默认为6 template: # 模板，当副本数量不足时，会根据下面的模板创建Pod模板 spec: restartPolicy: Never # 重启策略只能设置为Never或OnFailure containers: - name: counter image: busybox:1.30 command: [ "/bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1;do echo $i;sleep 20;done" ]schedule：cron表达式，用于指定任务的执行时间。*/1 * * * *：表示分钟 小时 日 月份 星期。分钟的值从 0 到 59 。小时的值从 0 到 23 。日的值从 1 到 31 。月的值从 1 到 12 。星期的值从 0 到 6，0 表示星期日。多个时间可以用逗号隔开，范围可以用连字符给出：* 可以作为通配符，/ 表示每...concurrencyPolicy：并发执行策略Allow：运行 Job 并发运行（默认）。Forbid：禁止并发运行，如果上一次运行尚未完成，则跳过下一次运行。Replace：替换，取消当前正在运行的作业并使用新作业替换它。9.2 部署 CronJob新建 CronJob ：vi k8s-cronjob.yamlapiVersion: batch/v1beta1 kind: CronJob metadata: name: cronjob-test namespace: default spec: schedule: "*/1 * * * *" jobTemplate: # job 控制器模板，用于为 cronjob 控制器生成 job 对象，下面其实就是 job 的定义 spec: template: spec: containers: - name: hello image: busybox args: ['/bin/sh', '-c', 'date; echo Hello from the Kubernetes cluster'] restartPolicy: Neverkubectl apply -f k8s-cronjob.yaml删除 CronJob ：kubectl delete -f k8s-cronjob.yaml
第一章 Kubernetes 对象（Kubernetes Objects）1.1 什么是 Kubernetes 的对象？官网。Kubernetes 里面操作的资源实体，就是 Kubernetes 的对象，可以使用 yaml 来声明，然后让 Kubernetes 根据 yaml 的声明创建出这个对象。操作 Kubernetes 对象，无论是创建、修改还是删除，都需要使用 Kubernetes 的 API 。如：当使用 kubectl 命令行的时候，CLI 会执行必要的 Kubernetes API 调用。Kubernetes 对象指的是 Kubernetes 系统的持久化实体，所有的这些 Kubernetes 对象，就代表了当前集群中的实际情况。常规的应用中，我们将应用程序产生的数据存放在数据库中；同理，Kubernetes 将其数据以 Kubernetes 对象的形式通过 api-server 存储在 etcd 中。具体来说，这些数据（Kubernetes 对象）描述了：集群中运行了那些容器化应用程序，以及在哪个节点上运行。集群中应用程序可用的资源，如：网络、存储等。应用程序相关的策略定义，如：重启策略、升级策略和容错策略等。其他 Kubernetes 管理应用程序时所需要的信息。每一个 Kubernetes 对象都包含了两个重要字段：spec：需要由我们来提供，描述了我们对该对象所期望的 目标状态 。status：只能由 Kubernetes 系统来修改，描述了该对象在 Kubernetes 系统中的实际状态。Kubernetes 通过对应的控制器（如：Deployment 等），不断的使得实际状态趋向于我们期望的目标状态。示例：查看 Deployment 时在系统底层运行的 yamlkubectl create deployment my-nginx --image=nginx -o yamlkubectl get deployment my-nginx -o yamlapiVersion: apps/v1 kind: Deployment metadata: annotations: deployment.kubernetes.io/revision: "1" creationTimestamp: "2022-03-11T06:39:04Z" generation: 1 labels: app: my-nginx name: my-nginx namespace: default resourceVersion: "21848" uid: 0919fb61-2543-49be-a6cc-9f4d41add783 spec: # 期望的状态 progressDeadlineSeconds: 600 replicas: 1 # 副本数量 revisionHistoryLimit: 10 selector: matchLabels: app: my-nginx strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: creationTimestamp: null labels: app: my-nginx spec: containers: - image: nginx # 使用这个镜像创建容器 imagePullPolicy: Always name: nginx resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 status: # 当前状态 conditions: - lastTransitionTime: "2022-03-11T06:39:04Z" lastUpdateTime: "2022-03-11T06:39:04Z" message: Deployment does not have minimum availability. reason: MinimumReplicasUnavailable status: "False" type: Available - lastTransitionTime: "2022-03-11T06:39:04Z" lastUpdateTime: "2022-03-11T06:39:04Z" message: ReplicaSet "my-nginx-6b74b79f57" is progressing. reason: ReplicaSetUpdated status: "True" type: Progressing observedGeneration: 1 replicas: 1 unavailableReplicas: 1 # 当前集群不可用 updatedReplicas: 1【问】：Kubernetes 是如何保证最终一致的？【答】：etcd 保存的创建资源期望的状态以及最终这个资源的状态，这两种状态需要最终一致，即：spec 和 status 要最终一致。当输入 kubectl create deployment my-nginx --image=nginx 命令的时候，api-server 保存到 etcd ，controller-manager 会解析数据，知道集群中需要 my-nginx ，保存到 etcd 中。kubelet 在底层不停的死循环使得 spec 状态和 status 最终一致，如果 spec.replicas != status.replicas ，就启动 Pod。 while(true){ if(status.replicas != spec.replicas){ kubelet.startPod(); } }1.2 描述 Kubernetes 对象当我们在 Kubernetes 集群中创建一个对象的时候，我们必须提供：该对象的 spec 字段，通过该字段描述我们期望的目标状态。该对象的一些基本信息，如：名字等。可以使用 kubectl 命令行创建对象，也可以使用 yaml 格式的文件进行创建。apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx spec: selector: matchLabels: app: nginx replicas: 3 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80使用 kubectl apply 命令进行部署：kubectl apply -f deployment.yaml使用 kubectl delete 命令进行部署：kubectl delete -f deployment.yaml1.3 Kubernetes 对象的 yaml 格式在上述的 yaml 文件中，如下的字段是必须填写的：apiVersion：用来创建对象时所需要的 Kubernetes API 的版本，可以通过 kubectl api-resources 查询。kind：创建对象的类型，可以通过 kubectl api-resources 查询。metadata：用来唯一确定该对象的元数据，包括 name 、namespace 等，如果 namespace 为空，则默认值为 default 。spec：翻译为 规格 ，表示我们对该对象的期望状态。status 不需要编写，那是 Kubernetes 实际运行过程中将变化的记录保存在此字段中。不同类型的 Kubernetes ，其 spec 对象的格式不同（含有不同的内嵌字段），通过 API 手册可以查看 Kubernetes 对象的字段和描述。当然，我们以后也可以通过此地址来参照。1.4 实际中如何创建 Kubernetes 对象的 yaml① 如果 Kubernetes 集群中已经存在了要创建的对象，那么可以使用 kubectl get 直接输出 yaml ，然后去除 status 即可：kubectl get pod xxx -o yaml > demo.yaml② 如果 Kubernetes 集群中不存在了要创建的对象，那么可以使用类似 kubectl run xxx --dry-run=client 输出 yaml ：# --dry-run=client 表示客户端干跑 kubectl run nginx-pod --image=nginx --dry-run=client -o yaml > demo.yaml1.5 对象名称1.5.1 概述Kubernetes REST API 中，所有的对象都是通过 name 和 UID 唯一性的确定。可以通过 namespace + name 唯一性的确定一个 RESTful 对象，如：/api/v1/namespaces/{namespace}/pods/{name}1.5.2 Name在同一名称空间下，同一个类型的对象，可以通过 name 来确定唯一性。如果删除该对象之后，可以再重新创建一个同名对象。根据命名规则，Kubernetes 对象的名称应该是：最长不超过 253 个字符。必须由小写字母、数字、减号 - 、小数点 . 组成。某些资源类型有更具体的要求。示例：下面的配置文件定义了一个 name 为 nginx-demo 的 Pod，该 Pod 包含一个 name 为 nginx 的 容器apiVersion: v1 kind: Pod metadata: name: nginx-demo ##pod的名字 spec: containers: - name: nginx ##容器的名字 image: nginx:1.7.9 ports: - containerPort: 801.5.3 UIDUID 是由 Kubernetes 系统生成的，唯一标识某个 Kubernetes 对象的字符串。Kubernetes集群中，每创建一个对象，都有一个唯一的 UID 。用于区分多次创建的同名对象（如前面所述，按照名字删除对象后，重新再创建同名对象时，两次创建的对象 name 相同，但是 UID 不同。）1.6 名称空间（命名空间）1.6.1 概述在 Kubernetes 中名称空间是用来对象资源进行隔离的。默认情况下，Kubernetes 会初始化四个名称空间：kubectl get ns|namespacedefault：所有没有指定 namespace 的对象都会被分配到此名称空间中。kube-node-lease：Kubernetes 集群节点之间的心跳维护，V 1.13 开始引入。kube-system：Kubernetes 系统创建的对象放在此名称空间中。kube-public：此名称空间是 Kubernetes 集群安装时自动创建的，并且所有的用户都可以访问（包括未认证的用户），主要是为集群预留的，如：在某些情况中，某些 Kubernetes 对象应用应该能被所有集群用户访问到。1.6.2 名称空间在实际开发中如何划分？① 基于环境隔离，如：dev（开发）、test（测试）、prod（生产）等。② 基于产品线隔离，如：前端、后端、中间件、大数据、Android、iOS、小程序等。③ 基于团队隔离，如：企业发展事业部、技术工程事业部、云平台事业部等。1.6.3 名称空间的特点名称空间资源隔离、网络不隔离，如：配置文件不可以跨名称空间访问，但是网络访问可以跨名称空间访问。默认情况下，安装 Kubernetes 集群的时候，会初始化一个 default 名称空间，用来承载那些没有指定名称空间的 Pod 、Service 、Deployment 等对象。1.6.4 名称空间的命名规则① 不能带小数点（.）。② 不能带下划线（_）。③ 使用数字、小写字母或减号（-）组成的字符串。1.6.5 名称空间的操作示例：创建和删除名称空间（yaml ）vim k8s-namespace.yamlapiVersion: v1 kind: Namespace metadata: name: demo # 名称空间的名字 spec: {} status: {}# 创建名称空间 kubectl apply -f k8s-namespace.yamlkubectl delete -f k8s-namespace.yaml示例：创建和删除名称空间（命令行 ）# 创建名称空间 kubectl create ns demokubectl delete ns demo示例：创建 Pod 的同时，指定自定义的名称空间（yaml）vim k8s-pod.yamlapiVersion: v1 kind: Namespace metadata: name: demo # 名称空间的名字 spec: {} # 默认为空，其实可以不写 status: {} # 默认为空，其实可以不写 # 以上是 namespace --- apiVersion: v1 kind: Pod metadata: name: nginx-pod namespace: demo # 指定自定义的名称空间，如果不写，默认为 default labels: app: nginx spec: containers: - name: nginx image: nginx resources: # 后面会讲 limits: cpu: 200m memory: 500Mi requests: cpu: 100m memory: 200Mi ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai restartPolicy: Always # 以上的 Podkubectl apply -f k8s-pod.yaml1.6.6 重点（后面讲）当我们创建一个 Service 的时候，Kubernetes 会创建一个相应的 DNS 条目。该条目的形式是<service-name>.<namespace-name>.svc.cluster.local，这意味着如果容器中只使用<服务名称>，它将被解析到本地名称空间的服务器。这对于跨多个名字空间（如开发、测试和生产） 使用相同的配置非常有用。如果你希望跨名字空间访问，则需要使用完全限定域名（FQDN）。1.6.7 注意事项大多数的 Kubernetes 资源（如：Pod、Service、副本控制器等）都位于某些名称空间中，但是名称空间本身并不在名称空间中，而且底层资源（如：node 和持久化卷）不属于任何命名空间。查看在名称空间中的资源：kubectl api-resources --namespaced=true查看不在名称空间中的资源：kubectl api-resources --namespaced=false1.7 标签和选择器1.7.1 概述标签（Label）是附件在 Kubernetes 对象上的一组键值对，其意图是按照对用户有意义的方式来标识 Kubernetes 镀锡，同时，又不对 Kubernetes 的核心逻辑产生影响。标签可以用来组织和选择一组 Kubernetes 对象。我们可以在创建 Kubernetes 对象的时候为其添加标签，也可以在创建以后为其添加标签。每个 Kubernetes 对象可以有多个标签，同一个对象的标签的 key 必须是唯一的，如：metadata: labels: key1: value1 key2: value2使用标签（Label）可以高效的查询和监听 Kubernetes 镀锡，在 Kubernetes 界面工具（如：Kubernetes DashBoard）和 kubectl 中，标签使用的非常普遍。而那些非标识性的信息应该记录在 注解（Annotation） 中。1.7.2 为什么要使用标签？使用标签，用户可以按照自己期望的形式组织 Kubernetes 对象之间的结构，而无需对 Kubernetes 有任何修改。应用程序的部署或者批处理程序的部署通常是多维度的（如：多个高可用分区、多个程序版本、多个微服务分层）。管理这些对象的时候，很多时候要针对某一个维护的条件做整体操作，如：将某个版本的程序整体删除。这种情况下，如果用户能够事先规划好标签的使用，再通过标签进行选择，就非常的便捷。标签的例子有：release: stable、release: canary。environment: dev、environment: qa、environment: production。tier: frontend、tier: backend、tier: cache。partition: customerA、partition: customerB。track: daily、track: weekly。上面只是一些使用比较普遍的标签，我们也可以根据自己的情况建立合适的标签。1.7.3 标签的语法标签是一组键值对（key/value），标签的 key 有两个部分：可选的前缀和标签名，通过 / 分隔。标签前缀：标签前缀部分是可选的。如果指定，必须是一个 DNS 的子域名，如：k8s.eip.work 。不能多余 253 个字符。使用 / 和标签名分隔。标签名：标签名部分是必须的。不能多余 63 个字符。必须由字母、数字开始和结尾。可以包含字母、数字、减号（-）、下划线（_）、小数点（.）。如果省略标签前缀，则标签的 key 就被认为是专属于用户的。Kubernetes 的系统组件（如：kube-scheduler、kube-controller-manager、kube-apiserver、kubectl 或其他第三方组件）向可以的 Kubernetes 对象添加标签的时候，必须指定一个前缀。kubernetes.io/ 和 k8s.io/ 这两个前缀是 Kubernetes 核心组件预留的。标签的 value ：不能多于 63 个字符。可以为空字符串。如果不为空，则必须由字母、数字开始和结尾。如果不为空，可以包含字母、数字、减号（-）、下划线（_）、小数点（.）。示例：apiVersion: v1 kind: Pod metadata: name: nginx-pod labels: # 标签 app: nginx environment: prod spec: containers: - name: nginx image: nginx1.7.4 标签选择器通常来讲，会有多个 Kubernetes 对象包含相同的标签。通过使用标签选择器（label selector），用户/客户端可以选择一组对象。标签选择器是 Kubernetes 中最主要的分类和筛选手段。Kubernetes 的 api-server 支持两种形式的标签选择器，equality-based 基于等式的 和 set-based 基于集合的 。标签选择器可以包含多个条件，并使用逗号进行分隔，此时只要满足所有条件的 Kubernetes对象才会被选中。基于等式的标签选择器，可以使用三种操作符 = 、== 、!=。前两个操作符含义是一样的，都代表相等；后一个操作符代表不相等。# 选择了标签名为 `environment` 且 标签值为 `production` 的Kubernetes对象 kubectl get pods -l environment=production,tier=frontend# 选择了标签名为 `tier` 且标签值不等于 `frontend` 的对象，以及不包含标签 `tier` 的对象 kubectl get pods -l tier != frontend# 选择所有包含 `partition` 标签的对象 kubectl get pods -l partition# 选择所有不包含 `partition` 标签的对象 kubectl get pods -l !partition基于集合标签选择器，可以根据标签名的一组值进行筛选。支持的操作符有三种：in、notin、exists。# 选择所有的包含 `environment` 标签且值为 `production` 或 `qa` 的对象 kubectl get pods -l environment in (production, qa)# 选择所有的 `tier` 标签不为 `frontend` 和 `backend`的对象，或不含 `tier` 标签的对象 kubectl get pods -l tier notin (frontend, backend)# 选择包含 `partition` 标签（不检查标签值）且 `environment` 不是 `qa` 的对象 kubectl get pods -l partition,environment notin (qa)示例：Job、Deployment、ReplicaSet 和 DaemonSet 同时支持基于等式的选择方式和基于集合的选择方式。apiVersion: apps/v1 kind: Deployment metadata: name: nginx namespace: default labels: app: nginx spec: selector: matchLabels: # matchLabels 是一个 {key,value} 组成的 map。map 中的一个 {key,value} 条目相当于 matchExpressions 中的一个元素，其 key 为 map 的 key，operator 为 In， values 数组则只包含 value 一个元素。matchExpression 等价于基于集合的选择方式，支持的 operator 有 In、NotIn、Exists 和 DoesNotExist。当 operator 为 In 或 NotIn 时，values 数组不能为空。所有的选择条件都以 AND 的形式合并计算，即所有的条件都满足才可以算是匹配 app: nginx matchExpressions: - {key: tier, operator: In, values: [cache]} - {key: environment, operator: NotIn, values: [dev]} replicas: 1 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:latest1.7.5 标签的操作示例：添加标签kubectl label pod nginx-pod hello=world示例：更新标签kubectl label pod nginx-pod hello=java --overwrite示例：删除标签kubectl label pod nginx-pod hello-1.8 注解 annotations注解（annotation） 可以用来向 Kubernetes 对象的 meta.annotations 字段添加任意的信息。Kubernetes 的客户端或者自动化工具可以存取这些信息以实现自定义的逻辑。metadata: annotations: key1: value1 key2: value21.9 给 VScode 安装插件推荐在 Vscode 中安装如下的插件，可以加快编写 Kubernetes 的 yaml 的速度：1.10 重新认识 kubectl 和 kubelet核心文件夹：/etc/kubernetes 。kubelet 额外参数配置： /etc/sysconfig/kubelet。kubelet配置位置： /var/lib/kubelet/config.yaml。第二章：万物基础 -- 容器2.1 镜像2.1.1 概述平时，我们使用 Docker 的镜像非常之简单，类似于下面的命令：docker pull nginx:latest其实，完整的写法应该是这样：docker pull docker.io/library/nginx:latest如果我们使用自己搭建过 Docker 的私有镜像仓库，可以发现会出现如下的命令：docker push 192.168.65.100:5000/xudaxian/ubuntu:1.0说明：192.168.65.100：镜像仓库的地址。5000：镜像仓库的端口。xudaxian：镜像仓库的名称。ubuntu：镜像的名称。1.0：镜像的版本，如果不写，默认就是 latest 。当然，如果拉取的是 hub.docker.com 中的镜像，那么镜像仓库地址以及端口都可以省略。2.1.2 Kubernetes 中的镜像在 Kubernetes 的 Pod 定义容器的时候，必须指定容器所使用的镜像，容器中的 image 字段支持的语法和 docker 命令是一样的，包括私有镜像仓库和标签，如：# 192.168.65.100:5000/xudaxian/ubuntu:1.0 my-registry.example.com:5000/example/web-example:v1.0注意：在生产环境中，建议锁定镜像的版本。示例：apiVersion: v1 kind: Namespace metadata: name: demo spec: {} status: {} # 以上是 namespace --- apiVersion: v1 kind: Pod metadata: name: nginx-pod namespace: demo labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 # Docker 的镜像名称，和 Docker 命令一样，my-registry.example.com:5000/example/web-example:v1.0，实际开发中，建议锁定镜像的版本。 ports: - containerPort: 80 # 以上的 PodKubernetes 中的镜像拉取策略：IfNotPresent（默认） ：只有当镜像在本地不存在时才会拉取。Always ：每当 kubelet 启动一个容器时，kubelet 会查询容器的镜像仓库， 将名称解析为一个镜像摘要。 如果 kubelet 有一个容器镜像，并且对应的摘要已在本地缓存，kubelet 就会使用其缓存的镜像； 否则，kubelet 就会使用解析后的摘要拉取镜像，并使用该镜像来启动容器。Never ：Kubelet 不会尝试获取镜像。如果镜像已经以某种方式存在本地， kubelet 会尝试启动容器；否则，会启动失败。示例：apiVersion: v1 kind: Namespace metadata: name: demo spec: {} status: {} # 以上是 namespace --- apiVersion: v1 kind: Pod metadata: name: nginx-pod namespace: demo labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 # Docker 的镜像名称，和 Docker 命令一样，my-registry.example.com:5000/example/web-example:v1.0，实际开发中，建议锁定镜像的版本。 imagePullPolicy: Always # 镜像拉取策略：IfNotPresent（默认）、Always、Never ports: - containerPort: 80 # 以上的 Pod2.1.3 下载私有仓库的镜像我们在使用阿里云容器镜像的私有仓库的使用，阿里云要求我们进行登录，如果是 docker 拉取镜像，那么只需要 docker login 之类的就可以了；但是，如果使用 Kubernetes 该怎么办？① 创建 secret ：# -n demo ：表示该密钥将只在指定的名称空间 demo 中生效 # docker-registry aliyun ：指定 Docker 镜像仓库的名称 # --docker-server：Docker 镜像仓库的地址 # --docker-username：Docker 镜像仓库的用户名 # --docker-password：Docker 镜像仓库的密码 kubectl create secret -n demo docker-registry aliyun \ --docker-server=registry.cn-shanghai.aliyuncs.com \ --docker-username=xudaxian \ --docker-password=123456② 在 yaml 中拉取镜像的时候设置镜像拉取的密钥（secret）：apiVersion: v1 kind: Namespace metadata: name: demo spec: {} status: {} # 以上是 namespace --- apiVersion: v1 kind: Pod metadata: name: demo-pod namespace: demo labels: app: nginx spec: containers: imagePullSecrets: # Pull镜像时使用的 secret 名称，以 key：secretkey 格式指定 - name: aliyun - name: nginx image: nginx:1.20.2 # Docker 的镜像名称，和 Docker 命令一样，my-registry.example.com:5000/example/web-example:v1.0，实际开发中，建议锁定镜像的版本。 imagePullPolicy: Always # 镜像拉取策略：IfNotPresent（默认）、Always、Never - name: arcgis image: registry.cn-shanghai.aliyuncs.com/xudaxian/arcgis/v1.0 imagePullPolicy: Always注意：这个肯定运行不了，需要将 secret 的用户名和密码设置为自己的，而且在拉取阿里云私有镜像的时候设置为自己的镜像。2.2 环境变量使用 env 用来给 Pod 中的容器设置环境变量，相当于 docker run -e xxx=xxx 中的 -e 参数。示例：vim k8s-mysql.yamlapiVersion: v1 kind: Pod metadata: name: mysql-pod namespace: default labels: app: mysql-pod spec: containers: - name: mysql-pod image: mysql:5.7 env: # 环境变量 相当于 docker run -e xxx = xxx - name: MYSQL_ROOT_PASSWORD # root 的密码 value: "123456" - name: MYSQL_DATABASE # mysql 的 数据库 value: ssm restartPolicy: Alwayskubectl apply -f k8s-mysql.yaml2.3 启动命令Docker 的镜像拥有存储镜像信息的相关元数据，如果不设置生命周期命令和参数，容器运行时会运行镜像制作时提供的默认的命令和参数，Docker 原生定义这两个字段为 ENTRYPOINT 和 CMD 。如果在创建工作负载时填写了容器的运行命令和参数，将会覆盖镜像构建时的默认命令 Entrypoint 、CMD，规则如下：镜像 Entrypoint镜像CMD容器 command容器 args最终执行[touch][/root/test]未设置未设置[touch /root/test][touch][/root/test][mkdir]未设置[mkdir][touch][/root/test]未设置[/opt/test][touch /opt/test][touch][/root/test][mkdir][/opt/test][mkdir /opt/test]换言之，如果在 Kubernetes 的 yaml 中定义了 comand 和 args ，那么就会覆盖 Dockerfile 中的 ENTRPOINT 和 CMD 。示例：启动 MySQLvim k8s-mysql.yamlapiVersion: v1 kind: Pod metadata: name: mysql-pod namespace: default labels: app: mysql-pod spec: containers: - name: mysql-pod image: mysql:5.7 env: - name: MYSQL_ROOT_PASSWORD value: "123456" - name: MYSQL_DATABASE value: ssm args: - "--lower_case_table_names=1" - "--character-set-server=utf8mb4" - "--collation-server=utf8mb4_general_ci" - "--default-authentication-plugin=mysql_native_password" ports: - containerPort: 3306 restartPolicy: Alwayskubectl apply -f k8s-mysql.yaml2.4 资源限额器中的程序要运行，肯定会占用一定的资源，比如 CPU 和内存等，如果不对某个容器的资源做限制，那么它就可能吃掉大量的资源，导致其他的容器无法运行。针对上面的情况，Kubernetes 提供了对内存和 CPU 的资源进行配额的机制，这种机制主要通过 resources 选项实现，它有两个子选项：limits：用于限制运行的容器的最大占用资源，当容器占用资源超过 limits 时会被终止，并进行重启。requests：用于设置容器需要的最小资源，如果环境资源不够，容器将无法启动。cpu：core 数，可以为整数或小数，1 == 1000m。memory：内存大小，可以使用 Gi、 Mi、G、M 等形式。示例：apiVersion: v1 kind: Pod metadata: name: nginx-pod labels: app: nginx spec: containers: - name: nginx image: nginx:1.20.2 resources: # 资源限制 limits: cpu: 2 memory: 1500M requests: cpu: 1 memory: 1024M ports: - containerPort: 80 restartPolicy: Always