如何实现一个小体积的 js docker 镜像 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
llej
V2EX    Javascript

如何实现一个小体积的 js docker 镜像

  •  2
     
  •   llej 2024-08-31 16:38:07 +08:00 6289 次点击
    这是一个创建于 408 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在服务端一般使用 node 来运行 js ,除了 node 外流行的还有 bun/deno 。

    但这三个运行时的打包体积都不小,在精简的情况下也在 50 mb 以上,我在这里记录一下我是如何将一个原来使用 node 开发的服务迁移为 3.78MB 的 docker 镜像。

    image-20240831145324-mzsh9nq

    https://hub.docker.com/layers/llej0/web-font/latest

    选择 js 运行时 (llrt

    要实现这么小的镜像肯定不能再使用 node 这种等级的 js 运行时了,现在最流行的轻量级 js 运行时可以锁定为 QuickJS

    我要迁移的项目是我之前写的一个字体裁剪工具 web-font , 它除了纯 js 的部分外还涉及到文件读写和 http server 部分的 api ,QuickJS 作为纯粹的解释器是没有这方面的 api 的。

    现有的比较成熟的基于 QuickJS 实现的微型 js 运行时有 txiki.jsllrt , 经过实践发现 llrt 可以完美运行在 docker 中 ,txiki.js 则没那么方便 (按照文档编译出来的 tjs 还会依赖其他库)

    所以我选择使用 llrt 来作为运行时。

    迁移遇到的问题

    主要问题是 llrt 没有提供 http 模块( tixiki.js 也是), 幸运的是它提供了 net 模块

    所以我基于 net.createServer 手搓了一个简易 http 服务和洋葱路由 server.ts

    这期间还发现了 llrt 一个 cpu 占用异常:https://github.com/awslabs/llrt/issues/546

    打包微小体积的 docker 镜像

    1. 代码打包

    这方面我使用的是 tsup 将 ts 源码打包为一个 js 文件。

    然后使用 llrt compile 命令将 js 文件编译为 .lrt 文件(这一步也能减少差不多 30%的体积)

    2. Dockerfile

    得益于 llrt ,可以不用依赖任何环境,直接使用 FROM scratch 来得到最小的 docker 镜像体积

    FROM scratch WORKDIR /home/ COPY dist_backend/app.lrt /home/app.lrt COPY llrt /home/llrt COPY dist/ /home/dist/ CMD ["/home/llrt", "/home/app.lrt"] 

    再经过 docker 的压缩后就得到了 3.78MB 这个数字。

    使用情况

    llrt 的运行速度比 node 还是慢了许多,在我这个场景下它比 node 要慢上两倍,gc 的运行速度也要慢许多。

    但初始内存占用和启动速度是碾压 node 的。

    由于运行时还不是特别完善的问题,很容易踩坑,所以除非你急需压缩 js 的运行内存占用/冷启动速度或者和我一样就是想要这么做,还是建议直接使用 node 吧。

    25 条回复    2024-09-03 17:22:54 +08:00
    CoverL
        1
    CoverL  
       2024-08-31 17:16:58 +08:00
    50mb 跟 node_modules 占用相比,就是皮毛
    huihuimoe
        2
    huihuimoe  
       2024-08-31 17:23:33 +08:00 via iPad   2
    @CoverL 要抠冷启动速度的怎么可能有 node_modules ,都是打成 bundle 直接跑的啊
    lneoi
        3
    lneoi  
       2024-08-31 17:30:05 +08:00   1
    挺有趣的,能搞的这么小,根据之前测试基于 QuickJS 的性能是没办法比上用 V8 的运行时了。
    rocmax
        4
    rocmax  
       2024-08-31 17:31:06 +08:00 via Android   1
    1. 使用 alpine
    2. 独立的 build stage
    3. npm prune 去除多余依赖

    以上足够了
    llej
        5
    llej  
    OP
       2024-08-31 17:34:49 +08:00 via Android
    @rocmax 这个的优势在于微小程序,能用 node 最好
    jlak
        6
    jlak  
       2024-08-31 17:46:29 +08:00 via iPhone
    我单个二进制 Go 项目基础容量都有 30MB+了
    你嫌 50MB node 项目大…
    povsister
        7
    povsister  
       2024-08-31 17:50:04 +08:00
    容器本身就是分层的,取一个 node v8 runtime 的 base 镜像,大量服务是可以共用的。
    裁剪 runtime 意义不大。
    llej
        8
    llej  
    OP
       2024-08-31 17:57:51 +08:00 via Android
    @povsister node 启动所占用的内存是不会随着分层减少的
    wolfsun
        9
    wolfsun  
       2024-08-31 18:06:46 +08:00   15
    楼主放心做你想做的事,这里总有人会“教你做事”,一定要说你这么做没意义如何如何。

    但是为什么要有意义呢?这些各种姿势贬低你的人,又有谁比 aws 的人水平更高?

    我觉得你可以把这些内容发到英文社区去,遇到问题的时候也可以在 issues 里和 llrt 的人讨论,这非常好,没必要来这里听一群水货质疑你做的东西的“意义”。
    kneo
        10
    kneo  
       2024-08-31 19:48:45 +08:00
    路由器这种内存受限的环境上也许有点用。一般情况真用不着。
    FishBear
        11
    FishBear  
       2024-08-31 19:50:53 +08:00
    nodejs 不需要 docker 吧 环境隔离我觉得没啥意义了
    0o0O0o0O0o
        12
    0o0O0o0O0o  
       2024-08-31 20:04:08 +08:00
    @FishBear #11 OP 可能是部署在什么 serverless 容器平台上
    jqtmviyu
        13
    jqtmviyu  
       2024-08-31 21:10:41 +08:00
    @kneo #10 但路由器内存受限就不太可能装 doker, 除了 opkg 里的那些编译好的. 需要大内存就上旁路由了.
    gouflv
        14
    gouflv  
       2024-08-31 23:50:52 +08:00 via iPhone
    真是极端的需求,我应该会选择 go 重写
    kenberkeley
        15
    kenberkeley  
       2024-09-01 06:14:04 +08:00 via iPhone
    @FishBear 正常的企业级应用都是容器化部署的。
    momo2789
        16
    momo2789  
       2024-09-01 09:38:12 +08:00
    一开始用 alpine ,但后来就遇到坑了,alpine 没有使用标准的 linux 组件和标准的 glibc ,还有 DNS 不兼容的问题,在 K8s 集群里经常出现集群内网络错误。
    而且生产环境里,真的没有人在乎几百兆的区别吧。
    yzld2002
        17
    yzld2002  
       2024-09-01 10:01:28 +08:00
    @momo2789 我也遇到过 alpine 镜像 DNS 的问题。。没想到使用面这么广的基础镜像还有这种问题
    Imindzzz
        18
    Imindzzz  
       2024-09-01 10:02:41 +08:00   3
    @wolfsun 有时挺烦你们这些虚空打靶的。
    总共就几条评论,有支持有反对,反对的也都说明了理由。
    评论氛围非常好啊,你气愤个啥呢。
    和那个什么一样,一点都不能接受反对意见吗,
    nt0p
        19
    nt0p  
       2024-09-01 10:11:05 +08:00 via iPhone
    都说 alpine 镜像 DNS 有问题、那么问题是啥呢?
    momo2789
        20
    momo2789  
       2024-09-01 10:11:54 +08:00
    @yzld2002 可能是因为监控做的不够好或者请求没那么多,我们定位到问题后发现 GitHub 上有很多 issue ,后来 alpine 修复了问题,但还是因为不是标准的 glibc ,我们选择了 debian-slim 。
    momo2789
        21
    momo2789  
       2024-09-01 10:15:27 +08:00   1
    @nt0p https://stackoverflow.com/questions/65181012/does-alpine-have-known-dns-issue-within-kubernetes

    还有 https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/dns-debugging-resolution/
    ```
    如果你使用 Alpine 3.17 或更早版本作为你的基础镜像,DNS 可能会由于 Alpine 的设计问题而无法工作。 在 musl 1.24 版本之前,DNS 存根解析器都没有包括 TCP 回退, 这意味着任何超过 512 字节的 DNS 调用都会失败。请将你的镜像升级到 Alpine 3.18 或更高版本。
    ```
    这个问题应该已经修复了
    guanzhangzhang
        22
    guanzhangzhang  
       2024-09-01 13:58:14 +08:00   2
    mark2025
        23
    mark2025  
       2024-09-01 15:56:07 +08:00
    @momo2789 nodejs 对于 dns 查询默认不缓存,可能会出现 dns 的 网络异常。
    mark2025
        24
    mark2025  
       2024-09-01 15:59:21 +08:00
    @nt0p nodejs 对于 dns 查询默认不缓存,可能会出现 dns 的 网络异常。
    mark2025
        25
    mark2025  
       2024-09-03 17:22:54 +08:00
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2974 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 13:25 PVG 21:25 LAX 06:25 JFK 09:25
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86