Lighthouse搭建UptimeKuma监控网站连通与证书状态并接入腾讯SMS通知

前言

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

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

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

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

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

计划

通过Docker部署Uptime。

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

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

不使用宝塔等面板程序。

步骤

安装Docker

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

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

安装Uptime Kuma

首先,启动docker

代码语言:javascript
复制
service docker start

创建放数据的文件夹

代码语言:javascript
复制
mkdir -p /root/docker/uptime/data

执行命令拉去镜像并部署

代码语言:javascript
复制
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/root/docker/uptime/data --name uptime-kuma louislam/uptime-kuma:1

UFW放通3001端口

代码语言:javascript
复制
ufw allow 3001

轻量防火墙放通3001端口

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

就能看到uptime的管理页面了

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

新建签名

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

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

审核通过后记下签名ID

新建模板

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

这里提供一个示例

代码语言:javascript
复制
模板名称:
服务器故障通知

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

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

服务器故障通知
监控名称: 主站
故障状态: 0
故障时间: 2023-11-23 17:23:19
故障信息: 访问超时

注意,\n在短信内容中会被当成文本输出

获取应用ID

编写webhook接口对接短信推送

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

代码语言:javascript
复制
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,所以要先安装下

代码语言:javascript
复制
apt install python3-pip

然后安装依赖库

代码语言:javascript
复制
python3 -m pip install requests fastapi uvicorn
部署

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

代码语言:javascript
复制
nano sms.py

试运行

代码语言:javascript
复制
python3 sms.py

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

后台运行sms.py

代码语言:javascript
复制
nohup python3 sms.py &

安装并配置Nginx反代

安装Nginx
代码语言:javascript
复制
apt install nginx
service nginx start

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

配置反向代理

进入Nginx配置文件存放目录

代码语言:javascript
复制
cd /etc/nginx/conf.d

配置uptime的反向代理

新建配置文件

代码语言:javascript
复制
nano uptime.conf

输入如下配置

代码语言:javascript
复制
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
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

配置sms webhook的反向代理

代码语言:javascript
复制
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
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
重启Nginx使配置生效
代码语言:javascript
复制
service nginx restart
测试设置结果

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

uptime

webhook

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

uptime的使用与接入短信通知

新建监控项

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

这里拿腾讯云举例

高级处可以找到证书通知

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

设置通知

找到webhook

填入刚才部署的webhook的url

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

完成设置

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

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

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

使用体验

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

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

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

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

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

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

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