路漫漫其修远兮
吾将上下而求索

k8s学习:有状态服务容器化调研

1、背景

公司部分业务功能的实现是有状态的方式实现,部署在亚马逊的ec2上,为了方便这部分业务进行容器化改造,需要调研业界常见的有状态服务容器上部署方式,通过开源的方案来查看里面架构和实现逻辑,进行讨论来找出一种适合公司业务的部署改造方式。

这里通过etcd集群在容器上部署的案例来进行了解。这个项目通过etcd-operator自定义控制器来实现对etcd集群的管理:创建,删除,升级,漂移等

2、controller实现

当前自定义控制器常见的有两种:

  • 一种是使用 code-generater 方式生成对于资源客户端代码,controller-manager组件以及很多开源operator都使用此种方式

  • 一种是使用 operator-sdk 方式生成对应controller代码框架,此方式是更高层次的封装,简化用户侧的代码,开发效率较高

下图是控制器工作流程,使用 client-go k8s客户端连接集群,将关注的资源的变更(增删改)放入队列,供自定义控制器来实现自定义的控制逻辑

20211223-6651082926.png


3、有状态和无状态服务区别

有状态服务和无状态服务区别

  • 实例数量:无状态服务可以有一个或多个实例,可以横向扩容,k8组件里的kube-apiserver服务;有些有状态的服务即使部署多个,也只有一个服务进行工作。或者某个节点为主,其余为从

  • 存储卷:无状态服务可以有存储卷,也可以没有,即使有也无法备份存储卷里面的数据;有状态服务必须要有存储卷,并且在创建服务时,必须指定给该存储卷分配的磁盘空间大小。

  • 数据存储:无状态服务运行过程中的所有数据(除日志和监控数据)都存在容器实例里的文件系统中,如果实例停止或者删除,则这些数据都将丢失,无法找回;而对于有状态服务,凡是已经挂载了存储卷的目录下的文件内都可以随时进行备份,备份的数据可以下载,也可以用于恢复新的服务

k8s自带有状态服务控制器statefulset,其应用场景包括:

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现

  • 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现

  • 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现

  • 有序收缩,有序删除(即从N-1到0)

常见的有状态业务场景

常见服务

etcd集群

statefulset方式

访问方式 客户端通过svc访问后端随机ip,etcd从节点将请求代理到主节点,pod名字不固定 客户端通过handless service方式访问具体的pod
存储 每个pod需要挂载单独的pvc来存取数据,没有严格的pod和pvc挂载对应关系 固定的pod需要挂载对应的pvc
有序部署 部署启动有顺序要求,第一个启动的pod参数为new,其余加入的pod参数为exist 有序部署,有序收缩

备注:不管是通过svc方式,还是通过handless方式,还是通过注册中心方式,都是为了感知pod切换事件,只是有些是从客户端进行感知进行额外操作,有些是从服务侧来进行感知处理。

4、etcd-operator使用

因项目比较旧,api版本不知道当前公司1.21版本的k8环境。经测试,可用的环境为:k8s 1.12,go 1.12,dep管理工具

创建crd描述和启动控制器

#kubectl apply -f deployment.yaml
deployment.apps/etcd-operator created

 

#kubectl get crd
NAME                                    CREATED AT
etcdclusters.etcd.database.coreos.com   2021-12-21T05:28:31Z

 

#kubectl get pod
NAME                              READY   STATUS    RESTARTS   AGE
etcd-operator-5c6bddb7f6-pzxmr    1/1     Running   0          5m28s

创建etcd集群

$ cat example/example-etcd-cluster.yaml
apiVersion: "etcd.database.coreos.com/v1beta2"
kind: "EtcdCluster"
metadata:
  name: "example-etcd-cluster"
spec:
  size: 3
  version: "3.2.13"

 

#kubectl apply -f example-etcd-cluster.yaml
etcdcluster.etcd.database.coreos.com/example-etcd-cluster created

 

#kubectl get pod
NAME                              READY   STATUS    RESTARTS   AGE
example-etcd-cluster-g8bp4lncsc   1/1     Running   0          2m26s
example-etcd-cluster-tcw4d8p6j2   1/1     Running   0          65s
example-etcd-cluster-tzcq4dlgcb   1/1     Running   0          106s

 

#kubectl describe svc example-etcd-cluster
Name:              example-etcd-cluster
Namespace:         default
Labels:            app=etcd
                   etcd_cluster=example-etcd-cluster
Annotations:       service.alpha.kubernetes.io/tolerate-unready-endpoints: true
Selector:          app=etcd,etcd_cluster=example-etcd-cluster
Type:              ClusterIP
IP:                None
Port:              client  2379/TCP
TargetPort:        2379/TCP
Endpoints:         172.30.22.4:2379,172.30.23.3:2379,172.30.24.4:2379
Port:              peer  2380/TCP
TargetPort:        2380/TCP
Endpoints:         172.30.22.4:2380,172.30.23.3:2380,172.30.24.4:2380
Session Affinity:  None
Events:            <none>

查看集群状态

ETCDCTL_API=3 etcdctl -w table --cluster --endpoints http://example-etcd-cluster-client:2379  endpoint statu
+------------------------------------------------------------------------------+------------------+---------+---------+-----------+-----------+------------+
|                                   ENDPOINT                                   |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+------------------------------------------------------------------------------+------------------+---------+---------+-----------+-----------+------------+
| http://example-etcd-cluster-g8bp4lncsc.example-etcd-cluster.default.svc:2379 | 430d76ef082b59eb |  3.2.13 |   25 kB |      true |        25 |          9 |
| http://example-etcd-cluster-tzcq4dlgcb.example-etcd-cluster.default.svc:2379 | 94b5e0613b0fec12 |  3.2.13 |   25 kB |     false |        25 |          9 |
| http://example-etcd-cluster-tcw4d8p6j2.example-etcd-cluster.default.svc:2379 | dce8dfcdd2583801 |  3.2.13 |   25 kB |     false |        25 |          9 |
+------------------------------------------------------------------------------+------------------+---------+---------+-----------+-----------+------------+

漂移

如果master所在pod因意外宕机,其余pod会使用raft协议重新进行选举,选举过程在外面看来就是多个pod之间进行网络通信,因此我们只要保证网络畅通即可,operator会重新启动一个pod。

并且外部是通过svc方式进行访问,所以宕机的pod会从svc中摘除,服务不会受影响

删除

删除crd和删除controller不会导致etcd集群意外删除,删除对应etcd应用(example-etcd-cluster)会删除所有pod资源

5、etcd-operator源码架构

代码:https://github.com/coreos/etcd-operator/

项目实现三个operator

operator

实现功能

crd

etcd-operator
etcd集群创建删除,pod,svc管理

etcdclusters.etcd.database.coreos.com

etcd-backup-operator
备份etcd数据到对象存储

etcdbackups.etcd.database.coreos.com

etcd-restore-operator
从存储还原etcd数据到新集群 etcdrestores.etcd.database.coreos.com

20211223-3491896852.png

备注:

依赖解决

Etcd-operator
1、go mod init etcd-operator 
replace (
github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.5
go.uber.org/atomic => github.com/uber-go/atomic v1.5.0
)

使用dep
安装 https://cloud.tencent.com/developer/article/1433229
把文件放到src目录
进入src的项目目录
如果有自己的mod文件,要删除
执行脚本 ./hack/update_vendor.sh
Go版本要降低,注意go是否为自己装的go 1.12,而不是系统带的
终于可以了
没法执行build镜像不存在,可以打成二进制,再执行dockerfile


未经允许不得转载:江哥架构师笔记 » k8s学习:有状态服务容器化调研

分享到:更多 ()

评论 抢沙发

评论前必须登录!