Skip to content

Install and operate HAPTIC with GitOps

Use stable credential and certificate Secrets, and keep the chart's hooks enabled. Argo CD maps the CRD, validation, and agent-certificate bootstrap hooks to PreSync. Flux runs them as Helm hooks. Both stop before applying the release's configuration if preflight validation fails.

These examples install release haptic in namespace haptic. Use one GitOps controller for the release. Adopting a release already managed by another tool requires transferring ownership; that migration isn't covered here.

Development chart

credentials.existingSecret and automatic agent-certificate renewal were added after 0.2.0-alpha.3. Until the next release, use a matching snapshot chart and controller image containing these changes.

Prepare stable Secrets

You need an installed Argo CD or Flux controller, Bash, kubectl, jq, and OpenSSL. The examples keep the default automatic agent-certificate renewal, which doesn't require cert-manager. Webhook and frontend certificates use one of the two sources below.

  1. Create the namespace.

    kubectl create namespace haptic --dry-run=client -o yaml | kubectl apply -f -
    
  2. Create the credential Secret if it doesn't exist.

    set -e
    existing=$(kubectl get secret haptic-gitops-credentials -n haptic --ignore-not-found -o name)
    if [ -z "$existing" ]; then
      password=$(openssl rand -base64 32)
      kubectl create secret generic haptic-gitops-credentials -n haptic \
        --from-literal=dataplane_username=admin \
        --from-literal=dataplane_password="$password"
      unset password
    fi
    

    Your Secret-management system can create this Secret instead. It must provide dataplane_username and dataplane_password. Keep its plaintext contents out of Git. When migrating from chart-generated credentials, use a new Secret name so removal of the old chart resource can't delete your replacement.

  3. Select a certificate source.

    Obtain a webhook server certificate for haptic-webhook.haptic.svc and a default frontend certificate for your application domains. This path uses webhook.crt, webhook.key, webhook-ca.crt, frontend.crt, and frontend.key from your issuer. The .crt files must contain the leaf and any intermediate certificates.

    kubectl create secret generic haptic-gitops-webhook -n haptic \
      --type=kubernetes.io/tls \
      --from-file=tls.crt=webhook.crt \
      --from-file=tls.key=webhook.key \
      --from-file=ca.crt=webhook-ca.crt \
      --dry-run=client -o yaml | kubectl apply -f -
    kubectl create secret tls haptic-gitops-frontend -n haptic \
      --cert=frontend.crt --key=frontend.key \
      --dry-run=client -o yaml | kubectl apply -f -
    jq -n --rawfile ca webhook-ca.crt '{
      credentials: {existingSecret: "haptic-gitops-credentials"},
      controller: {webhook: {
        secretName: "haptic-gitops-webhook",
        caBundle: ($ca | @base64),
        certManager: {enabled: false}
      }},
      defaultSSLCertificate: {
        secretName: "haptic-gitops-frontend",
        certManager: {enabled: false}
      }
    }' > haptic-values.json
    

    Your issuer must renew these certificates and update their Secrets before expiry. The controller reloads the webhook certificate; the frontend certificate follows normal configuration deployment. Changing the webhook's CA also requires updating controller.webhook.caBundle in Git. Automatic agent renewal doesn't renew these external certificates.

    Install cert-manager before applying this configuration. The chart uses it for webhook and default frontend certificates; agent certificates retain their independent default renewal mechanism.

    kubectl get crd certificates.cert-manager.io
    jq -n '{
      credentials: {existingSecret: "haptic-gitops-credentials"},
      controller: {webhook: {certManager: {enabled: true}}}
    }' > haptic-values.json
    

    This creates a self-signed default frontend certificate for localdev.me. Configure a production certificate before exposing application HTTPS; see SSL certificates.

The values file contains Secret names and public trust material. Store it with your GitOps configuration. The chart hashes an external credential Secret's name, so unchanged rendering doesn't generate a password or roll the pods. With explicit legacy Basic authentication, rotating that Secret also requires replacing the HAProxy pods because their password comes from the environment. Default mutual TLS doesn't use that password for agent authentication.

Argo CD installation

Use an Argo CD project that permits HAPTIC's namespaced resources, CRDs, cluster RBAC, IngressClass, and admission webhook. The example uses the standard default project. Argo CD Core installations need an AppProject created separately.

  1. Generate the Application.

    read -r -p "Chart version to install: " HAPTIC_CHART_VERSION
    test -n "$HAPTIC_CHART_VERSION"
    jq -n --arg version "$HAPTIC_CHART_VERSION" --slurpfile values haptic-values.json '{
      apiVersion: "argoproj.io/v1alpha1",
      kind: "Application",
      metadata: {name: "haptic", namespace: "argocd"},
      spec: {
        project: "default",
        destination: {server: "https://kubernetes.default.svc", namespace: "haptic"},
        source: {
          repoURL: "registry.gitlab.com/haproxy-haptic/haptic/charts",
          chart: "haptic",
          targetRevision: $version,
          helm: {releaseName: "haptic", valuesObject: $values[0]}
        },
        syncPolicy: {
          automated: {enabled: true, prune: true, selfHeal: true},
          retry: {limit: 1},
          syncOptions: ["ServerSideApply=true", "DisableClientSideApplyMigration=true"]
        }
      }
    }' > haptic-application.json
    

    Keep the Application name equal to the Helm release name when Argo CD uses its default label-based resource tracking. This preserves the instance labels used by the chart's selectors. The OCI Helm repository URL has no oci:// prefix in Argo CD's Helm source format.

  2. Apply the Application.

    kubectl apply -f haptic-application.json
    

    Commit the generated Application to the configuration repository watched by your Argo CD bootstrap application for subsequent changes.

  3. Inspect the result.

    kubectl get application haptic -n argocd
    kubectl get jobs,pods -n haptic
    

Use full syncs. Argo CD selective resource syncs don't run hooks. Don't add Argo-specific hook annotations to individual chart resources: Argo CD ignores Helm hooks when it finds Argo-specific hooks in the application. The chart already sets hook ordering. See Argo CD's Helm hook mapping.

Flux installation

The example uses an OCIRepository and a HelmRelease. RetryOnFailure retries a failed operation without uninstalling the existing release or attempting a rollback over the chart's CRDs.

  1. Generate the source and release.

    read -r -p "Chart version to install: " HAPTIC_CHART_VERSION
    test -n "$HAPTIC_CHART_VERSION"
    jq -n --arg version "$HAPTIC_CHART_VERSION" '{
      apiVersion: "source.toolkit.fluxcd.io/v1",
      kind: "OCIRepository",
      metadata: {name: "haptic", namespace: "flux-system"},
      spec: {
        interval: "10m",
        url: "oci://registry.gitlab.com/haproxy-haptic/haptic/charts/haptic",
        ref: {tag: $version},
        layerSelector: {mediaType: "application/vnd.cncf.helm.chart.content.v1.tar+gzip", operation: "copy"}
      }
    }' > haptic-source.json
    jq -n --slurpfile values haptic-values.json '{
      apiVersion: "helm.toolkit.fluxcd.io/v2",
      kind: "HelmRelease",
      metadata: {name: "haptic", namespace: "flux-system"},
      spec: {
        interval: "10m",
        timeout: "15m",
        releaseName: "haptic",
        targetNamespace: "haptic",
        chartRef: {kind: "OCIRepository", name: "haptic"},
        install: {strategy: {name: "RetryOnFailure", retryInterval: "5m"}},
        upgrade: {strategy: {name: "RetryOnFailure", retryInterval: "5m"}},
        values: $values[0]
      }
    }' > haptic-release.json
    
  2. Apply both resources.

    kubectl apply -f haptic-source.json -f haptic-release.json
    

    Commit them to the repository watched by your Flux Kustomization. If the same repository provisions the Secrets, order their Kustomization before this release with dependsOn and a Secret readiness check.

  3. Inspect the result.

    kubectl get ocirepository,helmrelease -n flux-system
    kubectl get jobs,pods -n haptic
    

Flux runs Helm install and upgrade actions, including live lookup calls. Argo CD renders Helm templates offline. External Secrets make the examples stable in both paths. See Flux HelmRelease behavior.

Upgrade and recover a rejected change

Pin a chart version and keep the chart and controller image from the same build. The validation Job renders the chart embedded in that image and checks its version before validating the complete future configuration. The CRD hook runs first; CRD changes may therefore have applied even when later validation fails.

When validation rejects a sync, inspect its retained Job:

kubectl get job haptic-haptic-pre-rollout -n haptic
kubectl logs job/haptic-haptic-pre-rollout -n haptic

Fix the rejected values or select a corrected release in Git. A full Argo CD sync or Flux reconciliation reruns the hooks. Existing HAProxy workers keep their last configuration while preflight blocks the rejected release.

Once the corrected version and values appear in the GitOps resources, you can request immediate reconciliation with the Argo CD or Flux CLI:

argocd app sync haptic
argocd app wait haptic --sync --health --timeout 900
flux reconcile source oci haptic -n flux-system
flux reconcile helmrelease haptic -n flux-system

Don't use --no-hooks, selective sync, or disabled validation to force the bad release through. Helm rollback doesn't reverse CRD evolution and bypasses these pre-upgrade hooks. HAPTIC doesn't claim compatibility for downgrades across its configuration API changes; recover with a corrected forward release. See the 0.2 upgrade contract.

Reproduce lifecycle verification

The lifecycle suite uses fresh Kubernetes 1.33 clusters with Argo CD 3.5.3 or Flux 2.9.5. It tests external certificates with no cert-manager installation and an explicit cert-manager 1.21.2 profile. Four synthetic chart versions exercise install, upgrade, rejection, and forward recovery against the candidate binary; the separate upgrade suite covers published release baselines.

make test-gitops-lifecycle GITOPS_PROVIDER=argo
make test-gitops-lifecycle GITOPS_PROVIDER=flux
make test-gitops-lifecycle GITOPS_PROVIDER=argo GITOPS_CERTIFICATES=cert-manager
make test-gitops-lifecycle GITOPS_PROVIDER=flux GITOPS_CERTIFICATES=cert-manager

Each run checks both HTTP and HTTPS on both HAProxy replicas, unchanged Secret contents, stable pods after an unchanged sync, and preservation of the live configuration after a rejected release. Artifacts include chart and image digests, Kubernetes and GitOps versions, hook results, and traffic checks. The test uses a private HTTP Helm repository and applies desired-state resources directly; it doesn't test a Git provider, OCI registry authentication, or cross-version CRD downgrades.

Found a problem on this page? Report it or edit the page with the pencil icon above the title.