🍧 概述
✨ Quick Start
☁️ 集成Kubernetes
🎭 Kubernetes无感升级
🍢 集成Consul
⚓ 普通代理模式
🥨 错误重试
🎡 权限验证
🎉 GRPC
👍 WebSocket
🧊 集成Swagger
Carp.Gateway 是.NET下生态的网关 基于微软的Yarp实现
支持Kubernetes、Consul
-
创建 .NET 8.0 WebAPI
-
NuGet 安装Carp.Gateway
Install-Package Carp.Gateway
- Program.cs
using Daily.Carp.Extension;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCarp();
var app = builder.Build();
app.UseCarp();
app.Run();
- appsettings.json
"Carp": {
"Routes": [
{
"Descriptions": "Quick Start ",
"ServiceName": "Demo",
"PathTemplate": "/api/{**catch-all}", //客户端请求路由
"TransmitPathTemplate": "{**catch-all}", //下游转发路由
"DownstreamHostAndPorts": [ "https://www.baidu.com", "https://www.jd.com" ]
}
]
}
- 运行项目观看效果把~
适配Kubernetes
Install-Package Carp.Gateway.Provider.Kubernetes
using Daily.Carp.Extension;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCarp().AddKubernetes();
var app = builder.Build();
app.UseCarp();
app.Run();
Kubernetes服务发现类型说明
在Carp中Kubernetes服务发现支持两种方式
1.ClusterIP(默认方式)
ClusterIP提供了一种在集群内部进行服务发现和负载均衡的机制。
builder.Services.AddCarp().AddKubernetes(KubeDiscoveryType.ClusterIP);
2.Endpoint
Endpoint = PodIP + ContainerPort,Carp会将一个Service中的所有的Endpoint交给Yarp进行管理,当Pod发生变化时(例如滚动更新时),Carp会实时更新Yarp配置
builder.Services.AddCarp().AddKubernetes(KubeDiscoveryType.EndPoint);
配置文件
配置文件中ServiceName对应Kubernetes中的ServiceName
"Carp": {
"Kubernetes": {
"Namespace": "dev"
},
"Routes": [
{
"Descriptions": "基础服务集群",
"ServiceName": "basics",
"PathTemplate": "/basics/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http"
},
{
"Descriptions": "主业务服务集群",
"ServiceName": "business",
"PathTemplate": "/business/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http"
},
{
"Descriptions": "登录服务集群",
"ServiceName": "lgcenter",
"PathTemplate": "/login/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http"
},
{
"Descriptions": "日志服务的集群",
"ServiceName": "logs",
"PermissionsValidation": ["Jwt"],
"PathTemplate": "/log/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http"
}
]
}
Gateway部署
在部署Gateway项目时,需要创建Role并增加读取service和pod的权限,然后创建ServiceAccount,将Role绑定给 ServiceAccount
在Deployment中指定 serviceAccount、serviceAccountName
yaml文件参考
kind: Deployment
apiVersion: apps/v1
metadata:
name: gateway
namespace: dev
labels:
app: gateway
spec:
replicas: 2
selector:
matchLabels:
app: gateway
template:
metadata:
creationTimestamp: null
labels:
app: gateway
spec:
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
type: File
containers:
- name: gateway
image: 192.168.1.1:8000/service/gateway:dev.20231005.18.42.41
ports:
- containerPort: 8888
protocol: TCP
volumeMounts:
- name: localtime
readOnly: true
mountPath: /etc/localtime
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
serviceAccount: gateway
serviceAccountName : gateway
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
securityContext: {}
imagePullSecrets:
- name: harbor-admin-secret
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- gateway
topologyKey: kubernetes.io/hostname
schedulerName: default-scheduler
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
---
apiVersion: v1
kind: Service
metadata:
name: gateway
namespace: dev
labels:
app: gateway
spec:
type: NodePort
ports:
- port: 8888
targetPort: 8888
nodePort: 31000
selector:
app: gateway
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gateway
namespace: dev
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
creationTimestamp: null
name: read-endpoints
namespace: dev
rules:
- apiGroups:
- ""
resources:
- endpoints
- pods
- services
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: null
name: permissive-binding
namespace: dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: read-endpoints
subjects:
- kind: ServiceAccount
name: gateway
namespace: dev
在K8S中我们在部署更新服务的时候,旧的Pod会被Kill,新的Pod会生成并逐步替换。但是在这个工作过程中旧的Pod在被Kill时可能还会有一些流量会被调度到该Pod,会导致一些请求出现错误 一般为502,为了解决这个问题,我们引入两个动作。
就绪探针
Pod在启动完毕后,我们为了确保容器正确启动并可以接收请求,会暴漏一个api,该api接收K8S的心跳探测,如果成功并状态码返回200 则代表该Pod已经可以处理流量
readinessProbe:
httpGet:
path: /api/health/index
port: 5000
scheme: HTTP
initialDelaySeconds: 15
timeoutSeconds: 5
periodSeconds: 30
successThreshold: 1
failureThreshold: 3
preStop钩子
容器在被下达Kill的命令后,仍然可以处理一段时间的请求,这个是至关重要的,解决了容器在退出的过程中的一瞬间流量被命中后无法处理的情况
lifecycle:
preStop:
exec:
command:
- /bin/sh
- '-c'
- sleep 10
using Com.Ctrip.Framework.Apollo;
using Com.Ctrip.Framework.Apollo.Core;
using Daily.Carp.Extension;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddCarp().AddConsul(); //添加Consul支持
builder.Services.AddControllers();
var app = builder.Build();
app.UseStaticFiles();
app.UseCarp();
app.MapControllers();
app.Run();
"Carp": {
"Consul": {
"Host": "localhost",
"Port": 8500,
"Protocol": "http",
"Token": "",
"Interval": 2000 //轮询查询更新Consul Service信息 ,默认3秒 单位毫秒
},
"Routes": [
{
"Descriptions": "简单的例子",
"ServiceName": "DemoService",
"LoadBalancerOptions": "RoundRobin",
"PathTemplate": "basics/{**catch-all}"
}
]
}
Install-Package Carp.Gateway
using Daily.Carp.Extension;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCarp();
var app = builder.Build();
app.UseCarp();
app.Run();
"Carp": {
"Namespace": "dev",
"Routes": [
{
"Descriptions": "基础服务集群",
"ServiceName": "basics",
"PathTemplate": "/basics/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http",
"DownstreamHostAndPorts" : [ "192.168.0.112:8001","192.168.0.113:8001"]
},
{
"Descriptions": "主业务服务集群",
"ServiceName": "business",
"PathTemplate": "/business/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http",
"DownstreamHostAndPorts" : [ "192.168.0.114:8001","192.168.0.115:8001"]
}
]
}
根据域名转发
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Carp": {
"Routes": [
{
"Descriptions": "Jd域名转发",
"ServiceName": "Jd",
"PathTemplate": "{**catch-all}",
"Hosts": [ "jd.daily.com" ],
"TransmitPathTemplate": "{**catch-all}",
"DownstreamHostAndPorts": [ "https://jd.com" ]
},
{
"Descriptions": "Baidu域名转发",
"ServiceName": "Baidu",
"PathTemplate": "{**catch-all}",
"Hosts": [ "baidu.daily.com" ],
"TransmitPathTemplate": "{**catch-all}", //下游转发路由
"DownstreamHostAndPorts": ["https://baidu.com" ]
}
]
},
"AllowedHosts": "*"
}
在Demos/Grpc中有详细的例子
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Carp": {
"Routes": [
{
"Descriptions": "简单的例子",
"ServiceName": "Basics",
"PathTemplate": "/basics/{**catch-all}", //客户端请求路由
"TransmitPathTemplate": "/Basics/{**catch-all}", //下游转发路由
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "192.168.1.113:31000" ]
},
//WebSocket转发
{
"Descriptions": "WebSocket服务器",
"ServiceName": "ImServer",
"PathTemplate": "/imServer/{**catch-all}",
"TransmitPathTemplate": "/imServer/{**catch-all}",
"DownstreamHostAndPorts": [ "wss://192.168.1.113:30000" ]
}
]
},
"AllowedHosts": "*"
}
app.UseCarp(options =>
{
options.EnableAuthentication = true; //启用权限验证
options.CustomAuthenticationAsync.Add("Jwt", async () => //这里的 “Jwt” 对应的是配置文件中的PermissionsValidation数组中的值
{
//自定义鉴权逻辑
var flag = true;
//验证逻辑
flag = false;
//.....
return await Task.FromResult(flag);
});
//可以多个
options.CustomAuthenticationAsync.Add("Signature", async () => //这里的 “Signature” 对应的是配置文件中的PermissionsValidation数组中的值
{
//自定义鉴权逻辑
var flag = true;
//验证逻辑
flag = false;
//.....
return await Task.FromResult(flag);
});
});
"Carp": {
"Routes": [
{
"Descriptions": "基础服务集群",
"ServiceName": "basics",
"PermissionsValidation": ["Jwt","Signature"], //验证Jwt和Signature
"PathTemplate": "/Basics/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "192.168.1.113:31000" ]
},
{
"Descriptions": "主业务服务集群",
"ServiceName": "business",
"PermissionsValidation": ["Signature"], // 只验证Signature
"PathTemplate": "/Business/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "192.168.1.113:32000" ]
}
]
}
状态码大于400才会触发重试
{
"Carp": {
"Routes": [
{
"Descriptions": "简单的例子",
"ServiceName": "Basics",
"PathTemplate": "/Basics/{**catch-all}",
"TransmitPathTemplate": "{**catch-all}",
"DownstreamHostAndPorts": [ "https://jd.com", "https://xxx.aasd.casd", "https://xxx.aasasd.casd", "https://xxx.aassssasd.casd" ],
"RetryPolicy": { //重试策略
"RetryCount": 2, //默认3次,重试次数
"RetryOnStatusCodes": [ "5xx","404" ] //可以不配置,默认5xx
}
}
]
}
}
在ASP.NET Core中已经内置了限流中间件
ASP.NET Core 中的速率限制中间件 | Microsoft Learn
推荐使用SwaggerUI库 - Knife4jUI ,体验会更好
# 安装Swagger
Install-Package Swashbuckle.AspNetCore
#这是IGeekFan版本
Install-Package IGeekFan.AspNetCore.Knife4jUI
#这是我自己修改版,支持了鉴权、身份认证、各种优化,以下代码基于我的修改版
Install-Package AspNetCore.Knife4jUI
using Daily.Carp.Extension;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using System.Data;
using Daily.Carp;
using IGeekFan.AspNetCore.Knife4jUI;
var builder = WebApplication.CreateBuilder(args);
//添加Carp配置
builder.Services.AddCarp().AddKubernetes();
builder.Services.AddControllers();
//添加Swagger配置
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "CARP Gateway API", Version = "v1" });
c.CustomOperationIds(apiDesc =>
{
var controllerAction = apiDesc.ActionDescriptor as ControllerActionDescriptor;
return controllerAction.ControllerName + "-" + controllerAction.ActionName;
});
c.CustomSchemaIds(type => type.FullName); //swagger支持动态生成的api接口
});
var app = builder.Build();
app.UseKnife4UI(c =>
{
c.Authentication = true; //开启鉴权
c.Password = "123456"; //设置密码
//配置服务swagger信息
c.SwaggerEndpoint("basics/swagger/v1/swagger.json", "Basics API");
c.SwaggerEndpoint("Business/swagger/v1/swagger.json", "Business API");
});
app.MapControllers();
app.Run();
//以下DEMO基于Kubernetes
{
"Carp": {
"Kubernetes": {
"Namespace": "test"
},
"Routes": [
{
"Descriptions": "基础服务集群",
"ServiceName": "basics",
"PathTemplate": "/basics/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http"
},
{
"Descriptions": "主业务服务集群",
"ServiceName": "business",
"PathTemplate": "/business/{**catch-all}",
"LoadBalancerOptions": "PowerOfTwoChoices",
"DownstreamScheme": "http"
}
]
}
}