应用的迁移部署是一件非常复杂的事情。我们不仅要针对每个环境单独调整,可能还会面临其它的问题,比如检查依赖、扩展应用、在不影响整体应用的情况下单独更新组件。Docker容器化的思想和面向服务式的设计模式试图解决这些问题。应用程序可以拆分为可管理的且按功能划分的组件,组件可以单独打包它们所需要的全部依赖,这样,应用程序可以非常容易的部署在任何架构中,扩展和更新组件也非常容易。
本部分主要讲述实现一个容器云平台的主要原理,主要包括容器Docker、镜像仓库harbor、编排工具K8S(监控heapster)、分布式键值对存储ETCD、网络工具、网络文件系统NFS、ElasticSearch+Fluentd日志架构、Istio服务网格(服务治理)、负载均衡、Jenkins持续集成工具等。K8S编排和Docker是谐云架构的两个核心,其他如监控、日志服务是不同Service的POD。这里同时实现Etcd数据库的高可用性、Kubernetes Master组件的高可用性等。
1 Docker容器化
Docker是目前使用最多的容器,与其它容器相比,Docker可以更加简单的创建和管理容器,并与其它开源软件集成。Docker的主要优点:①轻量级资源使用:容器在进程级别隔离并使用宿主机的内核,而不需要虚拟化整个操作系统。②可移植性:一个容器应用所需要的依赖都在容器中,这就让它可以在任意一台Docker主机上运行。③可预测性:宿主机不需要关心容器内运行的是什么,同样,容器也不需要关心是在哪个宿主机上运行。所需要的接口都是标准化的,并且交互也都是可预测的。
通常在用Docker来设计应用或者服务时,要避免大而全的架构,应该采用容器化设计,也就是所谓的SOA(面向服务架构),在以后的使用中更容易的独立扩展或者升级组件。
1.1 Docker原理
Docker的主要底层原理是内核cgroups(隔离和跟踪资源的使用)和命名空间(使组与组之间被隔离)来实现轻量级的进程隔离。Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。Docker容器通过 Docker镜像来创建。
Docker使用了C/S体系架构,Docker客户端与Docker守护进程通信,Docker守护进程负责构建,运行和分发Docker容器。Docker客户端和守护进程可以在同一个系统上运行,也可以将Docker客户端连接到远程Docker守护进程。Docker客户端和守护进程使用REST API通过UNIX套接字或网络接口进行通信。
Docker Damon用来监听Docker API的请求和管理Docker对象,比如镜像、容器、网络和Volume Docker Client。docker client是我们和Docker进行交互的最主要的方式方法,比如可以通过docker run来运行一个容器,然后我们的这个client会把命令发送给上面的Docker。Docker Registry 用来存储Docker镜像的仓库,Docker Hub是Docker官方提供的一个公共仓库,而且Docker默认也是从Docker Hub上查找镜像的,当然也可以很方便的运行一个私有仓库,当我们使用docker pull或者docker run命令时,就会从我们配置的Docker镜像仓库中去拉取镜像,使用docker push命令时,会将我们构建的镜像推送到对应的镜像仓库中 Images 镜像,镜像是一个模板,带有Docker容器的说明。容器是一个镜像的可运行的实例,可以使用Docker REST API或者CLI来操作容器,容器的实质是进程,但与直接在宿主执行的实例进程不同,容器进程属于自己的独立的命名空间。因此容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间、甚至自己的用户ID。容器是运行在一个隔离的环境里,这种特性使得容器封装的应用比直接在宿主运行更加安全。
1.2 Dockerfile
当使用交互式的操作来创建容器镜像时,我们应该及时把确定的操作加入到配置步骤中。Dockerfile是一个简单的配置文件,它描述了如何从一个已知起点开始构建镜像,其具有①易于版本控制:Dockerfile文件可以提交到一个版本控制软件中来追踪变更和复现任何错误。②可预测性:基于Dockerfile构建镜像可以从镜像创建的环节来避免操作上的错误。③问责制:如果要分享你的镜像,最好的办法是提供Dockerfile,这样别人可以创建这个镜像并审阅这个过程。Dockerfile提供了创建这个镜像的历史操作步骤。④灵活性:基于Dockerfile来创建镜像,可以覆盖原有的默认的构建配置。也就是说在构建或者运行时你不需要再输入那些复杂的命令行参数。在实现可重复的自动化构建容器方面,Dockerfile是一个非常好的工具。
1.3 容器化应用架构
如果要将应用部署到容器中,最好是采用独立容器的软件设计思路,这有利于扩展或者独立升级组件。实现这种设计的应用应该具有以下特点:①它们不应该依赖或者关心宿主机上的任何细节。②每一个组件应该提供一致性的接口,使得调用者可以访问服务。③每一个服务应该在初始化阶段从环境变量中获取参数。④应用产生的数据应该通过Volumes存储在容器外部或者数据容器中。
只要接口不变,这些策略使得组件可以被独立的更换或者升级。这使得它们向着水平扩展的方向发展,也就是根据瓶颈情况,每一个组件都可以扩展。每一个组件通常都有定义合理的参数默认值,而不是强制编码它们。组件可以使用这些值作为候选值,但最好是从运行环境中获取这些值。这通常需要借助于服务发现工具,使得组件可以在启动过程中去查询这些参数。
将配置文件放在实际容器的外面,就可以在不需要重新构建镜像的情况下简单的改变应用的行为。这样可以使得一个配置可以影响同一组件的不同实例。通常来说,面向服务的设计与运行环境配置策略配合的比较好,这是因为二者都需要更灵活的部署和更直接的扩展。
用Docker Registry管理容器:一旦应用被切分为功能组件,并在容器中部署,那下一步通常是确保你的容器可以通过Registry获取。将容器镜像上传到Registry,紧接着Docker宿主机可以通过简单的已知名称来拉取容器并启动其实例。满足这个要求的Docker Registry有很多。有一些是对外公开的,任何人都可以查看和使用已经被提交的镜像,相对的也有些是私人的Registry。镜像可以用标签来标识,以方便准确的获取或更新。
2 镜像仓库harbor
Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,是在Docker Registry上进行了相应的企业级扩展(直接用的Docker Registry API),比如说管理用户界面,基于角色的访问控制,AD/LDAP集成以及审计日志等。
主要组件包括proxy(nginx前端代理),主要是分发前端页面ui访问和镜像上传和下载流量;ui提供了一个web管理页面,当然还包括了一个前端页面和后端API,底层使用mysql数据库;registry是镜像仓库,负责存储镜像文件,当镜像上传完毕后通过hook通知ui创建repository,当然registry的token认证也是通过ui组件完成;adminserver是系统的配置管理中心附带检查存储用量,ui和jobserver启动时候回需要加载adminserver的配置;jobsevice是负责镜像复制工作的,他和registry通信,从一个registry pull镜像然后push到另一个registry,并记录job_log;log是日志汇总组件,通过docker的log-driver把日志汇总到一起。
Harbor高可用:
3 服务发现(分布式配置存储)
服务发现是Docker环境依赖的核心技术之一。服务发现可以让一个应用或者组件发现其运行环境以及其它应用或组件的信息。它通常采用的是分布式key-value的存储方式,而且它还可用来查询配置信息。用户配置一个服务发现工具就可以将实际容器跟运行配置分离开,这样用户就可以在多个环境中复用同一个镜像。服务发现工具和全局配置存储使得Docker容器可以适应它们当前所处环境并嵌入现有的组件。
3.1 服务发现与全局可读配置存储
服务发现工具通常是用全局可访问的存储信息注册表来实现,它存储了当前正在运行的实例或者服务的信息。大多数情况下,为了使这个配置具有容错与扩展能力,这个工具分布式地存储在基础设施中的多个宿主机上。虽然服务发现平台的初衷是提供连接信息来连接不同组件的,但是它们更普遍地是用来存储任何类型的配置信息。
服务发现的工作原理:每一个服务发现工具都会提供一套API,使得组件可以用其来设置或搜索数据。正是如此,对于每一个组件,服务发现的地址要么强制编码到程序或容器内部,要么在运行时以参数形式提供。通常来说,发现服务用键值对形式实现,采用标准http协议交互。当每一个服务启动上线之后,他们通过发现工具来注册自身信息;当一个服务的消费者上线时,它能够在预设的终端查询该服务的相关信息。然后它就可以基于查到的信息与其需要的组件进行交互。这可将配置信息从容器内拿出。一个好处是可以让组件容器更加灵活,并不受限于特定的配置信息。另一个好处是使得组件与一个新的相关服务实例交互时变得简单,可以动态进行调整配置。
配置存储的关联:全局分布式服务发现系统的一个主要优势是它可以存储任何类型的组件运行时所需的配置信息。这就意味着可以从容器内将更多的配置信息抽取出去,并放入更大的运行环境中。通常来说,为了让这个过程更有效率,应用在设计时应该赋上合理的默认值,并且在运行时可以通过查询配置存储来覆盖这些值。通过一个全局配置存储,可以不做额外工作就能够对所有组件的实例进行同样的配置操作。
配置存储的集群管理:在Docker部署中,分布式键值还可对集群成员进行存储和管理。配置存储是追踪宿主机成员变更和管理工具的最好环境。一些可能会存在分布式键值的存储个人宿主机的信息有:①宿主机IP②宿主机自身的链接信息③跟调度信息有关的标签或元数据信息④集群中的角色(如果是采用了主从模式的集群)。在正常情况下,使用一个服务发现平台时,这些细节可能不是你需要考虑的。但是他们为管理工具提供了一个可以查询或修改集群自身信息的地方。
3.2故障检测、动态配置及安全性
故障检测需要考虑如果一个组件出现故障,服务发现能否更新状态指出该组件不能再提供服务。许多服务发现平台允许赋值时带一个可配置的超时时间。组件可以设置一个超时时间,并能定期去请求服务发现来重置超时时间。如果该组件出现故障,超时时间达到设定值,那么这个组件的连接信息就会从服务发现的存储中被去掉。这可通过将一个基本的“助手”容器与每一个组件相连来实现,而它们唯一的责任是定期的健康检查组件以及组件出现故障时更新注册表。但如果辅助容器出现故障,将导致不正确的信息在存储中;此外,还可在服务发现的工具中定义健康检查。这样,发现平台本身就可以定期检查已注册组件是否仍然可用。
动态配置:普通服务发现工具允许用户通过检查在启动时的信息来影响组件的初始配置,而动态重新配置涉及配置组件来反映配置存储中的新信息。目前有多种方式来实现,常见的是HAProxy配置调整。某些项目更加灵活,它们可在任何类型的软件中被用来触发变更。这些工具周期性的去请求服务发现工具,并且当变更被发现时,利用模板系统和服务发现工具中的值来生成新配置文件。当配置文件生成结束,相应的服务将被重新加载。
安全性:几乎所有的服务发现工具可以采用SSL/TLS加密链接。发现服务放在内网;写入数据进行加密,使用者使用的信息必须用相应的密钥解码从服务发现中获取;一些服务发现工具实现了访问控制列表,将不同的键值切分到不同的分组中。根据访的问需要来制定不同的秘钥从而访问相应的分组。
目前常见的服务发现工具:①etcd:这是CoreOS的创建者提供的工具,面向容器和宿主机提供服务发现和全局配置存储功能。它在每个宿主机上有基于http协议的API和命令行的客户端。②consul:这个服务发现平台有很多高级的特性,使得它脱颖而出,例如:配置健康检查、ACL功能、HAProxy配置等等。③zookeeper:这个工具较上面两个都比较老,提供一个更加成熟的平台和一些新特性。
基本服务发现工具的扩展项目:①crypt:Crypt允许组件通过采用公钥加密的方式来保护它们的信息。需要读取数据的组件会被分配密钥,而其他组件则不能读取数据。②confd:Confd项目旨在基于服务发现的变化,而动态重新配置任意应用程序。该系统包含了一个工具来监测节点中的变化、一个模板系统来根据获取到的值来生成配置文件,并能够重新加载受影响的应用。③vulcand:Vulcand在组件中作为负载均衡使用。它使用etcd作为后端,并基于监测变更来调整它的配置。④marathon:虽然marathon主要是调度器(后续介绍),它也实现了一个基本的重新加载HAProxy的功能,当发现变更时它来协调可用的服务。⑤frontrunner:这个项目嵌入在marathon中对HAProxy的更新提供一个更稳定的解决方案。⑥synapse:这个项目引入了嵌入式的HAProxy组件,它能够发送流量给各个组件。⑦nerve:它被用来与synapse结合在一起来为各个组件提供健康检查,如果组件不可用,nerve将更新synapse并将该组件移除出去。
4 网络和通信
面向服务的架构严重依赖节点之间的通信。Docker原生提供一些功能,可通过虚拟接口的配置、子网、iptables、NAT表管理,另外一些项目还提供了更高级的配置。谐云在这块并没有特别多的功能,使用的是Docker原生的功能。
4.1 Docker原生解决方案
为了实现容器到容器和容器到宿主机之间的通信,Docker本身就已经提供了几种解决方案。当Docker进程启动之后,它会配置一个虚拟的网桥叫docker0在宿主机上。这个接口允许Docker去分配虚拟的子网给即将启动的容器。这个网桥在容器内的网络和宿主机网络之间将作为接口的主节点。Docker容器启动后,将创建一个新的虚拟接口并分配一个网桥子网内的IP地址。这个IP地址嵌在容器内网络中,用于提供容器网络到宿主机docker0网桥上的一个通道。Docker自动配置iptables规则来放行并配置NAT,连通宿主机上的docker0。
同一宿主机上的其他容器可以使用其他邻居提供的服务而不需要额外的配置。宿主系统会简单将路由请求从docker0传到目的地。容器可以暴露它们的端口给宿主,这些端口用于接收外部请求流量。暴露出的端口映射到宿主机上,可以通过选择一个特定的端口或者由Docker来随机选择一个高位空闲端口。
当创建一个容器镜像或者运行一个容器,可以选择暴露端口还是发布端口。暴露一个端口意味着Docker将获悉该端口是此容器所使用。这可以被用于服务发现和链接。暴露端口将简单的记录端口使用并在自动映射和链接中用于显示。相对的,端口发布将映射端口到宿主接口,使得它可以与外界交互。容器端口可选择映射宿主机上一个指定端口,或者Docker可以自动的随机选择一个高位空闲端口。
Docker还提供了Docker links来配置容器间的通信。如果一个新容器链接到一个已存在的容器,新容器将会通过环境变量获得已存在容器的链接信息。环境变量将会根据另一个容器暴露的端口进行设置。IP地址和其他信息将会有Docker自身补充。
4.2 扩展Docker的网络功能
在端口被正确的映射并且链接信息被提供的情况下,同一宿主机上容器间的通信是非常简单的,宿主机之间的通信可以通过标准公共网络。但在安全或特殊功能的要求下,许多项目都关注的一个功能性改进是实现覆盖网络(虚拟网络),使得可以在宿主机间创建一个更加可预测、统一的网络环境,同时还可用于构建结构计算集群。②还有一些项目通过管道扩展了Docker的网络功能使得可创建一个复杂的网络场景,或者通过特定主机之间实现私有网络,如配置网桥、vlan、定制化子网和网关。③成熟的私有网络和隧道技术常用于提供主机之间、容器之间的安全通信。
为Docker提供覆盖网络的项目①flannel:CoreOS团队开发,这个项目被开发的初衷是为每一个宿主系统提供一个共享网络的子网。②weave: Weave创建一个虚拟网络,使得每个宿主机互联。还有其他的一些高级网络功能项目如①pipework:这个项目使得任意高级网络配置变得容易配置。②tinc:Tinc是一个轻量的VPN软件,它采用隧道和加密实现。Tinc是一个健壮的解决方案,它能够使私有网络对任何应用透明。
5 调度和编排
Docker提供了编译、上传、下载、启动和停止容器的所有必要功能。而容器管理和调度器是在分布式主机上实现容器化服务的一个关键步骤。
5.1 容器调度、编排和集群管理
集群管理与调度紧密相连,因为调度必须有权限到集群中的每个宿主机来管理服务。为了在整个集群的宿主机上启动和管理容器,调度这必须与每台宿主机上的init系统进行交互。同时,为了管理的简便,调度器抽象出一个关于整个集群上所有服务状态的视图。调度器的最大的责任之一是宿主选择。如果管理员决定在集群中启动一个服务(容器),调度器通常是被要求自动的选择一个主机。管理者可以选择性提供调度限制让调度器执行这些特定任务。
调度器通常定义了默认调度策略,这决定了服务在没有管理员输入时将如何调度。调度器通常还提供覆盖机制,使得管理员可以调特定的进程来满足特殊需求。一个调度器应该注意的其他限制条件也可以通过任意的元数据来表示。调度通常捆绑在集群管理功能上,因为这两个功能都要求在特定的主机或者在集群上进行操作。通常来说,集群管理与服务发现工具或分布式键值存储相关联。键值对存储通常也是各主机元数据存储的地方。
多容器部署的调度:分组容器管理使得管理员可以将一个集合的容器作为一个单独应用来处理,其可以简化调度它们和提供同时启动或关闭的能力。它同样可以使得一些复杂的场景,例如为每组应用配置单独的子网、扩展整组的容器。
5.2 K8S编排工具
目前主流的集群资源管理与使用框架大多都是主从(Master/Worker)模式,即一个Master管理一堆Worker去执行任务,对使用者屏蔽集群中结点之前相互通信的复杂细节,可以使用户像操作单机一样去操控整个集群。在K8S中由master负责集群中应用的调度、更新、扩缩容等操作。 K8S中的执行角色为Node,一个Node一般是一个虚拟机或物理机,它上面事先运行着 docker 服务和 kubelet 服务( Kubernetes 组件), 当接收到 master 下发的任务后,Node 就要去完成任务(用 docker 运行一个指定的应用)。
同一Pod内的容器共享同一个网络命名空间,它们之间的访问可以用localhost地址 + 容器端口就可以访问;同一Node中Pod的默认路由都是docker0的地址,由于它们关联在同一个docker0网桥上,地址网段相同,所有它们之间应当是能直接通信的;不同Node中Pod间通信要满足2个条件: Pod的IP不能冲突; 将Pod的IP和所在的Node的IP关联起来。通过Node IP网络的转发,从而可以让Pod之间可以互相访问。这种转发通过iptables实现的。
存储卷:K8s集群中的存储卷跟Docker的存储卷有些类似,只不过Docker的存储卷作用范围为一个容器,而K8s的存储卷的生命周期和作用范围是一个Pod。每个Pod中声明的存储卷由Pod中的所有容器共享。
在基本的调度和集群管理功能方面有①fleet:Fleet是CoreOS的一个调度和集群管理组件。它从集群中etcd中读取每一个宿主机上的连接信息,然后提供systemd类似的服务管理。②marathon:Marathon是Mesosphere的一个调度和服务管理组件。它配合mesos来控制长时间运行的服务,并为进程和容器管理提供Web界面。③Swarm:Docker的Swarm是一个2014年12月发布的调度器。它旨在提供一个健壮的调度器,采用Docker原生句法使得可以在宿主机上启动容器和进行供应。
作为集群管理策略的一部分,Mesosphere配置依赖以下组件:①mesos:Apache mesos是一个抽象和管理集群中所有宿主资源的工具。从整个集群到构建在上面的组件,它可以表示一组可用的资源。它描述自己为集群配置的kernel。
在高级调度和将整租容器作为一个单元方面,有以下项目:①Kubernetes:这是Google的高级调度器,kubernetes提供对你的基础设备上的容器更多的控制。容器可以被打标签、分组和配置通信子网。②compose:Docker的compose项目通过定义配置文件来提供容器组管理功能。它通过Docker的link来分析容器间的依赖关系。
6 服务网格
Kubernetes本身是支持微服务的架构,在Pod中部署微服务很合适,也已经解决了微服务的互访互通问题,但对服务间访问的管理如服务的熔断、限流、动态路由、调用链追踪等都不在Kubernetes的能力范围内。而服务网格则提供了一套从底层的负载部署运行到上层的服务访问治理端到端的解决方案。其中Istio是最著名的服务网格架构,其仅支持K8S。其原理是每个服务实例提供一个sidecar的代理实例。这些sidecar会处理服务间的通信,监控和安全相关的问题,以及任何可以从各个服务中抽象出来的东西。这样,开发人员就可以专注于服务中应用程序代码的开发,支持和维护,而运维团队可以负责维护服务网格以及运行应用程序。当然也有Buoyant,HashiCorp,Solo.io等项目开发。Netflix的技术套件还使用了一种替代架构,他们使用了应用程序库(包含Ribbon,Hysterix,Eureka,Archaius)来提供服务网格功能,而AzureServiceFabric等平台在应用程序框架中嵌入了类似服务网格的功能。