Orin 上开机自启跑检测docker容器(含完整脚本)

Orin 上开机自启跑检测docker容器(含完整脚本) 文章目录1. 开机自启2. 先想清楚批处理一次还是常驻2.1 上电后处理一批文件就结束2.2 上电后一直跑着3. 开始前确认4. 路径一律改成绝对路径5. 给检测容器补一个“看目录常驻”入口6. 重建镜像带看目录入口7. 先手动拉起一个常驻容器8. 用 systemd 做开机自启8.1 unit 文件完整8.2 启用9. 另一种写法systemd 直接 docker run10. 批处理一次的 oneshot 方案11. 重启验收怎么做12. 开机时 GPU 还没好怎么办13. 开机后功耗档要不要一起拉14. 日志别只堆在终端15. 常见问题16. 日常运维几条命令17. 做到哪算这篇完成18. 小结摘要检测容器能手动跑通不等于上电就会自己起来。本文接着《Orin 上用 Docker 跑通目标检测功能》把重点放到开机自启Docker 服务先就绪、用 systemd 拉起检测容器、挂载路径怎么写、GPU 还没准备好怎么办、重启后怎么验收。适合已经在 Orin 上能手动docker run跑通检测、准备让板子掉电再上电能继续干活的人。1. 开机自启上一篇解决的是容器里能把检测跑通。现场常见下一句是板子重新上电检测还在不在容器崩了会不会自己起来半夜掉电重启要不要人再 ssh 上去敲命令手动docker run和开机自启差的不只是一条命令而是这些条件手动跑开机自启人在终端里环境变量现成没有交互式 shell相对路径经常能凑合systemd 里相对路径很容易翻车GPU / Docker 已经醒了启动时序可能还没齐跑完退出也无所谓要明确批处理一次还是常驻这篇就按「能上电自启、能看日志、能重启验收」来写。2. 先想清楚批处理一次还是常驻开机自启常见有两种目标别混着配。2.1 上电后处理一批文件就结束适合固定目录里丢图片 / 视频上电后扫一遍input/结果写到output/处理完容器退出等下次开机或下次手动触发这种用systemd oneshot更合适。2.2 上电后一直跑着适合盯着输入目录有新文件就处理后面接 USB 相机、RTSP 常驻推理容器挂了希望自动再拉起来这种用常驻容器 --restart unless-stopped再用 systemd 保证开机时docker start。本文两种都给默认先把常驻看目录讲清楚因为它更接近“设备上电就能干活”。3. 开始前确认默认你已经有镜像orin-detector:1.0目录~/orin-docker-detector/{models,input,output}本板建好的yolov8n_fp16.engine手动docker run已经能出结果图先确认 Docker 本身会开机启动sudosystemctlenabledockersudosystemctl is-enableddockerdockerversion再确认镜像还在dockerimages|rg orin-detector不在的话先回到上一篇把镜像构建好别在自启阶段同时排“镜像不存在”和“systemd 写错”。4. 路径一律改成绝对路径开机自启里最常见翻车点就是~/和相对路径。systemd 不会按你平时登录用户的习惯展开~。后面脚本、unit 文件里统一写成绝对路径例如echo$HOME# 假设是 /home/ubuntu下文就按这个写你改成自己的实际路径建议固定成BASE/home/ubuntu/orin-docker-detectorAPP$BASE/appMODELS$BASE/modelsINPUT$BASE/inputOUTPUT$BASE/outputLOGS$BASE/logsmkdir-p$APP$MODELS$INPUT$OUTPUT$LOGS后面所有示例都按/home/ubuntu/orin-docker-detector写。你板子用户名不同全局替换即可。5. 给检测容器补一个“看目录常驻”入口上一篇的detect_trt.py是处理单个--source就退出。要常驻需要一个循环扫input/处理完挪走或打标记结果写到output/。保存为/home/ubuntu/orin-docker-detector/app/watch_and_detect.py#!/usr/bin/env python3监视 input 目录有图片/视频就做检测结果写到 output。 用法容器内: python3 /app/watch_and_detect.py \ --engine /models/yolov8n_fp16.engine \ --input /input \ --output /output \ --done /input/done from__future__importannotationsimportargparseimportshutilimporttimefrompathlibimportPathfromdetect_trtimportTrtYolo,draw_boxes,open_source,postprocess,preprocessimportcv2 IMAGE_EXT{.jpg,.jpeg,.png,.bmp,.webp}VIDEO_EXT{.mp4,.avi,.mov,.mkv}deflist_pending(input_dir:Path,done_dir:Path):files[]forpinsorted(input_dir.iterdir()):ifnotp.is_file():continueifp.suffix.lower()notinIMAGE_EXT|VIDEO_EXT:continue# 已经处理过的别重复扫if(done_dir/p.name).exists():continuefiles.append(p)returnfilesdefprocess_one(detector,src:Path,output_dir:Path,conf:float,iou:float):kind,dataopen_source(str(src))input_hw(detector.in_h,detector.in_w)ifkindimage:nchw,ratio,dw,dhpreprocess(data,input_hw)outdetector.infer(nchw)boxes,scores,cls_idspostprocess(out,ratio,dw,dh,conf,iou,data.shape[:2])visdraw_boxes(data,boxes,scores,cls_ids)save_pathoutput_dir/f{src.stem}_det.jpgcv2.imwrite(str(save_path),vis)print(f[ok]{src.name}-{save_path.name}boxes{len(boxes)})returncapdata writerNonesave_pathoutput_dir/f{src.stem}_det.mp4whileTrue:ok,framecap.read()ifnotok:breaknchw,ratio,dw,dhpreprocess(frame,input_hw)outdetector.infer(nchw)boxes,scores,cls_idspostprocess(out,ratio,dw,dh,conf,iou,frame.shape[:2])visdraw_boxes(frame,boxes,scores,cls_ids)ifwriterisNone:h,wvis.shape[:2]fpscap.get(cv2.CAP_PROP_FPS)or25fourcccv2.VideoWriter_fourcc(*mp4v)writercv2.VideoWriter(str(save_path),fourcc,fps,(w,h))writer.write(vis)cap.release()ifwriterisnotNone:writer.release()print(f[ok]{src.name}-{save_path.name})defmain():apargparse.ArgumentParser()ap.add_argument(--engine,requiredTrue)ap.add_argument(--input,requiredTrue)ap.add_argument(--output,requiredTrue)ap.add_argument(--done,default)ap.add_argument(--conf,typefloat,default0.25)ap.add_argument(--iou,typefloat,default0.45)ap.add_argument(--interval,typefloat,default2.0,help空闲时轮询秒数)argsap.parse_args()input_dirPath(args.input)output_dirPath(args.output)done_dirPath(args.done)ifargs.doneelse(input_dir/done)output_dir.mkdir(parentsTrue,exist_okTrue)done_dir.mkdir(parentsTrue,exist_okTrue)print(floading engine:{args.engine})detectorTrtYolo(args.engine)print(watch started,flushTrue)whileTrue:pendinglist_pending(input_dir,done_dir)ifnotpending:time.sleep(args.interval)continueforsrcinpending:try:process_one(detector,src,output_dir,args.conf,args.iou)shutil.move(str(src),str(done_dir/src.name))exceptExceptionase:print(f[fail]{src.name}:{e},flushTrue)# 失败文件挪到 fail避免死循环反复炸fail_dirinput_dir/failfail_dir.mkdir(parentsTrue,exist_okTrue)shutil.move(str(src),str(fail_dir/src.name))if__name____main__:main()说明它复用上一篇镜像里的detect_trt.py有新文件就处理处理完挪到input/done失败挪到input/fail避免同一个坏文件把日志刷爆如果你暂时不想改镜像也可以先把这个脚本挂进容器再跑。长期还是建议打进镜像。6. 重建镜像带看目录入口在上一篇Dockerfile基础上多拷一个脚本并改入口。保存为/home/ubuntu/orin-docker-detector/app/Dockerfile# 基础镜像标签按你的 JetPack / L4T 替换 FROM nvcr.io/nvidia/l4t-ml:r36.2.0-py3 WORKDIR /app ENV DEBIAN_FRONTENDnoninteractive \ PYTHONDONTWRITEBYTECODE1 \ PYTHONUNBUFFERED1 RUN apt-get update apt-get install -y --no-install-recommends \ python3-pip \ libglib2.0-0 \ libgl1 \ libopencv-dev \ rm -rf /var/lib/apt/lists/* COPY requirements.txt /app/requirements.txt RUN pip3 install --no-cache-dir -r /app/requirements.txt COPY detect_trt.py /app/detect_trt.py COPY watch_and_detect.py /app/watch_and_detect.py ENTRYPOINT [python3, /app/watch_and_detect.py]requirements.txt仍可沿用上一篇numpy1.26.4 opencv-python4.10.0.84 pycuda2024.1.2构建cd/home/ubuntu/orin-docker-detector/appdockerbuild-torin-detector:1.0.7. 先手动拉起一个常驻容器开机自启之前先手动验证常驻模式。保存启动脚本/home/ubuntu/orin-docker-detector/app/start_detector.sh#!/usr/bin/env bashset-euopipefailNAMEorin-detectorIMAGEorin-detector:1.0BASE/home/ubuntu/orin-docker-detector# 已存在就先删掉保证配置是最新的ifdockerps-a--format{{.Names}}|grep-qx$NAME;thendockerrm-f$NAME/dev/nullfidockerrun-d\--name$NAME\--restartunless-stopped\--runtimenvidia\-v$BASE/models:/models\-v$BASE/input:/input\-v$BASE/output:/output\$IMAGE\--engine/models/yolov8n_fp16.engine\--input/input\--output/output\--done/input/done\--interval2dockerps--filtername$NAME执行chmodx /home/ubuntu/orin-docker-detector/app/start_detector.sh /home/ubuntu/orin-docker-detector/app/start_detector.shdockerlogs-forin-detector另开一个终端丢测试图cp/path/to/test.jpg /home/ubuntu/orin-docker-detector/input/sleep3ls/home/ubuntu/orin-docker-detector/output/ls/home/ubuntu/orin-docker-detector/input/done/能看到结果图说明常驻检测链路通了。这时再做 systemd才有意义。如果你的环境用--gpus all更稳把脚本里的--runtime nvidia换成--gpus all即可。8. 用 systemd 做开机自启常驻容器已经加了--restart unless-stoppedDocker 服务起来后一般会把容器带起来。但实践里我仍建议再加一个 systemd unit原因是开机时序更清楚先等docker.service方便统一看日志journalctl -u ...以后改启动参数集中改一处有的板上 Docker 起来了业务容器却没按预期恢复unit 能兜底8.1 unit 文件完整保存为/etc/systemd/system/orin-detector.service[Unit] DescriptionOrin Docker object detector Afternetwork-online.target docker.service Wantsnetwork-online.target Requiresdocker.service [Service] Typeoneshot RemainAfterExityes Userubuntu Groupdocker ExecStart/usr/bin/docker start orin-detector ExecStop/usr/bin/docker stop orin-detector ExecStartPre/bin/sleep 8 TimeoutStartSec120 [Install] WantedBymulti-user.target几点说明Requiresdocker.serviceDocker 没起来这个服务也不硬刚ExecStartPre/bin/sleep 8给 GPU / NVIDIA runtime 一点准备时间你板子如果启动慢可改成 15TypeoneshotRemainAfterExityes这里只负责把已有容器 start/stopUserubuntu改成你的实际用户该用户要在docker组如果容器还不存在docker start会失败。所以第一次要先跑上一节的start_detector.sh创建容器。8.2 启用sudosystemctl daemon-reloadsudosystemctlenableorin-detector.servicesudosystemctl start orin-detector.servicesudosystemctl status orin-detector.service看容器dockerps--filternameorin-detector9. 另一种写法systemd 直接 docker run有人不想先手动创建容器想让 unit 自己docker run。也可以但参数更长改起来要更小心。[Unit] DescriptionOrin Docker object detector (run) Afternetwork-online.target docker.service Wantsnetwork-online.target Requiresdocker.service [Service] Typesimple Userubuntu Groupdocker Restarton-failure RestartSec5 ExecStartPre-/usr/bin/docker rm -f orin-detector ExecStartPre/bin/sleep 8 ExecStart/usr/bin/docker run --rm \ --name orin-detector \ --runtime nvidia \ -v /home/ubuntu/orin-docker-detector/models:/models \ -v /home/ubuntu/orin-docker-detector/input:/input \ -v /home/ubuntu/orin-docker-detector/output:/output \ orin-detector:1.0 \ --engine /models/yolov8n_fp16.engine \ --input /input \ --output /output \ --done /input/done ExecStop/usr/bin/docker stop orin-detector [Install] WantedBymulti-user.target这种写法的好处是unit 即真相不依赖“以前有没有创建过容器”。代价是每次启动都是新容器生命周期日志和docker ps习惯会和--restart方案略有不同。我个人更常用第 8 节那种先创建带 restart 策略的容器systemd 负责开机 start。10. 批处理一次的 oneshot 方案如果你只想“开机扫一遍 input做完就停”不必常驻。保存/home/ubuntu/orin-docker-detector/app/run_batch.sh#!/usr/bin/env bashset-euopipefailBASE/home/ubuntu/orin-docker-detectorLOG$BASE/logs/batch-$(date%Y%m%d-%H%M%S).logmkdir-p$BASE/logs{echobatch start:$(date)forfin$BASE/input/*;do[-e$f]||continuename$(basename$f)stem${name%.*}ext${name##*.}case${ext,,}injpg|jpeg|png|bmp|webp)out$BASE/output/${stem}_det.jpg;;mp4|avi|mov|mkv)out$BASE/output/${stem}_det.mp4;;*)echoskip:$namecontinue;;esacdockerrun--rm\--runtimenvidia\-v$BASE/models:/models\-v$BASE/input:/input\-v$BASE/output:/output\--entrypointpython3\orin-detector:1.0\/app/detect_trt.py\--engine/models/yolov8n_fp16.engine\--source/input/$name\--save/output/$(basename$out)doneechobatch end:$(date)}|tee-a$LOG对应 unit[Unit] DescriptionOrin detector batch once at boot Afternetwork-online.target docker.service Requiresdocker.service [Service] Typeoneshot Userubuntu Groupdocker ExecStartPre/bin/sleep 10 ExecStart/home/ubuntu/orin-docker-detector/app/run_batch.sh [Install] WantedBymulti-user.target这种适合“上电处理一波文件”不适合“一直盯着目录”。11. 重启验收怎么做配完后别只看enable成功。真正验收是冷启动。sudoreboot重新登录后systemctl is-activedockersystemctl status orin-detector.service --no-pagerdockerps--filternameorin-detectordockerlogs--tail50orin-detector再丢一张图cp/home/ubuntu/orin-docker-detector/input/done/某张已处理图.jpg\/home/ubuntu/orin-docker-detector/input/reboot_test.jpg# 如果文件还在 done 里先复制一份新名字进去sleep5ls-lt/home/ubuntu/orin-docker-detector/output/|head验收清单可以记成检查项期望dockeractive是orin-detector容器 Up是logs 里有watch started是新丢的图能出结果是tegrastats有 GPU 负载处理时有12. 开机时 GPU 还没好怎么办这是 Orin 上很常见的坑systemd 已经执行docker start了但 NVIDIA runtime / 设备节点还没完全就绪容器起来就退出或者一推理就报错。可按这个顺序处理ExecStartPre里先sleep几秒到十几秒容器加--restart unless-stopped早期失败后还能再起来看日志确认是不是 runtime / library 初始化失败临时排查journalctl-uorin-detector.service-b--no-pagerdockerlogs orin-detectordmesg|rg-invgpu|tegra|nvidia|tail如果几乎每次开机第一次必失败、第二次才好优先加长 sleep或把 unit 改成Restarton-failure的docker run方案。还有一类更隐蔽容器起来了也能watch started但前几次推理特别慢甚至超时。这不一定是模型问题可能是刚开机还在降频 / 低功耗档磁盘刚挂载完第一次写output/很慢同时还有桌面、更新、别的容器在抢资源所以验收时别只看“容器 Up”最好在重启后真实丢一张图确认结果文件出现。13. 开机后功耗档要不要一起拉很多现场希望板子上电后不只是检测容器起来功耗档也切到测试或量产用的那档。可以单独做一个小脚本例如/home/ubuntu/orin-docker-detector/app/set_power.sh#!/usr/bin/env bashset-euopipefail# 档位名随板型和 JetPack 变化先用 nvpmodel -q 看本板有哪些sudonvpmodel-m0# 需要锁频再开不需要就注释掉# sudo jetson_clocksnvpmodel-q然后在orin-detector.service的ExecStartPre里加一行调用或者另做一个更早启动的 unit。注意量产外壳散热一般别一上来就锁最高档开发阶段可以高档方便暴露软件问题改功耗档需要权限unit 里若用普通用户可能要配好sudoers或改由 root 执行该脚本功耗档和自启是两件事但现场经常被一起要求。建议至少在文档里记下开机后默认是哪一档、检测验收时用的是哪一档。14. 日志别只堆在终端常驻容器跑久了docker logs会很长结果图也会堆满盘。两个最小习惯# 看最近日志别整锅端dockerlogs--tail200orin-detector# 输出目录体积du-sh/home/ubuntu/orin-docker-detector/outputdu-sh/home/ubuntu/orin-docker-detector/input/done更稳一点可以给容器加日志上限避免把系统盘写爆dockerrun-d\--nameorin-detector\--restartunless-stopped\--log-opt max-size10m\--log-opt max-file3\--runtimenvidia\-v/home/ubuntu/orin-docker-detector/models:/models\-v/home/ubuntu/orin-docker-detector/input:/input\-v/home/ubuntu/orin-docker-detector/output:/output\orin-detector:1.0\--engine/models/yolov8n_fp16.engine\--input/input\--output/output\--done/input/donedone/和output/也建议定期清理或按日期分子目录。自启配好了却因为满盘挂掉排障会很像“模型突然坏了”。15. 常见问题现象多见原因先查什么开机后容器不在没 enable、容器名不对、首次没创建容器systemctl status、docker ps -aunit 失败手动 docker 却正常相对路径、~、用户不在 docker 组unit 里绝对路径、groups容器反复重启引擎路径错、GPU 未就绪、代码异常docker logs有容器但不出结果输入目录挂错、文件后缀不对-v映射、input/内容重启后权限报错输出目录属主变化ls -l必要时统一属主系统盘又满了结果图/日志堆太多定期清output/和logs/权限上再补一句如果容器以 root 写文件宿主机output/可能变成 root 属主。团队共用板时提前约定清理和属主策略少很多扯皮。16. 日常运维几条命令# 看服务systemctl status orin-detector.service --no-pager# 看容器dockerps-a--filternameorin-detector# 看业务日志dockerlogs-f--tail100orin-detector# 临时停sudosystemctl stop orin-detector.servicedockerstop orin-detector# 改镜像后重建镜像 - 重建容器 - 再启服务cd/home/ubuntu/orin-docker-detector/appdockerbuild-torin-detector:1.0../start_detector.shsudosystemctl restart orin-detector.service建议把start_detector.sh当成“容器配置真相源”。改挂载、改引擎路径、改 restart 策略都先改脚本再重建容器。17. 做到哪算这篇完成Docker 已enable开机自己起来检测容器能在开机后自动处于 Up往input/丢文件能在output/看到结果知道去哪看systemctl status和docker logs至少做过一次真实reboot验收不是只看 enable 成功做到这些板子才更接近“设备”而不是“每次都要人上去敲命令的开发机”。18. 小结Orin 上做开机自启核心不是多记几条 systemd 语法而是把三件事对齐Docker 先就绪检测容器的启动方式固定常驻看目录或开机批处理一次路径、时序、日志能在无人值守时排障上一篇解决“容器里能检测”这篇解决“上电后还在检测”。两步都通了后面再接相机、告警、前端才有稳定底座。