如需转载请注明出处。
win10 64位、Python 3.6.3、Notepad++、Chrome 67.0.3396.99(正式版本)(64 位)
注:作者编写时间2018-04-11,linux、python 3.5.2
以下内容均是加入自己的理解与增删,以记录学习过程。不限于翻译,部分不完全照搬作者Miguel Grinberg的博客,版权属于作者,感谢他提供免费学习的资料。
传送门 | |||
---|---|---|---|
00 开篇 | 01 Hello world | 02 模板 | 03 Web表单 |
04 数据库 | 05 用户登录 | 06 个人资料和头像 | 07 错误处理 |
08 关注 | 09 分页 | 10 支持QQ邮箱 | 11 美化页面 |
12 时间和日期 | 13 I18n和L10n 翻译成中文 zh-CN | 14 Ajax(百度翻译API | 15 更好的App结构(蓝图) |
16 全文搜索 | 17 部署到腾讯云Ubuntu | 18 部署到Heroku | 19 部署到Docker容器 |
20 JavaScript魔法 | 21 用户通知 | 22 后台工作(Redis) | 23 应用程序编程接口(API) |
在本章中,将把Microblog部署到Docker容器平台。在第17章中,学习到传统部署,须在其中处理服务器配置的每个方面。在第18章中,学习到Heroku,带到另一个极端,Heroku是一项完全控制配置和部署任务的服务,使得我们可完全专注于应用程序。
本章将学习 基于容器的第三个应用程序部署策略,尤其是Docker容器平台。就部署工作量而言,这个部署工作量位于其他两个选项之间。
容器 构建在轻量级虚拟化技术之上,允许应用程序及其依赖关系和配置完全隔离,但无需使用虚拟机等完整的虚拟化解决方案,因为虚拟机需要更多资源,有时需要与主机相比,性能显著下降。配置为容器主机的系统可以执行许多容器,所有容器共享主机的内核并直接访问主机的硬件。这与虚拟机形成对比,虚拟机必须模拟完整的系统,包括CPU、磁盘、其他硬件、内核等。
尽管必须共享内核,但容器中的隔离级别相当高。容器具有自己的文件系统,并且可以基于与容器主机使用的操作系统不同的操作系统。例如,可在Fedora主机上运行基于Ubuntu Linux的容器,反之亦然。虽然容器是Linux操作系统的原生技术,但由于虚拟化,还可在Windows和Mac OS X主机上运行Linux容器。这允许在开发系统上测试部署,并且如果愿意,还可以在开发工作流程中包含容器。
要使用Docker CE,首先必须在系统上安装它。Docker网站上有Windows,Mac OS X和几个Linux发行版的安装程序。如果正在使用Microsoft Windows系统,请务必注意Docker CE需要Hyper-V。如有必要,安装程序将为我们启用此功能,但请记住,启用Hyper-V可防止其他虚拟化技术(如VirtualBox)正常工作。
在系统上安装Docker CE后,可以通过在终端窗口或命令提示符下键入以下命令来验证安装是否成功:
C:\Users\Administrator>docker version
Client:
Version: 18.06.1-ce
API version: 1.38
Go version: go1.10.3
Git commit: e68fc7a
Built: Tue Aug 21 17:21:34 2018
OS/Arch: windows/amd64
Experimental: false
Server:
Engine:
Version: 18.06.1-ce
API version: 1.38 (minimum version 1.12)
Go version: go1.10.3
Git commit: e68fc7a
Built: Tue Aug 21 17:29:02 2018
OS/Arch: linux/amd64
Experimental: false
为应用程序创建容器映像的最基本方法是为要使用的基本操作系统启动容器(Ubuntu、Fedora等),连接到在其中运行的bash shell进程,然后手动安装应用程序,可遵循第17章介绍的传统部署指南。安装完所有内容后,可拍摄容器的快照,然后成为图像。docker
命令 支持这种类型的工作流,但在此不打算讨论它,因为每次需要生成新映像时都必须手动安装应用程序。
更好的方法是 通过脚本生成容器图像。创建脚本化容器映像的命令是 docker build
。这个命令从名为 Dockerfile的文件中读取并执行构建指令,我将需要创建该文件。Dockerfile基本上是一种安装程序脚本,它执行安装步骤以部署应用程序,以及一些特定于容器的设置。
在git上新建Dockerfile文件:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ touch Dockerfile
下方是Microblog的基本Dockerfile文件:
microblog/Dockerfile:Microblog的Dockerfile
FROM python:3.6-alpine
RUN adduser -D microblog
WORKDIR /home/microblog
COPY requirements.txt requirements.txt
RUN python -m venv venv
RUN venv/bin/pip install -r requirements.txt
RUN venv/bin/pip install gunicorn
COPY app app
COPY migrations migrations
COPY microblog.py config.py boot.sh ./
RUN chmod +x boot.sh
ENV FLASK_APP microblog.py
RUN chown -R microblog:microblog ./
USER microblog
EXPOSE 5000
ENTRYPOINT ["./boot.sh"]
Dockerfile中的每一行都是一个命令。FROM
命令指定将在其上构建新映像的基本容器映像。我们的想法是:从现有图像开始,添加或更改某些内容,最终得到派生图像。图像由名称、标记引用,用冒号分隔。标记用作版本控制机制,允许容器图像提供多个变体。我选择的图形的名称是python
,这是Python的官方Docker镜像。此映像的标记允许指定解释器版本和基本操作系统。3.6-alpine标记选择安装在Apline Linux上的Python3.6解释器。由于其体积小,通常使用Apline Linux发行版而不是像Ubuntu这样更流行的发行版。可在Python映像存储库中查看Python映像可用的标记。
RUN
命令 在容器上下文中执行任意命令。这与在shell提示符下键入命令类似。adduser -D microblog
命令创建一个名为microblog
的新用户。大多数容器映像都是默认用户root
,但以root
身份运行应用程序并不是一个好习惯,因此创建自己的用户。
WORKDIR
命令设置将安装应用程序的默认目录。当我在上面创建用户microblog
时,创建了一个主目录,所以现在将该目录设为默认目录。新的默认目录将应用于Dockerfile中的任何剩余命令,以及稍后执行容器时。
COPY
命令将文件从你的机器传输到容器文件系统。此命令采用两个或多个参数,即源文件或目标文件或目录。源文件必须相对于Dockerfile所在的目录。目标可以是绝对路径,也可以是相对于上一个WORKDIR
命令中设置的目录的路径。在第一个COPY
命令中,我将requirements.txt
文件复制到容器文件系统中的用户microblog
主目录。
现在容器中有了requirements.txt
文件,可以使用RUN
命令创建一个虚拟环境。首先创建它;然后在其中安装所有要求。因为需求文件只包含通用依赖项,所有然后显示安装gunicorn,将把它用作Web服务器。或者,可在requirements.txt文件中添加gunicorn。
以下三个COPY
命令通过复制应用程序包,迁移目录和数据库迁移以及顶级目录中的microblog.py
和config.py
脚本,在容器中安装应用程序。还复制了一个新文件boot.sh
,下方将讨论。
RUN chmod
命令确保将此新的boot.sh
文件正确设置为 可执行文件。如果在基于Unix的文件系统中并且源文件已标记为可执行文件,则复制的文件也将设置可执行位。我添加一个显示集,因为在Windows上设置为可执行位更难。如果正在使用Mac OS X或Linux,可能不需要这个声明,但无论如何都没有它。
ENV
命令在容器内设置环境变量。需要设置FLASK_APP
,这是使用flask
命令所必需的。
以下RUN chown
命令将存储在/home/microblog
中的所有目录和文件的所有者设置为新用户microblog
。即使在Dockerfile顶部附近创建了这个用户,所有命令的默认用户仍然存在root
,因此所有这些文件都需要切换用户microblog
,以便这个用户可以在启动容器时使用它们。
下一行中的USER
命令使此新用户microblog
成为任何后续指令的默认值,也适用于容器启动时的默认值。
EXPOSE
命令配置此容器将用于其服务器的端口。这是必要的,以便Docker可以适当地配置容器中的网络。选择了标准的Flask端口5000,但这可以是任何端口。
最后,ENTRYPOINT
命令定义了在启动容器时应执行的默认命令。这是启动应用程序Web服务器的命令。为了保持井井有条,决定为此创建一个单独的脚本,这是我之前赋值到容器的boo.sh
文件。下方是这个脚本的内容:
microblog/boot.sh:Docker容器启动脚本
#!/bin/sh
source venv/bin/activate
flask db upgrade
flask translate compile
exec gunicorn -b :5000 --access-logfile - --error-logfile - microblog:app
这是一个相当标准的启动脚本,非常类似于 如何在第17章和第18章中部署的开始。我激活虚拟环境,通过迁移框架升级数据库,编译语言翻译,最后用gunicorn运行服务器。
注意在guncorn命令 exec
之前的那个。在shell脚本中,触发运行脚本的进程将替换为给定的命令exec
,而不是将其作为新进程启动。这很重要,因为Docker将容器的生命周期与在其上运行的第一个进程相关联。在这种情况下,启动过程不是容器的主要过程,需要确保主进程取代第一个进程,以确保Docker不会提前终止容器。
Docker的一个有趣的方面是容器写入stdout
或stderr
将被捕获并存储为容器日志的任何内容。出于这个原因,--access-logfile
和--error-logfile
都配置了 -,它将日志发送到标准输出,以便它们由Docker存储为日志。
创建Dockerfile后,现在可构建一个容器图像:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker build -t microblog:latest .
Sending build context to Docker daemon 76.98MB
Step 1/16 : FROM python:3.6-alpine
---> da04299043e3
Step 2/16 : RUN adduser -D microblog
---> Using cache
---> ecfd36557b81
Step 3/16 : WORKDIR /home/microblog
---> Using cache
---> 47562fe391c1
Step 4/16 : COPY requirements.txt requirements.txt
.....
.....
Successfully tagged microblog:latest
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
当然,先把requirements.txt
中最后两行注释掉,如下:
# requirements for Heroku
#psycopg2==2.7.3.1
#gunicorn==19.7.1
我给docker build
命令的参数-t
设置了新容器图像的名称和标记。在 .
表示基本目录所在的容器是要建。这是Dockerfile所在的目录。构建过程将评估Dockerfile中的所有命令并创建映像,这个映像将存储在我们自己的计算机上。
可使用下方命令docker images
获取本地图像的列表:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
microblog latest 72e69f44b498 45 seconds ago 214MB
16ff4a0ce03d 13 minutes ago 87MB
python 3.6-alpine da04299043e3 7 days ago 74.2MB
这个列表将包含我们的新图像以及构建它的基本图像。每次对应用程序进行更改时,都可以通过再次运行命令build
来更新容器映像。
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker run --name microblog -d -p 8000:5000 --rm microblog:latest
f73c63a328002c4ea5d9d93a867a30b3d3e0199681d1b0facdaa5f31c5996d5c
--name
选项提供新容器的名称。-d
选项告知Docker在后台运行容器。如果-d
容器不作为前台应用程序运行,则阻止命令提示符。-p
选项将容器端口映射到主机端口。第一个端口是主机上的端口,右边的端口是容器内的端口。上方示例在主机的端口8000上的容器中公开了端口5000,因此将在8000上访问这个应用程序,即使容器内部使用5000也是如此。--rm
选项将在终止后删除容器。虽然这不是必需的,但通常不再需要完成或中断的容器,因此可自动删除它们。最后一个参数是容器映像名称和用于容器的标记。运行上述命令后,可访问http://localhost:8000上的应用程序。
输出docker run
是分配给新容器的ID。这是一个长十六进制字符串,只要需要在后续命令引用容器,就可以使用它。实际上,只有前几个字符是必需的,足以使ID唯一。
如果要查看当前正在运行的容器,可使用下方docker ps
命令:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
甚至可以看到docker ps
命令缩短了容器ID。
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
95abf0daa282 16ff4a0ce03d "/bin/sh -c 'venv/bi…" 17 minutes ago Exited ( 1) 16 minutes ago focused_kepler
查看所有容器,包括停止的:docker ps -a
如果现在想要停止容器,可以使用docker stop <自己填写CONTAINER ID>
:
$ docker stop 021da2e1e0d3
021da2e1e0d3
如果还记得,应用程序配置中有许多选项来源于环境变量。例如,Flask密钥,数据库URL和电子邮件服务器选项都是从环境变量导入的。在上方docker run
示例中,并不担心这些,因此所有这些配置选项都将使用默认值。
在更现实的示例中,将在容器内设置这些环境变量。在上一节中看到Dockerfile中的ENV
命令设置了环境变量,对于将变为静态的变量,它是一个方便的选项。但是,对于依赖于安装的变量,将它们作为构建过程的一部分是不方便的,因为希望拥有一个相当可移植的容器映像。如果想将应用程序作为容器映像提供给另一个人,可能希望这人能够按原样使用它,而不必使用不同的变量重建它。
因此构建时环境变量可能很有用,但是还需要具有可以通过docker run
命令设置的运行时环境变量,对于这些变量,-e
可以使用该选项。以下示例设置密钥并通过QQ帐户发送电子邮件等等:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker run --name microblog -d -p 8000:5000 --rm -e SECRET_KEY=my-secret-key -e MAIL_SERVER=smtp.qq.com -e MAIL_PORT=465 -e MAIL_USE_SSL=true -e MAIL_USERNAME= -e MAIL_PASSWORD= -e APPID=<填写自己的> -e BD_TRANSLATOR_KEY=<填写自己的> -e ELASTICSEARCH_URL=http://localhost:9200 microblog:latest
88880d24e508f9fe6bf6913a2d1788bb568dded6a34340285c64278f7953e6b8
docker run
由于具有许多环境变量定义,命令行非常长并不罕见。
容器中的文件系统是短暂的,这意味着当容器消失时,它也会消失。可以将数据写入文件系统,如果容器需要读取数据,数据将会存在,但如果处于任何原因需要回收容器并将其替换为新容器,则应用程序保存的任何数据磁盘将永远丢失。
容器应用程序的一个好的设计策略 是使应用程序容器无状态。如果我的容器具有应用程序代码且没有数据,可将其丢弃,将其替换为新的容器而没有任何问题,容器变为真正的一次性容器,这在简化升级部署方面非常有用。
但是,当然,这意味着数据必须放在应用程序容器之外的某个位置。这就是梦幻般的Docker生态系统发挥作用的地方。Docker Container Registry包含各种容器映像。我已告诉过关于Python容器图像的信息,我将其用作Microblog容器的基本图像。除此之外,Docker还为Docker注册表中的许多其他语言,数据库和其他服务维护图像,如果还不够,注册表还允许公司为其产品发布容器图像,还有像我这样的普通用户发布自己的图像。这意味着安装第三方服务的努力减少到在注册表中查找适当的图像,并使用具有适当参数的命令docker run
启动它。
所以现在要做的是创建两个额外的容器,一个用于MySQL数据库,另一个用于Elasticsearch服务;然后将创建启动Microblog容器的命令行甚至更长的选项使其能够访问这两个新容器。
与许多其他产品和服务一样,MySQL在Docker注册表中提供了公共容器映像。就像我自己的Microblog容器一样,MySQL依赖于需要传递给docker run
的环境变量。这些配置密码,数据库名称等。虽然注册表中有许多MySQL映像,但我决定使用由MySQL团队正式维护的映像。您可以在其注册表页面中找到有关MySQL容器映像的详细信息:https://hub.docker.com/r/mysql/mysql-server/。
如果还记得第17章中设置MySQL的繁琐过程,那么当看到部署MySQL是多么容易时,将会欣赏Docker。这是docker run
启动MySQL服务器的命令:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker run --name mysql -d -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=microblog -e MYSQL_USER=microblog -e MYSQL_PASSWORD=Czy0905321 mysql/mysql-server:5.7
在安装了Docker的任何计算机上,都可以运行上面的命令,将获得一个完全安装的MySQL服务器,其中包含一个随机生成的root
密码,一个名为的全新数据库microblog
,以及一个配置为full
的同名用户访问数据库的权限。请注意,需要输入正确的密码作为MYSQL_PASSWORD
环境变量的值。
现在在应用程序方面,我需要添加一个MySQL客户端软件包,就像我在Ubuntu上的传统部署一样。我将再次使用pymysql
,我可以添加到Dockerfile:
microblog/Dockerfile:将pymysql添加到Dockerfile
# ...
RUN venv/bin/pip install gunicorn pymysql
# ...
每次对应用程序或Dockerfile进行更改时,都需要重建容器映像:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker build -t microblog:latest .
现在我可以再次启动Microblog,但这次有一个数据库容器的链接,以便两者都可以通过网络进行通信:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker run --name microblog -d -p 8000:5000 --rm -e SECRET_KEY=my-secret-key -e MAIL_SERVER=smtp.qq.com -e MAIL_PORT=465 -e MAIL_USE_SSL=true -e MAIL_USERNAME= -e MAIL_PASSWORD= -e APPID=<填写自己的> -e BD_TRANSLATOR_KEY=<填写自己的> --link mysql:dbserver -e DATABASE_URL=mysql+pymysql://microblog:Czy0905321@dbserver/microblog microblog:latest
24d99fecf54efd50e80681e5def29560729fd26102153eb82218bd432e25132b
--link
选项告诉Docker使另一个容器可以访问。该参数包含两个以冒号分隔的名称。第一部分是要链接的容器的名称或ID,在本例中mysql
是我在上面创建的名称。第二部分定义了一个主机名,可以在此容器中使用该主机名来引用链接的主机名。这里我用dbserver
作为代表数据库服务器的通用名称。
通过建立两个容器之间的链接,我可以设置DATABASE_URL
环境变量,以便指向SQLAlchemy在另一个容器中使用MySQL数据库。数据库URL将用dbserver
作为数据库主机名,数据库名称microblog
和用户以及启动MySQL时选择的密码。
我在试验MySQL容器时注意到的一件事是,这个容器需要几秒钟才能完全运行并准备好接受数据库连接。如果启动MySQL容器然后立即启动应用程序容器,当boot.sh
脚本尝试运行时flask db migrate
,由于数据库未准备好接受连接,它可能会失败。为了使我的解决方案更健壮,我决定在boot.sh
中添加一个重试循环:
microblog/boot.sh:重试数据库连接。
#!/bin/sh
source venv/bin/activate
while true; do
flask db upgrade
if [[ "$?" == "0" ]]; then
break
fi
echo Upgrade command failed, retrying in 5 secs...
sleep 5
done
flask translate compile
exec gunicorn -b :5000 --access-logfile - --error-logfile - microblog:app
此循环检查flask db upgrade
命令的退出代码,如果它不为零,则表示出现错误,因此等待五秒钟然后重试。
Docker的Elasticsearch文档显示了如何将服务作为单节点进行开发,以及作为双节点生产就绪部署。现在将使用单节点选项并使用“oss”图像,该图像只有开源引擎。使用以下命令启动容器:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker run --name elasticsearch -d -p 9200:9200 -p 9300:9300 --rm -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch-oss:6.1.1
这个docker run
命令与用于Microblog和MySQL的命令有很多相似之处,但是有一些有趣的区别。首先,有两个-p
选项,这意味着此容器将侦听两个端口而不是一个端口。端口9200
和9300
都映射到主机中的相同端口。
另一个区别在于用于引用容器图像的语法。对于我在本地构建的图像,语法是
。MySQL容器使用稍微更完整的语法格式
,适用于引用Docker注册表上的容器映像。我正在使用的Elasticsearch图像遵循模式
,其中包括注册表的地址作为第一个组件。此语法用于未托管在Docker注册表中的图像。在这种情况下,Elasticsearch在docker.elastic.co上运行自己的容器注册表服务,而不是使用Docker维护的主注册表。
现在我已启动并运行Elasticsearch服务,我可以修改我的Microblog容器的启动命令,以创建指向它的链接并设置Elasticsearch服务URL:
Administrator@Cchen-PC MINGW64 /d/microblog (master)
$ docker run --name microblog -d -p 8000:5000 --rm -e SECRET_KEY=my-secret-key -e MAIL_SERVER=smtp.qq.com -e MAIL_PORT=465 -e MAIL_USE_SSL=true -e MAIL_USERNAME= -e MAIL_PASSWORD= -e APPID=<填写自己的> -e BD_TRANSLATOR_KEY=<填写自己的> --link mysql:dbserver -e DATABASE_URL=mysql+pymysql://microblog:Czy0905321@dbserver/microblog --link elasticsearch:elasticsearch -e ELASTICSEARCH_URL=http://elasticsearch:9200 microblog:latest
在运行此命令之前,请记住如果仍然运行它,请停止以前的Microblog容器。在命令的适当位置设置数据库和Elasticsearch服务的正确密码时也要小心。
现在您应该可以访问http://localhost:8000并使用搜索功能。如果遇到任何错误,可以通过查看容器日志对其进行故障排除。您很可能希望查看Microblog容器的日志,其中将显示任何Python堆栈跟踪:
$ docker logs microblog
要访问Docker注册表,您需要访问https://hub.docker.com并为自己创建一个帐户。确保选择喜欢的用户名,因为这将用于发布的所有图像。
要从命令行访问您的帐户,您需要使用以下docker login命令登录:
$ docker login
如果一直按照上述说明操作,那么现在可以在计算机上本地存储一个名为microblog:latest
“ 存储” 的图像。为了能够将此图像推送到Docker注册表,需要将其重命名为包含该帐户,例如来自MySQL的图像。这是通过以下docker tag命令完成的:
$ docker tag microblog:latest /microblog:latest
如果再次列出图像,docker images
则不会看到Microblog的两个条目,原始的一个带有microblog:latest
名称,另一个带有我的帐户名称。这些实际上是同一图像的两个别名。
要将映像发布到Docker注册表,请使用以下docker push
命令:
$ docker push /microblog:latest
现在图像是公开的,可以记录如何安装它,并以与MySQL和其他人相同的方式从Docker注册表运行。
第17章中推荐的来自Digital Ocean,Linode或Amazon Lightsail 的相同服务器。即使是这些提供商提供的最便宜的产品也足以使用少量容器运行Docker。中国的话选择阿里、腾讯、华为咯。
在亚马逊集装箱服务(ECS),使我们能够创建容器主机群集在其上运行我们的容器,在一个完全集成的AWS环境下使用私营集装箱注册表的能力,以支持缩放和负载均衡,加上选项你的容器图像。
最后,像Kubernetes这样的容器编排平台提供了更高级别的自动化和便利性,允许您以YAML格式在简单文本文件中描述多容器部署,具有负载平衡,扩展,安全的秘密管理和滚动升级和回滚。
参考:
作者博客
源代码
如需转载请注明出处。