深入剖析Kubernetes 学习笔记

本文详细介绍了Kubernetes的基础概念,包括容器化的原理(cgroup、namespace、rootfs和容器引擎)、Docker的工作机制以及Kubernetes的关键组件。重点讲解了Pod作为调度基本单位的原因及其意义,探讨了容器健康检查、恢复机制以及控制器模型如Deployment和StatefulSet的工作原理。此外,还涵盖了存储管理(Volume、PersistentVolumeClaim和PVC)以及网络模型,强调了CRI(容器运行时接口)的重要性和实现方式。文章最后讨论了Kubernetes的安全容器解决方案,如Kata Containers和gVisor,以及监控和日志管理的实现策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Container & Kubernetes

【总结自张磊-深入剖析Kubernetes
【图片看不到,可以去

Container

容器 = cgroup + namespace + rootfs + 容器引擎
  • Cgroup: 资源控制(限制namspace隔离进程的资源,但是Cgroups 对资源的限制能力也有很多不完善的地方, /proc 文件系统的问题,/proc 文件系统不了解 Cgroups 限制的存在)
  • namespace: 访问隔离(进程级别的隔离,看不见其他进程,但是资源都是共享的,因此存在资源被占用的情况,也存在把所有资源都吃掉的情况)
  • rootfs:文件系统隔离。镜像的本质就是一个rootfs文件
  • 容器引擎:生命周期控制

**Namespace 的作用是“隔离”,它让应用进程只能看到该 Namespace内的“世界”;而 Cgroups 的作用是“限制”,它给这个“世界”围上了一圈看不见的墙。**这
么一折腾,进程就真的被“装”在了一个与世隔绝的房间里,而这些房间就是 PaaS 项目赖以生存的应用“沙盒”。,“Namespace 做隔离,Cgroups 做限制,rootfs 做文件系统”

参考链接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NxjfHsEj-1623207279252)(https://gitee.com/whoconli/k8s-learning/blob/master/Container%20&%20Kubernetes.assets/image-20210510230243050.png)]

  • Hypervisor 的软件是虚拟机最主要的部分。它通过硬件虚拟化功能,模拟出了运行一个操作系统需要的各种硬件,比如 CPU、内存、
    I/O 设备等等。然后,它在这些虚拟的硬件上安装了一个新的操作系统,即 Guest OS。

  • Docker 项目帮助用户启动的,还是原来的应用进程,只不过在创建这些进程时,Docker 为它们加上了各种各样的Namespace 参数。而不是使用docker engine代替hypervisor,但是多个容器之间使用的就还是同一个宿主机的操作系统内核,因此linux版本和不同系统的容器并不兼容。而虚拟机则是可以做到这种情况

  • 在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,比如时间;此外,共享宿主机内核的事实,容器给应用暴露出来的攻击面是相当大的,应用“越狱”的难度自然也比虚拟机低得多

  • 在生产环境中,这个问题必须进行修正,否则应用程序在容器里读取到的 CPU 核数、可用内存等信息都是宿主机上的数据,这会给应用的运行带来非常大的困惑和风险。这也是在企业中,容器化应用碰到的一个常见问题,也是容器相较于虚拟机另一个不尽如人意的地方

Docker 原理

最核心的原理实际上就是为待创建的用户进程:

  1. 启用 Linux Namespace 配置;
  2. 设置指定的 Cgroups 参数;
  3. 切换进程的根目录(Change Root)。
docker exec 是怎么做到进入容器里的呢?

Linux Namespace 创建的隔离空间虽然看不见摸不着,但一个进程的 Namespace 信息在宿主机上是确确实实存在的,并且是以一个文件的方式存在。也就是通过查看宿主机的 proc 文件,看到这个 25686 进程的所有 Namespace 对应的文件

ls -l /proc/25686/ns

一个进程的每种 Linux Namespace,都在它对应的/proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上,这也就意味着:一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。而这个操作所依赖的,乃是一个名叫 setns() 的 Linux 系统调用

docker volume挂载
  • 允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作

具体实现:直接挂载

  • 只需要在 rootfs 准备好之后,在执行 chroot 之前,把 Volume 指定的宿主机目录(比如 /home 目录),挂载到指定的容器目录(比如 /test 目录)在宿主机上对应的目录(即/var/lib/docker/aufs/mnt/[可读写层 ID]/test)上
  • 于执行这个挂载操作时,“容器进程”已经创建了,也就意味着此时 Mount Namespace 已经开启了。所以,这个挂载事件只在这个容器里可见。你在宿主机上,是看不见容器内部的这个挂载点的。这就保证了容器的隔离性不会被 Volume 打破。
  • 使用到的挂载技术,就是 Linux 的绑定挂载(Bind Mount)机制。它的主要作用就是,允许你将一个目录或者文件,而不是整个设备,挂载到一个指定的目录上。并且,这时你在该挂载点上进行的任何操作,只是发生在被挂载的目录或者文件上,而原挂载点的内容则会被隐
    藏起来且不受影响。(实验被挂载节点还能看到修改的东西,源挂载点应该就是这个名字,也就是/test里面东西不可见了,unmount后恢复)
  • 容器 Volume 里的信息,并不会被 docker commit 提交掉;但这个挂载点目录/test 本身,则会出现在新的镜像当中。
容器是“单进程模型”

容器的“单进程模型”,并不是指容器里只能运行“一个”进程,而是指容器没有管理多个进程的能力,这是因为容器里 PID=1 的进程就是应用本身,其他的进程都是这个PID=1 进程的子进程。所以一般一个容器里面只有一个进程。

Kubenertes

pod

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

重要字段和含义
  • NodeSelector:供用户将Pod和Node进行绑定的字段
  • NodeName:赋值意味该Pod已被调度
为何以pod作为调度基本单位?

因为存在容器成组问题,这种问题在调度上比较难以来做(成组调度问题),资源囤积带来了不可避免的调度效率损失和死锁的可能性;而乐观调度的复杂程度,则不是常规技术团队所能驾驭的。而pod的引入,就是将容器打包在一起,可以不管这种成组调度问题了。

pod的意义–容器设计模式
  • Pod,其实是一组共享了某些资源的容器,Pod 里的所有容器,共享的是同一个 Network Namespace,并且可以声明共享同一个Volume。

  • 在 Kubernetes 项目里,Pod 的实现需要使用一个中间容器,这个容器叫作 Infra 容器。在这个 Pod 中,Infra 容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起。使用的是一个非常特殊的镜像,叫作:k8s.gcr.io/pause。这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有 100~200 KB 左右。对于 Pod 里的容器 A 和容器 B 来说

    • 它们可以直接使用 localhost 进行通信;(容器本地通信就好了,不需要配置内网ip)
    • 它们看到的网络设备跟 Infra 容器看到的完全一样;(方便构建网络插件
    • 一个 Pod 只有一个 IP 地址,也就是这个 Pod 的 Network Namespace 对应的 IP 地址;
    • 当然,其他的所有网络资源,都是一个 Pod 一份,并且被该 Pod 中的所有容器共享;(比如volume)
    • Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bg67dpWT-1623207279254)(https://gitee.com/whoconli/k8s-learning/blob/master/Container%20&%20Kubernetes.assets/image-20210514000023852.png)]

projected Volume

v1.11之后特性,存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换,是为容器提供预先定义好的数据,有四种:

  • Secret,
    • 把 Pod 想要访问的加密数据,存放到 Etcd 中。然后,你就可以通过在 Pod 的容器里挂载 Volume 的方式,访问到这些 Secret里保存的信息了
    • Secret 对象要求这些数据必须是经过 Base64 转码的
  • ConfigMap
    • 保存的是不需要加密的、应用所需的配置信息
  • Downward API
    • 声明了要暴露 Pod 的 一些 信息给容器,让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息。
  • ServiceAccountToken
    • Service Account 对象的作用,就是 Kubernetes 系统内置的一种“服务账户”,它是 Kubernetes 进行权限分配的对象。比如,Service Account A,可以只被允许对 Kubernetes API 进行 GET 操作,而 Service Account B,则可以有 Kubernetes API 的所有操作的权限。
    • 这样的 Service Account 的授权信息和文件,实际上保存在它所绑定的一个特殊的 Secret 对象里的。这个特殊的 Secret 对象,就叫作ServiceAccountToken。任何运行在 Kubernetes 集群上的应用,都必须使用这个 ServiceAccountToken 里保存的授权信息,也就是 Token,才可以合法地访问 API Server。
容器健康检查和恢复机制
  • livenessProbe
    • 除了在容器中执行命令外,livenessProbe 也可以定义为发起 HTTP 或者 TCP 请求的方式
  • restartPolicy:pod.spec.restartPolicy,默认值是 Always
    • Pod 的恢复过程,永远都是发生在当前节点上,而不会跑到别的节点上去。
    • 事实上,一旦一个 Pod 与一个节点(Node)绑定,除非这个绑定发生了变化(pod.spec.node 字段被修改),否则它永远都不会离开这个节点(被调度了),这也就意味着,如果这个宿主机宕机了,这个 Pod 也不会主动迁移到其他节点上去。而如果你想让 Pod 出现在其他的可用节点上,就必须使用 Deployment 这样的“控制器”来管理Pod,哪怕你只需要一个 Pod 副本
      • Always:在任何情况下,只要容器不在运行状态,就自动重启容器;
      • OnFailure: 只在容器 异常时才自动重启容器;
      • Never: 从来不重启容器。
    • 只要 Pod 的 restartPolicy 指定的策略允许重启异常的容器(比如:Always),那么这个 Pod就会保持 Running 状态,并进行容器重启。否则,Pod 就会进入 Failed 状态。
    • 对于包含多个容器的 Pod,只有它里面所有的容器都进入异常状态后,Pod 才会进入 Failed 状态

控制器模型

  • 状态信息来源
    • kubelet 通过心跳汇报的容器状态和节点状态
    • 监控系统中保存的应用监控数据
    • 控制器主动收集的它自己感兴趣的信息,这些都是常见的实际状态的来源。
  • 控制方法:循环控制control loop
Deployment控制器
  • Pod的“水平扩展 / 收缩”(horizontal scaling out/in)

  • ReplicaSet,由副本数目的定义和一个 Pod 模板组成的。不难发现,它的定义其实是 Deployment 的一个子集,实现pod的滚动更新。

  • 对于一个 Deployment 所管理的 Pod,它的 ownerReference 是ReplicaSet

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sWbns9bF-1623207279257)(https://gitee.com/whoconli/k8s-learning/blob/master/Container%20&%20Kubernetes.assets/image-20210518222717150.png)]

    Deployment 控制 ReplicaSet(版本),ReplicaSet 控制 Pod(副本数)。

  • ReplicaSet 负责通过“控制器模式”,保证系统中 Pod 的个数永远等于指定的个数,–》 deployment只允许容器restartPolicy=Always,因为只有容器保证自己是running状态下,ReplicaSet调整pod的个数才有意义,如果pod里面容器都是never,运行完了不就没了,pod就死掉了(Failed )

水平拓展

kubectl scale deployment nginx-deployment --replicas=4/1

滚动更新

kubectl create -f nginx-deployment.yaml --record
kubectl rollout undo 滚动回去上一个版本

deployment对象状态查看

kubectl rollout status deployment/nginx-deployment

只生成一个ReplicaSet

Deployment更新操作会生成多个ReplicaSet,因此可以使用

kubectl rollout pause deployment/nginx-deployment 
暂停deployment,因此对于deployment所有修改不会触发滚动更新。
kubectl rollout resume deploy/nginx-deployment
Deployment 修改操作都完成之后,只需要再执行该指令,就可以把这个 Deployment“恢复”回来

当然,也可以使用spec.revisionHistoryLimit来限制历史ReplicaSet数量,如果为0则再也不能回滚操作。

StatefulSet

为了解决Deployment认为一个应用的pod都是完全一样的, 之间没有顺序,也没有所谓运行在哪一个主机上的问题。这个问题让Deployment随意创建或者删掉一个pod。

但是,实际上很多应用,特别时分布式应用,他们之间多个实例往往存在依赖关系:主从关系和主备关系等。还有一些数据存储类应用,被杀掉后丢失实例与数据之间的对应关系。这些实例间存在不对等关系以及实例对外部数据有依赖关系的应用,被称为有状态应用

由此,StatefulSet就是为了解决这个问题。

StatefulSef设计

  • 拓扑状态。应用的多个实例之间不是完全对等的关系,应用实例,必须按照某些顺序启动,并且新建的pod必需和原来的pod网络标识一样。
  • 存储状态。应用的多个实例分别绑定了不同的存储数据,应用实例,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。

通过这样的状态设计,SatefulSet将真实世界里的应用状态,抽象出来,并通过某种方式来记录这些状态。

Headless Service

Service 是 Kubernetes 项目中用来将一组 Pod 暴露给外界访问的一种机制。用户通过访问Service就可以访问到具体的Pod。

访问Service方式有:

  1. Service的VIP形式(Virtual IP)
  2. Service的DNS方式:访问“my-svc.mynamespace.svc.cluster.local”这条 DNS 记录,就可以访问到名叫 my-svc 的 Service 所代理的某一个 Pod
    • Normal Service:解析DNS得到对应的VIP,然后访问VIP就好了
    • Headless Service:解析DNS得到my-svc 代理的某一个 Pod 的 IP 地址,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。

创建Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都会被绑定为以下格式的 DNS 记录:

<pod-name>.<svc-name>.<namespace>.svc.cluster.local
# 改记录k8s为 Pod 分配的唯一的“可解析身份”

**Persistent Volume Claim PVC **

解决过度暴露问题,大大降低用户声明和使用持久化Volume的门槛

  1. 定义一个PVC,声明想要的Volume的属性;不需要任何关于 Volume 细节的字段,只有描述性的属性和定义。
  2. 在应用的 Pod 中,声明使用这个 PVC;只需要声明它的类型是persistentVolumeClaim,然后指定 PVC 的名字,而完全不必关心 Volume 本身的定义
  3. 创建这个 PVC 对象,Kubernetes 就会自动为它绑定一个符合条件的Volume(自于由运维人员维护的 PV(Persistent Volume)对象)。

StatefulSet 拓扑状态维护

StatefulSet 就是通过Headless Service维护网络拓扑结构,编号方式维护pod的顺序

  • 首先StatefulSet 按照 Pod 的“名字 + 编号”的方式将将 Pod 的拓扑状态固定下来。
  • 然后为每一个Pod 提供了一个固定并且唯一的访问入口,即:这个 Pod 对应的 DNS 记录 Headless Service
  • 这些状态,在 StatefulSet 的整个生命周期里都会保持不变,绝不会因为对应 Pod 的删除或者重新创建而失效。
  • 但是,通过Headless Service解析到的 Pod 的IP 地址,并不是固定的。因此必须使用 DNS 记录或者 hostname 的方式,而绝不应该直接访问这些 Pod 的 IP 地址

StatefulSet 存储状态维护

StatefulSet 通过PVC、PV 来管理存储状态

  • 在StatefulSet中声明一个PVC(就来自于volumeClaimTemplates 这个模板字段),这个 PVC 的名字,会被分配一个与这个Pod 完全一致的编号。
  • 自动创建的 PVC,与 PV 绑定成功后,就会进入 Bound 状态,这就意味着这个 Pod 可以挂载并使用这个 PV 了。PVC 与 PV 的绑定得以实现的前提是,运维人员已经在系统里创建好了符合条件的PV(比如,我们在前面用到的 pv-volume);或者,你的 Kubernetes 集群运行在公有云上,这样 Kubernetes 就会通过 Dynamic Provisioning 的方式,自动为你创建与 PVC 匹配的PV
  • PVC,都以<PVC 名字 >-<StatefulSet 名字 >-< 编号 >的方式命名,并且处于 Bound 状态。

也就是说,StatefulSet通过维护一个有状态的PVC,进而找到跟这个 PVC 绑定在一起的PV。这个PVC名字是不会变的重新创建一个pod时。

总结

  1. StatefulSet 的控制器直接管理的是 Pod。StatefulSet 里的不同 Pod 实例,有着不同的编号,用于维护拓扑以及唯一性
  2. Kubernetes 通过 Headless Service,为这些有编号的 Pod,在 DNS 服务器中生成带有同样编号的 DNS 记录。StatefulSet 能够保证这些 Pod 名字里的编号不变,对应的DNS记录也不会变,也就确保网络拓扑的一致性(解析到的 Pod 的IP 地址,并不是固定的,但是对于用户来说这是透明的)
  3. StatefulSet 还为每一个 Pod 分配并创建一个同样编号的 PVC。这样,Kubernetes 就可以通过 Persistent Volume 机制为这个 PVC 绑定上对应的 PV,从而保证了每一个 Pod 都拥有一个独立的 Volume。PVC和PV在pod被删除后依旧被保留下来的。

DaemonSet

在 Kubernetes 集群里,运行一个 Daemon Pod,这个pod特点有:

  • 这个 Pod 运行在 Kubernetes 集群里的每一个节点(Node)上;
  • 每一个节点上只有一个这样的pod实例;
  • 当有新的节点加入 Kubernetes 集群后,该 Pod 会自动地在新节点上被创建出来;而当旧节点被删除后,它上面的 Pod 也相应地会被回收掉;
  • 跟其他编排对象不一样,DaemonSet 开始运行的时机,很多时候比整个
    Kubernetes 集群出现的时机都要早
  • DaemonSet 自动地给被管理的 Pod 加上一个特殊的Toleration,即能容忍那些不被调度的节点

意义

  • 各种网络插件的 Agent 组件,都必须运行在每一个节点上,用来处理这个节点上的容器网络;
  • 各种存储插件的 Agent 组件,也必须运行在每一个节点上,用来在这个节点上挂载远程存储目录,操作容器的 Volume 目录
  • 各种监控组件和日志组件,也必须运行在每一个节点上,负责这个节点上的监控信息和日志搜集。

DaemonSet 其实是一个非常简单的控制器。在它的控制循环中,只需要遍历所有节点,然后根据节点上是否有被管理 Pod 的情况,来决定是否要创建或者删除一个 Pod。

版本管理

DaemonSet也存在版本号,但是和deployment控制器不一样,通过ReplicaSet来管理容器版本,DaemonSet的对象是pod,没有ReplicaSet来管理。但是k8s通过抽象对象来实现对应的功能。Kubernetes v1.7 之后添加了一个 API 对象,名叫ControllerRevision,专门用来记录某种Controller 对象的版本

万物皆对象,将DaemonSet和ReplicaSet都看抽象为ControllerRevision对象,通过这个ControllerRevision来进行管理。

特别地,如果undo一个对象,相当于进行一次 Patch操作,也就是将新版本更新为一个旧版本,所以对应这个 旧版本 实际上是一个 新版本,对应版本号 加一

PS: 对于操作粒度为Pod的控制器(如StatefulSet),也是通过ControllerRevision进行版本管理的。

Job & CronJob

Job,对于离线任务的管理

  • 这个 Job 对象在创建后,它的 Pod 模板,被自动加上了一个 controller-uid=< 一个随机字符串 > 这样的 Label。Job 对象本身,则被自动加上了这个 Label 对应的Selector,从而 保证了 Job 与它所管理的 Pod 之间的匹配关系。避免了不同 Job 对象所管理的 Pod 发生重合

  • 离线计算的 Pod 永远都不应该被重启,否则它们会再重新计算一遍

    • restartPolicy只有两种类型:

    • Never:

      运行结束后pod状态变为completed,如果离线作业失败,Job Controller就会不断地尝试创建一个新 Pod,Job 对象的 spec.backoffLimit 字段
      里定义了重试次数为 4, 默认值是 6

      Job Controller 重新创建 Pod 的间隔是呈指数增加的,即下一次重新创建 Pod的动作会分别发生在 10 s、20 s、40 s …后

    • OnFailure:

      离线作业失败后,Job Controller 就不会去尝试创建新的 Pod

      会不断地尝试重启 Pod 里的容器

  • spec.activeDeadlineSeconds 字段可以设置最长运行时间,避免jod一直不肯结束。

  • 并行控制

    • spec.parallelism,它定义的是一个 Job 在任意时间最多可以启动多少个 Pod 同时运行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值