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_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" DEFAULT_TARGET_ARCHES: "linux-x86_64 linux-arm64 linux-armv7"
MODULE_NAME: ngx_http_monitoring_module 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: >- NGINX_CONFIGURE_ARGS: >-
--with-compat --with-compat
--with-http_ssl_module --with-http_ssl_module
@@ -226,6 +227,7 @@ jobs:
-e NEXT_VERSION \ -e NEXT_VERSION \
-e NGINX_CONFIGURE_ARGS \ -e NGINX_CONFIGURE_ARGS \
-e RELEASE_BUILD_IMAGE \ -e RELEASE_BUILD_IMAGE \
-e RELEASE_LIBC_BASELINE \
-e TARGET_ARCH="$target_arch" \ -e TARGET_ARCH="$target_arch" \
-v "$GITHUB_WORKSPACE:/work" \ -v "$GITHUB_WORKSPACE:/work" \
-w /work \ -w /work \
@@ -306,6 +308,7 @@ jobs:
printf 'target=%s\n' "$TARGET_ARCH" printf 'target=%s\n' "$TARGET_ARCH"
printf 'docker_platform=%s\n' "$DOCKER_PLATFORM" printf 'docker_platform=%s\n' "$DOCKER_PLATFORM"
printf 'build_image=%s\n' "$RELEASE_BUILD_IMAGE" printf 'build_image=%s\n' "$RELEASE_BUILD_IMAGE"
printf 'libc_baseline=%s\n' "$RELEASE_LIBC_BASELINE"
printf 'build_machine=%s\n' "$(uname -m)" printf 'build_machine=%s\n' "$(uname -m)"
printf 'runner=ubuntu-22.04\n' printf 'runner=ubuntu-22.04\n'
printf 'git_ref=%s\n' "$GITHUB_REF" printf 'git_ref=%s\n' "$GITHUB_REF"
@@ -318,6 +321,7 @@ jobs:
printf '# %s for Nginx %s\n\n' "$MODULE_NAME" "$nginx_version" printf '# %s for Nginx %s\n\n' "$MODULE_NAME" "$nginx_version"
printf 'Release: `%s`\n\n' "$NEXT_TAG" printf 'Release: `%s`\n\n' "$NEXT_TAG"
printf 'Target: `%s` (`%s`)\n\n' "$TARGET_ARCH" "$DOCKER_PLATFORM" 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 'This package contains a prebuilt Linux dynamic module:\n\n'
printf '```text\n' printf '```text\n'
printf 'modules/%s.so\n' "$MODULE_NAME" printf 'modules/%s.so\n' "$MODULE_NAME"
@@ -326,8 +330,8 @@ jobs:
printf '```nginx\n' printf '```nginx\n'
printf 'load_module modules/%s.so;\n' "$MODULE_NAME" printf 'load_module modules/%s.so;\n' "$MODULE_NAME"
printf '```\n\n' 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 '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 compatible Linux/glibc environment. ' 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' printf 'If your Nginx package has a materially different module ABI, rebuild from source with this repository.\n'
} > "$package_dir/INSTALL.md" } > "$package_dir/INSTALL.md"
@@ -354,6 +358,7 @@ jobs:
{ {
printf '# ngx_http_monitoring_module %s\n\n' "$NEXT_TAG" printf '# ngx_http_monitoring_module %s\n\n' "$NEXT_TAG"
printf 'Previous tag: `%s`\n\n' "$LATEST_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 '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 '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" printf -- '- `modules/%s.so`\n' "$MODULE_NAME"
@@ -417,6 +422,9 @@ jobs:
file: dockerized/Dockerfile file: dockerized/Dockerfile
platforms: ${{ steps.targets.outputs.docker_platforms }} platforms: ${{ steps.targets.outputs.docker_platforms }}
push: true push: true
build-args: |
BASE_IMAGE=ubuntu:18.04
OPENSSL_RUNTIME_PACKAGE=libssl1.1
tags: | tags: |
${{ steps.image.outputs.repo }}:${{ steps.version.outputs.next_tag }} ${{ steps.image.outputs.repo }}:${{ steps.version.outputs.next_tag }}
${{ steps.image.outputs.repo }}:latest ${{ steps.image.outputs.repo }}:latest

View File

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

View File

@@ -36,6 +36,8 @@ The compiled module is written to:
build/ngx_http_monitoring_module.so 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 ## Docker Image
A Dockerized Nginx image is available in [dockerized](</dockerized>): 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 ARG NGINX_VERSION=1.28.0
FROM debian:${DEBIAN_VERSION}-slim AS builder FROM ${BASE_IMAGE} AS builder
ARG NGINX_VERSION 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" 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/ && 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 NGINX_VERSION
ARG OPENSSL_RUNTIME_PACKAGE=libssl3
LABEL org.opencontainers.image.title="ngx_http_monitoring_module nginx image" \ 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" \ 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 \ && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
libpcre2-8-0 \ libpcre2-8-0 \
libssl3 \ ${OPENSSL_RUNTIME_PACKAGE} \
zlib1g \ zlib1g \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& groupadd --system --gid 101 nginx \ && 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 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 ## Run
```sh ```sh

View File

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

View File

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

View File

@@ -58,3 +58,5 @@ Upstream metrics are passively observed from Nginx upstream state recorded on pr
## Compatibility ## 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. 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 # 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 ## 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 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 ## 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 <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <ifaddrs.h> #include <ifaddrs.h>
#include <mntent.h>
#include <net/if.h> #include <net/if.h>
#include <stdio.h> #include <stdio.h>
#include <string.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, static ngx_atomic_uint_t ngx_http_monitoring_delta(ngx_atomic_uint_t now,
ngx_atomic_uint_t prev); ngx_atomic_uint_t prev);
static ngx_uint_t ngx_http_monitoring_skip_fs(const char *type); 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 ngx_int_t
@@ -599,25 +601,45 @@ static void
ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh) ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh)
{ {
FILE *fp; 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; struct statvfs st;
ngx_uint_t count; ngx_uint_t count;
uint64_t total, free_bytes, avail, used; uint64_t total, free_bytes, avail, used;
fp = setmntent("/proc/mounts", "r"); fp = fopen("/proc/mounts", "r");
if (fp == NULL) { if (fp == NULL) {
return; return;
} }
count = 0; count = 0;
while ((mnt = getmntent(fp)) != NULL while (count < NGX_HTTP_MONITORING_FILESYSTEMS_MAX
&& 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; 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; 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; avail = (uint64_t) st.f_bavail * st.f_frsize;
used = total - free_bytes; 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_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); NGX_HTTP_MONITORING_NAME_LEN);
sh->filesystems[count].total = (ngx_atomic_uint_t) total; sh->filesystems[count].total = (ngx_atomic_uint_t) total;
sh->filesystems[count].used = (ngx_atomic_uint_t) used; 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++; count++;
} }
endmntent(fp); fclose(fp);
sh->fs_count = count; 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 static ngx_uint_t
ngx_http_monitoring_skip_fs(const char *type) ngx_http_monitoring_skip_fs(const char *type)
{ {