从零搭建一个本地ollama
先说一下整个架构
本次用的云服务器是火山云的内部开发机,它的内网IP固定但是外网IP在不断变化,可见这里字节使用了“动态NAT”或“负载均衡器出口IP池”技术。
整个的架构是:“用一个ingress来作为对外的通道,让调用方可以通过域名的方式来访问后端的service,然后service绑定一个deployment。这个deployment对应的backend pod会调用ollama的pod,询问对应的问题”。
这里的k8s用的是k3s。
再补一点名词解释:
- Ollama是一个大模型的运行框架。它可以把复杂的模型参数、配置文件打包成一个简单文件、然后开一个REST API的接口(也就是下面的11434接口),可以通过requests.post方法来请求。
- 因为我这个是CPU不是GPU,所以用的是Llama3 8B,它是一个开源大语言模型本体,后面我们会使用70B的大模型。他俩在CPU的支持下比较好,对内部团队使用以及足够了。
搭建过程
我们先搞一个ollama的pod,ollama-deployment.yaml如下:
1 | # ollama-deployment.yaml |
然后进入容器里下载模型,Llama3 8B大约要5个G:
1 | # 找到 pod 名字 |
整个过程是这样的:
- Deployment启动了Ollama引擎。
- 执行了ollama run llama3,引擎就去下载Llama3 8B 的权重文件。权重文件很大,70B 需要几乎50个G。
- 引擎将权重加载进内存,然后在11434接口等指令。
先写一个backend.py:
1 | from flask import Flask, jsonify |
Dockerfile如下:
1 | FROM docker.m.daocloud.io/library/python:3.9-slim |
有了Dockerfile之后,我们来打包一个镜像ai-backend:docker build -t ai-backend:v4 . ,然后通过docker images |grep v4里看本地的确有了docker images库里的确有了ai-backend:v4这个镜像,但是要注意这个镜像还不在k3s里,所以还要执行docker save ai-backend:v4 |sudo k3s ctr images import - , 这样k3s的镜像库才也有了这个ai-backend:v4的镜像,可以用k3s ctr images list|grep ai-backend这个命令来确认k3s已经复制到了这个v4的镜像。
backend-deployment.yaml如下:
1 | apiVersion: apps/v1 |
然后可以通过kubectl apply -f backend-deployment.yaml 或者 kubectl rollout restart deployment ai-backend来滚动发布。
后端搞定了之后, 作为迎宾的ingress.yaml如下:
1 | # ai-ingress.yaml |
把这些都启动了之后,检查一下状态:
上面说了,这个开发机是没有一个固定的外网IP,不过本身就是测试,我这里就在我的macos上手动将开发机的ip写死到/etc/hosts里,对应的域名先暂定ai.demo.com。最后在浏览器测试一下:
最后再总结一下整个流程:
- 浏览器通过写死域名以及域名对应的ip发送请求。
- 通过
ai.demo.com域名,流量请求到开发机的80接口。 - k3s内部的ingress接客,看到路径是/aigpt,就把请求转发给ai-backend-svc这个service。
- 后端backend Pod接收请求,执行对应的python代码,发起一个requests请求ollama svc,这俩pod是互通的。
- 返回结果到浏览器。整个过程闭环在macos和开发机里,哪怕开发机没有公网,一样请求OK,而且现在调用是不花token的。
- 做pv/pvc持久化,重启后就不用再去公网上下载权重文件了,避免了pod先活了,但是还要每次都现去下载模型,这样“服务其实没准备好,但是流量先进来”的尴尬还是解决掉比较好。
排错过程
如果发现pod起不来,可以先通过kubect describe命令粗略看一下原因,如果看不出来啥,可以再用kubectl logs pod名称 --previous ,来查看具体pods的日志,能看出来为啥“镜像顺利拉到手,但是程序为啥启动不起来”。
或者是kubectl exec -it pod名称 -- ls -R /app这个命令看一下pod里面的文件是不是因为docker没写好没有打包进去或者其他什么事。
使用pvc做持久化
虽然上面的效果达到了目标,但是有一个致命的问题,就是ai权重文件动辄几十GB,容器的镜像拉取和启动太慢了,怎么缓解?
所以这里要解耦一下,镜像只包括运行环境ollama,而巨大的模型权重用过pvc挂载,这样镜像只有只百MB,可快速分发,ollama POD在接收问题的时候,需要把pvc里的权重文件加载到内存里,这个完全取决于磁盘的读写速度,磁盘越强加载到内存速度越快。
首先先创建一个ollama-pvc.yaml,做存储声明
1 |
|
这里要注意!storage: 50Gi 这个数字将来只能往上加,不能往下减(会报错Forbidden: field can not be less than status.capacity),这么设计的原因是往下减的话,会破坏文件系统,如果非要缩,只能删了重建,里面之前的东西都白下载了。
然后修改ollama-deployment.yaml让它挂载到这个“pvc”上:
1 | # ollama-deployment.yaml |
然后重新部署一下,先用exec -it的方式来测试一个问题:
同样,此时用浏览器发起请求也是报错:
这里说明一下PVC → PV → 宿主机路径的关系链:
1 | 容器内路径 (/root/.ollama) |
如图,这里可以看到的Path就是宿主机上的路径:
PVC状态说明:
Pending:等待中,存储卷还未创建(有问题)Bound:已绑定,存储卷已成功创建并绑定到PVC(正常状态)Released:已释放,Pod已删除但PV还未清理Failed:失败,存储卷创建失败
现在你如果删除ollama的pod,重建一个新pod就会发现很快这个pod可以使用并且正确返回内容了。
改用70B的大模型
如果觉得8B模型不咋地,想升级用70B的大模型,就用这个:
1 | # 找到 pod 名 |
用了70B对8B是有了质的飞跃,而且即使想搞RAG,70B也比8B的效果更好,但是70B的大小更大,要39G。它回答的时候也是需要先把这39G都加载到内存里,所以这里注意一下backend.py调用ollama的post请求要加上timeout=300,这样不至于模型还没算完,链接就断了。
39G虽然大,但是PVC是支持断点续传的,即使中间断网了,下一次启动会去看临时文件的:

现在有了70B,8B对我就没用了,就直接干掉了:
1 | kubectl exec -it ollama-df8f889ff-nbjr2 -- ollama list #展示出来所有的ollama已经有的模型 |