Fork me on GitHub

K8s 入门笔记

K8s 入门笔记

本文第一章来源自 Weekend read, Serverless, Docker, Kubernetes,及文中涉及的五篇 tutorials。主要针对以下问题:

  1. 什么是 K8s?
  2. Pods、Nodes、Services,又是什么?
  3. Serverless 又是什么?
  4. Cluster 该如何开始,又该怎么维护?

本文第二章开始,就是对极客时间出品的《深入剖析Kubernetes》,进行概括总结。

K8s 知识图谱

一、基本概念

  1. k8s 的使用背景:需要多台机器分担负载并可能随时发生弹性缩放的场景下,k8s 是处理 containers 的好的实践方案。

    1. 使用 containers 可实现环境的复用,让 dev/pre/Online 环境都表现得一致。
    2. 使用 container API 可一次管理多个 containers,适用于任务调度、负载均衡、分布式等场景。
  2. k8s 是一个开源系统,能够自动进行部署、缩放、管理容器内的 app 等。

  3. Minikube 是一个工具,能够让k8s 跑在本地。

    1. 另外有一个开源的 kubernetes-client,不知道跟 Minukube 相比孰优孰劣。
  4. Nodes——作为 Pod 运行的地方。通常是一个 worker machine(可以是物理机或者虚拟机)。

    1. Node :pods = 1 :N
    2. 在集群中,Master 通过 Nodes 管理 pods。
    3. 每个 Node 都有至少一个Kubelet,至少一个container runtime
      1. Kubelet,负责 k8s master 跟 node 之间交流的进程, 负责管理 pods,是 node 节点的核心。kubelet 主要负责同容器运行时(比如 Docker 项目)打交道。而这个交互所依赖的,是一个称作 CRI(Container Runtime Interface)的远程调用接口,这个接口定义了容器运行时的各项核心操作,比如:启动一个容器需要的所有参数。
        1. 所以 K8s 不关心部署的是什么容器运行时,只要能够运行标准的容器镜像,就可以通过 CRI 接入到 K8s 中。
        2. kubelet 还通过 gRPC 协议同一个叫作 Device Plugin 的插件进行交互,用在对 GPU 等硬件使用上。
        3. kubelet通过 CNI 和 CSI 两个接口调用网络插件和存储插件为容器配置网络和持久化存储
      2. A container runtime(如 docker),负责从 registry 中拉 image,开启 container,并运行 app。
  5. Pods——k8s平台上的原子单位,是部署环境的最小单元。

    1. 通常是一个或一些 containers。常见的有 docker、containers 之间的共享资源(如共享存储、网络、镜像信息等)。
    2. pod 里的containers,共享同一个 ip 和端口。就算是同一个 Node 里的 Pod,也有唯一独立的 IP 地址。
  6. Pods 是具有生命周期的,当一个 Pod 死亡时,K8s 会通过一个叫做 ReplicaSet 的机制开启新的 Pods 来保证 app 的正常运行。

    1. 使用 Service 来实现 Pods 间的同步机制。
    2. 死亡后 ip 地址回收,重启 Pods 时,会发放新的 ip 地址。所以会出现同一个 Pod,经过短暂下线又上线后,换了个 ip 的情况。
  7. K8s 中的 Service 维护了一系列 Pods 的集合,并提供访问它们的方式。

    1. LabelSelector 来实现 Service 跟 Pods 之间的沟通,通常用 Label+logicalName 来指代某个Pod,而不是 ip 地址。Label 是 K-V 格式的。
    2. 有依赖关系的 Pods 间使用 yaml/json 进行记录。
  8. Service 有多种分类(角色):

    1. ClusterIP(default):仅允许集群访问的内部 IP。
    2. NodePort:让集群中的某一部分 Node使用同一个端口暴露服务,用 NAT 来实现。
      1. 外部使用”:”访问。
      2. 是 ClusterIP 的父集合。
    3. LoadBalancer:一个外部的负载均衡器,通常分配一个固定的外部 IP。
      1. 是 NodePort 的父集合。
    4. ExternalName:相当于取一个别名,然后返回一个 CNAME 记录。
      1. 不使用代理,需要 v1.7 或更高版本的 kube-dns。
  9. 像上文提到的 Docker,其实就是一种 container runtime,帮助用户创建可复制的环境(指定 os、lib 的版本、环境变量等等)。Docker 负责创建 container,这个 container 中会包含 app 运行需要的所有东西(含源码)。

    1. 注意 Docker 跟虚拟机的区别。Docker 使用 layered file system(分层文件系统),让containers 之间共享部分资源,相比虚拟机,更少出现独占资源的情况。
    2. Docker containers 间相互独立,而且是轻量级的、安全的系统单元。
    3. 欲开启一个 Docker。首先需要定义一个Dockerfile,文件中需要指定 os、环境变量等信息。然后创建 image(或者从别的地方拉)。最后运行一个container。
    4. Docker 采用 volume (或称为 Docker volume 数据卷)的方式向 containers 提供持久化存储,比如存储 log、json、SQLite 等,就算 containers 生命周期结束了,存在 volume 中的数据也不会被删除。
    5. 使用 Docker Compose 实现组件复用,具备了微服务的思想。使用了 Compose 后,开启 Docker时,可能仅需要定义 Dockerfile了,后续的image 和 containers 的管理都可以交给compose 来完成。
      1. 使用 docker-compose.yaml 进行配置,配置内容为:Build、Environment、Image、Networks、Ports、Volumes 等。
    6. 更多 Docker 内容参考:Docker 菜鸟教程
  10. Serverless,服务端逻辑运行在无状态的计算容器中,通常是 Event-driven。

    1. 并非不需要服务器作为计算资源,而是程序员无需理会计算资源的申请和维护,而交由平台来管理。
    2. 参考:大道至简 - 基于Docker的Serverless探索之旅

二、极客时间《深入剖析Kubernetes》 学习笔记

  1. docker 最简介绍,源自 PaaS,但因为 DockerImage 的存在,使得程序在打包时附带上了操作系统的文件系统,使得程序在多服务器上实现近似的”一键部署”的功能。

  2. 沙盒技术,容器本质是一种具有边界沙盒技术,沙盒中盛装的就是外部应用。

    1. 边界:容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。
    2. 回顾进程的概念:一个程序运行起来后的计算机执行环境的总和
    3. Docker 容器属于单进程模型,是指只有一个进程时 Docker 可控的。所以Docker 容器是允许多个进程的,区别在于其他进程不为 Docker 容器控制。
  3. Docker 最核心两个技术:CgroupsNamespace

1. Groups

  1. Cgroups,或叫 Linux Control Groups:制造约束(边界)的主要手段,多是指资源限制。

    1. 限制 docker 控制的进程对 host os (宿主机)的资源使用情况,避免此进程饥饿或占据过多资源(CPU、内存等)。

    2. Cgroups 就是用来限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等的工具。

    3. 具体过程是:使用一个子系统目录加上一组资源限制文件完成资源限制的意图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## cgroup 挂载的资源(cpuset(单独 cpu 核与内存结点),cpu,cpuacct,blkio(IO 限制),memory等)
$ mount -t cgroup
cpuset on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cpu on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu)
cpuacct on /sys/fs/cgroup/cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct)
blkio on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
memory on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)

## 比如在cpu 目录/sys/fs/cgroup/cpu 下,创建新文件夹container,linux 会自动生成一系列资源限制文件(也称控制组,归属于 container 子系统),如下:
1. cgroup.clone_children
2. cpu.cfs_period_us ## 总 cpu 时间,默认100ms
3. cpu.rt_period_us
4. cpu.shares notify_on_release
5. cgroup.procs
6. cpu.cfs_quota_us ## 此 container 最大cpu 时间限制
7. cpu.rt_runtime_us
8. cpu.stat
9. tasks ## 将某程序的 pid 写入,说明控制组的配置就会生效

2. Namespace

也可以在 Docker run命令执行时带上一些参数。

  1. Namespace:修改进程视图的手段,包括 PID Namespace、Mount/UTS/UTS/Network/User 等Namespace。

    1. PID Namespace 举例:一个 docker 在 run 了某个应用之后,此应用只能看到一个全新的进程空间(其实是 mock 空间),此空间里一般只有自己应用的进程(pid 被修改为 1,也是后续创建进程的父进程),看不到其他进程。

      1. 为什么这么设计?因为这么做之后,容器应用就可以跟应用同生命周期,对后续的容器编排非常重要。
    2. Mount Namespace:只让当前应用看到此 namespace 下的挂载点信息。

      1. 挂载:将新的文件系统关联至当前根文件系统(一般在分区后使用,举例:外置硬盘都被当成 file,但需要挂载后才能被 linux 识别)。参考:Linux 文件系统挂载Linux中挂载详解以及mount命令用法一文看懂linux的挂载原理和流程

      2. Mount Namespace 对容器进程视图的改变,一定是伴随着挂载操作(mount)才能生效

        1. 完整操作系统文件一般是root 开始,然后{/etc,/dev,…}文件,挂载时,将一套完整的 os 文件挂载到某个子目录下,这样一来,docker 中的进程只能看到此子目录下的文件系统,实现管窥而不得全貌。这一套完成的 os 文件系统也是俗称的容器镜像,官方名称—— rootfs(根文件系统,撑死几百兆,比虚拟机 OS 占用空间小太多了)

          1
          2
          3
          ## rootfs 包含的一级目录:
          $ ls /
          bin dev etc home lib lib64 mnt opt proc root run sbin sys tmp usr var
4. docker 是使用命令` pivot_root 系统调用`来实现以上文件挂载过程的(此命令借鉴并升级 自 linux 系统中的`chroot`命令)。

    5. 用户只需要解压打包好的容器镜像,那么这个应用运行所需要的完整的执行环境就被重现出来了。

        1. 不过这里的容器镜像其实是**增量镜像**(笔者自己编的词),因为一个 rootfs 可能是经过多次多人修改后的,每次修改都保存全量并不现实,所以会带上一些增量记录(专业名词:lay),将多个增量 lay 进行文件合并(AuFS命令,联合文件系统),最终组成一个完成的 os 文件系统。

        2. rootfs 一般分成三成:只读层、可读写层、init 层。只读层对应的正是 ubuntu:latest 镜像的五层。可读写层默认为空,一般增删改都发生在这一层,如果是删除操作,会通过创建`whiteout`文件,遮挡住只读层的文件来实现。init 层是容器启动时用户想设置的 hosts、resolv.conf 等信息,此信息一般不会再 Docker Hub 间流通,所以放在 init 层中。

    >1. 上面的读写层通常也称为容器层,下面的只读层称为镜像层,所有的增删查改操作都只会作用在容器层,相同的文件上层会覆盖掉下层。知道这一点,就不难理解镜像文件的修改,比如修改一个文件的时候,首先会从上到下查找有没有这个文件,找到,就复制到容器层中,修改,修改的结果就会作用到下层的文件,这种方式也被称为copy-on-write。
    >2. 查了一下,包括但不限于以下这几种:aufs, device mapper, btrfs, overlayfs, vfs, zfs。aufs是ubuntu 常用的,device mapper 是 centos,btrfs 是 SUSE,overlayfs ubuntu 和 centos 都会使用,现在最新的 docker 版本中默认两个系统都是使用的 overlayfs,vfs 和 zfs 常用在 solaris 系统。
    >3. 镜像的多层结构使得它可以在多个镜像之间共享和征用。

6. 需要明确的是,挂载了文件系统,但内核仍然是宿主机内核,而且无解。

    3. Network Namespace:只让当前应用看到此 namespace 下的网络设置和配置。

        4. 但是,如果 docker 中的 app 想修改时间,然后影响到了 host os 的时间,这种情况是不能被允许的。所以对于 docker 来说,有一些资源和对象(比如时间),是**不能被 Namespace 化的**。

        5. 一个进程的每种 Linux Namespace,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上。

3. Docker 技术与虚拟机比较

![](1.jpg)
  1. 左虚拟机,右 docker。

  2. 虚拟机需要 Hypervisor 对应用进程的隔离负责,需要 Guest OS 作为真实的虚拟机提供一个各种虚拟化软件,占用了百兆的内存资源。而且多经过虚拟化软件这一层对 IO、网络等性能损耗也大。

  3. docker 不需要 Hypervisor 和 Guest OS ,更加敏捷、更高性能。

  4. 但是就是因为没有 GuestOS,那么也就不存在自己专属的 OS 内核。

  5. 举例:在左图中,hostOS 可以是 win,但 app 是运行在 linux 环境下,所以GuestOS 安装上linux后,app 就可以运行。
  6. 但在右图,如果是同一个 hostOS 跟 app,那么因为内核是 win,所以 app 无法正常运行。即使可以挂载不同 OS 的操作系统文件,但内核没变,也变不了。
  7. 结论:如果应用依赖内核版本,那么 docker 也无法解决,除非再使用虚拟机。

  8. 数据卷 Volume

    1. Volume 机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。
    2. 挂载技术:Linux 的绑定挂载(bind mount)机制。挂载的本质就是修改 inode,实现了重定向。
-------------The End-------------