第三章 在swarm集群上运行docker币应用

在第一章的我们在单节点环境下运行docker币应用的时候, 首先我们build的应用镜像, 这时候image是保存在本机的.
现在我们使用swarm集群来运行,docker币服务分布在很多不通的服务器节点上, 难道每个节点都要build镜像, 或者要把image拷贝到别的节点上?

当然不用, 这个时候我们要用到本地镜像仓库docker registry, 用于保存image. 每个节点从registry拉取需要的image就可以了.


当然还有别的方法, 可以使用docker Hub公用的镜像仓库, registry是一种开源的本地镜像仓库服务.

3.1 部署镜像仓库registry

创建registry服务, 发布5000端口.

1
[root@node01 ~]# docker service create --name registry --publish 5000:5000 registry:2

镜像比较大, 可能要等很久才能拉取成功.
验证service部署成功后, 确实registry服务工作正常:

1
2
3
4
5
[root@node01 ~]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
eqicwcgv877q registry 1/1 registry:2
[root@node01 ~]# curl localhost:5000/v2/_catalog
{"repositories":[]}

返回{"repositories":[]} 说明registry服务工作正常.

Note 注意我们只启动了一个registry副本容器, 根据docker swarm集群的端口发布服务特性, swarm集群所有节点都会自动发布本机的5000
对应到registry, 所以不管你在哪个节点上执行curl localhost:5000/v2/_catalog, 都是可以访问到registry服务的.所以你不用关心这个registry跑在那个节点上. 很好很强大~

3.2 测试本地镜像仓库

有了本地镜像仓库registry, 现在我们推送一个测试镜像到本机镜像仓库, 测试下registry服务.

1
2
3
4
5
6
7
8
9
10
11
12
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dockercoins_webui latest c2d09bb0c442 47 hours ago 212.2 MB
dockercoins_rng latest ff1d886f2fa3 47 hours ago 83.53 MB
dockercoins_hasher latest 9d361dbaf589 47 hours ago 310.6 MB
dockercoins_worker latest bef5d2dc4bd0 47 hours ago 80.5 MB
python alpine 0298711f3095 2 weeks ago 73.04 MB
elasticsearch latest d0390797eb4f 3 weeks ago 346.5 MB
node 4-slim 6585f7b67838 5 weeks ago 207.9 MB
ruby alpine c872d09a2f2e 5 weeks ago 126 MB
alpine latest 4e38e38c8ce0 5 weeks ago 4.795 MB
redis latest 4465e4bcad80 6 weeks ago 185.7 MB

查看下我们现有的镜像, 选取一个例如alpine, 如果想把alpine镜像推送到本地registry.

需要先tag这个镜像的名字成<registry>/<image name>:<tag>:

1
2
3
4
5
6
7
[root@node01 ~]# docker tag alpine localhost:5000/busybox
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
...
alpine latest 4e38e38c8ce0 5 weeks ago 4.795 MB
localhost:5000/busybox latest 4e38e38c8ce0 5 weeks ago 4.795 MB
...

下面就可以使用docker push <image name>推送image了.

1
2
3
4
[root@node01 ~]# docker push localhost:5000/busybox
The push refers to a repository [localhost:5000/busybox]
4fe15f8d0ae6: Pushed
latest: digest: sha256:dc89ce8401da81f24f7ba3f0ab2914ed9013608bdba0b7e7e5d964817067dc06 size: 528

push成功后, 可以调用registry API查看 registry中的镜像

1
2
[root@node01 ~]# curl localhost:5000/v2/_catalog
{"repositories":["busybox"]}

可以看到busybox镜像, 已经在我们的本地镜像库中了.

3.3 推送docker币镜像到本地镜像库

绕了一大圈, 终于回到我们的docker币应用了, 让我们来挖更多的docker币吧. 忘记docker币应用的同学请参见本系列的第一章.

现在我们要做的是, 把build完成的,docker币的镜像推送到本地镜像库中, 这样我们用swram集群在多个节点部署应用的时候就不需要在build,可以直接从本地镜像库中拉取镜像了.

具体步骤:

  1. build docker币镜像(在第一章我们已经在node01上build过了, 你也可以直接使用这些镜像.)
  2. retag镜像名字成 localhost:5000/<service name>:<tag>名字
  3. push镜像到我们的本地镜像库

我们的docker币应用一共需要5个service(hasher, rng, webui, worker, redis), redis可以不用build直接只用官方的就行.

1
2
3
4
5
6
[root@node01 dockercoins]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dockercoins_webui latest c2d09bb0c442 47 hours ago 212.2 MB
dockercoins_rng latest ff1d886f2fa3 47 hours ago 83.53 MB
dockercoins_hasher latest 9d361dbaf589 47 hours ago 310.6 MB
dockercoins_worker latest bef5d2dc4bd0 2 days ago 80.5 MB

上面是第一章我们build好的.

下面我们会以hasher为例演示下, push的过程.

1
2
3
4
5
6
7
8
9
10
11
# 切换到我们应用的docker-compose file目录
[root@node01 ~]# cd orchestration-workshop/dockercoins/
# build hasher应用的镜像
[root@node01 dockercoins]# docker-compose build hasher
# retag镜像名
[root@node01 dockercoins]# docker tag dockercoins_hasher localhost:5000/dockercoins_hasher:v0.1
# 推送镜像
[root@node01 dockercoins]# docker push localhost:5000/dockercoins_hasher
# 验证推送结果
[root@node01 dockercoins]# curl localhost:5000/v2/_catalog
{"repositories":["busybox","dockercoins_hasher"]}

hasher推送完了, 你可以使用相同的步骤完成rng, webui, worker的推送.

简单起见, 你可以使用下面的一段命令, 完成整个过程, 直接贴到命令窗口执行就可以了.

1
2
3
4
5
6
7
DOCKER_REGISTRY=localhost:5000
TAG=v0.1
for SERVICE in hasher rng webui worker; do
docker-compose build $SERVICE
docker tag dockercoins_$SERVICE $DOCKER_REGISTRY/dockercoins_$SERVICE:$TAG
docker push $DOCKER_REGISTRY/dockercoins_$SERVICE
done

验证你的镜像库中内容:

1
2
[root@node01 dockercoins]# curl localhost:5000/v2/_catalog
{"repositories":["busybox","dockercoins_hasher","dockercoins_rng","dockercoins_webui","dockercoins_worker"]}

3.4 overlay网络

解决了镜像构建问题, 为了让我们的应用跑在swram集群上,我们还需要解决容器间的网络访问问题.

单台服务器的时候我们应用所有的容器都跑在一台主机上, 所以容器之之间的网络是互通的. 现在我们的集群有5台主机, 所以docker币应用的服务会分布在这5台主机上.

如何保证不同主机上的容器网络互通呢?

swarm集群已经帮我们解决了这个问题了,就是只用overlay network.

docker 1.12以前, swarm集群需要一个额外的key-value存储(consul, etcd etc). 来同步网络配置, 保证所有容器在同一个网段中.
docker 1.12已经内置了这个存储, 集成了overlay networks的支持.

至于overlay network的细节, 就不多介绍了.参见docker network介绍.下面我们演示下如何创建一个 overlay network:
只需要只用docker network create命令.

1
2
3
4
5
6
7
8
9
10
11
# 为我们的docker币应用创建一个名为dockercoins的overlay network
[root@node01 dockercoins]# docker network create --driver overlay dockercoins
396fwpd5ja6azh93dshalmxro
# 查询docker network 列表
[root@node01 dockercoins]# docker network ls
NETWORK ID NAME DRIVER SCOPE
...
396fwpd5ja6a dockercoins overlay swarm
5929cc7b1912 host host local
6yiheh8d2itd ingress overlay swarm
...

在网络列表中你可以看到dockercoins网络的SCOPEswarm, 表示该网络在整个swarm集群生效的, 其他一些网络是local, 表示本机网络.
你只需要在manager节点创建network, swarm集群会自动处理配置到其他的节点,这是你可以查看其他节点的network. dockercoins网络已经都创建了.:

1
[root@node01 dockercoins]# ssh node03 docker network ls

Note 大误, overlay网络在没有使用的时候,只会被同步到manager节点, 所以这时候你在worker节点是看不到这个网络地. 感谢大家,留言提醒嘿嘿.

在网络上运行容器

现在我们有了dockercoins网络了, 怎么指定容器或者服务运行在哪个网络上呢.
下面我们可以先启动一个redis服务, 作为我们docker币应用的数据库.如果你还记得我们的应用架构.

worker服务会把计算到的docker币保存到redis数据库中, webui会从redis中读取docker币的数量.
不记得, 请回顾第一章, 嘿嘿…

直接使用--network <network name>参数, 在指定网络上创建service.

1
2
3
4
5
[root@node01 dockercoins]# docker service create --network dockercoins --name redis redis
1lqi13cteor9qj5ihv40eo43i
[root@node01 dockercoins]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
1lqi13cteor9 redis 1/1 redis

3.5 在swarm集群上运行docker币应用

redis服务创建好了, 下面我们以前为其他的 服务创建service了.

下面我们可以使用之前push到本地镜像仓库的镜像启动hasher, rng, webui, worker服务, 以hasher为例:

1
2
[root@node01 dockercoins]# docker service create --network dockercoins --name hasher localhost:5000/dockercoins_hasher:v0.1
9jtetr0glu6uudzy72bywgrgf

注意, 我们启动的镜像名字localhost:5000/dockercoins_hasher:v0.1 使用我们本地镜像仓库的镜像名称, 这样当主机上没有这个镜像时, 会自动到本地镜像仓库拉取镜像.

下面的这段脚本可以帮我们启动所有的服务, 你可以直接粘贴到命令行执行:

1
2
3
4
5
6
DOCKER_REGISTRY=localhost:5000
TAG=v0.1
for SERVICE in hasher rng webui worker; do
docker service create --network dockercoins --name $SERVICE \
$DOCKER_REGISTRY/dockercoins_$SERVICE:$TAG
done

完成后检查我们的service启动情况:

1
2
3
4
5
6
7
8
[root@node01 dockercoins]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
1lqi13cteor9 redis 1/1 redis
9jtetr0glu6u hasher 1/1 localhost:5000/dockercoins_hasher:v0.1
akxrlmck4u0r webui 1/1 localhost:5000/dockercoins_webui:v0.1
d7g0estex65u worker 1/1 localhost:5000/dockercoins_worker:v0.1
eqicwcgv877q registry 1/1 registry:2
ey7oe8o40471 rng 1/1 localhost:5000/dockercoins_rng:v0.1

好了下面我们可以开心的继续挖docker币了, 打开webui先.

Ops~, 我们忘记发布webui的端口了, 所以我们没法访问webui. 因为没法动态的修改servcie端口发布, 所以我们只能删除webui服务,重新建一个了.

1
2
3
4
5
6
7
8
9
[root@node01 dockercoins]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
...
akxrlmck4u0r webui 1/1 localhost:5000/dockercoins_webui:v0.1
...
[root@node01 dockercoins]# docker service rm webui
webui
[root@node01 dockercoins]# docker service create --network dockercoins --name webui --publish 8000:80 localhost:5000/dockercoins_webui:v0.1
cvr2cwm7lqzezxeztljn7zgyr

好了, 现在你可以用浏览器访问http://192.168.33.101:8000/index.html 就能看到我们熟悉的webui了.
事实上, 你可以访问swarm集群中的所有节点 192.168.33.102 - 192.168.33.1058000端口, 都可以访问到我们的webui.

3.6 扩展(Scaling)应用

好了现在万事具备, 回到我们第一章结尾的问题, 这个坑有点长~~~ 嘿嘿.

在第一章结尾, 我们分析了docker币应用的瓶颈, 在单台服务器的情况下, 我们scale up worker节点, 到10个副本的时候. 产生的docker币数量并没有按照预想的情况增加.
我们找到了两个瓶颈:

  1. 单台服务器性能的瓶颈.
  2. rng 服务的瓶颈.

其实主要是rng服务的瓶颈啦, 为了解决这个性能问题, 我们引入了docker swarm集群.
打开webui, 可以看到我们只有一个worker副本, 每秒产生约4个docker币.
增加worker容器到10个.

1
2
3
4
5
6
7
[root@node01 dockercoins]# docker service scale worker=10
worker scaled to 10
[root@node01 dockercoins]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
...
d7g0estex65u worker 8/10 localhost:5000/dockercoins_worker:v0.1
...

瓶颈出现了, 再看webui可以发现每秒产生约10个docker币.
前面我们已经分析过瓶颈出现在rng服务, 下面我们就来扩展rng服务.
当然我们可以使用docker service scale命令, 跟上面一样增加rng服务的容器.不过这次我们换一种方式,这次我们通过修改rng服务的属性实现.
使用如下命令查询service属性:

1
2
3
4
5
6
7
8
[root@node01 dockercoins]# docker service inspect rng
...
"Mode": {
"Replicated": {
"Replicas": 1
}
},
...

更新rng服务属性:

1
2
3
4
5
6
[root@node01 dockercoins]# docker service update rng --replicas 5
rng
[root@node01 dockercoins]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
...
ey7oe8o40471 rng 5/5 localhost:5000/dockercoins_rng:v0.1

再回到webui, 这时可以看到每秒可以产生约30个docker币了.不过这离我们的理论值10个worker每秒产生40个docker币还差一点.

3.7 找到程序瓶颈

现在我们的整个docker币应用已经有:
10 个 worker,5 个 rng,1 个 hasher,1 个 webui.
为了找到程序瓶颈, 我们可以启动一个临时容器, 对我们的rnghasher服务做个简单的压力测试.

启动一个临时容器, debug使用alpine镜像, 连接到dockerconins网络中.

1
[root@node01 dockercoins]# docker service create --network dockercoins --name debug --mode global alpine sleep 1000000000

--mode globle 是啥意思呢, global模式的service, 就是在swarm集群的每个节点创建一个容器副本, 所以如果你想让一个service分布在集群的每个节点上. 可以使用这个模式.
sleep 1000000000 为啥, 因为懒, 想保持这个容器, 方便我们debug.
下面我们进入debug这个容器, 安装测试软件, 对我们的rnghasher服务进行性能测试.

1
2
3
4
5
6
[root@node01 dockercoins]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
542eef2e348d alpine:latest "sleep 1000000000" About an hour ago Up About an hour debug.0.4x82pcojee7378nb6z63mlc8l
[root@node01 dockercoins]# docker exec -ti 542eef2e348d sh
/ # hostname
542eef2e348d

安装性能测试工具, curl,abdrill:

1
/ # apk add --update curl apache2-utils drill

3.7.1 负载均衡模式

检查rng服务

1
2
3
4
5
6
7
8
9
10
/ # drill rng
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 16923
;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; rng. IN A

;; ANSWER SECTION:
rng. 600 IN A 10.0.0.6

....

可以看到rng服务的IP地址是10.0.0.6, 可是我们一共有5个rng服务, 其实这个IP地址是swarm集群分配给所有rng服务负载均衡的VIP.

swarm集群负载均衡service有两种方式VIPDNSRR:

  • VIP模式每个service会得到一个 virtual IP地址作为服务请求的入口. 基于virtual IP进行负载均衡.
  • DNSRR模式service利用DNS解析来进行负载均衡, 这种模式在旧的Docker Engine下, 经常行为诡异…所以不推荐.

如何查看service的负载均衡模式呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@node01 dockercoins]# docker service inspect rng
...
"EndpointSpec": {
"Mode": "vip"
}
},
"Endpoint": {
"Spec": {
"Mode": "vip"
},
"VirtualIPs": [
{
"NetworkID": "396fwpd5ja6azh93dshalmxro",
"Addr": "10.0.0.6/24"
}
]
},
...

指定一个service的模式, 可以在创建service的时候使用docker service create --endpoint-mode [VIP|DNSRR]参数.

修改一个service的模式, 使用如下命令:

1
docker service update --endpoint-mode [vip|dnssrr] <service name>

3.7.2 rng服务压力测试

介绍完负载均衡模式, 下面使用ab对我们的rng服务进行简单的压力测试.
测试之前, 我们要关掉所有的worker服务, 避免worker服务影响测试结果.

1
2
3
4
5
6
[root@node01 dockercoins]# docker service scale worker=0
worker scaled to 0
[root@node01 dockercoins]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
...
d7g0estex65u worker 0/0 localhost:5000/dockercoins_worker:v0.1

回到我们的debug容器中:

模拟一个客户端,发送50个请求给rng服务

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
42
43
44
45
46
/ # ab -c 1 -n 50 http://rng/10

This is ApacheBench, Version 2.3 <$Revision: 1748469 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking rng (be patient).....done


Server Software: Werkzeug/0.11.10
Server Hostname: rng
Server Port: 80

Document Path: /10
Document Length: 10 bytes

Concurrency Level: 1
Time taken for tests: 5.386 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 8250 bytes
HTML transferred: 500 bytes
Requests per second: 9.28 [#/sec] (mean)
Time per request: 107.716 [ms] (mean)
Time per request: 107.716 [ms] (mean, across all concurrent requests)
Transfer rate: 1.50 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 2 0.6 1 3
Processing: 103 106 1.5 106 110
Waiting: 102 105 1.2 105 109
Total: 104 107 1.7 107 112
WARNING: The median and mean for the initial connection time are not within a normal deviation
These results are probably not that reliable.

Percentage of the requests served within a certain time (ms)
50% 107
66% 108
75% 108
80% 108
90% 110
95% 110
98% 112
99% 112
100% 112 (longest request)

模拟50个并发客户端, 发送50个请求

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
42
43
44
/ # ab -c 50 -n 50 http://rng/10

This is ApacheBench, Version 2.3 <$Revision: 1748469 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking rng (be patient).....done


Server Software: Werkzeug/0.11.10
Server Hostname: rng
Server Port: 80

Document Path: /10
Document Length: 10 bytes

Concurrency Level: 50
Time taken for tests: 1.105 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 8250 bytes
HTML transferred: 500 bytes
Requests per second: 45.23 [#/sec] (mean)
Time per request: 1105.436 [ms] (mean)
Time per request: 22.109 [ms] (mean, across all concurrent requests)
Transfer rate: 7.29 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 7 9 1.3 9 12
Processing: 103 590 313.4 627 1087
Waiting: 103 589 313.3 626 1087
Total: 115 599 312.2 637 1095

Percentage of the requests served within a certain time (ms)
50% 637
66% 764
75% 869
80% 946
90% 1050
95% 1092
98% 1095
99% 1095
100% 1095 (longest request)

可以看出 单个客户端的时候rng的响应时间平均107.716ms, 多并发情况下增加到大约1000ms+.

3.7.3 hasher服务压力测试

hasher的服务测试稍微复杂点, 因为hasher服务需要POST一个随机的bytes数据.
所以我们需要先通过curl制作一个bytes数据文件:

1
/ # curl http://rng/10 > /tmp/random

模拟单客户端,发送50个请求给hasher服务:

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
42
43
44
45
46
47
48
/ # ab -c 1 -n 50 -T application/octet-stream -p /tmp/random http://hasher/


This is ApacheBench, Version 2.3 <$Revision: 1748469 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking hasher (be patient).....done


Server Software: thin
Server Hostname: hasher
Server Port: 80

Document Path: /
Document Length: 64 bytes

Concurrency Level: 1
Time taken for tests: 5.323 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 10450 bytes
Total body sent: 7250
HTML transferred: 3200 bytes
Requests per second: 9.39 [#/sec] (mean)
Time per request: 106.454 [ms] (mean)
Time per request: 106.454 [ms] (mean, across all concurrent requests)
Transfer rate: 1.92 [Kbytes/sec] received
1.33 kb/s sent
3.25 kb/s total

Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 1 0.4 1 3
Processing: 103 105 0.8 105 107
Waiting: 103 104 0.8 104 107
Total: 104 106 1.0 106 109

Percentage of the requests served within a certain time (ms)
50% 106
66% 106
75% 106
80% 107
90% 108
95% 108
98% 109
99% 109
100% 109 (longest request)

模拟50个并发客户端, 发送50个请求

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
42
43
44
45
46
47
48
/ # ab -c 50 -n 50 -T application/octet-stream -p /tmp/random http://hasher/


This is ApacheBench, Version 2.3 <$Revision: 1748469 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking hasher (be patient).....done


Server Software: thin
Server Hostname: hasher
Server Port: 80

Document Path: /
Document Length: 64 bytes

Concurrency Level: 50
Time taken for tests: 0.345 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 10450 bytes
Total body sent: 7250
HTML transferred: 3200 bytes
Requests per second: 144.95 [#/sec] (mean)
Time per request: 344.937 [ms] (mean)
Time per request: 6.899 [ms] (mean, across all concurrent requests)
Transfer rate: 29.59 [Kbytes/sec] received
20.53 kb/s sent
50.11 kb/s total

Connection Times (ms)
min mean[+/-sd] median max
Connect: 5 10 4.8 8 17
Processing: 131 214 71.5 238 323
Waiting: 126 207 72.2 231 322
Total: 147 224 67.2 246 328

Percentage of the requests served within a certain time (ms)
50% 246
66% 249
75% 252
80% 314
90% 324
95% 328
98% 328
99% 328
100% 328 (longest request)

从结果可以看出, 单客户端hasher平均响应时间106.454ms, 50并发平均响应时间344.937ms.
hasher服务并发响应时间稍慢, 不过比rng好点…

3.7.4 程序瓶颈分析

也许你注意到了, 单客户端请求的测试rng平均响应时间约107ms,hasher的响应时间约106ms.
所以可能是我们程序的最大瓶颈了, 为啥每个请求的响应时间都在100ms以上呢.

我们折腾了三章, 搭了swarm集群, Scale了应用.为啥单个请求的时间都在100ms以上呢.
咳咳, 当你做了很多的优化, 想了很多可能, 应用的性能还是上不去, 这时候一般都是开发挖的坑!!!! 嘿嘿.
下一章, 我们会去看看rnghasher的代码, 找到响应时间在100ms的原因. 更新代码, 试图缩短响应时间.
当然这个系列文章是介绍docker swarm的. 所以下一章重点介绍我们的应用程序更新以后如何使用swarm集群roll update容器服务.还会介绍如何搭建一个ELK架构的日志分析平台, 收集分析docker币应用的日志.