写点什么

k8s 集群搭建及对一些组件的简单理解

作者:EquatorCoco
  • 2024-06-24
    福建
  • 本文字数:7562 字

    阅读完需:约 25 分钟

背景


k8s 的学习环境(用 kubeadm 方式搭建),我也搭过几次了,但都有点问题。


要么在云服务器上弄,这个的问题是就只有一台轻量服务器,只能搭个单节点的;后来买了一台便宜的,所以就有了两台,但是不在一个 zone,一个是广州,一个是成都,内网不通,感觉搭起来很麻烦,还没试过。


要么是在本机的虚拟机上搞(vmware 或 virtualbox 这种),这种是没问题,就是笔记本性能一般,内存还得留着给 idea、chrome 这些,每次要学习实践一下,都得打开两台虚拟机,内存扛不住,也比较繁琐,久了就懒得实践了。


对我来说,最好的就是在公司的开发服务器上搭建(手里有些服务器的 cpu、内存还比较闲),公司的服务器的问题在于,网络是完全的内网环境,拉取镜像是个问题,当然也可以申请外网,就是麻烦;而且,申请外网的时候,有一些困难,就是你今天申请了某个国内镜像源,过两天,这个镜像源就挂了/关了,又得重新申请,最近不就是一堆镜像源停止服务了吗。你说,我不访问镜像源,就申请 docker 官方镜像的外网权限行不行呢,结果,我发现,docker pull 镜像的时候,竟然要访问一个被墙了的网站:https://production.cloudflare.docker.com



查了官方文档(https://docs.docker.com/desktop/allow-list/),pull 的时候确实要访问这个地址,



既然都没法访问,那我这网络就没法申请了。


当然,办法总比困难多,我这次采用的是最保险的方式,先找一台有网的机器,用 kubeadm 先搭建一次,把镜像弄下来之后,docker save 的方式将镜像导出,然后离线传输到内网机器上,docker load 就行了。

所以,我先讲讲,在有网的机器上,怎么利用 kubeadm 来搭建 k8s 集群。


另外,这种离线导入导出的方式是繁琐一些,后面我们慢慢再用其他方式,如自建镜像源等,来简化我们的使用;镜像问题的解决方式也很多,因为目前很多玩 nas 的,都受到影响了,因此,网上很多相关教程,我们参考着弄就行了。


服务器资源


因为是先讲讲正常机器,有外网的情况下的方式,所以我这边用 virtualbox 搞了两个干净的虚拟机,里面啥都没安装.


10.0.2.8 node4,准备作为主节点


10.0.2.9 node5,准备作为工作节点


操作系统都是 centos 7.9。


重要组件的简单理解


容器编排理解


我个人理解,k8s 负责的是容器编排,容器为什么需要编排呢?先说说最早的时候,我们怎么用 docker 的,我们 n 个服务加一堆中间件,当时经常要部署各种演示环境给客户演示啥的,就想着搞成个大的 docker 容器,把 docker 当虚拟机用,这样的话,代码架构、配置基本啥都不用改。


但是,主流的方式是,每个 docker 容器里面,只跑一个进程,如果按照这种模型来弄的话,就有点不好搞了,比如,服务 A 依赖中间件 A,中间件 A 现在的地址变成了容器对外暴露的地址,那服务 A 肯定是需要改配置啥的,对于一套复杂的微服务系统来说,把一堆配置改对就不容易了,而且改了配置之后,是不是得重新测试下。


还有服务之间的依赖关系,比如服务 A 依赖服务 B,启动过程中就要调服务 B 的接口这种,在这种容器模型下也是麻烦事。


docker 自己也出了 docker-compose,希望来解决服务之间的编排问题,这个东西,经常在简单的开源项目部署时,也时常看到(比如各种博客系统,前端+数据库之类的,就用 docker-compose 这种来搞)。

但是,docker-compose 我感觉好像是解决不了复杂的容器间关系的编排。


举个例子,很多 c 和 c++开发的程序,利用共享内存来通信,或者是利用 system v 的信号量、消息队列、共享内存通信的;还有用 Unix Domain Socket 通信的(比如 docker cli 和 docker daemon 间默认就是用这种方式通信),如果这些都要搞成一个容器一个进程,那容器间能支持这些共享内存、消息队列、unix domain socket 的通信方式吗,即使是单机能支持了,跨主机又该怎么办?


所以,最终就是 k8s 中提出了 pod 的概念,pod 是 k8s 中最小的调度单位,在一个 pod 中,里面的各个进程共享各种 linux 命名空间(Linux 中的命名空间(namespaces)是一种轻量级的虚拟化技术,让进程看起来像是在自己的独立系统中运行,也就是说进程只能看到相同命名空间内的进程和资源,而看不到其他命名空间的资源),让在同一个 pod 中的进程,可以互相看到对方,可以互相通信,而且是以宿主机内核支持的任意方式进行通信(如上面提到的共享内存、Unix Domain Socket 等)。


node agent 之 kubelet


K8S 中,我以目前的认识来看,kubelet,算是一个相当重要的组件。它被安装在每一台 node 机器上,作为该 node 上的一个后台服务运行,对外提供 api 接口。


先看下官方解释:


The kubelet is the primary "node agent" that runs on each node. It can register the node with the apiserver.kubelet 是基础的 node agent,运行每个 node 上,它可以注册 node 到 api server。

The kubelet works in terms of a PodSpec. A PodSpec is a YAML or JSON object that describes a pod.The kubelet takes a set of PodSpecs that are provided through various mechanisms (primarily through the apiserver) and ensures that the containers described in those PodSpecs are running and healthy.

kubelet 主要和 PodSpec 打交道。PodSpec(pod 规格)是一个 yaml 或者 json,用来描述一个 pod 的详情。kubelet 会通过各种方式来接收 PodSpec(主要是通过 apiserver),并确保 PodSpec 中描述的容器保持运行及正常。

https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/


这种架构呢,就和现在的很多日志、监控这类的架构很像,就是 agent/server 的架构,比如 filebeat、Prometheus 的 node exporter,如果是在大厂,相信会看到更多各种自研的 agent,这种的优势在于,agent 的升级独立于业务服务的升级,如果你是 agent 的提供方,你要是搞了个 sdk 提供给业务方,那升级的事,你基本推不动,业务方用得好好的,为啥你 agent 要升级我就要升级,所以,这种 agent 架构很受欢迎,另外,本机的业务服务调用 agent,也减少了网络调用,响应速度更快,这也是一个优势。


当然,我们说,agent 相对于 sdk 方案,升级要容易,但也没有那么容易,agent 承上启下,一方面作为 server 提供给业务服务,一方面作为 client 和 agent 的 server 方交互(不管是推还是拉),毕竟是多引入了一个组件,维护起来复杂得多。(像小公司,我就给 sdk 给业务方,sdk 直连我的服务端,这样可以少维护一个 agent,爽歪歪。)


kubelet 也是这样的一个 agent,一方面采集 node 机器的各类数据,上报给 k8s 的 api-server;你可以这样想,kubelet 作为这台 node 机器上的 agent(代理人),要掌握这台机器上的各类情况,比如,基础资源情况(CPU、内存、磁盘、操作系统信息、内核版本、容器运行时的信息、kubelet 版本、硬件情况如:是 x86 还是 arm),另外,最终 pod 不也是在当前 node 上运行吗,那这些 pod 运行情况怎么样,pod 的 status、ip、启动时间、pod 的事件,等各种数据,都需要上报。


另外,其实 kubelet 也不是说非得和 api-server 一起搭配使用,它自身也可以独立运行(具体可搜搜 kubelet standalone),此时,它就不需要再上报这些各种数据给 api server 了。


kubelet 作为 server 的一面


既然是一个 server,那自然要处理请求。那要通过什么方式将请求发给 kubelet 呢?


kubelet 启动时,可以配置很多参数,可以参见:

https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/


https 端口 10250

--port int32     Default: 10250The port for the kubelet to serve on. 
复制代码


这个接口主要是提供给 api server 调用的,协议为 https。用途的话,主要有如下几项:

https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/#api-server-to-kubelet

https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/

The connections from the API server to the kubelet are used for:

  1. Fetching logs for pods.


    获取 pod 的日志

  2. Attaching (usually through kubectl) to running pods.


    附着到运行中的 pod

  3. Providing the kubelet's port-forwarding functionality.


    端口转发功能


这个里面,具体有哪些 api 呢?


查阅了网上资料,发现文档没怎么提这块,主要有这么几种方式去了解:

(参考:https://www.reddit.com/r/kubernetes/comments/10n7a9t/how_does_control_plane_kubelet_communication_work/)



http 端口 10248


这个端口是使用 kubeadm 安装后,默认就打开了的。该端口就是暴露出来的一个 health 端口。

Copy--healthz-port int32     Default: 10248The port of the localhost healthz endpoint (set to 0 to disable).
复制代码


Copy[root@VM-12-4-centos ~]#curl localhost:10248/healthzok
复制代码


http 端口 10255


这个端口功能类似于 10250,只是 10250 是 https 的,也需要认证才能调用。而这个 10255 端口则不需要认证。

Copy--read-only-port int32     Default: 10255The read-only port for the kubelet to serve on with no authentication/authorization (set to 0 to disable)
复制代码


我发现这个端口默认没开启,我是通过如下方式开启:


修改 kubelet 的默认配置文件:vim /var/lib/kubelet/config.yaml,在 healthzPort 行下增加如下行:

healthzPort: 10248readOnlyPort: 10255
复制代码


然后 systemctl restart kubelet 重启。

[root@VM-12-4-centos ~]# curl localhost:10255/stats/summary{ "node": {  "nodeName": "vm-12-4-centos",  "systemContainers": [   {    "name": "pods",    "startTime": "2024-06-12T14:26:01Z",    "cpu": {     "time": "2024-06-22T07:55:41Z",     "usageNanoCores": 184092381,     "usageCoreNanoSeconds": 65091327269653    },    "memory": {     "time": "2024-06-22T07:55:41Z",     "availableBytes": 1087467520,     "usageBytes": 1179738112,     "workingSetBytes": 1008148480,     "rssBytes": 835305472,     "pageFaults": 0,     "majorPageFaults": 0    }   },   ...
复制代码


kubelet 接收 podSpec 的方式


根据文档:https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/

接收 podSpec 的主要方式是通过 api server,即上面提到的 https 端口:10250.


另外的方式为:

File: Path passed as a flag on the command line. Files under this path will be monitored periodically for updates. The monitoring period is 20s by default and is configurable via a flag.通过命令行传递的路径。在这个路径下的会被监控,周期性地检查更新。每 20s 检查一次。

HTTP endpoint: HTTP endpoint passed as a parameter on the command line. This endpoint is checked every 20 seconds (also configurable with a flag).http 接口:通过命令行传递,每 20s 检查一次。


这里,文件这种方式,咱们后面还会提到。在用 kubeadm 初次安装 k8s 集群时,k8s 的服务端组件(api-server、etcd 等,为啥自己就启动了),其实就是 kubeadm 在指定目录下(默认为/var/lib/kubelet/config.yaml 中的 staticPodPath: /etc/kubernetes/manifests),生成了 api-server、etcd 这几个组件的 podSpec,然后 kubelet 就根据这些 podSpec 自动创建了对应的 pod。


我们通过 kubeadm 的文档,也能确认这一点:

Generate static Pod manifests for control plane componentsKubeadm writes static Pod manifest files for control plane components to /etc/kubernetes/manifests. The kubelet watches this directory for Pods to be created on startup.为控制面的组件(即 k8s 的服务端组件)生成静态的 pod manifest,生成的文件存放在/etc/kubernetes/manifests。kubelet 监测这个目录,来生成对应的 pod。


CRI


CRI 全称:Container Runtime Interface (CRI),可以理解为一套标准接口,为的就是 k8s 和具体的容器解耦,这套接口就是:如果容器的具体实现方,想要通过 CRI 接口来和 k8s 交互,就需要实现这套接口。

说白了就是定标准。


这些在各行各业的案例太多了,比如各汽车制造商给用户提供的接口基本都差不多,方向盘、油门、刹车;在 java 领域,它定义了一套自己的 java 虚拟机规范,各个厂商可以自由按照规范来实现。


这套接口长啥样呢?这套接口是用 protobuf 定义的,

https://github.com/kubernetes/cri-api/blob/c75ef5b/pkg/apis/runtime/v1/api.proto


主要包含两类接口,运行时接口和镜像相关接口

// Runtime service defines the public APIs for remote container runtimesservice RuntimeService 
// ImageService defines the public APIs for managing images.service ImageService
复制代码


运行时相关接口,也包含了 CURD:

// CreateContainer creates a new container in specified PodSandboxrpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
// StartContainer starts the container.rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
// ListContainers lists all containers by filters.rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
// ContainerStatus returns status of the container. If the container is not// present, returns an error.rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
复制代码


也有执行类接口:

// ExecSync runs a command in a container synchronously.rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {}
// Exec prepares a streaming endpoint to execute a command in the container.rpc Exec(ExecRequest) returns (ExecResponse) {}
// Attach prepares a streaming endpoint to attach to a running container.rpc Attach(AttachRequest) returns (AttachResponse) {}
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox.rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {}
复制代码


也有统计类的接口:

// ContainerStats returns stats of the container. If the container does not// exist, the call returns an error.rpc ContainerStats(ContainerStatsRequest) returns (ContainerStatsResponse) {}
// ListContainerStats returns stats of all running containers.rpc ListContainerStats(ListContainerStatsRequest) returns (ListContainerStatsResponse) {}
复制代码


CRI 实现之 docker


先来讲重量级的实现:docker。docker 曾经也是风头无两,可惜最终在容器编排领域,还是拜下阵来,现在只能安心地当一个容器运行时。


k8s 刚开始的时候,只集成了一个容器实现,那就是 docker。后来,准备引入另一个实现:rkt 的时候,发现了问题,这样会导致 k8s 和特定的容器运行时紧密耦合,这样要求集成方要同时理解 k8s 和容器实现,要求很高,另外,容器运行时出现了 bug,也需要 k8s 跟着进行发版修复。


2016 年,k8s 引入了 CRI 这一套接口,在这之后,kubelet 只和 CRI 打交道,不再和具体的容器实现打交道。这样的话,就要求容器运行时需要实现 CRI,但是,比如 docker,docker 的诞生早于 CRI,docker 当然也不是不能改,但当时 k8s 和 docker 公司正在激烈竞争,docker 公司肯定不会配合 k8s 来实现 CRI 接口。(https://www.aquasec.com/cloud-native-academy/container-security/container-runtime-interface/)


所以,k8s 自己在 kubelet 的进程代码中,写了个适配层:dockershim,相当于将 kubelet 的 CRI 请求转换为对 docker 的请求。


这样一套适配层,维护起来也是不轻松的,终于,在 Kubernetes 1.20 版本(2020 年 12 月 8 日),Dockershim 被标记为过期(https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/),并将在后续的版本被正式弃用。


2022 年 4 月,k8s 发布了 1.24,在这个版本中,kubelet 中移除了 dockershim 这个适配层,意味着,kubelet 无法将 CRI 请求转发给 docker 了。(https://kubernetes.io/blog/2022/04/07/upcoming-changes-in-kubernetes-1-24/)


官方给出的建议是,切换到其他支持 CRI 接口的容器运行时,比如:containerd、CRI-O、Mirantis Container Runtime(docker 的商业版本)。


如果你就是想用 docker 作为容器运行时怎么办呢,只能说,东方不亮西方亮。k8s 团队不想维护这个适配层了,那就只能其他人来了。其他人是谁呢,就是 docker 公司的人。docker 公司如果真的不开发这么一个适配层出来,那损失的用户应该也不少,迫于压力,docker 公司开发了一个叫 cri-dockerd 的适配层,这个适配层服务,实现了 CRI 接口,将 CRI 请求转为对 docker 的请求。


这个 cri-dockerd 的项目介绍如下:

https://mirantis.github.io/cri-dockerd/about/motivation/

Mirantis and Docker have agreed to partner to maintain the shim code standalone outside Kubernetes, as a conformant CRI interface for the Docker Engine API. This means that you can continue to build Kubernetes based on the Docker Engine as before, just switching from the built in dockershim to the external one.Mirantis(docker 的商业化公司吧)和 docker 同意独立于 k8s,合作维护适配层代码,为 docker 提供 CRI 适配。这意味着你可以继续像之前一样在 k8s 中使用 docker,只需要从内置的 dockershim 切换为外置的 cri-dockerd。


所以,要继续使用 docker 的话,要先安装 docker、再安装 cri-dockerd,就可以了。


CRI 实现之 containerd


containerd 是一个开源的容器运行时,最初是作为 Docker 项目的一部分开发的。它由 Docker 公司于 2017 年开源,并移交给了 CNCF(Cloud Native Computing Foundation)管理。


在目前的 docker 版本中(当前为 2024 年 6 月),你安装了 docker engine 后,底层依然有 containerd 进程,只是不确定这个 containerd 进程和开源的版本是否一致,还是已经分叉。


反正目前来说,containerd 已经成为了 k8s 重要的一个容器运行时。它的优点在于,它比 docker 更轻量,它只关注容器运行时,而你要用 docker 的话,不仅重一些,还要搞个适配层:cri-dockerd 服务。


但是咋说呢,docker 目前还是比较习惯,后续再来捣鼓这个吧。其他容器运行时,CRI-O 等,就先不介绍了。


总结


本来是想记录下安装的,结果发现,在部署之前,最好把组件间的关系讲一下,便于在安装时知其然,就没收住。


下篇再讲安装吧。我也是新学这个,肯定有说的不对的地方,还请帮忙多指正。


文章转载自: 三国梦回

原文链接:https://www.cnblogs.com/grey-wolf/p/18262596

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
k8s集群搭建及对一些组件的简单理解_Kubernetes_EquatorCoco_InfoQ写作社区