Hi. At playing libvirt-lxc on Fedora18, I found that the user needs some workarounds to run /sbin/init as the root process of container. With Fedora15, I found this https://gist.github.com/peo3/1142202. And I know virt-sandbox-service have some clever way to understand RPM to make domain running with a service. Here is a script I wrote for F18 inspired by above two. Creating LXC domain running systemd as /init program. I'd like to get some feedback....and ...can't this kind of scripts be maintained by some virt-tools project ? Anyway, playing with lxc is fun. I'm glad if someone makes this script better :) == A user can create a lxc container by # ./lxc_systemd_setup.py -r -n Test This will generate my.xml and lxc file tree on /opt/lxc/Test # virsh -c lxc:/// define my.xml # virsh -c lxc:/// start my.xml This domain has following characteristics - /usr is bind mounted to host's /usr - all files other than files in /usr are copied - systemd and pam modules are tweaked a bit - eth0 is up by dhcp. - systemd, rsyslog, sshd is running. Users can add rpm package as # ./lxc_systemd_setup.py -r -n Test -a httpd some options may not work because of my bad skills. ==lxc_systemd_setup.py== #!/usr/bin/python # # A scripts for creating LXC domain where some daemons are running under systemd. # Tested with Fedora18, running systemd, rsyslog, sshd in a container at default. # # Most config files, including all security settings as passwd, selinux, # ssl/pki files are copied from the host. So, please fix them before # running a domain. # # New root dir will be /opt/lxc/<domain name> at default and domain XML # definition will be saved as my.xml # # All instllation under /usr is shared among containres/host, binaries are # not copied at all. /etc and /var are copied. So, you can adjust config files # under container as you like. # # for easy creation, run # # lxc_systemd_setup.py -n Test -r # # virsh -c lxc:/// define my.xml # # virsh -c lxc:/// start Test --console # # This will build a lxc root filesystem under /opt/lxc/Test and copy # required files under it. /usr will be shared with the host. # # to add some packages, pass package name with '-a' option. But this script doesn't # handle dependency of RPM, at all. please take care of it. # # for running httpd. # # lxc_systemd_setup.py -n Test -r -a httpd # _and_ you need to fix hostname lookup problem to run httpd. i.e. you may need to fix # some files under /etc.... # # please see # # lxc_systemd_setup.py --help # for other options. some may not work ;) # import sys, os, shutil from subprocess import Popen, PIPE from optparse import OptionParser,OptionGroup import re import rpm parser = OptionParser() parser.add_option('-p', '--prefix', action='store', default='/opt/lxc', type='string', dest='pathname', help='prefix of guest root path') parser.add_option('-n', '--name', action='store', default='_unknown', type='string', dest='domain_name', help='name of domain') parser.add_option('-o', '--out', action='store', default='my.xml', type='string', dest='def_file', help='name of generated xml def') parser.add_option('-r', '--renew', action='store_true', default=False, dest='renew_tree', help='delete existing tree if exists') parser.add_option('-D', '--destroy', action='store_true', default=False, dest='destroy_tree', help='destroy existing tree and quit') parser.add_option('-f', '--force', action='store_true', default=False, dest='force', help='update all files without checking timestamp') parser.add_option('-a','--add-packages', action='append', type='string', dest='package_list', help='copy package config to container') parser.add_option('-s','--skip-packages', action='append', type='string', dest='skip_list', help='skip packages') parser.add_option('-m','--memory', action='store', default='1024000', type='str', dest='memory', help='memory size of domain') parser.add_option('-H','--hostname', action='store', default='lxc', type='str', dest='hostname', help='hostname of domain') (options, args) = parser.parse_args(sys.argv) # # Utility functions. # # # Remove all files under domain ROOT. # def destroy_all(path) : if (not os.path.exists(path)) : return shutil.rmtree(path, ignore_errors=True) # # Check a file in a container is newer than hosts. # def file_is_newer(a, b) : time_a = os.stat(a).st_mtime time_b = os.stat(b).st_mtime return a > b # # Check Host's distro. # def check_version() : useRPM=True version="unknown" if (os.path.exists('/etc/redhat-release')) : with open('/etc/redhat-release') as f: version_string = f.readline() if (re.match("Fedora release 18.+$", version_string)) : version = "Fedora18" else : useRPM=False return (version, useRPM) # # directories created at domain creation (tested with Fedora 18) # ROOTDIR= options.pathname + "/" + options.domain_name class InstallInfo : def __init__(self) : self.DIRS = [] self.BINDDIRS = [] self.SYMLINKS=[] self.PACKAGES = [] self.FILES = [] self.MERGED = [] def add_dirs(self, x) : if (isinstance(x, str)) : x = [x] self.DIRS = self.DIRS + x def dirs(self) : return self.DIRS def add_files(self, x) : if (isinstance(x, str)) : x = [x] self.FILES += x def files(self) : return self.FILES def add_binds(self, x) : if (isinstance(x, str)) : x = [x] self.BINDDIRS = self.BINDDIRS + x def binds(self) : return self.BINDDIRS def add_links(self, x) : self.SYMLINKS = self.SYMLINKS + x def links(self) : return self.SYMLINKS def add_packages(self, x) : self.PACKAGES = self.PACKAGES + x def packages(self) : return self.PACKAGES def merge(self) : self.DIRS = list(set(self.DIRS)) self.FILES = list(set(self.FILES)) self.BINDDIRS = sorted(list(set(self.BINDDIRS))) ret = True ents =[] paths = [] for ent in self.DIRS : ents.append((ent, 'dir', '')) paths.append(ent) for ent in self.FILES : ents.append((ent, 'file', '')) paths.append(ent) for ent in self.SYMLINKS : ents.append((ent[0], 'link', ent[1])) paths.append(ent[0]) if (len(paths) - len(list(set(paths)))) : ret = False self.MERGED = sorted(ents) return ret def merged(self) : return self.MERGED # # Gather RPM information and copy config files to proper place. # class CopyRPMHandler: def __init__(self, name) : self.name = name self.files = [] self.service = "" def verify(self) : ts = rpm.TransactionSet() mi = ts.dbMatch('name', self.name) if not mi : return False # get list of files. for h in mi : myhead = h break fi = myhead.fiFromHeader() for x in fi : self.files.append(x[0]); self.test_service() self.strip_binds() return True def paths(self) : return self.files # # check all files in RPM which are not under bind-mount. # def strip_binds(self) : # remove all ents under /usr for avoiding copy. temp = self.files self.files = [] for file in temp : if (file == '') : continue if (re.match("/usr/.+$", file)) : continue if (re.match("/bin/.+$", file)) : continue if (re.match("/lib/.+$", file)) : continue if (re.match("/sbin/.+$", file)) : continue if (re.match("/lib64/.+$", file)) : continue self.files.append(file) self.files.sort() return def test_service(self) : for file in self.files : if (re.match("/usr/lib/systemd/system.+\.service$", file)) : ent.service = file # # Functions for workarounds. # # # systemd: create our own basic.target for avoiding some startups. # def systemd_tune() : # # we need to avoid some special services by systemd. # modify basic.target and avoid them. # filename = ROOTDIR + "/etc/systemd/system/basic.target" data="""[Unit] Description=Basic System Documentation=man:systemd.special(7) Requires=systemd-tmpfiles-setup.service sockets.target After=systemd-tmpfiles-setup.service sockets.target RefuseManualStart=yes """ with open(filename,"w") as f: f.write(data) # # we need getty only with tty1 # os.symlink("/usr/lib/systemd/system/getty@.service", ROOTDIR + "/etc/systemd/system/getty.target.wants/getty@tty1.service") # # Create ifcfg-eth0 and add service to bring up it. # def eth0_service() : # # /etc/sysconfig/network is generated by annaconda and we cannot # find it by rpms. # filename = ROOTDIR + "/etc/sysconfig/network" shutil.copy("/etc/sysconfig/network", filename); # # ifconfig setting for eth0 # filename = ROOTDIR + "/etc/sysconfig/network-scripts/ifcfg-eth0" data="""DEVICE=eth0 BOOTPROTO=dhcp ONBOOT=yes NAME=eth0 TYPE=Ethernet """ with open(filename, "w") as f: f.write(data) print "Creating %s" % filename filename = ROOTDIR + "/etc/systemd/system/lxc-eth0.service" data="""[Unit] Before=multi-user.target Conflicts=shutdown.target Description=bring up eth0 in this container [Service] ExecStart=/usr/sbin/ifup eth0 Type=simple """ with open(filename, "w") as f: f.write(data) print "Creating %s" % filename # # Bring up this. # filename = ROOTDIR + "/etc/systemd/system/basic.target.wants/lxc-eth0.service" src = "/etc/systemd/system/lxc-eth0.service" os.symlink(src, filename) # # Make fstab empty # def empty_fstab() : filename = ROOTDIR + "/etc/fstab" with open(filename, "w") as f: f.truncate(0) # # in Fedora18, pam's pam_loginuid.so doesn't work under container # we need to disable it. # def pam_tune() : pamdir = ROOTDIR + "/etc/pam.d" for root, dirs, files in os.walk(pamdir) : for path in files : path = root + "/" + path if (os.path.islink(path)) : continue data ="" with open(path) as f: for line in f : if (re.match("^.+pam_loginuid.so.*$", line)) : line = "#" + line data += line with open(path, "w") as f: f.write(data) # # securetty set up for login via system console. # def securetty_tune() : path = ROOTDIR + "/etc/securetty" with open(path, "a") as f: f.write("pts/0\n") # # set hostname of guest domain. # def hostname_modify() : path = ROOTDIR + "/etc/hostname" with open(path, "w") as f: f.write(options.hostname + "\n") # # parse memory size. # def parse_memory(data) : if data[-1] == 'K' : x = int(data[0:-1]) return str(x * 1024) elif data[-1] == 'M' : x = int(data[0:-1]) return str(x * 1024 * 1024) elif data[-1] == 'G' : x = int(data[0:-1]) return str(x * 1024 * 1024 * 1024) else : return data # # Main routine starts here ! # version, useRPM = check_version() if (not useRPM) : print 'now, we can handle RPM only' exit(1) info = InstallInfo() # Build a information. # # At first, gather required RPM information and some tweaks for distro. # if (version == 'Fedora18') : # # now, dont'handle yum and rpm info in container, so create fake dirs # instead of copying yum info by RPM. # info.add_dirs(["/etc/yum", "/etc/yum/protected.d", "/etc/yum/pluginconf.d","/etc/yum/vars"]) # # We share /usr between host and guest. # info.add_binds(["/usr"]) # # For Fedora18, we need following copies of configs packages at least. # info.add_packages(["filesystem","setup","rpm", "selinux-policy"]) info.add_packages(["systemd", "dbus", "initscripts","util-linux"]) info.add_packages(["pam","passwd", "crontabs","kmod","logrotate","rsyslog"]) info.add_packages(["openssh","openssh-server", "chkconfig","authconfig"]) info.add_packages(["glibc", "mailcap"]) # symlink and dirs for systemd info.add_links([["/etc/systemd/system/default.target", "/lib/systemd/system/multi-user.target"]]) info.add_dirs(["/etc/systemd/system/basic.target.wants"]) info.add_dirs(["/etc/systemd/system/default.target.wants"]) info.add_dirs(["/etc/systemd/system/getty.target.wants"]) info.add_dirs(["/etc/systemd/system/multi-user.target.wants"]) info.add_dirs(["/etc/systemd/system/sockets.target.wants"]) info.add_dirs(["/etc/systemd/system/sysinit.target.wants"]) info.add_dirs(["/etc/systemd/system/system-update.target.wants"]) # # Merge package list # package_names = info.packages() if (options.package_list) : package_names += options.package_list # Uniq. package_names = list(set(package_names)) # # delete unnecessary packages from list. # if (options.skip_list) : for name in options.skip_list : if (name in package_names) : package_names.remove(name) # # Verify package list (check installation of packages) # packages = [] error = False if (useRPM) : for name in package_names : ent = CopyRPMHandler(name) if (ent.verify()) : packages.append(ent) else : print "Couldn't find a package [%s] in RPM DB." % (name) error = True if (error) : exit(1) # # Now, we confirmed all RPMS required are installed in the host. # service_files = [] # # Extract dir,symlink,file information from RPMS. Later, we'll copy all # files other than /usr. # for ent in packages : for path in ent.paths() : if (not os.path.exists(path)) : continue if (os.path.islink(path)) : src = os.readlink(path) info.add_links([[path, src]]) elif (os.path.isfile(path)) : info.add_files(path) elif (os.path.isdir(path)) : info.add_dirs(path) if (ent.service != ""): service_files.append(ent.service) # # Uniq and sort it. # if (not info.merge()) : print "some confilction of files may happen..." # # Check Domain name is passed. # if (options.domain_name == '_unknown') : print "Guest Domain name must be specified" exit(1) # # Destroy tree. # if (options.destroy_tree) : destroy_all(ROOTDIR) exit(0) # # At first, clear tree if required. # (*) the scirpt may not work if we don't destroy the tree .... # if (os.path.exists(ROOTDIR)) : if (options.renew_tree) : destroy_all(ROOTDIR) # Create root dir try: os.mkdir(ROOTDIR) except: print "cannot create root dir %s" % ROOTDIR exit(1) # Ok, make world based on information gathered from RPMS. for ents in info.merged() : guestpath = ROOTDIR + ents[0] try: if (ents[1] == 'dir') : if (not os.path.exists(guestpath)) : print "Creating dir %s" % (guestpath) os.makedirs(guestpath) elif (ents[1] == 'link') : if (not os.path.exists(guestpath)) : print "Creating symlink %s => %s" % (guestpath, ents[2]) os.symlink(ents[2], guestpath) elif (ents[1] == 'file') : if (options.force or not os.path.exists(guestpath) or file_is_newer(ents[0], guestpath)) : print "Copyfile %s" % (guestpath) shutil.copy(ents[0], guestpath) except: print "error at creating tree %s" % guestpath exit(1) # # setup service files if necessary. # for file in service_files : service = os.path.basename(file) p = re.compile('WantedBy=(.+)$') for line in open(file, 'r') : m = p.match(line) if (m) : target = m.group(1) pathname = ROOTDIR + "/etc/systemd/system/" + target + ".wants/" + service print "%s=>%s" % (pathname, file) os.symlink(file, pathname) # # Tweak system settings. # if (version == "Fedora18") : # diable some services. dir = ROOTDIR + "/etc/systemd/system/" os.symlink("/dev/null", dir + "sysinit.target") os.symlink("/dev/null", dir + "console-shell.service") os.symlink("/dev/null", dir + "fedora-readonly.service") os.symlink("/dev/null", dir + "fedora-storage-init.service") systemd_tune() # modify basic.target etc... eth0_service() # bringup eth0 without udev empty_fstab() # make /etc/fstab empty pam_tune() # disable some pam module securetty_tune() # add pts/0 to securetty hostname_modify() # # Generate a Domain Def. # domain = r""" <domain type='lxc'> <name>%(NAME)s</name> <memory unit='bytes'>%(MEMORY)s</memory> <vcpu>1</vcpu> <os> <type arch='x86_64'>exe</type> <init>/sbin/init</init> </os> <clock offset='utc'/> <on_poweroff>destroy</on_poweroff> <on_reboot>restart</on_reboot> <on_crash>destroy</on_crash> <devices> <emulator>/usr/libexec/libvirt_lxc</emulator> <filesystem type='mount' accessmode='passthrough'> <source dir='%(ROOTDIR)s'/> <target dir='/'/> </filesystem> <filesystem type='mount' accessmode='passthrough'> <source dir='/usr'/> <target dir='/usr'/> </filesystem> <filesystem type='ram'> <source usage='%(MEMORY)s'/> <target dir='/tmp'/> </filesystem> <filesystem type='ram'> <source usage='%(MEMORY)s'/> <target dir='/dev/shm'/> </filesystem> <interface type="network"> <source network="default"/> </interface> <console type='pty'> <target type='lxc' port='0'/> </console> </devices> </domain> """ % {'NAME':options.domain_name, 'MEMORY': parse_memory(options.memory), 'ROOTDIR':ROOTDIR} with open(options.def_file, "w") as f : f.write(domain) -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list