概述
本文是笔者的系列博文 《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程序项目的操作和过程。