运满满自开始微服务改造以来,线上线下已有数千个微服务的Java实例在运行中。这些Java实例部署在数百台云服务器或虚机上,除少数访问量较高的关键应用外,大部分实例均混合部署。
这些实例的管理,采用自研平台结合开源软件的方式,已实现通过平台页面按钮菜单执行打包、部署、启动、停止以及回滚指定的版本等基本功能,取得了不错的效果。但仍然存在如下几个痛点:
运满满飞速发展的业务,对系统稳定性的要求越来越高,我们急需解决如上问题。
最初吸引我们的是容器技术良好的隔离和水平扩展等特性,而Docker的口碑以及几年前参与的一些Docker项目经验,使得采用Docker容器技术成了我们的不二选择。
但我们仍然需要一套容器编排系统,来实现自动化管理Docker容器,大致了解下来有3个选项:Kubetnetes(K8S)、swarm、mesos
这3个我们都不熟悉,而这个项目的节奏很紧迫,不允许我们对这3个系统深入了解后再做选择。好在Github有一个统计功能,我们在Github上查到了这3个开源项目的一些基本情况,如下图:
根据这份统计数据,以及拥有Google公司的光环,我们在很短的时间内确定了使用K8S作为容器编排管理系统。K8S,这个开源项目号称可以自动部署、扩展和管理容器应用,并且能解决如下核心问题:
负载均衡 - 一个应用运行多个同样的容器,内部Service提供了统一的访问定义,以负载均衡的方式来提供访问。
服务发现 - Service和Kube-DNS结合,只需要通过固定的Service名称就可以访问到对应的容器,不需要独立寻找使用服务发现组件。
高可用 - K8S会检查服务的健康状态,发现异常时会自动尝试重新启动服务,保障正常运行。
滚动升级 - 在升级过程中K8S会有规划的挨个容器滚动升级,把升级带来的影响降低到最小。
自动伸缩 - 可以配置策略当容器资源使用较高会自动增加新的容器来分担压力,当资源使用率降低会回收容器。
快速部署 - 编写好对应的编排脚本,可以在极短的时间部署一套环境。
资源限制 - 对程序限制最大资源使用量避免抢占资源遇到事故或压力也能从容保障基础服务不受影响。
进一步深入了解K8S之后,我们大致确定了会用到如下组件、相关技术和系统:
从下图中可以看到2个明显的变化:
当前业务应用主要分为2种,仅供内部应用调用的RPC服务(Pigeon框架)和对外提供服务的REST API,REST API可进一步细分为2种,已接入API网关和未接入API网关。其中RPC服务和已接入API网关的应用均有自己的注册中心,迁移步骤相对简单,在K8S集群中启动对应的应用即可。未接入API网关的应用采用K8S Ingress插件提供对外服务入口,需要一些配置。系统架构如下图,最终目标是要实现将图中下方的两个框内的应用全部迁入K8S集群中。
由于公有云的限制,我们主要结合服务商提供的SLB来实现,示意图如下:
由于集群内POD的IP地址动态变化,我们采用 Traefik+Ingress+Nginx+SLB 的方式,来提供一个对外服务的统一入口。Traefik根据HTTP请求的域名和路径路由到不同的应用服务,Nginx则执行一些复杂的诸如rewrite等操作,SLB提供高可用。架构示意图如下:
为了实现同一个镜像可以兼容运行在DEV、QA、Production等各种环境,必须编写一个初始化脚本,该脚本被存放在镜像中。当容器启动时,从Env变量中读取当前所在的环境,并创建一系列软链到各环境对应的配置文件以及设置日志目录等其他初始化操作,随后fork一个新进程用于检测和设置该容器内应用是否已完成正常启动(配合容器 readiness 探针使用),同时调用应用启动脚本。
下图为容器内通过软链指向不同的环境配置文件:
下图为容器内通过软链设置日志目录:
当前应用日志均以文件形式存放,且单个实例对应多个日志文件,无法采用K8S官方推荐的日志方案。同时由于容器的无状态化,我们必须另想其他办法保存日志。目前采用的是将Node上的固定目录作为存储卷挂载到容器内,在容器启动时通过初始化脚本按照应用名+容器IP生成该容器特定的日志路径。为了便于查看日志,我们提供3种途径:
下图为Node服务器上的日志目录结构:
下图为Node服务器上共享的日志下载路径:
采用 Heapster+InfluxDB+Grafana 组合,需要注意的是其中InfluxDB用于存放监控数据,需要将数据持久化。在Grafana上制作了不同维度的dashboard,可根据Namespace、Node、应用名进行检索,可按照CPU、内存、网络带宽、硬盘使用量筛选应用,方便故障排查和日常优化。(当然,更好的监控系统是Prometheus,已经在上线的路上。)
下图为监控大盘:
下图为监控菜单:
下图为某应用的监控图:
Harbor我们目前采用的是一主多从结构,主库与打包Jenkins都在线下网络中,镜像上传到主库后会被自动同步到线下另一个从库以及线上的从库中,如下图所示:
我们的规划是构建一颗镜像树,所有的应用都基于这颗树上的基础镜像来构建应用镜像,各应用构建时选择最相似的基础镜像,再增加应用的特殊需求即可。基于此镜像树,我们95%以上的应用均无需在Gitlab里放置Dockerfile,Dockerfile在打包时根据变量自动生成即可,例如:
下图为脚本自动生成的某应用Dockerfile:
镜像树结构示意图如下:
容器化:DEV/QA环境的应用已完成Docker化,产品环境中应用约98%已完成Docker化。
系统自愈:应用OOM或其他Crash时,系统能够自动拉起新的节点以替换故障节点,高级健康检查暂未开启(需其他方面配合)。
弹性伸缩:关键应用全部开启弹性伸缩,访问量高峰期观察到的效果很好。
滚动发布:可按指定的比例分批次部署更新应用版本,先更新一批,成功后销毁一批,依次滚动。
快速回滚:当前仅支持单应用快速回滚,后期如需要增加事务级回滚能力,采用K8S的rollout功能可以方便实现。
作者简介
王春林,就职于运满满技术保障部,关注容器、DevOPS等领域。