正在进入ing...

fastapi部署之单机蓝绿不停服更新

发布时间:2026-02-24 浏览量: 12 文章分类: python

背景

容器化管理,使用podman-compose 实际也支持docker-compose,部署服务是fastapi+nginx进行切换转发

整体通过switch.sh脚本来切换nginx 转发到不同的容器来实现不同版本的更新部署,也就是一份代码,通过fastapi-green fastapi-blue实现流量切换

项目目录

(api) ➜  demo git:(main) ✗ tree
.
├── Dockerfile
├── README.md
├── api
│   ├── README.md
│   ├── __pycache__
│   │   ├── gunicorn_conf.cpython-39.pyc
│   │   ├── gunicorn_conf.cpython-39.pyc.281472865685392
│   │   ├── main.cpython-311.pyc
│   │   └── main.cpython-39.pyc
│   ├── gunicorn_conf.py
│   ├── main.py
│   ├── pyproject.toml
│   ├── requirements.txt
│   ├── start.sh
│   └── uv.lock
├── nginx
│   ├── current.conf
│   ├── nginx.conf
│   └── upstreams
│       ├── blue.conf
│       └── green.conf
├── podman-compose.yml
├── scripts
│   └── switch.sh
├── static
├── switch.log
└── switch.py

switch.py \ switch.log这2个文件和项目无关,主要是实现观测切换是否存在异常的文件和日志.

镜像文件

对同样的代码实现2个不同的容器进行管理

# `podman-compose.yml`
version: '3.8'

services:

fastapi-blue:

build:

context: .

dockerfile: Dockerfile

image: fastapi:blue-${VERSION_BLUE:-latest}

container_name: fastapi-blue

environment:

- APP_COLOR=blue

networks:

- app-network

healthcheck:

test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]

interval: 10s

timeout: 5s

retries: 3

expose:

- "8000"



fastapi-green:

build:

context: .

dockerfile: Dockerfile

image: fastapi:green-${VERSION_GREEN:-latest}

container_name: fastapi-green

environment:

- APP_COLOR=green

networks:

- app-network

healthcheck:

test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]

interval: 10s

timeout: 5s

retries: 3

expose:

- "8000"



nginx:

image: nginx:alpine

container_name: nginx

ports:

- "8000:80"

volumes:

- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro,Z

- ./nginx/upstreams:/etc/nginx/upstreams:ro,Z

- ./nginx/current.conf:/etc/nginx/conf.d/upstream.conf:ro,Z

networks:

- app-network

user: "root"



networks:

app-network:

driver: bridge

# podman-compose 推荐显式指定网络名称(可选)

name: app-network

Dockerfile使用了python:3.13-slim和分段构建,降低了包体大小也提升了构建速度.

# Build stage

FROM python:3.13-slim as builder

WORKDIR /home/api

COPY api/requirements.txt /home/api/
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM python:3.13-slim

WORKDIR /home/api


RUN apt-get update && \

apt-get install -y --no-install-recommends curl && \

rm -rf /var/lib/apt/lists/*

COPY --from=builder /install /usr/local

COPY /api /home/api/


RUN chmod +x /home/api/start.sh

ENV PYTHONUNBUFFERED=1

ENV PYTHONPATH=/home/api

CMD ["/home/api/start.sh"]

日常操作

实际在这样部署后 ,需要基于git进行版本管理,在代码更新后需要实时进行更新,并能通过容器的tag来进行版本的切换.

首次使用

第一次模型给容器的tag版本号都是一样的,PODMAN_NO_PULL=1 VERSION_GREEN=v1.0.0 podman-compose up -d fastapi-blue fastapi-green nginx,这样即可启动容器,不过第一次可能会慢一点(具体看你的网络)

(api) ➜  demo git:(main) ✗ podman-compose ps
CONTAINER ID  IMAGE                           COMMAND               CREATED         STATUS                    PORTS                 NAMES
7e6c0be5bb5c  localhost/fastapi:blue-latest   /home/api/start.s...  17 seconds ago  Up 17 seconds (healthy)   8000/tcp              fastapi-blue
fddefaa91ccf  localhost/fastapi:green-v1.0.0  /home/api/start.s...  17 seconds ago  Up 17 seconds (healthy)   8000/tcp              fastapi-green
0e54e1c14179  docker.io/library/nginx:alpine  nginx -g daemon o...  16 seconds ago  Up 17 seconds (starting)  0.0.0.0:8000->80/tcp  nginx

现在我们确认成功后,在尝试对,可以自己在代码写一个固定的版本号和接口,来实现是否更新成功.

@app.get("/")
def read_root():
    COLOR = os.getenv("APP_COLOR", "unknown")
    HOSTNAME = socket.gethostname()
    return {
        "message": "Hello from FastAPI",
        "color": COLOR,
        "hostname": HOSTNAME,
        "version": "1.0.0"
    }

请求后接口返回如下

{"message":"Hello from FastAPI","color":"green","hostname":"fddefaa91ccf","version":"1.0.0"}

跑到这里就代表我们的容器和初始化全部完成

流量切换

因为我们实际是跑了2个容器的,所以会涉及fastapi-greenfastapi-blue2个容器,通过scripts/switch.sh即可实现切换.

#!/bin/bash
set -e

TARGET=$1


if [[ "$TARGET" != "blue" && "$TARGET" != "green" ]]; then

echo "Usage: $0 [blue|green]"

exit 1

fi

echo "Switching traffic to $TARGET..."

echo "Checking health of fastapi-$TARGET..."

if podman exec "fastapi-$TARGET" curl -s -f http://localhost:8000/health > /dev/null; then

echo "Health check passed."

else

echo "Health check FAILED for fastapi-$TARGET. Aborting switch."

exit 1

fi



CONFIG_PATH="./nginx/current.conf"

SOURCE_CONFIG="./nginx/upstreams/$TARGET.conf"



if [ ! -f "$SOURCE_CONFIG" ]; then

echo "Error: Configuration file $SOURCE_CONFIG not found."

exit 1

fi



echo "Updating Nginx configuration..."

cp "$SOURCE_CONFIG" "$CONFIG_PATH"




echo "Reloading Nginx..."

podman exec nginx nginx -s reload



echo "Successfully switched to $TARGET."

执行如下命令后 在请求接口,可以看到流量从green容器切换到blue容器

(api) ➜  demo git:(main) ✗ bash scripts/switch.sh blue
Switching traffic to blue...
Checking health of fastapi-blue...
Health check passed.
Updating Nginx configuration...
Reloading Nginx...
2026/02/24 05:53:33 [notice] 33#33: signal process started
Successfully switched to blue.

请求后接口返回

{"message":"Hello from FastAPI","color":"blue","hostname":"fddefaa91ccf","version":"1.0.0"}

更新版本

这里会比较复杂,因为是单机部署,且没有接入jenkinsk8s等自动化工具,所以对镜像的拉取、版本更新仍需要手动操作

现在我们的容器流量在green上,我们可以先对代码修改,同时更新blue容器的环境 更新blue容器代码,假设已经完成了代码变更. PODMAN_NO_PULL=1 VERSION_BLUE=v1.0.2 podman-compose up -d fastapi-blue 这里要注意VERSION_BLUE的版本号代表打包以后的镜像版本,

执行后确认打包成功后在执行流量切换后访问接口则能看到新的版本号已经返回

(api) ➜  demo git:(main) ✗ bash scripts/switch.sh blue                               
Switching traffic to blue...
Checking health of fastapi-blue...
Health check passed.
Updating Nginx configuration...
Reloading Nginx...
2026/02/24 05:58:42 [notice] 42#42: signal process started

{"message":"Hello from FastAPI","color":"blue","hostname":"1e7171b9b528","version":"1.0.2"}

按照上面的逻辑即可实现一个手动操作控制的不停服,通过单机实现的蓝绿更新服务.

接下来可以完善的点 + 没有镜像会滚机制,还需要新增rollback.sh工具,实现代码出错,快速回滚的机制 + 历史镜像堆积问题,每次更新后,历史镜像并没有清除.