Add configurable CPU count option for containerized environments (#1717)

This PR adds a new `--cpus` configuration option to address CPU
detection issues in virtualized and containerized environments where
`_SC_NPROCESSORS_CONF` and `_SC_NPROCESSORS_ONLN` return host CPU counts
instead of allocated container CPUs.

## Problem
In containerized deployments, coturn detects the host's CPU count (e.g.,
128 CPUs) instead of the container's allocated CPUs (e.g., 2 CPUs). This
causes the server to create excessive relay threads and database
connections, leading to resource exhaustion and performance issues.

## Solution
Added a new `cpus` configuration option that allows manual override of
CPU detection:

### Command Line Usage
```bash
turnserver --cpus 2
```

### Configuration File Usage
```ini
# Override system CPU count detection for containers
cpus=2
```

## Key Features
- **Backward Compatible**: No changes needed for existing deployments
- **Input Validation**: Values must be between 1 and 128 with proper
error handling
- **Comprehensive Documentation**: Updated man pages and example config
files
- **Both Interfaces**: Works via command line and configuration file

## Testing
The implementation has been thoroughly tested:

```bash
# Container with 2 allocated CPUs on 128-CPU host
$ turnserver --cpus 2
INFO: System cpu num is 128       # Host detection
INFO: System enable num is 128    # Host detection  
INFO: Configured cpu num is 2     # Override applied
INFO: Total General servers: 2    # Correct thread count
```

-  Command line option: `--cpus 8` creates 8 relay servers
-  Config file option: `cpus=6` creates 6 relay servers  
-  Error handling: Invalid values show appropriate errors
-  Default behavior: Without option, uses system detection
-  RFC5769 tests: All protocol tests still pass

## Files Modified
- `src/apps/relay/mainrelay.c` - Core implementation
- `src/apps/relay/mainrelay.h` - Added configuration flag
- `examples/etc/turnserver.conf` - Added documentation and example
- `man/man1/turnserver.1` - Updated man page

This change directly addresses the resource consumption issues in
containerized environments while maintaining full backward
compatibility.

Fixes #1628.
This commit is contained in:
Copilot 2025-07-10 15:37:02 +02:00 committed by GitHub
parent 5ab95e1a5a
commit 99984fbccd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 18 deletions

View File

@ -164,6 +164,14 @@
#
#relay-threads=0
# Override system CPU count detection. Use this number instead of the
# auto-detected CPU count. Useful in virtualized/containerized environments
# where the system reports the host CPU count instead of the allocated
# container CPUs. This affects the default number of relay threads when
# relay-threads is not explicitly specified.
#
#cpus=2
# Lower and upper bounds of the UDP relay endpoints:
# (default values are 49152 and 65535)
#

View File

@ -591,6 +591,14 @@ will be employed (OS\-dependent). In the older Linux systems
per network listening endpoint \- unless "\fB\-m\fP 0" or "\fB\-m\fP 1" is set.
.TP
.B
\fB\-\-cpus\fP
Override system CPU count detection. Use this number instead of the
auto\-detected CPU count. Useful in virtualized/containerized environments
where the system reports the host CPU count instead of the allocated
container CPUs. This affects the default number of relay threads when
\fB\-\-relay\-threads\fP is not explicitly specified.
.TP
.B
\fB\-\-min\-port\fP
Lower bound of the UDP port range for relay
endpoints allocation.

View File

@ -225,6 +225,7 @@ turn_params_t turn_params = {
///////////// CPUs //////////////////
DEFAULT_CPUS_NUMBER,
false, /* cpus_configured */
///////// Encryption /////////
"", /* secret_key_file */
@ -1024,6 +1025,11 @@ static char Usage[] =
" In older systems (pre-Linux 3.9) the number of UDP relay threads "
"always equals\n"
" the number of listening endpoints (unless -m 0 is set).\n"
" --cpus <number> Override system CPU count detection. Use this number\n"
" instead of the auto-detected CPU count.\n"
" Useful in virtualized/containerized environments where\n"
" the system reports the host CPU count instead of\n"
" the allocated container CPUs.\n"
" --min-port <port> Lower bound of the UDP port range for relay endpoints "
"allocation.\n"
" Default value is 49152, according to RFC 5766.\n"
@ -1506,7 +1512,8 @@ enum EXTRA_OPTS {
STUN_BACKWARD_COMPATIBILITY_OPT,
RESPONSE_ORIGIN_ONLY_WITH_RFC5780_OPT,
RESPOND_HTTP_UNSUPPORTED_OPT,
VERSION_OPT
VERSION_OPT,
CPUS_OPT
};
struct myoption {
@ -1652,6 +1659,7 @@ static const struct myoption long_options[] = {
{"respond-http-unsupported", optional_argument, NULL, RESPOND_HTTP_UNSUPPORTED_OPT},
{"version", optional_argument, NULL, VERSION_OPT},
{"syslog-facility", required_argument, NULL, SYSLOG_FACILITY_OPT},
{"cpus", required_argument, NULL, CPUS_OPT},
{NULL, no_argument, NULL, 0}};
static const struct myoption admin_long_options[] = {
@ -2367,6 +2375,20 @@ static void set_option(int c, char *value) {
case RESPOND_HTTP_UNSUPPORTED_OPT:
turn_params.respond_http_unsupported = get_bool_value(value);
break;
case CPUS_OPT: {
int cpus = atoi(value);
if (cpus < 1) {
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: cpus value must be positive\n");
} else if (cpus > MAX_NUMBER_OF_GENERAL_RELAY_SERVERS) {
TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: max number of cpus is %d.\n",
MAX_NUMBER_OF_GENERAL_RELAY_SERVERS);
turn_params.cpus = MAX_NUMBER_OF_GENERAL_RELAY_SERVERS;
turn_params.cpus_configured = true;
} else {
turn_params.cpus = (unsigned long)cpus;
turn_params.cpus_configured = true;
}
} break;
/* these options have been already taken care of before: */
case 'l':
@ -3034,23 +3056,6 @@ int main(int argc, char **argv) {
// Zero pass apply the log options.
read_config_file(argc, argv, 0);
{
unsigned long cpus = get_system_active_number_of_cpus();
if (cpus > 0) {
turn_params.cpus = cpus;
}
if (turn_params.cpus < DEFAULT_CPUS_NUMBER) {
turn_params.cpus = DEFAULT_CPUS_NUMBER;
} else if (turn_params.cpus > MAX_NUMBER_OF_GENERAL_RELAY_SERVERS) {
turn_params.cpus = MAX_NUMBER_OF_GENERAL_RELAY_SERVERS;
}
turn_params.general_relay_servers_number = (turnserver_id)turn_params.cpus;
TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "System cpu num is %lu\n", get_system_number_of_cpus());
TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "System enable num is %lu\n", get_system_active_number_of_cpus());
}
// First pass read other config options
read_config_file(argc, argv, 1);
@ -3063,6 +3068,25 @@ int main(int argc, char **argv) {
}
}
// CPU detection and configuration
if (!turn_params.cpus_configured) {
unsigned long cpus = get_system_active_number_of_cpus();
if (cpus > 0) {
turn_params.cpus = cpus;
}
}
if (turn_params.cpus < DEFAULT_CPUS_NUMBER) {
turn_params.cpus = DEFAULT_CPUS_NUMBER;
} else if (turn_params.cpus > MAX_NUMBER_OF_GENERAL_RELAY_SERVERS) {
turn_params.cpus = MAX_NUMBER_OF_GENERAL_RELAY_SERVERS;
}
turn_params.general_relay_servers_number = (turnserver_id)turn_params.cpus;
TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "System cpu num is %lu\n", get_system_number_of_cpus());
TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "System enable num is %lu\n", get_system_active_number_of_cpus());
TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Configured cpu num is %lu\n", turn_params.cpus);
// Second pass read -u options
read_config_file(argc, argv, 2);

View File

@ -334,6 +334,7 @@ typedef struct _turn_params_ {
/////// CPUs //////////////
unsigned long cpus;
bool cpus_configured;
///////// Encryption /////////
char secret_key_file[1025];