2、容器化基础:Docker核心概念、Docker安装与配置、Dockerfile编写实战
好,咱们正式开始聊容器化。说实话,在云原生这个领域里,Docker 几乎是绕不开的第一道门槛。我记得刚接触云原生那会儿,第一件事就是跟 Docker 打交道。你想想看,没有容器,哪来的编排?没有编排,哪来的云原生?所以这一章,咱们把 Docker 的底裤扒干净。
2.1 Docker 核心概念:镜像、容器、仓库
这三个词,你面试的时候肯定被问过。但很多人背得滚瓜烂熟,真用起来还是一脸懵。我个人的理解是这样的:
- 镜像(Image):说白了就是一个只读的模板。它包含了运行某个应用所需的一切——代码、运行时、系统工具、库、配置。你可以把它想象成一个“光盘”,刻好了就不能改了。
- 容器(Container):镜像跑起来之后,就成了容器。它是镜像的一个运行实例,有自己独立的文件系统、网络、进程空间。说白了,就是“光盘”插到“光驱”里开始读盘了。
- 仓库(Registry):存放镜像的地方。Docker Hub 是官方的公共仓库,你也可以搭私有的,比如 Harbor。
核心关系一句话:镜像构建容器,容器运行应用,仓库分发镜像。
我在项目中遇到过不少新手,把镜像和容器搞混。嗯,这里有个小技巧:你执行 docker images 看到的是镜像列表,执行 docker ps 看到的是正在运行的容器。记住这个,基本就不会错了。
2.2 Docker 安装与配置
安装 Docker 其实很简单,但不同系统有点区别。我主要讲 Linux 环境,毕竟生产环境基本都是 Linux。
2.2.1 在 Ubuntu 上安装 Docker
我个人习惯用官方脚本一键安装,省心:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
装完之后,记得把当前用户加到 docker 组里,不然每次都要 sudo:
sudo usermod -aG docker $USER
newgrp docker
验证一下:
docker --version
docker run hello-world
看到 Hello from Docker! 就说明成了。
小提示:如果你用的是 CentOS 或 RHEL,建议先卸载旧版本(podman、buildah 之类的),再装 Docker。我曾经踩过这个坑,旧版本冲突导致 Docker daemon 起不来。
2.2.2 配置镜像加速器
国内拉镜像慢,这是老生常谈的问题了。我建议你配个加速器,比如阿里云、中科大、清华的镜像源。配置方法如下:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://你的加速器地址"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
注意:加速器地址需要去对应平台申请,每个人的不一样。别直接复制网上的,大概率用不了。
2.3 Dockerfile 编写实战
Dockerfile 是 Docker 的灵魂。你写的每一行指令,都会生成一个镜像层。层数越少,镜像越小,构建越快。说白了,Dockerfile 就是一份“菜谱”,告诉 Docker 怎么一步步做出你要的镜像。
2.3.1 一个简单的 Node.js 应用 Dockerfile
咱们直接上手写一个。假设你有一个 Node.js 应用,目录结构如下:
my-app/
├── package.json
├── app.js
└── Dockerfile
Dockerfile 内容:
# 1. 指定基础镜像
FROM node:18-alpine
# 2. 设置工作目录
WORKDIR /app
# 3. 复制依赖文件
COPY package.json ./
# 4. 安装依赖
RUN npm install --production
# 5. 复制源码
COPY . .
# 6. 暴露端口
EXPOSE 3000
# 7. 启动命令
CMD ["node", "app.js"]
构建并运行:
docker build -t my-app:1.0 .
docker run -d -p 3000:3000 my-app:1.0
关键点:为什么先 COPY package.json 再 COPY 源码?因为 Docker 构建时会缓存每一层。如果你先 COPY 所有文件,只要源码改了,npm install 就得重跑。先 COPY package.json,只要依赖没变,这层缓存就能复用。构建速度能快不少。
2.3.2 多阶段构建(Multi-stage Build)
这个技巧我强烈推荐。尤其是编译型语言,比如 Go、Java、C++。你想想看,编译环境需要一堆工具链,但运行时根本不需要。多阶段构建就是让你“编译在一个镜像里,运行在另一个镜像里”。
拿 Go 应用举例:
# 第一阶段:编译
FROM golang:1.20 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .
# 第二阶段:运行
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
最终镜像只有几 MB,而如果不用多阶段构建,可能几百 MB。我在项目中遇到过,一个 Java 应用镜像 1.2GB,用多阶段构建优化后只剩 180MB。部署速度、存储成本都大幅下降。
2.3.3 最佳实践与避坑指南
- 尽量使用 Alpine 基础镜像:它基于 musl libc 和 busybox,体积小、安全。但要注意,有些 C 扩展可能不兼容,需要额外处理。
- 减少层数:能用
RUN合并的命令就合并。比如RUN apt-get update && apt-get install -y xxx && rm -rf /var/lib/apt/lists/*,这样只生成一层。 - 不要用 root 用户运行容器:默认是 root,但安全风险高。建议在 Dockerfile 里创建普通用户:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
我曾经踩过的坑:有一次在生产环境,容器里用 root 运行,结果应用被入侵,攻击者直接拿到了宿主机的 root 权限。从那以后,我所有 Dockerfile 都强制指定非 root 用户。别嫌麻烦,安全无小事。
2.4 常用 Docker 命令速查
| 命令 | 作用 | 示例 |
|---|---|---|
docker images |
查看本地镜像列表 | docker images | grep my-app |
docker ps |
查看运行中的容器 | docker ps -a 查看所有容器 |
docker build |
构建镜像 | docker build -t my-app:1.0 . |
docker run |
运行容器 | docker run -d -p 8080:80 nginx |
docker exec |
进入容器内部 | docker exec -it container_id sh |
docker logs |
查看容器日志 | docker logs -f container_id |
docker rm |
删除容器 | docker rm -f container_id |
docker rmi |
删除镜像 | docker rmi image_id |
这些命令你每天都会用,建议记熟。我个人习惯把常用命令写成 alias,比如 alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"',这样看起来更清爽。
2.5 本章小结
这一章咱们把 Docker 的核心概念、安装配置、Dockerfile 编写都过了一遍。说白了,Docker 就是“打包应用、隔离运行”的工具。你只要记住:镜像不可变、容器是实例、仓库是源头。Dockerfile 写得好,后面 Kubernetes 的 Pod 定义才能写得顺。
下一章,咱们要进入 Kubernetes 的世界了。那才是云原生的重头戏。做好准备,咱们继续往前冲。
课后练习:自己写一个 Python Flask 应用的 Dockerfile,要求使用多阶段构建,最终镜像体积控制在 50MB 以内。试试看,不难。
公众号:蓝海资料掘金营,微信deep3321