diff --git a/basic/srv/vm/cli.py b/basic/srv/vm/cli.py index f63b08b..6b650d9 100755 --- a/basic/srv/vm/cli.py +++ b/basic/srv/vm/cli.py @@ -44,6 +44,14 @@ parser_disable_autostart.add_argument('app', help='Application name') parser_rebuild_issue = subparsers.add_parser('rebuild-issue', help='Rebuilds /etc/issue using current settings - used on VM startup') parser_rebuild_issue.set_defaults(action='rebuild-issue') +parser_register_container = subparsers.add_parser('register-container', help='Register and assigns IP to an application container. Intended to be used with LXC hooks') +parser_register_container.add_argument('lxc', nargs=argparse.REMAINDER) +parser_register_container.set_defaults(action='register-container') + +parser_unregister_container = subparsers.add_parser('unregister-container', help='Removes IP assignment for an application container. Intended to be used with LXC hooks') +parser_unregister_container.add_argument('lxc', nargs=argparse.REMAINDER) +parser_unregister_container.set_defaults(action='unregister-container') + parser_register_proxy = subparsers.add_parser('register-proxy', help='Rebuilds nginx proxy target for an application container') parser_register_proxy.set_defaults(action='register-proxy') parser_register_proxy.add_argument('app', help='Application name') @@ -94,6 +102,10 @@ elif args.action == 'disable-autostart': mgr.disable_autostart(args.app) elif args.action == 'rebuild-issue': mgr.rebuild_issue() +elif args.action == 'register-container': + mgr.register_container() +elif args.action == 'unregister-container': + mgr.unregister_container() elif args.action == 'register-proxy': mgr.register_proxy(args.app) elif args.action == 'unregister-proxy': diff --git a/basic/srv/vm/mgr/__init__.py b/basic/srv/vm/mgr/__init__.py index c51274b..935d33b 100644 --- a/basic/srv/vm/mgr/__init__.py +++ b/basic/srv/vm/mgr/__init__.py @@ -208,6 +208,19 @@ class VMMgr: raise validator.InvalidValueException('app', app) subprocess.run(['/sbin/rc-update', 'del', app]) + def register_container(self): + # Set IP of a container based on values given via lxc.hook.start-host hook + app = os.environ['LXC_NAME'] + pid = os.environ['LXC_PID'] + ip = tools.get_unused_ip() + tools.update_hosts_lease(ip, app) + tools.set_container_ip(pid, ip) + + def unregister_container(self): + # Unset IP of a container based on values given via lxc.hook.post-stop hook + app = os.environ['LXC_NAME'] + tools.update_hosts_lease(None, app) + def register_proxy(self, app): # Rebuild nginx configuration using IP of referenced app container and reload nginx if not validator.is_valid_app(app, self.conf): diff --git a/basic/srv/vm/mgr/tools.py b/basic/srv/vm/mgr/tools.py index 018c10e..86006c0 100644 --- a/basic/srv/vm/mgr/tools.py +++ b/basic/srv/vm/mgr/tools.py @@ -16,13 +16,6 @@ def compile_url(domain, port, proto='https'): host = '{}{}'.format(domain, port) return '{}://{}'.format(proto, host) if proto is not None else host -def get_container_ip(app): - # Return an IP address of a container. If the container is not running, return address from IPv6 discard prefix instead - try: - return subprocess.run(['/usr/bin/docker', 'inspect', '-f', '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}', app], check=True, stdout=subprocess.PIPE).stdout.decode().strip() - except: - return NULL_IP - def get_local_ipv4(): # Return first routable IPv4 address try: @@ -122,3 +115,41 @@ def shutdown_vm(): def reboot_vm(): subprocess.run(['/sbin/reboot']) + +def get_unused_ip(): + # This is a poor man's DHCP server which uses /etc/hosts as lease database + # Leases the first unused IP from range 172.17.0.0/16 + leased = [] + with open('/etc/hosts', 'r') as fd: + for line in fd.read().splitlines(): + if line.startswith('172.17'): + ip = line.split()[0].split('.') + leased.append(int(ip[2]) * 256 + int(ip[3])) + for i in range(1, 65534): + if i not in leased: + break + return '172.17.{}.{}'. format(i // 256, i % 256) + +def update_hosts_lease(ip, app): + hosts = [] + with open('/etc/hosts', 'r') as fd: + for line in fd: + if not line.strip().endswith(' {}'.format(app)): + hosts.append(line) + if ip: + hosts.append('{} {}\n'.format(ip, app)) + with open('/etc/hosts', 'w') as fd: + fd.writelines(hosts) + +def get_container_ip(app): + # Return an IP of a container. If the container doesn't exist, return address from IPv6 discard prefix instead + with open('/etc/hosts', 'r') as fd: + for line in fd: + if line.strip().endswith(' {}'.format(app)): + return line.split()[0] + return NULL_IP + +def set_container_ip(pid, ip): + # Set IP in container based on PID given via lxc.hook.start-host hook + cmd = 'ip addr add {}/16 broadcast 172.17.255.255 dev eth0 && ip route add default via 172.17.0.1'.format(ip) + subprocess.run(['nsenter', '-a', '-t', pid, '--', '/bin/sh', '-c', cmd]) diff --git a/lxc-build b/lxc-build index d8cf821..a6236ad 100755 --- a/lxc-build +++ b/lxc-build @@ -13,8 +13,6 @@ lxc.uts.name = {name} lxc.net.0.type = veth lxc.net.0.link = lxcbr0 lxc.net.0.flags = up -lxc.net.0.ipv4.address = 172.17.0.254/16 -lxc.net.0.ipv4.gateway = auto # Volumes lxc.rootfs.path = {rootfs} @@ -43,6 +41,8 @@ lxc.console.logfile = /var/log/lxc/{name}.log # Other lxc.arch = x86_64 lxc.cap.drop = sys_admin +lxc.hook.start-host = /usr/bin/vmmgr register-container +lxc.hook.post-stop = /usr/bin/vmmgr unregister-container lxc.include = /usr/share/lxc/config/common.conf '''