使用Kubernetes的区块链案例(附代码)

262 阅读8分钟

使用Kubernetes的区块链案例

本文展示了我们如何使用Kubernetes和微服务架构来构建一个具有复杂功能的加密货币解决方案。

为了说明Kubernetes是如何被使用的,我提议考虑我们的一个案例。我们要谈的是为加密货币市场开发的应用程序。然而,这个应用所使用的技术是功利性的,也可以用于其他项目。换句话说,一个技术任务是一个非常通用的解决方案,主要是专门针对Kubernetes的调整,也可以用在其他行业。

我们使用的技术

该项目开始时是一个初创公司,预算有限。客户注意到,我们将为投资者定期演示,我们应该定期展示开发新功能的进展。因此,我们决定使用以下技术:

  • Node JS (NestJS框架)
  • PostgreSQL数据库
  • Kafka JS
  • Kubernetes(k8s)+Helm图表
  • Flutter
  • React

开发过程

在第一阶段,我们的主要目的是将我们的应用程序分割成微服务。在我们的案例中,我们决定创建6个微服务:

  1. 行政微服务
  2. 核心微服务
  3. 付款微服务
  4. 邮件和通知服务
  5. 缓存任务服务
  6. Webhooks微服务

值得一提的是,虽然技术栈是功利性的,可以在各种情况下使用而不需要改变,但上述的微服务却不是。它们是专门为我们项目中所需要的功能而创建的。因此,你可以使用相同的技术,但必须根据你的需要设计新的微服务。

让我们来看看如何在NestJS上制作这些微服务。我们需要为Kafka消息代理做出配置选项。因此,我们为所有微服务的通用模块和配置创建了一个共享资源文件夹。

微服务配置选项

import { ClientProviderOptions, Transport } from '@nestjs/microservices';

import CONFIG from '@application-config';

import { ConsumerGroups, ProjectMicroservices } from './microservices.enum';

const { BROKER_HOST, BROKER_PORT } = CONFIG.KAFKA;

 

export const PRODUCER_CONFIG = (name: ProjectMicroservices): ClientProviderOptions => ({

 name,

 transport: Transport.KAFKA,

 options: {

   client: {

     brokers: [`${BROKER_HOST}:${BROKER_PORT}`],

   },

 }

});

 

export const CONSUMER_CONFIG = (groupId: ConsumerGroups) => ({

 transport: Transport.KAFKA,

 options: {

   client: {

     brokers: [`${BROKER_HOST}:${BROKER_PORT}`],

   },

   consumer: {

     groupId

   }

 }

});

让我们以消费者模式将我们的Admin panel微服务连接到Kafka。这将使我们能够捕捉和处理来自主题的事件。

让应用程序在微服务模式下工作,以获得消费事件的能力。

app.connectMicroservice(CONSUMER_CONFIG(ConsumerGroups.ADMIN)); await app.startAllMicroservices();

我们可以注意到,消费者配置包括groupId。这是一个重要的选项,它将允许来自同一组的消费者从话题中获取事件,并将它们分配给对方以更快地处理它们。

例如,假设我们的微服务收到的事件比它能处理的要快。在这种情况下,我们可以使自动缩放产生额外的pod,在它们之间分享负载,并使处理速度加倍。

为了实现这一点,消费者应该在组内,在缩放后,产生的pod也将在同一个组内。因此,他们将能够共享加载,而不是处理来自不同Kafka分区的相同主题事件。

让我们看看如何在NestJS中捕捉和处理Kafka事件的例子。

消费者控制器

import { Controller } from '@nestjs/common';

import { Ctx, KafkaContext, MessagePattern, EventPattern, Payload } from '@nestjs/microservices';

 

@Controller('consumer')

export class ConsumerController {

 @MessagePattern('hero')

 readMessage(@Payload() message: any, @Ctx() context: KafkaContext) {

   return message;

 }

 

 @EventPattern('event-hero')

 sendNotif(data) {

   console.log(data);

 }

}

消费者可以在2种模式下工作。它接收事件并处理它们而不返回任何响应(EventPattern装饰器)或在处理事件后返回响应给生产者(MessagePattern装饰器)。EventPattern更好,如果可能的话,应该是首选,因为它不包含任何额外的源代码层来提供请求/响应功能。

关于生产者?

对于连接生产者,我们需要为一个负责发送事件的模块提供生产者配置。

生产者连接

import { Module } from '@nestjs/common';

import DatabaseModule from '@shared/database/database.module';

import { ClientsModule } from '@nestjs/microservices';

import { ProducerController } from './producer.controller';

import { PRODUCER_CONFIG } from '@shared/microservices/microservices.config';

import { ProjectMicroservices } from '@shared/microservices/microservices.enum';

 

@Module({

 imports: [

   DatabaseModule,

   ClientsModule.register([PRODUCER_CONFIG(ProjectMicroservices.ADMIN)]),

 ],

 controllers: [ProducerController],

 providers: [],

})

export class ProducerModule {}

基于事件的生产者

import { Controller, Get, Inject } from '@nestjs/common';

import { ClientKafka } from '@nestjs/microservices';

import { ProjectMicroservices } from '@shared/microservices/microservices.enum';

 

@Controller('producer')

export class ProducerController {

 constructor(

   @Inject(ProjectMicroservices.ADMIN)

   private readonly client: ClientKafka,

 ) {}

 

 @Get()

 async getHello() {

   this.client.emit('event-hero', { msg: 'Event Based'});

 }

}

基于请求/响应的生产者

import { Controller, Get, Inject } from '@nestjs/common';

import { ClientKafka } from '@nestjs/microservices';

import { ProjectMicroservices } from '@shared/microservices/microservices.enum';

 

@Controller('producer')

export class ProducerController {

 constructor(

   @Inject(ProjectMicroservices.ADMIN)

   private readonly client: ClientKafka,

 ) {}

 

 async onModuleInit() {

   // Need to subscribe to a topic

   // to make the response receiving from Kafka microservice possible

   this.client.subscribeToResponseOf('hero');

   await this.client.connect();

 }

 

 @Get()

 async getHello() {

   const responseBased = this.client.send('hero', { msg: 'Response Based' });

   return responseBased;

 }

}

每个微服务可以在两种模式(生产者/消费者)或两种模式(混合)中的任何一种同时工作。通常,微服务使用混合模式以达到负载平衡的目的,向主题生成事件,并均匀地消费它们,分享负载。

基于Helm图表模板的Kubernetes配置,为每个微服务实施。

管理API微服务组件及其结构由Helm图表描述

模板由几个配置文件组成:

  • 部署
  • hpa(horizontal pod autoscaler)
  • 入口控制器
  • 服务

让我们来看看每个配置文件(没有Helm模板)。

管理员-API部署

apiVersion: apps/v1

kind: Deployment

metadata:

 name: admin-api

spec:

 replicas: 1

 selector:

   matchLabels:

     app: admin-api

 template:

   metadata:

     labels:

       app: admin-api

   spec:

     containers:

     - name: admin-api

       image: xxx208926xxx.dkr.ecr.us-east-1.amazonaws.com/project-name/stage/admin-api

       resources:

         requests:

           cpu: 250m

           memory: 512Mi

         limits:

           cpu: 250m

           memory: 512Mi

       ports:

         - containerPort: 80

       env:

         - name: NODE_ENV

           value: production

 

         - name: APP_PORT

           value: "80"

部署可以包含更细的配置,如资源限制、健康检查配置、更新策略等。然而,我们提供了一个基本的配置例子,可以根据任何其他项目的需要进行扩展。

管理-API服务

---

apiVersion: v1

kind: Service

metadata:

 name: admin-api

spec:

 selector:

   app: admin-api

 ports:

   - name: admin-api-port

     port: 80

     targetPort: 80

     protocol: TCP

 type: NodePort

我们需要将服务暴露给外部世界来使用它。让我们通过一个负载均衡器暴露我们的应用程序,并提供SSL配置以使用安全的HTTPS连接。

我们需要在我们的集群上安装一个负载平衡器控制器。这里是最流行的解决方案。AWS负载平衡器控制器

然后,我们需要用以下配置创建ingress。

Admin-API Ingress Controller

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

 namespace: default

 name: admin-api-ingress

 annotations:

   alb.ingress.kubernetes.io/load-balancer-name: admin-api-alb

   alb.ingress.kubernetes.io/ip-address-type: ipv4

   alb.ingress.kubernetes.io/tags: Environment=production,Kind=application

   alb.ingress.kubernetes.io/scheme: internet-facing

   alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-2:xxxxxxxx:certificate/xxxxxxxxxx

   alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'

   alb.ingress.kubernetes.io/healthcheck-protocol: HTTPS

   alb.ingress.kubernetes.io/healthcheck-path: /healthcheck

   alb.ingress.kubernetes.io/healthcheck-interval-seconds: '15'

   alb.ingress.kubernetes.io/ssl-redirect: '443'

   alb.ingress.kubernetes.io/group.name: admin-api

spec:

 ingressClassName: alb

 rules:

   - host: example.com

     http:

       paths:

         - path: /*

           pathType: ImplementationSpecific

           backend:

             service:

               name: admin-api

               port:

                 number: 80

应用此配置后,将创建一个新的alb负载平衡器,我们需要用我们在 "主机 "参数中提供的名称创建一个域,并将流量从该主机路由到我们的负载平衡器。

Admin-API自动缩放配置

apiVersion: autoscaling/v2beta1

kind: HorizontalPodAutoscaler

metadata:

 name: admin-api-hpa

spec:

 scaleTargetRef:

   apiVersion: apps/v1

   kind: Deployment

   name: admin-api

 minReplicas: 1

 maxReplicas: 2

 metrics:

   - type: Resource

     resource:

       name: cpu

       targetAverageUtilization: 90

关于Helm是什么?

当我们想减少k8s基础设施的复杂性时,Helm变得非常有用。如果没有这个工具--我们需要写大量的yml文件,然后才能在集群上运行。

另外,我们应该牢记应用程序、标签、名称等之间的关系。然而,我们可以通过Helm使一切变得更简单。它的工作原理类似于软件包管理器,允许我们创建一个应用程序的模板,然后用简单的命令来准备和运行它。
让我们用Helm来制作我们的模板。

管理-API部署(Helm Chart)

apiVersion: apps/v1

kind: Deployment

metadata:

 name: {{ .Values.appName }}

spec:

 replicas: {{ .Values.replicas }}

 selector:

   matchLabels:

     app: {{ .Values.appName }}

 template:

   metadata:

     labels:

       app: {{ .Values.appName }}

   spec:

     containers:

     - name: {{ .Values.appName }}

       image: {{ .Values.image.repository }}:{{ .Values.image.tag }}"

       imagePullPolicy: {{ .Values.image.pullPolicy }}

       ports:

       - containerPort: {{ .Values.internalPort }}

       {{- with .Values.env }}

       env: {{ tpl (. | toYaml) $ | nindent 12 }}

       {{- end }}

管理-API服务(Helm Chart)

apiVersion: v1

kind: Service

metadata:

 name: {{ .Values.global.appName }}

spec:

 selector:

   app: {{ .Values.global.appName }}

 ports:

   - name: {{ .Values.global.appName }}-port

     port: {{ .Values.externalPort }}

     targetPort: {{ .Values.internalPort }}

     protocol: TCP

 type: NodePort

管理员-API入口(Helm Chart)

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

 namespace: default

 name: ingress

 annotations:

   alb.ingress.kubernetes.io/load-balancer-name: {{ .Values.ingress.loadBalancerName }}

   alb.ingress.kubernetes.io/ip-address-type: ipv4

   alb.ingress.kubernetes.io/tags: {{ .Values.ingress.tags }}

   alb.ingress.kubernetes.io/scheme: internet-facing

   alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }}

   alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'

   alb.ingress.kubernetes.io/healthcheck-protocol: HTTPS

   alb.ingress.kubernetes.io/healthcheck-path: {{ .Values.ingress.healthcheckPath }}

   alb.ingress.kubernetes.io/healthcheck-interval-seconds: {{ .Values.ingress.healthcheckIntervalSeconds }}

   alb.ingress.kubernetes.io/ssl-redirect: '443'

   alb.ingress.kubernetes.io/group.name: {{ .Values.ingress.loadBalancerGroup }}

spec:

 ingressClassName: alb

 rules:

   - host: {{ .Values.adminApi.domain }}

     http:

       paths:

         - path: {{ .Values.adminApi.path }}

           pathType: ImplementationSpecific

           backend:

             service:

               name: {{ .Values.adminApi.appName }}

               port:

                 number: {{ .Values.adminApi.externalPort }}

管理员-API自动缩放配置(Helm Chart)

{{- if .Values.autoscaling.enabled }}

apiVersion: autoscaling/v2beta1

kind: HorizontalPodAutoscaler

metadata:

 name: {{ include "ks.fullname" . }}

 labels:

   {{- include "ks.labels" . | nindent 4 }}

spec:

 scaleTargetRef:

   apiVersion: apps/v1

   kind: Deployment

   name: {{ include "ks.fullname" . }}

 minReplicas: {{ .Values.autoscaling.minReplicas }}

 maxReplicas: {{ .Values.autoscaling.maxReplicas }}

 metrics:

 {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}

   - type: Resource

     resource:

       name: cpu

       targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}

 {{- end }}

 {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}

   - type: Resource

     resource:

       name: memory

       targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}

 {{- end }}

{{- end }}

模板的值位于 "values.yml"、"values-dev.yml "和 "values-stage.yml "文件中。其中哪一个将被使用取决于环境。让我们来看看dev环境下的一些值的例子。

Admin-API Helm Values-Stage.yml文件

env: stage

appName: admin-api

domain: admin-api.xxxx.com

path: /*

internalPort: '80'

externalPort: '80'

 

replicas: 1

image:

 repository: xxxxxxxxx.dkr.ecr.us-east-2.amazonaws.com/admin-api

 pullPolicy: Always

 tag: latest

 

ingress:

 loadBalancerName: project-microservices-alb

 tags: Environment=stage,Kind=application

 certificateArn: arn:aws:acm:us-east-2:xxxxxxxxx:certificate/xxxxxx

 healthcheckPath: /healthcheck

 healthcheckIntervalSeconds: '15'

 loadBalancerGroup: project-microservices

 

autoscaling:

 enabled: false

 minReplicas: 1

 maxReplicas: 100

 targetCPUUtilizationPercentage: 80

 

env:

 - name: NODE_ENV

   value: stage

 

 - name: ADMIN_PORT

   value: "80"

为了在集群上应用配置,我们需要升级图表并重新启动我们的部署。

让我们看看负责这项工作的GitHub Actions步骤。

在GitHub Actions中应用Helm配置

env: stage

appName: admin-api

domain: admin-api.xxxx.com

path: /*

internalPort: '80'

externalPort: '80'

 

replicas: 1

image:

 repository: xxxxxxxxx.dkr.ecr.us-east-2.amazonaws.com/admin-api

 pullPolicy: Always

 tag: latest

 

ingress:

 loadBalancerName: project-microservices-alb

 tags: Environment=stage,Kind=application

 certificateArn: arn:aws:acm:us-east-2:xxxxxxxxx:certificate/xxxxxx

 healthcheckPath: /healthcheck

 healthcheckIntervalSeconds: '15'

 loadBalancerGroup: project-microservices

 

autoscaling:

 enabled: false

 minReplicas: 1

 maxReplicas: 100

 targetCPUUtilizationPercentage: 80

 

env:

 - name: NODE_ENV

   value: stage

 

 - name: ADMIN_PORT

   value: "80"

摘要

最终,我们研究了如何利用Kubernetes在特定情况下构建微服务。我们显然跳过了其他必须的步骤和组件,将代码样本转变为一个成熟的工作应用。然而,前述的源代码足以显示和解释Kubernetes微服务到底是如何建立的。