Initial cephrgw-operator: Ceph RGW buckets & keys via dashboard API
Adds a Kubernetes operator that provisions Ceph RGW (S3) buckets and access keys declaratively through the Ceph manager dashboard REST API. Three CRDs in group ceph.unkin.net/v1alpha1: - ObjectStoreUser: creates an RGW user, delivers its key pair to a Secret - Bucket: creates an S3 bucket owned by an ObjectStoreUser; owns the bucket's aggregate S3 policy (union of all BucketAccess grants) - BucketAccess: grants read-only/read-write/full access, provisioning a dedicated user (or reusing a referenced one) and delivering RW/RO keys The internal/ceph client wraps the dashboard /api/auth, /api/rgw/user and /api/rgw/bucket endpoints with lazy token auth and re-auth on 401. Bucket policies are rendered deterministically and applied via the bucket policy API (Reef 18.2+). Credentials come from the cephrgw-credentials Secret via env. Includes generated CRDs/RBAC, samples, kind manifests, Woodpecker CI, and docs/ceph-setup.md covering the required Ceph dashboard account, RGW wiring and permissions.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
cephv1alpha1 "git.unkin.net/unkin/cephrgw-operator/api/v1alpha1"
|
||||
"git.unkin.net/unkin/cephrgw-operator/internal/ceph"
|
||||
"git.unkin.net/unkin/cephrgw-operator/internal/controller"
|
||||
)
|
||||
|
||||
var scheme = runtime.NewScheme()
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
utilruntime.Must(cephv1alpha1.AddToScheme(scheme))
|
||||
}
|
||||
|
||||
func main() {
|
||||
var metricsAddr, probeAddr string
|
||||
var leaderElect bool
|
||||
|
||||
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "metrics endpoint address")
|
||||
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "health probe address")
|
||||
flag.BoolVar(&leaderElect, "leader-elect", false, "enable leader election")
|
||||
flag.Parse()
|
||||
|
||||
ctrl.SetLogger(zap.New(zap.UseDevMode(false)))
|
||||
logger := ctrl.Log.WithName("setup")
|
||||
|
||||
cephCfg, endpoint, err := cephConfigFromEnv()
|
||||
if err != nil {
|
||||
logger.Error(err, "invalid Ceph dashboard configuration")
|
||||
os.Exit(1)
|
||||
}
|
||||
cephClient, err := ceph.NewClient(cephCfg)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to build Ceph dashboard client")
|
||||
os.Exit(1)
|
||||
}
|
||||
// Fail fast on obviously-broken credentials, but do not block startup on a
|
||||
// transiently unreachable dashboard.
|
||||
pingCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
if err := cephClient.Ping(pingCtx); err != nil {
|
||||
logger.Error(err, "initial dashboard authentication failed; continuing and will retry per-reconcile")
|
||||
}
|
||||
cancel()
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsserver.Options{BindAddress: metricsAddr},
|
||||
HealthProbeBindAddress: probeAddr,
|
||||
LeaderElection: leaderElect,
|
||||
LeaderElectionID: "cephrgw-operator",
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to create manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := controller.SetupAll(mgr, cephClient, endpoint); err != nil {
|
||||
logger.Error(err, "unable to set up controllers")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
logger.Error(err, "unable to set up health check")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
|
||||
logger.Error(err, "unable to set up ready check")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Info("starting cephrgw-operator")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
logger.Error(err, "manager exited with error")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// cephConfigFromEnv reads dashboard connection settings from the environment,
|
||||
// which the deployment sources from the cephrgw-credentials Secret.
|
||||
func cephConfigFromEnv() (ceph.Config, string, error) {
|
||||
cfg := ceph.Config{
|
||||
BaseURL: os.Getenv("CEPH_DASHBOARD_URL"),
|
||||
Username: os.Getenv("CEPH_DASHBOARD_USERNAME"),
|
||||
Password: os.Getenv("CEPH_DASHBOARD_PASSWORD"),
|
||||
Insecure: os.Getenv("CEPH_DASHBOARD_INSECURE") == "true",
|
||||
}
|
||||
if f := os.Getenv("CEPH_DASHBOARD_CA_FILE"); f != "" {
|
||||
b, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return cfg, "", err
|
||||
}
|
||||
cfg.CACert = b
|
||||
} else if inline := os.Getenv("CEPH_DASHBOARD_CA"); inline != "" {
|
||||
cfg.CACert = []byte(inline)
|
||||
}
|
||||
endpoint := os.Getenv("CEPH_RGW_ENDPOINT")
|
||||
return cfg, endpoint, nil
|
||||
}
|
||||
Reference in New Issue
Block a user