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 run
    • 10. 批处理一次的 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,data=open_source(str(src))input_hw=(detector.in_h,detector.in_w)ifkind=="image":nchw,ratio,dw,dh=preprocess(data,input_hw)out=detector.infer(nchw)boxes,scores,cls_ids=postprocess(out,ratio,dw,dh,conf,iou,data.shape[:2])vis=draw_boxes(data,boxes,scores,cls_ids)save_path=output_dir/f"{src.stem}_det.jpg"cv2.imwrite(str(save_path),vis)print(f"[ok]{src.name}->{save_path.name}boxes={len(boxes)}")returncap=data writer=Nonesave_path=output_dir/f"{src.stem}_det.mp4"whileTrue:ok,frame=cap.read()ifnotok:breaknchw,ratio,dw,dh=preprocess(frame,input_hw)out=detector.infer(nchw)boxes,scores,cls_ids=postprocess(out,ratio,dw,dh,conf,iou,frame.shape[:2])vis=draw_boxes(frame,boxes,scores,cls_ids)ifwriterisNone:h,w=vis.shape[:2]fps=cap.get(cv2.CAP_PROP_FPS)or25fourcc=cv2.VideoWriter_fourcc(*"mp4v")writer=cv2.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():ap=argparse.ArgumentParser()ap.add_argument("--engine",required=True)ap.add_argument("--input",required=True)ap.add_argument("--output",required=True)ap.add_argument("--done",default="")ap.add_argument("--conf",type=float,default=0.25)ap.add_argument("--iou",type=float,default=0.45)ap.add_argument("--interval",type=float,default=2.0,help="空闲时轮询秒数")args=ap.parse_args()input_dir=Path(args.input)output_dir=Path(args.output)done_dir=Path(args.done)ifargs.doneelse(input_dir/"done")output_dir.mkdir(parents=True,exist_ok=True)done_dir.mkdir(parents=True,exist_ok=True)print(f"loading engine:{args.engine}")detector=TrtYolo(args.engine)print("watch started",flush=True)whileTrue:pending=list_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}",flush=True)# 失败文件挪到 fail,避免死循环反复炸fail_dir=input_dir/"fail"fail_dir.mkdir(parents=True,exist_ok=True)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_FRONTEND=noninteractive \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 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仍可沿用上一篇:

numpy==1.26.4 opencv-python==4.10.0.84 pycuda==2024.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-euopipefailNAME="orin-detector"IMAGE="orin-detector:1.0"BASE="/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--filter"name=$NAME"

执行:

chmod+x /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-stopped,Docker 服务起来后,一般会把容器带起来。
但实践里我仍建议再加一个 systemd unit,原因是:

  • 开机时序更清楚:先等docker.service
  • 方便统一看日志:journalctl -u ...
  • 以后改启动参数,集中改一处
  • 有的板上 Docker 起来了,业务容器却没按预期恢复,unit 能兜底

8.1 unit 文件(完整)

保存为/etc/systemd/system/orin-detector.service

[Unit] Description=Orin Docker object detector After=network-online.target docker.service Wants=network-online.target Requires=docker.service [Service] Type=oneshot RemainAfterExit=yes User=ubuntu Group=docker ExecStart=/usr/bin/docker start orin-detector ExecStop=/usr/bin/docker stop orin-detector ExecStartPre=/bin/sleep 8 TimeoutStartSec=120 [Install] WantedBy=multi-user.target

几点说明:

  • Requires=docker.service:Docker 没起来,这个服务也不硬刚
  • ExecStartPre=/bin/sleep 8:给 GPU / NVIDIA runtime 一点准备时间;你板子如果启动慢,可改成 15
  • Type=oneshot+RemainAfterExit=yes:这里只负责把已有容器 start/stop
  • User=ubuntu:改成你的实际用户;该用户要在docker

如果容器还不存在,docker start会失败。所以第一次要先跑上一节的start_detector.sh创建容器。

8.2 启用

sudosystemctl daemon-reloadsudosystemctlenableorin-detector.servicesudosystemctl start orin-detector.servicesudosystemctl status orin-detector.service

看容器:

dockerps--filtername=orin-detector

9. 另一种写法:systemd 直接 docker run

有人不想先手动创建容器,想让 unit 自己docker run。也可以,但参数更长,改起来要更小心。

[Unit] Description=Orin Docker object detector (run) After=network-online.target docker.service Wants=network-online.target Requires=docker.service [Service] Type=simple User=ubuntu Group=docker Restart=on-failure RestartSec=5 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] WantedBy=multi-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-detector"LOG="$BASE/logs/batch-$(date+%Y%m%d-%H%M%S).log"mkdir-p"$BASE/logs"{echo"batch 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";;*)echo"skip:$name"continue;;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")"doneecho"batch end:$(date)"}|tee-a"$LOG"

对应 unit:

[Unit] Description=Orin detector batch once at boot After=network-online.target docker.service Requires=docker.service [Service] Type=oneshot User=ubuntu Group=docker ExecStartPre=/bin/sleep 10 ExecStart=/home/ubuntu/orin-docker-detector/app/run_batch.sh [Install] WantedBy=multi-user.target

这种适合“上电处理一波文件”,不适合“一直盯着目录”。


11. 重启验收怎么做

配完后,别只看enable成功。真正验收是冷启动。

sudoreboot

重新登录后:

systemctl is-activedockersystemctl status orin-detector.service --no-pagerdockerps--filtername=orin-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 / 设备节点还没完全就绪,容器起来就退出,或者一推理就报错。

可按这个顺序处理:

  1. ExecStartPre里先sleep几秒到十几秒
  2. 容器加--restart unless-stopped,早期失败后还能再起来
  3. 看日志确认是不是 runtime / library 初始化失败

临时排查:

journalctl-uorin-detector.service-b--no-pagerdockerlogs orin-detectordmesg|rg-i'nvgpu|tegra|nvidia'|tail

如果几乎每次开机第一次必失败、第二次才好,优先加长 sleep,或把 unit 改成Restart=on-failuredocker 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.serviceExecStartPre里加一行调用,或者另做一个更早启动的 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-size=10m\--log-opt max-file=3\--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/done

done/output/也建议定期清理,或按日期分子目录。自启配好了却因为满盘挂掉,排障会很像“模型突然坏了”。


15. 常见问题

现象多见原因先查什么
开机后容器不在没 enable、容器名不对、首次没创建容器systemctl statusdocker ps -a
unit 失败,手动 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--filtername=orin-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 statusdocker logs
  • 至少做过一次真实reboot验收,不是只看 enable 成功

做到这些,板子才更接近“设备”,而不是“每次都要人上去敲命令的开发机”。


18. 小结

Orin 上做开机自启,核心不是多记几条 systemd 语法,而是把三件事对齐:

  1. Docker 先就绪
  2. 检测容器的启动方式固定(常驻看目录,或开机批处理一次)
  3. 路径、时序、日志能在无人值守时排障

上一篇解决“容器里能检测”,这篇解决“上电后还在检测”。两步都通了,后面再接相机、告警、前端,才有稳定底座。