大家好,我是讯享网,大家多多关注。
序
本文是kubernetes(以下替换为k8s)的入门文章,将涉及k8s的架构、集群搭建、Redis的一个例子,以及如何使用operator-sdk开发operator的教程。在文章的过程中,会交替引入Pod、Deployment、StatefulSet等等k8s的概念。这些概念都是通过例子介绍的,比较容易理解和实践。文章参考了很多博客和资料,放在最后参考部分。
K8s架构
我们来看看k8s集群的架构。从左到右分为两部分。第一部分是主节点(即图中的控制平面),第二部分是节点Node。
通常,一个主节点包括四个组件:apiserver、调度器、控制器管理器和etcd。它们各自的功能是什么:
Apiserver:上知天文下知地理,上连其余组件,下接ETCD,提供各类 api 处理、鉴权,和 Node 上的 kubelet 通信等,只有 apiserver 会连接 ETCD。Controller-manager:控制各类 controller,通过控制器模式,致力于将当前状态转变为期望的状态。Scheduler:调度,打分,分配资源。Etcd:整个集群的数据库,也可以不部署在 Master 节点,单独搭建。
一个节点一般包括三个组件,docker、kube-proxy和kubelet。
Docker:具体跑应用的载体。Kube-proxy:主要负责网络的打通,早期利用 iptables,现在使用 ipvs技术。Kubelet:agent,负责管理容器的生命周期。
综上所述,k8s集群是由Master和Node两个组件组成的架构,其中Master是整个集群的大脑,Node运行Master调度的应用。我们将在后面用一个具体的调度实例来解释这些组件的交互过程。
第二,构建k8s集群
说了k8s集群有哪些组件,我们先来看看如何搭建一个k8s集群。有以下几种方法(参考文末链接):
当我们安装了 Docker Desktop APP 之后,勾选 k8s 支持就能搭建起来。使用 MiniKube 来搭建,社区提供的一键安装脚本。直接在云平台购买,例如阿里云 ack。使用 kubeadmin,这是 k8s 社区推荐的可以部署生产级别 k8s 的工具。使用二进制,下载各组件安装,此教程需要注意,下载的各组件版本要和博客中保持一致,就可以成功。
本文下面的例子都是用本地Docker桌面APP搭建的k8s。
? ~ kubectl versionClient Version: version.Info{Major:”1″, Minor:”21″, GitVersion:”v1.21.4″, GitCommit:”3cce4a82b44f032d0cd1a1790e6d2f5a55d20aae”, GitTreeState:”clean”, BuildDate:”2021-08-11T18:16:05Z”, GoVersion:”go1.16.7″, Compiler:”gc”, Platform:”darwin/amd64″}Server Version: version.Info{Major:”1″, Minor:”21″, GitVersion:”v1.21.4″, GitCommit:”3cce4a82b44f032d0cd1a1790e6d2f5a55d20aae”, GitTreeState:”clean”, BuildDate:”2021-08-11T18:10:22Z”, GoVersion:”go1.16.7″, Compiler:”gc”, Platform:”linux/amd64″}
第三,从需求出发
我们从一个实际需求出发,来看看如何在k8s上部署Redis服务。
部署一个Redis服务支持高可用提供统一的 EndPoint 访问地址
1部署独立版本
如果我们想在k8s上部署一个独立版本的Redis,我们只需执行以下命令:
? ~ kubectl run redis –image=redispod/redis created? ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis 1/1 Running 0 5s
您可以使用kubectl exec进入Pod内部连接Redis来执行命令:
? ~ kubectl exec -it redis — bashroot@redis:/data# redis-cli127.0.0.1:6379> pingPONG127.0.0.1:6379>
那么Pod和Redis是什么关系呢?这里的Redis实际上是Docker进程启动的服务,但在k8s中,它被称为Pod。
2个吊舱和部署
先说第一款k8s概念Pod。Pod是k8s中最小的调度单元。一个Pod可以包含多个停靠站,所有停靠站都将被调度到同一个节点。这些dockers共享网络命名空间,可以声明共享同一个卷在磁盘空之间共享。
这样有什么好处?实际上,在现实世界中,许多应用程序需要部署在同一台机器上。例如,Redis日志收集插件必须与Redis部署在同一台机器上,以读取Redis日志。我们前面讲背景的时候说过Docker Swarm有一些问题,其中一个问题就是它只是基于Docker调度。虽然也可以设置亲缘关系,让两个docker在同一台机器上调度,但是因为不能一起调度,所以会提前把一个Docker调度到资源很少的机器上,导致第二个Docker调度失败。
比如我们有两个容器,A和B,分别是Redis和日志收集组件,每个组件都需要2g内存。现在有两个节点,node 1 3.5内存,node 2 4G内存。在Docker Swarm的调度策略下,首先调度Redis,可能调度到node1,然后调度日志收集组件。发现Node1只有1.5g内存,调度失败。但是,在k8s中,调度是基于pod的。如果两个组件在一个pod中,则计划不会考虑节点1。
虽然pod已经可以运行Redis服务,但是并不具备高可用性,因为一旦Pod绑定到某个节点,除非绑定发生变化(pod.spec.node字段被修改),否则它永远不会离开该节点,这意味着如果主机宕机,Pod不会主动迁移到其他节点。为了使服务始终可用,您需要使用像Deployment这样的控制器。
? ~ kubectl create deployment redis-deployment –image=redisdeployment.apps/redis-deployment created? ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis 1/1 Running 0 32mredis-deployment-866c4c6cf9-8z8k5 1/1 Running 0 8s? ~
redis-Deployment-866 C4 c6cf 9-8z8k 5是kubectl create刚刚创建的新部署。为了验证高可用性,我们用kubectl delete pod删除Redis和Redis-deployment-866 c 6 c 6 cf 9-8z8k 5,看看会发生什么。
? ~ kubectl delete pod redis redis-deployment-866c4c6cf9-8z8k5pod “redis” deletedpod “redis-deployment-866c4c6cf9-8z8k5” deleted? ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis-deployment-866c4c6cf9-zskkb 1/1 Running 0 10s? ~
Redis已经消失了,但是Redis-Deployment-866 c 6 c 6 cf 9-ZSK KB以不同的名称重新出现了!
部署可以定义多个副本和多个pod,从而为应用程序提供迁移能力。如果只使用POD,实际上当应用程序被调度到一台机器上时,该应用程序不能在机器停机时自动迁移。但是在使用部署时,会调用ReplicaSet(一种控制器)来保证当前集群中的应用副本数量与指定的一致。
K8s使用yaml描述命令。
在k8s中,kubectl可以用来创建简单的服务,但是还有一种方法可以创建复杂的服务,那就是提供yaml文件。例如,上面创建Pod的命令可以替换为下面的yaml文件。执行kubectl create后,可以看到redis Pod又被创建了。
? ~ cat pod.yamlapiVersion: v1kind: Podmetadata: name: redisspec: containers: – name: redis image: redis? ~ kubectl create -f pod.yamlpod/redis created? ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis 1/1 Running 0 6sredis-deployment-866c4c6cf9-zskkb 1/1 Running 0 6m32s
四个k8s组件调用流程
我们来看看Kubecl创建部署Redis-Deployment-image = redis发出后,k8s集群都做了些什么。
首先 controller-manager, scheduler, kubelet 都会和 apiserver 开始进行 List-Watch 模型,List 是拿到当前的状态,Watch 是拿到期望状态,然后 k8s 集群会致力于将当前状态达到达期望状态。kubectl 下发命令到 apiserver,鉴权处理之后将创建信息存入 etcd,Deployment 的实现是使用 ReplicaSet 控制器,当 controller-manager 提前拿到当前的状态(pod=0),接着接收到期望状态,需要创建 ReplicaSet(pod=1),就会开始创建 Pod。然后 scheduler 会进行调度,确认 Pod 被创建在哪一台 Node 上。之后 Node 上的 kubelet 真正拉起一个 docker。
在这些步骤中,apiserver的作用不言而喻,因此其他组件向上连接,ETCD向下连接。但是apiserver可以横向扩展,然后通过负载均衡,ETCD成为k8s架构中的瓶颈。
当我第一次看这个架构的时候,我会思考为什么ApiServer,Scheduler和Controller-Manager不合成一个组件。其实在Google Borg中,borgmaster就是这样,它的功能也是这些功能,只是组合在了一起。最后,他们还发现,borgmaster在集群大了之后会出现一些性能问题,包括kubelet的heartbeat就是一个大的,所以k8s从一开始就是开源的,设计中有三个组件来更好的维护代码。
部署五个主从版本
我们已经在上面部署了Redis的单机版,通过部署实现了服务的持续运行。接下来,我们来看看如何部署主从版本。其中比较难的部分是如何确定主从的同步关系。
1个状态集
K8s为有状态应用设计了StatefulSet控制器,主要通过以下两个特性为有状态应用服务:
拓扑状态:实例的创建顺序和编号是顺序的,会按照 name-index 来编号,比如 redis-0,redis-1 等。存储状态:可以通过声明使用外部存储,例如云盘等,将数据保存,从而 Pod 重启,重新调度等都能读到云盘中的数据。
让我们看一下Redis的StatefulSet的例子:
apiVersion: apps/v1kind: StatefulSet # 类型为 statefulsetmetadata: name: redis-sfs # app 名称spec: serviceName: redis-sfs # 这里的 service 下面解释 replicas: 2 # 定义了两个副本 selector: matchLabels: app: redis-sfs template: metadata: labels: app: redis-sfs spec: containers: – name: redis-sfs image: redis # 镜像版本 command: – bash – “-c” – | set -ex ordinal=`hostname | awk -F ‘-‘ ‘{print $NF}’` # 使用 hostname 获取序列 if [[ $ordinal -eq 0 ]]; then # 如果是 0,作为主 echo > /tmp/redis.conf else echo “slaveof redis-sfs-0.redis-sfs 6379” > /tmp/redis.conf # 如果是 1,作为备 fi redis-server /tmp/redis.conf
然后,启动StatefulSet,出现了redis-sfs-0和redis-sfs-1两个pod。他们根据名称索引的规则被正式编号。
? ~ kubectl create -f server.yamlstatefulset.apps/redis-sfs created? ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis 1/1 Running 0 65mredis-deployment-866c4c6cf9-zskkb 1/1 Running 0 71mredis-sfs-0 1/1 Running 0 33s # 按照 redis-sfs-1 1/1 Running 0 28s
然后我们继续看主从关系是否生效。查看redis-sfs-1的日志,我们发现:
? ~ kubectl logs -f redis-sfs-11:S 05 Nov 2021 08:02:44.243 * Connecting to MASTER redis-sfs-0.redis-sfs:63791:S 05 Nov 2021 08:02:50.287 # Unable to connect to MASTER: Resource temporarily unavailable…
2无头服务
好像redis-sfs-1不知道redis-sfs-0。原因是我们还没有让他们互相认识。这种相互理解需要k8s,一种叫无头服务的服务。服务是k8s项目中使用的一种机制,用于向外界公开一组pod。例如,如果一个部署有三个单元,那么我可以定义一个服务。然后,只要用户可以访问该服务,它就可以访问特定的Pod。一般有两种方式:
VIP:访问 VIP 随机返回一个后端的 PodDNS:通过 DNS 解析到后端某个 Pod 上
无头服务是通过DNS解析Pod地址的方法。此DNS地址的规则是:
让我们创建对应于集群的无头服务:
apiVersion: v1kind: Servicemetadata: name: redis-sfs labels: app: redis-sfsspec: clusterIP: None # 这里的 None 就是 Headless 的意思,表示会主动由 k8s 分配 ports: – port: 6379 name: redis-sfs selector: app: redis-sfs
再看一下,发现redis-sfs-1已经成功同步了主备,因为创建了Headless服务后,Redis-SFS-0 . Redis-SFS . default . SVC . cluster . local是集群中唯一可以访问的。
? ~ kubectl create -f service.yamlservice/redis-sfs created? ~ kubectl get serviceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24dredis-sfs ClusterIP None <none> 6379/TCP 33s? ~ kubectl logs -f redis-sfs-1…1:S 05 Nov 2021 08:23:31.341 * Connecting to MASTER redis-sfs-0.redis-sfs:63791:S 05 Nov 2021 08:23:31.345 * MASTER <-> REPLICA sync started1:S 05 Nov 2021 08:23:31.345 * Non blocking connect for SYNC fired the event.1:S 05 Nov 2021 08:23:31.346 * Master replied to PING, replication can continue…1:S 05 Nov 2021 08:23:31.346 * Partial resynchronization not possible (no cached master)1:S 05 Nov 2021 08:23:31.348 * Full resync from master: 29d1c03da6ee2af173b8dffbb85b6ad504ccc28f:01:S 05 Nov 2021 08:23:31.425 * MASTER <-> REPLICA sync: receiving 175 bytes from master to disk1:S 05 Nov 2021 08:23:31.426 * MASTER <-> REPLICA sync: Flushing old data1:S 05 Nov 2021 08:23:31.426 * MASTER <-> REPLICA sync: Loading DB in memory1:S 05 Nov 2021 08:23:31.431 * Loading RDB produced by version 6.2.61:S 05 Nov 2021 08:23:31.431 * RDB age 0 seconds1:S 05 Nov 2021 08:23:31.431 * RDB memory usage when created 1.83 Mb1:S 05 Nov 2021 08:23:31.431 # Done loading RDB, keys loaded: 0, keys expired: 0.1:S 05 Nov 2021 08:23:31.431 * MASTER <-> REPLICA sync: Finished with success^C? ~ kubectl exec -it redis-sfs-1 — bashroot@redis-sfs-1:/data# redis-cli -h redis-sfs-0.redis-sfs.default.svc.cluster.localredis-sfs-0.redis-sfs.default.svc.cluster.local:6379> pingPONGredis-sfs-0.redis-sfs.default.svc.cluster.local:6379>
这时候不管我们删除哪个Pod,都会按照原来的名字拉起来,这样就保证了待机关系。这个例子只是StatefulSet的一个例子。经过分析可以发现,虽然可以维持备用关系,但是当主设备挂机时,备用设备此时是无法切换上来的,因为没有任何组件可以帮助我们进行这种切换操作。一种方式是使用Redis Sentinel,可以参考这个项目的配置:k8s-redis-ha-master。如果你的k8s比较新,需要合并这个PR。
六算子
虽然StatefulSet可用,但它只能用于基本版本。如果想自己定制更复杂的操作,k8s的解决方案是operator。简而言之,operator就是定制你的k8s对象及其对应操作的解决方案。
对象是什么?Redis集群、etcd集群和zk集群都可以是一个对象。实际上,我们可以定义任何我们想描述的东西。其实我们在k8s yaml里决定一个是善良的。在前面的例子中,我们使用了Pod、Deployment和StatefulSet,它们是k8s的默认实现。现在,如果您想要定义自己的对象,有两个过程:
定义对象,比如你的集群默认有几个节点,都有啥组件定义对象触发的操作,当创建对象时候要做什么流程,HA 时候要做什么流程等
运算符方法是基于编程的,可以在很多语言中使用,最常用的是go语言。通常人们会用operator-sdk来完成,因为很多代码会自动生成。相当于运营商生成框架,然后我们实现相应的业务逻辑。
1准备工作
安装好 go 环境安装 operator-sdk
2初始化项目
然后,我们将根据官网的sdk示例,一步步实现一个memcached运算符。这里也可以改成Redis,但是为了保证和官网的一致性,我们会根据官网创建一个memcached运算符。
? ~ cd $GOPATH/src? src mkdir memcached-operator? src cd memcached-operator? memcached-operator operator-sdk init –domain yangbodong22011 –repo github.com/yangbodong22011/memcached-operator –skip-go-version-check // 这里需要注意 domain 最好是和你在 https://hub.docker.com 的注册名称相同,因为后续会发布 docker 镜像Writing kustomize manifests for you to edit…Writing scaffold for you to edit…Get controller runtime:$ go get sigs.k8s.io/controller-runtime@v0.9.2Update dependencies:$ go mod tidyNext: define a resource with:$ operator-sdk create api
3创建API和控制器
? memcached-operator operator-sdk create api –group cache –version v1alpha1 –kind Memcached –resource –controllerWriting kustomize manifests for you to edit…Writing scaffold for you to edit…api/v1alpha1/memcached_types.gocontrollers/memcached_controller.goUpdate dependencies:$ go mod tidyRunning make:$ make generatego: creating new go.mod: module tmpDownloading sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1go get: installing executables with ‘go get’ in module mode is deprecated. To adjust and download dependencies of the current module, use ‘go get -d’. To install using requirements of the current module, use ‘go install’. To install ignoring the current module, use ‘go install’ with a version, like ‘go install example.com/cmd@latest’. For more information, see https://golang.org/doc/go-get-install-deprecation or run ‘go help get’ or ‘go help install’….go get: added sigs.k8s.io/yaml v1.2.0/Users/yangbodong/go/src/memcached-operator/bin/controller-gen object:headerFile=”hack/boilerplate.go.txt” paths=”./…”? memcached-operator
以上步骤实际上生成了一个操作符框架。接下来,我们首先定义memcached集群包含什么,将默认实现修改为Size,这表示Memcached集群的数量。最后,我们调用make generate和make manifests来自动生成deepcopy和CRD资源。
? memcached-operator vim api/v1alpha1/memcached_types.go // 修改下面 Memcached 集群的定义// MemcachedSpec defines the desired state of Memcachedtype MemcachedSpec struct { //+kubebuilder:validation:Minimum=0 // Size is the size of the memcached deployment Size int32 `json:”size”`}// MemcachedStatus defines the observed state of Memcachedtype MemcachedStatus struct { // Nodes are the names of the memcached pods Nodes []string `json:”nodes”`}? memcached-operator make generate/Users/yangbodong/go/src/memcached-operator/bin/controller-gen object:headerFile=”hack/boilerplate.go.txt” paths=”./…”? memcached-operator make manifests/Users/yangbodong/go/src/memcached-operator/bin/controller-gen “crd:trivialVersions=true,preserveUnknownFields=false” rbac:roleName=manager-role webhook paths=”./…” output:crd:artifacts:config=config/crd/bases? memcached-operator
4机具控制器
接下来,第二步是定义创建Memcached集群时要做什么。
? memcached-operator vim controllers/memcached_controller.gohttps://raw.githubusercontent.com/operator-framework/operator-sdk/latest/testdata/go/v3/memcached-operator/controllers/memcached_controller.go //将 example 换成 yangbodong22011,注意,// 注释中的也要换,实际不是注释,而是一种格式? memcached-operator go mod tidy; make manifests/Users/yangbodong/go/src/memcached-operator/bin/controller-gen “crd:trivialVersions=true,preserveUnknownFields=false” rbac:roleName=manager-role webhook paths=”./…” output:crd:artifacts:config=config/crd/bases
5发布操作员图像
? memcached-operator vim Makefile将 -IMG ?= controller:latest 改为 +IMG ?= $(IMAGE_TAG_BASE):$(VERSION)? memcached-operator docker login // 提前登录下 dockerLogin with your Docker ID to push and pull images from Docker Hub. If you don’t have a Docker ID, head over to https://hub.docker.com to create one.Username: yangbodong22011Password:WARNING! Your password will be stored unencrypted in /Users/yangbodong/.docker/config.json.Configure a credential helper to remove this warning. Seehttps://docs.docker.com/engine/reference/commandline/login/#credentials-storeLogin Succeeded? memcached-operator sudo make docker-build docker-push …=> => writing image sha256:a7313209e321c84368c5cb7ec820fffcec2d6fcb510219d2b41e3b92a2d5545a 0.0s => => naming to docker.io/yangbodong22011/memcached-operator:0.0.1 0.0sfac03a24e25a: Pushed6d75f23be3dd: Pushed0.0.1: digest: sha256:242380214f997d98186df8acb9c13db12f61e8d0f921ed507d7087ca4b67ce59 size: 739
6修改镜像和部署
? memcached-operator vim config/manager/manager.yamlimage: controller:latest 修改为 yangbodong22011/memcached-operator:0.0.1? memcached-operator vim config/default/manager_auth_proxy_patch.yaml因为国内访问不了 gcr.ioimage: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 修改为 kubesphere/kube-rbac-proxy:v0.8.0 ? memcached-operator make deploy…configmap/memcached-operator-manager-config createdservice/memcached-operator-controller-manager-metrics-service createddeployment.apps/memcached-operator-controller-manager created? memcached-operator kubectl get deployment -n memcached-operator-system // ready 说明 operator 已经部署了NAME READY UP-TO-DATE AVAILABLE AGEmemcached-operator-controller-manager 1/1 1 1 31s? memcached-operator
7创建Memcached集群
? memcached-operator cat config/samples/cache_v1alpha1_memcached.yamlapiVersion: cache.yangbodong22011/v1alpha1kind: Memcachedmetadata: name: memcached-samplespec: size: 1? memcached-operator kubectl apply -f config/samples/cache_v1alpha1_memcached.yamlmemcached.cache.yangbodong22011/memcached-sample created? memcached-operator kubectl get podsNAME READY STATUS RESTARTS AGEmemcached-sample-6c765df685-xhhjc 1/1 Running 0 104sredis 1/1 Running 0 177mredis-deployment-866c4c6cf9-zskkb 1/1 Running 0 3h4mredis-sfs-0 1/1 Running 0 112mredis-sfs-1 1/1 Running 0 112m? memcached-operator
您可以通过kubectl日志查看操作员的日志:
? ~ kubectl logs -f deployment/memcached-operator-controller-manager -n memcached-operator-system2021-11-05T09:50:46.042Z INFO controller-runtime.manager.controller.memcached Creating a new Deployment {“reconciler group”: “cache.yangbodong22011”, “reconciler kind”: “Memcached”, “name”: “memcached-sample”, “namespace”: “default”, “Deployment.Namespace”: “default”, “Deployment.Name”: “memcached-sample”}
至此,我们运营商——SDK的任务暂时告一段落。
七份总结
本文通过一个循序渐进的Redis实例,介绍了k8s的架构,各个组件的功能,以及k8s中的Pod、Deployment、StatefulSet等概念,并通过operator-sdk演示了一个完整的运营商生产实例。
八。参考资料
[1]“对Kubernetes的深入分析”章雷,CNCF TOC成员,在阿里巴巴。[2] Kubernetes权威指南,第5版[3]Google用Borg进行大规模集群管理https://research . Google/pubs/pub 43438/[4]https://www.redhat.com/zh/topics/containers/what-is-.[5]https://www . infoworld . com/article/3632142/how-docker-broke-in-half . html?[6]https://landscape . cncf . io/[7]https://docs . docker . com/desktop/kubernetes/[8]https://minikube . sigs . k8s . io/docs/start/[9]https://www.aliyun.com/product/kubernetes?[10]https://github . com/kubernetes/kube ADM[11]https://www . cn blogs . com/Chiang Chou/p/k8s-1 . html[12]https://github . com/tarosk
作者|范澈
原文链接:https://developer.aliyun.com/article/804858? UTM _ content = g _ 1000308695
本文为阿里云原创内容,未经允许不得转载。
本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://51itzy.com/26247.html