【Docker + Python】使用 Docker 创建容器化 Python 应用程序(入门指南)

4 篇文章 0 订阅


概述

本文讲解如何使用 Docker 创建容器化 Python 应用程序,你将学到如下知识:

  • 创建一个 Python 应用程序示例
  • 创建一个新的 Dockfile,其中包含构建 Python 镜像所需的指令
  • 构建一个镜像,并将新构建的镜像作为一个容器运行
  • 设置卷和网络
  • 使用 Compose 编排容器
  • 使用容器进行开发
  • 使用 GitHub Actions 为你的应用程序配置 CI/CD 管道
  • 应用程序的云部署

安装

Docker 官方安装指南:https://docs.docker.com/engine/install/

构建

预备知识

阅读 Get started Part 1 中的介绍和设置,以了解 Docker 的 基本概念。

启用 BuildKit

构建镜像前,先在机器上启动 BuildKit。BuildKit 允许你高效地构建 Docker 镜像。

在 Docker Desktop 上,所有用户默认都启动了 BuildKit。如果你安装了 Docker Desktop,你不必手动启用 BuildKit。如果你在 Linux 上运行 Docker,你可以通过修改环境编辑或 BuildKit 默认设置来启用它。

要在运行 docker build 命令时设置 BuildKit 环境变量,请运行:

DOCKER_BUILDKIT=1 docker build .

要默认启用 Dokcer BuildKit,请将 /etc/docker/daemon.json 功能中的守护程序配置设置为 true,然后重启启动守护程序。如果 daemon.json 文件不存在,就新建一个,然后将以下内容添加到文件中:

{
  "features": {"buildkit": true}
}

最后,重启 Docker 守护程序。

概述

现在我们对容器和 Docker 平台有了很好的了解,让我们来看看如何如何构建我们的第一个镜像。镜像包含运行应用程序所需的一切:代码或二进制文件、运行时、依赖项和所需的其它文件系统对象。

要完成本教程,你需要以下内容:

示例应用程序

让我们使用 Flask 框架创建一个简单的 Python 应用程序作为我们的教程示例。在本地计算机创建一个名为 python-docker 的目录,并按照以下步骤创建一个简单的 web 服务器:

cd /path/to/python-docker
pip3 install Flask
pip3 freeze > requirements.txt
touch app.py

现在,让我们添加一些代码来处理简单的 web 请求。在你喜欢的 IDE 中打开这个工作目录,并在 app.py 文件中键入以下代码:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'Hello, Docker!'

测试应用程序

启动应用程序并确保它能正常运行。打开终端并导航到你创建的工作目录。

python3 -m flask run

为确保应用程序能够正常运行,打开一个新浏览器并导航到 http://localhost:5000

切换回服务器运行终端,你应该会在服务器日志中看到以下请求。你机器上的数据和时间戳是不同的。

127.0.0.1 - - [22/Sep/2020 11:07:41] "GET / HTTP/1.1" 200 -

为 Python 创建一个 Dockerfile

现在,我们的应用程序已经能够正常运行,让我们看看如何创建一个 Dockerfile。

Dockerfile 是一个文本文档,包含组装 Docker 镜像的指令。当我们告知 Docker 通过执行 docker build 命令来构建我们的镜像时,Docker 会读取这些指令并执行他们,最终创建一个 Docker 镜像。

让我们了解一下为应用程序创建 Dockerfile 的过程。在项目的根目录下,创建一个名为 Dockerfile 的文件,并在文本编辑器中打开该文件。

如何命名你的 Dockerfile?

Dockerfile 使用的默认文件名是 Dockerfile(不带文件拓展名)。使用默认名称运行您运行 docker build 命令,而不必指定额外的命令参数。
有些项目可能需要不同的 Dockerfile 用于特定目的。一个常见的约定是将这些文件命名为 Dockerfile.<something><something>.Dockerfile。这样的 Dockerfiles 可以通过 docker build 命令上的 --file(或 -f 速记)选项使用。请参阅 docker build 指南中的 “指定一个 Dockerfile” 部分,以了解 --file 选项。
我们建议对项目的主 Dockerfile 使用默认值(Dockerfile),本指南中的大多数示例都将使用该文件。

要添加到 Dockerfile 中的第一行是 # syntax 解析指令。虽然是可选的,但该指令指示 Docker builder 在解析 Dockerfile 时使用什么语法,并允许启用 BuildKit 的较旧版本的 Docker 在启动 build 之前升级解析器。解析指令必须出现在 Dockerfile 中的任何其它注释,空格或 Dockerfile 指令之前,并且应该是 Dockerfile 中的第一行。

# syntax=docker/dockerfile:1

我们推荐使用 docker/dockerfile:1,它始终指向版本1语法的最新版本。构建之前,BuildKit 会自动地检查语法更新,确保你使用的是最新的版本。

接下来,我们需要在 Dockerfile 中添加一行代码,告诉 Docker 我们想为应用程序使用什么基础的镜像。

# syntax=docker/dockerfile:1

FROM python:3.8-slim-buster

Docker 镜像可以从其它镜像继承。因此,我们将使用 Python 官方镜像,而不是使用我们自己的基础镜像,该镜像已经具有运行 Python 应用程序所需的所有工具和包。

注意
要了解创建自己基本镜像的更多信息,请参见 创建基本镜像

为了在运行其余命令时更容易,让我们创建一个工作目录。这指示 Docker 将此路径用作后续所有命令的默认位置。这样做以后,我们不必键入完整的文件路径,而是可以使用基于工作目录的相对路径。

WORKDIR /app

通常,下载下来用 Python 编写的项目只有,首先要做的就是安装 pip 包。这可以确保应用程序安装了其所有依赖项。

在我们运行 pip3 install 之前,我们需要将 requirements.txt 文件放到镜像中。我们将使用 COPY 命令来执行此操作。COPY 命令接受两个参数。第一个参数告诉 Docker 要将哪些文件复制到镜像中。第二个参数告诉 Docker 要将该文件复制到何处。我们将把 requirements.txt 文件复制到我们的工作目录 /app 中。

COPY requirements.txt requirements.txt

一旦镜像中有了 requirements.txt 文件,我们就可以使用 RUN 命令来执行 pip3 install 命令。这与我们在本地机器上运行 pip3 install 的工作原理完全相同,但这次模块被安装到镜像中。

RUN pip3 install -r requirements.txt

现在,我们有了一个基于 Python 3.8 版本的镜像,并且已经安装好了依赖项。下一步就是将源代码添加到镜像中。我们将像上面的 requirements.txt 文件一样使用 COPY 命令。

COPY . .

COPY 命令获取当前目录中的所有文件,并将他们复制到镜像中。现在,我们要做的就是告诉 Docker,当我们的镜像在容器中执行时,我们要运行什么命令。我们使用 CMD 命令来做这个。注意,我们需要通过指定 --host=0.0.0.0 使应用程序在外部可见(即从容器外部可见)。

CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]

以下是完整的 Dockerfile 文件:

# syntax=docker/dockerfile:1

FROM python:3.8-slim-buster

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

COPY . .

CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]

目录结构

简单回顾一下,我们在本机中创建了一个名为 python-docker 的目录,并使用 Flask 框架创建了一个简单的 Python 应用程序。我们使用 requirements.txt 文件收集我们的依赖包,并创建了一个包含构建镜像命令的 Dockerfile 文件。Python 应用程序的目录结构如下所示:

python-docker
|____ app.py
|____ requirements.txt
|____ Dockerfile

构建一个镜像

现在我们已经创建了 Dockerfile,让我们来构建镜像。为此,我们使用 docker build 命令。docker build 命令使用 Dockerfile 和 context 构建镜像。构建用的 context 是位于指定路径或 URL 中的一组文件。Docker 构建过程中可以访问 context 中的任何文件。

构建命令可以选择 --tag 标签。该 tag 用于设置镜像的名称和格式为 name:tag 的可选标签。我们暂时就不用 tag 了,有助于简化工作。如果未设置 tag 参数,Docker 默认使用 latest 作为 tag 的值。

让我们构建我们的第一个 Docker 镜像。

docker build --tag python-docker .
[+] Building 25.3s (16/16) FINISHED                                                                                                                 
 => [internal] load build definition from Dockerfile                                                                                           0.0s
 => => transferring dockerfile: 262B                                                                                                           0.0s
 => [internal] load .dockerignore                                                                                                              0.0s
 => => transferring context: 2B                                                                                                                0.0s
 => resolve image config for docker.io/docker/dockerfile:1                                                                                     5.4s
 => [auth] docker/dockerfile:pull token for registry-1.docker.io                                                                               0.0s
 => docker-image://docker.io/docker/dockerfile:1@sha256:9e2c9eca7367393aecc68795c671f93466818395a2693498debe831fd67f5e89                       2.2s
 => => resolve docker.io/docker/dockerfile:1@sha256:9e2c9eca7367393aecc68795c671f93466818395a2693498debe831fd67f5e89                           0.0s
 => => sha256:9e2c9eca7367393aecc68795c671f93466818395a2693498debe831fd67f5e89 2.00kB / 2.00kB                                                 0.0s
 => => sha256:cb8d6fa06268f199b41ccc0c2718caca915f74c640f3cd044af8e205e8d616cf 528B / 528B                                                     0.0s
 => => sha256:b1e2fdbfa8cb359e5e566c70344df94749db2e6419e06ac542ed14a348c3ce81 1.21kB / 1.21kB                                                 0.0s
 => => sha256:8d9a8cad598f6c5e97bcb90aab70cd2e868d3dc0d514fa9b60468bf72bf32338 9.67MB / 9.67MB                                                 2.0s
 => => extracting sha256:8d9a8cad598f6c5e97bcb90aab70cd2e868d3dc0d514fa9b60468bf72bf32338                                                      0.1s
 => [internal] load .dockerignore                                                                                                              0.0s
 => [internal] load build definition from Dockerfile                                                                                           0.0s
 => [internal] load metadata for docker.io/library/python:3.8-slim-buster                                                                      4.1s
 => [auth] library/python:pull token for registry-1.docker.io                                                                                  0.0s
 => [1/5] FROM docker.io/library/python:3.8-slim-buster@sha256:8e04987ebe19f6ec73575287f0caf836da0d3c14a8a68392352236af8b98b3fb                4.9s
 => => resolve docker.io/library/python:3.8-slim-buster@sha256:8e04987ebe19f6ec73575287f0caf836da0d3c14a8a68392352236af8b98b3fb                0.0s
 => => sha256:8e04987ebe19f6ec73575287f0caf836da0d3c14a8a68392352236af8b98b3fb 1.86kB / 1.86kB                                                 0.0s
 => => sha256:ab3056266306eb0abf54a5d51f76d7018bd6c210d526cfa0a3b2a8a91aa8f8c1 1.37kB / 1.37kB                                                 0.0s
 => => sha256:319f36747aed383d5a329b87812f818dd51dac6d3c20d40f817d02e6d7526522 7.92kB / 7.92kB                                                 0.0s
 => => sha256:07aded7c29c6011dfdf02fc98e087c941d3c2661c4e73d134c6491e25231d16c 27.15MB / 27.15MB                                               3.0s
 => => sha256:1242903d2b2348c44f7d30c366f0f9f034913c7353d1eaf1967f39233f343772 2.77MB / 2.77MB                                                 2.4s
 => => sha256:6feb96d3e4f934a8bf382bcb86902040578493fb0939239bd0aa2c9e2ae90a25 10.73MB / 10.73MB                                               3.2s
 => => sha256:36bf03acdc5060e3181ffab2b339aad6bc2d98b03f932d13450f87653a401c35 233B / 233B                                                     2.9s
 => => sha256:366f5e2f70434bee85845903597a45030c395be3c3b0f59b4a8616b6f973f981 2.64MB / 2.64MB                                                 3.9s
 => => extracting sha256:07aded7c29c6011dfdf02fc98e087c941d3c2661c4e73d134c6491e25231d16c                                                      0.8s
 => => extracting sha256:1242903d2b2348c44f7d30c366f0f9f034913c7353d1eaf1967f39233f343772                                                      0.1s
 => => extracting sha256:6feb96d3e4f934a8bf382bcb86902040578493fb0939239bd0aa2c9e2ae90a25                                                      0.3s
 => => extracting sha256:36bf03acdc5060e3181ffab2b339aad6bc2d98b03f932d13450f87653a401c35                                                      0.0s
 => => extracting sha256:366f5e2f70434bee85845903597a45030c395be3c3b0f59b4a8616b6f973f981                                                      0.2s
 => [internal] load build context                                                                                                              0.0s
 => => transferring context: 455B                                                                                                              0.0s
 => [2/5] WORKDIR /app                                                                                                                         0.3s
 => [3/5] COPY requirements.txt requirements.txt                                                                                               0.0s
 => [4/5] RUN pip3 install -r requirements.txt                                                                                                 8.0s
 => [5/5] COPY . .                                                                                                                             0.0s
 => exporting to image                                                                                                                         0.1s
 => => exporting layers                                                                                                                        0.1s
 => => writing image sha256:811f21deadc2fa57587e353d637a43543c39f40c46a375cfd4e3fe3897fe4eb2                                                   0.0s
 => => naming to docker.io/library/python-docker

查看本地镜像

我们有两种查看本地机器上的镜像列表的方式。一种是使用 CLI,另一种是使用 Docker Desktop。由于我们目前正在终端中工作,让我们看看用 CLI 列出的镜像。

只需运行 docker images 命令即可简单地列出镜像。

docker images
REPOSITORY      TAG               IMAGE ID       CREATED         SIZE
python-docker   latest            8cae92a8fbd6   3 minutes ago   123MB
python          3.8-slim-buster   be5d294735c6   9 days ago      113MB

你应该看到至少列出了两个镜像。其中一个是基础 3.8-slim-buster 镜像,另一个是我们刚才构建的 python-docker:latest 镜像。

标记镜像

如前所述,镜像名称由斜杠分割的名称组件组合而成。名称组件可以包含小写字母、数字和分隔符。分隔符定义为句号、一个或多个下划线、一个或多个破折号。名称组件不能以分隔符开始或结束

镜像由清单和层列表组成。除了指向这些工件组合的“tag”之外,不要太担心清单和层。一个经镜像可以有多个 tag。让我们为构建的镜像创建第二个 tag,并查看它的层。

要为上面创建的镜像构建新 tag,请运行以下命令:

docker tag python-docker:latest python-docker:v1.0.0

·docker tag` 命令为镜像创建一个新 tag,而不是创建一个新镜像。新 tag 和 旧 tag 指向相同的镜像,新 tag 只是引用镜像的另一种方式。

现在,运行 docker images 命令查看本地镜像列表。

docker images
REPOSITORY      TAG               IMAGE ID       CREATED         SIZE
python-docker   latest            8cae92a8fbd6   4 minutes ago   123MB
python-docker   v1.0.0            8cae92a8fbd6   4 minutes ago   123MB
python          3.8-slim-buster   be5d294735c6   9 days ago      113MB

你可以看到,我们有两个以 python-docker 开头的镜像。我们知道它们是相同的镜像,因为如果你看一下 IMAGE ID 列,你可以看到这两个镜像的值是相同的。

让我们删除刚才创建的 tag。为此,我们要使用 rmi 命令,rmi 命令代表删除镜像。

docker rmi python-docker:v1.0.0
Untagged: python-docker:v1.0.0

注意,Docker 的响应告诉我们镜像并没有被删除,只是未标记。你可以运行 docker images 命令进行检查。

docker images
REPOSITORY      TAG               IMAGE ID       CREATED         SIZE
python-docker   latest            8cae92a8fbd6   6 minutes ago   123MB
python          3.8-slim-buster   be5d294735c6   9 days ago      113MB

标记为 :v1.0.0 的镜像已经被删除,但我们的机器上仍有标记为 :latest 的镜像。

总结

在本模块中,我们介绍了如何设置示例 Python 应用程序,我们将在本教程的其余部分中使用该应用程序。我们还创建了一个 Dockerfile,用于构建 Docker 镜像。然后,我们学习了给镜像加标签和删除镜像。在下一个模块中,我们将了解如何将镜像作为容器运行。

运行

预备知识

阅读构建模块以了解如何构建一个 Python 镜像。

概述

前一个模块中,我们创建了示例应用程序,然后创建了一个用于生成镜像的 Dockerfile。我们使用 docker 命令 docker build 来创建映像。既然我们已经有了一个镜像,可以运行该镜像,看看应用程序是否能够正常运行。

容器是一个正常的操作系统进程,但这个进程是隔离的,因为它有自己的文件系统、自己的网络和自己独立于主机的独立进程树。

要在容器中运行镜像,我们可以可以使用 docker run 命令。docker run 命令需要一个参数,即镜像的名称。让我们启动镜像并确保它能正常运行。在终端运行以下命令。

docker run python-docker

运行此命令后,你会注意到没有返回到命令提示符。这是因为我们的应用程序是一个在循环中运行的 REST 服务器,持续等待传入的请求,而不会将控制权返回给操作系统,直到我们停止容器。

让我们打开一个新的终端,然后使用 curl 命令向服务器发起一个 GET 请求。

curl localhost:5000
curl: (7) Failed to connect to localhost port 5000: Connection refused

如你所见,我们的 curl 命令失败了,因为到服务器的连接被拒绝了。这意味着,我们无法链接到端口5000的主机。这是意料之中的,因为我们的容器是独立运行的,其中包括网络。让我们停止容器,并在本地网络上发布端口5000后重新启动。

要停止容器,请按 ctrl-c。这将返回到终端提示符。

要发布容器的端口,我们将在 docker run 命令上使用 --publish 参数(简称 -p)。--publish 命令的格式是 [host port]:[container port]。因此,如果我们想将容器内的端口5000公开给容器外的端口3000,我们将把 3000:5000 传递给 --publish 参数。

在容器中运行 flask 应用程序时,我们没有指定端口,默认值是5000。如果我们希望之前对端口5000的请求生效,我们可以将主机的端口5000映射到容器的端口5000:

docker run --publish 5000:5000 python-docker

现在,让我们按照上面一样重新运行 curl 命令。记得打开一个新的终端。

curl localhost:5000
Hello, Docker!

成功了!我们能够在端口5000上连接到容器中运行的应用程序。切换回运行容器的终端,您应该看到控制台记录了 GET 请求。

[04/Oct/2021 07:22:28] "GET / HTTP/1.1" 200 -

按 ctrl-c 停止容器。

以分离模式运行

到目前为止,这很好,但是我们的样例应用程序是一个 Web 服务器,我们不需要连接到容器。Docker 可以以分离模式或在后台运行容器。为此,我们可以使用 --detach 或缩写为 -d。Docker 会像以前一样启动您的容器,但这次将从容器“分离”并返回到终端提示符。

docker run -d -p 5000:5000 python-docker
6505e11551b2f34e7ac16071a30df26b905205d7c0f8961baa1e4427c21beb74

列出容器

因为我们是在后台运行容器的,我们如何知道容器是否在运行,或者其他什么容器在我们的机器上运行?我们可以运行 docker ps 命令。就像在查看 Linux 机器上的进程列表一样,我们可以运行 ps 命令。本着同样设计理念,我们可以运行 docker ps 命令,该命令显示机器上运行的容器列表。

docker ps
CONTAINER ID   IMAGE           COMMAND                  CREATED         STATUS         PORTS                                       NAMES
6505e11551b2   python-docker   "python3 -m flask ru…"   5 minutes ago   Up 5 minutes   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   serene_gauss

docker ps 命令提供了一系列关于正在运行的容器的信息。我们可以看到容器 ID、容器内运行的镜像、用于启动容器的命令、创建容器的时间、状态、公开的端口以及容器的名称。

您可能想知道我们的容器名称来自哪里。由于我们在启动容器时没有提供容器的名称,Docker 生成了一个随机名称。我们将在一分钟内解决这个问题,但首先我们需要停止容器。要停止容器,请运行 docker stop 命令,该命令将停止容器。您需要传递容器的名称,或者您可以使用容器 ID。

docker stop wonderful_kalam
wonderful_kalam

现在,重新运行 docker ps 命令,查看正在运行的容器列表。

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

停止、启动和命名容器

您可以启动、停止和重新启动 Docker 容器。当我们停止一个容器时,它不是被删除,而是状态被更改为已停止,容器内的进程也将停止。当我们在前一个模块中运行 docker ps 命令时,默认输出只显示正在运行的容器。

当我们传递 --all-a 参数时,我们会看到机器上的所有容器,而不管它们的启动或停止状态如何。

docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
ce02b3179f0f        python-docker         "python3 -m flask ru…"   16 minutes ago      Exited (0) 5 minutes ago                        wonderful_kalam
ec45285c456d        python-docker         "python3 -m flask ru…"   28 minutes ago      Exited (0) 20 minutes ago                       agitated_moser
fb7a41809e5d        python-docker         "python3 -m flask ru…"   37 minutes ago      Exited (0) 36 minutes ago                       goofy_khayyam

您现在应该看到列出了几个容器。这些是我们开始和停止但没有被移走的容器。

让我们重新启动刚刚停止的容器。找到我们刚刚停止的容器的名称,并在 restart 命令中替换下面容器的名称。

docker restart wonderful_kalam
wonderful_kalam

现在使用 docker ps 命令再次列出所有容器。

docker ps --all
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                    NAMES
ce02b3179f0f        python-docker         "python3 -m flask ru…"   19 minutes ago      Up 8 seconds                0.0.0.0:5000->5000/tcp   wonderful_kalam
ec45285c456d        python-docker         "python3 -m flask ru…"   31 minutes ago      Exited (0) 23 minutes ago                            agitated_moser
fb7a41809e5d        python-docker         "python3 -m flask ru…"   40 minutes ago      Exited (0) 39 minutes ago                            goofy_khayyam

注意,我们刚刚重启的容器是在分离模式下启动的,并且公开了端口5000。另外,观察容器的状态为“Up X seconds”。当您重新启动容器时,它将以与最初启动时相同的参数或命令启动。

现在,让我们停止并移除所有容器,看看如何修复随机命名问题。停止我们刚才启动的容器。找到正在运行的容器的名称,并将下面命令中的名称替换为系统上的容器名称。

docker stop wonderful_kalam
wonderful_kalam

现在,我们所有的容器都停止了,让我们把它们移除吧。移除容器时,该容器将不再运行,也不会处于停止状态,但容器内的进程已停止,容器的元数据已被删除。

docker ps --all
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                    NAMES
ce02b3179f0f        python-docker         "python3 -m flask ru…"   19 minutes ago      Up 8 seconds                0.0.0.0:5000->5000/tcp   wonderful_kalam
ec45285c456d        python-docker         "python3 -m flask ru…"   31 minutes ago      Exited (0) 23 minutes ago                            agitated_moser
fb7a41809e5d        python-docker         "python3 -m flask ru…"   40 minutes ago      Exited (0) 39 minutes ago                            goofy_khayyam

要删除容器,只需运行 docker rm 命令传递容器名称。您可以使用单个命令将多个容器名称传递给该命令。同样,将下面命令中的容器名称替换为系统中的容器名称。

docker rm wonderful_kalam agitated_moser goofy_khayyam
wonderful_kalam
agitated_moser
goofy_khayyam

再次运行 docker ps --all 命令以查看是否已删除所有容器。

现在,让我们讨论一下随机命名问题。标准做法是命名容器,原因很简单,即更容易识别容器中运行的内容以及与之关联的应用程序或服务。

要命名容器,只需将 --name 参数传递给 docker run 命令。

docker run -d -p 5000:5000 --name rest-server python-docker
1aa5d46418a68705c81782a58456a4ccdb56a309cb5e6bd399478d01eaa5cdda
docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
1aa5d46418a6        python-docker         "python3 -m flask ru…"   3 seconds ago       Up 3 seconds        0.0.0.0:5000->5000/tcp   rest-server

太好了!现在,我们可以根据名称轻松识别容器。

总结

在本模块中,我们了解了如何运行容器、发布端口和在分离模式下运行容器。我们还介绍了通过启动、停止和重新启动容器来管理容器。我们还研究了为容器命名,以便它们更容易识别。在下一个模块中,我们将学习如何在容器中运行数据库并将其连接到应用程序。

开发

预备知识

阅读运行模块以了解如何将镜像作为容器运行。

简介

在本模块中,我们将介绍如何为在前面模块中构建的应用程序设置本地开发环境。我们将使用 Docker 构建我们的镜像,Docker Compose 使一切变得更加简单。

在容器中运行数据库

首先,我们将了解如何在容器中运行数据库,以及如何使用卷和网络来持久化数据并允许应用程序与数据库通信。然后,我们将把所有内容合并到一个 Compose 文件中,该文件允许我们使用一个命令设置和运行本地开发环境。最后,我们将研究如何将调试器连接到在容器中运行的应用程序。

我们不需要下载 MySQL、安装、配置 MySQL 数据库,然后将其作为服务运行,而可以使用 Docker 官方 MySQL 映像并在容器中运行它。

在容器中运行MySQL之前,我们将创建两个卷,Docker可以管理这些卷来存储持久数据和配置。让我们使用 Docker 提供的托管卷功能,而不是使用绑定装载。您可以在我们的文档中阅读有关使用卷的所有内容。

现在让我们创建卷。我们将为数据和 MySQL 的配置各创建一个。

docker volume create mysql
docker volume create mysql_config

现在,我们将创建一个网络,应用程序和数据库将使用该网络相互通信。该网络称为用户定义的网桥网络,它为我们提供了一个很好的 DNS 查询服务,我们可以在创建连接字符串时使用该服务。

docker network create mysqlnet

现在我们可以在容器中运行 MySQL,并连接到上面创建的卷和网络。Docker 从 Hub 拉取映像并在本地运行它。在以下命令中,选项 -v 用于启动具有卷的容器。要了解更多信息,请参见 Docker卷

docker run --rm -d -v mysql:/var/lib/mysql \
  -v mysql_config:/etc/mysql -p 3306:3306 \
  --network mysqlnet \
  --name mysqldb \
  -e MYSQL_ROOT_PASSWORD=p@ssw0rd1 \
  mysql

现在,让我们确保 MySQL 数据库正在运行,并且可以连接到它。使用以下命令连接到容器内正在运行的 MySQL 数据库,然后输入“p@ssw0rd1”当提示输入密码时:

docker exec -ti mysqldb mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.23 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

将应用程序连接到数据库

在上面的命令中,我们通过将“MySQL”命令传递给 mysqldb 容器来登录 MySQL 数据库。按 CTRL-D 退出 MySQL 交互终端。

接下来,我们将更新在构建模块中创建的示例应用程序。要查看 Python 应用程序的目录结构,请参见 Python 应用程序目录结构

好的,现在我们有了一个正在运行的 MySQL,让我们更新 app.py 以使用 MySQL 作为数据存储。我们还要向服务器添加一些路由。一个用于获取记录,另一个用于插入记录。

import mysql.connector
import json
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
  return 'Hello, Docker!'

@app.route('/widgets')
def get_widgets() :
  mydb = mysql.connector.connect(
    host="mysqldb",
    user="root",
    password="p@ssw0rd1",
    database="inventory"
  )
  cursor = mydb.cursor()


  cursor.execute("SELECT * FROM widgets")

  row_headers=[x[0] for x in cursor.description] #this will extract row headers

  results = cursor.fetchall()
  json_data=[]
  for result in results:
    json_data.append(dict(zip(row_headers,result)))

  cursor.close()

  return json.dumps(json_data)

@app.route('/initdb')
def db_init():
  mydb = mysql.connector.connect(
    host="mysqldb",
    user="root",
    password="p@ssw0rd1"
  )
  cursor = mydb.cursor()

  cursor.execute("DROP DATABASE IF EXISTS inventory")
  cursor.execute("CREATE DATABASE inventory")
  cursor.close()

  mydb = mysql.connector.connect(
    host="mysqldb",
    user="root",
    password="p@ssw0rd1",
    database="inventory"
  )
  cursor = mydb.cursor()

  cursor.execute("DROP TABLE IF EXISTS widgets")
  cursor.execute("CREATE TABLE widgets (name VARCHAR(255), description VARCHAR(255))")
  cursor.close()

  return 'init database'

if __name__ == "__main__":
  app.run(host ='0.0.0.0')

我们添加了 MySQL 模块,并更新了连接到数据库服务器的代码,创建了一个数据库和表。我们还创建了几个路由来保存和获取小部件。现在我们需要重新构建我们的映像,以便它包含我们的更改。

首先,让我们使用 pipmysql-connector-python 模块添加到我们的应用程序中。

pip3 install mysql-connector-python
pip3 freeze | grep mysql-connector-python >> requirements.txt

现在我们可以构建我们的镜像。

docker build --tag python-docker-dev .

现在,让我们将容器添加到数据库网络,然后运行容器。这允许我们通过其容器名称访问数据库。

docker run \
  --rm -d \
  --network mysqlnet \
  --name rest-server \
  -p 5000:5000 \
  python-docker-dev

让我们测试一下我们的应用程序是否连接到数据库并能够添加注释。

curl http://localhost:5000/initdb
curl http://localhost:5000/widgets

您应该从我们的服务收到以下 JSON 返回。

[]

使用 Compose 进行本地开发

在本节中,我们将创建一个 Compose file,实现一个命令启动 python docker 和 MySQL 数据库。我们还将设置 Compose 文件,以在调试模式下启动 python-docker-dev 应用程序,以便将调试器连接到正在运行的进程。

在 IDE 或文本编辑器中打开 python-docker 目录,并创建一个名为 docker-compose.dev.yml 的新文件。将以下命令复制并粘贴到文件中。

version: '3.8'

services:
 web:
  build:
   context: .
  ports:
  - 5000:5000
  volumes:
  - ./:/app

 mysqldb:
  image: mysql
  ports:
  - 3306:3306
  environment:
  - MYSQL_ROOT_PASSWORD=p@ssw0rd1
  volumes:
  - mysql:/var/lib/mysql
  - mysql_config:/etc/mysql

volumes:
  mysql:
  mysql_config:

这个 Compose 文件非常方便,因为我们不需要输入传递给 docker run 命令的所有参数。我们可以使用 Compose 文件进行声明。

我们公开端口5000,这样我们就可以到达容器内的开发web服务器。我们还将本地源代码映射到正在运行的容器中,以便在文本编辑器中进行更改,并在容器中提取这些更改。

使用 Compose 文件的另一个非常酷的特性是,我们设置了服务解析来使用服务名称。因此,我们现在可以在连接字符串中使用“mysqldb”。我们之所以使用“mysqldb”,是因为我们在 Compose 文件中将 MySQL 服务命名为“mysqldb”。

现在,要启动应用程序并确认它正在正确运行,请运行以下命令:

docker-compose -f docker-compose.dev.yml up --build

我们传递 --build 参数,以便 Docker 编译我们的镜像,然后启动容器。

现在让我们测试我们的 API 端点。打开一个新终端,然后使用 curl 命令向服务器发出GET请求:

curl http://localhost:5000/initdb
curl http://localhost:5000/widgets

您将收到以下响应:

[]

这是因为我们的数据库是空的。

总结

在本模块中,我们研究了如何创建一个通用的开发镜像,我们可以像普通的命令行一样使用它。我们还设置了 Compose 文件,将源代码映射到正在运行的容器中,并公开了调试端口。在下一个模块中,我们将了解如何使用 GitHub Actions 设置 CI/CD 管道。

配置

预备知识

本页指导您完成使用 Docker 容器设置 GitHub Action CI/CD 管道的过程。在建立新管道之前,我们建议您先看看 Ben 关于 CI/CD 最佳实践的博客。

本指南包含了如何:

  • 以示例 Docker 项目为例来配置 GitHub Actions
  • 设置 GitHub Actions 工作流
  • 优化您的工作流以减少拉取请求的数量和总生成时间
  • 仅将特定版本推送到 Docker Hub

建立一个 Docker 项目

让我们开始吧。本指南以一个简单的 Docker 项目为例。SimpleWhaleDemo 库包含一个 Nginx alpine 镜像。您可以克隆这个库,或者使用自己的 Docker 项目。
在这里插入图片描述

开始之前,请确保您可以从创建的任何工作流访问 Docker Hub。为此:

  1. 将您的 Docker ID 作为秘钥添加到 GitHub。导航到 GitHub 存储库,然后单击 Settings > Secrets > New secret
  2. DOCKER_HUB_USERNAME 和 DOCKER_ID 作为值创建一个新秘钥。
  3. 创建一个 Personal Access Token(PAT)。要创建新令牌,请转到 Docker Hub设置,然后单击 New Access Token
  4. 让我们把这个 token 称为 simplewhaleci
    在这里插入图片描述
  5. 现在,将这个Personal Access Token(PAT)作为第二个秘钥添加到 GitHub secrets UI 中,名称为 DOCKER_HUB_Access_Token
    在这里插入图片描述

设置 GitHub Actions 工作流

在上一节中,我们创建了一个 PAT 并将其添加到 GitHub 中,以确保我们可以从任何工作流访问 Docker Hub。现在,让我们设置 GitHub Actions 工作流,以在 Hub 中构建和存储镜像。我们可以通过创建两个Docker actions 来实现这一点:

  1. 第一个 action 允许我们使用存储在 GitHub 存储库中的秘钥登录到 Docker Hub。
  2. 第二个是 build 和 push action。

这个例子中,让我们将 push 参数设置为 true,因为我们也想要 push。然后,我们将添加一个 tag 以指定始终转到最新版本。最后,我们将打印镜像摘要以查看推送的内容。

设置工作流:

  1. 转到 GitHub 中的存储库,然后单击 Actions > New workflow
  2. 点击 set up a workflow yourself 并添加以下内容

首先,我们将为这个工作流命名:

name: CI to Docker Hub

然后,我们将选择何时运行该工作流。在我们的例子中,我们将对项目的主分支的每一次推送都这样做:

on:
  push:
    branches: [ main ]

现在,我们需要指定在我们的 action 中实际想要发生什么(什么工作),我们将添加我们的构建一个,并选择它在最新的 Ubuntu 可用实例上运行:

jobs:

  build:
    runs-on: ubuntu-latest

现在,我们可以添加所需的步骤。第一个是在 $GITHUB_WORKSPACE 下检查我们的存储库,这样我们的工作流就可以访问它。第二个是使用 PAT 和 username 登录 Docker Hub。第三个是 Builder,该 action 通过一个简单的 Buildx action 在幕后使用 BuildKit,我们也将对其进行设置:

steps:

  - name: Check Out Repo 
    uses: actions/checkout@v2

  - name: Login to Docker Hub
    uses: docker/login-action@v1
    with:
      username: ${{ secrets.DOCKER_HUB_USERNAME }}
      password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

  - name: Set up Docker Buildx
    id: buildx
    uses: docker/setup-buildx-action@v1

  - name: Build and push
    id: docker_build
    uses: docker/build-push-action@v2
    with:
      context: ./
      file: ./Dockerfile
      push: true
      tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest

  - name: Image digest
    run: echo ${{ steps.docker_build.outputs.digest }}

现在,让工作流第一次运行,然后调整 Dockerfile 以确保 CI 正在运行并推送新的映像更改:
在这里插入图片描述

优化工作流

接下来,让我们看看如何通过构建缓存优化 GitHub Actions 工作流。这有两个主要优点:

  1. 构建缓存减少了构建时间,因为它不必重新下载所有映像
  2. 它还减少了我们对 Docker Hub 完成的 pull 次数。我们需要利用 GitHub 缓存来利用这一点

让我们设置一个带有构建缓存的构建器。首先,我们需要为构建器设置缓存。在这个例子中,让我们使用 GitHub 缓存添加路径和键来存储它。

- name: Cache Docker layers
  uses: actions/cache@v2
  with:
    path: /tmp/.buildx-cache
    key: ${{ runner.os }}-buildx-${{ github.sha }}
    restore-keys: |
      ${{ runner.os }}-buildx-

最后,在将构建器和构建缓存片段添加到 Actions 文件的顶部之后,我们需要为构建和推送步骤添加一些额外的属性。这涉及到:

  • 设置构建器以使用 buildx 步骤的输出
  • 使用我们之前为它设置的缓存来存储和检索
- name: Login to Docker Hub
  uses: docker/login-action@v1
  with:
    username: ${{ secrets.DOCKER_HUB_USERNAME }}
    password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and push
  id: docker_build
  uses: docker/build-push-action@v2
  with:
    context: ./
    file: ./Dockerfile
    builder: ${{ steps.buildx.outputs.name }}
    push: true
    tags:  ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
    cache-from: type=local,src=/tmp/.buildx-cache
    cache-to: type=local,dest=/tmp/.buildx-cache
- name: Image digest
  run: echo ${{ steps.docker_build.outputs.digest }}

现在,再次运行工作流并验证它使用了构建缓存。

将带有标签的版本推送到 Docker Hub

前面,我们学习了如何设置一个 GitHub Actions workflow 到一个 Docker 项目,如何通过设置一个构建器与构建缓存优化工作流。现在让我们看看如何进一步改进它。我们可以通过添加使标记版本的行为与所有提交到 master 的行为不同的功能来实现这一点。这意味着,只推送特定的版本,而不是每次提交都更新 Docker Hub 上的最新版本。

您可以考虑使用这种方法将提交提交到本地注册表,然后在夜间测试中使用。通过这样做,您可以始终测试最新的版本,同时保留已标记的版本,以便发布到 Docker Hub。

这包括两个步骤:

  1. 修改 GitHub 工作流,只将带有特定标签的提交推到 Docker Hub
  2. 在 GitHub 注册表中设置一个 GitHub Actions 文件来存储最新提交的映像

首先,让我们修改现有的 GitHub 工作流,使其仅在有特定标记时才推送到 Hub。例如:

on:
  push:
    tags:
      - "v*.*.*"

这确保了主 CI 只有在我们用 Vn.n.n 标记提交时才会触发。让我们测试一下。例如,运行以下命令:

git tag -a v1.0.2
git push origin v1.0.2

现在,去 GitHub 并检查你的 Actions
在这里插入图片描述
现在,让我们设置第二个 GitHub action 文件,将最新提交作为镜像存储在 GitHub 注册表中。您可能希望这样做:
3. 进行夜间测试或者重复测试
4. 与同事分享正在进行的工作镜像

让我们克隆上一个 GitHub action,并为所有推送添加上一个逻辑。这将意味着我们有两个工作流文件,我们以前的一个和我们现在要处理的新的一个。接下来,将 Docker Hub 登录更改为 GitHub 容器注册表登录:

if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
  registry: ghcr.io
  username: ${{ github.repository_owner }}
  password: ${{ secrets.GHCR_TOKEN }}

请记住更改镜像的标记方式。下面的示例将“latest”保留为唯一的标记。但是,如果愿意,您可以添加任何逻辑:

tags: ghcr.io/${{ github.repository_owner }}/simplewhale:latest

在这里插入图片描述
现在,我们将有两个不同的流:一个用于对 master 的更改,另一个用于 pull 请求。接下来,我们需要修改之前的内容,以确保将 PRs 推送到 GitHub 注册表,而不是 Docker Hub。

总结

在本模块中,您学习了如何为现有 Docker 项目设置 GitHub Actions 工作流,优化工作流以缩短构建时间并减少拉取请求的数量,最后,我们学习了如何仅将特定版本推送到 Docker Hub。您还可以针对最新的标签设置夜间测试,测试每个 PR,或者使用我们正在使用的标签做一些更优雅的事情,并对镜像中的同一标签使用 Git 标签。想了解更多关于部署应用程序的信息,请继续往下阅读。

部署(选读)

预备知识

现在,我们已经配置了 CI/CD pipleline,让我们看看如何部署应用程序。Docker 支持在 Azure ACI 和 AWS ECS 上部署容器。如果在 Docker Desktop 中启用了 Kubernetes,还可以将应用程序部署到 Kubernetes。

Docker 和 Azure ACI

Docker Azure 集成开发环境使开发人员能够在构建云本机应用程序时使用本机 Docker 命令在Azure容器实例(ACI)中运行应用程序。新体验提供了 Docker Desktop 和 Microsoft Azure 之间的紧密集成,允许开发人员使用 Docker CLI 或 VS Code 扩展快速运行应用程序,从本地开发无缝切换到云部署。

有关详细说明,请参见在 Azure 上部署 Docker 容器

Docker 和 AWS ECS

Docker ECS 集成开发环境使开发人员能够在构建云本机应用程序时,使用 Docker Compose CLI 中的本机 Docker 命令在 Amazon EC2 容器服务(ECS)中运行应用程序。

Docker 和 Amazon ECS 之间的集成允许开发人员使用 Docker Compose CLI 在一个 Docker 命令中设置 AWS 上下文,允许您从本地上下文切换到云上下文,并使用 Compose 文件快速轻松地运行应用程序,简化 Amazon ECS 上的多容器应用程序开发。

有关详细说明,请参见在 ECS 上部署 Docker 容器

Kubernetes

Docker Desktop 包括一个独立的 Kubernetes 服务器和客户端,以及在您的机器上运行的 Docker CLI 集成。当您启用 Kubernetes 时,您可以在 Kubernetes 上测试工作负载。

启用 Kubernetes:

  1. 从 Docker 菜单中,选择 Preferences (Windows 上的设置)。
  2. 选择 Kubernetes,单击 Enable Kubernetes。这将在 Docker Desktop 启动时启动 Kubernetes 单节点集群。

有关详细说明,请参见部署在 Kubernetes 使用 Kubernetes YAML 描述应用程序

原文

https://docs.docker.com/language/python/

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 你可以使用 PythonDocker 库来判断 Docker 容器是否创建成功。首先,你需要安装 Docker 库: ``` pip install docker ``` 然后,你可以使用以下代码来判断容器是否创建成功: ```python import docker # 创建 Docker 客户端 client = docker.DockerClient(base_url='unix://var/run/docker.sock') # 创建容器 container = client.containers.run("image_name", name="container_name") # 判断容器是否创建成功 if container.status == "created": print("容器创建成功") else: print("容器创建失败") ``` 还有一种方法是使用 `client.containers.list()` 方法来检查容器是否存在,然后判断容器是否正在运行。例如: ```python import docker # 创建 Docker 客户端 client = docker.DockerClient(base_url='unix://var/run/docker.sock') # 列出所有正在运行的容器 containers = client.containers.list(all=True) # 检查容器是否存在 if "container_name" in [c.name for c in containers]: print("容器创建成功") else: print("容器创建失败") ``` 希望这些信息能帮到你。 ### 回答2: 要使用Python中的docker库判断Docker容器是否成功创建,可以使用以下步骤: 1. 安装docker库:在Python环境中,使用以下命令安装docker库: ``` pip install docker ``` 2. 导入docker库:在Python脚本中导入docker库,以便使用其功能: ```python import docker ``` 3. 创建Docker客户端:使用docker创建一个Docker客户端对象,以便与Docker引擎通信: ```python client = docker.from_env() ``` 4. 创建容器使用Docker客户端的`create_container`方法创建一个容器对象,并指定容器的参数,如镜像名称、容器名称等: ```python container = client.create_container(image='镜像名称', name='容器名称') ``` 注意:`create_container`方法只是创建了一个容器对象,并没有实际创建容器。 5. 判断容器是否创建成功:使用Docker客户端的`containers`方法查询容器列表,检查容器是否存在于列表中: ```python containers = client.containers(all=True) container_exists = any(container['Names'] == '/容器名称' for container in containers) ``` 其中,`container['Names']`是一个包含容器名称的列表。 最后,在判断容器是否创建成功后,你可以根据需要执行后续操作,如启动容器、停止容器等。 请注意,以上步骤是使用Pythondocker库判断容器是否成功创建的一种方法,确保在使用之前安装好docker库并正确导入。 ### 回答3: 要判断Docker容器是否成功创建,可以使用PythonDocker库,它提供了操作Docker引擎的API。以下是一个示例代码: ```python import docker def check_container_creation(container_name): client = docker.from_env() containers = client.containers.list(all=True) for container in containers: if container.name == container_name: return True return False # 测试代码 container_name = "my_container" if check_container_creation(container_name): print(f"The container '{container_name}' is successfully created.") else: print(f"The container '{container_name}' is not found.") ``` 上面的代码首先导入了docker库,并定义了一个名为`check_container_creation`的函数,接受一个参数`container_name`表示要检查的容器名称。在函数内部,首先通过`docker.from_env()`创建了一个Docker客户端对象`client`,然后通过`client.containers.list(all=True)`获取所有容器的列表。 接下来使用循环遍历容器列表,判断每个容器的名称是否与给定的容器名称匹配。如果找到匹配的容器,则表示容器已成功创建,返回True。如果没有找到匹配的容器,则表示容器不存在,返回False。 在测试代码部分,我们通过调用`check_container_creation`函数传入容器名称来检测容器是否成功创建,并打印相应的提示信息。 通过这样的方法,我们可以使用Pythondocker库来判断容器是否创建成功。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值