2、Docker基础:Docker核心概念与第一个Dockerfile
好,咱们进入实战环节。说实话,很多新手一上来就对着Kubernetes猛学,结果发现连镜像怎么打包都搞不清楚。我个人习惯是,先把Docker玩明白,再上K8s会轻松很多。这一章,咱们就聊聊Docker最核心的三个东西:镜像、容器、仓库,再手把手写一个Dockerfile。
2.1 Docker核心概念:镜像、容器、仓库
这三个概念,说白了就是Docker世界的「三件套」。你想想看,没有镜像,容器跑什么?没有容器,镜像怎么用?没有仓库,镜像往哪存?
2.1.1 镜像(Image)
镜像是什么?镜像就是一个只读的模板。它包含了运行某个应用所需的一切:代码、运行时、系统工具、库、配置……
我打个比方:镜像就像你电脑上装的一个ISO系统安装盘。你拿着这个ISO,可以装出无数个一模一样的系统。镜像也一样,一个镜像可以启动无数个容器。
关键点:镜像是分层构建的。每一层都是只读的,修改时只会新增一层。这种设计让镜像复用变得非常高效。
我在项目中遇到过一个问题:团队里有人把镜像做得特别大,动不动几个G。后来发现,他每次构建都从零开始,没有利用缓存层。嗯,这里要注意,合理利用镜像分层,能大幅减少构建时间和存储空间。
2.1.2 容器(Container)
容器是镜像的运行实例。你可以把容器理解成一个轻量级的、隔离的、可运行的进程。
镜像和容器的关系,就像类和对象的关系。镜像是类,定义好了结构和行为;容器是对象,真正跑起来干活。
容器有几个特点:
- 隔离性:每个容器有自己的文件系统、网络、进程空间
- 可移植性:容器在任何安装了Docker的机器上都能跑
- 快速启动:秒级甚至毫秒级启动,比虚拟机快得多
小技巧:容器停止后,数据默认不会丢失。但如果你删除了容器,里面的数据就没了。所以重要数据一定要用卷(Volume)挂载出来。
2.1.3 仓库(Registry)
仓库就是存放镜像的地方。最常用的是Docker Hub,还有私有的Harbor、AWS ECR等。
仓库的作用很简单:让你能拉取(pull)和推送(push)镜像。就像GitHub存代码一样,仓库存镜像。
| 概念 | 类比 | 特点 |
|---|---|---|
| 镜像 | ISO安装盘 | 只读模板,分层构建 |
| 容器 | 运行中的虚拟机 | 隔离、可移植、快速启动 |
| 仓库 | GitHub | 存储和分发镜像 |
2.2 常用Docker命令
命令这东西,不用全记住,记住最常用的几个就行。我平时用得最多的就是pull、run、exec、build、push。咱们一个一个过。
2.2.1 docker pull:拉取镜像
从仓库拉取镜像到本地。比如拉一个Ubuntu镜像:
docker pull ubuntu:22.04
不指定标签的话,默认拉取latest。我个人习惯永远指定具体版本号,不然哪天latest变了,你的环境可能就炸了。
2.2.2 docker run:运行容器
这是最常用的命令。从镜像启动一个容器:
docker run -it --name my-ubuntu ubuntu:22.04 /bin/bash
参数说明:
-it:交互式终端,让你能进容器操作--name:给容器起个名字/bin/bash:容器启动后执行的命令
如果你想在后台运行,用-d参数:
docker run -d --name my-nginx -p 8080:80 nginx:latest
这里-p 8080:80是把宿主机的8080端口映射到容器的80端口。你访问宿主机IP:8080,就能看到Nginx页面了。
注意:我曾经遇到过一个问题:容器启动后马上退出。后来发现是前台进程没有保持住。容器必须有一个前台进程在运行,否则会立即退出。比如Nginx默认是后台守护进程,你需要用nginx -g 'daemon off;'来保持前台运行。
2.2.3 docker exec:进入容器
容器已经在后台跑了,你想进去看看日志或者调试一下,用exec:
docker exec -it my-nginx /bin/bash
这就进入了my-nginx容器内部。你可以像操作普通Linux一样操作它。
2.2.4 docker build:构建镜像
这是最核心的命令之一。通过Dockerfile构建镜像:
docker build -t my-app:v1.0 .
-t指定镜像名称和标签,最后的.表示Dockerfile所在的目录(当前目录)。
2.2.5 docker push:推送镜像
构建好的镜像,推送到仓库:
docker push my-app:v1.0
推送前需要先登录:docker login。如果是私有仓库,要指定仓库地址。
2.3 编写第一个Dockerfile
好,重头戏来了。咱们写一个最简单的Dockerfile,部署一个Python Flask应用。
先看项目结构:
my-flask-app/
├── app.py
├── requirements.txt
└── Dockerfile
app.py内容:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello, Docker!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
requirements.txt内容:
flask==2.3.0
现在,写Dockerfile:
# 使用官方Python镜像作为基础镜像
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY app.py .
# 暴露端口
EXPOSE 5000
# 启动命令
CMD ["python", "app.py"]
逐行解释一下:
FROM:指定基础镜像。我选python:3.9-slim,因为它小,只有100多MWORKDIR:设置容器内的工作目录,后续命令都在这个目录下执行COPY:从宿主机复制文件到镜像中RUN:在构建过程中执行的命令,这里安装Python依赖EXPOSE:声明容器运行时监听的端口,这只是文档作用,实际映射还要靠-pCMD:容器启动时执行的命令
最佳实践:把COPY requirements.txt .和RUN pip install放在COPY app.py .前面。为什么?因为依赖文件不常变,而代码经常变。这样可以利用Docker的缓存层,每次只重新构建代码层,节省大量时间。
构建并运行:
docker build -t my-flask-app:v1.0 .
docker run -d -p 5000:5000 my-flask-app:v1.0
打开浏览器访问http://localhost:5000,就能看到「Hello, Docker!」了。
2.4 本章知识体系
为了让你更直观地理解本章内容,我画了一张图:
这张图清晰地展示了Docker的工作流程:从Dockerfile构建镜像,从仓库拉取/推送镜像,从镜像运行容器。三者环环相扣,缺一不可。
2.5 避坑指南
最后,分享几个我踩过的坑:
- 镜像太大:尽量用
-slim或-alpine版本的基础镜像,能小很多 - 容器退出:确保你的应用是前台运行的,不然容器秒退
- 缓存失效:把不常变的层放在Dockerfile前面,充分利用缓存
- 端口冲突:运行容器前,检查宿主机端口是否被占用
我的习惯:每次构建镜像前,先跑一遍docker system prune清理无用的镜像和容器,保持环境干净。不然磁盘很快就满了。
好了,这一章的内容就到这里。Docker基础打牢了,后面上Kubernetes才会顺手。记住:镜像、容器、仓库这三个概念,以及pull、run、exec、build、push这五个命令,足够你应付大部分场景了。