要求
- 已安装Dockers1.13或更高版本
- 完成Part 1的阅读
- 在机器环境上快速启动运行一个容器保证所有都配置正确
docker run hello-world
介绍
是时候开始使用Docker方式来构建一个应用了。在本节我们将基于一个应用垂直结构的底层开始构建容器。在这之上是服务,我们将在Part3第三节介绍基于容器的服务是如何在生产环境运行的。最后,在最顶层是栈,定义了所有服务的交互行为,我们将在Part5第五节进行介绍说明。
- Stack栈
- Services服务
- Container容器(本节的介绍内容)
新的开发环境
在以往,如果我们要开始着手写一个Python应用,要做的第一件事就是在机器上安装Python的运行环境。但是,这样会造成一个问题,就是我们搭建的环境要完美的适配应用程序的需求,同时也要匹配将来生产环境的要求。
使用Docker,我们仅仅需要做的就是构建一个便捷的Python运行环境镜像,不再需要而外安装其他东西。基于一个通用的镜像我们将代码和运行环境一起构建为一个专用镜像,保证所有的依赖、运行环境、代码全都在一起。
通过使用Dockerfile我们能够定义这些非常便捷的镜像。
使用Dockerfile定义容器
Dockerfile定义了容器是如何运转的。类似于网络接口和数据硬盘驱动等这些资源的使用在环境中都是虚拟化的,并且与系统其他程序是相互隔离的,因此我们需要将端口映射到外部,确定文件是如何来实现共通。我们期望通过Dockerfile定义配置的应用容器能够准确无误在任何地方运行。
Dockerfile
- 创建一个空目录
- 切换工作路径到这个新目录
- 在目录下新建Dockerfile,将下文中的内容复制到Dockerfile中,然后保存文件(请详细查看每个语句的注释部分,解释了每个语句具体的含义)
# 使用官方的Python运行环境作为基础镜像
FROM python:2.7-slim
# 设置工作路径为/app
WORKDIR /app
# 复制当前目录下的内容到容器的/app下
ADD . /app
# 安装在文件requirements.txt指定的包
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# 将80端口进行对外开放,这样就能从容器外部通过映射访问容器内部的80端口
EXPOSE 80
# 定义环境变量
ENV NAME World
# 容器启动后运行 app.py程序
CMD ["python", "app.py"]
Dockerfile中提到了有几个我们还没有创建的文件,例如app.py和requirements.txt。我们接下来进行逐一的创建
应用程序
我们再创建两个文件,分别为requirements.txt和app.py,并将他们保存到跟Dockerfile一致的目录。这样我们就非常简单的完成了应用。当上文提到的Dockerfile构建为一个镜像后,app.py和requirements.txt也会在镜像中,因为我们使用了Dockerfile的ADD命令,并且因为使用的EXPOSE命令我们能够使用http协议访问到内部的服务
requirements.txt
Flask
Redis
app.py
from flask import Flask
from redis import Redis, RedisError
import os
import socket
# Connect to Redis
redis = 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
将按照Flask和Redis包,在应用程序中会将环境变量NAME进行打印输出,同时还会输出socket.gethostname()
的内容。最后应为Redis并没有运行(因为我们并没有启用任何的Redis服务),因此在程序中尝试使用Redistribution会出错同时也会输出相关的错误信息。
注意:在容器中获取机器的名称会返回一个类似于程序进程ID一样的容器ID
如此简单,不需要在机器上安装Python和相关的依赖,也不需要在机器上构建安装这些镜像。看上去就像我们没有配置Python和Flask,一样,实际上我们已经配置好了。
构建应用程序
通过上面的配置,我们已经准备好构建需要的所有。确保你的工作目录是处于之前创建的新目录。通过ls
命令我们可以看到
$ ls
Dockerfile app.py requirements.txt
运行构建命令。将会新建一个Docker镜像,我们可以通过使用-t
参数为镜像取一个友好的名字
docker build -t friendlyhello .
我们构建的镜像在哪里呢?它位于机器上的本地Docker镜像仓库中,
docker image ls
REPOSITORY TAG IMAGE ID
friendlyhello latest ******
Linux 用户可能遇到的问题
代理服务设置
代理服务在启动运行时能够阻止(修改)网络连接。如果你的机器上运行了一个代理服务器,需要将下面的相关指令添加到Dockerfile,通过使用ENV
指令能够指定代理服务的主机和端口# 配置代理服务,修改host:port为你自己机器上的对应值 EVN http_proxy host:port EVN https_proxy host:port
DNS设置
错误的DNS设置会给pip的使用带来问题。为确保pip的正常使用,需要设置DNS服务地址。我们可以通过修改Docker守护进程里的DNS信息。通过在编辑(或者新建)配置文件(/etc/docker/daemon.json)添加dns信息{ "dns": ["你的DNS地址", "8.8.8.8"] } 保存对daemon.json文件的修改,重启docker服务`sudo service docker restart`
问题修复后,重新使用
build
进行构建
运行应用
使用-p
参数进行端口映射将主机的4000端口映射为容器对外发布的80端口,docker run -p 4000:80 friendlyhello
通过控制台我们可以看到容器启动的相关信息,可以看到Python监听了80端口启动了一个HTTP服务。但是这个信息是来自于容器内容,因此80端口也是指的容器内的端口,在运行启动时我们通过端口映射将主机的4000端口映射到容器内的80,因此我们可以在本机通过http://localhost:4000
来访问服务。如果一起启动正常我们可以在浏览器看到相关的信息
注意
如果是在windows7上通过Docker Toolbox来运行容器复苏的话,需要将localhost替换为Docker Machine的IP。可以通过docker machine ip
来查看Docker Machine的IP地址
在终端中使用CTRL+C
,可以退出
在windows操作系统中,需要明确停止容器
在windows操作系统中,使用CTRL+C
不会停止容器,只会退出命令终端。因此我们需要通过CTRL+C
退出命令终端(此时通过docker container ls
查看还有哪些容器在运行),然后使用docker container stop <Container NAME od ID>
来停止容器
接下来,我们需要应用在后台运行,以静默的方式在后台运行
docker run -d -p 4000:80 friendlyhello
使用-d
参数,可以让容器在后台运行,通过docker container ls
查看正在运行的容器和相关信息,在后台运行的的容器可以使用docker container stop <Container NAME od ID>
来停止容器
发布共享镜像
为演示我们刚刚运行的应用的可移植性,我们可以上传构建的镜像然后就可以在任意地方运行了。在部署容器到其他环境(生产环境、测试环境)前,需要知道如何将镜像推送上传到登记注册处。
一个登记注册处是仓库的集合,仓库是镜像的集合——就像GitHub的仓库一样。登记注册处的一个账号可以创建多个仓库。docker
命令默认使用Dockers的公共注册处
注意
使用默认的注册处是因为它已经做好了默认的配置而且是免费的。当然还有很多其他的公共注册处提供选择,我们也可以自己创建私有的注册处
使用Docker账号登陆
如果还没有Docker账号,可以在http://hub.docker.com进行注册。在本机登陆公共的Docker登记注册处docker login
,根据提示信息输入账号密码,完成登陆
为镜像打标签
仓库上的镜像与本地镜像的关联格式是username/repository:tag
,tag是可选的,但是建议每次都附上tag,通过tag我们可以了解到docker镜像的版本。通过镜像功能和相关信息为repository:tag选择有意义的名称,例如get-started:part2
使用我们自己的username,repository和tag运行docker tag image
,命令的语法格式为:
docker tag image username/repository:tag
例如
docker tag friendlyhello gordon/get-started:part2
运行docker image ls
查看刚刚打上tag的镜像
推送发布镜像
上传打上标签的镜像到仓库
docker push username/repository:tag
,例如docker push gordon/get-started:part2
上传完成后,这个镜像就成为公共的,如果我们登陆Docker Hub,就可以看到刚刚上传的镜像
从远端拉取镜像并运行
至此,可以通过使用 docker run
在任何其他支持docker的机器上运行应用
docker run -p 4000:80 username/repository:tag
如果这个镜像在本机没有检测到,Docker就会从远端仓库哦查找获取这个镜像。无论docker run
在哪里运行,他都会获取到我们上传的镜像,在镜像中包含有Python、相关的依赖和运行的代码。它把所有需要的代码依赖组件等打包在一起,而在本地机器上我们不需要任何安装而外的东西就能够运行整个应用。
本节总结
本节通一个实际的案例讲解了Dockefile和Docker Hub的相关知识,在下一节,将会学习如何以服务的方式运行容器来扩展我们的应用
温故知新
下面的终端记录小视频记录了本节的所涉及的相关操作
下面列出了本节使用到的一些命令
docker build -t friendlyhello # 使用当前目录下的Dockerfile新建镜像
docker run -p 4000:80 friendlyhello # 运行“friendlyhello”,并将本机的4000端口映射到容器的80端口
docker run -d -p 4000:80 friendlyhello # 以后台静默方式运行容器,并将本机的4000端口映射到容器的80端口
docker container ls # 列出所有正在运行的容器
docker container ls -a # 列出所有容器,包括已停止的
docker container stop <hash> # 友好的停止指定的容器
docker contailer kill <hash> # 强制停止指定的容器
docker container rm <hash> # 从机器上删除指定的容器
docker container rm (docker image ls -a -q) # 删除本机上所有的镜像
docker login # 使用Docker账号进行登陆
docker tag <image> username/repository:tag # 为待上传的镜像打标签
docker push username/repository:tag # 上传镜像到仓库
docker run username/repository:tag # 运行镜像(如果本机没有该镜像,就会从仓库下载镜像并允许)
了解更多
- 微信公众号搜索“懒得糊涂个人工作室”了解更多
- 博客网站地址:http://www.403studio.com