mirror of
https://github.com/lkddi/dell-fans-controller-docker.git
synced 2026-04-03 09:55:11 +08:00
init commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.idea
|
||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM ubuntu:22.04
|
||||||
|
LABEL maintainer="joestar817@foxmail.com"
|
||||||
|
|
||||||
|
|
||||||
|
RUN apt update && apt install -y \
|
||||||
|
ipmitool \
|
||||||
|
python3 \
|
||||||
|
python3-pip && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY . /dell-fans-controller-docker
|
||||||
|
WORKDIR /dell-fans-controller-docker
|
||||||
|
|
||||||
|
ENV TZ=Asia/Shanghai
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||||
|
RUN echo 'Asia/Shanghai' >/etc/timezone
|
||||||
|
|
||||||
|
CMD ["python3","start.py"]
|
||||||
|
|
||||||
9
Makefile
Normal file
9
Makefile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.PHONY: help
|
||||||
|
|
||||||
|
help: ## show help message
|
||||||
|
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
|
||||||
|
build: ## build docker image
|
||||||
|
docker build -t dell-fans-controller-docker .
|
||||||
|
|
||||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
### dell-fans-controller-docker
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 项目说明
|
||||||
|
|
||||||
|
本项目通过python脚本调用ipmitool,来自动调整Dell R730服务器的风扇转速,并打包为docker镜像。
|
||||||
|
|
||||||
|
适用于在R730上搭建all in one服务场景,docker镜像可运行在pve、群晖,或其它支持docker的环境中
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
1. 请登录idrac打开ipmi服务
|
||||||
|
|
||||||
|
2. 运行以下命令
|
||||||
|
```
|
||||||
|
docker run -d --name=dell-fans-controller-docker -e HOST=192.168.1.1 -e USERNAME=root -e PASSWORD=password --restart always joestar817/dell-fans-controller-docker:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 代码说明
|
||||||
|
|
||||||
|
脚本首先通过ipmitool来获取 **进出口温度和CPU核心温度**,再通过其中的最大值来判断调整服务器的风扇转速
|
||||||
|
|
||||||
|
运行间隔为每60秒运行一次
|
||||||
|
|
||||||
|
| 温度(℃) | 风扇转速(%) |
|
||||||
|
|-------|--------------------|
|
||||||
|
| 0-50 | 10 |
|
||||||
|
| 50-55 | 20 |
|
||||||
|
| 55-60 | 30 |
|
||||||
|
| 60-65 | 40 |
|
||||||
|
| >65℃ | 设置为自动模式,由idrac自动调速 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 免责声明
|
||||||
|
|
||||||
|
手动调整风扇转速有一定的风险导致服务器过热损坏,请谨慎操作,对此使用此项目引发的任何问题,概不负责
|
||||||
0
controller/__init__.py
Normal file
0
controller/__init__.py
Normal file
33
controller/client.py
Normal file
33
controller/client.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from controller.logger import logger
|
||||||
|
|
||||||
|
from controller.ipmi import IpmiTool
|
||||||
|
|
||||||
|
|
||||||
|
class FanController:
|
||||||
|
|
||||||
|
def __init__(self, host: str, username: str, password: str):
|
||||||
|
self.host = host
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
self.ipmi = IpmiTool(self.host, self.username, self.password)
|
||||||
|
|
||||||
|
def set_fan_speed(self, speed: int):
|
||||||
|
logger.info(f'Set fan speed: {speed}%')
|
||||||
|
self.ipmi.set_fan_speed(speed)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
temperature: int = max(self.ipmi.temperature())
|
||||||
|
logger.info(f'Current maximum temperature: {temperature}')
|
||||||
|
|
||||||
|
if 0 < temperature < 50:
|
||||||
|
self.set_fan_speed(10)
|
||||||
|
elif 50 < temperature < 55:
|
||||||
|
self.set_fan_speed(20)
|
||||||
|
elif 55 < temperature < 60:
|
||||||
|
self.set_fan_speed(30)
|
||||||
|
elif 60 < temperature < 65:
|
||||||
|
self.set_fan_speed(40)
|
||||||
|
else:
|
||||||
|
logger.info(f'Switch fan control to auto mode')
|
||||||
|
self.ipmi.switch_fan_mode(auto=True)
|
||||||
74
controller/ipmi.py
Normal file
74
controller/ipmi.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class IpmiTool:
|
||||||
|
|
||||||
|
def __init__(self, host: str, username: str, password: str):
|
||||||
|
self.host = host
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def run_cmd(self, cmd: str) -> str:
|
||||||
|
basecmd = f'ipmitool -H {self.host} -I lanplus -U {self.username} -P {self.password}'
|
||||||
|
command = f'{basecmd} {cmd}'
|
||||||
|
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
f'execute command {cmd} failed:{result.stderr}'
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.stdout
|
||||||
|
|
||||||
|
def mc_info(self) -> str:
|
||||||
|
"""
|
||||||
|
execute ipmitool command mc info
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return self.run_cmd(cmd='mc info')
|
||||||
|
|
||||||
|
def sensor(self) -> str:
|
||||||
|
"""
|
||||||
|
execute ipmitool command sensor
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return self.run_cmd(cmd='sensor')
|
||||||
|
|
||||||
|
def temperature(self) -> list:
|
||||||
|
"""
|
||||||
|
get current temperature
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
data = self.sensor()
|
||||||
|
temperatures = []
|
||||||
|
|
||||||
|
for line in data.splitlines():
|
||||||
|
if 'Temp' in line:
|
||||||
|
temperatures.append(float(line.split('|')[1].strip()))
|
||||||
|
|
||||||
|
return temperatures
|
||||||
|
|
||||||
|
def switch_fan_mode(self, auto: bool):
|
||||||
|
"""
|
||||||
|
switch the fan mode
|
||||||
|
:param auto:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
manual_cmd = 'raw 0x30 0x30 0x01 0x00'
|
||||||
|
auto_cmd = 'raw 0x30 0x30 0x01 0x01'
|
||||||
|
return self.run_cmd(cmd=auto_cmd) if auto else self.run_cmd(cmd=manual_cmd)
|
||||||
|
|
||||||
|
def set_fan_speed(self, speed: int):
|
||||||
|
"""
|
||||||
|
set fan speed
|
||||||
|
:param speed:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if speed < 10 or speed > 100:
|
||||||
|
raise ValueError(
|
||||||
|
'speed must be between 10 and 100'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.switch_fan_mode(auto=False)
|
||||||
|
base_cmd = 'raw 0x30 0x30 0x02 0xff'
|
||||||
|
return self.run_cmd(cmd=f'{base_cmd} {hex(speed)}')
|
||||||
18
controller/logger.py
Normal file
18
controller/logger.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import logging
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFormatter(logging.Formatter):
|
||||||
|
def format(self, record):
|
||||||
|
desired_timezone = timezone(timedelta(hours=8))
|
||||||
|
current_time = datetime.now(desired_timezone).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
record.customtime = current_time
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
|
stream_handler = logging.StreamHandler()
|
||||||
|
stream_handler.setFormatter(CustomFormatter(' %(customtime)s [%(levelname)s] %(message)s'))
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
dell-fans-controller-docker:
|
||||||
|
image: joestar817/dell-fans-controller-docker:latest
|
||||||
|
container_name: dell-fans-controller-docker
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
HOST: 192.168.1.1
|
||||||
|
USERNAME: root
|
||||||
|
PASSWORD: password
|
||||||
31
start.py
Normal file
31
start.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from controller.client import FanController
|
||||||
|
from controller.logger import logger
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
host = os.getenv('HOST')
|
||||||
|
username = os.getenv('USERNAME')
|
||||||
|
password = os.getenv('PASSWORD')
|
||||||
|
|
||||||
|
if host is None:
|
||||||
|
raise RuntimeError('HOST environment variable not set')
|
||||||
|
|
||||||
|
if username is None:
|
||||||
|
raise RuntimeError('USERNAME environment variable not set')
|
||||||
|
|
||||||
|
if password is None:
|
||||||
|
raise RuntimeError('PASSWORD environment variable not set')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
client = FanController(host=host, username=username, password=password)
|
||||||
|
client.run()
|
||||||
|
time.sleep(60)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(
|
||||||
|
f'run controller failed {err}. {traceback.format_exc()}'
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user