DockOne微信分享(四十七):Docker在乐视的实践之路


【编者的话】现在Docker 技术越来越火热,越来越多的公司开始使用Docker技术部署应用。这次分享主要是讲如何充分利用Docker技术实现代码到线上环境的部署,以及在使用Docker中遇到的问题和困扰。

背景

我们是在14年开始研究并使用Docker,最开始主要想利用Docker解决组件化和服务的上线问题。现在慢慢演化成一个提供服务从代码环境到线上部署的一套解决方案。现在这套系统在内部我们称之为Harbor。

实战之路

1.0 版本

我们第一版采用docker-registry搭建了私有的镜像仓库,并使用Python开发的命令行模式,对Docker主机和容器进行管理,使用App->Service->Container 和主机集群的概念。一个App下对应多个Service,多个Service 组成一个App, 一个Servier对应多个容器,每个Server 都有对应的配置。

配置信息包括:内存、名称、业务描述、镜像、容器数量、export端口、环境变量等等。 提供基本的部署、删除容器、查看容器、添加主机集群等功能。可以将App部署在不同的主机集群中。

1.0 的架构比较简单,我们在每个Docker主机上安装上一个Python写的agent代理,专门向etcd中注册容器和主机的信息,提供服务发现功能。

命令行工具在部署时候,根据etcd中的信息,同时根据每个App的配置采用不同的策略,通过调用Docker的API将容器部署在不同的物理机或者虚拟机上。

部署策略保证同一个App下的多个容器尽量分散在不同的主机上,防止主机宕机,造成服务不可用。同时根据主机剩余可分配容器内存的概念,保证一个主机不会过量部署容器。

App-Service-Container逻辑:
1.png

1.0结构如下:
2.png

1.0 版本基本上能解决应用容器的快速部署,但是有很多缺陷:
  1. 用户或者开发人员需要直接写Dockerfile,自己通过docker build命令制作Docker镜像。 需要培训DockerFile语法,学习成本比较高。
  2. 用户的代码中加入了跟Docker有关的逻辑,比如Dockerfile、Volume。污染了用户或者开发人员的代码
  3. 镜像名称和版本定义没有统一的规范,而且存在镜像名称一样的风险。
  4. 没有可视化的操作界面,App和容器数量到达一定规模,出现问题就不好排查。
  5. 不支持应用服务的灰度升级
  6. Server升级时,需要先修改Server的配置信息,比如镜像版本、环境变量、export端口等等,一旦修改失败,想回退到上一版本,必须提前手工备份配置文件。


1.5 版本

我们推广一段时间后,在1.0 基础上推出 1.5 版本:

1.5 版本在1.0 结构基础上,保留了App-Service-Container概念,同时我们主要做了以下改善:
  1. 推荐用户或者开发人员,代码管理使用Git。
  2. 使用Jenkins ,将用户和开发人员的代码与Jenkins做关联,用户一旦在对应分支上提交代码或者打tag 会触发Jenkins相应的job自动构建镜像。实现了代码到镜像的自动构建。
  3. 使用Tornado搭建了一套可视化的Web服务,同时提供REST API接口。Web界面集成了私有镜像仓库的镜像列表查询,和单个镜像版本查看的功能。 利用Web 界面上,用户和开发人员可以很方便的创建一个App,并关联一个镜像。
  4. 提供统一的基础镜像,只需要用户提供一个Dockerfile和start.sh的脚本,前者用来定义运行环境,后者用来定义容器启动后所执行的操作,保证容器启动,服务也启动。
  5. 网络方面使用macvlan。
  6. 用户资源隔离,每个用户只能看到自己的容器和App、Service资源。


1.5 版本在一定程度上解决了代码到线上环境部署的问题,用户可以很直观的看到自己的App和容器资源。以及每个容器的运行状况。但是我们在内部推广的过程中,发现存在了很多不尽人意的地方:
  1. 必须向用户或者开发人员培训Docker、Jenkins的使用、Harbor的使用,每一个系统或者技术的推广会耗费很大的精力去说明。
  2. 依然存在镜像名称冲突的风险。
  3. 依然需要用户写Dockerfile,将Docker的逻辑放在代码环境中,在一定程度上污染了代码。
  4. Jenkins个人感觉比较重,在查看镜像构建过程时,需要登录到Jenkins系统中去查看。
  5. App、Service没有版本概念,无法做到基于版本的快速升级和回滚。
  6. 不能提供很好的进入容器的方法,用户只能在知道IP地址和密码后,通过SSH方式连接。
  7. macvlan需要专门给物理机划分VLAN,需要专门的网络环境。
  8. 没有很好的应用资源隔离和共享机制,用户A创建的App或者容器资源,只能用户A看到,其他的用户无法查看,如果这时用户A离职,则需要人工的去转移资源。


2.0 版本—构建私有云环境

2.0 在上一版本的基础上提出新的一些思路,整体的思路就是按照公有云的思想去构建私有云,并负责应用的开发到上线的整个生命周期:
  1. 用户资源隔离和成员管理,用户A的App应用或者主机集群资源可以通过添加成员的方式,来协助管理用户A的资源。也可以通过转移owner的方式来转移资源的使用权。体现一种团队协作的概念。
  2. 去掉之前的Service 概念,改为AppGroup -> App ->Version->Container 的概念,一个Group 下包含多个App 应用,每个应用有多个版本 Version,每个Version可以包含多个Container。每个应用版本包含了:镜像版本、描述、环境变量、export 端口、Volume映射和其他高级配置(SSH、web ssh密码)。每个容器都是根据版本的信息去创建。所以能很方便的实现容器的升级和回滚。
  3. 在原有docker-registry做了修改,加上权限的概念,防止创建相同的镜像名。并使用Namespace,一个Namespace下可以有多个镜像。在逻辑上可以表明这一组镜像的某种关联性。比如一个部门可以创建一个Namespace,这个部门的镜像都放在这个Namespace下。
  4. 公有集群和私有集群的概念,集群我们指的是一组Docker 主机,物理机和虚拟机都可。公有集群对所有的用户开放,用户和开发人员可以将自己的App应用部署在这些公有的集群上。 用户和开发人员可以随便创建自己的私有集群,并添加Docker主机。应用可以部署在自己的集群中,实现私有集群的自我管理。公有集群网络方面可以使用macvlan和NAT模式。而私有集群,我们现在只给用户提供NAT方式,主要是方便开发人员和业务部门的接入。
  5. 提供Docker环境一键部署的功能。业务部门分配下的主机资源可以很方便的部署Docker以及相关组件环境。

  6. 抛弃Jenkins,自己实现镜像构建。整合到一个界面中。 抛弃繁琐的Jenkins配置,用户和开发人员只需要在页面上选择所要创建镜像的基础镜像,输入Git地址、分支名、镜像名以及在界面上书写镜像构建脚本和镜像启动脚本即可。 同时在Git库中,设定好系统给定的web hook url。当下一次提交代码后,会自动触发构建任务,并在页面中展示实时的构建过程。镜像版本强制使用:git tag+commit号+配置变更数目来命名,这一能方便用户和开发人员定位镜像版本和Git的Commit做关联。下图展示的是我们镜像相关的操作:
    构建日志:
    3.png

    镜像版本列表:
    4.png

    如何创建镜像:
    1.jpg

    镜像构建脚本:
    2.jpg

    服务启动脚本:
    3.jpg

  7. 镜像分为基础镜像和业务镜像。基础镜像即包括我们提供的最基础的镜像(我们只提供了几个基本的基础镜像),也包括业务部门在我们提供基础镜像基础上修改的自己业务的基础镜像。 业务部门可以创建自己的基础镜像,下面的业务可以继承这个基础镜像。
    如图:
    8.png
  8. 支持应用的灰度上线,开发人员或者业务部门在创建完新的App版本后,可以选择特定的容器进行升级,最终测试通过后,即可全部升级。
    9.png
  9. 支持容器通过web ssh和SSH两种方式访问。若不想通过SSH访问容器资源,可以直接通过Web的方式,访问容器内部资源,实现bug调试以及问题查询。
  10. 自动接入负载均衡, 智能负载均衡系统,我们使用Nginx 实现,使用go和etcd实现Nginx 配置自动下发。
  11. 支持事件操作记录,包括集群,镜像,应用的操作事件,方便以后的问题追踪,下图是应用的事件记录:
    11.png
  12. 支持快速查看应用部署的拓扑结构,如下图:
    12.png

    第一层表示的是应用名,第二层表示的是应用所部署到的集群名称,第三层表示的应用的版本,第四层表示的是容器名称和运行状态。用户可以在这里直接点击特定集群,创建特定版本的容器,具反馈,这是最受业务部门喜爱的功能之一。


2.0版本在架构上比1.5版本上做了很大的改动,首先我们使用分层设计。
  1. 基础设施层面Docker主机或者虚拟机,并安装用go写的一个服务发现代理,go-registerd。
  2. 数据层包括etcd和数据库,消息队列以及docker-registry镜像仓库。 服务发现代理专门向etcd中注册容器和主机信息。
  3. 执行层主要包括异步任务处理:harbor-compute,获取消息队列中的任务,执行比如容器的创建、删除、电源操作、镜像构建、上传等任务。可以处理大量的容器和镜像操作请求,镜像构建我们提供专门的服务器去提供镜像构建的操作,镜像构建完后,自动push到私有镜像仓库中。
  4. 调度层marine,提供API接口,主要负责每个应用的容器部署调度,镜像构建的调度,以及集群,主机管理等等。当一个App需要创建容器时,根据所选的集群资源使用情况,以及容器数量等其他的配置进行资源决策,决定每个容器应该创建在哪个主机上,生成对应的任务,投放到消息队列中。
  5. 用户层,分为面向用户的开放系统:polaris和面向管理员的资源管理系统:ursa。整体架构如下:
    13.png

    14.png


2.0版本从15年11月份上线以来,已经陆续创建了上百个应用,200多个镜像,几百台主机,每天构建镜像400多次,平均每天容器销毁和创建100余次。镜像容量在100G左右。

Harbor基本上在乐视云计算有限公司内部成为了一个开放的容器平台,主机的添加,镜像的构建,应用容器的扩容、升级、回滚,现在已经完全实现开发人员和业务部门的自主管理。

开发人员和业务部门的学习成本由原来需要学习Docker、Jenkins、Harbor变为单纯学习如何使用Harbor即可,而且省去了大量的旧有上线部署和配置手册,只需要学习如何升级容器即可。

下一步的任务

随着社区容器编排系统越完善,我们也正研究Kubernetes和Mesos,也正充分借鉴开源社区的一些设计思路和理念。

Q&A

Q:想请问一下这些Docker是部署在物理机上还是虚拟机上?在扩容的时候除了增加内存,能增加CPU吗?

A:物理机和虚拟机都有,我们提供一键安装Docker和其他组件环境的脚本,业务部门单纯执行脚本即可。在扩容方面我们现在只做了内存扩容,CPU是共享的。
Q:通过镜像的构建脚本是怎么生成镜像的?在基础镜像上执行相关脚本么?一些端口存储卷环境变量这些镜像中的信息是怎么解决的?

A:我们对Dockerfile进行了封装,业务和开发人员不需要关心Dockerfile语法,直接写一个镜像构建脚本,最后根据一定的规则由Harbor生成Dockerfile,之后调用docker build去生成镜像。在这个过程中, 镜像的名称,版本都已经根据规则生成
Q:资源共享的方式,不能是一个部门或者小组增加一个公用账号,把需要分享的资源,push到公用账号吗?

A:我们没有使用公共账号的方式,我们使用的是镜像仓库的方式,镜像仓库可以有团队成员,达到的效果是一样的。
Q:抛弃Jenkins那你们多语言怎么办 Java编译和Golang编译这些?

A:我们不需要关心什么语言,我们现在提供了一个Java公用的基础镜像,编译什么的都可以在构建时候完成。 如果需要其他语言,比如go、Python,可以有业务部门自己创建自己的基础镜像。
Q:Jenkins配置jdk和maven,是要在容器里自己安装吗?

A:在构建时候,这些环境可以在业务部门基础镜像里,提前安装好。
Q:在构建时候,这些环境可以提前安装好?

A:应用里都有自己的版本概念,每个应用版本里有:镜像版本,环境变量、 export、Volmue等信息,所以在回退或者升级时候,最终的表现形式就是杀掉旧容器,根据版本的参数创建新容器。
Q:Harbor和Jenkins比起来你们是不是基于后者的实现思路开发的精简版,解决问题的方法和思路变了吗?

A:原因有多个,我们希望Harbor能从代码到构建到部署一气呵成,如果用Jenkins,会给用户已断片的感觉,业务部门一旦业务多的话,有可能会分不清哪个Git对应哪个Jenkins配置。学习成本也比较高。
Q:你们这样,开发人员的本地环境是不是不需要使用容器了?

A:本地环境一般不需要容器,但是如果开发人员想使用容器玩玩,可以通过我们提供的基础镜像创建一个容器即可。
Q:最近在学习Magnum和Kubernetes。有一些疑问比如minionA/B上分别有serviceA的podA/B。那么访问minionA的kube-proxy是不是也会被导流到minionB上的podB上?service的重要部件proxy就是起到了个负载均衡和反向代理的作用。这个角色是不是直接可以用haproxy/nginx这样的软件代替呢?

A:我们也在研究Kubernetes,不敢做过多的评价,据说proxy 这块性能有问题。 刚开始没用Kubernetes,是因为之前他还不是很稳定,变动也比较快。 负载均衡这块是我们自己写的,整个乐视网现在300多个域名都跑在上面。
Q:请问构建一次平均要多长时间?

A:现在Java、Dubbo、Python、go的多, 一般2分钟,而且有的镜像用户开启了自动构建后,在他们没意识的过程中,都已经构建完成。 到时候升级时候,选择对应的镜像版本即可。
Q:使用了harbor之后,Dockerfile是不是不用写了?

A:是的,用户和业务部门只需要关心基本的容器概念(export 和 Volume)就行, 不需要写Dockerfile。
Q:App的每一次提交都是一个version吗,是不是每次构建完测试完成,就可以发布了?

A:App 没有提交的概念,您说的应该是镜像,我们设计的是一个镜像对应一个Git仓库以及分支。当有push或者tag操作后,会自动触发构建,构建的行为是根据用户写的镜像构建shell脚本来决定的。 一般我们建议业务部门做出的镜像跟测试环境和生成环境没关系。 镜像就是镜像,只有应用有测试环境和生产环境。
Q:提到”用公有云的思想去构建私有云“,请问您在构建公有云和私有云时,在技术上有什么区别?可以谈谈您的看法吗?

A:我们主要是把我们内部用户也看成外部用户去对待,在产品设计上会考虑这一点。这也是我们一直摸索的方向。
Q:您有物理机和虚机,请问根据什么场景选择运行在物理机还是虚机上?是性能的选择吗?

A:这完全看业务方的需求,Harbor本身不关心。 它允许业务自己创建私有集群,把自己的虚拟机或者物理主机添加进去。
===========================
以上内容根据2016年3月3日晚微信群分享内容整理。分享人张杰,邮箱:zhangjie0619@yeah.net,QQ:695160664,乐视云计算有限公司高级研发工程师,容器云负责人,4年技术团队管理经验, 对新技术有着强烈的好奇心。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesz,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

5 个评论

请教 harbor 是独立开发的吗?
是的
能否详细分享一下harbor 呢?架构?原理?实现?……
harbor现在不新开发了, 你可以看看我们新的LeEngine :http://dockone.io/article/2077?notification_id=17873&item_id=1869
现在都转k8s了啊~~~

要回复文章请先登录注册