Bun技术评估 - 29 Docker集成

概述

本文是笔者的系列博文 《Bun技术评估》 中的第二十九篇。

在本文的内容中,笔者主要想要来探讨一下Bun中和Docker集成开发和执行的相关的问题。

为什么会有这个问题? 因为笔者在实际工作中就遇到了相关的情况,就导致了有这么一个需求。

问题和需求

我们在前面的讨论中,应该已经能够体会和理解到Bun的易用性是非常出色的。它和核心就是一个单一的可执行文件,非常方便部署和迁移。一般情况下,只需要直接将兼容操作系统和架构的Bun文件复制到系统默认执行文件路径(如Linux系统中的 /usr/local/bin) 中就可以了。然后在系统任意地方,使用任意的账号,都可以来执行这个bun文件,当然就能够执行对应的bun项目文件了。

但是,笔者在现实情况下,恰恰就遇到了由于兼容性和导致bun程序无法正常运行的情况。笔者的一个业务系统,由于建设的时间比较早的原因,虚拟机的版本,被固定在了CentOS 7和Kernal3.12的版本。

这时,如果直接在系统中,下载执行Bun文件,即使CPU架构支持,也会出现下面的错误:

js 复制代码
./bun -v
./bun: /lib64/libc.so.6: version `GLIBC_2.18' not found (required by ./bun)
./bun: /lib64/libc.so.6: version `GLIBC_2.24' not found (required by ./bun)
./bun: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by ./bun)

cat /etc/redhat-release 
CentOS Linux release 7.9.2009 (Core)

uname -a
Linux 3.10.0-1160.92.1.el7.x86_64 #1 SMP Tue Jun 20 11:48:01 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

其他常见的bun无法正常执行的因素可能包括CPU架构不兼容(如AMD64和ARM64),指令集不兼容(如AVX2)等等,但这里的错误,就是一个libc版本兼容的问题,它是由于CentOS版本和Kernal版本限制所决定的。所以,可能暂时是没有一个新版本的bun文件是可以在这个环境中运行的。

这里顺便提一下,CentOS这个操作系统,名义上已经不再升级和维护了(2024年底结束),原来的数量庞大的社区和免费用户,都无法直接获得相关的安全更新和Bug修复,越往后使用的安全风险也会逐步增加。所以业界建议有条件的使用者尽快的迁移到其他兼容的系统如AlmalLinux、RockyLinux包括国内的openEuler、AnolisOS等等系统。新建系统也不应当考虑选用CentOS,包括CentOS7和8。

由于技术方面的限制,笔者的系统无法正常的升级和迁移,所以只能考虑其他的技术方案,经过摸索和尝试,笔者认为相对而言,基于Docker的bun项目执行方案,是比较适合当前这个场景的。而且从实践过程和结果表明,使用Docker技术确实可以比较好的屏蔽系统的差异和兼容性细节,比较好的解决了可能的软件模块兼容性、版本差异或者冲突的问题。

下面来看一下具体的配置和操作。

Docker CE

Docker的本质,就是可以在标准系统上,提供一个轻量级、虚拟的、并且屏蔽宿主系统的执行环境。Docker本身的概念、安装和运行不是本文讨论的重点。这里主要想要说明的是在笔者的环境中,直接安装默认的docker也是有问题的,笔者使用的其实是docker的ce版本,这个版本比较新,需要使用自定义配置的软件仓库。基本安装配置过程和步骤如下:

  • 删除旧版本(如果有)
shell 复制代码
// 删除旧版本
yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-engine
  • 安装当前版本
arduino 复制代码
// 配置 docker 软件仓库,国内
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 

// 安装依赖包
yum install -y yum-utils device-mapper-persistent-data  lvm2
    
// 安装 docker ce版本
yum install -y docker-ce docker-ce-cli containerd.io            
  • 配置加速镜像, 强烈建议, 否则严重影响docker正常使用
bash 复制代码
// 配置镜像, 
mkdir -p /etc/docker
vi /etc/docker/daemon.json 
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://dockerproxy.com",
    "https://docker.nju.edu.cn",
    "https://docker.mirrors.sjtug.sjtu.edu.cn"
  ],
  "max-concurrent-downloads": 10,
  "log-driver": "json-file",
  "log-level": "warn",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

不知道是什么众所周知的原因,docker在中国的使用并不是很方便。原生的docker仓库基本不可用。很多地方查询到的仓库镜像也不是很好用,现在的配置是笔者尝试和实践的结果。当然也不能保证在读者的环境中同样好用。如果遇到那种问题,可能需要读者自行查询解决。

  • 启动和验证
yaml 复制代码
// 启用和启动docker服务
systemctl enable docker
systemctl start docker

// 检查docker版本和组件
docker --version
docker version
Client: Docker Engine - Community
 Version:           26.1.4
 API version:       1.45
 Go version:        go1.21.11
 Git commit:        5650f9b
 Built:             Wed Jun  5 11:32:04 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          26.1.4
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.11
  Git commit:       de5c9cf
  Built:            Wed Jun  5 11:31:02 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.33
  GitCommit:        d2d58213f83a351ca8f528a95fbd145f5654e957
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

// 试运行容器
docker run hello-world
docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

通常看到docker能够正常运行hello-world这个容器,说明整个docker环境应该就已经准备好了。

Bun项目

使用docker来执行标准的Bun项目,笔者认为,可以有两种方式。一种是将当前Bun项目的路径挂载到docker实例中进行执行;还有一种是基于基本Bun镜像和Bun项目编译一个新的Bun应用程序docker实例。从易用性和可维护性角度而言,笔者选择了前者,可能更适合于开发过程中的运行部署。而后者可能更适合于比较稳定的应用和相对比较稳定的生产环境中。本文也将着重讨论前一种方案。

所以,如果不考虑编译实例的话,其实操作就非常简单了。所涉及的项目文件夹,就是一个标准的Bun代码项目。直接进入项目文件夹,执行一个合适的docker命令,就可以使用docker中的bun来执行项目就,这个命令的大致形式如下:

docker run -d \

--name mapi \

-p 7083:7083 \

-v $(pwd):/app \

-w /app \

oven/bun:alpine \

bun web.ts

为了方便解释,笔者将这个命令拆分成很多小的部分,说明如下:

  • docker run -d 表示以守护进程模式执行(其他的执行模式包括it交换命令行模式)
  • --name 指定docker实例的名称,这个会在后续实例管理中非常有用
  • -p 指定端口映射,前者是实例中的端口,后者映射到主机端口
  • -v 挂载文件夹,将当前文件夹(pwd),挂载到docker实例中的 /app文件夹中
  • -w 指定实例启动后的工作文件夹,即bun程序项目文件夹
  • oven/bun:apline, 这是带有bun执行环境的原始docker镜像(基于轻量的alpine构建)
  • bun web.ts, bun执行命令,入口是web.ts,这个和常规的bun执行方式无异

这个完整命令的笔者的理解的大致原理如下。

命令执行时,Docker会先在宿主机环境中下载并缓存所需要的镜像,然后基于原始镜像,启动一个Docker实例;实例启动后,会将实例环境中的app路径,挂载到启动命令时的当前宿主机路径(这里就是当前的bun项目文件夹);随后在实例中切换到这个文件夹,执行定义好的Bun命令(bun web.ts),要注意这时的上下文是在docker实例当中,工作文件夹是指定的"/app";文件夹中,有bun的项目代码文件(web.ts)可以加载执行;如果有执行后有端口监听(在docker实例中),就会被按照预先的定义映射到外部(主机)并发布出去,就和外部主机运行了一个bun项目的效果是一样的。这样就实现了使用docker来执行bun项目的目的。

docker实例中的bun项目启动之后,如果要进行调整(如代码修改),也是非常方便的。甚至无需查找关闭和启动进程,直接操作docker实例就可以了,相关常用的管理功能操作包括(这时可以看到实例名称的重要性和方便性):

js 复制代码
// 查询当前的实例,包括关闭的实例
docker ps -a
CONTAINER ID   IMAGE             COMMAND                  CREATED        STATUS                   PORTS                                       NAMES
a3758637313d   hello-world       "/hello"                 3 hours ago    Exited (0) 3 hours ago                                               clever_black
ca98404583a6   oven/bun:alpine   "/usr/local/bin/dock..."   22 hours ago   Up 3 hours               0.0.0.0:7083->7083/tcp, :::7083->7083/tcp   mapi

// 关闭/启动/重启 docker 实例
docker shutdown mapi
docker start mapi
docker restart mapi 

// 查看docker实例运行日志,包括程序日志
docker logs -f mapi

当然,这样的操作,我们也可以感觉到,相对于原生bun应用的启动速度,bun docker重启明显要慢的多,一般需要数秒的时间(bun项目重启一般是亚秒级)。

笔者的程序项目相对比较简单,如果项目稍微复杂一点,其他可能需要进行扩展和注意的问题包括:

  • docker和bun项目正常运行后,正常情况下不会有任何的反馈(console.log),所以可能需要手动检查docker log来观察程序执行情况
  • 如果有依赖,可能需要先进行安装 ... bun install,它应该也会在项目实际文件夹中安装相关依赖文件,后续可以直接使用,注意这里的文件和代码,都是相对于Docker实例而非宿主机的
  • bun项目应当是无路径依赖的,即在任意路径中都可以执行,这样挂载到docker实例中,就不会有问题
  • 如果有其他的文件夹依赖,可以使用类似的方式,但需要注意引用关系
  • 其他的依赖项目,如数据库访问,外部接口访问等等,和在docker主机上直接操作,是没有区别的
  • 需要注意docker实例的运行状态,因为有可能由于错误而导致实例关闭

Bun项目的Docker构建

虽然笔者在现实环境中,选择的是docker实例+bun项目文件夹。但在前面另外一个项目中,其实是接触了bun项目构建的方式的,这里也简单说明一下。

要准备bun项目的docker构建,需要在项目文件夹中,创建一个Dockerfile文件,用于指定构建过程和配置。下面是一个示例:

Dockerfile 复制代码
FROM oven/bun:alpine

WORKDIR /app

# 复制 package.json 和 bun.lockb
COPY package.json bun.lockb* ./

# 安装依赖
RUN bun install

# 复制应用代码
COPY . .

# 暴露端口(Hugging Face Spaces 使用 7860 端口)
EXPOSE 7860

# 启动应用
CMD ["bun", "run", "start"]

Dockerfile准备好之后,就可以使用下面的命令构建项目docker实例和启动实例了:

js 复制代码
// 构建项目镜像
docker build -t my-node-app .

// 启动项目镜像实例
docker run -d -p 8080:3000 my-node-app

写到这里,笔者突然想到,如果构建过程比较完整的话,这个最后生成的,可以执行的docker镜像,是不是从另一种角度而言,就是一个SEA(Single Executable Application)单一可执行应用了呢?在某些场景中,可能可以满足一些比较特殊的需求(比如隐藏原始代码和配置)。

至此,我们就可以方便的将一个标准Bun程序项目,改造成为可以使用Docker来执行的方式了。

小结

本文讨论了如何将一个标准的Bun程序项目,改造成为使用Docker可以执行的方式。这个需求主要来自一些比较老旧的执行环境,无法直接执行Bun程序所产生的问题。探讨了在老版本的CentOS7环境中安装、配置Docker环境,并使用Docker执行Bun程序项目的操作和过程。

相关推荐
华仔啊2 小时前
MyBatis-Plus 让你开发效率翻倍!新手也能5分钟上手!
java·后端·mybatis
玉宇夕落2 小时前
JavaScript 执行状态全景图:从调用栈到事件循环,深入理解异步机制
javascript
ohyeah2 小时前
深入理解 JavaScript 数组:从创建到遍历的完整指南
前端·javascript
绝无仅有2 小时前
某东互联网大厂的Redis面试知识点分析
后端·面试·架构
乌暮2 小时前
JavaEE入门--计算机是怎么工作的
java·后端·java-ee
一室易安2 小时前
模仿elementUI 中Carousel 走马灯卡片模式 type=“card“ 的自定义轮播组件 图片之间有宽度
前端·javascript·elementui
前端世界2 小时前
ASP.NET 实战:用 CSS 选择器打造一个可搜索、响应式的书籍管理系统
css·后端·asp.net
在下胡三汉2 小时前
创建轻量级 3D 资产 - Three.js 中的 GLTF 案例
开发语言·javascript·3d
脸大是真的好~3 小时前
黑马JAVAWeb -Vue工程化 - Element Plus- 表格-分页条-中文语言包-对话框-Form表单
前端·javascript·vue.js