DockOne微信分享(一五七):某互联网公司的应用容器化之路


【编者的话】本次分享分为两个部分,第一部分比较常规,介绍如何用OpenShift搭建自动化测试、开发环境。第二部分介绍了在容器使用过程中遇到的问题,以及应对方案。作者在原有的OpenShift Router仅支持7层协议的基础上,对其进行支持4层协议的扩充,联合CoreDNS,让平台的使用者不用记录灵活多变的IP地址,仅需自己定义自己熟悉的hostname(或者由平台自动生成)就访问对应资源,让使用者几乎没有感知的使用容器还是物理机或者虚拟机。

在这里跟大家分享一下容器使用方面的经验,说的不好,肯定有不对的地方,还希望大家多多批评、指正。

OpenShift简介

我们都知道在容器编排领域,有3个最著名的编排系统,它们分别是Docker Swarm,Mesos和Kubernetes。其中Docker Swarm是Docker公司自己推出的容器管理和编排系统,由于起步较晚,目前大规模的应用尚不多见。

Mesos是Apache下的开源分布式资源管理框架,它被称为是分布式系统的内核。Mesos最初是由加州大学伯克利分校的AMPLab开发的,后在Twitter得到广泛使用。

Kubernetes和Mesos同样是起源于Google的Borg,是现在应用最广,用户数和社区活跃度最高的容器编排系统。OpenShift也是出于这个考虑,最终选择Kubernetes作为底层架构。OpenShift是一个开源容器云平台,他是基于主流的容器技术Docker和Kubernetes构建的云平台,作为一个开源项目,他已经有5年的历史,其最早的定位是一个应用云平台(Platform as a Service)。在Docker时代来临之前,各个厂商和社区项目倾向构建自己的容器标准,比如Cloud Foundry的Warden、OpenShift的Gear,但是在Docker成为主流及社区的技术发展方向之后,OpenShift快速的拥抱了Docker,并推出了市场上第一个基于Docker及Kubernetes的容器PaaS解决方案。当然很多人会有疑问,OpenShift与Docker及Kubernetes的关系究竟是什么

OpenShift是基于容器技术构建的一个云平台。这里所指的容器技术即包含Docker及Kubernetes。如下图所示:
93045234accd1ea8fcaa59f8776f326f.png

OpenShift底层以Docker作为容器引擎驱动,以Kubernetes作为容器编排引擎组件。OpenShift提供了开发语言、中间件、自动化流程工具及界面等元素,提供了一套完整的基于容器的应用云平台。OpenShift有三个版本,分别是企业版,社区版和在线版,我们这里使用社区版(origin版)用做我们企业内部的部署

部署介绍

我们的架构比较典型,前端有2个负载均衡器(HAProxy),两个负载均衡通过Heartbeat共享一个VIP,后面连接3个master node,每个master node上分别运行了一个etcd,后面连接了N个slave node,因为集群中所有的状态都会持久化到etcd中,所以api server基本上是无状态运行,当有节点需要和master node打交道时,先访问这个VIP,然后连接到vip后面的一个haproxy上,haproxy再选择其中一个master node上的api server进行与etcd通信,下面是我的HAProxy的配置文件haproxy.conf
backend atomic-openshift-api
balance source
mode tcp
server      master0 10.10.xx.xx:8443 check
server      master1 10.10.xx.xx:8443 check
server      master2 10.10.xx.xx:8443 check

同时,其实我们如果集群里面有其他的高可用的需求,比如我们使用了Harbor做为私有镜像仓库,三个镜像仓库配置了Replication规则,我们通过VIP推送一个镜像到3个中的任意一个仓库,其他两个仓库也会存在我们的同样镜像,从而实现高可用。下面是Haproxy的配:
harpoxy.conf
backend harbor-proxy
balance source
mode tcp
server      harbor1 10.10.xx.xx:80 check
server      harbor2 10.10.xx.xx:80 check
server      harbor3 10.10.xx.xx:80 check

运行流程

第一个是每天定时的作业,这个比较简单,我们快速过一下:首先在Jenkins里做定时任务,时间定为0:30,从代码下载代码,在编译之前进行代码风格检查(snoar)和执行单元测试(junit),然后进行编译,把编译出的二进制包和事先写好的Dockerfile进行docker build,然后把输出的docker image push到Harbor的镜像仓库,同时这个编译的jenkins job会触发,测试部署job,测试部署job执行时会触发OpenShift里的test project,我们把PullPolicy设置成alway是,每次执行从新部署,都会触发新的镜像的拉去,从而达到从新部署的目的,测试项目分为两个阶段,自动化测试和压力测试,自动化测试采用selenume和robot结合的方式,在测试结束之后生成测试报告。压力测试使用jmeter。结束之后也会以邮件的方式发送给订阅者。如果所有测试通过之后,测试job会先打给这个image打一个tag,然后push到Harbor的ready项目中。同时发送通知邮件。

待第二天测试人员需要测试的时候,它们会通过我们的release manager去拉去ready项目中最新的image,然后部署到它们自己的project里,进行特定功能的手工测试。整体流程大概如此。

第二个是针对开发测试环境的,由于资源有限,我们的开发人员也用容器的方式部署每个人的开发环境,这里OpenShift/Kubernetes多租户的优势就体现出来了。在我们的容器平台上,每一个开发人员对应一个账号,每一个账号就是我们OpenShift里的一个project,由于每一个project都可以做资源限制,所以只要给大家按照需求分配好固定份额,就可以做到资源的共享和互不干涉。举个例子,比如说,我们有一个项目,它分为前端和后端。前端人员开发的时候,他只关心前端代码,后端代码基本仅仅是调用而不做修改,那么这个时候我们就可以把前端打成一个docker image,后端打成一个docker image(这里是举个例子,实际情况可能比较复杂,可能前后端都不止一个image),然后在前端开发人员写完前端代码后,把自己的代码通过NFS共享给Release Manager,(NFS挂在可以是实现挂载好的,Windows、Linux、MacOS均可以,代码就可以直接存在上面),然后点击Relea se Manager上面的前端发布按钮,就会触发Jenkins中的一个front-end job,它会完成代码编译,打包和镜像推送,最后在这个开发人员对应的project里从新部署前端代码的整个过程,接着这个开发人员就可以通过url(比如http://test/frontend-dev1)去访问它刚刚部署的页面。如果是后端人员也会有同样的流程,具体就不在赘述了。具体说明一点,由于OpenShift集成了Router功能,也就是类似于Kubernetes里的Ingress,但是它是用Haproxy实现的,并且能够在Yaml文件中对Router进行配置,刚才引用的url我们可以配置成http://{集群名}/{项目名},所有整个集群都是用7层协议通过Router对外提供访问。

生产和预发布环境,这个环境的配置除了要求高可用之外,就是环境的硬件配置,一般意义上来将,预发布的环境因该不高于正式生产环境。当决定对测试通过的镜像版本进行上线时,首先会用docker tag把它换成tag换成pre-release,经过压力测试和手工最后的verify就可以发布到正式环境中了。

调试问题的解决

下面我们着重讲一下在实际部署和使用容器过程中,遇到了哪些问题,以及是如何去解决:

1、有一个问题也许很多刚刚使用容器的公司都会遇到,就是开发人员喜欢把容器当成虚拟机用,在遇到程序bug的时候,很多开发者都喜欢SSH到容器里,亲自看看log,或者是尝试替换一下它们程序的debug版(这里的所谓debug版,就是开发人员在代码里加入一些调试信息或者print一下log),然后重新启动应用。这里我们不推荐给容器内部安装SSHD,因为首先容器的IP是临时分配的,我们无法确定的告诉开发者它这一次的IP地址是多少,即使告诉了他也不一定能够访问的到(我们的容器系统网络层和外界不是一个网段),那么如何解决这个问题呢?我们开始也是尽量说服开发者,学会用log去debug,因为我们前面已经把log通过ES进行了收集,用Kabina可以去查看,但是没有办法,有些开发人员还是习惯自己去cat或者vim打开日志文件。所以这里我们就用到了OpenShift里提供的一个oc子命令oc get pod和oc exec,前者用来得到当前用户所在的项目中的Pod列表,后在类似docker exec命令可以直接跳进容器里(Kubernetes中也提供类似的命令),当开发人员需要把里面的日志文件拷贝出来,后者是拷贝一个debug版本的程序到容器里去运行时,可以用oc cp(同样这个在Kubernetes里也有类似的命令)。虽然这几个命令据我观察有一些bug,比如拷贝的目标目录不太准确,而且对容器里的tar命令有一些依赖。但这都是小问题,不影响使用,如果觉得几条命令结合起来使用有些麻烦,可以自己用Python脚本进行一个简单的封装,这样当开发人员使用的时候会更加简单。

2、每一个系统几乎都或多或少的使用了一些第三方工具,比如MySQL、MyCAT、Redis、ZooKeeper,这些组件我们都把它们进行了容器化,以便实现资源的整合和方便部署。那么这就引发了另一个问题,开发人员在碰到程序bug的时候,往往需要直接去连接这些第三方组件,去改修改和查看里面的信息。比如他需要查看Redis的键值是否存在,查看数据是否写入到了数据库里。这个时候,由于所有组件在容器中,你不知道它的准确IP,你可能很容易想到用OpenShift提供的router功能去像刚才的url那样提供外界的访问,但是由于这些中间件是4层的协议,而现有OpenShift的Router功能仅仅支持7层协议,所以我们为了解决这个问题就必须实现OpenShift的4层代理功能。通过修改Openshift的源代码haproxy-templte.conf和router部分的相关代码,然后通过yaml route的annotation段,定义一个规则,把对应的端口传进router的配置文件,让后端的第三方应用程序通过router节点对应的端口(Haproxy里的mode tcp)从而实现router代理4层协议的目的,但是这会导致另一个问题,因为router的每一个端口只能映射给一个后台应用,比如3306端口,只能映射给一个MySQL,如果有两个开发人员都要调试MySQL,那第二个开发者的MySQL的映射端口肯定就得用除了3306以外的端口(比如3307、第三个人3308等)那么就会产生一个问题router的映射端口如何告诉开发人员呢?这个问题有两个解决办法,第一个是通过一个Web UI,去显示的告诉开发人员他所有的资源对应的router节点的端口号,但是这有一个不方便的地方,如果的资源对应的Pod被重置了,那么他的端口号也就会被改变,即使端口号不改变,记起来也比较麻烦(大型的项目可能要用到5、6个中间件产品,每一个开发人员都要记自己的那套环境的端口号,还要学会如何用自己的工具去修改默认的端口号显然给开发者添加了许多不必要的麻烦。那么基于此问题的考虑,我们使用了第二种方法,即我们内部实现了一个DNS,开发者每个人的资源的IP都可以用DNS查到,比如developer1,他有Redis、MySQL、MyCAT、ZooKeeper、PostgreSQL等资源,那他的这些资源对应的DNS域名为:developer1.redis developer1.mysql developer1.mycat developer1.zookeeper等,但是由于DNS只能返回IP地址,无法返回端口号,我们还是得不到router节点对应资源的端口号。为了解决这个问题,我们可以用Haproxy加别名的方式,比如运行命令ifconfig eth0:1 10.10.xx.xx意思就是给eth0的NIC,加上了一个虚拟的IP10.10.xx.xx,那么此时,这个网卡eth0就有两2个IP,并且此时都能ping通。(当然实际实现的时候,我们不可能用是ifconfig命令完成这个网卡别名的方式,那样太low,也太不可靠了,我查看了ifconfig的源代码,把其中设置IP地址的代码集成到了我们修改过的OpenShift里)。然后router的haproxy.conf做bind的时候就需要指定这个IP,端口号还用原来这个应用默认的端口号如下图所示,这样开发人员不用每次都记住不同的端口号,仅仅配置一个DNS,不管环境发生了什么样的改变,都可以用默认端口和hostname去连接自己的资源进行调试。

3、配置Docker容器的时候,默认使用的是DeviceMapper方式,然而这种方式有众多的限制,可以参考https://docs.openshift.org/3.6 ... orage中的详细配置说明,在生产环境中我们采用的是第2种方式。

Q&A

Q:所有开发人员都是用一套OpenShift集群测试吗?CI/CD也是同一套环境吗?

A:我们是按业务分的,原则上,一套业务线(一个业务部门)用一套系统,这样成本上也好分摊。
Q:OpenShift也是用Go编写的?

A:是的,OpenShift用的是源码级别的和Kubernetes的集成,不是通过client-go或者REST API的方式,所以我们可以看到,Kubernetes发型的版本总是比OpenShift的快1 到2个版本。
Q:对于OpenShift比较适合多大规模的团队?

A:这个怎么说呢,其实引入DevOps或者CI/CD的流程就是为了给企业减少人员成本,让有些能够自动化的东西通过计算机运行起来。所以因该是人员越少越好,但是人员如果少,就要求每个人的技术能里比较强,开源的东西往往用起来不难,但是真到出了问题的时候就需要看代码去解决了。所以如果人少的话,可能每个人就要求懂得就比较多一些。
Q:router本身是否具备HAProxy?

A:OpenShift的Router就是用HAProxy实现的,当然我现在用的是3.6.1的版本,我不知道以后会不会支持Nginx或者其他别的LB,因为我看到代码里已经有关于Nginx的相关配置了,但是没有激活。OpenShift使用HAProxy的大致流程就是通过一个Yaml或者jason文件,配置一条route信息,然后通过api-server持久化到etcd中,router的代码启动一个goroutine,去通过api-server watch etcd,然后根据配置信息和环境变量,通过haproxy-template模版,去生成 haproxy.conf,然后去动态reload。
Q:OpenShift的project和Kubernetes的namespace是一对一的关系么?project可以设置资源配额么?怎么设的?

A:是一对一关系,当然有一些namespace 是有一些特殊意义的,不建议在里面跑应用。project可以设置资源配额,具体怎么设置就比较复杂了,建议参考一下官方文档,简单的说就是可以根据CPU内存做资源的限定,这个和Kubernetes是一样的。
Q:OpenShift中原生性能指标监控服务的Pod总挂有没有相应的解决办法?

A:解决Pod总挂的问题就得具体问题具体分析了,我记得它做性能监控的那个Pod比较吃资源,其实可以对他进行一下限定,比如:oc env rc hawkular-cassandra-1 MAX_HEAP_SIZE=1024M -n openshift-infra。
Q:OpenShift中的router默认情况下是跑在Pod里的,那么当service特别多,route规则也特别多的时候,如何解决router服务的性能问题的?

A:这是一个好问题,但其实我觉得这个和HAProxy有很大的关系,跟在不在Pod中关系不大,因为router这个容器其实用的是主机网络,那么这个问题其实就转化成了如何提升HAProxy的性能,这种情况其实有很多可以参考的方案,比如前面在加一层LVS负载,或者用DNS做域名解析的时候进行一定的负载功能。
以上内容根据2018年1月9日晚微信群分享内容整理。 分享人王晓轩,某传统互联网公司基础技术部技术经理,关注容器技术及DevOps PaaS领域,2014年关注容器技术,深度接触过Cloud Foundry等开源PaaS产品, 现主要研究OpenShift及周边技术。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesa,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

0 个评论

要回复文章请先登录注册