前情提要

懒, 没提要了, 前几章链接如下:

第四章 swarm集群运维

上一章中我们终于把docker币, 跑在swarm集群上了. 不过性能还是没有达到我们的预期, 经过性能测试我们发现所有的应用响应时间都在100ms以上.

为什么呢

下面我们来一起看看 rnghasher应用的源代码.

1
2
3
4
[root@node01 ~]# cd orchestration-workshop/dockercoins/
[root@node01 dockercoins]# ls
... hasher ports.yml rng webui worker
# 进入dockercoins应用的目录, 你会看到 hasher rng webui worker 几个目录

每个应用的源码程序就在这几个目录中:
我们一起来看看rng的源码:

1
2
3
4
5
6
7
8
9
[root@node01 dockercoins]# cat rng/rng.py
...
def rng(how_many_bytes):
# Simulate a little bit of delay
time.sleep(0.1)
return Response(
os.read(urandom, how_many_bytes),
content_type="application/octet-stream")
...

哈哈, 看到了没, time.sleep(0.1)每个请求会自动延迟100ms响应.
hasherworker的也一样.

1
2
3
4
5
6
7
8
9
10
[root@node01 dockercoins]# cat hasher/hasher.rb
...

post '/' do
# Simulate a bit of delay
sleep 0.1
content_type 'text/plain'
"#{Digest::SHA2.new().update(request.body.read)}"
end
...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@node01 dockercoins]# cat worker/worker.py
...
def work_once():
log.debug("Doing one unit of work")
time.sleep(0.1)
random_bytes = get_random_bytes()
hex_hash = hash_bytes(random_bytes)
if not hex_hash.startswith('0'):
log.debug("No coin found")
return
log.info("Coin found: {}...".format(hex_hash[:8]))
created = redis.hset("wallet", hex_hash, random_bytes)
if not created:
log.info("We already had that coin")
...

哈哈, 现在你知道100ms延迟的原因了吧. 下面我们以worker服务为例, 更新下源代码缩短下响应时间.
目的是 演示下当我们的应用代码更新的时候, 如何利用docker swarm集群滚动更新我们的服务副本容器,要知道我们是有多个worker副本容器的.

4.1 Rolling Updates

swarm 集群的环境下, 我们每个服务都会有多个容器副本, 如何在不停止应用的情况下滚动更新每个容器副本就十分重要了, docker swarm集群为我们提供了方便的命令.
那么如果我们要发布一个新版本的worker服务需要做什么呢:

  1. 更新worker服务的源代码, 缩短time.sleep(0.1)0.01.
  2. 重新buildworker镜像, 使用一个新的tag版本.
  3. push新镜像到我们本地的镜像仓库.
  4. 滚动更新worker服务, 使用新的镜像

在开始之前, 如果你一直跟着文章做的, 上一章为了性能测试我们已经把worker服务副本scale成0了, 我们先恢复成10个副本.

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

4.2 更新应用代码

下面我们来更新worker服务的代码~/orchestration-workshop/dockercoins/worker/worker.pysleep时间从0.1改成0.01.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@node01 dockercoins]# cat worker/worker.py
...
def work_once():
log.debug("Doing one unit of work")
time.sleep(0.01)
random_bytes = get_random_bytes()
hex_hash = hash_bytes(random_bytes)
if not hex_hash.startswith('0'):
log.debug("No coin found")
return
log.info("Coin found: {}...".format(hex_hash[:8]))
created = redis.hset("wallet", hex_hash, random_bytes)
if not created:
log.info("We already had that coin")
...

4.3 重新build, push镜像

重新build我们的worker服务镜像, tag为v0.0.1

1
2
3
4
5
6
7
[root@node01 dockercoins]# docker build -t localhost:5000/dockercoins_worker:v0.01 worker
[root@node01 dockercoins]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost:5000/dockercoins_worker v0.01 a9cb198fcb8c 21 seconds ago 80.5 MB
...
localhost:5000/dockercoins_worker v0.1 bef5d2dc4bd0 7 days ago 80.5 MB
...

Note 我们新build镜像tag不一样, 一般都版本号, 我们以sleeptime作为版本号, 方便区分. 新TAG是v0.0.1.

Push镜像到本地镜像库

1
[root@node01 dockercoins]# docker push localhost:5000/dockercoins_worker:v0.01

4.4 滚动更新worker服务

为了跟踪查询我们worker服务在滚动更新中的状态, 我们新开一个ssh窗口连接node01, 执行watch命令, 监控worker服务状态:
如下命令可以打开一个监控窗口, 每秒监控我们worker服务的状态变化:

1
[root@node01 ~]# watch -n1 "docker service ps worker -a | grep -v Shutdown.*Shutdown"

下面我们有了新worker服务的镜像v0.01, 我们来滚动更新我们的10个worker:

这里只用docker service update 命令, --update-parallelism指定每次update的容器数量, --update-delay 每次更新之后的等待时间.--image后面跟服务镜像名称

1
2
3
4
5
6
7
8
## 确认我们worker服务的版本`v0.1`
[root@node01 dockercoins]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
...
d7g0estex65u worker 10/10 localhost:5000/dockercoins_worker:v0.1
## 滚动更新我们`worker`, 每次更新2个副本容器, 延迟`5s`
[root@node01 dockercoins]# docker service update worker --update-parallelism 2 --update-delay 5s --image localhost:5000/dockercoins_worker:v0.01
worker

成功以后, 注意观察刚刚我们打开的监控窗口, 你会看到worker会一次2个容器副本的频率更新到v0.01版本.

1
2
3
4
5
[root@node01 ~]# docker service ls
ID NAME REPLICAS IMAGE COMMAND
...
d7g0estex65u worker 10/10 localhost:5000/dockercoins_worker:v0.01
...

全部update完成后, 你可以看到我们的worker镜像已经都更新到最新版v0.0.1版本了.
在去看看我们的webui每秒钟产生的docker币40个了, 哈哈^_^.

4.5 容器服务回滚

如果我们发现, 新版本worker有问题希望回滚怎么办呢.
很简单, 跟上面更新的命令一样啊, 直接只用v0.1版本的镜像就可以了.
上面我们演示了滚动更新, 如果你希望一次性都更新或者回滚呢, 更简单了, 不加参数就行了.

下面我们一次性回滚所有worker服务到v0.1版本

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

可以看到worker服务都回滚到v0.1版本了.
好了,后面你可以自己折腾下了, 尝试scale下几个服务, 尝试更新下rnghasher几个应用, 看看最多能挖到每秒多少docker币,嘿嘿!!!
当然这不是比特币哈, 挖了再多也不能用来买东西, 哈哈, have fun~~.

4.6 ELK日志平台

现在我们有了docker swarm集群, 上面跑了docker币应用, 以后我们还会有很多的应用跑在集群上面, 如何监控每个应用的运行状态呢.
所以我们需要一个日志平台, 用来收集分析展示, 所有swarm集群上应用的日志.

在本小节我们就一起来搭建一个ELK日志平台, 什么是ELK stack?
ELK 主要有三个部分组成:

  • ElasticSearch 用来存储和索引日志.
  • Logstash 用来接收, 发送, 过滤, 分隔日志.
  • Kibana 用来搜索, 展示, 分析日志的UI

    我们在会swarm集群的每个节点使用syslog协议发送日志到logstash, 存储在elasticsearch中, 最后使用kibana分析展示日志.

4.6.1 配置elasticsearch

创建一个overlay网络logging用于ELK平台日志收集

1
2
[root@node01 ~]# docker network create --driver overlay logging
biucttiifbj9ak4eh5kn07zqq

创建ElasticSearch服务

1
2
[root@node01 ~]# docker service create --network logging --name elasticsearch elasticsearch
8ts6wfiufj7gsj4cm34drb56x

4.6.2 配置Kibana

配置Kibana服务, 发布5601端口, 用于UI访问.

1
2
[root@node01 ~]# docker service create --network logging --name kibana --publish 5601:5601 \
-e LOGSPOUT=ignore -e ELASTICSEARCH_URL=http://elasticsearch:9200 kibana

4.6.3 配置logstash

创建Logstash服务, logstash需要一些配置去监听syslog日志, 把他们发送到elasticsearch中.

1
2
3
[root@node01 ~]# docker service create --network logging --name logstash \
-e LOGSPOUT=ignore logstash \
-e "$(cat ~/orchestration-workshop/elk/logstash.conf)"

Note cat ~/orchestration-workshop/elk/logstash.conf 这个文件是git库里面带的logstash配置文件, 如果你的路径不同注意替换.

1
2
3
4
5
[root@node01 ~]# docker service ps logstash
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
1lqb12g8owvqwji3wueyy4ltg logstash.1 logstash node01 Running Preparing 47 seconds ago
[root@node01 ~]# ssh node01
Last login: Mon Aug 8 08:45:40 2016 from 192.168.33.101

下面一段用于验证logstash服务, 由于docker hub 连接问题, 你可能很难pull到image, 确保你的service运行成功. 当然你也可以忽略下面一端. 有问题的话再回来验证.

验证logstash服务:

1
2
3
4
5
6
7
8
9
10
[root@node01 ~]# CID=$(docker ps -q --filter label=com.docker.swarm.service.name=logstash)
[root@node01 ~]# docker logs --follow $CID
{:timestamp=>"2016-08-08T08:56:32.639000+0000", :message=>"Pipeline main started"}
{
"message" => "ok",
"host" => "57c83db57ba2",
"@version" => "1",
"@timestamp" => "2016-08-08T08:56:30.539Z"
}
^C

现在开启一个新的ssh窗口, 我们测试下生成syslog日志,发送到logstash.

1
2
[root@node01 ~]# docker service create --network logging --restart-condition none debian \
logger -n logstash -P 51415 hello world

4.6.4 启动logspout服务

logspout运行在docker服务器节点上, 用于将docker服务器上运行的容器日志输出到logstash中.所以我们要在swarm集群的每个节点上运行这个容器, 采用global模式. 这里我们直接使用一个编译好的docker镜像来运行logspout服务.

关于logspout的更多信息, 请访问 https://github.com/gliderlabs/logspout

设置日志logspout, 将docker host日志输入到logstash:

1
2
3
[root@node01 ~]# docker service create --network logging --name logspout --mode global \
--mount source=/var/run/docker.sock,type=bind,target=/var/run/docker.sock \
-e SYSLOG_FORMAT=rfc3164 gliderlabs/logspout syslog://logstash:51415

4.6.5 访问Kibana

前面我们配置了Kibana, 发布了5601端口用于访问, 下面使用浏览器访问
http://192.168.33.101:5601/
点击Time-field name选择@timestamp, 点击Create.

然后点击屏幕上面的Discover.

下面你就能看见日志收集情况了, 为了方便查看我们再来配置下刷新时间.
点击右上角的Last 15 minutes选择Last 1 Hour, 点击Auto-refresh选择5 seconds.

配置下Fields, 在屏幕左边host,logsource,program后面点击add.

好了, kiban的简单配置就到完成了,最后你可以看到这样的效果:

注意日志的host列显示日志来自swram集群的哪个节点.
logsource列显示日志来日与那个容器, program列显示的是容器的名字, 你可以看到我们的服务名字.
message列就是日志的内容了.

关于ELK日志平台还有很多内容, 你可以在kibana上分析日志, 生成图表等等. 这里就不多介绍了.

最后

整个docker 1.12 swarm集群实战系列教程就结束了, 感谢大家的观看.
呃, 后面还有补充的第五章~