Skip to content

Instantly share code, notes, and snippets.

@fl64
Last active February 5, 2025 14:20
Show Gist options
  • Save fl64/75a5904f9bbadcb3d5a12c3dec3c7708 to your computer and use it in GitHub Desktop.
Save fl64/75a5904f9bbadcb3d5a12c3dec3c7708 to your computer and use it in GitHub Desktop.
dump-vm-debug-bundle

how to run

Usage example:

curl -s https://gist.githubusercontent.com/fl64/75a5904f9bbadcb3d5a12c3dec3c7708/raw/vm-debug-bundle.sh | bash -s -- -n env-1b4119-testcases -v env-1b4119-vm-hotplug-iso-vi-cr-2 --bundle-path . --keep --no-tar

Params:

  • --namespace / -n - Namespace where the VM is located
  • --vm-name / -v - Name of the VirtualMachine
  • --bundle-path - Path to save the bundle (directory will be created if missing)
  • --keep - Keep temporary folder with collected data after execution
  • --no-tar - Skip tar archive creation (requires --keep to retain files)
#!/usr/bin/env bash
set -euo pipefail
# Configuration defaults
: "${BUNDLE_PATH:=.}"
: "${KEEP_FILES:=0}"
: "${NO_TAR:=0}"
# Color output functions
say() {
local color="${1}"
shift
echo -e "\033[1;${color}m${*}\033[0m"
}
say_green() { say 32 "${@}"; }
say_red() { say 31 "${@}"; }
say_yellow() { say 33 "${@}"; }
say_magenta(){ say 35 "${@}"; }
# Validate required commands
check_dependencies() {
local deps=("kubectl" "jq" "tar")
for dep in "${deps[@]}"; do
if ! command -v "${dep}" >/dev/null 2>&1; then
say_red "Error: Required command '${dep}' not found"
exit 1
fi
done
}
parse_args() {
while (($#)); do
case "${1}" in
--ns|-n) shift; NAMESPACE="${1}" ;;
--vm-name|-v) shift; VM_NAME="${1}" ;;
--bundle-path) shift; BUNDLE_PATH="${1}" ;;
--keep) KEEP_FILES=1 ;;
--no-tar) NO_TAR=1 ;;
*) say_red "Unknown argument: ${1}"; exit 1 ;;
esac
shift
done
# Validate required parameters
if [[ -z "${NAMESPACE:-}" || -z "${VM_NAME:-}" ]]; then
say_red "Missing required parameters:"
say_red "Namespace: ${NAMESPACE:-[unset]}"
say_red "VirtualMachine: ${VM_NAME:-[unset]}"
exit 1
fi
}
dump_system_logs() {
local output_dir="${1}/d8-virtualization/"
mkdir -p "${output_dir}"
local pods=$(kubectl -n d8-virtualization get pods -o name | cut -d/ -f2)
for pod in ${pods}; do
say_yellow "- Processing pod: ${pod}"
if kubectl logs -n d8-virtualization "${pod}" > "${output_dir}/${pod}.log"; then
echo " Logs saved to: ${pod}.log"
fi
done
}
dump_resource() {
local namespace="${1}" resource_type="${2}" resource_name="${3}" output_dir="${4}" prefix="${5}"
local yaml_file="${output_dir}/${prefix}${resource_type,,}-${resource_name}.yaml"
say_yellow " - ${resource_type} ${resource_name}"
kubectl -n "${namespace}" get "${resource_type}" "${resource_name}" -o yaml > "${yaml_file}"
if [[ ${?} -eq 0 ]]; then
echo " Saved to: ${yaml_file##*/}"
else
say_red " Failed to get ${resource_type} ${resource_name}"
rm -f "${yaml_file}"
return 1
fi
}
dump_block_device() {
local namespace="${1}" bd_kind="${2}" bd_name="${3}" output_dir="${4}"
output_dir="${output_dir}/${bd_kind,,}-${bd_name}"
mkdir -p "${output_dir}"
dump_resource "${namespace}" "${bd_kind}" "${bd_name}" "${output_dir}" ""
if [[ "${bd_kind}" == "VirtualDisk" || "${bd_kind}" == "VirtualImage" ]]; then
local pvc_name=$(kubectl -n "${namespace}" get "${bd_kind}" "${bd_name}" -o json |
jq -cr '.status.target.persistentVolumeClaimName')
if [[ -n "${pvc_name}" && "${pvc_name}" != "null" ]]; then
dump_resource "${namespace}" "persistentvolumeclaim" "${pvc_name}" "${output_dir}" "pvc-"
local pv_name=$(kubectl -n "${namespace}" get persistentvolumeclaim "${pvc_name}" -o json |
jq -cr '.spec.volumeName')
[[ -n "${pv_name}" ]] && dump_resource "" "persistentvolume" "${pv_name}" "${output_dir}" "pv-"
fi
fi
}
dump_pod_data() {
local namespace="${1}" pod_name="${2}" output_dir="${3}"
local prefix="pod-${pod_name}"
output_dir="${output_dir}/${prefix}"
mkdir -p "${output_dir}"
# Get pod YAML
kubectl -n "${namespace}" get pod "${pod_name}" -o yaml > "${output_dir}/${prefix}.yaml"
echo " Saved to: ${prefix}.yaml"
# Get pod logs
if kubectl logs -n "${namespace}" "${pod_name}" > "${output_dir}/${prefix}.log"; then
echo " Logs saved to: ${prefix}.log"
fi
# Get XML files if pod is running
local phase=$(kubectl get pod -n "${namespace}" "${pod_name}" -o json | jq -cr '.status.phase')
if [[ "${phase}" == "Running" ]]; then
local xml_name="${namespace}_${VM_NAME}"
for path in "run/libvirt/qemu" "etc/libvirt/qemu"; do
local xml_file="${output_dir}/$(echo ${path} | cut -d/ -f1)-${xml_name}.xml"
if kubectl cp -n "${namespace}" "${pod_name}:${path}/${xml_name}.xml" "${xml_file}" >/dev/null 2>&1; then
echo " XML saved to: ${xml_file##*/}"
fi
done
fi
}
create_bundle() {
local namespace="${1}" vm_name="${2}" output_dir="${3}"
mkdir -p "${output_dir}"
say_green "\nDumping system logs:"
dump_system_logs "${output_dir}"
say_green "\nDumping core resources:"
dump_resource "${namespace}" "vms" "${vm_name}" "${output_dir}" "vm-"
dump_resource "${namespace}" "internalvirtualizationvirtualmachines" "${vm_name}" "${output_dir}" "kvvm-"
dump_resource "${namespace}" "internalvirtualizationvirtualmachineinstances" "${vm_name}" "${output_dir}" "kvvmi-"
# Dump pods and related data
say_green "\nDumping pod information:"
local pods=$(kubectl -n "${namespace}" get pods -l "vm.kubevirt.internal.virtualization.deckhouse.io/name=${vm_name}" -o name | cut -d/ -f2)
for pod in ${pods}; do
say_yellow "- Processing pod: ${pod}"
dump_pod_data "${namespace}" "${pod}" "${output_dir}"
done
# Dump block devices
say_green "\nDumping block devices:"
local vm_json=$(kubectl -n "${namespace}" get vms "${vm_name}" -o json)
# Static block devices
while IFS= read -r bd; do
[[ -z "${bd}" ]] && continue
dump_block_device "${namespace}" "$(jq -r '.kind' <<< "${bd}")" "$(jq -r '.name' <<< "${bd}")" "${output_dir}"
done <<< "$(jq -cr '.spec.blockDeviceRefs[]' <<< "${vm_json}")"
# Hotplug block devices
while IFS= read -r bd; do
[[ -z "${bd}" ]] && continue
local bd_kind=$(jq -r '.kind' <<< "${bd}")
local bd_name=$(jq -r '.name' <<< "${bd}")
dump_block_device "${namespace}" "${bd_kind}" "${bd_name}" "${output_dir}"
# Dump associated VMBDA
local vmbda_name=$(jq -r '.virtualMachineBlockDeviceAttachmentName' <<< "${bd}")
dump_resource "${namespace}" "vmbda" "${vmbda_name}" "${output_dir}" ""
done <<< "$(jq -cr '.status.blockDeviceRefs[] | select(.hotplugged==true)' <<< "${vm_json}")"
}
main() {
check_dependencies
parse_args "${@}"
local timestamp=$(date +"%Y-%m-%d-%H-%M")
local bundle_dir="${BUNDLE_PATH}/v12n-vm-${VM_NAME}-${timestamp}"
local bundle_tar="${bundle_dir}.tar.gz"
say_magenta "\nStarting bundle creation: ${VM_NAME}"
create_bundle "${NAMESPACE}" "${VM_NAME}" "${bundle_dir}"
if ((NO_TAR == 0)); then
say_magenta "\nCompressing bundle..."
tar -czf "${bundle_tar}" -C "${bundle_dir%/*}" "${bundle_dir##*/}"
say_magenta "Bundle size: $(du -h "${bundle_tar}" | cut -f1)"
echo "Created: ${bundle_tar}"
fi
((KEEP_FILES == 0)) && { say_magenta "Cleaning up..."; rm -rf "${bundle_dir}"; }
say_green "\nOperation completed successfully"
}
main "${@}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment