Comment obtenir les détails de la connexion iSCSI sur Linux avec un script Shell

Les environnements informatiques modernes s’appuient fortement sur des solutions de stockage évolutives et performantes. L’une de ces technologies est iSCSI (Internet Small Computer System Interface), qui permet de transmettre des données de stockage au niveau des blocs sur des réseaux IP. La gestion et la surveillance des connexions iSCSI sont essentielles pour les administrateurs système, en particulier dans les infrastructures complexes gérées par les entreprises ou les MSP. Cet article de blog présente un script shell qui permet aux administrateurs Linux d’obtenir les détails de la connexion iSCSI sur Linux à l’aide d’un script shell; idéal pour les diagnostics, les rapports et l’intégration avec des plateformes RMM comme NinjaOne.

Contexte

Pour les professionnels de l’informatique, la visibilité de la structure de stockage sous-jacente est cruciale. Les configurations iSCSI, souvent invisibles pour les applications de niveau supérieur, sont fondamentales dans les clusters de virtualisation, les environnements à haute disponibilité et les centres de données. Pourtant, Linux ne fournit que des outils en ligne de commande comme iscsiadm pour interroger ces informations, et ils renvoient généralement des données non structurées. Cela peut entraver l’automatisation et la création de rapports en temps réel, éléments clés des opérations informatiques modernes.

Ce script shell comble cette lacune. Non seulement il recueille des informations détaillées sur la session et la connexion, mais il formate également le résultat dans des tableaux HTML et le pousse éventuellement dans un champ personnalisé NinjaOne WYSIWYG. Cela permet une visibilité transparente dans les tableaux de bord RMM, aucune analyse manuelle ou recherche de journaux n’est nécessaire.

Le script

#!/usr/bin/env bash

# Description: Gets the details of all iSCSI devices on the system.
# By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
# Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
# Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
# Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
# Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
# Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
# Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
# EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#
# Release Notes: Initial Release
#
# Usage: [-WYSIWYGCustomFieldName <Arg>]
#
# Preset Parameter: --help
#   Displays the help menu.
#
# Preset Parameter: --WYSIWYGCustomFieldName "ReplaceMeWithAnyMultilineCustomField"
#   Optionally specify the name of a WYSIWYG custom field to save the results to.

# Functions
# Print an error message and exit with a specific status code
die() {
    local _ret="${2:-1}"
    test "${_PRINT_HELP:-no}" = yes && print_help >&2
    echo "$1" >&2
    exit "${_ret}"
}

print_help() {
    printf '\n\n%s\n\n' 'Usage: [-WYSIWYGCustomFieldName <Arg>]'
    printf '%s\n' 'Preset Parameter: --help'
    printf '\t%s\n' "Displays the help menu."
    printf '%s\n' 'Preset Parameter: --WYSIWYGCustomFieldName "ReplaceMeWithAnyMultilineCustomField"'
    printf '\t%s\n' "Optionally specify the name of a WYSIWYG custom field to save the results to."
}

convertToHTMLTable() {
    local _arg_delimiter=" "
    local _arg_inputObject

    # Process command-line arguments for the function.
    while test $# -gt 0; do
        _key="$1"
        case "$_key" in
        --delimiter | -d)
            test $# -lt 2 && echo "[Error] Missing value for the required argument" >&2 && return 1
            _arg_delimiter=$2
            shift
            ;;
        --*)
            echo "[Error] Got an unexpected argument" >&2
            return 1
            ;;
        *)
            _arg_inputObject=$1
            ;;
        esac
        shift
    done

    # Handles missing input by checking stdin or returning an error.
    if [[ -z $_arg_inputObject ]]; then
        if [ -p /dev/stdin ]; then
            _arg_inputObject=$(cat)
        else
            echo "[Error] Missing input object to convert to table" >&2
            return 1
        fi
    fi

    local htmlTable="<table>\n"
    htmlTable+=$(printf '%b' "$_arg_inputObject" | head -n1 | awk -F "$_arg_delimiter" '{
        printf "<tr>"
        for (i=1; i<=NF; i+=1)
        { printf "<th>"$i"</th>" }
        printf "</tr>"
    }')
    htmlTable+="\n"
    htmlTable+=$(printf '%b' "$_arg_inputObject" | tail -n +2 | awk -F "$_arg_delimiter" '{
        printf "<tr>"
        for (i=1; i<=NF; i+=1)
        { printf "<td>"$i"</td>" }
        print "</tr>"
    }')
    htmlTable+="\n</table>"

    printf '%b' "$htmlTable" '\n'
}

# Function to set a custom field
function SetCustomField() {
    if [[ -z "$1" ]] || [[ -z "$2" ]]; then
        echo "[Error] Missing required arguments."
        return 1
    fi
    local customfieldName=$1
    local customfieldValue=$2
    if [ -f "${NINJA_DATA_PATH}/ninjarmm-cli" ]; then
        if [ -x "${NINJA_DATA_PATH}/ninjarmm-cli" ]; then
            if "$NINJA_DATA_PATH"/ninjarmm-cli get "$customfieldName" >/dev/null; then
                # check if the value is greater than 10000 characters
                if [ ${#customfieldValue} -gt 10000 ]; then
                    echo "[Warn] Custom field value is greater than 10000 characters"
                fi
                if ! echo "${customfieldValue::10000}" | "$NINJA_DATA_PATH"/ninjarmm-cli set --stdin "$customfieldName"; then
                    echo "[Warn] Failed to set custom field"
                else
                    echo "[Info] Custom field value set successfully"
                fi
            else
                echo "[Warn] Custom Field ($customfieldName) does not exist or agent does not have permission to access it"
            fi
        else
            echo "[Warn] ninjarmm-cli is not executable"
        fi
    else
        echo "[Warn] ninjarmm-cli does not exist"
    fi
}

# Parse the command-line arguments
parse_commandline() {
    while test $# -gt 0; do
        _key="$1"
        case "$_key" in
        -w | --WYSIWYGCustomFieldName)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_WYSIWYGCustomFieldName="$2"
            shift
            ;;
        --WYSIWYGCustomFieldName=*)
            _arg_WYSIWYGCustomFieldName="${_key##--wysiwygCustomFieldName=}"
            ;;
        -w*)
            _arg_WYSIWYGCustomFieldName="${_key##-w}"
            ;;
        --help)
            _PRINT_HELP=yes die "" 0
            ;;
        -h)
            _PRINT_HELP=yes die "" 0
            ;;
        *)
            _PRINT_HELP=yes die "[Error] Got an unexpected argument '$1'" 1
            ;;
        esac
        shift
    done
}

parse_commandline "$@"

# If script form variables are used, replace the command line parameters with their value.
if [[ -n "${wysiwygCustomFieldName}" ]]; then
    _arg_WYSIWYGCustomFieldName="$wysiwygCustomFieldName"
fi

# If no valid custom field was provided, display an error message and exit the script.
# if [[ -z "${_arg_WYSIWYGCustomFieldName}" ]]; then
#     _PRINT_HELP=yes die "[Error] You must provide a valid WYSIWYG custom field name." 1
# fi

# Check if the root user is running the script
if [[ $EUID -ne 0 ]]; then
    die "[Error] This script must be run as root." 1
fi

if ! [ "$(command -v iscsiadm)" ]; then
    die "[Error] The command iscsiadm not installed." 1
fi

if ! [ -f "/etc/iscsi/iscsid.conf" ]; then
    die "[Error] /etc/iscsi/iscsid.conf is not present." 1
fi

# Print Initiator Name
_connection_identifier=$(grep -v "#" /etc/iscsi/initiatorname.iscsi 2>/dev/null | awk -F= '{print $2}')

# Get the iSCSI session details
session=$(iscsiadm -m session -P 3 2>&1)

# HTML title variables
_title_connections="iSCSI Connections"
_title_sessions="iSCSI Sessions"

# Delimiter for result parsing
_delimiter=";"

# iSCSI Connections
declare -g _iscsi_connections=""
declare -g _initiator_address=""
declare -g _initiator_port=""
declare -g _target_address=""
declare -g _target_port=""

# iSCSI Sessions
declare -g _iscsi_sessions=""
declare -g _authentication_type=""
declare -g _initiator_name=""
declare -g _initiator_node_address=""
declare -g _initiator_node_portal_address=""
declare -g _initiator_side_identifier=""
declare -g _is_connected=""
declare -g _is_data_digest=""
declare -g _is_persistent=""
declare -g _number_of_luns=0
declare -g _session_identifier=""
declare -g _target_node_address=""
declare -g _target_side_identifier=""

while read -r line; do
    if [[ "$line" == "Target:"* ]]; then
        if [[ -n "${_initiator_address}" ]]; then
            # Add results
            _iscsi_connections="${_iscsi_connections}\n"
            _iscsi_connections="${_iscsi_connections}${_connection_identifier}${_delimiter}"
            _iscsi_connections="${_iscsi_connections}${_initiator_address}${_delimiter}"
            _iscsi_connections="${_iscsi_connections}${_initiator_port}${_delimiter}"
            _iscsi_connections="${_iscsi_connections}${_target_address}${_delimiter}"
            _iscsi_connections="${_iscsi_connections}${_target_port}"
            _iscsi_sessions="${_iscsi_sessions}\n"
            _iscsi_sessions="${_iscsi_sessions}${_authentication_type}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_initiator_name}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_initiator_node_address}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_initiator_node_portal_address}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_initiator_side_identifier}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_is_connected}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_is_data_digest}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_is_persistent}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_number_of_luns}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_session_identifier}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_target_node_address}${_delimiter}"
            _iscsi_sessions="${_iscsi_sessions}${_target_side_identifier}"
        fi

        # Reset the variables
        # iSCSI Connections
        _initiator_address=""
        _initiator_port=""
        _target_address=""
        _target_port=""

        # iSCSI Sessions
        _authentication_type=""
        _initiator_name=""
        _initiator_node_address=""
        _initiator_node_portal_address=""
        _initiator_side_identifier=""
        _is_connected=""
        _is_data_digest=""
        _is_persistent=""
        _number_of_luns=0
        _session_identifier=""
        _target_node_address=""
        _target_side_identifier=""

        # Get the target
        connection_identifier=$(echo "$line" | cut -d ':' -f 2-3 | awk '{print $1}')
        _target_node_address=$(echo "$connection_identifier" | cut -d ':' -f 1-2 | awk '{$1=$1;print}')
        _target_address=$(echo "$connection_identifier" | cut -d ':' -f 1 | awk '{$1=$1;print}')
        _target_side_identifier=$(echo "$connection_identifier" | cut -d ':' -f 2 | awk '{print $1}')
    fi
    if [[ "$line" == *"Current Portal:"* ]]; then
        portal=$(echo "$line" | cut -d ':' -f 2-3)
        _initiator_node_portal_address=$(echo "$portal" | cut -d ':' -f 1-2 | cut -d ',' -f 1 | awk '{$1=$1;print}')
        _initiator_port=$(echo "$portal" | cut -d ':' -f 2 | cut -d ',' -f 1 | awk '{$1=$1;print}')
        _target_port=$(echo "$portal" | cut -d ':' -f 2 | cut -d ',' -f 1 | awk '{$1=$1;print}')
    fi
    if [[ "$line" == *"Persistent Portal:"* ]]; then
        _is_persistent="Yes"
    fi
    if [[ "$line" == *"Iface Name:"* ]]; then
        _initiator_name=$(echo "$line" | cut -d ':' -f 2 | awk '{$1=$1;print}')
    fi
    if [[ "$line" == *"Iface Initiatorname:"* ]]; then
        initiator=$(echo "$line" | cut -d ':' -f 2-4)
        _initiator_node_address=$(echo "$initiator" | cut -d ':' -f 1-2 | awk '{$1=$1;print}')
        _initiator_side_identifier=$(echo "$initiator" | cut -d ':' -f 3 | awk '{$1=$1;print}')
        _initiator_address=$(echo "$initiator" | cut -d ':' -f 1 | awk '{$1=$1;print}')
    fi
    if [[ "$line" == *"iSCSI Connection State:"* ]]; then
        _is_connected=$(echo "$line" | cut -d ':' -f 2 | awk '{$1=$1;print}')
    fi
    if [[ "$line" == *"SID:"* ]]; then
        _session_identifier=$(echo "$line" | cut -d ':' -f 2 | awk '{$1=$1;print}')
    fi
    if [[ "$line" == *"DataDigest:"* ]]; then
        _is_data_digest=$(echo "$line" | cut -d ':' -f 2 | awk '{$1=$1;print}')
    fi
    if [[ "$line" == *"CHAP:"* ]]; then
        _authentication_type="CHAP"
    fi
    if [[ "$line" == *"username: <empty>"* ]]; then
        # If the username is empty, set auth_method to None
        _authentication_type="None"
    fi
    if [[ "$line" == *"Lun:"* ]]; then
        _number_of_luns=$((_number_of_luns + 1))
    fi

done <<<"$session"

_iscsi_connections="${_iscsi_connections}\n${_connection_identifier}${_delimiter}${_initiator_address}${_delimiter}${_initiator_port}${_delimiter}${_target_address}${_delimiter}${_target_port}"
_iscsi_sessions="${_iscsi_sessions}\n${_authentication_type}${_delimiter}${_initiator_name}${_delimiter}${_initiator_node_address}${_delimiter}${_initiator_node_portal_address}${_delimiter}${_initiator_side_identifier}${_delimiter}${_is_connected}${_delimiter}${_is_data_digest}${_delimiter}${_is_persistent}${_delimiter}${_number_of_luns}${_delimiter}${_session_identifier}${_delimiter}${_target_node_address}${_delimiter}${_target_side_identifier}"

# Output results to Activity Feed
_console_iscsi_connections="Connection Identifier${_delimiter}Initiator Address${_delimiter}Initiator Port${_delimiter}Target Address${_delimiter}Target Port\n${_iscsi_connections}"
_console_iscsi_sessions="Auth Type${_delimiter}Name${_delimiter}Init Address${_delimiter}Init Portal Address${_delimiter}Init Side ID${_delimiter}Connected${_delimiter}Data Digest${_delimiter}Persistent${_delimiter}LUNs${_delimiter}SID${_delimiter}Tgt Node Address${_delimiter}Tgt Side Identifier\n${_iscsi_sessions}"
# Print the result to activity feed
echo -e "\n---${_title_connections}---"
echo -e "${_console_iscsi_connections}" | column -t -s ';'
echo -e "\n---${_title_sessions}---"

# Check if there are no active iSCSI sessions
if [[ "${session}" == *"No active sessions"* || $(echo "$session" | wc -l) -le 1 ]]; then
    echo "No active iSCSI sessions"
    # If no sessions are active, notify that no data to be saved to custom field
    if [[ -n "${_arg_WYSIWYGCustomFieldName}" ]]; then
        echo "[Info] No data to save to custom field."
    fi
    exit 0
else
    echo -e "${_console_iscsi_sessions}" | column -t -s ';'
fi

# Add Headers
_iscsi_connections="Connection Identifier${_delimiter}Initiator Address${_delimiter}Initiator Port${_delimiter}Target Address${_delimiter}Target Port\n${_iscsi_connections}"
_iscsi_sessions="Authentication Type${_delimiter}Initiator Name${_delimiter}Initiator Node Address${_delimiter}Initiator Node Portal Address${_delimiter}Initiator Side Identifier${_delimiter}Is Connected${_delimiter}Is Data Digest${_delimiter}Is Persistent${_delimiter}Number of LUNs${_delimiter}Session Identifier${_delimiter}Target Node Address${_delimiter}Target Side Identifier\n${_iscsi_sessions}"

# Convert the CSV result to an HTML table
html_connections_result=$(convertToHTMLTable "$_iscsi_connections" --delimiter "${_delimiter}")
html_sessions_result=$(convertToHTMLTable "$_iscsi_sessions" --delimiter "${_delimiter}")

# Combine the results
html_result+="<h3>${_title_connections}</h3>"
html_result+="$html_connections_result"
html_result+="<h3>${_title_sessions}</h3>"
html_result+="$html_sessions_result"

if [[ -n "${_arg_WYSIWYGCustomFieldName}" ]]; then
    SetCustomField "$_arg_WYSIWYGCustomFieldName" "$html_result"
fi

 

Description détaillée

Voici comment fonctionne le script, étape par étape :

1. Contrôles préalables et analyse des arguments

  • Privilèges root : Le script doit être exécuté en tant que root pour interroger les sessions iSCSI.
  • Vérifications des dépendances : Vérifie que iscsiadm est installé et que /etc/iscsi/iscsid.conf existe.
  • Argument facultatif : Accepte un paramètre –WYSIWYGCustomFieldName pour spécifier un champ personnalisé NinjaOne à éditer.

2. Exécution des commandes et collecte des données

  • Exécute iscsiadm -m session -P 3 pour extraire les informations verbales de la session.
  • Analyse les données telles que :
    • État de la connexion
    • Type d’authentification (CHAP ou Aucune)
    • Adresses de la cible et de l’initiateur
    • Portail et identifiants de session
    • Nombre de LUN (Logical Unit Numbers)

3. Structuration des données

  • Enregistre les détails de la connexion dans des chaînes délimitées par des points-virgules.
  • Formate les « Connexions » et les « Sessions » en sorties lisibles sous forme de tableaux.

4. Conversion HTML

Une fonction convertToHTMLTable transforme les données délimitées en tableaux HTML valides. Cela permet un affichage riche dans les interfaces utilisateur supportant le HTML, comme le champ WYSIWYG de NinjaOne.

5. Sortie optionnelle vers NinjaOne

Si un champ WYSIWYG valide est fourni, le script utilise ninjarmm-cli pour pousser le résultat au format HTML dans le champ personnalisé, garantissant ainsi que les données sont facilement accessibles depuis le tableau de bord NinjaOne.

6. Sortie de la console visuelle

En plus d’envoyer les données dans NinjaOne, le script imprime les résumés de connexion et de session directement sur la console en utilisant la colonne -t pour un formatage aligné.

Cas d’utilisation potentiels

Étude de cas : MSP Monitoring High-Availability iSCSI Storage (Surveillance du stockage iSCSI à haute disponibilité)

Un fournisseur de services gérés (MSP) entretient un cluster de serveurs Linux connectés à un SAN centralisé via iSCSI. Il arrive qu’un ou plusieurs serveurs soient confrontés à des problèmes de latence de stockage. Le MSP déploie ce script via NinjaOne pour qu’il s’exécute automatiquement chaque semaine, en stockant l’état de la session, les IP cibles et le nombre de LUN dans des champs personnalisés. Lorsque la latence est détectée, l’équipe peut rapidement établir une corrélation entre les changements de stockage et les régressions de performances, ce qui permet d’économiser des heures de diagnostic.

Comparaisons

MéthodeDescriptionPrêt pour l’automatisationVisualisationIntégration de NinjaOne
iscsiadm manuellementOutil CLI brut
Analyse Bash personnaliséeLogique d’analyse personnalisée⚠️
Ce scriptAutomatisation complète + sortie HTML

D’autres méthodes nécessitent une interprétation manuelle ou des outils externes. Ce script offre une solution automatisée de bout en bout, adaptée aux RMM.

FAQ

Q : Ce script modifie-t-il la configuration du système ?

R : Non. Il est strictement en lecture seule et sans danger pour les diagnostics.

Q : Que se passe-t-il si aucune session iSCSI n’est active ?

R : Il quitte le système et signale qu’il n’y a pas de session active, évitant ainsi les mises à jour inutiles des champs.

Q : Existe-t-il une limite de caractères pour les champs personnalisés ?

R : Oui. Le script émet un avertissement si la sortie HTML dépasse 10 000 caractères, ce qui est une limite typique pour les champs NinjaOne.

Q : Avec quelles plateformes est-il compatible ?

R : Ce script est conçu pour les systèmes Linux sur lesquels iscsiadm est installé et les agents NinjaOne sont configurés.

Implications

En utilisant un script shell sur Linux pour obtenir les détails des connexions iSCSI, les équipes informatiques bénéficient d’une visibilité structurée sur les couches de stockage, qui sont souvent à l’origine de problèmes complexes de performance ou de fiabilité. De plus, l’intégration de cette visibilité directement dans les tableaux de bord RMM permet un triage plus rapide, réduit les temps d’arrêt et améliore la transparence pour les clients.

Sur le plan de la sécurité, l’exposition d’informations au niveau de la session facilite également l’audit. Il est essentiel d’identifier les cibles connectées et de savoir si l’authentification CHAP est en place pour garantir des configurations sécurisées.

Recommandations

  • Planifier des exécutions périodiques : Utilisez les stratégies ou l’automatisation de NinjaOne pour exécuter ce script à intervalles réguliers.
  • Contrôler la taille des champs : Assurez-vous que les champs personnalisés ne sont pas tronqués en raison des limites de caractères.
  • Exécution sécurisée : Toujours exécuter avec les privilèges de root sur des agents sécurisés et de confiance.
  • Combinaison avec les alertes : Créez des alertes NinjaOne basées sur les valeurs des champs (par exemple, « Aucune session active » ou « Cible inattendue »).

Conclusion

Les utilisateurs de NinjaOne qui souhaitent approfondir leurs capacités de surveillance et de diagnostic devraient envisager de déployer ce script. Il illustre la manière dont le script shell sur Linux peut être exploité pour obtenir les détails de la connexion iSCSI d’une manière qui soit à la fois automatisable et lisible par l’homme. Grâce à son intégration dans les champs personnalisés de NinjaOne, il transforme les données obscures des sessions de stockage en informations exploitables, améliorant ainsi l’efficacité opérationnelle et la réactivité.

Pour les flux de travail plus avancés, ce script peut servir de modèle pour l’extension à d’autres diagnostics de protocole, faisant de NinjaOne non seulement un outil de surveillance mais un véritable centre de commande opérationnel.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service delivery tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Catégories :

Vous pourriez aussi aimer