From 3e278168a7733b7569a13b5139c281920f8fdf5d Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 4 Feb 2026 17:01:50 +1300 Subject: [PATCH 01/12] Add a feature flag example app called Flipt --- applications/flipt/.gitignore | 39 ++ applications/flipt/Makefile | 151 ++++ applications/flipt/QUICKSTART.md | 287 ++++++++ applications/flipt/README.md | 651 ++++++++++++++++++ applications/flipt/TROUBLESHOOTING.md | 529 ++++++++++++++ applications/flipt/chart/Chart.yaml | 46 ++ applications/flipt/chart/templates/NOTES.txt | 77 +++ .../flipt/chart/templates/_helpers.tpl | 100 +++ .../flipt/chart/templates/flipt-config.yaml | 61 ++ .../chart/templates/postgresql-cluster.yaml | 73 ++ applications/flipt/chart/values.yaml | 264 +++++++ .../flipt/docs/DEVELOPMENT_LICENSE.md | 231 +++++++ applications/flipt/examples/README.md | 392 +++++++++++ .../kubernetes/values-external-db.yaml | 36 + .../examples/kubernetes/values-minimal.yaml | 37 + .../kubernetes/values-production.yaml | 129 ++++ .../flipt/examples/sdk/golang-example.go | 330 +++++++++ .../flipt/examples/sdk/nodejs-example.js | 241 +++++++ .../flipt/examples/sdk/python-example.py | 367 ++++++++++ applications/flipt/replicated/kots-app.yaml | 47 ++ .../flipt/replicated/kots-config.yaml | 363 ++++++++++ .../flipt/replicated/kots-helm-chart.yaml | 212 ++++++ .../flipt/replicated/kots-preflight.yaml | 181 +++++ .../flipt/replicated/kots-support-bundle.yaml | 298 ++++++++ .../flipt/scripts/install-cnpg-operator.sh | 24 + applications/flipt/scripts/install.sh | 155 +++++ .../flipt/scripts/setup-dev-license.sh | 145 ++++ applications/wg-easy/Taskfile.yaml | 4 +- applications/wg-easy/replicated/cluster.yaml | 2 +- 29 files changed, 5469 insertions(+), 3 deletions(-) create mode 100644 applications/flipt/.gitignore create mode 100644 applications/flipt/Makefile create mode 100644 applications/flipt/QUICKSTART.md create mode 100644 applications/flipt/README.md create mode 100644 applications/flipt/TROUBLESHOOTING.md create mode 100644 applications/flipt/chart/Chart.yaml create mode 100644 applications/flipt/chart/templates/NOTES.txt create mode 100644 applications/flipt/chart/templates/_helpers.tpl create mode 100644 applications/flipt/chart/templates/flipt-config.yaml create mode 100644 applications/flipt/chart/templates/postgresql-cluster.yaml create mode 100644 applications/flipt/chart/values.yaml create mode 100644 applications/flipt/docs/DEVELOPMENT_LICENSE.md create mode 100644 applications/flipt/examples/README.md create mode 100644 applications/flipt/examples/kubernetes/values-external-db.yaml create mode 100644 applications/flipt/examples/kubernetes/values-minimal.yaml create mode 100644 applications/flipt/examples/kubernetes/values-production.yaml create mode 100644 applications/flipt/examples/sdk/golang-example.go create mode 100644 applications/flipt/examples/sdk/nodejs-example.js create mode 100644 applications/flipt/examples/sdk/python-example.py create mode 100644 applications/flipt/replicated/kots-app.yaml create mode 100644 applications/flipt/replicated/kots-config.yaml create mode 100644 applications/flipt/replicated/kots-helm-chart.yaml create mode 100644 applications/flipt/replicated/kots-preflight.yaml create mode 100644 applications/flipt/replicated/kots-support-bundle.yaml create mode 100755 applications/flipt/scripts/install-cnpg-operator.sh create mode 100755 applications/flipt/scripts/install.sh create mode 100755 applications/flipt/scripts/setup-dev-license.sh diff --git a/applications/flipt/.gitignore b/applications/flipt/.gitignore new file mode 100644 index 00000000..9fd36cb4 --- /dev/null +++ b/applications/flipt/.gitignore @@ -0,0 +1,39 @@ +# Helm +*.tgz +chart/Chart.lock +chart/charts/*.tgz + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Kubernetes +kubeconfig +*.kubeconfig + +# Secrets +*.pem +*.key +secrets.yaml +*-secret.yaml + +# Logs +*.log + +# Temporary files +*.tmp +*.temp +.tmp/ + +# Support bundles +support-bundle-*.tar.gz + +# Replicated development licenses +.replicated/ diff --git a/applications/flipt/Makefile b/applications/flipt/Makefile new file mode 100644 index 00000000..e0542b21 --- /dev/null +++ b/applications/flipt/Makefile @@ -0,0 +1,151 @@ +.PHONY: help lint package update-deps install uninstall upgrade test clean + +CHART_DIR := chart +CHART_NAME := flipt +NAMESPACE := flipt +RELEASE_NAME := flipt + +help: ## Display this help message + @echo "Flipt Helm Chart Management" + @echo "" + @echo "Available targets:" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' + +lint: ## Lint the Helm chart + @echo "Linting Helm chart..." + helm lint $(CHART_DIR) + +package: lint ## Package the Helm chart + @echo "Packaging Helm chart..." + helm package $(CHART_DIR) + +update-deps: ## Update Helm chart dependencies + @echo "Adding Helm repositories..." + helm repo add flipt https://helm.flipt.io || true + helm repo add bitnami https://charts.bitnami.com/bitnami || true + helm repo add cnpg https://cloudnative-pg.github.io/charts || true + helm repo add replicated https://charts.replicated.com || true + helm repo update + @echo "Updating chart dependencies..." + cd $(CHART_DIR) && helm dependency update + +install-operator: ## Install CloudNativePG operator (required, run once per cluster) + @echo "Installing CloudNativePG operator..." + helm repo add cnpg https://cloudnative-pg.github.io/charts || true + helm repo update + helm upgrade --install cnpg \ + --namespace cnpg-system \ + --create-namespace \ + cnpg/cloudnative-pg + @echo "Waiting for operator to be ready..." + kubectl wait --for=condition=available --timeout=300s \ + deployment/cnpg-cloudnative-pg \ + -n cnpg-system || true + @echo "✓ Operator ready!" + +install: install-operator ## Install the chart (includes operator installation) + @echo "Installing $(CHART_NAME)..." + helm install $(RELEASE_NAME) $(CHART_DIR) \ + --namespace $(NAMESPACE) \ + --create-namespace \ + --wait \ + --timeout 10m + +setup-license: ## Set up Replicated development license + @./scripts/setup-dev-license.sh + +install-with-license: setup-license install-operator ## Set up license and install + @if [ -f ".replicated/license.env" ]; then \ + . .replicated/license.env && \ + $(MAKE) install; \ + else \ + echo "License setup failed"; \ + exit 1; \ + fi + +clean-license: ## Remove development license + @if [ -f ".replicated/license.env" ]; then \ + . .replicated/license.env && \ + replicated customer rm --customer "$$CUSTOMER_NAME" 2>/dev/null || true; \ + fi + @rm -rf .replicated/ + @echo "License configuration removed" + +uninstall: ## Uninstall the chart + @echo "Uninstalling $(CHART_NAME)..." + helm uninstall $(RELEASE_NAME) --namespace $(NAMESPACE) + +upgrade: ## Upgrade the chart + @echo "Upgrading $(CHART_NAME)..." + helm upgrade $(RELEASE_NAME) $(CHART_DIR) \ + --namespace $(NAMESPACE) \ + --wait \ + --timeout 10m + +template: ## Render chart templates locally + @echo "Rendering templates..." + helm template $(RELEASE_NAME) $(CHART_DIR) \ + --namespace $(NAMESPACE) \ + --debug + +test: ## Run Helm tests + @echo "Running Helm tests..." + helm test $(RELEASE_NAME) --namespace $(NAMESPACE) + +status: ## Show release status + @echo "Release status:" + helm status $(RELEASE_NAME) --namespace $(NAMESPACE) + +values: ## Show computed values + @echo "Computed values:" + helm get values $(RELEASE_NAME) --namespace $(NAMESPACE) + +manifest: ## Show deployed manifest + @echo "Deployed manifest:" + helm get manifest $(RELEASE_NAME) --namespace $(NAMESPACE) + +logs: ## Tail Flipt logs + @echo "Tailing Flipt logs..." + kubectl logs -l app.kubernetes.io/name=flipt -n $(NAMESPACE) --tail=100 -f + +clean: ## Clean generated files + @echo "Cleaning generated files..." + rm -rf $(CHART_DIR)/charts/*.tgz + rm -rf $(CHART_DIR)/Chart.lock + rm -f *.tgz + +clean-install: clean update-deps install ## Clean rebuild and install + +port-forward: ## Port forward to Flipt service + @echo "Port forwarding to Flipt (http://localhost:8080)..." + kubectl port-forward -n $(NAMESPACE) svc/$(RELEASE_NAME)-flipt 8080:8080 + +replicated-lint: ## Lint Replicated KOTS configs + @echo "Linting KOTS configuration..." + replicated release lint --yaml-dir replicated/ + +replicated-create: ## Create a new Replicated release + @echo "Creating Replicated release..." + replicated release create --auto --yaml-dir replicated/ + +preflight: ## Run preflight checks + @echo "Running preflight checks..." + kubectl preflight ./replicated/kots-preflight.yaml + +support-bundle: ## Generate support bundle + @echo "Generating support bundle..." + kubectl support-bundle ./replicated/kots-support-bundle.yaml + +check-deps: ## Verify all required tools are installed + @echo "Checking dependencies..." + @command -v helm >/dev/null 2>&1 || { echo "helm is not installed"; exit 1; } + @command -v kubectl >/dev/null 2>&1 || { echo "kubectl is not installed"; exit 1; } + @echo "All dependencies satisfied!" + +# Development helpers +dev-install: update-deps install ## Update dependencies and install + +dev-upgrade: update-deps upgrade ## Update dependencies and upgrade + +watch-pods: ## Watch pod status + kubectl get pods -n $(NAMESPACE) -w diff --git a/applications/flipt/QUICKSTART.md b/applications/flipt/QUICKSTART.md new file mode 100644 index 00000000..629da4b7 --- /dev/null +++ b/applications/flipt/QUICKSTART.md @@ -0,0 +1,287 @@ +# Flipt Quick Start Guide + +Get up and running with Flipt in 5 minutes. + +## Prerequisites + +- Kubernetes cluster (1.24+) +- Helm 3.8+ +- kubectl configured + +## ⚠️ Prerequisites + +Before you begin, you need a **Replicated development license**: + +```bash +# 1. Set your Replicated API token +export REPLICATED_API_TOKEN=your-token-here + +# 2. Set up development license +./scripts/setup-dev-license.sh + +# 3. Load the license +source .replicated/license.env +``` + +**Don't have a Replicated account?** +- Sign up at [vendor.replicated.com](https://vendor.replicated.com) +- See [Development License Guide](docs/DEVELOPMENT_LICENSE.md) for detailed instructions + +## Option 1: Quick Install (Development) + +Install with default settings for testing: + +### Easy Install (Recommended) + +Use the automated installation script: + +```bash +./scripts/install.sh +``` + +This script will: +- ✅ Check prerequisites (kubectl, helm) +- ✅ Install CloudNativePG operator (if not present) +- ✅ Add all required Helm repositories +- ✅ Clean and rebuild dependencies +- ✅ Install Flipt with all components +- ✅ Show status and next steps + +### Manual Install + +If you prefer to run commands manually: + +```bash +# Step 1: Install CloudNativePG operator (cluster-wide, only needed once) +./scripts/install-cnpg-operator.sh + +# Step 2: Clean and update dependencies +cd chart +rm -f charts/cloudnative-pg-*.tgz Chart.lock # Clean cached files +helm repo add flipt https://helm.flipt.io +helm repo add bitnami https://charts.bitnami.com/bitnami +helm repo add replicated https://charts.replicated.com +helm repo update +helm dependency update +cd .. + +# Step 3: Install Flipt +helm install flipt ./chart \ + --namespace flipt \ + --create-namespace \ + --wait \ + --timeout 10m + +# Step 4: Port forward to access +kubectl port-forward -n flipt svc/flipt-flipt 8080:8080 +``` + +Open your browser to: **http://localhost:8080** + +## Option 2: Replicated KOTS Install + +For enterprise deployments with admin console: + +1. **Upload the application** to your Replicated vendor portal: + ```bash + replicated release create --auto --yaml-dir replicated/ + ``` + +2. **Install via Replicated Admin Console**: + - Log into your Replicated admin console + - Select the Flipt application + - Follow the configuration wizard + - Deploy + +3. **Access Flipt** through configured ingress or LoadBalancer + +## Option 3: Production Install + +For production with HA: + +```bash +helm install flipt ./chart \ + --namespace flipt \ + --create-namespace \ + --values examples/kubernetes/values-production.yaml \ + --wait +``` + +Access via your configured ingress hostname. + +## Your First Feature Flag + +### 1. Access the UI + +Navigate to the Flipt UI (http://localhost:8080 if using port-forward). + +### 2. Create a Flag + +1. Click **"Flags"** in the sidebar +2. Click **"Create Flag"** +3. Fill in: + - **Name**: `new_dashboard` + - **Description**: `Enable the new dashboard UI` + - **Type**: Boolean +4. Click **"Create"** + +### 3. Enable the Flag + +1. Toggle the flag to **Enabled** +2. Set a percentage rollout (e.g., 50%) +3. Click **"Save"** + +### 4. Use the Flag in Your App + +**Node.js:** +```javascript +const { FliptClient } = require('@flipt-io/flipt'); + +const flipt = new FliptClient({ url: 'http://localhost:8080' }); + +const result = await flipt.evaluateBoolean({ + namespaceKey: 'default', + flagKey: 'new_dashboard', + entityId: 'user-123', + context: {} +}); + +if (result.enabled) { + console.log('Show new dashboard!'); +} +``` + +**Go:** +```go +import flipt "go.flipt.io/flipt/rpc/flipt" + +client := flipt.NewFliptClient(conn) + +resp, _ := client.EvaluateBoolean(ctx, &flipt.EvaluationRequest{ + NamespaceKey: "default", + FlagKey: "new_dashboard", + EntityId: "user-123", +}) + +if resp.Enabled { + fmt.Println("Show new dashboard!") +} +``` + +**Python:** +```python +from flipt import FliptClient + +client = FliptClient(url="http://localhost:8080") + +result = client.evaluate_boolean( + namespace_key="default", + flag_key="new_dashboard", + entity_id="user-123" +) + +if result.enabled: + print("Show new dashboard!") +``` + +## Verify Installation + +Check that all components are running: + +```bash +# Check pods +kubectl get pods -n flipt + +# Should see: +# - flipt-flipt-xxx (2 replicas) +# - flipt-cluster-xxx (PostgreSQL) +# - flipt-redis-master-xxx + +# Check services +kubectl get svc -n flipt + +# Check ingress (if enabled) +kubectl get ingress -n flipt +``` + +## Common Commands + +```bash +# View logs +kubectl logs -l app.kubernetes.io/name=flipt -n flipt --tail=100 -f + +# Restart Flipt +kubectl rollout restart deployment/flipt-flipt -n flipt + +# Scale Flipt +kubectl scale deployment/flipt-flipt -n flipt --replicas=3 + +# Check database status +kubectl get cluster -n flipt + +# Check Redis status +kubectl get pods -l app.kubernetes.io/name=redis -n flipt +``` + +## Troubleshooting + +### Pods Not Starting + +```bash +kubectl describe pod -n flipt +kubectl logs -n flipt +``` + +### Can't Access UI + +```bash +# Verify service is running +kubectl get svc flipt-flipt -n flipt + +# Check if port-forward is working +kubectl port-forward -n flipt svc/flipt-flipt 8080:8080 + +# Test locally +curl http://localhost:8080/health +``` + +### Database Connection Issues + +```bash +# Check PostgreSQL cluster +kubectl get cluster -n flipt + +# Check PostgreSQL logs +kubectl logs -l cnpg.io/cluster=flipt-cluster -n flipt +``` + +## Next Steps + +1. **Set up ingress** for external access +2. **Configure authentication** for API security +3. **Enable metrics** for monitoring +4. **Create targeting rules** for user segmentation +5. **Integrate SDKs** into your applications + +## Resources + +- 📖 [Full Documentation](../README.md) +- 💻 [SDK Examples](examples/sdk/) +- ⚙️ [Configuration Examples](examples/kubernetes/) +- 🆘 [Troubleshooting Guide](../README.md#troubleshooting) + +## Uninstall + +```bash +# Uninstall Flipt +helm uninstall flipt --namespace flipt + +# Remove namespace and PVCs +kubectl delete namespace flipt +``` + +## Support + +- **Flipt Issues**: https://github.com/flipt-io/flipt/issues +- **Helm Chart Issues**: https://github.com/flipt-io/helm-charts/issues +- **Replicated Support**: https://support.replicated.com diff --git a/applications/flipt/README.md b/applications/flipt/README.md new file mode 100644 index 00000000..b12affc3 --- /dev/null +++ b/applications/flipt/README.md @@ -0,0 +1,651 @@ +# Flipt Feature Flags + +Enterprise-ready deployment of [Flipt](https://flipt.io), an open-source, self-hosted feature flag and experimentation platform, integrated with Replicated for streamlined Kubernetes deployment. + +## Overview + +Flipt enables teams to: + +- **Deploy features gradually** with percentage-based rollouts +- **Target specific users** with advanced segmentation rules +- **Run A/B tests** and experiments safely +- **Manage feature flags** across multiple environments +- **Reduce deployment risk** with instant kill switches + +This Helm chart provides a production-ready deployment with: + +- ✅ PostgreSQL database (embedded via CloudnativePG or external) +- ✅ Redis distributed caching for high performance +- ✅ Horizontal pod autoscaling support +- ✅ TLS/ingress configuration +- ✅ Replicated SDK integration for enterprise management +- ✅ Comprehensive monitoring and metrics +- ✅ Support bundle generation for troubleshooting + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Load Balancer │ +│ (Ingress) │ +└─────────────────┬───────────────────────────────────────────┘ + │ + ┌─────────▼─────────┐ + │ Flipt Service │ + │ (2+ replicas) │ + │ HTTP + gRPC │ + └──────┬────────┬───┘ + │ │ + ┌─────────▼──┐ ┌─▼──────────┐ + │ PostgreSQL │ │ Redis │ + │ (CNPG) │ │ (Cache) │ + └────────────┘ └────────────┘ +``` + +### Components + +1. **Flipt Server**: Core application handling feature flag evaluation and management +2. **PostgreSQL**: Durable storage for feature flag definitions and metadata (CloudnativePG operator) +3. **Redis**: Distributed cache for high-performance flag evaluation (required for multiple replicas) +4. **Ingress**: External access with TLS support + +## Prerequisites + +- Kubernetes 1.24.0+ +- Helm 3.8+ +- Minimum resources: + - 2 CPU cores + - 4GB RAM + - Default storage class with RWO support +- **CloudNativePG operator** (for embedded PostgreSQL) + - Install once per cluster (see installation instructions below) +- (Optional) Ingress controller (NGINX, Traefik, etc.) +- (Optional) cert-manager for automated TLS certificates + +## Installation + +### Using Replicated Admin Console (KOTS) + +1. **Install the application** through the Replicated admin console +2. **Configure settings** in the admin console UI: + - Ingress and TLS settings + - Database configuration (embedded or external) + - Redis cache settings + - Resource limits +3. **Deploy** and monitor via the admin console + +The admin console provides: +- One-click deployment +- Configuration validation +- Preflight checks +- Automated updates +- Support bundle generation + +### Using Helm Directly + +**⚠️ Important:** The CloudNativePG operator must be installed before deploying Flipt (only needed once per cluster). + +### Important: Replicated License Required + +Flipt requires a Replicated development license for local testing. This provides access to: +- Replicated SDK integration +- Admin console features +- Preflight checks +- Support bundle generation + +**Quick Setup:** +```bash +# 1. Set up development license +export REPLICATED_API_TOKEN=your-token +./scripts/setup-dev-license.sh + +# 2. Load license and install +source .replicated/license.env +./scripts/install.sh +``` + +**Detailed instructions:** See [Development License Guide](docs/DEVELOPMENT_LICENSE.md) + +1. **Install CloudNativePG operator:** + + ```bash + # Quick install using provided script + ./scripts/install-cnpg-operator.sh + + # Or manually + helm repo add cnpg https://cloudnative-pg.github.io/charts + helm repo update + helm upgrade --install cnpg \ + --namespace cnpg-system \ + --create-namespace \ + cnpg/cloudnative-pg + ``` + +2. **Add the Helm repositories:** + + ```bash + helm repo add flipt-repo https://helm.flipt.io + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo add replicated https://charts.replicated.com + helm repo update + ``` + +3. **Install the chart:** + + ```bash + cd chart + helm dependency update + cd .. + + helm install flipt ./chart \ + --namespace flipt \ + --create-namespace \ + --values custom-values.yaml \ + --timeout 10m + ``` + +4. **Wait for deployment:** + + ```bash + kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/name=flipt \ + -n flipt \ + --timeout=5m + ``` + +## Configuration + +### Key Configuration Options + +The chart can be configured via `values.yaml` or the Replicated admin console: + +#### Flipt Application + +```yaml +flipt: + replicaCount: 2 # Number of Flipt pods (2+ recommended with Redis) + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi +``` + +#### PostgreSQL Database + +```yaml +postgresql: + type: embedded # 'embedded' or 'external' + + # Embedded database (CloudnativePG) + embedded: + enabled: true + cluster: + instances: 1 # 3 for HA + storage: + size: 10Gi + storageClass: "" +``` + +#### Redis Cache + +```yaml +redis: + enabled: true # Required for multiple Flipt replicas + architecture: standalone # or 'replication' for HA + auth: + enabled: true + password: "" # Auto-generated if empty + master: + persistence: + enabled: true + size: 5Gi +``` + +#### Ingress + +```yaml +flipt: + ingress: + enabled: true + className: nginx + hosts: + - host: flipt.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: flipt-tls + hosts: + - flipt.example.com +``` + +## Accessing Flipt + +### Via Ingress + +If ingress is enabled, access Flipt at your configured hostname: + +``` +https://flipt.example.com +``` + +### Via Port Forward + +For local access without ingress: + +```bash +kubectl port-forward -n flipt svc/flipt-flipt 8080:8080 +``` + +Then open: http://localhost:8080 + +### Via LoadBalancer + +Change the service type to LoadBalancer: + +```yaml +flipt: + service: + type: LoadBalancer +``` + +## Using Flipt + +### 1. Create Your First Feature Flag + +Navigate to the Flipt UI and: + +1. Create a new flag (e.g., `new_dashboard`) +2. Set the flag type (boolean, variant, etc.) +3. Configure targeting rules (optional) +4. Enable the flag + +### 2. Integrate with Your Application + +#### Node.js Example + +```javascript +const { FliptClient } = require('@flipt-io/flipt'); + +const client = new FliptClient({ + url: 'http://flipt.example.com', +}); + +// Evaluate a boolean flag +const result = await client.evaluateBoolean({ + namespaceKey: 'default', + flagKey: 'new_dashboard', + entityId: 'user-123', + context: { + email: 'user@example.com', + plan: 'enterprise' + } +}); + +if (result.enabled) { + // Show new dashboard +} +``` + +#### Go Example + +```go +import ( + "context" + flipt "go.flipt.io/flipt/rpc/flipt" + "google.golang.org/grpc" +) + +conn, _ := grpc.Dial("flipt.example.com:9000", grpc.WithInsecure()) +client := flipt.NewFliptClient(conn) + +resp, _ := client.EvaluateBoolean(context.Background(), &flipt.EvaluationRequest{ + NamespaceKey: "default", + FlagKey: "new_dashboard", + EntityId: "user-123", + Context: map[string]string{ + "email": "user@example.com", + "plan": "enterprise", + }, +}) + +if resp.Enabled { + // Show new dashboard +} +``` + +#### Python Example + +```python +from flipt import FliptClient + +client = FliptClient(url="http://flipt.example.com") + +result = client.evaluate_boolean( + namespace_key="default", + flag_key="new_dashboard", + entity_id="user-123", + context={ + "email": "user@example.com", + "plan": "enterprise" + } +) + +if result.enabled: + # Show new dashboard +``` + +### 3. Advanced Features + +#### Percentage Rollouts + +Gradually release features to a percentage of users: + +```yaml +Rules: + - Rollout: 25% # Start with 25% of users + Value: true +``` + +#### User Targeting + +Target specific user segments: + +```yaml +Rules: + - Segment: + Key: email + Constraint: ends_with + Value: "@enterprise.com" + Value: true +``` + +#### A/B Testing + +Create variant flags for experiments: + +```yaml +Variants: + - control: 50% + - treatment_a: 25% + - treatment_b: 25% +``` + +## Scaling & High Availability + +### Horizontal Scaling + +Enable autoscaling for automatic pod scaling: + +```yaml +flipt: + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 +``` + +### Database HA + +For production, use 3 PostgreSQL instances: + +```yaml +postgresql: + embedded: + cluster: + instances: 3 +``` + +### Redis HA + +Enable primary-replica architecture: + +```yaml +redis: + architecture: replication + replica: + replicaCount: 2 +``` + +## Monitoring + +### Prometheus Metrics + +Enable metrics collection: + +```yaml +flipt: + serviceMonitor: + enabled: true + +redis: + metrics: + enabled: true + serviceMonitor: + enabled: true +``` + +### Available Metrics + +Flipt exposes metrics at `/metrics`: + +- `flipt_evaluations_total` - Total number of flag evaluations +- `flipt_evaluation_duration_seconds` - Evaluation latency +- `flipt_cache_hits_total` - Cache hit count +- `flipt_cache_misses_total` - Cache miss count + +## Troubleshooting + +### Generate Support Bundle + +Via Replicated admin console: Navigate to Troubleshoot > Generate Support Bundle + +Via CLI: + +```bash +kubectl support-bundle ./replicated/kots-support-bundle.yaml +``` + +### Common Issues + +#### Pods Not Starting + +Check pod status and events: + +```bash +kubectl get pods -n flipt +kubectl describe pod -n flipt +``` + +#### Database Connection Issues + +Check PostgreSQL cluster status: + +```bash +kubectl get cluster -n flipt +kubectl logs -l cnpg.io/cluster=flipt-cluster -n flipt +``` + +#### Redis Connection Issues + +Check Redis status: + +```bash +kubectl get pods -l app.kubernetes.io/name=redis -n flipt +kubectl logs -l app.kubernetes.io/name=redis -n flipt +``` + +#### Cache Not Working + +Verify Redis is enabled and Flipt can connect: + +```bash +kubectl exec -it deploy/flipt-flipt -n flipt -- sh +# Inside the pod: +nc -zv flipt-redis-master 6379 +``` + +### Debug Logs + +Enable debug logging: + +```yaml +flipt: + config: + log: + level: debug +``` + +## Upgrading + +### Via Replicated Admin Console + +1. Navigate to Version History +2. Select the new version +3. Review changes +4. Deploy + +### Via Helm + +```bash +helm upgrade flipt ./chart \ + --namespace flipt \ + --values custom-values.yaml +``` + +## Uninstallation + +### Via Replicated Admin Console + +Navigate to application settings and select "Remove Application" + +### Via Helm + +```bash +helm uninstall flipt --namespace flipt +``` + +To also remove PVCs: + +```bash +kubectl delete pvc --all -n flipt +``` + +## Security Considerations + +1. **Enable TLS**: Always use TLS in production +2. **Authentication**: Configure authentication methods for the API +3. **Network Policies**: Restrict pod-to-pod communication +4. **Secrets Management**: Use external secret management for sensitive data +5. **RBAC**: Implement Kubernetes RBAC for admin access +6. **Regular Updates**: Keep Flipt and dependencies updated + +### Authentication Setup + +Flipt supports multiple authentication methods: + +```yaml +flipt: + config: + authentication: + methods: + token: + enabled: true + # Or use OIDC + oidc: + enabled: true + issuerURL: "https://accounts.google.com" + clientID: "your-client-id" + clientSecret: "your-client-secret" +``` + +## Performance Tuning + +### Database Optimization + +```yaml +postgresql: + embedded: + cluster: + resources: + limits: + cpu: 2000m + memory: 4Gi + postgresql: + parameters: + max_connections: "200" + shared_buffers: "1GB" +``` + +### Redis Optimization + +```yaml +redis: + master: + resources: + limits: + memory: 2Gi + persistence: + enabled: true + size: 20Gi +``` + +### Flipt Optimization + +```yaml +flipt: + config: + db: + maxOpenConn: 100 + maxIdleConn: 25 + connMaxLifetime: 1h + cache: + ttl: 10m # Increase cache TTL for more stable flags +``` + +## Resources + +- **Flipt Documentation**: https://docs.flipt.io +- **API Reference**: https://docs.flipt.io/reference/overview +- **SDKs**: https://docs.flipt.io/integration +- **GitHub**: https://github.com/flipt-io/flipt +- **Discord Community**: https://discord.gg/kRhEqG2T +- **Replicated Documentation**: https://docs.replicated.com + +## Support + +For issues with: +- **Flipt application**: https://github.com/flipt-io/flipt/issues +- **Helm chart/deployment**: https://github.com/flipt-io/helm-charts/issues +- **Replicated integration**: https://support.replicated.com + +## License + +- Flipt is licensed under GPL-3.0 +- This Helm chart follows the same GPL-3.0 license +- Replicated SDK has its own licensing terms + +## Contributing + +Contributions are welcome! Please: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## Changelog + +### Version 1.0.0 + +- Initial release +- Flipt v1.61.0 +- PostgreSQL 16 via CloudnativePG +- Redis 7.2 for distributed caching +- Replicated SDK integration +- Comprehensive KOTS configuration +- Preflight checks and support bundles diff --git a/applications/flipt/TROUBLESHOOTING.md b/applications/flipt/TROUBLESHOOTING.md new file mode 100644 index 00000000..9b3ae9e3 --- /dev/null +++ b/applications/flipt/TROUBLESHOOTING.md @@ -0,0 +1,529 @@ +# Flipt Troubleshooting Guide + +Common issues and solutions for deploying Flipt. + +## Installation Issues + +### Error: "no matches for kind 'Cluster' in version 'postgresql.cnpg.io/v1'" + +**Full error:** +``` +Error: INSTALLATION FAILED: unable to build kubernetes objects from release manifest: +resource mapping not found for name: "flipt-cluster" namespace: "flipt" from "": +no matches for kind "Cluster" in version "postgresql.cnpg.io/v1" +ensure CRDs are installed first +``` + +**Cause:** The CloudNativePG operator is not installed in your cluster. + +**Solution:** Install the operator before installing Flipt: + +```bash +# Quick install +./scripts/install-cnpg-operator.sh + +# Or manually +helm repo add cnpg https://cloudnative-pg.github.io/charts +helm repo update +helm upgrade --install cnpg \ + --namespace cnpg-system \ + --create-namespace \ + cnpg/cloudnative-pg + +# Verify operator is running +kubectl get pods -n cnpg-system +``` + +**Verify CRDs are installed:** +```bash +kubectl get crd | grep postgresql.cnpg.io + +# Should show: +# backups.postgresql.cnpg.io +# clusters.postgresql.cnpg.io +# poolers.postgresql.cnpg.io +# scheduledbackups.postgresql.cnpg.io +``` + +--- + +### Error: "nil pointer evaluating interface {}.enabled" + +**Full error:** +``` +Error: INSTALLATION FAILED: flipt/templates/postgresql-cluster.yaml:65:32 + executing "flipt/templates/postgresql-cluster.yaml" at + <.Values.postgresql.embedded.cluster.monitoring.enabled>: + nil pointer evaluating interface {}.enabled +``` + +**Cause:** Missing field in values.yaml (should be fixed in the latest version). + +**Solution:** Ensure you're using the latest chart or add to values.yaml: + +```yaml +postgresql: + embedded: + cluster: + monitoring: + enabled: false +``` + +--- + +### Error: Dependencies not found + +**Error:** +``` +Error: found in Chart.yaml, but missing in charts/ directory: flipt, redis, replicated +``` + +**Solution:** Update Helm dependencies: + +```bash +cd chart +helm dependency update +cd .. +``` + +Or use the Makefile: +```bash +make update-deps +``` + +--- + +### Error: CRD ownership conflict with CloudNativePG + +**Full error:** +``` +Error: INSTALLATION FAILED: unable to continue with install: +CustomResourceDefinition "backups.postgresql.cnpg.io" in namespace "" exists +and cannot be imported into the current release: invalid ownership metadata; +annotation validation error: key "meta.helm.sh/release-name" must equal "flipt": +current value is "cnpg" +``` + +**Cause:** Cached CloudNativePG dependency files from a previous configuration. + +**Solution:** Clean and rebuild dependencies: + +```bash +cd chart + +# Remove cached operator dependency +rm -f charts/cloudnative-pg-*.tgz +rm -f Chart.lock + +# Rebuild dependencies +helm dependency update +cd .. + +# Now install +helm install flipt ./chart --namespace flipt --create-namespace +``` + +Or use the Makefile: +```bash +make clean +make update-deps +make install +``` + +**Note:** The CloudNativePG operator should be installed separately at the cluster level, not as a chart dependency. + +--- + +### Error: Replicated SDK License Required + +**Full error:** +``` +Error: either license in the config file or integration license id must be specified +``` + +**Cause:** No Replicated license is configured. + +**Solution:** Set up a development license: + +```bash +# Quick setup +export REPLICATED_API_TOKEN=your-token +./scripts/setup-dev-license.sh +source .replicated/license.env +./scripts/install.sh +``` + +**Detailed guide:** See [docs/DEVELOPMENT_LICENSE.md](docs/DEVELOPMENT_LICENSE.md) + +**For CI/CD:** Licenses can be created programmatically and cleaned up after tests. + +--- + +### Pods Stuck in Pending State + +**Symptoms:** +```bash +kubectl get pods -n flipt +# Shows pods in "Pending" state +``` + +**Common causes:** + +1. **No storage class available:** + ```bash + kubectl get storageclass + + # If empty, you need a storage class + # For local testing (minikube/kind): + kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml + ``` + +2. **Insufficient resources:** + ```bash + kubectl describe pod -n flipt + + # Look for: + # "0/3 nodes are available: 3 Insufficient cpu" + # "0/3 nodes are available: 3 Insufficient memory" + ``` + + **Solution:** Reduce resource requests in values.yaml or add more nodes. + +3. **PVC not binding:** + ```bash + kubectl get pvc -n flipt + + # If status is "Pending", check events: + kubectl describe pvc -n flipt + ``` + +--- + +### PostgreSQL Cluster Not Starting + +**Check cluster status:** +```bash +kubectl get cluster -n flipt + +# Should show status "Cluster in healthy state" +``` + +**If unhealthy, check pod logs:** +```bash +kubectl logs -l cnpg.io/cluster=flipt-cluster -n flipt + +# Common issues: +# - PVC not available +# - Insufficient permissions +# - Image pull errors +``` + +**Verify operator is running:** +```bash +kubectl get pods -n cnpg-system + +# Should show: +# cnpg-cloudnative-pg-xxx 1/1 Running +``` + +--- + +### Redis Connection Issues + +**Check Redis status:** +```bash +kubectl get pods -l app.kubernetes.io/name=redis -n flipt + +# Should show master (and replica if configured) running +``` + +**Test Redis connectivity from Flipt pod:** +```bash +kubectl exec -it deploy/flipt-flipt -n flipt -- sh + +# Inside pod: +nc -zv flipt-redis-master 6379 +# Should show: Connection to flipt-redis-master 6379 port [tcp/*] succeeded! + +# Test with redis-cli (if available): +redis-cli -h flipt-redis-master -p 6379 -a ping +# Should return: PONG +``` + +**Check Redis password:** +```bash +kubectl get secret flipt-redis -n flipt -o jsonpath='{.data.redis-password}' | base64 -d +``` + +--- + +### Flipt UI Not Accessible + +**Check service:** +```bash +kubectl get svc flipt-flipt -n flipt + +# Should show: +# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) +# flipt-flipt ClusterIP 10.96.xxx.xxx 8080/TCP,9000/TCP +``` + +**Test port-forward:** +```bash +kubectl port-forward -n flipt svc/flipt-flipt 8080:8080 + +# Open browser to http://localhost:8080 +``` + +**If ingress is enabled, check ingress:** +```bash +kubectl get ingress -n flipt +kubectl describe ingress flipt-flipt -n flipt + +# Common issues: +# - Ingress controller not installed +# - DNS not pointing to ingress +# - TLS certificate issues +``` + +--- + +### Ingress Issues + +**No ingress controller:** +```bash +kubectl get ingressclass + +# If empty, install one: +# For NGINX: +helm upgrade --install ingress-nginx ingress-nginx \ + --repo https://kubernetes.github.io/ingress-nginx \ + --namespace ingress-nginx --create-namespace +``` + +**TLS certificate issues:** +```bash +# Check certificate secret +kubectl get secret flipt-tls -n flipt + +# If using cert-manager, check certificate: +kubectl get certificate -n flipt +kubectl describe certificate flipt-tls -n flipt + +# Check cert-manager logs: +kubectl logs -n cert-manager -l app=cert-manager +``` + +--- + +## Alternative: Use External PostgreSQL + +If you prefer not to use the CloudNativePG operator, use an external PostgreSQL database: + +**values.yaml:** +```yaml +postgresql: + type: external + + embedded: + enabled: false + + external: + enabled: true + host: your-postgres-host.com + port: 5432 + database: flipt + username: flipt + password: your-secure-password + sslMode: require +``` + +**Or use Bitnami PostgreSQL (simpler, single instance):** + +1. Modify Chart.yaml to use Bitnami PostgreSQL instead: + ```yaml + dependencies: + - name: postgresql + version: "12.x.x" + repository: https://charts.bitnami.com/bitnami + condition: postgresql.enabled + ``` + +2. Adjust values.yaml accordingly. + +--- + +## Alternative: Use External Redis + +If you don't want embedded Redis: + +**values.yaml:** +```yaml +redis: + enabled: false + +flipt: + config: + cache: + enabled: false # Disable caching + # Or configure external Redis: + # backend: redis + # redis: + # url: redis://external-redis:6379 +``` + +--- + +## Debugging Commands + +### View all resources +```bash +kubectl get all -n flipt +``` + +### Check events +```bash +kubectl get events -n flipt --sort-by='.lastTimestamp' +``` + +### View logs +```bash +# Flipt logs +kubectl logs -l app.kubernetes.io/name=flipt -n flipt --tail=100 -f + +# PostgreSQL logs +kubectl logs -l cnpg.io/cluster=flipt-cluster -n flipt --tail=100 -f + +# Redis logs +kubectl logs -l app.kubernetes.io/name=redis -n flipt --tail=100 -f +``` + +### Check configuration +```bash +# View rendered values +helm get values flipt -n flipt + +# View full manifest +helm get manifest flipt -n flipt + +# Test template rendering locally +helm template flipt ./chart --debug +``` + +### Resource usage +```bash +# Check pod resource usage +kubectl top pods -n flipt + +# Check node resource usage +kubectl top nodes +``` + +--- + +## Performance Issues + +### Slow flag evaluations + +**Enable Redis caching:** +Ensure Redis is enabled and Flipt is configured to use it: + +```yaml +redis: + enabled: true + +flipt: + config: + cache: + enabled: true + backend: redis + ttl: 5m +``` + +**Increase cache TTL:** +```yaml +flipt: + config: + cache: + ttl: 10m # Increase from 5m +``` + +**Scale Flipt horizontally:** +```yaml +flipt: + replicaCount: 3 # More replicas +``` + +### Database performance + +**Check connection pool settings:** +```yaml +flipt: + config: + db: + maxIdleConn: 25 + maxOpenConn: 100 + connMaxLifetime: 1h +``` + +**Scale PostgreSQL:** +```yaml +postgresql: + embedded: + cluster: + instances: 3 # HA cluster + resources: + limits: + cpu: 2000m + memory: 4Gi +``` + +--- + +## Uninstall Issues + +### Complete uninstall +```bash +# Uninstall Flipt +helm uninstall flipt -n flipt + +# Delete PVCs (data will be lost!) +kubectl delete pvc -l app.kubernetes.io/instance=flipt -n flipt + +# Delete namespace +kubectl delete namespace flipt + +# Optionally uninstall operator (if no other apps use it) +helm uninstall cnpg -n cnpg-system +kubectl delete namespace cnpg-system +``` + +### Stuck in terminating state +```bash +# Force delete namespace +kubectl delete namespace flipt --grace-period=0 --force + +# Remove finalizers if needed +kubectl patch namespace flipt -p '{"metadata":{"finalizers":[]}}' --type=merge +``` + +--- + +## Getting Help + +1. **Check logs:** Start with `kubectl logs` for the failing component +2. **Review events:** `kubectl get events -n flipt --sort-by='.lastTimestamp'` +3. **Generate support bundle:** `make support-bundle` +4. **Community:** + - Flipt Discord: https://discord.gg/kRhEqG2T + - Flipt GitHub Issues: https://github.com/flipt-io/flipt/issues + - Replicated Support: https://support.replicated.com + +--- + +## Useful Resources + +- [Flipt Documentation](https://docs.flipt.io) +- [CloudNativePG Documentation](https://cloudnative-pg.io/) +- [Kubernetes Debugging Guide](https://kubernetes.io/docs/tasks/debug/) +- [Helm Troubleshooting](https://helm.sh/docs/faq/troubleshooting/) diff --git a/applications/flipt/chart/Chart.yaml b/applications/flipt/chart/Chart.yaml new file mode 100644 index 00000000..7e01a70f --- /dev/null +++ b/applications/flipt/chart/Chart.yaml @@ -0,0 +1,46 @@ +apiVersion: v2 +name: flipt +description: | + Flipt is an open-source, self-hosted feature flag and experimentation platform. + This enterprise-ready deployment includes PostgreSQL for durable storage and Redis + for distributed caching across multiple instances. +type: application +version: 1.0.0 +appVersion: "1.61.0" +keywords: + - feature-flags + - feature-toggles + - experimentation + - a-b-testing + - flipt +home: https://flipt.io +sources: + - https://github.com/flipt-io/flipt + - https://github.com/flipt-io/helm-charts +maintainers: + - name: Replicated + url: https://replicated.com + +dependencies: + # Flipt application (official chart) + - name: flipt + version: "0.87.0" + repository: https://helm.flipt.io + condition: flipt.enabled + + # PostgreSQL database is managed via CloudnativePG operator + # Note: The CloudnativePG operator must be installed separately at the cluster level + # Install it with: helm install cnpg --namespace cnpg-system --create-namespace cnpg/cloudnative-pg + # This chart creates Cluster resources that the operator manages + + # Redis for distributed caching + - name: redis + version: "20.5.0" + repository: https://charts.bitnami.com/bitnami + condition: redis.enabled + + # Replicated SDK for admin console integration + - name: replicated + version: "^1.12.0" + repository: oci://registry.replicated.com/library + condition: replicated.enabled diff --git a/applications/flipt/chart/templates/NOTES.txt b/applications/flipt/chart/templates/NOTES.txt new file mode 100644 index 00000000..75ddd48a --- /dev/null +++ b/applications/flipt/chart/templates/NOTES.txt @@ -0,0 +1,77 @@ +Thank you for installing {{ .Chart.Name }}! + +Your release is named {{ .Release.Name }}. + +Flipt is a feature flag and experimentation platform that allows you to: + - Deploy features gradually with percentage rollouts + - Target specific users or segments with feature flags + - Run A/B tests and experiments + - Manage feature flags across multiple environments + +🚀 Getting Started: + +1. Wait for all pods to be ready: + + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=flipt -n {{ .Release.Namespace }} --timeout=300s + +2. Access the Flipt UI: + +{{- if .Values.flipt.ingress.enabled }} +{{- range .Values.flipt.ingress.hosts }} + Open your browser to: {{ if $.Values.flipt.ingress.tls }}https{{else}}http{{ end }}://{{ .host }} +{{- end }} +{{- else }} + + Run the following command to port-forward: + + kubectl port-forward -n {{ .Release.Namespace }} svc/{{ .Release.Name }}-flipt 8080:8080 + + Then open your browser to: http://localhost:8080 + +{{- end }} + +📊 Component Status: + + Database: {{ if eq .Values.postgresql.type "embedded" }}Embedded PostgreSQL (CloudnativePG){{ else }}External PostgreSQL{{ end }} + Cache: {{ if .Values.redis.enabled }}Redis (enabled for distributed caching){{ else }}In-memory (single instance only){{ end }} + Replicas: {{ .Values.flipt.replicaCount }} + +{{- if .Values.replicated.enabled }} + Replicated SDK: Enabled +{{- end }} + +📚 Next Steps: + +1. Create your first feature flag through the UI +2. Integrate Flipt into your application using one of the SDKs: + - Go: https://docs.flipt.io/integration/server + - Node.js: https://docs.flipt.io/integration/server + - Python: https://docs.flipt.io/integration/server + - Java: https://docs.flipt.io/integration/server + - More: https://docs.flipt.io/integration + +3. Configure authentication (recommended for production): + https://docs.flipt.io/authentication/overview + +4. Set up GitOps integration (optional): + https://docs.flipt.io/guides/get-going-with-gitops + +🔧 Management Commands: + + # View logs + kubectl logs -l app.kubernetes.io/name=flipt -n {{ .Release.Namespace }} --tail=100 -f + + # Check database status + {{- if eq .Values.postgresql.type "embedded" }} + kubectl get cluster {{ include "flipt.postgresql.clustername" . }} -n {{ .Release.Namespace }} + {{- end }} + + # Check Redis status + {{- if .Values.redis.enabled }} + kubectl get pods -l app.kubernetes.io/name=redis -n {{ .Release.Namespace }} + {{- end }} + +📖 Documentation: https://docs.flipt.io +💬 Community: https://discord.gg/kRhEqG2T + +For more information about Flipt, visit: https://flipt.io diff --git a/applications/flipt/chart/templates/_helpers.tpl b/applications/flipt/chart/templates/_helpers.tpl new file mode 100644 index 00000000..cdb956b5 --- /dev/null +++ b/applications/flipt/chart/templates/_helpers.tpl @@ -0,0 +1,100 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "flipt.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "flipt.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "flipt.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "flipt.labels" -}} +helm.sh/chart: {{ include "flipt.chart" . }} +{{ include "flipt.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "flipt.selectorLabels" -}} +app.kubernetes.io/name: {{ include "flipt.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +PostgreSQL connection URL +*/}} +{{- define "flipt.postgresql.url" -}} +{{- if eq .Values.postgresql.type "embedded" }} +{{- printf "postgres://%s:%s@%s-cluster-rw.%s.svc.cluster.local:5432/%s?sslmode=require" .Values.postgresql.username .Values.postgresql.password .Release.Name .Release.Namespace .Values.postgresql.database }} +{{- else }} +{{- printf "postgres://%s:%s@%s:%d/%s?sslmode=%s" .Values.postgresql.external.username .Values.postgresql.external.password .Values.postgresql.external.host (int .Values.postgresql.external.port) .Values.postgresql.external.database .Values.postgresql.external.sslMode }} +{{- end }} +{{- end }} + +{{/* +Redis connection URL +*/}} +{{- define "flipt.redis.url" -}} +{{- if .Values.redis.enabled }} +{{- if .Values.redis.auth.enabled }} +{{- printf "redis://:%s@%s-redis-master.%s.svc.cluster.local:6379" .Values.redis.auth.password .Release.Name .Release.Namespace }} +{{- else }} +{{- printf "redis://%s-redis-master.%s.svc.cluster.local:6379" .Release.Name .Release.Namespace }} +{{- end }} +{{- else }} +{{- "" }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL cluster name for CloudnativePG +*/}} +{{- define "flipt.postgresql.clustername" -}} +{{- printf "%s-cluster" .Release.Name }} +{{- end }} + +{{/* +Database secret name +*/}} +{{- define "flipt.postgresql.secret" -}} +{{- if eq .Values.postgresql.type "embedded" }} +{{- printf "%s-cluster-app" .Release.Name }} +{{- else }} +{{- printf "%s-postgresql-external" (include "flipt.fullname" .) }} +{{- end }} +{{- end }} + +{{/* +Redis secret name +*/}} +{{- define "flipt.redis.secret" -}} +{{- printf "%s-redis" .Release.Name }} +{{- end }} diff --git a/applications/flipt/chart/templates/flipt-config.yaml b/applications/flipt/chart/templates/flipt-config.yaml new file mode 100644 index 00000000..2ebd74c3 --- /dev/null +++ b/applications/flipt/chart/templates/flipt-config.yaml @@ -0,0 +1,61 @@ +{{- if .Values.flipt.enabled }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "flipt.fullname" . }}-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 4 }} +data: + config.yaml: | + log: + level: {{ .Values.flipt.config.log.level }} + encoding: {{ .Values.flipt.config.log.encoding }} + + server: + protocol: {{ .Values.flipt.config.server.protocol }} + host: {{ .Values.flipt.config.server.host }} + http_port: {{ .Values.flipt.config.server.httpPort }} + grpc_port: {{ .Values.flipt.config.server.grpcPort }} + + db: + url: {{ include "flipt.postgresql.url" . | quote }} + max_idle_conn: {{ .Values.flipt.config.db.maxIdleConn }} + max_open_conn: {{ .Values.flipt.config.db.maxOpenConn }} + conn_max_lifetime: {{ .Values.flipt.config.db.connMaxLifetime }} + + {{- if .Values.redis.enabled }} + cache: + enabled: {{ .Values.flipt.config.cache.enabled }} + backend: {{ .Values.flipt.config.cache.backend }} + ttl: {{ .Values.flipt.config.cache.ttl }} + redis: + url: {{ include "flipt.redis.url" . | quote }} + mode: {{ .Values.flipt.config.cache.redis.mode }} + prefix: {{ .Values.flipt.config.cache.redis.prefix | quote }} + {{- else }} + cache: + enabled: true + backend: memory + ttl: {{ .Values.flipt.config.cache.ttl }} + memory: + eviction_interval: 2m + {{- end }} + + {{- if .Values.flipt.config.cors.enabled }} + cors: + enabled: {{ .Values.flipt.config.cors.enabled }} + allowed_origins: + {{- range .Values.flipt.config.cors.allowedOrigins }} + - {{ . | quote }} + {{- end }} + {{- end }} + + {{- if .Values.flipt.config.authentication.methods.token.enabled }} + authentication: + methods: + token: + enabled: {{ .Values.flipt.config.authentication.methods.token.enabled }} + {{- end }} +{{- end }} diff --git a/applications/flipt/chart/templates/postgresql-cluster.yaml b/applications/flipt/chart/templates/postgresql-cluster.yaml new file mode 100644 index 00000000..f1bda469 --- /dev/null +++ b/applications/flipt/chart/templates/postgresql-cluster.yaml @@ -0,0 +1,73 @@ +{{- if and .Values.postgresql.embedded.enabled (eq .Values.postgresql.type "embedded") }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "flipt.fullname" . }}-postgresql-credentials + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 4 }} +type: Opaque +stringData: + username: {{ .Values.postgresql.username | quote }} + password: {{ .Values.postgresql.password | default (randAlphaNum 32) | quote }} + database: {{ .Values.postgresql.database | quote }} +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: {{ include "flipt.postgresql.clustername" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 4 }} +spec: + instances: {{ .Values.postgresql.embedded.cluster.instances }} + + imageName: {{ .Values.postgresql.embedded.cluster.imageName }} + + bootstrap: + initdb: + database: {{ .Values.postgresql.database }} + owner: {{ .Values.postgresql.username }} + secret: + name: {{ include "flipt.fullname" . }}-postgresql-credentials + + storage: + size: {{ .Values.postgresql.embedded.cluster.storage.size }} + {{- if .Values.postgresql.embedded.cluster.storage.storageClass }} + storageClass: {{ .Values.postgresql.embedded.cluster.storage.storageClass }} + {{- end }} + + resources: + {{- toYaml .Values.postgresql.embedded.cluster.resources | nindent 4 }} + + {{- if .Values.postgresql.embedded.cluster.backup.enabled }} + backup: + {{- toYaml .Values.postgresql.embedded.cluster.backup | nindent 4 }} + {{- end }} + + postgresql: + parameters: + max_connections: "200" + shared_buffers: "256MB" + effective_cache_size: "1GB" + maintenance_work_mem: "64MB" + checkpoint_completion_target: "0.9" + wal_buffers: "16MB" + default_statistics_target: "100" + random_page_cost: "1.1" + effective_io_concurrency: "200" + work_mem: "2621kB" + min_wal_size: "1GB" + max_wal_size: "4GB" + + monitoring: + enablePodMonitor: {{ .Values.postgresql.embedded.cluster.monitoring.enabled | default false }} + + {{- if gt (int .Values.postgresql.embedded.cluster.instances) 1 }} + # Enable synchronous replication for HA + postgresql: + syncReplicaElectionConstraint: + enabled: true + {{- end }} +{{- end }} diff --git a/applications/flipt/chart/values.yaml b/applications/flipt/chart/values.yaml new file mode 100644 index 00000000..dea6ce4c --- /dev/null +++ b/applications/flipt/chart/values.yaml @@ -0,0 +1,264 @@ +## Flipt Feature Flag Platform Configuration +## This values file provides sensible defaults for deploying Flipt with PostgreSQL and Redis + +global: + ## Global image registry override + ## Useful for air-gap deployments or custom registries + imageRegistry: "" + +## Flipt application configuration +flipt: + enabled: true + + image: + ## Use global registry if set, otherwise default + registry: docker.flipt.io + repository: flipt/flipt + tag: "" # Uses appVersion from Chart.yaml + pullPolicy: IfNotPresent + + ## Number of Flipt replicas (2+ recommended for HA with Redis cache) + replicaCount: 2 + + ## Resource limits for Flipt pods + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + + ## Flipt configuration + ## See https://docs.flipt.io/configuration/overview for all options + config: + ## Logging configuration + log: + level: info + encoding: json + + ## Server configuration + server: + protocol: http + host: 0.0.0.0 + httpPort: 8080 + grpcPort: 9000 + + ## Database configuration (templated from values below) + db: + url: "" # Will be set via template based on postgresql configuration + maxIdleConn: 10 + maxOpenConn: 50 + connMaxLifetime: 1h + + ## Cache configuration (templated from redis configuration) + cache: + enabled: true + backend: redis # or 'memory' for single instance + ttl: 5m + redis: + url: "" # Will be set via template based on redis configuration + mode: single # or 'cluster' for Redis cluster + prefix: "flipt" + + ## CORS configuration for web UI + cors: + enabled: true + allowedOrigins: + - "*" + + ## Authentication (optional, configure for production) + authentication: + methods: + token: + enabled: false + + ## Service configuration + service: + type: ClusterIP + httpPort: 8080 + grpcPort: 9000 + annotations: {} + + ## Ingress configuration + ingress: + enabled: false + className: "" + annotations: {} + # cert-manager.io/cluster-issuer: letsencrypt-prod + # nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: flipt.example.com + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: flipt-tls + # hosts: + # - flipt.example.com + + ## Horizontal Pod Autoscaler + autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + + ## Pod Disruption Budget (recommended for HA) + podDisruptionBudget: + enabled: true + minAvailable: 1 + + ## ServiceMonitor for Prometheus metrics + serviceMonitor: + enabled: false + interval: 30s + +## PostgreSQL Database Configuration +postgresql: + ## Use embedded PostgreSQL (CloudnativePG) or external database + type: embedded # 'embedded' or 'external' + + ## Embedded PostgreSQL using CloudnativePG operator + embedded: + enabled: true + ## Database cluster configuration + cluster: + instances: 1 # 3 recommended for production HA + storage: + size: 10Gi + storageClass: "" # Uses default storage class + ## PostgreSQL version + imageName: ghcr.io/cloudnative-pg/postgresql:16 + ## Resource limits + resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi + ## Enable continuous backup (optional) + backup: + enabled: false + # Configure S3 or similar for WAL archiving + # See CloudnativePG docs for details + ## Enable monitoring (optional) + monitoring: + enabled: false + + ## External PostgreSQL connection details + external: + enabled: false + host: postgresql.example.com + port: 5432 + database: flipt + username: flipt + password: "" # Set via secret or KOTS config + sslMode: require + + ## Database initialization + database: flipt + username: flipt + password: "" # Auto-generated if empty (for embedded) + +## Redis Cache Configuration +redis: + enabled: true + + ## Redis architecture: standalone or replication + architecture: standalone # 'replication' for primary/replica setup + + ## Redis authentication + auth: + enabled: true + password: "" # Auto-generated if empty + + ## Redis Master configuration + master: + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + persistence: + enabled: true + size: 5Gi + storageClass: "" # Uses default storage class + + ## Redis Replica configuration (if architecture: replication) + replica: + replicaCount: 1 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + persistence: + enabled: true + size: 5Gi + + ## Redis metrics for monitoring + metrics: + enabled: false + serviceMonitor: + enabled: false + +## Replicated SDK Integration +replicated: + enabled: true + + ## Integration configuration + integration: + enabled: true + + ## Custom license fields (optional) + ## licenseFields: {} + +## Additional Kubernetes Resources + +## ConfigMap for additional Flipt configuration +extraConfigMap: {} + +## Secrets for sensitive configuration +extraSecrets: {} + +## Pod annotations +podAnnotations: {} + +## Pod labels +podLabels: {} + +## Security context +securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + +## Node selector +nodeSelector: {} + +## Tolerations +tolerations: [] + +## Affinity rules +affinity: {} + ## Example anti-affinity to spread pods across nodes + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 100 + # podAffinityTerm: + # labelSelector: + # matchExpressions: + # - key: app.kubernetes.io/name + # operator: In + # values: + # - flipt + # topologyKey: kubernetes.io/hostname diff --git a/applications/flipt/docs/DEVELOPMENT_LICENSE.md b/applications/flipt/docs/DEVELOPMENT_LICENSE.md new file mode 100644 index 00000000..2421256e --- /dev/null +++ b/applications/flipt/docs/DEVELOPMENT_LICENSE.md @@ -0,0 +1,231 @@ +# Development License Guide + +This guide explains how to obtain and configure a Replicated development license for local testing of Flipt. + +## Why a License is Required + +Flipt integrates with Replicated's SDK to provide: +- Admin console integration +- Preflight checks +- Support bundle generation +- License enforcement +- Automated updates + +The Replicated SDK requires a valid license to function, even in development environments. + +## Quick Start + +### Option 1: Automated Setup (Recommended) + +```bash +# 1. Set up your Replicated API token +export REPLICATED_API_TOKEN=your-token-here + +# 2. Run the setup script +./scripts/setup-dev-license.sh + +# 3. Load the license +source .replicated/license.env + +# 4. Install Flipt +./scripts/install.sh +``` + +### Option 2: Using Makefile + +```bash +# Set up license and install in one command +export REPLICATED_API_TOKEN=your-token-here +make install-with-license +``` + +## Prerequisites + +### 1. Replicated CLI + +Install the Replicated CLI: + +```bash +# macOS +brew install replicatedhq/replicated/cli + +# Linux/macOS (alternative) +curl -s https://api.github.com/repos/replicatedhq/replicated/releases/latest | \ + grep "browser_download_url.*$(uname -s)_$(uname -m)" | \ + cut -d '"' -f 4 | \ + xargs curl -L -o replicated +chmod +x replicated +sudo mv replicated /usr/local/bin/ +``` + +Verify installation: +```bash +replicated version +``` + +### 2. Replicated API Token + +1. Log in to [vendor.replicated.com](https://vendor.replicated.com) +2. Navigate to **Settings** > **Service Accounts** +3. Click **Create Service Account** +4. Copy the API token +5. Export it: + ```bash + export REPLICATED_API_TOKEN=your-token-here + ``` + + Or add to your shell profile (~/.bashrc, ~/.zshrc): + ```bash + echo 'export REPLICATED_API_TOKEN=your-token-here' >> ~/.zshrc + source ~/.zshrc + ``` + +## Manual License Setup + +If you prefer manual setup or need more control: + +### Step 1: Create a Development Customer + +```bash +replicated customer create \ + --app flipt \ + --name "dev-$(whoami)-$(date +%s)" \ + --channel Unstable \ + --license-type dev \ + --output json > customer.json +``` + +### Step 2: Extract License ID + +```bash +LICENSE_ID=$(jq -r '.id' customer.json) +echo "License ID: $LICENSE_ID" +``` + +### Step 3: Save License Configuration + +```bash +mkdir -p .replicated +echo "REPLICATED_LICENSE_ID=$LICENSE_ID" > .replicated/license.env +``` + +### Step 4: Use License + +```bash +source .replicated/license.env +./scripts/install.sh +``` + +## License Management + +### View Your Licenses + +```bash +replicated customer ls +``` + +### Delete a License + +```bash +replicated customer rm --customer "customer-name" + +# Or delete all dev licenses +replicated customer ls --output json | \ + jq -r '.[] | select(.licenseType == "dev") | .name' | \ + xargs -I {} replicated customer rm --customer {} +``` + +### License Expiry + +Development licenses may have expiration dates. If your license expires: + +1. Delete the old license: + ```bash + make clean-license + ``` + +2. Create a new one: + ```bash + ./scripts/setup-dev-license.sh + ``` + +## Troubleshooting + +### Error: "replicated: command not found" + +Install the Replicated CLI (see Prerequisites above). + +### Error: "unauthorized: authentication required" + +Your API token may be invalid or expired: +1. Verify token: `replicated api version` +2. Generate new token at vendor.replicated.com +3. Export new token: `export REPLICATED_API_TOKEN=new-token` + +### Error: "license not found" + +The license secret may not be created: +```bash +# Verify secret exists +kubectl get secret replicated-license -n flipt + +# Recreate if missing +kubectl create secret generic replicated-license \ + --from-literal=license="$REPLICATED_LICENSE_ID" \ + --namespace flipt +``` + +### Pod Still Crashing + +Check Replicated SDK logs: +```bash +kubectl logs -l app=replicated -n flipt +``` + +Common issues: +- License ID is incorrect +- License has expired +- Network issues accessing Replicated services + +## CI/CD Integration + +For automated testing in CI/CD: + +```yaml +# Example GitHub Actions +- name: Setup Replicated License + env: + REPLICATED_API_TOKEN: ${{ secrets.REPLICATED_API_TOKEN }} + run: | + ./scripts/setup-dev-license.sh + source .replicated/license.env + +- name: Install Flipt + run: | + source .replicated/license.env + ./scripts/install.sh + +- name: Cleanup License + if: always() + run: | + make clean-license +``` + +## Alternative: Disable Replicated SDK + +If you absolutely need to run without a license (not recommended for production testing): + +```bash +helm install flipt ./chart \ + --namespace flipt \ + --create-namespace \ + --set replicated.enabled=false +``` + +**Note:** This disables all Replicated features including support bundles and preflight checks. + +## Resources + +- [Replicated CLI Documentation](https://docs.replicated.com/reference/replicated-cli) +- [Customer Management](https://docs.replicated.com/vendor/customers-managing) +- [License Types](https://docs.replicated.com/vendor/licenses-about) diff --git a/applications/flipt/examples/README.md b/applications/flipt/examples/README.md new file mode 100644 index 00000000..bf5e6550 --- /dev/null +++ b/applications/flipt/examples/README.md @@ -0,0 +1,392 @@ +# Flipt Examples + +This directory contains examples for deploying and using Flipt in various scenarios. + +## Directory Structure + +``` +examples/ +├── kubernetes/ # Kubernetes deployment examples +│ ├── values-minimal.yaml +│ ├── values-production.yaml +│ └── values-external-db.yaml +└── sdk/ # SDK integration examples + ├── nodejs-example.js + ├── golang-example.go + └── python-example.py +``` + +## Kubernetes Deployment Examples + +### Minimal Setup (Development/Testing) + +The minimal configuration is perfect for local development or testing: + +```bash +helm install flipt ../chart \ + --namespace flipt \ + --create-namespace \ + --values kubernetes/values-minimal.yaml +``` + +Features: +- Single Flipt replica +- Embedded PostgreSQL (1 instance) +- Redis standalone +- No ingress (use port-forward) + +Access: +```bash +kubectl port-forward -n flipt svc/flipt-flipt 8080:8080 +``` + +### Production Setup (High Availability) + +The production configuration provides a highly available deployment: + +```bash +helm install flipt ../chart \ + --namespace flipt \ + --create-namespace \ + --values kubernetes/values-production.yaml +``` + +Features: +- 3 Flipt replicas with autoscaling +- PostgreSQL cluster (3 instances) +- Redis primary-replica architecture +- Ingress with TLS +- Prometheus metrics +- Pod disruption budgets + +### External Database Setup + +Use this when you have an existing PostgreSQL database: + +```bash +helm install flipt ../chart \ + --namespace flipt \ + --create-namespace \ + --values kubernetes/values-external-db.yaml +``` + +**⚠️ Important:** Update the database credentials before deploying! + +## SDK Integration Examples + +### Node.js + +The Node.js example demonstrates: +- Simple boolean flag evaluation +- Variant flags for A/B testing +- Batch flag evaluation +- Express middleware integration +- Local caching with TTL + +**Run the example:** + +```bash +cd sdk +npm install @flipt-io/flipt +export FLIPT_URL=http://localhost:8080 +node nodejs-example.js +``` + +**Key features:** +- ✅ Boolean flags +- ✅ Variant flags (A/B testing) +- ✅ Batch evaluation +- ✅ Express middleware +- ✅ Caching layer +- ✅ Error handling + +### Go + +The Go example demonstrates: +- gRPC client integration +- Boolean and variant flag evaluation +- HTTP middleware +- Cached client with TTL +- Production-ready patterns + +**Run the example:** + +```bash +cd sdk +go mod init example +go get go.flipt.io/flipt/rpc/flipt +export FLIPT_ADDR=localhost:9000 +go run golang-example.go +``` + +**Key features:** +- ✅ gRPC client +- ✅ Boolean flags +- ✅ Variant flags +- ✅ HTTP middleware +- ✅ Client-side caching +- ✅ Context propagation + +### Python + +The Python example demonstrates: +- HTTP REST API client +- Flask middleware integration +- Django middleware integration +- FastAPI dependency injection +- Caching with TTL + +**Run the example:** + +```bash +cd sdk +pip install requests flask # or django, or fastapi +export FLIPT_URL=http://localhost:8080 +python python-example.py +``` + +**Key features:** +- ✅ REST API client +- ✅ Flask integration +- ✅ Django integration +- ✅ FastAPI integration +- ✅ Client-side caching +- ✅ Error handling + +## Common Use Cases + +### 1. Feature Rollout + +Gradually enable a feature for increasing percentages of users: + +```javascript +// Week 1: 10% rollout +// Week 2: 25% rollout +// Week 3: 50% rollout +// Week 4: 100% rollout + +const enabled = await flipt.evaluateBoolean({ + flagKey: 'new_feature', + entityId: userId, + context: { /* user attributes */ } +}); +``` + +### 2. User Targeting + +Enable features for specific user segments: + +```javascript +const enabled = await flipt.evaluateBoolean({ + flagKey: 'premium_feature', + entityId: userId, + context: { + plan: 'enterprise', + email: user.email + } +}); +``` + +### 3. A/B Testing + +Run experiments with multiple variants: + +```javascript +const variant = await flipt.evaluateVariant({ + flagKey: 'checkout_experiment', + entityId: userId, + context: { /* user attributes */ } +}); + +switch (variant.variantKey) { + case 'control': + // Original experience + break; + case 'variant_a': + // Variant A experience + break; + case 'variant_b': + // Variant B experience + break; +} +``` + +### 4. Kill Switch + +Instantly disable a problematic feature: + +```javascript +// Simply toggle the flag off in Flipt UI +// All evaluations will immediately return false +const enabled = await flipt.evaluateBoolean({ + flagKey: 'problematic_feature', + entityId: userId +}); +``` + +### 5. Environment-Specific Config + +Different behavior per environment: + +```javascript +// In Flipt, set different values per environment: +// - dev: feature_enabled = true +// - staging: feature_enabled = true +// - production: feature_enabled = false + +const enabled = await flipt.evaluateBoolean({ + namespaceKey: process.env.ENVIRONMENT, + flagKey: 'experimental_feature', + entityId: userId +}); +``` + +## Best Practices + +### 1. Always Provide Default Values + +```javascript +const enabled = client.evaluateBoolean(/* ... */) || false; +``` + +### 2. Use Caching for High-Traffic Endpoints + +```javascript +const cachedClient = new CachedFliptClient(client, 60000); // 1 min TTL +``` + +### 3. Handle Errors Gracefully + +```javascript +try { + const enabled = await client.evaluateBoolean(/* ... */); +} catch (error) { + // Log error and return safe default + console.error('Flag evaluation failed:', error); + return false; +} +``` + +### 4. Use Meaningful Entity IDs + +```javascript +// Good: Consistent user identifier +entityId: user.id + +// Bad: Random or changing identifiers +entityId: Math.random() +``` + +### 5. Provide Rich Context + +```javascript +context: { + email: user.email, + plan: user.subscription.plan, + region: user.region, + accountAge: calculateAge(user.createdAt), + // Add any attribute you might want to target on +} +``` + +### 6. Monitor Flag Evaluations + +```javascript +const result = await client.evaluateBoolean(/* ... */); + +// Log or send metrics +metrics.increment('flipt.evaluation', { + flag: 'feature_name', + result: result.enabled +}); +``` + +## Testing Feature Flags + +### Unit Tests + +Mock the Flipt client in your tests: + +```javascript +// Jest example +jest.mock('@flipt-io/flipt'); + +test('shows new dashboard when flag enabled', () => { + FliptClient.prototype.evaluateBoolean.mockResolvedValue({ + enabled: true + }); + + // Test code that uses the flag +}); +``` + +### Integration Tests + +Use a test Flipt instance: + +```javascript +const testClient = new FliptClient({ + url: 'http://flipt-test:8080' +}); +``` + +### Local Development + +Override flags for development: + +```javascript +const FEATURE_OVERRIDES = { + 'new_feature': process.env.NODE_ENV === 'development' +}; + +const enabled = FEATURE_OVERRIDES[flagKey] ?? + await client.evaluateBoolean(/* ... */); +``` + +## Troubleshooting + +### Connection Issues + +```bash +# Check Flipt is accessible +curl http://flipt.flipt.svc.cluster.local:8080/health + +# Port forward for local testing +kubectl port-forward -n flipt svc/flipt-flipt 8080:8080 +``` + +### Cache Issues + +```javascript +// Clear cache if flags aren't updating +cachedClient.cache.clear(); +``` + +### Debug Logging + +```javascript +// Enable debug logging +const client = new FliptClient({ + url: 'http://flipt:8080', + debug: true +}); +``` + +## Additional Resources + +- [Flipt Documentation](https://docs.flipt.io) +- [SDK Reference](https://docs.flipt.io/integration) +- [API Documentation](https://docs.flipt.io/reference/overview) +- [Best Practices](https://docs.flipt.io/guides/best-practices) + +## Contributing + +Have a useful example? Please contribute! + +1. Add your example to the appropriate directory +2. Update this README +3. Submit a pull request + +## License + +Examples are provided under the same license as the parent repository. diff --git a/applications/flipt/examples/kubernetes/values-external-db.yaml b/applications/flipt/examples/kubernetes/values-external-db.yaml new file mode 100644 index 00000000..03485418 --- /dev/null +++ b/applications/flipt/examples/kubernetes/values-external-db.yaml @@ -0,0 +1,36 @@ +# Values for using external PostgreSQL database +# Use this when you have an existing PostgreSQL instance + +flipt: + enabled: true + replicaCount: 2 + + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +postgresql: + type: external + + embedded: + enabled: false + + external: + enabled: true + host: postgresql.database.svc.cluster.local + port: 5432 + database: flipt + username: flipt + password: "your-secure-password-here" # Use secret in production + sslMode: require + +redis: + enabled: true + architecture: standalone + +replicated: + enabled: true diff --git a/applications/flipt/examples/kubernetes/values-minimal.yaml b/applications/flipt/examples/kubernetes/values-minimal.yaml new file mode 100644 index 00000000..eedc7849 --- /dev/null +++ b/applications/flipt/examples/kubernetes/values-minimal.yaml @@ -0,0 +1,37 @@ +# Minimal values.yaml for development/testing +# Single replica, embedded database and Redis + +flipt: + enabled: true + replicaCount: 1 + + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + + ingress: + enabled: false # Use port-forward for local access + +postgresql: + type: embedded + embedded: + enabled: true + cluster: + instances: 1 + storage: + size: 5Gi + +redis: + enabled: true + architecture: standalone + master: + persistence: + enabled: true + size: 2Gi + +replicated: + enabled: true diff --git a/applications/flipt/examples/kubernetes/values-production.yaml b/applications/flipt/examples/kubernetes/values-production.yaml new file mode 100644 index 00000000..55ca4e78 --- /dev/null +++ b/applications/flipt/examples/kubernetes/values-production.yaml @@ -0,0 +1,129 @@ +# Production values.yaml +# High availability with multiple replicas, Redis caching, and monitoring + +flipt: + enabled: true + replicaCount: 3 + + resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 200m + memory: 256Mi + + ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + hosts: + - host: flipt.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: flipt-tls + hosts: + - flipt.example.com + + autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 80 + + podDisruptionBudget: + enabled: true + minAvailable: 2 + + serviceMonitor: + enabled: true + interval: 30s + + config: + log: + level: info + encoding: json + +postgresql: + type: embedded + embedded: + enabled: true + cluster: + instances: 3 # HA configuration + storage: + size: 50Gi + storageClass: fast-ssd + resources: + limits: + cpu: 2000m + memory: 4Gi + requests: + cpu: 500m + memory: 1Gi + backup: + enabled: true + # Configure backup to S3/MinIO + monitoring: + enabled: true + +redis: + enabled: true + architecture: replication + + auth: + enabled: true + # password will be auto-generated + + master: + persistence: + enabled: true + size: 20Gi + storageClass: fast-ssd + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 200m + memory: 512Mi + + replica: + replicaCount: 2 + persistence: + enabled: true + size: 20Gi + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 200m + memory: 512Mi + + metrics: + enabled: true + serviceMonitor: + enabled: true + +replicated: + enabled: true + +# Anti-affinity to spread pods across nodes +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - flipt + topologyKey: kubernetes.io/hostname diff --git a/applications/flipt/examples/sdk/golang-example.go b/applications/flipt/examples/sdk/golang-example.go new file mode 100644 index 00000000..162fd587 --- /dev/null +++ b/applications/flipt/examples/sdk/golang-example.go @@ -0,0 +1,330 @@ +/** + * Flipt Go SDK Example + * + * This example demonstrates how to integrate Flipt feature flags + * into a Go application. + * + * Install: go get go.flipt.io/flipt/rpc/flipt + */ + +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "time" + + flipt "go.flipt.io/flipt/rpc/flipt" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// FliptClient wraps the Flipt gRPC client +type FliptClient struct { + client flipt.FliptClient + conn *grpc.ClientConn +} + +// NewFliptClient creates a new Flipt client +func NewFliptClient(address string) (*FliptClient, error) { + // Connect to Flipt gRPC server + conn, err := grpc.Dial( + address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + grpc.WithTimeout(5*time.Second), + ) + if err != nil { + return nil, fmt.Errorf("failed to connect to Flipt: %w", err) + } + + client := flipt.NewFliptClient(conn) + return &FliptClient{ + client: client, + conn: conn, + }, nil +} + +// Close closes the gRPC connection +func (c *FliptClient) Close() error { + return c.conn.Close() +} + +// Example 1: Simple boolean flag evaluation +func (c *FliptClient) CheckBooleanFlag(ctx context.Context, userID string) (bool, error) { + resp, err := c.client.EvaluateBoolean(ctx, &flipt.EvaluationRequest{ + NamespaceKey: "default", + FlagKey: "new_dashboard", + EntityId: userID, + Context: map[string]string{ + "email": "user@example.com", + "plan": "enterprise", + "region": "us-east-1", + }, + }) + if err != nil { + return false, fmt.Errorf("failed to evaluate flag: %w", err) + } + + if resp.Enabled { + fmt.Println("✓ New dashboard is enabled for this user") + } else { + fmt.Println("✗ New dashboard is disabled for this user") + } + + return resp.Enabled, nil +} + +// Example 2: Variant flag evaluation (A/B testing) +func (c *FliptClient) CheckVariantFlag(ctx context.Context, userID string) (string, error) { + resp, err := c.client.EvaluateVariant(ctx, &flipt.EvaluationRequest{ + NamespaceKey: "default", + FlagKey: "checkout_flow", + EntityId: userID, + Context: map[string]string{ + "userId": userID, + "email": "test@example.com", + "accountAge": "30", + }, + }) + if err != nil { + return "control", fmt.Errorf("failed to evaluate variant: %w", err) + } + + fmt.Printf("User assigned to variant: %s\n", resp.VariantKey) + + switch resp.VariantKey { + case "control": + return "original_checkout", nil + case "variant_a": + return "streamlined_checkout", nil + case "variant_b": + return "express_checkout", nil + default: + return "original_checkout", nil + } +} + +// Example 3: Batch evaluation +func (c *FliptClient) EvaluateMultipleFlags(ctx context.Context, userID string, context map[string]string) (map[string]bool, error) { + flags := []string{"new_dashboard", "dark_mode", "beta_features"} + results := make(map[string]bool) + + for _, flagKey := range flags { + resp, err := c.client.EvaluateBoolean(ctx, &flipt.EvaluationRequest{ + NamespaceKey: "default", + FlagKey: flagKey, + EntityId: userID, + Context: context, + }) + if err != nil { + log.Printf("Error evaluating flag %s: %v", flagKey, err) + results[flagKey] = false + continue + } + results[flagKey] = resp.Enabled + } + + fmt.Printf("Feature flags for user: %+v\n", results) + return results, nil +} + +// Example 4: HTTP middleware for feature flags +type FeatureFlagMiddleware struct { + client *FliptClient +} + +func NewFeatureFlagMiddleware(client *FliptClient) *FeatureFlagMiddleware { + return &FeatureFlagMiddleware{client: client} +} + +func (m *FeatureFlagMiddleware) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Extract user info from request (simplified) + userID := r.Header.Get("X-User-ID") + if userID == "" { + userID = "anonymous" + } + + context := map[string]string{ + "email": r.Header.Get("X-User-Email"), + "plan": r.Header.Get("X-User-Plan"), + "ip": r.RemoteAddr, + "userAgent": r.UserAgent(), + } + + // Evaluate flags + features, err := m.client.EvaluateMultipleFlags(ctx, userID, context) + if err != nil { + log.Printf("Error loading feature flags: %v", err) + features = make(map[string]bool) // Empty on error + } + + // Add features to context + type contextKey string + const featuresKey contextKey = "features" + ctx = context.WithValue(ctx, featuresKey, features) + + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +// Example 5: Cached client with TTL +type CachedFliptClient struct { + client *FliptClient + cache map[string]*cacheEntry + ttl time.Duration +} + +type cacheEntry struct { + value bool + timestamp time.Time +} + +func NewCachedFliptClient(client *FliptClient, ttl time.Duration) *CachedFliptClient { + return &CachedFliptClient{ + client: client, + cache: make(map[string]*cacheEntry), + ttl: ttl, + } +} + +func (c *CachedFliptClient) EvaluateBoolean(ctx context.Context, namespaceKey, flagKey, entityID string, context map[string]string) (bool, error) { + cacheKey := fmt.Sprintf("%s:%s:%s", namespaceKey, flagKey, entityID) + + // Check cache + if entry, ok := c.cache[cacheKey]; ok { + if time.Since(entry.timestamp) < c.ttl { + return entry.value, nil + } + } + + // Fetch fresh value + resp, err := c.client.client.EvaluateBoolean(ctx, &flipt.EvaluationRequest{ + NamespaceKey: namespaceKey, + FlagKey: flagKey, + EntityId: entityID, + Context: context, + }) + if err != nil { + // Return stale cache on error if available + if entry, ok := c.cache[cacheKey]; ok { + log.Printf("Using stale cache due to error: %v", err) + return entry.value, nil + } + return false, err + } + + // Update cache + c.cache[cacheKey] = &cacheEntry{ + value: resp.Enabled, + timestamp: time.Now(), + } + + return resp.Enabled, nil +} + +// Example HTTP handlers +func dashboardHandler(w http.ResponseWriter, r *http.Request) { + type contextKey string + const featuresKey contextKey = "features" + + features, ok := r.Context().Value(featuresKey).(map[string]bool) + if !ok { + features = make(map[string]bool) + } + + if features["new_dashboard"] { + fmt.Fprintf(w, "Showing new dashboard v2") + } else { + fmt.Fprintf(w, "Showing old dashboard v1") + } +} + +func configHandler(w http.ResponseWriter, r *http.Request) { + type contextKey string + const featuresKey contextKey = "features" + + features, ok := r.Context().Value(featuresKey).(map[string]bool) + if !ok { + features = make(map[string]bool) + } + + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"features": %+v, "version": "1.0.0"}`, features) +} + +func main() { + // Get Flipt address from environment or use default + fliptAddr := os.Getenv("FLIPT_ADDR") + if fliptAddr == "" { + fliptAddr = "flipt.flipt.svc.cluster.local:9000" + } + + // Create Flipt client + client, err := NewFliptClient(fliptAddr) + if err != nil { + log.Fatalf("Failed to create Flipt client: %v", err) + } + defer client.Close() + + ctx := context.Background() + + fmt.Println("Flipt Go SDK Examples\n") + + // Example 1: Boolean flag + fmt.Println("Example 1: Boolean Flag") + _, err = client.CheckBooleanFlag(ctx, "user-123") + if err != nil { + log.Printf("Error: %v", err) + } + fmt.Println() + + // Example 2: Variant flag + fmt.Println("Example 2: Variant Flag") + variant, err := client.CheckVariantFlag(ctx, "user-456") + if err != nil { + log.Printf("Error: %v", err) + } else { + fmt.Printf("Selected checkout: %s\n", variant) + } + fmt.Println() + + // Example 3: Batch evaluation + fmt.Println("Example 3: Batch Evaluation") + _, err = client.EvaluateMultipleFlags(ctx, "user-789", map[string]string{ + "email": "user789@example.com", + "plan": "premium", + }) + if err != nil { + log.Printf("Error: %v", err) + } + fmt.Println() + + // Example 4: Cached evaluation + fmt.Println("Example 4: Cached Evaluation") + cachedClient := NewCachedFliptClient(client, 1*time.Minute) + result1, _ := cachedClient.EvaluateBoolean(ctx, "default", "new_dashboard", "user-123", nil) + fmt.Printf("First call (from API): %v\n", result1) + result2, _ := cachedClient.EvaluateBoolean(ctx, "default", "new_dashboard", "user-123", nil) + fmt.Printf("Second call (from cache): %v\n", result2) + fmt.Println() + + // Example 5: HTTP server with middleware + fmt.Println("Example 5: Starting HTTP server with feature flag middleware") + middleware := NewFeatureFlagMiddleware(client) + + mux := http.NewServeMux() + mux.HandleFunc("/dashboard", dashboardHandler) + mux.HandleFunc("/api/config", configHandler) + + handler := middleware.Handler(mux) + + fmt.Println("Server listening on :8080") + log.Fatal(http.ListenAndServe(":8080", handler)) +} diff --git a/applications/flipt/examples/sdk/nodejs-example.js b/applications/flipt/examples/sdk/nodejs-example.js new file mode 100644 index 00000000..2fea9956 --- /dev/null +++ b/applications/flipt/examples/sdk/nodejs-example.js @@ -0,0 +1,241 @@ +/** + * Flipt Node.js SDK Example + * + * This example demonstrates how to integrate Flipt feature flags + * into a Node.js application. + * + * Install: npm install @flipt-io/flipt + */ + +const { FliptClient } = require('@flipt-io/flipt'); + +// Initialize Flipt client +const flipt = new FliptClient({ + url: process.env.FLIPT_URL || 'http://flipt.flipt.svc.cluster.local:8080', + // Optional: authentication + // authentication: { + // clientToken: process.env.FLIPT_CLIENT_TOKEN + // } +}); + +// Example 1: Simple boolean flag evaluation +async function checkBooleanFlag() { + try { + const result = await flipt.evaluateBoolean({ + namespaceKey: 'default', + flagKey: 'new_dashboard', + entityId: 'user-123', + context: { + email: 'user@example.com', + plan: 'enterprise', + region: 'us-east-1' + } + }); + + if (result.enabled) { + console.log('✓ New dashboard is enabled for this user'); + return true; + } else { + console.log('✗ New dashboard is disabled for this user'); + return false; + } + } catch (error) { + console.error('Error evaluating flag:', error); + // Default to false on error (fail-safe) + return false; + } +} + +// Example 2: Variant flag evaluation (A/B testing) +async function checkVariantFlag() { + try { + const result = await flipt.evaluateVariant({ + namespaceKey: 'default', + flagKey: 'checkout_flow', + entityId: 'user-456', + context: { + userId: 'user-456', + email: 'test@example.com', + accountAge: '30' + } + }); + + console.log(`User assigned to variant: ${result.variantKey}`); + + switch (result.variantKey) { + case 'control': + return 'original_checkout'; + case 'variant_a': + return 'streamlined_checkout'; + case 'variant_b': + return 'express_checkout'; + default: + return 'original_checkout'; + } + } catch (error) { + console.error('Error evaluating variant:', error); + return 'original_checkout'; // Default variant + } +} + +// Example 3: Batch evaluation (multiple flags at once) +async function evaluateMultipleFlags(userId, context) { + try { + const flags = ['new_dashboard', 'dark_mode', 'beta_features']; + const results = {}; + + for (const flagKey of flags) { + const result = await flipt.evaluateBoolean({ + namespaceKey: 'default', + flagKey, + entityId: userId, + context + }); + results[flagKey] = result.enabled; + } + + console.log('Feature flags for user:', results); + return results; + } catch (error) { + console.error('Error evaluating flags:', error); + return {}; + } +} + +// Example 4: Using flags in Express middleware +function createFeatureFlagMiddleware(flipt) { + return async (req, res, next) => { + const userId = req.user?.id || 'anonymous'; + const context = { + email: req.user?.email || '', + plan: req.user?.plan || 'free', + ip: req.ip, + userAgent: req.get('user-agent') + }; + + try { + // Evaluate all relevant flags for this request + req.features = await evaluateMultipleFlags(userId, context); + next(); + } catch (error) { + console.error('Error loading feature flags:', error); + req.features = {}; // Empty features on error + next(); + } + }; +} + +// Example 5: Graceful degradation with caching +class FliptCache { + constructor(flipt, ttlMs = 60000) { // 1 minute default TTL + this.flipt = flipt; + this.cache = new Map(); + this.ttlMs = ttlMs; + } + + getCacheKey(namespaceKey, flagKey, entityId) { + return `${namespaceKey}:${flagKey}:${entityId}`; + } + + async evaluateBoolean(namespaceKey, flagKey, entityId, context) { + const cacheKey = this.getCacheKey(namespaceKey, flagKey, entityId); + const cached = this.cache.get(cacheKey); + + // Check if cache is valid + if (cached && Date.now() - cached.timestamp < this.ttlMs) { + return cached.value; + } + + // Fetch fresh value + try { + const result = await this.flipt.evaluateBoolean({ + namespaceKey, + flagKey, + entityId, + context + }); + + // Update cache + this.cache.set(cacheKey, { + value: result, + timestamp: Date.now() + }); + + return result; + } catch (error) { + // Return stale cache on error if available + if (cached) { + console.warn('Using stale cache due to error:', error); + return cached.value; + } + throw error; + } + } +} + +// Example usage in an Express app +const express = require('express'); +const app = express(); + +// Add feature flag middleware +app.use(createFeatureFlagMiddleware(flipt)); + +app.get('/dashboard', async (req, res) => { + if (req.features.new_dashboard) { + res.render('dashboard-v2'); + } else { + res.render('dashboard-v1'); + } +}); + +app.get('/api/config', async (req, res) => { + res.json({ + features: req.features, + version: '1.0.0' + }); +}); + +// Main execution +async function main() { + console.log('Flipt Node.js SDK Examples\n'); + + // Example 1: Boolean flag + console.log('Example 1: Boolean Flag'); + await checkBooleanFlag(); + console.log(''); + + // Example 2: Variant flag + console.log('Example 2: Variant Flag'); + const variant = await checkVariantFlag(); + console.log(`Selected checkout: ${variant}\n`); + + // Example 3: Batch evaluation + console.log('Example 3: Batch Evaluation'); + await evaluateMultipleFlags('user-789', { + email: 'user789@example.com', + plan: 'premium' + }); + console.log(''); + + // Example 4: Cached evaluation + console.log('Example 4: Cached Evaluation'); + const cachedClient = new FliptCache(flipt); + const result1 = await cachedClient.evaluateBoolean('default', 'new_dashboard', 'user-123', {}); + console.log('First call (from API):', result1.enabled); + const result2 = await cachedClient.evaluateBoolean('default', 'new_dashboard', 'user-123', {}); + console.log('Second call (from cache):', result2.enabled); +} + +// Run examples if executed directly +if (require.main === module) { + main().catch(console.error); +} + +module.exports = { + flipt, + checkBooleanFlag, + checkVariantFlag, + evaluateMultipleFlags, + createFeatureFlagMiddleware, + FliptCache +}; diff --git a/applications/flipt/examples/sdk/python-example.py b/applications/flipt/examples/sdk/python-example.py new file mode 100644 index 00000000..39d4186f --- /dev/null +++ b/applications/flipt/examples/sdk/python-example.py @@ -0,0 +1,367 @@ +""" +Flipt Python SDK Example + +This example demonstrates how to integrate Flipt feature flags +into a Python application. + +Install: pip install flipt +""" + +import os +from typing import Dict, Optional +from datetime import datetime, timedelta +import requests + + +class FliptClient: + """Simple Flipt HTTP client for Python""" + + def __init__(self, url: str = None, auth_token: str = None): + self.url = url or os.getenv("FLIPT_URL", "http://flipt.flipt.svc.cluster.local:8080") + self.auth_token = auth_token + self.session = requests.Session() + + if self.auth_token: + self.session.headers.update({"Authorization": f"Bearer {self.auth_token}"}) + + def evaluate_boolean( + self, + namespace_key: str, + flag_key: str, + entity_id: str, + context: Optional[Dict[str, str]] = None, + ) -> bool: + """Evaluate a boolean feature flag""" + url = f"{self.url}/api/v1/evaluate/v1/boolean" + + payload = { + "namespaceKey": namespace_key, + "flagKey": flag_key, + "entityId": entity_id, + "context": context or {}, + } + + try: + response = self.session.post(url, json=payload) + response.raise_for_status() + data = response.json() + return data.get("enabled", False) + except requests.exceptions.RequestException as e: + print(f"Error evaluating flag: {e}") + return False # Default to false on error + + def evaluate_variant( + self, + namespace_key: str, + flag_key: str, + entity_id: str, + context: Optional[Dict[str, str]] = None, + ) -> Optional[str]: + """Evaluate a variant feature flag""" + url = f"{self.url}/api/v1/evaluate/v1/variant" + + payload = { + "namespaceKey": namespace_key, + "flagKey": flag_key, + "entityId": entity_id, + "context": context or {}, + } + + try: + response = self.session.post(url, json=payload) + response.raise_for_status() + data = response.json() + return data.get("variantKey") + except requests.exceptions.RequestException as e: + print(f"Error evaluating variant: {e}") + return None + + +# Example 1: Simple boolean flag evaluation +def check_boolean_flag(client: FliptClient, user_id: str = "user-123") -> bool: + """Check a boolean feature flag""" + result = client.evaluate_boolean( + namespace_key="default", + flag_key="new_dashboard", + entity_id=user_id, + context={ + "email": "user@example.com", + "plan": "enterprise", + "region": "us-east-1", + }, + ) + + if result: + print("✓ New dashboard is enabled for this user") + else: + print("✗ New dashboard is disabled for this user") + + return result + + +# Example 2: Variant flag evaluation (A/B testing) +def check_variant_flag(client: FliptClient, user_id: str = "user-456") -> str: + """Check a variant feature flag""" + variant = client.evaluate_variant( + namespace_key="default", + flag_key="checkout_flow", + entity_id=user_id, + context={ + "userId": user_id, + "email": "test@example.com", + "accountAge": "30", + }, + ) + + print(f"User assigned to variant: {variant}") + + variants = { + "control": "original_checkout", + "variant_a": "streamlined_checkout", + "variant_b": "express_checkout", + } + + return variants.get(variant, "original_checkout") + + +# Example 3: Batch evaluation +def evaluate_multiple_flags( + client: FliptClient, user_id: str, context: Dict[str, str] +) -> Dict[str, bool]: + """Evaluate multiple feature flags at once""" + flags = ["new_dashboard", "dark_mode", "beta_features"] + results = {} + + for flag_key in flags: + try: + result = client.evaluate_boolean( + namespace_key="default", + flag_key=flag_key, + entity_id=user_id, + context=context, + ) + results[flag_key] = result + except Exception as e: + print(f"Error evaluating flag {flag_key}: {e}") + results[flag_key] = False + + print(f"Feature flags for user: {results}") + return results + + +# Example 4: Flask middleware +try: + from flask import Flask, request, g + from functools import wraps + + def feature_flags_middleware(client: FliptClient): + """Flask middleware to add feature flags to request context""" + + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + user_id = request.headers.get("X-User-ID", "anonymous") + context = { + "email": request.headers.get("X-User-Email", ""), + "plan": request.headers.get("X-User-Plan", "free"), + "ip": request.remote_addr, + "userAgent": request.headers.get("User-Agent", ""), + } + + try: + g.features = evaluate_multiple_flags(client, user_id, context) + except Exception as e: + print(f"Error loading feature flags: {e}") + g.features = {} + + return f(*args, **kwargs) + + return decorated_function + + return decorator + + # Example Flask app + def create_app(client: FliptClient) -> Flask: + app = Flask(__name__) + + @app.route("/dashboard") + @feature_flags_middleware(client) + def dashboard(): + if g.features.get("new_dashboard", False): + return "Showing new dashboard v2" + else: + return "Showing old dashboard v1" + + @app.route("/api/config") + @feature_flags_middleware(client) + def config(): + return {"features": g.features, "version": "1.0.0"} + + return app + +except ImportError: + print("Flask not installed, skipping Flask examples") + create_app = None + + +# Example 5: Cached client with TTL +class CachedFliptClient: + """Flipt client with local caching""" + + def __init__(self, client: FliptClient, ttl_seconds: int = 60): + self.client = client + self.cache = {} + self.ttl = timedelta(seconds=ttl_seconds) + + def _get_cache_key(self, namespace_key: str, flag_key: str, entity_id: str) -> str: + return f"{namespace_key}:{flag_key}:{entity_id}" + + def evaluate_boolean( + self, + namespace_key: str, + flag_key: str, + entity_id: str, + context: Optional[Dict[str, str]] = None, + ) -> bool: + """Evaluate flag with caching""" + cache_key = self._get_cache_key(namespace_key, flag_key, entity_id) + cached = self.cache.get(cache_key) + + # Check if cache is valid + if cached: + value, timestamp = cached + if datetime.now() - timestamp < self.ttl: + return value + + # Fetch fresh value + try: + result = self.client.evaluate_boolean( + namespace_key, flag_key, entity_id, context + ) + + # Update cache + self.cache[cache_key] = (result, datetime.now()) + + return result + except Exception as e: + # Return stale cache on error if available + if cached: + print(f"Using stale cache due to error: {e}") + return cached[0] + raise + + +# Example 6: Django middleware +try: + from django.utils.deprecation import MiddlewareMixin + + class FeatureFlagMiddleware(MiddlewareMixin): + """Django middleware to add feature flags to request""" + + def __init__(self, get_response): + self.get_response = get_response + self.client = FliptClient() + + def process_request(self, request): + user_id = getattr(request.user, "id", "anonymous") + context = { + "email": getattr(request.user, "email", ""), + "plan": getattr(request.user, "plan", "free"), + "ip": request.META.get("REMOTE_ADDR", ""), + "userAgent": request.META.get("HTTP_USER_AGENT", ""), + } + + try: + request.features = evaluate_multiple_flags( + self.client, str(user_id), context + ) + except Exception as e: + print(f"Error loading feature flags: {e}") + request.features = {} + +except ImportError: + print("Django not installed, skipping Django examples") + + +# Example 7: FastAPI dependency +try: + from fastapi import FastAPI, Depends, Request + from fastapi.responses import JSONResponse + + def get_feature_flags(request: Request): + """FastAPI dependency for feature flags""" + client = FliptClient() + user_id = request.headers.get("X-User-ID", "anonymous") + context = { + "email": request.headers.get("X-User-Email", ""), + "plan": request.headers.get("X-User-Plan", "free"), + "ip": request.client.host, + "userAgent": request.headers.get("User-Agent", ""), + } + + try: + return evaluate_multiple_flags(client, user_id, context) + except Exception as e: + print(f"Error loading feature flags: {e}") + return {} + + # Example FastAPI app + def create_fastapi_app() -> FastAPI: + app = FastAPI() + + @app.get("/dashboard") + def dashboard(features: dict = Depends(get_feature_flags)): + if features.get("new_dashboard", False): + return {"message": "Showing new dashboard v2"} + else: + return {"message": "Showing old dashboard v1"} + + @app.get("/api/config") + def config(features: dict = Depends(get_feature_flags)): + return {"features": features, "version": "1.0.0"} + + return app + +except ImportError: + print("FastAPI not installed, skipping FastAPI examples") + create_fastapi_app = None + + +def main(): + """Run all examples""" + print("Flipt Python SDK Examples\n") + + # Create client + client = FliptClient() + + # Example 1: Boolean flag + print("Example 1: Boolean Flag") + check_boolean_flag(client) + print() + + # Example 2: Variant flag + print("Example 2: Variant Flag") + variant = check_variant_flag(client) + print(f"Selected checkout: {variant}\n") + + # Example 3: Batch evaluation + print("Example 3: Batch Evaluation") + evaluate_multiple_flags( + client, "user-789", {"email": "user789@example.com", "plan": "premium"} + ) + print() + + # Example 4: Cached evaluation + print("Example 4: Cached Evaluation") + cached_client = CachedFliptClient(client, ttl_seconds=60) + result1 = cached_client.evaluate_boolean("default", "new_dashboard", "user-123") + print(f"First call (from API): {result1}") + result2 = cached_client.evaluate_boolean("default", "new_dashboard", "user-123") + print(f"Second call (from cache): {result2}") + print() + + print("Examples completed!") + + +if __name__ == "__main__": + main() diff --git a/applications/flipt/replicated/kots-app.yaml b/applications/flipt/replicated/kots-app.yaml new file mode 100644 index 00000000..40164370 --- /dev/null +++ b/applications/flipt/replicated/kots-app.yaml @@ -0,0 +1,47 @@ +apiVersion: kots.io/v1beta1 +kind: Application +metadata: + name: flipt +spec: + title: Flipt Feature Flags + icon: https://raw.githubusercontent.com/flipt-io/flipt/main/ui/public/logo.svg + statusInformers: + - deployment/flipt + - statefulset/redis + ports: + - serviceName: flipt + servicePort: 8080 + localPort: 8080 + applicationUrl: "http://flipt.{{repl Namespace}}.svc.cluster.local:8080" + graphs: + - title: Flipt Pods + query: 'count(kube_pod_status_phase{namespace="{{repl Namespace}}", pod=~"flipt-.*", phase="Running"})' + legend: Running Pods + queries: + - query: 'count(kube_pod_status_phase{namespace="{{repl Namespace}}", pod=~"flipt-.*", phase="Running"})' + legend: Running + - query: 'count(kube_pod_status_phase{namespace="{{repl Namespace}}", pod=~"flipt-.*", phase="Pending"})' + legend: Pending + - title: Database Status + query: 'count(kube_pod_status_phase{namespace="{{repl Namespace}}", pod=~"{{repl ConfigOption "release_name"}}-cluster-.*", phase="Running"})' + legend: PostgreSQL Pods + when: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' + - title: Redis Status + query: 'count(kube_pod_status_phase{namespace="{{repl Namespace}}", pod=~".*-redis-.*", phase="Running"})' + legend: Redis Pods + when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + releaseNotes: | + ## Flipt v1.61.0 + + This release includes: + - Latest Flipt v1.61.0 with enhanced performance + - PostgreSQL 16 support via CloudnativePG + - Redis distributed caching for multi-instance deployments + - Horizontal pod autoscaling support + - Comprehensive monitoring and metrics + + For more details, visit: https://github.com/flipt-io/flipt/releases + additionalImages: + - docker.flipt.io/flipt/flipt:v1.61.0 + - ghcr.io/cloudnative-pg/postgresql:16 + - docker.io/bitnami/redis:7.2.4 diff --git a/applications/flipt/replicated/kots-config.yaml b/applications/flipt/replicated/kots-config.yaml new file mode 100644 index 00000000..809da165 --- /dev/null +++ b/applications/flipt/replicated/kots-config.yaml @@ -0,0 +1,363 @@ +apiVersion: kots.io/v1beta1 +kind: Config +metadata: + name: flipt-config +spec: + groups: + - name: basic + title: Basic Configuration + description: Essential configuration for Flipt deployment + items: + - name: release_name + title: Release Name + type: text + default: flipt + required: true + help_text: Name for this Flipt deployment (used as Helm release name) + + - name: namespace + title: Namespace + type: text + default: flipt + required: true + help_text: Kubernetes namespace where Flipt will be deployed + + - name: replica_count + title: Number of Flipt Replicas + type: text + default: "2" + required: true + help_text: Number of Flipt pod replicas (2+ recommended for HA with Redis) + + - name: ingress + title: Ingress Configuration + description: Configure how Flipt is accessed from outside the cluster + items: + - name: ingress_enabled + title: Enable Ingress + type: bool + default: "1" + help_text: Enable ingress for external access to Flipt + + - name: ingress_hostname + title: Hostname + type: text + default: flipt.example.com + required: false + when: 'repl{{ ConfigOptionEquals "ingress_enabled" "1" }}' + help_text: Hostname for accessing Flipt UI + + - name: ingress_class + title: Ingress Class + type: select_one + default: nginx + items: + - name: nginx + title: NGINX + - name: traefik + title: Traefik + - name: custom + title: Custom + when: 'repl{{ ConfigOptionEquals "ingress_enabled" "1" }}' + help_text: Ingress controller class to use + + - name: ingress_class_custom + title: Custom Ingress Class + type: text + default: "" + when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "ingress_class" "custom") }}' + help_text: Custom ingress class name + + - name: tls_enabled + title: Enable TLS + type: bool + default: "1" + when: 'repl{{ ConfigOptionEquals "ingress_enabled" "1" }}' + help_text: Enable TLS/HTTPS for ingress + + - name: tls_cert_source + title: TLS Certificate Source + type: select_one + default: self_signed + items: + - name: self_signed + title: Self-Signed (Auto-Generated) + - name: cert_manager + title: Cert-Manager + - name: custom + title: Upload Custom Certificate + when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' + help_text: Source of TLS certificate + + - name: tls_cert_manager_issuer + title: Cert-Manager Issuer + type: text + default: letsencrypt-prod + when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_cert_source" "cert_manager") }}' + help_text: Name of the cert-manager ClusterIssuer + + - name: tls_cert + title: TLS Certificate + type: file + when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom") }}' + help_text: Upload your TLS certificate file (PEM format) + + - name: tls_key + title: TLS Private Key + type: file + when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom") }}' + help_text: Upload your TLS private key file (PEM format) + + - name: database + title: Database Configuration + description: PostgreSQL database settings for storing feature flags + items: + - name: postgres_type + title: PostgreSQL Type + type: select_one + default: embedded + items: + - name: embedded + title: Embedded PostgreSQL (CloudnativePG) + - name: external + title: External PostgreSQL Database + required: true + help_text: Use embedded PostgreSQL or connect to external database + + - name: postgres_instances + title: Number of PostgreSQL Instances + type: select_one + default: "1" + items: + - name: "1" + title: "1 (Development)" + - name: "3" + title: "3 (High Availability)" + when: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' + help_text: Number of PostgreSQL cluster instances (3 recommended for production) + + - name: postgres_storage_size + title: PostgreSQL Storage Size + type: text + default: 10Gi + when: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' + required: true + help_text: Storage size for PostgreSQL data (e.g., 10Gi, 50Gi, 100Gi) + + - name: postgres_storage_class + title: PostgreSQL Storage Class + type: text + default: "" + when: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' + help_text: Storage class for PostgreSQL PVCs (leave empty for default) + + - name: postgres_resources_cpu + title: PostgreSQL CPU Limit + type: text + default: 1000m + when: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' + help_text: CPU limit for PostgreSQL pods (e.g., 500m, 1000m, 2000m) + + - name: postgres_resources_memory + title: PostgreSQL Memory Limit + type: text + default: 1Gi + when: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' + help_text: Memory limit for PostgreSQL pods (e.g., 512Mi, 1Gi, 2Gi) + + - name: external_postgres_host + title: External PostgreSQL Host + type: text + default: postgresql.example.com + when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' + required: true + help_text: Hostname or IP address of external PostgreSQL server + + - name: external_postgres_port + title: External PostgreSQL Port + type: text + default: "5432" + when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' + required: true + help_text: Port number of external PostgreSQL server + + - name: external_postgres_database + title: Database Name + type: text + default: flipt + when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' + required: true + help_text: Name of the database to use + + - name: external_postgres_username + title: Database Username + type: text + default: flipt + when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' + required: true + help_text: Username for database authentication + + - name: external_postgres_password + title: Database Password + type: password + when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' + required: true + help_text: Password for database authentication + + - name: external_postgres_sslmode + title: SSL Mode + type: select_one + default: require + items: + - name: disable + title: Disable + - name: require + title: Require + - name: verify-ca + title: Verify CA + - name: verify-full + title: Verify Full + when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' + help_text: SSL mode for PostgreSQL connection + + - name: redis + title: Redis Configuration + description: Redis cache settings for high-performance multi-instance deployments + items: + - name: redis_enabled + title: Enable Redis Cache + type: bool + default: "1" + help_text: Enable Redis for distributed caching (required for multiple Flipt replicas) + recommended: true + + - name: redis_architecture + title: Redis Architecture + type: select_one + default: standalone + items: + - name: standalone + title: Standalone (Single Instance) + - name: replication + title: Primary-Replica (High Availability) + when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + help_text: Redis deployment architecture + + - name: redis_storage_size + title: Redis Storage Size + type: text + default: 5Gi + when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + required: true + help_text: Storage size for Redis persistence (e.g., 5Gi, 10Gi) + + - name: redis_storage_class + title: Redis Storage Class + type: text + default: "" + when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + help_text: Storage class for Redis PVCs (leave empty for default) + + - name: redis_password + title: Redis Password + type: password + when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + help_text: Password for Redis authentication (auto-generated if empty) + + - name: redis_replica_count + title: Number of Redis Replicas + type: text + default: "1" + when: '{{repl and (ConfigOptionEquals "redis_enabled" "1") (ConfigOptionEquals "redis_architecture" "replication") }}' + help_text: Number of Redis replica instances + + - name: resources + title: Resource Configuration + description: Configure CPU and memory resources for Flipt + items: + - name: flipt_cpu_request + title: Flipt CPU Request + type: text + default: 100m + help_text: CPU request for Flipt pods (e.g., 100m, 250m, 500m) + + - name: flipt_cpu_limit + title: Flipt CPU Limit + type: text + default: 500m + help_text: CPU limit for Flipt pods (e.g., 500m, 1000m, 2000m) + + - name: flipt_memory_request + title: Flipt Memory Request + type: text + default: 128Mi + help_text: Memory request for Flipt pods (e.g., 128Mi, 256Mi, 512Mi) + + - name: flipt_memory_limit + title: Flipt Memory Limit + type: text + default: 512Mi + help_text: Memory limit for Flipt pods (e.g., 512Mi, 1Gi, 2Gi) + + - name: advanced + title: Advanced Configuration + description: Advanced settings for production deployments + items: + - name: enable_autoscaling + title: Enable Horizontal Pod Autoscaling + type: bool + default: "0" + help_text: Enable HPA to automatically scale Flipt pods based on CPU/memory usage + + - name: hpa_min_replicas + title: HPA Minimum Replicas + type: text + default: "2" + when: 'repl{{ ConfigOptionEquals "enable_autoscaling" "1" }}' + help_text: Minimum number of replicas for autoscaling + + - name: hpa_max_replicas + title: HPA Maximum Replicas + type: text + default: "10" + when: 'repl{{ ConfigOptionEquals "enable_autoscaling" "1" }}' + help_text: Maximum number of replicas for autoscaling + + - name: hpa_cpu_target + title: HPA CPU Target Percentage + type: text + default: "80" + when: 'repl{{ ConfigOptionEquals "enable_autoscaling" "1" }}' + help_text: Target CPU utilization percentage for autoscaling + + - name: enable_metrics + title: Enable Prometheus Metrics + type: bool + default: "0" + help_text: Enable ServiceMonitor for Prometheus metrics collection + + - name: log_level + title: Log Level + type: select_one + default: info + items: + - name: debug + title: Debug + - name: info + title: Info + - name: warn + title: Warning + - name: error + title: Error + help_text: Logging verbosity level + + - name: log_encoding + title: Log Encoding + type: select_one + default: json + items: + - name: console + title: Console + - name: json + title: JSON + help_text: Log output format diff --git a/applications/flipt/replicated/kots-helm-chart.yaml b/applications/flipt/replicated/kots-helm-chart.yaml new file mode 100644 index 00000000..2edca9dc --- /dev/null +++ b/applications/flipt/replicated/kots-helm-chart.yaml @@ -0,0 +1,212 @@ +apiVersion: kots.io/v1beta2 +kind: HelmChart +metadata: + name: flipt +spec: + chart: + name: flipt + chartVersion: 1.0.0 + releaseName: 'repl{{ ConfigOption "release_name" }}' + + exclude: "" + + helmVersion: v3 + + useHelmInstall: true + + namespace: 'repl{{ ConfigOption "namespace" }}' + + values: + global: + imageRegistry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "" }}' + + flipt: + enabled: true + + image: + registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "docker.flipt.io" }}' + repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "flipt" }}/flipt' + pullPolicy: IfNotPresent + + replicaCount: 'repl{{ ConfigOption "replica_count" }}' + + resources: + requests: + cpu: 'repl{{ ConfigOption "flipt_cpu_request" }}' + memory: 'repl{{ ConfigOption "flipt_memory_request" }}' + limits: + cpu: 'repl{{ ConfigOption "flipt_cpu_limit" }}' + memory: 'repl{{ ConfigOption "flipt_memory_limit" }}' + + config: + log: + level: 'repl{{ ConfigOption "log_level" }}' + encoding: 'repl{{ ConfigOption "log_encoding" }}' + + server: + protocol: http + host: 0.0.0.0 + httpPort: 8080 + grpcPort: 9000 + + db: + maxIdleConn: 10 + maxOpenConn: 50 + connMaxLifetime: 1h + + cache: + enabled: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + backend: 'repl{{ ConfigOptionEquals "redis_enabled" "1" | ternary "redis" "memory" }}' + ttl: 5m + redis: + mode: single + prefix: "flipt" + + cors: + enabled: true + allowedOrigins: + - "*" + + service: + type: ClusterIP + httpPort: 8080 + grpcPort: 9000 + + autoscaling: + enabled: 'repl{{ ConfigOptionEquals "enable_autoscaling" "1" }}' + minReplicas: 'repl{{ ConfigOption "hpa_min_replicas" }}' + maxReplicas: 'repl{{ ConfigOption "hpa_max_replicas" }}' + targetCPUUtilizationPercentage: 'repl{{ ConfigOption "hpa_cpu_target" }}' + + podDisruptionBudget: + enabled: true + minAvailable: 1 + + serviceMonitor: + enabled: 'repl{{ ConfigOptionEquals "enable_metrics" "1" }}' + + postgresql: + type: 'repl{{ ConfigOption "postgres_type" }}' + + database: flipt + username: flipt + password: 'repl{{ RandomString 32 }}' + + embedded: + enabled: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' + cluster: + instances: 'repl{{ ConfigOption "postgres_instances" | ParseInt }}' + imageName: 'repl{{ HasLocalRegistry | ternary (printf "%s/%s/postgresql:16" LocalRegistryHost LocalRegistryNamespace) "ghcr.io/cloudnative-pg/postgresql:16" }}' + storage: + size: 'repl{{ ConfigOption "postgres_storage_size" }}' + storageClass: 'repl{{ ConfigOption "postgres_storage_class" }}' + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 'repl{{ ConfigOption "postgres_resources_cpu" }}' + memory: 'repl{{ ConfigOption "postgres_resources_memory" }}' + backup: + enabled: false + monitoring: + enabled: false + + redis: + enabled: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + + architecture: 'repl{{ ConfigOption "redis_architecture" }}' + + auth: + enabled: true + password: 'repl{{ if ConfigOption "redis_password" }}repl{{ ConfigOption "redis_password" }}repl{{ else }}repl{{ RandomString 32 }}repl{{ end }}' + + image: + registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "docker.io" }}' + repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "bitnami" }}/redis' + + master: + persistence: + enabled: true + size: 'repl{{ ConfigOption "redis_storage_size" }}' + storageClass: 'repl{{ ConfigOption "redis_storage_class" }}' + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + + replica: + replicaCount: 'repl{{ ConfigOption "redis_replica_count" | ParseInt }}' + persistence: + enabled: true + size: 'repl{{ ConfigOption "redis_storage_size" }}' + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + + metrics: + enabled: 'repl{{ ConfigOptionEquals "enable_metrics" "1" }}' + + replicated: + enabled: true + integration: + enabled: true + + optionalValues: + - when: 'repl{{ ConfigOptionEquals "ingress_enabled" "1" }}' + recursiveMerge: false + values: + flipt: + ingress: + enabled: true + className: 'repl{{ if ConfigOptionEquals "ingress_class" "custom" }}repl{{ ConfigOption "ingress_class_custom" }}repl{{ else }}repl{{ ConfigOption "ingress_class" }}repl{{ end }}' + hosts: + - host: 'repl{{ ConfigOption "ingress_hostname" }}' + paths: + - path: / + pathType: Prefix + + - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") (ConfigOptionEquals "tls_cert_source" "cert_manager") }}' + recursiveMerge: false + values: + flipt: + ingress: + annotations: + cert-manager.io/cluster-issuer: 'repl{{ ConfigOption "tls_cert_manager_issuer" }}' + tls: + - secretName: flipt-tls + hosts: + - 'repl{{ ConfigOption "ingress_hostname" }}' + + - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom") }}' + recursiveMerge: false + values: + flipt: + ingress: + tls: + - secretName: flipt-custom-tls + hosts: + - 'repl{{ ConfigOption "ingress_hostname" }}' + + - when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' + recursiveMerge: false + values: + postgresql: + external: + enabled: true + host: 'repl{{ ConfigOption "external_postgres_host" }}' + port: 'repl{{ ConfigOption "external_postgres_port" | ParseInt }}' + database: 'repl{{ ConfigOption "external_postgres_database" }}' + username: 'repl{{ ConfigOption "external_postgres_username" }}' + password: 'repl{{ ConfigOption "external_postgres_password" }}' + sslMode: 'repl{{ ConfigOption "external_postgres_sslmode" }}' + + builder: + {} diff --git a/applications/flipt/replicated/kots-preflight.yaml b/applications/flipt/replicated/kots-preflight.yaml new file mode 100644 index 00000000..cd897916 --- /dev/null +++ b/applications/flipt/replicated/kots-preflight.yaml @@ -0,0 +1,181 @@ +apiVersion: troubleshoot.sh/v1beta2 +kind: Preflight +metadata: + name: flipt-preflight-checks +spec: + analyzers: + # Kubernetes version check + - clusterVersion: + outcomes: + - fail: + when: "< 1.24.0" + message: Flipt requires Kubernetes 1.24.0 or later + uri: https://kubernetes.io/releases/ + - warn: + when: "< 1.27.0" + message: Kubernetes 1.27.0 or later is recommended for best performance + - pass: + when: ">= 1.27.0" + message: Kubernetes version is supported + + # Node resource checks + - nodeResources: + checkName: Minimum CPU cores available + outcomes: + - fail: + when: "sum(cpuCapacity) < 2" + message: The cluster must have at least 2 CPU cores available + - warn: + when: "sum(cpuCapacity) < 4" + message: At least 4 CPU cores are recommended for production + - pass: + message: Sufficient CPU resources available + + - nodeResources: + checkName: Minimum memory available + outcomes: + - fail: + when: "sum(memoryCapacity) < 4Gi" + message: The cluster must have at least 4GB of memory available + - warn: + when: "sum(memoryCapacity) < 8Gi" + message: At least 8GB of memory is recommended for production + - pass: + message: Sufficient memory available + + # Storage checks + - storageClass: + checkName: Default storage class + storageClassName: "" + outcomes: + - fail: + message: No default storage class found. A default storage class is required. + uri: https://kubernetes.io/docs/concepts/storage/storage-classes/ + - pass: + message: Default storage class is available + + - storageClass: + checkName: Storage class with RWO support + storageClassName: "" + outcomes: + - fail: + message: The storage class does not support ReadWriteOnce access mode + - pass: + message: Storage class supports required access modes + + # Database specific checks (for embedded PostgreSQL) + - nodeResources: + checkName: PostgreSQL resource requirements + outcomes: + - fail: + when: "sum(memoryCapacity) < 2Gi" + message: | + At least 2GB of memory is required for embedded PostgreSQL. + Consider using an external database if cluster resources are limited. + - warn: + when: "sum(cpuCapacity) < 2" + message: At least 2 CPU cores recommended for embedded PostgreSQL + - pass: + message: Sufficient resources for embedded PostgreSQL + + # Redis specific checks + - nodeResources: + checkName: Redis resource requirements + outcomes: + - warn: + when: "sum(memoryCapacity) < 1Gi" + message: | + At least 1GB of memory recommended for Redis cache. + Consider using in-memory caching if Redis is disabled. + - pass: + message: Sufficient resources for Redis cache + + # Network checks + - customResourceDefinition: + checkName: Check for Ingress Controller + customResourceDefinitionName: ingressclasses.networking.k8s.io + outcomes: + - fail: + message: | + No ingress controller detected in the cluster. + An ingress controller (like NGINX or Traefik) is required if ingress is enabled. + - pass: + message: Ingress controller CRDs are available + + # CloudnativePG operator check (for embedded database) + - customResourceDefinition: + checkName: CloudnativePG Operator + customResourceDefinitionName: clusters.postgresql.cnpg.io + outcomes: + - warn: + message: | + CloudnativePG operator is not installed. + The operator will be installed as part of this deployment. + If you prefer to use an external PostgreSQL database, configure it in the admin console. + - pass: + message: CloudnativePG operator is available + + # Image pull checks + - imagePullSecret: + checkName: Registry access + registryName: docker.flipt.io + outcomes: + - fail: + message: | + Cannot pull images from docker.flipt.io. + Check network connectivity or configure image pull secrets. + - pass: + message: Can pull images from container registry + + # Distribution-specific checks + - distribution: + outcomes: + - fail: + when: "== docker-desktop" + message: | + Docker Desktop is not recommended for production deployments. + Use a production-grade Kubernetes distribution. + - warn: + when: "== kind" + message: | + kind is detected. This is suitable for development only. + - warn: + when: "== minikube" + message: | + Minikube is detected. This is suitable for development only. + - pass: + message: Kubernetes distribution is suitable for Flipt deployment + + # Cluster resource capacity + - deploymentStatus: + checkName: Cluster is healthy + namespace: kube-system + name: coredns + outcomes: + - fail: + when: "absent" + message: CoreDNS is not running. The cluster may not be healthy. + - fail: + when: "!= Healthy" + message: CoreDNS deployment is not healthy + - pass: + message: Cluster DNS is healthy + + collectors: + - clusterInfo: {} + - clusterResources: {} + - logs: + selector: + - app=flipt + namespace: '{{repl ConfigOption "namespace"}}' + limits: + maxAge: 720h + maxLines: 10000 + - exec: + name: kubectl-version + selector: + - app=flipt + namespace: '{{repl ConfigOption "namespace"}}' + command: ["kubectl"] + args: ["version", "--short"] + timeout: 30s diff --git a/applications/flipt/replicated/kots-support-bundle.yaml b/applications/flipt/replicated/kots-support-bundle.yaml new file mode 100644 index 00000000..4289cb4c --- /dev/null +++ b/applications/flipt/replicated/kots-support-bundle.yaml @@ -0,0 +1,298 @@ +apiVersion: troubleshoot.sh/v1beta2 +kind: SupportBundle +metadata: + name: flipt-support-bundle +spec: + collectors: + # Cluster information + - clusterInfo: {} + - clusterResources: {} + + # Flipt application logs + - logs: + selector: + - app.kubernetes.io/name=flipt + namespace: '{{repl ConfigOption "namespace"}}' + limits: + maxAge: 720h + maxLines: 10000 + name: flipt/logs + + # PostgreSQL logs (embedded) + - logs: + selector: + - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster + namespace: '{{repl ConfigOption "namespace"}}' + limits: + maxAge: 168h + maxLines: 10000 + name: postgresql/logs + + # Redis logs + - logs: + selector: + - app.kubernetes.io/name=redis + namespace: '{{repl ConfigOption "namespace"}}' + limits: + maxAge: 168h + maxLines: 10000 + name: redis/logs + + # Pod status and events + - pods: + namespace: '{{repl ConfigOption "namespace"}}' + selector: + - app.kubernetes.io/name=flipt + + - pods: + namespace: '{{repl ConfigOption "namespace"}}' + selector: + - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster + + - pods: + namespace: '{{repl ConfigOption "namespace"}}' + selector: + - app.kubernetes.io/name=redis + + # Service and endpoint information + - services: + namespace: '{{repl ConfigOption "namespace"}}' + + - endpoints: + namespace: '{{repl ConfigOption "namespace"}}' + + # ConfigMaps and Secrets (redacted) + - configMaps: + namespace: '{{repl ConfigOption "namespace"}}' + + - secrets: + namespace: '{{repl ConfigOption "namespace"}}' + includeKeys: + - false + + # PVC and storage information + - pvcs: + namespace: '{{repl ConfigOption "namespace"}}' + + # Ingress configuration + - ingress: + namespace: '{{repl ConfigOption "namespace"}}' + + # PostgreSQL specific diagnostics + - exec: + name: postgresql-version + selector: + - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster + namespace: '{{repl ConfigOption "namespace"}}' + command: ["psql"] + args: ["-c", "SELECT version();"] + timeout: 30s + + - exec: + name: postgresql-connections + selector: + - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster + namespace: '{{repl ConfigOption "namespace"}}' + command: ["psql"] + args: ["-c", "SELECT count(*) as connections FROM pg_stat_activity;"] + timeout: 30s + + - exec: + name: postgresql-database-size + selector: + - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster + namespace: '{{repl ConfigOption "namespace"}}' + command: ["psql"] + args: ["-c", "SELECT pg_size_pretty(pg_database_size('flipt')) as database_size;"] + timeout: 30s + + # Redis diagnostics + - exec: + name: redis-info + selector: + - app.kubernetes.io/name=redis + - app.kubernetes.io/component=master + namespace: '{{repl ConfigOption "namespace"}}' + command: ["redis-cli"] + args: ["INFO"] + timeout: 30s + + - exec: + name: redis-memory + selector: + - app.kubernetes.io/name=redis + - app.kubernetes.io/component=master + namespace: '{{repl ConfigOption "namespace"}}' + command: ["redis-cli"] + args: ["INFO", "memory"] + timeout: 30s + + # Flipt API health check + - http: + name: flipt-health + get: + url: http://{{ ConfigOption "release_name" }}-flipt.{{ ConfigOption "namespace" }}.svc.cluster.local:8080/health + timeout: 30s + + # Helm release information + - exec: + name: helm-values + selector: + - app.kubernetes.io/name=flipt + namespace: '{{repl ConfigOption "namespace"}}' + command: ["sh"] + args: ["-c", "helm get values {{ ConfigOption 'release_name' }} -n {{ ConfigOption 'namespace' }}"] + timeout: 30s + + - exec: + name: helm-manifest + selector: + - app.kubernetes.io/name=flipt + namespace: '{{repl ConfigOption "namespace"}}' + command: ["sh"] + args: ["-c", "helm get manifest {{ ConfigOption 'release_name' }} -n {{ ConfigOption 'namespace' }}"] + timeout: 30s + + # Node information + - nodeMetrics: {} + + # Storage class information + - storageClasses: {} + + # Network diagnostics + - exec: + name: flipt-to-postgres-connectivity + selector: + - app.kubernetes.io/name=flipt + namespace: '{{repl ConfigOption "namespace"}}' + command: ["sh"] + args: ["-c", "nc -zv {{ ConfigOption 'release_name' }}-cluster-rw 5432 || echo 'Cannot connect to PostgreSQL'"] + timeout: 10s + + - exec: + name: flipt-to-redis-connectivity + selector: + - app.kubernetes.io/name=flipt + namespace: '{{repl ConfigOption "namespace"}}' + command: ["sh"] + args: ["-c", "nc -zv {{ ConfigOption 'release_name' }}-redis-master 6379 || echo 'Cannot connect to Redis'"] + timeout: 10s + + analyzers: + # Pod status analysis + - deploymentStatus: + name: flipt-deployment + namespace: '{{repl ConfigOption "namespace"}}' + outcomes: + - fail: + when: "< 1" + message: Flipt deployment has no ready replicas + - warn: + when: "< {{ ConfigOption 'replica_count' }}" + message: Flipt deployment has fewer replicas than configured + - pass: + message: Flipt deployment is healthy + + # PostgreSQL cluster health + - clusterPodStatuses: + name: postgresql-cluster-health + namespace: '{{repl ConfigOption "namespace"}}' + outcomes: + - fail: + when: "!= Healthy" + message: PostgreSQL cluster is not healthy + - pass: + message: PostgreSQL cluster is healthy + + # Redis health + - statefulsetStatus: + name: redis-health + namespace: '{{repl ConfigOption "namespace"}}' + outcomes: + - fail: + when: "< 1" + message: Redis has no ready replicas + - pass: + message: Redis is healthy + + # Storage analysis + - textAnalyze: + checkName: Persistent Volume Claims + fileName: /cluster-resources/persistent-volume-claims.json + regexGroups: '"phase": "(\w+)"' + outcomes: + - fail: + when: "Pending" + message: One or more PVCs are in Pending state + - fail: + when: "Failed" + message: One or more PVCs have failed + - pass: + when: "Bound" + message: All PVCs are bound + + # Node resources + - nodeResources: + checkName: Node CPU capacity + outcomes: + - warn: + when: "sum(cpuCapacity) < 4" + message: Less than 4 CPU cores available. Consider scaling cluster for production workloads. + - pass: + message: Sufficient CPU resources + + - nodeResources: + checkName: Node memory capacity + outcomes: + - warn: + when: "sum(memoryCapacity) < 8Gi" + message: Less than 8GB memory available. Consider scaling cluster for production workloads. + - pass: + message: Sufficient memory resources + + # Log analysis for common errors + - textAnalyze: + checkName: Check for database connection errors + fileName: flipt/logs/*.log + regex: 'database connection|connection refused|could not connect' + outcomes: + - fail: + when: "true" + message: Database connection errors detected in logs + - pass: + message: No database connection errors found + + - textAnalyze: + checkName: Check for Redis connection errors + fileName: flipt/logs/*.log + regex: 'redis.*error|ECONNREFUSED.*redis|redis.*timeout' + outcomes: + - warn: + when: "true" + message: Redis connection errors detected. Check Redis connectivity. + - pass: + message: No Redis connection errors found + + - textAnalyze: + checkName: Check for OOM errors + fileName: flipt/logs/*.log + regex: 'out of memory|OOMKilled|memory limit exceeded' + outcomes: + - fail: + when: "true" + message: Out of memory errors detected. Consider increasing memory limits. + - pass: + message: No OOM errors detected + + # HTTP health check analysis + - textAnalyze: + checkName: Flipt API health + fileName: http-response/flipt-health.json + regex: '"status": "ok"' + outcomes: + - fail: + when: "false" + message: Flipt API health check failed + - pass: + when: "true" + message: Flipt API is healthy diff --git a/applications/flipt/scripts/install-cnpg-operator.sh b/applications/flipt/scripts/install-cnpg-operator.sh new file mode 100755 index 00000000..0435350b --- /dev/null +++ b/applications/flipt/scripts/install-cnpg-operator.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +echo "Installing CloudNativePG operator..." + +# Add CNPG Helm repository +helm repo add cnpg https://cloudnative-pg.github.io/charts +helm repo update + +# Install the operator in its own namespace +helm upgrade --install cnpg \ + --namespace cnpg-system \ + --create-namespace \ + cnpg/cloudnative-pg + +echo "Waiting for operator to be ready..." +kubectl wait --for=condition=available --timeout=300s \ + deployment/cnpg-cloudnative-pg \ + -n cnpg-system + +echo "✓ CloudNativePG operator installed successfully!" +echo "" +echo "You can now install Flipt:" +echo " make install" diff --git a/applications/flipt/scripts/install.sh b/applications/flipt/scripts/install.sh new file mode 100755 index 00000000..9e216ca1 --- /dev/null +++ b/applications/flipt/scripts/install.sh @@ -0,0 +1,155 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +NAMESPACE="${NAMESPACE:-flipt}" +RELEASE_NAME="${RELEASE_NAME:-flipt}" + +# Check for Replicated license +if [ -f ".replicated/license.env" ]; then + echo -e "${BLUE}Loading Replicated license from .replicated/license.env${NC}" + source .replicated/license.env +fi + +if [ -z "$REPLICATED_LICENSE_ID" ]; then + echo -e "${RED}=================================================${NC}" + echo -e "${RED} Replicated License Required${NC}" + echo -e "${RED}=================================================${NC}" + echo "" + echo -e "${YELLOW}This application requires a Replicated development license.${NC}" + echo "" + echo "To set up a development license:" + echo "" + echo " 1. Run the license setup script:" + echo -e " ${YELLOW}./scripts/setup-dev-license.sh${NC}" + echo "" + echo " 2. Load the license:" + echo -e " ${YELLOW}source .replicated/license.env${NC}" + echo "" + echo " 3. Re-run this installation:" + echo -e " ${YELLOW}./scripts/install.sh${NC}" + echo "" + echo "Or set it manually:" + echo -e " ${YELLOW}export REPLICATED_LICENSE_ID=your-license-id${NC}" + echo "" + exit 1 +fi + +echo -e "${GREEN}✓ Replicated license configured: $REPLICATED_LICENSE_ID${NC}" +echo "" + +echo -e "${BLUE}=================================================${NC}" +echo -e "${BLUE} Flipt Feature Flags Installation${NC}" +echo -e "${BLUE}=================================================${NC}" +echo "" + +# Step 1: Check prerequisites +echo -e "${YELLOW}[1/7] Checking prerequisites...${NC}" +command -v kubectl >/dev/null 2>&1 || { echo -e "${RED}✗ kubectl is required but not installed${NC}"; exit 1; } +command -v helm >/dev/null 2>&1 || { echo -e "${RED}✗ helm is required but not installed${NC}"; exit 1; } +echo -e "${GREEN}✓ Prerequisites satisfied${NC}" +echo "" + +# Step 2: Check if operator is installed +echo -e "${YELLOW}[2/7] Checking CloudNativePG operator...${NC}" +if kubectl get deployment cnpg-cloudnative-pg -n cnpg-system >/dev/null 2>&1; then + echo -e "${GREEN}✓ CloudNativePG operator already installed${NC}" +else + echo -e "${YELLOW}⚠ CloudNativePG operator not found. Installing...${NC}" + + # Add CNPG repo + helm repo add cnpg https://cloudnative-pg.github.io/charts >/dev/null 2>&1 || true + helm repo update >/dev/null 2>&1 + + # Install operator + helm upgrade --install cnpg \ + --namespace cnpg-system \ + --create-namespace \ + cnpg/cloudnative-pg + + # Wait for operator to be ready + echo -e "${YELLOW} Waiting for operator to be ready...${NC}" + kubectl wait --for=condition=available --timeout=300s \ + deployment/cnpg-cloudnative-pg \ + -n cnpg-system 2>/dev/null || true + + echo -e "${GREEN}✓ Operator installed successfully${NC}" +fi +echo "" + +# Step 3: Add Helm repositories +echo -e "${YELLOW}[3/7] Adding Helm repositories...${NC}" +helm repo add flipt https://helm.flipt.io >/dev/null 2>&1 || true +helm repo add bitnami https://charts.bitnami.com/bitnami >/dev/null 2>&1 || true +helm repo add replicated https://charts.replicated.com >/dev/null 2>&1 || true +helm repo update >/dev/null 2>&1 +echo -e "${GREEN}✓ Repositories added${NC}" +echo "" + +# Step 4: Clean and update dependencies +echo -e "${YELLOW}[4/7] Updating chart dependencies...${NC}" +cd chart + +# Remove any cached cloudnative-pg dependency +rm -f charts/cloudnative-pg-*.tgz 2>/dev/null || true +rm -f Chart.lock 2>/dev/null || true + +# Update dependencies +helm dependency update +cd .. +echo -e "${GREEN}✓ Dependencies updated${NC}" +echo "" + +# Step 5: Configure Replicated SDK +echo -e "${YELLOW}[5/7] Configuring Replicated SDK...${NC}" +kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f - +kubectl create secret generic replicated-license \ + --from-literal=license="$REPLICATED_LICENSE_ID" \ + --namespace "${NAMESPACE}" \ + --dry-run=client -o yaml | kubectl apply -f - +echo -e "${GREEN}✓ Replicated SDK configured${NC}" +echo "" + +# Step 6: Install Flipt +echo -e "${YELLOW}[6/7] Installing Flipt...${NC}" +helm upgrade --install "${RELEASE_NAME}" ./chart \ + --namespace "${NAMESPACE}" \ + --create-namespace \ + --wait \ + --timeout 10m \ + "$@" + +echo -e "${GREEN}✓ Flipt installed successfully${NC}" +echo "" + +# Step 7: Show status +echo -e "${YELLOW}[7/7] Checking deployment status...${NC}" +kubectl get pods -n "${NAMESPACE}" +echo "" + +# Show next steps +echo -e "${GREEN}=================================================${NC}" +echo -e "${GREEN} Installation Complete!${NC}" +echo -e "${GREEN}=================================================${NC}" +echo "" +echo -e "${BLUE}Access Flipt:${NC}" +echo "" +echo -e " 1. Port-forward to the service:" +echo -e " ${YELLOW}kubectl port-forward -n ${NAMESPACE} svc/${RELEASE_NAME}-flipt 8080:8080${NC}" +echo "" +echo -e " 2. Open your browser:" +echo -e " ${YELLOW}http://localhost:8080${NC}" +echo "" +echo -e "${BLUE}Useful commands:${NC}" +echo "" +echo -e " View pods: ${YELLOW}kubectl get pods -n ${NAMESPACE}${NC}" +echo -e " View logs: ${YELLOW}kubectl logs -l app.kubernetes.io/name=flipt -n ${NAMESPACE} -f${NC}" +echo -e " View database: ${YELLOW}kubectl get cluster -n ${NAMESPACE}${NC}" +echo -e " Uninstall: ${YELLOW}helm uninstall ${RELEASE_NAME} -n ${NAMESPACE}${NC}" +echo "" diff --git a/applications/flipt/scripts/setup-dev-license.sh b/applications/flipt/scripts/setup-dev-license.sh new file mode 100755 index 00000000..e2a0e8da --- /dev/null +++ b/applications/flipt/scripts/setup-dev-license.sh @@ -0,0 +1,145 @@ +#!/bin/bash +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}=================================================${NC}" +echo -e "${BLUE} Replicated Development License Setup${NC}" +echo -e "${BLUE}=================================================${NC}" +echo "" + +# Check prerequisites +echo -e "${YELLOW}[1/4] Checking prerequisites...${NC}" +command -v replicated >/dev/null 2>&1 || { + echo -e "${RED}✗ Replicated CLI is required${NC}" + echo "" + echo "Install it with:" + echo " brew install replicatedhq/replicated/cli" + echo " # or" + echo " curl -s https://api.github.com/repos/replicatedhq/replicated/releases/latest | grep \"browser_download_url.*$(uname -s)_$(uname -m)\" | cut -d '\"' -f 4 | xargs curl -L -o replicated && chmod +x replicated && sudo mv replicated /usr/local/bin/" + exit 1 +} +echo -e "${GREEN}✓ Replicated CLI installed${NC}" +echo "" + +# Check API token +echo -e "${YELLOW}[2/4] Checking Replicated API token...${NC}" +if [ -z "$REPLICATED_API_TOKEN" ]; then + echo -e "${YELLOW}⚠ REPLICATED_API_TOKEN not set${NC}" + echo "" + echo "To obtain an API token:" + echo " 1. Log in to vendor.replicated.com" + echo " 2. Go to Settings > Service Accounts" + echo " 3. Create a new token" + echo " 4. Export it: export REPLICATED_API_TOKEN=your-token" + echo "" + read -p "Enter your Replicated API token: " api_token + export REPLICATED_API_TOKEN="$api_token" +fi +echo -e "${GREEN}✓ API token configured${NC}" +echo "" + +# Set up application +APP_SLUG="${REPLICATED_APP_SLUG:-flipt}" +CUSTOMER_NAME="${CUSTOMER_NAME:-dev-$(whoami)-$(date +%s)}" +CHANNEL="${CHANNEL:-Unstable}" + +echo -e "${YELLOW}[3/4] Creating development customer...${NC}" +echo " App: $APP_SLUG" +echo " Customer: $CUSTOMER_NAME" +echo " Channel: $CHANNEL" +echo "" + +# Create customer with dev license +replicated customer create \ + --app "$APP_SLUG" \ + --name "$CUSTOMER_NAME" \ + --channel "$CHANNEL" \ + --license-type dev \ + --output json > /tmp/customer.json 2>&1 || { + echo -e "${RED}✗ Failed to create customer${NC}" + cat /tmp/customer.json + exit 1 +} + +LICENSE_ID=$(jq -r '.id' /tmp/customer.json) +echo -e "${GREEN}✓ Customer created${NC}" +echo "" + +# Save license ID +echo -e "${YELLOW}[4/4] Saving license configuration...${NC}" +mkdir -p .replicated +echo "REPLICATED_LICENSE_ID=$LICENSE_ID" > .replicated/license.env +echo "REPLICATED_APP_SLUG=$APP_SLUG" >> .replicated/license.env +echo "CUSTOMER_NAME=$CUSTOMER_NAME" >> .replicated/license.env +echo "CHANNEL=$CHANNEL" >> .replicated/license.env + +cat > .replicated/README.md < Date: Tue, 10 Feb 2026 11:47:46 +1300 Subject: [PATCH 02/12] Resolve embedded cnpg and redis db installs --- .gitignore | 7 + applications/flipt/.gitignore | 39 -- applications/flipt/Makefile | 65 ++- applications/flipt/QUICKSTART.md | 18 +- applications/flipt/README.md | 25 +- applications/flipt/TROUBLESHOOTING.md | 86 ++-- applications/flipt/chart/Chart.yaml | 21 +- applications/flipt/chart/templates/NOTES.txt | 94 +---- .../flipt/chart/templates/_helpers.tpl | 89 +++++ .../templates/_preflight.tpl} | 6 +- .../templates/_supportbundle.tpl} | 72 ++-- .../flipt/chart/templates/deployment.yaml | 145 +++++++ .../flipt/chart/templates/flipt-config.yaml | 56 +-- applications/flipt/chart/templates/hpa.yaml | 41 ++ .../flipt/chart/templates/ingress.yaml | 65 +++ applications/flipt/chart/templates/pdb.yaml | 14 + .../templates/postgresql-cluster-job.yaml | 199 ++++++++++ .../chart/templates/postgresql-cluster.yaml | 73 ---- .../templates/postgresql-external-secret.yaml | 19 + .../chart/templates/secret-preflights.yaml | 10 + .../chart/templates/secret-supportbundle.yaml | 10 + .../flipt/chart/templates/service.yaml | 54 +++ .../flipt/chart/templates/serviceaccount.yaml | 13 + applications/flipt/chart/values.yaml | 372 ++++++++++-------- applications/flipt/chart/values.yaml.backup | 275 +++++++++++++ applications/flipt/replicated/ec-cluster.yaml | 13 + applications/flipt/replicated/kots-app.yaml | 3 +- .../flipt/scripts/install-cnpg-operator.sh | 24 -- applications/flipt/scripts/install.sh | 155 -------- .../flipt/scripts/setup-dev-license.sh | 145 ------- 30 files changed, 1328 insertions(+), 880 deletions(-) delete mode 100644 applications/flipt/.gitignore rename applications/flipt/{replicated/kots-preflight.yaml => chart/templates/_preflight.tpl} (97%) rename applications/flipt/{replicated/kots-support-bundle.yaml => chart/templates/_supportbundle.tpl} (74%) create mode 100644 applications/flipt/chart/templates/deployment.yaml create mode 100644 applications/flipt/chart/templates/hpa.yaml create mode 100644 applications/flipt/chart/templates/ingress.yaml create mode 100644 applications/flipt/chart/templates/pdb.yaml create mode 100644 applications/flipt/chart/templates/postgresql-cluster-job.yaml delete mode 100644 applications/flipt/chart/templates/postgresql-cluster.yaml create mode 100644 applications/flipt/chart/templates/postgresql-external-secret.yaml create mode 100644 applications/flipt/chart/templates/secret-preflights.yaml create mode 100644 applications/flipt/chart/templates/secret-supportbundle.yaml create mode 100644 applications/flipt/chart/templates/service.yaml create mode 100644 applications/flipt/chart/templates/serviceaccount.yaml create mode 100644 applications/flipt/chart/values.yaml.backup create mode 100644 applications/flipt/replicated/ec-cluster.yaml delete mode 100755 applications/flipt/scripts/install-cnpg-operator.sh delete mode 100755 applications/flipt/scripts/install.sh delete mode 100755 applications/flipt/scripts/setup-dev-license.sh diff --git a/.gitignore b/.gitignore index 37d3b44f..8c89176a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,12 @@ .idea/ .vscode/ *.swp +*.swo +*~ *.bak *.tmp *.temp +.tmp/ # System-specific ignores .DS_Store @@ -56,4 +59,8 @@ applications/wg-easy/release/ .specstory/.what-is-this.md *.tar.gz +# Flipt specific +applications/flipt/release/ +applications/flipt/chart/Chart.lock + **/.claude/settings.local.json diff --git a/applications/flipt/.gitignore b/applications/flipt/.gitignore deleted file mode 100644 index 9fd36cb4..00000000 --- a/applications/flipt/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# Helm -*.tgz -chart/Chart.lock -chart/charts/*.tgz - -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS -.DS_Store -Thumbs.db - -# Kubernetes -kubeconfig -*.kubeconfig - -# Secrets -*.pem -*.key -secrets.yaml -*-secret.yaml - -# Logs -*.log - -# Temporary files -*.tmp -*.temp -.tmp/ - -# Support bundles -support-bundle-*.tar.gz - -# Replicated development licenses -.replicated/ diff --git a/applications/flipt/Makefile b/applications/flipt/Makefile index e0542b21..3a0551f7 100644 --- a/applications/flipt/Makefile +++ b/applications/flipt/Makefile @@ -4,6 +4,8 @@ CHART_DIR := chart CHART_NAME := flipt NAMESPACE := flipt RELEASE_NAME := flipt +RELEASE_DIR := release +REPLICATED_DIR := replicated help: ## Display this help message @echo "Flipt Helm Chart Management" @@ -17,7 +19,8 @@ lint: ## Lint the Helm chart package: lint ## Package the Helm chart @echo "Packaging Helm chart..." - helm package $(CHART_DIR) + @mkdir -p $(RELEASE_DIR) + helm package $(CHART_DIR) -d $(RELEASE_DIR) update-deps: ## Update Helm chart dependencies @echo "Adding Helm repositories..." @@ -29,47 +32,25 @@ update-deps: ## Update Helm chart dependencies @echo "Updating chart dependencies..." cd $(CHART_DIR) && helm dependency update -install-operator: ## Install CloudNativePG operator (required, run once per cluster) - @echo "Installing CloudNativePG operator..." - helm repo add cnpg https://cloudnative-pg.github.io/charts || true - helm repo update - helm upgrade --install cnpg \ - --namespace cnpg-system \ - --create-namespace \ - cnpg/cloudnative-pg - @echo "Waiting for operator to be ready..." - kubectl wait --for=condition=available --timeout=300s \ - deployment/cnpg-cloudnative-pg \ - -n cnpg-system || true - @echo "✓ Operator ready!" - -install: install-operator ## Install the chart (includes operator installation) +install: ## Install the chart (operator included as dependency) @echo "Installing $(CHART_NAME)..." + @echo "Note: This includes CloudNativePG operator installation, which may take a few minutes..." helm install $(RELEASE_NAME) $(CHART_DIR) \ --namespace $(NAMESPACE) \ --create-namespace \ --wait \ - --timeout 10m + --wait-for-jobs \ + --timeout 15m -setup-license: ## Set up Replicated development license - @./scripts/setup-dev-license.sh - -install-with-license: setup-license install-operator ## Set up license and install - @if [ -f ".replicated/license.env" ]; then \ - . .replicated/license.env && \ - $(MAKE) install; \ - else \ - echo "License setup failed"; \ - exit 1; \ - fi - -clean-license: ## Remove development license - @if [ -f ".replicated/license.env" ]; then \ - . .replicated/license.env && \ - replicated customer rm --customer "$$CUSTOMER_NAME" 2>/dev/null || true; \ - fi - @rm -rf .replicated/ - @echo "License configuration removed" +install-no-operator: ## Install without CloudNativePG operator (use if already installed) + @echo "Installing $(CHART_NAME) without operator..." + helm install $(RELEASE_NAME) $(CHART_DIR) \ + --namespace $(NAMESPACE) \ + --create-namespace \ + --set cloudnative-pg.enabled=false \ + --wait \ + --wait-for-jobs \ + --timeout 10m uninstall: ## Uninstall the chart @echo "Uninstalling $(CHART_NAME)..." @@ -112,7 +93,7 @@ clean: ## Clean generated files @echo "Cleaning generated files..." rm -rf $(CHART_DIR)/charts/*.tgz rm -rf $(CHART_DIR)/Chart.lock - rm -f *.tgz + rm -rf $(RELEASE_DIR)/*.tgz clean-install: clean update-deps install ## Clean rebuild and install @@ -122,19 +103,19 @@ port-forward: ## Port forward to Flipt service replicated-lint: ## Lint Replicated KOTS configs @echo "Linting KOTS configuration..." - replicated release lint --yaml-dir replicated/ + replicated release lint --yaml-dir $(REPLICATED_DIR)/ -replicated-create: ## Create a new Replicated release +replicated-create: package ## Create a new Replicated release (includes packaging) @echo "Creating Replicated release..." - replicated release create --auto --yaml-dir replicated/ + replicated release create --auto --yaml-dir $(REPLICATED_DIR)/ preflight: ## Run preflight checks @echo "Running preflight checks..." - kubectl preflight ./replicated/kots-preflight.yaml + kubectl preflight $(CHART_DIR)/templates/secret-preflights.yaml support-bundle: ## Generate support bundle @echo "Generating support bundle..." - kubectl support-bundle ./replicated/kots-support-bundle.yaml + kubectl support-bundle $(CHART_DIR)/templates/secret-supportbundle.yaml check-deps: ## Verify all required tools are installed @echo "Checking dependencies..." diff --git a/applications/flipt/QUICKSTART.md b/applications/flipt/QUICKSTART.md index 629da4b7..a4b6e657 100644 --- a/applications/flipt/QUICKSTART.md +++ b/applications/flipt/QUICKSTART.md @@ -16,12 +16,6 @@ Before you begin, you need a **Replicated development license**: # 1. Set your Replicated API token export REPLICATED_API_TOKEN=your-token-here -# 2. Set up development license -./scripts/setup-dev-license.sh - -# 3. Load the license -source .replicated/license.env -``` **Don't have a Replicated account?** - Sign up at [vendor.replicated.com](https://vendor.replicated.com) @@ -52,27 +46,25 @@ This script will: If you prefer to run commands manually: ```bash -# Step 1: Install CloudNativePG operator (cluster-wide, only needed once) -./scripts/install-cnpg-operator.sh - -# Step 2: Clean and update dependencies +# Step 1: Update chart dependencies (includes CloudNativePG operator) cd chart -rm -f charts/cloudnative-pg-*.tgz Chart.lock # Clean cached files +rm -f Chart.lock # Clean cached files helm repo add flipt https://helm.flipt.io helm repo add bitnami https://charts.bitnami.com/bitnami +helm repo add cnpg https://cloudnative-pg.github.io/charts helm repo add replicated https://charts.replicated.com helm repo update helm dependency update cd .. -# Step 3: Install Flipt +# Step 2: Install Flipt (operator included automatically) helm install flipt ./chart \ --namespace flipt \ --create-namespace \ --wait \ --timeout 10m -# Step 4: Port forward to access +# Step 3: Port forward to access kubectl port-forward -n flipt svc/flipt-flipt 8080:8080 ``` diff --git a/applications/flipt/README.md b/applications/flipt/README.md index b12affc3..66de1ccd 100644 --- a/applications/flipt/README.md +++ b/applications/flipt/README.md @@ -83,7 +83,7 @@ The admin console provides: ### Using Helm Directly -**⚠️ Important:** The CloudNativePG operator must be installed before deploying Flipt (only needed once per cluster). +**✨ Note:** The CloudNativePG operator is now included as a chart dependency and will be installed automatically. ### Important: Replicated License Required @@ -97,31 +97,12 @@ Flipt requires a Replicated development license for local testing. This provides ```bash # 1. Set up development license export REPLICATED_API_TOKEN=your-token -./scripts/setup-dev-license.sh - -# 2. Load license and install -source .replicated/license.env -./scripts/install.sh +export REPLICATED_LICENSE_ID=your-license-id ``` **Detailed instructions:** See [Development License Guide](docs/DEVELOPMENT_LICENSE.md) -1. **Install CloudNativePG operator:** - - ```bash - # Quick install using provided script - ./scripts/install-cnpg-operator.sh - - # Or manually - helm repo add cnpg https://cloudnative-pg.github.io/charts - helm repo update - helm upgrade --install cnpg \ - --namespace cnpg-system \ - --create-namespace \ - cnpg/cloudnative-pg - ``` - -2. **Add the Helm repositories:** +1. **Add the Helm repositories:** ```bash helm repo add flipt-repo https://helm.flipt.io diff --git a/applications/flipt/TROUBLESHOOTING.md b/applications/flipt/TROUBLESHOOTING.md index 9b3ae9e3..bf67ba34 100644 --- a/applications/flipt/TROUBLESHOOTING.md +++ b/applications/flipt/TROUBLESHOOTING.md @@ -14,36 +14,35 @@ no matches for kind "Cluster" in version "postgresql.cnpg.io/v1" ensure CRDs are installed first ``` -**Cause:** The CloudNativePG operator is not installed in your cluster. +**Cause:** The CloudNativePG operator chart dependency failed to install or CRDs are not present. -**Solution:** Install the operator before installing Flipt: +**Solution:** The operator is now included as a chart dependency and should install automatically. If you see this error: -```bash -# Quick install -./scripts/install-cnpg-operator.sh - -# Or manually -helm repo add cnpg https://cloudnative-pg.github.io/charts -helm repo update -helm upgrade --install cnpg \ - --namespace cnpg-system \ - --create-namespace \ - cnpg/cloudnative-pg +1. **Check if operator is disabled:** + ```bash + # Ensure operator is enabled (default) + helm install flipt ./chart \ + --namespace flipt \ + --create-namespace \ + --set cloudnative-pg.enabled=true + ``` -# Verify operator is running -kubectl get pods -n cnpg-system -``` +2. **Verify CRDs are installed:** + ```bash + kubectl get crd | grep postgresql.cnpg.io -**Verify CRDs are installed:** -```bash -kubectl get crd | grep postgresql.cnpg.io + # Should show: + # backups.postgresql.cnpg.io + # clusters.postgresql.cnpg.io + # poolers.postgresql.cnpg.io + # scheduledbackups.postgresql.cnpg.io + ``` -# Should show: -# backups.postgresql.cnpg.io -# clusters.postgresql.cnpg.io -# poolers.postgresql.cnpg.io -# scheduledbackups.postgresql.cnpg.io -``` +3. **Check operator pods:** + ```bash + kubectl get pods -n flipt + # Look for cloudnative-pg-* pods + ``` --- @@ -104,33 +103,22 @@ annotation validation error: key "meta.helm.sh/release-name" must equal "flipt": current value is "cnpg" ``` -**Cause:** Cached CloudNativePG dependency files from a previous configuration. +**Cause:** The CloudNativePG operator was previously installed separately at cluster level, and now it's also included as a chart dependency. -**Solution:** Clean and rebuild dependencies: +**Solution:** If you already have the operator installed cluster-wide, disable it in the chart: ```bash -cd chart - -# Remove cached operator dependency -rm -f charts/cloudnative-pg-*.tgz -rm -f Chart.lock - -# Rebuild dependencies -helm dependency update -cd .. - -# Now install -helm install flipt ./chart --namespace flipt --create-namespace -``` +# Install without operator dependency +helm install flipt ./chart \ + --namespace flipt \ + --create-namespace \ + --set cloudnative-pg.enabled=false -Or use the Makefile: -```bash -make clean -make update-deps -make install +# Or use the Makefile +make install-no-operator ``` -**Note:** The CloudNativePG operator should be installed separately at the cluster level, not as a chart dependency. +**Note:** The CloudNativePG operator is now included as a chart dependency by default. If you prefer to manage it separately at the cluster level, use `--set cloudnative-pg.enabled=false`. --- @@ -148,15 +136,11 @@ Error: either license in the config file or integration license id must be speci ```bash # Quick setup export REPLICATED_API_TOKEN=your-token -./scripts/setup-dev-license.sh -source .replicated/license.env -./scripts/install.sh +export REPLICATED_LICENSE_ID=your-license-id ``` **Detailed guide:** See [docs/DEVELOPMENT_LICENSE.md](docs/DEVELOPMENT_LICENSE.md) -**For CI/CD:** Licenses can be created programmatically and cleaned up after tests. - --- ### Pods Stuck in Pending State diff --git a/applications/flipt/chart/Chart.yaml b/applications/flipt/chart/Chart.yaml index 7e01a70f..bb5bce58 100644 --- a/applications/flipt/chart/Chart.yaml +++ b/applications/flipt/chart/Chart.yaml @@ -5,7 +5,7 @@ description: | This enterprise-ready deployment includes PostgreSQL for durable storage and Redis for distributed caching across multiple instances. type: application -version: 1.0.0 +version: 1.0.3 appVersion: "1.61.0" keywords: - feature-flags @@ -22,20 +22,17 @@ maintainers: url: https://replicated.com dependencies: - # Flipt application (official chart) - - name: flipt - version: "0.87.0" - repository: https://helm.flipt.io - condition: flipt.enabled - - # PostgreSQL database is managed via CloudnativePG operator - # Note: The CloudnativePG operator must be installed separately at the cluster level - # Install it with: helm install cnpg --namespace cnpg-system --create-namespace cnpg/cloudnative-pg - # This chart creates Cluster resources that the operator manages + # CloudNativePG operator for PostgreSQL management + # Note: If you already have the operator installed cluster-wide, disable with: + # --set cloudnative-pg.enabled=false + - name: cloudnative-pg + version: "0.23.0" + repository: https://cloudnative-pg.github.io/charts + condition: cloudnative-pg.enabled # Redis for distributed caching - name: redis - version: "20.5.0" + version: "19.6.4" repository: https://charts.bitnami.com/bitnami condition: redis.enabled diff --git a/applications/flipt/chart/templates/NOTES.txt b/applications/flipt/chart/templates/NOTES.txt index 75ddd48a..8d0b4322 100644 --- a/applications/flipt/chart/templates/NOTES.txt +++ b/applications/flipt/chart/templates/NOTES.txt @@ -1,77 +1,23 @@ -Thank you for installing {{ .Chart.Name }}! +You have successfully deployed Flipt. -Your release is named {{ .Release.Name }}. - -Flipt is a feature flag and experimentation platform that allows you to: - - Deploy features gradually with percentage rollouts - - Target specific users or segments with feature flags - - Run A/B tests and experiments - - Manage feature flags across multiple environments - -🚀 Getting Started: - -1. Wait for all pods to be ready: - - kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=flipt -n {{ .Release.Namespace }} --timeout=300s - -2. Access the Flipt UI: - -{{- if .Values.flipt.ingress.enabled }} -{{- range .Values.flipt.ingress.hosts }} - Open your browser to: {{ if $.Values.flipt.ingress.tls }}https{{else}}http{{ end }}://{{ .host }} +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} {{- end }} -{{- else }} - - Run the following command to port-forward: - - kubectl port-forward -n {{ .Release.Namespace }} svc/{{ .Release.Name }}-flipt 8080:8080 - - Then open your browser to: http://localhost:8080 - -{{- end }} - -📊 Component Status: - - Database: {{ if eq .Values.postgresql.type "embedded" }}Embedded PostgreSQL (CloudnativePG){{ else }}External PostgreSQL{{ end }} - Cache: {{ if .Values.redis.enabled }}Redis (enabled for distributed caching){{ else }}In-memory (single instance only){{ end }} - Replicas: {{ .Values.flipt.replicaCount }} - -{{- if .Values.replicated.enabled }} - Replicated SDK: Enabled +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "flipt.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "flipt.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "flipt.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.webPort }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "flipt.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT + echo "Visit http://127.0.0.1:8080 to use your application" {{- end }} - -📚 Next Steps: - -1. Create your first feature flag through the UI -2. Integrate Flipt into your application using one of the SDKs: - - Go: https://docs.flipt.io/integration/server - - Node.js: https://docs.flipt.io/integration/server - - Python: https://docs.flipt.io/integration/server - - Java: https://docs.flipt.io/integration/server - - More: https://docs.flipt.io/integration - -3. Configure authentication (recommended for production): - https://docs.flipt.io/authentication/overview - -4. Set up GitOps integration (optional): - https://docs.flipt.io/guides/get-going-with-gitops - -🔧 Management Commands: - - # View logs - kubectl logs -l app.kubernetes.io/name=flipt -n {{ .Release.Namespace }} --tail=100 -f - - # Check database status - {{- if eq .Values.postgresql.type "embedded" }} - kubectl get cluster {{ include "flipt.postgresql.clustername" . }} -n {{ .Release.Namespace }} - {{- end }} - - # Check Redis status - {{- if .Values.redis.enabled }} - kubectl get pods -l app.kubernetes.io/name=redis -n {{ .Release.Namespace }} - {{- end }} - -📖 Documentation: https://docs.flipt.io -💬 Community: https://discord.gg/kRhEqG2T - -For more information about Flipt, visit: https://flipt.io diff --git a/applications/flipt/chart/templates/_helpers.tpl b/applications/flipt/chart/templates/_helpers.tpl index cdb956b5..7dc06de3 100644 --- a/applications/flipt/chart/templates/_helpers.tpl +++ b/applications/flipt/chart/templates/_helpers.tpl @@ -7,6 +7,8 @@ Expand the name of the chart. {{/* 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). +If release name contains chart name it will be used as a full name. */}} {{- define "flipt.fullname" -}} {{- if .Values.fullnameOverride }} @@ -21,6 +23,13 @@ Create a default fully qualified app name. {{- end }} {{- end }} +{{/* +Create a fully qualified name for the migration job +*/}} +{{- define "flipt.migration_name" -}} +{{- include "flipt.fullname" . | trunc 53 | trimSuffix "-" }}-migration +{{- end }} + {{/* Create chart name and version as used by the chart label. */}} @@ -48,6 +57,17 @@ app.kubernetes.io/name: {{ include "flipt.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} +{{/* +Create the name of the service account to use +*/}} +{{- define "flipt.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "flipt.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + {{/* PostgreSQL connection URL */}} @@ -98,3 +118,72 @@ Redis secret name {{- define "flipt.redis.secret" -}} {{- printf "%s-redis" .Release.Name }} {{- end }} + +{{/* +Renders a value that contains template. +Usage: +{{ include "common.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "common.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{/* +Return the proper Storage Class +{{ include "common.storage.class" ( dict "persistence" .Values.path.to.the.persistence "global" $) }} +*/}} +{{- define "common.storage.class" -}} +{{- $storageClass := .persistence.storageClass -}} +{{- if .global -}} + {{- if .global.storageClass -}} + {{- $storageClass = .global.storageClass -}} + {{- end -}} +{{- end -}} +{{- if $storageClass -}} + {{- if (eq "-" $storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" $storageClass -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Pod annotations +*/}} +{{- define "common.classes.podAnnotations" -}} + {{- if .Values.podAnnotations -}} + {{- tpl (toYaml .Values.podAnnotations) . | nindent 0 -}} + {{- end -}} + {{- printf "checksum/config: %v" (join "," .Values.flipt | sha256sum) | nindent 0 -}} +{{- end -}} + +{{/* +Deployment annotations +*/}} +{{- define "common.classes.deploymentAnnotations" -}} + {{- if .Values.deploymentAnnotations -}} + {{- tpl (toYaml .Values.deploymentAnnotations) . | nindent 0 -}} + {{- end -}} + {{- printf "checksum/config: %v" (join "," .Values.flipt | sha256sum) | nindent 0 -}} +{{- end -}} + +{{/* Return the target Kubernetes version */}} +{{- define "flipt.tools.kubeVersion" -}} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersionOverride }} +{{- end }} + +{{/* Return the appropriate apiVersion for autoscaling */}} +{{- define "flipt.apiVersion.autoscaling" -}} +{{- if (.Values.apiVersionOverrides).autoscaling -}} +{{- print .Values.apiVersionOverrides.autoscaling -}} +{{- else if semverCompare "<1.23-0" (include "flipt.tools.kubeVersion" .) -}} +{{- print "autoscaling/v2beta1" -}} +{{- else -}} +{{- print "autoscaling/v2" -}} +{{- end -}} +{{- end -}} diff --git a/applications/flipt/replicated/kots-preflight.yaml b/applications/flipt/chart/templates/_preflight.tpl similarity index 97% rename from applications/flipt/replicated/kots-preflight.yaml rename to applications/flipt/chart/templates/_preflight.tpl index cd897916..6d118be3 100644 --- a/applications/flipt/replicated/kots-preflight.yaml +++ b/applications/flipt/chart/templates/_preflight.tpl @@ -1,3 +1,4 @@ +{{- define "flipt.preflight" -}} apiVersion: troubleshoot.sh/v1beta2 kind: Preflight metadata: @@ -167,7 +168,7 @@ spec: - logs: selector: - app=flipt - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Values.namespace | default "flipt" | quote }} limits: maxAge: 720h maxLines: 10000 @@ -175,7 +176,8 @@ spec: name: kubectl-version selector: - app=flipt - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Values.namespace | default "flipt" | quote }} command: ["kubectl"] args: ["version", "--short"] timeout: 30s +{{- end -}} diff --git a/applications/flipt/replicated/kots-support-bundle.yaml b/applications/flipt/chart/templates/_supportbundle.tpl similarity index 74% rename from applications/flipt/replicated/kots-support-bundle.yaml rename to applications/flipt/chart/templates/_supportbundle.tpl index 4289cb4c..6934da36 100644 --- a/applications/flipt/replicated/kots-support-bundle.yaml +++ b/applications/flipt/chart/templates/_supportbundle.tpl @@ -1,3 +1,4 @@ +{{- define "flipt.supportbundle" -}} apiVersion: troubleshoot.sh/v1beta2 kind: SupportBundle metadata: @@ -12,7 +13,7 @@ spec: - logs: selector: - app.kubernetes.io/name=flipt - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} limits: maxAge: 720h maxLines: 10000 @@ -21,8 +22,8 @@ spec: # PostgreSQL logs (embedded) - logs: selector: - - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster - namespace: '{{repl ConfigOption "namespace"}}' + - cnpg.io/cluster={{ .Release.Name }}-cluster + namespace: {{ .Release.Namespace }} limits: maxAge: 168h maxLines: 10000 @@ -32,7 +33,7 @@ spec: - logs: selector: - app.kubernetes.io/name=redis - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} limits: maxAge: 168h maxLines: 10000 @@ -40,50 +41,50 @@ spec: # Pod status and events - pods: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} selector: - app.kubernetes.io/name=flipt - pods: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} selector: - - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster + - cnpg.io/cluster={{ .Release.Name }}-cluster - pods: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} selector: - app.kubernetes.io/name=redis # Service and endpoint information - services: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} - endpoints: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} # ConfigMaps and Secrets (redacted) - configMaps: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} - secrets: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} includeKeys: - false # PVC and storage information - pvcs: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} # Ingress configuration - ingress: - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} # PostgreSQL specific diagnostics - exec: name: postgresql-version selector: - - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster - namespace: '{{repl ConfigOption "namespace"}}' + - cnpg.io/cluster={{ .Release.Name }}-cluster + namespace: {{ .Release.Namespace }} command: ["psql"] args: ["-c", "SELECT version();"] timeout: 30s @@ -91,8 +92,8 @@ spec: - exec: name: postgresql-connections selector: - - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster - namespace: '{{repl ConfigOption "namespace"}}' + - cnpg.io/cluster={{ .Release.Name }}-cluster + namespace: {{ .Release.Namespace }} command: ["psql"] args: ["-c", "SELECT count(*) as connections FROM pg_stat_activity;"] timeout: 30s @@ -100,8 +101,8 @@ spec: - exec: name: postgresql-database-size selector: - - cnpg.io/cluster={{ ConfigOption "release_name" }}-cluster - namespace: '{{repl ConfigOption "namespace"}}' + - cnpg.io/cluster={{ .Release.Name }}-cluster + namespace: {{ .Release.Namespace }} command: ["psql"] args: ["-c", "SELECT pg_size_pretty(pg_database_size('flipt')) as database_size;"] timeout: 30s @@ -112,7 +113,7 @@ spec: selector: - app.kubernetes.io/name=redis - app.kubernetes.io/component=master - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} command: ["redis-cli"] args: ["INFO"] timeout: 30s @@ -122,7 +123,7 @@ spec: selector: - app.kubernetes.io/name=redis - app.kubernetes.io/component=master - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} command: ["redis-cli"] args: ["INFO", "memory"] timeout: 30s @@ -131,7 +132,7 @@ spec: - http: name: flipt-health get: - url: http://{{ ConfigOption "release_name" }}-flipt.{{ ConfigOption "namespace" }}.svc.cluster.local:8080/health + url: http://{{ .Release.Name }}-flipt.{{ .Release.Namespace }}.svc.cluster.local:8080/health timeout: 30s # Helm release information @@ -139,18 +140,18 @@ spec: name: helm-values selector: - app.kubernetes.io/name=flipt - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} command: ["sh"] - args: ["-c", "helm get values {{ ConfigOption 'release_name' }} -n {{ ConfigOption 'namespace' }}"] + args: ["-c", "helm get values {{ .Release.Name }} -n {{ .Release.Namespace }}"] timeout: 30s - exec: name: helm-manifest selector: - app.kubernetes.io/name=flipt - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} command: ["sh"] - args: ["-c", "helm get manifest {{ ConfigOption 'release_name' }} -n {{ ConfigOption 'namespace' }}"] + args: ["-c", "helm get manifest {{ .Release.Name }} -n {{ .Release.Namespace }}"] timeout: 30s # Node information @@ -164,31 +165,31 @@ spec: name: flipt-to-postgres-connectivity selector: - app.kubernetes.io/name=flipt - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} command: ["sh"] - args: ["-c", "nc -zv {{ ConfigOption 'release_name' }}-cluster-rw 5432 || echo 'Cannot connect to PostgreSQL'"] + args: ["-c", "nc -zv {{ .Release.Name }}-cluster-rw 5432 || echo 'Cannot connect to PostgreSQL'"] timeout: 10s - exec: name: flipt-to-redis-connectivity selector: - app.kubernetes.io/name=flipt - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} command: ["sh"] - args: ["-c", "nc -zv {{ ConfigOption 'release_name' }}-redis-master 6379 || echo 'Cannot connect to Redis'"] + args: ["-c", "nc -zv {{ .Release.Name }}-redis-master 6379 || echo 'Cannot connect to Redis'"] timeout: 10s analyzers: # Pod status analysis - deploymentStatus: name: flipt-deployment - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} outcomes: - fail: when: "< 1" message: Flipt deployment has no ready replicas - warn: - when: "< {{ ConfigOption 'replica_count' }}" + when: "< {{ .Values.replicaCount | default 1 }}" message: Flipt deployment has fewer replicas than configured - pass: message: Flipt deployment is healthy @@ -196,7 +197,7 @@ spec: # PostgreSQL cluster health - clusterPodStatuses: name: postgresql-cluster-health - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} outcomes: - fail: when: "!= Healthy" @@ -207,7 +208,7 @@ spec: # Redis health - statefulsetStatus: name: redis-health - namespace: '{{repl ConfigOption "namespace"}}' + namespace: {{ .Release.Namespace }} outcomes: - fail: when: "< 1" @@ -296,3 +297,4 @@ spec: - pass: when: "true" message: Flipt API is healthy +{{- end -}} diff --git a/applications/flipt/chart/templates/deployment.yaml b/applications/flipt/chart/templates/deployment.yaml new file mode 100644 index 00000000..26160b9c --- /dev/null +++ b/applications/flipt/chart/templates/deployment.yaml @@ -0,0 +1,145 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "flipt.fullname" . }} + namespace: {{ .Release.Namespace }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/flipt-config.yaml") . | sha256sum }} + {{- if .Values.deploymentAnnotations }} + {{- toYaml .Values.deploymentAnnotations | nindent 4 }} + {{- end }} + {{- if eq .Values.postgresql.type "embedded" }} + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "10" + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "flipt.labels" . | nindent 4 }} + {{- if .Values.deploymentLabels }} + {{- toYaml .Values.deploymentLabels | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + minReadySeconds: {{ .Values.minReadySeconds }} + {{- with .Values.strategy }} + strategy: + {{- toYaml . | nindent 8 }} + {{- end }} + + selector: + matchLabels: + {{- include "flipt.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: {{- include "common.classes.podAnnotations" . | nindent 8 }} + labels: + {{- include "flipt.selectorLabels" . | nindent 8 }} + {{- if .Values.podLabels }} + {{- toYaml .Values.podLabels | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end}} + serviceAccountName: {{ include "flipt.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + {{- toYaml .Values.image.command | nindent 12 }} + ports: + - name: http + containerPort: {{ .Values.config.server.httpPort }} + protocol: TCP + - name: https + containerPort: {{ .Values.service.httpsPort }} + protocol: TCP + - name: grpc + containerPort: {{ .Values.config.server.grpcPort }} + protocol: TCP + env: + - name: FLIPT_META_STATE_DIRECTORY + value: /home/flipt/.config/flipt + - name: FLIPT_META_CHECK_FOR_UPDATES + value: "0" + {{- if eq .Values.postgresql.type "embedded" }} + - name: FLIPT_DB_URL + valueFrom: + secretKeyRef: + name: {{ include "flipt.postgresql.clustername" . }}-app + key: uri + {{- end }} + {{- if and .Values.redis.enabled .Values.redis.auth.enabled }} + - name: FLIPT_CACHE_REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "flipt.redis.secret" . }} + key: redis-password + {{- end }} + {{- if .Values.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + - name: flipt-local-state + mountPath: /home/flipt/.config/flipt + - name: flipt-config + mountPath: /etc/flipt/config/default.yml + readOnly: true + subPath: default.yml + - name: flipt-data + mountPath: /var/opt/flipt + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if .Values.extraVolumeMounts }} + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: flipt-local-state + emptyDir: {} + - name: flipt-config + configMap: + name: {{ include "flipt.fullname" . }} + - name: flipt-data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ default (include "flipt.fullname" .) .Values.persistence.existingClaim }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.extraVolumes }} + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/applications/flipt/chart/templates/flipt-config.yaml b/applications/flipt/chart/templates/flipt-config.yaml index 2ebd74c3..0695cb05 100644 --- a/applications/flipt/chart/templates/flipt-config.yaml +++ b/applications/flipt/chart/templates/flipt-config.yaml @@ -1,61 +1,63 @@ -{{- if .Values.flipt.enabled }} ---- apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "flipt.fullname" . }}-config + name: {{ include "flipt.fullname" . }} namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-1" labels: {{- include "flipt.labels" . | nindent 4 }} data: - config.yaml: | + default.yml: | log: - level: {{ .Values.flipt.config.log.level }} - encoding: {{ .Values.flipt.config.log.encoding }} + level: {{ .Values.config.log.level }} + encoding: {{ .Values.config.log.encoding }} server: - protocol: {{ .Values.flipt.config.server.protocol }} - host: {{ .Values.flipt.config.server.host }} - http_port: {{ .Values.flipt.config.server.httpPort }} - grpc_port: {{ .Values.flipt.config.server.grpcPort }} + protocol: {{ .Values.config.server.protocol }} + host: {{ .Values.config.server.host }} + http_port: {{ .Values.config.server.httpPort }} + grpc_port: {{ .Values.config.server.grpcPort }} db: - url: {{ include "flipt.postgresql.url" . | quote }} - max_idle_conn: {{ .Values.flipt.config.db.maxIdleConn }} - max_open_conn: {{ .Values.flipt.config.db.maxOpenConn }} - conn_max_lifetime: {{ .Values.flipt.config.db.connMaxLifetime }} + # URL is set via FLIPT_DB_URL environment variable + max_idle_conn: {{ .Values.config.db.maxIdleConn }} + max_open_conn: {{ .Values.config.db.maxOpenConn }} + conn_max_lifetime: {{ .Values.config.db.connMaxLifetime }} {{- if .Values.redis.enabled }} cache: - enabled: {{ .Values.flipt.config.cache.enabled }} - backend: {{ .Values.flipt.config.cache.backend }} - ttl: {{ .Values.flipt.config.cache.ttl }} + enabled: {{ .Values.config.cache.enabled }} + backend: {{ .Values.config.cache.backend }} + ttl: {{ .Values.config.cache.ttl }} redis: - url: {{ include "flipt.redis.url" . | quote }} - mode: {{ .Values.flipt.config.cache.redis.mode }} - prefix: {{ .Values.flipt.config.cache.redis.prefix | quote }} + host: {{ .Release.Name }}-redis-master.{{ .Release.Namespace }}.svc.cluster.local + port: 6379 + # password is set via FLIPT_CACHE_REDIS_PASSWORD environment variable + mode: {{ .Values.config.cache.redis.mode }} + prefix: {{ .Values.config.cache.redis.prefix | quote }} {{- else }} cache: enabled: true backend: memory - ttl: {{ .Values.flipt.config.cache.ttl }} + ttl: {{ .Values.config.cache.ttl }} memory: eviction_interval: 2m {{- end }} - {{- if .Values.flipt.config.cors.enabled }} + {{- if .Values.config.cors.enabled }} cors: - enabled: {{ .Values.flipt.config.cors.enabled }} + enabled: {{ .Values.config.cors.enabled }} allowed_origins: - {{- range .Values.flipt.config.cors.allowedOrigins }} + {{- range .Values.config.cors.allowedOrigins }} - {{ . | quote }} {{- end }} {{- end }} - {{- if .Values.flipt.config.authentication.methods.token.enabled }} + {{- if .Values.config.authentication.methods.token.enabled }} authentication: methods: token: - enabled: {{ .Values.flipt.config.authentication.methods.token.enabled }} + enabled: {{ .Values.config.authentication.methods.token.enabled }} {{- end }} -{{- end }} diff --git a/applications/flipt/chart/templates/hpa.yaml b/applications/flipt/chart/templates/hpa.yaml new file mode 100644 index 00000000..23c1b844 --- /dev/null +++ b/applications/flipt/chart/templates/hpa.yaml @@ -0,0 +1,41 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: {{ include "flipt.apiVersion.autoscaling" . }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "flipt.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "flipt.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + {{- if eq (include "flipt.apiVersion.autoscaling" $) "autoscaling/v2beta1" }} + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- else }} + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + {{- if eq (include "flipt.apiVersion.autoscaling" $) "autoscaling/v2beta1" }} + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- else }} + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} + {{- end }} +{{- end }} diff --git a/applications/flipt/chart/templates/ingress.yaml b/applications/flipt/chart/templates/ingress.yaml new file mode 100644 index 00000000..1b598838 --- /dev/null +++ b/applications/flipt/chart/templates/ingress.yaml @@ -0,0 +1,65 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "flipt.fullname" . -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ default $fullName ((default dict .backend).serviceName) }} + port: + {{- if (default dict .backend).servicePort }} + name: {{ (default dict .backend).servicePort }} + {{- else }} + name: http + {{- end }} + {{- else }} + serviceName: {{ default $fullName ((default dict .backend).serviceName) }} + servicePort: {{ default "http" ((default dict .backend).servicePort) }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/applications/flipt/chart/templates/pdb.yaml b/applications/flipt/chart/templates/pdb.yaml new file mode 100644 index 00000000..4762f86e --- /dev/null +++ b/applications/flipt/chart/templates/pdb.yaml @@ -0,0 +1,14 @@ +{{- if (.Values.pdb).enabled }} +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "flipt.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "flipt.labels" . | nindent 4 }} +spec: + maxUnavailable: {{ default "25%" .Values.pdb.maxUnavailable }} + selector: + matchLabels: + {{- include "flipt.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/applications/flipt/chart/templates/postgresql-cluster-job.yaml b/applications/flipt/chart/templates/postgresql-cluster-job.yaml new file mode 100644 index 00000000..4048d9e2 --- /dev/null +++ b/applications/flipt/chart/templates/postgresql-cluster-job.yaml @@ -0,0 +1,199 @@ +{{- if and .Values.postgresql.embedded.enabled (eq .Values.postgresql.type "embedded") }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "flipt.fullname" . }}-create-db + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "flipt.fullname" . }}-create-db + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +rules: + - apiGroups: ["postgresql.cnpg.io"] + resources: ["clusters"] + verbs: ["get", "create", "patch"] + - apiGroups: [""] + resources: ["endpoints"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "flipt.fullname" . }}-create-db + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "flipt.fullname" . }}-create-db +subjects: + - kind: ServiceAccount + name: {{ include "flipt.fullname" . }}-create-db + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "flipt.fullname" . }}-db-manifest + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +data: + cluster.yaml: | + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + metadata: + name: {{ include "flipt.postgresql.clustername" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 8 }} + spec: + instances: {{ .Values.postgresql.embedded.cluster.instances }} + + imageName: {{ .Values.postgresql.embedded.cluster.imageName }} + + bootstrap: + initdb: + database: {{ .Values.postgresql.database }} + owner: {{ .Values.postgresql.username }} + + storage: + size: {{ .Values.postgresql.embedded.cluster.storage.size }} + {{- if .Values.postgresql.embedded.cluster.storage.storageClass }} + storageClass: {{ .Values.postgresql.embedded.cluster.storage.storageClass }} + {{- end }} + + resources: + {{- toYaml .Values.postgresql.embedded.cluster.resources | nindent 8 }} + + {{- if .Values.postgresql.embedded.cluster.backup.enabled }} + backup: + {{- toYaml .Values.postgresql.embedded.cluster.backup | nindent 8 }} + {{- end }} + + postgresql: + parameters: + max_connections: "200" + shared_buffers: "256MB" + effective_cache_size: "1GB" + maintenance_work_mem: "64MB" + checkpoint_completion_target: "0.9" + wal_buffers: "16MB" + default_statistics_target: "100" + random_page_cost: "1.1" + effective_io_concurrency: "200" + work_mem: "2621kB" + min_wal_size: "1GB" + max_wal_size: "4GB" + + monitoring: + enablePodMonitor: {{ .Values.postgresql.embedded.cluster.monitoring.enabled | default false }} + + {{- if gt (int .Values.postgresql.embedded.cluster.instances) 1 }} + # Enable synchronous replication for HA + postgresql: + syncReplicaElectionConstraint: + enabled: true + {{- end }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "flipt.fullname" . }}-create-db + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +spec: + backoffLimit: 10 + template: + metadata: + labels: + {{- include "flipt.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "flipt.fullname" . }}-create-db + restartPolicy: OnFailure + containers: + - name: create-cluster + image: bitnami/kubectl:latest + command: + - /bin/bash + - -c + - | + set -e + + echo "Waiting for CloudNativePG operator webhook to be ready..." + for i in {1..60}; do + if kubectl get endpoints cnpg-webhook-service -n {{ .Release.Namespace }} &>/dev/null; then + ENDPOINTS=$(kubectl get endpoints cnpg-webhook-service -n {{ .Release.Namespace }} -o jsonpath='{.subsets[*].addresses[*].ip}') + if [ -n "$ENDPOINTS" ]; then + echo "CloudNativePG webhook is ready!" + break + fi + fi + echo "Waiting for webhook endpoints... (attempt $i/60)" + sleep 5 + done + + echo "Creating PostgreSQL Cluster..." + if kubectl get cluster {{ include "flipt.postgresql.clustername" . }} -n {{ .Release.Namespace }} &>/dev/null; then + echo "Cluster already exists, patching..." + kubectl apply -f /manifests/cluster.yaml + else + echo "Creating new cluster..." + kubectl create -f /manifests/cluster.yaml + fi + + echo "Waiting for PostgreSQL Cluster to be ready..." + for i in {1..60}; do + READY=$(kubectl get cluster {{ include "flipt.postgresql.clustername" . }} -n {{ .Release.Namespace }} -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$READY" = "Cluster in healthy state" ]; then + echo "PostgreSQL Cluster is ready!" + break + fi + echo "Waiting for cluster to be ready... (attempt $i/60, status: $READY)" + sleep 10 + done + + # Verify the cluster is truly ready + kubectl wait --for=condition=Ready pod -l cnpg.io/cluster={{ include "flipt.postgresql.clustername" . }} -n {{ .Release.Namespace }} --timeout=600s + + echo "PostgreSQL Cluster created and ready!" + volumeMounts: + - name: manifests + mountPath: /manifests + volumes: + - name: manifests + configMap: + name: {{ include "flipt.fullname" . }}-db-manifest +{{- end }} diff --git a/applications/flipt/chart/templates/postgresql-cluster.yaml b/applications/flipt/chart/templates/postgresql-cluster.yaml deleted file mode 100644 index f1bda469..00000000 --- a/applications/flipt/chart/templates/postgresql-cluster.yaml +++ /dev/null @@ -1,73 +0,0 @@ -{{- if and .Values.postgresql.embedded.enabled (eq .Values.postgresql.type "embedded") }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "flipt.fullname" . }}-postgresql-credentials - namespace: {{ .Release.Namespace }} - labels: - {{- include "flipt.labels" . | nindent 4 }} -type: Opaque -stringData: - username: {{ .Values.postgresql.username | quote }} - password: {{ .Values.postgresql.password | default (randAlphaNum 32) | quote }} - database: {{ .Values.postgresql.database | quote }} ---- -apiVersion: postgresql.cnpg.io/v1 -kind: Cluster -metadata: - name: {{ include "flipt.postgresql.clustername" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "flipt.labels" . | nindent 4 }} -spec: - instances: {{ .Values.postgresql.embedded.cluster.instances }} - - imageName: {{ .Values.postgresql.embedded.cluster.imageName }} - - bootstrap: - initdb: - database: {{ .Values.postgresql.database }} - owner: {{ .Values.postgresql.username }} - secret: - name: {{ include "flipt.fullname" . }}-postgresql-credentials - - storage: - size: {{ .Values.postgresql.embedded.cluster.storage.size }} - {{- if .Values.postgresql.embedded.cluster.storage.storageClass }} - storageClass: {{ .Values.postgresql.embedded.cluster.storage.storageClass }} - {{- end }} - - resources: - {{- toYaml .Values.postgresql.embedded.cluster.resources | nindent 4 }} - - {{- if .Values.postgresql.embedded.cluster.backup.enabled }} - backup: - {{- toYaml .Values.postgresql.embedded.cluster.backup | nindent 4 }} - {{- end }} - - postgresql: - parameters: - max_connections: "200" - shared_buffers: "256MB" - effective_cache_size: "1GB" - maintenance_work_mem: "64MB" - checkpoint_completion_target: "0.9" - wal_buffers: "16MB" - default_statistics_target: "100" - random_page_cost: "1.1" - effective_io_concurrency: "200" - work_mem: "2621kB" - min_wal_size: "1GB" - max_wal_size: "4GB" - - monitoring: - enablePodMonitor: {{ .Values.postgresql.embedded.cluster.monitoring.enabled | default false }} - - {{- if gt (int .Values.postgresql.embedded.cluster.instances) 1 }} - # Enable synchronous replication for HA - postgresql: - syncReplicaElectionConstraint: - enabled: true - {{- end }} -{{- end }} diff --git a/applications/flipt/chart/templates/postgresql-external-secret.yaml b/applications/flipt/chart/templates/postgresql-external-secret.yaml new file mode 100644 index 00000000..a9b99dec --- /dev/null +++ b/applications/flipt/chart/templates/postgresql-external-secret.yaml @@ -0,0 +1,19 @@ +{{- if and (eq .Values.postgresql.type "external") .Values.postgresql.external.enabled }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "flipt.fullname" . }}-postgresql-external + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 4 }} +type: Opaque +stringData: + host: {{ .Values.postgresql.external.host | quote }} + port: {{ .Values.postgresql.external.port | quote }} + database: {{ .Values.postgresql.external.database | quote }} + username: {{ .Values.postgresql.external.username | quote }} + password: {{ .Values.postgresql.external.password | quote }} + sslMode: {{ .Values.postgresql.external.sslMode | quote }} + url: {{ include "flipt.postgresql.url" . | quote }} +{{- end }} diff --git a/applications/flipt/chart/templates/secret-preflights.yaml b/applications/flipt/chart/templates/secret-preflights.yaml new file mode 100644 index 00000000..02fa4ba5 --- /dev/null +++ b/applications/flipt/chart/templates/secret-preflights.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-preflights + labels: + troubleshoot.sh/kind: preflight +type: Opaque +stringData: + preflight.yaml: | +{{ include "flipt.preflight" . | indent 4 }} diff --git a/applications/flipt/chart/templates/secret-supportbundle.yaml b/applications/flipt/chart/templates/secret-supportbundle.yaml new file mode 100644 index 00000000..529232e5 --- /dev/null +++ b/applications/flipt/chart/templates/secret-supportbundle.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-supportbundle + labels: + troubleshoot.sh/kind: support-bundle +type: Opaque +stringData: + support-bundle-spec: | +{{ include "flipt.supportbundle" . | indent 4 }} diff --git a/applications/flipt/chart/templates/service.yaml b/applications/flipt/chart/templates/service.yaml new file mode 100644 index 00000000..912190a1 --- /dev/null +++ b/applications/flipt/chart/templates/service.yaml @@ -0,0 +1,54 @@ +{{- if or .Values.service.enabled (not (hasKey .Values.service "enabled")) }} +{{- $root := . }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "flipt.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 4 }} + {{- with .Values.service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.service.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if (or (eq .Values.service.type "ClusterIP") (empty .Values.service.type)) }} + type: ClusterIP + {{- with .Values.service.clusterIP }} + clusterIP: {{ . }} + {{- end }} + {{- else if eq .Values.service.type "LoadBalancer" }} + type: {{ .Values.service.type }} + {{- with .Values.service.loadBalancerIP }} + loadBalancerIP: {{ . }} + {{- end }} + {{- with .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- else }} + type: {{ .Values.service.type }} + {{- end }} + {{- with .Values.service.externalIPs }} + externalIPs: + {{- toYaml . | nindent 4 }} + {{- end }} + ports: + - port: {{ .Values.service.httpPort }} + targetPort: http + protocol: TCP + name: http + - port: {{ .Values.service.httpsPort }} + targetPort: https + protocol: TCP + name: https + - port: {{ .Values.service.grpcPort }} + targetPort: grpc + protocol: TCP + name: grpc + selector: + {{- include "flipt.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/applications/flipt/chart/templates/serviceaccount.yaml b/applications/flipt/chart/templates/serviceaccount.yaml new file mode 100644 index 00000000..5ee04ff7 --- /dev/null +++ b/applications/flipt/chart/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "flipt.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "flipt.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/applications/flipt/chart/values.yaml b/applications/flipt/chart/values.yaml index dea6ce4c..6078fc34 100644 --- a/applications/flipt/chart/values.yaml +++ b/applications/flipt/chart/values.yaml @@ -1,119 +1,201 @@ ## Flipt Feature Flag Platform Configuration -## This values file provides sensible defaults for deploying Flipt with PostgreSQL and Redis +## Standalone chart with PostgreSQL and Redis support global: ## Global image registry override ## Useful for air-gap deployments or custom registries imageRegistry: "" -## Flipt application configuration -flipt: +## CloudNativePG Operator Configuration +## Set to false if operator is already installed cluster-wide +cloudnative-pg: enabled: true - image: - ## Use global registry if set, otherwise default - registry: docker.flipt.io - repository: flipt/flipt - tag: "" # Uses appVersion from Chart.yaml - pullPolicy: IfNotPresent +## Flipt Application Configuration - ## Number of Flipt replicas (2+ recommended for HA with Redis cache) - replicaCount: 2 - - ## Resource limits for Flipt pods - resources: - limits: - cpu: 500m - memory: 512Mi - requests: - cpu: 100m - memory: 128Mi - - ## Flipt configuration - ## See https://docs.flipt.io/configuration/overview for all options - config: - ## Logging configuration - log: - level: info - encoding: json - - ## Server configuration - server: - protocol: http - host: 0.0.0.0 - httpPort: 8080 - grpcPort: 9000 - - ## Database configuration (templated from values below) - db: - url: "" # Will be set via template based on postgresql configuration - maxIdleConn: 10 - maxOpenConn: 50 - connMaxLifetime: 1h - - ## Cache configuration (templated from redis configuration) - cache: - enabled: true - backend: redis # or 'memory' for single instance - ttl: 5m - redis: - url: "" # Will be set via template based on redis configuration - mode: single # or 'cluster' for Redis cluster - prefix: "flipt" - - ## CORS configuration for web UI - cors: - enabled: true - allowedOrigins: - - "*" - - ## Authentication (optional, configure for production) - authentication: - methods: - token: - enabled: false - - ## Service configuration - service: - type: ClusterIP +replicaCount: 2 +minReadySeconds: 0 + +image: + registry: ghcr.io + repository: flipt-io/flipt + pullPolicy: IfNotPresent + tag: "v1.61.0" # Note: Flipt versions require 'v' prefix + command: ["/flipt"] + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} +podLabels: {} + +deploymentAnnotations: {} +deploymentLabels: {} + +podSecurityContext: + runAsUser: 100 + runAsGroup: 1000 + fsGroup: 1000 + runAsNonRoot: true + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: "RuntimeDefault" + +## Flipt Configuration +## See https://docs.flipt.io/configuration/overview for all options +config: + ## Logging configuration + log: + level: info + encoding: json + + ## Server configuration + server: + protocol: http + host: 0.0.0.0 httpPort: 8080 grpcPort: 9000 - annotations: {} - ## Ingress configuration - ingress: - enabled: false - className: "" - annotations: {} - # cert-manager.io/cluster-issuer: letsencrypt-prod - # nginx.ingress.kubernetes.io/ssl-redirect: "true" - hosts: - - host: flipt.example.com - paths: - - path: / - pathType: Prefix - tls: [] - # - secretName: flipt-tls - # hosts: - # - flipt.example.com - - ## Horizontal Pod Autoscaler - autoscaling: - enabled: false - minReplicas: 2 - maxReplicas: 10 - targetCPUUtilizationPercentage: 80 - targetMemoryUtilizationPercentage: 80 + ## Database configuration (templated from PostgreSQL values) + db: + url: "" # Will be set via template + maxIdleConn: 10 + maxOpenConn: 50 + connMaxLifetime: 1h - ## Pod Disruption Budget (recommended for HA) - podDisruptionBudget: + ## Cache configuration (templated from Redis values) + cache: enabled: true - minAvailable: 1 + backend: redis # or 'memory' for single instance + ttl: 5m + redis: + url: "" # Will be set via template + mode: single # or 'cluster' for Redis cluster + prefix: "flipt" + + ## CORS configuration for web UI + cors: + enabled: true + allowedOrigins: + - "*" - ## ServiceMonitor for Prometheus metrics - serviceMonitor: - enabled: false - interval: 30s + ## Authentication (optional, configure for production) + authentication: + methods: + token: + enabled: false + +## Health check probes +readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 3 + +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 3 + +## Resource limits for Flipt pods +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +## Service configuration +service: + enabled: true + type: ClusterIP + httpPort: 8080 + httpsPort: 443 + grpcPort: 9000 + annotations: {} + labels: {} + +## Ingress configuration +ingress: + enabled: false + className: "" + annotations: {} + # cert-manager.io/cluster-issuer: letsencrypt-prod + # nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: flipt.local + paths: + - path: / + pathType: ImplementationSpecific + backend: + servicePort: http + serviceName: "" + tls: [] + # - secretName: flipt-tls + # hosts: + # - flipt.local + +## Horizontal Pod Autoscaler +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +## Pod Disruption Budget (recommended for HA) +podDisruptionBudget: + enabled: true + minAvailable: 1 + +## ServiceMonitor for Prometheus metrics +serviceMonitor: + enabled: false + interval: 30s + scrapeTimeout: 10s + labels: {} + +## Persistence (optional) +persistence: + enabled: false + storageClass: "" + accessMode: ReadWriteOnce + size: 8Gi + +## Extra environment variables +extraEnvVars: [] + +## Extra volumes +extraVolumes: [] + +## Extra volume mounts +extraVolumeMounts: [] + +## Node selector +nodeSelector: {} + +## Tolerations +tolerations: [] + +## Affinity rules +affinity: {} ## PostgreSQL Database Configuration postgresql: @@ -142,8 +224,6 @@ postgresql: ## Enable continuous backup (optional) backup: enabled: false - # Configure S3 or similar for WAL archiving - # See CloudnativePG docs for details ## Enable monitoring (optional) monitoring: enabled: false @@ -167,16 +247,27 @@ postgresql: redis: enabled: true - ## Redis architecture: standalone or replication - architecture: standalone # 'replication' for primary/replica setup + ## Redis image configuration + image: + registry: docker.io + repository: redis + tag: latest + pullPolicy: IfNotPresent + + ## Redis architecture + architecture: standalone # or 'replication' for HA - ## Redis authentication + ## Authentication auth: enabled: true password: "" # Auto-generated if empty - ## Redis Master configuration + ## Master configuration master: + persistence: + enabled: true + size: 5Gi + storageClass: "" resources: limits: cpu: 500m @@ -184,14 +275,13 @@ redis: requests: cpu: 100m memory: 128Mi - persistence: - enabled: true - size: 5Gi - storageClass: "" # Uses default storage class - ## Redis Replica configuration (if architecture: replication) + ## Replica configuration (if architecture: replication) replica: replicaCount: 1 + persistence: + enabled: true + size: 5Gi resources: limits: cpu: 500m @@ -199,66 +289,18 @@ redis: requests: cpu: 100m memory: 128Mi - persistence: - enabled: true - size: 5Gi - ## Redis metrics for monitoring + ## Redis Sentinel (for HA) + sentinel: + enabled: false + + ## Metrics exporter metrics: enabled: false - serviceMonitor: - enabled: false ## Replicated SDK Integration replicated: enabled: true - - ## Integration configuration integration: enabled: true - - ## Custom license fields (optional) - ## licenseFields: {} - -## Additional Kubernetes Resources - -## ConfigMap for additional Flipt configuration -extraConfigMap: {} - -## Secrets for sensitive configuration -extraSecrets: {} - -## Pod annotations -podAnnotations: {} - -## Pod labels -podLabels: {} - -## Security context -securityContext: - runAsNonRoot: true - runAsUser: 1000 - fsGroup: 1000 - seccompProfile: - type: RuntimeDefault - -## Node selector -nodeSelector: {} - -## Tolerations -tolerations: [] - -## Affinity rules -affinity: {} - ## Example anti-affinity to spread pods across nodes - # podAntiAffinity: - # preferredDuringSchedulingIgnoredDuringExecution: - # - weight: 100 - # podAffinityTerm: - # labelSelector: - # matchExpressions: - # - key: app.kubernetes.io/name - # operator: In - # values: - # - flipt - # topologyKey: kubernetes.io/hostname + licenseID: "" diff --git a/applications/flipt/chart/values.yaml.backup b/applications/flipt/chart/values.yaml.backup new file mode 100644 index 00000000..933c8442 --- /dev/null +++ b/applications/flipt/chart/values.yaml.backup @@ -0,0 +1,275 @@ +## Flipt Feature Flag Platform Configuration +## This values file provides sensible defaults for deploying Flipt with PostgreSQL and Redis + +global: + ## Global image registry override + ## Useful for air-gap deployments or custom registries + imageRegistry: "" + +## CloudNativePG Operator Configuration +## Set to false if operator is already installed cluster-wide +cloudnative-pg: + enabled: true + +## Flipt application configuration +flipt: + enabled: true + + image: + ## Use global registry if set, otherwise default + registry: docker.flipt.io + repository: flipt/flipt + tag: "" # Uses appVersion from Chart.yaml + pullPolicy: IfNotPresent + + ## Number of Flipt replicas (2+ recommended for HA with Redis cache) + replicaCount: 2 + + ## Resource limits for Flipt pods + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + + ## Flipt configuration + ## See https://docs.flipt.io/configuration/overview for all options + config: + ## Logging configuration + log: + level: info + encoding: json + + ## Server configuration + server: + protocol: http + host: 0.0.0.0 + httpPort: 8080 + grpcPort: 9000 + + ## Database configuration (templated from values below) + db: + url: "" # Will be set via template based on postgresql configuration + maxIdleConn: 10 + maxOpenConn: 50 + connMaxLifetime: 1h + + ## Cache configuration (templated from redis configuration) + cache: + enabled: true + backend: redis # or 'memory' for single instance + ttl: 5m + redis: + url: "" # Will be set via template based on redis configuration + mode: single # or 'cluster' for Redis cluster + prefix: "flipt" + + ## CORS configuration for web UI + cors: + enabled: true + allowedOrigins: + - "*" + + ## Authentication (optional, configure for production) + authentication: + methods: + token: + enabled: false + + ## Service configuration + service: + type: ClusterIP + httpPort: 8080 + grpcPort: 9000 + annotations: {} + + ## Ingress configuration + ingress: + enabled: false + className: "" + annotations: {} + # cert-manager.io/cluster-issuer: letsencrypt-prod + # nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: flipt.example.com + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: flipt-tls + # hosts: + # - flipt.example.com + + ## Horizontal Pod Autoscaler + autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + + ## Pod Disruption Budget (recommended for HA) + podDisruptionBudget: + enabled: true + minAvailable: 1 + + ## ServiceMonitor for Prometheus metrics + serviceMonitor: + enabled: false + interval: 30s + +## PostgreSQL Database Configuration +postgresql: + ## Use embedded PostgreSQL (CloudnativePG) or external database + type: embedded # 'embedded' or 'external' + + ## Embedded PostgreSQL using CloudnativePG operator + embedded: + enabled: true + ## Database cluster configuration + cluster: + instances: 1 # 3 recommended for production HA + storage: + size: 10Gi + storageClass: "" # Uses default storage class + ## PostgreSQL version + imageName: ghcr.io/cloudnative-pg/postgresql:16 + ## Resource limits + resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi + ## Enable continuous backup (optional) + backup: + enabled: false + # Configure S3 or similar for WAL archiving + # See CloudnativePG docs for details + ## Enable monitoring (optional) + monitoring: + enabled: false + + ## External PostgreSQL connection details + external: + enabled: false + host: postgresql.example.com + port: 5432 + database: flipt + username: flipt + password: "" # Set via secret or KOTS config + sslMode: require + + ## Database initialization + database: flipt + username: flipt + password: "" # Auto-generated if empty (for embedded) + +## Redis Cache Configuration +redis: + enabled: true + + ## Override image to use working Bitnami tag + image: + registry: docker.io + repository: bitnami/redis + tag: latest + + ## Redis architecture: standalone or replication + architecture: standalone # 'replication' for primary/replica setup + + ## Redis authentication + auth: + enabled: true + password: "" # Auto-generated if empty + + ## Redis Master configuration + master: + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + persistence: + enabled: true + size: 5Gi + storageClass: "" # Uses default storage class + + ## Redis Replica configuration (if architecture: replication) + replica: + replicaCount: 1 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + persistence: + enabled: true + size: 5Gi + + ## Redis metrics for monitoring + metrics: + enabled: false + serviceMonitor: + enabled: false + +## Replicated SDK Integration +replicated: + enabled: true + + ## Integration configuration + integration: + enabled: true + + ## Custom license fields (optional) + ## licenseFields: {} + +## Additional Kubernetes Resources + +## ConfigMap for additional Flipt configuration +extraConfigMap: {} + +## Secrets for sensitive configuration +extraSecrets: {} + +## Pod annotations +podAnnotations: {} + +## Pod labels +podLabels: {} + +## Security context +securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + +## Node selector +nodeSelector: {} + +## Tolerations +tolerations: [] + +## Affinity rules +affinity: {} + ## Example anti-affinity to spread pods across nodes + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 100 + # podAffinityTerm: + # labelSelector: + # matchExpressions: + # - key: app.kubernetes.io/name + # operator: In + # values: + # - flipt + # topologyKey: kubernetes.io/hostname diff --git a/applications/flipt/replicated/ec-cluster.yaml b/applications/flipt/replicated/ec-cluster.yaml new file mode 100644 index 00000000..ef4123d0 --- /dev/null +++ b/applications/flipt/replicated/ec-cluster.yaml @@ -0,0 +1,13 @@ +apiVersion: embeddedcluster.replicated.com/v1beta1 +kind: Config +spec: + version: "3.0.0-alpha-8+k8s-1.34" + unsupportedOverrides: + k0s: |- + config: + spec: + workerProfiles: + - name: default + values: + allowedUnsafeSysctls: + - net.ipv4.ip_forward \ No newline at end of file diff --git a/applications/flipt/replicated/kots-app.yaml b/applications/flipt/replicated/kots-app.yaml index 40164370..1851cf7e 100644 --- a/applications/flipt/replicated/kots-app.yaml +++ b/applications/flipt/replicated/kots-app.yaml @@ -44,4 +44,5 @@ spec: additionalImages: - docker.flipt.io/flipt/flipt:v1.61.0 - ghcr.io/cloudnative-pg/postgresql:16 - - docker.io/bitnami/redis:7.2.4 + - docker.io/bitnami/redis:latest + - proxy.replicated.com/library/replicated-sdk-image:1.15.0 diff --git a/applications/flipt/scripts/install-cnpg-operator.sh b/applications/flipt/scripts/install-cnpg-operator.sh deleted file mode 100755 index 0435350b..00000000 --- a/applications/flipt/scripts/install-cnpg-operator.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -set -e - -echo "Installing CloudNativePG operator..." - -# Add CNPG Helm repository -helm repo add cnpg https://cloudnative-pg.github.io/charts -helm repo update - -# Install the operator in its own namespace -helm upgrade --install cnpg \ - --namespace cnpg-system \ - --create-namespace \ - cnpg/cloudnative-pg - -echo "Waiting for operator to be ready..." -kubectl wait --for=condition=available --timeout=300s \ - deployment/cnpg-cloudnative-pg \ - -n cnpg-system - -echo "✓ CloudNativePG operator installed successfully!" -echo "" -echo "You can now install Flipt:" -echo " make install" diff --git a/applications/flipt/scripts/install.sh b/applications/flipt/scripts/install.sh deleted file mode 100755 index 9e216ca1..00000000 --- a/applications/flipt/scripts/install.sh +++ /dev/null @@ -1,155 +0,0 @@ -#!/bin/bash -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -NAMESPACE="${NAMESPACE:-flipt}" -RELEASE_NAME="${RELEASE_NAME:-flipt}" - -# Check for Replicated license -if [ -f ".replicated/license.env" ]; then - echo -e "${BLUE}Loading Replicated license from .replicated/license.env${NC}" - source .replicated/license.env -fi - -if [ -z "$REPLICATED_LICENSE_ID" ]; then - echo -e "${RED}=================================================${NC}" - echo -e "${RED} Replicated License Required${NC}" - echo -e "${RED}=================================================${NC}" - echo "" - echo -e "${YELLOW}This application requires a Replicated development license.${NC}" - echo "" - echo "To set up a development license:" - echo "" - echo " 1. Run the license setup script:" - echo -e " ${YELLOW}./scripts/setup-dev-license.sh${NC}" - echo "" - echo " 2. Load the license:" - echo -e " ${YELLOW}source .replicated/license.env${NC}" - echo "" - echo " 3. Re-run this installation:" - echo -e " ${YELLOW}./scripts/install.sh${NC}" - echo "" - echo "Or set it manually:" - echo -e " ${YELLOW}export REPLICATED_LICENSE_ID=your-license-id${NC}" - echo "" - exit 1 -fi - -echo -e "${GREEN}✓ Replicated license configured: $REPLICATED_LICENSE_ID${NC}" -echo "" - -echo -e "${BLUE}=================================================${NC}" -echo -e "${BLUE} Flipt Feature Flags Installation${NC}" -echo -e "${BLUE}=================================================${NC}" -echo "" - -# Step 1: Check prerequisites -echo -e "${YELLOW}[1/7] Checking prerequisites...${NC}" -command -v kubectl >/dev/null 2>&1 || { echo -e "${RED}✗ kubectl is required but not installed${NC}"; exit 1; } -command -v helm >/dev/null 2>&1 || { echo -e "${RED}✗ helm is required but not installed${NC}"; exit 1; } -echo -e "${GREEN}✓ Prerequisites satisfied${NC}" -echo "" - -# Step 2: Check if operator is installed -echo -e "${YELLOW}[2/7] Checking CloudNativePG operator...${NC}" -if kubectl get deployment cnpg-cloudnative-pg -n cnpg-system >/dev/null 2>&1; then - echo -e "${GREEN}✓ CloudNativePG operator already installed${NC}" -else - echo -e "${YELLOW}⚠ CloudNativePG operator not found. Installing...${NC}" - - # Add CNPG repo - helm repo add cnpg https://cloudnative-pg.github.io/charts >/dev/null 2>&1 || true - helm repo update >/dev/null 2>&1 - - # Install operator - helm upgrade --install cnpg \ - --namespace cnpg-system \ - --create-namespace \ - cnpg/cloudnative-pg - - # Wait for operator to be ready - echo -e "${YELLOW} Waiting for operator to be ready...${NC}" - kubectl wait --for=condition=available --timeout=300s \ - deployment/cnpg-cloudnative-pg \ - -n cnpg-system 2>/dev/null || true - - echo -e "${GREEN}✓ Operator installed successfully${NC}" -fi -echo "" - -# Step 3: Add Helm repositories -echo -e "${YELLOW}[3/7] Adding Helm repositories...${NC}" -helm repo add flipt https://helm.flipt.io >/dev/null 2>&1 || true -helm repo add bitnami https://charts.bitnami.com/bitnami >/dev/null 2>&1 || true -helm repo add replicated https://charts.replicated.com >/dev/null 2>&1 || true -helm repo update >/dev/null 2>&1 -echo -e "${GREEN}✓ Repositories added${NC}" -echo "" - -# Step 4: Clean and update dependencies -echo -e "${YELLOW}[4/7] Updating chart dependencies...${NC}" -cd chart - -# Remove any cached cloudnative-pg dependency -rm -f charts/cloudnative-pg-*.tgz 2>/dev/null || true -rm -f Chart.lock 2>/dev/null || true - -# Update dependencies -helm dependency update -cd .. -echo -e "${GREEN}✓ Dependencies updated${NC}" -echo "" - -# Step 5: Configure Replicated SDK -echo -e "${YELLOW}[5/7] Configuring Replicated SDK...${NC}" -kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f - -kubectl create secret generic replicated-license \ - --from-literal=license="$REPLICATED_LICENSE_ID" \ - --namespace "${NAMESPACE}" \ - --dry-run=client -o yaml | kubectl apply -f - -echo -e "${GREEN}✓ Replicated SDK configured${NC}" -echo "" - -# Step 6: Install Flipt -echo -e "${YELLOW}[6/7] Installing Flipt...${NC}" -helm upgrade --install "${RELEASE_NAME}" ./chart \ - --namespace "${NAMESPACE}" \ - --create-namespace \ - --wait \ - --timeout 10m \ - "$@" - -echo -e "${GREEN}✓ Flipt installed successfully${NC}" -echo "" - -# Step 7: Show status -echo -e "${YELLOW}[7/7] Checking deployment status...${NC}" -kubectl get pods -n "${NAMESPACE}" -echo "" - -# Show next steps -echo -e "${GREEN}=================================================${NC}" -echo -e "${GREEN} Installation Complete!${NC}" -echo -e "${GREEN}=================================================${NC}" -echo "" -echo -e "${BLUE}Access Flipt:${NC}" -echo "" -echo -e " 1. Port-forward to the service:" -echo -e " ${YELLOW}kubectl port-forward -n ${NAMESPACE} svc/${RELEASE_NAME}-flipt 8080:8080${NC}" -echo "" -echo -e " 2. Open your browser:" -echo -e " ${YELLOW}http://localhost:8080${NC}" -echo "" -echo -e "${BLUE}Useful commands:${NC}" -echo "" -echo -e " View pods: ${YELLOW}kubectl get pods -n ${NAMESPACE}${NC}" -echo -e " View logs: ${YELLOW}kubectl logs -l app.kubernetes.io/name=flipt -n ${NAMESPACE} -f${NC}" -echo -e " View database: ${YELLOW}kubectl get cluster -n ${NAMESPACE}${NC}" -echo -e " Uninstall: ${YELLOW}helm uninstall ${RELEASE_NAME} -n ${NAMESPACE}${NC}" -echo "" diff --git a/applications/flipt/scripts/setup-dev-license.sh b/applications/flipt/scripts/setup-dev-license.sh deleted file mode 100755 index e2a0e8da..00000000 --- a/applications/flipt/scripts/setup-dev-license.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/bin/bash -set -e - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -echo -e "${BLUE}=================================================${NC}" -echo -e "${BLUE} Replicated Development License Setup${NC}" -echo -e "${BLUE}=================================================${NC}" -echo "" - -# Check prerequisites -echo -e "${YELLOW}[1/4] Checking prerequisites...${NC}" -command -v replicated >/dev/null 2>&1 || { - echo -e "${RED}✗ Replicated CLI is required${NC}" - echo "" - echo "Install it with:" - echo " brew install replicatedhq/replicated/cli" - echo " # or" - echo " curl -s https://api.github.com/repos/replicatedhq/replicated/releases/latest | grep \"browser_download_url.*$(uname -s)_$(uname -m)\" | cut -d '\"' -f 4 | xargs curl -L -o replicated && chmod +x replicated && sudo mv replicated /usr/local/bin/" - exit 1 -} -echo -e "${GREEN}✓ Replicated CLI installed${NC}" -echo "" - -# Check API token -echo -e "${YELLOW}[2/4] Checking Replicated API token...${NC}" -if [ -z "$REPLICATED_API_TOKEN" ]; then - echo -e "${YELLOW}⚠ REPLICATED_API_TOKEN not set${NC}" - echo "" - echo "To obtain an API token:" - echo " 1. Log in to vendor.replicated.com" - echo " 2. Go to Settings > Service Accounts" - echo " 3. Create a new token" - echo " 4. Export it: export REPLICATED_API_TOKEN=your-token" - echo "" - read -p "Enter your Replicated API token: " api_token - export REPLICATED_API_TOKEN="$api_token" -fi -echo -e "${GREEN}✓ API token configured${NC}" -echo "" - -# Set up application -APP_SLUG="${REPLICATED_APP_SLUG:-flipt}" -CUSTOMER_NAME="${CUSTOMER_NAME:-dev-$(whoami)-$(date +%s)}" -CHANNEL="${CHANNEL:-Unstable}" - -echo -e "${YELLOW}[3/4] Creating development customer...${NC}" -echo " App: $APP_SLUG" -echo " Customer: $CUSTOMER_NAME" -echo " Channel: $CHANNEL" -echo "" - -# Create customer with dev license -replicated customer create \ - --app "$APP_SLUG" \ - --name "$CUSTOMER_NAME" \ - --channel "$CHANNEL" \ - --license-type dev \ - --output json > /tmp/customer.json 2>&1 || { - echo -e "${RED}✗ Failed to create customer${NC}" - cat /tmp/customer.json - exit 1 -} - -LICENSE_ID=$(jq -r '.id' /tmp/customer.json) -echo -e "${GREEN}✓ Customer created${NC}" -echo "" - -# Save license ID -echo -e "${YELLOW}[4/4] Saving license configuration...${NC}" -mkdir -p .replicated -echo "REPLICATED_LICENSE_ID=$LICENSE_ID" > .replicated/license.env -echo "REPLICATED_APP_SLUG=$APP_SLUG" >> .replicated/license.env -echo "CUSTOMER_NAME=$CUSTOMER_NAME" >> .replicated/license.env -echo "CHANNEL=$CHANNEL" >> .replicated/license.env - -cat > .replicated/README.md < Date: Tue, 10 Feb 2026 11:54:48 +1300 Subject: [PATCH 03/12] Revert changes made to wg-easy versions --- applications/wg-easy/Taskfile.yaml | 4 ++-- applications/wg-easy/replicated/cluster.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/wg-easy/Taskfile.yaml b/applications/wg-easy/Taskfile.yaml index 49a0aca4..b5f26172 100644 --- a/applications/wg-easy/Taskfile.yaml +++ b/applications/wg-easy/Taskfile.yaml @@ -11,7 +11,7 @@ vars: # Release configuration RELEASE_CHANNEL: '{{.RELEASE_CHANNEL | default "Unstable"}}' RELEASE_CHANNEL_ID: '{{.RELEASE_CHANNEL_ID}}' - RELEASE_VERSION: '{{.RELEASE_VERSION | default "0.0.2"}}' + RELEASE_VERSION: '{{.RELEASE_VERSION | default "0.0.1"}}' RELEASE_NOTES: '{{.RELEASE_NOTES | default "Release created via task release-create"}}' REPLICATED_LICENSE_ID: '{{.REPLICATED_LICENSE_ID}}' @@ -415,7 +415,7 @@ tasks: vars: RELEASE_CHANNEL: '{{.RELEASE_CHANNEL | default "Unstable"}}' RELEASE_CHANNEL_ID: '{{.RELEASE_CHANNEL_ID}}' - RELEASE_VERSION: '{{.RELEASE_VERSION | default "0.0.2"}}' + RELEASE_VERSION: '{{.RELEASE_VERSION | default "0.0.1"}}' RELEASE_NOTES: '{{.RELEASE_NOTES | default "Release created via task release-create"}}' # Use channel ID if provided, otherwise fall back to channel name CHANNEL_TARGET: '{{if .RELEASE_CHANNEL_ID}}{{.RELEASE_CHANNEL_ID}}{{else}}{{.RELEASE_CHANNEL}}{{end}}' diff --git a/applications/wg-easy/replicated/cluster.yaml b/applications/wg-easy/replicated/cluster.yaml index f772ab64..7a3deb7b 100644 --- a/applications/wg-easy/replicated/cluster.yaml +++ b/applications/wg-easy/replicated/cluster.yaml @@ -1,7 +1,7 @@ apiVersion: embeddedcluster.replicated.com/v1beta1 kind: Config spec: - version: "3.0.0-alpha-8+k8s-1.34" + version: 2.1.3+k8s-1.29 unsupportedOverrides: k0s: |- config: From f908c014425d3f49bf4dcb169178fe5d9661cfb8 Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:22:44 +1300 Subject: [PATCH 04/12] Fix Replicated manifests and add K8s app yaml --- applications/flipt/chart/Chart.yaml | 2 +- .../templates/postgresql-cluster-job.yaml | 3 +- applications/flipt/chart/values.yaml | 8 + applications/flipt/chart/values.yaml.backup | 275 ------------------ applications/flipt/replicated/ec-cluster.yaml | 29 +- applications/flipt/replicated/k8s-app.yaml | 36 +++ applications/flipt/replicated/kots-app.yaml | 8 +- .../flipt/replicated/kots-config.yaml | 8 +- .../flipt/replicated/kots-helm-chart.yaml | 270 +++++++++++------ .../flipt/replicated/kots-tls-secret.yaml | 18 ++ 10 files changed, 281 insertions(+), 376 deletions(-) delete mode 100644 applications/flipt/chart/values.yaml.backup create mode 100644 applications/flipt/replicated/k8s-app.yaml create mode 100644 applications/flipt/replicated/kots-tls-secret.yaml diff --git a/applications/flipt/chart/Chart.yaml b/applications/flipt/chart/Chart.yaml index bb5bce58..3feeb965 100644 --- a/applications/flipt/chart/Chart.yaml +++ b/applications/flipt/chart/Chart.yaml @@ -5,7 +5,7 @@ description: | This enterprise-ready deployment includes PostgreSQL for durable storage and Redis for distributed caching across multiple instances. type: application -version: 1.0.3 +version: 1.0.4 appVersion: "1.61.0" keywords: - feature-flags diff --git a/applications/flipt/chart/templates/postgresql-cluster-job.yaml b/applications/flipt/chart/templates/postgresql-cluster-job.yaml index 4048d9e2..edbfe6b8 100644 --- a/applications/flipt/chart/templates/postgresql-cluster-job.yaml +++ b/applications/flipt/chart/templates/postgresql-cluster-job.yaml @@ -145,7 +145,8 @@ spec: restartPolicy: OnFailure containers: - name: create-cluster - image: bitnami/kubectl:latest + image: "{{ .Values.dbJob.image.registry }}/{{ .Values.dbJob.image.repository }}:{{ .Values.dbJob.image.tag }}" + imagePullPolicy: {{ .Values.dbJob.image.pullPolicy }} command: - /bin/bash - -c diff --git a/applications/flipt/chart/values.yaml b/applications/flipt/chart/values.yaml index 6078fc34..4b025a09 100644 --- a/applications/flipt/chart/values.yaml +++ b/applications/flipt/chart/values.yaml @@ -197,6 +197,14 @@ tolerations: [] ## Affinity rules affinity: {} +## Database Job Configuration (for PostgreSQL cluster creation) +dbJob: + image: + registry: docker.io + repository: bitnami/kubectl + tag: latest + pullPolicy: IfNotPresent + ## PostgreSQL Database Configuration postgresql: ## Use embedded PostgreSQL (CloudnativePG) or external database diff --git a/applications/flipt/chart/values.yaml.backup b/applications/flipt/chart/values.yaml.backup deleted file mode 100644 index 933c8442..00000000 --- a/applications/flipt/chart/values.yaml.backup +++ /dev/null @@ -1,275 +0,0 @@ -## Flipt Feature Flag Platform Configuration -## This values file provides sensible defaults for deploying Flipt with PostgreSQL and Redis - -global: - ## Global image registry override - ## Useful for air-gap deployments or custom registries - imageRegistry: "" - -## CloudNativePG Operator Configuration -## Set to false if operator is already installed cluster-wide -cloudnative-pg: - enabled: true - -## Flipt application configuration -flipt: - enabled: true - - image: - ## Use global registry if set, otherwise default - registry: docker.flipt.io - repository: flipt/flipt - tag: "" # Uses appVersion from Chart.yaml - pullPolicy: IfNotPresent - - ## Number of Flipt replicas (2+ recommended for HA with Redis cache) - replicaCount: 2 - - ## Resource limits for Flipt pods - resources: - limits: - cpu: 500m - memory: 512Mi - requests: - cpu: 100m - memory: 128Mi - - ## Flipt configuration - ## See https://docs.flipt.io/configuration/overview for all options - config: - ## Logging configuration - log: - level: info - encoding: json - - ## Server configuration - server: - protocol: http - host: 0.0.0.0 - httpPort: 8080 - grpcPort: 9000 - - ## Database configuration (templated from values below) - db: - url: "" # Will be set via template based on postgresql configuration - maxIdleConn: 10 - maxOpenConn: 50 - connMaxLifetime: 1h - - ## Cache configuration (templated from redis configuration) - cache: - enabled: true - backend: redis # or 'memory' for single instance - ttl: 5m - redis: - url: "" # Will be set via template based on redis configuration - mode: single # or 'cluster' for Redis cluster - prefix: "flipt" - - ## CORS configuration for web UI - cors: - enabled: true - allowedOrigins: - - "*" - - ## Authentication (optional, configure for production) - authentication: - methods: - token: - enabled: false - - ## Service configuration - service: - type: ClusterIP - httpPort: 8080 - grpcPort: 9000 - annotations: {} - - ## Ingress configuration - ingress: - enabled: false - className: "" - annotations: {} - # cert-manager.io/cluster-issuer: letsencrypt-prod - # nginx.ingress.kubernetes.io/ssl-redirect: "true" - hosts: - - host: flipt.example.com - paths: - - path: / - pathType: Prefix - tls: [] - # - secretName: flipt-tls - # hosts: - # - flipt.example.com - - ## Horizontal Pod Autoscaler - autoscaling: - enabled: false - minReplicas: 2 - maxReplicas: 10 - targetCPUUtilizationPercentage: 80 - targetMemoryUtilizationPercentage: 80 - - ## Pod Disruption Budget (recommended for HA) - podDisruptionBudget: - enabled: true - minAvailable: 1 - - ## ServiceMonitor for Prometheus metrics - serviceMonitor: - enabled: false - interval: 30s - -## PostgreSQL Database Configuration -postgresql: - ## Use embedded PostgreSQL (CloudnativePG) or external database - type: embedded # 'embedded' or 'external' - - ## Embedded PostgreSQL using CloudnativePG operator - embedded: - enabled: true - ## Database cluster configuration - cluster: - instances: 1 # 3 recommended for production HA - storage: - size: 10Gi - storageClass: "" # Uses default storage class - ## PostgreSQL version - imageName: ghcr.io/cloudnative-pg/postgresql:16 - ## Resource limits - resources: - limits: - cpu: 1000m - memory: 1Gi - requests: - cpu: 100m - memory: 256Mi - ## Enable continuous backup (optional) - backup: - enabled: false - # Configure S3 or similar for WAL archiving - # See CloudnativePG docs for details - ## Enable monitoring (optional) - monitoring: - enabled: false - - ## External PostgreSQL connection details - external: - enabled: false - host: postgresql.example.com - port: 5432 - database: flipt - username: flipt - password: "" # Set via secret or KOTS config - sslMode: require - - ## Database initialization - database: flipt - username: flipt - password: "" # Auto-generated if empty (for embedded) - -## Redis Cache Configuration -redis: - enabled: true - - ## Override image to use working Bitnami tag - image: - registry: docker.io - repository: bitnami/redis - tag: latest - - ## Redis architecture: standalone or replication - architecture: standalone # 'replication' for primary/replica setup - - ## Redis authentication - auth: - enabled: true - password: "" # Auto-generated if empty - - ## Redis Master configuration - master: - resources: - limits: - cpu: 500m - memory: 512Mi - requests: - cpu: 100m - memory: 128Mi - persistence: - enabled: true - size: 5Gi - storageClass: "" # Uses default storage class - - ## Redis Replica configuration (if architecture: replication) - replica: - replicaCount: 1 - resources: - limits: - cpu: 500m - memory: 512Mi - requests: - cpu: 100m - memory: 128Mi - persistence: - enabled: true - size: 5Gi - - ## Redis metrics for monitoring - metrics: - enabled: false - serviceMonitor: - enabled: false - -## Replicated SDK Integration -replicated: - enabled: true - - ## Integration configuration - integration: - enabled: true - - ## Custom license fields (optional) - ## licenseFields: {} - -## Additional Kubernetes Resources - -## ConfigMap for additional Flipt configuration -extraConfigMap: {} - -## Secrets for sensitive configuration -extraSecrets: {} - -## Pod annotations -podAnnotations: {} - -## Pod labels -podLabels: {} - -## Security context -securityContext: - runAsNonRoot: true - runAsUser: 1000 - fsGroup: 1000 - seccompProfile: - type: RuntimeDefault - -## Node selector -nodeSelector: {} - -## Tolerations -tolerations: [] - -## Affinity rules -affinity: {} - ## Example anti-affinity to spread pods across nodes - # podAntiAffinity: - # preferredDuringSchedulingIgnoredDuringExecution: - # - weight: 100 - # podAffinityTerm: - # labelSelector: - # matchExpressions: - # - key: app.kubernetes.io/name - # operator: In - # values: - # - flipt - # topologyKey: kubernetes.io/hostname diff --git a/applications/flipt/replicated/ec-cluster.yaml b/applications/flipt/replicated/ec-cluster.yaml index ef4123d0..b9f8de77 100644 --- a/applications/flipt/replicated/ec-cluster.yaml +++ b/applications/flipt/replicated/ec-cluster.yaml @@ -1,7 +1,34 @@ apiVersion: embeddedcluster.replicated.com/v1beta1 kind: Config spec: - version: "3.0.0-alpha-8+k8s-1.34" + version: "2.13.3+k8s-1.33" + extensions: + helm: + repositories: + - name: ingress-nginx + url: https://kubernetes.github.io/ingress-nginx + charts: + - name: ingress-nginx + chartname: ingress-nginx/ingress-nginx + namespace: ingress-nginx + version: "4.8.3" + values: | + controller: + service: + type: NodePort + nodePorts: + http: "80" + https: "443" + # Known issue: Only use image tags for multi-architecture images. + # Set digest to empty string to ensure the air gap builder uses + # single-architecture images. + image: + digest: "" + digestChroot: "" + admissionWebhooks: + patch: + image: + digest: "" unsupportedOverrides: k0s: |- config: diff --git a/applications/flipt/replicated/k8s-app.yaml b/applications/flipt/replicated/k8s-app.yaml new file mode 100644 index 00000000..73d2b794 --- /dev/null +++ b/applications/flipt/replicated/k8s-app.yaml @@ -0,0 +1,36 @@ +--- +# Kubernetes SIG Application Custom Resource +# Adds links/buttons to the Admin Console dashboard +# See: https://docs.replicated.com/vendor/admin-console-adding-buttons-links +apiVersion: app.k8s.io/v1beta1 +kind: Application +metadata: + name: flipt + labels: + app.kubernetes.io/name: flipt +spec: + descriptor: + type: "Feature Flag Platform" + version: "v1.61.0" + description: "Flipt is an open-source, self-hosted feature flag platform" + keywords: + - feature-flags + - feature-toggles + - experimentation + - ab-testing + links: + # Open App button - uses ingress hostname if configured + - description: Open Flipt + url: 'repl{{ if ConfigOptionEquals "ingress_enabled" "1" }}https://repl{{ ConfigOption "ingress_hostname" }}repl{{ else }}http://localhost:8080repl{{ end }}' + # Flipt Documentation + - description: Documentation + url: "https://docs.flipt.io" + # Flipt API Docs (built-in) + - description: API Reference + url: 'repl{{ if ConfigOptionEquals "ingress_enabled" "1" }}https://repl{{ ConfigOption "ingress_hostname" }}/docs/repl{{ else }}http://localhost:8080/docs/repl{{ end }}' + maintainers: + - name: Flipt + url: https://flipt.io + selector: + matchLabels: + app.kubernetes.io/name: flipt diff --git a/applications/flipt/replicated/kots-app.yaml b/applications/flipt/replicated/kots-app.yaml index 1851cf7e..bad26434 100644 --- a/applications/flipt/replicated/kots-app.yaml +++ b/applications/flipt/replicated/kots-app.yaml @@ -7,6 +7,7 @@ spec: icon: https://raw.githubusercontent.com/flipt-io/flipt/main/ui/public/logo.svg statusInformers: - deployment/flipt + - deployment/flipt-cloudnative-pg - statefulset/redis ports: - serviceName: flipt @@ -42,7 +43,10 @@ spec: For more details, visit: https://github.com/flipt-io/flipt/releases additionalImages: - - docker.flipt.io/flipt/flipt:v1.61.0 + - ghcr.io/flipt-io/flipt:v1.61.0 + - ghcr.io/cloudnative-pg/cloudnative-pg:1.25.0 - ghcr.io/cloudnative-pg/postgresql:16 - - docker.io/bitnami/redis:latest + - docker.io/bitnami/kubectl:latest + - docker.io/library/redis:latest - proxy.replicated.com/library/replicated-sdk-image:1.15.0 + allowRollback: true diff --git a/applications/flipt/replicated/kots-config.yaml b/applications/flipt/replicated/kots-config.yaml index 809da165..c069b6a5 100644 --- a/applications/flipt/replicated/kots-config.yaml +++ b/applications/flipt/replicated/kots-config.yaml @@ -78,16 +78,14 @@ spec: - name: tls_cert_source title: TLS Certificate Source type: select_one - default: self_signed + default: cert_manager items: - - name: self_signed - title: Self-Signed (Auto-Generated) - name: cert_manager - title: Cert-Manager + title: Cert-Manager (Automatic) - name: custom title: Upload Custom Certificate when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' - help_text: Source of TLS certificate + help_text: How TLS certificates are managed - name: tls_cert_manager_issuer title: Cert-Manager Issuer diff --git a/applications/flipt/replicated/kots-helm-chart.yaml b/applications/flipt/replicated/kots-helm-chart.yaml index 2edca9dc..d3400105 100644 --- a/applications/flipt/replicated/kots-helm-chart.yaml +++ b/applications/flipt/replicated/kots-helm-chart.yaml @@ -5,92 +5,104 @@ metadata: spec: chart: name: flipt - chartVersion: 1.0.0 - releaseName: 'repl{{ ConfigOption "release_name" }}' + chartVersion: 1.0.4 + + # releaseName is a top-level field in v2 (not under chart) + releaseName: 'repl{{ ConfigOption "release_name" }}' exclude: "" - helmVersion: v3 + # Weight determines install order (lower = first) + weight: 1 - useHelmInstall: true + # helmUpgradeFlags specifies additional flags for helm upgrade + helmUpgradeFlags: + - --timeout + - 15m + - --wait + - --wait-for-jobs + - --history-max=15 namespace: 'repl{{ ConfigOption "namespace" }}' + # Values for customer environment (uses template functions) values: global: imageRegistry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "" }}' - flipt: - enabled: true - - image: - registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "docker.flipt.io" }}' - repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "flipt" }}/flipt' - pullPolicy: IfNotPresent - - replicaCount: 'repl{{ ConfigOption "replica_count" }}' - - resources: - requests: - cpu: 'repl{{ ConfigOption "flipt_cpu_request" }}' - memory: 'repl{{ ConfigOption "flipt_memory_request" }}' - limits: - cpu: 'repl{{ ConfigOption "flipt_cpu_limit" }}' - memory: 'repl{{ ConfigOption "flipt_memory_limit" }}' - - config: - log: - level: 'repl{{ ConfigOption "log_level" }}' - encoding: 'repl{{ ConfigOption "log_encoding" }}' - - server: - protocol: http - host: 0.0.0.0 - httpPort: 8080 - grpcPort: 9000 - - db: - maxIdleConn: 10 - maxOpenConn: 50 - connMaxLifetime: 1h - - cache: - enabled: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' - backend: 'repl{{ ConfigOptionEquals "redis_enabled" "1" | ternary "redis" "memory" }}' - ttl: 5m - redis: - mode: single - prefix: "flipt" - - cors: - enabled: true - allowedOrigins: - - "*" - - service: - type: ClusterIP + replicaCount: 'repl{{ ConfigOption "replica_count" | ParseInt }}' + + image: + registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "ghcr.io" }}' + repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "flipt-io" }}/flipt' + tag: "v1.61.0" + pullPolicy: IfNotPresent + + resources: + requests: + cpu: 'repl{{ ConfigOption "flipt_cpu_request" }}' + memory: 'repl{{ ConfigOption "flipt_memory_request" }}' + limits: + cpu: 'repl{{ ConfigOption "flipt_cpu_limit" }}' + memory: 'repl{{ ConfigOption "flipt_memory_limit" }}' + + config: + log: + level: 'repl{{ ConfigOption "log_level" }}' + encoding: 'repl{{ ConfigOption "log_encoding" }}' + + server: + protocol: http + host: 0.0.0.0 httpPort: 8080 grpcPort: 9000 - autoscaling: - enabled: 'repl{{ ConfigOptionEquals "enable_autoscaling" "1" }}' - minReplicas: 'repl{{ ConfigOption "hpa_min_replicas" }}' - maxReplicas: 'repl{{ ConfigOption "hpa_max_replicas" }}' - targetCPUUtilizationPercentage: 'repl{{ ConfigOption "hpa_cpu_target" }}' + db: + maxIdleConn: 10 + maxOpenConn: 50 + connMaxLifetime: 1h - podDisruptionBudget: + cache: + enabled: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + backend: 'repl{{ ConfigOptionEquals "redis_enabled" "1" | ternary "redis" "memory" }}' + ttl: 5m + redis: + mode: single + prefix: "flipt" + + cors: enabled: true - minAvailable: 1 + allowedOrigins: + - "*" - serviceMonitor: - enabled: 'repl{{ ConfigOptionEquals "enable_metrics" "1" }}' + service: + type: ClusterIP + httpPort: 8080 + grpcPort: 9000 + + autoscaling: + enabled: 'repl{{ ConfigOptionEquals "enable_autoscaling" "1" }}' + minReplicas: 'repl{{ ConfigOption "hpa_min_replicas" | ParseInt }}' + maxReplicas: 'repl{{ ConfigOption "hpa_max_replicas" | ParseInt }}' + targetCPUUtilizationPercentage: 'repl{{ ConfigOption "hpa_cpu_target" | ParseInt }}' + + podDisruptionBudget: + enabled: true + minAvailable: 1 + serviceMonitor: + enabled: 'repl{{ ConfigOptionEquals "enable_metrics" "1" }}' + + # CloudNativePG operator (subchart) + cloudnative-pg: + enabled: true + + # PostgreSQL configuration postgresql: type: 'repl{{ ConfigOption "postgres_type" }}' database: flipt username: flipt - password: 'repl{{ RandomString 32 }}' embedded: enabled: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' @@ -112,6 +124,7 @@ spec: monitoring: enabled: false + # Redis configuration redis: enabled: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' @@ -119,11 +132,11 @@ spec: auth: enabled: true - password: 'repl{{ if ConfigOption "redis_password" }}repl{{ ConfigOption "redis_password" }}repl{{ else }}repl{{ RandomString 32 }}repl{{ end }}' image: registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "docker.io" }}' - repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "bitnami" }}/redis' + repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "library" }}/redis' + tag: latest master: persistence: @@ -154,47 +167,66 @@ spec: metrics: enabled: 'repl{{ ConfigOptionEquals "enable_metrics" "1" }}' + # Database creation Job image + dbJob: + image: + registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "docker.io" }}' + repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "bitnami" }}/kubectl' + tag: latest + + # Replicated SDK replicated: enabled: true integration: enabled: true + # Optional values applied conditionally optionalValues: + # Rewrite Replicated SDK image for local registry + - when: 'repl{{ HasLocalRegistry }}' + recursiveMerge: true + values: + replicated: + image: + registry: 'repl{{ LocalRegistryHost }}' + repository: 'repl{{ LocalRegistryNamespace }}/replicated-sdk-image' + + # Ingress configuration - when: 'repl{{ ConfigOptionEquals "ingress_enabled" "1" }}' recursiveMerge: false values: - flipt: - ingress: - enabled: true - className: 'repl{{ if ConfigOptionEquals "ingress_class" "custom" }}repl{{ ConfigOption "ingress_class_custom" }}repl{{ else }}repl{{ ConfigOption "ingress_class" }}repl{{ end }}' - hosts: - - host: 'repl{{ ConfigOption "ingress_hostname" }}' - paths: - - path: / - pathType: Prefix - + ingress: + enabled: true + className: 'repl{{ if ConfigOptionEquals "ingress_class" "custom" }}repl{{ ConfigOption "ingress_class_custom" }}repl{{ else }}repl{{ ConfigOption "ingress_class" }}repl{{ end }}' + hosts: + - host: 'repl{{ ConfigOption "ingress_hostname" }}' + paths: + - path: / + pathType: Prefix + + # TLS with cert-manager - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") (ConfigOptionEquals "tls_cert_source" "cert_manager") }}' recursiveMerge: false values: - flipt: - ingress: - annotations: - cert-manager.io/cluster-issuer: 'repl{{ ConfigOption "tls_cert_manager_issuer" }}' - tls: - - secretName: flipt-tls - hosts: - - 'repl{{ ConfigOption "ingress_hostname" }}' - + ingress: + annotations: + cert-manager.io/cluster-issuer: 'repl{{ ConfigOption "tls_cert_manager_issuer" }}' + tls: + - secretName: flipt-tls + hosts: + - 'repl{{ ConfigOption "ingress_hostname" }}' + + # TLS with custom certificate - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom") }}' recursiveMerge: false values: - flipt: - ingress: - tls: - - secretName: flipt-custom-tls - hosts: - - 'repl{{ ConfigOption "ingress_hostname" }}' + ingress: + tls: + - secretName: flipt-custom-tls + hosts: + - 'repl{{ ConfigOption "ingress_hostname" }}' + # External PostgreSQL - when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' recursiveMerge: false values: @@ -208,5 +240,61 @@ spec: password: 'repl{{ ConfigOption "external_postgres_password" }}' sslMode: 'repl{{ ConfigOption "external_postgres_sslmode" }}' + # Backup labels for KOTS snapshots + - when: "repl{{ LicenseFieldValue `isSnapshotSupported` }}" + recursiveMerge: true + values: + podLabels: + kots.io/backup: velero + podAnnotations: + kots.io/app-slug: repl{{ LicenseFieldValue "appSlug" }} + + # Builder values for air gap support + # These must be static/hardcoded to ensure all images are included builder: - {} + replicaCount: 2 + + # Flipt application image + image: + registry: ghcr.io + repository: flipt-io/flipt + tag: "v1.61.0" + + # CloudNativePG operator (subchart) + cloudnative-pg: + enabled: true + image: + repository: ghcr.io/cloudnative-pg/cloudnative-pg + tag: "1.25.0" + + # PostgreSQL configuration + postgresql: + type: embedded + embedded: + enabled: true + cluster: + instances: 1 + # PostgreSQL database image (managed by CNPG operator) + imageName: ghcr.io/cloudnative-pg/postgresql:16 + + # kubectl image used in database creation Job + dbJob: + image: + repository: bitnami/kubectl + tag: latest + + # Redis cache + redis: + enabled: true + image: + registry: docker.io + repository: library/redis + tag: latest + + # Replicated SDK + replicated: + enabled: true + image: + registry: proxy.replicated.com + repository: library/replicated-sdk-image + tag: "1.15.0" diff --git a/applications/flipt/replicated/kots-tls-secret.yaml b/applications/flipt/replicated/kots-tls-secret.yaml new file mode 100644 index 00000000..013c4d73 --- /dev/null +++ b/applications/flipt/replicated/kots-tls-secret.yaml @@ -0,0 +1,18 @@ +--- +# Custom TLS Secret for Ingress +# Only deployed when user selects "Upload Custom Certificate" for TLS +apiVersion: v1 +kind: Secret +metadata: + name: flipt-custom-tls + namespace: 'repl{{ ConfigOption "namespace" }}' + labels: + app.kubernetes.io/name: flipt + app.kubernetes.io/instance: 'repl{{ ConfigOption "release_name" }}' + annotations: + # Exclude this secret unless custom TLS is configured + kots.io/exclude: '{{repl not (and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom")) }}' +type: kubernetes.io/tls +data: + tls.crt: 'repl{{ ConfigOptionData "tls_cert" | Base64Encode }}' + tls.key: 'repl{{ ConfigOptionData "tls_key" | Base64Encode }}' From d639a17fb6f5aed07ec948e843e1b4589880f044 Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:22:48 +1300 Subject: [PATCH 05/12] Upgrade EC Nginx Ingress and move away from Bitnami images --- applications/flipt/chart/values.yaml | 6 +++--- applications/flipt/replicated/ec-cluster.yaml | 2 +- applications/flipt/replicated/kots-app.yaml | 2 +- applications/flipt/replicated/kots-helm-chart.yaml | 11 ++++++----- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/applications/flipt/chart/values.yaml b/applications/flipt/chart/values.yaml index 4b025a09..8ef44623 100644 --- a/applications/flipt/chart/values.yaml +++ b/applications/flipt/chart/values.yaml @@ -200,9 +200,9 @@ affinity: {} ## Database Job Configuration (for PostgreSQL cluster creation) dbJob: image: - registry: docker.io - repository: bitnami/kubectl - tag: latest + registry: registry.k8s.io + repository: kubectl + tag: "1.33.0" pullPolicy: IfNotPresent ## PostgreSQL Database Configuration diff --git a/applications/flipt/replicated/ec-cluster.yaml b/applications/flipt/replicated/ec-cluster.yaml index b9f8de77..316f71b3 100644 --- a/applications/flipt/replicated/ec-cluster.yaml +++ b/applications/flipt/replicated/ec-cluster.yaml @@ -11,7 +11,7 @@ spec: - name: ingress-nginx chartname: ingress-nginx/ingress-nginx namespace: ingress-nginx - version: "4.8.3" + version: "4.14.1" values: | controller: service: diff --git a/applications/flipt/replicated/kots-app.yaml b/applications/flipt/replicated/kots-app.yaml index bad26434..f4225ca5 100644 --- a/applications/flipt/replicated/kots-app.yaml +++ b/applications/flipt/replicated/kots-app.yaml @@ -46,7 +46,7 @@ spec: - ghcr.io/flipt-io/flipt:v1.61.0 - ghcr.io/cloudnative-pg/cloudnative-pg:1.25.0 - ghcr.io/cloudnative-pg/postgresql:16 - - docker.io/bitnami/kubectl:latest + - registry.k8s.io/kubectl:1.33.0 - docker.io/library/redis:latest - proxy.replicated.com/library/replicated-sdk-image:1.15.0 allowRollback: true diff --git a/applications/flipt/replicated/kots-helm-chart.yaml b/applications/flipt/replicated/kots-helm-chart.yaml index d3400105..f4cf3074 100644 --- a/applications/flipt/replicated/kots-helm-chart.yaml +++ b/applications/flipt/replicated/kots-helm-chart.yaml @@ -170,9 +170,9 @@ spec: # Database creation Job image dbJob: image: - registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "docker.io" }}' - repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "bitnami" }}/kubectl' - tag: latest + registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "registry.k8s.io" }}' + repository: 'repl{{ HasLocalRegistry | ternary (printf "%s/kubectl" LocalRegistryNamespace) "kubectl" }}' + tag: "1.33.0" # Replicated SDK replicated: @@ -280,8 +280,9 @@ spec: # kubectl image used in database creation Job dbJob: image: - repository: bitnami/kubectl - tag: latest + registry: registry.k8s.io + repository: kubectl + tag: "1.33.0" # Redis cache redis: From a7b4b4192aff3fe6a175d805c7468aa61d69ac47 Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:07:38 +1300 Subject: [PATCH 06/12] Automate release process via Make target --- applications/flipt/Makefile | 42 ++++++++++++++++++- applications/flipt/chart/Chart.yaml | 2 +- .../flipt/replicated/kots-helm-chart.yaml | 5 +-- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/applications/flipt/Makefile b/applications/flipt/Makefile index 3a0551f7..060819c6 100644 --- a/applications/flipt/Makefile +++ b/applications/flipt/Makefile @@ -1,4 +1,4 @@ -.PHONY: help lint package update-deps install uninstall upgrade test clean +.PHONY: help lint package update-deps install uninstall upgrade test clean release CHART_DIR := chart CHART_NAME := flipt @@ -6,6 +6,7 @@ NAMESPACE := flipt RELEASE_NAME := flipt RELEASE_DIR := release REPLICATED_DIR := replicated +CHART_VERSION := $(shell grep '^version:' $(CHART_DIR)/Chart.yaml | awk '{print $$2}') help: ## Display this help message @echo "Flipt Helm Chart Management" @@ -123,6 +124,45 @@ check-deps: ## Verify all required tools are installed @command -v kubectl >/dev/null 2>&1 || { echo "kubectl is not installed"; exit 1; } @echo "All dependencies satisfied!" +release: package ## Lint, package, update Replicated config, and create release + @# Clean previous chart archives from replicated dir + @rm -f $(REPLICATED_DIR)/$(CHART_NAME)-*.tgz + @# Copy packaged chart to replicated dir + @cp $(RELEASE_DIR)/$(CHART_NAME)-$(CHART_VERSION).tgz $(REPLICATED_DIR)/ + @# Update chartVersion in HelmChart config + @sed -i '' 's/chartVersion:.*/chartVersion: $(CHART_VERSION)/' $(REPLICATED_DIR)/kots-helm-chart.yaml + @echo "Chart $(CHART_NAME)-$(CHART_VERSION) packaged and staged in $(REPLICATED_DIR)/" + @echo "" + @# Determine next version by bumping previous release patch version + @PREV_VERSION=$$(replicated release ls 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1); \ + if [ -n "$$PREV_VERSION" ]; then \ + MAJOR=$$(echo $$PREV_VERSION | cut -d. -f1); \ + MINOR=$$(echo $$PREV_VERSION | cut -d. -f2); \ + PATCH=$$(echo $$PREV_VERSION | cut -d. -f3); \ + NEXT_VERSION="$$MAJOR.$$MINOR.$$((PATCH + 1))"; \ + echo "Previous release: $$PREV_VERSION"; \ + else \ + NEXT_VERSION="$(CHART_VERSION)"; \ + echo "No previous release found, defaulting to chart version"; \ + fi; \ + echo "Suggested version: $$NEXT_VERSION"; \ + echo ""; \ + read -p "Release version [$$NEXT_VERSION]: " INPUT_VERSION; \ + VERSION=$${INPUT_VERSION:-$$NEXT_VERSION}; \ + echo ""; \ + read -p "Release notes: " RELEASE_NOTES; \ + if [ -z "$$RELEASE_NOTES" ]; then \ + echo "Error: release notes are required"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Creating Replicated release v$$VERSION..."; \ + replicated release create \ + --promote Unstable \ + --yaml-dir $(REPLICATED_DIR) \ + --version "$$VERSION" \ + --release-notes "$$RELEASE_NOTES" + # Development helpers dev-install: update-deps install ## Update dependencies and install diff --git a/applications/flipt/chart/Chart.yaml b/applications/flipt/chart/Chart.yaml index 3feeb965..7457d154 100644 --- a/applications/flipt/chart/Chart.yaml +++ b/applications/flipt/chart/Chart.yaml @@ -5,7 +5,7 @@ description: | This enterprise-ready deployment includes PostgreSQL for durable storage and Redis for distributed caching across multiple instances. type: application -version: 1.0.4 +version: 1.0.6 appVersion: "1.61.0" keywords: - feature-flags diff --git a/applications/flipt/replicated/kots-helm-chart.yaml b/applications/flipt/replicated/kots-helm-chart.yaml index f4cf3074..3eb91fae 100644 --- a/applications/flipt/replicated/kots-helm-chart.yaml +++ b/applications/flipt/replicated/kots-helm-chart.yaml @@ -5,10 +5,7 @@ metadata: spec: chart: name: flipt - chartVersion: 1.0.4 - - # releaseName is a top-level field in v2 (not under chart) - releaseName: 'repl{{ ConfigOption "release_name" }}' + chartVersion: 1.0.6 exclude: "" From 2c49a4cab88b8007bd7d038ee843e0a57d8a91af Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Wed, 18 Feb 2026 06:32:21 +1300 Subject: [PATCH 07/12] Moved from Redis to Valkey --- applications/flipt/Makefile | 4 + applications/flipt/QUICKSTART.md | 8 +- applications/flipt/README.md | 72 ++++------- applications/flipt/TROUBLESHOOTING.md | 48 +++---- applications/flipt/chart/Chart.yaml | 14 +-- .../flipt/chart/templates/_helpers.tpl | 20 ++- .../flipt/chart/templates/_supportbundle.tpl | 94 +++++++------- .../flipt/chart/templates/deployment.yaml | 7 +- .../flipt/chart/templates/flipt-config.yaml | 4 +- .../templates/postgresql-cluster-job.yaml | 106 ++++++++++++++-- .../flipt/chart/templates/valkey-service.yaml | 21 ++++ applications/flipt/chart/values.yaml | 88 +++++-------- applications/flipt/examples/README.md | 4 +- applications/flipt/replicated/kots-app.yaml | 16 +-- .../flipt/replicated/kots-config.yaml | 87 ++----------- .../flipt/replicated/kots-helm-chart.yaml | 119 +++++++----------- .../flipt/replicated/kots-lint-config.yaml | 10 ++ .../flipt/replicated/kots-tls-secret.yaml | 18 --- 18 files changed, 344 insertions(+), 396 deletions(-) create mode 100644 applications/flipt/chart/templates/valkey-service.yaml create mode 100644 applications/flipt/replicated/kots-lint-config.yaml delete mode 100644 applications/flipt/replicated/kots-tls-secret.yaml diff --git a/applications/flipt/Makefile b/applications/flipt/Makefile index 060819c6..c844ebc4 100644 --- a/applications/flipt/Makefile +++ b/applications/flipt/Makefile @@ -133,6 +133,10 @@ release: package ## Lint, package, update Replicated config, and create release @sed -i '' 's/chartVersion:.*/chartVersion: $(CHART_VERSION)/' $(REPLICATED_DIR)/kots-helm-chart.yaml @echo "Chart $(CHART_NAME)-$(CHART_VERSION) packaged and staged in $(REPLICATED_DIR)/" @echo "" + @# Lint Replicated KOTS configs + @echo "Linting KOTS configuration..." + replicated release lint --yaml-dir $(REPLICATED_DIR)/ + @echo "" @# Determine next version by bumping previous release patch version @PREV_VERSION=$$(replicated release ls 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1); \ if [ -n "$$PREV_VERSION" ]; then \ diff --git a/applications/flipt/QUICKSTART.md b/applications/flipt/QUICKSTART.md index a4b6e657..9aa14bcb 100644 --- a/applications/flipt/QUICKSTART.md +++ b/applications/flipt/QUICKSTART.md @@ -50,7 +50,7 @@ If you prefer to run commands manually: cd chart rm -f Chart.lock # Clean cached files helm repo add flipt https://helm.flipt.io -helm repo add bitnami https://charts.bitnami.com/bitnami +helm repo add valkey https://valkey.io/valkey-helm/ helm repo add cnpg https://cloudnative-pg.github.io/charts helm repo add replicated https://charts.replicated.com helm repo update @@ -187,7 +187,7 @@ kubectl get pods -n flipt # Should see: # - flipt-flipt-xxx (2 replicas) # - flipt-cluster-xxx (PostgreSQL) -# - flipt-redis-master-xxx +# - flipt-valkey-xxx # Check services kubectl get svc -n flipt @@ -211,8 +211,8 @@ kubectl scale deployment/flipt-flipt -n flipt --replicas=3 # Check database status kubectl get cluster -n flipt -# Check Redis status -kubectl get pods -l app.kubernetes.io/name=redis -n flipt +# Check Valkey status +kubectl get pods -l app.kubernetes.io/name=valkey -n flipt ``` ## Troubleshooting diff --git a/applications/flipt/README.md b/applications/flipt/README.md index 66de1ccd..de5268a3 100644 --- a/applications/flipt/README.md +++ b/applications/flipt/README.md @@ -15,7 +15,7 @@ Flipt enables teams to: This Helm chart provides a production-ready deployment with: - ✅ PostgreSQL database (embedded via CloudnativePG or external) -- ✅ Redis distributed caching for high performance +- ✅ Valkey distributed caching for high performance - ✅ Horizontal pod autoscaling support - ✅ TLS/ingress configuration - ✅ Replicated SDK integration for enterprise management @@ -37,7 +37,7 @@ This Helm chart provides a production-ready deployment with: └──────┬────────┬───┘ │ │ ┌─────────▼──┐ ┌─▼──────────┐ - │ PostgreSQL │ │ Redis │ + │ PostgreSQL │ │ Valkey │ │ (CNPG) │ │ (Cache) │ └────────────┘ └────────────┘ ``` @@ -46,7 +46,7 @@ This Helm chart provides a production-ready deployment with: 1. **Flipt Server**: Core application handling feature flag evaluation and management 2. **PostgreSQL**: Durable storage for feature flag definitions and metadata (CloudnativePG operator) -3. **Redis**: Distributed cache for high-performance flag evaluation (required for multiple replicas) +3. **Valkey**: Distributed cache for high-performance flag evaluation (required for multiple replicas) 4. **Ingress**: External access with TLS support ## Prerequisites @@ -70,7 +70,7 @@ This Helm chart provides a production-ready deployment with: 2. **Configure settings** in the admin console UI: - Ingress and TLS settings - Database configuration (embedded or external) - - Redis cache settings + - Valkey cache settings - Resource limits 3. **Deploy** and monitor via the admin console @@ -106,7 +106,6 @@ export REPLICATED_LICENSE_ID=your-license-id ```bash helm repo add flipt-repo https://helm.flipt.io - helm repo add bitnami https://charts.bitnami.com/bitnami helm repo add replicated https://charts.replicated.com helm repo update ``` @@ -144,7 +143,7 @@ The chart can be configured via `values.yaml` or the Replicated admin console: ```yaml flipt: - replicaCount: 2 # Number of Flipt pods (2+ recommended with Redis) + replicaCount: 2 # Number of Flipt pods (2+ recommended with Valkey) resources: limits: cpu: 500m @@ -170,19 +169,15 @@ postgresql: storageClass: "" ``` -#### Redis Cache +#### Valkey Cache ```yaml -redis: +valkey: enabled: true # Required for multiple Flipt replicas - architecture: standalone # or 'replication' for HA - auth: - enabled: true - password: "" # Auto-generated if empty - master: - persistence: - enabled: true - size: 5Gi + image: + repository: ghcr.io/valkey-io/valkey + tag: "8.0" + # Uses emptyDir by default (cache data is ephemeral) ``` #### Ingress @@ -381,17 +376,6 @@ postgresql: instances: 3 ``` -### Redis HA - -Enable primary-replica architecture: - -```yaml -redis: - architecture: replication - replica: - replicaCount: 2 -``` - ## Monitoring ### Prometheus Metrics @@ -402,12 +386,6 @@ Enable metrics collection: flipt: serviceMonitor: enabled: true - -redis: - metrics: - enabled: true - serviceMonitor: - enabled: true ``` ### Available Metrics @@ -451,23 +429,23 @@ kubectl get cluster -n flipt kubectl logs -l cnpg.io/cluster=flipt-cluster -n flipt ``` -#### Redis Connection Issues +#### Valkey Connection Issues -Check Redis status: +Check Valkey status: ```bash -kubectl get pods -l app.kubernetes.io/name=redis -n flipt -kubectl logs -l app.kubernetes.io/name=redis -n flipt +kubectl get pods -l app.kubernetes.io/name=valkey -n flipt +kubectl logs -l app.kubernetes.io/name=valkey -n flipt ``` #### Cache Not Working -Verify Redis is enabled and Flipt can connect: +Verify Valkey is enabled and Flipt can connect: ```bash kubectl exec -it deploy/flipt-flipt -n flipt -- sh # Inside the pod: -nc -zv flipt-redis-master 6379 +nc -zv flipt-valkey 6379 ``` ### Debug Logs @@ -562,17 +540,13 @@ postgresql: shared_buffers: "1GB" ``` -### Redis Optimization +### Valkey Optimization ```yaml -redis: - master: - resources: - limits: - memory: 2Gi - persistence: - enabled: true - size: 20Gi +valkey: + resources: + limits: + memory: 2Gi ``` ### Flipt Optimization @@ -626,7 +600,7 @@ Contributions are welcome! Please: - Initial release - Flipt v1.61.0 - PostgreSQL 16 via CloudnativePG -- Redis 7.2 for distributed caching +- Valkey 8.0 for distributed caching - Replicated SDK integration - Comprehensive KOTS configuration - Preflight checks and support bundles diff --git a/applications/flipt/TROUBLESHOOTING.md b/applications/flipt/TROUBLESHOOTING.md index bf67ba34..a0a5fc51 100644 --- a/applications/flipt/TROUBLESHOOTING.md +++ b/applications/flipt/TROUBLESHOOTING.md @@ -74,7 +74,7 @@ postgresql: **Error:** ``` -Error: found in Chart.yaml, but missing in charts/ directory: flipt, redis, replicated +Error: found in Chart.yaml, but missing in charts/ directory: flipt, valkey, replicated ``` **Solution:** Update Helm dependencies: @@ -212,31 +212,31 @@ kubectl get pods -n cnpg-system --- -### Redis Connection Issues +### Valkey Connection Issues -**Check Redis status:** +**Check Valkey status:** ```bash -kubectl get pods -l app.kubernetes.io/name=redis -n flipt +kubectl get pods -l app.kubernetes.io/name=valkey -n flipt # Should show master (and replica if configured) running ``` -**Test Redis connectivity from Flipt pod:** +**Test Valkey connectivity from Flipt pod:** ```bash kubectl exec -it deploy/flipt-flipt -n flipt -- sh # Inside pod: -nc -zv flipt-redis-master 6379 -# Should show: Connection to flipt-redis-master 6379 port [tcp/*] succeeded! +nc -zv flipt-valkey 6379 +# Should show: Connection to flipt-valkey 6379 port [tcp/*] succeeded! -# Test with redis-cli (if available): -redis-cli -h flipt-redis-master -p 6379 -a ping +# Test with valkey-cli (if available): +valkey-cli -h flipt-valkey -p 6379 -a ping # Should return: PONG ``` -**Check Redis password:** +**Check Valkey password:** ```bash -kubectl get secret flipt-redis -n flipt -o jsonpath='{.data.redis-password}' | base64 -d +kubectl get secret flipt-valkey -n flipt -o jsonpath='{.data.valkey-password}' | base64 -d ``` --- @@ -337,23 +337,23 @@ postgresql: --- -## Alternative: Use External Redis +## Alternative: Use External Valkey -If you don't want embedded Redis: +If you don't want embedded Valkey: **values.yaml:** ```yaml -redis: +valkey: enabled: false flipt: config: cache: enabled: false # Disable caching - # Or configure external Redis: - # backend: redis - # redis: - # url: redis://external-redis:6379 + # Or configure external Valkey: + # backend: valkey + # valkey: + # url: valkey://external-valkey:6379 ``` --- @@ -378,8 +378,8 @@ kubectl logs -l app.kubernetes.io/name=flipt -n flipt --tail=100 -f # PostgreSQL logs kubectl logs -l cnpg.io/cluster=flipt-cluster -n flipt --tail=100 -f -# Redis logs -kubectl logs -l app.kubernetes.io/name=redis -n flipt --tail=100 -f +# Valkey logs +kubectl logs -l app.kubernetes.io/name=valkey -n flipt --tail=100 -f ``` ### Check configuration @@ -409,18 +409,18 @@ kubectl top nodes ### Slow flag evaluations -**Enable Redis caching:** -Ensure Redis is enabled and Flipt is configured to use it: +**Enable Valkey caching:** +Ensure Valkey is enabled and Flipt is configured to use it: ```yaml -redis: +valkey: enabled: true flipt: config: cache: enabled: true - backend: redis + backend: valkey ttl: 5m ``` diff --git a/applications/flipt/chart/Chart.yaml b/applications/flipt/chart/Chart.yaml index 7457d154..eb1ea4cc 100644 --- a/applications/flipt/chart/Chart.yaml +++ b/applications/flipt/chart/Chart.yaml @@ -2,10 +2,10 @@ apiVersion: v2 name: flipt description: | Flipt is an open-source, self-hosted feature flag and experimentation platform. - This enterprise-ready deployment includes PostgreSQL for durable storage and Redis + This enterprise-ready deployment includes PostgreSQL for durable storage and Valkey for distributed caching across multiple instances. type: application -version: 1.0.6 +version: 1.0.25 appVersion: "1.61.0" keywords: - feature-flags @@ -30,11 +30,11 @@ dependencies: repository: https://cloudnative-pg.github.io/charts condition: cloudnative-pg.enabled - # Redis for distributed caching - - name: redis - version: "19.6.4" - repository: https://charts.bitnami.com/bitnami - condition: redis.enabled + # Valkey for distributed caching (Redis-compatible) + - name: valkey + version: "0.2.0" + repository: https://valkey.io/valkey-helm/ + condition: valkey.enabled # Replicated SDK for admin console integration - name: replicated diff --git a/applications/flipt/chart/templates/_helpers.tpl b/applications/flipt/chart/templates/_helpers.tpl index 7dc06de3..3a002ba2 100644 --- a/applications/flipt/chart/templates/_helpers.tpl +++ b/applications/flipt/chart/templates/_helpers.tpl @@ -80,17 +80,11 @@ PostgreSQL connection URL {{- end }} {{/* -Redis connection URL +Valkey connection URL (Redis-compatible) */}} -{{- define "flipt.redis.url" -}} -{{- if .Values.redis.enabled }} -{{- if .Values.redis.auth.enabled }} -{{- printf "redis://:%s@%s-redis-master.%s.svc.cluster.local:6379" .Values.redis.auth.password .Release.Name .Release.Namespace }} -{{- else }} -{{- printf "redis://%s-redis-master.%s.svc.cluster.local:6379" .Release.Name .Release.Namespace }} -{{- end }} -{{- else }} -{{- "" }} +{{- define "flipt.valkey.url" -}} +{{- if .Values.valkey.enabled }} +{{- printf "redis://%s-valkey-svc.%s.svc.cluster.local:6379" .Release.Name .Release.Namespace }} {{- end }} {{- end }} @@ -113,10 +107,10 @@ Database secret name {{- end }} {{/* -Redis secret name +Valkey secret name */}} -{{- define "flipt.redis.secret" -}} -{{- printf "%s-redis" .Release.Name }} +{{- define "flipt.valkey.secret" -}} +{{- printf "%s-valkey" .Release.Name }} {{- end }} {{/* diff --git a/applications/flipt/chart/templates/_supportbundle.tpl b/applications/flipt/chart/templates/_supportbundle.tpl index 6934da36..a7215ff6 100644 --- a/applications/flipt/chart/templates/_supportbundle.tpl +++ b/applications/flipt/chart/templates/_supportbundle.tpl @@ -13,7 +13,7 @@ spec: - logs: selector: - app.kubernetes.io/name=flipt - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" limits: maxAge: 720h maxLines: 10000 @@ -23,68 +23,68 @@ spec: - logs: selector: - cnpg.io/cluster={{ .Release.Name }}-cluster - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" limits: maxAge: 168h maxLines: 10000 name: postgresql/logs - # Redis logs + # Valkey logs - logs: selector: - - app.kubernetes.io/name=redis - namespace: {{ .Release.Namespace }} + - app.kubernetes.io/name=valkey + namespace: "{{ .Release.Namespace }}" limits: maxAge: 168h maxLines: 10000 - name: redis/logs + name: valkey/logs # Pod status and events - pods: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" selector: - app.kubernetes.io/name=flipt - pods: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" selector: - cnpg.io/cluster={{ .Release.Name }}-cluster - pods: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" selector: - - app.kubernetes.io/name=redis + - app.kubernetes.io/name=valkey # Service and endpoint information - services: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" - endpoints: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" # ConfigMaps and Secrets (redacted) - configMaps: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" - secrets: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" includeKeys: - false # PVC and storage information - pvcs: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" # Ingress configuration - ingress: - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" # PostgreSQL specific diagnostics - exec: name: postgresql-version selector: - cnpg.io/cluster={{ .Release.Name }}-cluster - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" command: ["psql"] args: ["-c", "SELECT version();"] timeout: 30s @@ -93,7 +93,7 @@ spec: name: postgresql-connections selector: - cnpg.io/cluster={{ .Release.Name }}-cluster - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" command: ["psql"] args: ["-c", "SELECT count(*) as connections FROM pg_stat_activity;"] timeout: 30s @@ -102,29 +102,27 @@ spec: name: postgresql-database-size selector: - cnpg.io/cluster={{ .Release.Name }}-cluster - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" command: ["psql"] args: ["-c", "SELECT pg_size_pretty(pg_database_size('flipt')) as database_size;"] timeout: 30s - # Redis diagnostics + # Valkey diagnostics - exec: - name: redis-info + name: valkey-info selector: - - app.kubernetes.io/name=redis - - app.kubernetes.io/component=master - namespace: {{ .Release.Namespace }} - command: ["redis-cli"] + - app.kubernetes.io/name=valkey + namespace: "{{ .Release.Namespace }}" + command: ["valkey-cli"] args: ["INFO"] timeout: 30s - exec: - name: redis-memory + name: valkey-memory selector: - - app.kubernetes.io/name=redis - - app.kubernetes.io/component=master - namespace: {{ .Release.Namespace }} - command: ["redis-cli"] + - app.kubernetes.io/name=valkey + namespace: "{{ .Release.Namespace }}" + command: ["valkey-cli"] args: ["INFO", "memory"] timeout: 30s @@ -140,7 +138,7 @@ spec: name: helm-values selector: - app.kubernetes.io/name=flipt - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" command: ["sh"] args: ["-c", "helm get values {{ .Release.Name }} -n {{ .Release.Namespace }}"] timeout: 30s @@ -149,7 +147,7 @@ spec: name: helm-manifest selector: - app.kubernetes.io/name=flipt - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" command: ["sh"] args: ["-c", "helm get manifest {{ .Release.Name }} -n {{ .Release.Namespace }}"] timeout: 30s @@ -165,25 +163,25 @@ spec: name: flipt-to-postgres-connectivity selector: - app.kubernetes.io/name=flipt - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" command: ["sh"] args: ["-c", "nc -zv {{ .Release.Name }}-cluster-rw 5432 || echo 'Cannot connect to PostgreSQL'"] timeout: 10s - exec: - name: flipt-to-redis-connectivity + name: flipt-to-valkey-connectivity selector: - app.kubernetes.io/name=flipt - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" command: ["sh"] - args: ["-c", "nc -zv {{ .Release.Name }}-redis-master 6379 || echo 'Cannot connect to Redis'"] + args: ["-c", "nc -zv {{ .Release.Name }}-valkey-svc 6379 || echo 'Cannot connect to Valkey'"] timeout: 10s analyzers: # Pod status analysis - deploymentStatus: name: flipt-deployment - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" outcomes: - fail: when: "< 1" @@ -197,7 +195,7 @@ spec: # PostgreSQL cluster health - clusterPodStatuses: name: postgresql-cluster-health - namespace: {{ .Release.Namespace }} + namespace: "{{ .Release.Namespace }}" outcomes: - fail: when: "!= Healthy" @@ -205,16 +203,16 @@ spec: - pass: message: PostgreSQL cluster is healthy - # Redis health - - statefulsetStatus: - name: redis-health - namespace: {{ .Release.Namespace }} + # Valkey health + - deploymentStatus: + name: valkey-health + namespace: "{{ .Release.Namespace }}" outcomes: - fail: when: "< 1" - message: Redis has no ready replicas + message: Valkey deployment has no ready replicas - pass: - message: Redis is healthy + message: Valkey is healthy # Storage analysis - textAnalyze: @@ -264,15 +262,15 @@ spec: message: No database connection errors found - textAnalyze: - checkName: Check for Redis connection errors + checkName: Check for Valkey connection errors fileName: flipt/logs/*.log - regex: 'redis.*error|ECONNREFUSED.*redis|redis.*timeout' + regex: 'redis.*error|valkey.*error|ECONNREFUSED.*(redis|valkey)|(redis|valkey).*timeout' outcomes: - warn: when: "true" - message: Redis connection errors detected. Check Redis connectivity. + message: Valkey connection errors detected. Check Valkey connectivity. - pass: - message: No Redis connection errors found + message: No Valkey connection errors found - textAnalyze: checkName: Check for OOM errors diff --git a/applications/flipt/chart/templates/deployment.yaml b/applications/flipt/chart/templates/deployment.yaml index 26160b9c..c9765903 100644 --- a/applications/flipt/chart/templates/deployment.yaml +++ b/applications/flipt/chart/templates/deployment.yaml @@ -11,7 +11,6 @@ metadata: {{- if eq .Values.postgresql.type "embedded" }} helm.sh/hook: post-install,post-upgrade helm.sh/hook-weight: "10" - helm.sh/resource-policy: keep {{- end }} labels: {{- include "flipt.labels" . | nindent 4 }} @@ -80,12 +79,12 @@ spec: name: {{ include "flipt.postgresql.clustername" . }}-app key: uri {{- end }} - {{- if and .Values.redis.enabled .Values.redis.auth.enabled }} + {{- if and .Values.valkey.enabled .Values.valkey.auth.enable }} - name: FLIPT_CACHE_REDIS_PASSWORD valueFrom: secretKeyRef: - name: {{ include "flipt.redis.secret" . }} - key: redis-password + name: {{ include "flipt.valkey.secret" . }} + key: valkey-password {{- end }} {{- if .Values.extraEnvVars }} {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} diff --git a/applications/flipt/chart/templates/flipt-config.yaml b/applications/flipt/chart/templates/flipt-config.yaml index 0695cb05..8ac83d4b 100644 --- a/applications/flipt/chart/templates/flipt-config.yaml +++ b/applications/flipt/chart/templates/flipt-config.yaml @@ -26,13 +26,13 @@ data: max_open_conn: {{ .Values.config.db.maxOpenConn }} conn_max_lifetime: {{ .Values.config.db.connMaxLifetime }} - {{- if .Values.redis.enabled }} + {{- if .Values.valkey.enabled }} cache: enabled: {{ .Values.config.cache.enabled }} backend: {{ .Values.config.cache.backend }} ttl: {{ .Values.config.cache.ttl }} redis: - host: {{ .Release.Name }}-redis-master.{{ .Release.Namespace }}.svc.cluster.local + host: {{ .Release.Name }}-valkey-svc.{{ .Release.Namespace }}.svc.cluster.local port: 6379 # password is set via FLIPT_CACHE_REDIS_PASSWORD environment variable mode: {{ .Values.config.cache.redis.mode }} diff --git a/applications/flipt/chart/templates/postgresql-cluster-job.yaml b/applications/flipt/chart/templates/postgresql-cluster-job.yaml index edbfe6b8..6ee3b6f4 100644 --- a/applications/flipt/chart/templates/postgresql-cluster-job.yaml +++ b/applications/flipt/chart/templates/postgresql-cluster-job.yaml @@ -112,16 +112,15 @@ data: work_mem: "2621kB" min_wal_size: "1GB" max_wal_size: "4GB" + {{- if gt (int .Values.postgresql.embedded.cluster.instances) 1 }} + syncReplicaElectionConstraint: + enabled: true + nodeLabelsAntiAffinity: + - kubernetes.io/hostname + {{- end }} monitoring: enablePodMonitor: {{ .Values.postgresql.embedded.cluster.monitoring.enabled | default false }} - - {{- if gt (int .Values.postgresql.embedded.cluster.instances) 1 }} - # Enable synchronous replication for HA - postgresql: - syncReplicaElectionConstraint: - enabled: true - {{- end }} --- apiVersion: batch/v1 kind: Job @@ -197,4 +196,97 @@ spec: - name: manifests configMap: name: {{ include "flipt.fullname" . }}-db-manifest +--- +# Pre-delete hook to clean up the CNPG PostgreSQL Cluster CR +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "flipt.fullname" . }}-delete-db + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "flipt.fullname" . }}-delete-db + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +rules: + - apiGroups: ["postgresql.cnpg.io"] + resources: ["clusters"] + verbs: ["get", "delete"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "flipt.fullname" . }}-delete-db + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "flipt.fullname" . }}-delete-db +subjects: + - kind: ServiceAccount + name: {{ include "flipt.fullname" . }}-delete-db + namespace: {{ .Release.Namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "flipt.fullname" . }}-delete-db + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "flipt.labels" . | nindent 4 }} +spec: + backoffLimit: 3 + template: + metadata: + labels: + {{- include "flipt.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "flipt.fullname" . }}-delete-db + restartPolicy: OnFailure + containers: + - name: delete-cluster + image: "{{ .Values.dbJob.image.registry }}/{{ .Values.dbJob.image.repository }}:{{ .Values.dbJob.image.tag }}" + imagePullPolicy: {{ .Values.dbJob.image.pullPolicy }} + command: + - /bin/bash + - -c + - | + set -e + + CLUSTER_NAME="{{ include "flipt.postgresql.clustername" . }}" + NAMESPACE="{{ .Release.Namespace }}" + + if kubectl get cluster "$CLUSTER_NAME" -n "$NAMESPACE" &>/dev/null; then + echo "Deleting PostgreSQL Cluster $CLUSTER_NAME..." + kubectl delete cluster "$CLUSTER_NAME" -n "$NAMESPACE" --wait=true --timeout=120s + echo "PostgreSQL Cluster deleted." + else + echo "PostgreSQL Cluster $CLUSTER_NAME not found, skipping." + fi {{- end }} diff --git a/applications/flipt/chart/templates/valkey-service.yaml b/applications/flipt/chart/templates/valkey-service.yaml new file mode 100644 index 00000000..435cd460 --- /dev/null +++ b/applications/flipt/chart/templates/valkey-service.yaml @@ -0,0 +1,21 @@ +{{- if .Values.valkey.enabled }} +# Fix for broken Valkey chart service (targets wrong port name "http" instead of "valkey") +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-valkey-svc + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: valkey + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + type: ClusterIP + ports: + - port: 6379 + targetPort: 6379 + protocol: TCP + name: valkey + selector: + app.kubernetes.io/name: valkey + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/applications/flipt/chart/values.yaml b/applications/flipt/chart/values.yaml index 8ef44623..5dab938e 100644 --- a/applications/flipt/chart/values.yaml +++ b/applications/flipt/chart/values.yaml @@ -1,5 +1,5 @@ ## Flipt Feature Flag Platform Configuration -## Standalone chart with PostgreSQL and Redis support +## Standalone chart with PostgreSQL and Valkey support global: ## Global image registry override @@ -78,14 +78,14 @@ config: maxOpenConn: 50 connMaxLifetime: 1h - ## Cache configuration (templated from Redis values) + ## Cache configuration (templated from Valkey values) cache: enabled: true - backend: redis # or 'memory' for single instance + backend: redis # Valkey is Redis-compatible, use 'redis' backend ttl: 5m redis: url: "" # Will be set via template - mode: single # or 'cluster' for Redis cluster + mode: single # or 'cluster' for cluster mode prefix: "flipt" ## CORS configuration for web UI @@ -98,7 +98,7 @@ config: authentication: methods: token: - enabled: false + enabled: true ## Health check probes readinessProbe: @@ -200,9 +200,9 @@ affinity: {} ## Database Job Configuration (for PostgreSQL cluster creation) dbJob: image: - registry: registry.k8s.io - repository: kubectl - tag: "1.33.0" + registry: docker.io + repository: alpine/k8s + tag: "1.33.7" pullPolicy: IfNotPresent ## PostgreSQL Database Configuration @@ -251,60 +251,36 @@ postgresql: username: flipt password: "" # Auto-generated if empty (for embedded) -## Redis Cache Configuration -redis: +## Valkey Cache Configuration (Redis-compatible) +valkey: enabled: true - ## Redis image configuration + ## Valkey image configuration image: - registry: docker.io - repository: redis - tag: latest + repository: ghcr.io/valkey-io/valkey + tag: "8.0" pullPolicy: IfNotPresent - ## Redis architecture - architecture: standalone # or 'replication' for HA - - ## Authentication + ## Authentication (ACL-based) + ## Note: Flipt connects without auth since Valkey chart doesn't create secrets auth: - enabled: true - password: "" # Auto-generated if empty - - ## Master configuration - master: - persistence: - enabled: true - size: 5Gi - storageClass: "" - resources: - limits: - cpu: 500m - memory: 512Mi - requests: - cpu: 100m - memory: 128Mi - - ## Replica configuration (if architecture: replication) - replica: - replicaCount: 1 - persistence: - enabled: true - size: 5Gi - resources: - limits: - cpu: 500m - memory: 512Mi - requests: - cpu: 100m - memory: 128Mi - - ## Redis Sentinel (for HA) - sentinel: - enabled: false - - ## Metrics exporter - metrics: - enabled: false + enable: false + + ## Persistence (emptyDir - cache data is ephemeral) + ## Note: Official Valkey chart v0.2.0 has a bug with PVC creation + ## For persistent storage, create PVC manually and set storage.persistentVolumeClaimName + storage: + requestedSize: null + className: null + + ## Resources + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi ## Replicated SDK Integration replicated: diff --git a/applications/flipt/examples/README.md b/applications/flipt/examples/README.md index bf5e6550..495232ea 100644 --- a/applications/flipt/examples/README.md +++ b/applications/flipt/examples/README.md @@ -32,7 +32,7 @@ helm install flipt ../chart \ Features: - Single Flipt replica - Embedded PostgreSQL (1 instance) -- Redis standalone +- Valkey standalone - No ingress (use port-forward) Access: @@ -54,7 +54,7 @@ helm install flipt ../chart \ Features: - 3 Flipt replicas with autoscaling - PostgreSQL cluster (3 instances) -- Redis primary-replica architecture +- Valkey primary-replica architecture - Ingress with TLS - Prometheus metrics - Pod disruption budgets diff --git a/applications/flipt/replicated/kots-app.yaml b/applications/flipt/replicated/kots-app.yaml index f4225ca5..634366fd 100644 --- a/applications/flipt/replicated/kots-app.yaml +++ b/applications/flipt/replicated/kots-app.yaml @@ -8,7 +8,7 @@ spec: statusInformers: - deployment/flipt - deployment/flipt-cloudnative-pg - - statefulset/redis + - deployment/flipt-valkey ports: - serviceName: flipt servicePort: 8080 @@ -27,17 +27,17 @@ spec: query: 'count(kube_pod_status_phase{namespace="{{repl Namespace}}", pod=~"{{repl ConfigOption "release_name"}}-cluster-.*", phase="Running"})' legend: PostgreSQL Pods when: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' - - title: Redis Status - query: 'count(kube_pod_status_phase{namespace="{{repl Namespace}}", pod=~".*-redis-.*", phase="Running"})' - legend: Redis Pods - when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' + - title: Valkey Status + query: 'count(kube_pod_status_phase{namespace="{{repl Namespace}}", pod=~".*-valkey-.*", phase="Running"})' + legend: Valkey Pods + when: 'repl{{ ConfigOptionEquals "valkey_enabled" "1" }}' releaseNotes: | ## Flipt v1.61.0 This release includes: - Latest Flipt v1.61.0 with enhanced performance - PostgreSQL 16 support via CloudnativePG - - Redis distributed caching for multi-instance deployments + - Valkey distributed caching for multi-instance deployments - Horizontal pod autoscaling support - Comprehensive monitoring and metrics @@ -46,7 +46,7 @@ spec: - ghcr.io/flipt-io/flipt:v1.61.0 - ghcr.io/cloudnative-pg/cloudnative-pg:1.25.0 - ghcr.io/cloudnative-pg/postgresql:16 - - registry.k8s.io/kubectl:1.33.0 - - docker.io/library/redis:latest + - docker.io/alpine/k8s:1.33.7 + - ghcr.io/valkey-io/valkey:8.0 - proxy.replicated.com/library/replicated-sdk-image:1.15.0 allowRollback: true diff --git a/applications/flipt/replicated/kots-config.yaml b/applications/flipt/replicated/kots-config.yaml index c069b6a5..2db47267 100644 --- a/applications/flipt/replicated/kots-config.yaml +++ b/applications/flipt/replicated/kots-config.yaml @@ -15,19 +15,12 @@ spec: required: true help_text: Name for this Flipt deployment (used as Helm release name) - - name: namespace - title: Namespace - type: text - default: flipt - required: true - help_text: Kubernetes namespace where Flipt will be deployed - - name: replica_count title: Number of Flipt Replicas type: text default: "2" required: true - help_text: Number of Flipt pod replicas (2+ recommended for HA with Redis) + help_text: Number of Flipt pod replicas (2+ recommended for HA with Valkey cache) - name: ingress title: Ingress Configuration @@ -75,37 +68,13 @@ spec: when: 'repl{{ ConfigOptionEquals "ingress_enabled" "1" }}' help_text: Enable TLS/HTTPS for ingress - - name: tls_cert_source - title: TLS Certificate Source - type: select_one - default: cert_manager - items: - - name: cert_manager - title: Cert-Manager (Automatic) - - name: custom - title: Upload Custom Certificate - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' - help_text: How TLS certificates are managed - - name: tls_cert_manager_issuer title: Cert-Manager Issuer type: text default: letsencrypt-prod - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_cert_source" "cert_manager") }}' + when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' help_text: Name of the cert-manager ClusterIssuer - - name: tls_cert - title: TLS Certificate - type: file - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom") }}' - help_text: Upload your TLS certificate file (PEM format) - - - name: tls_key - title: TLS Private Key - type: file - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom") }}' - help_text: Upload your TLS private key file (PEM format) - - name: database title: Database Configuration description: PostgreSQL database settings for storing feature flags @@ -218,57 +187,17 @@ spec: when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' help_text: SSL mode for PostgreSQL connection - - name: redis - title: Redis Configuration - description: Redis cache settings for high-performance multi-instance deployments + - name: valkey + title: Valkey Configuration + description: Valkey cache settings for high-performance multi-instance deployments (Redis-compatible) items: - - name: redis_enabled - title: Enable Redis Cache + - name: valkey_enabled + title: Enable Valkey Cache type: bool default: "1" - help_text: Enable Redis for distributed caching (required for multiple Flipt replicas) + help_text: Enable Valkey for distributed caching (required for multiple Flipt replicas) recommended: true - - name: redis_architecture - title: Redis Architecture - type: select_one - default: standalone - items: - - name: standalone - title: Standalone (Single Instance) - - name: replication - title: Primary-Replica (High Availability) - when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' - help_text: Redis deployment architecture - - - name: redis_storage_size - title: Redis Storage Size - type: text - default: 5Gi - when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' - required: true - help_text: Storage size for Redis persistence (e.g., 5Gi, 10Gi) - - - name: redis_storage_class - title: Redis Storage Class - type: text - default: "" - when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' - help_text: Storage class for Redis PVCs (leave empty for default) - - - name: redis_password - title: Redis Password - type: password - when: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' - help_text: Password for Redis authentication (auto-generated if empty) - - - name: redis_replica_count - title: Number of Redis Replicas - type: text - default: "1" - when: '{{repl and (ConfigOptionEquals "redis_enabled" "1") (ConfigOptionEquals "redis_architecture" "replication") }}' - help_text: Number of Redis replica instances - - name: resources title: Resource Configuration description: Configure CPU and memory resources for Flipt diff --git a/applications/flipt/replicated/kots-helm-chart.yaml b/applications/flipt/replicated/kots-helm-chart.yaml index 3eb91fae..a2bf941d 100644 --- a/applications/flipt/replicated/kots-helm-chart.yaml +++ b/applications/flipt/replicated/kots-helm-chart.yaml @@ -5,7 +5,7 @@ metadata: spec: chart: name: flipt - chartVersion: 1.0.6 + chartVersion: 1.0.25 exclude: "" @@ -20,14 +20,12 @@ spec: - --wait-for-jobs - --history-max=15 - namespace: 'repl{{ ConfigOption "namespace" }}' - # Values for customer environment (uses template functions) values: global: imageRegistry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "" }}' - replicaCount: 'repl{{ ConfigOption "replica_count" | ParseInt }}' + replicaCount: repl{{ ConfigOption "replica_count" | ParseInt }} image: registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "ghcr.io" }}' @@ -60,8 +58,8 @@ spec: connMaxLifetime: 1h cache: - enabled: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' - backend: 'repl{{ ConfigOptionEquals "redis_enabled" "1" | ternary "redis" "memory" }}' + enabled: repl{{ ConfigOptionEquals "valkey_enabled" "1" }} + backend: 'repl{{ ConfigOptionEquals "valkey_enabled" "1" | ternary "redis" "memory" }}' ttl: 5m redis: mode: single @@ -78,17 +76,17 @@ spec: grpcPort: 9000 autoscaling: - enabled: 'repl{{ ConfigOptionEquals "enable_autoscaling" "1" }}' - minReplicas: 'repl{{ ConfigOption "hpa_min_replicas" | ParseInt }}' - maxReplicas: 'repl{{ ConfigOption "hpa_max_replicas" | ParseInt }}' - targetCPUUtilizationPercentage: 'repl{{ ConfigOption "hpa_cpu_target" | ParseInt }}' + enabled: repl{{ ConfigOptionEquals "enable_autoscaling" "1" }} + minReplicas: repl{{ ConfigOption "hpa_min_replicas" | ParseInt }} + maxReplicas: repl{{ ConfigOption "hpa_max_replicas" | ParseInt }} + targetCPUUtilizationPercentage: repl{{ ConfigOption "hpa_cpu_target" | ParseInt }} podDisruptionBudget: enabled: true minAvailable: 1 serviceMonitor: - enabled: 'repl{{ ConfigOptionEquals "enable_metrics" "1" }}' + enabled: repl{{ ConfigOptionEquals "enable_metrics" "1" }} # CloudNativePG operator (subchart) cloudnative-pg: @@ -102,9 +100,9 @@ spec: username: flipt embedded: - enabled: 'repl{{ ConfigOptionEquals "postgres_type" "embedded" }}' + enabled: repl{{ ConfigOptionEquals "postgres_type" "embedded" }} cluster: - instances: 'repl{{ ConfigOption "postgres_instances" | ParseInt }}' + instances: repl{{ ConfigOption "postgres_instances" | ParseInt }} imageName: 'repl{{ HasLocalRegistry | ternary (printf "%s/%s/postgresql:16" LocalRegistryHost LocalRegistryNamespace) "ghcr.io/cloudnative-pg/postgresql:16" }}' storage: size: 'repl{{ ConfigOption "postgres_storage_size" }}' @@ -121,55 +119,37 @@ spec: monitoring: enabled: false - # Redis configuration - redis: - enabled: 'repl{{ ConfigOptionEquals "redis_enabled" "1" }}' - - architecture: 'repl{{ ConfigOption "redis_architecture" }}' - - auth: - enabled: true + # Valkey configuration (Redis-compatible) + valkey: + enabled: repl{{ ConfigOptionEquals "valkey_enabled" "1" }} image: - registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "docker.io" }}' - repository: 'repl{{ HasLocalRegistry | ternary LocalRegistryNamespace "library" }}/redis' - tag: latest + repository: 'repl{{ HasLocalRegistry | ternary (printf "%s/%s/valkey" LocalRegistryHost LocalRegistryNamespace) "ghcr.io/valkey-io/valkey" }}' + tag: "8.0" - master: - persistence: - enabled: true - size: 'repl{{ ConfigOption "redis_storage_size" }}' - storageClass: 'repl{{ ConfigOption "redis_storage_class" }}' - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 500m - memory: 512Mi - - replica: - replicaCount: 'repl{{ ConfigOption "redis_replica_count" | ParseInt }}' - persistence: - enabled: true - size: 'repl{{ ConfigOption "redis_storage_size" }}' - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 500m - memory: 512Mi - - metrics: - enabled: 'repl{{ ConfigOptionEquals "enable_metrics" "1" }}' + auth: + enable: false + + # Note: Official Valkey chart v0.2.0 has a bug with PVC creation + # Using emptyDir for cache (data is ephemeral anyway) + storage: + requestedSize: null + className: null + + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi # Database creation Job image dbJob: image: - registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "registry.k8s.io" }}' - repository: 'repl{{ HasLocalRegistry | ternary (printf "%s/kubectl" LocalRegistryNamespace) "kubectl" }}' - tag: "1.33.0" + registry: 'repl{{ HasLocalRegistry | ternary LocalRegistryHost "docker.io" }}' + repository: 'repl{{ HasLocalRegistry | ternary (printf "%s/k8s" LocalRegistryNamespace) "alpine/k8s" }}' + tag: "1.33.7" # Replicated SDK replicated: @@ -202,7 +182,7 @@ spec: pathType: Prefix # TLS with cert-manager - - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") (ConfigOptionEquals "tls_cert_source" "cert_manager") }}' + - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' recursiveMerge: false values: ingress: @@ -213,16 +193,6 @@ spec: hosts: - 'repl{{ ConfigOption "ingress_hostname" }}' - # TLS with custom certificate - - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom") }}' - recursiveMerge: false - values: - ingress: - tls: - - secretName: flipt-custom-tls - hosts: - - 'repl{{ ConfigOption "ingress_hostname" }}' - # External PostgreSQL - when: 'repl{{ ConfigOptionEquals "postgres_type" "external" }}' recursiveMerge: false @@ -231,7 +201,7 @@ spec: external: enabled: true host: 'repl{{ ConfigOption "external_postgres_host" }}' - port: 'repl{{ ConfigOption "external_postgres_port" | ParseInt }}' + port: repl{{ ConfigOption "external_postgres_port" | ParseInt }} database: 'repl{{ ConfigOption "external_postgres_database" }}' username: 'repl{{ ConfigOption "external_postgres_username" }}' password: 'repl{{ ConfigOption "external_postgres_password" }}' @@ -277,17 +247,16 @@ spec: # kubectl image used in database creation Job dbJob: image: - registry: registry.k8s.io - repository: kubectl - tag: "1.33.0" + registry: docker.io + repository: alpine/k8s + tag: "1.33.7" - # Redis cache - redis: + # Valkey cache (Redis-compatible) + valkey: enabled: true image: - registry: docker.io - repository: library/redis - tag: latest + repository: ghcr.io/valkey-io/valkey + tag: "8.0" # Replicated SDK replicated: diff --git a/applications/flipt/replicated/kots-lint-config.yaml b/applications/flipt/replicated/kots-lint-config.yaml new file mode 100644 index 00000000..91d670ae --- /dev/null +++ b/applications/flipt/replicated/kots-lint-config.yaml @@ -0,0 +1,10 @@ +apiVersion: kots.io/v1beta1 +kind: LintConfig +metadata: + name: default-lint-config +spec: + rules: + - name: application-icon + level: "off" + - name: nonexistent-status-informer-object + level: "off" \ No newline at end of file diff --git a/applications/flipt/replicated/kots-tls-secret.yaml b/applications/flipt/replicated/kots-tls-secret.yaml deleted file mode 100644 index 013c4d73..00000000 --- a/applications/flipt/replicated/kots-tls-secret.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -# Custom TLS Secret for Ingress -# Only deployed when user selects "Upload Custom Certificate" for TLS -apiVersion: v1 -kind: Secret -metadata: - name: flipt-custom-tls - namespace: 'repl{{ ConfigOption "namespace" }}' - labels: - app.kubernetes.io/name: flipt - app.kubernetes.io/instance: 'repl{{ ConfigOption "release_name" }}' - annotations: - # Exclude this secret unless custom TLS is configured - kots.io/exclude: '{{repl not (and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") (ConfigOptionEquals "tls_cert_source" "custom")) }}' -type: kubernetes.io/tls -data: - tls.crt: 'repl{{ ConfigOptionData "tls_cert" | Base64Encode }}' - tls.key: 'repl{{ ConfigOptionData "tls_key" | Base64Encode }}' From 8d6d54c9a4d57e0d76209634ee05364e28f77d52 Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:53:55 +1300 Subject: [PATCH 08/12] Remove broken ingress controller and registry preflight checks --- .../flipt/chart/templates/_preflight.tpl | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/applications/flipt/chart/templates/_preflight.tpl b/applications/flipt/chart/templates/_preflight.tpl index 6d118be3..0bccda09 100644 --- a/applications/flipt/chart/templates/_preflight.tpl +++ b/applications/flipt/chart/templates/_preflight.tpl @@ -91,18 +91,6 @@ spec: - pass: message: Sufficient resources for Redis cache - # Network checks - - customResourceDefinition: - checkName: Check for Ingress Controller - customResourceDefinitionName: ingressclasses.networking.k8s.io - outcomes: - - fail: - message: | - No ingress controller detected in the cluster. - An ingress controller (like NGINX or Traefik) is required if ingress is enabled. - - pass: - message: Ingress controller CRDs are available - # CloudnativePG operator check (for embedded database) - customResourceDefinition: checkName: CloudnativePG Operator @@ -116,18 +104,6 @@ spec: - pass: message: CloudnativePG operator is available - # Image pull checks - - imagePullSecret: - checkName: Registry access - registryName: docker.flipt.io - outcomes: - - fail: - message: | - Cannot pull images from docker.flipt.io. - Check network connectivity or configure image pull secrets. - - pass: - message: Can pull images from container registry - # Distribution-specific checks - distribution: outcomes: From b1ee3f135c896cb8a598e197de220bfb986a4dd7 Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:23:58 -0600 Subject: [PATCH 09/12] Fix support bundle and preflight invalid checks --- applications/flipt/chart/Chart.yaml | 2 +- .../flipt/chart/templates/_preflight.tpl | 8 +- .../flipt/chart/templates/_supportbundle.tpl | 87 ++++++------------- .../flipt/replicated/kots-helm-chart.yaml | 2 +- 4 files changed, 32 insertions(+), 67 deletions(-) diff --git a/applications/flipt/chart/Chart.yaml b/applications/flipt/chart/Chart.yaml index eb1ea4cc..a0bc2f4a 100644 --- a/applications/flipt/chart/Chart.yaml +++ b/applications/flipt/chart/Chart.yaml @@ -5,7 +5,7 @@ description: | This enterprise-ready deployment includes PostgreSQL for durable storage and Valkey for distributed caching across multiple instances. type: application -version: 1.0.25 +version: 1.0.29 appVersion: "1.61.0" keywords: - feature-flags diff --git a/applications/flipt/chart/templates/_preflight.tpl b/applications/flipt/chart/templates/_preflight.tpl index 0bccda09..956b7f7f 100644 --- a/applications/flipt/chart/templates/_preflight.tpl +++ b/applications/flipt/chart/templates/_preflight.tpl @@ -36,11 +36,11 @@ spec: checkName: Minimum memory available outcomes: - fail: - when: "sum(memoryCapacity) < 4Gi" - message: The cluster must have at least 4GB of memory available + when: "sum(memoryCapacity) < 2Gi" + message: The cluster must have at least 2GB of memory available - warn: - when: "sum(memoryCapacity) < 8Gi" - message: At least 8GB of memory is recommended for production + when: "sum(memoryCapacity) < 4Gi" + message: At least 4GB of memory is recommended for production - pass: message: Sufficient memory available diff --git a/applications/flipt/chart/templates/_supportbundle.tpl b/applications/flipt/chart/templates/_supportbundle.tpl index a7215ff6..25b9e660 100644 --- a/applications/flipt/chart/templates/_supportbundle.tpl +++ b/applications/flipt/chart/templates/_supportbundle.tpl @@ -25,17 +25,27 @@ spec: - cnpg.io/cluster={{ .Release.Name }}-cluster namespace: "{{ .Release.Namespace }}" limits: - maxAge: 168h + maxAge: 720h maxLines: 10000 name: postgresql/logs + # CloudnativePG operator logs + - logs: + selector: + - app.kubernetes.io/name=cloudnative-pg + namespace: "{{ .Release.Namespace }}" + limits: + maxAge: 720h + maxLines: 10000 + name: cnpg-operator/logs + # Valkey logs - logs: selector: - app.kubernetes.io/name=valkey namespace: "{{ .Release.Namespace }}" limits: - maxAge: 168h + maxAge: 720h maxLines: 10000 name: valkey/logs @@ -50,6 +60,11 @@ spec: selector: - cnpg.io/cluster={{ .Release.Name }}-cluster + - pods: + namespace: "{{ .Release.Namespace }}" + selector: + - app.kubernetes.io/name=cloudnative-pg + - pods: namespace: "{{ .Release.Namespace }}" selector: @@ -130,7 +145,7 @@ spec: - http: name: flipt-health get: - url: http://{{ .Release.Name }}-flipt.{{ .Release.Namespace }}.svc.cluster.local:8080/health + url: http://{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local:8080/health timeout: 30s # Helm release information @@ -180,7 +195,7 @@ spec: analyzers: # Pod status analysis - deploymentStatus: - name: flipt-deployment + name: flipt namespace: "{{ .Release.Namespace }}" outcomes: - fail: @@ -205,7 +220,7 @@ spec: # Valkey health - deploymentStatus: - name: valkey-health + name: flipt-valkey namespace: "{{ .Release.Namespace }}" outcomes: - fail: @@ -214,29 +229,13 @@ spec: - pass: message: Valkey is healthy - # Storage analysis - - textAnalyze: - checkName: Persistent Volume Claims - fileName: /cluster-resources/persistent-volume-claims.json - regexGroups: '"phase": "(\w+)"' - outcomes: - - fail: - when: "Pending" - message: One or more PVCs are in Pending state - - fail: - when: "Failed" - message: One or more PVCs have failed - - pass: - when: "Bound" - message: All PVCs are bound - # Node resources - nodeResources: checkName: Node CPU capacity outcomes: - warn: - when: "sum(cpuCapacity) < 4" - message: Less than 4 CPU cores available. Consider scaling cluster for production workloads. + when: "sum(cpuCapacity) < 2" + message: Less than 2 CPU cores available. Consider scaling cluster for production workloads. - pass: message: Sufficient CPU resources @@ -244,50 +243,16 @@ spec: checkName: Node memory capacity outcomes: - warn: - when: "sum(memoryCapacity) < 8Gi" - message: Less than 8GB memory available. Consider scaling cluster for production workloads. + when: "sum(memoryCapacity) < 4Gi" + message: Less than 4GB memory available. Consider scaling cluster for production workloads. - pass: message: Sufficient memory resources - # Log analysis for common errors - - textAnalyze: - checkName: Check for database connection errors - fileName: flipt/logs/*.log - regex: 'database connection|connection refused|could not connect' - outcomes: - - fail: - when: "true" - message: Database connection errors detected in logs - - pass: - message: No database connection errors found - - - textAnalyze: - checkName: Check for Valkey connection errors - fileName: flipt/logs/*.log - regex: 'redis.*error|valkey.*error|ECONNREFUSED.*(redis|valkey)|(redis|valkey).*timeout' - outcomes: - - warn: - when: "true" - message: Valkey connection errors detected. Check Valkey connectivity. - - pass: - message: No Valkey connection errors found - - - textAnalyze: - checkName: Check for OOM errors - fileName: flipt/logs/*.log - regex: 'out of memory|OOMKilled|memory limit exceeded' - outcomes: - - fail: - when: "true" - message: Out of memory errors detected. Consider increasing memory limits. - - pass: - message: No OOM errors detected - # HTTP health check analysis - textAnalyze: checkName: Flipt API health - fileName: http-response/flipt-health.json - regex: '"status": "ok"' + fileName: flipt-health/result.json + regex: '"status": "SERVING"' outcomes: - fail: when: "false" diff --git a/applications/flipt/replicated/kots-helm-chart.yaml b/applications/flipt/replicated/kots-helm-chart.yaml index a2bf941d..d429d094 100644 --- a/applications/flipt/replicated/kots-helm-chart.yaml +++ b/applications/flipt/replicated/kots-helm-chart.yaml @@ -5,7 +5,7 @@ metadata: spec: chart: name: flipt - chartVersion: 1.0.25 + chartVersion: 1.0.29 exclude: "" From 513d27118cfe1150992a93349fcd7761efea9bb1 Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:53:39 -0600 Subject: [PATCH 10/12] Added smokescreen python test for Flipt and updated the examples Readme --- applications/flipt/examples/README.md | 64 ++-------- applications/flipt/tests/smoke/test-flipt.py | 125 +++++++++++++++++++ 2 files changed, 132 insertions(+), 57 deletions(-) create mode 100644 applications/flipt/tests/smoke/test-flipt.py diff --git a/applications/flipt/examples/README.md b/applications/flipt/examples/README.md index 495232ea..0d9869a0 100644 --- a/applications/flipt/examples/README.md +++ b/applications/flipt/examples/README.md @@ -1,76 +1,26 @@ # Flipt Examples -This directory contains examples for deploying and using Flipt in various scenarios. +This directory contains examples for testing and integrating with Flipt. + +For Helm installation and Kubernetes deployment instructions, see the [top-level README](../README.md). ## Directory Structure ``` examples/ -├── kubernetes/ # Kubernetes deployment examples +├── kubernetes/ # Helm values examples │ ├── values-minimal.yaml │ ├── values-production.yaml │ └── values-external-db.yaml -└── sdk/ # SDK integration examples +└── sdk/ # SDK integration examples ├── nodejs-example.js ├── golang-example.go └── python-example.py ``` -## Kubernetes Deployment Examples - -### Minimal Setup (Development/Testing) - -The minimal configuration is perfect for local development or testing: - -```bash -helm install flipt ../chart \ - --namespace flipt \ - --create-namespace \ - --values kubernetes/values-minimal.yaml -``` - -Features: -- Single Flipt replica -- Embedded PostgreSQL (1 instance) -- Valkey standalone -- No ingress (use port-forward) - -Access: -```bash -kubectl port-forward -n flipt svc/flipt-flipt 8080:8080 -``` - -### Production Setup (High Availability) - -The production configuration provides a highly available deployment: - -```bash -helm install flipt ../chart \ - --namespace flipt \ - --create-namespace \ - --values kubernetes/values-production.yaml -``` - -Features: -- 3 Flipt replicas with autoscaling -- PostgreSQL cluster (3 instances) -- Valkey primary-replica architecture -- Ingress with TLS -- Prometheus metrics -- Pod disruption budgets - -### External Database Setup - -Use this when you have an existing PostgreSQL database: - -```bash -helm install flipt ../chart \ - --namespace flipt \ - --create-namespace \ - --values kubernetes/values-external-db.yaml -``` +## Smoke Tests -**⚠️ Important:** Update the database credentials before deploying! +Smoke tests are located in [`tests/smoke/`](../tests/smoke/). See the test script there for usage instructions. ## SDK Integration Examples diff --git a/applications/flipt/tests/smoke/test-flipt.py b/applications/flipt/tests/smoke/test-flipt.py new file mode 100644 index 00000000..5873975d --- /dev/null +++ b/applications/flipt/tests/smoke/test-flipt.py @@ -0,0 +1,125 @@ +""" +Quick test script for a running Flipt instance. + +1. Creates a boolean flag via the Flipt API +2. Enables it +3. Evaluates it via the evaluation API +4. Cleans up + +Usage: + pip install requests + python test-flipt.py +""" + +import requests +import os +import sys + +FLIPT_URL = os.getenv("FLIPT_URL", "http://localhost:8080") +NAMESPACE = "default" +FLAG_KEY = "test-flag" + + +def main(): + session = requests.Session() + base = FLIPT_URL.rstrip("/") + + # 1. Health check + print(f"Checking Flipt health at {base} ...") + try: + resp = session.get(f"{base}/health", timeout=10) + resp.raise_for_status() + print(f" Health: {resp.json()}\n") + except requests.exceptions.RequestException as e: + print(f" Cannot reach Flipt: {e}") + sys.exit(1) + + # 2. Create a boolean flag + print(f"Creating boolean flag '{FLAG_KEY}' ...") + resp = session.post( + f"{base}/api/v1/namespaces/{NAMESPACE}/flags", + json={ + "key": FLAG_KEY, + "name": "Test Flag", + "type": "BOOLEAN_FLAG_TYPE", + "description": "Temporary flag created by test script", + "enabled": True, + }, + ) + if resp.status_code == 200: + print(f" Created: {resp.json()['key']}\n") + elif resp.status_code == 409: + print(f" Flag already exists, continuing.\n") + else: + print(f" Unexpected response: {resp.status_code} {resp.text}") + sys.exit(1) + + # 3. Enable the flag (update it) + print(f"Enabling flag '{FLAG_KEY}' ...") + resp = session.put( + f"{base}/api/v1/namespaces/{NAMESPACE}/flags/{FLAG_KEY}", + json={ + "key": FLAG_KEY, + "name": "Test Flag", + "type": "BOOLEAN_FLAG_TYPE", + "enabled": True, + }, + ) + if resp.status_code == 200: + print(f" Enabled: {resp.json().get('enabled')}\n") + else: + print(f" Update response: {resp.status_code} {resp.text}\n") + + # 4. Create a boolean rollout (100% true) + print(f"Creating rollout rule (100% true) ...") + resp = session.post( + f"{base}/api/v1/namespaces/{NAMESPACE}/flags/{FLAG_KEY}/rollouts", + json={ + "rank": 1, + "type": "THRESHOLD_ROLLOUT_TYPE", + "threshold": { + "percentage": 100.0, + "value": True, + }, + }, + ) + if resp.status_code == 200: + print(f" Rollout created.\n") + elif resp.status_code == 409: + print(f" Rollout already exists, continuing.\n") + else: + print(f" Rollout response: {resp.status_code} {resp.text}\n") + + # 5. Evaluate the flag + print(f"Evaluating flag '{FLAG_KEY}' ...") + resp = session.post( + f"{base}/evaluate/v1/boolean", + json={ + "namespaceKey": NAMESPACE, + "flagKey": FLAG_KEY, + "entityId": "test-user-1", + "context": {"plan": "enterprise"}, + }, + ) + if resp.status_code == 200: + data = resp.json() + print(f" Enabled: {data.get('enabled')}") + print(f" Reason: {data.get('reason')}\n") + else: + print(f" Evaluation response: {resp.status_code} {resp.text}\n") + + # 6. Clean up - delete the flag + print(f"Cleaning up - deleting flag '{FLAG_KEY}' ...") + resp = session.delete( + f"{base}/api/v1/namespaces/{NAMESPACE}/flags/{FLAG_KEY}", + ) + if resp.status_code == 200: + print(" Deleted.\n") + else: + print(f" Delete response: {resp.status_code} {resp.text}\n") + + print("Done!") + + +if __name__ == "__main__": + main() From a002fca9d2a7e350517a466037de78a1eb976634 Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:54:53 -0600 Subject: [PATCH 11/12] Added OS env var usage to smoke test --- applications/flipt/tests/smoke/test-flipt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/flipt/tests/smoke/test-flipt.py b/applications/flipt/tests/smoke/test-flipt.py index 5873975d..f8fab819 100644 --- a/applications/flipt/tests/smoke/test-flipt.py +++ b/applications/flipt/tests/smoke/test-flipt.py @@ -8,6 +8,7 @@ Usage: pip install requests + export FLIPT_URL=http://flipt.example.com # defaults to http://localhost:8080 python test-flipt.py """ From 0b6318aa9361842473007d0e7cdd62decd390f5c Mon Sep 17 00:00:00 2001 From: James Wilson <14128934+jmboby@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:29:41 -0600 Subject: [PATCH 12/12] Add Cert-manager and LetsEncrypt config --- applications/flipt/chart/Chart.yaml | 2 +- applications/flipt/replicated/ec-cluster.yaml | 20 +++++++++++++++++++ .../flipt/replicated/kots-cluster-issuer.yaml | 16 +++++++++++++++ .../flipt/replicated/kots-config.yaml | 8 ++++++++ .../flipt/replicated/kots-helm-chart.yaml | 15 ++++++++++---- 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 applications/flipt/replicated/kots-cluster-issuer.yaml diff --git a/applications/flipt/chart/Chart.yaml b/applications/flipt/chart/Chart.yaml index a0bc2f4a..73ebe87f 100644 --- a/applications/flipt/chart/Chart.yaml +++ b/applications/flipt/chart/Chart.yaml @@ -5,7 +5,7 @@ description: | This enterprise-ready deployment includes PostgreSQL for durable storage and Valkey for distributed caching across multiple instances. type: application -version: 1.0.29 +version: 1.0.33 appVersion: "1.61.0" keywords: - feature-flags diff --git a/applications/flipt/replicated/ec-cluster.yaml b/applications/flipt/replicated/ec-cluster.yaml index 316f71b3..1fe7ba7e 100644 --- a/applications/flipt/replicated/ec-cluster.yaml +++ b/applications/flipt/replicated/ec-cluster.yaml @@ -7,7 +7,27 @@ spec: repositories: - name: ingress-nginx url: https://kubernetes.github.io/ingress-nginx + - name: jetstack + url: https://charts.jetstack.io charts: + - name: cert-manager + chartname: jetstack/cert-manager + namespace: cert-manager + version: "v1.17.1" + values: | + crds: + enabled: true + image: + digest: "" + webhook: + image: + digest: "" + cainjector: + image: + digest: "" + startupapicheck: + image: + digest: "" - name: ingress-nginx chartname: ingress-nginx/ingress-nginx namespace: ingress-nginx diff --git a/applications/flipt/replicated/kots-cluster-issuer.yaml b/applications/flipt/replicated/kots-cluster-issuer.yaml new file mode 100644 index 00000000..6c395f7e --- /dev/null +++ b/applications/flipt/replicated/kots-cluster-issuer.yaml @@ -0,0 +1,16 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod + annotations: + kots.io/when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: 'repl{{ ConfigOption "tls_acme_email" }}' + privateKeySecretRef: + name: letsencrypt-prod-account-key + solvers: + - http01: + ingress: + ingressClassName: nginx diff --git a/applications/flipt/replicated/kots-config.yaml b/applications/flipt/replicated/kots-config.yaml index 2db47267..4553f8ff 100644 --- a/applications/flipt/replicated/kots-config.yaml +++ b/applications/flipt/replicated/kots-config.yaml @@ -75,6 +75,14 @@ spec: when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' help_text: Name of the cert-manager ClusterIssuer + - name: tls_acme_email + title: ACME Email Address + type: text + default: "" + required: true + when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' + help_text: Email address for Let's Encrypt certificate registration notifications + - name: database title: Database Configuration description: PostgreSQL database settings for storing feature flags diff --git a/applications/flipt/replicated/kots-helm-chart.yaml b/applications/flipt/replicated/kots-helm-chart.yaml index d429d094..c4349d40 100644 --- a/applications/flipt/replicated/kots-helm-chart.yaml +++ b/applications/flipt/replicated/kots-helm-chart.yaml @@ -5,7 +5,7 @@ metadata: spec: chart: name: flipt - chartVersion: 1.0.29 + chartVersion: 1.0.33 exclude: "" @@ -168,8 +168,8 @@ spec: registry: 'repl{{ LocalRegistryHost }}' repository: 'repl{{ LocalRegistryNamespace }}/replicated-sdk-image' - # Ingress configuration - - when: 'repl{{ ConfigOptionEquals "ingress_enabled" "1" }}' + # Ingress configuration (without TLS) + - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (not (ConfigOptionEquals "tls_enabled" "1")) }}' recursiveMerge: false values: ingress: @@ -181,13 +181,20 @@ spec: - path: / pathType: Prefix - # TLS with cert-manager + # Ingress configuration (with TLS + cert-manager) - when: '{{repl and (ConfigOptionEquals "ingress_enabled" "1") (ConfigOptionEquals "tls_enabled" "1") }}' recursiveMerge: false values: ingress: + enabled: true + className: 'repl{{ if ConfigOptionEquals "ingress_class" "custom" }}repl{{ ConfigOption "ingress_class_custom" }}repl{{ else }}repl{{ ConfigOption "ingress_class" }}repl{{ end }}' annotations: cert-manager.io/cluster-issuer: 'repl{{ ConfigOption "tls_cert_manager_issuer" }}' + hosts: + - host: 'repl{{ ConfigOption "ingress_hostname" }}' + paths: + - path: / + pathType: Prefix tls: - secretName: flipt-tls hosts: