Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

container: T6219: Add support for container sysctl / kernel parameters #3288

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions interface-definitions/container.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,26 @@
<multi/>
</properties>
</leafNode>
<tagNode name="kernel-parameter">
<properties>
<help>Add custom kernel parameters (sysctl)</help>
<constraint>
<regex>[._a-z*]+</regex>
</constraint>
<constraintErrorMessage>Kernel parameter name must be alphanumeric and can contain periods, asterisks and underscores</constraintErrorMessage>
</properties>
<children>
<leafNode name="value">
<properties>
<help>Set kernel parameter option value</help>
<valueHelp>
<format>txt</format>
<description>Set kernel parameter option value</description>
</valueHelp>
</properties>
</leafNode>
</children>
</tagNode>
#include <include/generic-description.xml.i>
<tagNode name="device">
<properties>
Expand Down
1 change: 1 addition & 0 deletions smoketest/config-tests/container-simple
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ set container name c01 image 'busybox:stable'
set container name c02 allow-host-networks
set container name c02 capability 'sys-time'
set container name c02 image 'busybox:stable'
set container name c02 kernel-parameter 'net.ipv4.conf.all.forwarding' value '1'
3 changes: 3 additions & 0 deletions smoketest/configs/container-simple
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ container {
allow-host-networks
cap-add sys-time
image busybox:stable
kernel-parameter "net.ipv4.ip_forward" {
value "1"
}
}
}
interfaces {
Expand Down
53 changes: 38 additions & 15 deletions src/conf_mode/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,27 @@
from vyos.xml_ref import default_value
from vyos import ConfigError
from vyos import airbag

airbag.enable()

config_containers = '/etc/containers/containers.conf'
config_registry = '/etc/containers/registries.conf'
config_storage = '/etc/containers/storage.conf'
systemd_unit_path = '/run/systemd/system'


def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
print(command)
return cmd(command)


def network_exists(name):
# Check explicit name for network, returns True if network exists
c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
return bool(c)


# Common functions
def get_config(config=None):
if config:
Expand All @@ -84,21 +88,22 @@ def get_config(config=None):
# registry is a tagNode with default values - merge the list from
# default_values['registry'] into the tagNode variables
if 'registry' not in container:
container.update({'registry' : {}})
container.update({'registry': {}})
default_values = default_value(base + ['registry'])
for registry in default_values:
tmp = {registry : {}}
tmp = {registry: {}}
container['registry'] = dict_merge(tmp, container['registry'])

# Delete container network, delete containers
tmp = node_changed(conf, base + ['network'])
if tmp: container.update({'network_remove' : tmp})
if tmp: container.update({'network_remove': tmp})

tmp = node_changed(conf, base + ['name'])
if tmp: container.update({'container_remove' : tmp})
if tmp: container.update({'container_remove': tmp})

return container


def verify(container):
# bail out early - looks like removal from running config
if not container:
Expand All @@ -123,8 +128,8 @@ def verify(container):
# of image upgrade and deletion.
image = container_config['image']
if run(f'podman image exists {image}') != 0:
Warning(f'Image "{image}" used in container "{name}" does not exist '\
f'locally. Please use "add container image {image}" to add it '\
Warning(f'Image "{image}" used in container "{name}" does not exist ' \
f'locally. Please use "add container image {image}" to add it ' \
f'to the system! Container "{name}" will not be started!')

if 'network' in container_config:
Expand Down Expand Up @@ -160,11 +165,11 @@ def verify(container):

# We can not use the first IP address of a network prefix as this is used by podman
if ip_address(address) == ip_network(network)[1]:
raise ConfigError(f'IP address "{address}" can not be used for a container, '\
raise ConfigError(f'IP address "{address}" can not be used for a container, ' \
'reserved for the container engine!')

if cnt_ipv4 > 1 or cnt_ipv6 > 1:
raise ConfigError(f'Only one IP address per address family can be used for '\
raise ConfigError(f'Only one IP address per address family can be used for ' \
f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!')

if 'device' in container_config:
Expand All @@ -179,6 +184,11 @@ def verify(container):
if not os.path.exists(source):
raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')

if 'kernel-parameter' in container_config:
for var, cfg in container_config['kernel-parameter'].items():
if 'value' not in cfg:
raise ConfigError(f'Kernel parameter {var} has no value assigned!')

if 'environment' in container_config:
for var, cfg in container_config['environment'].items():
if 'value' not in cfg:
Expand Down Expand Up @@ -212,7 +222,8 @@ def verify(container):

# Can not set both allow-host-networks and network at the same time
if {'allow_host_networks', 'network'} <= set(container_config):
raise ConfigError(f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
raise ConfigError(
f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')

# gid cannot be set without uid
if 'gid' in container_config and 'uid' not in container_config:
Expand All @@ -228,8 +239,10 @@ def verify(container):
raise ConfigError(f'prefix for network "{network}" must be defined!')

for prefix in network_config['prefix']:
if is_ipv4(prefix): v4_prefix += 1
elif is_ipv6(prefix): v6_prefix += 1
if is_ipv4(prefix):
v4_prefix += 1
elif is_ipv6(prefix):
v6_prefix += 1

if v4_prefix > 1:
raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
Expand All @@ -255,12 +268,19 @@ def verify(container):

return None


def generate_run_arguments(name, container_config):
image = container_config['image']
memory = container_config['memory']
shared_memory = container_config['shared_memory']
restart = container_config['restart']

# Add sysctl options
sysctl_opt = ''
if 'kernel-parameter' in container_config:
for k, v in container_config['kernel-parameter'].items():
sysctl_opt += f" --sysctl={k}={v['value']}"

# Add capability options. Should be in uppercase
capabilities = ''
if 'capability' in container_config:
Expand Down Expand Up @@ -329,7 +349,7 @@ def generate_run_arguments(name, container_config):
prop = vol_config['propagation']
volume += f' --volume {svol}:{dvol}:{mode},{prop}'

container_base_cmd = f'--detach --interactive --tty --replace {capabilities} ' \
container_base_cmd = f'--detach --interactive --tty --replace {capabilities} {sysctl_opt} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid}'

Expand Down Expand Up @@ -368,6 +388,7 @@ def generate_run_arguments(name, container_config):

return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()


def generate(container):
# bail out early - looks like removal from running config
if not container:
Expand All @@ -380,7 +401,7 @@ def generate(container):
for network, network_config in container['network'].items():
tmp = {
'name': network,
'id' : sha256(f'{network}'.encode()).hexdigest(),
'id': sha256(f'{network}'.encode()).hexdigest(),
'driver': 'bridge',
'network_interface': f'pod-{network}',
'subnets': [],
Expand All @@ -392,7 +413,7 @@ def generate(container):
}
}
for prefix in network_config['prefix']:
net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)}
net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
tmp['subnets'].append(net)

if is_ipv6(prefix):
Expand All @@ -411,11 +432,12 @@ def generate(container):

file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
run_args = generate_run_arguments(name, container_config)
render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args,},
render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args, },
formater=lambda _: _.replace("&quot;", '"').replace("&apos;", "'"))

return None


def apply(container):
# Delete old containers if needed. We can't delete running container
# Option "--force" allows to delete containers with any status
Expand Down Expand Up @@ -478,6 +500,7 @@ def apply(container):

return None


if __name__ == '__main__':
try:
c = get_config()
Expand Down
Loading