万物科技 学,以致用

2 Docker教程

2017-03-21
Geng

翻译自https://prakhar.me/docker-curriculum/,并有少许改动和修正。

试玩Busybox

Busybox可以认为就是一个简化的linux系统

运行

$ docker pull busybox

由于系统权限问题,上述`命令可能会有问题。Mac系统请查看Docker引擎正在运行。Linux请尝试“sudo docker pull busybox”

pull命令将busybox镜像从Docker寄存器提取并保存在你的系统。

运行下面命令,可以看到系统上安装的所有镜像

$ docker images

run

让我们运行一下这个镜像

$ docker run busybox

嗯。。。有什么变化吗?其实后台发生了很多事情,虽然你现在看不出来。当你运行run命令时,客户端找到镜像(这里就是busybox),载入容器然后在这个容器中运行命令。我们刚才并没有提供任何命令,所以这个容器启动,空跑一通就直接退出了。

那我们试着提供一个命令

$ docker run busybox echo "hello from busybox"
hello from busybox

赞!终于看到些变化了。这一个命令,我们经历了启动一个镜像,运行一个命令,然后退出。而且这个过程真的很快!你用是任何一个虚拟机能做到这个速度吗?知道Docker很快了吧。

我们再来看看docker ps命令,它显示所有正在运行的容器。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

因为现在并没有运行的容器,我们看不到什么信息。我们是一个更有用的形式:docker ps -a

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
305297d7a235        busybox             "uptime"            11 minutes ago      Exited (0) 11 minutes ago                       distracted_goldstine
ff0a5c3750b9        busybox             "sh"                12 minutes ago      Exited (0) 12 minutes ago                       elated_ramanujan
So what we see above is a list of all containers that we ran. Do notice that the STATUS column shows that these containers exited a few minutes ago.

看到了吗?这是我们之前运行过的容器的清单。其中STATUS显示这些容器在多久前退出。

那我们可以运行多条命令吗?我们试下启动镜像的一个bash可以吗?

$ docker run -it busybox sh
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # uptime
 05:45:21 up  5:58,  0 users,  load average: 0.00, 0.01, 0.04

it将我们连接到容器中一个交互的tty。其中, -t选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i则让容器的标准输入保持打开,即交互状态。

这里,我们可以输入任何我们想输入的命令。

在容器中,尝试新建一个文件,比如touch myfile.txt,然后ls确认新建成功。输入exit,点击Enter。再次docker run -it busybox sh进入容器,运行ls。有没有发现,刚才新建的文件消失了。这是因为我们使用run启动镜像,每次都会创建全新的容器。

下面在说下怎么删除容器。我们刚才看到,即使我们退出容器,仍然可以通过docker ps -a命令看到退出容器的残留。随着容器建立退出次数越来越多,这些残留将会占用大量硬盘空间。因此,如果没有特殊需求,最好用完之后清除这些残留。我们可以使用docker rm命令,后面填容器的container ID(通过docker ps -a可见此ID)

$ docker rm 305297d7a235 ff0a5c3750b9
305297d7a235
ff0a5c3750b9

如果想更快地删除大量容器,可以:

$ docker rm $(docker ps -a -q -f status=exited)

最后,还可以通过docker rmi删除不用的镜像

容器其他命令

docker start | restart | stop:启动停止的容器,重启容器,停止容器 docker exec:运行正在运行的一个容器的命令

建议自己查查,因为后面不用这些命令,这里略过,以免影响心情。

Docker的Webapp

玩够了,我们现在可以搞点真实的东西了。

静态网站

我们一步步来。我们首先尝试建立一个巨简单的静态网站。我们会从Docker Hub拖下来一个镜像,跑一个容器,感受一下跑一个网站服务是多么简单。

开始干活。这个静态网站托管在在Docker寄存器 - prakhar1989/static-site。下载这个镜像然后跑起来,以下这一条命令搞定:

$ docker run prakhar1989/static-site

因为本地没有这个镜像,所以首先下载才能跑起来。等到看到Nginx is running...就说明已经跑起来了。那怎么看到网站呢?我们怎么才能网站这个容器?

额。。。现在客户端没有暴露任何端口,我们需要重新运行docker run以开放端口。同时,当关掉终端时,我们也不想关掉运行中的容器。满足这种需求的模式叫做分离模式(detached mode)。

$ docker run -d -P --name static-site prakhar1989/static-site
e61d12292d69556eabe2a44c16cbd54486b2527e2ce4f95438e504afb7b02810

上述命令中,, -d进入分离模式, -P将所有端口暴露到随机端口。–name后面是我们起的名字。现在我们通过下面命令查看端口吧

$ docker port static-site
80/tcp -> 0.0.0.0:32769
443/tcp -> 0.0.0.0:32768

浏览器输入“http://localhost:32769”访问。当然,我们也可以指定端口,比如

$ docker run -p 8888:80 prakhar1989/static-site
Nginx is running...

想要停止容器,使用docker stop 容器id 命令。

是不是巨简单?你已经知道怎么使用Docker镜像跑起来一个网站了,那么,我们怎么建立自己的镜像呢?

Docker镜像

本小结介绍如何建立自己的镜像。

简单来讲,Docker镜像就像是一个git仓库,镜像可以做出有很多改变和版本的提交。如果不指定版本,默认使用最新版。

比如像要拉取某个版本的Ubuntu

$ docker pull ubuntu:12.04

想要得到一个Docker镜像,或者从一个Docker寄存器(比如Docker Hub)拉取,或者自己建立。你可以在Docker Hub搜索自己想要的镜像。

镜像有一个需要特变注意的区别是基镜像和子镜像(base and child images)。

基镜像没有任何父镜像,一般是系统镜像比如Ubuntu和刚才用到的busybox。子镜像基于基镜像并于其上添加一些功能。这样就有了官方镜像和用户镜像。

官方镜像由Docker员工维护,用户镜像由类似你我的用户建立。

我们的第一个镜像

需要python支持,没有的话自己去安装。

我们的目标是建立一个Flask应用。这个应用是原作者做的,每次载入会显示一个随机的小猫动图。请复制此库到本地

验证本地工作

进入flask-app路径然后运行此应用,如下:

$ cd flask-app
$ pip install -r requirements.txt
$ python app.py
 * Running on http://0.0.0.0:5000/ (Press CTRL`C to quit)

一切顺利的话,你可以在http://localhost:5000看到效果。

如果出现权限问题,可能需要sudo,或者你不想吧各种包安装到系统层面,可以试试pip install –user -r requirements.txt

不错吧?下一步我们就要用此镜像建立一个自己的镜像。

建立自己的镜像

如前所述,所有的用户镜像都是基于基镜像。

因为我们这个应用使用Python写成,我们使用的基镜像是Python 3。下载:

$ docker pull python:3-onbuild

onbuild版的镜像包括可以使应用启动的一些帮助项。现在我们有了足够的内容:可工作的app和基镜像。下一步做什么呢?我们使用Dockerfile

Dockerfile

什么是Dockerfile

Dockerfile就是一个简单的文本文件,它里面包含了创建镜像的时候Docker客户端调用的命令。这是一个简化自动化镜像创建的过程。你在Dockerfile写的命令和Linux命令几乎相同,是不是很爽?不用学就会了吧?

下面我们脑补一下Dockerfile应该有什么内容。

  1. 基镜像是谁?
  2. 怎么把我们需要的文件等装到基镜像?
  3. 怎么访问我们的应用?使用哪个端口?
  4. 怎样启动应用?

我们看下Dockerfile的内容(位于flask-app文件夹)

# 基镜像
FROM python:3-onbuild

# 端口
EXPOSE 5000

# 启动应用
CMD ["python", "./app.py"]

我们可以看到,这个文件没有回答第二个问题,如何是好? 不用担心,因为我们的Dockerfile位于flask-app文件夹,且使用onbuild版本,第二个问题自动解决了。

使用Dockerfile

怎么用呢?让docker build命令去干就行了。

首先,没有注册Docker hub的同学要去注册,记录自己的用户名cdDockerfile路径,然后:

$ docker build -t 你的用户名/catnip .
Sending build context to Docker daemon 8.704 kB
Step 1 : FROM python:3-onbuild
# Executing 3 build triggers...
Step 1 : COPY requirements.txt /usr/src/app/
 ---> Using cache
Step 1 : RUN pip install --no-cache-dir -r requirements.txt
 ---> Using cache
Step 1 : COPY . /usr/src/app
 ---> 1d61f639ef9e
Removing intermediate container 4de6ddf5528c
Step 2 : EXPOSE 5000
 ---> Running in 12cfcf6d67ee
 ---> f423c2f179d1
Removing intermediate container 12cfcf6d67ee
Step 3 : CMD python ./app.py
 ---> Running in f01401a5ace9
 ---> 13e87ed1fbc2
Removing intermediate container f01401a5ace9
Successfully built 13e87ed1fbc2

其中-t标记来添加tag,指定新的镜像的用户信息。 “.” 是 Dockerfile所在的路径(当前目录),也可以替换为一个具体的 Dockerfile的路径。

跑起来

$ docker run -p 8888:5000 用户名/catnip
* Running on http://0.0.0.0:5000/ (Press CTRL`C to quit)

访问“http://0.0.0.0:8888/”看看效果吧。

祝贺你,成功了!

Docker on AWS

算了吧

多容器环境

上面部分只运行了一个Docker。但是为了运行一个应用,你还用很多其他依赖吧,数据库得有吧?

这部分我们就讨论下怎么运行有依赖的应用。具体来讲,就是讨论运行和管理多容器环境。

将各种服务解耦很重要,使用容器隔离各个服务。这样就可以针对各个服务进行单独优化。

SF Food Trucks为例

此应用包含Flask后台和Elasticsearch服务。自然我们想把它分成两个容器。如果以后某部分成为性能瓶颈,我们可以通过增加容器单独优化。

Elasticsearch容器

建立容器应该不难吧,我们已经建立过一个Flask容器,那我们看看有没有Elasticsearch容器:

$ docker search elasticsearch
NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
elasticsearch                     Elasticsearch is a powerful open source se...   697       [OK]
itzg/elasticsearch                Provides an easily configurable Elasticsea...   17                   [OK]
tutum/elasticsearch               Elasticsearch image - listens in port 9200.     15                   [OK]
barnybug/elasticsearch            Latest Elasticsearch 1.7.2 and previous re...   15                   [OK]
digitalwonderland/elasticsearch   Latest Elasticsearch with Marvel & Kibana       12                   [OK]
monsantoco/elasticsearch          ElasticSearch Docker image                      9                    [OK]

我们可以看到有一个官方版本(没有前缀那个)。我们迅速就可以把它运行起来:

$ docker run -dp 9200:9200 --name es elasticsearch
d582e031a005f41eea704cdc6b21e62e7a8a42021297ce7ce123b945ae3d3763

$ curl 0.0.0.0:9200
{
  "name" : "Ultra-Marine",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}

Flask容器

下一步就是跑起来Flask容器

首先我们需要Dockerfile。这次,除了Python,我们需要JavaScript依赖和nodejs。因为有了自定义构建的步骤,我们需要从Ubuntu基镜像开始。

Dockerfile如下:

# start from base
FROM ubuntu:14.04
MAINTAINER Prakhar Srivastav <prakhar@prakhar.me>

# install system-wide deps for python and node
RUN apt-get -yqq update
RUN apt-get -yqq install python-pip python-dev
RUN apt-get -yqq install nodejs npm
RUN ln -s /usr/bin/nodejs /usr/bin/node

# copy our application code
ADD flask-app /opt/flask-app
WORKDIR /opt/flask-app

# fetch app specific deps
RUN npm install
RUN npm run build
RUN pip install -r requirements.txt

# expose port
EXPOSE 5000

# start app
CMD [ "python", "./app.py" ]

我们从基镜像开始,安装依赖项。yqq说明任何询问都答yes,而且创建了到node的符号链接来保证兼容性。

然后使用ADD命令将我们的应用复制到容器中的/opt/flask-app。然后我们将这个目录设为工作目录,这样下面的操作将会在这个目录进行。

现在系统级别的依赖都搞定了,下面就要安装应用级的依赖了。首先使用npm安装然后运行pip。后面步骤前别按介绍过,不再详述。

最后,跑起来吧(其实是爬,因为要下载很多东西):

$ docker build -t 你的用户名/foodtrucks-web .

都安装好后,再次运行会很快。我们试试:

$ docker run -P prakhar1989/foodtrucks-web
Unable to connect to ES. Retying in 5 secs...
Unable to connect to ES. Retying in 5 secs...
Unable to connect to ES. Retying in 5 secs...
Out of retries. Bailing out...

额。。。我们的应用跑步起来,因为它不知道Elasticsearch在哪。怎么办呢?

Docker Network

首先,我们想想怎么解决这个问题。想想我们学过什么命令可以用来给我们一些启示吗?docker ps如何?其实除了这个命令我们不知道该干什么了吧。

我们来试下:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
e931ab24dedc        elasticsearch       "/docker-entrypoint.s"   2 seconds ago       Up 2 seconds        0.0.0.0:9200->9200/tcp, 9300/tcp   cocky_spence

这段输出跟我们说在0.0.0.0:9200有一个ES容器可以访问。我们能告诉Flask去访问这里吗?

那么我们就得和Flask容器中的应用说去访问主机的0.0.0.0:9200吧。但是ES容器也运行在0.0.0.0,只不过是另外一个端口。那么并不能通过访问这个IP来交流。哪还有什么IP可用吗?

下面我们分析一下docker的网络。当docker安装之后,自动建立三个网络:

$ docker network ls
NETWORK ID          NAME                DRIVER
075b9f628ccc        none                null
be0f7178486c        host                host
8022115322ec        bridge              bridge

bridge网络是运行的默认网络。我们查看下:

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "8022115322ec80613421b0282e7ee158ec41e16f565a3e86fa53496105deb2d7",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Containers": {
            "e931ab24dedc1640cddf6286d08f115a83897c88223058305460d7bd793c1947": {
                "EndpointID": "66965e83bf7171daeb8652b39590b1f8c23d066ded16522daeb0128c9c25c189",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        }
    }
]

可以见到我们的容器e931ab24dedc在”Containers”中列出,而且分配了一个IP:172.17.0.2。这个IP是我们想要用的那个吗?我们运行flask容器试试。

$ docker run -it --rm 你的用户名/foodtrucks-web bash
root@35180ccc206a:/opt/flask-app# curl 172.17.0.2:9200
bash: curl: command not found
root@35180ccc206a:/opt/flask-app# apt-get -yqq install curl
root@35180ccc206a:/opt/flask-app# curl 172.17.0.2:9200
{
  "name" : "Jane Foster",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}
root@35180ccc206a:/opt/flask-app# exit

–rm说明运行完就自动删除这个容器。我们进入容器后,运行了一个curl命令,但是提示此命令没有安装,所以需要安装之后,再次运行。然后,大功告成!

虽然我们知道了怎么样让它们之间通信,但是我们仍然有两个问题需要解决:

  1. bridge网络是所有容器共享的,不够安全。
  2. 告诉Flask连接我们让他连接的地址。

Docker允许我们建立我们自己的网络,还能是它们互相隔离.

首先,我们建立一个自己的网络

$ docker network create foodtrucks
1a3386375797001999732cb4c4e97b88172d983b08cd0addfcb161eed0c18d89

$ docker network ls
NETWORK ID          NAME                DRIVER
1a3386375797        foodtrucks          bridge
8022115322ec        bridge              bridge
075b9f628ccc        none                null
be0f7178486c        host                host

这个命令新建了一个名为foodtrucksbirdge网络。Docker网路的详细内容,请阅读官方文档

现在我们有了网络,我们可以使用 –net将我们的容器运行在这个网络。首先,我们先把之前的容器停止。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
e931ab24dedc        elasticsearch       "/docker-entrypoint.s"   4 hours ago         Up 4 hours          0.0.0.0:9200->9200/tcp, 9300/tcp   cocky_spence

$ docker stop e931ab24dedc
e931ab24dedc

$ docker run -dp 9200:9200 --net foodtrucks --name es elasticsearch
2c0b96f9b8030f038e40abea44c2d17b0a8edda1354a08166c33e6d351d0c651

$ docker network inspect foodtrucks
[
    {
        "Name": "foodtrucks",
        "Id": "1a3386375797001999732cb4c4e97b88172d983b08cd0addfcb161eed0c18d89",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Config": [
                {}
            ]
        },
        "Containers": {
            "2c0b96f9b8030f038e40abea44c2d17b0a8edda1354a08166c33e6d351d0c651": {
                "EndpointID": "15eabc7989ef78952fb577d0013243dae5199e8f5c55f1661606077d5b78e72a",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {}
    }
]

我们这次给了这个容器一个名字es,运行在我们自定义的网络,其他都和以前一样。我们在试着将我们的flask容器运行在这个网络。

$ docker run -it --rm --net foodtrucks 你的账户名/foodtrucks-web bash
root@53af252b771a:/opt/flask-app# cat /etc/hosts
172.18.0.3  53af252b771a
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2  es
172.18.0.2  es.foodtrucks

root@53af252b771a:/opt/flask-app# curl es:9200
bash: curl: command not found
root@53af252b771a:/opt/flask-app# apt-get -yqq install curl
root@53af252b771a:/opt/flask-app# curl es:9200
{
  "name" : "Doctor Leery",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}
root@53af252b771a:/opt/flask-app# ls
app.py  node_modules  package.json  requirements.txt  static  templates  webpack.config.js
root@53af252b771a:/opt/flask-app# python app.py
Index not found...
Loading data in elasticsearch ...
Total trucks loaded:  733
 * Running on http://0.0.0.0:5000/ (Press CTRL`C to quit)
root@53af252b771a:/opt/flask-app# exit

搞定!下一步我们将容器正式运行起来。

$ docker run -d --net foodtrucks -p 5000:5000 --name foodtrucks-web 你的用户名/foodtrucks-web
2a1b77e066e646686f669bab4759ec1611db359362a031667cacbe45c3ddb413

$ docker ps
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                              NAMES
2a1b77e066e6        prakhar1989/foodtrucks-web   "python ./app.py"        2 seconds ago       Up 1 seconds        0.0.0.0:5000->5000/tcp             foodtrucks-web
2c0b96f9b803        elasticsearch                "/docker-entrypoint.s"   21 minutes ago      Up 21 minutes       0.0.0.0:9200->9200/tcp, 9300/tcp   es

$ curl -I 0.0.0.0:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 3697
Server: Werkzeug/0.11.2 Python/2.7.6
Date: Sun, 10 Jan 2016 23:58:53 GMT

进入http://0.0.0.0:5000享受一个你的应用吧。这个过程虽然看起来很恐怖,实际只有四条命令。原作者将其写入一个bash script

#!/bin/bash

# build the flask container
docker build -t prakhar1989/foodtrucks-web .

# create the network
docker network create foodtrucks

# start the ES container
docker run -d --net foodtrucks -p 9200:9200 -p 9300:9300 --name es elasticsearch

# start the flask app container
docker run -d --net foodtrucks -p 5000:5000 --name foodtrucks-web prakhar1989/foodtrucks-web

下次想运行这个应用,只要这样既可:

$ git clone https://github.com/prakhar1989/FoodTrucks
$ cd FoodTrucks
$ ./setup-docker.sh

完!很清爽吧!

还有更多docker内容,可以访问原文教程

本人也正在学习,以后可能会有更新


上一篇 1. Docker初步

下一篇 1. 爬虫准备

Comments

你可以请我喝喝茶,聊聊天,鼓励我

Wechat Pay
wechat

Thanks!