深入理解Docker(镜像、容器、服务、swarm、stack)

菜鸟教程
官方文档
本文基本参考官方文档,有部分翻译直接用的原文

Docker基础知识

1.什么是Docker

  Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
  容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
  1.不同的应用程序可能会有不同的应用环境,比如.net开发的网站和php开发的网站依赖的软件就不一样,如果把他们依赖的软件都安装在一个服务器上就要调试很久,而且很麻烦,还会造成一些冲突。比如IIS和Apache访问端口冲突。这个时候你就要隔离.net开发的网站和php开发的网站。常规来讲,我们可以在服务器上创建不同的虚拟机在不同的虚拟机上放置不同的应用,但是虚拟机开销比较高。docker可以实现虚拟机隔离应用环境的功能,并且开销比虚拟机小,小就意味着省钱了。
  2.你开发软件的时候用的是Ubuntu,但是运维管理的都是centos,运维在把你的软件从开发环境转移到生产环境的时候就会遇到一些Ubuntu转centos的问题,比如:有个特殊版本的数据库,只有Ubuntu支持,centos不支持,在转移的过程当中运维就得想办法解决这样的问题。这时候要是有docker你就可以把开发环境直接封装转移给运维,运维直接部署你给他的docker就可以了。而且部署速度快。
  3.在服务器负载方面,如果你单独开一个虚拟机,那么虚拟机会占用空闲内存的,docker部署的话,这些内存就会利用起来。总之docker就是集装箱原理。


2.Docker的应用场景

  Web 应用的自动化打包和发布。
  自动化测试和持续集成、发布。
  在服务型环境中部署和调整数据库或其他的后台应用。
  从头编译或者扩展现有的OpenShift或Cloud Foundry平台来搭建自己的PaaS环境。


3.Docker 的优点

1、简化程序:
  Docker 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,便可以实现虚拟化。Docker改变了虚拟化的方式,使开发者可以直接将自己的成果放入Docker中进行管理。方便快捷已经是 Docker的最大优势,过去需要用数天乃至数周的 任务,在Docker容器的处理下,只需要数秒就能完成。
2、避免选择恐惧症:
  如果你有选择恐惧症,还是资深患者。Docker 帮你 打包你的纠结!比如 Docker 镜像;Docker 镜像中包含了运行环境和配置,所以 Docker 可以简化部署多种应用实例工作。比如 Web 应用、后台应用、数据库应用、大数据应用比如 Hadoop 集群、消息队列等等都可以打包成一个镜像部署。
3、节省开支:
  一方面,云计算时代到来,使开发者不必为了追求效果而配置高额的硬件,Docker 改变了高性能必然高价格的思维定势。Docker 与云的结合,让云空间得到更充分的利用。不仅解决了硬件管理的问题,也改变了虚拟化的方式。


4.Docker 架构

  Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。
  Docker 容器通过 Docker 镜像来创建。
  容器与镜像的关系类似于面向对象编程中的对象与类。

Docker: 面向对象
容器 对象
镜像

名称: 说明
Docker        镜像(Images) Docker 镜像是用于创建 Docker 容器的模板。
Docker 容器(Container) 容器是独立运行的一个或一组应用。
Docker 客户端(Client) Docker 客户端通过命令行或者其他工具使用 Docker API (https://docs.docker.com/reference/api/docker_remote_api) 与 Docker 的守护进程通信。
Docker主机(Host) 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker仓库(Registry) Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用。
Docker Machine Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。

二、高级使用

菜鸟教程链接
官方文档链接
本文基本参考官方文档,有部分翻译直接用的原文

  现在是开始构建Docker应用程序的时候了。我们将从这样一个应用程序的层次结构的底部开始。Mac系统 首先,需要一个可以创建虚拟机的管理程序,安装VirtualBox。
  * 容器(Containers),
  * 服务(Services),它定义了容器在生产中的行为方式,
  * Swarms:管理集群
  * 堆栈(Stacks),定义了所有服务的交互,
  * 网络容器:解决不同容器在不同网络下通信的问题

2.1.容器

  过去,如果要开始编写一个Python应用程序,首先是安装Python。但是,这会造成在其他机器上的环境必须如此。
  使用Docker,您可以将便携式(portable)的Python作为镜像运行,无需安装。然后,构建基本的Python镜像与应用程序代码,确保您的应用程序,其依赖关系在运行时能一起运行。
  这些镜像由Dockerfile定义。

2.1.1.用Dockerfile定义一个容器
  Dockerfile将定义容器内的环境中发生的情况。访问资源(如网络接口和磁盘驱动器)在此环境中进行虚拟化,这与您系统的其余部分是隔离的,因此您必须将端口映射到外部世界,并具体说明要将哪些文件“复制”到那个环境。这样做之后,Dockerfile构建的应用程序将能在任何地方运行。

将下面三个文件放到同一个文件夹里
1.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"]

2.requirements.txt

Flask
Redis

3.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本身),我们应该期望在这里使用它的尝试将失败并产生错误消息。
  您不需要在系统里安装Python或requirements.txt里的库,也不用构建或运行此镜像安装在您的系统上。似乎并没有真正使用Python和Flask来设置一个环境,但是实际你已经有了。

1.编译得到镜像

$ ls
Dockerfile      app.py          requirements.txt

  运行build命令。这将创建一个Docker镜像,我们使用-t将它标记,friendlyhello为镜像名称,最后还有一个”.”,这是用来指明Dockerfile路径的,”.”表示当前路径下。

$ docker build -t friendlyhello .

  你的图像在哪里?它在您机器的本地Docker镜像注册表中:

$ docker images
REPOSITORY            TAG                 IMAGE ID
friendlyhello         latest              326387cea398

运行
  运行应用程序,
  -p 是端口映射,将本机的8080端口映射到容器的80.
  -d 让程序在后台运行

docker run -d -p 4000:80 friendlyhello  --name myfridend

  可以在网页上输入 http://192.168.59.103:4000 显示内容。(192.168.59.103这个IP是在Mac系统上VirtualBox的boot2docker的ip,如果系统是Linux/Unix,直接用localhost)
  也可以可以使用curl命令来查看相同的内容。

$ boot2docker ip
192.168.59.103

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

$ docker ps
CONTAINER ID    IMAGE          COMMAND          NAMES       
1fa4ab2cf395    friendlyhello  "python app.py"  myfridend
你会看到CONTAINER ID匹配http://localhost:4000。

# stop:停止一个运行中的容器, 类似的有start/restart 
$ docker stop 1fa4ab2cf395

2.1.2.分享镜像
  为了演示我们刚刚创建的可移植性,让我们上传我们build的映像,并能在其他地方运行它。毕竟,当您要将容器部署到生产中时,您将需要了解如何推送到注册表(you’ll need to learn how to push to registries when you want to deploy containers to production.)。
  (A registry is a collection of repositories, and a repository is a collection of images)一个注册用户有很多个存储库,一个存储库有很多镜像, 类似于代码已经被构建的GitHub存储库。注册的用户可以创建许多存储库。The docker CLI uses Docker’s public registry by default。

注意:我们将使用Docker的public registry,因为它是免费的和预配置的。but there are many public ones to choose from, and you can even set up your own private registry using Docker Trusted Registry.

Docker ID登录
  如果您没有Docker帐户,请在cloud.docker.com注册一个 。记下您的用户名。

# 登录
$ docker login

Tag the image
  标签是可选的:

# 该命令的语法是
docker tag image username/repository:tag

# 例如:
$ docker tag friendlyhello john/get-started:part1  

$ docker images
REPOSITORY          TAG         IMAGE ID          CREATED             SIZE
friendlyhello       latest      d9e555c53008      3 minutes ago       195MB
john/get-started    part1       d9e555c53008      3 minutes ago       195MB

Publish(发布) the image
将标记的镜像上传到存储库:命令格式如下:
  docker push username/repository:tag
一旦完成,此上传的结果是公开的,可以登陆查看。也可以使用pull命令获取该镜像。

Pull and run the image from the remote repository
  您可以使用docker run在任何机器上运行,使用此命令:

docker run -p 4000:80 username/repository:tag
如果图像在本机不可用,Docker将从存储库中拉出。

$ docker run -p 4000:80 john/get-started:part1
Unable to find image 'john/get-started:part1' locally
part1: Pulling from orangesnap/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 john/get-started:part1

注意:如果没有指定:tag这些命令的部分,:latest则将在构建时和运行映像时假定标记。Docker将使用运行的最后一个版本的图像,而没有指定标签(不一定是最新的图像).

无论在哪执行docker run,它都会将您的图像、Python和requirements.txt所依赖的关系从您的代码中提取出来,并运行您的代码。它们被打包在一起,主机不需要安装任何东西,Docker就能运行它。
   In the next section, we will learn how to scale(衡量) our application by running this container in a service。


2.2.服务

  在这节,我们扩展了应用程序并启用了负载均衡。为此,我们必须在分布式应用程序的层次结构中进行一级升级: 服务。
  在分布式应用程序中,应用程序的不同部分称为“服务”。
  服务只能“在容器里生成”。一个服务只运行一个镜像。
  使用Docker运行和改变服务非常简单,只需编写一个docker-compose.yml文件。

你的第一个docker-compose.yml文件
  一个docker-compose.yml文件是一个YAML文件,用于定义Docker容器在生产过程中的行为。
  将此文件保存docker-compose.yml到您想要的位置。确保您上一节创建的镜像推送到注册表(也就是自己的仓库里)。
  
docker-compose.yml

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    image: john/get-started:part1
    deploy:
      replicas: 5
      resources:
        limits:
          cpus: "0.1"
          memory: 50M
      restart_policy:
        condition: on-failure
    ports:
      - "80:80"  # 之前是4000:80
    networks:
      - webnet
networks:
  webnet:

该docker-compose.yml文件告诉Docker执行以下操作:
  * 从registry(自己的仓库)中pull我们在上一节上传的图像。
  * 运行该镜像的五个实例作为调用的服务web,限制每个实例使用最多使用10%的CPU(跨所有内核)和50MB RAM。
  * 如果发生故障,立即重新启动容器。
  * 将端口80映射到主机web端口80。
  * Instruct web’s containers to share port 80 via a load-balanced network called webnet. (Internally, the containers themselves will publish to web’s port 80 at an ephemeral port.) (指示web容器通过称为负载平衡网络共享端口80 webnet。(在内部,集装箱本身将web在短暂的港口发布到 80号港口。))
  * Define the webnet network with the default settings (which is a load-balanced overlay network).(webnet使用默认设置(这是一个负载平衡的重叠网络)来定义网络)。

想知道撰写文件的版本,名称和命令?
  请注意,将撰写文件设置为version: “3”。这基本上使它与 群模式兼容。我们可以利用部署密钥(仅适用于Compose文件格式3.x及更高版本)及其子选项来对每个服务(例如web)进行负载平衡和优化性能。我们可以使用docker stack deploy命令运行文件(也仅在Compose文件3.x及以上支持)。您可以使用非群组配置docker-compose up运行版本3文件,但是由于我们正在构建一个群体示例,因此我们专注于堆栈部署。
  您可以将Compose文件命名为任何您希望使其在逻辑上对您有意义的任何内容; docker-compose.yml只是一个标准名称。我们可以很容易地将这个文件docker-stack.yml或更具体的项目称为我们的项目。

运行新的负载平衡应用程序

// 这是集群初始化命令
$ docker swarm init

// 现在我们来运行它 你必须给你的应用程序一个名字。在这里,它设置为 getstartedlab:
$ docker stack deploy -c docker-compose.yml getstartedlab

// 看到刚刚推出的五个容器的列表:
$ docker stack ps getstartedlab

  您可以curl http://localhost(如果是Mac,用boot2docker的ip)连续运行多次,或者在浏览器中转到该URL,并点击刷新几次。无论哪种方式,您将看到容器ID更改,显示负载平衡; 在每个请求中,以循环方式选择五个副本之一进行响应。

注意:在此阶段,容器可能需要长达30秒才能响应HTTP请求。这并不代表Docker或群组性能,而是一个未满足的Redis依赖关系,我们将在本教程的后面介绍。

Scale the app
  通过修改docker-compose.yml文件里的replicas值来scale the app(改变这个app):

// Docker将进行就地更新,无需首先从stack里移除或者杀死任何容器。
$ docker stack deploy -c docker-compose.yml getstartedlab

// 如果replicas的值放大,将会看到有更多的容器
$ docker stack ps

// 移除,但在集群里还在运行
$ docker stack rm getstartedlab

// 移除集群里的某个节点 
$ docker swarm leave --force。

  接下来学习集群。


2.3.Swarm

  在第一部分里写过的应用程序,将其转换为服务.
  在这部分将此应用程序部署到集群上。

2.3.1.了解Swarm集群
  swarm一组在Docker上运行并加入集群的机器。之后,您继续运行以前使用的Docker命令,但现在它们是在集群上执行的由swarm管理。在swarm中的机器可以是物理的或虚拟的。加入swarm后,它们被称为节点。
  Swarm管理者是swarm中唯一可以执行命令,或授权其他机器作为工作人员加入swarm的管理机器,(加入的节点不能)。工作机器只是提供能力,没有权力告诉任何其他机器它可以做什么和不能做什么。
  到目前为止,您已经在本地计算机上以单主机模式使用Docker,Docker也可以切换到swarm 模式。swarm 模式可以立使当前的机器成为swarm管理员。从那时起,Docker将运行您正在管理的swarm上执行的命令,而不是在当前的机器。

2.3.1.设置swarm
  一个swarm由多个node组成,可以是物理机或虚拟机。

// 启用swarm模式,使您当前的机器成为群组管理器
$ docker swarm init

// 在其他机器上运行 ,让他们以工作人员身份加入群集
$ docker swarm join

1).创建一个集群
  首先,需要一个可以创建虚拟机的管理程序,安装VirtualBox。

// 创建两个虚拟机
$ docker-machine create --driver virtualbox myvm1
$ docker-machine create --driver virtualbox myvm2

$ docker-machine ls

  第一个将作为管理员,执行docker命令并认证工作者加入swarm,第二个将是一个工作者。
  到这里特别注意,如果是linux系统,执行下面的命令(不用登陆,将命令包装);

// 查看myvm1虚拟机的ip
$ docker-machine evn myvm1

// myvm1 成为一个管理员
$ docker-machine ssh myvm1 "docker swarm init --advertise-addr 192.168.33.10:2377" 
Swarm initialized: current node <node ID> is now a manager.

To add a worker to this swarm, run the following command:
docker swarm join \
     --token SWMTKN-1-6cqcc65jtfmldojryxg7s4rpljekcdrobz288mycgyu12gfxlf-bg27cxk75qhbgwjngxn500lqg \
     192.168.33.10:2377

// 这里的token,ip,port使用上面的
$ docker-machine ssh myvm2 "docker swarm join --token <token>  <ip>:<port>"

This node joined a swarm as a worker.

// 需要到 myvm1上执行docker node ls查看节点,因为myvm1是管理员:
$ docker-machine ssh myvm1 "docker node ls"

恭喜,你已经创建了你的第一个swarm。
  如果是Mac系统上面执行有错误,请执行下面

// 登陆myvm1
$ docker-machine ssh myvm1

// 初始化myvm1, 不要端口号!!!!
docker@myvm1:~$ docker swarm init --advertise-addr 192.168.33.10

//!!! myvm1 成为一个管理员  !!!
docker@myvm1:~$ docker swarm join-token manager

// 登陆myvm2
$ docker-machine ssh myvm2

// 直接执行,不要包装命令
docker@myvm2:~$ docker swarm join --token <token>  <ip>:<port>

// 需要到 myvm1上执行docker node ls查看节点,因为myvm1是管理员:
docker@myvm1:~$ docker node ls
ID                            HOSTNAME  STATUS  AVAILABILITY   MANAGER STATUS
brtu9urxwfd5j0zrmkubhpkbd     myvm2     Ready   Active              
rihwohkh3ph38fhillhhb84sk *   myvm1     Ready   Active         Leader

// 键入exit以退出。

2).在集群上部署应用程序
  困难的部分已经结束了。只要记住,只有swarm管理者myvm1能执行Docker命令; 工作者myvm2只能做事。

scp :将文件从本地主机复制到机器,或从机器复制到本地主机

// 将在服务那部分的docker-compose.yml文件复制到home目录(~)
$ docker-machine scp docker-compose.yml myvm1:~

// 将应用部署到集群上!!!,部署命令
docker@myvm1:~$ docker stack deploy -c docker-compose.yml getstartedlab

// 可以看到容器已经在myvm1和myvm2两者之间分布
docker@myvm1:~$ docker stack ps getstartedlab
ID            NAME        IMAGE                   NODE   DESIRED STATE
jq2g3qp8nzwx  test_web.1  john/get-started:part1  myvm1  Running
88wgshobzoxl  test_web.2  john/get-started:part1  myvm2  Running
vbb1qbkb0o2z  test_web.3  john/get-started:part1  myvm2  Running
ghii74p9budx  test_web.4  john/get-started:part1  myvm1  Running
0prmarhavs87  test_web.5  john/get-started:part1  myvm2  Running

docker@myvm1:~$ docker stack rm getstartedlab

// 删除节点
docker@myvm1:~$ docker swarm leave --force

3).访问您的集群
  你可以从myvm1或myvm2的IP地址来访问你的应用程序 。您创建的network(网络)在它们之间共享并且负载均衡。运行 docker-machine ls以获取您的虚拟机的IP地址,并在浏览器上访问它们,然后点击刷新(或仅仅curl它们)。您会看到五个可能的容器ID,它们都随机循环,显示了负载均衡。
  IP地址工作的原因是swarm中的节点参与ingress(入口)路由网格。This ensures that a service deployed at a certain port within your swarm always has that port reserved to itself, no matter what node is actually running the container. (这样可以确保部署服务时,在swarm中的某个端口始终能将该端口保留给自身,无论实际运行的是哪个节点)。
  以下是端口是8080的my-web服务发布在三节点的swarm的routing mesh(路由网格)如何显示的图像:

有连接问题?
 为了在swarm中使用ingress network(入口网络),在启用swarm模式之前,需要在swarm节点之间打开以下端口:
  * 端口7946: TCP/UDP for container network discovery。
  * 端口4789: UDP for the container ingress network。


2.4.stack

  这部分将学习分布式应用程序顶层结构:stack。stack是一组相互关联的服务,它们共享依赖关系,并且可以一起orchestrated(编排)和缩放。单个stack能够定义和协调整个应用程序的功能(尽管非常复杂的应用程序可能希望使用多个堆栈)。
  有一个好消息是,从第【服务】部分开始,当您创建一个Compose文件并使用时,您在技术上一直在使用堆栈docker stack deploy。但是,这是单个主机上运行的单一服务堆栈,这通常不会在实际中发生。在这里,您将获取所学知识,使多个服务相互关联,并在多台计算机上运行。
  
2.4.1.添加新服务并重新部署
  很容易为我们的docker-compose.yml文件添加服务。首先,我们添加一个可以看看我们的swarm如何调度的容器的免费的可视化服务。
1.再新建一个docker-compose.yml文件,用如下的内容。

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    image: john/get-started:part1
    deploy:
      replicas: 5
      restart_policy:
        condition: on-failure
      resources:
        limits:
          cpus: "0.1"
          memory: 50M
    ports:
      - "80:80"
    networks:
      - webnet
  visualizer:
    image: dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      placement:
        constraints: [node.role == manager]
    networks:
      - webnet
networks:
  webnet:

  跟以前的文件相比,这里多了个visualizer(可视化工具)。这里看到两个新的东西
1).volumes关键字,giving the visualizer access to the host’s socket file for Docker
2).placement关键字,确保这个服务只能在swarm管理员上运行,而不是工作人员。这是因为由Docker创建的开源项目构建的这个容器显示了在图中以swarm运行的Docker服务。
  稍后我们会详细讨论placement constraints(布局约束)和volumes(卷)。

2.将此新docker-compose.yml文件复制到swarm管理器myvm1:

$ docker-machine scp docker-compose.yml myvm1:~

3.docker stack deploy在管理员上运行如下命令进行更新:

$ docker-machine ssh myvm1

docker@myvm1:~$ docker stack deploy -c docker-compose.yml getstartedlab
Updating service getstartedlab_web (id: angi1bf5e4to03qu9f93trnxm)
Updating service getstartedlab_visualizer (id: l9mnwkeq2jiononb5ihz9u7a4)

4.看看可视化器。

$ docker-machine env myvm1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.101:2376"
export DOCKER_CERT_PATH="***/.docker/machine/machines/myvm1"
export DOCKER_MACHINE_NAME="myvm1"
# Run this command to configure your shell: 
# eval $(docker-machine env myvm1)

// 通过运行docker stack ps <stack>来证明上面的可视化
docker@myvm1:~$ docker stack ps getstartedlab

  可视化器是一种独立的服务,可以运行包含在堆栈中的任何应用程序。它不依赖于其他任何东西。
  现在让我们创建一个确实具有依赖性的服务:提供一个访客计数器Redis服务。

2.4.2.持久化数据
再新建一个docker-compose.yml文件,跟之前相比多一个Redis服务。

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    image: username/repo:tag
    deploy:
      replicas: 5
      restart_policy:
        condition: on-failure
      resources:
        limits:
          cpus: "0.1"
          memory: 50M
    ports:
      - "80:80"
    networks:
      - webnet
  visualizer:
    image: dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      placement:
        constraints: [node.role == manager]
    networks:
      - webnet
  redis:
    image: redis
    ports:
      - "6379:6379"
    volumes:
      - ./data:/data
    deploy:
      placement:
        constraints: [node.role == manager]
    networks:
      - webnet
networks:
  webnet:

  Redis在Docker库是一个官方镜像,只用redis即可,不需要username/repo符号。
Together, this is creating a “source of truth” in your host’s physical filesystem for the Redis data. Without this, Redis would store its data in /data inside the container’s filesystem, which would get wiped out if that container were ever redeployed.
最重要的是,在redis规范中有几点使数据在已经部署的stack之间能持久化:
  * redis 总是在管理员上运行,所以它总是使用相同的文件系统。
  * redis accesses an arbitrary directory in the host’s file system as /data inside the container, which is where Redis stores data.(redis访问主机文件系统中的任意目录都是被当作是容器的/data目录,那是Redis存储数据的地方)。
这是在您的主机的物理文件系统中为Redis数据创建“事实的根源”。没有这个,Redis会将其数据存储在容器的文件系统 /data 内,如果该容器被重新部署,它将被清除。

这个事实的根源有两个部分:
  * 您放置在Redis服务上的placement constraints(布局约束),确保它始终使用相同的主机。
  * 您创建的volumes(卷)允许容器访问./data(在主机上)为/data(在Redis容器内)。当容器来回切换时,存储在指定主机上的./data文件将持续存在,从而实现连续性。

$ docker-machine ssh myvm1

// 创建一个目录。跟上面的volumes定义的地址一致。如果切换机器觉得麻烦,可以把命令包装起来
// $ docker-machine ssh myvm1 "mkdir ./data"
docker@myvm1:~$ mkdir ./data

$ docker-machine scp docker-compose.yml myvm1:~

docker@myvm1:~$ docker stack deploy -c docker-compose.yml getstartedlab

  检查您的一个节点(例如http://192.168.99.101)的网页,您将看到访客计数器的结果,该计数器现在已存在,并存储有关Redis的信息。
  

  另外,检查可视化在上的任一节点的IP地址端口8080,你会看到redis与web服务和visualizer服务一起运行。
  


2.5.Network containers

2.5.1.在默认网络上启动一个容器
  Docker通过使用network drivers(网络驱动)程序来支持网络容器。默认情况下,Docker为您bridge和overlay网络驱动程序。您还可以编写网络驱动程序插件,以便您可以创建自己的驱动程序,但这是一项高级任务。
  Docker Engine每次安装都会自动包含三个默认网络:

$ docker network ls

NETWORK ID          NAME                DRIVER
18a2866682b8        none                null
c288470c46f6        host                host
7b369448dccb        bridge              bridge

  网络bridge是一个特殊的网络。除非您另有说明,否则Docker会始终在此网络中启动您的容器。

$ docker run -itd --name=networktest ubuntu
74695c9cea6d9810718fddadc01a727a5dd3ce6a69d09752239736c030599741
BRIDGE1

  
  检查网络是查找容器IP地址的一种简单方法。

$ docker network inspect bridge

[
    {
        "Name": "bridge",
        "Id": "f7ab26d71dbd6f557852c7156ae0574bbf62c42f539b50c8ebde0f728a253b6f",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.1/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "3386a527aa08b37ea9232cbcace2d2458d49f44bb05a6b775fba7ddd40d8f92c": {
                "Name": "networktest", // !!!!
                "EndpointID": "647c12443e91faf0fd508b6edfe59c30b642abb60dfab890b4bdccee38750bc1",
                "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": "9001"
        },
        "Labels": {}
    }
]
// 断开容器从网络中移除容器, 可以通过网络名称和容器名称,也可以使用容器ID。这里用名称更快。
$ docker network disconnect bridge networktest

  当您从一个网络上断开容器时,不能删除名为bridge的内置bridge network。Networks是以自然的方式将容器与其他容器或其他网络隔离开。因此,随着您对Docker的更多经验,您将需要创建自己的网络。

2.5.2.创建自己的桥网络
  Docker Engine本地支持bridge(桥接)网络和overlay(覆盖)网络。
  1).桥接网络仅限于运行Docker Engine的单个主机。
  2).覆盖网络可以包含多个主机,是一个更高级的主题。

// 创建一个桥接网
// -d标志告诉Docker将bridge驱动程序用于新网络,可以把该标志关掉
$ docker network create -d bridge my_bridge

$ docker network ls
NETWORK ID          NAME                DRIVER
7b369448dccb        bridge              bridge
615d565d498c        my_bridge           bridge
18a2866682b8        none                null
c288470c46f6        host                host

// 如果你检查网络,你会发现它没有任何内容。
$ docker network inspect my_bridge

[
    {
        "Name": "my_bridge",
        "Id": "5a8afc6364bccb199540e133e63adb76a557906dd9ff82b94183fc48c40857ac",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1"
                }
            ]
        },
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

2.5.3.将容器添加到网络
  要构建行为一致并且安全的web应用,需要创建一个网络。根据定义,Networks为容器提供完全隔离。首次运行容器时,可以将容器添加到网络中。

Launch a container running a PostgreSQL database and pass it the –net=my_bridge flag to connect it to your new network:

// 1.启动运行PostgreSQL数据库的容器,并将其--net=my_bridge标记连接到新的网络:
$ docker run -d --net=my_bridge --name db training/postgres

// 如果你检查my_bridge你会看到它附有一个容器。您还可以检查您的容器以查看连接的位置:
$ docker inspect --format='{{json .NetworkSettings.Networks}}'  db
{"my_bridge":{"NetworkID":"7d86d31b1478e7cca9ebed7e73aa0fdeec46c5ca29497431d3007d2d9e15ed99",
"EndpointID":"508b170d56b2ac9e4ef86694b0a76a22dd3df1983404f7321da5649645bf7043","Gateway":"10.0.0.1","IPAddress":"10.0.0.254","IPPrefixLen":24,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02"}}

// 2.启动web服务,注意:不要指定网络,为了让这两个容器不在同一个网络里。
$ docker run -d --name web training/webapp python app.py

  
  您的web应用程序运行在哪个网络?检查应用程序,你会发现它在默认bridge网络中运行。

$ docker inspect --format='{{json .NetworkSettings.Networks}}'  web
{"bridge":{"NetworkID":"7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812",
"EndpointID":"508b170d56b2ac9e4ef86694b0a76a22dd3df1983404f7321da5649645bf7043","Gateway":"172.17.0.1","IPAddress":"10.0.0.2","IPPrefixLen":24,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02"}}

// 得到你web的IP地
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
172.17.0.2

// 打开一个shell到你正在运行的db容器:
$ docker exec -it db bash

root@a205f0dd33b2:/# ping 172.17.0.2
ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
^C
--- 172.17.0.2 ping statistics ---
44 packets transmitted, 0 received, 100% packet loss, time 43185ms

  稍后,使用CTRL-C结束ping,你会发现ping失败。这是因为两个容器在不同的网络上运行。你可以解决这个问题。
  使用exit命令关闭容器。
  Docker网络允许您将容器附加到任意网络上。您还可以附加到已运行的容器。

// 将您正在运行的web应用程序附加到my_bridge。
$ docker network connect my_bridge web

  

// 再次进入db应用程序并尝试ping
$ docker exec -it db bash

// 这次只是ping web容器名而不是IP地址
root@a205f0dd33b2:/# ping web
PING web (10.0.0.2) 56(84) bytes of data.
64 bytes from web (10.0.0.2): icmp_seq=1 ttl=64 time=0.095 ms
64 bytes from web (10.0.0.2): icmp_seq=2 ttl=64 time=0.060 ms
64 bytes from web (10.0.0.2): icmp_seq=3 ttl=64 time=0.066 ms
^C
--- web ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.060/0.073/0.095/0.018 ms
  
// ping结果表明它是接触不同的IP地址,这个地址在my_bridge是而不是bridge network的地址。

阅读更多
换一批

没有更多推荐了,返回首页