幕后英雄SIG-Node与CRI

  • 调度完成后,kubelet就负责将这个调度成功的Pod在宿主机上创建出来,并把容器运行起来
  • k8s里面不可被替代的组件
    • 第一个就是kube-apiserver
    • 第二个就是kubelet
  • kubelet本身也是按照控制器模式来工作的,工作原理如下图

kubelet工作原理图.png

  • 驱动SyncLoop控制循环运行的事件包括四种:
    • Pod更新事件
    • Pod生命周期变化
    • kubelet本身设置的执行周期
    • 定时的清理事件
  • kubelet启动第一件事情就是设置Listeners,注册关心的各种事件的Informer
  • 此外kubelet还负责维护很多子控制循环,一般被成为某Manager,如VolumeManager 等
  • SyncLoop根据Pod对象的变化进行容器操作
    • 通过Watch机制,监听自己相关的Pod的变化
    • 具体处理过程会启动一个单独的goroutine进行
  • kubelet调用下层容器运行时时,不会直接调用dockerAPI,而是通过一组叫做CRI (Container Runtime Interface)的gRPC接口来间接完成的

    • 需要CRI来屏蔽底层容器实现的差异
    • 包括docker、rkt、runV等容器实现
    • kubelet调用GenericRuntime发起CRI请求,由CRI shim处理
      • 如果是docker 就是 dockershim组件处理
      • shim的作用非常单一就是实现CRI规定的每个接口,然后把具体的CRI请求翻译成对应容器实现的API

        CRI与容器运行时

        CRI.png
  • 除了dockershim外,其他容器的CRI shim都需要额外单独部署

  • 比如CNCF里containerd项目的CRI shim,把k8s的调用转成runC容器

containerd.png

  • CRI待实现的两组接口
    • RuntimeService
      • 主要是跟容器相关的操作接口,比如创建、启动、删除、执行命令等
      • 确保这个接口本身,只关注容器,不关注Pod
    • ImageService
      • 主要是跟镜像相关的操作,比如拉取、删除镜像等

CRI.png

  • CRI原则
    • 接口本身只关注容器,不关注Pod
      • Pod是编排概念,不是运行时概念
      • 如果CRI里有Pod,接下来Pod有更新CRI就需要更新,没有解耦
  • CRI调用流程(不同shim有不同的实现)

kubectl调用CRI.png

  • StreamingServer与StreamingAPI

StreamingAPI.png

绝不仅是安全:KataContainers与gVisor

  • 安全容器的实现
    • 以上两种方案都是给进程分配一个独立的内核,避免共享宿主机内核
    • 容器进程能看到的攻击面就变成了自己的内核
    • 示意图如下

安全容器.png

  • KataContainer
    • 本质就是一个精简后的轻量级虚拟机
    • 像虚拟机一样安全,像容器一样敏捷
    • 使用传统的虚拟化技术,通过虚拟硬件模拟了虚拟机,然后在虚拟机里安装裁剪后的内核实现强隔离

KataContainers.png
KataContainers架构.jpg

  • gVisor
    • 给容器进程配置一个go语言实现的、运行在用户态的、极小的“独立内核”
    • 内核对容器进程暴露Linux内核ABI,扮演“GuestKernel”角色,把容器和宿主机隔离开
    • go语言模拟出了一个用户态的内核,通过这个模拟内核代替容器进程向宿主机发起系统调用

gVisor架构.png

  • gVisor的Sentry进程分为两种不同实现
    • Ptrace机制拦截用户应用的系统调用,把这些系统调用交给Sentry处理(性能差,仅Demo)
    • Sentry使用KVM进行系统调用的拦截,处理地址空间等细节,并不会创建出虚拟硬件设备等

Sentry.png
SentryKVM拦截.png

  • 总结
    • 启动和占用资源上gVisor略胜一筹
    • 对于系统调用密集等应用(重IO/重网络)gVisor因为需要频繁的拦截系统调用而性能下降
    • gVisor用Sentry模拟一个内核,所以支持的系统调用有限
    • 但是与应用进程强隔离,安全性有保证