乐趣区

关于前端:解析Nodejs镜像原理轻松构建高效CICD流程

​1. 前言

如果公司我的项目应用容器化部署,那么或多或少理解过 nodejs 镜像,因为前端我的项目或者基于 nodejs 的 BFF 我的项目在构建或者部署的过程中都会依赖 nodejs 镜像

有同学会有疑难,nodejs 镜像有啥好理解的,间接去 docker 镜像官网搜寻下对应 node 版本,而后找到对应的版本号,看下有没有不就好了嘛,比方要找 16.20.0,如下图所示

编辑

而后在 Dockerfile 内写上对应的镜像版本,如下所示

javascript

复制代码

FROM node:16.20.0

眼尖的同学可能会看到,同一个版本比方 16.20.0、14.19.1 有不同的 tag,如下图所示

为什么一个 node 镜像版本会有那么多 tag?我该怎么选

为什么 14 与 16 的 tag,除了版本号之外竟然还有差别?为什么

  1. 解析 nodejs 镜像

在理解 nodejs 镜像之前,如果对 linux 不太熟,咱们能够先理解 debian 是什么及 Alpine 又是什么,如果对这部分很相熟,能够间接跳到 nodejs 官网镜像形成

2.1 Debian 是什么

Linux 有十分多的发行版本,从性质上划分,大体分为由商业公司保护的商业版本与由开源社区保护的收费发行版本。商业版本以 Redhat 为代表,开源社区版本则以 Debian 为代表。这些版本各有不同的特点,在不同的应用领域施展着不同的作用。

目前的风行发行版如下所示

一般来说 Debian 作为适宜于服务器的操作系统,它比 Ubuntu 要稳固得多。Debian 整个零碎,只有利用层面不呈现逻辑缺点,基本上不会出问题。Debian 整个零碎根底外围十分小,不仅稳固,而且占用硬盘空间小,占用内存小。

更多内容能够查看 CentOS、Ubuntu、Debian 三个 linux 比拟异同

Debian 发行版本 Debian 始终保护着至多三个发行版本:稳定版(stable),测试版(testing)和不稳定版(unstable)。


稳定版(stable): 稳定版蕴含了 Debian 官网最近一次发行的软件包。作为 Debian 的正式发行版本,它是咱们优先举荐给用户您选用的版本。以后 Debian 的稳定版版本号是 12,开发代号为 bookworm。最后版本为 12.0,于 2023 年 06 月 10 日 公布,其更新 12.1 已于 2023 年 07 月 22 日 公布。测试版(testing): 测试版蕴含了那些临时未被收录进入稳定版的软件包,但它们曾经进入了候选队列。应用这个版本的最大好处在于它领有更多版本较新的软件。以后的测试版版本代号是 trixie。不稳定版(unstable): 不稳定版寄存了 Debian 现行的开发工作。通常,只有开发者和那些喜爱过惊险刺激生存的人选用该版本,不稳定版的版本代号永远都被称为 sid。

Debian 发行生命周期及目录 Debian 通常会依照肯定的法则每隔一段时间公布一个新稳定版。对每个稳固发行版本,用户能够失去三年的残缺反对以及额定两年的长期反对。目前的发行工夫线如下图所示

能够看到最新的稳固版本是 12

2.2 Alpine 又是什么

Alpine 是一个面向平安的轻型 Linux 发行版。它不同于通常 Linux 发行版,Alpine 采纳了 musl libc 和 busybox 以减小零碎的体积和运行时资源耗费,但性能上比 busybox 又欠缺的多,因而失去开源社区越来越多的青眼。在放弃瘦身的同时,Alpine 还提供了本人的包管理工具 apk,能够间接通过 apk 命令间接查问和装置各种软件。

2.3 Debian 与 Alpine 的差别


空间大小差别,alpine 默认 5M 左右,debian 等都在 200M 左右。默认软件包差别,alpine 选用 busybox,debian 等则是 bash+coreutils 几件套。alpine 中,国际化组件被优化掉了。还有一点,alpine 中选用的都是“最简依赖”,这点和 archlinux 比拟像,举个例子,openssh 包不会自带 pam 插件,于是他也就不反对 ldap。这点我给 alpinelinux 官网提过 issue。和 php 不一样,php 能够做成 php-pdo,php-dom 的包,而后动静加载共享库。openssh 不行,“没带就是没写”。glibc 差别,alpine 选用 musl,centos 等选用 glibc,其余的倒还好,libc 的差别对开发很重要。

更多内容能够查看 Alpine Linux 与 CentOS 有什么区别?

2.4 nodejs 官网镜像形成

当初再来看 nodejs 镜像组成


node:<version>:基于 Debian 最新版本,依赖工具包 buildpack-deps,最根底的镜像,提供了最多最罕用的 Debian 软件包,比方 curl、bash、git 等


node:alpine:基于风行的 Alpine Linux 我的项目,提供最小的 node 镜像,没有内置罕用的软件包,须要通过 apk 进行装置,另外应用 musl libc 代替了 glibc, 只有对依赖了 glibc 的包有影响比方 grpc 之类的


node:buster:基于 Debian10 版本的镜像,依赖工具包 buildpack-deps


node:bullseye:基于 Debian11 版本的镜像,依赖工具包 buildpack-deps


node:bookworm:基于 Debian12 版本的镜像,依赖工具包 buildpack-deps


node:slim:基于 Debian 的以后版本,只蕴含可能让镜像跑起来的最根底的包,比方蕴含 bash、git,然而不蕴含 curl 等


当然当前还会有基于新的 Debian 版本的镜像

镜像内是否蕴含某个工具包,能够间接在镜像的详情页内搜寻,如下图所示

镜像整体构造组成如下图所示

不同 tag 的镜像区别如下,以 16.20.0(arm64) 为例


    
        镜像 tag
        node 版本
        包数量
        系统漏洞数
        镜像大小
        是否蕴含 yarn
    


    
        node
        16.20.0
        751
        230
        856MB
        ✅
    
    
        node:bullseye
        16.20.0
        751
        121
        890MB
        ✅
    
    
        node:bullseye-slim
        16.20.0
        347
        26
        184MB
        ✅
    
    
        node:bookworm
        16.20.0
        750
        92
        1.04GB
        ✅
    
    
        node:bookworm-slim
        16.20.0
        334
        15
        206MB
        ✅
    
    
        node:alpine
        16.20.0
        230
        4
        116MB
        ✅
    


从上表能够得出


alpine 与 slim 体积最小,bullseye、bookworm 及 node:version 镜像体积绝对 alpine 与 slim 都大上来好几倍


alpine 与 slim 的破绽数绝对起码,bullseye、bookworm 及 node:version 破绽数绝对 alpine 与 slim 多上好几倍


那么 node:version 到底对应哪个版本,能够通过 versions.json 文件查看,以 16.20.0 版本为例,那么 node:16.20.0 其实就是 node:16.20.0-buster

另外也能够从镜像的组成看,如下图所示

到这里咱们应该能够答复,后面的两个问题

为什么一个 node 镜像版本会有那么多 tag?我该怎么选

起因是: nodejs 基于不同的 linux 发型版本构建进去的镜像,能够依据镜像体积、性能来进行抉择

为什么 14 与 16 的 tag,除了版本号之外竟然还有差别?为什么

起因是:Debian 始终在公布新的版本,而 nodejs 只对最新的在保护的 nodejs 版本公布基于新的 Debian 版本的镜像

  1. 怎么抉择 nodejs 镜像

从下面咱们曾经晓得了各个 tag 的含意,及对应 tag 镜像的组成,那么咱们能够依据本人的应用场景进行抉择

3.1 web 我的项目

构建 web 我的项目,举荐 alpine 镜像,起因是咱们在构建 web 我的项目时候,个别只依赖构建工具 webpack 等,不依赖一些底层的工具库,个别也不须要进入容器内,具体示例如下所示

dockerfile

复制代码

FROM node:16.20.0-alpine AS builder_web

WORKDIR /app

ADD ./package.json /app/package.json
ADD ./pnpm-lock.yaml /app/pnpm-lock.yaml

装置依赖

RUN pnpm install –frozen-lockfile

ADD . /app

构建代码

RUN pnpm build

FROM nginx:1.21.0

CDN 同步脚本默认从 /app/dist 目录读文件

COPY –from=builder_web /app/dist /app/dist
COPY –from=builder_web /app/config/nginx.conf /etc/nginx/conf.d/default.conf

3.2 BFF 我的项目

构建基于 nodejs 的 BFF 我的项目,构建阶段举荐应用 alpine 镜像,运行部署阶段举荐 slim 镜像,起因镜像内蕴含一些根底的第三方工具包,不便咱们进容器的时候在进行二次装置

dockerfile

复制代码

构建阶段能够应用 alpine

FROM node:16.20.0-alpine as bff_build

工作目录

WORKDIR /workspace/app

拷贝依赖文件

COPY ./package.json /workspace/app/package.json
COPY ./yarn.lock /workspace/app/yarn.lock

装置依赖

RUN yarn –frozen-lockfile –check-files

拷贝源码

COPY . /workspace/app

构建

RUN yarn run build

部署阶段根底镜像,应用 slim 镜像

FROM node:16.20.0-slim

工作目录

WORKDIR /workspace/app

拷贝依赖

COPY –from=bff_build /workspace/app/package.json /workspace/app/package.json
COPY –from=bff_build /workspace/app/node_modules /workspace/app/node_modules

拷贝内容

COPY –from=bff_build /workspace/app /workspace/app

启动

CMD yarn run run

3.3 npm 包场景

公布 npm 包,以 gitlab-ci 为例,举荐 alpine or slim 镜像,起因是不依赖工具包,然而碰到 lerna 这样的多包管理工具时,因为依赖 git,能够在现有 alpine or slim 镜像的根底上线装置 git

yaml

复制代码

stages:

  • publish

before_script:

  • echo $CI_COMMIT_TAG

publish:
stage: publish
image: node:16.20.0-slim
script:

  • echo -e $NPM_AUTH_CONTENT >> ~/.npmrc # 注入私仓发包 token
  • yarn install –frozen-lockfile
  • |
    if [[$CI_COMMIT_TAG == “beta” ]]; then
    echo ‘ 公布 beta tag’
    yarn publish –tag beta –non-interactive –no-commit-hooks
    else
    echo ‘ 公布正式 tag’
    yarn publish –non-interactive –no-commit-hooks
    fi
    only:
    refs:
  • tags

当然还有更多的场景,然而只有依据每个 tag 的个性,就能选出适宜本人我的项目的 tag

  1. 自定义 nodejs 镜像

除了间接应用官网的 nodejs 镜像,其实咱们也可能会本人封装适宜本人公司我的项目的 nodejs 镜像,其目标都是为了在镜像内减少定制逻辑,不便对立解决公司所有我的项目的通用问题,比方设置公司的 npm 代理源,设置一些罕用的 npm 包第三方依赖变量,装置 pnpm,解决 install 及 build 过程中的因为 buildkit 缓存谬误导致的构建失败等,封装有两种思路


思路 1: 从零开始封装


思路 2: 基于官网镜像进行二次封装


4.1 从零封装本人的 nodejs 镜像

以 alpine 镜像为例

dockerfile

复制代码

抉择 alpine 版本镜像

FROM alpine:3.18

定义 node 版本

ENV NODE_VERSION 16.20.0

设置 apk 的源为阿里源

RUN sed -i ‘s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g’ /etc/apk/repositories && apk update

设置源及设置一些国内不好下载的二进制依赖文件 host

COPY ./.npmrc /root/.npmrc

应用 apk 来进行装置依赖

RUN addgroup -g 1000 node \
&& adduser -u 1000 -G node -s /bin/sh -D node \
&& apk add –no-cache \
libstdc++ \
&& apk add –no-cache –virtual .build-deps \
curl \
&& ARCH= && alpineArch=”$(apk –print-arch)” \
&& case “${alpineArch##*-}” in \
x86_64) \
ARCH=’x64′ \
CHECKSUM=”d2df78a192bd78b958e19a77821916a38def5e9e46c0c9a0989fdf5eb6c14a7e” \
;; \
*) ;; \
esac \
&& if [-n “${CHECKSUM}” ]; then \
set -eu; \
curl -fsSLO –compressed “https://unofficial-builds.nodejs.org/download/release/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz”; \
echo “$CHECKSUM node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz” | sha256sum -c – \
&& tar -xJf “node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz” -C /usr/local –strip-components=1 –no-same-owner \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs; \
else \
echo “Building from source” \

backup build

&& apk add –no-cache –virtual .build-deps-full \
binutils-gold \
g++ \
gcc \
gnupg \
libgcc \
linux-headers \
make \
python3 \

use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150

&& export GNUPGHOME=”$(mktemp -d)” \

gpg keys listed at https://github.com/nodejs/node#release-keys

&& for key in \
4ED778F539E3634C779C87C6D7062848A1AB005C \
141F07595B7B3FFE74309A937405533BE57C7D57 \
74F12602B6F1C4E913FAA37AD3A89613643B6201 \
DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \
61FC681DFB92A079F1685E77973F295594EC4689 \
8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 \
C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \
C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \
108F52B48DB57BB0CC439B2997B01419BD92F80A \
; do \
gpg –batch –keyserver hkps://keys.openpgp.org –recv-keys “$key” || \
gpg –batch –keyserver keyserver.ubuntu.com –recv-keys “$key” ; \
done \
&& curl -fsSLO –compressed “https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.xz” \
&& curl -fsSLO –compressed “https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc” \
&& gpg –batch –decrypt –output SHASUMS256.txt SHASUMS256.txt.asc \
&& gpgconf –kill all \
&& rm -rf “$GNUPGHOME” \
&& grep ” node-v$NODE_VERSION.tar.xz\$” SHASUMS256.txt | sha256sum -c – \
&& tar -xf “node-v$NODE_VERSION.tar.xz” \
&& cd “node-v$NODE_VERSION” \
&& ./configure \
&& make -j$(getconf _NPROCESSORS_ONLN) V= \
&& make install \
&& apk del .build-deps-full \
&& cd .. \
&& rm -Rf “node-v$NODE_VERSION” \
&& rm “node-v$NODE_VERSION.tar.xz” SHASUMS256.txt.asc SHASUMS256.txt; \
fi \
&& rm -f “node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz” \
&& apk del .build-deps \

smoke tests

&& node –version \
&& npm –version

ENV YARN_VERSION 1.22.19

RUN apk add –no-cache –virtual .build-deps-yarn curl gnupg tar \

use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150

&& export GNUPGHOME=”$(mktemp -d)” \
&& for key in \
6A010C5166006599AA17F08146C2130DFD2497F5 \
; do \
gpg –batch –keyserver hkps://keys.openpgp.org –recv-keys “$key” || \
gpg –batch –keyserver keyserver.ubuntu.com –recv-keys “$key” ; \
done \
&& curl -fsSLO –compressed “https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz” \
&& curl -fsSLO –compressed “https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz.asc” \
&& gpg –batch –verify yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
&& gpgconf –kill all \
&& rm -rf “$GNUPGHOME” \
&& mkdir -p /opt \
&& tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \
&& ln -s /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \
&& ln -s /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \
&& rm yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
&& apk del .build-deps-yarn \

smoke test

&& yarn –version

装置 pnpm 依赖

RUN npm install -g pnpm

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT [“docker-entrypoint.sh”]

CMD [“node”]
 

其实就是参照官网镜像的构建形式,在外面进行适当的批改

当然还能够基于其它某个 linux 发行版来进行封装,如下所示

yaml

复制代码

FROM centos:7
RUN curl -L https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo
RUN curl –silent –location https://rpm.nodesource.com/setup_14.x | bash –
RUN yum install -y nodejs yarn
WORKDIR /code
EXPOSE 80
CMD npm start

4.2 基于官网镜像二次封装

以 slim 镜像为例

dockerfile

复制代码

FROM node:16.20.0-slim

RUN sed -i s/deb.debian.org/archive.debian.org/g /etc/apt/sources.list && \
sed -i ‘s|security.debian.org|archive.debian.org|g’ /etc/apt/sources.list && \
sed -i ‘/stretch-updates/d’ /etc/apt/sources.list && \
apt-get clean && \
apt-get update && \
apt-get install -y tzdata tree git && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

ARG CUSTOM_NODE_VERSION

COPY ./.npmrc /root/.npmrc

RUN npm install -g pnpm@6.32.3 \
&& pnpm config set store-dir /root/.pnpm-store \
&& npm config set custom_node_version $CUSTOM_NODE_VERSION

这样的话,封装起来清新一些

至于本人封装的 nodejs 镜像能够发到本人的私仓,比方 Nexus 搭建的私仓,或者阿里云、腾讯云上买的私仓等

当然在封装的时候如果咱们要装置一些额定的工具包,因为国内网络的问题,所以咱们个别会将 debian 的源设置成国内源

  1. 总结

nodejs 官网镜像次要由三局部组成 linux 版本 + 工具包合集 + nodejs 运行时,三局部能够组合成不同的 tag,每种组合都有其利用的场景,咱们能够依据大小、平安、性能来进行抉择

个别我的项目举荐应用:alpine or slim 镜像,不举荐应用残缺镜像非凡我的项目举荐依据 alpine or slim 镜像进行二次封装,解决公司的通用问题,进步研发效率

像咱们公司就抉择封装本人的 nodejs 镜像,会在镜像内做如下事件


设置公司本人的 npm 代理源


设置 npm 包的第三方依赖环境变量,比方 node-sass、puppeteer 等


装置 pnpm


解决 CI 场景因为 buildkit 缓存问题导致的 install 失败或者 build 失败


bff 启动之前的前置查看等


当然如果对镜像大小谋求极致的,能够在去删减镜像内的工具包,或者通过 copy 的形式只拷贝工具的执行文件等形式去缩减镜像大小

最初通过抉择适合的镜像,能够帮忙咱们构建高效的 CI/CD 流程

参考链接

如何查看 Debian 版本

Debian 发行版本

Debian 源应用帮忙

Alpine Linux 与 CentOS 有什么区别?

Choosing the best Node.js Docker image

退出移动版