把一个 .NET 微服务跑在 Kubernetes 上,本质上要完成三件事:把服务打包成容器镜像、用 Kubernetes 的资源对象描述这个服务的运行方式、把流量正确地引导到服务实例上。听起来简单,但每一步都有不少细节值得推敲。本文以一个典型的 ASP.NET Core Web API 为例,从 Dockerfile 开始,一步步走完整个部署流程。

第一步:编写 Dockerfile,构建 .NET 服务镜像
Kubernetes 不认识你的 csproj 文件,它只认容器镜像。所以部署的第一件事就是把 .NET 项目打包成镜像。.NET 官方提供了适合生产环境的 Runtime 镜像,配合多阶段构建可以做出体积小、安全性高的镜像。
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["OrderService.csproj", "./"] RUN dotnet restore COPY . . RUN dotnet publish -c Release -o /app/publish FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime WORKDIR /app COPY --from=build /app/publish . EXPOSE 8080 ENTRYPOINT ["dotnet", "OrderService.dll"]
这里有几个点需要说明。首先,ENTRYPOINT 中指定的是编译产物 dll 的名字,要和项目名保持一致。其次,.NET 8 默认监听的端口是 8080 而不是早期的 80,如果想改端口可以通过环境变量 ASPNETCORE_URLS 来设置。最后,把 csproj 单独 COPY 再 restore 是为了利用 Docker 的层缓存机制,只要项目文件不变,依赖还原的层就会被缓存,重新构建时速度会快很多。
镜像构建好之后,需要推送到镜像仓库。如果是内网环境,可以用 Harbor 自建私有仓库;如果只是本地测试,也可以直接推到 Docker Hub 或者用 kind load docker-image、minikube image load 这类命令把镜像直接塞进本地集群,省去拉取这一步。推送到私有仓库时别忘了在集群里创建对应的 imagePullSecrets,否则 Pod 会卡在 ImagePullBackOff 状态。
第二步:编写 Deployment 和 Service 清单
镜像有了,接下来要告诉 Kubernetes 怎么运行它。最核心的两个资源对象是 Deployment 和 Service。Deployment 管的是「跑几个副本、怎么更新」,Service 管的是「这些副本在集群内部怎么被访问」。
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: registry.ippipp.com/orders/order-service:1.0.0
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080健康检查是这份清单里最容易被忽视但最关键的部分。.NET 从某个版本开始内置了健康检查中间件,在 Program 里加一行 builder.Services.AddHealthChecks() 和 app.MapHealthChecks("/healthz"),就能暴露出健康端点。readinessProbe 决定 Pod 是否接收流量,livenessProbe 决定 Pod 是否需要重启。如果服务启动较慢(比如要预热数据库连接),一定要给 livenessProbe 设置合理的 initialDelaySeconds,否则服务会被反复杀死重启,陷入崩溃循环。
资源限制同样重要。.NET 的 GC 会根据容器内存限制自动调整堆大小,但前提是你确实设置了 limits。如果不设,某个服务内存泄漏时可能拖垮整台节点。一般建议 requests 给一个偏低但真实的值,limits 给到峰值用量的 1.5 倍左右,再结合监控数据逐步调整。
第三步:通过 Ingress 对外暴露服务
Service 默认只能在集群内部访问(ClusterIP 类型),外部用户要访问服务,通常的做法是配置 Ingress。Ingress 相当于集群的七层反向代理,可以根据域名和路径把请求路由到不同的 Service,这正好契合微服务的场景。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-service-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: api.ipipp.com
http:
paths:
- path: /order
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80使用 Ingress 前需要先在集群里安装 Ingress Controller,最常见的是 ingress-nginx,可以通过 Helm 一条命令装好。配置好之后,访问 api.ipipp.com/order 的请求就会被转发到 order-service 这个 Service。如果服务之间需要相互调用,直接用 Service 名作为域名即可,比如 http://order-service/api/orders,集群自带的 DNS 会解析到对应的 Pod,这也是微服务内部通信最简单的方式。
发布、更新与常见问题排查
一切就绪后,用 kubectl apply -f 即可发布服务。更新版本时只需修改镜像 tag 重新 apply,Kubernetes 会自动执行滚动更新:先起新 Pod,等新 Pod 通过 readinessProbe 后再逐步停掉旧 Pod,整个过程服务不中断。如果新版本有问题,kubectl rollout undo deployment/order-service 一条命令就能回滚到上一个版本。
部署过程中难免遇到问题,这里整理几个高频场景。第一,Pod 一直处于 ImagePullBackOff,多半是镜像名字写错、tag 不存在,或者私有仓库认证失败,用 kubectl describe pod 看事件详情就能定位。第二,Pod 状态是 CrashLoopBackOff,通常是应用启动就抛异常,用 kubectl logs --previous 查看上一次崩溃的日志。第三,服务在集群内访问不通,检查 Service 的 selector 标签和 Pod 的 labels 是否匹配,这是最常见的低级错误。第四,配置和连接字符串这类敏感信息不要写死在 YAML 里,建议用 ConfigMap 管理普通配置、Secret 管理密钥,再通过环境变量或 volume 挂载进容器。
到这里,一个 .NET 微服务从打包、编排到暴露、运维的完整链路就跑通了。后续如果服务数量增多,还可以引入 Helm Chart 做模板化管理,或者配合 HPA 实现基于 CPU 指标的自动扩缩容,让整套体系真正具备云原生的弹性能力。
Kubernetes.NET微服务容器化部署修改时间:2026-09-07 17:02:43