
通过阅读许多Python Docker容器博客,我们发现大多数帖子都提供了如何将一个Python应用程序容器化的例子,而不是其框架(Django、Flask、Falcon等)。例如,你可能会看到这样的东西。
FROM python
WORKDIR /usr/app
COPY . .
RUN pip install -r requirements.txt
CMD [ "python", "app.py" ]
通过这个Docker文件,我们可以构建并运行一个Python Flask应用程序。
docker build -t flask-application .
docker run -p 8080:5000 flask-application
两个简单的步骤,就可以正常工作了,对吗?
虽然这个例子很简单,而且对演示和入门教程很有用,但它留下了许多重要的问题没有解决。因此,考虑到这一点,在这篇文章中,我们将关注这些问题,并看看用Docker容器化Python应用程序时的6个最佳实践。我们将探讨为什么你应该。
- 为容器化的Python应用程序使用明确和确定的Docker基础镜像标签。
- 将依赖关系与源代码分开。
- 在生产中使用Python WSGI。
- 以最小的权限运行容器(绝不以root身份)。
- 处理你的应用程序的不健康状态。
- 在你的Python Docker应用程序镜像中查找并修复安全漏洞。
1.为容器化的Python应用程序使用明确和确定的Docker基础镜像标签
虽然使用python 作为我们的Docker化Python应用程序的基础镜像似乎是合乎逻辑的,但它留下了使用哪个版本的Python的问题。
在写这篇文章的时候,Dockerfile 中提到的基础镜像是指Python 3.10的基础镜像。原因是什么?因为我们没有添加特定的标签,它默认为该基础镜像的:latest 版本,如果我们在Docker Hub的官方镜像页面上看,恰好是3.10
由于我们想控制我们正在容器化的Python的版本,我们应该在Docker文件中始终提供这个版本信息。
所以,既然我想使用3.10版本的Python,我只需要在我的Dockerfile中添加
:3.10标签,对吗?
嗯......不完全是。
:3.10 的Docker基础镜像标签是一个安装了Python 3.10的成熟的操作系统,它包含了大量的你可能永远不会用到的库。另外,它包含如此大量的软件这一事实还有一个副作用:由于这些库中存在的安全漏洞,它将增加你的安全攻击面。
如果你引入一个大型的Python Docker镜像,你将更难维护和保持所有这些库的版本更新。
如果我们使用Snyk Advisor工具来检查Python基础镜像,我们可以看到Docker基础镜像python:3.10 ,有12个高严重性问题,27个中等严重性问题,以及132个低严重性问题。所以,默认情况下,Python Docker镜像开始时至少有171个安全漏洞--而我们甚至还没有添加任何东西!
另外,由于我们有效地引入了一个完整的操作系统,对于我们的Python应用服务器来说,基础镜像的大小将是相当大的,这将导致构建速度变慢,这将需要更大的空间来容纳它。通常情况下,在选择基础镜像的时候没有什么规则,但有两条是关键。
选择Python Docker镜像的最佳实践
- 选择能满足你所有要求的最小基础镜像,然后在此基础上构建。较小的镜像包含较少的漏洞,资源消耗较少,并且有较少的不必要的包。
- 使用命名标签并不足以确保你总是使用相同的基础镜像。确保这一点的唯一方法是使用图像摘要。
有了这些知识,让我们重新审视一下Snyk Advisor,看看它推荐哪些替代标签。这个工具对基础图像的漏洞和大小提供了一个很好的概述,这将大大有助于我们的决定。
由于我们对在我们的应用程序中使用Python 3.10感兴趣,我们将寻找这个特定的标签。

除了上述的漏洞之外,我们可以看到这个镜像的基本大小约为 350 MB,它是基于 Debian 11 的,并且有 427 个已安装软件包。我们认为这个镜像对于我们的小Python应用程序来说有点太多了。
另一方面,还有Python的Docker基础镜像:3.10-slim ,有1个高严重度问题,1个中严重度问题和35个低严重度问题。Docker基础大小为46.2MB,这个操作系统也是基于Debian 11,并且有106个已安装的软件包。只需为我们的Python应用服务器选择这个Docker基础镜像,而不是默认镜像,就可以减少安全漏洞的数量、磁盘上的大小和安装的库数量--同时也满足我们对Python应用版本的需求:3.10 。
因此,这将是它!我只要加上标签:3.10-slim就可以了!
差不多了!我们满足了第一条规则--我们有一个小型的Docker基础镜像来满足我们的要求--但我们仍然需要解决第二个问题。我们需要确保每次构建我们的Python应用服务器时,我们都会实际使用这个确切的Docker基础镜像。
要做到这一点,我们有几个选择。
- 从Docker Hub抓取Docker基础镜像的摘要。
- 用
docker pull python:3.10-slim,将Docker镜像下载到我们的电脑上,这将显示出Docker镜像摘要。
3.10-slim: Pulling from library/python
7d63c13d9b9b: Pull complete
6ad2a11ca37b: Pull complete
1d79bc863ed3: Pull complete
c72b5f03bec8: Pull complete
0c3b0c5ce69b: Pull complete
Digest: sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
Status: Downloaded newer image for python:3.10-slim
docker.io/library/python:3.10-slim
如果我们的电脑上已经有了Python Docker镜像,我们就可以用命令docker images --digests | grep python ,从当前磁盘上的现有镜像中获取镜像摘要。
python 3.10-slim sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
一旦我们有了基本的镜像摘要,我们就可以直接把它添加到前面提到的Dockerfile中。
Dockerfile
FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
WORKDIR /usr/app
COPY . .
RUN pip install -r requirements.txt
CMD [ "python", "app.py" ]
这种做法确保我们每次为这个Python应用程序重建Docker镜像时,都会使用相同的底层操作系统和库版本。这就提供了一个确定性的构建。
2.将依赖性与源代码分开
这第二个最佳实践可以防止任何一种涉及有依赖关系的项目的Docker镜像中最常见的错误之一。首先,这是一个糟糕的做法。
- 从我们的项目文件夹中复制所有东西到镜像上下文中。
- 安装依赖项。
- 运行应用程序。
好吧,这很有效,但有很多需要改进的地方。首先,当你在本地开发一个项目时,你只在依赖项发生变化时才安装,对吗?所以,不要在你每次改变最轻微的一行代码时,都强迫你的Docker镜像下载和安装它们。
这个最佳实践是关于优化Docker镜像中的层。如果我们想在Docker构建过程中利用缓存系统,我们在编写Docker文件时应该始终牢记一件事:各层应该始终根据它们的变化几率来排序。
让我们看一下我们到现在为止一直携带的Dockerfile。每次我们构建Python应用程序镜像时,Docker软件都会检查不同的层并回答这个问题。有什么变化吗?还是我可以使用以前的东西?
在我们目前的Docker文件内容形式中,对我们项目文件夹的任何改变都会重新触发COPY 指令--以及随后--其他的构建层。这其实是没有意义的,它为优化和加速留下了很大的空间。
让我们用下面的Dockerfile来改善这个问题。
FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
WORKDIR /usr/app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD [ "python", "app.py" ]
有了这个新的Dockerfile,下次Docker检查层是否可以重复使用时,如果它发现requirements.txt 文件没有变化,它将直接 "跳转 "到COPY 指令,这将在几秒钟内解决。通过这个微小的变化,我们加快了很多构建过程。不再需要在每次修改代码时等待几分钟的构建时间。🎉🎉🎉
需要注意的是,有可能不是所有的依赖都被打包成了轮子。在这种情况下,需要在你的镜像上安装一个编译器。
但你告诉我们要尽可能使用最小的镜像来运行应用程序
你是对的,这就是为什么我们要向你展示另一个惊人的Docker功能:多阶段构建。
多阶段构建
多阶段构建意味着我们使用一个带有更多工具的Docker镜像来编译那些需要的依赖,之后我们只需将需要的工件复制到实际使用的Python Docker镜像上。
一个基于Node.js的应用程序的例子是这样的。
FROM node:latest AS build
ARG NPM_TOKEN
WORKDIR /usr/src/app
COPY package*.json /usr/src/app/
RUN npm install
FROM node:lts-alpine@sha256:b2da3316acdc2bec442190a1fe10dc094e7ba4121d029cb32075ff59bb27390a
WORKDIR /usr/src/app
COPY --from=build /usr/src/app/node_modules /usr/src/app/node_modules
COPY . .
CMD ["node", "server.js"]
注意:这是一个简短的例子,仅仅是为了展示多缓存的能力。如果你想学习正确的Node.js应用程序容器化的最佳实践,请参考Liran Tal(这个名字很熟悉......)和Yoni Goldberg的文章。
Node.js的多阶段构建很容易处理,因为node_modules 文件夹会和实际项目放在同一个文件夹中,然而,当我们处理Python应用程序时,情况就不是这样了。
如果我们只是运行pip install ,我们会在很多地方安装很多东西,使得我们无法进行多阶段的构建。为了解决这个问题,我们有两个潜在的解决方案。
- 使用
pip install --user - 使用
virtualenv
使用pip install --user 似乎是一个不错的选择,因为所有的包都将安装在~/.local 目录中,所以将它们从一个阶段复制到另一个阶段是相当容易的。但它会产生另一个问题:你会把我们用来编译依赖项的镜像中的所有系统级依赖项添加到最终的Docker基础镜像中--而我们不希望发生这种情况(记住我们的最佳做法是实现尽可能小的Docker基础镜像)。
排除了第一种选择,让我们探讨第二种选择:使用virtualenv 。 如果我们这样做,我们最终会得到以下Docker文件。
FROM python:3.10-slim as build
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
build-essential gcc
WORKDIR /usr/app
RUN python -m venv /usr/app/venv
ENV PATH="/usr/app/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install -r requirements.txt
FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
WORKDIR /usr/app/venv
COPY --from=build /usr/app/venv ./venv
COPY . .
ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "python", "app.py" ]
现在,我们有了所有必要的依赖,但没有编译它们所需的额外软件包。
容器化Python应用程序的多阶段构建的已知问题
目前,有一个已知的问题是预阶段没有被缓存。解决这个问题的最简单方法是使用BuildKit并在构建过程中添加参数BUILDKIT_INLINE_CACHE=1 。
我们第一次会正常构建,但后续的构建会使用以下命令。
export DOCKER_BUILDKIT=1
docker build -t flask-application --cache-from flask-application --build-arg BUILDKIT_INLINE_CACHE=1 .
你需要环境变量DOCKER_BUILDKIT=1 ,以便Docker知道使用BuildKit进行构建过程。也可以通过在Docker软件配置文件中添加以下设置来启用:/etc/docker/daemon.json (那么环境变量就不需要了)。
{ "features": { "buildkit": true } }
3.在生产中使用Python WSGI
为生产而构建的Python应用程序启用调试模式是一个大忌,也是一个等待发生的安全事件。不幸的是,这是一个常见的错误,我们在许多关于容器化Python Flask应用程序和其他WSGI Python应用程序框架的博客文章中看到过。
调试器允许从浏览器中执行任意的Python代码,这造成了巨大的安全风险。这可以在一定程度上得到保护,但它永远是一个漏洞。为了安全起见,再来一次。不要在生产环境中运行开发服务器或调试器!这也适用于你的容器化 Python 应用程序。
我们理解在调试模式下部署你的Python Flask应用程序比设置WSGI服务器和web/proxy更容易,但这只是在你必须解释为什么你的应用程序被黑掉时才会更容易。
那么如何解决这个问题呢?首先,你应该决定你想使用哪种WSGI服务器实现。主要有这四种,通常用于Python应用程序。
- Green Unicorn (Gunicorn)- 一个从Ruby的Unicorn项目移植过来的预分叉工作模式。
- uWSGI- 一个通用的、高性能的、低资源占用的WSGI服务器实现。
- mod_wsgi- 一个Apache模块,可以承载任何支持Python WSGI规范的Python Web应用程序。
- CherryPy- 一个Pythonic的、面向对象的HTTP框架,也可以作为WSGI服务器。
在这篇文章中,我们将使用Gunicorn作为例子,但请随时阅读所有这些模块的文档和信息,以便你可以选择最适合你的需求。在这篇文章中,我们将不看配置,因为它完全取决于用例。
对于容器化部分,我们只需要。
- 将
gunicorn依赖关系包含在requirements.txt - 改变Python应用程序容器的入口(关于这个改变,请看
CMD说明)。
FROM python:3.10-slim as build
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
build-essential gcc
WORKDIR /usr/app
RUN python -m venv /usr/app/venv
ENV PATH="/usr/app/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install -r requirements.txt
FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
WORKDIR /usr/app
COPY --from=build /usr/app/venv ./venv
COPY . .
ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "manage:app" ]
在我们基于这个新的Dockerfile重建Python应用程序后,我们可以运行并测试Flask应用程序是否可以处理请求。
docker run -p 8080:5000 flask-application
**注意:**我们建议,对于实际的生产就绪的部署,你不应该直接将Gunicorn暴露的端口绑定到主机上。相反,我们建议你在同一网络中部署一个反向代理服务器,处理所有的HTTP请求并提供静态文件。
4.以尽可能少的权限运行容器化的Python应用程序(而且永远不要以root )。
最小权限原则是Unix早期的一个长期的安全控制--当我们运行容器化的Python应用程序时,我们应该始终遵循这个原则。
官方的python Docker镜像默认不包含一个特权用户,所以我们需要创建它,以便能够用最小特权用户来运行程序。
为此,我们将在最终镜像(多阶段构建的第二阶段)的Docker文件中添加以下groupadd 命令,该文件实际运行gunicorn 进程。
FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
RUN groupadd -g 999 python && \
useradd -r -u 999 -g python python
USER 999
WORKDIR /usr/app
COPY --from=build /usr/app/venv ./venv
COPY . .
ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "manage:app" ]
但问题是,通过前面的修改,用户python 拥有被Docker执行的系统进程......那么复制的文件或WORKDIR 目录的文件所有权呢?默认情况下,Docker编译器将创建WORKDIR 目录,如果它不存在,但它将以root 系统用户作为所有者创建它,所以任何包括向该目录写入的操作都可能导致我们的应用程序出现致命的错误。此外,如果我们不改变它们的行为,即使我们已经改变了用户,复制的文件将默认为root 。
让我们来解决这个问题。
FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
RUN groupadd -g 999 python && \
useradd -r -u 999 -g python python
RUN mkdir /usr/app && chown python:python /usr/app
WORKDIR /usr/app
COPY --chown=python:python --from=build /usr/app/venv ./venv
COPY --chown=python:python . .
USER 999
ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "manage:app" ]
通过以上更新的COPY 指令,我们防止了在文件所有权方面对WORKDIR 目录的意外行为,并确保所有的文件都由将要运行该进程的同一个用户拥有。
5.处理你的容器化 Python 应用程序的不健康状态
当你部署一个应用程序时,你需要注意可能出现的未处理的事件或问题,这些事件或问题可能会使你的应用程序处于不健康的状态,即。1)它将不再工作,但 2)它不会杀死进程。如果发生这种情况,你的容器将不会被通知,你将有一个运行中的Python应用服务器,不再响应HTTP请求。
为了避免这种情况,我们要实现一个健康检查端点。为了检查你的容器化Python应用程序的健康状况,我们总是建议包括一个monitoring 或health HTTP端点,以确保应用程序仍然能够成功地处理用户请求。对于一些Python的Web应用框架,比如Flask,这将是一个简单的任务(下面是Flask的例子),结合Docker的HEALTHCHECK 指令,你将确保你的应用被很好地监控健康状态。
下面是一个Python Flask应用片段,它添加了一个/health HTTP端点。
@app.route('/health', methods=['GET'])
def health():
# Handle here any business logic for ensuring you're application is healthy (DB connections, etc...)
return "Healthy: OK"
一旦你的应用程序中有了这个端点,你只需要在你的Docker文件中包含HEALTHCHECK 指令。
FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
RUN groupadd -g 999 python && \
useradd -r -u 999 -g python python
RUN mkdir /usr/app && chown python:python /usr/app
WORKDIR /usr/app
COPY --chown=python:python --from=build /usr/app/venv ./venv
COPY --chown=python:python . .
USER 999
ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "manage:app" ]
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD curl -f https://localhost:5000/health
如果你在Kubernetes上进行部署,那么你应该知道DockerfileHEALTHCHECK 指令会被忽略。在你的YAML中需要一个相称的Kubernetes可用性、准备性和启动探针。
因此,为了解决上述HEALTHCHECK 指令的Kubernetes部署问题。
...
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 5
periodSeconds: 30
timeoutSeconds: 30
failureThreshold: 3
...
注意,这个配置将嵌套在pod或部署YAML文件的container spec 部分。现在,在重启策略为always 或unless_stopped 的情况下,如果我们的Python应用容器进入不健康状态,将始终被重启。
6.在你的Python Docker应用程序镜像中寻找并修复安全漏洞
我们已经确定,更大的Docker基础镜像会带来很多问题,比如我们需要维护的大型软件栈,保持最新的安全修复,等等。
我们还探索了使用Snyk Advisor来确定不同基础镜像中的大小和漏洞指标。但Advisor只是冰山一角。Snyk是一个免费的开发者安全平台,你可以用它来测试任何东西,从你自己的Python代码,到你的Python依赖项(如requirements.txt ),运行你的应用程序的Python容器镜像,甚至是协调这一切的Terraform或Kubernetes配置。
Snyk最好的一点是,它为它发现的漏洞提供推荐的修复方法。因此,它不只是告诉你存在哪些安全漏洞,而是会自动创建一个修复程序或提供补救建议。而且这一切都在你现有的工具(IDE、CLI、Docker等)和工作流程(Git、CI/CD等)中完成。
例子。用Snyk扫描我们的容器化Python应用
让我们看看当我们使用python:3.8 的Python Docker镜像来构建这个样本Python Flask应用程序时,如何从Snyk CLI中工作。
Snyk已经集成到Windows、macOS和Linux操作系统的Docker Desktop中(通过docker-ce),它有一个内置的命令--docker scan ,你可以运行它来扫描你的Docker镜像。
如果你安装了Snyk CLI,你可以用它来扫描你的Python项目依赖,你的Python代码,以及更多。所以,首先,我们安装Snyk CLI。如果你有一个Node.js环境,你可以用npm 软件包管理器来做,如下所示。
npm install -g snyk
或者如果你是在macOS或Linux上,并且使用Homebrew,你可以这样安装。
brew tap snyk/tap
brew install snyk
关于其他的安装方法,请看我们关于如何安装Snyk CLI的指南。
接下来,我们需要从CLI上进行认证,以获得一个有效的API令牌来查询漏洞数据库。
snyk auth
完成上述工作后,我们可以继续用python:3.8 基本镜像构建Python应用程序的本地Docker镜像。
❯ docker build . -t python-flask-app
FROM python:3.8 as build
[+] Building 5.2s (8/13)
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 513B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/python:3.8
=> [auth] library/python:pull token for registry-1.docker.io
=> [internal] load build context
...
而现在,让我们用Snyk扫描它,通过运行。
snyk container test python-flask-app
输出的结果如下(有意缩短,因为输出很大)。
Testing python-flask-app...
✗ Low severity vulnerability found in tiff/libtiff5
Description: Out-of-bounds Read
Info: https://snyk.io/vuln/SNYK-DEBIAN11-TIFF-514595
Introduced through: imagemagick@8:6.9.11.60+dfsg-1.3, imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3
From: imagemagick@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6.q16@8:6.9.11.60+dfsg-1.3 > imagemagick/libmagickcore-6.q16-6@8:6.9.11.60+dfsg-1.3 > tiff/libtiff5@4.2.0-1
From: imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3 > imagemagick/libmagickcore-6.q16-dev@8:6.9.11.60+dfsg-1.3 > tiff/libtiff-dev@4.2.0-1 > tiff/libtiff5@4.2.0-1
From: imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3 > imagemagick/libmagickcore-6.q16-dev@8:6.9.11.60+dfsg-1.3 > tiff/libtiff-dev@4.2.0-1 > tiff/libtiffxx5@4.2.0-1 > tiff/libtiff5@4.2.0-1
and 3 more...
✗ High severity vulnerability found in imagemagick/imagemagick-6-common
Description: Information Exposure
Info: https://snyk.io/vuln/SNYK-DEBIAN11-IMAGEMAGICK-1246513
Introduced through: imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3, imagemagick/libmagickwand-dev@8:6.9.11.60+dfsg-1.3, imagemagick@8:6.9.11.60+dfsg-1.3
From: imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6-common@8:6.9.11.60+dfsg-1.3
From: imagemagick/libmagickwand-dev@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6-common@8:6.9.11.60+dfsg-1.3
From: imagemagick@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6.q16@8:6.9.11.60+dfsg-1.3 > imagemagick/libmagickcore-6.q16-6@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6-common@8:6.9.11.60+dfsg-1.3
and 24 more...
✗ Critical severity vulnerability found in python3.9/libpython3.9-stdlib
Description: Improper Input Validation
Info: https://snyk.io/vuln/SNYK-DEBIAN11-PYTHON39-1290158
Introduced through: mercurial@5.6.1-4
From: mercurial@5.6.1-4 > python3-defaults/python3@3.9.2-3 > python3-defaults/libpython3-stdlib@3.9.2-3 > python3.9/libpython3.9-stdlib@3.9.2-1
From: mercurial@5.6.1-4 > python3-defaults/python3@3.9.2-3 > python3.9@3.9.2-1 > python3.9/libpython3.9-stdlib@3.9.2-1
From: mercurial@5.6.1-4 > python3-defaults/python3@3.9.2-3 > python3-defaults/python3-minimal@3.9.2-3 > python3.9/python3.9-minimal@3.9.2-1
and 4 more...
✗ Critical severity vulnerability found in glibc/libc-bin
Description: Use After Free
Info: https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-1296898
Introduced through: glibc/libc-bin@2.31-13+deb11u2, meta-common-packages@meta
From: glibc/libc-bin@2.31-13+deb11u2
From: meta-common-packages@meta > glibc/libc-dev-bin@2.31-13+deb11u2
From: meta-common-packages@meta > glibc/libc6@2.31-13+deb11u2
and 1 more...
Organization: snyk-demo-567
Package manager: deb
Project name: docker-image|python-flask-app
Docker image: python-flask-app
Platform: linux/amd64
Base image: python:3.8.12-bullseye
Licenses: enabled
Tested 427 dependencies for known issues, found 171 issues.
Base Image Vulnerabilities Severity
python:3.8.12-bullseye 171 6 critical, 6 high, 27 medium, 132 low
Recommendations for base image upgrade:
Alternative image types
Base Image Vulnerabilities Severity
python:3.9-slim 37 1 critical, 0 high, 1 medium, 35 low
python:3.11-rc-slim 37 1 critical, 0 high, 1 medium, 35 low
python:3.8.12-slim-bullseye 37 1 critical, 0 high, 1 medium, 35 low
python:3.10-slim-buster 70 2 critical, 9 high, 9 medium, 50 low
因此,我们有427个依赖项以开源库的形式引入,作为Python 3.8操作系统内容的一部分。它们为这个Python Flask应用程序引入了总共171个安全漏洞,这是因为我们选择了python:3.8 的那个基础镜像。
这时,你可能会不解地问:"我怎样才能修复它呢?"幸运的是,Snyk有一些建议,我们可以升级到哪些其他的基础镜像,或者完全换成其他的镜像,以降低攻击面。
下面是这个基础图像推荐建议的一个清晰的视觉截图。

现在我们可以做出一个明智的、由数据驱动的决定,以确保我们的Python应用程序的安全。通过选择Snyk推荐的任何一个备选Docker镜像,我们可以大大降低我们应用程序中捆绑的软件的攻击面。
为了更好地控制你的应用程序的安全,将你的资源库连接到Snyk UI,导入你的源代码和Dockerfile,这样你不仅可以发现这些漏洞,还可以持续监控它们的新安全问题。下面是来自Snyk UI的同样的Docker基础镜像报告。

有什么比监测和发现安全漏洞更好?修复它们! 🙂
如果你把你的Git仓库连接到Snyk,那么我们也可以在你的仓库中创建拉动请求(自动!),以提供这些Docker基础镜像的升级,就像你在这里看到的那样。

如果你喜欢这个,可以看看这篇关于用Dockerfile拉动请求自动实现容器安全的后续文章。
Python应用程序是如何被容器化的?
Docker是一种软件虚拟化技术,它允许我们以基于Python Docker镜像的容器化Python应用程序的形式构建可重用的、跨平台的、快速部署的软件。这些应用程序是通过基础设施即代码的方式定义的,文件名为Dockerfile,我们可以构建并使用它来运行一个容器化的Python应用程序。
docker build -t flask-application .
docker run -p 8080:5000 flask-application
给开发者的Python安全建议
这些最佳实践应该可以帮助你更好地创建、管理和保护你的容器化Python应用程序。如果你喜欢阅读这些最佳实践,并且你重视应用安全和对安全的整体拥护,那么我们会推荐以下资源作为后续阅读材料。
- 丹尼尔-伯曼的《安全Python开发的Snyk入门》非常精彩
- Brian Vermeer的《Snyk CLI的秘密》,有一个很好的作弊表
- Liran Tal和Yoni Golberg的《Docker基础应用的全面Node.js最佳实践》。
- 最后,对于你的Java朋友,这篇《Docker for Java开发者》。你需要知道的5件事,不要让你的安全失败,作者Brian Vermeer。
- Frank Fischer的Python安全最佳实践小抄
- 一份关于Python项目中常见安全问题的功能齐全、见解深刻的报告