-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path15281970486767.html
535 lines (304 loc) · 24.5 KB
/
15281970486767.html
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
最小权限的容器编排 - Junkman
</title>
<link href="atom.xml" rel="alternate" title="Junkman" type="application/atom+xml">
<link rel="stylesheet" href="asset/css/foundation.min.css" />
<link rel="stylesheet" href="asset/css/docs.css" />
<script src="asset/js/vendor/modernizr.js"></script>
<script src="asset/js/vendor/jquery.js"></script>
<script src="asset/highlightjs/highlight.pack.js"></script>
<link href="asset/highlightjs/styles/github.css" media="screen, projection" rel="stylesheet" type="text/css">
<script>hljs.initHighlightingOnLoad();</script>
<script type="text/javascript">
function before_search(){
var searchVal = 'site:panlw.github.io ' + document.getElementById('search_input').value;
document.getElementById('search_q').value = searchVal;
return true;
}
</script>
</head>
<body class="antialiased hide-extras">
<div class="marketing off-canvas-wrap" data-offcanvas>
<div class="inner-wrap">
<nav class="top-bar docs-bar hide-for-small" data-topbar>
<section class="top-bar-section">
<div class="row">
<div style="position: relative;width:100%;"><div style="position: absolute; width:100%;">
<ul id="main-menu" class="left">
<li id=""><a target="self" href="index.html">Home</a></li>
<li id=""><a target="_self" href="archives.html">Archives</a></li>
</ul>
<ul class="right" id="search-wrap">
<li>
<form target="_blank" onsubmit="return before_search();" action="http://google.com/search" method="get">
<input type="hidden" id="search_q" name="q" value="" />
<input tabindex="1" type="search" id="search_input" placeholder="Search"/>
</form>
</li>
</ul>
</div></div>
</div>
</section>
</nav>
<nav class="tab-bar show-for-small">
<a href="javascript:void(0)" class="left-off-canvas-toggle menu-icon">
<span> Junkman</span>
</a>
</nav>
<aside class="left-off-canvas-menu">
<ul class="off-canvas-list">
<li><a href="index.html">HOME</a></li>
<li><a href="archives.html">Archives</a></li>
<li><a href="about.html">ABOUT</a></li>
<li><label>Categories</label></li>
<li><a href="Infra.html">Infra</a></li>
<li><a href="Coding.html">Coding</a></li>
<li><a href="Modeling.html">Modeling</a></li>
<li><a href="Archtecting.html">Archtecting</a></li>
</ul>
</aside>
<a class="exit-off-canvas" href="#"></a>
<section id="main-content" role="main" class="scroll-container">
<script type="text/javascript">
$(function(){
$('#menu_item_index').addClass('is_active');
});
</script>
<div class="row">
<div class="large-8 medium-8 columns">
<div class="markdown-body article-wrap">
<div class="article">
<h1>最小权限的容器编排</h1>
<div class="read-more clearfix">
<span class="date">2018/6/5</span>
<span>posted in </span>
<span class="posted-in"><a href='Container.html'>Container</a></span>
<span class="comments">
</span>
</div>
</div><!-- article -->
<div class="article-content">
<blockquote>
<p>作者:Diogo Mónica[3] 译者:qhwdw 校对:wxy<br/>
本文由 LCTT 原创编译,Linux 中国 荣誉推出 <a href="https://linux.cn/article-9712-1.html">https://linux.cn/article-9712-1.html</a><br/>
via: <a href="https://blog.docker.com/2017/10/least-privilege-container-orchestration/">https://blog.docker.com/2017/10/least-privilege-container-orchestration/</a></p>
</blockquote>
<p>Docker 平台和容器已经成为打包、部署、和管理应用程序的标准。为了在一个集群内协调跨节点的容器,必须有一个关键的能力:一个容器编排器。</p>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215736pd1dhdoumu8ilhdi.jpg" alt=""/></p>
<p><u>container orchestrator</u></p>
<p>对于关键的集群化以及计划的任务,编排器是起重大作用的,比如:</p>
<ul>
<li> 管理容器计划和资源分配。</li>
<li> 支持服务发现和无缝的应用程序部署。</li>
<li> 分配应用程序运行必需的资源。</li>
</ul>
<p>不幸的是,在这种环境下,编排器的分布式特性和资源的短暂性使得确保编排器的安全是一个极具挑战性的任务。在这篇文章中,我们将讨论容器编排器安全模型中没有考虑到的、但是很重要的这方面的详细情况,以及 Docker 企业版中如何使用内置的编排性能、Swarm 模式,去克服这些问题。</p>
<p><a id="3_1089" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h3 id="toc_0">诱因和威胁模型</h3>
<p>使用 swarm 模式的 Docker 企业版的其中一个主要目标是提供一个内置安全特性的编排器。为达到这个目标,我们部署了第一个在我们心目中认为的以最小权限原则设计的容器编排器。</p>
<p>在计算机科学中,一个分布式系统所要求的最小权限原则是,系统中的每个参与者仅仅能访问它正当目的所需要的信息和资源。不是更多,也不是更少。</p>
<blockquote>
<p>“一个进程必须仅仅能去访问它的正当目的所需要的信息和资源”</p>
</blockquote>
<p><a id="4_1721" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h4 id="toc_1">最小权限原则</h4>
<p>在一个 Docker 企业版集群中的每个节点分配的角色:既不是管理者(manager),也不是工人(worker)。这些角色为节点定义了一个很粗粒度的权限级别:分别进行管理和任务执行。尽管如此,不用理会它的角色,通过使用加密的方式,来保证一个节点仅仅有执行它的任务所需要的信息和资源。结果是,确保集群安全变得更容易了,甚至可以防止大多数的有经验的攻击者模式:攻击者控制了底层通讯网络,或者甚至攻陷了集群节点。</p>
<p><a id="3_2340" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h3 id="toc_2">内核缺省安全</h3>
<p>这是一个很老的安全最大化状态:如果它不是缺省的,就没人用它。Docker Swarm 模式将缺省安全这一概念融入了核心,并且使用这一机制去解决编排器生命周期中三个很难并且很重要的部分:</p>
<ol>
<li> 可信引导和节点引入。</li>
<li> 节点身份发布和管理。</li>
<li> 认证、授权和加密的信息存储和传播。</li>
</ol>
<p>我们来分别看一下这三个部分:</p>
<p><a id="4_2847" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h4 id="toc_3">可信引导和节点引入</h4>
<p>确保集群安全的第一步,没有别的,就是严格控制成员和身份。管理员不能依赖它们节点的身份,并且在节点之间强制实行绝对的负载隔离。这意味着,未授权的节点不能允许加入到集群中,并且,已经是集群一部分的节点不能改变身份,突然去伪装成另一个节点。</p>
<p>为解决这种情况,通过 Docker 企业版 Swarm 模式管理的节点,维护了健壮的、不可改变的身份。期望的特性是,通过使用两种关键的构建块去保证加密:</p>
<ol>
<li> 为集群成员使用<ruby>安全加入令牌 <rt>Secure join token</rt></ruby>。</li>
<li> 从一个集中化的认证机构发行的内嵌唯一身份的证书。</li>
</ol>
<p><a id="5_3651" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h5 id="toc_4">加入 Swarm</h5>
<p>要加入 Swarm,节点需要一份<ruby>安全加入令牌 <rt>Secure join token</rt></ruby> 的副本。在集群内的每个操作角色的令牌都是独一无二的 —— 现在有两种类型的节点:工人(workers)和管理者(managers)。由于这种区分,拥有一个工人令牌的节点将不允许以管理者角色加入到集群。唯一得到这个特殊令牌的方式是通过 swarm 的管理 API 去向集群管理者请求一个。</p>
<p>令牌是安全的并且是随机生成的,它还有一个使得令牌泄露更容易被检测到的特殊语法:一个可以在你的日志和仓库中很容易监视的特殊前缀。幸运的是,即便发现一个泄露,令牌也可以很容易去更新,并且,推荐你经常去更新它们 —— 特别是,在一段时间中你的集群不进行扩大的情况下。</p>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215739qv7uv8sbyv87hfvx.jpg" alt=""/></p>
<p><u>Docker Swarm</u></p>
<p><a id="5_4684" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h5 id="toc_5">引导信任</h5>
<p>作为它的身份标识创建的一部分,一个新的节点将向任意一个网络管理者请求发布一个新的身份。但是,在我们下面的威胁模型中,所有的通讯可以被一个第三方拦截。这种请求存在的问题是:一个节点怎么知道与它进行对话的对方是合法的管理者?</p>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215739sbxzyy3i5yi7yv41.jpg" alt=""/></p>
<p><u>Docker Security</u></p>
<p>幸运的是,Docker 有一个内置机制可以避免这种情况。这个加入令牌被主机用于加入 Swarm,包含了一个根 CA 证书的哈希串。所以,主机可以使用单向 TLS,并且使用这个哈希串去验证它加入的 Swarm 是否正确:如果管理者持有的证书没有被正确的 CA 哈希串签名,节点就知道它不可信任。</p>
<p><a id="4_5558" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h4 id="toc_6">节点身份发布和管理</h4>
<p>在一个 Swarm 中,身份标识是内嵌在每个节点都单独持有的一个 x509 证书中。在一个最小权限原则的表现形式中,证书的私钥被绝对限制在主机的原始位置。尤其是,管理者不能访问除了它自己的私钥以外的任何一个私钥。</p>
<p><a id="5_5908" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h5 id="toc_7">身份发布</h5>
<p>要接收它们的证书而无需共享它们的私钥,新的主机通过发布一个证书签名请求(CSR)来开始,管理者收到证书签名请求之后,转换成一个证书。这个证书成为这个新的主机的身份标识,使这个节点成为 Swarm 的一个完全合格成员!</p>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215742n1acrwxccxngwici.jpg" alt=""/></p>
<p>当和安全引导机制一起使用时,发行身份标识的这个机制来加入节点是缺省安全的:所有的通讯部分都是经过认证的、授权的,并且非敏感信息从来都不会以明文方式进行交换。</p>
<p><a id="5_6601" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h5 id="toc_8">身份标识延期</h5>
<p>尽管如此,给一个 Swarm 中安全地加入节点,仅仅是 “故事” 的一部分。为降低证书的泄露或者失窃造成的影响,并且移除管理 CRL 列表的复杂性,Swarm 模式为身份标识使用了较短存活周期的证书。这些证书缺省情况下三个月后将过期,但是,也可以配置为一个小时后即刻过期!</p>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215742hud947n983d3s7a8.jpg" alt=""/></p>
<p><u>Docker secrets</u></p>
<p>较短的证书过期时间意味着不能手动去处理证书更新,所以,通常会使用一个 PKI 系统。对于 Swarm,所有的证书是以一种不中断的方式进行自动更新的。这个过程很简单:使用一个相互认证的 TLS 连接去证明一个特定身份标识的所有者,一个 Swarm 节点定期生成一个新的公钥 / 私钥密钥对,并且用相关的 CSR 去签名发送,创建一个维持相同身份标识的完整的新证书。</p>
<p><a id="4_7631" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h4 id="toc_9">经过认证、授权、和加密的信息存储和传播。</h4>
<p>在一个正常的 Swarm 的操作中,关于任务的信息被发送给去运行的工人(worker)节点。这里不仅仅包含将被一个节点运行的容器的信息;也包含那个容器运行所必需的资源的所有信息,包括敏感的机密信息,比如,私钥、密码和 API 令牌。</p>
<p><a id="5_8035" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h5 id="toc_10">传输安全</h5>
<p>事实上,参与 Swarm 的每个节点都拥有一个独一无二的 X509 格式的证书,因此,节点之间的通讯安全是没有问题的:节点使用它们各自的证书,与另一个连接方进行相互认证、继承机密、真实性、和 TLS 的完整性。</p>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215744kv6j5j6118rrrlrl.jpg" alt=""/></p>
<p><u>Swarm Mode</u></p>
<p>关于 Swarm 模式的一个有趣的细节是,本质上它是使用了一个推送模式:仅管理者被允许去发送信息到工人们(workers)—— 显著降低了暴露在低权限的工人节点面前的管理者节点的攻击面。</p>
<p><a id="5_8737" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h5 id="toc_11">将负载严格隔离进安全区域</h5>
<p>管理者节点的其中一个责任是,去决定发送到每个工人(worker)节点的任务是什么。管理者节点使用多种策略去做这个决定;根据每个节点和每个负载的特性,去跨 Swarm 去安排负载。</p>
<p>在使用 Swarm 模式的 Docker 企业版中,管理者节点通过使用附加到每个单个节点标识上的安全标签,去影响这些安排决定。这些标签允许管理者将节点组与不同的安全区域连接到一起,以限制敏感负载暴露,以及使相关机密信息更安全。</p>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215744hfd4zgcgzfrmthde.jpg" alt=""/></p>
<p><u>Docker Swarm Security</u></p>
<p><a id="5_9488" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h5 id="toc_12">安全分发机密</h5>
<p>除了加快身份标识发布过程之外,管理者节点还有存储和分发工人节点所需要的任何资源的任务。机密信息像任何其它类型的资源一样处理,并且基于安全的 mTLS 连接,从管理者推送到工人节点。</p>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215745uoodd3k3e43kivvv.jpg" alt=""/></p>
<p><u>Docker Secrets</u></p>
<p>在主机上,Docker 企业版能确保机密仅提供给它们指定的容器。在同一个主机上的其它容器并不能访问它们。Docker 以一个临时文件系统的方式显露机密给一个容器,确保机密总是保存在内存中,并且从不写入到磁盘。这种方式比其它竞争的替代者更加安全,比如,<a href="https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/">在环境变量中存储它们</a>。一旦这个任务完成,这个机密将永远消失。</p>
<p><a id="5_10460" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h5 id="toc_13">存储机密</h5>
<p>在管理者主机上的机密总是保持加密的。缺省情况下,加密这些机密的密钥(被称为数据加密密钥,DEK)是以明文的方式存储在硬盘上的。这使得那些对安全性要求较低的人可以轻松地去使用 Docker Swarm 模式。</p>
<p>但是,如果你运行一个生产集群,我们推荐你启用自动锁定模式。当自动锁定模式启用后,一个重新更新过的 DEK 被一个独立的加密密钥的密钥(KEK)所加密。这个密钥从不被存储在集群中;管理者有责任将它存储在一个安全可靠的地方,并且当集群启动的时候可以提供它。这就是所谓的 “解锁” Swarm。</p>
<p>根据 Raft 故障容错一致性算法,Swarm 模式支持多个管理者。在这个场景中,无缝扩展了机密存储的安全性。每个管理者主机除了共享密钥之外,还有一个唯一的磁盘加密密钥。幸运的是,Raft 日志在磁盘上也是加密的,并且,在自动锁定模式下,没有 KEK 同样是不可访问的。</p>
<p><a id="4_11568" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h4 id="toc_14">当一个节点被攻陷后发生了什么?</h4>
<p><img src="https://dn-linuxcn.qbox.me/data/attachment/album/201806/03/215746h7dbkd33obobtk66.jpg" alt=""/></p>
<p><u>Docker Secrets</u></p>
<p>在传统的编排器中,挽回一台被攻陷的主机是一个缓慢而复杂的过程。使用 Swarm 模式,恢复它就像运行一个 Docker 节点的 <code>rm</code> 命令一样容易。这是从集群中删除一个受影响的节点,而 Docker 将去处理剩下的事情,即,重新均衡负载,并且确保其它的主机已经知道,而不会去与受影响的节点通讯。</p>
<p>正如我们看到的那样,感谢最小权限的编排器,甚至是,如果攻击者在主机上持续活动,它们将被从剩余的网络上切断。主机的证书 —— 它的身份标识 —— 被列入黑名单,因此,管理者也不能有效访问它。</p>
<p><a id="3_12461" class="target-fix ext" rel="external nofollow" target="_blank"></a></p>
<h3 id="toc_15">结论</h3>
<p>使用 Swarm 模式的 Docker 企业版,在缺省情况下确保了编排器的所有关键区域的安全:</p>
<ul>
<li> 加入集群。阻止恶意节点加入到集群。</li>
<li> 把主机分组为安全区域。阻止攻击者的横向移动。</li>
<li> 安排任务。任务将仅被委派到允许的节点。</li>
<li> 分配资源。恶意节点不能 “盗取” 其它的负载或者资源。</li>
<li> 存储机密。从不明文保存并且从不写入到工人节点的磁盘上。</li>
<li> 与工人节点的通讯。使用相互认证的 TLS 加密。</li>
</ul>
<p>因为 Swarm 模式的持续改进,Docker 团队正在努力将最小权限原则进一步推进。我们正在处理的一个任务是:如果一个管理者被攻陷了,怎么去保证剩下的节点的安全?路线图已经有了,其中一些功能已经可以使用,比如,白名单功能,仅允许特定的 Docker 镜像,阻止管理者随意运行负载。这是通过 Docker 可信内容来实现的。</p>
<hr/>
<p>via: <a href="https://blog.docker.com/2017/10/least-privilege-container-orchestration/">https://blog.docker.com/2017/10/least-privilege-container-orchestration/</a></p>
<p>作者:<a href="https://blog.docker.com/author/diogo/">Diogo Mónica</a> 译者:<a href="https://github.com/qhwdw">qhwdw</a> 校对:<a href="https://github.com/wxy">wxy</a></p>
</div>
<div class="row">
<div class="large-6 columns">
<p class="text-left" style="padding:15px 0px;">
<a href="15282768663638.html"
title="Previous Post: Part 1: Building a Centralized Logging Application">« Part 1: Building a Centralized Logging Application</a>
</p>
</div>
<div class="large-6 columns">
<p class="text-right" style="padding:15px 0px;">
<a href="15281306050002.html"
title="Next Post: 分布式之 redis 复习精讲">分布式之 redis 复习精讲 »</a>
</p>
</div>
</div>
<div class="comments-wrap">
<div class="share-comments">
<script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5ae58078c0d7b2ab"></script>
</div>
</div>
</div><!-- article-wrap -->
</div><!-- large 8 -->
<div class="large-4 medium-4 columns">
<div class="hide-for-small">
<div id="sidebar" class="sidebar">
<div id="site-info" class="site-info">
<div class="site-a-logo"><img src="./asset/img/logo.jpg" /></div>
<h1>Junkman</h1>
<div class="site-des">“拾荒者”一词来自凯文・凯利的《失控》中关于机器学习的故事(“收集癖好机”如何完成他的收集工作)。</div>
<div class="social">
<a target="_blank" class="github" target="_blank" href="https://github.com/panlw/" title="GitHub">GitHub</a>
<a target="_blank" class="rss" href="atom.xml" title="RSS">RSS</a>
</div>
</div>
<div id="site-categories" class="side-item ">
<div class="side-header">
<h2>Categories</h2>
</div>
<div class="side-content">
<p class="cat-list">
<a href="Infra.html"><strong>Infra</strong></a>
<a href="Coding.html"><strong>Coding</strong></a>
<a href="Modeling.html"><strong>Modeling</strong></a>
<a href="Archtecting.html"><strong>Archtecting</strong></a>
</p>
</div>
</div>
<div id="site-categories" class="side-item">
<div class="side-header">
<h2>Recent Posts</h2>
</div>
<div class="side-content">
<ul class="posts-list">
<li class="post">
<a href="15517999043443.html">The Art of Crafting Architectural Diagrams</a>
</li>
<li class="post">
<a href="15517997955971.html">为什么说我们需要软件架构图?</a>
</li>
<li class="post">
<a href="15516128677869.html">DNS Servers That Offer Privacy and Filtering</a>
</li>
<li class="post">
<a href="15516123108194.html">Airbnb's Migration from Monolith to Services</a>
</li>
<li class="post">
<a href="15516097487470.html">Events As First-Class Citizens</a>
</li>
</ul>
</div>
</div>
</div><!-- sidebar -->
</div><!-- hide for small -->
</div><!-- large 4 -->
</div><!-- row -->
<div class="page-bottom clearfix">
<div class="row">
<p class="copyright">Copyright © 2015
Powered by <a target="_blank" href="http://www.mweb.im">MWeb</a>,
Theme used <a target="_blank" href="http://github.com">GitHub CSS</a>.</p>
</div>
</div>
</section>
</div>
</div>
<script src="asset/js/foundation.min.js"></script>
<script>
$(document).foundation();
function fixSidebarHeight(){
var w1 = $('.markdown-body').height();
var w2 = $('#sidebar').height();
if (w1 > w2) { $('#sidebar').height(w1); };
}
$(function(){
fixSidebarHeight();
})
$(window).load(function(){
fixSidebarHeight();
});
</script>
<script src="asset/chart/all-min.js"></script><script type="text/javascript">$(function(){ var mwebii=0; var mwebChartEleId = 'mweb-chart-ele-'; $('pre>code').each(function(){ mwebii++; var eleiid = mwebChartEleId+mwebii; if($(this).hasClass('language-sequence')){ var ele = $(this).addClass('nohighlight').parent(); $('<div id="'+eleiid+'"></div>').insertAfter(ele); ele.hide(); var diagram = Diagram.parse($(this).text()); diagram.drawSVG(eleiid,{theme: 'simple'}); }else if($(this).hasClass('language-flow')){ var ele = $(this).addClass('nohighlight').parent(); $('<div id="'+eleiid+'"></div>').insertAfter(ele); ele.hide(); var diagram = flowchart.parse($(this).text()); diagram.drawSVG(eleiid); } });});</script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script><script type="text/x-mathjax-config">MathJax.Hub.Config({TeX: { equationNumbers: { autoNumber: "AMS" } }});</script>
</body>
</html>