全网首发Aspire 13.1 + .NET 10 生产及K8s部署
using System.Collections.Generic; using System.Linq; using Aspire.Hosting.Kubernetes.Resources; using k8s.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Projects; using Scalar.Aspire; var builder = DistributedApplication.CreateBuilder(args); //aspire publish --publisher kubernetes --output ./k8s-artifacts Console.WriteLine($"Is IsPublishMode: {builder.ExecutionContext.IsPublishMode}"); Console.WriteLine($"Environment: {builder.Environment.EnvironmentName}"); // ===================== 1. 初始化Kubernetes环境(Aspire 13) ===================== var k8sEnv = builder.AddKubernetesEnvironment("k8s-env") .WithProperties(env => { env.DefaultImagePullPolicy = "IfNotPresent"; env.DefaultServiceType = "ClusterIP"; env.DefaultStorageClassName = "default"; }); // ===================== 2. 连接字符串配置 ===================== //var sharedCache = builder.AddConnectionString("shared-cache"); //var sharedDb = builder.AddConnectionString("shared-db"); //var dedicatedCache = builder.AddConnectionString("dedicated-cache"); //var dedicatedDb = builder.AddConnectionString("dedicated-db"); // ===================== 3. 通用K8s服务配置 ===================== Action<IResourceBuilder<ProjectResource>, bool, int?> ConfigureK8sService = (serviceBuilder, exposeNodePort, nodePort) => { var imageRepo = Environment.GetEnvironmentVariable("IMAGE_REPO") ?? "192.168.0.200:8086/aspireplatform"; var imageTag = Environment.GetEnvironmentVariable("IMAGE_TAG") ?? "latest"; serviceBuilder.PublishAsKubernetesService(k8sResource => { var service = k8sResource.Service; if (service?.Spec?.Ports is null) { return; } service.Spec.Type = exposeNodePort ? "NodePort" : "ClusterIP"; var httpPort = service.Spec.Ports.Find(p => p.Name == "http"); if (httpPort is not null) { httpPort.Port = exposeNodePort ? 80 : 8080; httpPort.TargetPort = 8080; httpPort.NodePort = exposeNodePort ? nodePort : null; } var httpsPort = service.Spec.Ports.FirstOrDefault(p => p.Name == "https"); if (httpsPort is not null) { service.Spec.Ports.Remove(httpsPort); } if (k8sResource.Workload is Deployment deploy) { deploy.Spec.Replicas = 1; deploy.Spec.RevisionHistoryLimit = 5; var imagePullSecrets = deploy.Spec.Template.Spec.ImagePullSecrets; if (imagePullSecrets is not null && imagePullSecrets.All(secret => secret.Name != "regcred")) { imagePullSecrets.Add(new LocalObjectReferenceV1 { Name = "regcred" }); } deploy.PodTemplate.Spec.Containers.ForEach(c => { var envVars = c.Env; if (envVars is null) { return; } if (envVars.All(e => e.Name != "ASPNETCORE_ENVIRONMENT")) { envVars.Add(new EnvVarV1 { Name = "ASPNETCORE_ENVIRONMENT", Value = Environment.GetEnvironmentVariable("ENVIRONMENT") ?? "Production" }); } if (envVars.All(e => e.Name != "ASPNETCORE_URLS")) { envVars.Add(new EnvVarV1 { Name = "ASPNETCORE_URLS", Value = "http://+:8080" }); } }); var container = deploy.PodTemplate.Spec.Containers.Find(c => c.Name == serviceBuilder.Resource.Name); if (container is not null) { container.Image = $"{imageRepo}/{serviceBuilder.Resource.Name}:{imageTag}"; var containerHttpPort = container.Ports.Find(p => p.Name == "http"); if (containerHttpPort is not null) { containerHttpPort.ContainerPort = 8080; } var containerHttpsPort = container.Ports.Find(p => p.Name == "https"); if (containerHttpsPort is not null) { containerHttpsPort.ContainerPort = 8081; } container.Resources = new ResourceRequirementsV1(); container.Resources.Limits.Add("cpu", "2"); container.Resources.Limits.Add("memory", "2Gi"); container.Resources.Requests.Add("cpu", "0.5"); container.Resources.Requests.Add("memory", "256Mi"); container.ReadinessProbe = new ProbeV1 { HttpGet = new HttpGetActionV1 { Path = "/health/ready", Port = 8080 }, InitialDelaySeconds = 10, PeriodSeconds = 10, TimeoutSeconds = 5, FailureThreshold = 3 }; container.LivenessProbe = new ProbeV1 { HttpGet = new HttpGetActionV1 { Path = "/health/live", Port = 8080 }, InitialDelaySeconds = 15, PeriodSeconds = 15, TimeoutSeconds = 5, FailureThreshold = 3 }; } } }); }; // ===================== 4. 注册生产环境服务 ===================== var apiService = builder.AddProject<ZhongDeAspirePlatform_ApiService>("apiservice"); var apiService1 = builder.AddProject<ZhongDeAspirePlatform_ApiService1>("apiservice1"); apiService.WithHttpHealthCheck("/health") .WithReference(apiService1); ConfigureK8sService(apiService, false, null); apiService1.WithHttpHealthCheck("/health") .WithReference(apiService); ConfigureK8sService(apiService1, false, null); var zsoundyfrontapi = builder.AddProject<ZhongDeAspirePlatform_Zsoundy_FrontApi>("frontapi") .WithHttpHealthCheck("/health"); ConfigureK8sService(zsoundyfrontapi, false, null); var zsoundybackendapi = builder.AddProject<ZhongDeAspirePlatform_Zsoundy_BackendApi>("backendapi") .WithHttpHealthCheck("/health"); ConfigureK8sService(zsoundybackendapi, false, null); var paymentapi= builder.AddProject<ZhongDeAspirePlatform_Zsoundy_PaymentApi>("paymentapi") .WithHttpHealthCheck("/health"); ConfigureK8sService(paymentapi, false, null); var usercenterapi= builder.AddProject<ZhongDeAspirePlatform_Zsoundy_UserCenterApi>("usercenterapi") .WithHttpHealthCheck("/health"); ConfigureK8sService(usercenterapi, false, null); var gateway = builder.AddProject<ZhongDeAspirePlatform_Gateway>("gateway") .WithHttpHealthCheck("/health"); var gatewayNodePort = builder.Configuration.GetValue<int?>("Gateway:NodePort") ?? 30100; ConfigureK8sService(gateway, true, gatewayNodePort); // ===================== 5. 本地开发专用服务(发布模式跳过) ===================== if (!builder.ExecutionContext.IsPublishMode && builder.Environment.IsDevelopment()) { Console.WriteLine("📦 注册本地开发服务(Python/Vue/Scalar)..."); /*
//注册 python 服务
builder.AddUvicornApp( name: "python-api", appDirectory: "C:\\Users\\Fox\\Desktop\\FastApiTest", app: "main:app") .WithHttpHealthCheck("/health111") .WithHttpEndpoint(port: 8700, targetPort: 8000, name: "python-http", env: "PORT"); builder.AddUvicornApp( name: "python-api1", appDirectory: "C:\\Users\\Fox\\Desktop\\FastApiTest1", app: "main:app") .WithHttpHealthCheck("/health") .WithHttpEndpoint(port: 8701, targetPort: 8000, name: "python-http1", env: "PORT"); // 注册前端项目 builder.AddNpmApp("Vue", "C:\\Users\\Fox\\Desktop\\your-project-name", scriptName: "dev") .WaitFor(apiService) .WaitFor(apiService1) .WithHttpEndpoint(port: 5173, name: "vue-http", env: "ASPIRE_PORT");*/ builder.AddScalarApiReference(options => { options.AddDocument("v1", "zsound") .WithTheme(ScalarTheme.Purple) .PreferHttpsEndpoint() .AllowSelfSignedCertificates(); }) .WithEnvironment("SCALAR__PREFER_HTTPS_ENDPOINT", "false") .WithEnvironment("SCALAR__ALLOW_SELF_SIGNED_CERTIFICATES", "false") .WithApiReference(apiService) .WithApiReference(apiService1) .WithApiReference(zsoundyfrontapi) .WithApiReference(zsoundybackendapi) .WithApiReference(paymentapi) .WithApiReference(usercenterapi); } else { Console.WriteLine($"🚀 发布模式/生产环境:跳过本地开发服务"); Console.WriteLine($" IsPublishMode={builder.ExecutionContext.IsPublishMode}"); Console.WriteLine($" Environment={builder.Environment.EnvironmentName}"); } builder.Build().Run();
纯C# 项目本地环境运行效果:

C# 项目 + Python 项目 + Vue3 + TS 前端项目 托管Aspire运行效果

部署Kubernetes生产环境后的效果


通过Yarp 的API 网关访问后端服务:

基于Aspire官方最佳实践并针对当前项目状况调整后的 整体架构:
抛弃了官方将 数据库、缓存、消息队列等 服务通过Aspire 进构建的想法,因为类似这样的服务我们一般在本地或云上进行独立部署和托管,符合我们自己项目使用特性,只是将API服务由Aspire进行构建。
上述看到的整体都是基于CI/CD 进行构建,非纯手工方式。


中间有比较多的坑,欢迎大家一起交流


浙公网安备 33010602011771号