Это дешевый и подверженный ошибкам -способ выполнения сопоставления слов .
Обратите внимание, что the
с пробелом после него не соответствует слову thereby
, поэтому сопоставление с пробелом после the
позволяет избежать сопоставления этой строки в начале слов. Тем не менее, по-прежнему соответствует bathe
(, если за ним следует пробел ), и не соответствует the
в конце строки.
Чтобы правильно сопоставить слово the
(или любое другое слово ), вы не должны использовать пробелы вокруг слова, так как это помешает вам сопоставить его в начале или конце строк или если оно окружено любым другим символом слова, отличным от -, например, любым символом пунктуации или символом табуляции.
Вместо этого используйте шаблон границы слова нулевой -ширины:
sed 's/\<the\>/this/'
\<
и \>
соответствуют границам до и после слова, то есть пробелу между словесным символом и не -словесным символом . Символ слова обычно представляет собой любой символ, соответствующий[[:alnum:]_]
(или [A-Za-z0-9_]
в локали POSIX ).
В GNU sed
вы также можете использовать \b
вместо \<
и \>
:
sed 's/\bthe\b/this/'
Эта работа может хорошо подходить для контейнеров systemd-nspawn
, за исключением X-сервера, если только xvfb
недостаточно, и здесь я сделал пару полных сценариев, включая базовое сетевое подключение к локальной сети.
Я сделал их по вашему каркасному сценарию, и они рассчитаны на максимальную скорость -установки.
Первый скрипт создает контейнеры на основе Ubuntu 20.04, предоставляя те же инструменты, что и в вашей docker
попытке, так как кажется, что вы довольны теми, которые подходят для вашего варианта использования. На одном -ЦП Xeon Silver 4114 2,20 ГГц (10 ядер + HT )с 32 ГБ ОЗУ этот скрипт выполняет полный цикл от установки до уничтожения 100 контейнеров за ~35 секунд, занимая ОЗУ ~600 МБ.
Второй сценарий создает контейнеры, которые больше напоминают настоящую виртуальную машину, с более полным дистрибутивом Ubuntu 20.04, включающим собственный systemd
и типичные сервисные демоны, такие как cron
, rsyslog
и т. д. Это выполняется менее чем за 3 минуты, с занятие около 3,3гб на 100 "машин".
В обоих случаях большая часть времени уходит на этап настройки, загрузку/загрузку шаблона образа и т. д.
Первый скрипт, похожий на докер -:
#!/bin/bash --
# vim: ts=4 noet
install() {
[ -e /etc/radvd.conf ] || cat > /etc/radvd.conf <<EOF
interface bogus {
IgnoreIfMissing on;
};
EOF
apt -y install systemd-container debootstrap wget radvd
}
setup() {
mkdir -p "$machines"
# Fetch Ubuntu 20.04 basic system
#debootstrap focal "$machines/$tmpl" # <-- either this, or the below wget + tar + mount
wget -P "$machines" https://partner-images.canonical.com/core/focal/current/ubuntu-focal-core-cloudimg-amd64-root.tar.gz
mkdir -p "$machines/$tmpl"
tar -C "$machines/$tmpl" -xzf "$machines/ubuntu-focal-core-cloudimg-amd64-root.tar.gz"
mount --bind /etc/resolv.conf "$machines/$tmpl/etc/resolv.conf"
# Put our ssh pubkeys
mkdir -p "$machines/$tmpl/root/.ssh"
(shopt -s failglob; : ~/.ssh/*.pub) 2>/dev/null \
&& cat ~/.ssh/*.pub > "$machines/$tmpl/root/.ssh/authorized_keys"
# Let nspawn use our parameterized hostname
rm -f "$machines/$tmpl/etc/hostname"
# Allow apt to function in chroot without complaints
mount -o bind,slave,unbindable /dev "$machines/$tmpl/dev"
mount -o bind,slave,unbindable /dev/pts "$machines/$tmpl/dev/pts"
export DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8
chroot "$machines/$tmpl" sh -c 'apt-get update && apt-get install -y --no-install-recommends apt-utils'
# No init-scripts are to be run while in chroot
cat >> "$machines/$tmpl/usr/sbin/policy-rc.d" <<'EOF'
#!/bin/sh --
exit 101
EOF
chmod +x "$machines/$tmpl/usr/sbin/policy-rc.d"
# Install additional packages for the use case
chroot "$machines/$tmpl" apt-get install -y --no-install-recommends \
bash-completion iproute2 vim iputils-ping \
openssh-server
# Uncomment these to allow root in, with password "let-me-in"
# echo 'PermitRootLogin yes' > "$machines/$tmpl/etc/ssh/sshd_config.d/allow-root-with-password.conf" \
# && chroot "$machines/$tmpl" chpasswd <<<'root:let-me-in'
umount -l "$machines/$tmpl/dev/pts" "$machines/$tmpl/dev" "$machines/$tmpl/etc/resolv.conf"
}
start() {
# Connect to physical LAN by building a temporary bridge over the specified physical interface
# Of course this is not required if the interface facing the LAN is already a bridge interface, in which case you can just use that as "$mybr" and skip this pipeline
# TODO: check on possible "$mybr" existence, and/or being already a bridge, and/or enslaving of "$intf" already in place
# NOTE: be careful how the interface in "$intf" is named, as here it is used in sed's regex
ip -o -b - <<EOF | awk '{print "route list " $4}' | ip -b - | sed "s/^/route replace /;s/ $intf / $mybr /g" | ip -b -
link add $mybr type bridge
link set $mybr up
link set $intf master $mybr
addr show $intf up
EOF
# Advertise a temporary private IPv6 network in LAN
ipv6pfx='fddf:' # this arbitrary pfx is not properly compliant, but very handy for quick use in simple LANs
cat >> /etc/radvd.conf <<EOF
### $tmpl
interface $mybr {
AdvSendAdvert on;
prefix $ipv6pfx:/64 {
AdvValidLifetime 7200;
AdvPreferredLifetime 3600;
};
};
###
EOF
systemctl start radvd
for i in $(seq "$vmnum"); do
# Spawn containers that don't persist on disk
systemd-run --unit="$tmpl-mini-$i" --service-type=notify \
systemd-nspawn --notify-ready=no --register=no --keep-unit --kill-signal=RTMIN+3 \
-M "${tmpl:0:8}$i" \
-D "$machines/$tmpl" --read-only --link-journal no \
--overlay +/etc::/etc --overlay +/var::/var \
--network-bridge="$mybr" \
--as-pid2 sh -c 'ip link set host0 up && ip addr add '"$ipv6pfx:$i/64"' dev host0 && mkdir -p /run/sshd && exec /usr/sbin/sshd -D' \
& # Run in bg and wait later; this way we allow systemd's parallel spawning
# Below is a --as-pid2 alternative for using dhcp, but beware bombing on LAN's dhcp server
#--as-pid2 sh -c 'udhcpc -fbi host0; mkdir -p /run/sshd && exec /usr/sbin/sshd -D' \
done
wait
}
stop() {
systemctl stop "$tmpl-mini-*"
systemctl stop radvd
ip link del "$mybr" 2>/dev/null
netplan apply
sed -i "/^### $tmpl/,/^###$/d" /etc/radvd.conf
}
destroy() {
rm -rf "$machines/$tmpl"
rm -f "$machines/ubuntu-focal-core-cloudimg-amd64-root.tar.gz"
}
: "${machines:=/var/lib/machines}" # default location for systemd-nspawn containers
: "${vmnum:=100}" # how many containers to spawn
: "${intf:?specify the physical interface facing the LAN to connect to}"
: "${tmpl:?specify directory basename under $machines to store the containers\' OS template into}"
: "${mybr:=$tmpl-br}" # the temporary bridge to LAN will be named this
install
setup
start
stop
destroy
Когда у вас появятся контейнеры, похожие на docker -, вы можете управлять ими через systemctl
. Все они создаются как службы systemd с именем <template-name>-mini-<number>
.
Вы можете ввести оболочку в любой из них либо через ssh
, либо черезnsenter -at <pid-of-any-process-belonging-to-a-specific-container>
Второй скрипт, опыт "vm -лайк":
#!/bin/bash --
# vim: ts=4 noet
install() {
[ -e /etc/radvd.conf ] || cat > /etc/radvd.conf <<EOF || return
interface bogus {
IgnoreIfMissing on;
};
EOF
apt -y install systemd-container debootstrap radvd || return
}
setup() {
mkdir -p "$machines/$tmpl" || return
# Fetch Ubuntu 20.04 base system
debootstrap focal "$machines/$tmpl" || return
# Allow apt to function in chroot without complaints
trap "umount -l $machines/$tmpl/dev/pts" RETURN
mount -o bind,slave,unbindable /dev/pts "$machines/$tmpl/dev/pts" || return
# Put our ssh pubkeys
mkdir -p "$machines/$tmpl/root/.ssh" || return
(shopt -s failglob; : ~/.ssh/*.pub) 2>/dev/null \
&& { cat ~/.ssh/*.pub > "$machines/$tmpl/root/.ssh/authorized_keys" || return; }
# Let nspawn use our parameterized hostname
rm -f "$machines/$tmpl/etc/hostname" || return
# Enable container's systemd-networkd, it blends automatically with host's systemd-networkd
chroot "$machines/$tmpl" systemctl enable systemd-networkd || return
# Make provision for static addresses passed along at start time (see start phase below)
cat > "$machines/$tmpl/etc/networkd-dispatcher/carrier.d/$tmpl-static-addrs.sh" <<'EOF' || return
#!/bin/bash --
[ -n "$static_ipaddrs" ] && printf 'addr add %s dev host0\n' ${static_ipaddrs//,/ } | ip -b -
EOF
chmod +x "$machines/$tmpl/etc/networkd-dispatcher/carrier.d/$tmpl-static-addrs.sh" || return
# Uncomment this to mind about updates and security
# printf 'deb http://%s.ubuntu.com/ubuntu/ focal-%s main\n' \
# archive updates security security \
# >> "$machines/$tmpl/etc/apt/sources.list" || return
# Uncomment this to consider [uni|multi]verse packages
# sed -i 's/$/ universe multiverse' "$machines/$tmpl/etc/apt/sources.list" || return
export DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8
chroot "$machines/$tmpl" apt-get update || return
# To upgrade or not to upgrade? that is the question..
#chroot "$machines/$tmpl" apt-get -y upgrade || return
# Install additional packages for the use case
chroot "$machines/$tmpl" apt-get install -y --no-install-recommends \
bash-completion \
openssh-server \
|| return
# Uncomment these to allow root in, with password "let-me-in"
# echo 'PermitRootLogin yes' > "$machines/$tmpl/etc/ssh/sshd_config.d/allow-root-with-password.conf" || return
# chroot "$machines/$tmpl" chpasswd <<<'root:let-me-in' || return
}
start() {
# For full-system modes we need inotify limits greater than default even for just a bunch of containers
(( (prev_max_inst = $(sysctl -n fs.inotify.max_user_instances)) < 10*vmnum )) \
&& { sysctl fs.inotify.max_user_instances=$((10*vmnum)) || return 1; }
(( (prev_max_wd = $(sysctl -n fs.inotify.max_user_watches)) < 40*vmnum )) \
&& { sysctl fs.inotify.max_user_watches=$((40*vmnum)) || return 1; }
[ -s "$machines/prev_inotifys" ] || declare -p ${!prev_max_*} > "$machines/prev_inotifys"
# Connect to physical LAN by building a temporary bridge over the specified physical interface
# Of course this is not required if the interface facing the LAN is already a bridge interface, in which case you can just use that as "$mybr" and skip this pipeline
# TODO: check on possible "$mybr" existence, and/or being already a bridge, and/or enslaving of "$intf" already in place
# NOTE: be careful how the interface in "$intf" is named, as here it is used in sed's regex
ip -o -b - <<EOF | awk '{print "route list " $4}' | ip -b - | sed "s/^/route replace /;s/ $intf / $mybr /g" | ip -b -
link add $mybr type bridge
link set $mybr up
link set $intf master $mybr
addr show $intf up
EOF
# Advertise a temporary private IPv6 network in LAN
ipv6pfx='fddf:' # this arbitrary pfx is not properly compliant, but very handy for quick use in simple LANs
cat >> /etc/radvd.conf <<EOF || return
### $tmpl
interface $mybr {
AdvSendAdvert on;
prefix $ipv6pfx:/64 {
AdvValidLifetime 7200;
AdvPreferredLifetime 3600;
};
};
###
EOF
systemctl start radvd
for i in $(seq "$vmnum"); do
# Spawn containers that don't persist on disk
systemd-run --unit="$tmpl-full-$i" --service-type=notify \
systemd-nspawn --notify-ready=yes -b \
-M "${tmpl:0:8}$i" \
-D "$machines/$tmpl" --read-only --link-journal no \
--overlay +/etc::/etc --overlay +/var::/var \
--network-bridge="$mybr" \
--capability=all --drop-capability=CAP_SYS_MODULE \
"systemd.setenv=static_ipaddrs=$ipv6pfx:$i/64" \
& # Run in bg and wait later; this way we allow systemd's parallel spawning
# All capabilities allowed and no users isolation provide an experience which is
# closer to a true vm (though with less security)
# The comma separated list of static addresses will be set by our script in networkd-dispatcher
done
wait
}
stop() {
systemctl stop "machine-$tmpl*" "$tmpl-full-*"
systemctl stop radvd
ip link del "$mybr" 2>/dev/null
netplan apply
sed -i "/^### $tmpl/,/^###$/d" /etc/radvd.conf
# restore previous inotify limits
source "$machines/prev_inotifys" || return
rm -f "$machines/prev_inotifys"
(( prev_max_wd > 0 )) && sysctl fs.inotify.max_user_watches="$prev_max_wd"
(( prev_max_inst > 0 )) && sysctl fs.inotify.max_user_instances="$prev_max_inst"
}
destroy() {
rm -rf "$machines/$tmpl"
}
: "${machines:=/var/lib/machines}" # default location for systemd-nspawn machines
: "${vmnum:=100}" # how many containers to spawn
: "${intf:?specify the physical interface facing the LAN to connect to}"
: "${tmpl:?specify directory basename under $machines to store the containers\' OS template into}"
: "${mybr:=$tmpl-br}" # the temporary bridge will be named this
install || exit
setup || { destroy; exit 1; }
start || { stop; exit 1; }
stop
destroy
После создания контейнеров типа «vm -like» на хосте вы также можете использовать machinectl
и systemctl
для их обработки. Примеры:
machinectl shell <container-name>
предоставляет удобный способ поместить оболочку в конкретный контейнер machinectl
отдельно или также systemctl list-machines
, предоставить список запущенных контейнеров machinectl poweroff <container-name>
,или также systemctl stop machine-<container-name>
останавливает контейнер (вы также можете сделать poweroff
из оболочки внутри контейнера)Для обоих сценариев я выбрал подключение по протоколу IPv6, так как он имеет встроенные функции для автоматической настройки хостов. Если все ваши хосты в локальной сети являются дружественными гражданами IPv6, они самостоятельно -настроят временный адрес сети fddf ::/64, инициированный в -с помощью моих сценариев и объявленный всей локальной сети (и, конечно же, общий для всех контейнеров ).
Такой префикс IPv6 «fddf ::/64» является полностью произвольным и попадает в официальный префикс, выделенный IANA для частных сетей. Я выбрал его очень удобно, так что с любого хоста в вашей локальной сети вы можете просто сделать ssh root@fddf::<vm-number>
.
Однако это не совсем соответствует тому, как должны генерироваться эти префиксы, и если вы хотите сгенерировать соответствующий частный префикс, прочтите RFC 4193 , в частности , раздел 3.2.2 .
В любом случае указанное «число vm -» — это число от 1 до любого числа приглашенных вами гостей, и я оставил их десятичными в шестнадцатеричном формате IPv6, чтобы у вас было место для 9999 адресов.
Конечно, вы также можете использовать IPv4-адреса, и здесь у вас есть два варианта: