Attu 连接本地 Milvus 双向 TLS(mTLS)实战指南:Docker 证书生成、Compose 配置与连接验证

原创2026-10-14 13:58:501,638 阅读

Attu 连接本地 Milvus 双向 TLS(mTLS)实战指南:Docker 证书生成、Compose 配置与连接验证

本指南基于仓库官方文档 docs/milvus-mtls-local-docker.md 展开,完整演示如何在本地 Docker 环境中为 Milvus Standalone 启用双向 TLS(mutual TLS,简称 mTLS),并让 Attu 通过客户端证书安全接入 Milvus。读完本文,你将掌握自签名 CA 与证书的生成流程、Milvus Docker Compose 的 TLS 配置、Attu 容器的 mTLS 连接预配置方法,以及容器内基于 Milvus Node.js SDK 的端到端验证手段。

适用场景:本地开发与联调验证。文中的自签名证书命令仅适用于本地测试环境,严禁直接照搬到生产环境——生产环境应使用企业 CA 或密钥管理系统签发并轮换的证书。

mTLS 基础:双向 TLS 的信任模型与 Attu 的 TLS 连接配置

Milvus 的 gRPC 服务监听在 19530 端口(另暴露 9091 端口用于健康检查与指标)。在单向 TLS 下,只有客户端校验服务端证书;而 mTLS 是双向校验:

  • Milvus(服务端)向 Attu(客户端)出示服务端证书 server.pem,Attu 用 CA 证书 ca.pem 校验其合法性;
  • Attu 同时出示客户端证书 client.pem 与私钥 client.key,Milvus 用同一个 CA 反向校验客户端身份;
  • 双方共同信任同一个 CA,实现"双向握手、互相验明正身"。

在 Attu 侧,TLS 连接由一组 MILVUS_TLS_* 环境变量控制,这与仓库根目录 README.md 中 "TLS / SSL" 小节(README.md#L177-L194)给出的配置完全一致:

环境变量 示例值 作用
MILVUS_SSL true 开启 TLS 加密连接
MILVUS_TLS_ROOT_CERT_PATH /etc/attu/certs/ca.pem CA 根证书路径,用于校验 Milvus 服务端
MILVUS_TLS_PRIVATE_KEY_PATH /etc/attu/certs/client.key 客户端私钥路径
MILVUS_TLS_CERT_CHAIN_PATH /etc/attu/certs/client.pem 客户端证书链路径
MILVUS_TLS_SERVER_NAME standalone 主机名校验(SNI),必须匹配服务端证书 SAN 中的 DNS 名称

在 Milvus 侧,TLS 强度由 COMMON_SECURITY_TLSMODE 控制。按 Milvus 的安全配置约定:0 表示关闭 TLS,1 表示单向 TLS(仅校验服务端证书),2 表示双向 TLS(服务端、客户端证书都校验)。本指南使用的 COMMON_SECURITY_TLSMODE: 2 正是开启外部 gRPC 双向 TLS 的关键开关。

版本差异提醒:仓库中的 README_CN.md 在 SSL 示例(README_CN.md#L157-L168)中展示的是 Attu v2.6 时代的配置方式,使用 ROOT_CERT_PATH、PRIVATE_KEY_PATH、CERT_CHAIN_PATH、SERVER_NAME 等不带 MILVUS_ 前缀的变量,且证书挂载目录为 /app/tls。本文基于 Attu v3(zilliz/attu:v3.0.0-beta.4),变量名与挂载路径(/etc/attu/certs)均已更新,请勿混用。

1. 生成本地 TLS 证书:CA、Milvus 服务端证书与 Attu 客户端证书

证书将统一生成在 ~/workspace/volumes/milvus/tls 目录下,后续 Milvus 与 Attu 容器都从这个目录挂载证书。

1.1 核心设计:openssl.cnf 与 SAN

证书生成依赖一份 openssl.cnf 配置文件,它同时定义了 CA、服务端、客户端三类证书的扩展属性,关键设计如下:

  • [ v3_ca ]:CA 证书扩展,声明 basicConstraints = critical, CA:true 与 keyCertSign,使该证书具备签发其他证书的资格;
  • [ v3_req ]:服务端证书扩展,extendedKeyUsage = serverAuth 限定用途为服务器认证,并通过 subjectAltName = @alt_names 引用 SAN 列表;
  • [ v3_client ]:客户端证书扩展,extendedKeyUsage = clientAuth 限定用途为客户端认证(mTLS 中服务端校验客户端身份的关键依据);
  • SAN 列表([ alt_names ])包含 milvus-standalone、standalone、localhost、milvus、host.docker.internal 及回环 IP。其中 standalone 是必须项:本指南中 Attu 容器通过 Docker 网络 DNS 以 standalone:19530 访问 Milvus,服务端证书必须覆盖该主机名,否则 TLS 握手阶段的主机名校验会失败。

1.2 完整生成命令

CERT_DIR="$HOME/workspace/volumes/milvus/tls"
mkdir -p "$CERT_DIR"

cat > "$CERT_DIR/openssl.cnf" <<'EOF'
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req

[ dn ]
C = US
ST = CA
L = Local
O = Milvus Local Test
OU = Attu TLS Test
CN = milvus-standalone

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_req ]
basicConstraints = CA:false
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = milvus-standalone
DNS.2 = standalone
DNS.3 = localhost
DNS.4 = milvus
DNS.5 = host.docker.internal
IP.1 = 127.0.0.1
IP.2 = ::1

[ v3_client ]
basicConstraints = CA:false
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
EOF

openssl genrsa -out "$CERT_DIR/ca.key" 4096
openssl req -x509 -new -nodes -key "$CERT_DIR/ca.key" -sha256 -days 3650 \
  -subj "/C=US/ST=CA/L=Local/O=Milvus Local Test/OU=Attu TLS Test/CN=Milvus Local Test CA" \
  -out "$CERT_DIR/ca.pem" \
  -extensions v3_ca -config "$CERT_DIR/openssl.cnf"

openssl genrsa -out "$CERT_DIR/server.key" 2048
openssl req -new -key "$CERT_DIR/server.key" -out "$CERT_DIR/server.csr" \
  -subj "/C=US/ST=CA/L=Local/O=Milvus Local Test/OU=Attu TLS Test/CN=milvus-standalone" \
  -config "$CERT_DIR/openssl.cnf"
openssl x509 -req -in "$CERT_DIR/server.csr" -CA "$CERT_DIR/ca.pem" -CAkey "$CERT_DIR/ca.key" -CAcreateserial \
  -out "$CERT_DIR/server.pem" -days 3650 -sha256 -extensions v3_req -extfile "$CERT_DIR/openssl.cnf"

openssl genrsa -out "$CERT_DIR/client.key" 2048
openssl req -new -key "$CERT_DIR/client.key" -out "$CERT_DIR/client.csr" \
  -subj "/C=US/ST=CA/L=Local/O=Milvus Local Test/OU=Attu TLS Test/CN=attu-client" \
  -config "$CERT_DIR/openssl.cnf"
openssl x509 -req -in "$CERT_DIR/client.csr" -CA "$CERT_DIR/ca.pem" -CAkey "$CERT_DIR/ca.key" -CAcreateserial \
  -out "$CERT_DIR/client.pem" -days 3650 -sha256 -extensions v3_client -extfile "$CERT_DIR/openssl.cnf"

chmod 755 "$CERT_DIR"
chmod 644 "$CERT_DIR"/*.pem "$CERT_DIR"/*.key
ls -l "$CERT_DIR"

命令要点(对应 docs/milvus-mtls-local-docker.md#L19-L89):

  • CA 私钥与自签名证书:genrsa 生成 4096 位 CA 私钥,req -x509 以 v3_ca 扩展自签 CA 证书,有效期 3650 天;
  • 服务端证书:genrsa 生成 2048 位私钥 → req -new 产出 CSR → x509 -req 用 CA 签发,签发时通过 -extensions v3_req -extfile 注入 SAN 与 serverAuth 用途;
  • 客户端证书:流程与服务端相同,仅 CN=attu-client 且签发时使用 -extensions v3_client,携带 clientAuth 用途;
  • 权限收尾:目录设为 755,证书与私钥设为 644,保证容器只读挂载时各用户可读。

1.3 产出文件说明

文件 角色
ca.pem CA 证书,Attu 用它校验 Milvus 服务端证书
server.pem / server.key Milvus 服务端证书与私钥
client.pem / client.key Attu 客户端证书与私钥(mTLS 双向认证用)

2. 在 Milvus Docker Compose 中启用 mTLS

2.1 standalone 服务配置

编辑 Milvus 的 docker-compose.yml(本指南使用 ~/workspace/docker-compose.yml),为 standalone 服务开启双向 TLS,并让 Milvus 从挂载在 /var/lib/milvus/tls 下的证书文件读取密钥(完整片段见 docs/milvus-mtls-local-docker.md#L103-L132):

services:
  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v3.0-beta
    command: ["milvus", "run", "standalone"]
    security_opt:
      - seccomp:unconfined
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
      MQ_TYPE: woodpecker
      COMMON_SECURITY_TLSMODE: 2
      PROXY_HTTP_ENABLED: "false"
      TLS_SERVERPEMPATH: /var/lib/milvus/tls/server.pem
      TLS_SERVERKEYPATH: /var/lib/milvus/tls/server.key
      TLS_CAPEMPATH: /var/lib/milvus/tls/ca.pem
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
    ports:
      - "19530:19530"
      - "9091:9091"
    depends_on:
      - etcd
      - minio

networks:
  default:
    name: milvus

2.2 关键配置项说明

配置项 值 说明
COMMON_SECURITY_TLSMODE 2 开启 Milvus 外部 gRPC 双向 TLS(mTLS)
TLS_SERVERPEMPATH /var/lib/milvus/tls/server.pem 服务端证书路径
TLS_SERVERKEYPATH /var/lib/milvus/tls/server.key 服务端私钥路径
TLS_CAPEMPATH /var/lib/milvus/tls/ca.pem CA 证书路径,用于校验客户端证书
PROXY_HTTP_ENABLED "false" 避免开启 TLS 时 Milvus 试图让 proxy HTTP 与外部 gRPC 共用同一端口
ETCD_ENDPOINTS / MINIO_ADDRESS / MQ_TYPE etcd:2379 / minio:9000 / woodpecker Milvus Standalone 的元数据、存储与消息队列依赖(与同 Compose 文件中的 etcd、minio 服务配合)
networks.default.name milvus 将 Compose 网络命名为 milvus,供后续 Attu 容器加入

上述片段是 standalone 服务的局部示例,实际使用时请保留原 Compose 文件中的 etcd、minio 服务定义与其余配置。

2.3 重启并确认状态

docker compose -f ~/workspace/docker-compose.yml up -d
docker compose -f ~/workspace/docker-compose.yml ps

等待 milvus-standalone 进入 healthy 状态后再进行下一步。重启后,Milvus 的 19530 端口对外仅接受 mTLS 握手——没有客户端证书的任何连接都将被拒绝。

3. 拉取 Attu 镜像

docker pull zilliz/attu:v3.0.0-beta.4

本指南经 milvusdb/milvus:v3.0-beta 与 zilliz/attu:v3.0.0-beta.4 组合验证。若需在其他 Milvus 版本上部署,可参考仓库 README.md 的兼容性矩阵(README.md#L210-L219)选择对应 Attu 版本。

4. 启动 Attu 并预配置 mTLS 连接

4.1 docker run 命令

以下命令启动 Attu、以只读方式挂载刚生成的证书,并在首次启动时自动创建一条指向 mTLS Milvus 的默认连接(完整命令见 docs/milvus-mtls-local-docker.md#L160-L175):

docker rm -f attu-tls-test >/dev/null 2>&1 || true && \
docker run -d \
  --name attu-tls-test \
  --network milvus \
  -p 3000:3000 \
  -v "$HOME/workspace/volumes/milvus/tls:/etc/attu/certs:ro" \
  -e ATTU_DB_PATH=/tmp/attu-tls-test.db \
  -e MILVUS_NAME=milvus-mtls-local \
  -e MILVUS_ADDRESS=standalone:19530 \
  -e MILVUS_SSL=true \
  -e MILVUS_TLS_ROOT_CERT_PATH=/etc/attu/certs/ca.pem \
  -e MILVUS_TLS_PRIVATE_KEY_PATH=/etc/attu/certs/client.key \
  -e MILVUS_TLS_CERT_CHAIN_PATH=/etc/attu/certs/client.pem \
  -e MILVUS_TLS_SERVER_NAME=standalone \
  zilliz/attu:v3.0.0-beta.4

启动后,浏览器访问 http://localhost:3000 即可打开 Attu。

4.2 参数逐项说明

参数 / 变量 值 说明
--network milvus — Attu 容器加入 Compose 创建的 milvus 网络,从而可通过 Docker DNS 访问 standalone:19530
-p 3000:3000 — 将 Attu Web 界面映射到宿主机 3000 端口
-v ...:/etc/attu/certs:ro — 只读挂载证书目录,容器内路径 /etc/attu/certs 与各 MILVUS_TLS_* 变量一一对应
ATTU_DB_PATH /tmp/attu-tls-test.db 指定 Attu 连接与偏好数据的 SQLite 数据库位置(测试用临时路径)
MILVUS_NAME milvus-mtls-local 连接列表中的显示名称
MILVUS_ADDRESS standalone:19530 Milvus gRPC 端点,必须是容器可达的地址(不能用 localhost)
MILVUS_SSL true 开启 TLS 连接
MILVUS_TLS_ROOT_CERT_PATH /etc/attu/certs/ca.pem CA 根证书
MILVUS_TLS_PRIVATE_KEY_PATH /etc/attu/certs/client.key 客户端私钥
MILVUS_TLS_CERT_CHAIN_PATH /etc/attu/certs/client.pem 客户端证书链
MILVUS_TLS_SERVER_NAME standalone 主机名校验名称,必须与 SAN 中的 DNS 名一致

首行 docker rm -f attu-tls-test 用于清理同名旧容器,保证脚本可重复执行。

4.3 持久化 Attu 数据库

默认情况下 Attu 镜像将 SQLite 数据库存放在 /data/attu.db(见 README.md#L46)。如需持久化保存已配置的连接、AI Agent 会话与偏好,可把临时 DB 路径替换为命名卷:

-v attu-data:/data \
-e ATTU_DB_PATH=/data/attu.db \

4.4 理解"默认连接只写入一次"的行为

由 MILVUS_ADDRESS 等变量自动创建的默认连接,每个 Attu SQLite 数据库只会写入一次。如果修改了 TLS 相关环境变量却复用了同一个 ATTU_DB_PATH,Attu 启动时不会自动更新已存在的连接记录。此时有两种处理方式:

  1. 在 Attu Web 界面中手动编辑已保存的连接(更新证书路径、SERVER_NAME 等字段);
  2. 使用全新的 ATTU_DB_PATH(或删除旧数据库文件)重新启动,让新环境变量生效。

这也是本地反复调试 mTLS 配置时最容易踩的"坑",建议调试期间使用独立、可丢弃的 DB 路径。

5. 可选:在容器内使用 Milvus Node.js SDK 验证 mTLS 连接

attu-tls-test 启动后,可以从容器内部,用镜像自带的 Milvus Node.js SDK 复现完全相同的 mTLS 参数做端到端验证(命令见 docs/milvus-mtls-local-docker.md#L193-L195):

docker exec attu-tls-test sh -lc 'cd /app/.output/server && node -e "import(\"@zilliz/milvus2-sdk-node\").then(async ({ MilvusClient }) => { const client = new MilvusClient({ address: \"standalone:19530\", ssl: true, tls: { rootCertPath: \"/etc/attu/certs/ca.pem\", privateKeyPath: \"/etc/attu/certs/client.key\", certChainPath: \"/etc/attu/certs/client.pem\", serverName: \"standalone\" } }); const result = await client.listCollections(); console.log(JSON.stringify(result, null, 2)); await client.closeConnection(); }).catch((error) => { console.error(error); process.exit(1); })"'

5.1 命令原理拆解

  • docker exec attu-tls-test sh -lc '...':进入 Attu 容器执行一段 shell 命令;
  • cd /app/.output/server:切入 Attu v3 服务端输出目录。从镜像结构可以推断,该目录是 Attu 服务端构建产物所在位置,@zilliz/milvus2-sdk-node 依赖被打包在此,可直接 import 使用;
  • new MilvusClient({ address, ssl: true, tls: { rootCertPath, privateKeyPath, certChainPath, serverName } }):以与 Attu 容器启动时完全一致的地址、证书三件套与 serverName 创建客户端;
  • client.listCollections():发起一次真实的 gRPC 请求,触发完整 mTLS 握手;
  • 失败时 process.exit(1),保证验证结果可被脚本感知。

5.2 期望输出

验证成功的响应中应包含 status.error_code: "Success",并返回当前 Milvus 中的集合列表(含 data 字段与各集合名称)。这一结果同时证明了:

  1. Milvus 端 mTLS 已生效,且接受了该客户端证书;
  2. Attu 镜像内挂载的 CA / 客户端证书路径正确、权限可读;
  3. 容器间 standalone:19530 的 Docker DNS 解析与 SAN 主机名校验通过。

故障排查

以下问题与解法均来自文档 docs/milvus-mtls-local-docker.md#L199-L228。

proxy server(http) and external grpc server share the same port, tls mode must be 0

Milvus 在开启 TLS 时检测到 proxy HTTP 与外部 gRPC 共用端口而拒绝启动。解决办法:

  • 在 Milvus standalone 服务中设置 PROXY_HTTP_ENABLED: "false";
  • 或为 proxy HTTP 单独配置一个不同的端口。

主机名校验失败(Hostname verification fails)

确保 MILVUS_TLS_SERVER_NAME 的值与服务端证书 SAN 列表中的某个 DNS 名称完全一致。本指南中 Attu 通过 standalone:19530 连接 Milvus,因此使用 MILVUS_TLS_SERVER_NAME=standalone——它与 openssl.cnf 中 DNS.2 = standalone 对应。若改动连接地址(如换成 milvus-standalone:19530),需同步修改该变量。

mTLS 握手失败

按以下顺序排查:

  1. 确认 Milvus 以 COMMON_SECURITY_TLSMODE: 2 运行(单向 TLS 模式下 Milvus 不会要求客户端证书);
  2. 确认 Attu 三个 mTLS 路径均已设置:MILVUS_TLS_ROOT_CERT_PATH、MILVUS_TLS_PRIVATE_KEY_PATH、MILVUS_TLS_CERT_CHAIN_PATH;
  3. 确认证书挂载在容器内可读:
docker exec attu-tls-test ls -l /etc/attu/certs

若挂载目录为空或文件缺失,请检查第 1 节证书生成是否成功、docker run 的 -v 源路径是否正确。

Attu 启动但默认连接未变化

这是第 4.4 节所述行为的直接表现:MILVUS_ADDRESS 创建的默认连接仅在每个 Attu 数据库中插入一次。处理方式:

  • 删除测试容器并使用全新的 ATTU_DB_PATH(例如删除 /tmp/attu-tls-test.db);
  • 或在 Attu 界面中直接编辑已保存的连接配置。

附:Attu TLS 环境变量速查与延伸阅读

Attu 侧(容器启动时设置):

环境变量 本指南取值
MILVUS_SSL true
MILVUS_ADDRESS standalone:19530
MILVUS_TLS_ROOT_CERT_PATH /etc/attu/certs/ca.pem
MILVUS_TLS_PRIVATE_KEY_PATH /etc/attu/certs/client.key
MILVUS_TLS_CERT_CHAIN_PATH /etc/attu/certs/client.pem
MILVUS_TLS_SERVER_NAME standalone

Milvus 侧(docker-compose 环境变量): COMMON_SECURITY_TLSMODE=2、TLS_SERVERPEMPATH、TLS_SERVERKEYPATH、TLS_CAPEMPATH、PROXY_HTTP_ENABLED="false"。

延伸阅读(仓库内):

以上步骤组合起来,即构成一套完整可复现的"本地 Milvus + mTLS + Attu"验证链路:证书生成 → Compose 开启双向 TLS → Attu 预配置连接 → SDK 端到端验证,适用于开发环境下的安全连接调试与回归测试。

登录后查看全文
attu