我们再深入浅出的了解一下 Pod,Pod 只是一个逻辑概念,Kubernetes 真正处理的,还是宿主机操作系统上 Linux 容器的 NameSpace 和 Cgroups,而并不存在一个所谓的 Pod 的边界或者隔离环境。Pod 是一组共享了某些资源的容器,Pod 里的所有容器共享的是一个 Network NameSpace,并且可以声明共享同一个 Volume。在 Kubernetes 里,Pod 的实现需要使用一个中间容器,这个容器叫做 Infra 容器(Infra 容器(k8s.gcr.io/pause)占用极少的资源,它的镜像时用汇编语言编写的,永远处于“暂停”状态的容器)。在 Pod 中 Infra 容器永远都是第一个被创建的容器,而其他用户自定义的容器,则通过 join Network NameSpace 的方式,与 Infra 容器关联在一起。对于同一个 Pod 里面的所有用户容器,它们的进出流量都是通过 Infra 容器完成的。凡是调度、网络、存储、以及安全相关的属性,基本上都是 Pod 级别的。
1. 数据卷
Container 中的文件在磁盘上是临时存放的,这给 Container 中运行的较重要的应用程序带来一些问题:
- 一是当容器崩溃时文件丢失;
- 二是同一 Pod 中运行的多个容器无法共享文件;
Kubernetes 的数据卷(Volume)便是为了解决上述问题的。数据卷有很多种类型,以下几种为常用的卷类型。
1.1 主机路径(hostPath)
hostPath 允许将节点(宿主机)上的文件系统挂载到 Pod 中的容器里面去,如果 Pod 需要使用节点上的文件,可以使用该类型。需要注意的是,默认调度策略下 Pod 在重建时,可能会调度到集群中的其它节点上,此时因调度的节点不同,所以无法获取"先前节点"上所保存的数据。
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-pd
volumeMounts:
- mountPath: /test-pd
name: test-volume
readOnly: true
volumes:
- name: test-volume
hostPath:
# 宿主上目录位置
path: /data
# 此字段为可选
type: Directory
1.2 临时路径(emptyDir)
emptyDir 可以让 Pod 中的所有容器共享同一文件系统,常用于作为临时目录、或缓存使用。它会在 Pod 被分配到节点上时被创建,并跟随 Pod 的生命中后期,即该该 Pod 一直运行,卷就会一直存在,当它被删除时卷也会同时删除。
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
readOnly: true
volumes:
- name: cache-volume
emptyDir: {}
1.3 配置项(configMap)
configMap 卷提供了向 Pod 注入 ConfigMap(配置)数据的方法。 ConfigMap 对象中存储的每条数据都会以一个个文件形式保存在指定的容器挂载路径中,此时你可以在容器中读取这些配置数据。
apiVersion: v1
kind: Pod
metadata:
name: configmap-vol-demo
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: config-vol
mountPath: /etc/config
readOnly: true
volumes:
- configMap:
defaultMode: 420
name: lytest
name: config-vol
1.4 密钥(secret)
secret 卷用来给 Pod 传递敏感信息(例如密码)。你可以将 Secret 存储在 Kubernetes API 服务器上,然后以文件的形式挂在到 Pod 中,无需直接与 Kubernetes 耦合。
apiVersion: v1
kind: Pod
metadata:
name: secret-vol-demo
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: secret-vol
mountPath: /etc/secret
readOnly: true
volumes:
- secret:
defaultMode: 420
secretName: ly-secret
name: secret-vol
1.5 存储声明(PVC)
...待补充
2. 节点选择
节点选择的作用是用来控制 Pod 的调度的,它是通过 Pod 的 spec.nodeSelector
字段来控制的。
自动分配
默认选项,在该模式下将由 Kubernetes 根据各节点的运行时状态自动分配。
指定节点
Pod 的 spec.nodeName
字段属性可以让其仅调度到指定节点。在 OpenKube 中我们在创建工作负载时通过指定节点来进行配置。
匹配节点
除了指定调度节点外,Kubernetes 还支持通过配置 spec.nodeSelector
字段来匹配节点的标签,从而决定是否将 Pod 调度到该节点上。在 OpenKube 中我们在创建工作负载时通过匹配节点来进行配置。
3. 升级策略
工作负载的 .spec.strategy
字段值用于指定新 Pod 替换旧 Pod 的策略配置。不同的工作负载类型所支持的策略有所不同:
Deployment
-
滚动升级(RollingUpdate):滚动升级将逐步用新版本的实例替换旧版本的实例,升级的过程中,业务流量会同时负载均衡分布到新老的实例上,因此业务不会中断。它支持以下选项来控制滚动更新过程:
- 最大峰值(maxSurge):用来指定可以创建的超出期望 Pod 个数的 Pod 数量。此值可以是绝对数(例如 5)或所需 Pods 的百分比(例如 10%)。 如果最大不可用副本数为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。 此字段的默认值为 25%。
- 最大不可用副本数(maxUnavailable):用来指定更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如 5),也可以是所需 Pod 的百分比(例如 10%)。百分比值会转换成绝对数并去除小数部分。 如果最大超出副本数为 0,则此值不能为 0。 默认值为 25%。
- 替换升级(Recreate):该策略会在创建新版本的 Pod 之前,"杀死"所有现有的 Pod。
StatefulSet
-
滚动升级(RollingUpdate):滚动升级会删除和重建 StatefulSet 中的每个 Pod, 它将按照与 Pod 终止相同的顺序(从最大序号到最小序号)进行,每次更新一个 Pod。 它会等到被更新的 Pod 进入 Running 和 Ready 状态,然后再更新其前身。它支持以下选项来控制滚动更新过程:
- 分区(partition):在大多数情况下,你不需要使用分区,但如果你希望进行阶段更新,执行金丝雀或分阶段上线,那么可以配置分区序号。当 StatefulSet 被更新时,所有序号大于等于该分区序号的 Pod 都会被更新,所有小于该分区号的 Pod 都不会更新(即使它们被删除后重建)。
删除容器时更新(OnDelete):在该策略下控制器将不会自动更新 StatefulSet 的 Pod,必须手动删除 Pod 以便让控制器创建新的 Pod。
DaemonSet
-
滚动升级(RollingUpdate):与 Deployment 滚动更新策略效果一样。
- 最大不可用副本数(maxUnavailable):与 Deployment 滚动更新策略的最大不可用副本数作用相同。
- 最小就绪时间(maxUnavailable):用于指定新创建的 Pod 在没有任意容器崩溃情况下的最小就绪时间, 只有超出这个时间 Pod 才被视为可用(默认值为 0,Pod 在准备就绪后立即将被视为可用)。但其本身并不属于更新策略的配置,它可作用于所有类型的工作负载,只是由于产品设计的原因将其置于此处。
删除容器时更新(OnDelete):在该策略下控制器将不会自动更新 DaemonSet 的 Pod,必须手动删除 Pod 以便让控制器创建新的 Pod。
4. 自动伸缩
自动伸缩(Horizontal Pod Autoscaler)可以根据 CPU 利用率自动扩缩 ReplicationController、 Deployment、ReplicaSet 或 StatefulSet 中的 Pod 数量 。
5. 容忍
可以通过 Pod 的 spec.tolerations
字段来添加了容忍,允许其部署在匹配特定污点的节点上。可以使用两种操作符来匹配污点,分别是:
- Exists:与污点信息完全匹配(即与污点的 key-value 值相等)
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 3600
- Equal:判断污点信息存在性(与污点的 key 值相等)
tolerations:
- key: "key2"
operator: "Exists"
effect: "NoSchedule"
tolerationSeconds: 3600
在实践中,某些有着特殊硬件的节点需要专用于运行一类有着此类硬件资源需求的 Pod 对象时,例如,那些有着 SSD 或 GPU 的设备,酒应该为其添加污点信息以排除其他的 Pod 对象。
6. 亲和性
待完善
一般情况下我们部署的 Pod 是通过集群自动调度选择某个节点的,默认情况下调度器考虑的是资源足够,并且负载尽量平均。但是有的时候我们需要能够更加细粒度的去控制 Pod 的调度,比如我们希望服务的 Pod 尽量跑在一些带有特定标签的节点上,或希望能某些服务的 Pod 调度到同样的节点上,这就需要用到 Kubernetes 里面的一个概念 --- 亲和性(Affinity)。
亲和性有又分为节点亲和性和应用亲和性两种类型,每种类型又支持两种策略:
- 软策略:尽量满足调度要求,若无匹配的节点,则会忽略这条规则,继续完成调度过程。
- 硬策略:即必须满足我的要求,如果没有满足条件的节点的话,就不断重试直到满足条件为止。
6.1 节点亲和性(nodeAffinity)
节点亲和性(nodeAffinity )是通过匹配节点的标签来决定是否调度到该节点**,但相比节点选择( nodeSelector )更加灵活,它可以进行一些简单的逻辑组合,不只是简单的相等匹配。
6.2 应用亲和性(podAffinity)/ 应用反亲和性(podAntiAffinity)
节点亲和性是从 Pod 和 Node 的关系角度进行匹配,而应用亲和性(podAffinity )与应用反亲和性(podAntiAffinity )是从 Pod 与 Pod 方面,即应用与应用的角度去考虑。它会基于已在节点上运行 Pod 的标签(LabelSelector)来约束 Pod 可以调度到的节点,并且也是基于命名空间限定的,而不是节点上的标签。
例如,因为服务 A 和服务 B 需要在同一节点上协同工作;或是我们希望系统数据服务 C 和数据服务 D 尽量分开,部署到不同节点上,以免主机或机房出问题时,不会导致应用完全不可用,等等这些场景下就需要用到应用亲和性了。
7. 终止等待时间
可以通过 spec.terminationGracePeriodSeconds
字段来设置终止等待时间,该配置的作用是当集群向 Pod 发送 SIGTERM 信号后,等待结束的时间。当超过该时间后,集群会强制结束该 Pod。因此该时间是 Kubernetes 集群给你程序预留的最后缓冲时间,来处理关闭之前的操作。
8. 主机别名
Kubernetes 中不同服务之间可以通过 "service-name" 域名来相互访问,这是通过集群中的 CoreDNS 组件来完成域名解析工作的。但当我们想在 Pod 上增加一些域名解析时,可以通过 Pod 的 spec.hostAliases
字段往 /etc/hosts
文件中添加域名解析(集群需 1.7.x 及以上版本)。
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
hostAliases:
– ip: "10.1.2.2"
hostnames:
– "mc.local"
– "rabbitmq.local"
containers:
– name: cathosts
image: busybox
...
9. DNS 策略与配置
我们可以通过 Pod 声明中的 spec.dnsPolicy
字段来设置 DNS 策略,目前支持以下类型:
-
(继承节点)Default:Pod 从运行所在的节点继承名称解析配置,即将宿主机的
/etc/resolv.conf
内容挂载到容器中。 - (集群优先)ClusterFirst:默认策略,会预先把 kube-dns(或 CoreDNS)的信息当作预设参数写入到该 Pod 内的 DNS 设置。
- (宿主机与 Kubernetes 共存)ClusterFirstWithHostNet:集群 DNS 优先,并伴随着使用宿主机 DNS 网络设置。
-
(无策略)None:不使用任何预设的 DNS 配置,此时必须通过
spec.dnsConfig
字段来配置 DNS 信息。
默认 DNS 策略为 ClusterFirst
在 OpenKube 中 DNS 策略设置目前仅支持 ClusterFirst 与 ClusterFirstWithHostNet 两种类型。除了采用策略预设的配置,Pod 的 spec.dnsConfig
字段可以让用户对 Pod 的 DNS 设置进行更多控制,该字段是可选的,它可以与任何 dnsPolicy
设置一起使用。 但是当 Pod 的 dnsPolicy
设置为 "None
" 时,必须指定 dnsConfig
字段。
用户可以在 dnsConfig
字段中指定以下属性:
-
nameservers
:将用作于 Pod 的 DNS 服务器的 IP 地址列表(最多可以指定 3 个 IP 地址),当 Pod 的dnsPolicy
设置为 "None
" 时, 列表必须至少包含一个 IP 地址,否则此属性是可选的。 所列出的服务器将合并到从指定的 DNS 策略生成的基本名称服务器,并删除重复的地址。 -
searches
:用于在 Pod 中查找主机名的 DNS 搜索域的列表(最多允许 6 个搜索域)。此属性是可选的, 指定此属性时,所提供的列表将合并到根据所选 DNS 策略生成的基本搜索域名中。 重复的域名将被删除。 -
options
:可选的对象列表,其中每个对象可能具有name
属性(必需)和value
属性(可选)。 此属性中的内容将合并到从指定的 DNS 策略生成的选项,重复的条目将被删除。
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "ClusterFirst"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options:
- name: ndots
value: "2"
10. 宿主机配置
NameSpace 是 Linux 内核用于隔离资源的机制,通过 NameSpace 可以让一些进程只能看到与自己相关的一部分资源,而不受其它进程影响。其实现方式是把一个或多个进程的相关资源指定在同一个 NameSpace 中。
容器本质上是把系统中为同一个业务目标服务的相关进程合成一组,放在同一个叫 Namespace 空间中。同一个 Namespace 中的进程能够互相通信,在这个 Namespace 中可以拥有自己独立的主机名、进程 ID 系统、IPC、网络、文件系统、用户等等资源。在某种程度上,实现了一个简单的虚拟:让一个主机上可以同时运行多个互不感知的系统。
10.1 是否使用主机 IPC
Pod 声明文件的 spec.hostIPC
字段值表示是否使用主机 PID。若选择"是",此时 Pod 中所有的容器会与宿主机处于同一个 IPC NameSpace 空间中,可直接与宿主机进程进行通信。
10.2 是否使用主机 PID
Pod 声明文件的 spec.hostPID
字段值表示是否使用主机 PID。若选择"是",此时 Pod 中所有的容器会与宿主机处于同一个 PID NameSpace 空间中 。当我们在 Pod 中的容器执行 ps -aux
命令时,可以查看到宿主机里正在运行的所有进程。
10.3 是否使用主机网络
Pod 声明文件的 spec.hostNetwork
字段值表示是否使用主机网络。若选择"是",Pod 中所有的容器会直接使用宿主机的网络,即它们与宿主机处于同一个 NET NameSpace。
10.4 是否共享进程命名空间
Pod 声明文件的 spec.shareProcessNamespace
字段值表示是否共享进程命名空间。若选择 "是" 时,Pod 里的每个容器的进程,对于所有容器来说都是可见的,即它们共享了同一个 PID Namespace。
11. 安全上下文
默认情况下,工作负载会以 root 用户运行容器(可通过在容器中执行
ps -aux
命令查看 USER 列信息),但在某些场景下这会带来安全问题,此时我们需要控制容器中运行的用户与权限,避免以超级用户身份运行容器。
安全上下文(Security Context)定义 Pod 或 Container 的特权与访问设置。目前 OpenKube 支持在创建工作负载时对安全上下文进行以下配置。
11.1 以非 root 用户身份运行
默认情况下,是否以非 root 用户身份运行的单选框值为"否",此时 Pod 声明文件中的 spec.securityContext.runAsNonRoot
字段值为 false
,表示以 root 身份运行容器。但当我们要以指定用户运行容器时,应勾选"是"(即字段值为 true
)。
除此之外,还需要配置 spec.securityContext.runAsUser
与 spec.securityContext.runAsGroup
字段,即分别设置运行容器的用户 ID 与主用户组 ID。例如我们设置以 ID 为 1
的用户运行容器,其用户组 ID 为 2
。
那么如何确认我们所指定的用户与用户组是否生效呢?可以在容器中执行 whoami
命令获取当前用户名,与用户详情配置文件 /etc/passwd
信息进行比较来确认当前用户。而用户组可以通过 group
命令获取当前用户组,与用户组详情文件 /etc/group
信息进行比较。
fsGroup
该配置项为 Pod 声明中的 spec.fsGroup
字段,用于指定卷的附加用户组 ID。
补充用户组
该配置项为 Pod 声明中的 spec.supplementalGroups
字段,一个用户可以加入很多组,但是其中只有一个是主组,而配置项用于指定额外的用户组 ID。
11.2 配置内核参数
sysctl 参数位于 /proc/sys
目录下,它们是系统运行时的内核参数。Kubernetes 支持我们在 Pod 的声明文件中去配置内核参数。这些配置将会影响容器运行时的系统环境,例如设置联网功能,IP 转发以及源路由检查等。