групповая IP-адресация на другую подсеть с помощью оболочки

Вопрос или проблема

На самом деле, это для кластера Redis. У меня есть 3 IP-адреса мастеров и 3 IP-адреса слейвов, нужно получить 3 пары IP-адресов, где адреса в паре находятся в разных подсетях /24.

Иными словами, given a list of n IP-адресов (n — четное число, и не более половины IP-адресов находятся в одной подсети /24), как разделить их на пары n/2, где два адреса в каждой паре находятся в разных подсетях /24?

Пары должны быть сохранены в виде ключей и значений ассоциативного массива ip_map. Например, дан список IP-адресов, хранящийся в массиве $ips:

ips=(
 "172.211.91.63"
 "172.211.90.61"
 "172.211.91.30"  
 "172.211.90.173" 
 "172.211.89.233"
 "172.211.89.166" 
)

Результат может выглядеть следующим образом:

declare -A ip_map=(
  [172.211.91.63]=172.211.90.61
  [172.211.91.30]=172.211.89.233
  [172.211.90.173]=172.211.89.166
)

Интуитивно я думаю, что если отсортировать ваши IP-адреса по ведрам, по одному для каждой подсети, и продолжать выбирать по 2 из 2 самых полных ведер, это должно позволить вам объединить их все. Это могло бы выглядеть так:

perl -e '
  push @{$s{s/\.\d+$//r}}, $_ for @ARGV;
  @l = values %s;
  for ($n = @ARGV; $n > 0; $n -= 2) {
    @l = sort {@$b <=> @$a} @l;
    printf "ip_map[%s]=%s\n", pop(@{$l[0]}), pop(@{$l[1]});
  }' -- "${ips[@]}"

Что на вашем примере дает:

ip_map[172.211.91.30]=172.211.89.166
ip_map[172.211.90.173]=172.211.91.63
ip_map[172.211.90.61]=172.211.89.233
  • code for @ARGV перебирает параметры, переданные встроенному скрипту (выражение -e), используя переменную по умолчанию $_ в качестве переменной цикла. Это более короткая форма for (@ARGV) {code}.
  • s/\.\d+$//r (которая действует по умолчанию на $_) удаляет часть .<digits> в конце, но с флагом r результат возвращается вместо того, чтобы сохраняться обратно в $_. Так что это расширяется до части подсети /24 IP-адреса.
  • В первой строке мы создаем ассоциативный массив %s (также известный как hash в perl), где $s{subnet} является ссылкой на список IP-адресов в этой подсети. @{that} разыменовывает его, чтобы мы могли push IP-адрес ($_) в список.
  • @l = values %s: получает значения хеша, то есть ссылки на наши ведерные списки в @l.
  • Итак, n/2 раз мы выбираем два IP-адреса из двух самых больших подсетей, сначала сортируя их по размеру (когда список используется в скалярном контексте, например в @$b <=> @$a, он расширяется до количества элементов в нем, поэтому мы сравниваем длину списков, чтобы их отсортировать) и затем pop один из первого и второго списка (pop(@{$l[...]})).

Вы можете оценить вывод в bash (с помощью source <(that-code) или eval "$(that-code)"), но вы могли бы также использовать perl для всего этого, оболочки (особенно bash) не очень хороши в программировании.

Если бы мне пришлось использовать оболочку, я бы предпочел zsh вместо bash, где что-то эквивалентное могло бы выглядеть так:

typeset -A s ip_map
for ip ($ips) s[${ip%.*}]="x$s[${ip%.*}] $ip"
l=( $s )
repeat $#ips/2 {
  l=( ${(O)l} )
  ip_map[${l[1][(w)-1]}]=${l[2][(w)-1]}
  l[1]=${l[1]#x}  l[2]=${l[2]#x}
  l[1]=${l[1]% *} l[2]=${l[2]% *}
}

Ассоциативные массивы zsh, как и массивы bash, не могут содержать списки, здесь мы храним список IP-адресов, разделенный пробелами (так что мы можем использовать индексирование на основе (w) позже) в значениях ассоциативного массива и с префиксом строки из x, использованной в качестве некоторой формы отметки для подсчета, по одной для каждого IP-адреса, так что это выглядит так:

typeset -A s=(
  [172.211.89]='xx 172.211.89.233 172.211.89.166'
  [172.211.90]='xx 172.211.90.61 172.211.90.173'
  [172.211.91]='xx 172.211.91.63 172.211.91.30'
)

Таким образом, ${(O)l}, которую мы используем для Oрганизации списка в обратном порядке, лексически сортирует по количеству элементов.

Ответ или решение

Для решения задачи, заключающейся в парном связывании IP-адресов из разных подсетей /24, можно использовать скрипт на Perl или Zsh. Этот подход потребует сначала группировать IP-адреса по их подсетям, а затем формировать пары из различных подсетей. Ниже приведено полное решение с примерами.

Шаги решения:

  1. Сборка входных данных: Создайте массив IP-адресов, который вам нужно будет обработать.
  2. Группировка адресов по подсетям: Разделите IP-адреса по первому трем октетам (класс /24).
  3. Формирование пар: Извлеките по одному IP-адресу из двух наиболее заполненных подсетей.

Пример реализации на Perl:

Вот пример скрипта на Perl, который выполняет указанные шаги.

#!/usr/bin/perl
use strict;
use warnings;

my @ips = (
    "172.211.91.63",
    "172.211.90.61",
    "172.211.91.30",
    "172.211.90.173",
    "172.211.89.233",
    "172.211.89.166"
);

# Хеш для группировки IP-адресов по подсетям
my %subnets;

# Группируем IP-адреса по подсетям /24
foreach my $ip (@ips) {
    my $subnet = $ip; $subnet =~ s/\.\d+$//; # Удаление последнего октета
    push @{$subnets{$subnet}}, $ip;
}

# Параметры для формирования пар
my @pairs;
while (scalar @ips > 0) {
    # Сортировка подсетей по количеству IP-адресов
    my @sorted_subnets = sort { scalar @{$subnets{$b}} <=> scalar @{$subnets{$a}} } keys %subnets;

    # Выбор двух IP-адресов из двух наибольших подсетей
    if (scalar @sorted_subnets < 2) {
        die "Недостаточно подсетей для формирования пар.";
    }

    my $ip1 = pop @{$subnets{$sorted_subnets[0]}};
    my $ip2 = pop @{$subnets{$sorted_subnets[1]}};

    # Присоединяем IP-адреса в парах
    push @pairs, "$ip1 => $ip2";

    # Удаляем подсеть, если она пустая
    delete $subnets{$sorted_subnets[0]} if scalar @{$subnets{$sorted_subnets[0]}} == 0;
    delete $subnets{$sorted_subnets[1]} if scalar @{$subnets{$sorted_subnets[1]}} == 0;
}

# Вывод результата
foreach my $pair (@pairs) {
    print "$pair\n";
}

Пример реализации на Zsh:

Теперь приведем аналогичное решение на Zsh.

#!/bin/zsh

# Объявляем массив IP-адресов
ips=(
    "172.211.91.63"
    "172.211.90.61"
    "172.211.91.30"
    "172.211.90.173"
    "172.211.89.233"
    "172.211.89.166"
)

# Объявляем ассоциативный массив для подсетей
typeset -A subnets 

# Группировка IP-адресов по подсетям
for ip in $ips; do
    subnet="${ip%.*}"
    subnets[$subnet]+=" $ip"
done

# Список пар IP-адресов
pairs=()

# Формирование пар
while [ ${#ips[@]} -gt 0 ]; do
    sorted_subnets=($(printf "%s\n" "${!subnets[@]}" | sort -n))

    if [ ${#sorted_subnets[@]} -lt 2 ]; then
        echo "Недостаточно подсетей для формирования пар."
        exit 1
    fi

    ip1="${subnets[${sorted_subnets[1]}]%% *}" # Берём первый IP из первой подсети
    ip2="${subnets[${sorted_subnets[0]}]%% *}" # Берём первый IP из второй подсети

    pairs+=("$ip1 => $ip2")

    # Удаляем IP из массивов
    subnets[${sorted_subnets[1]}]="${subnets[${sorted_subnets[1]}]#* }"
    subnets[${sorted_subnets[0]}]="${subnets[${sorted_subnets[0]}]#* }"

    # Удаляем подсеть, если её IP-адреса исчерпаны
    if [ -z "${subnets[${sorted_subnets[1]}]}" ]; then
        unset subnets[${sorted_subnets[1]}]
    fi
    if [ -z "${subnets[${sorted_subnets[0]}]}" ]; then
        unset subnets[${sorted_subnets[0]}]
    fi
done

# Вывод результата
for pair in "${pairs[@]}"; do
    echo "$pair"
done

Вывод

После выполнения любого из предложенных скриптов, вы получите пары IP-адресов, находящихся в разных подсетях /24. Эти скрипты обеспечивают эффективное группирование и парное связывание IP-адресов, что является важным для конфигурации кластеров Redis или других распределенных систем.

Если у вас есть дополнительные вопросы или требуется помощь в адаптации кода, не стесняйтесь спрашивать!

Оцените материал
Добавить комментарий

Капча загружается...