K8s Manifests vs Helm in ArgoCD
/ 4 min read
Series Navigation
- Part 1: Introduction to ArgoCD
- Part 2: Managing Applications with ArgoCD
- Part 3: Multi-Cluster Management with ArgoCD
- Part 4: Advanced ArgoCD Patterns
- Part 5: Real-World ArgoCD Case Studies
- Part 6: Multi-Environment Deployments
- Part 7: Environment-Specific Configurations
- Part 8: Comparing Deployment Approaches (Current)
Introduction
In this article, we’ll compare two popular approaches to deploying applications with ArgoCD:
- Using raw Kubernetes manifests with Kustomize
- Using Helm charts
We’ll implement the same application using both methods and analyze their pros and cons, with special attention to secret management.
Sample Application Overview
We’ll deploy a simple web application with:
- Frontend deployment
- Backend deployment
- Database deployment
- Required services
- Ingress configuration
- Secrets for database credentials and API keys
Approach 1: Kubernetes Manifests with Kustomize
Directory Structure
├── base/│ ├── kustomization.yaml│ ├── frontend/│ │ ├── deployment.yaml│ │ └── service.yaml│ ├── backend/│ │ ├── deployment.yaml│ │ └── service.yaml│ └── database/│ ├── statefulset.yaml│ └── service.yaml└── overlays/ ├── dev/ │ ├── kustomization.yaml │ └── secrets/ │ └── external-secret.yaml └── prod/ ├── kustomization.yaml └── secrets/ └── external-secret.yamlBase Configurations
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomizationresources: - frontend/deployment.yaml - frontend/service.yaml - backend/deployment.yaml - backend/service.yaml - database/statefulset.yaml - database/service.yamlBackend Deployment
apiVersion: apps/v1kind: Deploymentmetadata: name: backendspec: replicas: 2 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: myapp-backend:latest env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-credentials key: passwordSecret Management with External Secrets
apiVersion: external-secrets.io/v1beta1kind: ExternalSecretmetadata: name: db-credentialsspec: refreshInterval: 1h secretStoreRef: name: vault-backend kind: SecretStore target: name: db-credentials data: - secretKey: password remoteRef: key: dev/myapp/db property: passwordArgoCD Application
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: myapp-k8sspec: project: default source: repoURL: https://github.com/myorg/myapp.git path: overlays/dev targetRevision: HEAD destination: server: https://kubernetes.default.svc namespace: myapp-devApproach 2: Helm Charts
Chart Structure
├── Chart.yaml├── values.yaml├── values-dev.yaml├── values-prod.yaml└── templates/ ├── frontend/ │ ├── deployment.yaml │ └── service.yaml ├── backend/ │ ├── deployment.yaml │ └── service.yaml ├── database/ │ ├── statefulset.yaml │ └── service.yaml └── secrets/ └── external-secret.yamlValues Configuration
global: environment: production image: registry: docker.io pullPolicy: IfNotPresent
frontend: replicaCount: 2 image: repository: myapp-frontend tag: latest
backend: replicaCount: 2 image: repository: myapp-backend tag: latest
database: image: repository: postgres tag: "13"Backend Template
apiVersion: apps/v1kind: Deploymentmetadata: name: {{ include "myapp.fullname" . }}-backendspec: replicas: {{ .Values.backend.replicaCount }} selector: matchLabels: {{- include "myapp.selectorLabels" . | nindent 6 }} component: backend template: metadata: labels: {{- include "myapp.selectorLabels" . | nindent 8 }} component: backend spec: containers: - name: backend image: "{{ .Values.global.image.registry }}/{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: {{ include "myapp.fullname" . }}-db-credentials key: passwordSecret Management Template
apiVersion: external-secrets.io/v1beta1kind: ExternalSecretmetadata: name: {{ include "myapp.fullname" . }}-db-credentialsspec: refreshInterval: {{ .Values.secrets.refreshInterval }} secretStoreRef: name: {{ .Values.secrets.storeName }} kind: SecretStore target: name: {{ include "myapp.fullname" . }}-db-credentials data: - secretKey: password remoteRef: key: {{ .Values.global.environment }}/{{ include "myapp.name" . }}/db property: passwordArgoCD Application
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: myapp-helmspec: project: default source: repoURL: https://github.com/myorg/myapp.git path: helm/myapp targetRevision: HEAD helm: valueFiles: - values-dev.yaml destination: server: https://kubernetes.default.svc namespace: myapp-devComparison Analysis
Kubernetes Manifests with Kustomize
Advantages:
- Simple and straightforward
- Native Kubernetes resources
- No templating language to learn
- Easy to understand and debug
Disadvantages:
- Limited templating capabilities
- More repetitive code
- Complex value substitutions
- Manual chart versioning
Helm Charts
Advantages:
- Powerful templating
- Built-in versioning
- Easy package distribution
- Reusable components
- Built-in functions and helpers
Disadvantages:
- Learning curve for Helm templates
- More complex debugging
- Template rendering overhead
- Potential security concerns with Tiller (Helm 2)
Secret Management Comparison
Kubernetes Manifests Approach
Pros:
- Direct integration with Kubernetes secrets
- Straightforward external secrets configuration
- Easy to audit and track changes
Cons:
- Limited templating for secret names
- Manual secret rotation handling
- More verbose configuration
Helm Charts Approach
Pros:
- Template-based secret generation
- Dynamic secret naming
- Centralized secret configuration
- Easy secret rotation through values
Cons:
- More complex template debugging
- Potential security risks in values files
- Need for careful template escaping
Best Practices
-
Choose Based on Complexity
- Use K8s manifests for simple applications
- Use Helm for complex, multi-component applications
-
Secret Management
- Always use external secret management
- Implement proper RBAC
- Regular secret rotation
- Audit logging
-
Version Control
- Separate application and configuration repositories
- Use semantic versioning
- Maintain changelog
-
Deployment Strategy
- Implement proper health checks
- Use rolling updates
- Configure resource limits
Conclusion
Both approaches have their merits:
- Kubernetes Manifests: Better for simple applications and teams new to Kubernetes
- Helm Charts: Better for complex applications and experienced teams
Choose based on:
- Application complexity
- Team experience
- Maintenance requirements
- Deployment flexibility needs
Remember that both approaches can effectively manage secrets with external secret managers, but their implementation details differ significantly.