-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1543 lines (1543 loc) · 635 KB
/
search.xml
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
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Python如何将字符串转为字典]]></title>
<url>%2F2019%2F07%2F18%2FPython%E5%A6%82%E4%BD%95%E5%B0%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E4%B8%BA%E5%AD%97%E5%85%B8%2F</url>
<content type="text"><![CDATA[Python如何将字符串转为字典 引言在工作中遇到一个小问题,需要将一个python的字符串转为字典,比如字符串 1user_info = '{"name" : "john", "gender" : "male", "age": 28}' 我们想把它转为下面的字典 1user_dict = {"name" : "john", "gender" : "male", "age": 28} 有以下几种方法 通过json来转换12345>>> import json>>> user_info= '{"name" : "john", "gender" : "male", "age": 28}'>>> user_dict = json.loads(user_info)>>> user_dict{u'gender': u'male', u'age': 28, u'name': u'john'} 但是使用json进行转换存在一个潜在的问题。 由于json语法规定数组或对象之中的字符串必须使用双引号,不能使用单引号(官网上有一段描述是 “A string is a sequence of zero or more Unicode characters, wrapped in double quotes, using backslash escapes”),因此下面的转换是错误的 12345678910111213>>> import json>>> user_info = "{'name' : 'john', 'gender' : 'male', 'age': 28}"# 由于字符串使用单引号,会导致运行出错>>> user_dict = json.loads(user_info)Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 339, in loads return _default_decoder.decode(s) File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 364, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 380, in raw_decode obj, end = self.scan_once(s, idx)ValueError: Expecting property name: line 1 column 2 (char 1) 通过eval12345678>>> user_info = '{"name" : "john", "gender" : "male", "age": 28}'>>> user_dict = eval(user_info)>>> user_dict{'gender': 'male', 'age': 28, 'name': 'john'}>>> user_info = "{'name' : 'john', 'gender' : 'male', 'age': 28}">>> user_dict = eval(user_info)>>> user_dict{'gender': 'male', 'age': 28, 'name': 'john'} 通过eval进行转换就不存在上面使用json进行转换的问题。但是,使用eval却存在安全性的问题,比如下面的例子 1234567# 让用户输入 `user_info`>>> user_info = raw_input('input user info: ')# 输入 {"name" : "john", "gender" : "male", "age": 28},没问题>>> user_dict = eval(user_info)# 输入 __import__('os').system('dir'),user_dict 会列出当前的目录文件!# 再输入一些删除命令,则可以把整个目录清空了!>>> user_dict = eval(user_info) 通过literal_eval123456789>>> import ast>>> user = '{"name" : "john", "gender" : "male", "age": 28}'>>> user_dict = ast.literal_eval(user)>>> user_dict{'gender': 'male', 'age': 28, 'name': 'john'}user_info = "{'name' : 'john', 'gender' : 'male', 'age': 28}">>> user_dict = ast.literal_eval(user)>>> user_dict{'gender': 'male', 'age': 28, 'name': 'john'} 使用 ast.literal_eval进行转换既不存在使用json进行转换的问题,也不存在使用eval进行转换的安全性问题,因此推荐使用ast.literal_eval。 参考文档https://www.cnblogs.com/OnlyDreams/p/7850920.htmlhttp://funhacks.net/2016/04/24/python_将字符串转为字典/http://javascript.ruanyifeng.com/stdlib/json.html]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python如何将字符串转为字典</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Markdown代码块中有反引号处理]]></title>
<url>%2F2019%2F06%2F16%2FMarkdown%E4%BB%A3%E7%A0%81%E5%9D%97%E4%B8%AD%E6%9C%89%E5%8F%8D%E5%BC%95%E5%8F%B7%E5%A4%84%E7%90%86%2F</url>
<content type="text"><![CDATA[Markdown代码块中有反引号处理 123456789````html // 这里写4个反引号<details> <summary>点击时的区域标题</summary> ```bash echo "hello shell" echo "hello python" ```</details>```` // 这里用4个反引号关闭 参考文档https://www.jianshu.com/p/d6ca2d4dfaab]]></content>
<categories>
<category>Markdown笔记</category>
</categories>
<tags>
<tag>Markdown代码块中有反引号处理</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Markdown代码折叠与收起]]></title>
<url>%2F2019%2F06%2F16%2FMarkdown%E4%BB%A3%E7%A0%81%E6%8A%98%E5%8F%A0%E4%B8%8E%E6%94%B6%E8%B5%B7%2F</url>
<content type="text"><![CDATA[Markdown代码折叠与收起介绍:有的时候,代码太长,全部展开显得很臃肿,所以我们可以采用代码折叠的方式。 Markdown很方便,但基本语法有些不足:比如无法使用折叠语法,无法让文字有不同的颜色。这些功能可以实现,不过需要使用 html 语法进行扩展。这篇文章主要是整理一下这些技巧,方便更好的使用。 代码折叠折叠语法:<details> 标签 12345<details> <summary>点击时的区域标题:点击查看详细内容</summary> <p> - 测试 测试测试</p> <pre><code>title,value,callBack可以缺省</code></pre></details> details:折叠语法标签summary:折叠语法展示的摘要pre:以原有格式显示元素内的文字是已经格式化的文本code:指定代码范例blockcode:表示程序的代码块 1234567<details> <summary>点击时的区域标题</summary> ```bash echo "hello shell" echo "hello python" ```</details> 效果展示 点击时的区域标题:点击查看详细内容 - 测试 测试测试 title,value,callBack可以缺省 点击时的区域标题 12echo "hello shell"echo "hello python" 参考文档https://guoflight.github.io/posts/28189/https://www.cnblogs.com/buwuliao/p/9578918.html]]></content>
<categories>
<category>Markdown笔记</category>
</categories>
<tags>
<tag>Markdown代码折叠与收起</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python数据类型]]></title>
<url>%2F2019%2F05%2F18%2FPython%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%2F</url>
<content type="text"><![CDATA[Python数据类型 基本数据类型什么是数据?为何要有多种类型的数据?数据即变量的值,如:age=18,18则是我们保存的数据。变量是用来反映/保持状态以及状态变化的,毫无疑问针对不同的状态就应该用不同类型的数据去标识。 数字类型整型和浮点型统称为数字类型 int整型用于标识:年龄,等级,身份证号,QQ号,个数 123# int(整型)在32位机器上,整数的位数为32位,取值范围为-2**31~2**31-1,即 -2147483648~2147483647在64位系统上,整数的位数为64位,取值范围为-2**63~2**63-1,即 -9223372036854775808~9223372036854775807 12345678910# long(长整型)跟C语言不同,Python 的长整数没有指定位宽,即:Python没有限制长整数数值的大小,但实际上由于机器内存有限,我们使用的长整数数值不可能无限大。注意:自从 Python2.2 起,如果整数发生溢出,Python 会自动将整数数据转换为长整数,所以如今在长整数数据后面不加字母 L 也不会导致严重后果了。注意:在 Python3 里不再有 long 类型了,全都是int>>> a= 2**64>>> type(a) # type()是查看数据类型的方法<type 'long'>>>> b = 2**60>>> type(b)<type 'int'> 123456# complex复数型>>> x=1-2j>>> x.imag-2.0>>> x.real1.0 定义 1234567891011# int整型age=10 # age=int(10)print(id(age))9462848print(type(age))intprint(age)10 float浮点型用于标识:工资,身高,体重定义 12345678910salary=3.1 # salary=float(3.1)print(id(salary))140185593323832print(type(salary))floatprint(salary)3.1 str字符串在 Python 中,加了引号的字符就是字符串类型,Python 并没有字符类型用于标识:描述性的内容,如姓名,性别,国籍,种族定义 1name='zhangsan' # name=str('zhangsan') 单引号、双引号、多引号有什么区别?单双引号没有任何区别,只有下面这种情况,需要考虑单双的配合 12345678910msg = "My name is zhangsan, I'm 18 years old!"# 多引号什么作用?作用就是多行字符串必须用多引号msg = '''今天我想写首小诗,歌颂我的同桌,你看他那乌黑的短发,好像一只炸毛鸡。'''print(msg) 字符串拼接(只能在字符串之间进行,且只能相加或相乘)数字可以进行加减乘除等运算,字符串呢?也能,但只能进行”相加”和”相乘”运算。 123456>>> name='zhangsan'>>> age='18'>>> name+age # 相加其实就是简单拼接'zhangsan18'>>> name*5 'zhangsanzhangsanzhangsanzhangsanzhangsan' 注意1:字符串相加的效率不高字符串1+字符串3,并不会在字符串1的基础上加字符串2,而是申请一个全新的内存空间存入字符串1和字符串3,相当于字符串1与字符串3的空间被复制了一次。注意2:只能字符串加字符串,不能字符串加其他类型]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python数据类型</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python内存管理]]></title>
<url>%2F2019%2F05%2F18%2FPython%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%2F</url>
<content type="text"><![CDATA[Python内存管理 变量存哪了? 12vim p1.pyx = 10 当我们在p1.py中定义一个变量x=10,那么计算机把这个变量值10存放在哪里了呢?回顾计算机的三大核心组件为:CPU、内存和硬盘。一定不是CPU,那是存放在内存还是硬盘中了呢?再回顾变量运行的三个过程,如果我们没有使用Python解释器运行p1.py这个文件,那么x=10很明显只是很普通的四个字符x、=、1、0。而只有Python解释器运行了这个文件,那字符进入了内存,才会有变量这个概念。也就是说变量是存放在内存当中的。 变量存放在内存中这句话太宽泛了,我们把它具体化。现在想象我们在学校(电脑内存)里上课,学校每开一个班,学校都会开辟一个教室给这个班级上课用(存放变量值10),而班级的门牌号则是(变量名x)。也就是说,对于电脑内存这个大内存,每定义一个变量就会在这个大内存中开辟一个小空间,小空间内存放变量值10,然后内存给这个小空间一个变量名x(门牌号),x指向10。 Python垃圾回收机制对于p1.py,如果我们再加上一段代码x=11,大内存会开辟另一个小空间存储变量值11,把变量值绑定另一个门牌号x,但是由于之前有x,所以大内存会解除x与10的连接,让x与11连接。这个时候10由于没有了门牌号,所以成为了Python眼中的垃圾,Python就会处理这个垃圾,释放10的内存占用,这就是Python的垃圾回收机制。而其他语言需要手动把10的内存占用释放掉。 引用计数从上述的解释我们可以知道只要某个变量值绑定着门牌号,就不是垃圾,反之变量值没有绑定着门牌号,这个变量值就是垃圾,Python就会自动清理这个垃圾。这里我们对于这个门牌号给定一个专业的解释,在Python中这个门牌号被称作引用计数。 1234x = 257 # 257 引用计数加1为1y = x # 257 引用计数加1为2x = 258 # 257 引用计数减1为1;258引用计数加1为1del y # 257 引用计数减1为0,触发Python垃圾回收机制,Python清理257的内存占用 上述代码就是一个引用计数加减的过程。 小整数池对于引用计数,需要注意的是:Python实现int的时候有个小整数池。为了避免因创建相同的值而重复申请内存空间所带来的效率问题,Python解释器会在启动时创建出小整数池,范围是[-5,256],该范围内的小整数对象是全局解释器范围内被重复使用,永远不会被回收。 在PyCharm中运行Python程序时,PyCharm出于对性能的考虑,会扩大小整数池的范围,其他的字符串等不可变类型也都包含在内一便采用相同的方式处理了,我们只需要记住这是一种优化机制,至于范围到底多大,无需细究。 123456x = 10 # 10引用计数加1为1y = x # 10引用计数加1为2z = 10 # 10引用计数加1为3x = 11 # 10引用计数减1为2;11引用计数加1为1del y # 10引用计数减1为1del z # 10引用计数减1为0,但不触发Python垃圾回收机制,因为10数据Python小整数池内的数,会在Python解释器关闭前一直存在]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python内存管理</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python定义变量的三个特征]]></title>
<url>%2F2019%2F05%2F17%2FPython%E5%AE%9A%E4%B9%89%E5%8F%98%E9%87%8F%E7%9A%84%E4%B8%89%E4%B8%AA%E7%89%B9%E5%BE%81%2F</url>
<content type="text"><![CDATA[Python定义变量的三个特征 定义变量的三个特征对于每个变量,Python都提供了这三个方法分别获取变量的三个特征,其中python的内置功能id(),内存地址不一样,则id()后打印的结果不一样,因为每一个变量值都有其内存地址,而id是用来反映变量值在内存中的位置,内存地址不同则id不同。 12345678910111213x = 10# 获取变量的变量值print(x)10# 获取变量的id,可以理解成变量在内存中的地址print(id(x))9462848# 获取变量的数据类型print(type(x))<class 'int'> 变量比较判断变量值是否相等用12345name1='张三'name2='李四'print(name1==name2)False 判断变量id是否相等123456789101112x = 11y = xz = 11print(x == y) # Trueprint(x is y) # Trueprint(x is z) # True,整数池的原因x = 257z = 257print(x is z) # False 从上述的打印消息可以看出id相等的变量,值一定相等,指向的是同一个内存地址值相等的变量,id不一定相等 其中在第一次打印print(x is z)的时候就触发了上一章讲的整数池。这可以理解成Python的优化机制,11的值本身不大,并且由于我们快速的再一次使用了11,再由于申请内存空间需要计算机开销,因此Python让x和z都指向同一个11。因为存不是目的,取才是目的,这样进行优化的话并不会影响程序的运行。]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python定义变量的三个特征</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python变量]]></title>
<url>%2F2019%2F05%2F16%2FPython%E5%8F%98%E9%87%8F%2F</url>
<content type="text"><![CDATA[Python变量 什么是变量?(掌握)变量即变化的量,核心是“变”与“量”二字,变即变化,量即衡量状态。 计算机通过记录状态去识别,这就是量的概念。 变:现实世界中的状态是会发生改变的。 量:记录现实世界中的状态,让计算机能够像人一样去识别世间万物。今年可能175cm,明年可能就是180cm了,那这种状态是不是会发生变化。 为什么要有变量?(掌握)程序执行的本质就是一系列状态的变化,变是程序执行的直接体现,所以我们需要有一种机制能够反映或者说是保存下来程序执行时状态以及状态的变化。比如: 123英雄的等级为1,打怪升级(变)为10僵尸的存活状态True,被植物打死了,于是变为False人的名字为张三,也可以修改为张叁 定义变量(掌握)变量名(相当于门牌号,指向值所在的空间),等号,变量值 12345name = '张三'gender = 'male'age = 30height = 175weight = 130 变量的组成(掌握) 变量名:变量名用来引用变量值,凡是需要用变量值,都需要通过变量名 赋值符号:赋值 变量值:存放数据,用来记录现实世界中的某种状态 1name # 报错,无任何意义 12345age = 30height = 175print(age) # 30print(height) # 175 变量名的命名规范如果对于一个变量,想怎么命名就怎么命名,那样没有任何问题,顶多就是老板检查代码后,第二天就不用去公司了。 12sfasfewfasdfa='张三'print(sfasfewfasdfa) # '张三' 定义一个变量就是在记录现实世界中的的状态,存不是目的,取才是目的。变量的命名应该满足以下三个规范 变量的命名应该能反映变量值所描述的状态,不可用中文 变量名必须用字母,数字,下划线组合,变量名的第一个字符不能是数字 关键字不能声明为变量名 1['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield'] 变量名的两种方式(了解)驼峰体12AgeOfZhangsan = 30print(AgeOfZhangsan) # 30 下划线(推荐使用)12age_of_zhangsan = 30print(age_of_zhangsan) # 30 以上两种变量名的风格,推荐使用下划线的方式。 定义变量会有:id,type,value ==比较的是value is比较的是id 强调 id相同,意味着type和value必定相同 value相同type肯定相同,但id可能不同,如下 1234567891011>>> x='Info Zhangsan:18'>>> y='Info Zhangsan:18'>>> id(x)4376607152>>> id(y)4376607408>>> >>> x == yTrue>>> x is yFalse 常量(掌握)变量是变化的量,常量则是不变的量。Python中没有使用语法强制定义常量,也就是说,Python中定义常量本质上就是变量。如果非要定义常量,变量名必须全大写。 12AGE_OF_ZHANGSAN = 30print(AGE_OF_ZHANGSAN) # 30 如果是常量,那就没必要更改,所以Python就只制定了一个规范,而没指定常量的语法。而在C语言中有专门的常量定义语法,const int age = 19;,一旦定义age为常量,更改age即会报错。 注释(掌握)当把变量理解透了,就已经进入了编程的世界。随着学习的深入,用不了多久,就可以写复杂的上千甚至上万行的代码了,有些代码花了很久写出来,过了些天再回去看,发现竟然看不懂了,这很正常了。另外,以后在工作中会发现,一个项目多数是由几个甚至几十个开发人员一起做,要调用别人写的代码,别人也要用你的,如果代码不加注释,自己都看不懂,更别说别人了,这产会很麻烦。所以为了避免这种尴尬的事情发生,一定要增加代码的可读性。代码注释分单行和多行注释,单行注释用#,多行注释可以用三对单/双引号,使用三引号注释可以换行。 1234567891011# 单行注释'''三单引号注释三单引号注释'''"""三双引号多行注释三双引号多行注释""" 注释的原则 不用全部加注释,只需要在自己觉得重要或不好理解的部分加注释即可 注释可以用中文或英文,但不要用拼音 文件头12#!/usr/bin/env python# -*- coding: utf-8 -*- 掌握 -> 熟悉 -> 了解 掌握:倒背如流 熟悉:正背如流 了解:看到能够想起 参考文档http://www.cnblogs.com/linhaifeng/articles/7133167.htmlhttps://www.cnblogs.com/nickchen121/p/10722738.html]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python变量</tag>
</tags>
</entry>
<entry>
<title><![CDATA[grep显示前后几行信息]]></title>
<url>%2F2019%2F04%2F29%2Fgrep%E6%98%BE%E7%A4%BA%E5%89%8D%E5%90%8E%E5%87%A0%E8%A1%8C%E4%BF%A1%E6%81%AF%2F</url>
<content type="text"><![CDATA[grep显示前后几行信息 显示foo及前5行1grep -B 5 foo file 显示foo及后5行1grep -A 5 foo file 显示 file 文件里匹配 foo 字串那行以及上下5行1grep -C 5 foo file]]></content>
<categories>
<category>grep笔记</category>
</categories>
<tags>
<tag>grep显示前后几行信息</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Chrony 设置服务器集群系统时间同步]]></title>
<url>%2F2019%2F01%2F25%2FChrony%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%9B%86%E7%BE%A4%E7%B3%BB%E7%BB%9F%E6%97%B6%E9%97%B4%E5%90%8C%E6%AD%A5%2F</url>
<content type="text"><![CDATA[Chrony 设置服务器集群系统时间同步 RHEL7/CentOS7 新的 NTP 对时服务 Chrony 什么是Chrony?Chrony 是一个开源的自由软件,CentOS7/RHEL7 操作系统,已经是默认服务,默认配置文件在 /etc/chrony.conf 它能保持系统时间与时间服务器(NTP)同步,让时间始终保持同步。相对于 NTP 时间同步软件,占据很大优势。其用法也很简单。Chrony 应用本身已经有几年了,它其实是网络时间协议的 (NTP) 的另一种实现。一直以来众多发行版里标配的都是 ntpd 对时服务,自 CentOS7/RHEL7 起,Chrony 做为了发行版里的标配服务,不过老的 ntpd 服务依旧在 CentOS7/RHEL7 里可以找到。 Chrony 两个核心组件 Chrony 可以同时做为 NTP 服务的客户端和服务端。默认安装完后有两个程序 chronyd 和 chronyc。 chronyd(主程序文件)是守护 daemon 进程程序,主要用于调整内核中运行的系统时间和时间服务器同步。它确定计算机增减时间的比率,并对此进行调整补偿。 chronyc(工具程序)一个交互式命令行工具,提供一个用户界面,用于监控性能并进行多样化的配置。它可以在chronyd实例控制的计算机上工作,也可以在一台不同的远程计算机上工作。 OS环境 10.28.204.65 客户端10.28.204.66 服务端CentOS Linux release 7.6.1810 (Core) 安装 Chrony 系统默认已经安装,如未安装,请执行以下命令安装 1yum -y install chrony 启动并加入开机自启动1234567# 启动服务systemctl start chronyd.servicesystemctl enable chronyd.servicesystemctl -l status chronyd.servicesystemctl stop chronyd.service# 设置开机自启,默认是enable的systemctl restart chronyd.service 防火墙 Firewalld 设置 因 NTP 使用 123/UDP 端口协议,所以允许 NTP 服务即可 123firewall-cmd --add-service=ntp --permanentfirewall-cmd --add-port=123/udp --permanentfirewall-cmd --reload 配置 Chrony 以下是系统默认配置文件,对此加以说明 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758vim /etc/chrony.conf# 使用pool.ntp.org项目中的公共服务器。以server开,理论上你想添加多少时间服务器都可以。# Please consider joining the pool (http://www.pool.ntp.org/join.html).#server 0.centos.pool.ntp.org iburst#server 1.centos.pool.ntp.org iburst#server 2.centos.pool.ntp.org iburst#server 3.centos.pool.ntp.org iburstserver 0.cn.pool.ntp.org iburst minpoll 4 maxpoll 10server 1.cn.pool.ntp.org iburst minpoll 4 maxpoll 10server 2.cn.pool.ntp.org iburst minpoll 4 maxpoll 10server 3.cn.pool.ntp.org iburst minpoll 4 maxpoll 10server ntp1.aliyun.com iburst minpoll 4 maxpoll 10server ntp2.aliyun.com iburst minpoll 4 maxpoll 10server ntp3.aliyun.com iburst minpoll 4 maxpoll 10server ntp4.aliyun.com iburst minpoll 4 maxpoll 10server ntp5.aliyun.com iburst minpoll 4 maxpoll 10server ntp6.aliyun.com iburst minpoll 4 maxpoll 10# 根据实际时间计算出服务器增减时间的比率,然后记录到一个文件中,在系统重启后为系统做出最佳时间补偿调整。driftfile /var/lib/chrony/drift# chronyd根据需求减慢或加速时间调整,# 在某些情况下系统时钟可能漂移过快,导致时间调整用时过长。# 该指令强制chronyd调整时期,大于某个阀值时步进调整系统时钟。# 只有在因chronyd启动时间超过指定的限制时(可使用负值来禁用限制)没有更多时钟更新时才生效。makestep 1.0 3# 将启用一个内核模式,在该模式中,系统时间每11分钟会拷贝到实时时钟(RTC)。rtcsync# Enable hardware timestamping on all interfaces that support it.# 通过使用hwtimestamp指令启用硬件时间戳#hwtimestamp eth0#hwtimestamp eth1#hwtimestamp *# Increase the minimum number of selectable sources required to adjust# the system clock.#minsources 2# 指定一台主机、子网,或者网络以允许或拒绝NTP连接到扮演时钟服务器的机器allow 192.168.0.0/16#deny 192.168/16# Serve time even if not synchronized to a time source.# 即使自己未能通过网络时间服务器同步到时间,也允许将本地时间作为标准时间授时给其它客户端local stratum 10# 指定包含NTP验证密钥的文件。#keyfile /etc/chrony.keys# 指定日志文件的目录。logdir /var/log/chrony# Select which information is logged.#log measurements statistics tracking 设置时区查看当前系统时区123456789timedatectl Local time: Fri 2018-2-29 13:31:04 CST Universal time: Fri 2018-2-29 05:31:04 UTC RTC time: Fri 2018-2-29 08:17:20 Time zone: Asia/Shanghai (CST, +0800) NTP enabled: yesNTP synchronized: yes RTC in local TZ: no DST active: n/a 如果你当前的时区不正确,请按照以下操作设置。 查看所有可用的时区1timedatectl list-timezones 筛选式查看在亚洲 S 开的上海可用时区 1234567timedatectl list-timezones | grep -E "Asia/S.*"Asia/SakhalinAsia/SamarkandAsia/SeoulAsia/ShanghaiAsia/SingaporeAsia/Srednekolymsk 设置当前系统为 Asia/Shanghai 上海时区 1timedatectl set-timezone Asia/Shanghai 设置完时区后,强制同步下系统时钟 12chronyc -a makestep200 OK 服务器集群之间的系统时间同步在生产环境中,其网络都是内网结构,那么内网如何保证服务器之间的时间同步呢?其实这个问题很简单,只需要搭建一台内网时间服务器,然后让所有计算机都到服务端(10.28.204.66)去同步时间即可。 具体操作:在服务端注释以下内容 1234#server 0.centos.pool.ntp.org iburst#server 1.centos.pool.ntp.org iburst#server 2.centos.pool.ntp.org iburst#server 3.centos.pool.ntp.org iburst 并添加以下内容:表示与本机同步时间 1server 10.28.204.66 iburst 这样我们需求的一台内网时间服务器已经配置完毕。同样在客户端注释掉其他 server,并在客户端 10.28.204.65 添加以下 1server 10.28.204.66 iburst 到此已经完成系统时间的同步。如有多台机器,操作也是如此。 常用命令123456789101112131415161718192021222324252627282930313233343536373839404142# 检查 时间同步 详细源状态chronyc sourceschronyc sources -v# 查看 时间同步源服务器 状态chronyc sourcestatschronyc sourcestats -v# 设置硬件时间# 硬件时间默认为UTCtimedatectl set-local-rtc 1# 启用NTP时间同步timedatectl set-ntp yes# 可以通过直接执行 chronyc 命令来修改设置# 检查NTP访问是否对特定主机可用chronyc accheck# 该命令会显示有多少NTP源在线/离线chronyc activity# 手动添加一台新的NTP服务器chronyc add server# 在客户端报告已访问到服务器chronyc clients# 手动移除NTP服务器或对等服务器chronyc delete# 手动设置守护进程时间chronyc settime# 校准时间服务器,显示系统时间信息chronyc tracking# 其他时间设置相关指令# 查看日期时间、时区及NTP状态timedatectl# 查看时区列表timedatectl list-timezones# 修改时区timedatectl set-timezone Asia/Shanghai# 修改日期时间timedatectl set-time "2015-01-21 11:50:00" # 可以只修改其中一个# 开启NTPtimedatectl set-ntp true/flase 还有另一个有趣的命令 system-config-date,在 rhel7/centos7 里也给了我们一个可以图形化配置chrony 服务的工具 。 1yum -y install system-config-date 安装完成后运行 system-config-date 命令,界面如下 最后需要注意的是,配置完 /etc/chrony.conf 后,需重启 chrony 服务,否则可能会不生效。 Chrony的优势Chrony 的优势包括: 更快的同步只需要数分钟而非数小时时间,从而最大程度减少了时间和频率误差,这对于并非全天 24 小时运行的台式计算机或系统而言非常有用。 能够更好地响应时钟频率的快速变化,这对于具备不稳定时钟的虚拟机或导致时钟频率发生变化的节能技术而言非常有用。 在初始同步后,它不会停止时钟,以防对需要系统时间保持单调的应用程序造成影响。 在应对临时非对称延迟时(例如,在大规模下载造成链接饱和时)提供了更好的稳定性。 无需对服务器进行定期轮询,因此具备间歇性网络连接的系统仍然可以快速同步时钟。 参考文档https://renwole.com/archives/1032https://www.jianshu.com/p/dfdedfb9c59fhttps://my.oschina.net/91devel/blog/2878266http://www.pool.ntp.org/zone/asiahttp://www.pool.ntp.org/zone/cn]]></content>
<categories>
<category>NTP笔记</category>
</categories>
<tags>
<tag>Chrony 设置服务器集群系统时间同步</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iptables防火墙NAT服务器的设置]]></title>
<url>%2F2019%2F01%2F20%2Fiptables%E9%98%B2%E7%81%AB%E5%A2%99NAT%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E8%AE%BE%E7%BD%AE%2F</url>
<content type="text"><![CDATA[iptables防火墙NAT服务器的设置 NAT 服务器的设置假设我们准备要架设一个路由器的延伸服务器,就称之为 NAT 服务器。NAT 是什么呢?简单的说,可以称它为内部 LAN 主机的 IP 分享器。 NAT 的全名是 Network Address Translation,即网络地址的转换。通过字面上的意思我们来想一想,TCP/IP 的网络数据包不是有 IP 地址吗?IP 地址不是有来源与目的地吗?iptables 命令就能够修改 IP 数据包的报头数据,连目标或来源的 IP 地址都可以修改,甚至连 TCP 数据包报头的 port number 也能修改。 NAT 服务器的功能除了可以实现类似 IP 分享的功能之外 图:单一网络,仅有一个路由器的环境示意图 还可以达到类似 DMZ(非军事化隔离区)的功能。 图:架设在防火墙后端的网络服务器环境示意图 这完全取决于 NAT 是修改来源 IP 还是目标 IP。下面我们就来聊一聊此内容。 什么是 NAT?SNAT?DNAT?在介绍 NAT 的实际操作之前,让我们再来看一下比较简单的数据包通过 iptables 而传送到后端主机的表与链流程。参考图如下: 图:iptables 内建各表与链的相关性(简图) 当网络布线如 单一网络 的架构,若内部 LAN 有任何一台主机想要传送数据包出去时,那么这个数据包要如何通过 Linux 主机而传送出去呢?是这样的: 先经过 NAT table 的 PREROUTING 链。 经由路由判断确定这个数据包是否要进入本机,若不进入本机,则下一步。 再经过 Filter table 的 FORWARD 链。 通过 NAT table 的 POSTROUTING 链,最后传送出去。 NAT 服务器的重点就在于上面流程的第 1、4 步,也就是 NAT table 的两条重要的链:PREROUTING 与 POSTROUTING。那这两条链有什么重要的功能呢?重点在于修改 IP。但这两条链修改的 IP 是不一样的,POSTROUTING 修改的是来源 IP,PREROUTING 则修改的是目标 IP。由于修改的 IP 不一样,所以就称为来源 NAT (Source NAT, SNAT) 及目标 NAT (Destination NAT, DNAT)。我们先来谈一谈 IP 分享器功能的 SNAT。 来源 NAT (SNAT):修改数据包报头的来源项目你应该有听说过 IP 路由器,它可以让家庭里的好几台主机同时通过一条 ADSL 网络连接到 Internet 上,如图 单一网络 中所示,那个 Linux 主机就是 IP 路由器。那么它是如何实现 IP 分享的功能呢?就是通过 NAT 表的 POSTROUTING 来处理的。假设网络布线如图 单一网络 所示, 那么 NAT 服务器是如何处理这个数据包的呢? 如图所示: 图:SNAT 数据包传送出去的示意图 在客户端 192.168.1.100 这台主机要连接到 http://tw.yahoo.com 去时,它的数据包报头会如何变化? 客户端所发出的数据包报头中,来源会是 192.168.1.100,然后传送到 NAT 这台主机。 NAT 主机的内部接口 (192.168.1.2) 接收到这个数据包后,会主动分析报头数据,因为报头数据显示目的并非 Linux 本机,所以开始经过路由分析,将此数据包转到可以连接到 Internet 的 Public IP 处。 由于 Private IP 与 Public IP 不能互通,所以 Linux 主机通过 iptables 的 NAT table 内的 POSTROUTING 链将数据包报头的来源伪装成为 Linux 的 Public IP,并且将两个不同来源 (192.168.1.100 及 Public IP) 的数据包对应写入暂存内存当中,然后将此数据包传送出去。 此时 Internet 上面看到这个数据包时,都只会知道这个数据包来自 Public IP 而不知道其实是来自内部。好了,那么如果 Internet 返回数据包呢?又会怎么做?如图 图:SNAT 数据包接收的示意图 在 Internet 上面的主机接到这个数据包时,会将响应数据传送给那个 Public IP 的主机。 当 Linux NAT 服务器收到来自 Internet 的响应数据包后,会分析该数据包的序号,并比对刚刚记录到内存当中的数据,由于发现该数据包为后端主机之前传送出去的,因此在 NAT PREROUTING 链中,会将目标 IP 修改成为后端主机,亦即那台 192.168.1.100,然后发现目标已经不是本机 (Public IP),所以开始通过路由分析数据包流向。 数据包会传送到 192.168.1.2 这个内部接口,然后再传送到最终目标 192.168.1.100 机器上去。 经过这个流程,就可以发现,所有内部 LAN 的主机都可以通过这部 NAT 服务器连接出去,而大家在 Internet 上面看到的都是同一个 IP(就是 NAT 那台主机的 Public IP),所以,如果内部 LAN 主机没有连上不明网站的话,那么内部主机其实是具有一定程度的安全性的,因为 Internet 上的其他主机没有办法主动攻击你的 LAN 内的 PC。所以我们才会说,NAT 最简单的功能就是类似 IP 分享器。这也是 SNAT 的一种。 Tips:NAT 服务器与路由器有什么不同?基本上,NAT 服务器一定是路由器,不过,NAT 服务器由于会修改 IP 报头数据,因此与单纯转递数据包的路由器不同。最常见的 IP 分享器就是一个路由器,但是这个 IP 分享器一定会有一个 Public IP 与一个 Private IP,让 LAN 内的 Private IP 可以通过 IP 分享器的 Public IP 传送出去。至于路由器通常两边都是 Public IP 或同时为 Private IP。 目标 NAT (DNAT):修改数据包报头的目标项目SNAT 主要是应付内部 LAN 连接到 Internet 的使用方式,**DNAT 则主要用在内部主机想要架设可以让 Internet 访问的服务器。就有点类似图 架设在防火墙后端的网络服务器 的 DMZ 内的服务器。下面也先来谈一谈 DNAT 的运行吧。 如图所示: 图:DNAT 的数据包传送示意图 假设内部主机 192.168.1.210 启动了 WWW 服务,这个服务的 port 开启在 port 80,那么 Internet 上面的主机 (61.xx.xx.xx) 要如何连接到内部服务器呢?当然,还是得要通过 Linux NAT 服务器。所以这台 Internet 上面的机器必须要连接到 NAT 的 Public IP 才行。 外部主机想要连接到目的端的 WWW 服务,则必须要连接到 NAT 服务器上。 NAT 服务器已经设置好要分析出 port 80 的数据包,所以当 NAT 服务器接到这个数据包后,会将目标 IP 由 Public IP 改成 192.168.1.210,且将该数据包相关信息记录下来,等待内部服务器的响应。 上述的数据包在经过路由后,来到 Private 接口处,然后通过内部的 LAN 传送到 192.168.1.210 上。 192.186.1.210 会响应数据给 61.xx.xx.xx,这个回应当然会传送到 192.168.1.2 上去。 经过路由判断后,来到 NAT POSTROUTING 的链,然后通过第二步骤的记录,将来源 IP 由 192.168.1.210 改为 Public IP 后,就可以传送出去了。 其实整个步骤几乎就等于 SNAT 的反向传送。这就是 DNAT。 简单的NAT服务器:IP分享功能在 Linux 的 NAT 服务器服务当中,最常见的就是类似图 单一网络 的 IP 分享器功能。而由刚刚的介绍我们也该知道,这个 IP 分享器的功能其实就是 SNAT,作用就只是在 iptables 内的 NAT 表中,那个路由判断后的 POSTROUTING 链所做的工作就是进行 IP 的伪装。另外,需要了解的是,NAT 服务器必须要有一个 Public IP 接口,以及一个内部 LAN 连接的 Private IP 接口才行。下面的范例中,假设是这样的: 外部接口使用 eth0,这个接口具有 Public IP。 内部接口使用 eth1,假设这个 IP 为 192.168.100.254。 利用前面谈到的数据来设置网络参数后,务必要进行路由的检测,因为在 NAT 服务器的设置方面,最容易出错的地方就是路由。尤其是在拨号产生 ppp0 这个对外接口的环境下,这个问题最严重。一定要记住:如果 Public IP 取得的方式是拨号或 cable modem 时,对于配置文件 /etc/sysconfig/network、ifcfg-eth0、ifcfg-eth1 等,千万不要设置 GATEWAY,否则就会出现两个 default gateway,反而会造成问题。 编辑 iptables.rule 文件,该文件内已经含有 NAT 的脚本了。在该文件的第二部份关于 NAT 服务器的设置中,应该有看到下面这几行: 12345678910iptables -A INPUT -i $INIF -j ACCEPT# 这一行为非必要的,主要的目的是让内网 LAN 能够完全的使用 NAT 服务器资源# 其中 $INIF 在本例中为 eth1 接口echo "1" > /proc/sys/net/ipv4/ip_forward# 这一行则是在让 Linux 具有 router 的功能iptables -t nat -A POSTROUTING -s $innet -o $EXTIF -j MASQUERADE# 这一行最关键!就是加入 NAT table 数据包伪装。本例中 $innet 是 192.168.100.0/24# 而 $EXTIF 则是对外界面,本例中为 eth0 以上输出重点在 “MASQUERADE”。这个设置值就是 IP 伪装成为封包出去 (-o) 的那块设备上的 IP。以上面的例子来说,就是 $EXTIF,也就是 eth0。所以数据包来源只要来自 $innet (也就是内部 LAN 的其他主机) ,只要该数据包可通过 eth0 传送出去,那就会自动的修改 IP 的来源报头成为 eth0 的 Public IP。将 iptables.rule 设置好你内、外网络接口,执行 iptables.rule 后,Linux 就拥有主机防火墙以及 NAT 服务器的功能。 例题:如同上面所述的案例,那么 LAN 内的其他 PC 应该要如何设置相关的网络参数?答:答案其实很简单,将 NAT 服务器作为 PC 的 GATEWAY 即可。只要记得下面的参数值即可: NETWORK 为 192.168.100.0 NETMASK 为 255.255.255.0 BROADCAST 为 192.168.100.255 IP 可以设置 192.168.100.1 ~ 192.168.100.254 之间,不可重复 网关 (Gateway) 需要设置为 192.168.100.254(NAT 服务器的 Private IP) DNS (/etc/resolv.conf) 需设置为 168.95.1.1 (Hinet) 或 139.175.10.20 (Seed Net),这个请依 ISP 而定 事实上,除了 IP 伪装 (MASQUERADE) 之外,我们还可以直接指定修改 IP 数据包报头的来源 IP。如下面这个例子: 例题:假设对外的 IP 固定为 192.168.1.100,若不想使用伪装,该如何处理?答: 1iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 192.168.1.100 例题:假设 NAT 服务器对外 IP 有好几个,那想要轮流使用不同的 IP 时,该如何设置?假设你的 IP 范围为 192.168.1.210 ~ 192.168.1.220答: 1iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 192.168.1.210-192.168.1.220 这样也可以修改网络数据包的来源 IP 资料。不过,除非使用的是固定 IP,且有多个 IP 可以对外连接,否则一般使用 IP 伪装即可,不需要使用到 SNAT。当然,你也可能有自己独特的环境。 iptables 的额外内核模块功能如果在 iptables.rule 文件内的第二部分仔细观察,可能会奇怪,为何我们需要加载一些有用的模块?比如 ip_nat_ftp 及 ip_nat_irc ?这是因为很多通信协议使用的数据包传输比较特殊,尤其是 FTP 文件传输使用两个 port 来处理数据。在这里我们需要知道,iptables 提供很多好用的模块,这些模块可以辅助数据包的过滤,可以让我们节省很多 iptables 的规则拟定工作。 在防火墙后端的网络服务器上做 DNAT 设置既然可以做 SNAT 的 IP 分享功能,我们当然可以使用 iptables 做出 DMZ。但是再次重申,不同的服务器数据包传输的方式可能有点差异,因此,建议新手不要玩这个东西。否则很容易导致某些服务无法顺利对 Internet 提供的问题。 先来谈一谈,如果想要处理 DNAT 的功能时,iptables 要如何下达命令?另外,我们必须要知道的是,DNAT 用到的是 NAT table 的 PREROUTING 链。不要搞错了。 例题:假设内网有部主机 IP 为 192.168.100.10,该主机是可对 Internet 开放的 WWW 服务器。该如何通过 NAT 机制,将 WWW 数据包传到该主机上?答:假设 Public IP 所在的接口为 eth0 ,那么规则就是: 1iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.100.10:80 上面例题中的 -j DNAT --to-destination IP[:port] 就是精髓,代表从 eth0 这个接口传入的,且想要使用 port 80 的服务时,将该数据包重新传递到 192.168.100.10:80 的 IP 及 port 上。可以同时修改 IP 与 port,真方便。其他还有一些较高级的 iptables 使用方式,如下所示: 12345678910-j REDIRECT --to-ports <port number># 这个也挺常见的,基本上,就是进行本机上面 port 的转换# 不过,特别留意的是,这个操作仅能够在 NAT table 的 PREROUTING 以及 # OUTPUT 链上面实行# 范例:将要求与 80 连接的数据包转递到 8080 这个 portiptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080# 这种设置最容易在使用了非正规的 port 来进行某些 well known 的协议,# 例如使用 8080 这个 port 来启动 WWW ,但是别人都以 port 80 来连接,# 所以,就可以使用上面的方式来将对方对本机主机的连接传递到 8080 了]]></content>
<categories>
<category>iptables笔记</category>
</categories>
<tags>
<tag>iptables防火墙NAT服务器的设置</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iptables防火墙实例]]></title>
<url>%2F2019%2F01%2F19%2Fiptables%E9%98%B2%E7%81%AB%E5%A2%99%E5%AE%9E%E4%BE%8B%2F</url>
<content type="text"><![CDATA[iptables防火墙实例 推荐使用脚本来编写防火墙, 然后通过 /etc/init.d/iptables save 来将结果存储到 /etc/sysconfig/iptables 中去。而且此一特色还可以用在呼叫其他的 scripts ,可以让防火墙规则具有较为灵活的使用方式。 规则草拟下面介绍的这个防火墙,可以用来作为路由器上的防火墙,也可以用来作为本机的防火墙。 假设硬件连接如同下图所示, Linux 主机本身也是内部 LAN 的路由器,也就是一个简单的 IP 路由器的功能。假设目前网络接口有下面这些: 外部网络使用 eth0(如果是拨号,有可能是 ppp0,请针对具体环境来设置)。 内部网络使用 eth1,且内部使用 192.168.100.0/24 这个 Class。 主机默认开放的服务有 WWW、SSH、HTTPS 等。 图:一个局域网络的路由器架构示意图 由于希望将信任网域 (LAN) 与不信任网域 (Internet) 完全分开,所以建议在 Linux 主机上面安装两块以上的实体网卡,将两块网卡接在不同的网络,这样可以避免很多问题。最重要的防火墙策略是:关闭所有的连接,仅开放特定的服务模式。 而且假设内部用户已经受过良好的训练,因此在 filter table 的 3 条链中默认策略是: INPUT 为 DROP OUTPUT 及 FORWARD 为 ACCEPT 整个防火墙流程图: 图:本机的防火墙规则流程示意图 原则上,内部 LAN 主机与主机本身的开放度很高,因为 OUTPUT 与 FORWARD 是完全开放的。对于小家庭的主机是这种设置可以接受的,因为内部的计算机数量不多,而且人员都是熟悉的,所以不需要特别加以控制。但是在大企业的内部,这样的规划是很不合格的,因为不能保证内部所有的人都可以按照我们的规定来使用 Network,也就是说家贼难防。因此,在这种环境下,连 OUTPUT 与 FORWARD 都需要特别加以管理才行。 实际设置事实上,我们在设置防火墙的时候,不太可能会一个命令一个命令地输入,通常是利用 shell scripts 来帮我们实现这样的功能。下面是利用上面的流程图所规划出来的防火墙脚本,参考一下,但是需要将环境修改成适合自己的环境才行。此外,为了未来修改维护的方便,将整个 script 拆成 3 部分。 iptables.rule:设置最基本的规则,包括清除防火墙规则、加载模块、设置服务可接受等。 iptables.deny:设置阻挡某些恶意主机的进入。 iptables.allow:设置允许某些自定义的后门来源主机进入。 个人习惯是将这个脚本放置到 /data/scripts/iptables 目录下,你也可以自行放置到自己习惯的位置。那下面就来瞧瞧这个脚本是怎么写的。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108mkdir -p /data/scripts/iptablescd /data/scripts/iptablesvim iptables.rule#!/bin/bash# 请先输入相关参数,不要输入错误EXTIF="eth0" # 这个是可以连上 Public IP 的网络接口INIF="eth1" # 内部 LAN 的连接接口;若无则写成 INIF=""INNET="192.168.100.0/24" # 若无内部网域接口,请填写成 INNET=""export EXTIF INIF INNET########################### 第一部份,针对本机的防火墙设置 ############################ 1. 先设置好内核的网络功能echo "1" > /proc/sys/net/ipv4/tcp_syncookiesecho "1" > /proc/sys/net/ipv4/icmp_echo_ignore_broadcastsfor i in /proc/sys/net/ipv4/conf/*/{rp_filter,log_martians}; do echo "1" > $idonefor i in /proc/sys/net/ipv4/conf/*/{accept_source_route,accept_redirects,\send_redirects}; do echo "0" > $idone# 2. 清除规则、设置默认策略及开放 lo 与相关的设置值PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin; export PATHiptables -Fiptables -Xiptables -Ziptables -P INPUT DROPiptables -P OUTPUT ACCEPTiptables -P FORWARD ACCEPTiptables -A INPUT -i lo -j ACCEPTiptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT# 3. 启动额外的防火墙 script 模块if [ -f /data/scripts/iptables/iptables.deny ]; then sh /data/scripts/iptables/iptables.denyfiif [ -f /data/scripts/iptables/iptables.allow ]; then sh /data/scripts/iptables/iptables.allowfiif [ -f /data/scripts/httpd-err/iptables.http ]; then sh /data/scripts/httpd-err/iptables.httpfi# 4. 允许某些类型的 ICMP 数据包进入AICMP="0 3 3/4 4 11 12 14 16 18"for tyicmp in $AICMPdo iptables -A INPUT -i $EXTIF -p icmp --icmp-type $tyicmp -j ACCEPTdone# 5. 允许某些服务的进入,请依照自己的环境开启# iptables -A INPUT -p TCP -i $EXTIF --dport 21 --sport 1024:65534 -j ACCEPT # FTP# iptables -A INPUT -p TCP -i $EXTIF --dport 22 --sport 1024:65534 -j ACCEPT # SSH# iptables -A INPUT -p TCP -i $EXTIF --dport 25 --sport 1024:65534 -j ACCEPT # SMTP# iptables -A INPUT -p UDP -i $EXTIF --dport 53 --sport 1024:65534 -j ACCEPT # DNS# iptables -A INPUT -p TCP -i $EXTIF --dport 53 --sport 1024:65534 -j ACCEPT # DNS# iptables -A INPUT -p TCP -i $EXTIF --dport 80 --sport 1024:65534 -j ACCEPT # WWW# iptables -A INPUT -p TCP -i $EXTIF --dport 110 --sport 1024:65534 -j ACCEPT # POP3# iptables -A INPUT -p TCP -i $EXTIF --dport 443 --sport 1024:65534 -j ACCEPT # HTTPS########################### 第二部份,针对后端主机的防火墙设置 ############################ 1. 先加载一些有用的模块modules="ip_tables iptable_nat ip_nat_ftp ip_nat_irc ip_conntrack ip_conntrack_ftp ip_conntrack_irc"for mod in $modules; do testmod=$(lsmod | grep "^${mod} " | awk '{print $1}') if [ "$testmod" == "" ]; then modprobe $mod fidone# 2. 清除 NAT table 的规则iptables -F -t natiptables -X -t natiptables -Z -t natiptables -t nat -P PREROUTING ACCEPTiptables -t nat -P POSTROUTING ACCEPTiptables -t nat -P OUTPUT ACCEPT# 3. 若有内部接口的存在 (双网卡) 开放成为路由器,且为 IP 路由器if [ "$INIF" != "" ]; then iptables -A INPUT -i $INIF -j ACCEPT echo "1" > /proc/sys/net/ipv4/ip_forward if [ "$INNET" != "" ]; then for innet in $INNET; do iptables -t nat -A POSTROUTING -s $innet -o $EXTIF -j MASQUERADE done fifi# 如果你的 MSN 一直无法连接,或者是某些网站 OK 某些网站不 OK,# 可能是 MTU 的问题,那你可以将下面这一行取消注释来启动 MTU 限制范围# iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss \# --mss 1400:1536 -j TCPMSS --clamp-mss-to-pmtu# 4. NAT 服务器后端的 LAN 内对外之服务器设置# iptables -t nat -A PREROUTING -p tcp -i $EXTIF --dport 80 \# -j DNAT --to-destination 192.168.1.210:80 # WWW# 5. 特殊的功能,包括 Windows 远程桌面所产生的规则,假设桌面主机为 1.2.3.4# iptables -t nat -A PREROUTING -p tcp -s 1.2.3.4 --dport 6000 \# -j DNAT --to-destination 192.168.100.10# iptables -t nat -A PREROUTING -p tcp -s 1.2.3.4 --sport 3389 \# -j DNAT --to-destination 192.168.100.20# 6. 最终将这些功能存储下来/etc/init.d/iptables save 特别留意上面脚本的特殊字体部分,基本上,只要修改一下最上方的接口部分,就能够运作这个防火墙了。不过因为每个人的环境都不相同,因此在设置完成后,依旧需要测试一下才行。不然,出了问题可就麻烦了。再来看一下关于 iptables.allow 的内容。假如我要让一个 140.116.44.0/24 这个网络的所有主机来源可以进入本机主机的话,那么这个文件的内容可以写成这样: 123456789101112vim iptables.allow#!/bin/bash# 下面是填写允许进入本机的其他网络或主机iptables -A INPUT -i $EXTIF -s 140.116.44.0/24 -j ACCEPT# 下面是关于阻挡的文件设置方法vim iptables.deny#!/bin/bash# 下面填写的是你要阻挡的那个 IPiptables -A INPUT -i $EXTIF -s 140.116.44.254 -j DROPchmod 700 iptables.* 将这3个文件的权限设置为 700 且只属于 root 的权限后,就能够直接执行 iptables.rule 了。不过要注意的是,在上面的案例当中,默认将所有的服务通道都关闭的。所以你必须要到本机防火墙的第 5 步骤 处将一些注释 (#) 取消才行。同样地,如果有其他更多的 port 想要开启时,同样需要增加额外的规则。 不过,还是如同前面所说的,这个 firewall 仅能提供基本的安全防护,其他的相关问题还需要再测试。此外,如果希望一开机就自动执行这个 script 的话,请将这个文件的完整文件名写入 /etc/rc.d/rc.local 中。 1234vim /etc/rc.d/rc.local...(其他省略)...# 1. Firewall/data/scripts/iptables/iptables.rule 事实上,这个脚本的最下面已经加入写入防火墙默认规则文件的功能,所以只要执行一次,就拥有最正确的规则了。上述的 rc.local 仅是预防万一而已。上述 3 个文件请不要在 Windows 系统上面编辑后才传送到 Linux 上运行,因为 Windows 系统的换行符问题,将可能导致该文件无法执行。建议传送到 Linux 后可以利用 dos2unix 指令去转换换行符等,就不会有问题了 这就是一个最简单的防火墙。同时,这个防火墙还可以具有最简答的 IP 路由器的功能,也就是在 iptables.rule 这个文件中的第二部分。]]></content>
<categories>
<category>iptables笔记</category>
</categories>
<tags>
<tag>iptables防火墙实例</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iptables防火墙数据包过滤软件]]></title>
<url>%2F2019%2F01%2F18%2Fiptables%E9%98%B2%E7%81%AB%E5%A2%99%E8%A7%84%E5%88%99%2F</url>
<content type="text"><![CDATA[iptables防火墙数据包过滤软件iptables 的表 (table) 与链 (chain) Filter(过滤器)主要跟进入 Linux 本机的数据包有关,是默认的 table。 INPUT:主要与想要进入我们 Linux 本机的数据包有关。 OUTPUT:主要与我们 Linux 本机所要送出的数据包有关。 FORWARD:这个与 Linux 本机比较没有关系,他可以转递数据包到后端的计算机中,与下列 NAT 的 table 相关性较高。 NAT(地址转换)是 Network Address Translation 的缩写, 这个表格主要在进行来源与目的之 IP 或 port 的转换,与 Linux 本机较无关,主要与 Linux 主机后的局域网络内计算机较有相关。 PREROUTING:在进行路由判断之前所要进行的规则 (DNAT/REDIRECT) POSTROUTING:在进行路由判断之后所要进行的规则 (SNAT/MASQUERADE) OUTPUT:与发送出去的数据包有关 Mangle(破坏者)这个表格主要是与特殊的数据包的路由标志有关,早期仅有 PREROUTING 及 OUTPUT 链,不过从 kernel 2.4.18 之后加入了 INPUT 及 FORWARD 链。由于这个表格与特殊标志相关性较高,所以在我们讨论的这种单纯的环境当中,较少使用 Mangle 这个表。 123456iptables [-t tables] [-L] [-nv]选项与参数:-t # 后面接 table ,例如 nat 或 filter ,若省略此项目,则使用默认的 filter-L # 列出目前的 table 的规则-n # 不进行 IP 与 HOSTNAME 的反查,显示信息的速度会快很多-v # 列出更多的信息,包括通过该规则的数据包总位数、相关的网络接口等 列出 filter table 三条链的规则123456789101112131415iptables -L -nChain INPUT (policy ACCEPT) # 针对 INPUT 链,且默认政策为可接受target prot opt source destination # 说明栏ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED # 第 1 条规则ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 # 第 2 条规则ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 # 第 3 条规则ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22 # 以下类推REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibitedChain FORWARD (policy ACCEPT) # 针对 FORWARD 链,且默认政策为可接受target prot opt source destinationREJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibitedChain OUTPUT (policy ACCEPT) # 针对 OUTPUT 链,且默认政策为可接受target prot opt source destination 列出 nat table 三条链的规则123456789iptables -t nat -L -nChain PREROUTING (policy ACCEPT)target prot opt source destinationChain POSTROUTING (policy ACCEPT)target prot opt source destinationChain OUTPUT (policy ACCEPT)target prot opt source destination 上面的输出结果中,每一个 Chain 就是前面提到的每个链。Chain 那一行括号里面的 policy 就是默认的策略。 target:代表进行的操作,ACCEPT 是放行,而 REJECT 则是拒绝,此外,尚有 DROP (丢弃) 的项目。 prot:代表使用的数据包协议,主要有 TCP、UDP 及 ICMP 3 种数据包格式。 opt:额外的选项说明。 source:代表此规则是针对哪个 来源IP 进行限制。 destination:代表此规则是针对哪个 目标IP 进行限制。 第一个范例因为没有加上 -t 的选项,所以默认就是 filter 这个表格内的 INPUT、OUTPUT、FORWARD 三条链的规则。若针对单机来说,INPUT 与 FORWARD 算是比较重要的管制防火墙链, 所以可以发现最后一条规则的政策是 REJECT(拒绝)!虽然 INPUT 与 FORWARD 的政策是放行 (ACCEPT), 不过最后一条规则就已经将全部的数据包都拒绝了! 这个命令的查看只是做格式化的查看,要详细解释每个规则会比较不容易。举例来说, 我们将 INPUT 的 5 条规则依据输出结果来说明一下,结果会变成: 只要是数据包状态为 RELATED、ESTABLISHED 就予以接受 只要数据包协议是 icmp 类型的,就予以放行 无论任何来源 (0.0.0.0/0) 且要去任何目标的数据包,不论任何数据包格式 (prot 为 all),一律都接受 只要是传给 port 22 的主动式连接 TCP 数据包就接受 全部的数据包信息一律拒绝 还有第 3 条规则怎么会所有的数据包信息都予以接受呢?如果都接受的话,那么后续的规则根本就没有用了! 其实那条规则只是针对每台主机都有的内部循环测试网络 (lo) 接口,如果没有列出接口,那么我们就很容易搞错。所以,建议使用 iptables-save 这个指令来查看防火墙规则。因为 iptables-save 会列出完整的防火墙规则,只是没有格式化输出而已。 123456789101112131415161718iptables-save [-t table]选项与参数:-t # 可以仅针对某些表来输出,例如仅针对 NAT 或 Filter 等iptables-save# Generated by iptables-save v1.4.7 on Fri Jul 22 15:51:52 2011*filter # 星号开头的指的是表,这里为 Filter:INPUT ACCEPT [0:0] # 冒号开头的指的是链,3条内建的链:FORWARD ACCEPT [0:0] # 3条内建链的策略都是 ACCEPT:OUTPUT ACCEPT [680:100461]-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # 针对 INPUT 的规则-A INPUT -p icmp -j ACCEPT-A INPUT -i lo -j ACCEPT # 这条很重要,针对本机内部接口开放-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT-A INPUT -j REJECT --reject-with icmp-host-prohibited-A FORWARD -j REJECT --reject-with icmp-host-prohibited # 针对 FORWARD 的规则COMMIT# Completed on Fri Jul 22 15:51:52 2011 上面输出结果来看,内容含有 lo 的那条规则当中,-i lo 指的就是由 lo 网卡进来的数据包。这样就清楚多了!因为有写到接口的关系,不像之前的 iptables -L -n。不过,既然这个规则不是我们想要的,那该如何修改呢?建议先删除规则再慢慢建立各个需要的规则。那如何清除规则? 12345678910iptables [-t tables] [-FXZ]选项与参数:-F # 清除所有的已制订的规则-X # 除掉所有用户 "自定义" 的 chain (应该说的是 tables )-Z # 将所有的 chain 的计数与流量统计都归零# 清除本机防火墙 (filter) 的所有规则iptables -Fiptables -Xiptables -Z 由于这三条命令会将本机防火墙的所有规则都清除,但不会改变默认策略 (policy) , 所以如果你不是在本机使用这三行命令时,很可能会将自己挡在门外(若 INPUT 设置为 DROP 时)要注意! 一般来说,我们在重新定义防火墙的时候,都会先将规则给清除掉。我们前面说到的 防火墙的规则顺序是有特殊意义的,所以,应当先清除掉规则,然后再一条一条来设置会比较容易一些。接下来就谈谈如何定义默认策略。 定义默认策略 (policy)清除规则之后,接下来就是要设置规则的策略。还记得策略指的是什么吗?当数据包不在我们设置的规则之内时,则该数据包的通过与否,是以 Policy 的设置为准,在本机的默认策略中,假设对于内部的用户有信心的话, 那么 Filter 内的 INPUT 链方面可以定义的比较严格一点,而 FORWARD 与 OUTPUT 则可以制订得松一些。通常都是将 INPUT 的 policy 定义为 DROP ,其他两个则定义为 ACCEPT。 至于 NAT table 则暂时先不理会。 12345iptables [-t nat] -P [INPUT,OUTPUT,FORWARD] [ACCEPT,DROP]选项与参数:-P # 定义策略 (Policy)。注意,这个 P 为大写ACCEPT # 该数据包可接受DROP # 该数据包直接丢弃,不会让 Client 端知道为何被丢弃 将本机的 INPUT 设置为 DROP ,其他设置为 ACCEPT 1234567891011121314iptables -P INPUT DROPiptables -P OUTPUT ACCEPTiptables -P FORWARD ACCEPTiptables-save# Generated by iptables-save v1.4.7 on Fri Jul 22 15:56:34 2011*filter:INPUT DROP [0:0]:FORWARD ACCEPT [0:0]:OUTPUT ACCEPT [0:0]COMMIT# Completed on Fri Jul 22 15:56:34 2011# 由于 INPUT 设置为 DROP 而又尚未有任何规则,所以上面的输出结果显示# 所有的数据包都无法进入的主机,是不通的防火墙设置!(网络连接是双向的) 看到输出的结果,INPUT 的设置被修改了,其他的 NAT table 3 条链的默认策略设置也是一样的方式。例如:iptables -t nat -P PREROUTING ACCEPT 就设置了 NAT table 的 PREROUTING 链为可接受。默认策略设置完毕后,来聊一聊关于各规则的数据包基础比对设置。 数据包的基础比对:IP,网络及接口设备开始来进行防火墙规则的数据包比对设置。既然是因特网,那么我们就由最基础的 IP,网络及端口,亦即是 OSI 的第三层谈起,再来谈谈设备网卡的限制等等。 12345678910111213141516171819202122232425iptables [-AI 链名] [-io 网络接口] [-p 协议] [-s 来源IP/网域] [-d 目标IP/网域] -j [ACCEPT|DROP|REJECT|LOG]选项与参数:-AI 链名 # 针对某的链进行规则的 "插入" 或 "累加" -A # 新增加一条规则,该规则增加在原本规则的最后面。 # 例如原本已经有四条规则,使用 -A 就可以加上第五条规则 -I # 插入一条规则。如果没有指定此规则的顺序,默认是插入变成第一条规则。 # 例如原本有四条规则,使用 -I 则该规则变成第一条,而原本 4 条变成 2~5 条 链 # 有 INPUT、OUTPUT、FORWARD 等,此链名称又与 -io 有关,请看下面-io 网络接口 # 设置数据包进出的接口规范 -i # 数据包所进入的那个网络接口,例如 eth0、lo 等接口。需与 INPUT 链配合 -o # 数据包所传出的那个网络接口,需与 OUTPUT 链配合-p 协定 # 设置此规则适用于哪种数据包格式 # 主要的数据包格式有:tcp、udp、icmp 及 all-s 来源 IP/网域 # 设置此规则之数据包的来源项目,可指定单纯的 IP 或包括网域,例如: IP # 192.168.0.100 网络 # 192.168.0.0/24, 192.168.0.0/255.255.255.0 均可 # 若规范为 “不许” 时,则加上 “!” 即可,例如: -s ! 192.168.100.0/24 # 表示不接受 192.168.100.0/24 发来的数据包-d 目标 IP/网络 # 同 -s ,只不过这里指的是目标的 IP 或网络-j # 后面接操作,主要的操作有接受(ACCEPT)、丢弃(DROP)、拒绝(REJECT)及记录(LOG) iptables 的基本参数就如同上面所示,仅谈到 IP 、网络与设备等的信息, 至于 TCP、UDP 数据包特有的端口 (port number) 与状态(如 SYN 标志)则在后面介绍。先让我们来看看最基础的几个规则,例如开放 lo 这个本机的接口以及某个 IP 来源。 设定 lo 成为受信任的装置,亦即进出 lo 的封包都予以接受 1iptables -A INPUT -i lo -j ACCEPT 仔细看上面并没有列出 -s、-d 等等的规则,这表示:不论数据包来自何处或去到哪里,只要是来自 lo 这个接口,就予以接受。这个概念挺重要的,就是“没有指定的项目,则表示该项目完全接受”。例如这个案例当中,关于 -s、-d 等的参数没有规定时,就代表不论什么值都会被接受。 这就是所谓的信任设备。假如主机有两张以太网卡,其中一张是对内部的网络,假设该网卡的代号为 eth1, 如果内部网络是可信任的,那么该网卡的进出数据包就通通会被接受,那就可以用 iptables -A INPUT -i eth1 -j ACCEPT 来将该设备设置为信任设备。不过,使用这个命令前要特别注意,因为这样等于该网卡没有任何防备了。 只要是来自内网的 (192.168.100.0/24) 的数据包通通接受 12iptables -A INPUT -i eth1 -s 192.168.100.0/24 -j ACCEPT# 由于是内网就接受,因此也可以称之为“信任网络” 只要是来自 192.168.100.10 就接受,但 192.168.100.230 这个“恶意”来源就丢弃 123iptables -A INPUT -i eth1 -s 192.168.100.10 -j ACCEPTiptables -A INPUT -i eth1 -s 192.168.100.230 -j DROP# 针对单一 IP 来源,可视为信任主机或者是不信任的恶意来源 123456789101112iptables-save# Generated by iptables-save v1.4.7 on Fri Jul 22 16:00:43 2011*filter:INPUT DROP [0:0]:FORWARD ACCEPT [0:0]:OUTPUT ACCEPT [17:1724]-A INPUT -i lo -j ACCEPT-A INPUT -s 192.168.100.0/24 -i eth1 -j ACCEPT-A INPUT -s 192.168.100.10/32 -i eth1 -j ACCEPT-A INPUT -s 192.168.100.230/32 -i eth1 -j DROPCOMMIT# Completed on Fri Jul 22 16:00:43 2011 这就是最简单的防火墙规则的设置与查看方式。不过,在上面的案例中,其实也可以发现到有两条规则可能有问题,那就是上面的特殊字体圈起来的规则顺序。明明已经放行了 192.168.100.0/24,所以 192.168.100.230 的规则就不可能会被用到了!这就是防火墙设置的问题。那该怎么办?重写。那如果想要记录某个规则的记录怎么办?可以这样做: 1234iptables -A INPUT -s 192.168.2.200 -j LOGiptables -L -ntarget prot opt source destinationLOG all -- 192.168.2.200 0.0.0.0/0 LOG flags 0 level 4 输出结果的最左边出现 LOG。只要有数据包来自 192.168.2.200 这个 IP 时,那么该数据包的相关信息就会被写入到内核日志文件,即 /var/log/messages 这个文件当中。然后该数据包会继续进行后续的规则比对。所以说,LOG 这个动作仅在进行记录而已,并不会影响到这个数据包的其他规则比对。接下来我们分别来看看 TCP、UDP 以及 ICMP 数据包的其他规则比对。 TCP、UDP 的规则比对:针对端口设置网络中各种不同的数据包格式,TCP 与 UDP,比较特殊的就是那个端口 (port),在 TCP 方面则另外有所谓的连接数据包状态,包括最常见的 SYN 主动连接的数据包格式。那么如何针对这两种数据包格式进行防火墙规则的设置呢?可以这样看: 123456iptables [-AI 链] [-io 网络接口] [-p tcp,udp] \> [-s 来源IP/网络] [--sport 端口范围] \> [-d 目标IP/网络] [--dport 端口范围] -j [ACCEPT|DROP|REJECT]选项与参数:--sport 端口范围 # 限制来源的端口号码,端口号码可以是连续的,例如 1024:65535--dport 端口范围 # 限制目标的端口号码 事实上就是多了 --sport 及 --dport 这两个选项,重点在 port 上面。不过需要特别注意,因为只有 TCP 与 UDP 数据包具有端口,因此要想使用 --dport、--sport 时,得要加上 -p tcp 或 -p udp 的参数才会成功。下面让我们来进行几个小测试: 1234567# 想要连接进入本机 port 21 的数据包都阻挡掉iptables -A INPUT -i eth0 -p tcp --dport 21 -j DROP# 想连到本台主机的网上邻居 (upd port 137,138 tcp port 139,445) 就放行iptables -A INPUT -i eth0 -p udp --dport 137:138 -j ACCEPTiptables -A INPUT -i eth0 -p tcp --dport 139 -j ACCEPTiptables -A INPUT -i eth0 -p tcp --dport 445 -j ACCEPT 我们不但可以利用 UDP 与 TCP 协议所拥有的端口号码来进行某些服务的开放或关闭,还可以综合处理。例如,只要来自 192.168.1.0/24 的 1024:65535 端口的数据包,且想要连接到本机的 ssh port 就予以阻挡,可以这样做: 12iptables -A INPUT -i eth0 -p tcp -s 192.168.1.0/24 \> --sport 1024:65534 --dport ssh -j DROP 如果忘记加上 -p tcp 就使用了 --dport 时,会发生以下错误: 1`[root@www ~]# iptables -A INPUT -i eth0 --dport 21 -j DROP iptables v1.4.7: unknown option `--dport' Try `iptables -h' or 'iptables --help' for more information.` 你应该会觉得很奇怪,怎么 “–dport” 会是未知的参数 (arg) 呢?这是因为没有加上 -p tcp 或 -p udp 的缘故。这一点很重要。 除了端口之外,TCP 数据包还有特殊的标志。最常见的就是主动连接的 SYN 标志了。在 iptables 里面还支持 “–syn” 的处理方式,我们以下面的例子来说明: 123# 将来自任何地方来源 port 1:1023 的主动连接到本机端的 1:1023 连接丢弃iptables -A INPUT -i eth0 -p tcp --sport 1:1023 \> --dport 1:1023 --syn -j DROP 一般来说,Client 端启用的 port 都大于 1024,而 Server 端启用的端口都小于 1023。所以我们可以丢弃来自远程的小于 1023 的端口数据的主动连接,但不适用于 FTP 的主动连接中。 iptables 外挂模块:mac 与 state在 kernel 2.2 以前使用 ipchains 管理防火墙时,通常会让系统管理员相当头痛。因为 ipchains 没有所谓的数据包状态模块,因此我们必须要针对数据包的进、出方向进行控制。例如,如果想要连接到远程主机的 port 22 时,必须要针对两条规则来设置: 本机端的 1024:65535 到远程的 port 22 必须要放行 (OUTPUT 链)。 远程主机 port 22 到本机的 1024:65535 必须放行 (INPUT 链)。 这会很麻烦。因为要连接到 10 部主机的 port 22 时,假设 OUTPUT 为默认开启 (ACCEPT), 那么久需要填写 10 行规则,让那 10 台远程主机的 port 22 可以连接到本地端主机上。如果开启全部的 port 22,则又担心某些恶意主机会主动以 port 22 连接到本地主机。同理,如果要让本地端主机可以连到外部的 port 80 (WWW 服务),那就更不得了。因为网络连接是双向的,一个很重要的概念! iptables 可以帮我们免除这个困扰。它可以通过一个状态模块来分析这个想要进入的数据包是否为刚刚发出去的响应,如果是刚刚发出去的响应,那么就可以予以接受放行。这样就不用考虑远程主机是否连接进来的问题了。那如何实现呢?看看下面的语法: 123456789101112131415iptables -A INPUT [-m state] [--state 状态]选项与参数:-m # 一些 iptables 的外挂模块,主要常见的有: state # 状态模块 mac # 网络卡硬件地址 (hardware address)--state # 一些封包的状态,主要有: INVALID # 无效的数据包,例如数据破损的数据包状态 ESTABLISHED # 已经连接成功的连接状态 NEW # 想要新建立连接的数据包状态 RELATED # 这个最常用!表示这个数据包是与主机发送出去的数据包有关# 只要已建立连接或与已发出请求相关数据包就予以通过,不合法的数据报包就丢弃iptables -A INPUT -m state \> --state RELATED,ESTABLISHED -j ACCEPTiptables -A INPUT -m state --state INVALID -j DROP 这样,iptables 就会主动分析出该数据包是否为响应状态,若是的话,就直接予以接受。这样不需要针对响应的数据包来撰写个别的防火墙规则了。下面我们继续谈一下 iptables 的另一个外挂, 那就是针对网卡来进行放行与防御: 12345# 针对局域网络内的 aa:bb:cc:dd:ee:ff 主机开放其连接iptables -A INPUT -m mac --mac-source aa:bb:cc:dd:ee:ff \> -j ACCEPT选项与参数:--mac-source # 就是来源主机的 MAC 如果局域网当中有某些网络高手,老是可以通过修改 IP 去尝试通过路由器往外跑,那这时该怎么办? 难道将整个局域网拒绝?并不需要的,可以通过之前谈到的 ARP 相关概念,去捕捉到那台主机的 MAC,然后通过以上这个机制,将该主机整个 DROP 掉即可。不管他改了什么 IP,除非他知道你是用网卡的 MAC 来管理,否则他是出不去的。 其实 MAC 也是可以伪装的,可以通过某些软件来修改网卡的 MAC。不过,这里我们是假设 MAC 是无法修改的情况来说明的。此外,MAC 是不能跨路由的,因此上述的案例中才特别说明是在局域网内,而不是指 Internet 外部的来源。 ICMP 数据包规则的比对:针对是否响应 ping 来设计在 ICMP 协议当中我们知道 ICMP 的类型很多,而且很多 ICMP 数据包的类型都是用来进行网络检测的。所以最好不要将所有的 ICMP 数据包都丢弃。如果主机不是作为路由器时,通常我们会把 ICMP type 8 (echo request) 拿掉,这样远程主机就不知道我们是否存在,也不会接受 ping 的响应。ICMP 数据包格式的处理是这样的: 123456789101112131415iptables -A INPUT [-p icmp] [--icmp-type 类型] -j ACCEPT选项与参数:--icmp-type # 后面必须要接 ICMP 的数据包类型,也可以使用代号, 例如 8 代表 echo request 的意思# 让 0,3,4,11,12,14,16,18 的 ICMP type 可以进入本机vim somefile#!/bin/bashicmp_type="0 3 4 11 12 14 16 18"for typeicmp in $icmp_type; do iptables -A INPUT -i eth0 -p icmp --icmp-type $typeicmp -j ACCEPTdonesh somefile 这样就能够开放部分的 ICMP 数据包格式进入本机进行网络检测的工作了。不过,如果主机是作为局域网的路由器,那么建议 ICMP 数据包还是要全部放行才好,因为客户端检测网络时,常常会使用 ping 来测试到路由器的线路是否畅通。所以不要将路由器的 ICMP 关掉,会导致相关应用异常。 简单的客户端防火墙设计与防火墙规则存储经过上述的本机 iptables 语法分析后,接下来我们来想想,如果将 Linux 主机作为客户端且不提供网络服务时,应该如何设计防火墙呢?其实,只要分析一下 CentOS 默认的防火墙规则就会知道,理论上,应该要有的规则如下: 规则归零:清除所有已经存在的规则 (iptables -F 等) 默认策略:除了将 INPUT 这个自定义链设为 DROP 外,其他默认为 ACCEPT。 信任本机:由于 lo 对本机来说是相当重要的,因此 lo 必须设置为信任设备。 回应数据包:让本机通过主动向外发出请求而响应的数据包可以进入本机 (ESTABLISHED、RELATED) 信任用户:这是非必要的,可在想要让本地网络的来源使用主机资源时设置。 这就是最简单的防火墙,通过第 2 步骤可以阻挡所有远程的来源数据包,而通过第 4 步骤可以允许远程主机响应数据包进入主机,再让本机的 lo 这个内部循环设备放行,这样,一台 Client 专用的防火墙规则就配置好了。具体设置时,可以在某个 script 上面这样做: 12345678910111213141516171819202122232425vim firewall.sh#!/bin/bashPATH=/sbin:/bin:/usr/sbin:/usr/bin; export PATH# 1. 清除规则iptables -Fiptables -Xiptables -Z# 2. 设置策略iptables -P INPUT DROPiptables -P OUTPUT ACCEPTiptables -P FORWARD ACCEPT# 3~5. 制订各项规则iptables -A INPUT -i lo -j ACCEPTiptables -A INPUT -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT# iptables -A INPUT -i eth0 -s 192.168.1.0/24 -j ACCEPT# 6. 写入防火墙规则配置文件/etc/init.d/iptables savesh firewall.shiptables: Saving firewall rules to /etc/sysconfig/iptables:[ OK ] 其实防火墙也是一个服务,并可以通过 chkconfig --list iptables 或 systemctl -l status iptables 去查看。因此,若要让这次修改的各种设置在下次开机时还保存,那就需要对 /etc/init.d/iptables save 这个指令加参数。 因此,现在将存储的操作写入 firewall.sh 脚本中,比较单纯些。通过以上设置,现在,Linux 主机已经有相当的保护了,只是如果想要作为服务器,或者是作为路由器,那就需要自行加上某些自定义的规则。 Tips: 其实,如果你对 Linux 够熟悉的话,直接去修改 /etc/sysconfig/iptables 然后重启 iptables 这个服务,那防火墙规则就会在开机后持续存在了。 IPv4 的核心管理功能:/proc/sys/net/ipv4/除了 iptables 这个防火墙软件之外,Linux kernel 2.6 还提供了很多内核默认的攻击阻挡机制。由于是内核的网络功能,所以相关的设置数据都是放置在 /proc/sys/net/ipv4/ 这个目录当中。 至于该目录下各个文件的详细资料,可以参考内核的说明文件(你得要先安装 kernel-doc 软件): /usr/share/doc/kernel-doc-2.6.32/Documentation/networking/ip-sysctl.txt鸟哥的网站上也放了一份备份: http:/linux.vbird.org/linux_server/0250simple_firewall/ip-sysctl.txt有兴趣的话应该要自行去查看。我们下面来介绍几个简单的文件。 1. /proc/sys/net/ipv4/tcp_syncookies我们在前面谈到所谓的阻断式服务 (DoS) 攻击法当中的一种方式,就是利用 TCP 数据包的 SYN 三次握手原理实现的,这种方式称为 SYN Flooding。那如何预防这种方式的攻击呢?我们可以启用内核的 SYN Cookie 模块。这个 SYN Cookie 模块可以在系统用来启动随机连接的端口 (1024:65535) 即将用完时自动启动。 当启动 SYN Cookie 时,主机在发送 SYN/ACK 确认数据包前,会要求 Client 端在短时间内回复一个序号,这个序号包含许多原 SYN 数据包内的信息,包括 IP、port 等。若 Client 端可以回复正确的序号,那么主机就确定该数据包为可信的,因此会发送 SYN/ACK 数据包,否则就不理会此数据包。 通过这一机制可以大大降低无效的 SYN 等待端口,避免 SYN Flooding 的 DoS 攻击。那么如何启动这个模块呢?很简单,这样做即可: 1echo "1" > /proc/sys/net/ipv4/tcp_syncookies 但是这个设置值由于违反 TCP 的三次握手(因为主机在发送 SYN/ACK 之前需要先等待 Client 的序号响应),所以可能会造成某些服务的延迟现象,例如 SMTP (Mail Server)。不过总的来说,这个设置值还是不错,只是不适合用在负载已经很高的服务器内。因为负载太高的主机有时会让内核误判遭受 SYN Flooding 的攻击。 如果是为了系统的 TCP 数据包连接优化,则可以参考 tcp_max_syn_backlog、tcp_synack_retries、tcp_abort_on_overflow 这几个设置值的意义。 2. /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts 阻断式服务常见的是 SYN Flooding,不过,我们知道系统其实可以接受使用 ping 的响应,而 ping 的数据包数据量可以很大。想象一个状况,如果有个搞破坏的人使用 1000 台主机传送 ping 给你的主机,而且每个 ping 都高达数百 Kbytes 时,你的网络带宽会怎样?要么就是带宽被吃光,要么系统可能会宕机。这种方式分别被称为 ping flooding(不断发 ping)及 ping of death(发送大的 ping 数据包)。 那如何避免呢?取消 ICMP 类型 8 的 ICMP 数据包回应就是了。我们可以通过防火墙来阻挡,这也是建议的方式。当然也可以让内核自动取消 ping 的响应。不过,某些局域网络内常见的服务(例如动态 IP 分配 DHCP 协议)会使用 ping 的方式来侦测是否有重复的 IP,所以最好不要取消所有的 ping 响应比较好。 内核取消 ping 回应的设置值有两个,分别是 /proc/sys/net/ipv4 内的 icmp_echo_ignore_broadcasts(仅有 ping broadcast 地址时才取消 ping 的回应)及 icmp_echo_ignore_all(全部的 ping 都不回应)。建议设置 icmp_echo_ignore_broadcasts。可以这么做: 12echo "1" > \> /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts 3. /proc/sys/net/ipv4/conf/网络接口/* Linux 的内核还可以针对不同的网络接口进行不一样的参数设置。网络接口的相关设置放置在 /proc/sys/net/ipv4/conf/ 中,每个接口都以接口代号做为其代表,例如 eth0 接口的相关设置数据在 /proc/sys/net/ipv4/conf/eth0/ 内。那么网络接口的设置数据有哪些需要注意的呢? 大概有下面这几个: rp_filter:称为逆向路径过滤 (Reverse Path Filtering),可以通过分析网络接口的路由信息,配合数据包的来源地址,来分析该数据包是否为合理。举例来说,你有两张网卡,eth0 为 192.168.1.10/24,eth1 为 public IP。那么当有一个数据包自称来自 eth1,但是其 IP 来源为 192.168.1.200,那这个数据包就不合理,应予以丢弃。这个设置值建议启动。 log_martians:这个设置数据可以用来启动记录不合法的 IP 来源的功能,举例来说,包括来源为 0.0.0.0、127.x.x.x、及 Class E 的 IP 都是不合法的,因为这些来源的 IP 不应该应用于 Internet。记录的数据默认放置到内核放置的日志文件 /var/log/messages。 accept_source_route:或许某些路由器会启动这个设置值,不过目前的设备很少使用到这种来源路由,可以取消这个设置值。 accept_redirects:当你在同一个实体网络内架设一台路由器,但这个实体网络有两个 IP 网络,例如 192.168.0.0/24、 192.168.1.0/24。此时 192.168.0.100 想要向 192.168.1.100 传送信息时,路由器可能会传送一个 ICMP redirect 数据包告知 192.168.0.100 直接传送数据给 192.168.1.100 即可,而不需通过路由器。因为 192.168.0.100 与 192.168.1.100 确实是在同一个实体线路上(两者可以直接互通),所以路由器会告知来源 IP 使用最短路径去传递数据。但由于那两台主机在不同的 IP 网段,所以还是无法实际传递信息。这个设置也可能会产生一些轻微的安全风险,所以建议关闭。 send_redirects:与上一个类似,只是此值为发送一个 ICMP redirect 数据包。同样建议关闭。 虽然可以使用 echo "1" > /proc/sys/net/ipv4/conf/???/rp_filter 来启动这个项目,不过,比较建议修改系统设置值,即 /etc/sysctl.conf 这个文件。假设我们仅有 eth0 这个以太接口,而且上述的功能要全部启动,那可以这样做: 1234567891011vim /etc/sysctl.conf# Adding by VBird 2011/01/28net.ipv4.tcp_syncookies = 1net.ipv4.icmp_echo_ignore_broadcasts = 1net.ipv4.conf.all.rp_filter = 1net.ipv4.conf.default.rp_filter = 1net.ipv4.conf.eth0.rp_filter = 1net.ipv4.conf.lo.rp_filter = 1....(以下省略)....sysctl -p 参考文档http://cn.linux.vbird.org/linux_server/0250simple_firewall_3.php]]></content>
<categories>
<category>iptables笔记</category>
</categories>
<tags>
<tag>iptables防火墙数据包过滤软件</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Django 2.x 后生成数据库表时的报错]]></title>
<url>%2F2019%2F01%2F11%2FDjango-2.x%E5%90%8E%E7%94%9F%E6%88%90%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E6%97%B6%E7%9A%84%E6%8A%A5%E9%94%99%2F</url>
<content type="text"><![CDATA[Django 2.x 后生成数据库表时的报错问题 使用 Django 2.1.1 的时候发现执行 makemigrations 和 migrate 是会报错,少位置参数 on_delete,查了一下是因为指定外键的方式不对。TypeError: __init__() missing 1 required positional argument: 'on_delete' 1234567891011121314151617from django.db import models# 出版社class Publisher(models.Model): # 自增 id 主键 id = models.AutoField(primary_key=True) # 创建 varchar(64) 唯一的,不为空的字段 name = models.CharField(max_length=64, null=False, unique=True)# 书class Book(models.Model): # 自增 id 主键 id = models.AutoField(primary_key=True) # 创建 varchar(64) 唯一的,不为空的字段 name = models.CharField(max_length=64, null=False, unique=True) # 和出版社关联的的外键字段 publisher_id = models.ForeignKey(to="Publisher") 在创建模型类关联外键时,报如下错误 1TypeError: __init__() missing 1 required positional argument: 'on_delete' 解决方法1234将publisher_id = models.ForeignKey(to="Publisher")改成publisher_id = models.ForeignKey(to="Publisher", on_delete=models.CASCADE) 即在外键值的后面加上 on_delete=models.CASCADE 原因:在 Django 2.0 后,定义外键和一对一关系的时候需要加 on_delete 选项,此参数为了避免两个表里的数据不一致问题。不然会报错 1TypeError: __init__() missing 1 required positional argument: 'on_delete' 举例说明12user=models.OneToOneField(User)owner=models.ForeignKey(UserProfile) 需要改成 1234# 在老版本这个参数(models.CASCADE)是默认值user=models.OneToOneField(User,on_delete=models.CASCADE)# 在老版本这个参数(models.CASCADE)是默认值owner=models.ForeignKey(UserProfile,on_delete=models.CASCADE) 参数说明123456789on_delete 有 DO_NOTHING、CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET() 六个可选择的值DO_NOTHING # 什么都不做CASCADE # 此值设置,是级联删除PROTECT # 此值设置,是会报完整性错误SET_NULL # 此值设置,会把外键设置为null,前提是允许为nullSET_DEFAULT # 此值设置,会把设置为外键的默认值SET() # 此值设置,会调用外面的值,可以是一个函数一般情况下使用 CASCADE 就可以了 参考文档https://www.cnblogs.com/phyger/p/8035253.htmlhttps://blog.csdn.net/m0_38109046/article/details/82660038]]></content>
<categories>
<category>Django笔记</category>
</categories>
<tags>
<tag>Django 2.x 后生成数据库表时的报错</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Kubernetes部署rook+ceph存储系统]]></title>
<url>%2F2019%2F01%2F07%2FKubernetes%E9%83%A8%E7%BD%B2rook%2Bceph%E5%AD%98%E5%82%A8%E7%B3%BB%E7%BB%9F%2F</url>
<content type="text"><![CDATA[Kubernetes部署rook+ceph存储系统 rook简介Rook官网:https://rook.io 容器的持久化存储容器的持久化存储是保存容器存储状态的重要手段,存储插件会在容器里挂载一个基于网络或者其他机制的远程数据卷,使得在容器里创建的文件,实际上是保存在远程存储服务器上,或者以分布式的方式保存在多个节点上,而与当前宿主机没有任何绑定关系。这样,无论你在其他哪个宿主机上启动新的容器,都可以请求挂载指定的持久化存储卷,从而访问到数据卷里保存的内容。由于 Kubernetes 本身的松耦合设计,绝大多数存储项目,比如 Ceph、GlusterFS、NFS 等,都可以为 Kubernetes 提供持久化存储能力。 Ceph分布式存储系统Ceph是一种高度可扩展的分布式存储解决方案,提供对象、文件和块存储。在每个存储节点上,您将找到Ceph存储对象的文件系统和Ceph OSD(对象存储守护程序)进程。在Ceph集群上,您还可以找到Ceph MON(监控)守护程序,它们确保Ceph集群保持高可用性。 RookRook 是一个开源的cloud-native storage编排, 提供平台和框架;为各种存储解决方案提供平台、框架和支持,以便与云原生环境本地集成。Rook 将存储软件转变为自我管理、自我扩展和自我修复的存储服务,它通过自动化部署、引导、配置、置备、扩展、升级、迁移、灾难恢复、监控和资源管理来实现此目的。Rook 使用底层云本机容器管理、调度和编排平台提供的工具来实现它自身的功能。Rook 目前支持Ceph、NFS、Minio Object Store和CockroachDB。 Rook使用Kubernetes原语使Ceph存储系统能够在Kubernetes上运行。下图说明了Ceph Rook如何与Kubernetes集成: 随着Rook在Kubernetes集群中运行,Kubernetes应用程序可以挂载由Rook管理的块设备和文件系统,或者可以使用S3 / Swift API提供对象存储。Rook oprerator自动配置存储组件并监控群集,以确保存储处于可用和健康状态。Rook oprerator是一个简单的容器,具有引导和监视存储集群所需的全部功能。oprerator将启动并监控ceph monitor pods和OSDs的守护进程,它提供基本的RADOS存储。oprerator通过初始化运行服务所需的pod和其他组件来管理池,对象存储(S3 / Swift)和文件系统的CRD。oprerator将监视存储后台驻留程序以确保群集正常运行。Ceph mons将在必要时启动或故障转移,并在群集增长或缩小时进行其他调整。oprerator还将监视api服务请求的所需状态更改并应用更改。Rook oprerator还创建了Rook agent。这些agent是在每个Kubernetes节点上部署的pod。每个agent都配置一个Flexvolume插件,该插件与Kubernetes的volume controller集成在一起。处理节点上所需的所有存储操作,例如附加网络存储设备,安装卷和格式化文件系统。 该rook容器包括所有必需的Ceph守护进程和工具来管理和存储所有数据 - 数据路径没有变化。 rook并没有试图与Ceph保持完全的忠诚度。 许多Ceph概念(如placement groups和crush maps)都是隐藏的,因此您无需担心它们。 相反,Rook为管理员创建了一个简化的用户体验,包括物理资源,池,卷,文件系统和buckets。 同时,可以在需要时使用Ceph工具应用高级配置。Rook在golang中实现。Ceph在C ++中实现,其中数据路径被高度优化。我们相信这种组合可以提供两全其美的效果。 部署环境准备官方参考:root项目地址:https://github.com/rook/rookrook官方参考文档:https://rook.github.io/docs/rook/v0.9/ceph-quickstart.html kubernetes集群准备kubeadm部署3节点kubernetes1.13.1集群(,master节点x1,node节点x2),集群部署参考:https://blog.csdn.net/networken/article/details/84991940集群节点信息: 123192.168.92.56 k8s-master192.168.92.57 k8s-node1192.168.92.58 k8s-node2 在集群中至少有三个节点可用,满足ceph高可用要求,这里已配置master节点使其支持运行pod。 rook使用存储方式rook默认使用所有节点的所有资源,rook operator自动在所有节点上启动OSD设备,Rook会用如下标准监控并发现可用设备: 设备没有分区 设备没有格式化的文件系统 Rook不会使用不满足以上标准的设备。另外也可以通过修改配置文件,指定哪些节点或者设备会被使用。 添加新磁盘这里在所有节点添加1块50GB的新磁盘:/dev/sdb,作为OSD盘,提供存储空间,添加完成后扫描磁盘,确保主机能够正常识别到: 12345678# 扫描 SCSI总线并添加 SCSI 设备for host in $(ls /sys/class/scsi_host) ; do echo "- - -" > /sys/class/scsi_host/$host/scan; done# 重新扫描 SCSI 总线for scsi_device in $(ls /sys/class/scsi_device/); do echo 1 > /sys/class/scsi_device/$scsi_device/device/rescan; done# 查看已添加的磁盘,能够看到sdb说明添加成功lsblk 本次搭建的基本原理图: 无另外说明,以下全部操作都在master节点执行。 部署Rook Operator克隆rook github仓库到本地 12git clone https://github.com/rook/rook.gitcd rook/cluster/examples/kubernetes/ceph/ 执行yaml文件部署rook系统组件:1234567891011121314151617[centos@k8s-master ceph]$ kubectl apply -f operator.yamlnamespace/rook-ceph-system createdcustomresourcedefinition.apiextensions.k8s.io/cephclusters.ceph.rook.io createdcustomresourcedefinition.apiextensions.k8s.io/cephfilesystems.ceph.rook.io createdcustomresourcedefinition.apiextensions.k8s.io/cephobjectstores.ceph.rook.io createdcustomresourcedefinition.apiextensions.k8s.io/cephobjectstoreusers.ceph.rook.io createdcustomresourcedefinition.apiextensions.k8s.io/cephblockpools.ceph.rook.io createdcustomresourcedefinition.apiextensions.k8s.io/volumes.rook.io createdclusterrole.rbac.authorization.k8s.io/rook-ceph-cluster-mgmt createdrole.rbac.authorization.k8s.io/rook-ceph-system createdclusterrole.rbac.authorization.k8s.io/rook-ceph-global createdclusterrole.rbac.authorization.k8s.io/rook-ceph-mgr-cluster createdserviceaccount/rook-ceph-system createdrolebinding.rbac.authorization.k8s.io/rook-ceph-system createdclusterrolebinding.rbac.authorization.k8s.io/rook-ceph-global createddeployment.apps/rook-ceph-operator created[centos@k8s-master ~]$ 如上所示,它会创建如下资源: namespace:rook-ceph-system,之后的所有rook相关的pod都会创建在该namespace下面 CRD:创建五个CRDs,.ceph.rook.io role & clusterrole:用户资源控制 serviceaccount:ServiceAccount资源,给Rook创建的Pod使用 deployment:rook-ceph-operator,部署rook ceph相关的组件 部署rook-ceph-operator过程中,会触发以DaemonSet的方式在集群部署Agent和Discoverpods。operator会在集群内的每个主机创建两个pod:rook-discover,rook-ceph-agent: 12345678910[centos@k8s-master ~]$ kubectl get pod -n rook-ceph-system -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESrook-ceph-agent-49w7t 1/1 Running 0 7m48s 192.168.92.57 k8s-node1 <none> <none>rook-ceph-agent-dpxkq 1/1 Running 0 111s 192.168.92.58 k8s-node2 <none> <none>rook-ceph-agent-wb6r8 1/1 Running 0 7m48s 192.168.92.56 k8s-master <none> <none>rook-ceph-operator-85d64cfb99-2c78k 1/1 Running 0 9m3s 10.244.1.2 k8s-node1 <none> <none>rook-discover-597sk 1/1 Running 0 7m48s 10.244.0.4 k8s-master <none> <none>rook-discover-7h89z 1/1 Running 0 111s 10.244.2.2 k8s-node2 <none> <none>rook-discover-hjdjt 1/1 Running 0 7m48s 10.244.1.3 k8s-node1 <none> <none>[centos@k8s-master ~]$ 创建rook Cluster当检查到Rook operator, agent, and discover pods已经是running状态后,就可以部署roo cluster了。执行yaml文件结果: 1234567891011121314[centos@k8s-master ceph]$ kubectl apply -f cluster.yaml namespace/rook-ceph createdserviceaccount/rook-ceph-osd createdserviceaccount/rook-ceph-mgr createdrole.rbac.authorization.k8s.io/rook-ceph-osd createdrole.rbac.authorization.k8s.io/rook-ceph-mgr-system createdrole.rbac.authorization.k8s.io/rook-ceph-mgr createdrolebinding.rbac.authorization.k8s.io/rook-ceph-cluster-mgmt createdrolebinding.rbac.authorization.k8s.io/rook-ceph-osd createdrolebinding.rbac.authorization.k8s.io/rook-ceph-mgr createdrolebinding.rbac.authorization.k8s.io/rook-ceph-mgr-system createdrolebinding.rbac.authorization.k8s.io/rook-ceph-mgr-cluster createdcephcluster.ceph.rook.io/rook-ceph created[centos@k8s-master ~]$ 如上所示,它会创建如下资源: namespace:rook-ceph,之后的所有Ceph集群相关的pod都会创建在该namespace下 serviceaccount:ServiceAccount资源,给Ceph集群的Pod使用 role & rolebinding:用户资源控制 cluster:rook-ceph,创建的Ceph集群 Ceph集群部署成功后,可以查看到的pods如下,其中osd数量取决于你的节点数量: 12345678910111213[centos@k8s-master ~]$ kubectl get pod -n rook-ceph -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESrook-ceph-mgr-a-8649f78d9b-hlg7t 1/1 Running 0 3h30m 10.244.2.6 k8s-node2 <none> <none>rook-ceph-mon-a-7c7df4b5bb-984x8 1/1 Running 0 3h31m 10.244.0.5 k8s-master <none> <none>rook-ceph-mon-b-7b9bc8b6c4-8trmz 1/1 Running 0 3h31m 10.244.1.4 k8s-node1 <none> <none>rook-ceph-mon-c-54b5fb5955-5dgr7 1/1 Running 0 3h30m 10.244.2.5 k8s-node2 <none> <none>rook-ceph-osd-0-b9bb5df49-gt4vs 1/1 Running 0 3h29m 10.244.0.7 k8s-master <none> <none>rook-ceph-osd-1-9c6dbf797-2dg8p 1/1 Running 0 3h29m 10.244.2.8 k8s-node2 <none> <none>rook-ceph-osd-2-867ddc447d-xkh7k 1/1 Running 0 3h29m 10.244.1.6 k8s-node1 <none> <none>rook-ceph-osd-prepare-k8s-master-m8tvr 0/2 Completed 0 3h29m 10.244.0.6 k8s-master <none> <none>rook-ceph-osd-prepare-k8s-node1-jf7qz 0/2 Completed 1 3h29m 10.244.1.5 k8s-node1 <none> <none>rook-ceph-osd-prepare-k8s-node2-tcqdl 0/2 Completed 0 3h29m 10.244.2.7 k8s-node2 <none> <none>[centos@k8s-master ~]$ 可以看出部署的Ceph集群有: Ceph Monitors:默认启动三个ceph-mon,可以在cluster.yaml里配置 Ceph Mgr:默认启动一个,可以在cluster.yaml里配置 Ceph OSDs:根据cluster.yaml里的配置启动,默认在所有的可用节点上启动上述Ceph组件对应kubernetes的kind是deployment: 12345678910[centos@k8s-master ~]$ kubectl -n rook-ceph get deploymentNAME READY UP-TO-DATE AVAILABLE AGErook-ceph-mgr-a 1/1 1 1 5h34mrook-ceph-mon-a 1/1 1 1 5h36mrook-ceph-mon-b 1/1 1 1 5h35mrook-ceph-mon-c 1/1 1 1 5h35mrook-ceph-osd-0 1/1 1 1 5h34mrook-ceph-osd-1 1/1 1 1 5h34mrook-ceph-osd-2 1/1 1 1 5h34m[centos@k8s-master ~]$ 删除Ceph集群如果要删除已创建的Ceph集群,可执行下面命令: 1kubectl delete -f cluster.yaml 删除Ceph集群后,在之前部署Ceph组件节点的/var/lib/rook/目录,会遗留下Ceph集群的配置信息。若之后再部署新的Ceph集群,先把之前Ceph集群的这些信息删除,不然启动monitor会失败; 12345678910cat clean-rook-dir.shhosts=( k8s-master k8s-node1 k8s-node2)for host in ${hosts[@]} ; do ssh $host "rm -rf /var/lib/rook/*"done 配置ceph dashboard在cluster.yaml文件中默认已经启用了ceph dashboard,查看dashboard的service: 12345678[centos@k8s-master ~]$ kubectl get service -n rook-cephNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGErook-ceph-mgr ClusterIP 10.107.77.188 <none> 9283/TCP 3h33mrook-ceph-mgr-dashboard ClusterIP 10.96.135.98 <none> 8443/TCP 3h33mrook-ceph-mon-a ClusterIP 10.105.153.93 <none> 6790/TCP 3h35mrook-ceph-mon-b ClusterIP 10.105.107.254 <none> 6790/TCP 3h34mrook-ceph-mon-c ClusterIP 10.104.1.238 <none> 6790/TCP 3h34m[centos@k8s-master ~]$ rook-ceph-mgr-dashboard监听的端口是8443,创建nodeport类型的service以便集群外部访问。 1kubectl apply -f rook/cluster/examples/kubernetes/ceph/dashboard-external-https.yaml 查看一下nodeport暴露的端口,这里是32483端口: 1234[centos@k8s-master ~]$ kubectl get service -n rook-ceph | grep dashboardrook-ceph-mgr-dashboard ClusterIP 10.96.135.98 <none> 8443/TCP 3h37mrook-ceph-mgr-dashboard-external-https NodePort 10.97.181.103 <none> 8443:32483/TCP 3h29m[centos@k8s-master ~]$ 获取Dashboard的登陆账号和密码 1234[centos@k8s-master ~]$ MGR_POD=`kubectl get pod -n rook-ceph | grep mgr | awk '{print $1}'`[centos@k8s-master ~]$ kubectl -n rook-ceph logs $MGR_POD | grep password2019-01-03 05:44:00.585 7fced4782700 0 log_channel(audit) log [DBG] : from='client.4151 10.244.1.2:0/3446600469' entity='client.admin' cmd=[{"username": "admin", "prefix": "dashboard set-login-credentials", "password": "8v2AbqHDj6", "target": ["mgr", ""], "format": "json"}]: dispatch[centos@k8s-master ~]$ 找到username和password字段,我这里是admin,8v2AbqHDj6打开浏览器输入任意一个Node的IP+nodeport端口,这里使用master节点 ip访问:https://192.168.92.56:32483 登录后界面如下: 查看hosts状态:运行了1个mgr、3个mon和3个osd 查看monitors状态: 查看OSD状态3个osd状态正常,每个容量50GB. 部署Ceph toolbox默认启动的Ceph集群,是开启Ceph认证的,这样你登陆Ceph组件所在的Pod里,是没法去获取集群状态,以及执行CLI命令,这时需要部署Ceph toolbox,命令如下: 1kubectl apply -f rook/cluster/examples/kubernetes/ceph/ toolbox.yaml 部署成功后,pod如下: 123[centos@k8s-master ceph]$ kubectl -n rook-ceph get pods -o wide | grep ceph-toolsrook-ceph-tools-76c7d559b6-8w7bk 1/1 Running 0 11s 192.168.92.58 k8s-node2 <none> <none>[centos@k8s-master ceph]$ 然后可以登陆该pod后,执行Ceph CLI命令: 1234567[centos@k8s-master ceph]$ kubectl -n rook-ceph exec -it rook-ceph-tools-76c7d559b6-8w7bk bashbash: warning: setlocale: LC_CTYPE: cannot change locale (en_US.UTF-8): No such file or directorybash: warning: setlocale: LC_COLLATE: cannot change locale (en_US.UTF-8): No such file or directorybash: warning: setlocale: LC_MESSAGES: cannot change locale (en_US.UTF-8): No such file or directorybash: warning: setlocale: LC_NUMERIC: cannot change locale (en_US.UTF-8): No such file or directorybash: warning: setlocale: LC_TIME: cannot change locale (en_US.UTF-8): No such file or directory[root@k8s-node2 /]# 查看ceph集群状态 1234567891011121314151617[root@k8s-node2 /]# ceph status cluster: id: abddff95-5fa0-47dc-a001-7fb291a42bc6 health: HEALTH_OK services: mon: 3 daemons, quorum c,b,a mgr: a(active) osd: 3 osds: 3 up, 3 in data: pools: 1 pools, 100 pgs objects: 0 objects, 0 B usage: 12 GiB used, 129 GiB / 141 GiB avail pgs: 100 active+clean [root@k8s-node2 /]# 查看ceph配置文件 12345678910111213141516171819[root@k8s-node2 /]# cd /etc/ceph/[root@k8s-node2 ceph]# lltotal 12-rw-r--r-- 1 root root 121 Jan 3 11:28 ceph.conf-rw-r--r-- 1 root root 62 Jan 3 11:28 keyring-rw-r--r-- 1 root root 92 Sep 24 18:15 rbdmap[root@k8s-node2 ceph]# cat ceph.conf [global]mon_host = 10.104.1.238:6790,10.105.153.93:6790,10.105.107.254:6790[client.admin]keyring = /etc/ceph/keyring[root@k8s-node2 ceph]# cat keyring[client.admin]key = AQBjoC1cXKJ7KBAA3ZnhWyxvyGa8+fnLFK7ykw==[root@k8s-node2 ceph]# cat rbdmap # RbdDevice Parameters#poolname/imagename id=client,keyring=/etc/ceph/ceph.client.keyring[root@k8s-node2 ceph]# rook提供RBD服务rook可以提供以下3类型的存储: Block: Create block storage to be consumed by a pod Object: Create an object store that is accessible inside or outside the Kubernetes cluster Shared File System: Create a file system to be shared across multiple pods在提供(Provisioning)块存储之前,需要先创建StorageClass和存储池。K8S需要这两类资源,才能和Rook交互,进而分配持久卷(PV)。在kubernetes集群里,要提供rbd块设备服务,需要有如下步骤: 创建rbd-provisioner pod 创建rbd对应的storageclass 创建pvc,使用rbd对应的storageclass 创建pod使用rbd pvc 通过rook创建Ceph Cluster之后,rook自身提供了rbd-provisioner服务,所以我们不需要再部署其provisioner。备注:代码位置pkg/operator/ceph/provisioner/provisioner.go 创建pool和StorageClass查看storageclass.yaml的配置(默认): 12345678910111213141516171819202122232425262728[centos@k8s-master ~]$ vim rook/cluster/examples/kubernetes/ceph/storageclass.yamlapiVersion: ceph.rook.io/v1kind: CephBlockPoolmetadata: name: replicapool namespace: rook-cephspec: replicated: size: 1---apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: rook-ceph-blockprovisioner: ceph.rook.io/blockparameters: blockPool: replicapool # Specify the namespace of the rook cluster from which to create volumes. # If not specified, it will use `rook` as the default namespace of the cluster. # This is also the namespace where the cluster will be clusterNamespace: rook-ceph # Specify the filesystem type of the volume. If not specified, it will use `ext4`. fstype: xfs # (Optional) Specify an existing Ceph user that will be used for mounting storage with this StorageClass. #mountUser: user1 # (Optional) Specify an existing Kubernetes secret name containing just one key holding the Ceph user secret. # The secret must exist in each namespace(s) where the storage will be consumed. #mountSecret: ceph-user1-secret 配置文件中包含了一个名为replicapool的存储池,和名为rook-ceph-block的storageClass。 运行yaml文件 1kubectl apply -f /rook/cluster/examples/kubernetes/ceph/storageclass.yaml 查看创建的storageclass:1234[centos@k8s-master ~]$ kubectl get storageclassNAME PROVISIONER AGErook-ceph-block ceph.rook.io/block 171m[centos@k8s-master ~]$ 登录ceph dashboard查看创建的存储池: 使用存储以官方wordpress示例为例,创建一个经典的wordpress和mysql应用程序来使用Rook提供的块存储,这两个应用程序都将使用Rook提供的block volumes。查看yaml文件配置,主要看定义的pvc和挂载volume部分,以wordpress.yaml为例: 1234567891011121314151617181920212223242526[centos@k8s-master ~]$ cat rook/cluster/examples/kubernetes/wordpress.yaml ......---apiVersion: v1kind: PersistentVolumeClaimmetadata: name: wp-pv-claim labels: app: wordpressspec: storageClassName: rook-ceph-block accessModes: - ReadWriteOnce resources: requests: storage: 20Gi---...... volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wp-pv-claim[centos@k8s-master ~]$ yaml文件里定义了一个名为wp-pv-claim的pvc,指定storageClassName为rook-ceph-block,申请的存储空间大小为20Gi。最后一部分创建了一个名为wordpress-persistent-storage的volume,并且指定 claimName为pvc的名称,最后将volume挂载到pod的/var/lib/mysql目录下。启动mysql和wordpress: 12kubectl apply -f rook/cluster/examples/kubernetes/mysql.yamlkubectl apply -f rook/cluster/examples/kubernetes/wordpress.yaml 这2个应用都会创建一个块存储卷,并且挂载到各自的pod中,查看声明的pvc和pv: 123456789[centos@k8s-master ~]$ kubectl get pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGEmysql-pv-claim Bound pvc-5bfbe28e-0fc9-11e9-b90d-000c291c25f3 20Gi RWO rook-ceph-block 32mwp-pv-claim Bound pvc-5f56c6d6-0fc9-11e9-b90d-000c291c25f3 20Gi RWO rook-ceph-block 32m[centos@k8s-master ~]$ kubectl get pvNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGEpvc-5bfbe28e-0fc9-11e9-b90d-000c291c25f3 20Gi RWO Delete Bound default/mysql-pv-claim rook-ceph-block 32mpvc-5f56c6d6-0fc9-11e9-b90d-000c291c25f3 20Gi RWO Delete Bound default/wp-pv-claim rook-ceph-block 32m[centos@k8s-master ~]$ 注意:这里的pv会自动创建,当提交了包含 StorageClass 字段的 PVC 之后,Kubernetes 就会根据这个 StorageClass 创建出对应的 PV,这是用到的是Dynamic Provisioning机制来动态创建pv,PV 支持 Static 静态请求,和动态创建两种方式。在Ceph集群端检查: 1234567891011121314[centos@k8s-master ceph]$ kubectl -n rook-ceph exec -it rook-ceph-tools-76c7d559b6-8w7bk bash......[root@k8s-node2 /]# rbd info -p replicapool pvc-5bfbe28e-0fc9-11e9-b90d-000c291c25f3 rbd image 'pvc-5bfbe28e-0fc9-11e9-b90d-000c291c25f3': size 20 GiB in 5120 objects order 22 (4 MiB objects) id: 88156b8b4567 block_name_prefix: rbd_data.88156b8b4567 format: 2 features: layering op_features: flags: create_timestamp: Fri Jan 4 02:35:12 2019[root@k8s-node2 /]# 登陆 pod 检查 rbd 设备: 123456789101112131415[centos@k8s-master ~]$ kubectl get pod -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESwordpress-7b6c4c79bb-t5pst 1/1 Running 0 135m 10.244.1.16 k8s-node1 <none> <none>wordpress-mysql-6887bf844f-9pmg8 1/1 Running 0 135m 10.244.2.14 k8s-node2 <none> <none>[centos@k8s-master ~]$ [centos@k8s-master ~]$ kubectl exec -it wordpress-7b6c4c79bb-t5pst bashroot@wordpress-7b6c4c79bb-t5pst:/var/www/html#root@wordpress-7b6c4c79bb-t5pst:/var/www/html# mount | grep rbd/dev/rbd0 on /var/www/html type xfs (rw,relatime,attr2,inode64,sunit=8192,swidth=8192,noquota)root@wordpress-7b6c4c79bb-t5pst:/var/www/html# df -hFilesystem Size Used Avail Use% Mounted on....../dev/rbd0 20G 59M 20G 1% /var/www/html...... 登录 ceph dashboard 查看创建的 images 一旦 Wordpress 和 mysql pods 处于运行状态,获取 Wordpress 应用程序的集群IP并使用浏览器访问: 1234[centos@k8s-master ~]$ kubectl get svc wordpressNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEwordpress LoadBalancer 10.98.178.189 <pending> 80:30001/TCP 136m[centos@k8s-master ~]$ 访问Wordpress: 参考文档https://blog.csdn.net/networken/article/details/85772418]]></content>
<categories>
<category>Kubernetes笔记</category>
</categories>
<tags>
<tag>Kubernetes部署rook+ceph存储系统</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot 使用 Tomcat APR 模式]]></title>
<url>%2F2019%2F01%2F02%2FSpringBoot%E4%BD%BF%E7%94%A8TomcatAPR%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[SpringBoot 使用 Tomcat APR 模式 SpringBoot 内置的是 Tomcat Embed 容器,就是把 Tomcat 打包成一个 jar包库 来使用,直接运行 Application 就可以启动 Web服务器。本质没有区别,默认独立的 Tomcat 性能高于 SpringBoot 内置的 Tomcat。因为独立的 Tomcat 很多都配置了 APR 模式特性,大多数比较的时候 SpringBoot 内置的 Tomcat 并没有开启这个模式。可以选择自己启动这个特性。 从 GitHub 上的讨论看,如果需要 TLS 支持,使用 APR 是比较好的。否则没必要使用 APR,未来 APR 在 Tomcat 10 中可能会被移除。https://github.com/spring-projects/spring-boot/pull/10079 在 Tomcat 中提供了三种方式:BIO、NIO、APR。BIO 模式 采用 Java IO 技术,单线程处理单请求(Tomcat7以下默认)Tomcat 7 以下的版本都是 BIO,就是一个请求是一个独立的线程。不能适用高并发的场景。阻塞式 I/O,采用传统的 java I/O 进行操作,该模式下每个请求都会创建一个线程,适用于并发量小的场景 NIO 模式 采用 Java NIO 技术,少量线程处理大量请求(Tomcat8以上默认)Tomcat 8 以上的版本,默认都是 NIO。同步非阻塞,比传统 BIO 能更好的支持大并发,Tomcat 8.0 后默认采用该模式 APR 模式 APR(Apache Portable Runtime)采用 JNI 技术,从操作层面解决 I/O 阻塞问题,适合高并发场景APR 的整体模式还是非阻塞 I/O,实现的线程模型也是按照 NIO 的标准模型实现的,APR 是一种基于 JNI 形式调用http服务器的核心动态链接库来处理的文件和网络读写模式(文件读取和网络传输操作)。需要预先编译安装 APR库,现在很多高版本的 Tomcat 默认都走它了。在 Tomcat 中配置,很好配置,直接修改 protocol 就可以了。但是在 SpringBoot 中,配置是在 Java 代码中写的。从官方文档 http://apr.apache.org/docs/apr/1.6/modules.html可以看到 APR 根据不同操作系统,分别用 C 重写了大部分IO和系统线程操作模块,这就是为什么 APR 在不改动代码的情况下能够提升。 SpringBoot 开启 APR 模式 在 SpringBoot 中内嵌的 Tomcat 默认启动开启的是 NIO 模式,这里如果我们要在 Linux 内核的系统上使用 APR 模式,那么需要安装一些 lib库,可以通过 rpm -q | grep apr 来查看是否安装了 apr,如果安装了则不再需要安装。 安装依赖1yum -y install apr-devel gcc gcc-devel openssl openssl-devel expat-devel OpenSSL 安装 OpenSSL 需要版本大于 1.0.2,如果不使用 https openssl 也可以不安装,就是在启动的时候会报 OpenSSL 的错误,直接忽视就可以了。 1234# OpenSSL 下载地址https://www.openssl.org/source/wget -c https://www.openssl.org/source/openssl-1.1.1a.tar.gz 安装 OpenSSL123456tar -xvf openssl-1.1.1a.tar.gzcd openssl-1.1.1a/./config --prefix=/usr/local/openssl./config -tmake -j 4make install 将 openssl 的 lib 加入系统 ldconfig 中123456vim /etc/ld.so.conf.d/openssl.conf/usr/local/openssl/lib# 加载一下ldconfig -vldconfig -v | grep libssl 查看 OpenSSL 版本12345678910111213141516171819cd /usr/local/openssl/./bin/openssl version -aOpenSSL 1.1.1a 20 Nov 2018built on: Wed Jan 2 02:19:47 2019 UTCplatform: linux-x86_64options: bn(64,64) rc4(16x,int) des(int) idea(int) blowfish(ptr) compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -O3 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPADLOCK_ASM -DPOLY1305_ASM -DNDEBUGOPENSSLDIR: "/usr/local/openssl/ssl"ENGINESDIR: "/usr/local/openssl/lib/engines-1.1"Seeding source: os-specific# 如果遇到以下错误openssl: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directoryopenssl: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file: No such file or directory# 可能是由于 OpenSSL 库的位置不正确造成的# 做一下软链接,就好了ln -svnf /usr/local/openssl/lib/libssl.so.1.1 /usr/lib64/libssl.so.1.1ln -svnf /usr/local/openssl/lib/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1 配置环境变量12345678910111213141516vim /etc/profile.d/openssl.sh#!/bin/bashexport OPENSSL_HOME=/usr/local/opensslexport PATH=$PATH:$OPENSSL_HOME/bin:wqsource profileopenssl version -a# 或把原来的命令备份一下,做个软链接ll -h /usr/bin/opensslmv /usr/bin/openssl{,_bak}mv /usr/include/openssl{,openssl_bak}ln -svnf /usr/local/openssl/bin/openssl /usr/bin/opensslln -svnf /usr/local/openssl/include/openssl /usr/include/openssl APR 安装12# APR 下载地址http://apr.apache.org/download.cgi 安装 apr123456789101112131415161718192021222324cd /data/tools/tar -zxvf apr-1.6.5.tar.gzcd apr-1.6.5/# 检查是否符合安装条件并配置安装参数,检查是否缺失类库,一般来说如果安装的不是精简版系统都是能顺利通过的./configure \--prefix=/usr/local/apr# 报错信息config.status: executing libtool commandsrm: cannot remove 'libtoolT': No such file or directoryconfig.status: executing default commands# 解决方法# 编辑 configure 文件cd apr-1.6.5/vim configure把 $RM "$cfgfile" 这行代码注释掉或 把 $RM "$cfgfile" 这行删除掉或 写成 $RM -f "$cfgfile"重新再运行 ./configure 就可以了make -j 4make install# 如果不设置安装路径,系统默认安装路径为 /usr/apr/lib 安装 apr-util12345678910cd /data/tools/tar -zxvf apr-util-1.6.1.tar.gzcd apr-util-1.6.1/# 安装 apr-util 需要配置 apr路径 和 jvm路径,否则会报错找不到apr./configure \--prefix=/usr/local/apr-utils \--with-apr=/usr/local/apr# --with-java-home=/data/jdk1.8.0_192make -j 4make install 安装 apr-iconv12345678910111213# 安装 expat 开发库yum -y install expat-develcd /data/tools/tar -xvf apr-iconv-1.2.2.tar.gzcd apr-iconv-1.2.2/./configure \--prefix=/usr/local/apr-iconv \--with-apr=/usr/local/apr# --with-java-home=/data/jdk1.8.0_192make -j 4make install 安装 tomcat native1234567891011121314151617# 安装 libtool 依赖库yum -y install libtool libtool-develcd /data/tools/tar -zxvf apache-tomcat-8.5.37.tar.gzcd apache-tomcat-8.5.37/bin/tar -zxvf tomcat-native.tar.gzcd tomcat-native-1.2.19-src/native/./configure \--with-apr=/usr/local/apr \--with-java-home=/data/jdk1.8.0_192 \--with-ssl=/usr/local/openssl \--with-ssl=yesmake -j 4make install 配置 Apr12345678# vim /etc/profilevim /etc/profile.d/apr.sh#!/bin/bashexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/apr/lib:wqsource /etc/profile 新增 APRConfig 类 网上大部分讲解配置tomcat apr的文章,都只是讲了如何在独立 Tomcat 服务上如何配置 apr,只需要修改 server.xml 中的 connnector 的 protocol 就可以了,对于 SpringBoot 会稍微复杂些,需要增加一个 apr 配置类在启动的时候修改 Embed 的 tomcat connector 网络接入协议 1234567891011121314151617181920212223242526packagecom.ochain.data2chain.gateway.config;importorg.apache.catalina.LifecycleListener;importorg.apache.catalina.core.AprLifecycleListener;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.boot.context.embedded.EmbeddedServletContainerFactory;importorg.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@Configurationpublic class APRConfig { @Value("${tomcat.apr:false}") privateboolean enabled; @Bean public EmbeddedServletContainerFactory servletContainer() { TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory(); if(enabled) { LifecycleListener arpLifecycle = new AprLifecycleListener(); container.setProtocol("org.apache.coyote.http11.Http11AprProtocol"); container.addContextLifecycleListeners(arpLifecycle); } return container; }} 或 1234567891011121314151617import org.apache.catalina.core.AprLifecycleListener;import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class APRConfig { // https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#howto-discover-build-in-options-for-external-properties @Bean public EmbeddedServletContainerFactory servletContainer() { TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); tomcat.setProtocol("org.apache.coyote.http11.Http11AprProtocol"); tomcat.addContextLifecycleListeners(new AprLifecycleListener()); return tomcat; }} 配置完启动,有可能会报错错误问题122019-01-02 00:45:55,891 [main] ERROR org.apache.catalina.core.StandardService:181 - Failed to start connector [Connector[org.apache.coyote.http11.Http11AprProtocol-8080]]org.apache.catalina.LifecycleException: Failed to initialize component [Connector[org.apache.coyote.http11.Http11AprProtocol-8080]] 需要在启动 SpringBoot 的服务器上安装 tomcat-native 和 apr 的模块 启动 SpringBoot 系统找不到 apr 的 lib 库12345678org.springframework.boot.context.embedded.tomcat.ConnectorStartFailedException: Connector configured tolisten onport 8080 failed tostart...***************************APPLICATION FAILED TOSTART***************************Description:The Tomcat connector configured tolisten onport 8080 failed tostart. Theport may already be inuse orthe connector may be misconfigured. 打开 debug 后查看系统日志发现真正的原因是系统找不到 apr 的 lib库1Caused by: org.apache.catalina.LifecycleException: The configured protocol [org.apache.coyote.http11.Http11AprProtocol] requires the APR/native library which is not available 解决方法在启动命令行中添加指定 apr库路径 1java -Djava.library.path=/usr/local/apr/lib -jar xxxx-0.0.1-SNAPSHOT.jar 启动 SpringBoot 启动成功后看到日志中输出以下内容,则表示 apr 模式启动成功 1232019-01-02 15:31:19,032 - Initializing ProtocolHandler ["http-apr-8080"]2019-01-02 15:31:19,051 - Starting ProtocolHandler ["http-apr-8080"]2019-01-02 15:31:19,080 - Tomcat started on port(s): 8080(http) 参考文档https://www.jianshu.com/p/f716726ba340https://www.cnblogs.com/yueli/p/9668088.htmlhttp://www.cnblogs.com/xing901022/p/9145914.html]]></content>
<categories>
<category>SpringBoot笔记</category>
</categories>
<tags>
<tag>SpringBoot 使用 Tomcat APR 模式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Next 解决 Busuanzi 统计浏览失效]]></title>
<url>%2F2018%2F12%2F02%2FNext-%E8%A7%A3%E5%86%B3-Busuanzi-%E7%BB%9F%E8%AE%A1%E6%B5%8F%E8%A7%88%E5%A4%B1%E6%95%88%2F</url>
<content type="text"><![CDATA[Hexo Next 解决 Busuanzi 统计浏览失效 由于 busuanzi(不蒜子)的网址更新,导致了使用 Hexo Next 主题时统计浏览数失效。 不蒜子官网http://ibruce.info/2015/04/04/busuanzi/ 解决方法到 hexo 的 themes 目录下进入 1cd themes/next/layout/_third-party/analytics/ 打开 busuanzi-counter.swig将1src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js" 修改为1src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" 参考文档https://blog.csdn.net/ddydavie/article/details/83020549]]></content>
<categories>
<category>Next笔记</category>
</categories>
<tags>
<tag>Next 解决 Busuanzi 统计浏览失效</tag>
</tags>
</entry>
<entry>
<title><![CDATA[日志统计常用技巧]]></title>
<url>%2F2018%2F10%2F29%2F%E6%97%A5%E5%BF%97%E7%BB%9F%E8%AE%A1%E5%B8%B8%E7%94%A8%E6%8A%80%E5%B7%A7%2F</url>
<content type="text"><![CDATA[日志统计常用技巧 按时间截取 截取指定时间段日志 1sed -n '/^2018-10-29 14:30/,/^2018-10-29 14:32/p' server.log | more 按标识截取 截取特定标识日志的内容(仅特定标识日志行、或标识附近日志行)过滤包含特定标识(忽略大小写)的日志、并显示前后 10 行 12345grep -i -A 10 -B 10 "error" access.log | moregrep -i -A 10 -B 10 "error" access.log > access_error.txtgrep -i -C 10 "error" access.log | moregrep -i -C 10 "error" access.log > access_error.txt 按指定列截取 截取第7列为 ‘/test’ 的行 1cat access.log | awk 'if($7=="/test"{print $0}' | more 日志统计 分时访问量统计对日志中的时间列进行拆分,根据时间格式取 YYYY-mm-DD HH:MM 部分、进行排序、汇总,即可获得每分钟的访问量统计。 按分钟汇总统计访问量1cat access.log | grep "^2012-" | awk -F\: '{print $1" "$2":"$3}' | sort | uniq -c | more 按小时汇总统计访问量1cat access.log | grep "^2012-" | awk -F\: '{print $1" "$2"}' | sort | uniq -c | more 日志中的 IP 地址 Web服务器日志(Nginx)中,通常第一列为客户端IP地址,取该段值进行排序、汇总,即可得到IP:访问次数的统计。 从日志中统计 IP 访问次数1cat access.log | awk '{print $1}' | sort | uniq -c | more 统计来访唯一 IP 数1cat access.log | awk '{print $1}' | sort | uniq | wc -l 网络连接状态netstat 命令可获取系统当前的侦听、连接状态,包括协议、源IP:端口、目标IP:端口、连接状态信息,取关注的列进行排序、汇总,即可获得连接数、状态等统计。 TCP连接数1234567netstat -antp | grep ^tcp | awk '{print $5}' | awk -F\: '{print $1}' | sort | uniq -c | sort -nr 5 0.0.0.0 2 192.217.199.215 1 91.189.89.144 1 172.16.17.4 1 172.16.17.3 1 172.16.17.27 TCP 连接状态1234netstat -antp | grep ^tcp | awk '{print $6}' | sort | uniq -c | sort -nr 17 ESTABLISHED 8 LISTEN 1 CLOSE_WAIT 使用 awk 的正则匹配进行过滤,减少前面的 grep 指令不过 grep 的过滤处理速度似乎比 awk 的正则匹配速度更快,因此对大日志分析时可能还是先用 grep 过滤效率更高 123456789101112131415161718192021time cat netstat.txt | awk '/^tcp/ {print $6}' | sort | uniq -c | sort -nr 2713 TIME_WAIT 203 ESTABLISHED 29 FIN_WAIT1 18 LISTEN 17 FIN_WAIT2real 0m0.017suser 0m0.014ssys 0m0.002stime cat netstat.txt | grep ^tcp | awk '{print $6}' | sort | uniq -c | sort -nr 2713 TIME_WAIT 203 ESTABLISHED 29 FIN_WAIT1 18 LISTEN 17 FIN_WAIT2real 0m0.008suser 0m0.005ssys 0m0.001s 其他 awk 匹配方式12345678910111213cat netstat.txt | awk '{if($1=="tcp")print $6}' | sort | uniq -c | sort -nr 2713 TIME_WAIT 203 ESTABLISHED 29 FIN_WAIT1 18 LISTEN 17 FIN_WAIT2cat netstat.txt | awk '$1=="tcp" {print $6}' | sort | uniq -c | sort -nr 2713 TIME_WAIT 203 ESTABLISHED 29 FIN_WAIT1 18 LISTEN 17 FIN_WAIT2 多条件匹配1234567891011121314151617181920212223242526272829303132333435cat netstat.txt | awk '/^tcp|^udp/ {print $1"-"$6}' | sort | uniq -c | sort -nr 2713 tcp-TIME_WAIT 203 tcp-ESTABLISHED 29 tcp-FIN_WAIT1 18 tcp-LISTEN 17 tcp-FIN_WAIT2 4 udp-32366/rpcbind 4 udp-- 3 udp-32526/rpc.statd 2 udp-19339/rpc.mountd 1 udp-ESTABLISHED 1 udp-38143/gmond 1 udp-19335/rpc.rquotad 1 udp-1282/portreservecat netstat.txt | awk '{if($1~"^tcp" || $1~"^udp")print $1"-"$6}' | sort | uniq -c | sort -nr 2713 tcp-TIME_WAIT 203 tcp-ESTABLISHED 29 tcp-FIN_WAIT1 18 tcp-LISTEN 17 tcp-FIN_WAIT2 4 udp-32366/rpcbind 4 udp-- 3 udp-32526/rpc.statd 2 udp-19339/rpc.mountd 1 udp-ESTABLISHED 1 udp-38143/gmond 1 udp-19335/rpc.rquotad 1 udp-1282/portreservecat netstat.txt | awk '{if($1=="tcp" && $6!~"ESTABLISHED")print $6}' | sort | uniq -c | sort -nr 2713 TIME_WAIT 29 FIN_WAIT1 18 LISTEN 17 FIN_WAIT2 示例示例一 扫描 gz 压缩文件,从中寻找带有 dianping_reply 的行,将改行按照[进行拆分,然后直接使用if条件进行判断,比较,最终输出想要的结果。 1zcat access.log.tar.gz | grep 'dianping_reply.log' | awk '{split($4,array,"[");if(array[2]>="29/May/2016:00:00:26" && array[2]<="29/May/2016:00:01:14"){print $0}}' 命令解释zcat 直接读取压缩文件的内容grep 过滤特定字符的行awk 用于执行命令split 用于切分字符串 示例二 过滤 22/Feb/2017-18:52:59 之前的日志 123456789test.log[22/Feb/2017-18:51:58] api.momo.com /api/feed HTTP/1.1 121.0.0.1 android1.2[22/Feb/2017-18:51:59] api.momo.com /api/follow HTTP/1.1 121.0.0.2 iphone1.1[22/Feb/2017-18:52:58] api.momo.com /api/user HTTP/1.1 121.0.0.3 iphone1.1[22/Feb/2017-18:52:41] api.momo.com /api/feed HTTP/1.1 121.0.0.4 android1.2[22/Feb/2017-18:56:30] api.momo.com /api/follow HTTP/1.1 121.0.0.2 android1.2[22/Feb/2017-18:51:21] api.momo.com /api/user HTTP/1.1 121.0.0.3 iphone1.1[22/Feb/2017-18:59:58] api.momo.com /api/user HTTP/1.1 121.0.0.3 android1.2[22/Feb/2017-18:51:21] api.momo.com /api/feed HTTP/1.1 121.0.0.1 iphone1.1 1cat test.log | awk '{split($1,array,"[");split(array[2],array2,"]");if(array2[1] >= "22/Feb/2017-18:52:59"){print($0)}}' 参考文档http://xstarcd.github.io/wiki/shell/logview_tips.html]]></content>
<categories>
<category>Shell笔记</category>
</categories>
<tags>
<tag>日志统计常用技巧</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Shell 按时间截取日志]]></title>
<url>%2F2018%2F10%2F29%2FShell%E6%8C%89%E6%97%B6%E9%97%B4%E6%88%AA%E5%8F%96%E6%97%A5%E5%BF%97%2F</url>
<content type="text"><![CDATA[Shell 按时间截取日志截取查看某时间段内的日志 sed 截取 精确到 时分秒 1sed -n '/2018-10-22|10:23:44/,/2018-10-22|11:23:44/' access.log > 10-11_access.log 精确到小时 1sed -n '/2018-10-22|10/,/2018-10-22|11/' access.log > 10-11_access.log sed -n '/开始时间/,/结束时间/' 被截取日志原文件 > 截取后日志新文件$p 显示到最后一行 1234# 查看某时间段到当前的 messages 系统日志sed -n '/May 20 17/,$p' /var/log/messages | lesssed -n '/2018-10-29 09:00:00/,$p' access.log > /tmp/access.log sed 截取 10:00:00 到 现在的 catalina.out 日志内容 1sed -n '/2018-04-11 10:[0-9][0-9]:[0-9][0-9]/,$p' catalina.out | less sed 截取 2018-04-11 这天 到 现在的 catalina.out 日志内容 1sed -n '/2018-04-11 [0-9][0-9]:[0-9][0-9]:[0-9][0-9]/,$p' catalina.out | less sed 截取 2018-03-14 09:00:00 2018-03-14 12:00:00 的日志 1sed -n '/2018-03-14 09:[0][0]:[0][0]/,/2018-03-14 12:[0][0]:[0][0]/p' catalina.out > /tmp/catalina.log grep 截取 egrep 截取 13:38 到 14:00 之间的日志内容 1egrep '13:3[8-9]:[0-9][0-9]|13:[4-5][0-9]:[0-9][0-9]' catalina.out | less egrep 截取 2018-04-11 到 2018-04-12 之间的日志内容 1egrep '2018-04-11 [0-9][0-9]:[0-9][0-9]:[0-9][0-9]|2018-04-12 [0-9][0-9]:[0-9][0-9]:[0-9][0-9]' catalina.out | less 截取 Nginx 访问日志 查看 21/Jul/2014:14:37:50 到 21/Jul/2014:14:38:00 时间段内 access.log 的访问日志 1cat access.log | awk '$4 >="[21/Jul/2014:14:37:50" && $4 <="[21/Jul/2014:14:38:00"' 时间转换在线工具地址https://tool.lu/timestamp/]]></content>
<categories>
<category>Shell笔记</category>
</categories>
<tags>
<tag>Shell 按时间截取日志</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 详解time模块中 UTC世界时间、时间戳、字符串三者的转换]]></title>
<url>%2F2018%2F10%2F24%2FPython%E8%AF%A6%E8%A7%A3time%E6%A8%A1%E5%9D%97%E4%B8%ADUTC%E4%B8%96%E7%95%8C%E6%97%B6%E9%97%B4%E3%80%81%E6%97%B6%E9%97%B4%E6%88%B3%E3%80%81%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%89%E8%80%85%E7%9A%84%E8%BD%AC%E6%8D%A2%2F</url>
<content type="text"><![CDATA[Python 详解time模块中 UTC世界时间、时间戳、字符串三者的转换 本地时间 转换 为时间戳123456789101112import timeimport pytzimport datetimedate_=datetime.datetime(2018,6,19,20,55,00)timestamp2=time.mktime(date_.timetuple()) # date_.timetuple() 将datetime 格式的转化为 time 模块的 tuple 格式print(timestamp2)# 时间戳转换为本地时间ltime=time.localtime(1529112900) #time.struct_time(tm_year=2018, tm_mon=6, tm_mday=16, tm_hour=9, tm_min=35, tm_sec=0, tm_wday=5, tm_yday=167, tm_isdst=0)timeStr=time.strftime("%Y-%m-%d %H:%M:%S", ltime)print(timeStr) UTCS 时间转换为时间戳 2018-07-13T16:00:00Z123456789def utc_to_local(utc_time_str, utc_format='%Y-%m-%dT%H:%M:%SZ'): local_tz = pytz.timezone('Asia/Chongqing') # 定义本地时区 local_format = "%Y-%m-%d %H:%M:%S" # 定义本地时间format utc_dt = datetime.datetime.strptime(utc_time_str, utc_format) # 讲世界时间的格式转化为 datetime.datetime 格式 local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz) # 想将 datetime 格式添加上世界时区,然后 astimezone 切换时区:世界时区 ==> 本地时区 # time_str = local_dt.strftime(local_format) # 将 datetime 格式转化为 str—format 格式 # return int(time.mktime(time.strptime(time_str, local_format))) # 运用 mktime 方法将 date—tuple 格式的时间转化为时间戳; time.strptime() 可以得到 tuple 的时间格式 return int(time.mktime(local_dt.timetuple())) # 返回当地时间戳 本地时间转换为 UTC 传入的本地时间戳 1531411200123456789101112def local_to_utc(local_ts, utc_format='%Y-%m-%dT%H:%MZ'): local_tz = pytz.timezone('Asia/Chongqing') # 定义本地时区 local_format = "%Y-%m-%d %H:%M:%S" # 定义本地时间 format time_str = time.strftime(local_format, time.localtime(local_ts)) # 首先将本地时间戳转化为时间元组,用 strftime 格式化成字符串 dt = datetime.datetime.strptime(time_str, local_format) # 将字符串用 strptime 转为为 datetime 中 datetime 格式 local_dt = local_tz.localize(dt, is_dst=None) # 给时间添加时区,等价于 dt.replace(tzinfo=pytz.timezone('Asia/Chongqing')) utc_dt = local_dt.astimezone(pytz.utc) # astimezone 切换时区 return utc_dt.strftime(utc_format) # 返回世界时间格式print(utc_to_local('2018-07-13T16:00:00Z', utc_format='%Y-%m-%dT%H:%M:%SZ'))print(local_to_utc(1531411200, utc_format='%Y-%m-%dT%H:%MZ')) 123456'''1529412900.02018-06-16 09:35:0015314976002018-07-12T16:00Z''' 参考文档https://blog.csdn.net/brucewong0516/article/details/81100242]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 详解time模块中 UTC世界时间、时间戳、字符串三者的转换</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 软件开发的目录规范]]></title>
<url>%2F2018%2F09%2F24%2FPython%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E7%9A%84%E7%9B%AE%E5%BD%95%E8%A7%84%E8%8C%83%2F</url>
<content type="text"><![CDATA[Python 软件开发的目录规范 目录规范项目目录规范 bin 存放执行文件,整个程序的入口文件,比如启动功能conf 配置文件lib 存放自定义的共享库,经常使用的一些功能core 存放整个代码程序的核心逻辑db 存放数据库相关的log 日志相关的Readme 记录整个项目的描述信息 项目文件规范 src.py12345678910111213141516171819202122232425262728293031323334def register(): print('注册。。。')def pay(): print('支付。。。')def transfer(): print('转账。。。')def withdraw(): print('提现。。。')func_dic = { '1': register, '2': pay, '3': transfer, '4': withdraw}# 核心功能,和用户交互def run(): while True: print(""" 1 注册 2 支付 3 转账 4 提现 """) choice = input('>>>: ').strip() if choice in func_dic: func_dic[choice]() else: print('输入错误指令') 获取项目根路径 强调:只有被导入的模块才能使用 . 或 .. 的语法编辑 start.py123import syssys.path.append(r'E:\Project\core') 这样写会有问题,在程序给别人使用时,别人的路径不可能和程序开发者的目录路径一样 改进 __file__ 表示当前文件的绝对路径abspath() 规范路径格式1234567891011import sysimport osprint(__file__)print(os.path.abspath(__file__))# sys.path.append(r'E:\Project\core')# os.path.dirname()# 执行结果E:/Project/bin/start.pyE:\Project\bin\start.py 123456789import sysimport osprint(os.path.dirname(os.path.abspath(__file__)))print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))# 执行结果E:\Project\binE:\Project 123456789101112import sysimport osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))sys.path.append(BASE_DIR)from core import srcif __name__ == '__main__': src.run()# 执行结果 获取 db 目录文件的路径 编辑 setting.py123456789import osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))DB_PATH = '%s\%s\%s' % (BASE_DIR, 'db', 'db.txt')print(DB_PATH)# 执行结果E:\Project\db\db.txt 这种拼接方式有缺陷,如果是 Linux 系统,\ 右斜杠就不能用了 解决跨平台路径格式的问题 使用 os.path.join123456789import osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))DB_PATH = os.path.join(BASE_DIR, 'db', 'db.txt')print(DB_PATH)# 执行结果E:\Project\db\db.txt Linux 系统输出格式123456789import osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))DB_PATH = os.path.join(BASE_DIR, 'db', 'db.txt')print(DB_PATH)# 执行结果/data/Project/db/db.txt 获取 log 目录路径 编辑 settings.py 123456LOG_PATH = os.path.join(BASE_DIR, 'log', 'access.log')print(LOG_PATH)# 执行结果E:\Project\log\access.log 项目目录规范示例项目名/core/src.py12345678910111213141516171819202122232425262728293031323334353637383940414243444546from conf import settingsfrom lib import commondef register(): print('注册。。。') uname = input('用户名:').strip() pwd = input('密码:').strip() with open(settings.DB_PATH, 'a', encoding='utf-8') as f: f.write('%s:%s\n' % (uname, pwd)) common.logger(uname) print('注册成功')def pay(): print('支付。。。')def transfer(): print('转账。。。')def withdraw(): print('提现。。。')func_dic = { '1': register, '2': pay, '3': transfer, '4': withdraw}# 核心功能,和用户交互def run(): while True: print(""" 1 注册 2 支付 3 转账 4 提现 按 q 退出 """) choice = input('请选择相应的操作: ').strip() if choice == 'q': break if choice not in func_dic: print('输入错误指令,请重新输入') continue func_dic[choice]() 项目名/conf/settings.py12345import osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))DB_PATH = os.path.join(BASE_DIR, 'db', 'db.txt')LOG_PATH = os.path.join(BASE_DIR, 'log', 'access.log') 项目名/lib/common.py12345678from conf import settingsimport time# print(time.strftime('%Y-%m-%d %X'))def logger(msg): with open(settings.LOG_PATH, 'a', encoding='utf-8') as f: f.write('%s, %s, 注册成功\n' % (time.strftime('%Y-%m-%d %X', time.localtime()), msg)) 项目名/bin/start.py12345678910import sysimport osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))sys.path.append(BASE_DIR)from core import srcif __name__ == '__main__': src.run() 运行 start.py 12345cat db.txtegon:123tail access.log2018-09-24 01:08:11, egon, 注册成功]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 软件开发的目录规范</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 模块的使用]]></title>
<url>%2F2018%2F09%2F23%2FPython%E6%A8%A1%E5%9D%97%E7%9A%84%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[Python 模块的使用 模块介绍什么是模块?模块就是一组功能的集合体,我们的程序可以导入模块来复用模块里的功能。模块分为三大类: 自定义模块 内置模块:time, sys 等 第三方模块 常见的场景一个模块就是一个包含了一组功能的 python 文件,比如 spam.py,模块名为 spam,可以通过 import spam 使用。 模块四种表现形式在 Python 中,模块的使用方式都是一样的,但其实细说的话,模块可以分为四个通用类别 使用 Python 编写的 .py 文件 已被编译为共享库或 DLL 的 C 或 C++ 扩展 把一系列模块组织到一起的文件夹(注:文件夹下有一个 __init__.py 文件,该文件夹称之为包) 使用 C 编写并链接到 python 解释器的内置模块 12345678import timeprint(time)import sysprint(sys)# 执行结果<module 'time' (built-in)><module 'sys' (built-in)> 为什么要用模块? 可以将程序中频繁使用的一些公共功能可以拿内置的、第三方的模块,然后直接使用,这种拿来主义,可以极大地提高开发效率 将程序中公用的一些功能组织到一个文件中,然后程序各部分组件可以重用该文件中的功能 优点:减少代码冗余,增强程序的组织结构性与可维护性 如何使用模块? 被导入的文件123456789101112131415vim spam.pyprint('from the spam.py')money = 1000def read1(): print('spam模块:', money)def read2(): print('spam模块') read1()def change(): global money money = 0 执行文件 123456vim run.pyimport spam# 执行结果from the spam.py 导入模块:import 模块名首次导入模块都发生了哪些事? 先产生一个模块的名称空间 会执行模块文件的代码,将产生的名字放到模块的名称空间中 会在当前名称空间中拿到一个模块名,该模块名指向模块的名称空间 示例1 123456789101112131415# 如何使用模块?import spam# 模块名.名字,是在向模块的名称空间中拿名字print(spam.money)print(spam.read1)print(spam.read2)print(spam.change)# 执行结果from the spam.py1000<function read1 at 0x000002629B29B6A8><function read2 at 0x000002629B29B620><function change at 0x000002629B29B840> 12345678import spammoney = 0print(spam.money)# 执行结果from the spam.py1000 示例2 123456789101112131415vim spam.py# print('from the spam.py')money = 1000def read1(): print('spam模块:', money)def read2(): print('spam模块') read1()def change(): global money money = 0 1234567891011121314import spamspam.read1()# 执行结果spam模块: 1000############################################################################import spam# 凡是来自于 spam 名称空间中的功能,执行时都是模块自己的名称空间为准的money = 0spam.read1()# 执行结果spam模块: 1000 示例3 观察 spam.py 里的 change() 12345import spammoney = 1111111111111111111111spam.change()print(money) 12345678import spammoney = 1111111111111111111111spam.change()spam.read1()# 执行结果spam模块: 0 示例4 123456789101112import spammoney = 1111111111111111111111def read1(): print('run.py read1')spam.read2()# 执行结果spam模块spam模块: 1000 import 导入模块的方式,在引用模块名称空间中改名字时,必须加上前缀:模块名.名字优点:指定前缀地访问模块名称空间中的名字,不会与当前名称空间中名字冲突缺点:每次引用模块名称空间中的名字都需要加上前缀,模块名过长时,前缀会显得非常臃肿 起别名可以缩短 模块名.名字 的长度1234# 起别名方式import spam as smprint(sm.money) 模块用逗号分隔,一次导入多个模块1import spam, os, time 推荐多行导入123import spamimport osimport time from...import... 导入模块 可以在调用模块名称空间中的名字时,不需要在名字前加前缀模块名 123456x = 1y = 2from spam import money# 执行结果from the spam.py from...import...首次导入模块都发生了哪些事? 先产生一个模块的名称空间 会执行模块文件的代码,将产生的名字放到模块的名称空间中 会在当前名称空间中直接拿到一个模块名称空间中的名字 12345678x = 1y = 2from spam import moneyprint(money)# 执行结果1000 12345678910111213x = 1y = 2from spam import money, read1, read2# 使用:可以不用加前缀直接使用print(money)print(read1)print(read2)# 执行结果1000<function read1 at 0x000002384886B6A8><function read2 at 0x000002384886B620> 使用:可以不用加前缀直接使用优点:简洁缺点:容易与当前名称空间中的名字冲突 强调:来自于模块名称空间中的函数一定是模块的名称空间为准的 12345678910x = 1y = 2from spam import money, read1, read2, changemoney = 1change()print(money)# 执行结果1 12345678910x = 1y = 2from spam import money, read1, read2, changemoney = 1change()read1()# 执行结果spam模块: 0 模块名称空间里的名字可以不用一个个导入但是不推荐使用 '*',因为不知道会导入哪些功能名字,最好需要哪一个导入哪一个,这样比较清楚123456789101112from spam import *print(money)print(read1)print(read2)print(change)# 执行结果1000<function read1 at 0x000001F14E00B6A8><function read2 at 0x000001F14E00B620><function change at 0x000001F14E00B840> 起别名12345from spam import money as mprint(m)# 执行结果1000 控制 '*' 所导入的名字 123vim spam.py__all__ = ['money', 'read1'] # 控制的就是 '*' 所导入的名字...省略... 123456789101112131415from spam import *print(money, read1)# 执行结果1000 <function read1 at 0x000002035A6DB8C8>###############################################################################from spam import *print(money, read1)print(read2)# 执行结果NameError: name 'read2' is not defined 模块的嵌套(循环)导入 在 Python 中,一个模块凡是被导入过一次,下一次导入就会直接引用上一次导入的结果,即不会重新执行模块的文件 1234vim m1.pyprint('正在导入m1')from m2 import yx = 'm1' 1234vim m2.pyprint('正在导入m2')from m1 import xy = 'm2' 1234567891011vim run.pyimport m1# 执行结果正在导入m1Traceback (most recent call last):正在导入m2 import m1 from m2 import y from m1 import xImportError: cannot import name 'x' from 'm1' 以上代码执行过程 先运行 run.py,产生 run.py 的名称空间,开始运行 run.py 里的代码。 运行到 import m1 这行代码时,会产生一个 m1 的名称空间,开始运行 m1.py 里的代码。 运行到 from m2 import y 会发现需要导入 m2,会产生一个 m2 的名称空间,开始运行 m2.py 里的代码,但是 m1.py 里的代码 还没有完全运行完。 m2.py 里需要 from m1 import x 导入 m1 里的 x,需要导入 m1。 此时会产生一个问题,m1 已经被导入过了,此时不会再执行 m1 里的代码了,它只会直接引用 m1 的名称空间,直接向这个名称空间寻求 x,然而 m1 的名称空间中并没有 x。 解决方法1 1234vim m1.pyprint('正在导入m1')x = 'm1'from m2 import y 1234vim m2.pyprint('正在导入m2')y = 'm2'from m1 import x 123456vim run.pyimport m1# 执行结果正在导入m1正在导入m2 解决方法:把名字放在导入模块代码的上面,保证在导入之前先产生名字 先运行 run.py,产生 run.py 的名称空间,开始运行 run.py 里的代码。 运行到 import m1 代码,产生一个 m1 的名称空间,开始运行 m1.py 里的代码,把 x='m1' 放进去 运行到 from m2 import y 代码,需要导入 m2,产生一个 m2 的名称空间,开始运行 m2.py 里的代码,造一个 y='m2',然后运行到 from m1 import x 代码,需要导入 m1,m1 名称空间已经存在,寻求 x,此时寻找到了 x 解决方法2(推荐) 写代码尽量避免循环导入如果必须面对这种场景,可以使用第三个模块把共享的模块做统一导入 解决方法3 示例一 12345678vim m1.pyprint('正在导入m1')def f1(): from m2 import y print(y)x = 'm1' 12345678vim m2.pyprint('正在导入m2')def f2(): from m1 import x print(x)y = 'm2' 123456789vim run.pyimport m1m1.f1()# 执行结果正在导入m1正在导入m2m2 解决思路定义函数时,只检测语法,不执行代码在哪个函数中使用,就在哪个函数当中去 import 导入运行 run.py,产生 run.py 的名称空间,开始运行 run.py 里的代码。运行到 import m1 代码,产生一个 m1 的名称空间,开始运行 m1.py 里的代码,把 f1、x='m1' 放进去在 run.py,里执行 m1.f1(),调用 m1 里 f1 方法,导入 m2,产生 m2 的名称空间导入 m2 后,运行 m2.py 的代码,m2 里把 f2、y='m2' 放进去 示例二 12345678vim m1.pyprint('正在导入m1')def f1(): from m2 import y, f2 f2()x = 'm1' 12345678vim m2.pyprint('正在导入m2')def f2(): from m1 import x print(x)y = 'm2' 123456789vim run.pyimport m1m1.f1()# 执行结果正在导入m1正在导入m2m1 区分Python文件两种用途方式 直接运行,当做运行文件 被当做模块导入使用 需求:在文件运行的时候执行一种代码被当做模块导入的时候运行另一种代码 当文件被直接执行时 __name__ == '__main__'12345vim m1.pyprint(__name__)# 执行结果__main__ 当文件被导入时 __name__ == '模块名'模块的使用者1234import m1# 执行结果m1 模块开发者 12345678910111213141516171819vim m1.pydef f1(): print('f1')def f2(): print('f2')def f3(): print('f3')if __name__ == '__main__': f1() f2() f3()# 执行结果f1f2f3 该行代码用于区分 Python 文件的两种不同用途,应该写在文件末尾 模块的使用者123456import m1m1.f1() # 使用哪个模块就调用哪个# 执行结果f1 模块的搜索路径 模块的查找有限顺序: 内存中已经加载的模块 内置模块 sys.path 路径中包含的模块 内存中已经加载的模块 操作方式:在 20秒内 把 spam.py 文件删除123vim spam.pydef f1(): print('from f1') 12345678910vim run.pyimport timeimport spamtime.sleep(20)import spamspam.f1()# 执行结果from f1 发现 还是能输出 from f1,说明在删除文件之前,内存已经加载到了 spam.py 内置模块12345import timeprint(time)# 执行结果<module 'time' (built-in)> PS:我们自定义的模块名不应该与系统内置模块重名。 sys.path 路径中包含的模块 sys.path 的值是以当前执行文件为准 12345import sysprint(sys.path)# 执行结果['E:\\PycharmProjects\\SH_weekend_s1\\day05\\08 模块的使用\\模块的搜索路径', 'E:\\PycharmProjects\\SH_weekend_s1', 'D:\\Program Files (x86)\\Python37\\python37.zip', 'D:\\Program Files (x86)\\Python37\\DLLs', 'D:\\Program Files (x86)\\Python37\\lib', 'D:\\Program Files (x86)\\Python37', 'D:\\Program Files (x86)\\Python37\\lib\\site-packages', 'D:\\Program Files (x86)\\JetBrains\\PyCharm 2018.2.3\\helpers\\pycharm_matplotlib_backend'] 示例 123vim spam.pydef f1(): print('from spam') 12345678910vim run.pyimport syssys.path.append(r'E:\PycharmProjects\SH_weekend_s1\day05\08 模块的使用\模块的搜索路径\dir')import spamspam.f1()# 执行结果from spam 也可以在当前目录寻找 from ... import ... 123456789import sysprint(sys.path)from dir import spamspam.f1()# 执行结果['E:\\PycharmProjects\\SH_weekend_s1\\day05\\08 模块的使用\\模块的搜索路径', 'E:\\PycharmProjects\\SH_weekend_s1', 'D:\\Program Files (x86)\\Python37\\python37.zip', 'D:\\Program Files (x86)\\Python37\\DLLs', 'D:\\Program Files (x86)\\Python37\\lib', 'D:\\Program Files (x86)\\Python37', 'D:\\Program Files (x86)\\Python37\\lib\\site-packages', 'D:\\Program Files (x86)\\JetBrains\\PyCharm 2018.2.3\\helpers\\pycharm_matplotlib_backend']from spam 示例 123456789import sysprint(sys.path)from dir.dir2 import spamspam.f1()# 执行结果['E:\\PycharmProjects\\SH_weekend_s1\\day05\\08 模块的使用\\模块的搜索路径', 'E:\\PycharmProjects\\SH_weekend_s1', 'D:\\Program Files (x86)\\Python37\\python37.zip', 'D:\\Program Files (x86)\\Python37\\DLLs', 'D:\\Program Files (x86)\\Python37\\lib', 'D:\\Program Files (x86)\\Python37', 'D:\\Program Files (x86)\\Python37\\lib\\site-packages', 'D:\\Program Files (x86)\\JetBrains\\PyCharm 2018.2.3\\helpers\\pycharm_matplotlib_backend']from spam 示例 123456vim m1.pyimport m2def f1(): print('m1.f1') m2.f2() 123vim m2.pydef f2(): print('m2.f2') 12345678vim run.pyfrom dir1 import m1m1.f1()# 执行结果 from dir1 import m1 import m2ModuleNotFoundError: No module named 'm2' 示例修改1 12345vim m1.pyfrom dir1 import m2def f1(): print('m1.f1') m2.f2() 123vim m2.pydef f2(): print('m2.f2') 1234567vim run.pyfrom dir1 import m1m1.f1()# 执行结果m1.f1m2.f2 示例修改2 12345vim m1.pyfrom . import m2def f1(): print('m1.f1') m2.f2() 123vim m2.pydef f2(): print('m2.f2') 1234567vim run.pyfrom dir1 import m1m1.f1()# 执行结果m1.f1m2.f2]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 模块的使用</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 函数的递归调用]]></title>
<url>%2F2018%2F09%2F20%2FPython%E5%87%BD%E6%95%B0%E7%9A%84%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%2F</url>
<content type="text"><![CDATA[Python 函数的递归调用 函数的嵌套调用12345678def bar(): print('from bar')def foo(): print('from foo') bar()foo() 函数递归 函数的递归调用,就是函数嵌套调用的一种特殊格式函数递归调用,在调用一个函数的过程中又直接或间接地调用了自己本质就是一个重复的过程,递归必须要有一个明确的结束条件,在满足该条件的情况下,会终止递归每一次重复问题的规模都应该有所减少 直接调用1234567891011def bar(): print('from bar')def foo(): print('from foo') foo()foo()# 执行结果RecursionError: maximum recursion depth exceeded while calling a Python object 注:Python 中没有伪递归优化这一说 间接调用123456789101112def bar(): print('from bar') foo()def foo(): print('from foo') bar()foo()# 执行结果RecursionError: maximum recursion depth exceeded while calling a Python object Python 限制递归调用最大层级是多少?123456789def foo(n): print('from foo', n) foo(n+1)foo(0)# 执行结果from foo 997 # 这里看到的并不精准RecursionError: maximum recursion depth exceeded while calling a Python object 使用 sys.getrecursionlimit() 可以查看到 12345import sysprint(sys.getrecursionlimit())# 执行结果1000 递归的最大层级这个值是可以修改的,但是没有多大的意义sys.setrecursionlimit(10000) 递归必须满足两个阶段 回溯:一层一层地递归调用下去 递推:递归必须要有一个明确的结束条件,在满足该条件的情况下,会终止递归,往回一层一层地结束调用 练习 猜年龄 第一个人 18 岁第二个人比第一个人大2岁第三个人比第二个人大2岁第四个人比第三个人大2岁第五个人比第四个人大2岁问第五个人的年龄是多少岁? 12345678age(5) = age(4) + 2age(4) = age(3) + 2age(3) = age(2) + 2age(2) = age(1) + 2age(1) = 18age(n) = age(n-1) + 2 # n > 1age(n) = 18 # n = 1 123456789def age(n): if n == 1: return 18 return age(n-1) + 2print(age(5))# 执行结果26 递归是一次重复的过程,每一次重复问题的规模都应该有所减少 123456789101112131415161718192021l = [1, [2, [3, [4, [5, [6, [7, [8, [9, ]]]]]]]]]def tell(l): for item in l: if type(item) is not list: print(item) else: tell(item)tell(l)# 执行结果123456789 递归 vs while循环 递归只需要把控住结束或进入递归的条件,至于循环次数无需考虑 123456789101112131415161718192021222324l = [1, [2, [3, [4, [5, [6, [7, [8, [9, ]]]]]]]]]def tell(l): for item in l: if type(item) is list: # item 是列表 # 再次调用本身的逻辑,传入item tell(item) else: # item 是单独的元素 print(item)tell(l)# 执行结果123456789 递归的应用二分法 数字列表,数字从小打大排列需求:判断某一个值是否存在于这个列表中123456789101112nums = [3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101]find_num = 27for num in nums: if find_num == num: print('find it') break else: continue# 执行结果find it 以上方式效率较低 算法:就是高效解决某个问题的方法二分法是算法中的其中之一1234567891011121314151617181920212223242526nums = [3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101]# l1 = [3, 11, 13, 15, 23, 27]# l2 = [23, 27]# l3 = [23]def binary_search(nums, find_num): mid_index = len(nums) // 2 if find_num > nums[mid_index]: # in the right # 从一个大列表中取出一个子列表 nums = nums[mid_index+1:] # 重复调用本身的逻辑,传入切分之后的结果 binary_search(nums, find_num) elif find_num < nums[mid_index]: # in the left nums = nums[:mid_index] # 重复调用本身的逻辑,传入切分之后的结果 binary_search(nums, find_num) else: print('find it')binary_search(nums, 23)# 执行结果find it 123456789101112131415161718192021222324252627nums = [3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101]def binary_search(nums, find_num): print(nums) # 获取查找次数 mid_index = len(nums) // 2 if find_num > nums[mid_index]: # in the right # 从一个大列表中取出一个子列表 nums = nums[mid_index+1:] # 重复调用本身的逻辑,传入切分之后的结果 binary_search(nums, find_num) elif find_num < nums[mid_index]: # in the left nums = nums[:mid_index] # 重复调用本身的逻辑,传入切分之后的结果 binary_search(nums, find_num) else: print('find it')binary_search(nums, 23)# 执行结果[3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101][3, 11, 13, 15, 23, 27][23, 27][23]find it 传入一个不存在的值,会抛超出列表索引超出范围异常 123456789101112131415161718nums = [3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101]def binary_search(nums, find_num): print(nums) mid_index = len(nums) // 2 if find_num > nums[mid_index]: nums = nums[mid_index+1:] binary_search(nums, find_num) elif find_num < nums[mid_index]: nums = nums[:mid_index] binary_search(nums, find_num) else: print('find it')binary_search(nums, 94) # 传入一个不存在的值,会抛超出列表索引超出范围异常# 执行结果IndexError: list index out of range 改进 1234567891011121314151617181920212223242526nums = [3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101]def binary_search(nums, find_num): print(nums) if len(nums) == 0: print('not exists') return mid_index = len(nums) // 2 if find_num > nums[mid_index]: nums = nums[mid_index+1:] binary_search(nums, find_num) elif find_num < nums[mid_index]: nums = nums[:mid_index] binary_search(nums, find_num) else: print('find it')binary_search(nums, 94)# 执行结果[3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101][51, 72, 81, 93, 101][93, 101][93][]not exists]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 函数的递归调用</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 匿名函数]]></title>
<url>%2F2018%2F09%2F18%2FPython%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0%2F</url>
<content type="text"><![CDATA[Python 匿名函数 有名函数123456789def f1(): print('from f1')f1()f1()# 执行结果from f1from f1 匿名函数123456789101112131415def func(x, y): return x + yprint(func) # 通过函数名拿到内存地址# 执行结果<function func at 0x000001D493372EA0>########################################################################################## 通过 函数+() 的方式来调用函数func(1, 2)print(func(1, 2))# 执行结果3 针对这种简单形式的函数,可以用匿名函数简写成一行匿名函数本身自带 return1234567891011121314151617181920print(lambda x, y: x + y)# 执行结果<function <lambda> at 0x000001DA1A6A2EA0>########################################################################################## 以下方式不是正规的匿名函数使用方式res = (lambda x, y: x + y)(1, 2)print(res)# 执行结果3#########################################################################################f = lambda x, y: x + yres = f(1, 2)print(res)# 执行结果3 不会单独使用,会与其他函数配合使用 匿名函数在于没有名字,如果没有名字表示用一次就立即回收 匿名函数的应用场景仅应用于只使用使用一次的场景 匿名函数与其他函数配合使用 配合常用的内置函数:max,min,sorted,map, filter max() 函数获取薪资最高的人名 max 默认比较的是字典的 key12345678910salaries = { 'egon': 3000, 'alex': 100000000, 'wupeiqi': 10000, 'yuanhao': 2000}print(max(salaries))# 执行结果yuanhao max() 的结果一定是字典的 key可以更改比较依据max(比较对象,key=比较依据)1234567891011121314salaries = { 'egon': 3000, 'alex': 100000000, 'wupeiqi': 10000, 'yuanhao': 2000}def func(k): return salaries[k]print(max(salaries, key=func)) # key=指定比较依据# 执行结果alex 工作原理:max() 拿到 salaries 的 key结果,传给 key=func ,把 func 的返回值当做比较依据 func 就是一次性的函数,可以用 lambda 替换1234567891011salaries = { 'egon': 3000, 'alex': 100000000, 'wupeiqi': 10000, 'yuanhao': 2000}print(max(salaries, key=lambda x: salaries[x]))# 执行结果alex 获取薪资最小的人名 1234567891011salaries = { 'egon': 3000, 'alex': 100000000, 'wupeiqi': 10000, 'yuanhao': 2000}print(min(salaries, key=lambda x: salaries[x]))# 执行结果yuanhao sorted() 排序 默认从小到大排序1234print(sorted([2, 4, 1, 5]))# 执行结果[1, 2, 4, 5] 按照薪资排序,从小到大排队 sorted 默认也是比较字典的 key如果不给比较依据的话,就按默认的字典key字符串做比较1234567891011salaries = { 'egon': 3000, 'alex': 100000000, 'wupeiqi': 10000, 'yuanhao': 2000}print(sorted(salaries))# 执行结果['alex', 'egon', 'wupeiqi', 'yuanhao'] 所以也要通过所获取的字典key,给予 比较依据,然后再做排序123456789101112131415161718salaries = { 'egon': 3000, 'alex': 100000000, 'wupeiqi': 10000, 'yuanhao': 2000}print(sorted(salaries, key=lambda x: salaries[x]))# 执行结果['yuanhao', 'egon', 'wupeiqi', 'alex']########################################################################################## 反序排列print(sorted(salaries, key=lambda x: salaries[x], reverse=True))# 执行结果['alex', 'wupeiqi', 'egon', 'yuanhao'] map() 函数 映射:把一个值映射成一个新的值 需求:把 人名 都映射成 人名_PY123456names = ['alex', 'wupeiqi', 'yuanhao', 'liuqingzheng']l = [name + "_PY" for name in names]print(l)# 执行结果['alex_PY', 'wupeiqi_PY', 'yuanhao_PY', 'liuqingzheng_PY'] map(映射规则, 可迭代对象)123456789names = ['alex', 'wupeiqi', 'yuanhao', 'liuqingzheng']obj = map(lambda x:x+"_PY", names) # map 的结果其实就是一个迭代器print(obj)# 执行结果<map object at 0x00000230B9BDCA90>print(list(obj))# 执行结果['alex_PY', 'wupeiqi_PY', 'yuanhao_PY', 'liuqingzheng_PY'] 工作原理:会把可迭代对象作为迭代器传给第一个值,作为结果返回出来 filter() 函数 把 人名_sb 结尾的留下123456names = ['alex_sb', 'wupeiqi_sb', 'egon', 'yuanhao_sb', 'liuqingzheng_sb']l = [name for name in names if name.endswith('sb')]print(l)# 执行结果['alex_sb', 'wupeiqi_sb', 'yuanhao_sb', 'liuqingzheng_sb'] filter(过滤规则, 可迭代对象)filter 会得到 names 的迭代器对象obj,然后 next(obj),将得到的值传给函数 12345678910names = ['alex_sb', 'wupeiqi_sb', 'egon', 'yuanhao_sb', 'liuqingzheng_sb']res = filter(lambda x: x.endswith('sb'), names) # filte 将函数返回值为 True 的那个值留下print(res)# 执行结果<filter object at 0x000001867584C9B0>#########################################################################################print(list(res))# 执行结果['alex_sb', 'wupeiqi_sb', 'yuanhao_sb', 'liuqingzheng_sb'] 也可以用 生成器表达式 来做12345678910names = ['alex_sb', 'wupeiqi_sb', 'egon', 'yuanhao_sb', 'liuqingzheng_sb']l = (name for name in names if name.endswith('sb'))print(l)# 执行结果<generator object <genexpr> at 0x00000209162E55C8>#########################################################################################print(list(l))# 执行结果['alex_sb', 'wupeiqi_sb', 'yuanhao_sb', 'liuqingzheng_sb']]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 匿名函数</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 生成器表达式]]></title>
<url>%2F2018%2F09%2F17%2FPython%E7%94%9F%E6%88%90%E5%99%A8%E8%A1%A8%E8%BE%BE%E5%BC%8F%2F</url>
<content type="text"><![CDATA[Python 生成器表达式 () 小括号用来生成 生成器把列表推导式的 [] 换成 () 就是生成器表达式12345g = (i for i in range(10))print(g)# 执行结果<generator object <genexpr> at 0x000001719E5E3EB8> 用生成器表达式,可以用来造一个无限个值的数据1234567891011g = (i for i in range(10))print(next(g))print(next(g))print(next(g))print(next(g))# 执行结果0123 生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋,这也是生成器的特性123456789101112131415161718192021chicken = ('鸡蛋%s' % i for i in range(5))print(chicken)# 执行结果<generator object <genexpr> at 0x10143f200>#############################################################################################chicken = ('鸡蛋%s' % i for i in range(10))print(next(chicken))print(next(chicken))print(next(chicken))print(next(chicken))# 执行结果鸡蛋0鸡蛋1鸡蛋2鸡蛋3#############################################################################################chicken = ('鸡蛋%s' % i for i in range(5))print(list(chicken)) # 因 chicken 可迭代,因而可以转成列表['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4'] 优点:省内存,一次只产生一个值在内存中 1234567891011chicken = ('egg%s' % for i in range(3))print(next(chicken))print(next(chicken))print(next(chicken))print(next(chicken))# 执行结果egg0egg1egg2StopIteration 小练习 求文件 a.txt 中最长的行的长度(长度按字符个数算,需要使用 max 函数)123456789101112131415161718192021222324252627282930313233# 获取文件内每一行的字符长度with open('a.txt', 'r', encoding='utf-8') as f: nums = [len(line) for line in f] print(nums)# 执行结果[2, 3, 4, 5, 66, 54, 54, 54, 54, 53]############################################################################################## 获取最长那一行的长度with open('a.txt', 'r', encoding='utf-8') as f: nums = [len(line) for line in f] # 这里用列表生成器会有问题,如果文件内容非常多,这个列表会变得非常大,我们可以换成生成器表达式 print(max(nums))# 执行结果66############################################################################################## 获取最长那一行的长度with open('a.txt', 'r', encoding='utf-8') as f: nums = (len(line) for line in f) print(nums)# 执行结果<generator object <genexpr> at 0x00000153AFF855C8>#############################################################################################with open('a.txt', 'r', encoding='utf-8') as f: nums = (len(line) for line in f) print(max(nums))# 执行结果66]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 生成器表达式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 面向过程编程]]></title>
<url>%2F2018%2F09%2F15%2FPython%E9%9D%A2%E5%90%91%E8%BF%87%E7%A8%8B%E7%BC%96%E7%A8%8B%2F</url>
<content type="text"><![CDATA[Python 面向过程编程 首先强调:面向过程编程绝对不是用函数编程这么简单,面向过程是一种编程思路、思想,而编程思路是不依赖于具体的语言或语法的。言外之意是即使我们不依赖于函数,也可以基于面向过程的思想编写程序 定义面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式 优点:复杂的问题流程化,进而简单化 缺点:可扩展性差,修改流水线的任意一个阶段,都会牵一发而动全身 应用:扩展性要求不高的场景,典型案例如 Linux 内核,git,httpd 举例流水线1:用户输入用户名、密码 —> 用户验证—>欢迎界面流水线2:用户输入sql —> sql解析 —> 执行功能 PS:函数的参数传入,是函数吃进去的食物,而函数 return 的返回值,是函数拉出来的结果,面向过程的思路就是,把程序的执行当做一串首尾相连的功能,该功能可以是函数的形式,然后一个函数吃,拉出的东西给另外一个函数吃,另外一个函数吃了再继续拉给下一个函数吃。。。 注册功能1234567891011121314151617181920212223242526272829303132333435# 阶段1: 接收用户输入账号与密码,完成合法性校验def talk(): while True: username = input('请输入你的用户名: ').strip() if username.isalpha(): break else: print('用户必须为字母') while True: password1 = input('请输入你的密码: ').strip() password2 = input('请再次输入你的密码: ').strip() if password1 == password2: break else: print('两次输入的密码不一致') return username,password1# 阶段2: 将账号密码拼成固定的格式def register_interface(username, password): format_str = '%s:%s\n' %(username, password) return format_str# 阶段3: 将拼好的格式写入文件def handle_file(format_str, filepath): with open(r'%s' % filepath, 'at', encoding='utf-8') as f: f.write(format_str)def register(): user, pwd = talk() format_str = register_interface(user, pwd) handle_file(format_str, 'user.txt')register() 扩展功能麻烦 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950# 阶段1: 接收用户输入账号与密码,完成合法性校验def talk(): while True: username = input('请输入你的用户名: ').strip() if username.isalpha(): break else: print('用户必须为字母') while True: password1 = input('请输入你的密码: ').strip() password2 = input('请再次输入你的密码: ').strip() if password1 == password2: break else: print('两次输入的密码不一致') role_dic = { '1':'user', '2':'admin' } while True: for k in role_dic: print(k,role_dic[k]) choice = input('请输入您的身份>>: ').strip() if choice not in role_dic: print('输入的身份不存在') continue role = role_dic[choice] return username, password1, role# 阶段2: 将账号密码拼成固定的格式def register_interface(username, password, role): format_str = '%s:%s:%s\n' % (username, password, role) return format_str# 阶段3: 将拼好的格式写入文件def handle_file(format_str, filepath): with open(r'%s' % filepath, 'at', encoding='utf-8') as f: f.write(format_str)def register(): user, pwd, role = talk() format_str = register_interface(user, pwd, role) handle_file(format_str, 'user.txt')register() PS:talk 内对 用户名 \ 密码 \ 角色 的合法性校验也可以摘出来做成单独的功能,但本例就写到一个函数内了,力求用更少的逻辑来为大家说明过程式编程的思路 认证功能12345678910111213141516171819202122232425262728def interactive(): """接收用户输入的用户名、密码""" uname = input('username: ').strip() pwd = input('password: ').strip() return uname, group, pwddef auth(uname, group, pwd): """认证用户名与密码是否正确""" if uname == 'egon' and pwd == '123': return True, uname else: return False, unamedef index(res): """如果认证成功,则打印欢迎界面""" if res[0]: print('%s 登录成功' % res[1]) else: print('%s 登录失败' % res[1])uname, pwd = interactive()res = auth(uname, pwd)index(res)# 执行结果username: egonpassword: 123egon 登录成功 扩展功能麻烦123456789101112131415161718192021222324252627282930def interactive(): """接收用户输入的用户名、密码""" uname = input('username: ').strip() group = input('group: ').strip() pwd = input('password: ').strip() return uname, group, pwddef auth(uname, group, pwd): """认证用户名与密码是否正确""" if uname == 'egon' and pwd == '123' and group == 'group1': return True, uname, group else: return False, uname, groupdef index(res): """如果认证成功,则打印欢迎界面""" if res[0]: print('部门:%s 员工:%s 登录成功' % (res[2], res[1])) else: print('部门:%s 员工:%s 登录失败' % (res[2], res[1]))uname, group, pwd = interactive()res = auth(uname, group, pwd)index(res)# 执行结果username: egongroup: group1password: 123部门:group1 员工:egon 登录成功]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 面向过程编程</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 生成器]]></title>
<url>%2F2018%2F09%2F12%2FPython%E7%94%9F%E6%88%90%E5%99%A8%2F</url>
<content type="text"><![CDATA[Python 生成器 什么是生成器? 在函数体内凡是有 yield 关键字,再调用函数就不会执行函数体代码,得到的返回值就是一个生成器对象。 为什么要用生成器? 为了掌握一种自定义迭代器的方式12345678910def func(): print('first') print('second') print('third')func()# 执行结果firstsecondthird 在函数体内凡是有 yield 关键字,再调用函数就不会执行函数体代码,得到的返回值是一个生成器对象。123456789101112def func(): print('first') yield 1 print('second') yield 2 print('third')g = func()print(g)# 执行结果<generator object func at 0x000002C5C3A05EB8> 用 yield 的目的就是为了使用自定义迭代器为什么要自定义迭代器?优点:更加节省内存,可以生成一个无限大的迭代器 123456789101112def func(): print('first') yield 1 print('second') yield 2 print('third')g = func()next(g) # 等于 g.__next__()# 执行结果first next(g) 会去执行一次,才会去执行 g 所对应的函数体内的代码运行过程:会触发生成器 g 所对应函数的执行,直到遇到 yield 才停止,然后把 yield 后的返回值当做本次 next 操作的结果12345678910111213141516171819202122232425def func(): print('first') yield 1 # 暂停 print('second') yield 2 # 暂停 print('third')g = func()res1 = next(g) # g.__next__()print(res1)res2 = next(g) # g.__next__()print(res2)res3 = next(g) # g.__next__()# 执行结果first1second2thirdTraceback (most recent call last): res3 = next(g)StopIteration 1234567891011121314151617181920def func(): print('first') yield 1 # 暂停 print('second') yield 2 # 暂停 print('third')# g = func()# for item in g:# print(item)for item in func(): print(item)# 执行结果first1second2third 示例 模拟 range() 函数功能自定义一个 range() 函数range(1, 100, 2) Python2 的做法123456789101112def my_range(start, stop, step=1): res = [] while start < stop: res.append(start) start += step return resres = my_range(1, 100, 2)print(res)# 执行结果[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99] Python3 的做法12345678910def my_range(start, stop, step=1): while start < stop: yield start start += stepres = my_range(1, 1000, 2)print(res)# 执行结果<generator object my_range at 0x00000294DBE75EB8> 只要函数体内出现 yield 再去调函数,就不会运行函数体代码再调用函数会得到一个返回值,这个返回值就是我们所需要的生成器对象(迭代器对象)123456789101112def my_range(start, stop, step=1): print('开始运行') while start < stop: yield start start += step print('结束运行')obj = my_range(1, 1000, 2)print(obj)# 执行结果<generator object my_range at 0x00000294DBE75EB8> 123456789101112131415161718def my_range(start, stop, step=1): print('开始运行') while start < stop: yield start start += step print('结束运行')obj = my_range(1, 1000, 2)res1 = next(obj)print(res1)res2 = next(obj)print(res2)# 执行结果开始运行13 123456789101112131415161718def my_range(start, stop, step=1): print('开始运行') while start < stop: yield start start += step print('结束运行')for item in my_range(1, 10, 2): print(item)# 执行结果开始运行13579结束运行 1234567891011121314def my_range(start, stop, step=1): while start < stop: yield start start += stepfor item in my_range(1, 10, 2): print(item)# 执行结果13579 总结 yield 提供了一种自定义迭代器的方式。与 return 对比,都能返回值,都能返回多个值,都没有类型限制。return 只能返回一次值,yield 能暂停住函数,把函数体暂停在某一个位置,可以返回多次值。yield 可以帮我们保存函数的执行状态。 yield 表达式形式应用示例1 1234567891011def dog(name): print('%s 准备开吃' % name) while True: food = yield print('%s 吃了 %s' % (name, food))g = dog('来福')print(g)# 执行结果<generator object dog at 0x000001A607255F68> 示例2 1234567891011def dog(name): print('%s 准备开吃' % name) while True: food = yield print('%s 吃了 %s' % (name, food))g = dog('来福')next(g) # 让来福准备好,即让生成器对象先暂停到一个位置,准备接收# 执行结果来福 准备开吃 示例3 12345678910111213def dog(name): print('%s 准备开吃' % name) while True: food = yield # 暂停 food = yield = None print('%s 吃了 %s' % (name, food))g = dog('来福')next(g) # 让来福准备好,即让生成器对象先暂停到一个位置,准备接收next(g)# 执行结果来福 准备开吃来福 吃了 None 示例4 1234567891011121314151617def dog(name): print('%s 准备开吃' % name) while True: food = yield # 暂停 food = yield = '骨头' print('%s 吃了 %s' % (name, food))g = dog('来福')next(g) # 让来福准备好,即让生成器对象先暂停到一个位置,准备接收g.send('骨头')g.send('肉')g.send('包子')# 执行结果来福 准备开吃来福 吃了 骨头来福 吃了 肉来福 吃了 包子 上面方式是只调用了一次函数 下面方式是每次都要调用一次函数123456def dog(food): print('%s 吃了 %s' % (food))dog('来福1')dog('来福2')dog('来福3') 两者的区别执行函数体代码,会产生一个内存空间每调用一次函数,就要申请一次内存空间 示例512345678910111213141516171819202122232425262728def dog(name): print('%s 准备开吃' % name) while True: food = yield 123123 # 暂停 food = yield = None print('%s 吃了 %s' % (name, food))g = dog('来福')res1 = next(g)print(res1)res2 = g.send('骨头') # food = yield 123123 <== 暂停 food = yield = '骨头'print(res2)res3 = g.send('骨头1') # food = yield 123123 <== 暂停 food = yield = '骨头1'print(res3)res4 = next(g) # food = yield 123123 <== 暂停 food = yield = Noneprint(res4)# 执行结果来福 准备开吃123123来福 吃了 骨头123123来福 吃了 骨头1123123来福 吃了 None123123 示例7 123456789101112131415161718192021222324252627282930def dog(name): print('%s 准备开吃' % name) food_list = [] while True: food = yield food_list print('%s 吃了 %s' % (name, food)) food_list.append(food)g = dog('来福')res1 = next(g)print(res1)res2 = g.send('骨头')print(res2)res3 = g.send('骨头1')print(res3)res4 = g.send('骨头2')print(res4)# 执行结果来福 准备开吃[]来福 吃了 骨头['骨头']来福 吃了 骨头1['骨头', '骨头1']来福 吃了 骨头2['骨头', '骨头1', '骨头2'] 不能 send 一个不为 None 的值, 给一个刚刚开始的 生成器 TypeError: can't send non-None value to a just-started generator不能 send 一个不为 None 的值, 给一个刚刚开始的 生成器 123456789101112131415def dog(name): print('%s 准备开吃' % name) food_list = [] while True: food = yield food_list print('%s 吃了 %s' % name) food_list.append(food)g = dog('来福')g.send('包子')# 执行结果Traceback (most recent call last): g.send('包子')TypeError: can't send non-None value to a just-started generator g.send(None) 等同于 next(g)强调:对于表达式形式 yield 的生成器,在使用前必须先用 next(g) 或 g.send(None) 初始化一次12345678910111213141516def dog(name): print('%s 准备开吃' % name) food_list = [] while True: food = yield food_list print('%s 吃了 %s' % (name, food)) food_list.append(food)g = dog('来福')next(g)# g.send(None)g.send('包子')# 执行结果来福 准备开吃来福 吃了 包子 练习 编写装饰器,实现初始化协程函数的功能12345678910111213141516171819202122def init(func): def wrapper(*args, **kwargs): g = func(*args, **kwargs) next(g) return g return wrapper@initdef eater(name): print('%s 准备开始吃饭啦' % name) food_list = [] while True: food = yield food_list print('%s 吃了 %s' % (name, food)) food_list.append(food)g = eater('张三')g.send('蒸羊羔')# 执行结果张三 准备开始吃饭啦张三 吃了 蒸羊羔 实现功能:grep -rl 'python' /etc注意:target.send(...) 在拿到 target 的返回值后才算执行结束 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051import osdef init(func): def wrapper(*args,**kwargs): g=func(*args,**kwargs) next(g) return g return wrapper@initdef search(target): while True: filepath=yield g=os.walk(filepath) for dirname,_,files in g: for file in files: abs_path=r'%s\%s' %(dirname,file) target.send(abs_path)@initdef opener(target): while True: abs_path=yield with open(abs_path,'rb') as f: target.send((f,abs_path))@initdef cat(target): while True: f,abs_path=yield for line in f: res=target.send((line,abs_path)) if res: break@initdef grep(pattern,target): tag=False while True: line,abs_path=yield tag tag=False if pattern.encode('utf-8') in line: target.send(abs_path) tag=True@initdef printer(): while True: abs_path=yield print(abs_path)g=search(opener(cat(grep('你好',printer()))))# g.send(r'E:\CMS\aaa\db')g=search(opener(cat(grep('python',printer()))))g.send(r'E:\CMS\aaa\db')]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 生成器</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Prometheus Operator]]></title>
<url>%2F2018%2F09%2F10%2FPrometheus-Operator%2F</url>
<content type="text"><![CDATA[Prometheus Operator Prometheus介绍Prometheus是继Kubernetes之后CNCF基金会的第二个项目,最早也是孵化于Google内部的Brogmon监控系统,后来由前Google工程师在SoundCloud开源,现在已经成为云原生生态的标准监控系统。 Prometheus是一个开源的完整监控解决方案,涵盖数据采集、查询、告警、展示整个监控流程,下图是Prometheus的架构图: Prometheus ServerPrometheus server是整个方案的核心组件,负责监控数据的获取、存储和查询,它本身就是一个时序数据库,将采集到的监控数据按照时间序列的方式存储在本地,Prometheus Server对外提供了自定义的PromQL语言,实现对数据的查询以及分析。 Prometheus server可以通过静态配置监控目标,也可以通过服务发现的方式动态监控目标,Prometheus server采用pull的方式到target暴露出的对应http接口获取监控数据。 ExportersExporters将数据采集的target通过http的形式暴露给Prometheus server,Prometheus server通过访问该exporter提供的endpoints端点,获取到需要采集的监控数据。 Exporters分为两类: 直接采集:这类的exporters内置在了相应的应用中,能够直接提供target端点,比如etcd、kubernetes组件,都直接内置了用于向Prometheus暴露监控数据的端点。 间接采集:原有的监控目标不支持prometheus,需要通过prometheus提供的Client Library编写该监控目标的监控采集程序,比如redis、tomcat、mysql等应用,需要有外置的exporters先采集应用的监控项,再通过exporters的http接口把metrics暴露给prometheus server PushGateway因为prometheus数据采集采用pull模式,需要prometheus server能直接访问到exporters,当网络环境无法满足时,需要通过PushGateway中转,内部网络的监控数据主动pushl到Gateway当中,而Prometheus Server则可以采用同样Pull的方式从PushGateway中获取到监控数据。 AlertManager在prometheus server的配置文件中可以配置相应的告警规则,一旦达到告警规则,就会触发AlertManager,至于之后的操作由AlertManager自定义,可以是邮箱、微信、钉钉或webhook等。 promethus的告警被分成两个部分: 通过在Prometheus中定义告警触发条件规则,并向Alertmanager发送告警信息 Alertmanager作为一个独立的组件,负责接收并处理来自Prometheus Server(也可以是其它的客户端程序)的告警信息 在Prometheus全局配置文件中prometheus.yml通过rule_files指定一组告警规则文件的访问路径。Prometheus启动后会自动扫描这些路径下规则文件中定义的内容,并且根据这些规则计算是否向外部发送通知 在prometheus.yml中添加监控告警文件 12rule_files: - /etc/prometheus/rules/*.rules 在目录/etc/prometheus/rules/下创建告警文件hoststats-alert.rules,重启promethus后这个告警文件就能被promethus读取。 在promethus.yml中添加关联alertmanager配置: 1234alerting: alertmanagers: - static_configs: targets: ['localhost:9093'] 成功后就可以在alertmanager中查看promethus中触发的告警,之后的操作可以由alertmanager自定义 alertmanager是独立的服务,配置文件默认在/etc/prometheus/alertmanager.yml,配置文件中目前只写入基础配置: 1234route: receiver: 'default-receiver'receivers: - name: default-receiver route:所有的告警信息都会从配置中的顶级路由(route)进入路由树,根据路由规则将告警信息发送给相应的接收器 receivers:告警信息会根据路由发送给对应receivers,接收器可以关联邮件,Slack以及其它方式接收告警信息 Prometheus Operator对于云原生基础的Kubernetes,Prometheus对其有着代码级别的支持,Kubernetes中的组件原生支持Prometheus的metrics路径,而且能够通过服务发现的形式自动监控集群。在Kubernetes中部署Prometheus可以通过operator的框架,下图是prometheus-operator的架构: 其中operator是其核心,作为一个控制器,operator首先会创建Prometheus、ServiceMonitor、AlertManager三个CRD资源对象,并且监控并维持这三种资源对象的状态。 创建的Prometheus资源对象就是作为Prometheus server,ServiceMonitor就是exporters的抽象,通过ServiceMonitor,Prometheus server能够pull到对应target的监控metrics。 在prometheus的yaml文件中会指定需要采集的ServiceMonitor: 123serviceMonitorSelector: matchExpressions: - {key: k8s-app, operator: Exists} 只有在ServiceMonitor的yaml文件中匹配上了k8s-app的ServiceMonitor才能被Prometheus server采集: 1234567891011121314151617apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: kube-controller-manager labels: k8s-app: kube-controller-manager ##Prometheus中的ServiceMonitor选择器spec: jobLabel: k8s-app endpoints: - port: http-metrics ##对应service的端口名 interval: 30s selector: matchLabels: k8s-app: kube-controller-manager ##选择对应label的service namespaceSelector: matchNames: - kube-system ##选择对应namespace 也就是Prometheus server选取对应的ServiceMonitor进行监控,而ServiceMonitor会对应到相应的service,service会对应到endpoints,Prometheus server通过选择ServiceMonitor就能访问到最终的监控目标。 在Kubernetes中部署Prometheus Operator因为这是部署在非原生的k8s集群,所以需要做一些额外操作才能监控整个集群 下载源码123$ wget https://codeload.github.com/coreos/prometheus-operator/tar.gz/v0.16.0 -O prometheus-operator-0.16.0.tar.gz$ tar -zxvf prometheus-operator-0.16.0.tar.gz$ cd prometheus-operator-0.16.0/contrib/kube-prometheus 创建单独的namespace: 1$ kubectl create ns monitoring 安装 Prometheus Operator删除RBAC相关配置: 12345678910$ tree manifests/prometheus-operatormanifests/prometheus-operator├── prometheus-operator-cluster-role-binding.yaml├── prometheus-operator-cluster-role.yaml├── prometheus-operator-service-account.yaml├── prometheus-operator-service.yaml└── prometheus-operator.yaml0 directories, 5 files$ rm -rf manifests/prometheus-operator/prometheus-operator-cluster-role*$ rm -rf manifests/prometheus-operator/prometheus-operator-service-account.yaml 删除 prometheus-operator.yaml 中的 serviceAccountName: prometheus-operator: 12345678910111213apiVersion: extensions/v1beta1kind: Deploymentmetadata:...spec: replicas: 1 template: metadata: ... spec: containers: ... # serviceAccountName: prometheus-operator # 将这一行删除 部署Prometheus Operator 1234567$ tree manifests/prometheus-operator/manifests/prometheus-operator├── prometheus-operator-service.yaml└── prometheus-operator.yaml0 directories, 2 files$ kubectl apply -f manifests/prometheus-operator/ -n monitoringprometheus-operator-599487016-39w9m 1/1 Running 0 1d 部署完成后,operator会自动创建三个CRD: 12345$ kubectl get crdNAME KINDalertmanagers.monitoring.coreos.com CustomResourceDefinition.v1beta1.apiextensions.k8s.ioprometheuses.monitoring.coreos.com CustomResourceDefinition.v1beta1.apiextensions.k8s.ioservicemonitors.monitoring.coreos.com CustomResourceDefinition.v1beta1.apiextensions.k8s.io 安装Node ExporterPrometheus监控主机需要Node Exporter提供相应的target删除与 RBAC 相关的 yaml 文件: 12345678910$ tree manifests/node-exportermanifests/node-exporter├── node-exporter-cluster-role-binding.yaml├── node-exporter-cluster-role.yaml├── node-exporter-daemonset.yaml├── node-exporter-service-account.yaml└── node-exporter-service.yaml0 directories, 5 files$ rm -rf manifests/node-exporter/node-exporter-cluster-role*$ rm -rf manifests/node-exporter/node-exporter-service-account.yaml 删除node-exporter-daemonset.yaml中的serviceAccountName: node-exporter 123456789101112apiVersion: extensions/v1beta1kind: DaemonSetmetadata:...spec: ... template: metadata: ... spec: # serviceAccountName: node-exporter # 将这一行删除 ... 部署 node-exporter 1234567891011$ tree manifests/node-exportermanifests/node-exporter├── node-exporter-daemonset.yaml└── node-exporter-service.yaml0 directories, 2 files$ kubectl apply -f manifests/node-exporter -n monitoring$ kubectl -n monitoring get pods -l app=node-exporterNAME READY STATUS RESTARTS AGEnode-exporter-3mnvn 2/2 Running 0 1dnode-exporter-lwbjm 2/2 Running 0 1dnode-exporter-p2bw5 2/2 Running 0 1d 安装 Kube-state-metrics删除与 RBAC 相关的 yaml 文件: 123456789101112$ tree manifests/kube-state-metricsmanifests/kube-state-metrics├── kube-state-metrics-cluster-role-binding.yaml├── kube-state-metrics-cluster-role.yaml├── kube-state-metrics-deployment.yaml├── kube-state-metrics-role-binding.yaml├── kube-state-metrics-role.yaml├── kube-state-metrics-service-account.yaml└── kube-state-metrics-service.yaml0 directories, 7 files$ rm -rf manifests/kube-state-metrics/*role*$ rm -rf manifests/kube-state-metrics/kube-state-metrics-service-account.yaml 删除kube-state-metrics-deployment.yaml中的serviceAccountName: kube-state-metrics。 123456789101112apiVersion: extensions/v1beta1kind: Deploymentmetadata:...spec: ... template: metadata: ... spec: # serviceAccountName: kube-state-metrics # 将这一行删除 ... 部署 kube-state-metrics: 123456789$ tree manifests/kube-state-metricsmanifests/kube-state-metrics├── kube-state-metrics-deployment.yaml└── kube-state-metrics-service.yaml0 directories, 2 files$ kubectl apply -f manifests/kube-state-metrics -n monitoring$ kubectl -n monitoring get pods -l app=kube-state-metricsNAME READY STATUS RESTARTS AGEkube-state-metrics-3424261376-hcvd5 4/4 Running 0 1d 安装 Prometheus 和 ServiceMonitor删除与 RBAC 相关的 yaml 文件: 123456789101112131415161718$ tree manifests/prometheusmanifests/prometheus├── prometheus-k8s-role-bindings.yaml├── prometheus-k8s-roles.yaml├── prometheus-k8s-rules.yaml├── prometheus-k8s-service-monitor-alertmanager.yaml├── prometheus-k8s-service-monitor-apiserver.yaml├── prometheus-k8s-service-monitor-kube-controller-manager.yaml├── prometheus-k8s-service-monitor-kubelet.yaml├── prometheus-k8s-service-monitor-kube-scheduler.yaml├── prometheus-k8s-service-monitor-kube-state-metrics.yaml├── prometheus-k8s-service-monitor-node-exporter.yaml├── prometheus-k8s-service-monitor-prometheus-operator.yaml├── prometheus-k8s-service-monitor-prometheus.yaml├── prometheus-k8s-service.yaml└── prometheus-k8s.yaml0 directories, 14 files$ rm -rf manifests/prometheus/*role* 删除 prometheus-k8s.yaml 中的 serviceAccountName: prometheus-k8s: 12345678910apiVersion: monitoring.coreos.com/v1kind: Prometheusmetadata: name: k8s labels: prometheus: k8sspec: replicas: 2 version: v2.0.0# serviceAccountName: prometheus-k8s # 将这一行删除 部署 Prometheus 和 ServiceMonitor: 1234567891011121314151617$ kubectl -n monitoring apply -f manifests/prometheus/$ kubectl -n monitoring get prometheusesNAME KINDk8s Prometheus.v1.monitoring.coreos.com$ kubectl -n monitoring get servicemonitorNAME KINDalertmanager ServiceMonitor.v1.monitoring.coreos.cometcd-k8s ServiceMonitor.v1.monitoring.coreos.comkube-apiserver ServiceMonitor.v1.monitoring.coreos.comkube-controller-manager ServiceMonitor.v1.monitoring.coreos.comkube-scheduler ServiceMonitor.v1.monitoring.coreos.comkube-state-metrics ServiceMonitor.v1.monitoring.coreos.comkubelet ServiceMonitor.v1.monitoring.coreos.comnode-exporter ServiceMonitor.v1.monitoring.coreos.comprometheus ServiceMonitor.v1.monitoring.coreos.comprometheus-operator ServiceMonitor.v1.monitoring.coreos.com 监控kube-controller-manager kube-scheduler查看的kube-controller-manager的ServiceMonitor的yaml文件: 1234567891011121314151617apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: kube-controller-manager labels: k8s-app: kube-controller-managerspec: jobLabel: k8s-app endpoints: - port: http-metrics interval: 30s selector: matchLabels: k8s-app: kube-controller-manager namespaceSelector: matchNames: - kube-system 这个ServiceMonitor对应指定的是kube-system命名空间的打了k8s-app: kube-controller-manager标签的service,不是对应的endpoint的标签,对应endpoint端口名是http-metrics 因为kube-controller-manager和kube-scheduler在kube-system的命名空间中没有相应的service和endpoints,所以需要手动创建: 123456789101112131415161718192021222324252627282930313233343536# kube-controller-managerapiVersion: v1kind: Servicemetadata: namespace: kube-system name: kube-controller-manager-prometheus-discovery labels: k8s-app: kube-controller-managerspec: type: ClusterIP clusterIP: None ports: - name: http-metrics port: 10252 targetPort: 10252 protocol: TCP---apiVersion: v1kind: Endpointsmetadata: namespace: kube-system name: kube-controller-manager-prometheus-discovery labels: k8s-app: kube-controller-managersubsets:- addresses: - ip: 192.168.2.191 - ip: 192.168.2.194 - ip: 192.168.2.183 ports: - name: http-metrics port: 10252 protocol: TCP 123456789101112131415161718192021222324252627282930313233343536#kube-schedulerapiVersion: v1kind: Servicemetadata: namespace: kube-system name: kube-scheduler-prometheus-discovery labels: k8s-app: kube-schedulerspec: type: ClusterIP clusterIP: None ports: - name: http-metrics port: 10251 targetPort: 10251 protocol: TCP---apiVersion: v1kind: Endpointsmetadata: namespace: kube-system name: kube-scheduler-prometheus-discovery labels: k8s-app: kube-schedulersubsets:- addresses: - ip: 192.168.2.191 - ip: 192.168.2.194 - ip: 192.168.2.183 ports: - name: http-metrics port: 10251 protocol: TCP 监控api-server在原生的k8s集群中,是可以直接监控api-server的,但是在dce集群中,部署多个控制节点后kubernetes的endpoint对应的ip会一直在各个主机ip中变化,所以新建api-server的service和endpoints。在prometheus的配置文件中,通过label来过滤api-server的target: 1234567891011relabel_configs:- source_labels: [__meta_kubernetes_service_label_component] separator: ; regex: apiserver replacement: $1 action: keep- source_labels: [__meta_kubernetes_service_label_provider] separator: ; regex: kubernetes replacement: $1 action: keep 能看到service的label的值只能是apiserver和kubernetes,所以新建api-server的service和endpoint如下: 123456789101112131415161718192021222324252627282930313233apiVersion: v1kind: Servicemetadata: labels: component: apiserver provider: kubernetes name: kubernetes-dce namespace: defaultspec: clusterIP: None ports: - name: https port: 443 protocol: TCP targetPort: 16443 type: ClusterIP---apiVersion: v1kind: Endpointsmetadata: name: kubernetes-dce namespace: defaultsubsets:- addresses: - ip: 192.168.2.191 - ip: 192.168.2.194 - ip: 192.168.2.183 ports: - name: https port: 16443 protocol: TCP 监控ETCDETCD作为kubernetes集群的数据持久化的后端,是集群所有数据的存储点,也需要对其有相应监控。CoreOS给出了监控ETCD的方法监控ETCD 创建ETCD对应访问证书因为监控ETCD不像监控kubernetes组件是通过api-server或直接使用http访问组件的target,需要使用https双向证书验证,所以要先创建可访问ETCD的secret,供prometheus server使用: 1kubectl -n monitoring create secret generic etcd-certs --from-file=/etc/cni/net.d/calico-tls/etcd-cert --from-file=/etc/cni/net.d/calico-tls/etcd-key --from-file=/etc/cni/net.d/calico-tls/etcd-ca 证书、私钥及ca证书是可访问ETCD的,路径是主机本地存储证书的目录。在prometheus的yaml文件中挂载证书: 1234567891011apiVersion: monitoring.coreos.com/v1kind: Prometheusmetadata: name: k8s labels: prometheus: k8sspec: replicas: 2 secrets: - etcd-certs version: v1.7.1 如果已创建,可以直接edit该对象. 创建ETCD的ServiceMonitor 12345678910111213141516171819202122232425apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: etcd-k8s labels: k8s-app: etcd-k8sspec: jobLabel: k8s-app endpoints: - port: api interval: 30s scheme: https tlsConfig: caFile: /etc/prometheus/secrets/etcd-certs/etcd-ca certFile: /etc/prometheus/secrets/etcd-certs/etcd-cert keyFile: /etc/prometheus/secrets/etcd-certs/etcd-key #use insecureSkipVerify only if you cannot use a Subject Alternative Name insecureSkipVerify: true #serverName: ETCD_DNS_OR_ALTERNAME_ selector: matchLabels: k8s-app: etcd namespaceSelector: matchNames: - monitoring 其中tlsConfig的文件位置是prometheus容器里面挂载证书的位置,不确定的话可以进入容器内部验证一下当证书serverName和etcd中签发的不匹配可以使用insecureSkipVerify: true 创建ETCD对应的service和endpoints 1234567891011121314151617181920212223242526272829303132apiVersion: v1kind: Servicemetadata: name: etcd-k8s labels: k8s-app: etcdspec: type: ClusterIP clusterIP: None ports: - name: api port: 12379 protocol: TCP---apiVersion: v1kind: Endpointsmetadata: name: etcd-k8s labels: k8s-app: etcdsubsets:- addresses: - ip: 192.168.2.191 nodeName: dcemaster1 - ip: 192.168.2.194 nodeName: dcemaster2 - ip: 192.168.2.183 nodeName: dcemaster3 ports: - name: api port: 12379 protocol: TCP 登录prometheus的UI查看对应target是不是都是up状态,up状态说明数据pull正常 安装Grafana作为prometheus前端展示页面,Grafana提供了强大的数据聚合和展示的功能,可以通过自定义前端配置修改dashboard,官方社区有很多kubernetes的前端json文件供使用 1234$ ls manifests/grafana/grafana-credentials.yaml grafana-dashboards.yaml grafana-deployment.yaml grafana-service.yaml$ kubectl -n monitoring apply -f manifests/grafana/ 部署后就能通过Grafana监控整个kubernetes集群 配置AlertManager1234$ ls manifests/alertmanager/alertmanager-config.yaml alertmanager-service.yaml alertmanager.yaml$ kubectl -n monitoring apply -f manifests/alertmanager/ 告警接收器可以通过以下形式进行配置: 12receivers: - <receiver> ... 每一个receiver具有一个全局唯一的名称,并且对应一个或者多个通知方式: 1234567891011121314151617name: <string>email_configs: [ - <email_config>, ... ]hipchat_configs: [ - <hipchat_config>, ... ]pagerduty_configs: [ - <pagerduty_config>, ... ]pushover_configs: [ - <pushover_config>, ... ]slack_configs: [ - <slack_config>, ... ]opsgenie_configs: [ - <opsgenie_config>, ... ]webhook_configs: [ - <webhook_config>, ... ]victorops_configs: [ - <victorops_config>, ... ] 目前官方内置的第三方通知集成包括:邮件、 即时通讯软件(如Slack、Hipchat)、移动应用消息推送(如Pushover)和自动化运维工具(例如:Pagerduty、Opsgenie、Victorops)。Alertmanager的通知方式中还可以支持Webhook,通过这种方式开发者可以实现更多个性化的扩展支持。 以下是集成SMTP邮件的示例每一个receiver可以对应一组邮件通知配置email_configs,如下所示: 123name: <string>email_configs: [ - <email_config>, ... ] email_config配置: 123456789101112131415161718192021222324252627# Whether or not to notify about resolved alerts.[ send_resolved: <boolean> | default = false ]# The email address to send notifications to.to: <tmpl_string># The sender address.[ from: <tmpl_string> | default = global.smtp_from ]# The SMTP host through which emails are sent.[ smarthost: <string> | default = global.smtp_smarthost ]# SMTP authentication information.[ auth_username: <string> | default = global.smtp_auth_username ][ auth_password: <secret> | default = global.smtp_auth_password ][ auth_secret: <secret> | default = global.smtp_auth_secret ][ auth_identity: <string> | default = global.smtp_auth_identity ]# The SMTP TLS requirement.[ require_tls: <bool> | default = global.smtp_require_tls ]# The HTML body of the email notification.[ html: <tmpl_string> | default = '{{ template "email.default.html" . }}' ]# Further headers email header key/value pairs. Overrides any headers# previously set by the notification implementation.[ headers: { <string>: <tmpl_string>, ... } ] 如果所有的邮件配置使用了相同的SMTP配置,则可以直接定义全局的SMTP配置: 12345678global: [ smtp_from: <tmpl_string> ] [ smtp_smarthost: <string> ] [ smtp_auth_username: <string> ] [ smtp_auth_password: <secret> ] [ smtp_auth_secret: <secret> ] [ smtp_auth_identity: <string> ] [ smtp_require_tls: <bool> | default = true ] 以Gmail邮箱为例: 1234567891011global: smtp_smarthost: smtp.gmail.com:587 smtp_from: <smtp mail from> smtp_auth_username: <usernae> smtp_auth_identity: <username> smtp_auth_password: <password>receivers: - name: default-receiver email_configs: - to: <mail to address> 手动拉高CPU使用量后会触发告警并发送告警邮件: 1cat /dev/zero>/dev/null 参考文档https://www.troyying.xyz/index.php/operate/15.html]]></content>
<categories>
<category>Kubernetes笔记</category>
</categories>
<tags>
<tag>Prometheus Operator</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack 帮助查看]]></title>
<url>%2F2018%2F09%2F10%2FSaltStack%E6%9F%A5%E7%9C%8B%E5%B8%AE%E5%8A%A9%2F</url>
<content type="text"><![CDATA[SaltStack 查看帮助 查看所有 module 列表查看 Minion端 支持的所有 module 列表'*' sys.list.module1salt '*' sys.list.module 查看指定 module 的所有 function查看 cmd module 的所有 function 的命令1salt '*' sys.list_functions cmd 查看 所有 模块函数帮助信息1salt '*' sys.doc | less 查看指定 module 用法查看 cmd module 的详细用法与示例1salt '*' sys.doc cmd 查看所有 states 列表查看 Minion端 支持的所有 states 列表1salt '*' sys.list_state_modules 查看指定 states 的所有 function查看 file.states 的所有 function1salt '*' sys.list_state_functions file 查看指定 states 用法查看 file.states 的详细用法与示例1salt '*' sys.state_doc file 查看指定 states 指定 function 用法查看 file.managed states 的详细用法与示例1salt '*' sys.state_doc file.managed]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack 帮助查看</tag>
</tags>
</entry>
<entry>
<title><![CDATA[GitLab持续集成-(.gitlab-ci.yml)]]></title>
<url>%2F2018%2F09%2F08%2FGitLab%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90(.gitlab-ci.yml)%2F</url>
<content type="text"><![CDATA[GitLab持续集成-(.gitlab-ci.yml) 从7.12版本开始,GitLab CI使用YAML文件(.gitlab-ci.yml)来管理项目配置。该文件存放于项目仓库的根目录,它定义该项目如何构建。 stages stages用来定义可以被job调用的stages。stages的规范允许有灵活的多级pipelines。stages中元素的顺序决定了对应job的执行顺序: 相同stage的job是并行执行的; 下一个stage的job在前一个stage的job成功完成后才开始执行; 如果.gitlab-ci.yml中没有定义stages,那么stages默认定义为build、test和deploy; 如果一个job没有指定stage,那么这个任务会分配到test stage。 variables variables用来定义变量,全局变量作用于所有job,也可以在指定的job中定义变量(优先级高于全局变量)如果在job中想禁用全局定义的变量,可通过variables: {}定义一个空的哈希值。 GitLab CI/CD内置变量 variables 变量值 CI_JOB_NAME 对应的job_name GIT_STRATEGY 指定git获取代码的方式(clone,fetch,none) jobs jobs用来定义了一组作业,其中必须包含script语句。 job.stage(默认:test) job中指定的stage必须是stages中存在的元素 job.tags 指定该job所允许运行的Runner,必须在注册Runner时设置Runner的tag job.allow_failure 用于指定该job允许执行失败,则如果执行失败也不会影响下一个stage的执行。 job.script script是job中必须指定的语句,指定Runner所要执行的命令 job.before_script、job.after_script 指定script执行前/后所执行的命令,也可定义在全局模式,则在所有job中的script执行前/后都会执行。 job.artifacts 用于指定job执行成功后,将会被发送到Gitlab中的文件,且默认情况下job之间会根据stage的优先级自动下载之前所有stage中的artifacts。 artifacts.paths:必选 artifacts.name:指定artifact的名称,同时Gitlab上下载的文件名即为artifact_name.zip artifacts.when:指定artifact上传到Gitlab的条件(on_success[默认],on_failure,always) artifacts.expire_in:指定artifact的过期时间(默认为30天),使用keep可永久保存 job.dependencies dependencies用于在不同的job之间指定在不同的job之间传递artifacts,dependencies: []可禁止该job下载artifacts job.only、job.except only和except是两个参数用分支策略来限制jobs构建 only和except可同时使用。如果在一个job配置中同时存在,则同时有效; only和except可以使用正则表达式; only和except允许使用特殊的关键字:branches,tags和triggers; job.environment environment用于定义job部署到指定的运行环境中。 environment.name:必选,指定environment名称 environment.url:可选,指定environment对应的URL,将在指定的environment页面中添加一个链接按钮指向该URL 特殊的YAML特性 Hidden keys(jobs) 如果想临时disable某个job,不必注释整个job定义的行,只需在job name前加一个.即可 123456.compile_centos: stage: build_centos tags: - centos script: - echo "##### build library" Anchors 锚点可用于在文件中复制或继承配置,一般与Hidden keys(jobs)提供的job模版搭配使用。 12345678910111213.job_template: &job_definition #job中定义一个anchor:job_definition image: ruby:2.1 services: - postgres - redistest1: <<: *job_definition #合并anchor:job_definition中的模版内容 script: - test1 projecttest2: <<: *job_definition script: - test2 project 最终实现的效果如下: 12345678910111213141516171819.job_template: image: ruby:2.1 services: - postgres - redistest1: image: ruby:2.1 services: - postgres - redis script: - test1 projecttest2: image: ruby:2.1 services: - postgres - redis script: - test2 project Skipping jobs 如果你的commit信息中包含[ci skip]或者[skip ci],不论大小写,那么这个commit将会创建但是jobs也会跳过。 example: 以下示例为编译nginx的上传模块nginx-upload并测试验证上传功能,验证成功后将自动将编译好的文件打包通过curl上传到指定的文件服务器。其中只有在非master的branches中提交代码才会执行build和test的stage,只有在打tag后才会执行deploy,且需要手动在gitlab上执行。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102variables: DIR: nginx TOPNODE: package.function: &function | function build() { echo "execute function:build" chmod +x auto/configure sh build.sh } function changelog() { echo "execute function:changelog" git log --graph -n 3 --name-status --pretty="%h -[%cd] - <%an> %s" > CHANGELOG } function test() { echo "execute function:test" sudo \cp modules/nginx-upload-module-master/nginx.conf /etc/nginx/nginx.conf sudo sed -i '/error_log/,/working_directory/d' /etc/nginx/nginx.conf if [ -f /run/nginx.pid ];then sudo nginx -s reload;else sudo nginx;fi sudo rm -rf /tmp/{0,1,2,3,4,5,6,7,8,9} && sudo mkdir /tmp/{0,1,2,3,4,5,6,7,8,9} && sudo chown -R nginx. /tmp/{0,1,2,3,4,5,6,7,8,9} sudo echo nginx_upload > test && curl -F "filename=@test" http://localhost/upload sudo find /tmp/{0,1,2,3,4,5,6,7,8,9} -type f -exec grep nginx_upload {} \; } function artifacts() { echo "execute function:artifacts" URL="https://xxx.com/upload?dir=${DIR}/${VERSION}&override=1&topNode=${TOPNODE}" echo "push the artifacts:nginx_${VERSION}.tar.gz to $URL" tar zcf /tmp/nginx_${VERSION}.tar.gz --exclude=".git*" --exclude=build . curl -F "filename=@/tmp/${DIR}_${VERSION}.tar.gz" "$URL" echo "push the CHANGELOG to $URL" curl -F "filename=@CHANGELOG" "$URL" } function deploy() { echo "execute function:deploy" } function clean() { echo "execute function:clean" if [ -f /run/nginx.pid ];then sudo kill `cat /run/nginx.pid`;fi sudo rm -rf /tmp/{0,1,2,3,4,5,6,7,8,9} /tmp/nginx_${version}.tar.gz }#########only the section above need to be modify #################before_script: - VERSION=`head -n1 version` - *functionstages: - build - test - deploybuild: stage: build only: - branches except: - master script: - build - changelogtest: stage: test variables: GIT_STRATEGY: none only: - branches except: - master script: - test - artifacts - clean.job_template: &deploy_template stage: deploy variables: GIT_STRATEGY: none only: - tags script: - deploy - delete when: manualstaging: <<: *deploy_template environment: name: stagingproduction: <<: *deploy_template environment: name: production 参考文档http://blog.51cto.com/vnimos/2122951]]></content>
<categories>
<category>Git笔记</category>
</categories>
<tags>
<tag>GitLab持续集成-(.gitlab-ci.yml)</tag>
</tags>
</entry>
<entry>
<title><![CDATA[GitLab-CI 与 GitLab-Runner]]></title>
<url>%2F2018%2F09%2F08%2FGitLab-CI%E4%B8%8EGitLab-Runner%2F</url>
<content type="text"><![CDATA[GitLab-CI 与 GitLab-Runner 持续集成(Continuous Integration)要了解 GitLab-CI 与 GitLab Runner,我们得先了解持续集成是什么。 持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。许多团队发现这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件。 看完这段话,估计还是有点懵。怎么理解呢?我是这样理解的: 软件集成 是软件开发过程中的一个环节,这个环节的工作一般会包括以下流程:合并代码 —-> 安装依赖 —-> 编译 —-> 测试 —-> 发布。软件集成的工作一般会比较细碎繁琐,为了不影响开发效率,以前软件集成这个环节一般不会经常进行或者只会等到项目后期再进行。但是有些问题,如果等到后期才发现,解决问题的代价很大,有可能导致项目延期或者失败。因此,为了尽早发现软件集成错误,鼓励团队成员应该经常集成他们的工作,通常每个成员每天应该至少集成一次。这就是所说的 持续集成。所以说,持续集成是一种软件开发实践。 软件集成的工作细碎繁琐,以前是由人工完成的。但是现在鼓励持续集成,那岂不是要累死人,还影响开发效率。所以,应该考虑将软件集成这个工作自动化,这就出现了所谓的 持续集成系统。 持续集成详情见百度百科-持续集成 GitLab-CIGitLab-CI 就是一套配合 GitLab 使用的持续集成系统(当然,还有其它的持续集成系统,同样可以配合 GitLab 使用,比如 Jenkins)。而且 GitLab8.0 以后的版本是默认集成了 GitLab-CI 并且默认启用的。 GitLab-Runner那 GitLab-Runner 又是什么?和 GitLab-CI 有什么关系? GitLab-Runner 是配合 GitLab-CI 进行使用的。一般地,GitLab 里面的每一个工程都会定义一个属于这个工程的软件集成脚本,用来自动化地完成一些软件集成工作。当这个工程的仓库代码发生变动时,比如有人 push 了代码,GitLab 就会将这个变动通知 GitLab-CI。这时 GitLab-CI 会找出与这个工程相关联的 Runner,并通知这些 Runner 把代码更新到本地并执行预定义好的执行脚本。 所以,GitLab-Runner 就是一个用来执行软件集成脚本的东西。可以想象一下:Runner 就像一个个的工人,而 GitLab-CI就是这些工人的一个管理中心,所有工人都要在 GitLab-CI 里面登记注册,并且表明自己是为哪个工程服务的。当相应的工程发生变化时,GitLab-CI 就会通知相应的工人执行软件集成脚本。如下图所示: GitLab-CI 与 GitLab-Runner 关系示意图 Runner 可以分布在不同的主机上,同一个主机上也可以有多个Runner。 Runner类型GitLab-Runner 可以分类两种类型:Shared Runner(共享型) 和 Specific Runner(指定型)。Shared Runner: 这种 Runner(工人)是所有工程都能够用的。只有系统管理员能够创建Shared Runner。Specific Runner: 这种 Runner(工人)只能为指定的工程服务。拥有该工程访问权限的人都能够为该工程创建 Shared Runner。 GitLab-Runner 的安装与使用操作系统:Centos 7.0 64位 安装 gitlab-ci-multi-runner 添加 yum 源 1curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.rpm.sh | sudo bash 安装 1yum -y install gitlab-ci-multi-runner 这里是官网的安装教程,其它操作系统的请参考 https://gitlab.com/gitlab-org/gitlab-ci-multi-runner 使用 gitlab-ci-multi-runner 注册 Runner安装好 gitlab-ci-multi-runner 这个软件之后,我们就可以用它向 GitLab-CI 注册 Runner 了。 向 GitLab-CI 注册一个 Runner 需要两样东西:GitLab-CI的url 和 注册token。其中,token 是为了确定你这个 Runner 是所有工程都能够使用的 Shared Runner 还是具体某一个工程才能使用的Specific Runner。 如果要注册 Shared Runner,你需要到管理界面的 Runners 页面里面去找注册 token。如下图所示: Shared Runner 如果要注册 Specific Runner,你需要到项目的设置的 Runner 页面里面去找注册 token。如下图所示: Specific Runner 找到 token 之后,运行下面这条命令注册 Runner(当然,除了 url 和 token 之外,还需要其他的信息,比如执行器 executor、构建目录 builds_dir 等)。 gitlab-ci-multi-runner register 注册完成之后,GitLab-CI 就会多出一条 Runner 记录,如下图所示: GitLab-CI Runner GitLab-CI 会为这个 Runner 生成一个唯一的 token,以后 Runner 就通过这个 token 与 GitLab-CI 进行通信。 那么,问题来了。注册好了的 Runner 的信息存放在哪儿了呢?原来,Runner 的信息是存放在一个配置文件里面的,配置文件的格式一般是 .toml。这个配置文件的存放位置有以下几种情况: 在类Unix操作系统下(0.5.0之后版本) 如果是以 root 用户身份运行 gitlab-ci-multi-runner register,那么配置文件默认是 /etc/gitlab-runner/config.toml 如果是以非 root 用户身份运行 gitlab-ci-multi-runner register,那么配置文件默认是 ~/.gitlab-runner/config.toml 在其他操作系统下以及 0.5.0 之前版本配置文件默认在当前工作目录下./config.toml 一般情况下,使用默认的配置文件存放 Runner 的配置信息就可以了。当然,如果你有更细化的分类需求,你也可以在注册的时候通过 -c 或 --config 选项指定配置文件的位置。具体查看register 命令的使用方法:gitlab-ci-multi-runner register --help。 问题: 如果不运行 gitlab-ci-multi-runner register 命令,直接在配置文件里面添加 Runner 的配置信息可以吗?回答: 当然不可以。因为 gitlab-ci-multi-runner register 的作用除了把 Runner 的信息保存到配置文件以外,还有一个很重要的作用,那就是向 GitLab-CI 发出请求,在 GitLab-CI 中登记这个 Runner 的信息并且获取后续通信所需要的 token。 让注册好的 Runner 运行起来Runner 注册完成之后还不行,还必须让它运行起来,否则它无法接收到 GitLab-CI 的通知并且执行软件集成脚本。怎么让 Runner 运行起来呢?gitlab-ci-multi-runner 提供了这样一条命令gitlab-ci-multi-runner run-single,详情如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445[root@localhost ~]# gitlab-ci-multi-runner run-single --helpNAME: run-single - start single runnerUSAGE: command run-single [command options] [arguments...]OPTIONS: --name, --description Runner name [$RUNNER_NAME] --limit Maximum number of builds processed by this runner [$RUNNER_LIMIT] --ouput-limit Maximum build trace size [$RUNNER_OUTPUT_LIMIT] -u, --url Runner URL [$CI_SERVER_URL] -t, --token Runner token [$CI_SERVER_TOKEN] --tls-ca-file File containing the certificates to verify the peer when using HTTPS [$CI_SERVER_TLS_CA_FILE] --executor Select executor, eg. shell, docker, etc. [$RUNNER_EXECUTOR] --builds-dir Directory where builds are stored [$RUNNER_BUILDS_DIR] --cache-dir Directory where build cache is stored [$RUNNER_CACHE_DIR] --env Custom environment variables injected to build environment [$RUNNER_ENV] --shell Select bash, cmd or powershell [$RUNNER_SHELL] --ssh-user User name [$SSH_USER] --ssh-password User password [$SSH_PASSWORD] --ssh-host Remote host [$SSH_HOST] --ssh-port Remote host port [$SSH_PORT] --ssh-identity-file Identity file to be used [$SSH_IDENTITY_FILE] --docker-host Docker daemon address [$DOCKER_HOST] --docker-cert-path Certificate path [$DOCKER_CERT_PATH] --docker-tlsverify Use TLS and verify the remote [$DOCKER_TLS_VERIFY] --docker-hostname Custom container hostname [$DOCKER_HOSTNAME] --docker-image Docker image to be used [$DOCKER_IMAGE] --docker-privileged Give extended privileges to container [$DOCKER_PRIVILEGED] --docker-disable-cache Disable all container caching [$DOCKER_DISABLE_CACHE] --docker-volumes Bind mount a volumes [$DOCKER_VOLUMES] --docker-cache-dir Directory where to store caches [$DOCKER_CACHE_DIR] --docker-extra-hosts Add a custom host-to-IP mapping [$DOCKER_EXTRA_HOSTS] --docker-links Add link to another container [$DOCKER_LINKS] --docker-services Add service that is started with container [$DOCKER_SERVICES] --docker-wait-for-services-timeout How long to wait for service startup [$DOCKER_WAIT_FOR_SERVICES_TIMEOUT] --docker-allowed-images Whitelist allowed images [$DOCKER_ALLOWED_IMAGES] --docker-allowed-services Whitelist allowed services [$DOCKER_ALLOWED_SERVICES] --docker-image-ttl [$DOCKER_IMAGE_TTL] --parallels-base-name VM name to be used [$PARALLELS_BASE_NAME] --parallels-template-name VM template to be created [$PARALLELS_TEMPLATE_NAME] --parallels-disable-snapshots Disable snapshoting to speedup VM creation [$PARALLELS_DISABLE_SNAPSHOTS] --virtualbox-base-name VM name to be used [$VIRTUALBOX_BASE_NAME] --virtualbox-disable-snapshots Disable snapshoting to speedup VM creation [$VIRTUALBOX_DISABLE_SNAPSHOTS] 要让一个 Runner 运行起来,--url、--token 和 --executor 选项是必要的。其他选项可根据具体情况和需求进行设置。我们可以看出来,这个命令里面的选项跟配置文件中 Runner 的配置项基本上是一样的。那这个命令的运行和配置文件有没有什么关系呢?从我的试验和思考来看,应该是没有什么关系的。因为: 这个命令里面并没有指定配置文件位置的选项,如果读取配置文件难道去读取默认位置吗?但是配置文件的位置是可以指定的,不一定在默认位置,这不符合逻辑,所以它应该不会去读配置文件。 我删掉配置文件,这个命令依然能够运行 所以,这个命令应该只是一个能让 Runner 运行起来的基础命令。但这个命令运行起来的前提是,GitLab-CI 中必须事先注册有这个 Runner。 那配置文件有毛用?配置文件的作用在后面,但是从这里我们知道一点:配置文件里面有 Runner运行时所需要的信息。 可能你还有一个问题:我用 root 的用户注册 Runner 时,注册完 Runner 就可以用了,并没有手动地去运行 Runner 啊?这个后面讲。 批量地运行 Runner正常情况下,如果我有多个 Runner,我并不想手动一个个地运行,要是能一次运行多个 Runner 多爽啊!嗯哼,gitlab-ci-multi-runner 就提供了这样一个命令 gitlab-ci-multi-runner run,详情如下: 12345678910111213[root@localhost gitlab-runner]# gitlab-ci-multi-runner run --helpNAME: run - run multi runner serviceUSAGE: command run [command options] [arguments...]OPTIONS: -c, --config "/etc/gitlab-runner/config.toml" Config file [$CONFIG_FILE] -n, --service "gitlab-runner" Use different names for different services -d, --working-directory Specify custom working directory -u, --user Use specific user to execute shell scripts --syslog Log to syslog 这个命令总共有 5 个选项,让我们从选项来理解一下这个命令: -c, --config 选项这个选项是用来指定配置文件路径的。如果你想同时运行多个 Runner,你必须得知道你要运行哪些 Runner 以及这些 Runner 运行时所需要的信息。而前面我们说过,配置文件里面就存放着 Runner 运行时所需要的信息。而且一个配置文件是可以存放多个 Runner 的信息的。如果不指定这个选项,就会使用默认的配置文件。 -n, --service 选项这个选项是用来指定服务的别名的。为什么要有这个选项呢?指定别名有什么意义呢?我们从上一个选项可以看出来,一次只能运行一批 Runner,因为一次只能指定一个配置文件。那如果我有多个配置文件,我要运行多批 Runner,那是不是给每一次批量运行服务取不同的别名来区分更好一点呢。 -d, --working-directory 选项这个选项是用来指定此次批量运行服务的工作目录的。如果自己没有指定 builds_dir 的话,此次运行起来的 Runner 会把 builds_dir 放到这个目录里面。 -u, --user 选项这个选项很重要,它指定了该以什么用户权限来运行 Runner。为了安全,我认为不应该给运行 Runner 的用户过高的权限,更不应该以 root 用户来运行 Runner。 --syslog 选项如果指定了这个选项,则把日志记录到系统日志。 使用服务能够批量地运行 Runner 已经很好了,但是还不够好,为什么呢? 首先,gitlab-ci-multi-runner run 默认是前台运行的,使用体验不好;其次,当 gitlab-ci-multi-runner run 在后台运行的时候,要查看其运行状态不方便,而且也没有提供停止 gitlab-ci-multi-runner run 的命令。 所以,要是能将批量运行Runner这个功能安装为一项服务,就更爽了! gitlab-ci-multi-runner 确实就提供了这样的功能。install、uninstall、start、stop、restart、status 这 6 个命令就是和服务相关的。我一开始对 gitlab-ci-multi-runner 的服务概念感觉比较懵,让我们来看看安装服务 install 这个命令到底干了一件什么事情。 123456789101112[root@localhost ~]# gitlab-ci-multi-runner install --helpNAME: install - install serviceUSAGE: command install [command options] [arguments...]OPTIONS: --service, -n "gitlab-runner" Specify service name to use --working-directory, -d "/root" Specify custom root directory where all data are stored --config, -c "/etc/gitlab-runner/config.toml" Specify custom config file --user, -u Specify user-name to secure the runner 从选项可以看出,一项服务的信息有 4 个:服务名、工作目录、配置文件和用户。这个命令的选项和 gitlab-ci-multi-runner run 的选项基本一样。可见,批量运行 Runner 和服务之间的关系暧昧。至于是什么关系,往下看 gitlab-ci-multi-runner start 这个命令。 123456789[root@localhost ~]# gitlab-ci-multi-runner start --helpNAME: start - start serviceUSAGE: command start [command options] [arguments...]OPTIONS: --service, -n "gitlab-runner" Specify service name to use 启动一项服务,只要指定服务的名称就行了(默认服务名称是 gitlab-runner)。启动服务后,运行命令 ps -aux | grep gitlab-runner 查看后台程序,发现启动服务其实就是在后台执行了一个批量运行Runner的任务,所以服务安装命令的选项才会和批量运行 Runner 命令的选项基本一样。 1root 18219 0.0 0.1 331872 5332 ? Ssl 00:06 0:00 /usr/bin/gitlab-ci-multi-runner run --working-directory /home/gitlab-runner --config /etc/gitlab-runner/config.toml --service gitlab-runner --user gitlab-runner --syslog 还有 stop 命令用于停止服务,restart 命令用于重启服务,status 用于查看服务状态。这三个命令的使用方法和 start 类似,就不一一介绍了。 其他一些思考 什么情况下需要注册 Shared Runner?比如,GitLab 上面所有的工程都有可能需要在公司的服务器上进行编译、测试、部署等工作,这个时候注册一个Shared Runner 供所有工程使用就很合适。 什么情况下需要注册 Specific Runner?比如,我可能需要在我个人的电脑或者服务器上自动构建我参与的某个工程,这个时候注册一个 Specific Runner 就很合适。 什么情况下需要在同一台机器上注册多个 Runner?比如,我是 GitLab 的普通用户,没有管理员权限,我同时参与多个项目,那我就需要为我的所有项目都注册一个 Specific Runner,这个时候就需要在同一台机器上注册多个 Runner。 参考文档https://www.jianshu.com/p/2b43151fb92e]]></content>
<categories>
<category>Git笔记</category>
</categories>
<tags>
<tag>GitLab-CI 与 GitLab-Runner</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack 数据系统 Pillar]]></title>
<url>%2F2018%2F09%2F06%2FSaltStack%E6%95%B0%E6%8D%AE%E7%B3%BB%E7%BB%9FPillar%2F</url>
<content type="text"><![CDATA[SaltStack 数据系统 Pillar 名称 存储位置 数据类型 数据采集更新方式 应用 Grains Minion端 静态数据 Minion启动时收集,也可以使用 saltutil.sync_grains 进行刷新 存储 Minion 基本数据,比如用于匹配 Minion,自身数据可以用来作资产管理等。 Pillar Master端 动态数据 在Master端定义,指定给对应的Minion。可以使用 saltutil.refresh_pillar 刷新 存储 Master 指定的数据,只有指定的 Minion 可以看到。用于敏感数据保存。 Pillar 是在 Master端 设置的Pillar 给 Minion端 指定它想要的数据安全性比较高 查看 Pillar 信息默认是 False 状态12345[root@master01 ~]# salt '*' pillar.itemsdbfw01: ----------minion01: ---------- 开启查看 Pillar 状态,一般不用打开 1234567891011[root@master01 ~]# cd /etc/salt/[root@master01 ~]# vim /etc/salt/master...省略...#pillar_opts: False改成pillar_opts: True...省略...:wq保存退出# 重启 salt-master 服务systemctl restart salt-master 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342[root@master01 salt]# salt 'minion01' pillar.itemsminion01: ---------- master: ---------- __role: master auth_mode: 1 auto_accept: False cache_sreqs: True cachedir: /var/cache/salt/master cli_summary: False client_acl: ---------- client_acl_blacklist: ---------- cluster_masters: cluster_mode: paranoid con_cache: False conf_file: /etc/salt/master config_dir: /etc/salt cython_enable: False daemon: False default_include: master.d/*.conf enable_gpu_grains: False enforce_mine_cache: False enumerate_proxy_minions: False environment: None event_return: event_return_blacklist: event_return_queue: 0 event_return_whitelist: ext_job_cache: ext_pillar: extension_modules: /var/cache/salt/extmods external_auth: ---------- failhard: False file_buffer_size: 1048576 file_client: local file_ignore_glob: None file_ignore_regex: None file_recv: False file_recv_max_size: 100 file_roots: ---------- base: - /srv/salt fileserver_backend: - roots fileserver_followsymlinks: True fileserver_ignoresymlinks: False fileserver_limit_traversal: False gather_job_timeout: 10 gitfs_base: master gitfs_env_blacklist: gitfs_env_whitelist: gitfs_insecure_auth: False gitfs_mountpoint: gitfs_passphrase: gitfs_password: gitfs_privkey: gitfs_pubkey: gitfs_remotes: gitfs_root: gitfs_user: hash_type: md5 hgfs_base: default hgfs_branch_method: branches hgfs_env_blacklist: hgfs_env_whitelist: hgfs_mountpoint: hgfs_remotes: hgfs_root: id: minion01 interface: 0.0.0.0 ioflo_console_logdir: ioflo_period: 0.01 ioflo_realtime: True ioflo_verbose: 0 ipv6: False jinja_lstrip_blocks: False jinja_trim_blocks: False job_cache: True keep_jobs: 24 key_logfile: /var/log/salt/key keysize: 2048 log_datefmt: %H:%M:%S log_datefmt_logfile: %Y-%m-%d %H:%M:%S log_file: /var/log/salt/master log_fmt_console: [%(levelname)-8s] %(message)s log_fmt_logfile: %(asctime)s,%(msecs)03.0f [%(name)-17s][%(levelname)-8s][%(process)d] %(message)s log_granular_levels: ---------- log_level: warning loop_interval: 60 maintenance_floscript: /usr/lib/python2.7/site-packages/salt/daemons/flo/maint.flo master_floscript: /usr/lib/python2.7/site-packages/salt/daemons/flo/master.flo master_job_cache: local_cache master_pubkey_signature: master_pubkey_signature master_roots: ---------- base: - /srv/salt-master master_sign_key_name: master_sign master_sign_pubkey: False master_tops: ---------- master_use_pubkey_signature: False max_event_size: 1048576 max_minions: 0 max_open_files: 100000 minion_data_cache: True minionfs_blacklist: minionfs_env: base minionfs_mountpoint: minionfs_whitelist: nodegroups: ---------- open_mode: False order_masters: False outputter_dirs: peer: ---------- permissive_pki_access: False pidfile: /var/run/salt-master.pid pillar_opts: True pillar_roots: ---------- base: - /srv/pillar pillar_safe_render_error: True pillar_source_merging_strategy: smart pillar_version: 2 pillarenv: None ping_on_rotate: False pki_dir: /etc/salt/pki/master preserve_minion_cache: False pub_hwm: 1000 publish_port: 4505 publish_session: 86400 queue_dirs: raet_alt_port: 4511 raet_clear_remotes: False raet_main: True raet_mutable: False raet_port: 4506 range_server: range:80 reactor: reactor_refresh_interval: 60 reactor_worker_hwm: 10000 reactor_worker_threads: 10 renderer: yaml_jinja ret_port: 4506 root_dir: / rotate_aes_key: True runner_dirs: saltversion: 2015.5.10 search: search_index_interval: 3600 serial: msgpack show_jid: False show_timeout: True sign_pub_messages: False sock_dir: /var/run/salt/master sqlite_queue_dir: /var/cache/salt/master/queues ssh_passwd: ssh_port: 22 ssh_scan_ports: 22 ssh_scan_timeout: 0.01 ssh_sudo: False ssh_timeout: 60 ssh_user: root state_aggregate: False state_auto_order: True state_events: False state_output: full state_top: salt://top.sls state_top_saltenv: None state_verbose: True sudo_acl: False svnfs_branches: branches svnfs_env_blacklist: svnfs_env_whitelist: svnfs_mountpoint: svnfs_remotes: svnfs_root: svnfs_tags: tags svnfs_trunk: trunk syndic_dir: /var/cache/salt/master/syndics syndic_event_forward_timeout: 0.5 syndic_jid_forward_cache_hwm: 100 syndic_master: syndic_max_event_process_time: 0.5 syndic_wait: 5 timeout: 5 token_dir: /var/cache/salt/master/tokens token_expire: 43200 transport: zeromq user: root verify_env: True win_gitrepos: - https://github.com/saltstack/salt-winrepo.git win_repo: /srv/salt/win/repo win_repo_mastercachefile: /srv/salt/win/repo/winrepo.p worker_floscript: /usr/lib/python2.7/site-packages/salt/daemons/flo/worker.flo worker_threads: 5 zmq_filtering: False Pillar 应用场景用于敏感数据,给某一个配置文件设置一个密码,这个密码只希望指定 minion 能看到使用 Pillar 来处理变量差异性 定义 Pillar 数据 打开 pillar_roots 配置12345678910[root@master01 ~]# vim /etc/salt/master...省略...pillar_roots: base: - /srv/pillar...省略...:wq 保存退出# 重启 salt-master 服务systemctl restart salt-master 注意:base 环境是必须有的基础环境,和 topfile 里是对应的可以配置多个环境存放在 /srv/pillar 目录下Pillar 也有 topfile,可以指定哪个 Minion 能看到哪一个topfile 必须放在 base 环境下 123456789[root@master01 ~]# mkdir -pv /srv/pillar[root@master01 ~]# cd /srv/pillar/[root@master01 pillar]# vim /srv/pillar/apache.sls{% if grains['os'] == 'CentOS' %}apache: httpd{% elif grains['os'] == 'Debian' %}apache: apache2{% endif %}:wq 保存退出 将 apache.sls 指定哪个 Minion 能看到12345[root@master01 ~]# cd /srv/pillar/[root@master01 pillar]# vim /srv/pillar/top.slsbase: '*': - apache 让所有机器都能看到这个 apache.sls 的 Pillar 123456789[root@master01 pillar]# salt '*' pillar.itemsdbfw01: ---------- apache: httpdminion01: ---------- apache: httpd 获取到了 apache 的 pillar,值都是 httpd,因为都是 CentOS 的机器,如果是 Ubuntu 的机器就会显示 apache2 Pillar 主要用于做配置管理,用来定义一些变量和参数,比如:主机名,IP Pillar 定位主机 -I, --pillar 表示使用 Pillar 来匹配设置完 Pillar 后,需要刷新 Pillar 数据信息,使其生效salt '*' saltutil.refresh_pillar 1234567891011[root@master01 pillar]# salt '*' saltutil.refresh_pillardbfw01: Trueminion01: True[root@master01 pillar]# salt -I 'apache:httpd' test.pingdbfw01: Trueminion01: True]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack 数据系统 Pillar</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack states sls 状态管理]]></title>
<url>%2F2018%2F09%2F06%2FSaltStack-states-sls%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%2F</url>
<content type="text"><![CDATA[SaltStack states sls 状态管理 开启 file_roots 状态管理123456789101112131415161718192021222324[root@master01 ~]# cd /etc/salt/[root@master01 salt]# vim /etc/salt/master...省略...# file_roots:# base:# - /srv/salt/# dev:# - /srv/salt/dev/services# - /srv/salt/dev/states# prod:# - /srv/salt/prod/services# - /srv/salt/prod/states#file_roots: # 打开这段注释 base: - /srv/salt...省略...:wq保存退出# 创建 mkdir -pv /srv/salt 目录[root@master01 ~]# mkdir -pv /srv/salt# 重启 Master端服务systemctl restart salt-master 默认必须有一个 base 环境可以指定多个环境:基准环境(base),开发环境(dev),生产环境(prod) 编辑 sls 状态配置123456789101112[root@master01 ~]# cd /srv/salt/[root@master01 salt]# vim /srv/salt/nginx.slsnginx-install: # 自定义名称 pkg.installed: # 模块.方法 - names: - nginxnginx-servcice: # 自定义名称 service.running: # 模块.方法 - name: nginx # nginx 服务名 - enable: True # 开机自启 - reload: True # 开启重新加载 执行salt '*' 模块.方法 sls文件1[root@master01 ~]# salt '*' state.sls nginx salt ‘*’ 意思是在所有机器上执行 nginx 这个状态(sls) 如果没有指定 -name: nginx 服务名,会报 Result 失败命名服务 nginx-service 不可用,因为会把 nginx-service 当成服务名另一个办法是把 nginx-service 改成 nginx,-name: nginx 就不用写了12345678---------- ID: nginx-service Function: service.running Result: False Comment: The named service nginx-service is not available Started: 01:38:22.609850 Duration: 108.528 ms Changes: salt ‘minion01’ 指定在 minion01 机器上执行 nginx 这个状态(sls)12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970[root@master01 ~]# salt 'minion01' state.sls nginxminion01:---------- ID: nginx-install Function: pkg.installed Name: nginx Result: True Comment: The following packages were installed/updated: nginx Started: 01:40:29.448702 Duration: 8950.813 ms Changes: ---------- nginx: ---------- new: 1:1.12.2-2.el7 old: nginx-all-modules: ---------- new: 1:1.12.2-2.el7 old: nginx-mod-http-geoip: ---------- new: 1:1.12.2-2.el7 old: nginx-mod-http-image-filter: ---------- new: 1:1.12.2-2.el7 old: nginx-mod-http-perl: ---------- new: 1:1.12.2-2.el7 old: nginx-mod-http-xslt-filter: ---------- new: 1:1.12.2-2.el7 old: nginx-mod-mail: ---------- new: 1:1.12.2-2.el7 old: nginx-mod-stream: ---------- new: 1:1.12.2-2.el7 old:---------- ID: nginx Function: service.running Result: True Comment: Service nginx has been enabled, and is running Started: 01:40:38.412396 Duration: 732.741 ms Changes: ---------- nginx: TrueSummary------------Succeeded: 2 (changed=2)Failed: 0------------Total states run: 2 在 Minion 端查看 12[root@minion01 ~]# watch 'ps -ef | grep yum'[root@minion01 ~]# lsof -i:80 一台机器可能不止一种状态(sls)配置,可能有多个状态(sls)配置可以使用高级状态(sls)配置写一个入口文件 topfile一般入口文件名为 top.sls1234[root@master01 ~]# vim /srv/salt/top.slsbase: # 在 base 下 '*': # 在 '*' 所有主机上 - nginx # 执行 nginx 这个 sls 1234[root@master01 ~]# vim /srv/salt/top.slsbase: # 在 base 下 'minion01': # 在 minion01 主机上 - nginx # 执行 nginx 这个 sls 执行salt '*' 模块.高级状态 1[root@master01 ~]# salt '*' state.highstate 从入口文件开始读,从 topfile 开始读topfile 里包含了哪些普通状态(sls),就执行哪些状态(sls)远程执行,状态管理]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack states sls 状态管理</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack 数据系统 Grains]]></title>
<url>%2F2018%2F09%2F06%2FSaltStack%E6%95%B0%E6%8D%AE%E7%B3%BB%E7%BB%9FGrains%2F</url>
<content type="text"><![CDATA[SaltStack 数据系统 Grains 名称 存储位置 数据类型 数据采集更新方式 应用 Grains Minion端 静态数据 Minion启动时收集,也可以使用 saltutil.sync_grains 进行刷新 存储 Minion 基本数据,比如用于匹配 Minion,自身数据可以用来作资产管理等。 Pillar Master端 动态数据 在Master端定义,指定给对应的Minion。可以使用 saltutil.refresh_pillar 刷新 存储 Master 指定的数据,只有指定的 Minion 可以看到。用于敏感数据保存。 Grains 存放 minion 启动时收集的系统信息Grains 是存储在 minion 端的只有在 minion 启动时才会进行收集,收集完之后就不变了,等下一次 minion 重启,才会变Grains 收集的数据信息称之为静态数据,重启才会收集,不重启就不变 Grains 在做配置部署的过程中会经常使用到它,Grains 是 SaltStack 记录 Minion 的一些静态信息的组件,我们可以简单地理解为 Grains 里面记录着每台 Minion 的一些常用属性,比如 CPU、内存、磁盘、网络信息等,我们可以通过 granis.items 查看某台 Minion 的所有 Grains 信息,Minion 的 Grains 信息是 Minion 启动的时候采集汇报给 Master 的,在实际应用环境中我们需要根据自己的业务需求去自定义一些 Grains,关于自定义 Grains 的常用方法有以下几种: 通过 Minion 配置文件定义 通过 Grains 相关模块定义 通过 Python 脚本定义 查看 Grains 方法列表1234567891011121314[root@master01 ~]# salt 'minion*' sys.list_functions grainsminion01: - grains.append - grains.delval - grains.filter_by - grains.get - grains.get_or_set_hash - grains.has_value - grains.item - grains.items - grains.ls - grains.remove - grains.setval - grains.setvals 详细用法与例子可以通过以下命令查看 123456789101112131415161718192021222324[root@master01 ~]# salt 'minion*' sys.doc grains'grains.append:' New in version 0.17.0 Append a value to a list in the grains config file. If the grain doesn't exist, the grain key is added and the value is appended to the new grain as a list item. key The grain key to be appended to val The value to append to the grain key...省略...'grains.setvals:' Set new grains values in the grains config file :param Destructive: If an operation results in a key being removed, delete the key, too. Defaults to False. CLI Example: salt '*' grains.setvals "{'key1': 'val1', 'key2': 'val2'}" 通过 sys.doc 查看 grains模块.items方法 的使用方法 123456789101112[root@master01 ~]# salt 'minion*' sys.doc grains.items'grains.items:' Return all of the minion's grains CLI Example: salt '*' grains.items Sanitized CLI Example: salt '*' grains.items sanitize=True Grains 应用场景信息收集列出 Grains 所有的 key123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960[root@master01 ~]# salt 'minion*' grains.lsminion01: - SSDs - biosreleasedate - biosversion - cpu_flags - cpu_model - cpuarch - domain - fqdn - fqdn_ip4 - fqdn_ip6 - gpus - host - hwaddr_interfaces - id - init - ip4_interfaces - ip6_interfaces - ip_interfaces - ipv4 - ipv6 - kernel - kernelrelease - locale_info - localhost - lsb_distrib_id - machine_id - manufacturer - master - mdadm - mem_total - nodename - num_cpus - num_gpus - os - os_family - osarch - oscodename - osfinger - osfullname - osmajorrelease - osrelease - osrelease_info - path - productname - ps - pythonexecutable - pythonpath - pythonversion - saltpath - saltversion - saltversioninfo - selinux - serialnumber - server_id - shell - systemd - virtual - zmqversion 显示 grains 所有内容信息123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195[root@master01 ~]# salt 'minion*' grains.itemsminion01: ---------- SSDs: biosreleasedate: 04/01/2014 biosversion: 1.11.0-2.el7 cpu_flags: - fpu - de - pse - tsc - msr - pae - mce - cx8 - apic - sep - mtrr - pge - mca - cmov - pse36 - clflush - mmx - fxsr - sse - sse2 - ht - syscall - nx - lm - rep_good - nopl - pni - cx16 - x2apic - hypervisor - lahf_lm cpu_model: QEMU Virtual CPU version 2.5+ cpuarch: x86_64 domain: fqdn: minion01 fqdn_ip4: - 192.168.50.208 fqdn_ip6: gpus: |_ ---------- model: GD 5446 vendor: unknown host: minion01 hwaddr_interfaces: ---------- eth0: fa:57:ad:54:77:00 lo: 00:00:00:00:00:00 id: minion01 init: systemd ip4_interfaces: ---------- eth0: - 192.168.50.171 lo: - 127.0.0.1 ip6_interfaces: ---------- eth0: lo: ip_interfaces: ---------- eth0: - 192.168.50.171 lo: - 127.0.0.1 ipv4: - 127.0.0.1 - 192.168.50.171 ipv6: kernel: Linux kernelrelease: 3.10.0-862.el7.x86_64 locale_info: ---------- defaultencoding: UTF-8 defaultlanguage: en_US detectedencoding: UTF-8 localhost: minion01 lsb_distrib_id: CentOS Linux machine_id: 2e1eda950cd34bdd9dc04c8df9294ae0 manufacturer: Red Hat master: 192.168.35.223 mdadm: mem_total: 3772 nodename: minion01 num_cpus: 2 num_gpus: 1 os: CentOS os_family: RedHat osarch: x86_64 oscodename: Core osfinger: CentOS Linux-7 osfullname: CentOS Linux osmajorrelease: 7 osrelease: 7.5.1804 osrelease_info: - 7 - 5 - 1804 path: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin productname: KVM ps: ps -efH pythonexecutable: /usr/bin/python pythonpath: - /usr/bin - /usr/lib64/python27.zip - /usr/lib64/python2.7 - /usr/lib64/python2.7/plat-linux2 - /usr/lib64/python2.7/lib-tk - /usr/lib64/python2.7/lib-old - /usr/lib64/python2.7/lib-dynload - /usr/lib64/python2.7/site-packages - /usr/lib/python2.7/site-packages pythonversion: - 2 - 7 - 5 - final - 0 saltpath: /usr/lib/python2.7/site-packages/salt saltversion: 2015.5.10 saltversioninfo: - 2015 - 5 - 10 - 0 selinux: ---------- enabled: False enforced: Disabled serialnumber: Not Specified server_id: 1223839238 shell: /bin/sh systemd: ---------- features: +PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 -SECCOMP +BLKID +ELFUTILS +KMOD +IDN version: 219 virtual: kvm zmqversion: 3.2.5 显示某个信息内容 显示 fqdn 信息 12345[root@master01 ~]# salt 'minion*' grains.item fqdnminion01: ---------- fqdn: minion01 123[root@master01 ~]# salt 'minion*' grains.get fqdnminion01: minion01 注意:item 和 get 显示的效果不同 查询 eth0 网卡信息123[root@master01 ~]# salt 'minion*' grains.get ip_interfaces:eth0minion01: - 192.168.50.171 在 远程执行里 匹配 Minion 在所有 CentOS 的机器上执行命令 123456789[root@master01 ~]# salt 'minion*' grains.item osminion01: ---------- os: CentOS[root@master01 ~]# salt 'minion*' grains.get osminion01: CentOS 12345[root@master01 ~]# salt '*' grains.get osfingerdbfw01: CentOS-6minion01: CentOS Linux-7 -G: 表示使用 Grains 来匹配1234[root@master01 ~]# salt -G os:CentOS cmd.run 'w'minion01: 23:59:26 up 27 days, 3:00, 0 users, load average: 0.01, 0.03, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT 1234[root@master01 ~]# salt -G osfinger:'CentOS Linux-7' cmd.run 'w'minion01: 00:43:03 up 27 days, 3:44, 0 users, load average: 0.00, 0.01, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT 使用 Grains 可以来匹配主机 自定义 Grains 当内置 Grains 满足不了需求时,可以自定义 Grains。SaltStack 支持在 Minion 端通过配置文件的方式来自定义 Grains 通过 Minion 配置文件定义 Grains 在 minion 端,编辑 /etc/salt/minion打开 grains: roles: 添加一行自定义 Grains 内容 1234> grains:> roles:> - minion01-server> 123456789101112[root@master01 ~]# vim /etc/salt/minion...省略...grains: roles: # 角色 - minion-server # 配置一个角色 - webserver # 可以配置多个 - memcache...省略...:wq保存退出# 重启 salt-minion[root@master01 ~]# systemctl restart salt-minion 在 Master 端,在 roles 是 minion-server 的机器上,执行 echo hello minion01 123[root@master01 ~]# salt -G 'roles:minion-server' cmd.run 'echo hello minion01'minion01: hello minion01 当配置 /etc/salt/minion 主配置文件,配置信息过多的情况下,可读性就会变差我们可以另做一个配置 /etc/salt/grains 文件,专门来配置自定义 Grains123456[root@minion01 salt]# vim /etc/salt/grainsroles: - minion-server - minion-java - minion-springboot - minion-tomcat Salt 默认会读取这个文件 推荐使用这种方式 /etc/salt/minion.d/grains.conf123456789101112[root@minion01 salt]# vim /etc/salt/minion.d/grains.confgrains: Server_business: Java, Web Site Soft: - Tomcat - Spring Boot roles: - minion-server restful: - minion-springboot web: - minion-tomcat 在 master 端执行1234567891011[root@master01 ~]# salt '*' grains.item rolesdbfw01: ---------- roles:minion01: ---------- roles: - minion-server - minion-java - minion-springboot - minion-tomcat 12345678910111213141516[root@master01 hosts]# salt 'minion01' grains.item rolesminion01: ---------- roles: - minion-server[root@master01 hosts]# salt 'minion01' grains.item Softminion01: ---------- Soft: - Tomcat - Spring Boot[root@master01 hosts]# salt 'minion01' grains.item Server_businessminion01: ---------- Server_business: Java, Web Site 注意:如果 minion 配置文件开着 roles,这里的 roles 名字就不能开着,否则会冲突我们可以换个名字12345[root@minion01 salt]# vim /etc/salt/grainsrestful: - minion-springbootweb: - minion-tomcat 在 master 端执行1234567891011121314151617181920212223242526272829303132[root@master01 ~]# salt '*' grains.item rolesdbfw01: ---------- roles:minion01: ---------- roles: - minion-server[root@master01 ~]# salt '*' grains.item restfuldbfw01: ---------- restful:minion01: ---------- restful: - minion-springboot[root@master01 ~]# salt '*' grains.item webdbfw01: ---------- web:minion01: ---------- web: - minion-tomcat[root@master01 ~]# salt -G web:minion-tomcat cmd.run 'w'minion01: 01:22:54 up 27 days, 4:23, 1 user, load average: 0.00, 0.02, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT luming pts/0 192.168.35.219 00:50 1:18 0.10s 0.00s sshd: luming [priv] 在 topfile 里匹配 Minion12345678910[root@master01 salt]# vim nginx.slsnginx-install: pkg.installed: - names: - nginxnginx: service.running: - enable: True - reload: True 123456789[root@master01 ~]# vim /srv/salt/top.slsbase: 'roles:minion-server': # 'restful:minion-springboot': # 'web:minion-tomcat': - match: grain - nginx[root@master01 salt]# salt '*' state.highstate]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack 数据系统 Grains</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack 基本架构]]></title>
<url>%2F2018%2F09%2F04%2FSaltStack%E5%9F%BA%E6%9C%AC%E6%9E%B6%E6%9E%84%2F</url>
<content type="text"><![CDATA[SaltStack 基本架构 SaltStack 是一种基于 C/S 架构的服务模式。在 SaltStack 架构中服务端叫作 Master,客户端叫作 Minion。客户端发送请求给服务端,服务端接收到请求并且处理完成后再返回给客户端。 在 Master 和 Minion 端都是以守护进程的模式运行,一直监听配置文件里面定义的 ret_port(接受 minion 请求)和 publish_port(发布消息)的端口。当 Minion 运行时会自动连接到配置文件里面定义的 Master 地址 ret_port 端口进行连接认证。默认客户端请求 id 是 socket.getfqdn() 取到的值,也可以在 Minion 启动之前修改 Minion 的 id 值。关于整个启动通信过程,可以使用 debug 查看详细记录。 Master端12salt-master -l debugss -a | egrep '4505|4506' Minion端1salt-minion -l debug 4505(publish_port) 为 salt 的消息发布专用端口为 Salt Master pub 接口,提供远程执行命令发送功能 4506(ret_port) 为客户端与服务端通信的端口。监控此端口可以监控哪些 minion 收到的消息,并回复了结果为 Salt Master Ret 接口,支持认证(auth),文件服务,结果收集等功能要确保 客户端 能跟 服务端 的这 2 个端口通信,需要保证防火墙对于这两个端口是放开的 Salt 架构中最主要的角色是 Salt master 和 Salt minion另外一种角色是 syndic顾名思义master 是中心控制系统minion 是被管理的客户端 Salt 部署架构可以分为三种第一种:master -> minion这种架构中 master 和所有 minion 都直接连接,minion 接收来自 master 的指令,完成命令执行或配置管理 第二种:master -> syndic -> minion这种架构中 master 通过 syndic 对 minion 进行管理,同时该架构可以进行多级扩展 第三种:无 master 的 minionMasterless 架构,就不需要单独安装一台 SaltStack Master 机器,只需要在每台机器上安装 Minion,然后采用本机只负责对本机的配置管理工作机制服务模式。这种架构中 minion 不受任何 master 控制,通过本地运行即可完成相关功能 Salt 的两个主要设计理念是 远程执行 和 配置管理。Salt 中的 配置管理系统 可以称作 state,也是基于远程执行系统之上,通过 master 的定义可以让对应的 minion 达到想要的系统状态。 SaltStack 软件依赖SaltStack 有两种消息系统,一种是 RAET,另一种是 ZeroMQ,默认使用 ZeroMQ。 组件名 注释 msgpack-python SaltStack 消息交换库 YAML SaltStack 配置解析定义语法 Jinja2 SaltStack states 配置模板 MarkupSafe Python unicode 转换库 apache-libcloud SaltStack 对云架构编排库 Requests HTTP Python 库 ZeroMQ SaltStack 消息系统 pyzmq ZeroMQ Python 库 PyCrypto Python 密码库 M2Crypto Openssl Python 包装库]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack 基本架构</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack 认证配置]]></title>
<url>%2F2018%2F09%2F04%2FSaltStack%E8%AE%A4%E8%AF%81%E9%85%8D%E7%BD%AE%2F</url>
<content type="text"><![CDATA[SaltStack 认证配置 Salt 证书管理 SaltStack 使用 SSL 签发证书的方式进行安全认证。在签发证书之前,可以在 Master 端看到 Minion 的证书签证请求。Salt 和 minion 之间的认证关系是通过 salt-key 在 master 端管理的,生成证书记录在 /etc/salt/pki/master/ 目录下 salt-minion 启动后生成秘钥文件1234[root@minion01 ~]# cd /etc/salt/pki/minion/[root@minion01 minion]# ll -h-r-------- 1 root root 1.7K Sep 4 16:19 minion.pem # 私钥文件-rw-r--r-- 1 root root 451 Sep 4 16:19 minion.pub # 公钥文件 然后 salt-minion 会把公钥发送给 salt-master salt-master 启动后生成秘钥文件12345678910[root@master01 ~]# cd /etc/salt/pki/master/[root@master01 master]# ll -htotal 8.0K-r-------- 1 root root 1.7K Aug 27 09:03 master.pem # 私钥文件-rw-r--r-- 1 root root 451 Aug 27 09:03 master.pub # 公钥文件drwxr-xr-x 2 root root 23 Sep 5 15:59 minionsdrwxr-xr-x 2 root root 6 Aug 27 09:03 minions_autosigndrwxr-xr-x 2 root root 6 Aug 27 09:03 minions_denieddrwxr-xr-x 2 root root 6 Sep 5 15:49 minions_predrwxr-xr-x 2 root root 6 Aug 27 09:03 minions_rejected 授权前,在获取到 minion端后,公钥文件会存放在 minions_pre 目录下 12345678910[root@master01 master]# tree.├── master.pem├── master.pub├── minions├── minions_autosign├── minions_denied├── minions_pre│ └── minion01 # minion端的公钥文件└── minions_rejected 授权后,minion的公钥文件会被存放在 minions 目录下 12345678910[root@master01 master]# tree.├── master.pem├── master.pub├── minions│ └── minion01 # minion端的公钥文件├── minions_autosign├── minions_denied├── minions_pre└── minions_rejected 123456789101112131415161718[root@master01 master]# cd minions/[root@master01 minions]# ll -h-rw-r--r-- 1 root root 451 Sep 5 15:46 minion01[root@master01 minions]# file minion01minion01: ASCII text[root@master01 minions]# cat minion01-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0DP70JjbCaGB47qsTxn9I0M46rRv5HEUcEyaRBRehA+DvuZjjV1ud63WxrvMcRXq/M86EURVyjFDcNaZywi3SO2K70J+wWxD0HDCV0k8SFR/i83n7nPUVmy+PLlSO+ADl8aWogwUw2wZB4JxZ+OSgWfwvfFZ41yxIlS+Eteb5SkQtRp/OfZrVyEgY6xp4biURsKzP9kFNvOJHW27fcQ9/XI4JmX2aKYk4LJRVvudkkLzYBW1NeYRv0kMAVGpMm8SiAkIwi+7CJnEWorqAmc0TNLGhtDyhV51cbG5Wbt/vbg6N/NX5BOmErXibEsh7679Wph0mJ1fq5jKjn1fYu/JrwIDAQAB-----END PUBLIC KEY----- minion 端也会保存一份 master的公钥 123456[root@minion01 ~]# cd /etc/salt/pki/minion/[root@minion01 minion]# ll -htotal 12K-rw-r--r-- 1 root root 451 Sep 4 16:19 minion_master.pub # master端的公钥文件-r-------- 1 root root 1.7K Sep 4 16:19 minion.pem-rw-r--r-- 1 root root 451 Sep 4 16:19 minion.pub salt-key 命令和参数123格式:salt-key 参数 [minion端ID(可以是IP,也可以是主机名) [-y]-h, --help # 帮助-y # 该参数可免去证书操作的交互(除非对 minion 端很信任,一般不建议使用) 显示所有 public keys12345678910111213-l ARG, --list=ARG # 显示指定状态的 key(支持正则表达式)-L, --list-all # 列出当前所有认证,包括Accepted Keys、Denied Keys、Unaccepted Keys、Rejected KeysAccepted Keys: # 已经接受的keyDenied Keys: # 未被允许的keyUnaccepted Keys: # 未被接受的keyRejected Keys: # 被拒绝的key[root@master01 ~]# salt-key -LAccepted Keys:Denied Keys:Unaccepted Keys:minion01Rejected Keys: 接受等待认证的 key1234567891011121314151617181920212223242526-a ACCEPT, --accept=ACCEPT # 接受指定等待认证的key(支持正则表达式) # 添加某个或某些个未接受(Unaccepted Keys)认证-A, --accept-all # 接受所有等待认证的key(Unaccepted Keys)下所有的minion # 添加所有未接受(Unaccepted Keys)认证[root@master01 ~]# salt-key -a minion01The following keys are going to be accepted:Unaccepted Keys:minion01Proceed? [n/Y] yKey for minion minion01 accepted.#################################################################################################[root@master01 ~]# salt-key -A -yThe following keys are going to be accepted:Unaccepted Keys:minion01Key for minion minion01 accepted.#################################################################################################[root@master01 ~]# salt-key -LAccepted Keys:minion01Denied Keys:Unaccepted Keys:Rejected Keys: 拒绝等待认证的 key123-r REJECT, --reject=REJECT # 拒绝指定等待认证的key(支持正则表达式)-R, --reject-all # 拒绝所有等待认证的key--include-all # 显示所有状态的key(包含non-pending状态) 打印 public key1234567891011121314151617181920212223242526-p PRINT, --print=PRINT # 打印指定的 public key(支持正则表达式)-P, --print-all # 打印所有的 public key[root@master01 ~]# salt-key -p "minion01"Unaccepted Keys:minion01: -----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0DP70JjbCaGB47qsTxn9I0M46rRv5HEUcEyaRBRehA+DvuZjjV1ud63WxrvMcRXq/M86EURVyjFDcNaZywi3SO2K70J+wWxD0HDCV0k8SFR/i83n7nPUVmy+PLlSO+ADl8aWogwUw2wZB4JxZ+OSgWfwvfFZ41yxIlS+Eteb5SkQtRp/OfZrVyEgY6xp4biURsKzP9kFNvOJHW27fcQ9/XI4JmX2aKYk4LJRVvudkkLzYBW1NeYRv0kMAVGpMm8SiAkIwi+7CJnEWorqAmc0TNLGhtDyhV51cbG5Wbt/vbg6N/NX5BOmErXibEsh7679Wph0mJ1fq5jKjn1fYu/JrwIDAQAB-----END PUBLIC KEY-----[root@master01 ~]# salt-key -PUnaccepted Keys:minion01: -----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0DP70JjbCaGB47qsTxn9I0M46rRv5HEUcEyaRBRehA+DvuZjjV1ud63WxrvMcRXq/M86EURVyjFDcNaZywi3SO2K70J+wWxD0HDCV0k8SFR/i83n7nPUVmy+PLlSO+ADl8aWogwUw2wZB4JxZ+OSgWfwvfFZ41yxIlS+Eteb5SkQtRp/OfZrVyEgY6xp4biURsKzP9kFNvOJHW27fcQ9/XI4JmX2aKYk4LJRVvudkkLzYBW1NeYRv0kMAVGpMm8SiAkIwi+7CJnEWorqAmc0TNLGhtDyhV51cbG5Wbt/vbg6N/NX5BOmErXibEsh7679Wph0mJ1fq5jKjn1fYu/JrwIDAQAB-----END PUBLIC KEY----- 删除已接受的 key12345678-d DELETE, --delete=DELETE # 删除某个或某些个已接受(Accepted Keys)认证-D, --delete-all # 删除所有已接受(Accepted Keys)认证[root@master01 ~]# salt-key -d minion01 -yDeleting the following keys:Accepted Keys:minion01Key for minion minion01 deleted. 显示 key 的指纹信息12345678910111213141516171819202122232425-f FINGER, --finger=FINGER # 显示指定 key 的指纹信息(正则表达式)-F, --finger-all # 显示所有 key 的指纹信息[root@master01 ~]# salt-key -f "minion01"Unaccepted Keys:minion01: c0:e4:83:48:ef:f7:38:70:c7:1d:47:41:df:9c:03:f0[root@master01 ~]# salt-key -FLocal Keys:master.pem: db:5a:f0:25:13:50:94:b9:e0:61:1d:b9:fe:bc:62:b6master.pub: b9:bd:58:90:01:f2:61:7f:03:be:e1:51:f5:0b:4f:08Unaccepted Keys:minion01: c0:e4:83:48:ef:f7:38:70:c7:1d:47:41:df:9c:03:f0#################################################################################################[root@master01 ~]# salt-key -f "minion01"Accepted Keys:minion01: c0:e4:83:48:ef:f7:38:70:c7:1d:47:41:df:9c:03:f0[root@master01 ~]# salt-key -FLocal Keys:master.pem: db:5a:f0:25:13:50:94:b9:e0:61:1d:b9:fe:bc:62:b6master.pub: b9:bd:58:90:01:f2:61:7f:03:be:e1:51:f5:0b:4f:08Accepted Keys:minion01: c0:e4:83:48:ef:f7:38:70:c7:1d:47:41:df:9c:03:f0 检测主机是否存活123[root@master01 ~]# salt 'minion01' test.pingminion01: True 我们发送一条消息给一个或所有的 minion,并告诉它们运行 salt 内置的一个模块中的一条命令(也可以说是模块中的一个函数)。该示例中,miniion 返回 true。这个命令能查询有哪些 minion 是存活的。]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack 认证配置</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack 安装]]></title>
<url>%2F2018%2F09%2F04%2FSaltStack%E5%AE%89%E8%A3%85%2F</url>
<content type="text"><![CDATA[SaltStack 安装 SaltStack 官网官网:https://repo.saltstack.com/#rhelGitHub:https://github.com/saltstack/salt 最新版本(Pin to Latest Release)123456789# Redhat/CentOS 7 PY2yum -y install https://repo.saltstack.com/yum/redhat/salt-repo-latest-2.el7.noarch.rpm# Redhat/CentOS 7 PY3# Redhat Python3 支持要求先安装EPELyum -y install https://repo.saltstack.com/py3/redhat/salt-py3-repo-latest-2.el7.noarch.rpm# Redhat/CentOS 6 PY2yum -y install https://repo.saltstack.com/yum/redhat/salt-repo-latest-2.el6.noarch.rpm 主版本(Pin to Major Version)12345678# Redhat/CentOS 7 PY2yum -y install https://repo.saltstack.com/yum/redhat/salt-repo-2018.3-1.el7.noarch.rpm# Redhat/CentOS 7 PY3yum -y install https://repo.saltstack.com/py3/redhat/salt-py3-repo-2018.3-1.el7.noarch.rpm# Redhat/CentOS 6 PY2yum -y install https://repo.saltstack.com/yum/redhat/salt-repo-2018.3-1.el6.noarch.rpm 次版本(Pin to Minor Release)12345678910# 安装指定 SaltStack yum仓库文件 和 key文件rpm --import https://repo.saltstack.com/yum/redhat/6/x86_64/archive/2018.3.2/SALTSTACK-GPG-KEY.pubcat /etc/yum.repos.d/saltstack.repo[saltstack-repo]name=SaltStack repo for RHEL/CentOS $releaseverbaseurl=https://repo.saltstack.com/yum/redhat/$releasever/$basearch/archive/2018.3.2enabled=1gpgcheck=1gpgkey=https://repo.saltstack.com/yum/redhat/$releasever/$basearch/archive/2018.3.2/SALTSTACK-GPG-KEY.pub 安装 Salt 组件 获取完以上的仓库源后,执行以下命令,开始安装 1234567yum clean expire-cacheyum -y install salt-masteryum -y install salt-minionyum -y install salt-sshyum -y install salt-syndicyum -y install salt-cloudyum -y install salt-api 启停 salt-master 服务1234567systemctl start salt-mastersystemctl -l status salt-mastersystemctl enable salt-mastersystemctl restart salt-mastersystemctl stop salt-masterjournalctl -fu salt-masterps -ef | grep salt-master 启停 salt-minion 服务1234567systemctl start salt-minionsystemctl -l status salt-minionsystemctl enable salt-minionsystemctl restart salt-minionsystemctl stop salt-minionjournalctl -fu salt-minionps -ef | grep salt-minion]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack 安装</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 可迭代对象与迭代器对象]]></title>
<url>%2F2018%2F09%2F03%2FPython%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1%E4%B8%8E%E8%BF%AD%E4%BB%A3%E5%99%A8%E5%AF%B9%E8%B1%A1%2F</url>
<content type="text"><![CDATA[Python 可迭代对象与迭代器对象 什么是迭代器? 迭代的工具什么是迭代?迭代是一个重复的过程,迭代不是单纯的重复,每一次重复都是基于上一次结果而进行的单纯的重复,并不是迭代 为什么要用迭代器? 找到一种可以不依赖索引的迭代取值方式12345678910l = ['a', 'b', 'c']i = 0while i < len(i): print(l[i]) i += 1# 执行结果abc 依赖于列表的的索引而进行的索引的取值方式只适用于 字符串,列表,元组 的序列数据类型 针对没有索引的数据类型:字典,集合,文件用迭代器,不依赖于索引 怎么用迭代器? 可迭代对象在 Python 中,凡是内置有 __iter__ 方法对象,都是可迭代的对象12345678910a = 1b = 1.1# 以下都是可迭代的对象c = 'hello'l = [1, 2, 3]t = (1, 2, 3)d = {'a': 1}g = {1, 2, 3}f = open('aa.py', 'rb') # 文件类型是迭代器对象 迭代器对象?执行可迭代对象下 __iter__ 方法得到的返回值是一个迭代器对象迭代器对象是内置有 __next__ 方法 123456l = ['a', 'b', 'c']iter_obj = l.__iter__()print(iter_obj)# 执行结果<list_iterator object at 0x00000252754AD080> 如何拿到迭代器?执行可迭代对象下的 __iter__() 方法,就能拿到迭代器对象 迭代器对象是内置有 __next__ 方法列表类型1234567891011l = ['a', 'b', 'c']iter_obj = l.__iter__()print(iter_obj.__next__())print(iter_obj.__next__())print(iter_obj.__next__())# 执行结果abc 字典类型1234567891011dic = {'k1': 1, 'k2': 2, 'k3': 3}iter_obj = dic.__iter__()print(iter_obj.__next__())print(iter_obj.__next__())print(iter_obj.__next__())# 执行结果k1k2k3 这种取值方式,不依赖于索引 迭代器拿到了以后,就可以想可以把所有可迭代对象全都变成迭代器对象,然后再按照迭代器的方式,一点一点取值就完全可以脱离索引对我们取值的一种限制12345678910111213dic = {'k1': 1, 'k2': 2, 'k3': 3}iter_obj = dic.__iter__()print(iter_obj.__next__())print(iter_obj.__next__())print(iter_obj.__next__())print(iter_obj.__next__())# 执行结果k1k2k3StopIteration # 结束信号 结束信号:StopIteration,一旦这个信号出现后,就应该知道,迭代器里的值已经被取完了按照索引依次排开称为有序,有索引的类型,才是有序的(字符串,列表,元组)字典是无序的,字典取值是按照 key取值的,和顺序没有关系 123456789101112131415161718vim aa.pydef f1(): def f2(): def f3(): def f4(): iff = open('aa.py', 'rt', encoding='utf-8')iter_obj = f.__iter__()print(iter_obj.__next__(), end='')print(iter_obj.__next__(), end='')print(iter_obj.__next__(), end='')# 执行结果def f1(): def f2(): def f3(): 对文件来说,特点是什么?也能够按照迭代器的方式,把里面的值,一个个取出来可以完全不依赖于索引,仍然能把一个数据类型里面的值,一次次取出来 把字典里的多个值取出来12345678910111213dic = {'k1': 1, 'k2': 2, 'k3': 3}iter_obj = dic.__iter__()while True: try: # 检测有可能抛异常的代码块 print(iter_obj.__next__()) except StopIteration: break# 执行结果k1k2k3 总结 内置有 __iter__ 方法的称为可迭代对象内置同时有 __iter__,__next__ 方法的称为迭代器对象 迭代器对象内置 __next__ 方法__iter__ 方法,执行该方法得到仍然是迭代器本身 1234567# 基于迭代器的迭代取值方式dic = {'k1': 1, 'k2': 2, 'k3': 3}iter_obj = dic.__iter__()print(iter_obj.__iter__() is iter_obj)# 执行结果True 迭代器对象一定是可迭代的对象可迭代对象不一定是迭代器对象 for 循环的底层运行机制 for循环可以称为迭代器循环 执行 for 循环 先调用 in 后面那个对象的 dic.__iter__() 方法,拿到它的迭代器对象 iter_obj = dic.__iter__() 执行迭代器对象的 __next__ 方法,得到的返回值赋值 in 前面的变量名k,然后执行一次循环体代码 print(k) 循环执行,直到取完迭代器内所有的值,自动捕捉 StopIteration 异常结束循环 for 循环字典123456789dic = {'k1': 1, 'k2': 2, 'k3': 3}for k in dic: print(k)# 执行结果k1k2k3 for 循环数字12345for x in 10: pass# 执行结果TypeError: 'int' object is not iterable for 循环文件1234567891011121314f = open('aa.py', 'rt', encoding='utf-8')for line in f: print(line)# 执行结果def f1(): def f2(): def f3(): def f4(): if 是否能区分出 in 后面跟的是迭代器对象还是可迭代对象,答:不能只能区分出只要能被 for循环 循环的,都称为可迭代对象,并不能确定它是否是迭代器对象迭代器对象最关键的方法是 __next__ 方法,也内置了一个 __iter__ 方法,为了和 for循环 的标准统一 12345678910dic = {'k1': 1, 'k2': 2, 'k3': 3}iter_obj = dic.__iter__() # 迭代器对象 = dic.__iter__()for k in iter_obj: print(k)# 执行结果k1k2k3 迭代器的优缺点优点: 提供了一种不依赖与索引的迭代取值方式 节省内存 缺点:值一次性取完,取完后无法再次取值,除非重新得到新的迭代器对象1234567891011121314151617dic = {'k1': 1, 'k2': 2, 'k3': 3}iter_obj = dic.__iter__()print('第一次迭代iter_obj')for k in iter_obj: print(k)print('第二次迭代iter_obj')for k in iter_obj: print(k)# 执行结果第一次迭代iter_objk1k2k3第二次迭代iter_obj 需求:两次都取到值1234567891011121314151617181920dic = {'k1': 1, 'k2': 2, 'k3': 3}iter_obj = dic.__iter__()print('第一次迭代iter_obj')for k in iter_obj: print(k)iter_obj = dic.__iter__()print('第二次迭代iter_obj')for k in iter_obj: print(k)# 执行结果第一次迭代iter_objk1k2k3第二次迭代iter_objk1k2k3 12345678910111213141516171819dic = {'k1': 1, 'k2': 2, 'k3': 3}print('第一次迭代')for k in dic: print(k)print('第二次迭代')for k in dic: print(k)# 执行结果第一次迭代k1k2k3第二次迭代k1k2k3 迭代器节省内存 12345f = open('aa.py')print(f)# 执行结果<_io.TextIOWrapper name='aa.py' mode='r' encoding='cp936'> 12345678f = open('aa.py')print(f.__next__())print(f.__next__())# 执行结果def f1(): def f2(): 谨慎使用f.read() 读取方式是一下子把文件内容全部读出,假设文件过大,就会把内存撑爆了f.readlines() 它也是一下子把文件内容全部读出1234567f = open()# f.readline() # 读文件内容最好使用 readline(),一次读一行# 推荐读文件方式# 即便文件非常大,也不会影响到内存(只不过for循环的次数会比较多)for line in f: print(line) Python2 中 range() 获取的是一个列表类型12>>> range(1, 3)[1, 2] Python3 中 把 range() 做成了一个可迭代对象123456789101112131415161718192021>>> range(1, 3)range(1, 3)>>> obj = range(1, 3)>>> obj.__iter__<method-wrapper '__iter__' of range object at 0x000002A00528B450>>>> obj.__next__Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: 'range' object has no attribute '__next__'>>> obj=range(1, 100000000000000000000000000000000000000000000000000000000)>>> objrange(1, 100000000000000000000000000000000000000000000000000000000)>>> iter_obj = obj.__iter__()>>> next(iter_obj)1>>> next(iter_obj)2>>> iter_obj.__next__()3>>> iter_obj.__next__()4 Python3 中使用以下方式,不用担心卡死现象Python2 中就有卡死的风险1234567for i in range(1, 10000): print(i)# 执行结果1...省略...9999 缺点:只能往后取,不能往前取,值一次性取完,值取完后无法再次取值,除非重新得到新的迭代器对象无法预测迭代器的长度 Python2123>>> d = {'a':1, 'b':2}>>> d.keys()['a', 'b'] Python3 做了很多优化机制,在 Python2 中的列表数据类型,在 Python3 中都做成了 可迭代对象12345678910111213141516171819>>> d = {'a':1, 'b':2}>>> d{'a': 1, 'b': 2}>>> d.__iter__<method-wrapper '__iter__' of dict object at 0x0000019A9FD67828>>>> d.__next__Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: 'dict' object has no attribute '__next__'>>> obj = d.keys()>>> objdict_keys(['a', 'b'])>>> obj.__iter__<method-wrapper '__iter__' of dict_keys object at 0x0000019A9FDAA528>>>> obj.__next__Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: 'dict_keys' object has no attribute '__next__']]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 可迭代对象与迭代器对象</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 三元表达式、列表生成式、字典生成式]]></title>
<url>%2F2018%2F09%2F03%2FPython%E4%B8%89%E5%85%83%E8%A1%A8%E8%BE%BE%E5%BC%8F-%E5%88%97%E8%A1%A8%E7%94%9F%E6%88%90%E5%BC%8F-%E5%AD%97%E5%85%B8%E7%94%9F%E6%88%90%E5%BC%8F%2F</url>
<content type="text"><![CDATA[Python 三元表达式、列表生成式、字典生成式 都是为了写代码写的简介一点儿,不用它们有点问题都没有这四个东西都是为了将多行代码写到一行去,而且还能兼顾一个简洁性的特点 三元表达式 比较两个值最大值最小值的功能1234567891011def max2(x, y): if x > y: return x else: return yres = max2(1, 3)print(res)# 执行结果3 三元表达式 其实就是想用一行把上面这些事情搞定 语法 条件成立时的返回值 if 条件 else 条件不成立时的返回值 12345678x = 10y = 30res = x if x > y else yprint(res)# 执行结果30 条件算一元:if x > y条件成立时返回的值算一元:x条件不成立时返回的值算一元:else y 12345678x = 10y = 2res = True if x > y else Falseprint(res)# 执行结果True 12345678x = 1y = 2res = True if x > y else Falseprint(res)# 执行结果False 列表生成式(列表推导式)12345678l = []for i in range(10): # res = 'egg %s' % i # l.append(res) l.append('egg %s' % i)# 执行结果['egg0', 'egg1', 'egg2', 'egg3', 'egg4', 'egg5', 'egg6', 'egg7', 'egg8', 'egg9'] 12345678nums = []for i in range(10): nums.append(i)print(nums)# 执行结果[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 语法1234567891011121314[expression for item1 in iterable1 if condition1for item2 in iterable2 if condition2...for itemN in iterableN if conditionN]类似于res = []for item1 in iterable1: for item2 in iterable2: if condition2 ... for itemN in iterableN: if conditionN: res.append(expression) 列表生成式示例 12345l = ['egg%s' % i for i in range(10)]print(l)# 执行结果['egg0', 'egg1', 'egg2', 'egg3', 'egg4', 'egg5', 'egg6', 'egg7', 'egg8', 'egg9'] 获取10个数字12345nums = [i for i in range(10)]print(nums)# 执行结果[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 获取10个数的平方12345nums = [i**2 for i in range(10)]print(nums)# 执行结果[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 获取从4 开始往后的值123456789nums = []for i in range(10): if i > 3: nums.append(i)print(nums)# 执行结果[4, 5, 6, 7, 8, 9] 12345nums = [i for i in range(10) if i > 3]print(nums)# 执行结果[4, 5, 6, 7, 8, 9] 小练习 将 name 不是 lisi 的字符串后缀加上 py,并输出12345678910names = ['zhangsan', 'lisi', 'wangwu', 'zhaoliu']l = []for name in names: if name != 'lisi': l.append(name + 'py')print(l)# 执行结果['zhangsanpy', 'wangwupy', 'zhaoliupy'] 将后缀为 py 的字符串,小写变大写,并输出12345678910names = ['zhangsan_py', 'lisi', 'wangwu_py', 'zhaoliu_py']l = []for name in names: if name.endswith('py'): l.append(name.upper())print(l)# 执行结果['ZHANGSAN_PY', 'WANGWU_PY', 'ZHAOLIU_PY'] 将不是 py 结尾的字符串过滤掉,并转换为大写输出1234567891011names = ['zhangsan_py', 'lisi', 'wangwu_py', 'zhaoliu_py']l = []for name in names: if name.endswith('py'): l.append(name.upper())names = lprint(names)# 执行结果['ZHANGSAN_PY', 'WANGWU_PY', 'ZHAOLIU_PY'] 将以下列表中将不是 lisi 的字符串后缀加上 py,并大写输出 123456names = ['zhangsan_py', 'lisi', 'wangwu_py', 'zhaoliu_py']names = [name.upper() for name in names if name.endswith('py')]print(names)# 执行结果['ZHANGSAN_PY', 'WANGWU_PY', 'ZHAOLIU_PY'] 将以下列表中以 py 结尾的名字过滤掉,然后保留剩下的名字长度 123456789101112names = ['zhangsan', 'lisi_py', 'wangwu', 'zhaoliu']names = [name for name in names if not name.endswith('py')]print(names)# 执行结果['zhangsan', 'wangwu', 'zhaoliu']#####################################################################################names = ['zhangsan', 'lisi_py', 'wangwu', 'zhaoliu']names = [name for name in names if not name.endswith('py')]print(names)# 执行结果[8, 6, 7] 字典生成式 集合生成式12345s = {i for i in range(10) if i > 3}print(3)# 执行结果{4, 5, 6, 7, 8, 9} 把符号 [] 换成 {} 就是字典表达式123456789101112d = {i:i for i in range(10)}print(d)# 执行结果{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}##########################################################################d = {i:i for i in range(10) if i > 3}print(d)# 执行结果{4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9} 小练习 将以下列表内的字典数据类型,转换成字典类型 123456userinfo = [('egon', '123'), ('alex', '456'), ('wxx', '679')]dic = {k: v for k, v in userinfo}print(dic)# 执行结果{'egon': '123', 'alex', '456', 'wxx': '679'} 123456userinfo = [('egon', '123'), ('alex', '456'), ('wxx', '679')]dic = {item[0]:item[1] for item in userinfo}print(dic)# 执行结果{'egon': '123', 'alex', '456', 'wxx': '679'} 123456userinfo = [('egon', '123'), ('alex', '456'), ('wxx', '679')]dic = {k:v for k, v in userinfo}print(dic)# 执行结果{'egon': '123', 'alex', '456', 'wxx': '679'} 将列表转换成字典1234567891011121314info = [ ['name', 'egon'], ('age', 18), ['sex', 'male']]d = {}for item in info: print(item)# 执行结果['name', 'egon']('age', 18)['sex', 'male'] 1234567891011121314info = [ ['name', 'egon'], ('age', 18), ['sex', 'male']]d = {}for item in info: print(item[0], item[1])# 执行结果name egonage 18sex male 12345678910111213info = [ ['name', 'egon'], ('age', 18), ['sex', 'male']]d = {}for item in info: d[item[0]] = item[1]print(d)# 执行结果{'name': 'egon', 'age': 18, 'sex': 'male'} 1234567891011info = [ ['name', 'egon'], ('age', 18), ['sex', 'male']]d = {item[0]: item[1] for item in info}print(d)# 执行结果{'name': 'egon', 'age': 18, 'sex': 'male'} 把 key 变成大写1234567891011d = { 'name': 'egon', 'age': 18, 'sex': 'male'}d = {k.upper():v for k,v in d.items()}print(d)# 执行结果{'NAME': 'egon', 'AGE': 18, 'SEX': 'male'}]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 三元表达式、列表生成式、字典生成式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker 资源汇总]]></title>
<url>%2F2018%2F09%2F01%2FDocker%E8%B5%84%E6%BA%90%E6%B1%87%E6%80%BB%2F</url>
<content type="text"><![CDATA[Docker 资源汇总 Docker官方英文资源Docker官网:http://www.docker.comDocker windows入门:https://docs.docker.com/windows/Docker Linux 入门:https://docs.docker.com/linux/Docker mac 入门:https://docs.docker.com/mac/Docker 用户指引:https://docs.docker.com/engine/userguide/Docker 官方博客:http://blog.docker.com/Docker Hub: https://hub.docker.com/Docker开源: https://www.docker.com/open-source Docker中文资源Docker中文网站:https://www.docker-cn.com/Docker安装手册:https://docs.docker-cn.com/engine/installation/ Docker 国内镜像网易加速器:http://hub-mirror.c.163.com官方中国加速器:https://registry.docker-cn.comustc的镜像:https://docker.mirrors.ustc.edu.cndaocloud:https://www.daocloud.io/mirror#accelerator-doc(注册后使用) 参考文档http://www.runoob.com/docker/docker-resources.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker 资源汇总</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker tag 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-tag%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker tag 命令 docker tag : 标记本地镜像,将其归入某一仓库。 语法1docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG] 实例将镜像 ubuntu:15.10 标记为 runoob/ubuntu:v3 镜像。 1234[root@localhsot ~] docker tag ubuntu:15.10 runoob/ubuntu:v3[root@localhsot ~] docker images runoob/ubuntu:v3REPOSITORY TAG IMAGE ID CREATED SIZErunoob/ubuntu v3 4e3b13c8a266 3 months ago 136.3 MB 参考文档http://www.runoob.com/docker/docker-tag-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker tag 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker search 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-search%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker search 命令 docker search : 从 Docker Hub 查找镜像 语法1docker search [OPTIONS] TERM OPTIONS 说明: –automated : 只列出 automated build 类型的镜像; –no-trunc : 显示完整的镜像描述; -s : 列出收藏数不小于指定值的镜像。 实例从 Docker Hub 查找所有镜像名包含 java,并且收藏数大于 10 的镜像 12345678[root@localhost ~] docker search -s 10 javaNAME DESCRIPTION STARS OFFICIAL AUTOMATEDjava Java is a concurrent, class-based... 1037 [OK] anapsix/alpine-java Oracle Java 8 (and 7) with GLIBC ... 115 [OK]develar/java 46 [OK]isuper/java-oracle This repository contains all java... 38 [OK]lwieske/java-8 Oracle Java 8 Container - Full + ... 27 [OK]nimmis/java-centos This is docker images of CentOS 7... 13 [OK] 参考文档http://www.runoob.com/docker/docker-search-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker search 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker push 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-push%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker push 命令 docker push : 将本地的镜像上传到镜像仓库,要先登陆到镜像仓库。 语法1docker push [OPTIONS] NAME[:TAG] OPTIONS 说明: –disable-content-trust : 忽略镜像的校验,默认开启。 实例上传本地镜像 myapache:v1 到镜像仓库中。 1docker push myapache:v1 参考文档http://www.runoob.com/docker/docker-push-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker push 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker pull 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-pull%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker pull 命令 docker pull : 从镜像仓库中拉取或者更新指定镜像 语法1docker pull [OPTIONS] NAME[:TAG|@DIGEST] OPTIONS 说明: -a : 拉取所有 tagged 镜像 –disable-content-trust : 忽略镜像的校验,默认开启 实例从 Docker Hub 下载 java 最新版镜像。 1docker pull java 从 Docker Hub 下载 REPOSITORY 为 java 的所有镜像。 1docker pull -a java 参考文档http://www.runoob.com/docker/docker-pull-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker pull 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker version 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-version%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker version 命令 docker version : 显示 Docker 版本信息。 语法1docker version [OPTIONS] OPTIONS 说明: -f : 指定返回值的模板文件。 实例显示 Docker 版本信息。 12345678910111213141516docker versionClient: Version: 1.8.2 API version: 1.20 Go version: go1.4.2 Git commit: 0a8c2e3 Built: Thu Sep 10 19:19:00 UTC 2015 OS/Arch: linux/amd64Server: Version: 1.8.2 API version: 1.20 Go version: go1.4.2 Git commit: 0a8c2e3 Built: Thu Sep 10 19:19:00 UTC 2015 OS/Arch: linux/amd64 参考文档http://www.runoob.com/docker/docker-version-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker version 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker info 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-info%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker info 命令 docker info : 显示 Docker 系统信息,包括镜像和容器数。 语法1docker info [OPTIONS] 实例查看 docker 系统信息。 12345678910111213141516docker infoContainers: 12Images: 41Storage Driver: aufs Root Dir: /var/lib/docker/aufs Backing Filesystem: extfs Dirs: 66 Dirperm1 Supported: falseExecution Driver: native-0.2Logging Driver: json-fileKernel Version: 3.13.0-32-genericOperating System: Ubuntu 14.04.1 LTSCPUs: 1Total Memory: 1.954 GiBName: iZ23mtq8bs1ZID: M5N4:K6WN:PUNC:73ZN:AONJ:AUHL:KSYH:2JPI:CH3K:O4MK:6OCX:5OYW 参考文档http://www.runoob.com/docker/docker-info-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker info 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker import 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-import%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker import 命令 docker import : 从归档文件中创建镜像。 语法1docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]] OPTIONS 说明: -c : 应用 docker 指令创建镜像; -m : 提交时的说明文字; 实例从镜像归档文件 my_ubuntu_v3.tar 创建镜像,命名为 runoob/ubuntu:v4 12345[root@localhost ~] docker import my_ubuntu_v3.tar runoob/ubuntu:v4 sha256:63ce4a6d6bc3fabb95dbd6c561404a309b7bdfc4e21c1d59fe9fe4299cbfea39[root@localhost ~] docker images runoob/ubuntu:v4REPOSITORY TAG IMAGE ID CREATED SIZErunoob/ubuntu v4 63ce4a6d6bc3 20 seconds ago 142.1 MB 参考文档http://www.runoob.com/docker/docker-import-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker import 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker save 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-save%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker save 命令 docker save : 将指定镜像保存成 tar 归档文件。 语法1docker save [OPTIONS] IMAGE [IMAGE...] OPTIONS 说明: -o : 输出到的文件。 实例将镜像 runoob/ubuntu:v3 生成 my_ubuntu_v3.tar 文档 123[root@localhost ~] docker save -o my_ubuntu_v3.tar runoob/ubuntu:v3[root@localhost ~] ll my_ubuntu_v3.tar-rw------- 1 runoob runoob 142102016 Jul 11 01:37 my_ubuntu_v3.ta Docker 容器导入导出有两种方法一种是使用 save 和 load 命令使用例子如下: 12docker save ubuntu:load > /root/ubuntu.tardocker load < ubuntu.tar 一种是使用 export 和 import 命令使用例子如下: 12docker export 98ca36 > ubuntu.tarcat ubuntu.tar | sudo docker import - ubuntu:import 需要注意两种方法不可混用。 参考文档http://www.runoob.com/docker/docker-save-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker save 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker build 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-build%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker build 命令 docker build 命令用于使用 Dockerfile 创建镜像。 语法1docker build [OPTIONS] PATH | URL | - OPTIONS说明: –build-arg=[] : 设置镜像创建时的变量; –cpu-shares : 设置 CPU 使用权重; –cpu-period : 限制 CPU CFS周期; –cpu-quota : 限制 CPU CFS配额; –cpuset-cpus : 指定使用的CPU id; –cpuset-mems : 指定使用的内存 id; –disable-content-trust : 忽略校验,默认开启; -f : 指定要使用的 Dockerfile 路径; –force-rm : 设置镜像过程中删除中间容器; –isolation : 使用容器隔离技术; –label=[] : 设置镜像使用的元数据; -m : 设置内存最大值; –memory-swap : 设置 swap 的最大值为 内存+swap,”-1” 表示不限 swap; –no-cache : 创建镜像的过程不使用缓存; –pull : 尝试去更新镜像的新版本; –quiet, -q : 安静模式,成功后只输出镜像 ID; –rm : 设置镜像成功后删除中间容器; –shm-size : 设置 /dev/shm 的大小,默认值是 64M; –ulimit : Ulimit 配置。 –tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。 –network: 默认 default。在构建期间设置RUN指令的网络模式 实例使用当前目录的 Dockerfile 创建镜像,标签为 runoob/ubuntu:v1。 1docker build -t runoob/ubuntu:v1 . 使用 URL github.com/creack/docker-firefox 的 Dockerfile 创建镜像。 1docker build github.com/creack/docker-firefox 也可以通过 -f Dockerfile 文件的位置: 1docker build -f /path/to/a/Dockerfile . 在 Docker 守护进程执行 Dockerfile 中的指令前,首先会对 Dockerfile 进行语法检查,有语法错误时会返回: 123docker build -t test/myapp .Sending build context to Docker daemon 2.048 kBError response from daemon: Unknown instruction: RUNCMD 参考文档http://www.runoob.com/docker/docker-build-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker build 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker rmi 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-rmi%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker rmi 命令 docker rmi : 删除本地一个或多个镜像。 语法1docker rmi [OPTIONS] IMAGE [IMAGE...] OPTIONS 说明: -f : 强制删除; –no-prune : 不移除该镜像的过程镜像,默认移除; 实例强制删除本地镜像 runoob/ubuntu:v4。 1234[root@localhost ~] docker rmi -f runoob/ubuntu:v4Untagged: runoob/ubuntu:v4Deleted: sha256:1c06aa18edee44230f93a90a7d88139235de12cd4c089d41eed8419b503072beDeleted: sha256:85feb446e89a28d58ee7d80ea5ce367eebb7cec70f0ec18aa4faa874cbd97c73 参考文档http://www.runoob.com/docker/docker-rmi-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker rmi 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker images 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-images%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker images 命令 docker images : 列出本地镜像。 语法1docker images [OPTIONS] [REPOSITORY[:TAG]] OPTIONS 说明: -a 列出本地所有的镜像(含中间映像层,默认情况下,过滤掉中间映像层) --digests 显示镜像的摘要信息 -f 显示满足条件的镜像 --format 指定返回值的模板文件 --no-trunc 显示完整的镜像信息 -q 只显示镜像ID 实例查看本地镜像列表。 123456789101112[root@localhost ~] docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEmymysql v1 37af1236adef 5 minutes ago 329 MBrunoob/ubuntu v4 1c06aa18edee 2 days ago 142.1 MB<none> <none> 5c6e1090e771 2 days ago 165.9 MBhttpd latest ed38aaffef30 11 days ago 195.1 MBalpine latest 4e38e38c8ce0 2 weeks ago 4.799 MBmongo 3.2 282fd552add6 3 weeks ago 336.1 MBredis latest 4465e4bcad80 3 weeks ago 185.7 MBphp 5.6-fpm 025041cd3aa5 3 weeks ago 456.3 MBpython 3.5 045767ddf24a 3 weeks ago 684.1 MB... 列出本地镜像中 REPOSITORY 为 ubuntu 的镜像列表。 1234[root@localhost ~] docker images ubuntuREPOSITORY TAG IMAGE ID CREATED SIZEubuntu 14.04 90d5884b1ee0 9 weeks ago 188 MBubuntu 15.10 4e3b13c8a266 3 months ago 136.3 MB 参考文档http://www.runoob.com/docker/docker-images-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker images 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker cp 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-cp%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker cp 命令 docker cp : 用于容器与主机之间的数据拷贝。 语法12docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH OPTIONS 说明: -L : 保持源目标中的链接 实例将主机 /www/runoob 目录拷贝到容器 96f7f14e99ab 的 /www 目录下。 1docker cp /www/runoob 96f7f14e99ab:/www/ 将主机 /www/runoob 目录拷贝到容器 96f7f14e99ab 中,目录重命名为 www。 1docker cp /www/runoob 96f7f14e99ab:/www 将容器 96f7f14e99ab 的 /www 目录拷贝到主机的 /tmp 目录中。 1docker cp 96f7f14e99ab:/www /tmp/ 参考文档http://www.runoob.com/docker/docker-cp-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker cp 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker commit 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-commit%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker commit 命令 docker commit : 从容器创建一个新的镜像。 语法1docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] OPTIONS 说明: -a : 提交的镜像作者; -c : 使用 Dockerfile 指令来创建镜像; -m : 提交时的说明文字; -p : 在 commit 时,将容器暂停。 实例将容器 a404c6c174a2 保存为新的镜像,并添加提交人信息和说明信息。 12345[root@localhost ~] docker commit -a "runoob.com" -m "my apache" a404c6c174a2 mymysql:v1 sha256:37af1236adef1544e8886be23010b66577647a40bc02c0885a6600b33ee28057[root@localhost ~] docker images mymysql:v1REPOSITORY TAG IMAGE ID CREATED SIZEmymysql v1 37af1236adef 15 seconds ago 329 MB 参考文档http://www.runoob.com/docker/docker-commit-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker commit 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker port 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-port%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker port 命令 docker port : 列出指定的容器的端口映射,或者查找将 PRIVATE_PORT NAT 到面向公众的端口。 语法1docker port [OPTIONS] CONTAINER [PRIVATE_PORT[/PROTO]] 实例查看容器 mynginx 的端口映射情况。 12[root@localhost ~] docker port mymysql3306/tcp -> 0.0.0.0:3306 参考文档http://www.runoob.com/docker/docker-port-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker port 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker export 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-export%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker export 命令 docker export : 将文件系统作为一个 tar 归档文件导出到 STDOUT。 语法1docker export [OPTIONS] CONTAINER OPTIONS 说明: -o : 将输入内容写到文件。 实例将 id 为 a404c6c174a2 的容器按日期保存为 tar 文件。 123[root@localhost ~]# docker export -o mysql-`date +%Y%m%d`.tar a404c6c174a2[root@localhost ~]# ls mysql-`date +%Y%m%d`.tarmysql-20160711.tar 参考文档http://www.runoob.com/docker/docker-export-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker export 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker logs 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-logs%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker logs 命令 docker logs : 获取容器的日志 语法1docker logs [OPTIONS] CONTAINER OPTIONS 说明: -f : 跟踪日志输出 –since : 显示某个开始时间的所有日志 -t : 显示时间戳 –tail :仅列出最新N条容器日志 实例跟踪查看容器 mynginx 的日志输出。 123456[root@localhost ~]# docker logs -f mynginx192.168.239.1 - - [10/Jul/2016:16:53:33 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36" "-"2016/07/10 16:53:33 [error] 5#5: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 192.168.239.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "192.168.239.130", referrer: "http://192.168.239.130/"192.168.239.1 - - [10/Jul/2016:16:53:33 +0000] "GET /favicon.ico HTTP/1.1" 404 571 "http://192.168.239.130/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36" "-"192.168.239.1 - - [10/Jul/2016:16:53:59 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36" "-"... 查看容器 mynginx 从 2016年7月1日 后的最新 10条 日志。 1docker logs --since="2016-07-01" --tail=10 mynginx 参考文档http://www.runoob.com/docker/docker-logs-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker logs 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker attach 命令]]></title>
<url>%2F2018%2F09%2F01%2FDocker-attach%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker attach 命令 docker attach : 连接到正在运行中的容器。 语法1docker attach [OPTIONS] CONTAINER 要 attach 上去的容器必须正在运行,可以同时连接上同一个 container 来共享屏幕(与 screen 命令的 attach 类似)。 官方文档中说 attach 后可以通过 CTRL-C 来 detach,但实际上经过测试,如果 container 当前在运行 bash,CTRL-C 自然是当前行的输入,没有退出;如果 container 当前正在前台运行进程,如输出 nginx 的 access.log 日志,CTRL-C 不仅会导致退出容器,而且还 stop 了。这不是我们想要的,detach 的意思按理应该是脱离容器终端,但容器依然运行。好在 attach 是可以带上 --sig-proxy=false 来确保 CTRL-D 或 CTRL-C 不会关闭容器。 实例容器 mynginx 将访问日志指到标准输出,连接到容器查看访问信息。 12[root@localhost ~]# docker attach --sig-proxy=false mynginx192.168.239.1 - - [10/Jul/2016:16:54:26 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36" "-" 参考文档http://www.runoob.com/docker/docker-attach-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker attach 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker top 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-top%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker top 命令 docker top : 查看容器中运行的进程信息,支持 ps 命令参数。 语法1docker top [OPTIONS] CONTAINER [ps OPTIONS] 容器运行时不一定有 /bin/bash 终端来交互执行 top 命令,而且容器还不一定有 top 命令,可以使用docker top 来实现查看 container 中正在运行的进程。 实例查看容器 mymysql 的进程信息。 123[root@localhost mysql]# docker top mymysqlUID PID PPID C STIME TTY TIME CMD999 40347 40331 18 00:58 ? 00:00:02 mysqld 查看所有运行容器的进程信息。 1for i in `docker ps | grep Up | awk '{print $1}'`; do echo \ && docker top $i; done 参考文档http://www.runoob.com/docker/docker-top-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker top 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker inspect 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-inspect%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker inspect 命令 docker inspect : 获取容器/镜像的元数据。 语法1docker inspect [OPTIONS] NAME|ID [NAME|ID...] OPTIONS说明: -f : 指定返回值的模板文件。 -s : 显示总的文件大小。 –type : 为指定类型返回JSON。 实例获取镜像 mysql:5.6 的元信息。 1234567891011121314151617181920212223runoob@runoob:~$ docker inspect mysql:5.6[ { "Id": "sha256:2c0964ec182ae9a045f866bbc2553087f6e42bfc16074a74fb820af235f070ec", "RepoTags": [ "mysql:5.6" ], "RepoDigests": [], "Parent": "", "Comment": "", "Created": "2016-05-24T04:01:41.168371815Z", "Container": "e0924bc460ff97787f34610115e9363e6363b30b8efa406e28eb495ab199ca54", "ContainerConfig": { "Hostname": "b0cf605c7757", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "3306/tcp": {} },... 获取正在运行的容器 mymysql 的 IP。 12runoob@runoob:~$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mymysql172.17.0.3 参考文档http://www.runoob.com/docker/docker-inspect-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker inspect 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker ps 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-ps%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker ps 命令 docker ps : 列出容器 语法1docker ps [OPTIONS] OPTIONS 说明: -a : 显示所有的容器,包括未运行的。 -f : 根据条件过滤显示的内容。 –format : 指定返回值的模板文件。 -l : 显示最近创建的容器。 -n : 列出最近创建的 n 个容器。 –no-trunc : 不截断输出。 -q : 静默模式,只显示容器编号。 -s : 显示总的文件大小。 实例列出所有在运行的容器信息 1234runoob@runoob:~$ docker psCONTAINER ID IMAGE COMMAND ... PORTS NAMES09b93464c2f7 nginx:latest "nginx -g 'daemon off" ... 80/tcp, 443/tcp myrunoob96f7f14e99ab mysql:5.6 "docker-entrypoint.sh" ... 0.0.0.0:3306->3306/tcp mymysql 列出最近创建的 5 个容器信息 1234567runoob@runoob:~$ docker ps -n 5CONTAINER ID IMAGE COMMAND CREATED 09b93464c2f7 nginx:latest "nginx -g 'daemon off" 2 days ago ... b8573233d675 nginx:latest "/bin/bash" 2 days ago ... b1a0703e41e7 nginx:latest "nginx -g 'daemon off" 2 days ago ... f46fb1dec520 5c6e1090e771 "/bin/sh -c 'set -x \t" 2 days ago ... a63b4a5597de 860c279d2fec "bash" 2 days ago ... 列出所有创建的容器ID 123456789101112runoob@runoob:~$ docker ps -a -q09b93464c2f7b8573233d675b1a0703e41e7f46fb1dec520a63b4a5597de6a4aa42e947bde7bb36e796843a432b73776664a8ab1a585ba52eb632bbd... 参考文档http://www.runoob.com/docker/docker-ps-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker ps 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker exec 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-exec%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker exec 命令 docker exec : 在运行的容器中执行命令 语法1docker exec [OPTIONS] CONTAINER COMMAND [ARG...] OPTIONS说明: -d : 分离模式: 在后台运行 -i : 即使没有附加也保持 STDIN 打开 -t : 分配一个伪终端 实例在容器 mynginx 中以交互模式执行容器内 /root/runoob.sh 脚本 12runoob@runoob:~$ docker exec -it mynginx /bin/sh /root/runoob.shhttp://www.runoob.com/ 在容器 mynginx 中开启一个交互模式的终端 12runoob@runoob:~$ docker exec -i -t mynginx /bin/bashroot@b1a0703e41e7:/# 参考文档http://www.runoob.com/docker/docker-exec-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker exec 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker pause/unpause 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-pause-unpause%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker pause/unpause 命令 docker pause : 暂停容器中所有的进程。docker unpause : 恢复容器中所有的进程。 语法12docker pause [OPTIONS] CONTAINER [CONTAINER...]docker unpause [OPTIONS] CONTAINER [CONTAINER...] 实例暂停数据库容器 db01 提供服务。 1docker pause db01 恢复数据库容器 db01 提供服务。 1docker unpause db01 参考文档http://www.runoob.com/docker/docker-pause-unpause-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker pause/unpause 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker rm 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-rm%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker rm 命令 docker rm :删除一个或多个容器 语法1docker rm [OPTIONS] CONTAINER [CONTAINER...] OPTIONS 说明: -f : 通过 SIGKILL 信号强制删除一个运行中的容器 -l : 移除容器间的网络连接,而非容器本身 -v : 删除与容器关联的卷 实例强制删除容器 db01、db02 1docker rm -f db01 db02 移除容器 nginx01 对容器 db01 的连接,连接名 db 1docker rm -l db 删除容器 nginx01,并删除容器挂载的数据卷 1docker rm -v nginx01 参考文档http://www.runoob.com/docker/docker-rm-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker rm 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker kill 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-kill%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker kill 命令 docker kill : 杀掉一个运行中的容器。 语法1docker kill [OPTIONS] CONTAINER [CONTAINER...] OPTIONS说明: -s : 向容器发送一个信号 实例杀掉运行中的容器 mynginx12docker kill -s KILL mynginxmynginx 参考文档http://www.runoob.com/docker/docker-kill-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker kill 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker start/stop/restart 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-start-stop-restart%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker start/stop/restart 命令 docker start: 启动一个或多少已经被停止的容器docker stop: 停止一个运行中的容器docker restart: 重启容器 语法123docker start [OPTIONS] CONTAINER [CONTAINER...]docker stop [OPTIONS] CONTAINER [CONTAINER...]docker restart [OPTIONS] CONTAINER [CONTAINER...] 实例启动已被停止的容器 myrunoob 1docker start myrunoob 停止运行中的容器 myrunoob 1docker stop myrunoob 重启容器 myrunoob 1docker restart myrunoob 参考文档http://www.runoob.com/docker/docker-start-stop-restart-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker start/stop/restart 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker create 命令]]></title>
<url>%2F2018%2F08%2F31%2FDocker-create%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker create 命令 docker create :创建一个新的容器但不启动它 用法同 docker run 语法1docker create [OPTIONS] IMAGE [COMMAND] [ARG...] 语法同 docker run 实例使用 docker 镜像 nginx:latest 创建一个容器,并将容器命名为 myrunoob 12docker create --name myrunoob nginx:latest09b93464c2f75b7b69f83d56a9cfc23ceb50a48a9db7652ee4c27e3e2cb1961f 参考文档http://www.runoob.com/docker/docker-create-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker create 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[find 命令结合 exec 和 xargs 使用的区别]]></title>
<url>%2F2018%2F08%2F30%2Ffind%E5%91%BD%E4%BB%A4%E7%BB%93%E5%90%88exec%E5%92%8Cxargs%E4%BD%BF%E7%94%A8%E7%9A%84%E5%8C%BA%E5%88%AB%2F</url>
<content type="text"><![CDATA[find 命令结合 exec 和 xargs 使用的区别 -exec xargs 区别一 该参数是将查找的结果文件名逐个传递给后面的命令执行,如果文件比较多则执行的效率会较低 该命令是将查找的结果一次性传给后面的命令执行,命令执行效率高,可以使用 -n 参数控制一次传递文件的个数 区别二 文件名有空格等特殊字符也照常处理 处理特殊的文件名(例如:文件名有空格)需要采用特殊的方式(find . -name “edu” -print0 使用 -exec 选项命令操作示例及结果如下 1234567891011121314151617# 从命令执行的结果可以看到,每次获得一个文件就输出一次find . -type f -exec echo oldboyedu {} \ ;oldboyedu ./.viminfooldboyedu ./anaconda-ks.cfgoldboyedu ./install.logoldboyedu ./install.log.syslogoldboyedu ./.bash_logoutoldboyedu ./.cshrcoldboyedu ./ls.txtoldboyedu ./.bash_historyoldboyedu ./.lesshstoldboyedu ./oldboy. Logoldboyedu ./test. txtoldboyedu ./.tcshrcoldboyedu ./GB2312.txtoldboyedu ./.bash_profileoldboyedu ./.bashrc 使用 xargs 命令操作示例及结果如下 123# 输出结果只有一行,xargs 获取到所有文件名一次性输出find . -type f | xargs echo oldboyeduoldboyedu ./.viminfo ./anaconda-ks.cfg ./install.log ./install.log.syslog ./.bash_logout ./.cshrc ./ls.txt ./.bash_history ./.lesshst ./oldboy.log ./test.txt ./.tcshrc ./GB2312.txt ./.bash_profile ./.bashrc xargs 还能控制每行输出的参数个数,示例如下,更多使用方法见 xargs 命令 1234567# 使用 -n 3 指定每次输出 3 个参数find . -type f | xargs -n 3 echo oldboyeduoldboyedu ./.viminfo ./anaconda-ks.cfg ./install.logoldboyedu ././install.log.syslog ./.bash_logout ./.cshrcoldboyedu ./ls.txt ./.bash_history ./.lesshstoldboyedu ./oldboy.log ./test.txt ./.tcshrcoldboyedu ./GB2312.txt ./.bash_profile ./.bashrc 验证区别二的案例 123456789101112131415# 创建一个文件名带有空格的特殊文件touch "oldboy edu"ll -h "oldboy edu"-rw-r--r-- 1 root root 0 May 17 16:30 oldboy edu# 使用 -exec 参数正常使用find . -name "*oldboy*" -exec ls -lh {} \;-rw-r--r-- 1 root root 0 May 17 16:30 ./oldboy edu# 使用 xargs 命令无法正常打印find . -name "*edu*" | xargs ls -lhls: cannot access ./oldboy: No such file or directoryls: cannot access edu: No such file or directoryfind . -name "*edu*" -print0 | xargs -0 ls -lh-rw-r--r-- 1 root root 0 May 17 16:30 . /oldboy edu]]></content>
<categories>
<category>Linux笔记</category>
</categories>
<tags>
<tag>find 命令结合 exec 和 xargs 使用的区别</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker run 命令]]></title>
<url>%2F2018%2F08%2F27%2FDocker-run%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Docker run 命令 docker run :创建一个新的容器并运行一个命令 语法1docker run [OPTIONS] IMAGE [COMMAND] [ARG...] OPTIONS 说明: -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项; -d: 后台运行容器,并返回容器ID; -i: 以交互模式运行容器,通常与 -t 同时使用; -p: 端口映射,格式为:主机(宿主)端口:容器端口 -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用; –name=”nginx-lb”: 为容器指定一个名称; –dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致; –dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致; -h “mars”: 指定容器的hostname; -e username=”ritchie”: 设置环境变量; –env-file=[]: 从指定文件读入环境变量; –cpuset=”0-2” or –cpuset=”0,1,2”: 绑定容器到指定CPU运行; -m : 设置容器使用内存最大值; –net=”bridge”: 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型; –link=[]: 添加链接到另一个容器; –expose=[]: 开放一个端口或一组端口; 实例使用 docker 镜像 nginx:latest 以后台模式启动一个容器,并将容器命名为 mynginx。 1docker run --name mynginx -d nginx:latest 使用镜像 nginx:latest 以后台模式启动一个容器,并将容器的 80 端口映射到主机随机端口。 1docker run -P -d nginx:latest 使用镜像 nginx:latest,以后台模式启动一个容器,将容器的 80 端口映射到主机的 80 端口,主机的目录 /data 映射到容器的 /data。 1docker run -p 80:80 -v /data:/data -d nginx:latest 绑定容器的 8080 端口,并将其映射到本地主机 127.0.0.1 的 80 端口上。 1docker run -p 127.0.0.1:80:8080/tcp ubuntu bash 使用镜像 nginx:latest 以交互模式启动一个容器,在容器内执行 /bin/bash 命令。 12docker run -it nginx:latest /bin/bashroot@b8573233d675:/# 参考文档http://www.runoob.com/docker/docker-run-command.html]]></content>
<categories>
<category>Docker笔记</category>
</categories>
<tags>
<tag>Docker run 命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python装饰器]]></title>
<url>%2F2018%2F08%2F27%2FPython%E8%A3%85%E9%A5%B0%E5%99%A8%2F</url>
<content type="text"><![CDATA[Python 装饰器 什么是装饰器?装饰指的是为被装饰对象添加新的功能器指的是工具 装饰器本身可以是任意可调用对象,被装饰的对象也可以是任意可调用的对象 目的:写一个函数用来为另一个函数添加新功能,需要遵循开放封闭原则对修改(源代码+调用方式)封闭的,对扩展是开放的不修改被装饰对象的源代码不修改被装饰对象的调用方式 示例 12345678910import timedef index(): time.sleep(3) print('welcome to index page')index()# 执行结果(3秒后获得结果)welcome to index page 需求:为 index() 函数增加(装饰)一个统计时间的功能123456789101112131415import timedef index(): time.sleep(3) print('welcome to index page')# 获取一个时间段start = time.time()index()stop = time.time()print('run time is %s' % (stop - start))# 执行结果welcome to index pagerun time is 3.004941940307617 time.time() 获取当前时间秒数的表达形式以 1970 年为基准,当前时间减去 1970 年,中间经过的秒数123456import timeprint(time.time())# 执行结果1535380439.073392 为无参函数做装饰 为无参函数做装饰 1234567891011121314151617import timedef index(): time.sleep(3) print('welcome to index page')def timmer(func): # func = index start = time.time() # 统计起始时间 func() # index() stop = time.time() # 统计结束时间 print('run time is %s' % (stop - start))timmer(index)# 执行结果welcome to index pagerun time is 3.0023746490478516 这里更改了被装饰对象的调用方式 1234567891011121314151617181920import timedef index(): time.sleep(3) print('welcome to index page')def outter(func): # func=最原始的index def wrapper(): start = time.time() func() # 最原始的index() stop = time.time() print('run time is %s' % (stop - start)) return wrapperf = outter(index) # f = wrapperf()# 执行结果welcome to index pagerun time is 3.0037789344787598 原功能该执行还是执行,并且再加上统计运行时间的功能 上面修改了被装饰对象的调用方式把 f=outter(index) 改成 index=outter(index) 就行了1234567891011121314151617181920import timedef index(): time.sleep(3) print('welcome to index page')def outter(func): # func=最原始的index def wrapper(): start = time.time() func() # 指向最原始的index() stop = time.time() print('run time is %s' % (stop - start)) return wrapperindex = outter(index) # index = wrapperindex() # wrapper()# 执行结果welcome to index pagerun time is 3.0034096240997314 为有参函数做装饰 为有参函数做装饰 1234567891011121314151617181920212223242526272829import timedef index(): time.sleep(3) print('welcome to index page')def home(name): time.sleep(2) print('welcome %s to home page' % name)def outter(func): # func = 最原始的index def wrapper(*args, **kwargs): start = time.time() func(*args, **kwargs) stop = time.time() print('run time is %s' % (stop - start)) return wrapperindex = outter(index) # index = wrapperindex()home = outter(home)home('egon')# 执行结果welcome to index pagerun time is 3.003574848175049welcome egon to home pagerun time is 2.003159523010254 为有函数返回值的函数做装饰 为有函数返回值的函数做装饰 12345678910111213141516171819202122232425262728import timedef index(): time.sleep(0.5) print('welcome to index page') return 1234def home(name): time.sleep(1) print('welcome %s to home page' % name)def outter(func): # func = 最原始的index def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print('run time is %s' % (stop - start)) return res return wrapperindex = outter(index) # index = wrapperres = index()print(res)# 执行结果welcome to index pagerun time is 0.50071883201599121234 装饰器语法糖 在 被装饰对象 正上方,单独一行写上 @装饰器函数名Python 会自上而下运行代码一旦运行到 @装饰器函数名 的位置,Python解释器就会立刻将正下方函数当做参数传给 装饰器函数然后将返回的结果,重新赋值 给 函数(函数变量名) 1234567891011121314151617181920212223242526272829303132333435import timedef timmer(func): # func = 最原始的index def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print('run time is %s' % (stop - start)) return res return wrapper@timmer # index = timmer(index)def index(): time.sleep(0.5) print('welcome to index page') return 1234@timmer # home = timmer(index)def home(name): time.sleep(1) print('welcome %s to home page' % name)index = timmer(index) # index = wrapperhome = timmer(home)res = index()print(res)home('egon')# 执行结果welcome to index pagerun time is 0.50152611732482911234welcome egon to home pagerun time is 1.0023066997528076 认证功能装饰器 为被装饰对象添加认证功能 123456789101112131415161718192021222324252627282930313233343536373839404142import timecurrent_userinfo = {'user': None}def outter(func): def wrapper(*args, **kwargs): if current_userinfo['user']: # res = func(*args, **kwargs) # return res return func(*args, **kwargs) user = input('please input your username: ').strip() pwd = input('please input your password: ').strip() if user == 'egon' and pwd == '123': print('login successfull') # 保存登录状态 current_userinfo['user'] = user res = func(*args, **kwargs) return res else: print('user or password error') return wrapper@outter # index = outter(index)def index(): print('welcome to index page') time.sleep(3)@outter # home = outter(home)def home(name): print('welcome %s' % name) time.sleep(2) return 123index()res = home('egon')# 执行结果please input your username: egonplease input your password: 123login successfullwelcome to index pagewelcome egon 多个装饰器的执行顺序 为一个被装饰对象,同时添加多个装饰器同时添加多个装饰器的执行顺序 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647import timecurrent_userinfo = {'user': None}def timmer(func): # func = wrapper1 def wrapper2(*args, **kwargs): print('wrapper2......') start = time.time() res = func(*args, **kwargs) stop = time.time() print('run time is %s' % (stop - start)) return res return wrapper2def outter(func): # func = 最原始的 index def wrapper1(*args, **kwargs): print('wrapper1......') if current_userinfo['user']: return func(*args, **kwargs) user = input('please input your username: ').strip() pwd = input('please input your password: ').strip() if user == 'egon' and pwd == '123': print('login successfull') # 保存登录状态 current_userinfo['user'] = user res = func(*args, **kwargs) # 调用最原始的index return res else: print('user or password error') return wrapper1@timmer # index = timmer(wrapper1)@outter # outter(最原始的index) ==> wrapper1def index(): print('welcome to index page') time.sleep(3)index() # index ==> wrapper2# 执行结果wrapper2......wrapper1......please input your username: egonplease input your password: 123login successfullwelcome to index pagerun time is 11.453307151794434 @timmer 在前,@outter 在后代码会统计 outter() 和 index() 的总时间,这不是我们想要的效果我们只要统计 index() 的运行时间 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import timecurrent_userinfo = {'user': None}def timmer(func): # func = wrapper1 def wrapper2(*args, **kwargs): print('wrapper2......') start = time.time() res = func(*args, **kwargs) stop = time.time() print('run time is %s' % (stop - start)) return res return wrapper2def outter(func): # func = 最原始的 index def wrapper1(*args, **kwargs): print('wrapper1......') if current_userinfo['user']: return func(*args, **kwargs) user = input('please input your username: ').strip() pwd = input('please input your password: ').strip() if user == 'egon' and pwd == '123': print('login successfull') # 保存登录状态 current_userinfo['user'] = user res = func(*args, **kwargs) # 调用最原始的index return res else: print('user or password error') return wrapper1@outter@timmerdef index(): print('welcome to index page') time.sleep(3)index() # index ==> wrapper2# 执行结果wrapper1......please input your username: egonegonplease input your password: 123123login successfullwrapper2......welcome to index pagerun time is 3.003432512283325 @outter 在前,@timmer 在后 总结:可以连续写多个装饰器,处于最顶层的装饰器先执行 有参装饰器12345678910111213141516171819202122232425262728293031323334353637383940414243import timecurrent_userinfo = {'user': None}def auth(engine = 'file'): def outter(func): # func = 最原始的index def wrapper(*args, **kwargs): if engine == 'file': if current_userinfo['user']: return func(*args, **kwargs) user = input('please input your username: ').strip() pwd = input('please input your password: ').strip() if user == 'egon' and pwd == '123': print('login successfull') # 保存登录状态 current_userinfo['user'] = user res = func(*args, **kwargs) return res else: print('user or password error') elif engine == 'mysql': print('mysql 的认证机制') elif engine == 'ldap': print('ldap 的认证机制') else: print('不支持该engine') return wrapper return outterx = auth(engine = 'file')@x # @autter # index = outter(最原始的index) # index = wrapperdef index(): print('welcome to index page') time.sleep(3)index()# 执行结果please input your username: egonplease input your password: 123login successfullwelcome to index page 1234x = auth(engine = 'mysql')# 执行结果mysql 的认证机制 合并一下 x = auth(engine = 'file') 和 @xauth(engine = 'file')这就是有参装饰器 12345678910111213141516171819202122232425262728293031323334353637383940414243444546import timecurrent_userinfo = {'user': None}def auth(engine = 'file'): def outter(func): # func = 最原始的index def wrapper(*args, **kwargs): if engine == 'file': if current_userinfo['user']: return func(*args, **kwargs) user = input('please input your username: ').strip() pwd = input('please input your password: ').strip() if user == 'egon' and pwd == '123': print('login successfull') # 保存登录状态 current_userinfo['user'] = user res = func(*args, **kwargs) return res else: print('user or password error') elif engine == 'mysql': print('mysql 的认证机制') elif engine == 'ldap': print('ldap 的认证机制') else: print('不支持该engine') return wrapper return outter@auth(engine = 'mysql') # @autter # index = outter(最原始的index) # index = wrapperdef index(): print('welcome to index page') time.sleep(3)@auth(engine = 'file')def home(name): print('welcome %s' % name) time.sleep(2) return 123index() # warpper()home('egon') # warpper('egon')# 执行结果mysql 的认证机制ldap 的认证机制 wraps 装饰器123456789101112131415161718192021import timedef timmer(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print('run time is %s' % (stop - start)) return res return wrapper@timmerdef index(): print('welcome to index page') time.sleep(3)index()# 执行结果welcome to index pagerun time is 3.0011937618255615 12345678910111213@timmerdef index(): print('welcome to index page') time.sleep(1) return 123res = index()print(res)# 执行结果welcome to index pagerun time is 1.0007963180541992123 定义函数,函数体最好有写文档注释 1234567891011121314151617def index(): """ 这是一个 index 函数 :return: """ print('welcome to index page') time.sleep(1) return 123print(help(index))# 执行结果Help on function index in module __main__:index() 这是一个index函数 :return: print(help(index)) 可以查看函数的帮助信息 12345678910def index(x, y): """ :param x: :param y: :return: """ print('welcome to index page') time.sleep(1) return 123 help() 其实就是在查看 __doc__ 属性 123456789101112131415def index(x, y): """ 这是一个index函数 :return: """ print('welcome to index page') time.sleep(1) return 123# print(help(index)) # index.__doc__print(index.__doc__)# 执行结果 这是一个index函数 :return: 1234567print(help(len))# 执行结果Help on built-in function len in module builtins:len(obj, /) Return the number of items in a container. 在不修改被装饰源代码,以及被装饰器调用的方式,为其加上新功能最好在使用 index() 的时候,和原来一模一样12345678910111213141516@timmerdef index(x, y): """ 这是一个index函数 :return: """ print('welcome to index page') time.sleep(1) return 123print(help(index))# 执行结果Help on function index in module __main__:wrapper(*args, **kwargs) 但是加上装饰器后,index() 就不是原来的 index() 了help() 是调用的 wrapper() 的注释打印的是 wrapper() 的注释 应该将 wrapper() 伪装的和原来的 index() 一模一样把 index() 的 __doc__ 拿过来把 index() 的 __name__ 拿过来12345678910111213141516171819202122232425262728293031323334import timedef timmer(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print('run time is %s' % (stop - start)) return res wrapper.__doc__ = func.__doc__ wrapper.__name__ = func.__name__ return wrapper@timmerdef index(x, y): """ 这是一个index函数 :return: """ print('welcome to index page') time.sleep(1) return 123print(help(index)) # index.__doc__print(index.__name__)# 执行结果Help on function index in module __main__:index(*args, **kwargs) 这是一个index函数 :return:index 这些功能不用自己写,Python 为我们内置了一个装饰器 wraps导入 from functools import wraps@wraps 加在最内层函数正上方12345678910111213141516171819202122232425262728293031323334import timefrom functools import wrapsdef timmer(func): @wraps(func) # 加在最内层函数正上方 def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print('run time is %s' % (stop - start)) return res # wrapper.__doc__ = func.__doc__ # wrapper.__name__ = func.__name__ return wrapper@timmerdef index(): """ 这是一个index函数 :return: """ print('welcome to index page') time.sleep(1) return 123print(help(index)) # index.__doc__print(index.__name__)# 执行结果index() 这是一个index函数 :return:index]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python装饰器</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python闭包函数]]></title>
<url>%2F2018%2F08%2F27%2FPython%E9%97%AD%E5%8C%85%E5%87%BD%E6%95%B0%2F</url>
<content type="text"><![CDATA[Python 闭包函数 前提:函数的作用域关系是在函数定义阶段就已经固定死的,与调用位置无关 闭包函数介绍 定义在函数的内部的函数 该内部函数包含对其外层函数作用域名字的引用 闭包函数通常需要结合函数对象的概念,将闭包函数返回到外部使用 示例一123456789101112def outter(): x = 100 def inner(): print(x) return innerf = outter()x = 200f()# 执行结果100 示例二1234567891011121314def outter(): x = 100 def inner(): print(x) return innerdef foo(): x = 300 f()foo()# 执行结果100 示例三 12345678910111213141516171819def outter(): x = 100 def inner(): print(x) return innerf = outter()def foo(): x = 300 def bar(): x = 400 f() bar()foo()# 执行结果100 闭 指的是 -> 定义在函数的内部的函数闭 指的就是将一个函数闭合在另一个函数内部包 指的是 -> 该内部函数包含对其外层函数作用域名字的引用 闭包函数的使用12345def outter(): x = 1 def inner(): print(x) return inner 闭包函数的基本形式 1234def outter(x): def inner(): print(x) return inner 求两个数的和 直接通过参数为其传值 12345def sum2(x, y): res = x + y return ressum2(1, 2) 将 值 包给 函数 sum2 12345678910def outter(x, y): # x = 1 # y = 2 def sum2(): res = x + y return res return sum2sum2 = outter(1, 2)sum2() 示例12345import requestsresponse = requests.get('https://www.jd.com')# if response.status_code = 200:print(response.text) 通过参数的形式为函数体传值 123456789import requestsdef get(url): response = requests.get(url) print(len(response.text))get('https://www.baidu.com')get('https://www.jd.com')get('https://www.tmall.com') 将值包给函数 12345678910111213141516171819202122232425262728import requestsdef outter(url): # url = 'https://www.jd.com' def get(): response = requests.get(url) print(len(response.text)) return getjd = outter('https://www.jd.com')jd()jd()jd()# 执行结果117377117377117377baidu = outter('https://www.baidu.com')baidu()baidu()baidu()# 执行结果244324432443 123456789def outter(): x = 1 def foo(): print(x) return foof = outter()print(f)f()]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python闭包函数</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 函数之 global 与 nonlocal 关键字]]></title>
<url>%2F2018%2F08%2F26%2FPython%E5%87%BD%E6%95%B0%E4%B9%8Bglobal%E4%B8%8Enonlocal%E5%85%B3%E9%94%AE%E5%AD%97%2F</url>
<content type="text"><![CDATA[Python 函数 global 与 nonlocal 关键字 global global 是用于在函数内去修改全局作用域的名字 12345678910x = 1def func(): x = 2func()print(x)# 执行结果1 12345678910x = []def func(): xfunc()print(x)# 执行结果[] 在局部名称空间,修改一个可变类型的变量名字 12345678910x = []def func(): x.append(1) # 在局部名称空间,修改一个可变类型的变量func()print(x)# 执行结果[1] 在 局部名称空间 造一个值覆盖全局的变量名字global 在局部声明名字是来自于全局的 1234567891011x = 1def func(): global x # 在 局部名称空间 造一个值覆盖全局的名字 x = 2func()print(x)# 执行结果2 nonlocal global 修改的是 全局名称空间 的所定义 的 名字但无法修改 当前层函数的 外面一层 的 局部名字 12345678910111213141516x = 222def f1(): x = 111 def f2(): global x x = 0 f2() print('f1--->', x)f1()print(x)# 执行结果f1---> 1110 nonlocal 声明变量是来自于当前层外层的名字只能找到 当前层函数 的外面一层的 名字如果没有则抛出异常:SyntaxError: no binding for nonlocal 'x' found必须是在函数内,不能跳出函数123456789101112131415x = 222def f1(): def f2(): # x = 111 nonlocal x x = 0 f2() print('f1--->', x)f1()print(x)# 执行结果SyntaxError: no binding for nonlocal 'x' found 12345678910111213141516x = 222def f1(): x = 111 def f2(): nonlocal x x = 0 f2() print('f1--->', x)f1()print('global===>', x)# 执行结果f1---> 0global===> 222]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 函数之 global 与 nonlocal 关键字</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python名称空间与作用域]]></title>
<url>%2F2018%2F08%2F23%2FPython%E5%90%8D%E7%A7%B0%E7%A9%BA%E9%97%B4%E4%B8%8E%E4%BD%9C%E7%94%A8%E5%9F%9F%2F</url>
<content type="text"><![CDATA[Python 名称空间与作用域 什么是名称空间?名称空间就是存放名字与值绑定映射关系的地方要取到值必须通过名字才能找到,而名字又在名称空间中存放着,所以在取值时首先是去名称空间中找名字找到了名字就拿到值的内存地址了 名称空间氛围三种内置名称空间 存放的 python 解释器自带的名字生命周期:在解释器启动时生成,在解释器关闭时回收12345678print(len)print(max)print(print)# 执行结果<built-in function len><built-in function max><built-in function print> 全局名称空间 除了内置的与局部的之外的名字,都属于全局名称空间生命周期:在程序文件执行时就立刻生成,在程序执行完毕后就回收 1234567891011x = 1y = 2def foo(): x = 1 y = 2foo()if y > x: z = 3 局部名称空间 存放的是函数内部定义的名字生命周期:在调用函数时临时生成,在调用函数结束后,立刻回收12345def foo(): x = 1 y = 2foo() 加载顺序 内置名称空间 -> 全局名称空间 -> 局部名称空间 全局查找 12345len = 100print(len)# 执行结果100 12345# len = 100print(len)# 执行结果<built-in function len> 局部查找 12345678910len = 100def foo(): len = 2222 print(len)foo()# 执行结果2222 加载名称空间的目的是为了将名字与值的绑定关系存放起来,存的目的是为取,也就是说,当我们在查找名字时,必然是在三者之一找到 查找顺序 局部名称空间 -> 全局名称空间 -> 内置名称空间基于当前所在的位置往后查找1234567891011x = 100y = 200# 注:函数的形参名属于局部名称空间def foo(x, y): print(x, y)foo(1, 2)# 执行结果1, 2 示例示例一 顺序 调用 f1() 找到 f2() f2() 打印 from f212345678910def f1(): x = 1 def f2(): print('from f2') f2()f1()# 执行结果from f2 示例二 顺序 调用 f1() 找到 f2() f2() 找到局部名称变量 x 打印 from f2 x1234567891011def f1(): x = 1 def f2(): x = 2 print('from f2', x) f2()f1()# 执行结果from f2 2 示例三 顺序 调用 f1() 找到 f2() f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 找到 名字变量 x f2() 打印 from f2 11234567891011def f1(): x = 1 def f2(): # x = 2 print('from f2', x) f2()f1()# 执行结果from f2 1 示例四 顺序 调用 f1() 找到 f2() f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 最终没有找到 名字变量 x 抛出异常NameError: name 'x' is not defined1234567891011def f1(): # x = 1 def f2(): # x = 2 print('from f2', x) f2()f1()# 执行结果NameError: name 'x' is not defined 示例五 顺序 调用 f1() 找到 f2() f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 在 全局名称空间 里找到 名字变量 x f2() 打印 from f2 111123456789101112x = 111def f1(): # x = 1 def f2(): # x = 2 print('from f2', x) f2()f1()# 执行结果from f2 111 示例六 顺序 调用 f1() 找到 f2() f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 在 全局名称空间 里找到 名字变量 x f2() 打印 from f2 111注:名称空间并不是往上找,只要有就能找到123456789101112def f1(): # x = 1 def f2(): # x = 2 print('from f2', x) f2()x = 111f1()# 执行结果from f2 111 示例七 顺序 调用 f1() 找到 f2() f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 在局部名称空间没有 名字变量 x,继续往外层寻找 f2() 在 全局名称空间 里找到 名字变量 x 名字变量 x=2222 被重新赋值 x=111 f2() 打印 from f2 11112345678910111213x = 2222def f1(): # x = 1 def f2(): # x = 2 print('from f2', x) f2()x = 111f1()# 执行结果from f2 111 作用域 域是范围,作用域指的是作用范围 分为两大类全局作用范围 包含 内置名称空间 与 全局名称空间 中的名字特点:全局有效,全局存活1234567891011121314151617def f1(): def f2(): def f3(): print(len) f3() f2()f1()def foo(): print(len)foo()# 执行结果<build-in function len><build-in function len> 内置名字,具备一个特点,无论在任何位置,都能访问的到这叫全局有效 12345678910111213141516171819x = 1def f1(): def f2(): def f3(): print(x) f3() f2()f1()def foo(): print(x)foo()# 执行结果11 局部作用范围 包含 局部名称空间 的名字特点:局部有效,临时存活 gloabls 与 locals12345678x = 1def foo(): y = 2print(globals)# 执行结果<built-in function globals> 返回 全局作用域 中的名字123456789x = 1111111111def foo(): y = 2print(globals()) # 返回 全局作用域 中的名字# 执行结果<built-in function globals>{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fa1f58357f0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/data/app/SH_weekend_s2/day04/04_名称空间与作用域.py', '__cached__': None, 'x': 1111111111, 'foo': <function foo at 0x7fa1f5766bf8>} 查看 全局名称空间 包含的 内置名称空间 的名字12345678x = 1111111111def foo(): y = 2print(dir(globals()['__builtins__'])) # 返回 全局作用域中 包含的 内置名称空间 的名字# 执行结果['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'] 查看 局部作用域12345678x = 1111111111def foo(): y = 2print(locals() is globals())# 执行结果True 在全局作用范围内查看,locals() 和 gloables() 是一样的全局查看就是 优先查看自己,再查看局部的在全局作用范围查看检索范围就是 全局名称空间,以及内置名称空间123456789x = 1111111111def foo(): y = 2 print(locals())foo()# 执行结果{'y': 2} 打破函数层级限制的解决方案 如何打破函数层级的访问限制?让能够在任意位置都可以访问到一个内部函数123456789def outter(): def inner(): print('from inner') inner()outter()# 执行结果from inner 如何在任何位置都能访问到 inner() 函数?12345678910def outter(): def inner(): print('from inner') return innerf = outter()print(f)# 执行结果<function outter.<locals>.inner at 0x7f2b7b0a0c80> 返回 inner() 的存地址return inner,注意这里 inner 不能加括号然后调用 outter() 函数,将其赋值给 一个 全局名称空间 的名字变量打印这个名字变量,输出 inner() 的内存地址 名字变量 加 括号 执行,就可以访问 内部函数inner() 内的内容了12345678910def outter(): def inner(): print('from inner') return innerf = outter()f()# 执行结果from inner 定义个 全局名称空间 foo() 函数通过 foo() 调用 全局名称空间 的 名字变量 f,也能访问到 内部函数inner() 内的内容了 12345678910111213141516def outter(): def inner(): print('from inner') return innerf = outter()def foo(): print(f) f()foo()# 执行结果<function outter.<locals>.inner at 0x7f8aaa6f6c80>from inner 基于函数对象的概念将一个内部函数返回到全局使用,从而打破了函数的的层级限制 也可以将 内部函数 inner() 返回到 foo() 内 12345678910111213def outter(): def inner(): print('from inner') return innerdef foo(): f = outter() f()foo()# 执行结果from inner 函数的作用域关系是在函数定义阶段就已经固定死的,与函数的调用位置无关即在调用函数时,一定要跑到定义函数的位置寻找作用域关系 示例示例一 注意查看 x 变量的位置1234567891011121314x = 111def outter(): def inner(): print('from inner', x) return innerdef foo(): x = 222 f()foo()# 执行结果from inner 111 示例二 注意查看 x 变量的位置1234567891011121314151617x = 111def outter(): x = 33333 def inner(): print('from inner', x) return innerf = outter()def foo(): x = 222 f()foo()# 执行结果from inner 33333 示例三 注意查看 x 变量的位置1234567891011121314151617x = 111def outter(): def inner(): print('from inner', x) return innerx = 4444f = outter()def foo(): x = 222 f()foo()# 执行结果from inner 4444]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python名称空间与作用域</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 函数嵌套]]></title>
<url>%2F2018%2F08%2F21%2FPython%E5%87%BD%E6%95%B0%E5%B5%8C%E5%A5%97%2F</url>
<content type="text"><![CDATA[Python 函数嵌套 函数的嵌套调用 在调用一个函数时,其内部的代码又调用了其他的函数12345678910def bar(): print('from bar')def foo(): print('from foo')foo()# 执行结果from foofrom bar 求两个数的最大值12345def max2(x, y): if x > y: return x else: return y 求四个数的最大值12345678910def max4(a, b, c, d): res1 = max2(a, b): res2 = max2(res1, c) res3 = max2(res2, d) return res3print(max4(1, 2, 3, 4))# 执行结果4 函数的嵌套定义 在一个函数的内部又定义了另外一个函数123456789def f1(): x = 1 def f2(): print('from f2')print(f1)# 执行结果<function f1 at 0x00000288E1A51E18> 123456789101112def f1(): x = 1 def f2(): print('from f2') print(x) print(f2)f1()# 执行结果1<function f1.<locals>.f2 at 0x0000024BD90F89D8> 12345678910def f1(): x = 1 def f2(): print('from f2') f2()f1()# 执行结果from f2]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 函数嵌套</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 函数对象]]></title>
<url>%2F2018%2F08%2F20%2FPython%E5%87%BD%E6%95%B0%E5%AF%B9%E8%B1%A1%2F</url>
<content type="text"><![CDATA[Python 函数对象 函数是第一类对象,意味着函数可以当做数据去使用 可以被引用123456789101112def foo(): print('from foo')print(foo)func = fooprint(func)func() # 加括号运行# 执行结果<function foo at 0x00000221E334E18><function foo at 0x00000221E334E18>from foo 可以当做参数传给另外一个函数123456789101112def foo(): print('from foo')def bar(x): # x=foo 的内存地址 print(x) x() # 在调用 bar 的时候触发 foobar(foo)# 执行结果<function foo at 0x00000221E334E18>from foo 可以当作函数的返回值123456789101112131415def foo(): print('from foo')def bar(): return foof = bar()print(f)print(f is foo)f()# 执行结果<function foo at 0x00000221E334E18>Truefrom foo 可以当作容器类型的元素12345678910111213def f1(): print('from f1')def f2(): print('from f2')l = [f1, f2]print(l)l[1]()# 执行结果[<function f1 at 0x000001E9A24A89D8>, <function f2 at 0x000001E9A24A8A60>]from f2 示例(基于函数对象思想)12345678910111213141516171819202122232425262728293031323334353637def pay(): print('pay function')def withdraw(): print('withdraw')def auth(): print('auth function')def shopping(): print('shopping function')def transer(): print('transfer function')while True: print(""" 1 支付 2 取款 3 购物 4 转账 5 退出 """) choice = input('请输入您要执行的操作:').strip() if choice == '1': pay() elif choice == '2': withdraw() elif choice == '3': shopping() elif choice == '4': transfer() elif choice == "5": break else: print('输入错误,请重新输入') 示例优化01 1234567891011121314151617181920def pay(): print('pay function')def withdraw(): print('withdraw')def auth(): print('auth function')func_dic = {} '1': pay, '2': withdraw, '3': authprint(func_dic)func_dic['2']()# 执行结果{'1': <function pay at 0x0000029EF7F589D8>, '2': <function withdraw at 0x0000029EF7F58A60>, '3': <function auth at 0x0000029EF7F58AE8>}withdraw function 示例优化02 1234567891011121314151617181920212223242526272829303132333435363738def pay(): print('pay function')def withdraw(): print('withdraw')def auth(): print('auth function')def shopping(): print('shopping function')def transer(): print('transfer function')func_dic = { '1': pay, '2': withdraw, '3': auth, '4': shopping, '5': transfer}while True: print(""" 1 支付 2 取款 3 认证 4 购物 5 转账 """) choice = input('请输入您要执行的操作(按q退出):').strip() # choice = '1' if choice == 'q': break if choice not in func_dic: print('输入错误,请重新输入') continue func_dic[choice]()]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 函数对象</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 函数参数-命名关键字参数]]></title>
<url>%2F2018%2F08%2F20%2FPython%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0-%E5%91%BD%E5%90%8D%E5%85%B3%E9%94%AE%E5%AD%97%E5%8F%82%E6%95%B0%2F</url>
<content type="text"><![CDATA[Python 函数参数-命名关键字参数命名关键字参数 函数参数格式 def foo(位置形参, 默认形参, **args, 命名关键字参数, **kwargs) 12def foo(x, y=1, **args, m, **kwargs): print(x, y, args, m, kwargs) m 为 命名关键字参数 命名关键字参数:凡是在 * 后 ** 之前定义的的参数称之为 命名关键字参数注意: 在调用函数时,传值的形式必须是 key=value 的形式 1234567def foo(x, y, *, m, n): print(x, y, m, n)foo(1, 2, 3, 4)# 执行结果TypeError: foo() takes 2 positional arguments but 4 were given 需要改成 1234567def foo(x, y, *, m, n): print(x, y, m, n)foo(1, 2, n=3, m=4)# 执行结果1 2 4 3 以下 m=1 表示为命名关键字参数指定一个默认值 1234567def foo(x, y, *, m=1, n): print(x, y, m, n)foo(1, 2, n=3) # m=1 命名关键字参数有值了,可以不传值# 执行结果1 2 1 3 命名关键字参数示例123456789def foo(x, y, *args, m=1, n): print(x, y, m, n) print(args)foo(1, 2, 3, 4, 5, n=222, m=11)# 执行结果1 2 11 222(3, 4, 5) 1234567def foo(x, y=1, *args, m, **kwargs): print(x, y, args, m, kwargs)foo(1, 2, 3, 4, 5, 6, m=200, n=300, a=400)# 执行结果1 2 (3, 4, 5, 6) 200 {'n': 300, 'a': 400} 常用函数参数形式12345678def foo(x, y): passdef foo(x, y=1): passdef wrapper(*args, **kwargs): index(*args, **kwargs) 示例:求和函数 12345678910def my_sum(*args): res = 0 for item in args: res += item return resprint(my_sum(1, 2, 3, 5, 6))# 执行结果21]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python 函数参数-命名关键字参数</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python函数参数-可变长参数]]></title>
<url>%2F2018%2F08%2F15%2FPython%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0-%E5%8F%AF%E5%8F%98%E9%95%BF%E5%8F%82%E6%95%B0%2F</url>
<content type="text"><![CDATA[Python函数参数-可变长参数 可变长参数 指的是在调用函数时,传入的实参个数可以不固定实参两种形式: 位置实参 关键字实参所以对应的,形参也必须对应两种解决方案,专门用于接收溢出的位置实参和关键字实参 接收溢出的位置实参 星(*):接收溢出的位置实参,存成元组类型,然后赋值给(*)后边跟的那个变量名 用法一:在形参中使用(*)12345678910111213def foo(x, y, *args): print(x, y) print(z)foo(1, 2, 3, 4, 5) # 3, 4, 5 是溢出的# 执行结果1, 2(3, 4, 5)foo(1, 2, 3, 4, 5, 6, 7, 8, 9) # 3, 4, 5, 6, 7, 8, 9 是溢出的# 执行结果1 2(3, 4, 5, 6, 7, 8, 9) 用法二:在实参中用(*)123456789def foo(x, y, *args): # args = ((3, 4, 5, 6, 7, 8, 9)) print(x, y) print(z)foo(1, 2, (3, 4, 5, 6, 7, 8, 9))# 执行结果1 2(3, 4, 5, 6, 7, 8, 9) 实参中使用星(*),将实参打散成位置实参,再传值给形参 123456789def foo(x, y, *args): # args = (3, 4, 5, 6, 7, 8, 9) print(x, y) print(z)foo(1, 2, *(3, 4, 5, 6, 7, 8, 9)) # foo(1, 2, 3, 4, 5, 6, 7, 8, 9)# 执行结果1 2(3, 4, 5, 6, 7, 8, 9) 123456789def foo(x, y, *args): # args = (3, 4, 5,) print(x, y) print(z)foo(1, 2, *[3, 4, 5]) # foo(1, 2, 3, 4, 5)# 执行结果1 2(3, 4, 5) 123456789def foo(x, y, *args): print(x, y) print(z)foo(1, 2, *'abc') # foo(1, 2, 'a', 'b', 'c')# 执行结果1 2('a', 'b', 'c') 注意以下传参问题:1234567def foo(x, y): print(x, y)foo(1, *(2, 3, 4, 5)) # foo(1, 2, 3, 4, 5)# 执行结果TypeError: foo() takes 2 positional arguments but 5 were given 1234567def foo(x, y): print(x, y)foo(1, (2, 3, 4, 5))# 执行结果1, (2, 3, 4, 5) 1234567def foo(x, y): print(x, y)foo(*(1, 2, 3, 4, 5)) # foo(1, 2, 3, 4, 5)# 执行结果TypeError: foo() takes 2 positional arguments but 5 were given 示例:求和函数,传一堆数字,求这堆数字的和 123456789101112131415161718def my_sum(*args): res = 0 for n in args: res = res + n return resres1 = my_sum(1, 2, 3)res2 = my_sum(1, 2, 3, 4)res3 = my_sum(1, 2, 3, 4, 5)print(res1)print(res2)print(res3)# 执行结果61015 接收溢出的关键字实参 星星(**):接收溢出的关键字实参,存成字典类型,然后赋值给(**)后面跟的那个变量名 用法一:在形参中使用(**)1234567891011def foo(x, y, **kwargs): # kwargs={'a':2, 'b':4, 'c':4} print(x) print(y) print(z)foo(1, a=2, b=3, c=4, y=5)# 执行结果15{'a': 2, 'b': 4, 'c': 4} 用法二:在实参中使用(**)1234567891011def foo(x, y, **kwargs): print(x) print(y) print(z)foo(1, {'a':2, 'c':3, 'b':10, 'y':111})# 执行结果1{'a':2, 'c':3, 'b':10, 'y':111}{} 1 赋值给了 x{‘a’:2, ‘c’:3, ‘b’:10, ‘y’:111} 赋值给了 y没有溢出的关键字参数,所以 **kwargs 为空 1234567891011def foo(x, y, **kwargs): # kwargs={'a':2, 'c':3, 'b':10} print(x) print(y) print(z)foo(1, **{'a':2, 'c':3, 'b':10, 'y':111}) # foo{1, y=111, a=2, b=10, c=3}# 执行结果1111{'a':2, 'c':3, 'b':10} 1 赋值给了 x111 赋值给了 y{‘a’:2, ‘c’:3, ‘b’:10} 为溢出的关键字参数,赋值给了 **kwargs 12345678910def foo(x, y, **kwargs): print(x) print(y) print(z)foo(1, **{'a':2, 'c':3}) # foo(1, c=3, a=2)# 执行结果TypeError: foo() missing 1 required positional argument: 'y'缺少1个所需的位置参数:'y' 示例:把字典里的每个值取出来,分别赋值给 x, y, z 123456789101112def foo(x, y, z): print(x) print(y) print(z)d = {'x': 1, 'y': 2, 'z': 3}foo(d['x'], d['y'], d['z'])# 执行结果123 123456789101112def foo(x, y, z): print(x) print(y) print(z)d = {'x': 1, 'y': 2, 'z': 3}foo(**d) # foo(x=1, y=2, z=3)# 执行结果123 12345678910def foo(x, y, z): print(x) print(y) print(z)d = {'x': 1, 'y': 2, 'z': 3, 'a': 10}foo(**d) # foo(x=1, y=2, z=3, a=10)# 执行结果TypeError: foo() got an unexpected keyword argument: 'a' 12345678910def foo(x, y, z): print(x) print(y) print(z)d = {'x': 1, 'y': 2,}foo(**d) # foo(x=1, y=2)# 执行结果TypeError: foo() missing 1 required positional argument: 'z' 接收任意形式,任意长度的参数123456789def foo(*args, **kwargs): # args=(1, 2, 3) print(x) print(y)foo(1, 2, 3, a=1, b=2, c=3)# 执行结果(1, 2, 3){'a': 1, 'b': 2, 'c': 3} 示例场景 12345678910def index(name, gender): print('welcome %s gender is %s' % (name, gender))def wrapper(*args, **kwargs): # args=(1,2,3) kwargs={'a':1, 'b':2} index(*args, **kwargs) # index(*(1,2,3), **{'a':1,'b':2}) # index(1,2,3,a=1,b=2)wrapper(1, 2, 3, a=1, b=2)# 执行结果TypeError: index() got an unexpected keyword argument 'a' 123456789101112def index(name, gender): print('welcome %s gender is %s' % (name, gender))def wrapper(*args, **kwargs): # args=('zhangsan','male') kwargs={} index(*args, **kwargs) # index(*('zhangsan','male'), **{}) # index('zhangsan','male')# wrapper('zhangsan', 'male')# wrapper(gender='male', name='zhangsan')wrapper('zhangsan', gender='male')# 执行结果welcome zhangsan gender is male]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python函数参数-可变长参数</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python函数参数]]></title>
<url>%2F2018%2F08%2F12%2FPython%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0%2F</url>
<content type="text"><![CDATA[Python函数参数 函数的参数氛围两大类:形参与实参 形参:指的是在定义函数时,括号指定的参数,本质就是“变量名”实参:指的是在调用函数时,括号内传入的值,本质就是值 123456789def foo(x, y): # x=1, y=2 print(x) print(y)foo(1, 2)# 输出结果12 只有在调用函数时才会在函数体内发生实参(值)与形参(变量名)的绑定关系该绑定关系只在调用函数时临时生效,在调用函数结束后就解除绑定 123456789def foo(x, y): # x=1, y=2 print(x) print(y)foo(1.3, {'a': 1})# 输出结果1.3{'a': 1} 在传值的时候没有类型限制 位置参数 位置形参:在定义函数时,按照从左到右的顺序依次定义的形参称之为位置形参位置实参:在调用函数时,按照从左到右的顺序依次传入的值 1234567def foo(x, y, z): print(x, y, z)foo(1, 2, 3)# 输出结果1 2 3 位置形参:在定义函数时,按照从左到右的顺序依次定义的形参称之为位置形参凡是按照位置定义的形参,在调用函数时必须为其传值,多一个不行少一个也不行 12345678def foo(x, y, z): print(x, y, z)foo(1, 2)# 输出结果TypeError: foo() missing 1 required positional argument: 'z'# 缺少 1 个所需的位置参数 1234567def foo(x, y, z): print(x, y, z)foo(1, 2, 3, 4)# 输出结果TypeError: foo() takes 3 positional arguments but 4 were given 位置实参:在调用函数时,按照从左到右的顺序依次传入的值在传值时按照顺序与形参一一对应 1234567def foo(x, y, z): print(x, y, z)foo(3, 2, 1)# 输出结果3 2 1 缺点:基于位置的方式传值,按照顺序与形参一一对应,对应错了,传值就错了 1234567891011def register(name, sex, age): print(name) print(sex) print(age)register('egon', 'male', 18)# 输出结果egonmale18 关键字实参 关键字实参:在调用函数时,按照 key=value 的形式定义的实参,称之为关键字实参在传值时,可以完全打乱顺序,仍然能为指定的参数传值 1234567891011def register(name, sex, age): print(name) print(sex) print(age)register(name='egon', sex='male', age=18)# 输出结果egonmale18 123456789def register(name, sex, age): print(name) print(sex) print(age)register(sex='male', age=18)# 输出结果TypeError: register() missing 1 required positional argument: 'name' 在调用函数时,可以混合使用位置实参和关键字实参但是位置实参必须在关键字实参的左边 123456789def register(name, sex, age): print(name) print(sex) print(age)register(name='egon', 'male', age=18)# 输出结果SyntaxError: positional argument follows keyword argument 不能为同一个形参重复传值 123456789def register(name, sex, age): print(name) print(sex) print(age)register('male', name='egon', age=18)# 输出结果TypeError: register() got multiple value for argument 'name' 1234567891011def register(name, sex, age): print(name) print(sex) print(age)register('egon', age=18, sex='male')# 输出结果egonmale18 默认参数 形参在定义时就已经为其赋值可以传值也可以不传值经常需要变得参数定义成位置形参,变化较小的参数定义成默认参数(形参)注意: 在定义阶段已经赋值,在调用阶段可以不用为其传值 默认参数的定义应该在位置形参右边 默认参数通常应该定义成不可变类型 1234567891011def foo(x, y, z=3): print(x) print(y) print(z)foo(1, 2)# 输出结果123 如需传值,以新的值为准 1234567891011def foo(x, y, z=3): print(x) print(y) print(z)foo(1, 2, 4)# 输出结果124 经常需要变得参数定义成位置形参,变化较小的参数定义成默认参数(形参) 1234567def register(name, sex='female', age): print(name) print(sex) print(age)# 执行结果SyntxError: non-default argument follows default argument 默认参数的定义应该在位置形参右边 1234567891011121314151617181920212223def register(name, age, sex='female'): print(name) print(sex) print(age)register('wxx', 38)register('lxx', 48)register('cxx', 28)register('alex', 73, 'male')# 输出结果wxxfemale38lxxfemale48cxxfemale28alexmale73 默认形参的值只在定义阶段生效一次,在函数定义之后发生的改动无效 12345678910111213m = 10def foo(x, y, z=m): print('x: %s' % x) print('y: %s' % y) print('z: %s' % z)m = 123456foo(1, 2)# 执行结果x: 1y: 2z: 10 默认参数通常应该定义成不可变类型 123456789101112def foo(name, hobby, l=[]): # 这里 l=[] 默认参数为可变类型 l.append(hobby) print('%s 的爱好是 %s' % (name, l))foo('张三', 'read')foo('李四', '吃饭')foo('王五', '睡觉')# 执行结果张三 的爱好是 ['read']李四 的爱好是 ['read', '吃饭']王五 的爱好是 ['read', '吃饭', '睡觉'] 问题:三次调用之间彼此之间有关联,一次调用基于上次的结果继续调用注意:函数定义的时候一定要做到函数的解耦合性 12345678910111213def foo(name, hobby,): l = [] l.append(hobby) print('%s 的爱好是 %s' % (name, l))foo('张三', 'read')foo('李四', '吃饭')foo('王五', '睡觉')# 执行结果张三 的爱好是 ['read']李四 的爱好是 ['吃饭']王五 的爱好是 ['睡觉'] 123456789101112def foo(name, hobby, l=None): if l is None: l = [] l.append(hobby) print('%s 的爱好是 %s' % (name, l))foo('张三', 'read', ['music', 'movie']])foo('李四', '吃饭', ['撸串',])# 执行结果张三 的爱好是 ['music', 'movie', 'read']张三 的爱好是 ['撸串', '吃饭'] 1234567891011121314151617def foo(name, hobby, l=None): if l is None: l = [] l.append(hobby) print('%s 的爱好是 %s' % (name, l))l1 = []foo('张三', 'read', l1)foo('张三', '音乐', l1)foo('张三', '旅行', l1)foo('李四', '吃饭')foo('王五', '睡觉')# 执行结果张三 的爱好是 ['read', '音乐', '旅行']李四 的爱好是 ['吃饭']王五 的爱好是 ['睡觉']]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python函数参数</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack network模块]]></title>
<url>%2F2018%2F08%2F08%2FSaltStack-network%E6%A8%A1%E5%9D%97%2F</url>
<content type="text"><![CDATA[network模块 返回 Minion 主机的网络信息 获取 minion01 主机 ping 域名信息1salt 'minion01' network.ping www.baidu.com 获取 minion 的主机名1salt '*' network.get_hostname 获取指定网络接口的 mac 地址1salt '*' network.hw_addr eth0 获取主机是否在某个子网内 在就返回True,如果不在的话就返回 False,多子网用空格隔开1salt '*' network.in_subnet 192.168.1.0/24 查看 minion 端绑定的 IP 地址 多 IP 也会显示出来,127.0.0.1 除外1salt '*' network.ip_addrs 显示所有接口的详细信息 但是别名的网卡类似于 eth0:1 这种不会显示1salt '*' network.interfaces 显示指定网卡接口上面的 IP 只会显示IP不会显示其他内容1salt '*' network.interface_ip eth0 network.interface 会连网关子网掩码也显示 修改某一个 minion 的主机名 显然这一步操作只适合在初始化的时候而且不适合执行所有主机1salt 'agent1.salt' network.mod_hostname test1.salt 显示的 ping 的结果信息 如果不加 return_boolean=True 显示的是 ping 的结果信息,加了就是如果 ping 通了就返回 True,ping不通就返回 False1salt '*' network.ping www.baidu.com return_boolean=True timeout=3 timeout=3 就是 ping 的时间,3秒超时这样能快速返回结果这个其实挺好用的,比如我们可以测试哪些主机的 DNS 设置有问题不能正常解析啊,或者是我们内网 DNS 指向了一个非公网的域名解析,可以通过这个看哪些主机设置了内网 DNS 而哪些没设置内网 DNS 获取主机所属的子网1salt '*' network.subnets]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack network模块</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack host模块]]></title>
<url>%2F2018%2F08%2F08%2FSaltStack-hosts%E6%A8%A1%E5%9D%97%2F</url>
<content type="text"><![CDATA[hosts 模块 通过这个命令可以查看详细用法1salt '*' sys.doc hosts 我们生产中如果没有用内建 DNS 服务,使用 hosts 模块修改 /etc/hosts 还是经常会用到的 hosts.add_host 追加123salt '*' hosts.add_host 192.168.1.113 minion01# hosts.add 会先判断 /etc/hosts 里面有没有这个 IP 192.168.1.113# 如果有 minion01 会将 alias 名追加到这个 IP 后面,跟之前的解析别名一起使用这个 IP 12salt '*' hosts.add_host 192.168.1.114 minion01# 如果没有这个 IP,则会新创建一行 hosts 记录,但是 192.168.1.113 那条还是存在的。两条记录 hosts.set_host 覆盖123salt '*' hosts.set_host 192.168.1.114 minion02hosts.set_host 这个的不同之处就是,如果这个 IP 不存在则创建新的一条 hosts 记录,如果 hosts 存在这个 IP 的解析记录。会完全覆盖掉也就是旧的 IP 解析记录完全变成现在的结果 hosts.rm_host 删除123salt '*' hosts.rm_host 192.168.1.114 minion02# 删除 192.168.1.114 minion02 这条解析记录# 如果 192.168.1.114 这行有多条解析记录,而只会删除 minion02 这条记录,其他的 192.168.1.114 xxx.xxx,还会存在不会删除,可以说是有选择性的删除 查看 hosts 解析的用法 123# 192.168.1.114 这个 IP 对应的别名解析salt '*' hosts.get_alias 192.168.1.114# 如果有则返回对应的 hosts 别名解析记录,没有则什么都不返回 123# 这个就是返回 minion01 在 /etc/hosts 里面对应的 IPsalt '*' hosts.get_ip minion01# 但是有个问题,如果有多条只会返回文件最上方的那一条对应的 IP 地址 12# 如果有这条别名解析记录,则返回True,如果没有则返回Falsesalt '*' hosts.has_pair 192.168.1.113 minion01 1234# 类似于 cat /etc/hosts 的操作salt '*' hosts.list_hosts# 不同的是,这相当于一个汇总,会以第一行是 IP:下一行是其对应的别名解析的方式来呈现# 比如一个 IP 有好几行的解析,这所有的解析记录都会汇总到这个 IP 下面,注释过的别名解析的行不会出现在这个汇总信息里面 dnsutil 模块 Minion 主机通用 DNS 操作添加 192.168.2.71 www.test.com 到 minion01 端 /etc/hosts 下1salt 'minion01' dnsutil.hosts_append /etc/hosts 192.168.2.71 www.test.com]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack host模块</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack group模块]]></title>
<url>%2F2018%2F08%2F08%2FSaltStack-group%E6%A8%A1%E5%9D%97%2F</url>
<content type="text"><![CDATA[group 模块 添加指定用户组 group.add 方法123[root@salt-master ~]# salt 'salt-minion02' group.add user1 1000salt-minion02: True 返回用户组信息 group.info 方法12345678910[root@salt-master ~]# salt 'salt-minion02' group.info user1salt-minion02: ---------- gid: 1000 members: name: user1 passwd: x 返回所有用户组的信息 group.getent 方法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485[root@salt-master ~]# salt 'salt-minion02' group.getentsalt-minion02: |_ ---------- gid: 0 members: name: root passwd: x |_ ---------- gid: 1 members: - bin - daemon name: bin passwd: x |_ ---------- gid: 2 members: - bin - daemon name: daemon passwd: x |_ ---------- gid: 3 members: - bin - adm name: sys passwd: x |_ ---------- gid: 4 members: - adm - daemon name: adm passwd: x |_ ---------- gid: 5 members: name: tty passwd: x |_ ---------- gid: 6 members: name: disk passwd: x |_ ---------- gid: 7 members: - daemon name: lp passwd: x |_....... 添加一个用户到指定组中 group.adduser 方法必须是一个已经存在的组和已存在的用户123[root@salt-master ~]# salt 'salt-minion02' group.adduser user1 zabbixsalt-minion02: True 将用户从用户组中移除 group.deluser 方法123[root@salt-master ~]# salt 'salt-minion02' group.deluser user1 zabbixsalt-minion02: True 移除指定用户组 group.delete 方法123[root@salt-master ~]# salt 'salt-minion02' group.delete user1salt-minion02: True]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack group模块</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack user模块]]></title>
<url>%2F2018%2F08%2F08%2FSaltStack-user%E6%A8%A1%E5%9D%97%2F</url>
<content type="text"><![CDATA[user模块 用于用户管理,如创建用户,删除用户,更改用户信息等官方文档https://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.useradd.html 在 minion 端上创建一个用户 语法1salt '*' user.add name <uid> <gid> <groups> <home> <shell> 创建用户 创建一个 test 用户,其它都和 useradd 一样默认12345salt * user.add test[root@salt-master ~]# salt 'salt-minion02' user.add test 610 605 zabbix /home/test /bin/bashsalt-minion02: True 获取用户信息 user.info 方法返回用户信息123456789101112131415161718192021[root@salt-master ~]# salt 'salt-minion02' user.info testsalt-minion02: ---------- fullname: gid: 605 groups: - zabbix home: /home/test homephone: name: test passwd: x roomnumber: shell: /bin/bash uid: 610 workphone: 获取所有系统用户信息的列表 user.getent 方法返回所有系统用户信息的列表1234567891011121314151617181920212223242526272829303132333435363738394041424344454647[root@salt-master ~]# salt 'salt-minion02' user.getent salt-minion02: |_ ---------- fullname: root gid: 0 groups: - root home: /root homephone: name: root passwd: x roomnumber: shell: /bin/bash uid: 0 workphone: |_ ---------- fullname: bin gid: 1 groups: - bin - daemon - sys home: /bin homephone: name: bin passwd: x roomnumber: shell: /sbin/nologin uid: 1 workphone: ...... 查看所有用户 查看所有用户1salt * user.list_users 列出指定用户所属组的列表 user.list_groups 方法列出指定用户所属组的列表123[root@salt-master ~]# salt 'salt-minion02' user.list_groups zabbixsalt-minion02.contoso.com: - zabbix 创建用户指定 shell 创建用户时指定 shell1salt * user.add test shell=/sbin/nologin 创建用户时指定不创建家目录 创建用户时指定不创建家目录1salt * user.add test createhome=False 创建用户时指定附加组 创建用户时指定附加组1salt * user.add test groups=nginx 将用户加入到其他组,为附加组 将 test 用户加入到 nginx 组,此为附加组1salt * user.chgroups test nginx 查看用户所有的组 查看 test 用户所有的组1salt * user.list_groups test 删除用户 user.delete 方法删除 test 用户1salt * user.delete test remove=True 在 minion 端删除一个用户123[root@salt-master ~]# salt 'salt-minion02' user.delete testsalt-minion02: True 修改用户名 user.rename 方法123[root@salt-master ~]# salt 'salt-minion02' user.rename test testusersalt-minion02.contoso.com: False 虽然返回 False 但是操作是成功完成了的 编辑 user.sls 文件12345678vim /srv/salt/nginx/create_users.sls# usernginx_user: user.present: # 用户创建 - name: nginx - createhome: False # 不用家目录 - gid_from_name: True - shell: /sbin/nologin # 指定shell]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack user模块</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack archive模块]]></title>
<url>%2F2018%2F08%2F08%2FSaltStack-archive%E6%A8%A1%E5%9D%97%2F</url>
<content type="text"><![CDATA[archive 模块 主要用于打包,压缩和归档使用官方文档https://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.archive.html实现系统层面的压缩包调用,支持 gunzip、gzip、rar、tar、unrar、unzip 等 可以设置为 “jinja” 或另一个受支持的模板引擎,以便在执行之前呈现命令参数1salt '' archive.tar cjvf /tmp/salt.tar.bz2 {{grains.saltpath}} template=jinja 将 /tmp/file_1,/tmp/file_2 打包压缩成 tarfile.tar.bz2 文件 到 /tmp 目录下1salt '' archive.tar cjvf /tmp/tarfile.tar.bz2 /tmp/file_1,/tmp/file_2 解压缩 foo.tar 文件到 /target/directory 目录下1salt '*' archive.tar xf foo.tar dest=/target/directory 执行压缩命令1234567salt '*' archive.tar zcvf /root/test.tar.gz /root/python,/root/testa192.168.10.249:- tar: Removing leading `/' from member names- /root/python/- /root/python/p2- /root/python/p1- /root/testa 通过该方法压缩的文件,解压后带有全路径,可通过 cwd 指定执行的目录123456salt '*' archive.tar zcvf /root/test/test.tar.gz python,testa cwd=/root192.168.10.249:python/python/p2python/p1testa 执行解压缩命令123456salt '*' archive.tar zxvf /root/test/test.tar.gz dest=/root/test192.168.10.249:- python/- python/p2- python/p1- testa 12345678# 采用 gzip 压缩 sourcefile.txt 文件salt '*' archive.gzip sourcefile.txt# 采用 gzip 压缩 test.txt 文件salt 'minion01' archive.gzip test.txt# 采用 gunzip 解压 sourcefile.txt.gz 包salt '*' archive.gunzip sourcefile.txt.gz 12345# 解压在 /root 目录下salt 'minion01' archive.tar xf /tmp/access.tar.gz# 压缩在 /tmp 目录下salt 'minion01' archive.tar /tmp/test.txt 1234567891011121314151617181920# 打包指定文件,多个文件使用空隔分开,打包后的名称为 test.zipsalt minion* archive.cmd_zip /opt/test.zip /tmp/test.sh# 打包目录salt minion* archive.cmd_zip /opt/test.zip /tmp/init# 将 init.zip 解压至 /root 目录下面salt minion* archive.cmd_unzip /opt/init.zip /root/# 将 test.sh 打包为 test.sh.gz,打包后就在当前目录salt minion* archive.gzip /tmp/test.sh# 将 test.sh.gz 解压,解压后就在当前目录salt minion* archive.gunzip /tmp/test.sh.gz# 对 /tmp/init 目录打包后压缩为 tar.gzsalt minion* archive.tar czvf /opt/init.tar.gz /tmp/init# 解压 init.tar.gz,默认放到 /root 目录下面,因为 minion 进程是以 root 用户启动的salt minion* archive.tar xvzf /opt/init.tar.gz api 调用12client.cmd('*', 'archive.gunzip', ['sourcefile.txt.gz'])client.cmd('minion01', 'archive.tar', ['xf', '/tmp/access.tar.gz']) 通过 Python 扩展模块,使用 API通过调用 master client 模块,实例化一个 LocalClient 对象,再调用 cmd() 方法来实现12345678910# API 实现 archive.gzipvim archive_gzip.pyimport salt.clientclient = salt.client.LocalClient()res = client.cmd('*', 'archive.gzip', '/tmp/test.txt')print(res)# 执行结果返回一个字典{'minion01': '/tmp/test.txt.gz'}]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack archive模块</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SaltStack 常用模块]]></title>
<url>%2F2018%2F08%2F07%2FSaltStack%E5%B8%B8%E7%94%A8%E6%A8%A1%E5%9D%97%2F</url>
<content type="text"><![CDATA[SaltStack 常用模块 SaltStack 内置模块汇总1acl, aliases, alternatives, apache, archive, artifactory, blockdev, btrfs, buildout, cloud, cmd, composer, config, container_resource, cp, cron, data, defaults, devmap, dig, disk, django, dnsmasq, dnsutil, drbd, elasticsearch, environ, etcd, event, extfs, file, gem, genesis, git, grains, group, grub, hashutil, hg, hipchat, hosts, http, img, incron, ini, introspect, ip, iptables, jboss7, jboss7_cli, key, kmod, locale, locate, logrotate, lowpkg, match, mine, modjk, mount, network, openstack_config, pagerduty, pillar, pip, pkg, pkg_resource, postfix, publish, puppet, pyenv, raid, random, random_org, rbenv, ret, rsync, runit, rvm, s3, saltutil, schedule, scsi, sdb, seed, selinux, serverdensity_device, service, shadow, slack, smtp, sqlite3, ssh, state, status, supervisord, sys, sysctl, syslog_ng, system, test, timezone, user, vbox_guest, virtualenv, webutil, xfs SaltStack 常用模块介绍这里重点是要将经常用到的模块记录的用法记录下来,直接在 master 端用 salt 命令可以做一些简单的操作,也为做 .sls文件 打基础。 cmd 模块常用方法12salt 'agent1.salt' sys.doc cmd# 可以看 cmd 模块都有哪些使用方法,这里只列举常用的 cmd.has_exec 用法 如果可执行文件在 minion 上可用,则返回true,否则返回false 12# 这里只能是单个命令,如果你用 'ip addr' 这种形式的话,肯定返回的是 Falsesalt '*' cmd.has_exec ifconfig cmd.retcode 用法 在 minion 端执行一个 shell 命令并返回命令的返回码。0 表示成功,0 以外表示失败有问题。123salt '*' cmd.retcode 'ls -l /etc/hostname'# 如我们可以查看一个文件是否存在根据返回码来判断,或者执行一个脚本等,''里面就是你要执行的命令# 正好跟 cmd.has_exec 相反 cmd.run 用法 这个执行 shell 命令跟 cmd.retcode 类似,但是不同的是,cmd.run 就像在本地执行一样。cmd.run_stderr 意思只会在出问题的时候返回信息。 1234567891011121314151617[root@master ~]# salt '*' cmd.retcode 'cat /root/1'zwidc_kvm_192.168.1.104: 0agent1.salt: 0[root@master ~]# salt '*' cmd.run 'cat /root/1'agent1.salt: 123zwidc_kvm_192.168.1.104:[root@master ~]# salt '*' cmd.run_stderr 'cat /root/1'agentl.salt:zwidc_kvm_192.168.1.104:[root@master ~]# salt '*' cmd.run_stderr 'cat /root/6'agent1.salt: cat: /root/6: No such file or directoryzwidc_kvm_192.168.1.104: cat: /root/6: No such file or directory cmd.script 和 cmd.script_retcode 从远程 salt 服务器 或 ftp 服务器 或 http 服务器 下载脚本到本地执行 12salt '*' cmd.script salt://scripts/runme.shsalt '*' cmd.script salt://scripts/runme.sh 'arg1 arg2 "arg 3"' cmd.shell 和 cmd.shells cmd.shell 跟 cmd.run 一样一般用 cmd.run, cmd.shells 是通过 /etc/shells 文件列出此系统上的有效 shell 1234567891011121314[root@master ~]# salt '*' cmd.shellszwidc_kvm_192.168.1.104: - /bin/sh - /bin/bash - /sbin/nologin - /user/bin/sh - /usr/bin/bash - /usr/sbin/nologinagent1.salt: - /bin/sh - /bin/bash - /sbin/nologin - /bin/dash cmd.which 和 cmd.which_bin 用法 就是查找执行文件所在的位置,which 命令嘛都不陌生1234567891011121314151617181920# 查看节点所有 ifconfig 命令的位置,在写脚本或者定时任务的时候很有用salt '*' cmd.which ifconfig# 因为系统不同执行文件的绝对路径也不同 和 salt '*' cmd.run "which ifconfig" 一个效果# 这是定义了一个列表,返回在命令列表中找到的第一个命令salt '*' cmd.which_bin '[cat, ifconfig, touch]'# 存在就返回第一个命令的路径[root@master ~]# salt '*' cmd.which_bin '[cat, ifconfig, touch]'agent1.salt: /bin/catzwidc_kvm_192.168.1.104: /usr/bin/cat# 如果第一个命令不存在就往后寻找[root@master ~]# salt '*' cmd.which_bin '[cat, ifconfig, touch]'zwidc_kvm_192.168.1.104: /usr/bin/ifconfigagent1.salt: /bin/ifconfig cp模块常用方法 只列举一些常用的,详细的可以自行执行此命令查看 1salt 'agent1.salt' sys.doc cp cp.get_dir 和 cp.get_file 用法 就是从 master 端 cp 目录 或 文件 到 minion 端的目录,get_dir 支持与 get_file 相同的模板和 gzip 参数。对应的是 cp.push,cp.push_dir,就是把客户端的文件 或 目录推送到 master 端的 cachedir,默认为 /var/cache/salt/master/minions/minion-id/files,但是这种用法是禁用状态,一般不让 minion 端的文件或目录发送到 master 端,这里只是记录一下有这种用法。 12345678# 从 salt master 递归复制目录到 minion 客户端的 /minion/dest 目录下面salt '*' cp.get_dir salt://path/to/dir/ /minion/dest# 从服务端拷贝单个文件到 minion 端的 /minion/dest 目录下面salt '*' cp.get_file salt://path/to/file /minion/dest# 所有 Salt minions 从与其 os 粒度相同名称的目录下载 vimrc,并将其复制到 /etc/vimrcsalt '*' cp.get_file "salt://{{grains.os}}/vimrc" /etc/vimrc template=jinja cp.get_url 用法 用于从 URL 获取单个文件 1234567891011121314151617181920212223242526272829# 将 salt://cptest1/cptest1file 文件里面的内容写入到 /tmp/test 文件里面,每次都会覆盖里面的内容# 这种就跟 cp.get_file 一样salt '*' cp.get_url salt://cptest1/cptest1file /tmp/test# 如这种就把一个页面的 html 信息写入到了客户端的 /tmp/test 文件,切记只能是这种文本形式的文件,不要是压缩包啥的salt '*' cp.get_url http://blog.51niux.com/?id=116 /tmp/test# 最主要的用法还是这种,我们可以以 httpd 的形式去下载一个 sh 脚本,config 文件等salt '*' cp.get_url http://blog.51niux.com/zb_users/upload/2017/03/201703091489030442220789.txt /tmp/load_one_check.shsalt '*' cmd.run " cat /tmp/load_one_check.sh"# 下面是部分内容,这样我们很多文本类的文件就不用从 salt 服务端发布了,直接做个 ftp 服务 或 http 服务来发布文本类的东西什么的就可以了。当然 cmd.run 命令也可以了...agent1.salt: #!/bin/bash # ======================================================================================== # System loadavg plugin for Nagios # # Written by : chaishao # From : 51niux.com # Release : 1.1.0 # Creation date : 2017-03-08 # Revision date : 2017-03-08 # Description : Nagios plugin (script) to check system load_one . # This script has been designed and written on Linux System. # # USAGE : ./$PROGNAME [-w -c] # # Exemple: : ./$PROGNAME -w n1 -c n2 # ======================================================================================== cp.list_master 和 cp.list_master_dirs 用法 这个就是查看 salt master 本地的 file 服务器又哪些文件 或 目录 12345# 这种就不要指定所有机器了,匹配一台机器就可以了,列出存储在主机上的所有文件salt 'agent1.salt' cp.list_master# 列出存储在 master 主机上面的所有目录salt 'agent1.salt' cp.list_master_dirs 123456789101112# 查看目录下的文件[root@master cptest2]# salt 'agent1.salt' cp.list_masteragent1.salt: - cptest1/cptest1file# 只会将目录列出来以及子目录[root@master cptest2]# salt 'agent1.salt' cp.list_master_dirsagent1.salt: - . - cptest1 - cptest2 - cptest2/cptest2dir file 模块常用方法file.access 用法 f 代表存在rwx 分别代表读、写、执行权限file.file_exists、file.get_mode 和 file.stats 的用法1234567891011121314151617181920212223# 查看 /opt/check.sh 文件是否存在,这个挺有用的salt '*' file.access /opt/check.sh f# 我们查看某个脚本或者某个文件是否存在# 上面的例子也可以写成这种salt '*' file.file_exists /opt/check.sh# 文件存在就返回True,否则返回False# 如果有此脚本之后,我们还可以查看此脚本是否具有执行权限salt '*' file.access /opt/check.sh x# f、r、w、x 只能写一种,真就返回true,否则falsesalt '*' file.get_mode /etc/passwd# file.get_mode 后面指定目录或者文件,可以查看其授权情况,如文件一般是0644,如果文件或目录不存在无信息# 还有# file.is_blkdev 检查文件是否存在并且是块设备# file.is_chrdev 检查文件是否存在并且是字符设备# file.is_fifo 检查文件是否存在并且是FIFO# file.is_link 检查路径是否是符号链接)# file.stats 返回一个文件 或 目录的统计信息salt '*' file.stats /etc/passwd# 这里是返回 /etc/passwd 文件的统计信息(类型,时间,属组,权限等) file.append 和 file.write 用法 前者将内容追加到文件的末尾,后者是直接覆盖类似于 echo >,但是格式跟前者一样 12345678910111213# 第一组:单引号 和 双引号 的区别,还有 ! 需要注意的地方salt '*' file.append /tmp/1 "`hostname` This is a good day\!"# 用双引号,就是里面可以接变量,但是这个 ! 需要注意,不用 \ 转义的话会报错# salt '*' file.append /tmp/1 '`hostname` This is a good day!'# 所以如果出现 ! 最好放到单引号里面来引用,因为上面就算转义了也显示的不对# 第二组:换行符的使用salt '*' file.append /tmp/1 "Two""Two Two"# 这表示两组字符串在一行,中间默认加个空格隔开# 这表示两组字符串是换行的salt '*' file.append /tmp/1 "Two" "Two Two" 1234567[root@activemq ~]# cat /tmp/1master.hadoop This is a good day\!'hostname' This is a good day!TwoTwo TwoTwoTwo Two[root@activemq ~]# 上面两组测试结果,可以明显的比较出差别 1234567891011121314151617# 第三组:! 是不知道怎么解决了,要么就放单引号# 还有个 = 是需要注意的,有个 args 用法,以及 [] 外面加不加双引号的区别# 如果字符串里面有等号要用这种 args 的用法salt '*' file.append /tmp/1 args='Hostname=`hostname`'# args 用 单引号 和 双引号 的区别就在于里面的变量是否解析为变量的值还是字符串salt '*' file.append /tmp/1 args="Hostname=`hostname`"# 这里就是定义一个并排的两个字符串salt '*' file.append /tmp/1 args=['Hostname=`hostname`''rel=`cat /etc/redhat-release`']# 可见 args 默认是单引号的形式,两组字符串中间加逗号salt '*' file.append /tmp/1 args=['Hostname=`hostname`','rel=`cat /etc/redhat-release`']# 双引号 不是默认的,所以要单独的加上salt '*' file.append /tmp/1 args="['Hostname=`hostname`','rel=`cat /etc/redhat-release`']" 12345678[root@activemq !]# cat /tmp/1Hostname= hostnameHostname=master.hadoopHostname='hostname'rel='cat /etc/redhat-release'Hostname='hostname'rel='cat /etc/redhat-release'Hostname=master.hadooprel=CentOS release 6.4 (Final) 从结果我们可以看出,两个字符串之间如果没有逗号的话,不换行,但是中间也没有分隔,可以学习 awk,在两个字符串中间加 “ “ 来进行添加空格的操作。其实主要还是字符串里面有=就用 args 的形式,如果是多组字符串可以用[]的形式,如果要是用变量就用双引号的形式,默认是单引号的形式。 file.chgrp、file.chown 和 file.set_mode 用法 前者是更改文件的属组,中者是更改文件数的属主属组,后者是更改文件或目录的权限 1234567891011# 将 minion 端的 /tmp/1 文件更改用户组为 test9salt '*' file.chgrp /tmp/1 test9# 如果客户端有此用户组则返回 None,没有此用户组则返回用户组不存在# 第一个是用户,第二个是用户组,固定格式必须存在salt '*' file.chown /tmp/1 test7 test9# 将 /tmp/1 的用户组设置为 test7,用户组设置为 test9# 设置 /opt/cs 目录权限为 0550salt '*' file.set_mode /opt/cs 0550# 如果授权成功会显示授权后的权限,如果没有此文件或目录会提示 file.comment 和 file.comment_line 用法 注释指定内容的行,每次操作前都会更新文件名命令的 .bak 备份文件12345678910# 这就是将以 /tmp/passwd 文件以 ftp 开头的行注释掉salt '*' file.comment /tmp/passwd ftp# 如果注释会显示注释行的信息,如果没注释则返回False# 另外还支持正则表达式,这里就表示以 ftp 开头以 nologin 结尾的行,多行注释salt '*' file.comment /tmp/passwd ftp.*nologin$# 后面可以指定在行开头加什么字符,当然默认是 #salt '*' file.comment /tmp/passwd ftp.*nologin$ '-'# 前面也可以这样后面指定要行头添加的字符 file.copy 用法 复制文件 或 目录到指定的目录下面,成功返回True,失败会有提示的另外还有 file.move,移动文件的用法 12345678910111213141516# 这里是文件拷贝,将文件 /path/to/src 拷贝到 /path/to/ 目录下面,其名称为 dstsalt '*' file.copy /path/to/src /path/to/dst# 切记这里一定要是文件名# 目录复制的区别看下面的例子# 复制目录的话,要加 recurse=True 递归标记salt '*' file.copy /opt/file2 /tmp/haha/ recurse=True# 这种不管是 /tmp/haha/ 还是 /tmp/haha,如果这个 haha 存在的话,就是将 /opt/file2 下面的内容 cp -r 拷贝到 /tmp/haha 目录下面,如果 haha 目录不存在的话,就是将 /opt/file2 目录变为 /tmp/haha 目录# 这种才是正确的将 /opt/file2 目录复制到 /tmp/haha/ 目录下方salt '*' file.copy /opt/file2 /tmp/haha/file2 recurse=True# 如 haha 目录不存在会创建# remove_existing=True 这种就是完全覆盖的形式salt '*' file.copy /path/to/src_dir /path/to/dst_dir recurse=True remove_existing=True file.directory_exists 和 file.dirname 用法 前者检查一个目录是否存在,后者取文件的路径 12345678# /tmp/haha/file 目录存在就会返回True,不存在就会返回Falsesalt '*' file.directory_exists /tmp/haha/file2# 取出来的结果是 /opt/file2,这就是末尾加 /,认为这两个都是目录,当然不管是否有这个目录salt '*' file.dirname '/opt/file2/'# 取出来的结果是 /opt/file2salt '*' file.dirname '/opt/file2/test1' file.find 用法 类似于 Linux下面的 find 命令 123456789101112131415161718192021222324252627282930313233343536salt '*' file.find / type=f name=\*.bak size=+10m# 查找/目录下,文件类型为文件的# a:所有文件类型# b:块设备# c:字符设备# d:目录# p:FIFO(命名管道)# f:普通文件# l:符号链接# s:套接字,名称为 .bak 结尾的(这里支持正则表达式),大小大于 10MB 的文件# b:字节# k:千字节# m:兆字节# g:GB# t:太字节也是TBsalt '*' file.find /var mtime=+30d size=+10m print=path,size,mtime# 这里是查找 /var 目录下,最后一次更改时间是 30 天以前# w:周# d:天# h:小时# m:分钟# s:秒,大小大于 10MB 的文件,并打印文件的路径,大小,更改时间# 可打印的内容有# group:组名# md5:文件内容的 MD5 摘要# mode:文件权限(以整数形式)# mtime:最后修改时间# name:文件基础名称# path:文件绝对路径# size:文件大小(以字节为单位)# type:文件类型# user:用户名salt '*' file.find /var/log name=\*.[0-9] mtime=+30d size=+10m delete# find 的匹配条件有(name区分大小写,iname不区分大小写,type类型,user用户,group用户组,size[+-]大小,mtime修改时间,grep搜索文件内容),最后执行的动作除了 delete 和 print,还有 exec command file.get_gid、file.get_uid 和 file.get_group、file.get_user 用法 前一组返回文件或目录的 gid号 和 uid号,后一组返回文件或目录 group 和 user 1234567# 查看 /etc 目录的属组salt '*' file.get_user /etc# 如果文件或目录不存在返回 false# 查看 /etc 目录的属组的 uid 号salt '*' file.get_uid /etc# 如果目录或者文件不存在返回 -1 file.grep 用法 类似于 Linux上面的 grep 命令 12345678910111213salt '*' file.grep /etc/passwd nobody# 过滤 /ect/passwd 文件中包含 nobody 的行# 输出:pid: 是 grep 运行的 pid 号# retcode: 为状态码# 0 是成功过滤 1 为非成功过滤# stderr: 错误输出# stdout: 正常输出也就是我们要过滤的内容# "-i" 的目的是不区分大小写,注意 -i 前面有空格,额外的参数之间都有空格salt '*' file.grep /etc/sysconfig/network-scripts/ifcfg-eth0 ipaddr " -i" # -B2 就是连上面两行也过滤出来,-A2 就是连下两行也过滤出来salt '*' file.grep /etc/sysconfig/network-scripts/ifcfg-eth0 ipaddr " -i -B2 -A2" file.link 和 file.symlink 用法 前者是创建文件的硬链接,后者是创建符号链接也就是软链接 12345# 为 /tmp/1 创建一个硬链接是 /tmp/2,只能是文件salt '*' file.link /tmp/1 /tmp/2# 为 /tmp/haha 目录创建一个软链接 /tmp/buhaha,成功返回 Ture,失败有提示信息salt '*' file.symlink /tmp/haha /tmp/buhaha file.mkdir 和 file.makedirs 用法 两种都是创建目录,前者对结尾的/不敏感,后者对/敏感 1234567# 这就是在 /opt/ 目录下面创建 cs 目录,并在 cs 目录下面创建 ds 目录salt '*' file.mkdir /opt/cs/ds# 如果 cs 目录不存在就创建。目录存不存在也不会有提示# 这里只会创建 /opt/cs 目录,首先如果 /opt 要创建的目录是存在的会有提示salt '*' file.makedirs /opt/cs/ds# /opt/cs/ds/ 才会在 cs 目录下面创建 ds 目录 file.remove、file.rmdir 和 file.rename 用法 前者是删除文件或者目录,中间是删除目录但是目录一定要为空、后者是重命名文件或目录 123456789# 删除 /opt 目录下面的 cs 目录salt '*' file.remove /opt/cs/# 删除 /opt/cs 目录salt '*' file.rmdir /opt/cs# 如果 cs 目录下面有内容会提示目录不会空删除失败,如果为空则会执行并返回 True# 更改 /opt/cs 目录下的 ds 目录为 dss 目录salt '*' file.rename /opt/cs/ds /opt/cs/dss file.touch 和 file.truncate 的用法12345# 文件不存在则创建此文件,如果文件存在里面的内容不会发生变化,但是它的 time 信息会更新,上级目录必须存在salt '*' file.touch /tmp/test1# 将 /tmp/passwd 第三个字段以后的内容全删除掉了,就剩下了 roo 三个字段salt '*' file.truncate /tmp/passwd 3 hosts模块常用方法12# 通过这个命令可以查看详细用法,如果没有用 内建DNS服务,使用 hosts 模块修改 /etc/hosts 还是经常会用到的salt '*' sys.doc hosts hosts.add_host、hosts.rm_host 和 hosts.set_host 用法 前者是追加,中着是删除、后者是覆盖 123456789101112131415# hosts.add 会先判断 /etc/hosts 里面有没有这个 IP 192.168.1.113,如果有 foreman.puppet 会将 alias 名追加到这个 IP 后面,跟之前的解析别名一起使用这个 IPsalt '*' hosts.add_host 192.168.1.113 foreman.puppet# 如果没有这个IP,则会新创建一行 hosts 记录,但是 192.168.1.113 那条还是存在的,两条记录salt '*' hosts.add_host 192.168.1.114 foreman.puppet# hosts.set_host 这个的不同之处就是# 如果这个 IP 不存在则创建新的一条 hosts 记录# 如果 hosts 存在这个 IP 的解析记录,会完全覆盖掉,也就是旧的 IP 解析记录完全变成现在的结果salt '*' hosts.set_host 192.168.1.114 test.hahahah# 删除 192.168.1.114 test.haha 这条解析记录salt '*' hosts.rm_host 192.168.1.114 test.haha# 如果 192.168.1.114 这行有多条解析记录,而只会删除 test.haha 这条记录# 其他的 192.168.1.114 xxx.xxx,还会存在不会删除,可以说是有选择性的删除 其他查看 hosts 解析的用法12345678910111213# 192.168.1.114 这个 IP 对应的别名解析,如果有则返回对应的 hosts 别名解析记录,没有则什么都不返回salt '*' hosts.get_alias 192.168.1.114# 这个就是返回 wo.haha 在 /etc/hosts 里面对应的IP# 但是有个问题,如果有多条只会返回文件最上方的那一条对应的 IP 地址salt '*' hosts.get_ip wo.haha# 如果有这条别名解析记录,则返回True,如果没有则返回Falsesalt '*' hosts.has_pair 192.168.1.113 foreman.puppet# 类似于 cat /etc/hosts 的操作salt '*' hosts.list_hosts# 但是不同的是,这相当于一个汇总,会以第一行是IP:下一行是其对应的别名解析的方式来呈现,比如一个 IP 有好几行的解析,这所有的解析记录都会汇总到这个 IP 下面,注释过的别名解析的行不会出现在这个汇总信息里面 cron模块常用方法cron.raw_cron 用法 cron.list_tab 和 cron.ls 和跟其效果一样,格式也一样必须要指定某一个用户,都是显示指定用户 crontab 文件里面的定时任务 1salt '*' cron.raw_cron root #必须指定用户,这里是显示root的crontab文件里面的内容,注释的行也会显示 cron.set_job 用法 为指定用户设置一个定时任务 123salt '*' cron.set_job root '0' '0' '*' '*' '*' '/bin/bash /opt/scripts/scp.sh > /dev/null 2>&1'# 如果 '/bin/bash /opt/scripts/scp.sh > /dev/null 2>&1'# 这一部分存在了,那么这一步操作就是 update,也就是更新前面执行 crontab 的时间,如果不存在,这就相当于一条添加定时任务的操作返回内容为 new cron.rm_job 用法 删除指定用户指定的的定时任务 12# 注意格式是用户 后面跟要删除的任务,不要加前面的时间,成功会返回 removed,如果没有这条记录会返回 absentsalt '*' cron.rm_job root '/bin/bash /opt/scripts/scp.sh > /dev/null 2>&1' network 模块常用方法1234567891011121314151617181920212223242526272829# 返回minion的主机名salt '*' network.get_hostname# 返回指定网络接口的mac地址salt '*' network.hw_addr eth0# 查看主机在某个子网内就返回True,如果不在的话就返回False,多子网用空格隔开salt '*' network.in_subnet 192.168.1.0/24# 查看 minion 端绑定的 IP 地址,多 IP 也会显示出来,127.0.0.1除外salt '*' network.ip_addrs# 会显示所有接口的详细信息,但是别名的网卡类似于 eth0:1 这种不会显示salt '*' network.interfaces# 显示指定网卡接口上面的 IP,只会显示 IP 不会显示其他内容# network.interface 会连网关子网掩码也显示salt '*' network.interface_ip eth0# 修改某一个minion的主机名,显然这一步操作只适合在初始化的时候而且不适合执行所有主机salt 'agent1.salt' network.mod_hostname test1.saltsalt '*' network.ping www.baidu.com return_boolean=True timeout=3# 如果不加 return_boolean=True 显示的是 ping 的结果信息,加了就是如果 ping 通了就返回True,ping 不通就返回 False# timeout=3 就是 ping 的时间,3秒超时这样能快速返回结果# 这个其实挺好用的,比如我们可以测试哪些主机的 DNS 设置有问题不能正常解析啊,或是我们内网 DNS 指向了一个非公网的域名解析,可以通过这个看哪些主机设置了内网 DNS 而哪些没设置内网 DNS# 返回主机所属的子网salt '*' network.subnets sys模块常用方法sys.argspec 用法 返回 Salt 执行模块中函数的参数说明。对于我们后期写 .sls文件 很有帮助 12345# 查看 pkg.install 函数的参数说明salt '*' sys.argspec pkg.install# 查看sys模块里面所有函数的规则说明,或者#salt '*' sys.argspec 'sys.*'salt '*' sys.argspec sys sys.doc 用法 显示模块下函数的使用文档信息类似于man帮助,前面已介绍过,多模块或者多函数之间用空格隔开 sys.list_functions 和 sys.list_modules 用法 前者就是列出所有模块下面的函数,多模块也是用空格隔开。后者是将所有模块列出来 12345678# 可以用这种方法将所有 sys.list 开头的函数列出来salt '*' sys.list_functions 'sys.list_*'# 列出所有的模块salt '*' sys.list_modules# 列出所有以 s 开头的模块salt '*' sys.list_modules 's*' service 模块常用方法1234567891011121314151617181920212223242526272829303132333435# 查看某个命令的服务是否可用,这里是查看sshd服务是否可用,可用返回True,不可用返回Falsesalt '*' service.available sshd# 禁止某个服务开机启动,这里是禁止 postfix 服务开机启动salt '*' service.disable postfix# 查看某个服务是否已经开机不启动,这里是以postfix服务为例,是返回True,否则返回Falsesalt '*' service.disabled postfix# 设置某个服务开机启动,这里以 postfix 为例salt '*' service.enable postfix# 查看某个服务是否开机启动,这里以 postfix 服务为例salt '*' service.enabled postfix# 查看所有的服务项salt '*' service.get_all# 查看所有开机启动的服务salt '*' service.get_enabled# 重新加载指定名称的服务salt '*' service.reload <service name># 重新启动指定名称的服务salt '*' service.restart <service name># 启动指定名称的服务salt '*' service.start <service name># 查看指定服务的状态,启动状态是True,关闭状态是Falsesalt '*' service.status <service name># 关闭指定名称的服务salt '*' service.stop <service name> pkg 模块常用方法pkg.install 用法 安装传递的包,在安装包之前,添加 refresh=True 来清理 yum 数据库 1234567891011121314151617181920# 参数介绍name # 要安装的软件包的名称。如果传递了 "pkgs" 或 "sources" 此参数则会被忽略# 如这就相当于在 minion 端执行 yum -y install httpd 操作salt '*' pkg.install httpd# 如果是第一个 yum 的话,还是可以 refresh 参数,相当于 yum clean all 操作salt '*' pkg.install httpd refresh=Trueskip_verify # 跳过 GPG 验证检查version # 安装包的特定版本fromrepo # 指定从哪个 repo 库来安装软件pkgs # 指定多个软件包,一定是要以列表传递salt '*' pkg.install pkgs='["foo", "bar"]'salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3-4.el5"}]'sources # 要安装的RPM软件包列表,其中的键是包名称,值作为包的源URI或本地路径salt '*' pkg.install sources='[{"foo": "salt://foo.rpm"}, {"bar": "salt://bar.rpm"}]' pkg.latest_version 用法 更新软件包至最新版本 12345678# 更新指定的软件包salt '*' pkg.latest_version <package name># 指定 repo 源来更新软件包salt '*' pkg.latest_version <package name> fromrepo=epel-testing# 多个要更新的软件之间用空格隔开salt '*' pkg.latest_version <package1> <package2> <package3> ... pkg.remove 用法 删除软件的操作 12345678# 卸载指定的软件salt '*' pkg.remove <package name># 多软件可以用空格隔开salt '*' pkg.remove <package1>,<package2>,<package3># 也可以用 pkgs 使用 python 列表的形式salt '*' pkg.remove pkgs='["foo", "bar"]' salt ‘*’ pkg.version 用法 查看软件的版本 12345# 查看指定软件的版本号salt '*' pkg.version <package name># 查看多软件版本号salt '*' pkg.version <package1> <package2> <package3> ... 参考文档http://blog.51niux.com/?id=116]]></content>
<categories>
<category>SaltStack笔记</category>
</categories>
<tags>
<tag>SaltStack 常用模块</tag>
</tags>
</entry>
<entry>
<title><![CDATA[持续集成CI持续交付CD]]></title>
<url>%2F2018%2F08%2F06%2F%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90CI%E6%8C%81%E7%BB%AD%E4%BA%A4%E4%BB%98CD%2F</url>
<content type="text"><![CDATA[持续集成CI/持续交付CD CI 很容易理解,就是持续集成。但是 CD 既可以指代码持续交付,也可理解为代码持续部署。CI 和 CD 之间有很多相似的部分,但是也有很大的区别。 持续集成 (Continuous Integration)在持续集成环境中,开发人员将会频繁的提交代码到主干。这些新提交在最终合并到主线之前,都需要通过编译和自动化测试流进行验证。这样做是基于之前持续集成过程中很重视自动化测试验证结果,以保障所有的提交在合并主线之后的质量问题,对可能出现的一些问题进行预警。 持续交付 (Continuous Delivery)持续交付就是讲我们的应用发布出去的过程。这个过程可以确保我们尽可能快的实现交付。这就意味着除了自动化测试,我们还需要有自动化的发布流,以及通过一个按键就可以随时随地实现应用的部署上线。通过持续交付,可以决定每天,每周,每两周发布一次,这完全可以根据自己的业务进行设置。但是,如果您真的希望体验持续交付的优势,就需要先进行小批量发布,尽快部署到生产线,以便在出现问题时方便进行故障排除。 持续部署 (Continuous Deployment)如果我们想更加深入一步的话,就是持续部署了。通过这个方式,任何修改通过了所有已有的工作流就会直接和客户见面。没有人为干预(没有一键部署按钮),只有当一个修改在工作流中构建失败才能阻止它部署到产品线。持续部署是一个很优秀的方式,可以加速与客户的反馈循环,但是会给团队带来压力,因为不再有“发布日”了。开发人员可以专注于构建软件,他们看到他们的修改在他们完成工作后几分钟就上线了。基本上,当开发人员在主分支中合并一个提交时,这个分支将被构建、测试,如果一切顺利,则部署到生产环境中。 合并 CI CD and CD?当然,正如我所说,他们每部分都更加接近生产环境。你可以构建自己的持续集成环境,然后,一旦团队适应,你可以添加持续交付流,最后,可以添加持续部署流到整个工作流中。 举例 CI,CD 和 CD 流水线 到底值不值这样做? 持续集成 需要具备哪些条件? 你的团队需要为每个新功能,代码改进,或者问题修复创建自动化测试用例。你需要一个持续集成服务器,它可以监控代码提交情况,对每个新的提交进行自动化测试。研发团队需要尽可能快的提交代码,至少每天一次提交。 可以获得什么? 通过自动化测试可以提早拿到回归测试的结果,避免将一些问题提交到交付生产中发布编译将会更加容易,因为合并之初已经将所有问题都规避了减少工作问题切换,研发可以很快获得构建失败的消息,在开始下一个任务之前就可以很快解决。测试成本大幅降低-你的 CI 服务器可以在几秒钟之内运行上百条测试。你的 QA 团队花费在测试上面的时间会大幅缩短,将会更加侧重于质量文化的提升上面。 持续交付 需要具备的条件 你需要有强大的持续集成组件和足够多的测试项可以满足你代码的需求部署需要自动化。触发是手动的,但是部署一旦开始,就不能人为干预。你的团队可能需要接受特性开关,没有完成的功能模块不会影响到线上产品。 可以获得什么? 繁琐的部署工作没有了。你的团队不在需要花费几天的时间去准备一个发布。你可以更快的进行交付,这样就加快了与客户之间的反馈环。轻松应对小变更,加速迭代 持续部署 需要具备的条件 研发团队测试理念比较完善。测试单元的健壮性直接决定你的交付质量。你的文档和部署频率要保持一致。特征标志成为发布重大变化过程的固有部分,以确保您可以与其他部门(支持,市场营销,公关…)协调。 可以获得什么? 发布频率更快,因为你不需要停下来等待发布。每一处提交都会自动触发发布流。在小批量发布的时候,风险降低了,发现问题也可以很轻松的修复。客户每天都可以看到我们的持续改进和提升,而不是每个月或者每季度,或者每年。如前所述,您可以采用持续集成,持续交付和持续部署。你怎么做取决于你的需求和你的业务情况。如果你刚刚开始一个项目,并且还没有客户,那么你就可以去创建这些工作流,最好是将这三个方面都实现,并且在你的项目迭代和需求增长中同时迭代它们。如果您已经有一个生产项目,那么您可以一步一步地分阶段去实现他们。 参考文档https://www.sohu.com/a/204652724_640923]]></content>
<categories>
<category>CI/CD笔记</category>
</categories>
<tags>
<tag>持续集成CI持续交付CD</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python函数基础]]></title>
<url>%2F2018%2F08%2F03%2FPython%E5%87%BD%E6%95%B0%E5%9F%BA%E7%A1%80%2F</url>
<content type="text"><![CDATA[Python函数基础 什么是函数? 函数就是具备某一功能的工具 函数的使用必须遵循先定义,后调用的原则 实现准备工具的过程即函数的定义 拿来就用即函数的调用 函数氛围两大类 内置函数 自定义函数 为什么要用函数? 不用函数的问题 代码的组织结构不清晰,可读性差 遇到重复的功能只能重复编写实现代码,代码冗余过多 程序的扩展性差,功能需要扩展时,需要找出所有实现该功能的地方修改之,无法统一管理且维护难度极大 怎么用函数? 定义函数语法12345678910def 函数名(参数1,参数2,参数3,...): """ 文档注释 """ 函数体 code1 code2 code3 ...... return 返回值 自定义函数的三种形式无参函数1234567891011def func1(): print('hello1') print('hello2') print('hello3')func1()# 输出结果hello1hello2hello3 有参函数1234567891011121314def func2(x, y): if x > y: print(x) else: print(y)func2(1, 3)func2(2, 3)func2(2, 4)# 输出结果334 空函数 用于预设程序的框架 12345678910def get(): # 下载功能 passdef put(): # 上传功能 passdef auth(): # 认证功能 passdef ls(): # 浏览功能 passdef cd(): # 切换目录的功能 pass 定义函数阶段 相当于定义了一个名字,名字指向了一个值,值是内存地址,该地址包含了函数体代码定义函数阶段,只检测语法,不执行函数体代码 123456789101112131415def foo(): # foo = 函数的内存地址 print('first') print('second') print('third')print(foo) # 打印输出函数值内存地址# 输出结果<function for at 0x000001E71A588950>foo() # 函数调用阶段# 输出结果firstsecondthird 测试一123456789101112def bar(): print('from bar')def foo(): print('from foo') bar()foo()# 输出结果from foofrom bar 测试二123456789101112def foo(): print('from foo') bar()def bar(): print('from bar')foo() # 调用阶段# 输出结果from foofrom bar 调用函数语法1函数名() 调用函数过程 根据函数名找到函数的内存地址函数的内存地址加括号可以触发函数体代码的运行 调用函数的三种形式无参函数1234567def f1(): print('from f1')f1()# 输出结果from f1 有参函数1234567891011def max(x, y): if x > y: return x else: return yres = max(1, 2)print(res)# 输出结果2 1234567891011def max(x, y): if x > y: return x else: return yres = max(1, 2)*10print(res)# 输出结果20 当作参数传给其他函数 1234567891011def max(x, y): if x > y: return x else: return yres = max(max(1, 2), 3)print(res)# 输出结果3 函数的返回值什么是返回值?函数的返回值是函数体代码运行的一个结果 什么时候使用返回值?如何使用? return 返回值: 返回值没有类型限制 返回值没有个数限制逗号分隔多个值,返回一个元组一个值,返回值本身没有return,默认返回None 12345678def f1(): return [1, 2,]res = f1()print(res)# 输出结果[1, 2] 逗号分隔多个值,返回一个元组12345678def f1(): return 1, 2, [1, 2, 3]res = f1()print(res)# 输出结果(1, 2, [1, 2, 3]) 没有return,默认返回None12345678def f1(): passres = f1()print(res)# 输出结果None return 是函数结束的标志函数内可以有多个return,但只要执行一次,整个函数就立即结束,并且将return后的值当作本次调用的结果返回123456789101112def f1(): print(1) return 'first' print(2) return 'second' print(3) return 'third'f1()# 输出结果1 总结 函数使用的原则:先定义,再调用 函数名即“变量名”,“变量名”必须先定义后引用。未定义而直接引用函数,就相当于在引用一个不存在的变量名 我们在使用函数时,一定要明确地区分定义阶段和调用阶段 函数在定义阶段都做了哪些事? 只检测语法,不执行代码 也就说,语法错误在函数定义阶段就会检测出来,而代码的逻辑错误只有在执行时才会知道 函数名:是用来访问到函数的内存地址,拿到函数的内存地址加括号就可以触发函数体代码函数参数:是外部调用者为函数体传值的媒介函数体代码:是函数功能的具体实现 return 函数返回值:函数的返回值是函数体执行的成果 返回值没有类型限制 返回值没有个数限制 没有return,默认返回None return 值1:返回值1 return 值1, 值2, 值3:返回(值1, 值2, 值3) return 注意点: return 是函数结束运行的标志,函数体内可以都有多个 return 但是只有执行一次,整个函数就终止运行]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python函数基础</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python修改文件内容]]></title>
<url>%2F2018%2F08%2F01%2FPython%E4%BF%AE%E6%94%B9%E6%96%87%E4%BB%B6%E5%86%85%E5%AE%B9%2F</url>
<content type="text"><![CDATA[Python修改文件内容 想要修改文件中的内容,没有办法直接实现。硬盘上的数据都是一圈一圈写的,没有修改一说,都是新数据覆盖老数据 12vim db.txt你好哈哈哈 预期 在 你好 后面插入 上上上 1234567with open('db.txt' mode='r+t', encoding='utf-8') as f: f.seek(6, 0) # 移动了6个bytes f.write('上上上')# 执行结果cat db.txt你好上上上 使用 seek() 文件指针偏移 会覆盖其他内容,不好掌控 方法一 先将文件内容全部读入内存 在内存中修改完毕 将修改的结果覆盖写回硬盘中 12vim db.txtHello World! 优点:在修改期间硬盘上同一时刻只有一份数据缺点:占用内存过高 1234567891011with open('db.txt', mode='rt', encoding='utf-8') as f: data = f.read() new_data = data.replace('World', 'Python') print(new_data)with open('db.txt', mode='wt', encoding='utf-8') as f: f.write(new_data)# 执行结果cat db.txtHello Python! 方法二(推荐使用) 一行一行的读,一行一行的改 以读的模式打开源文件,以写的模式打开一个临时文件(解决占用内存的问题) 然后用 for 循环读取原文件一行行内容,每读一行则修改一行,将修改的内容写入临时文件,直到把源文件都遍历完, 删除原文件,将临时文件重命名为原文件名 1234567891011import oswith open('db.txt', mode='rt', encoding='utf-8') as src_f, \ open('.db.txt.swap', mode='wt', encoding='utf-8') as temp_f: for line in src_f: if 'World' in line: line = line.replace('World', 'Python') temp_f.write(line)os.remove('db.txt')os.rename('.db.txt.swap', 'db.txt') 优点:同一时刻在内存中只存在文件的一行内容缺点:在修改期间,硬盘上同一份数据会保存两份]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python修改文件内容</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python字典操作]]></title>
<url>%2F2018%2F07%2F30%2FPython%E5%AD%97%E5%85%B8%E6%93%8D%E4%BD%9C%2F</url>
<content type="text"><![CDATA[Python字典操作字典(dict),是一系列放在 {} 的键值对(key-value)。可以使用键来访问对应的值,与键对应的值可以是 数字、字符串、列表、字典字典具有极快的查找速度。 下边是一个与手机信息相关的 dict1{'os': 'Android', 'soc': 'Qualcomm', 'screen': 'Samsung'} 使用 dict()方法 创建字典1d = dict(os= 'Android', soc='Qualcomm', screen='Samsung') 使用可迭代对象(列表、元组等)也可以创建字典123456>>> d = dict((['x', 1], ['y', 2], ['z', 3]))>>> d{'x': 1, 'y': 2, 'z': 3}>>> d = dict(zip(('x', 'y', 'z'), (1, 2, 3)))>>> d{'x': 1, 'y': 2, 'z': 3} 使用 fromkeys()内建方法 创建默认字典12>>> {}.fromkeys(('x', 'y', 'z'), -1){'x': -1, 'y': -1, 'z': -1} 访问字典中的值123456789101112131415>>> m = {'os': 'Android', 'soc': 'Qualcomm', 'screen': 'Samsung'}# 通过 key 访问 value,如果 key 不存在则报错>>> m['os']'Android'# 为了避免 key 不存在报错问题,可以使用 get 方法,如果 key 不存在则返回 None,或指定返回值>>> m.get('soc')'Qualcomm'# key 不存在时,指定返回值>>> m.get('xxx',-1)-1# 除了 get 方法外,还可以先通过 in 判断 key 是否存在>>>'xxx' in mFalse>>>'os' in m True 添加 key-value 为字典增加一项 1234>>> m = {'os': 'Android', 'soc': 'Qualcomm', 'screen': 'Samsung'}>>> m['camera'] = 'Sony'>>> m{'os': 'Android', 'soc': 'Qualcomm', 'screen': 'Samsung', 'camera': 'Sony'} 删除 key-value123456789101112>>> m = {'os': 'Android', 'soc': 'Qualcomm', 'screen': 'Samsung'}>>> del m['os']>>> m{'soc': 'Qualcomm', 'screen': 'Samsung'}>>> m.pop('soc')'Qualcomm'>>> m{'screen': 'Samsung'}# 删除所有键值对>>> m.clear()>>> m{} 修改字典中的值1234>>> m = {'os': 'Android', 'soc': 'Qualcomm', 'screen': 'Samsung'}>>> m['screen'] = 'BOE'>>> m{'os': 'Android', 'soc': 'Qualcomm', 'screen': 'BOE'} 字典的长度(键值对数量)123>>> d = {'x': 1, 'y': 2, 'z': 3}>>> len(d)3 update() 字典合并 将一个字典的内容添加到另一个字典12345>>> d = {'x': 1, 'y': 2, 'z': 3}>>> d1 = {'m': 4, 'n': 5}>>> d.update(d1)>>> d{'x': 1, 'y': 2, 'z': 3, 'm': 4, 'n': 5} 遍历字典遍历字典的键 key12345678>>> d = {'list': [1, 2, 3], 1: 123, '111': 'python3', 'tuple': (4, 5, 6)}>>> for key in d.keys():··· print(key)1list111tuple 遍历字典的值value12345678>>> d = {'list': [1, 2, 3], 1: 123, '111': 'python3', 'tuple': (4, 5, 6)}>>> for value in d.values():··· print (value)[1, 2, 3]123python3(4, 5, 6) 遍历获取 key 和 value1234567>>> m = {'os': 'Android', 'soc': 'Qualcomm', 'screen': 'Samsung'}>>> for key in m:··· print(key + ' -> ' + m[key])os -> Androidsoc -> Qualcommscreen -> Samsung 12345678>>> d = {'list': [1, 2, 3], 1:123, '111': 'python3', 'tuple': (4, 5, 6)}>>> for key in d:··· print(str(key) + ':' + str(d[key]))list:[1, 2, 3]1:123111:python3tuple:(4, 5, 6) 遍历字典的项 items,获取 key 和 value items() 返回一个列表,列表的每一个元素是一个包含 key、value 的 tuple 12345678>>> m = {'os': 'Android', 'soc': 'Qualcomm', 'screen': 'Samsung'}# m.items() 返回一个列表,列表的每一个元素是一个包含 key、value 的 tuple>>> for key, value in m.items():··· print(key + ' -> ' + value)os -> Androidsoc -> Qualcommscreen -> Samsung 12345678>>> d = {'list': [1, 2, 3], 1: 123, '111': 'python3', 'tuple': (4, 5, 6)}>>> for item in d.items():··· print(item)('list', [1, 2, 3])(1, 123)('111', 'python3')('tuple', (4, 5, 6)) 12345678>>> d = {'list': [1, 2, 3], 1: 123, '111': 'python3', 'tuple': (4, 5, 6)}>>> for key, value in d.items():··· print(key, value)list [1, 2, 3]1 123111 python3tuple (4, 5, 6) 12345678>>> d = {'list': [1, 2, 3], 1: 123, '111': 'python3', 'tuple': (4, 5, 6)}>>> for (key, value) in d.items():··· print(key, value)list [1, 2, 3]1 123111 python3tuple (4, 5, 6)]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python字典操作</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python文件内指针移动]]></title>
<url>%2F2018%2F07%2F28%2FPython%E6%96%87%E4%BB%B6%E5%86%85%E5%85%89%E6%A0%87%E7%A7%BB%E5%8A%A8%2F</url>
<content type="text"><![CDATA[Python文件内指针移动 read(3) 文件打开方式为文本模式时,代表读取 3 个字符 文件打开方式为 b模式 时,代表读取 3 个字节 其余的文件内光标移动都是以字节为单位如 seek,tell,truncate seek() 文件内光标操作 seek 有三种移动方式 0,1,2其中 1 和 2 必须在 b模式 下进行,但无论哪种模式,都是以 bytes 为单位移动的 单位统一为字节第一个参数:控制移动的字节数第二个参数:控制移动的参照物,值可以为:0、1、20:参照文件开头,默认为01:参照当前位置2:参照文件末尾 0:参照文件开头(在 b 和 t 模式下都能使用) 默认为 0强调:除了 0 模式以外的模式都只能在 b 模式下使用1 和 2 只能用在 b 模式下 12cat e.txt你好hello 123456with open('e.txt', mode='rt', encoding='utf-8') as f: f.seek(3) print(f.read())# 输出结果好hello 如果一个 字节 没有读完,会报以下错误 1234567with open('e.txt', mode='rt', encoding='utf-8') as f: f.seek(2) print(f.read())# 输出结果(result, consumed) = self._buffer_decode(data, self.errors, final)UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa0 in position 0: invalid start byte 123456with open('e.txt', mode='rt', encoding='utf-8') as f: f.seek(6, 0) print(f.read())# 输出结果hello 1234567with open('e.txt', mode='rt', encoding='utf-8') as f: f.read() f.seek(0, 0) print('第二次', f.read())# 输出结果第二次 你好hello 1:参照当前位置(只能在 b模式下使用) 储备:read(n)read 的 n 在 t模式下 读的是字符个数123456with open('e.txt', mode='rt', encoding='utf-8') as f: data = f.read(2) print(data)# 输出结果你好 read 的 n 在 b模式下 读的是字节个数123456with open('e.txt', mode='rb') as f: data = f.read(3) print(data)# 输出结果b'\xe4\xbd\xa0' 123456with open('e.txt', mode='rb') as f: data = f.read(3) print(data.decode('utf-8'))# 输出结果你 其余所有文件内指针的移动都是以字节为单位 123456with open('e.txt', mode='rt', encoding='utf-8') as f: s = f.read(1) print(s)# 输出结果你 12345678with open('e.txt', mode='rt', encoding='utf-8') as f: s = f.read(1) print(s) print(f.tell()) # 查看当前指针位置# 输出结果你3 # 表示当前指针在第 3 个字节 1234567with open('e.txt', mode='rt', encoding='utf-8') as f: s = f.read(1) # 读取一个字符 f.seek(6, 0) # 0 将指针放到文件开头,移动到第 6 个字节位 print(f.read()) # 打印输出# 输出结果hello 12345678with open('e.txt', mode='rb') as f: s = f.read(3) # f.seek(6, 0) f.seek(3, 1) print(f.read())# 输出结果b'hello' 12345678with open('e.txt', mode='rb') as f: s = f.read(3) # f.seek(6, 0) f.seek(3, 1) print(f.read().decode('utf-8'))# 输出结果hello 2:参照文件末尾(只能在 b模式下使用)1234cat e.txt你好hello1你好hello2你好hello3 123456with open('e.txt', mode='rb') as f: f.seek(-6,2) # 读取 'hello3' print(f.read())# 输出结果b'hello3' truncate 截断文件 文件的打开方式必须可写,但是不能用 w 或 w+ 等方式打开,因为那样直接清空文件了所以 truncate 要在 r+ 或 a 或 a+ 等模式下测试效果a:追加写r+:可读可写 1234cat e.txt你aaa好hello1你好hello2你好hello3 123456with open('e.txt', mode='at', encoding='utf-8') as f: f.truncate(9) # 从文件开头截取到 3个bytes 的位置,其余的删除# 执行结果cat e.txt你aaa好 123456with open('e.txt', mode='r+t', encoding='utf-8') as f: f.truncate(9) # 从文件开头截取到 3个bytes 的位置,其余的删除# 执行结果cat e.txt你aaa好 练习:基于 seek 实现 tail -f 功能12345678910import timewith open('test.txt', 'rb') as f: f.seek(0,2) while True: line = f.readline() if line: print(line.decode('utf-8')) else: time.sleep(0.2)]]></content>
<categories>
<category>Python笔记</category>
</categories>
<tags>
<tag>Python文件内指针移动</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RabbitMQ集群配置]]></title>
<url>%2F2018%2F07%2F25%2FRabbitMQ%E9%9B%86%E7%BE%A4%E9%85%8D%E7%BD%AE%2F</url>
<content type="text"><![CDATA[RabbitMQ 集群配置这里展示的是多机集群的部署,如果机器受限,可以选择单机集群部署 RabbitMQ 可以通过三种方法来部署分布式集群系统分别是:cluster,federation,shovel cluster 不支持跨网段,用于同一个网段内的局域网 可以随意的动态增加或者减少 节点之间需要运行相同版本的 RabbitMQ 和 Erlang federation 应用于广域网,允许单台服务器上的交换机或队列接收发布到另一台服务器上交换机或队列的消息,可以是单独机器或集群。federation 队列类似于单向点对点连接,消息会在联盟队列之间转发任意次,直到被消费者接受。通常使用 federation 来连接 internet 上的中间服务器,用作订阅分发消息或工作队列。 shovel 连接方式与 federation 的连接方式类似,但它工作在更低层次。可以应用于广域网。 节点类型 Ram node:内存节点将所有的队列、交换机、绑定、用户、权限和 vhost 的元数据定义存储在内存中,好处是可以使得像交换机和队列声明等操作更加的快速。 Disk node:将元数据存储在磁盘中,单节点系统只允许磁盘类型的节点,防止重启 RabbitMQ 的时候,丢失系统的配置信息。 选用三台主机,主机名分别是 pro-rabbitmq01pro-rabbitmq02pro-rabbitmq03 集群配置RabbitMQ 是用 Erlang 开发的,集群非常方便,因为 Erlang 天生就是一门分布式语言,但其本身并不支持负载均衡。RabbitMQ 利用 Erlang 的分布式特性组建集群,erlang 集群通过 magic cookie 实现,此 cookie 保存在 $HOME/.erlang.cookierpm安装即 /var/lib/rabbitmq/.erlang.cookie二进制安装在 /data/erlang/.erlang.cookie需要保证集群各节点的此 cookie 一致,可以选取一个节点的 cookie,用 rsync,scp 同步到其余节点。 同步 Cookie读取其中一个节点的 cookie,并复制到其他节点节点之间通过 cookie 确定相互是否可通信。cookie 存放在 /var/lib/rabbitmq/.erlang.cookie或$HOME/.erlang.cookie 中 设置 Erlang CookieRabbitMQ 的集群是依赖于 Erlang 的集群来工作的,所以必须先构建起 Erlang 的集群环境。Erlang 的集群中各节点是通过一个 magic cookie 来实现的,这个 Cookie 存放在 /data/erlang/.erlang.cookie 中,文件是 400 的权限。所以必须保证各节点 Cookie 保持一致,否则节点之间就无法通信。我们是删除其中两台的 /data/erlang/.erlang.cookie,然后将另一台的 /data/erlang/.erlang.cookie 拷贝到这两台上。文件权限是 400 注意:.erlang.cookie 文件的权限,RabbitMQ账号,权限 400 或 600 即可,为组或 other 账号赋权会报错 123rsync -avzP /data/erlang/.erlang.cookie [email protected]:/data/erlang/# scp /var/lib/rabbitmq/.erlang.cookie [email protected]:/var/lib/rabbitmq/# scp /var/lib/rabbitmq/.erlang.cookie [email protected]:/var/lib/rabbitmq/ 逐个启动节点1rabbitmq-server -detached 查看各节点的状态12rabbitmqctl statusrabbitmqctl cluster_status 配置各节点的 hosts 文件1234vim /etc/hostsx.x.x.x pro-rabbitmq01x.x.x.x pro-rabbitmq02x.x.x.x pro-rabbitmq03 组建集群pro-rabbitmq02 和 pro-rabbitmq03以 pro-rabbitmq01 为主节点在 pro-rabbitmq02 上 rabbitmqctl join_cluster rabbit@pro-rabbitmq01 中的 rabbit@pro-rabbitmq01,rabbit 代表集群名pro-rabbitmq01 代表集群节点节点名同 hostname,hostname 与 /etc/hosts 中设置必须保持一致pro-rabbitmq02 与 pro-rabbitmq03 均连接到 pro-rabbitmq01,它们之间也会自动建立连接。如果需要使用内存节点,增加一个 --ram 的参数即可如:rabbitmqctl join_cluster --ram rabbit@pro-rabbitmq01rabbitmqctl join_cluster rabbit@pro-rabbitmq01 --ram一个集群中至少需要一个 disk 节点默认是磁盘节点,如果是内存节点的话,需要加 --ram 参数 1234[root@pro-rabbitmq02 ~]# rabbitmqctl stop_app[root@pro-rabbitmq02 ~]# rabbitmqctl reset[root@pro-rabbitmq02 ~]# rabbitmqctl join_cluster rabbit@pro-rabbitmq01[root@pro-rabbitmq02 ~]# rabbitmqctl start_app pro-rabbitmq03 上的操作与 pro-rabbitmq02 的相同 1234[root@pro-rabbitmq03 ~]# rabbitmqctl stop_app[root@pro-rabbitmq03 ~]# rabbitmqctl reset[root@pro-rabbitmq03 ~]# rabbitmqctl join_cluster rabbit@pro-rabbitmq01[root@pro-rabbitmq03 ~]# rabbitmqctl start_app 12345678910111213141516171819202122232425262728[root@pro-rabbitmq02 rabbitmq]# rabbitmqctl stop_appStopping rabbit application on node rabbit@pro-rabbitmq02 ...[root@pro-rabbitmq02 rabbitmq]# netstat -ntlpActive Internet connections (only servers)Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:4369 0.0.0.0:* LISTEN 2556/epmd tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 2717/sshd tcp 0 0 0.0.0.0:25672 0.0.0.0:* LISTEN 2467/beam.smp tcp6 0 0 :::4369 :::* LISTEN 2556/epmd [root@pro-rabbitmq02 rabbitmq]# rabbitmqctl resetResetting node rabbit@pro-rabbitmq02 ...[root@pro-rabbitmq02 rabbitmq]# rabbitmqctl join_cluster rabbit@pro-rabbitmq01Clustering node rabbit@pro-rabbitmq02 with rabbit@pro-rabbitmq01[root@pro-rabbitmq02 rabbitmq]# rabbitmqctl start_appStarting node rabbit@pro-rabbitmq02 ... completed with 0 plugins.[root@pro-rabbitmq02 rabbitmq]# rabbitmqctl cluster_statusCluster status of node rabbit@pro-rabbitmq02 ...[{nodes,[{disc,['rabbit@pro-rabbitmq01','rabbit@pro-rabbitmq02']}]}, {running_nodes,['rabbit@pro-rabbitmq01','rabbit@pro-rabbitmq02']}, {cluster_name,<<"rabbit@pro-rabbitmq01">>}, {partitions,[]}, {alarms,[{'rabbit@pro-rabbitmq01',[]},{'rabbit@pro-rabbitmq02',[]}]}] 修改集群类型 修改 disk 节点到 内存 节点 1234567891011121314[root@pro-rabbitmq02 ~]# rabbitmqctl change_cluster_node_type ramTurning rabbit@pro-rabbitmq02 into a ram node[root@pro-rabbitmq02 ~]# rabbitmqctl start_appStarting node rabbit@pro-rabbitmq02 ... completed with 3 plugins.[root@pro-rabbitmq02 ~]# rabbitmqctl cluster_statusCluster status of node rabbit@pro-rabbitmq02 ...[{nodes,[{disc,['rabbit@pro-rabbitmq01']},{ram,['rabbit@pro-rabbitmq02']}]}, {running_nodes,['rabbit@pro-rabbitmq01','rabbit@pro-rabbitmq02']}, {cluster_name,<<"rabbit@pro-rabbitmq01">>}, {partitions,[]}, {alarms,[{'rabbit@pro-rabbitmq01',[]},{'rabbit@pro-rabbitmq02',[]}]}] 1234567891011121314[root@pro-rabbitmq03 rabbitmq]# rabbitmqctl cluster_statusCluster status of node rabbit@pro-rabbitmq03 ...[{nodes,[{disc,['rabbit@pro-rabbitmq01']}, {ram,['rabbit@pro-rabbitmq03','rabbit@pro-rabbitmq02']}]}, {running_nodes,['rabbit@pro-rabbitmq02','rabbit@pro-rabbitmq01', 'rabbit@pro-rabbitmq03']}, {cluster_name,<<"rabbit@pro-rabbitmq01">>}, {partitions,[]}, {alarms,[{'rabbit@pro-rabbitmq02',[]}, {'rabbit@pro-rabbitmq01',[]}, {'rabbit@pro-rabbitmq03',[]}]}]# 可以看到三个节点都加入了集群中,两个 ram 节点、一个 disc 节点。# 其中三个节点都在运行中,以及集群名称显示。 默认是磁盘节点,如果是内存节点的话,需要加 --ram 参数RabbitMQ 集群节点有 disc 和 ram 两种类型,一个集群中至少要有一个 disc 类型的节点,不指定默认加入为 disc 12# 将集群类型修改成 disk 节点rabbitmqctl change_cluster_node_type disc]]></content>
<categories>
<category>RabbitMQ笔记</category>
</categories>
<tags>
<tag>RabbitMQ集群配置</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HTML基础学习]]></title>
<url>%2F2018%2F07%2F25%2FHTML%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[HTML基础学习Web运行本质 对于所有的 Web 应用,本质上其实就是一个 socket 服务端,用户的浏览器其实就是一个 socket 客户端 12345678910111213141516171819import socketdef handle_request(client): buf = client.recv(1024) client.send(bytes("HTTP/1.1 200 OK\r\n\r\n", encoding='utf-8')) client.send(bytes("Hello, World", encoding='utf-8')) def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost', 8000)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close()if __name__ == '__main__': main() HTML 相关概念html是什么 超文本标记语言(Hypertext Markup Language,HTML)通过标记符号来标记要显示的网页中的内容。 其实就是一套规则,浏览器认识的规则。 浏览器按顺序渲染网页文件,然后根据标记符解释和显示内容。 对于不同的浏览器,对同一标签可能会有不完全相同的解释(兼容性)。 w3c(了解) w3c是什么? 万维网联盟(World Wide Web Consortium,简称W3C)创建于1994年,是Web技术领域具权威和影响力的国际标准化组织 w3c的主要工作? W3C主要工作,是制作Web规范。到目前为止,W3C已发布了200多项影响深远的Web技术标准。比如:XHTML、HTML5、XML、CSS、DOM、XSTL等 HTML 文件结构以及标签格式html 的文件格式12345678910<!DOCTYPE html><html> <head> <meta charset="UTF-8"/> <title>网页标题</title> </head> <body> 文件体 </body></html> 上述代码说明 <html></html>是文档的开始标记和结束标记。此元素告诉浏览器其自身是一个 HTML 文档,在它们之间是文档的头部和主体 <head></head>元素出现在文档的开头部分。<head>与</head>之间的内容不会在浏览器的文档窗口显示,但是其中的元素有特殊重要的意义 <title></title>定义网页标题,在浏览器标题栏显示 <body></body>之间的文本是可见的网页主体内容 html 标签格式 HTML标签是由尖括号包围的关键词,比如 <html> 标签对中的第一个标签是开始标签,第二个标签是结束标签 HTML标签通常是成对出现的(双边标记),比如 <div> 和 </div> 但也有单独呈现的标签(单边标记),如:<br />、<hr />和<img src=“images/1.jpg” />等 标签可以有若干个属性,也可以不带属性。如<head>元素就不带任何属性 123标答的语法:<标签名 属性1=“属性值1” 属性2=“属性值2”...>内容部分</标签名><标签名 属性1=“属性值1” 属性2=“属性值2”.../> HTML 常用标签之MetaMeta介绍 <meta>元素可提供有关页面的元信息(meta-information),针对搜索引擎和更新频度的描述和关键词 <meta>标签位于文档的头部,不包含任何内容 <meta>提供的信息是用户不可见的 关于meta常见的用法 author(作者) 说明:标注网页的作者用法:<meta name="author" content="张三"> Copyright(版权信息) 功能:说明网站版权信息用法:<meta name="copyright" content="信息参数"> 简单示例 HTML 常用标签之字体以及排版标签排版元素(常用) 标签 作用 <p></p> 用来创建一个段落,该元素自动在其前后创建一些空白 <br/> 换行 <hr/> 华丽的分割线 <h1></h1>...<h6></h6> 6种标题效果标签。分别为h1~h6。<h1>字体最大,<h6>字体最小 marquee标签(跑马灯的实现)HTML常用标签之列表无序列表1234<ul> <li>内容</li> <li>内容</li></ul> 常用属性解释 type属性:disc(实心圆点)(默认)、circle(空心圆圈)、square(实心方块) 有序列表1234<ol> <li>内容</li> <li>内容</li></ol> 常用属性解释 属性:type编号类型,默认为整数。可选(1、A、a、Ⅰ、i)属性:start起始编号,默认为1,即由最小编号开始 HTML常用标签之a标签语法 <a 属性="属性值">标签内容</a> 常见的属性 href – 指定目标网页的地址,该地址可以有如下类型: 链接远程目标:通过URL地址链接到远程目标。 链接本地页面:可以通过相对路径或者绝对路径链接本地页面。 相对路径:指相对于当前页面位置的路径./:表示当前页面所在的目录../:表示当前页面所在的上一级目录 绝对路径:绝对路径指当前站点中确切的路径,一般以”/”开始 例如:<a href="/admin/index.py">后台首页</a> target _blank 表示在 新窗口 中打开目标网页_self 表示在 当前窗口 中打开目标网页 常见例子 链接到远程地址 12<a href="http://www.sina.com.cn">新浪网</a><a href="http://www.qq.com">腾讯网</a> 链接到本地文件 12相对路径:<a href="include/login.html">登录页面</a>绝对路径:<a href="/html/123.html">国内新闻</a> 链接到邮箱 1<a href="mailto:[email protected]">给我发邮件</a> 下载文件 12<a href="/download/winRAR.rar">下载WinRAR</a><a href="download/office2007.rar">下载office2007</a> HTML常用标签之div和span元素块级元素 <div></div><div>只是一个块级元素,并无实际的意义. 主要通过CSS为其赋予不同的表现 行内元素 <span></span><span>内联行(行内元素),并无实际的意义. 主要通过CSS为其赋予不同的表现 块级元素与行内元素的区别 所谓块元素,是以另起一行开始渲染的元素,行内元素则不需另起一行如果单独在网页中插入这两个元素,不会对页面产生任何的影响这两个元素是专门为定义CSS样式而生的 HTML常用标签之img标签常见的用法1<img src="URL" alt="图片说明"/> 常见的属性用法说明 属性 值 含义 src 图像URL 规定图像的URL alt 字符串 规定图像的替代文本 width px / % 规定图像的宽 height px / % 规定图像的高 border px 图像的边框粗细 HTML常用标签之table标签html表格的基本结构12345678910<table> <tr> <th>标题</th> <th>标题</th> </tr> <tr> <td>内容</td> <td>内容</td> </tr></table> 解释说明 <table></table>表示的表格的开始和结束 <tr></tr>表示的是表格的一行 <td></td>表示的是一个单元数据格 <th></th>表示表格标题单元格,且加粗居中显示 table的常用属性 属性 值 含义 width px或% 表格的宽度 height px或% 表格的高度 border px 表格的边框的粗细 align Left/center/right 元素的对齐方式 简单的案例实现 HTML常用标签之form表单元素标签FORM表单的基本概念 基本概念 HTML表单是HTML元素中较为复杂的部分,表单往往和脚本、动态页面、数据处理等功能相结合,因此它是制作动态网站很重要的内容。表单一般用来收集用户的输入信息 表单的工作原理 访问者在浏览有表单的网页时,可填写必需的信息,然后按某个按钮提交这些信息通过Internet传送到服务器上服务器上专门的程序对这些数据进行处理,如果有错误会返回错误信息,并要求纠正错误 12345678910111213141516import tornado.ioloopimport tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") def post(self): self.write('this is a test') application = tornado.web.Application([ (r"/index", MainHandler),]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start() 表单的常见结构 123456<form id="form1" method="post" action=""> 账号:<input type="text" name="account" /><br /> 密码:<input type="password" name="password" /><br /> <input type="submit" name="submit" value="提交" /> <input type="reset" name="reset" value="重置" /></form> 表单标记属性 属性 值 含义 action url 指定一个表单处理目标URL,表单数据将被提交到该URL地址的处理程序。如果该属性值为空,则提交到文档自身。该属性值可以为绝对地址、相对地址、文档片段,甚至是脚本代码 method get或post 将表单数据提交到http服务器的方法,可能值有两个:get和post enctype application/x-www-form-urlencoded 指定表单数据的编码类型,此属性只有在method属性设置为post时才有效。默认值为application/x-www-form-urlencoded对所有字符进行编码。如果表单包含用于文件上传的控件(input type=“file”),那么这个属性值必须设为multipart/form-data ,不对字符进行编码。 补充:Get和Post提交的区别 Get方式提交 如果为get,那么所提交的数据集将被作为一个由表单的所有名/值对组成的查询字符串(query string)添加到表单处理器的URL(action属性)的末尾比如:http://www.oldboyedu.com/index.py?variable1=content1&variable1=content2这种方法提交的信息在长度上有一定限制,而且不安全,适合简单的数据查询 Post方式提交 如果为post,那么数据集将直接发给表单处理程序,而不是以可见的URL查询字符串的形式。post方法可以提交更长的数据,并且相对安全一些,传送的数据类型更多一些(不限于ASCII字符),因而适用于数据更复杂的表单 常见的 INPUT 标签 type属性值 空间名称 对应代码 text 单行文本输入框 <input type="text"/> password 密码输入框 <input type="password"/> checkbox 复选框 <input type="checkbox" checked='checked'/> radio 单选框 <input type="radio"/> submit 提交按钮 <input type="submit" value='提交'/> reset 重置按钮 <input type="reset" value='重置'/> button 普通按钮 <input type="button" value=“普通按钮”/> hidden 隐藏按钮 <input type="hidden" value=“隐藏按钮”/> file 文本选择框 <input type="file"/> 需要注意的问题 上传文件控件 当一个中有“上传文件域”,必须指定MIME类型 enctype=”multipart/form-data”>,否则无法上传文件上传文件域,只在 method=”post” 下才有效 隐藏控件 <input type="hidden" name="nid" value="234" />隐藏字段对于用户是不可见的隐藏字段通常会存储一个默认值一般用在,修改某条数据时,用来记录数据的id号 普通按钮 <input type="button" name="button" value="普通按钮" />定义可点击的按钮,但没有任何行为,一般配合JS使用 各种按钮显示效果 SELECT下拉列表常见的基本结构 123456789<form id="form1" name="form1" method="post" action=""> <select name="city" id="city"> <option value="北京">北京</option> <option value="上海" selected="selected">上海</option> <option value="南京">南京</option> <option value="杭州">杭州</option> <option value="深圳">深圳</option> </select></form> 属性说明 multiple:布尔属性,设置后允许多选,否则只能选择一个disabled:禁用该下拉列表selected:首次显示时,为选中状态value:定义发往服务器的选项值 TEXTAREA多行文本框12345<form id="form1" name="form1" method="post" action=""> <textarea cols="宽度" rows="高度" name="名称"> 默认内容 </textarea></form> 属性说明 属性 属性值 说明 name name 控件名称 rows number 设置多行文本框的显示行数(高度) cols number 设置多行文本框的显示列数(宽度) disabled disabled 布尔属性,设置当前文本框为禁用状态 LABEL表单修饰1234<form id="form1" name="form1" method="post" action=""> <label for="username">用户名</label> <input type="text" name="username" id="username" /></form> 说明 label 元素不会向用户呈现任何特殊效果<label> 标签的 for 属性应当与相关元素的 id 属性相同结合CSS可以控制表单文本或控件对齐,美化表单 案例 参考文档http://www.shangzekai.xyz/2017/05/07/html基础目录/]]></content>
<categories>
<category>HTML笔记</category>
</categories>
<tags>
<tag>HTML基础学习</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RabbitMQ常用命令]]></title>
<url>%2F2018%2F07%2F24%2FRabbitMQ%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[RabbitMQ常用命令 服务启动关闭12345# 启动rabbitmq-server -detached # 关闭rabbitmqctl stop 集群配置相关命令12345678910111213rabbitmqctl stop_app rabbitmqctl reset # 在当前集群中加入某节点rabbitmqctl join_cluster {rabbit_node_name}# 某些低版本可以采用 rabbitmqctl cluster {rabbit_node_name}rabbitmqctl start_app # 集群状态rabbitmqctl cluster_status# 将某节点剔除出当前集群rabbitmqctl forget_cluster_node {rabbit_node_name} 插件管理123456789101112# 开启某个插件rabbitmq-plugins enable {插件名}# 关闭某个插件rabbitmq-plugins disable {插件名}# 有关插件名可以在 rabbitmq 的安装目录下的 plugins 目录中查看$RABBITMQ_HOME/plugins# 举例rabbitmq-plugins enable rabbitmq_management rabbitmq-plugins list 用户管理1234567891011121314151617# 添加用户rabbitmqctl add_user {username} {password}# 删除用户rabbitmqctl delete_user {username}# 修改密码rabbitmqctl change_password {username} {newpassword}# 设置用户角色rabbitmqctl set_user_tags {username} {tag}tag 可以为 administrator, monitoring, management# 举例rabbitmqctl add_user root rootrabbitmqctl set_user_tags root administratorrabbitmqctl list_users 权限管理123456789101112131415161718# 权限设置rabbitmqctl set_permissions [-p vhostpath] {user} {conf} {write} {read}conf # 一个正则表达式match哪些配置资源能够被该用户访问write # 一个正则表达式match哪些配置资源能够被该用户读read # 一个正则表达式match哪些配置资源能够被该用户访问# 查看(指定vhost)所有用户的权限信息rabbitmqctl list_permissions [-p vhostPath]# 查看指定用户的权限信息rabbitmqctl list_user_permissions {username}# 清除用户的权限信息rabbitmqctl clear_permissions [-p vhostPath] {username}# 举例rabbitmqctl set_permissions -p / root "." "." ".*" 获取服务器状态信息1234567891011121314151617181920212223242526272829303132333435# 服务器状态rabbitmqctl status# 队列信息rabbitmqctl list_queues -p vhostpath# queueinfoitem可以为name, durable, auto_delete, arguments, messages_ready, messages_unacknowled, messages, consumers, memory.# Exchange信息rabbitmqctl list_exchanges -p vhostpath# exchangeinfoitem有name, type, durable, auto_delete, internal, arguments.# Binding信息rabbitmqctl list_bindings -p vhostpath# bindinginfoitem有source_name, source_kind, destination_name, destination_kind, routing_key, arguments.等# connection信息rabbitmqctl list_connections [connectioninfoitem ...]# connectioninfoitem有recv_oct,recv_cnt,send_oct,send_cnt,send_pend 等。# channel信息rabbitmqctl list_channels [channelinfoitem ...]# channelinfoitem有consumer_count,messages_unacknowledged,messages_uncommitted,acks_uncommitted,messages_unconfirmed,prefetch_count,client_flow_blocked# 举例rabbitmqctl list_queues name messages_ready pid slave_pids 更改节点类型12345rabbitmqctl stop_apprabbitmqctl change_cluster_node_type disc或rabbitmqctl change_cluster_node_type ramrabbitmqctl start_app vhost管理12345# 添加vhostrabbitmqctl add vhost {name}# 删除vhostrabbitmqctl delete vhost {name} 镜像队列的设置12345678910111213141516镜像队列的配置通过添加 policy 完成,policy 添加的命令为rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority] -p Vhost: # 可选参数,针对指定 vhost 下的 queue 进行设置Name: # policy 的名称Pattern: # queue 的匹配模式(正则表达式)Definition: # 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode ha-mode: # 指明镜像队列的模式,有效值为 all/exactly/nodes all # 表示在集群所有的节点上进行镜像 exactly # 表示在指定个数的节点上进行镜像,节点的个数由 ha-params 指定 nodes # 表示在指定的节点上进行镜像,节点名称通过 ha-params 指定 ha-params: # ha-mode模式需要用到的参数 ha-sync-mode: # 镜像队列中消息的同步方式,有效值为 automatic,manually # 有效值为 automatic(自动同步),manually(手动同步),默认是 manually # 请注意一定要记得设置为 automatic(自动同步),否则消息在镜像队列中是不会自动同步的(即普通集群模式),只能通过命令手动去同步Priority: # 可选参数,policy 的优先级 示例1234567# 对队列名称以 hello 开头的所有队列进行镜像,并在集群的两个节点上完成镜像# policy 的设置命令为rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'# 对队列名称以 “queue_” 开头的所有队列进行镜像,并在集群的两个节点上完成进行# policy 的设置命令为rabbitmqctl set_policy --priority 0 --apply-to queues mirror_queue "^queue_" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}' policy 相关解释12345Name: # policy 策略的唯一名称Pattern: # 用于匹配 exchange 和 queue 等名称的正则表达式Apply to: # 指定将此 policy 应用到哪些项上(例如:exchange,queue等)Priority: # 可选参数,policy 的优先级,exchange 和 queue 名称可以匹配到多个 policy,优先级则定义了 policy 被执行的顺序Definition:# 参数定义 消息的同步 将新节点加入已存在的镜像队列是,默认情况下 ha-sync-mode=manual,镜像队列中的消息不会主动同步到新节点,除非显式调用同步命令。当调用同步命令后,队列开始阻塞,无法对其进行操作,直到同步完毕。当 ha-sync-mode=automatic 时,新加入节点时会默认同步已知的镜像队列。由于同步过程的限制,所以不建议在生产的 active 队列(有生产消费消息)中操作。 12345678910# 可以使用下面的命令来查看那些 slaves 已经完成同步rabbitmqctl list_queues name slave_pids synchronised_slave_pids# 可以通过手动的方式同步一个 queuerabbitmqctl sync_queue name# 同样也可以取消某个 queue 的同步功能rabbitmqctl cancel_sync_queue name# 当然这些都可以通过 management 插件来设置 参考文档https://blog.csdn.net/u013256816/article/details/53524814]]></content>
<categories>
<category>RabbitMQ笔记</category>
</categories>
<tags>
<tag>RabbitMQ常用命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RabbitMQ用户管理]]></title>
<url>%2F2018%2F07%2F24%2FRabbitMQ%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86%2F</url>
<content type="text"><![CDATA[RabbitMQ 用户管理 创建用户 在 RabbitMQ 中,用户是访问控制(Access Control)的基本单元,且单个用户可以跨越多个vhost 进行授权。针对一至多个 vhost,用户可以被赋予不同级别的访问权限,并使用标准的用户名和密码来认证用户。创建用户的命令为:rabbitmqctl add_user {username} {password}。其中username 表示要创建的用户名称。password 表示创建用户登录的密码。具体创建一个用户名为 root、密码为 root123 的用户 12[root@node1 ~]# rabbitmqctl add_user root root@123Creating user "root" 修改密码可以通过rabbitmqctl change_password {username} {newpassword}命令来更改指定用户的密码其中username 表示要变更密码的用户名称newpassword 表示要变更的新的密码 举例:将 root用户的密码变更为 root32112[root@node1 ~]# rabbitmqctl change_password root root@321Changing password for user "root" 清除密码同样可以清除密码,这样用户就不能使用密码登录了对于的操作命令为:rabbitmqctl clear_password {username}其中username 表示要清楚密码的用户名称 验证用户使用rabbitmqctl authenticate_user {username} {password}可以通过密码来验证用户其中username 表示需要被验证的用户名称password 表示密码 下面示例中分别采用 root321 和 root322 来验证 root 用户1234567[root@node1 ~]# rabbitmqctl authenticate_user root root@321Authenticating user "root"Success[root@node1 ~]# rabbitmqctl authenticate_user root root@322Authenticating user "root"Error: failed to authenticate user "root" 删除用户删除用户的命令是rabbitmqctl delete_user {username}其中username 表示要删除的用户名称 删除用户 root 的示例如下12[root@node1 ~]# rabbitmqctl delete_user rootDeleting user "root" 查询用户列表rabbitmqctl list_users 命令可以用来罗列当前的所有用户每个结果行都包含用户名称,其后紧跟用户的角色(tags) 示例代码如下:1234[root@node1 ~]# rabbitmqctl list_usersListing usersguest [administrator]root [] 用户的角色分为 5 种类型none:无任何角色。新创建的用户的角色默认为 none。management:可以访问 Web 管理页面。policymaker:包含 management 的所有权限,并且可以管理策略(policy)和参数(parameter)。monitoring:包含 management 的所有权限,并且可以看到所有连接(connections)、信道(channels)以及节点相关的信息。administartor:包含 monitoring 的所有权限,并且可以管理用户、虚拟主机、权限、策略、参数等等。administator 代表了最高的权限。 设置用户角色用户的角色可以通过rabbitmqctl set_user_tags {username} {tag...}命令设置。其中username 参数表示需要设置角色的用户名称。tag 参数用于设置 0 个、1 个 或者 多 个的角色,设置之后任何之前现有的身份都会被删除。 使用示例如下:12345678910111213141516171819202122[root@node1 ~]# rabbitmqctl set_user_tags root monitoringSetting tags for user "root" to [monitoring][root@node1 ~]# rabbitmqctl list_users -qguest [administrator]root [monitoring][root@node1 ~]# rabbitmqctl set_user_tags root policymaker -q[root@node1 ~]# rabbitmqctl list_users -qguest [administrator]root [policymaker][root@node1 ~]# rabbitmqctl set_user_tags rootSetting tags for user "root" to [][root@node1 ~]# rabbitmqctl list_users -qguest [administrator]root [][root@node1 ~]# rabbitmqctl set_user_tags root policymaker,managementSetting tags for user "root" to ['policymaker,management'][root@node1 ~]# rabbitmqctl list_users -qguest [administrator]root [policymaker,management] 参考文档https://blog.csdn.net/u013256816/article/details/78181306]]></content>
<categories>
<category>RabbitMQ笔记</category>
</categories>
<tags>