اپلیکیشن ASP.NET Core روی سیستم شما اجرا میشود، اما بعد از انتقال به کلاستر پاسخ نمیدهد، مدام Restart میشود یا تنظیمات Production را نمیخواند. دیپلوی dotnet core روی کوبرنتیز زنجیرهای از تصمیمهاست: انتشار برنامه، ساخت Image، تعیین پورت، تزریق تنظیمات، تعریف Health Check و در دسترس قرار دادن سرویس.
در این راهنما یک مسیر قابلآزمایش میسازیم و توضیح میدهیم چه زمانی مدیریت مستقیم Kubernetes منطقی است و چه زمانی بهتر است Build و عملیات روزمره را به پاستا بسپارید.
پاسخ سریع: برای دیپلوی dotnet core به چه چیزهایی نیاز دارید؟
برای استقرار یا Deploy یک API نوشتهشده با ASP.NET Core، ابتدا برنامه را داخل Container Image قرار میدهید. سپس در Kubernetes یک Deployment برای اجرای Podها و یک Service برای مسیریابی ترافیک داخلی تعریف میکنید. تنظیمات و Secretها باید از Image جدا باشند و Health Check وضعیت آمادهبودن برنامه را بسنجد.
قبل از ارسال Image به Registry یا شروع Build، آن را محلی اجرا کنید. برنامه باید روی همان پورتی گوش دهد که در تنظیمات استقرار معرفی کردهاید. در Imageهای ASP.NET Core مربوط به .NET 8، پورت پیشفرض Container از 80 به 8080 تغییر کرده است؛ بنابراین تنظیمات نسخههای قدیمی ممکن است سرویس فعال اما غیرقابلدسترسی بسازد. توضیح رسمی پورت Container در مستندات Microsoft
مدل ذهنی درست برای استقرار ASP.NET Core چیست؟
زنجیره استقرار را به پنج بخش تقسیم کنید:
-
خروجی برنامه: آیا dotnet publish بدون خطا اجرا میشود؟
-
Image: آیا Container مستقل از محیط توسعه بالا میآید؟
-
تنظیمات Runtime: پورت، Connection String و متغیرهای محیطی چگونه وارد برنامه میشوند؟
-
Kubernetes: آیا Deployment، Service و Probe به پورت و مسیر درست اشاره میکنند؟
-
ورودی عمومی: آیا دامنه و TLS ترافیک را به Service صحیح میرسانند؟
این تفکیک عیبیابی را سادهتر میکند. موفقشدن Build به معنی سالمبودن Runtime نیست؛ Running بودن Pod هم ثابت نمیکند Service به برنامه دسترسی دارد.
چگونه یک Dockerfile مناسب برای ASP.NET Core بسازیم؟
در داکر دات نت بهتر است Build و Runtime را جدا کنید. در Multi-stage build، مرحله اول از SDK برای Restore و Publish استفاده میکند و Image نهایی فقط Runtime و فایلهای منتشرشده را نگه میدارد. در نتیجه، وابستگیهای Build وارد Image اجرایی نمیشوند. مستندات Docker درباره Multi-stage build
نمونه زیر فرض میکند فایل پروژه MyApi.csproj و اسمبلی خروجی MyApi.dll نام دارد. نسخه Image را با Target Framework پروژه هماهنگ کنید.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApi.csproj", "./"]
RUN dotnet restore "./MyApi.csproj"
COPY . .
RUN dotnet publish "./MyApi.csproj" \
-c Release \
-o /app/publish \
--no-restore
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApi.dll"]
کپی جداگانه فایل csproj باعث میشود لایه Restore تا زمان تغییر وابستگیهای پروژه قابل استفاده مجدد باشد. برای بررسی دستورها و الگوهای پروژههای چندبخشی، راهنمای «Dockerfile چیست و چگونه بنویسیم، با نمونههای آماده» را ببینید.
Image را محلی بسازید:
docker build -t myapi:local .
این دستور یک Image محلی با Tag مشخصشده میسازد. اجرای دوباره آن Build را تکرار میکند و ممکن است از Cache استفاده کند؛ فایلهای پروژه تغییر نمیکنند.
سپس Container را اجرا کنید:
docker run --rm -p 8080:8080 myapi:local
این دستور پورت 8080 سیستم را به پورت 8080 داخل Container متصل میکند. گزینه --rm پس از توقف، Container را حذف میکند اما Image باقی میماند. حالا Endpoint برنامه، برای مثال http://localhost:8080/health، را بررسی کنید. اگر پاسخی ندارید، پیش از ورود به Kubernetes پورت، نام DLL و لاگ Startup را اصلاح کنید.
مانیفست Kubernetes چگونه نوشته میشود؟
نمونه فرضی زیر یک Deployment و Service میسازد. نام Image، Registry، مسیر Health Check و مقادیر منابع را متناسب با پروژه تغییر دهید.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapi
spec:
replicas: 2
selector:
matchLabels:
app: myapi
template:
metadata:
labels:
app: myapi
spec:
containers:
- name: myapi
image: registry.example.com/myapi:1.0.0
ports:
- name: http
containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: Production
- name: ASPNETCORE_HTTP_PORTS
value: "8080"
readinessProbe:
httpGet:
path: /health/ready
port: http
livenessProbe:
httpGet:
path: /health/live
port: http
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: myapi
spec:
selector:
app: myapi
ports:
- name: http
port: 80
targetPort: http
این اعداد فقط مقادیر یک مثال فرضیاند، نه پیشنهاد ظرفیت برای همه پروژهها. مصرف سرویس را اندازه بگیرید و Requests و Limits را بر همان اساس تنظیم کنید.
Readiness مشخص میکند Pod چه زمانی آماده دریافت ترافیک است. Liveness وضعیتی را تشخیص میدهد که فرایند اجرا میشود اما سالم نیست. مسیرهای بالا باید در برنامه پیادهسازی شده باشند؛ استفاده از آنها بدون Endpoint متناظر باعث شکست Probe میشود. راهنمای رسمی Probeهای Kubernetes
برای مقایسه همین ساختار در اکوسیستم Java، مقاله «دیپلوی Spring Boot روی کوبرنتیز» را ببینید.
تنظیمات و Connection String را کجا نگه داریم؟
تنظیمات غیرحساس مانند نام محیط یا Feature Flag میتوانند در ConfigMap باشند. رمز دیتابیس، Token و کلید API باید بیرون از Repository و Dockerfile بمانند و از مسیر Secret وارد Runtime شوند.
در ASP.NET Core، دو آندرلاین جداکننده سلسلهمراتبی متغیرهای محیطی است. برای نمونه، ConnectionStrings__MainDb به کلید ConnectionStrings:MainDb نگاشت میشود. مقدار واقعی را در Command history یا مانیفست ثبتشده در Git قرار ندهید.
راهنمای «ConfigMap و Secret برای مدیریت امن تنظیمات» تفاوت این دو منبع و روش تزریق آنها را توضیح میدهد. Kubernetes نیز ConfigMap و Secret را دو نوع اصلی برای ارائه تنظیمات به Pod معرفی میکند. مستندات رسمی تنظیمات Kubernetes