2020年值得关注的混合云计算趋势

从混合多云的兴起到混合云策略中边缘计算和超融合基础设施的使用增加,到2020年将见证数个主导趋势,这些趋势将塑造混合云的未来。

全球混合云市场:概述

混合云是指结合了私有云和公有云网络的计算平台。公司通过将私有云的速度和可靠性以及公有云的经济性和可扩展性相结合,来实现混合云,以此来提高业务流程效率。两种云网络的结合使公司可以利用两者的优势。因此,对于那些努力在私有云的帮助下降低运营成本的公司而言,混合云已成为一种更可行的解决方案。

全球混合云市场:机遇与挑战

推动混合云市场的一些关键因素包括,人们越来越意识到通过混合云实现业务流程中的高效率,业务数据量不断增加以及跨行业实施物联网的情况有所增加。大数据管理工具在组织中的日益普及也导致混合云的合并增加。通过使用灵活且兼容的云网络,可以增强通过大数据技术实现的业务运营增值,从而可以将数据平稳地吸收到公司的运营数据库中。

云计算行业以惊人的速度发展,每年都有新的趋势和发展。 但是,有一件事已经很清楚了-云的未来是混合的。 混合云将私有云随附的任务关键型应用程序的更高安全性与公有云提供的灵活性和可扩展性相结合,可为组织带来两全其美的承诺。

有了这些优势,混合云已经成为首选的实施模型,它将推动云计算行业的发展。 研究公司Gartner的研究结果证实了这一趋势,该发现表明,计划,实施或升级云技术的企业全球基础架构决策者中有77%表示他们处于混合云环境中。

随着混合云空间的不断成熟,它正在快速发展。 让我们看一下确定未来一年混合云未来的一些明确趋势:

一致的混合云体验

关于混合云成为未来的讨论已经进行了一段时间。 2020年将变成现实。 借助混合云空间中的新创新,组织将能够确保在整个环境中获得无缝体验,而不必将公有云和本地或私有云视为单独的部分。 企业将能够克服延迟挑战,并体验真正一致的混合体验。

混合云正在崛起

跨行业的组织正在根据其特定的业务需求从多个云提供商选择和混合技术和服务,以避免供应商锁定并从同类最佳功能中受益。 随着多个云提供商被添加到混合云的公共领域,混合多云将成为新的IT常态。 通过混合使用来自多个提供商的本地和/或私有/公有云,混合多云为组织提供了自由和灵活性,可以在企业内部或云上运行其工作负载,甚至在需要时更改云提供商。 此外,混合多云方法使组织能够在整个环境中采用通用的管理和软件开发功能。 到2020年,混合多云将成为主导趋势,并将影响云产业。 根据麦肯锡公司(McKinsey&Company)的数据,到2022年,混合云将成为1.2万亿美元的市场机会。

边缘计算在混合云战略中越来越重要

边缘计算是一种模型,在该模型中,计算应尽可能靠近数据和内容的源和汇,这将成为混合云策略的关键要素。 混合云和边缘计算的组合功能为组织带来了巨大的价值,其中混合云生态系统可用于聚合最相关的数据和后端功能,而边缘则可支持处理和实时分析。 2020年,随着物联网设备数量的增加,越来越多的企业将利用混合云模型利用边缘优势来发现关键业务见解。

集装箱化持续上升

不管是在内部部署还是在一个或多个云上部署工作负载,行业专家都普遍认为容器是有效的混合云模型的核心,因为它们具有提供一致性的功能。 容器简化了与混合云相关的部署,管理和运营问题,从而帮助组织从其混合云战略中最大化业务价值。 到2020年,虽然集装箱将不再成为主流,但我们可以期望采用率更高。 随着每家技术巨头引入平台以简化集群的部署和管理,组织将考虑利用容器的功能来实现混合云异构和工作负载不可知。

用于混合云的超融合基础架构越来越受欢迎

超融合基础架构(HCI)逐渐成为支持混合环境的最佳选择,因为它解决了对环境的最大关注-复杂性的增加。 HCI使组织无需再将计算,存储和网络资源作为单独的层进行管理,从而使组织能够将云集成到其环境中。 组织获得了从一个单一窗格管理所有内容的能力。 此外,HCI解决方案的预集成,整合的计算和存储资源使云实施能够更快地运行,扩展更大并响应更快。 随着混合云的发展,HCI解决方案也在不断发展,以适应混合云世界的需求。 到2020年,预计会有越来越多的组织在HCI上运行其混合云,根据行业观察家的说法,它将最终成为混合云的首选基础架构平台。

灾难恢复和备份要求将刺激混合云的采用

在当今始终在线的业务场景中,有效的灾难恢复和备份对于确保业务连续性和数据安全比以往任何时候都变得更加重要。 提供可扩展性,灵活性和成本效益,灾难恢复和备份的混合云方法可以证明对企业极为有利。 DR是一项复杂且成本和资源密集的活动,因此基于云的DR和备份正在成为一种可行的选择。 使用混合云模型,组织可以在云中拥有二级异地备份位置,这比传统存储更有效,同时可以灵活地在专用网络上托管敏感数据并满足合规性要求。 不断增加的灾难恢复和备份要求将继续推动混合云在2020年之前的采用。

对混合云托管服务的吸收更强

2020年,许多组织将与云服务提供商合作,以定义和确定最佳的云管理方法。 组织将选择提供商以处理与混合云相关的复杂性,并有效地管理跨云提供商的实例,并托管在各种部署模型(私有云和内部部署)上。 提供强大的云管理平台和托管服务相结合的云提供商将凭借其帮助组织确保其混合基础架构的统一视图和整体无缝体验的能力而成为首选合作伙伴。

随着我们进入新的十年,云无疑比以往任何时候都更加勇敢和强大。 混合云将继续增长和发展,为组织提供更多的灵活性和一致性,并帮助他们加快应用程序部署周期。


相关链接:

2020年值得关注的7大新兴混合云计算趋势

混合云市场-2016年全球行业分析,规模,份额,增长,趋势和预测-2024

PaaS平台概述

什么是容器PaaS

平台即服务(PaaS)是一种云计算模型,其中第三方提供商通过互联网向用户提供硬件和软件工具(通常是应用程序开发所需的工具)。 PaaS提供商在其自己的基础架构上托管硬件和软件。 因此,PaaS使开发人员免于必须安装内部硬件和软件来开发或运行新应用程序的麻烦。

PaaS如何运作

如上所述,PaaS不会取代公司用于软件开发的整个IT基础架构。 它是通过云服务提供商的托管基础结构提供的,用户最常通过Web浏览器访问产品。 PaaS可以通过公有,私有和混合云交付,以交付诸如应用程序托管和Java开发之类的服务。

其他PaaS服务包括:

  • 与开发团队合作
  • 应用程序设计与开发
  • 应用程序测试和部署
  • Web服务集成
  • 信息安全
  • 数据库整合

用户按使用量付费使用PaaS。 但是,某些提供商对访问平台及其应用程序收取固定的月租费。

PaaS的优缺点

PaaS的主要好处是为用户提供了简便性-PaaS提供商提供了许多基础架构和其他IT服务,用户可以通过Web浏览器在任何地方访问。按使用付费的能力使企业可以消除传统上用于本地硬件和软件的资本支出。

但是,服务可用性或弹性可能是PaaS所关注的问题。如果提供商遇到服务中断或其他基础设施中断,则可能对客户产生不利影响,并导致生产力损失惨重。

供应商锁定是另一个常见的问题,因为用户无法轻松地将许多服务以及通过一个PaaS产品生成的许多数据迁移到另一种竞争产品。用户在选择PaaS提供商时必须评估服务停机和供应商锁定的业务风险。

PaaS产品的内部更改也是一个潜在问题。例如,如果PaaS提供商停止支持某种编程语言或选择使用其他开发工具集,则对用户的影响可能是困难且具有破坏性的。用户必须遵循PaaS提供商的服务路线图,以了解提供商的计划

PaaS通常不会取代一家企业,这会影响其环境和能力。

PaaS体系结构具有整个IT基础架构。相反,它倾向于合并各种底层的云基础架构组件,例如操作系统,服务器,数据库,中间件,网络设备和存储服务。这些功能均由服务提供商拥有,操作,配置和维护。 PaaS还提供其他资源,包括数据库管理系统,编程语言,库和各种开发工具。
PaaS提供商构建并提供了一个弹性和优化的环境,用户可以在该环境上安装应用程序和数据集。用户可以专注于创建和运行应用程序,而不是构建和维护基础架构和服务。

许多PaaS产品都面向软件开发。这些平台提供了计算和存储基础结构,以及文本编辑,版本管理,编译和测试服务,可帮助开发人员更快,更高效地创建新软件。 PaaS产品还可以使开发团队进行协作,而无论他们身在何处。

PaaS体系结构使其基础结构对开发人员和其他用户隐藏。结果,该模型类似于无服务器计算和功能即服务的架构,其中云服务提供商管理和运行服务器并控制资源的分配。

PaaS的类型

当前,开发人员可以使用各种类型的PaaS。 他们是:

  • 公有PaaS
  • 专用PaaS
  • 混合PaaS
  • 通讯PaaS
  • 移动PaaS
  • OpenPaaS

公有PaaS最适合在公有云中使用。公有PaaS允许用户控制软件的部署,而云提供商则管理托管应用程序所需的所有其他主要IT组件的交付,包括操作系统,数据库,服务器和存储系统网络。

公有PaaS供应商提供了中间件,该中间件使开发人员可以设置,配置和控制服务器和数据库,而无需设置基础结构。结果,公有PaaS和基础架构即服务(IaaS)一起运行,而PaaS在供应商的IaaS基础架构之上运行,同时利用了公有云。不幸的是,这意味着用户只能使用他们可能不想使用的单个公有云选项。

一些中小型企业已采用公有PaaS,但是较大的组织和企业由于与公有云的紧密联系而拒绝接受它。这主要是由于公有云内的企业应用程序开发涉及大量法规和合规性问题而导致的。

私有PaaS旨在提供公有PaaS的敏捷性,同时保持私有数据中心的安全性,合规性,收益并可能降低成本。私有PaaS通常作为设备或软件在用户防火墙内交付,该防火墙通常在公司的本地数据中心中维护。私有PaaS可以在任何类型的基础架构上开发,并且可以在公司的特定私有云中工作。

私有PaaS使组织可以更好地为开发人员提供服务,改善内部资源的使用并减少许多公司面临的昂贵的云计算蔓延。此外,私有PaaS允许开发人员部署和管理其公司的应用程序,同时还遵守严格的安全性和隐私要求。

混合PaaS将公有PaaS和私有PaaS结合在一起,为公司提供了公有PaaS提供的无限容量的灵活性以及在私有PaaS中拥有内部基础架构的成本效率。混合PaaS利用混合云。

Communication PaaS(CPaaS)是一个基于云的平台,允许开发人员在不使用后端基础结构和接口的情况下向其应用程序添加实时通信。通常,实时通信发生在专门为这些功能构建的应用程序中。示例包括Skype,FaceTime,WhatsApp和传统手机。

CPaaS提供了用于创建实时通信功能的完整开发框架,而无需开发人员构建自己的框架,包括基于标准的应用程序编程接口,软件工具,预构建的应用程序和示例代码。

CPaaS提供程序还通过提供支持和产品文档帮助用户整个开发过程。一些提供商还提供软件开发工具包以及可以帮助在不同的台式机和移动平台上构建应用程序的库。选择使用CPaaS的开发团队可以节省基础架构,人力资源和上市时间。

移动PaaS(MPaaS)是使用付费集成开发环境来配置移动应用程序。在mPaaS中,不需要编码技能。 MPaaS通过Web浏览器交付,通常支持公有云,私有云和本地存储。该服务通常按月租用,价格随所含设备和支持功能的数量而异。

MPaaS通常提供一个面向对象的拖放界面,通过直接访问设备的GPS,传感器,照相机和麦克风等功能,用户可以简化HTML5或本机应用程序的开发。它通常支持各种移动操作系统。

公司经常使用MPaaS来创建将提供内部使用和面向客户的应用程序。此实现可促进BYOD环境和生产力应用程序的发展,而无需移动应用程序开发人员或额外的IT支持。

OpenPaaS是一个免费的,开源的,面向业务的协作平台,在所有设备上都很有吸引力,并提供有用的Web应用程序,包括日历,联系人和邮件应用程序。 OpenPaaS旨在允许用户快速部署新应用程序,其目的是开发一种致力于企业协作应用程序的PaaS技术,特别是在混合云上部署的应用程序。

PaaS的使用

PaaS解决方案经常用于移动应用程序的开发中。但是,许多开发人员和公司也使用PaaS来构建跨平台应用程序,因为它提供了灵活而动态的解决方案,能够创建几乎可以在任何设备上运行的应用程序。

PaaS的另一种用法是在DevOps工具中。 PaaS提供了应用程序生命周期管理功能以及适合公司产品开发方法的特定功能。该模型还允许DevOps团队插入基于云的持续集成工具,这些工具可以添加更新而不会造成停机。此外,遵循瀑布式模型的公司可以使用与日常管理相同的控制台来部署更新。

PaaS还可以通过自动执行或完全消除内务处理和维护任务来减少应用程序的上市时间。此外,PaaS可以通过减轻管理可伸缩基础架构的负担来减少基础架构管理。 PaaS消除了负载平衡,扩展和分发新的依赖服务的复杂性。 PaaS提供者无需承担开发人员控制这些任务的责任。

此外,借助PaaS现在提供的更新编程语言和技术(例如无服务器功能和容器)的支持,开发人员可以使用该模型引入技术发展的新渠道。这尤其适用于技术变革缓慢的行业,例如银行业和制造业。 PaaS使这些组织可以适应最新的产品,而无需完全更改其业务流程。

平台即服务示例

PaaS提供程序的许多示例提供了在云中构建企业应用程序所需的工具和服务。 领先的提供商包括:

  • Google
  • Microsoft
  • Amazon Web Services (AWS)
  • com
  • IBM
  • Red Hat
  • Pivotal
  • Oracle
  • Heroku
  • Mendix
  • Engine Yard

Google App Engine支持使用Java,Python,PHP和Go的分布式Web应用程序。 Red Hat OpenShift是一种Pa​​aS产品,用于使用多种语言,数据库和组件来创建开源应用程序。 Heroku PaaS提供Unix样式的容器计算实例,这些实例在隔离的环境中运行进程,同时支持Ruby,Python,Java,Scala,Clojure和Node.js等语言。

Microsoft Azure支持.NET,Node.js,PHP,Python,Java和Ruby中的应用程序开发,并允许开发人员使用软件开发人员工具包和Azure DevOps创建和部署应用程序。

AWS Elastic Beanstalk允许用户在Apache,Nginx,Passenger和IIS等常见服务器上创建,部署和扩展使用Java,.NET,PHP,Node.js,Python,Ruby,Go和Docker开发的Web应用程序和服务。

尽管许多PaaS提供商都提供类似的服务,但是每个提供商都有其细微的差别和局限性。用户必须测试潜在的提供商,以确保其服务满足任何业务或技术要求,例如支持的语言和服务可用性,这一点很重要。

PaaS,IaaS和SaaS之间的区别

PaaS是云计算服务的三个主要类别之一。 另外两个是软件即服务(SaaS)和基础架构即服务(IaaS)。

比较云计算服务模型:
下表提供了三种云计算服务类别的描述

借助IaaS,提供商可以提供基本的计算,存储和网络基础结构以及虚拟机管理程序-虚拟化层。 然后,用户必须创建虚拟机,安装操作系统,支持应用程序和数据,并处理与这些任务相关的所有配置和管理。

借助PaaS,提供商可以提供比IaaS解决方案更多的应用程序堆栈,并将操作系统,中间件(例如数据库)和其他运行时添加到云环境中。

借助SaaS,提供商可以提供整个应用程序堆栈。 用户只需登录并使用完全在提供商的基础架构上运行的应用程序即可。


相关链接

平台即服务(PaaS)

大数据容器化-基于Kubernetes构建现代大数据系统

Apache Spark

在大数据处理与分析领域,Apache Spark无疑占据着重要地位。它的特点是基于内存计算,支持各类资源管理平台,其中以YARN最为常见,同时又与Hadoop平台集成,在集群节点以HDFS作为分布式文件存储系统。

我们可以先看一下搭建一个常见的Apache Spark大数据平台需要哪些步骤:

  1. 安装Hadoop集群
  2. 配置HDFS
  3. 配置YARN
  4. 安装Spark
  5. 配置Spark与YARN集成

事实上如果参阅官方文档,还有更多细节检查与配置,有过大数据相关领域从业经验的人都知道,要搭建一套可用的大数据环境并不容易,再加上后期维护,就更吃力了,而一套稳定的大数据平台正是进行大数据应用开发的基础。根据笔者了解,有不少公司正是因为大数据平台搭建及配置的复杂性等原因,不得不在多个测试环境中,共用一套大数据平台,这种方式长期看维护成本较高,也可能存在安全隐患。

大数据领域需要一些变化,而Kubernetes的出现则提供了契机。

Kubernete(以下简称k8s)是容器集群管理系统,是一个开源的平台,可以实现容器集群的自动化部署、自动扩缩容、维护等功能。通过Kubernetes你可以:

  • 快速部署应用
  • 快速扩展应用
  • 无缝对接新的应用功能
  • 节省资源,优化硬件资源的使用

大数据社区

随着K8s社区的发展壮大,微服务及容器化被越来越多的公司应用到生产环境。与此同时,K8s也成为容器编排的首选平台。大数据社区在容器化进程中当然也是不甘落后的。

Spark自2.3开始官方支持K8s
Flink自1.9开始官方支持K8s
Hue官方Helm chart包
Hive以MR3为执行引擎支持K8s
Airflow自1.10开始支持K8s
Presto支持K8s
…… 

可以看到整个大数据社区也在积极支持容器化,但大数据的容器化并不是生硬地将各个组件搬到K8s上,以Spark on YARN为例,核心组件YARN作为资源调度器,其结构如下图所示

下图讲述了Apache Spark on YARN的工作方式:

YARN ResourceManager的功能为:

负责集群中所有资源的统一管理和分配,它接收来自各个节点(NodeManager)的资源汇报信息,并把这些信息按照一定的策略分配给各个应用程序

了解K8s的同学可以看出YARN的功能其实与K8s Scheduler的功能非常类似

Kubernetes 调度器是一个策略丰富、拓扑感知、工作负载特定的功能,调度器显著影响可用性、性能和容量。调度器需要考虑个人和集体的资源要求、服务质量要求、硬件/软件/政策约束、亲和力和反亲和力规范、数据局部性、负载间干扰、完成期限等。

所以与其将YARN生搬到K8s中(早期确实是这样做的),何不用K8s调度器替换掉YARN,使得Spark适应K8s呢? 事实上社区确实是在这个方向努力尝试,并且自Spark 2.3开始,实验性支持使用K8s原生Scheduler替代YARN。

spark on k8s:

在该方案中

  1. 客户端通过`spark-submit`将任务提交到K8s集群中,并在集群中启动一个Spark Driver Pod;
  2. Spark Driver启动相应的Executor Pod, 组成一个Spark Application集群并执行作业任务;
  3. 任务执行完成后,Executor Pod会被销毁, 而Driver Pod会持久化相关日志,并保持在’completed’状态,直到用户手清理或被K8s集群的垃圾回收机制回收.

Spark原生支持K8s的好处也是很明显的:可以更好的利用K8s的集群资源,通过K8s赋能,更好的进行资源的隔离。这个方案不太友好的地方在于:`spark-submit`在K8s集群之外,使用非声明式的提交接口,实际使用起来不够友好。

将Spark应用迁移到K8s环境中

Spark Operator是Google基于Operator模式开发的一款的工具, 用于通过声明式的方式向K8s集群提交Spark作业,并且负责管理Spark任务在K8s中的整个生命周期,其工作模式如下图所示:

我们可通过Hem安装`spark-operator`

$ helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
$ helm install incubator/sparkoperator --namespace spark-operator

创建服务用户及绑定权限

$ kubectl create serviceaccount spark 
$ kubectl create clusterrolebinding spark-role --clusterrole=edit --serviceaccount=default:spark --namespace=default

一个典型的Spark应用在K8s中的资源描述文件`spark-pi.yaml`如下所示

apiVersion: "sparkoperator.k8s.io/v1beta2"
kind: SparkApplication
metadata:
  name: spark-pi
  namespace: default
spec:
  type: Scala
  mode: cluster
  image: "gcr.io/spark-operator/spark:v2.4.4"
  imagePullPolicy: Always
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.11-2.4.4.jar"
  sparkVersion: "2.4.4"
  restartPolicy:
    type: Never
  volumes:
    - name: "test-volume"
      hostPath:
        path: "/tmp"
        type: Directory
  driver:
    cores: 1
    coreLimit: "1200m"
    memory: "512m"
    labels:
      version: 2.4.4
    serviceAccount: spark
    volumeMounts:
      - name: "test-volume"
        mountPath: "/tmp"
  executor:
    cores: 1
    instances: 1
    memory: "512m"
    labels:
      version: 2.4.4
    volumeMounts:
      - name: "test-volume"
        mountPath: "/tmp"

部署运行

$ kubectl apply -f spark-pi.yaml

计算与存储分离

计算与存储耦合存在的问题:

  1. 当存储或计算其中一方资源不足时,只能同时对两者进行扩容,导致扩容的经济效率比较低(另一种扩容的资源被浪费了);
  2. 在云计算场景下,不能实现真正的弹性计算,因为计算集群中也有数据,关闭闲置的计算集群会丢失数据。

因为耦合导致的以上这些问题,导致很多公司不得不考虑这种耦合的必要性。而Hadoop的架构设计正是计算与存储耦合,这种设计并不适合云原生架构。而作为大数据存储的基石-HDFS,目前并无官方的K8s解决方案,不过在K8s社区本身就有许多优秀的存储解决方案-MINIO

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。而且实验数据表明,其性能丝毫不逊色于HDFS

安装MINIO也非常容易

$ helm install stable/minio

我们以WordCount,数据读写使用minio存储系统(兼容亚马逊S3云存储服务接口)

JavaRDD<String> textFile = sc.textFile("s3a://...");
JavaPairRDD<String, Integer> counts = textFile
    .flatMap(s -> Arrays.asList(s.split(" ")).iterator())
    .mapToPair(word -> new Tuple2<>(word, 1))
    .reduceByKey((a, b) -> a + b);
counts.saveAsTextFile("s3a://..."); 

由于兼容亚马逊S3云存储服务接口这一优势,minio也同样可以作为Hive数据仓库的可选存储系统。

<property>
<name>fs.s3a.path.style.access</name>
<value>true</value>
<description>Enable S3 path style access.</description>
</property>
<property>
<name>hive.metastore.warehouse.dir</name>
<value>s3a://hive/warehouse</value>
</property>

总结

通过以上论述,在K8s集群上搭建Spark大数据平台,相比传统YARN调度方式而言更为简洁,MINIO可作为大数据的存储系统,在保证数据的持久性的同时,也实现了大数据计算系统与存储系统的解耦。

参考

GPU在容器云中的方案及使用

问题根源需求

在使用容器云调度资源场景下,我们可以请求并限制特定配额的CPU及内存供于容器创建使用,K8S调度器会将Pod绑定到资源合适的节点上;但对于现实使用场景原生资源的调度能力仍然不能满足现有的用户,其他特定资源例如GPU、IB卡、硬加密狗等也是迫切需要的,用户希望特定资源也可以被调度工具发现、监管、分配并最终使用。

GPU卡作为重要的计算资源不管是在算法训练还是预测上都不可或缺,而对于常见的算法训练平台或智能业务平台都有往容器迁移演进的趋势,此时如何更好的利用GPU资源成了容器云平台需要解决的问题。

所以可以看出需要解决的问题主要是一下三个方面:

1.资源管理:调度器可以发现并调度GPU资源;

2.资源限制隔离:运行在同一张卡上的GPU资源可以限制在配额之内;

3.资源算力损耗较少:同等算力的GPU资源计算能力不出现明显衰减;

解决方案

目前K8S官方对于如何共享单张GPU资源没有很好的解决方案,而对于使用多张GPU也停留在整张GPU卡作为调度颗粒度方式,应付复杂的使用场景譬如集群内存在多种GPU类型时仍有不足。处理复杂使用场景的GPU调度方案需要将资源多维度的标注,针对这种需求可以做如下处理:

1.以显存为单位上报资源并调度使用,用于应对共享单张GPU的场景;

2.以卡为单位上报资源并调度使用,用于应对多卡加速计算场景;

3.以类型为标签标注节点,尽量在同一节点安装相同GPU;

同时用户对于使用特型资源都不太愿意修改K8S原生代码,我们可以利用K8S现有的机制来避免对主干代码的侵入,如下:

1.Extended Resource机制:用于定义GPU资源;

2.Scheduler Extender机制:用于对GPU资源进行调度;

3.Device Plugin机制:用于上报、监管和分配GPU资源;

方案设计迭代

计划分三步迭代:

一、初步卡共享可行

已实现以显存/卡作为调度颗粒的资源上报使用,用户可以在该方案下在单张GPU上运行多个Pod和将多卡同时供单个Pod使用。

二、多类型GPU集群调度

需要支持多类型GPU调度使用,根据请求类型调度到特定节点上运行;同一节点上显存资源及卡资源联动,避免被以显存分配的卡重分配给卡单位的调度请求。

完成以上两部迭代后,主要架构如下:

三、GPU卡内配额限制

对于共享单张卡都无可避免的会出现计算效率下降的问题,此时可以利用Nvida官方提供的MPS接口,开启该功能可以运行多个进程在GPU上叠加提供利用率,减少了GPU上下文存储与切换,降低了调度带来的开销。需要注意的是容器内使用MPS需要GPU架构高于volta,runc默认为nvidia。对于配额的限制前期还是建议利用应用程序自己的机制来实现。在容器环境中开启MPS功能将根据一下来实现:

需要注意的时Nvidia在Volta 架构引入了新的 MPS 功能。与 Volta GPU 前的 MPS 相比,Volta MPS 提供了一些关键改进:

  • Volta MPS 客户端直接向 GPU 提交工作,而无需通过 MPS 服务器。
  • 每个 Volta MPS 客户端都拥有自己的 GPU 地址空间,而不是与所有其他 MPS 客户端共享 GPU 地址空间。
  • Volta MPS 支持为服务质量 (QoS) 提供有限的执行资源资源。

Volta前架构与Volta机构GPU使用MPS对比:

GPU在K8S中使用的全流程演示

要求:

1.节点有GPU资源

2.docker >=1.12

3.K8S >=1.10

一、安装Nvidia驱动及CUDA

驱动是应用使用GPU资源的前提

按照自己的需求安装特定版本的驱动(需要>=384.81),例如在ubuntu1604上安装CUDA10.2的驱动可以参考一下:

$ wget http://developer.download.nvidia.com/compute/cuda/10.2/Prod/local_installers/cuda_10.2.89_440.33.01_linux.run
$ sudo sh cuda_10.2.89_440.33.01_linux.run

安装完成后,可以用一下命令确认:

$ nvidia-smi

二、部署安装nvidia-docker2

NVIDIA 容器工具包允许用户构建和运行 GPU 加速 Docker 容器。该工具包包括一个容器运行时和实用程序,用于自动配置容器以利用 NVIDIA GPU。

可以参考一下安装:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker

安装完成后验证是否已成功可以使用:

docker run --gpus all nvidia/cuda:9.0-base nvidia-smi

三、切换runtime为nvidia-container-runtime

通过修改Docker的Runtime为nvidia runtime工作,当我们执行 nvidia-docker create 或者 nvidia-docker run 时,它会默认加上 --runtime=nvidia 参数。将runtime指定为nvidia。为了方便使用,可以直接修改Docker daemon 的启动参数,修改默认的 Runtime为:

cat /etc/docker/daemon.json
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}

然后重启docker

四、部署GPU-Scheduler及开启K8S相关功能

使用GPU-Scheduler需要更改原生调度启动参数,利用K8S的扩展机制,在全局调度器筛选绑定的时候查找某个节点的特定GPU卡是否能够提供足够的显存,并且在绑定时将GPU分配结果通过annotation记录到Pod Spec以供后续检查分配结果。添加以下:

- --policy-config-file=/etc/kubernetes/scheduler-policy-config.json

scheduler-policy-config.json的具体内容为:

{
  "kind": "Policy",
  "apiVersion": "v1",
  "extenders": [
    {
      "urlPrefix": "http://127.0.0.1:32766/gpushare-scheduler",
      "filterVerb": "filter",
      "bindVerb":   "bind",
      "enableHttps": false,
      "nodeCacheCapable": true,
      "managedResources": [
        {
          "name": "aliyun.com/gpu-mem",
          "ignoredByScheduler": false
        }
      ],
      "ignorable": false
    }
  ]
}

待调度组件正常启动后,再部署GPU-Scheduler:

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: gpushare-schd-extender
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - update
  - patch
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - bindings
  - pods/binding
  verbs:
  - create
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gpushare-schd-extender
  namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: gpushare-schd-extender
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: gpushare-schd-extender
subjects:
- kind: ServiceAccount
  name: gpushare-schd-extender
  namespace: kube-system

# deployment yaml
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: gpushare-schd-extender
  namespace: kube-system
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: gpushare
        component: gpushare-schd-extender
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ''
    spec:
      hostNetwork: true
      tolerations:
      - effect: NoSchedule
        operator: Exists
        key: node-role.kubernetes.io/master
      - effect: NoSchedule
        operator: Exists
        key: node.cloudprovider.kubernetes.io/uninitialized
      serviceAccount: gpushare-schd-extender
      containers:
        - name: gpushare-schd-extender
          image: registry.cn-hangzhou.aliyuncs.com/acs/k8s-gpushare-schd-extender:1.11-d170d8a
          env:
          - name: LOG_LEVEL
            value: debug
          - name: PORT
            value: "12345"

# service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: gpushare-schd-extender
  namespace: kube-system
  labels:
    app: gpushare
    component: gpushare-schd-extender
spec:
  type: NodePort
  ports:
  - port: 12345
    name: http
    targetPort: 12345
    nodePort: 32766
  selector:
    # select app=ingress-nginx pods
    app: gpushare
    component: gpushare-schd-extender

五、简单部署GPUshare-device-plugin确认可行

GPUshare-device-plugi是开源的GPU资源上报组件,利用Device Plugin机制,由Kubelet负责调度GPU卡分配,依据GPU-Scheduler分配结果执行。可以用来简单的部署测试下是否已经可使用:

kind: DaemonSet
metadata:
  name: gpushare-device-plugin-ds
  namespace: kube-system
spec:
  template:
    metadata:
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ""
      labels:
        component: gpushare-device-plugin
        app: gpushare
        name: gpushare-device-plugin-ds
    spec:
      serviceAccount: gpushare-device-plugin
      hostNetwork: true
      nodeSelector:
        gpushare: "true"
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/acs/k8s-gpushare-plugin:v2-1.11-aff8a23
        name: gpushare
        command:
          - gpushare-device-plugin-v2
          - -logtostderr
          - --v=5
          - --memory-unit=GiB
          - --mps=true
        resources:
          limits:
            memory: "300Mi"
            cpu: "1"
          requests:
            memory: "300Mi"
            cpu: "1"
        env:
        - name: KUBECONFIG
          value: /etc/kubernetes/kubelet.conf
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
        volumeMounts:
          - name: device-plugin
            mountPath: /var/lib/kubelet/device-plugins
      volumes:
        - name: device-plugin
          hostPath:
            path: /var/lib/kubelet/device-plugins
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: gpushare-device-plugin
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - update
  - patch
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - nodes/status
  verbs:
  - patch
  - update
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gpushare-device-plugin
  namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: gpushare-device-plugin
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: gpushare-device-plugin
subjects:
- kind: ServiceAccount
  name: gpushare-device-plugin
  namespace: kube-system
$ kubectl create -f gpu.yml

使用kubectl命令确认节点内已经有GPU资源:aliyun.com/gpu-mem 后就可以尝试部署一个服务看是否正常:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-1
  labels:
    app: gpu-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gpu-1
  template:
    metadata:
      labels:
        app: gpu-1

    spec:
      containers:
      - name: gpu-1
        image: bvlc/caffe:gpu
        command: ["/bin/sh"]
        args: ["-c","while true;do echo hello;sleep 1;done"]
        resources:
          limits:
            # GiB
            aliyun.com/gpu-mem: 1

如果Pod调度成功且可以在容器内正常调用GPU说明已经可以使用。

总结

方案在已成功验证步骤一情况下,结合现有资料说明发展路径是可行,后续的步骤是具体实现上的精力花费。

参考:

魔方云钉钉告警服务

用户可以在魔方云中定义告警,当告警被触发时,魔方云。这样的告警流程是基于Prometheus和Alertmanager的,具体的流程如图1所示。

图1 告警流程

其中Prometheus是监控系统,负责对集群的监控。Alertmanager负责告警的发送。

用户在魔方云中设置了具体的告警规则,每个告警规则对应接收对象,魔方云会把告警规则写入Prometheus的配置文件,并把告警规则对应的接收对象的信息写入Alertmanager。Prometheus会监控告警规则描述的值,当告警被触发时,Prometheus将告警的内容发送给Alertmanager,Alertmanager则将告警信息与的接受者信息对应起来,将信息发送给接收者。

Alertmanager本身支持以下几种类型的接收对象:

  • 电子邮件
  • Slack
  • PagerDuty
  • 微信
  • Webhook

其中前4种是主流的IT服务对象。Webhook是通用接收对象,可以用于扩展其他原本不支持的服务对象,钉钉的告警服务就是通过webhook来扩展的。扩展的思路为:首先编写一个http服务端,用于接收钉钉的告警信息。随后在魔方云中添加一个webhook配置,指向部署的服务端的地址,并把钉钉告警的配置作为参数添加在url中。告警触发后按照上图的流程由alertmanager转发到部署的服务端,服务端接收到告警信息后,读取url中的相关参数,最后将告警发送至钉钉。图2是添加了告警转发服务后的流程图。

图2 钉钉告警流程

钉钉告警扩展方法

1.编写钉钉告警转发服务端程序

服务端首先需要做的事是接收魔方云发送的webhook告警信息,并从URL中读取钉钉告警的配置:钉钉webhook、需要at用户的账号和是否at所有人。

对于webhook告警,alertmanager会以json形式发送如下的结构体

type Alert struct {
    Status       string            `json:"status"`
    Labels       map[string]string `json:"labels"`
    Annotations  map[string]string `json:"annotations"`
    StartsAt     time.Time         `json:"startsAt"`
    EndsAt       time.Time         `json:"endsAt"`
    GeneratorURL string            `json:"generatorURL"`
}

type Message struct {
    Version           string            `json:"version"`
    GroupKey          string            `json:"groupKey"`
    Status            string            `json:"status"`
    Receiver          string            `json:"receiver"`
    GroupLabels       map[string]string `json:"groupLabels"`
    CommonLabels      map[string]string `json:"commonLabels"`
    CommonAnnotations map[string]string `json:"commonAnnotations"`
    ExternalURL       string            `json:"externalURL"`
    Alerts            []Alert           `json:"alerts"`
}

服务端接收告警信息并读取url中的参数。

func ReceiveAndSend(w http.ResponseWriter, req *http.Request) {
    log.SetFlags(log.LstdFlags | log.Lshortfile)

    body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        _, _ = fmt.Fprint(w, err)
        log.Printf("[ERROR] %s", err)
        return
    }

    alertMessage := Message{}
    _ = json.Unmarshal(body, &alertMessage)

    err = req.ParseForm()
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        _, _ = fmt.Fprint(w, err)
        return
    }

    if _, ok := req.Form["webhook"]; !ok {
        log.Print("[ERROR] url argument \"webhook\" is null")
        return
    }
    if _, ok := req.Form["atmobiles"]; !ok {
        log.Print("[ERROR] url argument \"atmobiles\" is null")
        return
    }
    if _, ok := req.Form["isatall"]; !ok {
        log.Print("[ERROR] url argument \"isatall\" is null")
        return
    }
    webhook := req.Form["webhook"][0]
    atmobiles := req.Form["atmobiles"]
    isatall, _ := strconv.ParseBool(req.Form["isatall"][0])

    err = SendToDingtalk(alertMessage, webhook, atmobiles, isatall)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        _, _ = fmt.Fprint(w, err)
        log.Printf("[ERROR] %s", err)
        return
    }

    _, _ = fmt.Fprint(w, "Alert sent successfully")
}

向从url中读取的地址发送钉钉告警信息。

type At struct {
    AtMobiles []string `json:"atMobiles"`
    IsAtAll   bool     `json:"isAtAll"`
}

type DingTalkMarkdown struct {
    MsgType  string   `json:"msgtype"`
    At       At       `json:"at"`
    Markdown Markdown `json:"markdown"`
}

type Markdown struct {
    Title string `json:"title"`
    Text  string `json:"text"`
}

const layout = "Jan 2, 2006 at 3:04pm (MST)"

func SendToDingtalk(alertMessage Message, webhook string, atMobiles []string, isAtAll bool) error {
    groupKey := alertMessage.CommonLabels["group_id"]
    status := alertMessage.Status

    message := fmt.Sprintf("### 通知组:%s(状态:%s)\n\n", groupKey, status)

    if _, ok := alertMessage.CommonLabels["alert_type"]; !ok {
        return errors.New("alert type is null")
    }

    var description string
    switch alertMessage.CommonLabels["alert_type"] {
    case "event":
        if _, ok := alertMessage.CommonLabels["event_type"]; !ok {
            return errors.New("event_type is null in commonLabels")
        }
        if _, ok := alertMessage.GroupLabels["resource_kind"]; !ok {
            return errors.New("resource kind is null in groupLabels")
        }
        description = fmt.Sprintf("\n > %s event of %s occuored\n\n", alertMessage.CommonLabels["event_type"], alertMessage.GroupLabels["resource_kind"])
    case "systemService":
    // ...
    default:
        return errors.New("invalid alert type")
    }

    message += description

    for _, alert := range alertMessage.Alerts {
        if alert.Status != "firing" {
            continue
        }
        message += "-----\n"

        for k, v := range alert.Labels {
            message += fmt.Sprintf("- %s : %s\n", k, v)
        }
        message += fmt.Sprintf("- 起始时间:%s\n", alert.StartsAt.Format(layout))
    }

    dingtalkText := DingTalkMarkdown{
        MsgType: "markdown",
        At: At{
            AtMobiles: atMobiles,
            IsAtAll:   isAtAll,
        },
        Markdown: Markdown{
            Title: fmt.Sprintf("通知组:%s(当前状态:%s)", groupKey, status),
            Text:  message,
        },
    }

    data, err := json.Marshal(dingtalkText)
    if err != nil {
        return err
    }

    req, err := http.NewRequest(http.MethodPost, webhook, bytes.NewBuffer(data))
    if err != nil {
        return err
    }

    req.Header.Set("Content-Type", "application/json")
    tr := &http.Transport{
        TLSClientConfig:    &tls.Config{
            InsecureSkipVerify:        true,
        },
    }
    client := http.Client{Transport:tr}

    resp, err := client.Do(req)
    if err != nil {
        return err
    }

    if resp.StatusCode != 200 {
        log.Printf("[ERROR] %s", resp.Header)
    }

    log.Printf("[INFO] Alert message sent to %s successfully", webhook)
    _ = resp.Body.Close()
    return nil
}

2.部署服务端

将服务端程序制作成docker镜像,上传至镜像仓库。在魔方云的helm包中添加一个依赖charts,使用刚才制作的docker镜像。在用户添加了告警规则后,钉钉告警转发服务就会自动启动。

钉钉告警使用流程

1.添加钉钉通知

首先在钉钉群中添加一个自定义机器人,并复制该机器人的webhook。

进入集群页面,点击侧边栏的“通知”,然后点击右边的“添加通知”按钮。

选择“dingtalk”,并填写相关信息。可以点击“测试”按钮来测试填写的信息是否正确,如果没有错误,对应的钉钉账号会收到一条测试消息。确认无误后点击下方的”添加“按钮。

2.添加告警规则

点击侧边栏的”告警“进入告警页面,然后点击右边的”添加告警组“按钮,配置告警规则,最好降低告警触发的条件,便于测试,然后在接收者栏中选择钉钉,可以在“Notifier”中填写要at的用户的手机号码,用英文逗号分隔。在这里添加的at用户会覆盖通知中的相应用户。最后点击”创建“按钮。此时一条告警规则已经创建完毕,当告警触发时会向钉钉发送告警信息。

3.等待告警触发

等待告警触发后,相应告警的状态会变成红色字体的“Alerting”。

相应的钉钉账户就会收到一条消息。

Istio入门

一、背景

随着单片应用程序向分布式微服务架构过渡 ,特别是服务之间呈现拓扑状的复杂关系,service mesh的提出就是为了简化管理微服务之间的通信问题。为了实现微服务 Service Mesh 模式和诸多理念,Google , IBM 和 Lyft 这三家公司协同研发,并于 2017 年 6 月 8 日( 根据 Github 最后一次提交的时间 )发布了 Istio 的第一个发行版——Istio 0.1 版本。

二、istio架构

istio分为控制面和数据面,架构如下图所示。

![](/tp/5de067fab481934666d3bb44090fadad/istio

数据面:由一组sidecar组成,对应具体的组件为envoy;通过给每用启动一个轻量级的网络代理,来执行对网络通信的控制和调整,Sidecar和外围代理,实现客户端和服务器之间的安全通信;

控制面:负责管理和配置代理流量。具体通过mixer组件下发策略给envoy,执行策略并对各个sidecar收集数据。Citadel用于密钥和证书管理;pilot将身份验证策略和安全命名信息分发到代理;mixer用于管理授权和审核。

三、核心功能

流量管理:

下图显示了pilot的服务发现过程。

stio根据Kubernetes的适配器发现并注册service后,流量规则会由pilot解析成envoy理解的格式传送给Sidecar,进而控制服务间的流量和 API 调用。Istio 简化了断路器、超时和重试等服务级别属性的配置,并且可以轻松设置 A/B 测试、金丝雀部署和基于百分比的流量分割的分阶段部署等重要任务。

安全:

Istio提供底层安全通信通道,使开发人员可以专注于应用程序级别的安全,并提供大规模管理服务通信的身份验证、授权和加密。使用Istio,服务通信在默认情况下是安全的,可以跨不同的协议和运行时一致地实施策略,所有这些都只需很少或根本不需要更改应用程序。

安全涉及的几个组件及架构如下图所示:

Citadel:用于密钥和证书管理。

Sidecar和外围代理:实现客户端和服务器之间的安全通信。

pilot:将身份验证策略和安全命名信息分发到代理。

mixer:用于管理授权和审核。

策略定制:为应用程序配置自定义策略,以在运行时强制执行规则,如动态限制服务的通信量,通过名单限制对服务的访问,也可以创建自己的策略适配器,添加自定义授权行为。

可观察性:Istio强大的跟踪、监控和日志记录功能可让人深入了解服务网格的部署。通过Istio的监控功能,可以真正了解服务性能对上下游的影响,同时其定制的仪表盘可查看所有服务的性能,并了解该性能对其他流程有何影响。

四、基本功能验证

本环境基于Kubernets1.14和istio1.13版本进行验证。其中下面的例子都在官方链接的samples目录中,官方链接[1]istio官方例子

1、流量管理:

为了填充自己的服务注册表,Istio连接到服务发现系统,而在Kubernetes集群上安装了Istio,Istio会自动检测该集群中的服务和端点,使用此服务注册表,代理就可以将流量定向到相关服务。默认情况下同一工作负载多个实例,流量会平均分发,而作为A/B测试的一部分,也可以将特定百分比的流量定向到服务的新版本,或者对特定服务实例子集的流量应用不同的负载平衡策略。还可以对进出Mesh的流量应用特殊规则,或者将Mesh的外部依赖项添加到服务注册表。

以官方的bookinfo为例,使用对同一程序多版本的流量管理,具体配置如下:

自注入使能

kubectl label namespace default istio-injection=enabled

部署bookinfo到default namespaces,bookinfo服务之间默认的调用关系如下图:

可创建virtualService全部流量导向reviews-v1,yaml文件中host指向的是reviews service,只指定了v1版本,因此流量全导向reviews v1。

virtualService yaml如下:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1

结果如下图,可以看到图中绿色部分就是当前流量的走向,全部走向previews v1:

2、安全,主要提供服务网格之间安全访问,这里以enable TLS为例。

创建meshPolicy全局enable tls

kubectl apply -f - <<EOF
apiVersion: "authentication.istio.io/v1alpha1"
kind: "MeshPolicy"
metadata:
  name: "default"
spec:
  peers:
  - mtls: {}
EOF

因为使能了TLS,所以不带证书访问会报错,直接http访问结果如下:

for from in "foo" "bar"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 503
sleep.foo to httpbin.bar: 503
sleep.bar to httpbin.foo: 503
sleep.bar to httpbin.bar: 503

创建 destination rules使能TLS,目标是所有的集群内部的服务,然后服务之间就可以正常的访问了,使能TLS的操作如下:

kubectl apply -f - <<EOF
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
  name: "default"
  namespace: "istio-system"
spec:
  host: "*.local"
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
EOF

根据destination rules,访问所有集群内部服务都会带上TLS证书进行访问,使能TLS的访问结果:

for from in "foo" "bar"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200

除了全局指定tls,也可以单独指定namespace使能TLS,操作如下:

kubectl apply -f - <<EOF
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
  name: "default"
  namespace: "foo"
spec:
  peers:
  - mtls: {}
EOF

kubectl apply -f - <<EOF
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
  name: "default"
  namespace: "foo"
spec:
  host: "*.foo.svc.cluster.local"
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
EOF

指定特定service tls,操作如下:

cat <<EOF | kubectl apply -n bar -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
  name: "httpbin"
spec:
  targets:
  - name: httpbin
  peers:
  - mtls: {}
EOF
cat <<EOF | kubectl apply -n bar -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
  name: "httpbin"
spec:
  host: "httpbin.bar.svc.cluster.local"
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
EOF

3、策略

这里还是以官方的bookinfo为例,指定应用拒绝访问。

首先修改istio configmap修改disablePolicyChecks为false,使能policy;然后制定策略拒绝v3版本的访问版本,匹配源为reviews v3和目的ratings制定rule对应handler为拒绝访问,yaml如下:

 code: 7
      message: Not allowed
---
apiVersion: "config.istio.io/v1alpha2"
kind: instance
metadata:
  name: denyreviewsv3request
spec:
  compiledTemplate: checknothing
---
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: denyreviewsv3
spec:
  match: destination.labels["app"] == "ratings" && source.labels["app"]=="reviews" && source.labels["version"] == "v3"
  actions:
  - handler: denyreviewsv3handler
    instances: [ denyreviewsv3request ]

4、可观察性

Istio为网格内的所有服务通信生成详细的telemetry信息。此telemetry提供服务行为的可观察性,使运维人员能够对应用程序进行故障排除、维护和优化, 具体通过三个方面表现,第一个是metrics即指标,Istio根据监控的四个维度(延迟、流量、错误和饱和度)生成一组服务指标,暴露给proetheus。第二个是访问日志,当流量流入网格内的服务时,Istio可以生成每个请求的完整记录,包括源和目标元数据。此信息使操作员能够审核服务行为,直至各个工作负载实例级别。第三个是分布式跟踪, Istio提供了一种通过监视流经网格的各个请求来监视和了解行为的方法,了解服务网状网内的服务依赖关系和延迟来源。

以bookinfo为例,配置istio自动收集服务指标,每次调用网格内的服务,都会有相应的指标生成。

配置收集metrics的yaml文件如下:

# Configuration for metric instances
apiVersion: config.istio.io/v1alpha2
kind: instance
metadata:
  name: doublerequestcount
  namespace: istio-system
spec:
  compiledTemplate: metric
  params:
    value: "2" # count each request twice
    dimensions:
      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "client", "server")
      source: source.workload.name | "unknown"
      destination: destination.workload.name | "unknown"
      message: '"twice the fun!"'
    monitored_resource_type: '"UNSPECIFIED"'
---
# Configuration for a Prometheus handler
apiVersion: config.istio.io/v1alpha2
kind: handler
metadata:
  name: doublehandler
  namespace: istio-system
spec:
  compiledAdapter: prometheus
  params:
    metrics:
    - name: double_request_count # Prometheus metric name
      instance_name: doublerequestcount.instance.istio-system # Mixer instance name (fully-qualified)
      kind: COUNTER
      label_names:
      - reporter
      - source
      - destination
      - message
---
# Rule to send metric instances to a Prometheus handler
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: doubleprom
  namespace: istio-system
spec:
  actions:
  - handler: doublehandler
    instances: [ doublerequestcount ]

在prometheus graph界面搜索istio_double_request_count,结果如下:

日志功能,使用资源instance,handler,rule创建,具体内容如下:

# Configuration for logentry instances
apiVersion: config.istio.io/v1alpha2
kind: instance
metadata:
  name: newlog
  namespace: istio-system
spec:
  compiledTemplate: logentry
  params:
    severity: '"warning"'
    timestamp: request.time
    variables:
      source: source.labels["app"] | source.workload.name | "unknown"
      user: source.user | "unknown"
      destination: destination.labels["app"] | destination.workload.name | "unknown"
      responseCode: response.code | 0
      responseSize: response.size | 0
      latency: response.duration | "0ms"
    monitored_resource_type: '"UNSPECIFIED"'
---
# Configuration for a stdio handler
apiVersion: config.istio.io/v1alpha2
kind: handler
metadata:
  name: newloghandler
  namespace: istio-system
spec:
  compiledAdapter: stdio
  params:
    severity_levels:
      warning: 1 # Params.Level.WARNING
    outputAsJson: true
---
# Rule to send logentry instances to a stdio handler
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: newlogstdio
  namespace: istio-system
spec:
  match: "true" # match for all requests
  actions:
   - handler: newloghandler
     instances:
     - newlog

访问productpage,可以看到有对应的日志生成,操作如下:

kubectl logs -n istio-system -l istio-mixer-type=telemetry -c mixer | grep "newlog" | grep -v '"destination":"telemetry"' | grep -v '"destination":"pilot"' | grep -v '"destination":"policy"' | grep -v '"destination":"unknown"'
{"level":"warn","time":"2019-12-16T16:45:53.950607Z","instance":"newlog.instance.istio-system","destination":"ratings","latency":"1.494269ms","responseCode":200,"responseSize":48,"source":"reviews","user":"unknown"}

分布式跟踪,使用jaeger进行trace, 默认采样率为1%。至少需要发送100个请求,才能看到一个跟踪,访问productpage操作:

for i in `seq 1 100`; do curl -s -o /dev/null http://$GATEWAY_URL/productpage; done

可以在jaeger dashboard看到对应跟踪信息,如下图:

DevOps功能实现解析

概述

过去传统的开发模式是开发团队研发了产品,后期的部署运维交给单独的运维团队负责。这种开发模式经常会导致一些混乱的问题,比如,前期开发时由于缺乏后面测试和部署时的及时反馈,一些小问题没有及时发现,导致后面错误累积,甚至积重难返,需要返工重做;也有可能前期开发时没有出现任何问题,但是到后面部署运维时一些基础环境变了,导致很多冲突产生,运维或开发团队又需要在短时间内解决该问题,耗时耗力,甚至可能拖延产品的上线日期。这种传统的开发模式虽然有分工明确,各司其职的优点,但是正因为如此,开发、测试和运维团队之间严重脱节,缺乏密切的合作,很多前期没有发现的小问题会在后期部署运维时集中爆发,大大提高了开发的成本以及延长了产品的迭代周期。针对现代软件越来越复杂,需求变化越来越快的趋势,人们提出了DevOps(Development&Operations)开发模式,它不是一种工具集,而是一套方法论,主张开发、测试和运维团队之间进行沟通、协作、集成和自动化,以综合协作的工作方式改善整个团队在交付软件过程中的速度和质量。

如果说DevOps是一种开发理念,那么CI/CD(持续集成、持续部署)管道就是其中的一种实践方式,它代表着发布流程自身的一个循环,从编写代码、构建镜像、测试代码、部署代码到后面生产环境重新测试和部署等,这是一个持续的过程,反复的过程。它使开发、测试和运维团队等一开始就以综合协作的方式绑定在一起,解决了前期开发得不到及时的测试、部署反馈以及运维在后期才开始介入等问题,避免了错误累积以及能够快速响应新的需求变化。常用的CI/CD工具有Jenkins、Drone和GitLab CI等等,由于系统平台使用的是Jenkins,我们就以Jenkins为例简单介绍下其相关概念。Jenkins是一个用java语言编写的开源工具,其CI/CD功能是通过一个叫pipeline的插件完成的。顾名思义,pipeline就是一套运行在Jenkins上的流水线框架,类似于工厂中的流水线作业。它将代码编译、脚本运行、镜像构建、测试、部署等功能集成在一起,为了清楚地划分不同的功能逻辑,一个pipeline被划分成了若干个Stage(称之为阶段),每个Stage代表一组相关的操作,比如:“build”,“Test”,“Deploy”等。其中每个Stage内又划分了多个Step(称之为步骤),它是最基本的操作单元,比如:创建目录、构建镜像、部署应用等等,由各类Jenkins插件提供具体功能。pipeline示意图如下所示:

魔方云DevOps实现

简单地说,魔方云是基于Kubernetes管理多种云的云平台管理系统,内部功能的设计基本上均是通过Kubernetes(以下简称k8s)提供的自定义controller功能来实现的,其基本逻辑就是根据业务需要抽象出多个CRD(Custom Resource Definition,自定义资源对象),并编写对应的controller来实现业务逻辑。

为了实现CI/CD功能,我们抽象出了多个CRD,跟pipeline相关的3个CRD:

  • pipeline:记录pipeline运行时状态、仓库的认证信息、钩子触发配置以及项目代码地址等信息。
  • pipelineExecution:每次pipeline运行时产生的执行实例(以下简称执行实例),运行完记录结果信息;
  • pipelineSetting:整个项目下pipeline运行的设置信息,比如内存、CPU的限制,最大流水线并行运行个数等等。

跟源代码仓库(以下以gitlab为例)相关的3个CRD:

  • sourceCodeProviderConfig:记录代码仓库的application id 和secret授权信息;
  • sourceCodeCredential:记录代码仓库的oauth认证信息;
  • sourceCodeRepository:记录代码仓库的每个具体的项目信息。

除了抽象出对应的CRD外,我们还需要编写对应的controller代码实现对应的业务逻辑,比如当pipeline运行时,我们需要产生pipeline执行实例,并实时同步其运行的状态信息等等。文章下面会详细介绍。

pipeline功能步骤有很多种类型,包括运行脚本、构建发布镜像、发布应用模板、部署YAML、部署应用等等。为了提供这些功能,我们采用Jenkins作为底层的CI/CD工具,docker registry 作为镜像仓库中心,minio作为日志存储中心等等。这些服务是运行在pipeline所在项目的命名空间下。综上,我们设计的CI/CD系统功能的实现逻辑如图所示:

如上,当触发pipeline执行逻辑时,会产生一个pipelineExecution crd,以记录本次运行pipeline的状态信息。当goroutine(syncState)发现有新的执行实例产生时,就会通过Jenkins引擎接口启动Jenkins server端流水线作业的运行,Jenkins server 端收到信息后会启动单独的一个Jenkins slave pod进行流水线作业的响应。同时,goroutine(syncState)会不断地通过引擎接口轮询pipeline执行实例的运行情况进而更新 pipelineExecution crd的状态,比如,运行成功或失败等等。当pipeline执行实例发生状态变化时,就会触发其对应的controller业务逻辑,进而通过Jenkins引擎接口与Jenkins server 通信进行不同的操作,比如,暂停流水线的运行,运行完清除不需要的资源等等。当流水线作业发生状态变化时,又会通过goroutine(syncState)更改pipeline执行实例的状态,进而又触发对应的controller业务代码进行不同的业务逻辑处理,往复循环,直到流水线运行结束。这就是整个pipeline执行时的一个逻辑流程。我们把整个pipeline模块的实现划分成了3个部分:

  • crd:即上述提到的6个crd类型;
  • 基础接口定义:即与Jenkins server通信的客户端,与代码仓库交互的客户端等等基础模块;
  • controller:实现逻辑功能的业务代码。

CRD

我们首先介绍下3个与源代码仓库相关的crd。当我们首次配置pipeline时会进行代码仓库授权设置,填写的gitlab Application Id和secret等信息会被存入到sourceCodeProviderConfig这个crd中,下面摘取了一些主要的字段信息并添加了详细注释,敏感信息使用了‘*’代替。

apiVersion: project.cubepaas.com/v3
metadata:
  name: gitlab
  namespace: p-qqxs7
clientId: 89d840b****** // Application Id
clientSecret: a69657b****** // secret
enabled: true
hostname: gitlab.******.cn // 代码仓库的地址
projectName: c-llqwv:p-qqxs7
redirectUrl: https://******/verify-auth
type: gitlabPipelineConfig // 仓库类型

当配置完授权信息后,平台就会与源代码仓库进行交互,获取到仓库的oauth认证信息并填充到sourceCodeCredential crd中,同时会获取该仓库下所有的项目代码的信息并填充到sourceCodeRepository crd中。如下所示:

apiVersion: project.cubepaas.com/v3
metadata:
  name: p-qqxs7-gitlab-******
spec:
  accessToken: 65b4e90****** // 访问token
  displayName: ****** // 代码仓库的显示昵称
  gitLoginName: oauth2
  loginName: ****** // 代码仓库的登入用户名
  projectName: c-llqwv:p-qqxs7
  sourceCodeType: gitlab // 代码仓库类型, gitlab github ...
apiVersion: project.cubepaas.com/v3
kind: SourceCodeRepository
metadata:
  labels:
    cubepaas.com/creator: linkcloud
  name: 89c129ff-f572-489e-98f7-3f111b3056f7
  namespace: user-lwckv
spec:
  defaultBranch: master // 默认代码分支
  projectName: c-llqwv:p-qqxs7
  sourceCodeCredentialName: user-lwckv:p-qqxs7-gitlab-****** // 指向上述 sourceCodeCredential crd
  sourceCodeType: gitlab
  url: https://gitlab.netbank.cn/******/testpipeline.git // 项目的url地址

这样就将仓库的oauth认证信息以及仓库的项目信息保存到了2个不同的crd中。

下面详细看下与pipeline相关的3个crd结构信息。

sh-4.4# kubectl get crds | grep pipeline
pipelineexecutions.project.cubepaas.com                            2019-12-18T10:32:10Z
pipelines.project.cubepaas.com                                     2019-12-18T10:32:11Z
pipelinesettings.project.cubepaas.com                              2019-12-18T10:32:10Z

pipelinesetting crd 保存着整个项目下所有的pipeline的运行设置信息,比如CPU、内存资源限额,最多可同时运行多少个pipeline等等,不同功能的配置信息保存在多个crd下。

p-qqxs7     executor-cpu-limit          19d // cpu限制配置
p-qqxs7     executor-cpu-request        19d // cpu预留配置
p-qqxs7     executor-memory-limit       19d // 内存限制配置
p-qqxs7     executor-memory-request     19d // 内存预留配置
p-qqxs7     executor-quota              19d // 最多可同时运行多少个pipeline
p-qqxs7     registry-signing-duration   19d // 用于设置docker镜像仓库证书的有效时长

// 比如,看下executor-quota详细信息
apiVersion: project.cubepaas.com/v3
metadata:
  name: executor-quota
  namespace: p-qqxs7
default: "2" // 默认最多可同时运行2个pipeline
projectName: c-llqwv:p-qqxs7
value: "3" // 自定义设置,最多可同时运行3个pipeline,没有值会取上面默认值

下面是pipeline crd 的详细字段信息,主要是保存了上次执行pipeline时的结果信息、仓库的认证信息、钩子信息以及项目代码地址信息等。

apiVersion: project.cubepaas.com/v3
kind: Pipeline
metadata:
  name: p-2qz4b
  namespace: p-qqxs7
spec:
  projectName: c-llqwv:p-qqxs7
  repositoryUrl: https://gitlab.******.cn/******/testpipeline.git // 项目代码地址
  sourceCodeCredentialName: user-lwckv:p-qqxs7-gitlab-****** // 指向对应的用户认证信息(sourceCodeCredential crd,见下面 status ---> sourceCodeCredential 字段)
  triggerWebhookPush: true // 当push代码时触发该流水线执行
status:
  lastExecutionId: p-qqxs7:p-2qz4b-1 // 最新一次运行的执行实例对应的id
  lastRunState: Success // 最新一次运行的最后结果
  nextRun: 2 // 下次运行时执行实例对应的序号
  pipelineState: active // 该流水线处于激活状态
  sourceCodeCredential: // 上述已介绍,此处不再赘述
    apiVersion: project.cubepaas.com/v3
    kind: SourceCodeCredential
    metadata:
      name: p-qqxs7-gitlab-******
      namespace: user-lwckv
    spec:
      displayName: ******
      gitLoginName: oauth2
      loginName: ******
      projectName: c-llqwv:p-qqxs7
      sourceCodeType: gitlab
      userName: user-lwckv
    status: {}
  token: a8c70ff2-a87a-4ef5-b677-73fc6c65fe69 // 访问token
  webhookId: "241" // 通过gitlab webhook触发流水线执行逻辑

pipelineExecution crd 结构的字段信息:

apiVersion: project.cubepaas.com/v3
kind: PipelineExecution
metadata:
  name: p-2qz4b-1 // 执行实例的命名规则是:pipeline crd 的名字加序号,运行一次pipeline,序号加1
  namespace: p-qqxs7
spec:
  branch: master // 代码仓库分支
  commit: 6f67ae4****** // git commit id
  event: push // push代码触发流水线执行
  message: Update .cubepaas-devops.yml file
  pipelineConfig: // 以下是pipeline具体的stage和step的配置信息,每次运行时从代码仓库的配置文件(.cubepaas-devops.yml)拉取下来填充该结构
    notification: {}
    stages:
    - name: Clone // 克隆代码
      steps:
      - sourceCodeConfig: {}
    - name: one // 构建发布镜像
      steps:
      - publishImageConfig:
          buildContext: .
          dockerfilePath: ./Dockerfile
          registry: 127.0.0.1:34844
          tag: test:001
    - name: two // 运行脚本
      steps:
      - runScriptConfig:
          image: golang:latest
          shellScript: go env
    timeout: 60 // 超时设置
  pipelineName: p-qqxs7:p-2qz4b
  projectName: c-llqwv:p-qqxs7
  ref: refs/heads/master // 拉取的是master分支
  repositoryUrl: https://gitlab.netbank.cn/******/testpipeline.git // 项目代码地址
  run: 1 // 此次运行序号
  triggeredBy: webhook // 如何触发
status: // 以下记录着pipeline每个stage和step的运行结果信息
  executionState: Success
  stages:
  - ended: 2020-01-07T08:06:36Z
    state: Success
    steps:
    - ended: 2020-01-07T08:06:36Z
      state: Success
  ...
  ...

以上就是实现CI/CD系统功能所抽象出的6个主要的CRD。

基础接口定义

pipeline 涉及到的功能模块很多,从源代码仓库拉取代码、设置钩子操作、仓库触发流水线操作以及与 Jenkins server 端通信的 client 等等。为了方便以后的扩展,关键数据结构都被定义成了接口,如下所示:

这是与Jenkins server端通信的client接口定义。

type PipelineEngine interface {
    // 检查jenkins运行条件是否满足
    PreCheck(execution *v3.PipelineExecution) (bool, error)
    // 运行pipeline
    RunPipelineExecution(execution *v3.PipelineExecution) error
    // 重新运行pipeline
    RerunExecution(execution *v3.PipelineExecution) error
    // 停止pipeline的运行
    StopExecution(execution *v3.PipelineExecution) error
    // 获取详细的日志信息
    GetStepLog(execution *v3.PipelineExecution, stage int, step int) (string, error)
    // 同步运行状态
    SyncExecution(execution *v3.PipelineExecution) (bool, error)
}

这是代码仓库触发流水线操作的接口定义:

// 当代码仓库触发流水线操作时,由该接口响应请求并启动流水线执行
type Driver interface {
    Execute(req *http.Request) (int, error)
}

以 gitlab 为例

func (g GitlabDriver) Execute(req *http.Request) (int, error) {
    ...

    event := req.Header.Get(GitlabWebhookHeader)
    if event != gitlabPushEvent && event != gitlabMREvent && event != gitlabTagEvent {
        return http.StatusUnprocessableEntity, fmt.Errorf("not trigger for event:%s", event)
    }

    ...

    // 根据不同的触发条件获取请求参数中对应的信息
    info := &model.BuildInfo{}
    if event == gitlabPushEvent {
        info, err = gitlabParsePushPayload(body)
        ...
    } else if event == gitlabMREvent {
        info, err = gitlabParseMergeRequestPayload(body)
        ...
    } else if event == gitlabTagEvent {
        info, err = gitlabParseTagPayload(body)
        ...
    }

    // 验证并创建pipeline执行实例,开始流水线的运行
    return validateAndGeneratePipelineExecution(g.PipelineExecutions, g.SourceCodeCredentials, g.SourceCodeCredentialLister, info, pipeline)
}

除此之外,还有与代码仓库进行交互的客户端(获取代码分支信息、克隆代码、拉取pipeline配置文件等)、与minio server通信的客户端(保存、获取日志信息)等结构,不再赘述。

controller

当触发流水线执行逻辑时,会通过pipeline crd和代码仓库中的pipeline配置文件产生一个pipelineExecution crd,这时会触发pipelineExecution对应的controller运行业务逻辑。如下所示:

func (l *Lifecycle) Create(obj *v3.PipelineExecution) (runtime.Object, error) {
    return l.Sync(obj)
}

func (l *Lifecycle) Updated(obj *v3.PipelineExecution) (runtime.Object, error) {
    return l.Sync(obj)
}

当创建或更新pipelineExecution时会调用Sync同步函数进行业务处理。下面只摘取重要的代码逻辑,如下所示:

func (l *Lifecycle) Sync(obj *v3.PipelineExecution) (runtime.Object, error) {

    ...

    // 如果 pipeline 执行实例被暂停,则会停止流水线作业
    if obj.Status.ExecutionState == utils.StateAborted {
        if err := l.doStop(obj); err != nil {
            return obj, err
        }
    }

    // 如果 pipeline 执行实例运行完毕,则会清理流水线作业的一些资源
    // 比如,产生的Jenkins slave pod
    if obj.Labels != nil && obj.Labels[utils.PipelineFinishLabel] == "true" {
        return l.doFinish(obj)
    }

    // 如果 pipeline 执行实例正在运行中,则直接返回,无操作
    if v3.PipelineExecutionConditionInitialized.GetStatus(obj) != "" {
        return obj, nil
    }

    // 判断流水线作业是否超出资源限额
    exceed, err := l.exceedQuota(obj)
    if err != nil {
        return obj, err
    }
    // 如果超出资源限额,则会设置当前 pipeline 执行实例为阻塞状态
    if exceed {
        obj.Status.ExecutionState = utils.StateQueueing
        obj.Labels[utils.PipelineFinishLabel] = ""

        if err := l.newExecutionUpdateLastRunState(obj); err != nil {
            return obj, err
        }

        return obj, nil
    } else if obj.Status.ExecutionState == utils.StateQueueing {
        obj.Status.ExecutionState = utils.StateWaiting
    }

    // 更新 pipeline 执行实例的状态: 比如运行序号+1
    if err := l.newExecutionUpdateLastRunState(obj); err != nil {
        return obj, err
    }
    v3.PipelineExecutionConditionInitialized.CreateUnknownIfNotExists(obj)
    obj.Labels[utils.PipelineFinishLabel] = "false"

    // 在数据面部署pipeline功能所需资源
    if err := l.deploy(obj.Spec.ProjectName); err != nil {
        obj.Labels[utils.PipelineFinishLabel] = "true"
        obj.Status.ExecutionState = utils.StateFailed
        v3.PipelineExecutionConditionInitialized.False(obj)
        v3.PipelineExecutionConditionInitialized.ReasonAndMessageFromError(obj, err)
    }

    // 将 configMap 存储的docker镜像仓库端口信息同步到pipeline执行实例中去.
    if err := l.markLocalRegistryPort(obj); err != nil {
        return obj, err
    }

    return obj, nil
}

其中,deploy函数的逻辑就是第一次运行时通过判断数据面中是否存在pipeline的命名空间,如果存在就代表基础资源已经配置完成,直接走reconcileRb函数,该函数的逻辑见下面;如果不存在,就会在数据面中初始化必要的基础资源,比如:pipeline命名空间, Jenkins docker minio服务, 配置configMap, secret等等。

func (l *Lifecycle) deploy(projectName string) error {
    clusterID, projectID := ref.Parse(projectName)
    ns := getPipelineNamespace(clusterID, projectID)
    // 如果该pipeline的namespace已经有了,说明下面的资源部署已经完成了,则直接走reconcileRb流程
    // 否则走下面的资源部署流程
    if _, err := l.namespaceLister.Get("", ns.Name); err == nil {
        return l.reconcileRb(projectName)
    } else if !apierrors.IsNotFound(err) {
        return err
    }

    // 创建pipeline对应的命名空间,如p-qqxs7-pipeline
    if _, err := l.namespaces.Create(ns); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the pipeline namespace")
    }

    ...

    // 随机产生一个token,用于配置下面的secret
    token, err := randomtoken.Generate()

    nsName := utils.GetPipelineCommonName(projectName)
    ns = getCommonPipelineNamespace()
    // 创建用于部署docker镜像仓库的代理服务的命名空间
    if _, err := l.namespaces.Create(ns); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the cattle-pipeline namespace")
    }

    // 在 pipeline namespace 内创建secret : pipeline-secret
    secret := getPipelineSecret(nsName, token)
    l.secrets.Create(secret);

    ...

    // 获取管理面项目的系统用户token
    apikey, err := l.systemAccountManager.GetOrCreateProjectSystemToken(projectID)

    ...

    // 在 pipeline namespace 内创建secret: pipeline-api-key,用于数据面与管理面通信的凭证
    secret = GetAPIKeySecret(nsName, apikey)
    l.secrets.Create(secret);

    // 调谐 docker 镜像仓库的证书配置(在控制面中)
    if err := l.reconcileRegistryCASecret(clusterID); err != nil {
        return err
    }

    // 将控制面中的 docker 镜像仓库的证书配置同步到数据面中
    if err := l.reconcileRegistryCrtSecret(clusterID, projectID); err != nil {
        return err
    }

    // 在 pipeline namespace 内创建 serviceAccount : jenkins
    sa := getServiceAccount(nsName)
    if _, err := l.serviceAccounts.Create(sa); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating a pipeline service account")
    }

    ...

    // 在 pipeline namespace 内创建 service: jenkins
    jenkinsService := getJenkinsService(nsName)
    if _, err := l.services.Create(jenkinsService); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the jenkins service")
    }

    // 在 pipeline namespace 内创建 deployment: jenkins
    jenkinsDeployment := GetJenkinsDeployment(nsName)
    if _, err := l.deployments.Create(jenkinsDeployment); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the jenkins deployment")
    }

    // 在 pipeline namespace 内创建 service: docker-registry
    registryService := getRegistryService(nsName)
    if _, err := l.services.Create(registryService); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the registry service")
    }

    // 在 pipeline namespace 内创建 deployment: docker-registry
    registryDeployment := GetRegistryDeployment(nsName)
    if _, err := l.deployments.Create(registryDeployment); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the registry deployment")
    }

    // 在 pipeline namespace 内创建 service: minio
    minioService := getMinioService(nsName)
    if _, err := l.services.Create(minioService); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the minio service")
    }

    // 在 pipeline namespace 内创建 deployment: minio
    minioDeployment := GetMinioDeployment(nsName)
    if _, err := l.deployments.Create(minioDeployment); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the minio deployment")
    }

    // 调谐 configMap: proxy-mappings,用于配置docker镜像仓库代理服务的端口信息
    if err := l.reconcileProxyConfigMap(projectID); err != nil {
        return err
    }

    // 创建secret: devops-docker-registry,存储访问docker仓库的认证信息
    if err := l.reconcileRegistryCredential(projectName, token); err != nil {
        return err
    }

    // 创建 daemonset: registry-proxy,每个节点部署一套docker镜像仓库的nginx代理服务
    // 可以在任意一个节点上通过不同的端口即可访问到不同的docker镜像仓库
    nginxDaemonset := getProxyDaemonset()
    if _, err := l.daemonsets.Create(nginxDaemonset); err != nil && !apierrors.IsAlreadyExists(err) {
        return errors.Wrapf(err, "Error creating the nginx proxy")
    }

    return l.reconcileRb(projectName)
}

reconcileRb函数的功能就是遍历所有namespace, 对其调谐rolebindings, 目的是让 pipeline serviceAccount(jenkins) 对该project下的所有namespace具有所需要的操作权限,这样Jenkins server才能够在数据面中正常提供CI/CD基础服务。

func (l *Lifecycle) reconcileRb(projectName string) error {

    ...

    var namespacesInProject []*corev1.Namespace
    for _, namespace := range namespaces {
        parts := strings.Split(namespace.Annotations[projectIDLabel], ":")
        if len(parts) == 2 && parts[1] == projectID {
            // 过滤出属于该project下的所有namespace
            namespacesInProject = append(namespacesInProject, namespace)
        } else {
            // 对非该project下的namespace, 清除有关该 pipeline 的 rolebinding
            if err := l.roleBindings.DeleteNamespaced(namespace.Name, commonName, &metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
                return err
            }
        }
    }

    for _, namespace := range namespacesInProject {
        // 对属于该project下的namespace, 创建 rolebinding: 对 jenkins serviceAccount 绑定角色
        // 即赋予 jenkins serviceAccount 对该project下的所有namespace所需要的操作权限
        rb := getRoleBindings(namespace.Name, commonName)
        if _, err := l.roleBindings.Create(rb); err != nil && !apierrors.IsAlreadyExists(err) {
            return errors.Wrapf(err, "Error create role binding")
        }
    }

    // 赋予 jenkins serviceAccount 在 cluster 内创建和修改 namespace 的权限
    // 当部署应用时可以指定创建新的命名空间
    clusterRbs := []string{roleCreateNs, projectID + roleEditNsSuffix}
    for _, crbName := range clusterRbs {
        crb := getClusterRoleBindings(commonName, crbName)
        if _, err := l.clusterRoleBindings.Create(crb); err != nil && !apierrors.IsAlreadyExists(err) {
            return errors.Wrapf(err, "Error create cluster role binding")
        }
    }

    return nil
}

我们通过kubernator ui查看下数据面k8s中部署的有关pipeline的资源,如图所示:

pipeline-secret用于配置Jenkins、docker-registry、minio服务的auth信息,pipeline-api-key用于数据面与管理面通信的凭证,registry-crt用于配置docker镜像仓库的证书。

goroutine(syncState)的代码逻辑比较简单,当产生新的pipeline执行实例时就会启动Jenkins server端流水线作业的运行并实时同步其运行状态到pipeline执行实例中。代码逻辑如下:

func (s *ExecutionStateSyncer) syncState() {
    set := labels.Set(map[string]string{utils.PipelineFinishLabel: "false"})
    allExecutions, err := s.pipelineExecutionLister.List("", set.AsSelector())
    executions := []*v3.PipelineExecution{}
    // 遍历该cluster下的 pipeline 执行实例
    for _, e := range allExecutions {
        if controller.ObjectInCluster(s.clusterName, e) {
            executions = append(executions, e)
        }
    }

    for _, execution := range executions {
        if v3.PipelineExecutionConditionInitialized.IsUnknown(execution) {
            // 检查数据面k8s中 Jenkins pod 是否正常,正常则运行该 pipeline job
            s.checkAndRun(execution)
        } else if v3.PipelineExecutionConditionInitialized.IsTrue(execution) {
            e := execution.DeepCopy()
            // 如果已经启动了,则同步运行状态
            updated, err := s.pipelineEngine.SyncExecution(e)
            if updated {
                // 更新最新的状态到 pipelineExecution crd 中
                s.updateExecutionAndLastRunState(e);
            }
        } else {
            // 更新最新的状态到 pipelineExecution crd 中
            s.updateExecutionAndLastRunState(execution);
        }
    }

    logrus.Debugf("Sync pipeline execution state complete")
}

除此之外,系统为了内部的docker镜像仓库的安全性,会启动一个goroutine每隔一段时间就更新下docker镜像仓库的证书。同时为了方便访问docker镜像仓库,系统在数据面k8s中启动了一个daemonset类型的代理服务,内部采用的是nginx代理,通过不同的端口指向不同的docker镜像仓库,这样就可以在任意一个节点通过不同的端口方便地访问到不同的docker镜像仓库。通过kubectl在数据面中获取到的configMap(proxy-mappings)信息如下所示:

apiVersion: v1
items:
- apiVersion: v1
  data:
    mappings.yaml: |
      mappings: '{"p-ljzgf":"34380","p-qqxs7":"34844"}'
  kind: ConfigMap
  metadata:
    labels:
      cubepaas.com/creator: linkcloud
    name: proxy-mappings
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

我们可以看到现在系统上有两个pipeline的docke镜像仓库端口信息。我们可以通过不同的端口访问到对应pipeline的docker镜像仓库。如:

michael@work1:~$ docker login 127.0.0.1:34844
Username: admin
Password: 
WARNING! Your password will be stored unencrypted in /home/michael/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

魔方云DevOps使用

通常,流水线第一步是从代码仓库克隆代码,然后才是编译、测试和部署等一系列功能的设置,因此我们首先需要授权平台能够访问代码仓库。我们以gitlab仓库为例,将配置好的Application Id、secret和仓库地址填入到对应的文本框中,点击授权,这样我们就完成了仓库的授权配置。如图所示:

配置完成后,我们就可以激活pipeline功能了,我们简单地设置pipeline配置文件( .cubepaas-devops.yml)如下:

stages:
- name: one
  steps:
  - publishImageConfig:
      dockerfilePath: ./Dockerfile
      buildContext: .
      tag: test:001
      registry: 127.0.0.1:34844
- name: two
  steps:
  - runScriptConfig:
      image: golang:latest
      shellScript: go env
timeout: 60
notification: {}

如上,简单地配置了两个阶段,一个是构建发布镜像,一个是运行脚本,你可以按照自己项目的发布流程配置好pipeline,最后点击完成,我们就可以看到该pipeline自动运行了。pipeline的配置文件会推送到代码仓库中,这样就可以实现pipeline历史版本的回溯,支持从代码库直接读取pipeline配置文件,从而实现了Pipeline as Code的理念。点击查看日志,你可以看到pipeline各个阶段运行的详细日志信息。如图所示。

【注意】首次运行pipeline时系统会从网络下载Jenkins、docker、minio以及其他pipeline-tools镜像,如果长时间未运行,请查看网络是否有问题。

魔方云用户操作指南

一、魔方云概述

 

        LinkCloud魔方云,是基于以docker为代表的容器技术,为企业提供镜像构建、DevOps、多云容器管理的新一代企业PAAS平台。魔方云容器服务使用目前业界最广泛使用的编排工具 Kubernetes 对容器进行编排,轻松地管理各种环境的Kubernetes,满足IT需求并为DevOps团队提供支持。

        魔方云用户可以通过自定义添加节点集群来创建Kubernetes集群,也可以使用GKE,AKS和EKS等云Kubernetes服务来创建集群,用户还可以导入和管理现有的Kubernetes集群。

        魔方云致力于打造以应用服务为中心的云计算平台,帮助企业实现持续创新,为开发与运维者提供高效敏捷的生产环境以及优质的使用体验。魔方云提供了一个直观的用户界面来管理他们的服务容器,用户不需要深入了解Kubernetes概念就可以使用魔方云,并且包含应用市场,支持一键式部署应用。

二、准备工作

  1.  单节点最低配置2C4G,推荐4C8G;HA最低配置4C8G;
  2. 关闭firewalld等类似控制访问的服务;
  3. 机器使用root账户;
  4. 预安装docker;

三、集群管理

3.1 创建集群
3.1.1 通过云厂商节点接入(阿里云为例)

操作步骤
1. 登录魔方云容器平台。
2. 在top栏选择全局模式下,在左侧导航栏中,单击【集群】,进入集群列表页面。
3. 在集群列表页面中,单击【添加集群】。如下图所示:

    选择集群的添加方式
4. 在tab栏中,选择云厂商节点,然后选中Aliyun Ecs

5. 填写集群名称。

   集群名称应包含 1-63个字符,可包含数字、小写英文字符或连字符(-);不支持中文字符

6. 成员角色

控制哪些用户可以访问集群,以及他们拥有的对其进行更改的权限。可为集群添加多个成员,并选择角色权限。
名称下拉选择必须先在全局【成员】菜单下添加成员,否则下拉为空;角色有三种权限选择:Ower、Member、Read Only。

注意:删除成员不会删除其项目权限

7. 集群节点池配置

主机池名称前缀:用于主机池的名称。

主机数量:
填写所需的主机数量。建议设置数量为奇数,设置偶数是对硬件的浪费,因为它不会增加仲裁。
主机模板:若有主机模板;则下拉选择主机模板即可;若无主机模板,需要添加主机模板,点击个人中心,选择【主机模板】即可添加模板。
Etcd、Control、Worker:若集群为单节点,则Etcd、Control和Worker必须全选;若集群为多节点,可以自由选择,只需保证在多节点上都有Etcd、Control和Worker即可。

8. 集群选项。自定义Kubernetes集群选项。

表单输入

Kubernetes版本:您可根据需要下拉选择 Kubernetes 版本。
网络组件:支持flannel、calico、canal和weave选择。

Flannel: 最成熟、最简单的选择 ,不支持隔离策略。

Calico: 性能好、灵活性最强,目前的企业级主流,不支持隔离策略。

Canal: 将Flannel提供的网络层与Calico的网络策略功能集成在一起,支持隔离策略。

Weave: 独有的功能,是对整个网络的简单加密,会增加网络开销,不支持隔离策略。

模式:仅支持创建阿里云Flannel下有效。
项目网络隔离:选择是否进行隔离策略。

编辑YAML

注意:使用编辑YAML方式编辑集群选项时,不要使用Tabs,Tabs字符在yaml中不能解析。
3.1.1 添加自有节点

操作步骤
1. 登录魔方云容器平台
2. 在top栏选择全局模式下,在左侧导航栏中,单击【集群】,进入集群列表页面。
3. 集群列表页面中,单击【添加集群】。如下图所示:

4. 在tab栏,选择【添加自有节点】按钮​

5. 填写集群名称。

   集群名称应包含 1-63个字符,可包含数字、小写英文字符或连字符(-);不支持中文字符

6. 成员角色

控制哪些用户可以访问集群,以及他们拥有的对其进行更改的权限。可为集群添加多个成员,并选择角色权限。
名称下拉选择必须先在全局菜单下添加成员,否则下拉为空;角色有三种权限选择:Ower、Member、Read Only。

注意:删除成员不会删除其项目权限

7. 集群选项。自定义Kubernetes集群选项。

表单输入

Kubernetes版本:您可根据需要下拉选择 Kubernetes 版本。
网络组件:支持flannel、calico、canal和weave选择。

Flannel: 最成熟、最简单的选择 ,不支持隔离策略。

Calico: 性能好、灵活性最强,目前的企业级主流,不支持隔离策略。

Canal: 将Flannel提供的网络层与Calico的网络策略功能集成在一起,支持隔离策略。

Weave: 独有的功能,是对整个网络的简单加密,会增加网络开销,不支持隔离策略。

模式:仅支持创建阿里云Flannel下有效。
项目网络隔离:选择是否进行隔离策略。

编辑YAML

注意:使用编辑YAML方式编辑集群选项时,不要使用Tabs,Tabs字符在yaml中不能解析。

8. 单击【下一步】,进行自定义节点运行设置。

9. 自定义节点运行命令

角色选择:一台节点可以对应多个角色,但是每个集群至少需要一个Etcd角色、一个Control角色、一个Worker角色,可根据自有节点需求选择角色。 节点地址:为节点配置公网地址和内网地址, 如果为VPC网络的云服务器,如果不指定公网地址节点将无法获取到对应公网IP。 节点名称:自定义节点显示的名称,不显示实际的节点名。 节点标签:应用于节点的可选标签,标签由输入键和对应的值组成。 ​

说明:
1. 键是必填项,值是选填项;
2. 键名称部分必须由字母数字字符、“-”、“_”或“.”组成,并且必须以字母数字字符(例如“my name”或“my.name”或“123 abc”开头和结尾, 最多 63 个字符。
3. 值名称部分必须由字母数字字符、“-”、“_”或“.”组成,并且必须以字母数字字符(例如“my name”或“my.name”或“123 abc”开头和结尾, 最多 63 个字符。
4. 同一个资源,标签键不能重复,相同标签键(Key)的标签会被覆盖。

3.2 集群总览

操作步骤
已在魔方云容器平台创建集群。当集群状态处于Running或Updating时,集群总览才有数据展示。
集群状态说明如下:

Runing

集群正常运行。

Preparing

集群正在创建,正在申请云资源。

Updating

正在升级集群中。

Removing

正在删除集群中。

Unavailable

当前集群不可用,集群存在异常。

Runing

集群正常运行。

Preparing

集群正在创建,正在申请云资源。

Updating

正在升级集群中。

Removing

正在删除集群中。

Unavailable

当前集群不可用,集群存在异常。

操作步骤
1.登录魔方云容器平台。
2.在全局模式下,选择【集群】菜单,然后在集群列表里点击对应集群的【查看详情】按钮;
在集群模式下,选择【集群】菜单;
3.集群总览。您可查看应用状态、组件状态和资源监控图表。

CPU、Memory和Pods:分别监控CPU、Memory和Pods的使用状态;外环表示预留的使用量,内环表示实际的使用量。
应用状态:显示当前应用的状态,绿色表示正常,红色表示异常。
节点状态:显示当前集群的节点状态。
组件状态:Kubernetes 集群的组件通常部署在 kube-system 命名空间下,包括 scheduler、controller-manager和 etcd 和Nodes等核心组件。
节点使用率:显示使用量前三的节点的内存和CPU使用率。
配置信息:展示当前集群的提供者、版本、节点数、CPU大小、内存大小和创建时间。

集群监控:提供CPU使用率、CPU负载、内存使用率、磁盘使用率、磁盘I/O、网络I/O和网路数据包的资源监控,可自定义时间段选择查看。

Kubernetes组件监控:API Server请求延迟、API Server请求速率、调度失败的Pod、ingress控制连接数。
管理集群

● 升级集群 1. 登录魔方云容器平台。
2. 进入全局集群列表,选择需要升级的集群,下拉右上角菜单,点击【编辑】进行升级;

进入集群总览仪表盘,在面包屑导航栏右上角点击下拉菜单,选择【编辑】进行升级

● 删除集群:入口位置同升级集群。在云平台中创建的关联资源(如卷,负载均衡器和网络)不会自动删除。
● Kubeconfig文件:可以复制文件,然后下载kubectl(如有需要)并运行。
● 备份:将集群备份到local存储。
● 恢复:选择可用的备份进行数据恢复。
● 更新证书:更新集群的证书。提供更新所有服务证书(CA证书不变)和更新单个服务(CA证书不变)两种方式。

工作负载

部署工作负载

前提条件

注意:
使用工作负载相关服务,当前集群状态应为Running状态。

操作步骤

1. 登录魔方云容器平台。
2. 选择所需部署工作负载的集群,在集群下对应的项目进入。

3. 在左侧导航栏中,点击【工作负载】,进入工作负载列表页,点击右上角【部署服务】,如图所示:

3. 在左侧导航栏中,点击【工作负载】,进入工作负载列表页,点击右上角【部署服务】,如图所示:

▶ 基本信息

在“部署服务”页面,填写部署工作负载的基本信息。如下图所示::

主要参数信息如下:
名称:集群名称应包含 1-63个字符,可包含数字、小写英文字符或连字符(-)。
类型:下拉选择工作负载的类型,提供无状态(Deployment)、有状态(StatefulSet)、DaemonSet、Cron定时运行和Job五种类型选择,使用场景如下图所示:

 

无状态(Deployment)

·        定义Deployment来创建Pod和ReplicaSet

·        滚动升级和回滚应用

·        扩容和缩容

·        暂停和继续Deployment

 

 

有状态(StatefulSet)

·        稳定,唯一的网络标志。

·        稳定,持久化存储。

·        有序,优雅地部署和 scale。

·        有序,优雅地删除和终止。

·        有序,自动的滚动升级。

 

DaemonSet

·        运行集群存储 daemon。

·        在每个 Node 上运行日志收集 daemon。

·        在每个 Node 上运行监控 daemon。

Cronjob

·        在给定时间点只运行一次

·        周期性地在给定时间点运行

Job

Job负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。

命名空间:下拉选择命名空间,或创建新的命名空间。您可以使用命名空间实现资源和服务的隔离。

拉取镜像策略:为你的镜像更新选择一个操作策略,提供总是拉取、从不、不存在则拉取三种策略
◆ 总是拉取:总是从远程拉取该镜像。
◆ 不存在则拉取:默认使用本地镜像,若本地无该镜像则远程拉取该镜像。
◆ 从不:只使用本地镜像,若本地没有该镜像将报异常。

容器规格:提供2X、4X、8X、16X和32X快捷选择,如不选择默认2X配置;根据实际需要,可自定义容器规格,
◆ 资源预留:即为该应用预留资源额度,包括 CPU 和内存两种资源,即容器独占该资源,防止因资源不足而被其他服务或进程争占资源,导致应用不可用。
◆ 资源限制:可指定该应用所能使用的资源上限,包括 CPU 和内存两种资源,防止占用过多资源。

端口映射:您需要添加服务端口和容器端口,若类型选择为节点端口,还需要自己设置节点端口,防止端口出现冲突。支持 TCP/UDP 协议。支持四种网络模式如下:

网络模式:
NodePort:所有节点端口均可访问;
HostPort:仅Pod所在节点端口可访问;
集群IP:集群内部访问;
L4层负载均衡器:对接公有云负载均衡服务;

端口映射:您需要添加服务端口和容器端口,若类型选择为节点端口,还需要自己设置节点端口,防止端口出现冲突。支持 TCP/UDP 协议。支持四种网络模式如下:

▶ 监控告警和弹性伸缩

5. 在“部署服务”页面,填写部署工作负载的监控告警和弹性伸缩信息。如下图所示:

(1)workload告警
workload告警:选择是否开启告警。未开启告警,则workload告警信息不可配置。 告警条件:滑动滚动条选择触发告警的条件。
选择告警组:下拉选择工作负载告警组和添加新的告警组,并填写告警规则名称;
(2)工作负载伸缩和告警

说明:
若需要为工作负载/监控或者自定义监控/告警加入弹性伸缩,必须为项目开启监控;
可以自定义添加多个自定义监控或者工作负载监控;

弹性伸缩:选择是否为工作负载伸缩/监控加入弹性伸缩。
◆ 开启弹性伸缩,需填写HPA最大最小副本数,以及在工作负载配置项询问是否加入伸缩;
◆ 关闭弹性伸,将不再询问是否加入伸缩,页面不显示配置伸缩项。 监控项:支持为CPU、内存、网络数据包(Transmit packets)、网络数据包(Receive packets)、网络I/O(Transmit)、网络I/O(Receive)、磁盘I/O(Write)、磁盘I/O(Read)。
加入伸缩:询问是否加入伸缩,开启后,需填写触发伸缩阈值。
工作负载告警:询问是否开启告警。
◆ 关闭工作负载告警,告警配置项不显示;
◆ 开启工作负载告警,填写配置信息;下拉选择告警组,并为告警组命名;选择触发的条件和填写触发的告警阈值;选择持续时间;

注意:
Workload告警不需要开启监控;工作负载告警必须开启当前集群监控才可以配置;

(3)自定义监控/告警

注意:
需要为当前集群开启监控才可以配置自定义监控和弹性伸缩;

图标名称:自定义需要监控的图标名称。
表达式:先下拉选择workload,在下拉选择该workload下面的表达式, 表达式支持加减乘数函数和速率等。选择完表达式,右侧将会出现该表达式的图表信息。
Y轴单位:填写Y轴单位。
加入伸缩:开启后,需填写触发伸缩阈值,右侧图标可根据阈值预览变化效果;
自定义监控告警:询问是否开启告警。
◆ 关闭工作负载告警,告警配置项不显示;
◆ 开启工作负载告警,填写配置信息;下拉选择告警组,并为告警组命名;选择触发的条件和填写触发的告警阈值;选择持续时间;

(4)添加自定义指标

添加自定义指标,将端口暴露,支持http和https协议。

▶ 自定义配置

命令:在默认情况下,镜像会运行默认命令,如果想运行特定命令需配置命令。
环境变量:环境变量是指容器运行环境中设定的一个变量,为创建的工作负载提供极大的灵活性。以添加键值对的形式来设置环境变量。
节点调度:可选两种情况进行节点调度配置。
● 指定节点运行所有Pods。下拉选择当前集群节点。
● 为每一个Pod自动选择符合调度规则的节点
通过 Node 节点的 Label 标签进行设置,节点调度支持必须、最好、首选。
◆ 必须,一定要满足,您可以定义多条必须规则,但只需满足其中一条。
◆ 最好,不一定满足,您可定义多条最好规则。
◆ 首选,首先满足,您可定义多条首选规则。
设置调度容忍,可以为每一个类型的节点调度规则添加多个调度容忍,根据节点上运行的 Pod 的标签(Label)来进行调度,匹配的表达式有Equals和Exists。可选的效果有NoSchedule、NoExecute和(Custom)

健康检查:支持存活检查(liveness)和就绪检查(Readiness),根据用户需要,定时检查容器健康状况。
● 存活探针: 监测到容器实例不健康时,重启应用。
● 就绪探针:监测到容器实例不健康时,将工作负载设置为未就绪状态,业务流量不会导入到该容器中。

请求类型和配置说明如下:

 

HTTP请求

即向容器发送一个 HTTPget 请求,支持的参数包括:

  • 路径:访问 HTTP server 的路径
  • 端口:容器暴露的访问端口或端口名,端口号必须介于 1~65535。

 

HTTPS请求

即向容器发送一个 HTTPSget 请求,支持的参数包括:

  • 路径:访问 HTTPS server 的路径
  • 端口:容器暴露的访问端口或端口名,端口号必须介于 1~65535。

 

 

TCP端口检查

即向容器发送一个 TCP Socket,kubelet 将尝试在指定端口上打开容器的套接字。 如果可以建立连接,容器被认为是健康的,如果不能就认为是失败的。支持的参数包括:

  • 端口:容器暴露的访问端口或端口名,端口号必须介于 1~65535。

 

状态码检查

通过在容器中执行探针检测命令,来检测容器的健康情况。支持的参数包括:

  • 命令行:用于检测容器健康情况的探测命令。

标签/注释:对资源进行额外的元数据设置,例如 Label 和 Annotation。
6. 点击右上角【部署】,完成对工作负载的部署。

工作负载管理
升级

操作步骤

在Docker Hub上发布新版本的应用程序镜像后,您可以将运行旧版本应用程序的所有工作负载升级到新版本。
1. 在项目视图中,点击【工作负载】,进入工作负载列表页面;
2. 找到您要升级的工作负载,然后在操作列点击【编辑】;
3. 将Docker镜像更新为Docker Hub上应用程序镜像的更新版本;
4. 更改你所需要更改的其他任何选项;
5. 单击右上角【升级】按钮;

结果:
工作负载开始根据您的规范升级其容器。 请注意,扩大部署规模或更新升级/扩展策略不会导致Pod重新创建。

回滚

操作步骤

有时出于调试目的或因为升级未按计划进行而需要回滚到应用程序的先前版本。
1. 在项目视图中,点击【工作负载】,进入工作负载列表页面;
2. 找到您要回滚的工作负载,然后在操作列下拉菜单中点击【回滚】;
3. 选择要回滚的修订版。
4. 单击【回滚】;

结果: 您的工作负载将还原为您选择的先前版本。 等待几分钟,以完成操作。

删除容器

注意:
只有一个容器作为主容器时,不能被删除。

操作步骤

1. 在项目视图中,点击【工作负载】,进入工作负载列表页面;
2. 找到您要删除的容器所在的工作负载,然后在操作列下拉菜单中点击【编辑】;
3. 进入容器选择页面,找到需要删除的容器,点击【删除】 ;

访问入口

访问入口是允许访问到集群内 Service 的规则的集合,您可以通过配置转发规则,实现不同 URL 可以访问到集群内不同的 Service,可以为工作负载添加入口。 操作步骤

操作步骤

1. 登录魔方云容器管理平台。
2. 在项目视图下,在左侧导航栏中,单击【访问入口】进入访问入口列表页面。
3. 在访问入口列表页,单击右上角【添加访问规则】

4. 新建访问入口,如下图所示:

访问入口名称:自定义;
命名空间:选择命名空间,根据实际需求进行选择;
规则:根据实际需求进行设置;
● 域名:默认自动生成sslip.io域名,可自定义域名;
● 目标后端:填写目标后端访问路径、提供服务和工作负载两种目标后端类型选择、监听端口默认为80,经根据实际需要选择;

SSL/TLS证书:选用合适的服务器证书能够确保访问安全,根据实际需要为证书添加域名;
标签和注释:用于调度决策的键值对。
后端重写请配置注释: 键: nginx.ingress.kubernetes.io/rewrite-target,值: /。

5. 点击【保存】,完成访问入口的创建。
6. 访问入口列表点击名称,查看相对应的访问入口详情;
查看访问入口规则、证书和标签注释;

7. 访问入口管理
编辑:对当前访问入口进行任何配置的编辑,支持视图化文本编辑和编辑YAML两种方式;
删除:删除当前访问入口;
克隆:当前访问入口的复制编辑,方便快捷;

应用市场

市场设置

▶ 应用市场管理

1. 登录魔方云容器平台。
2. 左侧导航栏选择展开【应用市场】。
3. 单击【市场设置】,应用市场列表。
4. 应用市场可查看该应用市场的范围、名称、市场地址、分支和操作; 操作可进行编辑、克隆和删除;

▶ 添加应用市场

点击应用市场列表右上角按钮【添加应用市场】;

6. 应用市场信息配置

名称:填写该应用市场的名词;
市场地址:输入应用市场的URL地址;
使用私有应用市场:根据实际需要选择;
分支和范围:根据实际需要选择分支和范围;

7. 点击【创建】,成功添加应用市场;

应用列表

1. 登录魔方云容器平台。
2. 左侧导航栏选择展开【应用市场】。
3. 单击【应用列表】,进入应用列表页面。

应用列表:展示您在应用市场启动的应用。
应用管理:提供克隆、升级、回滚、日志查看和删除应用;
状态:应用的运行状态和应用的版本信息;该应用的工作负载状态;

4. 点击应用的名称,进入此应用的详细信息页面;

基本信息:包含应用的版本、命名空间和创建时间;
注释:关于如何使用此应用的说明;
状态:当前应用程序的状态,包括状态原因、最后更新等;

应答:自定义应用的应答;
端点:此应用的公共端口;
工作负载:此应用的工作负载,可查看该工作负载详情;
Ingress规则:此应用创建的入口规则;
服务:此应用创建的服务;
卷:此应用创建的持久化声明(PVC);
密文:此应用相关的密文(Secrets);
配置映射:于此应用关联的配置映射;

应用市场

1. 登录魔方云容器平台。
2. 左侧导航栏选择展开【应用市场】。
3. 单击【应用市场】,进入应用市场列表页面。

4. 点击应用名称,对应用进行配置。以wordpress为例,如下图所示:

启动项页面分三块:
◆ 此应用的简单概述;
◆ 此应用的详细描述;
◆ 应用的配置选项和预览,配置选项因应用不同而不同、预览为该应用配置的YAML文件;

5. 点击tab栏菜单【配置选项】,对应用进行配置;

名称:自定义该应用的名称;
模板版本:下拉选择该应用的历史版本;
命名空间:为该应用选择命名空间;
其他配置:应用不同,而配置选项不同,根据需要配置其他选项;
预览:预览提供的配置选项的YAML信息面板查看和编辑;

6. 点击右上角【启动】,则启动该应用;
7. 返回【应用列表】,查看该应用的具体信息;

白皮书

背景

在这个需要更深入“云”的时代,越来越多的企业借助云计算为企业提质增效,实现产业升级,有助于企业聚焦于核心业务,从而更快适应变化多端的市场竞争。但随着应用程序在云中大规模部署,导致云环境的复杂性不断增加,越来越多的互联网应用在云上遇到挑战,企业需要更高效的应用管理和更弹性的计算架构。在众多的实践和方法论中,CloudNative——云原生的概念越来越清晰,以Kubernetes为代表的云原生技术让“云原生”从一个模糊的概念,强势成长为通用技术。魔方云在“云”浪潮的推动下破云而出。

云原生是当前云计算架构的自然进化。企业业务系统本身的不断膨胀,导致结构更加错综复杂,业务洪峰频繁遭遇挑战,运维的压力已经远远不能靠砌人墙的方式来解决问题,而以容器为基石,通过微服务化的改造,融合DevOps理念,能够帮助企业快速构建更加适合云的敏捷应用服务。正因如此,云原生应用等技术概念的诞生,对企业数字化转型产生了颠覆性影响,魔方云可以更好地帮助业务和应用生长于云、扎根于云,从而真正享受云所带来的便捷和可持续服务。

云原生构架
传统IT架构面临的问题

• 开发与运维之间的鸿沟
• 云上云下资源难以协同
• 集群资源利用率不高
• 应用监控出现盲区
• 日志管理缺失
• 难以实时弹性伸缩

产品介绍

LinkCloud魔方云,是基于以docker为代表的容器技术,为企业提供镜像构建、DevOps、多云容器管理的新一代企业PAAS平台。魔方云容器服务使用目前业界最广泛使用的编排工具 Kubernetes 对容器进行编排,轻松地管理各种环境的Kubernetes,满足IT需求并为DevOps团队提供支持。

魔方云用户可以通过自定义添加节点集群来创建Kubernetes集群,也可以使用GKE,AKS和EKS等云Kubernetes服务来创建集群,用户还可以导入和管理现有的Kubernetes集群。

魔方云致力于打造以应用服务为中心的云计算平台,帮助企业实现持续创新,为开发与运维者提供高效敏捷的生产环境以及优质的使用体验。魔方云提供了一个直观的用户界面来管理他们的服务容器,用户不需要深入了解Kubernetes概念就可以使用魔方云,并且包含应用市场,支持一键式部署应用。

产品构架

总的产品架构如下图所示:

功能模块构架图:

总的产品架构如下图所示:

基础构架资源:魔方云为运行容器化的应用提供了一层灵活的基础设施服务。 魔方云可以使用任何公有云或者私有云的Linux主机资源。Linux主机可以是虚拟机,也可以是物理机。

容器编排引擎:通常包括容器管理、调度、副本维护、负载均衡、滚动升级、集群定义和服务发现等。通过容器编排引擎,容器被有机的组合成微服务应用,实现业务需求。

应用市场:应用市场吸纳了各类优秀的企业级软件和开源软件,为开发者提供了国内全面的镜像资源,可视化的界面与开箱即用的优质体验。

应用管理:魔方云实现企业应用全生命周期的管理服务,满足各种类型和不同规模的企业应用层需求。

核心概念

使用魔方容器服务,会涉及到以下一些基本概念:

产品功能

集群管理

• 支持同时管理多个Kubernetes集群,集群之间相互隔离。
• 不同集群可以设置不同的网络模式,包括flannel、Calico、Canal等。
• 通过平台创建的Kubernetes集群,集群的系统软件,可通过图形界面在线升级或回退,不影响集群环境中业务的使用。
• 集群的添加:

添加集群示例(以Hybird为例)
前提条件:必须打通阿里云VPC和IDC之间的高速通道

多租户

• 多租户隔离,不同租户可以创建使用独立的集群。
• 集群内的租户可以按需配置独立的管理员、用户和自定义角色,并相互隔离。

日志

• 提供日志收集代理,自动收集容器和应用程序的日志。
• 日志的搜索功能快速查找历史记录,帮助更高效运维。
• 集日志的收集、存储、搜索等功能于一体,形成完整的日志解决方案。
• 提供Elacticsearch和CubePaaS两种收集日志的方式,增强日志的聚合。 Elacticsearch为外置的日志集成平台,需要填写访问地址; CubePaaS为内置的日志集成平台,开启即用,方便快捷;
• 日志系统能够在集群层面、项目层面分别对应不同的日志平台,以满足用户部门对日志功能使用的灵活性,以满足不同应用场景需求。

块存储

分布式存储系统,为业务与数据在集群内漂移提供了自由保障,满足企业对于不受约束的系统环境要求。同时,平台仅保存迁移被改动的数据,而非整体增加数据量,实现增量备份。

魔方云块存储的功能:
1.每个块设备单独创建一个专用的存储控制器。每个存储控制器上只有一个volume。
2.根据需求自定义volume的大小,为容器和虚拟机创建块存储。
3.每个卷都有自己的控制器,因此我们可以为每个卷升级控制器和副本容器,而不会导致IO操作明显中断
4.指定备份操作的计划。可以指定这些操作的频率(例如每天、每小时等),以及执行这些操作的确切时间(你可以指定任意一个时间来执行这个操作),以及保留多少个循环快照和备份集。

kubectl web命令行

kubectl是Kubernetes集群的命令行工具。魔方云对更专业的运维操作提供了kubectl web命令行窗口,更深层地管理容器。

集群告警监控

1.告警监控对资源进行自动化地监测与反馈,在界面上实现方便管理。
2.支持集群和租户不同层级的监控设置。
3.可以通过图形界面设置告警目标、不同告警规则按需设置告警目标。
4.告警规则支持电子邮箱和企业微信,同时提供 Webhook扩展方式;
5.支持临时暂停、恢复,初始发送延迟、发送间隔等高级设置,以按需控制告警信息的发送;

应用市场管理

1.通过容器化应用市场实现模板化展示和快速部署。
2.提供应用的多种版本。
3.可以设置不同的应用市场路径,每个路径指向独立的应用市场。
4.提供图形界面,方便查看应用和管理应用。
5.支持搜索和分类展示应用。
6.支持自定义配置应用的交互参数。

应用市场吸纳了各类优秀的企业级软件和开源软件,为开发者提供了国内全面的镜像资源,可视化的界面与开箱即用的优质体验。

DevOps

1.建立标准化、一致性的开发、测试和运维环境,实现敏捷开发。
2.应用全生命周期管理,实现私有云环境应用托管、开发、自动化运维等。
3.基于Docker和微服务,实现弹性伸缩、灰度发布等能力。
4.支持公有Github、Gitlab和私有Gitlab代码库的对接。

魔方云打通了从代码提交,自动测试,自动构建镜像,自动部署镜像的全流程,高效替代业内部署复杂、迭代缓慢的传统方式,实现从代码变更到代码构建,镜像构建和应用部署的全流程自动化。

在魔方云容器服务上部署服务,开发人员在 Git 或其他代码平台提交新代码后,可立即进行构建、测试、打包集成,在持续集成的基础上,将集成的代码部署到预发布环境和现网环境上。

持续集成与持续交付过程如下图所示:

优势

混合云服务

提供混合云解决方案,支持无缝集成基础设施和云资源,提高业务连续性;
为基础设施提供安全可靠的容灾、备份环境;
通过高速通道连接VPC和本地IDC,实现数据备份;

导入已有容器PaaS平台

支持快速接入其他容器平台,提高管理效率、保障应用的高稳定和高可靠性。

日志

集群和项目层面对应不同的日志平台,满足不同应用场景需求,并增 强日志聚合和搜索,方便快捷查找。

块存储

用于有状态应用(如数据库等),快照功能允许将volume回滚到特定时间点,支持增量备份到云端。

应用场景

敏捷开发快速上线

平台提供代码构建、镜像打包、服务快速部署、灰度发布、自动伸缩、负载均衡等持续交付工具链,使开发者专注业务的开发和测试无需关注运行环境和运维,加速应用的快速迭代和上线。
传统 DevOps 体系中,开发测试和生产环境往往是异构的,同时服务部署方式也缺乏标准化的流程,这些成为实现高效的应用持续集成/持续交付平台的巨大鸿沟。而基于 Docker 及 Kubernetes 构建的 容器编排与调度平台,则可统一应用的部署环境,提供标准化的部署以及更新流程。

微服务构架应用

容器为微服务架构的"天然"的载体,借助Kubernetes集群服务,容器的轻量级、统一部署特性为解决各个微服务统一管理、调度、部署带来了全新的思路,并且大大降低了服务、版本之间的依赖关系,让同一个微服务在不同环境中保持良好的一致性。可将现有的大型应用系统通过微服务架构拆成多个独立模块,每个模块使用一个应用镜像作为模板进行微服务部署;微服务架构可轻量级构建冗余,可扩展性强,非常适合构建云原生应用程序。

快速弹性收缩

对于需求变化量大,并且需要快速启动几百甚至上千台机器的应用,当遇到业务高峰,容器平台将会识别现有性能的承载性,在不足的情况下自动获取必要的计算资源。通过Kubernetes集群服务,借助容器快速启动、快速部署、海量并发的能力, Kubernetes集群服务可以非常好地满足业务的突发需求。

网银互联荣获智云奖——2019年度最佳云服务提供商

企业数字化的时代已经来临,各行业面临智能化、云端化的升级。工信部印发了《推动企业上云实施指南(2018-2020年)》,从实施上云路径、强化政策保障、完善支撑服务等层面为推进企业上云提出了指导。业务云化越来越成为企业需要迫切思考的问题。

10月15日,由中国云体系产业创新战略联盟、上海市软件行业协会、国际云安全联盟和拓普会展主办的第二届企业云服务大会在上海举行,大会邀请了数百名企业CIO和信息化负责人,围绕云计算发展趋势及实践、企业上云转型应用、数智赋能产业升级等话题,分享经验和理念,探索前沿和未来。

在本次企业云服务大会上,由上海市软件行业协会秘书长杨根兴为网银互联颁发智云奖“2019年度最佳云服务提供商”。此殊荣经产业联盟、行业协会、媒体等机构严格甄选而出,是对网银互联产品与服务的肯定。

网银互联凭借专业服务能力、多云管理能力和企业组网能力,为企业提供云咨询、云迁移、云管理和云应用全生命周期云管理服务,帮助企业解决技术选型、架构设计、风险评估、上云迁移、资源编排、智能监控等难题,轻松实现业务云化,搭建敏捷、高效、经济的云化IT架构。

在2019年,网银互联已为电商、教育、大数据、互联网、金融等多个行业数十家企业提供专业云服务。此次,网银互联荣获“2019年度最佳云服务提供商”,将激励着我们继续打磨产品、提升服务,帮助更多的客户上好云、用好云和管好云。