部署与打包
本页讲怎样用应用清单 xihan.app.json 和 xihan pack 生成容器镜像与 Kubernetes、Compose、Aspire 的部署产物,以及探针、停机宽限、配置热更新、领导选举与 Native AOT 在部署时怎样对齐,适用于把应用交付到容器平台的开发与运维人员。命令的完整用法见 Cli 命令行工具。
应用清单
应用在项目旁放一份 xihan.app.json 描述自己(RFC-0016,规范 spec/packaging/app-manifest.md,Schema spec/packaging/app.schema.json):名字、项目、组合包、镜像、端口、资源绑定、配置挂载、探针、停机宽限、资源与伸缩、领导租约。xihan pack 只读这份清单,产物每次整份重新生成,不手改。它与能力包的清单 xihan.manifest.json(能力包与清单)是两回事:后者描述一个包提供与依赖什么能力,前者描述一个可部署的应用。
示例 samples/KubernetesApp/xihan.app.json:
{
"$schema": "https://spec.xihanfun.com/packaging/app.v1.json",
"name": "kubernetes-app",
"project": "KubernetesApp.csproj",
"profile": "full",
"build": { "context": "../.." },
"image": { "repository": "ghcr.io/xihanfun/kubernetes-app", "tag": "1.0.0" },
"ports": { "http": 8080, "grpc": 8081 },
"config": { "files": ["appsettings.Production.json"] },
"probes": { "startupSeconds": 60, "periodSeconds": 10, "timeoutSeconds": 2 },
"shutdown": { "graceSeconds": 25 },
"resources": { "requests": { "cpu": "100m", "memory": "128Mi" }, "limits": { "cpu": "500m", "memory": "256Mi" } },
"replicas": 2,
"scaling": { "min": 2, "max": 4, "cpuPercent": 70 },
"leader": { "lease": "kubernetes-app-scheduler" },
"environment": { "ASPNETCORE_ENVIRONMENT": "Production" }
}| 字段 | 必填 | 缺省 | 约束与作用 |
|---|---|---|---|
name | 是 | — | DNS 标签 ^[a-z][a-z0-9-]{0,62}$,不以 - 结尾;同时是镜像仓库、k8s 资源与 Compose 项目的缺省名 |
project | 是 | — | 相对清单所在目录的项目文件,必须存在 |
profile | 否 | standard | minimal / standard / full,只写进标签 xihan.profile |
build.context | 否 | . | Docker 构建上下文,相对清单所在目录,必须存在;Dockerfile 里的项目路径与 Compose 的 context / dockerfile 都相对它 |
image.repository | 否 | name | 镜像仓库,可带注册表前缀 |
image.tag | 否 | latest | 镜像标签 |
image.aot | 否 | false | 为真时以 Native AOT 发布,运行镜像为 runtime-deps |
ports.http | 否 | 8080 | 1–65535 |
ports.grpc | 否 | — | 1–65535,不等于 http;有值时多暴露一个 h2c 端点 |
bindings.<name>.type | 有绑定时必填 | — | connection:relational / cache:distributed / queue / blob / stream / configuration:remote;绑定名 ^[a-z][a-z0-9-]*$ |
bindings.<name>.secret | 否 | <name 字段>-<绑定名> | k8s Secret 名 |
config.mountPath | 否 | /etc/xihan | 绝对路径 |
config.files | 否 | [] | 文件名(不含目录分隔符),文件存在时内容进 ConfigMap,否则为 {} |
probes.startupSeconds | 否 | 60 | 不小于 periodSeconds |
probes.periodSeconds | 否 | 10 | 至少 1 |
probes.timeoutSeconds | 否 | 2 | 至少 1 |
shutdown.graceSeconds | 否 | 30 | 至少 1 |
resources.requests / limits | 否 | — | cpu、memory,k8s 数量写法(500m、512Mi) |
replicas | 否 | 1 | 至少 1 |
scaling.min / max / cpuPercent | 否 | — | min 至少 1,max 不小于 min,cpuPercent 1–100(缺省 70);有值时生成 HPA |
leader.lease | 否 | — | DNS 标签;有值时生成 Lease 的 RBAC |
environment | 否 | {} | 环境变量,键 ^[A-Za-z_][A-Za-z0-9_]*$ |
清单不合法时 xihan pack 以 CLI_VALIDATION_001 失败(退出码 2),detail 一次列出全部问题。
生成部署产物
工具可以全局安装(dotnet tool install --global XiHan.Framework.Cli --prerelease),也可以从源码运行(在仓库根执行):
dotnet run --project framework/src/XiHan.Framework.Cli -- pack --manifest framework/samples/KubernetesApp/xihan.app.json取得预览包后作为 .NET 工具安装(来源见快速上手),在清单所在目录执行:
xihan pack # 读 ./xihan.app.json,写到 ./deploy/
xihan pack --output deploy/orders --only dockerfile,kustomize成功时逐行输出写出的文件(相对输出目录),最后一行是写出的文件数与输出目录,退出码 0。产物:
| 产物 | 路径 | 内容 |
|---|---|---|
| Dockerfile | Dockerfile、Dockerfile.dockerignore | 多阶段构建;BuildKit 读与 Dockerfile 同目录的同名忽略文件,忽略 bin/、obj/、deploy/、.git/ |
| Kustomize | kustomize/*.yaml | Deployment、Service、ConfigMap、HPA(有 scaling 时)、PDB(副本大于 1 时)、ServiceAccount + Role + RoleBinding(有 leader 时)、kustomization.yaml;有绑定时另有不在 kustomization 里的 bindings.example.yaml(Secret 示例) |
| Compose | compose.yaml | 应用服务,加每个绑定一个依赖服务 |
| Aspire | aspire/XiHanAppHost.g.cs | Add<Name>(this IDistributedApplicationBuilder) 扩展方法,放进 AppHost 项目调用 |
Dockerfile
生成的 Dockerfile 以 mcr.microsoft.com/dotnet/sdk:10.0 发布,运行阶段用 chiseled 镜像 mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled(没有 shell 与包管理器),以 USER $APP_UID 非 root 运行;Kestrel 端点与内核停止超时以环境变量写入。chiseled 镜像里没有 curl 与 wget,构建阶段因此另外从内联源码编译一个就绪探针,复制到运行镜像的 /xihan/healthprobe,供 Compose 的 healthcheck 使用(见下文)。samples/KubernetesApp/deploy/Dockerfile(探针的项目与源码两段 heredoc 略去):
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG TARGETARCH
# 就绪探针:chiseled 运行镜像里没有 shell、curl 与 wget,Compose 的 healthcheck 用它探测 /health/ready
COPY <<"EOF" /xihan/healthprobe-src/XiHanHealthProbe.csproj
…
EOF
COPY <<"EOF" /xihan/healthprobe-src/Program.cs
…
EOF
RUN dotnet publish /xihan/healthprobe-src/XiHanHealthProbe.csproj --configuration Release --output /xihan/healthprobe-src/publish
WORKDIR /src
COPY . .
RUN dotnet publish "samples/KubernetesApp/KubernetesApp.csproj" --configuration Release --arch $TARGETARCH --output /app/publish --self-contained false
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled AS runtime
WORKDIR /app
COPY --from=build /app/publish .
COPY --from=build /xihan/healthprobe-src/publish /xihan/healthprobe
ENV Kestrel__Endpoints__Http__Url=http://0.0.0.0:8080
ENV Kestrel__Endpoints__Grpc__Url=http://0.0.0.0:8081 Kestrel__Endpoints__Grpc__Protocols=Http2
ENV XiHan__Kernel__StopTimeoutSeconds=25
EXPOSE 8080 8081
USER $APP_UID
ENTRYPOINT ["dotnet", "KubernetesApp.dll"]image.aot 为真时,构建阶段先装 clang 与 zlib1g-dev,以 --self-contained true -p:PublishAot=true 发布,运行阶段换成 mcr.microsoft.com/dotnet/runtime-deps:10.0-noble-chiseled,入口是原生可执行文件;就绪探针也以 Native AOT 发布(探针项目里声明 PublishAot),运行镜像只复制它的原生可执行文件 /xihan/healthprobe/XiHanHealthProbe。
Kustomize
Deployment 带三个探针、停机宽限、Pod 与容器的安全上下文(runAsNonRoot、seccompProfile: RuntimeDefault、allowPrivilegeEscalation: false、丢弃全部能力、只读根文件系统,/tmp 是 emptyDir)、资源、配置挂载与绑定挂载;Service 暴露同名的 http / grpc 端口;kustomization.yaml 的 images 写入 image.tag。
Kustomize 产物只包含应用自己,不在集群里起依赖服务:六种绑定类型一律只生成 Secret 示例 bindings.example.yaml(键 type 与 uri)与挂载,PostgreSQL、Redis、RabbitMQ、S3 兼容存储如此,Kafka(stream)与 Nacos(configuration:remote)也一样(ADR-0083)。集群里的数据库、缓存、消息与配置中心要按环境定副本数、存储类、备份与升级策略,生成器给不出通用的正确答案,由部署方用各自的 Helm Chart、Operator 或托管服务提供,再按示例创建绑定 Secret。单节点的本地联调形态见下文的 Compose 与 Aspire。
Compose 与 Aspire
Compose 用于本地联调:应用服务的 stop_grace_period 取 shutdown.graceSeconds,每个绑定一个依赖服务,以 XiHan__Bindings__<绑定名>__type / __value 注入,依赖服务健康后应用才启动。Aspire 片段把应用登记为项目资源,按绑定添加资源并把连接串注入 XiHan__Bindings__<绑定名>__value。blob 的凭据不在主值里:两者都以 RUSTFS_ACCESS_KEY=xihan、RUSTFS_SECRET_KEY=xihanxihan 启动 S3 兼容的 RustFS,并把同一对值作为绑定属性 XiHan__Bindings__<绑定名>__accessKey / __secretKey 注入,覆盖 S3 配置节里的同名键(配置「资源绑定」);其余类型的凭据已在主值里(PostgreSQL 连接串、AMQP URI,Aspire 集成资源的连接串自带口令),或依赖服务没开认证(Redis、Kafka、Nacos)。
| 绑定类型 | Compose 镜像 | 注入的 value | Aspire |
|---|---|---|---|
connection:relational | postgres:17-alpine | Host=<绑定名>;Port=5432;Database=<应用名>;Username=xihan;Password=xihan | AddPostgres(<绑定名>).AddDatabase(<应用名>) |
cache:distributed | redis:7-alpine | <绑定名>:6379 | AddRedis(<绑定名>) |
queue | rabbitmq:4-alpine | amqp://guest:guest@<绑定名>:5672 | AddRabbitMQ(<绑定名>) |
blob | rustfs/rustfs:1.0.4(S3 兼容) | http://<绑定名>:9000 | AddContainer(<绑定名>, "rustfs/rustfs", "1.0.4"),注入它的 api 端点 |
stream | apache/kafka:4.1.0(单节点 KRaft) | <绑定名>:9092 | AddKafka(<绑定名>) |
configuration:remote | nacos/nacos-server:v2.5.1(standalone) | http://<绑定名>:8848 | AddContainer(<绑定名>, "nacos/nacos-server", "v2.5.1"),注入它的 http 端点 |
这六种类型与框架 Provider 声明的绑定一一对应:持久化的 db(connection:relational)、Caching.Redis 的 cache(cache:distributed)、Messaging.RabbitMQ 的 queue(queue)、ObjectStorage.S3 的 objectstorage(blob)、Messaging.Kafka 的 stream(stream)、Configuration.Nacos 的 nacos(configuration:remote)。清单里的绑定名要写成 Provider 读取的这些名字,Provider 才取得到注入的值。
Kafka 依赖服务是单节点 KRaft(同一进程兼任 broker 与 controller),对外监听地址写成依赖服务名 PLAINTEXT://<绑定名>:9092,应用在 Compose 网络里拿到的集群元数据才可达;主题按需自动创建,内部主题副本数为 1。Nacos 依赖服务以 MODE=standalone 运行,配置存在内嵌存储里,数据目录挂命名卷 <绑定名>-data,删卷才会清空;应用要读的 dataId 先在 Nacos 里建好。两者都是 JVM 服务,healthcheck 另设 start_period(Kafka 30 秒、Nacos 60 秒),启动期内的失败不计入重试。
blob 的依赖服务是 RustFS(Apache-2.0)而不是 MinIO:MinIO 社区版已停发镜像,Docker Hub 上的 minio/minio 拉取不到。RustFS 以 /data 为数据目录(挂命名卷 <绑定名>-data),healthcheck 请求不需认证的 /health;桶不会自动创建,应用开 XiHan:ObjectStorage:S3:EnsureBuckets 或先自行建桶。S3 Provider 启动时在 XiHan:ObjectStorage:S3:HealthBucket 里探测条件写入(ADR-0089),应用要配这个键(RustFS 认条件写入,探测通过);RustFS 晚于应用就绪(连不上)时应用照常启动,探测在后台重试,通过之前不覆盖写入返回 OBJECTSTORAGE_UNAVAILABLE_004(ADR-0090);探测桶不存在或凭据被拒是配置错误,应用启动失败(KERNEL_HOSTING_002,内层 OBJECTSTORAGE_NOTFOUND_001 / OBJECTSTORAGE_DENIED_002,ADR-0093),所以桶要在应用启动前建好,或开 EnsureBuckets。选型时实测过的其余 S3 兼容服务与差异见 ADR-0086;生产环境照常连 Amazon S3 或其他兼容服务,S3 Provider 不绑定某一家。
依赖服务都不映射宿主机端口,要从宿主机访问(如打开 Nacos 控制台)时在 compose.yaml 外另加一份 override 文件声明 ports。
Compose 里的账号口令是本地联调用的固定开发值。应用服务的 healthcheck 用镜像里的就绪探针请求 http://127.0.0.1:<ports.http>/health/ready,interval、timeout、start_period 分别取 probes.periodSeconds、timeoutSeconds、startupSeconds:
healthcheck:
test: ["CMD", "dotnet", "/xihan/healthprobe/XiHanHealthProbe.dll", "8080", "/health/ready"]
interval: 10s
timeout: 2s
start_period: 60simage.aot 为真时运行镜像里没有 dotnet,test 直接执行原生探针 /xihan/healthprobe/XiHanHealthProbe。探针向本机端口发一次 HTTP/1.1 GET,2xx 退出 0,其余状态、连接失败或 10 秒无应答退出 1。Kubernetes 用 httpGet 探针,不使用它。
资源绑定
bindings 在 Kubernetes 里按 Service Binding 规范落地:每个绑定一个 Secret(键 type 与 uri)挂到 /bindings/<绑定名>,并设 SERVICE_BINDING_ROOT=/bindings。内核的 BindingResolver 先读 SERVICE_BINDING_ROOT 下的目录,再读配置节 XiHan:Bindings:<绑定名>(子键 value / type,或节本身的标量值),同名时配置整体覆盖目录来源。所以同一个应用在 k8s 里读 Secret、在 Compose 里读环境变量,代码不变。bindings.example.yaml 只是 Secret 的样板,真实连接串由部署方另行创建,不进仓库;样板以注释列出各类型可单独给出的凭据键(cache、queue、nacos 的 username / password,blob 的 accessKey / secretKey,stream 的 security.protocol、sasl.mechanism、sasl.username、sasl.password),写进 Secret 就覆盖应用配置节里的同类键,凭据可以不放进连接串。
消息代理
多副本部署接 RabbitMQ 或 Kafka 时,下面这些要在部署时定下来(消息与事件)。
实例标识。广播订阅给每个实例一个独占的队列(RabbitMQ)或消费组(Kafka),名字里带实例标识 MessagingInstance.Id。缺省每次启动生成(机器名、进程 ID 与随机后缀),不用配置;要在代理的管理界面里认出实例,可以配 XiHan:Messaging:InstanceId,但必须每个同时在线的副本都不同——重复时 Kafka 的广播静默退化成竞争,RabbitMQ 后启动的副本启动失败。Kubernetes 里用下行 API 把 Pod 名设进去,不要写在副本共用的配置文件里:
env:
- name: XiHan__Messaging__InstanceId
valueFrom:
fieldRef:
fieldPath: metadata.name固定标识在节点失联、旧连接没有及时断开时,同名重启的副本要等代理发现旧连接已断(RabbitMQ 心跳超时前启动失败,Kafka 会话超时前收不到广播);不需要认出实例就别配。
Kafka 主题。框架不建主题。集群开着 auto.create.topics.enable(Kafka 缺省,Compose 里的单节点依赖服务就是)时主题按需自动建出;生产集群关掉它时,主题不存在会让发布约 30 秒后失败(MESSAGING_UNAVAILABLE_001)、订阅一直等、死信回退重投,框架不会把它当作「没有订阅者」吞掉。这类集群在部署时预先建好应用的主题、被订阅主题的死信主题,以及框架自己发布的调度与工作流事件主题(没人订阅也要建),清单与命令见 Messaging.Kafka。
探针与停机宽限
宿主的 app.MapXiHanHealth() 映射 /health/startup、/health/ready、/health/live(生命周期与健康),生成的 Deployment 以它们做三个探针:
| 探针 | 路径 | failureThreshold |
|---|---|---|
| startupProbe | /health/startup | ⌈startupSeconds ÷ periodSeconds⌉,示例为 6 |
| readinessProbe | /health/ready | 3 |
| livenessProbe | /health/live | 3 |
三个探针的 periodSeconds 与 timeoutSeconds 取清单的 probes。内核没有进入服务状态时 /health/ready 返回 503,插件或 Provider 健康条目降级只把应用压成 Degraded,不让探针失败。
停机宽限要两边对齐:shutdown.graceSeconds 写进 XiHan:Kernel:StopTimeoutSeconds(Dockerfile 与 Deployment 都以环境变量 XiHan__Kernel__StopTimeoutSeconds 设置),Deployment 的 terminationGracePeriodSeconds 取 graceSeconds + 5。kubelet 发 SIGTERM 后,内核在 StopTimeoutSeconds 内按与启动相反的顺序停止特性并完成 Draining,多出的 5 秒保证 kubelet 不会在内核收尾前 SIGKILL。进程外插件的停止宽限缺省也取 XiHan:Kernel:StopTimeoutSeconds(插件)。示例里 graceSeconds: 25 生成 XiHan__Kernel__StopTimeoutSeconds=25 与 terminationGracePeriodSeconds: 30。
配置热更新
config.files 把列出的文件做成 ConfigMap(<name>-config)只读挂到 mountPath,并设两个环境变量:
| 环境变量 | 值 | 作用 |
|---|---|---|
XIHAN_CONFIG_PATH | mountPath(缺省 /etc/xihan) | 宿主的 AddXiHan 把这个目录里的 JSON 文件作为 reloadOnChange 配置源,插在 appsettings.{环境}.json 之后 |
DOTNET_USE_POLLING_FILE_WATCHER | 1 | ConfigMap 以切换符号链接的方式更新,文件监视必须轮询 |
Hosting.AspNetCore 与 Hosting.CommandLine 的 AddXiHan 在装配内核之前处理这个目录:只取顶层 *.json,跳过以 . 开头的条目(ConfigMap 的 ..data 之类),appsettings.json 最先装载,其余按文件名序数排序随后覆盖。这些源紧接 appsettings.{环境}.json 插入:覆盖 appsettings.json 与 appsettings.{环境}.json,环境变量与命令行参数又覆盖它们,所以 Dockerfile 与 Deployment 以环境变量写的 XiHan__Kernel__StopTimeoutSeconds 不会被 ConfigMap 里的同名键改掉(完整顺序见配置与选项)。文件变化后 IOptionsMonitor<T> 随之更新,应用不需要重启:
using Microsoft.Extensions.Options;
using XiHan.Framework.Hosting.AspNetCore;
using XiHan.Framework.Profiles.Standard;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<BannerOptions>(builder.Configuration.GetSection(BannerOptions.Section));
// AddXiHan 先装载 XIHAN_CONFIG_PATH 目录里的 JSON 文件并监听变化
builder.AddXiHan(xihan => xihan.UseStandardProfile());
var app = builder.Build();
app.UseXiHan();
app.MapXiHanHealth();
// 读 CurrentValue 而不是在启动时缓存,ConfigMap 更新后返回新值
app.MapGet("/banner", (IOptionsMonitor<BannerOptions> banner) => banner.CurrentValue.Banner);
app.Run();
public sealed class BannerOptions
{
public const string Section = "Sample";
public string Banner { get; set; } = "Hello from XiHan";
}kubelet 同步 ConfigMap 有延迟,示例里改完约一分钟内生效。要在 Native AOT 下照常绑定与重载,项目设 <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>(KubernetesApp 就这样做)。
Lease 领导选举
多副本部署时,集群唯一的作业只应在一个 Pod 上运行。leader.lease 生成 ServiceAccount、只含该 Lease 的 Role(coordination.k8s.io 的 leases:对该名字 get / update / patch,另加 create)与 RoleBinding,并在 Deployment 里设:
| 环境变量 | 来源 |
|---|---|
XiHan__Scheduling__Kubernetes__Lease | leader.lease |
XIHAN_POD_NAME | Pod 字段 metadata.name,作为 Lease 持有者 |
POD_NAMESPACE | Pod 字段 metadata.namespace |
应用在集群里装配 KubernetesSchedulingFeature(Scheduling.Kubernetes),它以 Lease 顶替进程内选举,直接调用 API Server、不依赖客户端库。KubernetesApp 按 KUBERNETES_SERVICE_HOST 是否存在决定是否装配,本地运行退回 Full 组合自带的进程内选举:
using XiHan.Framework.Hosting.AspNetCore;
using XiHan.Framework.Profiles.Full;
using XiHan.Framework.Scheduling.Kubernetes;
var builder = WebApplication.CreateBuilder(args);
// API Server 的环境变量只在 Pod 里存在
var inCluster = Environment.GetEnvironmentVariable("KUBERNETES_SERVICE_HOST") is not null;
builder.AddXiHan(xihan =>
{
xihan.UseFullProfile();
if (inCluster)
{
xihan.UseFeature<KubernetesSchedulingFeature>();
}
});
var app = builder.Build();
app.UseXiHan();
app.MapXiHanHealth();
app.Run();调度引擎与作业的写法见调度与后台作业。需要跨副本持久化作业与分发运行时换 Scheduling.Database。
Native AOT 发布
内核与原语层零反射,序列化走源生成,标为 AOT 兼容的包可以随应用以 Native AOT 发布;不兼容的包(如 Plugins.*、EF Core、Redis、RabbitMQ、JWT 等 Provider)在各自包页的信息表里标明,选型见序列化与 Native AOT。samples/SimpleApp 是 AOT 冒烟示例,CI 每次都以 Native AOT 发布它:
# Linux 上先装原生工具链
sudo apt-get install -y clang zlib1g-dev
dotnet publish framework/samples/SimpleApp/SimpleApp.csproj --configuration Release -o ./artifacts/aotSimpleApp 做到可 AOT 发布的写法:项目文件声明 <PublishAot>true</PublishAot>,不在命令行传,以免这个全局属性传给 netstandard2.0 的分析器与生成器项目(NETSDK1207);项目设 <EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator>,把最小 API 的端点委托改为编译期生成;源生成的 JsonSerializerContext 插在 ConfigureHttpJsonOptions 解析链最前面;操作契约经 Kernel.Generators 在编译期生成登记代码与 JSON Schema(Kernel.Generators)。容器里以 AOT 运行时,在应用清单里设 "image": { "aot": true },生成的 Dockerfile 自动换成原生构建与 runtime-deps 镜像。
在 kind 上验证
samples/KubernetesApp 用 Full 组合演示探针、有序停机、ConfigMap 热更新与 Lease 领导选举;它的 deploy/ 是 xihan pack 生成并提交的产物,测试 SampleDeployTests.KubernetesSample_CommittedDeployFolder_MatchesItsManifest 保证它与清单不漂移。在仓库根执行:
本地运行,确认端点:
bashdotnet run --project framework/samples/KubernetesApp curl -s localhost:5100/config # banner 来自 appsettings.json,configPath 为空 curl -s localhost:5100/leader # election 为 InMemoryLeaderElection,isLeader 为 true改了
xihan.app.json后重新生成产物:bashdotnet run --project framework/src/XiHan.Framework.Cli -- pack --manifest framework/samples/KubernetesApp/xihan.app.json构建镜像(
build.context是framework/)并载入 kind:bashcd framework docker build -f samples/KubernetesApp/deploy/Dockerfile -t ghcr.io/xihanfun/kubernetes-app:<tag> . # <tag> 是 xihan.app.json 的 image.tag kind load docker-image ghcr.io/xihanfun/kubernetes-app:<tag>部署并转发端口:
bashkubectl apply -k samples/KubernetesApp/deploy/kustomize kubectl rollout status deployment/kubernetes-app kubectl port-forward service/kubernetes-app 8080:8080逐项验证:
项 做法 预期 探针 kubectl describe pod <pod>startup /health/startup(failureThreshold: 6),readiness/health/ready,liveness/health/live有序停机 kubectl delete pod <pod>后看日志内核在 25 秒内按逆序停止特性; terminationGracePeriodSeconds: 30,kubelet 不提前 SIGKILL热更新 kubectl edit configmap kubernetes-app-config,改Sample.Banner约一分钟内 curl localhost:8080/config返回新横幅,Pod 不重启领导选举 kubectl get lease kubernetes-app-scheduler -o yaml,对两个 Pod 各请求/leader只有持有者 isLeader: true且ticks增长;删除持有者 Pod 时它在有序停机里释放 Lease,另一个 Pod 在下一次轮询(示例appsettings.json的XiHan:Scheduling:PollIntervalSeconds,1 秒)接管;持有者被强杀时等租约(XiHan:Scheduling:LeaseTimeToLiveSeconds,15 秒)过期后接管,leaseTransitions加一
相关
- Cli 命令行工具
- 生命周期与健康、配置与选项、调度与后台作业、序列化与 Native AOT
- Profiles.Full、Scheduling.Kubernetes、Hosting.AspNetCore
- 规范:RFC-0016《应用清单与打包》
