Skip to content

Commit

Permalink
Merge pull request #3614 from nvollmar/T6219
Browse files Browse the repository at this point in the history
T6219: Add support for container sysctl parameter
  • Loading branch information
c-po authored Jun 10, 2024
2 parents 8260743 + f030464 commit eaf20bb
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 15 deletions.
29 changes: 29 additions & 0 deletions interface-definitions/container.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,35 @@
<multi/>
</properties>
</leafNode>
<node name="sysctl">
<properties>
<help>Configure namespaced kernel parameters of the container</help>
</properties>
<children>
<tagNode name="parameter">
<properties>
<help>Sysctl key name</help>
<completionHelp>
<script>${vyos_completion_dir}/list_container_sysctl_parameters.sh</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Sysctl key name</description>
</valueHelp>
<constraint>
<validator name="sysctl"/>
</constraint>
</properties>
<children>
<leafNode name="value">
<properties>
<help>Sysctl configuration value</help>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
#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 @@ -11,3 +11,4 @@ set container name c02 allow-host-networks
set container name c02 allow-host-pid
set container name c02 capability 'sys-time'
set container name c02 image 'busybox:stable'
set container name c02 sysctl parameter kernel.msgmax value '8192'
5 changes: 5 additions & 0 deletions smoketest/configs/container-simple
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ container {
allow-host-pid
cap-add sys-time
image busybox:stable
sysctl {
parameter kernel.msgmax {
value "8192"
}
}
}
}
interfaces {
Expand Down
5 changes: 5 additions & 0 deletions smoketest/scripts/cli/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def test_basic(self):

self.cli_set(base_path + ['name', cont_name, 'image', cont_image])
self.cli_set(base_path + ['name', cont_name, 'allow-host-networks'])
self.cli_set(base_path + ['name', cont_name, 'sysctl', 'parameter', 'kernel.msgmax', 'value', '4096'])

# commit changes
self.cli_commit()
Expand All @@ -91,6 +92,10 @@ def test_basic(self):
# Check for running process
self.assertEqual(process_named_running(PROCESS_NAME), pid)

# verify
tmp = cmd(f'sudo podman exec -it {cont_name} sysctl kernel.msgmax')
self.assertEqual(tmp, 'kernel.msgmax = 4096')

def test_cpu_limit(self):
cont_name = 'c2'

Expand Down
20 changes: 20 additions & 0 deletions src/completion/list_container_sysctl_parameters.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/sh
#
# Copyright (C) 2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

declare -a vals
eval "vals=($(/sbin/sysctl -N -a|grep -E '^(fs.mqueue|net)\.|^(kernel.msgmax|kernel.msgmnb|kernel.msgmni|kernel.sem|kernel.shmall|kernel.shmmax|kernel.shmmni|kernel.shm_rmid_forced)$'))"
echo ${vals[@]}
exit 0
55 changes: 40 additions & 15 deletions src/conf_mode/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,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 @@ -86,21 +90,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 @@ -125,8 +130,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 'cpu_quota' in container_config:
Expand Down Expand Up @@ -167,11 +172,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 @@ -186,6 +191,13 @@ def verify(container):
if not os.path.exists(source):
raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')

if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
for var, cfg in container_config['sysctl']['parameter'].items():
if 'value' not in cfg:
raise ConfigError(f'sysctl parameter {var} has no value assigned!')
if var.startswith('net.') and 'allow_host_networks' in container_config:
raise ConfigError(f'sysctl parameter {var} cannot be set when using host networking!')

if 'environment' in container_config:
for var, cfg in container_config['environment'].items():
if 'value' not in cfg:
Expand Down Expand Up @@ -219,7 +231,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 @@ -235,8 +248,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 @@ -262,13 +277,20 @@ def verify(container):

return None


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

# Add sysctl options
sysctl_opt = ''
if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
for k, v in container_config['sysctl']['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 @@ -341,7 +363,7 @@ def generate_run_arguments(name, container_config):
if 'allow_host_pid' in container_config:
host_pid = '--pid host'

container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} ' \
container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {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} {host_pid}'

Expand Down Expand Up @@ -375,6 +397,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 @@ -387,7 +410,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 @@ -399,7 +422,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 @@ -418,11 +441,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 @@ -485,6 +509,7 @@ def apply(container):

return None


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

0 comments on commit eaf20bb

Please sign in to comment.