- 1. 以 gpushare-device-plugin 为例,探究 Resource yaml 配置
1. 以 gpushare-device-plugin 为例,探究 Resource yaml 配置
我理解 k8s 中最核心的 resource 就是 Pod,创建 Pod 需要先生成 yaml 文件,然后通过 kubectl apply -f example-pod.yaml
来创建 pod。
k8s 中处理 pod 的内部流程应该来说会比较复杂,才搞两周的我肯定是没有这个实力;而熟悉创建 pod 的 yaml 配置相对比较容易,只有先会用,才能逐步的了解具体的实现细节,由浅入深由表及里这个方法论是不会错误的。
因此,本文主要分析 type Pod struct
的结构,晦涩的字段暂且不管,只抓核心字段,然后再分析 type DaemonSet struct
,最后通过创建 gpushare-device-plugin
的 device-plugin-ds.yaml
来验证我们的学习效果
1.1. Pod 结构体分析
代码见:kubernetes/vendor/k8s.io/api/core/v1/types.go
type Pod struct { /* 注意,metav1.TypeMeta 是类型,但是成员变量却不存在?这不合常理 查询了好久才发现,这种叫做 promoted field,还有一个孪生姐妹 anonymous field 参见:https://stackoverflow.com/questions/28014591/nameless-fields-in-go-structs metav1.TypeMeta 在 kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go 中 type TypeMeta struct { Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` } 注意,`json:",inline"` 这个被称为 go struct field tag: 参见:https://medium.com/rungo/structures-in-go-76377cc106a2 json 代表对 json unpack/pack 处理的 metadata protobuf 是对 protobuf 处理的 metadata inline 比较特殊,https://github.com/isayme/blog/issues/15,主要是为了从 json 中读取数据后去掉当前层,直接内嵌进去 注意,Pod 实例可以直接使用 metav1.TypeMeta 实例的成员:pod.Kind、pod.APIVersion,但是 yaml 的配置应该是: apiVersion: extensions/v1beta1 kind: DaemonSet 这是和 inline 以及 protobuf:"bytes,1,opt,name=kind" 相关的,请注意 */ metav1.TypeMeta `json:",inline"` /* 注意,对于 metav1.ObjectMeta,代码中调用时 Pod.Name、Pod.Namespace,但是对于 yaml 文件却是: metadata: name: gpushare-device-plugin-ds namespace: kube-system 这是因为有 protobuf tag 的指示,注意 metav1.ObjectMeta 不含 inline,因此和 metav1.TypeMeta 的 yaml 有不同,请一定注意 */ metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` }
1.1.1. type TypeMeta struct
代码见:kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go
只有两个参数:
- apiVersion
- kind
1.1.2. type ObjectMeta struct
代码见:kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go
常用的字段如下:
- name
- labels
- annotations
- namespace
完整分析如下:
type ObjectMeta struct { // Pod 的名字(常用) /* metadata: name: gpushare-device-plugin-ds */ Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` // 打标签(常用) https://www.jianshu.com/p/cd6b4b4caaab /* metadata: labels: app: nginx release: stable */ Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"` // annotations (常用)k8s 内部组件对这个会比较关心(偏系统),labels 是用户对其关心(偏用户),前者不需要自己设置,k8s 会自动设置,达到某种效果 Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` // kube-system,不填就是 default(常用) /* metadata: name: gpushare-device-plugin-ds namespace: kube-system */ Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` // 不常用 GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"` // 不常用 SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"` // 不常用 UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"` // 不常用,系统填写 https://k8smeetup.github.io/docs/reference/api-concepts/ ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"` // 不常用 Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"` // 不常用,系统填写 CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"` // 不常用,系统填写 DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"` // 优雅的被删除,不常用 DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"` // 不常用,垃圾收集相关:https://kubernetes.io/zh/docs/concepts/workloads/controllers/garbage-collection/ OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"` // 不常用,1.15 将被废弃 Initializers *Initializers `json:"initializers,omitempty" protobuf:"bytes,16,opt,name=initializers"` // 不常用,垃圾回收相关:https://draveness.me/kubernetes-garbage-collector Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"` // 不常用,有多 cluster 的时候,可能需要指定 cluster ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"` // 不常用,不了解 ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"` }
1.1.3. type PodSpec struct
代码:kubernetes/vendor/k8s.io/api/core/v1/types.go
我们提取几个比较重要的,核心的,容易搞不清楚的:
- volumes
- InitContainers
- Containers
其他的详细分析见下文,在这里提供一个非常棒的网站Kubernetes 指南,非常全面,对我帮助很大
// PodSpec is a description of a pod. type PodSpec struct { // 常用,volumes,下文详细分析 Volumes []Volume `json:"volumes,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,1,rep,name=volumes"` // 常用,initContainers,下文详细分析 InitContainers []Container `json:"initContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,20,rep,name=initContainers"` // 常用,containers,下文详细分析 Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=containers"` // 常用,重启策略 Always、OnFailure、Never RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" protobuf:"bytes,3,opt,name=restartPolicy,casttype=RestartPolicy"` // 不常用,退出等待多少时间~ TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty" protobuf:"varint,4,opt,name=terminationGracePeriodSeconds"` // 不常用,和重试有关,也许是标志失败Pod的重试最大时间,超过这个时间不会继续重试 ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty" protobuf:"varint,5,opt,name=activeDeadlineSeconds"` // 不常用,DNS 配置,ClusterFirstWithHostNet、ClusterFirst、Default、None DNSPolicy DNSPolicy `json:"dnsPolicy,omitempty" protobuf:"bytes,6,opt,name=dnsPolicy,casttype=DNSPolicy"` // 常用,是一个供用户将 Pod 与 Node 进行绑定的字段 /* spec: nodeSelector: disktype: ssd */ NodeSelector map[string]string `json:"nodeSelector,omitempty" protobuf:"bytes,7,rep,name=nodeSelector"` /* 常用,pod 也可以访问 apiserver,但是用什么权限呢?ServiceAccountName 就是这个东西 首先需要创建一个 ServiceAccount: apiVersion: v1 kind: ServiceAccount metadata: name: gpushare-device-plugin namespace: kube-system 然后,可以直接使用: spec: serviceAccount: gpushare-device-plugin */ ServiceAccountName string `json:"serviceAccountName,omitempty" protobuf:"bytes,8,opt,name=serviceAccountName"` // 不常用,废弃 DeprecatedServiceAccount string `json:"serviceAccount,omitempty" protobuf:"bytes,9,opt,name=serviceAccount"` // 不常用,serviceAccount 是否自动挂载?? AutomountServiceAccountToken *bool `json:"automountServiceAccountToken,omitempty" protobuf:"varint,21,opt,name=automountServiceAccountToken"` // 常用,但不太应该用!指定调度到哪个 node 上,跳过了调度器! NodeName string `json:"nodeName,omitempty" protobuf:"bytes,10,opt,name=nodeName"` // 常用,isolation,是否用宿主机的网络(namespace)一般 false HostNetwork bool `json:"hostNetwork,omitempty" protobuf:"varint,11,opt,name=hostNetwork"` // 常用,isolation,是否能看到宿主机的进程 (pid namespace)一般 false HostPID bool `json:"hostPID,omitempty" protobuf:"varint,12,opt,name=hostPID"` // 常用,isolation,是否能看到宿主机 IPC (ipc namespace)一般 false HostIPC bool `json:"hostIPC,omitempty" protobuf:"varint,13,opt,name=hostIPC"` // 常用,是否 pod 下面多个 container 共享一个 PID namespace // 置为 true 后,container 能互相看到对方的进程 ShareProcessNamespace *bool `json:"shareProcessNamespace,omitempty" protobuf:"varint,27,opt,name=shareProcessNamespace"` // 常用,参见 https://feisky.gitbooks.io/kubernetes/concepts/security-context.html // 简单说,启用 selinux?限制端口,总之,限制不可信容器的使用 // 暂时不用关注 SecurityContext *PodSecurityContext `json:"securityContext,omitempty" protobuf:"bytes,14,opt,name=securityContext"` // 常用,下载镜像也得有权限吧?不过默认是用 default serviceAccount 的 ImagePullSecrets ImagePullSecrets []LocalObjectReference `json:"imagePullSecrets,omitempty" patchStrategy:"merge" patchMergeKey:"name" // 常用,hostname // If specified, the fully qualified Pod hostname will be "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>". Hostname string `json:"hostname,omitempty" protobuf:"bytes,16,opt,name=hostname"` // 同上 Subdomain string `json:"subdomain,omitempty" protobuf:"bytes,17,opt,name=subdomain"` // 常用,调度相关,共分 3 级,NodeAffinity、PodAffinity、PodAntiAffinity // 详见:https://feisky.gitbooks.io/kubernetes/components/scheduler.html Affinity *Affinity `json:"affinity,omitempty" protobuf:"bytes,18,opt,name=affinity"` // 不常用,调度相关,指定调度器的名字 SchedulerName string `json:"schedulerName,omitempty" protobuf:"bytes,19,opt,name=schedulerName"` // 常用,https://feisky.gitbooks.io/kubernetes/components/scheduler.html#taints-%E5%92%8C-tolerations // 不调度到哪台机器 Tolerations []Toleration `json:"tolerations,omitempty" protobuf:"bytes,22,opt,name=tolerations"` // 常用,指定 /etc/hosts,非常重要 /* spec: hostAliases: - ip: "10.1.2.3" hostnames: - "foo.remote" - "bar.remote" cat /etc/hosts # Kubernetes-managed hosts file. 127.0.0.1 localhost ... 10.244.135.10 hostaliases-pod 10.1.2.3 foo.remote 10.1.2.3 bar.remote */ HostAliases []HostAlias `json:"hostAliases,omitempty" patchStrategy:"merge" patchMergeKey:"ip" protobuf:"bytes,23,rep,name=hostAliases"` // 不重要,调度相关,指定被调度的优先级 // PriorityClass 是一个 resource 需要自己去创建,创建后在这里指定 // https://feisky.gitbooks.io/kubernetes/concepts/pod.html#%E4%BC%98%E5%85%88%E7%BA%A7 /* apiVersion: scheduling.k8s.io/v1alpha1 kind: PriorityClass metadata: name: high-priority value: 1000000 globalDefault: false description: "This priority class should be used for XYZ service pods only." */ PriorityClassName string `json:"priorityClassName,omitempty" protobuf:"bytes,24,opt,name=priorityClassName"` // 不常用,调度,Priority Admission Controller is enabled 后失效(读 PriorityClassName 中的 value),否则生效。越大越优先 Priority *int32 `json:"priority,omitempty" protobuf:"bytes,25,opt,name=priority"` // 常用,写死 resolve.conf,我觉得 DNSConfig 和 DNSPolicy 类似,前者是用户写死,后者是根据用户选的策略系统自己填写 DNSConfig *PodDNSConfig `json:"dnsConfig,omitempty" protobuf:"bytes,26,opt,name=dnsConfig"` // 不常用,pod 启动后额外的检测,只有通过才是 ready // v1.11 引入:https://godleon.github.io/blog/Kubernetes/k8s-Pod-Overview/ ReadinessGates []PodReadinessGate `json:"readinessGates,omitempty" protobuf:"bytes,28,opt,name=readinessGates"` // 不常用,支持多 CRI,v1.12 引入,比如 Pod 包含 Kata Containers/gVisor + runc 的多个容器 RuntimeClassName *string `json:"runtimeClassName,omitempty" protobuf:"bytes,29,opt,name=runtimeClassName"` // 不常用,不详 EnableServiceLinks *bool `json:"enableServiceLinks,omitempty" protobuf:"varint,30,opt,name=enableServiceLinks"` // 不常用,新搞出来的一个资源抢占的内容??? PreemptionPolicy *PreemptionPolicy `json:"preemptionPolicy,omitempty" protobuf:"bytes,31,opt,name=preemptionPolicy"` }
1.1.3.1. type Volume struct
代码见:kubernetes/vendor/k8s.io/api/core/v1/types.go
这里的 Volume 我理解是创建 volume,因此需要 volume name + volume source。
我们常用的是使用宿主机的地址映射到 container 中,作为卷,或者是映射宿主机的设备进入 container。
除此之外,volumes 更多的 source 是 ceph、cinder、nfs、iscsi 等,这样才具有持久化的能力。这部分十分复杂,我们简单分析下
核心参数:
- name
- volumeSource
type Volume struct { // volume 名字 Name string `json:"name" protobuf:"bytes,1,opt,name=name"` // Volume 的资源来源于哪里? VolumeSource `json:",inline" protobuf:"bytes,2,opt,name=volumeSource"` }
1.1.3.2. type VolumeSource struct
代码见:kubernetes/vendor/k8s.io/api/core/v1/types.go
VolumeSource 我理解就是 k8s 支持的后端存储有哪些,最常见的就是把宿主机磁盘挂载到容器中,作为 volume
type VolumeSource struct { // 映射宿主机目录到 container 中,注意,这里仅仅是创建资源,映射在 container 的 spec 中的 /* type HostPathVolumeSource struct { // 宿主机路径,可以是 device、dir、socket 等 Path string `json:"path" protobuf:"bytes,1,opt,name=path"` // 支持很多类型,File、socket、Device,最保险的使用默认值 "",一般来说兼容一切 Type *HostPathType `json:"type,omitempty" protobuf:"bytes,2,opt,name=type"` } */ /* 举个例子: spec: volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins */ HostPath *HostPathVolumeSource `json:"hostPath,omitempty" protobuf:"bytes,1,opt,name=hostPath"` // 创建一个宿主机临时目录,给 container 用,这可能涉及到多个 container 之间的数据交互的配合,也可能是 container 本身受到 10GB 大小的限制,可能日志会写不下 /* 例子: spec: volumes: - name: nginx-vol emptyDir: {} */ EmptyDir *EmptyDirVolumeSource `json:"emptyDir,omitempty" protobuf:"bytes,2,opt,name=emptyDir"` // 重点在 ceph 上,但是目前没有这个实力 NFS *NFSVolumeSource `json:"nfs,omitempty" protobuf:"bytes,7,opt,name=nfs"` RBD *RBDVolumeSource `json:"rbd,omitempty" protobuf:"bytes,11,opt,name=rbd"` PersistentVolumeClaim *PersistentVolumeClaimVolumeSource `json:"persistentVolumeClaim,omitempty" protobuf:"bytes,10,opt,name=persistentVolumeClaim"` // 很多其他的 volume 数据源 ...
1.1.4. type Container struct
initcontainer 和 container 都是 struct []Container 类型的,因此只需要分析 Container 即可,Container 是核心中的核心!
我们总结了常用的参数:
- name
- image
- command
- args
- envs
- ports
- resources
- volumeMounts
- lifecycle
- preStop
- postStart
- devicePath
- imagePullPolicy
- stdin
- tty
详解如下:
type Container struct { // 常用,容器得有名字吧? Name string `json:"name" protobuf:"bytes,1,opt,name=name"` // 常用,容器得用镜像吧? /* 例子 spec: containers: - name: nginx image: nginx:1.8 */ Image string `json:"image,omitempty" protobuf:"bytes,2,opt,name=image"` // 常用,启动容器的命令行,会覆盖 docker 本身的 entrypoint /* spec: containers: - command: - gpushare-device-plugin-v2 - -logtostderr - --v=5 - --memory-unit=GiB */ Command []string `json:"command,omitempty" protobuf:"bytes,3,rep,name=command"` // 常用,命令行参数 /* spec: containers: - name: command-demo-container image: debian command: ["printenv"] args: ["HOSTNAME", "KUBERNETES_PORT"] */ Args []string `json:"args,omitempty" protobuf:"bytes,4,rep,name=args"` // 常用,工作目录 WorkingDir string `json:"workingDir,omitempty" protobuf:"bytes,5,opt,name=workingDir"` // 常用,pod 对外的 port /* spec: containers: - ports: - containerPort: 80 */ Ports []ContainerPort `json:"ports,omitempty" patchStrategy:"merge" patchMergeKey:"containerPort" protobuf:"bytes,6,rep,name=ports"` // 不常用,可能是 env 存放在 configmap resource 中,这里引用一下 EnvFrom []EnvFromSource `json:"envFrom,omitempty" protobuf:"bytes,19,rep,name=envFrom"` // 传入容器的环境变量,比 EnvFrom 直接 Env []EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,7,rep,name=env"` // 常用,需要的资源 /* spec: containers: - resources: limits: memory: "300Mi" cpu: "1" requests: memory: "300Mi" cpu: "1" */ Resources ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,8,opt,name=resources"` // 常用,volume 映射,type VolumeMount struct 还需要仔细研究下 /* spec: containers: - volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins */ VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath" protobuf:"bytes,9,rep,name=volumeMounts"` // 常用,但需要研究,貌似是 blockdevice,需要先创建 PVC /* apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: accessModes: - ReadWriteMany volumeMode: Block storageClassName: my-sc resources: requests: storage: 1Gi */ /* apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: my-container image: busybox command: - sleep - “3600” volumeDevices: - devicePath: /dev/block name: my-volume imagePullPolicy: IfNotPresent volumes: - name: my-volume persistentVolumeClaim: claimName: my-pvc */ VolumeDevices []VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath" // 重要,但暂时不看,健康检查 // https://feisky.gitbooks.io/kubernetes/introduction/201.html#%E5%81%A5%E5%BA%B7%E6%A3%80%E6%9F%A5 LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,10,opt,name=livenessProbe"` // 重要,但暂时不看,监控检查 // https://feisky.gitbooks.io/kubernetes/introduction/201.html#%E5%81%A5%E5%BA%B7%E6%A3%80%E6%9F%A5 ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"` // 在 poststart 和 prestop 的时候可以插入执行的内容 /* spec: containers: - name: lifecycle-demo-container image: nginx lifecycle: postStart: exec: command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message" preStop: exec: command: ["/usr/sbin/nginx","-s","quit"] */ Lifecycle *Lifecycle `json:"lifecycle,omitempty" protobuf:"bytes,12,opt,name=lifecycle"` // 详见:https://k8smeetup.github.io/docs/tasks/debug-application-cluster/determine-reason-pod-failure/ // 貌似不用特别管 TerminationMessagePath string `json:"terminationMessagePath,omitempty" protobuf:"bytes,13,opt,name=terminationMessagePath"` // 同上,注意 TerminationMessagePolicy TerminationMessagePolicy `json:"terminationMessagePolicy,omitempty" // 常用,类型 Always、Never、IfNotPresent ImagePullPolicy PullPolicy `json:"imagePullPolicy,omitempty" protobuf:"bytes,14,opt,name=imagePullPolicy,casttype=PullPolicy"` // 同 Pod 中的 SecurityContext SecurityContext *SecurityContext `json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"` // 是否开启 stdin Stdin bool `json:"stdin,omitempty" protobuf:"varint,16,opt,name=stdin"` // 不常用,估计只能同时允许一个 stdin 的连接 StdinOnce bool `json:"stdinOnce,omitempty" protobuf:"varint,17,opt,name=stdinOnce"` // 是否开启 tty TTY bool `json:"tty,omitempty" protobuf:"varint,18,opt,name=tty"` }
1.1.5. type PodStatus struct
PodStatus 全部由系统来填写,和创建无关,我们仅仅需要了解即可
总体来说
- phase (Pod 状态)
- 有 Pod 下面所有 container 的状态,有原因,有一些奇怪的字段。
type PodStatus struct { // 系统填写,Pending、Running、Succeeded、Failed、Unknown 就五种状态 Phase PodPhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=PodPhase"` // 系统填写,Pod 下多个 container 的状态,包括 /* type PodCondition struct { // ContainersReady,Initialized,Ready,PodScheduled Type PodConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=PodConditionType"` // True, False, Unknown. 不知道什么意思 Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"` // 上一次提交状态的时间? LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"` // 上一次提交状态变化的时间? LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"` // 为啥会状态会变化 Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"` // 为啥状态会变化给用户看的 Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` } */ Conditions []PodCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,2,rep,name=conditions"` // 人能看懂的为啥处于这个状态的原因 Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"` // 工程师能看懂的为啥处于这个状态的原因 Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` // 和抢占有关系,不懂 https://www.jianshu.com/p/bdcb9528a8b1 NominatedNodeName string `json:"nominatedNodeName,omitempty" protobuf:"bytes,11,opt,name=nominatedNodeName"` // 调度该 pod 的 scheduler 的 IP HostIP string `json:"hostIP,omitempty" protobuf:"bytes,5,opt,name=hostIP"` // Pod 的 IP 地址? PodIP string `json:"podIP,omitempty" protobuf:"bytes,6,opt,name=podIP"` // 启动时间?,具体再议 StartTime *metav1.Time `json:"startTime,omitempty" protobuf:"bytes,7,opt,name=startTime"` // initcontainer 的状态,因为它是最先启动的,它启动成功后,container 才能启动 InitContainerStatuses []ContainerStatus `json:"initContainerStatuses,omitempty" protobuf:"bytes,10,rep,name=initContainerStatuses"` // container 状态 ContainerStatuses []ContainerStatus `json:"containerStatuses,omitempty" protobuf:"bytes,8,rep,name=containerStatuses"` // QoS 相关的,可选 Guaranteed、Burstable、BestEffort QOSClass PodQOSClass `json:"qosClass,omitempty" protobuf:"bytes,9,rep,name=qosClass"` }
1.2. DaemonSet 结构体
代码:kubernetes/vendor/k8s.io/api/apps/v1/types.go
分析的过程中,一定要注意与 Pod 对比
type DaemonSet struct { // 同 Pod 第一个字段 metav1.TypeMeta `json:",inline"` // 同 Pod 第二个字段 metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // 需要仔细分析,类似于 PodSpec Spec DaemonSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` // 需要仔细分析,类似于 PodStatus Status DaemonSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` }
1.2.1. type DaemonSetSpec struct
代码:kubernetes/vendor/k8s.io/api/apps/v1/types.go
type DaemonSetSpec struct { // 该 DaemonSet 部署在哪些机器上?用 selector 来过滤 Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,1,opt,name=selector"` // 需要仔细分析 Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,2,opt,name=template"` // update 的策略,默认是 RollingUpdate /* type DaemonSetUpdateStrategy struct { // 两种升级策略 RollingUpdate 和 OnDelete Type DaemonSetUpdateStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type"` // 如果是 RollingUpdate,才生效,如果填数字 5,代表 5 个 5 个逐步升级,如果填 5%,则5% 5% 逐步升级,默认是 1 RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"` } */ UpdateStrategy DaemonSetUpdateStrategy `json:"updateStrategy,omitempty" protobuf:"bytes,3,opt,name=updateStrategy"` // 当 ready 后多少分钟,才认为该 DaemonSet 是 avaliable? MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` // 保存的历史的 checkpoint 有多少个,默认值是 10 个 RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"` }
1.2.2. type PodTemplateSpec struct
代码:kubernetes/vendor/k8s.io/api/core/v1/types.go
type PodTemplateSpec struct { // 同 Pod/DaemonSet 中第二个字段,不知道这是何意? metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // 同 Pod 中的 PodSpec Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` }
1.2.3. type DaemonSetStatus struct
代码:kubernetes/vendor/k8s.io/api/apps/v1/types.go
DaemonSetStatus 比 PodStatus 简单很多,毕竟它的定义就是仅仅在每个符合条件的 host 上部署一个 Pod。
type DaemonSetStatus struct { // 正在运行这个 daemonset 的 node 个数 CurrentNumberScheduled int32 `json:"currentNumberScheduled" protobuf:"varint,1,opt,name=currentNumberScheduled"` // 不该运行这个 daemonset 的 node 个数 NumberMisscheduled int32 `json:"numberMisscheduled" protobuf:"varint,2,opt,name=numberMisscheduled"` // 应当运行这个 daemonset 的 node 个数 DesiredNumberScheduled int32 `json:"desiredNumberScheduled" protobuf:"varint,3,opt,name=desiredNumberScheduled"` // 准备好运行这个 daemonset 的 node 个数 NumberReady int32 `json:"numberReady" protobuf:"varint,4,opt,name=numberReady"` // The most recent generation observed by the daemon set controller. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,5,opt,name=observedGeneration"` // 运行最新的 daemonset 的 node 个数 UpdatedNumberScheduled int32 `json:"updatedNumberScheduled,omitempty" protobuf:"varint,6,opt,name=updatedNumberScheduled"` // 好烦 NumberAvailable int32 `json:"numberAvailable,omitempty" protobuf:"varint,7,opt,name=numberAvailable"` NumberUnavailable int32 `json:"numberUnavailable,omitempty" protobuf:"varint,8,opt,name=numberUnavailable"` CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` // 同 PodCondition Conditions []DaemonSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,10,rep,name=conditions"` }
看一看这个 DaemonSet 的实际状态,和定义的 DaemonSetStatus 还真挺类似:
[root@k8s-master kubernetes]# kubectl describe daemonset gpushare-device-plugin-ds -n kube-system Name: gpushare-device-plugin-ds Selector: app=gpushare,component=gpushare-device-plugin,name=gpushare-device-plugin-ds Node-Selector: gpushare=true Labels: app=gpushare component=gpushare-device-plugin name=gpushare-device-plugin-ds Annotations: deprecated.daemonset.template.generation: 2 kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"DaemonSet","metadata":{"annotations":{},"name":"gpushare-device-plugin-ds","namespace":"kube-sy... Desired Number of Nodes Scheduled: 1 Current Number of Nodes Scheduled: 1 Number of Nodes Scheduled with Up-to-date Pods: 1 Number of Nodes Scheduled with Available Pods: 1 Number of Nodes Misscheduled: 0 Pods Status: 1 Running / 0 Waiting / 0 Succeeded / 0 Failed Pod Template: Labels: app=gpushare component=gpushare-device-plugin name=gpushare-device-plugin-ds Annotations: scheduler.alpha.kubernetes.io/critical-pod: Service Account: gpushare-device-plugin Containers: gpushare: Image: registry.cn-hangzhou.aliyuncs.com/acs/k8s-gpushare-plugin:v2-1.12-lihao-test Port: <none> Host Port: <none> Command: gpushare-device-plugin-v2 -logtostderr --v=5 --memory-unit=GiB Limits: cpu: 1 memory: 300Mi Requests: cpu: 1 memory: 300Mi Environment: KUBECONFIG: /etc/kubernetes/kubelet.conf NODE_NAME: (v1:spec.nodeName) Mounts: /var/lib/kubelet/device-plugins from device-plugin (rw) Volumes: device-plugin: Type: HostPath (bare host directory volume) Path: /var/lib/kubelet/device-plugins HostPathType: Events: <none>
1.3. 分析 gpushare-device-plugin 的 daemon 配置
按照其文档,gpushare-device-plugin
的 daemonset 配置是 device-plugin-ds.yaml
[root@k8s-master kubernetes]# cat device-plugin-ds.yaml apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: gpushare-device-plugin-ds namespace: kube-system spec: template: metadata: annotations: # 调度相关 scheduler.alpha.kubernetes.io/critical-pod: "" labels: # 调度相关 component: gpushare-device-plugin app: gpushare name: gpushare-device-plugin-ds # 就是 PodSpec spec: # 在 gpushare-device-plugin 的 device-plugin-rbac.yaml 中创建了该 serviceAccount serviceAccount: gpushare-device-plugin # 使用宿主机网络 hostNetwork: true # 其实我觉得最好在最外层 spec 下用 selector # 用 nodeSelector 也行,有 gpushare: true 的 node 才启动该 device plugin nodeSelector: gpushare: "true" containers: - image: registry.cn-hangzhou.aliyuncs.com/acs/k8s-gpushare-plugin:v2-1.12-lihao-test name: gpushare # 用 args 装 -logtostderr 没准更好 command: - gpushare-device-plugin-v2 - -logtostderr - --v=5 - --memory-unit=GiB # 资源用不多 resources: limits: memory: "300Mi" cpu: "1" requests: memory: "300Mi" cpu: "1" # 环境变量有这么几个 env: - name: KUBECONFIG value: /etc/kubernetes/kubelet.conf - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName # 权限限制 securityContext: # 不用放大权利 allowPrivilegeEscalation: false capabilities: # 不用任何额外的权限 drop: ["ALL"] # 把 grpc unix socket(device plugin)映射到容器中 volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins # 把 grpc 的 Unix socket 所在本地文件夹作为卷 volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins