看啥推荐读物
专栏名称: RobinsonZhang
前端工程师&&职能主管,达摩兵网站主
今天看啥  ›  专栏  ›  RobinsonZhang

一下科技关于docker实践分享摘录

RobinsonZhang  · 掘金  · 前端  · 2018-11-23 07:04
阅读 59

一下科技关于docker实践分享摘录

公司介绍

炫一下(北京)科技有限公司2011年8月成立于北京,是移动短视频娱乐分享应用和移动视频技术服务提供商。旗下拥有“秒拍”、“小咖秀”、“一直播”三款移动视频产品 [1-2] 。 2016年11月21日,旗下拥有秒拍、小咖秀、一直播的一下科技宣布完成5亿美元E轮融资,该轮融资也创下了国内移动视频行业的单轮融资金额最高纪录。 [3] 根据一下科技公布的数据,截至2016年11月21日,秒拍与小咖秀的日播放量峰值已突破25亿次,日均上传量突破150万条,日均覆盖用户超过 7000万 [2] 。

分享嘉宾

一下科技的吴飞群,感谢大佬的文档支持。

容器化的背景介绍

随着公司业务规模的不断变大, 为了能够快速迭代, 公司的后端架构也逐渐地从传统架构模型转向了微服务架构. 公司主要开发语言是Java, Golang, PHP.大部分的服务都运行在公有云的虚拟主机上

暴露问题

流程繁琐

  • 后端架构转成微服务以后服务的数量变得更多, 每个服务都需要开通虚拟机, 配置监控, 配置jenkins, 配置ELK, 配置白名单等.
  • 不同的开发语言有些配置还不同,有一定的瓶颈
  • 很多公司甚至没有专业运维

没有稳定的测试环境.

由于测试环境之间也需要互相调用, 经常联调, 由于一些历史原因, 不是所有的服务都有稳定的测试环境, 这给测试人员带来了不少的麻烦, 经常会用线上的部分机器进行调试, 存在不少的潜在风险.

资源利用率低

为了方便管理, 每个服务都是分配的单独的服务器进行部署, 由于产品用户的使用习惯, 公司大部分的服务的资源使用都有高峰低估的特点, 为了保障服务的资源充足, 我们核心的服务高峰的时候也会控制资源使用率, 所以大部分服务在平时的资源使用率都是很低的, 造成了资源的大量浪费.

扩容/缩容不及时

业务高峰期的时候经常需要扩容机器, 扩容需要开发申请, 运维审批, 部署服务, 测试确认等很多环节, 一个服务的扩容最快也得需要半个小时才能完成, 半个小时对于很对关键服务来说其实是很长的, 是无法忍受的.

部署系统的不足

后端系统在向微服务演进的过程中, 不同的后端小团队出现了编程语言, 框架等的多元化, 之前存在的部署系统不能充分满足多语言多框架的部署需求.(之前大部分是PHP)

整体架构

各个模块的落地细节

容器的接入

这一部分主要跟大家分享的是我们怎样把运行在传统虚拟机/物理机上的服务逐渐接入到k8s上的.

在进行容器化改造之前, 我们的运维平台已经具有了代码发布的功能, 我们为不同编程语言的项目制定了部署规范, 比如部署路径, 日志路径, 启停方式, 回调脚本等. 每一个服务要接入部署系统都需要在平台上提工单申请,工单信息大致如下:

通过上面的工单, 足够可以收集运维关心的服务信息, 这些信息都会记录到我们的运维平台上.容器集群搭建完成以后, 开发人员只需要为该项目申请容器部署即可, 构建的过程可以复用之前的, 不用做任何改动, 只需要在构建完成以后制作相应的镜像推送到内部的docker仓库中。

集群内访问/负载均衡, 如果连接该项目的服务都已经部署到了容器中, 那么该服务选择集群内访问的方式就行, 也就是采用clusterIP的方式, 如果该服务是对公网的, 或者访问该服务的客户端部署在传统VM/物理机上, 这时候就需要一个负载均衡了.这里的负载均衡是公有云提供的LoadBalancer. 开发人员提交工单, 运维人员处理完工单以后这个项目基本上就可以通过部署系统部署到容器了.运维平台通过调用K8s集群的接口来创建相应的service, deployment, HPA等. 当然这只是大概的流程, 具体实施过程中还需要做好开发人员的容器知识的培训和编写相关基础文档.

发布流程

  1. 推送代码, 代码仓库使用的是内部搭建的gitlab仓库

  2. 运维平台是以项目为中心而设计开发的, 上面说了每个项目要想接入部署系统都会提交发布申请工单,提交完以后项目和该项目的代码仓库的绑定关系就会存储到平台上,然后开发就能从平台上提交容器的发布工单, 如下图是开发者提交发布申请的表单: 3.我们的项目构建采用的是Jenkins, 跟大部分使用Jenkins的方式不太一样, 我们通过Jenkins的API来触发构建, Jenkins对于开发人员是不可见的. 我们会在Jenkins上提前创建不同编程语言的任务模板, 开发提交发布工单以后, 运维平台会通过调用Jenkins的API复制模板创建一个和该项目在运维平台上名字一致的Job, 并配置好相关的参数信息, 比如git地址, 分支/tag名称, 构建的shell等, 然后触发构建任务. 这里有2个点需要给大家说明一下, 一个就是我们之前对服务的标准化以后, 开发人员不需要编写Dockerfile, 而是采用通用的dockerfile, 替换一下相关的变量即可, 这些工作在构建的时候自动触发; 二是我们采用gitlab上的tag来作为每次部署的版本号.我们没有采用commitID是因为它可读性较差, 开发每次提交发布以后, 我们平台会用时间戳+用户ID去gitlab上创建tag, 同时这个tag也会作为该服务镜像的一部分.

构建shell样例

mvn clean;
mvn package -Dmaven.test.skip;
sed  s/service-name.jar/${JOB_NAME}/g /template/Dockerfile > Dockerfile
cp target/*.jar ${JOB_NAME}
docker build -t inner.hub.com/${JOB_NAME}:#tag# .
docker push inner.hub.com/${JOB_NAME}:#tag#
复制代码
  1. Jenkins配置了gitlab的互信密钥, 可以拉取所有项目的代码.

  2. 平台触发Jenkins构建任务以后, 就会把该次构建的容器image推送到内部的hub中.

  3. 测试的k8s集群开发和测试可以随意发布, 线上的是需要测试人员确认通过以后才可以上线的, 这里有一个点是线上环境上线的镜像是已经上线到测试环境的相同镜像.

  4. 上线到k8s, 开发人员从运维平台上点击按钮触发, 运维平台调用k8s的接口通过替换容器的image地址来实现发布/回滚.

容器的管理

运维平台上提供的容器相关的功能. 我们的运维平台是以项目为中心来设计的, 所有的其他资源都会跟项目绑定, 比如服务器, 数据库, 容器资源, 负责人等. 此外平台还提供了权限的管理, 不同的人员对于项目有不同的权限, 基本上每个开发对于项目的常规操作我们都会集成到平台上. 容器这块我们没有使用官方的dashboard, 也是基于API来集成到运维平台的.

  • 编辑配置 这个功能主要是给运维人员开放的, 方便给项目调整参数.
  • 基本信息 包括容器信息, 服务信息, HPA信息等, 方便开发查看
  • 监控信息 这些信息也是通过直接使用的prometheus和heapster的接口来获取的.
  • web终端 这里也是通过调用k8s相关的接口并写了一个websocket服务来实现的.方便开发人员紧急处理问题.

日志系统

在没有采用容器之前, 我们已经用ELK来解决日志的问题了, 我们主要有2种使用方式:

当服务的日志量不是很大的时候我们采用的是程序直接以json的形式写日志到redis的list中, 然后我们使用logstash发送到ES中, 然后从kibana上就可以查看了. 当服务的日志量较大的时候, 我们采用写文件的方式, 然后采用filebeat发送到kafka集群, 然后采用logstash发送到ES集群

采用k8s容器以后, 由于磁盘限制这块不是很完善, 我们要求所有的服务都采用输出到redis的方式或者标准输出的方式来打日志, 这样本地磁盘也就不用考虑限制了.这里有一点要说明一下:

由于所有服务都直接采用redis的方式后, redis很容易成为瓶颈, 如果redis挂了很可能会影响线上业务, 基于这一点, 我们让公司的Golang工程师帮忙开发了一个redis -> kafka的一个代理服务, 这样的话业务不用修改任何代码, 还是用redis的方式输出日志, 只不过后面其实是输出到了kafka集群了, 代理服务是无状态的, 可以无限扩容, 这样性能问题也就解决了.

监控系统

在接入容器之前我们已经采用了prometheus来监控我们的服务器和业务, k8s对prometheus的支持很完美, 所以我们这块能很快适应, 业务代码也不用怎么修改. 这里有几个点:

我们在k8s上部署了prometheus来专门监控所有业务的Pod状态, 比如Pod的HPA使用率, Pod的不可用数量等.

由于业务监控需要采集的数据巨大, 多个服务如果公用一个prometheus抓取端的话会有性能问题, 我们采用了为每一个服务启动一个prometheus抓取端的办法来解决这个问题,每个服务单独的prometheus抓取端通过配置文件中的正则匹配来控制只抓取该服务的数据.

看板采用的是grafna.服务注册到运维平台的时候也会自动通过grafna的接口创建相应的看板项目.

报警发送的话我们自己有封装的钉钉/企业微信/邮件/短信等功能的接口.

spring boot1.5 项目接入prometheus相关实例参考 github.com/hellorocky/…

服务发现

公司用到服务发现的主要是Java的spring项目, 所以采用的是Eureka, 我们之前在没有采用容器的时候就已经有了一套eureka注册中心, 由于pod的网络和虚拟机的网络是直接互通的, 服务迁移到容器中以后我们依然采用的之前的一套注册中心.

配置中心

服务迁移到容器的时候我们就要求开发在开发代码的时候生产和测试是同一个jar包, 根据启动参数的不同来选择环境, 这样的话就可以实现构建一次, 生产/测试到处运行了. 配置中心这块我们采用的是apollo, 这块也是通过调用apollo本身的API来集成到运维平台的, 做到了从平台上修改/编辑配置等.

踩过的坑 && 总结经验

initialDelaySeconds参数导致pod循环重启问题

这个参数是k8s配置服务的健康检查的时候使用的, 我们知道, k8s的健康检查有2种, 一种是存活检查, 当这个检查失败的时候, k8s会尝试重启该pod, 另一种是就绪检查,当这个检查失败的时候, k8s会把该pod从service上摘下来, 还有一个点是很重要的, 比如我在k8s上部署了一个Java项目, 这个项目启动时间大概是30秒, 那么我就需要配置上面的这个参数,pod启动的时候让健康检查的探针30秒以后再执行检查,实际场景中, 一个普通的springboot项目启动要花费40秒左右(limit为4核), 所以这个值对于Java来说至少需要配置成60才可以, 不然经常会出现新部署的pod一直重启.

使用prestop hook保证服务安全退出

在实际生产环境中使用spring框架,由于服务更新过程中,服务容器被直接终止,由于eureka server有缓存, 部分请求仍然被分发到终止的容器,虽然有重试机制, 但是高并发情况下也经常会遇到接口调用超时/失败的情况,为了减少这些错误, 可以在容器退出前主动从eureka上注销这个节点, 等待2倍左右的eureka检测时间(2*5), 这需要开发提供接口并将调用接口的脚本添加到prestop中, 参考: curl http://127.0.0.1/debug/stop/eurekaClient;sleep 10

健康检查超时时间相关配置

实际工作中遇到过一个服务不管怎么配置, 总是会莫名奇怪地出现重启的现象, 看日志也没有看出不来, 后来问题找到了, 是健康检查配置的问题, 最开始使用k8s的时候我们给每个服务配置健康检查的超时时间是1秒, 失败1次就算失败; 后来跟业务沟通了一下, 他们的服务本来就是响应慢, 好吧, 我们只好修改了一下超时时间, 调大了一点, 失败次数也改成了3次. 连续3次失败才算失败.

Java获取宿主机CPU/内存的问题

由于JVM默认情况下获取的是宿主机系统的参数,所以导致JVM获取的GC线程数和分配的heapsize不是我们想要的, 我们是通过调整JVM启动参数来解决这些问题的, 参考: java -server -XX:ParallelGCThreads=4 -Xmx2g -Xms2g -jar service.jar, 当然也可以通过其他的方式来修改.

QA

Q: prestop hook的参考地址能给个外网地址看嘛? A: 这个看官方的文档就行吧, 我这个场景里只是用了一个curl, 让开发提供一个接口就行. 具体prestop hook官方文档上有详细的举例.

Q: apollo配置中心,配置怎么落到服务里的,或者容器里? A: 我们这边大部分是Java项目, 使用的官方提供的SDK, 具体你可以看下apollo的使用文档.

Q: 日志怎么采集和展示?用什么方案? A: ELK, 采集日志主要是程序直接输出到redis, 这点有些不一样.

Q: CD的配置是怎么管理的? A: 相关的上线配置都是存在运维平台上, 服务的配置使用的apollo配置中心.

Q: ELK是部署成sidecar模式集成采集器还是日志输出到外部存储?日志怎么收集怎么清洗的? A: 上面说了, 程序直接输出到redis. 然后直接存储到了ES中.

Q: redis和mysql之类的组件也都完成了容器化吗?做的是集群吗? 怎么样的形式? A: 没有, 我们暂时推广的是无状态的接口, 有状态的暂时没有进行容器化.

Q: 关于war tomcat运行方式,如何指定不同环境的配置文件能否像 jar在启动的时候指定环境配置文件,在tomcat里的catalina.sh里指定配置文件。 A: 这个不是很清楚, 公司的服务都是springboot的, 都是jar包这种形式的了.

Q: k8s的hpa组件是原生的吗,只根据cpu内存来进行伸缩,有没有出现过什么问题 A: 是原生的, 出现过的问题是, 之前都只采用了一个纬度的扩容(CPU), 后来发现该pod经常OOM, 后来加上了内存的扩容, Java服务例外.

Q: Prometheus 数据怎么保存的,每个实例都存在本地目录吗? A: 我们有专门的Node节点来跑prometheus pod通过node label调度, 采用的本地SSD磁盘, 每个服务一个目录, 大概这个样子.

Q: 还有就是还有就是日志部分 现在redis是瓶颈吗 redis也是集群? A: 分享的时候提到了, redis是瓶颈, 后来公司golang工程师搞了一个reids--> kafka的代理服务, 采用的redis协议, 但是直接写入到了kafka, 解决了性能问题.

Q: 网络组件用的什么,容器和虚机是大二层吗? A: 容器和虚机是互通的,网络组件是用的Flannel, 这块主要是公有云提供商负责维护的, 我们其实这块接触的不多. 性能的话这块我也会答不了.

Q: 请问FileBeat是直接在容器里获取文本的吗? A: 容器中我们没有用到filebeat.

Q: pod有配request 和 limit 吗,配了的话怎么和jvm 参数打通?谢谢 A: 配置了, 这块暂时都是显性指定参数, 没有做到自动感知, 也就没有打通.

Q: 请问集群的配额限制是从什么角度做的 ,是pod层面还是namespace层面 A: 目前只配置了pod的request和limit, 基于namespace的配额暂时没有配置, 目前没有遇到相关的问题.

Q: prometeus也是k8s管理吗,配置文件他的配置文件怎么管理的 A: 这块我们写了一个简单的服务部署到了k8s的master节点上, 当一个服务接入k8s上以后, 运维平台会去掉这个服务的接口, 就会创建一个prometheus server专门抓取该服务的监控数据.通过prometheus的配置可以做到只匹配该服务, 我们这边是每个服务配置一个单独的prometheus server抓取端.




原文地址:访问原文地址
快照地址: 访问文章快照