资讯动态

Docker基本命令实战指南:从容器管理到故障排查

发布时间:2026/10/21 10:21:46 来源:尧图企业网站定制
1. 为什么你还是得把docker基本命令玩明白做了几年后端和运维相关的工作我带过不少刚接触容器化的同事。大家普遍有个心态先上编排、先搞交付基础命令简单看看就行。但真到线上排查问题的时候会发现那些基础命令才是兜底的本事比如容器异常退出要捞日志镜像体积异常要逐层分析数据没持久化要临时挂载目录这时候能快速敲出正确的命令比什么都管用。很多人以为docker基本命令就是背一背“run”“ps”“exec”其实远远不止。你缺的不是命令列表而是一套围绕命令背后的理解框架容器生命周期是怎么流转的镜像和容器到底什么关系数据卷怎么挂才安全网络模式选错了会踩什么坑。这篇文章不打算做成命令大全而是按我实际工作里最常用的场景来拆。你会发现真正决定你能不能熟练用docker的不是记忆命令本身而是遇到问题时知道该用哪条命令去看状态出了错误提示能定位到哪一层。文章里所有命令我都按顺序跑过写下来的都是可复现的适合正在学docker的开发者也适合已经用了一阵但总感觉基础不牢的人对照着查漏补缺。2. 绕不开的容器生命周期ps、run、stop、rm的日常用法2.1 用ps看容器状态别只盯着第一列的CONTAINER ID先说说最基础的docker ps。大多数教程会让你加-a参数查看所有容器包括已停止的这确实是最常用的方式。但你得清楚docker ps显示的每个字段分别代表什么因为在排查问题时这些字段能帮你快速缩小范围。比如STATUS字段它不只是告诉你容器在不在跑。Up 5 minutes表示正常运行了5分钟Exited (0)表示正常退出比如执行完一次性任务的容器Exited (137)通常意味着被强制杀死常见原因是内存不足触发OOMRestarting状态说明容器在反复重启这时候优先去看日志而不是反复重启容器。我见过有人看到Exited (0)还以为是故障其实对跑一次性脚本的容器来说这是正常结束。PORTS字段还有个容易被忽略的细节0.0.0.0:8080-80/tcp表示宿主机所有网卡的8080端口会转发到容器的80端口访问时用宿主机IP加8080就行。但如果你看到的是127.0.0.1:8080-80/tcp那只有本机能访问外部设备是连不上的。部署测试环境时经常在这里栽跟头明明容器在跑外部就是访问不了。使用docker ps常用筛选参数组合docker ps -a查看所有容器包括停止状态的docker ps -q只显示容器ID不会显示其他字段docker ps --filter statusexited只看已退出的容器docker ps --format table {{.Names}}\t{{.Status}}自定义输出列只显示容器名和状态--format这个参数在容器多的时候非常实用配合--filter可以快速找到异常容器比如查所有退出状态的容器名然后批量清理。在脚本里处理容器时也经常配合-q用比如批量停止所有容器先取ID列表再做停止操作。2.2 run命令的三个关键参数名称、端口和重启策略docker run是使用频率最高的命令但很多人都是随手敲一串参数出了问题也不知道怎么排查。我建议一定要先明确三个参数--name、-p、--restart。给容器起名字是习惯问题却非常影响排查效率。不指定名字的话docker会随机生成一个名字比如focused_jackson这种日志和状态里全是这种名字人脑很难记住哪个容器对应哪个服务。--name myweb明确命名后后续操作docker logs myweb、docker restart myweb都直接用名字不用去查ID。端口映射-p 8080:80的含义要记清楚宿主机8080端口对应容器80端口。这里有个常见误区-p 80:80如果宿主机端口已经被占用执行时不会报错但运行后外部永远访问不了因为端口绑定失败了。检查方法是用docker port 容器名查看实际绑定情况能看到宿主机端口和容器的映射关系。--restart参数决定容器退出后是否自动重启生产环境里这个参数比你想的重要。比如--restartalways表示容器无论怎么退出都会自动重启适合常驻服务--restartunless-stopped表示容器被手动停止时不会自动重启但如果是意外退出会拉起适合大多数Web服务--restarton-failure:3则是在失败时最多重启3次适合执行任务的容器。需要注意的是--restartalways会带来一个问题如果容器一直启动失败它会无限重启日志里全是报错。一个比较完整的实际启动命令可以这样写docker run -d \ --name nginx-web \ -p 8080:80 \ --restartunless-stopped \ -v /opt/nginx/html:/usr/share/nginx/html \ nginx:1.24-d表示后台运行-v是把宿主机目录挂到容器里这样修改宿主机文件就能让容器内的服务直接生效。第一次使用时会发现执行后docker只返回了一串容器ID没有任何输出这是正常现象因为-d已经把容器放到后台了后续所有信息都得靠docker ps和docker logs来查。2.3 停止和删除容器rm之前想清楚数据还在不在容器停止和删除看起来简单但真要清理环境的时候经常出错。核心要理解stop只是停止容器进程不会删除容器本身和挂载的数据而rm是删除容器对象。实际操作里最常见的场景是这样容器状态异常你直接执行docker rm 容器名结果提示“You cannot remove a running container”这是正常的保护机制需要先docker stop再docker rm。如果想省一步可以执行docker rm -f 容器名这会强制停止并删除容器但有个副作用容器内的进程来不及优雅退出有些需要保存状态的应用会丢数据所以生产环境不建议直接用-f。还有一个批量清理的常用组合docker stop $(docker ps -q) docker rm $(docker ps -aq)这段命令先把所有运行中容器停止再把所有容器删除本地环境做清理时非常高效。但放到生产环境前一定要确认清楚这是一键清空所有容器的操作。相比之下docker system prune -a是更综合的清理命令它会把所有未被使用的镜像、停止的容器、未使用的网络和构建缓存全部清掉直接释放磁盘空间。执行前docker会提示你确认因为它会删除所有不在运行态的镜像和容器。关于docker rm -v这个参数是在删除容器的同时删除它关联的匿名卷。有些容器比如数据库会把数据写到匿名卷里如果只执行docker rm这些卷会变成悬空卷残留在磁盘上占用空间。想查看哪些卷还残留可以执行docker volume ls --filter danglingtrue。3. 镜像管理没你想象的那么简单pull、tag、build、rmi3.1 理解镜像和容器的关系模板与实例要管理好镜像得先理解镜像和容器的关系。打个比方镜像就是安装包容器就是安装完之后的运行实例。你从仓库拉下来的是镜像但真正在跑的是容器。同一个镜像可以启动多个容器每个容器有自己的可写层互不影响。这个关系在删除镜像时会体现得很明显。当你尝试删除一个正在被容器使用的镜像时会得到错误提示因为镜像不可删除必须先删除引用它的容器。有些时候你把容器删了但镜像还是删不掉提示“image is being used by stopped container”原因就是还有已停止但未删除的容器在占用镜像。这时通过docker ps -a --filter ancestor镜像名可以查哪些容器引用这个镜像清理掉之后再执行docker rmi就不会报错了。docker rmi也可以支持多个镜像同时删除比如docker rmi 镜像A 镜像B按空格分隔即可。如果只是删掉不再使用的镜像推荐用docker image prune它只会清理悬空镜像也就是没有被任何容器使用且没有标签的镜像比rmi更安全。3.2 tag命名规范仓库地址、镜像名和版本号的关系docker tag是给镜像打标签的命令但很多人不清楚它和重命名的区别。实际上docker tag并不会复制镜像层它只是给同一个镜像添加一个引用名你可以理解为给文件建了一个快捷方式。所以执行docker tag 原镜像名 新镜像名后同一个镜像会有两个标签镜像ID完全相同。在生产环境里tag的命名规范尤其重要。标准格式是仓库地址/命名空间/镜像名:版本号比如registry.example.com/myteam/nginx:1.24。如果你只是在本地玩可以简化成nginx:v1这样的格式。但一旦涉及多人协作或私服推送建议从第一天就养成完整tag的习惯。常见的信息丢失场景是这样的镜像打错tag后直接push私服仓库里出现一堆nginx这种不带版本号的镜像等要回滚版本时发现根本不知道哪个对应哪个只能靠时间去猜。规范的tag规则可以自定义比如按日期加版本号也可以按环境标识关键是整个团队保持一致。还有一个docker tag的妙用本地开发的镜像想推送到私服需要先打个带私服地址的tag才能push。比如本地有个myapp:latest私服地址是registry.example.com执行docker tag myapp:latest registry.example.com/myteam/myapp:v1.0.0这样就新建了一个指向同一镜像的带私服地址的标签再执行docker push registry.example.com/myteam/myapp:v1.0.0就能推送成功。3.3 build命令的必要参数Dockerfile和-tdocker build是把Dockerfile构建成镜像的命令日常使用里最需要养成习惯的是-t参数和镜像上下文目录。-t表示给构建出来的镜像打标签比如docker build -t myweb:1.0 .最后的.表示构建上下文是当前目录Dockerfile会从这个目录里找。这里有个容易忽略的点构建上下文不只是Dockerfile所需的文件它包含当前目录下的所有文件会作为上下文打包发给docker引擎。如果目录里有个几百兆的无用文件每次构建都会因为打包上下文而变得很慢。以前有个同事在项目根目录放了个数据库备份文件构建一次要几分钟每次排查都找不到原因。解决办法是在项目里加.dockerignore文件和.gitignore类似把不需要的文件排除掉比如node_modules target *.log .git这样构建时这些文件就不会被包含进上下文。实际项目里优化构建速度第一步都是检查.dockerignore里有没有把依赖目录和大文件排除掉。docker build还有两个实用参数--no-cache忽略缓存强制重新构建适合构建环境里缓存了旧的依赖时使用--pull会强制拉取最新的基础镜像适合基础镜像有安全更新时使用。本地调试时常用--no-cache排除缓存导致的构建结果异常。3.4 镜像体积检查从本地到仓库的报错排查镜像体积和磁盘占用是生产环境最常见的排查点。docker images会显示每个镜像的大小但这里的体积是镜像层的总和可能有共享层的多个镜像会分别显示完整大小实际磁盘占用比总和要小。检查本地镜像常用的命令docker images docker system df docker image ls --format table {{.Repository}}\t{{.Tag}}\t{{.Size}}docker system df会显示镜像、容器、卷和构建缓存各自占用的空间对于快速定位磁盘占用很有用。之前遇到磁盘告警一条docker system df发现构建缓存占了将近20GB执行docker builder prune后空间立刻释放。构建缓存的清理往往被忽视但它实际占用可能比镜像还大。推送到仓库时也有一个常见的坑镜像大小超过了私服限制报错提示一般是“blob unknown to registry”或者“denied: requested access to the resource is denied”这类信息往往和镜像本身无关除非你已经打过私服的tag。实际的限制来源要看私服的配置有些私服对层大小有上限这时需要压缩基础镜像或拆分镜像层来规避。4. 进入容器的正确方式exec、cp、logs4.1 exec执行命令但bash和sh之间别搞混进入容器的标准命令是docker exec常用形式是docker exec -it 容器名 bash这里有两个基础但关键的细节。第一个是-it参数-i表示保持标准输入打开-t表示分配一个伪终端。只加-i的话命令能执行但交互界面会缺失使用vim之类需要终端的程序时会出问题。第二个是bash和sh的区别很多基础镜像比如基于alpine的里面只有sh没有bash直接执行docker exec -it 容器名 bash会提示找不到bash改用sh即可。容器里没有想要的操作命令也很常见。比如想用curl调试接口但容器里没装。这时候有两个思路一个是直接在宿主机上访问容器映射出来的端口另一个是在宿主机用docker exec加上容器里的可用工具比如docker exec 容器名 wget -qO- http://127.0.0.1:8080/health。docker exec有一个被低估的场景当容器内应用崩溃但容器没退出时可以用它来检查进程状态。执行docker exec 容器名 ps aux能看到容器内部的进程列表确认应用进程是否还在。如果进去之后看到进程还在但服务访问不了那问题大概率出在端口绑定或网络配置上而不是应用本身。4.2 cp命令宿主机和容器互传文件不落盘调试时经常需要在宿主机和容器之间传文件比如覆盖配置文件、导出日志或把宿主机上的修复脚本拷进容器。docker cp可以直接实现这个需求无需在容器里额外装任何工具。基本用法docker cp 宿主机文件路径 容器名:容器内路径 docker cp 容器名:容器内路径 宿主机文件路径两个方向的语义都是一样的前一个参数是源后一个参数是目标。执行docker cp ./app.conf myweb:/etc/nginx/conf.d/能把配置文件拷入容器然后执行docker exec myweb nginx -s reload让配置生效。一个任何时候都要注意的点docker cp拷贝后容器内文件的所有者和权限不会自动适配。如果宿主机的文件权限是1000容器内进程所有者是root可能会因为权限不足报错。拷完文件之后执行docker exec 容器名 chown -R root:root /etc/nginx/conf.d/这类命令来修正所有者。docker cp适合小文件应急传输但批量大文件或频繁传输的场景更好的方案是直接在docker run时挂载目录宿主机上操作文件容器内即时可见不需要进入容器操作。4.3 logs命令排查的第一入口也是被用得最糙的docker logs是排查异常时第一时间要用的命令。新版docker默认只输出stdout和stderr所以应用日志要输出到这两个通道而不是写文件否则docker logs什么都看不到。常用的logs参数docker logs -f 容器名 docker logs --tail 100 容器名 docker logs --since 30m 容器名 docker logs --until 2026-01-01T10:00:00 容器名-f是跟随输出类似tail的-f实时刷新日志--tail只显示最近N行--since显示某时间点之后的日志支持30m、2h这类相对时间。这些参数能大幅提升定位日志的效率。比如应用报错但是翻了几千行日志也找不到问题只执行docker logs --tail 200 容器名就能先看最后的输出再决定是否需要加--since缩小范围。日志在容器频繁重启时会非常难排查因为每次重启后之前的日志可能已经没了。docker默认日志驱动是json-file容器删除后日志也随之删除。所以生产环境建议把日志驱动改为外部存储或文件采集方案比如--log-driver local或者把日志目录挂载到宿主机。一个反向使用docker logs的思路应用启动失败的报错通常会在前几十行日志里直接执行docker logs 容器名 21 | head -n 50能看到启动阶段的关键信息比全量日志慢慢翻高效得多。5. 数据持久化volume和bind mount的取舍5.1 为什么容器里的数据会消失写过一次才发现容器数据不持久化的坑用docker run启动一个数据库容器在里面建了表插了数据然后docker stop、docker rm再启动一个新容器发现数据全没了。这个问题的根源是容器运行期间写入的数据都在容器的可写层里容器一删可写层跟着一起消失。理解这个机制是搞清楚volume和bind mount的基础。容器文件系统分两层只读的镜像层和可写层。镜像层不会因为容器删除而丢失但可写层会。想持久化数据必须让应用写的数据越过可写层落到宿主机或其他存储上。5.2 volume的匿名卷和命名卷一个让我反复踩坑的区别volume是docker负责管理的存储空间它独立于容器生命周期存在。有两种创建方式匿名卷和命名卷。匿名卷是在docker run里只指定容器内路径的写法比如-v /var/lib/mysql。docker会自动创建一个随机名字的卷并挂载到容器的/var/lib/mysql目录。但匿名卷很难管理因为你不知道random出来的名字对应哪个容器需要通过docker inspect去反查哪个卷被哪些容器引用了。命名卷是显式指定卷名的写法比如-v mysql-data:/var/lib/mysql它创建了一个叫mysql-data的卷。这样维护起来清晰太多备份和恢复也方便。查看所有卷docker volume ls docker volume inspect mysql-data关键的区别是绑定挂载和命名的volume使用方式不同。命名卷的挂载会复制镜像中该目录下已有的文件到卷里初次挂载时这一点对web应用部署非常省事而bind mount是直接把宿主机目录挂进去镜像里该目录的内容会被覆盖没有复制过程。5.3 用-v还是--mount我的实际建议docker提供-v和--mount两种挂载语法。-v写法简练/宿主机路径:/容器路径格式直观--mount则用键值对明确指定类型、源、目标可读性更好。实际使用中简单场景我用-v复杂场景用--mountdocker run -d --name web -v /opt/html:/usr/share/nginx/html:ro nginx docker run -d --name mysql \ --mount typevolume,sourcemysql-data,target/var/lib/mysql \ --mount typebind,source/opt/backup,target/backup \ mysql:8后者的每个参数都一目了然避免因为-v写法中冒号里还混着权限标志导致误写。比如-v /opt/html:/usr/share/nginx/html:ro最后的ro是只读挂载缺了这个权限标志后容器内应用会尝试写宿主机的目录排查半天才发现问题。还有一个经常被忽视的地方bind mount的宿主机目录不存在时docker会自动创建但创建的目录默认所有者是root。如果容器进程是以普通用户运行的就会出现权限拒绝。不信你可以跑一个有独立用户的应用挂载一个宿主机空目录进去一写文件就报Permission denied。解决办法是在宿主机上先手动mkdir并设置好权限再挂载。5.4 卷备份与迁移用临时容器做最笨也最稳的事卷备份没有一条内置命令但可以用临时容器挂载卷并打包目录的方式实现。以mysql-data卷为例docker run --rm \ -v mysql-data:/var/lib/mysql \ -v /opt/backup:/backup \ ubuntu \ tar czf /backup/mysql-data.tar.gz -C /var/lib/mysql .这段命令启动了一个临时ubuntu容器把命名卷挂到/var/lib/mysql把宿主机备份目录挂到/backup容器里执行tar打包--rm确保任务完成后自动删除容器。反过来恢复备份也是类似思路把tar解压到挂载的卷目录里即可。--rm这个参数特别适合这种一次性任务容器容器只执行一次打包任务跑完就没了不会残留停止状态的容器。日常调试中也可以用--rm开一个临时工具容器比如docker run --rm -it ubuntu bash退出后自动清理非常干净。6. 网络模式的取舍bridge和host的生产教训6.1 bridge网络是默认但容器间访问没那么随意docker默认创建一个bridge网络容器默认接入这个网络通过NAT方式访问外部。两个容器之间虽然在同一网络但如果不是通过容器名访问而是直接访问对方的IP可能因为容器重启IP变化而出问题。同一个bridge网络里的容器之间通过容器名可以直接互通。比如一个应用容器要连数据库容器只需在应用配置里把数据库连接地址写成数据库容器名加端口比如jdbc:mysql://mysql-container:3306docker内置的DNS解析会自动把容器名解析成对应IP。但容器名解析只在同一个自定义网络内生效默认bridge网络中容器名解析支持较有限论而且生产环境经常会创建多个自定义网络来隔离不同服务。比如某个连接数据库的容器网络接入错误没有被加到数据库容器所在的自定义网络里配置写的还是容器名就会一直连接超时。常用网络命令docker network ls docker network inspect 网络名 docker network create --driver bridge my-net docker network connect my-net 容器名 docker network disconnect 网络名 容器名6.2 host网络的性能和隔离host网络模式会让容器直接使用宿主机网络栈没有端口映射的概念容器内监听某个端口后宿主机同样端口直接被占用。这种模式的性能损耗小延迟低适合对延迟敏感的服务。但host模式的隔离性差容器内监听任何端口都会直接暴露在宿主机网络上如果宿主机已经有服务监听了同一端口就会冲突。比如宿主机上已经跑了一个nginx监听80你再启动一个host模式的nginx容器启动时不一定报错实际访问就时好时坏日志里也是端口被占用的错误。所以我的建议是默认都用bridge网络通过端口映射暴露服务只有压测、性能敏感型应用或特殊网络需求时才考虑host模式。大多数公司跑常规Web服务bridge网络已经足够。6.3 为什么容器连不上外网但宿主机可以这是一个非常典型的排查场景宿主机能正常访问网络但容器里ping外网或下载依赖就超时。常见原因有三个第一个是宿主机的IP转发没开启。bridge网络模式下容器访问外网依赖iptables NAT和IP转发如果/proc/sys/net/ipv4/ip_forward是0容器就出不去。检查方法sysctl net.ipv4.ip_forward输出为1表示正常为0则执行sysctl -w net.ipv4.ip_forward1并写入配置文件持久化。第二个是DNS配置问题。容器里默认使用docker分配的DNS如果宿主机的DNS配置异常或不通容器解析域名就会卡住。这时可以在容器里执行cat /etc/resolv.conf查看快速验证方式是按固定IP跑一个临时容器并指定DNSdocker run --rm -it --dns 223.5.5.5 ubuntu bash第三个是防火墙规则拦截了容器网段的流量。bridge网络的容器IP段默认是172.17.0.0/16如果宿主机防火墙设了DROP规则容器访问外网可能被拒绝。排查时临时关闭防火墙验证然后按需放行对应网段。7. 容器信息洞察inspect、stats、top和events7.1 inspect命令把容器的隐私全部挖出来docker inspect是排查问题时信息量最大的命令没有之一。它输出容器完整的配置、状态、网络、挂载、日志配置等JSON信息内容很多关键是要会用过滤筛选。常用场景举例查看容器IP地址docker inspect -f {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} 容器名查看容器挂载的卷和宿主机路径docker inspect -f {{range .Mounts}}{{.Source}} - {{.Destination}}{{end}} 容器名查看容器的重启次数docker inspect -f {{.RestartCount}} 容器名查看容器环境变量docker inspect -f {{range .Config.Env}}{{println .}}{{end}} 容器名这些过滤表达式虽然有点绕但真实排查中比直接看一长串JSON高效得多。如果不习惯-f语法可以先docker inspect 容器名全量输出再用jq处理。比如docker inspect 容器名 | jq .[0].NetworkSettings.IPAddress。7.2 stats、top、events三个命令组成应急三件套容器层面的性能问题第一反应是用docker stats查看所有容器的CPU、内存、网络、磁盘IO实时指标。docker stats不带参数显示所有运行中容器加--no-stream只输出一次快照适合脚本采集。docker stats --no-stream的输出里能直观看到每个容器内存使用量是否超过限制CPU百分比是否持续高企。看到异常容器后下一步用docker top 容器名查看容器内的进程列表确认是哪个具体进程在占用资源以及进程对应的PID、用户等信息。判断资源问题时还可以结合docker inspect查看容器的内存限制配置。比如docker inspect -f {{.HostConfig.Memory}} 容器名输出0通常表示不限制内存输出数值表示限制了多少字节。容器因内存不足被杀死时docker events流水会记录 OOM 事件比如container die。docker events是一个流式事件命令可以实时显示docker守护进程收到的各类事件比如容器启动、停止、重启、镜像拉取等docker events --filter containermyweb --filter eventdie这三条命令配合docker logs基本能解决大多数容器运行异常排查先看日志有没有报错再看资源有没有超限再看进程还在不在最后看事件流里有没有异常的启停记录。8. 一条命令打通镜像构建与推送的常见踩坑8.1 命名规范不统一导致的推送失败多人协作时最常遇到的情况就是自己构建的镜像推送到私服报权限错误或找不到。实际上多半是tag没有带完整的私服地址docker不知道往哪推。比如直接执行docker push myapp:v1docker会默认推到Docker Hub官方仓库请求被拒绝了才知道要打私服tag。先确认私服地址并打标签docker tag myapp:v1 registry.example.com/myteam/myapp:v1 docker push registry.example.com/myteam/myapp:v1推送前先执行docker login登录私服否则推送时大概率遇到401认证错误。登录信息会保存在当前用户的docker配置里如~/.docker/config.json里面存的是加密后的认证信息。8.2 构建上下文过大导致镜像臃肿镜像变大的一个隐形原因就是构建上下文太大或Dockerfile里的COPY或ADD把大文件打进去了。排查时可以用docker history 镜像名查看每一层的体积变化哪一层突然变大问题基本上就锁定在哪一行。常见优化思路是合并Dockerfile里的RUN步骤减少层数把不变的依赖安装放前面经常变的代码复制放后面充分利用构建缓存。一个简单的Dockerfile示例FROM node:18-alpine WORKDIR /app COPY package.json ./ RUN npm install COPY . . CMD [npm, start]这种写法的好处是代码变动时只需重新执行COPY . .之后的步骤npm install会命中缓存构建速度快很多。反过来的写法把COPY . .放前面每次代码变动都触发npm install重新执行构建效率会差很多。8.3 多阶段构建主流的镜像瘦身方案多阶段构建是用Dockerfile里多次FROM指令从不同阶段构建产物最终只保留最小的运行阶段。这个思路对后端和前端项目都适用。比如一个Java项目第一阶段用maven镜像编译出jar包第二阶段用jre镜像启动jar包最终镜像体积能从几百兆降到一百多兆。奔跑环境不同多阶段构建的写法可以参考FROM node:18-alpine AS build WORKDIR /app COPY package.json ./ RUN npm install COPY . . RUN npm run build FROM nginx:alpine COPY --frombuild /app/dist /usr/share/nginx/html EXPOSE 80 CMD [nginx, -g, daemon off;]这种方式好处很多最终镜像只包含nginx和构建产物不包含node环境和源码既减小体积又降低安全风险。我在团队里推荐所有新项目都采用多阶段构建收益是稳步提升的。9. 日常维护中容易被忽略的综合清理与信息排查9.1 磁盘空间被吞了system df和prune怎么用刚接触docker没多久的人往往只关注镜像和容器忽视构建缓存和悬空卷结果磁盘空间告警时一头雾水。先执行docker system df它会分别列出镜像、容器、本地卷、构建缓存占用多少空间一眼就能找到空间被吃在哪。输出里RECLAIMABLE列表示可回收的空间如果构建缓存这一列的RECLAIMABLE特别大说明大量缓存可以清理。执行docker system prune会清理所有停止的容器、未被使用的网络、悬空镜像以及构建缓存。加上-a参数则会把所有未被正在运行的容器使用的东西全部清理包括没有标签的镜像和未被任何容器使用的镜像。执行前docker会再次确认有需要保留的镜像和容器可以标记或临时启动着避免被误删。清理构建缓存单独执行docker builder prune -a这个命令不删除镜像和容器只清构建缓存是安全度最高、效果最直接的空间回收手段。9.2 查看容器配置的快捷方式排查问题的时间有一大半花在看配置上。除了docker inspect有几个快捷方式值得记住docker inspect 容器名 | jq .[0].Config.Env docker inspect 容器名 | jq .[0].HostConfig.PortBindings docker inspect 容器名 | jq .[0].NetworkSettings.Networks第一条看环境变量配置第二条看端口映射是否生效第三条看容器接入的网络和IP。这些信息排查时基本必查熟练了能省很多时间。还有docker port 容器名能直接列出端口映射关系比在整个JSON里找要直观docker port nginx-web如果输出是80/tcp - 0.0.0.0:8080说明映射正常。如果没有任何输出说明容器启动时可能没有指定端口映射参数或者端口映射被忽略。9.3 日志驱动的选择别等日志把磁盘塞满docker日志驱动默认是json-file日志文件会不断累积如果没有配置日志轮转时间长了会占满磁盘。容器运行时可以指定日志大小和数量限制docker run -d \ --log-driver json-file \ --log-opt max-size10m \ --log-opt max-file3 \ nginx这样单个日志文件最大10MB最多保留3个文件超过会滚动覆盖。生产环境里这是避免日志撑爆磁盘的有效手段。如果你用--log-driver local日志会按压缩格式存占空间更小但有些日志采集工具读起来可能不兼容。选日志驱动要提前考虑后续日志采集方案别等日志采集器接入时才发现格式不匹配。9.4 精简镜像的选择为什么我偏爱alpine基础镜像镜像体积直接影响拉取速度和磁盘占用。同等功能下用alpine基础镜像比标准镜像小很多比如nginx的alpine版本标签带-alpine后缀。但代价是一些依赖需要额外安装因为alpine用的包管理器和GNU工具链略有差异比如没有标准Linux发行版自带的一些库。如果项目强依赖某些C库或需要在容器里编译alpine不一定是最省事的选择。这时候选slim版本可能更合适slim基于Debian但裁剪了大部分组件兼容性更好。选基础镜像的原则是够用就好不要为了追求极致小体积牺牲易用性。每次写完Dockerfile都执行docker images看一眼最终镜像大小心里有数。10. 一些不必照搬的进阶用法但值得知道10.1 以非root用户运行容器默认情况下容器内的进程以root身份运行很多基础镜像也默认如此。但从安全角度建议在Dockerfile里创建普通用户并在启动时切换FROM node:18-alpine RUN addgroup -S appgroup adduser -S appuser -G appgroup USER appuser WORKDIR /app COPY --chownappuser:appgroup . . CMD [node, server.js]这条习惯养成了以后配合宿主机上的bind mount目录那些奇葩的权限问题会少很多。10.2 临时容器不进容器调试的替代方案数据卷、网络的故障排查不一定非得进容器。用临时容器挂载相同卷或网络来调试有时更方便比如查数据库卷里的数据文件docker run --rm -it \ -v mysql-data:/var/lib/mysql \ alpine \ sh进入后就能直接查看卷内文件用完退出自动删除容器宿主机不留下任何垃圾。10.3 develop模式的前置配置容器健康检查如果你的最终目标是做编排或交付健康检查这个能力得提前了解。在Dockerfile或run命令里可以配置docker run -d \ --name web \ --health-cmdcurl -f http://localhost/ || exit 1 \ --health-interval10s \ --health-retries3 \ nginx执行docker ps时STATUS字段会显示(healthy)或(unhealthy)编排系统会根据这些状态决定是否重启或摘除流量。容器没有健康检查的话编排系统只能靠进程是否存在来粗粒度判断很多假死问题就发现不了。cmd里用了curl但基础镜像不一定自带curl如果容器里没有curl健康检查命令会一直失败。所以写健康检查时要同时确认镜像里有没有对应工具或者换成内置命令比如wget。11. 写在最后一套自己的docker排障思路用了这么多年docker我觉得最重要的不是背了多少条命令而是形成自己的排障路径。我个人的固定顺序是先docker ps看容器状态再docker logs看报错然后docker inspect看配置和网络接着docker stats看资源占用最后docker system df看整体空间。这套顺序基本能解决我遇到的80%以上的container异常状态异常、日志报错、配置错误、资源不足、磁盘占满。剩下20%会涉及更底层的网络或内核问题那时再往上翻events和系统日志。对刚入门的朋友我的建议是不必一次记住所有命令先从docker ps -a、docker logs、docker run、docker exec这四个命令开始把容器的运行、查看日志、进入容器、启动容器这几件事吃透其他命令用到了再看。实际工作中你会发现日常工作里80%的操作都落在这些基础命令上剩下的随时查文档、查帮助都能解决。还有一个小技巧官网的cheat sheet可以打印一份放在手边但用的时候一定想一想这条命令为什么是这么设计的参数为什么这么写。理解背后逻辑之后多复杂的命令都不会忘。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑