计量计费与账户充值
简介
计量计费用于完整记录平台所有用户在任何时间、任何场景下使用的 Pod 所占用的资源信息,并据此核算成本。
实现代码
监听采集进程:
myapp/tools/watch_pod.py。该进程运行在infra命名空间下的kubeflow-watchDeployment(Pod)中,通过 supervisord 启动。- 容器启动命令为
supervisord -c /home/myapp/myapp/tools/supervisord.conf(见install/kubernetes/cube/base/deploy-watch.yaml:55)。 supervisord.conf中以[program:watch-pod] command=python myapp/tools/watch_pod.py拉起该进程(见myapp/tools/supervisord.conf:35)。
- 容器启动命令为
历史 Pod 信息通过
myapp/views/view_pod.py实现。Pod 特殊字段通过
myapp/models/model_pod.py设置。
数据库存储
保存在 kubeflow 数据库的 pod 表中(表名见 myapp/models/model_pod.py:23,__tablename__ = 'pod')。
自定义价格信息
在 myapp/models/model_pod.py 中,Pod.price 属性负责计算单个 Pod 的总价格:先从配置文件读取 PRICE_FUN 函数得到每小时单价,再根据起止时间计算运行时长,折算出该任务的总价格。若配置中没有 PRICE_FUN,或计算异常,则价格为 0。
# 获取总价(myapp/models/model_pod.py:54)
@property
def price(self):
price_fun = conf.get('PRICE_FUN', None)
if price_fun:
try:
price = price_fun(self) # 获取每个小时的价格
end_time = datetime.datetime.strptime(self.end_time, '%Y-%m-%d %H:%M:%S') if self.end_time else datetime.datetime.now()
start_time = datetime.datetime.strptime(self.start_time, '%Y-%m-%d %H:%M:%S') if self.start_time else datetime.datetime.now()
duration = (end_time - start_time).total_seconds()
price = round(duration * price / 3600, 2) # 单价 × 时长(秒)/3600 = 总价
return price
except Exception as e:
print(e)
return 0
PRICE_FUN 在配置文件 config.py 中定义,用于返回 Pod 资源的每小时单价。需要自定义价格时,可在配置文件中按集群实际资源单价改写该函数。
说明:
config.py已内置一个非零的默认PRICE_FUN(install/kubernetes/cube/overlays/config/config.py:900、install/docker/config.py:900),按 CPU / 内存 / GPU 的资源量计价;未配置或计算异常时价格为 0。
# 自定义任务计价函数(config.py:900,返回每小时价格)
import pysnooper, datetime, math
# @pysnooper.snoop(watch_explode=('pod'))
def PRICE_FUN(pod):
resource = json.loads(pod.resource)
# 每核 cpu 0.075 元/h 每 G 内存 0.035 元/h gpu v100 16 元/h T4 9 元/h
price = 16 * float(resource.get('gpu', 0)) + 0.075 * float(resource.get('cpu', 0)) + 0.035 * float(resource.get('memory', 0))
return price # 返回每个小时的价格
传入 PRICE_FUN 的 pod 对象包含如下字段(可据此自定义计价逻辑),示例(通过 pysnooper 打印):
pod.end_time = '2024-03-03 19:27:39'
pod.host = '192.168.3.7'
pod.id = 1
pod.name = 'admin-098b'
pod.namespace = 'jupyter'
pod.node_selector = '{"cpu": "true", "notebook": "true", "org": "public"}'
pod.org = 'public'
pod.pod_type = 'notebook'
pod.project = public
pod.project_id = 1
pod.resource = '{"memory": 0, "cpu": 0.0}' # 计价主要依据此字段(含 cpu/memory/gpu)
pod.start_time = '2024-02-29 20:48:44'
pod.status = 'Failed'
pod.user = admin
pod.user_id = 1
提示:计价单价属于运维侧策略配置,落在配置文件中,相关说明见「04-运维管理」的配置文件管理。
账户充值
充值为计量计费与算力 / 存储租赁提供余额入口:用户创建充值订单、完成支付后余额入账,后续账单从余额扣减。
充值订单复用账单 Bill 表(订单号 RC + 时间戳 + 6 位随机);余额挂在用户上(user.get_balance() / user.add_balance())。
后端:
myapp/views/view_recharge.py(路由/recharge_modelview/api);前端pages/Recharge。
支付渠道
| 渠道 | channel |
流程 |
|---|---|---|
| 微信支付 | wechat |
调支付网关生成二维码,扫码支付,网关回调后自动入账 |
| 支付宝 | alipay |
同上 |
| 对公汇款 | offline |
返回对公银行账户信息与汇款识别码(由用户名 + 用户 id + 前缀生成,汇款时备注),管理员确认到账后入账 |
充值档位由前端页面提供,不依赖后端配置。
接口一览
| 接口 | 方法 | 说明 |
|---|---|---|
/account |
GET | 账户信息:当前余额、汇款识别码、对公账户 |
/order/create |
POST | 创建充值订单(amount + channel);微信 / 支付宝返回二维码,对公返回银行信息 |
/order/status/<order_id> |
GET | 查询订单状态(前端轮询用) |
/order/confirm/<order_id> |
POST | 仅管理员:确认对公汇款到账 |
/notify/<channel> |
GET/POST | 支付网关异步回调 |
入账一致性(幂等设计)
入账唯一出口为 settle_order(bill_id):
- 对账单行加锁(
with_for_update)并判断status,未支付才置「已支付」并加余额,已支付直接返回——同一订单余额只加一次; - 加余额时锁用户行——
balance为 JSON 读改写,避免同一用户多笔订单并发入账丢更新。
因此支付回调重复推送、管理员重复点击确认,都不会导致重复入账。 算力 / 存储租赁的按量 / 按天费用从余额扣减,余额不足可触发欠费释放。