Harbor Registry是VMware公司的Docker镜像管理产品。相较于其他镜像仓库,Harbor提供身份管理功能,安全性更高,支持单个主机上的多个registry,这些功能正是很多企业用户需要的。
在Rancher容器管理平台之上,VMware Harbor可以被添加为Rancher应用商店(Catalog)中的一个条目。本文将展示如何将Harbor在线安装程序docker化,然后rancher化,从而在Docker主机的分布式集群上安装Harbor。
本文作者MASSIMO是VMware云原生应用程序事业部的云架构师。
为了进一步了解Docker容器和它们的生态系统,我在过去几个月里一直在关注Rancher(开源容器管理平台)。
我最感兴趣的是Rancher的可扩展的应用商店(Catalog)和基础设施服务: Library为官方应用商店目录(由Rancher维护和构建),还有一个叫做“社区应用商店”(由Rancher维护,但由Rancher社区建立和支持),此外,用户还可以添加私有应用商店目录(你可以添加自己的私有目录条目并且自己进行维护)。
虽然Rancher支持用户连接Cloud Registry,但是在社区目录中只有一个 Registry(Convoy Registry)可以进行部署和管理。这会是添加VMware Harbor为私有目录项选项的好机会,如下图所示:
你应该考虑所有你了解到的情况,从字面上说,在当前的形态下,能否把一个想法照搬到生产目的中去。
致谢
在我们深入讨论这个问题之前,我想要感谢Rancher的Raul Sanchez(耐心地)为我答疑解惑(并帮我修复了一些错误的yaml)。如果没有他的帮助,我想这篇blog会短很多。当然了,因为大家应该都会这样吧,所以我还要感谢我的妻子和我的狗(虽然我没有狗)。
困难升级
首先,我需要先完成一定数量的任务,以完成初始目标。一般来说,一个任务都是依赖于另一个任务的。
如果你想要创建Rancher应用商店条目,就要从应用程序定义文件(使用默认的Cattle调度程序时的标准Docker Compose文件)和Rancher
Compose文件上实例化你的应用程序。shell脚本或类似的东西并不能成为Rancher应用商店条目的一部分。
你是不是在研究如何(通过文档化的“在线安装程序”)在Docker主机上安装Harbor? 它与Rancher应用商店模型并不真正兼容。你可以参考下面的步骤:
安装Harbor时,必须先下载Harbor在线tar.gz安装程序文件,并在harbor.cfg文件中设置你的配置设置,然后运行”准备”脚本。这个脚本会输入harbor.cfg文件,然后创建配置文件和环境变量文件。最后,运行Docker Compose文件以传递配置文件和环境变量文件作为Docker Compose的卷和指令(要注意一些过程是发生在主安装脚本下的,并且是在屏幕下发生的)。这才是标准的在线安装程序。实际上,Docker Compose文件抓取了Docker Hub的Docker镜像,而且根据配置输入实例化了Harbor。
最后,将开始的简单”PoC”项目分成了三个”子项目”:
- 将Harbor在线安装程序docker化,这样“准备”过程就能作为Docker Compose的一部分,并将输入的参数作为变量传递到Docker Compose中去(而不是手动编辑harbor.cfg文件,然后执行整个准备circus)。
- 将已经Docker化Harbor在线安装程序Rancher化,并且创建一个Rancher私有应用商店以模拟典型的单主机Harbor设置。
- 作为奖励:将docker化的Harbor在线安装程序rancher化,并创建一个Rancher私有应用商店的应用模版,让我们可以在Docker主机的分布式集群上安装Harbor。
要注意的是,我需要创建一个docker化的Harbor在线安装程序来匹配Rancher的目录模型,而且当你无法采用手动和交互式的方式、只能自动stand up Harbor时,你也可以用这个在线安装程序(Rancher是这些用户实例之一)。
在接下来的几节中,我会详细介绍为了实施这些子项目我所做的一些工作。
子项目1: 将Harbor 在线安装程序容器化
在我写这篇文章的时候,Harbor 0.5.0已经可以使用OVA或通过安装程序安装。安装程序可以在线(镜像从Docker Hub动态提取)或离线(镜像是安装程序的一部分并会在本地加载)。
我们接下来要关注的是在线安装程序。
正如我们已经提到的,一旦你下载了在线安装程序,就要调整在安装包模板中附带的harbor.cfg文件的参数以“准备”你的Harbor安装。
然后将生成的配置集输入到Docker Compose文件中(通过映射为“卷”的本地目录和通过“env_file”指令)。
如果不通过“准备”过程,直接将Harbor设置参数传递给Docker Compose文件,这样做是不是会更容易或更好?
进入 Harbor-setupwrapper。
Harbor-setupwrapper是一个包含新docker镜像的Harbor安装包,并(或多或少)在docker容器中实现“准备”进程。而Harbor配置参数作为环境变量输入到容器中。最后,在容器中运行一个脚本,启动准备例程(这是所有容器本身就包含的),当然这一步也是很重要的。
这个镜像的Dockerfile和启动准备例程的脚本都是值得研究的问题。
其实,什么是harbor-setupwrapper.sh和什么是以install.sh为标准的Harbor在线安装程序,这两个问题是非常相似的。
我们现在有一个新的Docker Compose文件,这个文件很大程度上是建立在原始Docker Compose文件的基础上的。此外,这个原始Docker Compose文件是官方在线安装程序附带的。现在,你可以通过”组合”这个新的Docker Compose文件传递你在harbor.cfg文件中调整过的参数了。
如下图所示:
一定要注意这只是一个PoC!
- 我只测试了HARBORHOSTNAME和HARBOR ADMIN_PASSWORD变量。其他变量应该是可以运行的,但我并没有进行测试
- 肯定会有特殊情况发生。例如,如果你选择使用安全连接(https),而我还没有找到创建证书的方法。这个时候就需要实现harbor-setupwrapper.sh中的附加逻辑(提示:启用https可能会发生一些奇怪的事情)
- 采用原始的在线安装程序就意味着要在单个Docker主机上运行。我会在同样的模型和相同的前提条件下,实现这个新的安装机制
- 由于以上原因,我没有试过在分布式Swarm集群上部署这个compose文件。另外,虽然说“legacy Swarm”已转换成了“Swarm模式”,但Docker Compose和后者似乎并不兼容,而我又不想花太多的时间在前者上,于是我选择不在Swarm环境中测试它
- 也许会有更多的警告,只是我没有想过(但肯定是可能存在的!)
将wrapper(由harbor-setupwrapper.sh脚本生成)中的配置文件提供给应用程序容器并不难。我已经实现了“volumes_from”指令,所以应用程序容器可以直接从wrapper容器中获得相关的配置文件。
找出将ENVIRONMENT变量(在wrapper容器上的各种文件)传递到应用程序容器上的方法具有一定的难度。而且我无法在compose中运行”env_file”指令,因为指令指向的是运行compose的系统中可见的文件(在我使用时,这些文件在wrapper容器中)。长话短说,我改变了应用程序容器的入口,指向了一个脚本,首先要加载这些环境变量,然后启动原始脚本或原始入口的命令。如果你愿意的话,你可以检查harbor-setupwrapper GitHub repo中所有的entrypoint * .sh文件。
如果你打算按照这个方法做,并且用这个新的机制设置Harbor的话,那你就需要克隆harbor-setupwrapper repo和”up”你在harbor-setupwrapper目录中发现的Docker Compose文件。但是,在启动它之前,先要导出HARBORHOSTNAM和HARBOR_ADMIN_PASSWORD变量。这相当于在原始安装程序中调整harbor.cfg文件。如果你忘记导出这些变量,Docker Compose会显示:
至少要对HARBORHOSTNAME变量进行设置,把HARBORHOSTNAME变量设置为将要安装它的主机的IP地址或FQDN(否则设置将不起作用,我会在后面解释原因)。如果你没有对HARBOR_ADMIN_PASSWORD变量进行设置的话,那你就需要用默认的Harbor密码(Harbor12345)。
你要做的是:
注:如果你打算在同一个主机上反复实施Harbor实例,并打算从头开始的话,那么一定要删除主机上的/ data目录(因为它会保存实例状态,如果新实例找到了这个目录,那么它将接收之前的实例状态)。
子项目2: 创建单主机部署的Rancher 应用商店条目
我们可以通过“compose up”来docker化Harbor安装程序。现在我们可以把注意力放在第二个子项目了。换句话说就是,创建Rancher应用商店条目的结构。
我觉得这应该是比较容易的。毕竟,我们之前讨论过关于重新使用新docker-compose.yml文件的问题。
我了解到比较困难的都是细节的问题,特别是在容器的文章里,为了“修复”一个特定问题而进行的调整通常意味着在其他地方打开一些worms。
我以后会尽可能写一些你们需要的或是你们想要了解的东西,以便于详细了解这个安装包,我把我的经历分享给你们,是希望可以在其他情况下帮到你们(我会把我的发现记录下来,不然我恐怕会在2周后忘记)。
首先,在Rancher中,在sidekick中你只能做到“volume_from”。最开始的时候,我是将“io.rancher.sidekicks:harbor-setupwrapper”添加到compose中的每个容器去。然而,我突然发现,这会为每个容器创建一个harbor-setupwrapper以辅助容器,这是一个sidekick。
虽然看起来都已经准备就绪,但我最后发现在一个单一的Harbor部署下运行多个脚本的实例可能会导致各种配置不一致(例如用不可信的密钥签名的令牌等)。
我需要改变策略,变成只有一个harbor-setupwrapper容器的实例(在一个过程中将会一致地生成所有的配置文件),我已经在主容器与所有其他应用程序容器中实现了它。实际上,我只是添加了“io.rancher.sidekicks:registry,ui,jobservice,mysql,proxy”作为harbor-setupwrapper容器的标签。 (警告:我并没有告诉Raul这件事,因为这可能会吓到他或任何其他Rancher专家。 但是这成功了,所以请原谅我。)
我们通过开放一个问题来解决另一个问题。sidekick容器的名称解析并不会真的像你预想的那样运作,所以我只能找出其它解决方法(如果你感兴趣的话,你可以在这里了解问题并且修复它:https://forums.rancher.com/t/cant-resolve-simple-container-names-within-sidekick-structure/3876)。
在创建Rancher应用商店条目的过程中,还有两个问题需要解决:
- “harborhostname”变量需要设置为确切值,这样用户就可以通过它连接到该Harbor实例。
- 所有的Harbor容器都只能部署在单个主机上,这个主机可能是许多主机(Cattle)集群中的一个。
我可能会让Rancher专家再一次感到害怕,为了给主机上的所有容器贴上“harbor-host = true”的标签,我已经配置Docker Compose文件。
这样我就能确保所有容器都部署在同一台主机上(更重要的是,对某一个主机有一定程度的控制权)。 而且,因为我知道我的容器将要到达哪个主机,所以我可以明智地选择变量“harborhostname”。 它可以是主机IP地址或主机FQDN。
最后,Docker Compose文件将会发布主机上代理容器的端口80和443(显然在在该主机上这些端口是免费的,不然部署会失效)。也许这不是一个最佳实践,但可以解决一些基本的或是较为容易的问题。
注意:因为状态会保存在主机的/ data目录中,所以如果你是为了测试而启动和关闭Harbor实例,那你要将状态保存在多个部署中。这和你运行一个真正的云本地应用程序还是有很大差距的,但它能说明Harbor(0.5.0)是怎么构建的,我只是忠于单个主机上的Rancherization方案的原本的操作模式。
下图说明了单个主机部署中各个组件间的详细信息和关系:
下图是我运行过程中的实际部署:
用于单主机部署的Rancher的Harbor私有应用商店应用模版的当前状态如下:
- 它只适用于Cattle调度程序
构建Swarm和K8s的Harbor目录的版本和Cattle的版本之间应该是没什么关系的 - 这个应用模版有上述docker化在线安装程序的所有限制(例如,它不支持https等)
- 在Docker主机上推送或拉取镜像时,要在Docker守护进程上设置“-insecure-registry”标志(因为我们只能通过http访问启动Harbor)
- 有一个主机必须要有docker-compose的“harbor-host = true”标签,这样才能够正常工作和调度容器
- 具有“harbor-host = true”标签的主机上必须要有可用的端口80和443
你可以在我的Rancher 应用商店的扩展库中找到这个子项目的可交付项:https://github.com/mreferre/rancher-catalog-extension。
子项目3: 分布式部署的Rancher 应用商店条目创建
这是操作分布式应用程序中非常有挑战性的部分,也是很有趣有用的部分。
虽然Harbor是一个容器化应用程序,但因为某些原因,它并不是应用云本地应用程序操作最佳实践的理想选择。它不遵守“十二因子应用(The Twelve-Factor App)”的方法论。
首先,在6个容器都在“众所周知的”单个主机上运行的前提下,Harbor安装程序已经建立了。这里有一些例子强调了部分挑战。我们可能已经提到了其中一些:
- Harbor软件包附带了一个嵌入式的syslog服务器,Docker守护进程会和这个服务器进行会话/日志。如果你查看了原始的Docker Compose文件,你会发现假定syslog在所有其他容器的同一主机上运行,那么所有应用程序容器都会记录到127.0.0.1
- 您必须输入(作为设置参数)确切的Harbor主机名,以便于用户连接注册表服务器。理想情况下,在云原生的环境中,应用程序应该能够使用与其关联的任何给定的IP / FQDN。最后,应该有一个选项来设置(后设置)应用程序将使用的正确的IP / FQDN端点。对于Harbor 0.5.0,你要知道(预先)在启动设置之前的IP / FQDN是什么(使得一些事在动态,自服务和分布式环境中更加难以操作)。也就是说,如果你的harbor服务以“service123.mycompany.com”这个形式暴露在用户面前,你就必须在部署时输入该字符串作为FQDN(甚至可能不知道容器的哪些主机去部署)
- Harbor在已知的单个主机上运行是假设的一部分,产品将自己的状态保存在其部署到的主机的本地目录上。这是通过容器配置中不同的目录映射完成的
这个子项目的目标是让Harbor在一个Cattle集群上运行,而不是在一个已知的主机上运行。
为此,日志图像在集群的每个节点上需要实例化(要求:每个节点必须具有标签“harbor-log = true”)。一个更好的解决方案是有一个单独的syslog服务器指向(从而完全摆脱Docker Compose中的日志服务)。
此外,由于我们不知道代理服务器将要到达哪个主机(在这种情况下,我们希望在服务发现方面实现低接触体验),我们通过利用Traefik实现了Harbor分布式模型。如果你熟悉Docker的话,就会发现Traefik做的(有点)类似于Docker通过Swarm模式提供的“HTTP Routing Mesh”开箱即用体验。要注意代理容器端口(80和443)不会暴露在主机上,Traefik是将服务暴露给外界的唯一方法(在这个特定的分布式实现中)。
总体想法是,你的DNS可以解析运行Traefik的IP,然后Traefik会“自动”将你在Harbor设置中输入的主机名添加到其配置。
存储管理也是一个有趣的部分。在分布式环境中,你不能让容器将数据存储在任何给定时间点都能及时运行的服务器上。
如果容器在另一台主机上重新启动(由于失败或升级),它需要访问同一组数据。更不用说其他容器(可能在不同的主机上运行)需要访问同一组数据。
为了解决这个问题,我选择用Rancher提供的通用NFS服务,它灵活而方便,并且是有用的。因为它允许你预先配置所需的所有卷(在这种情况下,它们通过Harbor目录条目重新实例化),或者你可以让Docker Compose在实例化时自动创建(在这种情况下,当Harbor实例关闭时,它们会被删除)。要注意的是,所有卷都映射到应用程序容器(除了不需要卷的日志和代理容器之外)。这里有很大的优化空间(因为不是所有的卷都需要映射到容器),但我暂时不会考虑这个问题。
因为在Docker Compose中没有卷目录映射(所有卷都命名为NFS共享上的卷),所以这会使得所有主机无状态。
下图显示了分布式部署中各个组件间的详细信息和关系:
此图片显示了实际操作中的部署:
Rancher用于分布式部署的Harbor私有应用商店条目的当前状态如下:
- 它只适用于Cattle调度程序
构建Swarm和K8s的Harbor应用商店的版本和Cattle的版本之间应该是没什么关系的 - 这个应用商店条目有上述docker化在线安装程序的所有限制(例如,它不支持https等)
- 在Docker主机上拉/推图像时,要在Docker守护进程上设置“-insecure-registry”标志(因为我们只能通过http访问启动Harbor)
- 集群上的所有主机必须要有docker-compose的“harbor-host = true”标签,这样才能够正常工作和调度容器
- Traefik服务(位于社区目录中)需要启动并运行才能从外部访问Harbor。这已经暴露端口80(要注意Traefik默认值是8080)
- NFS服务(位于库目录中)需要启动,运行并正确配置连接到NFS共享。Docker Compose文件已经参数化,其他驱动程序中我只测试了”rancher-nfs”
你可以在我的Rancher私有应用商店目录中找到这个子项目的可交付项:https://github.com/mreferre/rancher-catalog-extension 。
总体挑战和问题
在这个项目中,我遇到了一些挑战。我会介绍其中一些,把这些记录下来主要是为了将来可以进行参考。
- 一些小东西有时会有一些大用处。有时它是一个级联问题(即做A你需要完成B,但做B又需要完成C)。例如,Rancher sidekick要求能够执行“volume_from”。这基本上打破了完全名称解析(请参阅单主机部分了解更多信息,了解问题是什么)
- 容器出现“全绿色”并不代表着你的应用程序会启动和(正常)运行。有时,容器开始确定并没有错误,但我无法登录到harbor(由于运行安装wrapper的多个实例生成的证书不匹配)。有时,我可以登录,但不能push镜像。有时,我可以push镜像,但它们不会在UI中显示出来(因为sidekicks的名称解析问题,注册表容器无法解析ui容器名称)
- 在分布式环境中调试容器很困难。有时候,我以为遇到了一个随机问题,后来才发现是因为特定的容器在错误配置的主机上(随机)调度了。如果找到了问题根源,解决这个问题很容易。但往往,事情难就难在找不到问题根源。
- 将应用程序包装为在容器中运行(最重要的是编排部署)时,了解应用程序内部是至关重要的。在分布式场景中我将所有命名卷连接到所有容器的原因之一是因为我不能100%确定哪个容器从哪个卷读取/写入。此外,不知道应用程序会使其包装困难(特别是当某些东西不能正常工作需要调试的时候)。总而言之,容器(和编排)更类似于你如何打包和运行应用程序与你如何管理基础设施
- 虽然容器编排是关于自动化和可重复,但它也有点像“手工精灵艺术”。有时候(随机)代理容器只会显示nginx欢迎页面(而不是Harbor用户界面)。最后我通过重新启动该容器(部署后)解决了这个问题。我认为这是某个启动序列的原因。我尝试用“depends_on”指令,使得代理容器开始“朝向结束”的组成,但这没有成功。现在似乎是通过利用“external_links”指令(理论上应该不需要AFAIK)。总而言之,正确地协调容器的启动仍然是工作进程中的一部分(从2014年开始)
- 管理基础架构(和服务)以运行容器化的应用程序是很困难的。我运用一些简单的服务,例如基本的Rancher NFS服务时,遇到了一些问题,我必须解决使用不同级别的软件,不同的部署机制等等。从一个版本的基础设施到另一个版本的基础设施的升级也很关键
- 我遇到的另一个NFS问题是,当堆栈关闭时,卷不能在NFS共享上正确清除。在Rancher UI中,卷似乎已经不存在了,但是直接查看NFS共享,其中一些(一个随机数)似乎以剩余的目录的形式留下。我没有深入研究这是为什么。
结论
正如我所提到的,这只是一个粗略的整合,毫无疑问,它可以更完善一些。这主要是一次很好的学习经历,未来可以进一步扩展,比如在Rancher Kubernetes内集成,启用https协议等等。
在分布式系统中完成应用服务的动态配置也是这次实验的挑战之一,当然这不是很复杂,但通过这个过程可以让你更好地了解如何解决这些问题。
从更高的层次看,将一个应用容器化并部署在分布式系统中,它有两种方式:一是你的基础服务就以Paas方式构建,所需要的服务资源(如调度、负载均衡、DNS解析等)向Paas申请,你可能需要深度改造你的应用;二是,选用Rancher这样的平台,利用它提供的特性,只需要编
写compose编排文件,就可以很轻易的实现需求。实际上Ranche也是更多得帮助你在第二种场景中快速实现你的需求,简单易用的基础设施服务助你快速实现应用容器化。
原文来源:Rancher Labs