Skip to main content

· 13 分钟阅读

Choerodon猪齿鱼平台使用微服务架构进行开发,部署在Kubernetes 扩展中,并且服务新功能开发完成后会被依次部署到暂存环境测试,UAT 环境验收和生产环境使用。在这多个环境的部署过程中,猪齿鱼平台只需要一次CI生成的包,便能实现服务部署的“因地制宜”。

需求

猪齿鱼平台集成的GitLab用于进行CI的过程,在微服务程序中经过Gitlab CI的docker_build和chart_build步骤之后,对应一次成功的CI过程,会有一个可以进行部署的Helm的发行包。

一般情况下,不同环境下基础设施如数据库的地址是不同。而部署服务的需求是一次打包生成的安装包可以在不同的环境进行部署而不需要对源代码重新打包。

spring:
datasource:
url: "jdbc:mysql://localhost/demo_service?useUnicode=true&characterEncoding=utf-8&useSSL=false" # 本地数据库
username: demo
password: demo

方案基础

猪齿鱼微服务后端环境变量方案的本质上是Springboot的环境变量机制和头盔环境变量机制的结合。

所以在此之前,先聊一聊SpringBoot 和头盔所提供的支持。

SpringBoot环境变量支持

SpringBoot支持外部化配置,允许使用者通过属性文件,YAML文件,环境变量及命令行参数对服务进行外部化配置。同时允许 @Value和@ConfigurationProperties注解的方式对上述变量进行访问。

@Value方式:

@Value("${JAVA_HOME}")
private String javaHome;

@Value("${spring.application.name}")
private String appName;

@ConfigurationProperties 方式:

@Component
@ConfigurationProperties(prefix = "services.gitlab")
public class GitlabConfigurationProperties {
private String password;
private Integer projectLimit;

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Integer getProjectLimit() {
return projectLimit;
}

public void setProjectLimit(Integer projectLimit) {
this.projectLimit = projectLimit;
}
}

在SpringBoot官方文档中,SpringBoot提供了多于十种配置环境变量的方式,并列出他们所配置的环境变量的优先级。从文档中可以看到,对于应用程序而言,系统环境变量的优先级高于应用内的配置文件所配置的值。

Helm环境变量支持

在猪齿鱼微服务中,项目根目录下会有一个 chart 目录用于定义此服务如何打包成 Helm Chart 包,这个目录下就是 helm 的 chart 文件结构:

devops-service/      
Chart.yaml # 包含关于chart的的信息的YAML文件
LICENSE # 可选:包含chart的许可证信息的纯文本文件
README.md # 可选:chart的README文件
requirements.yaml # 可选:列出依赖信息的YAML文件
values.yaml # 这个chart的默认配置值
charts/ # 包含这个chart所依赖的多个chart
templates/ # 这个目录下包含了多个模板文件以结合配置值生成有效的Kubernetes manifest文件
templates/NOTES.txt # 可选:包含简短使用提示的纯文本文件

更多关于chart下文件的信息,可点击这里

Helm的模板文件主要是使用 Go Template Language 以及一些其他补充函数。所有的模板文件存在于 templates 目录下,在helm对chart包进行渲染的时候,模板所用到的配置值会有两个来源:chart包内的values.yaml和Chart.yaml文件及命令行提供的配置值。

chart包内提供的配置值:

.Release # 和release相关的一些属性值,如当前部署的release的名称,命名空间
.Files # 一个包含其它没有被指定(未被上文提到的)的文件的map结构,可以用于访问其它文件内的内容
.Chart # 访问 Chart.yaml 文件中的一些值,只能访问这个文件预定义的值,其它值会被忽视
.Values # 访问 Values.yaml 文件中定义的配置值

更过关于预定义变量的文档,可点击这里

命令行提供的配置值:

# 命令行直接指定配置值的方式,--set:
$ helm install nginx --repo localhost --name nginx-release-zmf --set deployment.name=zmf-naginx-dep

# 命令行使用--values指定values.yaml文件的方式
# 在命令行的配置文件名称可以不为values.yaml
# 命令行指定的文件会和chart包内的values.yaml文件合并
# 且会覆盖values.yaml内同名配置项的值
# 如下:
$ helm install nginx --repo localhost --values=myvals.yaml

在模板文件中使用模板文件定义的语法和内置函数可以访问到以上所提供的配置值,下列是一个Kubernetes Deployment文件的模板示例:

apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: {{ .Values.deployment.name }}
labels:
app.kubernetes.io/name: {{ .Values.deployment.name }}
helm.sh/chart: {{ include "nginx.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.deployment.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.deployment.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
{{- range $name, $value := .Values.env.open }}
{{- if not (empty $value) }}
- name: {{ $name | quote }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}

在模板文件中可以使用 {{ .Value.property.name }} 的方式直接访问配置值。

使用模板文件的方式可以使得 Helm 根据命令传入的参数渲染chart包以部署在不同的环境中。

更多的的模板语法,可点击这里

方案实现

在重温SpringBoot和Helm提供的环境变量方案之后,再来详细看看猪齿鱼微服务是怎么将它们结合的。

此处以devops-service的0.18.0版本为例, 说明这个过程。

猪齿鱼微服务的打包

打包过程分为三步:

  • maven 打包 SpringBoot 项目为 JAR 包。在项目被打包生成JAR包后,JAR包中实际上只有 bootstrap.yml 和 application.yml 两个配置文件,也可以说只有一份配置文件,猪齿鱼微服务后端并没有采用多种配置文件进行切换的方式。resources文件夹下内容如下:

1.png

  • docker 根据项目根目录下docker目录下的配置生成包含 JAR 包的镜像文件。

  • helm 从源代码中的chart目录下的文件夹(在下图中是devops-service目录)生成 chart 包。

在打包的步骤结束后,一个可以在Kubernetes集群中进行部署Helm Release就出炉了。

注:以上目录结构由根目录下的 .gitlab-ci.yml所定义。

猪齿鱼微服务环境变量的设置过程

在/chart/devops-service/values.yaml文件中,有一部分如下图所示:

这里定义了一部分由 env.open开头的配置项。

在/chart/devops-service/templates/deployment.yaml文件中,有一部分如下图所示:

在图中的第26行,通过模板的语法访问了 values.yaml文件中定义的 env.open 的值,而range函数是将 env.open 所代表的 map的键值对赋值到 name 和 value 中,并且在内层判断 value 不为空就为模板添加 28 行和29 行所示的结构。

结合文件中上下文,可以发现,这是在Kubernetes的对象文件中,定义docker容器的环境变量的位置。也就是说,这里通过模板语法读取helm所提供的配置值,定义kubernetes中部署的Deployment中的容器的系统变量值。而猪齿鱼的微服务最终是通过helm部署helm release而运行在Kubernetes的容器中的,此时运行在容器中的SpringBoot应用就能够读取到系统环境变量,由于上文提到的SpringBoot配置项优先级的关系,系统环境变量会覆盖 JAR 包内默认提供的值。

所以,在猪齿鱼微服务打包完成之后,即可使用helm install或者 helm upgrade命令在命令行传入根据环境所配置的values以部署同一个服务于不同的环境中。

最后,再来一起回顾一下整个过程:

从方案看猪齿鱼平台实现多环境部署

在理解猪齿鱼如何实现后端一次打包生成的包,可以多环境部署之后,再看看猪齿鱼平台如何运用这个方案实现这个多环境部署的过程。

第一步:猪齿鱼平台使用者选择一个服务的版本,根据所需部署的环境,填写对应的values进行部署:

第二步:猪齿鱼平台的 devops-service 收到请求后对参数进行校验,异步处理部署请求,为部署的一个release在环境对应的GitLab仓库中生成一个release开头的文件,这个文件中存储这次部署的所对应的版本信息、名称信息以及values值。

第三步:choerodon-agent会去拉取环境库解析文件,并根据文件的新增或更新在Kubernetes集群执行对应的helm install或helm upgrade操作。

经过以上三步,猪齿鱼平台的使用者部署或升级的实例会在对应的Kubernetes集群中安装或升级。

至此,猪齿鱼平台支持一个服务在多个环境部署的实现方式也就理清了。

更多关于猪齿鱼平台GitOps运用的详细情况,可阅读《Choerodon猪齿鱼 Agent——基于GitOps的云原生持续交付模型》。

更多关于Choerodon猪齿鱼的相关文章,点击蓝字阅读 ▼

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 5 分钟阅读

1.jpg

  • 时间:2019年11月15日
  • 地点:广州阳光酒店
  • 指导单位:上海市经济和信息化委员会等
  • 主办单位:dbaplus社群
  • 支持单位:粤港澳合作促进会 金融专业委员会

▧ 收获1:主流数据库选型、架构设计、迁移实践,一网打尽!

时下数据库可选种类众多,成本低、灵活性强的开源数据库无疑成为了越来越多企业的新尝试。至于如何进行选型、设计、迁移与应用,希望Gdevops广州站能给到你答案。

  • 中国电信甜橙金融 创新中心总经理 张小虎 《分布式数据库能力验证与落地实践》

  • 腾讯 云数据库总负责人 丁奇(林晓斌) 《云上MySQL产品研发和运维的挑战与实践》

  • 贝壳找房 技术总监 肖鹏 《数据库选型那些事儿》

  • 腾讯 云数据库产品副总监 邵宗文 《图数据库及其应用场景解读》

  • SequoiaDB巨杉 数据库研发副总裁 许建辉 《云架构下的分布式数据库设计实践》

  • 爱可生 技术总监 明溪源 《金融行业MySQL高可用实践》

  • 《Oracle/MySQL DBA工作笔记》作者 杨建荣 《迁移到MySQL的架构和性能探索》

  • 微众银行 数据库运维经理 胡盼盼 《微众银行Redis应用实践》

▧ 收获2:DevOps与AIOps的落地,互联网、金融、电信行业之间有何异同?

这两年,DevOps和AIOps从概念逐渐落地开花,尤其在一些互联网大厂和传统名企的成功实践之下,可学习借鉴的案例越来越多。如果你的企业还困扰于不知从何落地,不妨来Gdevops广州站看看有没有合适的范例参考?

  • 微博 广告大数据团队负责人 彭冬 《智能运维:从0搭建大规模分布式AIOps系统》

  • 新炬网络 运维产品部总经理 宋辉 《打通任督二脉:一体化智能运维平台建设与落地》

  • 中国联通大数据 技术部平台组核心技术负责人 余澈 《运营商的大数据运维及集群治理实践》

  • 阿里 搜索事业部技术专家 施立 《阿里搜索数据化DevOps和AIOps探索与实践》

  • 前汇丰银行 DevOps负责人 周纪海 《DevSevOps在国际大型银行的落地和实践》

  • JFrog 高级架构师 高欣 《基于Kubernetes的DevOps实践之路(拟)》

议程安排

agenda.jpg

Choerodon猪齿鱼社区 · 专属优惠

名额有限,马上扫码报名吧~

qr-code.png

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 20 分钟阅读

翻译 | 高钰淋

本文翻译自《From Scrum to Kanban–A Team’s Journey》,以第一人称视角讲述了移动广告公司Marchex的团队Kanban过渡经历,从改变动机,到过渡过程,再到实践经验,希望能给大家带来一些关于团队敏捷实践的启发。

Marchex是一家移动广告技术公司,2014年团队从支持一个已经运行了7年以上的产品转变去开发一个全新产品,当时Scrum实践的效率并不高,想要成功执行sprint计划越来越难,于是公司决定从Scrum转换到Kanban,看看它是否有助于缓解团队在sprint计划中出现的一些问题。

有关Scrum和Kanban的区别,可阅读《Kanban VS Scrum:哪个是最好的敏捷项目管理框架

Marchex和敏捷

为了使团队的变更最小化,我们在从一个产品过渡到下一个产品时,尽量保持基本的Scrum结构,但事实证明,这个计划并不像我们所希望的那么顺利。

Marchex对敏捷并不陌生,整个产品组织都以某种形式采用了敏捷,不过有些方法上的不同。2014年,在我们过渡到Kanban之前,我的团队看起来像是XP + Scrumban的组合。我们采用了一些标准的XP实践,例如TDD、DailyStandups、结对编程、演示和定期回顾。我们还使用白板通过看板跟踪我们的sprint进度。到了2014年夏天,我们开始采用新的敏捷范例。

向Kanban过渡的动机

不熟悉的领域和技术

第一个面临的问题是我们的故事定义和故事点的准确性。例如,我们有一组关于创建新服务的故事,其中一个故事被定义为为这个新服务创建一个API。这个故事的接受标准是模糊的,即创建一个客户端应用程序可以用来获取和推送数据的API。一些流程会被归纳在一个状态中,难以准确识别进度,比如团队与内部客户交谈,设计解决方案等,在整个故事中全部属于“处理中”这一状态。再者,我们没有以前的数据比较来帮助我们使用准确地判断故事的大小,所以我们的sprint计划中经常有未完成的故事。因为一个又一个sprint的故事都没有按计划交付,让团队十分受挫,日复一日,周复一周看着同样的故事处于同样的状态,让人感觉陷入了困境。

波动积压

另一个问题是我们不稳定的任务积压。对于从瀑布式转换到敏捷的团队,一周冲刺似乎是短暂而紧迫的。然而,我们发现在不断发展的新产品开发业务需求中,每周的冲刺时间过长。我们的业务开发团队实时与潜在客户讨论产品,并进行反馈,相应的,我们也必须不断调整产品需求列表的优先级,所以有时感觉我们每天都在改变优先级。

在这种环境下,sprint计划显得过于笨拙,不再适合我们的业务需求。所以在sprint边界的刚性、不断变化的backlog和不断发布特性增强和健壮性的需求之间,需要一个新的范例。

变更需求

在几次回顾之后,我们发现自己无法完成sprint计划,于是开始寻找如何缓解这些问题的方法。我们将电子Scrum板移到站立空间的一个大白板上,使任务更加显眼。另外,我的团队从一个单独的开发团队(主要是独立工作)变成了三个团队中的一个,3个团队都被要求开发和交付这个新产品。

我采访了一位协作团队的开发人员,问她对Scrum到看板的转变有什么看法。她说她更喜欢看板风格有两个原因:团队只关注待办事项列表中最重要的1或2个故事,当它们完成时,就会转移到下一个故事。第二是她喜欢从积压的故事中挑选最重要的故事,然后完成工作。她还提到,他们团队的过程更容易管理,因为故事范围很小。在与她交谈之后,我确定了团队的正确方向。

不需要改变的事情

当我们考虑脱离Scrum时,我们坚信有一些实践对团队来说仍然是有效的。众所周知,Scrum是一种组织项目的方法,而XP实践主要是如何开发代码。XP的拥护者经常采用Scrum+XP。同样,我们决定继续使用XP实践,而使用Kanban作为结构:

  • 结对编程——2014年团队采用了结对编程方法,我们发现,在采用新的Scala语言和学习搜索范式时,这两种方法是成对的。编程是保持团队生产力的必要条件,我们每天都在会上安排大家组队,尽量一组人一起合作直到完成一个故事。
  • 测试驱动开发——几乎所有的新开发都是通过测试驱动开发创建的,我们认为这种实践仍然是开发软件的最佳方式。
  • 回顾——这是一个永远不会消失的敏捷标准实践,它是我们持续改进质量的方法。
  • 故事点——我们改变了我们的故事点定义,但仍坚持使用点来估计故事的大小。我们会讨论一个故事,就接受标准达成一致,然后通过小组投票分配点数。

过渡过程

我与团队讨论了迁移到Kanban的概念,并组织了一个Lean Coffee (leancoffee.org)风格的会议,以征求反馈并解决团队的关注点。

精益咖啡风格的会议通过要求参与者提交讨论主题,明确地征求每个人的反馈,然后通过分组投票确定优先级。会议中大家认为最大的问题是故事的规模,如果所有的故事都更小、更一致,Kanban将工作得更好。在那次会议之后,我们决定采用以下流程来支持我们的新看板模型:

  • 每周一次1小时的会议计划。
  • 每周五进行1小时的回顾。
  • 如果我们在周中(也就是下周一计划会议之前)故事量开始减少,我们会在每日站立会(daily standup)上做一个小计划。
  • 新的故事点“度量参考”:1个故事点=1/2的理想工作日为一对程序员。以前,我们的量表是1个故事点= 一对程序员的理想工作日。这种缩减的规模帮助我们保持我们的故事更小,因为5点故事意味着它应该在大约2.5天内完成,而不是5天。
  • 如果在计划过程中,我们给一个故事分配了超过8个点,我们会把它分成2个或更多的故事。
  • 每日站会将专注于讨论故事状态,并将它们移动到Kanban上,而不是绕着圈子给出状态。这意味着我们的周期更短,更专注于眼前的任务。这一变化似乎是我们转向Kanban的自然延伸,因为它强调了Kanban对工作和循环的重视,时间更为明显。
  • 我们在迭代期间进行结对。我们不一定每天都会改变,尤其是在故事进行到一半时,但我们每天都要讨论到每一对。考虑到我们的团队规模,只花了一两分钟。
  • 我们保留了第16分钟的概念——也就是说,如果有人想更深入地讨论某个问题,我们会把它写在白板上,然后把它放在第16分钟的讨论中。在讨论完板上的故事状态和分配对之后,我们在第16分钟讨论项目。
  • 团队中每对开发人员的WIP为1,即6名开发人员“开发中”的WIP为3。我们还为“QA”列指定了一个WIP为3。

我们在sprint边界完成了从Scrum到Kanban的过渡,也就是说,我们完成了当前的sprint,在下一次计划会议上,我们创建了一个新的故事点“度量参考”,并开始像Kanban团队一样进行计划。这意味着我们只需要为下周的故事设定范围和计划,因为计划是在周一上午进行的。

对小故事的更改解决了我们现有的一个问题——尚未指定的故事。通过讨论范围较小的故事,我们自然也倾向于收紧接受标准,由于我们的故事是8点(4天)或更少,所以我们以更小的粒度讨论了故事的目标。

例如,与简单地为新服务创建API的故事不同,新故事被分解为更小的故事,如初始化、发布、验证、日志记录和最后定稿。

在为每个较小的故事处理我们的验收标准时,由于范围更小,我们的验收标准在范围和粒度上也变得更加适中。例如,为日志记录的故事创建接受标准变得非常具体,相反,为创建API的故事创建的接受标准的类型要模糊得多。的确,创造更小的故事也意味着创造更多的故事,所以创建一个好的故事层次结构对于确保更大的特性得到充分的实现也非常重要。故事层次结构通常用于在范围更广的故事下组织子故事。更广泛的故事通常被称为“史诗”。使用这种机制,我们可以围绕大量较小范围的故事创建一些顺序。最后我们能够在不到一个小时的时间里轻松地计划一周的工作量,这比我们之前的3h要少很多。

每周的sprint计划会议,有人曾经深情地称之为“伤眼的日子”。不过,公平地说,我们以前的sprint计划会议也包括回顾。现在,我们每个星期五都有一个单独的回顾,把计划和回顾分成两个会议比一个更容易接受。每周会议,我们使用白板开始新的Kanban生活,没有调整列名。

列名分别为:

在开发准备好QAQA准备好发布完成

几周后,我们开始使用电子板来进行我们的测试和计划,方便团队更容易看到backlog。此时,我们在现有的5列的左边增加了4列:

NewBucketBacklogOn Deck
  • NEW=新故事
  • Bucket =无序积压
  • Backlog=优先级列表
  • Start =团队认为已经准备好实现的故事,即良好的验收标准和分配好故事点。

过渡非常顺利,在9个月之后,团队仍然对看板范例感到满意。计划更加及时,故事更短,也更容易处理,任何大于8点的故事都必须分解的规则迫使我们将故事保持在小范围内。我们成功从Scrum转向了Kanban。

此外,我们还发现,为范围更小的故事编写接受标准可以更容易地编写更具体的、不那么模糊的接受标准。换句话说,我们的故事定义更加严格。在转换之前和之后,我们从来没有做过关于生产力的A/B比较,但是团队感觉更有生产力,士气也得到了提高,因为我们能够看到每天的进展。此外,我们与项目经理的沟通也得到了改善,因为他对状态变更和完成一个功能需要多长时间有了更好的了解。

持续学习

有几个因素使我们的过渡相当顺利。

经验丰富的团队领导

在我们从Scrum过渡到Kanban之后,角色变化最大的人是项目经理(扮演Scrum Master的角色)。在整理积压的工作时,他必须至少领先团队一步,考虑团队不断变化的业务需求,有足够好的验收标准来为“Bucket”添加更多的故事,以防处理中的故事在我们下一次站会之前完成。

作为项目经理的另一个挑战是管理转换本身,他指导团队采用新的实践,并帮助督促大家坚持下去。

成熟的敏捷组织

另一个使我们的转换更加顺畅的因素是组织已经拥有敏捷经验,团队的大多数成员都是经验丰富的敏捷实践者。团队会时常进行回顾,在回顾中我们讨论了当前流程的执行情况,并根据需要进行流程更改。所以一个基本的范例转换对我们来说并不像对一个还没有实践敏捷的组织那样可怕。

看板培训

在我们向看板过渡的前一年,整个开发组织都通过了由Modus Cooperandi提供的看板培训。了解了精益和看板的概念,如在制品(WIP)、周期时间等。所以当我们的团队开始讨论看板的采用时,我们对一些词汇的含义已经很熟悉。

保持每周的节奏

每周一次的节奏有助于我们构建新的Kanban。我们每周都有回顾,所以如果出现问题,我们可以对我们的流程做一些小的调整。周一计划一周的工作,周五回顾一周的工作,这有助于保持良好的节奏。即使我们取消了冲刺,保持每周的时间表仍然很有意义。

结果

在我们进行了9个月的转换之后,我们在Kanban方面的实践越来越熟练,我们的业务需求仍在频繁地变化,但是团队效率很高,并且持续稳定地发布新功能,同时,JIT(及时)计划也使我们所有的会议都更加高效和富有成效。敏捷有许多不同的实践方法,每个团队有自己的路径,希望我们的经历能对你有用。

关于Choerodon猪齿鱼敏捷管理实践的相关文章,点击蓝字阅读 ▼

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 20 分钟阅读

作者 | Christopher Lee & Sean D. Mack

如果您曾经对敏捷或DevOps的历史、结构、原则或共性有过疑问,那么您将在这篇文里找到答案,本文被拆分两篇,上篇《使用DevOps强化敏捷(上)》主要介绍敏捷和DevOps的历史、差异和好处,本篇主要介绍敏捷和DevOps的几个共性。

敏捷和DevOps的目标是一致的,因此在实践过程中会发现它们有所重叠。在许多方面,DevOps和敏捷的交叉关系到协作文化以及从这种文化中产生的现代技术实践和过程。

协作文化

敏捷和DevOps之间的核心共性之一是他们都强调打造协作文化。这两种方法都着眼于打破壁垒,增加成员共同责任感。通过打破隔离,DevOps和Agile减少了交接,提高了向客户交付的速度。DevOps将这种协作概念扩展到了运维团队中,而敏捷关注QA是否包含在内。

在敏捷中,我们看到协作文化直接融入到了敏捷宣言的核心原则中。第一个定义“个人和交互重于流程和工具”明显地表达了合作的需要。此外,第三个原则,“客户协作重于合同谈判”,强调需要将这种协作扩展到开发团队之外,也扩展到客户当中。

敏捷教练Susan McIntosh在她的文章《敏捷心态到底是什么》中提到,“敏捷心态是一种支持敏捷工作环境的态度。其中包括尊重、协作、改进和学习反馈、对归属的自豪感、专注于提供价值以及适应变化的能力。这种心态对于培养高绩效团队是必要的,而这些团队反过来又为客户带来了惊人的价值。”

在敏捷中有许多协作的例子,诸如结对编程、Mob编程和Swarming等实践都允许更大的团队合作开发软件,虽然这与开发的原概念相悖(开发原本是指由一个单独的程序员单独完成的任务)。此外,敏捷工作还将QA无缝链接到流程中,作为团队任务的一部分。RonLichty表示:“我将通过Scrum中的产品所有者角色将产品管理集成到团队中。PdMs向开发人员“越过墙”抛出需求的历史由来已久,这与开发人员向测试人员“越过墙”抛出代码的历史没有太大不同!”RonJeffries写了他在极限编程中处理故事的方法。Atlassian建议通过使用单页仪表板缩小PRD(产品需求文档)来保持需求的精简。

2.png

DevOps的许多基本概念也是围绕协作的概念构建的。事实上,这个可以追溯到早先反对将开发、运维和QA分割之时。DevOps运动的基础之一,正是人们认识到开发、运维和QA彼此独立会导致利益冲突、增加交接成本。

Thoughtworks的首席科学家Martin Fowler指出协作在DevOps中扮演重要角色,他认为,“DevOps文化的主要特征是增加了开发和运维角色之间的协作……开发和运维之间不应该存在壁垒。”和从一开始就一起工作解决问题相比,切换周期和文档是一个糟糕的替代品。调整资源结构,使运维人员能够尽早参与到团队中来是很有帮助的。

另外,建立协作文化的一个关键方法是在所有参与交付的团队之间制定共同的目标。使开发、运维和QA之间在明确的目标上保持一致,并将重点放在客户需求和最终交付上。

DevOps鼓励协作的另一种方式是将运维活动集成到Sprint中。这可以通过在Sprint中加入运维团队成员来实现,甚至更好的方法是,将运维职责分给所有Sprint团队成员。

除了交付特性和“功能”之外,在高性能的DevOps组织中,通常还向交付产品的团队提供维护这些产品的职责。一旦系统的稳定性得到证明,它就会移交给另一个团队进行维护。其他公司包括开发团队,作为操作问题的寻呼机轮换的一部分。这就产生了共同的责任,并加强了共同的目标和责任。

当然,DevOps不是一份工作,它不是一个角色,也不是任何一个人或团队的责任。DevOps的核心是协作,这与敏捷宣言中的原则保持一致。

小批量、短周期

小批量和短周期是敏捷化的关键。通过减少对系统的更改,敏捷可以更快地为客户带来价值。这种快速部署能带来快速反馈,客户或用户可以快速查看已开发的内容,团队能在必要时快速调整路线。这与瀑布式方法形成了鲜明的对比,在瀑布式方法中,客户可能要等上几个月甚至几年才能看到交付成果,只有到那时才能确定产品是他们所想的还是所要的。DevOps采用小批量的概念,并通过持续集成和持续部署(CI/CD)对其进行扩展。CI/CD提供的工具链加速团队对客户的价值,将周期从几周缩短到几天甚至几小时。

小批量是精益的关键。起源于20世纪30年代的精益为敏捷和开发人员提供了一些核心基础,它专注于消除浪费和减少批量,减少正在处理的工作量,从而减少等待下一个阶段处理的库存量。

这一概念与敏捷的核心原则相呼应,敏捷的核心原则规定“经常交付产品,从几周到几个月,优先选择较短的时间段。”小批量和较短的周期时间有很多好处。根据DonReinersen的说法,为了让产品开发增加价值,结果必然会有不确定性。我们不应该试图最小化失败,而应该接受可变性。我们可以通过有效地学习和高效地生成信息来减少不确定性。VirtualCTO官诺亚•坎特(Noah Cantor)强调了短反馈循环的影响,“有很多学术研究和行业研究表明,反馈周期越长,人们从中学习的知识就越少。反之亦然——反馈周期越短,人们可以从中学习的越多。”

有很多方法可以确保你在敏捷中拥有小批量和较短的周期时间。最基本的方法之一是将用户故事分割成适合迭代的片段。减少故事的规模很多,比如将功能拆分为单个用户故事或任务等。

当划分和拆分用户故事时,重要的是确保您不创建合成型团队,而是坚持使用功能团队。垂直而不是水平地拆分用户故事。也就是说,观察端到端的特性,而不是应用程序的特定层。

小批量和短周期也是DevOps的关键。DevOps的重点是从左到右增加产品流。通过使用精益的工具(如价值流图),可以识别瓶颈并消除它们,从而增加对客户的价值流。

此外,较短的循环时间是DevOps第二和第三种方法的关键。与敏捷一样,更短的周期意味着价值能更快地传递给客户,因此团队可以更快地获得反馈,以便能快速向客户发布特性或更改,并根据反馈快速调整。

DevOps中缩短循环时间和I迭代周期的关键之一是持续集成(CI)和持续部署(CD)。通过持续的集成,一些更改会不断地被合并和验证,从而使整个产品始终具有潜在的可交付性。而持续部署会确保产品始终处于可部署状态,允许随时向客户交付价值。这两个实践采用了敏捷方法中最初引入的快速开发和交付,并将其周期进一步减少到每天甚至每小时。

工作可视化

可视化是DevOps和敏捷中的另一个关键元素。对于像Scrum和看板这样的敏捷实践,通常采用板的形式来共享信息。DevOps利用并进一步扩展了这些方法,来共享系统在某一特定时间内的执行情况,这可以通过大型可视仪表盘和共享仪表盘的形式等展现。

虽然敏捷宣言并没有规定工作可视化的必要性,但是概念是实践的基础。宣言强调“个人和交互重于流程和工具”和“客户协作重于合同谈判”以及“响应变化重于遵循计划”,这三个方面都能通过工作可视化而得到加强。

敏捷促成了“信息辐射源”概念的发展,这是一种位于敏捷开发团队附近的大型图表,能显示整个开发周期的工作进展。Alistair Cockburn在2000年创造了“信息辐射源”这个术语,并在他2001年出版的《敏捷软件开发》一书中做了介绍。

工作可视化能直接暴露出时间的缺漏,以优化工作和流程。通过为团队提供可视化工作的方法,使团队能够一起工作更加方便,这些板还帮助快速识别瓶颈。

DevOps的第二种方法集中于增强反馈和共享操作信息,这是一种很好的方法。这可以包括Scrum板,也可以包括关于系统性能、用户体验以及代码和基础结构性能的实时数据。这些信息图表就像在整个建筑的战略位置张贴的大型监视器。

1.png

在DevOps手册中,作者还用了整整一章的篇幅来讨论遥测的问题。他们在他们的“创建遥测技术以发现和解决问题”一章中写道,“我们的目标是将这些信息辐射到组织的其他部门,确保任何想要我们正在运行的任何服务的信息的人都能获得这些信息……使价值流中的每个人都可以实时共享信息和提出观点。”

Etsy是一家以DevOps思想领导能力闻名的工艺电子商务公司,在工作可视化的领域也做了很多工作。“如果Etsy的工程有宗教信仰,那就是图形教会。”Patrick McDonnell在DevOps手册中谈到了监控部署数据的好处,他说:“通过这样做,我们可以更快地看到任何意外的部署副作用。我们甚至开始在办公室周围安装电视屏幕,以便每个人都能看到我们的服务表现。”

持续学习

敏捷和开发人员的核心原则中都有持续学习。在敏捷中,这个概念是敏捷宣言的一部分,在DevOps中,它是DevOps的第三种方法的一部分。

敏捷宣言强调“响应变化而不是遵循计划”,因此,它构建了一个强调适应需求的原则。在这种适应性中,假设团队不断的反思和改进,在持续的敏捷短周期中,团队便能够审查事情的进展情况,并对他们交付的产品和交付过程进行快速调整。此外,“客户协作重于合同谈判”的宣言宗旨意味着严格的反馈循环、倾听和从客户反馈中学习。在敏捷中,产品团队可以快速地向客户交付功能,并快速地从实际反馈中学习和快速调整。

DevOps也强调持续学习,其第三种方法便聚焦于此。在IT革命网站上,Kim写道:“DevOps第三种方法是创造一种能促进两件事的文化:一是持续实践、冒险和从失败中学习;二是理解重复和实践是精通某件事的先决条件。”

同时,敏捷和DevOps都把不断学习和不断实验的精神付诸实践。在Scrum中,有被置于流程中的回顾会,用以确保团队花时间对每一次迭代进行反思、从错误中学习并总结成功的经验。回顾会是团队对前一次迭代进行反思并寻找改进的会议,小组成员会讨论哪些进展顺利,哪些进展不顺利,并列举需要改进的方面。

Sprint演示是敏捷流程中持续学习的另一个很好的例子。许多敏捷团队会在每次迭代结束时对Sprint可交付成果进行演示,这样可以让团队成员互相学习,更好地理解产品的所有部分。Sprint演示中还能加入项目涉众的快速反馈,为团队提供了一个互提意见和互相学习的机会,帮助大家继续成长。

在DevOps中,不断学习和不断实验的精神通过事故事后分析等行为得到了强调。JohnAllspaw帮助推出了事后无指责概念,之后这成为了现在DevOps实践的一个共识。他在博客中写道:“事后总结最重要的事情不是一系列可以采取的行动,而是组织学习。”

在Etsy,员工不仅毫无责备地看待事件,甚至还庆祝失败。庆祝失败的原因之一是犯错误的人实际上是最好的工程师,这些人是那些为企业做出最大改变和推动创新的人。因此,重要的不仅仅是限制责备,实际上还灌输了一种文化习惯,这种习惯将庆祝失败视为学习的机会。Etsy的CTO每年会颁发一个很有声望的奖项,以庆祝今年最大的失败。DevOps通过灌输诸如无指责事后分析和失败庆祝等习惯来鼓励一种开放讨论失败并不断学习和成长的文化。

『由于篇幅原因,本文被拆分两篇,上篇主要介绍敏捷和DevOps的历史、差异和好处,点击蓝字即可阅读《使用DevOps强化敏捷(上)》。』

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 18 分钟阅读

作者 | Christopher Lee & Sean D. Mack

译者 | 温馨

如果您曾经对敏捷或DevOps的历史、结构、原则或好处有过疑问,那么您将在这篇文里找到答案,本文被拆分两篇,上篇主要介绍敏捷和DevOps的历史、差异和好处。

敏捷和DevOps从表面上看可能是不同的,但如果关注他们的目标,会发现它们其实非常相似。两者的目标都是更快地为客户创造价值并更快地改变市场需求。DevOps采用敏捷中介绍的原则,并将其扩展使用到代码以外的部署和运维操作中。

敏捷和DevOps的目标是一致的,因此在实践过程中会发现它们有所重叠。在许多方面,DevOps和敏捷的交叉关系到协作文化以及从这种文化中产生的现代技术实践和过程。连续测试和小批量生产等过程中更容易看到了这一点,在使工作尽量可见化的过程中,公开工作流和系统遥测,有助于确保向客户快速交付工作产品,能加速向客户传递价值。

敏捷和DevOps专注于提供客户价值,这不是为了提供更多功能,而是为了尽可能快速有效地向最终客户提供正确的增值功能。DevOps使用敏捷的概念并对其进行了扩展延伸,因此您可以通过实现DevOps实践来增强敏捷性。

什么是DevOps?什么是敏捷?

在开始研究DevOps和敏捷之间的关系之前,首先要对这些术语的含义有一个共同的理解。虽然有许多定义,但它们可以从根本上理解为一套原则,从中可以衍生出工程师文化和实践。

敏捷的存在时间比DevOps长,定义也更为成熟。首次定义于2001年2月的《敏捷宣言》,该宣言包含了以下几条定义:

  • 个人和交互重于流程和工具
  • 工作软件重于公共文档
  • 与客户协作重于合同谈判
  • 响应变化胜过遵循计划

尽管已经有了对DevOps宣言的尝试,但还没有哪一个宣言能够获得敏捷宣言所具有的那种广泛接受度。作为一个一般概念,DevOps重视协作,特别关注开发和运营团队之间的交叉功能以及这两个方面的责任。敏捷教练Anthony Gardiner说,“我测试,我编码,我部署,我在周末起床并修复错误——我让我的代码更好,所以我不必在周末起床。”DevOps可以被认为是一种为客户提供价值的方法,专注于协作和小批量,聚焦持续集成,自动化,持续测试和持续交付。

虽然没有单一的定义,Gene Kim、Kevin Behr和George Spafford在他们的开创性书籍《凤凰计划》中介绍了DevOps的“三种方法”。这三种方法是许多DevOps实践的核心。

Kim后来在《DevOps Handbook》中对这些方法进行了扩展,他与Jez Humble和Patrick Debois合著了这本手册。

这三种方法包括:

方法一系统思维强调整个系统的性能,而不是特定工作或部门的性能——大到可以是一个部门,小到可以是单个贡献者。
方法二增强反馈循环创建从右到左的反馈循环。以缩短和增强反馈为流程改进计划的目标,以便持续进行必要的修正。
方法三持续实践和学习的文化创造一种能促进两件事的文化:一是持续实践、冒险和从失败中学习;二是理解重复和实践是精通某件事的先决条件。

历史

追溯到20世纪90年代,敏捷已存在的时间比DevOps长得多,后者在将近20年后才出现。然而,这两套原则都源于精益制造,其历史可以追溯到20世纪40年代。虽然DevOps的流行度持续增长,但敏捷仍然比DevOps更广为人知。谷歌趋势表示“敏捷”一词的搜索量大约是“DevOps”一词的三倍。

敏捷的根源可以追溯到20世纪40年代的精益流程,其中包括看板,一种可视化丰田生产系统一部分工作流程的方法。限制理论也是后来发展起来的。然而,敏捷的软件开发方法在20世纪90年代真正起步,当时一群软件开发人员常受到瀑布式开发方法中涉及的高度严格的流程的困扰。这些常导致软件项目花费了数月甚至数年才导致失败。在20世纪80年代和90年代,诞生了几种软件迭代开发方法作为瀑布方法的替代,包括极限编程(XP)和看板。1995年,引入了敏捷开发的Scrum实践。然后,在2001年Snowbird山度假村的著名会议上,一群思想领袖齐聚一堂,共同编写敏捷宣言。敏捷发展至今,其中许多变化和实践都是从最初的宣言演变而来的,包括现代敏捷。虽然敏捷制定了总体原则,但实践敏捷原则的方法有很多,Scrum和看板是最受欢迎的两种(关于它们的区别,可阅读《Kanban VS Scrum:哪个是最好的敏捷项目管理框架》)。

DevOps是一套更为新的原则,它源于精益制造中的一些相同概念。“DevOps”一词于2009年首次推出,当时Patrick Debois在比利时举办了“Devopsdays”活动。2013年,Gene Kim(作者),Kevin Behr(作者)和George Spafford(作者)撰写了他们的书《凤凰计划》,其中提出的许多内容构成了DevOps的基础概念。2014年,随着DevOps企业峰会的启动,DevOps不断扩展到企业环境中。今天,DevOps继续与敏捷一起成长和发展。

关键差异

虽然敏捷和DevOps具有很多一致性,但需要注意的是它们的侧重点不同。敏捷主要关注应用程序的构建,而DevOps则将这一重点扩展到构建和运营应用程序。DevOps采用了敏捷的许多概念,并将这些概念扩展到构建过程之外并带入生产。正是这个扩展导致了真正的差异。

如果说存在不同,那么主要在于聚焦点的不同。敏捷教练Martin Corbett表示,“敏捷专注于分解工作,以尽快为客户提供更多价值,而DevOps专注于将代码从构建扩展到部署的项目,例如持续部署。”此外,敏捷专注于构建工作软件以及开发和QA之间的协作,而DevOps则专注于开发、QA和运营之间的协作。

虽然许多人都在寻找敏捷与DevOps之间的差异,但实际情况是,这两种方法具有非常相似的目标和基础原则,并且最终具有比差异更多的相似性。

DevOps是敏捷的延伸

在许多方面,DevOps可以被视为敏捷的延伸,甚至是敏捷的自然演变。在瀑布开发流程中,有一个明确的交接(它是强制执行的过程)。敏捷作为一个持续的过程,需要一种新的方法,DevOps有助于实现这种方法。

为了实现精益和敏捷最佳实践的核心小批量发布,在DevOps中普及的全栈工程师的概念是这种需求的最佳答案。为了减少交接,工程师必须悉知系统的所有部分,以便团队中的任何成员都能理解操作要求,而无需交付给其他团队。同样,跨职能团队必须成为常态,以便小型,独立的团队可以提供功能齐全的产品,而无需向运营团队进行额外的交接。

此外,敏捷的持续流程需要一定程度的构建和部署自动化。其中大部分都是在DevOps的CI / CD实践和工具中提供的。CI / CD需要快速部署经过全面测试并且功能齐全的代码给客户。因此,很容易看出CI /CD是敏捷实践所必需的自然演化。这些做法持续发展,使发布周期时间进一步缩短,从数周到数天甚至数小时。

从另一个角度考虑,如果您有一个只有在开发完成后才能开始的为期两周的QA周期,那么在一两周内就很难将代码推出。同样,诸如变更审批、释放资源、硬件购买和安装等需要耗费时间的事情,都会影响你按时推出代码。更不用说,您可能还有很多的交接工作,或者有一个周期长又繁重的发布过程。如何进行为期两周的迭代并进行为期两个月的发布过程?对此的明显答案是DevOps。

这并不是说没有DevOps就无法实现敏捷。敏捷在DevOps之前很久就存在,并且肯定有敏捷团队不遵循许多DevOps实践。正如Noah Cantor所说,“你可以做敏捷实践和DevOps实践,但你不能采用敏捷原则或DevOps原则,因为它们太相似而不能分开。”并不是说它们不可分割,只是敏捷作为DevOps的前身,同时激发了DevOps的影响力。

好处

精益一直是DevOps的核心,就像敏捷是从精益中生长出来的一样。DevOps也是如此,所以这两者有很大的共同点并不奇怪。DevOps采用Agile中的概念,并将其扩展到代码部署之外。DevOps采用这些概念并将其应用于应用程序和服务的管理。它利用并优化了敏捷的原则,并且沿用了敏捷组织早已意识到的长处。

并不是说为DevOps而做DevOps,或者说为敏捷而做DevOps。2017年DevOps状态报告显示,高性能DevOps团队的部署速度提高了46倍,交付时间缩短了44倍,更改失败率降低了5倍,平均恢复时间缩短了96倍(MTTR)。在具有成熟DevOps实践的组织中,这些数字显然被低估了。

当我们查看敏捷和DevOps重叠的每个区域时,DevOps放大了敏捷实践,我们希望您能够采取一些具体措施来推动组织的发展。构建协作的最关键步骤之一是制定共同目标。有了共同的目标,您可以确保开发,运营,QA都一致朝着同一目标努力。

小批量交付为推动DevOps实践加速组织提供了另一个绝佳机会。在Scrum或看板等流程中,要将操作任务和操作思想集成到工作中。如果您正在使用Scrum,您也可以考虑使用看板,它更容易适应操作流程。

无论使用哪种方法进行小批量交付,您都可能希望确保使用相同的系统来跟踪整个团队的工作。通过统一跟踪系统,您可以确保不积压工作,并真实反应所有计划内和计划外工作,让您更容易地使工作可见。作为敏捷的一个关键原则,我们还可以扩展使工作可见的概念,以显示运营工作、系统操作和架构支持等工作。组织还可以通过信息辐射体(比如看板)跨团队分享这些信息。

当您着眼于构建更具协作性的文化时,要把每一次失败都当作组织学习的机会。在这个文化中建立一些仪式,比如无可指责的事后反思、黑客马拉松和失败奖励等。

对于本文中讨论的重叠,存在组织上的含义。在敏捷和DevOps中包含QA意味着我们必须开始构建跨职能团队,并培养具有广泛知识技能的人员。这种重叠是随着“全堆栈工程师”的概念而发展起来的。虽然每个人都知道一切可能不现实,但我们当然可以努力确保团队中的每个人都有共同的责任和目标,包括质量和运营,如果他们乐于负责和学习的话。

有许多方式可以采用敏捷原则并使用DevOps强化它们,但最重要的是组织文化的一致性。如果团队在目标上没有保持一致,那么敏捷中规定的团队方法将不能扩展到部署以外的操作中使用。如果所有技术交付团队都遵循相同的目标,即可立即开始打破这些组织之间的孤岛。如果我们可以通过敏捷开始打破组织孤岛并通过DevOps强化这项工作,我们将拥有真正的高性能组织。

『由于篇幅原因,本文被拆分两篇,下一篇主要将介绍DevOps和敏捷之间的几个共性,欢迎大家持续关注。』

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 13 分钟阅读

随着云原生概念盛行,对于容器、服务、节点以及集群的监控变得越来越重要。Prometheus 作为 Kubernetes 监控的事实标准,有着强大的功能和良好的生态。但是它不支持分布式,不支持数据导入、导出,不支持通过 API 修改监控目标和报警规则,所以在使用它时,通常需要写脚本和代码来简化操作。Prometheus Operator 为监控 Kubernetes service、deployment 和 Prometheus 实例的管理提供了简单的定义,简化在 Kubernetes 上部署、管理和运行 Prometheus 和 Alertmanager 集群。

功能

Prometheus Operator (后面都简称 Operater) 提供如下功能:

  • 创建/销毁:在 Kubernetes namespace 中更加容易地启动一个 Prometheues 实例,一个特定应用程序或者团队可以更容易使用 Operator。

  • 便捷配置:通过 Kubernetes 资源配置 Prometheus 的基本信息,比如版本、存储、副本集等。

  • 通过标签标记目标服务: 基于常见的 Kubernetes label 查询自动生成监控目标配置;不需要学习 Prometheus 特定的配置语言。

先决条件

对于版本高于 0.18.0 的 Prometheus Operator 要求 Kubernetes 集群版本高于 1.8.0。如果你才开始使用 Prometheus Operator,推荐你使用最新版。

如果你使用的旧版本的 Kubernetes 和 Prometheus Operator 还在运行,推荐先升级 Kubernetes,再升级 Prometheus Operator。

安装与卸载

快速安装

使用 helm 安装 Prometheus Operator。使用 helm 安装后,会在 Kubernetes 集群中创建、配置和管理 Prometheus 集群,chart 中包含多种组件:

安装一个版本名为 my-release 的 chart:

helm install --name my-release stable/prometheus-operator

这会在集群中安装一个默认配置的 prometheus-operator。这份配置文件列出了安装过程中可以配置的选项。

默认会安装 Prometheus Operator, Alertmanager, Grafana。并且会抓取集群的基本信息。

卸载

卸载 my-release 部署:

helm delete my-release

这个命令会删除与这个 chart 相关的所有 Kubernetes 组件。

这个 chart 创建的 CRDs 不会被默认删除,需要手动删除:

kubectl delete crd prometheuses.monitoring.coreos.com
kubectl delete crd prometheusrules.monitoring.coreos.com
kubectl delete crd servicemonitors.monitoring.coreos.com
kubectl delete crd podmonitors.monitoring.coreos.com
kubectl delete crd alertmanagers.monitoring.coreos.com

架构

Prometheus Operator 架构图如下:

1.png

上面架构图中,各组件以不同的方式运行在 Kubernetes 集群中:

  • Operator: 根据自定义资源(Custom Resource Definition / CRDs)来部署和管理 Prometheus Server,同时监控这些自定义资源事件的变化来做相应的处理,是整个系统的控制中心。
  • Prometheus:声明 Prometheus deployment 期望的状态,Operator 确保这个 deployment 运行时一直与定义保持一致。
  • Prometheus Server: Operator 根据自定义资源 Prometheus 类型中定义的内容而部署的 Prometheus Server 集群,这些自定义资源可以看作是用来管理 Prometheus Server 集群的 StatefulSets 资源。
  • ServiceMonitor:声明指定监控的服务,描述了一组被 Prometheus 监控的目标列表。该资源通过 Labels 来选取对应的 Service Endpoint,让 Prometheus Server 通过选取的 Service 来获取 Metrics 信息。
  • Service:简单的说就是 Prometheus 监控的对象。
  • Alertmanager:定义 AlertManager deployment 期望的状态,Operator 确保这个 deployment 运行时一直与定义保持一致。

自定义资源

Prometheus Operater 定义了如下的四类自定义资源:

  • Prometheus
  • ServiceMonitor
  • Alertmanager
  • PrometheusRule

Prometheus

Prometheus 自定义资源(CRD)声明了在 Kubernetes 集群中运行的 Prometheus 的期望设置。包含了副本数量,持久化存储,以及 Prometheus 实例发送警告到的 Alertmanagers等配置选项。

每一个 Prometheus 资源,Operator 都会在相同 namespace 下部署成一个正确配置的 StatefulSet,Prometheus 的 Pod 都会挂载一个名为 <prometheus-name> 的 Secret,里面包含了 Prometheus 的配置。Operator 根据包含的 ServiceMonitor 生成配置,并且更新含有配置的 Secret。无论是对 ServiceMonitors 或者 Prometheus 的修改,都会持续不断的被按照前面的步骤更新。

一个样例配置如下:

kind: Prometheus
metadata: # 略
spec:
alerting:
alertmanagers:
- name: prometheus-prometheus-oper-alertmanager # 定义该 Prometheus 对接的 Alertmanager 集群的名字, 在 default 这个 namespace 中
namespace: default
pathPrefix: /
port: web
baseImage: quay.io/prometheus/prometheus
replicas: 2 # 定义该 Proemtheus “集群”有两个副本,说是集群,其实 Prometheus 自身不带集群功能,这里只是起两个完全一样的 Prometheus 来避免单点故障
ruleSelector: # 定义这个 Prometheus 需要使用带有 prometheus=k8s 且 role=alert-rules 标签的 PrometheusRule
matchLabels:
prometheus: k8s
role: alert-rules
serviceMonitorNamespaceSelector: {} # 定义这些 Prometheus 在哪些 namespace 里寻找 ServiceMonitor
serviceMonitorSelector: # 定义这个 Prometheus 需要使用带有 k8s-app=node-exporter 标签的 ServiceMonitor,不声明则会全部选中
matchLabels:
k8s-app: node-exporter
version: v2.10.0

Prometheus 配置

ServiceMonitor

ServiceMonitor 自定义资源(CRD)能够声明如何监控一组动态服务的定义。它使用标签选择定义一组需要被监控的服务。这样就允许组织引入如何暴露 metrics 的规定,只要符合这些规定新服务就会被发现列入监控,而不需要重新配置系统。

要想使用 Prometheus Operator 监控 Kubernetes 集群中的应用,Endpoints 对象必须存在。Endpoints 对象本质是一个 IP 地址列表。通常,Endpoints 对象由 Service 构建。Service 对象通过对象选择器发现 Pod 并将它们添加到 Endpoints 对象中。

一个 Service 可以公开一个或多个服务端口,通常情况下,这些端口由指向一个 Pod 的多个 Endpoints 支持。这也反映在各自的 Endpoints 对象中。

Prometheus Operator 引入 ServiceMonitor 对象,它发现 Endpoints 对象并配置 Prometheus 去监控这些 Pods。

ServiceMonitorSpec 的 endpoints 部分用于配置需要收集 metrics 的 Endpoints 的端口和其他参数。在一些用例中会直接监控不在服务 endpoints 中的 pods 的端口。因此,在 endpoints 部分指定 endpoint 时,请严格使用,不要混淆。

注意:endpoints(小写)是 ServiceMonitor CRD 中的一个字段,而 Endpoints(大写)是 Kubernetes 资源类型。

ServiceMonitor 和发现的目标可能来自任何 namespace。这对于跨 namespace 的监控十分重要,比如 meta-monitoring。使用 PrometheusSpec 下 ServiceMonitorNamespaceSelector, 通过各自 Prometheus server 限制 ServiceMonitors 作用 namespece。使用 ServiceMonitorSpec 下的 namespaceSelector 可以现在允许发现 Endpoints 对象的命名空间。要发现所有命名空间下的目标,namespaceSelector 必须为空。

spec:
namespaceSelector:
any: true

一个样例配置如下:

kind: ServiceMonitor
metadata:
labels:
k8s-app: node-exporter # 这个 ServiceMonitor 对象带有 k8s-app=node-exporter 标签,因此会被 Prometheus 选中
name: node-exporter
namespace: default
spec:
selector:
matchLabels: # 定义需要监控的 Endpoints,带有 app=node-exporter 且 k8s-app=node-exporter标签的 Endpoints 会被选中
app: node-exporter
k8s-app: node-exporter
endpoints:
- bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
interval: 30s # 定义这些 Endpoints 需要每 30 秒抓取一次
targetPort: 9100 # 定义这些 Endpoints 的指标端口为 9100
scheme: https
jobLabel: k8s-app

ServiceMonitor 配置

Alertmanager

Alertmanager 自定义资源(CRD)声明在 Kubernetes 集群中运行的 Alertmanager 的期望设置。它也提供了配置副本集和持久化存储的选项。

每一个 Alertmanager 资源,Operator 都会在相同 namespace 下部署成一个正确配置的 StatefulSet。Alertmanager pods 配置挂载一个名为 <alertmanager-name>Secret, 使用 alertmanager.yaml key 对作为配置文件。

当有两个或更多配置的副本时,Operator 可以高可用性模式运行Alertmanager实例。

一个样例配置如下:

kind: Alertmanager #  一个 Alertmanager 对象
metadata:
name: prometheus-prometheus-oper-alertmanager
spec:
baseImage: quay.io/prometheus/alertmanager
replicas: 3 # 定义该 Alertmanager 集群的节点数为 3
version: v0.17.0

Alertmanager 配置

PrometheusRule

PrometheusRule CRD 声明一个或多个 Prometheus 实例需要的 Prometheus rule。

Alerts 和 recording rules 可以保存并应用为 yaml 文件,可以被动态加载而不需要重启。

一个样例配置如下:

kind: PrometheusRule
metadata:
labels: # 定义该 PrometheusRule 的 label, 显然它会被 Prometheus 选中
prometheus: k8s
role: alert-rules
name: prometheus-k8s-rules
spec:
groups:
- name: k8s.rules
rules: # 定义了一组规则,其中只有一条报警规则,用来报警 kubelet 是不是挂了
- alert: KubeletDown
annotations:
message: Kubelet has disappeared from Prometheus target discovery.
expr: |
absent(up{job="kubelet"} == 1)
for: 15m
labels:
severity: critical

PrometheusRule 配置

它们之间的关系如下图:

2.png

Prometheus Operator 的优点

Prometheus Operator 中所有的 API 对象都是 CRD 中定义好的 Schema,API Server会校验。当开发者使用 ConfigMap 保存配置没有任何校验,配置文件写错时,自表现为功能不可用,问题排查复杂。在 Prometheus Operator 中,所有在 Prometheus 对象、ServiceMonitor 对象、PrometheusRule 对象中的配置都是有 Schema 校验的,校验失败 apply 直接出错,这就大大降低了配置异常的风险。

Prometheus Operator 借助 K8S 将 Prometheus 服务平台化。有了 Prometheus 和 AlertManager 这样的 API 对象,非常简单、快速的可以在 K8S 集群中创建和管理 Prometheus 服务和 AlertManager 服务,以应对不同业务部门,不同领域的监控需求。

ServiceMonitor 和 PrometheusRule 解决了 Prometheus 配置难维护问题,开发者不再需要去专门学习 Prometheus 的配置文件,不再需要通过 CI 和 k8s ConfigMap 等手段把配置文件更新到 Pod 内再触发 webhook 热更新,只需要修改这两个对象资源就可以了。

prometheus-operator-blog

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 13 分钟阅读

本文将会对Gitlab CI进行简要介绍,包括Gitlab Runner,Gitlab CI中的相关概念以及.gitlab-ci.yml的常用配置。

那么,GitLab CI 是什么?

GitLab CI 是GitLab内置的进行持续集成的工具,只需要在仓库根目录下创建.gitlab-ci.yml 文件,并配置GitLab Runner;每次提交的时候,gitlab将自动识别到.gitlab-ci.yml文件,并且使用Gitlab Runner执行该脚本。

Gitlab Runner

简介

GitLab-Runner就是一个用来执行.gitlab-ci.yml 脚本的工具。可以理解成,Runner就像认真工作的工人,GitLab-CI就是管理工人的中心,所有工人都要在GitLab-CI里面注册,并且表明自己是为哪个项目服务。当相应的项目发生变化时,GitLab-CI就会通知相应的工人执行对应的脚本。

Runner类型

GitLab-Runner可以分类两种类型:Shared Runner(共享型)和Specific Runner(指定型)。

  • Shared Runner:所有工程都能够用的,且只有系统管理员能够创建。
  • Specific Runner:只有特定的项目可以使用。

Runner搭建

▍1. Runner 安装

以Linux为例:

# For Debian/Ubuntu
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash

# For RHEL/CentOS
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.rpm.sh | sudo bash

其他系统请参考官网文档: https://docs.gitlab.com/runner/install/

▍2. 获取Runner注册Token

安装好Runner之后,需要向Gitlab进行注册,注册Runner需要GitLab-CI的url和token。可根据需求注册选择所需类型Runner。

获取Shared Runner注册Token: 使用管理员用户登录,进入Admin Area->OverView->Runners界面。

1

获取Specific Runner注册Token: 进行项目仓库->settings->CI/CD界面

2

▍3. 注册Runner

执行gitlab-ci-multi-runner register命令进行Runner注册,期间会用到前期获取的url及token;注册完成之后,GitLab-CI就会多出一条Runner记录:

3

更多系统注册,请参考阅读官方文档:https://docs.gitlab.com/runner/register/

相关概念

▍管道(pipeline)

每个推送到 Gitlab 的提交都会产生一个与该提交关联的管道(pipeline),若一次推送包含了多个提交,则管道与最后那个提交相关联,管道(pipeline)就是一个分成不同阶段(stage)的作业(job)的集合。

▍阶段(Stage)

阶段是对批量的作业的一个逻辑上的划分,每个 GitLab CI/CD 都必须包含至少一个 Stage。多个 Stage 是按照顺序执行的,如果其中任何一个 Stage 失败,则后续的 Stage 不会被执行,整个 CI 过程被认为失败。

4

以图中所示为例,整个 CI 环节包含三个 Stage:build、test 和deploy。

  • build 被首先执行。如果发生错误,本次 CI 立刻失败;
  • test 在 build 成功执行完毕后执行。如果发生错误,本次 CI 立刻失败;
  • deploy 在 test 成功执行完毕后执行。如果发生错误,本次 CI 失败。

下图是Gitlab对阶段和阶段状态的展示:

5

6

▍作业(Job)

作业就是运行器(Runner)要执行的指令集合,Job 可以被关联到一个 Stage。当一个 Stage 执行的时候,与其关联的所有 Job 都会被执行。在有足够运行器的前提下,同一阶段的所有作业会并发执行。作业状态与阶段状态是一样的,实际上,阶段的状态就是继承自作业的。

作业必须包含script(由Runner执行的shell脚本),随着项目越来越大,Job 越来越多,Job 中包含的重复逻辑可能会让配置文件臃肿不堪。.gitlab-ci.yml 中提供了 before_script 和 after_script 两个全局配置项。这两个配置项在所有 Job 的 script 执行前和执行后调用。

Job 的执行过程中往往会产生一些数据,默认情况下 GitLab Runner 会保存 Job 生成的这些数据,然后在下一个 Job 执行之前(甚至不局限于当次 CI/CD)将这些数据恢复。这样即便是不同的 Job 运行在不同的 Runner 上,它也能看到彼此生成的数据。

在了解了 Job 配置的 script、before_script、after_script 和 cache 以后,可以将整个 Job 的执行流程用一张图概括:

7

创建.gitlab-ci.yml 文件

什么是.gitlab-ci.yml文件

从7.12版本开始,GitLab CI使用YAML文件(.gitlab-ci.yml)来管理项目配置。该文件存放于项目仓库的根目录,并且包含了你的项目如何被编译的描述语句。YAML文件使用一系列约束叙述定义了Job启动时所要做的事情。

Job

Job是.gitlab-ci.yml文件中最基本的元素,由一系列参数定义了任务启动时所要做的事情,用户可以创建任意个任务;每个任务必须有一个独一无二的名字,但有一些保留keywords不能用于Job名称,image,services,stages,types,before_script,after_script,variables,cache。

Job被定义为顶级元素,并且至少包括一条script语句,如果一个 Job 没有显式地关联某个 Stage,则会被默认关联到 test Stage。

示例:

job1:
# 关联到bulid阶段
stage: build
# 所需执行的脚本
script:
- execute-script-for-job1

参数详情

下面是关于配置CI/CD管道的常用参数详细说明。

▍stages

用于定义所有作业(job)可以使用的全局阶段,gitlab-ci.yml允许灵活定义多个阶段,stages元素的顺序定义了作业执行的顺序。Job关联的stage名相同时,该多个Job将并行执行(在拥有足够Runner情况下)。下一个阶段的job将会在前一个阶段的job都完成的情况下执行。

如果文件中没有定义 stages,那么则默认包含 build、test 和 deploy 三个 stage。Stage 中并不能直接配置任何具体的执行逻辑,具体的执行逻辑应该在 Job 中配置。

示例:

stages:
- build
- test
- deploy

▍stage

阶段是根据每个Job定义的,并且依赖于全局定义的阶段。它允许将作业(Job)分组到不同的阶段。

示例:

stages:
- build
- test
- deploy

job 1:
stage: build
script: make build dependencies

job 2:
stage: build
script: make build artifacts

job 3:
stage: test
script: make test

job 4:
stage: deploy
script: make deploy

▍script

script是一段由Runner执行的shell脚本。

示例:

job:
script: "bundle exec rspec"

这个参数也可以使用数组包涵好几条命令:

job:
script:
- uname -a
- bundle exec rspec

有些时候,script命令需要被单引号或者双引号所包裹。举个例子,命令中包涵冒号的时候,该命令需要被引号所包裹,这样YAML解析器才知道该命令语句不是“key: value”语法的一部分。当命令中包涵以下字符时需要注意打引号:: { } [ ] , & * #? | - < > = ! % @ `

▍image and services

这两个选项允许开发者指定任务运行时所需的自定义的docker镜像和服务。

示例:

#为每个作业定义不同的映像和服务
test:2.1:
image: ruby:2.1
services:
- postgres:9.3
script:
- bundle exec rake spec

test:2.2:
image: ruby:2.2
services:
- postgres:9.4
script:
- bundle exec rake spec

▍before_script和after_script

before_script是用于定义一些在所有任务执行前所需执行的命令, 包括部署工作,可以接受一个数组或者多行字符串。after_script用于定义所有job执行过后需要执行的命令,可以接受一个数组或者多行字符串。

示例:

#定义全局 before_script:
default:
before_script:
- global before script
#覆盖全局before_script
job:
before_script:
- execute this instead of global before script
script:
- my command
after_script:
- execute this after my script

▍only and except

  • only和except两个参数说明了job什么时候将会被创建:
  • only定义了job需要执行的所在分支或者标签
  • except定义了job不会执行的所在分支或者标签

以下是这两个参数的几条用法规则:

  • only和except如果都存在在一个job声明中,则所需引用将会被only和except所定义的分支过滤
  • only和except允许使用正则
  • only和except允许使用指定仓库地址,但是不forks仓库

此外,only和except允许使用以下一些特殊关键字:

描述
branches当一个分支被push上来
tags当一个打了tag的分支被push上来
api当一个pipline被piplines api所触发调起,详见piplines api(https://docs.gitlab.com/ce/api/pipelines.html)
external当使用了GitLab以外的CI服务
pipelines针对多项目触发器而言,当使用CI_JOB_TOKEN并使用gitlab所提供的api创建多个pipelines的时候
pushes当pipeline被用户的git push操作所触发的时候
schedules针对预定好的pipline而言(每日构建一类~,具体请看https://docs.gitlab.com/ce/user/project/pipelines/schedules.html)
triggers用token创建piplines的时候
web在GitLab页面上Pipelines标签页下,你按了run pipline的时候

下面的例子,job将会只在issue-开头的refs下执行,反之则其他所有分支被跳过:

job:
# use regexp
only:
- /^issue-.*$/
# use special keyword
except:
- branches

更多配置详情,请参考官网文档: https://docs.gitlab.com/ee/ci/yaml/README.html#parameter-details

验证.gitlab-ci.yml

GitLab CI的每个实例都有一个名为Lint的嵌入式调试工具,它可以验证.gitlab-ci.yml文件的内容,进入项目仓库->CI/CD->CI Lint,示例如下:

8

参考文档:

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 10 分钟阅读

Choerodon猪齿鱼知识管理是一种内容管理工具,具有创建、编辑、导航、链接、搜索等功能。它为企业、IT团队提供方便的项目协作平台和强大的项目内容管理平台,集中式管理产品相关内容等,例如需求收集、架构设计、功能设计、开发规范、命名规范、会议记录、计划安排等。

在0.17版本中,猪齿鱼上线了新的知识管理,作为项目团队内容管理功能,并在0.18做了升级和功能增强,而原有的承载知识管理功能的Wiki管理将在0.19版本正式下线。

那么,这个新的知识管理和之前的“知识管理(Wiki管理)”有何不同?

新老知识管理有何区别?

猪齿鱼早期的知识管理——“Wiki管理”是团队基于开源产品XWiki所开发。XWiki是一个由Java编写的基于LGPL协议发布的开源wiki和应用平台,基本涵盖了在知识、文档管理等方面所需的各种大小功能。但随着产品的不断演进,XWiki官方团队减少对产品的投入,大大降低了迭代以及运维的效率。

而0.18版本中使用的知识管理则由猪齿鱼团队自行开发,内置于猪齿鱼平台,不再需要跳转至Wiki空间,保留了文档编辑、分享、管理等基本功能,整体上更加轻量。

为什么要重新开发一个新的知识管理服务?

随着更多新用户的增加以及数据的不断积累,XWiki已经无法满足用户在体验和操作上的更高需求,根据社区成员和用户的反馈,猪齿鱼进行了商议和选型,经过1个迭代的周期推出了新的知识管理。

选择重新开发的原因有很多,主要有以下几点:

▍1. 功能开发成本高

Wiki管理自上线以来,一直在不断迭代优化,但猪齿鱼在实际开发过程中发现,针对wiki管理的功能优化,即使是一些普遍性需求,功能开发代价也常常加倍。

比如使用频率很高的分享功能,由于原XWiki中的分享功能在猪齿鱼平台中无法满足到用户的需求,猪齿鱼只能进行二次开发。在开发过程中,经常会因为一些诸如需要先读懂源代码逻辑等工作,让工作量超出预期,反而降低了团队的开发效率。

▍2. 升级引起重构

猪齿鱼引用XWiki(版本)实现了内容的管理,但同时,为了满足用户的需求,一般都会对其进行二次开发,在此情况下,如果猪齿鱼跟随官方对XWiki进行了升级,那就意味着二次开发代码的重构,升级的不确定性会带来很大的成本,因此弃用XWiki并开发新的知识管理很有必要。

▍3. 操作较为复杂,用户体验不佳

猪齿鱼是一个既追求产品功能又重视产品体验的平台。在前几个版本中,开发者更多地是在关注功能是否缺失,可随着产品功能的逐步完善,产品经理和设计师的关注度逐步向体验和操作转移。

根据产品团队的不完全统计,针对Wiki管理,社区论坛上关于操作的问题达到50%以上,性能问题达到了20%以上,比如:

1、Wiki服务与猪齿鱼平台用户同步的问题; 2、权限配置的问题; 3、左侧树形菜单结果加载过慢问题。

这一切本不该出现的问题,占到了总数的70%,此时,猪齿鱼团队更加确定Wiki必须重构。

猪齿鱼知识管理服务上线

从决定重构,选型,开发再到最后的上线,开发团队前后花了大概3周的时间,在0.17版本发布了一个可用的基础版。

新的知识管理是一个开发轻量级,操作简便的内容管理工具,具有最基本的创建、编辑、导航、链接、版本回滚、搜索等功能,组织层和项目层均有属于自己的知识管理服务。

一、操作和体验

▍1. 安装、升级更加快捷

无需引入第三方XWiki,可直接部署、升级猪齿鱼知识管理服务。

▍2. 操作简单,弱化了权限设置操作,突出团队共享

结构权限上与之前的Wik相比,没变的是同项目的成员可查看项目内创建的所有文档,不同的是组织层的文档,各个项目成员均能查看;

项目成员可对其他成员创建的文档进行编辑,但只能删除自己创建的文档。

▍3. 界面布局清晰,无需手册容易上手

1.png

二、功能

▍1. 文档的创建与编辑

2.png

  • 创建文档即是创建内容也是创建页面;
  • 编辑器支持基本的编辑需要;
  • 支持实时保存,离开界面时,会进行确认提醒;
  • 支持2种编辑模式:markdown/所见即所得。

▍2. 文档的查看

3.png

  • 点击左侧树形菜单结构进行查看(无限层级);
  • 支持全局搜索查看;
  • 可显示目录,通过目录跳转到目标位置进行查看;
  • 可针对文档进行评论、附件上传;
  • 面包屑导航显示文档的位置;
  • 可通过分享链接的方式,分享自己创建的文档,非项目成员可通过分享的链接查看文档。

▍3. 其他操作

4.png

  • 文档可在左侧树型菜单上自由移动,可移动单个文档或文档集;
  • 支持Word格式文档的导入;
  • 支持PDF的导出;
  • 支持版本回滚以及历史版本对比(并显示新增和删除记录);
  • 所有操作日志的记录。

针对知识管理,猪齿鱼团队会逐步迭代,进行功能和操作体验的提升。希望重构后的知识管理可以更好地帮助到大家,也欢迎大家多多反馈,开发团队会积极回应大家的需求和意见。

插播通知

Choerodon猪齿鱼将于0.19版本正式取消Wiki管理功能,所有文档内容管理相关操作请大家在知识管理中进行。

Wiki管理和知识管理会在0.18版本中并行,方便用户进行数据迁移。Choerodon猪齿鱼系统已经将所有用户所属项目空间下的Wiki文档自动迁移至知识管理,如您在wiki空间中还有新的增改,可点击知识管理菜单栏上方“wiki迁移”进行手动二次迁移。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 13 分钟阅读

配置React应用程序的方法有很多,本文中将向大家展示Choerodon平台前端的新环境变量方案,该方案可以实现在运行时配置,所以不需要针对每个环境都进行构建。

需求

希望能够将React应用程序使用Docker运行,只构建一次,能够在任何地方运行,并且希望在运行时提供重新配置容器的时机,允许在docker-compose文件内进行变量配置。

例如:

version: "3.2"
services:
my-react-app:
image: my-react-app
ports:
- "3000:80"
environment:
- "API_URL=production.example.com"

*注: 在开发中,有两种不同的环境变量。一是在编译时已确定的,通过类似config.js的配置文件进行配置;而另一种是在部署时(运行时)才确定的,比较常见的是根据环境进行区分的一些变量,比如API请求地址前缀,根据部署的环境不同而不同。当前端镜像生成后,需要通过外部去注入这个变量。

原来的方案

原来的方案将两种变量混合在一起,导致很难区分到底哪些是可以通过环境变量注入修改的。

代码如下:

// updateWebpackConfig.js
const { apimGateway } = choerodonConfig;

...

if (mode === 'start') {
defaultEnterPoints = {
APIM_GATEWAY: apimGateway,
};
} else if (mode === 'build') {
if (isChoerodon) {
defaultEnterPoints = getEnterPointsConfig();
}
}

const mergedEnterPoints = {
NODE_ENV: env,
...defaultEnterPoints,
...enterPoints(mode, env),
};
const defines = Object.keys(mergedEnterPoints).reduce((obj, key) => {
obj[`process.env.${key}`] = JSON.stringify(process.env[key] || mergedEnterPoints[key]);
return obj;
}, {});

customizedWebpackConfig.plugins.push(
new webpack.DefinePlugin(defines),

...
// getEnterPointsConfig.js
const enterPoints = {
APIM_GATEWAY: 'localhost:apimgateway',
};

export default function getEnterPointsConfig() {
return enterPoints;
}

具体步骤为:

当为本地启动(start,development)时:

  • 先通过config.js中获取到变量值
  • 构造defaultEnterPoints对象,把变量放入
  • 然后通过DefinePlugin插件把这个对象的键作为变量注入

为了便于管理,在@choerodon/boot/lib/containers/common/constants中有如下代码:

export const TYPE = `${process.env.TYPE}`;
export const RESOURCES_LEVEL = `${process.env.RESOURCES_LEVEL || ''}`;
export const APIM_GATEWAY = `${process.env.APIM_GATEWAY}`;
export const UI_CONFIGURE = `${process.env.UI_CONFIGURE}`;
export const EMAIL_BLOCK_LIST = `${process.env.EMAIL_BLOCK_LIST}`;

在代码中就可以使用了。

当为生产环境(build, product)时:

  • 直接使用默认的环境变量及其占位值(这些值是后来替换环境变量的依据)
  • 通过structure/enterpoint.sh去执行全局替换
#!/bin/bash
set -e

find /usr/share/nginx/html -name '*.js' | xargs sed -i "s localhost:8080 $PRO_API_HOST g"
find /usr/share/nginx/html -name '*.html' | xargs sed -i "s localhost:titlename $PRO_TITLE_NAME g"

exec "$@"

使用脚本去进行全局搜索,然后进行字符替换。

可见,当为product环境时,只有环境变量才起效(本地设置的值是无效的)。

pic1

缺点

通过上一章节和示意图的分析,可以发现如下缺点:

  1. 增加环境变量是很复杂的:当增加一个环境变量,要修改至少三处地方(enterpoint.sh, contants.js, updateWebpackConfig.js)。如果使用@choerodon/boot的其他项目要加入一个环境变量(这个变量可能只有该项目使用),即使boot(启动器项目,脚手架)没有做任何修改,也必须增加了变量发布一个新版本。

  2. 而且从上文可以看出,界定哪些变量是config.js中配置,哪些是环境变量注入是很不明确的(或者说是随@choerodon/boot开发者确定的)

  3. 无法明确知道变量是否还在使用。

  4. 部署生产环境时,有些变量是必须有环境变量的(一般的逻辑是环境变量覆盖用户变量再覆盖默认值)。

新方案

还是使用shell脚本的方式,但这次直接生成js文件,通过window.env = {}来注入一个全局的变量,使用时只要通过window._env_yourVarName来获取即可。

具体分为如下几个文件:

  • .default.env: 该文件一般是一些初始环境变量的默认值,目前包括一些原有的环境变量,达到平滑升级的效果。
  • .env: 用户使用的环境变量文件,用户可以在该文件中以键=值的形式声明环境变量
  • \env-config.js: 通过运行shell脚本后生成的最终环境变量对象,结构如下:
  • env.sh: sh脚本,进行用户环境变量和注入环境变量的合并
#!/bin/bash

mode=$1

# Recreate config file
rm -rf ./env-config.js
touch ./env-config.js

# Add assignment
echo "window._env_ = {" >> ./env-config.js

# Read each line in .env file
# Each line represents key=value pairs
while read -r line || [[ -n "$line" ]];
do
# Split env variables by character `=`
if printf '%s\n' "$line" | grep -q -e '='; then
varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')
fi

# Read value of current variable if exists as Environment variable
value=$(printf '%s\n' "${!varname}")
# Otherwise use value from .env file
[[ -z $value ]] && value=${varvalue}

# Append configuration property to JS file
echo " $varname: \"$value\"," >> ./env-config.js
done < .env

while read -r line || [[ -n "$line" ]];
do
# Split env variables by character `=`
if printf '%s\n' "$line" | grep -q -e '='; then
varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')
fi

# Read value of current variable if exists as Environment variable
value=$(printf '%s\n' "${!varname}")
# Otherwise use value from .env file
[[ -z $value ]] && value=${varvalue}

# Append configuration property to JS file
echo " $varname: \"$value\"," >> ./env-config.js
done < .default.env

echo "}" >> ./env-config.js

echo "// ${mode}" >> ./env-config.js

步骤解析:

  • 创建env-config.js文件
  • 写入第一行window.env={
  • 遍历.default.env,如果存在环境变量,则写入键:环境变量值,不存在则写入键:值
  • 遍历.env,处理逻辑同上(这里使用了js对象重复声明一个值,后面的会覆盖前面的特性)
  • 在文件最后写上}表示结束

优化

由于考虑到window平台的开发者,在node内部调用shell脚本可能不会运行,所以用node模拟了一套上述方案,在本地开发和打包时,使用node进行环境变量的合并,当使用环境变量注入时,调用shell脚本进行合并。

使用

  1. 将@choerodon/boot版本进行升级

  2. 在项目根目录下(和package.json同级)创建.env文件(如果没有自定义变量可以不创建)

  3. 以键=值的形式写入变量,如

SERVER=http://choerodon.com.cn
AUTH_URL=https://api.choerodon.com.cn/oauth/login

需要解决的问题

开发环境时,通过webpack-dev-server生成的html文件在内存中,那env-config.js写到哪?

通过配置contentBase来加载

// start.js
const serverOptions = {
quiet: true,
hot: true,
...devServerConfig,
// contentBase: path.join(process.cwd(), output),
contentBase: [path.join(__dirname, '../../'), ...],
historyApiFallback: true,
host: 'localhost',
};
WebpackDevServer.addDevServerEntrypoints(webpackConfig, serverOptions);

shell的当前目录相对于命令运行时的目录而不是文件目录

spawn.sync(shellPath, ['development'], { cwd: path.join(__dirname, '../../../'), stdio: 'inherit' });

通过指明cwd命令来改变当前文件路径。

具体过程

当为本地启动(start,development)时:

  • 先在用户根目录下进行查找是否有.env文件
  • 如果有.env文件,复制到@choerodon/boot根目录下,与env.sh同级
  • 根据shell中的逻辑,合并.default.env和.env的环境变量
  • 生成env-config.js到同级目录下,由于该目录被设置为contentBase,所以启动的代码中能够加载到该目录

当为生产环境打包(build,product)时:

  • 先在用户根目录下进行查找是否有.env文件
  • 如果有.env文件,复制到@choerodon/boot根目录下,与env.sh同级
  • 根据shell中的逻辑,合并.default.env和.env的环境变量
  • 生成env-config.js到同级目录下
  • 复制.env,.default.env,env.shell,env-config.js到dist目录下,与index.html同级

(全部复制是为了应对环境变量注入,也要考虑不注入环境变量的情况,此时env-config.js就是最终的变量)

环境变量替换时:

  • 通过docker运行shell脚本
  • 根据.default.env和.env的键去生成window.env对象,此时有环境变量则替换为环境变量,无环境变量则使用原来值,生成的env-config.js在同级目录下

Q&A

▍哪些变量适合放在这

当采用新的模式后,所有的决定权都在于开发人员(需要慎重),开发人员可以自己声明一个变量,然后在代码中使用,这时当部署生产环境时,可以在.env中声明一个值,然后通过环境变量去覆盖,也可以只是声明这个值(类似于原来的config.js中配置)。

但是总的来说,建议仔细考虑哪些变量是应该作为环境变量注入的。

有两种比较方便的判断方式:

  • 当一个前端镜像部署到不同环境时,该变量值是否应该改变,如果是,可能应该作为一个环境变量
  • 变量是运用在代码打包时的,那么该变量可能是个非环境变量

▍加入了环境变量后不起效

加入了环境变量后,可以在node_modules/@choerodon/boot/env-config.js中查看,自己的环境变量到底有没有被注入,如果被别的库覆盖,可以考虑起个独特的名字或者和他人进行商议(后期会考虑当环境变量重复时,进行警告等检测)。

当部署后,可以通过浏览器直接打开env-config.js文件来查看变量的情况。

pic2

▍原来的环境变量方案会被剔除吗

暂时不会剔除原先的环境变量方案,即还是可以通过costants.js中获取部分环境变量值,但是不排除在今后剔除这种模式。

▍当环境变量不是字符类型时怎么处理

一般环境变量都是以字符形式注入的,非字符形式可以通过config.js进行处理(如钩子函数等),或者通过序列化来进行处理(JSON.stringfiy)。

如上所述,构建时配置将满足大多数场景,既可以自定义增加变量,用自定义的值作为最终指,也可以用环境变量覆盖。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 15 分钟阅读

本文是从 0 到 1 使用 Kubernetes 系列第七篇,上一篇《从 0 到 1 使用 Kubernetes 系列(六):数据持久化实战》 介绍了 Kubernetes 中的几种常用储存类型,本文将介绍 K8S 网络相关的内容。

不同宿主机上运行的容器并不能通过 IP 相互访问,那么 Kubernetes 是如何实现不同节点上 Pod 的互通?Pod 有生命周期,它的 IP 会随着动态的创建和销毁而动态变化,Kubernetes 又是怎样对外提供稳定的服务?今天就为大家一一解答这些疑问。

Docker 网络

先来看一下 Docker 中的网络。在启动 Docker 服务后,默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。

Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500 Bytes,或宿主主机网络路由上支持的默认值,这些值都可以在服务启动的时候进行配置。

root@ubuntu:/root# ifconfig
...
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
ether 02:42:d2:00:10:6c txqueuelen 0 (Ethernet)
...
root@ubuntu:/root# docker inspect busybox
···
"IPAddress": "172.17.0.2",
···

为了实现上述功能,Docker 主要用到了 linux 的 BridgeNetwork NamespaceVETH

  • Bridge 相当于是一个虚拟网桥,工作在第二层网络。也可以为它配置 IP,工作在三层网络。docker0 网关就是通过 Bridge 实现的。
  • Network Namespace 是网络命名空间,通过 Network Namespace 可以建立一些完全隔离的网络栈。比如通过 docker network create xxx 就是在建立一个 Network Namespace
  • VETH 是虚拟网卡的接口对,可以把两端分别接在两个不同的 Network Namespace 中,实现两个原本隔离的 Network Namespace 的通信。

所以总结起来就是:Network Namespace 做了容器和宿主机的网络隔离,Bridge 分别在容器和宿主机建立一个网关,然后再用 VETH 将容器和宿主机两个网络空间连接起来。但这都是在同一个主机上的网络实现,如果想要在多台主机上进行网络就得看看下面介绍的 Kubernetes 网络。

Kubernetes 网络

Kubernetes 为了解决容器的“跨主通信”问题,提出了很多解决方案。常见思路有两种:

  • 直接在宿主机上建立不同宿主机上子网的路由规则;
  • 通过特殊的网络设备封装二层数据帧,根据目标 IP 地址匹配到对应的子网找到对应的宿主机 IP 地址,最后将转发 IP 包,目的宿主机上同样的特殊网络设备完成解封并根据本机路由表转发。

Flannel

大家所熟知的 Flannel 项目是 CoreOS 公司推出的容器网络解决方案。它本身只是一个框架,为开发者提供容器网络功能的是 Flannel 的后端实现。目前有如下三种具体实现:

  • UDP
  • VXLAN
  • host-gw

下面的三层网络指的是七层网络模型中的底部的三层:网络层、数据链路层和物理层。

UDP 模式是最早支持,性能最差,但最容易理解和实现的容器跨主网络方案。Flannel UDP 模式提供的是一个三层的覆盖网络:首先对发出端的 IP 包进行 UDP 封装,然后在接受端进行解封拿到原始的 IP 包,进而把这个包转发给目标容器。它相当于在两个容器之间打通一条“隧道”,使得两个容器可以直接使用 IP 通信,而不关心容器和宿主机的分布情况。

Flannel UDP 模式原理图

因为 Flannel 进行 UDP 封装和解封都是在用户态完成,而在 Linux 系统中上下文切换和用户态操作的代价非常大,这就是它性能不好的主要原因。

VXLAN 即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络虚拟化技术。VXLAN 在内核态就完成了上面的封装和解封工作,通过与 UDP 模式类似的“隧道”机制,构建出覆盖网络(Overlay Network),使得连接在这个 VXLAN 二层网络的“主机”可以像在局域网自由通信。

Flannel VXLAN 模式原理图

host-gw 模式的工作原理是将每一个 Flannel 子网的下一跳设置为该子网对应的宿主机 IP 地址。

也就是说,这台“主机”(host)会充当这条容器通信路径里的“网关”(Getway)。Flannel host-gw 模式必须要求集群宿主机之间是二层连通的

Flannel host-gw 示意图

Calico

Calico 项目提供的网络解决方案与 Flannel Host-gw 模式同理。但是不同于 Flannel 通过 Etcd 和宿主机的 flanneld 来维护路由信息得做法,Calio 项目使用 BGP(边界网关协议) 来自动的在整个集群中分发路由消息。它由三部分组成:

Calico 的 CNI 插件:这是 Calico 与 Kubernetes 对接的部分。 Felix:它是一个 DaemonSet,负责在宿主机插入路由规则,以及维护 Calico 所需的网络设备等。 BIRD:它是 BGP 的客户端,负责在集群里分发路由规则信息。

除了对路由信息的维护方式之外,Calico 项目和 Flannel 的 host-gw 另一个不同是它不会在宿主机上创建任何网桥设备。

Calico 工作原理图

CNI(容器网络接口)

CNI)是 CNCF 旗下的一个项目,由一组用于配置 Linux 容器的网络接口的规范和库组成,同时还包含了一些插件。CNI 仅关心容器创建时的网络分配,和当容器被删除时释放网络资源。其基本思想为: Kubernetes 在启动 Infra 容器之后,就可以直接调用 CNI 网络插件,为这个 Infra 容器的 Network Namespace 配置符合预期的网络栈。

Kubernetes 使用 CNI 接口,维护一个单独的网桥来代替 docker0。这个网桥就叫做 CNI 网桥,它在宿主机上的默认名称是:cni0。以 Flannel 的 VXLAN 模式为例,在 Kubernetes 环境里,它的工作方式没有变化,只是 docker0 网桥替换成了 CNI 网桥。CNI 网桥只是接管所有 CNI 插件负责的,即 Kuberntes 创建的容器(Pod)。

Flannel vxlan cni 版

Service

Kubernetes 中 Pod 有生命周期,它的 IP 会随着动态的创建和销毁而动态变化,不能稳定的提供服务。Kubernetes Service 定义这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略。开发者可以通过一个 Service 的入口地址访问其背后的一组 Pod。一旦 Service 被创建,Kubernetes 就会自动为它分配一个可用的 Cluster IP,在 Service 的整个生命周期中它的 Cluster IP 都不会发生改变。这样就解决了分布式集群的服务发现。

一个典型的 Service 定义如下:

apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- nmae: dafault
protocol: TCP
port: 8000
targetPort: 80

在这个 Service 例子中,笔者使用 selector 字段声明这个 Service 只代理 app=nginx 标签的 pod。这个 Service 的 8000 端口代理 Pod 的 80 端口。

然后定义应用 Delpoyment 如下:

apiVersion: v1
kind: Delpoyment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
meatdata:
lalels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containers: 80
protocol: TCP

被 selector 选中的 Pod,就被称为 Serivce 的 Endpoints,你可以使用 kubectl get ep 查看它们,如下所示:

$ kubectl get endpoints nginx
NAME ENDPOINTS AGE
nginx 172.20.1.16:80,172.20.2.22:80,172.20.2.23:80 1m

通过该 Service 的 VIP 10.68.57.93 地址,就可以访问到它所代理的 Pod:

$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.68.57.93 <none> 80/TCP 1m

$ curl 10.68.57.93
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
......
<h1>Welcome to nginx!</h1>
......
</html>

这个 VIP 地址是 Kubernetes 自动为 Service 分配的。访问 Service 的 VIP 地址和代理的 80 端口,它就为我们返回了默认的 nginx 页面,这种方式称为:Cluster IP 模式的 Service。

集群外访问 Service

Servcie 的访问信息在 kubernates 集群外无效,因为所谓的 Service 的访问接口,实际上是每台宿主机上由 kube-proxy 生成的 iptables 规则,以及 kube-dns 生成的 DNS 记录。

解决外部访问 Kubernetes 集群里创建的 servcie 有以下几种方法:

  • NodePort
  • LoadBalancer

NodePort 方法

下面是 NodePort 的例子:

apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- name: http
nodePort: 30080
port: 8080
targetPort: 80
protocol: TCP

在这个 Service 定义中,声明它的类型为 type=NodePort。此时在 ports 字段中声明了 Service 的 8080 端口代理 Pod 的 80 端口。

如果你不显示声明 nodePort 字段,Kubernetes 会为你随机分配可用端口来设置代理,这个端口的范围默认为:30000-32767。这里设置为 30080。

这里就可以如此访问这个 service:

<任何一台宿主机 IP 地址>:30080

LoadBalancer

这种方法适用于公有云上的 Kubernetes 服务,通过指定一个 LoadBalancer 类型的 Service 实现。

apiVersion: v1
kind: Service
metadata:
name: example-service
spec:
ports:
- port: 8765
targetPort: 9379
selector:
app: example
type: LoadBalancer

创建 Service 时,你可以选择自动创建云网络负载均衡器。这提供了一个外部可访问的 IP 地址,只要您的群集在受支持的云环境中运行,就可以将流量发送到群集节点上的正确端口。

Ingress

为代理不同后端 Service 而设置的路由规则集合就是 Kubernetes 里的 Ingress。

举一个例子,这里有一个订阅系统,它的域名是:https://wwww.example.com 。其中 http://www.example.com/book 是订书系统,https://www.example.com/food 是订餐系统。这两个系统分别由 book 和 food 两个 Deployment 来提供服务。

apiVersion: v1
kind: Ingress
metadata:
name: example-ingress
spec:
tls:
- hosts:
- www.example.com
secretName: example-secret
rules:
- host: www.example.com
http:
paths:
- path: book
backend:
serviceName: book-svc
servicePort: 80
- path: /food
backend:
serviceName: food-svc
servicePort: 80

这个 yaml 文件值得关注的 rules 字段,它叫作:IngressRules。

IngressRule 的 Key 就是 host,它必须是一个标准域名格式的字符串,不能是 IP 地址。

host 字段定义的值就是 Ingress 的入口,也就是说当用户访问 www.example.com 的时候,实际上访问到的是这个 Ingress 对象。Kubernetes 就能根据 IngressRule 进行下一步转发,这里定义两个 path,它们分别对应 book 和 food 这个两个 Deployment 的 Service。

由此不难看出,Ingress 对象其实就是 Kubernetes 项目对“反向代理”的一种抽象。

总结

今天这篇文主要介绍了 Kubernetes 集群实现容器跨主机通信的几种方式的原理,并且介绍了如何使用 Service 对外界提供服务。

更多 Kubernetes 相关文章,点击蓝字可阅读:

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献: