Zanzana: Refactor remote client initialization (#114142)

* Zanzana: Refactor remote client

* rename config field URL to Addr

* Instrument grpc queries

* fix duplicated field
This commit is contained in:
Alexander Zobnin
2025-12-11 10:55:12 +01:00
committed by GitHub
parent d48455cd20
commit e2d12f4cce
6 changed files with 55 additions and 32 deletions
+3 -2
View File
@@ -4,6 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator" "github.com/grafana/grafana-app-sdk/operator"
@@ -12,7 +14,6 @@ import (
foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/apps/iam/pkg/reconcilers" "github.com/grafana/grafana/apps/iam/pkg/reconcilers"
"github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/authz"
"github.com/prometheus/client_golang/prometheus"
) )
var appManifestData = app.ManifestData{ var appManifestData = app.ManifestData{
@@ -78,7 +79,7 @@ func New(cfg app.Config) (app.App, error) {
folderReconciler, err := reconcilers.NewFolderReconciler(reconcilers.ReconcilerConfig{ folderReconciler, err := reconcilers.NewFolderReconciler(reconcilers.ReconcilerConfig{
ZanzanaCfg: appSpecificConfig.ZanzanaClientCfg, ZanzanaCfg: appSpecificConfig.ZanzanaClientCfg,
Metrics: metrics, Metrics: metrics,
}) }, appSpecificConfig.MetricsRegisterer)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create FolderReconciler: %w", err) return nil, fmt.Errorf("unable to create FolderReconciler: %w", err)
} }
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
@@ -35,9 +36,9 @@ type FolderReconciler struct {
metrics *ReconcilerMetrics metrics *ReconcilerMetrics
} }
func NewFolderReconciler(cfg ReconcilerConfig) (operator.Reconciler, error) { func NewFolderReconciler(cfg ReconcilerConfig, reg prometheus.Registerer) (operator.Reconciler, error) {
// Create Zanzana client // Create Zanzana client
zanzanaClient, err := authz.NewRemoteZanzanaClient("*", cfg.ZanzanaCfg) zanzanaClient, err := authz.NewRemoteZanzanaClient(cfg.ZanzanaCfg, reg)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create zanzana client: %w", err) return nil, fmt.Errorf("unable to create zanzana client: %w", err)
@@ -11,15 +11,16 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/client-go/rest"
"k8s.io/client-go/transport"
"github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator" "github.com/grafana/grafana-app-sdk/operator"
folder "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" folder "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/apps/iam/pkg/app" "github.com/grafana/grafana/apps/iam/pkg/app"
"github.com/grafana/grafana/pkg/server" "github.com/grafana/grafana/pkg/server"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/client-go/rest"
"k8s.io/client-go/transport"
"github.com/grafana/authlib/authn" "github.com/grafana/authlib/authn"
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
@@ -95,7 +96,7 @@ func buildIAMConfigFromSettings(cfg *setting.Cfg, registerer prometheus.Register
if zanzanaURL == "" { if zanzanaURL == "" {
return nil, fmt.Errorf("zanzana_url is required in [operator] section") return nil, fmt.Errorf("zanzana_url is required in [operator] section")
} }
iamCfg.AppConfig.ZanzanaClientCfg.URL = zanzanaURL iamCfg.AppConfig.ZanzanaClientCfg.Addr = zanzanaURL
iamCfg.AppConfig.InformerConfig.MaxConcurrentWorkers = operatorSec.Key("max_concurrent_workers").MustUint64(20) iamCfg.AppConfig.InformerConfig.MaxConcurrentWorkers = operatorSec.Key("max_concurrent_workers").MustUint64(20)
+1 -1
View File
@@ -152,7 +152,7 @@ func ProvideStandaloneAuthZClient(
//nolint:staticcheck // not yet migrated to OpenFeature //nolint:staticcheck // not yet migrated to OpenFeature
zanzanaEnabled := features.IsEnabledGlobally(featuremgmt.FlagZanzana) zanzanaEnabled := features.IsEnabledGlobally(featuremgmt.FlagZanzana)
zanzanaClient, err := ProvideStandaloneZanzanaClient(cfg, features) zanzanaClient, err := ProvideStandaloneZanzanaClient(cfg, features, reg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+31 -20
View File
@@ -4,16 +4,19 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"time"
"github.com/fullstorydev/grpchan/inprocgrpc" "github.com/fullstorydev/grpchan/inprocgrpc"
authnlib "github.com/grafana/authlib/authn" authnlib "github.com/grafana/authlib/authn"
authzv1 "github.com/grafana/authlib/authz/proto/v1" authzv1 "github.com/grafana/authlib/authz/proto/v1"
"github.com/grafana/authlib/grpcutils" "github.com/grafana/authlib/grpcutils"
"github.com/grafana/authlib/types" "github.com/grafana/authlib/types"
"github.com/grafana/dskit/middleware"
"github.com/grafana/dskit/services" "github.com/grafana/dskit/services"
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
openfgav1 "github.com/openfga/api/proto/openfga/v1" openfgav1 "github.com/openfga/api/proto/openfga/v1"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
@@ -43,14 +46,14 @@ func ProvideZanzanaClient(cfg *setting.Cfg, db db.DB, tracer tracing.Tracer, fea
switch cfg.ZanzanaClient.Mode { switch cfg.ZanzanaClient.Mode {
case setting.ZanzanaModeClient: case setting.ZanzanaModeClient:
return NewRemoteZanzanaClient( zanzanaConfig := ZanzanaClientConfig{
fmt.Sprintf("stacks-%s", cfg.StackID), Addr: cfg.ZanzanaClient.Addr,
ZanzanaClientConfig{ Token: cfg.ZanzanaClient.Token,
URL: cfg.ZanzanaClient.Addr, TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL,
Token: cfg.ZanzanaClient.Token, TokenNamespace: cfg.ZanzanaClient.TokenNamespace,
TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL, ServerCertFile: cfg.ZanzanaClient.ServerCertFile,
ServerCertFile: cfg.ZanzanaClient.ServerCertFile, }
}) return NewRemoteZanzanaClient(zanzanaConfig, reg)
case setting.ZanzanaModeEmbedded: case setting.ZanzanaModeEmbedded:
logger := log.New("zanzana.server") logger := log.New("zanzana.server")
@@ -97,32 +100,33 @@ func ProvideZanzanaClient(cfg *setting.Cfg, db db.DB, tracer tracing.Tracer, fea
// ProvideStandaloneZanzanaClient provides a standalone Zanzana client, without registering the Zanzana service. // ProvideStandaloneZanzanaClient provides a standalone Zanzana client, without registering the Zanzana service.
// Client connects to a remote Zanzana server specified in the configuration. // Client connects to a remote Zanzana server specified in the configuration.
func ProvideStandaloneZanzanaClient(cfg *setting.Cfg, features featuremgmt.FeatureToggles) (zanzana.Client, error) { func ProvideStandaloneZanzanaClient(cfg *setting.Cfg, features featuremgmt.FeatureToggles, reg prometheus.Registerer) (zanzana.Client, error) {
//nolint:staticcheck // not yet migrated to OpenFeature //nolint:staticcheck // not yet migrated to OpenFeature
if !features.IsEnabledGlobally(featuremgmt.FlagZanzana) { if !features.IsEnabledGlobally(featuremgmt.FlagZanzana) {
return zClient.NewNoopClient(), nil return zClient.NewNoopClient(), nil
} }
zanzanaConfig := ZanzanaClientConfig{ zanzanaConfig := ZanzanaClientConfig{
URL: cfg.ZanzanaClient.Addr, Addr: cfg.ZanzanaClient.Addr,
Token: cfg.ZanzanaClient.Token, Token: cfg.ZanzanaClient.Token,
TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL, TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL,
TokenNamespace: cfg.ZanzanaClient.TokenNamespace,
ServerCertFile: cfg.ZanzanaClient.ServerCertFile, ServerCertFile: cfg.ZanzanaClient.ServerCertFile,
} }
return NewRemoteZanzanaClient(cfg.ZanzanaClient.TokenNamespace, zanzanaConfig) return NewRemoteZanzanaClient(zanzanaConfig, reg)
} }
type ZanzanaClientConfig struct { type ZanzanaClientConfig struct {
URL string Addr string
Token string Token string
TokenExchangeURL string TokenExchangeURL string
ServerCertFile string
TokenNamespace string TokenNamespace string
ServerCertFile string
} }
// NewRemoteZanzanaClient creates a new Zanzana client that connects to remote Zanzana server. // NewRemoteZanzanaClient creates a new Zanzana client that connects to remote Zanzana server.
func NewRemoteZanzanaClient(namespace string, cfg ZanzanaClientConfig) (zanzana.Client, error) { func NewRemoteZanzanaClient(cfg ZanzanaClientConfig, reg prometheus.Registerer) (zanzana.Client, error) {
tokenClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{ tokenClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{
Token: cfg.Token, Token: cfg.Token,
TokenExchangeURL: cfg.TokenExchangeURL, TokenExchangeURL: cfg.TokenExchangeURL,
@@ -139,18 +143,25 @@ func NewRemoteZanzanaClient(namespace string, cfg ZanzanaClientConfig) (zanzana.
} }
} }
authzRequestDuration := promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{
Name: "authz_zanzana_client_request_duration_seconds",
Help: "Time spent executing requests to zanzana server.",
NativeHistogramBucketFactor: 1.1,
NativeHistogramMaxBucketNumber: 160,
NativeHistogramMinResetDuration: time.Hour,
}, []string{"operation", "status_code"})
unaryInterceptors, streamInterceptors := instrument(authzRequestDuration, middleware.ReportGRPCStatusOption)
dialOptions := []grpc.DialOption{ dialOptions := []grpc.DialOption{
grpc.WithTransportCredentials(transportCredentials), grpc.WithTransportCredentials(transportCredentials),
grpc.WithPerRPCCredentials( grpc.WithPerRPCCredentials(
NewGRPCTokenAuth( NewGRPCTokenAuth(AuthzServiceAudience, cfg.TokenNamespace, tokenClient),
AuthzServiceAudience,
namespace,
tokenClient,
),
), ),
grpc.WithChainUnaryInterceptor(unaryInterceptors...),
grpc.WithChainStreamInterceptor(streamInterceptors...),
} }
conn, err := grpc.NewClient(cfg.URL, dialOptions...) conn, err := grpc.NewClient(cfg.Addr, dialOptions...)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err) return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err)
} }
+12 -3
View File
@@ -110,15 +110,24 @@ func (cfg *Cfg) readZanzanaSettings() {
zc.Mode = "embedded" zc.Mode = "embedded"
} }
zc.Token = clientSec.Key("token").MustString("")
zc.TokenExchangeURL = clientSec.Key("token_exchange_url").MustString("")
zc.Addr = clientSec.Key("address").MustString("") zc.Addr = clientSec.Key("address").MustString("")
zc.ServerCertFile = clientSec.Key("tls_cert").MustString("") zc.ServerCertFile = clientSec.Key("tls_cert").MustString("")
// TODO: read Token and TokenExchangeURL from grpc_client_authentication section
grpcClientAuthSection := cfg.SectionWithEnvOverrides("grpc_client_authentication") grpcClientAuthSection := cfg.SectionWithEnvOverrides("grpc_client_authentication")
zc.Token = grpcClientAuthSection.Key("token").MustString("")
zc.TokenExchangeURL = grpcClientAuthSection.Key("token_exchange_url").MustString("")
zc.TokenNamespace = grpcClientAuthSection.Key("token_namespace").MustString("stacks-" + cfg.StackID) zc.TokenNamespace = grpcClientAuthSection.Key("token_namespace").MustString("stacks-" + cfg.StackID)
// TODO: remove old settings when migrated
token := clientSec.Key("token").MustString("")
tokenExchangeURL := clientSec.Key("token_exchange_url").MustString("")
if token != "" {
zc.Token = token
}
if tokenExchangeURL != "" {
zc.TokenExchangeURL = tokenExchangeURL
}
cfg.ZanzanaClient = zc cfg.ZanzanaClient = zc
zs := ZanzanaServerSettings{} zs := ZanzanaServerSettings{}