From aa3e8621687fc36840ee562cb84eef952c69e326 Mon Sep 17 00:00:00 2001 From: churru Date: Wed, 7 Jan 2026 18:49:38 -0300 Subject: [PATCH 1/2] BAC-492 - Prevent Rollout From Losing spec.replicas During ArgoCD Sync When Re-enabling HPA --- src/commands/argo-deploy-application.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/commands/argo-deploy-application.yml b/src/commands/argo-deploy-application.yml index e5de3a8..662a5f3 100644 --- a/src/commands/argo-deploy-application.yml +++ b/src/commands/argo-deploy-application.yml @@ -202,6 +202,11 @@ steps: kind: ServiceAccount jqPathExpressions: - ".metadata.annotations[\"eks.amazonaws.com/role-arn\"]" + # Ignore differences in the Rollout replicas as it is managed by HPA when it is enabled + - group: argoproj.io + kind: Rollout + jqPathExpressions: + - ". | select(.metadata.annotations[\"tiendanube.com/hpa-enabled\"] == \"true\") | .spec.replicas" EOF echo "------------------------------------------------------" echo "📄 ArgoCD Application manifest:" From 8537bc1128eba88faa3402cc148ecee1c4a1f775 Mon Sep 17 00:00:00 2001 From: "Juan G. Arias" Date: Thu, 12 Feb 2026 18:03:24 -0300 Subject: [PATCH 2/2] Implement HPA transition step Implement HPA transition preparation script and update ArgoCD deployment steps to clean spec.replicas from last-applied-configuration. This change prevents conflicts during HPA transitions by ensuring the correct replica count is maintained. --- src/commands/argo-deploy-application.yml | 13 ++-- src/scripts/argo_hpa_transition.sh | 99 ++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 src/scripts/argo_hpa_transition.sh diff --git a/src/commands/argo-deploy-application.yml b/src/commands/argo-deploy-application.yml index 662a5f3..f264858 100644 --- a/src/commands/argo-deploy-application.yml +++ b/src/commands/argo-deploy-application.yml @@ -202,11 +202,6 @@ steps: kind: ServiceAccount jqPathExpressions: - ".metadata.annotations[\"eks.amazonaws.com/role-arn\"]" - # Ignore differences in the Rollout replicas as it is managed by HPA when it is enabled - - group: argoproj.io - kind: Rollout - jqPathExpressions: - - ". | select(.metadata.annotations[\"tiendanube.com/hpa-enabled\"] == \"true\") | .spec.replicas" EOF echo "------------------------------------------------------" echo "📄 ArgoCD Application manifest:" @@ -220,6 +215,14 @@ steps: - argo-app-previous-status: release-name: << parameters.release-name >> + - run: + name: Prepare HPA transition + environment: + ARGO_PARAMETERS_FILE: /tmp/argo-parameters/parameters.yaml + RELEASE_NAME: << parameters.release-name >> + NAMESPACE: << parameters.namespace >> + command: << include(scripts/argo_hpa_transition.sh) >> + - run: name: Upsert Argo Application command: | diff --git a/src/scripts/argo_hpa_transition.sh b/src/scripts/argo_hpa_transition.sh new file mode 100644 index 0000000..4871e14 --- /dev/null +++ b/src/scripts/argo_hpa_transition.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# This script prepares a Rollout resource for HPA transition by cleaning the +# spec.replicas field from the kubectl last-applied-configuration annotation. +# +# When transitioning from fixed replicas to HPA-managed replicas, kubectl's +# three-way merge interprets the removal of spec.replicas from the desired +# manifest as an intentional deletion, causing Kubernetes to default to 1 replica. +# By removing the field from last-applied-configuration beforehand, the three-way +# merge sees "was absent, still absent" and preserves the live replica count. +# +# Environment variables: +# ARGO_PARAMETERS_FILE - Path to the rendered Helm parameters file +# RELEASE_NAME - Name of the Rollout resource +# NAMESPACE - Kubernetes namespace + +# Colors for output +RED="\033[0;31m" +GREEN="\033[0;32m" +YELLOW="\033[1;33m" +NC="\033[0m" # No Color + +function usage() { + echo -e "${GREEN}Usage:${NC} Set the following environment variables before running this script:" + echo " ARGO_PARAMETERS_FILE Path to the rendered Helm parameters file (required)" + echo " RELEASE_NAME Name of the Rollout resource (required)" + echo " NAMESPACE Kubernetes namespace (required)" +} + +function main() { + local parameters_file="${ARGO_PARAMETERS_FILE}" + local release_name="${RELEASE_NAME}" + local namespace="${NAMESPACE}" + + # Validate required variables + local missing_vars=() + [[ -z "$parameters_file" ]] && missing_vars+=("ARGO_PARAMETERS_FILE") + [[ -z "$release_name" ]] && missing_vars+=("RELEASE_NAME") + [[ -z "$namespace" ]] && missing_vars+=("NAMESPACE") + + if [[ ${#missing_vars[@]} -gt 0 ]]; then + echo -e "${RED}Error: The following required environment variables are missing:${NC}" + printf ' - %s\n' "${missing_vars[@]}" + usage + exit 2 + fi + + # Check if autoscaling is being enabled in this deploy + local hpa_enabled + hpa_enabled=$(grep -A1 'name: autoscaling\.enabled' "$parameters_file" | grep 'value:' | awk '{print $2}' || echo "false") + + if [[ "$hpa_enabled" != "true" ]]; then + echo -e "${GREEN}HPA not enabled in this deploy, skipping.${NC}" + return 0 + fi + + echo -e "${YELLOW}HPA enabled — checking if spec.replicas cleanup is needed for Rollout '$release_name' in namespace '$namespace'.${NC}" + + # Check if the Rollout exists (might not on first deploy) + if ! kubectl get rollout "$release_name" -n "$namespace" > /dev/null 2>&1; then + echo -e "${GREEN}Rollout '$release_name' not found (likely first deploy), skipping.${NC}" + return 0 + fi + + # Get the last-applied-configuration annotation + local last_applied + last_applied=$(kubectl get rollout "$release_name" -n "$namespace" \ + -o jsonpath='{.metadata.annotations.kubectl\.kubernetes\.io/last-applied-configuration}' 2>/dev/null || echo "") + + if [[ -z "$last_applied" ]]; then + echo -e "${GREEN}No last-applied-configuration annotation found, skipping.${NC}" + return 0 + fi + + # Check if spec.replicas exists in last-applied-configuration + if ! echo "$last_applied" | jq -e '.spec.replicas' > /dev/null 2>&1; then + echo -e "${GREEN}spec.replicas not present in last-applied-configuration, no cleanup needed.${NC}" + return 0 + fi + + local current_replicas + current_replicas=$(echo "$last_applied" | jq '.spec.replicas') + echo -e "${YELLOW}Removing spec.replicas ($current_replicas) from last-applied-configuration${NC}" + echo "to prevent three-way merge conflict during HPA transition." + + # Remove spec.replicas from last-applied-configuration + local cleaned + cleaned=$(echo "$last_applied" | jq -c 'del(.spec.replicas)') + + if ! kubectl annotate rollout "$release_name" -n "$namespace" \ + "kubectl.kubernetes.io/last-applied-configuration=$cleaned" --overwrite; then + echo -e "${RED}Failed to update last-applied-configuration annotation.${NC}" + return 1 + fi + + echo -e "${GREEN}Successfully cleaned spec.replicas from last-applied-configuration.${NC}" +} + +main "$@"