docker run hello-world
现在是时候使用docker的方式来构建应用了。我们从这个应用层次结构的底部开始,本文将描述容器相关的内容。关于如何成为一个具有生产能力的服务将在第三部分中详述。最后在第五部分中将描述顶层的服务堆栈,包含所有服务的交互问题。
在过去,如果想编写一个Python应用,你的首要任务就是在你的机器上安装Python运行环境。而且还需要该环境能够符合你的应用运行所需的扩展库,并且还要与你的生产环境进行匹配。
如果使用docker, 你只需获取一个可以移植的python运行环境镜像,不需要额外安装。然后你就可以构建一个包含程序代码运行的基本python环境以及运行时的依赖项一起的python镜像。
这个可移植镜像通过一个叫做Dockerfile
的文件定义。
Dockerfile
定义一个容器Dockerfile
中定义了容器内部环境中发生的事情,比如在环境内对网络接口和磁盘驱动器的虚拟化。由于该环境与系统的其他部分时相互隔离的,所以你需要将内部端口隐射到外部环境中来,并指定那些内容需要复制到环境中去。另外,在执行操作后,你可以认定该Dockerfile
的程序构建操作在任何地方运行都是相同的。
Dockerfile
创建一个空的目录,并将目录定位到新的目录中(使用cd
命令),然后创建一个名为Dockerfile
的文件,将下面的内容拷贝到该文件中并保存。注意文件中的每一个注释。
# 使用python官方运行环境作为父镜像
FROM python:2.7-slim
# 将/app设置成工作目录
WORKDIR /app
# 将当前目录中的内容拷贝到容器的/app目录中去
COPY . /app
# 安装requirements.txt指定的库
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# 对外开放容器的80端口
EXPOSE 80
# 定义环境变量
ENV NAME World
# 当容器启动后运行app.py文件
CMD ["python", "app.py"]
下面将进行将创建Dockerfile
中使用到的外部文件,如app.py
和requirements.txt
。
在Dockerfile
文件所在目录中创建名为app.py
和requirements.txt
的两个文件。这样就完成了我们的应用创建,这个过程看起来非常简单。当构建上述Dockerfile
时,由于使用了的COPY
命令,所以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
# 连接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
使用来下载python依赖库Flask
和Redis
的,然后应用输出了环境变量NAME
,同时也输出了socket.gethostname()
的结果。最后因为Redis未运行(我们仅仅安装了python的库文件,而不是Redis本身),我们可以预料这里将会运行失败并抛出异常信息。
注意:在容器内部访问容器ID时就是访问其主机名,这个有些类似正在运行的进程ID。
完成! 您的系统上不需要Python或requirements.txt
中的任何内容,构建或运行此镜像也不需要在系统上安装它们。看起来你并没有真正建立一个Python
和Flask
的环境,但你已经拥有了一切。
我们已经准备号构建这个应用了。确保你当前所处位置在工作目录的顶层。下面的ls
命令应有的结果:
$ ls
Dockerfile app.py requirements.txt
现在运行构建命令。我们可以使用--tag
选项为将要创建的镜像命令,也可以使用短名选项-t
代替。
docker build --tag=friendlyhello .
在哪儿可以看到你构建的镜像呢?它保存在你本地docker的镜像仓库里:
$ docker image ls
REPOSITORY TAG IMAGE ID
friendlyhello latest 326387cea398
注意镜像默认TAG为latest
。如果要指定其他的TAG,可以使用完成标记进行定义,如:--tag=friendlyhello:v0.0.1
.
Linux用户的故障排除
代理服务器配置
代理服务器可以在启动并运行后阻止与Web应用程序的连接。如果您位于代理服务器后面,请将以下行添加到
Dockerfile
,使用ENV
命令指定代理服务器的主机和端口:# 设置代理服务器, 将host:port替换成你的服务器信息 ENV http_proxy host:port ENV https_proxy host:port
DNS配置
DNS配置错误可能会导致pip出现问题。您需要设置自己的DNS服务器地址才能使pip正常工作。 您将需要更改Docker守护程序的DNS设置,可以在
/etc/docker/daemon.json
配置文件(文件可能不存在,需要自行创建)中编辑(创建)dns
关键字,如下所示:{ "dns": ["your_dns_address", "8.8.8.8"] }
在上面的示例中,列表的第一个元素是你自己DNS服务器的地址。第二项是Google的DNS,可在第一项无法使用时使用。
完成上述修改,保存
daemon.json
文件,重启Dockersudo service docker restart
修复后,重新尝试
build
命令。
运行应用,并使用-p
选项将容器开发的80端口映射到外部的4000端口上:
docker run -p 4000:80 friendlyhello
你可以看到python在本机的80端口(http://0.0.0.0:80
)上服务。但是这个设置是在容器内的环境中有效,容器内部并不知道你使用了外部的4000端口映射内部的80端口,所以你在外部访问该服务是需要使用http://localhost:4000
。
在浏览器中输入这个地址,你就可以看到该服务提供的一个web页面。
注意:如果你是在window7上使用Docker Toolbox,则需要使用Docker的宿主机ip来替换
localhost
,例如:http://192.168.99.100:4000/
。可以使用docker-machine ip
命令来获取宿主机ip。
你可以在终端中使用curl
命令来查看相同的内容。
$ curl http://localhost:4000
<h3>Hello World!</h3><b>Hostname:</b> 8fc990912a14<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>
在执行docker run -p 4000:80
命令分别对应着该服务在外部提供服务的端口和容器内Dockerfile
中使用EXPOSE
指令对外开发的端口。这是该操作后就可以在本地(http://localhost
)使用4000端口访问容器的80端口了。
在终端中按CTRL+C
退出。
在window中显式停止容器
在window系统中,
CTRL+C
不能直接停止容器。所以,你应该首先按下CTRL+C
查看相关提示或者打开一个新的终端,然后执行docker container ls
查看正在运行的容器列表,最后使用docker container stop <容器NAME或者ID>
停止相应容器。否则,在你下一次运行该容器的时候,守护进程将会抛出错误信息。
现在我们来让应用以独立模式背后运行:
docker run -d -p 4000:80 friendlyhello
你将获得一个应用运行的容器长ID,然后返回终端,应用将在背后运行。你还可以使用docker container ls
命令查看容器的短ID(在运行命令是可以互换):
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED
1fa4ab2cf395 friendlyhello "python app.py" 28 seconds ago
请注意,CONTAINER ID
匹配http:// localhost:4000
上的内容。
现在使用docker container stop
命令停止进程,以CONTAINER ID
作为参数,如下:
docker container stop 1fa4ab2cf395
为了证明我们所创建镜像的便携性,我们将上传该镜像,然后在任意地方运行。为此,你需要知道如何将想要发布到生产环境的镜像推送到镜像注册服务。
镜像注册服务就是一个仓库集合,想Github那样的镜像仓库,不同点在于docker仓库的代码是已经编译过的。一个账号可以创建若干个仓库,docker
命令默认使用Docker公共注册服务。
注意:我们在这里使用Docker的公共注册服务只是因为它是免费和预配置的,另外还有许多公共注册服务可供选择,您甚至可以使用Docker Trusted Registry设置自己的私有注册服务。
如果你还没有Docker账号,可以到hub.docker.com
注册一个,然后记住你的用户名。
在本机登录Docker公共注册服务:
$ docker login
将本地镜像与注册服务上的仓库相关联的表达式为:username/repository:tag
。tag
为可选项,但还是建议显式声明,因为它注册服务用来区分镜像版本的机制。为镜像分配仓库并使用一个有意义的名字,比如:get-started:part2
。这个镜像将放入get-started
仓库中,并标记为part2
版本。
现在我们来为镜像打标签。使用你的用户名、仓库名以及标签名来执行docker tag image
命令,以便确定该镜像上传的目的地。完整命令如下:
docker tag image username/repository:tag
例如:
docker tag friendlyhello gordon/get-started:part2
执行docker image ls
查看你最近标记的镜像。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
friendlyhello latest d9e555c53008 3 minutes ago 195MB
gordon/get-started part2 d9e555c53008 3 minutes ago 195MB
python 2.7-slim 1c7128a655f6 5 days ago 183MB
...
上传你标记的镜像到仓库中:
docker push username/repository:tag
一旦完成上述操作,你的镜像将会作为公共镜像。当你登录Docker Hub后,你就可以看到你新上传的镜像,以及相关pull命令。
从先在开始,你可以使用docker run
命令在任何机器上运行你的应用,具体命令如下:
docker run -p 4000:80 username/repository:tag
如果本机不存在该镜像,docker将从远程仓库自动下载(或使用docker pull
命令手动下载)。
$ docker run -p 4000:80 gordon/get-started:part2
Unable to find image 'gordon/get-started:part2' locally
part2: Pulling from gordon/get-started
10a267c67f42: Already exists
f68a39a6a5e4: Already exists
9beaffc0cf19: Already exists
3c1fe835fb6b: Already exists
4c9f1fa8fcb8: Already exists
ee7d8f576a14: Already exists
fbccdcced46e: Already exists
Digest: sha256:0601c866aab2adcc6498200efd0f754037e909e5fd42069adeff72d1e2439068
Status: Downloaded newer image for gordon/get-started:part2
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
无论在什么地方执行docker run
,都将获取你的镜像,连同python和requirements.txt
中的依赖,并运行你的代码。这些都将整合到一个很小的镜像包里面,你不需要额外安装任何东西就可以使用docker运行它。
本文全部介绍完毕。下一本将会介绍如何在服务中扩展我们的应用。
全部评论