半兽人

354 声望

只有比别人更早、更勤奋地努力,才能尝到成功的滋味。

只有比别人更早、更勤奋地努力,才能尝到成功的滋味。

个人动态
  • 半兽人 回复 whatCeph ingress一直创建中,状态0/2 中 :

    你的日志显示是:端口 2049 冲突

    日志中关键的错误信息是:

    Cannot bind to IP 10.0.19.203 port 2049: [Errno 98] Address already in use
    ERROR: TCP Port(s) '10.0.19.203:2049,0.0.0.0:9000' required for haproxy already in use

    原因分析

    1. Ingress (HAProxy) 试图绑定到 2049 端口(这是 NFS 的标准端口,用于对外提供服务)。
    2. 但是,后端的 NFS 守护进程 (nfs.nfs-cephfs...) 当前也配置为监听 2049 端口,并且它已经正在运行中。
    3. 因为端口已被 NFS 守护进程占用,HAProxy 无法启动。

    解决方案

    你需要将后端的 NFS 服务修改为监听一个非标准端口(例如 12049),把 2049 端口让给 Ingress (HAProxy) 使用。

    按照以下步骤操作:

    第一步:导出当前的 NFS 服务配置

    找到你的后端 NFS 服务名称(通常是 nfs.nfs-cephfs,根据你的 ceph orch ps 输出):

    ceph orch ls --service-name nfs.nfs-cephfs --export > nfs_backend.yaml
    

    第二步:修改配置文件

    使用文本编辑器(如 vinano)打开 nfs_backend.yaml
    你需要添加或修改 spec 下的 port 字段。将其设置为一个非 2049 的端口(例如 12049)。

    修改后的文件应该类似这样:

    service_type: nfs
    service_id: nfs-cephfs
    service_name: nfs.nfs-cephfs
    placement:
      count: 1
    spec:
      port: 2049
    

    第三步:应用新配置

    将修改后的配置应用到集群:

    ceph orch apply -i nfs_backend.yaml
    

    第四步:验证 NFS 是否重启

    应用配置后,Ceph 编排器会重启后端的 NFS 守护进程。
    运行以下命令,确认 NFS 守护进程现在是否正在监听新端口(PORTS 列应该显示 *:12049):

    ceph orch ps --service_name nfs.nfs-cephfs
    

    第五步:验证 Ingress 是否恢复

    一旦后端 NFS 释放了 2049 端口,Ingress 服务(HAProxy)应该会自动尝试重启并成功绑定。

    运行以下命令查看状态:

    ceph orch ps --service_name ingress.nfs.nfs-cephfs-ha
    

    或者查看列表:

    ceph orch ls --service-name ingress.nfs.nfs-cephfs-ha
    

    如果 RUNNING 列显示 2/2,说明问题已解决。

    总结

    Ingress 架构是:
    客户端 (2049) -> HAProxy (监听 2049) -> NFS Ganesha (监听 12049)

    你目前两个都试图监听 2049,所以必须把后面那个改成别的端口。

    15天前
  • 半兽人 回复 whatError EPERM: Failed to update caps for nfs.nfs-cephfs.2: updated caps for client.nfs.nfs-cephfs.2 中 :

    这个错误是 Ceph NFS 的经典“权限覆盖”问题,核心意思是:

    现在用 nfs-cephfs 这个名字重新创建 NFS 集群时,系统发现旧的 client(nfs.nfs-cephfs.2)的 caps 还在,但因为旧集群已经被彻底删掉,mgr 想重新给它写 caps,结果发现这个 client 的 key 或者 caps 已经被锁死/残留,导致 Failed to update caps

    一句话:旧的 nfs.nfs-cephfs.* 这个 client 身份还活着,挡住了你用同一个名字重建。

    一键彻底解决(3 条命令,30 秒搞定)

    # 1. 把残留的 client 身份彻底干掉(重点!)
    ceph auth del client.nfs.nfs-cephfs.2
    
    # 2. 如果还有其他残留的 nfs-cephfs.* client,也一起删(一般就这一个)
    ceph auth list | grep -A 5 nfs.nfs-cephfs | grep entity | awk '{print $2}' | xargs -I {} ceph auth del {}
    
    # 3. 现在再创建,就绝对成功了
    ceph nfs export create cephfs nfs-cephfs /ceph myFs --path=/
    
    16天前
  • 半兽人 回复 whatCeph NFS Export 脏数据 中 :

    刷新 Ceph 缓存

    ceph mgr module disable nfs  # 临时禁用 NFS 模块
    ceph mgr module enable nfs   # 重新启用,强制刷新
    

    之后,你在查看一下,应该是缓存

    最后,你在运行 ceph df 检查空间是否释放(残留对象通常很小,但可能影响 PG 平衡)。

    16天前
  • 16天前
  • 发表了 Ceph NFS 僵尸挂载  
    16天前
  • 发表了 Ceph NFS 入门介绍  
    17天前
  • 17天前
  • 18天前
  • 半兽人 回复 whatmount.nfs: mounting 10.0.19.209:/sub1 failed, reason given by server: No such file or directory 中 :

    排查顺序:

    ceph nfs cluster ls
    ceph nfs export ls cephnfs
    ceph fs subvolume ls myFs
    ceph fs subvolume info myFs <name>
    ceph orch ps | grep nfs
    

    另外:敲重点!!!
    如果你是通过Dashboard页面编辑的子目录,是不行的,你需要删除重建这种,为什么我也不太清楚,因为我编辑更改了子目录是不生效的,也许是某个ceph版本的bug吧。

    19天前
  • 半兽人 回复 小蕊ceph健康检查non-power-of-two pg_num警告 中 :

    有 1 个 pool 的 PG 数不是 2 的幂(例如 300、500 这种),在 Ceph 中 不推荐,可能导致分布不均。

    通过以下命令查看:

    ceph osd pool ls detail | grep pg_num
    

    PG 推荐值:

    • 对副本池:每个 pool 的 PG 数取值一般:128、256、512、1024……
    • 对 EC 池:PG 可以不是 2 的幂,但 Ceph 还是推荐 power-of-two

    修复:

    ceph osd pool set <poolname> pg_num 256
    ceph osd pool set <poolname> pgp_num 256
    

    详细解释

    PG 到 OSD 的映射是通过哈希(hash)和一致性映射计算出来的,Ceph 对 PG 的编号是按位(二进制位)运算优化的。如果 PG 数不是 2 的幂,PG 的哈希空间无法平均划分,从而导致某些 PG 映射到更多 OSD,某些更少,引起数据倾斜(PG 不均衡)。

    为什么要 2 的幂?

    因为 二进制世界里最容易等分的就是 2 的幂

    1)PG 映射本质是取哈希值的前几位

    Ceph 用 CRUSH + 哈希把对象分配到 PG。

    过程简化如下:

    object -> hash -> % pg_num -> pg_id
    

    注意关键:

    • % pg_num(取模操作)决定对象落在哪个 PG
    • 如果 pg_num = 2^n,则 % 操作相当于 取哈希的低 n 位(二进制)

    例:

    pg_num = 256 = 2^8
    => object_hash % 256 = 取 hash 的最低 8
    

    这样可以做到:

    • 哈希空间平均切成 256 份
    • 每一份大小一致
    • 每一份对应一个 PG
    • 完全均匀

    因为哈希空间天然在二进制上是均匀的。

    2)如果 pg_num 不是 2 的幂,就不能整齐切分哈希空间

    例如:

    pg_num = 300
    

    哈希空间不能平均分成 300 份:

    • 哈希空间是 2^k 大小(比如 2^32)
    • 300 不是能整整分割这个空间的数
    • %300 后的结果并不均匀 → 会造成偏差

    举例:

    hash % 300
    

    哈希空间范围:0 ~ 2^32-1
    而 2^32 除以 300 会留下余数:

    2^32 % 300 ≠ 0
    

    这意味着:

    • 一些 PG 会覆盖更多哈希范围
    • 一些 PG 覆盖更少
    • PG 的对象数量不均
    • 所属的 OSD 负载开始倾斜

    最终导致:OSD 空间、IO、PG 数量都不平衡。

    3)CRUSH 映射也因为 PG 分布不均而无法完全平衡

    CRUSH 的目标是:

    • PG 在 OSD 上均匀分布
    • 尽可能保证副本在不同机架、主机、盘上分开

    但 CRUSH 的前提是:

    PG 自己必须是均匀的。

    如果 PG 数内部已经倾斜(因为不是 2 的幂),那么:

    • 一些 OSD 会被分到更多 PG
    • 有的 OSD 负载更大
    • 有的更空
    • 数据迁移时也会不均匀

    尤其OSD 数少时(比如你只有 6 个 OSD)这个不均匀放大特别明显

    4)举个超级直观的例子(最容易理解)

    假设:

    • 你有哈希空间大小 1024(2^10)
    • 你把它分成 10 份(pg_num = 10)

    1024 / 10 = 102 余 4

    所以:

    • 有 4 个 PG 得到 103 单位空间
    • 其他 PG 得到 102 单位空间

    看似差不大,但 Ceph 一个 PG 可能对应几十 GB 数据。

    这个小差异会导致 OSD 最终负载明显不同。

    如果使用:

    pg_num = 8 (2^3)
    

    则:

    1024 / 8 = 128(整除)
    

    每个 PG 完全一样 → 完美均匀。

    5)真实案例(Ceph 官方报告)

    官方统计表明:

    • pg_num 使用非 2 的幂,会出现 5%~30% 的数据倾斜
    • OSD 数越少,倾斜越严重
    • 后期扩容/缩容时倾斜更明显

    所以直接在 health 警告里要求修复。

    6)为什么 Ceph 允许不是 2 的幂?

    历史原因 + 某些算法可以容忍,但最终都建议:

    powers of 2
    

    尤其你这种 6 OSD 的小集群,影响更大。

    总结(最简精华)

    PG 是从哈希空间切分出来的,而哈希空间天然是 2^n 的二进制结构。
    用 2 的幂作为 PG 数可以完美等分哈希空间,数据分布才会均匀。
    如果 PG 不为 2 的幂,则哈希空间无法整除,PG 分布必然不均匀,导致 OSD 空间、IO 倾斜。

    26天前
  • 赞了 半兽人启动kafka时候,报java.net.bindException: cannot assign requested address错误? 的评论!

    ip不存在,不能使用外网,绑定内网ip。

    2月前
  • 半兽人 赞了 在 启动kafka时候,报java.net.bindException: cannot assign requested address错误? 的评论!

    ip不存在,不能使用外网,绑定内网ip。

    2月前
  • 無名 赞了 在 什么是Low-Level Server? 的评论!

    “Low-Level Server”这个名字,源于软件开发中一个常见的术语:“low-level”(低层/底层)。它不是说“服务器很差”,而是强调它比高级封装更接近底层、原始的实现,意思是:

    1. 什么是 “Low-Level Server”?

    在 MCP 框架中,Low-Level Server 是一种更接近协议核心、需要你手动控制生命周期、注册处理函数的服务器写法,也就是说:

    • 你负责:

      • 启动关闭逻辑(生命周期)
      • 注册处理函数(比如 call_tool()list_prompts() 等)
      • 管理数据资源(比如数据库连接)
      • 使用低层 API(如 server.run()

    它不像官方的 mcp runmcp dev 那样封装好一整套流程(这些是 high-level 工具,自动帮你处理各种事情)。

    2. 为什么叫 “low-level”?

    • 因为这是“手动控制一切”的模式,没有帮你封装生命周期、标准输入输出处理、异常处理等。
    • 和 “high-level”(高级封装) 相对:

      • high-level 例子:uv run mcp run
      • low-level:你自己写 server.run(...),你控制 lifespan,你处理 stream

    就像 Python 的 asyncio

    • high-level:asyncio.run(main())
    • low-level:你用 loop = asyncio.get_event_loop() 手动跑 loop

    3. 什么时候要用 low-level server?

    用它是为了“更细粒度的控制”,适合这几种场景:

    场景 为什么用 low-level server
    你想注册多个自定义 handler,比如 get_promptcall_tool 高级接口可能不够灵活
    你想控制资源生命周期,比如连接数据库、清理缓存 需要用 lifespan
    你要接入自定义协议或更复杂的启动流程 高级封装不支持
    你不想使用 mcp runuv run 这些不支持 low-level server

    4. 总结一句话:

    “low-level server” 指的是一种更底层、自由度更高、但需要你自己处理细节的服务器写法。适合需要完全控制启动流程、资源管理、自定义能力的高级开发者。

    5月前
  • 赞了 半兽人什么是Low-Level Server? 的评论!

    “Low-Level Server”这个名字,源于软件开发中一个常见的术语:“low-level”(低层/底层)。它不是说“服务器很差”,而是强调它比高级封装更接近底层、原始的实现,意思是:

    1. 什么是 “Low-Level Server”?

    在 MCP 框架中,Low-Level Server 是一种更接近协议核心、需要你手动控制生命周期、注册处理函数的服务器写法,也就是说:

    • 你负责:

      • 启动关闭逻辑(生命周期)
      • 注册处理函数(比如 call_tool()list_prompts() 等)
      • 管理数据资源(比如数据库连接)
      • 使用低层 API(如 server.run()

    它不像官方的 mcp runmcp dev 那样封装好一整套流程(这些是 high-level 工具,自动帮你处理各种事情)。

    2. 为什么叫 “low-level”?

    • 因为这是“手动控制一切”的模式,没有帮你封装生命周期、标准输入输出处理、异常处理等。
    • 和 “high-level”(高级封装) 相对:

      • high-level 例子:uv run mcp run
      • low-level:你自己写 server.run(...),你控制 lifespan,你处理 stream

    就像 Python 的 asyncio

    • high-level:asyncio.run(main())
    • low-level:你用 loop = asyncio.get_event_loop() 手动跑 loop

    3. 什么时候要用 low-level server?

    用它是为了“更细粒度的控制”,适合这几种场景:

    场景 为什么用 low-level server
    你想注册多个自定义 handler,比如 get_promptcall_tool 高级接口可能不够灵活
    你想控制资源生命周期,比如连接数据库、清理缓存 需要用 lifespan
    你要接入自定义协议或更复杂的启动流程 高级封装不支持
    你不想使用 mcp runuv run 这些不支持 low-level server

    4. 总结一句话:

    “low-level server” 指的是一种更底层、自由度更高、但需要你自己处理细节的服务器写法。适合需要完全控制启动流程、资源管理、自定义能力的高级开发者。

    5月前
  • 半兽人 赞了 在 什么是Low-Level Server? 的评论!

    “Low-Level Server”这个名字,源于软件开发中一个常见的术语:“low-level”(低层/底层)。它不是说“服务器很差”,而是强调它比高级封装更接近底层、原始的实现,意思是:

    1. 什么是 “Low-Level Server”?

    在 MCP 框架中,Low-Level Server 是一种更接近协议核心、需要你手动控制生命周期、注册处理函数的服务器写法,也就是说:

    • 你负责:

      • 启动关闭逻辑(生命周期)
      • 注册处理函数(比如 call_tool()list_prompts() 等)
      • 管理数据资源(比如数据库连接)
      • 使用低层 API(如 server.run()

    它不像官方的 mcp runmcp dev 那样封装好一整套流程(这些是 high-level 工具,自动帮你处理各种事情)。

    2. 为什么叫 “low-level”?

    • 因为这是“手动控制一切”的模式,没有帮你封装生命周期、标准输入输出处理、异常处理等。
    • 和 “high-level”(高级封装) 相对:

      • high-level 例子:uv run mcp run
      • low-level:你自己写 server.run(...),你控制 lifespan,你处理 stream

    就像 Python 的 asyncio

    • high-level:asyncio.run(main())
    • low-level:你用 loop = asyncio.get_event_loop() 手动跑 loop

    3. 什么时候要用 low-level server?

    用它是为了“更细粒度的控制”,适合这几种场景:

    场景 为什么用 low-level server
    你想注册多个自定义 handler,比如 get_promptcall_tool 高级接口可能不够灵活
    你想控制资源生命周期,比如连接数据库、清理缓存 需要用 lifespan
    你要接入自定义协议或更复杂的启动流程 高级封装不支持
    你不想使用 mcp runuv run 这些不支持 low-level server

    4. 总结一句话:

    “low-level server” 指的是一种更底层、自由度更高、但需要你自己处理细节的服务器写法。适合需要完全控制启动流程、资源管理、自定义能力的高级开发者。

    5月前
  • 西宫. ​ 赞了 在 docker中文教程 的评论!

    2022开工大吉。
    「即使现在,对手也不停地翻动书页。」 --- 《哈佛图书馆二十条训言》

    6月前
  • 赞了 半兽人kafka2.4.0的MirrorMaker2.0配置问题 的评论!

    是的,kafka 2.4还不支持2.0,所以你的还是1.0版本。

    解决,升级kafka版本

    • kafka 2.6+ 后是支持的。

    可参考:https://cwiki.apache.org/confluence/display/KAFKA/KIP-382%3A+MirrorMaker+2.0

    6月前
  • 半兽人 赞了 在 kafka2.4.0的MirrorMaker2.0配置问题 的评论!

    是的,kafka 2.4还不支持2.0,所以你的还是1.0版本。

    解决,升级kafka版本

    • kafka 2.6+ 后是支持的。

    可参考:https://cwiki.apache.org/confluence/display/KAFKA/KIP-382%3A+MirrorMaker+2.0

    6月前
  • 夜雨 赞了 在 kafka发送延时消息 的评论!

    可以使用RabbitMQ,Kafka天然不支持做延迟队列,因为它的消息没有状态。

    9月前
  • ? 赞了 在 Kubernetes安装Kafka集群 的评论!

    嗯,k8s的版本,所以对参数有区别,可以参考:https://www.kubebiz.com/KubeBiz/kafka
    里面有k8s的版本选择,会自动补全。

    9月前
  • Z 关注了Ta · 9月前
  • 赞了 半兽人volatile关键字解析 - java并发 的评论!

    噢噢噢,
    volatile不保证原子性

    10月前
  • 怎奈 赞了 在 KafkaStreams客户端(0.10.1.1 API) 的评论!

    好的,我最近更新一下

    11月前
  • 怎奈 赞了 在 Kubernetes StatefulSet介绍 的评论!

    好的,我尽快补充,我以为人看的少 :(

    1年前
  • 土豆 赞了 在 镜像拉取失败google_containers/kicbase:v0.0.45 的评论!

    一般是基础镜像的问题:

    先手动拉取:

    docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.45
    

    然后手动指定基础镜像:

    minikube start --force --base-image='registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.45'
    
    1年前
  • 赞了 半兽人CDH Kafka 配置权限未生效 的评论!
    enabled mechanisms are []
    

    启用的机制是空,并没有生效,先看看kafka日志中是否有什么异常。
    另外,我看你配置里有些其他的认证方式,建议你注掉,防止干扰。
    可参考:https://www.orchome.com/1966
    先保证命令行可以运行成功。

    1年前
  • 赞了 半兽人CDH Kafka 配置权限未生效 的评论!
    LISTENERS=listeners=PLAINTEXT://phm-data02:9092,
    

    这个换成

    LISTENERS=listeners=SASL_PLAINTEXT://phm-data02:9092
    
    1年前
  • 赞了 半兽人镜像拉取失败google_containers/kicbase:v0.0.45 的评论!

    一般是基础镜像的问题:

    先手动拉取:

    docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.45
    

    然后手动指定基础镜像:

    minikube start --force --base-image='registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.45'
    
    1年前
  • Sisyphus 关注了Ta · 1年前
  • yh 关注了Ta · 1年前
  • 赞了 半兽人Kubernetes Namespace命名空间状态为Terminating,一直处于“卡住”,我该如何删除它? 的评论!

    无法删除是因为命名空间中仍然存在的资源引起的。

    以下命令显示命名空间中剩余的资源:

    kubectl api-resources --verbs=list --namespaced -o name \
      | xargs -n 1 kubectl get --show-kind --ignore-not-found -n <namespace>
    

    一旦你移除了这些资源之后,命名空间就能删掉了。

    1年前
  • g 关注了Ta · 1年前
  • 🌂️ 关注了Ta · 1年前
  • 赞了 NFS 高可用部署 · 1年前
  • 赞了 半兽人外网连接内网kafka-kerberos 报错验证失败! 的评论!

    感谢大佬的指点,目前已经全部调通,包括kerberos环境!

    非kerberos环境最后配置的格式就是上面贴的。
    kerberos环境 大致还需要以下几点。

    1、kafka-server端加了环境变量

    export KAFKA_OPTS="-Djava.security.auth.login.config=/usr/hdp/current/kafka-broker/conf/kafka_jaas.conf"
    

    2、/etc/krb5.conf文件可能需要加一行udp_preference_limit = 1 将udp改成tcp防止丢包(这个不一定需要)

    3、客户端需要一个kafka_client_jaas.conf

    KafkaClient {
       com.sun.security.auth.module.Krb5LoginModule required
       useTicketCache=true
       renewTicket=true
       serviceName="kafka";
    };
    Client {
       com.sun.security.auth.module.Krb5LoginModule required
       useTicketCache=true
       renewTicket=true
       serviceName="zookeeper";
    };
    

    4、然后一些sasl的配置,监听器的配置就不赘述了

    总结:之前对“主动发现集群机制”了解不够,也不知道消费时要对每一个broker都开一个长连接* 加上报错一直都是权限验证失败让人感觉是kerberos的问题,绕了很久。后面排除无关的因素,就很明显了。另外提醒ambari安装的kafka不管界面上配置的advertised.listeners是多少,内部代码还是会强行将listeners的值赋给advertised.listeners。

    还是很感谢大佬的及时回复 耐心指导。期待以后更多的交流

    1年前
  • 关注了用户 無名 · 1年前