Update deploy-lab.sh
This commit is contained in:
+195
-45
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Laboratorio estable v4.4.3: ZimaOS + Docker + k3d + Argo CD + Multi-Repo + Governance + Monitoring Recovery + Runner
|
||||
# Laboratorio estable v4.4.6.1: ZimaOS + Docker + k3d + Argo CD + Multi-Repo + Governance + Monitoring + CI/CD
|
||||
#
|
||||
# Modos:
|
||||
# bootstrap Crea el clúster si no existe; si existe, lo inicia y reconcilia.
|
||||
@@ -10,6 +10,7 @@
|
||||
# gitops Actualiza el repo y aplica root-apps-registry.
|
||||
# monitoring-diagnose Diagnostica kube-prometheus-stack/Grafana.
|
||||
# monitoring-recover Finaliza una operación obsoleta y resincroniza Grafana.
|
||||
# postdeploy-secrets Regenera y valida credenciales de Gitea Actions.
|
||||
# runner Adopta, crea o recupera el Gitea Actions runner.
|
||||
# install-autostart Instala servicio/timer systemd, orden de montajes y autorreparación.
|
||||
# reset Reconstrucción limpia; requiere confirmación explícita.
|
||||
@@ -52,6 +53,10 @@ EXPECTED_HOST_IP="${EXPECTED_HOST_IP:-192.168.68.61}"
|
||||
ALLOW_IP_CHANGE="${ALLOW_IP_CHANGE:-false}"
|
||||
INSTALL_HEADLAMP_RBAC="${INSTALL_HEADLAMP_RBAC:-true}"
|
||||
APPLY_POSTDEPLOY_RBAC="${APPLY_POSTDEPLOY_RBAC:-true}"
|
||||
POSTDEPLOY_SERVICE_ACCOUNT="${POSTDEPLOY_SERVICE_ACCOUNT:-gitea-postdeploy-validator}"
|
||||
POSTDEPLOY_TOKEN_SECRET="${POSTDEPLOY_TOKEN_SECRET:-gitea-postdeploy-validator-token}"
|
||||
POSTDEPLOY_NAMESPACE="${POSTDEPLOY_NAMESPACE:-ecommerce}"
|
||||
POSTDEPLOY_TOKEN_WAIT_SECONDS="${POSTDEPLOY_TOKEN_WAIT_SECONDS:-90}"
|
||||
WAIT_SECONDS="${WAIT_SECONDS:-240}"
|
||||
ARGOCD_ROLLOUT_TIMEOUT="${ARGOCD_ROLLOUT_TIMEOUT:-420}"
|
||||
AGENT_RECOVERY_WAIT="${AGENT_RECOVERY_WAIT:-75}"
|
||||
@@ -869,20 +874,24 @@ apply_postdeploy_rbac() {
|
||||
|
||||
log "Aplicando RBAC de solo lectura para validación postdeploy"
|
||||
|
||||
kubectl create namespace ecommerce --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl create namespace "$POSTDEPLOY_NAMESPACE" \
|
||||
--dry-run=client \
|
||||
-o yaml |
|
||||
kubectl apply -f -
|
||||
|
||||
cat <<'YAML' | kubectl apply -f -
|
||||
cat <<YAML | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: gitea-postdeploy-validator
|
||||
namespace: ecommerce
|
||||
name: ${POSTDEPLOY_SERVICE_ACCOUNT}
|
||||
namespace: ${POSTDEPLOY_NAMESPACE}
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: gitea-postdeploy-validator
|
||||
namespace: ecommerce
|
||||
name: ${POSTDEPLOY_SERVICE_ACCOUNT}
|
||||
namespace: ${POSTDEPLOY_NAMESPACE}
|
||||
rules:
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments", "replicasets"]
|
||||
@@ -894,51 +903,114 @@ rules:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: gitea-postdeploy-validator
|
||||
namespace: ecommerce
|
||||
name: ${POSTDEPLOY_SERVICE_ACCOUNT}
|
||||
namespace: ${POSTDEPLOY_NAMESPACE}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: gitea-postdeploy-validator
|
||||
namespace: ecommerce
|
||||
name: ${POSTDEPLOY_SERVICE_ACCOUNT}
|
||||
namespace: ${POSTDEPLOY_NAMESPACE}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: gitea-postdeploy-validator
|
||||
name: ${POSTDEPLOY_SERVICE_ACCOUNT}
|
||||
YAML
|
||||
|
||||
ensure_postdeploy_token_secret
|
||||
}
|
||||
|
||||
ensure_postdeploy_token_secret() {
|
||||
local sa_uid secret_sa_uid=""
|
||||
|
||||
sa_uid="$(
|
||||
kubectl get serviceaccount "$POSTDEPLOY_SERVICE_ACCOUNT" \
|
||||
-n "$POSTDEPLOY_NAMESPACE" \
|
||||
-o jsonpath='{.metadata.uid}'
|
||||
)"
|
||||
|
||||
[[ -n "$sa_uid" ]] ||
|
||||
die "No fue posible obtener el UID del ServiceAccount postdeploy."
|
||||
|
||||
if kubectl get secret "$POSTDEPLOY_TOKEN_SECRET" \
|
||||
-n "$POSTDEPLOY_NAMESPACE" >/dev/null 2>&1; then
|
||||
secret_sa_uid="$(
|
||||
kubectl get secret "$POSTDEPLOY_TOKEN_SECRET" \
|
||||
-n "$POSTDEPLOY_NAMESPACE" \
|
||||
-o jsonpath='{.metadata.annotations.kubernetes\.io/service-account\.uid}' \
|
||||
2>/dev/null || true
|
||||
)"
|
||||
|
||||
if [[ -n "$secret_sa_uid" && "$secret_sa_uid" != "$sa_uid" ]]; then
|
||||
warn "El token postdeploy pertenece a un ServiceAccount anterior; recreándolo."
|
||||
kubectl delete secret "$POSTDEPLOY_TOKEN_SECRET" \
|
||||
-n "$POSTDEPLOY_NAMESPACE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! kubectl get secret "$POSTDEPLOY_TOKEN_SECRET" \
|
||||
-n "$POSTDEPLOY_NAMESPACE" >/dev/null 2>&1; then
|
||||
cat <<YAML | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ${POSTDEPLOY_TOKEN_SECRET}
|
||||
namespace: ${POSTDEPLOY_NAMESPACE}
|
||||
annotations:
|
||||
kubernetes.io/service-account.name: ${POSTDEPLOY_SERVICE_ACCOUNT}
|
||||
type: kubernetes.io/service-account-token
|
||||
YAML
|
||||
fi
|
||||
}
|
||||
|
||||
generate_postdeploy_credentials() {
|
||||
[[ "$APPLY_POSTDEPLOY_RBAC" == "true" ]] || return 0
|
||||
|
||||
local secret_dir="${APP_DATA_PATH}/gitea-actions-secrets"
|
||||
local owner tmp_dir token ca_b64 server kubeconfig_file output_file
|
||||
local owner tmp_dir token_b64="" token="" ca_b64="" server
|
||||
local kubeconfig_file output_file deadline
|
||||
|
||||
owner="$(id -un)"
|
||||
server="https://${LAB_HOST_IP}:${API_PORT}"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
kubeconfig_file="${tmp_dir}/postdeploy-validator.kubeconfig"
|
||||
|
||||
# No aplicamos chmod a un glob con archivos históricos que podrían pertenecer
|
||||
# a root. Generamos todo en un directorio temporal y luego instalamos cada
|
||||
# archivo con propietario y permisos explícitos.
|
||||
sudo_cmd install -d -m 0700 -o "$owner" "$secret_dir"
|
||||
|
||||
token="$(
|
||||
kubectl create token gitea-postdeploy-validator \
|
||||
-n ecommerce \
|
||||
--duration=8760h
|
||||
)"
|
||||
ensure_postdeploy_token_secret
|
||||
|
||||
deadline=$((SECONDS + POSTDEPLOY_TOKEN_WAIT_SECONDS))
|
||||
|
||||
while (( SECONDS < deadline )); do
|
||||
token_b64="$(
|
||||
kubectl get secret "$POSTDEPLOY_TOKEN_SECRET" \
|
||||
-n "$POSTDEPLOY_NAMESPACE" \
|
||||
-o jsonpath='{.data.token}' 2>/dev/null || true
|
||||
)"
|
||||
ca_b64="$(
|
||||
kubectl config view \
|
||||
--raw \
|
||||
--minify \
|
||||
--flatten \
|
||||
-o jsonpath='{.clusters[0].cluster.certificate-authority-data}'
|
||||
kubectl get secret "$POSTDEPLOY_TOKEN_SECRET" \
|
||||
-n "$POSTDEPLOY_NAMESPACE" \
|
||||
-o jsonpath='{.data.ca\.crt}' 2>/dev/null || true
|
||||
)"
|
||||
|
||||
[[ -n "$token" ]] || die "No fue posible generar el token postdeploy."
|
||||
[[ -n "$ca_b64" ]] || die "No fue posible leer la CA del clúster."
|
||||
if [[ -n "$token_b64" && -n "$ca_b64" ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
[[ -n "$token_b64" ]] ||
|
||||
die "El controlador no generó el token en Secret/${POSTDEPLOY_TOKEN_SECRET}."
|
||||
|
||||
[[ -n "$ca_b64" ]] ||
|
||||
die "El controlador no generó ca.crt en Secret/${POSTDEPLOY_TOKEN_SECRET}."
|
||||
|
||||
token="$(
|
||||
printf '%s' "$token_b64" |
|
||||
base64 --decode
|
||||
)"
|
||||
|
||||
[[ -n "$token" ]] ||
|
||||
die "El token postdeploy quedó vacío."
|
||||
|
||||
printf '%s\n' "$token" > "${tmp_dir}/K8S_TOKEN.txt"
|
||||
printf '%s\n' "$ca_b64" > "${tmp_dir}/K8S_CA_B64.txt"
|
||||
@@ -955,20 +1027,18 @@ clusters:
|
||||
server: ${server}
|
||||
tls-server-name: ${LAB_HOST_IP}
|
||||
contexts:
|
||||
- name: gitea-postdeploy-validator@${CLUSTER_NAME}
|
||||
- name: ${POSTDEPLOY_SERVICE_ACCOUNT}@${CLUSTER_NAME}
|
||||
context:
|
||||
cluster: ${CLUSTER_NAME}
|
||||
namespace: ecommerce
|
||||
user: gitea-postdeploy-validator
|
||||
current-context: gitea-postdeploy-validator@${CLUSTER_NAME}
|
||||
namespace: ${POSTDEPLOY_NAMESPACE}
|
||||
user: ${POSTDEPLOY_SERVICE_ACCOUNT}
|
||||
current-context: ${POSTDEPLOY_SERVICE_ACCOUNT}@${CLUSTER_NAME}
|
||||
users:
|
||||
- name: gitea-postdeploy-validator
|
||||
- name: ${POSTDEPLOY_SERVICE_ACCOUNT}
|
||||
user:
|
||||
token: ${token}
|
||||
EOF
|
||||
|
||||
# Compatibilidad con el workflow histórico que usa un único secret
|
||||
# KUBE_CONFIG_DATA en Base64, pero ahora con privilegios mínimos.
|
||||
base64 "$kubeconfig_file" |
|
||||
tr -d '\n' \
|
||||
> "${tmp_dir}/KUBE_CONFIG_DATA.txt"
|
||||
@@ -988,19 +1058,85 @@ EOF
|
||||
"${secret_dir}/${output_file}"
|
||||
done
|
||||
|
||||
# Los kubeconfig históricos también contienen credenciales. Ajustamos
|
||||
# únicamente propietario y permisos; no asumimos ningún grupo llamado devops.
|
||||
for output_file in kubeconfig-gitea.yaml lab-cluster.kubeconfig; do
|
||||
if [[ -f "${secret_dir}/${output_file}" ]]; then
|
||||
sudo_cmd chown "$owner" "${secret_dir}/${output_file}"
|
||||
sudo_cmd chmod 0600 "${secret_dir}/${output_file}"
|
||||
fi
|
||||
done
|
||||
sudo_cmd install \
|
||||
-m 0600 \
|
||||
-o "$owner" \
|
||||
"$kubeconfig_file" \
|
||||
"${secret_dir}/postdeploy-validator.kubeconfig"
|
||||
|
||||
rm -rf "$tmp_dir"
|
||||
|
||||
warn "Credenciales postdeploy regeneradas en ${secret_dir}."
|
||||
warn "Actualiza en Gitea K8S_TOKEN/K8S_CA_B64/K8S_SERVER/K8S_TLS_SERVER_NAME o el secret histórico KUBE_CONFIG_DATA."
|
||||
warn "Actualiza en Gitea el secret KUBE_CONFIG_DATA con KUBE_CONFIG_DATA.txt."
|
||||
ok "Token persistente asociado a Secret/${POSTDEPLOY_TOKEN_SECRET}."
|
||||
}
|
||||
|
||||
|
||||
validate_postdeploy_credentials() {
|
||||
local secret_dir="${APP_DATA_PATH}/gitea-actions-secrets"
|
||||
local expected_server="https://${LAB_HOST_IP}:${API_PORT}"
|
||||
local server_file="${secret_dir}/K8S_SERVER.txt"
|
||||
local tls_file="${secret_dir}/K8S_TLS_SERVER_NAME.txt"
|
||||
local kubeconfig_file="${secret_dir}/postdeploy-validator.kubeconfig"
|
||||
local actual_server actual_tls kube_server kube_tls can_read
|
||||
|
||||
[[ -s "$server_file" ]] ||
|
||||
die "No existe ${server_file}."
|
||||
|
||||
[[ -s "$tls_file" ]] ||
|
||||
die "No existe ${tls_file}."
|
||||
|
||||
[[ -s "$kubeconfig_file" ]] ||
|
||||
die "No existe ${kubeconfig_file}."
|
||||
|
||||
actual_server="$(tr -d '\r\n\t ' < "$server_file")"
|
||||
actual_tls="$(tr -d '\r\n\t ' < "$tls_file")"
|
||||
|
||||
[[ "$actual_server" == "$expected_server" ]] ||
|
||||
die "K8S_SERVER incorrecto. Actual=${actual_server}; esperado=${expected_server}."
|
||||
|
||||
[[ "$actual_tls" == "$LAB_HOST_IP" ]] ||
|
||||
die "K8S_TLS_SERVER_NAME incorrecto. Actual=${actual_tls}; esperado=${LAB_HOST_IP}."
|
||||
|
||||
kube_server="$(
|
||||
KUBECONFIG="$kubeconfig_file" \
|
||||
kubectl config view \
|
||||
--minify \
|
||||
-o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || true
|
||||
)"
|
||||
|
||||
kube_tls="$(
|
||||
KUBECONFIG="$kubeconfig_file" \
|
||||
kubectl config view \
|
||||
--minify \
|
||||
-o jsonpath='{.clusters[0].cluster.tls-server-name}' 2>/dev/null || true
|
||||
)"
|
||||
|
||||
[[ "$kube_server" == "$expected_server" ]] ||
|
||||
die "El kubeconfig postdeploy apunta a ${kube_server:-vacío}; esperado=${expected_server}."
|
||||
|
||||
[[ "$kube_tls" == "$LAB_HOST_IP" ]] ||
|
||||
die "El kubeconfig postdeploy usa tls-server-name=${kube_tls:-vacío}; esperado=${LAB_HOST_IP}."
|
||||
|
||||
can_read="$(
|
||||
KUBECONFIG="$kubeconfig_file" \
|
||||
kubectl auth can-i get deployments \
|
||||
-n "$POSTDEPLOY_NAMESPACE" 2>/dev/null || true
|
||||
)"
|
||||
|
||||
[[ "$can_read" == "yes" ]] ||
|
||||
die "El ServiceAccount postdeploy no puede leer Deployments en ecommerce."
|
||||
|
||||
KUBECONFIG="$kubeconfig_file" \
|
||||
kubectl get deployments \
|
||||
-n "$POSTDEPLOY_NAMESPACE" \
|
||||
--request-timeout=15s \
|
||||
>/dev/null
|
||||
|
||||
ok "Credenciales postdeploy verificadas contra ${expected_server}."
|
||||
printf 'K8S_SERVER esperado : %s\n' "$expected_server"
|
||||
printf 'K8S_TLS_SERVER_NAME esperado : %s\n' "$LAB_HOST_IP"
|
||||
printf 'Archivos para Gitea : %s\n' "$secret_dir"
|
||||
}
|
||||
|
||||
|
||||
@@ -2880,6 +3016,20 @@ main() {
|
||||
MONITORING_FORCE_RECOVERY=true recover_monitoring_operation true
|
||||
return
|
||||
;;
|
||||
postdeploy-secrets)
|
||||
require_commands
|
||||
ensure_docker
|
||||
validate_data_mount
|
||||
validate_host_ip
|
||||
prepare_directories
|
||||
cluster_exists || die "No existe ${CLUSTER_NAME}."
|
||||
write_kubeconfig
|
||||
wait_for_api
|
||||
apply_postdeploy_rbac
|
||||
generate_postdeploy_credentials
|
||||
validate_postdeploy_credentials
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
require_commands
|
||||
@@ -2906,7 +3056,7 @@ main() {
|
||||
reset_cluster
|
||||
;;
|
||||
*)
|
||||
die "Modo no válido: ${MODE}. Usa bootstrap, recover, ensure, status, repo-sync, gitops, runner, monitoring-diagnose, monitoring-recover, install-autostart o reset."
|
||||
die "Modo no válido: ${MODE}. Usa bootstrap, recover, ensure, status, repo-sync, gitops, runner, monitoring-diagnose, monitoring-recover, postdeploy-secrets, install-autostart o reset."
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
Reference in New Issue
Block a user