BOSh
文章227
标签320
分类74
315晚会 36氪 80后 ADB AI AI Agent AI 代理 AI 助手 AI 网关 AI 评测 AI助手 AI大模型 AI安全 AI应用 AI智能体 AI网关 API API 集成 Agent AionUi Android Automation C++ CLI CLI Proxy API CLIProxyAPI CRM Chrome 插件 Claude Opus 4.6 ConnectBot Debian DeepSeek DenchClaw DevOps Docker GEO GPL GPS GPU Gemini Gemini 3.1 Pro GitHub Gmail Gog Google AI Pro Google API Google Gemini HKUDS Hermes Hermes Agent Hexo Hugo IPV6 Jetpack Compose Kimi-K2.5 Kotlin LINUX LaTeX Linux Markdow Markdown MemU Bot MiniMax NAT64 NIX NODE NVIDIA Build NanoClaw Netcatty Newsletter Open WebUI OpenAI OpenAI 兼容接口 OpenCLI OpenClaw PDF 编译 PicoClaw Prismer QClaw QQ机器人 RAG Reddit Rust SFTP SSH Skills Subagent SuperCall Telegram Bot WebSSH Windows WorkBuddy X X热榜 YouTube ZeroClaw arXiv arch c++ git hugo iMessage n8n nanobot node js ntfs pacman podman zz.ac 东海 两性关系 个人助理 中东 中东冲突 中东局势 中关村论坛 中南大学 中国 中美 习惯养成 云同步 亚洲 代理 以色列 任务管理 伊朗 伊朗危机 伊朗战争 伦理 体育 保护主义 信息流 信息管理 停火 健康管理 光通信 免费试用 共和党 养老金 内容工厂 内容生产 内容筛选 军事冲突 军事动态 军民融合 农村 分享 创业 办公自动化 加密 加密货币 加沙 北斗 医学生 半导体 华为 博客 博客助手 博客部署成功 卫星 原生 JS 反重力 台海局势 台湾 命令 喷嚏网 国产 国产化 国产替代 国际 国际关系 国际局势 国际新闻 图卦 图说 地缘政治 基础设施 多代理 多模态AI 大学分析 大模型 孙少平 学习 安全 实时监控 家庭助理 家庭服务器 家装设计 工作总结 工作效率 工作流编排 工具链 平凡的世界 平台责任 开发 开发实录 开源 开源项目 张雪峰 微信 心理健康 情感 战争 投资工具 指标看板 提示词工程 播客 收件箱清理 效率 效率工具 教程 教育制度 数据分析 数据投毒 文献管理 新能源汽车 新闻汇总 日历聚合 时事 时事总结 显卡 晨报 智能体 智能体生态 朝鲜 架构 架构实践 核协议 核武器 桌面Cowork 模型接入 每日图说 比亚迪 油价 活动运营 浏览器自动化 消息通道 消费者权益 渔船 游戏开发 湘雅医院 热点新闻 版本更新 特朗普 生态系统 生活 生活自动化 生物识别 用例 甲骨文云 电池技术 症状追踪 皮皮虾 监管 目标管理 知识库 社交媒体 社会保障 社会百态 社会观察 科技 科研助手 笔记 第一财经 算法推荐 纽森 经济 经济观察 经验分享 编程 网关 网络 网络安全 美伊关系 美伊冲突 美国 美国大选 美国政治 能源安全 能源市场 腾讯 腾讯,龙虾,OpenClaw 腾讯云 自动化 自动化创作 自动化协作 自动化提醒 自动化流水线 自动化运维 自律教练 自由软件 行为改变 视频摘要 记录 许可证 论文写作 论文阅读 语义搜索 语音代理 读书 读书笔记 读后感 财报季 路遥 迁移 运维 远程运维 邀请确认 部署指南 量子计算 销售自动化 阅读感悟 随笔 霍尔木兹海峡 项目管理 飞书 高中生活 高考志愿 黎巴嫩 龙虾

一言

文章归档

谷歌云!将白嫖进行到底

谷歌云!将白嫖进行到底

Google Cloud 免费计划 {#Google-Cloud-免费计划.wp-block-heading}

具体步骤可参考这篇文章 谷歌云Free Tier长期免费云服务器

  • 1 non-preemptible e2-micro VM instance per month in one of the following US regions:
    • Oregon: us-west1
    • Iowa: us-central1
    • South Carolina: us-east1
  • 30 GB-months standard persistent disk (标准永久性磁盘HDD)
  • 1 GB of outbound data transfer from North America to all region destinations (excluding China and Australia) per month
  • 网络选择 标准网络层级 每月有200g的免费流量,

另外创建虚拟机实例页面有两个坑

数据保护中的备份要去掉,选择无备份
可观测性中 Ops Agent 安装要去掉,这是用于监控和日志录的,按量付费

创建交换分区 {#创建交换分区.wp-block-heading}

由于 e2-micro 只有1G 内存,创建交换分区可以缓解内存不足的状况

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

使交换空间永久生效,编辑 /etc/fstab 文件,添加以下内容:

/swapfile none swap sw 0 0

优化交换使用策略,编辑 /etc/sysctl.conf,添加:

vm.swappiness=10

Cloudflare ddns {#Cloudflare-ddns.wp-block-heading}

按照免费计划创建的 e2-micro 还有一个缺陷就是公网 IP 是临时性的,关机后会更换 IP 地址。这个可以用 ddns 解决,将域名托管在 cloudflare ,通过cloudflare API 动态更新域名的解析地址,以后地址再怎么变也不怕

后面就可以安心用域名 ssh 登录,下面是脚本每次重启自动运行一次,推荐使用 systemd 服务而不用 crontab,systemd 服务可确保在网络准备好之后再运行脚本

#!/bin/bash

# --- Configuration ---
AUTH_EMAIL="" # Optional: Older API key method
API_TOKEN="" # !!! 必须修改: Your Cloudflare API Token !!!
ZONE_ID="" # !!! 必须修改: Your Zone ID !!!
RECORD_TYPE="A" # !!! 必须修改: Record type ("A" or "AAAA") !!!
LOG_FILE="" # !!! 检查确认: Log file path and permissions !!!
IP_CACHE_FILE="" # !!! 检查确认: Cache file path and permissions !!!
SLEEP_BETWEEN_RECORDS=1 # Seconds to wait between processing records (0 to disable)

# !!! 必须修改: List of DNS records to update !!!
RECORD_NAMES=(
"baidu.com"
"sb.baidu.com"
# Add more records here...
)

# Public IP lookup services (primary and backups)
if [ "<span class="katex math inline">RECORD_TYPE" == "A" ]; then
IP_SERVICES=("https://api.ipify.org" "https://icanhazip.com" "https://ipinfo.io/ip")
IP_TYPE_DESC="IPv4"
elif [ "</span>RECORD_TYPE" == "AAAA" ]; then
IP_SERVICES=("https://api64.ipify.org" "https://icanhazip.com" "https://ipinfo.io/ip")
IP_TYPE_DESC="IPv6"
else
# Log initial config error to file if possible, otherwise echo
err_msg="<span class="katex math inline">(date '+%Y-%m-%d %H:%M:%S') - Error: Unsupported RECORD_TYPE:</span>RECORD_TYPE"
if [ -n "<span class="katex math inline">LOG_FILE" ] && [ -d "</span>(dirname "<span class="katex math inline">LOG_FILE")" ] && [ -w "</span>(dirname "<span class="katex math inline">LOG_FILE")" ]; then
echo "</span>err_msg" >> "<span class="katex math inline">LOG_FILE"
else
echo "</span>err_msg"
fi
exit 1
fi
# --- End Configuration ---

# --- Helper function for logging ---
log() {
local message="<span class="katex math inline">1"
local log_entry="</span>(date '+%Y-%m-%d %H:%M:%S') - <span class="katex math inline">message"
# Try to log to file, fallback to echo if file/dir not writable or not set
if [ -n "</span>LOG_FILE" ]; then
if ! echo "<span class="katex math inline">log_entry" >> "</span>LOG_FILE" 2>/dev/null; then
echo "<span class="katex math inline">log_entry (Error writing to log file:</span>LOG_FILE)"
fi
else
echo "<span class="katex math inline">log_entry"
fi
}

# --- Function to get current public IP with redundancy ---
get_current_ip() {
local ip=""
for service in "</span>{IP_SERVICES[@]}"; do
log "Attempting to get public IP from <span class="katex math inline">service..."
ip=</span>(curl -s --connect-timeout 5 "<span class="katex math inline">service")
if [[ -n "</span>ip" && "<span class="katex math inline">ip" =~ ^[0-9a-fA-F.:]+</span> ]]; then
log "Successfully retrieved IP: <span class="katex math inline">ip from</span>service"
echo "<span class="katex math inline">ip"
return 0
else
log "Failed or received invalid response from</span>service (Response: <span class="katex math inline">ip)"
fi
done
log "Error: Could not retrieve current public IP from any service."
return 1
}

# --- Main Logic ---

# 1. Get current public IP address
CURRENT_IP=</span>(get_current_ip)
if [ <span class="katex math inline">? -ne 0 ]; then
exit 1
fi
log "Current public</span>IP_TYPE_DESC IP is: <span class="katex math inline">CURRENT_IP"

# 2. Compare with cached IP
CACHED_IP=""
if [ -f "</span>IP_CACHE_FILE" ]; then
CACHED_IP=<span class="katex math inline">(cat "</span>IP_CACHE_FILE")
log "Last known IP from cache (<span class="katex math inline">IP_CACHE_FILE):</span>CACHED_IP"
fi

if [ "<span class="katex math inline">CURRENT_IP" == "</span>CACHED_IP" ]; then
log "Public IP (<span class="katex math inline">CURRENT_IP) matches cached IP. No Cloudflare update needed."
exit 0
fi

log "Public IP (</span>CURRENT_IP) differs from cached IP (<span class="katex math inline">CACHED_IP) or cache is empty. Proceeding with Cloudflare checks..."

# --- Loop through each record name ---
log "Starting DNS update process for</span>{#RECORD_NAMES[@]} record(s)..."
ALL_SUCCESS=true
UPDATES_PERFORMED=false

for RECORD_NAME in "<span class="katex math inline">{RECORD_NAMES[@]}"; do
log "--- Processing record:</span>RECORD_NAME ---"

# 3. Get the DNS Record ID, current IP, and **Proxied Status** for THIS record from Cloudflare
RECORD_INFO=<span class="katex math inline">(curl -s --connect-timeout 10 -X GET "https://api.cloudflare.com/client/v4/zones/</span>ZONE_ID/dns_records?type=<span class="katex math inline">RECORD_TYPE&name=</span>RECORD_NAME" \
-H "Authorization: Bearer <span class="katex math inline">API_TOKEN" \
-H "Content-Type: application/json")

RECORD_SUCCESS=</span>(echo "<span class="katex math inline">RECORD_INFO" | jq -r '.success')
if [ "</span>RECORD_SUCCESS" != "true" ]; then
log "Error querying DNS record for '<span class="katex math inline">RECORD_NAME'. API Response:</span>RECORD_INFO"
ALL_SUCCESS=false
continue
fi

RECORD_COUNT=<span class="katex math inline">(echo "</span>RECORD_INFO" | jq '.result | length')
if [ "<span class="katex math inline">RECORD_COUNT" -eq 0 ]; then
log "Error: DNS record '</span>RECORD_NAME' (<span class="katex math inline">RECORD_TYPE) not found. Skipping."
ALL_SUCCESS=false
continue
fi
if [ "</span>RECORD_COUNT" -gt 1 ]; then
log "Warning: Multiple records found for '<span class="katex math inline">RECORD_NAME' (</span>RECORD_TYPE). Using the first one."
fi

# Extract ID, IP, and Proxied Status
RECORD_ID=<span class="katex math inline">(echo "</span>RECORD_INFO" | jq -r '.result[0].id')
RECORD_IP=<span class="katex math inline">(echo "</span>RECORD_INFO" | jq -r '.result[0].content')
RECORD_PROXIED_STATUS=<span class="katex math inline">(echo "</span>RECORD_INFO" | jq -r '.result[0].proxied') # <<< 添加: 获取代理状态

# Validate all extracted values
if [ -z "<span class="katex math inline">RECORD_ID" ] || [ "</span>RECORD_ID" == "null" ] || \
[ -z "<span class="katex math inline">RECORD_IP" ] || [ "</span>RECORD_IP" == "null" ] || \
[ -z "<span class="katex math inline">RECORD_PROXIED_STATUS" ] || [ "</span>RECORD_PROXIED_STATUS" == "null" ]; then # <<< 添加: 验证代理状态
log "Error: Could not retrieve valid DNS Record ID, IP, or Proxied status for '<span class="katex math inline">RECORD_NAME'. Response:</span>RECORD_INFO. Skipping."
ALL_SUCCESS=false
continue
fi

log "Cloudflare DNS IP for <span class="katex math inline">RECORD_NAME is:</span>RECORD_IP, Proxied: <span class="katex math inline">RECORD_PROXIED_STATUS" # <<< 修改: 日志包含代理状态

# 4. Compare IPs and update THIS record if necessary, preserving proxied status
if [ "</span>CURRENT_IP" != "<span class="katex math inline">RECORD_IP" ]; then
log "IP has changed for</span>RECORD_NAME (<span class="katex math inline">RECORD_IP -></span>CURRENT_IP). Updating Cloudflare DNS record..."

UPDATE_RESPONSE=<span class="katex math inline">(curl -s --connect-timeout 10 -X PUT "https://api.cloudflare.com/client/v4/zones/</span>ZONE_ID/dns_records/<span class="katex math inline">RECORD_ID" \
-H "Authorization: Bearer</span>API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"<span class="katex math inline">RECORD_TYPE\",\"name\":\"</span>RECORD_NAME\",\"content\":\"<span class="katex math inline">CURRENT_IP\",\"ttl\":1,\"proxied\":</span>RECORD_PROXIED_STATUS}") # <<< 修改: 使用变量 <span class="katex math inline">RECORD_PROXIED_STATUS

UPDATE_SUCCESS=</span>(echo "<span class="katex math inline">UPDATE_RESPONSE" | jq -r '.success')

if [ "</span>UPDATE_SUCCESS" == "true" ]; then
log "Successfully updated DNS record for <span class="katex math inline">RECORD_NAME to</span>CURRENT_IP (Proxied status kept as <span class="katex math inline">RECORD_PROXIED_STATUS)."
UPDATES_PERFORMED=true
else
log "Error updating DNS record for</span>RECORD_NAME. API Response: <span class="katex math inline">UPDATE_RESPONSE"
ALL_SUCCESS=false
fi
else
log "Cloudflare IP for</span>RECORD_NAME already matches current public IP (<span class="katex math inline">CURRENT_IP). No update needed."
fi

# Optional sleep between records
if [ "</span>SLEEP_BETWEEN_RECORDS" -gt 0 ]; then
log "Waiting <span class="katex math inline">{SLEEP_BETWEEN_RECORDS}s before next record..."
sleep "</span>SLEEP_BETWEEN_RECORDS"
fi

done # --- End of loop for RECORD_NAMES ---

# 5. Update cache file if the process completed without critical errors preventing updates and IP changed
# (We update cache even if some non-critical errors occurred for individual records,
# as long as we successfully determined the new public IP differs from the old cache)
if [ "<span class="katex math inline">CURRENT_IP" != "</span>CACHED_IP" ]; then
# Check if we should update the cache. We update if *any* update was attempted or if cache was initially empty.
# We might choose *not* to update if ALL attempts failed, but let's update if the public IP genuinely changed.
log "Updating IP cache file <span class="katex math inline">IP_CACHE_FILE with new IP:</span>CURRENT_IP"
echo "<span class="katex math inline">CURRENT_IP" > "</span>IP_CACHE_FILE"
# More conservative approach: only update cache if <span class="katex math inline">ALL_SUCCESS is true AND</span>UPDATES_PERFORMED is true.
# if <span class="katex math inline">ALL_SUCCESS &&</span>UPDATES_PERFORMED; then ... echo ... fi
fi


log "Finished processing all records."
if $ALL_SUCCESS; then
exit 0
else
log "One or more errors occurred during the update process. Check logs above."
exit 1
fi

填入开头的变量,可遍历修改同个ID域的多个二级域名,其中会生成 log 文件,和 ip 缓存文件, ip 缓存文件用于比对 ip 免于频繁访问 API 触发风控。

在 /etc/systemd/system/ 目录下创建 systemd 服务单元文件 cloudflare-ddns.service

[Unit]
Description=Cloudflare DDNS Update Script
Wants=network-online.target # 需要网络连接后才运行
After=network-online.target # 在网络连接之后运行

[Service]
Type=oneshot # 只需要运行一次
ExecStart= # 脚本的完整路径
RemainAfterExit=yes # 即使 ExecStart 完成,服务也视为活动 (可选,对 oneshot 有用)
# 如果你的脚本需要以特定用户运行,可以取消下面一行的注释
# User=guest
# 注意:如果以特定用户运行,确保该用户对日志文件/目录有写入权限

[Install]
WantedBy=multi-user.target # 在多用户模式下启用

重新加载 systemd 配置,设置服务开机自启

sudo systemctl daemon-reload
sudo systemctl enable cloudflare-ddns.service

立即运行一次服务来测试它,检查服务状态(cloudflare-ddns.service需要将注释去掉,不然会报错)

sudo systemctl start cloudflare-ddns.service
sudo systemctl status cloudflare-ddns.service

安全防护 CrowdSec {#安全防护-CrowdSec.wp-block-heading}

服务器当然要对外服务,难免要开放个别端口,如果只是用来 ssh remote 完全可以安装 VPN 只对自己开放( IP 地址会经常被封),为了避免被反薅需要特别的安全防护,长亭雷池太吃资源所以选择了 CrowdSec ,有总比没有好

安装步骤

直接按照官网走就好,注册了账号会提示,其中安全组件,我只安装了防火墙和 Nginx,规则订阅的话因为没有会员选择了官方推荐的 Firehol greensnow.co list 和 Free Proxies list

网络达量停机 {#网络达量停机.wp-block-heading}

云主机最怕的就是被刷流量,一觉醒来一套房就没了,采用基于 vnstat 的流量监控和自动停机方案可以防止流量超标,

为云主机实现网络达量停机
上面是我参考的博客文章,不过里面的脚本也太简洁了,我还是让 ChatGPT 帮我另外写了一个脚本,每分钟执行一次 sudo crontab e,因为关机需要 sudo 操作

#!/bin/bash
#
# 脚本名称: check_traffic_limit.sh
# 描述:     监控指定网络接口的月度流量,并在达到限制时关闭服务器。
# 作者:     (根据用户需求编写)
# 日期:     2025-04-12
#
# 注意:     此脚本需要以 root 权限运行 (例如通过 sudo) 才能执行关机操作
#           并且需要确保 vnstat, jq 命令已安装。
#           建议通过 cron 定期执行此脚本。
#

# --- 配置项 ---
INTERFACE="ens4"                  # 要监控的网络接口名称
LIMIT_GB=200                      # 流量限制 (单位: GB)
BYTES_PER_GB=1000000000           # 每 GB 的字节数 (10^9, SI 标准)
LOG_FILE="/var/log/traffic_limit_check.log" # 日志文件路径

# --- 计算字节单位的限制值 ---
LIMIT_BYTES=<span class="katex math inline">((LIMIT_GB * BYTES_PER_GB))

# --- 命令路径 (自动查找) ---
# 使用 command -v 查找命令路径,更可靠
VNSTAT_CMD=</span>(command -v vnstat)
JQ_CMD=<span class="katex math inline">(command -v jq)
SHUTDOWN_CMD="/sbin/shutdown" #crontab 可能没有 PATH 环境变量 使用 sudo which shutdown 来查找
AWK_CMD=</span>(command -v awk)
DATE_CMD=<span class="katex math inline">(command -v date)

# --- 脚本选项 ---
# set -o pipefail # 让管道命令的返回值取最后一个非零返回值 (如果需要更严格的错误处理)

# --- 日志记录函数 ---
log_message() {
    # 将带有时间戳的消息追加到日志文件
    echo "</span>(<span class="katex math inline">DATE_CMD '+%Y-%m-%d %H:%M:%S') -</span>1" >> "<span class="katex math inline">LOG_FILE"
}

# --- 依赖检查 ---
# 检查必要的命令是否存在
if [ -z "</span>VNSTAT_CMD" ]; then
    log_message "错误: 未找到 'vnstat' 命令。请安装 vnstat 并确保其在 PATH 中。"
    exit 1
fi
if [ -z "<span class="katex math inline">JQ_CMD" ]; then
    log_message "错误: 未找到 'jq' 命令。请安装 jq 并确保其在 PATH 中。"
    exit 1
fi
if [ -z "</span>SHUTDOWN_CMD" ]; then
    # 注意: shutdown 通常需要 root 权限才能找到或执行
    log_message "错误: 未找到 'shutdown' 命令。请确保以 root 权限运行,或命令路径正确。"
    exit 1
fi
if [ -z "<span class="katex math inline">AWK_CMD" ]; then
    log_message "错误: 未找到 'awk' 命令。"
    exit 1
fi
if [ -z "</span>DATE_CMD" ]; then
    # 这个几乎不可能找不到,但以防万一
    log_message "错误: 未找到 'date' 命令。"
    exit 1
fi

# --- 主要逻辑 ---
log_message "====== 开始检查接口 <span class="katex math inline">INTERFACE 的流量 ======"
log_message "配置限制:</span>LIMIT_GB GB (<span class="katex math inline">LIMIT_BYTES 字节)"

# 获取指定接口的月度流量数据 (JSON 格式)
# 使用 -i 指定接口,--json m 获取月度 json
JSON_DATA=</span>(<span class="katex math inline">VNSTAT_CMD --json m -i "</span>INTERFACE")
VNSTAT_EXIT_CODE=<span class="katex math inline">?

# 检查 vnstat 命令是否成功执行
if [</span>VNSTAT_EXIT_CODE -ne 0 ]; then
    log_message "错误: vnstat 命令执行失败 (接口: <span class="katex math inline">INTERFACE, 退出码:</span>VNSTAT_EXIT_CODE)。请检查 vnstat 是否正确配置并运行。"
    # 可以选择记录 vnstat 可能的错误输出 (如果有的话)
    # log_message "vnstat 输出: <span class="katex math inline">JSON_DATA" # 注意 JSON_DATA 此时可能包含错误信息而非 JSON
    exit 1
fi

# 使用 jq 解析 JSON 获取当月的 rx 和 tx 字节数
# **重要修正**: 使用 .name 选择接口,而不是 .id
# 使用 '.interfaces[] | select(.name ==</span>iface) | .traffic.month[0]' 定位到当月数据
# 然后分别提取 .rx 和 .tx,使用 // 0 提供默认值以防字段缺失或为 null
CURRENT_MONTH_RX_BYTES=<span class="katex math inline">(echo "</span>JSON_DATA" | <span class="katex math inline">JQ_CMD --arg iface "</span>INTERFACE" '.interfaces[] | select(.name == <span class="katex math inline">iface) | .traffic.month[0].rx // 0')
CURRENT_MONTH_TX_BYTES=</span>(echo "<span class="katex math inline">JSON_DATA" |</span>JQ_CMD --arg iface "<span class="katex math inline">INTERFACE" '.interfaces[] | select(.name ==</span>iface) | .traffic.month[0].tx // 0')

# 验证 jq 是否成功提取了有效的数值 (必须是非负整数)
# 使用正则表达式检查变量是否只包含数字
if ! [[ "<span class="katex math inline">CURRENT_MONTH_RX_BYTES" =~ ^[0-9]+</span> ]]; then
    log_message "错误: 未能从 <span class="katex math inline">INTERFACE 的 vnstat JSON 中解析出有效的 RX 字节数。获取到的值: '</span>CURRENT_MONTH_RX_BYTES'"
    log_message "请手动执行 'sudo vnstat --json m -i <span class="katex math inline">INTERFACE' 检查 JSON 输出内容。"
    # 如果需要调试,取消下面这行的注释以记录完整的 JSON 数据
    # echo "</span>JSON_DATA" >> "<span class="katex math inline">LOG_FILE"
    exit 1
fi
if ! [[ "</span>CURRENT_MONTH_TX_BYTES" =~ ^[0-9]+<span class="katex math inline">]]; then
    log_message "错误: 未能从</span>INTERFACE 的 vnstat JSON 中解析出有效的 TX 字节数。获取到的值: '<span class="katex math inline">CURRENT_MONTH_TX_BYTES'"
    log_message "请手动执行 'sudo vnstat --json m -i</span>INTERFACE' 检查 JSON 输出内容。"
    # 如果需要调试,取消下面这行的注释以记录完整的 JSON 数据
    # echo "<span class="katex math inline">JSON_DATA" >> "</span>LOG_FILE"
    exit 1
fi

# 计算当月总字节数 (RX + TX)
CURRENT_TOTAL_BYTES=<span class="katex math inline">((CURRENT_MONTH_RX_BYTES + CURRENT_MONTH_TX_BYTES))

# 将当前总字节数转换为 GB (保留两位小数) 以便日志阅读
# 使用 awk 进行浮点数计算
CURRENT_TOTAL_GB=</span>(<span class="katex math inline">AWK_CMD "BEGIN {printf \"%.2f\",</span>CURRENT_TOTAL_BYTES / <span class="katex math inline">BYTES_PER_GB}")

log_message "</span>INTERFACE 当前月度总用量: <span class="katex math inline">CURRENT_TOTAL_GB GB (</span>CURRENT_TOTAL_BYTES 字节)。"

# 比较当前用量与限制值 (大于或等于)
if [ "<span class="katex math inline">CURRENT_TOTAL_BYTES" -ge "</span>LIMIT_BYTES" ]; then
    log_message "警告: 接口 <span class="katex math inline">INTERFACE 的月度流量 (</span>CURRENT_TOTAL_GB GB) 已达到或超出限制 (<span class="katex math inline">LIMIT_GB GB)。"
    log_message "====== 正在执行关机操作! ======"
    # 执行立即关机 - 需要 ROOT 权限
    "</span>SHUTDOWN_CMD" -h now "系统关机:接口 <span class="katex math inline">INTERFACE 的月度网络流量 (</span>CURRENT_TOTAL_GB GB) 已达到 $LIMIT_GB GB 的限制。"
    # 关机命令发出后,脚本可能不会执行到 exit 0
    exit 0 # 理论上关机后不会执行到这里
else
    log_message "状态: 流量用量在限制范围内。"
    log_message "====== 检查完成 ======"
    exit 0 # 正常退出
fi

脚本每分钟执行一次产生的 log 文件,可以用日志轮转(logrotate)解决

谷歌官方超预算停机 {#谷歌官方超预算停机.wp-block-heading}

查阅了谷歌云文档发现,官方可以实现超预算停机的效果,这不巧了吗

停用带通知的结算使用情况
监听 Pub/Sub 通知

image 这是官方的流程图很好理解,创建一个预算和提醒,当费用超过预算时,会发送邮件和 Pub/Sub 通知,而Pub/Sub 通知可以触发 Cloud run Function 来停用结算账号避免产生超额费用。
  1. 首先创建一个预算,勾选邮件提醒和关联 Pub/Sub 通知,关联 Pub/Sub 通知会要求你新建一个主题
  2. IAM 中新建服务账号,授予 roles/billing.admin 权限,作为 运行时服务账号
  3. 进入 Pub/Sub 界面找到刚创建的主题,在主题的 更多操作 中触发 Cloud Run 函数,其中代码直接粘贴文档代码,不设置环境变量话,其中 PROJECT_ID 修改为你实际的项目 ID
  4. Pub/Sub 主题中发布消息测试

走了一遍流程发现其中的难点在于权限

  • Pub/Sub 服务 (service-<PROJECT_NUMBER>@gcp-sa-pubsub.iam.gserviceaccount.com) 需要 Cloud Run Invoker 权限来触发/调用你的函数
  • 函数需要调用 Billing API 的权限: 当你的函数代码运行时,去调用 Cloud Billing API 来执行停用结算的操作。

要解决上面两个问题,在创建 Cloud run Function 时,直接用默认的Compute Engine default service account 为 触发器服务账号(默认),运行时服务账号 需要在 IAM 中新建,并授予其 Billing Account Administrator 角色

还有用文档中的 node.js ,用 Python 好多次没有成功。

信用卡限额 {#信用卡限额.wp-block-heading}

最好绑定一张虚拟信用卡,真实的信用卡的话一定要限制额度

本文作者:BOSh
本文链接:http://bosh.zz.ac/posts/2889292810.html
版权声明:本文由BoSh发布,部分内容来源于网络。