Docker容器化部署

Web开发专题 · 掌握容器化部署技术

专题:Python Web开发系统学习

关键词:Python, Web开发, Docker, 容器化, docker-compose, Dockerfile, 镜像优化, CI/CD, 部署

一、Docker基础

1.1 容器 vs 虚拟机对比

Docker容器与传统的虚拟机(VM)在架构上有着本质区别。虚拟机在宿主机操作系统之上运行一个完整的Guest OS,每个虚拟机都包含独立的内核、系统库和应用程序,因此资源开销较大,启动时间通常以分钟计。而Docker容器直接共享宿主机的操作系统内核,通过Namespace实现资源隔离,通过Cgroups进行资源限制,每个容器只包含应用程序及其依赖项,没有独立的操作系统层。

对比维度Docker容器虚拟机
启动速度毫秒级到秒级分钟级
磁盘占用MB级别GB级别
性能损耗接近原生有一定损耗
内核共享宿主机内核独立内核
隔离性进程级隔离完全隔离

1.2 Docker核心概念

镜像(Image):镜像是容器的只读模板,包含了运行应用程序所需的文件系统、依赖库、环境变量和配置文件。镜像是分层构建的,每一层都可以被缓存和复用,这得益于Docker的Union File System技术。

容器(Container):容器是镜像的运行实例,可以被启动、停止、删除和暂停。每个容器是一个隔离的轻量级运行环境,拥有自己的网络、进程空间和文件系统。容器在镜像只读层之上添加一个可写层,所有运行时修改都写在这一层。

仓库(Registry):仓库用于存储和分发Docker镜像。Docker Hub是官方公共仓库,此外还有Amazon ECR、Google Container Registry、阿里云容器镜像服务等。开发者可以推拉镜像实现分发和协作。

Dockerfile:Dockerfile是一个文本文件,包含构建镜像所需的指令集。通过docker build命令可以基于Dockerfile自动化构建镜像,实现基础设施即代码。

1.3 Docker安装与基本命令

在Ubuntu系统中,可以通过以下命令安装Docker引擎:

# Ubuntu安装Docker sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io # 验证安装 docker --version docker info # 启动Docker服务 sudo systemctl start docker sudo systemctl enable docker

常用Docker命令:

docker ps:列出当前运行的容器,加 -a 参数可列出所有容器。 docker images:列出本地镜像列表。 docker pull nginx:从仓库拉取镜像。 docker run -d -p 8080:80 nginx:在后台运行nginx容器,将宿主机8080端口映射到容器80端口。 docker exec -it container_id bash:进入容器内部交互式操作。 docker logs container_id:查看容器日志。 docker rm container_id:删除容器。 docker rmi image_id:删除镜像。

二、Dockerfile编写

2.1 FROM选择基础镜像

FROM指令指定构建所依赖的基础镜像,必须是Dockerfile中的第一条指令。选择合适的基础镜像至关重要,它直接影响镜像的大小、安全性和构建速度。对于Python项目,常用的基础镜像包括 python:3.11-slim(约120MB)、python:3.11-alpine(约50MB)和python:3.11(约330MB)。生产环境推荐使用slim或alpine变体以减小镜像体积。

2.2 WORKDIR工作目录

WORKDIR指令为后续的RUN、CMD、ENTRYPOINT、COPY和ADD指令设置工作目录。如果目录不存在,WORKDIR会自动创建。推荐在Dockerfile开头设置WORKDIR,使后续路径操作都基于此目录,避免使用绝对路径带来的混乱。

2.3 COPY/ADD文件复制

COPY指令将本地文件复制到镜像中,格式为 COPY <源路径> <目标路径>。ADD指令在COPY基础上增加了自动解压tar文件和从URL下载文件的功能。最佳实践是优先使用COPY,只在需要自动解压时使用ADD,因为ADD的行为不够透明。

2.4 RUN执行命令

RUN指令在镜像构建过程中执行命令,每一条RUN指令都会创建新的镜像层。为减少层数和镜像大小,建议将多个命令用 && 连接为一条RUN指令,并在最后清理apt缓存。

2.5 ENV环境变量

ENV指令设置环境变量,这些变量在构建过程和容器运行时都可用。常用于设置Python的PYTHONUNBUFFERED=1(确保日志实时输出)、PYTHONDONTWRITEBYTECODE=1(不生成.pyc文件)等。

2.6 EXPOSE暴露端口

EXPOSE指令声明容器运行时监听的端口,仅作为文档说明,不会自动发布端口。运行时仍需使用 -p 或 -P 参数将端口映射到宿主机。

2.7 CMD/ENTRYPOINT启动命令

CMD提供容器启动时的默认执行命令,可以被 docker run 后面的参数覆盖。ENTRYPOINT设置固定的入口点,不易被覆盖。两者结合使用时,CMD作为ENTRYPOINT的默认参数。推荐将ENTRYPOINT设为固定的启动脚本,CMD设为默认参数。

2.8 多阶段构建优化

多阶段构建允许在一个Dockerfile中使用多个FROM语句,每个FROM开始一个新的构建阶段。编译阶段使用完整的基础镜像和所有构建工具,最终运行阶段只将编译产物复制到精简的运行时镜像中。这种方式既保证了构建过程灵活,又保证了最终镜像小巧。

# 多阶段构建示例 # 第一阶段:编译 FROM python:3.11-slim AS builder WORKDIR /app COPY requirements.txt . RUN pip install --user -r requirements.txt # 第二阶段:运行 FROM python:3.11-slim WORKDIR /app COPY --from=builder /root/.local /root/.local COPY . . ENV PATH=/root/.local/bin:$PATH EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

三、docker-compose多服务编排

3.1 docker-compose.yml结构

docker-compose是一个用于定义和运行多容器Docker应用的工具。通过一个YAML文件配置应用的服务、网络和卷,一条命令即可启动整个应用栈。Compose文件的version字段指定文件格式版本,services字段定义所有微服务,volumes和networks字段定义共享资源。

version: '3.8' services: web: build: . ports: - "8000:8000" depends_on: - db db: image: postgres:15 volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:

3.2 services服务定义

每个service对应一个容器化的应用组件。服务定义中可以指定build(构建上下文)、image(使用的镜像)、ports(端口映射)、volumes(卷挂载)、environment(环境变量)、command(启动命令)等。服务之间可以通过服务名互相访问,Compose自动为所有服务创建一个默认网络。

3.3 volumes数据持久化

容器被删除后,其文件系统也随之消失。volume(卷)是Docker推荐的持久化数据方式,它将数据存储在宿主机文件系统的一个特殊目录中,由Docker管理。在Compose中定义顶级volumes,然后在服务中通过volumes字段挂载。对于数据库等有状态服务,数据持久化是必须的。

3.4 networks网络配置

Docker Compose默认创建一个桥接网络,所有服务都加入该网络并通过服务名互相解析。通过自定义网络可以实现更精细的网络隔离:将前端服务放在frontend网络,后端服务和数据库放在backend网络,数据库不暴露给前端,提升安全性。

3.5 depends_on依赖控制

depends_on指定服务之间的启动顺序依赖关系。Compose会按照依赖顺序启动服务,但这只保证服务进程已启动,不保证服务内部已就绪。对于需要等待数据库就绪的场景,应配合健康检查(healthcheck)或使用wait-for-it.sh脚本。

3.6 environment环境变量

Compose中可以通过environment字段直接设置环境变量,也可以通过env_file字段引用外部文件。管理环境变量的最佳实践是:在Compose文件中设置非敏感变量(如DEBUG=False),敏感信息(如数据库密码)使用.env文件或在容器编排平台中通过密钥管理服务注入。

四、Python Web应用容器化

4.1 Flask Dockerfile

Flask是最轻量级的Python Web框架之一,适合微服务和API开发。容器化Flask应用时,可使用Gunicorn作为WSGI服务器,通过环境变量控制运行模式。

FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . ENV FLASK_APP=app.py ENV FLASK_ENV=production EXPOSE 5000 CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]

4.2 Django + PostgreSQL + Redis编排

Django项目通常需要数据库和缓存服务配合。使用docker-compose可以将Django应用、PostgreSQL数据库和Redis缓存编排在一起运行。Django的settings.py中数据库和缓存的主机名直接使用Compose中的服务名。

version: '3.8' services: db: image: postgres:15 volumes: - postgres_data:/var/lib/postgresql/data environment: - POSTGRES_DB=myapp - POSTGRES_USER=myuser - POSTGRES_PASSWORD=mypassword redis: image: redis:7-alpine web: build: . ports: - "8000:8000" depends_on: - db - redis environment: - DATABASE_URL=postgres://myuser:mypassword@db:5432/myapp - REDIS_URL=redis://redis:6379/0 command: > sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000" volumes: postgres_data:

4.3 FastAPI + Uvicorn容器化

FastAPI是新兴的高性能Python Web框架,基于ASGI标准,使用Uvicorn作为服务器。容器化FastAPI时需要注意异步特性,合理配置Uvicorn的工作模式和并发参数。

FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

4.4 环境变量管理

环境变量是容器化配置的核心手段。对所有敏感信息(数据库密码、API密钥、JWT密钥等)必须使用环境变量注入,绝不能硬编码在代码中。在开发环境使用.env文件管理,在生产环境使用容器编排平台(Kubernetes Secrets、Docker Secrets等)或云服务商的密钥管理服务。Python项目推荐使用pydantic-settings或python-dotenv库管理配置,定义清晰的配置模型,支持从环境变量自动读取。

4.5 日志管理

在容器化环境中,日志应写入标准输出(stdout)和标准错误(stderr),由容器运行时统一收集。Python应用需设置PYTHONUNBUFFERED=1确保日志不缓冲。生产环境中,通常使用日志收集工具(如Filebeat、Fluentd)将容器日志转发到集中式日志系统(如ELK栈、Loki)。

五、镜像优化

5.1 选择轻量基础镜像

基础镜像的大小直接影响镜像的拉取和部署速度。对于Python项目,推荐使用python:3.11-slim(基于Debian,约120MB)或python:3.11-alpine(基于Alpine,约50MB)。Alpine虽然体积最小,但使用musl libc而非glibc,部分Python C扩展可能需要额外编译。slim版本在体积和兼容性之间取得了较好的平衡。

5.2 层缓存优化

Docker利用构建缓存加速镜像构建。Dockerfile中每一条指令对应一个镜像层,如果某层没有变化且之前的层缓存命中,则该层直接使用缓存。最佳实践是:将不太可能变动的指令放在前面,最常变动的指令放在最后。具体来说,先复制requirements.txt安装依赖,再复制源代码。这样只要requirements.txt不变,依赖安装层就复用缓存,大幅加速构建。

# 层缓存优化示例 FROM python:3.11-slim WORKDIR /app # 先复制依赖文件,利用缓存减少构建时间 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 最后复制经常变化的源代码 COPY . . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

5.3 多阶段构建

对于包含编译步骤的Python项目(如安装包含C扩展的依赖),多阶段构建尤为重要。第一阶段安装完整的编译工具链并编译依赖,第二阶段只将编译产物和Python运行时复制到精简镜像中。这样最终的运行镜像不包含gcc、make等编译工具,既减小了体积又降低了攻击面。

5.4 依赖安装优化

使用 --no-cache-dir 参数避免缓存pip包,减少镜像体积。将依赖包按变更频率分层:将固定版本的核心框架放在requirements-base.txt中,将项目特有且可能频繁变动的依赖放在requirements.txt中。对于包含大量科学计算库的项目,可考虑使用conda或预编译的wheel包加速安装。

5.5 镜像安全扫描

安全是镜像优化的重要环节。使用docker scan或Trivy等工具扫描镜像中的已知漏洞。定期更新基础镜像以获得最新安全补丁。避免在镜像中留存敏感信息(如私钥、密码)。以非root用户运行容器进程,减少容器逃逸风险。

# 安全扫描命令 docker scan myapp:latest trivy image myapp:latest # 创建非root用户运行 RUN groupadd -r mygroup && useradd -r -g mygroup myuser USER myuser

六、生产环境Docker部署

6.1 Docker Compose生产配置

生产环境的Compose配置与开发环境有显著不同。生产环境需要:使用镜像标签而非build(确保部署的可复现性)、配置资源限制、启用健康检查、设置日志驱动、使用外部网络。通过多个Compose文件实现环境差异化:docker-compose.yml作为基础配置,docker-compose.prod.yml覆盖生产特定配置,使用 -f 参数合并。

# docker-compose.prod.yml version: '3.8' services: web: image: registry.example.com/myapp:1.2.3 restart: always deploy: resources: limits: cpus: '1' memory: 512M healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 logging: driver: "json-file" options: max-size: "10m" max-file: "3"

6.2 容器健康检查

健康检查是保障容器可靠性的关键机制。Docker支持在Dockerfile中使用HEALTHCHECK指令,或在Compose文件中配置healthcheck。健康检查会定期执行指定的命令,根据退出码判断容器是否健康。容器编排平台根据健康状态决定是否重启容器或切换流量。

# Dockerfile中定义健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1

6.3 日志收集

Docker默认将容器日志以JSON文件形式存储在宿主机。生产环境中需要配置日志轮转,避免日志占满磁盘。更专业的方案是使用日志驱动将日志发送到集中式日志系统:如使用fluentd驱动发送到Fluentd,或使用awslogs驱动发送到CloudWatch。推荐在Compose文件中统一配置logging驱动和参数。

6.4 自动重启策略

restart策略定义了容器退出后的自动重启行为。可选项包括:no(不重启)、always(总是重启)、on-failure(仅在出错时重启,可指定最大重试次数)、unless-stopped(除非手动停止否则始终重启)。生产环境推荐使用unless-stopped或on-failure,并配合健康检查和资源限制使用。

6.5 资源限制

为避免单个容器耗尽宿主机资源,必须对容器进行资源限制。主要限制CPU和内存:通过 --memory 限制最大内存使用,通过 --cpus 限制CPU核心数。在Compose文件中使用deploy.resources配置。当容器超出内存限制时,会被OOM Killer终止;超出CPU限制时会被限流。合理的资源限制是保障多租户环境下服务稳定性的基础。

七、Docker与CI/CD

7.1 镜像标签策略

合理的镜像标签策略是CI/CD流程的基础。不推荐使用latest标签,因为它不具备可追溯性。推荐采用语义化版本加Git提交信息的标签策略,例如:1.2.3-abc1234(版本号+Git提交SHA前7位),或包含构建日期的标签如:20260505-1.2.3。标签策略需要兼顾可读性和可追溯性,确保能够从镜像标签追溯到对应的源代码版本。

# GitLab CI中自动生成镜像标签 image_tag="${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}" docker build -t registry.example.com/myapp:${image_tag} . docker push registry.example.com/myapp:${image_tag}

7.2 私有镜像仓库

私有镜像仓库用于存储内部镜像,避免将私有代码上传到公共仓库。Docker官方提供了Registry镜像,可快速搭建私有仓库。更成熟的方案包括Harbor(企业级,支持漏洞扫描和RBAC)、Amazon ECR、Google Container Registry等。CI/CD流水线构建完成后将镜像推送到私有仓库,部署时从私有仓库拉取。

7.3 自动化构建

完整的CI/CD流水线通常包含以下阶段:代码检查(linting、类型检查)、单元测试、构建镜像、镜像安全扫描、推送到镜像仓库、部署到目标环境。以下是一个GitLab CI的流水线示例:

# .gitlab-ci.yml stages: - test - build - deploy test: stage: test image: python:3.11-slim script: - pip install -r requirements-dev.txt - pytest --cov=app tests/ build: stage: build image: docker:latest services: - docker:dind script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA deploy: stage: deploy script: - ssh deploy@server "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA && docker compose up -d" only: - main

核心要点总结

Docker容器化部署是现代Web开发不可或缺的技能。理解容器与虚拟机的本质区别、掌握Dockerfile编写技巧、熟练使用docker-compose进行多服务编排、针对Python Web框架(Flask、Django、FastAPI)进行容器化改造、持续优化镜像体积和安全、建立生产级部署规范、将容器化与CI/CD流水线集成,以上构成了完整的容器化部署能力体系。在实践中应当从小项目开始积累经验,逐步将容器化应用到复杂微服务架构中。