欢迎光临
我们一直在努力

Day 61 | Docker部署AI推理服务:Ollama + Open WebUI生产实践

很多人以为部署大模型是算法工程师的专属技能——下载几个Python包,跑个transformers demo就算完事。但真正到了生产环境,问题才刚开始:模型版本怎么管理?GPU显存怎么分配?API并发撑不住怎么办?日志和监控怎么接?

这篇文章不讲理论,只讲一个Java后端工程师能直接上手落地的方案:用Docker容器化部署Ollama推理服务,配合Open WebUI提供可视化界面,再用vLLM解决高并发瓶颈。全程代码可复制,配置可运行。


一、整体架构:我们在搭建什么

先搞清楚要搭的这套东西长什么样:

核心组件就三个:

  • Ollama:本地大模型推理引擎,负责加载模型、管理版本、暴露REST API

  • Open WebUI:基于Web的ChatGPT风格对话界面,支持多用户、多模型切换、对话历史

  • Docker:把整个环境打包成可移植的容器,开发环境一键复制到生产环境


二、Ollama Docker部署:5分钟跑起来

Ollama的Docker镜像已经预装了推理运行时,不需要你本地安装CUDA工具链,也不需要配Python环境。

2.1 基础部署(CPU模式,适合测试)

# docker-compose.yml —— Ollama基础版
version: '3.8'

services:
ollama:
  image: ollama/ollama:0.3.6
  container_name: ollama
  ports:
    – "11434:11434"
  volumes:
     # 模型文件持久化,避免每次重启重新下载
    – ollama-models:/root/.ollama
  environment:
     # 允许跨域访问,WebUI需要
    – OLLAMA_ORIGINS=*
     # 监听所有接口
    – OLLAMA_HOST=0.0.0.0:11434
  restart: unless-stopped

volumes:
ollama-models:

启动命令:

docker compose up -d
# 拉取模型(以通义千问7B为例,约4.5GB)
docker exec -it ollama ollama pull qwen2:7b
# 验证模型列表
docker exec -it ollama ollama list

关键点:volumes一定要配。模型文件动辄几个GB,不配持久化卷,容器重启就全丢,下次启动重新下载,血泪教训。

2.2 直接调用Ollama API

Ollama暴露的是兼容OpenAI格式的REST API,从你的Java后端调起来非常直接:

/**
* Ollama API 调用示例
* 依赖:Spring Boot 3.2+ + Spring Web
*/
@Service
public class OllamaChatService {

   private final WebClient webClient;

   public OllamaChatService(WebClient.Builder builder) {
       // 连接本地Ollama服务
       this.webClient = builder
          .baseUrl("http://localhost:11434")
          .build();
  }

   /**
    * 同步对话调用
    */
   public String chat(String userMessage) {
       Map<String, Object> request = Map.of(
           "model", "qwen2:7b",
           "messages", List.of(
               Map.of("role", "system", "content", "你是一个Java技术专家"),
               Map.of("role", "user", "content", userMessage)
          ),
           "stream", false,
           "options", Map.of(
               "temperature", 0.7,
               "num_ctx", 4096  // 上下文窗口大小
          )
      );

       return webClient.post()
          .uri("/api/chat")
          .bodyValue(request)
          .retrieve()
          .bodyToMono(String.class)
          .block();
  }

   /**
    * 流式输出(SSE)—— 用于实时打字机效果
    */
   public Flux<String> chatStream(String userMessage) {
       Map<String, Object> request = Map.of(
           "model", "qwen2:7b",
           "messages", List.of(
               Map.of("role", "user", "content", userMessage)
          ),
           "stream", true
      );

       return webClient.post()
          .uri("/api/chat")
          .bodyValue(request)
          .retrieve()
          .bodyToFlux(String.class);
  }
}

参数说明:

  • temperature:控制生成随机性,0.1~0.3适合代码/问答,0.7~0.9适合创意写作

  • num_ctx:上下文token数,7B模型建议4096,13B可开到8192

  • stream:true开启SSE流式,false等完整结果返回


三、Open WebUI:给推理服务穿上衣服

光有API不够,团队里的产品、测试、运营也需要一个界面来跟模型对话。Open WebUI是目前最成熟的方案,功能对标ChatGPT:

# docker-compose.yml —— Ollama + Open WebUI 完整版
version: '3.8'

services:
ollama:
  image: ollama/ollama:0.3.6
  container_name: ollama
  ports:
    – "11434:11434"
  volumes:
    – ollama-models:/root/.ollama
  environment:
    – OLLAMA_ORIGINS=*
    – OLLAMA_HOST=0.0.0.0:11434
  restart: unless-stopped

open-webui:
  image: ghcr.io/open-webui/open-webui:0.3.10
  container_name: open-webui
  ports:
    – "3000:8080"
  volumes:
    – open-webui-data:/app/backend/data
  environment:
     # 指向Ollama服务(容器内通过服务名访问)
    – OLLAMA_BASE_URL=http://ollama:11434
     # 允许新用户注册(生产环境建议关闭,改用手动导入)
    – ENABLE_SIGNUP=true
     # 默认语言
    – DEFAULT_LOCALE=zh-CN
  depends_on:
    – ollama
  restart: unless-stopped

volumes:
ollama-models:
open-webui-data:

启动后访问 http://localhost:3000,注册一个账号,就能在界面里选择已下载的模型开始对话。

生产环境注意:ENABLE_SIGNUP=true只适合内网测试。外网部署时建议:

  • 关闭注册,通过管理员后台批量导入用户

  • 或者接入OAuth2(支持GitHub、Google、企业微信等)

  • 前面加一层Nginx做HTTPS和基础认证


  • 四、GPU加速:让推理速度翻5倍

    CPU跑7B模型,生成速度大概5~10 token/秒,能用但体验差。上了GPU,同样模型能跑到60~100 token/秒,差距肉眼可见。

    4.1 nvidia-docker 配置

    前提:宿主机已安装NVIDIA驱动 + NVIDIA Container Toolkit

    # docker-compose.yml —— GPU加速版
    version: '3.8'

    services:
    ollama:
      image: ollama/ollama:0.3.6
      container_name: ollama
      ports:
        – "11434:11434"
      volumes:
        – ollama-models:/root/.ollama
      environment:
        – OLLAMA_ORIGINS=*
        – OLLAMA_HOST=0.0.0.0:11434
       # ========== GPU 配置核心 ==========
      deploy:
        resources:
          reservations:
            devices:
              – driver: nvidia
                count: 1          # 使用1张GPU,all表示全部
                capabilities: [gpu]
       # ==================================
      restart: unless-stopped

    open-webui:
      image: ghcr.io/open-webui/open-webui:0.3.10
      container_name: open-webui
      ports:
        – "3000:8080"
      volumes:
        – open-webui-data:/app/backend/data
      environment:
        – OLLAMA_BASE_URL=http://ollama:11434
        – ENABLE_SIGNUP=true
      depends_on:
        – ollama
      restart: unless-stopped

    volumes:
    ollama-models:
    open-webui-data:

    验证GPU是否生效:

    # 进入容器查看
    docker exec -it ollama nvidia-smi

    # 运行模型时观察显存占用
    docker exec -it ollama ollama run qwen2:7b
    # 另开一个终端:
    docker exec -it ollama nvidia-smi

    4.2 显存占用参考表

    模型参数量FP16显存4-bit量化建议GPU
    qwen2 7B ~14GB ~4GB RTX 3060 12GB
    qwen2 14B ~28GB ~8GB RTX 3090 24GB
    llama3 8B ~16GB ~5GB RTX 4060 Ti 16GB
    llama3 70B ~140GB ~40GB A100 40GB × 2

    省钱技巧:Ollama默认会自动选择量化级别。显存不够时,它会自动加载Q4_K_M量化版本,牺牲一点精度换运行能力。你也可以手动指定:ollama pull qwen2:7b-q4_K_M


    五、vLLM:高并发场景的核武器

    Ollama适合个人开发和中小团队使用,但遇到高并发(比如同时几十个用户提问),单实例Ollama会排队处理,延迟直线上升。这时候需要 vLLM。

    5.1 vLLM核心优势

    vLLM是UC Berkeley开源的推理引擎,核心创新是 PagedAttention 技术——把GPU显存管理从粗粒度的"预分配一大块"改成细粒度的"按需分页",显著提升吞吐量。

    实际压测数据(单张RTX 4090,qwen2:7b模型):

    方案并发数平均延迟吞吐量(token/s)
    Ollama 1 800ms 45
    Ollama 8 3200ms 38
    vLLM 1 750ms 48
    vLLM 8 1100ms 180
    vLLM 32 2800ms 420

    结论:高并发下vLLM吞吐量是Ollama的10倍以上。

    5.2 vLLM Docker部署

    # docker-compose.yml —— vLLM高并发版
    version: '3.8'

    services:
    vllm:
      image: vllm/vllm-openai:v0.5.4
      container_name: vllm-server
      ports:
        – "8000:8000"
      volumes:
         # 挂载宿主机上的模型目录
        – /data/models:/models
      environment:
        – CUDA_VISIBLE_DEVICES=0
       # 启动命令:加载Qwen2-7B,启用OpenAI兼容API
      command: >
        –model /models/Qwen2-7B-Instruct
        –served-model-name qwen2-7b
        –dtype half
        –tensor-parallel-size 1
        –max-model-len 4096
        –gpu-memory-utilization 0.9
      deploy:
        resources:
          reservations:
            devices:
              – driver: nvidia
                count: 1
                capabilities: [gpu]
      restart: unless-stopped

    关键参数解析:

    • –tensor-parallel-size:多GPU张量并行,2表示用2张卡同时算

    • –gpu-memory-utilization 0.9:使用90%显存,留10%给KV Cache动态增长

    • –max-model-len:最大上下文长度,超过会截断

    5.3 Java后端接入vLLM

    vLLM暴露的是标准OpenAI API,Spring AI直接就能对接:

    /**
    * Spring AI 接入 vLLM 本地推理服务
    * 依赖:org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-M1
    */
    @Configuration
    public class VllmConfig {

       @Bean
       public OpenAiApi openAiApi() {
           // 指向本地vLLM服务,而非OpenAI官方
           return new OpenAiApi(
               "http://localhost:8000/v1",  // vLLM的OpenAI兼容端点
               "sk-no-key-required"          // 本地服务不需要真实API Key
          );
      }

       @Bean
       public OpenAiChatModel chatModel(OpenAiApi api) {
           var options = OpenAiChatOptions.builder()
              .withModel("qwen2-7b")        // 与vLLM的served-model-name一致
              .withTemperature(0.7)
              .withMaxTokens(2048)
              .build();
           return new OpenAiChatModel(api, options);
      }
    }

    @Service
    public class AiChatService {

       @Autowired
       private OpenAiChatModel chatModel;

       public String ask(String question) {
           return chatModel.call(question);
      }

       public Flux<String> askStream(String question) {
           return chatModel.stream(question)
              .map(chunk -> chunk.getResult().getOutput().getContent());
      }
    }

    兼容性说明:vLLM的/v1/chat/completions端点与OpenAI API完全兼容,所以Spring AI的OpenAiChatModel可以直接复用,一行不改。


    六、压测与性能调优

    部署完了,得知道它能扛多少并发。推荐用 locust 或 k6 做压测。

    # locustfile.py —— 简单的Ollama压测脚本
    from locust import HttpUser, task, between

    class OllamaUser(HttpUser):
       wait_time = between(1, 3)

       @task
       def chat(self):
           self.client.post("/api/chat", json={
               "model": "qwen2:7b",
               "messages": [{"role": "user", "content": "用Java写一个单例模式"}],
               "stream": False
          })

    运行:locust -f locustfile.py –host http://localhost:11434

    调优 checklist:

  • 模型量化:显存不够 → 换Q4量化版,精度损失通常在可接受范围

  • 上下文截断:num_ctx不要设太大,按需分配,省显存

  • 批处理大小:vLLM的–max-num-seqs控制最大并发序列数,默认256,可根据GPU调整

  • 多实例负载均衡:单卡撑不住时,开多个Ollama/vLLM实例,前面挂Nginx轮询


  • 七、建议

    建议一:开发用Ollama,生产用vLLM

    Ollama的模型管理和WebUI生态更完善,适合开发调试阶段。正式上线后,如果QPS超过10,建议切到vLLM,吞吐量提升一个数量级。

    建议二:模型文件做CDN缓存

    团队多人部署时,每个人重新下载几个GB的模型很浪费时间。可以在内网搭一个Harbor或Nexus,把常用模型镜像缓存起来,新人入职docker pull几分钟搞定。

    建议三:监控必须接,否则出事找不到根因

    至少监控三个指标:

    • GPU显存占用(nvidia-smi或DCGM exporter)

    • 推理延迟P99(Prometheus + Grafana)

    • 模型加载状态(Ollama的/api/tags接口轮询)


    部署大模型和部署MySQL本质上没有区别——都是起一个服务、挂一个卷、配一个端口。区别在于,大模型的"数据库"是几十亿个参数,查询一次要烧几焦耳的电。

    明天我们聊一个更接地气的话题:国内三大AI云平台(阿里云百炼 / 腾讯云混元 / 火山引擎方舟)的企业级接入对比。如果你不想自己运维GPU机器,那篇就是为你写的。

    赞(0)
    未经允许不得转载:171主机测评 » Day 61 | Docker部署AI推理服务:Ollama + Open WebUI生产实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址