** 11. Docker Compose 声明式环境
*** 1. 使用 Docker Compse    
    1. [ ] Docker Compose:第一天的启动并运行
       1. Compose 是一个用于定义,启动和管理服务的工具,其中一个服务可以定义为
          Docker 容器的一个或多个副本。在 YAML 文件(http://yaml.org)中定义了服
          务和服务系统,并通过命令行 docker-compose 进行管理。有了 Compose,可以
          使用简单的命令来完成下面的这些任务。
          1. 构建 Docker 镜像
          2. 启动容器化的应用及服务
          3. 启动完整的服务系统
          4. 管理系统中单个服务的状态
          5. 服务伸缩
          6. 查看生成服务的容器的收集日志

       2. 用一个简单的开发环境入门 
          安装 Docker 
          安装 Docker Compose
          安装和使用 Git 来克隆开发环境
          
          #Filename: docker-compose.yml 
          wordpress:                     #定义命名为 wordpress 服务
            image: wordpress:4.2.2  #使用官方 wordpress4.2.2 镜像
            links:
          - db:mysql #链接依赖到 db 服务上
          ports:
          - 8080:80 

          db:   # 定义命名为 db 的服务
            image: mariadb   #使用官方的 mariadb:latest 镜像
            environment:
              MYSQL_ROOT_PASSWORD: example 通过环境变量设置数据库系统管理员密码
         
       3. docker -compose 命令列出的容器值包括那些在当前目录下
          docker-compose.yml 文件中定义的容器,这种方式更加优雅和简洁。 

          Compose 有个 rm 命令,非常类似于 docker rm 命令,区别在于
          docker-compose rm 命令会删除所有的服务或者一个有环境定义的特定服务。另
          一个小的区别是, -f 选项并不强迫 删除正在运行的容器,相反,它会关闭用
          户确认阶段。
          所以,清理的第一步是关闭环境,你可以使用 docker-compose stop 或者
          docker-compose kill 命令达到此目的。使用 stop 优先于 kill。就像其他的
          Compose 命令,这两个命令可以传入一个服务名字作为要关闭的目标。一旦你停
          止了服务,需要使用 docker-compose rm  命令来完成清理工作。如果你省略
          -v 选项,卷有可能成为孤立的:
          docker-compose rm -v 
          
          Compose 会显示将被你删除的容器列表 ,并提示你确认,按【Y】键继续。
    
    2. [ ] 一个复杂的架构:分布式系统和 Elasticsearch 的集成 
       git clone https://github.com/dockerinaction/ch11_notifications.git
       cd ch11_notifications
       docker-compose up -d
       -d 选项表示在 datached 模式下启动容器,它的作用于用 -d 选项运行 docker
       run 命令完全一样。当容器分离时,每个容器输出的日志将不会流向终端
       
       查看所有服务的日志 docker-compose logs 
       如果想查看一个或多个服务,需要给出具体的服务名
       docker-compose logs pump elasticsearch: 
       
       假设你有另一个想绑定在 3000 端口的服务,这与本示例的 calaca 服务冲突。改
       变非常简单,只要改变 ch11notifications/docker-compose.yml 文件,并再次运
       行 docker-compose up 命  
       
       registry:
         build: ./registry 
         ports:
           - "5555:5000"
         links:
           - pump:webhookmonitor
       pump:
         build:  ./pump 
         expose:
           - "8000"
         links:
           - elasticsearch:esnode 
       elasticsearch:  
         image: elasticsearch:1.6 
         ports:      
           - "9200:9200"
       calaca:   
         build: ./calaca    
         ports:
           - "3000:3000"  #改变端口 "3001:3000"  

       保存文件,再次运行 docker-compose up -d 命令重新构建环境。当你执行这个命
       令后,它将停止当前并删除运行的容器,接着创建新的容器,并重新附着前代环境
       挂载的数据卷,在可能的情况下,Compose 将限制那些发生更改的已重启的容器的
       范围。
   
       如果你的服务源代码发生了变化,可以用一个命令重建一个或所有服务,运行如下
       命令:
       docker-compose build 

       如果重建一个服务或服务的一些子集,然后简单地命名为服务。这个命令将重建
       calaca 和 pump 服务 
       docker-compose build calaca pump 
       
       此时此刻,停止和删除你为这些服务创建的容器 
       docker-compose rm -vf 
       
*** 2. 操作环境和项目迭代
    1. [ ] 环境内的迭代
       git clone https://github.com/dockerinaction/ch11_coffee_api.git
data:
  image: gliderlabs/alpine
  command: echo Data Container
  user: 999:999
  labels:
    com.dockerinaction.chapter: "11"
    com.dockerinaction.example: "Coffee API"
    com.dockerinaction.role: "Volume Container"

dbstate:
  extends:
    file: docker-compose.yml
    service: data
  volumes:
    - /var/lib/postgresql/data/pgdata

# the postgres image uses gosu to change to the postgres user
db:
  image: postgres
  volumes_from:
    - dbstate
  environment:
    - PGDATA=/var/lib/postgresql/data/pgdata
    - POSTGRES_PASSWORD=development
  labels:
    com.dockerinaction.chapter: "11"
    com.dockerinaction.example: "Coffee API"
    com.dockerinaction.role: "Database"

# the nginx image uses user de-escalation to change to the nginx user
proxy:
  image: nginx
  restart: always
  volumes:
    - ./proxy/app.conf:/etc/nginx/conf.d/app.conf
  ports:
    - "8080:8080"
  links:
    - coffee
  labels:
    com.dockerinaction.chapter: "11"
    com.dockerinaction.example: "Coffee API"
    com.dockerinaction.role: "Load Balancer"

coffee:
  build: ./coffee
  user: 777:777
  restart: always
  expose:
    - 3000
  ports:
    - "0:3000"
  links:
    - db:db
  environment:
    - COFFEEFINDER_DB_URI=postgresql://postgres:development@db:5432/postgres
    - COFFEEFINDER_CONFIG=development
    - SERVICE_NAME=coffee
  labels:
    com.dockerinaction.chapter: "11"
    com.dockerinaction.example: "Coffee API"
    com.dockerinaction.role: "Application Logic"

    docker-compose pull 拉取镜像
    
    docker-compose up -d db  启动 db service 
    因为 db 依赖 dbstate, 所以会先启动柜 dbstate
    
    当使用一个未经限定的 docker-compose up 命令时, Compose 将创建或重新创建环境
    中的每一个服务并启动所有的服务,如果 Compose  检测到有任何还没构建或者使用缺
    失镜像的服务,它会触发一个构建或获取合适的镜像(docker run 命令)

    可以不要依赖关系就可以启动或重新启动一个服务。 (docker-compose.yml),想重新
    启动代理。你可能只是需要运行如下命令即可:
    docker-compose up --no-dep -d proxy
    
    如果省略了flag--no-dep ,那么每个服务都会被重新创建并重启,因为 proxy 服务
    要么直接依赖环境中的这些服务,要么是传递依赖的。
    当你启动一个其中的组件,需要长时间启动过程的系统并且经历竞争条件时,
    flag--no-dep 就可以派上用场了。在这些情况下,你可能会在启动剩下的服务之前,
    先启动那些服务让它们完成初始化。
    
    docker-compose build coffee 
    docker-compose up -d 
    第一个命令将 Coffe API 再次运行一个 docker build 命令,生成一个更新后的镜像。
    第二个命令将会重新创建环境。不必担心你创建的咖啡店数据。用于存储数据库的管理
    卷将会无缝地分离和附着到新的数据库容器上。

    2. [ ] 服务伸缩和删除 
       compose 命令应该在你的 docker-compose.yml 文件所在的目录执行。如果环境没
       有处于运行状态(proxy,coffee和 db 服务运行),那么用 docker-compose up
       -d 命令来运行
       
       docker-compose ps coffee 
       
       使用以下的命令扩展 coffee 服务:
       docker-compose scale coffee=5 
       docker-compose ps 命令查看,发现有5个容器运行 coffee 服务
       
       docker-compose scale coffee=1 
       发现 coffee 服务缩减到一个

       docker-compose rm 清理环境
       
    3. [ ] 迭代和持久化状态
       1. Compose 使得在迭代环境中管理卷的工作变得微不足道了。当服务重新构建时,
          附加的管理卷不会被删除。相反,它们重新附加到了那个服务更换后的容器上。
          这意味着你可以自由地迭代而不用担心丢失数据。当最后一个容器使用
          docker-compose rm 命令和 flg -v 删除时,管理卷最后也会被清理。
          
          与状态管理和 Compose 相关的最大的问题还是环境状态,在高数迭代的环境中
          你会变更一些东西,包括环境配置,某些类型的变更会带来一些问题。
          如果你在 docker-compose.yml 文件中重命名或删除一个服务定义,那么你就失
          去了使用 Compose 管理这个服务的能力。回到 Coffee API 示例中,coffee 服
          务在开发时被重命名为 coffee。当这种情况发送时,Compose 将不再感知到
          api 服务。重新构建启动后,新的 coffee 服务将会工作,而 api 服务被孤立。
          恢复很简单,你可以直接使用 docker 命令清理环境或者回到
          docker-compose.yml 文件中添加孤立的服务定义,用 Compose 清理。
          
    4. [ ] 网络和连接问题
       Docker 构建的镜像是通过创建防火墙规则和注入服务发现信息到所依赖的容器的环
       境变量和 /etc/hosts 文件中来建立连接关系的。
       
*** 3. 扩展服务和清理
    1. [ ] 开始一个新项目:三个示例中的 Compose YAML 

       1. [ ]  启动前的 构建,环境,元数据和网络
coffee:
  build: ./coffee   #1. 从位于 ./coffee 的 Dockefile 构建
  user: 777:777
  restart: always
  expose:           #
    - 3000          #
  ports:            #
    - "0:3000"      #4. 公开和映射容器端口
  links:
    - db:db
  environment:      # 2.为数据库设置环境变量
    - COFFEEFINDER_DB_URI=postgresql://postgres:development@db:5432/postgres
    - COFFEEFINDER_CONFIG=development
    - SERVICE_NAME=coffee
  labels:           #3. 为服务打上标签
    com.dockerinaction.chapter: "11"
    com.dockerinaction.example: "Coffee API"
    com.dockerinaction.role: "Application Logic"

    build 键的值是用于构建的 Dockerfile 文件所在位置的目录,你可以使用从 YAML 文
    件位置开始的相对路径,你还可以使用 dockerfile 键来提供一个备选的 Dockefile
    文件的名称
    
    environment 键可以为服务设置环境变量,environment 键的值为内嵌列表或者字典,
    在 2 中使用的是列表方式。或者可以提供一个或多个包含 env_file 作为键的环境变
    量定义的文件,与环境变量相似,容器元数据可以设置为以 labels 为键,以内嵌列表
    为或者字典表为值,3中使用的是字典形式。
    
       2. [ ] 已知的组件和绑定挂载卷
db:
  image: postgres@sha256:66ba100bc635bel7....      #为授信的 Postgres 版本使用内 容可寻址镜像
  volumes_from:
    - dbstate   #使用数据容器模式
  environment:
    - PGDATA=/var/lib/postgresql/data/pgdata
    - POSTGRES_PASSWORD=development
  labels:
    com.dockerinaction.chapter: "11"
    com.dockerinaction.example: "Coffee API"
    com.dockerinaction.role: "Database"

# the nginx image uses user de-escalation to change to the nginx user
proxy:
  image: nginx@sha256:a2b8bef333864317...     #为授信的 NGINX 版本使用内容可寻址镜像
  restart: always
  volumes:
    - ./proxy/app.conf:/etc/nginx/conf.d/app.conf  #通过卷注入配置
  ports:
    - "8080:8080"
  links:
    - coffee
  labels:
    com.dockerinaction.chapter: "11"
    com.dockerinaction.example: "Coffee API"
    com.dockerinaction.role: "Load Balancer"

    proxy 服务使用一个卷绑定挂载一个本地的配置文件到 Nginx 的动态配置位置,这是
    一个简单的注入配置而不用构建一个完整的新镜像
    db 服务使用 volumes_from 键来列出那些定义了必需卷的服务,在本示例中,db 在
    dbstate 服务上 声明了一个依赖关系,dbstate 服务是一个卷容器服务
    一般来说,YAML 中的键是与 docker run 命令已公开的功能 密切相关

      3. [ ] 卷容器和扩展服务 
         data:
           image: gliderlabs/alpine
           command: echo Data Container
           user: 999:999
           labels:
             com.dockerinaction.chapter: "11"
             com.dockerinaction.example: "Coffee API"
             com.dockerinaction.role: "Volume Container"
             
         单独的服务除了定义 卷容器的合理默认值外,其他什么都没有做。注意,它不定
         义任何卷,专门化工作是留给每个扩展自原型的 卷容器的。
         
         dbstate:
           extends:                        #
             file: docker-compose.yml      #引用到另外一个文件中的父服务
             service: data                 # 
           volumes:
             - /var/lib/postgresql/data/pgdata
           dbstate 服务定义了一个扩展自 data 服务的卷容器,服务扩展必须指定文件
         和被扩展的服务名称,相关的键是 extends 以及内嵌的 file 和 service。服务
         扩展工作类似于 Dockefile 构建的方式。首先构建原型容器,然后提交。子容器
         是一个由新生成的层构建的新容器。就像 Dockefile 构建,这些子容器继承了父
         容器所有的属性,包括元数据。
          dbstate 服务使用 volumes 键定义了挂载在
         /var/lib/postgresql/data/pgdata 的管理卷,volumes 键接受一系列由 flag
         docker run -v 所允许的卷规范。

*** 4. 构建声明式环境
*** 5. 小结
    1. Docker Compose 是一个用于定义, 启动和管理服务的工具,其中的服务被定义为
       Docker 容器的一个或多个副本
    2. Compose 使用 YAML 配置文件来提供环境定义
    3. 使用 docker-compose 命令行程序,你可以构建镜像,启动和管理服务, 扩展服务并
       在任何运行 Docker Daemon 的主机上查看日志
    4. 在项目中管理环境和迭代的 Compose 命令,类似于 docker 命令行。构建,启动,停
       止,删除和列出服务,所有这些都相当于单个容器关注的那些。
    5. 有了 Docker Compose ,你可以通过运行一个 up 和 down 命令来伸缩容器数目
    6. 借助于 YAML 格式的声明式环境配置可以启用环境版本控制,共享,迭代和一致性。

** 12. Docker Machine 和 Swarm 集群
*** 1. 创建虚拟机,以 Docker Machine 运行 Docker 
    1. [ ] 介绍 Docker Machine
       1. 构建和管理 Docker Machine
          docker-machine help
          docker-machine create --driver virtualbox host1
          docker-machine create --driver virtualbox host2
          docker-machine create --driver virtualbox host3

          docker-machine ls 
          
          任何用 docker 或者 docker-compose 命令行接口发布的命令都将会连接到活跃
          机器上的 Docker Daemon
          
          docker-machine inspect host1
          docker-machine inspect --format "{{.Driver.IPAddress}}" host1
          
          docker-machine ip host1 
          
          docker-machine upgrade host3
          
          docker-machine ssh host1 #绑定你的终端到 host1 的 shell 上
          touch dog.file 
          exit  #退出远程 shell 并终止命令
          
          指定参数运行 ssh 命令 
          docker-machine ssh host1 "echo spot > dog.file"
          
          docker-machine scp host1:dog.file host2:dog.file 
          docker-machine ssh host2 "cat dog.file"
          
          用于启动,停止(或杀死)和删除机器的命令就像用于容器的那些命令,
          docker-machine 命令提供了四个命令:start,stop, kill 和 rm 
          
          docker-machine stop host2 
          docker-machine kill host3
          docker-machine start host2 
          docker-machine rm host1 host2 host3 
          
       2. 配置 Docker 客户端与远程 Daemon 工作
          docker 命令行接口或者 docker-compose 都被设计为每次只能连接单个 Docker
          主机。出于这个原因,Docker Machine 的一个最重要的功能是为一台活跃的
          Docker 主机生成环境配置
          
          create 命令 
          docker-machine create --driver virtualbox machine1
          docker-machine create --driver virtualbox machine2

          env 子命令检测用户的 shell 并打印命令来配置环境连接到一个特定的机器
          docker-machine env machine1 
          docker-machine env --shell powershell machine1 # 获得 PowerShell 配置
          docker-machine env --shell cmd machine1  # 获得 CMD 配置
          docker-machine env --shell fish machine1 # 获得 fish 配置
          docker-machine env --shell bash machine1 #获得默认(POSIX)配置
          
          例如设置 machine1 作为活跃的机器,你可以在 POSIX Shell 中执行
          docker-machine env 命令 
          eval "$(docker-machine env machine1)"
          
          如果你使用 Windows 并运行 PowerShell 运行,你可以运行一个如下的命令 
          docker-machine env --shell=powershell machine1 | Invoke-Expression 
          
          查看是否激活了 machine1 
          docker-machine active 
          docker-machine ls 

          拉取一个镜像到活跃机器
          docker pull dockerinaction/ch12_painted 
          
          eval "$(docker-machine env machine1)" #用适合的等效命令替代
          docker images 
          
          images 子命令的输出应该是空的,这个示例有助于说明独立地拉取镜像到每一
          台机器的必要性,这台机器 machine2 ,从来没有安装过任何镜像

          在 machine2机器上拉取镜像,并运行容器 dockerinaction/ch12painted
          docker run -t dockerinaction/ch12_painted \
          Tiny turtles tenderly touch tough turnips
          
          现在比较 machine1 和 machine2 上的容器列表
          docker ps -a 
          eval "$(docker-machine env machine1)" #用适合的等效命令替代
          docker ps -a 
          
          列表再次为空,因为 machine1 机器没有创建容器,

          清理删除 machine1 和 machine2 
          docker-machine rm machine1 machine2
          
          Docker Machine 是一个构建和管理基于 Docker 的集群机器的很棒的工具。
          Docker Compose 为基于容器的服务提供了编排,不过依然存在存在的主要问题
          是跨 Docker Machine 集群调度容器,但后来发现那些服务其实已经被部署在某
          些地方了。Docker Swarm 将会解决这个问题。

*** 2. 集成并管理远程 Dokcer Daemon
*** 3. Docker Swarm 集群介绍
    1. [ ] 借助于 Docker Machine 构建 Swarm 集群
       Swarm 集群是由两种类型的机器组成的,以管理模式运行 Swarm 的机器称为
       manager,而运行 Swarm 代理的机器称为 node

    2. Docker Machine 可以生成 Swarm 集群,就像操作独立的 Docker Manchine 一样容
       易,唯一的区别是有一小组额外的命令行参数,这些参数包含在了 create 子命令
       里。
       第一个参数是 --swarm, 表明正在创建的机器应该运行 Swarm 代理软件并加入一个
       集群。第二个是使用参数 --swarm-master,将会指示 Docker Machine 配置型的机
       器作为 Swarm manager。第三个是 Swarm 集群的每一种机器类型都需要一种定位和
       识别其加入或者管理的集群的方法,--swarm-discovery 以一个额外的参数指定集
       群的唯一标识符。

    3. 创建自己的 Swarm 集群的第一步是创建一个集群标识符,类似于大多数的扩展自
       Docker 的子系统, Swarm 发现子系统可以通过改变来适应你的环境。在默认情况
       下 ,Swarm 使用 Docker Hub 提供的一个免费托管解决方案,运行以下命令创建一
       个新的集群标识符。

       1. 创建一个新的本地 Docker 
          docker-machine create --driver virtualbox local
          eval "$(docker-machine env local)"  #用适合你的 shell 的等效命令取代
          
          docker run --rm swarm create 
          最后一个命令应该输出一个十六进制的标识符,(Token)
          
          docker-machine create \
              --driver virtualbox \
              --swarm \
              --swarm-discovery token://b866583a3b8791bcd705b7bc0fd94c66b695a1a2dbaeb5f59ed29940e5015dc8 --swarm-master machine0-manager 
              
          docker-machine create \
             --driver virtualbox \
             --swarm \
             --swarm-discovery  token://b866583a3b8791bcd705b7bc0fd94c66b695a1a2dbaeb5f59ed29940e5015dc8 \
             machine1

          docker-machine create \
             --driver virtualbox \
             --swarm \
             --swarm-discovery token://b866583a3b8791bcd705b7bc0fd94c66b695a1a2dbaeb5f59ed29940e5015dc8 \
             machine2
              
    4. Swarm 扩展了 Docker 远程 API 
       如果使用兼容 POSIX 的 shell,运行以下命令
        eval "$(docker-machine env --swarm machine0-manager)"

       如果你使用 PowerShell,运行如下: 
       docke-machine env --swarm machine0-master | Inovke-Expression 
       
       当你的环境被配置为访问一个 Swarm 端点时, docker 命令行接口将使用 Swarm 功
       能特性。例如使用 docke info 命令将会报告整个集群的信息而不是某个特定
       Docker Daemon 的细节
       
       docker info 
       
       接下来在集群中创建一个容器
       docker run -t -d --name hello-swarm dockerinaction/ch12_painted  Hello Swarm 
          
       docker logs hello-swarm 
       
       docker ps -a -f name=hello-swarm 
       注意 NAMES 列中容器名字是以你的集群中的一台机器名字作为前缀的,这可以是任
       何节点。如果你创建了一个类似的但有一个略显不同的名字,就像 hello-world2
       的容器,集群可能会在不同的主机上调度该容器。不过,在这样做之前,花一点时
       间再次检查集群消息。
       docker info 
       
       注意集群中的容器和镜像数量
       Containers: 5 
       Imagees: 4 
       Container 和 Images 的数量对于集群是不太清晰的总和,因为在集群中有三个节
       点,你需要三个代理容器和一个 manager 容器,剩下的容器是你刚刚创建的
       hello-swarm 容器。这四个镜像是三个 swarm 镜像的副本和一个
       dockerinaction/ch12_painted 镜像的副本。这里注意的一点是,当你用 run 命令
       创建一个容器时,所需的镜像将只会被拉取到被调度的容器所在的主机上。这就是
       为什么只有一个镜像的副本,而不是三个。
       
       如果你想确保你使用的镜像被拉取到了集群中的每一台机器上,你可以使用 pull
       子命令,如果容器被调度为在一个没有所需镜像的节点上运行的话,这样做将消除
       任何热身延迟。
       
       docker pull dockerinaction/ch12_painted 
       这个命令将在每一个节点上启动拉取操作
       machine0-manager: Pulling dockerinaction/ch12_painted:latest....:downloaded
       machine1:Pulling...
       machine2:Pulling... 

       同样,删除容器将会在任何一台机器上删除指定的容器,而删除镜像将会从集群中
       的所有节删除该镜像。Swarm 对于用户隐藏了所有这些复杂的细节,这样做使得用
       户可以解决更有趣的问题。

*** 4. 借助于 Docker Machine 提供完整的 Swarm 集群
*** 5. 在集群中管理容器
*** 6. 有关容器调度和服务发现的 Swarm 解决方案
    1. [ ] Swarm  调度 
       Swarm 提供了三种不同的调度算法,每一个都有自己的优势和不足。当创建 Swarm
       manager 时就会设置调度算法,用户可以对于一个给定的 Swarm 集群通过特定的容
       器提供约束来调整调度算法。
       
       可以为每个容器设置约束,但是因为调度算法是在 Swarm manager 上设置的,所以
       创建集群时,你需要指定这些设置。Dokcer Machine 的 create 子命令为这一目的
       提供了  --swarm -strategy 参数,默认选择是 spread。

    2. Spread 算法
       在 flock.yml 文件中创建一个新的 Compose 环境描述,并定义一个名为 bird 的
       服务,该服务定期地按照标准输出绘制 bird:
       bird:
         image: dockerinaction/ch12_painted 
         command: bird 
         restart: always
       
      使用 Compose 向上扩展时,观察 Swarm 跨集群地分发了 10 个 bird 服务的副本:
      docker-compose -f flock.yml scale bird=10   
      docker ps #检查容器分发

      清理容器
      docker-compose -f flock.yml kill 
      docker-compose -f flock.yml rm -vf

    3. 用过滤器调整调度
       
      可以设置 docker-machine create 命令的 --enginelable 参数来给集群中节点打上
       标签。
       
      docker-machine create -d virtualbox \
        --swarm \
        --swarm-discovery token:// <YOUR TOKEN>
        --engine-label size=small \
        little-machine 

      docker-machine create -d virtualbox \
        --swarm \
        --swarm-discovery token://<YOUR TOKEN>
        --engine-label size=xxl \
        big-machine 
        
      除了用于节点的任何标签,容器可以为所有节点默认指定的标准属性指定约束:
      node---集群中节点的名字或者ID 
      storagedriver----节点使用的执行驱动器的名称
      executiondriver---节点使用的执行去驱动程序的名称
      kernelversion--- 节点的 Linux 内核版本
      operatingsystem--- 节点的操作系统的名称
      
      容器使用环境变量来与它们的关联和约束需求通信,可以使用一个单独的环境变量来
      设置每个约束或者关联规则。比如可能用于前面例子的 xxl_container 容器的规则
      看起来像以下这样。
      
      docker run -d -e constraint:size==xxl \   #约束环境变量
        -m 4G \
        -c 512 \
        postgres 
        
      容器关联 
      docker run -d -e afffinity:image=nginx \   #关联环境变量
        -p 80:80  \
        nginx 
      任何安装了 nginx 镜像节点的都会在候选节点集合中。另外,如果你想运行一个类
      似的程序,比如 ha-proxy,相比较而言,需要确保程序运行在一个单独的节点,你
      可以创建一个否定的关联
      
      docker run -d -e afffinity:image!=nginx \ 否定关联环境变量
       -p 8080:8080 \
       haproxy 
       
       一个关联或者约束规则是由一个键,一个操作符合一个值做成的,而键必须是已知的
       并完全合规的,值可以有以下三种形式。
       1. 完全限定为字母, 点, 连字符, 数字及下画线(比如 my-favorite.image-l)
       2. 全局语法指定模式(比如 my-favorite.image-*)
       3. 一个完整的 Golang 风格的正则表达式(比如/my-[a-z]+\.image-[0-9]+/)

       创建有效规则的最后一个工具是软操作符,当你想做调度而不是规则时,在操作符
       的最后添加波浪字符。例如,如果你想建议 Swarm 调度一个 nginx 容器,该容器
       所在的节点已经安装了该镜像,不过如果条件不能满足,就进行调度,那么你会使
       用一个如下的规则: 
       docker run -d -e afffinity:image==~nginx \ 建议关联环境变量
         -p 80:80 \
         nginx 
         
       过滤器可以用来定制任何调度算法,借助于预见到你的预期工作量和基础设施的不
       同性质,过滤器可以发挥很大的效用。

    4. BinBack 和随机调度算法
       BinBack 调度算法倾向于在调度工作负载到另外的节点前最有效地利用每一个节点。
       该算法使用最少数量的必须节点来支持工作负载,而随机算法提供了一个分配方案,
       其可以在 Spread 和 BinBack 之间达成妥协。

    5. Swarm 服务发现
       1. Swarm 和单主机网络
          Docker Engine 在其被安装的每台机器上的桥接网络后面创建了本地网络。
          如果一个 Swarm 集群部署到了运行但主机网络的 Docker 机器上,那么用
          Swarm部署的容器只能发现运行在相同主机上的其他容器。
          
          git clone git@github.com:dockerinaction/ch12_coffee_api.git
          
          cd ch12_coffee_api 
          eval "$(docker-machine env --swarm machine0-manager)"
          
          docker-compose up -d 
          
          docker ps 
          docker ps | less -S
          
          curl http://$(docker-machine ip <MACHINE>):8080/api/coffeeshops/
          
          docker-machine stop 命令关闭并用 docker-compose rm -vf 命令删掉示例容
          器
          
       2. 服务发现生态系统和权宜之计
          1. 网络服务发现的主要接口是 DNS
             
             服务发现软件的例子包括 etcd, Consul, ZooKeeper 和 Serf 。
             
             服务器名称可以由一个集成的外部发现系统通过容器对的 DNS 配置来解析,
             在这些拓扑中容器要么留下来与外部系统注册其自身,要么留给可以观察单
             独的 daemon 事件流的观看者来处理新容器的注册。

       3. 展望多主机网络
          1. Docker Engine 的实验性分支在可插拔接口后抽象了网络设施,该接口可以
             被大量的网络驱动实现,包括桥接,主机和 OverLay。桥接和主机网络驱动实
             现了你已经很熟悉的单主机网络的特性,Overlay 驱动通过 IP 封锁或者
             VXLAN 实现了一个 overlay 网络,该网络为每个由 Docker Machine 管理的
             容器提供了可路由的 IP 地址,并被配置为使用相同的键值对存储。
*** 7. 小结
    1. 用户可以使用 Docker Machine 的 create 命令创建一个本地虚拟机或者在云上创
       建一台 Machine
    2. Docker Machine 的 env 和 config 子命令可用于配置 Docker 客户端与 Docker
       Machine 提供的远程 Docker Engine 一起工作
    3. Docker Swarm 是一个向后兼容 Docker 远程 API 的协议,并且可以在一组成员节
       点上提供集群设施。
    4. Docker manager 程序实现了 Swarm API,并且为集群处理容器调度工作
    5. Docker Machine 为 Swarm node 和 manager 都提供了 flag。
    6. Docker Swarm 提供了是三种不同的调度算法,可以使用过滤器进行调整。
    7. Docker Engine 的标签和其他默认属性可以通过容器调度约束用作过滤标准
    8. 容器调度关联可以用来在同一台主机上将容器置为另外的容器,这些容器或者镜像
       匹配一个已提供的模式或者表达式
    9. 当任何一个 Docker 客户端被配置为与一个 Swarm  端点进行通信时,客户端将于
       整个 Swarm 互动,就好像是一台 Docker Machine 一样。
    10. Docker Swarm 目前调度在同一个节点上依赖的容器,直到多主机网络的发行版发
        布,或者你可以提供另一个服务发现机制并禁用 Swarm 的依赖性过滤器。
    11. 多主机网络将从 Docker 容器中的应用程序关注点抽象容器位置信息,每个容器在
        Overlay 网络上都将是一台主机。