1. 问题初现:当Dify遇上本地Ollama,点击保存就报错

最近在折腾一个本地AI应用,想把Dify这个好用的AI应用开发框架,和我本地用Ollama跑起来的大模型连起来。想法很美好:Dify负责提供漂亮的界面和工作流,我本地的模型负责提供“智力”,完全私有化部署,数据不出本地,想想就挺酷。

我的环境是这样的:Dify为了图省事和好管理,是用Docker跑的。而Ollama呢,我直接装在了我的Ubuntu物理主机上,没走容器化。一来觉得Ollama本身管理模型就挺方便,二来也想让模型直接吃满宿主机的硬件资源。按照常理,只要网络通,Dify容器里配置上我主机的IP和Ollama的端口(默认11434),应该就能愉快地握手了。

结果,现实给我上了一课。在Dify的后台,我满怀信心地填好了模型名称(比如我本地跑的 llama3.2:1b),在API地址那里输入了 http://我的主机IP:11434,然后点击了那个至关重要的“保存并测试”按钮。页面转了几圈,弹出来的不是成功的绿色对勾,而是一段让我心头一紧的报错:

An error occurred during credentials validation: HTTPConnectionPool(host='', port=): Max retries exceeded with url: /api/chat (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7550bc7cb350>: Failed to establish a new connection: [Errno 111] Connection refused'))

这段错误信息,乍一看有点懵,但拆开来看其实说了两件事。第一句是“在凭证验证过程中发生了一个错误”,这常常会误导人,让人以为是API密钥之类的认证问题。但真正的重点在第二句,它揭示了问题的本质:HTTP连接池报错,最大重试次数已用尽,原因是连接被拒绝。那个 host='', port= 显示为空也很诡异,感觉像是Dify在尝试解析某个配置时出了问题,但最终落脚点很明确——Connection refused,连接被拒绝了。

当时我的第一反应和大多数人一样:网络不通。是不是Docker容器访问不了我宿主机的网络?是不是防火墙把11434端口给拦了?我立刻开始了最基础的网络排查。

2. 第一步排查:真的是网络不通吗?

遇到“Connection refused”,网络问题永远是首要怀疑对象。我放下对错误信息的纠结,决定从最底层、最基础的网络连通性开始验证。这个过程就像侦探破案,要逐一排除不可能,剩下的再不可思议也是真相。

首先,我确认了Ollama服务本身是活着的。在宿主机上执行 systemctl status ollama,看到服务状态是 active (running),这就排除了服务根本没启动这种低级错误。接着,我在宿主机上直接用 curl 命令测试Ollama服务是否正常响应:curl http://localhost:11434/api/tags。命令很快返回了我本地已下载的模型列表,这说明Ollama服务本身在本地环回接口(127.0.0.1)上工作完全正常。

接下来,关键的一步是:从Dify的容器内部,能否访问到宿主机的Ollama服务?这里有个小技巧,Docker容器访问宿主机,通常不能直接用 localhost 或 127.0.0.1,因为那指的是容器自己。最通用的方法是使用宿主机的 Docker网桥IP,通常是 172.17.0.1,或者在Mac/Windows的Docker Desktop里是 host.docker.internal。为了保险起见,我先进入Dify的容器内部看看。

我执行了 docker exec -it dify-app bash 进入了Dify应用容器(你的容器名可能不同,可能是 dify-web 或 dify-api)。在容器内部,我尝试用 ping 和 curl 来测试:

# 尝试ping宿主机IP(我宿主机的IP是192.168.1.100)
ping 192.168.1.100
# 能ping通,说明基础网络路由是通的。

# 尝试curl宿主机上的Ollama API
curl http://192.168.1.100:11434/api/tags

这个时候,我遇到了和Dify界面里类似的错误:Connection refused。这就奇怪了,宿主机能自己访问自己,容器也能ping通宿主机,但就是访问不了11434端口。问题范围一下子缩小了:Ollama服务很可能只绑定在了 127.0.0.1 这个本地环回地址上,没有绑定在宿主机的物理网卡IP(如192.168.1.100)或者 0.0.0.0(所有接口)上。所以,来自容器(属于另一个网络命名空间)的请求,即使目标IP是宿主机,也因为Ollama没有监听那个IP对应的网络接口而被操作系统直接拒绝。

为了验证这个猜想,我在宿主机上使用 netstat 或更现代的 ss 命令来查看Ollama到底在监听哪些端口和IP:

sudo ss -tlnp | grep 11434
# 或者
sudo netstat -tlnp | grep 11434

果然,输出结果类似于:

LISTEN 0      4096     127.0.0.1:11434   0.0.0.0:*

看,关键就在这里:127.0.0.1:11434。这说明Ollama服务只监听在本地环回地址 127.0.0.1 的11434端口上。对于来自宿主机的 192.168.1.100 这个IP的请求,或者来自Docker容器的请求,Ollama根本“听不见”,因此操作系统内核会直接返回“连接被拒绝”。网络本身是通的,但服务的“耳朵”只长在了一个非常局部的“内部电话”上,接不到外部的“来电”。

3. 深入核心:Ollama服务配置的“两道锁”

定位到问题根源是Ollama服务监听地址受限后,下一步就是如何修改它。Ollama在Linux系统上通常以systemd服务的形式运行,它的行为由服务配置文件控制。这个文件一般位于 /etc/systemd/system/ollama.service。我们需要修改这个文件,给Ollama服务“松绑”,让它能接受来自外部(包括Docker容器)的连接。

用你熟悉的编辑器(如 vim 或 nano)打开这个服务文件:

sudo vim /etc/systemd/system/ollama.service

打开后,你会看到类似下面的内容(不同版本可能略有差异):

[Unit]
Description=Ollama Service
After=network-online.target

[Service]
ExecStart=/usr/local/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="PATH=/usr/local/bin:/usr/bin:/bin"

[Install]
WantedBy=default.target

现在,我们要在 [Service] 部分添加两个至关重要的环境变量,我称之为打开外部访问的“两道锁”。

第一道锁:OLLAMA_HOST 这个环境变量决定了Ollama服务监听的主机和端口。默认情况下,它没有设置,Ollama就会使用默认值,也就是只监听 127.0.0.1:11434。我们需要将它设置为 0.0.0.0:11434。0.0.0.0 是一个特殊的IP地址,表示“所有可用的网络接口”。这样一来,Ollama就会同时监听本地环回接口、物理网卡接口、虚拟网卡接口等,任何发送到本机11434端口的请求它都能接收到。

第二道锁:OLLAMA_ORIGINS 这个环境变量控制跨域资源共享(CORS)。当Dify运行在浏览器中(前端页面),而API请求发往另一个域名或端口(你的Ollama服务)时,浏览器会因为同源策略而阻止请求。将 OLLAMA_ORIGINS 设置为 *,意味着允许来自任何来源的跨域请求。这在开发或内网环境中是方便的,但请注意在生产环境中,出于安全考虑,应该设置为具体的Dify前端访问地址。

修改后的 [Service] 部分应该像这样:

[Service]
ExecStart=/usr/local/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
Environment="OLLAMA_HOST=0.0.0.0:11434"
Environment="OLLAMA_ORIGINS=*"

注意:这里有一个非常重要的安全提示。将 OLLAMA_HOST 设置为 0.0.0.0 和 OLLAMA_ORIGINS 设置为 *,意味着你的Ollama服务将对整个网络开放,且允许任何网页前端访问。如果你的机器暴露在公网,或者处于一个不可信的内网环境中,这将带来极大的安全风险,可能导致模型被他人随意调用或攻击。请务必确保你的服务运行在安全的内部网络环境中,或者配置防火墙(如ufw)只允许特定的IP(如Dify容器所在的宿主机IP或Docker网段)访问11434端口。

修改完成后,保存并退出编辑器。接下来,我们需要让systemd重新加载配置,并重启Ollama服务使更改生效:

# 重新加载systemd配置,使其识别我们对服务文件的修改
sudo systemctl daemon-reload

# 重启ollama服务
sudo systemctl restart ollama

# 再次检查服务状态,确认重启成功
sudo systemctl status ollama

服务重启成功后,我们再次使用 ss 命令验证监听情况:

sudo ss -tlnp | grep 11434

这次,你应该能看到令人欣喜的变化:

LISTEN 0      4096     0.0.0.0:11434   0.0.0.0:*

0.0.0.0:11434 出现了!这表示Ollama现在正在所有网络接口上监听11434端口。第一道锁已经打开。

4. 再次测试与进阶验证:从容器内部发起挑战

配置修改并重启服务后,我们回到Dify容器内部,再次进行测试。这次,我们预期应该能成功连接到Ollama了。

# 再次进入Dify容器(如果已退出)
docker exec -it dify-app bash

# 测试连接Ollama API
curl http://192.168.1.100:11434/api/tags

如果一切顺利,这条命令会返回一个JSON格式的响应,列出你本地所有的模型,类似于:

{"models":[{"name":"llama3.2:1b","modified_at":"2023-10-01T12:00:00Z","size":1234567890}]}

看到这个,就说明从容器到宿主机的Ollama服务,网络通道已经完全打通了。现在,你可以充满信心地回到Dify的管理后台。

刷新Dify的模型配置页面,再次填入模型信息和API地址(http://你的宿主机IP:11434),点击“保存并测试”。这一次,那个恼人的 HTTPConnectionPool 和 credentials validation 错误应该消失了,取而代之的是一个成功的提示,或者至少错误信息变成了更具体的模型加载或API调用问题(那已经是下一个层次的调试了)。

关于“credentials validation”的进一步解释:你可能会有疑问,为什么最初的错误会提到“凭证验证”?我个人的理解是,Dify在测试一个模型提供商配置时,其流程可能包含两步:第一步是基础的网络连通性和API端点可达性测试(这步失败了,报出HTTP连接错误);第二步才是发送一个简单的测试请求(比如 /api/chat)来验证API密钥或令牌。由于第一步的网络连接就失败了,Dify可能将整个流程的错误统称为“凭证验证过程中发生错误”。所以,解决了网络连接问题,这个“凭证验证”错误自然也就跟着消失了。

5. 避坑指南与安全加固建议

通过上面的步骤,我们解决了Dify连接本地Ollama的核心网络问题。但在实际部署中,你可能会遇到其他相关的小坑,或者需要考虑更安全、更稳定的配置方式。这里分享几个我踩过或者觉得重要的点。

1. Docker网络模式的影响 如果你在启动Dify的Docker容器时,使用了 --network=host 模式(主机网络模式),那么容器会直接共享宿主机的网络命名空间。在这种情况下,容器内访问 localhost:11434 就直接等同于宿主机访问 localhost:11434。这时,即使Ollama只监听 127.0.0.1,容器也能访问到。但主机网络模式有安全性和端口冲突的风险,不是所有环境都适用。我们更常见的还是默认的桥接网络模式,这就需要我们按照上文修改 OLLAMA_HOST。

2. 防火墙(Firewall)的拦截 除了服务本身的监听地址,系统防火墙也可能阻止访问。在Ubuntu上,如果你使用了 ufw,可能需要放行11434端口:

sudo ufw allow 11434/tcp
sudo ufw reload

在CentOS/RHEL或使用firewalld的系统上,命令会有所不同。确保你的防火墙规则允许从Docker网桥(如 172.17.0.0/16)或特定IP到11434端口的入站流量。

3. 更安全的OLLAMA_ORIGINS设置 在生产环境或对安全有要求的内网,将 OLLAMA_ORIGINS 设置为 * 过于宽松。你应该设置为运行Dify前端的确切地址。例如,如果你的Dify通过 http://192.168.1.100:3000 访问,那么可以设置为:

Environment="OLLAMA_ORIGINS=http://192.168.1.100:3000"

如果需要多个来源,可以用逗号分隔。

4. 使用Docker Compose统一管理 如果你希望环境更整洁,也可以考虑将Ollama也容器化,并通过Docker Compose与Dify放在同一个自定义网络中。这样,容器间可以通过服务名直接通信,完全绕开主机IP和端口暴露的问题。一个简单的 docker-compose.yml 示例如下:

version: '3.8'
services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: always
    volumes:
      - ollama_data:/root/.ollama
    ports:
      - "11434:11434"
    networks:
      - dify-net
    # 在容器内部,Ollama默认监听11434,通过ports暴露给宿主机和同网络容器

  dify:
    image: langgenius/dify-ai:latest
    container_name: dify
    restart: always
    depends_on:
      - ollama
    environment:
      - MODE=api
      - CONSOLE_API_URL=http://dify-web:3000
      - CONSOLE_WEB_URL=http://localhost:3000
    volumes:
      - dify_data:/app/api/storage
    ports:
      - "3000:3000"
      - "5001:5001"
    networks:
      - dify-net

networks:
  dify-net:
    driver: bridge

volumes:
  ollama_data:
  dify_data:

在这种配置下,Dify容器内配置Ollama的API地址时,就可以直接使用服务名 http://ollama:11434,既简单又避免了跨主机网络配置的麻烦。

5. 模型加载与内存问题 网络通了之后,Dify在测试或使用时可能会调用模型。确保你的宿主机有足够的内存(RAM)来加载你选择的模型。大型模型可能需要数十GB内存,如果内存不足,Ollama服务可能会崩溃或无响应,这又会引发新的超时或连接错误。可以通过 ollama run 命令先在本地测试一下模型是否能正常加载和对话。

整个排查过程,从看到令人困惑的报错,到一步步使用基础命令进行诊断,最终定位到一个具体的配置文件参数,这本身就是一次很好的系统调试实战。问题的关键往往不在于错误信息本身有多复杂,而在于你是否能将其拆解,并系统地验证每一个可能的环节。修改 OLLAMA_HOST 和 OLLAMA_ORIGINS 这两个环境变量,就是打开Dify与本地Ollama之间通信大门的两把关键钥匙。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐