Skip to content

Commit 362bc56

Browse files
authored
Merge pull request #626 from souleb/reuse-index-pool
Add optional in-memory cache of HelmRepository index files
2 parents 65b7468 + 7ff96a8 commit 362bc56

File tree

9 files changed

+489
-19
lines changed

9 files changed

+489
-19
lines changed

api/v1beta2/condition_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,7 @@ const (
9797
// ArtifactUpToDateReason signals that an existing Artifact is up-to-date
9898
// with the Source.
9999
ArtifactUpToDateReason string = "ArtifactUpToDate"
100+
101+
// CacheOperationFailedReason signals a failure in cache operation.
102+
CacheOperationFailedReason string = "CacheOperationFailed"
100103
)

controllers/helmchart_controller.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
securejoin "github.com/cyphar/filepath-securejoin"
3232
helmgetter "helm.sh/helm/v3/pkg/getter"
33+
helmrepo "helm.sh/helm/v3/pkg/repo"
3334
corev1 "k8s.io/api/core/v1"
3435
apierrs "k8s.io/apimachinery/pkg/api/errors"
3536
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -55,6 +56,7 @@ import (
5556
"github.com/fluxcd/pkg/untar"
5657

5758
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
59+
"github.com/fluxcd/source-controller/internal/cache"
5860
serror "github.com/fluxcd/source-controller/internal/error"
5961
"github.com/fluxcd/source-controller/internal/helm/chart"
6062
"github.com/fluxcd/source-controller/internal/helm/getter"
@@ -111,6 +113,9 @@ type HelmChartReconciler struct {
111113
Storage *Storage
112114
Getters helmgetter.Providers
113115
ControllerName string
116+
117+
Cache *cache.Cache
118+
TTL time.Duration
114119
}
115120

116121
func (r *HelmChartReconciler) SetupWithManager(mgr ctrl.Manager) error {
@@ -456,6 +461,13 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
456461
}
457462
}
458463

464+
// Try to retrieve the repository index from the cache
465+
if r.Cache != nil {
466+
if index, found := r.Cache.Get(r.Storage.LocalPath(*repo.GetArtifact())); found {
467+
chartRepo.Index = index.(*helmrepo.IndexFile)
468+
}
469+
}
470+
459471
// Construct the chart builder with scoped configuration
460472
cb := chart.NewRemoteBuilder(chartRepo)
461473
opts := chart.BuildOptions{
@@ -479,6 +491,26 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
479491
return sreconcile.ResultEmpty, err
480492
}
481493

494+
defer func() {
495+
// Cache the index if it was successfully retrieved
496+
// and the chart was successfully built
497+
if r.Cache != nil && chartRepo.Index != nil {
498+
// The cache key have to be safe in multi-tenancy environments,
499+
// as otherwise it could be used as a vector to bypass the helm repository's authentication.
500+
// Using r.Storage.LocalPath(*repo.GetArtifact() is safe as the path is in the format /<helm-repository-name>/<chart-name>/<filename>.
501+
err := r.Cache.Set(r.Storage.LocalPath(*repo.GetArtifact()), chartRepo.Index, r.TTL)
502+
if err != nil {
503+
r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.CacheOperationFailedReason, "failed to cache index: %s", err)
504+
}
505+
506+
}
507+
508+
// Delete the index reference
509+
if chartRepo.Index != nil {
510+
chartRepo.Unload()
511+
}
512+
}()
513+
482514
*b = *build
483515
return sreconcile.ResultSuccess, nil
484516
}

controllers/suite_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/fluxcd/pkg/testserver"
3636

3737
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
38+
"github.com/fluxcd/source-controller/internal/cache"
3839
// +kubebuilder:scaffold:imports
3940
)
4041

@@ -126,12 +127,15 @@ func TestMain(m *testing.M) {
126127
panic(fmt.Sprintf("Failed to start HelmRepositoryReconciler: %v", err))
127128
}
128129

130+
cache := cache.New(5, 1*time.Second)
129131
if err := (&HelmChartReconciler{
130132
Client: testEnv,
131133
EventRecorder: record.NewFakeRecorder(32),
132134
Metrics: testMetricsH,
133135
Getters: testGetters,
134136
Storage: testStorage,
137+
Cache: cache,
138+
TTL: 1 * time.Second,
135139
}).SetupWithManager(testEnv); err != nil {
136140
panic(fmt.Sprintf("Failed to start HelmRepositoryReconciler: %v", err))
137141
}

docs/spec/v1beta2/helmcharts.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,53 @@ Besides being reported in Events, the reconciliation errors are also logged by
390390
the controller. The Flux CLI offer commands for filtering the logs for a
391391
specific HelmChart, e.g. `flux logs --level=error --kind=HelmChart --name=<chart-name>`.
392392

393+
### Improving resource consumption by enabling the cache
394+
395+
When using a `HelmRepository` as Source for a `HelmChart`, the controller loads
396+
the repository index in memory to find the latest version of the chart.
397+
398+
The controller can be configured to cache Helm repository indexes in memory.
399+
The cache is used to avoid loading repository indexes for every `HelmChart`
400+
reconciliation.
401+
402+
The following flags are provided to enable and configure the cache:
403+
- `helm-cache-max-size`: The maximum size of the cache in number of indexes.
404+
If `0`, then the cache is disabled.
405+
- `helm-cache-ttl`: The TTL of an index in the cache.
406+
- `helm-cache-purge-interval`: The interval at which the cache is purged of
407+
expired items.
408+
409+
The caching strategy is to pull a repository index from the cache if it is
410+
available, otherwise to load the index, retrieve and build the chart,
411+
then cache the index. The cached index TTL is refreshed every time the
412+
Helm repository index is loaded with the `helm-cache-ttl` value.
413+
414+
The cache is purged of expired items every `helm-cache-purge-interval`.
415+
416+
When the cache is full, no more items can be added to the cache, and the
417+
source-controller will report a warning event instead.
418+
419+
In order to use the cache, set the related flags in the source-controller
420+
Deployment config:
421+
422+
```yaml
423+
spec:
424+
containers:
425+
- args:
426+
- --watch-all-namespaces
427+
- --log-level=info
428+
- --log-encoding=json
429+
- --enable-leader-election
430+
- --storage-path=/data
431+
- --storage-adv-addr=source-controller.$(RUNTIME_NAMESPACE).svc.cluster.local.
432+
## Helm cache with up to 10 items, i.e. 10 indexes.
433+
- --helm-cache-max-size=10
434+
## TTL of an index is 1 hour.
435+
- --helm-cache-ttl=1h
436+
## Purge expired index every 10 minutes.
437+
- --helm-cache-purge-interval=10m
438+
```
439+
393440
## HelmChart Status
394441

395442
### Artifact

internal/cache/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2012-2019 Patrick Mylund Nielsen and the go-cache contributors
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

0 commit comments

Comments
 (0)