k8s.io/api/core/v1/types.go下的Pod结构体中包含了所有Pod字段。
1. 架构
.---{etcd}
/
+----------------------------|------------------+
|Master {Controller} { API }----{Scheduler}|
| { Manager }----{Server} |
+----------------------------\-\----------------+
|\ \
\ \ \
+-----------------------------\-\-\-------------+
| +------------------+ | \ \ |
| | Node | | \ *--. |
| /(|{Networking} | | \ \__ |
| CNI<| | | | \ | |
|+-----X(|{ kubelet }------|--+ +----+ +-\--+ |
||CRI<| | | |Node| |Node| |
|| \(|{ContainerRuntime}--+ | | | | |
|| | | | +----+ +----+ |
|+-CSI---|{VolumePlugin} | | |
|| | | |->OCI |
|+-grpc--|{DevicePlugin} | | |
| | | | |
| |{LinuxOS} -------|-+ |
| +------------------+ |
| |
+-----------------------------------------------+
从上图可以看出,k8s集群由Master和Node两种节点组成,而这两种角色分别对应着控制节点和计算节点。 其中,控制节点,由负责API服务的kube-apiserver、负责调度的kube-scheduler,以及负责容器编排的kube-controller-manager组成。 整个集群的持久化数据由kube-apiserver处理后保存在Etcd中。
计算节点最核心的部分,则是一个叫做kubelet的组件。
- 使用CRI(container runtime interface)接口和各种容器运行时打交道。
- 使用gRPC协议同DevicePlugin插件(管理GPU等宿主机物理设备)打交道。
- 使用CNI(container networking interface)接口和各种网络插件打交道。
- 使用CSI(container storage interface)接口和各种持久化存储打交道。
2. 概念备忘
-
static pod kubelet启动的时候,会自动检查/etc/kubernetes/manifests目录下的yaml文件,并部署它们。这种部署方式称之为"static pod"。
-
污点与容忍 kubectl taint nodes node1 foo=bar:NoSchedule kubectl taint nodes –all node-role.kubernetes.io/master-
3. kubelet
在k8s社区中,与kubelet以及容器运行时管理相关的内容,都属于SIG-Node的范畴。无论如何,我都不太建议你对kubelet的代码进行大量的改动。保持kubelet根上游基本一致的重要性,就跟保持kube-apiserver跟上游一致是一个道理。当然,kubelet本身,也是按照“控制器”模式来工作的,可以用如下所示的一幅示意图来表示:
4-sources
chooseRuntime api-server(primary,watch)
(dockershim,remote) http endpoint(pull)
| AddPodAdmitHandler http server(push)
| NewContainerGC | Eviction .---<----. file(pull)
| | | | {NodeStatus}
+-------+------+->--+--+----+----+---------+-------------. *---->---*.-<---.
| | | | | . \ ** ./ {Network}
registerListers| | | | . * * .{ Status}
| | | NetGenericPLEG .* *->--.*--<-.
diskSpaceManager| | * * {Status }
| | * *{Manager}
oomWatcher | * **-->--*
| * * {PLEG}
InitNetworkPlugin * ->*>-*
( SyncLoop ) .-<---.
* <-chan kubetypes.PodUpdate* | {volume }
* <chan *pleg.PodLifecycleEvent V\{Manager}
( periodic sync events ) *->---*
* housekeeping events * .--<--.
* * { image }
* * \{Manager}
+------------------------+ * * *-->--*
|PodUpdateWorker(e.g.ADD)| *. .*
|*generate pod status | * . . *
|*check volume status | * . . * \
|call runtime to start | ** \
|containers | HandlerPods \
+------------------<-----+<----{Add,Update,Remove,Delete,...}---------------------*
从上图可以看到,kubelet的工作核心,就是一个控制循环,即:SyncLoop(图中的大圆圈)。而驱动这个控制循环运行的事件,包括四种:
- Pod更新事件;
- Pod 生命周期变化;
- kubelet 本身设置的执行周期;
- 定时的清理事件。 所以,跟其他控制器类似,kubelet 启动的时候,要做的第一件事情,就是设置 Listers,也就是注册它所关心的各种事件的 Informer。这些 Informer,就是 SyncLoop 需要处理的数据的来源。 此外,kubelet 还负责维护着很多很多其他的子控制循环(也就是图中的小圆圈)。这些控制循环的名字,一般被称作某某 Manager,比如 Volume Manager、Image Manager、Node Status Manager 等等。 不难想到,这些控制循环的责任,就是通过控制器模式,完成 kubelet 的某项具体职责。比如 Node Status Manager,就负责响应 Node 的状态变化,然后将 Node 的状态收集起来,并通过 Heartbeat 的方式上报给 APIServer。再比如 CPU Manager,就负责维护该 Node 的 CPU 核的信息,以便在 Pod 通过 cpuset 的方式请求 CPU 核的时候,能够正确地管理 CPU 核的使用量和可用量。
那么这个 SyncLoop,又是如何根据 Pod 对象的变化,来进行容器操作的呢? 实际上,kubelet 也是通过 Watch 机制,监听了与自己相关的 Pod 对象的变化。当然,这个 Watch 的过滤条件是该 Pod 的 nodeName 字段与自己相同。kubelet 会把这些 Pod 的信息缓存在自己的内存里。 而当一个 Pod 完成调度、与一个 Node 绑定起来之后, 这个 Pod 的变化就会触发 kubelet 在控制循环里注册的 Handler,也就是上图中的 HandlePods 部分。此时,通过检查该 Pod 在 kubelet 内存里的状态,kubelet 就能够判断出这是一个新调度过来的 Pod,从而触发 Handler 里 ADD 事件对应的处理逻辑。
在具体的处理过程当中,kubelet 会启动一个名叫 Pod Update Worker 的、单独的 Goroutine 来完成对 Pod 的处理工作。 比如,如果是 ADD 事件的话,kubelet 就会为这个新的 Pod 生成对应的 Pod Status,检查 Pod 所声明使用的 Volume 是不是已经准备好。然后,调用下层的容器运行时(比如 Docker),开始创建这个 Pod 所定义的容器。 而如果是 UPDATE 事件的话,kubelet 就会根据 Pod 对象具体的变更情况,调用下层容器运行时进行容器的重建工作。
在这里需要注意的是,kubelet 调用下层容器运行时的执行过程,并不会直接调用 Docker 的 API,而是通过一组叫作 CRI(Container Runtime Interface,容器运行时接口)的 gRPC 接口来间接执行的。
+------------------------------------------------------------------------------------------------------------+
| +---------------------------------------------------------+|
| | Management ||
| |+-<-----+ +----------+ .----<----. ||
| |Vschedul|<pod,node list---|api-server| /Workloads \ ||
| .------------------------------||-ing |---------bind--->| {etcd} |----VOrchestration|||
| / |+---->--+ +----------+ *----->-----* ||
| / +---------------------------------------------------------+|
| pod/ CRI Spec-----+ |
|+-------------V---------------------+---------------------------------------+ |
|| Kubelet |Sendbox: |client api |
|| ------- ------- |Create/Delete/List +--dockershim------+----------->docker |
|| /kubelet\ pod /Generic\ CRI grpc |Container: | | |
||^SyncLoop V---->Runtime |----------|------------------->| | |
|| \ / \SyncPod/ |Create/Start/Exec | | |
|| ------- ------- |Image: +--remote(no-op)---|->CRI shim->ContainerRuntime |
|| |Pull/List | |
|+-----------------------------------+---------------------------------------+ |
+------------------------------------------------------------------------------------------------------------+
CRI 机制能够发挥作用的核心,就在于每一种容器项目现在都可以自己实现一个 CRI shim,自行对 CRI 请求进行处理。 CNCF 里的 containerd 项目,就可以提供一个典型的 CRI shim 的能力,即:将 Kubernetes 发出的 CRI 请求,转换成对 containerd 的调用,然后创建出 runC 容器。而 runC 项目,才是负责执行我们前面讲解过的设置容器 Namespace、Cgroups 和 chroot 等基础操作的组件。
CRI接口的定义如下:
|
|
4. pod的常用字段
k8s.io/api/core/v1/types.go下的Pod结构体中包含了所有Pod字段。
- k8s为什么选择pod作为编排的基本单位呢? 在一个真正的操作系统里,进程并不是"孤苦伶仃"地独立运行的,而是以进程组的方式,“有原则地"组织在一起。 在k8s中,pod相当于进程组,docker容器相当于进程,进程组中的进程间是公用同一个文件系统和网络的, 那k8s是如何实现多个docker容器共享同一个文件系统和网络呢?
假设有A、B两个容器,可以先运行B容器,然后使用命令docker run --net=B --volumes-from=B --name=A image-A ...
运行A容器,
这样A就会共享B的文件系统和网络了。但是,这有个问题,就是A必须在B之后执行,如果解决这个问题呢?
所以,在Kubernetes项目里,Pod的实现需要使用一个中间容器,这个容器叫作Infra容器。在这个Pod中,Infra容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过Join Network Namespace的方式,与Infra容器关联在一起。而对于同一个Pod里面的所有用户容器来说,它们的进出流量,也可以认为都是通过Infra容器完成的。所以网络插件其实是和infra容器打交道的,由于infra容器的rootfs几乎什么都没有,所以网络插件无法在infra容器中存放任何的配置,只能关注于Infra容器的Network Namespace。
infra容器的镜像是k8s.gcr.io/pause,这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有100~200KB。
+--------Pos-------------------------------+
| /proc/{pid}/ns/net -> net:[4026532483] |
| {ContainerA} {ContainerB} |
| | -net=k8s.gcr.io/pause | |
| | | |
| +-->{Infra container}<----+ |
+--------------------^---------------------+
|
- shareProcessNamespace pod内的所有容器间共享PID命名空间(默认是不共享的)
由于busybox容器开启了stdin和tty,我们就可以使用kubectl attach命令就可以直接进入busybox容器,而不用重复使用-t -i选项了。
可以看到,在这个容器里,我们不仅可以看到它本身的ps ax指令,还可以看到nginx容器的进程,以及Infra容器的/pause进程。这就意味着,整个Pod里的每个容器的进程,对于所有容器来说都是可见的。
- pod内共享宿主机的Namespace
- 对于需要预先执行的操作,可以在pod中的initContainers中定义
|
|
在 Pod 中,所有 Init Container 定义的容器,都会比 spec.containers 定义的用户容器先启动。并且,Init Container 容器会按顺序逐一启动,而直到它们都启动并且退出了,用户容器才会启动。
-
ImagePullPolicy Always(默认): 每次创建Pod都重新拉取一次镜像。 Never: 永远不会主动拉取这个镜像 IfNotPresent: 只在宿主机上不存在这个镜像时才拉取
-
lifecycle(Container Lifecycle Hooks)
|
|
postStart: 在容器启动后,立刻执行一个指定的操作。需要明确的是,postStart定义的操作,虽然是在Docker容器ENTRYPOINT执行之后,但它并不严格保证顺序(非阻塞的)。也就是说,在postStart启动时,ENTRYPOINT有可能还没有结束。 preStop: 容器被杀死之前(比如,收到了SIGKILL信号)。而需要明确的是,preStop操作的执行,是阻塞的。所以,它会阻塞当前容器杀死进程,直到这个Hook定义操作完成之后,才允许容器被杀死,这跟postStart不一样。
- Pod对象在Kubernetes中的生命周期 Pending: 这个状态意味着,Pod的YAML文件已经提交给了Kubernetes,API对象已经被创建并保存在Etcd当中。但是,这个Pod里有些容器因为某些原因而不能被顺利创建。比如,调度不成功。 Running: 这个状态下,Pod已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。 Succeeded: 这个状态意味着,Pod里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。 Failed: 这个状态下,Pod里至少有一个容器以不正常的状态(非0的返回码)退出。这个状态的出现,意味着你得想办法Debug这个容器得应用,比如查看Pod的Events和日志。 Unknown: 这是一个异常状态,意味着Pod的状态不能持续地被kubelet汇报给kube-apiserver,这很有可能是主从节点(Master和kubelet)间的通信出现了问题。
Pod对象的Status字段,还可以再细化出一组Conditions。这些细分状态的值包括:PodScheduled、Ready、Initialized,以及Unschedulable。它们主要用于描述造成当前Status的具体原因是什么。比如,Pod当前的Status是Pending,对应的Condition是Unschedulable,这就意味着它的调度出现了问题。
- Projected Volume(投射数据卷)
- Secret;
- ConfigMap;
- Downward API: 让Pod里的容器能够直接获取到这个Pod API对象本身的信息。
- ServiceAccountToken
我现在有了一个Pod,我能不能在这个Pod里安装一个Kubernetes的Client,这样就可以从容器里直接访问并且操作这个Kubernetes的API了呢?
Service Account对象的作用,就是Kubernetes系统内置的一种"服务账号”,它是Kubernetes进行权限分配的对象。比如,Service Account A,可以只被允许对Kubernetes API进行GET操作,而Service Account B,则可以有Kubernetes API的所有操作权限。
像这样的Service Account的授权信息和文件,实际上保存在它所绑定的一个特殊的Secret对象里的。这个特殊的Secret对象,就叫作ServiceAccountToken。任何运行在Kubernetes集群上的应用,都必须使用这个ServiceAccountToken里保存的授权信息,也就是Token,才可以合法地访问API Server。
所以说,Kubernetes项目的Projected Volume其实只有三种,因为第四种ServiceAccountToken,只是一种特殊的Secret而已。
另外,为了方便使用,Kubernetes已经为你提供了一个默认"服务账户"(default Service Account)。并且,任何一个运行在Kubernetes里的Pod,都可以直接使用这个默认的Service Account,而无需显示地声明挂载它。
这是如何做到的呢?
当然还是靠Projected Volume机制。
如果你查看一下任意一个运行在Kubernetes集群里的Pod,就会发现,每一个Pod,都已经自动声明一个类型是Secret、名为default-token-xxxx的Volume,然后自动挂载在每个容器的一个固定目录上。比如:
|
|
这样,一旦Pod创建完成,容器里的应用就可以直接从这个默认ServiceAccountToken的挂载目录 (/var/run/secrets/kubernetes.io/serviceaccount)里访问到授权信息和文件。而这个Secret类型的Volume里面的内容如下所示:
所以,你的应用程序只要直接加载这些授权文件,就可以访问并操作Kubernetes API了。而且,如果你使用的是Kubernetes官方的Client包(k8s.io/client-go)的话,它还可以自动挂载这个目录下的文件,你不需要做任何配置或者编码操作。
这种把Kubernetes客户端以容器的方式运行在集群里,然后使用default Service Account自动授权的方式,被称为"InClusterConfig",也是我最推荐的进行Kubernetes API编程的授权方式。
RBAC(Role-Based Access Control)
- Role(权限组):角色,它其实是一组规则,定义了一组对 Kubernetes API 对象的操作权限。
- RoleBinding:定义了“被作用者”和“角色”的绑定关系。
|
|
需要再次提醒的是,Role 和 RoleBinding 对象都是 Namespaced 对象(Namespaced Object),它们对权限的限制规则仅在它们自己的 Namespace 内有效,roleRef 也只能引用当前 Namespace 里的 Role 对象。 那么,对于非 Namespaced(Non-namespaced)对象(比如:Node),或者,某一个 Role 想要作用于所有的 Namespace 的时候,我们又该如何去做授权呢?
- ClusterRole
- ClusterRoleBinding
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: example-clusterrolebinding
subjects:
- kind: ServiceAccount
name: example-user
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: example-clusterrole
apiGroup: rbac.authorization.k8s.io
- Subject:被作用者,既可以是“人”,也可以是“机器”,也可以是你在 Kubernetes 里定义的“用户”。
部署完ServiceAccount后,再来查看下它的yaml
可以发现,Kubernetes 会为一个 ServiceAccount 自动创建并分配一个 Secret 对象,即:上述 ServiceAcount 定义里最下面的 secrets 字段。 这个 Secret,就是这个 ServiceAccount 对应的、用来跟 APIServer 进行交互的授权文件,我们一般称它为:Token。Token 文件的内容一般是证书或者密码,它以一个 Secret 对象的方式保存在 Etcd 当中。
- 使用ServiceAccount 在pod的yaml中添加serviceAccountName字段。
默认会挂载到/var/run/secrets/kubernetes.io/serviceaccount 目录下。
- 用户组
实际上,一个 ServiceAccount,在 Kubernetes 里对应的“用户”的名字是
system:serviceaccount:<Namespace名字>:<ServiceAccount名字>
而它对应的内置“用户组”的名字,就是:system:serviceaccounts:<Namespace名字>
比如,现在我们可以在 RoleBinding 里定义如下的 subjects:
这就意味着这个 Role 的权限规则,作用于 mynamespace 里的所有 ServiceAccount。这就用到了“用户组”的概念。
就意味着这个 Role 的权限规则,作用于整个系统里的所有 ServiceAccount。 最后,值得一提的是,在 Kubernetes 中已经内置了很多个为系统保留的 ClusterRole,它们的名字都以 system: 开头。你可以通过 kubectl get clusterroles 查看到它们。
- 探针和restartPolicy
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: test-liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
在这个Pod中,我们定义了一个有趣的容器。它在启动之后做的第一件事,就是在/tmp目录下创建了一个healthy文件,以此作为自己已经正常运行的标志。而30s过后,它会把这个文件删除掉。
于此同时,我们定义了一个这样的livenessProbe(健康检查)。它的类型是exec,这意味着,它会在容器启动后,在容器里面执行一条我们指定的命令,比如:“cat /tmp/healthy”。这时,如果这个文件存在,这条命令的返回值就是0,Pod就会认为这个容器不仅已经启动,而且是健康的。这个健康检查,在容器启动5s后开始执行(initialDelaySeconds:5),每5s执行一次(periodSeconds: 5)。
现在,让我们来具体实践一下这个过程。
首先,创建这个Pod:
$ kubectl create -f test-liveness-exec.yaml
然后,查看这个Pod的状态:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
test-liveness-exec 1/1 Running 0 10s
可以看到,由于已经通过了健康检查,这个Pod就进入了Running状态。
而30s之后,我们再查看一下Pod的Events:
$ kubectl describe pod test-liveness-exec
你会发现,这个Pod在Events报告了一个异常:
FirstSeen LastSeen Count From SubobjectPath Type
--------- -------- ----- ---- ------------- -----
2s 2s 1 {kubelet worker0} spec.containers{liveness} Warning
显然,这个健康检查探查到/tmp/healthy已经不存在了,所以它报告容器是不健康的。那么接下来会发生什么呢?
我们不妨再次查看一下这个Pod的状态:
$ kubectl get pod test-livenes-exec
NAME READY STATUS RESTARTS AGE
liveness-exec 1/1 Running 1 1m
这时我们发现,Pod并没有进入Failed状态,而是保持了Running状态。这是为什么呢?
其实,如果你注意到RESTARTS字段从0到1的变化,就明白原因了:这个异常的容器已经被Kubernetes重启了 。在这个过程中,Pod保持Running状态不变。
需要注意的是:Kubernetes中并没有Docker的Stop语义。所以虽然是Restart(重启),但实际却是重新创建了容器。
这个功能就是Kubernetes里的Pod恢复机制,叫作restartPolicy。它是Pod的Spec部分的一个标准字段(pod.spec.restartPolicy)。
- Always(默认):任何时候这个容器发生了异常,它一定会被重新创建
- OnFailure: 只在容器异常时才自动重启容器;
- Never: 从来不重启容器
pod的重启遵循两个基本的设计原则:
- 只要Pod的restartPolicy指定的策略允许重启异常的容器(比如:Always),那么这个Pod就会保持Running状态,并进行容器重启。否则,Pod就会进入Failed状态。
- 对于包含多个容器的Pod,只要它里面所有的容器都计入异常状态后,Pod才会进入Failed状态。在此之前,Pod都是Running状态。此时,Pod的READY字段会显示正常容器的个数。
假设一个Pod里只有一个容器,然后这个容器异常退出了。那么,只有当restartPolicy=Never时,这个Pod才会进入Failed状态。而其他情况下,由于Kubernetes都可以重启这个机器,所以Pod的状态保持Running不变。
而如果这个Pod有多个容器,仅有一个容器异常退出,它就始终保持Running状态,哪怕即使restartPolicy=Never。只有当所有容器也异常退出之后,这个Pod才会进入Failed状态。
- readniessProbe探针(探测该Pod能否被Service访问到) readinessProbe检查结果的成功与否,决定的这个Pod是不是能被通过Service的方式访问到,而并不影响Pod的生命周期。 Service 是 Kubernetes 项目中用来将一组 Pod 暴露给外界访问的一种机制。那么,这个 Service 又是如何被访问的呢? __第一种方式,是以 Service 的 VIP(Virtual IP,即:虚拟 IP)方式。__比如:当我访问 10.0.23.1 这个 Service 的 IP 地址时,10.0.23.1 其实就是一个 VIP,它会把请求转发到该 Service 所代理的某一个 Pod 上。这里的具体原理,我会在后续的 Service 章节中进行详细介绍。 __第二种方式,就是以 Service 的 DNS 方式。__比如:这时候,只要我访问“my-svc.my-namespace.svc.cluster.local”这条 DNS 记录,就可以访问到名叫 my-svc 的 Service 所代理的某一个 Pod。 Service DNS 的方式下,具体还可以分为两种处理方法: 第一种处理方法,是 Normal Service。这种情况下,你访问“my-svc.my-namespace.svc.cluster.local”解析到的,正是 my-svc 这个 Service 的 VIP,后面的流程就跟 VIP 方式一致了。 而第二种处理方法,正是 Headless Service。这种情况下,你访问“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是 my-svc 代理的某一个 Pod 的 IP 地址。可以看到,这里的区别在于,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。
面是一个标准的定义 Headless Service的 YAML 文件:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
当Service的clusterIP字段的值为None时,就说明该Service是Headless Service。 当你按照这样的方式创建了一个 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
这个 DNS 记录,正是 Kubernetes 项目为 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。这个“可解析身份”包含svc-name字段,所以只要你知道了一个 Pod 的名字,以及它对应的 Service 的名字,你就可以非常确定地通过这条 DNS 记录访问到 Pod 的 IP 地址。
在StatefulSet中,由于Pod的名字是不变的(通过编号来维护,编号还能保证Deployment下的多个Pod在部署的时候是阻塞的有顺序的),所以<pod-name>.<svc-name>.<namespace>.svc.cluster.local
这个DNS记录就不会改变,这样就可以保证Pod重建后,还是可以重新访问到之前的域名。
- PodPreset功能
- 首先开发人员给出一个yaml:
- 然后,运维人员想给此yaml追加些字段,这个使用就可以使用如下的yaml:
apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
name: allow-database
spec:
selector:
matchLabels:
role: frontend
env:
- name: DB_PORT
value: "6379"
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
在这个PodPreset的定义中,首先是一个selector。这就意味着后面这些追加的定义,只会作用于selector所定义的、带有"role:frontend"标签的Pod对象,这就可以防止"误伤"。
需要说明的是,PodPreset里定义的内容,只会在Pod API对象被创建之前追加在这个对象本身上,而不会影响任何Pod的控制器的定义。 比如,我们现在提交的是一个nginx-deployment,那么这个Deployment对象本身是永远不会被PodPreset改变的,被修改的只是这个Deployment创建出来的所有Pod。这一点请务必区分清楚。
- NodeName 一旦 Pod 的这个字段被赋值,Kubernetes 项目就会被认为这个 Pod 已经经过了调度,调度的结果就是赋值的节点名字。所以,这个字段一般由调度器负责设置,但用户也可以设置它来“骗过”调度器,当然这个做法一般是在测试或者调试的时候才会用到。
本文发表于 0001-01-01,最后修改于 0001-01-01。
本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。