Implemented Bionic/libc compatibility.

This commit is contained in:
2026-05-12 19:12:02 +03:30
parent d8dfe1559d
commit f97ac2f7c5
10 changed files with 140 additions and 20 deletions

View File

@@ -34,7 +34,8 @@ env:
DEFAULT_NGINX_VERSIONS: "1.22.1 1.24.0 1.26.3 1.28.0 1.30.0"
DEFAULT_TARGET_ARCHES: "linux-x86_64 linux-arm64 linux-armv7"
MODULE_NAME: ngx_http_monitoring_module
RELEASE_BUILD_IMAGE: ubuntu:22.04
RELEASE_BUILD_IMAGE: ubuntu:18.04
RELEASE_LIBC_BASELINE: ubuntu-bionic-glibc-2.27
NGINX_CONFIGURE_ARGS: >-
--with-compat
--with-http_ssl_module
@@ -226,6 +227,7 @@ jobs:
-e NEXT_VERSION \
-e NGINX_CONFIGURE_ARGS \
-e RELEASE_BUILD_IMAGE \
-e RELEASE_LIBC_BASELINE \
-e TARGET_ARCH="$target_arch" \
-v "$GITHUB_WORKSPACE:/work" \
-w /work \
@@ -306,6 +308,7 @@ jobs:
printf 'target=%s\n' "$TARGET_ARCH"
printf 'docker_platform=%s\n' "$DOCKER_PLATFORM"
printf 'build_image=%s\n' "$RELEASE_BUILD_IMAGE"
printf 'libc_baseline=%s\n' "$RELEASE_LIBC_BASELINE"
printf 'build_machine=%s\n' "$(uname -m)"
printf 'runner=ubuntu-22.04\n'
printf 'git_ref=%s\n' "$GITHUB_REF"
@@ -318,6 +321,7 @@ jobs:
printf '# %s for Nginx %s\n\n' "$MODULE_NAME" "$nginx_version"
printf 'Release: `%s`\n\n' "$NEXT_TAG"
printf 'Target: `%s` (`%s`)\n\n' "$TARGET_ARCH" "$DOCKER_PLATFORM"
printf 'Libc baseline: `%s`\n\n' "$RELEASE_LIBC_BASELINE"
printf 'This package contains a prebuilt Linux dynamic module:\n\n'
printf '```text\n'
printf 'modules/%s.so\n' "$MODULE_NAME"
@@ -326,8 +330,8 @@ jobs:
printf '```nginx\n'
printf 'load_module modules/%s.so;\n' "$MODULE_NAME"
printf '```\n\n'
printf 'This binary is built with `--with-compat` against Nginx %s on Ubuntu 22.04 for %s. ' "$nginx_version" "$TARGET_ARCH"
printf 'For safest production use, run the same Nginx version, CPU architecture, and a compatible Linux/glibc environment. '
printf 'This binary is built with `--with-compat` against Nginx %s on Ubuntu Bionic 18.04 for %s. ' "$nginx_version" "$TARGET_ARCH"
printf 'For safest production use, run the same Nginx version, CPU architecture, and a Linux/glibc environment compatible with `%s`. ' "$RELEASE_LIBC_BASELINE"
printf 'If your Nginx package has a materially different module ABI, rebuild from source with this repository.\n'
} > "$package_dir/INSTALL.md"
@@ -354,6 +358,7 @@ jobs:
{
printf '# ngx_http_monitoring_module %s\n\n' "$NEXT_TAG"
printf 'Previous tag: `%s`\n\n' "$LATEST_TAG"
printf 'Libc baseline: `%s`\n\n' "$RELEASE_LIBC_BASELINE"
printf 'Prebuilt Linux dynamic module packages are attached for targets: `%s`.\n\n' "$NORMALIZED_TARGET_ARCHES"
printf 'Each tarball is built against the Nginx version and target architecture in its filename and includes:\n\n'
printf -- '- `modules/%s.so`\n' "$MODULE_NAME"
@@ -417,6 +422,9 @@ jobs:
file: dockerized/Dockerfile
platforms: ${{ steps.targets.outputs.docker_platforms }}
push: true
build-args: |
BASE_IMAGE=ubuntu:18.04
OPENSSL_RUNTIME_PACKAGE=libssl1.1
tags: |
${{ steps.image.outputs.repo }}:${{ steps.version.outputs.next_tag }}
${{ steps.image.outputs.repo }}:latest

View File

@@ -13,7 +13,7 @@ permissions:
contents: read
env:
TEST_BUILD_IMAGE: ubuntu:22.04
TEST_BUILD_IMAGE: ubuntu:18.04
NGINX_CONFIGURE_ARGS: >-
--with-compat
--with-http_ssl_module

View File

@@ -36,6 +36,8 @@ The compiled module is written to:
build/ngx_http_monitoring_module.so
```
The source avoids glibc-only mount parsing APIs such as `getmntent()` and parses `/proc/mounts` directly, which keeps the collectors portable across Linux libc implementations that expose the required `/proc`, `statvfs()`, and `getifaddrs()` interfaces.
## Docker Image
A Dockerized Nginx image is available in [dockerized](</dockerized>):

View File

@@ -1,7 +1,7 @@
ARG DEBIAN_VERSION=bookworm
ARG BASE_IMAGE=debian:bookworm-slim
ARG NGINX_VERSION=1.28.0
FROM debian:${DEBIAN_VERSION}-slim AS builder
FROM ${BASE_IMAGE} AS builder
ARG NGINX_VERSION
ARG NGINX_CONFIGURE_ARGS="--with-compat --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_v2_module --with-http_gzip_static_module"
@@ -48,9 +48,10 @@ RUN ./configure \
&& cp objs/ngx_http_monitoring_module.so /usr/local/nginx/modules/
FROM debian:${DEBIAN_VERSION}-slim AS runtime
FROM ${BASE_IMAGE} AS runtime
ARG NGINX_VERSION
ARG OPENSSL_RUNTIME_PACKAGE=libssl3
LABEL org.opencontainers.image.title="ngx_http_monitoring_module nginx image" \
org.opencontainers.image.description="Nginx built with ngx_http_monitoring_module and embedded monitoring dashboard" \
@@ -60,7 +61,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
libpcre2-8-0 \
libssl3 \
${OPENSSL_RUNTIME_PACKAGE} \
zlib1g \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system --gid 101 nginx \

View File

@@ -22,6 +22,12 @@ To choose a different Nginx version:
NGINX_VERSION=1.28.0 sh dockerized/build.sh
```
To build the image on an Ubuntu Bionic 18.04 libc baseline:
```sh
BASE_IMAGE=ubuntu:18.04 OPENSSL_RUNTIME_PACKAGE=libssl1.1 sh dockerized/build.sh
```
## Run
```sh

View File

@@ -3,11 +3,15 @@ set -eu
IMAGE="${IMAGE:-ngx-http-monitoring-module:local}"
NGINX_VERSION="${NGINX_VERSION:-1.28.0}"
BASE_IMAGE="${BASE_IMAGE:-debian:bookworm-slim}"
OPENSSL_RUNTIME_PACKAGE="${OPENSSL_RUNTIME_PACKAGE:-libssl3}"
cd "$(dirname "$0")/.."
docker build \
--build-arg "NGINX_VERSION=$NGINX_VERSION" \
--build-arg "BASE_IMAGE=$BASE_IMAGE" \
--build-arg "OPENSSL_RUNTIME_PACKAGE=$OPENSSL_RUNTIME_PACKAGE" \
-f dockerized/Dockerfile \
-t "$IMAGE" \
.

View File

@@ -4,7 +4,9 @@ services:
context: ..
dockerfile: dockerized/Dockerfile
args:
BASE_IMAGE: "debian:bookworm-slim"
NGINX_VERSION: "1.28.0"
OPENSSL_RUNTIME_PACKAGE: "libssl3"
image: ngx-http-monitoring-module:local
container_name: ngx-http-monitoring-module
ports:

View File

@@ -58,3 +58,5 @@ Upstream metrics are passively observed from Nginx upstream state recorded on pr
## Compatibility
The module is designed as an Nginx dynamic HTTP module and should be built with `--with-compat` against the target Nginx source tree. Stub-status counters are compiled in when the target Nginx build includes `--with-http_stub_status_module`; otherwise request counters still work but low-level active/reading/writing/waiting counters remain zero.
Collectors avoid glibc-only helpers such as `getmntent()` and parse `/proc/mounts` directly. This keeps filesystem collection compatible with libc implementations that do not ship `<mntent.h>`, while still requiring Linux `/proc`, `statvfs()`, and `getifaddrs()`.

View File

@@ -1,6 +1,6 @@
# Release Builds
The GitHub Actions workflow in `.github/workflows/release.yml` builds ready-to-use Linux dynamic module packages for x86_64 and ARM targets.
The GitHub Actions workflow in `.github/workflows/release.yml` builds ready-to-use Linux dynamic module packages for x86_64 and ARM targets. Release binaries are built in Ubuntu Bionic 18.04 containers, giving the module artifacts a `ubuntu-bionic-glibc-2.27` libc baseline.
## Manual Release Only
@@ -71,6 +71,13 @@ ghcr.io/<owner>/<repo>:latest
The image is built with Docker Buildx for the same normalized target platforms requested by `target_arches`.
The release image build passes:
```text
BASE_IMAGE=ubuntu:18.04
OPENSSL_RUNTIME_PACKAGE=libssl1.1
```
## Compatibility Warning
Nginx dynamic modules are ABI-sensitive. The release packages are built with `--with-compat` on Ubuntu 22.04 against the Nginx version and CPU architecture in the filename. Use the matching Nginx version, CPU architecture, and a compatible Linux/glibc runtime. Rebuild locally when using a vendor Nginx package with materially different module ABI or hardening options.
Nginx dynamic modules are ABI-sensitive. The release packages are built with `--with-compat` on Ubuntu Bionic 18.04 against the Nginx version and CPU architecture in the filename. Use the matching Nginx version, CPU architecture, and a Linux/glibc runtime compatible with `ubuntu-bionic-glibc-2.27`. Rebuild locally when using a vendor Nginx package with materially different module ABI or hardening options.

View File

@@ -5,7 +5,6 @@
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <mntent.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
@@ -41,6 +40,9 @@ static ngx_atomic_uint_t ngx_http_monitoring_sum_disk(
static ngx_atomic_uint_t ngx_http_monitoring_delta(ngx_atomic_uint_t now,
ngx_atomic_uint_t prev);
static ngx_uint_t ngx_http_monitoring_skip_fs(const char *type);
static char *ngx_http_monitoring_next_mount_field(char **cursor);
static void ngx_http_monitoring_copy_mount_field(u_char *dst, size_t size,
char *src);
ngx_int_t
@@ -599,25 +601,45 @@ static void
ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh)
{
FILE *fp;
struct mntent *mnt;
char line[4096], *cursor, *source, *mount, *type;
u_char mount_path[4096], fs_type[NGX_HTTP_MONITORING_NAME_LEN];
struct statvfs st;
ngx_uint_t count;
uint64_t total, free_bytes, avail, used;
fp = setmntent("/proc/mounts", "r");
fp = fopen("/proc/mounts", "r");
if (fp == NULL) {
return;
}
count = 0;
while ((mnt = getmntent(fp)) != NULL
&& count < NGX_HTTP_MONITORING_FILESYSTEMS_MAX)
while (count < NGX_HTTP_MONITORING_FILESYSTEMS_MAX
&& fgets(line, sizeof(line), fp) != NULL)
{
if (ngx_http_monitoring_skip_fs(mnt->mnt_type)) {
cursor = line;
source = ngx_http_monitoring_next_mount_field(&cursor);
mount = ngx_http_monitoring_next_mount_field(&cursor);
type = ngx_http_monitoring_next_mount_field(&cursor);
if (source == NULL || mount == NULL || type == NULL) {
continue;
}
if (statvfs(mnt->mnt_dir, &st) != 0 || st.f_blocks == 0) {
(void) source;
ngx_http_monitoring_copy_mount_field(mount_path, sizeof(mount_path),
mount);
ngx_http_monitoring_copy_mount_field(fs_type, sizeof(fs_type), type);
if (mount_path[0] == '\0' || fs_type[0] == '\0') {
continue;
}
if (ngx_http_monitoring_skip_fs((char *) fs_type)) {
continue;
}
if (statvfs((char *) mount_path, &st) != 0 || st.f_blocks == 0) {
continue;
}
@@ -626,9 +648,9 @@ ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh)
avail = (uint64_t) st.f_bavail * st.f_frsize;
used = total - free_bytes;
ngx_cpystrn(sh->filesystems[count].path, (u_char *) mnt->mnt_dir,
ngx_cpystrn(sh->filesystems[count].path, mount_path,
NGX_HTTP_MONITORING_KEY_LEN);
ngx_cpystrn(sh->filesystems[count].type, (u_char *) mnt->mnt_type,
ngx_cpystrn(sh->filesystems[count].type, fs_type,
NGX_HTTP_MONITORING_NAME_LEN);
sh->filesystems[count].total = (ngx_atomic_uint_t) total;
sh->filesystems[count].used = (ngx_atomic_uint_t) used;
@@ -640,7 +662,7 @@ ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh)
count++;
}
endmntent(fp);
fclose(fp);
sh->fs_count = count;
}
@@ -822,6 +844,72 @@ ngx_http_monitoring_delta(ngx_atomic_uint_t now, ngx_atomic_uint_t prev)
}
static char *
ngx_http_monitoring_next_mount_field(char **cursor)
{
char *p, *start;
p = *cursor;
while (*p == ' ' || *p == '\t') {
p++;
}
if (*p == '\0' || *p == '\n') {
*cursor = p;
return NULL;
}
start = p;
while (*p != '\0' && *p != '\n' && *p != ' ' && *p != '\t') {
p++;
}
if (*p != '\0') {
*p++ = '\0';
}
*cursor = p;
return start;
}
static void
ngx_http_monitoring_copy_mount_field(u_char *dst, size_t size, char *src)
{
u_char *p, *last;
ngx_uint_t value;
if (size == 0) {
return;
}
p = dst;
last = dst + size - 1;
while (*src != '\0' && p < last) {
if (src[0] == '\\'
&& src[1] >= '0' && src[1] <= '7'
&& src[2] >= '0' && src[2] <= '7'
&& src[3] >= '0' && src[3] <= '7')
{
value = (ngx_uint_t) ((src[1] - '0') * 64
+ (src[2] - '0') * 8
+ (src[3] - '0'));
*p++ = (u_char) value;
src += 4;
continue;
}
*p++ = (u_char) *src++;
}
*p = '\0';
}
static ngx_uint_t
ngx_http_monitoring_skip_fs(const char *type)
{