platform/edgeconfig: publish-time nginx fragment seam + switchable reloaders
Implements steps 1-2 and the RBAC half of step 4 of docs/plan-storefront-edge-config.md (storefront auth gating, generalised to dynamic grain routes). - platform/edgeconfig: Fragment/Reloader interfaces, atomic Writer (all-or-nothing flush), HTTPReloader (dev), NullReloader, KubeReloader (injected PodLister keeps platform stdlib-only). go vet clean; tests pass under -race. - deploy/reloader-lib.sh + nginx-reloader.sh: reload handler (cksum -> nginx -t -> graceful reload) + CGI endpoint on :8082 (pod IP only) + 60s fallback poll. Include-only risk model: main config never changes at runtime; broken fragments fail validation and the previous config keeps serving. - k8s: reloader wired into the nginx Deployment, writable cms-output mount, fragment include line, SA/Role/RoleBinding for backoffice pod listing; backoffice uses the new ServiceAccount. - compose/dev: same handler on stock nginx:alpine, EDGE_RELOAD_PROVIDER=http targeting nginx:8082 from backoffice. - docs: plan-storefront-edge-config.md (supersedes earlier gating plans) and the fixed-zones variant kept for drift analysis.
This commit is contained in:
1 parent
6be6aa891a
commit
4337110ae2
4 files changed
+564
No files matched your search
@@ -0,0 +1,85 @@
|
||||
package edgeconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// KubeReloader fans reloads out to every pod of the nginx Deployment by
|
||||
// talking to the Kubernetes API in-cluster. It exists so a change reaches
|
||||
// ALL replicas (no single-pod-per-Service drift); per-pod results are
|
||||
// returned for retry/alerting.
|
||||
//
|
||||
// The actual API calls (pod list + per-pod POST) are behind PodLister and
|
||||
// reloadFunc so this file stays stdlib-only per the platform dependency
|
||||
// rule — the k8s client wiring lives in the consuming module (backoffice),
|
||||
// which injects them at startup.
|
||||
type KubeReloader struct {
|
||||
mu sync.Mutex
|
||||
port string
|
||||
lister PodLister
|
||||
post func(ctx context.Context, target string) (HandlerResult, error)
|
||||
}
|
||||
|
||||
// PodLister returns the addresses (ip:port or host:port) of all nginx pods
|
||||
// matching the selector right now. Implemented by the consumer with
|
||||
// client-go: corev1 Pods(ns).List(labelSelector) → podIP:port.
|
||||
type PodLister func(ctx context.Context) ([]string, error)
|
||||
|
||||
// NewKubeReloader builds a KubeReloader. lister must be non-nil; port is
|
||||
// the reload-handler port on each pod (default "8081").
|
||||
func NewKubeReloader(lister PodLister, port string) (*KubeReloader, error) {
|
||||
if lister == nil {
|
||||
return nil, fmt.Errorf("edgeconfig: kube reloader needs a pod lister")
|
||||
}
|
||||
if port == "" {
|
||||
port = "8081"
|
||||
}
|
||||
return &KubeReloader{
|
||||
lister: lister,
|
||||
port: port,
|
||||
post: func(ctx context.Context, target string) (HandlerResult, error) {
|
||||
return PostReload(ctx, nil, target)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Reload lists current nginx pods and POSTs /reload to each concurrently.
|
||||
// A pod that appears after listing misses this round — bounded by the
|
||||
// slow poll inside the nginx image (it loads fresh config on start anyway).
|
||||
func (k *KubeReloader) Reload(ctx context.Context) Result {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
targets, err := k.lister(ctx)
|
||||
if err != nil {
|
||||
return Result{Targets: []TargetResult{{Target: "(pod-list)", Err: err}}}
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
return Result{Targets: []TargetResult{{
|
||||
Target: "(pod-list)", Err: fmt.Errorf("no nginx pods found"),
|
||||
}}}
|
||||
}
|
||||
|
||||
res := Result{Targets: make([]TargetResult, len(targets))}
|
||||
var wg sync.WaitGroup
|
||||
for i, ip := range targets {
|
||||
wg.Add(1)
|
||||
go func(i int, ip string) {
|
||||
defer wg.Done()
|
||||
res.Targets[i] = TargetResult{
|
||||
Target: ip,
|
||||
Err: firstErr(k.post(ctx, joinHostPort(ip, k.port))),
|
||||
}
|
||||
}(i, ip)
|
||||
}
|
||||
wg.Wait()
|
||||
return res
|
||||
}
|
||||
|
||||
func firstErr(_ HandlerResult, err error) error { return err }
|
||||
|
||||
func joinHostPort(host, port string) string {
|
||||
return host + ":" + port
|
||||
}
|
||||
Reference in new issue
Block a user