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

T6219: Add support for container sysctl parameter (backport #3614) #3630

Merged
merged 2 commits into from
Jun 11, 2024
Merged
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
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
Loading