概述
笔者最近正在研究bun应用程序的开发,并打算将其实验性的应用到一些不太重要的业务场景当中,比如一些数据处理和服务等等。
但是,笔者的实际业务的软件环境有一些限制,很多业务服务是运行在比较老旧的软硬件平台之上的。一个比较典型的环境就是:
- OS: CentOS7, Linux Kernal 3.10, Glibc 2.17
- Intel(R) Xeon(R) CPU E5-4620 v2 @ 2.60GHz,指令集AMD64
上面的软硬件环境,限制了很多软件平台的直接正常运行,就比如这个架构和实现比较现代化的bun。比如在CentOS7中直接运行bun的二进制版本,可能会出现以下错误:
js
./bun --version
./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
显然,bun的运行,需要GBLIC2.18以上的版本。但是由于操作环境的限制,可能不能满足这个需求。经过一段时间的实践和摸索,笔者认为可以利用容器技术,在这些老旧平台上运行比较新的业务应用的代码。在这个过程中,遇到和解决了很多细节化的技术问题,随著文记录,以作备忘和研讨使用。
技术路线和方案
当前笔者当前和选择的技术栈如下:
- CentOS 7.9.2009 (Core)
- Kernal 3.10.0-1062.el7.x86_64
- Glibc 2.17
以上是现有软硬件平台,暂时无法改善,只能适应和兼容。但应当考虑到以后升级或者迁移,应当尽量考虑兼容性和减少修改和调整的工作。
- podman 1.6.4
笔者没有考虑使用docker,是觉得相对而言podman的易用性更好,而且podman不需要守护程序,更好管理和维护。而且从底层原理和使用方式,基本上和docker兼容。两者的使用方式也没有太大区别,基本上可以简单的进行替换。
- bun oven/bun:alpine
没有使用官方建议的oven/bun,是由于测试发现这个版本和当前硬件是不兼容的,所以转而选择了兼容性更好的alpine版本。
实践操作
下面简单记录和分析一下相关的实操工作。
支撑软件
下面的命令可以用于安装必要的支撑软件,主要是podman和fuse
sudo yum install podman buildah skopeo fuse-overlayfs fuse3 unzip
podman -v
这个阶段并没有碰到太多的问题。
但在后续阶段,笔者遇到了一个很大的问题,就是在当前软件环境下(可能主要是centos7.9的限制),podman/bun实际上是不能以普通账号工作的,主要是fuse挂载方面的问题(后面会简单探讨),现在只能暂时使用root账号进行工作。
镜像拉取
其实,在正常的环境当中,默认的podman镜像仓库是不可以正常使用的。下面是笔者实验后实际验证可行的一种操作方式:
js
// 切换到root
sudo -i
// 测试拉取
podman pull docker.tbedu.top/oven/bun:alpine
Trying to pull docker.tbedu.top/oven/bun:alpine...
Getting image source signatures
Copying blob 89ae849369e5 done
Copying blob 01d036902a3c done
Copying blob 14642c7f4b63 done
Copying blob 3ae00eb93f6c done
Copying blob acb61fd57fa9 done
Copying blob 58504060378f done
Copying config b8b943d885 done
Writing manifest to image destination
Storing signatures
b8b943d8852c7e6ca9a39caead1ba6d101d4bf48ceba66327b4ffa17b55d3193
// 修改prefix
podman tag docker.tbedu.top/oven/bun:alpine bun:alpine
拉取镜像后,可以使用tag指令将镜像重新命名成一个简单明了的形式,方便后续的使用。
为什么不能直接使用标准的方式拉取并执行镜像呢? 可以看下正常操作的方式:
js
podman run --rm nodejs:20 node -v
Trying to pull docker.io/library/nodejs:20...
denied: requested access to the resource is denied
Error: unable to pull nodejs:20: 1 error occurred:
* Error initializing source docker://nodejs:20: (Mirrors also failed: [registry.cn-hangzhou.aliyuncs.com/library/nodejs:20: Error reading manifest 20 in registry.cn-hangzhou.aliyuncs.com/library/nodejs: errors:
denied: requested access to the resource is denied
unauthorized: authentication required
]
[docker.mirrors.ustc.edu.cn/library/nodejs:20: error pinging docker registry docker.mirrors.ustc.edu.cn: Get https://docker.mirrors.ustc.edu.cn/v2/: dial tcp: lookup docker.mirrors.ustc.edu.cn on 61.139.2.69:53: no such host]
[hub-mirror.c.163.com/library/nodejs:20: error pinging docker registry hub-mirror.c.163.com: Get https://hub-mirror.c.163.com/v2/: dial tcp: lookup hub-mirror.c.163.com on 61.139.2.69:53: no such host]): docker.tbedu.top/library/nodejs:20: Error reading manifest 20 in docker.tbedu.top/library/nodejs: errors:
denied: requested access to the resource is denied
unauthorized: authentication required
这已经是修改了仓库镜像后的结果,包括了几乎所有AI推荐的地址,默认仓库的结果也是类似的。所以,最后的处理方式就是,直接使用可用的镜像仓库地址来拉取镜像,然后修改镜像的标签(tag)来使用。
现在笔者环境中,可用的仓库镜像包括,可以考虑将其加入到配置信息中:
js
// 用于拉取 bun
docker.tbedu.top
// 用于拉取 nodejs
registry.access.redhat.com
## 编辑 /etc/containers/registries.conf
unqualified-search-registries = ['docker.io']
[[registry]]
prefix = "docker.io"
location = "docker.tbedu.top"
用户名字空间(user_namespaces)
如果在docker启动时,出现以下错误,说明用户名字空间设置无效或者有问题:
js
cannot clone: Invalid argument
user namespaces are not enabled in /proc/sys/user/max_user_namespaces
修改用户名字空间:
js
// 修改配置文件
sudo nano /etc/sysctl.conf
user.max_user_namespaces = 10000
// 生效并检查
sudo sysctl -p
sysctl user.max_user_namespaces
测试镜像执行
我们可以使用以下命令,简单的测试和确认bun镜像的运行:
js
// 测试镜像
podman run --rm -it --privileged bun:alpine bun repl
Welcome to Bun v1.2.20
Type ".help" for more information.
[!] Please note that the REPL implementation is still experimental!
Don't consider it to be representative of the stability or behavior of Bun overall.
> Bun.version
'1.2.20'
> Bun.revision
'6ad208bc32e01c20e35e286a3194be60565254fa'
> require("crypto").createHmac("SHA256","key").update("Data").digest()
<Buffer 8c a8 63 35 61 db ed 42 4c 1a 8f fd 9b 68 8b
ad 7d 57 82 0b 4c 2e af bb 2b c8 92 d5 79 f0 b2 78>
最后的测试命令,使用--it选项,使用容器的shell来运行 bun repl 命令。这个操作充分测试了bun确实能够正常的在容器中运行。一般情况下,这里面有一个安全方面的限制,会影响到终端交互,示例中增加了 --privileged 选项,让bun在容器中的运行和真实环境中基本相同。
程序项目执行
要使用容器运行一个已经编写好的项目,在部署项目到docker主机之后,就可以使用运行指令,在容器中运行这个项目了。简单的示例如下:
js
// 进入项目文件夹
cd approject
// 安装项目依赖
podman run --rm -it \
-v "$(pwd):/app:Z" \
-w /app \
-p 3000:3000 \
bun:alpine \
bun install
// 执行bun项目,默认入口 index.ts
podman run --rm -it \
-v "$(pwd):/app:Z" \
-w /app \
-p 3000:3000 \
bun:alpine \
bun .
笔者理解,上述指令的意思和要点是:
- run 运行镜像实例,当前bun:alpine
- rm: 程序结束后删除实例
- it: 允许交互,i保存stdin打开,t分配一个伪终端
- "$(pwd):/app:Z", 将当前文件夹(程序主文件夹),映射到实例中的 /app文件夹中,Z是私有挂载
- w /app, 启动时的工作文件夹设置为容器实例中的 /app (挂载文件夹)
- p 映射网络端口,宿主机:容器端口
- d 以守护模式运行
- sh -c "": 在镜像实例中执行shell指令
一般情况下,如果是初次部署,可以使用 bun install安装依赖;后续的正常运行可以直接运行 bun . 。
容器实例管理
如果以守护模式( -d参数 )在后台运行的容器实例,运行时的日志是不会出现在启动终端中的,可能需要podman logs指令。其他的常用管理任务可能包括查看容器状态,关闭实例等等如下:
js
// 列举正在运行的容器
podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
18a06e9e6a71 docker.tbedu.top/oven/bun:alpine bun . 2 minutes ago
Up 2 minutes ago 0.0.0.0:3000->3000/tcp mystifying_ganguly
// 查看日志
podman logs 18a06e9e6a71
NATS Connected: [ "nats://192.168.206.155:4222" ]
Database Init: PostgreSQL 13.7 on x86_64-pc-linux-gnu,
compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44), 64-bit
// 停止运行实例
podman kill 18a06e9e6a71
18a06e9e6a71bc420b0380a52fe20d26436fbe6d9c16338043fb426a3f41ff41
每个启动的实例,系统会为其分配一个随机的ID,开发者可以基于这个ID对实例进行管理,也可以在启动时使用 --name 指定实例的名字,方便进行操作特别是可以方便通过脚本来进行管理。
普通账号问题
文中前面的操作,都是以root账号进行的。这在生产系统运维中,并不是一个很好的实践。但笔者也没有找到一个简单易行的方式解决这个问题。
js
podman run --rm -it bun:alpine bun --version
ERRO[0000] error unmounting /home/uladmin/.local/share/containers/storage/overlay/d23baef53202689fa9209b1f82f66a4c4095accd5519496764c13be122d9d981/merged: invalid argument
Error: error mounting storage for container 21b4a1baf85e68da46a327c8e1e8cac2823465aa8c2e6271ea9f7cee97e839d2: error creating overlay mount to /home/uladmin/.local/share/containers/storage/overlay/d23baef53202689fa9209b1f82f66a4c4095accd5519496764c13be122d9d981/merged: using mount program /usr/bin/fuse-overlayfs: fusermount3: mount failed: Operation not permitted
fuse-overlayfs: cannot mount: Operation not permitted
: exit status 1
笔者尝试了很多操作,包括用户名字空间的配置,用户id映射,权限配置等等,都没有得到解决,现在只能暂时使用root账号。有相关材料提示这可能是比较老旧的CentOS7的Docker权限模型造成的问题。
小结
本文作为一个工作和研究笔记,主要讨论了在一个受限制的软硬件环境中,运行bun应用程序的技术方案, 简单而言就是将其容器化。文中探讨和展示了其基本的操作流程和其中可能需要注意到的问题。