前言

随着Google推进90天证书步伐的加快,已有几家机构发布了调整通知,缩短有效期已经成为了板上钉钉的事。

主流的解决办法是使用证书自动化部署,但受限于部分设备不方便安装ACME以及可能出现的更新失败,还是得搭一个证书监控应用。

也借由这个契机,把一直想做但没做的网站状态监控一并解决了,摆脱掉"网站挂了全靠用户通知"的局面。

几番寻找,发现Uptime Kuma正好符合规定,不止支持监控证书和网站,还支持DNS、Ping、Radis等应用的监控,而且通知方式也多。

美中不足的是不支持接入腾讯的SMS,所以本次部署还会要自己写一个接口来做适配。

计划

通过Docker部署Uptime。

使用Python实现一个WebHook接口,用于发送通知短信。

安装Nginx反代Uptime和WebHook接口,并绑定域名,更加美观。

不使用宝塔等面板程序。

步骤

安装Docker

这里选择使用Lighthouse自带的Docker镜像。

既省去安装的时间,后期也可以在控制台直接看服内得多Docker容器,十分方便。

20231027_155423_92ed5070bab1423599e94669ec60e3cb.png

安装Uptime Kuma

首先,启动docker

service docker start

创建放数据的文件夹

mkdir -p /root/docker/uptime/data

执行命令拉去镜像并部署

docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/root/docker/uptime/data --name uptime-kuma louislam/uptime-kuma:1

UFW放通3001端口

ufw allow 3001

轻量防火墙放通3001端口

20231027_160647_83eca4c097034541b67e86f0cc7b4d39.png

这时候,访问:http://[IP]:3001

就能看到uptime的管理页面了

开通并新建短信签名与模板

新建签名

进入短信控制台,点击国内短信->签名管理新建一个签名

20231122_230704_24698260c871465a87440d4af5b023e1.png

选择网站公众号最容易过,需要注意的是网站需要是已备案的网站

20231122_230922_676f9a5ee65548f59f06d4ed81153f41.png

审核通过后记下签名ID

20231122_231152_31ab8306dd3a44abbc9eedb07c43f2dd.png

新建模板

点击正文模板管理创建一个新的模板,同样的,审核通过后记下模板ID

20231122_231316_0a8bb36e0c5540dd9cb18ea123d25183.png

这里提供一个示例
模板名称:
服务器故障通知

短信内容:
服务器故障通知
监控名称: {1}
故障状态: {2}
故障时间: {3}
故障信息: {4}

申请说明:
用于监控程序发送服务器故障提醒,在服务故障时提醒管理员进行排查。
如下为示例:

服务器故障通知
监控名称: 主站
故障状态: 0
故障时间: 2023-11-23 17:23:19
故障信息: 访问超时
注意,\n在短信内容中会被当成文本输出

20231123_003808_fd33d7db65634782ae9e7389a10c382e.png

获取应用ID

20231123_004659_f6faee0a042446f0831b2e050d7195b1.png

编写webhook接口对接短信推送

用python写一个接口,用来给uptime做webhook告警推送

import hashlib, hmac, json, time, requests, uvicorn, re
from datetime import datetime
from fastapi import FastAPI,Request


# 短信应用ID
SmsSdkAppId = ""
# 签名文本
SignName = ""
# 模板ID
TemplateId = ""
# 手机号
PhoneNumber = ""

# API密钥
SecretId=""
SecretKey=""

app = FastAPI()

def qcloud_v3_post(SecretId,SecretKey,service,bodyDict,headersDict):
    secret_id = SecretId
    secret_key = SecretKey

    service = service
    algorithm = "TC3-HMAC-SHA256"
    timestamp = int(time.time())
    
    date = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d")
    
    http_request_method = "POST"
    canonical_uri = "/"
    canonical_querystring = ""
    
    headersDict = {k: v for k, v in sorted(headersDict.items(), key=lambda x: x[0])}

    signed_headers = ""
    canonical_headers = ""
    
    for dict_key in headersDict:
        signed_headers = signed_headers + dict_key.lower() + ";"
    signed_headers = signed_headers[:-1]
    
    for dict_value in headersDict:
        canonical_headers = canonical_headers + dict_value.lower() + ":" + headersDict[dict_value].lower() + "\n"
    
    payload = json.dumps(bodyDict)
    hashed_request_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest()
    canonical_request = (http_request_method + "\n" +
                        canonical_uri + "\n" +
                        canonical_querystring + "\n" +
                        canonical_headers + "\n" +
                        signed_headers + "\n" +
                        hashed_request_payload)
    
    credential_scope = date + "/" + service + "/" + "tc3_request"
    hashed_canonical_request = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
    string_to_sign = (algorithm + "\n" +
                    str(timestamp) + "\n" +
                    credential_scope + "\n" +
                    hashed_canonical_request)
    
    def sign(key, msg):
        return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
    secret_date = sign(("TC3" + secret_key).encode("utf-8"), date)
    secret_service = sign(secret_date, service)
    secret_signing = sign(secret_service, "tc3_request")
    signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
    
    authorization = (algorithm + " " +
                    "Credential=" + secret_id + "/" + credential_scope + ", " +
                    "SignedHeaders=" + signed_headers + ", " +
                    "Signature=" + signature)
    headersDict["X-TC-Timestamp"] = str(timestamp)
    headersDict["Authorization"] = authorization

    return headersDict

@app.post("/sendsms")
async def sendsms(request: Request):
    content_type = request.headers['Content-Type']

    if content_type != "application/json":
        return {"code":-1,"msg":"类型不正确"}

    body = await request.body()

    try:
        body_json = json.loads(body)
    except Exception as e:
        return {"code":-1,"msg":str(e)}
    
    name = body_json["monitor"]["name"]
    status = body_json["heartbeat"]["status"]
    time = body_json["heartbeat"]["time"]
    msg = body_json["msg"]

    Service = "sms"
    host = "sms.tencentcloudapi.com"
    protocol = "https://"
    apiurl = protocol + host
    PhoneNumberSet = [PhoneNumber]

    name = re.sub(r'(\d+).(\d+).\d+.\d+', '*.*.*.*', name)
    msg = re.sub(r'(\d+).(\d+).\d+.\d+', '*.*.*.*', msg)
    name = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\[\]\s]', '', name)
    msg = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\[\]\s]', '', msg)

    TemplateParamSet = [name,str(status),time,msg]
    SessionContext = "uptime"

    payload = {
            "PhoneNumberSet": PhoneNumberSet,
            "SmsSdkAppId" : SmsSdkAppId,
            "SignName" : SignName,
            "TemplateId" : TemplateId,
            "TemplateParamSet": TemplateParamSet,
            "SessionContext" : SessionContext
    }

    headersPending = {
            'Host': host,
            'Content-Type': 'application/json',
            'X-TC-Action': 'SendSms',
            'X-TC-Version': '2021-01-11',
            'X-TC-Region': 'ap-guangzhou',
    }

    headersSend = qcloud_v3_post(SecretId,SecretKey,Service,payload,headersPending)
    r = requests.post(apiurl,json=payload,headers=headersSend)

    return {"code":200,"msg":"OK"}

if __name__ == '__main__':
    uvicorn.run(app=app, host="0.0.0.0", port=8080)

部署WebHook接口

安装依赖库

轻量Docker镜像自带了Python 3.8.2,但没有安装pip,所以要先安装下
apt install python3-pip
然后安装依赖库
python3 -m pip install requests fastapi uvicorn

部署

使用nano直接新建一个sms.py文件,把上面的程序粘贴上去
nano sms.py
试运行
python3 sms.py

看到如下结果表示依赖没有问题:

20231123_010719_d098d7e429ac45949dc8cc28d8142b01.png

后台运行sms.py
nohup python3 sms.py &

安装并配置Nginx反代

安装Nginx

apt install nginx
service nginx start

安装成功后,在浏览器输入IP可以看到如下网页

20231123_011327_aa6f42ca5fb24f158e3f35eac02d9b6d.png

配置反向代理

进入Nginx配置文件存放目录

cd /etc/nginx/conf.d
配置uptime的反向代理

新建配置文件

nano uptime.conf

输入如下配置

server
{
    listen 80;
    server_name [需要绑定到uptime的域名];
    location / {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
    }
}
配置sms webhook的反向代理
server
{
    listen 80;
    server_name [需要绑定到webhook的域名];
    location / {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
    }
}

重启Nginx使配置生效

service nginx restart

测试设置结果

分别访问两个域名,可以看到已经设置成功了

uptime

20231123_012038_8930d1999c134cb9be7a12af33948f8c.png

webhook

因为接口使用的是POST,使用浏览器测试是GET请求,所以显示如下内容是正常的,不影响实际使用

20231123_012413_b52a29cb25994b67a2db69d422abf3ee.png

uptime的使用与接入短信通知

新建监控项

登录以后,直接点击左上角新建监控项

这里拿腾讯云举例

20231123_012746_1e5246306ac84298b035db1f23ebedf8.png

高级处可以找到证书通知

20231123_053503_974e8304bdc542f58b87e17d737c868e.png

除了网站,uptime还能监控许多不同类型的服务

20231123_012941_2ebf75f639aa4ae0a6d7a51a8ac6a9b3.png

设置通知

找到webhook

20231123_013058_c932f940ecc04b04803d1413e1a18795.png

填入刚才部署的webhook的url

20231123_013231_83cba68dd70444ceb2ca1331bb7d5ea7.png

注意不用点右下角的测试,右下角的测试发送的内容不完整,是无法收到信息的

完成设置

保存通知设置和监控项设置

设置完成后即可看到当前网站的监控状态。

点击证书有效期还能看到网站当前的证书信息

1700694611915.jpg

使用体验

相比于CVM,Lighthouse不管是价格还是操作体验上都要舒服得多。

换成CVM,同样的2C2G-3M不限流量要103
20231208_051954_dee84681238b491c850ea40a70ec872f.png

换成按量计费0.8元/G,200G要160,还没算配置得38
20231208_052054_c24d9b2f7ccb4d9ab0e654bd52c16ab1.png

即使使用共享流量包,按37元/50G算,200G也得148元。

而且操作还麻烦,购买页面和CVM是分开的,你得到VPC里才能找到购买和管理入口
20231208_052358_e219a5d65267459c886707e053c111b4.png

而换成Lighthouse,流量使用状况一目了然,套餐不够用点一下升级重启就完事了
20231208_053329_124a53750fc84e20a1563aab3fa63de9.png

几台Lighthouse之间也不用考虑什么VPC直接默认内网互通,对于普通用户来说十分方便。

最后修改:2024 年 01 月 31 日
如果觉得我的文章对你有用,请随意赞赏