案例一:ap2部署

基础工作:使用阿里通义生成部署的docker文件和nginx文件。

1. 问题

  • 服务器上的 Docker 版本过旧 (1.13.1),缺少 docker-compose 命令 通过docker解决

  • 由于网络限制,无法从 Docker Hub 拉取镜像 首先使用阿里云加速docker,本地编译vue,将编译后的前端文件打包上传;

  • python版本依赖问题 使用python list 得到全部包版本,交给LLM生成requirements.txt

  • 浏览器访问前端,一直显示空白页面 清除浏览器缓存,by ctrl + shift + del

  • 前端请求404

        这个问题常见,多半是请求地址错误

2. 操作

部署和更新

# 构建并启动服务
docker compose -f docker-compose.prod.yml up -d --build

# 查看服务状态
docker compose -f docker-compose.prod.yml ps

# 下面涉及的命令默认dockerfile文件名为docker-compose.yml
docker compose down #完全清理当前项目的容器环境,从头开始
docker compose build --no-cache frontend #重新构建名为"frontend"的服务镜像
docker compose up -d #启动所有服务并在后台运行,-d 或 --detach:后台运行模式

# 查看系统端口占用情况
netstat -tlnp | grep -E "(3000|8000)"

验证部署结果

  1. 检查容器运行状态:

    docker compose -f docker-compose.prod.yml ps
    
  2. 测试后端 API:

    curl -X POST http://localhost:8000/api/standardize \
      -H "Content-Type: application/json" \
      -d '{"text": "北京市朝阳区某某街道123号"}'
    
  3. 测试前端页面:

    curl http://localhost:3000
    

未经测试的复现命令

# 在当前服务器保存镜像为tar文件
docker save -o backend.tar ap-demo-2-backend:latest
docker save -o frontend.tar ap-demo-2-frontend:latest

# 将tar文件传到其他服务器(使用scp/rsync/ftp等)
scp backend.tar frontend.tar user@other-server:/tmp/

# 在其他服务器上加载镜像
docker load -i /tmp/backend.tar
docker load -i /tmp/frontend.tar

# 验证镜像已加载
docker images | grep ap-demo-2

3. docker相关概念

区分分镜像文件制作,镜像文件,和编排。

  • Dockerfile定义如何构建镜像(怎么做的过程定义)

  • 镜像是一个快照是一系列的文件,这包含了程序能够运行的一切条件(系统、环境、磁盘卷等)

  • 编排是容器如何运行和交互,如本项目中定义了前后端的接口,后端的热重启,环境变量等

    version: '3.8'
    
    services:
      backend:
        build: ./backend #使用 ./backend 目录下的 Dockerfile 来构建一个镜像,并用这个镜像来创建和运行服务容器
        ports:
          - "8000:8000"
        environment:#设置容器内的环境变量
          - DASHSCOPE_API_KEY=sk-36264a12c5962603a21ecc535aec8aeb
          - AMAP_KEY=c7520c6288555ca42a4ac2c5ceb9738
        volumes:
          - ./backend:/app #挂载后端文件,配合自定义启动命令,实现热重载,即修改服务器上后端代码,保存即可,无需重启docker
        command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload #自定义启动命令,使用 uvicorn 运行应用并开启热重载
    #若未定义command,则将使用Dockerfile中的CMD或ENTRYPOINT作为启动命令
    
      frontend:
        build: ./frontend
        ports:
          - "3000:80" #服务器端口:容器内部端口
        depends_on:
          - backend #当前前端容器,在后端容器启动后,再启动
        environment:
          - NODE_ENV=production
    

区别卷挂载和热重启

前者只是文件的同步,后者则是需要相关自定义command的支持

容器实例的创建过程

编写DockerFile通过 docker build 生成镜像 或者 docker pull 拉取公共镜像,再 docker run 创建实际的容器实例。

注意到本项目中使用了两类nginx配置,

容器内部 nginx:专注于静态文件服务和容器间通信

服务器 nginx:专注于域名解析和外部请求路由

这里是本项目的目录结构

ap-demo/
├── backend/
│   ├── Dockerfile              # 后端 Docker 配置文件
│   ├── main.py                 # 后端主应用文件
│   ├── llm_service.py          # LLM 服务实现
│   └── requirements.txt        # Python 依赖包列表
├── frontend/
│   ├── Dockerfile              # 前端 Docker 配置文件
│   ├── nginx.conf              # 前端容器内部 Nginx 配置
│   ├── vite.config.js          # Vite 构建配置
│   ├── index.html              # 前端入口 HTML 文件
│   ├── package.json            # 前端依赖配置
│   └── src/                    # 前端源代码目录
│       ├── App.vue             # Vue 主应用组件
│       ├── main.js             # 前端入口 JS 文件
│       ├── style.css           # 全局样式文件
│       ├── components/         # Vue 组件目录
│       │   ├── AddressParser.vue  # 地址解析主组件
│       │   └── HelloWorld.vue     # 示例组件
│       ├── api/                # API 接口目录
│       │   └── address.js         # 地址相关 API 调用封装
│       └── utils/              # 工具类目录
│           └── request.js         # HTTP 请求封装
├── docker-compose.yml          # Docker Compose 配置文件
├── docker-compose.prod.yml     # 生产环境 Docker Compose 配置文件
├── ap2.amebob.cn.conf          # 服务器 Nginx 域名配置文件
└── deployment-summary.md       # 部署总结文档

前端的dockerfile

创建并配置一个 Nginx 环境,并将构建好的静态文件放入其中。

FROM nginx:stable-alpine #官方的nginx镜像
COPY dist /usr/share/nginx/html #复制编译后的静态文件
COPY nginx.conf /etc/nginx/nginx.conf #复制自定义配置
EXPOSE 80 #暴露端口80,这个端口是容器内的
CMD ["nginx", "-g", "daemon off;"] #启动 Nginx 服务的命令

后的dockerfile

创建并配置了一个 Python 环境,并将后端代码放入其中

FROM python:3.9-slim

WORKDIR /app #设置工作目录为/app

# 使用国内镜像源安装依赖
COPY requirements.txt .  #复制依赖文件到/app目录下
RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

COPY . .  #将本地所有文件(除了 .dockerignore 中排除的)复制到容器的 /app 目录 包括 main.py、llm_service.py 等后端代码

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

4.Nginx代理

  • 对于多站点的Nginx配置,通常,我们在/etc/nginx/conf.d/下以域名命名的xxx.conf,
[root@iZgw0ee0yux4vr71p4i7xgZ ~]# cd /etc/nginx/conf.d/
[root@iZgw0ee0yux4vr71p4i7xgZ conf.d]# ls
address-parser.conf  amebob.cn.conf  ap2.amebob.cn.conf

而单个站点的conf,我们仅配置server部分(下面有站点nginx的示例)

  • 在服务器的Nginx配置中,我们会使用include /etc/nginx/conf.d/*.conf;来加载所有的站点Nginx配置。

    注意:后面的server配置会覆盖前面的(如果发生冲突)

[root@iZgw0ee0yux4vr71p4i7xgZ conf.d]# cat /etc/nginx/nginx.conf

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;


    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name _;
        
        root /home;
        index index.html;
        
        # 记录访问日志(可选)
        access_log /var/log/nginx/default_access.log;
        error_log /var/log/nginx/default_error.log warn;
        
        # 安全头
        add_header X-Frame-Options SAMEORIGIN;
        add_header X-Content-Type-Options nosniff;
        
        location / {
            try_files $uri $uri/ /index.html;
        }
        
        # 防止爬虫索引
        location /robots.txt {
            return 200 "User-agent: *\nDisallow: /\n";
        }
    }
}  
  • 注意到本项目中使用了两类nginx配置,

容器内部 nginx:专注于静态文件服务和容器间通信

站点nginx:专注于域名解析和外部请求路由

站点nginx

当用户访问 http://ap2.amebob.cn 时,Nginx 会将请求转发到本地运行的前端 Docker 容器(端口 3000),同时保留用户的原始请求信息

server {
    listen 80;
    server_name ap2.amebob.cn; #处理外部访问端口为80,域名为ap2.amebob.cn的请求

    location / {#/表示匹配所有请求
        proxy_pass http://localhost:3000;  #将所有请求反向代理给前端容器映射的服务器接口3000
        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_pass_header Content-Type;
        proxy_pass_header Content-Length;
    }
}

后端容器中的nginx

这里给出一部分

server {
        listen 80;
        server_name localhost;

        root /usr/share/nginx/html;
        index index.html index.htm;

        location / {
            try_files $uri $uri/ /index.html;
        }

        location /api/ {
            proxy_pass http://backend; #
            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;
        }

新的客户端请求处理流程