Docker部署python项目

目的:为了解决生产环境和开发环境的环境版本问题,同时也是因为不同服务器中的git库同步不方便的原因所以使用docker进行开发部署。

  • 最终结论是选择拉取基础镜像直接在镜像中进行开发最为方便

开发部署流程

  • 方式一:直接在docker容器中开发
    • 操作:启动一个基础镜像的容器,进入容器内部安装依赖、写代码、调试。
    • 特点:
      • 开发效率低|调试困难|环境容易丢失|依赖管理混乱

  • 方式二:本地开发+docker打包部署
    • 操作:在本地python环境进行开发,开发完成后通过Dockerfile构建镜像生产镜像。
    • 特点:
      • 开发体验好|环境隔离|可复现性强|镜像精简

开发案例

方案一

  • 实验环境 |macOS|Docker version 28.0.1, build 068a01e``
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
28
29
30
31
# 终端命令步骤

# 拉取基础镜像
docker pull python:3.11-slim-bullseye
# 新建容器 这里我想直接使用dockercompose 进行容器实例化
'''
services:
app:
image: python:3.11-slim-bullseye
container_name: python-app
working_dir: /app
volumes:
- ./config.py:/app/config.py
- ./app:/app
ports:
- "8015:8015"
command: tail -f /dev/null
restart: unless-stopped
'''
docker-compose up

# 进入容器
docker exec -it python-app bash

# 同时也可以使用vscode插件直接进入容器进行开发
# 然后将容器打包成镜像

# 开发完成后可选择上传至阿里云镜像库 方便进行其它环境的部署
$ docker login --username=stylite灬玉京子 crpi-iodthkq5b3ol4fv7.cn-chengdu.personal.cr.aliyuncs.com
$ docker tag [ImageId] crpi-iodthkq5b3ol4fv7.cn-chengdu.personal.cr.aliyuncs.com/dummy-v07/dummy_space:[镜像版本号]
$ docker push crpi-iodthkq5b3ol4fv7.cn-chengdu.personal.cr.aliyuncs.com/dummy-v07/dummy_space:[镜像版本号]

📍 必须预先定义的容器参数

目录挂载|工作路径|端口暴露|交互模式

🧩 推荐的高级参数

环境变量|容器名称|自动清理

1
2
3
4
5
6
7
8
-e PYTHONUNBUFFERED=1 \      # 实时输出日志
-e DEBUG=1 \ # 调试模式
-e PYTHONDONTWRITEBYTECODE=1 # 避免生成.pyc文件

--name my-python-dev # 便于管理

--rm # 容器停止后自动删除 待考虑

方案二

整体体验感并不好。。。

项目开发完毕后写一个Dockerfile

EXPOSE 8000 左右:声明容器内部应用程序监听的端口 告诉

8000:8000 “宿主机端口:容器端口” 端口映射,将宿主机的端口映射到容器内的端口,将宿主机的8000端口映射到容器的8000端口

Dockerfile

构建自定义镜像主要是为了满足特定应用场景的需求,克服使用通用或官方镜像时可能遇到的局限性。

DockerFile文件是指导docker进行打包的文档:

FROM : 指定运行环境

WORKEIR:指定工作目录

COPY:拷贝指令

RUN:预运行指令

CMD:CMD运行指令

基本命令

docker build -t xxx 路径 将该路径建立为docker镜像

使用Docker

  1. 构建镜像:
    1
    docker build -t oil-meter-api .

构建镜像的时候可能会遇见下载慢或者下载直接失败的情况,所以经常可能需要换源。

  1. 运行容器:
1
docker run -p 8000:8000 oil-meter-api

Docker换源

  • 更换docker镜像源。这里由于docker下载东西从国外的服务器很慢,因此要换成国内的镜像源。
  • 打开/etc/docker/daemon.json文件,sudo gedit /etc/docker/daemon.json,发现是空白的,添加以下内容:
  • {“registry-mirrors”:[“http://docker.m.daocloud.io"]}
  • Systemctl restart docker

Docker compose

  • 批量部署

docker-compose.json

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# Docker Compose 配置文件
# 用于定义和运行多容器Docker应用程序
# 版本声明 - 不同版本支持不同的功能特性
version: '3.8'

# 定义服务列表 - 每个服务对应一个容器
services:
# 油表识别API服务
# 服务名称,可以自定义,用于容器命名和网络通信
oil-meter-api:
# 构建配置 - 指定如何构建镜像
build:
context: . # 构建上下文目录(当前目录)
dockerfile: Dockerfile # 指定Dockerfile文件
args: # 构建参数(可选)
BUILDKIT_INLINE_CACHE: 1 # 启用内联缓存

# 端口映射 - 宿主机端口:容器端口
# 格式: "宿主机端口:容器端口"
# 宿主机8000端口映射到容器8000端口
ports:
- "8000:8000" # HTTP端口
- "8001:8000" # 备用端口(可选)

# 环境变量配置
# 这些变量会在容器运行时设置
environment:
- PYTHONUNBUFFERED=1 # 禁用Python输出缓冲
- DEBUG=false # 调试模式开关
- LOG_LEVEL=INFO # 日志级别
- API_KEY=your_api_key # API密钥(生产环境应使用secrets)
- DATABASE_URL=postgresql://user:pass@db:5432/db # 数据库连接

# 环境变量文件(推荐方式)
# 从.env文件读取环境变量
env_file:
- .env # 默认环境变量文件
- .env.production # 生产环境变量文件

# 数据卷挂载 - 宿主机目录映射到容器目录
# 格式: "宿主机路径:容器路径[:权限]"
volumes:
- ./logs:/app/logs # 日志目录挂载
- ./uploads:/app/uploads # 上传文件目录挂载
- ./config:/app/config:ro # 配置文件挂载(只读)
- postgres_data:/var/lib/postgresql/data # 命名卷挂载

# 网络配置
networks:
- frontend # 前端网络
- backend # 后端网络(内部网络)

# 依赖关系 - 确保其他服务先启动
depends_on:
- postgres # 依赖数据库服务
- redis # 依赖缓存服务

# 重启策略
# no: 不重启(默认)
# always: 总是重启
# on-failure: 失败时重启
# unless-stopped: 除非手动停止,否则总是重启
restart: unless-stopped

# 资源限制配置
deploy:
resources:
limits:
memory: 1G # 最大内存限制
cpus: '1.0' # 最大CPU限制(1个核心)
reservations:
memory: 512M # 最小内存保证
cpus: '0.5' # 最小CPU保证
replicas: 1 # 服务实例数量
update_config:
parallelism: 1 # 并行更新数量
delay: 10s # 更新间隔
failure_action: rollback # 失败时回滚
restart_policy:
condition: on-failure # 重启条件
delay: 5s # 重启延迟
max_attempts: 3 # 最大重启次数
window: 120s # 重启窗口期

# 健康检查配置
# 用于监控服务是否正常运行
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"] # 检查命令
interval: 30s # 检查间隔
timeout: 10s # 检查超时
retries: 3 # 重试次数
start_period: 40s # 启动宽限期
disable: false # 是否禁用健康检查

# 容器配置
container_name: oil-meter-api # 指定容器名称
hostname: api-server # 容器主机名

# 用户和组配置
user: "1000:1000" # 用户ID:组ID

# 工作目录
working_dir: /app # 容器内工作目录

# 命令覆盖
# 覆盖Dockerfile中的CMD命令
command: ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

# 入口点覆盖
# 覆盖Dockerfile中的ENTRYPOINT
# entrypoint: ["python", "-m", "uvicorn"]

# 标签配置
labels:
- "com.example.description=Oil Meter API"
- "com.example.department=engineering"
- "com.example.version=1.0"

# 日志配置
logging:
driver: "json-file" # 日志驱动
options:
max-size: "10m" # 单个日志文件最大大小
max-file: "3" # 最大日志文件数量

# 安全配置
security_opt:
- no-new-privileges:true # 禁止获取新权限

# 设备映射
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0" # 串口设备映射

# 额外主机配置
extra_hosts:
- "host.docker.internal:host-gateway" # 宿主机访问
- "db-server:192.168.1.100" # 自定义主机映射

# 临时文件系统
tmpfs:
- /tmp:size=100m # 临时文件系统大小

# 特权模式(不推荐)
# privileged: true

# 只读根文件系统(安全)
read_only: true

# 临时文件系统挂载
tmpfs:
- /tmp:size=100m
- /var/tmp:size=100m

# 数据库服务示例
postgres:
image: postgres:13-alpine # 使用官方镜像
environment:
POSTGRES_DB: oilmeter # 数据库名
POSTGRES_USER: oiluser # 用户名
POSTGRES_PASSWORD: oilpass # 密码
volumes:
- postgres_data:/var/lib/postgresql/data # 数据持久化
ports:
- "5432:5432" # 数据库端口
networks:
- backend # 后端网络
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U oiluser -d oilmeter"]
interval: 10s
timeout: 5s
retries: 5

# Redis缓存服务示例
redis:
image: redis:6-alpine # Redis镜像
ports:
- "6379:6379" # Redis端口
volumes:
- redis_data:/data # 数据持久化
networks:
- backend # 后端网络
restart: unless-stopped
command: redis-server --appendonly yes # 启用AOF持久化
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5

# 网络配置
# 定义容器间通信的网络
networks:
# 前端网络 - 对外服务
frontend:
driver: bridge # 网络驱动类型
# external: true # 使用外部网络
# name: frontend-network # 指定网络名称

# 后端网络 - 内部服务
backend:
driver: bridge
internal: true # 内部网络,不对外暴露
# driver_opts: # 驱动选项
# com.docker.network.bridge.name: br-backend

# 数据卷配置
# 定义持久化存储
volumes:
# 命名卷 - 由Docker管理
postgres_data:
driver: local # 本地存储驱动
# external: true # 使用外部卷
# name: postgres-data # 指定卷名称

redis_data:
driver: local

# 外部卷示例
# external_volume:
# external: true
# name: my-external-volume

# 配置管理
# 定义配置文件
configs:
# 应用配置文件
app_config:
file: ./config/app.yml # 配置文件路径
# external: true # 使用外部配置
# name: app-config # 配置名称

# 密钥管理
# 定义敏感信息
secrets:
# API密钥
api_key:
file: ./secrets/api_key.txt # 密钥文件路径
# external: true # 使用外部密钥
# name: api-secret # 密钥名称

阿里云云镜像仓库

https://cr.console.aliyun.com/repository/cn-chengdu/dummy-v07/dummy_space/details

操作和git大同小异很简单,就不再赘述

copy和volumes的区别

# Dockerfile
1
2
3
# Dockerfile
COPY app.py . # 构建时复制到镜像中
COPY requirements.txt . # 成为镜像的一部分```
  • copy:是在docker build(构建)时复制到镜像中,成为镜像的一部分,具有不可变性(镜像),文件与镜像版本绑定
  • 实用场景:应用代码;依赖文件;静态资源;模版文件
  • copy建议:核心部分代码,需要版本控制,不需要频繁修改
1
2
3
4
# docker-compose.yml
volumes:
- ./logs:/app/logs # 运行时挂载
- ./uploads:/app/uploads # 宿主机目录映射到容器
  • volumes:实在docker run时挂载,文件存储在宿主机,可以随时修改文件,容器删除后文件保留(性能影响:可能会有I/O开销)

  • 实用场景:日志文件;用户上传文件;数据库文件;配置文件

  • volumes建议:数据需要持久化,需要频繁修改,多个容器共享数据