一统江湖:Kubernetes(一)

前文我们详细的介绍了容器与容器掀起的 DevOps 风潮,本节我们跳出容器,开始学习容器管理与编排,并思考基础设施的建设。我们将带着如下的问题开始本节的内容:

  1. 为什么需要容器编排?
  2. Kubernetes 到底是什么?
  3. 如何系统学习 Kubernetes?

为什么需要容器编排?

按照 Docker 官网的文档,编排器的定义是

Tools to manage, scale, and maintain containerized applications are called orchestrators

即:虽然容器为应用提供了 DevOps 的有力支持,但容器本身的能力是受限的:

  1. 从用户应用角度来说,微服务体系中大量服务的服务暴露、服务注册、服务发现、服务通信、负载均衡等等能力,仅靠容器本身无法有效支持。单个容器只是一种能力,而服务才是价值所在。
  2. 从应用运维角度来说,容器的生命周期管理,包括部署、更新、删除、错误恢复、扩缩容,容器的调度、迁移、资源分配、监控、追踪等能力,都需要额外的机制支撑;从分布式系统角度来说,基础设施节点的管理、运维,更是云平台的重中之重。

所以我们不仅需要容器,还需要容器编排与基础设施管理。典型的容器编排系统有:Docker-SwarmmesosKubernetes等。在长达数年的竞争、合作后,Kubernetes 最终一统江湖,成为容器编排的事实标准。

Kubernetes 的前世今生

首先,我们把时间线拨回到 Golang 问世的 2007 年,此后经过若干年的发展,服务端高并发的优势、简单易入门的特性、Google 的背书,使得 Golang 迅速发展,并积累了一批企业用户,其中包括了 Docker 的母公司 dotCloud。

同时,2007 年开始,众多云计算厂家开始将目光投向 PaaS,Saleforce、AWS、Google 等陆续开始在 PaaS 领域发力,dotCloud 公司也是 PaaS 浪潮中的一家小公司。此时云计算还是 SaaS 和 IaaS 的天下,经过五六年的发展,PaaS 逐渐渡过了筚路蓝缕、以启山林的日子,CloudFoundry、Heroku、OpenShift 等产品不断涌现,但应用在不同环境中的迁移、打包仍一直是云计算的一个痛点。

2013 年 3 月,dotCloud 公司开源了 Docker 容器技术。Docker 解决了后端应用的环境与打包问题,一出生就引发了广泛的关注和讨论。Docker 的开源给 PaaS 带来了一次降维打击,众多云厂商纷纷开始拥抱 Docker,将 docker 作为底层的容器运行时。一时间 Docker 风光无二,社区迅速壮大,隐隐有成为容器的运行时标准的趋势;Docker 母公司也趁机推出了 Docker Swarm、Docker Compose 等项目,希望将整个 Docker 生态 PaaS 化。

Docker 走向 PaaS 化的操作与 CoreOS、Google 等公司产生了利益冲突。PaaS 巨头并不希望自己的蛋糕被分走,他们更希望 Docker 仅仅是一个标准的容器运行时。为了对抗 Docker 一家独大的现状,2014 年 CoreOS 推出了自家的容器运行时 Rocket,与 Docker 分道扬镳,PaaS 产品也同时开始支持多种容器运行时。而 Docker 自己在 Swarm 等项目上也并不算成功,随着时间推移,Docker 隐隐有衰落之势。

在容器编排方面,谷歌有深厚的技术积累。Kubernetes 项目就起源于谷歌内部的容器管理系统 Borg,在 Kubernetes 官网 2015 年 4 月的博客中我们可以找到这样一句话:

Google has been running containerized workloads in production for more than a decade.
Kubernetes traces its lineage directly from Borg.

2014 年,Google 开源了内部项目 Borg,并用 Go 语言进行了重写,这就是 Kubernetes。Kubernetes 含着金钥匙出身,自己实力又出众,迅速吸引了大批公司。Google 希望通过 Kubernetes 成为 PaaS 领域的领头羊。而在容器方面,Docker 与其他公司各退了一步。Docker 公司牵头将 Docker 的底层运行时:libcontainer 捐赠给基金会管理,并改名为 RunC。众多厂商,如 CoreOS、Docker、Google、RedHat 等,共同制定标准的容器与镜像规范,这就是 OCI(Open Container Initiative),这意味着容器的核心能力从 Docker 公司中剥离,容器之争升级为容器编排之争。

随着 OCI 一起诞生的还有 CNCF(云原生基金会),旨在以 Kubernetes 项目为基础,建立一个由开源基础设施领域厂商主导的、按照独立基金会方式运营的平台级社区,对抗 Docker 公司的容器生态。之后 Kubernetes 发展越来越火热,Docker 不断式微,最终在 2017 年 10 月,Docker 宣布在 Docker 企业版内置 Kubernetes。最终商业巨头们瓜分了容器市场,Kubernetes 一统江湖。

在本地快速部署一个 Kubernetes 集群

现在我们了解了 Kubernetes 的历史,在正式开始学习 Kubernetes 之前,我们首先在本地部署一个 Kubernetes 集群,这样照着 Demo 学习会更直观一些。

网上有很多 Kubernetes 的部署项目,如 kubeAdm 等等,都可以一键启动一个 Kubernetes 集群,但对我们来说,kubeAdm 还是太复杂了,而且***下载镜像的体验十分糟糕。所以,我们需要 KinD (Kubernets in Docker)。但是社区的 KinD 项目对电脑的配置有一定的要求,且启动过程仍然偏慢。我们需要一个更快、更轻便的 KinD,这就是今天的主角:byscorp/kind

Kubernetes-in-Docker - A single node cluster to run your CI tests against thats ready in 30 seconds

由于我们只需要进行最基本的学习,这个单节点的 KinD 已经足够了。它足够快,足够方便,足够省资源。为了启动和使用这个 KinD 集群,首先确保

  1. 你的电脑里装了 Docker,安装说明见这里
  2. KinD 的版本可以调整,如果想使用其他版本,在这里挑选一个即可。
  3. 保证自己的环境中 8843 与 10080 端口没有被占用。
  4. 应保证 kubectl 已经安装,见这里

注:我的电脑是 Macbook Air,8G 内存,i5 双核,运行 KinD 绰绰有余。

打开命令行工具,执行

docker run -tid --rm --name=kind --privileged -p 8443:8443 -p 10080:10080 bsycorp/kind:v1.15.8

之后执行

docker logs -f kind | grep "Kubernetes ready"

等到屏幕有日志输出后,代表 KinD 已经初始化完毕。我们还需要获取 kubeconfig 文件:

curl localhost:10080/config > $HOME/.kube/config
  1. 应保证 kubectl 已经安装,见这里

查看一下这个集群:

$ kubectl get nodes
NAME       STATUS   ROLES         AGE    VERSION
minikube   Ready    master,node   340d   v1.15.1

这个单节点的 Kubernetes 集群已经可以使用了。

Kubernetes 基础知识扫盲

首先介绍一下 Kubernetes 的字面含义。Docker 单词是集装箱的意思,Kubernetes 在希腊语中的含义是舵手,代表着当应用打包进“集装箱”开始出海远航时,Kubernetes 是一艘船,装载、管理着所有的容器应用。对了,Kubernetes 可以缩写为 K8s,因为 K 和 S 中间有 8 个字母。对于 Kubernetes,更专业的定义

Kubernetes (K8s) is an open-source system for automating deployment, scaling, and management of containerized applications.

系统核心组件简介

上一小节我们已经搭建了一个 K8s 集群,现在看一下这个集群上部署了哪些东西。执行

$ kubectl get pod -n kube-system
NAME                               READY   STATUS    RESTARTS   AGE
coredns-5c98db65d4-skm27           1/1     Running   1          340d
coredns-5c98db65d4-vmdg6           1/1     Running   1          340d
etcd-minikube                      1/1     Running   0          340d
kube-addon-manager-minikube        1/1     Running   0          23m
kube-apiserver-minikube            1/1     Running   0          340d
kube-controller-manager-minikube   1/1     Running   0          340d
kube-proxy-lk5pf                   1/1     Running   0          340d
kube-scheduler-minikube            1/1     Running   0          340d
storage-provisioner                1/1     Running   0          340d

这里涉及第一个知识点:什么是 pod ?pod 是 Kubernetes 进行容器编排的最小单位,是若干容器的载体。pod 的详细含义可以参考概念-pods。形象一点的解释是:pod 的原意是“豆荚”,一个 pod 可以包含一个或多个容器,就像一个豆荚可以包含许多豆子一样,直观地反映了 K8s 侧重的并非容器本身,而是容器编排

看着上面的输出,相信大多数初学者是懵逼的,所以我们先看一下 K8s 集群的架构图:

kubernetes architecture

Kubernetes 的架构是 CU 分离的,CU 分离意味着控制面的组件不涉及用户任务的具体执行,只涉及控制信令的执行。集群的节点分为 Master 节点与 Node 节点,Master 节点组成控制面板,Node 节点组成用户面;一个节点可单独作为 Master 节点或 Node 节点,也可以两种角色都担任。用户可通过 kubectl CLI 与 K8s 集群进行交互。

Master 是 K8s 系统的控制面板,有如下核心组件:

  • APIServer:APIServer 是一个大型 Web Server,它负责接收与中转(几乎)所有进出 K8s 集群的流量。APIServer 除了做一些默认值填充、校验等操作外,几乎不直接处理收到的请求,而是通过启动 Web Server 时注册的 HTTP Handler,将请求转发给其他控制面板组件,或转发给用户面的组件进行处理。
  • Kube-Scheduler:Scheduler 是 K8s 集群的调度器,负责为每个待调度的 pod 选择一个节点进行调度。与其说这是一个调度器,不如说这是一个任务的放置器,它只告诉系统哪个 pod 应当被放到哪个节点运行,但不绝对保证 pod 真的可以在这个节点被启动,更不管理这个任务占有多少时间片。对初学者来说,K8s 调度器和我们认为的调度器,如 linux kernel scheduler 有所不同,一定要注意他们的区别。
  • Controller-Manager:Controller Manager 是众多 Controller 的集合,其作用是监控集群资源的变化,确保集群资源始终处于预期的状态。例如:默认情况下某个 Pod 意外失败退出时,Pod Controller 会监听到这一事件,并重新启动这个 Pod。当然,Controller Manager 还有许多其他 Controller,这一点我们后面再细说。
  • ETCD:ETCD 是一个高可用的分布式键值数据库。K8s 默认使用 ETCD 存储集群数据,且对 etcd 的所有操作都通过 APIServer 进行,任何组件无法僭越。多说一句,在 k3s 项目中,etcd 可被 SQLite 替换。

Node 节点时 K8s 系统的用户面,有如下核心组件:

  • Kubelet:集群中每个 Node 节点上运行的代理,负责 Pod 的生命周期管理,如创建、更新、删除等,此外,
  • kube-proxy:集群中每个节点上运行的网络代理,负责 K8s 集群中应用的服务通信、路由转发。
  • Docker:kubelet 通过 CRI (Container Runtime Interface) 可支持多种容器运行时,如 Docker、Rocket、kata container 等等。默认情况下,K8s 使用 Docker 作为底层的容器运行时。为了方便理解,这里先写成 Docker,不一下子引入过多概念。

对比架构图中提到的核心组件与 KinD 集群的结果,我们发现有一些组件还没有提到,如

  • coreDNS:用作 K8s 集群中的服务注册与服务发现。
  • Storage-provisioner:kubeadm 独有的组件,为 K8s 集群提供外部存储的接入能力。
  • Addon-manager:kubeadm 独有的组件,管理 K8s 插件。

忽略这三个组件,整个 K8s 最核心的组件就是我们上面提到的这些了。下面,我们结合一个 Pod 的创建流程,体会一下 K8s 各组件究竟起到了什么作用。这个例子中,我们将创建的 Pod 的 Yaml 文件为

apiVersion: v1
kind: Pod
metadata:
  name: test-create-pod
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

注意由于我们没有为该 nginx 服务做服务暴露,在集群外部我们是无法访问到的。

将上面的代码复制到本地的 pod.yaml 中,在命令行中执行

kubectl create -f pod.yaml

之后,可以通过 kubectl get pod 观察到一个名叫 test-create-pod-xxxxx 的 Pod 被启动了。下面我们讲解一下 Pod 的简化版启动过程,以分析 K8s 集群各组件的作用,以及 K8s 的工作模式:

  1. kubectl 执行用户命令。首先 kubectl 读取我们之前保存的 config 文件,它记录了集群的信息,如 APIServer 的 IP,TLS Certs 等,用来连接目标集群的 APIServer 并鉴权。由于我们指定了 -f 的 flag,kubectl 还会读取 YAML 文件。kubectl 发现是一个创建 Pod 的请求,于是经过一系列的填充、校验等预处理之后,kubectl 向 APIServer 发出一个 REST 请求,告诉 APIServer 某个用户希望创建 Pod,并包含了 Pod 的描述信息。
  2. APIServer 接受 kubectl 发送的请求,于是将这个 pod 的信息存储到 ETCD。此时 Pod 还没有真正被创建出来。如果存储到 ETCD 成功,APIServer 向 kubectl 返回成功信息。此后,用户就可以通过 kubectl get pod 来查看 pod 的信息和状态了。Pod 的信息写入 ETCD 成功之后,APIServer 还会通知 Kube-Scheduler,告知调度器有一个 Pod 的 CREATE 事件到来。
  3. Kube-Scheduler 接收到一个创建 Pod 的事件,开始为 Pod 选择一个绑定的节点。经过调度器内部的调度算法和流程,最终挑选出一个合适的节点。选出一个合适的节点之后,调度器向 APIServer 发出一个 REST 请求,请求更新 Pod 的状态(其关键细节为填充 Pod 的 nodeName),从而驱使目标节点的 Kubelet 创建该 Pod。
  4. APIServer 接收到 Kube-Scheduler 的请求,向 ETCD 中更新 Pod 的信息。更新完成后,APIServer 向目标集群的 Kubelet 发送事件,告知 Kubelet 处理该 Pod 的创建请求。
  5. Kubelet 接收到 APIServer 的请求,经过进行一系列的检查、校验,开始创建 Pod。创建过程中,Kubelet 会调用宿主机上的 Docker 创建容器,并设置容器的网络、存储卷。在这个过程中,Kubelet 会发送若干个 REST 请求给 APIServer,请求更新 Pod 的信息。
  6. APIServer 也将接收到若干个 Kubelet 的请求,向 ETCD 更新 Pod 的信息。在其中某个请求中,APIServer 会向调度器发送一个事件,通知 Pod 已在目标主机真正创建成功。
  7. Kube-Scheduler 接收到 Pod 真正创建成功的消息,于是结束调度器对该 Pod 的处理。
  8. 如果一切顺利,容器成功创建并处于 Running 状态,且没有其他错误,Kubelet 将认为 Pod 处于 Running 且 Ready 的状态。最终向 APIServer 发出更新 Pod 信息的请求。最后,用户就可以看到 Pod 已经在成功运行了。

注意:为了不涉及太多太细节、太底层的知识,也为了让初学者对 K8s 有最直观、又不失一定深度的认识,这里省略了大量的细节和术语,只保留了最关键的某些步骤,旨在说明 K8s 的大致工作方式。大家切不可认为这就是完整的创建 Pod 的过程。

我们可以总结出 K8s 的核心工作模式:

  1. APIServer 是集群的流量出入口。用户所有的请求都将到达 APIServer;内部所有的请求也均通过 APIServer 进行转发,各组件之间并不做直接的交互。
  2. 只有 APIServer 直接操作后端的 ETCD 数据库。
  3. 各组件是异步工作的,通过某种“通知”机制同步数据与状态(该机制在后面细说)。
  4. CU 分离,控制面不负责用户任务的真正创建。

K8s 的大脑:controller manager

上面为了尽量简单,我们只创建了一个 Pod,而创建 Pod 的流程没有涉及到 controller manager,这一 K8s 的大脑。但实际使用中,我们极少单独创建一个 Pod。考虑这样的场景:我需要部署一个 nginx 服务器。

第一种方案:部署一个 Pod,启动一个单节点 Redis 服务。然而无法解决的问题有:

  • 多副本
  • 负载均衡
  • 服务暴露:集群外部无法访问该服务,集群内部只能通过 Pod IP 访问
  • 自动扩缩容
  • ...

所以显然,在 Pod 为我们做了应用的容器化封装后,我们需要一种新的资源类型,为我们做应用运维。像 Deployment, StatefulSet, DaemonSet, Job, CronJob 等等,就具有应用运维能力。它们都是对 Pod 的上层封装,这些工作负载将包含若干个 Pod。

每种工作负载有自己的工作逻辑,以最常见的 Deployment 为例。用户需要在 Deployment 资源中指定 Replicas 值。比如 Replicas 为 2,意味着该 Deployment 将始终试图保持有 2 个 Pod 同时在运行。一旦数量不等于 Replicas 值,系统将新建/删除 Pod,以达到期望值。例如下面是一个 Deployment 样例,为了不干扰大家的思路,这里不对 Deployment 的其他字段进行说明。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: default
  labels:
    app: nginx
spec:
  replicas: 3 # 最核心的能力
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

注:这里的 YAML 内容可以保存成文件,然后 kubectl create -f xx.yaml 来创建。创建这个 Deployment 后,可以用 kubectl get deployment 来查看。

这样的应用运维能力由 Controller manager 提供。Controller manager 是一组 Controllers 的集合,例如 Replicas Controller, Deployment Controller, Job Controller 等等,这些 Controller 彼此独立,为不同的资源类型提供各自的应用运维能力。可以说,Controller Manager 是 K8s 的大脑。

有了工作负载,我们可以解决多副本问题了。现在,我们试着解决服务暴露的问题。这部分内容不属于 Controller Manager 的范畴,我们只简单提一下。

用户可以在 K8s 中定义 Service,对自己的容器化应用进行服务注册、服务暴露。CoreDNS 组件提供了服务发现的能力,当 Service 被注册进 K8s 系统中后,K8s 会为服务创建一个固定的 IP,这个 IP 称为 ClusterIP,同时,创建一条 DNS 规则,使得用户可以通过特定的域名访问该服务。K8s 通过 Iptables 进行服务到后端的 Pod 的路由转发与负载均衡,该能力是通过用户面的 Kube-Proxy 实现的。这样一来,不用担心 Pod 宕机、重启后 IP 变化,因为 Service 的 ClusterIP 是不变的。例如,下面是一个 Service 的样例:

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: default
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

创建该服务后,用户在集群内可以通过 nginx-service.default.svc 这个域名查找到服务的 ClusterIP,即服务名.命名空间.svc:端口的模式。

K8s 开放接口

K8s 的成功不仅在于设计良好的架构,还在于其强大的可拓展性,如 Aggregated APIServer + Custom Resource Definition 增强了 K8s 在资源类型上的可拓展性,CNI/CSI/CRI 增强了用户面的拓展性,使 K8s 可以更专注于容器编排。

我们首先看一下 Kubelet 中最核心的三个接口:CNI/CRI/CSI:

  • CRI (Container Runtime Interface):容器运行时接口,可兼容多种容器运行时,如 runC, kata containers 等等
  • CSI (Container Storage Interface):容器存储接口,可对接多种第三方存储,如 ceph, nfs 等等
  • CNI (Container Network Interface):容器网络接口,可对接多种网络插件,如 flannel, calico 等等

Kubelet 通过这三个接口,极大的拓展了 K8s 的能力,使得 K8s 可兼容多种基础设施。

小小的总结

K8s 是一个开源的容器管理平台,核心组件包括了控制面的 APIServer, ETCD, controller manager, scheduler,其中

  • APIServer 是所有流量的出入口
  • ETCD 是 K8s 存储自身所有信息的数据库
  • Controller manager 是 K8s 的大脑,提供强大的应用运维能力
  • Scheduler 是一个 Pod 放置器,负责调度 Pod 到某个节点

还有用户面的 Kube-Proxy, Kubelet 等等,其中

  • Kube-Proxy 提供了路由转发能力
  • Kubelet 真正负责 Pod 的生命周期管理,是每个节点上运行的代理

初次之外,K8s 通过标准化的开放接口 CNI/CSI/CRI 实现了用户面的可拓展性,并使得 K8s 可以专注于容器管理。

全部评论

相关推荐

05-29 19:11
已编辑
北方民族大学 Java
😭😭😭😭本人26届双非本,后端选手。从25年秋招开始,一直到春招5月份,一共面了12次字节。可以说后面能继续投递面上字节大概率是因为前面一直累计的面评还不错,但是最终的结果往往不尽如人意,黄梁一梦。timeline:如标题,总共面了12次字节,4个不同的岗位。第一次:抖音生活服务测开二面完排序挂第二次:TikTok国际化电商测开三面完排序挂第三次:飞书后端安全团队三面完挂第四次:飞书后端偏基架团队三面完过,HR面完之后询问综合排序不推进。我知道像BAT这样的公司,双非本想拿到一张入场券有多难,也知道每次挂在排序/三面/HR面,那种差一步上岸又被打回原点的落差感有多磨人。可是最后一次字节的这个岗位,已经是5月中旬才开始面得了,春招末期的岗位,我本以为真的缺人,三面过的那天,我真的以为就差一步hr面就稳了,但是,最终的结果很遗憾,综合排序综合排序,不推进了。如果是技术能力的问题,我想也不会每一轮技术面给我通过。思来想去。难道真的就是因为我们双非有案底,所以最后的一切又算什么呢。付出这么多的时间精力,还是抵不过双非学历太差吗?既然如此一开始直接卡掉简历不用给面试不就行了嘛,每一轮面试都给我们生的希望,最后的最后又回到了那个必输的起点。12次字节,说不遗憾是假的,也无数次怀疑过自己:是不是我算法刷得还不够?是不是项目亮点讲得不够好?是不是学历就是一道跨不过去的坎?但回头看,这一年的秋招到春招,从面对面试官紧张到说话卡壳,到后来的从容面对,再到如今甚至能和面试官探讨AI&大模型技术的一些方案思路,我已经比去年的自己强太多了。可能字节于我,真的是一场盛大的单恋,拼尽全力奔赴,却还是没能收到想要的回应。前路漫漫,字节的梦碎了,但我的路还在继续,希望下一站,会有属于我的一场徐风。
不愿吃饼的山羊很友好:你的心理素质是真的强大,如果是我碰到这样都会疯了
点赞 评论 收藏
分享
头顶尖尖的程序员:这应该就是数据标注工作吧,对医学生好像没啥成长
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务