It's our wits that make us men.

起岛自动化,部署容器化与集群化

Posted on By 刘电波

introduction

本文是对于测试环境的容器化部署的一些思路和展望。

1.痛点所在

(1) 服务器资源利用率低,测试环境的服务器配置差异较大,使用情况各异,总体利用率不高。

(2) 测试若刮风频繁,不仅会影响在体验的人,测试自己也不太能自由的刮风。

(3) 服务器没有任何隔离性,多个岛的隔离性只能通过存在于不同服务器来保证,会引发一些问题,例如炸岛可能会极大的占用服务器资源,或者更改时间会影响到其它人或服务。

(4) 扩展不太方便(指的是岛数量增加)。

(5) 不好简单的配套相应的监控,需要比较兼容且轻量级的。

(6) 在大家都不愿手动起岛的情况下,影响团队和谐和开发效率。

2.解决思路

(1) 将岛的进程放到docker里面,每次启动容器就可以了。但是有个很严重的缺陷,扩展起来十分不易,因为我们测试环境涉及到多个服务器,不易管理。

(2) 所以引入了k8s集群管理方案,但是我们的起岛进程和传统的微服务又不太一样,我们对状态的要求很严格,而且还存在以下的问题:

1.我们要将1号岛起在52服务器上面,就要先去数据库注册数据,把1号岛写到数据库,然后再起岛。但是k8s集群分配pod的时候用户进程是不知道k8s会把哪个pod(岛进程),也不应该知道。

2.对于测试环境,我们的ci会不断的把新的代码推送到仓库,如果每次推送都构建一个镜像,那么每次创建新的pod就会需要拉取新的镜像,是十分耗时的。

3.对于development(k8s的一类创建pod的资源,会持续管理pod的生命周期)的起岛方案来说,我们的pod被损坏后,他会持续的去重启pod,但是我们的岛却不太适合这种恢复方案,因为我们希望我们的用户进程去管理岛,而不仅仅是配置一些恢复方案去恢复状态,而且对于development 来说每个pod 没有差异性,这是用户进程不能容忍的。

解决:

对于1,我们将注册进程放到pod里面,每次启动pod的时候执行注册,并且在创建pod之前就预计要暴露的端口。

对于2,我们用永久卷的方式,将代码挂载到共享卷里面,就不用每次拉取镜像了,而且也不用去改变ci在测试环境现存的运作形式。

对于3,我们直接创建pod,由用户进程管理pod,但是分配还是交给k8s,因为不想放弃k8s分配的诱人。

3.代码实现

(1) 嵌套在自动导表系统里面,耦合性会强一点,有必要再分离吧。

(2) 与自动导表不同的是,这边要与服务器做通信,我们用jsch的shell方式去通信,将读取交给另外的线程处理。

4.最终思路总结

(1) 现在的起岛步骤变为: (824服务器是我们自动导表的服务器,与pod管理进程放在一起 52服务器是我们k8smaster服务器 其它服务器是普通的node节点)

1.用户点击起岛。

2.824服务器接到这个请求,然后根据自己的计数分配端口和id(全局唯一),然后通过idk8s的启动podyaml创建,通过apply命令通过API在52服务器进行创建。

3.52服务器的apiapiserver将其写入etcd

4.scheduluer 检测到未绑定 NodePod ,开始调度并更新 PodNode 绑定。

5.kubelet 检测到有新的 Pod 调度过来,通过 container runtime 运行该 Pod。{

运行该pod的时候,我们会将此服务器注册进mysql,并且根据yaml的参数起岛。

}

6.kubelet 通过 container runtime 取到 Pod 状态,并更新到 apiserver 中。

7.创建成功,我们的824服务器检测到这个状态,最终返回给前端。

5.一些小细节

1.什么时机进行岛信息注册?

容器创建的时候,里面会有一个注册脚本,注册脚本通过http请求,将本岛注册到mysql。

2.side container有什么作用?

防止pod被销毁,保留server container的残留信息。

3.怎么简单监控Podserver container的状态?

对于server container,使用容器指针,通过检测TCP端口,可以检测到server进程是否已经启动。

对于Pod,使用API Server即可直接看到状态。

4.为什么要以挂载的形式将代码单独挂载?

1是处于对全量复制的速度的担心。

2.因为还没有使用私人仓库,镜像是从docker hub直接拉取的,也会有隐私问题。

5.时间修改

由于容器技术并不保证隔离时间,通过此项目完成时间的隔离https://github.com/wolfcw/libfaketime 可做到进程级别或者pod级别的隔离。

6.展望与改进

(1) 每个岛可能有些特殊化配置,之后评估一下影响。

(2) 目前为了兼容现存ci环境,所以把集群机器强行分为内测和当周,通过label分配机器,看后续是否有必要处理。

(3) 有位童鞋提了个思路很好,就是把所有的windows机器加入集群,这样就不用担心内存不够了,但是这里面可能会涉及到安全的问题,而且windows装虚拟化产品也不易,暂时作为一个思考方向。

(4) 调度策略不行 加了资源限制,不够会被驱逐或者分配时直接失败。

附录:原始yaml,每个pod暴露3个端口,全局管理30000开始

apiVersion: v1
kind: Pod
metadata:
  name: altraserver-liudianbo-dev-269
  labels:
    name: altraserver
spec:
  volumes:
    - name: server-pv-storage
      persistentVolumeClaim:
        claimName: server-pv-claim
  containers:
  - name: altraserver
    image: bo602505401/jdk8-curl-libfaketime:latest
    resources:
      limits:
        memory: "2048Mi"
      requests:
        memory: "1500Mi"
    env:
    - name: curlServer
      value: 10.18.8.97
    - name: username
      value: 你猜
    - name: LD_PRELOAD
      value: "/usr/lib64/faketime/libfaketime.so.1"
    - name: FAKETIME_TIMESTAMP_FILE
      value: "/tmp/server-faketime.rc"
    - name: FAKETIME_DONT_RESET
      value: "1"
    - name: FAKETIME_DONT_FAKE_MONOTONIC
      value: "1"
    command: ["sh","/home/seed.sh","269","false","1269","2021-09-18 23:30:07"]
    securityContext:
        capabilities:
          add: ["NET_ADMIN","SYS_TIME"]
    volumeMounts:
        - mountPath: "/home/server"
          name: server-pv-storage
    ports:
    - name: app-common-port
      containerPort: 30807
      hostPort: 30807
    - name: remote-msg-port
      containerPort: 30808
      hostPort: 30808
    - name: server-port
      containerPort: 30809
      hostPort: 30809
    readinessProbe:
      tcpSocket:
        port: server-port
      initialDelaySeconds: 15
      periodSeconds: 5
      successThreshold: 1
      timeoutSeconds: 10
      failureThreshold: 10
  - name: side-container
    image: alpine:3.14.1
    command: ["/bin/sh", "-ce", "tail -f /dev/null"]
  nodeSelector:
    branch: dev
  dnsPolicy: Default
  restartPolicy: Never

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: server-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/home/server"


---
`apiVersion`: `v1`
`kind`: `PersistentVolumeClaim`
`metadata`:
  `name`: `server-pv`-`claim`
`spec`:
  `storageClassName`: `manual`
  `accessModes`:
    - `ReadWriteOnce`
  `resources`:
    `requests`:
      `storage`: 8`Gi`