Немного теории
Для начала, вспомним некоторые базовые вещи ОС Unix.
Любой процесс в Unix имеет три открытых файла по умолчанию, (но он может их потом закрыть/переоткрыть, например перенаправляя вывод в log file):
0 (stdin)
1 (stdout)
2 (stderr)
примечание: stderr, отличается от stdout тем, что он не использует буфер для вывода.
Процессы unix могут выполняться асинхронно, как параллельно, так и последовательно, например:
$ cmd1 & cmd2;(cmd3 && cmd4) & (cmd5 || cmd6)
Разберем приведенную вязь команд по косточкам:
cmd1 & cmd2
; — вначале параллельно запустятся cmd1 и cmd2
(cmd3 && cmd4) & (cmd5 || cmd6)
— после завершения cmd1,параллельно запустятся условные цепочки cmd3,cmd4
и cmd5,cmd6
. При этом:
cmd4
запуститься если cmd3
завершиться успешно, то есть ее код возврата будет 0,
cmd6
, только в том случае, если cmd5
вернёт ненулевой код возврата.
Мне нравится термин конвейер для таких цепочек команд.
Pipe
Конвейер, помимо асинхронного выполнения, может синхронизировать выполнение процессов по вводу/выводу, используя специальный тип файла: pipe, или по русски канал.
Pipe — специальный тип файла, реализующий очередь FIFO (First Input, First Output) в Unix. Pipe может быть именованным или нет. Проще всего перенаправлять стандартные открытые файлы.
Неименованные каналы
Используя различные комбинацию конвейера и каналов, можно решить самые разные задачи администратора. Приведу примеры из собственной практики синхронизации через неименованные каналы:
## вывод размера файлов и числовая сортировка (про ключи -sS команды ls в курсе)
$ ls | awk '{printf("%25d %s\n",$5,$9)}'|sort -n
## найти пакеты postgresql
$ apt list | grep postgresql
## сформировать SQL команды для переноса файлов oracle
## из одной файловой системы в другую.
$ (echo "set line 1000 pages 0 feedback off";echo "select 'alter database rename file '''||name||''' to '''||replace(name,'/u/','/u00/')||''';' as cmd from v\$datafile where name like '/u/%';")|sqlplus -s -l / as sysdba
## пример пробрасывания канала с одного host на другой из разных сессий
## префикс приглашения показывает hostname сервера
## на котором запускается команда
host2$ nc -l -p 44665|tar xvf -
host1$ tar cvf - /home/mydata | nc host2 44665
Прокомментирую последний пример: Тут используется команда netcat (или nc), которая открывает сетевой канал для проброса канала tar/untar.
При копировании больших объёмов, это может дать в разы большую скорость, чем стандартное scp. Например, у меня при копировании 1500G backup database c одной VM на другую, скорость выросла в пять раз без сильной нагрузки на CPU.
Понятно, что при копировании между ЦОД, нужно учитывать открытость такого трафика для перехвата.
Именованные каналы
Когда команды запускаются из под одной учетной записи, и команды работают с нужной информацией через stdin/stdout, особых проблем нет. Но вот реальный пример, когда oracle imp, умеет писать dump последовательно только в файл, которые указан параметром FILE=.
У вас не хватает пространства, чтобы выгрузить нужные данные, но можно сжать полученные данные используя Named Pipe и потоковую компрессию данных.
## Для начала создадим канал как элемент файловой системы
## используя команды mkfifo или mknod
$ mknod exp.dmp p
## запускаем в фоновом режиме gzip с чтением из именованного файла
$ gzip -c < exp.dmp > exp.dmp.gz &
## запускаем oracle exp с выводом в Name Pipe
$ exp file=exp.dmp userid=/ tables="(DROPME1,DROPME2)"
Как сделать imp из полученного архива, предлагаю подумать самим в качестве задания.
Разумеется, с именованными каналами, можно использовать netcat, для организации межсерверной коммуникации.
SSH туннели
ssh — универсальный инструмент администратора. В стандартной реализации ssh, существует возможность создать специальный шифрованный канал - ssh tunnel.
Пример использования ssh tunnel для проброса VNC:2 удаленного порта 5902 доступного только для 127.0.0.1 на vm2 на локальный порт 5902 машины vm1. После входа на vm2 командой:
vm1$ ssh -L localhost:5902:localhost:5902 user@vm2
вы можете запустить на vm1 локальный vncviwer :2 и попасть на vnc:2 на машине vm2.
vm1$ vncviwer localhost:2
Схематично это можно показать так:
Здесь через локальный порт (-L) подключались к удаленному через ssh tunnel.
Для проброса удаленного порта (-R) на пользовательский комп, поступают аналогично. Например надо подключится к домашней postgresql базе с рабочего компа подключившись из дома к рабочей машине:
my1$ ssh -R localhost:5432:localhost:5432 user@vm1
vm1$ psql -h localhost -p 5432
Здесь мы свой домашний 5432 порт пробрасываем на удаленную машину.
Более сложный пример (начало):
По проекту, потребовалось RDP подключение с рабочего notebook через VPN и цепочку hosts на windows машину. При этом, напрямую подключиться к каждой из цепочки машин невозможно, только последовательно.
В целом, подключение выглядит так :
Идем стандартным путем, используем туннель + ssh jump:
## команда, которой я пользовался для подключения:
my$ ssh -i ~/.ssh/vm1.key -J gateuser@gatehost -L 127.0.0.1:3390:win1:3389 user@vm1
Где:
-i vm1.key
— файл private key пользователя user@vm1
-J (Jump)
, подключится к user@vm1 через gateuser@gatehost, само подключение к gateuser@gatehost, так как постоянно работаю через него, прописано у меня в ~/.ssh/config
-L 127.0.0.1:3390:win1:3389
— создание сквозного ssh tunnel между my (127.0.0.1:3390)
и (win1:3389)
через машину user@vm1. То есть, пробрасывается открытый на vm1 конец канала vm1:3389 — win1:3389
## осталось подключится по RDP:
my$ xfreerdp /scale:140 /u:winuser /smart-sizing:1900x1000 /size:1900x1000 /bpp:32 /network:lan /p:**** /v:127.0.0.1:3390
Пример (продолжение):
Все работало отлично, но в один из черных дней погибает vm1...
Сервера расположены не у нас, процедура перенастройки требует согласования и времени, вот только доступ на win1 понадобился вот прямо сейчас!
Рядом с vm1 расположен еще один сервер vm2, но доступа с его IP на win1 нет.
Было принято решение (с получением «добра» от начальника), поднять второй IP от первого vm1 на втором vm2 и ходить через него.
Сеть наша, лишних в ней нет, поэтому: сказано — сделано:
# добавляем второй IP
vm2$ sudo ip a add 192.168.45.56/24 dev eth0
## проверяем
vm2$ ip a
...
2: eth0: mtu 1500 qdisc mq state UP group default qlen 1000
...
inet 192.168.45.57/24 brd 192.168.56.255 scope global eth0
...
inet 192.168.45.56/24 scope global secondary eth0
...
Вот только засада, ssh не умеет указывать какой IP использовать при подключении через ssh tunnel.
То есть при попытке подключения предыдущей командой окончилось неудачей, так как соединение к win1 идет через default IP ( для этого сервера 192.168.45.57), а «волшебного ключа», для указание ssh tunnel через какой удаленный IP ему подключаться дальше — не существует в текущей версии ssh. Ключа --remote-bind-address - не существует:
## таких не бывает!
$ ssh -J ... -L localhost:3390:win1:3389 --remote-bind-address 192.168.45.56 user@vm2
Начались поиски решения, найденные варианты не подошли по разным причинам:
route add --host
...iptables -t nat -I POSTROUTING
...
Но все же решение Было найдено.
Помог netcat + named pipe. Итак вот оно, решение!:
## создаем на vm2 uniх name pipe
vm2$ mkfifo /tmp/rdp.pipe
## запускаем двухстороннее перенаправление
## localhost:3389<->win1:3389 через созданный rdp.pipe
vm2$ nc -v -n -s 192.168.23.1 192.168.45.56 3389 < /tmp/rdp.pipe |nc -lkv -n -s 127.0.0.1 -p 3389 1> /tmp/rdp.pipe
## проверяем
vm2$ sudo netstat -anp | grep 3389
tcp 0 0 127.0.0.1:3389 0.0.0.0:* LISTEN 15460/nc
tcp 0 0 192.168.45.56:42175 192.168.45.111:3389 ESTABLISHED 15459/nc
## Чуток модифицируем предыдущую команду для ssh tunnel
## так как теперь RDP у нас на localhost:3389 сервера vm2
## и только потом через nc + rdp.pipe идет подключение на win1:3389
my$ ssh -i ~/.ssh/linuxhostkey -J gateuser@gatehost -L localhost:3390:localhost:3389 user@vm2
## на my notebook подключаемся через RDP
my$ xfreerdp /scale:140 /u:winuser /smart-sizing:1900x1000 /size:1900x1000 /bpp:32 /network:lan /p:***** /v:127.0.0.1:3390
Как заключение
Я в принципе знал сам принцип, но вот использовать такой трюк для проброса RDP между локальными IP не додумался, да и на удивление, такое решение нашлось далеко не сразу.
Надеюсь, данная статья поможет многим администраторам разобраться с каналами, и их использованием в своей работе.
artebel
Хорошая статья!
А почему не подняли просто прокси, а затем в ssh через параметр -c не прокидывали соединение до таргет сервера?
grayrat Автор
Решение временное, практически на 1-2 раза, пока не поднимут упавшую машину. Разумеется, у меня есть скрипт по первому варианту выполняющий все необходимые действия. Да и нагляднее так для для публикации.
dbax
А почему нельзя было "получить от начальника" разрешение на доступ с vm2 к win1 нет, если "сеть все равно ваша" и "посторонних нет"?
grayrat Автор
тут все сложно :), если очень грубо:
Арендуются в ЦОД ресурсы, Машины создаем сами, внутри сеть изолированная, но дальше, ответственность ЦОД. win1 не наша VM, это интерфейс управления системой backup ЦОД, то что нам можно делать (сделать backup VM, восстановить VM). Сам ЦОД, таким бесплатно не занимается, типа это мы сами должны делать.
Мы отвечаем за несколько служб, у нас круглосуточная работа, остальные только рабочее время.
Собственно, то что случилось, явный косяк договора: для восстановления VM1 нужно зайти на win1 и восстановить VM1
Чтобы поменять доступ к win1, нужна бумага и согласования на уровне договора с ЦОД.
grayrat Автор
Фразу читать:
....для восстановления VM1 нужно зайти на win1 с VM1 и восстановить VM1...