全网首发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# 项目本地环境运行效果:

image

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

image

 部署Kubernetes生产环境后的效果

image

 

image

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

image

 

基于Aspire官方最佳实践并针对当前项目状况调整后的 整体架构:

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

image

 

image

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

81c69d05015af8bdad46804a390095ca

 

posted @ 2026-01-20 12:51  zhuweirandy  阅读(13)  评论(0)    收藏  举报