再战 k8s:Pod 定义及基本用法
Posted 看,未来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了再战 k8s:Pod 定义及基本用法相关的知识,希望对你有一定的参考价值。
文章目录
1. 什么是pod
一些关于pod的表述很专业,例如
Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元
pod是kubernetes项目中的最小API对象
pod是kubernetes项目的原子调度单位
官方描述:https://kubernetes.io/zh/docs/concepts/workloads/pods/
2. 为什么需要pod
对于这些对pod概念的总结大多殊路同归。
在理解概念前不妨先想一个问题:
明明有了容器,为什么还需要pod?
因为:
为了解决容器不足,或者说容器无法满足需求。
确实是这样,先分析一下容器的本质:
namespace做隔离
Cgroup做限制
rootfs做文件系统
三者相辅相成组成容器基本模型
但是,宏观的来说,**容器的本质是系统中的一个进程。**只不过这个进程启动时被附加了以上等等一下特殊的属性。
所谓容器只是一个恰当的说法。
就如系统中的大多数进程来说,不是单个进程独自工作,而是以“进程组”的方式“原则性”的组织在一起,互相协作,完成复杂任务。当然,它们之间也会共享一些资源,例如pid,namespace,存储等等。
在实际开发和运维中也是随处可见的这种问题,应用之间有深切的联系和依赖。
比如说,我要将一个应用容器化,这个应用由负责各个功能的5个进程组成,这时候,问题来了。
正是容器的局限性:单进程模型
单进程不是指容器中只能运行一个进程,而是容器无法去管理多个进程
例如,容器中有pid=1的进程,还有一个pid=5的进程,当这个pid=5的进程异常退出时,后续的垃圾回收等处理工作又由谁去做呢?
所以,这个应用的5个模块就必须分别制成5个容器,而且必须在同一个机器上运行。
随之而来的又是一个问题:
也就是容器调度问题,例如有两个容器调度节点node1和node2,可用内存分别为5G和4.5G,
每个容器分1G内存。由于5个容器必须在一台机器,正常全部调度到node1刚刚好,没有任何问题。
但是,因为是以容器为单位调度,有这样一些特殊情况。当前4个容器被调度到node2上时,空间只有0.5G,不足以运行最后一个容器,它有只能在node1运行,这就是以容器为调度单位的缺点。
当然也有解决方案:如Mesos中的资源囤积(resource hoarding),也就是所有调度任务都到达了才进行调度,也有谷歌Omega论文提出乐观调度,就是先不管冲突,而是在冲突之后通过一系列回滚机制解决冲突。
但这些都没有很完善的解决容器调度问题,但是在kubernetes中,这些问题都迎刃而解。
因为不在按照传统思维的将容器作为调度单位
kubernetes中的项目调度器是统一按照pod的资源需求做调度计算
也就是开始总结的那句话: *pod*是kubernetes项目的原子调度单位
3. pod结构
如图所示,一个pod包含了两类容器:
- 用户容器
- Pause容器,也常常被称为“根容器”(貌似老版本叫做infra容器)
用户容器好理解,但是Pause容器,也就是根容器,他是做什么用的呢?
pause容器主要为每个用户容器提供以下功能:
① PID命名空间:Pod中的不同应用程序可以看到其他应用程序的进程ID。
② 网络命名空间:Pod中的多个容器能够访问同一个IP和端口范围。
③ IPC命名空间:Pod中的多个容器能够使用SystemV IPC或POSIX消息队列进行通信。
④ UTS命名空间:Pod中的多个容器共享一个主机名;Volumes(共享存储卷):
⑤ Pod中的各个容器可以访问在Pod级别定义的Volumes。
4. 容器设计模式
考虑这样一个问题,容器间的关系是一成不变的吗?
举一些例子:
- 在pod中的一些容器的启动,必须依赖某一个正在运行的容器,也就是说这个容器必须比其他容器先启动
- 在pod中的容器必须同时协作,也就是说所有容器必须并行执行
- 一个pod需要给外部的其他pod提供接口
- 外部访问pod时,又怎样确保响应报文的一致性
- …
常见容器设计模式:
Init(初始化)容器
Sidecar(边车)容器
Adapter(适配器)容器
Ambassador(外交官)容器
关于容器设计模式可以参考论文:https://www.usenix.org/conference/hotcloud16/workshop-program/presentation/burns
5. pod实现原理
一定要明白一点:pod只是一个逻辑上的概念
因为kubernetes真正处理的还是宿主机操作系统上容器的Namespace和Cgroup,也就是说没有所谓的pod边界或隔离环境。
所以说,pod就是一组共享了某些资源的容器
在一个pod中所有容器是共享一个Network Namespace
的,根据声明的不同来实现不同的资源共享
但是容器间的复杂关系在容器上难以解决,所以kubernetes项目里,pod的实现借用了一个中间容器,也就是常常说的根容器(也叫pause容器或infra容器,现在好像都统称pause容器,infra容器已经不再使用)。
在pod中,根容器永远是第一个创建的容器,用户后面定义的容器会加入进pod的Network Namespace
,从而在视图上容器都在pod里。
不妨在k8s中看看这个容器镜像:
[root@master ~]# docker images | grep pause
registry.aliyuncs.com/google_containers/pause 3.2 80d28bedfe5d 15 months ago 683kB
#当然,主机上有多少个pod就能docker ps 过滤看到多少pause容器
pause镜像大小只有683k,它是由汇编语言写的镜像
在一个pod中的容器,他们的Namespace文件,是一样的
也就意味着:
- pod内的容器可以使用localhost进行通信
- 根容器能看到的网络资源,其他容器都能看到,也就是pod的网络资源和pod内的容器共享
- 一个pod有一个ip地址,和pod对应的Network namespace的ip一致
- pod的生命周期只与根容器有关,与pod内的容器无关
- 从pod里的容器的视角来说,它们的流量进出可以看做是通过根容器完成的
Pod 定义
属性名称 | 取值类型 | 是否必选 | 取值说明 |
---|---|---|---|
apiVersion: v1 | String | Requried | 版本号,例如v1,版本号必须可以用 kubectl api-versions 查询到 |
kind: Pod | String | Requried | Pod |
metadata: | Object | Requried | 元数据 |
name: string | String | Requried | Pod名称,需符合RFC 1035规范 |
namespace: string | String | Requried | Pod所属的命名空间,默认为"default" |
labels: | List | 自定义标签 | |
- name: string | String | 自定义标签名字 | |
annotations: | List | 自定义注释列表 | |
- name: string | |||
spec: | Object | Requried | Pod中容器的详细定义 |
containers: | List | Requried | Pod中容器列表 |
- name: string | String | Requried | 容器名称,需符合RFC 1035规范 |
image: string | String | Requried | 容器的镜像名称 |
imagePullPolicy: [ Always|Never|IfNotPresent ] | String | 获取镜像的策略,默认值为Alawys,Alawys表示每次都尝试下载镜像,IfnotPresent表示优先使用本地镜像,否则下载镜像,Nerver表示仅使用本地镜像 | |
command: [string] | List | 容器的启动命令列表,如不指定,使用打包时使用的启动命令 | |
args: [string] | List | 容器的启动命令参数列表 | |
workingDir: string | String | 容器的工作目录 | |
volumeMounts: | List | 挂载到容器内部的存储卷配置 | |
- name: string | String | 引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名 | |
mountPath: string | String | 存储卷在容器内mount的绝对路径,应少于512字符 | |
readOnly: boolean | Boolean | 是否为只读模式,默认为读写模式 | |
ports: | List | 需要暴露的端口库号列表 | |
- name: string | String | 端口的名称 | |
containerPort: int | Int | 容器需要监听的端口号 | |
hostPort: int | Int | 容器所在主机需要监听的端口号,默认与Container相同 | |
protocol: string | String | 端口协议,支持TCP和UDP,默认TCP | |
env: | List | 容器运行前需设置的环境变量列表 | |
- name: string | String | 环境变量名称 | |
value: string | String | 环境变量的值 | |
resources: | Object | 资源限制和请求的设置 | |
limits: | Object | 资源限制的设置 | |
cpu: string | String | CPU的限制,单位为core数,将用于docker run --cpu-shares参数 | |
memory: string | String | 内存限制,单位可以为Mib/Gib,将用于docker run --memory参数 | |
requests: | Object | 资源限制的设置 | |
cpu: string | String | CPU请求,容器启动的初始可用数量 | |
memory: string | String | 内存请求,容器启动的初始可用数量 | |
livenessProbe: | Object | 对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器,可设置的方法包括exec、httpGet和tcpSocket,对一个容器仅需设置其中一种健康检查方法 | |
exec: | Object | 对Pod容器内检查方式设置为exec方式 | |
command: [string] | String | exec方式需要制定的命令或脚本 | |
httpGet: | Object | 对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port | |
path: string | |||
port: number | |||
host: string | |||
scheme: string | |||
HttpHeaders: | |||
- name: string | |||
value: string | |||
tcpSocket: | Object | 对Pod内个容器健康检查方式设置为tcpSocket方式 | |
port: number | |||
initialDelaySeconds: 0 | Number | 容器启动完成后首次探测的时间,单位为秒 | |
timeoutSeconds: 0 | Number | 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒,若超过该超时时间设置,则认为该容器不健康,会重启该容器 | |
periodSeconds: 0 | Number | 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次 | |
successThreshold: 0 | |||
failureThreshold: 0 | |||
securityContext: | |||
privileged: false | |||
restartPolicy: [Always | Never | OnFailure] | String | Pod的重启策略,(1)Always表示一旦不管以何种方式终止运行,kubelet都将重启它,(2)OnFailure表示只有Pod以非0退出码退出才重启,如果容器正常结束(退出码为0)kubelet将不会重启它,(3)Nerver:Pod终止后,kubelet将退出码报告给Master,不会再重启该Pod。 | |
nodeSelector: obeject | Object | 设置NodeSelector表示将该Pod调度到包含这个label的node上,以key:value的格式指定 | |
imagePullSecrets: | Object | Pull镜像时使用的secret名称,以name:secretkey格式指定 | |
- name: string | |||
hostNetwork: false | Boolean | 是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络,不再使用Docker网桥,该Pod将无法在同一宿主机上启动第2哥副本 | |
volumes: | List | 在该pod上定义共享存储卷列表 | |
- name: string | String | 共享存储卷名称 ,在一个Pod中每个存储卷定义一个名称,应符合RFC 1035规范。容器定义部分的containers[].volumeMounts[].name将引用该共享存储卷的名称。Volume的类型包括:emptyDir、hostPath、gcePersistentDisk、awsElasticBlockStore、gitRepo、secret、nfs、iscsi、glusterfs、persistentVolumeClaim、rbd、flexVolume、cinder、cephfs、flocker、downwardAPI、fc、azureFile、configMap、vsphereVolume,可以定义多个Volume,每个Volume的name保持唯一。 | |
emptyDir: | Object | 类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值 | |
hostPath: string | Object | 类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录 | |
path: string | String | Pod所在宿主机的目录,将被用于同期中mount的目录 | |
secret: | Object | 类型为secret的存储卷,挂载集群与定义的secre对象到容器内部 | |
scretname: string | |||
items: | |||
- key: string | |||
path: string | |||
configMap: | Object | 类型为configMap的存储卷,挂载预定义的configMap对象到容器内部 | |
name: string | |||
items: | |||
- key: string | |||
path: string |
容器基本用法
1.定义创建pod
创建一个Hello World Pod,运行一个输出“Hello World的容器”
- 定义hello-world-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: hello-world
spec:
restartPolicy: OnFailure
containers:
- name: hello
image: "ubuntu"
command: ["/bin/echo","hello”,”world"]
- 创建
kubectl create -f hello-world-pod.yaml
2.查询Pod
# 简要查询
kubectl get pod hello-world
#JSON格式显示Pod的完整信息
kubectl get pod hello-world --output json
#YAML方式显示Pod的完整信息
kubectl get pod hello-world --output yaml
- 状态和生命周期查询
kubectl describe pod hello-world
3.更新Pod
- 更新
kubectl replace -f hello-world-pod.yaml
但是由于Pod的很多属性没办法修改,比如容器镜像,这时候可以采用–force参数
- 重建Pod
kubectl replace --force -f hello-world-pod.yaml
4.删除Pod
- 通过kubectl delete删除指定Pod
kubectl delete pod hello-world
- 通过kubectl delete批量删除全部Pod
kubectl delete pod --all
静态 Pod
什么是 Static Pod
静态 Pod 在指定的节点上由 kubelet 守护进程直接管理,不需要 API 服务器监管。 与由控制面管理的 Pod(例如,[Deployment]) 不同;kubelet 监视每个静态 Pod(在它崩溃之后重新启动)。
静态 Pod 永远都会绑定到一个指定节点上的 [Kubelet]。
kubelet 会尝试通过 Kubernetes API 服务器为每个静态 Pod 自动创建一个 [镜像 Pod]。 这意味着节点上运行的静态 Pod 对 API 服务来说是可见的,但是不能通过 API 服务器来控制。 Pod 名称将把以连字符开头的节点主机名作为后缀。
最常见的 Static Pod
- etcd
- kube-apiserver
- kube-controller-manager
- kube-scheduler
创建静态Pod有两种方式:配置文件方式和HTTP方式。
配置文件方式
可以通过kubelet的启动参数查看kubelet扫描静态Pod配置文件的路径,如下:
可以到kubelet是由这个配置文件进行启动的,在通过查看此配置文件,内容如下:
apiVersion: kubelet.config.k8s.io/v1beta1
.....
.....
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 0s
syncFrequency: 0s
volumeStatsAggPeriod: 0s
其中staticPodPath: /etc/kubernetes/manifests就是正在运行的kubelet需要监控的配置文件所在的目录,kubelet会定期扫描该目录,并根据该目录下的.yaml或.json文件进行创建操作。
在目录/etc/kubernetes/manifests中放入static-web.yaml文件,内容如下:
---
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
name: static-web
spec:
containers:
- name: static-web
image: nginx
ports:
- name: web
containerPort: 80
等待一会儿,查看本机中已经启动的容器:
[root@k8s-node0 ~]# docker ps|grep static-web
85fd075aaddd nginx "/docker-entrypoint.…" About an hour ago Up About an hour k8s_static-web_static-web-k8s-node0_default_d9890f08374e33f99c31a2a034276178_0
可以看到一个Nginx容器已经被kubelet成功创建了出来。
到Master上查看Pod列表,可以看到这个static pod:
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-f89759699-gn2wn 1/1 Running 1 17h
static-web-k8s-node0 1/1 Running 0 12s
由于静态Pod无法通过API Server直接管理,所以在Master上尝试删除这个Pod时,会使其变成Pending状态,且不会被删除。
[root@k8s-master ~]# kubectl delete pod static-web-k8s-node0
pod "static-web-k8s-node0" deleted
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-f89759699-gn2wn 1/1 Running 1 18h
static-web-k8s-node0 0/1 Pending 0 2s
删除该Pod的操作只能是到其所在Node上将其定义文件static-web.yaml从/etc/kubernetes/manifests目录下删除。
[root@k8s-node0 ~]# rm /etc/kubernetes/manifests/static_pod_web_nginx.yaml
rm: remove regular file ‘/etc/kubernetes/manifests/static_pod_web_nginx.yaml’? y
[root@k8s-node0 ~]# docker ps|grep static-web
[root@k8s-node0 ~]# docker ps|grep static-web
在Master上查看这个Pod:
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-f89759699-gn2wn 1/1 Running 1 18h
HTTP方式
通过设置kubelet的启动参数–manifest-url或kubelet的配置文件加上此配置项,kubelet将会定期从该URL地址下载Pod的定义文件,并以.yaml或.json文件的格式进行解析,然后创建Pod。其实现方式与配置文件方式是一致的。
belet的配置文件加上此配置项,kubelet将会定期从该URL地址下载Pod的定义文件,并以.yaml或.json文件的格式进行解析,然后创建Pod。其实现方式与配置文件方式是一致的。
以上是关于再战 k8s:Pod 定义及基本用法的主要内容,如果未能解决你的问题,请参考以下文章