离线场景下的k8s集群设计
by 伊布
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方案:打包在宿主机上。
- 解析k8s编排文件(分为app编排文件和白名单编排文件),将需要的docker镜像从Harbor pull到宿主机中;
- 在宿主机上,docker save镜像为若干个单独的镜像压缩包
优势是,在持续开发过程中,由于前面已经docker pull过,所以本次打包时,docker pull的只有差异部分,效率比较高。但缺点也非常明显,由于各镜像单独打包,所以最终交付件膨胀严重(约2-3倍),对离线部署不是很友好。
alpha方案:
在宿主机上拉起registry容器,将镜像push到registry容器后,将registry容器存储镜像的目录打一个大包,可以解决离线包膨胀的问题,但无法支持多用户同时打包,未实践。
beta方案:打包在docker in docker容器中进行,以解决离线包膨胀的问题,以及支持多用户同时打包。
- 拉起dind容器,将本地目录
/home/$user/$build_patch
挂载到dind容器的/home/registry
- 解析k8s编排文件(分为app编排文件和白名单编排文件),将需要的docker镜像从Harbor pull到dind容器中;
- 在dind容器中,拉起新的docker registry容器;将dind容器的
/home/registry
目录挂载到 registry容器的/var/lib/registry
;将pull下来的容器再push到registry容器中; - 在宿主机上,将本地目录
/home/$user/$build_patch
打一个大包。 - 最后将registry镜像单独打一个包
如此,可以得到:
- 用来在离线环境上拉起registry容器的registry:v2镜像
- 用来在离线环境上,运行所有app的镜像(包括k8s和业务app)
可改进点: 每次在dind中都需要从0开始pull,实际上很多基础镜像是可以复用的,可以考虑将pull下来的基础镜像做一个base,给dind容器,减少pull的工作量。
4 离线交付
yum离线+docker镜像离线+部署软件
目前实践的是release方案。
- 准备docker环境
- docker方式拉起minio集群。
- docker方式拉起registry,其数据存储后端为minio集群的特定bucket。
- docker load各app的离线镜像压缩包,并push到registry中。
针对beta方案,可以考虑:
- 准备docker环境
- docker方式拉起minio集群。将前述dind得到的镜像包,push到minio集群中特定bucket。
- docker方式拉起registry,其数据存储后端为minio集群的特定bucket。
- registry容器功能ready
Subscribe via RSS