TencentHub 是一个集 Docker 镜像、二进制文件、helmcharts 于一体的仓库存储服务。那么这一架构技术是如何基于 Kubernetes 快速实现 workflow 引擎的呢?今天将为大家分享《TencentHub 技术架构与 DevOps 落地实践揭秘》,让我们开始吧!
大家好,这次主要是给大家分享 TencentHub 技术架构和 DevOps 相关的一些东西,这两点和大家日常开发结合会比较紧密,比较接地气一点。我今天会主要分享三部分,第一,简单聊一下 TencentHub 这个产品和 DevOps 的关系,我们如何去思考建设一个 TencentHub 镜像仓库+DevOps 引擎。第二部分讲讲 TencentHub 总体的一些东西和 Docker 镜像存储在腾讯云上是如何实现的,最后是我们的 workflow 引擎:为了支撑 DevOps 工作流去设计产品以及一些具体设计/实现细节。
TencentHub 与 DevOps?
什么是 DevOps?
我相信很多人对 DevOps 已经比较熟悉了,这个概念从 2009 年大火起来到现在已经过去了接近十年的时间。直击 DevOps 的核心理念,下面这句话是我最赞同的对 DevOps 的描述:以业务敏捷为中心,去构造适应快速发布软件的工具和文化。我们做 DevOps,要去推崇 DevOps,肯定不能忘掉我们的目标是快速交付软件产品。在达到这个目标的过程当中,非常重要的落地就是我们需要工具去支撑 DevOps,所以接下来分享 TencentHub 这样一个以 DevOps 引擎为指导的工具。
什么是 TencentHub?
TencentHub 是什么?TencentHub 核心有两部分,第一,它是一个多功能的存储仓库,包含了 Docker 镜像存储功能以及 helmcharts 这样的存储,还有一些构建产物的存储等等。第二,TencentHub 是一个 DevOps 引擎,通过我们自己设计的 workflow 引擎对工作流进行编排,去帮助大家建立自己的 DevOps 流程,比如说构建、测试、审核、部署等等任务。当然,在前置环节,我们也将去对接腾讯云提供的 TGit 代码托管服务,最终给开发者提供一套完整的云上 Devops 工具解决方案。
TencentHub 技术架构
TencentHub – 总体架构
我简单介绍一下 TencentHub 的总体架构,TencentHubd 的镜像仓库存储是基于 COS 实现,因为 COS 可靠性非常高,这非常方便地给我们的镜像存储带来高可靠保证。TencentHub 核心还有一个自研 workflow 引擎,可以使用 YAML 定义 DevOps 流程,让我们的 DevOps 本身达到 Programmable,这对 Devops 的管理是很重要的。在 TencentHub 的 Devops 引擎里面,我们使用容器去实现插件机制,用来封装用户自定义的 DevOps 任务,后面会详细介绍。使用 Kubernetes 作为执行引擎,运行 DevOps 任务,目前是使用 TKE 来实现的。在 Docker 存储仓库里,我们也会加入 Docker 镜像漏洞的安全扫描,后面会做一个的简单介绍。
镜像存储
Registry Token Authentication Specification
左边这个图分为两大部分。上面是 Registry,下面是 StorageEngine。Registry 主要包含了组织、团队、成员的管理,以及一些权限的控制,同时还包括仓库 Repository 的管理,负责 Docker 镜像、文件、helmchart 的存储。我们所有存储都是使用一个仓库来管理,镜像、文件等都会放在某一个仓库下面。在最上面,我们提供了 Webhook,可以帮助用户对接自己的系统,监听仓库或者 DevOps 流程发生的一些事情。在 Registry 里面还有 Component 组件,负责 DevOps 任务的存储。下面这部分 StorageEngine,是我们去解决统一的仓库存储层的问题,它是一个我们提供出来给全球的腾讯云的机房都能使用 TencentHub 服务的机制。
TencentHub 仓库中最重要的是 Docker 镜像的存储。除了公共的镜像存储之外,TencentHub 还支持私有的镜像存储。私有镜像存储需要通过登录才能获取或者上传 Docker 镜像。如何是想登录认证呢?这是 Docker 官方的 Registry 用户认证的规范,它并没有纳入到 OCI 规范里面,只是在官方有一个文档去说明流程是什么样的,Docker 客户端也是按这个流程去实现的,所以我们只需要在 TencentHub 后端按这个流程去实现自己的授权服务就可以了。
TencentHub pull 授权流程
在我们使用 Docker 进行 push/pull 的时候,本地的 Docker 客户端会调到 containerd,检查当前这个操作是否有授权。当我们没有带上一个 Token 的时候,后端的存储服务会返回 401,并且携带一个授权服务的地址,告诉客户端去这个地方拿授权码。客户端请求授权服务获取将要进行操作的授权 Token。
获取 Token 支持两种方式,一种是 OAuth2 密码模式,你可以通过登录之后,授权服务会给你下发一个 key,它和你的密码无关系,后续的通信都是使用 Key 获取 Token。
第二种方式是 Basic Authentication 方式,很多时候我们采用的都是这种方式。TencentHub 实现的也是后者。这种方式会在 Token 客户端留下帐号密码,并不是太安全,我们后面可能会去支持 OAuth2 的密码模式。
当客户端发起一个请求到授权服务的时候,授权服务就根据当前的请求去生成一个授权的列表,封装成 Token 返回客户端,这个 Token 的格式对客户端是透明的,可用使用任意自己的格式。我们这里继续保持了选择 JWT 的格式,是因为我们把 StorageEngine 和 Registry 做了一个状态的隔离,因为我们把需要的权限都放到了 Token 里面进行封装,不需要有一个中心化的带状态的权限校验服务去连接两者。最后当 Token 拿到之后,客户端重新再去请求 Registry,重新拉取它的 Docker 镜像,或者进行数据的上传/下载之类。
这里用一个时序图简单说一下它的流程。首先可以看到,Docker 服务端首先会去访问/v2/这样的路径,客户端会根据当前是否有携带 Token 来返回一个授权的地址,我们目前返回的是hub.tencentyun.com/token这个地址,客户端就会自动访问/token的URL,带上当前的账户以及申请的权限范围。客户端申请Token完成之后,就会进入Docker镜像拉取流程。
OCI Distribution Specification
Docker 镜像是分层组织形式,每个 Docker 镜像包含多个 Layer 和一个 Config 文件,每个 Layer 包含构建一个 Docker 镜像时文件系统里面出现的差异,Config 文件里面会包含当前这个 Docker 镜像能运行的一些环境要求,例如 OS,及一些配置入口命令,导出的一些端口之类。这些文件被一个 Manifest 文件引用起来,通过它可以找到这个 Docker 镜像的所有内容。Docker 是在 V2 版本设计了这样一套规范,目前它也已经提交到 OCI 组织,成为一个 Distribution Specification 标准。
这样的设计带来很多好处。
首先,它的安全性有比较大的提高。镜像中的所有文件都是 content addressable 的设计,让我们只需要拿到最后的 Manifest 去校验,就可以发现整个数据的传输过程中是否有篡改。这在现在比较流行的区块链中都采用类似的技术,是 merkle tree 的一种实现。
第二,它可以极大减少冗余。每个构造的 Docker 镜像都不会从空白文件开始,都是从各个发行版或者是比如 CentOS 的基础镜像开始,这些基础镜像存在非常多的基础镜像 Layer 都是相同的。它通过 content addressable storage 的组织结构,不仅在客户端减少存储空间,也可以在远端仓库中减少存储所需要的空间。
最后,它对缓存是非常友好的。因为整个 Docker 镜像里面不管哪一个 Layer 有改变,都会最终影响到 Manifest 的改变,所以 Docker 镜像并不是重新修改一个 Layer,而是重新生成的,我们去做缓存的时候就可以非常方便地在不同的环境下地部署我们的缓存,只需要使用 content digest 作为 Layer 的 key 来引用 Layer 文件即可,Layer 不会修改,只会生成新的或删除老的 Layer,缓存的管理就非常简单。
distribution RESTful API
这里再简单补充一下关于 distribution 规范的一些 API。前面两个不说,主要是第三个,每个 Docker 镜像的上传都会访问到/v2/仓库名/manifests/tags|digest 地址,在这个地址上,它支持去 GET、PUT、DELETE,去实现 Docker 镜像在远端仓库的拉取/上传/删除的功能。刚才所说的 Layer、Config 文件,包括 Menifests 文件,也可以通过 V2 这个路径下面的 name+blobs+digest 去获取。更详细的规范,可以看一下 OCI 组织已经成为标准的说明,下面有个 URL。
distribution 架构
TencentHub 镜像的存储核心还是利用了 Docker 官方的 distribution 来实现的,distribution 的实现在这个图里面描述得比较清楚,最上面有 API route 层分发 URL 的规则。第二层会有一个权限控制,我们在这里做了一些改造,增加了 TencentHub 的 hubtoken 的实现。我们为什么要去实现 hubtoken,而不是用官方现在自己的 GWT 格式?
是因为 TencentHub 可能还会面临着一些私有化部署的需求,或者说一些用户在公有云上面希望有自己独立的存储仓库。但是我们上面的 Registry 和 StorageEngine 两者之间并没有中心化的授权服务,所以我们就通过在 hubtoken 里面包含了一些不同租户的身份识别信息,在上下文进行传输,所以就单独改造了这里。
在权限控制下面会有 API 协议的函数处理,一些主要的业务逻辑,都在这里实现。最终到下面 storage 的实现,它提供了一套存储的插件机制,有一个标准的存储接口,规定了文件上传、文件移动等等接口,只要去实现就可以了。我们这里也会实现 GOS,是对 COS 的简单封装,后面会做一个 GOS 简单介绍。
现在腾讯云容器服务的仓库是 CCR,还没有提到 TencentHub 上面来。CCR 有两个问题,第一是不同地域的镜像是不通的,比如说我们在广州上传一个镜像,想在硅谷拉取是拉取不到的,这是我们直接依赖了 COS 提供的分发能力,没有去对它做封装,COS 是无法跨区域访问的,所以会存在这个问题,这会带来用户体验上的不一致,基于仓库的触发器、Docker 镜像构建功能,都会受到这个问题的影响,所有基于仓库的服务都需要单独又去部署一份,维护上也带来了很大影响。所以我们就做了下面这一层。
我们在设计 global object storage 的时候,发现 distribution 协议的设计没有事务的需求,第二是我们拉取多个镜像,它对延时不太敏感,但是对吞吐量很敏感。因此,实现这个存储,我们只对 COS 做了很简单的封装,提供中心化的服务,它类似于一个 p2p 的 tracker,会去记录它所有的文件分布在哪个区域的哪个 COS 里面。比如说我们从广州上传了一个镜像,他需要在硅谷去拉取,我们通过这个 global object storage 服务,会发现它在本地是没有的。这个时候通过腾讯云内部的专线,直接去广州把它拉取过来。在拉的过程当中,它会同步写到本地的 COS 和客户端去,这样客户端也不用做很长的等待。当下一次硅谷再访问该镜像的时候,它就不会再跨区域去访问这个数据,给我们减少了很多成本,最终也给用户带来很好的体验。
评论