Helm Best Practices¶
Production-ready patterns and practices for MCP Mesh Helm deployments
Overview¶
This guide covers best practices for using Helm with MCP Mesh in production environments. You'll learn about chart development standards, security practices, performance optimization, and operational excellence. These practices are derived from real-world deployments and community standards.
Following these best practices ensures reliable, secure, and maintainable Helm deployments at scale.
Key Concepts¶
- Chart Standards: Following Helm community conventions
- Security Hardening: Protecting deployments and secrets
- Performance Optimization: Efficient chart rendering and deployment
- Operational Excellence: Monitoring, upgrading, and troubleshooting
- GitOps Integration: Declarative deployment workflows
Step-by-Step Guide¶
Step 1: Chart Development Standards¶
Follow consistent patterns for chart development:
# Chart.yaml - Comprehensive metadata
apiVersion: v2
name: mcp-mesh-agent
description: |
A Helm chart for deploying MCP Mesh agents.
This chart supports multiple agent types and configurations.
type: application
version: 1.2.3 # Chart version (SemVer)
appVersion: "1.0.0" # Application version
keywords:
- mcp-mesh
- microservices
- service-mesh
home: https://github.com/mcp-mesh/charts
sources:
- https://github.com/mcp-mesh/mcp-mesh
maintainers:
- name: Platform Team
email: platform@mcp-mesh.io
url: https://mcp-mesh.io
dependencies:
- name: common
version: "1.x.x"
repository: "https://charts.bitnami.com/bitnami"
annotations:
# Chart documentation
"artifacthub.io/readme": |
https://raw.githubusercontent.com/mcp-mesh/charts/main/charts/mcp-mesh-agent/README.md
# Security scanning
"artifacthub.io/containsSecurityUpdates": "false"
# License
"artifacthub.io/license": "Apache-2.0"
# Operator compatibility
"artifacthub.io/operator": "true"
# Recommendations
"artifacthub.io/recommendations": |
- url: https://charts.mcp-mesh.io/mcp-mesh-registry
Step 2: Values Schema Validation¶
Implement JSON Schema for values validation:
// values.schema.json
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["agent", "image"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "Number of agent replicas"
},
"image": {
"type": "object",
"required": ["repository", "tag"],
"properties": {
"repository": {
"type": "string",
"pattern": "^[a-z0-9-_/]+$",
"description": "Container image repository"
},
"tag": {
"type": "string",
"pattern": "^[a-zA-Z0-9.-]+$",
"description": "Container image tag"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"],
"default": "IfNotPresent"
}
}
},
"agent": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"pattern": "^[a-z0-9-]+$",
"minLength": 1,
"maxLength": 63,
"description": "Agent name (DNS-1123 subdomain)"
},
"capabilities": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[a-z0-9_]+$"
},
"minItems": 1,
"uniqueItems": true
},
"resources": {
"type": "object",
"properties": {
"limits": {
"$ref": "#/definitions/resourceRequirements"
},
"requests": {
"$ref": "#/definitions/resourceRequirements"
}
}
}
}
}
},
"definitions": {
"resourceRequirements": {
"type": "object",
"properties": {
"cpu": {
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?(m)?$"
},
"memory": {
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?(Mi|Gi)$"
}
}
}
}
}
Step 3: Template Best Practices¶
Write maintainable and efficient templates:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}
namespace: {% raw %}{{ .Release.Namespace }}{% endraw %}
labels:
{% raw %}{{- include "mcp-mesh-agent.labels" . | nindent 4 }}{% endraw %}
{% raw %}{{- with .Values.commonLabels }}{% endraw %}
{% raw %}{{- toYaml . | nindent 4 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
annotations:
{% raw %}{{- include "mcp-mesh-agent.annotations" . | nindent 4 }}{% endraw %}
{% raw %}{{- with .Values.commonAnnotations }}{% endraw %}
{% raw %}{{- toYaml . | nindent 4 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
spec:
{% raw %}{{- if not .Values.autoscaling.enabled }}{% endraw %}
replicas: {% raw %}{{ .Values.replicaCount }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
revisionHistoryLimit: {% raw %}{{ .Values.revisionHistoryLimit | default 10 }}{% endraw %}
selector:
matchLabels:
{% raw %}{{- include "mcp-mesh-agent.selectorLabels" . | nindent 6 }}{% endraw %}
{% raw %}{{- with .Values.updateStrategy }}{% endraw %}
strategy:
{% raw %}{{- toYaml . | nindent 4 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
template:
metadata:
annotations:
# Force pod restart on config change
checksum/config: {% raw %}{{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}{% endraw %}
checksum/secret: {% raw %}{{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}{% endraw %}
{% raw %}{{- with .Values.podAnnotations }}{% endraw %}
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
labels:
{% raw %}{{- include "mcp-mesh-agent.selectorLabels" . | nindent 8 }}{% endraw %}
{% raw %}{{- with .Values.podLabels }}{% endraw %}
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
spec:
{% raw %}{{- with .Values.imagePullSecrets }}{% endraw %}
imagePullSecrets:
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
serviceAccountName: {% raw %}{{ include "mcp-mesh-agent.serviceAccountName" . }}{% endraw %}
automountServiceAccountToken: {% raw %}{{ .Values.serviceAccount.automountToken | default false }}{% endraw %}
securityContext:
{% raw %}{{- toYaml .Values.podSecurityContext | nindent 8 }}{% endraw %}
{% raw %}{{- with .Values.priorityClassName }}{% endraw %}
priorityClassName: {% raw %}{{ . }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.hostAliases }}{% endraw %}
hostAliases:
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- if .Values.initContainers }}{% endraw %}
initContainers:
{% raw %}{{- include "mcp-mesh-agent.renderTpl" (dict "value" .Values.initContainers "context" $) | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
containers:
- name: {% raw %}{{ .Chart.Name }}{% endraw %}
securityContext:
{% raw %}{{- toYaml .Values.securityContext | nindent 12 }}{% endraw %}
image: "{% raw %}{{ .Values.image.repository }}{% endraw %}:{% raw %}{{ .Values.image.tag | default .Chart.AppVersion }}{% endraw %}"
imagePullPolicy: {% raw %}{{ .Values.image.pullPolicy }}{% endraw %}
{% raw %}{{- with .Values.command }}{% endraw %}
command:
{% raw %}{{- toYaml . | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.args }}{% endraw %}
args:
{% raw %}{{- toYaml . | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
{% raw %}{{- if .Values.env }}{% endraw %}
{% raw %}{{- include "mcp-mesh-agent.renderTpl" (dict "value" .Values.env "context" $) | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- if or .Values.envFrom .Values.agent.configMap .Values.agent.secret }}{% endraw %}
envFrom:
{% raw %}{{- with .Values.envFrom }}{% endraw %}
{% raw %}{{- toYaml . | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- if .Values.agent.configMap }}{% endraw %}
- configMapRef:
name: {% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- if .Values.agent.secret }}{% endraw %}
- secretRef:
name: {% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
ports:
- name: http
containerPort: {% raw %}{{ .Values.agent.port | default 8080 }}{% endraw %}
protocol: TCP
{% raw %}{{- if .Values.metrics.enabled }}{% endraw %}
- name: metrics
containerPort: {% raw %}{{ .Values.metrics.port | default 9090 }}{% endraw %}
protocol: TCP
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- range .Values.extraPorts }}{% endraw %}
- name: {% raw %}{{ .name }}{% endraw %}
containerPort: {% raw %}{{ .port }}{% endraw %}
protocol: {% raw %}{{ .protocol | default "TCP" }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.livenessProbe }}{% endraw %}
livenessProbe:
{% raw %}{{- toYaml . | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.readinessProbe }}{% endraw %}
readinessProbe:
{% raw %}{{- toYaml . | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.startupProbe }}{% endraw %}
startupProbe:
{% raw %}{{- toYaml . | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
resources:
{% raw %}{{- toYaml .Values.resources | nindent 12 }}{% endraw %}
{% raw %}{{- with .Values.volumeMounts }}{% endraw %}
volumeMounts:
{% raw %}{{- toYaml . | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.lifecycle }}{% endraw %}
lifecycle:
{% raw %}{{- toYaml . | nindent 12 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.sidecars }}{% endraw %}
{% raw %}{{- include "mcp-mesh-agent.renderTpl" (dict "value" . "context" $) | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.volumes }}{% endraw %}
volumes:
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.nodeSelector }}{% endraw %}
nodeSelector:
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.affinity }}{% endraw %}
affinity:
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.tolerations }}{% endraw %}
tolerations:
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.topologySpreadConstraints }}{% endraw %}
topologySpreadConstraints:
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.terminationGracePeriodSeconds }}{% endraw %}
terminationGracePeriodSeconds: {% raw %}{{ . }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.dnsPolicy }}{% endraw %}
dnsPolicy: {% raw %}{{ . }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- with .Values.dnsConfig }}{% endraw %}
dnsConfig:
{% raw %}{{- toYaml . | nindent 8 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
Step 4: Security Best Practices¶
Implement comprehensive security measures:
# templates/podsecuritypolicy.yaml
{% raw %}{{- if .Values.podSecurityPolicy.enabled }}{% endraw %}
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: {% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}
labels:
{% raw %}{{- include "mcp-mesh-agent.labels" . | nindent 4 }}{% endraw %}
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
readOnlyRootFilesystem: true
{% raw %}{{- end }}{% endraw %}
---
# templates/networkpolicy.yaml
{% raw %}{{- if .Values.networkPolicy.enabled }}{% endraw %}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}
labels:
{% raw %}{{- include "mcp-mesh-agent.labels" . | nindent 4 }}{% endraw %}
spec:
podSelector:
matchLabels:
{% raw %}{{- include "mcp-mesh-agent.selectorLabels" . | nindent 6 }}{% endraw %}
policyTypes:
- Ingress
- Egress
ingress:
# Allow traffic from registry
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: mcp-mesh-registry
ports:
- protocol: TCP
port: {% raw %}{{ .Values.agent.port | default 8080 }}{% endraw %}
# Allow metrics scraping
{% raw %}{{- if .Values.metrics.enabled }}{% endraw %}
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: {% raw %}{{ .Values.metrics.port | default 9090 }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
egress:
# Allow DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
# Allow registry access
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: mcp-mesh-registry
ports:
- protocol: TCP
port: 8080
# Allow external HTTPS
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
{% raw %}{{- end }}{% endraw %}
Step 5: Performance Optimization¶
Optimize chart rendering and deployment:
# templates/_helpers.tpl
{{/*
Efficient helper functions with caching
*/}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{% raw %}{{- define "mcp-mesh-agent.fullname" -}}{% endraw %}
{% raw %}{{- if .Values.fullnameOverride }}{% endraw %}
{% raw %}{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}{% endraw %}
{% raw %}{{- else }}{% endraw %}
{% raw %}{{- $name := default .Chart.Name .Values.nameOverride }}{% endraw %}
{% raw %}{{- if contains $name .Release.Name }}{% endraw %}
{% raw %}{{- .Release.Name | trunc 63 | trimSuffix "-" }}{% endraw %}
{% raw %}{{- else }}{% endraw %}
{% raw %}{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{{/*
Render template with caching for performance
*/}}
{% raw %}{{- define "mcp-mesh-agent.renderTpl" -}}{% endraw %}
{% raw %}{{- $value := .value -}}{% endraw %}
{% raw %}{{- $context := .context -}}{% endraw %}
{% raw %}{{- if typeIs "string" $value -}}{% endraw %}
{% raw %}{{- tpl $value $context -}}{% endraw %}
{% raw %}{{- else -}}{% endraw %}
{% raw %}{{- tpl ($value | toYaml) $context -}}{% endraw %}
{% raw %}{{- end -}}{% endraw %}
{% raw %}{{- end -}}{% endraw %}
{{/*
Common labels with minimal computation
*/}}
{% raw %}{{- define "mcp-mesh-agent.labels" -}}{% endraw %}
{% raw %}{{- if not .labels_cached }}{% endraw %}
{{- $_ := set . "labels_cached" (dict
"helm.sh/chart" (include "mcp-mesh-agent.chart" .)
"app.kubernetes.io/name" (include "mcp-mesh-agent.name" .)
"app.kubernetes.io/instance" .Release.Name
"app.kubernetes.io/version" (.Chart.AppVersion | default "0.3" | quote)
"app.kubernetes.io/managed-by" .Release.Service
) }}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- range $key, $value := .labels_cached }}{% endraw %}
{% raw %}{{ $key }}{% endraw %}: {% raw %}{{ $value }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
Step 6: Operational Excellence¶
Implement comprehensive operational practices:
# Chart testing
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}-test-connection"
labels:
{% raw %}{{- include "mcp-mesh-agent.labels" . | nindent 4 }}{% endraw %}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test-health
image: curlimages/curl:7.85.0
command: ['sh', '-c']
args:
- |
echo "Testing agent health endpoint..."
curl -f http://{% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}:{% raw %}{{ .Values.agent.port | default 8080 }}{% endraw %}/health
echo "Testing agent readiness..."
curl -f http://{% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}:{% raw %}{{ .Values.agent.port | default 8080 }}{% endraw %}/ready
echo "Testing metrics endpoint..."
{% raw %}{{- if .Values.metrics.enabled }}{% endraw %}
curl -f http://{% raw %}{{ include "mcp-mesh-agent.fullname" . }}{% endraw %}:{% raw %}{{ .Values.metrics.port | default 9090 }}{% endraw %}/metrics
{% raw %}{{- end }}{% endraw %}
echo "All tests passed!"
restartPolicy: Never
Create comprehensive documentation:
# templates/NOTES.txt
{% raw %}{{- $fullName := include "mcp-mesh-agent.fullname" . -}}{% endraw %}
โจ MCP Mesh Agent {% raw %}{{ .Values.agent.name }}{% endraw %} has been deployed!
๐ Release Information:
Name: {% raw %}{{ .Release.Name }}{% endraw %}
Namespace: {% raw %}{{ .Release.Namespace }}{% endraw %}
Version: {% raw %}{{ .Chart.Version }}{% endraw %}
Revision: {% raw %}{{ .Release.Revision }}{% endraw %}
๐ Application Details:
Agent Name: {% raw %}{{ .Values.agent.name }}{% endraw %}
Replicas: {% raw %}{{ .Values.replicaCount }}{% endraw %}
Image: {% raw %}{{ .Values.image.repository }}{% endraw %}:{% raw %}{{ .Values.image.tag | default .Chart.AppVersion }}{% endraw %}
{% raw %}{{- if .Values.agent.capabilities }}{% endraw %}
Capabilities: {% raw %}{{ .Values.agent.capabilities | join ", " }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
๐ Resources:
CPU Request: {% raw %}{{ .Values.resources.requests.cpu | default "not set" }}{% endraw %}
Memory Request: {% raw %}{{ .Values.resources.requests.memory | default "not set" }}{% endraw %}
CPU Limit: {% raw %}{{ .Values.resources.limits.cpu | default "not set" }}{% endraw %}
Memory Limit: {% raw %}{{ .Values.resources.limits.memory | default "not set" }}{% endraw %}
๐ Service Discovery:
Internal DNS: {% raw %}{{ $fullName }}{% endraw %}.{% raw %}{{ .Release.Namespace }}{% endraw %}.svc.cluster.local
Service Port: {% raw %}{{ .Values.service.port | default 8080 }}{% endraw %}
{% raw %}{{- if .Values.ingress.enabled }}{% endraw %}
๐ External Access:
{% raw %}{{- range $host := .Values.ingress.hosts }}{% endraw %}
{% raw %}{{- range .paths }}{% endraw %}
URL: http{% raw %}{{ if $.Values.ingress.tls }}{% endraw %}s{% raw %}{{ end }}{% endraw %}://{% raw %}{{ $host.host }}{% endraw %}{% raw %}{{ .path }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- else }}{% endraw %}
๐ External Access: Disabled (ingress.enabled=false)
{% raw %}{{- end }}{% endraw %}
{% raw %}{{- if .Values.autoscaling.enabled }}{% endraw %}
๐ Autoscaling:
Min Replicas: {% raw %}{{ .Values.autoscaling.minReplicas }}{% endraw %}
Max Replicas: {% raw %}{{ .Values.autoscaling.maxReplicas }}{% endraw %}
Target CPU: {% raw %}{{ .Values.autoscaling.targetCPUUtilizationPercentage }}{% endraw %}%
{% raw %}{{- end }}{% endraw %}
๐ฅ Health Checks:
Liveness: curl http://{% raw %}{{ $fullName }}{% endraw %}:{% raw %}{{ .Values.agent.port | default 8080 }}{% endraw %}/health
Readiness: curl http://{% raw %}{{ $fullName }}{% endraw %}:{% raw %}{{ .Values.agent.port | default 8080 }}{% endraw %}/ready
{% raw %}{{- if .Values.metrics.enabled }}{% endraw %}
Metrics: curl http://{% raw %}{{ $fullName }}{% endraw %}:{% raw %}{{ .Values.metrics.port | default 9090 }}{% endraw %}/metrics
{% raw %}{{- end }}{% endraw %}
๐ Common Operations:
1. Check deployment status:
kubectl rollout status deployment/{% raw %}{{ $fullName }}{% endraw %} -n {% raw %}{{ .Release.Namespace }}{% endraw %}
2. View logs:
kubectl logs -f deployment/{% raw %}{{ $fullName }}{% endraw %} -n {% raw %}{{ .Release.Namespace }}{% endraw %}
3. Scale deployment:
kubectl scale deployment/{% raw %}{{ $fullName }}{% endraw %} --replicas=5 -n {% raw %}{{ .Release.Namespace }}{% endraw %}
4. Port forward for local access:
kubectl port-forward deployment/{% raw %}{{ $fullName }}{% endraw %} 8080:{% raw %}{{ .Values.agent.port | default 8080 }}{% endraw %} -n {% raw %}{{ .Release.Namespace }}{% endraw %}
5. Run tests:
helm test {% raw %}{{ .Release.Name }}{% endraw %} -n {% raw %}{{ .Release.Namespace }}{% endraw %}
{% raw %}{{- if .Values.debug.enabled }}{% endraw %}
โ ๏ธ DEBUG MODE IS ENABLED - Not recommended for production!
{% raw %}{{- end }}{% endraw %}
For more information, visit: https://docs.mcp-mesh.io
Step 7: GitOps Integration¶
Integrate with GitOps workflows:
# argocd/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mcp-mesh-platform
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/mcp-mesh/deployments
targetRevision: HEAD
path: helm/mcp-mesh-platform
helm:
valueFiles:
- values.yaml
- values-production.yaml
parameters:
- name: image.tag
value: "1.0.0"
destination:
server: https://kubernetes.default.svc
namespace: mcp-mesh
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 10
Configuration Options¶
Practice | Configuration | Impact |
---|---|---|
Schema Validation | values.schema.json | Prevents misconfigurations |
Security Policies | podSecurityPolicy.enabled | Enforces security standards |
Network Policies | networkPolicy.enabled | Controls traffic flow |
Resource Limits | resources.limits | Prevents resource exhaustion |
Monitoring | metrics.enabled | Enables observability |
Examples¶
Example 1: Production-Ready Chart¶
# values-production.yaml
# Production-ready configuration
# High availability
replicaCount: 5
# Resource management
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
# Security hardening
podSecurityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 10001
capabilities:
drop:
- ALL
# Network policies
networkPolicy:
enabled: true
# Pod disruption budget
podDisruptionBudget:
enabled: true
minAvailable: 2
# Monitoring
metrics:
enabled: true
serviceMonitor:
enabled: true
interval: 30s
# Health checks
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 5
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# Autoscaling
autoscaling:
enabled: true
minReplicas: 5
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Example 2: Chart Lifecycle Management¶
#!/bin/bash
# chart-lifecycle.sh
# Lint chart
echo "Linting chart..."
helm lint ./mcp-mesh-agent
# Package chart
echo "Packaging chart..."
helm package ./mcp-mesh-agent
# Test chart
echo "Testing chart..."
helm install test-release ./mcp-mesh-agent \
--dry-run --debug \
--generate-name
# Security scan
echo "Security scanning..."
helm template ./mcp-mesh-agent | \
kubesec scan -
# Sign chart
echo "Signing chart..."
helm gpg sign ./mcp-mesh-agent-*.tgz
# Push to registry
echo "Pushing to registry..."
helm push mcp-mesh-agent-*.tgz oci://registry.mcp-mesh.io/charts
Best Practices¶
- Use Subcharts: Create modular, reusable components
- Version Everything: Pin all versions (charts, images, dependencies)
- Test Thoroughly: Unit tests, integration tests, upgrade tests
- Document Extensively: README, NOTES.txt, inline comments
- Secure by Default: Minimal permissions, network policies
Common Pitfalls¶
Pitfall 1: Hardcoded Values¶
Problem: Values hardcoded in templates
Solution: Always parameterize:
# Bad
image: mcp-mesh/agent:1.0.0
# Good
image: "{% raw %}{{ .Values.image.repository }}{% endraw %}:{% raw %}{{ .Values.image.tag | default .Chart.AppVersion }}{% endraw %}"
Pitfall 2: Missing Resource Limits¶
Problem: Pods without resource constraints
Solution: Always set defaults:
Testing¶
Chart Unit Testing¶
# tests/deployment_test.yaml
suite: test deployment
templates:
- deployment.yaml
tests:
- it: should create deployment with correct name
asserts:
- isKind:
of: Deployment
- equal:
path: metadata.name
value: RELEASE-NAME-mcp-mesh-agent
- it: should have security context
asserts:
- isNotNull:
path: spec.template.spec.securityContext
- equal:
path: spec.template.spec.securityContext.runAsNonRoot
value: true
- it: should have resource limits
asserts:
- isNotNull:
path: spec.template.spec.containers[0].resources.limits
- exists:
path: spec.template.spec.containers[0].resources.limits.memory
Integration Testing¶
# test_helm_deployment.py
import subprocess
import json
import time
import pytest
def helm_install(release_name, namespace):
"""Install Helm chart"""
cmd = [
"helm", "install", release_name, "./mcp-mesh-agent",
"--namespace", namespace,
"--create-namespace",
"--wait",
"--timeout", "5m"
]
subprocess.run(cmd, check=True)
def test_production_deployment():
"""Test production-ready deployment"""
namespace = "test-prod"
release = "test-release"
try:
# Install with production values
helm_install(release, namespace)
# Verify deployment
cmd = f"kubectl get deployment -n {namespace} -o json"
result = subprocess.run(cmd.split(), capture_output=True, text=True)
deployments = json.loads(result.stdout)
assert len(deployments['items']) > 0
# Check security context
deployment = deployments['items'][0]
security_context = deployment['spec']['template']['spec']['securityContext']
assert security_context['runAsNonRoot'] is True
# Check resource limits
containers = deployment['spec']['template']['spec']['containers']
assert all('resources' in c and 'limits' in c['resources']
for c in containers)
finally:
# Cleanup
subprocess.run([
"helm", "uninstall", release, "-n", namespace
])
Monitoring and Debugging¶
Monitor Chart Performance¶
# Measure template rendering time
time helm template large-release ./mcp-mesh-platform \
-f values-production.yaml > /dev/null
# Check rendered size
helm template large-release ./mcp-mesh-platform \
-f values-production.yaml | wc -c
# Profile template execution
helm template large-release ./mcp-mesh-platform \
--debug 2>&1 | grep -E "took|duration"
Debug Chart Issues¶
# Enable debug output
helm install my-release ./mcp-mesh-agent \
--debug \
--dry-run
# Check computed values
helm get values my-release --all
# Verify hooks
helm get hooks my-release
# List all resources
helm get manifest my-release | kubectl get -f -
๐ง Troubleshooting¶
Issue 1: Schema Validation Failures¶
Symptoms: values don't meet the specifications of the schema
Cause: Values don't match schema
Solution:
# Validate values against schema
helm lint ./mcp-mesh-agent --strict
# Test specific values file
helm template ./mcp-mesh-agent \
-f values-custom.yaml \
--validate
Issue 2: Template Rendering Slow¶
Symptoms: Long deployment times
Cause: Inefficient templates
Solution:
# Cache computed values
{% raw %}{{- $fullname := include "chart.fullname" . -}}{% endraw %}
{% raw %}{{- $labels := include "chart.labels" . -}}{% endraw %}
# Reuse throughout template
name: {% raw %}{{ $fullname }}{% endraw %}
labels:
{% raw %}{{- $labels | nindent 4 }}{% endraw %}
For more issues, see the section troubleshooting guide.
โ ๏ธ Known Limitations¶
- ConfigMap Size: Limited to 1MB for rendered templates
- CRD Ordering: CRDs must be installed before use
- Hooks Limitations: Limited hook weights (pre/post)
- Cross-Namespace: Helm doesn't manage cross-namespace resources well
๐ TODO¶
- Add mutation webhook examples
- Create Helm plugin for MCP Mesh
- Document OPA policy integration
- Add cost optimization practices
- Create security scanning automation
Summary¶
You now understand Helm best practices for production:
Key takeaways:
- ๐ Follow chart development standards
- ๐ Implement comprehensive security
- ๐ Optimize performance
- ๐ Test thoroughly at all levels
Next Steps¶
Return to the Helm deployment overview or explore troubleshooting.
Continue to Troubleshooting Guide โ
๐ก Tip: Use helm create
with a custom starter: helm create mychart --starter mcp-mesh-starter
๐ Reference: Helm Best Practices Guide
๐งช Try It: Create a production-ready chart for your own agent following these practices