在.NET Core和Kubernetes上构建应用程序:我们的经验

大家好!

今天,我们将讨论其中一个DevOps项目的经验。 我们决定在微服务架构上使用.Net Core为Linux实现一个新的应用程序。

我们希望该项目将得到积极发展,并且会有越来越多的用户。 因此,就功能和性能而言,它都应该易于扩展。

我们需要一个容错系统-如果功能块之一不起作用,则其余功能应该起作用。 我们还希望确保持续集成,包括在客户服务器上部署解决方案。

因此,我们使用了以下技术:

  • .Net Core用于微服务的实现。 我们的项目使用的是2.0版,
  • 用于微服务编排的Kubernetes
  • 用于创建微服务映像的Docker,
  • 集成总线Rabbit MQ和大众运输,
  • Elasticsearch和Kibana用于记录日志,
  • TFS用于实现CI / CD管道。

本文将分享我们解决方案的详细信息。



这是我们在.NET会议上的演讲成绩单,这里是演讲视频的链接

我们的业务挑战


我们的客户是一家拥有商品的联邦公司-这些人负责商品在商店中的展示方式。 还有主管-这些是采购员的主管。

公司有一个由主管培训和评估采购员工作的过程,该过程需要自动化。



这是我们的解决方案的工作方式:

1.主管起草一份调查表-这是您需要检查采购员工作的清单。
2.接下来,主管选择要检查其工作的员工。 询问日期已分配。
3.接下来,将活动发送到主管的移动设备。
4.然后填写调查表并将其发送到门户。
5.门户生成结果和各种报告。

微服务将帮助我们解决三个问题:


1.将来,我们希望轻松扩展功能,因为公司中有许多类似的业务流程。
2.我们希望解决方案能够容错。 如果任何部分停止运行,则解决方案将能够自行恢复其工作,并且一个部分的故障不会对解决方案的整体运行产生很大影响。
3.我们为其实施解决方案的公司有许多分支机构。 因此,该解决方案的用户数量不断增长。 因此,我希望这不会影响性能。

结果,我们决定在该项目上使用微服务,这需要做出许多不平凡的决定。

哪些技术有助于实现此解决方案:


•Docker简化了解决方案分发的分发。 在我们的案例中,分布是一组微服务映像
•由于我们的解决方案中有许多微服务,因此我们需要对其进行管理。 为此,我们使用Kubernetes。
•我们使用.Net Core实现微服务。
•为了在客户处快速更新解决方案,我们必须实施便利的持续集成和交付。

这是我们的整套技术:

•我们用于创建微服务的.Net Core,
•微服务打包在Docker映像中,
•使用TFS实现持续集成和持续交付,
•前端以Angular实现,
•为了进行监控和记录,我们使用了Elasticsearch和Kibana,
•RabbitMQ和MassTransit用作集成总线。

.NET Core for Linux解决方案


我们都知道经典的.Net Framework是什么。 该平台的主要缺点是它不是跨平台的。 因此,我们无法在Docker的.Net Framework for Linux上运行解决方案。

为了提供在Docker中使用C#的能力,Microsoft重新考虑了.Net Framework并创建了.Net Core。 为了使用相同的库,Microsoft创建了.Net标准库规范。 .Net Standart库程序集可以在.Net Framework和.Net Core中使用。



Kubernetes-用于微服务编排


Kubernetes用于管理和集群Docker容器。 这是我们已经利用的Kubernetes的主要优势:

-提供轻松配置微服务环境的功能,
-简化环境管理(开发,质量检查,阶段),
-开箱即用的功能可以在副本上复制微服务和负载平衡。



解决方案架构


在工作开始时,我们问自己如何将功能划分为微服务。 该划分是根据单一责任的原则进行的,级别更高。 它的主要任务是使一项服务中的更改尽可能少地影响其他微服务。 结果,在我们的案例中,微服务开始执行单独的功能区域。

结果,我们出现了用于计划问卷调查的服务,用于显示结果的微服务,用于移动应用程序的微服务以及其他微服务。



与外部客户互动的选项


微软在其有关微服务的书“ .NET微服务”中 “ .NET容器应用程序体系结构 ”提供了与微服务交互的三种可能的实现。 我们审查了所有三个因素,并选择了最合适的。

•API网关服务
网关服务API是用户对其他服务的请求的外观实现。 解决方案的问题在于,如果外墙不起作用,则整个解决方案将停止运行。 他们决定放弃这种方法以实现容错。

•具有Azure API管理的API网关
Microsoft提供了在Azure中使用云外观的功能。 但是此解决方案不适合,因为我们将不在云中而是在客户的服务器上部署该解决方案。

•直接的客户端到微服务通信
结果,我们剩下的最后一个选项是直接用户与微服务的交互。 我们选择了他。



加上容错能力。 缺点是,必须在每个服务上分别复制部分功能。 例如,有必要在用户有权访问的每个微服务上分别配置授权。

当然,出现了如何平衡负载以及如何实现容错的问题。 这里的一切都很简单-Ingress Controller Kubernetes做到了。



节点1,节点2和节点3是同一微服务的副本。 如果其中一个副本失败,则负载平衡器将自动将负载重定向到其他微服务。

物理架构


这是我们组织解决方案基础结构的方式:

•每个微服务都有其自己的数据库(当然,如果他需要它),其他服务则不会访问另一个微服务的数据库。
•微服务仅通过RabbitMQ + Mass Transit总线以及使用HTTP请求相互通信。
•每个服务都有其明确定义的责任。
•对于日志记录,我们使用Elasticsearch和Kibana以及与它一起使用的库Serilog



数据库服务部署在单独的虚拟机上,而不是Kubernetes中,因为Microsoft DBMS不建议在产品环境中使用Docker。

出于容错的原因,日志记录服务也部署在单独的虚拟机上-如果我们在Kubernetes方面遇到问题,那么我们可以找出问题所在。

部署:我们如何组织开发和产品环境


我们的基础架构在Kubernetes中具有3个名称空间。 所有这三种环境都访问一项数据库服务和一项日志记录服务。 而且,当然,每个环境都将查看其自己的数据库。



在客户的基础架构上,我们还有两个环境-试生产和生产。 在生产中,我们有单独的数据库服务器用于售前和产品环境。 为了进行日志记录,我们在基础架构和客户基础架构上分配了一台ELK服务器。

如何部署5个环境,每个环境具有10个微服务?


平均而言,我们每个项目有10个服务,并且有3个环境:QA,DEV,Stage,在其上总共部署了约30个微服务。 而且这仅在开发基础架构上! 在客户的基础架构上再添加2个环境,我们将获得50个微服务。



显然,必须以某种方式管理这么多的服务。 Kubernetes为此提供了帮助。

为了部署微服务,您必须
•扩展秘密,
•部署部署,
•扩展服务。

关于秘密,写在下面。
部署是Kubernetes的指令,在此基础上它将启动我们的微服务的Docker容器。 这是将部署部署到的命令:

kubectl apply -f .\(yaml deployment-) --namespace=DEV

 apiVersion: apps/v1beta1 kind: Deployment metadata: name: imtob-etr-it-dictionary-api spec: replicas: 1 template: metadata: labels: name: imtob-etr-it-dictionary-api spec: containers: - name: imtob-etr-it-dictionary-api image: nexus3.company.ru:18085/etr-it-dictionary-api:18289 resources: requests: memory: "256Mi" limits: memory: "512Mi" volumeMounts: - name: secrets mountPath: /app/secrets readOnly: true volumes: - name: secrets secret: secretName: secret-appsettings-dictionary 


该文件描述了部署的名称(imtob-etr-it-dictionary-api),执行所需的映像以及其他设置。 在秘密部分,我们将自定义环境。

部署完成后,如有必要,我们需要部署服务。

当需要从外部访问微服务时,需要服务。 例如,当您希望用户或另一个微服务能够向另一个微服务发出Get请求时。

kubectl apply -f .\imtob-etr-it-dictionary-api.yml --namespace=DEV

 apiVersion: v1 kind: Service metadata: name: imtob-etr-it-dictionary-api-services spec: ports: - name: http port: 80 targetPort: 80 protocol: TCP selector: name: imtob-etr-it-dictionary-api 


通常,服务描述很小。 在其中,我们可以看到服务的名称,如何访问它以及端口号。

因此,要部署环境,我们需要

•一组带有所有微服务机密的文件,
•一组文件,其中包含所有微服务的部署,
•具有所有微服务的服务的一组文件。

我们将所有这些脚本存储在git存储库中。

为了部署该解决方案,我们获得了三种类型的脚本集:

•包含机密的文件夹-这些是每个环境的配置,
•包含所有微服务部署的文件夹,
•包含一些微服务的服务的文件夹,

每个大约有十个团队,每个微服务一个。 为了方便起见,我们在Confluence中创建了一个包含脚本的页面,这有助于我们快速部署新环境。

这是一个部署部署脚本(秘密和服务有相似的集合):

部署脚本
kubectl apply -f。\ imtob-etr-it-image-api.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-mobile-api.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-planning-api.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-result-api.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-web.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-report-api.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-template-constructor-api.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-dictionary-api.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-integration-api.yml --namespace = DEV
kubectl apply -f。\ imtob-etr-it-identity-api.yml --namespace = DEV


CI / CD实施



每个服务都在其自己的文件夹中,此外,我们还有一个包含通用组件的文件夹。



每个微服务还有一个构建定义和发布定义。 当提交到适当的服务或提交到适当的文件夹时,我们配置了Build Definion的启动。 如果具有公共组件的文件夹的内容已更新,则将部署所有微服务。

这样的构建组织有什么优势?

1.解决方案位于一个git存储库中,
2.当更改多个微服务时,程序集与免费程序集代理并行开始,
3.每个构建定义都会显示一个简单的脚本,用于构建映像并将其推送到Nexus注册表中。

构建定义和发布定义


前面我们在本文中介绍如何部署VSTS代理。



首先是构建定义。 在TFS VSTS命令中,代理启动Dockerfile构建。 结果,我们得到了微服务的映像。 该映像在运行VSTS代理的环境中本地保存。

构建完成后,将启动Push,它将上一步中收到的图像发送到Nexus注册表。 现在可以在外部使用。 Nexus Registry是一种Nuget,不仅适用于库,而且适用于Docker映像等。

准备好映像并从外部访问该映像之后,您需要对其进行部署。 为此,我们有发布定义。 一切都很简单-我们执行set image命令:

kubectl set image deployment/imtob-etr-it-dictionary-api imtob-etr-it-dictionary-api=nexus3.company.ru:18085/etr-it-dictionary-api:$(Build.BuildId)

之后,他将为所需的微服务更新映像并启动新容器。 结果,我们的服务已更新。

现在让我们比较使用和不使用Dockerfile的构建。



没有Dockerfile,我们将有很多步骤,其中包含很多.Net细节。 在右侧,我们看到一个Docker镜像构建。 一切都变得更加容易。

构建映像的整个过程在Dockerfile中进行了描述。 该程序集可以在本地调试。



总计:我们获得了一个简单透明的CI / CD



1.分离开发和部署。 程序集在Dockerfile中进行了描述,并位于开发人员的肩膀上。
2.在配置CI / CD时,您无需了解程序集的详细信息和功能-仅使用Dockerfile完成工作。
3.我们仅更新更改的微服务。

接下来,您需要在K8S中配置RabbitMQ:我们为此写了另一篇文章

环境设定


一种或另一种方式,我们需要配置微服务。 环境的主要部分在根配置文件Appsettings.json中进行配置。 该文件包含与环境无关的设置。

取决于环境的那些设置存储在appsettings.secret.json文件的secrets文件夹中。 我们采用了在Kubernetes上管理ASP.NET Core应用程序设置一文中描述的方法。

 var configuration = new ConfigurationBuilder() .AddJsonFile($"appsettings.json", true) .AddJsonFile("secrets/appsettings.secrets.json", optional: true) .Build(); 


appsettings.secrets.json文件包含Elastic Search索引的设置和数据库连接字符串。
 { "Serilog": { "WriteTo": [ { "Name": "Elasticsearch", "Args": { "nodeUris": "http://192.168.150.114:9200", "indexFormat": "dev.etr.it.ifield.api.dictionary-{0:yyyy.MM.dd}", "templateName": "dev.etr.it.ifield.api.dictionary", "typeName": "dev.etr.it.ifield.api.dictionary.event" } } ] }, "ConnectionStrings": { "DictionaryDbContext": "Server=192.168.154.162;Database=DEV.ETR.IT.iField.Dictionary;User Id=it_user;Password=PASSWORD;" } } 


将配置文件添加到Kubernetes


为了添加此文件,您需要将其部署在Docker容器中。 这是在Kubernetis部署文件中完成的。 部署描述了应该在哪个文件夹中创建c机密文件以及将文件与哪个机密相关联。

 apiVersion: apps/v1beta1 kind: Deployment metadata: name: imtob-etr-it-dictionary-api spec: replicas: 1 template: metadata: labels: name: imtob-etr-it-dictionary-api spec: containers: - name: imtob-etr-it-dictionary-api image: nexus3.company.ru:18085/etr-it-dictionary-api:18289 resources: requests: memory: "256Mi" limits: memory: "512Mi" volumeMounts: - name: secrets mountPath: /app/secrets readOnly: true volumes: - name: secrets secret: secretName: secret-appsettings-dictionary 


您可以使用kubectl实用程序在Kubernetes中创建密钥。 我们在这里看到密钥的名称和文件的路径。 我们还指出我们为其创建机密的环境的名称。

kubectl create secret generic secret-appsettings-dictionary
--from-file=./Dictionary/appsettings.secrets.json --namespace=DEMO


结论


所选方法的缺点


1.高入门门槛。 如果您是第一次进行这样的项目,将会有很多新信息。
2.微服务→更复杂的设计。 由于我们不是整体解决方案,而是微服务解决方案,因此有必要应用许多非显而易见的解决方案。
3.并不是所有的东西都为Docker实现。 并非所有内容都可以在微服务架构中运行。 例如,当SSRS不在docker中时。

自我测试方法的优点


1.基础设施即代码
基础结构描述存储在源代码管理中。 在部署时,您无需调整环境。
2.开箱即用地在功能级别和性能级别上进行扩展。
3.微服务是很好的绝缘
实际上没有关键部件,这些部件的故障会导致整个系统无法运行。
4.快速交付变更
仅更新其中已更新的那些微服务。 如果您不考虑协调时间以及其他与人为因素有关的事情,那么更新一个微服务的时间将在2分钟或更短的时间内完成。

对我们的结论


1.在.NET Core上,您可以并且应该实施工业解决方案。
2. K8S确实使生活更轻松,简化了环境更新,简化了服务配置。
3. TFS可用于为Linux实现CI / CD。

Source: https://habr.com/ru/post/zh-CN420665/


All Articles