k8s 为什么需要Pod?

Pod,是 Kubernetes 项目中最小的 API 对象,更加专业的说,Pod,是 Kubernetes 项目的原子调度单位。

Pod 是 Kubernetes 里的原子调度单位。这就意味着,Kubernetes 项目的调度器,是统一按照 Pod 而非容器的资源需求进行计算的。

例子:

所以,像 imklog、imuxsock 和 main 函数主进程这样的三个容器,正是一个典型的由三个容器组成的 Pod。这样 Kubernetes 项目在调度时,自然就会去选择可用内存等于 3 GB 的 node-1 节点进行 绑定,而根本不会考虑 node-2。

但并不是所有有"关系"的容器都属于同一个 Pod。比如,PHP 应用容器和 MySQL 虽然会发生访问关系,但并没有必要、也不应该部署在同一台机器上,它们更适合做成两个 Pod。

Pod 在 Kubernetes 项目里还有更重要的意义,那就是:容器设计模式。

为了理解这一层含义,我就必须先给你介绍一下Pod 的实现原理。

首先,关于 Pod 最重要的一个事实是:它只是一个逻辑概念。

问题:Pod 是怎么被"创建"出来的呢?

Kubernetes 真正处理的,还是宿主机操作系统上 Linux 容器的 Namespace 和 Cgroups,而并不存在一个所谓的 Pod 的边界或者隔离环境。

也就是说 Pod,其实是一组共享了某些资源的容器。更具体的说 Pod 里的所有容器,共享的是同一个 Network Namespace,并且可以声明共享同一个 Volume。

那么你会认为假如 一个有A和B两个容器的Pod,不就等同于一个容器共享另一个容器的网络和Volume吗?通过 docker run --net=B --volumes-from=B --name=A image-A ...

这样的问题,容器B必须比容器A先启动,这样Pod里面多个容器就不是对等关系,而是拓扑关系了。

那么在 Kubernetes 项目里面,Pod 的实现需要使用一个中间容器,这个容器叫做 infra 容器,这个 Pod 中,Infra 容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起。

Infra 容器一定要占用极少的资源,所以它使用的是一个非常特殊的镜像,叫 作:k8s.gcr.io/pause。

对于 Pod 里的容器 A 和容器 B 来说:

  • 它们可以直接使用 localhost 进行通信;
  • 它们看到的网络设备跟 Infra 容器看到的完全一样;
  • 一个 Pod 只有一个 IP 地址,也就是这个 Pod 的 Network Namespace 对应的 IP 地址;
  • 当然,其他的所有网络资源,都是一个 Pod 一份,并且被该 Pod 中的所有容器共享;
  • Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关。
    而对于同一个 Pod 里面的所有用户容器来说,它们的进出流量,也可以认为都是通过 Infra 容器完 成的。这一点很重要,因为将来如果你要为 Kubernetes 开发一个网络插件时应该重点考虑的是 如何配置这个 Pod 的 Network Namespace,而不是每一个用户容器如何使用你的网络配置,这是 没有意义的。

有了这个设计之后,共享 Volume 就简单多了:Kubernetes 项目只要把所有 Volume 的定义都设计在 Pod 层级即可。

这样,一个 Volume 对应的宿主机目录对于 Pod 来说就只有一个,Pod 里的容器只要声明挂载这个 Volume,就一定可以共享这个 Volume 对应的宿主机目录。

栗子:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: "two-containers"
  namespace: default
spec:
  restartPolicy: Never
  volumes:
  - name: shared-data
    hostPath:
      path: /data
  containers:
  - name: nginx-container
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
  - name: debian-container
    image: debian
    volumeMounts:
    - name: shared-data
      mountPath: /pod-data
    command: ["/bin/sh"]
    args: ["-c", "echo Hello from the debian container > /pod-data/index.html"]

解释:

该例子中有两个 debian-container 和 nginx-container 都声明挂载了 shared-data 这个 Volume。而 shared-data 是 hostPath 类型。所以,它对应在宿主机上的目录就是:/data。而这个目录,其实就被同时绑定挂载进了上述两个容器当中。

这就是为什么,nginx-container 可以从它的 /usr/share/nginx/html 目录中,读取到 debian-container 生成的 index.html 文件的原因。

"容器设计模式" 是什么?

Pod 这种"超亲密关系"容器的设计思想,实际上就是希望,当用户想在一个容器里跑多个功能并不相关的应用时,应该优先考虑它们是不是更应该被描述成一个 Pod 里的多个容器。

为了能够掌握这种思考方式,你就应该尽量尝试使用它来描述一些用单个容器难以解决的问题。

典型例子1:WAR 包与 Web 服务器。

我们现在有一个 Java Web 应用的 WAR 包,它需要被放在 Tomcat 的 webapps 目录下运行起来。

有了 Pod 之后,这样的问题就很容易解决了。我们可以把 WAR 包和 Tomcat 分别做成镜像,然后把它们作为一个 Pod 里的两个容器"组合"在一起。这个 Pod 的配置文件如下所示:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: "javaweb-2"
spec:
  initContainers:
  - image: yftime/sample:v2
    name: war
    command: ["cp","/sample.war", "/app"]
    volumeMounts:
    - mountPath: /app
      name: app-volume
  containers:
  - name: yftime/tomcat:7.0
    image: tomcat
    command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
    volumeMounts:
    - name: app-volume
      mountPath: /root/apache-tomcat-7.0.42-v2/webapps
    ports:
    - containerPort: 8080
      hostPort: 8001
  volumes:
  - name: app-volume
    emptyDir: {}

Pod 里面定义两个容器,第一个容器是 sample.war 放在根目录下,第二个容器是标准的 Tomcat 镜像。

WAR 包容器的类型不再是一个普通容器,而是一个 Init Container 类型 的容器。在 Pod 中,所有 Init Container 定义的容器,都会比 spec.containers 定义的用户容器先启动。并 且,Init Container 容器会按顺序逐一启动,而直到它们都启动并且退出了,用户容器才会启动。 所以,这个 Init Container 类型的 WAR 包容器启动后,我执行了一句 "cp /sample.war /app", 把应用的 WAR 包拷贝到 /app 目录下,然后退出。

而后这个 /app 目录,就挂载了一个名叫 app-volume 的 Volume。

Tomcat 容器,同样声明了挂载 app-volume 到自己的 webapps 目录下。所以,等 Tomcat 容器启动时,它的 webapps 目录下就一定会存在 sample.war 文件:这个文件 正是 WAR 包容器启动时拷贝到这个 Volume 里面的,而这个 Volume 是被这两个容器共享的。

像这样,我们就用一种"组合"方式,解决了 WAR 包与 Tomcat 容器之间耦合关系的问题。实际上,这个所谓的"组合"操作,正是容器设计模式里最常用的一种模式,它的名字叫: sidecar。顾名思义,sidecar 指的就是我们可以在一个 Pod 中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作。

典型例子2:则是容器的日志收集

比如,我现在有一个应用,需要不断地把日志文件输出到容器的 /var/log 目录中。这时,我就可以把一个 Pod 里的 Volume 挂载到应用容器的 /var/log 目录上。

这样,接下来 sidecar 容器就只需要做一件事儿,那就是不断地从自己的 /var/log 目录里读取日志 文件,转发到 MongoDB 或者 Elasticsearch 中存储起来。这样,一个最基本的日志收集工作就完 成了。

跟第一个例子一样,这个例子中的 sidecar 的主要工作也是使用共享的 Volume 来完成对文件的操 作。

深入理解 Pod 对象概念

牢记:Pod 扮演的是传统部署 环境里"虚拟机"的角色。这样的设计,是为了使用户从传统环境(虚拟机环境)向 Kubernetes(容器环境)的迁移,更加平滑。

比如,凡是调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的。

Pod 中几个重要字段的含义和用法:

yaml 复制代码
apiVersion: v1
kind: Pod
spec:
  nodeSelector:
  disktype: ssd

这样的一个配置,意味着这个 Pod 永远只能运行在携带了"disktype: ssd"标签(Label)的节点 上;否则,它将调度失败。

Pod 生命周期的变化:

  1. Pending。这个状态意味着,Pod 的 YAML 文件已经提交给了 Kubernetes,API 对象已经被 创建并保存在 Etcd 当中。但是,这个 Pod 里有些容器因为某种原因而不能被顺利创建。比 如,调度不成功。
  2. Running。这个状态下,Pod 已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创 建成功,并且至少有一个正在运行中
  3. Succeeded。这个状态意味着,Pod 里的所有容器都正常运行完毕,并且已经退出了。这种情 况在运行一次性任务时最为常见
  4. Failed。这个状态下,Pod 里至少有一个容器以不正常的状态(非 0 的返回码)退出。这个状 态的出现,意味着你得想办法 Debug 这个容器的应用,比如查看 Pod 的 Events 和日志。
  5. Unknown。这是一个异常状态,意味着 Pod 的状态不能持续地被 kubelet 汇报给 kube- apiserver,这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题。

深入解析 Pod 对象进阶

Kubernetes 支持的 Projected Volume 一共有四种

  1. Secret;

  2. ConfigMap;

  3. Downward API;

  4. ServiceAccountToken。

Secret:

作用是帮你把Pod想要访问的加密数据存放到 Etcd 中,然后通过在 Pod 的容器挂载 Volume 的方式,访问到这些 Secret 里面保存的信息了。典型场景就是存放数据库 Credentials 信息了。

讲解 pod.spec.volumes 字段:

shell 复制代码
   awsElasticBlockStore	<Object>
   azureDisk	<Object>
   azureFile	<Object>
   cephfs	<Object>
   cinder	<Object>
   configMap	<Object>
   csi	<Object>
   downwardAPI	<Object>
   emptyDir	<Object>
   ephemeral	<Object>

   fc	<Object>
   flexVolume	<Object>
   flocker	<Object>
   gcePersistentDisk	<Object>
   gitRepo	<Object>
   glusterfs	<Object>
   hostPath	<Object>
   iscsi	<Object>
   name	<string> -required-
   nfs	<Object>
   persistentVolumeClaim	<Object>

   volumes#persistentvolumeclaims
   photonPersistentDisk	<Object>
   portworxVolume	<Object>
   projected	<Object>
   quobyte	<Object>
   rbd	<Object>
   scaleIO	<Object>
   secret	<Object>
   storageos	<Object>
   vsphereVolume	<Object>
pod字段
activeDeadlineSeconds
affinity
automountServiceAccountToken
containers 
dnsConfig
dnsPolicy
ephemeralContainers
hostAliases
hostIPC
hostNetwork
hostPID
hostname
imagePullSecrets
initContainers
nodeName
nodeSelector
overhead
preemptionPolicy
priority
priorityClassName
readinessGates
restartPolicy
runtimeClassName
schedulerName
securityContext
serviceAccount
serviceAccountName
setHostnameAsFQDN
shareProcessNamespace
subdomain
terminationGracePeriodSeconds
tolerations
topologySpreadConstraints
volumes
pod.spec.containers 字段

args

command

env

envFrom

image

imagePullPolicy:

  • 定义了镜像拉取策略(默认Always每次创建Pod都重新拉取一次镜像)似于 nginx:latest 这样的名字时,ImagePullPolicy 也会被认为 Always
  • Never 或者 IfNotPresent,则意味着 Pod 永远不会主动拉取这个镜像,或 者只在宿主机上不存在这个镜像时才拉取。
    lifecycle:

livenessProbe

name

ports

readinessProbe

resources

securityContext

startupProbe

stdin

stdinOnce

terminationMessagePath

terminationMessagePolicy

tty

volumeDevices

volumeMounts

workingDir

apiVersion: npool.yf.io/v1alpha1
kind: NodePool
metadata:
  name: demo
spec:
	size: 3  # 副本数量
	image: cnych/etcd:v3.4.13  # 镜像
相关推荐
尚雷55801 小时前
云计算IaaS-PaaS-SaaS三种服务模式转至元数据结尾
云原生·云计算·paas
LeonNo115 小时前
k8s,operator
云原生·容器·kubernetes
云川之下5 小时前
【k8s源码】kubernetes-1.22.3\staging 目录作用
云原生·容器·kubernetes
怡雪~5 小时前
k8s的Pod亲和性
linux·容器·kubernetes
A5rZ5 小时前
CTF: 在本地虚拟机内部署CTF题目docker
运维·网络安全·docker·容器
fragrans6 小时前
设置docker镜像加速器
运维·docker·容器
Karoku0667 小时前
【自动化部署】Ansible 基础命令行模块
运维·服务器·数据库·docker·容器·自动化·ansible
诡异森林。10 小时前
Docker--Docker Container(容器)
docker·容器·eureka
代码浪人10 小时前
docker 基于Debian镜像安装FreeSwitch1.10.7
docker·容器·debian·freeswitch
qichengzong_right10 小时前
CNCF云原生生态版图-分类指南(一)- 观测和分析
linux·云原生