一、引言为什么需要Docker还记得第2篇我们安装Linux环境时用了虚拟机吗虚拟机确实解决了“一台电脑跑多个系统”的问题但它有三大痛点重每个虚拟机都要跑一个完整的操作系统内核启动要几十秒占用几个GB的磁盘慢虚拟机的资源是预先分配的不管实际用不用分配的CPU和内存都得占着资源利用率不高** “我这里能跑啊” **开发环境里正常运行的应用到了测试环境就各种报错——“依赖版本不对”“系统库缺失”“配置文件路径不一样”Docker的解决方案很巧妙不再笨拙地模拟整个硬件和操作系统而是让所有容器共享宿主机的操作系统内核只是在用户空间上做隔离。Docker vs 虚拟机对比维度虚拟机Docker容器启动速度几十秒到几分钟毫秒到秒级磁盘占用每个VM几个GB镜像通常几十到几百MB资源利用率预先分配浪费多按需使用共享宿主隔离级别完全操作系统隔离进程级隔离共享内核可移植性受虚拟化平台限制任何装了Docker的系统都能跑Docker的三大核心价值环境一致性开发、测试、生产用同一个镜像“这里能跑”不再是问题轻量化共享宿主内核秒级启动一台服务器能运行几十上百个容器可复现构建Dockerfile描述构建步骤任何人都能复现一模一样的镜像二、核心概念镜像、容器、仓库Docker有三个核心概念理解它们的关系是入门的关键。2.1 镜像镜像可以类比为一个软件的“安装文件”或“快照”。它包含了运行某个应用所需的一切——代码、运行时、系统库、环境变量、配置文件。关键特性镜像是只读的、分层的。当你修改镜像时Docker不会改动原有层而是在上面叠加新的一层。这种设计让不同镜像可以共享相同的基础层极大节省磁盘空间。2.2 容器容器可以类比为运行起来的“进程实例”。容器就是镜像的运行态。关键特性容器是可读写的——Docker在镜像的只读层上加了一个读写层你在容器中的所有操作写文件、装软件都发生在这个读写层。容器删除读写层也随之消失。镜像和容器的关系就像程序和进程的关系第11篇的概念镜像是静态的定义程序容器是动态的运行实例进程同一个镜像可以启动多个互相隔离的容器2.3 仓库仓库可以类比为镜像的“GitHub”。Docker Hub是默认的公共仓库里面有官方维护的Nginx、MySQL、Python等镜像也有社区贡献的各种镜像。关键操作docker pull nginx就是把Nginx镜像从仓库拉取到本地docker push myname/myimage就是把你自己的镜像推送上去。2.4 一张图理清三者关系textdocker pull → 从仓库下载镜像到本地 docker run → 从本地镜像启动容器 docker build → 用Dockerfile把应用打包成镜像 docker push → 把本地镜像上传到仓库三者关系也可以简单总结为从仓库下载镜像用镜像启动容器把容器打包成新镜像再将镜像上传到仓库。整个Docker生态就是围绕这个闭环运转的。三、Docker快速上手3.1 安装DockerDocker官方提供了一键安装脚本适合快速体验生产环境建议用发行版官方源bash# Ubuntu/Debian curl -fsSL https://get.docker.com | sudo bash # 将当前用户加入docker组省去每次sudo sudo usermod -aG docker $USER # 退出重新登录后生效验证安装bashdocker --version docker run hello-world如果看到Hello from Docker!的欢迎信息说明安装成功。3.2 docker run启动你的第一个容器bash# 启动一个Nginx容器 docker run -d --name my-nginx -p 8080:80 nginx参数拆解参数含义-d后台运行detached mode--name my-nginx给容器命名方便后续操作-p 8080:80端口映射将宿主8080端口映射到容器80端口nginx使用的镜像名本地没有会自动拉取验证bashcurl http://localhost:8080 # 看到Nginx欢迎页面的HTML内容3.3 docker ps查看运行中的容器bashdocker ps # 只看运行中的 docker ps -a # 包括已停止的输出解读textCONTAINER ID IMAGE STATUS PORTS NAMES a1b2c3d4e5f6 nginx Up 10 minutes 0.0.0.0:8080-80/tcp my-nginxCONTAINER ID容器的唯一标识可以只用前几位来操作STATUS容器当前状态PORTS端口映射关系3.4 docker exec进入容器的“门”bash# 进入容器内部启动bash交互式环境 docker exec -it my-nginx bash参数拆解-i交互模式保持标准输入打开-t分配一个伪终端my-nginx容器名也可以用容器IDbash要执行的命令进入容器后你就等于在这台“微型Linux”里操作了可以查看配置、调试问题就像SSH进了另一台机器一样bash# 在容器内部 ls /etc/nginx/ cat /usr/share/nginx/html/index.html # 这就是Nginx欢迎页的源文件 exit # 退出容器3.5 容器的生命周期管理命令作用类比docker start 容器名启动已停止的容器开机docker stop 容器名停止运行中的容器关机docker restart 容器名重启容器重启docker rm 容器名删除已停止的容器卸载需先stopdocker rm -f 容器名强制删除包括运行中的强制卸载重要docker rm只能删除已停止的容器。要删除运行中的容器先docker stop再docker rm或用docker rm -f直接强制删除。3.6 查看日志bash# 查看容器日志 docker logs my-nginx # 实时跟踪 docker logs -f my-nginx四、实战将应用打包成Docker镜像下面我们将一个简单的Python Web应用打包成Docker镜像。4.1 准备应用代码bashmkdir myapp cd myapp创建app.pypythonfrom flask import Flask import os app Flask(__name__) app.route(/) def hello(): host os.uname().nodename return fHello Docker! (hostname: {host}) if __name__ __main__: app.run(host0.0.0.0, port5000)创建requirements.txttextflask4.2 编写DockerfileDockerfile是一个描述了“如何构建这个镜像”的指令文件dockerfile# 1. 基础镜像从官方Python镜像开始 FROM python:3.10-slim # 2. 设置工作目录 WORKDIR /app # 3. 复制依赖文件并安装 COPY requirements.txt . RUN pip install -r requirements.txt # 4. 复制应用代码 COPY app.py . # 5. 声明容器监听的端口仅声明实际映射在运行时指定 EXPOSE 5000 # 6. 容器启动时执行的命令 CMD [python, app.py]逐条解释FROM基础镜像所有后续操作都在它的基础上叠加WORKDIR设置工作目录后续命令和CMD都以此为基础COPY从宿主机复制文件到镜像中RUN在镜像构建时执行命令安装依赖等EXPOSE文档性质的端口声明真正的端口映射需要运行时的-p参数CMD容器启动时默认执行的命令4.3 构建镜像bashdocker build -t myapp:v1 .-t myapp:v1给镜像打标签myapp是名称v1是版本.表示Dockerfile在当前目录输出会显示每一步的执行过程Step 1/6 → Step 2/6 ...。4.4 运行容器bashdocker run -d --name myapp-container -p 5000:5000 myapp:v1验证bashcurl http://localhost:5000 # 输出Hello Docker! (hostname: 容器ID)4.5 查看与管理镜像bash# 查看本地所有镜像 docker images # 删除镜像需先删除关联的所有容器 docker rmi myapp:v1 # 给镜像打新标签 docker tag myapp:v1 myapp:latest五、Docker常用命令速查类别命令作用镜像docker images查看本地镜像镜像docker pull nginx:1.25从仓库拉取镜像1.25指定版本不加则默认latest镜像docker build -t name:tag .用Dockerfile构建镜像镜像docker rmi 镜像名删除镜像容器docker run -d --name xxx -p 宿:容 镜像启动新容器容器docker ps/docker ps -a查看运行中/所有容器容器docker exec -it 容器名 bash进入容器容器docker start/stop/restart 容器名启动/停止/重启容器容器docker rm 容器名删除容器容器docker logs -f 容器名跟踪查看日志六、本篇小结核心概念金三角镜像构建一次到处运行相当于二进制可执行文件容器镜像的运行实例隔离但共享内核仓库镜像的存储和分发中心最小化上手流程bashdocker run hello-world # 验证安装 docker run -d --name web -p 80:80 nginx # 后台启动Nginx docker ps # 查看状态 docker exec -it web bash # 进入容器探索构建自己的应用镜像bash# 1. 写DockerfileFROM COPY RUN CMD # 2. docker build -t app:v1 . # 3. docker run -d -p 端口:端口 app:v1动手练习bash# 1. 验证Docker安装 docker run hello-world # 2. 启动Nginx容器并验证 docker run -d --name test-nginx -p 8088:80 nginx curl http://localhost:8088 # 3. 进入容器看看Nginx的文件结构 docker exec -it test-nginx bash # 在容器内cat /usr/share/nginx/html/index.html # 退出exit # 4. 停止并删除容器 docker stop test-nginx docker rm test-nginx # 5. 构建自己的应用镜像 mkdir ~/docker-test cd ~/docker-test # 按第四节的方法创建app.py、requirements.txt、Dockerfile # docker build -t myapp:v1 . # docker run -d --name myapp -p 5000:5000 myapp:v1 # curl http://localhost:5000常见问题排查端口已被占用textError: port is already allocated换一个宿主机端口-p 8081:80宿主的8081映射到容器的80。镜像拉取失败textError response from daemon: pull access denied检查镜像名是否正确国内环境可配置镜像加速器阿里云容器镜像服务提供免费加速器配置方法搜索“Docker镜像加速”。七、下篇预告掌握了Docker的基本操作下一篇我们将探索Linux中另一个重要的网络服务——DNS。DNS是互联网的“电话簿”下一篇我们将自己搭建一个内网DNS服务器实现局域网内的域名解析甚至可以劫持广告域名来屏蔽广告请求。延伸思考Docker的“一次构建到处运行”听起来完美但实际中仍有“坑”——latest标签在不同时间拉取的镜像版本可能完全不同。生产环境推荐的做法是锁定具体版本如nginx:1.25.3确保所有环境跑的是完全相同的镜像。版本固定才能让“可复现”真正落地。