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 · 16 天前 · 3209 次点击

    在服务端一般使用 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  
       16 天前
    50mb 跟 node_modules 占用相比,就是皮毛
    huihuimoe
        2
    huihuimoe  
       16 天前 via iPad   ❤️ 2
    @CoverL 要抠冷启动速度的怎么可能有 node_modules ,都是打成 bundle 直接跑的啊
    lneoi
        3
    lneoi  
       16 天前   ❤️ 1
    挺有趣的,能搞的这么小,根据之前测试基于 QuickJS 的性能是没办法比上用 V8 的运行时了。
    rocmax
        4
    rocmax  
       16 天前 via Android   ❤️ 1
    1. 使用 alpine
    2. 独立的 build stage
    3. npm prune 去除多余依赖

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

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

    我觉得你可以把这些内容发到英文社区去,遇到问题的时候也可以在 issues 里和 llrt 的人讨论,这非常好,没必要来这里听一群水货质疑你做的东西的“意义”。
    kneo
        10
    kneo  
       16 天前
    路由器这种内存受限的环境上也许有点用。一般情况真用不着。
    FishBear
        11
    FishBear  
       16 天前
    nodejs 不需要 docker 吧 环境隔离我觉得没啥意义了
    0o0O0o0O0o
        12
    0o0O0o0O0o  
       16 天前
    @FishBear #11 OP 可能是部署在什么 serverless 容器平台上
    jqtmviyu
        13
    jqtmviyu  
       16 天前
    @kneo #10 但路由器内存受限就不太可能装 doker, 除了 opkg 里的那些编译好的. 需要大内存就上旁路由了.
    gouflv
        14
    gouflv  
       16 天前 via iPhone
    真是极端的需求,我应该会选择 go 重写
    kenberkeley
        15
    kenberkeley  
       15 天前 via iPhone
    @FishBear 正常的企业级应用都是容器化部署的。
    momo2789
        16
    momo2789  
       15 天前
    一开始用 alpine ,但后来就遇到坑了,alpine 没有使用标准的 linux 组件和标准的 glibc ,还有 DNS 不兼容的问题,在 K8s 集群里经常出现集群内网络错误。
    而且生产环境里,真的没有人在乎几百兆的区别吧。
    yzld2002
        17
    yzld2002  
       15 天前
    @momo2789 我也遇到过 alpine 镜像 DNS 的问题。。没想到使用面这么广的基础镜像还有这种问题
    Imindzzz
        18
    Imindzzz  
       15 天前   ❤️ 3
    @wolfsun 有时挺烦你们这些虚空打靶的。
    总共就几条评论,有支持有反对,反对的也都说明了理由。
    评论氛围非常好啊,你气愤个啥呢。
    和那个什么一样,一点都不能接受反对意见吗,
    nt0p
        19
    nt0p  
       15 天前 via iPhone
    都说 alpine 镜像 DNS 有问题、那么问题是啥呢?
    momo2789
        20
    momo2789  
       15 天前
    @yzld2002 可能是因为监控做的不够好或者请求没那么多,我们定位到问题后发现 GitHub 上有很多 issue ,后来 alpine 修复了问题,但还是因为不是标准的 glibc ,我们选择了 debian-slim 。
    momo2789
        21
    momo2789  
       15 天前   ❤️ 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 或更高版本。
    ```
    这个问题应该已经修复了
    mark2025
        23
    mark2025  
       15 天前
    @momo2789 nodejs 对于 dns 查询默认不缓存,可能会出现 dns 的 网络异常。
    mark2025
        24
    mark2025  
       15 天前
    @nt0p nodejs 对于 dns 查询默认不缓存,可能会出现 dns 的 网络异常。
    mark2025
        25
    mark2025  
       13 天前
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1008 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 19:09 · PVG 03:09 · LAX 12:09 · JFK 15:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.