ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Docker容器停止后启动:start、restart、run区别与排错

Docker容器停止后启动:start、restart、run区别与排错 在容器这一亩三分地上折腾久了你会发现真正高频的操作其实就那么几个而“把已经停掉的容器重新拉起来”绝对排得进前三。刚接触 Docker 的朋友最常见的场景就是昨天跑得好好的一个容器今天docker ps一看空空如也心里咯噔一下以为数据没了或者敲了exit从容器里退出来回头再想进去却发现容器状态已经变成 Exited。这时候你要做的不是重新docker run一个而是把原来那个已经停止的容器重新启动起来。这件事听起来简单但里面牵扯到状态判断、退出码含义、参数继承、信号处理、重启策略等一堆细节踩过坑的人都知道docker start只是最表面的那一层。这篇文章面向的是刚上手 Docker、能跑通docker run hello-world但一遇到容器停止就发懵的朋友。我会从“容器为什么会停”讲到“怎么判断它为什么停”再到“三种启动方式怎么选”“启动完进不去怎么办”“怎么让容器自己站起来”最后给你一张报错速查表和一次真实的排查复盘。全程都是可复制的命令和我在实际环境里验证过的结论不玩虚的。如果你手边正好有一个 Exited 状态的容器建议直接开个终端跟着敲效果比看一遍强很多。1. 先把“停止”这件事说透启动才有方向很多人对“容器停止”的理解是“关机了”其实不太准确。容器本质上是宿主机上的一个进程或者一组进程它的生命周期完全由容器内的主进程PID 1决定。主进程活着容器就是 Up主进程退出容器就变成 Exited。所以“启动一个停止的容器”这句话准确的说法是重新把容器内的主进程拉起来并把它的标准输出、标准输入重新接上。理解了这一点后面所有的现象都能解释得通。1.1 容器的 Exited、Paused、Created 三种“非运行”状态docker ps默认只显示正在运行的容器加-a才能看到全部。你在 STATUS 列会看到几种不同的“非运行”状态它们的处理方式完全不一样Created容器已经创建但主进程从来没启动过。常见于docker create出来的容器或者docker run过程中创建成功但启动失败。这种状态用docker start就能唤醒。Exited (n)主进程已经退出n 是退出码。这是最常见的状态也是本文的主角。docker start可以直接把它重新跑起来但要注意它是沿用创建时的配置重新执行主进程。Paused容器还在运行只是所有进程被冻结了。这个不是“停止”用的是docker unpausedocker start对它无效。我见过有朋友对 Paused 状态的容器执行docker start然后困惑为什么没反应。其实 Docker 会直接报错或者说“已经启动”因为容器的进程根本没退出只是被 SIGSTOP 冻住了。另外还有一个容易混淆的点docker stop之后容器是 Exiteddocker kill之后也是 Exited但两者退出码可能不同。前者会先发 SIGTERM 给主进程给它默认 10 秒时间做优雅退出超时才补一刀 SIGKILL后者直接 SIGKILL不给缓冲。这个差异在排查数据丢失类问题的时候非常关键后面第 6 节我会展开说。1.2 容器为什么会停下来主进程退出才是根本原因容器停止的原因千奇百怪但归根结底只有一句话主进程退出了。主进程退出又可以分为几类第一类是“任务型”容器的正常结束。比如你跑docker run ubuntu echo hello输出完 hello主进程echo结束容器自然 Exited (0)。这不是故障是设计如此。第二类是被外部信号打断。你敲了docker stop、docker kill或者宿主机重启、内存吃紧触发 OOM Killer都会向主进程发信号。这类情况的退出码通常是 1371289被 SIGKILL或者 14312815被 SIGTERM。第三类是应用自己崩了。配置文件写错、依赖的数据库连不上、端口被占用、证书过期都可能让主进程在启动几秒后直接退出退出码一般是 1 或者 2。第四类是容器创建时就没给主进程一个“活得下去”的命令。最典型的就是docker run -d ubuntu——你只说了用 ubuntu 镜像没说跑什么镜像默认的 CMD 是 bash而后台模式下 bash 没有可交互的终端读不到任何输入立刻退出。这种情况容器会进入“启动-退出-启动-退出”的循环你看到的是刚 start 起来docker ps里闪一下就没了。注意判断一个容器能不能被顺利启动先看它的启动命令是不是一个“长期驻留型”的进程。像 nginx、redis-server、mysqld 这类前台进程天然适合做容器主进程而 bash、sh 这种需要交互的 shell只有在-it交互模式下才活得下去。2. 动手之前三步体检法定位容器停止原因新手最容易犯的错是看到容器停了就急着docker start起来之后又挂反复几次浪费时间。正确的顺序是先搞清楚“它为什么停”再决定“怎么起”。我自己固定用三步体检法看状态、翻日志、查配置。整个过程不超过一分钟但能省掉后面反复试错的十分钟。2.1 第一步 docker ps -a 看状态与退出码第一步永远是这条命令别嫌简单docker ps -a重点看三列CONTAINER ID、NAMES、STATUS。STATUS 里的括号数字就是退出码它是最直接的线索。下面这张表是我整理的高频退出码含义建议存成便签退出码含义常见原因0正常退出任务型容器执行完毕或者主进程优雅处理了退出信号1应用级错误配置错误、依赖服务不可达、代码抛异常2命令用法错误启动命令参数写错、脚本语法错误126命令不可执行入口脚本没有执行权限127命令找不到入口脚本里引用的可执行文件不存在137被 SIGKILL 强杀OOM、docker kill、docker stop 超时后强杀139段错误程序内存越界常见于底层依赖不兼容143被 SIGTERM 终止docker stop 正常发送的终止信号这里解释一下 137 和 143 的来历Linux 里进程被信号杀死时退出码是 128 加上信号编号。SIGKILL 是 9所以 137SIGTERM 是 15所以 143。记住这个换算关系以后再看到 134SIGABRT、130CtrlC 的 SIGINT就不会懵了。如果你只记得容器名字不记得 ID用docker ps -a --filter name关键字过滤一下比肉眼翻一屏输出快得多。再进一步docker ps -a --format table {{.Names}}\t{{.Status}}\t{{.Image}}可以自定义输出列长名字的容器看起来清爽很多。2.2 第二步 docker logs 翻日志看它“临终遗言”退出码只能告诉你“死因大类”具体是哪个配置写错了还得看日志docker logs --tail 50 容器名 docker logs -t --since 10m 容器名--tail 50只看最后 50 行-t加上时间戳--since 10m只看最近 10 分钟的。排查启动类问题这三个参数基本够用。需要提醒的是docker logs读的是 Docker 帮你收集的 stdout/stderr。如果你的应用把日志写到了容器内的文件而不是标准输出docker logs会是空的。这时候有两个办法一是docker inspect找到日志文件路径用docker cp拷出来看二是直接改配置让应用输出到 stdout——这也是容器化应用的通用最佳实践。我遇到过一个很典型的例子某次跑 Elasticsearch 容器docker logs最后一行是max virtual memory areas vm.max_map_count [65530] is too low退出码 1。这就是典型的环境参数问题不是容器本身坏了。改宿主机内核参数后重新启动就正常了。如果当时不翻日志直接反复 start只会一遍遍失败。2.3 第三步 docker inspect 挖出配置与退出细节日志看不出来的时候docker inspect是终极武器。输出是一大坨 JSON不要硬看配合--format精准取值# 看退出码和错误信息 docker inspect --format {{.State.ExitCode}} {{.State.Error}} 容器名 # 看启动命令、入口点、工作目录 docker inspect --format {{.Config.Cmd}} | {{.Config.Entrypoint}} | {{.Config.WorkingDir}} 容器名 # 看端口映射和环境变量 docker inspect --format {{json .HostConfig.PortBindings}} 容器名 docker inspect --format {{json .Config.Env}} 容器名 # 看挂载 docker inspect --format {{json .Mounts}} 容器名 # 看重启策略 docker inspect --format {{.HostConfig.RestartPolicy.Name}} 容器名命令看起来长但用熟了比翻 JSON 快太多。尤其.State.Error字段很多“启动失败但日志为空”的情况答案就写在这里。比如挂载源目录不存在、端口冲突、网络不存在这类“容器还没跑起来就失败”的问题日志是不会有记录的只能靠 inspect。还有一个隐藏信息值得关注.State.StartedAt和.State.FinishedAt。把这两个时间减一下你就能知道容器活了多久。如果只活了两三秒基本可以断定是启动阶段就崩了如果活了几小时甚至几天才停那更像运行期的问题或者被外部停止了。3. 三种启动方式实操start、restart、run 到底怎么选搞清楚原因之后终于到了启动环节。但我要先泼一盆冷水Docker 里跟“启动”相关的命令有三个很多人用错了很多年。docker start、docker restart、docker run各自解决的是完全不同的问题用错了轻则没效果重则把数据搞丢。3.1 docker start唤醒一个已经存在的容器这是本文的正主。语法极其简单docker start 容器名或ID docker start -a 容器名 # 启动并把日志输出接到当前终端 docker start -ai 容器名 # 启动并附加 stdin可交互 docker start 容器A 容器B # 一次启动多个关键点在于docker start不会创建新容器它只是把已有容器的主进程重新拉起来所有配置端口映射、环境变量、挂载、网络完全沿用创建时的设定。这一点是理解它和docker run区别的核心。默认的docker start是后台启动不会把日志打到你的终端上。新手常犯的困惑是“我 start 完怎么什么都没显示是不是没成功”——用docker ps确认状态或者加-a把日志引到当前终端就能看到了docker start -a my-redis-a的副作用是它会占用你当前终端想脱离得按CtrlPCtrlQ不是CtrlCCtrlC会把容器停掉。这一点跟docker attach完全一致我在第 4 节会细说。-i参数则是把容器的标准输入接回来只有创建时带了-i的容器才有效。所以docker start -ai最适合的场景就是那些当初用docker run -it跑起来、后来用exit退出来的交互式容器比如一个 Ubuntu 调试环境。实操心得如果你创建容器时用了--rm那容器一旦停止就会被自动删除docker start是找不到它的。这是我见过新手最崩溃的场景之一辛苦装的软件全没了。调试类容器别加--rm等确认稳定了再考虑。3.2 docker restart运行中容器的原地重启docker restart和docker start的区别经常被忽视。docker restart的实际语义是“先 stop 再 start”docker restart 容器名 docker restart -t 30 容器名 # 给 30 秒优雅退出时间默认是 10 秒它和docker start最大的不同是docker start只对停止状态的容器有意义对运行中的容器执行会提示已经启动而docker restart对运行中和已停止的容器都有效。什么时候用 restart我总结了几种改了应用配置文件后需要重新加载容器内某个服务假死但进程还在环境变量没变但依赖的外部服务重启了需要重连。这时候 restart 比 start 更合适因为如果容器本来在跑docker start不会做任何事。-t参数值得单独说。它决定了 Docker 给容器多少秒做优雅退出超过就 SIGKILL。对于数据库类容器我一般会调到 30 甚至 60 秒。原因很简单像 MySQL、PostgreSQL 这类数据库在收到 SIGTERM 后需要时间刷脏页、关连接、写 redo log10 秒有时候真的不够强杀后恢复起来可能要做崩溃恢复甚至丢数据。这个参数我踩过坑某次给一个写入压力较大的数据库容器做 restart默认 10 秒直接被强杀重启后做了将近两分钟的崩溃恢复。改成 60 秒之后就没再出现这种情况。3.3 docker run 与 start 的本质区别别用错这是本文最想纠正的一个认知误区。很多人把docker run当成“启动容器”的通用命令其实docker run的完整语义是“创建一个新容器并启动它”docker run -d --name web -p 8080:80 nginx这条命令每次执行都会产生一个全新的容器。容器名重复会直接报错Conflict. The container name /web is already in use。如果你在容器里装过软件、改过配置、存过数据重新 run 一个相当于全部重来。所以正确的判断逻辑是这样的容器被删了或者从来没创建过 → 用docker run或docker createdocker start容器还在只是停了 → 用docker start容器在跑想让它重新加载 → 用docker restart这里有个很隐蔽的坑有些朋友发现docker run报名称冲突就顺手把旧容器docker rm掉再 run。如果旧容器里有没挂载出来的数据这一删就彻底找不回来了。我的做法是删任何容器之前先跑一遍docker inspect --format {{json .Mounts}} 容器名确认数据要么存在绑定挂载的宿主目录里要么存在命名卷里。匿名卷最危险因为它跟着容器走docker rm的时候如果不加-v卷虽然还在但已经成了“孤儿卷”得靠docker volume ls慢慢找。关于孤儿卷的清理第 7 节会讲到。4. 启动之后进不去attach 与 exec 的取舍容器启动成功后下一个高频需求就是“我要进去看看”。这时候你会遇到两个命令docker attach和docker exec。它们看起来都能进容器但行为差异极大用错了一个CtrlC就可能把生产容器干掉。4.1 docker attach 的正确姿势与 CtrlC 的坑docker attach的作用是“附加到容器主进程的标准输入输出上”docker attach 容器名注意它附加的是**主进程PID 1**的终端不是给你新开一个 shell。这意味着你在终端里敲的任何东西都会直接送给主进程你按CtrlC发出的 SIGINT也是发给主进程的。这就是那个经典事故的来源你 attach 到一个 nginx 容器上想退出习惯性地按了CtrlC——nginx 收到 SIGINT 后直接退出容器 Exited。你以为只是“退出终端”实际上是“关掉服务”。正确的脱离方式是CtrlP然后CtrlQ这叫 detach容器继续运行。这个组合键我建议拿张便利贴贴在显示器边上直到形成肌肉记忆。那 attach 还有什么用主要两个场景一是调试那些需要交互输入的程序比如跑一个 Python REPL 或者某个需要你确认的启动脚本二是观察主进程的实时输出它比docker logs -f更“原始”能看到控制字符和交互提示。4.2 docker exec -it 才是日常首选绝大多数时候你要的不是 attach而是 execdocker exec -it 容器名 /bin/bash # Alpine 镜像没有 bash用 sh docker exec -it 容器名 /bin/shdocker exec的语义是“在容器内启动一个新进程”。你在里面敲的所有命令、包括exit和CtrlC影响的只是你自己开的这个 shell主进程完全不受干扰。这是它和 attach 最本质的区别。几个我认为很值钱的 exec 技巧# 不进去直接执行一条命令 docker exec 容器名 ls -al /app # 以 root 身份进有些容器默认用户权限不足 docker exec -u root -it 容器名 /bin/bash # 进去排查网络但很多精简镜像连 ping 都没有 docker exec -it 容器名 cat /etc/resolv.conf docker exec -it 容器名 cat /etc/hosts # 看容器内进程树确认 PID 1 是谁 docker exec -it 容器名 ps aux最后那条特别有用。当你不确定容器的主进程是什么时进去ps aux看一眼就能明白为什么它容易退出。如果 PID 1 是/bin/bash而没有任何前台任务那这个容器必然活不长。注意docker exec只能对运行中的容器使用。容器是 Exited 状态时执行会报Error response from daemon: Container xxx is not running。必须先 start 再 exec。4.3 启动后立刻又变成 Exited 的四种典型解法这是新手遇到最多的“玄学”问题docker start明明没报错docker ps里就是看不到。下面四种解法覆盖了我遇到的绝大部分情况。第一种容器创建时没有前台进程。典型是docker run -d ubuntu或者docker run -d --name test centos。解法是别指望这种容器能一直活着重新创建一个带常驻命令的docker run -d --name keepalive ubuntu tail -f /dev/nulltail -f /dev/null是社区里最常用的“占位进程”什么也不干但永不退出。等进去装完东西、调完配置再把它 commit 成镜像正式启动时换成真正的启动命令。第二种交互式容器没有 stdin。你当初用docker run -it --name devbox ubuntu bash创建exit之后容器停止。这时如果直接docker start devboxbash 立刻发现没有交互终端秒退。正确做法docker start -ai devbox第三种应用启动就报错退出。那就回到第 2 节的三步体检法看退出码、翻日志。这个没有捷径。第四种容器内的启动脚本引用了不存在的文件。这种报错通常在日志里表现为no such file or directory或者退出码 127。有个隐蔽的子情况是文件存在但没有执行权限退出码 126。还有更隐蔽的脚本是在 Windows 上编辑的行尾是 CRLF 而不是 LFLinux 下会报/bin/sh^M: bad interpreter。这时候用sed -i s/\r$// 脚本名处理一下就好或者编辑器里把换行符改成 LF。5. 让容器自己站起来重启策略与配置微调手动 start 能解决问题但半夜服务器重启、容器 OOM 挂掉这类场景你不可能每次都守着敲命令。Docker 提供了重启策略让容器在退出后自动拉起这是让服务“自愈”的基础手段。5.1 --restart 四种策略怎么选创建容器时通过--restart指定一共四个值策略行为适用场景no不自动重启默认一次性任务、调试容器on-failure[:N]非 0 退出码时重启可限制次数有明确失败语义的批处理任务always无论退出码是什么都重启长期驻留服务如 Web、数据库unless-stopped同 always但手动 stop 后不再拉起大部分生产服务的推荐值always和unless-stopped的区别很微妙但实际用起来差别很大。用always时你手动docker stop一个容器然后重启宿主机 Docker 服务这个容器会被重新拉起来——因为你手动 stop 的意图被忘记了。而unless-stopped会记住“是你主动停的”Docker 重启后它保持停止。我个人的选择是无人值守的服务用unless-stopped因为我不想在维护窗口手动停掉的服务被偷偷拉起来而那些希望无论如何都活着的核心服务才用always。on-failure后面可以跟次数比如on-failure:5表示最多重启 5 次。这个在防止“启动即崩溃”的容器无限重启上很有用能避免它把宿主机 CPU 和磁盘日志打爆。5.2 已经创建好的容器如何补上重启策略很多朋友的问题是“容器早就创建好了当时忘了加--restart”。不用删了重建docker update可以直接改docker update --restartunless-stopped 容器名 # 一次改多个 docker update --restartalways 容器A 容器B 容器Cdocker update在容器运行中和停止状态下都能执行改的是 HostConfig 里的配置不用重启容器就生效策略本身要等到下次退出时才起作用。这一点非常省事。docker update还能改资源限制比如内存和 CPUdocker update --memory 512m --memory-swap 1g 容器名 docker update --cpus 1.5 容器名内存限制这里有个坑要说清楚JVM 类应用在容器里如果不感知 cgroup 限制会按宿主机的内存去算堆大小很容易被 OOM Killer 干掉表现就是退出码 137。所以跑 Java 容器时内存限制和-XX:MaxRAMPercentage这类参数要配套设置不能只改一边。5.3 想改端口、环境变量怎么办只能重建或 commit这是docker start最大的局限也是新手最容易失望的地方容器一旦创建端口映射、环境变量、挂载路径就不能通过 start 修改了。比如你当初docker run -d -p 8080:80 nginx现在想改成 9090docker start做不到docker update也做不到。可选的路径有三条路径一重新 run 一个新容器。前提是你的数据都在宿主目录或命名卷里配置都通过挂载传入。这是最干净的做法我推荐 90% 的情况走这条。路径二commit 成镜像再 run。如果容器里有些手动装的软件不想重来docker commit 旧容器名 临时镜像名:tag docker run -d --name 新容器 -p 9090:80 临时镜像名:tagcommit 会把容器的可写层打包成新镜像。缺点是镜像会变大、构建过程不可复现、容易积累垃圾文件。我一般只在救急时用。路径三备份容器数据后彻底重建。写个 Dockerfile把改动固化下来以后所有环境都从 Dockerfile 构建。这才是长期方案。顺便说一个容易被当成“捷径”的野路子直接改/var/lib/docker/containers/id/hostconfig.json然后重启 Docker 服务。我不建议这么干因为不同版本 Docker 的容器元数据结构会变改错了轻则容器起不来重则整个容器目录状态不一致。为了省几分钟冒这个险不划算。6. 高频报错速查表与真实排查复盘前面讲了原理和方法论这一节全是实战。我把这几年的报错记录整理成速查表再挑一个完整案例讲讲排查思路是怎么走的。6.1 启动相关报错速查表报错信息触发时机原因与解法Container is not runningexec / attach 时目标容器已停止先docker start再操作No such containerstart / inspect 时容器名或 ID 写错或容器已被--rm自动删除Conflict. The container name is already in userun 时同名容器存在改用docker start或先docker rm旧容器port is already allocatedrun 时宿主端口被占用ss -lntp找到占用进程或换端口driver failed programming external connectivityrun / start 时Docker 网络规则异常重启 dockerd 通常能恢复Cannot connect to the Docker daemon所有命令Docker 服务没起来检查服务状态或 Docker Desktopbind: address already in userun 时同上端口冲突注意区分 IPv4/IPv6 监听no space left on devicerun / start 时磁盘满了docker system df看占用清理无用镜像和卷permission denied挂载目录相关SELinux 或宿主目录权限看看是否该加:z或:Z标签特别说一下倒数第二条。容器跑了一段时间后磁盘告警是很常见的事因为镜像层、日志文件、孤儿卷都会慢慢堆积。docker system df -v能看到详细的占用分布docker system prune可以清理停止的容器、悬空镜像和未使用的网络。但注意别随手docker volume prune它会删掉所有没有容器引用的卷如果里面有你的业务数据就麻烦了。6.2 一次 RabbitMQ 启动失败的排查全过程分享一个我印象比较深的案例。环境是 Ubuntu用 Docker 跑 RabbitMQ某天服务器例行重启后容器再也没有自己起来。第一步docker ps -a看状态结果是Exited (1)。重启策略我当初设的是no所以宿主机重启后它不会自动拉起这解释了“为什么没起来”。但退出码是 1说明它临死前还遇到了应用级错误。第二步docker logs --tail 100 rabbit最后几行是error, cannot access vhost /, check rabbitmq logs Error: unable to connect to node rabbitxxx这个报错信息指向性不算强但可以确定是 Erlang 节点启动或数据加载阶段出了问题不是配置语法错误。第三步docker inspect重点看挂载和退出时的状态docker inspect --format {{json .Mounts}} rabbit docker inspect --format {{.State.ExitCode}} | {{.State.Error}} | {{.State.StartedAt}} | {{.State.FinishedAt}} rabbit挂载显示数据目录是一个命名卷容器没有删数据理论上还在。退出时间是启动后约 8 秒说明是启动阶段就崩的。第四步进不去容器没运行那就docker start一下再看看结果又立刻退出退出码还是 1。于是我用docker run临时起了一个同版本镜像把那个命名卷挂进去docker run --rm -it -v rabbit_data:/var/lib/rabbitmq rabbitmq:3.11-management bash进去之后翻/var/lib/rabbitmq目录发现问题了宿主机磁盘曾经满过一次导致 Erlang 的mnesia数据目录里有个.DCD文件是 0 字节的节点启动时校验失败直接退出。解决办法是把损坏的节点数据文件挪走让 RabbitMQ 重新初始化节点元数据vhost 和用户再重新配一遍。数据虽然做了备份但没用上因为这次坏的是节点级的元数据而不是消息数据。这次排查给我的教训有三条一是重启策略一定要设哪怕是on-failure:3也比no强二是宿主机磁盘监控必须做Docker 环境下磁盘吃紧的后果往往不是“变慢”而是“数据损坏”三是排查停止类问题一定要看StartedAt和FinishedAt的差值它能帮你判断是启动即崩还是运行期崩溃方向完全不同。6.3 Docker Desktop 本身没起来怎么办前面讲的是容器起不来但还有一种情况是 Docker 引擎自己没起来那所有容器自然都是停止状态。Windows 和 macOS 上用 Docker Desktop 的朋友遇到这个的概率不低。典型症状是打开 Docker Desktop 一直转圈或者干脆弹出一句“virtualisation support wasnt detected”再或者提示 WSL 版本需要更新。排查顺序我一般是第一确认 BIOS 里的虚拟化开关是否打开。这听起来很基础但换过主板电池、重置过 BIOS 的机器真的会把它关掉。这个开关的具体名字各家主板叫法不同通常在 CPU 相关菜单里。第二确认后端选的是 WSL 2 还是 Hyper-V。Windows 家庭版默认只能走 WSL 2需要在“启用或关闭 Windows 功能”里勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”然后重启。很多人只勾了一个结果就是启动失败。第三检查 WSL 版本。在命令行执行wsl --update把它升到最新再wsl --status看看默认版本是不是 2。有两个以上 Linux 发行版时还要显式设置默认版本。第四看 Docker Desktop 自己的日志。托盘图标右键里能打开日志目录里面会写清楚初始化卡在哪一步。比对着界面转圈猜要高效得多。第五如果前面都对还是不行重启 Docker 服务或者干脆重启机器。Docker Desktop 的虚拟机状态偶尔会进入一种奇怪的状态重启是最省事的解法。在 Linux 上情况不太一样通常用系统服务管理工具看状态和日志就够了。关键是要区分“服务没启动”和“服务启动了但 socket 权限不对”后者表现为permission denied while trying to connect to the Docker daemon socket把用户加入 docker 组并重新登录会话通常能解决。7. 我个人的几条习惯与经验写到这该讲的命令和原理基本覆盖完了。最后分享几条我自己养成的习惯它们没什么技术含量但实实在在省了我很多时间。7.1 命名、日志与清理给容器起名字。不用--name的话Docker 会给你随机生成一个比如pedantic_morse这样的名字。当时可能觉得无所谓等你docker ps -a看到二十几个随机名字时就知道痛苦了。命名规范我一般用服务-环境-序号比如redis-dev-1、web-prod-2一眼就知道是什么。限制日志大小。Docker 默认的 json-file 日志驱动不限制文件大小一个话痨应用跑上几个月能把磁盘写满。创建容器时加两个参数就能管住docker run -d \ --log-opt max-size50m \ --log-opt max-file3 \ --name web nginx意思是单个日志文件最大 50MB最多保留 3 个滚动文件总共不超过 150MB。这两个参数我每个容器都会加属于“加一次受益终身”的类型。已经创建好的容器改起来麻烦得改 daemon 的全局配置所以最好一开始就带上。定期清理但别乱清理。我一般每周跑一次docker system df看占用然后用docker image prune清理悬空镜像那些none:none的层。容器和卷的清理我很少用自动命令都是手动确认后再删。原因说过卷里可能躺着数据。7.2 给小白的行动清单如果你刚开始学我建议按这个顺序练一遍每一步都亲手敲跑一个docker run -d --name demo nginx确认 Up 状态。用docker stop demo停掉再用docker ps -a看它的退出码。用docker start demo唤醒再用docker exec -it demo /bin/bash进去看看。故意制造一个失败docker run --name fail alpine sh -c exit 1然后练习用日志和 inspect 定位原因。给demo加上docker update --restartunless-stopped demo然后重启一次 Docker 服务观察它的行为。最后docker rm -f掉所有测试容器跑一遍docker system df看看空间变化。这六步做完你对“启动已经停止的容器”这件事的理解会比看十篇文章都扎实。因为每一个命令的结果都是你亲眼看到的出错了也是自己解决的。再补一个小技巧如果你的终端用久了想不起来某个容器当初是怎么创建的可以装一个社区维护的小工具它能反查出容器的完整docker run命令包括所有端口、挂载、环境变量和重启策略。这对复现和迁移特别有用比自己对着 inspect 输出拼命令快得多。当然你也可以自己拿前面那几段docker inspect --format拼一个脚本出来二三十行就够了。容器这东西的学习曲线有点像骑自行车看别人骑觉得简单自己上去总怕摔。但真的摔两次——比如误CtrlC关掉一个服务、比如忘了--restart导致服务器重启后服务全挂——之后就会记住。我到现在这些坑基本都踩全了现在看到Exited反而觉得很平静因为知道它一定能被拉起来只要找对原因。
返回列表