Docker 完整教程(5,6) | 容器编译与编排
本教程侧重于命令实践和理解,提供可在本地环境测试的实例,每章结束都有总结要点。
目录
前边篇章介绍了
- Docker 基础概念和安装
- Docker 常用命令实践
- Docker 网络机制详解
- Docker 数据卷和挂载
本篇内容:
- Dockerfile 编写和镜像构建
- Docker Compose 多容器编排
本系列内容较多,建议在侧边栏根据需要点击目录进行跳转。
第5章:Dockerfile 编写和镜像构建
5.1 Dockerfile 基础
Dockerfile 是一个文本文件,包含了构建 Docker 镜像的所有指令。通过 Dockerfile,我们可以自动化地创建自定义镜像。
Dockerfile 基本结构
# 基础镜像
FROM ubuntu:20.04# 维护者信息
LABEL maintainer="your-email@example.com"# 设置工作目录
WORKDIR /app# 复制文件
COPY . .# 安装依赖
RUN apt-get update && apt-get install -y python3# 暴露端口
EXPOSE 8080# 启动命令
CMD ["python3", "app.py"]
第一个 Dockerfile
# 创建项目目录
mkdir -p /tmp/docker-tutorial/first-dockerfile
cd /tmp/docker-tutorial/first-dockerfile# 创建应用文件
cat > app.py << 'EOF'
#!/usr/bin/env python3
print("Hello from my first Docker image!")
print("This is a simple Python application.")
EOF# 创建 Dockerfile
cat > Dockerfile << 'EOF'
FROM python:3.11-slimWORKDIR /appCOPY app.py .CMD ["python", "app.py"]
EOF# 构建镜像
docker build -t my-first-image .# 运行容器
docker run --rm my-first-image# 查看镜像
docker images my-first-image
5.2 Dockerfile 指令详解
FROM - 基础镜像
# 使用官方镜像
FROM python:3.11-slim# 使用特定版本
FROM node:18.17.0-alpine# 使用多阶段构建
FROM golang:1.21 AS builder
FROM alpine:latest AS runtime
RUN - 执行命令
# 单个命令
RUN apt-get update# 多个命令(推荐)
RUN apt-get update && \apt-get install -y \curl \vim \git && \apt-get clean && \rm -rf /var/lib/apt/lists/*# 使用 shell 形式
RUN echo "Hello World"# 使用 exec 形式
RUN ["echo", "Hello World"]
COPY 和 ADD
# COPY - 复制文件(推荐)
COPY app.py /app/
COPY requirements.txt /app/
COPY . /app/# ADD - 复制文件(支持 URL 和自动解压)
ADD https://example.com/file.tar.gz /tmp/
ADD archive.tar.gz /app/# 复制并设置权限
COPY --chown=1000:1000 app.py /app/
WORKDIR - 工作目录
# 设置工作目录
WORKDIR /app# 相对路径(基于当前 WORKDIR)
WORKDIR subdir# 绝对路径
WORKDIR /var/log
ENV - 环境变量
# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000
ENV DATABASE_URL=postgresql://localhost/mydb# 一次设置多个
ENV NODE_ENV=production \PORT=3000 \DEBUG=false
EXPOSE - 暴露端口
# 暴露单个端口
EXPOSE 8080# 暴露多个端口
EXPOSE 8080 8443# 指定协议
EXPOSE 53/udp
EXPOSE 80/tcp
CMD 和 ENTRYPOINT
# CMD - 默认命令(可被覆盖)
CMD ["python", "app.py"]
CMD python app.py# ENTRYPOINT - 入口点(不可被覆盖)
ENTRYPOINT ["python", "app.py"]# 组合使用
ENTRYPOINT ["python"]
CMD ["app.py"]
为方便解释区别,以组合为例,比如:
FROM python:3.9
ENTRYPOINT ["python"]
CMD ["app.py"]
运行效果:
# 默认运行
docker run myapp
# 实际执行:python app.py# 运行不同的 Python 脚本 | CMD 命令会被替换
docker run myapp test.py
# 实际执行:python test.py# 传递参数给脚本
docker run myapp app.py --debug
# 实际执行:python app.py --debug
5.3 实践练习
练习1:Python Web 应用
# 创建 Python Web 应用项目
mkdir -p /tmp/docker-tutorial/python-webapp
cd /tmp/docker-tutorial/python-webapp# 创建应用代码
cat > app.py << 'EOF'
from flask import Flask, jsonify
import os
import socketapp = Flask(__name__)@app.route('/')
def hello():return jsonify({'message': 'Hello from Python Web App!','hostname': socket.gethostname(),'environment': os.environ.get('ENVIRONMENT', 'development')})@app.route('/health')
def health():return jsonify({'status': 'healthy'})if __name__ == '__main__':app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
EOF# 创建依赖文件
cat > requirements.txt << 'EOF'
Flask==2.3.3
gunicorn==21.2.0
EOF# 创建 Dockerfile
cat > Dockerfile << 'EOF'
FROM python:3.11-slim# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV PORT=5000
ENV ENVIRONMENT=production# 创建应用用户
RUN groupadd -r appuser && useradd -r -g appuser appuser# 设置工作目录
WORKDIR /app# 复制依赖文件并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt# 复制应用代码
COPY app.py .# 更改文件所有者
RUN chown -R appuser:appuser /app# 切换到非 root 用户
USER appuser# 暴露端口
EXPOSE 5000# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \CMD curl -f http://localhost:5000/health || exit 1# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
EOF# 构建镜像
docker build -t python-webapp .# 运行容器
docker run -d --name webapp -p 5000:5000 python-webapp# 测试应用
sleep 5
curl http://localhost:5000
curl http://localhost:5000/health# 查看健康状态
docker ps# 清理
docker stop webapp
docker rm webapp
练习2:Node.js 应用(多阶段构建)
# 创建 Node.js 项目
mkdir -p /tmp/docker-tutorial/nodejs-app
cd /tmp/docker-tutorial/nodejs-app# 创建 package.json
cat > package.json << 'EOF'
{"name": "nodejs-docker-app","version": "1.0.0","description": "Node.js app with Docker","main": "server.js","scripts": {"start": "node server.js","dev": "nodemon server.js"},"dependencies": {"express": "^4.18.2"},"devDependencies": {"nodemon": "^3.0.1"}
}
EOF# 创建服务器代码
cat > server.js << 'EOF'
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;app.get('/', (req, res) => {res.json({message: 'Hello from Node.js Docker app!',timestamp: new Date().toISOString(),environment: process.env.NODE_ENV || 'development'});
});app.get('/health', (req, res) => {res.json({ status: 'healthy' });
});app.listen(port, '0.0.0.0', () => {console.log(`Server running on port ${port}`);
});
EOF# 创建 .dockerignore
cat > .dockerignore << 'EOF'
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.DS_Store
EOF# 创建多阶段 Dockerfile
cat > Dockerfile << 'EOF'
# 构建阶段
FROM node:18-alpine AS builderWORKDIR /app# 复制 package 文件
COPY package*.json ./# 安装所有依赖(包括开发依赖)
RUN npm ci --only=production && npm cache clean --force# 运行阶段
FROM node:18-alpine AS runtime# 创建应用用户
RUN addgroup -g 1001 -S nodejs && \adduser -S nextjs -u 1001WORKDIR /app# 从构建阶段复制 node_modules
COPY --from=builder /app/node_modules ./node_modules# 复制应用代码
COPY --chown=nextjs:nodejs server.js .
COPY --chown=nextjs:nodejs package*.json ./# 切换到非 root 用户
USER nextjs# 暴露端口
EXPOSE 3000# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1# 启动应用
CMD ["node", "server.js"]
EOF# 构建镜像
docker build -t nodejs-app .# 运行容器
docker run -d --name nodeapp -p 3000:3000 -e NODE_ENV=production nodejs-app# 测试应用
sleep 5
curl http://localhost:3000
curl http://localhost:3000/health# 查看镜像大小
docker images nodejs-app# 清理
docker stop nodeapp
docker rm nodeapp
5.4 构建优化技巧
使用 .dockerignore
# 创建 .dockerignore 文件
cat > .dockerignore << 'EOF'
# Git
.git
.gitignore# Documentation
README.md
docs/# Dependencies
node_modules/
__pycache__/
*.pyc# IDE
.vscode/
.idea/# OS
.DS_Store
Thumbs.db# Logs
*.log
logs/# Test files
test/
tests/
*.test# Build artifacts
dist/
build/
target/
EOF
docker 在构建镜像过程中,会忽略掉 .dockerignore 文件中指定的文件和目录。
层缓存优化
# 不好的做法 - 每次都重新安装依赖
FROM node:18-alpine
COPY . /app
WORKDIR /app
RUN npm install# 好的做法 - 利用层缓存
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
后者的做法利用了 Docker 的层缓存机制,只有在依赖配置文件(package.json 或 requirements.txt)发生变化时才会重新安装依赖,而业务代码的变化不会触发依赖安装。
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./ # ← 只复制依赖配置文件
RUN npm install # ← 只有依赖变化时才重新安装
COPY . . # ← 最后复制业务代码
减少镜像层数
# 不好的做法 - 多个 RUN 指令
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get clean# 好的做法 - 合并 RUN 指令
RUN apt-get update && \apt-get install -y curl git && \apt-get clean && \rm -rf /var/lib/apt/lists/*
5.5 构建上下文和高级技巧
构建参数 (ARG)
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpineARG BUILD_DATE
ARG VERSION
LABEL build_date=${BUILD_DATE}
LABEL version=${VERSION}# 构建时传递参数
# docker build --build-arg NODE_VERSION=16 --build-arg VERSION=1.0.0 .
留意 ARG 和 ENV 使用场景的区别:
- ARG = Argument(参数)→ 构建时的"参数",构建时用完就扔
- ENV = Environment(环境)→ 运行时的"环境变量",运行时也能用
多平台构建
# 创建构建器
docker buildx create --name multiplatform --use# 多平台构建
docker buildx build --platform linux/amd64,linux/arm64 -t my-app:latest .# 推送到仓库
docker buildx build --platform linux/amd64,linux/arm64 -t my-app:latest --push .
注:大多数容器化应用都是 Linux 容器,通过 Docker Desktop 使用 manifest list (多架构清单) 来实现跨平台支持。
构建缓存
# 使用构建缓存
docker build --cache-from my-app:latest -t my-app:new .# 禁用缓存
docker build --no-cache -t my-app .# 查看构建历史
docker history my-app
5.6 安全最佳实践
使用非 root 用户
FROM alpine:latest# 创建用户
RUN addgroup -g 1001 -S appgroup && \adduser -u 1001 -S appuser -G appgroup# 设置工作目录权限
WORKDIR /app
RUN chown appuser:appgroup /app# 切换用户
USER appuserCOPY --chown=appuser:appgroup . .
最小化攻击面
# 使用最小基础镜像
FROM alpine:latest# 只安装必要的包
RUN apk add --no-cache ca-certificates# 删除不必要的文件
RUN rm -rf /var/cache/apk/* /tmp/*# 使用特定版本而非 latest
FROM node:18.17.0-alpine
本章总结
在本章中,我们深入学习了 Dockerfile 编写和镜像构建:
- Dockerfile 基础:掌握了 Dockerfile 的基本语法和常用指令
- 指令详解:深入理解了 FROM、RUN、COPY、WORKDIR、ENV、EXPOSE、CMD、ENTRYPOINT 等指令
- 实践练习:通过 Python 和 Node.js 应用构建加深理解
- 构建优化:学会了使用 .dockerignore、层缓存、减少层数等优化技巧
- 高级技巧:了解了构建参数、多平台构建、构建缓存等高级特性
- 安全实践:掌握了非 root 用户、最小化攻击面等安全最佳实践
关键概念总结:
- 多阶段构建:减少最终镜像大小,分离构建和运行环境
- 层缓存:合理安排指令顺序,提高构建效率
- 构建上下文:理解 Docker 构建过程中的文件传输机制
- 镜像优化:通过各种技巧减少镜像大小和提高安全性
下一章我们将学习 Docker Compose,实现多容器应用的编排和管理。
第6章:Docker Compose 多容器编排
6.1 Docker Compose 简介
Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过 YAML 文件配置应用的服务,然后使用单个命令创建并启动所有服务。
为什么需要 Docker Compose
# 传统方式启动多容器应用(繁琐且容易出错)
docker network create app-network
docker run -d --name database --network app-network -e MYSQL_ROOT_PASSWORD=root mysql
docker run -d --name redis --network app-network redis
docker run -d --name web --network app-network -p 8080:80 nginx# 使用 Docker Compose(简单且可重复)
docker-compose up -d
安装 Docker Compose
# 检查是否已安装
docker-compose --version# macOS (通过 Docker Desktop 自带)
# 已包含在 Docker Desktop 中# Linux 安装
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose# 验证安装
docker-compose --version
6.2 Docker Compose 文件结构
基本语法
version: '3.8'services:service_name:image: image_nameports:- "host_port:container_port"environment:- ENV_VAR=valuevolumes:- host_path:container_pathnetworks:network_name:volumes:volume_name:
6.3 Compose 文件详解
服务配置选项
version: '3.8'services:app:# 使用镜像image: nginx:alpine# 或者构建镜像build:context: .dockerfile: Dockerfileargs:- BUILD_ARG=value# 容器名称container_name: my-app# 端口映射ports:- "8080:80"- "443:443"# 环境变量environment:- NODE_ENV=production- DEBUG=false# 环境变量文件env_file:- .env- .env.local# 数据卷volumes:- ./data:/app/data- app_logs:/app/logs# 网络networks:- frontend- backend# 依赖关系depends_on:- database- redis# 重启策略restart: unless-stopped# 资源限制deploy:resources:limits:memory: 512Mcpus: '0.5'# 健康检查healthcheck:test: ["CMD", "curl", "-f", "http://localhost"]interval: 30stimeout: 10sretries: 3
这里包括几个关键概念:
- image:使用的镜像名称(必填,除非使用 build 构建)
- build:构建镜像的上下文和 Dockerfile 路径
- container_name:容器名称,(可选,容器的唯一标识,默认为
{项目名}_{服务名}_{序号}
) - ports:端口映射
- environment:环境变量
- env_file:环境变量文件(默认读取
.env
文件) - volumes:数据卷挂载
- networks:网络连接
- depends_on:依赖服务(可选,确保服务启动顺序)
- restart:重启策略(默认
no
,可选always
、on-failure
、unless-stopped
) - deploy:用于配置资源约束****、副本数量和健康检查等部署相关设置。
网络配置
version: '3.8'services:web:image: nginxnetworks:- frontendapi:image: node:alpinenetworks:- frontend- backenddatabase:image: mysqlnetworks:- backendnetworks: # 这里定义了两个网络frontend:driver: bridgebackend:driver: bridgeinternal: true # 内部网络,无法访问外网
此外,可以通过 external: true
参数来指定外部已存在的网络。留意二者的区别:
internal: true
:只能和同一网络内的其他容器通信,无法访问外网。external: true
:声明这是一个已经存在的外部网络,在 docker-compose down 时不会删除这个网络。
数据卷配置
version: '3.8'services:app:image: nginxvolumes:- app_data:/app/data # 命名卷- ./config:/app/config:ro # 绑定挂载(只读)- /tmp:/app/tmp # 绑定挂载volumes:app_data:driver: localexternal_volume:external: true # 使用外部已存在的卷
6.4 Compose 命令详解
基本命令
# 启动服务
docker-compose up # 前台启动
docker-compose up -d # 后台启动
docker-compose up --build # 重新构建并启动
docker-compose up service_name # 启动指定服务# 停止服务
docker-compose stop # 停止所有服务
docker-compose stop service_name # 停止指定服务
docker-compose down # 停止并删除容器、网络
docker-compose down -v # 同时删除数据卷# 查看状态
docker-compose ps # 查看服务状态
docker-compose logs # 查看所有日志
docker-compose logs -f service_name # 实时查看指定服务日志# 执行命令
docker-compose exec service_name command # 在运行的容器中执行命令
docker-compose run service_name command # 运行一次性命令# 扩展服务
docker-compose up -d --scale web=3 # 扩展 web 服务到 3 个实例
配置管理
# 验证配置文件,展开默认值
docker-compose config# 使用多个配置文件
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up# 指定项目名称
docker-compose -p myproject up# 使用环境变量,优先级高于 .env 文件
export COMPOSE_PROJECT_NAME=myapp
docker-compose up
6.6 高级特性
服务扩展和负载均衡
version: '3.8'services:nginx:image: nginx:alpineports:- "80:80"volumes:- ./nginx.conf:/etc/nginx/nginx.confdepends_on:- webweb:build: .expose:- "3000"environment:- NODE_ENV=productiondatabase:image: mysql:8.0environment:- MYSQL_ROOT_PASSWORD=rootpass
# 扩展 web 服务
docker-compose up -d --scale web=3# nginx 配置负载均衡
upstream backend {server web_1:3000;server web_2:3000;server web_3:3000;
}
健康检查和依赖管理
version: '3.8'services:web:image: nginxhealthcheck:test: ["CMD", "curl", "-f", "http://localhost"]interval: 30stimeout: 10sretries: 3start_period: 40sdepends_on:api:condition: service_healthyapi:build: .healthcheck:test: ["CMD", "curl", "-f", "http://localhost:3000/health"]interval: 30stimeout: 10sretries: 3
本章总结
在本章中,我们全面学习了 Docker Compose 多容器编排:
- Compose 基础:理解了 Docker Compose 的作用和基本概念
- 文件结构:掌握了 docker-compose.yml 的语法和配置选项
- 服务配置:学会了配置服务、网络、数据卷等各种选项
- 实践应用:通过完整的 Web 应用栈和开发环境配置加深理解
- 命令操作:掌握了 Compose 的各种命令和使用技巧
- 高级特性:了解了服务扩展、负载均衡、健康检查等高级功能
Compose vs 手动管理对比:
- 简化操作:一个命令启动整个应用栈
- 配置管理:声明式配置,易于版本控制
- 环境一致性:确保开发、测试、生产环境一致
- 服务发现:自动的服务名称解析
- 扩展性:轻松扩展服务实例数量
最后一章我们将学习 Docker 镜像管理和仓库操作,包括镜像的推送、拉取、私有仓库搭建等内容。