479 lines
12 KiB
Markdown
479 lines
12 KiB
Markdown
# GitOps Status Server Helm Chart
|
|
|
|
A dual-container HTTP server that receives GitOps status updates via POST API and serves status information as JSON for monitoring and observability purposes.
|
|
|
|
## Overview
|
|
|
|
This chart deploys a two-container pod:
|
|
1. **Nginx** - Serves `/status.json` endpoint for monitoring tools and handles API routing
|
|
2. **Flask API** - Processes POST requests to `/api/status` and updates the status JSON
|
|
|
|
It's designed to be consumed by Grafana's Infinity datasource or other monitoring tools, and to receive updates from CI/CD pipelines like Woodpecker.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
CI/CD Pipeline (Woodpecker)
|
|
↓
|
|
POST /api/status
|
|
↓
|
|
Kubernetes Service (port 80)
|
|
↓
|
|
Nginx (port 8080)
|
|
├─→ /api/status → Proxies to Flask (localhost:5000)
|
|
└─→ /status.json → Serves static file
|
|
↓
|
|
Shared Volume (emptyDir)
|
|
├─→ status.json (updated by Flask API)
|
|
└─→ Read by Nginx
|
|
↓
|
|
Grafana Infinity Datasource
|
|
Reads /status.json
|
|
```
|
|
|
|
## Features
|
|
|
|
- **API-driven updates**: POST endpoint for CI/CD pipelines to update status
|
|
- **Read-only serving**: Grafana-friendly JSON endpoint
|
|
- **Minimal footprint**: nginx-unprivileged + Python-Alpine with minimal resources
|
|
- **Secure by default**: Runs as non-root with restricted filesystems
|
|
- **Internal only**: ClusterIP service for cluster-internal access
|
|
- **ArgoCD compatible**: Init container auto-initializes status from ConfigMap
|
|
- **Production-ready**: Includes health checks, security contexts, and resource limits
|
|
|
|
## Installation
|
|
|
|
### Using Helm
|
|
|
|
```bash
|
|
# Install with default values
|
|
helm install gitops-status ./gitops-status-server
|
|
|
|
# Install with custom namespace
|
|
helm install gitops-status ./gitops-status-server -n observability-stack --create-namespace
|
|
|
|
# Install with custom values
|
|
helm install gitops-status ./gitops-status-server -f custom-values.yaml
|
|
```
|
|
|
|
### Using ArgoCD
|
|
|
|
Create an Application manifest:
|
|
|
|
```yaml
|
|
apiVersion: argoproj.io/v1alpha1
|
|
kind: Application
|
|
metadata:
|
|
name: gitops-status-server
|
|
namespace: argocd
|
|
spec:
|
|
project: default
|
|
source:
|
|
repoURL: https://github.com/your-org/observability-stack
|
|
targetRevision: main
|
|
path: gitops-status-server
|
|
helm:
|
|
values: |
|
|
replicaCount: 1
|
|
statusJson:
|
|
repo: "rsyslog"
|
|
server: "rsyslog-lab"
|
|
sync_status: "UNKNOWN"
|
|
```
|
|
|
|
## API Endpoints
|
|
|
|
### GET /status.json
|
|
Returns the current status JSON
|
|
|
|
```bash
|
|
curl http://gitops-status-server.observability-stack.svc.cluster.local:80/status.json
|
|
```
|
|
|
|
Response:
|
|
```json
|
|
{
|
|
"repo": "rsyslog",
|
|
"server": "rsyslog-lab",
|
|
"sync_status": "SYNCED",
|
|
"drift_count": 0,
|
|
"files": [],
|
|
"last_check": "2026-04-21T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
### POST /api/status
|
|
Updates the status with new data
|
|
|
|
```bash
|
|
curl -X POST http://gitops-status-server.observability-stack.svc.cluster.local:80/api/status \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"repo": "rsyslog",
|
|
"server": "rsyslog-lab",
|
|
"sync_status": "OUT_OF_SYNC",
|
|
"drift_count": 2,
|
|
"files": [
|
|
{"name": "rsyslog.conf"},
|
|
{"name": "rsyslog.d/30-lab.conf"}
|
|
],
|
|
"last_check": "2026-04-21T10:30:00Z"
|
|
}'
|
|
```
|
|
|
|
Response (HTTP 200):
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Status updated successfully",
|
|
"status": { ... }
|
|
}
|
|
```
|
|
|
|
### GET /health
|
|
Health check endpoint (returns HTTP 200)
|
|
|
|
```bash
|
|
curl http://gitops-status-server.observability-stack.svc.cluster.local:80/health
|
|
```
|
|
|
|
### GET /ready
|
|
Readiness check (verifies status file is readable)
|
|
|
|
```bash
|
|
curl http://gitops-status-server.observability-stack.svc.cluster.local:80/ready
|
|
```
|
|
|
|
## Integration with Woodpecker
|
|
|
|
The rsyslog CI/CD pipeline can update status by POSTing to the `/api/status` endpoint:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
GITOPS_STATUS_SERVER_URL="http://gitops-status-server.observability-stack.svc.cluster.local:80"
|
|
|
|
STATUS_JSON='{
|
|
"repo": "rsyslog",
|
|
"server": "rsyslog-lab",
|
|
"sync_status": "SYNCED",
|
|
"drift_count": 0,
|
|
"files": [],
|
|
"last_check": "2026-04-21T10:30:00Z"
|
|
}'
|
|
|
|
curl -X POST "$GITOPS_STATUS_SERVER_URL/api/status" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$STATUS_JSON"
|
|
```
|
|
|
|
## Service Discovery
|
|
|
|
### Internal Kubernetes URL
|
|
```
|
|
http://gitops-status-server.observability-stack.svc.cluster.local:80/status.json
|
|
```
|
|
|
|
### Port Forwarding (for local testing)
|
|
```bash
|
|
kubectl port-forward -n observability-stack svc/gitops-status-server 8080:80
|
|
# Then access at http://localhost:8080/status.json
|
|
```
|
|
|
|
### NodePort (if service type is changed)
|
|
```bash
|
|
kubectl patch service -n observability-stack gitops-status-server -p '{"spec":{"type":"NodePort"}}'
|
|
# Then access at http://<node-ip>:<node-port>/status.json
|
|
```
|
|
|
|
## Configuration
|
|
|
|
See `values.yaml` for all configuration options:
|
|
|
|
- `replicaCount`: Number of replicas
|
|
- `image.repository`: Container image
|
|
- `image.tag`: Image tag
|
|
- `service.type`: Service type (ClusterIP, NodePort, LoadBalancer)
|
|
- `service.port`: Service port (default 80)
|
|
- `service.targetPort`: Container port (default 8080)
|
|
- `resources`: CPU/memory limits and requests
|
|
- `statusJson`: Default status JSON values
|
|
- `api.image.*`: Python/Flask image configuration
|
|
|
|
## Grafana Integration
|
|
|
|
### Infinity Datasource Configuration
|
|
|
|
1. Install Infinity datasource plugin:
|
|
```bash
|
|
grafana-cli plugins install yesoreyeram-infinity-datasource
|
|
```
|
|
|
|
2. Add datasource with URL:
|
|
```
|
|
http://gitops-status-server.observability-stack.svc.cluster.local:80/status.json
|
|
```
|
|
|
|
3. Create panels to visualize:
|
|
- `sync_status`: Current synchronization state
|
|
- `drift_count`: Number of drifted files
|
|
- `files[]`: List of changed files
|
|
- `last_check`: Timestamp of last check
|
|
|
|
### Example Query
|
|
|
|
```json
|
|
{
|
|
"url": "http://gitops-status-server.observability-stack.svc.cluster.local:80/status.json",
|
|
"format": "json"
|
|
}
|
|
```
|
|
|
|
## Security
|
|
|
|
- Runs as non-root user (UID 101)
|
|
- Read-only root filesystem (except for /tmp, /var/cache/nginx, /var/run)
|
|
- No privileged capabilities
|
|
- Network policies recommended for production
|
|
- Service Account with minimal RBAC
|
|
|
|
## Troubleshooting
|
|
|
|
### POST Request Returns 400 Error
|
|
|
|
**Issue**: "Invalid JSON" error
|
|
|
|
**Solution**: Verify JSON formatting with:
|
|
```bash
|
|
echo '{...}' | jq '.'
|
|
```
|
|
|
|
### POST Updates Not Appearing in GET Response
|
|
|
|
**Issue**: Update endpoint returns 200 but status.json isn't updated
|
|
|
|
**Possible causes**:
|
|
- Shared volume permission issue
|
|
- API container crashed after POST
|
|
- Status file permissions
|
|
|
|
**Debug**:
|
|
```bash
|
|
# Check logs
|
|
kubectl logs -f deployment/gitops-status-server -c api
|
|
kubectl logs -f deployment/gitops-status-server -c nginx
|
|
|
|
# Check shared volume
|
|
kubectl exec deployment/gitops-status-server -c nginx -- ls -la /usr/share/nginx/html/
|
|
|
|
# Test API directly (port-forward to 5000 first)
|
|
kubectl port-forward deployment/gitops-status-server 5000:5000
|
|
curl -X POST http://localhost:5000/api/status -H "Content-Type: application/json" -d '{...}'
|
|
```
|
|
|
|
### Connection Refused to gitops-status-server
|
|
|
|
**Issue**: Woodpecker can't reach the service
|
|
|
|
**Possible causes**:
|
|
- Service in different namespace
|
|
- Network policies blocking traffic
|
|
- Woodpecker outside cluster
|
|
- Service DNS name incorrect
|
|
|
|
**Solutions**:
|
|
- Verify service exists: `kubectl get svc gitops-status-server -n observability-stack`
|
|
- Use NodePort for external access (update service type in values)
|
|
- Use port-forward as a temporary solution
|
|
- Verify network policies allow traffic
|
|
|
|
## Performance
|
|
|
|
- **CPU**: 150m limit (100m nginx + 100m API)
|
|
- **Memory**: 192Mi limit (64Mi nginx + 128Mi API)
|
|
- **Startup time**: ~5 seconds (Flask app install + startup)
|
|
- **Update latency**: <100ms (direct file write)
|
|
- **Read performance**: <10ms (static file serving)
|
|
|
|
## License
|
|
|
|
Same as observability-stack repository
|
|
statusJson:
|
|
repo: "my-repo"
|
|
server: "my-server"
|
|
sync_status: "SYNCED"
|
|
drift_count: 0
|
|
files: []
|
|
last_check: "2026-04-21T10:00:00Z"
|
|
destination:
|
|
server: https://kubernetes.default.svc
|
|
namespace: monitoring
|
|
syncPolicy:
|
|
automated:
|
|
prune: true
|
|
selfHeal: true
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Key Values
|
|
|
|
| Parameter | Description | Default |
|
|
|-----------|-------------|---------|
|
|
| `replicaCount` | Number of replicas | `1` |
|
|
| `image.repository` | Container image repository | `nginxinc/nginx-unprivileged` |
|
|
| `image.tag` | Container image tag | `1.25-alpine` |
|
|
| `service.type` | Kubernetes service type | `ClusterIP` |
|
|
| `service.port` | Service port | `80` |
|
|
| `service.targetPort` | Container target port | `8080` |
|
|
| `resources.limits.cpu` | CPU limit | `100m` |
|
|
| `resources.limits.memory` | Memory limit | `64Mi` |
|
|
| `statusJson` | JSON content to serve | See values.yaml |
|
|
|
|
### Custom Status JSON
|
|
|
|
Override the status JSON content in your values:
|
|
|
|
```yaml
|
|
statusJson:
|
|
repo: "production-apps"
|
|
server: "prod-cluster-01"
|
|
sync_status: "SYNCED"
|
|
drift_count: 2
|
|
files:
|
|
- "deployment.yaml"
|
|
- "service.yaml"
|
|
last_check: "2026-04-21T12:30:00Z"
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Access the Status Endpoint
|
|
|
|
From inside the cluster:
|
|
|
|
```bash
|
|
# Using the service DNS name
|
|
curl http://gitops-status-server/status.json
|
|
|
|
# With namespace
|
|
curl http://gitops-status-server.monitoring.svc.cluster.local/status.json
|
|
```
|
|
|
|
### Grafana Infinity Datasource Configuration
|
|
|
|
1. Add an Infinity datasource in Grafana
|
|
2. Configure URL: `http://gitops-status-server.monitoring.svc.cluster.local/status.json`
|
|
3. Parser: JSON
|
|
4. Use fields from the JSON response in your dashboard
|
|
|
|
Example query fields:
|
|
- `sync_status` - Current sync status
|
|
- `drift_count` - Number of drifted resources
|
|
- `files` - List of changed files
|
|
- `last_check` - Timestamp of last check
|
|
|
|
## Updating Status Data
|
|
|
|
### Manual Update
|
|
|
|
Edit the ConfigMap directly:
|
|
|
|
```bash
|
|
kubectl edit configmap gitops-status-server -n monitoring
|
|
```
|
|
|
|
The deployment will automatically roll out with the new content due to the ConfigMap checksum annotation.
|
|
|
|
### Automated Update via Pipeline
|
|
|
|
Use `kubectl` in your CI/CD pipeline:
|
|
|
|
```bash
|
|
kubectl create configmap gitops-status-server \
|
|
--from-file=status.json=./status.json \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
```
|
|
|
|
### ArgoCD Hook (Advanced)
|
|
|
|
Create a PostSync hook that updates the ConfigMap with current sync status:
|
|
|
|
```yaml
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: update-status
|
|
annotations:
|
|
argocd.argoproj.io/hook: PostSync
|
|
spec:
|
|
template:
|
|
spec:
|
|
containers:
|
|
- name: update
|
|
image: bitnami/kubectl
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
# Update status.json with current sync status
|
|
kubectl patch configmap gitops-status-server \
|
|
--patch '{"data":{"status.json":"..."}}'
|
|
restartPolicy: Never
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
- Runs as non-root user (UID 101)
|
|
- Read-only root filesystem
|
|
- No privilege escalation
|
|
- Minimal capabilities (all dropped)
|
|
- No external network access required
|
|
- ClusterIP only (no external exposure)
|
|
|
|
## Resource Requirements
|
|
|
|
Minimal resource footprint suitable for small clusters:
|
|
- CPU: 50m request / 100m limit
|
|
- Memory: 32Mi request / 64Mi limit
|
|
|
|
## Troubleshooting
|
|
|
|
### Check pod status
|
|
|
|
```bash
|
|
kubectl get pods -l app.kubernetes.io/name=gitops-status-server
|
|
```
|
|
|
|
### View logs
|
|
|
|
```bash
|
|
kubectl logs -l app.kubernetes.io/name=gitops-status-server
|
|
```
|
|
|
|
### Test endpoint
|
|
|
|
```bash
|
|
kubectl run -it --rm curl --image=curlimages/curl --restart=Never -- \
|
|
curl http://gitops-status-server/status.json
|
|
```
|
|
|
|
### Common Issues
|
|
|
|
**Pod not starting**: Check security context compatibility with your cluster's PSP/PSA policies.
|
|
|
|
**Empty response**: Verify the ConfigMap is mounted correctly:
|
|
```bash
|
|
kubectl describe pod -l app.kubernetes.io/name=gitops-status-server
|
|
```
|
|
|
|
**Service not accessible**: Ensure you're accessing from within the cluster and using the correct namespace.
|
|
|
|
## License
|
|
|
|
This chart is part of the observability-stack project.
|
|
|
|
## Maintainers
|
|
|
|
- DevOps Team
|