关于前端:解析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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理