本文将介绍:
- 北冥坞系统502报错的故障排查与解决方案
- 无法注册账号的临时解决方案
本文将涉及如下常用命令:
curl http://localhost:8088/ #测试本机8088端口是否启用
docker stop learnware-backend #停止运行learnware-backend容器(该操作不会丢失数据)
docker start learnware-backend #启动停止运行的容器
sudo docker compose -f docker-compose.yaml -p learnwaresingle down #卸载容器(你对容器的修改将丢失,请在备份后再运行此命令!)
sudo docker compose -f docker-compose.yaml -p learnwaresingle up -d #重新部署容器
docker ps #查看容器运行状况和端口配置
docker exec -it learnware-backend /bin/sh #进入learnware-backend容器的CLI界面
docker logs learnware-backend #查看learnware-backend容器日志
docker cp ./bert_cache learnware-backend:/root/.cache/huggingface/transformers/bert-base-uncased #将服务器的特定文件或文件夹拷贝到容器特定目录内
clash & #后台运行clash
wget "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=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&suffix=tar.gz" -O GeoLite2-Country.tar.gz #下载特定文件
curl -I https://www.google.com --proxy http://127.0.0.1:7890 #测试代理的端口能否访问谷歌
ps aux | grep clash #查看后台运行的clash进程
kill <PID> #删除多余的clash进程
curl -X PUT "http://127.0.0.1:9090/proxies/🔰%20选择节点" -H "Content-Type: application/json" -d '{"name":"🇺🇲 美国W02"}' #设置节点为美国W02
502故障排查与解决方案
问题描述
在服务器上部署的北冥坞系统中,注册账号会出现502 Bad Gateway
报错。暑假,我尝试在本机部署的北冥坞系统中注册账号,也同样会出现该报错。
但我没做任何修改,开学后本机的北冥坞系统有不报错,可以正常注册了。
原因猜测
- (一)前后端交互问题:前端注册账号的http请求的格式或目标链接不正确,无法正常发送到后端,需要修改特定代码或配置文件才能使其正常交互
- (二)端口开放问题:特定端口没有开放,导致服务器和容器交互存在问题,或前后端交互出现问题
- (三)后端运行问题:后端没能正常运行或启动
大家都倾向于认为问题是一或二,我本来也是这么想的,但排查发现真正的问题其实是三。
下面尝试回溯下排查过程(由于当时输出的时候没截图,所以有些命令的输出难以复现)
检查request请求
通过浏览器开发者工具,可以看到502报错的request请求如下:
Request URL:
http://xxx.xxx.xxx.90:5173/api/auth/register
Request Method:
POST
Status Code:
502 Bad Gateway
Remote Address:
xxx.xxx.xxx.90:5173
Referrer Policy:
strict-origin-when-cross-origin
HTTP/1.1 502 Bad Gateway
Server: nginx/1.13.8
Date: Sat, 21 Sep 2024 17:04:30 GMT
Content-Type: text/html
Content-Length: 575
Connection: keep-alive
POST /api/auth/register HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Content-Length: 92
Content-Type: application/json
Host: xxx.xxx.xxx.90:5173
Origin: http://xxx.xxx.xxx.90:5173
Referer: http://xxx.xxx.xxx.90:5173/register
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
注意,`Request URL:
http://xxx.xxx.xxx.90:5173/api/auth/register`说明它是通过这个url来注册的,并没有想有的队员想象中链接到官方的服务器那边了。
其次,这个url也符合注册账号时的url的常识,即通过当前网址的某个特定路径路由到后端。
所以,基本可以确定http请求的格式是没问题的,但这并不能保证前后端的交互一定没问题,如果后端特定端口没能正常开放,前端仍然访问不到后端。
检查端口号
(base) dachuang@master:~/Beimingwu/deploy/docker-compose$ docker ps
IMAGE PORTS NAMES
lamda/bm-system-backend:0.0.1 8088/tcp learnware-monitor
lamda/bm-system-backend:0.0.1 8088/tcp learnware-backend
lamda/bm-system-admin-frontend:0.0.1 80/tcp, 0.0.0.0:5174->5173/tcp, :::5174->5173/tcp learnware-admin-frontend
redis 6379/tcp learnware-redis
lamda/bm-system-frontend:0.0.1 80/tcp, 0.0.0.0:5173->5173/tcp, :::5173->5173/tcp learnware-frontend
xiaohang07/slices:v9 0.0.0.0:8888->8888/tcp, :::8888->8888/tcp jovial_greider
可以看到,learnware-frontend和learnware-admin-frontend分别绑定了5173和5174端口,而我们关心的后端容器绑定了8088端口。
测试8088端口是否开放
运行docker exec -it learnware-backend /bin/sh
进入容器,测试该8088端口是否处于监听状态:
# netstat -tuln | grep 8088
tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN
# curl http://localhost:8088/
^C
# curl http://localhost:8088/api/auth/register
^C
可以看到,尽管8088确实处于监听状态,但是8088在容器内也没法正常访问,这就非常奇怪,我默认是端口配置出了问题,决定先试着改一下相关配置,但其实猜测大概率不是端口的问题。
修改后端容器的端口配置
(base) dachuang@master:~/Beimingwu/deploy/docker-compose$ vim docker-compose.yaml
backend:
image: lamda/bm-system-backend:0.0.1
build:
context: .
dockerfile: Dockerfile_backend
container_name: learnware-backend
restart: always
networks:
- learnware
#expose:
#- '8088'
ports:
- "8088:8088" # 改为和前端一样的配置方式
volumes:
- backend_data:/backend_data
- learnware_storage:/root/.learnware
frontend:
重启容器
sudo docker compose -f docker-compose.yaml -p learnwaresingle down
sudo docker compose -f docker-compose.yaml -p learnwaresingle up -d
重新查看端口状况
(base) dachuang@master:~/Beimingwu/deploy/docker-compose$ docker exec -it learnware-backend /bin/sh
# netstat -tuln | grep 8088
tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN
# curl http://localhost:8088/
^C
运行结果不变,但是同样的端口映射方式,前端却可以正常访问。
8088端口确实处于监听状态,但却curl不到应有的响应,这说明不是端口的问题,而是后端的问题!
检查后端运行状况
(base) dachuang@master:~/Beimingwu/deploy/docker-compose$ docker logs learnware-backend
...
File "/root/miniconda3/lib/python3.8/site-packages/transformers/tokenization_utils_base.py", line 2008, in from_pretrained
raise EnvironmentError(
OSError: Can't load tokenizer for '/root/.cache/huggingface/transformers/bert-base-uncased/models--bert-base-uncased'. If you were trying to load it from 'https://huggingface.co/models', make sure you don't have a local directory with the same name. Otherwise, make sure '/root/.cache/huggingface/transformers/bert-base-uncased/models--bert-base-uncased' is the correct path to a directory containing all relevant files for a BertTokenizerFast tokenizer.
[2024-09-22 07:21:04 +0000] [29] [INFO] Worker exiting (pid: 29)
[2024-09-22 07:21:04 +0000] [1] [ERROR] Worker (pid:28) exited with code 3
[2024-09-22 07:21:04 +0000] [1] [ERROR] Worker (pid:29) was sent SIGTERM!
[2024-09-22 07:21:04 +0000] [1] [ERROR] Shutting down: Master
[2024-09-22 07:21:04 +0000] [1] [ERROR] Reason: Worker failed to boot....
通过和ChatGPT交流(https://chatgpt.com/share/66ef91b4-d2c8-8010-bff1-3a33bb4e8c81)了解到,后端实际上尝试访问huggingface下载“bert-base-uncased”模型,但是下载一直失败,导致后端容器其实在不停重启(boot....)。
其实,之前使用docker exec -it learnware-backend /bin/sh
尝试进入容器时就可以发现,往往进入一会便自动退出了,我本来以为是服务器不稳定导致的,实际是容器因为报错不断重启的结果。
尝试解决
既然问题确定了,就该考虑解决方案了,有三个解决思路,按照治标到治本可以这么排序:
- 尝试去掉访问huggingface的相关代码,并对其他代码进行相应调整,来至少先让后端不会出现报错重启的情况。
- 尝试将相关文件先下载到本地,然后从本地上传到服务器,在从服务器上传到容器,最后修改容器内响应代码,让它不尝试访问huggingface,而是直接从本地加载相应文件。
- 配置好代理,让容器内可以正常访问huggingface。
我没给服务器配过代理,也懒得折腾代理(结果最后还是折腾了),所以我是不太倾向于选③的。
然后我尝试考虑②,我专门去bert-base-uncased模型的huggingface页面看了下,好家伙,一个模型3G,这得传多久。尽管很大,我还是率先尝试了②,我把3G的模型相关文件都下载到了本机,然后尝试把它们从本机传到服务器,结果上传速度只有100k(我试了两种方式都是100k),按这速度要传10h,直接把我整不会了。
于是我决定试一下①,但①其实很难搞,因为有很多变量来来回回都用到self.tokenizer变量了,而self.tokenizer是基于这些huggingface文件初始化的,这样来来回回要改的变量很多,很容易改出问题来,所以①其实不太靠谱。
本来已经想放弃了,但我稍微多想了一下,后端要从huggingface上下载一个3G的模型实际上是不符合常理的,加载这么大的模型干什么用?能正常下载的话得下载多久?没下载完会不会影响后端使用?
于是我跟GPT交流了下,尝试在本地运行相关加载代码看看到底下载了个啥,结果一弄发现其实就加载了1M左右的配置文件,这样的话,②就应该是一个可行的方案了。
本地下载相关文件
from transformers import BertTokenizerFast
# 下载并缓存模型到指定目录
tokenizer = BertTokenizerFast.from_pretrained("bert-base-uncased", cache_dir="./bert_cache")
上传文件到服务器
scp -r ./bert_cache daxxxx@xxx.xxx.xxx.90:/tmp
上传文件到容器
docker cp ./bert_cache learnware-backend:/root/.cache/huggingface/transformers/bert-base-uncased
修改相关代码
(要先进入容器)
vim /learnware/learnware/market/heterogeneous/organizer/hetero_map/feature_extractor.py
#self.tokenizer = BertTokenizerFast.from_pretrained("bert-base-uncased", cache_dir=cache_dir)
from transformers import BertTokenizerFast
self.tokenizer = BertTokenizerFast.from_pretrained(
"/root/.cache/huggingface/transformers/bert-base-uncased",
local_files_only=True # 防止联网下载
)
再次查看日志
(待补充)
可以看到容器仍然在不断重启,但报错变了,所以我们的修改是有影响的。
经过反思,发现报错是因为我的相对路径配得有问题。
所以最终正确的代码如下:
#self.tokenizer = BertTokenizerFast.from_pretrained("bert-base-uncased", cache_dir=cache_dir)
from transformers import BertTokenizerFast
self.tokenizer = BertTokenizerFast.from_pretrained(
"/root/.cache/huggingface/transformers/bert-base-uncased/models--bert-base-uncased/snapshots/86b5e0934494bd15c9632b12f734a8a67f723594",
local_files_only=True # 防止联网下载
)
解决账号注册问题
502报错解决后,便可以成功登录管理员账号了。但如果你尝试自己注册账号就会发现,它会发一个成功发送验证邮件的提示,但你实际验证邮件发不出来,导致账号没有通过验证。
我首先尝试在管理员账号的后台看看能不能修改验证状态,无果。
然后我尝试找到存储用户数据的数据库文件,能不能通过修改数据库数据来通过验证,成功。
进入容器CLI并找到数据库文件
(base) dachuang@master:~/Beimingwu$ docker exec -it learnware-backend /bin/sh
# cd /backend_data
# ls
backup
database.db
datasets
...
其中,database.db即为北冥坞系统的数据库文件(该文件由系统自动创建,无需手动创建)
查看用户数据
# sqlite3 database.db #打开数据库文件
sqlite> .tables #查看所有表单
tb_global_counter
tb_user #tb_user即为用户表单
...
sqlite> PRAGMA table_info(tb_user); #查看用户表单内的字段
_info(tbguser):934494bd15c9632612f734a8a67f723594
0| id| INTEGER| 1||1
1|username|TEXT|1||0 #用户名
2 password TEXT 1||0 #密码
3|email| TEXT|1||0 #邮箱
4| role| INTEGER| 1|0|0 #角色
5| nickname| TEXT|1||0 #昵称
...
8|email_confirm_time|DATETIME|0||0 #邮箱验证时间
sqlite> SELECT* FROM tb_user LIMIT 10; #查看用户数据
1|admin|$2b$12$uhXw8KyIKmgEaNVWaBGFrubf3.gqx7IYpB5cdK1B0hI0USm5I4z7q|admin@localhost|2|admin
itrator 2024-07-25 08:18:28.828013| 2024-07-25 08:18:28.828013
2|1zy|$2b$12$R/TUd1EaHTERTx823H7J7eynBt5LG6VK/rn8IdjHc6AuRi5skrpli|1571547820@qq.com|1|lzy|2
024-09-22 07:26:33.314545||
3|dream.w|$2b$12$X?XAuWV4ĨiB83L1s/3mTuE.SCBE.jxD0tcK1PWqiIV/1EX09FXV6|2024172050@mail.hfut.
edu.cn|@|dream.w|2024-09-22 07:56:10.394471|1
4|qwa| $2b$12$uXR1DWeICGWY.3Q4nokiRuvdrkBl17Svud1XjaC7C4HNL98Uwp23a|2023217514@mail.hfut.edu.
cn |0|qwq| 2024-09-22 07:57:00.665925||
5|Billows|$2b$12$qvMBQC0YmGXzLhMJ/968H,TUh2hUSJDcMKV8X0kYDQDmR1DXMZya. |2022215010@mail.hfut.
edu.cn|@|Billows|2024-09-22 07:58:17.27344411
sqlite>
可以看到,尽管邮箱验证还没通过,但是用户的注册数据已经写到数据库了,所以便可以合理猜测只要修改email_confirm_time为一个合适的值,便相当于通过验证了。
设置所有用户都通过邮箱验证
sqlite> UPDATE tb_user
···· SET email_confirm_time = '2024-07-22 06:10:17.754347'
···· WHERE email_confirm_time IS NULL;