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