mirror of
https://github.com/lukaszraczylo/kubemirror.git
synced 2026-06-05 22:43:51 +00:00
76237c6bf6
Discloses the single anonymous adoption ping sent on startup and points users to the upstream README section for full opt-out instructions instead of duplicating the table here.
989 lines
34 KiB
Markdown
989 lines
34 KiB
Markdown
# KubeMirror
|
|
|
|
A production-ready Kubernetes controller for automatically mirroring any resource type across namespaces with intelligent synchronization and minimal API overhead.
|
|
|
|
Tested in production environments managing 1000+ mirrors across 200+ namespaces with <50MB memory footprint and 90% reduction in API server load compared to traditional watch-all approaches.
|
|
|
|
- [KubeMirror](#kubemirror)
|
|
- [Why This Project Exists](#why-this-project-exists)
|
|
- [Features](#features)
|
|
- [Important Releases](#important-releases)
|
|
- [Quick Start](#quick-start)
|
|
- [Prerequisites](#prerequisites)
|
|
- [Installation](#installation)
|
|
- [Using Helm (Recommended)](#using-helm-recommended)
|
|
- [Verifying Release Signatures](#verifying-release-signatures)
|
|
- [Using kubectl](#using-kubectl)
|
|
- [Usage Examples](#usage-examples)
|
|
- [Mirror a Secret to Specific Namespaces](#mirror-a-secret-to-specific-namespaces)
|
|
- [Mirror to Pattern-Matched Namespaces](#mirror-to-pattern-matched-namespaces)
|
|
- [Mirror to All Namespaces](#mirror-to-all-namespaces)
|
|
- [Mirror to All Labeled Namespaces](#mirror-to-all-labeled-namespaces)
|
|
- [Mirror Custom Resources (CRDs)](#mirror-custom-resources-crds)
|
|
- [Using with ExternalSecrets Operator](#using-with-externalsecrets-operator)
|
|
- [Configuration](#configuration)
|
|
- [Helm Chart Values](#helm-chart-values)
|
|
- [Command-line Flags](#command-line-flags)
|
|
- [Resource Auto-Discovery](#resource-auto-discovery)
|
|
- [Architecture](#architecture)
|
|
- [Components](#components)
|
|
- [How It Works](#how-it-works)
|
|
- [Performance Optimizations](#performance-optimizations)
|
|
- [Supported Resources](#supported-resources)
|
|
- [Monitoring](#monitoring)
|
|
- [Production Recommendations](#production-recommendations)
|
|
- [High-Throughput Configuration](#high-throughput-configuration)
|
|
- [Multi-Tenant Configuration](#multi-tenant-configuration)
|
|
- [Development Configuration](#development-configuration)
|
|
- [Troubleshooting](#troubleshooting)
|
|
- [Common Issues](#common-issues)
|
|
- [Debugging](#debugging)
|
|
- [Development](#development)
|
|
- [Building](#building)
|
|
- [Testing](#testing)
|
|
- [Releasing](#releasing)
|
|
- [Roadmap](#roadmap)
|
|
- [Documentation](#documentation)
|
|
- [License](#license)
|
|
|
|
## Why This Project Exists
|
|
|
|
Kubernetes doesn't provide a native way to share resources like Secrets, ConfigMaps, or custom resources across namespaces. Existing solutions either:
|
|
- Watch all resources cluster-wide (massive API overhead)
|
|
- Require manual duplication (maintenance nightmare)
|
|
- Only support specific resource types (not extensible)
|
|
- Don't detect drift or handle cleanup properly
|
|
|
|
KubeMirror solves this with:
|
|
- **Server-side filtering** - 90%+ reduction in API load vs. watch-all approaches
|
|
- **Universal support** - Works with any Kubernetes resource type including CRDs
|
|
- **Intelligent sync** - Multi-layer change detection avoids unnecessary updates
|
|
- **Production-ready** - Leader election, metrics, graceful shutdown, comprehensive testing
|
|
|
|
## Features
|
|
|
|
| Category | Feature |
|
|
|----------|---------|
|
|
| **Resources** | Mirror any Kubernetes resource type - Secrets, ConfigMaps, Ingresses, Services, CRDs, and more |
|
|
| **Resources** | Auto-discovery of all mirrorable resources with periodic refresh |
|
|
| **Resources** | Safety deny list prevents mirroring dangerous resources (Pods, Events, Nodes) |
|
|
| **Targeting** | Mirror to specific namespaces, patterns (`app-*`), `all` namespaces, or `all-labeled` (opt-in) |
|
|
| **Targeting** | Configurable maximum targets per source (default: 100) |
|
|
| **Targeting** | `all-labeled` requires namespace opt-in via `kubemirror.raczylo.com/allow-mirrors` label |
|
|
| **Sync** | Multi-layer change detection: generation field + SHA256 content hash |
|
|
| **Sync** | Automatic drift detection and correction for manually modified mirrors |
|
|
| **Sync** | Finalizer-based cleanup ensures mirrors are deleted with source |
|
|
| **Sync** | Metadata filtering - source kubemirror labels/annotations never copied to mirrors |
|
|
| **Transform** | Modify resources during mirroring with transformation rules |
|
|
| **Transform** | Static values, Go templates, map merging, and field deletion |
|
|
| **Transform** | Template functions: upper, lower, replace, trimPrefix, default, etc. |
|
|
| **Transform** | Sandboxed execution with timeout protection and size limits |
|
|
| **Performance** | Cluster-scoped watches with server-side filtering (label selector) |
|
|
| **Performance** | O(1) reverse lookups via field indexing (target → source) |
|
|
| **Performance** | Configurable worker threads and rate limiting |
|
|
| **Production** | Leader election for high availability |
|
|
| **Production** | Prometheus metrics with recording rules and alerts |
|
|
| **Production** | Graceful shutdown with proper cleanup |
|
|
| **Production** | Comprehensive health checks and readiness probes |
|
|
|
|
## Quick Start
|
|
|
|
### Prerequisites
|
|
|
|
- Kubernetes 1.28+
|
|
- kubectl configured
|
|
- Helm 3.x (for Helm installation)
|
|
|
|
### Installation
|
|
|
|
#### Using Helm (Recommended)
|
|
|
|
```bash
|
|
# Add the Helm repository
|
|
helm repo add lukaszraczylo https://lukaszraczylo.github.io/helm-charts/
|
|
helm repo update
|
|
|
|
# Install kubemirror
|
|
helm install kubemirror lukaszraczylo/kubemirror \
|
|
--namespace kubemirror-system \
|
|
--create-namespace
|
|
|
|
# Verify installation
|
|
helm status kubemirror -n kubemirror-system
|
|
kubectl -n kubemirror-system get pods
|
|
kubectl -n kubemirror-system logs -l app.kubernetes.io/name=kubemirror
|
|
```
|
|
|
|
**Custom Configuration:**
|
|
```bash
|
|
# Install with custom values
|
|
helm install kubemirror lukaszraczylo/kubemirror \
|
|
--namespace kubemirror-system \
|
|
--create-namespace \
|
|
--set controller.maxTargets=200 \
|
|
--set controller.workerThreads=10 \
|
|
--set controller.rateLimitQPS=100
|
|
```
|
|
|
|
**Development:**
|
|
```bash
|
|
# Test local chart during development
|
|
helm install kubemirror ./charts/kubemirror \
|
|
--namespace kubemirror-system \
|
|
--create-namespace \
|
|
--values ./charts/kubemirror/values.yaml
|
|
```
|
|
|
|
#### Verifying Release Signatures
|
|
|
|
All release checksums and Docker images are signed with [cosign](https://github.com/sigstore/cosign) using keyless signing. To verify:
|
|
|
|
```bash
|
|
# Verify checksum signature
|
|
cosign verify-blob \
|
|
--certificate-identity-regexp "https://github.com/lukaszraczylo/kubemirror/.*" \
|
|
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
|
--bundle "kubemirror_v<version>_checksums.txt.sigstore.json" \
|
|
kubemirror_v<version>_checksums.txt
|
|
|
|
# Verify Docker image
|
|
cosign verify \
|
|
--certificate-identity-regexp "https://github.com/lukaszraczylo/kubemirror/.*" \
|
|
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
|
ghcr.io/lukaszraczylo/kubemirror:latest
|
|
```
|
|
|
|
#### Using kubectl
|
|
|
|
```bash
|
|
# Using kustomize
|
|
kubectl apply -k deploy/
|
|
|
|
# Or apply manifests individually
|
|
kubectl apply -f deploy/namespace.yaml
|
|
kubectl apply -f deploy/rbac.yaml
|
|
kubectl apply -f deploy/deployment.yaml
|
|
kubectl apply -f deploy/service.yaml
|
|
|
|
# Verify controller is running
|
|
kubectl -n kubemirror-system get pods
|
|
kubectl -n kubemirror-system logs -l app.kubernetes.io/name=kubemirror
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Mirror a Secret to Specific Namespaces
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: database-credentials
|
|
namespace: default
|
|
labels:
|
|
kubemirror.raczylo.com/enabled: "true" # Required for server-side filtering
|
|
annotations:
|
|
kubemirror.raczylo.com/sync: "true" # Enable mirroring
|
|
kubemirror.raczylo.com/target-namespaces: "app1,app2,app3"
|
|
type: Opaque
|
|
data:
|
|
username: YWRtaW4=
|
|
password: cGFzc3dvcmQ=
|
|
```
|
|
|
|
### Mirror to Pattern-Matched Namespaces
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: common-config
|
|
namespace: default
|
|
labels:
|
|
kubemirror.raczylo.com/enabled: "true"
|
|
annotations:
|
|
kubemirror.raczylo.com/sync: "true"
|
|
kubemirror.raczylo.com/target-namespaces: "app-*,prod-*"
|
|
data:
|
|
log_level: "info"
|
|
api_url: "https://api.example.com"
|
|
```
|
|
|
|
### Mirror to All Namespaces
|
|
|
|
Use the `all` keyword to mirror to every namespace in the cluster (except the source):
|
|
|
|
**Source Resource:**
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: global-config
|
|
namespace: default
|
|
labels:
|
|
kubemirror.raczylo.com/enabled: "true"
|
|
annotations:
|
|
kubemirror.raczylo.com/sync: "true"
|
|
kubemirror.raczylo.com/target-namespaces: "all"
|
|
data:
|
|
cluster_name: "production"
|
|
region: "us-west-2"
|
|
```
|
|
|
|
> **⚠️ Use with caution:** The `all` keyword mirrors to ALL namespaces (including kube-system, kube-public, etc.) except the source namespace. Consider using `all-labeled` for safer opt-in behavior.
|
|
|
|
### Mirror to All Labeled Namespaces
|
|
|
|
Use `all-labeled` for opt-in mirroring where target namespaces must explicitly allow mirrors:
|
|
|
|
**Source Resource:**
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: shared-tls-cert
|
|
namespace: default
|
|
labels:
|
|
kubemirror.raczylo.com/enabled: "true"
|
|
annotations:
|
|
kubemirror.raczylo.com/sync: "true"
|
|
kubemirror.raczylo.com/target-namespaces: "all-labeled"
|
|
type: kubernetes.io/tls
|
|
data:
|
|
tls.crt: LS0tLS1CRUdJTi...
|
|
tls.key: LS0tLS1CRUdJTi...
|
|
```
|
|
|
|
**Target Namespaces Must Opt-In:**
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: my-app-1
|
|
labels:
|
|
kubemirror.raczylo.com/allow-mirrors: "true"
|
|
---
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: my-app-2
|
|
labels:
|
|
kubemirror.raczylo.com/allow-mirrors: "true"
|
|
```
|
|
|
|
### Mirror Custom Resources (CRDs)
|
|
|
|
KubeMirror works with any custom resource:
|
|
|
|
```yaml
|
|
apiVersion: traefik.io/v1alpha1
|
|
kind: Middleware
|
|
metadata:
|
|
name: compression
|
|
namespace: default
|
|
labels:
|
|
kubemirror.raczylo.com/enabled: "true"
|
|
annotations:
|
|
kubemirror.raczylo.com/sync: "true"
|
|
kubemirror.raczylo.com/target-namespaces: "app-*"
|
|
spec:
|
|
compress:
|
|
excludedContentTypes:
|
|
- text/event-stream
|
|
```
|
|
|
|
### Using with ExternalSecrets Operator
|
|
|
|
KubeMirror works seamlessly with the [ExternalSecrets Operator](https://external-secrets.io/) to distribute secrets from external stores (like 1Password, Vault, AWS Secrets Manager) across multiple namespaces.
|
|
|
|
**Example - Distribute Docker Registry Credentials from 1Password:**
|
|
|
|
```yaml
|
|
apiVersion: external-secrets.io/v1
|
|
kind: ExternalSecret
|
|
metadata:
|
|
name: 1p-docker-config
|
|
namespace: default
|
|
spec:
|
|
# Pull secrets from 1Password/Vault/etc
|
|
secretStoreRef:
|
|
kind: ClusterSecretStore
|
|
name: 1password-homecluster
|
|
|
|
target:
|
|
creationPolicy: Owner # Standard ExternalSecrets setting - KubeMirror strips ownerReferences from mirrors
|
|
deletionPolicy: Retain
|
|
name: multi-registry-secret
|
|
|
|
# Include KubeMirror annotations in the secret template
|
|
template:
|
|
metadata:
|
|
labels:
|
|
kubemirror.raczylo.com/enabled: "true"
|
|
annotations:
|
|
kubemirror.raczylo.com/sync: "true"
|
|
kubemirror.raczylo.com/target-namespaces: "all" # or specific namespaces
|
|
|
|
type: kubernetes.io/dockerconfigjson
|
|
data:
|
|
.dockerconfigjson: |
|
|
{
|
|
"auths": {
|
|
"ghcr.io": {
|
|
"username": "{{ .ghcrUsername | toString }}",
|
|
"auth": "{{ printf "%s:%s" .ghcrUsername .ghcrPassword | b64enc }}"
|
|
}
|
|
}
|
|
}
|
|
|
|
data:
|
|
- remoteRef:
|
|
key: DockerAuth/ghcrio_username
|
|
secretKey: ghcrUsername
|
|
- remoteRef:
|
|
key: DockerAuth/ghcrio_password
|
|
secretKey: ghcrPassword
|
|
|
|
refreshInterval: 24h
|
|
```
|
|
|
|
**How it Works:**
|
|
|
|
1. **ExternalSecrets creates the source secret** with KubeMirror labels/annotations (source can be owned by any controller)
|
|
2. **KubeMirror detects the source** via the `kubemirror.raczylo.com/enabled` label
|
|
3. **KubeMirror creates mirrors** in target namespaces with:
|
|
- Labels identifying them as KubeMirror-managed mirrors
|
|
- Annotations linking back to the source (namespace, name, UID, content hash)
|
|
- **No ownerReferences** - preventing conflicts with source controllers
|
|
4. **ExternalSecrets refreshes the source** every 24h, updating only the source secret
|
|
5. **KubeMirror detects content changes** via hash comparison and updates all mirrors
|
|
6. Each controller manages its own resources independently - no conflicts
|
|
|
|
**Verification:**
|
|
|
|
```bash
|
|
# Check source secret was created by ExternalSecrets
|
|
kubectl get secret multi-registry-secret -n default -o jsonpath='{.metadata.annotations}'
|
|
|
|
# Verify mirrors were created by KubeMirror
|
|
kubectl get secrets --all-namespaces -l kubemirror.raczylo.com/mirror=true
|
|
|
|
# Check sync status on source
|
|
kubectl get secret multi-registry-secret -n default -o jsonpath='{.metadata.annotations.kubemirror\.raczylo\.com/sync-status}'
|
|
```
|
|
|
|
See [examples/externalsecret-dockerconfig.yaml](examples/externalsecret-dockerconfig.yaml) for a complete working example.
|
|
|
|
### Transformation Rules
|
|
|
|
KubeMirror supports powerful transformation rules that modify resources during mirroring. This enables environment-specific configurations, security hardening, and dynamic value generation.
|
|
|
|
**Basic Example - Environment-Specific Values:**
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: app-config
|
|
namespace: default
|
|
labels:
|
|
kubemirror.raczylo.com/enabled: "true"
|
|
annotations:
|
|
kubemirror.raczylo.com/sync: "true"
|
|
kubemirror.raczylo.com/target-namespaces: "dev-*,staging-*,prod-*"
|
|
kubemirror.raczylo.com/transform: |
|
|
rules:
|
|
# Set log level to error in production
|
|
- path: data.LOG_LEVEL
|
|
value: "error"
|
|
|
|
# Generate namespace-specific API URLs
|
|
- path: data.API_URL
|
|
template: "https://{{.TargetNamespace}}.api.example.com"
|
|
|
|
# Add environment labels
|
|
- path: metadata.labels
|
|
merge:
|
|
environment: "production"
|
|
|
|
# Remove debug configurations
|
|
- path: data.DEBUG_MODE
|
|
delete: true
|
|
data:
|
|
LOG_LEVEL: "debug"
|
|
API_URL: "https://localhost:8080"
|
|
DEBUG_MODE: "true"
|
|
```
|
|
|
|
**Transformation Rule Types:**
|
|
|
|
| Type | Purpose | Example |
|
|
|------|---------|---------|
|
|
| `value` | Set static value | `value: "production"` |
|
|
| `template` | Dynamic Go template | `template: "{{.TargetNamespace}}-app"` |
|
|
| `merge` | Add map entries | `merge: {key: "value"}` |
|
|
| `delete` | Remove field | `delete: true` |
|
|
|
|
**Template Variables:**
|
|
- `.TargetNamespace` - Target namespace name
|
|
- `.SourceNamespace` - Source namespace name
|
|
- `.SourceName` - Source resource name
|
|
- `.TargetName` - Mirror resource name
|
|
- `.Labels` - Source labels map
|
|
- `.Annotations` - Source annotations map
|
|
|
|
**Template Functions:**
|
|
- `upper`, `lower` - Case conversion
|
|
- `replace` - String replacement: `{{replace .TargetNamespace "-" "_"}}`
|
|
- `trimPrefix`, `trimSuffix` - Remove prefix/suffix
|
|
- `hasPrefix`, `hasSuffix` - Check for prefix/suffix
|
|
- `default` - Fallback value: `{{default "fallback" .Field}}`
|
|
|
|
**Array Indexing:**
|
|
|
|
Transform specific array elements using bracket notation:
|
|
|
|
```yaml
|
|
annotations:
|
|
kubemirror.raczylo.com/transform: |
|
|
rules:
|
|
# Container image
|
|
- path: spec.template.spec.containers[0].image
|
|
template: "registry.{{.TargetNamespace}}.example.com/app:v1"
|
|
|
|
# Environment variable
|
|
- path: spec.template.spec.containers[0].env[1].value
|
|
template: "postgres://{{.TargetNamespace}}-db.svc:5432"
|
|
|
|
# Volume ConfigMap reference
|
|
- path: spec.template.spec.volumes[0].configMap.name
|
|
template: "{{.TargetNamespace}}-config"
|
|
```
|
|
|
|
Common paths: `containers[N].image`, `containers[N].env[M].value`, `initContainers[N].image`, `volumes[N].configMap.name`
|
|
|
|
**Namespace Patterns:**
|
|
|
|
Apply rules conditionally based on target namespace using glob patterns:
|
|
|
|
```yaml
|
|
annotations:
|
|
kubemirror.raczylo.com/transform: |
|
|
rules:
|
|
# Global rule (no pattern) - applies to ALL namespaces
|
|
- path: data.APP_NAME
|
|
value: "my-app"
|
|
|
|
# Only preprod namespaces (preprod-*)
|
|
- path: data.GRAPHQL_HOST
|
|
value: "https://preprod.example.com/v1/graphql"
|
|
namespacePattern: "preprod-*"
|
|
|
|
# Only production namespaces (prod-*)
|
|
- path: data.GRAPHQL_HOST
|
|
value: "https://api.example.com/v1/graphql"
|
|
namespacePattern: "prod-*"
|
|
|
|
# Staging environments (*-staging)
|
|
- path: data.LOG_LEVEL
|
|
value: "warn"
|
|
namespacePattern: "*-staging"
|
|
```
|
|
|
|
**Pattern Syntax:**
|
|
- `*` - Matches zero or more characters
|
|
- `?` - Matches exactly one character
|
|
- Examples: `preprod-*`, `*-staging`, `namespace-?`, `prod-*-v?`
|
|
- No pattern or empty pattern matches all namespaces
|
|
|
|
**Strict Mode:**
|
|
```yaml
|
|
annotations:
|
|
kubemirror.raczylo.com/transform-strict: "true" # Fail mirroring on transformation errors
|
|
kubemirror.raczylo.com/transform: |
|
|
rules:
|
|
- path: data.CRITICAL_VALUE
|
|
value: "must-succeed"
|
|
```
|
|
|
|
**Security Example - Remove Sensitive Data:**
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: app-credentials
|
|
namespace: default
|
|
labels:
|
|
kubemirror.raczylo.com/enabled: "true"
|
|
annotations:
|
|
kubemirror.raczylo.com/sync: "true"
|
|
kubemirror.raczylo.com/target-namespaces: "app-*"
|
|
kubemirror.raczylo.com/transform: |
|
|
rules:
|
|
# Remove admin credentials from mirrors
|
|
- path: data.ADMIN_PASSWORD
|
|
delete: true
|
|
- path: data.ROOT_TOKEN
|
|
delete: true
|
|
|
|
# Create namespace-specific database hosts
|
|
- path: data.DB_HOST
|
|
template: "{{.TargetNamespace}}.postgres.svc.cluster.local"
|
|
type: Opaque
|
|
stringData:
|
|
APP_KEY: "app-key-12345"
|
|
ADMIN_PASSWORD: "super-secret"
|
|
ROOT_TOKEN: "root-token-xyz"
|
|
DB_HOST: "localhost"
|
|
```
|
|
|
|
**Performance & Security:**
|
|
- **Sandboxed Execution**: Templates run in a secure environment with no file/network access
|
|
- **Timeout Protection**: 100ms execution limit per template (configurable)
|
|
- **Size Limits**: Max 50 rules per resource, 10KB total rule size (configurable)
|
|
- **Overhead**: <1ms average transformation time per mirror
|
|
|
|
See [examples/transform-configmap.yaml](examples/transform-configmap.yaml), [examples/transform-secret.yaml](examples/transform-secret.yaml), and [examples/transform-deployment.yaml](examples/transform-deployment.yaml) for comprehensive examples including array indexing.
|
|
|
|
## Configuration
|
|
|
|
### Helm Chart Values
|
|
|
|
Complete configuration reference:
|
|
|
|
| Parameter | Description | Default | Example |
|
|
|-----------|-------------|---------|---------|
|
|
| **Resource Discovery** | | | |
|
|
| `controller.resourceTypes` | Explicit resource type list (empty = auto-discover all) | `[]` | `["Secret.v1", "ConfigMap.v1", "Ingress.v1.networking.k8s.io"]` |
|
|
| `controller.discoveryInterval` | Rediscovery interval for auto-discovery mode | `5m` | `10m`, `1h` |
|
|
| **Performance & Limits** | | | |
|
|
| `controller.leaderElect` | Enable leader election for HA | `true` | `true`, `false` |
|
|
| `controller.maxTargets` | Maximum mirrors per source resource | `100` | `50`, `200`, `500` |
|
|
| `controller.workerThreads` | Concurrent reconciliation workers | `5` | `10`, `20` |
|
|
| `controller.rateLimitQPS` | API rate limit (queries per second) | `50.0` | `100.0`, `200.0` |
|
|
| `controller.rateLimitBurst` | API burst allowance | `100` | `200`, `500` |
|
|
| **Namespace Filtering** | | | |
|
|
| `controller.excludedNamespaces` | Comma-separated namespace exclusion list | `""` | `kube-system,kube-public,kube-node-lease` |
|
|
| `controller.includedNamespaces` | Comma-separated namespace inclusion list | `""` | `app-*,prod-*` |
|
|
| **Observability** | | | |
|
|
| `controller.metricsBindAddress` | Metrics endpoint address | `:8080` | `:9090` |
|
|
| `controller.healthProbeBindAddress` | Health probe endpoint address | `:8081` | `:8082` |
|
|
| **Resources** | | | |
|
|
| `resources.limits.cpu` | CPU limit | `500m` | `1000m`, `2000m` |
|
|
| `resources.limits.memory` | Memory limit | `512Mi` | `256Mi`, `1Gi` |
|
|
| `resources.requests.cpu` | CPU request | `100m` | `200m`, `500m` |
|
|
| `resources.requests.memory` | Memory request | `128Mi` | `64Mi`, `256Mi` |
|
|
|
|
### Command-line Flags
|
|
|
|
When running the binary directly:
|
|
|
|
**Resource Discovery:**
|
|
- `--resource-types string` - Comma-separated list (e.g., `Secret.v1,ConfigMap.v1,Ingress.v1.networking.k8s.io`)
|
|
- `--discovery-interval duration` - Rediscovery interval (default: 5m)
|
|
|
|
**Performance & Limits:**
|
|
- `--leader-elect` - Enable leader election (default: true)
|
|
- `--max-targets int` - Max mirrors per source (default: 100)
|
|
- `--worker-threads int` - Concurrent workers (default: 5)
|
|
- `--rate-limit-qps float32` - API rate limit (default: 50.0)
|
|
- `--rate-limit-burst int` - API burst limit (default: 100)
|
|
- `--verify-source-freshness` - Verify cache freshness before mirroring (default: false)
|
|
|
|
**Namespace Filtering:**
|
|
- `--excluded-namespaces string` - Comma-separated exclusion list
|
|
- `--included-namespaces string` - Comma-separated inclusion list
|
|
|
|
**Observability:**
|
|
- `--metrics-bind-address string` - Metrics endpoint (default: :8080)
|
|
- `--health-probe-bind-address string` - Health endpoint (default: :8081)
|
|
|
|
### Resource Auto-Discovery
|
|
|
|
KubeMirror automatically discovers all mirrorable resources in your cluster, eliminating manual resource type configuration.
|
|
|
|
**Auto-Discovery Mode (Default):**
|
|
|
|
When `resourceTypes` is empty, KubeMirror:
|
|
1. Scans all available API resources via Kubernetes discovery API
|
|
2. Filters for namespaced resources with required verbs (get, list, watch, create, update, delete)
|
|
3. Excludes dangerous resources using a comprehensive deny list
|
|
4. Periodically rediscovers (default: every 5 minutes) to detect new CRDs
|
|
|
|
**Explicit Mode:**
|
|
|
|
Specify exact resources to mirror:
|
|
```yaml
|
|
controller:
|
|
resourceTypes:
|
|
- "Secret.v1"
|
|
- "ConfigMap.v1"
|
|
- "Ingress.v1.networking.k8s.io"
|
|
- "Middleware.v1alpha1.traefik.io"
|
|
```
|
|
|
|
**Safety Features:**
|
|
|
|
- **Deny List:** Never mirrors: Pods, Events, Nodes, Endpoints, EndpointSlice, Leases, PersistentVolumes, and other cluster-scoped or dangerous resources
|
|
- **Namespaced Only:** Only discovers namespaced resources (cluster-scoped excluded)
|
|
- **Verb Filtering:** Resources must support all CRUD operations
|
|
- **Opt-In Required:** Resources must have `kubemirror.raczylo.com/enabled: "true"` label
|
|
|
|
**Monitoring Discovery:**
|
|
|
|
```bash
|
|
# View discovered resources
|
|
kubectl logs -n kubemirror-system -l app.kubernetes.io/name=kubemirror | grep "resource discovery"
|
|
|
|
# Check discovery manager startup
|
|
kubectl logs -n kubemirror-system -l app.kubernetes.io/name=kubemirror | grep "discovery manager"
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Components
|
|
|
|
- **Discovery Manager**: Automatically discovers all mirrorable resource types with periodic refresh
|
|
- **Source Reconciler**: Watches labeled source resources, creates/updates mirrors across target namespaces
|
|
- **Target Reconciler**: Watches mirrored resources, detects drift and orphans, triggers re-sync when needed
|
|
- **Namespace Reconciler**: Watches namespace creation, auto-creates mirrors when new namespaces match patterns
|
|
|
|
### How It Works
|
|
|
|
1. **Opt-In via Labels** - Source resources must have `kubemirror.raczylo.com/enabled: "true"` label for server-side filtering
|
|
2. **Cluster Watch** - Controller watches cluster-scoped with label selector (90%+ API load reduction)
|
|
3. **Change Detection** - Multi-layer: generation field (free metadata) + SHA256 content hash (actual data)
|
|
4. **Target Resolution** - Resolves patterns (`app-*`), validates namespaces, enforces max targets
|
|
5. **Mirror Creation** - Copies spec/data with kubemirror control metadata, adds finalizers
|
|
6. **Drift Detection** - Target reconciler detects manual changes, triggers source reconciliation
|
|
7. **Cleanup** - Finalizers ensure all mirrors deleted before source removal
|
|
|
|
### Performance Optimizations
|
|
|
|
- **Server-Side Filtering:** Label selector in watch predicate reduces event volume by 90%+
|
|
- **Field Indexing:** O(1) reverse lookups for target → source relationships
|
|
- **Content Hashing:** SHA256 hash avoids deep equality checks and unnecessary API calls
|
|
- **Generation Field:** Free change detection from Kubernetes metadata before content hash
|
|
- **Worker Pools:** Concurrent reconciliation with configurable parallelism
|
|
- **Rate Limiting:** Protects API server with configurable QPS and burst
|
|
- **Bounded Queues:** Prevents memory leaks under high load
|
|
- **Cache Freshness Verification (Optional):** When `--verify-source-freshness=true`, compares cached source with direct API read to detect informer cache lag. Prevents mirroring stale data during the 5-20 second window after watch events. Trade-off: Extra API call when cache is stale, but guarantees data freshness (see [Cache Staleness](#cache-staleness) for details)
|
|
|
|
## Supported Resources
|
|
|
|
KubeMirror can mirror any namespaced Kubernetes resource that supports standard CRUD operations:
|
|
|
|
| Resource Type | Support Level | Notes |
|
|
|---------------|---------------|-------|
|
|
| **Core Resources** | | |
|
|
| Secret | ✅ Full | Includes all secret types (Opaque, TLS, etc.) |
|
|
| ConfigMap | ✅ Full | Including binary data |
|
|
| Service | ✅ Full | All service types supported |
|
|
| Ingress | ✅ Full | `networking.k8s.io/v1` |
|
|
| **Traefik CRDs** | | |
|
|
| Middleware | ✅ Full | `traefik.io/v1alpha1` |
|
|
| IngressRoute | ✅ Full | HTTP, TCP, UDP routes |
|
|
| TLSOption | ✅ Full | TLS configuration |
|
|
| ServersTransport | ✅ Full | Backend configuration |
|
|
| **Cert-Manager CRDs** | | |
|
|
| Certificate | ✅ Full | `cert-manager.io/v1` |
|
|
| Issuer | ✅ Full | Namespace-scoped issuers |
|
|
| **Other CRDs** | ✅ Full | Any custom resource with namespaced scope |
|
|
| **Excluded Resources** | | |
|
|
| Pod | ❌ Never | Too dynamic, deny-listed |
|
|
| Event | ❌ Never | Ephemeral, deny-listed |
|
|
| Endpoint | ❌ Never | Auto-managed, deny-listed |
|
|
| Lease | ❌ Never | Leader election, deny-listed |
|
|
| PersistentVolume | ❌ Never | Cluster-scoped |
|
|
| Namespace | ❌ Never | Cluster-scoped |
|
|
|
|
**Auto-Discovery** automatically finds all supported resources. The deny list is comprehensive and prevents mirroring of dangerous or inappropriate resources.
|
|
|
|
## Cache Staleness
|
|
|
|
Kubernetes controllers use informer caches for performance. KubeMirror implements a hybrid strategy to handle cache lag:
|
|
|
|
**The Problem:**
|
|
1. Source Secret updated → Watch event arrives
|
|
2. Reconciliation triggered immediately3. Controller reads from cache → **Gets stale data** (cache hasn't updated yet)
|
|
4. Stale data mirrored to targets5. Cache updates 5-20 seconds later → But reconciliation already ran
|
|
|
|
**The Solution (Optional):**
|
|
|
|
Enable `--verify-source-freshness=true` to activate hybrid caching:
|
|
1. Read from cache (fast)
|
|
2. Make direct API call to verify freshness
|
|
3. If resourceVersions differ → Use fresh API data
|
|
4. If resourceVersions match → Use cached data
|
|
|
|
**Trade-offs:**
|
|
|
|
| Mode | API Calls | Data Freshness | Use Case |
|
|
|------|-----------|----------------|----------|
|
|
| **Default** (`false`) | 0 extra calls | Eventually consistent (5-20s lag) | Most deployments - 95%+ of updates propagate correctly |
|
|
| **Freshness Verification** (`true`) | 1-2 extra calls per update | Always fresh | Critical secrets that must propagate immediately |
|
|
|
|
**Recommendation:** Default mode is sufficient for most use cases. Enable freshness verification only for environments where stale data is unacceptable (e.g., security-critical secrets, zero-downtime deployments).
|
|
|
|
## Monitoring
|
|
|
|
KubeMirror exposes Prometheus metrics and includes production-ready monitoring resources:
|
|
|
|
```bash
|
|
# Deploy ServiceMonitor for Prometheus Operator
|
|
kubectl apply -f monitoring/servicemonitor.yaml
|
|
|
|
# Deploy Alert Rules
|
|
kubectl apply -f monitoring/prometheusrule.yaml
|
|
|
|
# Import Grafana dashboard
|
|
# Use monitoring/grafana-dashboard.json in Grafana UI
|
|
```
|
|
|
|
**Key Metrics:**
|
|
|
|
- `kubemirror_reconcile_total` - Total reconciliations by controller and result
|
|
- `kubemirror_reconcile_duration_seconds` - Reconciliation latency histogram
|
|
- `kubemirror_mirror_resources_total` - Number of mirrors by namespace and source type
|
|
- `kubemirror_sync_errors_total` - Sync failures by controller and error type
|
|
- `workqueue_depth` - Current queue depth per controller
|
|
- `workqueue_adds_total` - Total items added to queues
|
|
|
|
**Alert Examples:**
|
|
|
|
- High reconciliation error rate
|
|
- Mirror resource sync lag
|
|
- Queue depth consistently high
|
|
- Discovery manager failures
|
|
|
|
See [monitoring/README.md](monitoring/README.md) for complete setup including:
|
|
- Recording rules for performance analysis
|
|
- Alert rules for operational issues
|
|
- Grafana dashboard with KPIs and SLOs
|
|
|
|
## Production Recommendations
|
|
|
|
### High-Throughput Configuration
|
|
|
|
For large clusters (500+ namespaces, 2000+ mirrors):
|
|
|
|
```yaml
|
|
controller:
|
|
maxTargets: 200
|
|
workerThreads: 20
|
|
rateLimitQPS: 200.0
|
|
rateLimitBurst: 500
|
|
discoveryInterval: "10m" # Less frequent rediscovery
|
|
|
|
resources:
|
|
limits:
|
|
cpu: 2000m
|
|
memory: 1Gi
|
|
requests:
|
|
cpu: 500m
|
|
memory: 256Mi
|
|
```
|
|
|
|
### Multi-Tenant Configuration
|
|
|
|
For strict namespace isolation:
|
|
|
|
```yaml
|
|
controller:
|
|
maxTargets: 50 # Limit blast radius
|
|
workerThreads: 10
|
|
excludedNamespaces: "kube-system,kube-public,kube-node-lease,kubemirror-system"
|
|
|
|
# Explicit resource types for security
|
|
resourceTypes:
|
|
- "Secret.v1"
|
|
- "ConfigMap.v1"
|
|
```
|
|
|
|
### Development Configuration
|
|
|
|
For local testing:
|
|
|
|
```yaml
|
|
controller:
|
|
leaderElect: false # Single instance
|
|
maxTargets: 20
|
|
workerThreads: 2
|
|
rateLimitQPS: 10.0
|
|
rateLimitBurst: 20
|
|
discoveryInterval: "1m" # Faster iteration
|
|
|
|
resources:
|
|
limits:
|
|
cpu: 200m
|
|
memory: 128Mi
|
|
requests:
|
|
cpu: 50m
|
|
memory: 64Mi
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **Mirrors not created**
|
|
- Verify source has `kubemirror.raczylo.com/enabled: "true"` label
|
|
- Check `kubemirror.raczylo.com/sync: "true"` annotation exists
|
|
- Validate target namespace exists and matches pattern
|
|
- Check controller logs for errors: `kubectl logs -n kubemirror-system -l app.kubernetes.io/name=kubemirror`
|
|
|
|
2. **"Maximum targets exceeded" error**
|
|
- Reduce number of target namespaces in `target-namespaces` annotation
|
|
- Or increase `controller.maxTargets` in Helm values
|
|
- Check logs: `kubectl logs -n kubemirror-system -l app.kubernetes.io/name=kubemirror | grep "maximum targets"`
|
|
|
|
3. **Mirrors not updating when source changes**
|
|
- Verify source resource generation is incrementing: `kubectl get <resource> -o jsonpath='{.metadata.generation}'`
|
|
- Check content hash calculation in logs
|
|
- Ensure target reconciler is running: `kubectl get pods -n kubemirror-system`
|
|
|
|
4. **High API server load**
|
|
- Reduce `controller.rateLimitQPS` and `controller.rateLimitBurst`
|
|
- Decrease `controller.workerThreads`
|
|
- Increase `controller.discoveryInterval` for less frequent rediscovery
|
|
- Check metrics: `kubectl port-forward -n kubemirror-system svc/kubemirror 8080:8080`
|
|
|
|
5. **Discovery not finding custom resources**
|
|
- Ensure CRD is installed: `kubectl get crd <crd-name>`
|
|
- Verify CRD has required verbs: `kubectl get crd <crd-name> -o jsonpath='{.spec.versions[0].storage}'`
|
|
- Check discovery logs: `kubectl logs -n kubemirror-system -l app.kubernetes.io/name=kubemirror | grep "discovery"`
|
|
|
|
6. **Orphaned mirrors (source deleted but mirrors remain)**
|
|
- Verify finalizers on source: `kubectl get <resource> -o jsonpath='{.metadata.finalizers}'`
|
|
- Check target reconciler logs for cleanup errors
|
|
- Manually remove finalizer if needed: `kubectl patch <resource> -p '{"metadata":{"finalizers":null}}'`
|
|
|
|
7. **"all-labeled" not working**
|
|
- Verify target namespaces have `kubemirror.raczylo.com/allow-mirrors: "true"` label
|
|
- Check namespace reconciler logs
|
|
- Validate namespace watch is active
|
|
|
|
8. **Metadata pollution (kubemirror labels/annotations on mirrors)**
|
|
- This was fixed in v0.2.0+
|
|
- Upgrade to latest version
|
|
- Manually clean up old mirrors if needed
|
|
|
|
### Debugging
|
|
|
|
**Enable Debug Logging:**
|
|
```bash
|
|
# Edit deployment to set log level
|
|
kubectl edit deployment -n kubemirror-system kubemirror
|
|
|
|
# Add env var:
|
|
# - name: LOG_LEVEL
|
|
# value: "debug"
|
|
```
|
|
|
|
**Check Metrics:**
|
|
```bash
|
|
# Port-forward metrics endpoint
|
|
kubectl port-forward -n kubemirror-system svc/kubemirror 8080:8080
|
|
|
|
# Query metrics
|
|
curl http://localhost:8080/metrics | grep kubemirror
|
|
```
|
|
|
|
**Verify RBAC:**
|
|
```bash
|
|
# Check ClusterRole permissions
|
|
kubectl get clusterrole kubemirror -o yaml
|
|
|
|
# Verify ServiceAccount
|
|
kubectl get sa -n kubemirror-system kubemirror
|
|
kubectl get clusterrolebinding kubemirror
|
|
```
|
|
|
|
**Test Resource Discovery:**
|
|
```bash
|
|
# Watch discovery manager logs
|
|
kubectl logs -n kubemirror-system -l app.kubernetes.io/name=kubemirror -f | grep "discovery manager"
|
|
|
|
# Force rediscovery by restarting pod
|
|
kubectl rollout restart deployment -n kubemirror-system kubemirror
|
|
```
|
|
|
|
## Development
|
|
|
|
### Building
|
|
|
|
```bash
|
|
# Run all checks (tests, linters, build)
|
|
make ci
|
|
|
|
# Build binary
|
|
make build
|
|
|
|
# Build Docker image
|
|
make docker-build
|
|
|
|
# Push to registry (requires authentication)
|
|
make docker-push
|
|
```
|
|
|
|
### Testing
|
|
|
|
```bash
|
|
# Run unit tests
|
|
make test
|
|
|
|
# Run tests with race detector
|
|
make test-race
|
|
|
|
# Run benchmarks
|
|
make bench
|
|
|
|
# Run specific package tests
|
|
go test -v ./pkg/controller/...
|
|
|
|
# Run with coverage
|
|
go test -cover ./...
|
|
go test -coverprofile=coverage.out ./...
|
|
go tool cover -html=coverage.out
|
|
```
|
|
|
|
### Releasing
|
|
|
|
```bash
|
|
# Test release locally (dry run)
|
|
make release-dry
|
|
|
|
# Create and push tag (triggers CI/CD)
|
|
git tag -a v0.2.0 -m "Release v0.2.0: Universal resource support"
|
|
git push origin v0.2.0
|
|
|
|
# GitHub Actions will:
|
|
# 1. Build binaries for all platforms
|
|
# 2. Build and push Docker images
|
|
# 3. Sign artifacts with cosign
|
|
# 4. Create GitHub release
|
|
```
|
|
|
|
## Documentation
|
|
|
|
- [examples/](examples/) - Working examples and testing scenarios
|
|
- [monitoring/](monitoring/) - Prometheus metrics, Grafana dashboards, alerting setup
|
|
- [Helm Chart Documentation](charts/kubemirror/README.md) - Kubernetes deployment via Helm
|
|
- [GitHub Repository](https://github.com/lukaszraczylo/kubemirror) - Source code and issue tracker
|
|
|
|
## Telemetry
|
|
|
|
On startup this controller sends a single anonymous adoption ping — project
|
|
name, version, timestamp; no identifiers, no Kubernetes object data, no
|
|
cluster metadata. Fire-and-forget with a 2-second timeout; cannot block
|
|
startup or panic.
|
|
|
|
See **[oss-telemetry — Disabling telemetry](https://github.com/lukaszraczylo/oss-telemetry#disabling-telemetry)**
|
|
for the exact wire format, source, and full opt-out documentation.
|
|
|
|
Quick opt-out: set any of `DO_NOT_TRACK=1`, `OSS_TELEMETRY_DISABLED=1`,
|
|
or `KUBEMIRROR_DISABLE_TELEMETRY=1`.
|
|
|
|
## License
|
|
|
|
See [LICENSE](LICENSE) file for details.
|