这个《Docker入门系列》文档,是根据Docker官网(https://docs.docker.com)的帮助文档大致翻译而成。主要是作为个人学习记录。有错误的地方,Robin欢迎大家指正。分为如下几个部分:

1 Docker入门:简介

2 Docker入门:安装运行

3 Docker入门:容器(Containers)

4 Docker入门:服务(Services)

5 Docker入门:Swarms

6 Docker入门:Stacks

7 Docker入门:部署app

8 Docker入门:稍高级的话题

3.1 引言

现在我们采用Docker方式来构建一个app。这种app分为三层,从低至上分别为container、service、stack:

stack

service

container

本章介绍最低层:container。再上一层是service,用于描述产品中container的行为方式,在第四章介绍。最上层是stack,定义了所有services的交互方式,在第六章介绍。

3.2 新的开发环境

以前你想着手写一个Python app,首先要做的就是在你的机器上安装Python运行时。这就在你的机器上创建了一个环境,从而使得你的app像期望中一样的运行;如果在服务器上运行你的app,也需要做同样的事情。

使用Docker后,你只需要将Python运行时作为一个映像,而不再需要安装。于是,你所构建的程序包括基本的Python映像和你的app代码。保证你的app及其依赖,以及运行时,在传送时一个都不能落下。

这种便携式的映像是由Dockerfile来定义的。

3.3 使用Dockerfile来定义容器

Dockerfile会在容器里定义一个环境,这个环境包括所有要做的事情。在这个环境里,一些资源访问(如网络接口,磁盘访问)是虚拟化的,从而和其余的系统隔离开来。因此,你可以将端口映射到外面的世界,也可以限定将哪些文件拷贝到那个环境中。于是,用Dockerfile构建的app无论在哪里运行,其行为表现都是一样的。

下面来写一个Dockerfile文件。

首先创建一个空目录,并cd到该目录下,然后在该目录下创建文件Dockerfile,并将下面的内容拷贝到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 -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"]

警告:你是否使用了代理服务器?当代理服务器运行时,它会阻止到web app的网络连接。解决这个问题,需要在Dockerfile中加入如下代码,使用ENV命令来为代理服务器设定主机IP和端口号:

# Set proxy server, replace host:/port with values for your servers

ENV http_proxy host:/port

ENV https_proxy host:/port

上面的Dockerfile中引用了几个还没有创建的文件:app.py和requirements.txt,下面我们来创建它们。

3.4 app程序

在Dockerfile文件的同一目录下,创建requirements.txt和app.py两个文件。这样整个app就完成了,很简单吧。当用这个Dockerfile构建映像时,app.py和requirements.txt也都有了,这是因为Dockerfile中的ADD命令起了作用;同样,由于命令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 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的Python库,这个app会打印环境变量NAME,并输出socket.gethostname()的调用结果。因为Redis并没有运行(因为我们只是安装了其Python库,而不是Redis本身),这么使用最终会失败并打印出错误信息。

注意:在容器中访问host的名称将会得到一个容器ID,相等于主机环境下可执行程序的进程ID。

这就OK了。你的系统里并不需要Python或者requirements.txt中的任何东西,构建或运行这个映像也不会将这些东西安装到你的系统。看起来并没有安装Python和Flask的环境,但是这个环境你有了。

3.5 构建这个app

准备构建(build)这个app。确保你还在刚创建的目录下,确认一下:

$ ls

Dockerfile                     app.py                         requirements.txt

没问题。接下来运行构建命令。这个命令将会创建一个Docker映像,在命令中使用-t来指定一个友好的名字:

docker build -t friendlyhello .

可以通过如下命令列出本地Docker映像:

[root@localhost docker]# docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

friendlyhello       latest              4005a12618da        17 minutes ago      195MB

python              2.7-slim            9724e90f1f17        6 days ago          184MB

hello-world         latest              05a3bd381fc2        4 weeks ago         1.84kB

也可以使用新的命令docker images ls来列出映像。

3.6 运行这个app

运行这个app,并通过-p将机器的端口4000映射到容器发布的端口80上:

[root@localhost docker]# docker run -p 4000:80 friendlyhello

* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

可以看到一个提示,friendlyhello在http://0.0.0.0:80上提供服务。这个信息来自于容器内部,容器并不知道你将容器的端口80映射到4000了,使用正确的URL:http://localhost:4000

在浏览器里运行这个URL,可以看到如下页面:

192.168.137.100是虚拟机的IP,这是在宿主主机上运行的浏览器。

注意:这里的端口重映射4000:80是为了演示两个端口的区别,一个是Dockerfile中EXPOSE的端口,另一个是docker run –p所发布的端口。后面我们只将主机的端口80映射到容器中的端口80,所以使用http://localhost就可以了。

在终端里使用CTRL+C来结束容器运行。

下面使用detached mode,在后台运行这个app:

[root@localhost docker]# docker run -d -p 4000:80 friendlyhello

59dfe83b4b1264f2d04be89f3e2a9a02e54fcac3b169a45e721dd81d6429cdbd

运行后,可以看到这个app的容器ID,然后就在后台运行了。使用命令docker container ls可以查看容器:

[root@localhost /]# docker container ls

CONTAINER ID  IMAGE        COMMAND         CREATED    STATUS      PORTS           NAMES

46f21d48ba94 friendlyhello "python app.py" 42 minutes ago Up42minutes 0.0.0.0:4000->80/tcp   relaxed_raman

可以看到,容器ID已经简化了,并且这个ID和http://localhost:4000保持一致。

使用docker container stop来终止容器进程,命令中包括容器ID:

docker container stop 46f21d48ba94

3.7 分享映像

为了展示便携性,我们来上传构建的映像并在其他地方运行。如果你想在产品中部署容器,后面还需要学习怎样把映像推送到registry中。

一个registry就是多个repository(仓库)的集合;一个repository就是多个映像的集合。Registry的一个帐号可以创建许多repository。Docker CLI默认使用Docker的public registry。

注意:这里我们将使用Docker的public registry,因为它是免费的,并且是已经配置好的。也可以选择其他的public registry,当然,你也可以自己搭建个人的registry(使用Docker Trusted Registry)。

3.7.1 使用Docker ID登录

如果你没有Docker帐号,到cloud.docker.com上注册一个。记住用户名和密码。然后在你的本地机器上登录到public registry:

docker login

3.7.2 给映像加上标签

通过标识username/repository:tag,将一个本地映像和registry中的一个映像关联起来。tag是可选的,但是强烈推荐加上tag,因为registry使用这个机制,来给Docker映像提供版本号。提供registry和tag,例如:get-started:part2,将会把这个映像放到get-started repository中,并加上标签part2。

运行命令docker tag image,并提供用户名,repository和tag名称,然后映像就会被上传到相应的目标中。这个命令的语法是:

docker tag image username/repository:tag

例如:

docker tag friendlyhello robin/get-started:part2

运行命令docker image来查看加了标签的映像:

[root@localhost /]# docker images

REPOSITORY                  TAG           IMAGE ID            CREATED             SIZE

friendlyhello               latest        4005a12618da        25 hours ago        195MB

robin/get-started   part2         4005a12618da        25 hours ago        195MB

python                      2.7-slim      9724e90f1f17        7 days ago          184MB

hello-world                 latest        05a3bd381fc2        4 weeks ago         1.84kB

3.7.3 发布映像

使用如下命令,就可以将加了标签的映像上传到repository中:

docker push username/repository:tag

运行命令如下:

[root@localhost /]# docker push robin/get-started:part2

The push refers to a repository [docker.io/robin/get-started]

02bdd180d5f0: Pushed

bf76f22d8e58: Pushed

e82724fbda2b: Pushed

f39f8a7b1485: Mounted from library/python

5e4d4a29edae: Mounted from library/python

f46f014db30a: Mounted from library/python

c01c63c6823d: Mounted from library/python

part2: digest: sha256:5fb9a92175f6918c8baf55ef5248e9e234e1f9cc0ccb683b8d0bf49da4286724 size: 1787

一旦命令运行完成,这个映像就公开可用了。如果你登录到Docker Hub,就可以看到新提交的映像,以及相应的拉取命令。

3.7.4 从远程repository中拉取并运行映像

现在,可以在任何机器上使用如下命令来运行你的app:

docker run -p 4000:80 username/repository:tag

如果本地机器没有这个映像,Docker将会从repository中拉取。

$ docker run -p 4000:80 robin/get-started:part2

Unable to find image robin/get-started:part2' locally

part2: Pulling from robin/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 robin/get-started:part2

* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

注意:无论你是在构建或者运行映像时,如果你没有在命令中给出:tag部分,假定的tag是:latest。如果没有标注tag,Docker将会使用映像的最新版本。

无论在哪里执行docker run,它将会拉取你的映像、Python以及requirements.txt中的所有依赖,并运行你的代码。所有这些东西作为一个整体一起传递,并且主机系统除了安装Docker之外,不需要安装任何其他东西。

3.8 总结

下一节,我们通过在service中运行这个容器,来学习如何使我们的程序具有可伸缩性。

3.9 本节命令列表

下面是本节的基本Docker命令,有些命令后面的部分也会用到。

docker build -t friendlyname .  # Create image using this directory's Dockerfile

docker run -p 4000:80 friendlyname  # Run "friendlyname" mapping port 4000 to 80

docker run -d -p 4000:80 friendlyname         # Same thing, but in detached mode

docker container ls                                # List all running containers

docker container ls -a             # List all containers, even those not running

docker container stop <hash>           # Gracefully stop the specified container

docker container kill <hash>         # Force shutdown of the specified container

docker container rm <hash>        # Remove specified container from this machine

docker container rm $(docker container ls -a -q)         # Remove all containers

docker image ls -a                             # List all images on this machine

docker image rm <image id>            # Remove specified image from this machine

docker image rm $(docker image ls -a -q)   # Remove all images from this machine

docker login             # Log in this CLI session using your Docker credentials

docker tag <image> username/repository:tag  # Tag <image> for upload to registry

docker push username/repository:tag            # Upload tagged image to registry

docker run username/repository:tag                   # Run image from a registry

Docker入门(三):容器(Containers)的更多相关文章

  1. docker入门——管理容器

    除了交互式的容器(interactive container),我们也可以创建长期运行的容器.守护式容器(daemonized container)没有交互式会话,非常适合运行应用程序和服务.大多数时 ...

  2. docker 入门2 - 容器 【翻译】

    入门,第 2 部分:容器 先决条件 安装的 Docker 版本是 1.13 及以上. 读完 第一部分 用下面的命令快速测试你的环境是否完备: docker run hello-world 概述 现在开 ...

  3. Docker入门之三容器

    上一篇博客学习了下镜像,今天来学习容器.容器类似一个手机中的沙盒环境,用来运行app实例.和镜像一样也是对容器的创建.删除.导出等. 由于我买的参考书中的例子好多都是基于linux的,所以我将dock ...

  4. docker入门(二)容器与镜像的理解

    10张图带你深入理解Docker容器和镜像 申明:此篇文章是转载的(原文地址http://dockone.io/article/783),今天意外发现已经有人转载了(复制了),希望大家关注原创 原本打 ...

  5. docker入门(二)容器与镜像的关系

    [编者的话]本文用图文并茂的方式介绍了容器.镜像的区别和Docker每个命令后面的技术细节,能够很好的帮助读者深入理解Docker. 这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(co ...

  6. Docker 入门:容器

    容器看着像机器,实际是进程,是一个运行时程序. 要操作一个 Docker 容器,只需要执行 docker container 命令. 可以通过 help 查看 run 运行容器 基础使用: docke ...

  7. Docker入门(七):部署app

    这个<Docker入门系列>文档,是根据Docker官网(https://docs.docker.com)的帮助文档大致翻译而成.主要是作为个人学习记录.有错误的地方,Robin欢迎大家指 ...

  8. Docker入门(六):Stacks

    这个<Docker入门系列>文档,是根据Docker官网(https://docs.docker.com)的帮助文档大致翻译而成.主要是作为个人学习记录.有错误的地方,Robin欢迎大家指 ...

  9. Docker入门(五):Swarms

    这个<Docker入门系列>文档,是根据Docker官网(https://docs.docker.com)的帮助文档大致翻译而成.主要是作为个人学习记录.有错误的地方,Robin欢迎大家指 ...

随机推荐

  1. MySQL 原理性

    1.MySQL的复制原理以及流程 (1).复制基本原理流程 1. 主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中: 2. 从:io线程——在使用start ...

  2. 05 redis中的Setbit位图法统计活跃用户

    一:场景=>>>长轮询Ajax,在线聊天时,能够用到 Setbit 的实际应用 场景: 1亿个用户, 每个用户 登陆/做任意操作 ,记为 今天活跃,否则记为不活跃 每周评出: 有奖活 ...

  3. wpf 获取datagrid 模板列中的控件

    目前采用的 方法  (网上提供的一款) public static DataGridRow GetRow(DataGrid datagrid, int columnIndex)        {    ...

  4. 转载 -- 基于原生JS与OC方法互相调用并传值(附HTML代码)

    最近项目里面有有个商品活动界面,要与web端传值,将用户在网页点击的商品id 传给客户端,也就是js交互,其实再说明白一点就是方法的互相调用而已. 本文叙述下如何进行原生的JavaScript交互 本 ...

  5. 真正入坑git

    之前使用git一直用sourceTree可视化操作,直到今天刚好装不了sourceTree,所有只能苦逼的用git命令行了,真的一片空白,要做下笔记才行. 创建sshKey 创建ssh: ssh-ke ...

  6. Hihocoder #1602 : 本质不同的回文子串的数量 manacher + BKDRhash

    #1602 : 本质不同的回文子串的数量 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定一个字符串S,请统计S的所有子串中,有多少个本质不同的回文字符串? 注意如果 ...

  7. MVC教程--MiniProfiler.EF监控调试MVC和EF的性能

    上一篇谈到mvc中ef输出执行sql日志:来谈用mvc开发项目的调试和性能监控.EF框架自动给我生成sql语句,当我们的程序遇到性能问题的时候我们可以用MiniProfiler.EF来监控调试MVC和 ...

  8. 九度OJ 1154:Jungle Roads(丛林路径) (最小生成树)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:832 解决:555 题目描述: The Head Elder of the tropical island of Lagrishan has ...

  9. 九度OJ 1035:找出直系亲属 (二叉树、递归)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:2380 解决:934 题目描述:     如果A,B是C的父母亲,则A,B是C的parent,C是A,B的child,如果A,B是C的(外) ...

  10. js版本的汉字转拼音

    var PinYin = {"a":"\u554a\u963f\u9515","ai":"\u57c3\u6328\u54ce\u ...