Fork me on GitHub

深入理解Docker(服务、swarm、stack)


服务

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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或更具体的项目称为我们的项目。

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

1
2
3
4
5
6
7
8
// 这是集群初始化命令
$ 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):

1
2
3
4
5
6
7
8
9
10
11
// Docker将进行就地更新,无需首先从stack里移除或者杀死任何容器。
$ docker stack deploy -c docker-compose.yml getstartedlab
// 如果replicas的值放大,将会看到有更多的容器
$ docker stack ps
// 移除,但在集群里还在运行
$ docker stack rm getstartedlab
// 移除集群里的某个节点
$ docker swarm leave --force。

  接下来学习集群。


Swarm

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

了解Swarm集群

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

设置swarm

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

1
2
3
4
5
// 启用swarm模式,使您当前的机器成为群组管理器
$ docker swarm init
// 在其他机器上运行 ,让他们以工作人员身份加入群集
$ docker swarm join

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

1
2
3
4
5
// 创建两个虚拟机
$ docker-machine create --driver virtualbox myvm1
$ docker-machine create --driver virtualbox myvm2
$ docker-machine ls

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 查看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系统上面执行有错误,请执行下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 登陆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 :将文件从本地主机复制到机器,或从机器复制到本地主机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 将在服务那部分的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。


stack

  这部分将学习分布式应用程序顶层结构:stack。stack是一组相互关联的服务,它们共享依赖关系,并且可以一起orchestrated(编排)和缩放。单个stack能够定义和协调整个应用程序的功能(尽管非常复杂的应用程序可能希望使用多个堆栈)。
  有一个好消息是,从第【服务】部分开始,当您创建一个Compose文件并使用时,您在技术上一直在使用堆栈docker stack deploy。但是,这是单个主机上运行的单一服务堆栈,这通常不会在实际中发生。在这里,您将获取所学知识,使多个服务相互关联,并在多台计算机上运行。   

添加新服务并重新部署

  很容易为我们的docker-compose.yml文件添加服务。首先,我们添加一个可以看看我们的swarm如何调度的容器的免费的可视化服务。
1.再新建一个docker-compose.yml文件,用如下的内容。

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
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在管理员上运行如下命令进行更新:

1
2
3
4
5
$ 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.看看可视化器。

1
2
3
4
5
6
7
$ 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)


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

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

持久化数据**

再新建一个docker-compose.yml文件,跟之前相比多一个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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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文件将持续存在,从而实现连续性。

1
2
3
4
5
6
7
8
9
$ 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服务一起运行。
  


Network containers

在默认网络上启动一个容器

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

1
2
3
4
5
6
$ docker network ls
NETWORK ID NAME DRIVER
18a2866682b8 none null
c288470c46f6 host host
7b369448dccb bridge bridge

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

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

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

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
$ 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": {}
}
]

1
2
// 断开容器从网络中移除容器, 可以通过网络名称和容器名称,也可以使用容器ID。这里用名称更快。
$ docker network disconnect bridge networktest

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

创建自己的桥网络

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

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
// 创建一个桥接网
// -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": {}
}
]

将容器添加到网络

  要构建行为一致并且安全的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
2
3
4
5
6
7
8
9
10
// 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网络中运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ 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网络允许您将容器附加到任意网络上。您还可以附加到已运行的容器。

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

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 再次进入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的地址。


  菜鸟教程
  官方文档

-----------------本文结束,感谢您的阅读-----------------