MYF

Get Started, Part 2: Containers

参考文档:Get Started, Part 2: Containers

Prerequisites

已安装docker

Introduction

层次结构:

  • Top level: stack,定义service之间的交互,将会在Part 5介绍
  • Middle level: service,定义container在production期间的行为,将会在Part 3介绍
  • Bottom level: container(本节介绍)

Your new development environment

过去,如果你需要开发一个Python应用,首先就是要在电脑上安装一个python的运行环境,但是,这需要你的电脑非常适合你的应用,并且需要match你的开发环境。

使用Docker,你可以grab一个轻量级的Python环境作为一个镜像,不必安装。然后,你的开发可以包括基础的Python镜像、你的code,从而保证你的应用、依赖、运行环境打包。

这些可移植的images称为Dockerfile

使用Dockerfile定义一个container

Dockerfile定义了你的container中的环境会运行的内容。对于一些如网络接口、磁盘驱动的资源访问在环境中被虚拟化了,这些与你的系统隔绝开来,所以你需要将这些ports映射到外界,明确哪些文件是你想拷贝到环境中的。然而,这样做之后,你可以期待你在Dockerfile中定义的build版本在任何地方都有相同的运行效果。

创建一个新的目录,进入该目录,创建一个文件叫做Dockerfile,复制粘贴如下内容保存。注释语句解释了每一行的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# filename: Dockerfile
# Use an official Python runtime as a parent image
FROM python:2.7-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
ADD . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

Tips: 使用代理服务器

代理服务器可以在你的container运行的时候屏蔽连接你的web app的请求,如果你使用了代理服务器,在Dockerfilepip指令前添加如下行,使用ENV名来指定你的代理服务器

1
2
3
# Set proxy server, replace host:port with values for your servers
ENV http_proxy host:port
ENV https_proxy host:port

The app itself

创建另外两个文件requirements.txtapp.py,将他们放到和Dockerfile相同的目录中。当Dockerfile被创建的称为image的时候,由于Dockerfile中的ADD指令,app.pyrequirements.txt会出现,由于EXPOSE指令,通过http可以访问。

1
2
3
# filename: requirements.txt
Flask
Redis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# filename: 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将会为Python安装Flask和Redis库,这个应用会显示环境变量NAME,同样会输出socket.gethostname()的返回值。最终,因为Redis没有运行(由于我们只是安装了Python库,而不是Redis本身),我们可以预期到,在这里会fail并且报一些error。

注意:在contaner内部访问host的名称返回container的ID,如同可执行文件的运行ID一样。

就这样,你的系统不需要Python或者requirements.txt文件中的任何东西,也不需要创建或者运行这个镜像安装到你的系统中。他不需要你真的设置一个Python和Flask环境,但是你已经有了。

Build the app

我们已经创建了这个app,确保你当前目录下有三个文件

1
2
3
4
5
[root@digitalocean docker]# ls -l
总用量 12
-rw-r--r-- 1 root root 665 3月 8 22:03 app.py
-rw-r--r-- 1 root root 512 3月 8 21:54 Dockerfile
-rw-r--r-- 1 root root 12 3月 8 22:02 requirements.txt

现在运行创建指令,这会创建一个Docker镜像,我们将使用-t标记来给他其一个友好的名字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
[root@digitalocean docker]# docker build -t friendlyhello .
Sending build context to Docker daemon 4.608kB
Step 1/7 : FROM python:2.7-slim
2.7-slim: Pulling from library/python
d2ca7eff5948: Pull complete
cef69dd0e5b9: Pull complete
50e1d7e4f3c6: Pull complete
861e9de5333f: Pull complete
Digest: sha256:e9baca9b405d3bbba71d4c3c4ce8a461e4937413b8b910cb1801dfac0a2423aa
Status: Downloaded newer image for python:2.7-slim
---> 52ad41c7aea4
Step 2/7 : WORKDIR /app
Removing intermediate container 55fb7675239c
---> 5edd0305e2c4
Step 3/7 : ADD . /app
---> f034a0bbe582
Step 4/7 : RUN pip install --trusted-host pypi.python.org -r requirements.txt
---> Running in 54157f9c3db3
Collecting flask (from -r requirements.txt (line 1))
Downloading Flask-0.12.2-py2.py3-none-any.whl (83kB)
Collecting Redis (from -r requirements.txt (line 2))
Downloading redis-2.10.6-py2.py3-none-any.whl (64kB)
Collecting itsdangerous>=0.21 (from flask->-r requirements.txt (line 1))
Downloading itsdangerous-0.24.tar.gz (46kB)
Collecting Jinja2>=2.4 (from flask->-r requirements.txt (line 1))
Downloading Jinja2-2.10-py2.py3-none-any.whl (126kB)
Collecting Werkzeug>=0.7 (from flask->-r requirements.txt (line 1))
Downloading Werkzeug-0.14.1-py2.py3-none-any.whl (322kB)
Collecting click>=2.0 (from flask->-r requirements.txt (line 1))
Downloading click-6.7-py2.py3-none-any.whl (71kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->flask->-r requirements.txt (line 1))
Downloading MarkupSafe-1.0.tar.gz
Building wheels for collected packages: itsdangerous, MarkupSafe
Running setup.py bdist_wheel for itsdangerous: started
Running setup.py bdist_wheel for itsdangerous: finished with status 'done'
Stored in directory: /root/.cache/pip/wheels/fc/a8/66/24d655233c757e178d45dea2de22a04c6d92766abfb741129a
Running setup.py bdist_wheel for MarkupSafe: started
Running setup.py bdist_wheel for MarkupSafe: finished with status 'done'
Stored in directory: /root/.cache/pip/wheels/88/a7/30/e39a54a87bcbe25308fa3ca64e8ddc75d9b3e5afa21ee32d57
Successfully built itsdangerous MarkupSafe
Installing collected packages: itsdangerous, MarkupSafe, Jinja2, Werkzeug, click, flask, Redis
Successfully installed Jinja2-2.10 MarkupSafe-1.0 Redis-2.10.6 Werkzeug-0.14.1 click-6.7 flask-0.12.2 itsdangerous-0.24
Removing intermediate container 54157f9c3db3
---> 6bf01ede89f8
Step 5/7 : EXPOSE 80
---> Running in 1d5de282b1bb
Removing intermediate container 1d5de282b1bb
---> 39b7f0c55405
Step 6/7 : ENV NAME World
---> Running in ea59af0bb271
Removing intermediate container ea59af0bb271
---> fdd3ff46f874
Step 7/7 : CMD ["python", "app.py"]
---> Running in cc079cdcccf5
Removing intermediate container cc079cdcccf5
---> 4ba59f6d8530
Successfully built 4ba59f6d8530
Successfully tagged friendlyhello:latest

你创建的image在哪里?在你电脑的本地Docker image的registry中

1
2
3
4
5
[root@digitalocean docker]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
friendlyhello latest 4ba59f6d8530 About a minute ago 148MB
python 2.7-slim 52ad41c7aea4 3 weeks ago 139MB
hello-world latest f2a91732366c 3 months ago 1.85kB

Run the app

运行app,通过-p标记,使你电脑的4000端口映射到container的80端口

1
2
[root@digitalocean docker]# docker run -p 4000:80 friendlyhello
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

这样就可以看到Python运行在http://0.0.0.0:80上了,但是这个消息是来自container内部,通过http://localhost:4000可以访问,当然,如果你是挂在云服务器上,那么将地址中的localhost换成你主机的ip,这样就可以访问了。

注意:如果你使用Docker Toolbox on Windows 7的话,使用电脑的主机IP而不是localhost,比如http://192.168.99.100:4000/。可以通过命令`docker-machine ip`来查看ip地址

你也可以通过curl命令来获取相同的内容

1
2
3
$ curl http://localhost:4000

<h3>Hello World!</h3><b>Hostname:</b> 8fc990912a14<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>

这个端口映射是用来展示Dockerfile中EXPOSE的端口和你发布的时候使用docker run -p的区别。在后续的步骤中,我们将会用主机的80端口和container的80端口进行映射,这样就可以通过http://localhost访问了

在终端中按CTRL+C以退出。

在Windows中,显式的停止container
在windows系统中,ctrl+c并不会停止container,所以先按ctrl+c获取返回的提示,然后输入docker container ls显式运行中的container,然后通过docker container stop <Container NAME or ID>来停止container,否则你会报错当你下一步充实重新执行这个container的时候

我们现在来执行这个app通过分离模式(-d)

1
docker run -d -p 4000:80 friendlyhello

你会得到一个对应你app的很长的contianer ID,然后回到你的终端。你的container此时在后台运行。你可以通过docker container ls指令看到更简短的conatiner ID。然后可以通过docker container stop 1fa4ab2cf395来停止你的container,这里请将1fa4ab2cf395替换为你的container ID。

Share your image

为了展现我们刚才创建的东西有多么强大的可移植性,让我们把它上传,在任何地方运行。不管怎么说,当你想要部署conatiner的时候,你需要知道如何push到registries中。

一个registries是一个仓库(repositories)的集合(collection),一个仓库(repositories)是一系列的images,有点像GitHub的仓库,除了代码已经运行好了。一个帐号中的一个registry可以创建多个仓库。docker CLI默认使用Docker的公共registry。

注意:在这里我们使用Docker的公共registry,仅仅是因为它是免费并且事先配置好的,但是有很多公共的registry可选,你同样可以通过Docker Trusted Registry创建你自己的registry

Log in with your Docker ID

如果你没有Docker帐号,可以通过cloud.docker.com注册,请注意你的用户名。

在你的本机上登录你的Docker公共registry

1
docker login

Tag the image

通过usernam/repository:tag来关联本地image和registry中的仓库。tag是可选的,但是是推荐的,因为这是docker给image版本的方式。给你的repo和tag有意义的名字,比如get-started:part2。这就会把镜像存在get-started仓库中,并且给它打上part2的标记。

现在,我们把这些放在一起,并且对这个image打上tag。运行docker tag image`和username,仓库名,和tag名称,这样image会上传到你想要的重点上,语法如下:

1
docker tag image username/repository:tag

比如,

1
docker tag friendlyhello john/get-started:part2

运行docker image ls就可以看到你新打tag的image了

1
2
3
4
5
[root@centos-s-1vcpu-1gb-sfo2-01 1.1 simple web app]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
friendlyhello latest d2c166dc3296 5 minutes ago 148MB
manyfun711/get-started part2 d2c166dc3296 5 minutes ago 148MB
python 2.7-slim 52ad41c7aea4 3 weeks ago 139MB

Publish the image

上传你打标记了的image到仓库

1
2
3
4
5
6
7
8
9
10
[root@centos-s-1vcpu-1gb-sfo2-01 1.1 simple web app]# docker push manyfun711/get-started:part2
The push refers to repository [docker.io/manyfun711/get-started]
14cddc36d75b: Pushed
c3c9f3efc9d7: Pushed
a31db1034caf: Pushed
03cd3fb86dd2: Mounted from library/python
630d02da980e: Mounted from library/python
b2f046b20847: Mounted from library/python
cf051be4e149: Mounted from library/python
part2: digest: sha256:8c8d162f2594d3b69e391465d9b75d4e43ee1071848b70572607c97467e93a01 size: 1788

在完成之后,这个结果是公开可见的,如果你登录了Docker hub,你可以看到一个新的image。

Pull and run the image from the remote repository

从现在开始,你可以使用docker run来运行你的app使用这一行指令

1
docker run -p 4000:80 manyfun711/get-started:part2

如果这个image在本地没有,那么Docker就回将它从repo中pull下来。

1
2
3
4
5
6
7
8
9
10
11
12
13
 ~  docker run -p 4000:80 manyfun711/get-started:part2                                                                            
Unable to find image 'manyfun711/get-started:part2' locally
part2: Pulling from manyfun711/get-started
d2ca7eff5948: Pull complete
cef69dd0e5b9: Pull complete
50e1d7e4f3c6: Pull complete
861e9de5333f: Pull complete
a780c396ddb7: Pull complete
80b7e1b0a9f2: Pull complete
19eef67397e2: Pull complete
Digest: sha256:8c8d162f2594d3b69e391465d9b75d4e43ee1071848b70572607c97467e93a01
Status: Downloaded newer image for manyfun711/get-started:part2
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

不论docker run指令在哪里执行,它都会pull下来你的image,同时还有你的python和所有requirements.txt中的以来,并且可以运行你的code。这些内容会以一个较小的形式一并下来,你也不需要安装任何东西从而让docker运行。

Conclusion of part two

这就是本节所有内容了,在下一个section中,我们将会学到如何扩大我们的应用,让这个container运行在一个service