如何理解Kubernetes中的Pod

为什么Kubernetes不直接使用已经非常成熟稳定的容器?为什么要再单独抽象出一个Pod对象?为什么几乎所有人都说Pod是Kubernetes里最核心最基本的概念呢?

Pod 生命周期

和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。 Pod 会被创建、赋予一个唯一的 ID, 并被调度到节点,并在终止(根据重启策略)或删除之前一直运行在该节点。

如果一个节点死掉了,调度到该节点的 Pod 也被计划在给定超时期限结束后删除。

Pod 自身不具有自愈能力。如果 Pod 被调度到某节点而该节点之后失效, Pod 会被删除;类似地,Pod 无法在因节点资源耗尽或者节点维护而被驱逐期间继续存活。 Kubernetes 使用一种高级抽象来管理这些相对而言可随时丢弃的 Pod 实例, 称作控制器。

任何给定的 Pod (由 UID 定义)从不会被"重新调度(rescheduled)"到不同的节点; 相反,这一 Pod 可以被一个新的、几乎完全相同的 Pod 替换掉。 如果需要,新 Pod 的名字可以不变,但是其 UID 会不同。

为什么要有Pod

Pod这个词原意是"豌豆荚",后来又延伸出"舱室""太空舱"等含义,你可以看一下这张图片,形象地来说Pod就是包含了很多组件、成员的一种结构。

容器技术可以实现让进程在一个"沙盒"环境里运行,具有良好的隔离性,对应用是一个非常好的封装。

不过,当容器技术进入到现实的生产环境中时,这种隔离性就带来了一些麻烦。因为很少有应用是完全独立运行的,经常需要几个进程互相协作才能完成任务,比如搭建一个网站就需要Nginx、业务容器、数据库容器三个容器一起工作。

有一些特殊情况,多个应用结合得非常紧密以至于无法把它们拆开。比如,有的应用运行前需要其他应用帮它初始化一些配置,还有就是日志代理,它必须读取另一个应用存储在本地磁盘的文件再转发出去。这些应用如果被强制分离成两个容器,切断联系,就无法正常工作了。

那么把这些应用都放在一个容器里运行可不可以呢?

当然可以,但这并不是一种好的做法。因为容器的理念是对应用的独立封装,它里面就应该是一个进程、一个应用,如果里面有多个应用,不仅违背了容器的初衷,也会让容器更难以管理。

为了解决这样多应用联合运行的问题,同时还要不破坏容器的隔离,就需要在容器外面再建立一个"收纳舱",让多个容器既保持相对独立,又能够小范围共享网络、存储等资源,而且永远是"绑在一起"的状态。

所以,Pod的概念也就呼之欲出了,容器正是"豆荚"里那些小小的"豌豆",你可以在Pod的YAML里看到,"spec.containers"字段其实是一个数组,里面允许定义多个容器。

如果拿"小板房"来比喻的话,Pod就是由客厅、卧室、厨房等预制房间拼装成的一个齐全的生活环境,不仅同样具备易于拆装易于搬迁的优点,而且要比单独的"一居室"功能强大得多,能够让进程"住"得更舒服。

如果没有 Pod,直接使用容器来管理应用会有什么样的麻烦?

Pod是一个或多个容器的集合,首先,如果Pod中运行的是单容器,则使用Pod包一层的好处是可以管理其中容器的状态如暂停、重启等,比如可以替换容器镜像;其次,如果Pod中运行的是多容器,则这些容器之间共享网络与存储,可以高效的分享资源与本地化服务调用,在调度方面,如果关联容器不使用Pod包一层则会增加调试的复杂度,影响性能,有了Pod则可以统一调度方便管理。

  1. 一般一个pod中只包含一个容器,但因为containers是一个数组,里面是可以有多个容器的。 Pod里的多个容器共享IP地址、存储卷、进程空间,pod就类似逻辑主机、虚拟机了,这些容器就可以像在主机里一样协同工作。
  2. 可以把 Pod 看作是介于容器之上的一层抽象,之所以需要这一层抽象是因为容器与容器之间有着不确定的关系,有的容器需要与彼此隔离,而有的容器却需要彼此交互。当容器规模增大,容器之间的作用关系就会变得极其复杂,难于管理。Pod 的出现就是为了解决容器管理的问题,让大规模应用下的容器编排变得更加清晰有序,易于维护
  3. 不管是容器还是 Pod,都是虚拟概念。把普通进程或应用加上权限限制就成了容器,再把容器加上权限限制就成了 Pod。说白了,就是不断地抽象封装,这也是软件中解决复杂问题的唯一手段。容器之于Pod,就好比 线程之于进程、函数之于类、文件之于文件夹等等

为什么Pod是Kubernetes的核心对象

因为Pod是对容器的"打包",里面的容器是一个整体,总是能够一起调度、一起运行,绝不会出现分离的情况,而且Pod属于Kubernetes,可以在不触碰下层容器的情况下任意定制修改。所以有了Pod这个抽象概念,Kubernetes在集群级别上管理应用就会"得心应手"了。

Kubernetes让Pod去编排处理容器,然后把Pod作为应用调度部署的最小单位,Pod也因此成为了Kubernetes世界里的"原子"(当然这个"原子"内部是有结构的),基于Pod就可以构建出更多更复杂的业务形态了。

下面的这张图你也许在其他资料里见过,它从Pod开始,扩展出了Kubernetes里的一些重要API对象,比如配置信息ConfigMap、离线作业Job、多实例部署Deployment等等,它们都分别对应到现实中的各种实际运维需求。

从图中能够看出来,所有的Kubernetes资源都直接或者间接地依附在Pod之上,所有的Kubernetes功能都必须通过Pod来实现,所以Pod理所当然地成为了Kubernetes的核心对象。

如何使用YAML描述Pod

下面这段YAML代码就描述了一个简单的Pod,名字是"ngx-pod",再附加上一些标签

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: ngx-pod
  labels:
    env: demo
    owner: evan

spec:
  containers:
  - image: nginx:alpine
    name: ngx
    ports:
    - containerPort: 80

下面这段YAML代码就描述了一个简单的Pod,名字是"busy-pod",再附加上一些标签

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: busy-pod
  labels:
    owner: evan
    env: demo
    region: north
    tier: back
spec:
  containers:
  - image: busybox:latest
    name: busy
    imagePullPolicy: IfNotPresent
    env:
      - name: os
        value: "ubuntu"
      - name: debug
        value: "on"
    command:
      - /bin/echo
    args:
      - "$(os), $(debug)"		
		
		

因为Pod也是API对象,所以它也必然具有apiVersion、kind、metadata、spec这四个基本组成部分。

"apiVersion"和"kind"这两个字段很简单,对于Pod来说分别是固定的值 v1Pod

"metadata"里应该有 namelabels 这两个字段。我们在使用Docker创建容器的时候,可以不给容器起名字,但在Kubernetes里,Pod必须要有一个名字。name 只是一个基本的标识,信息有限,所以 labels 字段就派上了用处。它可以添加任意数量的Key-Value,给Pod"贴"上归类的标签,结合 name 就更方便识别和管理了。

比如说,我们可以根据运行环境,使用标签 env=dev/test/prod,或者根据所在的数据中心,使用标签 region: north/south,还可以根据应用在系统中的层次,使用 tier=front/middle/back ......如此种种,只需要发挥你的想象力。

"spec"字段由于需要管理、维护Pod这个Kubernetes的基本调度单元。

"containers"是一个数组,里面的每一个元素又是一个container对象,也就是容器。

和Pod一样,container对象也必须要有一个 name 表示名字,然后当然还要有一个 image 字段来说明它使用的镜像,这两个字段是必须要有的,否则Kubernetes会报告数据验证错误。

container对象的其他字段基本上都可以和Docker对应:

  • ports:列出容器对外暴露的端口,和Docker的 -p 参数有点像。
  • imagePullPolicy:指定镜像的拉取策略,可以是Always/Never/IfNotPresent,一般默认是IfNotPresent,也就是说只有本地不存在才会远程拉取镜像,可以减少网络消耗。
  • env:定义Pod的环境变量,和Dockerfile里的 ENV 指令有点类似,但它是运行时指定的,更加灵活可配置。
  • command:定义容器启动时要执行的命令,相当于Dockerfile里的 ENTRYPOINT 指令。
  • args:它是command运行时的参数,相当于Dockerfile里的 CMD 指令,这两个命令和Docker的含义不同,要特别注意。

把YAML文件和Docker命令对比一下,你就可以看出,YAML在 spec.containers 字段里用"声明式"把容器的运行状态描述得非常清晰准确,要比 docker run 那长长的命令行要整洁的多,对人、对机器都非常友好。

如何使用kubectl操作Pod

有了描述Pod的YAML文件,现在我就介绍一下用来操作Pod的kubectl命令。

kubectl applykubectl delete 这两个命令,它们可以使用 -f 参数指定YAML文件创建或者删除Pod,例如:

arduino 复制代码
kubectl apply -f busy-pod.yml
kubectl delete -f busy-pod.yml

不过,因为我们在YAML里定义了"name"字段,所以也可以在删除的时候直接指定名字来删除:

arduino 复制代码
kubectl delete pod busy-pod

和Docker不一样,Kubernetes的Pod不会在前台运行,只能在后台(相当于默认使用了参数 -d),所以输出信息不能直接看到。我们可以用命令 kubectl logs,它会把Pod的标准输出流信息展示给我们看,在这里就会显示出预设的两个环境变量的值:

kubectl logs busy-pod
typescript 复制代码
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl logs busy-pod
ubuntu, on

使用命令 kubectl get pod 可以查看Pod列表和运行状态:

arduino 复制代码
kubectl get pod
sql 复制代码
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl get pod
NAME                       READY   STATUS              RESTARTS      AGE
busy-pod                   0/1     CrashLoopBackOff    1 (4s ago)    5s
ngx-pod                    1/1     Running             0             16m
redis-ds-2f5m9             1/1     Running             1 (20m ago)   10h

你会发现这个Pod运行有点不正常,状态是"CrashLoopBackOff",那么我们可以使用命令 kubectl describe 来检查它的详细状态,它在调试排错时很有用:

sql 复制代码
kubectl describe pod busy-pod
sql 复制代码
Events:
  Type     Reason     Age                 From               Message
  ----     ------     ----                ----               -------
apiVersion: v1
  Normal   Scheduled  2m12s               default-scheduler  Successfully assigned default/busy-pod to minikube
  Normal   Pulling    2m12s               kubelet            Pulling image "busybox:latest"
  Normal   Pulled     117s                kubelet            Successfully pulled image "busybox:latest" in 15.345233668s (15.34526314s including waiting)
  Normal   Created    20s (x5 over 117s)  kubelet            Created container busy
  Normal   Started    20s (x5 over 116s)  kubelet            Started container busy
  Normal   Pulled     20s (x4 over 116s)  kubelet            Container image "busybox:latest" already present on machine
  Warning  BackOff    19s (x9 over 115s)  kubelet            Back-off restarting failed container busy in pod busy-pod_default(71cd657f-3aaa-4885-9927-efaec7aa9b9b)

通常需要关注的是末尾的"Events"部分,它显示的是Pod运行过程中的一些关键节点事件。对于这个busy-pod,因为它只执行了一条 echo 命令就退出了,而Kubernetes默认会重启Pod,所以就会进入一个反复停止-启动的循环错误状态。

因为Kubernetes里运行的应用大部分都是不会主动退出的服务,所以我们可以把这个busy-pod删掉,用上次课里创建的ngx-pod.yml,启动一个Nginx服务,这才是大多数Pod的工作方式。

kubectl apply -f ngx-pod.yml

启动之后,我们再用 kubectl get pod 来查看状态,就会发现它已经是"Running"状态了:

sql 复制代码
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl get pod
NAME                       READY   STATUS              RESTARTS      AGE
busy-pod                   0/1     CrashLoopBackOff    1 (4s ago)    5s
ngx-pod                    1/1     Running             0             16m
redis-ds-2f5m9             1/1     Running             1 (20m ago)   10h

命令 kubectl logs 也能够输出Nginx的运行日志:

bash 复制代码
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl logs ngx-pod
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up

另外,kubectl也提供与docker类似的 cpexec 命令,kubectl cp 可以把本地文件拷贝进Pod,kubectl exec 是进入Pod内部执行Shell命令,用法也差不多。

比如我有一个"a.txt"文件,那么就可以使用 kubectl cp 拷贝进Pod的"/tmp"目录里:

bash 复制代码
echo 'aaa' > a.txt
kubectl cp a.txt ngx-pod:/tmp

不过 kubectl exec 的命令格式与Docker有一点小差异,需要在Pod后面加上 --,把kubectl的命令与Shell命令分隔开,你在用的时候需要小心一些:

sql 复制代码
kubectl exec -it ngx-pod -- sh
bash 复制代码
 xuyatao@evan171206  ~/learn/k8s/kube-demo  echo 'aaa' > a.txt
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl cp a.txt ngx-pod:/tmp
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl exec -it ngx-pod -- sh
/ # ls
bin                   docker-entrypoint.sh  lib                   opt                   run                   sys                   var
dev                   etc                   media                 proc                  sbin                  tmp
docker-entrypoint.d   home                  mnt                   root                  srv                   usr
/ # cd /tmp/
/tmp # cat a.txt
aaa
/tmp # nginx -v
nginx version: nginx/1.25.3
/tmp # uname -a
Linux ngx-pod 5.15.49-linuxkit #1 SMP Tue Sep 13 07:51:46 UTC 2022 x86_64 Linux
/tmp # exit
 xuyatao@evan171206  ~/learn/k8s/kube-demo 

pod操作

sql 复制代码
 ✘ xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl get pod
NAME                          READY   STATUS             RESTARTS        AGE
busy-pod                      0/1     CrashLoopBackOff   9 (3m27s ago)   24m
hello-world-cb5c88574-8xxfg   1/1     Running            2 (4h13m ago)   21h
hello-world-cb5c88574-cdnlv   1/1     Running            2 (4h13m ago)   21h
hello-world-cb5c88574-g56r8   1/1     Running            2 (4h13m ago)   21h
nginx                         1/1     Running            2 (4h13m ago)   21h
nginx-748c667d99-r6jxm        1/1     Running            2 (4h13m ago)   20h
ngx                           1/1     Running            3 (4h13m ago)   259d
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl delete pod nginx
pod "nginx" deleted
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl delete pod ngx
pod "ngx" deleted
 xuyatao@evan171206  ~/learn/k8s/kube-demo  kubectl get pod
NAME                          READY   STATUS             RESTARTS        AGE
busy-pod                      0/1     CrashLoopBackOff   9 (4m38s ago)   26m
hello-world-cb5c88574-8xxfg   1/1     Running            2 (4h14m ago)   21h
hello-world-cb5c88574-cdnlv   1/1     Running            2 (4h14m ago)   21h
hello-world-cb5c88574-g56r8   1/1     Running            2 (4h14m ago)   21h
nginx-748c667d99-r6jxm        1/1     Running            2 (4h14m ago)   20h  

总结

Pod是用来编排一个或多个容器,让这些容器共享网络、存储等资源,总是共同调度,从而紧密协同工作。

因为Pod比容器更能够表示实际的应用,所以Kubernetes不会在容器层面来编排业务,而是把Pod作为在集群里调度运维的最小单位。

相关推荐
吃面不喝汤661 小时前
Flask + Swagger 完整指南:从安装到配置和注释
后端·python·flask
讓丄帝愛伱2 小时前
spring boot启动报错:so that it conforms to the canonical names requirements
java·spring boot·后端
weixin_586062022 小时前
Spring Boot 入门指南
java·spring boot·后端
凡人的AI工具箱8 小时前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
是店小二呀8 小时前
【C++】C++ STL探索:Priority Queue与仿函数的深入解析
开发语言·c++·后端
canonical_entropy9 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
我叫啥都行9 小时前
计算机基础知识复习9.7
运维·服务器·网络·笔记·后端
无名指的等待71210 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
.生产的驴11 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
AskHarries11 小时前
Spring Boot利用dag加速Spring beans初始化
java·spring boot·后端