-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
596 lines (339 loc) · 270 KB
/
atom.xml
File metadata and controls
596 lines (339 loc) · 270 KB
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
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>ShenH.'s Blog</title>
<icon>https://www.gravatar.com/avatar/543fc8a83d9b480e5f69c3842db96518</icon>
<subtitle>Learn Anything, Anytime, Anywhere~</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://readailib.com/"/>
<updated>2019-03-27T08:30:31.520Z</updated>
<id>https://readailib.com/</id>
<author>
<name>ShenHengheng</name>
<email>shenhengheng17g@ict.ac.cn</email>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>物联网和边缘计算</title>
<link href="https://readailib.com/2019/03/27/kubernetes/edge-computing/"/>
<id>https://readailib.com/2019/03/27/kubernetes/edge-computing/</id>
<published>2019-03-27T08:18:50.000Z</published>
<updated>2019-03-27T08:30:31.520Z</updated>
<content type="html"><![CDATA[<h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><p>当今许多开发团队的都在关注云原生应用开发, 这其实是有原因的。比如微服务和 devops 等概念,从根本上改变了开发团队构建应用程序的方式。</p><p>但还有另一类应用程序, 乍一看并不属于这个世界(IT)。物联网和 Edge 应用程序有很多分布式组件, 一方面,这些组件通常不在同一数据中心基础架构之中。另一方面, 这些应用程序的开发人员将大大受益于云原生环境下中正在开发的概念、基础架构和工具。 </p><p>本文件的目的是努力将这两个世界联系在一起, 为今后的工作奠定基础。为了做到这一点, 我们需要从定义<strong>常用术语</strong>并确保我们彼此了解。 </p><p>接下来, 我们可以开始定义一些我们今天所用到的<strong>基本概念、用例和架构</strong>。请注意, 我们不会尝试深入讨论一般的物联网和边缘计算讨论, 因为有一些优秀的论文已经详细介绍了这些主题, 您可以在参考文献中找到它们。</p><p>最后, 我们可以将 Kubernetes 作为开发物联网和边缘应用程序的平台。对于一些已确定的用例,我们将尝试定义<strong>使用**</strong>kubernete<strong>**进行管理的集群布局</strong>。另外还会介绍了当前在部署一些工作负载和用例方面存在的挑战。</p><p>希望在我们的总体目标的基础之上,这将给我们<strong>未来的工作奠定</strong>足够的<strong>基础</strong>,改进文档并改进 Kubernetes, 使其成为物联网和边缘计算的最佳平台。但同时, 在必要时启动新项目, 使云原生计算和边缘计算更加紧密地结合在一起。</p><h2 id="什么是边缘"><a href="#什么是边缘" class="headerlink" title="什么是边缘"></a>什么是边缘</h2><p>当在不同的环境下讨论IoT和边缘计算,你很快就会认识到这些概念对于不同的人来说意味着不同的东西,因此让我们花点时间来定义这些术语,以方便下文的理解。 </p><p>通过<strong>物联网</strong> <strong>(Iot)</strong>, 我们将主要讨论如何<strong>将设备连接到云</strong>。这些设备是数据的来源, 它们使我们能够<strong>感知环境</strong>。但它们也是数据接收器, 因为它们通过接收来自云 (或本地) 应用程序的命令来<strong>控制环境</strong>。物联网解决方案的典型示例是下一代工厂自动化, 通常称为工业物联网 (或 IIoT)、智能家居、智慧城市等。 我们将在接下来的章节中更详细地描述与物联网相关的所有概念, 但总的来说, 我们将重点介绍如何使用 Kubernetes 改进物联网系统的部署和操作。</p><p><strong>边缘计算</strong> <strong>(Edge Computing**</strong>)<strong> 是一个更泛化的术语, 在其最一般的背景下, </strong>与使计算机资源更接近终端用户和终端设备**。例如, 在物联网解决方案中, 设备通常是在地理上分散的, 因此, 出于多种原因 (我们将很快讨论), 将数据处理尽可能靠近设备的位置可能是有益的。还有许多其他有趣的非物联网相关的边缘应用程序, 如低延迟游戏服务器等等。但是, 这里的边缘是一种从主要的云数据中心分布在地理上分散的计算机资源。因此, 主要的挑战是我们如何扩展云中使用的资源管理系统, 以管理边缘资源。在此背景下, 我们的目标是描述与边缘计算相关的所有概念, 并了解如何使用 (以及在多大程度上) 来应对预期的挑战。</p><h2 id="为什么边缘"><a href="#为什么边缘" class="headerlink" title="为什么边缘"></a>为什么边缘</h2><p>正如我们前面提到的, 有多种原因可以解释为什么人们希望在边缘端运行计算工作负载。在本节中, 我们将尝试列出主要的动机。根据用例的不同, 这些动机中的一个或多个可能同时适用, 并且它们的重要性排名也可能不同。 </p><p>使边缘计算不同于纯粹的基于云的解决方案的关键因素之一是边缘计算最大限度地<strong>减少设备</strong> <strong>(**</strong>用户<strong>**)</strong> <strong>和工作负载之间更远距离的网络流量。</strong>这将以多种方式影响数据的处理, 并可被视为边缘计算的明显优势。 </p><p>首先, 我们为系统增加了<strong>可靠性</strong>, 因为即使网络出现故障, 系统也能存活和正常运行。这对于许多使用可靠的网络连接很难连接边缘设备的用例非常重要。在这些情况下, 网络连接通常也很昂贵, 因此将所有数据从边缘端传输到云端并不总是具有成本效益的, 甚至网络是不可达的。因此, <strong>在边缘节点上处理数据</strong> <strong>(**</strong>数据预处理<strong>**)</strong> <strong>可以帮助我们处理带宽问题**</strong>,<strong> </strong>避免网络饱和度。**</p><p><strong>边缘数据</strong> <strong>(**</strong>预处理<strong>**)</strong> <strong>处理</strong>的价值不仅可以避免受到网络带宽的限制, 还有助于减少系统的整体延迟, 这是边缘计算在许多应用中的主要驱动因素之一, 例如, VR应用场景或处理关键应用的工业应用基础设施。通过将计算推送到更接近设备和用户的地方, 这些用例可以从减少延迟中大大受益。</p><p>除了数据处理外, <strong>数据局部性</strong>也是需要考虑的一个重要方面。通过使用边缘计算体系结构, 我们可以实施<strong>强制策略</strong>使敏感数据永远不会离开物理环境。我们也可以将 GDPR 和其他数据保护和隐私政策。通过在边缘实施策略, 我们应该能够为整个系统提供<strong>更好的安全性</strong>, 并为更接近和更容易攻击的来源 (设备) 添加保护层。 </p><p>最后, 我们可以讨论边缘系统如何帮助我们扩展以前的集中式数据中心。在某些情况下, 在边缘端添加更多资源可能会更容易 (也更有效)。我们还可以使用更适合解决方案特殊需求的硬件, 并创建更好地与内部部署系统集成的系统。所有这些方面也都会明显影响部署解决方案的总成本。</p><p>毫无疑问, 所有这些方面都可以改进当今存在的许多解决方案, 但我们当然期望整个领域将随着时间的推移而演变, 边缘计算的新趋势将在未来可用。</p><h2 id="边缘资源分类"><a href="#边缘资源分类" class="headerlink" title="边缘资源分类"></a>边缘资源分类</h2><p>“边缘” 用于描述资源容量和位置的多种形态和大小。本节试图消除 “什么” 是边缘的歧义。我们将介绍不同的类别, 解释在这些环境中通常可以找到的环境和节点类型。</p><p>但是,在我们深入了解之前,让我们先关注一个组件, 该组价是物联网应用的关键部分,但我们不认为它是边缘端托管的部分:受约束的设备。</p><h4 id="受约束设备"><a href="#受约束设备" class="headerlink" title="受约束设备"></a>受约束设备</h4><p>物联网的基本组成部分是设备。它们在系统中有两个主要用途: 传感器和驱动(执行器)。<strong>在传感器角色中**</strong>,<strong> 设备正在感知其环境 (例如温度传感器), 并将该数据发送到云 (或我们将很快讨论的其他边缘组件)。</strong>在执行器角色中<strong>**,</strong> 它根据从其他系统组件接收到的命令来控制环境 (例如控制 AC)。设备通常同时是传感器和执行器, 并具有相应的控制器逻辑。 </p><p>尽管术语 “设备” 可能会有所不同 (我们将在一分钟内看到), 但最常用的 “事物” 具有一些常见的技术属性。它们是由它们的主要属性定义的: 需要<strong>在任何地方大规模的部署</strong>和价格便宜. 这意味着他们是<strong>通常是电池供电</strong>这进一步决定了他们的其他技术方面。这通常意味着, 他们是非常<strong>有限的计算能力</strong>, 更不用说缺乏其他资源, 如存储, 例如。此外, 还对网络层和模式有影响, 这些设备如何与系统的其余部分进行通信。通常情况下, 他们是<strong>未启用</strong> <strong>ip</strong>并仅使用<strong>近场通讯协议</strong>, 像 BLE 或 ZigBee。它们通常在睡眠模式下运行, 偶尔醒来与云系统交换消息并执行必要的任务。</p><p>从上面的讨论中, 我们可以看到为什么受约束的设备不被视为边缘的托管部分, 而更多的是连接到托管边缘基础结构的终端结点。另外它们不适合容器化, 不属于 IT (甚至 OT 环境), <strong>不能由集群控制或管理</strong></p><p>但是, 请注意, 可能有并且通常是具有与受约束设备相同或类似功能的设备, 但这些设备的资源更丰富。我们将这些设备称为智能设备 (见下文部分), 它们可以是托管边缘的一部分。</p><p>现在让我们回到上一级, 讨论不同类型的边缘组件。我们将这些组件分为两大类: <strong>设备边缘和基础结构边缘</strong>。这两个边缘域都位于最后一英里的两侧, 因此靠近物联网设备和/或终端用户。最大的区别是, 基础设施边缘通常由运营商 (电信或服务提供商) 拥有和运营, 而设备边缘位于客户前提下。物联网设备通常是设备边缘 (智能设备) 的一部分, 或直接连接到该设备 (受约束的设备)。</p><p><a href="https://www.41sh.cn/zb_users/upload/2019/03/201903271553674496574051.png" target="_blank" rel="noopener"><img src="https://www.41sh.cn/zb_users/upload/2019/03/201903271553674496574051.png" alt="屏幕快照 2019-03-27 下午4.14.29.png"></a></p><h3 id="边缘设备"><a href="#边缘设备" class="headerlink" title="边缘设备"></a>边缘设备</h3><p>有关边缘设备方案的一个关键功能是, 它们处于物理环境中, 通常不会作为面向 IT 的空间进行管理和维护。数据中心和城市互联 pop 地点在物理安全、电源冗余和备份、多个网络链接和高质量网络等方面有大量投资。物联网环境是指以前从<strong>未被视为计算基础结构的环境</strong>, <strong>或由 OT</strong> <strong>部门 (**</strong>而不是 IT)<strong> </strong>管理的环境,** 并且可能无法提供相同的环境标准。边缘设备环境的示例包括工厂、油田、火车或卡车等。 </p><p>此环境的一个重要方面是网络连接。网络连接在可用性或带宽等方面受到许多很多的限制。通常情况下, 它们会带来高昂费用成本。 在那里运行的节点至少应该能够离线工作, 甚至可以在相当长的时间内工作。</p><p>现在, 让我们来看看在设备边缘环境中找到的节点类型。</p><h4 id="智能设备"><a href="#智能设备" class="headerlink" title="智能设备"></a>智能设备</h4><p>智能设备是更传统的计算资源, 例如手机或平板电脑。当然, 它们能够运行所有类型的复杂应用程序。它们能够运行不同类型的工作负载, 并直接连接到基础架构的其余部分。这些都是广泛使用的被广泛理解的设备, 所以没有必要更详细地解释。</p><h4 id="IoT网关"><a href="#IoT网关" class="headerlink" title="IoT网关"></a>IoT网关</h4><p>在设备边缘上发现的另一个有趣的 “专用” 节点是物联网网关。它的主要目的是成为一个<strong>受约束设备的聚合点</strong>.那是什么意思?首先也是最重要的, 网关的工作是做一个传感器和执行器的IP-onboarding(打通IP)。正如我们前面说过的, 受限设备通常使用近场协议, 网关将它们与云平台之间的通信连接起来。他们使用一些 “云协议” 来做到这一点, 我们将在一点上更详细地介绍它们。此外, 他们还可以运行一些工作负载来对数据执行转换、正规化或聚合等操作然后发送到云上。当网关在设备边缘环境中运行时, 连接的所有方面都适用于此处, 这将同时影响在其上运行的工作负载以及云平台对其的管理方式。</p><p>网关通常伴随着物联网云平台使用, 该平台管理网关本身和连接到它的设备。目前将物联网网关进行容器化并将其作为群集节点进行管理的兴趣越来越大。这样做的主要挑战之一是将上述网络进行连接。我们将在整个文档中更多地讨论这个问题。</p><h4 id="边缘节点"><a href="#边缘节点" class="headerlink" title="边缘节点"></a>边缘节点</h4><ul><li><p>其他名称: 边缘网关, IoT 边缘系统</p></li><li><p>更强大的网关, 可以进行数据存储和处理</p></li><li><p>没有足够的能力运行一个完整的kubernetes控制平面, 但足够可以运行几个容器</p></li><li><p>更多的重型应用程序从云移动到设备边缘节点, 如</p><ul><li>数据存储</li><li>流式数据分析</li><li>商业流程管理(BPM)</li></ul></li><li><p>优点</p><ul><li>减少了网关与云之间的网络流量</li><li>可以在你本地执行决策</li><li>离线模式下可以提供更多的功能</li></ul></li></ul><h4 id="边缘集群"><a href="#边缘集群" class="headerlink" title="边缘集群"></a>边缘集群</h4><ul><li><p>有足够的计算能力来运行完整的 Kubernetes 控制平面</p></li><li><p>存在于IoT 或OT环境</p></li><li><p>可能有下面的异构节点类型组成</p><ul><li>混合x86 和ARM 架构</li><li>不同的硬件功能(GPU,SSD等)</li><li>不同类别的物理安全</li><li>节点也可能正在运行其他工作负载</li></ul></li></ul><h3 id="基础架构边缘端"><a href="#基础架构边缘端" class="headerlink" title="基础架构边缘端"></a>基础架构边缘端</h3><h4 id="网络边缘"><a href="#网络边缘" class="headerlink" title="网络边缘"></a>网络边缘</h4><ul><li><p>替代名称:Edge cloudlets</p></li><li><p>与数据中心级基础架构相比,边缘云靠靠且可以低延迟的连接到云平台或数据中心</p></li><li><p>与数据中心基础架构相比,计算能力有限</p></li><li><p>重点是延迟的改善,因为网络边缘往往在分布在设备密集处</p></li><li><p>POP 或 CDN 点的基础结构可能已经存在</p></li><li><p>通常是 IT 管理的物理环境</p></li><li><p>可以执行与物理世界没有直接连接的工作负载, 因此不是 “物联网” (如低延迟游戏服务器)</p></li></ul><table><thead><tr><th></th><th>可靠性</th><th>带宽</th><th>策略</th><th>延迟</th></tr></thead><tbody><tr><td>设备</td><td>-</td><td>-</td><td>-</td><td>-</td></tr><tr><td>网关</td><td>轻度/中度</td><td>轻度/中度</td><td>-</td><td>-</td></tr><tr><td>边缘节点</td><td>-</td><td>轻度/中度</td><td>中度/严重</td><td>-</td></tr><tr><td>网络边缘</td><td>-</td><td>-</td><td>中度/严重</td><td>严重</td></tr></tbody></table><h2 id="边缘工作负载"><a href="#边缘工作负载" class="headerlink" title="边缘工作负载"></a>边缘工作负载</h2><h3 id="流量治理"><a href="#流量治理" class="headerlink" title="流量治理"></a>流量治理</h3><h4 id="它是什么?"><a href="#它是什么?" class="headerlink" title="它是什么?"></a>它是什么?</h4><p>流量治理包括管理Pods之间以及集群与其他网络之间的进出流量/带宽使用情况的任务。对于物联网边缘环境, 网络链接可能会更有限 (例如 SMB 非对称电缆调制解调器或蜂窝连接)。kubernetes 集群可能与其他计算机或进程共享该链接, 因此可能只需要分配一部分。 在网络带宽的这一部分中, 在 Kubernetes 中运行的不同工作负载可能更关键, 因此可能会被视为以不同的优先级运行。</p><h4 id="Kubernetes-在哪里参与"><a href="#Kubernetes-在哪里参与" class="headerlink" title="Kubernetes 在哪里参与?"></a>Kubernetes 在哪里参与?</h4><p>需要一个集群范围或站点范围的流量管理设置</p><ul><li><p>策略更多的是关于访问和权限, 而不是资源使用</p><ul><li><a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/" target="_blank" rel="noopener">https://kubernetes.io/docs/concepts/services-networking/network-policies/</a></li></ul></li><li><p>听起来像在kubelet</p><ul><li><p><a href="https://github.com/kubernetes/kubernetes/issues/2856" target="_blank" rel="noopener">https://github.com/kubernetes/kubernetes/issues/2856</a></p></li><li><p><a href="https://godoc.org/k8s.io/kubernetes/pkg/util/bandwidth" target="_blank" rel="noopener">https://godoc.org/k8s.io/kubernetes/pkg/util/bandwidth</a></p></li></ul></li><li><p><a href="https://github.com/kubernetes/kubernetes/issues/11965" target="_blank" rel="noopener">https://github.com/kubernetes/kubernetes/issues/11965</a></p></li></ul><h3 id="协议转换"><a href="#协议转换" class="headerlink" title="协议转换"></a>协议转换</h3><h4 id="它是什么?-1"><a href="#它是什么?-1" class="headerlink" title="它是什么?"></a>它是什么?</h4><p>边缘环境中充满了许多不同的协议, 这些协议通常是由特殊行业或传统行业和专有环境开发而来的。”协议” 可适用于数据的内容和形状, 或适用于总线级格式。 许多工业协议在非 ip 串行总线上传输, 而其他现代 RF 协议也可能是非 ip 协议 (例如蓝牙)。严格地说, 这是 “数据处理” 的序言或子集, 但很常见, 可以将其突出显示为具体的工作量。</p><h4 id="Kubernetes-在哪里参与-1"><a href="#Kubernetes-在哪里参与-1" class="headerlink" title="Kubernetes 在哪里参与?"></a>Kubernetes 在哪里参与?</h4><p>对于数据内容转换, 这可以只是在容器中运行的逻辑, 而 Kubernetes 并不担心。对于网络层协议转换, 这通常需要特权容器 (root) 来访问硬件接口。节点污染 (tainted)和容忍度 (tolerate)可用于表示某些接口的存在。可以通过某些服务注释 (annotation) 的约定来公开这些硬件接口, 以便由无特权的容器使用, 从而建立约定。</p><h3 id="工作负载优先级"><a href="#工作负载优先级" class="headerlink" title="工作负载优先级"></a>工作负载优先级</h3><h4 id="它是什么?-2"><a href="#它是什么?-2" class="headerlink" title="它是什么?"></a>它是什么?</h4><p>边缘集群有可能由有限数量的节点组成, 并且没有集群自动缩放的优点。边缘环境也可能更有可能具有很大的工作负载优先级范围, 其中有几个关键的工作负载和其他更低的优先级。此条件对于边缘群集不是唯一的, 但考虑到通常较小的群集占用空间, 这种情况更有可能成为关注的问题。</p><h4 id="Kubernetes-在哪里参与-2"><a href="#Kubernetes-在哪里参与-2" class="headerlink" title="Kubernetes 在哪里参与?"></a>Kubernetes 在哪里参与?</h4><p>对于工作负载, 可以进行以下交互:</p><ul><li><p><a href="https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/" target="_blank" rel="noopener">priority</a>: ~high</p></li><li><p><a href="https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/" target="_blank" rel="noopener">qos</a>: best effort</p></li><li><p><a href="https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/" target="_blank" rel="noopener">tolerance</a>: PreferNoSchedule</p></li></ul><p>See also WG on <a href="https://docs.google.com/document/d/1Ynkey7UREOE5iny01L7fgKL84LFAzT7KViCHGrfZwsQ/edit#heading=h.oazh9v95dhnv" target="_blank" rel="noopener">oversubscription</a></p><h3 id="缓冲区和批处理-即存储和转发"><a href="#缓冲区和批处理-即存储和转发" class="headerlink" title="缓冲区和批处理(即存储和转发)"></a>缓冲区和批处理(即存储和转发)</h3><h4 id="它是什么?-3"><a href="#它是什么?-3" class="headerlink" title="它是什么?"></a>它是什么?</h4><p>边缘集群可以作为生成数据的设备和云数据接收器之间的网关。集群和云之间的连接可能不可靠且时断时续。就跟踪失败的最终交付其有效负载而言, 生成数据的设备的逻辑和内存可能有限。</p>]]></content>
<summary type="html">
<h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><p>当今许多开发团队的都在关注云原生应用开发, 这其实是有原因的。比如微服务和 devops 等概念,从根本上改变了开发团队构建应用程序的方式。
</summary>
<category term="edge computing" scheme="https://readailib.com/tags/edge-computing/"/>
<category term="IoT" scheme="https://readailib.com/tags/IoT/"/>
</entry>
<entry>
<title>kubernetes GPU分布式集群配置与安装</title>
<link href="https://readailib.com/2019/03/20/kubernetes/gpu-device-plugins/"/>
<id>https://readailib.com/2019/03/20/kubernetes/gpu-device-plugins/</id>
<published>2019-03-20T06:10:31.000Z</published>
<updated>2019-03-22T04:56:35.573Z</updated>
<content type="html"><![CDATA[<p>最近单位刚来一台T630 GPU服务器,我想在日常的深度学习的训练此外,想让它docker容器化并且使其成为 Kubernetes 节点,让深度学习任务的训练在kubernetes中完成。注意:本教程的篇幅较长!</p><p>主要分为以下几部分:</p><ul><li>环境准备</li><li>安装cuda环境并测试</li><li>安装docker</li><li>安装kubernetes</li><li>测试GPU</li></ul><h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><table><thead><tr><th>节点</th><th>ip</th><th>配置</th></tr></thead><tbody><tr><td>gpu服务器</td><td>172.16.3.33</td><td>centos7系统,2*GTX1080Ti</td></tr></tbody></table><h3 id="修改默认yum源为国内yum镜像源"><a href="#修改默认yum源为国内yum镜像源" class="headerlink" title="修改默认yum源为国内yum镜像源"></a>修改默认yum源为国内yum镜像源</h3><pre><code class="bash">$ cp /etc/yum.repos.d/CentOS-Base.repo{,.bak}$ wget http://mirrors.aliyun.com/repo/Centos-7.repo -O /etc/yum.repos.d/CentOS-Base.repo</code></pre><h3 id="安装开发环境"><a href="#安装开发环境" class="headerlink" title="安装开发环境"></a>安装开发环境</h3><pre><code class="bash">$ yum groupinstall -y "Development Tools"</code></pre><h3 id="网卡设备开机自启动"><a href="#网卡设备开机自启动" class="headerlink" title="网卡设备开机自启动"></a>网卡设备开机自启动</h3><pre><code class="bash">$ systemctl start network && systemctl enable network$ ifup em1 # 默认centos网卡设备在开机时不启动$ sed -i 's/ONBOOT=no/ONBOOT=yes/g' /etc/sysconfig/network-scripts/ifcfg-em1$ sed -i 's/ONBOOT=no/ONBOOT=yes/g' /etc/sysconfig/network-scripts/ifcfg-em2</code></pre><p>如果你想将其中一个网卡设置为静态网卡,可以参考下面的格式:(比如em1网卡)</p><pre><code class="bash">$ cat /etc/sysconfig/network-scripts/ifcfg-em1TYPE="Ethernet"BOOTPROTO="static"DEFROUTE="yes"PEERDNS="yes"PEERROUTES="yes"IPADDR=172.16.3.33GATEWAY=172.16.3.1NETMASK=255.255.255.0DNS1=172.16.3.1IPV4_FAILURE_FATAL="no"IPV6INIT="yes"IPV6_AUTOCONF="yes"IPV6_DEFROUTE="yes"IPV6_PEERDNS="yes"IPV6_PEERROUTES="yes"IPV6_FAILURE_FATAL="no"NAME=em1UUID=5f2f3298-1185-43d8-8509-62795e7db8f9DEVICE=em1ONBOOT=yes</code></pre><h2 id="安装cuda环境并测试"><a href="#安装cuda环境并测试" class="headerlink" title="安装cuda环境并测试"></a>安装cuda环境并测试</h2><h3 id="检查显卡驱动和型号"><a href="#检查显卡驱动和型号" class="headerlink" title="检查显卡驱动和型号"></a>检查显卡驱动和型号</h3><p>1) 添加软件源</p><pre><code class="bash">$ sudo rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org $ sudo rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm</code></pre><p>2) 安装NVIDIA驱动检测</p><pre><code class="bash">$ sudo yum install nvidia-detect$ nvidia-detect -vProbing for supported NVIDIA devices...[102b:0534] Matrox Electronics Systems Ltd. G200eR2[10de:1b06] NVIDIA Corporation GP102 [GeForce GTX 1080 Ti]This device requires the current 418.43 NVIDIA driver kmod-nvidia[10de:1b06] NVIDIA Corporation GP102 [GeForce GTX 1080 Ti]This device requires the current 418.43 NVIDIA driver kmod-nvidia</code></pre><p>可以看出这两块GPU的型号和需要的驱动型号。</p><h3 id="安装显卡驱动"><a href="#安装显卡驱动" class="headerlink" title="安装显卡驱动"></a>安装显卡驱动</h3><p>访问 <a href="https://www.geforce.cn/drivers" target="_blank" rel="noopener">https://www.geforce.cn/drivers</a> 网站,选择 手动搜索驱动程序。设置如下的条件,并开始搜索。</p><p>选择搜索结果的第一项即可。</p><p><a href="https://www.41sh.cn/zb_users/upload/2019/03/201903201553058815156821.png" target="_blank" rel="noopener"><img src="https://www.41sh.cn/zb_users/upload/2019/03/201903201553058815156821.png" alt="屏幕快照 2019-03-20 下午1.13.07.png"></a></p><p>点击获取下载链接:<a href="http://cn.download.nvidia.com/XFree86/Linux-x86_64/418.43/NVIDIA-Linux-x86_64-418.43.run,然后使用ssh登陆服务器使用wget工具即可下载。" target="_blank" rel="noopener">http://cn.download.nvidia.com/XFree86/Linux-x86_64/418.43/NVIDIA-Linux-x86_64-418.43.run,然后使用ssh登陆服务器使用wget工具即可下载。</a></p><pre><code class="bash">$ cd ~/0315-cuda$ wget -r -np -nd http://cn.download.nvidia.com/XFree86/Linux-x86_64/418.43/NVIDIA-Linux-x86_64-418.43.run</code></pre><h3 id="解决显卡冲突"><a href="#解决显卡冲突" class="headerlink" title="解决显卡冲突"></a>解决显卡冲突</h3><p>因为NVIDIA驱动会和系统自带nouveau驱动冲突,执行下面的命令查看该驱动状态:</p><pre><code class="bash">$ lsmod | grep nouveau</code></pre><p><a href="https://www.41sh.cn/zb_users/upload/2019/03/20190320133021_11625.png" target="_blank" rel="noopener"><img src="https://www.41sh.cn/zb_users/upload/2019/03/20190320133021_11625.png" alt="img"></a></p><p>修改/etc/modprobe.d/blacklist.conf 文件,以阻止 nouveau 模块的加载,如果系统没有该文件需要新建一个,这里使用root权限,普通用户无法再在/etc内生成.conf文件。</p><pre><code class="bash">$ su root -$ echo -e "blacklist nouveau\noptions nouveau modeset=0" > /etc/modprobe.d/blacklist.conf</code></pre><h3 id="重新建立initramfs-image文件"><a href="#重新建立initramfs-image文件" class="headerlink" title="重新建立initramfs image文件"></a>重新建立initramfs image文件</h3><pre><code class="bash">$ mv /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r).img.bak$ dracut /boot/initramfs-$(uname -r).img $(uname -r)</code></pre><h3 id="重启"><a href="#重启" class="headerlink" title="重启"></a>重启</h3><pre><code class="bash">$ reboot</code></pre><h3 id="安装显卡驱动-1"><a href="#安装显卡驱动-1" class="headerlink" title="安装显卡驱动"></a>安装显卡驱动</h3><p>如果此处安装了,那么安装cuda时就不安装,但建议和安装cuda时一起安装。</p><pre><code class="bash">$ chmod +x NVIDIA-Linux-x86_64-418.43.run$ ./NVIDIA-Linux-x86_64-418.43.run</code></pre><p>如果安装完成,可以运行命令查看显卡状态</p><pre><code class="bash">$ nvidia-smiWed Mar 20 13:23:40 2019 +-----------------------------------------------------------------------------+| NVIDIA-SMI 418.39 Driver Version: 418.39 CUDA Version: 10.1 ||-------------------------------+----------------------+----------------------+| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC || Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. ||===============================+======================+======================|| 0 GeForce GTX 108... Off | 00000000:02:00.0 Off | N/A || 20% 25C P8 9W / 250W | 0MiB / 11178MiB | 0% Default |+-------------------------------+----------------------+----------------------+| 1 GeForce GTX 108... Off | 00000000:04:00.0 Off | N/A || 20% 23C P8 8W / 250W | 0MiB / 11178MiB | 0% Default |+-------------------------------+----------------------+----------------------++-----------------------------------------------------------------------------+| Processes: GPU Memory || GPU PID Type Process name Usage ||=============================================================================|| No running processes found |+-----------------------------------------------------------------------------+</code></pre><p>如果出现上面所示的情况,证明你的驱动安装成功了。</p><h3 id="安装cuda"><a href="#安装cuda" class="headerlink" title="安装cuda"></a>安装cuda</h3><p>官网下载包<a href="https://developer.nvidia.com/cuda-downloads" target="_blank" rel="noopener">https://developer.nvidia.com/cuda-downloads</a>,一定要对应自己的版本。</p><pre><code class="bash">$ ./cuda_10.1.105_418.39_linux.run</code></pre><p>如果上面已经安装了驱动,这里安装的时候就不需要安装驱动了。</p><h3 id="测试cuda"><a href="#测试cuda" class="headerlink" title="测试cuda"></a>测试cuda</h3><pre><code class="bash">$ cd /root/NVIDIA_CUDA-10.1_Samples/1_Utilities/deviceQuery$ make$ ./deviceQuery Run time limit on kernels: No Integrated GPU sharing Host Memory: No Support host page-locked memory mapping: Yes Alignment requirement for Surfaces: Yes Device has ECC support: Disabled Device supports Unified Addressing (UVA): Yes Device supports Compute Preemption: Yes Supports Cooperative Kernel Launch: Yes Supports MultiDevice Co-op Kernel Launch: Yes Device PCI Domain ID / Bus ID / location ID: 0 / 4 / 0 Compute Mode: < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >> Peer access from GeForce GTX 1080 Ti (GPU0) -> GeForce GTX 1080 Ti (GPU1) : Yes> Peer access from GeForce GTX 1080 Ti (GPU1) -> GeForce GTX 1080 Ti (GPU0) : YesdeviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 10.1, CUDA Runtime Version = 10.1, NumDevs = 2Result = PASS</code></pre><p>如果出现PASS,就证明你的cuda环境安装完成了。</p><h2 id="安装docker和kubernetes"><a href="#安装docker和kubernetes" class="headerlink" title="安装docker和kubernetes"></a>安装docker和kubernetes</h2><p>请参考作者的另一篇博客文章:<a href="https://readailib.com/2018/11/15/kubernetes/kubernetes-install-note/">https://readailib.com/2018/11/15/kubernetes/kubernetes-install-note/</a></p><h3 id="安装nvidia-docker"><a href="#安装nvidia-docker" class="headerlink" title="安装nvidia-docker"></a>安装nvidia-docker</h3><p>参考: <a href="https://github.com/NVIDIA/nvidia-docker" target="_blank" rel="noopener">https://github.com/NVIDIA/nvidia-docker</a></p><p>docker-ce版本:</p><pre><code class="bash"># If you have nvidia-docker 1.0 installed: we need to remove it and all existing GPU containersdocker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -fsudo yum remove nvidia-docker# Add the package repositoriesdistribution=$(. /etc/os-release;echo $ID$VERSION_ID)curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | \ sudo tee /etc/yum.repos.d/nvidia-docker.repo# Install nvidia-docker2 and reload the Docker daemon configurationsudo yum install -y nvidia-docker2sudo pkill -SIGHUP dockerd# Test nvidia-smi with the latest official CUDA imagedocker run --runtime=nvidia --rm nvidia/cuda:9.0-base nvidia-smi</code></pre><h3 id="安装Nvidia-Device-Plugin"><a href="#安装Nvidia-Device-Plugin" class="headerlink" title="安装Nvidia Device Plugin"></a>安装Nvidia Device Plugin</h3><p>参考:<a href="https://k2r2bai.com/2018/03/01/kubernetes/nvidia-device-plugin/" target="_blank" rel="noopener">https://k2r2bai.com/2018/03/01/kubernetes/nvidia-device-plugin/</a></p><p>参考:<a href="https://github.com/NVIDIA/k8s-device-plugin%EF%BC%88%E5%AE%98%E6%96%B9%EF%BC%89" target="_blank" rel="noopener">https://github.com/NVIDIA/k8s-device-plugin(官方)</a></p><pre><code class="bash">$ cp /etc/docker/daemon.json{,.bak}$ vi /etc/docker/daemon.json{ "default-runtime": "nvidia", "runtimes": { "nvidia": { "path": "/usr/bin/nvidia-container-runtime", "runtimeArgs": [] } }}</code></pre><p>注意:前提是已经安装好nvidia-docker</p><p>接下来就是启动kubernetes集群,并且将gpu节点加入到集群中。具体的操作命令这里就不再赘述。</p><h3 id="Enabling-GPU-Support-in-Kubernetes"><a href="#Enabling-GPU-Support-in-Kubernetes" class="headerlink" title="Enabling GPU Support in Kubernetes"></a>Enabling GPU Support in Kubernetes</h3><p>Once you have enabled this option on all the GPU nodes you wish to use, you can then enable GPU support in your cluster by deploying the following Daemonset:</p><pre><code class="bash">$ kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta/nvidia-device-plugin.yml</code></pre><p>然后查看pod的运行状态:</p><pre><code class="bash">$ kubectl -n kube-system get po -o wideNAME READY STATUS RESTARTS AGE IP NODE...nvidia-device-plugin-daemonset-bncw2 1/1 Running 0 2m 10.244.41.135 kube-gpu-node1nvidia-device-plugin-daemonset-ddnhd 1/1 Running 0 2m 10.244.152.132 kube-gpu-node2</code></pre><h3 id="测试-GPU"><a href="#测试-GPU" class="headerlink" title="测试 GPU"></a>测试 GPU</h3><pre><code class="bash">$ kubectl get nodes "-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu"NAME GPUbigdata <none>gpucluster 2</code></pre><p>测试实例</p><pre><code class="bash">$ cat <<EOF | kubectl create -f -apiVersion: v1kind: Podmetadata: name: gpu-podspec: restartPolicy: Never containers: - image: nvidia/cuda name: cuda command: ["nvidia-smi"] resources: limits: nvidia.com/gpu: 1EOFpod "gpu-pod" created$ kubectl get po -a -o wideNAME READY STATUS RESTARTS AGE IP NODEgpu-pod 0/1 Completed 0 50s 10.244.41.136 kube-gpu-node1$ kubectl logs gpu-pod -c cudaWed Mar 20 04:23:49 2019 +-----------------------------------------------------------------------------+| NVIDIA-SMI 418.39 Driver Version: 418.39 CUDA Version: 10.1 ||-------------------------------+----------------------+----------------------+| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC || Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. ||===============================+======================+======================|| 0 GeForce GTX 108... Off | 00000000:02:00.0 Off | N/A || 20% 24C P8 8W / 250W | 0MiB / 11178MiB | 1% Default |+-------------------------------+----------------------+----------------------++-----------------------------------------------------------------------------+| Processes: GPU Memory || GPU PID Type Process name Usage ||=============================================================================|| No running processes found |+-----------------------------------------------------------------------------+</code></pre><p>测试tensorflow,新建一个deployment,名字为tf-gpu-dep.yaml</p><pre><code class="yaml">apiVersion: apps/v1kind: Deploymentmetadata: name: tf-gpuspec: replicas: 1 selector: matchLabels: app: tf-gpu template: metadata: labels: app: tf-gpu spec: containers: - name: tensorflow image: tensorflow/tensorflow:latest-gpu ports: - containerPort: 8888 resources: limits: nvidia.com/gpu: 1</code></pre><p>利用 kubectl 建立 Deployment,并暴露 Jupyter port:</p><pre><code class="bash">$ kubectl create -f tf-gpu-dp.ymldeployment "tf-gpu" created$ kubectl expose deploy tf-gpu --type LoadBalancer --external-ip=172.16.3.33 --port 8888 --target-port 8888service "tf-gpu" exposed$ kubectl get po,svc -o wideNAME READY STATUS RESTARTS AGE IP NODEpo/tf-gpu-6f9464f94b-pq8t9 1/1 Running 0 1m 10.244.152.133 kube-gpu-node2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTORsvc/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23h <none>svc/tf-gpu LoadBalancer 10.105.104.183 172.22.132.53 8888:30093/TCP 12s app=tf-gpu</code></pre><blockquote><p>确认无误后,通过 logs 指令取得 token。</p></blockquote><p>这里执行一个简单的例子,并在用 logs 指令查看就能看到 Pod 通过 NVIDIA Device Plugins 使用 GPU:</p><pre><code class="bash">$ kubectl logs -f tf-gpu-6f9464f94b-pq8t9...2018-03-15 07:37:22.022052: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA2018-03-15 07:37:22.155254: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:898] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2018-03-15 07:37:22.155565: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1212] Found device 0 with properties:name: GeForce GTX 1060 3GB major: 6 minor: 1 memoryClockRate(GHz): 1.7845pciBusID: 0000:01:00.0totalMemory: 2.95GiB freeMemory: 2.88GiB2018-03-15 07:37:22.155586: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1312] Adding visible gpu devices: 02018-03-15 07:37:22.346590: I tensorflow/core/common_runtime/gpu/gpu_device.cc:993] Creating TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 2598 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1060 3GB, pci bus id: 0000:01:00.0, compute capability: 6.1)</code></pre><p>最后因为目前 Pod 会绑定整张 GPU 使用,因此当无多余显卡時就让 Pod 处于 Pending:</p><pre><code class="bash">$ kubectl scale deploy tf-gpu --replicas=3$ kubectl get po -o wideNAME READY STATUS RESTARTS AGE IP NODEtf-gpu-6f9464f94b-42xcf 0/1 Pending 0 4s <none> <none>tf-gpu-6f9464f94b-nxdw5 1/1 Running 0 12s 10.244.41.138 kube-gpu-node1tf-gpu-6f9464f94b-pq8t9 1/1 Running 0 5m 10.244.152.133 kube-gpu-node2</code></pre>]]></content>
<summary type="html">
<p>最近单位刚来一台T630 GPU服务器,我想在日常的深度学习的训练此外,想让它docker容器化并且使其成为 Kubernetes 节点,让深度学习任务的训练在kubernetes中完成。注意:本教程的篇幅较长!</p>
<p>主要分为以下几部分:</p>
<ul>
<li
</summary>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Helm" scheme="https://readailib.com/tags/Helm/"/>
<category term="Minikube" scheme="https://readailib.com/tags/Minikube/"/>
</entry>
<entry>
<title>Helm 的基本概念与安装详解</title>
<link href="https://readailib.com/2019/03/14/kubernetes/helm-concepts-and-installation/"/>
<id>https://readailib.com/2019/03/14/kubernetes/helm-concepts-and-installation/</id>
<published>2019-03-14T04:57:35.000Z</published>
<updated>2019-03-14T05:46:54.553Z</updated>
<content type="html"><![CDATA[<p>在Kubernetes集群中运行和管理应用程序的最简单方法是使用<a href="https://github.com/kubernetes/helm" target="_blank" rel="noopener">Helm</a>。Helm允许使用安装,升级或删除等操作来管理应用程序。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>Kuberentes Helm 介紹 <a href="https://k2r2bai.com/2017/03/25/kubernetes/helm-quickstart/" target="_blank" rel="noopener">https://k2r2bai.com/2017/03/25/kubernetes/helm-quickstart/</a><br>Get Started With Kubernetes Using Minikube <a href="https://docs.bitnami.com/kubernetes/get-started-kubernetes/" target="_blank" rel="noopener">https://docs.bitnami.com/kubernetes/get-started-kubernetes/</a></p><p><a href="https://github.com/kubernetes/helm" target="_blank" rel="noopener">Helm</a> 是 Kubernetes Chart 的管理工具,Kubernetes Chart 是一套预先组织好的 Kubernetes 资源套件。使用 Helm 有以下几个好处:</p><ul><li>查询与使用热门的 <a href="https://github.com/kubernetes/charts" target="_blank" rel="noopener">Kubernetes Chart</a> 软件。</li><li>可以通过Kuberntes Chart来分享自己的软件</li><li>可基于现有 Chart 开发自己的Chart。</li><li>智能地管理 Kubernetes manifest 文件。</li><li>管理最新发布的 Helm 版本</li></ul><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>Helm 有三个重要的概念需要了解,分別为 Chart、Release 和 Repository,细节如下:</p><ul><li><strong>Chart</strong>:主要定义要被执行的应用程式中,所需要的工具、资源、服务等资料,有点类似 Homebrew 的 Formula 或是 APT 的 dpkg 安装包。</li><li><strong>Release</strong>:一个被执行于 Kubernetes 的 Chart 实例。Chart 能够在一个集群中拥有多个 Release,例如 MySQL Chart,可以在集群中建立基于该 Chart 的两个资料库实例,其中每个 Release 都会有独立的名称。</li><li><strong>Repository</strong>:主要用来存放 Chart 的仓库,如 <a href="https://kubeapps.com/" target="_blank" rel="noopener">KubeApps</a>。</li></ul><p>可以理解 Helm 主要目标就是从 Chart Repository 中,查找需要的应用程式 Chart,然后以 Release 形式来部署到 Kubernetes 中进行管理。</p><p>Helm由两部分组成:Helm(客户端)和Tiller(服务器)。</p><p>下面是Helm的架构图:</p><p><img src="./helm.png" alt="Helm的架构图"></p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>按照以下步骤完成Helm和Tiller安装并创建必要的Kubernetes对象,以使Helm与基于角色的访问控制(RBAC)一起工作:</p><pre><code class="bash">$ curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh$ chmod 700 get_helm.sh$ ./get_helm.sh</code></pre><blockquote class="colorquote warning"><p>如果操作系统为OSX,请使用<a href="https://brew.sh/" target="_blank" rel="noopener">Homebrew</a>安装helm.</p><pre><code class="bash">$ brew install helm</code></pre></blockquote><p>使用以下内容创建ClusterRole配置文件。在此示例中,它名为clusterrole.yaml。</p><pre><code class="yaml">apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: annotations: rbac.authorization.kubernetes.io/autoupdate: "true" labels: kubernetes.io/bootstrapping: rbac-defaults name: cluster-adminrules:- apiGroups: - '*' resources: - '*' verbs: - '*'- nonResourceURLs: - '*' verbs: - '*'</code></pre><p>要创建ClusterRole,请运行以下命令:</p><pre><code class="bash">$ kubectl create -f clusterrole.yaml</code></pre><p>创建ServiceAccount并将其与ClusterRole关联,请使用ClusterRoleBinding,如下所示:</p><pre><code class="bash">$ kubectl create serviceaccount -n kube-system tiller$ kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller</code></pre><p>初始化Helm,如下所示:</p><pre><code class="bash">$ helm init --service-account tiller</code></pre><blockquote class="colorquote warning"><p>如果你之前已经初始化过helm,直接使用下面的指令升级helm。</p><pre><code class="bash">$ helm init --upgrade --service-account tiller</code></pre></blockquote><p>使用 <em>kubectl get pods</em> 检查当前的Tiller是否正确安装。如下面所示:</p><pre><code class="bash">$ kubectl --namespace kube-system get pods | grep tiller tiller-deploy-dbb85cb99-p5cqt 0/1 ContainerCreating 0 46s</code></pre><p>或者直接使用 <em>helm version</em>,检查是否安装正确。如下所示。</p><pre><code class="bash">$ helm version Client: &version.Version{SemVer:"v2.12.3", GitCommit:"eecf22f77df5f65c823aacd2dbd30ae6c65f186e", GitTreeState:"clean"}Server: &version.Version{SemVer:"v2.12.3", GitCommit:"eecf22f77df5f65c823aacd2dbd30ae6c65f186e", GitTreeState:"clean"}</code></pre><p>一旦完成安装,就可以使用下面的命令执行相关的操作。</p><p><img src="./helm-cmd.png" alt="helm command"></p><h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><p>通过执行helm install命令,应用程序将部署在Kubernetes集群上。您可以跨集群安装多个图表。</p><blockquote class="colorquote warning"><p><strong>Important:</strong>如果没有使用<code>—name</code>选项指定版本名称,系统则会自动分配一个名称。</p></blockquote><p>你可以使用下面的Helm Chart 找到Redis的安装示例:</p><pre><code class="bash">$ helm install stable/redis</code></pre><p>也可以声明一个版本名称或者其他的选项。比如下面声明了一个版本名称为<code>my-redis</code>,并且指定该部署将使用持久化存储,大小为15G:</p><pre><code class="bash">$ helm install --name my-redis \ --set persistence.enabled=true,persistence.size=15Gi stable/redis</code></pre><p>安装图表后,“注释”部分将显示在安装信息的底部。它包含有关如何获取应用程序的IP地址或凭证的说明.</p><pre><code class="bash">NAME: my-influxLAST DEPLOYED: Thu Mar 14 12:36:50 2019NAMESPACE: defaultSTATUS: DEPLOYEDRESOURCES:==> v1beta1/DeploymentNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGEmy-influx-redis-slave 1 1 1 0 0s==> v1beta2/StatefulSetNAME DESIRED CURRENT AGEmy-influx-redis-master 1 0 0s==> v1/Pod(related)NAME READY STATUS RESTARTS AGEmy-influx-redis-slave-556f9894b7-wd5cf 0/1 ContainerCreating 0 0smy-influx-redis-master-0 0/1 Pending 0 0s==> v1/SecretNAME TYPE DATA AGEmy-influx-redis Opaque 1 0s==> v1/ConfigMapNAME DATA AGEmy-influx-redis 3 0smy-influx-redis-health 3 0s==> v1/ServiceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEmy-influx-redis-master ClusterIP 10.107.184.240 <none> 6379/TCP 0smy-influx-redis-slave ClusterIP 10.97.224.81 <none> 6379/TCP 0sNOTES:** Please be patient while the chart is being deployed **Redis can be accessed via port 6379 on the following DNS names from within your cluster:my-influx-redis-master.default.svc.cluster.local for read/write operationsmy-influx-redis-slave.default.svc.cluster.local for read-only operationsTo get your password run: export REDIS_PASSWORD=$(kubectl get secret --namespace default my-influx-redis -o jsonpath="{.data.redis-password}" | base64 --decode)To connect to your Redis server:1. Run a Redis pod that you can use as a client: kubectl run --namespace default my-influx-redis-client --rm --tty -i --restart='Never' \ --env REDIS_PASSWORD=$REDIS_PASSWORD \ --image docker.io/bitnami/redis:4.0.13 -- bash2. Connect using the Redis CLI: redis-cli -h my-influx-redis-master -a $REDIS_PASSWORD redis-cli -h my-influx-redis-slave -a $REDIS_PASSWORDTo connect to your database from outside the cluster execute the following commands: kubectl port-forward --namespace default svc/my-influx-redis 6379:6379 & redis-cli -h 127.0.0.1 -p 6379 -a $REDIS_PASSWORD</code></pre><h2 id="刪除-Release"><a href="#刪除-Release" class="headerlink" title="刪除 Release"></a>刪除 Release</h2><p>Helm 除了基本的建立功能外,还包含了整個 Release 的生命周期管理功能,如我们不需要该 Release 时,就可以通过以下方式刪除:</p><pre><code class="bash">$ helm ls NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACEmy-influx 1 Tue May 30 21:18:43 2017 DEPLOYED redis-6.3.0 4.0.13 default $ helm del my-influxrelease "my-influx" deleted</code></pre><p>此时并未完全删除,我们可以通过 <code>helm ls</code> 查看被刪除的 Release:</p><pre><code class="bash">$ helm ls --allNAME REVISION UPDATED STATUS CHART NAMESPACEmy-influx 2 Tue May 30 21:18:43 2017 DELETED redis-6.3.0 default</code></pre><blockquote><p>当执行 <code>helm ls</code> 指令为加入 <code>--all</code> 选项时,表示只列出<code>DEPLOYED</code>状态的 Release。</p></blockquote><pre><code class="bash">$ helm rollback my-influx 1Rollback was a success! Happy Helming!$ helm del my-influx --purgerelease "my-influx" deleted# 执行以下指令就不会看到已刪除的 Release.$ helm ls --all</code></pre><h2 id="建立简单的-Chart-结构"><a href="#建立简单的-Chart-结构" class="headerlink" title="建立简单的 Chart 结构"></a>建立简单的 Chart 结构</h2><p>Helm 提供了 <code>create</code> 指令建立一个 Chart 基本结构:</p><pre><code class="bash">$ helm create example$ tree example/example/├── charts├── Chart.yaml├── templates│ ├── deployment.yaml│ ├── _helpers.tpl│ ├── ingress.yaml│ ├── NOTES.txt│ └── service.yaml└── values.yaml</code></pre><p>当我们设置完 Chart 后,就可以通过 helm 指令打包:</p><pre><code class="bash">$ helm package example/example-0.1.0.tgz</code></pre><p>最后通过<em>helm</em>安装</p><pre><code class="bash">$ helm install ./example-0.1.0.tgz</code></pre><h2 id="自己建立-Repository"><a href="#自己建立-Repository" class="headerlink" title="自己建立 Repository"></a>自己建立 Repository</h2><p>Helm 指令除了可以建立 Chart 基本结构外,也提供了建立 Helm Repository 的功能,建立方式如下:</p><pre><code class="bash">$ helm serve --repo-path example-0.1.0.tgz$ helm repo add example http://repo-url</code></pre>]]></content>
<summary type="html">
<p>在Kubernetes集群中运行和管理应用程序的最简单方法是使用<a href="https://github.com/kubernetes/helm" target="_blank" rel="noopener">Helm</a>。Helm允许使用安装,升级或删除等操作来
</summary>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Helm" scheme="https://readailib.com/tags/Helm/"/>
<category term="Minikube" scheme="https://readailib.com/tags/Minikube/"/>
</entry>
<entry>
<title>使用 Kubernetes API 写点东西</title>
<link href="https://readailib.com/2019/03/13/kubernetes/client-go/pvc-watcher-example-for-client-go/"/>
<id>https://readailib.com/2019/03/13/kubernetes/client-go/pvc-watcher-example-for-client-go/</id>
<published>2019-03-13T13:46:37.000Z</published>
<updated>2019-03-14T04:41:44.379Z</updated>
<content type="html"><![CDATA[<p>本篇文章翻译自博客:<a href="https://medium.com/programming-kubernetes/building-stuff-with-the-kubernetes-api-part-4-using-go-b1d0e3c1c899" target="_blank" rel="noopener">https://medium.com/programming-kubernetes/building-stuff-with-the-kubernetes-api-part-4-using-go-b1d0e3c1c899</a></p><p>由于一直向通过一个<code>client-go</code>案例进入kubernetes api的开发,因此我想借这篇文章开启我之后的kubernetes api开发。当然 kubernetes的客户端不仅仅只有<code>client-go</code>一个,虽然<code>client-go</code>是最原生的一种客户端,另外还有python、java等实现的。下面将访问kubernetes集群的几种方式列举下来,仅供参考。(注:表格来自 <a href="https://jimmysong.io/kubernetes-handbook/develop/client-go-sample.html)" target="_blank" rel="noopener">https://jimmysong.io/kubernetes-handbook/develop/client-go-sample.html)</a></p><table><thead><tr><th>方式</th><th style="text-align:left">特点</th><th>支持者</th></tr></thead><tbody><tr><td>Kubernetes dashboard</td><td style="text-align:left">直接通过Web UI进行操作,简单直接,可定制化程度低</td><td>官方支持</td></tr><tr><td>kubectl</td><td style="text-align:left">命令行操作,功能最全,但是比较复杂,适合对其进行进一步的分装,定制功能,版本适配最好</td><td>官方支持</td></tr><tr><td><a href="https://github.com/kubernetes/client-go" target="_blank" rel="noopener">client-go</a></td><td style="text-align:left">从kubernetes的代码中抽离出来的客户端包,简单易用,但需要小心区分kubernetes的API版本</td><td>官方支持</td></tr><tr><td><a href="https://github.com/kubernetes-incubator/client-python" target="_blank" rel="noopener">client-python</a></td><td style="text-align:left">python客户端,kubernetes-incubator</td><td>官方支持</td></tr><tr><td><a href="https://github.com/fabric8io/kubernetes-client" target="_blank" rel="noopener">Java client</a></td><td style="text-align:left">fabric8中的一部分,kubernetes的java客户端</td><td>redhat</td></tr></tbody></table><p>下面将使用 <code>client-go</code> 实现一个简单的 <code>PVC</code> 对象监控工具,用于监控我们持久化卷的容量并告警。</p><h2 id="The-Kubernetes-Go-Client-Project-client-go"><a href="#The-Kubernetes-Go-Client-Project-client-go" class="headerlink" title="The Kubernetes Go Client Project (client-go)"></a>The Kubernetes Go Client Project (client-go)</h2><p>在进入代码之前,了解Kubernetes Go客户端(或<em>client-go</em>)项目是非常有必要的。它是Kubernetes客户端框架中最古老的,最原生的,因此具有更多的机关和功能。Client-go不使用<code>Swagger</code>API生成器,就像我们在之前的帖子中介绍过的OpenAPI客户端一样。相反,它使用源自Kubernetes项目的源代码生成器来创建Kubernetes样式的API对象和序列化对象。</p><p><code>client-go</code>是由很多包构成,可以满足从REST访问的编程范式到更复杂客户端的不同编程需求。</p><p><img src="https://cdn-images-1.medium.com/max/1600/1*4noxYkVSvMPmlbt1kSOIHw.png" alt="img"></p><p><em>RESTClient</em> 是一个基础包,它使用<em>api-machinery</em>存储库中的类型来提供对作为一组REST原语的API的访问。作为<em>RESTClient</em>之上的抽象而构建,<em>Clientset</em> 将是创建简单的Kubernetes客户端工具的起点。它公开了版本化的API资源及其序列化程序。></p><blockquote><p>There are several other packages in client-go including discovery, d<em>ynamic, and scale</em>. While we are not going to cover these packages, it is important to be aware of their capabilities.</p></blockquote><h2 id="A-simple-client-tool-for-Kubernetes"><a href="#A-simple-client-tool-for-Kubernetes" class="headerlink" title="A simple client tool for Kubernetes"></a>A simple client tool for Kubernetes</h2><p><img src="https://cdn-images-1.medium.com/max/1600/0*0QuAKMfq9V8PkhOf." alt="img"></p><blockquote><p>You can find the complete example on <a href="https://github.com/vladimirvivien/k8s-client-examples" target="_blank" rel="noopener">GitHub</a>.</p></blockquote><p>这个例子涵盖了 kubernetes Go客户端的几个高阶的概念</p><ul><li>连接</li><li>在集群中遍历资源列表</li><li>监视资源对象</li></ul><h2 id="Setup"><a href="#Setup" class="headerlink" title="Setup"></a>Setup</h2><p>client-go项目支持<em>Godep</em>和<em>dep</em>管理依赖。为了方便起见,这里使用<em>dep</em>(例如,以下是代码中所需的最小<em>Gopkg.toml</em>配置,它依赖于<em>client-go</em>版本<em>6.0</em>和版本<em>1.9</em>的Kubernetes API:</p><pre><code class="toml">[[constraint]] name = "k8s.io/api" version = "kubernetes-1.9.0"[[constraint]] name = "k8s.io/apimachinery" version = "kubernetes-1.9.0"[[constraint]] name = "k8s.io/client-go" version = "6.0.0"</code></pre><p>接下来,执行<code>dep ensure</code>完成后面的事情。</p><h2 id="Connecting-to-the-API-server"><a href="#Connecting-to-the-API-server" class="headerlink" title="Connecting to the API server"></a>Connecting to the API server</h2><p>我们Go client程序第一步就是与API Server建立连接,为了实现连接,我们此时需要用到 <em>clientcmd</em> 包。</p><pre><code class="go">import (... "k8s.io/client-go/tools/clientcmd")func main() { kubeconfig := filepath.Join( os.Getenv("HOME"), ".kube", "config", ) config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { log.Fatal(err) }...}</code></pre><p>Client-go通过提供实用程序功能来从不同的上下文导入配置,从而使这成为一项微不足道的任务,主要有以下几种方式。</p><h3 id="From-a-config-file"><a href="#From-a-config-file" class="headerlink" title="From a config file"></a>From a config file</h3><p>如上例所示,您可以从<em>kubeconfig</em>文件获取配置用于连接API服务器。当你的代码在集群外运行时,这是理想的选择。</p><pre><code class="go">clientcmd.BuildConfigFromFlags("", configFile)</code></pre><h3 id="From-a-cluster"><a href="#From-a-cluster" class="headerlink" title="From a cluster"></a>From a cluster</h3><p>如果您的代码将部署在Kubernetes集群中,则可以使用与上面相同但参数为空的函数,在客户端代码注定要在pod中运行时,使用集群信息配置连接。</p><pre><code class="go">clientcmd.BuildConfigFromFlags("", "")</code></pre><p>或者,使用 <em>rest</em> 包直接从集群信息创建配置,如下所示:</p><pre><code class="go">import "k8s.io/client-go/rest"...rest.InClusterConfig()</code></pre><h3 id="Create-a-clientset"><a href="#Create-a-clientset" class="headerlink" title="Create a clientset"></a>Create a clientset</h3><p>我们需要创建一个客户端序列化对象来让我们访问API对象。<em>Clientset</em>类型来自 <em>kubernetes</em> 包,提供对生成的客户端序列化对象的访问,以访问版本化(比如:v1)API对象,如下所示。</p><pre><code class="go">type Clientset struct { *authenticationv1beta1.AuthenticationV1beta1Client *authorizationv1.AuthorizationV1Client... *corev1.CoreV1Client}</code></pre><p>一旦我们完成了配置连接,我们就可以使用配置初始化一个<em>clientset</em>对象。如下所示</p><pre><code class="go">func main() { config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) ... clientset, err := kubernetes.NewForConfig(config) if err != nil { log.Fatal(err) }}</code></pre><p>对于我们的例子,我们使用`v1版本的API对象,因此接下来,我们将会使用<em>clientset</em>调用<em>CoreV1</em>方法来访问核心的API资源。</p><pre><code class="go">func main() { ... clientset, err := kubernetes.NewForConfig(config) if err != nil { log.Fatal(err) } api := clientset.CoreV1()}</code></pre><blockquote><p>You can see the available clientsets <a href="https://github.com/kubernetes/client-go/blob/master/kubernetes/clientset.go" target="_blank" rel="noopener">here</a>.</p></blockquote><h2 id="Listing-cluster-PVCs"><a href="#Listing-cluster-PVCs" class="headerlink" title="Listing cluster PVCs"></a>Listing cluster PVCs</h2><p>我们可以对clientset执行的最基本操作之一是检索存储的API对象的资源列表。在这里我们将检索一个特定的<em>namespace</em>的PVC列表,如下所示。</p><pre><code class="go">import (... metav1 "k8s.io/apimachinery/pkg/apis/meta/v1")func main() { var ns, label, field string flag.StringVar(&ns, "namespace", "", "namespace") flag.StringVar(&label, "l", "", "Label selector") flag.StringVar(&field, "f", "", "Field selector")... api := clientset.CoreV1() // setup list options listOptions := metav1.ListOptions{ LabelSelector: label, FieldSelector: field, } pvcs, err := api.PersistentVolumeClaims(ns).List(listOptions) if err != nil { log.Fatal(err) } printPVCs(pvcs)...}</code></pre><p>在上面的代码片段中,我们使用<code>ListOptions</code>来指定标签和字段选择器(以及命名空间),以缩小返回的类型为<code>v1.PeristentVolumeClaimList</code>的PVC资源。下一个代码段显示了我们如何遍历和打印从集群中检索到的PVC列表。</p><pre><code class="go">func printPVCs(pvcs *v1.PersistentVolumeClaimList) { template := "%-32s%-8s%-8s\n" fmt.Printf(template, "NAME", "STATUS", "CAPACITY") for _, pvc := range pvcs.Items { quant := pvc.Spec.Resources.Requests[v1.ResourceStorage] fmt.Printf( template, pvc.Name, string(pvc.Status.Phase), quant.String()) }}</code></pre><h2 id="Watching-the-cluster-PVCs"><a href="#Watching-the-cluster-PVCs" class="headerlink" title="Watching the cluster PVCs"></a>Watching the cluster PVCs</h2><p>Kubernetes Go客户端框架支持在指定的API对象生命周期事件中监视集群的能力,包括分别创建,更新和删除对象时生成的<code>ADDED</code>,<code>MODIFIED</code>,<code>DELETED</code>。对于我们的简单CLI工具,我们将使用此监视功能来监视已声明的持久存储对正在运行的群集的总容量。</p><p>当给定命名空间的总的声明容量达到某个阈值(比如<code>200Gi</code>)时,我们将采取任意行动。为简单起见,我们将在屏幕上打印通知。但是,在更复杂的实现中,可以使用相同的方式来触发某些自动操作。</p><h3 id="Setup-a-watch"><a href="#Setup-a-watch" class="headerlink" title="Setup a watch"></a>Setup a watch</h3><p>现在,让我们使用<code>Watch</code>方法为<code>PersistentVolumeClaim</code>资源创建一个监视器。然后,通过方法<code>ResultChan</code>从Go通道接受事件通知。</p><pre><code class="go">func main() {... api := clientset.CoreV1() listOptions := metav1.ListOptions{ LabelSelector: label, FieldSelector: field, } watcher, err :=api.PersistentVolumeClaims(ns). Watch(listOptions) if err != nil { log.Fatal(err) } ch := watcher.ResultChan()...}</code></pre><h3 id="Loop-through-events"><a href="#Loop-through-events" class="headerlink" title="Loop through events"></a>Loop through events</h3><p>接下来,我们准备开始处理资源事件。然而,在我们处理事件之前,我们声明变量<code>maxClaimsQuant</code>和<code>totalClaimQuant</code>其中<code>totalClaimQuant</code>类型为<code>resource.Quantity</code>(表示k8s中的SI数量)来设置我们的数量阈值和运行总数。</p><pre><code class="go">import( "k8s.io/apimachinery/pkg/api/resource" ...)func main() { var maxClaims string flag.StringVar(&maxClaims, "max-claims", "200Gi", "Maximum total claims to watch") var totalClaimedQuant resource.Quantity maxClaimedQuant := resource.MustParse(maxClaims)... ch := watcher.ResultChan() for event := range ch { pvc, ok := event.Object.(*v1.PersistentVolumeClaim) if !ok { log.Fatal("unexpected type") } ... }}</code></pre><p>上面的<em>for-range</em>循环中的<code>watcher</code>的通道用于处理来自服务器的事件传入通知。每个事件都分配给变量<code>event</code>,其中<code>event.Object</code>值被声明为<code>PersistentVolumeClaim</code>类型,因此我们可以提取所需的信息。</p><h3 id="Processing-ADDED-events"><a href="#Processing-ADDED-events" class="headerlink" title="Processing ADDED events"></a>Processing ADDED events</h3><p>添加新PVC时,<code>event.Type</code>设置为值<code>watch.Added</code>。然后,我们使用以下代码提取添加的声明(<code>quant</code>)的容量,将其添加到运行总容量(<code>totalClaimedQuant</code>)。最后,我们检查总容量是否大于定义的最大容量(<code>maxClaimedQuant</code>)。如果是大于,程序可以触发一个动作。</p><pre><code class="go">import( "k8s.io/apimachinery/pkg/watch" ...)func main() {... for event := range ch { pvc, ok := event.Object.(*v1.PersistentVolumeClaim) if !ok { log.Fatal("unexpected type") } quant := pvc.Spec.Resources.Requests[v1.ResourceStorage] switch event.Type { case watch.Added: totalClaimedQuant.Add(quant) log.Printf("PVC %s added, claim size %s\n", pvc.Name, quant.String()) if totalClaimedQuant.Cmp(maxClaimedQuant) == 1 { log.Printf( "\nClaim overage reached: max %s at %s", maxClaimedQuant.String(), totalClaimedQuant.String()) // trigger action log.Println("*** Taking action ***") } } ... } }}</code></pre><h3 id="Process-DELETED-events"><a href="#Process-DELETED-events" class="headerlink" title="Process DELETED events"></a>Process DELETED events</h3><p>代码也会在删除PVC时做出反应。它从运行总计数中减少已删除的PVC大小。</p><pre><code class="go">func main() {... for event := range ch { ... switch event.Type { case watch.Deleted: quant := pvc.Spec.Resources.Requests[v1.ResourceStorage] totalClaimedQuant.Sub(quant) log.Printf("PVC %s removed, size %s\n", pvc.Name, quant.String()) if totalClaimedQuant.Cmp(maxClaimedQuant) <= 0 { log.Printf("Claim usage normal: max %s at %s", maxClaimedQuant.String(), totalClaimedQuant.String(), ) // trigger action log.Println("*** Taking action ***") } } ... }}</code></pre><h2 id="Run-the-program"><a href="#Run-the-program" class="headerlink" title="Run the program"></a>Run the program</h2><pre><code class="bash"># 创建两个pvc,此时观察程序的日志输出$ helm install --name my-redis2-redis \--set persistence.enabled=true,persistence.size=100Gi stable/redis$ helm install --name my-redis-redis \--set persistence.enabled=true,persistence.size=50Gi stable/redis</code></pre><p>当针对正在运行的集群执行程序时,它首先显示现有PVC的列表。然后它开始在集群中查看新的PersistentVolumeClaim事件。</p><pre><code class="bash">$ ./pvcwatchUsing kubeconfig: /Users/vladimir/.kube/config--- PVCs ----NAME STATUS CAPACITYmy-redis-redis Bound 50Gimy-redis2-redis Bound 100Gi-----------------------------Total capacity claimed: 150Gi-------------------------------- PVC Watch (max claims 200Gi) ----2018/02/13 21:55:03 PVC my-redis2-redis added, claim size 100Gi2018/02/13 21:55:03At 50.0% claim capcity (100Gi/200Gi)2018/02/13 21:55:03 PVC my-redis-redis added, claim size 50Gi2018/02/13 21:55:03At 75.0% claim capcity (150Gi/200Gi)</code></pre><p>接下来,让我们将另一个应用程序部署到请求额外的<code>75Gi</code>存储声明的集群中(对于我们的示例,让我们使用<a href="https://github.com/kubernetes/helm" target="_blank" rel="noopener">Helm</a>来部署一个实例,例如,<a href="https://github.com/kubernetes/charts/tree/master/stable/influxdb" target="_blank" rel="noopener">InfluxDB</a> 实例)。</p><pre><code class="bash">$ helm install --name my-influx \--set persistence.enabled=true,persistence.size=75Gi stable/influxdb</code></pre><p>如下所示,我们的工具会立即对新的声明卷作出反应,并显示我们的警报,因为总声明容量超过了阈值。</p><pre><code class="bash">--- PVC Watch (max claims 200Gi) ----...2018/02/13 21:55:03At 75.0% claim capcity (150Gi/200Gi)2018/02/13 22:01:29 PVC my-influx-influxdb added, claim size 75Gi2018/02/13 22:01:29Claim overage reached: max 200Gi at 225Gi2018/02/13 22:01:29 *** Taking action ***2018/02/13 22:01:29At 112.5% claim capcity (225Gi/200Gi)</code></pre><p>相反,当从集群中删除PVC时,该工具会相应地响应警报消息。</p><pre><code class="bash">...At 112.5% claim capcity (225Gi/200Gi)2018/02/14 11:30:36 PVC my-redis2-redis removed, size 100Gi2018/02/14 11:30:36 Claim usage normal: max 200Gi at 125Gi2018/02/14 11:30:36 *** Taking action ***</code></pre><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><p>Series <a href="https://medium.com/programming-kubernetes/building-stuff-with-the-kubernetes-api-toc-84d751876650" target="_blank" rel="noopener">table of content</a></p><p>Code — <a href="https://github.com/vladimirvivien/k8s-client-examples" target="_blank" rel="noopener">https://github.com/vladimirvivien/k8s-client-examples</a></p><p>Kubernetes clients — <a href="https://kubernetes.io/docs/reference/client-libraries/" target="_blank" rel="noopener">https://kubernetes.io/docs/reference/client-libraries/</a></p><p>Client-go — <a href="https://github.com/kubernetes/client-go" target="_blank" rel="noopener">https://github.com/kubernetes/client-go</a></p><p>Kubernetes API reference — <a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/" target="_blank" rel="noopener">https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/</a></p>]]></content>
<summary type="html">
<p>本篇文章翻译自博客:<a href="https://medium.com/programming-kubernetes/building-stuff-with-the-kubernetes-api-part-4-using-go-b1d0e3c1c899" target=
</summary>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Golang" scheme="https://readailib.com/tags/Golang/"/>
<category term="client-go" scheme="https://readailib.com/tags/client-go/"/>
</entry>
<entry>
<title>在树莓派上编译安装Go版本的Tensorflow</title>
<link href="https://readailib.com/2019/03/07/golang/tensorflow/tensorflow-for-golang-on-raspberrypi/"/>
<id>https://readailib.com/2019/03/07/golang/tensorflow/tensorflow-for-golang-on-raspberrypi/</id>
<published>2019-03-06T16:14:16.000Z</published>
<updated>2019-03-07T07:13:29.046Z</updated>
<content type="html"><![CDATA[<h1 id="0-Used-Hardwares-and-Softwares"><a href="#0-Used-Hardwares-and-Softwares" class="headerlink" title="0. Used Hardwares and Softwares"></a>0. Used Hardwares and Softwares</h1><p>All steps were taken on my <strong>Raspberry Pi 3 B</strong> model with:</p><ul><li>Minimum GPU memory allocated (16MB)</li><li><strong>1GB</strong> of swap memory</li><li>External USB HDD (as root partition)</li></ul><a id="more"></a><p>and software versions were:</p><ul><li>Raspbian (Stretch) / gcc 6.3.0</li><li>Tensorflow 1.3.0</li><li>Protobuf 3.1.0</li><li>Bazel 0.5.1</li></ul><p>Before the beginning, I had to install dependencies:</p><h3 id="for-protobuf"><a href="#for-protobuf" class="headerlink" title="for protobuf"></a>for protobuf</h3><pre><code class="bash">$ sudo apt-get install autoconf automake libtool</code></pre><h3 id="for-bazel"><a href="#for-bazel" class="headerlink" title="for bazel"></a>for bazel</h3><pre><code class="bash">$ sudo apt-get install pkg-config zip g++ zlib1g-dev unzip oracle-java8-jdk</code></pre><hr><h1 id="1-Install-Protobuf"><a href="#1-Install-Protobuf" class="headerlink" title="1. Install Protobuf"></a>1. Install Protobuf</h1><p>I cloned the protobuf’s repository:</p><pre><code class="bash">$ git clone https://github.com/google/protobuf.git</code></pre><p>and started building:</p><pre><code class="bash">$ cd protobuf$ git checkout v3.1.0$ ./autogen.sh$ ./configure$ make -j 4$ sudo make install$ sudo ldconfig</code></pre><p>It took less than an hour to finish.</p><p>I could see the version of installed protobuf with:</p><pre><code class="bash">$ protoc --versionlibprotoc 3.1.0</code></pre><h1 id="2-Install-Bazel"><a href="#2-Install-Bazel" class="headerlink" title="2. Install Bazel"></a>2. Install Bazel</h1><h2 id="a-download"><a href="#a-download" class="headerlink" title="a. download"></a>a. download</h2><p>I got a zip file of bazel from <a href="https://github.com/bazelbuild/bazel/releases" target="_blank" rel="noopener">here</a> and unzipped it:</p><pre><code class="bash">$ wget https://github.com/bazelbuild/bazel/releases/download/0.5.1/bazel-0.5.1-dist.zip$ unzip -d bazel bazel-0.5.1-dist.zip</code></pre><h2 id="b-edit-bootstrap-files"><a href="#b-edit-bootstrap-files" class="headerlink" title="b. edit bootstrap files"></a>b. edit bootstrap files</h2><p>In the unzipped directory, I opened the <code>scripts/bootstrap/compile.sh</code> file:</p><pre><code class="bash">$ cd bazel$ vi scripts/bootstrap/compile.sh</code></pre><p>searched for lines that looked like following:</p><pre><code class="bash">run "${JAVAC}" -classpath "${classpath}" -sourcepath "${sourcepath}" \ -d "${output}/classes" -source "$JAVA_VERSION" -target "$JAVA_VERSION" \ -encoding UTF-8 "@${paramfile}"</code></pre><p>and appended <code>-J-Xmx500M</code> to the last line so that the whole lines would look like:</p><pre><code class="bash">run "${JAVAC}" -classpath "${classpath}" -sourcepath "${sourcepath}" \ -d "${output}/classes" -source "$JAVA_VERSION" -target "$JAVA_VERSION" \ -encoding UTF-8 "@${paramfile}" -J-Xmx500M</code></pre><p>It was for enlarging the max heap size of Java.</p><h2 id="c-compile"><a href="#c-compile" class="headerlink" title="c. compile"></a>c. compile</h2><p>After that, started building with:</p><pre><code class="bash">$ chmod u+w ./* -R$ ./compile.sh</code></pre><p>It also took about an hour.</p><h2 id="d-install"><a href="#d-install" class="headerlink" title="d. install"></a>d. install</h2><p>After the compilation had finished, I could find the compiled binary in <code>output</code> directory.</p><p>Copied it into <code>/usr/local/bin</code> directory:</p><pre><code class="bash">$ sudo cp output/bazel /usr/local/bin/</code></pre><h1 id="3-Build-libtensorflow-so"><a href="#3-Build-libtensorflow-so" class="headerlink" title="3. Build libtensorflow.so"></a>3. Build libtensorflow.so</h1><p>(I referenced <a href="https://github.com/samjabrahams/tensorflow-on-raspberry-pi/blob/master/GUIDE.md" target="_blank" rel="noopener">this document</a> for following processes)</p><h2 id="a-download-1"><a href="#a-download-1" class="headerlink" title="a. download"></a>a. download</h2><p>Got the tensorflow go code with:</p><pre><code class="bash">$ go get -d github.com/tensorflow/tensorflow/tensorflow/go</code></pre><h2 id="b-edit-files"><a href="#b-edit-files" class="headerlink" title="b. edit files"></a>b. edit files</h2><p>In the downloaded directory, I checked out the latest tag and replaced <code>lib64</code> to <code>lib</code> in the files with:</p><pre><code class="bash">$ cd ${GOPATH}/src/github.com/tensorflow/tensorflow$ git fetch --all --tags --prune$ git checkout tags/v1.3.0$ grep -Rl 'lib64' | xargs sed -i 's/lib64/lib/g'</code></pre><p>Raspberry Pi still runs on 32bit OS, so they had to be changed like this.</p><p>After that, I commented <code>#define IS_MOBILE_PLATFORM</code> out in <code>tensorflow/core/platform/platform.h</code>:</p><pre><code class="c">// Since there's no macro for the Raspberry Pi, assume we're on a mobile// platform if we're compiling for the ARM CPU.//#define IS_MOBILE_PLATFORM // <= commented this line</code></pre><p>If it is not commented out, bazel will build for mobile platforms like <code>iOS</code> or <code>Android</code>, not Raspberry Pi.</p><p>To do this easily, just run:</p><pre><code class="bash">$ sed -i "s|#define IS_MOBILE_PLATFORM|//#define IS_MOBILE_PLATFORM|g" tensorflow/core/platform/platform.h</code></pre><p>Finally, it was time to configure and build tensorflow.</p><h2 id="c-configure-and-build"><a href="#c-configure-and-build" class="headerlink" title="c. configure and build"></a>c. configure and build</h2><pre><code class="bash">$ ./configure</code></pre><p>I had to answer to some questions here.</p><p>Then I started building <code>libtensorflow.so</code> with:</p><pre><code class="bash">$ bazel build -c opt --copt="-mfpu=neon-vfpv4" --copt="-funsafe-math-optimizations" --copt="-ftree-vectorize" --copt="-fomit-frame-pointer" --jobs 1 --local_resources 1024,1.0,1.0 --verbose_failures --genrule_strategy=standalone --spawn_strategy=standalone //tensorflow:libtensorflow.so</code></pre><p>My Pi became unresponsive many times during this process, but I kept it going on.</p><h2 id="d-install-1"><a href="#d-install-1" class="headerlink" title="d. install"></a>d. install</h2><p>After a long time of struggle, (it took nearly 7 hours for me!)</p><p>I finally got <code>libtensorflow.so</code> compiled in <code>bazel-bin/tensorflow/</code>.</p><p>So I copied it into <code>/usr/local/lib/</code>:</p><pre><code class="bash">$ sudo cp ./bazel-bin/tensorflow/libtensorflow.so /usr/local/lib/$ sudo chmod 644 /usr/local/lib/libtensorflow.so$ sudo ldconfig</code></pre><p>All done. Time to test!</p><h1 id="4-Go-Test"><a href="#4-Go-Test" class="headerlink" title="4. Go Test"></a>4. Go Test</h1><p>I ran a test for validating the installation:</p><pre><code class="bash">$ go test github.com/tensorflow/tensorflow/tensorflow/go</code></pre><p>then I could see:</p><pre><code class="bash">ok github.com/tensorflow/tensorflow/tensorflow/go 0.350s</code></pre><p>Ok, it works!</p><p><strong>Edit</strong>: As <a href="https://github.com/tensorflow/tensorflow/tree/master/tensorflow/go#generate-wrapper-functions-for-ops" target="_blank" rel="noopener">this instruction</a> says, I had to regenerate operations before the test:</p><pre><code class="bash">$ go generate github.com/tensorflow/tensorflow/tensorflow/go/op</code></pre><h1 id="5-Further-Test"><a href="#5-Further-Test" class="headerlink" title="5. Further Test"></a>5. Further Test</h1><p>I wanted to see a simple go program running, so I wrote this code:</p><pre><code class="go">// sample.gopackage mainimport ( "fmt" tf "github.com/tensorflow/tensorflow/tensorflow/go")// Sorry - I don't have a good example yet :-Pfunc main() { tensor, _ := tf.NewTensor(int64(42)) if v, ok := tensor.Value().(int64); ok { fmt.Printf("The answer to the life, universe, and everything: %v\n", v) }}</code></pre><p>and ran it with <code>go run sample.go</code>:</p><pre><code>The answer to the life, universe, and everything: 42</code></pre><p>See the result?</p><p>From now on, I can write tensorflow applications in go, on Raspberry Pi! :-)</p><hr><h1 id="98-Trouble-shooting"><a href="#98-Trouble-shooting" class="headerlink" title="98. Trouble shooting"></a>98. Trouble shooting</h1><h2 id="Build-failure-due-to-a-problem-with-Eigen"><a href="#Build-failure-due-to-a-problem-with-Eigen" class="headerlink" title="Build failure due to a problem with Eigen"></a>Build failure due to a problem with Eigen</h2><p>Back in the day with <a href="https://github.com/tensorflow/tensorflow/releases/tag/v1.2.0" target="_blank" rel="noopener">Tensorflow 1.2.0</a>, I encountered <a href="https://github.com/tensorflow/tensorflow/issues/9697" target="_blank" rel="noopener">this issue</a> while building, but it’s still not fixed yet in <a href="https://github.com/tensorflow/tensorflow/releases/tag/v1.3.0" target="_blank" rel="noopener">1.3.0</a>.</p><p>So I had to work around this problem again by editing <code>tensorflow/workspace.bzl</code>from:</p><pre><code class="bzl">native.new_http_archive( name = "eigen_archive", urls = [ "http://mirror.bazel.build/bitbucket.org/eigen/eigen/get/f3a22f35b044.tar.gz", "https://bitbucket.org/eigen/eigen/get/f3a22f35b044.tar.gz", ], sha256 = "ca7beac153d4059c02c8fc59816c82d54ea47fe58365e8aded4082ded0b820c4", strip_prefix = "eigen-eigen-f3a22f35b044", build_file = str(Label("//third_party:eigen.BUILD")),)</code></pre><p>to:</p><pre><code class="bash">native.new_http_archive( name = "eigen_archive", urls = [ "http://mirror.bazel.build/bitbucket.org/eigen/eigen/get/d781c1de9834.tar.gz", "https://bitbucket.org/eigen/eigen/get/d781c1de9834.tar.gz", ], sha256 = "a34b208da6ec18fa8da963369e166e4a368612c14d956dd2f9d7072904675d9b", strip_prefix = "eigen-eigen-d781c1de9834", build_file = str(Label("//third_party:eigen.BUILD")),)</code></pre><p>and starting again from the beginning:</p><pre><code class="bash">$ bazel clean$ ./configure$ bazel build -c opt --copt="-mfpu=neon-vfpv4" --copt="-funsafe-math-optimizations" --copt="-ftree-vectorize" --copt="-fomit-frame-pointer" --jobs 1 --local_resources 1024,1.0,1.0 --verbose_failures --genrule_strategy=standalone --spawn_strategy=standalone //tensorflow:libtensorflow.so...</code></pre><p>Then I could build it without further problems.</p><p>I hope it would be fixed on future releases.</p><hr><h1 id="99-Wrap-up"><a href="#99-Wrap-up" class="headerlink" title="99. Wrap-up"></a>99. Wrap-up</h1><p>Installing TensorFlow on Raspberry Pi is not easy yet. (There’s <a href="https://github.com/samjabrahams/tensorflow-on-raspberry-pi" target="_blank" rel="noopener">a kind project</a> which makes it super easy though!)</p><p>Installing <code>libtensorflow.so</code> is a lot more difficult, because it takes too much time to build it.</p><p>But it is worth trying; managing TensorFlow graphs in golang will be handy for people who don’t love python - just like me.</p><hr><h1 id="999-If-you-need-one"><a href="#999-If-you-need-one" class="headerlink" title="999. If you need one,"></a>999. If you need one,</h1><p>You don’t have time to build it yourself, but still need the compiled file?</p><p>Good, take it <a href="https://github.com/meinside/libtensorflow.so-raspberrypi/releases" target="_blank" rel="noopener">here</a>.</p><p>I cannot promise, but will try keeping it up-to-date whenever a newer version of tensorflow comes out.</p>]]></content>
<summary type="html">
<h1 id="0-Used-Hardwares-and-Softwares"><a href="#0-Used-Hardwares-and-Softwares" class="headerlink" title="0. Used Hardwares and Softwares"></a>0. Used Hardwares and Softwares</h1><p>All steps were taken on my <strong>Raspberry Pi 3 B</strong> model with:</p>
<ul>
<li>Minimum GPU memory allocated (16MB)</li>
<li><strong>1GB</strong> of swap memory</li>
<li>External USB HDD (as root partition)</li>
</ul>
</summary>
<category term="Golang" scheme="https://readailib.com/tags/Golang/"/>
<category term="RaspberryPi" scheme="https://readailib.com/tags/RaspberryPi/"/>
<category term="Tensorflow" scheme="https://readailib.com/tags/Tensorflow/"/>
<category term="ARM" scheme="https://readailib.com/tags/ARM/"/>
</entry>
<entry>
<title>在树莓派kubernetes集群部署gRPC框架编写的微服务</title>
<link href="https://readailib.com/2019/03/05/kubernetes/raspberrypi/deploy-a-microservice-on-k8s/"/>
<id>https://readailib.com/2019/03/05/kubernetes/raspberrypi/deploy-a-microservice-on-k8s/</id>
<published>2019-03-04T16:14:16.000Z</published>
<updated>2019-03-06T00:50:11.802Z</updated>
<content type="html"><![CDATA[<p>今天带着大家如何在树莓派kubernetes集群中部署微服务,这次文章和上次文章的区别就是这个涉及两个微服务,如何使两个微服务在kubernetes中实现服务之间的调用可能是与上次的不同之处,这次也使用Google的开源框架gRPC框架来加快微服务的开发.</p><a id="more"></a><p>本次教程需要准备:</p><ul><li>一个启动好的树莓派kubernetes集群(参考: <a href="https://41sh.cn/?id=16" target="_blank" rel="noopener">边缘智能-在树莓派上部署kubernetes集群</a>)</li><li>protobuf工具(参考 <a href="https://github.com/google/protobuf,从release页下载对应的操作系统的版本即可" target="_blank" rel="noopener">https://github.com/google/protobuf,从release页下载对应的操作系统的版本即可</a>)</li><li>Docker (参考: <a href="https://docs.docker.com/engine/installation/" target="_blank" rel="noopener">https://docs.docker.com/engine/installation/</a>)</li><li>kubectl工具 (参考: <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/" target="_blank" rel="noopener">https://kubernetes.io/docs/tasks/tools/install-kubectl/</a>)</li></ul><p>本文章参考: </p><ul><li><a href="https://outcrawl.com/getting-started-microservices-go-grpc-kubernetes" target="_blank" rel="noopener">Getting Started with Microservices using Go, gRPC and Kubernetes</a></li></ul><p>本文章的实例代码:</p><ul><li><a href="https://github.com/rh01/grpc-microservice-k8s" target="_blank" rel="noopener">https://github.com/rh01/grpc-microservice-k8s</a></li></ul><h2 id="定义我们的protobuf文件"><a href="#定义我们的protobuf文件" class="headerlink" title="定义我们的protobuf文件"></a>定义我们的protobuf文件</h2><p>protobuf是google的一个序列化结构化数据工具,它可以让人们定义好相关的结构,使用protoc工具自动生成对应的代码.类似的结构化工具还有thrift.</p><blockquote><p>微服务:这里我主要实现一个最大公约数的功能,输入两个数值,返回这两个数的最大公约数.</p></blockquote><p>这里既然使用gRPC来做,那么主要使用rpc来实现服务调用,因为rpc实现的是服务之间的同步调用,即客户端调用服务并等待响应。gRPC是提供RPC功能的框架之一。此时我们需要使用Protocol Buffer的接口定义语言中编写消息类型和服务的代码并进行编译。</p><p>下面就是我们使用protobuf语言定义的消息类型和服务.(具体参考: <a href="https://grpc.io/docs/quickstart/go.html)" target="_blank" rel="noopener">https://grpc.io/docs/quickstart/go.html)</a></p><pre><code class="bash">$ mkdir -pv ~/go/src/github.com/rh01/mini-deploy-app/pb$ cd ~/go/src/github.com/rh01/mini-deploy-app/pb$ vim pb.proto</code></pre><pre><code class="yaml">syntax = "proto3"; // protobuf 版本package pb; // 代码生成的package名字// 定义的请求消息体message GCDRequest { uint64 a = 1; uint64 b = 2;}// 定义的响应消息体message GCDResponse { uint64 result = 1;}// 调用的远程服务,这是client请求server端的远程计算服务service GCDService { rpc Compute (GCDRequest) returns (GCDResponse) {}}</code></pre><p>接下来我们需要使用 protoc 生成对应的服务代码</p><pre><code>$ protoc -I . --go_out=plugins=grpc:. ./*.proto</code></pre><blockquote class="colorquote warning"><p><strong>提前须知</strong>:</p><p>1). 执行上面的指令需要使用安装 grpc 和 proto-gen-go 工具,使用下面的命令:</p><pre><code class="bash">$ go get -u google.golang.org/grpc$ go get -u github.com/golang/protobuf/protoc-gen-go</code></pre><p>2). 将 $GOPATH/bin 目录添加到PATH环境变量中</p><pre><code class="bash">$ <span class="built_in">export</span> PATH=<span class="variable">$PATH</span>:<span class="variable">$GOPATH</span>/bin</code></pre></blockquote><p>这时应该生成了 gcd.pb.go 程序.</p><h2 id="最大公约数服务"><a href="#最大公约数服务" class="headerlink" title="最大公约数服务"></a>最大公约数服务</h2><h3 id="定义服务端"><a href="#定义服务端" class="headerlink" title="定义服务端"></a>定义服务端</h3><p>gcd 服务将会使用上一步生成的代码进行实现gcd计算服务.</p><pre><code class="bash">$ cd ~/go/src/github.com/rh01/mini-deploy-app/$ mkdir -p gcd$ vim main.go</code></pre><pre><code class="go">package mainimport ( "log" "net" context "golang.org/x/net/context" pb "github.com/rh01/mini-deploy-app/pb" "google.golang.org/grpc" "google.golang.org/grpc/reflection")</code></pre><p>在main函数中主要定义server结构,并将其注册为server端,用于处理 gcd 计算的请求.然后启动grpc服务.</p><pre><code class="go">type server struct {}func main() { lis, err := net.Listen("tcp", ":3000") if err != nil { log.Fatalf("Failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGCDServiceServer(s, &server{}) reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf("Failed to serve: %v", err) }}</code></pre><p>实现GCDServiceServer接口的 <code>Compute</code> 方法, server 结构对象的指针作为方法接受者.</p><pre><code class="Go">// gcd.pb.go// GCDServiceServer is the server API for GCDService service.type GCDServiceServer interface { Compute(context.Context, *GCDRequest) (*GCDResponse, error)}func (s *server) Compute(ctx context.Context, r *pb.GCDRequest) (*pb.GCDResponse, error) { a, b := r.A, r.B for b != 0 { a, b = b, a%b } return &pb.GCDResponse{Result: a}, nil}</code></pre><h2 id="定义RESTFul客户端"><a href="#定义RESTFul客户端" class="headerlink" title="定义RESTFul客户端"></a>定义RESTFul客户端</h2><p>前端使用 <a href="https://github.com/gin-gonic/gin" target="_blank" rel="noopener">gin</a> 框架,主要是提供一个REST风格的访问方式和调用我们定义的gcd服务端执行实际的计算任务.</p><pre><code class="go">$ cd ~/go/src/github.com/rh01/mini-deploy-app/$ mkdir -p api$ vim main.go</code></pre><pre><code>package mainimport ( fmt "fmt" "log" "net/http" "strconv" "github.com/gin-gonic/gin" pb "github.com/rh01/mini-deploy-app/pb" "google.golang.org/grpc")func main() { conn, err := grpc.Dial("gcd-service:3000", grpc.WithInsecure()) if err != nil { log.Fatalf("Dial failed: %v", err) } gcdClient := pb.NewGCDServiceClient(conn)}</code></pre><p>上面的代码主要使用rpc的方式访问我们定义的服务端,此时的 gcd-service:3000 就是我们gcd服务端的 endpoint,这个就是服务地址,需要在kubernetes中定义.</p><pre><code class="go"> r := gin.Default() r.GET("/gcd/:a/:b", func(c *gin.Context) { // Parse parameters a, err := strconv.ParseUint(c.Param("a"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter A"}) return } b, err := strconv.ParseUint(c.Param("b"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter B"}) return } // Call GCD service req := &pb.GCDRequest{A: a, B: b} if res, err := gcdClient.Compute(c, req); err == nil { c.JSON(http.StatusOK, gin.H{ "result": fmt.Sprint(res.Result), }) } else { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } })</code></pre><p>接下来处理 /gcd/:a/:b 请求,读取参数 A 和 B,然后调用GCD服务.</p><p>最后运行我们的REST API端.启动一个API server.</p><pre><code class="go"> // Run HTTP server if err := r.Run(":3000"); err != nil { log.Fatalf("Failed to run server: %v", err) }</code></pre><h2 id="构建Docker镜像"><a href="#构建Docker镜像" class="headerlink" title="构建Docker镜像"></a>构建Docker镜像</h2><p>下面是我定义的Dockerfile文件,因为有两个服务,所以这里分成两个Docker镜像,一个是gcd服务,另外一个是提供RESTAPI访问的客户端api.</p><blockquote><p>有关Docker的多阶段构建参考: <a href="https://www.41sh.cn/?id=61" target="_blank" rel="noopener">[实战] 将golang编写的微服务部署在树莓派搭建的kubernetes集群</a></p></blockquote><pre><code class="dockerfile"># Dockerfile.gcdFROM golang AS build-envWORKDIR /go/src/github.com/rh01/mini-deploy-app/gcdCOPY gcd .COPY pb ../pbCOPY vendor ../vendorENV http_proxy http://192.168.1.9:12333ENV https_proxy http://192.168.1.9:12333RUN go get -u -v github.com/kardianos/govendorRUN govendor syncRUN GOOS=linux GOARCH=arm GOARM=7 go build -v -o /go/src/github.com/rh01/mini-deploy-app/gcd-serverFROM armhf/alpine:latestCOPY --from=build-env /go/src/github.com/rh01/mini-deploy-app/gcd-server /usr/local/bin/gcdEXPOSE 3000CMD [ "gcd" ]</code></pre><pre><code class="dockerfile"># Dockerfile.apiFROM golang AS build-envWORKDIR /go/src/github.com/rh01/mini-deploy-app/apiCOPY api .COPY pb ../pbCOPY vendor ../vendorENV http_proxy http://192.168.1.9:12333ENV https_proxy http://192.168.1.9:12333RUN go get -u -v github.com/kardianos/govendorRUN govendor syncRUN GOOS=linux GOARCH=arm GOARM=7 go build -v -o /go/src/github.com/rh01/mini-deploy-app/api-serverFROM armhf/alpine:latestCOPY --from=build-env /go/src/github.com/rh01/mini-deploy-app/api-server /usr/local/bin/apiEXPOSE 3000CMD [ "api" ]</code></pre><p>然后构建</p><pre><code class="bash">$ docker build -t rh02/apiserver:v1.0.0 -f Dockerfile.api . $ docker build -t rh02/gcdserver:v1.0.0 -f Dockerfile.gcd .</code></pre><h2 id="部署到kubernetes"><a href="#部署到kubernetes" class="headerlink" title="部署到kubernetes"></a>部署到kubernetes</h2><p>定义两个Deployment和对应的两个Service,并且将gcd服务的名字写成我们api服务调用的名字:gcd-service.</p><p>下面是gcd的deployment和service的定义: gcd.yaml</p><pre><code class="yaml">apiVersion: apps/v1beta1kind: Deploymentmetadata: name: gcd-deployment labels: app: gcdspec: selector: matchLabels: app: gcd replicas: 3 template: metadata: labels: app: gcd spec: containers: - name: gcd image: rh02/gcdserver:v1.0.0 imagePullPolicy: Always ports: - name: gcd-service containerPort: 3000---apiVersion: v1kind: Servicemetadata: name: gcd-servicespec: selector: app: gcd ports: - port: 3000 targetPort: gcd-service</code></pre><p>创建api.yaml, service类型设置为NodePort,从而可以在集群外部也可以访问,对于GCD服务,类型设置为ClusterIP即可,只需要在集群内部访问就可以.</p><pre><code class="yaml">apiVersion: apps/v1beta1kind: Deploymentmetadata: name: api-deployment labels: app: apispec: selector: matchLabels: app: api replicas: 1 template: metadata: labels: app: api spec: containers: - name: api image: rh02/apiserver:v1.0.0 imagePullPolicy: Always ports: - name: api-service containerPort: 3000---apiVersion: v1kind: Servicemetadata: name: api-servicespec: type: NodePort selector: app: api ports: - port: 3000 targetPort: api-service</code></pre><p>使用kubectl创建两个资源:</p><pre><code class="bash">$ kubectl create -f api.yaml$ kubectl create -f gcd.yaml</code></pre><p>检查所有的Pod是否正在运行, 可以指定 <code>-w</code> 标记,查看启动的过程.</p><pre><code class="bash">$ kubectl get pods -wNAME READY STATUS RESTARTS AGEapi-deployment-778049682-3vd0z 1/1 Running 0 3sgcd-deployment-544390878-0zgc8 1/1 Running 0 2sgcd-deployment-544390878-p78g0 1/1 Running 0 2sgcd-deployment-544390878-r26nx 1/1 Running 0 2s</code></pre><h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p><img src="https://www.41sh.cn/zb_users/upload/2019/03/201903051551771763579640.png" alt="截图_2019-03-05_15-42-18.png"></p><p><img src="https://www.41sh.cn/zb_users/upload/2019/03/201903051551771802295607.png" alt="截图_2019-03-05_15-43-02.png"></p>]]></content>
<summary type="html">
<p>今天带着大家如何在树莓派kubernetes集群中部署微服务,这次文章和上次文章的区别就是这个涉及两个微服务,如何使两个微服务在kubernetes中实现服务之间的调用可能是与上次的不同之处,这次也使用Google的开源框架gRPC框架来加快微服务的开发.</p>
</summary>
<category term="Golang" scheme="https://readailib.com/tags/Golang/"/>
<category term="RaspberryPi" scheme="https://readailib.com/tags/RaspberryPi/"/>
<category term="kubernetes" scheme="https://readailib.com/tags/kubernetes/"/>
<category term="Edge computing" scheme="https://readailib.com/tags/Edge-computing/"/>
<category term="gRPC" scheme="https://readailib.com/tags/gRPC/"/>
<category term="REST" scheme="https://readailib.com/tags/REST/"/>
</entry>
<entry>
<title>部署微服务到树莓派的kubernetes集群</title>
<link href="https://readailib.com/2019/03/01/kubernetes/raspberrypi/simple-microservice-on-pis/"/>
<id>https://readailib.com/2019/03/01/kubernetes/raspberrypi/simple-microservice-on-pis/</id>
<published>2019-02-28T17:02:07.000Z</published>
<updated>2019-03-07T08:18:18.768Z</updated>
<content type="html"><![CDATA[<p>最近一直在如何将微服务部署在我的边缘机器上(树莓派),通过kubernetes管理。然后就开始了今天的项目成果,为此特别对此总结。</p><p>本次的微服务我使用的golang,主要因为golang的天然适合开发云端服务的特性并且golang的轻便,包的管理方便等特点。并且golang的docker镜像稍微小,占用空间小。</p><a id="more"></a><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><p>那么下面就开始吧,本次的环境主要分为两台机器,分别我的本地计算机(这里使用的Mac),另外就是我需要部署的环境(有三个树莓派节点组成的kubernetes集群)。</p><p>这里你需要:</p><ul><li>熟悉go语言</li><li>熟悉微服务</li><li>熟悉Docker</li><li>熟悉kubernetes的部署与基本管理任务</li><li>熟悉Linux</li></ul><p>本章主要参考了下面的文章:</p><ul><li><a href="https://41sh.cn/?id=16" target="_blank" rel="noopener">边缘智能-在树莓派上部署kubernetes集群</a></li><li><a href="https://github.com/rh01/deploy-golang-applicaton-on-kubernetes" target="_blank" rel="noopener">https://github.com/rh01/deploy-golang-applicaton-on-kubernetes</a></li><li><a href="https://github.com/rh01/traefik-for-pi" target="_blank" rel="noopener">https://github.com/rh01/traefik-for-pi</a></li><li><a href="https://zhuanlan.zhihu.com/p/33813413" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/33813413</a></li></ul><h2 id="编写golang的应用"><a href="#编写golang的应用" class="headerlink" title="编写golang的应用"></a>编写golang的应用</h2><p><strong>本小节全部在我们本地的计算机中做的!</strong></p><p>在本地创建项目文件夹goappk8s,并且将该文件夹设置成临时的 GOPATH环境变量的Value值。</p><pre><code class="bash">$ mkdir -pv k8sapp/src$ export GOPATH=/User/rh01/k8sapp/</code></pre><p>然后在<strong>src</strong>目录下面添加 golang 代码:</p><pre><code class="bash">$ mkdir -pv github.com/rh01/goappk8s && cd github.com/rh01/goappk8s$ cat << EOF >> main.gopackage mainimport ( "github.com/gin-gonic/gin" "net/http")func main() { router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "PONG") }) router.Run(":8080")}EOF</code></pre><blockquote><p>注:上面的代码取自 <a href="https://github.com/cnych/goappk8s" target="_blank" rel="noopener">https://github.com/cnych/goappk8s</a></p><p>另外上面的代码主要是创建了一个微服务,主要实现PING的功能</p></blockquote><p>可以看到上面的代码中,我们导入了第三方包 github.com/gin-gonic/gin,这时可以手动将该依赖包下载下来放置到<strong>GOPATH</strong>下面,这里使用了 <strong>govendor</strong> 来进行管理,当然你可以使用其他的包管理工具,比如:dep、glid或者利用go mod 等等。</p><p>在 <strong>github.com/rh01/goappk8s</strong> 目录下面执行下面的操作:</p><blockquote class="colorquote info"><p><strong>如何安装 govendor:</strong></p><p>在Mac中可以使用brew工具或者使用下面的命令</p><pre><code class="bash">$ go get -u github.com/kardianos/govendor</code></pre></blockquote><p>下面我们使用govendor来将依赖包缓存到本地项目中,方便移植和项目管理。</p><pre><code class="bash"># 初始化本地项目文件夹为使用vendor管理包$ govendor init# 将依赖包缓存到本地$ govendor fetch github.com/gin-gonic/gin</code></pre><blockquote><p>上面 fetch 需要设置代理才能通过,还是老办法:</p><pre><code class="bash">$ export http_proxy="http://127.0.0.1:12333"$ export https_proxy="http://127.0.0.1:12333"</code></pre></blockquote><p>这样一个非常小的微服务应用就做完了,这时需要测试一下,看看是否正常运行。这时切换到 <strong>GOPARH</strong> 文件夹。执行下面的语句</p><pre><code class="bash">$ go install github.com/rh01/goappk8s && ./bin/goappk8s[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)[GIN-debug] GET /ping --> main.main.func1 (3 handlers)[GIN-debug] Listening and serving HTTP on :8080</code></pre><p>这时会打印出如上面所示的日志信息,这时我们可以通过 curl 来访问。</p><pre><code class="bash">$ curl localhost:8080/pingPONG</code></pre><p>这时我们的服务是已经正常可以运行的了。</p><h2 id="打包成Docker镜像(使用ARM)"><a href="#打包成Docker镜像(使用ARM)" class="headerlink" title="打包成Docker镜像(使用ARM)"></a>打包成Docker镜像(使用ARM)</h2><p>这里主要使用了 Docker 的多阶段构建来打造一个非常小的镜像,这对我们的边缘端来讲,是非常必要的,因为他们的资源是非常有限的。</p><blockquote><p>有关 Docker镜像的多阶段构建以及Docker镜像优化问题,请详见我之前的一篇文章:</p><ul><li><a href="https://www.41sh.cn/?id=25" target="_blank" rel="noopener">https://www.41sh.cn/?id=25</a></li></ul></blockquote><p>下面我们看一下Dockerfile文件吧。</p><pre><code class="dockerfile">FROM golang AS build-envADD . /go/src/appWORKDIR /go/src/appENV http_proxy http://192.168.1.9:12333ENV https_proxy http://192.168.1.9:12333RUN go get -u -v github.com/kardianos/govendorRUN govendor syncRUN GOOS=linux GOARCH=arm GOARM=7 go build -v -o /go/src/app/app-serverFROM armhf/alpine:latestRUN apk add -U tzdataRUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtimeCOPY --from=build-env /go/src/app/app-server /usr/local/bin/app-serverEXPOSE 8080CMD [ "app-server" ]</code></pre><blockquote class="colorquote info"><p>这里也参考了知乎的这片文章:<a href="https://zhuanlan.zhihu.com/p/33813413" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/33813413</a></p><p>主要修改如下:</p><p>(1). 增加了</p><pre><code class="dockerfile"><span class="keyword">ENV</span> http_proxy http://<span class="number">192.168</span>.<span class="number">1.9</span>:<span class="number">12333</span><span class="keyword">ENV</span> https_proxy http://<span class="number">192.168</span>.<span class="number">1.9</span>:<span class="number">12333</span></code></pre><p>这是因为国内的网络环境,我们需要添加代理,才能拉取相关的依赖包</p><p>(2). 修改了</p><pre><code class="dockerfile"><span class="keyword">RUN</span><span class="bash"> GOOS=linux GOARCH=arm GOARM=7 go build -v -o /go/src/app/app-server</span></code></pre><p> 这是因为我们需要在树莓派 arm架构下去编译和运行golang程序,因此需要交叉编译。</p><p>(3). 修改了</p><pre><code class="dockerfile"><span class="keyword">FROM</span> armhf/alpine:latest</code></pre><p>这个也是因为硬件架构的不同进行相应的修改</p></blockquote><p>然后构建<code>Docker</code>镜像:</p><pre><code class="bash">$ docker build -t rh02/goappk8s:v1.0.0 ........(省略了)Successfully built 00751f94d8a9Successfully tagged cnych/goappk8s:v1.0.0$ docker push rh02/goappk8s:v1.0.0</code></pre><p>上面的操作可以将我们本地的镜像<code>rh02/goappk8s:v1.0.0</code>推送到公共的<code>dockerhub</code>上面去(前提是你得先注册了dockerhub)。</p><h2 id="将服务部署在kubernetes"><a href="#将服务部署在kubernetes" class="headerlink" title="将服务部署在kubernetes"></a>将服务部署在kubernetes</h2><p>如果要将微服务部署在kubernetes上,只需要写一个yaml文件,定义好你需要的资源对象即可,下面先给出我们的部署的yaml文件内容。</p><pre><code class="yaml">---apiVersion: extensions/v1beta1kind: Deploymentmetadata: name: goapp-deploy namespace: kube-apps labels: k8s-app: goappk8sspec: replicas: 2 revisionHistoryLimit: 10 minReadySeconds: 5 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: k8s-app: goappk8s spec: containers: - image: rh02/goappk8s:v1.1.0 imagePullPolicy: Always name: goappk8s ports: - containerPort: 8080 protocol: TCP resources: limits: cpu: 100m memory: 100Mi requests: cpu: 50m memory: 50Mi livenessProbe: tcpSocket: port: 8080 initialDelaySeconds: 10 timeoutSeconds: 3 readinessProbe: httpGet: path: /ping port: 8080 initialDelaySeconds: 10 timeoutSeconds: 2---apiVersion: v1kind: Servicemetadata: name: goapp-svc namespace: kube-apps labels: k8s-app: goappk8sspec: ports: - name: api port: 8080 protocol: TCP targetPort: 8080 selector: k8s-app: goappk8s---kind: IngressapiVersion: extensions/v1beta1metadata: name: goapp-ingressspec: rules: - host: k8sapp1.41sh.cn http: paths: - path: / backend: serviceName: goapp-svc servicePort: api</code></pre><blockquote><p>这里的k8sapp1.41sh.cn 需要解析为ingress的node。详细可以参考 <a href="https://github.com/rh01/traefik-for-pi" target="_blank" rel="noopener">https://github.com/rh01/traefik-for-pi</a></p></blockquote><p>因为我们编写了一个无状态的应用,因此上面主要创建了三个资源对象,分别为deployment(部署),service(服务)和ingress(主要负责负载均衡和提供一种访问的方式)对象。</p><p>使用kubectl来创建这三个资源对象。</p><pre><code class="bash">$ kubectl apply -f deployment.yamldeployment "goapp-deploy" createdservice "goapp-svc" createdingress "goapp-ingress" created</code></pre><p>这时我们需要创建一个traefik的ingress应用,用来处理ingress的请求。</p><pre><code class="bash">$ kubectl label node edge-node2 ingress-controller=traefik $ kubectl apply -f traefik.yaml</code></pre><h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>这时,我们都已经做完了,这时我们可以打开我们的dashboard看看怎么样。并且可以通过在浏览器中访问 <a href="http://k8sapp1.41sh.cn/ping" target="_blank" rel="noopener">http://k8sapp1.41sh.cn/ping</a> 来访问我们的微服务。</p><p>下面是成果截图。</p><p><img src="https://www.41sh.cn/zb_users/upload/2019/03/201903011551423865486307.png" alt="屏幕快照 2019-03-01 下午3.03.48.png"></p><p><img src="https://www.41sh.cn/zb_users/upload/2019/03/201903011551423899753911.png" alt="屏幕快照 2019-03-01 下午3.04.39.png"></p>]]></content>
<summary type="html">
<p>最近一直在如何将微服务部署在我的边缘机器上(树莓派),通过kubernetes管理。然后就开始了今天的项目成果,为此特别对此总结。</p>
<p>本次的微服务我使用的golang,主要因为golang的天然适合开发云端服务的特性并且golang的轻便,包的管理方便等特点。并且golang的docker镜像稍微小,占用空间小。</p>
</summary>
<category term="RPI kubernetes" scheme="https://readailib.com/categories/RPI-kubernetes/"/>
<category term="Kubernetes" scheme="https://readailib.com/categories/RPI-kubernetes/Kubernetes/"/>
<category term="Microservice" scheme="https://readailib.com/categories/RPI-kubernetes/Kubernetes/Microservice/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Golang" scheme="https://readailib.com/tags/Golang/"/>
<category term="Simple Example" scheme="https://readailib.com/tags/Simple-Example/"/>
<category term="RaspberryPi" scheme="https://readailib.com/tags/RaspberryPi/"/>
<category term="Edge Computing" scheme="https://readailib.com/tags/Edge-Computing/"/>
<category term="Gin" scheme="https://readailib.com/tags/Gin/"/>
</entry>
<entry>
<title>使用minikube快速安装istio集群</title>
<link href="https://readailib.com/2019/02/22/kubernetes/istio-minikube/"/>
<id>https://readailib.com/2019/02/22/kubernetes/istio-minikube/</id>
<published>2019-02-22T05:48:07.000Z</published>
<updated>2019-03-14T01:54:56.766Z</updated>
<content type="html"><![CDATA[<p>今天带着大家如何在minikube本地kubernetes测试集群中安装和尝试部署一个服务。</p><p>本节课不教:</p><ul><li>使用minikube部署多节点的kubernetes集群,详细教程请看:<a href="https://www.41sh.cn/?id=53" target="_blank" rel="noopener">https://www.41sh.cn/?id=53</a></li></ul><p>本节课的目标是:</p><ul><li><p>使用Helm或者手动方式来构建istio集群</p></li><li><p>使用istio框架来部微服务</p></li><li><p>服务治理与金丝雀发布等等</p><p><img src="https://www.41sh.cn/zb_users/upload/2019/02/201902221550820119606413.png" alt="23534644.png"></p></li></ul><h2 id="启动minikube集群"><a href="#启动minikube集群" class="headerlink" title="启动minikube集群"></a>启动minikube集群</h2><p>使用下面的指令在本地启动两个节点的k8s集群,分别为master和node节点,并使node节点加入到集群中。</p><pre><code class="bash"># 启动master节点,名字为k8s-m1$ minikube --profile k8s-m1 start --network-plugin=cni --docker-env HTTP_PROXY=http://192.168.99.1:12333 --docker-env HTTPS_PROXY=http://192.168.99.1:12333 --docker-env NO_PROXY=127.0.0.1/24# 以同样的方式启动node节点,名字为k8s-n1$ minikube --profile k8s-n1 start --network-plugin=cni --docker-env HTTP_PROXY=http://192.168.99.1:12333 --docker-env HTTPS_PROXY=http://192.168.99.1:12333 --docker-env NO_PROXY=127.0.0.1/24</code></pre><p>确认两个节点已经启动,但是现在node节点并没有加入到集群中,你可能使用下面指令会看到NotReady的标示,也有可能列表中不会出现k8s-n1的条目。</p><pre><code class="bash"># 切换到master节点的配置中,这样才可以使用kubectl来查询资源信息$ kubectl config --use-context k8s-m1# 查看当前的节点列表$ kubectl get no</code></pre><p>这时候需要将node节点加入集群,使用下面指令获得TOKEN,并且使用kubeadm join指令,使得node节点加入集群。</p><pre><code class="bash"># 获取master节点的ip地址$ minikube --profile k8s-m1 ssh "ifconfig eth1"# 获取当前的TOKEN列表$ minikube --profile k8s-m1 ssh "sudo kubeadm token list"# 执行下面指令进入 k8s-n1$ minikube --profile k8s-n1 ssh# 下面为进入 k8s-n1 VM 內执行的指令$ sudo su -$ TOKEN=7rzqkm.1goumlnntalpxvw0$ MASTER_IP=192.168.99.100$ kubeadm join --token ${TOKEN} ${MASTER_IP}:8443 \ --discovery-token-unsafe-skip-ca-verification \ --ignore-preflight-errors=Swap \ --ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests# 看到以下结果后,即可以在 k8s-m1 context 来操作。...Run 'kubectl get nodes' on the master to see this node join the cluster.</code></pre><p>这时候我们回到我们本地的机器(非VM),通过执行下面命令确认k8s-n1已经准备完成。</p><pre><code class="bash"># 查看当前的节点列表$ kubectl get no</code></pre><h2 id="使用helm安装istio"><a href="#使用helm安装istio" class="headerlink" title="使用helm安装istio"></a>使用helm安装istio</h2><p>为了方便起见,这里我使用helm chart来安装istio,这里参考了下面的文档:</p><ul><li><a href="https://istio.io/docs/setup/kubernetes/helm-install/" target="_blank" rel="noopener">https://istio.io/docs/setup/kubernetes/helm-install/</a></li><li><a href="https://istio.io/docs/setup/kubernetes/download-release/" target="_blank" rel="noopener">https://istio.io/docs/setup/kubernetes/download-release/</a></li></ul><p><em>1). 使用下面的命令安装helm</em>(mac系统)</p><pre><code class="bash">$ brew install kubernetes-helm</code></pre><blockquote><p>其他系统的安装方式请参考:<br><a href="https://helm.sh/docs/using_helm/#installing-helm" target="_blank" rel="noopener">https://helm.sh/docs/using_helm/#installing-helm</a></p></blockquote><p>这里是自动安装的,这里你安装的只是一个helm client,你需要又一个helm后端来支持helm自动化部署的功能,你也可以通过下面的命令查看当前的helm的安装情况以及版本。</p><pre><code class="bash">$ helm version</code></pre><blockquote><p>如果出现Server端没有起来的情况,使用helm init –service-account tiller 就自动将Tiller端安装到kubernetes集群中 。</p></blockquote><p><em>2). 下载istio并准备安装</em></p><p>使用下面的指令自动下载istio的最新发行版本,并解压到当前的文件夹下,这里有一个istio的客户端二进制文件,这时你需要手动的将二进制文件的目录添加到PATH环境变量中。</p><pre><code class="bash">$ curl -L https://git.io/getLatestIstio | sh -$ export PATH="$PATH:/Users/rh01/istio-1.0.6/bin" #这是会话环境变量的设置,临时使用,如果想永久生效,请添加到 ~/.bashrc 或者 /etc/profile中</code></pre><p><em>3). 安装istioz</em></p><p>切换到istio的文件夹下,并使用helm 安装 install 文件夹下面的yaml 来创建istio。下面的命令是我在本地测试过的:</p><pre><code class="bash">$ cd istio-1.0.6$ helm template install/kubernetes/helm/istio --name istio --namespace istio-system > $HOME/istio-1.0.6/istio.yaml$ kubectl create namespace istio-system # 创建istio-system命名空间,之后管理istio的所有资源$ kubectl apply -f install/kubernetes/helm/helm-service-account.yaml # 创建helm服务账号,使得helm能够有权限对istio-system命名空间进行操作$ kubectl apply -f install/kubernetes/helm/istio/templates/crds.yaml # 创建crd$ kubectl apply -f $HOME/istio-1.0.6/istio.yaml # 创建istio所有资源对象$ kubectl get po -n istio-systemNAME READY STATUS RESTARTS AGEistio-citadel-6f444d9999-7l2hx 1/1 Running 0 1mistio-cleanup-secrets-qjk7c 0/1 Completed 0 1mistio-egressgateway-6d79447874-v6xds 1/1 Running 0 1mistio-galley-685bb48846-pxcs2 1/1 Running 0 1mistio-ingressgateway-5b64fffc9f-4jnr9 1/1 Running 0 1mistio-pilot-8645f5655b-s6nbq 0/2 Pending 0 1mistio-policy-547d64b8d7-vtxqh 2/2 Running 0 1mistio-security-post-install-r27sv 0/1 Completed 0 1mistio-sidecar-injector-5d8dd9448d-bwcvg 1/1 Running 0 1mistio-telemetry-c5488fc49-qr7sg 2/2 Running 0 1mprometheus-76b7745b64-pm68q 1/1 Running 0 1m</code></pre><blockquote class="colorquote warning"><p><strong>遇到的坑:</strong>等等我遇到了一个istio-pilot内存不足的情况,因为我是两个节点,但是因为是在本地创建的两个虚拟机,所有内存和CPU资源都比较小,因此当出现资源不足的时候,就Pending了,这时候需要手动修改一下请求的内存资源的大小就可以了。</p><p><em>使用到的命令</em>:</p><pre><code class="bash">$ kubectl edit pods istio-pilot-xxx -n istio-system<span class="comment"># 修改resource的request的memory为100Mi即可</span></code></pre></blockquote><h2 id="部署一个应用"><a href="#部署一个应用" class="headerlink" title="部署一个应用"></a>部署一个应用</h2><p>这个是官方给出的一个例子 bookinfo,这个应用由四个微服务组成,下面是应用的架构图。</p><p><img src="https://www.41sh.cn/zb_users/upload/2019/03/201903061551882998396723.png" alt="20190222153800_46088.png"></p><p>下面的所有命令以及说明部分来自下面的文档:</p><ul><li><a href="https://istio.io/docs/examples/bookinfo/" target="_blank" rel="noopener">https://istio.io/docs/examples/bookinfo/</a></li><li><a href="https://istio.io/docs/tasks/traffic-management/ingress/#determining-the-ingress-ip-and-ports" target="_blank" rel="noopener">https://istio.io/docs/tasks/traffic-management/ingress/#determining-the-ingress-ip-and-ports</a></li></ul><p>下面的指令将会在kubernetes上部署一个bookinfo应用,并由istio提供微服务的一些服务调用和路由等等功能,具体的istio特性,后期见。</p><pre><code class="bash">$ kubectl label namespace default istio-injection=enablednamespace "default" labeled$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yamlservice "details" createddeployment.extensions "details-v1" createdservice "ratings" createddeployment.extensions "ratings-v1" createdservice "reviews" createddeployment.extensions "reviews-v1" createddeployment.extensions "reviews-v2" createddeployment.extensions "reviews-v3" createdservice "productpage" createddeployment.extensions "productpage-v1" created$ kubectl get servicesNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEdetails ClusterIP 10.97.137.200 <none> 9080/TCP 7skubernetes ClusterIP 10.96.0.1 <none> 443/TCP 56mproductpage ClusterIP 10.106.43.117 <none> 9080/TCP 6sratings ClusterIP 10.108.175.114 <none> 9080/TCP 7sreviews ClusterIP 10.96.73.150 <none> 9080/TCP 6s$ kubectl get pods$ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yamlgateway.networking.istio.io "bookinfo-gateway" createdvirtualservice.networking.istio.io "bookinfo" created$ kubectl get gatewayNAME AGEbookinfo-gateway 9s$ kubectl get svc istio-ingressgateway -n istio-systemNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEistio-ingressgateway LoadBalancer 10.101.64.224 <pending> 80:31380/TCP,443:31390/TCP,31400:31400/TCP,15011:31643/TCP,8060:30348/TCP,853:30934/TCP,15030:32334/TCP,15031:32681/TCP 17m</code></pre><p>这时候大家会看到istio-ingressgateway服务的EXTERNAL_IP是pending状态,这是因为我们没有指定外置的负载均衡器的ip地址,这里有三种处理方式:</p><ul><li>如果处于云服务厂商的环境,并且有负载均衡器,这时候就填写负载均衡器的ip地址</li><li>如果没有,可以采取将服务的类型改为NodePort类型,或者直接使用提供的NodePort,这样就可以使用http://<Ingress所在Node的IP>:NodePort/productpage的方式访问</li><li>另外你也可以将ingress对象处于的Node的ip作为ExternalIP,也是可以访问的。这样就可以直接使用<a href="http://ExternalIP:80/productpage进行访问。" target="_blank" rel="noopener">http://ExternalIP:80/productpage进行访问。</a></li></ul><blockquote><p><em>具体的如何获取ingress和配置ingress,详见下面的文档</em>:<br><a href="https://istio.io/docs/tasks/traffic-management/ingress/#determining-the-ingress-ip-and-ports" target="_blank" rel="noopener">https://istio.io/docs/tasks/traffic-management/ingress/#determining-the-ingress-ip-and-ports</a></p></blockquote><h2 id="大功告成"><a href="#大功告成" class="headerlink" title="大功告成"></a>大功告成</h2><p><img src="https://www.41sh.cn/zb_users/upload/2019/02/201902221550820787188445.png" alt="屏幕快照 2019-02-22 下午3.15.31.png"></p><p>如果继续刷新,会发现有三个版本的应用出现,就是红色的星星,黑色的星星,没有星星这三个版本,会随机出现,我们可以使用istio来管理这些不同版本的应用,通过金丝雀发布,灰度发布等一些高级特性实现切流量的功能,后面的文章我会详细介绍有关istio的高级特性。</p><p><img src="https://www.41sh.cn/zb_users/upload/2019/02/201902221550821049797553.png" alt="屏幕快照 2019-02-22 下午3.37.12.png"></p><p><img src="https://www.41sh.cn/zb_users/upload/2019/02/201902221550821077460003.png" alt="屏幕快照 2019-02-22 下午3.37.39.png"></p><h2 id="Bug"><a href="#Bug" class="headerlink" title="Bug"></a>Bug</h2><blockquote class="colorquote warning"><pre><code class="bash">devmapper: Thin Pool has 82984 free data blocks <span class="built_in">which</span> is less than minimum required 163840 free data blocks. Create more free space <span class="keyword">in</span> thin pool or use dm.min_free_space option to change behavior</code></pre><p>解决办法:</p><ol><li>Cleanup exited processes:</li></ol><pre><code class="bash">docker rm $(docker ps -q -f status=exited)</code></pre><ol start="2"><li>Cleanup dangling volumes:</li></ol><pre><code class="bash">docker volume rm $(docker volume ls -qf dangling=<span class="literal">true</span>)</code></pre><ol start="3"><li>Cleanup dangling images:</li></ol><pre><code class="bash">docker rmi $(docker images --filter <span class="string">"dangling=true"</span> -q --no-trunc)</code></pre></blockquote>]]></content>
<summary type="html">
<p>今天带着大家如何在minikube本地kubernetes测试集群中安装和尝试部署一个服务。</p>
<p>本节课不教:</p>
<ul>
<li>使用minikube部署多节点的kubernetes集群,详细教程请看:<a href="https://www.41sh.c
</summary>
<category term="istio" scheme="https://readailib.com/categories/istio/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Simple Example" scheme="https://readailib.com/tags/Simple-Example/"/>
<category term="istio" scheme="https://readailib.com/tags/istio/"/>
</entry>
<entry>
<title>使用 Minikube 来部署本地 kubernetes 多节点集群</title>
<link href="https://readailib.com/2019/02/18/kubernetes/multi-nodes-kubernetes-using-minikube/"/>
<id>https://readailib.com/2019/02/18/kubernetes/multi-nodes-kubernetes-using-minikube/</id>
<published>2019-02-18T07:57:21.000Z</published>
<updated>2019-03-14T07:53:49.428Z</updated>
<content type="html"><![CDATA[<p>一般来讲,使用minikube的目的主要用于作为本地单机测试集群,也只能构建单节点的kubernetes集群,本文章参看<a href="https://k2r2bai.com/" target="_blank" rel="noopener">凯仁兄</a>的方法使得minikube能够借助vitual box软件来实现多节点的部署,其中包括Master/Worker节点的部署与安装,下面主要针对kubernetes的最新版本kubernetes 1.13.2, 网络插件为Calico,主要为了测试Network Policy的功能。</p><a id="more"></a><p><img src="https://www.41sh.cn/zb_users/upload/2019/02/20190218164923_24529.jpg" alt="img"></p><h2 id="系统准备"><a href="#系统准备" class="headerlink" title="系统准备"></a>系统准备</h2><p>下面一定要确保以下的步骤都已经执行,所有要安装的软件包已经安转。</p><p>第一步肯定是准备minikube的执行文件,下面我列出了不同平台的二进制文件的下载地址:</p><ul><li><a href="https://github.com/kairen/minikube/releases/download/v0.33.1-multi-node/minikube-linux-amd64" target="_blank" rel="noopener">Linux</a></li><li><a href="https://github.com/kairen/minikube/releases/download/v0.33.1-multi-node/minikube-darwin-amd64" target="_blank" rel="noopener">Mac OS X</a></li><li><a href="https://github.com/kairen/minikube/releases/download/v0.33.1-multi-node/minikube-windows-amd64.exe" target="_blank" rel="noopener">Windows</a></li></ul><blockquote class="colorquote danger"><p><strong>误操作:</strong> 为了能够使 minikube 能够启动多节点的集群,一定使用上面给出的二进制执行文件链接。</p><p><em>提示:</em></p><p>如果你要使用 官方给的 minikube 的二进制文件来启动集群,此时如果你要使用网络插件Calico,请手动输入下面的命令即可生效。</p><pre><code class="bash">$ kubectl apply -f https://docs.projectcalico.org/v2.4/getting-started/kubernetes/installation/hosted/kubeadm/1.6/calico.yaml</code></pre></blockquote><p>第二步,需要将<a href="https://www.virtualbox.org/wiki/Downloads" target="_blank" rel="noopener">Virtual Box</a>下载下来,然后提供给minikube来创建虚拟机。</p><blockquote><p>IMPORTANT: 测试机器一定要开启 VT-x or AMD-v virtualization.</p><p>虽然建议使用 VBox,但是也可以其他的虚拟化解决方案,比如使用 KVM, xhyve等虚拟机管理软件。</p></blockquote><p>第三步,下载测试机器操作系统适配的 <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/" target="_blank" rel="noopener">kubeclt</a>。</p><h2 id="启动集群"><a href="#启动集群" class="headerlink" title="启动集群"></a>启动集群</h2><p>本节主要介绍如何使用minikube来建立集群,并且相应的创建master节点和worker节点。</p><p>开始之前,确认本测试机器是否已经安装过Minikube,如果有的话,就把上面下载二进制文件放置在任意方便的位置,或者直接替代之前的然后在启动集群之前,删除Home文件夹下的.minikube文件夹。</p><pre><code class="bash">$ rm -rf $HOME/.minikube</code></pre><h3 id="Master-节点"><a href="#Master-节点" class="headerlink" title="Master 节点"></a>Master 节点</h3><p>首先通过 Minikube 执行以下指令来启动 Master节点,并通过 kubectl 检查:</p><pre><code class="bash">$ minikube --profile k8s-m1 start --network-plugin=cni --docker-env HTTP_PROXY=http://192.168.99.1:12333 --docker-env HTTPS_PROXY=http://192.168.99.1:12333 --docker-env NO_PROXY=127.0.0.1/24? minikube v0.34.1 on darwin (amd64)? Creating virtualbox VM (CPUs=2, Memory=2048MB, Disk=20000MB) ...? "k8s-m1" IP address is 192.168.99.102? Configuring Docker as the container runtime ... ▪ env HTTP_PROXY=http://192.168.99.1:12333 ▪ env HTTPS_PROXY=http://192.168.99.1:12333 ▪ env NO_PROXY=127.0.0.1/24✨ Preparing Kubernetes environment ...? Pulling images required by Kubernetes v1.13.3 ...? Launching Kubernetes v1.13.3 using kubeadm ... ? Configuring cluster permissions ...? Verifying component health .....? kubectl is now configured to use "k8s-m1"? Done! Thank you for using minikube!</code></pre><blockquote><p><code>--vm-driver</code> 可以选择使用其他 VM driver 启动虚拟机,如 xhyve、hyperv、hyperkit 与 kvm2 等等。</p></blockquote><p>完成后,确认 k8s-m1 节点處处于Ready 状态:</p><pre><code class="bash">$ kubectl get noNAME STATUS ROLES AGE VERSIONk8s-m1 Ready master 2m8s v1.13.2</code></pre><p>下面来部署Node节点,通过minikube执行下面指令来启动Node节点。</p><pre><code>$ minikube --profile k8s-n1 start --network-plugin=cni --node...Stopping extra container runtimes...# 接着取得 Master IP 与 Token$ minikube --profile k8s-m1 ssh "ifconfig eth1"$ minikube --profile k8s-m1 ssh "sudo kubeadm token list"# 執行以下指令進入 k8s-n1$ minikube --profile k8s-n1 ssh# 下面是 在k8s-n1 VM 里执行的指令$ sudo su -$ TOKEN=7rzqkm.1goumlnntalpxvw0$ MASTER_IP=192.168.99.100$ kubeadm join --token ${TOKEN} ${MASTER_IP}:8443 \ --discovery-token-unsafe-skip-ca-verification \ --ignore-preflight-errors=Swap \ --ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests# 看到以下结果后,即可以在 k8s-m1 context 操作。...Run 'kubectl get nodes' on the master to see this node join the cluster.</code></pre><blockquote class="colorquote warning"><p>1). 上面的 IP 有可能不同,请手动确认 Master 节点 IP。</p><p>2). 如果上面的TOKEN失效,请使用下面的指令生成一个,或者生成TOKEN时指定tt值为-1</p><pre><code class="bash">$ minikube --profile k8s-m1 ssh <span class="string">"sudo kubeadm token create"</span></code></pre></blockquote><p>接下来,我们可以通过 kuubectl 客户端检查Node是否加入到集群:</p><pre><code>$ kubectl config use-context k8s-m1Switched to context "k8s-m1".$ kubectl get noNAME STATUS ROLES AGE VERSIONk8s-m1 Ready master 3m44s v1.13.2k8s-n1 Ready <none> 80s v1.13.2$ kubectl get csrNAME AGE REQUESTOR CONDITIONnode-csr-Ut1k5mLXpXVsyZwjn2z2-fpie9HHyTkMU7wnrjDnD3E 118s system:bootstrap:3qeeeu Approved,Issued$ kubectl -n kube-system get po -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATEScalico-node-qxkw5 2/2 Running 0 86s 192.168.99.101 k8s-n1 <none> <none>calico-node-srhlk 2/2 Running 0 3m24s 192.168.99.100 k8s-m1 <none> <none>coredns-86c58d9df4-826nz 1/1 Running 0 3m27s 10.244.0.3 k8s-m1 <none> <none>coredns-86c58d9df4-9z7mr 1/1 Running 0 3m27s 10.244.0.2 k8s-m1 <none> <none>etcd-k8s-m1 1/1 Running 0 2m40s 192.168.99.100 k8s-m1 <none> <none>kube-addon-manager-k8s-m1 1/1 Running 0 3m48s 192.168.99.100 k8s-m1 <none> <none>kube-addon-manager-k8s-n1 1/1 Running 0 86s 192.168.99.101 k8s-n1 <none> <none>kube-apiserver-k8s-m1 1/1 Running 0 2m36s 192.168.99.100 k8s-m1 <none> <none>kube-controller-manager-k8s-m1 1/1 Running 0 2m50s 192.168.99.100 k8s-m1 <none> <none>kube-proxy-768w8 1/1 Running 0 86s 192.168.99.101 k8s-n1 <none> <none>kube-proxy-b7ndj 1/1 Running 0 3m27s 192.168.99.100 k8s-m1 <none> <none>kube-scheduler-k8s-m1 1/1 Running 0 2m46s 192.168.99.100 k8s-m1 <none> <none>storage-provisioner 1/1 Running 0 2m46s 192.168.99.100 k8s-m1 <none></code></pre><p>这样一个 Kubernetes 集群就完成了,速度快一点不到 10 分钟就可以建立好了。</p><h2 id="删除虚拟机"><a href="#删除虚拟机" class="headerlink" title="删除虚拟机"></a>删除虚拟机</h2><p><em>清除环境一条指令即可:</em></p><pre><code>$ minikube --profile <node_name> delete</code></pre>]]></content>
<summary type="html">
<p>一般来讲,使用minikube的目的主要用于作为本地单机测试集群,也只能构建单节点的kubernetes集群,本文章参看<a href="https://k2r2bai.com/" target="_blank" rel="noopener">凯仁兄</a>的方法使得minikube能够借助vitual box软件来实现多节点的部署,其中包括Master/Worker节点的部署与安装,下面主要针对kubernetes的最新版本kubernetes 1.13.2, 网络插件为Calico,主要为了测试Network Policy的功能。</p>
</summary>
<category term="Minikube Multi-Node Cluster" scheme="https://readailib.com/categories/Minikube-Multi-Node-Cluster/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Dooker" scheme="https://readailib.com/tags/Dooker/"/>
<category term="Calico" scheme="https://readailib.com/tags/Calico/"/>
</entry>
<entry>
<title>在kubernetes API和client-go中使用 Go modules</title>
<link href="https://readailib.com/2019/02/15/kubernetes/kubernetes-api-gomod/"/>
<id>https://readailib.com/2019/02/15/kubernetes/kubernetes-api-gomod/</id>
<published>2019-02-15T08:41:31.000Z</published>
<updated>2019-03-13T07:19:30.776Z</updated>
<content type="html"><![CDATA[<p>现如今kubernetes和golang的发展非常之快,Golang的依赖管理也不断的更新换代,从最初的<code>Go dep</code> 到现在<code>go mod</code>。本篇文章主要介绍如何使用<code>go mod</code>来管理项目依赖。</p><blockquote class="colorquote warning"><p>这篇文章不是 <strong><em>Go module</em></strong> 教程,网上有很多关于这个主题的资料。大家可以参考下面的:</p><ul><li><a href="https://github.com/golang/go/wiki/Modules" target="_blank" rel="noopener">https://github.com/golang/go/wiki/Modules</a></li><li><a href="https://www.youtube.com/watch?v=H_4eRD8aegk" target="_blank" rel="noopener">justforfunc #43: Migrating Go Modules to v2+</a></li></ul></blockquote><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><p>在开始之前,需要确认以下:</p><ul><li>Go 的版本要在1.11以上</li><li>在GOPATH之外,即 <code>$GOPATH/src</code> 之外新建了一个目录作为本地的项目文件夹</li><li>确认 <code>GO111MODULE=on</code> 环境变量是否正确</li></ul><p>为此,我在GOPATH之外创建了一个目录。我将创建一个简单的PVC控制器,它可以监控所需的PVC数量。</p><h2 id="从-Dep-工具开始-或其他工具)"><a href="#从-Dep-工具开始-或其他工具)" class="headerlink" title="从 Dep 工具开始(或其他工具)"></a>从 Dep 工具开始(或其他工具)</h2><p>对于使用<a href="https://github.com/kubernetes/client-go" target="_blank" rel="noopener">client-go</a>编写Kubernetes API工具的大多数人来说,都可能正在使用<code>Dep</code>来管理依赖。幸运的是,<code>go mod</code>工具可以从<code>Dep</code>(以及其他工具,如Godep,Govendor和Glide)导入依赖项配置。我的示例项目使用<code>dep</code>与以下<code>Gopkg.toml</code>:</p><pre><code class="toml">[[constraint]] name = "k8s.io/api" version = "kubernetes-1.9.0"[[constraint]] name = "k8s.io/apimachinery" version = "kubernetes-1.9.0"[[constraint]] name = "k8s.io/client-go" version = "6.0.0"</code></pre><p>可以看出,代码使用旧版本的client-go版本6.0.0和Kubernetes API版本1.9。</p><p>首先要做的是将项目初始化为模块。从代码的根目录,使用以下命令:</p><pre><code class="bash">$ cd ./pvcwatch$ go mod init github.com/vladimirvivien/pvcwatch</code></pre><p> <code>mod</code> 命令将会创建一个新的文件 <code>go.mod</code> 并复制Dep的依赖信息。</p><pre><code class="bash">go: creating new go.mod: module github.com/vladimirvivien/pvcwatchgo: copying requirements from Gopkg.lock</code></pre><p>这时,可以看一下从<code>Gopkg.toml</code>生成的<code>go.mod</code>文件的内容。</p><pre><code class="bash">module github.com/vladimirvivien/pvcwatchrequire ( github.com/PuerkitoBio/purell v1.1.0... k8s.io/api v0.0.0-20171214033149-af4bc157c3a2 k8s.io/apimachinery v0.0.0-20171207040834-180eddb345a5 k8s.io/client-go v6.0.0+incompatible k8s.io/kube-openapi v0.0.0-20180216212618-50ae88d24ede)</code></pre><p>生成的<code>go.mod</code>文件依然遵循<code>Gopkg.toml</code>版本限制.</p><blockquote><p><code>go mod</code> <em>will resort to the latest version of discovered packages that do not have any version information (from Dep or otherwise). If you don’t want that, you can update or downgrade to your preferred version (discussed later).</em></p></blockquote><p>此时,您可以像平常一样构建代码。除此之外,构建工具将显示包解析的进度:</p><pre><code class="bash">$ go build .go: finding github.com/golang/protobuf v1.0.0...go: downloading github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81go: downloading github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1ddgo: downloading golang.org/x/sys v0.0.0-20180322165403-91ee8cde4354</code></pre><p>如果解析了所有包,将构建成功,此时拥有了可以运行的二进制文件。</p><h2 id="从无到有"><a href="#从无到有" class="headerlink" title="从无到有"></a>从无到有</h2><p>假若我们此时的项目并没有依赖管理工具,这时怎么使用<code>go mod</code>来管理我们的依赖呢?</p><p>首先第一步,将我们的项目初始化为一个Go module:</p><pre><code class="bash">$ go mod init github.com/vladimirvivien/pvcwatchgo: creating new go.mod: module github.com/vladimirvivien/pvcwatch</code></pre><p>这时,生成的 <code>go.mod</code> 文件将会是空的,只有我们执行了Go的一些命令(比如<code>build</code>, <code>get</code>,<code>test</code>, 等等)之后,<code>go.mod</code>才会出现依赖项. 接下来, 在我们的项目文件夹下执行 <code>build</code> 命令。</p><pre><code class="bash">$ cd ./pvcwatch$ go build .go: finding k8s.io/apimachinery/pkg/util/runtime latest...go: downloading golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51go: finding golang.org/x/text/unicode latestgo: finding golang.org/x/text/secure latest</code></pre><p>在构建过程结束时,如果没有依赖性问题,我们应该获得构建的二进制文件。在这种情况下,<code>go mod</code>将自动提取所有已解析包的最新版本,如更新的go.mod文件中所示:</p><pre><code class="bash">$> cat go.modmodule github.com/vladimirvivien/pvcwatchrequire (... k8s.io/api v0.0.0-20181018013834-843ad2d9b9ae k8s.io/apimachinery v0.0.0-20181015213631-60666be32c5d k8s.io/client-go v9.0.0+incompatible)</code></pre><p><code>go.mod</code>显示正在使用client-go v9.0.0(最新的版本)(k8s.io/api和k8s.io/apimachinery尚未<em>SemVer’d</em>,因此使用了最新的HEAD版本)。</p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>Trouble will come when you want to adjust (upgrade/downgrade) client-go to a specific version. For instance, in the previous section, <code>go mod</code> selected v9.0.0 of client-go. But, let us say we want do downgrade to v7.0.0 because:</p><pre><code class="bash">$> cd ./pvcwatch$> go get k8s.io/client-go@v7.0.0</code></pre><p>This, unfortunately, will create a version mismatch between client-go and its dependent packages <code>k8s.io/api</code> and <code>k8s.io/apimachinery</code>:</p><pre><code class="bash">$> go build .go: finding github.com/howeyc/gopass latestgo: finding k8s.io/client-go v7.0.0+incompatible#../../pkg/apis/clientauthentication/v1alpha1/zz_generated.conversion.go:39:15: scheme.AddGeneratedConversionFuncs undefined (type *runtime.Scheme has no field or method AddGeneratedConversionFuncs)</code></pre><p>现在,您必须手动确定您的依赖关系图。幸运的是,client-go附带了一个方便的兼容性矩阵,可以准确地告诉我们需要哪些版本(参见下图)。</p><p><img src="./compatible-matrix.png" alt="client-go兼容性矩阵"></p><p>根据矩阵可以看出,client-go v7.0.0与Kubernetes 1.10兼容。因此,让我们使用go get(其支持分支名称或非semver标记名称)将其他依赖组件降级到匹配版本。</p><p>降级 k8s.io/api to Kubernets-1.10:</p><pre><code class="bash"># Downgrade with a tag name$> go get k8s.io/api@kubernetes-1.10.9go: finding k8s.io/api kubernetes-1.10.9go: downloading k8s.io/api v0.0.0-20180828232432-12444147eb11...# equivalent to using matching branch name$> go get k8s.io/api@release-1.10go: finding k8s.io/api release-1.10</code></pre><p>降级 k8s.io/apimachinery to Kubernetes-1.10</p><pre><code>> go get k8s.io/apimachinery@release-1.10go: finding k8s.io/apimachinery release-1.10go: downloading k8s.io/apimachinery v0.0.0-20180619225948-e386b2658ed2</code></pre><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p><code>Dep</code>将`Go包管理推向了极致的高度。现在,Go模块及其与Go命令行工具的深度集成,已经将Go依赖管理做的更上一层楼了。</p>]]></content>
<summary type="html">
<p>现如今kubernetes和golang的发展非常之快,Golang的依赖管理也不断的更新换代,从最初的<code>Go dep</code> 到现在<code>go mod</code>。本篇文章主要介绍如何使用<code>go mod</code>来管理项目依赖。</p
</summary>
<category term="Go modules" scheme="https://readailib.com/categories/Go-modules/"/>
<category term="Golang" scheme="https://readailib.com/tags/Golang/"/>
<category term="Go modules" scheme="https://readailib.com/tags/Go-modules/"/>
<category term="client-go" scheme="https://readailib.com/tags/client-go/"/>
</entry>
<entry>
<title>在仪表盘上增加heapster指标</title>
<link href="https://readailib.com/2019/01/15/kubernetes/add-heapster-to-dashboard/"/>
<id>https://readailib.com/2019/01/15/kubernetes/add-heapster-to-dashboard/</id>
<published>2019-01-15T08:41:31.000Z</published>
<updated>2019-03-12T07:48:04.942Z</updated>
<content type="html"><![CDATA[<p><img src="https://www.41sh.cn/zb_users/upload/2019/01/201901151547542105188485.png" alt="2019-01-15 16-47-57屏幕截图.png"></p><p>Prerequisites: You have a kubernetes cluster with the dashboard plugin installed, see the article from <a href="https://medium.com/@mrjensens" target="_blank" rel="noopener">Martin Jensen</a> entitled Kubernetes dashboard on ARM with RBAC for the instructions on how to do that.</p><a id="more"></a><pre><code class="bash">$ git clone $ cd heapster/</code></pre><p>and edit the heapster.yaml and influxdb.yaml files to change the image architecture from -amd64 to -arm. For example image: k8s.gcr.io/heapster-amd64:v1.4.2 should be changed to image: k8s.gcr.io/heapster-arm:v1.4.2</p><pre><code class="bash">$ kubectl create -f influxdb.yaml$ kubectl create -f heapster.yaml</code></pre><p>to deploy the heapster and influxeb deployment, service and serviceaccounts.</p><p>Now we need to add the roles from <a href="https://github.com/kubernetes/heapster" target="_blank" rel="noopener">heapster</a>/<a href="https://github.com/kubernetes/heapster/tree/master/deploy" target="_blank" rel="noopener">deploy</a>/<a href="https://github.com/kubernetes/heapster/tree/master/deploy/kube-config" target="_blank" rel="noopener">kube-config</a>/rbac/.</p><pre><code class="bash">$ cd rbac/$ kubectl create -f heapster-rbac.yaml</code></pre><p>If you go back to the kubernetes dashboard, you will not see any metrics. They are being collected but will not appear until we restart the dashboard.</p><pre><code class="bash">$ kubectl delete -n kube-system kubernetes-dashboard-7fcc5cb979–85vt5</code></pre><p>should take care of that.</p><h2 id="Some-wrong"><a href="#Some-wrong" class="headerlink" title="Some wrong!!!"></a>Some wrong!!!</h2><hr><p>Bug solution: <a href="https://brookbach.com/2018/10/29/Heapster-on-Kubernetes-1.11.3.html" target="_blank" rel="noopener">https://brookbach.com/2018/10/29/Heapster-on-Kubernetes-1.11.3.html</a></p><blockquote class="colorquote info"><h1 id="Heapster"><a href="#Heapster" class="headerlink" title="Heapster"></a>Heapster</h1><h2 id="Clone"><a href="#Clone" class="headerlink" title="Clone"></a>Clone</h2><p>First, clone the Heapster repository.</p><pre><code class="bash">$ git <span class="built_in">clone</span> https://github.com/kubernetes/heapster/$ <span class="built_in">cd</span> heapster</code></pre><p>Then, set Grafana Service Type to NodePort and downgrade container version from 5.0.4 to 4.4.3 as follows. The reason why I downgrade the version is the dashboard on Grafana is not shown in 5.0.4.</p><pre><code class="bash">$ diff --git a/deploy/kube-config/influxdb/grafana.yaml b/deploy/kube-config/influxdb/grafana.yamlindex 216bd9a..266f47a 100644--- a/deploy/kube-config/influxdb/grafana.yaml+++ b/deploy/kube-config/influxdb/grafana.yaml@@ -13,7 +13,7 @@ spec: spec: containers: - name: grafana- image: k8s.gcr.io/heapster-grafana-amd64:v5.0.4+ image: k8s.gcr.io/heapster-grafana-amd64:v4.4.3 ports: - containerPort: 3000 protocol: TCP@@ -64,7 +64,7 @@ spec: <span class="comment"># or through a public IP.</span> <span class="comment"># type: LoadBalancer</span> <span class="comment"># You could also use NodePort to expose the service at a randomly-generated port</span>- <span class="comment"># type: NodePort</span>+ <span class="built_in">type</span>: NodePort ports: - port: 80 targetPort: 3000</code></pre><p>In this point, The pods are launched if I <code>kubectl apply</code> under <code>deploy/kube-config/rbac</code>and <code>deploy/kube-config/influxdb</code> , but the following error logs are generated and not worked correctly.</p><pre><code class="bash">E1028 07:39:05.011439 1 manager.go:101] Error <span class="keyword">in</span> scraping containers from Kubelet:XX.XX.XX.XX:10255: failed to get all container stats from Kubelet URL <span class="string">"http://XX.XX.XX.XX:10255/stats/container/"</span>: Post http://XX.XX.XX.XX:10255/stats/container/: dial tcp XX.XX.XX.XX:10255: getsockopt: connection refused</code></pre><h2 id="Apply-patch"><a href="#Apply-patch" class="headerlink" title="Apply patch"></a>Apply patch</h2><p>After googling the error message, I found <a href="https://github.com/kubernetes/heapster/issues/1936" target="_blank" rel="noopener">this issue</a>.</p><p>It looks the port to access are changed and I need to deal with HTTPS.</p><p>So I edit <code>deploy/kube-config/influxdb/heapster.yaml</code> and <code>deploy/kube-config/rbac/heapster-rbac.yaml</code> as below.</p><pre><code class="bash">diff --git a/deploy/kube-config/influxdb/heapster.yaml b/deploy/kube-config/influxdb/heapster.yamlindex e820ca5..195061a 100644--- a/deploy/kube-config/influxdb/heapster.yaml+++ b/deploy/kube-config/influxdb/heapster.yaml@@ -24,7 +24,7 @@ spec: imagePullPolicy: IfNotPresent <span class="built_in">command</span>: - /heapster- - --<span class="built_in">source</span>=kubernetes:https://kubernetes.default+ - --<span class="built_in">source</span>=kubernetes.summary_api:<span class="string">''</span>?useServiceAccount=<span class="literal">true</span>&kubeletHttps=<span class="literal">true</span>&kubeletPort=10250&insecure=<span class="literal">true</span> - --sink=influxdb:http://monitoring-influxdb.kube-system.svc:8086 --- apiVersion: v1diff --git a/deploy/kube-config/rbac/heapster-rbac.yaml b/deploy/kube-config/rbac/heapster-rbac.yamlindex 6e63803..1f982fb 100644--- a/deploy/kube-config/rbac/heapster-rbac.yaml+++ b/deploy/kube-config/rbac/heapster-rbac.yaml@@ -5,7 +5,7 @@ metadata: roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole- name: system:heapster+ name: heapster subjects: - kind: ServiceAccount name: heapster</code></pre><p>Moreover, I create <code>deploy/kube-config/rbac/heapster-role.yaml</code>.</p><pre><code class="yaml"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span><span class="attr">kind:</span> <span class="string">ClusterRole</span><span class="attr">metadata:</span><span class="attr"> name:</span> <span class="string">heapster</span><span class="attr">rules:</span><span class="attr">- apiGroups:</span><span class="bullet"> -</span> <span class="string">""</span><span class="attr"> resources:</span><span class="bullet"> -</span> <span class="string">pods</span><span class="bullet"> -</span> <span class="string">nodes</span><span class="bullet"> -</span> <span class="string">namespaces</span><span class="attr"> verbs:</span><span class="bullet"> -</span> <span class="string">get</span><span class="bullet"> -</span> <span class="string">list</span><span class="bullet"> -</span> <span class="string">watch</span><span class="attr">- apiGroups:</span><span class="bullet"> -</span> <span class="string">extensions</span><span class="attr"> resources:</span><span class="bullet"> -</span> <span class="string">deployments</span><span class="attr"> verbs:</span><span class="bullet"> -</span> <span class="string">get</span><span class="bullet"> -</span> <span class="string">list</span><span class="bullet"> -</span> <span class="string">update</span><span class="bullet"> -</span> <span class="string">watch</span><span class="attr">- apiGroups:</span><span class="bullet"> -</span> <span class="string">""</span><span class="attr"> resources:</span><span class="bullet"> -</span> <span class="string">nodes/stats</span><span class="attr"> verbs:</span><span class="bullet"> -</span> <span class="string">get</span></code></pre><h2 id="Create-pods"><a href="#Create-pods" class="headerlink" title="Create pods"></a>Create pods</h2><p>Finally, I apply these configurations.</p><pre><code class="bash">$ kubectl create -f ./deploy/kube-config/rbac/clusterrolebinding.rbac.authorization.k8s.io/heapster createdclusterrole.rbac.authorization.k8s.io/heapster created$ kubectl create -f ./deploy/kube-config/influxdb/deployment.extensions/monitoring-grafana createdservice/monitoring-grafana createdserviceaccount/heapster createddeployment.extensions/heapster createdservice/heapster createddeployment.extensions/monitoring-influxdb createdservice/monitoring-influxdb created</code></pre><p>After updating, I can configure launching Heapster, Grafana, and Influx DB by <code>kubectl get all -n kube-system</code>. And after waiting for minutes, I can see the metrics by <code>kubectl top node</code>.</p><h1 id="Thought"><a href="#Thought" class="headerlink" title="Thought"></a>Thought</h1><p>When I faced to some miss behavior on Kubernetes, I often find a clue of solution by seeing the log by <code>kubectl logs</code>.</p></blockquote>]]></content>
<summary type="html">
<p><img src="https://www.41sh.cn/zb_users/upload/2019/01/201901151547542105188485.png" alt="2019-01-15 16-47-57屏幕截图.png"></p>
<p>Prerequisites: You have a kubernetes cluster with the dashboard plugin installed, see the article from <a href="https://medium.com/@mrjensens" target="_blank" rel="noopener">Martin Jensen</a> entitled Kubernetes dashboard on ARM with RBAC for the instructions on how to do that.</p>
</summary>
<category term="Kubernetes DashBoard" scheme="https://readailib.com/categories/Kubernetes-DashBoard/"/>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Dashboard" scheme="https://readailib.com/tags/Dashboard/"/>
<category term="Heapster" scheme="https://readailib.com/tags/Heapster/"/>
</entry>
<entry>
<title>树莓派kuernetes集群中部署dashboard</title>
<link href="https://readailib.com/2019/01/15/kubernetes/kubernetes-dashboard/"/>
<id>https://readailib.com/2019/01/15/kubernetes/kubernetes-dashboard/</id>
<published>2019-01-15T08:32:03.000Z</published>
<updated>2019-03-07T08:42:28.349Z</updated>
<content type="html"><![CDATA[<p>在最近关于在树莓派集群上使用kubeadm配置安装Kubernetes 1.13.0的教程中,默认情况下启用了RBAC,本文章将介绍如何在启用RBAC的情况下运行Kubernetes仪表板。</p><a id="more"></a><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><ul><li>部署和启动Kubernetes集群 (see <a href="https://www.41sh.cn/?id=16" target="_blank" rel="noopener">这篇文章</a>)</li><li><a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/" target="_blank" rel="noopener">kubectl</a> v. 1.13.0</li></ul><h2 id="配置-kubeconfig"><a href="#配置-kubeconfig" class="headerlink" title="配置 kubeconfig"></a>配置 kubeconfig</h2><p>配置本地树莓派(并非master),以便使用kubectl与上一个教程中配置的集群进行通信。使用scp从master节点上下载配置文件</p><pre><code class="bash">$ scp pi@edge-master:/home/pi/.kube/config ./config</code></pre><p>将配置文件拷贝到本地机器上的 ~/.kube 目录下.</p><p>注意:如果你已经有一个集群的配置,它将会覆盖之前的配置,为了避免这种情况,可以在复制时添加 — kubeconfig.</p><pre><code class="b">$ cp config ~/.kube/config</code></pre><p>测试:</p><pre><code class="bash">$ kubectl get nodesNAME STATUS ROLES AGE VERSIONedge-master Ready master 7d23h v1.13.1edge-node1 Ready <none> 7d23h v1.13.1edge-node2 Ready <none> 7d23h v1.13.1edge-node3 Ready <none> 6d v1.13.1</code></pre><h2 id="创建仪表盘"><a href="#创建仪表盘" class="headerlink" title="创建仪表盘"></a>创建仪表盘</h2><p><a href="https://github.com/kubernetes/dashboard/blob/master/src/deploy/recommended/kubernetes-dashboard-arm.yaml" target="_blank" rel="noopener">Kubernetes source</a> 在 kube-system 命名空间下将会创建以下几种资源: secret, service account, role, rolebinding, deployment, and service.</p><pre><code class="bash">$ kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/72832429656c74c4c568ad5b7163fa9716c3e0ec/src/deploy/recommended/kubernetes-dashboard-arm.yamlsecret "kubernetes-dashboard-certs" createdserviceaccount "kubernetes-dashboard" createdrole "kubernetes-dashboard-minimal" createdrolebinding "kubernetes-dashboard-minimal" createddeployment "kubernetes-dashboard" createdservice "kubernetes-dashboard" created</code></pre><blockquote class="colorquote warning"><p><strong>坑</strong>:这里会出现一个问题就是Pod总是重启,后来查资料发现dashboard的版本低,这时需要手动修改一下yaml或者在线修改pods的镜像即可。</p><p>这里介绍几个技巧:</p><p><em>查看日志</em>:</p><pre><code class="bash">$ kubectl logs -p POD_NAME</code></pre><p><em>编辑 Pod</em>:</p><pre><code class="bash">$ kubectl edit pods POD_NAME -n NAMESPACE</code></pre><p><em>导出pod或者deployment配置</em>:</p><pre><code class="bash">$ kubectl get pods POD_NAME -n NAMESPACE_NAME -o yaml</code></pre></blockquote><p>之后,您将能够从本地机器启动代理以访问刚刚创建的服务。</p><pre><code class="bash">$ nohup kubectl proxy --address 0.0.0.0 --accept-hosts '.*' &</code></pre><p>Dashboard 可以通过 <a href="https://172.16.3.17:32351" target="_blank" rel="noopener">https://172.16.3.17:32351</a> 访问仪表盘。</p><blockquote class="colorquote warning"><p><strong>注意</strong>:</p><ul><li><p>这里要使用https协议</p></li><li><p>由于kubernetes-dashboard服务使用了NodePort的方式,因此这里的端口号为NodePort,这里的IP为NodeIP,即该POd所在的Node地址。 </p></li></ul></blockquote><p><img src="https://www.41sh.cn/zb_users/upload/2019/01/201901151547536342389405.png" alt="2019-01-15 15-12-04屏幕截图.png"></p><p>这里我们选择跳过,可以出现下面所示的资源管理界面。</p><p><img src="https://www.41sh.cn/zb_users/upload/2019/01/201901151547536405708187.png" alt="2019-01-15 15-13-14屏幕截图.png"></p><p>但是,这种打开方式是安全的,但是集群管理是不允许操作的,这就是说这种打开方式是匿名或者游客模式。</p><p>接下来需要创建一个 service account 在 default namespace 并且创建一个 clusterrolebinding 对象允许我们的service account 来使用 dashboard.</p><pre><code class="bash">$ kubectl create serviceaccount dashboard -n defaultserviceaccount “dashboard” created$ kubectl create clusterrolebinding dashboard-admin -n default \ --clusterrole=cluster-admin \ --serviceaccount=default:dashboardclusterrolebinding "dashboard-admin" created</code></pre><blockquote><p><em>如果想了解更多有关集群角色和更细的配置可以参考</em>:<br><a href="https://kubernetes.io/docs/admin/authorization/rbac/#user-facing-roles" target="_blank" rel="noopener">官方文档</a>. </p></blockquote><p>以下命令可以得到我们创建的 服务帐号的 token(令牌),我们将打印出的token值复制到登录对话框中。</p><pre><code class="bash">$ kubectl get secret $(kubectl get serviceaccount dashboard -o jsonpath="{.secrets[0].name}") -o jsonpath="{.data.token}" | base64 --decodeeyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRhc2hib2FyZC10b2tlbi01bmo5ayIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJkYXNoYm9hcmQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1OGEzNzlmMC0xODk1LTExZTktYjBlMi1iODI3ZWJiMWY4NTgiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkYXNoYm9hcmQifQ.X6evSc9wDPPa6T9CLy40gDj8aUTjVMg1PhA5bQIhSTnvVeB8mG763y9j8c0G3wOv4gCk1egziTIenpFx0w04P2zUTcqrHdse51vnbE5TNETQo8EiY2ELsqUJuaGd-O3Z6nmL8psBk4CmloPCMgYaBXPWiHPeS9dyOgTH-KxFoEEAuCX1i3BWPkYN_faN-sQe7zlrhu27lPJVUey8HGVjPu_6zGxMSWcZu2Wz3Euc1A-Rg6tekDhnxhxH_dcMRF38jSVY_z9r7mfvw6dJmxXTlH-KNzagruulH2l-Pg9obpz7HO7t14JB6c1F6p5Qa4zk2y9vOz4qCPk6IM7_ZfTwCw</code></pre>]]></content>
<summary type="html">
<p>在最近关于在树莓派集群上使用kubeadm配置安装Kubernetes 1.13.0的教程中,默认情况下启用了RBAC,本文章将介绍如何在启用RBAC的情况下运行Kubernetes仪表板。</p>
</summary>
<category term="Kubernetes DashBoard" scheme="https://readailib.com/categories/Kubernetes-DashBoard/"/>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Dashboard" scheme="https://readailib.com/tags/Dashboard/"/>
</entry>
<entry>
<title>在树莓派上建立kubernetes集群</title>
<link href="https://readailib.com/2019/01/07/kubernetes/raspberrypi/build-a-kubernetes-cluster/"/>
<id>https://readailib.com/2019/01/07/kubernetes/raspberrypi/build-a-kubernetes-cluster/</id>
<published>2019-01-07T07:37:08.000Z</published>
<updated>2019-03-07T08:42:31.713Z</updated>
<content type="html"><![CDATA[<p><img src="https://www.41sh.cn/zb_users/upload/2019/01/201901161547625729333503.jpg" alt="IMG_0468.jpg"></p><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>最近学了Kubernetes一段时间后,突然想在自己的树莓派上玩玩,搭建一个集群出来,玩过树莓派的同学都知道,树莓派作为一个“卡片电脑式”的嵌入式电脑,它的性能是非常有限的,内存和CPU对于我们传统的机器智能是远远不可及的,因此我在这里PO出我为什么要做树莓派的kubernetes研究:</p><a id="more"></a><p><em>打造一个具有边缘智能的系统,可以在上面部署边缘级别的微服务组件,包括传感器的采集服务、控制和计算单元为核心的服务!</em></p><p>这里的实验环境为 1 个kubernetes master和 3 个 worker节点,共计 4 个树莓派节点。为了方便,这里的主机名以及IP地址如下:</p><ul><li>edge-master, IP:172.16.3.1</li><li>edge-node1, IP:172.16.3.17</li><li>edge-node2, IP:172.16.3.32</li><li>edge-node3, IP:172.16.3.3</li></ul><blockquote><p>这里我没有使用静态IP,因此IP地址是有可能变的,因此为了稳定,建议设置成静态IP。但为了教程的完整性,我也会说明如何设置静态IP。</p></blockquote><p>这里主要参考了下面的教程:</p><ul><li><a href="https://gist.github.com/aaronkjones/d996f1a441bc80875fd4929866ca65ad" target="_blank" rel="noopener">https://gist.github.com/aaronkjones/d996f1a441bc80875fd4929866ca65ad</a></li><li><a href="https://github.com/alexellis/k8s-on-raspbian/blob/master/GUIDE.md" target="_blank" rel="noopener">https://github.com/alexellis/k8s-on-raspbian/blob/master/GUIDE.md</a></li></ul><h2 id="系统准备-TL-DR"><a href="#系统准备-TL-DR" class="headerlink" title="系统准备(TL;DR)"></a>系统准备(TL;DR)</h2><p>这里的准备系统是要准备树莓派操作系统,一般来讲运行kubernetes这么大的系统,最好的系统选择方式为轻量级的操作系统,这里推荐两个操作系统:</p><ul><li>Raspbian Stretch Lite - <a href="https://www.raspberrypi.org/downloads/raspbian/" target="_blank" rel="noopener">https://www.raspberrypi.org/downloads/raspbian/</a></li><li>Hypriot OS - <a href="https://blog.hypriot.com/" target="_blank" rel="noopener">https://blog.hypriot.com/</a></li></ul><p>这里为了方便,我使用了 Raspbian Stretch Lite 系统作为我的边缘设备操作系统,下载地址: <a href="https://www.raspberrypi.org/downloads/raspbian/" target="_blank" rel="noopener">https://www.raspberrypi.org/downloads/raspbian/</a></p><p>树莓派系统安装都是一个样,安装教程可以参考:<a href="https://www.shenhengheng.xyz/files/respberry_doc.pdf" target="_blank" rel="noopener">https://www.shenhengheng.xyz/files/respberry_doc.pdf</a></p><p>安装完成之后,需要初始化下面的操作,包括更改主机名,密码,连接wifi,设置docker网络代理,还有设置静态IP等。</p><h3 id="初始化系统"><a href="#初始化系统" class="headerlink" title="初始化系统"></a>初始化系统</h3><ul><li>更改主机名,修改密码以及连接Wi-Fi等工作都可以通过 raspi-config 命令来完成。</li></ul><h2 id="Master节点设置"><a href="#Master节点设置" class="headerlink" title="Master节点设置"></a>Master节点设置</h2><h3 id="设置静态IP"><a href="#设置静态IP" class="headerlink" title="设置静态IP"></a>设置静态IP</h3><pre><code class="bash">$ cat << EOF >> /etc/dhcpcd.confprofile static_eth0static ip_address=192.168.0.100/24static routers=192.168.0.1static domain_name_servers=8.8.8.8EOF</code></pre><blockquote><p>安装100,101,102,103 的格式设置其他的树莓派节点IP地址</p></blockquote><h3 id="设置网络代理"><a href="#设置网络代理" class="headerlink" title="设置网络代理"></a>设置网络代理</h3><p>因为后期会需要下载kubeadm,kubelete,docker等软件,因此需要设置网络代理,通过代理下载软件。</p><pre><code class="bash">$ export http_proxy="http://172.14.1.54:1080"$ export https_proxy="http://172.14.1.54:1080"</code></pre><h3 id="安装docker"><a href="#安装docker" class="headerlink" title="安装docker"></a>安装docker</h3><p>这个步骤依赖于上面的网络代理设置的,如果设置成功了,那么这步才可能成功执行。</p><pre><code class="bash">$ curl -sSL get.docker.com | sh && sudo usermod pi -aG docker$ newgrp docker</code></pre><h3 id="关闭swap"><a href="#关闭swap" class="headerlink" title="关闭swap"></a>关闭swap</h3><pre><code class="bash">$ sudo dphys-swapfile swapoff && \ sudo dphys-swapfile uninstall && \ sudo update-rc.d dphys-swapfile remove</code></pre><p>这个步骤是为了后面的 kubeadm init 成功执行!具体的缘由可以参考我之前的文章:<a href="https://www.41sh.cn/?id=8" target="_blank" rel="noopener">https://www.41sh.cn/?id=8</a></p><p>为了检测是否成功关闭,可以执行下面的命令,如果成功执行了,那么下面的命令将不会有任何的输出。</p><pre><code class="bash">$ sudo swapon --summary</code></pre><h3 id="编辑-boot-cmdline-txt"><a href="#编辑-boot-cmdline-txt" class="headerlink" title="编辑 /boot/cmdline.txt"></a>编辑 /boot/cmdline.txt</h3><p>打开 /boot/cmdline.txt,并在末尾添加如下指令。</p><pre><code class="bash">cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory</code></pre><blockquote><p>该步骤执行完成后,一定要重启!否则后面会有错误。</p></blockquote><h3 id="安装kubernetes"><a href="#安装kubernetes" class="headerlink" title="安装kubernetes"></a>安装kubernetes</h3><p>该步骤也同样依赖于设置网络代理那一步骤!</p><pre><code class="bash">$ sudo su # 切换到root用户$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list && \apt-get update -q && \apt-get install -qy kubeadm=1.10.2-00 kubectl=1.10.2-00 kubelet=1.10.2-00</code></pre><blockquote class="colorquote warning"><p>1). 这里有个<strong>小坑</strong>,切到root用户后,需要重新配置网络代理,然后就可以使用了。</p><pre><code class="bash">$ <span class="built_in">export</span> http_proxy=<span class="string">"http://172.14.1.54:1080"</span>$ <span class="built_in">export</span> https_proxy=<span class="string">"http://172.14.1.54:1080"</span></code></pre><p>2). 使用root时,不用加sudo前缀,这个已经踩了很多次了</p></blockquote><p>如果安装最新版本,不用指定版本。可以直接使用下面的命令:</p><pre><code class="bash">$ apt-get install -qy kubeadm kubectl kubelet</code></pre><h3 id="修改docker的网络代理"><a href="#修改docker的网络代理" class="headerlink" title="修改docker的网络代理"></a>修改docker的网络代理</h3><p>这一步非常重要!因为我们可能需要pull很多国内不能访问的镜像,因此需要设置docker网络代理,因为默认情况下,docker服务不是通过systemctl管理的,因此需要创建一个系统服务,具体的命令如下:</p><pre><code class="bash">$ mkdir -pv /etc/systemd/system/docker.service.d$ cat << EOF >> /etc/systemd/system/docker.service.d/http-proxy.conf[Service]Environment="HTTP_PROXY=http://172.16.3.12:1080/" "HTTPS_PROXY=http://172.16.3.12:1080/"EOF $ systemctl daemon-reload$ systemctl restart docker</code></pre><blockquote><p><strong>参考:</strong><a href="https://docs.docker.com/config/daemon/systemd/" target="_blank" rel="noopener">https://docs.docker.com/config/daemon/systemd/</a></p></blockquote><h3 id="预先pull镜像"><a href="#预先pull镜像" class="headerlink" title="预先pull镜像"></a>预先pull镜像</h3><pre><code class="bash">$ kubeadm config images pull -v3</code></pre><h3 id="初始化master节点"><a href="#初始化master节点" class="headerlink" title="初始化master节点"></a>初始化master节点</h3><pre><code class="bash">$ kubeadm init --token-ttl=0 --pod-network-cidr=10.244.0.0/16</code></pre><p>这步如果成功的话,会打印下面的消息: </p><pre><code class="bash">Your Kubernetes master has initialized successfully!To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/configYou should now deploy a pod network to the cluster.Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/You can now join any number of machines by running the following on each nodeas root: kubeadm join 172.16.3.1:6443 --token xo78oj.02cia85vdh285aqj --discovery-token-ca-cert-hash sha256:9517c72036f8261ac912adaf8339b65583fdaa7dbb8dd60054c6e84e8880a3fd</code></pre><p> 然后根据输出的提示,执行下面的语句:</p><pre><code class="bash">$ mkdir -p $HOME/.kube$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config$ sudo chown $(id -u):$(id -g) $HOME/.kube/config</code></pre><blockquote><p>上面的语句需要在pi用户下执行!否则不管用!</p></blockquote><p>这步完成之后,执行下面的命令,你会发现master节点NotReady的消息,这是因为我们的网络需要依赖于flannel网络组件!</p><blockquote class="colorquote info"><h2 id="Setup-networking-with-Weave-Net-or-Flannel"><a href="#Setup-networking-with-Weave-Net-or-Flannel" class="headerlink" title="Setup networking with Weave Net or Flannel"></a>Setup networking with Weave Net or Flannel</h2><p>Some users have reported stability issues with Weave Net on ARMHF. These issues do not appear to affect x86_64 (regular PCs/VMs). You may want to try Flannel instead of Weave Net for your RPi cluster.</p><h3 id="Weave-Net"><a href="#Weave-Net" class="headerlink" title="Weave Net"></a>Weave Net</h3><p>Install <a href="https://www.weave.works/oss/net/" target="_blank" rel="noopener">Weave Net</a> network driver</p><pre><code class="bash">$ kubectl apply -f <span class="string">"https://cloud.weave.works/k8s/net?k8s-version=<span class="variable">$(kubectl version | base64 | tr -d '\n')</span>"</span></code></pre><p>If you run into any issues with Weaveworks’ networking then <a href="https://github.com/coreos/flannel" target="_blank" rel="noopener">flannel</a> is also a popular choice for the ARM platform.</p><h3 id="Flannel-alternative"><a href="#Flannel-alternative" class="headerlink" title="Flannel (alternative)"></a>Flannel (alternative)</h3><p>Apply the Flannel driver on the master:</p><pre><code class="bash">$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/c5d10c8/Documentation/kube-flannel.yml</code></pre><p>On each node that joins including the master:</p><pre><code class="bash">$ sudo sysctl net.bridge.bridge-nf-call-iptables=1</code></pre></blockquote><h2 id="其他节点设置"><a href="#其他节点设置" class="headerlink" title="其他节点设置"></a>其他节点设置</h2><p>其他节点和master类似,最后需要执行下面的命令就可以了!</p><pre><code class="bash">$ kubeadm join 172.16.3.1:6443 --token xo78oj.02cia85vdh285aqj --discovery-token-ca-cert-hash sha256:9517c72036f8261ac912adaf8339b65583fdaa7dbb8dd60054c6e84e8880a3fd</code></pre>]]></content>
<summary type="html">
<p><img src="https://www.41sh.cn/zb_users/upload/2019/01/201901161547625729333503.jpg" alt="IMG_0468.jpg"></p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>最近学了Kubernetes一段时间后,突然想在自己的树莓派上玩玩,搭建一个集群出来,玩过树莓派的同学都知道,树莓派作为一个“卡片电脑式”的嵌入式电脑,它的性能是非常有限的,内存和CPU对于我们传统的机器智能是远远不可及的,因此我在这里PO出我为什么要做树莓派的kubernetes研究:</p>
</summary>
<category term="RPI Kubernetes" scheme="https://readailib.com/categories/RPI-Kubernetes/"/>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="RaspberryPi" scheme="https://readailib.com/tags/RaspberryPi/"/>
</entry>
<entry>
<title>构建高效率的 Docker 镜像</title>
<link href="https://readailib.com/2018/11/29/docker/build-effective-image/"/>
<id>https://readailib.com/2018/11/29/docker/build-effective-image/</id>
<published>2018-11-29T12:04:25.000Z</published>
<updated>2019-03-13T12:12:37.914Z</updated>
<content type="html"><![CDATA[<p>今天要学习主题是:如何构建高效的 Docker 镜像, 更小,更快!今天学习的大纲如下。</p><ul><li>Docker 镜像和容器的层的工作原理</li><li>构建最小镜像的基础知识</li><li>什么是好的、坏的和臃肿的 Dokcerfile</li><li>如何构建面向特定编程语言环境的镜像</li></ul><blockquote><p>主要参考 Dockercon 大会 @abbyfuller 的 “Creating Effective Images” 演讲。 <a href="https://www.youtube.com/watch?v=vlS5EiapiII&t=396s" target="_blank" rel="noopener">大会视频</a></p></blockquote><h2 id="Docker-容器的-layer"><a href="#Docker-容器的-layer" class="headerlink" title="Docker 容器的 layer"></a>Docker 容器的 layer</h2><h3 id="镜像-images-与层-layers"><a href="#镜像-images-与层-layers" class="headerlink" title="镜像(images)与层(layers):"></a>镜像(images)与层(layers):</h3><p>Docker 镜像是由多个文件系统(只读层)叠加而成,每个层仅包含了前一层的差异部分。当我们启动 一个容器的时候,Docker 会加载镜像层并在其上添加一个可写层。容器上所做的任何更改,比如新建文件、更改文件、删除文件,都将记录与可写层上<a href="https://shenheng.xyz/posts/2018-11-29-effective-docker-1/#fn:https-docs-docke" target="_blank" rel="noopener">1</a>。容器层与镜像层的结构如下图所示。(这段话的<a href="https://blog.csdn.net/shengjmm/article/details/77411253" target="_blank" rel="noopener">来源</a>)</p><p><img src="https://shenheng.xyz/img/1031517-20170818160738428-270877903.png" alt="容器层与镜像层的结构图"></p><p>容器层与镜像层的结构图</p><h3 id="容器与层级的联系"><a href="#容器与层级的联系" class="headerlink" title="容器与层级的联系"></a>容器与层级的联系</h3><p>容器与镜像最大的区别就在于可写层上。如果运行中的容器修改了现有的一个已存在的文件,那该文件 将会从可写层下的只读层复制到可写层,该文件的只读版本仍然存在,只是已经被可写层中该文件的副 本所隐藏。其中,多个容器共享镜像的结构如下所示。</p><p><img src="https://shenheng.xyz/img/1031517-20170818160746662-307561193.png" alt="容器层与镜像层的结构图"></p><p>容器层与镜像层的结构图</p><h2 id="如何构建最小镜像"><a href="#如何构建最小镜像" class="headerlink" title="如何构建最小镜像"></a>如何构建最小镜像</h2><p><strong>问题:</strong> 构建的层数越多真的好吗?</p><blockquote><p>更多的层意味着你的构建的 镜像就越大,同时也意味着你构建的时间就越长,并且你 pull 到本地 或者 push 到 registry 的时间就越长。 通常来讲,小镜像的层数都是比较少的,他会占用你很少的资源。</p></blockquote><p>那么如何减小镜像的体积呢?</p><p>我们在构建自己的镜像时,可能都会使用一些 base image (基础镜像)来开始构建,比如</p><pre><code class="docker">FROM ubuntu:14.04</code></pre><p>等等, 这些镜像可能是官方给出的,或者是别人构建的,那么这些基础镜像我们真的选对了吗?除了合理的选择基础镜像意外,还需要做到如下几点:</p><ul><li>第一、限制写入容器的数据</li><li>第二、 链式化 RUN 语句</li><li>第三、尽可能出现防止在构建的时候 cache 未命中的情况</li></ul><h3 id="认识-Dockerfile"><a href="#认识-Dockerfile" class="headerlink" title="认识 Dockerfile"></a>认识 Dockerfile</h3><p>什么是Dockerfile?Dockerfile 是记录着你构建镜像的一系列的操作步骤。</p><p>首先我们先认识一下 Dockerdfile, 下面的 Dockerfile 主要是 构建一个 python 的应用程序。</p><pre><code class="docker">FROM ubuntu:latestLABEL maintainer abbyfull@amazon.comRUN apt-get update -y && apt-get install -y python-pip python-dev build-essentialCOPY . /appWORKDIR /appRUN pip install –r requirements.txtEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]</code></pre><h3 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h3><p>我们要根据上面的规则,开始优化它!</p><p>第一步,选择一个合适的基础镜像,使用不同基镜像构建出来的镜像大小差距还是很悬殊的。</p><pre><code class="bash">ubuntu latest 2b1dc137b50252 seconds ago 458 MBFrom python:2.7-alpine:alpine latest d3145c9ba1fa2 minutes ago 86.8 MB</code></pre><p>从上面可以看出,使用 ubuntu 作为基础镜像构建的应用镜像是 458MB,而使用 python:2.7-alpine 作为基础镜像构建的镜像才 85.8MB。因此选择一个合适的镜像是非常重要的一步。</p><p>那么这些基础镜像原来有多大呢,下面列出了常用到的基础镜像的大小:</p><p><img src="https://shenheng.xyz/img/base-image.png" alt="常用的基础镜像列表"></p><p>常用的基础镜像列表</p><p>那么什么是时候使用一个具有完整操作系统功能的镜像,比如ubuntu,centos等镜像呢?需要满足几点,建议使用。</p><ul><li>安全性</li><li>开发</li><li>部署大型应用程序,分布式应用程序</li></ul><p>下面是使用ubuntu镜像构建的程序:</p><pre><code class="docker">FROM ubuntu:latestRUN apt-get update -y && apt-get install -y python-pip python-dev build-essentialCOPY . /appWORKDIR /appRUN pip install –r requirements.txtEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]</code></pre><p>下面是使用 python 镜像构建的:</p><pre><code class="docker">FROM python:2.7-alpineCOPY . /appWORKDIR /appRUN pip install –r requirements.txtEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]</code></pre><p>可以发现使用 python 镜像的Dockerfile 更加简单,并且避免了安装了 python 环境,因为 python 镜像已经整合了 python 的开发环境。</p><p>等等!我们还可以改进它!我们可以发现 <code>COPY . /app</code> 会占用我们很多的缓存资源,因为我们只用了<code>requirements.txt</code> 文件,记住:更少的缓存利用,说明你构建的镜像会更小。改进后的 Dockerfile如下:</p><pre><code class="docker">FROM python:2.7-alpineCOPY requirements.txt /appRUN pip install –r /app/requirements.txtCOPY . /appWORKDIR /appEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]</code></pre><p>接下来我们将使用 ONBUILD 指令来更好的重用我们的环境,使后期开发更加高效。</p><blockquote><p>The <code>ONBUILD</code> instruction adds to the image a trigger instruction to be executed at a later time, when the image is used as the base for another build. The trigger will be executed in the context of the downstream build, as if it had been inserted immediately after the <code>FROM</code> instruction in the downstream <code>Dockerfile</code>.</p><p>Any build instruction can be registered as a trigger.</p><p>This is useful if you are building an image which will be used as a base to build other images, for example an application build environment or a daemon which may be customized with user-specific configuration.</p><p>For example, if your image is a reusable Python application builder, it will require application source code to be added in a particular directory, and it might require a build script to be called after that. You can’t just call <code>ADD</code> and <code>RUN</code> now, because you don’t yet have access to the application source code, and it will be different for each application build. You could simply provide application developers with a boilerplate <code>Dockerfile</code> to copy-paste into their application, but that is inefficient, error-prone and difficult to update because it mixes with application-specific code.</p></blockquote><pre><code class="docker">FROM python:2.7-alpineONBUILD ADD requirements.txt /appONBUILD RUN pip install –r /app/requirements.txtONBUILD COPY . /appWORKDIR /appEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]</code></pre><h2 id="Good-Bad-Bloated-Dockerfile"><a href="#Good-Bad-Bloated-Dockerfile" class="headerlink" title="Good/Bad/Bloated Dockerfile"></a>Good/Bad/Bloated Dockerfile</h2><h3 id="玩个例子"><a href="#玩个例子" class="headerlink" title="玩个例子"></a>玩个例子</h3><p>现在让我们看个大一点的 Dockerfile,记住镜像的层越多越大!我们可以数一数它的行数!必须优化它!</p><pre><code class="docker">FROM ubuntu:latestRUN apt-get update -yRUN apt-get install -y python-pip python-dev build-essentialCOPY . /appWORKDIR /appRUN pip install -r requirements.txtEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]</code></pre><p>可以看到两个连续的 <code>RUN</code> 指令。</p><pre><code class="docker">RUN apt-get update -yRUN apt-get install -y python-pip python-dev build-essential</code></pre><p>我们可以链式 <code>RUN</code> 指令!</p><pre><code class="docker">FROM ubuntu:latestRUN apt-get update -y && apt-get install -y python-pip python-dev build-essential –no-install-recommendsCOPY . /appWORKDIR /appRUN pip install -r requirements.txtEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]</code></pre><p>现在尝试一下其他的基础镜像,比如 python 镜像:</p><pre><code class="docker">FROM python:2.7-alpineCOPY . /appWORKDIR /appRUN pip install -r requirements.txtEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]</code></pre><h3 id="更高效的使用-RUN-指令"><a href="#更高效的使用-RUN-指令" class="headerlink" title="更高效的使用 RUN 指令"></a>更高效的使用 RUN 指令</h3><pre><code class="docker">RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ build-essential \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.* \&& rm -rf /var/lib/apt/lists/*</code></pre><p>在使用 RUN 指令时,需要注意语句的顺序很重要!</p><h3 id="避免使用-ADD-指令添加大文件"><a href="#避免使用-ADD-指令添加大文件" class="headerlink" title="避免使用 ADD 指令添加大文件"></a>避免使用 ADD 指令添加大文件</h3><p>下面是一种不推荐的方法,因为如果添加大文件,不避免地会使用很大的缓存,劲儿会使得我们得到的镜像会很大。</p><pre><code class="docker">ADD http://cruft.com/bigthing.tar.xz /app/cruft/RUN tar -xvf /app/cruft/bigthing.tar.xz -C /app/cruft/RUN make -C /app/cruft/ all</code></pre><p>更好推荐方案是将你要添加的文件放在互联网上,使用 curl 或者 wget 下载,最好避免使用 ADD。</p><pre><code class="docker">RUN mkdir -p /app/cruft/ \ && curl -SL http://cruft.com/bigthing.tar.xz \ | tar -xJC /app/cruft/ && make -C /app/cruft/ all</code></pre><h3 id="多阶段构建"><a href="#多阶段构建" class="headerlink" title="多阶段构建"></a>多阶段构建</h3><pre><code class="dockerfile">FROM ubuntu AS build-envRUN apt-get install makeADD . /srcRUN cd /src && make</code></pre><pre><code class="dockerfile">ROM busyboxCOPY --from=build-env /src/build/app /usr/local/bin/appEXPOSE 80ENTRYPOINT /usr/local/bin/app</code></pre><p>可以写成两个 dockerfile 也可以写成单独一个 dockerfile,但是记住构建的顺序非常重要的。</p><h2 id="构建面向-Golang-的-Dockerfile"><a href="#构建面向-Golang-的-Dockerfile" class="headerlink" title="构建面向 Golang 的 Dockerfile"></a>构建面向 Golang 的 Dockerfile</h2><p>有两种构建特定语言的 Docker 镜像方式:</p><ul><li>从头开始构建</li><li>从一个基础镜像开始构建<ul><li>选择基础镜像有一点说明:编程语言官方镜像可能会比较大,但是稳定。自己构建的镜像可能小但是不一定稳定,需要平衡选择。</li></ul></li></ul><p>下面会讲解 Golang 应用程序的构建方法。</p><p>稍微熟悉 Golang 的同学都应噶了解,Go 的执行方式很简单,只需要一个二进制文件即可,这也意味着构建的 Docker 镜像会非常的简单。</p><p><strong>步骤:</strong>编译,然后拷贝编译好的二进制文件</p><pre><code class="bash">go build -o dockercon .docker build -t dockercon .</code></pre><p>Dockerfile 文件内容为:</p><pre><code class="docker">FROM scratchCOPY ./dockercon /dockerconENTRYPOINT ["/dockercon"]</code></pre><blockquote><p><code>scratch</code> 镜像非常特殊,构建它的 Dockerfile 是空的。一般开发人员喜欢使用 <code>scratch</code> 镜像构建收于自己的基础镜像。</p></blockquote><p>比如下面的例子,只需要执行打印一个 helloworld 的二进制文件的镜像构建过程,这时使用 <code>scratch</code>最合适不过了。</p><pre><code class="docker">FROM scratchCOPY hello /CMD [ “/hello” ]</code></pre>]]></content>
<summary type="html">
<p>今天要学习主题是:如何构建高效的 Docker 镜像, 更小,更快!今天学习的大纲如下。</p>
<ul>
<li>Docker 镜像和容器的层的工作原理</li>
<li>构建最小镜像的基础知识</li>
<li>什么是好的、坏的和臃肿的 Dokcerfile</li>
</summary>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
</entry>
<entry>
<title>Go语言中的并发机制系列-4/1 | 深入理解 context 包</title>
<link href="https://readailib.com/2018/11/28/golang/deep-understand-context-package/"/>
<id>https://readailib.com/2018/11/28/golang/deep-understand-context-package/</id>
<published>2018-11-28T11:50:22.000Z</published>
<updated>2019-03-13T11:52:34.109Z</updated>
<content type="html"><![CDATA[<p>本系列阅读笔记是主要基于 Golang 经典图书 <em>Concurrency in GO</em> ,主要有6个章节:</p><ul><li>An Introduction to Concurrency</li><li>Modeling Your Code: Communicating Sequential Processes**</li><li>Go’s Concurrency Building Blocks</li><li><strong>Concurrency Patterns in Go</strong></li><li>Concurrency at Scale</li><li>Goroutines and the Go Runtime</li></ul><p>本部分主要摘选自第 4 章:<em>Concurrency Patterns in Go</em> ,Go 的并发模式.</p><p>在 Go Web编程中,由于每个传进来的请求(handlerFunc)都要交给一个 goroutine 来处理,那么为了使得主进程能够感应每个请求的状态,或者互联网中常见到的超时处理等,都不免少了 context 包的应用,它很强大,简约,但很容易让人忽视.本篇文章主要深度讲解 context 包的用法.下面是 <code>context</code> <a href="https://blog.golang.org/context" target="_blank" rel="noopener">官方的博客</a>给出介绍(粗略翻译):</p><blockquote><p>在Go服务器中,每个传入的请求都在单独的一个 goroutine 中进行处理. 请求处理程序通常会启动其他goroutine 去访问后端,例如数据库和RPC服务. 处理请求的 goroutine 集合通常需要访问特定请求的值,例如终端用户的身份(auth),授权令牌(token/password)和请求的截止日期(deadline).当请求取消或者超时,处理该请求的所有 goroutine 都应该快速退出,以便系统可以回收它们正在使用的任何资源.</p><p>在Google,我们开发了 context 包,它可以轻松地将 <strong>请求值</strong>,<strong>取消信号</strong> 和 <strong>API边界的截止日期</strong>传递给处理该请求的所有 goroutine .</p></blockquote><h2 id="Context-Type"><a href="#Context-Type" class="headerlink" title="Context Type"></a>Context Type</h2><p>让我们看看 context 包里面有什么吧.</p><pre><code class="go">var Canceled = errors.New("context canceled")var DeadlineExceeded error = deadlineExceededError{}type CancelFunctype Contextfunc Background() Contextfunc TODO() Contextfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc)func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)func WithValue(parent Context, key, val interface{}) Context</code></pre><p>下面是 Context 类型的定义, Context 类型是 context 包的核心.</p><pre><code class="go">// A Context carries a deadline, cancelation signal, and request-scoped values// across API boundaries. Its methods are safe for simultaneous use by multiple// goroutines.type Context interface { // 只有当 Context 取消或者超时,Done 将返回一个因抢占被关闭的channel. Done() <-chan struct{} // context取消的原因. Err() error // 用于标示 goroutine 是否在给定的时间后取消 Deadline() (deadline time.Time, ok bool) // Context 携带的数据 Value(key interface{}) interface{}}</code></pre><ul><li>Done方法返回一个通道,作为代表 Context 运行的函数的取消信号:当通道关闭时,函数应该放弃它们的工作并返回。</li><li>Err方法返回一个错误,指示 Context 被取消的原因。</li><li>Go 作者注意到 goroutines 的主要用途之一是为请求提供服务.通常在这些程序中,除了有关抢占的信息之外,还需要传递特定请求的信息.这是 <code>Value</code> 函数的目的.</li></ul><p>这种类型将像 <code>Done</code> 通道一样流经系统.如果使用 context 包, 顶级并发调用子/下游程序的每个函数都会将 Context 类型变量作为其第一个参数. 就像下面这样调用一样:</p><pre><code class="go">import "golang.org/x/net/context"// ReadFile reads file name and returns its contents.// If ctx.Done is closed, ReadFile returns ctx.Err immediately.func ReadFile(ctx context.Context, name string) ([]byte, error)</code></pre><p>现在我们只需要知道 context 包主要有两个目的:</p><ul><li>To provide an API for canceling branches of your call-graph.</li><li>To provide a data-bag for transporting request-scoped data through your call-graph.</li></ul><h2 id="取消"><a href="#取消" class="headerlink" title="取消"></a>取消</h2><p>函数中的取消有三个方面:</p><ul><li>goroutine 的父母可能想要取消它.</li><li>goroutine 可能想要取消它的孩子.</li><li>goroutine 中的任何阻塞操作都是可抢占的,以便可以取消它.(被抢占)</li></ul><p>context 包都已经实现了这三种方面.</p><p>正如之前定义的那样,Context 类型将会是函数的第一个参数. 如果你看一下Context接口上的方法,你会发现没有任何东西可以改变底层结构的状态.此外,没有任何东西允许接受Context的函数取消它.这可以保护来自取消上下文的子代的调用堆栈的功能.结合提供完成通道的 Done 方法,这允许Context类型从其前提安全地管理取消.</p><p>context 包提供了从现有 context 值中派生新的 Context 值的函数。这些值构成一个树:当一个Context被取消时,从它派生的所有上下文也被取消。</p><p><img src="https://shenheng.xyz/img/Understanding-Go-Context-Library-Google-Docs.png" alt="Context tree"></p><p>Context tree</p><pre><code class="go">A = context.Background()B = context.WithCancel(A)C = context.WithValue(A, “Value”, “C”)D = context.WithValue(A, “Value”, “D”)E = context.WithValue(B, “B Key”, “E”)..H = context.WithValue(D, “D Key”, “H”)..</code></pre><p>这提出了一个问题:如果 Context 是不可变的,那么我们如何影响调用堆栈中当前函数下的函数中取消的行为?这是 context 包中的函数变得重要的地方.我们会到源码中,看看的下面三种函数:</p><blockquote><p>Context 接口并没有定义,取消的方法.如果要使用,使用下面的三种函数,他们都返回可取消的 context 和 cancel 函数.</p></blockquote><pre><code class="go">func WithCancel(parent Context) (ctx Context, cancel CancelFunc)func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)</code></pre><p><strong>注意</strong>:所有这些函数都接受 Context 并返回一个Context.其中一些还涉及其他参数,如截止日期和超时时间.这些函数都生成一个新的 Context 实例,其中包含与这些函数相关的选项.</p><ul><li><code>WithCancel</code> 返回一个新的 Context 和一个 cancel 函数, 当调用 cancel 函数后主动关闭 完成的通道,此时 <code>ctx.Done()</code> 将返回。</li><li><code>WithDeadline</code> 返回一个新的 Context 和一个 cancel 函数,当超过给定的截止时间(时间戳)时,它关闭完成的通道. 此时 <code>ctx.Done()</code> 将返回。</li><li><code>WithTimeout</code> 返回一个新的 Context 和一个 cancel函数,在超过给定的持续时间(持续时间长度)后关闭完成的通道. 此时 <code>ctx.Done()</code> 将返回。</li></ul><blockquote><p><strong>注意</strong>: 关闭通道与关闭文件或者关闭套接字不一样,关闭通道并不会使通道的机能完全停止— 他的作用就通知其他正在尝试从这个通道接受值的 goroutine,这个通道已经不再接受任何值了。</p></blockquote><h2 id="取消实例"><a href="#取消实例" class="headerlink" title="取消实例"></a>取消实例</h2><pre><code class="go">// goroutine #1ctx, cancel := context.WithCancel(parent)...data, err := ReadFile(ctx, name)// goroutine #2cancel()</code></pre><p>context 包提供了两种创建空的 context 实例的方法:</p><pre><code class="go">func Background() Contextfunc TODO() Context</code></pre><p><code>Background</code> 简单地返回一个空的 <code>Context</code> , <code>TODO</code> 不是应用在生产环境下的,但是也返回一个空的 <code>Context</code> , <code>TODO</code> 的目的是作为 <code>Context</code> 的占位符,当不知道要使用哪个 <code>Context</code> 时, 或者你的代码将提供 <code>Context</code> 时,但上游代码尚未提供。</p><pre><code class="go">func main() { var wg sync.WaitGroup done := make(chan interface{}) defer close(done) wg.Add(1) go func() { defer wg.Done() if err := printGreeting(done); err != nil { fmt.Printf("%v", err) return } }() wg.Add(1) go func() { defer wg.Done() if err := printFarewell(done); err != nil { fmt.Printf("%v", err) return } }() wg.Wait()}func printGreeting(done <-chan interface{}) error { greeting, err := genGreeting(done) if err != nil { return err } fmt.Printf("%s world!\n", greeting) return nil}func printFarewell(done <-chan interface{}) error { farewell, err := genFarewell(done) if err != nil { return err } fmt.Printf("%s world!\n", farewell) return nil}func genGreeting(done <-chan interface{}) (string, error) { switch locale, err := locale(done); { case err != nil: return "", err case locale == "EN/US": return "hello", nil } return "", fmt.Errorf("unsupported locale")}func genFarewell(done <-chan interface{}) (string, error) { switch locale, err := locale(done); { case err != nil: return "", err case locale == "EN/US": return "goodbye", nil } return "", fmt.Errorf("unsupported locale")}func locale(done <-chan interface{}) (string, error) { select { case <-done: return "", fmt.Errorf("canceled") case <-time.After(1*time.Minute): } return "EN/US", nil}</code></pre><p>运行结果为:</p><pre><code class="text">goodbye worldhello world</code></pre><p>忽略竞争条件,我们可以看到程序中有两个分支同时运行。我们通过创建一个 done 通道并将其传递给我们的调用图来设置标准抢占方法。如果我们在 main 函数中的任何一点关闭完成通道,则两个分支都将被取消。</p><p>通过在 main 函数中引入 goroutine,我们开辟了以一些不同且有趣的方式控制该程序的可能性。也许我们希望 genGreeting 如果花费太长时间就超时。如果我们知道它的父母将很快被取消,我们可能不希望genFarewell 调用语言环境。在每个堆栈帧,函数可以影响它下面的整个调用堆栈。</p><p>使用 done 通道模式,我们可以通过将传入的 done 通道包装在其他完成的通道中然后在其中任何一个触发时返回来完成此操作,但是我们不会获得有关上下文给出的最后期限和错误的额外信息。</p><p>下面使用 context 包修改一下:</p><pre><code class="go">func main() { var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) defer cancel() wg.Add(1) go func() { defer wg.Done() if err := printGreeting(ctx); err != nil { fmt.Printf("cannot print greeting: %v\n", err) cancel() } }() wg.Add(1) go func() { defer wg.Done() if err := printFarewell(ctx); err != nil { fmt.Printf("cannot print farewell: %v\n", err) } }() wg.Wait()}func printGreeting(ctx context.Context) error { greeting, err := genGreeting(ctx) if err != nil { return err } fmt.Printf("%s world!\n", greeting) return nil}func printFarewell(ctx context.Context) error { farewell, err := genFarewell(ctx) if err != nil { return err } fmt.Printf("%s world!\n", farewell) return nil}func genGreeting(ctx context.Context) (string, error) { ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() switch locale, err := locale(ctx); { case err != nil: return "", err case locale == "EN/US": return "hello", nil } return "", fmt.Errorf("unsupported locale")}func genFarewell(ctx context.Context) (string, error) { switch locale, err := locale(ctx); { case err != nil: return "", err case locale == "EN/US": return "goodbye", nil } return "", fmt.Errorf("unsupported locale")}func locale(ctx context.Context) (string, error) { select { case <-ctx.Done(): return "", ctx.Err() case <-time.After(1 * time.Minute): } return "EN/US", nil}</code></pre>]]></content>
<summary type="html">
<p>本系列阅读笔记是主要基于 Golang 经典图书 <em>Concurrency in GO</em> ,主要有6个章节:</p>
<ul>
<li>An Introduction to Concurrency</li>
<li>Modeling Your Code: Co
</summary>
<category term="Golang" scheme="https://readailib.com/tags/Golang/"/>
<category term="Go Concurrency" scheme="https://readailib.com/tags/Go-Concurrency/"/>
</entry>
<entry>
<title>Go语言中的并发机制系列-2|Go的并发哲学 & CSP</title>
<link href="https://readailib.com/2018/11/25/golang/concurracy-philosophy-and-csp/"/>
<id>https://readailib.com/2018/11/25/golang/concurracy-philosophy-and-csp/</id>
<published>2018-11-25T11:53:28.000Z</published>
<updated>2019-03-13T12:02:38.388Z</updated>
<content type="html"><![CDATA[<p>本系列阅读笔记是基于 Golang 经典图书 <em>Concurrency in GO</em> ,主要有6个章节:</p><ul><li>An Introduction to Concurrency</li><li><strong>Modeling Your Code: Communicating Sequential Processes</strong></li><li>Go’s Concurrency Building Blocks</li><li>Concurrency Patterns in Go</li><li>Concurrency at Scale</li><li>Goroutines and the Go Runtime</li></ul><p>本部分主要介绍第二章:<em>Modeling Your Code: Communicating Sequential Processes</em> ,Go并发设计哲学。</p><h2 id="并发与并行的区别"><a href="#并发与并行的区别" class="headerlink" title="并发与并行的区别"></a>并发与并行的区别</h2><blockquote><p>Concurrency is a property of the code; parallelism is a property of the running program.</p></blockquote><p>这揭示了一些有趣而重要的事情。</p><ul><li>第一个有趣的事情是,我们不写并行代码,只写我们希望并行运行的并发代码。并行性是我们程序运行时的属性,而不是代码。</li><li>第二个有趣的事情是,我们发现有可能 - 甚至可能 - 不知道我们的并发代码是否实际并行运行。这只能通过我们程序模型下面的抽象层 <strong>“并发原语”</strong> 来实现,程序运行时,操作系统、操作系统运行的平台(包括在虚拟机管理程序,容器和虚拟机的情况下),最终是CPU。这些抽象使我们能够区分并发性和并行性。</li><li>第三个也是最后一个有趣的事情是,并行性是时间或上下文的函数。还记得在 <a href="http://shenheng.xyz/posts/2018-11-24-go-routine-1/#%E5%8E%9F%E5%AD%90%E6%80%A7" target="_blank" rel="noopener">“原子性”</a> 中我们讨论过语境的概念吗?在那里,上下文被定义为操作被视为原子的边界。这里,它被定义为可以将两个或多个操作视为并行的边界。</li></ul><p>例如,如果我们的上下文被分成5个单位时间片,并且我们运行了两个操作,每个操作花费一秒钟来运行,我们会认为操作是并行运行的。如果我们的上下文是一秒钟,我们会认为操作是按顺序运行的。</p><h2 id="CSP"><a href="#CSP" class="headerlink" title="CSP"></a>CSP</h2><p>当人们讨论 Go 的时候,往往会谈到 Go 语言的成功离不开 CSP。那么CSP是什么?</p><p>CSP代表“通信顺序进程”,它既是一种技术,也是引入它的论文的名称。1978年,Charles Antony Richard Hoare 在 ACM 上发表了这篇<a href="http://bit.ly/HoareCSP" target="_blank" rel="noopener">论文</a>。</p><p>在这篇论文中,Hoare认为输入和输出是两个被忽略的编程原语,特别是在并发代码中。在Hoare撰写该论文时,关于如何构建程序的研究仍在进行中,但大部分工作都是针对顺序代码的技术: <strong>goto</strong> 语句的使用正在争论中,面向对象的范式正在开始扎根。同时 <strong>进程</strong> 的操作没有经过深思熟虑。Hoare 开始纠正这个问题,因此他的论文和CSP诞生了。</p><p>在1978年的论文中,CSP只是一种简单的编程语言,仅用于展示 CSP 的能力;事实上,他在论文中提到:</p><blockquote><p>Thus the concepts and notations introduced in this paper should … not be regarded as suitable for use as a programming language, either for abstract or for concrete programming.</p></blockquote><p>Hoare 非常担心他所提出的技术对于进一步研究程序的正确性没有任何帮助,并且这些技术可能无法在基于他自己的真实语言中表现出来。在接下来的六年中,CSP的概念被细化为一种称为 <strong>process calculus</strong> 的正式表示,以便采用 CSP 的思想并实际开始推理程序的正确性。</p><p>过程演算是一种对并发系统进行数学建模的方法,并且还提供代数法则来对这些系统执行变换以分析它们的各种属性,例如效率和正确性。</p><p>为了支持输入和输出需要被视为原语的断言,Hoare 的 CSP 编程语言包含了正确地模拟进程之间的输入和输出或通信的原语。Hoare将术语进程应用于逻辑的任何封装部分,这些逻辑需要输入运行并产生其他进程将消耗的输出。Hoare可能会使用“函数”这个词,如果不是在撰写论文时如何构建社区中发生的程序的辩论。</p><p>对于进程之间的通信,Hoare创建了输入和输出命令:<code>!</code> 用于将输入发送到进程中,<code>?</code>用于读取进程的输出。每个命令都必须指定输出变量(在从进程读取变量的情况下)或目标(在向进程发送输入的情况下)。有时这两个会引用相同的东西,在这种情况下,两个过程将被认为是对应的。换句话说,来自一个进程的输出将直接流入另一个进程的输入。表1显示了该论文的一些例子。</p><table><thead><tr><th>Operation</th><th>Explanation</th></tr></thead><tbody><tr><td>cardreader?</td><td>cardimage From cardreader, read a card and assign its value (an array of characters) to the variable cardimage.</td></tr><tr><td>lineprinter!</td><td>lineimage To lineprinter, send the value of lineimage for printing.</td></tr><tr><td>X?(x, y)</td><td>From process named X, input a pair of values and assign them to x and</td></tr><tr><td>y.</td><td></td></tr><tr><td>DIV!(3*a+b, 13)</td><td>To process DIV, output the two specified values.</td></tr><tr><td>→ east!c] east.</td><td>The repetition terminates when the process west terminates.</td></tr></tbody></table><p>很明显,与 Go 的 channel 具有相似之处。请注意,在最后一个示例中,来自 <code>east</code> 的输出是如何发送到变量<code>c</code>,而来自 <code>east</code> 的输入是从同一变量接收的。这两个进程相对应。在Hoare关于CSP的第一篇论文中,流程只能通过命名的源和目的地进行通信。他承认,这会导致将代码嵌入库中的问题,因为代码的使用者必须知道输入和输出的名称。他随便提到了注册他所谓的“端口名称”的可能性,其中名称可以在并行命令的头部声明,我们可能会将其识别为命名参数和命名返回值。</p><p>该语言还使用了所谓的守卫命令( <em>guarded</em> ),Edgar Dijkstra 在 1974 年写的一篇文章 “<a href="https://dl.acm.org/citation.cfm?doid=360933.360975" target="_blank" rel="noopener">Guarded commands, nondeterminacy and formal derivation of programs</a>”中提到了:一个守卫的命令只是一个左右手边的声明,用 <code>→</code> 分开。箭头左边作为右边的条件或守卫,如果左边是假的,或者在命令的情况下,返回假或退出,右边将永远不会被执行。将这些与 Hoare 的 I/O 命令相结合,为 Hoare 的通信过程奠定了基础,从而为 Go 的Channel 奠定了基础。</p><p>内存同步访问本质上并不坏。我们将在“Go的并发哲学”中看到,有时共享内存在某些情况下是合适的,即使在Go中也是如此。但是,共享内存模型可能难以正确使用 - 尤其是在大型复杂程序中。正是由于这个原因,并发被认为是Go的优势之一:它从一开始就是基于CSP的原则而构建的,因此它易于阅读,编写和推理。</p><h2 id="Go的并发设计哲学"><a href="#Go的并发设计哲学" class="headerlink" title="Go的并发设计哲学"></a>Go的并发设计哲学</h2><p>CSP曾经并且是Go设计的重要组成部分;但 Go 还支持更传统的通过内存同步访问编写并发代码的方法以及遵循该技术的原语。<a href="https://golang.org/pkg/sync/" target="_blank" rel="noopener">sync</a> 和其他包中的结构和方法允许您执行加锁,创建资源池以及抢占 goroutine 等。</p><p>这种在CSP原语和内存同步访问之间进行选择的能力对程序员来说非常有用,因为它可以让程序员更好地控制选择写入什么样的并发代码来解决问题,但它也可能有点令人困惑。Go 语言的新手常常会得到这样的印象: CSP 的并发风格被认为是在 Go 中编写并发代码的唯一方法。例如,在同步包的文档中:</p><blockquote><p>Package <em>sync</em> provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.</p></blockquote><p>在 <a href="https://golang.org/doc/faq" target="_blank" rel="noopener">language FAQ</a></p><blockquote><p>Regarding mutexes, the sync package implements them, but we hope Go programming style will encourage people to try higher-level techniques. In particular, consider structuring your program so that only one goroutine at a time is ever responsible for a particular piece of data. Do not communicate by sharing memory. Instead, share memory by communicating.</p></blockquote><p>还有许多文章,讲座和访谈,其中Go核心团队的各个成员支持 CSP 样式而不是像 <code>sync.Mutex</code> 这样的原语。</p><p>因此,完全可以理解为什么Go团队选择公开内存访问同步原语。但令人困惑的是,你会看到频繁出现的同步原语,看到人们抱怨过度使用频道,还听到一些Go团队成员说可以使用它们。以下是关于此事的 Go <a href="https://github.com/golang/go/wiki/MutexOrChannel" target="_blank" rel="noopener">Wiki</a> 的引用:</p><blockquote><p>One of Go’s mottos is “Share memory by communicating, don’t communicate by sharing memory.” That said, Go does provide traditional locking mechanisms in the sync package. Most locking issues can be solved using either channels or traditional locks. So which should you use? Use whichever is most expressive and/or most simple.</p><p>Go的一个主题是“通过通信共享内存,不通过共享内存进行通信。”</p><p>也就是说,Go确实在同步包中提供了传统的锁定机制。</p><p>使用通道或传统锁可以解决大多数加锁问题。</p><p>那应该用哪个?使用最具表现力和/或最简单的表达方式。</p></blockquote><p><img src="https://shenheng.xyz/img/2018112501.png" alt="决策树"></p><p>决策树</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本节主要讲解了Go的并发哲学,并且为什么Go的并发那么优秀:基于CSP原则,然后讲了CSP是什么和它的设计原则,最后说明如何选择 “通道” 还是 “传统锁”!</p>]]></content>
<summary type="html">
<p>本系列阅读笔记是基于 Golang 经典图书 <em>Concurrency in GO</em> ,主要有6个章节:</p>
<ul>
<li>An Introduction to Concurrency</li>
<li><strong>Modeling Your Co
</summary>
<category term="Golang" scheme="https://readailib.com/tags/Golang/"/>
<category term="Concurrency" scheme="https://readailib.com/tags/Concurrency/"/>
</entry>
<entry>
<title>kubernetes学习笔记|利用 Kubeflow 管理 Tensorflow 程序</title>
<link href="https://readailib.com/2018/11/23/kubernetes/kubeflow-deployment/"/>
<id>https://readailib.com/2018/11/23/kubernetes/kubeflow-deployment/</id>
<published>2018-11-22T16:00:00.000Z</published>
<updated>2019-03-21T02:31:36.644Z</updated>
<content type="html"><![CDATA[<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://kairen.github.io/2018/03/15/tensorflow/kubeflow/" target="_blank" rel="noopener">https://kairen.github.io/2018/03/15/tensorflow/kubeflow/</a></li></ul><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>每个人是不是都想拥有一个属于自己的 “人工智能实验室”,他有哪些特点呢?主要包括以下几个特点:</p><ol><li>支持 tensorflow/torch/keras等等主流深度学习库</li><li>用户界面友好</li><li>支持分布式训练</li><li>支持 GPU 训练</li><li>支持快速产生人工智能产品原型</li></ol><p>Kubeflow 是 Google 开源的机器学习工具,目的是简化 Kubernetes 上运行机器学习的工程,使之更简单、可携带与可扩展。Kubeflow 目标不是在于重建其他服务,而是提供一个最佳的开发系统用于部署到各种基础设施架构中,另外由于使用kubernetes 来作为基础,因此只要有 Kubernetes 的地方,都能夠执行 Kubeflow。</p><p>该工具可以用来建立如下功能:</p><ol><li>用于建立与管理工具(IDE) Jupyter notebook 的 JupyterHub。</li><li>可以使用 CPU 或 GPU,并可以通过单一设定来调整单个数据集的 Tensorflow Training Controller。</li><li>用 TensorFlow Serving 容器來提供模型服务。</li></ol><p>Kubeflow 目标是通过 Kubernetes 的特性使机器学习更加简单和快捷:</p><ol><li>在不同 IaaS 上实现简单可重复的携带性部署(Laptop <-> ML rig <-> Training cluster <-> Production cluster)。</-></-></-></li><li>管理与部署松耦合的微服务。</li><li>可根据需要来自动扩缩容</li></ol><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>本次安装基于 1.12.1 版本的 kube*,我的安装比较简单粗暴!适合有运维经验的同学,如果有疑问可以联系我或者访问上面给出的链接?。</p><p>本次安装假定你的集群已经运行,且稳定!</p><blockquote><p>注意:这里只给出 CPU 版本</p></blockquote><p>安装 ksonnet 0.9.2,请参考以下:</p><pre><code class="bash">$ wget https://github.com/ksonnet/ksonnet/releases/download/v0.9.2/ks_0.9.2_linux_amd64.tar.gz$ tar xvf ks_0.9.2_linux_amd64.tar.gz$ sudo cp ks_0.9.2_linux_amd64/ks /usr/local/bin/$ ks versionksonnet version: 0.9.2jsonnet version: v0.9.5client-go version: 1.8</code></pre><h2 id="部署-Kubeflow"><a href="#部署-Kubeflow" class="headerlink" title="部署 Kubeflow"></a>部署 Kubeflow</h2><p>本节將说明如歌利用 ksonnet 来部署 Kubeflow 到 Kubernetes 集群中。首先在 master 节点初始化 ksonnet 应用程序目录:</p><pre><code class="bash">$ ks init my-kubeflow</code></pre><blockquote class="colorquote warning"><p>如果遇到以下问题的話,可以自己建立 GitHub Token 来存取 GitHub API,请参考 <a href="https://ksonnet.io/docs/tutorial#troubleshooting-github-rate-limiting-errors" target="_blank" rel="noopener">Github rate limiting errors</a>。</p><pre><code class="bash">ERROR GET https://api.github.com/repos/ksonnet/parts/commits/master: 403 API rate <span class="built_in">limit</span> exceeded <span class="keyword">for</span> 122.146.93.152.</code></pre></blockquote><p>接着安装 Kubeflow 套件到上面创建的应用程序目录下:</p><pre><code class="bash">$ cd my-kubeflow$ ks registry add kubeflow github.com/kubeflow/kubeflow/tree/master/kubeflow$ VERSION=v0.1.2$ ks pkg install kubeflow/core@${VERSION}$ ks pkg install kubeflow/tf-serving@${VERSION}$ ks pkg install kubeflow/tf-job@${VERSION}</code></pre><p>然后建立 Kubeflow 核心组件,该组件包含 JupyterHub 和 TensorFlow job controller:</p><pre><code class="bash">$ kubectl create namespace kubeflow$ kubectl create clusterrolebinding tf-admin --clusterrole=cluster-admin --serviceaccount=default:tf-job-operator$ ks generate core kubeflow-core --name=kubeflow-core --namespace=kubeflow$ ks env add kubeflow$ ks env set kubeflow --namespace kubeflow$ ks apply kubeflow -c kubeflow-core$ PODNAME=`kubectl get pods --namespace=kubeflow --selector="app=tf-hub" --output=template --template="{{with index .items 0}}{{.metadata.name}}{{end}}"`</code></pre><p>这样就可以访问了!如果想要在集群外部访问,修改 <code>tf-hub-lb service</code> 的类型为 <code>NodePort</code> 或者 <code>LoadBalancer</code> 即可!</p><pre><code class="bash">$ kubectl get service tf-hub-lb -nkubeflowNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEtf-hub-lb LoadBalancer 10.109.167.223 172.16.3.4 8000:30962/TCP 34m</code></pre><p><img src="https://shenheng.xyz/img/kubeflow.png" alt="img"></p><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p><img src="https://shenheng.xyz/img/kubeflow-test.png" alt="img"></p><h1 id="Bug"><a href="#Bug" class="headerlink" title="Bug"></a>Bug</h1><blockquote><p>could not post report: HTTPSConnectionPool(host=’kubernaut.io’, port=443): Max</p></blockquote><p>For those who just got started with kubernetes in general. Here’s what you need to do</p><pre><code class="bash">$ kubectl edit configmap coredns -n kube-system</code></pre><p>NOTE: the changes are permanent and will survive a reboot of the node.</p><p>You’ll need to add 8.8.8.8 at two places, upstream and proxy</p><pre><code class="yaml">...apiVersion: v1data: Corefile: | .:53 { errors health kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure upstream 8.8.8.8 8.8.4.4 fallthrough in-addr.arpa ip6.arpa } prometheus :9153 proxy . /etc/resolv.conf 8.8.8.8 8.8.4.4 cache 30 reload }...</code></pre>]]></content>
<summary type="html">
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://kairen.github.io/2018/03/15/tensorflow/kubeflow
</summary>
<category term="kubeflow Installation" scheme="https://readailib.com/categories/kubeflow-Installation/"/>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
<category term="Deep Learning" scheme="https://readailib.com/tags/Deep-Learning/"/>
</entry>
<entry>
<title>K8s集群部署</title>
<link href="https://readailib.com/2018/11/15/kubernetes/kubernetes-install-note/"/>
<id>https://readailib.com/2018/11/15/kubernetes/kubernetes-install-note/</id>
<published>2018-11-15T08:41:31.000Z</published>
<updated>2019-03-13T10:59:08.507Z</updated>
<content type="html"><![CDATA[<h2 id="重点参考"><a href="#重点参考" class="headerlink" title="重点参考"></a>重点参考</h2><ul><li><a href="http://blog.51cto.com/douya/1945382" target="_blank" rel="noopener">http://blog.51cto.com/douya/1945382</a></li><li><a href="http://www.cnblogs.com/burningTheStar/p/7865998.html" target="_blank" rel="noopener">http://www.cnblogs.com/burningTheStar/p/7865998.html</a></li></ul><h2 id="实验环境"><a href="#实验环境" class="headerlink" title="实验环境"></a>实验环境</h2><p>采用 CentOS7.4 minimual,docker 1.13,kubeadm 1.10.0,etcd 3.0, k8s 1.10.0 我们这里选用两个个节点搭建一个实验环境。</p><pre><code class="bash">192.168.59.133 k8smaster192.168.59.150 k8snode1</code></pre><p>下面开始准备环境。</p><ol><li><p>配置好各节点hosts文件</p><pre><code class="bash">$ cat>> /etc/hosts << EOF192.168.59.133 k8smaster192.168.59.150 k8snode1EOF</code></pre></li><li><p>关闭系统防火墙</p><pre><code class="bash">$ systemctl stop firewalld && systemctl disable firewalld</code></pre></li><li><p>关闭SElinux</p><pre><code class="bash">$ setenforce 0$ sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config</code></pre><p>重启生效!</p></li><li><p>关闭swap</p><pre><code class="bash">$ swapoff -a</code></pre></li><li><p>配置系统内核参数</p><p>使流过网桥的流量也进入iptables/netfilter框架中,在/etc/sysctl.conf中添加以下配置:</p><pre><code class="bash">net.bridge.bridge-nf-call-iptables = 1net.bridge.bridge-nf-call-ip6tables = 1</code></pre><p>然后执行下面的命令:</p><pre><code class="bash"> $ sysctl -p</code></pre></li></ol><h2 id="使用kubeadm安装"><a href="#使用kubeadm安装" class="headerlink" title="使用kubeadm安装"></a>使用kubeadm安装</h2><ol><li><p>首先配置阿里K8S YUM源</p><pre><code class="bash">$ cat <<EOF > /etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64enabled=1gpgcheck=0EOF$ yum -y install epel-release$ yum clean all$ yum makecache</code></pre></li><li><p>安装kubeadm和相关工具包</p><pre><code class="bash">$ yum -y install docker kubelet kubeadm kubectl kubernetes-cni</code></pre></li><li><p>启动Docker与kubelet服务</p><pre><code class="bash">$ systemctl enable docker && systemctl start docker$ systemctl enable kubelet && systemctl start kubelet</code></pre><blockquote><p>提示:此时kubelet的服务运行状态是异常的,因为缺少主配置文件kubelet.conf。但可以暂不处理,因为在完成Master节点的初始化后才会生成这个配置文件。</p></blockquote></li><li><p>下载K8S相关镜像(可以直接看5)</p><p>因为无法直接访问gcr.io下载镜像,所以需要配置一个国内的容器镜像加速器 配置一个阿里云的加速器:(可省略)</p><p>登录 <a href="https://cr.console.aliyun.com/" target="_blank" rel="noopener">https://cr.console.aliyun.com/</a></p><p>在页面中找到并点击镜像加速按钮,即可看到属于自己的专属加速链接,选择Centos版本后即可看到配置方法。</p><p>提示: 在阿里云上使用 Docker 并配置阿里云镜像加速器,可能会遇到 daemon.json 导致 docker daemon 无法启动的问题,可以通过以下方法解决。</p><p>你需要的是编辑 </p><pre><code class="bash">$ vim /etc/sysconfig/docker</code></pre><p>然后</p><pre><code class="bash">OPTIONS='--selinux-enabled --log-driver=journald --registry-mirror=http://xxxx.mirror.aliyuncs.com'</code></pre><p><code>registry-mirror</code> 输入你的镜像地址 </p><p>最后 </p><pre><code class="bash">$ service docker restart</code></pre><p>重启 daemon. 然后 </p><pre><code class="bash">$ ps aux |grep docker</code></pre><p>然后你就会发现带有镜像的启动参数了。</p></li><li><p>下载K8S相关镜像</p><p>OK,解决完加速器的问题之后,开始下载k8s相关镜像,下载后将镜像名改为k8s.gcr.io/开头的名字,以便kubeadm识别使用。</p><pre><code class="bash">#!/bin/bashimages=(kube-proxy-amd64:v1.10.0 kube-scheduler-amd64:v1.10.0 kube-controller-manager-amd64:v1.10.0 kube-apiserver-amd64:v1.10.0etcd-amd64:3.1.12 pause-amd64:3.1 kubernetes-dashboard-amd64:v1.8.3 k8s-dns-sidecar-amd64:1.14.8 k8s-dns-kube-dns-amd64:1.14.8k8s-dns-dnsmasq-nanny-amd64:1.14.8)for imageName in ${images[@]} ; do docker pull keveon/$imageName docker tag keveon/$imageName k8s.gcr.io/$imageName docker rmi keveon/$imageNamedone</code></pre><p>上面的shell脚本主要做了3件事,下载各种需要用到的容器镜像、重新打标记为符合k8s命令规范的版本名称、清除旧的容器镜像。</p><blockquote><p>提示:镜像版本一定要和kubeadm安装的版本一致,否则会出现time out问题。</p></blockquote></li><li><p>初始化安装K8S Master</p><p>执行上述shell脚本,等待下载完成后,执行<code>kubeadm init</code>.</p><pre><code class="bash">$ kubeadm init --token=102952.1a7dd4cc8d1f4cc5 --kubernetes-version 1.10.0··············································································kubeadm join 192.168.59.133:6443 --token 102952.1a7dd4cc8d1f4cc5 --discovery-token-ca-cert-hash sha256:6f1a864c6f530351f9ec7a42f74404497fcbe91ad7bf726bffd8cb3e3c333a38</code></pre><p>最后会出现这个信息,在这个信息会非常有用</p><pre><code class="bash">$ kubeadm join 192.168.59.133:6443 --token 102952.1a7dd4cc8d1f4cc5 --discovery-token-ca-cert-hash sha256:6f1a864c6f530351f9ec7a42f74404497fcbe91ad7bf726bffd8cb3e3c333a38</code></pre><blockquote><p>提示:选项–kubernetes-version=v1.10.0是必须的,否则会因为访问google网站被墙而无法执行命令。这里使用v1.10.0版本,刚才前面也说到了下载的容器镜像版本必须与K8S版本一致否则会出现time out。</p></blockquote><p>上面的命令大约需要1分钟的过程,期间可以观察下tail -f /var/log/message日志文件的输出,掌握该配置过程和进度。上面最后一段的输出信息保存一份,后续添加工作节点还要用到。</p></li><li><p>配置kubectl认证信息 # 对于非root用户</p><pre><code class="bash">$ mkdir -p $HOME/.kube$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config$ sudo chown $(id -u):$(id -g) $HOME/.kube/config</code></pre><p># 对于root用户</p><pre><code class="bash">$ export KUBECONFIG=/etc/kubernetes/admin.conf</code></pre><p>也可以直接放到~/.bash_profile</p><pre><code class="bash">$ echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile</code></pre></li><li><p>安装flannel网络</p><pre><code class="bash">$ mkdir -p /etc/cni/net.d/$ cat <<EOF> /etc/cni/net.d/10-flannel.conf{“name”: “cbr0”,“type”: “flannel”,“delegate”: {“isDefaultGateway”: true}}EOF$ mkdir /usr/share/oci-umount/oci-umount.d -p$ mkdir /run/flannel/$ cat <<EOF> /run/flannel/subnet.envFLANNEL_NETWORK=10.244.0.0/16FLANNEL_SUBNET=10.244.1.0/24FLANNEL_MTU=1450FLANNEL_IPMASQ=trueEOFkubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.9.1/Documentation/kube-flannel.yml</code></pre></li><li><p>让node1加入集群</p><p>在node1节点上分别执行kubeadm join命令,加入集群:</p><pre><code class="bash">$ kubeadm join 192.168.59.133:6443 --token 102952.1a7dd4cc8d1f4cc5 --discovery-token-ca-cert-hash sha256:6f1a864c6f530351f9ec7a42f74404497fcbe91ad7bf726bffd8cb3e3c333a38[preflight] Running pre-flight checks. [WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service' [WARNING FileExisting-crictl]: crictl not found in system pathSuggestion: go get github.com/kubernetes-incubator/cri-tools/cmd/crictl[discovery] Trying to connect to API Server "10.0.100.202:6443"[discovery] Created cluster-info discovery client, requesting info from "https://10.0.100.202:6443"[discovery] Requesting info from "https://10.0.100.202:6443" again to validate TLS against the pinned public key[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "10.0.100.202:6443"[discovery] Successfully established connection with API Server "10.0.100.202:6443"This node has joined the cluster:* Certificate signing request was sent to master and a response was received.* The Kubelet was informed of the new secure connection details.Run 'kubectl get nodes' on the master to see this node join the cluster.</code></pre><blockquote><p>提示:细心的童鞋应该会发现,这段命令其实就是前面K8S Matser安装成功后我让你们保存的那段命令。</p></blockquote><p>默认情况下,Master节点不参与工作负载,但如果希望安装出一个All-In-One的k8s环境,则可以执行以下命令,让Master节点也成为一个Node节点:</p><pre><code class="bash">$ kubectl taint nodes --all node-role.kubernetes.io/master-</code></pre></li><li><p>验证K8S Master是否搭建成功</p><pre><code class="bash"># 查看节点状态kubectl get nodes# 查看pods状态kubectl get pods --all-namespaces# 查看K8S集群状态kubectl get cs</code></pre></li><li><p>安装 dashboard</p><p>具体参考:<a href="https://github.com/kubernetes/dashboard" target="_blank" rel="noopener">https://github.com/kubernetes/dashboard</a></p><pre><code class="bash">$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml</code></pre></li></ol><p><img src="https://shenheng.xyz/img/k8-1.png" alt="img"></p><h2 id="常见错误"><a href="#常见错误" class="headerlink" title="常见错误"></a>常见错误</h2><ol><li><p>如果出现NotReady,说明网络问题,需要创建一个网络服务,在这里需要先下载一个文件:</p></li><li><p>到 <a href="https://github.com/weaveworks/weave/releases" target="_blank" rel="noopener">https://github.com/weaveworks/weave/releases</a> 下载</p><pre><code class="bash"> $ wget https://github.com/weaveworks/weave/releases/download/v2.3.0/weave-daemonset-k8s-1.7.yaml</code></pre><p> 然后执行</p><pre><code class="bash"> $ kubectl create -f weave-daemonset-k8s-1.7.yaml</code></pre><p> 现在可以了!?</p><ul><li>或者使用flannel网络!</li></ul></li><li><p>安装时候最常见的就是time out,因为K8S镜像在国外,所以我们在前面就说到了提前把他下载下来,可以用一个国外机器采用habor搭建一个私有仓库把镜像都download下来。</p><pre><code>[root@k8smaster ~]# kubeadm init[init] Using Kubernetes version: v1.10.0[init] Using Authorization modes: [Node RBAC][preflight] Running pre-flight checks. [WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service' [WARNING FileExisting-crictl]: crictl not found in system pathSuggestion: go get github.com/kubernetes-incubator/cri-tools/cmd/crictl[preflight] Starting the kubelet service[certificates] Generated ca certificate and key.[certificates] Generated apiserver certificate and key.[certificates] apiserver serving cert is signed for DNS names [k8smaster kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.0.100.202][certificates] Generated apiserver-kubelet-client certificate and key.[certificates] Generated etcd/ca certificate and key.[certificates] Generated etcd/server certificate and key.[certificates] etcd/server serving cert is signed for DNS names [localhost] and IPs [127.0.0.1][certificates] Generated etcd/peer certificate and key.[certificates] etcd/peer serving cert is signed for DNS names [k8smaster] and IPs [10.0.100.202][certificates] Generated etcd/healthcheck-client certificate and key.[certificates] Generated apiserver-etcd-client certificate and key.[certificates] Generated sa key and public key.[certificates] Generated front-proxy-ca certificate and key.[certificates] Generated front-proxy-client certificate and key.[certificates] Valid certificates and keys now exist in "/etc/kubernetes/pki"[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"[controlplane] Wrote Static Pod manifest for component kube-apiserver to "/etc/kubernetes/manifests/kube-apiserver.yaml"[controlplane] Wrote Static Pod manifest for component kube-controller-manager to "/etc/kubernetes/manifests/kube-controller-manager.yaml"[controlplane] Wrote Static Pod manifest for component kube-scheduler to "/etc/kubernetes/manifests/kube-scheduler.yaml"[etcd] Wrote Static Pod manifest for a local etcd instance to "/etc/kubernetes/manifests/etcd.yaml"[init] Waiting for the kubelet to boot up the control plane as Static Pods from directory "/etc/kubernetes/manifests".[init] This might take a minute or longer if the control plane images have to be pulled.Unfortunately, an error has occurred: timed out waiting for the conditionThis error is likely caused by: - The kubelet is not running - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled) - Either there is no internet connection, or imagePullPolicy is set to "Never", so the kubelet cannot pull or find the following control plane images: - k8s.gcr.io/kube-apiserver-amd64:v1.10.0 - k8s.gcr.io/kube-controller-manager-amd64:v1.10.0 - k8s.gcr.io/kube-scheduler-amd64:v1.10.0 - k8s.gcr.io/etcd-amd64:3.1.12 (only if no external etcd endpoints are configured)If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands: - 'systemctl status kubelet' - 'journalctl -xeu kubelet'couldn't initialize a Kubernetes cluster</code></pre><p>那出现这个问题大部分原因是因为安装的K8S版本和依赖的K8S相关镜像版本不符导致的,关于这部分排错可以查看/var/log/message 我们在文章开始安装的时候也提到了要多看日志。 还有些童鞋可能会说,那我安装失败了,怎么清理环境重新安装啊?下面教大家一条命令:</p><pre><code>kubeadm reset</code></pre></li><li><p>我们可能在使用kubeadm 安装完会出现下面的问题:</p><pre><code>The connection to the server localhost:8080 was refused - did you specify the right host or port?</code></pre><p>解决方案如下:</p><pre><code>$ sudo cp /etc/kubernetes/admin.conf $HOME/$ sudo chown $(id -u):$(id -g) $HOME/admin.conf$ export KUBECONFIG=$HOME/admin.conf</code></pre></li><li><p>访问 apiserver 或者 dashboard 出现 <code>"<h3>Unauthorized</h3>"</code></p><p>可以查看当前的是否存在 <code>kubectl proxy</code> 进程</p><pre><code>$ ps -aux | grep 'kubectl proxy'</code></pre><p>如果有,杀死该进程,执行下面的命令:</p><pre><code>$ kubectl proxy --address 0.0.0.0 --accept-hosts '.*'</code></pre></li><li><p>可能在初始化集群出现下面的错误</p><pre><code>[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1 [ERROR Swap]: running with swap on is not supported. Please disable swap[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`</code></pre><p>处理方法:</p><pre><code>$ echo "1" >/proc/sys/net/bridge/bridge-nf-call-iptables</code></pre></li><li><p>可能出现 swap 不支持错误</p><pre><code>[ERROR Swap]: running with swap on is not supported. Please disable swap[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`</code></pre><p>关闭它:</p><pre><code>$ swapoff -a</code></pre></li><li><p>在初始化集群的时候,可能会出现下面的错误:</p><pre><code class="bash">[kubelet-check] It seems like the kubelet isn't running or healthy.[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10255/healthz' failed with error: Get http://localhost:10255/healthz: dial tcp [::1]:10255: getsockopt: connection refused.</code></pre><p>很容易看出 <code>kubelet</code> 异常,<strong>centos</strong> 的解决方案如下:</p><blockquote class="colorquote info"><p>On CentOS I can add these options in <code>/etc/systemd/system/kubelet.service.d/10-kubeadm.conf</code>:</p><pre><code class="bash"><span class="comment"># egrep KUBELET_CGROUP_ARGS= /etc/systemd/system/kubelet.service.d/10-kubeadm.conf</span>Environment=<span class="string">"KUBELET_CGROUP_ARGS=--cgroup-driver=cgroupfs --runtime-cgroups=/systemd/system.slice --kubelet-cgroups=/systemd/system.slice"</span></code></pre></blockquote><p>然后重启 <code>kubelet</code> 服务即可。</p><pre><code class="bash">$ systemctl daemon-reload$ systemctl restart kubelet</code></pre></li></ol>]]></content>
<summary type="html">
<h2 id="重点参考"><a href="#重点参考" class="headerlink" title="重点参考"></a>重点参考</h2><ul>
<li><a href="http://blog.51cto.com/douya/1945382" target="_b
</summary>
<category term="Kubernetes Installation" scheme="https://readailib.com/categories/Kubernetes-Installation/"/>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
<category term="Kubernetes" scheme="https://readailib.com/tags/Kubernetes/"/>
</entry>
<entry>
<title>Go语言中的并发机制系列-1|并发简介</title>
<link href="https://readailib.com/2018/11/13/golang/concurracy-introduction-use-golang/"/>
<id>https://readailib.com/2018/11/13/golang/concurracy-introduction-use-golang/</id>
<published>2018-11-13T11:57:34.000Z</published>
<updated>2019-03-13T12:02:15.542Z</updated>
<content type="html"><![CDATA[<p>本系列阅读笔记是基于 Golang 经典图书 <em>Concurrency in GO</em> ,主要有6个章节:</p><ul><li><strong>An Introduction to Concurrency</strong></li><li>Modeling Your Code: Communicating Sequential Processes</li><li>Go’s Concurrency Building Blocks</li><li>Concurrency Patterns in Go</li><li>Concurrency at Scale</li><li>Goroutines and the Go Runtime</li></ul><p>本部分主要介绍第一章:<em>An Introduction to Concurrency</em> ,并发的基本概念,程序中的并发若要控制为什么难?以及有哪些难题?</p><p>大家都知道,程序中若要实现并发或者程序员们要写对并发代码是很难做正确的,通常需要几次迭代才能使并发代码按照预期工作。即便如此,有的支持并发的系统虽然已经非常成熟了,但是系统内部仍存在一些未被发现或者忽略的 bug。这就是为什么并发那么难搞的原因。</p><h2 id="竞争条件(Race-Condition)"><a href="#竞争条件(Race-Condition)" class="headerlink" title="竞争条件(Race Condition)"></a>竞争条件(Race Condition)</h2><p>当两个或多个操作必须以正确的顺序执行时会发生竞争条件,但是程序尚未写入以确保保持此顺序。</p><p>大多数情况下,我们遇见的是<strong>数据竞争</strong>,在所谓的数据竞争中,其中一个并发操作尝试读取某一变量,而在某个未确定的时间,另一个并发操作试图写入同一个变量。</p><p>下面是一个典型的例子</p><pre><code class="go">var data intgo func() { data++}()if data == 0 { fmt.Printf("the value is %v.\n", data)}</code></pre><blockquote><p>在 Go 语言中,我们在某个函数前使用 go 关键字,表明你要创建一个 goroutine。</p></blockquote><p>这里,第3行和第5行都试图访问变量 <code>data</code> ,但无法保证执行的顺序。因此运行此代码有三种可能的结果:</p><ul><li>什么也不打印. 第 3 行 在 第 5 行 之前执行</li><li>打印 <code>the value is 0</code>. 在这种情况下, 第 5 行和第 6 行在第 3 行之前执行。</li><li>打印 <code>the value is 1</code>. 在这种情况下, 第 5 行在第 3 行之前执行,但第 3 行在第 6 行之前执行。</li></ul><p>为什么会产生这种结果?往往是因为程序员在编写程序时,并未考虑程序所出现的所有可能结果或者并未考虑完整。</p><p>有时候在出现可能并发的时候,大家(包括我)可能都会第一时间想到:我给它 <code>sleep</code> 一下不就行了。<strong>但是</strong>,这是非常糟糕的想法。实际上,一些开发人员在整个代码中插入 <code>sleep</code> 的陷阱,它似乎解决了并发问题。现在让我们在前面的程序中尝试:</p><pre><code class="go">var data intgo func() { data++ }()time.Sleep(1*time.Second) // This is bad!if data == 0 { fmt.Printf("the value is %v.\n" data)}</code></pre><p>解决数据竞争了吗?没有。事实上,上面出现的三个结果仍然可能发生,并且更不加确定是哪一个结果了!我们在调用我们的goroutine和检查变量值之间睡得越久,我们的程序越接近实现正确性,这种概率渐近地接近逻辑正确性;但它永远不会在逻辑上正确。除此之外,我们算法的效率越来越低。现在必须 <code>sleep</code> 一秒钟才能让我们更有可能看不到我们的数据竞争。如果我们使用了正确的方法,我们可能根本不必等待,或者等待可能只有一微秒。 <strong>结论</strong>:将 <code>sleep</code> 引入代码可以是调试并发程序的一种快捷方法,但它们不是解决方案!</p><h2 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a>原子性</h2><p>当某些东西被认为是 <strong>原子</strong> 的,或具有原子属性时,这意味着在它运行的上下文中,它是不可分割的或不可中断的。</p><p>那么这究竟是什么意思,为什么在使用并发代码时知道这一点很重要?第一件非常重要的事情是“上下文”这个词。在某种情况下,某些东西可能是原子的,而不是另一种。在程序流程上下文中是原子的操作但在操作系统的上下文中可能不是原子的;在操作系统的上下文中是原子的操作但在您的机器的上下文中可能不是原子的;并且在您的机器上下文中是原子的操作但在您的应用程序的上下文中可能不是原子的。换句话说,操作的原子性可以根据当前定义的范围而改变。这个事实既可以支持也可以反对你!</p><p>在考虑原子性时,通常需要做的第一件事就是定义上下文或范围,操作将被认为是原子的。一切都从此开始。</p><p><strong>“不可分割”</strong> 和 <strong>“不间断”</strong> 这两个术语。这些术语意味着在你定义的上下文中,程序要不然完全执行,要不然完全不执行。下面看个例子:</p><pre><code class="go"> i++</code></pre><p>这是一个非常简单的例子,几乎所有学过程序语言设计的同学都知道他是什么意思,但它很容易证明原子性的概念。它可能看起来像原子,但简短的分析揭示了几个操作:</p><ul><li>访问变量 <code>i</code> 的值.</li><li>将变量 <code>i</code> 加 1.</li><li>将加 1 之后i储存在变量i中.</li></ul><p>虽然这些操作中的每一个都是原子的,但三者的组合可能不是,具体取决于您的上下文。这揭示了原子操作的一个有趣特性:组合它们不一定产生更大的原子操作。使操作原子化取决于您希望它在哪个上下文中是原子的。如果您的上下文是没有并发进程的程序,则此代码在该上下文中是原子的。如果你的上下文是一个不会将我暴露给其他goroutine的goroutine,那么这段代码就是原子的。</p><p>那我们为什么要关心呢?原子性很重要,因为如果某些东西是原子的,那么隐含地它在并发上下文中是安全的。这使我们能够编写逻辑上正确的程序,并且 - 我们稍后会看到 - 甚至可以作为优化并发程序的一种方法。</p><h2 id="同步访问内存"><a href="#同步访问内存" class="headerlink" title="同步访问内存"></a>同步访问内存</h2><p>假设我们有一个数据竞争:两个并发进程正在尝试访问相同的内存区域,并且它们访问内存的方式不是原子的。我们之前的简单数据竞争示例将通过一些修改很好地完成:</p><pre><code class="go">var data intgo func() { data++}()if data == 0 { fmt.Println("the value is 0.")} else { fmt.Printf("the value is %v.\n", data)}</code></pre><p>在这里添加了一个 <code>else</code> 子句,这样无论数据的值如何,我们总会获得一些输出。请记住,在编写存在数据竞争的程序时,输出将完全不确定。</p><p>实际上,程序中需要对共享资源进行独占访问的代码被称作 <strong>“临界区”</strong>。这被称为关键部分。在这个例子中,我们有三个临界区:</p><ul><li>goroutine,开启一个 goroutine</li><li><code>if</code> 语句, 检查变量的值是否为0.</li><li><code>fmt.Printf</code> 语句,格式化打印我们的数据.</li></ul><p>为了保证能够互斥访问,我们需要保护这些 “临界区” 代码,在 Golang 中是如何保护的呢?看下面的代码:</p><pre><code class="go">var memoryAccess sync.Mutexvar value intgo func() { memoryAccess.Lock() value++ memoryAccess.Unlock()}()memoryAccess.Lock()if value == 0 { fmt.Printf("the value is %v.\n", value)} else { fmt.Printf("the value is %v.\n", value)}memoryAccess.Unlock()</code></pre><p>无论何时开发人员想要访问数据变量的内存 (shared),他们必须首先调用Lock,当访问完成后,他们必须调用Unlock退出该临界区。这两个语句之间的代码可以认为它具有对数据的独占访问权。</p><h3 id="死锁,活锁和饥饿"><a href="#死锁,活锁和饥饿" class="headerlink" title="死锁,活锁和饥饿"></a>死锁,活锁和饥饿</h3><p>前面几节都讨论了程序的正确性,如果这些问题得到正确管理,你的程序永远不会给出错误的答案。不幸的是,即使您成功处理了这些类问题,还有另一类需要解决的问题:<strong>死锁,活锁和饥饿</strong>。这些问题都与确保您的计划始终有用的事情有关。如果处理不当,您的程序可能会进入一个完全停止运行的状态。</p><h3 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h3><p>我们先看一段程序。</p><pre><code class="go">type value struct { mu sync.Mutex value int}var wg sync.WaitGroupprintSum := func(v1, v2 *value) { defer wg.Done() v1.mu.Lock() defer v1.mu.Unlock() time.Sleep(2*time.Second) v2.mu.Lock() defer v2.mu.Unlock() fmt.Printf("sum=%v\n", v1.value + v2.value)}var a, b valuewg.Add(2)go printSum(&a, &b)go printSum(&b, &a)wg.Wait()</code></pre><ul><li>第 8 行,进入临界区.</li><li>第 9 行,这里使用 <code>defer</code> 语句在 <code>printSum</code> 返回之前退出临界区。</li><li>第 10 行, 我们 <code>sleep</code> 了一段时间来模拟工作(并触发死锁)。</li></ul><p>运行结果:</p><pre><code class="go">fatal error: all goroutines are asleep - deadlock!</code></pre><p><img src="https://shenheng.xyz/img/2018112401.png" alt="Figure 1. Demonstration of a timing issue giving rise to a deadlock"></p><p>Figure 1. Demonstration of a timing issue giving rise to a deadlock</p><p><strong>解释</strong>:我们第一次调用 <code>printSum</code> 锁定 <code>a</code> 然后尝试锁定 <code>b</code>,但与此同时我们对 <code>printSum</code> 的第二次调用已锁定 <code>b</code> 并试图锁定 <code>a</code>。两个 goroutine 彼此无限等待。</p><p>当且仅当以下所有条件同时存在于系统中时,才会出现资源上的死锁情况, 下面是产生死锁的四个必要条件<a href="https://shenheng.xyz/posts/2018-11-24-go-routine-1/#fn:https-en-wikiped" target="_blank" rel="noopener">1</a>:</p><ul><li>互斥条件:一个资源每次只能被一个进程使用。</li><li>请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。</li><li>不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。</li><li>循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。</li></ul><p>这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。</p><p><img src="https://shenheng.xyz/img/440px-Process_deadlock.svg.png" alt="Figure 2. 死锁的产生。两个进程都需要资源来继续执行。P1需要额外的资源R1并且拥有资源R2,P2需要额外的资源R2并且拥有R1;两个过程都不能继续"></p><p>Figure 2. 死锁的产生。两个进程都需要资源来继续执行。P1需要额外的资源R1并且拥有资源R2,P2需要额外的资源R2并且拥有R1;两个过程都不能继续</p><p>让我们检查一下我们设计的程序,并确定它是否满足所有四个条件:</p><ol><li><code>printSum</code> 函数确实需要a和b的独占访问权,因此它满足这个条件。</li><li>因为 <code>printSum</code> 函数持有 <code>a</code> 或 <code>b</code> 并且正在等待另一个,所以它满足这个条件。</li><li>我们没有任何办法让我们的 goroutines 被抢先一步。</li><li>我们第一次调用 printSum 正在等待第二次调用,反之亦然。</li></ol><h3 id="活锁"><a href="#活锁" class="headerlink" title="活锁"></a>活锁</h3><p>活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。</p><blockquote><p>A livelock is similar to a deadlock, except that the states of the processes involved in the livelock constantly change with regard to one another, none progressing.</p><p>— <em>wiki</em> <a href="https://en.wikipedia.org/wiki/Deadlock/#Livelock" target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Deadlock/#Livelock</a></p></blockquote><p>你有没有走过走廊走向另一个人?她移到一边让你通过,但你刚刚完成了同样的事情。所以你转移到另一边,但她也做了同样的事情。导致你们两个无法向前走。这种情景就是“程序”出现了“活锁”. 我们将通过下面这个例子来模拟上面这个场景。</p><pre><code class="go">cadence := sync.NewCond(&sync.Mutex{})go func() { for range time.Tick(1*time.Millisecond) { cadence.Broadcast() }}()takeStep := func() { cadence.L.Lock() cadence.Wait() cadence.L.Unlock()}tryDir := func(dirName string, dir *int32, out *bytes.Buffer) bool { fmt.Fprintf(out, " %v", dirName) atomic.AddInt32(dir, 1) takeStep() if atomic.LoadInt32(dir) == 1 { fmt.Fprint(out, ". Success!") return true } takeStep() atomic.AddInt32(dir, -1) return false}var left, right int32tryLeft := func(out *bytes.Buffer) bool { return tryDir("left", &left, out) }tryRight := func(out *bytes.Buffer) bool { return tryDir("right", &right, out) }</code></pre><p><strong>程序解释</strong>:</p><ul><li><code>tryDir</code> 允许一个人尝试向一个方向移动并返回移动是否成功,每个方向都表示为试图朝那个方向移动的人数,dir。</li><li>第 14 行,首先,我们打算通过将该方向增加 1 来朝着一个方向前进。</li><li>第 15 行,为了演示活锁,每个人必须以相同的速度移动。<code>takeStep</code> 模拟各方之间的常量的僵持。</li><li>第 17 行,在这里,这个人意识到他们不能走向这个方向而放弃。我们通过将该方向减1来表明这一点。</li></ul><pre><code class="go">walk := func(walking *sync.WaitGroup, name string) { var out bytes.Buffer defer func() { fmt.Println(out.String()) }() defer walking.Done() fmt.Fprintf(&out, "%v is trying to scoot:", name) for i := 0; i < 5; i++ { if tryLeft(&out) || tryRight(&out) { return } } fmt.Fprintf(&out, "\n%v tosses her hands up in exasperation!", name)}var peopleInHallway sync.WaitGrouppeopleInHallway.Add(2)go walk(&peopleInHallway, "Alice")go walk(&peopleInHallway, "Barbara")peopleInHallway.Wait()</code></pre><p>程序的输出:</p><pre><code class="go">Alice is trying to scoot: left right left right left right left right left rightAlice tosses her hands up in exasperation!Barbara is trying to scoot: left right left right left right left rightleft rightBarbara tosses her hands up in exasperation!</code></pre><h3 id="饥饿"><a href="#饥饿" class="headerlink" title="饥饿"></a>饥饿</h3><p>饥饿指的进程无法访问到它需要的资源而不能继续执行时,引发饥饿最常见资源就是CPU时钟周期。</p><p>当我们讨论活锁时,每个goroutine缺乏的资源都是共享锁。活锁和饥饿是不同的概念,因为在活锁中,所有并发进程都是平等的,并且没有完成任何工作。更广泛地说,饥饿通常意味着存在一个或多个贪婪并发进程,这些进程持续占有共享资源而不公平地阻止一个或多个并发进程完成工作。</p><p>下面这段程序模拟饥饿的发生,通过创建两个 goroutine <code>greedyWorker</code> 和 <code>politeWorker</code>.</p><p><code>greedyWorker</code> 贪婪地独占整个工作循环的共享锁,而 <code>politeWorker</code> 只在需要时尝试占有。两个 worker都进行相同数量的模拟工作(睡眠时间为3纳秒),但正如你在相同的时间内看到的那样,贪婪的工人几乎完成了两倍的工作量!</p><pre><code class="go">var wg sync.WaitGroupvar sharedLock sync.Mutexconst runtime = 1*time.SecondgreedyWorker := func() { defer wg.Done() var count int for begin := time.Now(); time.Since(begin) <= runtime; { sharedLock.Lock() time.Sleep(3*time.Nanosecond) sharedLock.Unlock() count++ } fmt.Printf("Greedy worker was able to execute %v work loops\n", count)}politeWorker := func() { defer wg.Done() var count int for begin := time.Now(); time.Since(begin) <= runtime; { sharedLock.Lock() time.Sleep(1*time.Nanosecond) sharedLock.Unlock() sharedLock.Lock() time.Sleep(1*time.Nanosecond) sharedLock.Unlock() sharedLock.Lock() time.Sleep(1*time.Nanosecond) sharedLock.Unlock() count++ } fmt.Printf("Polite worker was able to execute %v work loops.\n", count)}wg.Add(2)go greedyWorker()go politeWorker()wg.Wait()</code></pre><p>如果我们假设两个 worker 都具有相同大小的临界区,而不是认为 <code>greedyWorker</code> 的算法更有效(或者加锁和解锁的开销很大 ),我们得出的结论是,<code>greedyWorker</code> 不必要地扩大了对其临界区之外的共享锁的控制,并通过饥饿阻止了 <code>politeWorker</code> 的 goroutine 有效地执行工作。</p><p>我们还应该考虑饥饿来自 Go进程 之外的情况。<strong>请记住</strong>,饥饿也可以应用于CPU,内存,文件句柄,数据库连接:任何必须共享的资源都是饥饿的候选者!</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本部分主要介绍了并发中的基本概念,引入了 Golang 语言中有关解决并发问题的一些方法,但没有深入讲解,接下来的几个教程中重点剖析 Golang 语言有关并发的 API。</p>]]></content>
<summary type="html">
<p>本系列阅读笔记是基于 Golang 经典图书 <em>Concurrency in GO</em> ,主要有6个章节:</p>
<ul>
<li><strong>An Introduction to Concurrency</strong></li>
<li>Modelin
</summary>
<category term="Golang" scheme="https://readailib.com/tags/Golang/"/>
<category term="Concurrency" scheme="https://readailib.com/tags/Concurrency/"/>
</entry>
<entry>
<title>自动化构建生产环境下的 Docker 镜像</title>
<link href="https://readailib.com/2018/11/11/cicd/build-cicd-system-with-jekins/"/>
<id>https://readailib.com/2018/11/11/cicd/build-cicd-system-with-jekins/</id>
<published>2018-11-11T12:16:00.000Z</published>
<updated>2019-03-13T12:23:04.146Z</updated>
<content type="html"><![CDATA[<p>本教程将会学习如何配置 Jenkins 来基于 Dockerfile 构建的 Docker 镜像,和如何使用 Docker 来实现(CICD)持续集成与开发。使用自动化镜像构建,在企业生产环境下将可以完成高效的升级与部署。</p><blockquote><p><strong>说明:</strong> 本教程借鉴于 <a href="https://www.katacoda.com/courses/cicd/build-docker-images-using-jenkins" target="_blank" rel="noopener">katacoda</a> 创建的关于CICD的教程.</p></blockquote><p>这次教程主要我尝试了两个版本:</p><ul><li>宿主机是 x86 架构的系统(系统:Ubuntu,Arch)</li><li>宿主机是 给予Arm 的系统(树莓派,系统为Hypriot)</li></ul><h1 id="Linux下部署安装"><a href="#Linux下部署安装" class="headerlink" title="Linux下部署安装"></a>Linux下部署安装</h1><h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><p><img src="https://shenheng.xyz/img/arch1.png" alt="三种安装部署方案"></p><p>三种安装部署方案</p><blockquote><p>说明:这三种方案均测试过,大家可以从图中看到方案2可能是生产中所使用到的方案,方案 1 较为冗余,比较传统, 方案三是我本次教程所要使用的部署方案。</p></blockquote><p>是用这三种方案都需要安装 Docker, 并提供一个供外部访问的 REST API 守护程序(Docker daemon), 类似于 kubernetes 的 kube-api-server一样。在 ubuntu 和 Hyproiot 中需要这样操作:</p><pre><code class="bash">sudo mkdir -pv /etc/systemd/system/docker.service.dcat << EOF > /etc/systemd/system/docker.service.d/remote-api.conf[Service]ExecStart=ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sockEOFsudo systemctl daemon-reloadsudo systemctl restart docker</code></pre><p>可以使用下面的命令测试</p><pre><code class="bash"># 查看端口是否打开netstat -an | grep 2375ss -ntlp | grep 2375# 检查守护程序能否正常工作docker -H 172.16.3.16:2375 info</code></pre><p>简便方法:可以直接是用下面命令启动守护程序,但在生产中是不允许的。</p><pre><code class="bash"># listen using the default unix socket, and on 2 specific IP addresses on this host.$ sudo dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375</code></pre><h2 id="启动-Jenkins"><a href="#启动-Jenkins" class="headerlink" title="启动 Jenkins"></a>启动 Jenkins</h2><p>首先创建一个数据卷,用来保存 Jenkins 安装的插件。</p><pre><code class="bash">docker create -v /data --name jenkinsdata busybox</code></pre><p>使用下面的命令启动一个 Jenkins 的docker容器,并关联/挂着到之前创建的数据卷。此时 Jenkins 容器中所有插件和配置文件都将会持久化到我们的 jenkinssdata 这个数据卷中。</p><pre><code class="bash">docker run -d -u root --name jenkins \ -p 8080:8080 -p 50000:50000 \ --volumes-from jenkinsdata\ --restart always \ jenkins/jenkins:2.112-alpine</code></pre><p><strong>注意</strong>: 当然可以直接挂载到指定的宿主机上的目录</p><h2 id="启动Jenkins-仪表盘"><a href="#启动Jenkins-仪表盘" class="headerlink" title="启动Jenkins 仪表盘"></a>启动Jenkins 仪表盘</h2><p>可以通过访问 <a href="http://172.16.3.32:8080/" target="_blank" rel="noopener">http://172.16.3.32:8080/</a> 来访问 Jenkins 的仪表板</p><p><img src="https://shenheng.xyz/img/login.png" alt="login page"></p><p>login page</p><p>这里需要输入 admin 的密码,密码是随机的,需要通过下面的命令查看初始密码。</p><pre><code class="bash">docker exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword</code></pre><p>Jenkins 可能需要几秒钟才能完成启动。在接下来的步骤中,将使用仪表板配置插件并开始构建Docker镜像。</p><h2 id="安装与配置插件"><a href="#安装与配置插件" class="headerlink" title="安装与配置插件"></a>安装与配置插件</h2><p>第一步是配置 Docker 插件。该插件基于 Jenkins Cloud 插件。当构建需要 Docker 时,它将通过插件创建“Cloud Agent”。代理将是一个 Docker 容器,需要经过配置与我们的前面配置的 Docker 守护进程通信。</p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><ul><li>在仪表板中,选择左侧的 系统管理。</li><li>在“管理Jenkins”页面上,选择“管理插件”。</li><li>“管理插件”页面将为您提供选项卡式界面。单击“可选插件”以查看可以安装的所有Jenkins插件。</li><li>使用搜索框搜索Docker。有多个Docker插件,使用Cloud Providers标题下的复选框选择Docker。</li><li>点击直接安装</li><li>插件将会自动下载并安装,安装完成后点击返回即可完成安装。</li></ul><p><img src="https://shenheng.xyz/img/docker.png" alt="docker plugin"></p><p>docker plugin</p><h3 id="添加-docker-agent"><a href="#添加-docker-agent" class="headerlink" title="添加 docker agent"></a>添加 docker agent</h3><ul><li>在仪表板中,选择左侧的 <strong>系统管理</strong> ;</li><li>在系统管理中,选择 <strong>系统设置</strong> ;</li><li>在底部,有一个下拉菜单叫 <strong>增加一个云</strong> ,从下拉列表中选择 Docker.</li><li>Docker Host URI 是 Jenkins 启动 agent 容器的位置,在本教程中,我们将使用我们前面定义好的 <code>tcp://172.16.3.32:2375</code></li><li>使用 Test Connection 来验证 Jenkins 是否可以与 Docker Daemon 通讯, 如果正常的话,你可以看到返回过来的 Docker version.</li></ul><h3 id="配置-Docker-Agent-Template"><a href="#配置-Docker-Agent-Template" class="headerlink" title="配置 Docker Agent Template"></a>配置 Docker Agent Template</h3><p>Docker Agent Template 是将会处理应用构建(比如自动化构建 Docker 镜像)的Container。</p><ul><li>单击 Docker Agent 模板…然后添加Docker模板。</li><li>配置容器<ul><li>将 agent 的标签设置为 docker-agent。表示我们的 Jenkins 将使用具有该标签的容器来构建我们的应用程序。(类似于 k8s 中的标签选择器)</li><li>在 Docker Image 选项里填写 <code>benhall/dind-jenkins-agent:v2</code> 。 镜像可以从 <a href="https://hub.docker.com/r/benhall/dind-jenkins-agent/" target="_blank" rel="noopener">https://hub.docker.com/r/benhall/dind-jenkins-agent/</a> 获取。</li></ul></li><li>在容器设置下,在 Volumn 文本框中输入 <code>/var/run/docker.sock:/var/run/docker.sock</code>。这允许我们的构建容器与主机通信。</li><li>在 Connect Method 选项里选择 <code>Connect with SSH</code> 选项。由于上一步填写的镜像基于 <code>Jenkins SSH Slave</code> 映像,这意味着默认的 Inject SSH 密钥将处理身份验证。</li><li>单击保存。</li></ul><p>Jenkins现在可以在需要时将Build Agent作为容器启动。</p><p><img src="https://shenheng.xyz/img/docker-agent.png" alt="Configuration of docker agent"></p><p>Configuration of docker agent</p><h2 id="Create-New-Job"><a href="#Create-New-Job" class="headerlink" title="Create New Job"></a>Create New Job</h2><ul><li>选择新建一个 jobs</li><li>填写 job 的名字为 Katacoda Jenkins Demo, 然后选择 自由项目,点击 OK.</li></ul><p><img src="https://shenheng.xyz/img/job.png" alt="New a job"></p><p>New a job</p><ul><li>接下来在 job 配置页中,选择 “Restrict where this project can be run”, 这时需要在标签表达式(类似于标签选择器)填写我们配置 Docker Agent 时填写的标签: docker-agent, 如果一切正常的话,你会看到”Label is serviced by no nodes and 1 cloud”.</li></ul><p><img src="https://shenheng.xyz/img/docker-agent-label-select.png" alt="标签选择"></p><p>标签选择</p><blockquote><p>If you see the error message There’s no agent/cloud that matches this assignment. Did you mean ‘master’ instead of ‘docker-agent’?, then the Docker plugin and the Docker Agent has not been Enabled. Go back to configure the system options and enable both checkboxes.</p></blockquote><ul><li>选择存储库(Repository)类型为 Git,并且设置仓库位置为 <a href="https://github.com/katacoda/katacoda-jenkins-demo" target="_blank" rel="noopener">https://github.com/katacoda/katacoda-jenkins-demo</a>.</li><li>接下来添加一个构建步骤,选择执行 Shell 选项。Shell 命令如下</li></ul><pre><code class="bash">docker infodocker build -t katacoda/jenkins-demo:${BUILD_NUMBER} .docker tag katacoda/jenkins-demo:${BUILD_NUMBER} katacoda/jenkins-demo:latestdocker images</code></pre><h2 id="开始构建项目"><a href="#开始构建项目" class="headerlink" title="开始构建项目"></a>开始构建项目</h2><p>现在我们可以回到我们刚刚新创建工作的主目录中,选择立即构建。一般情况下,初次构建过程时间比较长,因为要 pull 下来我们之前定义的Docker Agent的镜像,并且还要pull 下我们构建项目的 Docker镜像。</p><p><img src="https://shenheng.xyz/img/build.png" alt="构建项目"></p><p>构建项目</p><p><img src="https://shenheng.xyz/img/build-success.png" alt="构建成功"></p><p>构建成功</p><p><img src="https://shenheng.xyz/img/build-procedure.png" alt="查看构建过程"></p><p>查看构建过程</p><h2 id="查看构建完成的镜像"><a href="#查看构建完成的镜像" class="headerlink" title="查看构建完成的镜像"></a>查看构建完成的镜像</h2><p>这时候可以查看宿主机的docker镜像,打印结果如下</p><pre><code class="bash">$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEkatacoda/jenkins-demo latest 63b4e5d2500e About a minute ago 5.81MBpostgres latest 67ebc8a2f066 3 months ago 237MBredis latest 4e8db158f18d 3 months ago 83.4MBgolang latest d0e7a411e3da 4 months ago 794MBbenhall/dind-jenkins-agent v2 8f582645af33 8 months ago 1.14GBjenkins/jenkins 2.112-alpine 481b49e06417 8 months ago 223MB$ _</code></pre><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><pre><code class="bash">$ docker run -d -p 80:80 katacoda/jenkins-demo:latestc716310a068f3ad95ab19c5e5a977981e8f061dec7209b8a55c6f2840700ecce$ curl localhost<h1>This request was processed by host: c716310a068f</h1></code></pre><h1 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h1><ul><li>Jenkins 还没有达到完全自动化构建的目的</li><li>Jenkins 配置较为复杂,坑比较多</li></ul><h1 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h1><ul><li>分布式的CICD解决方案</li><li>强大的社区</li></ul><h1 id="常遇到的坑总结"><a href="#常遇到的坑总结" class="headerlink" title="常遇到的坑总结"></a>常遇到的坑总结</h1><ul><li>jenkins启动一直显示 Jenkins正在启动,请稍后…</li></ul><p><strong>解决方法:</strong></p><pre><code class="bash">docker exec -it jenkins bashcd /var/jenkins_home/updates/ #到jenkins的工作目录下vim default.json# 把 "connectionCheckUrl":"http://www.google.com/" 改为"connectionCheckUrl":"http://www.baidu.com/"</code></pre><ul><li>最新版Jenkins首次安装一直停在向导界面问题</li></ul><p>jenkins启动后一直停在那个获取网络界面。 你需要修改下配置文件,因为你所在网络被限制了。配置文件是<code>$JENKINS_HOME/hudson.model.UpdateCenter.xml</code>,默认内容如下:</p><pre><code class="xml"><?xml version='1.0' encoding='UTF-8'?> <sites> <site> <id>default</id> <url>http://updates.jenkins-ci.org/update-center.json</url> </site></sites></code></pre><p>只需要将 url 改为 <a href="http://mirror.xmission.com/jenkins/updates/update-center.json" target="_blank" rel="noopener">http://mirror.xmission.com/jenkins/updates/update-center.json</a> 即可</p><ul><li>遇到问题有哪些 debug 方法<ul><li>在 Jenkins 的系统配置中的日志记录中查看</li><li>在宿主机中,运行下面的命令</li></ul></li></ul><pre><code class="bash">docker logs jenkins</code></pre><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>Jenkins 是 自动化运维和DevOps不可缺少的工具,它会使你的工作变得轻松,不在纠结环境配置等等,它也作为很多开源项目的项目管理工具,推荐使用!!!</p><p>接下来我将会介绍更加自动化构建项目的一种方案。</p>]]></content>
<summary type="html">
<p>本教程将会学习如何配置 Jenkins 来基于 Dockerfile 构建的 Docker 镜像,和如何使用 Docker 来实现(CICD)持续集成与开发。使用自动化镜像构建,在企业生产环境下将可以完成高效的升级与部署。</p>
<blockquote>
<p><stro
</summary>
<category term="Docker" scheme="https://readailib.com/tags/Docker/"/>
<category term="Jenkins" scheme="https://readailib.com/tags/Jenkins/"/>
</entry>
</feed>