本文和你一起快速了解Kubernetes 对象和组件的详细信息(Kubectl、Pod、Deployment、Service、ConfigMap、Volume、PV、PVC、Daemonset、Secret、Affinity、Taint-Toleration、Helm 等),还提供了示例使用场景。
涵盖了Kubernetes的关键特性,如服务发现和负载均衡、存储编排、自动更新和回滚、自动资源分配、自我监测和自我修复、多集群管理、安全性、水平扩展、容器平衡、跨云部署、密钥和配置管理等等。
先决条件
- 了解容器技术 (Docker)
关键词: 容器化、Kubernetes、Kubectl、Pod、部署、服务、ConfigMap、ReplicaSet、卷、Cheatsheet。
注意: K8s 对象和对象功能可能更新/更改。创建此存储库时,K8s 的版本是 v1.22.3。
动机
我们为什么要使用 Kubernetes?Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化工作负载和服务,促进声明式配置和自动化。它拥有一个庞大且快速增长的生态系统。
什么是容器化?什么是容器编排?
-
容器化是跨多个网络资源的操作系统级虚拟化或应用程序级虚拟化,以便软件应用程序可以在任何云或非云环境中的称为容器的隔离用户空间中运行
(维基百科) - 有了Docker环境,我们就可以创建容器了。
- Kubernetes 和 Docker Swarm 是容器编排和管理工具,可自动执行和调度容器的部署、管理、扩展和网络。
特征
-
服务发现和负载均衡: Kubernetes 可以使用 DNS 名称或使用自己的 IP 地址公开容器。如果容器的流量很高,Kubernetes 能够进行负载均衡并分配网络流量,从而使部署稳定。
-
存储编排: Kubernetes 允许你自动挂载你选择的存储系统,例如本地存储、公共云提供商等。
-
自动更新和回滚: 你可以使用 Kubernetes 描述已部署容器的所需状态,并且它可以以受控的速率将实际状态更改为所需状态。
-
自动装箱: 你告诉 Kubernetes 每个容器需要多少 CPU 和内存 (RAM)。Kubernetes 可以将容器安装到你的节点上,以充分利用你的资源。
-
自我监控: Kubernetes 不断检查节点和容器的健康状况
-
自我修复: Kubernetes 重新启动失败的容器、替换容器、杀死不响应用户定义的健康检查的容器
-
自动化各种手动流程: 例如,Kubernetes 将为你控制哪个服务器将托管容器、如何启动容器等。
-
与多组容器交互: Kubernetes能够同时管理更多集群
-
提供附加服务: 除了容器管理之外,Kubernetes 还提供安全、网络和存储服务
-
水平扩展: Kubernetes 不仅可以垂直扩展资源,还轻松快速可以水平扩展资源。
-
容器平衡: Kubernetes 通过计算容器的
最佳位置
,始终知道将容器放置在哪里。 -
到处运行: Kubernetes 是一种开源工具,让你可以自由地利用本地、混合或公共云基础设施,从而将工作负载移动到你想要的任何地方。
-
密钥和配置管理: Kubernetes 让你可以存储和管理敏感信息。
什么是 Kubernetes?
-
Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化工作负载和服务,促进声明式配置和自动化。它拥有一个庞大且快速增长的生态系统。Kubernetes 服务、支持和工具广泛可用。
Kubernetes架构
Kubernetes 组件
- 控制平面:用户从控制平面输入命令和配置文件。它控制所有集群。以下控制器可以具有云提供商依赖性:
-
-
节点控制器:用于检查云提供商以确定节点停止响应后是否已在云中删除
-
路由控制器:用于在底层云基础设施中设置路由
-
服务控制器:用于创建、更新和删除云提供商负载均衡器。
-
从逻辑上讲,每个控制器都是一个单独的进程,但为了降低复杂性,它们都被编译成单个二进制文件并在单个进程中运行。
这些控制器的一些类型是:
-
节点控制器:负责在节点出现故障时进行通知并做出响应。
-
任务控制器:监视代表一次性任务的任务对象,然后创建 Pod 来运行这些任务直至完成。
-
Endpoints 控制器:填充 Endpoints 对象(即加入 Services 和 Pod)。
-
服务帐户和令牌控制器:为新命名空间创建默认帐户和 API 访问令牌
-
调度决策考虑的因素包括:
-
个人和集体资源需求,
-
硬件/软件/政策限制,
-
亲和力和反亲和力规范,
-
数据局部性,
-
工作负载间的干扰,
-
最后期限。
-
API Server:
它公开了 Kubernetes API。API Server是 Kubernetes 控制平面的前端。
-
Etcd:
一致且高度可用的键值存储,用作 Kubernetes 所有集群数据(元数据、对象等)的存储。
-
Scheduler:它监视新创建的没有分配节点的 Pod,并选择一个节点供它们运行。
-
Controller Manager:它运行控制器进程。
-
Cloud Controller Manager:它嵌入了特定于云的控制逻辑。Cloud Controller Manager允许你将集群链接到云提供商的 API,并将与该云平台交互的组件与仅与集群交互的组件分开。云-controller-manager 仅运行特定于你的云提供商的控制器
-
- Node:
节点组件在每个节点上运行,维护运行的 Pod 并提供 Kubernetes 运行时环境。
-
- Kubernetes 支持多种容器运行时:Docker、containerd、CRI-O以及 Kubernetes CRI(容器运行时接口)的任何实现`
- 它维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。
- 它使用操作系统数据包过滤层(如果有且可用)。否则,kube-proxy 会自行转发流量。
- Kubelet:
在集群中每个节点上运行的代理。它确保容器在 Pod 中运行。kubelet 采用通过各种机制提供的一组 PodSpec,并确保这些 PodSpec 中描述的容器正在运行并且健康。
- Kube-proxy:它是一个网络代理,在集群中的每个节点上运行,实现了 Kubernetes 服务概念的一部分。
- 容器运行时:`容器运行时是负责运行容器的软件。
安装
下载:
- Kubectl: Kubernetes 命令行工具 kubectl 允许你针对 Kubernetes 集群运行命令。
- Minikube: 它是一个可让你在本地运行 Kubernetes 的工具。它在你的个人计算机上运行单节点 Kubernetes 集群 ( minikube.sigs.k8s.io/docs/start/ )
- KubeAdm: 你可以使用kubeadm工具创建和管理Kubernetes集群。这是为了使用计算机创建集群。
-
- 转到实操场景: LAB: K8s Kubeadm Cluster Setup
对于学习K8s并在电脑上运行,安装Kubectl和Minikube就足够了。
Kubectl
配置文件
- 你可以通过不同的方式与 K8s 集群通信:REST API、命令行工具 (CLI-Kubectl)、GUI(kube-dashboard 等)
- 安装完成后,你可以找到 kubernetes 配置文件(C:UsersUser.kubeconfig),该文件是 YAML 文件。
- 配置文件包含3个主要部分:集群(集群证书数据、服务器、名称)、上下文(集群和用户、命名空间)、用户(名称、配置功能、证书等)
用法
- Kubectl 是我们连接 minikube 的主要命令行工具。命令的组合有很多种。因此不可能列出所有命令。
- 在终端上运行
kubectl
时,可以看到一些简单的命令。kubectl --help
也提供了更多信息。 - 模式:
kubectl [get|delete|edit|apply] [pods|deployment|services] [podName|serviceName|deploymentName]
- 示例:
kubectl get pods podName
、kubectl delete pods test_pod
、kubectl describe pods firstpod
等。
Pod
- Pod 是 K8s 中创建和管理的最小单元。
- Pod 可能包含 1 个以上容器,但大多数 Pod 仅包含 1 个容器。
- 每个 Pod 都有唯一的 ID(uid)。
- 每个 Pod 都有唯一的 IP 地址。
- 同一个 Pod 中的容器运行在同一个 Node(计算机)上,这些容器可以在 localhost 上相互通信。
- 创建第一个 pod,命令方式(使用命令):
- 实操场景
-
- 如何使用命令式命令创建基本的 K8s pod,
- 如何获取有关 pod 的更多信息(以解决故障),
-
- 如何在 pod 中运行命令,
- 如何删除 Pod。
转到实操场景: LAB:K8s 创建 Pod - 命令式方式
Pod:YAML 文件
- 命令式方式可能难以存储和管理流程。每次我们都必须输入命令。为了防止这种情况,我们可以使用 YAML 文件来定义 pod 和 pod 的功能。这种方式称为
声明式方式
。 - 声明式方式(带文件)、命令式方式(带命令)
- 示例 Yaml 文件:
- 请查看场景(创建 Pod - 声明式方式,下面的链接)以了解更多信息。
转到实操场景: LAB:K8s 创建 Pod - 声明式方式(带文件) - 环境变量
Pod:生命周期
-
Pending: API->etcd,已创建 pod,已创建 pod id,但未在节点上运行。
-
Creating: Scheduler从 etcd 获取 pod,在节点上进行分配。节点上的 Kubelet 从 docker 镜像仓库拉取镜像。
-
ImagePullBackOff: Kubelet 无法从镜像仓库中拉取镜像。例如,镜像名称错误(拼写错误)、授权失败、用户名/密码错误。
-
Running;
-
-
容器以 3 种方式关闭:
-
1. 应用程序完成任务并自动关闭而不报错,
-
2. 使用或系统发送关闭信号并自动关闭而不报错,
- 给出错误并关闭并给出错误代码。
-
-
重启策略(可以在 Pod 定义中定义):
Successed (completed) : 如果容器成功关闭且没有错误,并且重启策略配置为 on-failure/never,则转换为成功。
Failed
CrashLoopBackOff:
- 如果重启策略配置为始终并且容器一次又一次关闭,则容器一次又一次重新启动(再次重新启动之前重新启动等待时间:10秒 -> 20秒 -> 40秒 -> .. -> 5分钟)。
- 如果容器运行超过 10 分钟,状态将从
CrashLoopBackOff
转换为正在运行
。
多容器 Pod、初始化容器
- 最佳实践:1个Container通常运行在1个Pod中,因为K8s中最小的元素是Pod(Pod可以放大/缩小)。
- 当容器相互依赖时,多个容器在同一个 Pod 中运行。
- 一个 Pod 中的多个容器具有以下特点:
-
- 运行在同一个 Pod 上的多个容器运行在同一个 Node 上。
- 同一 Pod 中的容器同时运行/暂停/删除。
- 同一 Pod 中的容器在 localhost 上相互通信,不存在任何网络隔离。
- 同一个Pod中的容器通常使用一个卷,并且它们可以访问该卷中的相同文件。
初始化容器
- 初始化容器用于在运行应用程序容器之前配置应用程序。
- 初始化容器处理它应该运行的内容,然后成功关闭,初始化容器关闭后,应用程序容器启动。
- 下面的示例展示了如何在一个 Pod 中定义 init 容器。有 2 个容器:appcontainer 和 initcontainer。Initcontainer 正在轮询服务 (myservice)。当它找到时,它会关闭并启动应用程序容器。
apiVersion: v1
kind: Pod
metadata:
name: initcontainerpod
spec:
containers:
- name: appcontainer # after initcontainer closed successfully, appcontainer starts.
image: busybox
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: initcontainer
image: busybox # init container starts firstly and look up myservice is up or not in every 2 seconds, if there is myservice available, initcontainer closes.
command: ['sh', '-c', "until nslookup myservice; do echo waiting for myservice; sleep 2; done"]
---
# save as service.yaml and run after pod creation
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
- 请查看场景(下面的链接)以了解更多信息。
转到实操场景: LAB:K8s 多容器 - Sidecar - Emptydir卷 - 端口转发
标签和选择器、注释、命名空间
标签
- 标签对于使用键:值对访问 K8s 对象非常重要。
- key:value 用于标签。
- 前缀也可以用于 key:value 的可选。例如 example.com/tier:front-end、kubernetes.io/、k8s.io/
- 在文件中(声明方式),标签添加在元数据下。可以添加多个标签。
- 在命令中(命令式),我们还可以给 Pod 添加标签。
kubectl label pods pod1 team=development #adding label team=development on pod1
kubectl get pods --show-labels
kubectl label pods pod1 team- #remove team (key:value) from pod1
kubectl label --overwrite pods pod1 team=test #overwrite/change label on pod1
kubectl label pods --all foo=bar # add label foo=bar for all pods
选择器
- 我们可以使用 kubectl 选择/过滤 pod。
kubectl get pods -l "app=firstapp" --show-labels
kubectl get pods -l "app=firstapp,tier=frontend" --show-labels
kubectl get pods -l "app=firstapp,tier!=frontend" --show-labels
kubectl get pods -l "app,tier=frontend" --show-labels #equality-based selector
kubectl get pods -l "app in (firstapp)" --show-labels #set-based selector
kubectl get pods -l "app not in (firstapp)" --show-labels #set-based selector
kubectl get pods -l "app=firstapp,app=secondapp" --show-labels # comma means and => firstapp and secondapp
kubectl get pods -l "app in (firstapp,secondapp)" --show-labels # it means or => firstapp or secondapp
节点选择器
- 通过 Node Selector,我们可以指定哪个 pod 在哪个 Node 上运行。
- 也可以用命令式的方式来标记节点。
kubectl apply -f podnode.yaml
kubectl get pods -w #always watch
kubectl label nodes minikube hddtype=ssd #after labelling node, pod11 configuration can run, because node is labelled with hddtype:ssd
注解
- 它与标签类似,但它用于详细信息(例如所有者、通知电子邮件、发布日期等),不用于链接对象。
kubectl apply -f podannotation.yaml
kubectl describe pod annotationpod
kubectl annotate pods annotationpod foo=bar #imperative way
kubectl delete -f podannotation.yaml
命名空间
- 命名空间提供了一种用于隔离单个集群内的资源组的机制。它们提供了名称的范围。
- 命名空间不能相互嵌套,并且每个 Kubernetes 资源只能位于一个命名空间中。
- 如果命令中未确定,Kubectl 命令将在默认命名空间中运行。
kubectl get pods --namespaces kube-system #get all pods in the kube-system namespaces
kubectl get pods --all-namespaces # get pods from all namespaces
kubectl create namespace development #create new development namespace in imperative way
kubectl get pods -n development # get pods from all namespace
- 通过声明方式,可以创建命名空间并在相关命名空间上运行 pod。
kubectl apply -f namespace.yaml
kubectl get pods -n development #get pods in the development namespace
kubectl exec -it namespacedpod -n development -- /bin/sh #run namespacepod in development namespace
- 如果我们不使用 -n 命名空间,kubectl 命令将在默认命名空间上运行
kubectl config set-context --current --namespace=development #now default namespace is development
kubectl get pods #returns pods in the development namespace
kubectl config set-context --current --namespace=default #now namespace is default
kubectl delete namespaces development #delete development namespace
Deployment
- Deployment 为 Pod 和 ReplicaSet 提供声明性更新。
- 我们在部署中定义状态,部署控制器比较所需状态并采取必要的操作来保持所需状态。
- 部署对象是更高级别的 K8s 对象,它自动控制和保持单个或多个 Pod 的状态。
kubectl create deployment firstdeployment --image=nginx:latest --replicas=2
kubectl get deployments
kubectl get pods -w #on another terminal
kubectl delete pods #we can see another terminal, new pod will be created (to keep 2 replicas)
kubectl scale deployments firstdeployment --replicas=5
kubectl delete deployments firstdeployment
- 请查看场景(下面的链接)以了解有关部署和创建部署的声明方式的更多信息。
转到实操场景: LAB:K8s Deployment - 扩缩容 - Bash 连接 - 端口转发
Replicaset
- 部署对象创建Replicaset对象。部署自动提供不同副本集的转换。
- Replicaset 负责管理副本的创建和删除。但是,当 Pod 更新时(例如镜像更改),它无法更新复制集 Pod。但是,部署可以针对所有更改进行更新。因此,最佳实践是使用部署,而不是直接使用副本集。
- 重要提示: 可以直接创建副本集,但我们不能使用副本集的更新/回滚、撤消功能。Deployment提供使用更新/回滚、撤消功能。
更新和回滚
- 2个更新策略:
-
- maxUnavailable: 在更新期间,显示删除的容器的最大数量(总计:10个容器;如果maxUn:2,则该时间段内运行的最小:8个容器)
- maxSurge: 在更新期间,显示集群上运行的最大容器数(总计:10个容器;如果maxSurge:2,则一次最多运行12个容器)
- 重新创建策略: 先删除所有 Pod,然后从头创建 Pod。如果两个不同版本的软件相互产生负面影响,则可以使用此策略。
- 滚动策略(默认) :逐步更新 Pod。Pod 是逐步更新的,所有 Pod 不会同时删除。
kubectl set image deployment rolldeployment nginx=httpd:alpine --record # change image of deployment
kubectl rollout history deployment rolldeployment #shows record/history revisions
kubectl rollout history deployment rolldeployment --revision=2 #select the details of the one of the revisions
kubectl rollout undo deployment rolldeployment #returns back to previous deployment revision
kubectl rollout undo deployment rolldeployment --to-revision=1 #returns back to the selected revision=1
kubectl rollout status deployment rolldeployment -w #show live status of the rollout deployment
kubectl rollout pause deployment rolldeployment #pause the rollout while updating pods
kubectl rollout resume deployment rolldeployment #resume the rollout if rollout paused
转到实操场景: 实验室:K8s 更新 - 回滚
网络、服务
K8s 网络要求
- 每个 Pod 都有唯一的、自己的 IP 地址(Pod 内的容器共享网络命名空间)。
- 所有 POD 都可以与所有其他 Pod 通信,无需 NAT(网络地址转换)
- 所有 NODE 都可以与所有 pod 通信,无需 NAT。
- POD 的 IP 在整个集群中是唯一的。
CNI(容器网络接口)
- 不同供应商和设备的容器和节点的联网很难处理。因此,K8s 将这个责任交给了 CNI 插件来处理网络需求。
-
[CNI](https://github.com/containernetworking/cni)(容器网络接口)是一个云原生计算基金会项目,由用于编写插件以在 Linux 容器中配置网络接口的规范和库组成,以及许多受支持的插件。
- K8s 具有由用户选择的 CNI 插件。一些 CNI 方法包括:Flannel, calico, weave, and canal。
- Calico是K8s 中流行的开源 CNI 方法/插件之一。
- 集群中的网络管理:
-
- 不使用 NAT 的节点之间的覆盖定义(例如 --pod-network-cidr 管理)
- 给 Pod 分配 IP
- IP表管理
- Vxlan接口实现等
Service
- 一种将在一组 Pod 上运行的应用程序公开为网络服务的抽象方法。
- Kubernetes ServiceTypes 允许你指定你想要的服务类型。默认为 ClusterIP。
- 类型有:
-
- ClusterIP: 在集群内部 IP 上公开服务。选择此值使得服务只能从集群内部访问。这是默认的服务类型。
- NodePort: 在静态端口(NodePort)上公开每个节点 IP 上的服务。NodePort 服务到 ClusterIP 服务的路由会自动创建。你将能够通过请求 : 从集群外部联系 NodePort 服务。
- LoadBalancer: 使用云提供商的负载均衡器向外部公开服务。外部负载均衡器到NodePort 或ClusterIP 服务的路由是自动创建的。
- ExternalName: 通过返回 CNAME 记录及其值,将服务映射到 externalName 字段的内容(例如 foo.bar.example.com)。没有设置任何类型的代理。
- 服务对象定义示例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
转到实操场景: 实验室:K8s 服务实现(ClusterIp、NodePort 和 LoadBalancer)
存活性和就绪性探针
存活性探针
-
kubelet 使用存活性探针来了解何时重新启动容器。例如,存活性探针可能会陷入死锁,即应用程序正在运行,但无法取得进展。
- 控制 Pod 的方式有多种:
-
- httpGet,
- exec command,
- tcpSocket,
- grpc.
- initialDelaySeconds:启动后等待一段时间。例如5秒,5秒后开始运行命令
- periodSeconds:在一段时间内,运行命令。
转到实操场景: LAB:K8s 存活性探针
就绪探针
-
有时,应用程序会暂时无法提供流量。例如,应用程序可能需要在启动时加载大量数据或配置文件,或者启动后依赖外部服务。在这种情况下,你不想杀死应用程序,但你也不想向它发送请求。Kubernetes 提供了就绪探针来检测和缓解这些情况。带有容器的 pod 报告它们尚未准备好,不会通过 Kubernetes 服务接收流量。
- 就绪探针类似于 liveness pod。唯一的区别是定义
readinessProbe
而不是livenessProbe
。
资源限制、环境变量
资源限制
- 如果没有任何限制,Pod 可以消耗高达物理资源限制的资源(CPU、内存)。
- Pod 所使用的资源可能是有限的。
-
- 使用 1 个 cpu 核心 => cpu = "1" = "1000" = "1000m"
- 使用 1 个 cpu 核心的 10% => cpu = "0.1" = "100" = "100m"
- 使用 64 MB => 内存:
64M
- CPU资源在它定义的时候就受到了严格的限制。
- 当 pod 请求的内存资源超过限制时,pod 会将其状态更改为
OOMKilled
并重新启动以限制内存使用。 - 示例(如下),Pod 请求 64MB 内存和 0.25 个 CPU 核心,最大使用 256MB 内存和 0.5 个 CPU 核心。
环境变量
- 可以在 YAML 文件中为每个 pod 定义环境变量。
转到实操场景: LAB:K8s 创建 Pod - 声明式方式 - 环境变量
存储
- 临时卷(临时卷):Pod 中的多个容器达到临时卷。当 pod 被删除/终止时,卷也会被删除。但是当容器重新启动时,卷仍然可用,因为 pod 仍在运行。
- 有两种类型的临时卷:
-
- Directory
- DirectoryOrCreate
- FileOrCreate
- Emptydir
- Hostpath
Emptydir
- Emptydir(节点上的空目录)是在创建 pod 的节点上创建的,并使用
volumeMounts
挂载到容器上。Pod 中的多个容器可以读/写此卷。 - Emptydir 卷取决于 Pod 生命周期。如果 pod 被删除,emptydir 也会被删除。
spec:
containers:
- name: sidecar
image: busybox
command: ["/bin/sh"]
args: ["-c", "sleep 3600"]
volumeMounts: # volume is mounted under "volumeMounts"
- name: cache-vol # "name" of the volume type
mountPath: /tmp/log # "mountPath" is the path in the container.
volumes:
- name: cache-vol
emptyDir: {} # "volume" type "emptydir"
转到实操场景: LAB:K8s 多容器 - Sidecar - 空目录卷 - 端口转发
HostPath
- 和emtpydir类似,hostpath也是在pod创建的节点上创建的。另外,hostpath是节点上专门定义的路径。
apiVersion: v1
kind: Pod
metadata:
name: hostpath
spec:
containers:
- name: hostpathcontainer
image: ImageName # e.g. nginx
volumeMounts:
- name: directory-vol # container connects "volume" name
mountPath: /dir1 # on the container which path this volume is mounted
- name: dircreate-vol
mountPath: /cache # on the container which path this volume is mounted
- name: file-vol
mountPath: /cache/config.json # on the container which file this volume is mounted
volumes:
- name: directory-vol # "volume" name
hostPath: # "volume" type "hostpath"
path: /tmp # "path" on the node, "/tmp" is defined volume
type: Directory # "hostpath" type "Directory", existed directory
- name: dircreate-vol
hostPath: # "volume" type "hostpath"
path: /cache # "path" on the node
type: DirectoryOrCreate # "hostpath" type "DirectoryOrCreate", if it is not existed, create directory
- name: file-vol
hostPath: # "volume" type "hostpath"
path: /cache/config.json # "path" on the node
type: FileOrCreate # "hostpath" type "FileOrCreate", if it is not existed, create file
密钥
- 密钥对象存储敏感且安全的信息,例如用户名、密码、ssh 令牌、证书。
- Secret(你定义的)和 pod(你定义的)应该位于同一命名空间中(例如,如果定义的 Secret 位于
default
命名空间中,则 pod 也应该位于default
命名空间中)。 - 有 8 种不同的机密类型(basic-auth、tls、ssh-auth、token、service-account-token、dockercfg、dockerconfigjson、opaque)。opaque类型是默认类型,也是最常用的类型。
- Pod 通过两种不同的方式调用 Secret:卷和环境变量
- 命令式方式,在终端上运行(命令中的通用=不透明):
kubectl create secret generic mysecret2 --from-literal=db_server=db.example.com --from-literal=db_username=admin --from-literal=db_password=P@ssw0rd!
- 使用文件命令式方式
kubectl create secret generic mysecret3 --from-file=db_server=server.txt --from-file=db_username=username.txt --from-file=db_password=password.txt
- 使用 json 文件的命令式方式
kubectl create secret generic mysecret4 --from-file=config.json
转到实操场景: LAB:K8s Secret(声明式和命令式方式)
配置信息
- 与
密钥
相同。不同的是configmap不保存敏感信息。它存储配置变量。 - Configmap 使用键值对存储数据。
- Pod 通过两种不同的方式调用 Configmap:卷和环境变量
- 场景显示了配置映射的用法。
转到实操场景: LAB:K8s 配置
节点 – Pod 亲和力
- 亲和力意味着亲近、接近、熟悉。
节点亲和力
- 通过节点亲和性,特定的 Pod 可以在所需的节点上运行(节点选择器也支持该功能,但节点亲和性更灵活)。
- 如果节点标有键值,我们可以在该特定节点上运行一些 Pod。
- 节点亲和性的术语:
-
- weight: 如果权重大于其他权重,则该权重的优先级高于其他权重。
- requiredDuringSchedulingIgnoredDuringExecution: 根据
matchExpression
找到调度时的节点,并在该节点上运行pod。如果未找到,则在找到特定节点matchExpression
之前不要运行此 pod。 - IgnoredDuringExecution: 调度后,如果节点标签被从节点中移除/删除,则在执行时忽略它。
- PreferredDuringSchedulingIgnoredDuringExecution:根据
matchExpression
找到调度时的节点,并在该节点上运行pod。如果未找到,请在找到的任何位置运行此 pod。
- 为了更好地理解,请查看LAB:K8s Node Affinity
进入场景: LAB:K8s 节点亲和性
Pod 亲和力
- 某些 pod 应该与同一节点或同一可用区上的其他 pod 一起运行(例如,前端 pod 与同一可用区上的缓存 pod 一起运行)
- 如果为一个 Pod 定义了 Pod 关联性,则该 Pod 会与相关 Pod 在同一节点或同一可用区上运行。
- 集群中的每个节点都标有默认标签。
-
-
kubernetes.io/hostname
:例如kubernetes.io/hostname=minikube
-
kubernetes.io/arch
:例如kubernetes.io/arch=amd64
-
kubernetes.io/os
:例如kubernetes.io/os=linux
-
- 在云上运行的集群中的每个节点都标有以下标签。
-
-
topology.kubernetes.io/region
:例如topology.kubernetes.io/region=northeurope
-
topology.kubernetes.io/zone
:例如topology.kubernetes.io/zone=northeurope-1
-
apiVersion: v1
kind: Pod
metadata:
name: frontendpod
labels:
app: frontend # defined labels
deployment: test
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: cachepod
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # required: if not found, not run this pod on any node
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- frontend
topologyKey: kubernetes.io/hostname # run this pod with the POD which includes "app=frontend" on the same worker NODE
preferredDuringSchedulingIgnoredDuringExecution: # preferred: if not found, run this pod on any node
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: branch
operator: In
values:
- develop
topologyKey: topology.kubernetes.io/zone # run this pod with the POD which includes "branch=develop" on the any NODE in the same ZONE
podAntiAffinity: # anti-affinity: NOT run this pod with the following match ""
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: deployment
operator: In
values:
- prod
topologyKey: topology.kubernetes.io/zone # NOT run this pod with the POD which includes "deployment=prod" on the any NODE in the same ZONE
containers:
- name: cachecontainer # cache image and container name
image: redis:6-alpine
污点和容忍
- 节点亲和性是 Pod 的一个属性,用于吸引/接受它们到一组节点。污点则相反,它们允许节点排斥/拒绝一组 Pod。
- TAINT 被分配给节点。分配给 POD 的 TOLERATION
-
kubectl描述节点minikube
,在污点部分,可以看到污点。 - 要使用命令将污点添加到节点:
kubectl taint node minikube app=production:NoSchedule
- 要使用命令删除节点的污点:
kubectl taint node minikube app-
- 如果 pod 对相关污点没有任何容忍度,则无法在污点节点上启动(pod 状态保持待定)
- 污点类型:
- key1=value1:effect:(例如
kubectl 污点节点 minikube app=生产:NoSchedule
) - 污点
效果
类型: - NoSchedule: 如果 pod 不能容忍这种影响,则它不能在相关节点上运行(状态将处于待定状态,直到容忍/untaint)
- PreferNoSchedule: 如果 pod 不能容忍这种影响,并且没有任何未受污点的节点,则它可以在相关节点上运行。
- NoExecute: 如果pod不能容忍这种效果,则无法在相关节点上运行。如果在分配
NoExecute
污点之前该节点上有 pod 正在运行,则在污点NoExecute
之后,无法容忍的 pod 将在此节点上被驱逐。 - 如需澄清,请查看LAB:K8s Taint Toleration
转到实操场景: 实验室:K8s 污点容忍
Deamon Set
- 它允许在每个节点上运行 Pod。可以将其配置为仅运行特定节点。
- 例如,你可以运行在集群中每个节点上运行的日志应用程序,并且应用程序将这些日志发送到主日志服务器。在这种情况下,手动配置每个节点可能会很麻烦,因此使用DeamonSet将有利于节省时间和精力。
- 如果在该时间段内将新节点添加到集群上并在集群上运行DeamonSet,则DeamonSet上定义的默认 pod 也会在新节点上运行,无需执行任何操作。
转到实操场景: 实验室:K8s Daemonset - 在 Minikube 上创建 3 个节点
持久卷和持久卷声明
- 卷是存储数据的短暂/临时区域。Emptydir 和 hostpath 在运行相关 pod 的节点上创建卷。
- 在集群上创建Mysql pod的场景中,我们不能长期使用emptydir和hostpath。因为他们不提供长期/持久的数量。
- 持久卷提供在集群外运行的长期存储区域。
- 集群上可以启用多种存储解决方案:nfs、iscsi、azure disk、aws ebs、google pd、cephfs。
- 容器存储接口(CSI)提供K8s集群和不同存储解决方案的连接。
持久卷
-
accessModes
类型: -
ReadWriteOnce
:仅对 1 个节点进行读/写。 -
ReadOnlyMany
:仅读取多个节点。 -
ReadWriteMany
:对多个节点进行读/写。 -
persistentVolumeReclaimPolicy
类型:它定义了使用卷结束后卷的行为。 -
Retain
:卷在使用后保留所有数据。 -
Recycle
:不删除卷,但删除卷中的所有数据。如果选择它,我们会得到空体积。 -
Delete
:卷在使用后被删除。
# Creating Persistent Volume on NFS Server on the network
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysqlpv
labels:
app: mysql # labelled PV with "mysql"
spec:
capacity:
storage: 5Gi # 5Gibibyte = power of 2; 5GB= power of 10
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle # volume is not deleted, all data in the volume will be deleted.
nfs:
path: /tmp # binds the path on the NFS Server
server: 10.255.255.10 # IP of NFS Server
持久卷声明 (PVC)
- 我们应该创建 PVC 来使用卷。对于 PVC,可以选择现有的 PV。
- K8s之所以用2个文件(PVC和PV)来管理卷,是为了将K8s集群(PV)的管理和卷(PVC)的使用分开。
- 如果K8s集群的系统管理有单独的角色,系统管理员创建PV(连接不同的存储供应商),开发人员只使用现有的PV和PVC。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysqlclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem # VolumeMode
resources:
requests:
storage: 5Gi
storageClassName: ""
selector:
matchLabels:
app: mysql # choose/select "mysql" PV that is defined above.
转到实操场景: LAB:K8s 持久卷和持久卷声明
Storage Class
- 使用 PV 创建卷是手动创建卷的方式。通过存储类,它可以自动化。
- 云提供商在其基础设施上提供存储类。
- 创建 pod/deployment 时,会触发存储类自动创建 PV(触发顺序:Pod -> PVC -> Storage Class -> PV)。
# Storage Class Creation on Azure
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standarddisk
parameters:
cachingmode: ReadOnly
kind: Managed
storageaccounttype: StandardSSD_LRS
provisioner: kubernetes.io/azure-disk
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
-
storageClassName
被添加到 PVC 文件中。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysqlclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
storageClassName: "standarddisk" # selects/binds to storage class (defined above)
- 当部署/Pod 请求 PVC(声明)时,存储类会自动在基础设施上提供卷。
Stateful Set
- Pod/Deployment 是无状态对象。有状态集提供运行有状态应用程序。
- Deployment 和 Statefulset 的区别:
- Statefulset 中 Pod 的名称不是随机分配的。它给出名称 statefulsetName_0,1,2,3。
- StatefulSet 中的 Pod 不是同时创建的。Pod 按顺序创建(新的 Pod 创建会等到上一个 Pod 的运行状态)。
- 当 StatefulSet 缩容时,Pod 会被随机删除。Pod 按顺序删除。
- 如果在statefulset中定义了PVC,则statefulset中的每个pod都有自己的PV
转到实操场景: 实验室:K8s 有状态集 - Nginx
任务、定时任务
Job
-
一项任务创建一个或多个 Pod,并将继续重试执行 Pod,直到指定数量的 Pod 成功终止
。如果容器未成功完成,它将再次重新创建。 -
当达到指定的成功完成次数时,任务(即任务)就完成了。
- 完成任务后,Pod 不会被删除。可以查看 Pod 中的日志。
- Job用于运行一次的任务(例如维护脚本,用于创建DB的脚本)
- Job还用于处理存储在队列或桶中的任务。
spec:
parallelism: 2 # each step how many pods start in parallel at a time
completions: 10 # number of pods that run and complete job at the end of the time
backoffLimit: 5 # to tolerate fail number of job, after 5 times of failure, not try to continue job, fail the job
activeDeadlineSeconds: 100 # if this job is not completed in 100 seconds, fail the job
转到实操场景: 实验室:K8s Job
定时任务
- Cron任务是一个可以在预定时间启动的定时任务。
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │ 7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
#
# https://crontab.guru/
# Examples:
# 5 * * * * : (means) For every day start at minute 5: 00:05 - Second day 00:05 ....
# */5 * * * * : (means) At every 5th minute: 00:05 - 00:10 - 00:15 ...
# 0 */2 * * * : (means) At minute 0 pass every 2d hour: 00:00 - 02:00 - 04:00 ...
# "*" means "every"
# "/" means "repetitive"
spec:
schedule: "*/1 * * * *" # At every 1st minute: 00:01 - 00:02 ...
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
imagePullPolicy: IfNotPresent
command: # start shell and echo
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
进入场景: LAB:K8s Cron Job
认证、基于角色的访问控制、服务帐户
认证
- 创建认证的原理简单解释一下:
- 用户使用 openssl 应用程序创建 .key(密钥文件)和 .csr(证书签名请求文件,包括用户名和角色)
- 用户将 .csr 文件发送到 K8s 管理员
- K8s 管理员使用此 .csr 文件创建一个 K8s 对象,并创建 .crt 文件(认证文件)以提供给用户
- 用户获取此 .crt 文件(认证文件)并在用户的 PC 中创建带有认证的凭证(set-credentials)。
- 用户使用集群和凭证创建上下文(set-context),并使用该上下文。
- 现在它需要为用户获取/创建授权。
基于角色的访问控制(RBAC、授权)
- 它提供向特定用户授予授权(角色)。
-
Role
、RoleBinding
K8s 对象用于将用户绑定到特定的命名空间
。 -
ClusterRole
、ClusterRoleBinding
K8s 对象用于将用户绑定到特定的命名空间
。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"] # "services", "endpoints", "pods", "pods/log" etc.
verbs: ["get", "watch", "list"] # "get", "list", "watch", "create", "update", "patch", "delete"
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: username@hostname.net # "name" is case sensitive, this name was defined while creating .csr file
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role #this must be Role or ClusterRole
name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to
apiGroup: rbac.authorization.k8s.io
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: DevTeam # Name is case sensitive
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
服务帐号
- RBAC 用于真实的人。
- 服务帐户用于可以连接 K8s API 以创建 K8s 对象的 pod/应用程序。
Ingress
-
管理对集群中服务的外部访问的 API 对象,通常是 HTTP。
-
Ingress 可以提供负载均衡、SSL等
Ingress 不是服务类型,但它充当集群的入口点。
-
入口资源仅支持用于引导 HTTP(S) (L7) 流量的规则。
-
Ingress 将 HTTP 和 HTTPS 路由从集群外部公开到集群内的服务。流量路由由 Ingress 资源上定义的规则控制。
Ingress 控制器是一个 L7 应用程序负载均衡器,根据 K8s 规范在 K8s 中工作。
-
入口控制器:Nginx、HAproxy、Traefik
# Simple Ingress Object Definition
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx-example
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
转到实操场景: LAB:K8s Ingress
可视化Dashborad
- 你可以使用默认的 K8s 仪表板查看以下内容:
- 集群上的所有工作负载:内存和 CPU 使用情况、更新时间、镜像名称、节点名称、状态
- Cron Jobs 和 Jobs
- Daeamon Sets
- Deployments, Replicasets
- Pods, Stateful Sets
- Services, Endpoints, IPs, Ports,
- Persistent Volume Claims, Persisten Volumes
- Config Maps,
- Secrets, Storage Classes
- Cluster Roles and Role Binding
- Namespaces
- Network Policies
- Nodes
- Roles and Role Bindings
- Service Accounts
# if working on minikube
minikube addons enable dashboard
minikube addons enable metrics-server
minikube dashboard
# if running on WSL/WSL2 to open browser
sensible-browser http://127.0.0.1:45771/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/
转到实操场景: 实验室:在 Real Cluster 上启用仪表板
Helm
- Helm 是 K8s 的包管理器(helm.sh/)。
-
Helm 将Chart安装到 Kubernetes 中,为每次安装创建一个新版本。要查找新Chart,你可以搜索 Helm Chart存储库。
- 借助 Helm,可以轻松安装最佳实践 K8s 设计和产品。搜索 K8s 包 => artifacthub.io/
- 详细教程 => helm.sh/docs/intro/…
- 重要术语:(参考:Helm.sh)
- Chart: Chart 是一个 Helm 包。它包含在 Kubernetes 集群内运行应用程序、工具或服务所需的所有资源定义。可以将其视为 Kubernetes 中的 Homebrew 公式、Apt dpkg 或一个 Yum RPM 文件。
- Repository: 存储库是可以收集和共享Chart的地方
- Release: Release是在 Kubernetes 集群中运行的Chart的实例。一个Chart通常可以多次安装到同一个集群中。每次安装时,都会创建一个新版本。考虑一个 MySQL Chart。如果你如果你希望集群中运行两个数据库,你可以将该Chart安装两次。每个数据库都有自己的版本,而该版本又会有自己的版本名称。
参考
- Kubernetes.io
- KubernetesTutorial
- udemy-course:Kubernetes-Temelleri
- 本文实操场景示例: github.com/omerbsezer/…