Skip to content

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

  1. Use Subcharts: Create modular, reusable components
  2. Version Everything: Pin all versions (charts, images, dependencies)
  3. Test Thoroughly: Unit tests, integration tests, upgrade tests
  4. Document Extensively: README, NOTES.txt, inline comments
  5. 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:

# values.yaml
resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 100m
    memory: 128Mi

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