ネットワークテストをするためにはポートを分けることが一般的に行われますが、IPアドレス自体を分離したいこともあると思います。unshareを使えば比較的(?)容易に行えます。
worker
masterからNICを受け取るまで待ち、IPを設定してupするだけ。
ただしNIC名は15文字以下でないといけないらしい。
#!/usr/bin/python
from subprocess import call
from subprocess import check_call
from os.path import isdir
from time import sleep
check_call(['mount', '-t', 'sysfs', '/sys', '/sys'])
while not isdir('/sys/devices/virtual/net/wrk-container'):
sleep(1)
try:
check_call(['ip', 'link', 'set', 'lo', 'up'])
check_call(['mount', '--rbind', '/tmp/wrk-container/log', '/var/log'])
check_call(['mount', '--rbind', '/tmp/wrk-container/run', '/run'])
# todo: add other binds if required
check_call(['ip', 'addr', 'add', '172.127.0.2/24', 'dev', 'wrk-container'])
check_call(['ip', 'link', 'set', 'wrk-container', 'up'])
# todo: use proper daemon program such as https://pypi.org/project/supervisor/
check_call(['python','-c','''
import signal
import sys
import socket
from time import sleep
def on_signal(signum, frame):
print('[worker] signal %d' % signum)
sys.exit(0)
signal.signal(signal.SIGINT, on_signal)
signal.signal(signal.SIGTERM, on_signal)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 12345))
sock.listen(1)
sleep(9999)
'''])
finally:
call(['umount','-R','/run'])
call(['umount','-R','/var/log'])
call(['umount','/sys'])
master
- workerをunshareを用いて起動する
-
ip link add wrk-host type veth peer wrk-container
で仮想NICを作成する - ホスト側のIPを設定
-
ip link set wrk-container netns str(proc.pid)
でワーカー端のNICをワーカーのnet namespace内に移動する - unshareをkillするだけだとworker daemonをkillできないようなので、worker daemonをプロセスツリーをたどってkillしています。詳細不明。
#!/usr/bin/python
from os import kill
from os.path import join
from os.path import dirname
from os.path import realpath
from shutil import rmtree
from shutil import copytree
from subprocess import check_call
from signal import SIGTERM
from time import sleep
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from psutil import Process
from checkpopen import CheckPopen # https://pypi.org/project/checkpopen
def main():
with CheckPopen(['unshare','-m','-i','-n','-u','-p','--mount-proc','--kill-child=SIGTERM','--fork','--root=/',join(dirname(realpath(__file__)), 'worker.py')]) as proc:
try:
# rmtree('/tmp/worker-container/log', ignore_errors=True)
Path('/tmp/wrk-container/log').mkdir(parents=True, exist_ok=True)
rmtree('/tmp/worker-container/run', ignore_errors=True)
Path('/tmp/wrk-container/run').mkdir(parents=True, exist_ok=True)
# todo: transfer files to /tmp/wrk-container/foo
check_call(['ip','link','add','wrk-host','type','veth','peer','wrk-container'])
check_call(['ip','addr','add','172.127.0.1/24','dev','wrk-host'])
check_call(['ip','link','set','up','wrk-host'])
check_call(['ip','link','set','wrk-container','netns',str(proc.pid)])
try:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('172.127.0.2', 12345)) # check connection
finally:
kill(Process(proc.pid).children()[0].children()[0].pid, SIGTERM)
print('sent sigterm to worker daemon')
finally:
rmtree('/var/log/wrk-container', ignore_errors=True)
copytree('/tmp/wrk-container/log', '/var/log/wrk-container', symlinks=True)
try:
kill(Process(proc.pid).children()[0].pid, SIGTERM)
print('sent sigterm to worker launcher')
except IndexError:
# probably worker daemon terminated quickly
pass
proc.terminate()
print('sent sigterm to unshare')
if __name__ == '__main__':
main()