容器技术基础:Docker核心概念、镜像与容器、Dockerfile最佳实践
好,咱们进入第二章。说实话,容器技术是整个云原生体系的基石。你想想看,没有容器,微服务怎么跑?Kubernetes编排谁去?所以这一章,我会把Docker最核心的东西给你讲透。
我个人习惯把Docker理解成「轻量级的虚拟机」,但又不完全是。它共享宿主机内核,启动快,资源占用小。嗯,这里要注意,很多人一开始会把容器和虚拟机搞混,其实它们底层原理完全不同。
Docker核心概念:镜像、容器、仓库
这三个概念,说白了就是Docker的「三驾马车」。我刚开始学的时候,总觉得镜像和容器是一回事,后来踩了坑才明白——镜像是个只读模板,容器是运行中的实例。
- 镜像(Image):一个只读的、分层结构的文件。包含了运行应用所需的一切:代码、运行时、库、环境变量、配置文件。你可以把它想象成一个「压缩包」或者「模板」。
- 容器(Container):镜像运行起来后的实例。它有自己的文件系统、网络、进程空间。说白了,镜像就是「菜谱」,容器就是「做出来的菜」。
- 仓库(Registry):存放镜像的地方。Docker Hub是官方的公共仓库,企业里一般用Harbor或者阿里云镜像仓库。
核心关系:镜像 → 运行 → 容器。你可以从同一个镜像启动多个容器,每个容器都是独立的。
我在项目中遇到过一个问题:有同事把容器当虚拟机用,在里面装了一堆东西,然后commit成新镜像。结果镜像越来越大,分层混乱。记住,容器应该是「无状态」的,数据要挂载出来。
镜像与容器的生命周期
咱们来看看一个容器从生到死的过程。我习惯用命令来理解,这样更直观。
# 拉取镜像
docker pull nginx:1.21
# 查看本地镜像
docker images
# 运行容器
docker run -d --name my-nginx -p 8080:80 nginx:1.21
# 查看运行中的容器
docker ps
# 进入容器内部
docker exec -it my-nginx /bin/bash
# 停止容器
docker stop my-nginx
# 删除容器
docker rm my-nginx
# 删除镜像
docker rmi nginx:1.21
你看,就这么几个命令,覆盖了镜像和容器的完整生命周期。我曾经犯过一个低级错误:忘记加 -d 参数,结果终端被容器日志刷屏,Ctrl+C 后容器也停了。嗯,-d 是后台运行的意思,一定要记住。
| 命令 | 作用 | 我的建议 |
|---|---|---|
docker pull |
拉取镜像 | 尽量指定版本标签,别用latest |
docker run |
创建并启动容器 | 常用参数:-d, -p, -v, --name |
docker ps |
查看容器列表 | 加 -a 看所有容器(包括已停止的) |
docker exec |
进入容器执行命令 | 生产环境慎用,尽量用日志排查 |
docker logs |
查看容器日志 | 加 -f 实时跟踪日志输出 |
Dockerfile最佳实践
Dockerfile是构建镜像的「配方」。写得好不好,直接影响镜像大小、构建速度、安全性。我见过太多人把Dockerfile写成「屎山」了——一个RUN写到底,镜像几个G。
为什么会这样?说白了,就是没理解Docker的分层构建机制。每一行指令都会生成一个层,层越多,镜像越大。所以,最佳实践的核心就是:减少层数,利用缓存,保证安全。
1. 选择合适的基础镜像
别上来就 FROM ubuntu:latest。我建议用 alpine 或者 slim 版本,体积能小好几倍。
# ❌ 不推荐
FROM ubuntu:20.04
# ✅ 推荐
FROM node:16-alpine
# 或者
FROM python:3.9-slim
记得有一次,我把一个Java应用的镜像从 openjdk:11 换成 openjdk:11-jre-slim,镜像直接从800MB降到了200MB。部署速度提升明显。
2. 合并RUN指令
每个RUN都会创建一个新层。把多个命令用 && 连接起来,能有效减少层数。
# ❌ 不推荐:多个RUN
RUN apt-get update
RUN apt-get install -y curl vim
RUN apt-get clean
# ✅ 推荐:合并为一个RUN
RUN apt-get update && \
apt-get install -y curl vim && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
小技巧:最后加上 rm -rf /var/lib/apt/lists/* 清理缓存,能进一步减小镜像体积。
3. 合理利用构建缓存
Docker构建时会缓存每一层。如果你把变化频繁的指令放在前面,缓存就废了。我建议把「不常变」的指令放前面。
# ✅ 推荐:先安装依赖,再复制代码
COPY package.json package-lock.json ./
RUN npm install
COPY . .
# ❌ 不推荐:先复制全部代码
COPY . .
RUN npm install # 代码一变,npm install就得重跑
我曾经因为这个问题,每次构建都要等5分钟装依赖。后来调整了顺序,只要代码不变,依赖安装直接命中缓存,构建时间缩短到30秒。
4. 使用多阶段构建
这是我最喜欢的功能。说白了,就是「先在一个大镜像里编译,再把编译产物复制到小镜像里」。最终镜像只保留运行所需的东西。
# 第一阶段:编译
FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 第二阶段:运行
FROM alpine:3.15
WORKDIR /app
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
你看,最终镜像只有alpine加上一个二进制文件,可能就十几MB。而如果直接用golang镜像,至少几百MB。我在生产环境里,所有Go、Java、Node应用都用了多阶段构建,效果非常好。
5. 安全相关的最佳实践
- 不要用root用户运行容器:默认是root,但生产环境一定要切换到普通用户。
- 不要存储敏感信息:密码、密钥用环境变量或Secret管理,别写死在Dockerfile里。
- 定期扫描镜像漏洞:用Trivy或Docker Scout检查基础镜像的安全问题。
# 创建非root用户并切换
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
警告:千万不要在Dockerfile里写 ENV PASSWORD=123456。镜像会被推送到仓库,密码就暴露了。我曾经见过有人把数据库密码写死在镜像里,结果被全网扫描到,数据全丢了。
总结一下
这一章我们聊了Docker的三个核心概念,镜像和容器的基本操作,以及Dockerfile的五个最佳实践。说白了,容器技术没那么玄乎,关键是多动手、多踩坑。
我个人建议你:先写一个简单的Dockerfile,构建、运行、调试一遍。遇到问题别怕,用 docker logs 和 docker exec 排查。等你把这一套流程跑通了,后面的Kubernetes学习就会轻松很多。
下一章,我们会深入Docker的网络和存储,到时候再聊。