Kubernetes é o padrão de facto para orquestração de containers em produção. Helm é o gerenciador de pacotes do Kubernetes — ele empacota todos os recursos (Deployment, Service, Ingress, ConfigMap, HPA) num chart reutilizável e versionado. Juntos, eliminam a duplicação de YAMLs entre ambientes e tornam o deploy de aplicações .NET reproduzível e auditável.
Dockerfile otimizado para .NET
Antes do Kubernetes, a imagem Docker precisa ser enxuta:
# Multi-stage build: imagem final sem SDK (.NET runtime apenas) FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /srcCopia apenas os arquivos de projeto primeiro (cache layer otimizado)
COPY [“src/NeryxApp.API/NeryxApp.API.csproj”, “src/NeryxApp.API/”] COPY [“src/NeryxApp.Application/NeryxApp.Application.csproj”, “src/NeryxApp.Application/”] COPY [“src/NeryxApp.Infrastructure/NeryxApp.Infrastructure.csproj”, “src/NeryxApp.Infrastructure/”] RUN dotnet restore “src/NeryxApp.API/NeryxApp.API.csproj”
Copia o código e faz o build
COPY . . WORKDIR “/src/src/NeryxApp.API” RUN dotnet build -c Release -o /app/build
FROM build AS publish RUN dotnet publish -c Release -o /app/publish —no-restore
/p:UseAppHost=false
/p:PublishTrimmed=falseImagem final: apenas runtime (sem SDK) — muito menor
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final WORKDIR /app
Usuário não-root por segurança
RUN adduser —disabled-password —gecos "" appuser USER appuser
COPY —from=publish /app/publish .
Health check nativo do Docker (Kubernetes usa probes, mas é bom ter)
HEALTHCHECK —interval=30s —timeout=5s —start-period=10s —retries=3
CMD curl -f http://localhost:8080/health || exit 1
EXPOSE 8080 ENTRYPOINT [“dotnet”, “NeryxApp.API.dll”]
Criando o Helm Chart
# Estrutura de um chart .NET:
helm create neryx-api
# Estrutura gerada (e o que vamos customizar):
neryx-api/
├── Chart.yaml # metadados do chart
├── values.yaml # valores padrão
├── values-staging.yaml # overrides para staging
├── values-prod.yaml # overrides para produção
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── configmap.yaml
├── hpa.yaml # Horizontal Pod Autoscaler
└── _helpers.tpl # templates reutilizáveis
# Chart.yaml:
apiVersion: v2
name: neryx-api
description: API .NET da Neryx Digital Architects
type: application
version: 0.1.0
appVersion: "1.0.0"
# values.yaml — valores padrão (desenvolvimento):
replicaCount: 1
image:
repository: ghcr.io/neryx/neryx-api
pullPolicy: IfNotPresent
tag: "latest" # substituído no CI/CD pelo SHA do commit
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
hosts:
- host: api.neryx.com.br
paths:
- path: /
pathType: Prefix
tls:
- secretName: neryx-api-tls
hosts:
- api.neryx.com.br
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
# Configurações da aplicação (não-sensíveis)
config:
ASPNETCORE_ENVIRONMENT: "Production"
ASPNETCORE_URLS: "http://+:8080"
Logging__LogLevel__Default: "Warning"
# Referências a Secrets do Kubernetes (criados separadamente)
secretName: neryx-api-secrets
Templates principais
# templates/deployment.yaml: apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "neryx-api.fullname" . }} labels: {{- include "neryx-api.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "neryx-api.selectorLabels" . | nindent 6 }} strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 # zero downtime deploy template: metadata: labels: {{- include "neryx-api.selectorLabels" . | nindent 8 }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: 8080# Variáveis de configuração não-sensíveis via ConfigMap envFrom: - configMapRef: name: {{ include "neryx-api.fullname" . }}-config # Variáveis sensíveis via Secret (connection strings, API keys) - secretRef: name: {{ .Values.secretName }} # Liveness probe: reinicia o pod se a app travar livenessProbe: httpGet: path: /health/live port: 8080 initialDelaySeconds: 10 periodSeconds: 15 failureThreshold: 3 # Readiness probe: remove do balanceador se não pronto readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3 # Startup probe: dá tempo extra na inicialização a frio startupProbe: httpGet: path: /health/live port: 8080 failureThreshold: 30 periodSeconds: 5 resources: {{- toYaml .Values.resources | nindent 12 }}
# templates/configmap.yaml — variáveis não-sensíveis:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "neryx-api.fullname" . }}-config
data:
{{- range $key, $value := .Values.config }}
{{ $key }}: {{ $value | quote }}
{{- end }}
# templates/hpa.yaml — escala automática por CPU/memória:
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "neryx-api.fullname" . }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "neryx-api.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
Health checks em .NET para o Kubernetes
// Program.cs — health checks para liveness e readiness: builder.Services.AddHealthChecks() .AddCheck("liveness", () => HealthCheckResult.Healthy(), tags: ["live"]) .AddNpgsql( // verifica conexão com banco builder.Configuration.GetConnectionString("Default")!, name: "database", tags: ["ready"]) .AddRedis( // verifica Redis builder.Configuration["Redis:ConnectionString"]!, name: "redis", tags: ["ready"]) .AddUrlGroup( // verifica serviço externo new Uri("https://api.externa.com/health"), name: "api-externa", tags: ["ready"]);// Endpoints separados para liveness e readiness: app.MapHealthChecks(“/health/live”, new HealthCheckOptions { // Liveness: apenas verifica se o processo está vivo (sem checks externos) Predicate = check => check.Tags.Contains(“live”), ResultStatusCodes = { [HealthStatus.Healthy] = StatusCodes.Status200OK, [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable, } });
app.MapHealthChecks(“/health/ready”, new HealthCheckOptions { // Readiness: verifica banco, cache e dependências externas Predicate = check => check.Tags.Contains(“ready”), ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse });
Pipeline CI/CD com GitHub Actions
# .github/workflows/deploy.yml: name: Build e Deployon: push: branches: [main]
env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}
jobs: build-and-deploy: runs-on: ubuntu-latest permissions: contents: read packages: write
steps: - uses: actions/checkout@v4 - name: Login no GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build e push da imagem Docker uses: docker/build-push-action@v5 with: context: . push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest cache-from: type=gha cache-to: type=gha,mode=max - name: Configurar kubectl uses: azure/setup-kubectl@v3 - name: Configurar kubeconfig run: | mkdir -p ~/.kube echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config - name: Deploy com Helm run: | helm upgrade --install neryx-api ./helm/neryx-api \ --namespace production \ --create-namespace \ --values ./helm/neryx-api/values-prod.yaml \ --set image.tag=${{ github.sha }} \ --set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \ --wait \ # aguarda pods ficarem Ready --timeout 5m \ --atomic # rollback automático se falhar
Gerenciando Secrets de forma segura
# Nunca commite Secrets no Git! Crie separado no Kubernetes: kubectl create secret generic neryx-api-secrets \ --namespace production \ --from-literal=ConnectionStrings__Default="Host=db;Database=neryx;Username=app;Password=..." \ --from-literal=Redis__ConnectionString="redis:6379,password=..." \ --from-literal=Jwt__Secret="sua-chave-jwt-256-bits" \ --dry-run=client -o yaml | kubectl apply -f -Para ambientes AWS/Azure, use External Secrets Operator:
O operator busca os secrets do AWS Secrets Manager ou Azure Key Vault
e os injeta automaticamente como Kubernetes Secrets
apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: neryx-api-secrets spec: refreshInterval: 1h secretStoreRef: name: aws-secrets-manager kind: ClusterSecretStore target: name: neryx-api-secrets data: - secretKey: ConnectionStrings__Default remoteRef: key: production/neryx-api property: db-connection-string
Conclusão
Helm transforma o deploy de aplicações .NET em Kubernetes numa operação previsível e versionada. O chart com values por ambiente elimina a duplicação de YAMLs, o HPA escala automaticamente baseado em CPU/memória, e o pipeline CI/CD com --atomic garante rollback automático se algo der errado. O resultado: deploys com zero downtime e rastreabilidade completa.
Se você precisa containerizar e orquestrar aplicações .NET em Kubernetes — seja EKS, GKE ou AKS — a Neryx tem experiência com todo o processo, da criação dos Dockerfiles ao pipeline de deploy. Consultoria inicial gratuita.
Leitura complementar: