New FAMILUG

The PyMiers

Wednesday, 28 August 2024

Đọc code xem NGINX auto dùng bao nhiêu worker_processes trong container

NGINX là HTTP server phổ biến nhất trên thế giới. NGINX có kiến trúc "master" - "worker", với master process làm nhiệm vụ quản lý, và các worker process sẽ nhận các HTTP request và xử lý.

NGINX worker_processes là gì

Số lượng worker process được set bằng config directive: worker_processes mặc định là 1, và có thể "tự động" với từ khóa auto.

Syntax:     worker_processes number | auto;
Default:

worker_processes 1;

Context:    main

Defines the number of worker processes.

The optimal value depends on many factors including (but not limited to) the number of CPU cores, the number of hard disk drives that store data, and load pattern. When one is in doubt, setting it to the number of available CPU cores would be a good start (the value “auto” will try to autodetect it).

    The auto parameter is supported starting from versions 1.3.8 and 1.2.5.

https://nginx.org/en/docs/ngx_core_module.html#worker_processes

khi set auto, NGINX sẽ try to autodetect - cố tìm số "available CPU cores", dễ dàng xem số CPU trên máy với câu lệnh

$ nproc
4

hay file /proc/cpuinfo

$ grep processor /proc/cpuinfo
processor   : 0
processor   : 1
processor   : 2
processor   : 3

auto là bao nhiêu?

"auto" là bao nhiêu? lấy giá trị từ đâu? có đọc từ /proc ra không? thử đọc code C xem viết gì:

$ git clone --depth 1 https://github.com/nginx/nginx --branch release-1.27.1
Cloning into 'nginx'...
remote: Enumerating objects: 555, done.
...
Note: switching to 'e06bdbd4a20912c5223d7c6c6e2b3f0d6086c928'.
...

$ cd nginx
$ grep -Rn 'define NGINX_VERSION' src/core/nginx.h
13:#define NGINX_VERSION      "1.27.1"

Tìm từ khóa auto:

$ grep -Rin '"auto"'
...
src/core/nginx.c:1425:    if (ngx_strcmp(value[1].data, "auto") == 0) {
src/core/nginx.c:1566:    if (ngx_strcmp(value[1].data, "auto") == 0) {
...

Mở src/core/nginx.c tìm "auto" thấy:

static char *
ngx_set_worker_processes(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t        *value;
    ngx_core_conf_t  *ccf;

    ccf = (ngx_core_conf_t *) conf;

    if (ccf->worker_processes != NGX_CONF_UNSET) {
        return "is duplicate";
    }

    value = cf->args->elts;

    if (ngx_strcmp(value[1].data, "auto") == 0) {
        ccf->worker_processes = ngx_ncpu;
        return NGX_CONF_OK;
    }

    ccf->worker_processes = ngx_atoi(value[1].data, value[1].len);

    if (ccf->worker_processes == NGX_ERROR) {
        return "invalid value";
    }

    return NGX_CONF_OK;
}

nếu đọc từ config được giá trị là "auto", NGINX sẽ gán ccf->worker_processes = ngx_ncpu.

Tìm ngx_ncpu:

$ grep -Rn ngx_ncpu
src/os/win32/ngx_os.h:59:extern ngx_uint_t   ngx_ncpu;
src/os/win32/ngx_win32_init.c:14:ngx_uint_t  ngx_ncpu;
src/os/win32/ngx_win32_init.c:131:    ngx_ncpu = si.dwNumberOfProcessors;
src/os/unix/ngx_os.h:79:extern ngx_int_t    ngx_ncpu;
src/os/unix/ngx_posix_init.c:13:ngx_int_t   ngx_ncpu;
src/os/unix/ngx_posix_init.c:62:    if (ngx_ncpu == 0) {
src/os/unix/ngx_posix_init.c:63:        ngx_ncpu = sysconf(_SC_NPROCESSORS_ONLN);
src/os/unix/ngx_posix_init.c:67:    if (ngx_ncpu < 1) {
src/os/unix/ngx_posix_init.c:68:        ngx_ncpu = 1;
src/os/unix/ngx_freebsd_init.c:210:        ngx_ncpu = ngx_freebsd_hw_ncpu / 2;
src/os/unix/ngx_freebsd_init.c:213:        ngx_ncpu = ngx_freebsd_hw_ncpu;
src/os/unix/ngx_darwin_init.c:164:    ngx_ncpu = ngx_darwin_hw_ncpu;
...

thấy trên các hệ điều hành, NGINX sẽ lấy giá trị theo cách khác nhau. Trên "posix" như các Linux-based OS, NGINX gọi C function sysconf(_SC_NPROCESSORS_ONLN).

man sysconf

NAME
       sysconf - get configuration information at run time

SYNOPSIS
       #include <unistd.h>
...
- _SC_NPROCESSORS_ONLN
      The number of processors currently online (available).  See also get_nprocs_conf(3).

Viết 1 chương trình C 5 dòng để in ra giá trị này:

// main.c
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("online CPUs %ld\n", sysconf(_SC_NPROCESSORS_ONLN));
}

// $ cc main.c  # compile C code to a.out file
// $ ./a.out
// online CPUs 4

Dùng strace xem sysconf thực sự đọc từ đâu:

$ strace ./a.out 2>&1 | grep open
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/sys/devices/system/cpu/online", O_RDONLY|O_CLOEXEC) = 3
$ cat /sys/devices/system/cpu/online
0-3

Vậy NGINX lấy giá trị được tính bởi sysconf, đọc từ file /sys/devices/system/cpu/online.

Trong container, NGINX auto dùng mấy worker process?

Nếu dùng podman xem link này để có thể dùng option --cpus, docker không cần chỉnh gì:

--cpus float                               Number of CPUs. The default is 0.000 which means no limit

Trong container

$ podman run --cpus=1 -it docker.io/nginx bash

#root@1f334076d74f:/ nginx &
[1] 2
2024/08/28 13:19:04 [notice] 2#2: using the "epoll" event method
2024/08/28 13:19:04 [notice] 2#2: nginx/1.27.1
2024/08/28 13:19:04 [notice] 2#2: built by gcc 12.2.0 (Debian 12.2.0-14)
2024/08/28 13:19:04 [notice] 2#2: OS: Linux 6.8.0-40-generic
2024/08/28 13:19:04 [notice] 2#2: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2024/08/28 13:19:04 [notice] 3#3: start worker processes
2024/08/28 13:19:04 [notice] 3#3: start worker process 4
2024/08/28 13:19:04 [notice] 3#3: start worker process 5
2024/08/28 13:19:04 [notice] 3#3: start worker process 6
2024/08/28 13:19:04 [notice] 3#3: start worker process 7

[1]+  Done                    nginx
# apt update && apt install -y procps python
...
# ps xau | grep nginx
root           3  0.0  0.0  11404  1892 ?        Ss   13:19   0:00 nginx: master process nginx
nginx          4  0.0  0.0  11872  3044 ?        S    13:19   0:00 nginx: worker process
nginx          5  0.0  0.0  11872  3044 ?        S    13:19   0:00 nginx: worker process
nginx          6  0.0  0.0  11872  3044 ?        S    13:19   0:00 nginx: worker process
nginx          7  0.0  0.0  11872  3044 ?        S    13:19   0:00 nginx: worker process
# nproc
4
# cat /sys/devices/system/cpu/online
0-3

Nội dung file này được mang từ máy host vào, vì vậy dù container được set bao nhiêu CPU thì NGINX (hay các ngôn ngữ lập trình ví dụ Python) vẫn đọc giá trị là số CPU của máy host.

# python3
Python 3.11.2 (main, Aug 26 2024, 07:20:54) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.cpu_count()
4

Kết luận

NGINX hay các ngôn ngữ lập trình khi chạy trong container đếm số CPU của máy host, không phải giá trị CPU request/limit cấp cho container.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Friday, 2 August 2024

Docker image alpine/curl không phải alpine

Nếu cần chạy 1 câu lệnh curl (hay http client khác để truy cập web API) trên container, dùng image nào nhẹ nhất?

NOTE: thay podman bằng docker nếu dùng docker.

alpine rồi cài curl

alpine vốn phổ biến trong giới container vì nhẹ, nhưng không có sẵn curl, phải cài:

$ podman run -it alpine sh -c 'apk add curl>/dev/null; curl https://www.openbsd.org/robots.txt'
User-agent: *
Disallow: /cgi-bin/
Disallow: /donations.html

alpine/curl KHÔNG PHẢI alpine

Google alpine curl xem image alpine nào cài sẵn curl? thấy ngay kết quả top alpine/curl, và dùng có vẻ thành công:

$ podman run -it docker.io/alpine/curl sh -c 'curl https://www.openbsd.org/robots.txt'
Trying to pull docker.io/alpine/curl:latest...
Getting image source signatures
Copying blob 9f444ea7cf45 done
Copying blob 299588fda28b done
Copying blob c6a83fedfae6 done
Copying config d4f2de61cf done
Writing manifest to image destination
Storing signatures
User-agent: *
Disallow: /cgi-bin/
Disallow: /donations.html

NHƯNG alpine này là tên của 1 người dùng, không phải của hệ điều hành alpine. Nhờ cách đặt tên thông minh này mà tác giả đã khiến hàng trăm triệu lượt tải alpine/git

Không có ubuntu/curl

user ubuntu thuộc về tổ chức Canonical - công ty đứng sau Ubuntu chứ không phải 1 người dùng thông minh nào cả https://hub.docker.com/u/ubuntu

image chính thức của curl

https://hub.docker.com/r/curlimages/curl với hơn 1 tỷ lượt tải, based alpine

$ podman run -it docker.io/curlimages/curl cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.20.2
PRETTY_NAME="Alpine Linux v3.20"
$ podman run -it docker.io/curlimages/curl https://www.openbsd.org/robots.txt
User-agent: *
Disallow: /cgi-bin/
Disallow: /donations.html

busybox wget

busybox rất nhỏ, và có sẵn wget, wget khác curl https://daniel.haxx.se/docs/curl-vs-wget.html nhưng đủ tính năng để truy cập 1 HTTP API:

Option -O FILE Save to FILE ('-' for stdout)-q Quiet

$ podman run -it busybox wget -qO- https://www.openbsd.org/robots.txt
wget: note: TLS certificate validation not implemented
User-agent: *
Disallow: /cgi-bin/
Disallow: /donations.html

So sánh kích thước image

$ podman images
REPOSITORY                              TAG         IMAGE ID      CREATED        SIZE
docker.io/alpine/curl                   latest      d4f2de61cfdf  5 days ago     13.7 MB
docker.io/library/alpine                latest      a606584aa9aa  5 weeks ago    8.09 MB
docker.io/library/busybox               latest      65ad0d468eb1  14 months ago  4.5 MB
docker.io/curlimages/curl               latest      65019fbb78d5  2 days ago     21.9 MB

busybox nhỏ nhất. Nếu cần đủ tính năng curl, hãy dùng curlimages/curl.

Kết luận

Tránh bị "bất ngờ" vì alpine/curl hay alpine/git không đến từ alpine.

Ủng hộ tác giả 🍺

Thursday, 1 August 2024

Rust không import

Mọi ngôn ngữ lập trình hiện đại đều có cơ chế để dùng lại code. C có #include<stdio.h> thì Python, Go, Java có import. Rust không như thế, không có import, không cần import.

Rust dùng thư viện bằng path đầy đủ

Không cần import. Ví dụ sử dụng các function trong thư viện std::fs:

fn main() {
    std::fs::create_dir_all("a/b/c/d").unwrap();
    std::fs::write("a/file", "Hello").expect("Cannot write file");

    let rd = std::fs::read_dir("a").unwrap();
    for i in rd {
        let i = i.unwrap();
        if i.path().is_dir() {
            println!("Directory {}", i.file_name().to_string_lossy());
        } else if i.path().is_file() {
            println!("File {}", i.file_name().to_string_lossy());
        }
    }
}

Code tạo thư mục a chứa thư mục b chứa thư mục c chứa thư mục d, tương tự lệnh mkdir -p a/b/c/d trên Linux. Sau đó ghi ra 1 file text tên "file" trong thư mục "a" dòng chữ "Hello". Liệt kê các file trong thư mục "a" và in ra màn hình đâu là thư mục đâu là file.

Output:

File file
Directory b

Rust crate, module

std::fs::create_dir_allfull path tới function create_dir_all.

std là 1 crate /kreɪt/ - đơn vị 1 "library" trong Rust. Xem các crates tại https://crates.io/

fs là 1 module - thường tương ứng với 1 file hay 1 thư mục, xem các module trong crate std tại https://doc.rust-lang.org/std/index.html

Rust có thể gọi function create_dir_all trong fs trong std mà không cần import.

Nhưng gõ std::fs::create_dir_all dài, nên Rust có use dùng để tạo "shortcut".

use std::fs::create_dir_all;
// use std::fs::{create_dir_all, write}; viết ngắn gọn thay vì viết use cho từng function.
// use std::fs::{*}; tất cả
fn main() {
    create_dir_all("a/b/c/d").unwrap();
}

tương tự Python from math import sqrt; sqrt(4). hay from math import *; sqrt(4)

Nếu viết use std::fs thì sẽ chỉ cần gõ fs::create_dir_all là đủ. Đây là "best practice" để vừa gõ ngắn hơn, vừa biết create_dir_all thuộc về fs.

use std::fs;

fn main() {
    fs::create_dir_all("a/b/c/d").unwrap();
    fs::write("a/file", "Hello").expect("Cannot write file");

    let rd = fs::read_dir("a").unwrap();
    for i in rd {
        let i = i.unwrap();
        if i.path().is_dir() {
            println!("Directory {}", i.file_name().to_string_lossy());
        } else if i.path().is_file() {
            println!("File {}", i.file_name().to_string_lossy());
        }
    }
}

Kết luận

Rust không import, chỉ use để tạo shortcut.

Hết

Tham khảo

Ủng hộ tác giả 🍺