实战笔记:搬瓦工 Docker 部署 .NET 8 与 BWH API 自动监控流水线
更多语言
更多操作
最近社区里有不少帖子在讨论各种面板、测速,或者教大家写 Bash 脚本查 VPS 流量。
其实对于开发者来说,买一台网络稳的机器(比如搬瓦工的 GIA),最大的用途还是跑自己的后端服务。但这年头,如果每次改完代码还要手动 FTP 传文件、在宿主机折腾各种运行时环境,不仅容易把系统弄脏,维护起来也挺繁琐。
今天分享一套我日常在用的 DevOps 工作流:在 Linux 下用 Docker Compose 隔离运行 .NET 8 服务,搭配 GitHub Actions 做全自动 CI/CD。顺便用 C# 对接一下搬瓦工官方的 KiwiVM API,写个 Telegram 监控端。废话不多说,直接开始。
01. 代码实现:C# 调用 KiwiVM API 与 TG 机器人
相比传统的 Bash 脚本定时任务,用 C# 的 BackgroundService 做长连接监控更稳定。另外,很多人用脚本查 API 经常会算错流量,主要是没留意官方文档里的两个细节:搬瓦工的高端机房流量是有 monthly_data_multiplier(流量乘数)的,而且 API 返回的日期是 UNIX 时间戳。
我们在 .NET 8 Worker 项目中引入 Telegram.Bot,顺手把这两个容易踩坑的地方处理掉:
using Telegram.Bot;
using Telegram.Bot.Types;
using System.Net.Http.Json;
public class BwhMonitorWorker : BackgroundService
{
private readonly TelegramBotClient _botClient = new("你的_TG_BOT_TOKEN");
private readonly HttpClient _http = new();
private const string API_HOST = "https://" + "api.64clouds.com";
private const string VEID = "你的VEID";
private const string API_KEY = "你的API_KEY";
private const long ADMIN_CHAT_ID = 123456789; // 你的 TG 账号 ID
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_botClient.StartReceiving(HandleUpdateAsync, HandleErrorAsync, null, stoppingToken);
while (!stoppingToken.IsCancellationRequested) await Task.Delay(1000, stoppingToken);
}
private async Task HandleUpdateAsync(ITelegramBotClient bot, Update update, CancellationToken ct)
{
if (update.Message?.Text == null || update.Message.Chat.Id != ADMIN_CHAT_ID) return;
var text = update.Message.Text;
var chatId = update.Message.Chat.Id;
if (text == "/status")
{
var url = $"{API_HOST}/v1/getServiceInfo?veid={VEID}&api_key={API_KEY}";
var res = await _http.GetFromJsonAsync<BwhInfo>(url, ct);
if (res.error == 0)
{
// 细节 1:计算时必须带上官方规定的流量乘数 (monthly_data_multiplier)
double multiplier = res.monthly_data_multiplier > 0 ? res.monthly_data_multiplier : 1;
double usedGb = (res.data_counter * multiplier) / 1024.0 / 1024.0 / 1024.0;
double limitGb = (res.plan_monthly_data * multiplier) / 1024.0 / 1024.0 / 1024.0;
// 细节 2:UNIX 时间戳转换为本地直观时间
DateTime resetDate = DateTimeOffset.FromUnixTimeSeconds(res.data_next_reset).LocalDateTime;
await bot.SendTextMessageAsync(chatId,
$"🖥️ 主机名: {res.hostname}\n" +
$"📍 节点: {res.node_location}\n" +
$"📶 流量: {usedGb:F2}GB / {limitGb:F2}GB\n" +
$"📅 重置: {resetDate:yyyy-MM-dd HH:mm:ss}",
cancellationToken: ct);
}
}
else if (text == "/reboot")
{
var url = $"{API_HOST}/v1/restart?veid={VEID}&api_key={API_KEY}";
var res = await _http.GetFromJsonAsync<BwhInfo>(url, ct);
if(res.error == 0) {
await bot.SendTextMessageAsync(chatId, "🔄 硬件重启指令已发送,请稍候...", cancellationToken: ct);
}
}
}
private Task HandleErrorAsync(ITelegramBotClient bot, Exception ex, CancellationToken ct) => Task.CompletedTask;
}
// 对应官方文档的 JSON 映射类
public class BwhInfo {
public int error { get; set; }
public string hostname { get; set; }
public string node_location { get; set; }
public long data_counter { get; set; }
public long plan_monthly_data { get; set; }
public double monthly_data_multiplier { get; set; }
public long data_next_reset { get; set; }
}
02. Docker 容器化:告别环境污染
我们不需要在宿主机上装任何 .NET SDK 或运行时,直接把程序打包进 Docker。这样不仅系统干净,而且自带进程守护(挂了自动拉起),省去了配置 Systemd 的麻烦。
在代码根目录下新建这两个文件:
1. Dockerfile(极简运行时镜像):
# 使用微软官方轻量级 ASP.NET 8 运行时镜像
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# 拷贝编译好的文件到容器内
COPY . .
# 启动程序 (替换为你的 DLL 名字)
ENTRYPOINT ["dotnet", "MyBwhBot.dll"]
2. docker-compose.yml:
services:
bwh-bot:
build: .
container_name: bwh-telegram-bot
restart: always # 容器挂了自动重启
environment:
- TZ=Asia/Shanghai
03. GitHub Actions 全自动化 CI/CD
服务和容器配置都准备好了,接下来的痛点是怎么自动化部署。这里我们直接用 GitHub Actions 搞定。
只要你往 main 分支推代码,GitHub 就会自动走完这个流程:装 .NET SDK -> 编译代码 -> 打包连同 Docker 文件一起 SCP 传到服务器 -> 最后通过 SSH 触发 docker compose up 构建并重启。
在代码仓库新建 .github/workflows/deploy.yml 文件:
name: Docker Deploy to BWH
on:
push:
branches: [ "main" ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 配置 .NET 8 编译环境
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: 编译发布
run: dotnet publish -c Release -o ./publish_out
- name: 准备 Docker 文件
run: |
cp Dockerfile ./publish_out/
cp docker-compose.yml ./publish_out/
- name: SCP 传输文件到服务器
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.BWH_IP }} # 你的 VPS IP
username: root
key: ${{ secrets.SSH_PRIVATE_KEY }} # 私钥 (在 GitHub 后台配置)
source: "./publish_out/*"
target: "/opt/bwh-bot"
strip_components: 1
- name: SSH 触发 Docker 重新构建
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.BWH_IP }}
username: root
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/bwh-bot
docker compose down
docker compose up -d --build
docker image prune -f # 清理旧的无用镜像
提示:记得在 GitHub 仓库的
Settings -> Secrets and variables -> Actions里把BWH_IP和SSH_PRIVATE_KEY填好。
总结
配置好之后,以后每次在本地更新完代码只要 push 一下,云端的服务就会自动完成重建和替换。
这套流程其实对 VPS 的网络连通性有一定要求。如果服务器网络比较差,GitHub Action 在 SCP 传文件或者 SSH 连接时偶尔会超时报错。平时部署后端服务,建议尽量选网络稳一点的机房(类似 搬瓦工 CN2 GIA 高端线这种),文件传输基本秒达,CI/CD 流水线跑起来会顺畅很多。