编辑
2024-12-12
我当运维
00

目录

1. 服务端docker-compose
2. 客户端-探针
3. 终端WEB

公司各种环境比较多,VPS 开的也多,之前的Uptime Kuma 能监听服务,非入侵监听不到CPU,内存什么的,所以还是要探针,简单些主流的有 ServerStatus哪吒探针,随便挑了一个 ServerStatus,这里记录下🕳️坑

104741.jpg

1. 服务端docker-compose

百度了一下 docker📦镜像 有两个项目

我都试了,最后用的 cppla这个,这个返回的信息丰富📚一些,其实也用不上

version: "3.8" services: serverstatus: restart: unless-stopped image: cppla/serverstatus ports: - 8014:80 - 8015:35601 volumes: - /home/serverstatus/server/config.json:/ServerStatus/server/config.json - /home/serverstatus/html:/usr/share/nginx/html networks: {}

注意

这里介绍和教程没说清楚🤷‍♂️,这里的印射,在docker启动前,是需要先有这两个目录和文件的,所以让先下源码,我是下载后传到服务器的,我开始以为会自动创建目录和文件📄,所以报错了

图片.png

然后服务端配置 config.json,要监听几个服务,集合里就放几个

{ "servers": [{ "username": "2.16", "password": "_admin", "name": "MES正式环境", "type": "VPS", "host": "192.168.2.16", "location": "cn", "disabled": false, "region": "HK" },{ "username": "2.17", "password": "_admin", "name": "MES备份环境", "type": "VPS", "host": "192.168.2.17", "location": "cn", "disabled": false, "region": "HK" }] }

username password 就是客户端要输入的,剩下的参数都是影响客户端显示的,其实没啥用

这个配置我传到本地的Git服务上了,然后在服务器上拉下来,总用vim不好编辑

2. 客户端-探针

客户端其实就是收集数据抛到🎯指定的服务

客户端比较折磨,按理说有自动的脚本,但是各种问题,有的服务器好使有的不好使,死活连不上服务端

后来还是第三个 BotoX 比较稳定,但是会少上传一些参数,好在我也用不上

  • cppla
wget -N --no-check-certificate https://raw.githubusercontent.com/cppla/ServerStatus/refs/heads/master/status.sh && chmod +x status.sh && bash status.sh c

  • stilleshan
wget -N --no-check-certificate https://raw.githubusercontent.com/stilleshan/ServerStatus/master/status.sh && chmod +x status.sh && bash status.sh c

  • BotoX
wget -N --no-check-certificate https://raw.github.com/BotoX/ServerStatus/master/other/client-setup.sh && chmod +x client-setup.sh && bash client-setup.sh c
Which client implementation do you want to use? [python, python-psutil, bash] > python 或 bash 随缘 比一定哪个可以 What is your status servers address (DNS or IP)? > IP What is your status servers port? [35601,...] > 8015 看docker Specify the username. > 就是 username Specify a password for the user. > 就是 password Is this correct? [yes/no] > yes 后面还有几个yes不知道啥意思 which user should the script be run? [http, ...] > 最后会出现 哪个用户执行脚本,输入 root 用户不存在会报错

注意

会卡在 raw.githubusercontent.com 没有响应,有时候行,有时候不行

正在连接 raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... 已连接。 无法建立 SSL 连接。

方法是 vim /etc/hosts,然后把IP写进去,保存就行了

185.199.108.133 raw.githubusercontent.com 185.199.109.133 raw.githubusercontent.com 185.199.110.133 raw.githubusercontent.com 185.199.111.133 raw.githubusercontent.com

注意

有一台服务器BotoX会报没有nc,安装下就行了

yum install nc

3. 终端WEB

终端页面💻还是有区别的,

cppla 68747470733a2f2f646c2e6370702e6c612f417263686976652f7365727665727374617475735f6c617975692e706e67.png

stilleshan screenshot.jpg

这个终端就是读取 /home/serverstatus/html/json/stats.json (docker的印射路径),拼html显示,就是个静态的页面,docker里放了个nginx露出👀就完事了,然后我也不用那么多信息,类型,地址,流量,公司内部也不用监控,所以就手撸一个(让GPT),

index.html

<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="css/index.css"> <title>服务器监控</title> </head> <body> <div class="container"> <header> <h1>服务器监控</h1> <span id="update-time"></span> </header> <div id="cards-container" class="cards-container"></div> </div> <script src="js/index.js"></script> </body> </html>

index.js

function bytesToSize(bytes, precision, si = false) { const units = si ? ['B', 'KB', 'MB', 'GB', 'TB'] : ['B', 'KiB', 'MiB', 'GiB', 'TiB']; if (bytes === 0) return '0 B'; const k = si ? 1000 : 1024; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(precision)) + ' ' + units[i]; } function updateCards(data) { const container = document.getElementById('cards-container'); data.servers.forEach(server => { const cardId = server.name.replace(/\s+/g, '-'); let card = document.getElementById(cardId); // 如果卡片不存在,则创建新的卡片 if (!card) { card = document.createElement('div'); card.id = cardId; card.className = 'card'; container.appendChild(card); // 使用表格布局 card.innerHTML = ` <div class="card-header"> <img src="image/os/BareMetalServer.svg" alt="${server.location}" width="40"/> <h2 class="card-title ${server.online4 ? 'online' : 'offline'}">${server.name}</h2> <span class="online-indicator ${server.online4 ? 'online' : 'offline'}"></span> </div> <table> <tbody> <tr> <td>Host:</td> <td>${server.host}</td> </tr> <tr> <td>CPU:</td> <td> <div class="progress-bar"> <div class="progress" style="width: ${server.cpu}%;"><span class="progress-text">${server.cpu}%</span></div> </div> </td> </tr> <tr> <td>内存:</td> <td> <div class="progress-bar"> <div class="progress" style="width: ${(server.memory_used / server.memory_total) * 100}%;"> <span class="progress-text">${bytesToSize(server.memory_used * 1024, 1)} / ${bytesToSize(server.memory_total * 1024, 1)}</span> </div> </div> </td> </tr> <tr> <td>硬盘:</td> <td> <div class="progress-bar"> <div class="progress" style="width: ${(server.hdd_used / server.hdd_total) * 100}%;"> <span class="progress-text">${bytesToSize(server.hdd_used * 1024 * 1024, 2)} / ${bytesToSize(server.hdd_total * 1024 * 1024, 2)}</span> </div> </div> </td> </tr> </tbody> </table> `; } else { // 更新已存在卡片的动态数据 const onlineIndicator = card.querySelector('.online-indicator'); onlineIndicator.className = `online-indicator ${server.online4 ? 'online' : 'offline'}`; const cardTitle = card.querySelector('.card-title'); cardTitle.className = `card-title ${server.online4 ? 'online' : 'offline'}`; const cpuProgress = card.querySelector('.progress-bar .progress'); cpuProgress.style.width = `${server.cpu}%`; setProgressColor(cpuProgress, server.cpu); cpuProgress.querySelector('.progress-text').innerText = `${server.cpu}%`; const memoryProgress = card.querySelectorAll('.progress-bar')[1].querySelector('.progress'); const memoryUsedPercent = (server.memory_used / server.memory_total) * 100; memoryProgress.style.width = `${memoryUsedPercent}%`; setProgressColor(memoryProgress, memoryUsedPercent); memoryProgress.querySelector('.progress-text').innerText = `${bytesToSize(server.memory_used * 1024, 1)} / ${bytesToSize(server.memory_total * 1024, 1)}`; const hddProgress = card.querySelectorAll('.progress-bar')[2].querySelector('.progress'); const hddUsedPercent = (server.hdd_used / server.hdd_total) * 100; hddProgress.style.width = `${hddUsedPercent}%`; setProgressColor(hddProgress, hddUsedPercent); hddProgress.querySelector('.progress-text').innerText = `${bytesToSize(server.hdd_used * 1024 * 1024, 2)} / ${bytesToSize(server.hdd_total * 1024 * 1024, 2)}`; } }); document.getElementById('update-time').innerText = `最后更新时间: ${new Date(data.updated * 1000).toLocaleString()}`; } function setProgressColor(progressElement, percent) { if (percent > 90) { progressElement.style.backgroundColor = 'darkslateblue'; } else if (percent > 70) { progressElement.style.backgroundColor = 'mediumblue'; } else if (percent > 50) { progressElement.style.backgroundColor = 'royalblue'; } else { progressElement.style.backgroundColor = 'cornflowerblue'; // 默认颜色 } } function fetchData() { fetch('json/stats.json') .then(response => response.json()) .then(data => updateCards(data)) .catch(error => console.error('Error fetching data:', error)); } // 初始加载 fetchData(); // 每2秒更新 setInterval(fetchData, 2000);

css

body { background-color: #212e36; color: #ffffff; font-family: Arial, sans-serif; margin: 0; padding: 0; } .container { padding: 20px 10%; } header { /* text-align: center; */ margin-bottom: 20px; } .cards-container { display: grid; --rowcount: 4; /* 默认一行4个卡片 */ grid-template-columns: repeat(var(--rowcount), 1fr); gap: 30px; } @media screen and (max-width: 1279px) { .cards-container { --rowcount: 2; } } @media screen and (max-width: 559px) { .cards-container { --rowcount: 1; } } .card { background-color: #2c3e50; border-radius: 8px; padding: 15px; transition: transform 0.2s; } .card-header { display: flex; align-items: center; /* 垂直居中对齐 */ margin-bottom: 15px; /* 增加底部间距 */ } .card-header img { margin-right: 10px; /* 图标与标题之间的间距 */ } .online-indicator { width: 10px; height: 10px; background-color: #28a745; /* 绿色 */ border-radius: 50%; /* 圆形 */ margin-left: 10px; /* 与标题之间的间距 */ animation: pulse 1.5s infinite; /* 呼吸效果 */ } .online-indicator.offline{ background-color: #aaa; /* 灰色 */ animation: pulse 3s ease-in-out infinite; /* 呼吸效果 */ } .offline{ color: #aaa; /* 灰色 */ } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.5); } 100% { transform: scale(1); } } table { width: 100%; border-collapse: collapse; /* 合并边框 */ margin-top: 10px; /* 增加顶部间距 */ } td { padding: 8px; vertical-align: middle; /* 垂直居中 */ border-bottom: 1px solid #34495e; /* 添加底部边框 */ } .progress-bar { background-color: #4caf50; border-radius: 5px; height: 20px; position: relative; overflow: hidden; } .progress { background-color: cornflowerblue; height: 100%; width: 0; transition: width 0.5s ease-in-out, background-color 0.5s ease-in-out; } .progress-text { position: absolute; color: white; font-weight: bold; left: 50%; transform: translateX(-50%); white-space: nowrap; /* 防止文本换行 */ line-height:22px; } /* 额外样式以增强卡片视觉效果 */ .card:hover { /* transform: scale(1.02); */ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } #update-time { font-size: 0.9em; color: #bdc3c7; /* 更新信息的颜色 */ } td:first-child { width: 20%; /* 设置第一列较小的宽度 */ } td:last-child { width: 80%; /* 设置第二列为默认宽度 */ }

图片.png


如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:没想好

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!