Введение: Зачем запускать C# в Kubernetes?
Современная разработка на C# (.NET) всё чаще требует масштабируемой и отказоустойчивой инфраструктуры. Kubernetes (K8s) стал стандартом для оркестрации контейнеров, позволяя автоматизировать развертывание, масштабирование и управление приложениями. В этом руководстве мы покажем, как превратить ваше C#-приложение в контейнер и запустить его в кластере Kubernetes на практике.
Вы узнаете, как подготовить Docker-образ, написать манифесты для Kubernetes и выполнить деплой с помощью kubectl. Статья ориентирована на разработчиков, которые уже знакомы с основами C# и хотят освоить современные практики DevOps.
1. Подготовка C#-приложения к контейнеризации
Прежде чем деплоить приложение в Kubernetes, его необходимо упаковать в Docker-образ. Для примера возьмём простое ASP.NET Core Web API приложение.
1.1. Создание проекта
Создайте новый проект с помощью .NET CLI:
dotnet new webapi -n MyK8sApp
cd MyK8sApp
Убедитесь, что приложение работает локально:
dotnet run
По умолчанию приложение будет доступно по адресу http://localhost:5000.
1.2. Добавление Dockerfile
В корне проекта создайте файл Dockerfile (без расширения). Используйте многоступенчатую сборку для оптимизации размера образа:
# Этап сборки
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
COPY *.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish -c Release -o out
# Этап выполнения
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/out ./
ENV ASPNETCORE_URLS=http://+:80
EXPOSE 80
ENTRYPOINT ["dotnet", "MyK8sApp.dll"]
1.3. Сборка и проверка Docker-образа
Соберите образ и запустите локально:
docker build -t myk8sapp:latest .
docker run -d -p 8080:80 --name myapp myk8sapp:latest
Проверьте, что приложение отвечает: curl http://localhost:8080/weatherforecast (или откройте в браузере).
2. Написание манифестов Kubernetes
Для деплоя в Kubernetes потребуются YAML-файлы, описывающие желаемое состояние приложения. Минимальный набор включает Deployment и Service.
2.1. Создание Deployment
Файл deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myk8sapp-deployment
labels:
app: myk8sapp
spec:
replicas: 2
selector:
matchLabels:
app: myk8sapp
template:
metadata:
labels:
app: myk8sapp
spec:
containers:
- name: myk8sapp
image: myk8sapp:latest
ports:
- containerPort: 80
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
Важно: Если вы используете локальный кластер (например, Minikube или Kind), образ должен быть доступен внутри кластера. Либо укажите imagePullPolicy: Never для локальных образов.
2.2. Создание Service
Файл service.yaml:
apiVersion: v1
kind: Service
metadata:
name: myk8sapp-service
spec:
selector:
app: myk8sapp
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
Тип LoadBalancer подходит для облачных провайдеров. Для локальных кластеров используйте NodePort или ClusterIP с пробросом портов через kubectl port-forward.
3. Деплой и проверка приложения
Теперь, когда манифесты готовы, выполните деплой в кластер Kubernetes.
3.1. Применение манифестов
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
Проверьте статус подов и сервиса:
kubectl get pods
kubectl get svc
3.2. Доступ к приложению
Если вы используете Minikube, выполните:
minikube service myk8sapp-service
Для Kind или другого локального кластера используйте проброс портов:
kubectl port-forward service/myk8sapp-service 8080:80
Теперь приложение доступно по адресу http://localhost:8080.
3.3. Масштабирование и обновление
Kubernetes позволяет легко масштабировать приложение:
kubectl scale deployment myk8sapp-deployment --replicas=5
Для обновления образа измените тег в deployment.yaml и снова выполните kubectl apply. Kubernetes выполнит rolling update без downtime.
4. Типичные ошибки и их решение
- ImagePullBackOff: образ не найден в реестре. Проверьте имя образа или используйте
imagePullPolicy: Neverдля локальных тестов. - CrashLoopBackOff: приложение падает при запуске. Проверьте логи пода:
kubectl logs <pod-name>. - Порт недоступен: убедитесь, что
ASPNETCORE_URLSнастроен на порт 80, и чтоtargetPortв сервисе совпадает сcontainerPort. - Проблемы с конфигурацией: используйте ConfigMap и Secrets для хранения настроек. Пример ConfigMap для строки подклю