2.2 主要概念 - Pod
Pod 是一组紧密关联的容器集合,它们共享 IPC、Network 和 UTS namespace,是 Kubernetes 调度的基本单位。Pod 的设计理念是支持多个容器在一个 Pod 中共享网络和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。
Pod 的特征
- 包含多个共享 IPC、Network 和 UTC namespace 的容器,可直接通过 localhost 通信
- 所有 Pod 内容器都可以访问共享的 Volume,可以访问共享数据
- 无容错性:直接创建的 Pod 一旦被调度后就跟 Node 绑定,即使 Node 挂掉也不会被重新调度(而是被自动删除),因此推荐使用 Deployment、Daemonset 等控制器来容错
- 优雅终止:Pod 删除的时候先给其内的进程发送 SIGTERM,等待一段时间(grace period)后才强制停止依然还在运行的进程
- 特权容器(通过 SecurityContext 配置)具有改变系统配置的权限(在网络插件中大量应用)
Kubernetes v1.8 还支持容器间共享 PID namespace,需要 docker >= 1.13.1,并配置 kubelet
--docker-disable-shared-pid=false
。
Pod 定义
通过 yaml 或 json 描述 Pod 和其内 Container 的运行环境以及期望状态,比如一个最简单的 nginx pod 可以定义为
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
API 版本对照表
Kubernetes 版本 | Core API 版本 | 默认开启 |
---|---|---|
v1.5+ | core/v1 | 是 |
使用 Volume
Volume 可以为容器提供持久化存储,比如
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: redis
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
更多挂载存储卷的方法参考 Volume。
私有镜像
在使用私有镜像时,需要创建一个 docker registry secret,并在容器中引用。
创建 docker registry secret:
kubectl create secret docker-registry regsecret --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>
容器中引用该 secret:
apiVersion: v1
kind: Pod
metadata:
name: private-reg
spec:
containers:
- name: private-reg-container
image: <your-private-image>
imagePullSecrets:
- name: regsecret
RestartPolicy
支持三种 RestartPolicy
- Always:只要退出就重启
- OnFailure:失败退出(exit code 不等于 0)时重启
- Never:只要退出就不再重启
注意,这里的重启是指在 Pod 所在 Node 上面本地重启,并不会调度到其他 Node 上去。
环境变量
环境变量为容器提供了一些重要的资源,包括容器和 Pod 的基本信息以及集群中服务的信息等:
(1) hostname
HOSTNAME
环境变量保存了该 Pod 的 hostname。
(2)容器和 Pod 的基本信息
Pod 的名字、命名空间、IP 以及容器的计算资源限制等可以以 Downward API 的方式获取并存储到环境变量中。
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: test-container
image: gcr.io/google_containers/busybox
command: ["sh", "-c"]
args:
- env
resources:
requests:
memory: "32Mi"
cpu: "125m"
limits:
memory: "64Mi"
cpu: "250m"
env:
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: MY_POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: MY_CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: test-container
resource: requests.cpu
- name: MY_CPU_LIMIT
valueFrom:
resourceFieldRef:
containerName: test-container
resource: limits.cpu
- name: MY_MEM_REQUEST
valueFrom:
resourceFieldRef:
containerName: test-container
resource: requests.memory
- name: MY_MEM_LIMIT
valueFrom:
resourceFieldRef:
containerName: test-container
resource: limits.memory
restartPolicy: Never
(3) 集群中服务的信息
容器的环境变量中还可以引用容器运行前创建的所有服务的信息,比如默认的 kubernetes 服务对应以下环境变量:
KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.0.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
由于环境变量存在创建顺序的局限性(环境变量中不包含后来创建的服务),推荐使用 DNS 来解析服务。
ImagePullPolicy
支持三种 ImagePullPolicy
- Always:不管镜像是否存在都会进行一次拉取
- Never:不管镜像是否存在都不会进行拉取
- IfNotPresent:只有镜像不存在时,才会进行镜像拉取
注意:
- 默认为
IfNotPresent
,但:latest
标签的镜像默认为Always
。 - 拉取镜像时 docker 会进行校验,如果镜像中的 MD5 码没有变,则不会拉取镜像数据。
- 生产环境中应该尽量避免使用
:latest
标签,而开发环境中可以借助:latest
标签自动拉取最新的镜像。
访问 DNS 的策略
通过设置 dnsPolicy 参数,设置 Pod 中容器访问 DNS 的策略
- ClusterFirst:优先基于 cluster domain 后缀,通过 kube-dns 查询 (默认策略)
- Default:优先从 kubelet 中配置的 DNS 查询
使用主机的 IPC 命名空间
通过设置 spec.hostIPC
参数为 true,使用主机的 IPC 命名空间,默认为 false。
使用主机的网络命名空间
通过设置 spec.hostNetwork
参数为 true,使用主机的网络命名空间,默认为 false。
使用主机的 PID 空间
通过设置 spec.hostPID
参数为 true,使用主机的 PID 命名空间,默认为 false。
apiVersion: v1
kind: Pod
metadata:
name: busybox1
labels:
name: busybox
spec:
hostIPC: true
hostPID: true
hostNetwork: true
containers:
- image: busybox
command:
- sleep
- "3600"
name: busybox
设置 Pod 的 hostname
通过 spec.hostname
参数实现,如果未设置默认使用 metadata.name
参数的值作为 Pod 的 hostname。
设置 Pod 的子域名
通过 spec.subdomain
参数设置 Pod 的子域名,默认为空。
比如,指定 hostname 为 busybox-2 和 subdomain 为 default-subdomain,完整域名为 busybox-2.default-subdomain.default.svc.cluster.local
,也可以简写为 busybox-2.default-subdomain.default
:
apiVersion: v1
kind: Pod
metadata:
name: busybox2
labels:
name: busybox
spec:
hostname: busybox-2
subdomain: default-subdomain
containers:
- image: busybox
command:
- sleep
- "3600"
name: busybox
注意:
- 默认情况下,DNS 为 Pod 生成的 A 记录格式为
pod-ip-address.my-namespace.pod.cluster.local
,如1-2-3-4.default.pod.cluster.local
- 上面的示例还需要在 default namespace 中创建一个名为
default-subdomain
(即 subdomain)的 headless service,否则其他 Pod 无法通过完整域名访问到该 Pod(只能自己访问到自己)
kind: Service
apiVersion: v1
metadata:
name: default-subdomain
spec:
clusterIP: None
selector:
name: busybox
ports:
- name: foo # Actually, no port is needed.
port: 1234
targetPort: 1234
注意,必须为 headless service 设置至少一个服务端口(spec.ports
,即便它看起来并不需要),否则 Pod 与 Pod 之间依然无法通过完整域名来访问。
资源限制
Kubernetes 通过 cgroups 限制容器的 CPU 和内存等计算资源,包括 requests(请求, 调度器保证调度到资源充足的 Node 上,如果无法满足会调度失败 )和 limits(上限)等:
spec.containers[].resources.limits.cpu
:CPU 上限,可以短暂超过,容器也不会被停止spec.containers[].resources.limits.memory
:内存上限,不可以超过;如果超过,容器可能会被终止或调度到其他资源充足的机器上spec.containers[].resources.requests.cpu
:CPU 请求,也是调度 CPU 资源的依据,可以超过spec.containers[].resources.requests.memory
:内存请求,也是调度内存资源的依据,可以超过;但如果超过,容器可能会在 Node 内存不足时清理spec.containers[].resources.limits.ephemeral-storage
:临时存储的请求,调度容器存储的依据spec.containers[].resources.requests.ephemeral-storage
:临时存储的上限,超过后 Pod 会被驱逐
比如 nginx 容器请求 30% 的 CPU 和 56MB 的内存,但限制最多只用 50% 的 CPU 和 128MB 的内存:
apiVersion: v1
kind: Pod
metadata:
labels:
app: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
resources:
requests:
cpu: "300m"
memory: "56Mi"
limits:
cpu: "1"
memory: "128Mi"
注意
- CPU 的单位是 CPU 个数,可以用
millicpu (m)
表示少于 1 个 CPU 的情况,如500m = 500millicpu = 0.5cpu
,而一个 CPU 相当于- AWS 上的一个 vCPU
- GCP 上的一个 Core
- Azure 上的一个 vCore
- 物理机上开启超线程时的一个超线程
- 内存的单位则包括
E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki
等。
健康检查
为了确保容器在部署后确实处在正常运行状态,Kubernetes 提供了两种探针(Probe)来探测容器的状态:
- LivenessProbe:探测应用是否处于健康状态,如果不健康则删除并重新创建容器
- ReadinessProbe:探测应用是否启动完成并且处于正常服务状态,如果不正常则不会接收来自 Kubernetes Service 的流量
Kubernetes 支持三种方式来执行探针:
- exec:在容器中执行一个命令,如果 命令退出码 返回
0
则表示探测成功,否则表示失败 - tcpSocket:对指定的容器 IP 及端口执行一个 TCP 检查,如果端口是开放的则表示探测成功,否则表示失败
- httpGet:对指定的容器 IP、端口及路径执行一个 HTTP Get 请求,如果返回的 状态码 在
[200,400)
之间则表示探测成功,否则表示失败
apiVersion: v1
kind: Pod
metadata:
labels:
app: nginx
name: nginx
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: http
livenessProbe:
httpGet:
path: /
port: 80
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 15
timeoutSeconds: 1
readinessProbe:
exec:
command:
- cat
- /usr/share/nginx/html/index.html
initialDelaySeconds: 5
timeoutSeconds: 1
- name: goproxy
image: gcr.io/google_containers/goproxy:0.1
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
Init Container
Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init 容器。Init 容器在所有容器运行之前执行(run-to-completion),常用来初始化配置。
如果为一个 Pod 指定了多个 Init 容器,那些容器会按顺序一次运行一个。 每个 Init 容器必须运行成功,下一个才能够运行。 当所有的 Init 容器运行完成时,Kubernetes 初始化 Pod 并像平常一样运行应用容器。
下面是一个 Init 容器的示例:
apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
# These containers are run during pod initialization
initContainers:
- name: install
image: busybox
command:
- wget
- "-O"
- "/work-dir/index.html"
- http://kubernetes.io
volumeMounts:
- name: workdir
mountPath: "/work-dir"
dnsPolicy: Default
volumes:
- name: workdir
emptyDir: {}
因为 Init 容器具有与应用容器分离的单独镜像,使用 init 容器启动相关代码具有如下优势:
- 它们可以包含并运行实用工具,处于安全考虑,是不建议在应用容器镜像中包含这些实用工具的。
- 它们可以包含使用工具和定制化代码来安装,但是不能出现在应用镜像中。例如,创建镜像没必要 FROM 另一个镜像,只需要在安装过程中使用类似 sed、 awk、 python 或 dig 这样的工具。
- 应用镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。
- 它们使用 Linux Namespace,所以对应用容器具有不同的文件系统视图。因此,它们能够具有访问 Secret 的权限,而应用容器不能够访问。
- 它们在应用容器启动之前运行完成,然而应用容器并行运行,所以 Init 容器提供了一种简单的方式来阻塞或延迟应用容器的启动,直到满足了一组先决条件。
Init 容器的资源计算,选择一下两者的较大值:
- 所有 Init 容器中的资源使用的最大值
- Pod 中所有容器资源使用的总和
Init 容器的重启策略:
- 如果 Init 容器执行失败,Pod 设置的 restartPolicy 为 Never,则 pod 将处于 fail 状态。否则 Pod 将一直重新执行每一个 Init 容器直到所有的 Init 容器都成功。
- 如果 Pod 异常退出,重新拉取 Pod 后,Init 容器也会被重新执行。所以在 Init 容器中执行的任务,需要保证是幂等的。
容器生命周期钩子
容器生命周期钩子(Container Lifecycle Hooks)监听容器生命周期的特定事件,并在事件发生时执行已注册的回调函数。支持两种钩子:
- postStart: 容器创建后立即执行,注意由于是异步执行,它无法保证一定在 ENTRYPOINT 之前运行。如果失败,容器会被杀死,并根据 RestartPolicy 决定是否重启
- preStop:容器终止前执行,常用于资源清理。如果失败,容器同样也会被杀死
而钩子的回调函数支持两种方式:
- exec:在容器内执行命令,如果命令的退出状态码是
0
表示执行成功,否则表示失败 - httpGet:向指定 URL 发起 GET 请求,如果返回的 HTTP 状态码在
[200, 400)
之间表示请求成功,否则表示失败
postStart 和 preStop 钩子示例:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
httpGet:
path: /
port: 80
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
使用 Capabilities
默认情况下,容器都是以非特权容器的方式运行。比如,不能在容器中创建虚拟网卡、配置虚拟网络。
Kubernetes 提供了修改 Capabilities 的机制,可以按需要给容器增加或删除。比如下面的配置给容器增加了 CAP_NET_ADMIN
并删除了 CAP_KILL
。
apiVersion: v1
kind: Pod
metadata:
name: cap-pod
spec:
containers:
- name: friendly-container
image: "alpine:3.4"
command: ["/bin/sleep", "3600"]
securityContext:
capabilities:
add:
- NET_ADMIN
drop:
- KILL
限制网络带宽
可以通过给 Pod 增加 kubernetes.io/ingress-bandwidth
和 kubernetes.io/egress-bandwidth
这两个 annotation 来限制 Pod 的网络带宽
apiVersion: v1
kind: Pod
metadata:
name: qos
annotations:
kubernetes.io/ingress-bandwidth: 3M
kubernetes.io/egress-bandwidth: 4M
spec:
containers:
- name: iperf3
image: networkstatic/iperf3
command:
- iperf3
- -s
[warning] 仅 kubenet 支持限制带宽
目前只有 kubenet 网络插件支持限制网络带宽,其他 CNI 网络插件暂不支持这个功能。
kubenet 的网络带宽限制其实是通过 tc 来实现的
# setup qdisc (only once)
tc qdisc add dev cbr0 root handle 1: htb default 30
# download rate
tc class add dev cbr0 parent 1: classid 1:2 htb rate 3Mbit
tc filter add dev cbr0 protocol ip parent 1:0 prio 1 u32 match ip dst 10.1.0.3/32 flowid 1:2
# upload rate
tc class add dev cbr0 parent 1: classid 1:3 htb rate 4Mbit
tc filter add dev cbr0 protocol ip parent 1:0 prio 1 u32 match ip src 10.1.0.3/32 flowid 1:3
调度到指定的 Node 上
可以通过 nodeSelector、nodeAffinity、podAffinity 以及 Taints 和 tolerations 等来将 Pod 调度到需要的 Node 上。
也可以通过设置 nodeName 参数,将 Pod 调度到指定 node 节点上。
比如,使用 nodeSelector,首先给 Node 加上标签:
kubectl label nodes <your-node-name> disktype=ssd
接着,指定该 Pod 只想运行在带有 disktype=ssd
标签的 Node 上:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd
nodeAffinity、podAffinity 以及 Taints 和 tolerations 等的使用方法请参考 调度器章节。
自定义 hosts
默认情况下,容器的 /etc/hosts
是 kubelet 自动生成的,并且仅包含 localhost 和 podName 等。不建议在容器内直接修改 /etc/hosts
文件,因为在 Pod 启动或重启时会被覆盖。
默认的 /etc/hosts
文件格式如下,其中 nginx-4217019353-fb2c5
是 podName:
$ kubectl exec nginx-4217019353-fb2c5 -- cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.244.1.4 nginx-4217019353-fb2c5
从 v1.7 开始,可以通过 pod.Spec.HostAliases
来增加 hosts 内容,如
apiVersion: v1
kind: Pod
metadata:
name: hostaliases-pod
spec:
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "foo.local"
- "bar.local"
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
containers:
- name: cat-hosts
image: busybox
command:
- cat
args:
- "/etc/hosts"
$ kubectl logs hostaliases-pod
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.244.1.5 hostaliases-pod
127.0.0.1 foo.local
127.0.0.1 bar.local
10.1.2.3 foo.remote
10.1.2.3 bar.remote
HugePages
v1.8 + 支持给容器分配 HugePages,资源格式为 hugepages-<size>
(如 hugepages-2Mi
)。使用前要配置
- 开启
--feature-gates="HugePages=true"
- 在所有 Node 上面预分配好 HugePage ,以便 Kubelet 统计所在 Node 的 HugePage 容量
使用示例
apiVersion: v1
kind: Pod
metadata:
generateName: hugepages-volume-
spec:
containers:
- image: fedora:latest
command:
- sleep
- inf
name: example
volumeMounts:
- mountPath: /hugepages
name: hugepage
resources:
limits:
hugepages-2Mi: 100Mi
volumes:
- name: hugepage
emptyDir:
medium: HugePages
注意事项
- HugePage 资源的请求和限制必须相同
- HugePage 以 Pod 级别隔离,未来可能会支持容器级的隔离
- 基于 HugePage 的 EmptyDir 存储卷最多只能使用请求的 HugePage 内存
- 使用
shmget()
的SHM_HUGETLB
选项时,应用必须运行在匹配proc/sys/vm/hugetlb_shm_group
的用户组(supplemental group)中
优先级
从 v1.9 开始,可以为 Pod 设置一个优先级(alpha),保证高优先级的 Pod 优先调度。
优先级调度功能还在 Alpha 版,使用前需要开启:
--feature-gates=PodPriority=true
--runtime-config=scheduling.k8s.io/v1alpha1=true --admission-control=Controller-Foo,Controller-Bar,...,Priority
为 Pod 设置优先级前,先创建一个 PriorityClass,并设置优先级(数值越大优先级越高):
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."
为 Pod 指定优先级
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
当调度队列有多个 Pod 需要调度时,优先调度高优先级的 Pod。而当高优先级的 Pod 无法调度时,Kubernetes 会尝试先删除低优先级的 Pod 再将其调度到对应 Node 上(Preemption)。
注意:受限于 Kubernetes 的调度策略,抢占并不总是成功。
PodDisruptionBudget
PodDisruptionBudget (PDB) 用来保证一组 Pod 同时运行的数量,这些 Pod 需要使用 Deployment、ReplicationController、ReplicaSet 或者 StatefulSet 管理。
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: zookeeper