A Kubernetes operator to manage Elasticsearch and OpenSearch configuration (ILM/ISM policies, Index/Component Templates, Snapshot Lifecycle/Management Policies, Snapshot Repositories, and Cluster Settings) as Kubernetes Custom Resources.
The Elastic Config Operator enables declarative management of Elasticsearch and OpenSearch configuration through Kubernetes Custom Resources. It provides automated lifecycle management for ILM/ISM policies, Index/Component Templates, Snapshot configurations, and Cluster Settings.
- Declarative Configuration: Define Elasticsearch/OpenSearch resources as Kubernetes manifests
- ECK Integration: Automatic discovery of Elasticsearch endpoints and credentials from Elastic Cloud on Kubernetes (ECK)
- External Cluster Support: Connect to any Elasticsearch/OpenSearch cluster with manual configuration
- Resource Tracking: Automatic detection and cleanup of configuration drift
- Status Reporting: Detailed phase tracking (Pending, Syncing, Ready, Error) with timestamps
- Connection Pooling: Efficient reuse of HTTP connections across reconciliation cycles
- Configurable Sync Intervals: Per-resource control of reconciliation frequency
- Dual Platform Support: Compatible with both Elasticsearch and OpenSearch
- mTLS Authentication: Support for certificate-based client authentication (mTLS) alongside username/password
| Custom Resource | Elasticsearch API | OpenSearch API | Notes |
|---|---|---|---|
ClusterSettings |
✅ Cluster Settings | ✅ Cluster Settings | Fully compatible |
ComponentTemplate |
✅ Component Templates | ✅ Component Templates | Fully compatible |
IndexLifecyclePolicy |
✅ Index Lifecycle Management (ILM) | ❌ Not supported | Elasticsearch only |
IndexStateManagement |
❌ Not supported | ✅ Index State Management (ISM) | OpenSearch only |
IndexTemplate |
✅ Index Templates | ✅ Index Templates | Fully compatible |
SnapshotLifecyclePolicy |
✅ Snapshot Lifecycle Management (SLM) | ❌ Not supported | Elasticsearch only |
SnapshotManagementPolicy |
❌ Not supported | ✅ Snapshot Management (SM) | OpenSearch only |
SnapshotRepository |
✅ Snapshot Repositories | ✅ Snapshot Repositories | Fully compatible |
The operator supports deployment via Kustomize or Helm, enabling GitOps workflows with tools like ArgoCD or FluxCD.
Reference the desired release version in your Kustomization manifest:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- https://github.com/freepik-company/elastic-config-operator/releases/download/v0.1.0/install.yamlAdd the Helm repository and install:
helm repo add elastic-config-operator https://freepik-company.github.io/elastic-config-operator/
helm repo update
helm install elastic-config-operator elastic-config-operator/elastic-config-operator \
--namespace elastic-config-operator \
--create-namespaceFor production deployments with namespace-specific RBAC:
helm install elastic-config-operator elastic-config-operator/elastic-config-operator \
--namespace elastic-config-operator \
--create-namespace \
--set rbac.secretAccess.enabled=true \
--set rbac.secretAccess.namespaces="{elasticsearch-apps,opensearch-cluster}"See the Helm Chart documentation for detailed configuration options.
Sample manifests for all resource types are available in the config/samples directory.
Define hot-warm-cold-delete lifecycle phases for Elasticsearch indices:
apiVersion: elastic-config-operator.freepik.com/v1alpha1
kind: IndexLifecyclePolicy
metadata:
name: my-ilm-policies
spec:
syncInterval: "30s" # Optional, defaults to 10s
resourceSelector:
name: elasticsearch # ECK Elasticsearch resource name
namespace: default # Optional, defaults to CR namespace
resources:
hot-warm-cold:
policy:
phases:
hot:
min_age: "0ms"
actions:
rollover:
max_age: "7d"
max_size: "50gb"
warm:
min_age: "7d"
actions:
forcemerge:
max_num_segments: 1
shrink:
number_of_shards: 1
cold:
min_age: "30d"
actions:
freeze: {}
delete:
min_age: "90d"
actions:
delete: {}Define ISM policies for OpenSearch clusters:
apiVersion: elastic-config-operator.freepik.com/v1alpha1
kind: IndexStateManagement
metadata:
name: my-ism-policies
spec:
resourceSelector:
name: opensearch
clusterType: opensearch # Required for OpenSearch
resources:
hot-warm-delete:
description: "Hot-warm-delete policy for logs"
default_state: "hot"
states:
- name: "hot"
actions:
- rollover:
min_index_age: "1d"
min_primary_shard_size: "50gb"
transitions:
- state_name: "warm"
conditions:
min_index_age: "7d"
- name: "warm"
actions:
- replica_count:
number_of_replicas: 1
- force_merge:
max_num_segments: 1
transitions:
- state_name: "delete"
conditions:
min_index_age: "30d"
- name: "delete"
actions:
- delete: {}Define reusable component templates for composable index templates:
apiVersion: elastic-config-operator.freepik.com/v1alpha1
kind: ComponentTemplate
metadata:
name: my-component-templates
spec:
resourceSelector:
name: opensearch
endpoint: https://opensearch.example.com:9200
clusterType: opensearch
certificatesSecretRef:
name: opensearch-certificates
keyCA: ca.crt
keyCert: client-tls.crt
keyKey: client-tls.key
resources:
logs-mappings:
template:
mappings:
properties:
"@timestamp":
type: date
message:
type: text
level:
type: keyword
logs-settings:
template:
settings:
number_of_shards: 1
number_of_replicas: 1Define composable index templates with mappings and settings:
apiVersion: elastic-config-operator.freepik.com/v1alpha1
kind: IndexTemplate
metadata:
name: my-index-templates
spec:
resourceSelector:
name: elasticsearch
resources:
logs-template:
index_patterns:
- "logs-*"
priority: 100
template:
settings:
number_of_shards: 1
number_of_replicas: 1
index:
lifecycle:
name: "30d-retention"
rollover_alias: "logs"
mappings:
properties:
"@timestamp":
type: date
message:
type: text
level:
type: keywordConfigure snapshot storage backends (filesystem, S3, GCS, Azure):
apiVersion: elastic-config-operator.freepik.com/v1alpha1
kind: SnapshotRepository
metadata:
name: my-snapshot-repos
spec:
resourceSelector:
name: elasticsearch
resources:
my-fs-repository:
type: fs
settings:
location: "/usr/share/elasticsearch/snapshots"
compress: trueAutomate snapshot scheduling and retention:
apiVersion: elastic-config-operator.freepik.com/v1alpha1
kind: SnapshotLifecyclePolicy
metadata:
name: my-slm-policies
spec:
resourceSelector:
name: elasticsearch
resources:
daily-snapshots:
schedule: "0 0 1 * * ?" # 1:00 AM daily (6-field cron format)
name: "<daily-snap-{now/d}>"
repository: my-fs-repository
config:
indices: ["*"]
ignore_unavailable: false
include_global_state: false
retention:
expire_after: "30d"
min_count: 5
max_count: 50Automate snapshot scheduling for OpenSearch clusters using the SM API:
apiVersion: elastic-config-operator.freepik.com/v1alpha1
kind: SnapshotManagementPolicy
metadata:
name: my-sm-policies
spec:
resourceSelector:
name: opensearch
endpoint: https://opensearch.example.com:9200
clusterType: opensearch
certificatesSecretRef:
name: opensearch-certificates
keyCA: ca.crt
keyCert: client-tls.crt
keyKey: client-tls.key
resources:
daily-snapshots:
description: "Daily snapshot policy"
creation:
schedule:
cron:
expression: "0 1 * * *"
timezone: "UTC"
time_limit: "1h"
snapshot_config:
repository: my-gcs-repository
indices: "*"
ignore_unavailable: true
deletion:
schedule:
cron:
expression: "0 2 * * *"
timezone: "UTC"
condition:
max_age: "30d"
max_count: 50
min_count: 5
time_limit: "1h"Manage persistent and transient cluster-level settings:
apiVersion: elastic-config-operator.freepik.com/v1alpha1
kind: ClusterSettings
metadata:
name: my-cluster-settings
spec:
resourceSelector:
name: elasticsearch
resources:
# Persistent settings survive cluster restarts
persistent:
cluster.routing.allocation.cluster_concurrent_rebalance: 2
cluster.routing.allocation.enable: "all"
cluster.routing.allocation.node_concurrent_recoveries: 2
cluster.blocks.read_only: false
indices.lifecycle.poll_interval: "10m"
# Transient settings are cleared on cluster restart
transient:
cluster.routing.allocation.enable: "none"When using Elastic Cloud on Kubernetes (ECK), the operator automatically discovers:
- Cluster endpoint URL
- Authentication credentials (elastic user)
- CA certificate for TLS verification
spec:
resourceSelector:
name: elasticsearch # ECK Elasticsearch resource name
namespace: default # Optional, defaults to CR namespaceFor non-ECK or external clusters with basic authentication:
spec:
resourceSelector:
name: my-cluster
endpoint: https://my-elasticsearch.example.com:9200
username: elastic
passwordSecretRef:
name: es-credentials
namespace: default
key: password
caCertSecretRef: # Optional, skip TLS verification if omitted
name: es-ca-cert
namespace: default
key: ca.crt
clusterType: elasticsearch # or "opensearch"For clusters using certificate-based authentication (mTLS). The secret must contain the CA certificate, client certificate, and client private key:
spec:
resourceSelector:
name: my-cluster
endpoint: https://opensearch.example.com:9200
clusterType: opensearch
certificatesSecretRef:
name: opensearch-certificates # Secret containing all certificates
keyCA: ca.crt # Key for CA certificate
keyCert: client-tls.crt # Key for client certificate
keyKey: client-tls.key # Key for client private keyWhen certificatesSecretRef is provided, the operator uses mTLS authentication instead of username/password. This is common for OpenSearch clusters secured with certificate-based authentication.
Configure per-resource reconciliation frequency:
spec:
syncInterval: "5m" # Accepts: "10s", "30s", "1m", "5m", "1h", etc.Default: 1m
The operator automatically detects cluster type and validates CRD compatibility:
- Elasticsearch: Use
IndexLifecyclePolicyfor ILM - OpenSearch: Use
IndexStateManagementfor ISM
Platform-specific resources:
- OpenSearch only:
IndexStateManagement(ISM),SnapshotManagementPolicy(SM) - Elasticsearch only:
IndexLifecyclePolicy(ILM),SnapshotLifecyclePolicy(SLM)
All other resource types (ClusterSettings, ComponentTemplate, IndexTemplate, SnapshotRepository) are compatible with both platforms.
View resource status with standard kubectl commands:
kubectl get indexlifecyclepoliciesNAME PHASE CLUSTER LAST SYNC AGE
my-ilm-policies Ready default/elasticsearch 2025-01-02T11:00Z 5m
Detailed status information:
kubectl describe indexlifecyclepolicy my-ilm-policiesStatus:
Phase: Ready
Message: Successfully synced 2 policies
Applied Resources:
- hot-warm-cold
- delete-after-30d
Last Sync Time: 2025-01-02T11:00:00Z
Target Cluster: default/elasticsearchThe operator maintains a connection pool indexed by <namespace>_<cluster-name>. Connections feature:
- 10-second request timeout
- Persistent HTTP keep-alive
- Automatic TLS certificate verification
- mTLS client certificate authentication support
- Credential refresh on secret changes
- Watch: Observe Custom Resource changes
- Status Update: Set phase to "Syncing"
- Connect: Retrieve or create cluster connection from pool
- Detect Type: Identify Elasticsearch vs OpenSearch
- Compare: Diff desired state (CR spec) against applied state (CR status)
- Cleanup: Remove resources deleted from CR spec
- Apply: Synchronize all resources in CR spec to cluster
- Update Status: Set phase to "Ready" with applied resource list and timestamp
CR Created → Phase: Pending
↓
Connecting → Phase: Syncing
↓
Applying → Phase: Syncing
↓
Success → Phase: Ready
↓
Modified → Re-reconcile
↓
Deleted → Cleanup from cluster
- Kubebuilder v4.0.0+
- Go 1.24.6+
- Docker 17.03+
- kubectl 1.11.3+
- Kubernetes cluster (v1.11.3+)
Create a local Kubernetes cluster (using Kind or Minikube):
kind create clusterInstall CRDs and run the operator locally:
make install runApply sample resources:
kubectl apply -k config/samples/# Run tests
make test
# Build binary
make build
# Build and push container image
export VERSION="0.1.0"
export IMG="ghcr.io/freepik-company/elastic-config-operator:v$VERSION"
make docker-build docker-push
# Deploy to cluster
make deploy IMG=$IMGThe operator requires the following Kubernetes permissions:
| Resource | Verbs | Purpose |
|---|---|---|
secrets |
get, list, watch | Read cluster credentials and TLS certificates |
elasticsearches.elasticsearch.k8s.elastic.co |
get, list, watch | Discover ECK-managed Elasticsearch clusters |
clustersettings.elastic-config-operator.freepik.com |
* | Manage Cluster Settings CRs |
componenttemplates.elastic-config-operator.freepik.com |
* | Manage Component Template CRs |
indexlifecyclepolicies.elastic-config-operator.freepik.com |
* | Manage ILM CRs |
indexstatemanagements.elastic-config-operator.freepik.com |
* | Manage ISM CRs |
indextemplates.elastic-config-operator.freepik.com |
* | Manage Index Template CRs |
snapshotlifecyclepolicies.elastic-config-operator.freepik.com |
* | Manage SLM CRs |
snapshotmanagementpolicies.elastic-config-operator.freepik.com |
* | Manage SM CRs (OpenSearch) |
snapshotrepositories.elastic-config-operator.freepik.com |
* | Manage Snapshot Repository CRs |
kubectl logs -n elastic-config-operator deployment/elastic-config-operator-controller-manager -fkubectl describe indexlifecyclepolicy <name>Connection Failures
Error: failed to connect to Elasticsearch
- Verify cluster is running:
kubectl get elasticsearch - Check secret exists and contains valid credentials
- Verify network connectivity and firewall rules
Cluster Type Mismatch
Error: OpenSearch clusters use ISM instead of ILM
- Use
IndexStateManagementfor OpenSearch clusters - Use
IndexLifecyclePolicyfor Elasticsearch clusters
SLM Cron Format
Error: invalid schedule: must be a valid cron expression
- Elasticsearch SLM requires 6-field cron format (includes seconds)
- Example:
0 0 1 * * ?(1:00 AM daily)
Status Stuck in Syncing
- Check operator logs for detailed error messages
- Verify cluster accessibility and authentication
- Review timeout settings (default: 10s per request)
TLS Certificate Verification
Error: tls: failed to verify certificate
- Ensure
caCertSecretRefis correctly configured - For ECK, verify CR namespace matches cluster namespace
- Check certificate SANs match the endpoint hostname
-
Run test suite:
make test -
Set version and image:
export VERSION="0.1.0" export IMG="ghcr.io/freepik-company/elastic-config-operator:v$VERSION"
-
Build and push image:
make docker-build docker-push
-
Generate installation manifests:
make build-installer
This project is built with Kubebuilder. Contributions are welcome via pull requests. All submissions require:
- Passing test suite (
make test) - Code review approval
- Adherence to Go best practices
- Clear commit messages describing changes
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
