本文目录导读:

我来详细介绍如何实现服务启停脚本,包含不同场景的实现方式。
Systemd Service (推荐 - Linux系统)
创建服务文件
sudo vi /etc/systemd/system/myapp.service
[Unit] Description=My Application Service After=network.target [Service] Type=simple User=myapp Group=myapp WorkingDirectory=/opt/myapp ExecStart=/usr/bin/python3 /opt/myapp/app.py ExecStop=/bin/kill -SIGTERM $MAINPID ExecReload=/bin/kill -SIGHUP $MAINPID Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
命令使用
# 启动服务 sudo systemctl start myapp # 停止服务 sudo systemctl stop myapp # 重启服务 sudo systemctl restart myapp # 查看状态 sudo systemctl status myapp # 开机自启 sudo systemctl enable myapp
Shell脚本版 (通用)
基本服务启停脚本
#!/bin/bash
# 配置项
APP_NAME="myapp"
APP_PATH="/opt/myapp"
APP_CMD="python3 app.py"
PID_FILE="/var/run/${APP_NAME}.pid"
LOG_FILE="/var/log/${APP_NAME}.log"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 获取进程PID
get_pid() {
if [ -f "${PID_FILE}" ]; then
cat "${PID_FILE}"
else
echo ""
fi
}
# 检查服务状态
status() {
local pid=$(get_pid)
if [ -n "${pid}" ] && kill -0 "${pid}" 2>/dev/null; then
echo -e "${GREEN}${APP_NAME} 正在运行 (PID: ${pid})${NC}"
return 0
else
echo -e "${RED}${APP_NAME} 未运行${NC}"
return 1
fi
}
# 启动服务
start() {
echo -e "${YELLOW}正在启动 ${APP_NAME}...${NC}"
# 检查是否已运行
if status > /dev/null 2>&1; then
echo -e "${YELLOW}${APP_NAME} 已经在运行中${NC}"
return 1
fi
# 进入工作目录
cd "${APP_PATH}" || {
echo -e "${RED}无法进入工作目录: ${APP_PATH}${NC}"
return 1
}
# 启动服务
nohup ${APP_CMD} >> "${LOG_FILE}" 2>&1 &
local pid=$!
echo ${pid} > "${PID_FILE}"
# 等待启动完成
sleep 2
if kill -0 "${pid}" 2>/dev/null; then
echo -e "${GREEN}${APP_NAME} 启动成功 (PID: ${pid})${NC}"
return 0
else
echo -e "${RED}${APP_NAME} 启动失败,请检查日志${NC}"
return 1
fi
}
# 停止服务
stop() {
echo -e "${YELLOW}正在停止 ${APP_NAME}...${NC}"
local pid=$(get_pid)
if [ -z "${pid}" ]; then
echo -e "${YELLOW}${APP_NAME} 未在运行${NC}"
return 0
fi
# 先尝试优雅停止
kill "${pid}" 2>/dev/null
# 等待进程结束
local timeout=30
while [ ${timeout} -gt 0 ]; do
if ! kill -0 "${pid}" 2>/dev/null; then
break
fi
sleep 1
timeout=$((timeout - 1))
done
# 如果无法优雅停止,强制终止
if kill -0 "${pid}" 2>/dev/null; then
echo -e "${YELLOW}强制停止进程...${NC}"
kill -9 "${pid}" 2>/dev/null
fi
# 清理PID文件
rm -f "${PID_FILE}"
echo -e "${GREEN}${APP_NAME} 已停止${NC}"
}
# 重启服务
restart() {
stop
sleep 2
start
}
# 查看日志
logs() {
if [ -f "${LOG_FILE}" ]; then
tail -f "${LOG_FILE}"
else
echo -e "${RED}日志文件不存在: ${LOG_FILE}${NC}"
fi
}
# 主函数
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
logs)
logs
;;
*)
echo "用法: $0 {start|stop|restart|status|logs}"
exit 1
;;
esac
exit $?
Java应用服务脚本
#!/bin/bash
# Java应用配置
APP_NAME="spring-boot-app"
JAR_NAME="app.jar"
JAVA_HOME="/usr/local/java/jdk11"
APP_DIR="/opt/${APP_NAME}"
JAVA_OPTS="-Xms512m -Xmx1024m -Dspring.profiles.active=prod"
PID_FILE="/var/run/${APP_NAME}.pid"
LOG_FILE="/var/log/${APP_NAME}/app.log"
# 加载环境变量
if [ -f "${APP_DIR}/env.sh" ]; then
source "${APP_DIR}/env.sh"
fi
# 启动Java应用
start() {
echo "Starting ${APP_NAME}..."
cd "${APP_DIR}"
# 使用exec启动Java进程
exec ${JAVA_HOME}/bin/java ${JAVA_OPTS} \
-jar ${APP_DIR}/${JAR_NAME} \
2>&1 &
echo $! > ${PID_FILE}
echo "${APP_NAME} started with PID $(cat ${PID_FILE})"
}
# 优雅停止Java应用
stop() {
echo "Stopping ${APP_NAME}..."
if [ ! -f ${PID_FILE} ]; then
echo "${APP_NAME} is not running"
return 0
fi
local pid=$(cat ${PID_FILE})
# 发送SIGTERM信号
kill -15 ${pid}
# 等待进程结束
for i in $(seq 1 30); do
if ! kill -0 ${pid} 2>/dev/null; then
echo "${APP_NAME} stopped successfully"
rm -f ${PID_FILE}
return 0
fi
sleep 1
done
# 超时后强制停止
echo "Force stopping ${APP_NAME}..."
kill -9 ${pid} 2>/dev/null
rm -f ${PID_FILE}
}
Docker版服务管理
#!/bin/bash
# Docker容器管理
CONTAINER_NAME="myapp"
IMAGE_NAME="myapp:latest"
NETWORK="myapp-network"
VOLUMES="-v /data/myapp:/app/data"
start() {
echo "Starting container ${CONTAINER_NAME}..."
# 检查容器是否存在
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
# 如果存在则启动
docker start ${CONTAINER_NAME}
else
# 不存在则创建并启动
docker run -d \
--name ${CONTAINER_NAME} \
--restart unless-stopped \
--network ${NETWORK} \
${VOLUMES} \
-p 8080:8080 \
${IMAGE_NAME}
fi
}
stop() {
echo "Stopping container ${CONTAINER_NAME}..."
docker stop -t 30 ${CONTAINER_NAME}
}
restart() {
stop
sleep 5
start
}
status() {
docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
}
logs() {
docker logs -f --tail 100 ${CONTAINER_NAME}
}
Python装饰器版服务框架
#!/usr/bin/env python3
import os
import sys
import signal
import argparse
import logging
import time
from contextlib import contextmanager
class ServiceManager:
"""服务管理器装饰器"""
def __init__(self, name, pid_file=None, log_file=None):
self.name = name
self.pid_file = pid_file or f"/var/run/{name}.pid"
self.log_file = log_file or f"/var/log/{name}.log"
self.logger = logging.getLogger(name)
self._setup_logging()
def _setup_logging(self):
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(self.log_file),
logging.StreamHandler(sys.stdout)
]
)
def __call__(self, func):
def wrapper(*args, **kwargs):
parser = argparse.ArgumentParser(description=f'{self.name} Service Manager')
parser.add_argument('action', choices=['start', 'stop', 'restart', 'status'],
help='Service action')
args = parser.parse_args()
if args.action == 'start':
return self._start(func, *args, **kwargs)
elif args.action == 'stop':
return self._stop()
elif args.action == 'restart':
return self._stop() or self._start(func, *args, **kwargs)
elif args.action == 'status':
return self._status()
return wrapper
def _start(self, func, *args, **kwargs):
if self._get_pid() and os.path.exists(f'/proc/{self._get_pid()}'):
self.logger.warning(f"{self.name} is already running")
return
pid = os.fork()
if pid > 0:
# Parent process
with open(self.pid_file, 'w') as f:
f.write(str(pid))
self.logger.info(f"{self.name} started (PID: {pid})")
sys.exit(0)
elif pid == 0:
# Child process
self._daemonize()
func(*args, **kwargs)
def _stop(self):
pid = self._get_pid()
if not pid:
self.logger.info(f"{self.name} is not running")
return
try:
os.kill(pid, signal.SIGTERM)
# Wait for process to exit
for _ in range(30):
time.sleep(1)
if not os.path.exists(f'/proc/{pid}'):
break
else:
os.kill(pid, signal.SIGKILL)
os.remove(self.pid_file)
self.logger.info(f"{self.name} stopped")
except ProcessLookupError:
os.remove(self.pid_file)
self.logger.info(f"{self.name} was not running")
def _status(self):
pid = self._get_pid()
if pid and os.path.exists(f'/proc/{pid}'):
self.logger.info(f"{self.name} is running (PID: {pid})")
return True
else:
self.logger.info(f"{self.name} is not running")
return False
def _get_pid(self):
if os.path.exists(self.pid_file):
with open(self.pid_file) as f:
return int(f.read().strip())
return None
def _daemonize(self):
os.setsid()
os.umask(0)
sys.stdin.close()
sys.stdout.close()
sys.stderr.close()
# 使用示例
@ServiceManager("myapp", pid_file="/tmp/myapp.pid", log_file="/tmp/myapp.log")
def main():
"""Your application main function"""
import time
while True:
print("Service is running...")
time.sleep(10)
if __name__ == "__main__":
main()
最佳实践建议
安全性考虑
# 1. 权限控制
chmod 750 /etc/init.d/myservice
chown root:root /etc/init.d/myservice
# 2. 防止重复启动
if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE) 2>/dev/null; then
echo "Service is already running"
exit 1
fi
# 3. 资源限制
ulimit -n 65535
ulimit -u 10240
测试脚本
#!/bin/bash
# test_service.sh - 服务脚本测试
# 测试用例
test_service() {
local script=$1
echo "Testing $script..."
# 测试启动
$script start
if [ $? -eq 0 ]; then
echo "✓ Start successful"
else
echo "✗ Start failed"
return 1
fi
# 测试状态
$script status
if [ $? -eq 0 ]; then
echo "✓ Status check passed"
fi
# 测试重启
$script restart
if [ $? -eq 0 ]; then
echo "✓ Restart successful"
fi
# 测试停止
$script stop
if [ $? -eq 0 ]; then
echo "✓ Stop successful"
fi
}
# 运行测试
test_service "./myservice.sh"
选择哪种方式取决于你的具体需求:
- 现代Linux系统:优先使用systemd
- 传统系统或简单服务:使用Shell脚本
- Java应用:使用专门的Java脚本
- 容器化部署:使用Docker管理
- Python应用:使用装饰器框架
建议始终包含错误处理、日志记录和状态检查功能。