案例一: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. 操作

部署和更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 构建并启动服务
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. 检查容器运行状态:

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

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

    1
    
    curl http://localhost:3000
    

未经测试的复现命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 在当前服务器保存镜像为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定义如何构建镜像(怎么做的过程定义)

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

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

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    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:专注于域名解析和外部请求路由

这里是本项目的目录结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 环境,并将构建好的静态文件放入其中。

1
2
3
4
5
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 环境,并将后端代码放入其中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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配置,

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

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

服务器nginx

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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

这里给出一部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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;
        }

新的客户端请求处理流程