Docker新手必看:8个核心操作、6大高频坑,一次讲透容器本质

Docker新手必看:8个核心操作、6大高频坑,一次讲透容器本质 你说你是Docker新手拉个镜像慢到怀疑人生docker run 参数抄了又抄还是记不住好不容易把容器跑起来第二天发现数据全没了瞬间想砸电脑。别急这些问题我全踩过今天这篇就把Docker容器最核心的操作逻辑一次讲透没有废话全是实操。这个标题看着可能有点密集——8个核心操作、6大高频坑、3个核心逻辑但核心就一件事让你从只会跟着教程敲命令变成遇到问题能自己判断怎么处理。我接触Docker这么多年带过不少新人发现最容易出问题的往往不是命令本身而是对容器本质的理解偏差——不知道它是个什么东西出问题的时候自然不知道往哪个方向排查。所以我会先讲透底层逻辑再带你过一遍必须会的操作最后把高频坑逐个拆开保证你看完能少走一大半弯路。1. 三个核心逻辑先搞懂容器到底是个啥1.1 容器不是虚拟机它就是一个进程很多新手第一次接触Docker脑子里默认把它当成了轻量级虚拟机。这个误解非常要命因为一旦用虚拟机的思维去理解容器后面遇到的所有问题都会觉得莫名其妙。举一个最简单的例子你用docker run -d nginx启动了一个容器然后执行docker ps看不到它。如果你把容器理解成虚拟机你的第一反应是机器没开起来但如果你理解成进程你的第一反应是进程挂了然后再去docker logs查看问题出在哪。这个思路的转变决定了你能不能快速定位问题。实际上的原理是这样的Docker容器共享宿主机内核通过Linux内核的namespace做资源隔离通过cgroup做资源限制。容器里的进程在宿主机上就是一个个普通进程只是被圈在了一个隔离环境里罢了。注意这个认知直接决定你的排查思路容器应用挂了第一件事就是看日志不是去重启系统。1.2 镜像是一层一层的文件系统变更靠写时复制Docker镜像为什么能复用为什么拉一个几百MB的镜像有时只需要几十秒核心在于镜像的分层存储机制。Docker镜像是由多个只读层堆叠而成的每一层对应Dockerfile里的一条指令。你拉取一个镜像时Docker会检查本地已有的层只下载缺失的层这就是为什么如果你的本机已经有其他镜像共享了一些基础层拉新镜像会快很多。当一个容器运行后Docker会在镜像层之上挂载一个可写层。容器中对文件系统的任何修改包括新增、删除、修改文件都发生在这个可写层。用写时复制的机制即使多个容器共享同一个镜像或底层只读层每个容器内部的修改也是相互隔离的。这就是为什么容器删除后数据会丢——因为数据写在可写层里容器删了可写层也就没了。理解了这个原理你就会明白为什么官方一直强调数据必须挂载到数据卷或宿主机目录里。1.3 容器是一次性的可写层不是保险箱顺着上面的逻辑再往下走容器的生命周期应该是短暂的、可替换的。你要做的是随时可以删掉一个容器再用同一个镜像重新创建一个而业务数据丝毫不受影响。我之前见过一个小伙子在容器里部署了一个MySQL用了一段时间之后数据越来越多某天服务器重启后容器没自动启动他一着急直接去容器内部检查数据结果发现数据还在就没放在心上。过了几天他手动清理镜像的时候顺手把那个容器给删了等他发现数据库连不上的时候数据已经彻底没了。这个场景太典型了。你得把容器理解成一次性的沙滩城堡而数据卷是你放在岸边的保险柜。城堡被海浪冲走了没关系保险柜里的东西不能丢。2. 八个核心操作从零开始跑通容器2.1 拉取镜像先搞明白你下载的是啥镜像是什么你可以理解为应用运行所需的全部材料打包。nginx镜像里包含了nginx程序、配置文件、依赖库等MySQL镜像里包含了MySQL程序、初始化脚本等。拉取命令很简单docker pull nginx默认会从Docker Hub拉取最新版本latest也可以指定版本和仓库地址docker pull mysql:8.0 docker pull registry.cn-hangzhou.aliyuncs.com/library/mysql:8.0这里多提一句国内拉取Docker Hub镜像经常很慢甚至超时失败。解决方案有两种一是配置镜像加速器在Docker Desktop的设置里可以配置registry-mirrors二是在拉取命令里直接指定国内镜像源地址。配置好之后实测下载速度能提升好几倍。2.2 运行容器docker run 是全部操作的入口docker run是最常用的命令但新手往往被它的参数搞晕。我拆解一下最常用的组合docker run -d -p 8080:80 --name web-server nginx-d后台运行不加这个参数你的终端就会被nginx的前台日志刷屏CtrlC一按容器就停了。-p 8080:80端口映射格式是宿主机端口:容器内端口。这里把宿主机的8080端口映射到容器的80端口这样你访问http://localhost:8080就能访问到容器内部的nginx。--name给容器起个名字不加的话Docker会随机生成一个类似angry_gates的名字。启动MySQL时一般还要加环境变量和挂载数据卷这就是比较完整的形态docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -e MYSQL_DATABASEmydb \ -v mysql-data:/var/lib/mysql \ mysql:8.0-e是设置环境变量MySQL镜像需要通过MYSQL_ROOT_PASSWORD指定root密码不然容器会直接启动失败。-v是挂载数据卷我们后面专门说。新手最怕看到又长又乱的命令我的建议是把常用参数写成一个小抄贴在手边用几次就熟练了。2.3 查看容器状态docker ps 的两种用法刚跑完容器第一件事就是确认它有没有正常运行docker ps这个命令只显示正在运行的容器。如果容器启动后因为各种原因退出了docker ps列表里看不到它你可能会以为容器不存在。这时候要用docker ps -a-a参数显示所有容器包括已退出的。每个容器会有一个唯一的CONTAINER ID后续很多操作都可以直接使用这个ID的前几位来定位容器比如docker logs abc123。2.4 查看日志排查问题的第一把钥匙容器跑起来了但访问不了或者没几秒就退出了第一个排查手段永远是看日志docker logs web-server如果日志太多可以只看最后100行并且持续跟踪docker logs --tail 100 -f web-server--tail是只看末尾多少行-f是持续输出新产生的日志类似tail -f的效果。排查问题的时候这招最实用。很多新手容器一启动就退出不查日志就盲目删了重跑这完全是碰运气。八成的情况日志里已经写清楚了失败原因——比如端口被占用、环境变量缺失、配置文件写错等等。2.5 进入容器在容器内部执行命令需要进入容器内部调试的时候用这个docker exec -it web-server bash-it是两个参数-i保持标准输入打开-t分配一个伪终端。不这样写交互式操作没法玩。如果容器里没有bash比如一些精简版alpine镜像可以试试shdocker exec -it web-server sh如果你只是想执行单条命令不用进入容器docker exec web-server ls /usr/share/nginx/html这个在排查问题时非常常用不用反复进出容器。2.6 停止、启动、删除容器生命周期管理容器用完了想停下来或者修改了配置想重启常用命令如下docker stop web-server # 停止容器会发出SIGTERM信号给它留出优雅退出的时间 docker start web-server # 启动一个已停止的容器 docker restart web-server # 重启容器 docker rm web-server # 删除容器需要先停止如果要删除所有已停止的容器记住这个docker container prune它会一次性清除所有处于停止状态的容器释放命名和存储资源。如果明确要强制删除正在运行的容器加-f参数。2.7 端口映射与数据卷容器外部访问入口和数据存放点端口映射在2.2里已经提过这里强调一个点端口映射是单向的你可以在启动容器时通过-p参数指定映射但容器运行中不能追加新的映射。如果想改端口只能把容器删了重新run。数据卷是Docker里最容易被忽视、后果最严重的一环。数据卷就是挂载到容器里的一个独立存储空间它可以在宿主机上也可以由Docker管理目的是让数据独立于容器生命周期。docker run -d --name mysql8 \ -v mysql-data:/var/lib/mysql \ mysql:8.0上面这个mysql-data由Docker管理数据存在宿主机的/var/lib/docker/volumes/目录下。另一种方式是直接挂载宿主机目录-v /opt/mysql/data:/var/lib/mysql这种方式更直观你可以直接在宿主机上看到和备份数据。具体用哪种看你的运维习惯我建议生产环境直接用宿主机目录的方式方便备份和迁移。2.8 清理镜像和缓存给磁盘瘦身长期使用Docker磁盘空间会被镜像、容器、数据卷、构建缓存占满。我清理的思路是先删容器、再删镜像最后清没用的数据卷docker rm $(docker ps -aq) # 删除所有容器 docker rmi 镜像ID或名称 # 删除指定镜像 docker image prune -a # 删除所有没有被容器使用的镜像 docker volume prune # 删除未被任何容器挂载的数据卷 docker system df # 查看磁盘占用情况docker system df会列出镜像、容器、数据卷、构建缓存各自占了多少空间非常直观。3. 六大高频坑每一个都是新手踩出来的3.1 镜像拉取失败或超时这个问题在国内几乎每个人都碰到过。官方镜像源在国外网络连接不稳定或速度极慢导致拉取超时、中断甚至出现manifest unknown之类的错误。解决办法第一选择是配置镜像加速器。打开 Docker Desktop 的 Settings → Docker Engine在配置里加上registry-mirrors地址然后重启Docker。Linux环境下修改或新建/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://hub-mirror.c.163.com ] }然后重启Docker服务sudo systemctl daemon-reload sudo systemctl restart docker配置好之后拉镜像速度是肉眼可见的提升。如果还是慢就直接从国内镜像仓库拉取比如阿里云容器镜像服务docker pull registry.cn-hangzhou.aliyuncs.com/...。3.2 容器一启动就退出秒退这是新手最常遇到的问题没有之一。docker ps -a看到容器显示EXITED第一反应往往是再跑一次结果依然秒退。绝大多数的原因是镜像里定义的执行命令运行失败。常见场景有指定了错误的环境变量比如MySQL的密码没设依赖的端口已被占用配置文件写错应用启动时直接报错退出终极解法就三个字看日志。docker logs 容器ID或名称日志会明确告诉你失败的原因。比如MySQL缺少MYSQL_ROOT_PASSWORDyesql配置的端口被占用了等等。看完日志再动手效果立竿见影。另外有些容器是前后台不分的如果镜像里的进程本身是后台守护进程比如某些脚本里写了daemon off容器会认为主进程已经退出了所以也秒退。这个问题常见于直接用系统包管理器装nginx之类的软件然后手动启动的镜像。3.3 端口被占用了怎么办启动容器报错Bind for 0.0.0.0:8080 failed: port is already allocated这是端口冲突。可能的情况是宿主机上有其他进程占了这个端口也可能是另一个容器已经映射了这个端口。排查方法lsof -i :8080 # 或者 netstat -tunlp | grep 8080看看是谁占用了8080端口然后改成其他端口重新启动容器。注意改端口可不是改条命令那么简单你得先删掉原来的容器docker rm -f web-server docker run -d -p 8081:80 --name web-server nginx3.4 容器删除后数据全没了这个问题在前面已经反复强调过了根因就是没挂载数据卷。运行MySQL、Redis、MongoDB这类有状态服务时数据目录一定要挂载到宿主机MySQL数据目录/var/lib/mysqlRedis持久化文件/dataMongoDB数据目录/data/dbPostgreSQL数据目录/var/lib/postgresql/data如果你用命名数据卷即使容器删了只要卷还在数据就还在。你完全可以放心重建容器重新挂载同一个卷数据立马回来。我用一句话总结操作数据库容器之前先检查启动命令里有没有-v没有就先别干。3.5 容器时区不是北京时间容器默认时区通常是UTC所以日志时间比北京时间慢8小时。排查日志的时候时间对不上很误导人。最简单的解决方式是启动时挂载宿主机的时区配置docker run -d \ --name web-server \ -e TZAsia/Shanghai \ nginx很多官方镜像支持TZ环境变量启动时指定一下就行。如果镜像不支持或者你已经启动了就进容器改docker exec -it web-server bash ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime改完重启容器生效。不过每次重建容器都要重新设置所以我更推荐在docker run时直接加-e TZAsia/Shanghai或者挂载时区文件-v /etc/timezone:/etc/timezone:ro3.6 容器里用root跑服务安全隐患很大默认情况下容器里的进程是以root身份运行的这非常危险。一旦容器被攻破攻击者获得的就是宿主机上的root权限。即使有namespace隔离风险依然很大。解决办法是启动容器时指定运行用户docker run -d --user 1000:1000 nginx或者用-u参数指定用户名。更规范的做法是如果你自己写Dockerfile在构建镜像时就创建一个普通用户并用USER指令切换过去。需要注意一个前提容器里的用户必须对挂载的目录有写权限。如果数据卷目录是root创建的普通用户进不去应用就会启动失败这个在调试时最容易迷惑人。4. 进阶但不难的补充网络、Compose与日常排障4.1 容器网络别默认把端口暴露到公网-p 8080:80会让端口直接暴露在宿主机的所有网卡上包括公网。如果你只是本机调试不想让外部访问可以绑定回环地址-p 127.0.0.1:8080:80多个容器之间通信推荐创建自定义网络docker network create my-net docker run -d --network my-net --name mysql8 mysql:8.0 docker run -d --network my-net --name web-server nginx使用自定义网络后容器之间可以用容器名直接访问比如web容器里连接MySQL直接写mysql8:3306就行IP地址不用记。4.2 docker compose多个容器一条命令跑起来如果项目依赖了MySQL、Redis、应用本身等多个容器再用docker run一个个启动就太痛苦了。docker compose可以把所有服务定义在docker-compose.yml文件里一条命令全部拉起version: 3 services: mysql: image: mysql:8.0 container_name: mysql8 environment: MYSQL_ROOT_PASSWORD: rootpassword ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql app: build: . container_name: web-app depends_on: - mysql ports: - 8080:80 volumes: mysql-data:然后运行docker compose up -d这个文件把镜像、端口、数据卷、环境变量都声明清楚了下次服务器迁移或者换机器部署拷贝文件直接跑比手动翻历史命令找docker run靠谱一万倍。我强烈建议凡是超过一个容器的场景直接用compose。4.3 常见排查命令速查把最有用的排查命令整理成一张表建议收藏场景命令说明查看运行中容器docker ps列出所有运行中的容器查看所有容器docker ps -a包含已退出容器查看容器日志docker logs -f 容器跟踪日志输出进入容器docker exec -it 容器 bash容器内执行交互命令查看镜像docker images列出本地镜像查看端口占用lsof -i :8080检查端口占用情况查看资源占用docker stats实时看容器CPU、内存查看容器信息docker inspect 容器包括IP、挂载卷、环境变量等docker inspect是排查问题的神器比如确认容器实际的挂载路径对不对、IP是多少、环境变量有没有生效全在这里面。5. 实操总结与个人体会如果说这么多内容里只能留下一个观念给你我最想强调的是把容器当成一只一次性手套而不是一台小电脑。它随手可取、随时可换你需要保护的是手套之外的数据和配置。任何需要长期保留的东西都必须挂载到宿主机目录或数据卷里任何需要改的配置都应该写进docker run参数或者compose文件里而不是手动进入容器改完就这样用。我自己早期因为没挂载数据卷把MySQL数据弄丢过一次从那以后定了个死规矩跑有状态服务容器先想清楚数据落在哪里跑测试环境容器一律标注清理日期过时就容器和镜像一起清掉。如果你看完想动手试试我建议的路线是先拉一个nginx跑起来修改默认页面然后把容器删了再重建一次体会一下增减容器对配置和数据无感的爽快。接下来可以试着跑一个Redis设置密码、挂载数据卷再用compose把Redis和一个小应用一起拉起来。走完这三四个实验你基本就具备日常使用Docker的底气了。