This is a runbook for migrating GenAI workload comprising of AKS server and langfuse namespaces from one region to another:
1. Step 1: export rg with aztfexport:
#!/usr/bin/env bash
set -euo pipefail
# ---- CONFIG ----
SOURCE_SUBSCRIPTION_ID="<SOURCE_SUBSCRIPTION_ID>"
SOURCE_RG="<SOURCE_GENAI_RG>" # e.g., rg-genai-aks
SOURCE_LOCATION="<SOURCE_REGION>" # e.g., westus2
TARGET_SUBSCRIPTION_ID="<TARGET_SUBSCRIPTION_ID>"
TARGET_LOCATION="eastus2"
SUFFIX="eus2"
TARGET_RG="${SOURCE_RG}-${SUFFIX}"
EXPORT_DIR="./tfexport-${SOURCE_RG}-${SUFFIX}"
# ---- EXPORT FROM SOURCE ----
az account set --subscription "${SOURCE_SUBSCRIPTION_ID}"
mkdir -p "${EXPORT_DIR}"
echo "Exporting all resources from ${SOURCE_RG} using aztfexport..."
aztfexport group \
--resource-group "${SOURCE_RG}" \
--output-directory "${EXPORT_DIR}" \
--append
echo "Export complete: ${EXPORT_DIR}"
# ---- CREATE TARGET RG ----
az account set --subscription "${TARGET_SUBSCRIPTION_ID}"
echo "Creating target RG ${TARGET_RG} in ${TARGET_LOCATION}..."
az group create \
--name "${TARGET_RG}" \
--location "${TARGET_LOCATION}" \
--output none
# ---- REWRITE TF FOR DR ----
echo "Rewriting Terraform for ${TARGET_LOCATION} and -${SUFFIX} names..."
find "${EXPORT_DIR}" -type f -name "*.tf" | while read -r FILE; do
# Change region
sed -i "s/\"${SOURCE_LOCATION}\"/\"${TARGET_LOCATION}\"/g" "${FILE}"
# Append suffix to resource names (simple heuristic; review before apply)
sed -i -E "s/(name *= *\"[a-zA-Z0-9_-]+)\"/\1-${SUFFIX}\"/g" "${FILE}"
# Retarget RG references
sed -i "s/\"${SOURCE_RG}\"/\"${TARGET_RG}\"/g" "${FILE}"
done
echo "Rewrite done. Review ${EXPORT_DIR} and then:"
echo " cd ${EXPORT_DIR}"
echo " terraform init && terraform apply"
2. Step 2: migrate namespaces and workloads:
#!/usr/bin/env bash
set -euo pipefail
# ---- CONFIG ----
SOURCE_SUBSCRIPTION_ID="<SOURCE_SUBSCRIPTION_ID>"
TARGET_SUBSCRIPTION_ID="<TARGET_SUBSCRIPTION_ID>"
SRC_AKS_RG="<SRC_AKS_RG>"
SRC_AKS_NAME="<SRC_AKS_NAME>"
DST_AKS_RG="<DST_AKS_RG>"
DST_AKS_NAME="<DST_AKS_NAME>"
# Namespaces to exclude (system)
EXCLUDE_NS_REGEX="^(kube-system|kube-public|kube-node-lease|gatekeeper-system|azure-arc|default)$"
# ---- GET CONTEXTS ----
echo "Getting kubeconfig for source AKS..."
az account set --subscription "${SOURCE_SUBSCRIPTION_ID}"
az aks get-credentials -g "${SRC_AKS_RG}" -n "${SRC_AKS_NAME}" --overwrite-existing
SRC_CONTEXT=$(kubectl config current-context)
echo "Getting kubeconfig for destination AKS..."
az account set --subscription "${TARGET_SUBSCRIPTION_ID}"
az aks get-credentials -g "${DST_AKS_RG}" -n "${DST_AKS_NAME}" --overwrite-existing
DST_CONTEXT=$(kubectl config current-context)
echo "Source context: ${SRC_CONTEXT}"
echo "Destination context: ${DST_CONTEXT}"
echo ""
# ---- EXPORT NAMESPACES & WORKLOADS FROM SOURCE ----
EXPORT_DIR="./aks-migration-eus2"
mkdir -p "${EXPORT_DIR}"
kubectl config use-context "${SRC_CONTEXT}"
echo "Exporting namespaces and workloads from source cluster..."
NAMESPACES=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}')
for NS in ${NAMESPACES}; do
if [[ "${NS}" =~ ${EXCLUDE_NS_REGEX} ]]; then
echo "Skipping system namespace: ${NS}"
continue
fi
NS_DIR="${EXPORT_DIR}/${NS}"
mkdir -p "${NS_DIR}"
echo "Exporting namespace: ${NS}"
# Namespace definition
kubectl get ns "${NS}" -o yaml > "${NS_DIR}/namespace.yaml"
# Core workload types (adjust as needed)
for KIND in deployment statefulset daemonset service configmap secret ingress cronjob job; do
kubectl get "${KIND}" -n "${NS}" -o yaml > "${NS_DIR}/${KIND}.yaml" || true
done
done
echo "Export complete: ${EXPORT_DIR}"
# ---- APPLY TO DESTINATION CLUSTER ----
kubectl config use-context "${DST_CONTEXT}"
echo "Applying namespaces and workloads to destination cluster..."
for NS in ${NAMESPACES}; do
if [[ "${NS}" =~ ${EXCLUDE_NS_REGEX} ]]; then
continue
fi
NS_DIR="${EXPORT_DIR}/${NS}"
if [[ ! -d "${NS_DIR}" ]]; then
continue
fi
echo "Creating namespace: ${NS}"
kubectl apply -f "${NS_DIR}/namespace.yaml" || true
for KIND in deployment statefulset daemonset service configmap secret ingress cronjob job; do
FILE="${NS_DIR}/${KIND}.yaml"
if [[ -s "${FILE}" ]]; then
echo "Applying ${KIND} in ${NS}"
kubectl apply -n "${NS}" -f "${FILE}"
fi
done
done
echo "AKS namespace/workload migration complete."
3. Step 3: find storage accounts with aks subnet allows and migrate data
#!/usr/bin/env bash
set -euo pipefail
# ---- CONFIG ----
SOURCE_SUBSCRIPTION_ID="<SOURCE_SUBSCRIPTION_ID>"
TARGET_SUBSCRIPTION_ID="<TARGET_SUBSCRIPTION_ID>"
SRC_AKS_RG="<SRC_AKS_RG>"
SRC_AKS_NAME="<SRC_AKS_NAME>"
SUFFIX="eus2"
# ---- GET AKS VNET/SUBNETS ----
az account set --subscription "${SOURCE_SUBSCRIPTION_ID}"
AKS_INFO=$(az aks show -g "${SRC_AKS_RG}" -n "${SRC_AKS_NAME}")
# For Azure CNI with custom VNet:
VNET_SUBNET_IDS=$(echo "${AKS_INFO}" | jq -r '.agentPoolProfiles[].vnetSubnetId' | sort -u)
echo "AKS subnets:"
echo "${VNET_SUBNET_IDS}"
echo ""
# ---- FIND MATCHING STORAGE ACCOUNTS ----
echo "Finding storage accounts whose network rules allow these subnets..."
STORAGE_ACCOUNTS=$(az storage account list --query "[].id" -o tsv)
MATCHED_SA=()
for SA_ID in ${STORAGE_ACCOUNTS}; do
SA_NAME=$(basename "${SA_ID}")
SA_RG=$(echo "${SA_ID}" | awk -F/ '{print $5}')
RULES=$(az storage account network-rule list \
--account-name "${SA_NAME}" \
--resource-group "${SA_RG}" 2>/dev/null || echo "")
if [[ -z "${RULES}" ]]; then
continue
fi
for SUBNET_ID in ${VNET_SUBNET_IDS}; do
if echo "${RULES}" | jq -e --arg sn "${SUBNET_ID}" '.virtualNetworkRules[]?.virtualNetworkResourceId == $sn' >/dev/null 2>&1; then
echo "Matched storage account: ${SA_NAME} (RG: ${SA_RG}) for subnet: ${SUBNET_ID}"
MATCHED_SA+=("${SA_ID}")
break
fi
done
done
MATCHED_SA_UNIQ=($(printf "%s\n" "${MATCHED_SA[@]}" | sort -u))
echo ""
echo "Matched storage accounts:"
printf "%s\n" "${MATCHED_SA_UNIQ[@]}"
echo ""
# ---- COPY DATA TO DR STORAGE ACCOUNTS ----
for SA_ID in "${MATCHED_SA_UNIQ[@]}"; do
SA_NAME=$(basename "${SA_ID}")
SA_RG=$(echo "${SA_ID}" | awk -F/ '{print $5}')
TARGET_SA_NAME="${SA_NAME}${SUFFIX}"
echo "Processing storage account:"
echo " Source: ${SA_NAME} (RG: ${SA_RG})"
echo " Target: ${TARGET_SA_NAME}"
echo ""
# Source key
az account set --subscription "${SOURCE_SUBSCRIPTION_ID}"
SRC_KEY=$(az storage account keys list \
--account-name "${SA_NAME}" \
--resource-group "${SA_RG}" \
--query "[0].value" -o tsv)
SRC_CONN="DefaultEndpointsProtocol=https;AccountName=${SA_NAME};AccountKey=${SRC_KEY};EndpointSuffix=core.windows.net"
# Target key
az account set --subscription "${TARGET_SUBSCRIPTION_ID}"
# Adjust RG derivation if needed
TARGET_SA_RG="<TARGET_RG_FOR_${TARGET_SA_NAME}>"
TGT_KEY=$(az storage account keys list \
--account-name "${TARGET_SA_NAME}" \
--resource-group "${TARGET_SA_RG}" \
--query "[0].value" -o tsv)
TGT_CONN="DefaultEndpointsProtocol=https;AccountName=${TARGET_SA_NAME};AccountKey=${TGT_KEY};EndpointSuffix=core.windows.net"
# List containers in source
az account set --subscription "${SOURCE_SUBSCRIPTION_ID}"
CONTAINERS=$(az storage container list \
--connection-string "${SRC_CONN}" \
--query "[].name" -o tsv)
for CONT in ${CONTAINERS}; do
echo "Copying container: ${CONT}"
SRC_URL="https://${SA_NAME}.blob.core.windows.net/${CONT}"
TGT_URL="https://${TARGET_SA_NAME}.blob.core.windows.net/${CONT}"
azcopy copy "${SRC_URL}" "${TGT_URL}" --recursive=true
echo "Completed copy for container ${CONT}"
done
done
echo "Storage data copy to DR accounts complete."
No comments:
Post a Comment