第一章:容器化基础——Docker核心概念、镜像与容器、Dockerfile编写规范、Docker Compose入门

大家好,我是你们的老朋友。今天咱们正式开始聊云原生应用容器化改造。说实话,我接触Docker也有七八年了,从最初在测试环境里瞎折腾,到后来在生产环境大规模落地,踩过的坑真不少。这一章,咱们就把容器化的地基打牢。

1.1 Docker核心概念:镜像、容器、仓库

先说说最核心的三个概念。你想想看,Docker到底解决了什么问题?说白了,就是「环境一致性」这个老大难。以前我们经常遇到「在我机器上跑得好好的」这种尴尬,Docker就是来终结这个问题的。

  • 镜像(Image):一个只读的模板,包含了运行应用所需的一切——代码、运行时、系统工具、库、配置。你可以把它理解成一个「快照」或者「压缩包」。
  • 容器(Container):镜像的运行实例。镜像启动后就变成了容器,容器是可写的,你可以往里面写日志、改文件。但注意,容器一旦被删除,这些改动就没了。
  • 仓库(Registry):存放镜像的地方。最常用的是Docker Hub,企业里一般用Harbor或者阿里云镜像仓库。

重要理解:镜像和容器的关系,就像类和对象的关系。镜像是静态的,容器是动态的。我见过不少新手把镜像和容器混为一谈,嗯,这里要特别注意。

1.2 镜像与容器的基本操作

光说不练假把式。咱们直接上手操作。我个人习惯把常用命令记在便签上,因为有些参数确实容易忘。

镜像操作

# 拉取镜像
docker pull nginx:1.25-alpine

# 查看本地镜像
docker images

# 删除镜像
docker rmi nginx:1.25-alpine

# 构建镜像(后面会细讲)
docker build -t my-app:v1 .

容器操作

# 创建并启动容器
docker run -d --name my-nginx -p 8080:80 nginx:1.25-alpine

# 查看运行中的容器
docker ps

# 查看所有容器(包括已停止的)
docker ps -a

# 进入容器内部
docker exec -it my-nginx /bin/sh

# 停止容器
docker stop my-nginx

# 启动已停止的容器
docker start my-nginx

# 删除容器
docker rm my-nginx

小技巧:我建议你给容器命名(--name参数),不然Docker会随机生成一个名字,比如「focused_curie」这种,时间长了根本分不清哪个是哪个。

1.3 Dockerfile编写规范

Dockerfile是构建镜像的「配方」。写得好不好,直接影响镜像大小、构建速度和安全性。我在项目中遇到过不少因为Dockerfile写得随意导致线上问题的案例。

一个标准的Dockerfile示例

# 1. 选择基础镜像
FROM node:18-alpine AS builder

# 2. 设置工作目录
WORKDIR /app

# 3. 先复制依赖文件,利用缓存
COPY package*.json ./

# 4. 安装依赖
RUN npm ci --only=production

# 5. 复制源码
COPY . .

# 6. 构建应用
RUN npm run build

# 7. 多阶段构建:用更小的镜像运行
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

编写规范要点

  • 选择合适的基础镜像:能用alpine就别用ubuntu。alpine只有5MB,ubuntu有200MB。我曾经把一个Java应用的镜像从1.2GB压缩到180MB,就是靠换基础镜像。
  • 减少层数:每个RUN、COPY、ADD都会创建一层。可以用&&把多个命令连起来。
  • 利用构建缓存:把不常变动的指令放在前面(比如安装依赖),常变动的放在后面(比如复制源码)。
  • 使用.dockerignore:排除不需要的文件,比如node_modules、.git、日志文件。
  • 多阶段构建:构建阶段用大镜像,运行阶段用小镜像。这是生产环境的标配。

避坑指南:我曾经犯过一个错误——在Dockerfile里直接写RUN npm install而不是npm ci。npm install会更新package-lock.json,导致每次构建都不同。npm ci则严格按照lock文件安装,保证一致性。

1.4 Docker Compose入门

单机单容器还好说,一旦涉及多个服务(比如前端+后端+数据库+Redis),手动启动就太痛苦了。Docker Compose就是用来解决这个问题的——用一个YAML文件定义和运行多容器应用。

一个典型的docker-compose.yml

version: '3.8'

services:
  # 后端服务
  backend:
    build: ./backend
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
      - DB_PORT=3306
    depends_on:
      - db
    networks:
      - app-net

  # 前端服务
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend
    networks:
      - app-net

  # 数据库
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: myapp
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - app-net

volumes:
  db-data:

networks:
  app-net:

常用命令

# 启动所有服务
docker-compose up -d

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs -f

# 停止并删除所有服务
docker-compose down

# 重新构建并启动
docker-compose up -d --build

关键点:Compose文件里的服务名就是容器间的DNS名称。比如上面backend服务里配置了DB_HOST=db,它就能直接通过db这个主机名访问到MySQL容器。你想想看,这比配IP地址方便多了吧?

1.5 实战经验总结

最后,分享几个我在实际项目中总结的经验:

  1. 镜像标签不要用latest:生产环境一定要指定具体版本号,比如nginx:1.25.3-alpine。latest会随着时间变化,哪天拉下来一个不兼容的版本就麻烦了。
  2. 容器数据要持久化:数据库、日志、上传文件这些数据,一定要用volume挂载出来。不然容器一删,数据全没了。我见过有人把数据库跑在容器里没挂载volume,结果升级时把数据搞丢了……
  3. 资源限制要加上docker run --memory=512m --cpus=0.5。不加限制的话,一个容器可能把宿主机所有资源吃光。
  4. 日志要管理:容器默认会把日志写到宿主机,时间长了会撑爆磁盘。建议配置日志轮转:--log-opt max-size=10m --log-opt max-file=3

好了,第一章的内容就到这里。这一章我们打下了容器化的基础——理解了镜像和容器的关系,学会了Dockerfile的编写规范,也知道了怎么用Compose编排多容器应用。下一章,咱们会深入聊聊容器网络和存储,这些都是生产环境绕不开的话题。

记住一句话:容器化不是目的,稳定高效地交付才是。咱们下章见。