【docker基础】第十二周:综合实战项目 前言回顾系统学习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 超详细小白教程本课学习目标项目架构设计前端 后端 数据库 缓存多服务开发Node.js API Vue.js 前端配置文件MySQL、Redis、Nginx环境配置开发环境和生产环境资源限制CPU、内存限制监控日志日志轮转、健康检查部署流程构建、推送、部署性能优化网络、存储、缓存优化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.jsconst 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 后端 Dockerfilebackend/Dockerfile# 使用 Node.js Alpine 镜像减小体积 FROM node:18-alpine # 创建应用目录 WORKDIR /app # 复制 package 文件 COPY package*.json ./ # 安装依赖使用缓存层 RUN npm ci --onlyproduction # 复制应用代码 COPY src/ ./src/ # 暴露端口 EXPOSE 3000 # 健康检查 HEALTHCHECK --interval30s --timeout3s \ CMD wget -qO- http://localhost:3000/health || exit 1 # 启动命令 CMD [node, src/index.js]第四章前端服务开发4.1 前端代码frontend/public/index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.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# onclickloadArticles()首页/a a href# onclickshowForm()发布文章/a /nav /header div classcontainer div idstatus classstatus styledisplay: none;/div div idcontent/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 classarticle-card h2${article.title}/h2 div classarticle-meta作者: ${article.author} | ${new Date(article.created_at).toLocaleString()}/div div classarticle-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 classarticle-card h2发布新文章/h2 form onsubmitsubmitArticle(event) p stylemargin-bottom: 10px; input typetext idtitle placeholder标题 required stylewidth: 100%; padding: 10px; /p p stylemargin-bottom: 10px; input typetext idauthor placeholder作者 required stylewidth: 100%; padding: 10px; /p p stylemargin-bottom: 10px; textarea idcontent placeholder内容 rows5 required stylewidth: 100%; padding: 10px;/textarea /p p button typesubmit stylepadding: 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 /html4.2 前端 Dockerfilefrontend/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 --frombuilder /app/dist /usr/share/nginx/html # 复制 Nginx 配置 COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD [nginx, -g, daemon off;]frontend/nginx.confevents { 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 插入测试数据 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 notice5.3 Nginx 配置nginx/conf.d/blog.confupstream 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.ymldocker-compose.ymlversion: 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_PASSWORDyour_secure_password_here # 其他配置 NODE_ENVdevelopment TZAsia/Shanghai第七章生产环境配置7.1 docker-compose.prod.ymldocker-compose.prod.ymlversion: 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.ymlglobal: 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/articles9.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 userserver 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 -f9.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-pluginmysql_native_password --innodb-buffer-pool-size256M10.3 缓存优化合理设置 Redis 缓存时间使用缓存预热监控缓存命中率# 查看 Redis 统计信息 docker exec blog-redis redis-cli info stats本周总结本周我们完成了一个完整的实战项目项目架构设计前端 后端 数据库 缓存多服务开发Node.js API Vue.js 前端配置文件MySQL、Redis、Nginx环境配置开发环境和生产环境资源限制CPU、内存限制监控日志日志轮转、健康检查部署流程构建、推送、部署性能优化网络、存储、缓存优化练习作业完成整个项目的部署配置健康检查和监控实现蓝绿部署