1 k8s集群设计

1.1 网络

使用flannel with vxlan backend.

  • 不同node的容器间通信:通过 flannel 的vxlan解决
  • k8s集群外部到容器内通信:通过 nodePort+vip(keepalived) 解决
  • node到其他node的容器通信:通过 flannel 下发的iptables规则,做SNAT解决

1.2 分布式配置

没有使用CMDB,而是通过configmap/secret来解决分布式配置的问题。

要求app容器与外部的交流手段只有环境变量,不允许使用配置文件挂载的方式。特殊情况必须引入到容器的文件,以configmap/secret volume的方式挂载。

1 配置变量

在部署管理软件中,将app需要的信息:nodes详情、vip、hadoop的配置信息等,创建为configmap,将用户名、密码等信息,创建为secret;需要使用该信息的app通过configMapKeyRef和secretKeyRef来获取相应的信息,并以环境变量的形式导入到容器中;容器中app启动时获取环境变量,生成需要的配置文件。

2 secret volume

某些app,需要宿主机的.ssh文件用来做ssh免密打通,将此类文件做成secret volume挂载到容器中。

3 configmap volume

部分app的配置,以configmap volume的形式挂载到容器中,例如mysql的my.cnf文件、hadoop-conf、spark-conf。

1.3 存储

有状态app,例如mysql,需要存储数据,通常建议使用PVC来获取k8s集群的PV,但受限于整体方案,无法引入ceph类的分布式存储,因此使用 StatefulSet+nodeSelector+hostPath volume,即:将容器固定在某node上,并固定其在宿主上的目录,若容器重启,数据不丢。

注意StatefulSet不是必须的。使用Statefulset的目的,是为了解决像 mysql 集群这种小集群不同app间通过主机名寻找集群各成员的问题:StatefulSet会给其下面的Pod命名为xxx-0/xxx-1。

但如果mysql直接跑在hostnet上,也就不需要使用容器的主机名了,可以直接使用node的ip来寻找。

1.4 HA

部署基于kubeadm。但kubeadm当前不支持HA,需要做一些改造。

  • etcd集群先于k8s集群部署。etcd直接容器方式拉起。为了保障etcd服务,将其作为systemd service。相应的,kubeadm初始化集群时需要指定etcd的信息。
  • apiserver, controller-manager,在3个node的manifests目录中放编排文件。3个apiserver会将自己的endpoint挂到 svc kubernetes 上,client通过该svc访问apiserver。
  • dns deployment的replicas数改为3。由于容器访问dns是通过dns svc访问的,因此直接增加replicas即可。

1.5 数据库

k8s集群提供具备ha的mysql。mysql使用半同步。

使用CronJob来定期清理mysql的binlog。

部署管理软件提供框架,各应用只需要声明自己需要的数据库、表即可,框架会自行去mysql完成库表的创建工作。

1.6 日志收集

flume作为app容器的sideCar(僚机),将emptyDir volume共享挂载到app容器和flume容器;flume以tailDir的方式,收集app生成的所有日志,并sink到hdfs上以供后续使用。

1.7 容器内支持宿主机的DNS解析

为了解决spark和hadoop的互通问题。专利。支持扩容。

1.8 资源声明

要求所有容器都必须声明其cpu/memory的requests和limits,且尽可能设置为一致,避免被OOM Killer杀死。

1.9 健康检测

livenessProbe。要求web app提供detect api,用来给livenessProbe使用。

2 开发

k8s镜像:直接由k8s负责人push到Harbor

业务app镜像:各业务编写Dockerfile,push到Harbor:直接docker build、容器内docker build。

Harbor区分产品,支持多用户、自注册。Harbor后端可以为minio集群。 版本管理

3 打包

release方案:打包在宿主机上。

  1. 解析k8s编排文件(分为app编排文件和白名单编排文件),将需要的docker镜像从Harbor pull到宿主机中;
  2. 在宿主机上,docker save镜像为若干个单独的镜像压缩包

优势是,在持续开发过程中,由于前面已经docker pull过,所以本次打包时,docker pull的只有差异部分,效率比较高。但缺点也非常明显,由于各镜像单独打包,所以最终交付件膨胀严重(约2-3倍),对离线部署不是很友好。

alpha方案:

在宿主机上拉起registry容器,将镜像push到registry容器后,将registry容器存储镜像的目录打一个大包,可以解决离线包膨胀的问题,但无法支持多用户同时打包,未实践。

beta方案:打包在docker in docker容器中进行,以解决离线包膨胀的问题,以及支持多用户同时打包。

  1. 拉起dind容器,将本地目录/home/$user/$build_patch挂载到dind容器的/home/registry
  2. 解析k8s编排文件(分为app编排文件和白名单编排文件),将需要的docker镜像从Harbor pull到dind容器中;
  3. 在dind容器中,拉起新的docker registry容器;将dind容器的/home/registry目录挂载到 registry容器的 /var/lib/registry;将pull下来的容器再push到registry容器中;
  4. 在宿主机上,将本地目录/home/$user/$build_patch打一个大包。
  5. 最后将registry镜像单独打一个包

如此,可以得到:

  1. 用来在离线环境上拉起registry容器的registry:v2镜像
  2. 用来在离线环境上,运行所有app的镜像(包括k8s和业务app)

可改进点: 每次在dind中都需要从0开始pull,实际上很多基础镜像是可以复用的,可以考虑将pull下来的基础镜像做一个base,给dind容器,减少pull的工作量。

4 离线交付

yum离线+docker镜像离线+部署软件

目前实践的是release方案。

  1. 准备docker环境
  2. docker方式拉起minio集群。
  3. docker方式拉起registry,其数据存储后端为minio集群的特定bucket。
  4. docker load各app的离线镜像压缩包,并push到registry中。

针对beta方案,可以考虑:

  1. 准备docker环境
  2. docker方式拉起minio集群。将前述dind得到的镜像包,push到minio集群中特定bucket。
  3. docker方式拉起registry,其数据存储后端为minio集群的特定bucket。
  4. registry容器功能ready