ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

在CentOS 7用Docker Compose部署APISIX网关全流程

在CentOS 7用Docker Compose部署APISIX网关全流程

在 CentOS 7 这种老牌服务器系统上部署一个现代化的 API 网关,是我这几年经常被问到的事。Apache APISIX 是当前热度很高的云原生网关项目,它把 Nginx 的稳定性和动态路由、插件化的控制面能力结合到了一起;而 Docker 又能把 APISIX 的整套依赖环境打包好,让你不用在 CentOS 7 上手动折腾 OpenResty、LuaJIT、luarocks 这些组件。这篇文章记录的是我在实际环境里用 Docker Compose 在 CentOS 7 上安装并打通 APISIX 全流程的完整过程,包括环境准备、编排文件、路由创建、常见坑位和排错记录。适合已经会一点 Linux 和 Docker、想快速体验或评估 API 网关的读者,也适合想把 APISIX 作为正式网关前先在测试环境里完整跑一遍的团队。

1. 方案选型:APISIX 网关 + Docker 这套组合为什么成立

1.1 APISIX 解决的是传统网关做不到的事

如果你用过传统 Nginx 做转发,大概会有这种感受:改一个 location 要编辑配置文件,然后nginx -s reload;如果是多台机器,还得逐台同步;想加个限流、加个鉴权,要么装第三方模块编译,要么自己写不少 Lua 脚本。这在一两个实例时还能接受,服务一多、路由一变,立刻变成运维负担。

APISIX 的思路完全不一样。它把路由、上游服务、插件这几个核心配置对象从文件系统挪到了 etcd 里,APISIX 的数据面进程会实时监听 etcd 的变化。你在 Admin API 上创建一个路由,几毫秒内整个网关集群都能感知,不需要 reload,不需要重启。这在做灰度发布、AB 测试、动态切流量时尤其有用。

插件系统也是 APISIX 的强项。限流限速、JWT 鉴权、Key Auth、CORS、响应改写、Mock、gRPC 转码,这些能力都以插件形式挂在路由上,用一句配置就能启用。不需要像传统 Nginx 那样为每个模块操心编译问题。对二次开发者来说,APISIX 的插件用 Lua 编写,架构清晰,和 OpenResty 生态也兼容。

所以如果你的场景只是"一台 Nginx 转发几个静态域名",APISIX 反而显得重;但如果你要面对几十上百个微服务、要统一做认证和限流、要可视化地管理路由,APISIX 的方向就是对的。

1.2 Docker 部署 vs 源码编译的取舍

APISIX 官方文档其实更推荐用源码或 RPM 方式部署,但我在 CentOS 7 上试过编译安装,体验是真的繁琐。CentOS 7 默认的 gcc 版本旧,OpenResty 和 Lua 生态的组件版本匹配要求又高,装个依赖都可能要折腾半小时,一旦某个 lua-resty 库版本不对,编译期就翻车。而且编译安装的产物散落在系统里,以后想升级、想回滚,都是一笔糊涂账。

换成 Docker 之后,最直观的好处是依赖被打包在镜像里。你只需要关心两件事:镜像版本和挂载进去的配置文件。升级时换 tag 重建容器,回滚时再换回旧 tag,干净利落。对 CentOS 7 这种系统环境相对保守的服务器来说,用容器把网关隔离起来,还能避免污染系统动态库。

当然 Docker 部署也有它的代价:首先你要有一个能稳定运行的 Docker 环境,像 CentOS 7 这种老系统,Docker 版本不能乱追;其次 APISIX 的路由数据存在 etcd 容器里,必须做好数据卷和备份,否则容器一删配置全丢。这两点我在后面都会重点讲到。

1.3 部署前必须理解的三个核心组件

这套部署方案里其实有四个角色,但核心组件可以概括为三块:etcd、APISIX 本体、Dashboard。

etcd 是整个网关的配置仓库。路由规则、上游服务、插件配置、SSL 证书,全部作为 key-value 存在 etcd 里。没有 etcd,APISIX 就是空壳。生产环境 etcd 要单独做高可用,测试环境至少给它配一个稳定的数据卷。

APISIX 本体是数据面。它内部仍然基于 OpenResty,所以本质上还是一位"加强版 Nginx",但它不直接读文件配置,而是从 etcd 拉取动态配置,再把请求转发到真实后端。

Dashboard 是可视化管理台,提供 Web 界面来创建路由、配插件、看监控。它不是网关运行的必要组件,但没有它,新手用 Admin API 操作会有一定门槛。对我个人来说,测试环境装一个 Dashboard 省很多事,生产环境则不一定开放它。

2. CentOS7 环境准备:先把 Docker 这条裤子穿好

2.1 CentOS7 上 Docker 版本选择和安装步骤

CentOS 7 的内核停留在 3.10,这是 Docker 能不能装新版的关键限制。Docker 官方仓库目前针对 CentOS 7 能正常安装的最高版本基本就是 20.10.x 这个系列,再新的版本往往直接放弃对 RHEL 7 / CentOS 7 的支持。所以不要试图在这套系统上装 Docker 24 或更新版本,20.10 反而最稳定。

安装过程不复杂,下面这套命令我在多台 CentOS 7 机器上跑过:

# 先卸载系统里可能存在的旧 docker yum remove docker docker-common docker-selinux docker-engine -y # 安装 yum 源管理工具和存储驱动依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加 Docker 官方 yum 源 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装 Docker 引擎 yum install -y docker-ce docker-ce-cli containerd.io # 启动并设置开机自启 systemctl enable --now docker # 验证版本 docker version

有个需要注意的细节是,CentOS 7 的 yum 可能会把 docker-ce 更新到仓库里能拿到的最新版本。为了避免某个不注意的yum update把 Docker 升级到不支持 CentOS 7 的版本,建议给 Docker 相关包加个版本锁:

yum install -y yum-plugin-versionlock yum versionlock docker-ce docker-ce-cli containerd.io

如果你在安装过程中遇到 "No package docker-ce available" 这种错误,多半是 yum 缓存问题,先执行yum clean all && yum makecache fast再重试。

2.2 Docker Compose 工具准备

APISIX 官方推荐的容器部署方式就是用 Docker Compose,因为它需要同时拉起 etcd、APISIX、Dashboard 三个服务。Compose 工具在 CentOS 7 上有两种装法:一种是装 Docker 官方仓库里的docker-compose-plugin,装上后命令是docker compose;另一种是下载独立二进制docker-compose,命令就是docker-compose。

我在 CentOS 7 上的习惯是直接用 Compose v1 的独立二进制。原因很简单:CentOS 7 的系统 Python 太老,Compose V2 插件在某些老版本 Docker 上偶尔会有兼容性怪问题。独立二进制不依赖系统 Python,放上去就能用:

curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose docker-compose --version

看到docker-compose version 1.29.2就说明 OK 了。下面文章里的命令我都使用docker-compose这种写法,如果你装的是 Compose V2 插件,自己把命令替换成docker compose即可。

2.3 防火墙、SELinux 与 Docker 网段的三个预检项

CentOS 7 上跑 Docker 容器后连不通,十次有八次不是 APISIX 的问题,而是系统层面的网络预检没做。这里有三件事我每次都会先检查一遍。

第一,firewalld 和 iptables 的 NAT 规则。Docker 的端口映射靠的是 iptables,但 CentOS 7 默认又开着 firewalld,两者经常会打架。如果容器里能访问、宿主机访问不了,首先检查防火墙是否放行了端口:

firewall-cmd --permanent --zone=public --add-port=9080/tcp firewall-cmd --permanent --zone=public --add-port=9180/tcp firewall-cmd --permanent --zone=public --add-port=9000/tcp firewall-cmd --reload

第二,SELinux。CentOS 7 默认 SELinux 是 enforcing 状态,如果你用自定义目录挂载 APISIX 的配置文件,SELinux 可能拦截容器读取。最直接的解决方法是给目录打上容器文件标签:

chcon -Rt container_file_t /opt/apisix

如果不处理,你可能会看到容器启动失败或配置根本没加载进去,排错时很难想到这里。

第三,Docker 默认网段冲突。Docker 默认的 bridge 网段是 172.17.0.0/16,这个网段如果和你们公司内网或云上 VPC 段撞了,外部主机回包很可能直接走错路由。遇到这种问题,可以改 Docker 的 bridge 地址:

{ "bip": "10.90.0.1/24" }

写到/etc/docker/daemon.json后执行systemctl restart docker。注意改网段会重建 Docker 默认网络,已有容器需要重启才可能恢复正常连接。

3. 用 Docker Compose 把 APISIX 全家桶跑起来

3.1 准备目录和配置文件骨架

部署之前先把目录结构规划好。我把整套东西放在/opt/apisix下面,配置文件统一放configs,日志统一放logs:

mkdir -p /opt/apisix/{configs,logs} cd /opt/apisix

然后创建两个配置文件:一个是 APISIX 主配置,一个是 Dashboard 配置。这里先说最终的docker-compose.yml,让整体结构有个概念:

version: "3.8" networks: apisix-net: driver: bridge volumes: etcd-data: services: etcd: image: bitnami/etcd:3.5.11 container_name: apisix-etcd restart: always environment: - ALLOW_NONE_AUTHENTICATION=yes - ETCD_NAME=apisix-etcd - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 volumes: - etcd-data:/bitnami/etcd networks: - apisix-net apisix: image: apache/apisix:3.9.0-centos container_name: apisix restart: always depends_on: - etcd ports: - "9080:9080" - "127.0.0.1:9180:9180" volumes: - ./configs/apisix_config.yaml:/usr/local/apisix/conf/config.yaml:ro - ./logs:/usr/local/apisix/logs environment: - TZ=Asia/Shanghai networks: - apisix-net dashboard: image: apache/apisix-dashboard:3.0.0-alpine container_name: apisix-dashboard restart: always depends_on: - etcd - apisix ports: - "9000:9000" volumes: - ./configs/dashboard_conf.yaml:/usr/local/apisix-dashboard/conf/conf.yaml:ro networks: - apisix-net mock-echo: image: nginx:alpine container_name: mock-echo restart: unless-stopped ports: - "8081:80" networks: - apisix-net

这里面mock-echo是后面用来验证路由的后端服务,生产环境可以删掉或替换成你的真实业务服务。etc 和 APISIX 的配置都在下面的小节单独拆解。

3.2 etcd:网关配置的持久化底座

etcd 是整个网关的"持久化底座",所以我可以先解释为什么它需要这么配置。ETCD_LISTEN_CLIENT_URLS和ETCD_ADVERTISE_CLIENT_URLS是 etcd 最关键的两个参数:前者控制 etcd 监听哪个地址,后者告诉其他客户端"请用这个地址访问我"。

在 Compose 网络里,etcd这个服务名就是容器之间的 DNS 名,所以 advertise 地址写http://etcd:2379是让 APISIX 和其他容器能通过服务名找到它。我把 etcd 的 2379 端口只保留在 Compose 内部网络里,没有映射到宿主机,这是安全考量——外部完全不需要直接访问 etcd。

数据卷etcd-data必须保留,这是路由配置能不能持久化的关键。如果这里不挂卷,你创建一个路由,然后docker-compose down再up,路由直接消失。

ALLOW_NONE_AUTHENTICATION=yes是 bitnami 镜像的约定配置,意思是允许无认证启动。测试环境可以这么写,但一旦放到多租户或生产环境,一定要给 etcd 配上用户名密码或者使用 TLS 方案。

3.3 APISIX 容器:核心编排参数说明

APISIX 容器的核心是挂载进去的configs/apisix_config.yaml。这是我整理的最小可用版本:

apisix: node_listen: 9080 enable_ipv6: false deployment: role: traditional role_traditional: config_provider: etcd admin: admin_key: - name: admin key: "请替换成你自己的随机字符串" role: admin etcd: host: - "http://etcd:2379" prefix: "/apisix" timeout: 30

逐个说一下为什么这么配。

node_listen: 9080是让 APISIX 数据面监听 9080 端口。对外只暴露这个端口接收外部流量,这个设计符合网关的常规使用方式。

deployment.role: traditional表示传统模式,也就是 APISIX 实例从 etcd 拉取配置并处理请求。config_provider: etcd明确告诉 APISIX 配置来源是 etcd,而不是本地文件。如果以后想跑数据面与控制面分离的混合模式,这个字段会变成control或data_plane,但测试环境不需要。

admin.admin_key是 Admin API 的调用密钥。这里千万别留着官方示例里的默认值,我见过有人图省事用默认 key 上线,结果任何人都能通过 9180 端口增删路由,这是非常危险的。替换成你自己的随机字符串后,重跑容器就会生效。

deployment.etcd.host指向 etcd 服务名。这里要强调一个新手容易踩的坑:APISIX 容器里的127.0.0.1不是宿主机,如果写http://127.0.0.1:2379,APISIX 会去找自己容器内部的 etcd,结果连接失败。在 Compose 网络里,一定要写服务名etcd。

prefix: "/apisix"是 etcd 里的 key 前缀。如果多个环境共用同一个 etcd 集群,不同环境要使用不同 prefix,避免路由互相覆盖。

配置文件挂载时我加了:ro,让容器内只读。这样即使有人docker exec进容器误改了配置,容器重启后也会恢复成宿主机上的内容。修改配置后,执行docker-compose restart apisix即可生效。

3.4 Dashboard:可视化管理面板接入

Dashboard 配置文件相对简单,放到configs/dashboard_conf.yaml:

server: listen: host: 0.0.0.0 port: 9000 enable_cors: true authentication: secret: "请替换成随机字符串" expire_time: 3600 etcd: endpoints: - "http://etcd:2379"

server.listen是 Dashboard 自身监听端口,对应 Compose 里的9000:9000。authentication.secret是 Dashboard 用来签发登录态 token 的密钥,随便换一个随机串就行。etcd.endpoints告诉 Dashboard 去哪里读 APISIX 的配置数据,这里同样用服务名etcd。

Dashboard 默认登录账号一般是 admin,初始密码通常是 admin。第一次登录后记得立刻改掉。如果版本更新导致默认密码有变化,以官方 release 说明为准。

顺便说一句,Dashboard 不是 APISIX 的必要组件。如果你只想用脚本或 CI/CD 管路由,完全可以不启动它。我测试环境习惯留着 Dashboard,便于临时看数据和手工验证。

3.5 启动一条龙与初始化验证

配置文件准备好之后,在/opt/apisix目录下执行:

docker-compose up -d docker-compose ps

正常情况下你会看到 etcd、apisix、dashboard、mock-echo 四个容器都是 Up 状态。首次启动看日志确认没有报错:

docker logs -f apisix

如果 APISIX 一直 CrashLoopBackOff,大概率是连不上 etcd。先docker logs apisix看报错,再从容器里测试到 etcd 的连通性:

docker exec apisix curl http://etcd:2379/version

能返回版本信息说明网络没问题,接着检查 config.yaml 里的 admin key 和 etcd 地址是否写对。

初始化验证最直接的办法是调一次 Admin API:

curl http://127.0.0.1:9180/apisix/admin/routes -H "X-API-KEY: 你配置的key"

如果返回类似{"list":[],"total":0}的 JSON,说明管理通道已通。注意 9180 我映射在宿主机的 127.0.0.1 上,所以外部网络访问不到这个管理端口,这也是刻意的安全设计。

4. 打通第一个 API 路由:从 Admin API 到业务后端

4.1 先准备一个用于验证的后端容器

前面编排文件里已经包含了一个mock-echo服务。它跑的是nginx:alpine,相当于一个最简单的后端 Web 服务。为了让返回内容更容易辨认,可以在容器里替换一下默认首页:

docker exec -it mock-echo sh -c 'echo "<h1>hello from apisix backend</h1>" > /usr/share/nginx/html/index.html' curl http://127.0.0.1:8081/

看到hello from apisix backend就说明后端已就绪。

生产环境里这里就是一个真实的业务服务,可能是 Java 应用、Go 应用或者另一个容器。只要它和 APISIX 处于同一个 Docker 网络,并且你能通过"服务名:端口"访问到它,APISIX 的 upstream 节点就可以指向它。

4.2 通过 Admin API 创建路由对象

现在创建一个最简单的路由,把/hello开头的请求转发到mock-echo:80:

curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -X PUT \ -H "X-API-KEY: 你配置的key" \ -d '{ "name": "demo-route", "uri": "/hello*", "upstream": { "type": "roundrobin", "nodes": { "mock-echo:80": 1 } } }'

这里为什么用PUT而不是POST?因为PUT /apisix/admin/routes/1表示创建或更新 id 为 1 的路由,具有幂等性。假如用POST /apisix/admin/routes,APISIX 会随机生成一个 id,不利于后续脚本化管理。

uri写/hello*表示匹配以/hello开头的路径。upstream.nodes是后端节点列表,mock-echo:80是 Compose 网络里的服务名和端口。如果后端不在同一个网络里,可以填内网 IP,但 IP 变了就得改路由,远不如服务名方便。

创建成功后返回的 JSON 会包含完整路由对象,其中"key": "/apisix/routes/1"说明这条路由已经写入 etcd。

4.3 验证请求能正确转发到后端

路由创建后,立即测试:

curl -i http://127.0.0.1:9080/hello

理论上会返回 200,并且响应体是后端容器里的hello from apisix backend。

再试一个不存在的路径:

curl -i http://127.0.0.1:9080/not-exist

因为没匹配到任何路由,APISIX 会返回 404。这说明路由的匹配逻辑是正常的。

如果你这时候修改路由,比如把 upstream 的节点权重改一下,再马上请求一次,APISIX 会直接按新配置转发,不需要重启。这就是动态网关的核心体验:配置变更的生效是即时的。

4.4 Dashboard 界面的操作路径补充

如果你开了 Dashboard,可以直接打开http://服务器IP:9000登录,然后在左侧菜单找到"路由",点击创建路由。一般只需要填两个核心内容:匹配路径,也就是 URI;上游服务,也就是要转发到哪台后端。保存后回到命令行再请求一次 9080 端口,效果和用 Admin API 创建是完全一样的,因为 Dashboard 底层写的就是同一个 etcd。

我个人建议把 Admin API 和 Dashboard 两种方式都掌握。日常调试用 Dashboard 很方便,但批量操作、自动化发布还是走 Admin API 脚本更靠谱。

5. 常见问题与排错实录:CentOS7 上最容易踩的坑

5.1 外部访问不到 APISIX

最常见的问题是容器起来了、宿主机 curl 9080 也有响应,但局域网其他机器访问不到。这个坑基本都在防火墙和 iptables 层面。先按第 2.3 节放行端口,如果还不行,检查宿主机上有没有自定义 iptables 规则把 Docker 的链搞乱了。

还有一种隐蔽情况是公司的局域网和 Docker 默认网段 172.17.0.0/16 冲突。比如你公司的办公网段恰好也用了 172.17.x.x,外部机器访问网关时会因为路由分不清到底走哪条路径而卡住。解决办法就是前面提到的改 Docker daemon 的bip参数,换一个不冲突的网段。

如果以上都排查完了还是不通,试着systemctl restart docker。这是一个"重启大法",会重建 Docker 的 NAT 链,但副作用是重启所有容器,非必要别在生产环境随便用。

5.2 容器重启后路由配置全部丢失

很多人都栽在这一条上。APISIX 的路由本身不在 APISIX 容器里,而是在 etcd 里。如果你在 Compose 文件里没有给 etcd 挂 volume,只要执行docker-compose down -v或者有人手滑删了 etcd 容器,所有路由配置瞬间清空。

正确做法就是第 3.2 节里说的,把etcd-data这个 volume 挂到 etcd 容器。还要注意docker-compose down不会删除 volume,但docker-compose down -v会连 volume 一起删掉,这个命令在生产上要慎重。

更稳妥的做法是把路由配置本身也用代码管理起来。我通常会在 Git 仓库里维护一组初始化脚本,通过 Admin API 批量重建路由和上游。这样即使 etcd 数据彻底损坏,也能在几分钟内恢复网关配置。

5.3 Admin API 返回 403 或一直超时

返回 403 基本都是 API key 不匹配。先确认 config.yaml 里deployment.admin.admin_key的 key 字段和你 curl 请求头里的X-API-KEY一致。如果改了配置,记得要重启 APISIX 容器,:ro挂载的文件不会热生效。

如果是超时,优先怀疑 9180 端口没有正常映射。我在 Compose 里故意把 9180 绑到了 127.0.0.1,所以如果容器内正常、宿主机 curl 超时,先看ss -lnt | grep 9180确认监听地址。要远程管理的话,建议走 SSH 隧道,而不是把管理端口暴露到公网。

5.4 日志时区、日志增长与容器 OOM

APISIX 容器默认使用 UTC 时区,日志时间和北京时间差 8 小时,排查问题时很容易看错时间线。我在 Compose 里给 apisix 服务加了TZ=Asia/Shanghai环境变量,docker 容器内日志时间就能对齐。

APISIX 挂载./logs到宿主机后,access.log和error.log会一直增长。我遇到过一次日志把磁盘写满的故障。处理的时候注意不要直接rm日志文件,因为 Nginx 进程还持有文件句柄,删了也不会释放空间,反而可能出现"文件没了但磁盘没释放"的诡异状态。正确做法是:

truncate -s 0 /opt/apisix/logs/error.log

容器 OOM 通常是宿主机内存不够。CentOS 7 上跑 APISIX 加上 etcd,最低建议给 2G 内存。如果机器紧张,可以在 Compose 里给容器加内存限制:

apisix: mem_limit: 1g

超限时容器可能被系统杀掉,检查dmesg | grep -i oom能确认是不是这个原因。

5.5 一些安全加固建议

最后说几条实际部署时的加固经验。Admin API 不能暴露公网,我平时的做法是只绑 127.0.0.1,远程管理走 SSH 隧道,或者通过内部跳板机访问。etcd 端口不要映射到宿主机,它只服务内部网络。Dashboard 如果必须在生产开,至少加一层访问控制,别让它裸奔在公网。

TLS 证书也是容易忽略的点。APISIX 支持在路由上挂证书并通过 Admin API 动态下发,生产环境上线 HTTPS 时不要用测试自签证书,而是通过正规证书渠道签发后,把证书内容作为 SSL 资源配置写进 Admin API。这样证书更新也不需要重启网关。

其实这套组合用久了会发现,APISIX 真正带来的价值不只是"Docker 里跑了个网关",而是把网关的配置变更变成了可以自动化、可以版本化的操作。我个人的习惯是每次搭完环境后,把所有 compose 文件和初始化脚本提交到内部代码仓库,这样即便是全新的机器,也能在十几分钟内把网关完整恢复出来。这个习惯帮我省过不少麻烦,建议你也试试。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进