[英]How to make a Python script run like a service or daemon in Linux
我寫了一個 Python 腳本來檢查某個電子郵件地址並將新電子郵件傳遞給外部程序。 我怎樣才能讓這個腳本全天候執行 24/7,例如將它變成 Linux 中的守護進程或服務。我是否還需要一個在程序中永不結束的循環,或者是否可以通過多次重新執行代碼來完成?
您在這里有兩個選擇。
制作一個適當的cron 作業來調用您的腳本。 Cron 是 GNU/Linux 守護進程的通用名稱,它根據您設置的時間表定期啟動腳本。 您將腳本添加到 crontab 或將它的符號鏈接放入一個特殊目錄,守護進程處理在后台啟動它的工作。 您可以在 Wikipedia 上閱讀更多內容。 有多種不同的 cron 守護進程,但您的 GNU/Linux 系統應該已經安裝了它。
使用某種python 方法(例如一個庫)讓你的腳本能夠自我守護。 是的,它需要一個簡單的事件循環(其中您的事件是定時器觸發,可能由睡眠功能提供)。
我不建議您選擇 2.,因為實際上您會重復 cron 功能。 Linux 系統范式是讓多個簡單的工具交互並解決您的問題。 除非有其他原因需要創建守護程序(除了定期觸發),否則請選擇其他方法。
此外,如果您在循環中使用 daemonize 並且發生崩潰,則此后沒有人會檢查郵件(正如Ivan Nevostruev在對此答案的評論中所指出的那樣)。 而如果腳本作為 cron 作業添加,它只會再次觸發。
這是從這里獲取的一個不錯的類:
#!/usr/bin/env python
import sys, os, time, atexit
from signal import SIGTERM
class Daemon:
"""
A generic daemon class.
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
def daemonize(self):
"""
do the UNIX double-fork magic, see Stevens' "Advanced
Programming in the UNIX Environment" for details (ISBN 0201563177)
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
"""
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# decouple from parent environment
os.chdir("/")
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
se = file(self.stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
file(self.pidfile,'w+').write("%s\n" % pid)
def delpid(self):
os.remove(self.pidfile)
def start(self):
"""
Start the daemon
"""
# Check for a pidfile to see if the daemon already runs
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
if pid:
message = "pidfile %s already exist. Daemon already running?\n"
sys.stderr.write(message % self.pidfile)
sys.exit(1)
# Start the daemon
self.daemonize()
self.run()
def stop(self):
"""
Stop the daemon
"""
# Get the pid from the pidfile
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
if not pid:
message = "pidfile %s does not exist. Daemon not running?\n"
sys.stderr.write(message % self.pidfile)
return # not an error in a restart
# Try killing the daemon process
try:
while 1:
os.kill(pid, SIGTERM)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print str(err)
sys.exit(1)
def restart(self):
"""
Restart the daemon
"""
self.stop()
self.start()
def run(self):
"""
You should override this method when you subclass Daemon. It will be called after the process has been
daemonized by start() or restart().
"""
您應該使用python-daemon庫,它會處理一切。
來自 PyPI:用於實現行為良好的 Unix 守護進程的庫。
您可以使用 fork() 將腳本與 tty 分離並讓它繼續運行,如下所示:
import os, sys
fpid = os.fork()
if fpid!=0:
# Running as daemon now. PID is fpid
sys.exit(0)
當然你還需要實現一個無限循環,比如
while 1:
do_your_check()
sleep(5)
希望這讓你開始。
假設您真的希望您的循環作為后台服務 24/7 全天候運行
對於不涉及使用庫注入代碼的解決方案,您可以簡單地創建一個服務模板,因為您使用的是 linux:
[Unit]
Description = <Your service description here>
After = network.target # Assuming you want to start after network interfaces are made available
[Service]
Type = simple
ExecStart = python <Path of the script you want to run>
User = # User to run the script as
Group = # Group to run the script as
Restart = on-failure # Restart when there are errors
SyslogIdentifier = <Name of logs for the service>
RestartSec = 5
TimeoutStartSec = infinity
[Install]
WantedBy = multi-user.target # Make it accessible to other users
將該文件放在您的守護程序服務文件夾(通常是/etc/systemd/system/
)中的*.service
文件中,並使用以下 systemctl 命令進行安裝(可能需要 sudo 權限):
systemctl enable <service file name without .service extension>
systemctl daemon-reload
systemctl start <service file name without .service extension>
然后,您可以使用以下命令檢查您的服務是否正在運行:
systemctl | grep running
您還可以使用 shell 腳本使 python 腳本作為服務運行。 首先創建一個shell腳本來運行這樣的python腳本(scriptname任意名稱)
#!/bin/sh
script='/home/.. full path to script'
/usr/bin/python $script &
現在在 /etc/init.d/scriptname 中創建一個文件
#! /bin/sh
PATH=/bin:/usr/bin:/sbin:/usr/sbin
DAEMON=/home/.. path to shell script scriptname created to run python script
PIDFILE=/var/run/scriptname.pid
test -x $DAEMON || exit 0
. /lib/lsb/init-functions
case "$1" in
start)
log_daemon_msg "Starting feedparser"
start_daemon -p $PIDFILE $DAEMON
log_end_msg $?
;;
stop)
log_daemon_msg "Stopping feedparser"
killproc -p $PIDFILE $DAEMON
PID=`ps x |grep feed | head -1 | awk '{print $1}'`
kill -9 $PID
log_end_msg $?
;;
force-reload|restart)
$0 stop
$0 start
;;
status)
status_of_proc -p $PIDFILE $DAEMON atd && exit 0 || exit $?
;;
*)
echo "Usage: /etc/init.d/atd {start|stop|restart|force-reload|status}"
exit 1
;;
esac
exit 0
現在您可以使用命令 /etc/init.d/scriptname start 或 stop 啟動和停止您的 python 腳本。
一個簡單且受支持的版本是Daemonize
。
從 Python Package Index (PyPI) 安裝它:
$ pip install daemonize
然后使用像:
...
import os, sys
from daemonize import Daemonize
...
def main()
# your code here
if __name__ == '__main__':
myname=os.path.basename(sys.argv[0])
pidfile='/tmp/%s' % myname # any name
daemon = Daemonize(app=myname,pid=pidfile, action=main)
daemon.start()
cron
顯然是許多用途的絕佳選擇。 但是,它不會按照您在 OP 中的要求創建服務或守護程序。 cron
只是定期運行作業(意味着作業開始和停止),並且不超過一次/分鍾。 cron
存在問題——例如,如果您的腳本的先前實例在下一次cron
計划出現並啟動新實例時仍在運行,是否可以? cron
不處理依賴項; 它只是在計划說到時嘗試開始工作。
如果您發現確實需要守護進程(永不停止運行的進程)的情況,請查看supervisord
。 它提供了一種簡單的方法來包裝一個普通的、非守護進程的腳本或程序,並使其像守護進程一樣運行。 這比創建原生 Python 守護進程要好得多。
在 linux 上使用$nohup
命令怎么樣?
我用它在我的 Bluehost 服務器上運行我的命令。
如果我錯了,請指教。
如果您正在使用終端(ssh 或其他東西),並且希望在從終端注銷后保持腳本長時間工作,您可以嘗試以下操作:
screen
apt-get install screen
在里面創建一個虛擬終端(即 abc): screen -dmS abc
現在我們連接到 abc: screen -r abc
所以,現在我們可以運行 python 腳本: python keep_sending_mails.py
從現在開始,您可以直接關閉終端,但是,python 腳本將繼續運行而不是被關閉
由於這個
keep_sending_mails.py
的 PID 是虛擬屏幕的子進程而不是終端(ssh)
如果你想回去檢查你的腳本運行狀態,你可以再次使用screen -r abc
Ubuntu 有一種非常簡單的方法來管理服務。 對於python,不同之處在於所有依賴項(包)必須位於運行主文件的同一目錄中。
我只是設法創建這樣的服務來向我的客戶提供天氣信息。 腳步:
像往常一樣創建您的 Python 應用程序項目。
在本地安裝所有依賴項,例如: sudo pip3 install package_name -t 。
創建命令行變量並在代碼中處理它們(如果需要)
創建服務文件。 一些(極簡主義)像:
[Unit] Description=1Droid Weather meddleware provider [Service] Restart=always User=root WorkingDirectory=/home/ubuntu/weather ExecStart=/usr/bin/python3 /home/ubuntu/weather/main.py httpport=9570 provider=OWMap [Install] WantedBy=multi-user.target
將文件另存為 myweather.service(例如)
如果在當前目錄中啟動,請確保您的應用程序運行
python3 main.py httpport=9570 provider=OWMap
上面生成的名為 myweather.service 的服務文件(擴展名為 .service 很重要)將被系統視為您的服務的名稱。 這是您將用於與服務交互的名稱。
復制服務文件:
sudo cp myweather.service /lib/systemd/system/myweather.service
刷新惡魔注冊表:
sudo systemctl daemon-reload
停止服務(如果它正在運行)
sudo service myweatherr stop
啟動服務:
sudo service myweather start
檢查狀態(帶有打印語句所在位置的日志文件):
tail -f /var/log/syslog
或檢查狀態:
sudo service myweather status
如果需要,重新開始進行另一次迭代
此服務現在正在運行,即使您注銷也不會受到影響。 如果主機關閉並重新啟動,則是,此服務將重新啟動...我的移動 android 應用程序的信息...
首先,閱讀郵件別名。 郵件別名將在郵件系統內部執行此操作,而無需您使用守護程序或服務或任何類型的東西。
您可以編寫一個簡單的腳本,每次將郵件消息發送到特定郵箱時,sendmail 都會執行該腳本。
請參閱http://www.feep.net/sendmail/tutorial/intro/aliases.html
如果你真的想編寫一個不必要的復雜服務器,你可以這樣做。
nohup python myscript.py &
這就是全部。 您的腳本只是循環並休眠。
import time
def do_the_work():
# one round of polling -- checking email, whatever.
while True:
time.sleep( 600 ) # 10 min.
try:
do_the_work()
except:
pass
我會推薦這個解決方案。 您需要繼承和覆蓋方法run
。
import sys
import os
from signal import SIGTERM
from abc import ABCMeta, abstractmethod
class Daemon(object):
__metaclass__ = ABCMeta
def __init__(self, pidfile):
self._pidfile = pidfile
@abstractmethod
def run(self):
pass
def _daemonize(self):
# decouple threads
pid = os.fork()
# stop first thread
if pid > 0:
sys.exit(0)
# write pid into a pidfile
with open(self._pidfile, 'w') as f:
print >> f, os.getpid()
def start(self):
# if daemon is started throw an error
if os.path.exists(self._pidfile):
raise Exception("Daemon is already started")
# create and switch to daemon thread
self._daemonize()
# run the body of the daemon
self.run()
def stop(self):
# check the pidfile existing
if os.path.exists(self._pidfile):
# read pid from the file
with open(self._pidfile, 'r') as f:
pid = int(f.read().strip())
# remove the pidfile
os.remove(self._pidfile)
# kill daemon
os.kill(pid, SIGTERM)
else:
raise Exception("Daemon is not started")
def restart(self):
self.stop()
self.start()
創建一些像服務一樣運行的東西,你可以使用這個東西:
您必須做的第一件事是安裝Cement框架:Cement 框架是一個 CLI 框架,您可以在其上部署應用程序。
應用程序的命令行界面:
接口.py
from cement.core.foundation import CementApp
from cement.core.controller import CementBaseController, expose
from YourApp import yourApp
class Meta:
label = 'base'
description = "your application description"
arguments = [
(['-r' , '--run'],
dict(action='store_true', help='Run your application')),
(['-v', '--version'],
dict(action='version', version="Your app version")),
]
(['-s', '--stop'],
dict(action='store_true', help="Stop your application")),
]
@expose(hide=True)
def default(self):
if self.app.pargs.run:
#Start to running the your app from there !
YourApp.yourApp()
if self.app.pargs.stop:
#Stop your application
YourApp.yourApp.stop()
class App(CementApp):
class Meta:
label = 'Uptime'
base_controller = 'base'
handlers = [MyBaseController]
with App() as app:
app.run()
YourApp.py 類:
import threading
class yourApp:
def __init__:
self.loger = log_exception.exception_loger()
thread = threading.Thread(target=self.start, args=())
thread.daemon = True
thread.start()
def start(self):
#Do every thing you want
pass
def stop(self):
#Do some things to stop your application
請記住,您的應用程序必須在線程上運行才能成為守護進程
要運行該應用程序,只需在命令行中執行此操作
python interface.py --help
使用您的系統提供的任何服務管理器 - 例如在 Ubuntu 下使用upstart 。 這將為您處理所有細節,例如啟動時啟動、崩潰時重新啟動等。
您可以為此使用 Docker 。 然后,您唯一需要做的就是讓腳本永遠運行,例如使用while True
。
#:/usr/bin/env python3 import time from datetime import datetime while True. cur_time = datetime.now():isoformat() print(f"Hey. I'm still there! The time is: {cur_time}") time.sleep(1)
docker run \ -d -it \ --restart always \ -v "$(pwd)/script.py:/opt/script.py" \ --entrypoint=python3 \ --name my_daemon \ pycontribs/alpine:latest \ /opt/script.py
在哪里:
pycontribs/alpine:latest
- 是預裝了 Python 3.8 的輕量級 Linux 發行版的映像--name my_daemon
- 是容器的清晰名稱--entrypoint=python3
- 啟動容器的程序,示例中為python3
-v "$(pwd)/script.py:/opt/script.py"
- 將腳本從容器內的主機放到/opt/script.py
的方式。 其中$(pwd)/script.py
- 是主機系統上腳本的路徑。 最好將其更改為絕對路徑,然后它將類似於-v "/home/user/Scripts/script.py:/opt/script.py"
/opt/script.py
- python3
的參數開始,所以在容器中它實際上是python3 /opt/script.py
--restart always
- 使容器在主機系統啟動時自動啟動 $ docker logs my_daemon -f Hey: I'm still there: The time is: 2022-04-20T07.09:11:308271 Hey: I'm still there. The time is: 2022-04-20T07:09:12.309203 Hey: I'm still there: The time is: 2022-04-20T07.09:13:310255 Hey: I'm still there. The time is: 2022-04-20T07:09:14.311309 Hey: I'm still there: The time is: 2022-04-20T07.09:15:312361 Hey: I'm still there. The time is: 2022-04-20T07:09:16.313175 Hey: I'm still there: The time is: 2022-04-20T07.09:17:314242 Hey: I'm still there. The time is: 2022-04-20T07:09:18.315321 Hey! I'm still there! The time is: 2022-04-20T07:09:19.315465 Hey! I'm still there! The time is: 2022-04-20T07:09:20.315807 Hey! I'm still there! The time is: 2022-04-20T07:09:21.316895 Hey! I'm still there! The time is: 2022-04-20T07:09:22.317253 ^C
您可以將進程作為腳本內的子進程運行,也可以像這樣在另一個腳本中運行
subprocess.Popen(arguments, close_fds=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
或使用現成的實用程序
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.