简介面向 ARM64 CPU 的离线运维场景这份工具包将 Tendis 2.7.0 单机版与 Docker Compose 封装为一体解决内网环境下无法在线拉取镜像、手工配置繁琐的问题用户只需调整数据目录、端口与密码即可完成部署、启动、停止、卸载和检测等操作适合云原生运维人员和需要快速搭建 Tendis 测试环境的开发者。资源压缩后约 310MB共 19 个文件核心包括 shell 自动化脚本、conf/tpl 配置模板、yaml 编排文件、Dockerfile 以及 redis-cli、binlog_tool 等辅助工具各类型分工清晰可离线复用。配置与数据目录支持持久化便于服务升级与数据迁移。包内附带离线镜像与安装包部署时不再依赖外部仓库环境一致性更有保障。目前已有 283 人学习使用可直接落地的部署脚本、参数化模板和完整操作入口能省去源码编译与镜像适配的重复劳动尤其适合在 ARM64 国产化环境中交付 Tendis 服务时参考。1. 为什么这个工具要把 ARM64 和离线部署绑在一起一台核心机型是 ARM64 的服务器内网不能连通外网手里只有一个 U 盘和一个 docker-compose 安装包却要把 Tendis 2.7.0 单机版跑起来。这种环境里你大概率会经历“x86 镜像直接拉起来就 Exec format error”“docker-compose 命令找不到”“端口通了你却不敢确定能不能写数据”这一连串爆雷。这个工具解决的就是这个问题在一台已经能联网的 ARM64 机器上把 Tendis 镜像和 compose 组件都备好再把这些产物搬到离线环境最后用一条deploy.sh完成导入、编排和启动。Tendis 是腾讯开源的兼容 Redis 协议的 KV 存储底层用 RocksDB 做持久化所以它既保留了 Redis 的数据模型又能在数据量超出内存时继续工作。单机版部署的用途很明确把原来跑在 Redis 上的缓存或简单 KV 场景平移到 Tendis不引入集群复杂度。这篇笔记会讲清楚离线镜像怎么做、compose 文件怎么写、一键脚本为什么那样封以及我踩过的几个坑。适合在信创服务器、ARM 数据中心或纯内网环境里做基础服务的工程师也适合刚接触离线交付的人照着抄。2. 先备料在联网机器上打好 ARM64 镜像和 docker-compose离线部署的难点不是 docker-compose 文件而是“物料”。Tendis 官方的发行物大多是 Linux 二进制Docker 镜像虽然也能找到但很多默认是 x86_64 的。ARM64 宿主机如果加载了 x86_64 镜像跑起来不出三秒就报exec format error。所以第一步是必须在联网机器上把 ARM64 镜像和 docker-compose 组件都准备好。2.1 确认目标平台的架构和 Docker 环境先确认联网机器的 CPU 架构和内核是否支持 ARM64 容器。这里不能只看uname -m因为有可能你人在一台 x86 开发机上却要用buildx交叉构建 ARM64 镜像。确认命令如下uname -m docker version --format {{.Server.OS}}/{{.Server.Arch}} docker buildx version第一行告诉你宿主机架构第二行告诉你 Docker 服务端所在平台第三行确认是否支持 buildx。如果是linux/arm64直接在本地构建即可如果是linux/amd64需要额外确认 buildx 的 qemu 模拟出是否正常。生产环境建议单独找一台 ARM64 机器来出镜像因为交叉构建的产物在某些旧版本 Docker 下可能带出奇怪的文件权限问题。如果联网机器本身就是 ARM64那么可以直接用docker pull拉镜像。拉取时最好指定--platform linux/arm64避免 docker 因为其他原因默认为 x86 镜像。docker pull --platform linux/arm64 tendis:2.7.0这里tendis:2.7.0是你从内部镜像仓库或官方仓库解析出来的完整镜像名。如果你拿不到现成的 ARM64 镜像那就走源码构建路线在 ARM64 机器上编译 Tendis再用一个最小 Dockerfile 把它打成镜像这个后面会写到。2.2 构建或拉取 ARM64 镜像并导出为 tar我一般建议先在联网环境下用docker buildx build直接构建 ARM64 版镜像。假设项目目录下已经有 Tendis 的编译产物和一份 Dockerfile构建命令是docker buildx build --platform linux/arm64 -t tendis:2.7.0 -o typedocker .-o typedocker表示把构建结果直接导入到当前 docker 守护进程里。省略这个参数时buildx 默认把产物推到远端 registry本地反而看不到镜像。很多人第一次用 buildx 时把这一步省了最后发现docker images里什么都没有。构建完成后用docker save把镜像打包成 tar方便拷贝到离线环境docker save -o tendis-2.7.0-arm64.tar tendis:2.7.0 scp tendis-2.7.0-arm64.tar useroffline-host:/opt/tendis/docker save保存的是包含全部镜像层和元数据的单文件离线机上用docker load就能恢复。注意不要用docker export那个只会导出容器文件系统不带镜像历史和配置信息。2.3 在离线机器上导入镜像和 docker-compose 组件离线机上的实际操作无非两个动作导入镜像、放置 docker-compose 可执行文件。镜像导入的代码没人会觉得棘手sudo docker load -i /opt/tendis/tendis-2.7.0-arm64.tardocker-compose 的离线安装反而是真正的门槛。现在 docker-compose 在很多发行版里是以插件形式存在的但离线机上根本没法用包管理器安装。常见的做法是从官方 Release 获取docker-compose-linux-aarch64这个二进制文件然后直接放到/usr/local/bin/docker-composesudo cp docker-compose-linux-aarch64 /usr/local/bin/docker-compose sudo chmod x /usr/local/bin/docker-compose docker-compose version这里要留意docker-compose 二进制是否依赖 glibc 版本有些旧的 aarch64 系统只有 2.27 以下的 glibc可能跑不起来。处理办法是在联网机器上提前用ldd检查链接库版本或者在离线机上找已有的 compose 插件。不要指望pip install那还需要 Python 环境和依赖包离线装起来非常痛苦。还有一个细节docker-compose 的配置文件在离线机上其实不是必需的你可以把编排文件直接写到docker-compose.yml里。但务必要在导入镜像后确认镜像名和 tag 完全一致否则后面up时会尝试去 registry 拉取然后超时。离线环境下最怕 docker 自动去联网docker image inspect可以有效防止这种乌龙。docker image inspect tendis:2.7.0 --format {{.Os}}/{{.Architecture}}如果显示的是linux/arm64离线的第一关才算真正过了。3. 用 docker-compose 编排 Tendis 单机版一个可复用的 yaml 模板物料备齐后就开始写编排文件。Tendis 单机版和 Redis 单机版在 docker-compose 里的差异主要在数据目录、日志配置进程数上。Tendis 的容器内进程通常叫tendis它会读一个配置文件。我们需要把配置文件和持久化目录都挂进容器。3.1 compose 文件整体结构和关键参数下面这份docker-compose.yml是我在 ARM64 离线机上验证过的最小可用版本version: 3.9 services: tendis: image: tendis:2.7.0 container_name: tendis-single restart: unless-stopped ports: - 6379:6379 volumes: - ./data:/data - ./conf/tendis.conf:/etc/tendis/tendis.conf:ro command: [tendis, -f, /etc/tendis/tendis.conf] environment: - TZAsia/Shanghai mem_limit: 2g healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 3s retries: 5version: 3.9不是必须的但指明后可以避免某些旧版 compose 解析器采用不同默认行为。restart: unless-stopped希望传递的意思很明确宿主机重启后容器自动拉起这在离线环境没有 supervisor 时非常关键。command里的tendis是容器内命令名-f指定配置文件路径。注意配置文件路径必须和 volume 挂载的路径一致我遇到过不少人挂载到/redis.confcommand 里却写/etc/tendis/tendis.conf结果容器直接拿默认配置启动端口没问题但数据目录完全不对。3.2 持久化和资源限制volume、mem_limit、sysctlsTendis 的数据目录默认在配置文件里的dir字段控制。这里把宿主机的./data挂载到容器的/data配置文件里的dir就写/data。这样重启容器不会丢数据。# tendis.conf bind 0.0.0.0 port 6379 daemonize no dir /data logfile /data/tendis.log save 900 1 save 300 10bind 0.0.0.0很重要。如果不写这个Tendis 默认只监听127.0.0.1从宿主机访问会直接拒绝连接。daemonize no是因为容器需要前台进程运行否则容器跑完配置就退出。资源限制方面mem_limit: 2g是经验值。单机版如果只给 Redis 缓存类业务使用1-2GB 足够如果准备存上 GB 级别的数据请适当调大。这里不要使用swapRocksDB 自己管理缓存过度依赖系统 swap 会导致性能骤降。Tendis 的底层 RocksDB 对系统vm.overcommit_memory比较敏感你可能需要给容器加内核参数。可以在docker-compose.yml里加一段sysctls: - vm.overcommit_memory1如果没有权限直接设置 sysctl那就用docker run --sysctl试或者干脆在宿主机上写成/etc/sysctl.d/90-overcommit.conf。这个参数不是必须但遇到 RocksDB 报Cannot allocate memory的玄学问题时多半就是它。3.3 健康检查和依赖顺序健康检查可用于 docker-compose 判断服务是否真正可用。配置里用了redis-cli pinghealthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 3s retries: 5这个检查把 Tendis 当成 Redis 来探活。redis-cli必须在容器内存在如果官方镜像里没有这个工具你可以换成bash -c配合/proc/net/tcp检查端口但那种方式远不如 redis-cli 明确。我见过有人把 healthcheck 写成curl但 Tendis 并不提供 HTTP 接口最后容器一直显示 unhealthy。在依赖顺序上单机版没有依赖所以不用depends_on。但如果后边接了业务容器可以加一个条件app: depends_on: tendis: condition: service_healthy这在 compose 规范里是支持的能避免业务容器在 Tendis 准备好之前就抢跑导致连接失败。4. 一键执行deploy.sh 把导入、编排、起服务串起来物料有了compose 文件有了最后差一个入口。离线交付时使用者不一定熟悉 docker-compose所以一个deploy.sh脚本才是真正的“一键”。它要做的事很朴素检查 docker、导入镜像、执行 up但细节上必须考虑重复执行和回滚。4.1 deploy.sh 的整体流程下面是我常用的脚本骨架#!/usr/bin/env bash set -euo pipefail IMAGE_NAMEtendis:2.7.0 TAR_FILEtendis-2.7.0-arm64.tar COMPOSE_FILEdocker-compose.yml DATA_DIR./data # 检查 docker 是否存在 if ! command -v docker /dev/null 21; then echo docker not found exit 1 fi # 检查镜像是否已导入避免重复导入 if ! docker image inspect $IMAGE_NAME /dev/null 21; then echo Loading image from $TAR_FILE ... docker load -i $TAR_FILE else echo Image $IMAGE_NAME already exists, skip load. fi # 拉起来 docker-compose -f $COMPOSE_FILE up -dset -euo pipefail的意思是任何命令失败就退出未定义变量报错管道中的失败命令也能捕获。这能防止镜像导入失败后脚本继续往上冲最后把错误掩盖在一个五颜六色的日志里。脚本会把TAR_FILE放在和脚本相同的目录下这样可以一次性把整个文件夹拷进离线机而不需要单独 scp。执行时先用docker image inspect判断镜像存不存在如果不存在才导入否则每次都 load 会拖慢整个流程还会让使用者觉得“是不是出了问题”。4.2 参数化与环境检查为了让部署脚本更通用我会把端口、数据目录、容器名都做成环境变量允许通过.env文件覆盖。compose 文件本身支持环境变量插值所以可以这么写ports: - ${TENDIS_PORT:-6379}:6379 volumes: - ${DATA_DIR:-./data}:/data然后在 deploy.sh 里指定默认值PORT${TENDIS_PORT:-6379} DATA_DIR${DATA_DIR:-./data} mkdir -p $DATA_DIR这里的mkdir -p $DATA_DIR绝不是多余的。如果目录不存在docker 会以 root 身份帮我们创建但创建出来的目录属主是 root。Tendis 容器内的进程可能以非 root 用户运行写入时就会Permission denied。手动创建并 chown 能避免这个问题mkdir -p $DATA_DIR chown -R 1000:1000 $DATA_DIR 2/dev/null || truechown的 UID 取决于镜像里定义的运行用户不是所有镜像都用 1000。可以先查一下docker run --rm tendis:2.7.0 id如果是uid1000那上面的命令就成立如果报错就以 root 身份运行容器或在 compose 里指定user: root。离线交付最怕这种隐式权限问题提前在 deploy.sh 里处理比事后自动检查舒服得多。4.3 回滚与清理一条 up 也要保留后悔药部署工具不算完整除非有回滚。docker-compose down只会删除容器不会删除数据目录所以可以作为回滚手段docker-compose -f $COMPOSE_FILE down --remove-orphans而down -v会连数据卷一起删掉一般在决定彻底重建时才用。为了让使用者在误操作时不至于删掉数据我会在 deploy.sh 里显式区分两个动作部署用up -d清数据用另一个脚本或加一个--reset参数。if [[ ${1:-} --reset ]]; then docker-compose -f $COMPOSE_FILE down -v echo Data removed, container stopped. exit 0 fi这样既提供了后悔药又避免误删。生产环境里一般不建议自动执行down -v所以我只把它藏在参数后面。5. 避坑实录ARM64 离线部署 tendis 的 5 个高频翻车点这部分是我自己在不同客户现场反复遇到的坑。每一条都按现象、原因、解决来写希望你在照着部署之前先扫一眼能省掉至少一晚上的排查时间。5.1 现象容器瞬间退出docker logs 显示 exec format error部署完执行docker-compose up -d容器一直重启docker logs tendis-single只有一行standard_init_linux.go:228: exec user process caused: exec format error原因非常直接宿主机是 ARM64但镜像实际是 x86_64。这种镜像往往是拿一台 x86 机器用docker pull直接导出 tar 带进内网的。解决方法是回到联网机用docker buildx build --platform linux/arm64重新构建或者直接用docker pull --platform linux/arm64拉取带架构标签的镜像。导入前用docker image inspect检查架构是必须的动作最好写进 deploy.sh 里元数据不匹配直接拒绝启动。5.2 现象docker-compose 命令找不到离线机上执行脚本报command not found或者docker-compose: command not found。原因很常见docker-compose 不在/usr/local/bin下或者只装了 compose 插件但没有二进制别名。解决方法是下载 aarch64 的docker-compose-linux-aarch64放到/usr/local/bin/docker-compose并加上执行权限。如果系统提示 “please usedocker compose” 而不是docker-compose说明 docker 已经以插件形式安装 compose那么脚本里改用docker compose去掉横杠也能跑。但为了兼容我在脚本里写了一个分支判断if docker compose version /dev/null 21; then docker compose -f $COMPOSE_FILE up -d else docker-compose -f $COMPOSE_FILE up -d fi5.3 现象端口已占用导致 bind 失败启动时日志里出现bind: Address already in use容器一直重启。原因往往是宿主机上的 6379 已经被其他 Redis 实例占用了。一种解决是修改docker-compose.yml的端口映射把宿主机端口改成别的值比如 6380。另一种是用ss -lntp确认占用来源。Tendis 容器自身的端口可以不变宿主映射变了不影响容器内部监听。5.4 现象容器正常但 redis-cli 连接超时docker logs显示 Tendis 已经起来但外面用redis-cli -p 6379就连不上。原因有两个高频点一是 Tendis 配置里bind只写了127.0.0.1二是宿主机防火墙没放行。解决方法是把配置文件里的bind改成0.0.0.0并且确认requirepass没误设。如果还不行检查/proc/sys/net/ipv4/ip_forward是否开启虽然 docker 一般自动处理但在某些改过内核参数的内网机上可能造成端口映射失效。5.5 现象RocksDB 在容器里频繁报 Errno 13日志里经常出现IO error: No space left on device或Permission denied但硬盘明明有剩余空间。原因多数是/data目录的属主不是容器内用户RocksDB 写 WAL 和 SST 文件时没有权限。解决方式是使用命名卷让 Docker 来管理目录权限如果坚持使用 bind mount就在 deploy.sh 里加一行chown。还有一个变体SELinux 开启了隔离导致容器写不了宿主机目录。临时用chcon -Rt container_file_t ./data解决但更省事的方式是直接把 SELinux 设成 permissive如果公司安全策略允许。6. 验证与进阶redis-cli 打通后还能做什么部署完成后验证不能只停留在“容器起来了”。我会依次跑这几条命令docker exec -it tendis-single redis-cli ping docker exec -it tendis-single redis-cli set a 1 docker exec -it tendis-single redis-cli get a docker exec -it tendis-single redis-cli info server输出PONG后写一个 key重启容器再读一遍确认数据持久化正常工作。info server里的redis_version会显示 Tendis 的版本号这能确认当前跑的确实是 2.7.0而不是误挂到其他 Redis 镜像。进阶用法上我会把健康检查接入部署脚本作为“真正可用”的判断条件for i in $(seq 1 30); do if docker inspect --format{{.State.Health.Status}} tendis-single | grep -q healthy; then echo tendis is ready exit 0 fi sleep 2 done echo tendis not healthy in 60s 2 exit 1这段循环会等待容器健康检查通过再给业务侧返回成功。离线交付时可以把这段逻辑放进 deploy.sh 的最后避免业务脚本接不到服务。更进阶一点的用法是把单机版工具扩展成多实例管理。Tendis 单机版本身没有哨兵机制但可以用 docker-compose 的多个服务来分别跑不同端口例如tendis-cache和tendis-data每个服务都有自己的数据卷和配置。这样既隔离了数据又避免了改 app 去连不同 socket 的麻烦。我以前会把每个实例的container_name写死方便docker exec直接进去看日志但如果你要横向扩容就别写死容器名用服务名来自动生成。最后说一个我个人的习惯上生产前我会先跑一遍redis-benchmark或者redis-cli --stat看 QPS 是否正常然后用docker logs --tail 200扫一遍警告。Tendis 的日志里如果出现Updating bounds之类的 RocksDB 信息其实不用紧张真正需要关注的是反复出现的IO error和Slow Level-0。这些细节在离线部署时容易被忽略但往往决定你到底是一个人睡个好觉还是半夜起来重置容器。希望帮到你。本文还有配套的精品资源点击获取
