diff --git a/Dockerfile b/Dockerfile index d09ddad..b1a2670 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ RUN apk add --no-cache ca-certificates && \ COPY --from=builder /go/src/github.com/AliyunContainerService/ack-secret-manager/build/bin/ack-secret-manager /bin/ack-secret-manager #ADD ./build/bin/ack-secret-manager /bin/ack-secret-manager -CMD ["./ack-secret-manager"] +CMD ["./ack-secret-manager"] \ No newline at end of file diff --git a/charts/ack-secret-manager/Chart.yaml b/charts/ack-secret-manager/Chart.yaml index 4455e50..23a8a60 100644 --- a/charts/ack-secret-manager/Chart.yaml +++ b/charts/ack-secret-manager/Chart.yaml @@ -1,14 +1,14 @@ apiVersion: v1 name: ack-secret-manager -version: 0.5.0 -appVersion: 0.5.0 +version: 0.5.2 +appVersion: 0.5.2 description: Provides external secret definitions for importing secret managed in Alibaba Cloud KMS secret-manager keywords: - secret-manager - secrets - namespace:kube-system - releaseName:ack-secret-manager - - arch:amd64 + - arch:amd64,arm64 home: https://github.com/AliyunContainerService/ack-secret-manager sources: - https://github.com/AliyunContainerService/ack-secret-manager \ No newline at end of file diff --git a/charts/ack-secret-manager/README.md b/charts/ack-secret-manager/README.md index 3038917..9151482 100644 --- a/charts/ack-secret-manager/README.md +++ b/charts/ack-secret-manager/README.md @@ -89,6 +89,7 @@ | command.region | 从指定region拉取secret凭据 | | | command.disablePolling | 关闭从KMS后端自动同步拉取最新的凭据内容,默认false | false | | command.pollingInterval | 从KMS后端同步存量secret实例的间隔时间 | 120s | +| command.maxConcurrentSecretPulls | secret 同步的最大并发数量 | 5 | | image.repository | 指定的ack-secret-manager 镜像仓库名称 | acs/ack-secret-manager | | image.tag | 指定的ack-secret-manager 镜像tag | v0.5.0 | | image.pullPolicy | 镜像拉取策略,默认为Always | Always | @@ -486,4 +487,7 @@ ack-secret-manager 涉及了两种 CRD,SecretStore 用于存放访问凭据( | 版本号 | 变更时间 | 变更内容 | | ------- | -------------- | ------------------------------------------------------------ | | `0.4.0` | 2022年12月22日 | 支持基于JMES解析提取JSON格式的密文字段 | -| `0.5.0` | 2023年10月10日 | 1.支持专属版 KMS 凭据同步
2.多阿里云访问凭据管理
3.凭据自解析与键规则替换
4.共享版 KMS 跨账号凭据同步 | \ No newline at end of file +| `0.5.0` | 2023年10月10日 | 1.支持专属版 KMS 凭据同步
2.多阿里云访问凭据管理
3.凭据自解析与键规则替换
4.共享版 KMS 跨账号凭据同步 | +| `0.5.1` | 2023年10月18日 | 部分功能与性能优化 | +| `0.5.2` | 2024年8月1日 | 大规模资源同步并发优化 | + diff --git a/charts/ack-secret-manager/README_EN.md b/charts/ack-secret-manager/README_EN.md index 32c4f7b..e27aac5 100644 --- a/charts/ack-secret-manager/README_EN.md +++ b/charts/ack-secret-manager/README_EN.md @@ -87,6 +87,7 @@ Make sure that the current account has sufficient permissions to access the Alib | command.region | Pull secret credentials from the specified region | | | command.disablePolling | Turn off automatic synchronization of pulling the latest credential content from the KMS backend, default false | false | | command.pollingInterval | The interval for synchronizing existing secret instances from the KMS backend | 120s | +| command.maxConcurrentSecretPulls | Maximum concurrent synchronization of secrets | 5 | | image.repository | Specified ack-secret-manager mirror warehouse name | acs/ack-secret-manager | | image.tag | Specified ack-secret-manager image tag | v0.5.0 | | image.pullPolicy | Image pull strategy, default is Always | Always | @@ -482,4 +483,6 @@ ack-secret-manager involves two CRDs. SecretStore is used to store access creden | Version | Date | Changes | | ------- | ---------- | ------------------------------------------------------------ | | `0.4.0` | 2022/12/22 | Support sync specific key-value pairs extract from a JSON-formatted secret based on JMES path | -| `0.5.0` | 2023/10/10 | 1.dedicated KMS credential synchronization
2.multiple Alibaba Cloud access credentials management,
3.self-resolving credentials and key rule replacement
4.shared KMS cross-account credential synchronization. | \ No newline at end of file +| `0.5.0` | 2023/10/10 | 1.dedicated KMS credential synchronization
2.multiple Alibaba Cloud access credentials management,
3.self-resolving credentials and key rule replacement
4.shared KMS cross-account credential synchronization. | +| `0.5.1` | 2023/10/18 | Function and performance optimization | +| `0.5.2` | 2024/08/01 | Large-scale resource synchronization concurrency optimization | \ No newline at end of file diff --git a/charts/ack-secret-manager/values.yaml b/charts/ack-secret-manager/values.yaml index aed689d..907dc14 100644 --- a/charts/ack-secret-manager/values.yaml +++ b/charts/ack-secret-manager/values.yaml @@ -11,6 +11,7 @@ command: region: disablePolling: false pollingInterval: 120s + maxConcurrentSecretPulls: 5 # watchamespaces: # excludeamespaces: @@ -65,7 +66,7 @@ replicaCount: 2 image: repository: registry-cn-hangzhou.ack.aliyuncs.com/acs/ack-secret-manager - tag: v0.5.0 + tag: v0.5.2 pullPolicy: Always cleanupImage: diff --git a/cmd/manager/main.go b/cmd/manager/main.go index f784c2a..982bb97 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -19,6 +19,7 @@ import ( "context" "flag" "fmt" + "golang.org/x/sync/semaphore" "os" "runtime" "strings" @@ -67,6 +68,7 @@ func main() { var excludeNamespaces string var region string var tokenRotationPeriod time.Duration + var maxConcurrentSecretPulls int flag.StringVar(&selectedBackend, "backend", "alicloud-kms", "Selected backend. Only alicloud-kms supported") flag.DurationVar(&rotationInterval, "polling-interval", 120*time.Second, "How often the controller will sync existing secret from kms") @@ -77,6 +79,8 @@ func main() { flag.StringVar(®ion, "region", "", "Region id, change it according to where you want to pull the secret from") flag.StringVar(&watchNamespaces, "watch-namespaces", "", "Comma separated list of namespaces that ack-secret-manager watch. By default all namespaces are watched.") flag.StringVar(&excludeNamespaces, "exclude-namespaces", "", "Comma separated list of namespaces that that ack-secret-manager will not watch. By default all namespaces are watched.") + flag.IntVar(&maxConcurrentSecretPulls, "max-concurrent-secret-pulls", 5, "used to control how many secrets are pulled at the same time.\n\n") + flag.Parse() ctrl.SetLogger(zap.New()) @@ -100,9 +104,13 @@ func main() { if region == "" || region != instanceRegion { region = instanceRegion } + opts := &backend.ProviderOptions{ + Region: region, + MaxConcurrent: maxConcurrentSecretPulls, + } for providerName, f := range backend.SupportProvider { log.Info("new provider ", providerName) - f(region) + f(opts) } err = backend.NewProviderClientByENV(ctx, region) @@ -142,6 +150,7 @@ func main() { watchNs[ns] = false } } + w := semaphore.NewWeighted(int64(opts.MaxConcurrent)) esReconciler := externalsecret.ExternalSecretReconciler{ Client: mgr.GetClient(), APIReader: mgr.GetAPIReader(), @@ -150,6 +159,7 @@ func main() { ReconciliationPeriod: reconcilePeriod, WatchNamespaces: watchNs, RotationInterval: rotationInterval, + ConcurrentController: w, } err = (&esReconciler).SetupWithManager(mgr, reconcileCount) if err != nil { diff --git a/go.mod b/go.mod index 271d3ce..f4ce447 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AliyunContainerService/ack-secret-manager go 1.18 require ( - github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.10.0 + github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.13.0 github.com/alibabacloud-go/darabonba-openapi v0.1.4 github.com/alibabacloud-go/kms-20160120/v2 v2.0.0 github.com/alibabacloud-go/tea v1.2.1 @@ -75,6 +75,7 @@ require ( golang.org/x/crypto v0.10.0 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/term v0.9.0 // indirect golang.org/x/text v0.10.0 // indirect diff --git a/go.sum b/go.sum index 3bcce0d..c5b4114 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,10 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.10.0 h1:I/2/vwxNbykn6fQViKe/EDo578RdPX3+4R1Rs5yLNjU= -github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.10.0/go.mod h1:ULtI7L9xkNeJ07YNqSeT5EhjQAl1CpTgPcUn4KoNcuc= +github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.12.0 h1:nUTgdlM3aLzOHQhdxrtUdO2T4vqBoW3kn6C9QIeClKk= +github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.12.0/go.mod h1:tlqp9mUGbsP+0z3Q+c0Q5MgSdq/OMwQhm5bffR3Q3ss= +github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.13.0 h1:J6JXctkQI6QCdPOKTyERNf2KdgNkBVQfwJUn2qy12TQ= +github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.13.0/go.mod h1:tlqp9mUGbsP+0z3Q+c0Q5MgSdq/OMwQhm5bffR3Q3ss= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= @@ -671,6 +673,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/pkg/backend/kms/auth.go b/pkg/backend/kms/auth.go index 978e1af..11708ff 100644 --- a/pkg/backend/kms/auth.go +++ b/pkg/backend/kms/auth.go @@ -32,6 +32,7 @@ type authConfig struct { func (a *authConfig) getKMSAuthCred(region string, p *Provider) (credentials.Credential, error) { providers := make([]provider.CredentialsProvider, 0) + var semaphoreProvider *provider.SemaphoreProvider if a.oidcArn != "" && a.roleArn != "" { oidcProvider := provider.NewOIDCProvider(provider.OIDCProviderOptions{ STSEndpoint: provider.GetSTSEndpoint(region, true), @@ -60,20 +61,24 @@ func (a *authConfig) getKMSAuthCred(region string, p *Provider) (credentials.Cre })) chainProvider := provider.NewChainProvider(providers...) var remoteRoleProvider *provider.RoleArnProvider + var cred *provider.CredentialForV2SDK if a.remoteRoleArn != "" && a.remoteRoleSessionName != "" { remoteRoleProvider = provider.NewRoleArnProvider(chainProvider, a.remoteRoleArn, provider.RoleArnProviderOptions{ STSEndpoint: provider.GetSTSEndpoint(region, true), SessionName: a.remoteRoleSessionName, RefreshPeriod: a.refreshPeriod, }) - } - var cred *provider.CredentialForV2SDK - if remoteRoleProvider != nil { - p.RegisterRamProvider(a.clientName, remoteRoleProvider) - cred = provider.NewCredentialForV2SDK(remoteRoleProvider, provider.CredentialForV2SDKOptions{}) + semaphoreProvider = provider.NewSemaphoreProvider(remoteRoleProvider, provider.SemaphoreProviderOptions{ + MaxWeight: int64(p.maxConcurrentCount), + }) } else { - p.RegisterRamProvider(a.clientName, chainProvider) - cred = provider.NewCredentialForV2SDK(chainProvider, provider.CredentialForV2SDKOptions{}) + semaphoreProvider = provider.NewSemaphoreProvider(chainProvider, provider.SemaphoreProviderOptions{ + MaxWeight: int64(p.maxConcurrentCount), + }) } + p.RegisterRamProvider(a.clientName, semaphoreProvider) + cred = provider.NewCredentialForV2SDK(semaphoreProvider, provider.CredentialForV2SDKOptions{ + CredentialRetrievalTimeout: 10 * time.Minute, + }) return cred, nil } diff --git a/pkg/backend/kms/client_manager.go b/pkg/backend/kms/client_manager.go index 7aca0da..514b4f9 100644 --- a/pkg/backend/kms/client_manager.go +++ b/pkg/backend/kms/client_manager.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sync" + "time" "k8s.io/klog" @@ -35,7 +36,10 @@ func (m *Manager) RegisterRamProvider(clientName string, stopper provider.Stoppe defer m.ramLock.Unlock() providerIns, ok := m.ramProvider[clientName] if ok { - providerIns.Stop(context.Background()) + timeoutCtx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) + // cancel is earlier than m.ramLock.Unlock + defer cancel() + providerIns.Stop(timeoutCtx) } m.ramProvider[clientName] = stopper klog.Infof("register provider %v success", clientName) @@ -48,7 +52,10 @@ func (m *Manager) StopProvider(clientName string) { if !ok || providerIns == nil { return } - providerIns.Stop(context.Background()) + timeoutCtx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) + // cancel is earlier than m.ramLock.Unlock + defer cancel() + providerIns.Stop(timeoutCtx) delete(m.ramProvider, clientName) klog.Infof("stop provider %v success", clientName) } diff --git a/pkg/backend/kms/client_provider.go b/pkg/backend/kms/client_provider.go index 5430566..903834f 100644 --- a/pkg/backend/kms/client_provider.go +++ b/pkg/backend/kms/client_provider.go @@ -36,15 +36,17 @@ func init() { // Provider provides the ability to generate kms clients and manage kms clients type Provider struct { *Manager - region string - name string + region string + name string + maxConcurrentCount int } -func NewProvider(region string) { +func NewProvider(opts *backend.ProviderOptions) { provider := &Provider{ - Manager: NewManager(region), - region: region, - name: ProviderName, + Manager: NewManager(opts.Region), + region: opts.Region, + name: ProviderName, + maxConcurrentCount: 5, } backend.RegisterProvider(ProviderName, provider) } diff --git a/pkg/backend/provider.go b/pkg/backend/provider.go index 2b03986..c5e8afe 100644 --- a/pkg/backend/provider.go +++ b/pkg/backend/provider.go @@ -12,7 +12,12 @@ import ( const EnvClient = "env.client" -type CreateProvider func(region string) +type CreateProvider func(opt *ProviderOptions) + +type ProviderOptions struct { + Region string + MaxConcurrent int +} var ( SupportProvider map[string]CreateProvider diff --git a/pkg/controller/externalsecret/secret_controller.go b/pkg/controller/externalsecret/secret_controller.go index 2e5916e..18b7199 100644 --- a/pkg/controller/externalsecret/secret_controller.go +++ b/pkg/controller/externalsecret/secret_controller.go @@ -19,6 +19,7 @@ import ( "context" "encoding/json" "fmt" + "golang.org/x/sync/semaphore" "reflect" "sync" "time" @@ -57,6 +58,7 @@ type ExternalSecretReconciler struct { rotationTicker *time.Ticker secrets sync.Map // secrets map is the cache for secrets. closing chan bool // close channel. + ConcurrentController *semaphore.Weighted } var ( @@ -255,9 +257,16 @@ func (r *ExternalSecretReconciler) rotate() { es := v.(*api.ExternalSecret) r.Log.Info("rotate checking secret", "index", k) // Re-generate secret if update in kms secret-manager. + timeoutContext, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + if err := r.ConcurrentController.Acquire(timeoutContext, 1); err != nil { + cancel() + return true + } wg.Add(1) go func() { defer wg.Done() + defer r.ConcurrentController.Release(1) + defer cancel() updated, _ := r.syncIfNeedUpdate(es) if updated { secretMap.Store(fmt.Sprintf("%s/%s", es.Namespace, es.Name), es)