05. Docker 练习项目
5.1 项目 1:全栈 Web 应用(MERN Stack)
5.1.1 项目概述
技术栈:
- MongoDB - 数据库
- Express - 后端框架
- React - 前端框架
- Node.js - 运行时环境
项目结构:
mern-app/
├── docker-compose.yml
├── .env
├── backend/
│ ├── Dockerfile
│ ├── package.json
│ ├── server.js
│ └── src/
├── frontend/
│ ├── Dockerfile
│ ├── Dockerfile.prod
│ ├── package.json
│ └── src/
└── nginx/
└── nginx.conf
5.1.2 后端 Dockerfile
backend/Dockerfile:
dockerfile
FROM node:18-alpine
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
USER nodejs
EXPOSE 5000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
CMD node -e "require('http').get('http://localhost:5000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
CMD ["node", "server.js"]
backend/server.js(示例):
javascript
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const app = express();
// 中间件
app.use(cors());
app.use(express.json());
// MongoDB 连接
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB connected'))
.catch(err => console.error('MongoDB connection error:', err));
// 路由
app.get('/api/health', (req, res) => {
res.json({ status: 'OK' });
});
app.get('/api/items', async (req, res) => {
// 业务逻辑
res.json({ items: [] });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
5.1.3 前端 Dockerfile
frontend/Dockerfile(开发环境):
dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
frontend/Dockerfile.prod(生产环境 - 多阶段构建):
dockerfile
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/build /usr/share/nginx/html
# 复制 NGINX 配置
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
5.1.4 Docker Compose 配置
docker-compose.yml:
yaml
version: '3.8'
services:
# MongoDB 数据库
mongodb:
image: mongo:6
container_name: mern-mongodb
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
MONGO_INITDB_DATABASE: ${MONGO_DB}
volumes:
- mongodb-data:/data/db
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
ports:
- "27017:27017"
networks:
- mern-network
restart: unless-stopped
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
interval: 10s
timeout: 5s
retries: 5
# 后端 API
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: mern-backend
environment:
NODE_ENV: ${NODE_ENV}
PORT: 5000
MONGO_URI: mongodb://${MONGO_ROOT_USER}:${MONGO_ROOT_PASSWORD}@mongodb:27017/${MONGO_DB}?authSource=admin
JWT_SECRET: ${JWT_SECRET}
ports:
- "5000:5000"
volumes:
- ./backend:/app
- /app/node_modules
depends_on:
mongodb:
condition: service_healthy
networks:
- mern-network
restart: unless-stopped
# 前端应用
frontend:
build:
context: ./frontend
dockerfile: ${FRONTEND_DOCKERFILE:-Dockerfile}
container_name: mern-frontend
environment:
REACT_APP_API_URL: ${REACT_APP_API_URL}
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
depends_on:
- backend
networks:
- mern-network
restart: unless-stopped
stdin_open: true
tty: true
volumes:
mongodb-data:
networks:
mern-network:
driver: bridge
.env 文件:
env
# 环境
NODE_ENV=development
# MongoDB
MONGO_ROOT_USER=admin
MONGO_ROOT_PASSWORD=secret123
MONGO_DB=mernapp
# JWT
JWT_SECRET=your-secret-key
# 前端
REACT_APP_API_URL=http://localhost:5000/api
FRONTEND_DOCKERFILE=Dockerfile
5.1.5 NGINX 反向代理(生产环境)
nginx/nginx.conf:
nginx
server {
listen 80;
server_name localhost;
# 前端静态文件
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# 代理后端 API
location /api {
proxy_pass http://backend:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
5.1.6 启动和使用
bash
# 开发环境
docker compose up -d
# 生产环境
NODE_ENV=production FRONTEND_DOCKERFILE=Dockerfile.prod docker compose up -d --build
# 查看日志
docker compose logs -f
# 进入 MongoDB
docker exec -it mern-mongodb mongosh -u admin -p secret123
# 停止服务
docker compose down
# 停止并删除数据
docker compose down -v
5.2 项目 2:电商平台微服务
5.2.1 架构设计
┌─────────────┐
│ NGINX │
│ (Gateway) │
└──────┬──────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ User │ │ Product │ │ Order │
│ Service │ │ Service │ │ Service │
└────┬────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└──────────────────┼──────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐
│PostgreSQL│ │ Redis │ │ RabbitMQ │
└─────────┘ └───────────┘ └───────────┘
5.2.2 Docker Compose 配置
docker-compose.yml:
yaml
version: '3.8'
services:
# =============================================================================
# 基础设施服务
# =============================================================================
# PostgreSQL 数据库
postgres:
image: postgres:15-alpine
container_name: ecommerce-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: ecommerce
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init-db.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Redis 缓存
redis:
image: redis:7-alpine
container_name: ecommerce-redis
command: redis-server --appendonly yes --requirepass redis123
volumes:
- redis-data:/data
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 5
# RabbitMQ 消息队列
rabbitmq:
image: rabbitmq:3-management-alpine
container_name: ecommerce-rabbitmq
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: admin123
ports:
- "15672:15672" # 管理界面
volumes:
- rabbitmq-data:/var/lib/rabbitmq
networks:
- backend
restart: unless-stopped
healthcheck:
test: rabbitmq-diagnostics -q ping
interval: 10s
timeout: 5s
retries: 5
# =============================================================================
# 微服务
# =============================================================================
# 用户服务
user-service:
build:
context: ./services/user
dockerfile: Dockerfile
container_name: user-service
environment:
SERVICE_NAME: user-service
PORT: 3001
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/ecommerce
REDIS_URL: redis://:redis123@redis:6379
JWT_SECRET: ${JWT_SECRET}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- frontend
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
interval: 30s
timeout: 10s
retries: 3
# 产品服务
product-service:
build:
context: ./services/product
dockerfile: Dockerfile
container_name: product-service
environment:
SERVICE_NAME: product-service
PORT: 3002
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/ecommerce
REDIS_URL: redis://:redis123@redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- frontend
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3002/health"]
interval: 30s
timeout: 10s
retries: 3
# 订单服务
order-service:
build:
context: ./services/order
dockerfile: Dockerfile
container_name: order-service
environment:
SERVICE_NAME: order-service
PORT: 3003
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/ecommerce
REDIS_URL: redis://:redis123@redis:6379
RABBITMQ_URL: amqp://admin:admin123@rabbitmq:5672
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
rabbitmq:
condition: service_healthy
networks:
- frontend
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3003/health"]
interval: 30s
timeout: 10s
retries: 3
# =============================================================================
# API 网关
# =============================================================================
nginx:
image: nginx:alpine
container_name: ecommerce-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
depends_on:
- user-service
- product-service
- order-service
networks:
- frontend
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 10s
timeout: 5s
retries: 3
# =============================================================================
# 监控和管理工具(可选)
# =============================================================================
# Adminer - 数据库管理
adminer:
image: adminer:latest
container_name: ecommerce-adminer
ports:
- "8080:8080"
networks:
- backend
restart: unless-stopped
profiles:
- tools
volumes:
postgres-data:
redis-data:
rabbitmq-data:
networks:
frontend:
driver: bridge
backend:
driver: bridge
5.2.3 NGINX 网关配置
nginx/nginx.conf:
nginx
events {
worker_connections 1024;
}
http {
upstream user_service {
server user-service:3001;
}
upstream product_service {
server product-service:3002;
}
upstream order_service {
server order-service:3003;
}
server {
listen 80;
server_name localhost;
# 健康检查
location /health {
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 用户服务
location /api/users {
proxy_pass http://user_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 产品服务
location /api/products {
proxy_pass http://product_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 订单服务
location /api/orders {
proxy_pass http://order_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
5.2.4 微服务 Dockerfile 示例
services/user/Dockerfile:
dockerfile
FROM node:18-alpine
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm ci --only=production
# 复制源代码
COPY . .
# 非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3001
HEALTHCHECK --interval=30s --timeout=3s \
CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
CMD ["node", "index.js"]
5.3 项目 3:DevOps 工具链
5.3.1 完整的 CI/CD 环境
docker-compose.yml:
yaml
version: '3.8'
services:
# GitLab CE
gitlab:
image: gitlab/gitlab-ce:latest
container_name: devops-gitlab
hostname: gitlab.local
ports:
- "8929:80"
- "2289:22"
volumes:
- gitlab-config:/etc/gitlab
- gitlab-logs:/var/log/gitlab
- gitlab-data:/var/opt/gitlab
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://gitlab.local:8929'
gitlab_rails['gitlab_shell_ssh_port'] = 2289
networks:
- devops
restart: unless-stopped
shm_size: '256m'
# Jenkins
jenkins:
image: jenkins/jenkins:lts
container_name: devops-jenkins
user: root
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins-data:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
JAVA_OPTS: "-Djenkins.install.runSetupWizard=false"
networks:
- devops
restart: unless-stopped
# SonarQube - 代码质量检查
sonarqube:
image: sonarqube:community
container_name: devops-sonarqube
ports:
- "9000:9000"
environment:
SONAR_JDBC_URL: jdbc:postgresql://postgres:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: sonar
volumes:
- sonarqube-data:/opt/sonarqube/data
- sonarqube-extensions:/opt/sonarqube/extensions
- sonarqube-logs:/opt/sonarqube/logs
depends_on:
- postgres
networks:
- devops
restart: unless-stopped
# Nexus - 制品库
nexus:
image: sonatype/nexus3:latest
container_name: devops-nexus
ports:
- "8081:8081"
- "8082:8082" # Docker Registry
volumes:
- nexus-data:/nexus-data
networks:
- devops
restart: unless-stopped
# PostgreSQL(SonarQube 数据库)
postgres:
image: postgres:15-alpine
container_name: devops-postgres
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
POSTGRES_DB: sonar
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- devops
restart: unless-stopped
# Harbor - Docker Registry
harbor:
image: goharbor/harbor:v2.8.0
container_name: devops-harbor
ports:
- "8090:80"
volumes:
- harbor-data:/data
networks:
- devops
restart: unless-stopped
volumes:
gitlab-config:
gitlab-logs:
gitlab-data:
jenkins-data:
sonarqube-data:
sonarqube-extensions:
sonarqube-logs:
nexus-data:
postgres-data:
harbor-data:
networks:
devops:
driver: bridge
5.4 项目 4:监控和日志系统
5.4.1 ELK + Prometheus + Grafana
docker-compose.yml:
yaml
version: '3.8'
services:
# =============================================================================
# ELK Stack(日志收集)
# =============================================================================
# Elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.10.0
container_name: monitoring-elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
networks:
- monitoring
restart: unless-stopped
# Kibana
kibana:
image: docker.elastic.co/kibana/kibana:8.10.0
container_name: monitoring-kibana
ports:
- "5601:5601"
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
depends_on:
- elasticsearch
networks:
- monitoring
restart: unless-stopped
# Logstash
logstash:
image: docker.elastic.co/logstash/logstash:8.10.0
container_name: monitoring-logstash
ports:
- "5000:5000"
- "9600:9600"
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline:ro
environment:
LS_JAVA_OPTS: "-Xms256m -Xmx256m"
depends_on:
- elasticsearch
networks:
- monitoring
restart: unless-stopped
# Filebeat(日志采集器)
filebeat:
image: docker.elastic.co/beats/filebeat:8.10.0
container_name: monitoring-filebeat
user: root
volumes:
- ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- elasticsearch
- logstash
networks:
- monitoring
restart: unless-stopped
# =============================================================================
# Prometheus + Grafana(监控)
# =============================================================================
# Prometheus
prometheus:
image: prom/prometheus:latest
container_name: monitoring-prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
networks:
- monitoring
restart: unless-stopped
# Grafana
grafana:
image: grafana/grafana:latest
container_name: monitoring-grafana
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin123
GF_INSTALL_PLUGINS: grafana-piechart-panel
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
depends_on:
- prometheus
networks:
- monitoring
restart: unless-stopped
# Node Exporter(主机监控)
node-exporter:
image: prom/node-exporter:latest
container_name: monitoring-node-exporter
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
networks:
- monitoring
restart: unless-stopped
# cAdvisor(容器监控)
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: monitoring-cadvisor
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
networks:
- monitoring
restart: unless-stopped
# AlertManager(告警)
alertmanager:
image: prom/alertmanager:latest
container_name: monitoring-alertmanager
ports:
- "9093:9093"
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager-data:/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
networks:
- monitoring
restart: unless-stopped
volumes:
elasticsearch-data:
prometheus-data:
grafana-data:
alertmanager-data:
networks:
monitoring:
driver: bridge
5.4.2 Prometheus 配置
prometheus/prometheus.yml:
yaml
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scrape_configs:
# Prometheus 自身
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Node Exporter
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
# cAdvisor
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
# 你的应用
- job_name: 'my-app'
static_configs:
- targets: ['app:3000']
5.5 项目 5:大数据处理平台
docker-compose.yml:
yaml
version: '3.8'
services:
# Hadoop NameNode
namenode:
image: bde2020/hadoop-namenode:2.0.0-hadoop3.2.1-java8
container_name: bigdata-namenode
ports:
- "9870:9870"
- "9000:9000"
volumes:
- hadoop-namenode:/hadoop/dfs/name
environment:
CLUSTER_NAME: bigdata
networks:
- bigdata
restart: unless-stopped
# Hadoop DataNode
datanode:
image: bde2020/hadoop-datanode:2.0.0-hadoop3.2.1-java8
container_name: bigdata-datanode
volumes:
- hadoop-datanode:/hadoop/dfs/data
environment:
SERVICE_PRECONDITION: "namenode:9870"
depends_on:
- namenode
networks:
- bigdata
restart: unless-stopped
# Spark Master
spark-master:
image: bde2020/spark-master:3.1.1-hadoop3.2
container_name: bigdata-spark-master
ports:
- "8081:8080"
- "7077:7077"
environment:
INIT_DAEMON_STEP: setup_spark
networks:
- bigdata
restart: unless-stopped
# Spark Worker
spark-worker:
image: bde2020/spark-worker:3.1.1-hadoop3.2
container_name: bigdata-spark-worker
depends_on:
- spark-master
environment:
SPARK_MASTER: spark://spark-master:7077
networks:
- bigdata
restart: unless-stopped
# Kafka
kafka:
image: wurstmeister/kafka:latest
container_name: bigdata-kafka
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: kafka
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_CREATE_TOPICS: "test:1:1"
depends_on:
- zookeeper
networks:
- bigdata
restart: unless-stopped
# Zookeeper
zookeeper:
image: wurstmeister/zookeeper:latest
container_name: bigdata-zookeeper
ports:
- "2181:2181"
networks:
- bigdata
restart: unless-stopped
volumes:
hadoop-namenode:
hadoop-datanode:
networks:
bigdata:
driver: bridge
5.6 常用管理脚本
5.6.1 项目启动脚本
bash
#!/bin/bash
echo "🚀 启动 Docker 服务..."
# 检查 Docker 是否运行
if ! docker info > /dev/null 2>&1; then
echo "❌ Docker 未运行,请先启动 Docker"
exit 1
fi
# 拉取最新镜像
echo "📦 拉取最新镜像..."
docker compose pull
# 构建自定义镜像
echo "🔨 构建镜像..."
docker compose build
# 启动服务
echo "▶️ 启动服务..."
docker compose up -d
# 等待服务就绪
echo "⏳ 等待服务启动..."
sleep 10
# 检查服务状态
echo "📊 服务状态:"
docker compose ps
echo "✅ 所有服务已启动!"
5.6.2 备份脚本
bash
#!/bin/bash
BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
echo "🔄 开始备份..."
# 备份数据库
docker compose exec -T db pg_dump -U postgres mydb > "$BACKUP_DIR/db_$DATE.sql"
# 备份数据卷
docker run --rm \
-v myproject_data:/source:ro \
-v $(pwd)/$BACKUP_DIR:/backup \
ubuntu \
tar czf /backup/volumes_$DATE.tar.gz -C /source .
echo "✅ 备份完成: $BACKUP_DIR"