Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。
在同一个 context
下,应用可能还会有独立的 cgroup
隔离机制,一个 Pod 是一个容器环境下的 “逻辑主机”
。
Pod 是一组容器单元, 这些容器共享存储、网络、以及怎样运行这些容器的声明。 Pod 中的内容总是并置(colocated)的并且一同调度,在共享的 context(上下文)
中运行。而 Pod 中的这些应用容器可能还有独立的 cgroup
隔离机制,一个 Pod 相当于应用的 “逻辑主机”
,其中包含一个或多个应用容器, 这些容器相对紧密地耦合在一起。 在非云环境中,在相同的物理机或虚拟机(VM)上运行的应用类似于在同一逻辑主机上运行的云应用。
我们可以借助上面这张图(豌豆荚)来形象的理解 Pod,豌豆荚的整体外壳就等同于 Pod,豌豆荚里面的豆子就等同于应用容器(App Container),豆子被豌豆荚包裹这就类似于应用容器运行在 Pod 中。
除了应用容器,Pod 还可以包含在 Pod 启动期间运行的 Init Container
。 也可以在集群中支持 临时性容器
的情况下,为调试注入 临时性容器
进行故障排查。
每个 Pod 创建的时候都会包含一个 Pause 容器和用户的业务容器(或者应用容器,App Container),Pod 的基本组成结构如下图所示:
既然 Pod 是 k8s 中最小的运行单元,那么在一个 Pod 中包含几个容器呢?
答:至少两个,分别是 1 个基础容器(pause)+ 应用容器(1个或者多个)。
此处我们以 asp.net core webapi
项目为例,定义 Pod 的 yaml 文件结构如下:
apiVersion: v1 # API 版本号
kind: Pod # 资源类型
metadata: # meta 信息
name: myweb
labels: # 标签
name: myweb
spec:
containers:
- name: myweb-aspnetcore # 容器名称
image: aspnetcore-6.0:v1.0.0 # 镜像:版本
imagePullPolicy: IfNotPresent # 如果镜像不存在则拉取
ports:
- containerPort: 5001 # 容器暴露端口
env: # 环境变量
- name: ASPNETCORE_ENVIRONMENT # asp.net core 运行时环境
value: Development # 开发环境
- name: ASPNETCORE_URLS # 设置 asp.net core 启动端口
value: http://localhost:5001/ # 本地主机 5001 端口
【Pod IP:containerPort
】就组成了一个新的概念 —— Endpoint,它代表此 Pod 里的一个服务进程的对外通信地址
。之前的文章介绍过,这里不再叙述。
容器的设计是单进程原则,推荐一个容器里只有一个应用在运行。然而单进程模型在某些特定场景下,满足不了应用的需求,因此在这种情况下有了 Pod 的存在,这也很好的诠释了 Pod 相当于应用的 “逻辑主机”
。
例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的 “sidecar” 容器负责从远端更新这些文件,这两个容器在功能上进行紧密的协调,如下图所示:
Pod 的 context
可以理解成多个 linux 命名空间的联合
前面我们介绍了 Pod 的基本概念和组成结构,那么 Kubernetes 设计这样的 Pod 概念和特殊组成结构有什么用意?
按照 Pod 的运行方式,可以把 Pod 分为以下两类:
自我管理的 Pod,创建以后仍然需要提交给 kube-apiserver
,由 kube-apiserver 接收以后借助于 Scheduler(调度器)
将其调度至指定的 node 节点,再由该 node 启动被调度过来的 Pod。
如果该 Pod 出现故障,需要重启容器则由 kubelet
来完成,本身不能自我修复。
如果 node 节点故障了,那么该 Pod 将会消失,Pod 不会自愈。这种方式的 Pod 无法实现全局调度,所以 不推荐使用此种 Pod
。
在 k8s 环境中,通常大多数情况下都使用该方式来管理 Pod,常见的 Pod 控制器有以下这些:
ReplicationController(副本控制器,简称 RC)
,当启动一个 Pod 时,这个 Pod 如果不够用可以再启一个副本,而后由该控制器来管理同一类 Pod 的各种副本与对象。一旦副本达不到预期目标数量就会自动增加或减少。采取多退少补的规则,精确符合我们所定义的期望。RC 同时还支持滚动更新。ReplicaSet(复制集,简称 RS)
,下一代 RC ,是 RC 的替换,由一个名叫 Deployment 的声明式更新的控制器来管理。Deployment(部署,简称 deploy)
,用于管理无状态的应用。StatefulSet(状态集)
,有状态副本集可以用于管理有状态的应用。DaemonSet(守护程序)
如果需要在每个 node 上运行一个副本即可用该对象。Job(任务)
用于运行批处理任务、运维 / ad-hoc 任务等。Cronjob(定时任务)
在 Job 的基础上,增加了时间周期,用于执行周期性的动作,例如备份、邮件、报告生成等。cron 时间配置与 linux crontab 相似
。以上每种控制器都是用来实现一种独特的应用管理的。
关于 Job 的应用场景
批处理任务: 比如说你想每天运行一次批处理任务,或者在指定日程中运行。它可能是像从存储库或数据库中读取文件那样,将它们分配给一个服务来处理文件。
运维 / ad-hoc 任务: 比如你想要运行一个脚本/代码,该脚本/代码会运行一个数据库清理活动,甚至备份一个Kubernetes集群。
静态 Pod(Static Pod)
直接由特定节点上的 kubelet
守护进程管理, 不需要通过控制面(例如,Deployment )来管理的,对于静态 Pod 而言,kubelet 直接监控每个 Pod,并在其失效时重启。
静态 Pod 通常绑定到某个 node 节点上的 kubelet
。 其主要用途是运行自托管的控制面(master)。 在自托管场景中,使用 kubelet 来管理各个独立的 控制面组件。
kubelet 自动尝试为每个静态 Pod 在 Kubernetes API 服务器上创建一个 镜像 Pod。 这意味着在节点上运行的 Pod 在 API 服务器(kube-apiserver)
上是可见的,但不可以通过 API 服务器来控制。
说明:静态 Pod 的 spec 不能引用其他的 API 对象(例如: ServiceAccount、 ConfigMap、 Secret 等)。
ssh k8s-node1
/etc/kubernetes/manifests
目录来保存 Web 服务 Pod 的 YAML 定义文件,例如 /etc/kubernetes/manifests/static-web.yaml
:# 在 kubelet 运行的节点上执行以下命令
mkdir -p /etc/kubernetes/manifests/
cat <<EOF >/etc/kubernetes/manifests/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
role: myrole
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
protocol: TCP
EOF
--pod-manifest-path=/etc/kubelet.d/
执行。(或者在 Kubelet 配置文件 中添加 staticPodPath: <目录>字段)KUBELET_ARGS="--cluster-dns=10.254.0.10 --cluster-domain=kube.local --pod-manifest-path=/etc/kubernetes/manifests/"
# 在 kubelet 运行的节点上执行以下命令
systemctl restart kubelet
KubeletConfiguration
中包含 kubelet
的配置。
字段 | 描述 |
---|---|
staticPodPath string |
staticPodPath 是指向要运行的本地(静态)Pod 的目录, 或者指向某个静态 Pod 文件的路径。默认值:“” |
设置 kubelet 的启动参数 “–manifest-url”,kubelet 将会定期从该 URL 地址下载 Pod 的定义文件,并以 .yaml 和 .json 文件的格式进行解析,然后创建 Pod。
以上两种方式创建的 Pod 是一致的。
关于 KubeletConfiguration 更多信息,请查看:https://kubernetes.io/zh-cn/docs/reference/config-api/kubelet-config.v1beta1/#kubelet-config-k8s-io-v1beta1-KubeletConfiguration
方式一:
在 kubelet 运行的 node 节点上来查看正在运行的容器(包括静态 Pod):
crictl ps
输出信息:
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
129fd7d382018 docker.io/library/nginx@sha256:... 11 minutes ago Running web 0 34533c6729106
说明:
crictl 会输出镜像 URI 和 SHA-256 校验和。NAME 看起来像: docker.io/library/nginx@sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31。
方式二:
可以在 API 服务上看到镜像 Pod:
kubectl get pods
输出信息:
NAME READY STATUS RESTARTS AGE
static-web 1/1 Running 0 2m
说明:要确保 kubelet 在 API 服务上有创建镜像 Pod 的权限。如果没有,创建请求会被 API 服务拒绝。
关于创建 Static Pod 的更多信息,请查看:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/static-pod/
Pod 中的容器又能分三类:基础容器(Pause)、初始化容器(Init Container)和应用容器(App Container)。
基础容器(pause)
:又叫 “根容器” 或 “父容器” ,给 pod 中的所有应用容器提供网络和存储资源的共享;提供 init 进程。管理整个 Pod 里的容器组的生命周期。初始化容器(Init Container)
:是在应用容器之前完成所有 init 容器的启动,多个 init 容器是串行启动
。每个 Init 容器都必须在下一个 Init 容器启动之前成功完成启动。应用容器(App Container)
:提供应用程序业务。并行启动
。由于 Pod 的设计是临时性的,用完即销毁的理念,那么 Pod 中容器 Container 运行产生的数据也会随着 Pod 的销毁而消失,为了保证 Container 里面产生的数据可以持久化保存,Pod 引入了 Volume(数据卷) 的概念,简单的理解类似于电脑磁盘的挂载。
这里我们以 redis 的 Pod 为例,使用 Volume 的 yaml 定义文件如下:
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: {}
上面的定义的 yaml 文件中,使用的是 emptyDir
临时卷,其中包含一个 挂载点 volumeMounts
,将 redis 容器产生的数据挂载到该容器内的 /data/redis
路径。
在 k8s 中 Volume 支持的类型有很多,这里不再列举,更多 Volume 的信息请查看:
- https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/
- https://feisky.gitbooks.io/kubernetes/content/concepts/volume.html
你可以通过以下方式之一使用边车(Sidecar)容器:
利用边车容器向自己的 stdout
和 stderr
传输流的方式, 你就可以利用每个节点上的 kubelet 和日志代理来处理日志。 边车容器从文件、套接字或 journald 读取日志。 每个边车容器向自己的 stdout 和 stderr 流中输出日志。
这种方法允许你将日志流从应用程序的不同部分分离开,其中一些可能缺乏对写入 stdout
或 stderr
的支持。重定向日志背后的逻辑是最小的,因此它的开销几乎可以忽略不计。 另外,因为 stdout 和 stderr 由 kubelet 处理,所以你可以使用内置的工具 kubectl logs
。
例如,某 Pod 中运行一个容器,该容器向两个文件写不同格式的日志。 下面是这个 Pod 的配置文件:
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
不建议在同一个日志流中写入不同格式的日志条目
,即使你成功地将其重定向到容器的 stdout 流。 推荐创建两个边车容器。每个边车容器可以从共享卷跟踪特定的日志文件, 并将文件内容重定向到各自的 stdout 流
。
下面是运行两个边车(Sidecar)容器的 Pod 的配置文件:
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-1
image: busybox:1.28
args: [/bin/sh, -c, 'tail -n+1 -F /var/log/1.log']
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-2
image: busybox:1.28
args: [/bin/sh, -c, 'tail -n+1 -F /var/log/2.log']
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
如果节点级日志记录代理程序对于你的场景来说不够灵活, 你可以创建一个带有单独日志记录代理的边车容器,将代理程序专门配置为与你的应用程序一起运行。
说明:
在边车容器中使用日志代理会带来严重的资源损耗。 此外,你不能使用kubectl logs
命令访问日志,因为日志并没有被kubelet
管理。
下面是两个配置文件,可以用来实现一个带日志代理的边车容器。
1、创建名称为 fluentd
的 Pod 的 ConfigMap
,yaml 定义文件如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
data:
fluentd.conf: |
>
**>
type google_cloud
>
说明:要进一步了解如何配置 fluentd,请参考 fluentd 官方文档:https://docs.fluentd.org/
2、创建 fluentd 边车容器的 Pod , flutend 通过 Pod 的挂载卷(ConfigMap)获取它的配置数据。
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-agent
image: k8s.gcr.io/fluentd-gcp:1.30
env:
- name: FLUENTD_ARGS
value: -c /etc/fluentd-config/fluentd.conf
volumeMounts:
- name: varlog
mountPath: /var/log
- name: config-volume
mountPath: /etc/fluentd-config
volumes:
- name: varlog
emptyDir: {}
- name: config-volume
configMap:
name: fluentd-config
在上面的示例配置中,fluentd 可以被替换为任何日志代理,从应用容器内的任何来源读取数据。
关于 k8s 中日志架构的更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/cluster-administration/logging/
应用部署的最佳实践是将应用所需的配置信息与程序进行分离,这样可以使应用程序被更好地复用,通过不同的配置能实现更灵活的功能。
将应用打包为容器镜像后,可以通过环境变量(env)或外挂文件(volume)这两种方式在创建容器时进行配置的注入,但在大规模化容器集群的环境中,对多个容器进行不同的配置会变得非常的复杂,多个容器配置出错的概率大幅提升,随之管理成本上升,难以方便的管理。
面对以上问题,k8s v1.2 版本开始提供了一种统一的集中化应用配置管理方案 —— ConfigMap(也叫集中化配置中心)。
ConfigMap 是一个 API 对象,使用键-值(key-value)对形式保存非机密性的数据
。 可以存储其他对象所需要使用的配置。 不同的是,其他 k8s 对象都有一个 spec
,而 ConfigMap 使用 data
和 binaryData
字段。这些字段能够接收 键-值(key-value)
对作为其取值。
data 和 binaryData 字段都是可选的
。
UTF-8
字符串。二进制数据作为 base64 编码的字串
。注意:
ConfigMap 并不提供保密或者加密功能。 如果你想存储的数据是机密的,请使用Secret
, 或者使用其他第三方工具来保证你的数据的私密性,而不是用 ConfigMap。
ConfigMap 在设计上不是用来保存大量数据的。在 ConfigMap 中保存的数据不可超过 1 MiB
。如果你需要保存超出此尺寸限制的数据,考虑使用 挂载存储卷
或者使用 独立的数据库或者文件服务
。
ConfigMap 供 Pod 容器使用的典型用法,有以下四种方式:
这些不同的方法适用于不同的数据使用方式。 对前三个方法,kubelet 使用 ConfigMap 中的数据在 Pod 中启动容器。
第四种方法意味着你必须编写代码才能读取 ConfigMap 和它的数据。然而, 由于你是直接使用 Kubernetes API,因此只要 ConfigMap 发生更改, 你的应用就能够通过订阅来获取更新,并且在这样的情况发生的时候做出反应。 通过直接进入 Kubernetes API,这个技术也可以让你能够获取到不同的Namespace 里的 ConfigMap。
可以通过 YAML 配置文件
方式或者直接使用 kubectl create configmap
命令行的方式来创建 ConfigMap。
上面的示例(使用 Sidecar 容器运行日志代理)中已经展示过,接下来我们再创建一个 game-demo
的 ConfigMap。
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# 类属性键;每一个键都映射到一个简单的值
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"
# 类文件键
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
注意区分 key 键的类型:
类属性键
和类文件键
。
执行如下命令创建上面定义的 ConfigMap:
kubectl create -f game-demo.yaml
输出信息:
configmap "game-demo" created
查看创建好的 ConfigMap:
kubectl get configmap
查看 ConfigMap 的详细信息:
kubectl describe configmap game-demo
除了上面的方式,还可以将 配置文件(xml 或 json 文件)定义为 ConfigMap 的用法,设置 key 为配置文件的别名,value 为该配置文件的全部文本内容
。比如:asp.net core webapi 里面的 appsettings.json 文件
。
除了上面的 YAML 文件方式,还可以通过 kubectl 命令行方式创建 ConfigMap,并且还可以在一行命令中指定多个参数。
2.1 通过 --from-file
参数从文件中进行创建,可以指定 key 的名称,也可以在一个命令行中创建包含多个 key 的 ConfigMap ,语法格式:
kubectl create configmap NAME --from-file=[key=]source --from-file=[key=]source
2.2 通过 --from-file
参数从目录中进行创建,该目录下的每个配置文件名都被设置为 key,文件的内容被设置为 value ,语法格式:
kubectl create configmap NAME --from-file=config-files-dir
2.3 使用 --from-literal
时会从文本中进行创建,直接将指定的 key#=value#
创建为 ConfigMap 内容,语法格式:
kubectl create configmap NAME --from-literal=key1=value1 --from-literal=key2=value2
分别对上面这几种命令方式创建 ConfigMap 举例说明:
示例1:比如在 asp.net core
项目中有配置文件 appsettings.json
,可以创建一个包含该文件内容的 ConfigMap
:
kubectl create configmap appsettings.json --from-file=appsettings.json
configmap "appsettings.json" created
示例2:假如,在 asp.net core
项目中的 configfiles
目录下包含两个配置文件 appconfig.xml 和 appsettings.json
,创建一个包含两个文件内容的 ConfigMap
:
kubectl create configmap cm-appconf --from-file=configfiles
示例3:使用 --from-literal
参数进行创建 ConfigMap
:
kubectl create configmap cm-appenv --from-literal=loglevel=info --from-literal=appdatadir=/var/data
配置 Pod 使用 ConfigMap 有以下两种方式:
这里我们使用上面创建的 game-demo
的 ConfigMap,通过两种方式配置 Pod 使用 game-demo
。
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-pod
spec:
containers:
- name: demo
image: alpine
command: ["sleep", "3600"]
env:
# 定义环境变量
- name: PLAYER_INITIAL_LIVES # 请注意这里和 ConfigMap 中的键名是不一样的
valueFrom:
configMapKeyRef:
name: game-demo # 这个值来自 ConfigMap
key: player_initial_lives # 需要取值的键
- name: UI_PROPERTIES_FILE_NAME
valueFrom:
configMapKeyRef:
name: game-demo
key: ui_properties_file_name
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
# 你可以在 Pod 级别设置卷,然后将其挂载到 Pod 内的容器中
- name: config
configMap:
# 提供你想要挂载的 ConfigMap 的名字
name: game-demo
# 来自 ConfigMap 的一组键,将被创建为文件
items:
- key: "game.properties"
path: "game.properties"
- key: "user-interface.properties"
path: "user-interface.properties"
说明:
环境变量的名称遵循 POSIX 命名规范([a-zA-Z_][a-zA-Z0-9_]*),不能以数字开头。如果包含非法字符,则系统将跳过该条环境变量的创建,并记录一个 Event 来提示环境变量无法创建,但并不阻止 Pod 的启动。
--manifest-url
或 --config
自动创建的 Static Pod 将无法引用 ConfigMap。当 Pod 被成功创建之后,都会被系统分配唯一的名字、IP 地址,并且处于某个 namespace 中,那么这些信息如何在 Pod 的容器内获取该 Pod 这些重要信息呢?
对于容器来说,在不与 Kubernetes 过度耦合的情况下,拥有关于自身的信息有时是很有用的。 Downward API
允许容器在不使用 Kubernetes 客户端(kubectl)或 API 服务器(kube-apiserver)的情况下获得自己或集群的信息。
Downward API
可以通过以下两种方式将 Pod 信息注入容器内部:
这两种暴露 Pod 和容器字段的方式统称为 Downward API
。
通过 Downward API 将 Pod 的 IP、名称、所在的 Namespace 和 node 名称注入容器的环境变量中,容器应用使用 env 命令将全部环境变量打印到标准输出中,dapi-envars-fieldref.yaml
文件定义如下:
apiVersion: v1
kind: Pod
metadata:
name: dapi-envars-fieldref
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox
command: [ "sh", "-c", "env"]
args:
- while true; do
echo -en '\n';
printenv MY_NODE_NAME MY_POD_NAME MY_POD_NAMESPACE;
printenv MY_POD_IP MY_POD_SERVICE_ACCOUNT;
sleep 10;
done;
env:
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName # Pod 所在的 Node 名称
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name # Pod 的名称,当 Pod 通过 Deployment 创建时,其名称是 RS 随机产生的唯一名称
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace # Pod 所在的 Namespace
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP # Pod 的 IP 地址,之所以叫做 status.podIP 而非 metadata.podIP,是因为 Pod 的 IP 属于状态数据,而非元数据(metadata)
- name: MY_POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName # Pod 的服务账号名称
restartPolicy: Never # Pod 重启策略,从不重启
执行以下命令创建 Pod:
kubectl create -f dapi-envars-fieldref.yaml
pod "dapi-envars-fieldref" created
查看 “dapi-envars-fieldref” 的日志信息:
kubectl logs dapi-envars-fieldref
...
MY_NODE_NAME=xxx
MY_POD_NAME=dapi-envars-fieldref
MY_POD_NAMESPACE=default
MY_POD_IP=172.17.1.2
MY_POD_SERVICE_ACCOUNT=yyy
...
注意,上面 valueFrom 这种特殊的语法是 Downward API 的写法。
用 Container 字段作为环境变量的值,dapi-envars-resourcefieldref.yaml
文件定义如下:
apiVersion: v1
kind: Pod
metadata:
name: dapi-envars-resourcefieldref
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox:1.24
command: [ "/bin/sh", "-c", "env"]
args:
- while true; do
echo -en '\n';
printenv MY_CPU_REQUEST MY_CPU_LIMIT;
printenv MY_MEM_REQUEST MY_MEM_LIMIT;
sleep 10;
done;
resources:
requests:
memory: "32Mi"
cpu: "125m"
limits:
memory: "64Mi"
cpu: "250m"
env:
- name: MY_CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: test-container
resource: requests.cpu # 容器的 CPU 请求值(最小值)
- name: MY_CPU_LIMIT
valueFrom:
resourceFieldRef:
containerName: test-container
resource: limits.cpu # 容器的 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
这个配置文件中,可以看到四个环境变量。env
字段是一个 EnvVars.
对象的数组。数组中第一个元素指定 MY_CPU_REQUEST
这个环境变量从 Container
的 requests.cpu
字段获取变量值。同样,其它环境变量也是从 Container
的字段获取它们的变量值。
说明: 本例中使用的是 Container 的字段而不是 Pod 的字段。
将创建一个包含一个容器的 Pod,并将 Pod 级别的字段作为文件映射到正在运行的容器中。kubernetes-downwardapi-volume-example.yaml
定义文件如下:
apiVersion: v1
kind: Pod
metadata:
name: kubernetes-downwardapi-volume-example
labels:
zone: us-est-coast
cluster: test-cluster1
rack: rack-22
annotations:
build: two
builder: john-doe
spec:
containers:
- name: client-container
image: k8s.gcr.io/busybox
command: ["/bin/sh", "-c", "env"]
args:
- while true; do
if [[ -e /etc/podinfo/labels ]]; then
echo -en '\n\n'; cat /etc/podinfo/labels; fi;
if [[ -e /etc/podinfo/annotations ]]; then
echo -en '\n\n'; cat /etc/podinfo/annotations; fi;
sleep 5;
done;
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
volumes:
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
命令行执行 kubectl create -f kubernetes-downwardapi-volume-example.yaml
创建 Pod,再查看该 Pod 的日志,输出的信息和文件描述的意思相符,这里就不在演示了。
只有部分 Kubernetes API 字段可以通过 Downward API 使用
。
你可以使用 fieldRef
传递来自可用的 Pod 级字段的信息。在 API 层面,一个 Pod 的 spec
总是定义了至少一个 Container
。 你可以使用 resourceFieldRef
传递来自可用的 Container
级字段的信息。
可以使用的字段如下表:
fieldRef
获得的信息字段 | 描述信息 | 是否可作为环境变量获取 |
---|---|---|
metadata.name | Pod 的名称 | 是 |
metadata.namespace | Pod 的命名空间 | 是 |
metadata.uid | Pod 的唯一 ID | 是 |
metadata.annotations[‘’] | Pod 的注解 的值(例如:metadata.annotations[‘myannotation’]) | 是 |
metadata.labels[‘’] | Pod 的标签 的值(例如:metadata.labels[‘mylabel’]) | 是 |
spec.serviceAccountName | Pod 的服务账号名称 | 是 |
spec.nodeName | Pod 运行时所处的节点名称 | 是 |
status.hostIP | Pod 所在节点的主 IP 地址 | 是 |
status.podIP | Pod 的主 IP 地址(通常是其 IPv4 地址) | 是 |
metadata.labels | Pod 的所有标签,格式为 标签键名=“转义后的标签值”,每行一个标签 | 否 |
metadata.annotations | Pod 的全部注解,格式为 注解键名=“转义后的注解值”,每行一个注解 | 否 |
resourceFieldRef
获得的信息字段 | 描述信息 | 是否可作为环境变量获取 |
---|---|---|
resource: limits.cpu | 容器的 CPU 限制值 | 是 |
resource: requests.cpu | 容器的 CPU 请求值 | 是 |
resource: limits.memory | 容器的内存限制值 | 是 |
resource: requests.memory | 容器的内存请求值 | 是 |
resource: limits.hugepages-* | 容器的巨页限制值(前提是启用了 DownwardAPIHugePages 特性门控) | 是 |
resource: requests.hugepages-* | 容器的巨页请求值(前提是启用了 DownwardAPIHugePages 特性门控) | 是 |
resource: limits.ephemeral-storage | 容器的临时存储的限制值 | 是 |
resource: requests.ephemeral-storage | 容器的临时存储的请求值 | 是 |
资源限制的后备信息
如果没有为容器指定 CPU 和内存限制时尝试使用 Downward API 暴露该信息,那么 kubelet 默认会根据 node 节点可分配资源 计算并暴露 CPU 和内存的最大可分配值。
在某些集群环境中,集群中的每个 node 都需要将自身的标识(ID)及进程绑定的 IP 地址等信息先写入配置文件中,进程在启动时会读取这些信息,然后将这些信息发布到某个类似的服务注册中心的地方,以实现集群节点的自动发现功能。这种场景就是 Downward API 发挥价值的地方
,具体做法:
先写一个预启动脚本或者 Init Container
,然后通过环境变量或文件方式
获取 Pod 的自身名称、IP地址等信息,接下来再将这些信息写入主程序的配置文件中,最后启动主程序,即可大功告成。
Kubernetes 基于 list-watch 机制的控制器架构,实现组件间交互的解耦。
Pod 创建的工作流程如下:
kubectl run nginx --image=nginx
查看创建的 Pod:
kubectl get pods
API Server(kube-apiserver) 在 k8s 中的两个角色:
使用 Deployment 对象创建 Pod 的工作流程:
说明:上图中 Init Container 是线性顺序执行
的,必须等待前一个 Init Container 成功结束才开始执行下一个 Init Container ,而后面的 应用容器(Main Container)则是并行执行
的,多个业务容器可以并行执行。
Pod 创建的时候可能经历这么几个阶段(phase):
取值 | 描述 |
---|---|
Pending(悬决) | Pod 已被 k8s 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。 |
Running(运行中) | Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。 |
Succeeded(成功) | Pod 中的所有容器都已成功终止,并且不会再重启。 |
Failed(失败) | Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止。 |
Unknown(未知) | 因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。 |
如果某节点 “死掉” 或者与集群中其他节点 “失联”,k8s 会实施一种策略,将失去的节点上运行的所有 Pod 的 phase 设置为 Failed。
Pod 的重启策略包括 Always、OnFailure 和 Never
。默认值是 Always
。
kubelet 重启失效容器的 时间间隔以 ync-frequency 乘以 2n 来计算
,例如:1、2、4、8 倍等,最长延时 5min,并且在成功重启后的 10min 后重置该时间。
结合 Pod 的阶段(phase)状态信息和重启策略,列举出一些常见的状态转换场景,如下表所示:
容器配置活跃(Liveness)、就绪(Readiness)和启动(Startup)探测器。
更多信息请查看:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
k8s 对 Pod 的健康状态可以通过两类探针来检查:LivenessProbe 和 ReadinessProbe
,kubelet 定期执行这两类探针来诊断容器的健康状况。
LivenessProbe(存活探针)
:用于判断容器是否存活(Running)状态,如果该探针探测到容器不健康,则 kubelet 将杀掉该容器,并依据容器的重启策略做相应的处理。如果容器不包含 LivenessProbe 探针,kubelet 则认为该容器的 LivenessProbe 探针返回值永远是 Success。ReadinessProbe(就绪探针)
:用于判断容器服务是否可用(Ready)状态,达到 Ready 状态的 Pod 才可以接受请求。 对于被 Service 管理的 Pod,Service 与 Pod Endpoint 的关联关系也将基于 Pod 是否 Ready 进行设置。如果在运行中过程中 Pod 的状态由 Ready 变为 False,则系统自动将其从 Service 的后端 Endpoint 列表中隔离出去,后续再把恢复到 Ready 状态的 Pod 加回后端 Endpoint 列表。这样就能保证客户端在访问 Service 时不会被转发到服务不可用的 Pod 副本实例上。LivenessProbe 和 ReadinessProbe 均可配置以下三种实现方式
:
ExecAction
:在容器内部执行一个命令,如果该命令的返回码为 0 ,则说明容器处于健康状态。示例文件:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: k8s.gcr.io/busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
TCPSocketAction
:通过容器的 IP 地址和端口号执行 TCP 检查,如果能够建立 TCP 连接,则说明容器处于健康状态。示例文件:
apiVersion: v1
kind: Pod
metadata:
name: pod-with-healthcheck
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 30
periodSeconds: 5
HTTPGetAction
:通过容器的 IP 地址、端口号及路径调用 HTTP Get 方法,如果响应的状态码大于等于 200 且小于 400( HTTP Status Code ≥ 200 &&<400),则说明容器处于健康状态。示例文件:
apiVersion: v1
kind: Pod
metadata:
name: pod-with-healthcheck
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /status/healthz
port: 80
httpHeaders:
- name: Custom-Header # 自定义 http 请求头,比如:Accept
value: Awesome # 自定义 http 请求头对应的值,比如:application/json
initialDelaySeconds: 30
periodSeconds: 5
说明:
HTTPGetAction
方式可以添加httpHeaders
配置,用户可依据自身业务情况扩展 http 请求头。
对于以上三种探测方式,都需要设置 initialDelaySeconds
和 periodSeconds
两个参数,他们的含义分别如下:
initialDelaySeconds
:启动容器后进行首次健康检查的等待时间,单位为秒 s。periodSeconds
:健康检查发送请求后等待响应的超时时间,单位为秒 s。当超时发生,kubelet 会认为容器已经无法提供服务,将会重启该容器。k8s 的 ReadinessProbe
机制可能无法满足某些复杂应用对容器内服务可用状态的判断,从 k8s v1.11 版本开始,引入 Pod Ready++
特性对 ReadinessProbe
探测机制进行扩展,在 v1.14 版本时达到 GA 稳定版,称其为 PodReadiness Gates
。
关于 k8s 更多特性的信息,请查看:https://kubernetes.io/zh-cn/docs/reference/command-line-tools-reference/feature-gates/
通过 Pod Readiness Gates
机制,用户可以将自定义的 ReadinessProbe
探测方式设置在 Pod 上,辅助 k8s 设置 Pod 何时达到服务可用状态(Ready)。为了使用自定义的 ReadinessProbe
生效,用户需要提供一个外部的控制器(Controller)来设置相应的 Condition(条件)状态。
示例文件(伪 yaml 定义结构):
...
kind: Pod
...
spec:
readinessGates:
- conditionType: "www.example.com/feature-1" # 新 Readiness Gate,类型为 www.example.com/feature-1
status:
conditions:
- type: Ready # k8s 系统内置的名为 Ready 的 condition
status:"True"
lastProbeTime: null
lastTransitionTime: 2021-01-01T00:00:00Z
- type: "www.example.com/feature-1" # 用户自定义的 condition
status:"True"
lastProbeTime: null
lastTransitionTime: 2022-08-28T00:00:00Z
containerStatuses:
- conditionID: docker://abcd...
ready: true
新增的自定义 Condition(条件)状态(status)将由用户自定义的外部控制器设置,默认值为 False。k8s 将在判断全部 readinessGates 条件都为 True 时,才设置 Pod 为服务可用状态(Ready 为 True)。
有时候,会有一些现有的应用在启动时需要较长的初始化时间。 在这种情况下,若要不影响对死锁作出快速响应的探测,设置存活探测参数是要技巧的。 技巧就是使用相同的命令来设置启动探测,针对 HTTP 或 TCP 检测
,可以通过将 failureThreshold * periodSeconds
参数设置为足够长的时间来应对糟糕情况下的启动时间。
前面定义的 yaml 文件示例,修改如下:
ports:
- name: liveness-port
containerPort: 8080
hostPort: 8080
# 存活探针
livenessProbe:
httpGet:
path: /healthz
port: liveness-port
failureThreshold: 1
periodSeconds: 10
# 启动探针
startupProbe:
httpGet:
path: /healthz
port: liveness-port
failureThreshold: 30
periodSeconds: 10
k8s 的核心基本是围绕 Pod 展开的,顺着 Pod 的资源抽象概念,可以延伸出 k8s 基于容器化资源管理系统的各个知识点,比如:RC、RS、Deployment、DaemonSet、Job & CronJob 、StatefulSet、Serivice、Volume、PV/PVC 等资源对象对 Pod 的一系列管控机制,比如 Pod 背后的调度机制等,因此理解并深入掌握 Pod 是进入 k8s 知识体系的基本前提和保障。