FluxCD GitOps Made Simple: A Follow-up
In my previous post, "FluxCD GitOps Made Simple: My Journey to Automated Kubernetes Deployments", I mentioned wanting to follow up on how to setup the ConfigMap automatically following GitOps principles. This post discusses the various next steps I took in order to achieve that.
Some required techniques and technologies
To achieve a proper GitOps setup with automated ConfigMap management, I leveraged three key technologies that work together seamlessly:
1. Kustomize configMapGenerator
Kustomize's configMapGenerator is a powerful feature that automatically creates
ConfigMaps from files, literals, or environment files. According to the official Kustomize
documentation, this generator creates ConfigMaps by reading files, environment files, or
literal key-value pairs. The key advantage is that it automatically handles ConfigMap naming and
updates when the source files change.
The configMapGenerator supports several sources:
- Files: Direct file references that get embedded as ConfigMap data
- Environment files: Key-value pairs from .env files
- Literals: Direct key-value pairs specified inline
This approach eliminates the need to manually create and maintain ConfigMaps, ensuring they're always synchronized with your Git repository.
2. Git Submodules for Component Management
Git submodules allow you to include other Git repositories as subdirectories within your main repository. As documented in the Git Submodules guide, this feature enables you to:
- Reference external repositories: Include component-specific configurations from separate repositories
- Maintain version control: Each submodule can be pinned to specific commits or branches
- Separate concerns: Keep component configurations isolated from the main FluxCD repository
- Enable team collaboration: Different teams can manage different components independently
In my setup, I used submodules to reference separate repositories containing Helm chart values and
configurations for each component (static-website, api-java, chatbot-ui, etc.). This approach was
particularly effective for managing the previous values.yaml content that I had
accumulated.
3. FluxCD HelmRelease valuesFrom
FluxCD's valuesFrom feature in HelmRelease resources allows you to reference multiple
ConfigMaps and merge their values. According to the FluxCD
HelmRelease documentation, this feature supports:
- Multiple ConfigMap references: Combine values from several ConfigMaps
- Ordered merging: Values are merged in the order specified, with later values overriding earlier ones
- Inline value overrides: Direct values in the HelmRelease take final precedence
- Secret references: Also supports referencing Kubernetes Secrets for sensitive data
I implemented a layered configuration approach using this feature:
- Base ConfigMap: Contains common values shared across all environments
- Environment ConfigMap: Contains environment-specific overrides
- Inline values: Contains frequently changing values like image tags
This layered approach provides the flexibility to maintain consistency across environments while allowing for environment-specific customizations and rapid updates to frequently changing values.
A new folder structure
Here's the complete folder structure I implemented, required after some Kustomization validation errors with the proper path to the Git submodules folders:
fluxcd-gitops-repo/
├── .gitmodules # Git submodule definitions
├── clusters/
│ ├── base/ # Base configurations
│ │ ├── apps/examples/ # Base HelmRelease definitions
│ │ └── infrastructure/ # Base infrastructure components
│ ├── components/ # Git submodules for each component
│ │ ├── static-website/ # Static website component
│ │ │ ├── base.values.yaml # Base configuration
│ │ │ ├── lcl.values.yaml # Local environment values
│ │ │ └── Chart.yaml # Helm chart definition
│ │ ├── api-java/ # Java API component...
│ │ ├── chatbot-ui/ # Chatbot UI component...
│ ├── local/ # Local environment
│ │ ├── apps/examples/ # Local-specific HelmRelease patches
│ │ ├── infrastructure/ # Local infrastructure patches
│ │ └── kustomization.yaml # Local environment kustomization
│ └── sandbox/ # Sandbox environment
│ ├── apps/examples/ # Sandbox-specific HelmRelease patches
│ ├── infrastructure/ # Sandbox infrastructure patches
│ └── kustomization.yaml # Sandbox environment kustomization
└── README.md
The Layered Configuration Approach
One of the key insights I discovered was implementing a layered configuration approach that follows GitOps principles while maintaining flexibility across different environments. Here's how it works and its benefits.
Benefits of This Approach
- Environment Consistency: Base configurations ensure consistency across environments
- Environment Flexibility: Environment-specific values allow for customization
- Component Reusability: Components can be easily shared and reused
- GitOps Compliance: All configurations are version-controlled and follow GitOps principles
- Automated Updates: Image automation and ConfigMap generation reduce manual work
- Scalability: Easy to add new components and environments
1. Base Configuration Layer
The foundation of my approach starts with base.values.yaml files in each component
submodule. These contain values that are common across all environments and should rarely change:
# Example: clusters/components/static-website/base.values.yaml
replicaCount: 1
image:
pullPolicy: Always
service:
type: ClusterIP
port: 80
targetPort: 80
resources:
requests:
cpu: 1m
memory: 48Mi
autoscaling:
minReplicas: 1
maxReplicas: 2
targetCPUUtilizationPercentage: 80
2. Environment-Specific Layer
Each environment (local, sandbox, production) has its own values file that overrides only what's necessary:
# Example: clusters/components/static-website/lcl.values.yaml (local)
image:
repository: static-web-example
imagePullSecrets:
environment:
LOG_LEVEL: "debug"
autoscaling:
enabled: false
# Example: clusters/components/static-website/snbx.values.yaml (sandbox)
image:
repository: registry.gitlab.com/your-org/static-web-example
imagePullSecrets: "registry-key"
autoscaling:
enabled: true
3. Dynamic Values Layer
The final layer consists of values that change frequently and are managed directly in the HelmRelease:
# In HelmRelease values section
values:
image:
tag: "1.0.1" # {"$imagepolicy": "flux-system:static-web-imgpolicy:tag"}
replicaCount: 1
This is only an example. Each component must be reviewed to determine the best option for each value item.
Git Submodules for Component Management
I implemented Git submodules to manage individual component configurations separately from the main FluxCD repository. This approach provides several benefits and follows the FluxCD repository structure guidelines:
- Separation of Concerns: Each component has its own repository with its own lifecycle
- Independent Versioning: Components can be updated independently
- Reusability: Components can be shared across different projects
- Security: Different teams can have different access levels to different components
Here's how the submodules are configured in .gitmodules:
[submodule "clusters/components/static-website"]
path = clusters/components/static-website
url = https://gitlab.com/your-org/static-web-chart.git
[submodule "clusters/components/chatbot-ui"]
path = clusters/components/chatbot-ui
url = https://gitlab.com/your-org/chatbot-chart.git
ConfigMapGenerator Implementation
The configMapGenerator in Kustomization is the key to automatically creating ConfigMaps
from the values files. This follows the Kustomize kustomization.yaml specification. Here's how it's implemented:
configMapGenerator:
- name: static-web-base-values
namespace: default
files:
- base.values.yaml=../components/static-website/base.values.yaml
- name: static-web-level-values
namespace: default
files:
- lcl.values.yaml=../components/static-website/lcl.values.yaml
- name: api-java-base-values
namespace: default
files:
- base.values.yaml=../components/api-java/base.values.yaml
- name: api-java-level-values
namespace: default
files:
- lcl.values.yaml=../components/api-java/lcl.values.yaml
This approach automatically generates ConfigMaps for each component and environment combination, ensuring that the values are always up-to-date with the Git repository.
FluxCD HelmRelease Integration
The valuesFrom feature in FluxCD HelmRelease allows us to reference multiple ConfigMaps
and merge their values. This approach is inspired by the FluxCD Kustomize
Helm example:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: static-web-helmrelease
namespace: default
spec:
valuesFrom:
- kind: ConfigMap
name: static-web-base-values
valuesKey: base.values.yaml
- kind: ConfigMap
name: static-web-level-values
valuesKey: lcl.values.yaml
values:
image:
repository: registry.gitlab.com/your-org/static-web-example
tag: "1.0.1" # {"$imagepolicy": "flux-system:static-web-imgpolicy:tag"}
The values are merged in order: base values are applied first, then environment-specific values override them, and finally, inline values take precedence.
Environment-Specific Kustomization
Each environment has its own Kustomization file that references the appropriate values files:
- Local Environment: Uses
lcl.values.yamlfor development-specific settings - Sandbox Environment: Uses
snbx.values.yamlfor testing configurations - Production Environment: Would use
prod.values.yamlfor production settings
In other words, the folders for each environment in FluxCD contains a Kustomization.yaml file referring to all the base files to include, the patches files to include (which refers to the proper level since it is known from the file perspective which environment I'm building) and an item for each ConfigMap to generate from which file. Although a better Kustomization could potentially simplify that file, reducing the duplication between each environment, I chose to keep it simple and referencing directly the files to be explicit; since I am weak technically on Kustomize, I have found it easier for me!
A few gotchas
One thing to note, in FluxCD and using GitLab, you have to use a submodule using the HTTPS instead of the SSH due to the authentication mechanism of FluxCD. This is related to how FluxCD GitRepository sources handle authentication.
Kustomization is a pain! I had to change and re-change my FluxCD Git repo folder structure to ensure I was meeting all security requirements. And even then, I'm still failing on one validation which for now, doesn't seem to have an impact. More to investigate here!!
When the content of one of the base or level values file changed, it is a bit more cumbersome to bring it updated. You have to first update the component Git repo and commit the changes. then, in the submodule repo folder, which is under the main FluxCD git repo, do a git pull. Then commit those changes to the FluxCD git repo and reconcile everything, which means the git source as well as the helmrelease.
Additional Challenges Encountered
- FluxCD Reconciliation: After updating submodules, you need to trigger reconciliation of both the GitSource and HelmRelease
- Image Automation: Ensuring that image automation works correctly with the layered approach requires careful configuration
Conclusion
The current setup marks a significant step forward in adopting robust GitOps principles. By layering configurations through Git submodules, configMapGenerator, and FluxCD’s valuesFrom, the system now supports better automation, clarity, and environment-specific overrides—all driven by version-controlled changes.
The layered configuration approach with Git submodules and ConfigMapGenerator has significantly improved my GitOps workflow. While there are still challenges to overcome, particularly around secret management and infrastructure components, the foundation is now in place for a robust, scalable GitOps implementation.
Next Steps
- Secret Management: Evaluate and implement secure, GitOps-friendly secret handling mechanisms such as Sealed Secrets or External Secrets Operator
- Infrastructure GitOps: Refactor critical services like cert-manager and Istio into fully managed GitOps deployments
References and Further Reading
This implementation draws inspiration from several official examples and documentation from FluxCD or Git:
- FluxCD Multi-Tenancy Example - Shows how to structure repositories for multi-tenant environments
- FluxCD Kustomize Helm Example - Demonstrates the integration of Kustomize with Helm charts
- FluxCD Repository Structure Guide - Best practices for organizing FluxCD repositories
- FluxCD GitRepository Documentation - Official documentation for Git repository sources
- Kustomize kustomization.yaml Specification - Official Kustomize configuration reference
- Kustomize configMapGenerator Documentation - Detailed guide for ConfigMap generation
- Git Submodules Guide - Comprehensive documentation on Git submodules
AI Usage Disclosure
This document was created with assistance from AI tools. The content has been reviewed and edited by a human. For more information on the extent and nature of AI usage, please contact the author.