譯者 | 李睿
審校 | 孫淑娟
為了說明如何使用Kubernetes,建議考慮本文介紹的區(qū)塊鏈案例,并探討為加密貨幣市場(chǎng)開發(fā)的應(yīng)用程序。這個(gè)應(yīng)用程序使用的技術(shù)是實(shí)用的,也可以用于其他項(xiàng)目。換句話說,技術(shù)任務(wù)是一個(gè)非常通用的解決方案,主要針對(duì)Kubernetes進(jìn)行了專門調(diào)整,也可以用于其他行業(yè)。
使用的技術(shù)
該項(xiàng)目是一個(gè)啟動(dòng)項(xiàng)目,預(yù)算有限。而開發(fā)人員將定期為投資者提供演示,并定期展示開發(fā)新功能的進(jìn)展情況。因此決定使用以下技術(shù):
- Node JS(NestJS 框架)
- Postgre SQL數(shù)據(jù)庫
- Kafka JS
- Kubernetes(k8s)+Helm圖表
- Flutter
- React
開發(fā)過程
對(duì)于第一階段,開發(fā)人員的主要目的是將其應(yīng)用程序拆分為微服務(wù)。在這一案例中,決定創(chuàng)建6個(gè)微服務(wù)。
(1)管理微服務(wù)
(2)核心微服務(wù)
(3)支付微服務(wù)
(4)郵件和通知服務(wù)
(5)Cron任務(wù)服務(wù)
(6)Webhooks微服務(wù)
值得一提的是,雖然技術(shù)堆棧是實(shí)用的,并且可以在各種情況下使用而無需更改,但上述微服務(wù)卻不是,它們是專門為這一項(xiàng)目中所需的功能而創(chuàng)建的。因此可以使用相同的技術(shù),但必須根據(jù)需要設(shè)計(jì)新的微服務(wù)。
以下了解如何在NestJS上制作這些微服務(wù)。需要為Kafka消息代理進(jìn)行配置選項(xiàng)。因此,為所有微服務(wù)的常用模塊和配置創(chuàng)建了一個(gè)共享資源文件夾。
微服務(wù)配置選項(xiàng)
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
}
}
});
以消費(fèi)者模式將管理面板微服務(wù)連接到Kafka。它將允許捕獲和處理來自主題的事件。
使應(yīng)用程序在微服務(wù)模式下工作,以便能夠使用事件:
app.connectMicroservice(CONSUMER_CONFIG(ConsumerGroups.ADMIN)); await app.startAllMicroservices();
可以注意到消費(fèi)者配置包含groupId。這是一個(gè)重要的選項(xiàng),它將允許來自同一組的消費(fèi)者從主題中獲取事件,并將它們分發(fā)給彼此以更快地處理它們。
例如,假設(shè)微服務(wù)接收事件的速度快于處理它們的速度。在這種情況下,可以進(jìn)行自動(dòng)擴(kuò)展以生成額外的pod以在它們之間共享負(fù)載,并使該過程加快兩倍。
為了實(shí)現(xiàn)這一點(diǎn),消費(fèi)者應(yīng)該在組中,并且在擴(kuò)展之后,生成的pod也將在同一個(gè)組中。因此,他們將能夠共享加載,而不是處理來自不同Kafka分區(qū)的相同主題事件。
以下了解一個(gè)如何在NestJS中捕獲和處理Kafka事件的示例:
消費(fèi)者控制器
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);
}
}
消費(fèi)者可以在兩種模式下工作。它接收事件并處理它們而不返回任何響應(yīng)(EventPattern
decorator),或者在處理事件后將響應(yīng)返回給生產(chǎn)者(MessagePattern
decorator)。EventPattern更好,如果可能的話應(yīng)該是首選,因?yàn)樗话魏晤~外的源代碼層來提供請(qǐng)求/響應(yīng)功能。
生產(chǎn)者呢?
對(duì)于連接生產(chǎn)者,需要為負(fù)責(zé)發(fā)送事件的模塊提供生產(chǎn)者配置。
生產(chǎn)者連接
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 {}
基于事件的生產(chǎn)者
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'});
}
}
基于請(qǐng)求/響應(yīng)的生產(chǎn)者
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;
}
}
每個(gè)微服務(wù)可以同時(shí)在兩種模式(生產(chǎn)者/消費(fèi)者)或兩種模式(混合)下工作。通常情況下,微服務(wù)使用混合模式來達(dá)到負(fù)載平衡的目的,為主題生成事件并均勻地使用它們,共享負(fù)載。
基于Helm圖表模板的Kubernetes配置,針對(duì)每個(gè)微服務(wù)實(shí)現(xiàn)。

Helm圖表描述的Admin API微服務(wù)組件及其結(jié)構(gòu)
該模板由幾個(gè)配置文件組成:
- 部署
- hpa(水平pod自動(dòng)擴(kuò)展器)
- 入口控制器
- 服務(wù)
以下來看每個(gè)配置文件(沒有Helm模板)
Admin-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"
部署可以包含更多的精簡配置,如資源限制、健康檢查配置、更新策略等。但是提供了一個(gè)基本配置示例,可以根據(jù)任何其他項(xiàng)目的需要進(jìn)行擴(kuò)展。
Admin-API服務(wù)
--
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
需要將服務(wù)暴露給外界才能使用它。通過負(fù)載均衡器公開的應(yīng)用程序,并提供SSL配置以使用安全的HTTPS連接。
需要在集群上安裝一個(gè)負(fù)載均衡控制器。這是最流行的解決方案:AWS負(fù)載均衡器控制器。
然后,需要使用以下配置創(chuàng)建入口:
Admin-API入口控制器
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
應(yīng)用這一配置后,將創(chuàng)建一個(gè)新的alb負(fù)載均衡器,需要使用在“host”參數(shù)中提供的名稱創(chuàng)建一個(gè)域,并將流量從該主機(jī)路由到負(fù)載均衡器。
Admin-API自動(dòng)擴(kuò)展配置
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呢?
當(dāng)想要降低K8s基礎(chǔ)設(shè)施的復(fù)雜性時(shí),Helm變得非常有用。如果沒有這個(gè)工具——需要編寫很多yml文件才能在集群上運(yùn)行它。
此外,應(yīng)該記住應(yīng)用程序、標(biāo)簽、名稱等之間的關(guān)系。但是,可以使用Helm使一切變得更簡單。它的工作方式類似于包管理器,允許創(chuàng)建應(yīng)用程序的模板,然后使用簡單的命令準(zhǔn)備和運(yùn)行它。
使用Helm來制作模板:
Admin-API 部署(Helm圖表)
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
Admin-API 服務(wù)(Helm圖表)
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 }}
Admin-API自動(dòng)擴(kuò)展配置(Helm圖表)
{{- 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”文件中。使用哪一個(gè)取決于環(huán)境。檢查一下dev
env的一些值的示例。
Admin-API Helm Values-Stage.yml File
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"
要在集群上應(yīng)用配置,需要升級(jí)圖表并重新啟動(dòng)部署。
以下檢查負(fù)責(zé)此操作的GitHub Actions步驟。
在GitHub操作中應(yīng)用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"
結(jié)語
最終,本文研究了如何在特定案例中使用Kubernetes構(gòu)建微服務(wù)。顯然跳過了其他必備步驟和組件,將代碼示例轉(zhuǎn)換為成熟的工作應(yīng)用程序。但是,上述源代碼足以展示和解釋Kubernetes微服務(wù)是如何構(gòu)建的。
原文標(biāo)題:??Blockchain Case Using Kubernetes??,作者:Tetiana Stoyk