proxmox 안드로이드vm을 필요할 때만 부팅하여 전기를 절약해보자
iTIPs
2024. 11. 19. 18:07
반응형
https://www.ppomppu.co.kr/zboard/view.php?id=nas&no=44870&ismobile
위 링크는 안드로이드 x86 가상머신을 proxmox 미니PC에 올려 구글포토 무제한 사용하는 방법을 설명한글입니다. 이 글을 잘 따라해서 무제한구글포토를 즐기고 있었습니다.
그런데 구글포토를 돌릴 android-x86 VM을 상시구동하고있으면 CPU, RAM사용량이 많아 전력소모량도 많아집니다. 실제 구동중인 N100 미니PC의 전력량을 스마트플러그를 이용해 측정 시 안드로이드vm이 켜졌을 때 대략 25W, 꺼졌을때 18W로 꽤나 전력소모가 많아지는것을 확인할 수 있습니다.
그래서 전력 낭비를 줄이기 위해 안드로이드는 평상시에는 꺼진상태를 유지하기로 했습니다. 만약 nas에 사진이 업로드되면 그 때 안드로이드를 켜고 CPU사용률이 idle상태가 되면 (사진을 모두 업로드 했으면) 다시 안드로이드 전원을 끄는 자동화를 구상해봤습니다.
기억에 의존해 쓰는 글이므로 그대로 따라해도 오류가 있을 수 있습니다..
사전준비사항
- nas에 사진업로드 폴더가 안드로이드 VM에 마운트 되어있으며 전원을 켜면 업로드가 시작됨 (맨위의 링크까지 완료된 상태)
- Proxmox에서 api를 사용할 user생성, api토큰받아두고 권한설정하기 (VM pwmgmt랑 audit? 이것만 있으면 됨. api와 user(또는 그룹)에 둘다 권한 줘야함.)
- 안드로이드 x86에 macrodroid설치후 웹훅(/shutdown)을 트리거로 종료하도록 매크로 짜두기.
대략적인 순서
- lxc를 생성합니다. (데비안으로 cpu1, ram 512Mb로 생성함.)
- lxc에 필요한것들을 설치합니다. 파이썬.. 등
- lxc에 사진이 업로드되는 폴더를 마운트합니다. (/etc/fstab에 설정.. 예: 192.168.***.***:/volume3/gshare /mnt/gshare nfs defaults 0 0 )
- gshare_manager.py를 만들어 아래 코드를 넣기
import logging
import time
from dataclasses import dataclass
from typing import Optional
import requests
import subprocess
from datetime import datetime
import pytz
@dataclass
class Config:
# Proxmox android 사용량 감시
## Proxmox API 호스트
PROXMOX_HOST: str = "https://192.168.***.***:8006/api2/json"
## Proxmox 노드 이름
NODE_NAME: str = "******"
## android VM ID
VM_ID: str = "***"
## API 토큰 ID
TOKEN_ID: str = "*****@pve!*****"
## API 토큰 시크릿
SECRET: str = "*******-****-****-****-************"
## CPU 사용량 임계치(%)
CPU_THRESHOLD: float = 3.0
## 체크 간격(초)
CHECK_INTERVAL: int = 120
## 체크 횟수
THRESHOLD_COUNT: int = 2
# 폴더용량 감시
## 감시폴더 마운트 경로
MOUNT_PATH: str = "/mnt/gshare"
## 체크 간격은 CHECK_INTERVAL을 공통으로 사용
## macrodroid 종료 웹훅 URL
SHUTDOWN_WEBHOOK_URL: str = "http://192.168.***.***:8080/shutdown"
# 로그 시간대
TIMEZONE: str = "Asia/Seoul"
class ProxmoxAPI:
def __init__(self, config: Config):
self.config = config
self.session = requests.Session()
self.session.verify = False
self._set_token_auth()
def _set_token_auth(self) -> None:
# API 토큰을 사용하여 인증 헤더 설정
self.session.headers.update({
"Authorization": f"PVEAPIToken={self.config.TOKEN_ID}={self.config.SECRET}"
})
logging.info("Proxmox API 토큰 인증 설정 완료")
def is_vm_running(self) -> bool:
try:
response = self.session.get(
f"{self.config.PROXMOX_HOST}/nodes/{self.config.NODE_NAME}/qemu/{self.config.VM_ID}/status/current"
)
response.raise_for_status()
return response.json()["data"]["status"] == "running"
except Exception as e:
logging.error(f"VM 상태 확인 실패: {e}")
return False
def get_vm_uptime(self) -> Optional[float]:
try:
response = self.session.get(
f"{self.config.PROXMOX_HOST}/nodes/{self.config.NODE_NAME}/qemu/{self.config.VM_ID}/status/current"
)
response.raise_for_status()
return response.json()["data"]["uptime"]
except Exception as e:
logging.error(f"VM 부팅 시간 확인 실패: {e}")
return None
def get_cpu_usage(self) -> Optional[float]:
try:
response = self.session.get(
f"{self.config.PROXMOX_HOST}/nodes/{self.config.NODE_NAME}/qemu/{self.config.VM_ID}/status/current"
)
response.raise_for_status()
return response.json()["data"]["cpu"] * 100
except Exception as e:
logging.error(f"CPU 사용량 확인 실패: {e}")
return None
def start_vm(self) -> bool:
try:
response = self.session.post(
f"{self.config.PROXMOX_HOST}/nodes/{self.config.NODE_NAME}/qemu/{self.config.VM_ID}/status/start"
)
response.raise_for_status()
logging.info("VM 시작 성공")
return True
except Exception as e:
logging.error(f"VM 시작 실패: {e}")
return False
class FolderMonitor:
def __init__(self, config: Config):
self.config = config
self.previous_size = self._get_folder_size()
def _get_folder_size(self) -> int:
try:
result = subprocess.run(
['du', '-sb', self.config.MOUNT_PATH],
capture_output=True,
text=True,
check=True
)
return int(result.stdout.split()[0])
except Exception as e:
logging.error(f"폴더 크기 확인 실패: {e}")
return 0
def has_size_changed(self) -> bool:
current_size = self._get_folder_size()
if current_size != self.previous_size:
logging.info(f"폴더 크기 변경: {self.previous_size} -> {current_size}")
self.previous_size = current_size
return True
return False
class GShareManager:
def __init__(self, config: Config, proxmox_api: ProxmoxAPI):
self.config = config
self.proxmox_api = proxmox_api
self.low_cpu_count = 0
self.folder_monitor = FolderMonitor(config)
def _format_uptime(self, seconds: float) -> str:
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
if hours > 0:
return f"{hours}시간 {minutes}분 {secs}초"
elif minutes > 0:
return f"{minutes}분 {secs}초"
else:
return f"{secs}초"
def _send_shutdown_webhook(self) -> None:
try:
response = requests.post(self.config.SHUTDOWN_WEBHOOK_URL)
response.raise_for_status()
uptime = self.proxmox_api.get_vm_uptime()
uptime_str = self._format_uptime(uptime) if uptime is not None else "알 수 없음"
logging.info(f"종료 웹훅 전송 성공, 업타임: {uptime_str}")
except Exception as e:
logging.error(f"종료 웹훅 전송 실패: {e}")
def monitor(self) -> None:
while True:
if self.folder_monitor.has_size_changed() and not self.proxmox_api.is_vm_running():
self.proxmox_api.start_vm()
if self.proxmox_api.is_vm_running():
cpu_usage = self.proxmox_api.get_cpu_usage()
if cpu_usage is not None:
if cpu_usage < self.config.CPU_THRESHOLD:
self.low_cpu_count += 1
if self.low_cpu_count >= self.config.THRESHOLD_COUNT:
self._send_shutdown_webhook()
self.low_cpu_count = 0
else:
self.low_cpu_count = 0
time.sleep(self.config.CHECK_INTERVAL)
if __name__ == '__main__':
config = Config()
logging.Formatter.converter = lambda *args: datetime.now(tz=pytz.timezone(config.TIMEZONE)).timetuple()
logging.basicConfig(
filename='./gshare_manager.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
try:
proxmox_api = ProxmoxAPI(config)
gshare_manager = GShareManager(config, proxmox_api)
logging.info("GShare 관리 시작")
gshare_manager.monitor()
except KeyboardInterrupt:
logging.info("프로그램 종료")
except Exception as e:
logging.error(f"예상치 못한 오류 발생: {e}")
- config부분을 채워넣고 lxc 부팅시마다 자동실행되도록 서비스를 등록합니다.
nano /etc/systemd/system/gshare_manager.service
[Unit]
Description=GShare Manager Service
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/*****/gshare_manager.py
WorkingDirectory=/home/*****/
StandardOutput=inherit
StandardError=inherit
Restart=always
User=root
[Install]
WantedBy=multi-user.target
- 저는 그냥 user를 root로 했습니다. 이것만 하는 lxc이기 때문에..
# 서비스 파일 권한 설정
sudo chmod 644 /etc/systemd/system/gshare_manager.service
# systemd 데몬 리로드
sudo systemctl daemon-reload
# 부팅 시 자동 시작 설정
sudo systemctl enable gshare_manager
# 서비스 시작
sudo systemctl start gshare_manager
# 서비스 상태 확인
sudo systemctl status gshare_manager
반응형