Skip to content

Helm Releases

The HelmRelease API defines a resource for automated controller driven Helm releases.

Specification

A HelmRelease object defines a resource for controller driven reconciliation of Helm releases via Helm actions such as install, upgrade, test, uninstall, and rollback. This includes release placement (namespace/name), release content (chart/values overrides), action trigger configuration, individual action configuration, and statusing.

// HelmReleaseSpec defines the desired state of a Helm Release.
type HelmReleaseSpec struct {
    // Chart defines the template of the v1beta1.HelmChart that should be created
    // for this HelmRelease.
    // +required
    Chart HelmChartTemplate `json:"chart"`

    // Interval at which to reconcile the Helm release.
    // +required
    Interval metav1.Duration `json:"interval"`

    // KubeConfig for reconciling the HelmRelease on a remote cluster.
    // +optional
    KubeConfig *KubeConfig `json:"kubeConfig,omitempty"`

    // Suspend tells the controller to suspend reconciliation for this HelmRelease,
    // it does not apply to already started reconciliations. Defaults to false.
    // +optional
    Suspend bool `json:"suspend,omitempty"`

    // ReleaseName used for the Helm release. Defaults to a composition of
    // '[TargetNamespace-]Name'.
    // +kubebuilder:validation:MinLength=1
    // +kubebuilder:validation:MaxLength=53
    // +kubebuilder:validation:Optional
    // +optional
    ReleaseName string `json:"releaseName,omitempty"`

    // TargetNamespace to target when performing operations for the HelmRelease.
    // Defaults to the namespace of the HelmRelease.
    // +kubebuilder:validation:MinLength=1
    // +kubebuilder:validation:MaxLength=63
    // +kubebuilder:validation:Optional
    // +optional
    TargetNamespace string `json:"targetNamespace,omitempty"`

    // DependsOn may contain a dependency.CrossNamespaceDependencyReference slice with
    // references to HelmRelease resources that must be ready before this HelmRelease
    // can be reconciled.
    // +optional
    DependsOn []dependency.CrossNamespaceDependencyReference `json:"dependsOn,omitempty"`

    // Timeout is the time to wait for any individual Kubernetes operation (like Jobs
    // for hooks) during the performance of a Helm action. Defaults to '5m0s'.
    // +optional
    Timeout *metav1.Duration `json:"timeout,omitempty"`

    // MaxHistory is the number of revisions saved by Helm for this HelmRelease.
    // Use '0' for an unlimited number of revisions; defaults to '10'.
    // +optional
    MaxHistory *int `json:"maxHistory,omitempty"`

    // Install holds the configuration for Helm install actions for this HelmRelease.
    // +optional
    Install *Install `json:"install,omitempty"`

    // Upgrade holds the configuration for Helm upgrade actions for this HelmRelease.
    // +optional
    Upgrade *Upgrade `json:"upgrade,omitempty"`

    // Test holds the configuration for Helm test actions for this HelmRelease.
    // +optional
    Test *Test `json:"test,omitempty"`

    // Rollback holds the configuration for Helm rollback actions for this HelmRelease.
    // +optional
    Rollback *Rollback `json:"rollback,omitempty"`

    // Uninstall holds the configuration for Helm uninstall actions for this HelmRelease.
    // +optional
    Uninstall *Uninstall `json:"uninstall,omitempty"`

    // ValuesFrom holds references to resources containing Helm values for this HelmRelease,
    // and information about how they should be merged.
    ValuesFrom []ValuesReference `json:"valuesFrom,omitempty"`

    // Values holds the values for this Helm release.
    // +optional
    Values *apiextensionsv1.JSON `json:"values,omitempty"`
}

// KubeConfig references a Kubernetes secret that contains a kubeconfig file.
type KubeConfig struct {
    // SecretRef holds the name to a secret that contains a 'value' key with
    // the kubeconfig file as the value. It must be in the same namespace as
    // the HelmRelease.
    // It is recommended that the kubeconfig is self-contained, and the secret
    // is regularly updated if credentials such as a cloud-access-token expire.
    // Cloud specific `cmd-path` auth helpers will not function without adding
    // binaries and credentials to the Pod that is responsible for reconciling
    // the HelmRelease.
    // +required
    SecretRef corev1.LocalObjectReference `json:"secretRef,omitempty"`
}

// HelmChartTemplate defines the template from which the controller will
// generate a v1beta1.HelmChart object in the same namespace as the referenced
// v1beta1.Source.
type HelmChartTemplate struct {
    // Spec holds the template for the v1beta1.HelmChartSpec for this HelmRelease.
    // +required
    Spec HelmChartTemplateSpec `json:"spec"`
}

// HelmChartTemplateSpec defines the template from which the controller will
// generate a v1beta1.HelmChartSpec object.
type HelmChartTemplateSpec struct {
    // The name or path the Helm chart is available at in the SourceRef.
    // +required
    Chart string `json:"chart"`

    // Version semver expression, ignored for charts from v1beta1.GitRepository and
    // v1beta1.Bucket sources. Defaults to latest when omitted.
    // +optional
    Version string `json:"version,omitempty"`

    // The name and namespace of the v1beta1.Source the chart is available at.
    // +required
    SourceRef CrossNamespaceObjectReference `json:"sourceRef"`

    // Interval at which to check the v1beta1.Source for updates. Defaults to
    // 'HelmReleaseSpec.Interval'.
    // +optional
    Interval *metav1.Duration `json:"interval,omitempty"`

    // Alternative values file to use as the default chart values, expected to be a
    // relative path in the SourceRef. Ignored when omitted.
    // +optional
    ValuesFile string `json:"valuesFile,omitempty"`
}

// Install holds the configuration for Helm install actions performed for this
// HelmRelease.
type Install struct {
    // Timeout is the time to wait for any individual Kubernetes operation (like
    // Jobs for hooks) during the performance of a Helm install action. Defaults to
    // 'HelmReleaseSpec.Timeout'.
    // +optional
    Timeout *metav1.Duration `json:"timeout,omitempty"`

    // Remediation holds the remediation configuration for when the Helm install
    // action for the HelmRelease fails. The default is to not perform any action.
    // +optional
    Remediation *InstallRemediation `json:"remediation,omitempty"`

    // DisableWait disables the waiting for resources to be ready after a Helm
    // install has been performed.
    // +optional
    DisableWait bool `json:"disableWait,omitempty"`

    // DisableHooks prevents hooks from running during the Helm install action.
    // +optional
    DisableHooks bool `json:"disableHooks,omitempty"`

    // DisableOpenAPIValidation prevents the Helm install action from validating
    // rendered templates against the Kubernetes OpenAPI Schema.
    // +optional
    DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"`

    // Replace tells the Helm install action to re-use the 'ReleaseName', but only
    // if that name is a deleted release which remains in the history.
    // +optional
    Replace bool `json:"replace,omitempty"`

    // SkipCRDs tells the Helm install action to not install any CRDs. By default,
    // CRDs are installed if not already present.
    // +optional
    SkipCRDs bool `json:"skipCRDs,omitempty"`
}

// InstallRemediation holds the configuration for Helm install remediation.
type InstallRemediation struct {
    // Retries is the number of retries that should be attempted on failures before
    // bailing. Remediation, using an uninstall, is performed between each attempt.
    // Defaults to '0', a negative integer equals to unlimited retries.
    // +optional
    Retries int `json:"retries,omitempty"`

    // IgnoreTestFailures tells the controller to skip remediation when the Helm
    // tests are run after an install action but fail. Defaults to
    // 'Test.IgnoreFailures'.
    // +optional
    IgnoreTestFailures *bool `json:"ignoreTestFailures,omitempty"`

    // RemediateLastFailure tells the controller to remediate the last failure, when
    // no retries remain. Defaults to 'false'.
    // +optional
    RemediateLastFailure *bool `json:"remediateLastFailure,omitempty"`
}

// Upgrade holds the configuration for Helm upgrade actions for this
// HelmRelease.
type Upgrade struct {
    // Timeout is the time to wait for any individual Kubernetes operation (like
    // Jobs for hooks) during the performance of a Helm upgrade action. Defaults to
    // 'HelmReleaseSpec.Timeout'.
    // +optional
    Timeout *metav1.Duration `json:"timeout,omitempty"`

    // Remediation holds the remediation configuration for when the Helm upgrade
    // action for the HelmRelease fails. The default is to not perform any action.
    // +optional
    Remediation *UpgradeRemediation `json:"remediation,omitempty"`

    // DisableWait disables the waiting for resources to be ready after a Helm
    // upgrade has been performed.
    // +optional
    DisableWait bool `json:"disableWait,omitempty"`

    // DisableHooks prevents hooks from running during the Helm upgrade action.
    // +optional
    DisableHooks bool `json:"disableHooks,omitempty"`

    // DisableOpenAPIValidation prevents the Helm upgrade action from validating
    // rendered templates against the Kubernetes OpenAPI Schema.
    // +optional
    DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"`

    // Force forces resource updates through a replacement strategy.
    // +optional
    Force bool `json:"force,omitempty"`

    // PreserveValues will make Helm reuse the last release's values and merge in
    // overrides from 'Values'. Setting this flag makes the HelmRelease
    // non-declarative.
    // +optional
    PreserveValues bool `json:"preserveValues,omitempty"`

    // CleanupOnFail allows deletion of new resources created during the Helm
    // upgrade action when it fails.
    // +optional
    CleanupOnFail bool `json:"cleanupOnFail,omitempty"`
}

// UpgradeRemediation holds the configuration for Helm upgrade remediation.
type UpgradeRemediation struct {
    // Retries is the number of retries that should be attempted on failures before
    // bailing. Remediation, using 'Strategy', is performed between each attempt.
    // Defaults to '0', a negative integer equals to unlimited retries.
    // +optional
    Retries int `json:"retries,omitempty"`

    // IgnoreTestFailures tells the controller to skip remediation when the Helm
    // tests are run after an upgrade action but fail.
    // Defaults to 'Test.IgnoreFailures'.
    // +optional
    IgnoreTestFailures *bool `json:"ignoreTestFailures,omitempty"`

    // RemediateLastFailure tells the controller to remediate the last failure, when
    // no retries remain. Defaults to 'false' unless 'Retries' is greater than 0.
    // +optional
    RemediateLastFailure *bool `json:"remediateLastFailure,omitempty"`

    // Strategy to use for failure remediation. Defaults to 'rollback'.
    // +kubebuilder:validation:Enum=rollback;uninstall
    // +optional
    Strategy *RemediationStrategy `json:"strategy,omitempty"`
}

// Test holds the configuration for Helm test actions for this HelmRelease.
type Test struct {
    // Enable enables Helm test actions for this HelmRelease after an Helm install
    // or upgrade action has been performed.
    // +optional
    Enable bool `json:"enable,omitempty"`

    // Timeout is the time to wait for any individual Kubernetes operation during
    // the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'.
    // +optional
    Timeout *metav1.Duration `json:"timeout,omitempty"`

    // IgnoreFailures tells the controller to skip remediation when the Helm tests
    // are run but fail. Can be overwritten for tests run after install or upgrade
    // actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'.
    // +optional
    IgnoreFailures bool `json:"ignoreFailures,omitempty"`
}

// Rollback holds the configuration for Helm rollback actions for this
// HelmRelease.
type Rollback struct {
    // Timeout is the time to wait for any individual Kubernetes operation (like
    // Jobs for hooks) during the performance of a Helm rollback action. Defaults to
    // 'HelmReleaseSpec.Timeout'.
    // +optional
    Timeout *metav1.Duration `json:"timeout,omitempty"`

    // DisableWait disables the waiting for resources to be ready after a Helm
    // rollback has been performed.
    // +optional
    DisableWait bool `json:"disableWait,omitempty"`

    // DisableHooks prevents hooks from running during the Helm rollback action.
    // +optional
    DisableHooks bool `json:"disableHooks,omitempty"`

    // Recreate performs pod restarts for the resource if applicable.
    // +optional
    Recreate bool `json:"recreate,omitempty"`

    // Force forces resource updates through a replacement strategy.
    // +optional
    Force bool `json:"force,omitempty"`

    // CleanupOnFail allows deletion of new resources created during the Helm
    // rollback action when it fails.
    // +optional
    CleanupOnFail bool `json:"cleanupOnFail,omitempty"`
}

// Uninstall holds the configuration for Helm uninstall actions for this
// HelmRelease.
type Uninstall struct {
    // Timeout is the time to wait for any individual Kubernetes operation (like
    // Jobs for hooks) during the performance of a Helm uninstall action. Defaults
    // to 'HelmReleaseSpec.Timeout'.
    // +optional
    Timeout *metav1.Duration `json:"timeout,omitempty"`

    // DisableHooks prevents hooks from running during the Helm rollback action.
    // +optional
    DisableHooks bool `json:"disableHooks,omitempty"`

    // KeepHistory tells Helm to remove all associated resources and mark the
    // release as deleted, but retain the release history.
    // +optional
    KeepHistory bool `json:"keepHistory,omitempty"`
}

Reference types

// CrossNamespaceObjectReference contains enough information to let you locate the
// typed referenced object at cluster level.
type CrossNamespaceObjectReference struct {
    // APIVersion of the referent.
    // +optional
    APIVersion string `json:"apiVersion,omitempty"`

    // Kind of the referent.
    // +kubebuilder:validation:Enum=HelmRepository
    // +required
    Kind string `json:"kind,omitempty"`

    // Name of the referent.
    // +kubebuilder:validation:MinLength=1
    // +kubebuilder:validation:MaxLength=253
    // +required
    Name string `json:"name"`

    // Namespace of the referent.
    // +kubebuilder:validation:MinLength=1
    // +kubebuilder:validation:MaxLength=63
    // +kubebuilder:validation:Optional
    // +optional
    Namespace string `json:"namespace,omitempty"`
}

// ValuesReference contains a reference to a resource containing Helm values,
// and optionally the key they can be found at.
type ValuesReference struct {
    // Kind of the values referent, valid values are ('Secret', 'ConfigMap').
    // +kubebuilder:validation:Enum=Secret;ConfigMap
    // +required
    Kind string `json:"kind"`

    // Name of the values referent. Should reside in the same namespace as the
    // referring resource.
    // +kubebuilder:validation:MinLength=1
    // +kubebuilder:validation:MaxLength=253
    // +required
    Name string `json:"name"`

    // ValuesKey is the data key where the values.yaml or a specific value can be
    // found at. Defaults to 'values.yaml'.
    // +optional
    ValuesKey string `json:"valuesKey,omitempty"`

    // TargetPath is the YAML dot notation path the value should be merged at. When
    // set, the ValuesKey is expected to be a single flat value. Defaults to 'None',
    // which results in the values getting merged at the root.
    // +optional
    TargetPath string `json:"targetPath,omitempty"`

    // Optional marks this ValuesReference as optional. When set, a not found error
    // for the values reference is ignored, but any ValuesKey, TargetPath or
    // transient error will still result in a reconciliation failure.
    // +optional
    Optional bool `json:"optional,omitempty"`
}

Status specification

// HelmReleaseStatus defines the observed state of a HelmRelease.
type HelmReleaseStatus struct {
    // ObservedGeneration is the last observed generation.
    // +optional
    ObservedGeneration int64 `json:"observedGeneration,omitempty"`

    // LastHandledReconcileAt is the last manual reconciliation request (by
    // annotating the HelmRelease) handled by the reconciler.
    // +optional
    LastHandledReconcileAt string `json:"lastHandledReconcileAt,omitempty"`

    // Conditions holds the conditions for the HelmRelease.
    // +optional
    Conditions []meta.Condition `json:"conditions,omitempty"`

    // LastAppliedRevision is the revision of the last successfully applied source.
    // +optional
    LastAppliedRevision string `json:"lastAppliedRevision,omitempty"`

    // LastAttemptedRevision is the revision of the last reconciliation attempt.
    // +optional
    LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`

    // LastAttemptedValuesChecksum is the SHA1 checksum of the values of the last
    // reconciliation attempt.
    // +optional
    LastAttemptedValuesChecksum string `json:"lastAttemptedValuesChecksum,omitempty"`

    // LastReleaseRevision is the revision of the last successful Helm release.
    // +optional
    LastReleaseRevision int `json:"lastReleaseRevision,omitempty"`

    // HelmChart is the namespaced name of the HelmChart resource created by
    // the controller for the HelmRelease.
    // +optional
    HelmChart string `json:"helmChart,omitempty"`

    // Failures is the reconciliation failure count against the latest observed
    // state. It is reset after a successful reconciliation.
    // +optional
    Failures int64 `json:"failures,omitempty"`

    // InstallFailures is the install failure count against the latest observed
    // state. It is reset after a successful reconciliation.
    // +optional
    InstallFailures int64 `json:"installFailures,omitempty"`

    // UpgradeFailures is the upgrade failure count against the latest observed
    // state. It is reset after a successful reconciliation.
    // +optional
    UpgradeFailures int64 `json:"upgradeFailures,omitempty"`
}

Condition types

const (
    // ReleasedCondition represents the status of the last release attempt
    // (install/upgrade/test) against the latest desired state.
    ReleasedCondition string = "Released"

    // TestSuccessCondition represents the status of the last test attempt against
    // the latest desired state.
    TestSuccessCondition string = "TestSuccess"

    // RemediatedCondition represents the status of the last remediation attempt
    // (uninstall/rollback) due to a failure of the last release attempt against the
    // latest desired state.
    RemediatedCondition string = "Remediated"
)

Condition reasons

const (
    // InstallSucceededReason represents the fact that the Helm install for the
    // HelmRelease succeeded.
    InstallSucceededReason string = "InstallSucceeded"

    // InstallFailedReason represents the fact that the Helm install for the
    // HelmRelease failed.
    InstallFailedReason string = "InstallFailed"

    // UpgradeSucceededReason represents the fact that the Helm upgrade for the
    // HelmRelease succeeded.
    UpgradeSucceededReason string = "UpgradeSucceeded"

    // UpgradeFailedReason represents the fact that the Helm upgrade for the
    // HelmRelease failed.
    UpgradeFailedReason string = "UpgradeFailed"

    // TestSucceededReason represents the fact that the Helm tests for the
    // HelmRelease succeeded.
    TestSucceededReason string = "TestSucceeded"

    // TestFailedReason represents the fact that the Helm tests for the HelmRelease
    // failed.
    TestFailedReason string = "TestFailed"

    // RollbackSucceededReason represents the fact that the Helm rollback for the
    // HelmRelease succeeded.
    RollbackSucceededReason string = "RollbackSucceeded"

    // RollbackFailedReason represents the fact that the Helm test for the
    // HelmRelease failed.
    RollbackFailedReason string = "RollbackFailed"

    // UninstallSucceededReason represents the fact that the Helm uninstall for the
    // HelmRelease succeeded.
    UninstallSucceededReason string = "UninstallSucceeded"

    // UninstallFailedReason represents the fact that the Helm uninstall for the
    // HelmRelease failed.
    UninstallFailedReason string = "UninstallFailed"

    // ArtifactFailedReason represents the fact that the artifact download for the
    // HelmRelease failed.
    ArtifactFailedReason string = "ArtifactFailed"

    // InitFailedReason represents the fact that the initialization of the Helm
    // configuration failed.
    InitFailedReason string = "InitFailed"

    // GetLastReleaseFailedReason represents the fact that observing the last
    // release failed.
    GetLastReleaseFailedReason string = "GetLastReleaseFailed"
)

Helm release placement

The namespace/name in which to deploy the Helm release defaults to the namespace/name of the HelmRelease. These can be overridden respectively via spec.targetNamespace and spec.releaseName. If spec.targetNamespace is set, spec.releaseName defaults to <spec.targetNamespace>-<metadata.name>.

Note: that configuring the spec.targetNamespace only defines the namespace the release is made in, the metadata for the release (also known as the "Helm storage") will always be stored in the metadata.namespace of the HelmRelease.

Helm chart template

The spec.chart.spec values are used by the helm-controller as a template to create a new HelmChart resource with the given spec.

The spec.chart.spec.sourceRef is a reference to an object managed by source-controller. When the source revision changes, it generates a Kubernetes event that triggers a new release.

Supported source types:

The HelmChart is created in the same namespace as the sourceRef, with a name matching the HelmRelease <metadata.namespace>-<metadata.name>.

The chart.spec.chart can either contain:

  • The name of the chart as made available by the HelmRepository (without any aliases), for example: podinfo
  • The relative path the chart can be found at in the GitRepository, for example: ./charts/podinfo

The chart.spec.version can be a fixed semver, or any semver range (i.e. >=4.0.0 <5.0.0). It is ignored for HelmRelease resources that reference a GitRepository or Bucket source.

Values overrides

The simplest way to define values overrides is inline via spec.values. It is also possible to define a list of ConfigMap and Secret resources from which to take values via spec.valuesFrom. The values are merged in the order given, with the later values overwriting earlier, and then spec.values overwriting those:

spec:
  values:
    replicaCount: 2
  valuesFrom:
  - kind: ConfigMap
    name: prod-env-values
    valuesKey: values-prod.yaml
  - kind: Secret
    name: prod-tls-values
    valuesKey: crt
    targetPath: tls.crt
    optional: true

The definition of the listed keys for items in spec.valuesFrom is as follows:

  • kind: Kind of the values referent (ConfigMap or Secret).
  • name: Name of the values referent, in the same namespace as the HelmRelease.
  • valuesKey (Optional): The data key where the values.yaml or a specific value can be found. Defaults to values.yaml when omitted.
  • targetPath (Optional): The YAML dot notation path at which the value should be merged. When set, the valuesKey is expected to be a single flat value. Defaults to None when omitted, which results in the values getting merged at the root.
  • optional (Optional): Whether this values reference is optional. When true, a not found error for the values reference is ignored, but any valuesKey, targetPath or transient error will still result in a reconciliation failure. Defaults to false when omitted.

Note: that the targetPath supports the same formatting as you would supply as an argument to the helm binary using --set [path]=[value]. In addition to this, the referred value can contain the same value formats (e.g. {a,b,c} for a list). You can read more about the available formats and limitations in the Helm documentation.

Reconciliation

If no Helm release with the matching namespace/name is found it will be installed. It will be upgraded any time the desired state is updated, which consists of:

  • spec (and thus metadata.generation)
  • Latest HelmChart revision available
  • ConfigMap and Secret values overrides. Changes to these do not trigger an immediate reconciliation, but will be handled upon the next reconciliation. This is to avoid a large number of upgrades occurring when multiple resources are updated.

If the latest Helm release revision was not made by the helm-controller, it may not match the desired state, so an upgrade is made in this case as well.

The spec.interval tells the reconciler at which interval to reconcile the release. The interval time units are s, m and h e.g. interval: 5m, the minimum value should be 60 seconds.

The reconciler can be told to reconcile the HelmRelease outside of the specified interval by annotating the object with a reconcile.fluxcd.io/requestedAt annotation. For example:

kubectl annotate --overwrite helmrelease/podinfo reconcile.fluxcd.io/requesteddAt="$(date +%s)"

Reconciliation can be suspended by setting spec.suspend to true.

The timeout for any individual Kubernetes operation (like Jobs for hooks) during the performance of Helm actions can be configured via spec.timeout and can be overridden per action via spec.<action>.timeout.

Disabling resource waiting

For install, upgrade, and rollback actions resource waiting is enabled by default, but can be disabled by setting spec.<action>.disableWait.

HelmRelease dependencies

When applying a HelmRelease, you may need to make sure other releases are Ready before the release is reconciled. For example, because your chart relies on the presence of a Custom Resource Definition installed by another HelmRelease. The spec.dependsOn field allows you to specify each of these dependencies.

Assuming two HelmRelease resources:

  • backend - contains the backend of the application
  • frontend - contains the frontend of the application and relies on the backend
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: backend
  namespace: default
spec:
  interval: 5m
  chart:
    spec:
      chart: podinfo
      version: '>=4.0.0 <5.0.0'
      sourceRef:
        kind: HelmRepository
        name: podinfo
        namespace: default
      interval: 1m
  upgrade:
    remediation:
      remediateLastFailure: true
  test:
    enable: true
  values:
    service:
      grpcService: backend
    resources:
      requests:
        cpu: 100m
        memory: 64Mi
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: frontend
  namespace: default
spec:
  interval: 5m
  chart:
    spec:
      chart: podinfo
      version: '>=4.0.0 <5.0.0'
      sourceRef:
        kind: HelmRepository
        name: podinfo
        namespace: default
      interval: 1m
  dependsOn:
    - backend
  upgrade:
    remediation:
      remediateLastFailure: true
  test:
    enable: true
  values:
    backend: http://backend-podinfo:9898/echo
    resources:
      requests:
        cpu: 100m
        memory: 64Mi

Note that this does not account for upgrade ordering. Kubernetes only allows applying one resource (HelmRelease in this case) at a time, so there is no way for the controller to know when a dependency HelmRelease may be updated. Also, circular dependencies between HelmRelease resources must be avoided, otherwise the interdependent HelmRelease resources will never be reconciled.

Configuring Helm test actions

To make the controller run the Helm tests available for your chart after a successful Helm install or upgrade, spec.test.enable should be set to true.

By default, when tests are enabled, failures in tests are considered release failures, and thus are subject to the triggering Helm action's remediation configuration. However, test failures can be ignored by setting spec.test.ignoreFailures to true. In this case, no remediation will be taken, and the test failure will not affect the Released and Ready status conditions. This can be overridden per Helm action by setting spec.install.remediation.ignoreTestFailures or spec.upgrade.remediation.ignoreTestFailures.

apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: podinfo
  namespace: default
spec:
  interval: 5m
  chart:
    spec:
      chart: podinfo
      version: '>=4.0.0 <5.0.0'
      sourceRef:
        kind: HelmRepository
        name: podinfo
        namespace: default
      interval: 1m
  test:
    enable: true
    ignoreFailures: true
  values:
    resources:
      requests:
        cpu: 100m
        memory: 64Mi

Configuring failure remediation

From time to time a Helm install/upgrade and accompanying Helm test may fail. When this occurs, by default no action is taken, and the release is left in a failed state. However, several automatic failure remediation options can be set via spec.install.remediation and spec.upgrade.remediation.

The retries can be set to configure the number of retries after an initial failure. A negative integer results in infinite retries. This implicitly opts-in to a remediation action between each attempt. The remediation action for install failures is an uninstall. The remediation action for upgrade failures is by default a rollback, however spec.upgrade.remediation.strategy can be set to uninstall, in which case after the uninstall, the spec.install configuration takes over.

One can also opt-in to remediation of the last failure (when no retries remain) by setting spec.<action>.remediation.remediateLastFailure to true. For upgrades, this defaults to true if at least one retry is configured.

apiVersion: helm.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: podinfo
  namespace: default
spec:
  interval: 5m
  chart:
    spec:
      chart: podinfo
      version: '>=4.0.0 <5.0.0'
      sourceRef:
        kind: HelmRepository
        name: podinfo
        namespace: default
      interval: 1m
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      remediateLastFailure: false
  values:
    resources:
      requests:
        cpu: 100m
        memory: 64Mi

Remote Clusters / Cluster-API

If the spec.kubeConfig field is set, Helm actions will run against the default cluster specified in that KubeConfig instead of the local cluster that is responsible for the reconciliation of the HelmRelease.

The secret defined in the spec.kubeConfig.secretRef must exist in the same namespace as the HelmRelease. On every reconciliation, the KubeConfig bytes will be loaded from the values key of the secret's data, and the secret can thus be regularly updated if cluster-access-tokens have to rotate due to expiration.

The Helm storage is stored on the remote cluster in a namespace that equals to the namespace of the HelmRelease, the release itself is made in either this namespace, or the configured spec.targetNamespace. In any case, both are expected to exist.

Other references to Kubernetes resources in the HelmRelease, like ValuesReference resources, are expected to exist on the reconciling cluster.

This composes well with Cluster API bootstrap providers such as CAPBK (kubeadm), as well as the CAPA (AWS) EKS integration.

To reconcile a HelmRelease to a CAPI controlled cluster, put the HelmRelease in the same namespace as your Cluster object, and set the spec.kubeConfig.secretRef.name to <cluster-name>-kubeconfig:

apiVersion: cluster.x-k8s.io/v1alpha3
kind: Cluster
metadata:
  name: stage  # the kubeconfig Secret will contain the Cluster name
  namespace: capi-stage
spec:
  clusterNetwork:
    pods:
      cidrBlocks:
      - 10.100.0.0/16
    serviceDomain: stage-cluster.local
    services:
      cidrBlocks:
      - 10.200.0.0/12
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
    kind: KubeadmControlPlane
    name: stage-control-plane
    namespace: capi-stage
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
    kind: DockerCluster
    name: stage
    namespace: capi-stage
---
# ... unrelated Cluster API objects omitted for brevity ...
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: kube-prometheus-stack
  namespace: capi-stage
spec:
  kubeConfig:
    secretRef:
      name: stage-kubeconfig # Cluster API creates this for the matching Cluster
  chart:
    spec:
      chart: prometheus
      version: '>=4.0.0 <5.0.0'
      sourceRef:
        kind: HelmRepository
        name: prometheus-community
  install:
    remediation:
      retries: -1

The Cluster and HelmRelease can be created at the same time if the install remediation configuration is set to a forgiving amount of retries. The HelmRelease will then eventually reconcile once the cluster is available.

If you wish to target clusters created by other means than CAPI, you can create a ServiceAccount on the remote cluster, generate a KubeConfig for that account, and then create a secret on the cluster where helm-controller is running e.g.:

kubectl -n default create secret generic prod-kubeconfig \
    --from-file=value=./kubeconfig

Note that the KubeConfig should be self-contained and not rely on binaries, environment, or credential files from the helm-controller Pod. This matches the constraints of KubeConfigs from current Cluster API providers. KubeConfigs with cmd-path in them likely won't work without a custom, per-provider installation of helm-controller.

Status

When the controller completes a reconciliation, it reports the result in the status sub-resource.

The following status.condtions types are advertised. Here, "desired state" is as detailed in reconciliation:

  • Ready - status of the last reconciliation attempt
  • Released - status of the last release attempt (install/upgrade/test) against the latest desired state
  • TestSuccess - status of the last test attempt against the latest desired state
  • Remediated - status of the last remediation attempt (uninstall/rollback) due to a failure of the last release attempt against the latest desired state

For example, you can wait for a successful helm-controller reconciliation with:

kubectl wait helmrelease/podinfo --for=condition=ready

Each of these conditions also include descriptive reason / message fields as to why the status is as such.

Examples

Install success

status:
  conditions:
  - lastTransitionTime: "2020-07-13T13:13:40Z"
    message: Helm install succeeded
    reason: InstallSucceeded
    status: "True"
    type: Released
  - lastTransitionTime: "2020-07-13T13:13:40Z"
    message: Helm test succeeded
    reason: TestSucceeded
    status: "True"
    type: TestSuccess
  - lastTransitionTime: "2020-07-13T13:13:42Z"
    message: release reconciliation succeeded
    reason: ReconciliationSucceeded
    status: "True"
    type: Ready
  lastAppliedRevision: 4.0.6
  lastAttemptedRevision: 4.0.6
  lastReleaseRevision: 1
  observedGeneration: 2

Upgrade failure

status:
  conditions:
  - lastTransitionTime: "2020-07-13T13:17:28Z"
    message: 'error validating "": error validating data: ValidationError(Deployment.spec.replicas):
      invalid type for io.k8s.api.apps.v1.DeploymentSpec.replicas: got "string",
      expected "integer"'
    reason: UpgradeFailed
    status: "False"
    type: Released
  - lastTransitionTime: "2020-07-13T13:17:28Z"
    message: 'error validating "": error validating data: ValidationError(Deployment.spec.replicas):
      invalid type for io.k8s.api.apps.v1.DeploymentSpec.replicas: got "string",
      expected "integer"'
    reason: UpgradeFailed
    status: "False"
    type: Ready
  failures: 1
  lastAppliedRevision: 4.0.6
  lastAttemptedRevision: 4.0.6
  lastReleaseRevision: 1
  observedGeneration: 3

Ignored test failure

status:
  conditions:
  - lastTransitionTime: "2020-07-13T13:13:40Z"
    message: Helm install succeeded
    reason: InstallSucceeded
    status: "True"
    type: Released
  - lastTransitionTime: "2020-07-13T13:13:40Z"
    message: Helm test failed
    reason: TestFailed
    status: "False"
    type: TestSuccess
  - lastTransitionTime: "2020-07-13T13:13:42Z"
    message: release reconciliation succeeded
    reason: ReconciliationSucceeded
    status: "True"
    type: Ready
  lastAppliedRevision: 4.0.6
  lastAttemptedRevision: 4.0.6
  lastReleaseRevision: 1
  observedGeneration: 2