【docker基础】第十二周:综合实战项目
2026/6/17 13:47:24 网站建设 项目流程

📚前言

👀回顾,系统学习docker系列已发布内容:

【docker基础】0、系统学习docker之总计划

【docker基础】第一课、从零开始理解容器技术

【docker基础】第二课:安装、配置与基础命令

【docker基础】第三课:镜像管理与Dockerfile基础

【docker基础】第四课:容器操作与数据管理

【docker基础】第五课:Docker网络详解-CSDN博客

【docker基础】第六课:Web应用与数据库容器部署-CSDN博客

【docker基础】 第七课:Docker Compose 多容器实战-CSDN博客

【docker基础】 第八周:容器监控与应用更新策略-CSDN博客

【docker基础】第九周:Docker安全与镜像优化-CSDN博客

【docker基础】Docker第十周:CI/CD集成-CSDN博客

【docker基础】第十一周:容器编排基础-CSDN博客

🔗相关文档:

windows下安装docker

【docker基础】Ubuntu 安装 Docker 超详细小白教程

📒本课学习目标:

  1. 项目架构设计:前端 + 后端 + 数据库 + 缓存
  2. 多服务开发:Node.js API + Vue.js 前端
  3. 配置文件:MySQL、Redis、Nginx
  4. 环境配置:开发环境和生产环境
  5. 资源限制:CPU、内存限制
  6. 监控日志:日志轮转、健康检查
  7. 部署流程:构建、推送、部署
  8. 性能优化:网络、存储、缓存优化

🌍Docker第十二周:综合实战项目

欢迎来到 Docker 第十二周的学习!本周我们将进行一个完整的实战项目,从代码开发到容器化部署,完成一个真实的生产级应用部署流程。


第一章:项目概述

1.1 项目架构

我们将部署一个完整的博客系统,包含以下组件:

┌─────────────────────────────────────────────────────────────┐ │ 用户请求 │ │ http://example.com │ └───────────────────────────┬─────────────────────────────────┘ │ ↓ ┌─────────────────────────────────────────────────────────────┐ │ Nginx 反向代理 │ │ (负载均衡 + SSL) │ └───────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────┼───────────────────┐ ↓ ↓ ↓ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Web Server 1 │ │ Web Server 2 │ │ Web Server 3 │ │ (Nginx) │ │ (Nginx) │ │ (Nginx) │ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │ │ │ └───────────────────┼───────────────────┘ │ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 应用服务器 (Node.js) │ │ API 服务集群 │ └───────────────────────────┬─────────────────────────────────┘ │ ↓ ┌─────────────────────────────────────────────────────────────┐ │ Redis 缓存服务器 │ │ (会话 + 热点数据) │ └─────────────────────────────────────────────────────────────┘ │ ↓ ┌─────────────────────────────────────────────────────────────┐ │ MySQL 数据库 │ │ (主从复制) │ └─────────────────────────────────────────────────────────────┘

1.2 技术栈

组件技术说明
前端Vue.js单页应用
后端Node.js + ExpressREST API
数据库MySQL 5.7关系型数据库
缓存Redis会话和缓存
Web服务器Nginx反向代理
容器编排Docker Compose本地开发
CI/CDGitHub Actions自动部署

第二章:项目结构

2.1 目录结构

blog-app/ ├── frontend/ # 前端代码 │ ├── src/ │ ├── public/ │ ├── Dockerfile │ └── package.json ├── backend/ # 后端代码 │ ├── src/ │ ├── Dockerfile │ └── package.json ├── nginx/ # Nginx 配置 │ ├── nginx.conf │ └── conf.d/ │ └── blog.conf ├── mysql/ # MySQL 配置 │ └── init.sql ├── redis/ # Redis 配置 │ └── redis.conf ├── monitoring/ # 监控配置 │ └── prometheus.yml ├── docker-compose.yml # 开发环境 ├── docker-compose.prod.yml # 生产环境 └── .env # 环境变量

2.2 创建项目目录

mkdir -p ~/blog-app/{frontend,backend,nginx/conf.d,mysql,redis,monitoring} cd ~/blog-app

第三章:后端服务开发

3.1 创建后端项目

backend/package.json

{ "name": "blog-api", "version": "1.0.0", "description": "Blog API Server", "main": "src/index.js", "scripts": { "start": "node src/index.js", "dev": "nodemon src/index.js" }, "dependencies": { "express": "^4.18.2", "mysql2": "^3.6.0", "redis": "^4.6.0", "cors": "^2.8.5", "dotenv": "^16.3.1" }, "devDependencies": { "nodemon": "^3.0.1" } }

3.2 后端代码

backend/src/index.js

const express = require('express'); const cors = require('cors'); const mysql = require('mysql2/promise'); const redis = require('redis'); require('dotenv').config(); const app = express(); const PORT = process.env.PORT || 3000; // 中间件 app.use(cors()); app.use(express.json()); // MySQL 连接池 const db = mysql.createPool({ host: process.env.DB_HOST || 'mysql', user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD, database: process.env.DB_NAME || 'blog', waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); // Redis 客户端 const redisClient = redis.createClient({ url: `redis://${process.env.REDIS_HOST || 'redis'}:${process.env.REDIS_PORT || 6379}` }); redisClient.on('error', (err) => console.log('Redis Client Error', err)); redisClient.connect().then(() => console.log('Redis Connected')); // 健康检查 app.get('/health', async (req, res) => { try { await db.query('SELECT 1'); res.json({ status: 'healthy', timestamp: new Date().toISOString() }); } catch (error) { res.status(500).json({ status: 'unhealthy', error: error.message }); } }); // 获取文章列表(带缓存) app.get('/api/articles', async (req, res) => { try { // 尝试从缓存获取 const cached = await redisClient.get('articles:list'); if (cached) { return res.json(JSON.parse(cached)); } // 从数据库获取 const [rows] = await db.query('SELECT * FROM articles ORDER BY created_at DESC'); // 存入缓存(60秒) await redisClient.setEx('articles:list', 60, JSON.stringify(rows)); res.json(rows); } catch (error) { console.error('Error fetching articles:', error); res.status(500).json({ error: 'Failed to fetch articles' }); } }); // 获取单篇文章 app.get('/api/articles/:id', async (req, res) => { try { const { id } = req.params; // 尝试从缓存获取 const cached = await redisClient.get(`articles:${id}`); if (cached) { return res.json(JSON.parse(cached)); } // 从数据库获取 const [rows] = await db.query('SELECT * FROM articles WHERE id = ?', [id]); if (rows.length === 0) { return res.status(404).json({ error: 'Article not found' }); } // 存入缓存 await redisClient.setEx(`articles:${id}`, 300, JSON.stringify(rows[0])); res.json(rows[0]); } catch (error) { console.error('Error fetching article:', error); res.status(500).json({ error: 'Failed to fetch article' }); } }); // 创建文章 app.post('/api/articles', async (req, res) => { try { const { title, content, author } = req.body; const [result] = await db.query( 'INSERT INTO articles (title, content, author) VALUES (?, ?, ?)', [title, content, author] ); // 清除列表缓存 await redisClient.del('articles:list'); res.status(201).json({ id: result.insertId, message: 'Article created' }); } catch (error) { console.error('Error creating article:', error); res.status(500).json({ error: 'Failed to create article' }); } }); // 启动服务器 app.listen(PORT, '0.0.0.0', () => { console.log(`Server running on port ${PORT}`); });

3.3 后端 Dockerfile

backend/Dockerfile

# 使用 Node.js Alpine 镜像(减小体积) FROM node:18-alpine # 创建应用目录 WORKDIR /app # 复制 package 文件 COPY package*.json ./ # 安装依赖(使用缓存层) RUN npm ci --only=production # 复制应用代码 COPY src/ ./src/ # 暴露端口 EXPOSE 3000 # 健康检查 HEALTHCHECK --interval=30s --timeout=3s \ CMD wget -qO- http://localhost:3000/health || exit 1 # 启动命令 CMD ["node", "src/index.js"]

第四章:前端服务开发

4.1 前端代码

frontend/public/index.html

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客系统</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: Arial, sans-serif; background: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; } header { background: #333; color: white; padding: 20px; text-align: center; } header h1 { margin-bottom: 10px; } nav a { color: white; margin: 0 15px; text-decoration: none; } .article-list { margin-top: 30px; } .article-card { background: white; padding: 20px; margin-bottom: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .article-card h2 { color: #333; margin-bottom: 10px; } .article-meta { color: #666; font-size: 14px; margin-bottom: 10px; } .article-content { line-height: 1.6; color: #444; } .status { padding: 10px; background: #4CAF50; color: white; text-align: center; margin-bottom: 20px; border-radius: 4px; } </style> </head> <body> <header> <h1>博客系统</h1> <nav> <a href="#" onclick="loadArticles()">首页</a> <a href="#" onclick="showForm()">发布文章</a> </nav> </header> <div class="container"> <div id="status" class="status" style="display: none;"></div> <div id="content"></div> </div> <script> const API_BASE = window.location.port === '8080' ? 'http://localhost:3000' : '/api'; async function loadArticles() { const status = document.getElementById('status'); const content = document.getElementById('content'); try { status.style.display = 'block'; status.textContent = '加载中...'; status.style.background = '#2196F3'; const response = await fetch(`${API_BASE}/articles`); const articles = await response.json(); status.style.background = '#4CAF50'; status.textContent = `共 ${articles.length} 篇文章`; content.innerHTML = articles.length === 0 ? '<p>暂无文章</p>' : articles.map(article => ` <div class="article-card"> <h2>${article.title}</h2> <div class="article-meta">作者: ${article.author} | ${new Date(article.created_at).toLocaleString()}</div> <div class="article-content">${article.content}</div> </div> `).join(''); } catch (error) { status.style.background = '#f44336'; status.textContent = '加载失败: ' + error.message; } setTimeout(() => { status.style.display = 'none'; }, 3000); } function showForm() { document.getElementById('content').innerHTML = ` <div class="article-card"> <h2>发布新文章</h2> <form onsubmit="submitArticle(event)"> <p style="margin-bottom: 10px;"> <input type="text" id="title" placeholder="标题" required style="width: 100%; padding: 10px;"> </p> <p style="margin-bottom: 10px;"> <input type="text" id="author" placeholder="作者" required style="width: 100%; padding: 10px;"> </p> <p style="margin-bottom: 10px;"> <textarea id="content" placeholder="内容" rows="5" required style="width: 100%; padding: 10px;"></textarea> </p> <p> <button type="submit" style="padding: 10px 30px; background: #333; color: white; border: none; cursor: pointer;">发布</button> </p> </form> </div> `; } async function submitArticle(event) { event.preventDefault(); const title = document.getElementById('title').value; const author = document.getElementById('author').value; const content = document.getElementById('content').value; try { const response = await fetch(`${API_BASE}/articles`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, author, content }) }); if (response.ok) { alert('发布成功!'); loadArticles(); } } catch (error) { alert('发布失败: ' + error.message); } } window.onload = loadArticles; </script> </body> </html>

4.2 前端 Dockerfile

frontend/Dockerfile

# 多阶段构建 # 阶段1:构建 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 阶段2:运行 FROM nginx:alpine # 复制构建产物 COPY --from=builder /app/dist /usr/share/nginx/html # 复制 Nginx 配置 COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

frontend/nginx.conf

events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:3000; 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; } } }

第五章:配置文件

5.1 MySQL 初始化脚本

mysql/init.sql

-- 创建数据库 CREATE DATABASE IF NOT EXISTS blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE blog; -- 创建文章表 CREATE TABLE IF NOT EXISTS articles ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, author VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 插入测试数据 INSERT INTO articles (title, content, author) VALUES ('欢迎使用博客系统', '这是我们的第一篇文章!', '管理员'), ('Docker 入门指南', '本文介绍 Docker 的基本概念和使用方法...', '技术编辑'), ('容器化最佳实践', '本文分享容器化部署的最佳实践...', 'DevOps工程师');

5.2 Redis 配置

redis/redis.conf

# 基本配置 port 6379 bind 0.0.0.0 protected-mode no # 持久化配置 save 900 1 save 300 10 save 60 10000 # 内存配置 maxmemory 256mb maxmemory-policy allkeys-lru # 日志配置 loglevel notice

5.3 Nginx 配置

nginx/conf.d/blog.conf

upstream backend { server backend1:3000; server backend2:3000; server backend3:3000; keepalive 32; } server { listen 80; server_name blog.example.com; # 前端静态文件 location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } # API 代理 location /api { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # 健康检查 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } }

第六章:开发环境配置

6.1 docker-compose.yml

docker-compose.yml

version: '3.8' services: # MySQL 数据库 mysql: image: mysql:5.7 container_name: blog-mysql environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: blog volumes: - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql - mysql_data:/var/lib/mysql ports: - "3306:3306" networks: - blog-network healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 5 # Redis 缓存 redis: image: redis:7-alpine container_name: blog-redis command: redis-server /usr/local/etc/redis/redis.conf volumes: - ./redis/redis.conf:/usr/local/etc/redis/redis.conf - redis_data:/data ports: - "6379:6379" networks: - blog-network healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3 # 后端服务1 backend1: build: ./backend container_name: blog-backend-1 environment: NODE_ENV: development DB_HOST: mysql DB_USER: root DB_PASSWORD: ${MYSQL_ROOT_PASSWORD} DB_NAME: blog REDIS_HOST: redis PORT: 3000 volumes: - ./backend/src:/app/src networks: - blog-network depends_on: mysql: condition: service_healthy redis: condition: service_healthy # 后端服务2 backend2: build: ./backend container_name: blog-backend-2 environment: NODE_ENV: development DB_HOST: mysql DB_USER: root DB_PASSWORD: ${MYSQL_ROOT_PASSWORD} DB_NAME: blog REDIS_HOST: redis PORT: 3000 volumes: - ./backend/src:/app/src networks: - blog-network depends_on: mysql: condition: service_healthy redis: condition: service_healthy # 前端服务 frontend: image: nginx:alpine container_name: blog-frontend volumes: - ./frontend/public:/usr/share/nginx/html - ./nginx/nginx.conf:/etc/nginx/nginx.conf ports: - "8080:80" networks: - blog-network depends_on: - backend1 - backend2 networks: blog-network: driver: bridge volumes: mysql_data: redis_data:

6.2 .env 文件

.env

# 数据库 MYSQL_ROOT_PASSWORD=your_secure_password_here # 其他配置 NODE_ENV=development TZ=Asia/Shanghai

第七章:生产环境配置

7.1 docker-compose.prod.yml

docker-compose.prod.yml

version: '3.8' services: mysql: image: mysql:5.7 container_name: blog-mysql environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: blog volumes: - mysql_data:/var/lib/mysql ports: - "127.0.0.1:3306:3306" networks: - blog-network restart: always healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine container_name: blog-redis command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis_data:/data ports: - "127.0.0.1:6379:6379" networks: - blog-network restart: always backend: image: ${DOCKER_REGISTRY}/blog-backend:${VERSION} container_name: blog-backend environment: NODE_ENV: production DB_HOST: mysql DB_USER: root DB_PASSWORD: ${MYSQL_ROOT_PASSWORD} DB_NAME: blog REDIS_HOST: redis REDIS_PASSWORD: ${REDIS_PASSWORD} PORT: 3000 deploy: replicas: 3 resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M networks: - blog-network depends_on: mysql: condition: service_healthy redis: condition: service_healthy restart: always nginx: image: nginx:alpine container_name: blog-nginx volumes: - ./nginx/conf.d:/etc/nginx/conf.d - nginx_logs:/var/log/nginx ports: - "80:80" - "443:443" networks: - blog-network depends_on: - backend restart: always networks: blog-network: driver: bridge volumes: mysql_data: redis_data: nginx_logs:

7.2 资源限制配置

在生产环境中,我们使用deploy.resources进行资源限制:

deploy: replicas: 3 resources: limits: cpus: '0.5' # 每个容器最多使用 0.5 个 CPU memory: 512M # 每个容器最多使用 512MB 内存 reservations: cpus: '0.25' # 预留资源 memory: 256M

第八章:监控与日志

8.1 Prometheus 配置

monitoring/prometheus.yml

global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'blog-backend' static_configs: - targets: ['backend:3000'] labels: app: 'blog-api' - job_name: 'nginx' static_configs: - targets: ['nginx:80']

8.2 日志管理

配置日志轮转:

services: backend: image: ${DOCKER_REGISTRY}/blog-backend:${VERSION} logging: driver: "json-file" options: max-size: "10m" max-file: "3"

第九章:部署流程

9.1 开发环境启动

# 1. 克隆代码 git clone https://github.com/yourusername/blog-app.git cd blog-app # 2. 创建 .env 文件 cp .env.example .env # 编辑 .env 文件,设置密码 # 3. 启动开发环境 docker-compose up -d # 4. 查看服务状态 docker-compose ps # 5. 查看日志 docker-compose logs -f # 6. 访问应用 # 前端: http://localhost:8080 # API: http://localhost:8080/api/articles

9.2 生产环境部署

# 1. 构建镜像 docker-compose -f docker-compose.prod.yml build # 2. 标记镜像版本 docker tag blog-backend:latest ${DOCKER_REGISTRY}/blog-backend:${VERSION} docker tag blog-backend:latest ${DOCKER_REGISTRY}/blog-backend:latest # 3. 推送镜像 docker push ${DOCKER_REGISTRY}/blog-backend:${VERSION} docker push ${DOCKER_REGISTRY}/blog-backend:latest # 4. 部署到服务器 ssh user@server cd /opt/blog-app docker-compose -f docker-compose.prod.yml pull docker-compose -f docker-compose.prod.yml up -d # 5. 查看服务状态 docker-compose -f docker-compose.prod.yml ps docker-compose -f docker-compose.prod.yml logs -f

9.3 更新与回滚

# 更新服务 docker-compose -f docker-compose.prod.yml up -d --no-deps backend # 回滚到上一版本 docker-compose -f docker-compose.prod.yml rollback backend # 查看更新历史 docker-compose -f docker-compose.prod.yml ps

第十章:性能优化

10.1 网络优化

  • 使用keepalive连接池
  • 配置合理的缓冲区大小
  • 启用压缩(gzip)
# Nginx 配置 gzip on; gzip_types text/plain application/json application/javascript text/css; gzip_min_length 1000;

10.2 存储优化

services: mysql: image: mysql:5.7 command: --default-authentication-plugin=mysql_native_password --innodb-buffer-pool-size=256M

10.3 缓存优化

  • 合理设置 Redis 缓存时间
  • 使用缓存预热
  • 监控缓存命中率
# 查看 Redis 统计信息 docker exec blog-redis redis-cli info stats

本周总结

本周我们完成了一个完整的实战项目:

  1. 项目架构设计:前端 + 后端 + 数据库 + 缓存
  2. 多服务开发:Node.js API + Vue.js 前端
  3. 配置文件:MySQL、Redis、Nginx
  4. 环境配置:开发环境和生产环境
  5. 资源限制:CPU、内存限制
  6. 监控日志:日志轮转、健康检查
  7. 部署流程:构建、推送、部署
  8. 性能优化:网络、存储、缓存优化

练习作业

  1. 完成整个项目的部署
  2. 配置健康检查和监控
  3. 实现蓝绿部署

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询