App-LXC-Container

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

These disadvantages are compensated by several advantages:

- +

    All applications are automatically updated together with the Linux
    distribution of the machine.

- +

    The applications do not need additional disk space (except for the
    configuration files as well as some directories, bind-mounts and symbolic
    links - we're writing about 250-2500 additional inodes and 500-2500 kB of
    disk space).

- +

    The applications do not use additional main memory when compared to
    running outside of the LXC container (except for the overhead of a few
    scripts and LXC itself).

App::LXC::Container is a toolbox basically providing three commands:

README.md  view on Meta::CPAN


    Starting an application inside of a virtual machine is slower than starting
    an application container.

_Additional advantages/disadvantages are welcome._

# BEST PRACTICES

Especially external packages often haven't all their real dependencies
configured.  For those it is often necessary to manually add some packages
and bind mount points like the following:

## additional packages

Note that the examples are from Debian.

- fontconfig-config (select `/usr/share/fontconfig`)
- locales (select `/usr/share/locale/locale.alias`)

## additional bind mounts

Note that again the examples are from Debian.

- `/usr/share/fonts`

# KNOWN BUGS

Currently the package only supports Debian based distributions.  If you're
using something different please get in touch to extend the support.  (The
framework is already there, but the specific commands are missing, and

lib/App/LXC/Container.pm  view on Meta::CPAN

=over

=item +

All applications are automatically updated together with the Linux
distribution of the machine.

=item +

The applications do not need additional disk space (except for the
configuration files as well as some directories, bind-mounts and symbolic
links - we're writing about 250-2500 additional inodes and 500-2500 kB of
disk space).

=item +

The applications do not use additional main memory when compared to
running outside of the LXC container (except for the overhead of a few
scripts and LXC itself).

=back

lib/App/LXC/Container.pm  view on Meta::CPAN

an application container.

=back

I<Additional advantages/disadvantages are welcome.>

=head1 BEST PRACTICES

Especially external packages often haven't all their real dependencies
configured.  For those it is often necessary to manually add some packages
and bind mount points like the following:

=head2 additional packages

Note that the examples are from Debian.

=over

=item fontconfig-config (select C</usr/share/fontconfig>)

=item locales (select C</usr/share/locale/locale.alias>)

=back

=head2 additional bind mounts

Note that again the examples are from Debian.

=over

=item C</usr/share/fonts>

=back

=head1 KNOWN BUGS

lib/App/LXC/Container/Data/common.pm  view on Meta::CPAN

=cut

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
sub content_default_mounts($)
{
    _check_singleton(shift);
    my @output =
	('# Some notes to the list of default mounts (mounts that are needed in',
	 '# every application container):',
	 '#',
	 '# 1. Default mounts are read-only bind mounts.',
	 '# 2. Other mount options must be specified explicitly in field 2.',
	 '# 3. Special filesystems must be specified explicitly in field 3.',
	 '#',
	 '# In addition to directories (for mount-points) this list may also',
	 '# contain symbolic links, that are simply copied to the created',
	 '# configuration.',
	 '',
	 '# common:');
    local $_;
    foreach
      ('/bin',
       '/dev/shm	create=dir,rw			tmpfs',
       # the next 3 are needed by su:
       '/etc/login.defs',
       '/etc/pam.d',
       '/etc/security',
       '/lib',
       '/root		create=dir,rw,mode=700		tmpfs',
       '/sbin',
       # a shared and writable /tmp and extra unshared /usr/tmp and /var/tmp:
       '/tmp		create=dir,rw,bind',
       '/usr/tmp	create=dir,rw			tmpfs',
       '/var/tmp	create=dir,rw			tmpfs',
      )
    {
	(my $entry = $_) =~ s/\s+.*//;
	next  if  -l $entry;
	next  unless  -d $entry  or  -f $entry;
	push @output, abs_path($_);
    }
    return @output;

lib/App/LXC/Container/Data/common.pm  view on Meta::CPAN


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
sub content_x11_mounts($)
{
    _check_singleton(shift);
    my @output =
      ('# This is an additional mount configuration file for X11 applications.',
       '# See 40-MNT-default.mounts for more explanations.',
       '',
       '# common:',
       '/dev/dri	create=dir,rw,bind,optional',
       '/usr/share/icons',
       '/usr/share/mime',
       '/usr/share/pixmaps',
       '/var/cache/fontconfig',
       '/var/lib/dbus');
    return @output
}

#########################################################################

lib/App/LXC/Container/Mounts.pm  view on Meta::CPAN

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

sub implicit_mount_lines($$)
{
    my ($self, $path) = @_;
    my @mount_lines = ();
    if ($self->mount_point($path) == IMPLICIT)
    {
	push @mount_lines,
	    'lxc.mount.entry = ' . $path . ' ' . substr($path, 1) .
	    ' none create=' . (-d $path ? 'dir' : 'file') . ',ro,bind 0 0';
    }
    local $_;
    foreach ($self->sub_directories($path))
    {	push @mount_lines, $self->implicit_mount_lines($_);   }
    return @mount_lines;
}

#########################################################################

=head2 B<merge_mount_points> - merge mount-points

lib/App/LXC/Container/Setup.pm  view on Meta::CPAN

sub _mark2mount($)
{
    my ($conf_str) = @_;
    my $mark = substr($conf_str, 0, 2);
    my $file = substr($conf_str, 3);
    local $_ = $file;
    if ($mark eq '  ')
    {}
    elsif ($mark eq 'RW')
    {
	$_ = sprintf("%-39s create=%s,rw,bind%s",
		     $_,
		     (-d $_ ? 'dir' : 'file'),
		     # relaxed bind-mounting for /dev and /var items:
		     (m!^/(?:dev|var)/!) ? ',optional' : '');
    }
    elsif ($mark eq 'OV')
    {
	$_ = sprintf("%-39s create=%s,rw\t\t%s",
		     $_, (-d $_ ? 'dir' : 'file'), 'tmpfs');
    }
    else
    {	fatal 'internal_error__1', "bad mark '$mark' in _mark2mount";   }
    return $_;

lib/App/LXC/Container/Setup.pm  view on Meta::CPAN

    $self->{mounts} = [];
    local $_;
    while (<$file>)
    {
	next if m/^\s*(?:#|$)/;
	s/\s*#.*$//;
	if (m|^\s*(\S+)(\s+\S.*)?$|)
	{
	    my ($path, $special) = ($1, $2);
	    $_ = (! defined $special ? '  '
		  : $special =~ m/rw,bind/ ? 'RW' : 'OV') . ' ' . $path;
	    push @{$self->{mounts}}, $_;
	}
	else
	{   error 'ignoring_unknown_item_in__1__2', $path, $.;   }
    }
    close $file;
}

#########################################################################

lib/App/LXC/Container/Update.pm  view on Meta::CPAN

	    if (m|^\s*(/\S+)(?:\s+(\S+)(?:\s+(\S+)\s*)?)?$|)
	    {
		my ($path, $options, $fsys) = ($1, $2, $3);
		my $entry = ($fsys
			     ? $fsys . ' ' . substr($path, 1) . ' ' . $fsys
			     : $path . ' ' . substr($path, 1) . ' none');
		$entry .=
		    ' ' .
		    ($options
		     ? $options
		     : 'create=' . (-d $path ? 'dir' : 'file') . ',ro,bind')
		    . ' 0 0';
		$self->{mount_entry}{$path} = $entry;
		$self->{mount_source}{$path} = $source;
		push @{$self->{mounts_of_source}{$source}}, $path;
	    }
	    else
	    {	error 'ignoring_unknown_item_in__1__2', $path, $.;   }
	}
	close $in;
    }

lib/App/LXC/Container/Update.pm  view on Meta::CPAN

	'^(?:' . join('|', values %{$self->{users}}) . '):.*:(/[^:]+):[^:]+$';
    # Normally this could never fail:
    # uncoverable branch true
    open my $pwd, '<', PWD  or  fatal 'can_t_open__1__2', PWD, $!;
    my @users = ();
    local $_;
    while (<$pwd>)
    {
	next unless m/$re_users/o;
	$self->{mount_entry}{$1} =
	    $1 . ' ' . substr($1, 1) . ' none create=dir,rw,bind';
	$self->{mount_source}{$1} = $key;
	push @{$self->{mounts_of_source}{$key}}, $1;
    }
    close $pwd;
}

#########################################################################

=head2 B<_write_lxc_configuration> - write LXC configuration file

t/03-setup.t  view on Meta::CPAN

#########################################################################
# now check configuration created by above monster-test:
check_config_against_regexp
    ('tt-CNF-test.master', '3rd',
     qr{^network=1\nx11=1\naudio=1\nusers=$re_user$}m);
check_config_against_regexp
    ('tt-MNT-test.mounts', '3rd',
     qr{\A\#\ mounts\ for\ container\ test$
	.*/t/tmp/usr/bin/2something$
	.*/t/tmp/usr/bin/2something\s+create=file,rw\s+tmpfs$
	.*/t/tmp/usr/bin/2something\s+create=file,rw,bind$}msx);
check_config_against_regexp
    ('tt-NOT-test.filter', '3rd',
     qr{^/var/log\s+empty\n
	.*/t/tmp/usr/bin/2something\s+copy\n
	.*/t/tmp/usr/bin/2something\s+nomerge\n
	.*/t/tmp/usr/bin/2something\s+ignore\n}mx);
check_config_against_regexp
    ('tt-PKG-test.packages', '3rd',
     qr{\A# package list for container test\n.*^chromium\n\Z}ms);

t/03-setup.t  view on Meta::CPAN

    'reading bad configuration file ' . $file . ' fails';
}
test_bad_config('bd-NOT-bad.filter', 'bad entry');
test_bad_config('bd-CNF-bad.master', 'bad entry');
test_bad_config('bd-MNT-bad.mounts', 'bad-entry ');
test_bad_config('bd-PKG-bad.packages', 'bad entry');

#########################################################################
# simulate some more valid configuration entries:
like(App::LXC::Container::Setup::_mark2mount('RW /dev/disk'),
     qr{^/dev/disk\s+create=dir,rw,bind,optional$},
     'valid optional device directory');
like(App::LXC::Container::Setup::_mark2mount('RW /dev/somedevice'),
     qr{^/dev/somedevice\s+create=file,rw,bind,optional$},
     'valid optional device file');
like(App::LXC::Container::Setup::_mark2mount('OV /'),
     qr{^/\s+create=dir,rw\s+tmpfs$},
     'valid temporary directory');

#########################################################################
# run tests with other maximum screen sizes:
sub test_other_screen_size($$)
{
    my ($w, $h) = @_;

t/06-update.t  view on Meta::CPAN

is($_->{audio}, 0, 'master test 8 audio is correct');
is($_->{network}, 0, 'master test 8 network is correct');
is_deeply([sort keys %{$_->{users}}], [0],
	  'master test 8 users are correct');
is_deeply([sort values %{$_->{users}}], ['root'],
	  'master test 8 users are correct');
is_deeply($_->{users_from}, ['update-test-4'],
	  'master test 8 users have correct origin');
is($_->{x11}, 0, 'master test 8 X11 is correct');
$_->_parse_users();
like($_->{mount_entry}{'/root'}, qr{^/root\s+root\s+none create=dir,rw,bind$},
     'user test 8 created correct mount entry');
is($_->{mount_source}{'/root'}, 'container users',
   'user test 8 created correct mount source');
is($_->{mounts_of_source}{'container users'}[0], '/root',
   'user test 8 created correct mount source list');

#########################################################################
# test for packages files:
_setup_file('/lxc/conf/u1-PKG-update-test-1.packages',
	    '# 2 identical', '', 'chromium', 'chromium');

t/06-update.t  view on Meta::CPAN

	    'lxc.namespace.keep=ipc');

$update_object = App::LXC::Container::Update->new('update-test-1');
$update_object->_parse_mounts();
obj_keys_in_range('mount_entry', 8, 12,
		  'mounts test 1 has correct entry count');
obj_keys_in_range('mount_source', 8, 12,
		  'mounts test 1 has correct source count');
is($update_object->{mount_entry}{$path2something},
   $path2something . ' ' . substr($path2something, 1)
   . ' none create=file,ro,bind 0 0',
   'mounts test 1 source entry is correct');
is($update_object->{mount_source}{$path2something}, 'u1-MNT-update-test-1.mounts',
   'mounts test 1 source entry is correct');

$update_object = App::LXC::Container::Update->new('update-test-2');
$update_object->_parse_master();		# now with X11 mounts!
$update_object->_parse_mounts();
obj_keys_in_range('mount_entry', 18, 22,
		  'mounts test 2 has correct entry count');
obj_keys_in_range('mount_source', 18, 22,
		  'mounts test 2 has correct source count');
is($update_object->{mount_entry}{$path2something},
   $path2something . ' ' . substr($path2something, 1)
   . ' none create=file,ro,bind 0 0',
   'mounts test 2 source entry is correct');
is($update_object->{mount_source}{$path2something}, 'u2-MNT-update-test-2.mounts',
   'mounts test 2 source entry is correct');
is($update_object->{mount_entry}{'/usr/share/icons'},
   '/usr/share/icons usr/share/icons none create=dir,ro,bind 0 0',
   'mounts test 2 source entry for X11 is correct');
is($update_object->{mount_source}{'/usr/share/icons'}, '61-MNT-X11.mounts',
   'mounts test 2 source entry for X11 is correct');

$update_object = App::LXC::Container::Update->new('update-test-1', 'update-test-2');
$update_object->_parse_mounts();
obj_keys_in_range('mount_entry', 8, 12,
		  'mounts test 3 has correct entry count');
obj_keys_in_range('mount_source', 8, 12,
		  'mounts test 3 has correct source count');
is($update_object->{mount_entry}{$path2something},
   $path2something . ' ' . substr($path2something, 1)
   . ' none create=file,ro,bind 0 0',
   'mounts test 3 source entry is correct');
is($update_object->{mount_source}{$path2something}, 'u2-MNT-update-test-2.mounts',
   'mounts test 3 source entry is correct');

$update_object = App::LXC::Container::Update->new('update-test-2', 'update-test-1');
$update_object->_parse_mounts();
obj_keys_in_range('mount_entry', 8, 12,
		  'mounts test 4 has correct entry count');
obj_keys_in_range('mount_source', 8, 12,
		  'mounts test 4 has correct source count');
is($update_object->{mount_entry}{$path2something},
   $path2something . ' ' . substr($path2something, 1)
   . ' none create=file,ro,bind 0 0',
   'mounts test 4 source entry is correct');
is($update_object->{mount_source}{$path2something}, 'u1-MNT-update-test-1.mounts',
   'mounts test 4 source entry is correct');

#########################################################################
# test for filter files:
_setup_file('/lxc/conf/u1-NOT-update-test-1.filter',
	    '# 2nd overwrites 1st',
	    TMP_PATH . '/usr/lib nomerge',
	    TMP_PATH . '/var/log empty',

t/06-update.t  view on Meta::CPAN

	 '',
	 '#+ special configuration #+',
	 'lxc.namespace.keep=ipc',
	 '',
	 '#+ container users #+',
	 '',
	 '#+ 40-MNT-default\.mounts #+',
	 # distributions may have additional non-symlink directories here,
	 # some are missing /dev/shm:
	 '.*(lxc\.mount\.entry = tmpfs dev/shm tmpfs create=dir,rw 0 0',
	 ')?lxc\.mount\.entry = /etc/login.defs etc/login.defs none create=file,ro,bind 0 0',
	 'lxc\.mount\.entry = /etc/pam.d etc/pam.d none create=dir,ro,bind 0 0',
	 'lxc\.mount\.entry = /etc/security etc/security none create=dir,ro,bind 0 0',
	 '.*lxc\.mount\.entry = tmpfs root tmpfs create=dir,rw,mode=700 0 0',
	 '.*lxc\.mount\.entry = /tmp tmp none create=dir,rw,bind 0 0',
	 'lxc\.mount\.entry = tmpfs var/tmp tmpfs create=dir,rw 0 0',
	 '(lxc\.mount\.entry = /etc/debian_version etc/debian_version none create=file,ro,bind 0 0',
	 ')?',
	 '#+ 41-MNT-network\.mounts #+',
	 'lxc\.mount\.entry = /etc/ssl/certs etc/ssl/certs none create=dir,ro,bind 0 0',
	 'lxc\.mount\.entry = /usr/lib/ssl usr/lib/ssl none create=dir,ro,bind 0 0',
	 'lxc\.mount\.entry = /usr/share/ca-certificates usr/share/ca-certificates none create=dir,ro,bind 0 0',
	 'lxc\.mount\.entry = /usr/share/ssl-cert usr/share/ssl-cert none create=dir,ro,bind 0 0',
	 '',
	 '#+ 61-MNT-X11\.mounts #+',
	 '[^#]+#+ u2-MNT-update-test-2\.mounts #+',
	 '',
	 '#+ u1-MNT-update-test-1\.mounts #+',
	 'lxc\.mount\.entry = /.+/bin/2something none create=file,ro,bind 0 0',
	 'lxc\.mount\.entry = /.+/bin/2something none create=file,ro,bind 0 0',
	 '',
	 '#+ 30-PKG-default\.packages #+',
	 '# coreutils',
	 '# dash',
	 '# libc-bin',
	 '# util-linux',
	 '#+ 31-PKG-network.packages #+',
	 '# iproute2',
	 '#+ 60-PKG-X11.packages #+',
	 '# fontconfig-config',

t/06-update.t  view on Meta::CPAN

	 '#+ u2-PKG-update-test-2\.packages #+',
	 '# chromium',
	 '# evince',
	 '#+ u1-PKG-update-test-1\.packages #+',
	 '',
	 '#+ empty filters #+',
	 'lxc\.mount\.entry = tmpfs .+/tmp/var/log tmpfs create=dir,rw 0 0',
	 'lxc\.mount\.entry = tmpfs var/log tmpfs create=dir,rw 0 0',
	 '',
	 '#+ mounts derived from above packages #+',
	 'lxc\.mount\.entry = /.+/tmp/usr/bin/1chromium .+/tmp/usr/bin/1chromium none create=file,ro,bind 0 0',
	 'lxc\.mount\.entry = /.+/tmp/usr/lib/some/directory/with .+/tmp/usr/lib/some/directory/with none create=dir,ro,bind 0 0'
# helper expression to update test after modifying Data/*.pm (move up/down):
#(1?():(
##))
	);
    my $conf = '';
    if (-f CONF_ROOT . '/update-test-1.conf')
    {
	open my $in, '<', CONF_ROOT . '/update-test-1.conf'
	    or  die "can't open ", CONF_ROOT, '/update-test-1.conf: ', $!;
	$conf = join('', <$in>);

t/06-update.t  view on Meta::CPAN

	     'lxc\.mount\.auto = cgroup:ro proc:mixed sys:ro',
	     '',
	     '#+ -no privileged users- #+',
	     'lxc\.idmap = u 0 100000 65536',
	     'lxc\.idmap = g 0 100000 65536',
	     '',
	     '#+ 40-MNT-default\.mounts #+',
	     # distributions may have additional non-symlink directories here,
	     # some are missing /dev/shm:
	     '.*(lxc\.mount\.entry = tmpfs dev/shm tmpfs create=dir,rw 0 0',
	     ')?lxc\.mount\.entry = /etc/login.defs etc/login.defs none create=file,ro,bind 0 0',
	     'lxc\.mount\.entry = /etc/pam.d etc/pam.d none create=dir,ro,bind 0 0',
	     'lxc\.mount\.entry = /etc/security etc/security none create=dir,ro,bind 0 0',
	     '.*lxc\.mount\.entry = tmpfs root tmpfs create=dir,rw,mode=700 0 0',
	     '.*lxc\.mount\.entry = /tmp tmp none create=dir,rw,bind 0 0',
	     'lxc\.mount\.entry = tmpfs var/tmp tmpfs create=dir,rw 0 0',
	     '(lxc\.mount\.entry = /etc/debian_version etc/debian_version none create=file,ro,bind 0 0',
	     ')?',
	     '#+ 41-MNT-network\.mounts #+',
	     'lxc\.mount\.entry = /etc/ssl/certs etc/ssl/certs none create=dir,ro,bind 0 0',
	     'lxc\.mount\.entry = /usr/lib/ssl usr/lib/ssl none create=dir,ro,bind 0 0',
	     'lxc\.mount\.entry = /usr/share/ca-certificates usr/share/ca-certificates none create=dir,ro,bind 0 0',
	     'lxc\.mount\.entry = /usr/share/ssl-cert usr/share/ssl-cert none create=dir,ro,bind 0 0',
	     '',
	     '#+ 30-PKG-default\.packages #+',
	     '# coreutils',
	     '# dash',
	     '# libc-bin',
	     '# util-linux',
	     '#+ 31-PKG-network.packages #+',
	     '# iproute2',
	     '',
	     '#+ empty filters #+',
	     'lxc.mount.entry = tmpfs var/log tmpfs create=dir,rw 0 0',
	     '',
	     '#+ mounts derived from above packages #+',
	     'lxc\.mount\.entry = /.+/tmp/usr/bin/2something .+/tmp/usr/bin/2something none create=file,ro,bind 0 0'
# helper expression to update test after modifying Data/*.pm (move up/down):
#(1?():(
#))
	    );
	if (-f $conf_file)
	{
	    open my $in, '<', $conf_file
		or  die "can't open ", $conf_file . ': ', $!;
	    $conf = join('', <$in>);
	    close $in;

t/07-run.t  view on Meta::CPAN

_setup_file('/lxc/run-test-1.conf',
	    '#MASTER:N,-,-',
	    'lxc.rootfs.path=' . CONF_ROOT . '/run-test-1',
	    'lxc.idmap = u 0 0 1',
	    'lxc.idmap = u 1 1 1',
	    'lxc.idmap = u 2 100002 65534',
	    'lxc.idmap = g 0 0 1',
	    'lxc.idmap = g 1 1 1',
	    'lxc.idmap = g 2 100002 65534',
	    'lxc.mount.entry = tmpfs dev/shm tmpfs create=dir,rw 0 0',
	    'lxc.mount.entry = /tmp tmp none create=dir,rw,bind 0 0',
	    '');
$_ = App::LXC::Container::Run->new('run-test-1', 'root', '/', 'do', 'it');
check_config_object($_,
		    'valid configuration 1',
		    [[audio => '-'],
		     [command => ['do', 'it']],
		     [dir => '/'],
		     [gateway => '^$'],
		     [gids => [1]],
		     [init => CONF_ROOT . '/run-test-1/lxc-run.sh'],

t/07-run.t  view on Meta::CPAN

# tests with 2nd valid configuration:
_setup_dir('/lxc/run-test-2');
_remove_file('/lxc/run-test-2.conf');
_setup_file('/lxc/run-test-2.conf',
	    '#MASTER:G42,X,A',
	    'lxc.rootfs.path=' . CONF_ROOT . '/run-test-2',
	    'lxc.net.0.ipv4.address = 10.0.3.42/24',
	    'lxc.idmap = u 0 100000 65536',
	    'lxc.idmap = g 0 100000 65536',
	    'lxc.mount.entry = tmpfs dev/shm tmpfs create=dir,rw 0 0',
	    'lxc.mount.entry = /tmp tmp none create=dir,rw,bind 0 0',
	    '');
_setup_dir('/lxc/run-test-2/etc');
$_ = App::LXC::Container::Run->new('run-test-2', 'root', '/', 'do', 'it');
check_config_object($_,
		    'valid configuration 2',
		    [[audio => 'A'],
		     [command => ['do', 'it']],
		     [dir => '/'],
		     [gateway => '^10\.0\.3\.1$'],
		     [gids => []],



( run in 1.142 second using v1.01-cache-2.11-cpan-2398b32b56e )