Sealos is All You Need —— 3分钟部署 Kubernetes
Sealos 是什么? #
Kubernetes(K8s)发展至今,已经成为了一个极其复杂的系统。而作为云原生的基石,涌现了一大批辅助工具,帮助用户快速搭建 k8s 集群。而其中,sealos 是一个以 kubernetes 为内核的云操作系统,kuberentes 生命周期管理是 sealos 的一个重要功能,sealos 可以非常方便的安装/升级/伸缩/备份恢复集群等。接下来,让我们通过一个例子来看看 sealos 的强大。
如何使用 #
假设我们想要在 192.168.0.100,192.168.0.101 和 192.168.0.102 这三台机器上部署一主两从的 K8s 集群,那么使用 Sealos 的话,主要输入以下的命令:
sudo sealos run labring/kubernetes:v1.24.0 labring/calico:v3.22.1 \
--masters 192.168.0.100 \
--nodes 192.168.0.101,192.168.0.102 \
--passwd xxx
是的,Sealos 将一个复杂的 K8s 的集群部署简化成了短短一行命令,将部署体验拉到了极致。甚至不需要过多的文档解释,仅凭这一行命令就可以满足大多数普通的部署场景。
原理 #
那么,如此强大的 Sealos 是如何运行的呢?除了刚才演示的 run
命令之外,Sealos 还提供了大量提升用户体验的命令,例如 create
创建镜像、reset
格式化集群等等。由于篇幅有限,本章节就以最核心的 run
命令为例,看看 Sealos 在背后替用户完成了哪些自动化的操作。(本文以 Sealos V4.1.0 代码为例)
Applier #
首先,Sealos 会创建一个 Applier
结构体,负责了部署集群的核心逻辑。Applier
采用了 k8s 的声明式的设计思想,用户声明一个期望的集群状态,而 Applier
负责将集群现在的状态转换成用户期望的状态。
type Applier struct {
ClusterDesired *v2.Cluster // 用户期望的集群状态
ClusterCurrent *v2.Cluster // 集群当前状态
ClusterFile clusterfile.Interface // 当前集群接口
Client kubernetes.Client
CurrentClusterInfo *version.Info
RunNewImages []string // run 命令新增的镜像名称
}
clusterfile.Interface
是一个接口类型,Sealos 中通过 ClusterFile
实现了这一接口。因此,Applier
结构体中最重要的就是 Cluster
和 ClusterFile
这两个类型,它们定义了集群的状态和配置。接下来,我们展开介绍一下两者。
Cluster #
type Cluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ClusterSpec `json:"spec,omitempty"`
Status ClusterStatus `json:"status,omitempty"`
}
type ClusterSpec struct {
Image ImageList `json:"image,omitempty"`
SSH SSH `json:"ssh"`
Hosts []Host `json:"hosts,omitempty"`
Env []string `json:"env,omitempty"`
Command []string `json:"command,omitempty"`
}
type ClusterStatus struct {
Phase ClusterPhase `json:"phase,omitempty"`
Mounts []MountImage `json:"mounts,omitempty"`
Conditions []ClusterCondition `json:"conditions,omitempty" `
}
Cluster
的内容按照 K8s Resource 的格式进行了设计,这非常的 K8s 哈哈。在 ClusterSpec
中,定义了一系列用于部署 K8s 集群的参数,例如,镜像、SSH参数、节点等等。
而在 ClusterStatus
中,Phase
定义了当前集群的状态,Mounts
定义了集群使用的镜像,Conditions
保存了集群中所发生的一系列事件。
ClusterFile #
type ClusterFile struct {
path string // 保存路径
customValues []string
customSets []string
customEnvs []string
Cluster *v2.Cluster // 集群状态
Configs []v2.Config
KubeConfig *runtime.KubeadmConfig // 集群配置
}
ClusterFile
是真正被 Applier
操作的对象,以及持久化到文件中的内容。这里包含了所有集群的当前状态信息,同时还包含了 kubeconfig。这里的 kubeconfig 并不是我们平时操作 k8s 时所用的 config 文件,而是一系列用于搭建集群所需的配置项。在使用 kubeadm
时,这些配置项往往需要我们手动配置,而 Sealos 在这里会自动帮我们填写并应用于集群中。可以看出,Cluster
更像是 ClusterFile
的一个实例,记录了集群实时的状态。
创建 Applier #
创建一个 Applier
会经过以下步骤:
判断是否已经存在
ClusterFile
,如果存在,那么直接读取,构建出集群状态Cluster
。否则,初始化创建一个空的集群状态Cluster
。根据用户本次的参数,更新集群状态
Cluster
中的 spec,此时,Cluster
即为目标的集群状态。再次从文件中构建
ClusterFile
,作为集群当前的状态和对象。构建
Applier
结构体返回。
Apply #
接下来,通过 Applier.Apply()
,Sealos 开始正式的部署集群,使集群状态向目标靠近。首先,Sealos 会将当前集群的状态置为 ClusterInProcess
。接下来,根据集群创建或是更新,分别进入两个分支。
initCluster #
initCluster
负责从零开始创建一个集群。函数中会通过 CreateProcessor
去部署期望状态的集群。
type CreateProcessor struct {
ClusterFile clusterfile.Interface // 当前集群对象
ImageManager types.ImageService // 处理镜像
ClusterManager types.ClusterService // 管理 clusterfile
RegistryManager types.RegistryService // 管理镜像 registry
Runtime runtime.Interface // kubeadm 对象
Guest guest.Interface // 基于 sealos 的应用对象
}
CreateProcessor.Execute
接收期望的集群状态 ClusterDesired
。接下来会执行一系列 pipeline,正式进入实际的集群部署过程中:
Check:检查集群的 host
PreProcess:负责集群部署前的镜像预处理操作,在这里就会利用
CreateProcessor
中的各个 Manager。拉取镜像
检查镜像格式
使用
buildah
从 OCI 格式的镜像中创建 working container,并将容器挂载到 rootfs 上将容器的 manifest 添加到集群状态中
RunConfig:将集群状态中的 working container 导出成 yaml 格式的配置并持久化到宿主机的文件系统中
MountRootfs:将挂载的镜像内容按照类别,以
rootfs
,addons
,app
的顺序分发到每台机器上。这里需要介绍一下 sealos 镜像的一般结构,以最基础的 k8s 镜像为例:
labring/kubernetes - etc // 配置项 - scripts // 脚本 - init-containerd.sh - init-kube.sh - init-shim.sh - init-registry.sh - init.sh - Kubefile // dockerfile 语法,定义了镜像的执行逻辑
K8s 作为整个集群的基础,虽然最终镜像内的目录结构与其他一致,但其构建过程稍微有所不同。在 CI https://github.com/labring/cluster-image/blob/faca63809e7a3eae512100a1eb8f9b7384973175/.github/scripts/kubernetes.sh#L35 中,我们可以看到,k8s 镜像其实是合并了 cluster-image 仓库下的多个文件夹,
containerd
,rootfs
和registry
。这些独立的文件夹中包含有安装对应组件的脚本。Sealos 在挂载一个镜像后,会首先执行
init.sh
脚本。例如,以下是 k8s 镜像的脚本中,分别按顺序执行了init-containerd.sh
安装 containerd,init-shim.sh
安装 image-cri-shim 和init-kube.sh
安装 kubelet。source common.sh REGISTRY_DOMAIN=${1:-sealos.hub} REGISTRY_PORT=${2:-5000} # Install containerd chmod a+x init-containerd.sh bash init-containerd.sh ${REGISTRY_DOMAIN} ${REGISTRY_PORT} if [ $? != 0 ]; then error "====init containerd failed!====" fi chmod a+x init-shim.sh bash init-shim.sh if [ $? != 0 ]; then error "====init image-cri-shim failed!====" fi chmod a+x init-kube.sh bash init-kube.sh logger "init containerd rootfs success"
在 MountRootfs 这步中,只会执行
rootfs
和addons
类型的init.sh
脚本。这也很好理解,因为到目前为止,Sealos 仅仅在每台机器上安装成功了 kubelet,整个 k8s 集群还未可用。Init:初始化 k8s 集群。在这步中,其实也是执行了一系列的子操作。
Sealos 会从
ClusterFile
中加载kubeadm
的配置,然后拷贝到 master0 上。根据 master0 的 hostname 生成证书以及 k8s 配置文件,例如
admin.conf
,controller-manager.conf
,scheduler.conf
,kubelet.conf
。Sealos 将这些配置以及 rootfs 中的静态文件(主要是一些 policy 的配置)拷贝到 master0 上。
Sealos 通过 link 的方式将 rootfs 中的 registry 链接到宿主机的目录上,然后执行脚本
init-registry.sh
,启动 registry 守护进程。最后也是最重要的,初始化 master0。首先,将 registry 的域名,api server的域名(IP 为 master0 的 IP)添加到 master0 宿主机上。然后,调用
kubeadm init
创建 k8s 集群。最后,将生成的管理员 kubeconfig 拷贝到.kube/config
。
Join:使用 kubeadm 将其余 master 和 node 加入现有的集群,然后更新
ClusterFile
。此时,整个 k8s 集群就已经搭建完毕了。RunGuest: 运行所有类型为
app
的镜像的 CMD,安装所有应用。
至此一个 k8s 集群以及基于这个集群的所有应用都被安装完毕。
reconcileCluster #
第二个分支是负责集群的更新,大部分内容与 initCluster
都比较类似。执行主要包含了以下几步:
ConfirmOverrideApps: 确认是否覆盖已有的应用。
PreProcess, RunConfig, MountRootfs, RunGuest: 都与
initCluster
类似。PostProcess: 执行一些安装后的操作,但目前似乎并没有进行任何操作。
不仅仅如此… #
经过上文的介绍,可以看到 Sealos 对于 Kubernetes 生命周期管理有着非常好的抽象,而不只是个简单的安装脚本,你甚至可以扩展其它的 runtime 来支持 k3s k0s 等,而大部分定制化只需要修改集群镜像而不用修改 sealos 的源代码。不仅如此,sealos 还可以让你像使用 PC 操作系统一样用云,各种分布式软件信手拈来,真正让用云的门槛降到足够低。