Retry with exponential backoff when fetching artifacts

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan 2021-02-26 13:37:45 +02:00
parent 83e1cca067
commit a8dcafaf2e
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF
4 changed files with 31 additions and 8 deletions

View File

@ -24,6 +24,7 @@ import (
"time" "time"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"github.com/hashicorp/go-retryablehttp"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
@ -71,6 +72,7 @@ import (
// HelmReleaseReconciler reconciles a HelmRelease object // HelmReleaseReconciler reconciles a HelmRelease object
type HelmReleaseReconciler struct { type HelmReleaseReconciler struct {
client.Client client.Client
httpClient *retryablehttp.Client
Config *rest.Config Config *rest.Config
Scheme *runtime.Scheme Scheme *runtime.Scheme
requeueDependency time.Duration requeueDependency time.Duration
@ -93,6 +95,16 @@ func (r *HelmReleaseReconciler) SetupWithManager(mgr ctrl.Manager, opts HelmRele
} }
r.requeueDependency = opts.DependencyRequeueInterval r.requeueDependency = opts.DependencyRequeueInterval
// Configure the retryable http client used for fetching artifacts.
// By default it retries 10 times within a 3.5 minutes window.
httpClient := retryablehttp.NewClient()
httpClient.RetryWaitMin = 5 * time.Second
httpClient.RetryWaitMax = 30 * time.Second
httpClient.RetryMax = opts.HTTPRetry
httpClient.Logger = nil
r.httpClient = httpClient
return ctrl.NewControllerManagedBy(mgr). return ctrl.NewControllerManagedBy(mgr).
For(&v2.HelmRelease{}, builder.WithPredicates( For(&v2.HelmRelease{}, builder.WithPredicates(
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}), predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
@ -254,6 +266,7 @@ func (r *HelmReleaseReconciler) reconcile(ctx context.Context, hr v2.HelmRelease
type HelmReleaseReconcilerOptions struct { type HelmReleaseReconcilerOptions struct {
MaxConcurrentReconciles int MaxConcurrentReconciles int
HTTPRetry int
DependencyRequeueInterval time.Duration DependencyRequeueInterval time.Duration
} }

View File

@ -27,6 +27,7 @@ import (
"strings" "strings"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"github.com/hashicorp/go-retryablehttp"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -109,17 +110,22 @@ func (r *HelmReleaseReconciler) loadHelmChart(source *sourcev1.HelmChart) (*char
artifactURL = u.String() artifactURL = u.String()
} }
res, err := http.Get(artifactURL) req, err := retryablehttp.NewRequest(http.MethodGet, artifactURL, nil)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to create a new request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("artifact '%s' download failed (status code: %s)", source.GetArtifact().URL, res.Status)
} }
if _, err = io.Copy(f, res.Body); err != nil { resp, err := r.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to download artifact, error: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("artifact '%s' download failed (status code: %s)", source.GetArtifact().URL, resp.Status)
}
if _, err = io.Copy(f, resp.Body); err != nil {
return nil, err return nil, err
} }

1
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/fluxcd/pkg/runtime v0.8.3 github.com/fluxcd/pkg/runtime v0.8.3
github.com/fluxcd/source-controller/api v0.9.0 github.com/fluxcd/source-controller/api v0.9.0
github.com/go-logr/logr v0.3.0 github.com/go-logr/logr v0.3.0
github.com/hashicorp/go-retryablehttp v0.6.8
github.com/onsi/ginkgo v1.14.1 github.com/onsi/ginkgo v1.14.1
github.com/onsi/gomega v1.10.2 github.com/onsi/gomega v1.10.2
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5

View File

@ -62,6 +62,7 @@ func main() {
concurrent int concurrent int
requeueDependency time.Duration requeueDependency time.Duration
watchAllNamespaces bool watchAllNamespaces bool
httpRetry int
clientOptions client.Options clientOptions client.Options
logOptions logger.Options logOptions logger.Options
) )
@ -76,6 +77,7 @@ func main() {
flag.DurationVar(&requeueDependency, "requeue-dependency", 30*time.Second, "The interval at which failing dependencies are reevaluated.") flag.DurationVar(&requeueDependency, "requeue-dependency", 30*time.Second, "The interval at which failing dependencies are reevaluated.")
flag.BoolVar(&watchAllNamespaces, "watch-all-namespaces", true, flag.BoolVar(&watchAllNamespaces, "watch-all-namespaces", true,
"Watch for custom resources in all namespaces, if set to false it will only watch the runtime namespace.") "Watch for custom resources in all namespaces, if set to false it will only watch the runtime namespace.")
flag.IntVar(&httpRetry, "http-retry", 9, "The maximum number of retries when failing to fetch artifacts over HTTP.")
flag.CommandLine.MarkDeprecated("log-json", "Please use --log-encoding=json instead.") flag.CommandLine.MarkDeprecated("log-json", "Please use --log-encoding=json instead.")
clientOptions.BindFlags(flag.CommandLine) clientOptions.BindFlags(flag.CommandLine)
logOptions.BindFlags(flag.CommandLine) logOptions.BindFlags(flag.CommandLine)
@ -130,6 +132,7 @@ func main() {
}).SetupWithManager(mgr, controllers.HelmReleaseReconcilerOptions{ }).SetupWithManager(mgr, controllers.HelmReleaseReconcilerOptions{
MaxConcurrentReconciles: concurrent, MaxConcurrentReconciles: concurrent,
DependencyRequeueInterval: requeueDependency, DependencyRequeueInterval: requeueDependency,
HTTPRetry: httpRetry,
}); err != nil { }); err != nil {
setupLog.Error(err, "unable to create controller", "controller", v2.HelmReleaseKind) setupLog.Error(err, "unable to create controller", "controller", v2.HelmReleaseKind)
os.Exit(1) os.Exit(1)