文章目录
参考文章
Python虚拟环境使用教程(以虚拟环境管理工具venv为例)(virtualenv、venv、pyenv、virtualenvwrapper、conda不同管理工具对比)
目录结构
步骤
安装venv
它会基于当前版本的python3来安装python3-venv
python
apt update && apt install python3.8-venv
查看python版本
执行python
:
创建虚拟环境
进入项目目录,执行:
python
python3 -m venv .venv-python3.8
(我把每个项目虚拟环境固定取名为.venv-python3.8
,后续方便脚本操作)
20230813:改了,不这么搞了,容易混淆,每个虚拟环境都应该有自己特定的名字:
python
python3 -m venv .venv-python3.8-ky_ai_ip_change
可以看到,生成了目录.venv-python3.8
我这刚生成的虚拟环境占空间也不大,才7兆多:
激活虚拟环境
python
source .venv-python3.8/bin/activate
运行我们程序看缺少哪些依赖库,依次安装它们
执行:
python
python3 ip_change
发现少了ping3,装上:
python
pip install ping3
反复执行:
python
python3 ip_change
缺少啥库就装啥库:
下载太慢,我换成清华源,怎么还给我卡住了?
python
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gevent
20230813:如果有requirements.txt文件,可以直接:
python
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
可以禁用缓存,因为缓存可能会导致大问题(比如缓存里有但是是用之前正确的方式下载的,当前下载方式不正确但用缓存也成功了,就会导致下载者误判):
python
pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
第二天看,好了:
继续:
可以了,跑起来了,接口也能调通:
接下来我们配置python程序启动脚本,脚本中启动python程序前需先激活虚拟环境
注意journalctl -u <servicename>
没有及时打印python日志的原因是因为在systemd unit文件中执行shell脚本,脚本中再执行python命令,命令没加-u参数导致的。估计是systemd自动把shell脚本输出重定向到journalctl日志,跟我们之前遇到的问题一样
ky_ai_ip_change.sh
python
#!/bin/bash
# 打印所有,包括注释
# set -v
# 打印执行命令
# set -x
# 命令出错退出
set -e
# 使用未初始化变量退出
set -u
USER="root"
# --------------------------------------------------------------------------
# 检查是否是root
WHO=$(whoami | grep "${USER}$")
if [ -z "${WHO}" ]; then
echo
echo "Please change to \"${USER}\" user mode first!"
echo
exit 1
fi
# --------------------------------------------------------------------------
# 获取脚本所在路径
SCRIPT_LOCATION=$(
cd "$(dirname "$0")" || {
echo "cd Failure"
exit 1
}
pwd
)
echo "SCRIPT_LOCATION = $SCRIPT_LOCATION"
# --------------------------------------------------------------------------
VENV_NAME=".venv-python3.8"
PYTHON_ENTRY_FILE="ip_change"
# --------------------------------------------------------------------------
# 激活虚拟环境
# 加指令注释消除shellcheck警告,source后有变量就会警告
# shellcheck source=/ky/tml/ky_ai_ip_change/.venv-python3.8/bin/activate
source $SCRIPT_LOCATION/$VENV_NAME/bin/activate
if [ $? -ne 0 ]; then
echo "Execute [source $SCRIPT_LOCATION/$VENV_NAME/bin/activate] failed"
exit 1
fi
# 运行项目
# python -u $SCRIPT_LOCATION/$PYTHON_ENTRY_FILE
# if [ $? -ne 0 ]; then
# echo "Execute [python $SCRIPT_LOCATION/$PYTHON_ENTRY_FILE] failed"
# exit 1
# fi
# 改一改,避免命令报错直接退出脚本,没有机会退出虚拟环境
if python -u $SCRIPT_LOCATION/$PYTHON_ENTRY_FILE; then
echo "命令 [python -u $SCRIPT_LOCATION/$PYTHON_ENTRY_FILE] 执行成功"
else
echo "命令 [python -u $SCRIPT_LOCATION/$PYTHON_ENTRY_FILE] 执行失败"
fi
deactivate
if [ $? -ne 0 ]; then
echo "Execute [deactivate] failed,退出虚拟环境失败"
exit 1
fi
echo "Execute [deactivate] successfully,退出虚拟环境成功"
配置.service文件
ky_ai_ip_change.service
python
[Unit]
Description=ky_ai_ip_change
After=network.target
[Service]
ExecStart=/ky/tml/ky_ai_ip_change/ky_ai_ip_change.sh
WorkingDirectory=/ky/tml/ky_ai_ip_change
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
然后执行部署脚本,成功了
python
#!/bin/bash
# 打印所有,包括注释
# set -v
# 打印执行命令
# set -x
# 命令出错退出
set -e
# 使用未初始化变量退出
set -u
USER=root
# USER_HOME=/root
# --------------------------------------------------------------------------
# 检查是否是root
WHO=$(whoami | grep "${USER}$")
if [ -z "${WHO}" ]; then
echo
echo "Please change to \"${USER}\" user mode first!"
echo
exit 1
fi
# --------------------------------------------------------------------------
# 获取脚本所在路径
SCRIPT_LOCATION=$(
cd "$(dirname "$0")" || {
echo "cd Failure"
exit 1
}
pwd
)
# echo "SCRIPT_LOCATION = $SCRIPT_LOCATION"
chmod 777 ${SCRIPT_LOCATION} -R
# --------------------------------------------------------------------------
# 不同服务只用改 SERVICE_NAME 变量即可
SERVICE_NAME="ky_ai_ip_change"
SERVICE_FILE_NAME="$SERVICE_NAME.service"
SERVICE_SOURCE_FILE_PATH="$SCRIPT_LOCATION/$SERVICE_FILE_NAME"
# 不能在 /etc/systemd/system 中创建子目录吗?(貌似是的,测试很多次都不行)
# SERVICE_TARGET_DIR_NAME="ky_ai_service"
# SERVICE_TARGET_DIR_PATH="/etc/systemd/system/$SERVICE_TARGET_DIR_NAME"
SERVICE_TARGET_DIR_PATH="/etc/systemd/system"
# --------------------------------------------------------------------------
# 创建目标目录
# if [ ! -d "$SERVICE_TARGET_DIR_PATH" ]; then
# mkdir -p "$SERVICE_TARGET_DIR_PATH"
# echo "创建目录:[$SERVICE_TARGET_DIR_PATH]"
# else
# echo "目录已存在,不重新创建:[$SERVICE_TARGET_DIR_PATH]"
# fi
# chmod 777 "$SERVICE_TARGET_DIR_PATH" -R
# --------------------------------------------------------------------------
# 判断服务是否存在
if systemctl list-unit-files --type=service | grep -q "$SERVICE_NAME"; then
echo "$SERVICE_NAME.service exists"
# 打印服务状态
# systemctl status $SERVICE_NAME
echo
# 这句明明是打印居然会触发 set -e 报错退出,加上 || true
# systemctl status $SERVICE_NAME || true
# 貌似信息比较长时,会有分页等待用户输入阻塞程序,加上 --no-pager 选项
systemctl status $SERVICE_NAME --no-pager || true
echo
# 询问用户是否删除
read -p "Do you want to delete $SERVICE_NAME.service? (y/n): " choice
if [[ $choice == "y" || $choice == "Y" ]]; then
# 删除服务
systemctl stop $SERVICE_NAME
echo "已 stop [$SERVICE_NAME] 服务 "
systemctl disable $SERVICE_NAME
echo "已 disable [$SERVICE_NAME] 服务"
# rm /etc/systemd/system/$SERVICE_NAME.service # 不用删,会自动删的
systemctl daemon-reload
echo "已 daemon-reload"
echo "$SERVICE_NAME.service has been deleted"
else
echo "Exiting script"
exit 0
fi
else
echo "$SERVICE_NAME.service not exists"
fi
echo
# --------------------------------------------------------------------------
# 这句明明是打印居然会触发 set -e 报错退出
# systemctl list-unit-files | grep "$SERVICE_NAME"
# systemctl list-unit-files | grep "$SERVICE_NAME" || true
# --------------------------------------------------------------------------
# Function: create_symlink
# Description: Check if a symlink exists and is valid. If it is valid, prompt the user to delete and relink it.
# If it is invalid, display an error message. If it does not exist, create a new symlink.
# Parameters:
# $1 - The target path of the symlink
# $2 - The path of the symlink
# Returns:
# None
function create_symlink() {
SRC=$1
LINK=$2
if [ -e $LINK ]; then
if [ -L $LINK ]; then
echo "The symlink $LINK is valid."
read -p "Do you want to delete and relink it? (y/n) " choice
case "$choice" in
y | Y)
rm $LINK
echo "The symlink $LINK has been deleted."
;;
*)
return 0
;;
esac
else
echo "The symlink $LINK is invalid."
fi
fi
echo "Force create soft link: [$LINK -> $SRC]"
ln -sf $SRC $LINK
if [ $? -ne 0 ]; then
echo "Force create soft link: [$LINK -> $SRC] failed"
exit 1
fi
ls -l --color=auto $LINK
}
# --------------------------------------------------------------------------
# 判断软链接/usr/local/bin/node是否存在,如果存在,判断软链接是否有效,如果有效,询问用户是否删除,如果用户选择是,则删除此软链接,并重新创建
SERVICE_TARGET_FILE_PATH="$SERVICE_TARGET_DIR_PATH/$SERVICE_FILE_NAME"
# ls -l --color=auto $NODE_LINK
create_symlink $SERVICE_SOURCE_FILE_PATH $SERVICE_TARGET_FILE_PATH
echo
# --------------------------------------------------------------------------
# 通知systemd重新加载配置文件
systemctl daemon-reload
if [ $? -ne 0 ]; then
echo "Systemctl daemon-reload failed"
exit 1
fi
echo "Daemon-reload successfully"
# 启用服务
systemctl enable $SERVICE_NAME
if [ $? -ne 0 ]; then
echo "Enable service [$SERVICE_NAME] failed"
exit 1
fi
echo "Enable service [$SERVICE_NAME] successfully"
# 启动服务
systemctl start $SERVICE_NAME
if [ $? -ne 0 ]; then
echo "Start service [$SERVICE_NAME] failed"
exit 1
fi
echo "Start service [$SERVICE_NAME] successfully"
# --------------------------------------------------------------------------
echo
# systemctl status $SERVICE_NAME || true
# 貌似信息比较长时,会有分页等待用户输入阻塞程序,加上 --no-pager 选项
systemctl status $SERVICE_NAME --no-pager || true
# --------------------------------------------------------------------------
echo
echo "Service [$SERVICE_FILE_NAME] install successfully"
echo
但是又莫名其妙搞出来一个问题,后面一直不能复现,后来又好了,一直无法复现(考虑问题复现时,使用备用方案)
参考文章:Current command vanished from the unit file, execution of the command list won't be resumed.
后面如果又碰到,可以考虑直接systemd unit文件中直接执行python指令而不是脚本,同时定义ExecStartPre
和ExecStopPost
执行指令前的激活虚拟环境和退出虚拟环境的操作:
如果使用systemd来执行Python服务,可以在service配置文件中设置ExecStartPre
和ExecStopPost
来在执行前进入虚拟环境,执行后退出虚拟环境。
以下是一个示例的service配置文件,展示了如何在执行前进入虚拟环境,执行后退出虚拟环境:
plaintext
[Unit]
Description=My Python Service
[Service]
ExecStartPre=/bin/bash -c 'source /path/to/venv/bin/activate'
ExecStart=/path/to/python /path/to/script.py
ExecStopPost=/bin/bash -c 'deactivate'
[Install]
WantedBy=multi-user.target
在这个示例中,ExecStartPre
指定了在执行前要执行的命令,即进入虚拟环境的命令source /path/to/venv/bin/activate
。ExecStart
指定了要执行的Python脚本的路径。ExecStopPost
指定了在执行后要执行的命令,即退出虚拟环境的命令deactivate
。
请将/path/to/venv
替换为你的虚拟环境的路径,将/path/to/python
替换为你的Python解释器的路径,将/path/to/script.py
替换为你的Python脚本的路径。
通过这样的配置,当你启动或停止该service时,会自动进入和退出虚拟环境。
反正现在是正常的
20230811 虚拟环境中搞jtop(jetson-stats)还是有亿点问题
python
pip3 install -U jetson-stats