前提条件
阅读本系列教程第一部分的技术方向
运行以下命令,快速测试你的Docker环境,确保所有配置的正确性:
docker run hello-world
简介
现在,我们会以Docker的方式来构建应用程序。首先,我们会从这种容器化的应用程序的最底层开始学习,本文将会涵盖容器(Container)的相关知识。其次,容器的更上一层是服务(Service),它能够定义容器在生产环境中的具体行为,本系列教程的第三部分将会涵盖服务的相关知识。最后,最顶层是堆栈(Stack),它能够定义所有服务之间的交互作用,本系列教程的第五部分将会涵盖堆栈的相关知识。
堆栈
服务
容器(本文涵盖内容)
你的新开发环境
在过去,如果你想要开始编写一个Python应用程序,那么你首先应当为你的计算机安装一个Python运行时环境。但是,这样便会存在一种情况,只有你的计算机环境才能使得你的应用程序能够按照预期运行,因此运行这个应用程序的服务器也必须按照你的计算机环境进行配置。
通过Docker,你可以只抓取一个可移植的Python运行时镜像,而不需要为主机安装Python运行时环境。然后,你构建的应用程序不仅包含你的程序代码,还会包含基础的Python镜像,这样便能确保你的应用程序、依赖关系和运行时环境能够整体部署和迁移。
这些可移植的镜像是由一些被称为Dockerfile的配置文件所定义的。
通过Dockerfile定义容器
Dockerfile文件将会定义你的容器内部环境的方方面面。在容器环境中,对诸如网络接口和磁盘驱动器之类资源的访问都是虚拟化的,这样便能将容器和宿主机系统完全隔离。因此,你必须将容器的端口映射至外部网络,并且需要指定将哪些文件拷贝至容器环境之中。然而,在这样做之后,你就可以确保在Dockerfile文件中定义和构建的应用程序能够在任何环境中保持相同的行为。
创建一个空目录,然后将以下文件放置在该目录之中,文件名为Dockerfile。请仔细阅读解释每条语句的注释。
Dockerfile文件:
# 使用官方的Python运行时镜像作为父镜像 FROM python:2.7-slim # 将工作目录设置为/app WORKDIR /app # 将当前目录的内容拷贝至容器的/app目录 ADD . /app # 安装requirements.txt文件中指定的软件包 RUN pip install -r requirements.txt # 向外界开放容器的80端口 EXPOSE 80 # 定义环境变量 ENV NAME World # 当容器启动时,运行app.py CMD ["python", "app.py"]
这个Dockerfile会引用两个文件,名为app.py和requirements.txt,现在尚未创建这两个文件。接下来,我们便会创建这两个文件。
应用程序本身
创建以下两个文件,然后将它们放置在存放Dockerfile的文件夹之中。这样便能完成我们的应用程序,你会发现这个应用程序非常简单。当根据上述的Dockerfile文件构建镜像时,Dockerfile的ADD命令会将app.py和requirements.txt文件添加至镜像,并且EXPOSE命令会使得通过HTTP访问app.py文件时,容器还会输出相应的信息。
requirements.txt文件:
Flask Redis
app.py文件:
from flask import Flask from redis import Redis, RedisError import os import socket # Connect to Redisredis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2) app = Flask(__name__) @app.route("/") def hello(): try: visits = redis.incr("counter") except RedisError: visits = "<i>cannot connect to Redis, counter disabled</i>" html = "<h3>Hello {name}!</h3>" \ "<b>Hostname:</b> {hostname}<br/>" \ "<b>Visits:</b> {visits}" return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits) if __name__ == "__main__": app.run(host='0.0.0.0', port=80)
由上述文件的内容可知,“pip install -r requirements.txt”命令会安装Python的Flask和Redis程序库,应用程序会输出环境变量NAME的值,除此之外还会输出调用“socket.gethostname()”的结果。最后,因为Redis并没有运行(我们只安装了Python程序库,并没有安装Redis缓存),我们应当可以预见,当应用程序试图使用缓存时,便会产生出错消息。
注意:当在一个容器内部访问主机名时,便会获取容器的标识符,类似于运行一个可执行程序时获得的进程标识符。
构建应用程序
就是这样!你的宿主机系统不需要安装Python或requirements.txt中的任何组件,构建或运行这个Docker镜像时也不会将上述组件安装至你的宿主机系统。虽然看起来你并没有建立Python和Flask的运行环境,但实际上你的宿主机系统已经具备运行条件了。
ls命令的输出,应当如下图所示:
现在,我们可以运行构建命令。这个命令会创建一个Docker镜像,我们会使用“-t”选项创建镜像的标签,这样便能为镜像指定一个较为友好的名称:
docker build -t friendlyhello .
若镜像构建成功,则命令输出如下图所示:
你构建的镜像存放在哪儿呢?它会存放在宿主机的本地Docker镜像仓库之中,如下图所示:
运行应用程序
运行应用程序,通过“-p”选项将宿主机的4000端口映射至容器的80端口,这个端口是由Dockerfile中的EXPOSE命令指定对外公开的:
docker run -p 4000:80 friendlyhello
你应该能够看到一条通知信息,提示Python正在通过http://0.0.0.0:80为你的应用程序提供服务。但是,这条消息是从容器内部发出的,由于你已经将容器的80端口映射至宿主机的4000端口,因此正确的访问URL应该是http://localhost:4000。
通过浏览器访问上述的URL,网页中显示的内容包括“Hello World”文本、容器标识符和Redis出错消息,如下图所示:
你还可以在Shell中运行curl命令,可以看到相同的内容:
注意:此处的4000:80端口映射可以说明通过Dockerfile文件中的EXPOSE命令公开端口和通过“docker run -p”命令发布端口映射之间的不同之处。在稍后的步骤中,我们会将宿主机的80端口映射至容器的80端口,通过http://localhost访问应用程序。
在终端中按下“CTRL+C”组合键,退出正在运行的容器。
现在,我们会在后台以分离模式运行应用程序的容器:
docker run -d -p 4000:80 friendlyhello
你会看到一串很长的容器标识符,然后便会返回至终端的命令提示符。此时,你的容器正在后台运行。你还可以通过“docker ps”命令查看缩短的容器标识符(当运行命令时,既可以使用缩短的容器标识符,也可以使用完整的容器标识符),如下图所示:
你会发现CONTAINER ID列的值和http://localhost:4000页面显示的值是相同的。
现在,使用“docker stop”命令就可以结束容器的进程,需要指定CONTAINER ID列的值,如下所示:
docker stop 829a0f568391
共享你的镜像
为了演示我们刚刚创建的容器镜像的可移植性,我们会上传构建得到的容器镜像,然后在其他地方运行相应的容器。最后,当你想要将容器部署至生产环境时,你还需要学习如何将容器镜像推送至镜像仓库。
镜像仓库是多个镜像存储库的集合,而镜像存储库又是多个镜像的集合 —— 类似于GitHub的存储库,但是镜像存储库的代码是已经构建完成的。镜像仓库的注册账号可以创建很多镜像存储库。docker命令行工具默认会使用Docker的公开镜像仓库。
注意:此处,我们将使用Docker的公开镜像仓库,因为它是免费和预配置的,但是你还可以选择很多其他的公开镜像仓库,你甚至还可以通过Docker可信镜像仓库搭建你自己的私有镜像仓库。
登录Docker账号
如果你没有Docker账号,那么可以在cloud.docker.com网站注册一个账号。请务必记住你的用户名!
在你的本地主机中登录至Docker的公开镜像仓库:
docker login
创建镜像标签
Docker镜像的名称是由仓库名称、存储库名称和标签构成的,格式为“username/repository:tag”。虽然标签(tag)是可选的,但是我们推荐使用标签,因为镜像仓库会通过标签来管理Docker镜像的版本。存储库名称和标签最好具有实际含义,例如:“get-started:part1”。镜像仓库会将上述镜像放置在“get-started”存储库之中,并且为其创建名为“part1”的标签。
现在,可以将所有组成部分拼接起来,然后为镜像创建标签。运行“docker tag image”命令,指定你的用户名、存储库和标签,这样便能将镜像上传至预想的位置。命令的语法如下所示:
docker tag image username/repository:tag
例如:
docker tag friendlyhello ghoulich/get-started:part1
运行“docker images”命令,查看最新创建的镜像标签(你也可以运行“docker image ls”命令),如下图所示:
发布镜像
将已经创建标签的镜像上传至镜像存储库,命令的语法如下所示:
docker push username/repository:tag
例如:
docker push ghoulich/get-started:part1
一旦镜像上传完成,那么这个镜像对外就是公开可用的。如果你登录Docker Hub网站,你将能看到先前上传的容器镜像,以及用来拉取这个镜像的命令。如下图所示:
从远程存储库拉取和运行镜像
从现在开始,你可以在任何一台主机中运行“docker run”命令,这样便能运行你的应用程序容器,命令的语法如下所示:
docker run -p 4000:80 username/repository:tag
例如:
docker run -p 4000:80 ghoulich/get-started:part1
如果宿主机本地没有找到这个容器镜像,那么Docker将会从镜像存储库拉取这个容器镜像,如下图所示:
注意:如果你在构建镜像或者运行容器时,没有在这些命令中指定镜像名称的“:tag”部分,那么默认会使用“:latest”标签。如果运行容器时没有指定标签,那么Docker将会使用最新版本的容器镜像。
无论在何处执行“docker run”命令,Docker都会拉取你的镜像(这个镜像包含Python运行环境,以及requirements.txt文件指定的所有依赖关系),然后运行容器中的app.py代码。应用程序的运行环境和依赖关系都被封装至容器镜像之中,宿主机只需要安装用于运行容器的Docker软件包,不需要安装其他任何东西。
结论
本节内容到此结束。在下一节之中,我们将学习如何以服务的方式运行容器,这样便能很方便地调整应用程序的规模。
回顾和备忘
以下列表包含本节涉及的基本Docker命令,以及进入下一节学习之前需要了解的一些命令:
docker build -t friendlyname . # 通过当前目录的Dockerfile创建镜像 docker run -p 4000:80 friendlyname # 运行“friendlyname”镜像,将4000端口映射至80端口 docker run -d -p 4000:80 friendlyname # 运行“friendlyname”镜像,以分离模式工作 docker ps # 查看所有正在运行的容器列表 docker stop <hash> # 停止运行指定的容器 docker ps -a # 查看所有容器列表,包括尚未运行的容器 docker kill <hash> # 强制停止运行指定的容器 docker rm <hash> # 从宿主机删除指定的容器 docker rm $(docker ps -a -q) # 从宿主机删除所有的容器 docker images -a # 显示宿主机所有的镜像 docker rmi <imagename> # 从宿主机删除指定的镜像 docker rmi $(docker images -q) # 从宿主机删除所有的镜像 docker login # 通过你的Docker账号登录CLI会话 docker tag <image> username/repository:tag # 为<image>指定的镜像创建标签 docker push username/repository:tag # 将已创建标签的镜像上传至镜像仓库 docker run username/repository:tag # 从镜像仓库拉取和运行镜像