######################################################################### # File Name: docker_start.sh # Version: v1.3 # Author: yangyangen # Mail: yangyangen@exocr.com # Created Time: 2024-12-19 12:39:10 ######################################################################### #!/bin/bash # startup docker container echo "-----------------------------易道博识DeepOCR识别服务Docker版本执行开始--------------------------------" port="7085" # 设置docker run -p参数中对外映射的端口号 nvidia="5" # 设置要挂载的Nvidia显卡,默认全部显卡all,如果要修改指定显卡的话,举例:nvidia="0,2,3" forward="y" # 设置docker容器的端口转发模式: # 当forward="y"时,使用-p和--mac-address参数生效:将容器的端口映射到主机的端口上,可以从主机访问容器内部的服务;允许在启动容器时指定一个固定的 MAC 地址。 # 当forward="n"时,指定对外映射端口的port参数和指定mac地址参数不在生效,将使用容器内的server.conf配置文件中指定的端口。表示容器内的所有端口将直接暴露在主机上,意味着容器和主机共享使用网络接口、IP 地址和端口。 use_cpu="n" # 设置OCR识别服务是否需要CPU方式启动,y表示使用cpu模式,n表示不使用cpu模式,默认为不使用CPU模式。 ip_port_node="5000" # 设置docker run -p参数中容器内的端口号,通常禁止修改,除非很清楚的知道修改该参数值的意义和造成的影响。 pod_name="httpsrv_7085" # 指定容器名称,默认即可,通常不建议修改,否则会影响docker_shutdown.sh脚本的执行过程 # 检查 forward 是否为 y 或 n if [[ "$forward" != "y" && "$forward" != "n" ]]; then echo "报错:forward 参数的值无效,请修改为等于 y 或 n。" exit 1 fi # 检查 forward 是否为 y 或 n if [[ "$use_cpu" != "y" && "$use_cpu" != "n" ]]; then echo "报错:use_cpu 参数的值无效,请修改为等于 y 或 n。" exit 1 fi # 检查系统是否安装了系统常见的命令工具包 packages_list=( "pciutils" # 用于支持lspci命令 "iproute" # 用于支持ss命令 ) # 用来记录未安装的包 missing_packages_list=() echo "检查以下系统依赖相关部署包是否已安装:" for package in "${packages_list[@]}"; do if command -v dpkg &>/dev/null; then pkg_check_cmd="dpkg -l" pkg_name_check_cmd="dpkg -s" elif command -v rpm &>/dev/null; then pkg_check_cmd="rpm -q" pkg_name_check_cmd="rpm -q" else echo "错误:无法找到适用的包管理工具(dpkg 或 rpm)。" exit 1 fi # 检查包是否已安装 if $pkg_check_cmd "$package" &>/dev/null; then echo "$package 已安装" else echo "$package 未安装" missing_packages_list+=("$package") fi done # 如果有未安装的包,则退出并提示 if [ ${#missing_packages_list[@]} -gt 0 ]; then echo "错误:以下部署包未安装,请安装后再试。" for pkg in "${missing_packages_list[@]}"; do echo "- $pkg" done exit 1 fi echo "-------------------------------------------------------------------------------------------------" # 检查端口号是否为合法数字,且在有效范围内 if [ "$forward" = "y" ]; then if ! [[ "$port" =~ ^[1-9][0-9]{0,4}$ ]] || (( port < 1 || port > 65535 )); then echo "报错:端口号 $port 无效。端口号必须是1到65535之间的正整数。" exit 1 fi # 检查端口号是否已被占用 if ss -tuln | grep ":$port " > /dev/null; then echo "报错:端口号 $port 已被占用。" exit 1 fi # 端口号校验通过 echo "DeepOCR服务端口号为 $port 校验通过,可以使用。" forwarding=$(sysctl net.ipv4.ip_forward | awk '{print $3}') if [ "$forwarding" -eq 1 ]; then echo "IPv4转发已开启。" else echo "报错:IPv4转发未开启,端口转发功能不可用,IPv4开启方法请参考:" echo "1.临时开启IPv4转发:临时开启会在操作系统重启后失效,执行命令为:sudo sysctl -w net.ipv4.ip_forward=1" echo "2.永久开启IPv4转发:请在/etc/sysctl.conf文件中将net.ipv4.ip_forward的值改为1,然后:wq进行保存。" echo "请注意:必须运行该命令将设置的参数生效:sudo sysctl -p && sudo systemctl restart docker" exit 1 fi else # 如果 forward="n",提示不进行端口号校验 echo "跳过转发的端口号校验,将使用 ${pod_name} 容器中DeepOCR服务的 /root/dist/conf/server.conf 中配置的端口进行对外访问。" echo "请检查 ${pod_name} 容器内的DeeoOCR端口号与宿主机端口号是否冲突,端口号是否被其他程序占用。" fi echo "-------------------------------------------------------------------------------------------------" # 检查是否存在 log 目录,如果没有则创建 if [ ! -d "./log_7085/" ]; then # 尝试创建 log 目录 if ! mkdir -p log_7085; then echo "报错:无法创建 log 目录,可能是没有写入权限。" exit 1 fi log_created=true else # 检查是否有写入权限 if [ ! -w "./log_7085" ]; then echo "报错:log 目录存在,但没有写入权限。" exit 1 fi log_created=true fi # 如果目录创建成功并且有权限,输出创建成功的信息 if [ "$log_created" = true ]; then echo "已创建DeepOCR服务日志目录,绝对路径为:$PWD/log" echo "日志名称默认为:httplog.txt,可以通过修改DeepOCR的server.conf配置文件的suffix=y参数进行控制是否要对脚本添加主机名,例如日志格式为:httplog_testdemo.txt" fi echo "-------------------------------------------------------------------------------------------------" # 查找当前目录下的所有.lic文件 lic_file=($(find . -maxdepth 1 -type f -name '*.lic')) # 检查是否找到了lic授权文件 if [ ${#lic_file[@]} -eq 0 ]; then echo "报错:没有找到后缀为.lic的授权文件。" exit 1 elif [ ${#lic_file[@]} -gt 1 ]; then echo "报错:发现多个后缀为lic文件,请只保留一个。" exit 1 fi echo "当前目录下的授权文件名称为: $(basename "$lic_file")" echo "-------------------------------------------------------------------------------------------------" # 检查 Docker 是否安装,并且版本是否大于等于 19.06 if command -v docker &> /dev/null; then # 获取 Docker 版本 docker_version=$(docker --version | awk '{print $3}' | sed 's/,//') # 提取主版本号和次版本号 IFS='.' read -r major minor patch <<< "$docker_version" # 删除次版本号的前导零 minor=$(echo $minor | sed 's/^0*//') # 检查版本是否大于等于 18.06 if [ "$major" -gt 19 ] || { [ "$major" -eq 19 ] && [ "$minor" -ge 6 ]; }; then echo "Docker已安装,版本号为:$docker_version,符合版本要求。" else echo "报错:Docker版本 $docker_version 小于 19.06,不符合版本要求,请升级到 Docker 19.06 或以上版本。" exit 1 fi # 检查 Docker 服务是否启动并正常运行 if systemctl is-active --quiet docker; then echo "Docker服务已启动并正常运行。" else echo "报错:Docker服务未启动或运行异常,可执行命令尝试重新启动 Docker 服务:sudo systemctl restart docker" exit 1 fi else echo "报错:Docker服务未安装,请联系易道博识获取 Docker 部署包和部署手册,待安装 Docker 后重试。" exit 1 fi echo "-------------------------------------------------------------------------------------------------" # 检查当前用户是否具有执行docker命令的权限 # 检查当前用户是否是 root if [ "$(whoami)" = "root" ]; then echo "当前用户 $(whoami) 有权限执行 Docker 命令。" else # 检查当前用户是否在 docker 组中 if ! groups $(whoami) | grep -q '\bdocker\b'; then echo "报错:当前用户 $(whoami) 不在 docker 组中,无法执行 Docker 命令。" echo "请将当前用户添加到 docker 组:sudo usermod -aG docker $(whoami)" echo "然后退出bash并重新登录,或者运行 'newgrp docker' 来应用更改。" exit 1 else echo "当前用户 $(whoami) 有权限执行 Docker 命令。" fi fi echo "-------------------------------------------------------------------------------------------------" # 检查设置模型使用显卡显存的系数。 # 初始化环境变量命令 env_command="" if [ $# -eq 2 ]; then echo "设置模型使用显卡显存的系数的环境变量" arg1="$1" arg2="$2" if [ "$1" != "limit_gpu_memory" ]; then echo "参数错误:第一个参数只能是 'limit_gpu_memory'" exit 1 fi if [ "$arg1" = "limit_gpu_memory" ]; then # 检查第二个参数是否为小数点后最多两位的小数,并且值不大于1.0 if [[ "$arg2" =~ ^0(\.[0-9]{1,2})?$|^1(\.0{1,2})?$ ]]; then env_command="-e TF_PER_PROCESS_GPU_MEMORY_FRACTION=$arg2" # echo "env set $arg1=$arg2" else echo "请将limit_gpu_memory的值修改为小于1.0的浮点值,小数点后保留两位" exit 1 fi fi fi if [ -z "$env_command" ]; then echo "没有指定 limit_gpu_memory 的显存系数。" else echo "limit_gpu_memory 的显存系数为:$arg1=$arg2" fi echo "-------------------------------------------------------------------------------------------------" # 检查docker中是否存在当前需要部署的docker镜像 if [ ! -s version.txt ]; then echo "报错:文件 version.txt 不存在或内容为空。" exit 1 fi image_name=$(cat version.txt) if docker inspect --type=image $image_name &> /dev/null; then echo "镜像为 $image_name 已存在,无需重复导入" else echo "请耐心等待,正在导入Docker镜像" if ! docker load -i ${PWD}/Deep_OCR_Docker.tar; then echo "导入Docker镜像失败" exit 1 fi echo "导入已完成,镜像名称为: ${image_name}" fi echo "-------------------------------------------------------------------------------------------------" # 查找当前目录下的server.conf配置文件 server_conf=$(find . -maxdepth 1 -type f -name 'server.conf') server_conf_arg="" if [ -n "$server_conf" ]; then server_conf_arg="-v ${PWD}/server.conf:/root/dist/conf/server.conf" echo "已向容器内部映射server.conf文件" fi # 查找当前目录下的httpsrv启动文件 httpsrv=$(find . -maxdepth 1 -type f -name 'httpsrv') httpsrv_arg="" if [ -n "$httpsrv" ]; then chmod +x httpsrv httpsrv_arg="-v ${PWD}/httpsrv:/root/dist/httpsrv/httpsrv" echo "已向容器内部映射httpsrv文件" fi # 查找当前目录下的apis.conf启动文件 apis_conf=$(find . -maxdepth 1 -type f -name 'apis.conf') apis_conf_arg="" if [ -n "$apis_conf" ]; then apis_conf_arg="-v ${PWD}/apis.conf:/root/dist/conf/apis.conf" echo "已向容器内部映射apis.conf文件" fi # 查找当前目录下的libpywrap_tc.so启动文件 libpywrap_tc=$(find . -maxdepth 1 -type f -name 'libpywrap_tc.so') libpywrap_tc_arg="" if [ -n "$libpywrap_tc" ]; then libpywrap_tc_arg="-v ${PWD}/libpywrap_tc.so:/root/dist/httpsrv/libpywrap_tc.so" echo "已向容器内部映射libpywrap_tc.so文件" fi # 查找当前目录下的libpywrap.so启动文件 libpywrap=$(find . -maxdepth 1 -type f -name 'libpywrap.so') libpywrap_arg="" if [ -n "$libpywrap" ]; then libpywrap_arg="-v ${PWD}/libpywrap.so:/root/dist/httpsrv/libpywrap.so" echo "已向容器内部映射libpywrap.so文件" fi # 查找当前目录下的libpywrap.so启动文件 libpaddle_inference=$(find . -maxdepth 1 -type f -name 'libpaddle_inference.so') libpaddle_inference_arg="" if [ -n "$libpaddle_inference" ]; then libpaddle_inference_arg="-v ${PWD}/libpaddle_inference.so:/root/dist/httpsrv/libpaddle_inference.so" echo "已向容器内部映射libpaddle_inference.so文件,建议仅限CPU模式时使用。" fi echo "-------------------------------------------------------------------------------------------------" # 检查系统是否安装了 nvidia-smi 驱动工具,并且是否有显卡设备 nvidia_gpus="" # 设置cpu模式的docker命令参数 cpu_mode="" packages=( "libnvidia-container1" # docker挂载显卡时需要 "nvidia-container-toolkit" # docker挂载显卡时需要 #"nvidia-container-toolkit-base" "libnvidia-container-tools" # docker挂载显卡时需要 ) # 用来记录未安装的包 missing_packages=() if [ -f "./libpaddle_inference.so" ] || [ "$use_cpu" = "y" ]; then echo "检测到当前目录下存在 libpaddle_inference.so 文件,或者环境变量 use_cpu=y ,跳过显卡加载过程。将使用 CPU 模式启动OCR识别服务。" cpu_mode="-e use_cpu=y" nvidia_gpus="" # 如果检测到该文件,设置显卡挂载为空 else cpu_mode="" # 检查 PCIe 总线上是否存在 NVIDIA 硬件设备 if lspci | grep -i "nvidia" >/dev/null 2>&1; then echo "检测到 NVIDIA 显卡硬件设备。" # 检查是否安装了 nvidia-smi 驱动工具 if command -v nvidia-smi &>/dev/null; then echo "检测到 nvidia-smi 驱动工具。" # 检查是否可以检测到 NVIDIA GPU if nvidia-smi -L >/dev/null 2>&1; then echo "已检测到 NVIDIA GPU。" # 根据 nvidia 的值来设置 nvidia_gpus if [ "$nvidia" = "all" ]; then nvidia_gpus="--gpus all" echo "默认已向容器内部挂载所有 NVIDIA 显卡。" elif [[ "$nvidia" =~ ^[0-9,]+$ ]]; then nvidia_gpus="--gpus '\"device=$nvidia\"'" echo "指定了显卡设备 $nvidia,已挂载指定显卡。" else echo "警告:指定的显卡的内容格式无效,无法挂载。请重新修改第16行的值。" nvidia_gpus="" exit 1 fi # 如果检测到显卡驱动,才检查相关的 NVIDIA 容器包 echo "检查以下 NVIDIA 相关部署包是否已安装:" for package in "${packages[@]}"; do if command -v dpkg &>/dev/null; then pkg_check_cmd="dpkg -l" pkg_name_check_cmd="dpkg -s" elif command -v rpm &>/dev/null; then pkg_check_cmd="rpm -q" pkg_name_check_cmd="rpm -q" else echo "错误:无法找到适用的包管理工具(dpkg 或 rpm)。" exit 1 fi # 检查包是否已安装 if $pkg_check_cmd "$package" &>/dev/null; then echo "$package 已安装" else echo "$package 未安装" missing_packages+=("$package") fi done # 如果有未安装的包,则退出并提示 if [ ${#missing_packages[@]} -gt 0 ]; then echo "错误:以下部署包未安装,不能挂载nvidia显卡设备到OCR识别容器中进行使用,请安装后再试。" for pkg in "${missing_packages[@]}"; do echo "- $pkg" done exit 1 fi else echo "警告:未检测到 NVIDIA GPU,可能是驱动安装不正确或没有 GPU 显卡。将使用CPU模式运行OCR服务。CPU模式请修改该启动脚本的第22行为use_cpu="y",或者请联系易道博识获取 libpaddle_inference.so 文件才能进行识别。" nvidia_gpus="" fi else echo "警告:未检测到 nvidia-smi 驱动工具,无法使用 NVIDIA 显卡,将使用CPU模式运行OCR服务。CPU模式请修改该启动脚本的第22行为use_cpu="y",或者请联系易道博识获取 libpaddle_inference.so 文件才能进行识别。" nvidia_gpus="" fi else echo "警告:未检测到 NVIDIA 显卡硬件设备,将使用CPU模式运行OCR服务。CPU模式请修改该启动脚本的第22行为use_cpu="y",或者请联系易道博识获取 libpaddle_inference.so 文件才能进行识别。" nvidia_gpus="" fi fi echo "挂载到 ${pod_name} 容器内的显卡为:nvidia_gpus: $nvidia_gpus" echo "-------------------------------------------------------------------------------------------------" network="" mac_address="" # 如果 forward 为 y,检测 MAC 地址和端口号 if [ "$forward" = "y" ]; then # 获取已启动并有效网卡的 MAC 地址 mac_address=$(ip link show | awk '/state UP/ && !/lo/ {getline; print $2; exit}') if [ -z "$mac_address" ]; then echo "警告:没有检测到已启动并且有效的网卡MAC地址。" exit 1 fi ip_port="-p ${port}:${ip_port_node}" mac_address="--mac-address ${mac_address}" echo "检测到的MAC地址为: ${mac_address}" echo "指定端口映射为:${ip_port}" else # 如果 forward 为 n,设置 network 为 host network="--network host" echo "指定 ${pod_name} 容器使用 主机网络模式 :${network}" echo "对外映射端口的 port 参数不在生效,将使用 ${pod_name} 容器内的 /root/dist/conf/server.conf 配置文件中 listening_port 指定的端口。" fi echo "-------------------------------------------------------------------------------------------------" docker_cmd="docker run -itd --name=${pod_name} \ ${ip_port} \ ${network} \ ${cpu_mode} \ ${mac_address} \ ${nvidia_gpus} \ --restart=on-failure \ --workdir /root/dist \ -v ${PWD}/log_7085:/root/dist/log \ -v ${PWD}/$(basename $lic_file):/root/dist/licence/$(basename $lic_file) \ -v ${PWD}/ptxas.12.2:/root/dist/httpsrv/ptxas \ -v /usr/lib/x86_64-linux-gnu/libcuda.so.570.169:/root/dist/httpsrv/libcuda.so.1 \ -v /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.570.169:/root/dist/httpsrv/libnvidia-ml.so.1 \ ${libpaddle_inference_arg} \ ${apis_conf_arg} \ ${env_command} \ ${server_conf_arg} \ ${httpsrv_arg} \ ${libpywrap_tc_arg} \ ${libpywrap_arg} \ ${image_name} sh startup.sh" # 打印完整的 docker run 命令 echo "执行的 Docker 命令为:$docker_cmd" # 执行 docker run 命令 if ! eval "$docker_cmd"; then echo "启动 Docker 容器失败" exit 1 fi echo "------------------${pod_name} 容器已启动,正在检查容器运行状态,请耐心等待------------------------------" # 获取 容器的 ID 或名称 container_id=$(docker ps -aq --filter "name=^${pod_name}$" | tr -d '\r\n\t ') # 等待 3 秒后,检查容器状态 sleep 3 # 获取容器当前的状态 container_status=$(docker inspect --format '{{.State.Status}}' $container_id | tr -d '\r\n\t ') # 如果容器状态是 running,则认为启动成功 if [ "$container_status" == "running" ]; then # 再次确认 3 秒内容器状态是否稳定为 running sleep 3 container_status=$(docker inspect --format '{{.State.Status}}' $container_id | tr -d '\r\n\t ') if [ "$container_status" == "running" ]; then echo "易道博识Docker版本DeepOCR服务启动完成。 ${pod_name} 容器状态为:$container_status" else echo "Docker中容器名称为 ${pod_name} 启动失败,再次检查容器状态不再是 running。当前状态为:$container_status ,请联系易道博识的技术支持人员排查问题。" exit 1 fi else echo "报错:Docker中容器名称为 ${pod_name} 启动失败,容器状态为:$container_status ,请联系易道博识的技术支持人员排查问题。" exit 1 fi echo "-------------------------------------------------------------------------------------------------" # 根据不同的端口映射方式,输出不同提示内容 listening_port=$(docker exec -it ${pod_name} bash -c "grep '^listening_port' /root/dist/conf/server.conf | awk -F'=' '{print \$2}'" | tr -d '\r\n\t ') port_value=$(echo $ip_port | cut -d ':' -f 2) if [ "$forward" = "y" ]; then # 检查 listening_port 和 port_value 是否一致 if [ "$listening_port" != "$port_value" ]; then echo "警告:${pod_name} 容器虽然启动成功,但是 ${pod_name} 容器内/root/dist/conf/server.conf的listening_port:($listening_port) 与 脚本第21行的 ip_port_node :($ip_port_node) 中指定容器内端口号不一致!" echo "请将 ${pod_name} 容器内/root/dist/conf/server.conf的listening_port:($listening_port) ,修改为默认的 (5000);或者修改本脚本第22行 ip_port_node 的值与之保持一致。" echo "修改一致后,需要停止并删除运行当前 ${pod_name} 容器后: docker rm -f ${pod_name} ,重新执行该脚本再次启动容器生效。" exit 1 fi echo "DeepOCR服务当前端口号为:${port},如需修改DeepOCR转发端口号,请将该脚本第14行 port 的 ${port} 修改为指定的有效值。" echo "查看DeepOCR服务的版本号:curl http://localhost:${port}/version" echo "查看DeepOCR服务授权信息:curl http://localhost:${port}/authorize" else # 如果 forward="n",提示不进行端口号校验 # 使用 docker exec 获取 listening_port,并去除多余的换行符或空格 echo "DeepOCR服务当前端口号为:$listening_port" # 检查端口号是否已被占用 if ss -tuln | grep ":$listening_port " > /dev/null; then echo "报错:httpsrv 容器内的 /root/dist/conf/server.conf 中配置的 listening_port 端口号 $listening_port 已被占用。" echo "端口号与宿主机共用,如需指定DeepOCR服务端口,请对 ${pod_name} 容器内的 /root/dist/conf/server.conf 中配置的 listening_port 端口进行修改,已确保端口号不与其他进程端口号造成冲突。" exit 1 fi echo "查看DeepOCR服务的版本号:curl http://localhost:${listening_port}/version" echo "查看DeepOCR服务授权信息:curl http://localhost:${listening_port}/authorize" fi echo "1.如需限制OCR模型占用显卡显存资源,可以执行命令,例如:./docker_startup.sh limit_gpu_memory 0.8" echo "2.如需查看服务日志,可以通过执行命令:tail -100f log/httplog.txt 或 docker exec -it ${pod_name} tail -f log/httplog.txt" echo "3.如需查看服务运行状态,可以通过执行命令:docker ps -a | grep ${pod_name}" echo "4.如需查看容器内的DeepOR服务进程运行情况,可以通过执行命令:docker exec -it ${pod_name} ps -ef | grep httpsrv" echo "---------------------------易道博识DeepOCR识别服务Docker版本执行已完毕-------------------------------"