简介:面向需要在ARM64架构CPU上离线部署Tendis 2.7.0单机版的运维与开发人员,这份基于docker-compose的部署工具解决离线环境下软件分发和安装配置的痛点。资源包共19个文件,压缩后体积约310.27MB,主要包含shell控制脚本、conf/tpl配置模板、docker-compose yaml编排文件、Dockerfile以及ARM64离线镜像包;templates目录提供可复用的配置模板,可按实际环境调整端口、密码、数据目录等参数。该工具完整覆盖部署、启动、停止、卸载、检测等操作,支持数据目录、端口、密码灵活配置,并可将Tendis配置与数据目录持久化到宿主机,避免容器重建导致数据丢失,适合国产化替代或ARM64服务器上的生产/测试环境搭建。目前已有282人学习下载,对于需要快速掌握ARM64容器化部署Tendis的团队,是一套结构清晰、值得参考的离线落地实践。
1. ARM64 离线部署 tendis 2.7.0:真正难的不是 docker-compose,而是镜像与配置齐步走
ARM64 服务器在机房里已经是常态,私有化交付现场经常是机器到位了,网络策略还没放开。tendis 2.7.0 作为兼容 Redis 协议、把数据落到 RocksDB 的存储组件,单机版部署要解决的其实是三个问题:镜像拿不到、镜像架构和 CPU 不匹配、容器启动缺配置。docker-compose 本身只是编排层,它不会帮你绕过这些前置条件。
把 tendis 单机版做成一个可以整体拷贝的离线部署工具,目录里一次性放好镜像 tar、compose 文件、tendis 配置、install.sh 脚本和校验清单,目标节点拷进去之后执行一条命令即可完成从docker load到健康检查的全过程。这套做法对经常接触 ARM64 交付的平台工程师和运维是有直接参考价值的,下面按造包、编排、脚本、验证四个阶段展开。
2. 先预备离线镜像:确认 ARM64 架构后再打包 tendis 2.7.0
2.1 在联网构建机上用 --platform 拉取并导出镜像
离线部署工具的第一步,是在能联网的构建机上把 tendis 2.7.0 的 arm64 镜像生成出来。构建机可以是同为 ARM64 的机器,也可以在 x86_64 机器上直接拉取 ARM64 镜像层,只要镜像仓库的 manifest 里存在linux/arm64平台分支。
常见做法是先检查 manifest,再拉取导出,命令如下:
docker manifest inspect tendis:2.7.0 | grep -E '"architecture"|"os"' docker pull --platform linux/arm64 tendis:2.7.0 docker save tendis:2.7.0 -o images/tendis-2.7.0-arm64.tar第一行做架构预检,确认仓库里确实有 arm64 分支。如果 manifest 里 architecture 是 amd64,继续拉取导出入不了离线包。第二行是关键,--platform linux/arm64让 Docker 在 manifest list 中选择 arm64 分支,不受构建机自身 CPU 架构影响。第三行把完整镜像层导出为 tar。
docker save的产物在离线节点上用docker load恢复。如果镜像体积大,建议压缩传输:docker save tendis:2.7.0 | gzip -1 > images/tendis-2.7.0-arm64.tar.gz。-1是低压缩级别,离线节点加载时受磁盘和 CPU 瓶颈影响,压缩级别太高会拖慢docker load。目标节点上若已有同名同 tag 镜像,docker load不会报错,但会留下悬空层,脚本里一般在 load 前先查docker images -q tendis:2.7.0,存在就跳过。
注意:manifest 里是 arm64 和运行时能跑是两码事。如果发布方编译时启用了 ARMv8.2 特性,老款 ARMv8.0 的 CPU 启动时会报 Illegal instruction,这个坑跟镜像 tar 本身没有关系,尽量选择带明确基线版本的镜像。
2.2 没有现成 ARM64 镜像时,走 buildx 或源码打包
项目组只提供 amd64 版 tendis 镜像的情况很常见。此时可以在构建机上用docker buildx构建 ARM64 镜像,但要清楚 buildx 在 x86 主机执行 arm64 的 RUN 指令时,依赖 qemu 模拟 arm64 内核的 binfmt 机制,编译大项目会明显变慢,而且容易在依赖本地 x86 二进制时碰壁。
更可控的做法是把准备好的 tendis 二进制、配置和数据目录一起塞进多架构基础镜像。我封装工具时会在仓库里留一个 build-helper 目录,Dockerfile 框架如下:
FROM --platform=$BUILDPLATFORM debian:bullseye-slim AS prep ARG TARGETARCH COPY tendis-${TARGETARCH}-2.7.0.tar.gz /tmp/ FROM debian:bullseye-slim COPY --from=prep /tmp/tendis-${TARGETARCH}-2.7.0.tar.gz /tmp/ RUN tar xzf /tmp/tendis-${TARGETARCH}-2.7.0.tar.gz -C /usr/local/tendis \ && useradd -r tendis && chown -R tendis:tendis /usr/local/tendis USER tendis EXPOSE 16379 ENTRYPOINT ["/usr/local/tendis/bin/tendis"]构建执行的是:
docker buildx build --platform linux/arm64 --output type=oci,dest=tendis-2.7.0-arm64.tar .关键点是ARG TARGETARCH由 buildx 自动注入,COPY tendis-${TARGETARCH}-2.7.0.tar.gz会按目标平台选择对应二进制的文件名。生成的 OCI tar 在离线节点上可以用skopeo copy oci-archive:tendis-2.7.0-arm64.tar docker-daemon:tendis:2.7.0导入。
真正把 tendis 2.7.0 源码交叉编译成 ARM64 二进制,涉及 RocksDB 版本、编译选项、透明大页取舍,不是几行 Dockerfile 能覆盖的。离线部署工具要把这部分隔离到造包阶段,让部署的人只消费最终的镜像 tar。
2.3 离线工具分发目录的结构与体积预估
整个离线工具建议设计成一个目录,直接压缩成单个归档分发。具体结构如下:
| 路径 | 作用 | 部署时状态 |
|---|---|---|
docker-compose.yml | 容器编排,声明 tendis 服务 | 原位使用 |
conf/tendis.conf | tendis 启动配置,读入容器 | 只读挂载 |
images/tendis-2.7.0-arm64.tar | docker load的镜像归档 | load 后可留可删 |
scripts/install.sh | 一键部署主入口 | 原地执行 |
checksum.sha256 | 整个工具包的完整性校验 | 开跑前校验 |
这个结构解决离线交付里“东西不全”的问题。很多时候离线部署失败不是镜像或者配置写错,而是复制过程中丢文件,所以工具包根目录一定要放 checksum,install.sh 第一步做校验,不通过就不允许继续执行。
3. docker-compose 编排与 tendis 配置:端口、数据目录和资源边界
3.1 单机版最小的 docker-compose.yml 写法
离线环境里 docker-compose 只需要保证一件事:不依赖网络、不依赖构建、容器能自行恢复。最小可用的 compose 文件如下:
version: "3.8" services: tendis: image: tendis:2.7.0 container_name: tendis-single restart: unless-stopped ports: - "127.0.0.1:16379:16379" volumes: - ./data:/data:rw - ./conf/tendis.conf:/tc/tendis.conf:ro command: - /usr/local/tendis/bin/tendis - -f - /tc/tendis.conf environment: - TZ=Asia/Shanghai ulimits: nofile: soft: 1024000 hard: 1024000参数说明:
restart: unless-stopped保证节点重启后 Docker daemon 自动把容器拉起,这是离线节点无人值守的第一道保险。ports只绑定127.0.0.1,单机部署默认不开远程访问。如果同一台机器其他容器要访问,可以放到同一个自定义 network;绑定宿主机端口是给 redis-cli 和运维探针用的。command里写绝对路径,避免镜像没设默认启动入口时 compose 去猜命令。nofile软硬限制都调到 1024000,处理连接数堆积时的 too many open files。- 特意保留
version: "3.8"是为了兼容还在用 docker-compose v1 的旧节点,新版 Docker 会忽略该字段,不影响执行。
3.2 tendis 配置文件挂载进容器而不是打进镜像
tendis 配置建议放conf/tendis.conf再挂载,改配置不用重建镜像,离线改错也能直接改回原文件。单机版里我常用的最小配置如下:
port 16379 bind 0.0.0.0 protected-mode no daemonize no dir /data pidfile /data/tendis.pid logfile /data/tendis.log maxmemory 10737418240关键点:
daemonize no必须保持 no。容器里 PID 1 必须是 tendis 进程本身,daemonize 后容器启动完就退出,compose 会按 restart 策略反复拉起重启。dir /data对应 compose 挂载的宿主机数据目录,RocksDB 的数据文件写在这里,容器重建不丢。logfile /data/tendis.log落盘保存,方便 docker logs 之外做历史排查。如果镜像构建时把日志直接输出到 stdout,这里会出现双写,按现场需要二选一。protected-mode no配合bind 0.0.0.0使用。部署环境是隔离内网时这样写没问题,如果要跨网段访问,建议再叠加一条requirepass。
3.3 ARM64 节点上资源限制参数的选型
ARM64 服务器内存分配策略和 x86 有差异,尤其开启了大页内存和 NUMA 的机器,资源边界最好写进 compose。docker compose v2 单独执行up时,顶层的mem_limit、memswap_limit、cpuset是直接生效的,示例:
mem_limit: 8g memswap_limit: 8g cpuset: "0-3" pids_limit: 4096参数速查表:
| 参数 | 与 x86 的主要差别 | 典型取值 |
|---|---|---|
mem_limit | ARM 机型内存密度高,按可用内存 70% 留余量 | 8g |
memswap_limit | 与 mem_limit 相等,禁用 swap 兜底 | 8g |
cpuset | 固定到大核区间,避免大小核调度抖动 | "0-3" |
pids_limit | 限制 fork 进程数,防句柄泄漏 | 4096 |
这里最容易出事的是 RocksDB block cache。tendis 启动后即使没有请求,也会按maxmemory配置预留相当一部分内存作为 block cache,mem_limit设得太紧,进程会在 compaction 或后台写盘期间被内核 OOM 杀掉,现象是容器一直重启但不报配置错误。
3.4 compose 文件离线执行时最容易踩的两个坑
第一个坑是 compose 里残留build段。离线目录里只要还有 Dockerfile,docker compose up就会优先尝试构建,构建时找不到基础镜像就卡住。工具脚本应该在启动前用docker compose config --quiet做校验。
第二个坑是命令差异。部分机器只有docker-composev1,部分只有docker composev2。脚本不能写死,常见做法是探测后赋值变量:
if docker compose version >/dev/null 2>&1; then COMPOSE_CMD="docker compose" elif docker-compose version >/dev/null 2>&1; then COMPOSE_CMD="docker-compose" else echo "docker compose not found"; exit 1 fi这个探测要放在 install.sh 的统一入口处,后续所有执行都使用同一个$COMPOSE_CMD,避免 v1/v2 参数差异引发二次问题。
4. 一键部署脚本:镜像加载、compose up 与健康检查的顺序不能乱
4.1 脚本主流程和前置条件检查
一键部署脚本由前置检查、镜像加载、compose 启动、健康检查四个阶段组成。顺序颠倒会让错误解释变得困难,比如先启动 compose 再加载镜像,离线节点上 compose 会直接去远程仓库拉取,然后长时间卡住。
前置检查 checklist 如下:
uname -m必须是 aarch64 或 arm64;docker version可连通,daemon 处于运行状态;- 镜像 tar 存在且 checksum 匹配;
- 端口未被占用;
- 数据目录可写且磁盘剩余空间足够。
脚本核心片段:
#!/usr/bin/env bash set -Eeuo pipefail cd "$(dirname "$0")" ROOT_DIR="$(pwd)" IMAGE_NAME="tendis:2.7.0" IMAGE_TAR="images/tendis-2.7.0-arm64.tar" PORT="16379" precheck() { local arch="$(uname -m)" if [[ "${arch}" != "aarch64" && "${arch}" != "arm64" ]]; then echo "arch not supported: ${arch}"; exit 1 fi if ! docker version >/dev/null 2>&1; then echo "docker daemon not ready"; exit 1 fi if ss -lnt | grep -q "[:.]${PORT} "; then echo "port ${PORT} already in use"; exit 1 fi }参数解释:
set -Eeuo pipefail的-E让 ERR trap 覆盖函数内部错误,-u防止未定义变量把路径拼坏。- 端口检查用
ss -lnt,新装离线系统经常没有 netstat,ss 由 iproute2 提供,基本是内核标配。 - 架构检查放在最前面,也能顺带把 qemu 模拟 arm64 的场景挡在流程外。qemu-user 模拟出的 aarch64 在
uname -m里同样显示 aarch64,所以这个检查只是保证宿主机确实是 ARM64,不能代替对模拟部署的判断。
4.2 镜像加载、compose up 与健康检查的代码骨架
load_image() { if docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "${IMAGE_NAME}"; then echo "image already loaded, skip" return 0 fi docker load -i "${IMAGE_TAR}" echo "image loaded: ${IMAGE_NAME}" } start_compose() { ${COMPOSE_CMD} config --quiet ${COMPOSE_CMD} up -d --no-build echo "tendis container started" } wait_tendis_online() { local i=0 while (( i < 30 )); do if (exec 3<>"/dev/tcp/127.0.0.1/${PORT}") 2>/dev/null; then exec 3>&- 3<&- echo "tendis on ${PORT} is accepting connections" return 0 fi sleep 2 (( i++ )) done return 1 } trap 'echo "deploy failed"; docker ps -a --filter name=tendis-single; docker logs --tail 100 tendis-single' ERR main() { precheck load_image start_compose wait_tendis_online } main "$@"逻辑说明:
docker compose config --quiet在 up 前检查编排文件。--quiet是 v2 参数,v1 用docker-compose config -q,由 COMPOSE_CMD 分支保证命令正确。--no-build必须保留。离线目录里一旦存在构建文件,没有这个参数就会触发网络依赖。/dev/tcp是 bash 内置的 TCP 通道,在没有 nc、curl 的最小系统里也能做端口探测。它只能验证 TCP accept,协议层面的 ping 放到部署后验证阶段处理。- ERR trap 打印容器列表和最近 100 行日志,失败现场保留在终端,不需要重新翻日志。
4.3 失败时的三类错误信号与排查方法
4.3.1 exec format error
容器显示 Exited,docker logs输出exec format error,优先怀疑镜像平台标签。确认命令:
docker image inspect tendis:2.7.0 --format '{{.Architecture}} {{.Os}}'正常输出为arm64 linux。显示amd64时,说明构建机打包阶段--platform linux/arm64没有生效,或者是后续有人用错误架构重新打了镜像。替换正确架构的镜像即可。
4.3.2 容器反复重启且 ExitCode 为 137
ExitCode 137 通常是 OOM 或被 kill。先查 OOMKilled 字段:
docker inspect tendis-single --format '{{.State.OOMKilled}} {{.State.ExitCode}}'OOMKilled 为 true 时,按第 3.3 节调大 mem_limit,或减小配置里 maxmemory 预留空间。宿主机的 dmesg 一般保留out of memory记录,cgroup v2 环境下用journalctl -k查看。
4.3.3 端口 bind 冲突的时序问题
预检查已经查过端口监听,但 docker-proxy 占用端口时 ss 不一定直接显示,尤其是 ipv4、ipv6 绑定并存时。推荐两条命令复合确认:
ss -lntp | grep 16379 docker ps -a --filter publish=16379第二条更直接,能列出所有声明过该端口映射的容器,失败提示里给出已占用容器的名称,排错路径会短很多。
5. 部署后的验证与加固:redis-cli、systemd 与离线包的 checksum
5.1 部署完先做三个协议级验证
镜像和配置都到位后,不能用“端口能通”当作成功标准。tendis 兼容 Redis 协议,最省事的验证就是 redis-cli:
docker exec tendis-single redis-cli -p 16379 ping docker exec tendis-single redis-cli -p 16379 info server ss -lnt | grep 16379第一行验证读写通道,第二行确认启动后的版本与编译平台。如果镜像里没内置 redis-cli,改用docker exec tendis-single sh -c 'redis-cli -p 16379 ping',或者从宿主机 redis-cli 连接127.0.0.1:16379。info 输出里顺带确认run_id,避免多台机器之间复制工具包后误部署了旧配置。
5.2 将部署工具托管到 systemd
离线节点经常按需重启,compose 的 restart 策略能拉起容器,但工具目录的加载顺序最好交给 systemd 统一管理。unit 文件放在/etc/systemd/system/tendis-deploy.service:
[Unit] Description=tendis offline deploy service Requires=docker.service After=docker.service network-online.target [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/opt/tendis-offline ExecStart=/opt/tendis-offline/scripts/install.sh start ExecStop=/opt/tendis-offline/scripts/install.sh stop ExecReload=/opt/tendis-offline/scripts/install.sh reload [Install] WantedBy=multi-user.targetType=oneshot配合RemainAfterExit=yes,让 install.sh 结束后 unit 保持 active,systemctl restart才能正确走 stop、start 流程。ExecStart 等路径写绝对路径,不能依赖 systemd 服务环境里的相对路径。
5.3 用 checksum 和冷备闭环一个单机版工具
离线包会在多台 ARM64 节点间复制,镜像 tar 被截断或覆盖的情况并不少见。工具包分发时生成校验清单:
find . -type f -exec sha256sum {} \; > checksum.sha256install.sh 的 precheck 里校验通过后再执行后续步骤。这样可以把“镜像损坏”和“容器启动失败”两类问题快速区分开,避免在错误方向上反复抓日志。
最后的备份环节也建议固定成一条命令:单机版 tendis 的数据都在/data下,备份时先docker stop做一次冷备,再用tar czf style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />