從封裝 Nginx NJS 工具鏡像聊起
最近發現有不少需求可以通過 Nginx JavaScript (NJS)來完成,相比較運行一套完整的 Web 服務來說,輕量高效的方案總是惹人喜愛,更何況這套方案是由 Nginx 官方團隊推出,並搭上了繁榮的 JavaScript 生態。
本篇文章先從 NJS 容器封裝、以及容器鏡像優化來聊聊。
寫在前面
NJS 目前還處於相對早期的版本,截止本篇文章發佈,官方最新的版本是 0.5.0,官網並沒有二進制文件可以下載,軟件隨 Nginx 應用的各版本軟件包提供,目前並未獨立提供。
不過爲了更方便的進行腳本調試,能夠使用顯式聲明的使用 NJS 的運行時,我創建了一個開源項目,包含了 NJS 目前的主要版本的容器鏡像:https://github.com/soulteary/docker-njs。
相比較官方鏡像動輒 20MB 來說,最小的版本不到 1MB,更小的尺寸帶來的是更輕量和快速的體驗。如果你想獲取最新的鏡像,可以訪問 DockerHub 官方倉庫。
下面來聊聊如何針對 NJS 進行鏡像封裝以及過程中的一些思考。
基於官方鏡像進行鏡像構建
構建 NJS 鏡像的最簡單的方式是從官方容器中直接提取我們所需要的可執行文件。況且,相對於自行編譯,官方構建產物更讓人用的放心一些。
通過分析發現 NJS 依賴 libpcre、libedit、libncursesw,所以除了將 njs 的 bin 文件提取之外,還需要將上述依賴庫進行拷貝 。
以最新版本的 NJS 封裝爲例:
FROM nginx:1.19.6-alpine AS builder
FROM alpine:3.12
COPY --from=builder /usr/bin/njs /usr/bin/njs
COPY --from=builder /usr/lib/libpcre.so.1.2.12 \
/usr/lib/libedit.so.0.0.63 \
/usr/lib/libncursesw.so.6.2 \
/usr/lib/
RUN ln -sf /usr/lib/libpcre.so.1.2.12 /usr/lib/libpcre.so.1 && \
ln -sf /usr/lib/libedit.so.0.0.63 /usr/lib/libedit.so.0 && \
ln -sf /usr/lib/libncursesw.so.6.2 /usr/lib/libncursesw.so.6
ENTRYPOINT [ "njs" ]
這樣一個基礎的 NJS 鏡像就構建好了。
針對不同版本進行構建
常常使用容器的小夥伴都知道 Nginx 官方提供了 Alpine / Debian 兩個版本的鏡像,而 NJS 目前也有三個小版本:0.3.x / 0.4.x 以及最新的 0.5.x,而這幾個版本對於上述依賴庫的版本、以及基礎 Nginx 依賴都略有不同。
爲了減少代碼重複,以及提高代碼可維護性,可以將不同版本的依賴單獨聲明爲 .env 配置文件,然後搭配一個抽象度比較高的容器配置文件,對多個版本進行構建。以0.5.0 的 NJS 爲例:
DIST_OS=debian:10
NGX_VER=1.19.6
PCRE_VER=1.2.12
EDIT_VER=0.0.63
CURSESW_VER=6.2
將上面的文件保存爲 .env 保存至 njs/0.5.0/.env,接着開始編寫 Dockerfile:
ARG DIST_OS=alpine:3.12
ARG NGX_VER=1.19.6-alpine
FROM "nginx:$NGX_VER" AS builder
FROM "$DIST_OS"
COPY --from=builder /usr/bin/njs /usr/bin/njs
COPY --from=builder /usr/lib/libpcre.so.* \
/usr/lib/libedit.so.* \
/usr/lib/libncursesw.so.* \
/usr/lib/
RUN ls /usr/lib/libpcre.so.*.* | xargs -I {} ln -sf {} $(echo {} | cut -b 1-21) && \
ls /usr/lib/libedit.so.*.* | xargs -I {} ln -sf {} $(echo {} | cut -b 1-21) && \
ls /usr/lib/libncursesw.so.*.* | xargs -I {} ln -sf {} $(echo {} | cut -b 1-25)
ENTRYPOINT [ "njs" ]
其他幾個版本也可以如法炮製,最終整個項目結構如下:
├── Dockerfile
├── LICENSE
├── README.md
├── docker-build.sh
├── docker-slim.sh
└── njs
├── 0.3.9
├── 0.3.9-alpine
├── 0.4.4
├── 0.4.4-alpine
├── 0.5.0
└── 0.5.0-alpine
爲了能夠自動化的構建各個版本的 NJS 鏡像,我們需要編寫一個 BASH 腳本:
#!/bin/bash
RELEASE_DIR='./njs';
REPO_NAME='soulteary/docker-njs'
for njs_ver in $RELEASE_DIR/*; do
tag=$(echo $njs_ver | cut -b 7-);
echo "Build: $tag";
set -a
. "$njs_ver/.env"
set +a
docker build --build-arg DIST_OS=$DIST_OS --build-arg NGX_VER=$NGX_VER --build-arg PCRE_VER=$PCRE_VER --build-arg EDIT_VER=$EDIT_VER --build-arg CURSESW_VER=$CURSESW_VER --tag $REPO_NAME:$tag .
done
將上面的內容保存爲 make-image.sh,然後執行它之後就能得到各個版本的鏡像了。
REPOSITORY TAG IMAGE ID CREATED SIZE
njs 0.5.0-alpine 0f6e379160a1 About a minute ago 8.07MB
njs 0.5.0 80071066f7ca About a minute ago 115MB
njs 0.4.4-alpine 5b9fb7872be3 About a minute ago 8.06MB
njs 0.4.4 d6663992a6ec About a minute ago 115MB
njs 0.3.9-alpine 155e2a710c02 About a minute ago 7.97MB
njs 0.3.9 7a041ccd4f86 About a minute ago 101MB
使用 docker-slim 優化鏡像尺寸
上文構建完畢的鏡像尺寸略大了一些,可以藉助 Docker Slim 進行精簡。下載Docker Slim 後,使用命令對原有鏡像進行二次構建,即可構建出新的小巧的鏡像:
docker-slim build --target soulteary/njs:0.5.0 --tag soulteary/njs:0.5.0-slim --http-probe=false
爲了減少後續維護成本,我們可以和之前構建不同版本 NJS 一樣,準備一個 slim.sh 腳本,簡化後續操作:
#!/bin/bash
RELEASE_DIR='./njs';
REPO_NAME='soulteary/docker-njs'
for njs_ver in $RELEASE_DIR/*; do
tag=$(echo $njs_ver | cut -b 7-);
echo "Build: $tag";
set -a
. "$njs_ver/.env"
set +a
docker-slim build --target $REPO_NAME:$tag --tag $REPO_NAME:$tag-slim --http-probe=false
done
腳本執行完畢,可以看到本地鏡像尺寸有了大幅的減少,如果我們推送到 DockerHub,官方鏡像倉庫會對鏡像進一步壓縮,最終最小的鏡像尺寸會在 1MB 以內,非常利於快速啓動,進行調試。
REPOSITORY TAG IMAGE ID CREATED SIZE
njs 0.5.0-alpine-slim 9cd49bb22a26 About a minute ago 2.17MB
njs 0.5.0-slim 766a3f6ef92b About a minute ago 2.96MB
njs 0.4.4-alpine-slim 2f401dee2bd6 About a minute ago 2.16MB
njs 0.4.4-slim d62a8af54253 About a minute ago 2.96MB
njs 0.3.9-alpine-slim 5dc7a6799f66 About a minute ago 2.03MB
njs 0.3.9-slim 32b0ec660c4b About a minute ago 2.28MB
生成批量推送鏡像腳本
一次性生成多個鏡像之後,如果是手動推送到 DockerHub 其實挺繁瑣的,這個時候可以使用Docker Image 命令 來“偷懶”:
docker images soulteary/docker-njs --format "docker push {{.Repository}}:{{.Tag}}"
使用格式參數,可以快速生成帶 docker push 的命令,然後不論是在命令行中通過管道符執行,還是保存爲文件執行,都可以做到批量推送鏡像啦:
docker push soulteary/docker-njs:0.5.0-alpine-slim
docker push soulteary/docker-njs:0.5.0-slim
docker push soulteary/docker-njs:0.5.0-alpine
docker push soulteary/docker-njs:0.5.0
其他
Nginx 在去年十二月發佈了主線版本的例行更新,版本升級到了 1.19.6,官方對於本次升級只有聊聊數語,未曾提到 NJS 相關的事情:
Changes with nginx 1.19.6 15 Dec 2020
*) Bugfix: "no live upstreams" errors if a "server" inside "upstream"
block was marked as "down".
*) Bugfix: a segmentation fault might occur in a worker process if HTTPS
was used; the bug had appeared in 1.19.5.
*) Bugfix: nginx returned the 400 response on requests like
"GET http://example.com?args HTTP/1.0".
*) Bugfix: in the ngx_http_flv_module and ngx_http_mp4_module.
Thanks to Chris Newton.
但是因爲折騰 NJS 運行時鏡像,發現了這個隱藏在主版本更新日誌外的變更,發現了這個時隔三個月大量更新的 0.5.0。
最後
我創建了一個名爲 njs-learning-materials 的開源倉庫,目前已經整理了 NJS 相關的一些開源參考資料,後續會隨着更深入的折騰,不斷更新和補充內容。
如果你感興趣的話,歡迎加入我一起折騰。
--EOF