-
Notifications
You must be signed in to change notification settings - Fork 3
/
search.xml
2133 lines (2133 loc) · 785 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[Django前端输入变量通过内部脚本返回前端展示之四]]></title>
<url>%2F2018%2F11%2F24%2FDjango%E5%90%8E%E5%8F%B0%E6%89%A7%E8%A1%8C%E8%84%9A%E6%9C%AC%E5%8F%8D%E9%A6%88%E5%88%B0%E5%89%8D%E7%AB%AF%E8%BE%93%E5%87%BA%2F</url>
<content type="text"><![CDATA[背景说明python:3.6.5Django:2.1.1Project:Kubernetes,文件夹路径就是/django/Kubernetes/App:createyaml,文件夹路径就是/django/Kubernetes/createyaml前文地址:https://rorschachchan.github.io/2018/09/26/Django%E4%BD%BF%E7%94%A8form%E8%A1%A8%E5%8D%95%E5%88%A4%E6%96%AD%E8%BE%93%E5%85%A5%E5%80%BC%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95/ 需求说明之前我们已经达到了“页面判断输入值是否合法”,“页面输入值录入数据库”这两个目的,现在就到了重头戏–网页上点击按钮,然后调用后台python脚本,并且把脚本的结果反馈到网页端。 我们本次使用一个加密的python脚本encrypt.py,它主要得作用是输入某个字段,然后进行AES256加密,然后把加密结果返回给界面,整个脚本内容如下: 1234567#!/usr/bin/env python#coding=utf-8import subprocessAESWord = input("输入字段:")result = list(subprocess.getstatusoutput("java -jar /yunwei/AES/aesEncrpt.jar "+AESWord))[1].split("=")[1]print (AESWord+ "的加密结果是:"+(result)) 脚本执行效果如下: 笨方法解决前端的页面内容如下: 12345678910111213{% extends 'base.html' %} #这部分是引入base.html这个模板{% block title %} AES加密{% endblock %}{% block content %} <form action="/k8s/encrypt/" method="post" name='encrypt'> {% csrf_token %} 要加密的字段:<input type="text" name="AESWord" /><br /> <input type="reset" value="清除所有" /> <input type="submit" value="查询解析" /> </form>{% endblock %} 目前已知views.py里使用request.POST.get()方法是可以捕获到前端输入值,但是这个输入值怎么传递给encrypt.py呢?这一点非常的复杂。 可能这个时候很多人会想使用“外部脚本引入django系统”的方法,但是那个方法可以引用到数据库,但是无法引用views.py里的函数的变量。于是只能用一个笨招:先把前端输入值记录到本地某个文件里,然后encrypt.py去读取这个文件,这样达到获取变量的方法。 于是views.py里的相关部分就是这样: 1234567891011def encrypt(request): if request.method == 'POST': AESWord = request.POST.get('AESWord') with open('/yunwei/AES/AESWord.txt','w') as f: #把前端获取到的值记录到本地的AESWord.txt文件里 f.write(AESWord+"\n") child = subprocess.Popen('python /yunwei/AES/Encrypt.py',stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True) stdout, stderr = child.communicate() result = str(stdout,encoding='utf-8') #将脚本反馈的结果输入result return HttpResponse(result) #页面展示result else: return render(request,'encrypt.html') 而encrypt.py内容改成如下: 1234567#!/usr/bin/env python#coding=utf-8import linecache,subprocessAESWord = linecache.getline('/yunwei/AES/AESWord.txt',1).strip('\n') #在这里读取前端的变量result = list(subprocess.getstatusoutput("java -jar /yunwei/AES/aesEncrpt.jar "+AESWord))[1].split("=")[1]print (AESWord+ "的加密结果是:"+(result)) 执行效果如下: 这样的操作达到了目的!后期就是把result使用render加工映射到某个网页,页面就好看很多了。 js+ajax方法解决上面的方法虽然可以达到我们想要的目的,但是其实是十分不推荐的:一是因为网页调用本地程序的权限正在被取消,二是因为真不如JS写直接,三是只能在自己本地调用。 所以还是用前端来解决更专业更优雅,那么就要使用js+ajax。 具体内容下次补充… 补充在外部脚本引入django系统的方法就是在外部脚本的开头加上下面的内容: 12345678#!/usr/bin/env python#coding=utf-8import os,sys,djangosys.path.append('/django/Kubernetes/') # 将项目路径添加到系统搜寻路径当中os.environ['DJANGO_SETTINGS_MODULE'] = 'Kubernetes.settings' # 设置项目的配置文件django.setup()from createyaml.models import parameter #这样就可以引入models.py文件里的parameter这个类 但是上面说过,这个方法可以引入数据库models.py文件,并不能引入views.py文件。 参考资料https://stackoverflow.com/questions/15151133/execute-a-python-script-on-button-clickhttps://blog.csdn.net/yzy_1996/article/details/80223053https://simpleisbetterthancomplex.com/tutorial/2016/08/29/how-to-work-with-ajax-request-with-django.htmlhttps://www.candypapi.com/2017/11/02/Python-external-script-calls-the-Django-project-model-table/https://segmentfault.com/q/1010000005096919]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Git clone的几个错误]]></title>
<url>%2F2018%2F11%2F12%2FGit-clone%E7%9A%84%E5%87%A0%E4%B8%AA%E5%B0%8F%E6%95%85%E9%9A%9C%2F</url>
<content type="text"><![CDATA[Git clone的时候可能会出现fatal: HTTP request failed的错误,如图: 一般来说这样的情况多半就是git版本太低,<=1.7的版本经常出现这样的错误,解决问题的办法就是使用最新的git,安装git 1.9的方法在这里:https://rorschachchan.github.io/2018/06/13/Centos6%E5%AE%89%E8%A3%85git1-9%E5%AE%89%E8%A3%85%E8%BF%87%E7%A8%8B/ 。 更新到1.9之后重新去git clone,这一次换成了SSL connect error错误: 此时就需要执行一下yum update -y nss curl libcurl,这样才能顺利的git clone。 如果出现了easy_install command not found,可以使用wget https://bootstrap.pypa.io/ez_setup.py -O - | python 解决,有了easy_install就可以安装pip了。 以上的操作是在python2.7下进行的。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>git</tag>
<tag>Python 2.7</tag>
</tags>
</entry>
<entry>
<title><![CDATA[通过调整Css让界面美观一点]]></title>
<url>%2F2018%2F11%2F05%2F%E9%80%9A%E8%BF%87%E8%B0%83%E6%95%B4Css%E8%AE%A9%E7%95%8C%E9%9D%A2%E7%BE%8E%E8%A7%82%E4%B8%80%E7%82%B9%2F</url>
<content type="text"><![CDATA[数据可视化肯定需要前端知识,同时也要美化前端,让用户的体验更好,这时候就需要接触到css技术。 css简单来说就是先给你需要修饰的部分设定变量,然后针对不同的变量做不同的声明,达到修改界面的目的。css规则由两个主要的部分构成:选择器,以及一条或多条声明,格式是:selector {declaration1; declaration2; ... declarationN }。 在html文本里添加一个style标签,比如:<style type="test/css"> </style>。这个标签可以放到<body>最尾处也可以放到<head>最尾处。不过一般来说都是放到<body>里。 在调整css的时候,可以搭配chrome的F12键直接修改,然后将修改的内容拷贝粘贴到html文件里。 比如我现在的页面是如下这个样子的: 这个结构可以看出使用最直白的html语言编写,为了美观大方,我们需要把它改成如下的样子: 原来的代码如下: 12345678910111213<body> <div> <a href="{% url 'home' %}"> <h2>Homepage</h2> </a> <a href="{% url 'blog_list' %}">List</a> <a href="http://www.baidu.com">跳往百度</a> <a href="http://www.lechange.com">跳往乐橙</a> </div> <hr> {% block content %} {% endblock %}</body></html> 更改后的代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142<body> <div class="nav"> <!-- 给这个div标签添加一个class叫nav --> <a class="logo" href="{% url 'home' %}"> <!-- 给这个div下的这个a标签添加一个class叫logo --> <h2>Homepage</h2> </a> <a href="{% url 'blog_list' %}">List</a> <a href="http://www.baidu.com">跳往百度</a> <a href="http://www.lechange.com">跳往乐橙</a> </div> <hr> {% block content %} {% endblock %} <style type='text/css'> body{ margin: 0; padding: 0; <!-- 这是对整个body标签进行声明,外边距和内边距都是0 --> } div.nav{ background-color: #eee; border-bottom: 2px solid blue; padding: 5px 10px; <!-- 这是对整个nav的div标签进行声明:颜色灰色 --> <!-- 增加一条底线取代<hr>,设定宽是2px,实线,颜色是蓝色 --> <!-- 设定上下边距5px,左右边距10px --> } div.nav a{ text-decoration: none; color: #000; <!-- 这是对整个nav的div标签里的所有a标签说明:取消下划线,并且规定为黑色 --> } div.nav a.logo { display: inline-block; color: green; font-size:120%; <!-- 在这里对nav的div标签里那个叫logo的a标签进行单独的说明:缩进,并且规定为绿色 --> <!-- 字体大小是原来的120% --> } </style></body></html> 调整css是一个很繁琐很麻烦的事情,需要耐心。至于如何整合css样式到一个文件然后统一配置的内容,请去看:https://rorschachchan.github.io/2018/05/12/%E5%8A%A0%E8%BD%BDcss%E6%A0%B7%E5%BC%8F%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%96%B9%E6%B3%95/ 。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>html</tag>
<tag>css</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用模板嵌套来精简html代码]]></title>
<url>%2F2018%2F11%2F02%2F%E4%BD%BF%E7%94%A8%E6%A8%A1%E6%9D%BF%E6%A0%87%E7%AD%BE%E6%9D%A5%E7%B2%BE%E7%AE%80html%E4%BB%A3%E7%A0%81%2F</url>
<content type="text"><![CDATA[在编写django的时候,前端html文件里经常会遇到很多有大量重复代码的情况出现,为了代码精简好看以及后期维护的方便,就需要把那些重复的代码统一放到一个文件里去,不重复的部分自然保留,文件到时直接调用重复模板就好,不同的部分对应填充。 举个例子,有一个代码是templates/aaa.html: 12345678910111213141516171819202122232425<!DOCTYPE html><html><head> <meta charset='UTF-8'> <title>{{ blog.title }}</title> <!-- blog.title就是文章标题,从数据库中提取,使用vender映射出来 --></head><body> <div> <a href="{% url 'home' %}"> <h2>BACK TO HOMEPAGE</h2> <!-- 这部分是重复的 --> </a> </div> <h3>{{ blog.title }}</h3> <!-- 这一部分也是同样用vender映射,展现每一篇文章对应的作者和内容 --> <p>作者:{{ blog.author }}</p> <p>分类: <a href="{% url 'blogs_with_type' blog.blog_type.pk %}"> {{ blog.blog_type }} </a> </p> <p> {{ blog.blog_type.pk }}</p> <p>发表时间:{{ blog.created_time|date:"Y-m-d H:i:s"}}</p> <!-- 这里规定了时间格式 --> <hr> <p>{{ blog.content }}</p></body></html> 假设aaa.html里”BACK TO HOMEPAGE”这个部分是重复的,即每一个页面都有返回主页的点击。既然都有这个功能,那么就单独做一个base.html文件当框架,把重复的部分写进去: 12345678910111213141516<!DOCTYPE html><html><head> <meta charset='UTF-8'> <title>{% block title %}{% endblock %}</title> <!--这里加入了一个block(块),块的名字叫title--></head><body> <div> <a href="{% url 'home' %}"> <h2>BACK TO HOMEPAGE</h2> </a> </div> <hr> {% block content%} {% endblock %} <!--这里又加入了一个block,块的名字叫content--></body></html> 现在的base.html就是一个框架,里面有了两个block,这两个块有各自的名称,因为这两个块的内容是变化的。再把aaa.html里需要对应配置的部分定义成对应的变量,并且引入这个base.html即可。重新修理后的aaa.html就长这个样子了: 12345678910111213141516171819{% extends 'base.html' %} <!--首先引入同目录下的base.html-->{% block title%} {{ blog.title }} <!--这部分就是title块的内容-->{% endblock%}{% block content %} <!--这一段就是content块的内容--> <h3>{{ blog.title }}</h3> <p>作者:{{ blog.author }}</p> <p>分类: <a href="{% url 'blogs_with_type' blog.blog_type.pk %}"> {{ blog.blog_type }} </a> </p> <p> {{ blog.blog_type.pk }}</p> <p>发表时间:{{ blog.created_time|date:"Y-m-d H:i:s"}}</p> <hr> <p>{{ blog.content }}</p>{% endblock %} 将aaa.html保存之后,刷新对应的页面,会发现依旧可以成功读取而且界面没有任何的变化。 可是在实际操作中也会出现这样的需求:多个不同的django APP可能会要访问同一个模板文件(即base.html),那么就要每一个app都复制一遍base.html吗?其实大可不必。这里可以修改一下setting.py,在里面设置一下公共的模板文件路径。 首先我们现在project根目录下建立一个base文件夹,把base.html复制进去,然后修改一下setting.py如下的字段: 1234567891011121314151617TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BASE_DIR,'base'), #BASE_DIR是在文件最开始定义的,即project的根目录 ], 'APP_DIRS': True, #这句话的意思是templates文件夹里所有的文件都可以访问 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },] 保存之后,再次刷新界面,发现界面没变化。这里django在寻找页面的时候,就会去project的路径/base下先找对应的文件,如果没有,会再去自己应用下的templates文件夹里找。如果两个都没有,那就会报错base.html is not exist。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>前端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Nginx配置IP白名单]]></title>
<url>%2F2018%2F10%2F31%2FNginx%E9%85%8D%E7%BD%AEIP%E7%99%BD%E5%90%8D%E5%8D%95%2F</url>
<content type="text"><![CDATA[环境交代Nginx配置IP白名单是非常基础的工作,这次试验就是配置某网页可以正常被部分IP访问,而其他网页访问将是403。目标网页地址是http://xxdtq.lechange.com/test/test.html,内容如下: 本机的外网IP地址是115.205.2.28,如图: 首先先nginx.conf里的日志配置格式如下: 123log_format access '$http_x_forwarded_for - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $remote_addr $request_time $upstream_response_time $http_host'; Nginx的转发文件default.conf如下: 123456789101112131415server { listen 80; server_name xxdtq.lechange.com; #如果浏览器输入的是xxdtq.lechange.com,那么就跳转到82端口 location / { proxy_pass http://localhost:82; }}server { listen 80; server_name xhssf.lechange.com; #如果浏览器输入的是xhssf.lechange.com,那么就跳转到82端口 location / { proxy_pass http://localhost:83; }} 配置步骤现在配置xxdtq.conf文件内容如下: 123456789101112131415161718192021 server{ listen 82 default; #82端口 server_name xxdtq.lechange.com; root /mnt/xiuxiudetiequan/; #根目录是/mnet/xiuxiudetiequan/ index index.html index.htm index.php; add_header Set-Cookie "HttpOnly"; add_header Set-Cookie "Secure"; add_header X-Frame-Options "SAMEORIGIN"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; location = /test/test.html { #如果remote_addr是125.205.2.28来访问/test/test.html,那么就返回403 if ($remote_addr = 115.205.2.28) { return 403; } } access_log /var/log/nginx/xxdtq/access.log access; error_log /var/log/nginx/xxdtq/error.log error;} 执行了nginx -s reload后,刷新一下界面,却发现页面没变,并不是预期中的403,打开nginx的日志一看,发现获取到的$remote_addr是127.0.0.1!如下: 为什么是127.0.0.1?因为我们这个nginx做了一个80到82端口的转发呀,所以到80的地址是真实的外界IP,而80转发到82就是本机IP了。那这样的情况怎么办?就需要在default.conf里添加一句proxy_set_header x-forwarded-for $remote_addr;,如下: 12345678server { listen 80; server_name xxdtq.lechange.com; location / { proxy_pass http://localhost:82; proxy_set_header x-forwarded-for $remote_addr; }} 重启一波nginx,发现http_x_forwarded_for正是远程访问的IP地址115.205.2.28,于是将xxdtq.conf判断IP改成如下内容: 12345location = /test/test.html { if ($http_x_forwarded_for = 115.205.2.28) { #改用http_x_forwarded_for return 403; } } 重启nginx之后,果然页面是403,如图: 然后用其他的IP地址,比如用手机连接4G去打开http://xxdtq.lechange.com/test/test.html ,发现是正常读取的,试验成功! 如果是要整个/test/目录都不让访问的话,就要改成如下内容: 12345location ^~ /test/ { if ($http_x_forwarded_for = 115.205.2.28) { # =是精确匹配 return 403; } } 如果要配置多个IP地址,就要改成如下内容: 12345location ~ ^/shopadmin { if ($remote_addr ~* "第一个IP|第二个IP|第三个IP") { #这里改成~* return 403; }} elk里提取http_x_forwarded_fornginx日志中的http_x_forwarded_for字段会有多个IP。使用自定义的模板,grok常用表达式的IPORHOST匹配http_x_forwarded_for该字段,获取的IP值是最后一个,如何取第一个IP值? 答案是: 1234mutate { split => ["http_x_forwarded_for",","] add_field => ["real_remote_addr","%{http_x_forwarded_for[0]}"] } IPORHOST这些变量匹配不到所有IP,只能通过自定义正则来匹配到所有IP;再通过以上方法截取第一个IP值。正则表达式写法是:[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\,\s]* 参考资料http://seanlook.com/2015/05/17/nginx-location-rewrite/https://zhuanlan.zhihu.com/p/21354318http://blog.pengqi.me/2013/04/20/remote-addr-and-x-forwarded-for/http://gong1208.iteye.com/blog/1559835https://my.oschina.net/moooofly/blog/295853]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>nginx</tag>
</tags>
</entry>
<entry>
<title><![CDATA[两个Zabbix_get问题记录]]></title>
<url>%2F2018%2F10%2F30%2FZabbix-get%E5%8F%8D%E9%A6%88%E7%9A%84%E7%BB%93%E6%9E%9C%E6%98%AFcontacting%2F</url>
<content type="text"><![CDATA[Zabbix_get的结果是contacting在监控zookeeper的时候,我写了一个简单的脚本checkZKrole.sh来获取当前的角色,如下: 1234[root@zookeeper1 ~]# cat checkZKrole.sh #!/bin/bashrole=$(sh /usr/zookeeper/bin/zkServer.sh status| cut -d" " -f2)echo $role 执行效果如下: 本地执行没问题,然后在zabbix-agentd.conf里也把这个脚本添加到自定义监控项里: 重启了zabbix-agent后,发现一个很奇怪的现象,在zabbix-server里使用zabbix-get去拿值的时候拿到的是contacting,如图: 从上图可见,同样在127.1.1.28里取值,proc.num没问题,而且是秒取,但是这个自定义项就取不到。 我怀疑是脚本的问题,于是我改成一个单纯的echo,如下: 12345[root@zookeeper1 ~]# cat checkZKrole.sh #!/bin/bashrole=$(sh /usr/zookeeper/bin/zkServer.sh status| cut -d" " -f2)#echo $roleecho woshinibaba 这一次的返回值是正常的,可见不是脚本的问题: 那是他妈的什么问题,真是见了鬼了…后来想干脆写一个crontab,让crontab把角色写到本地,然后再用cut命令切开把结果当做zabbix_get的目标。但是在这里发现了问题所在,当我的crontab是* * * * * cd /usr/zookeeper/bin/; ./zkServer.sh status > /tmp/role.txt > /dev/null 2>&1,发现/tmp/role.txt里根本没有值,应该是crontab在执行有参数的命令的时候出现了问题。 后来发现了,原来是sudo搞得鬼,如果是由于zookeeper是root用户启动的,所以只有root用户能成功访问,如果是sudo的话,那么就会返回“Error contacting service. It is probably not running.”,所以截取出来的部分就是contacting,如图: zabbix_get执行脚本超时在监控mq队列时候,同样也需要到了自定义监控项,我写了几个简单的脚本如下: 1234567891011121314151617[root@dahuatech zabbix]# cat monitor_mq.sh #!/bin/ship=$1queuename=$2type=$3case ${type} in Pending) curl -s -u 'admin:admin' "http://${ip}:8161/admin/queues.jsp"|grep "${queuename}</a></td>" -A 5|sed -n '2p'|egrep -o '[0-9]+' ;; Enqueued) curl -s -u 'admin:admin' "http://${ip}:8161/admin/queues.jsp"|grep "${queuename}</a></td>" -A 5|sed -n '4p'|egrep -o '[0-9]+' ;; Dequeued) curl -s -u 'admin:admin' "http://${ip}:8161/admin/queues.jsp"|grep "${queuename}</a></td>" -A 5|sed -n '5p'|egrep -o '[0-9]+' ;;esac 配置了UserParameter=activemq.check[*],sh /etc/zabbix/monitor_mq.sh $1 $2 $3放到zabbix-agentd.conf里,重启了zabbix-agent。在zabbix-server配置了对应的item,如图: 然后在本地执行这个脚本,发现回值秒取,但是同样在zabbix-get里使用,就是timeout: 后来发现原来自己摆了一个乌龙,在zabbix-get的时候不能使用{HOST.IP},因为zabbix-get不识别他,但是zabbix-server是识别的,所以在脚本里把ip=$1改成ip=真实的IP地址即可。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>zabbix</tag>
<tag>zookeeper</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Mysql主从同步的几个要点总结]]></title>
<url>%2F2018%2F10%2F23%2FMysql%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5%E7%9A%84%E5%87%A0%E4%B8%AA%E8%A6%81%E7%82%B9%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[同步原理主从同步是Mysql非常常见的一个应用,也是非常重要的监控之处,这里简单总结在配置Mysql时候的几个要点,防止以后自己踩坑。 先说一下主从同步的原理,就是主数据库在数据库更新的时候会更新自己的binlog,同时也会向读数据库(一个或多个)传递这个binlog,此时从库开始一个io_thread这个线程用来接收这个binlog,然后把binlog写入到自己的relaylog,当relaylog发现有数据更新了,就开始一个sql_thread来按照主库更新自己的库,这样达到了“主库读库一致”的效果。图示如下: 上述过程:主从延迟:「步骤2」开始,到「步骤7」执行结束。步骤 2:存储引擎处理,时间极短步骤 3:文件更新通知,磁盘读取延迟步骤 4:Bin Log 文件更新的传输延迟,单线程步骤 5:磁盘写入延迟步骤 6:文件更新通知,磁盘读取延迟步骤 7:SQL 执行时长 要监控主从同步是否出现异常,可以通过show slave status\G里的Seconds_Behind_Master字段来查看,如图: 但是要注意!Seconds_Behind_Master是有前提的,那就是主库跟读库之间的网络情况要良好,因为这个字段是从属服务器SQL线程和从属服务器I/O线程之间的时间差距,(即比较binlog和relaylog执行sql的timestamp时间差),单位是秒。如果主服务器和从属服务器之间的网络连接较快,则从属服务器I/O线程会非常接近主服务器,所以本字段能够十分近似地指示,从属服务器SQL线程比主服务器落后多少。如果网络较慢,则这种指示不准确;从属SQL线程经常会赶上读取速度较慢地从属服务器I/O线程,因此,Seconds_Behind_Master经常显示值为0。即使I/O线程落后于主服务器时,也是如此。换句话说,本列只对速度快的网络有用。 要点总结 主库和读库的mysql版本保持一致,硬件情况也保持一致; binlog文件在生产系统中不易过大,建议小于500m,不然容易拖慢数据库性能; 设置slave前先检查一下设置的账号能不能远程登陆; 在设置多个库同步时,一个binlog-do-db参数对应一个库,不能一行写多个库; 如果出现了Slave_IO_Running: No这个状态,去主库上show master status\G,查看一下是否file跟从库的file是不是对不上; 代码里避免出现“查询读库后马上到主库操作”的字段,由于主从同步有延迟,这样很有可能会出现前端多次请求,而从库一致无法从主库得到最新的数据消息,所以sql被执行了好几次的错误,这样的情况可以考虑加入“可以用唯一索引限制”或者用insert … select … where not exist这种方式; 主库的慢sql太多的话,也会影响主从同步; 参考资料https://dba.stackexchange.com/questions/24793/mysql-replication-slave-is-continuously-lagging-behind-masterhttp://ningg.top/inside-mysql-master-slave-delay/http://database.51cto.com/art/201108/287653.htmhttps://zhuanlan.zhihu.com/p/28554242]]></content>
<categories>
<category>大牛之路</category>
</categories>
<tags>
<tag>Mysql</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用Dockbix监控进程]]></title>
<url>%2F2018%2F10%2F15%2F%E4%BD%BF%E7%94%A8dockbix%E7%9B%91%E6%8E%A7%E8%BF%9B%E7%A8%8B%2F</url>
<content type="text"><![CDATA[之前在https://rorschachchan.github.io/2018/05/17/%E4%BD%BF%E7%94%A8zabbix%E5%8E%BB%E7%9B%91%E6%8E%A7docker%E5%AE%B9%E5%99%A8/ 介绍了如何使用dockbix去自动监控容器的cpu、mem和端口等值。而本文的内容就是讲述如何使用dockbix监控进程。 服务器情况如下:172.31.0.77,普通模式安装zabbix-server;172.16.0.194,服务器里有两个容器,一个是dockbix,另一个是具体的服务,里面是一个centos 7跑着nginx和php两个进程,如图: 如果你启动dockbix的语句是这样的话: 12345678910docker run \ --name=dockbix-agent-xxl \ --net=host \ --privileged \ -v /:/rootfs \ -v /var/run:/var/run \ --restart unless-stopped \ -e "ZA_Server=zabbix-server的IP地址" \ -e "ZA_ServerActive=zabbix-server的IP地址" \ -d monitoringartist/dockbix-agent-xxl-limited:latest 那么发现监控进程是失败的,如图: 原因就是dockbix和具体服务之间是两个独立的进程,所以dockbix无法访问到另一个容器的进程情况,这样就要干掉原有的dockbix,并且更改一下dockbix的启动语句: 1234567891011docker run \ --name=dockbix-agent-xxl \ --net=host \ --pid=host \ #增加这句话 --privileged \ -v /:/rootfs \ -v /var/run:/var/run \ --restart unless-stopped \ -e "ZA_Server=172.31.0.77" \ -e "ZA_ServerActive=172.31.0.77" \ -d monitoringartist/dockbix-agent-xxl-limited:latest 然后再去重新使用zabbix-get命令,就可以获取到进程了! 默认下,所有的容器都启用了PID命名空间。PID命名空间提供了进程的分离。PID命名空间删除系统进程视图,允许进程ID可重用,包括pid 1。docker run的时候添加了--pid=host就是允许容器内的进程可以查看主机的所有进程。 如果是不要看所有主机的进程,而只是看某一个容器的进程,其他进程pid不看怎么设置呢? 12docker run --name my-redis -d redis #假设你启动了一个名叫my-redis的容器docker run -it --pid=container:my-redis my_strace_docker_image bash #在建立一个my_strace_docker_imag容器,只与my-redis共享pid 如果zabbix-server发现容器内的某个服务死了,要进入容器里重启服务怎么办?答曰:docker exec 容器ID /bin/bash -c "启动服务命令" 参考资料https://github.com/monitoringartist/dockbix-agent-xxl/issues/42https://www.zabbix.com/documentation/3.4/zh/manual/appendix/items/proc_mem_num_noteshttps://docs.docker.com/engine/reference/run/#imagetag]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>zabbix</tag>
<tag>Docker</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Mysql.sock没了怎么办?]]></title>
<url>%2F2018%2F10%2F15%2FMysql-sock%E6%B2%A1%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F%2F</url>
<content type="text"><![CDATA[今天在调整jumpserver堡垒机资产用户的时候,在点击“更新”的时候,爆出127.0.0.1:3306无法被访问,于是登录到服务器里一看,发现mysql进程挂了。先检查服务器存储空间,发现还很富裕,于是就启动mysql,爆出来如下错误: 12[root@lcshop-jumpserver ~]# mysqlERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (111) 然后来到/var/lib/mysql/里,瞅着这个紫了吧唧的mysql.sock,脑子一抽,把它删了… 删了… 这尼玛,再次启动mysql,错误码从111变成2: 12[root@lcshop-jumpserver mysql]# mysqlERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2) 这一下就尴尬了,mysql.sock没了怎么生成?有人说“重启服务器可以生成”,事实证明这就是纯粹的扯淡。真实的方法是:mysqld_safe &。 如果mysqld_safe &命令失败了,就要去查看一下mysql的日志,多半是某个文件权限不对,要改成mysql用户。 补充一句其他的问题:ImportError: libxslt.so.0: cannot open shared object file: No such file or directory,遇到这个问题怎么办? yum install libxslt-devel -y]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>mysql</tag>
<tag>jumpserver堡垒机</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker部署的几个tips]]></title>
<url>%2F2018%2F10%2F10%2FDocker%E9%83%A8%E7%BD%B2%E7%9A%84%E5%87%A0%E4%B8%AAtips%2F</url>
<content type="text"><![CDATA[公司的电商平台使用的是阿里云VPC网络,整个交换机和云服务器都是部署在D区。今天在部署测试环境的时候,发现无法购买服务器,在钉钉上与阿里云售后交涉后,接到噩耗—D区已经不再出售服务器了,如图: 没办法,只能把现有的服务器调高配置,在里面安装docker,尽可能的让各进程的环境彼此之间不受干扰。由于事发仓促,整个架构都要重新调整,镜像就先选用centos:latest,生成容器后在里面装环境以及git pull代码,把容器当做虚拟机来用了。 几个小提示 如果要pecl install swoole的话,要先yum install -y glibc-headers gcc-c++ kernel-headers gcc openssl pcre-devel和yum install -y openssl-devel; centos:latest镜像目前是7.5版本,如果要查看的话需要先安装lsb命令:yum install redhat-lsb -y; 如果容器里使用yum下载爆’Operation too slow. Less than 1000 bytes/sec transferred the last 30 seconds’,用yum -y install wget解决; 容器需要php7.2的环境的话,就要用最新的源: 12yum install epel-release -yrpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm 别忘了开机自启动docker进程:systemctl enable docker; yum install node npm之前要 123yum install -y epel-releasecurl --silent --location https://rpm.nodesource.com/setup_8.x | bash -yum install -y nodejs #这样版本是8.12,npm的版本是6.4.1 在容器里查看端口情况就要安装netstat命令:yum install -y net-tools; 将一个运行中的容器做成镜像的命令:docker commit 容器ID号 镜像名称; 进入容器最好不要用docker attach 容器ID的方式,而是用docker exec -it 容器ID /bin/bash,离开容器的时候也不要用exit或者ctrl + D,这样会将容器停止,而是用ctrl + P、ctrl + Q 或者ctrl + Q + P组合键退出,这样就不会终止容器运行; 容器默认的时间与宿主机的时间相差8个小时,可以在docker run的时候使用-v挂载的方法挂载宿主机的时间文件,比如:docker run --name 容器名 -v /etc/localtime:/etc/localtime:ro ...,或者在dockerfile里添加“设定时区”的语句: 123#设置时区RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo 'Asia/Shanghai' >/etc/timezone \ 容器映射默认情况下是tcp6的,这是正常的现象,如果telnet不通,请先去检查容器内的服务是否正常,比如在容器里curl 127.0.0.1 端口号; 使用docker top 容器id命令能获取的PID是容器内进程在宿主机上的pid,ppid是容器内进程在宿主机上的父进程pid; 如果多个容器要挂载一样的数据就是用-volumes-from,比如docker run --volume-from 容器ID号; 在容器外启动容器内部进程的方法是:docker exec 容器ID /bin/bash -c "对应的命令",在zabbix监控docker发现进程死了后,就可以用这个方法拉起来; 接上一条的说,docker跟虚拟机不同,它启动的时候是不会运行/etc/rc.d/rc.local的,如果想要Docker在启动后就自动运行/etc/rc.d/rc.local,请看https://github.com/johnnian/Blog/issues/13 里面说的方法; 容器内的进程是会映射到宿主机上的,举个例子,比如容器里运行了swoole,如图: 在宿主机上看也是能看到这个进程的: 参考资料http://blog.sina.com.cn/s/blog_5ff8e0a00102wmti.htmlhttps://outmanzzq.github.io/2018/01/11/docker-exit-without-stop/http://dockone.io/article/128https://blog.csdn.net/halcyonbaby/article/details/46884605https://stackoverflow.com/questions/30960686/difference-between-docker-attach-and-docker-exechttps://www.binss.me/blog/learn-docker-with-me-about-volume/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>docker</tag>
<tag>容器</tag>
</tags>
</entry>
<entry>
<title><![CDATA[容器报错:rpc error: code = 14 desc = grpc: the connection is unavailable]]></title>
<url>%2F2018%2F09%2F29%2F%E5%AE%B9%E5%99%A8%E6%8A%A5%E9%94%99%EF%BC%9Arpc-error-code-14-desc-grpc-the-connection-is-unavailable%2F</url>
<content type="text"><![CDATA[开发同学反馈某一个开发环境的机器卡的要命,我登录一看,内存已经被耗的差不多,但是一看top又看不出来哪个进程占用了很多的内存,如图: 换ps -e -o 'pid,comm,args,pcpu,rsz,vsz,stime,user,uid' | sort -nrk5看也没看出来个之乎者也。 发现这个服务器里有两个容器,但是很奇怪,用docker stats却无法获得他们的基础值: 明明容器都是up状态啊,于是我就尝试链接到其中一台,发现报错:rpc error: code = 14 desc = grpc: the connection is unavailable,而且不能restart和kill,如图: 使用docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc --debug,发现里面是这样: 后来在https://github.com/moby/moby/issues/30984 这个文章下面找到了一个跟我情况差不多的哥们,也是docker stats命令失效。解决方法是重启docker进程:systemctl restart docker.service,果然,重启之后在手动启动上面两个容器,容器就可以正常访问了: 服务器的内存情况也得到了一定的缓解: 后来跟开发复盘,原来是这个机器上一次死机了,没法关闭容器,只能直接在阿里云控制台重启,而正常的流程应该是先关闭容器再重启的。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>docker</tag>
<tag>容器</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Django前端输入变量通过内部脚本返回前端展示之三]]></title>
<url>%2F2018%2F09%2F26%2FDjango%E4%BD%BF%E7%94%A8form%E8%A1%A8%E5%8D%95%E5%88%A4%E6%96%AD%E8%BE%93%E5%85%A5%E5%80%BC%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%2F</url>
<content type="text"><![CDATA[背景说明python:3.6.5Django:2.1.1Project:Kubernetes,文件夹路径就是/django/Kubernetes/App:createyaml,文件夹路径就是/django/Kubernetes/createyaml前文地址:https://rorschachchan.github.io/2018/09/18/Django%E9%80%9A%E8%BF%87%E5%94%AF%E4%B8%80%E6%A0%87%E8%AF%86%E7%AC%A6%E5%B0%86%E5%90%8E%E5%8F%B0%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AF%B9%E5%BA%94%E8%BE%93%E5%87%BA/ 需求与解决思路对于表单而言,检查用户输入的信息是否合法是必然项。检查合法一般来说都是用JavaScript或JQuery。不过我是一个前端白痴,JavaScript对我来说就是天书。但是Django非常的贴心,在form表单里就准备了“验证输入内容”这个功能。 如果使用这个功能,首先先在app的views.py里导入form模块:from django import forms。 导入模块之后,设定一个类,这个类就是要在前端html页面中生成form表单中的input标签的,比如: 123456class YamlInfo(forms.Form): #定义的django表单 name = forms.CharField(error_messages={'required': u'此节点不能为空'},) #自定义错误信息 replicas = forms.DecimalField(max_digits=2,error_messages={'required': u'副本个数不能大于100'}) #最大只有2位数 labels_app = forms.CharField(error_messages={'required': u'此节点不能为空'}) containers_name = forms.CharField(error_messages={'required': u'此节点不能为空'}) containers_image = forms.CharField(error_messages={'required': u'此节点不能为空'}) 表单上输入的东西可能会有很多,根据实际情况哪些字段不能为空就把那些字段写到这个class里,在上面那个YamlInfo里把这五项配置对应的Django表单字段,比如replicas,这个字节代表的是容器副本个数,所以它只能是数字,而且我们不要求它大于100,就设定max为2。 创建完类之后,需要在html页面里根据类的对象创建html标签,然后再提交的时候,需要后台views.py把前端页面提交的数据封装到一个对象里:obj = YamlInfo(request.POST)。由于每个Django表单的实例都有一个内置的is_valid()方法,用来验证接收的数据是否合法。如果所有数据都合法,那么该方法将返回True,并将所有的表单数据转存到它的一个叫做cleaned_data的属性中,该属性是以个字典类型数据,然后对这组数据进行展示或者保存到数据库就随你便了;如果有一个数据是非法的,就可以return一个别的结果。 实际代码理论到此结束,先看views.py: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667from django.shortcuts import render,render_to_responsefrom django.http import HttpResponsefrom .models import parameter #引入数据库里的类from django import forms #引入模块class YamlInfo(forms.Form): #定义的django表单 name = forms.CharField(error_messages={'required': u'此节点不能为空'},) replicas = forms.DecimalField(max_digits=2,error_messages={'required': u'副本个数不能大于100'}) #最大只有2位数 labels_app = forms.CharField(error_messages={'required': u'此节点不能为空'}) containers_name = forms.CharField(error_messages={'required': u'此节点不能为空'}) containers_image = forms.CharField(error_messages={'required': u'此节点不能为空'})#create_yaml就是用来展示输入的页面而已def create_yaml(request): obj = YamlInfo() #创建form的对象 return render(request,'create_yaml.html',{'obj':obj}) #返回create_yaml这个模板,模板里的内容其实都是空的#yaml_list就是展示所有的输入情况def yaml_list(request): obj = YamlInfo() #创建form的对象 if request.method == 'POST': input_obj = YamlInfo(request.POST) #request.POST为提交过来的所有数据 if input_obj.is_valid(): data = input_obj.clean() #用clean()函数获取提交的数据 apiVersion = request.POST.get('apiVersion','v1') #POST.get方法获取到非form的对象 kind = request.POST.get('kind','RC') name = data['name'] #用data字典来获取form的对象 replicas = data['replicas'] labels_app = data['labels_app'] containers_name = data['containers_name'] containers_image = data['containers_image'] containerPort1 = request.POST.get('containerPort1',None) containerPort2 = request.POST.get('containerPort2',None) containers_name2 = request.POST.get('containers_name2',None) containers_image2 = request.POST.get('containers_image2',None) containerPort2_1 = request.POST.get('containerPort2_1',None) containerPort2_2 = request.POST.get('containerPort2_2',None) print (data) #可以在后台看到整个data的内容 else: #如果输入不合法,返回错误信息 error_msg = input_obj.errors #errors为错误信息 return render(request,'create_yaml.html',{'obj':input_obj,'errors':error_msg}) #将错误信息直接返回到前端页面去展示,刚刚输入的非法字段也保留 else: #如果不是post提交,那么就是展示数据里的情况 yamls = parameter.objects.all().order_by('-id') #以倒数展示,即新加的在上面 context = {} context['yamls'] = yamls return render_to_response('yaml_list.html',context) #返回yaml_list.html,里面有数据库的所有数据 Parameter = parameter() #将数据库的类实例化 Parameter.apiVersion = apiVersion Parameter.kind = kind Parameter.name = name Parameter.replicas = replicas Parameter.labels_app = labels_app Parameter.containers_name = containers_name Parameter.containers_image = containers_image Parameter.containerPort1 = containerPort1 Parameter.containerPort2 = containerPort2 Parameter.containers_name2 = containers_name2 Parameter.containers_image2 = containers_image2 Parameter.containerPort2_1 = containerPort2_1 Parameter.containerPort2_2 = containerPort2_2 Parameter.save() #保存这些到数据库里 yamls = parameter.objects.all().order_by('-id') context = {} context['yamls'] = yamls return render_to_response('yaml_list.html',context) 配置一下urls.py: 123456789from django.contrib import adminfrom django.urls import pathfrom createyaml import views #将app的views.py文件引入urlpatterns = [ path('admin/', admin.site.urls), #每个页面对应各自在views.py里的函数 path(r'create_yaml/', views.create_yaml), path(r'yaml_list/', views.yaml_list),] 配置一下用户输入的界面—create_yaml.html: 123456789101112131415161718192021222324252627282930313233343536373839<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>生成K8S所用的YAML文件</title> </head> <body> <h1>用户输入</h1> <h2>请注意!大小写敏感!!!</h2> <form action="/yaml_list/" method="post" name='yamllist'> {% csrf_token %} API版本: <select name='apiVersion'> <option value="v1" selected>v1</option> <option value="extensions/v1beta1">beta1</option> </select><br /> 任务类型: <select name='kind'> <option value="Pod" selected>Pod</option> <option value="Service">Service</option> <option value="Deployment">Deployment</option> <option value="ReplicationController">ReplicationController</option> </select><br /> <p>任务名称:{{ obj.name }} <span>{{ errors.name }}</span></p> <p>任务数量:{{ obj.replicas }} <span>{{ errors.replicas }}</span></p> <p>APP名称:{{ obj.labels_app }} <span>{{ errors.labels_app }}</span></p> <p>容器1名称:{{ obj.containers_name }} <span>{{ errors.containers_name }}</span></p> <p>容器1镜像:{{ obj.containers_image }} <span>{{ errors.containers_image }}</span></p> 容器1开放端口1:<input type="text" placeholder="没有可以不填" name="containerPort1" /><br /> 容器1开放端口2:<input type="text" placeholder="没有可以不填" name="containerPort2" /><br /> 容器2名称:<input type="text" placeholder="没有可以不填" name="containers_name2" /><br /> 容器2镜像:<input type="text" placeholder="没有可以不填" name="containers_image2" /><br /> 容器2开放端口1:<input type="text" placeholder="没有可以不填" name="containerPort2_1" /><br /> 容器2开放端口2:<input type="text" placeholder="没有可以不填" name="containerPort2_2" /><br /> <input type="reset" value="清除所有" /> <input type="submit" value="生成yaml文件" /> </form> </body></html> 而跳转后的yaml_list.html就是这样: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>yaml文件展示</title></head> <body> <h1>数据库里的yaml数据展示</h1> <table width="100%" border="1"> <thead> <a href="http://121.41.37.251:33664/create_yaml/"><button>返回</button></a> <!--插入按钮 开始--> <input type="button" value="执行" onclick="MsgBox()" /> <!--插入按钮 结束--> <!--引用JS代码以达到弹出对话框目的 开始--> <script language="javascript"> function MsgBox() //声明标识符 { confirm("确定要执行后台脚本么?"); //弹出对话框 } </script> <!--引用JS代码以达到弹出对话框目的 结束--> <br> <form> <tr> <td align="center">任务序号</td> <td align="center">yaml名称</td> <td align="center">api版本</td> <td align="center">任务类型</td> <td align="center">任务数量</td> <td align="center">对应应用</td> <td align="center">使用的第一个镜像名称</td> <td align="center">镜像1的第一个端口</td> <td align="center">镜像1的第二个端口</td> <td align="center">使用的第二个镜像名称</td> <td align="center">镜像2的第一个端口</td> <td align="center">镜像2的第二个端口</td> </tr> </thead> <tbody> {% for yaml in yamls %} <tr> <td><input type="radio" name="id" checked="checked"/>{{ yaml.id }} </td> <td align="center">{{ yaml.name }} </td> <td align="center">{{ yaml.apiVersion }}</td> <td align="center">{{ yaml.kind }}</td> <td align="center">{{ yaml.replicas }}</td> <td align="center">{{ yaml.labels_app }}</td> <td align="center">{{ yaml.containers_image }}</td> <td align="center">{{ yaml.containerPort1 }}</td> <td align="center">{{ yaml.containerPort2 }}</td> <td align="center">{{ yaml.containers_image2 }}</td> <td align="center">{{ yaml.containerPort2_1 }}</td> <td align="center">{{ yaml.containerPort2_2 }}</td> </tr> {% endfor %} </tbody> </table> </body></html> 启动django,我们来看一下效果! 不过说实话,对于用户来说,肯定选择题的感觉要比填空题好。所以到时候我们可以把阿里云容器仓库里的所有的镜像做成一个数据库,到时候映射到这个页面,让用户去在里面做选择而不是填空。而且django的form检查相比较JavaScript而言还是很粗糙的,如果是处女座的话,还是要搞JavaScript,而且两者也并不冲突,一个是对前端用户而言,一个是后台检查录入数据库的。 参考资料https://docs.djangoproject.com/en/2.1/topics/forms/ (官方文档)http://www.liujiangblog.com/course/django/152https://www.cnblogs.com/chenchao1990/p/5284237.htmlhttp://dokelung-blog.logdown.com/posts/221431-django-notes-8-form-validation-and-modelinghttps://www.jb51.net/article/103135.htm]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Django将某个数据库字段给多个app使用]]></title>
<url>%2F2018%2F09%2F25%2FDjango%E5%B0%86%E6%9F%90%E4%B8%AA%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AD%97%E6%AE%B5%E7%BB%99%E5%A4%9A%E4%B8%AAapp%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[前言Django里经常会有这样的一个需求—-同样的一组数据要给很多个app使用。比如一个运维系统,运维人员的名单就既要给“项目部署”这个APP用又要给“责任负责人”这个APP用。如果每次都要去跨应用去from XXX.models import xxx的话,代码感觉很不友好。那么要解决这个问题,就要用到django自带的ContentTypes框架。以下是所用软件版本:Django:2.1.1Python:3.6.4old app:Articlesnew app:read_stats 原始状态与前期配置目前在django的控制台页面的情况是这样的: 可见里面就一个叫Articles的app,点开之后,发现对应的项目也很简单,只有id和title这两个字段而已: 本次试验的目的就是新建立一个文章统计计数的app,在里面配置数据库,然后让原来的blog这个app能够使用得到新app的数据项。 首先先建立一个专门用来计数的app,比如就叫read_stat。那么就在django项目路径下python manage.py startapp read_stats,再把这个新的app名称添加到settings.py里: 12345678910INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'article', #先加载django自身的app,然后是第三方app,最后是自己开发的app 'read_stats',] 编辑一下read_stats里的models.py,创建模型先: 12345678910from django.db import modelsfrom django.contrib.contenttypes.fields import GenericForeignKey #这句话是固定的,引用类型from django.contrib.contenttypes.models import ContentType #这句话是固定的,引用类型# Create your models here.class ReadNum(models.Model): read_num = models.IntegerField(default=0) #设定read_num就是一个普通的数字 content_type = models.ForeignKey(ContentType,on_delete=models.DO_NOTHING) #说明这是一个外键,即关联的模型,加上后面的话的意思是:即使删除了这个字段也不会影响其他数据 object_id = models.PositiveIntegerField() #这里是一个主键,即pk content_object = GenericForeignKey("content_type","object_id") #通过上面两个变量,配置成一个通用的外键 通过使用一个content_type属性代替了实际的model(如Post,Picture),而object_id则代表了实际model中的一个实例的主键,其中,content_type和object_id的字段命名都是作为字符串参数传进content_object的。 配置了数据库,肯定需要python manage.py makemigrations和python manage.py migrate: 数据更新完毕之后,修改一下负责后台展示的admin.py: 1234567from django.contrib import adminfrom .models import ReadNum #引用ReadNum这个模型# Register your models here.@admin.register(ReadNum) #装饰器class ReadNumAdmin(admin.ModelAdmin): list_display = ('read_num','content_object') 此时刷新一下django页面就看到read_stats这个app已经注册成功了: 由于是新的,所以里面空空如也,点击一下ADD,就可以输入值了:Read num就是设定的“阅读次数”,Content type这个数据是一个选择项,选择需要对应的数据库模型,即Article这个app里的models.py的类—Article,而Object id就Articles对应的文章编号: 这样达到了后台配置“将Article应用里的第2篇文章的阅读次数上调到了99次”。 数据库的跨app配置刚才手动在后台配置完毕,但是目前这个read_num数据只能是在read_stats这个app里自嗨。要给让Article能够得到这个read_num的话,就需要通过模型获取到具体数值,这里要用到ContentType.objects.get_for_model方法。首先要配置Article下的models.py: 123456789101112131415161718from django.db import modelsfrom django.db.models.fields import exceptions #引入错误给try...except使用from django.contrib.contenttypes.models import ContentType #引入ContentTypefrom read_stats.models import ReadNum #从另一个app里引入类# Create your models here.class Article(models.Model): title = models.CharField(max_length=30) content = models.TextField() #这是它原来的数据库内容 #添加一个方法给admin.py使用,如果有就直接返回值(字符串),如果没有object就返回一个0 def get_read_num(self): try: ct = ContentType.objects.get_for_model(self) #确定ContentType readnum = ReadNum.objects.get(content_type=ct,object_id=self.pk) #每个readnum都是content_type和object_id对应的QuerySet return readnum.read_num #这样返回就是一个具体的值,不然只是一个数据 except exceptions.ObjectDoesNotExist: return 0 再修改Article下的admin.py,让后台可以体现出来read_num: 1234567from django.contrib import adminfrom .models import Article# Register your models here.@admin.register(Article)class Article(admin.ModelAdmin): list_display = ('id','title','get_read_num') #这里新加上刚才的那个方法 由于admin.py里返回的必须是字段,所以我们才在models.py里添加了一个方法去生成字段。 刷新一下Django后台页面,就看到效果了: 至此,这个read_num数据就同时被两个APP关联分享了。至于再把read_num通过一定的处理方法之后映射到html前端就很简单了。 参考资料https://docs.djangoproject.com/en/2.1/ref/contrib/contenttypes/ (官方文档)]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Django前端输入变量通过内部脚本返回前端展示之二]]></title>
<url>%2F2018%2F09%2F18%2FDjango%E9%80%9A%E8%BF%87%E5%94%AF%E4%B8%80%E6%A0%87%E8%AF%86%E7%AC%A6%E5%B0%86%E5%90%8E%E5%8F%B0%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AF%B9%E5%BA%94%E8%BE%93%E5%87%BA%2F</url>
<content type="text"><![CDATA[背景说明python:3.6.5Django:2.1.1Project:Kubernetes,文件夹路径就是/django/Kubernetes/App:createyaml,文件夹路径就是/django/Kubernetes/createyaml前文地址:https://rorschachchan.github.io/2018/09/13/Django%E5%88%B6%E4%BD%9C%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2%E7%94%9F%E6%88%90yaml%E6%96%87%E4%BB%B6%E4%B9%8B%E6%94%B9%E8%BF%9B%E7%89%88/ sqlite3的用法sqlite是django默认的数据库,如果只是存一点简单的数据,那么它是足够胜任的。如果在django的APP文件夹里配置了models.py而且执行了python manage.py makemigrations和python manage.py migrate的话,那么在project的文件夹里是会生成db.sqlite3这个文件的。至于如何命令行操作sqlite和python调用sqlite,请去看:http://blog.51cto.com/zengestudy/1904680 ,里面说的已经很清楚了。 不过要注意的是execute方法得到的是一个对象,是看不到具体的sql结果。还需要fetchall方法进一步的解析,这样得到的是一个列表,然后取其中的具体元素,如图: 使用唯一标识符由于yaml的参数是从前端传入的,如果同时有多个人传入数据,那么后端脚本在取参数就会出现错误:多个人在传入不同的数据之后得到的结果却是一样的,即服务器接收到的最后那个数据返回的结果。为了不出现这样的混乱,所以我们就要引入唯一标识符保证每个人得到都是他们的结果。 在数据库里是有一个主键的也就是id,它是django生成数据库的时候自带的private key,每一个id都是唯一的,既然唯一那肯定就是我们选做唯一标识符的首选。至于怎么用它,其实就是在原有的views.py上做一点小手脚。如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344from django.shortcuts import renderfrom django.http import HttpResponsefrom .models import parameter #引入同级的modes.py里的parameter类def create_yaml(request): return render(request,'create_yaml.html') #这个页面是用来输入各值def get_yaml(request): if request.method == 'POST': #如果是post传参,那么就记录下来 apiVersion = request.POST.get('apiVersion','v1') kind = request.POST.get('kind','RC') name = request.POST.get('name') replicas = request.POST.get('replicas','1') labels_app = request.POST.get('labels_app',None) containers_name = request.POST.get('containers_name',None) containers_image = request.POST.get('containers_image',None) containerPort1 = request.POST.get('containerPort1',None) containerPort2 = request.POST.get('containerPort2',None) containers_name2 = request.POST.get('containers_name2',None) containers_image2 = request.POST.get('containers_image2',None) containerPort2_1 = request.POST.get('containerPort2_1',None) containerPort2_2 = request.POST.get('containerPort2_2',None) signer = request.POST.get('signer', 'Micheal Jackson') else: return HttpResponse('404') Parameter = parameter() #将parameter实例化 Parameter.apiVersion = apiVersion #把刚刚从前端得到的值对应赋值 Parameter.kind = kind Parameter.name = name Parameter.replicas = replicas Parameter.labels_app = labels_app Parameter.containers_name = containers_name Parameter.containers_image = containers_image Parameter.containerPort1 = containerPort1 Parameter.containerPort2 = containerPort2 Parameter.containers_name2 = containers_name2 Parameter.containers_image2 = containers_image2 Parameter.containerPort2_1 = containerPort2_1 Parameter.containerPort2_2 = containerPort2_2 Parameter.save() #保存修改 yaml = parameter.objects.get(id=Parameter.id) #通过object.get方法是得到保存的所有值,但是我们只要本次的值,也就是id与private key一致的 return HttpResponse('api版本:%s yaml类型:%s yaml名称:%s 副本数量:%s yaml所属APP:%s 容器名称:%s 容器镜像名:%s' % (yaml.apiVersion,yaml.kind,yaml.name,yaml.replicas,yaml.labels_app,yaml.containers_name,yaml.containers_image))) #输出部分刚输入的值到页面,检查一下是否正确 urls.py如下: 123456789from django.contrib import adminfrom django.urls import pathfrom createyaml import viewsurlpatterns = [ path('admin/', admin.site.urls), path(r'create_yaml/', views.create_yaml), path(r'get_yaml/', views.get_yaml),] 启动django,在前端页面测试一下看看是否得到的结果就是本次输入的结果,如图: 可以看到,返回的页面正确的输出了本次各个参数!剩下还有三部分: 做一个python脚本,把脚本加工的结果返回到前端; 用css/js把界面加工一下; 加入javascript,在前端输入的时候判断输入值是否合法; 参考资料http://blog.51cto.com/lannyma/1735751http://www.liujiangblog.com/course/django/152https://www.jianshu.com/p/46188b39eae5]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>python</tag>
<tag>html</tag>
</tags>
</entry>
<entry>
<title><![CDATA[调用阿里云api去修改域名对应IP]]></title>
<url>%2F2018%2F09%2F17%2F%E8%B0%83%E7%94%A8%E9%98%BF%E9%87%8C%E4%BA%91api%E5%8E%BB%E4%BF%AE%E6%94%B9%E5%9F%9F%E5%90%8D%2F</url>
<content type="text"><![CDATA[问题简述以阿里云厂家为例,假设我们有一个网站,它的服务器、数据库、负载均衡都部署在杭州区可用区B,将IP A绑定到某个域名上,启动了系统之后为客户提供服务。那么如果现在要对这套系统进行灾备,应该怎么做? 第一个方法:在可用区D复制一模一样的环境,然后以“主备服务器组”的方式配置一下负载均衡:如果端口监听不正常就会切换到备用服务器上,监听正常了再切回来。但是这个方式有一个问题,就是当前模式阿里云的主备切换是不支持HTTPS/HTTP的,如图: 可见,这种方式是有很大的局限性的。 那既然同是花钱,干脆就做一个异地容灾,整套系统在其他的地理区域比如上海区也复制一遍,把上海区的B IP也绑定到这个网站域名上,阿里云的域名解析是支持多IP绑定同一个域名的。平时的时候,上海区的IP被域名解析的权重是0,一旦杭州区出现了某些线路方面的硬件问题,那么就将杭州区的权重降成0,同时提高上海区的权重,这样用户就会直接访问到上海区的系统。 理想是丰满的,但是现实是骨感的,因为阿里云的权重配置区域是1~100,而不是0~100,如下图: 也就是说这个云解析的负载均衡是不能当做主备切换使用的,如果想要通过阿里云解析来达到主备切换的目的,方法只能是升级VIP DNS,配置网站监控,具体操作是https://help.aliyun.com/document_detail/59372.html?spm=5176.215331.1147916.23.65de614dac85Sw 。但是这个VIP升级是需要钱的,如果监控的网站越多,花费越大,如果老板不肯掏这份钱,那就只能换条路走。 脚本内容想来想去,还是老办法—-调用阿里云API修改云解析记录达到切换IP的目的。脚本如下,这里我采取了命令行交互的形式,实际上都是将域名IP写死的: 1234567891011121314151617181920212223242526272829303132333435363738#!/usr/bin/env python#coding=utf-8#此脚本版本是2.7,用来修改阿里云云解析IP地址,使用之前请先安装sdk:pip install aliyun-python-sdk-domainimport jsonfrom aliyunsdkcore.client import AcsClientfrom aliyunsdkcore.request import CommonRequestprint "请注意!本脚本只会修改lechange.com域名下的A记录!!!"RRKeyWord = raw_input("请输入您要修改的域名:")Value = raw_input("请输入新的IP:")client = AcsClient('这里是AK', '这里是SK','cn-hangzhou')request = CommonRequest()request.set_accept_format('json')request.set_domain('alidns.aliyuncs.com')request.set_method('POST')request.set_version('2015-01-09')def getRecordId(RRKeyWord): global RecordId request.set_action_name('DescribeDomainRecords') request.add_query_param('DomainName', 'lechange.com') #这里写死了lechange.com域名 request.add_query_param('RRKeyWord', RRKeyWord) request.add_query_param('TypeKeyWord', 'A') response = client.do_action_with_exception(request) encode_json = json.loads(response) RecordId = encode_json['DomainRecords']['Record'][0]['RecordId'] #需要获取这个RecordId def UpdateDomainRecord(RRKeyWord,Value): request.set_action_name('UpdateDomainRecord') request.add_query_param('RecordId', RecordId) request.add_query_param('RR', RRKeyWord) request.add_query_param('Type', 'A') request.add_query_param('Value', Value) response = client.do_action_with_exception(request)if __name__ == "__main__": getRecordId(RRKeyWord) UpdateDomainRecord(RRKeyWord,Value) 这个脚本比较粗糙,可以改进的地方如下: 判断输入的域名和IP是否符合格式的规范; 判断输入的域名是否存在; 如果添加错误,对应的报错; 搭配爬虫页面脚本使用,如果爬虫页面脚本出现了异常,那么直接启动这个脚本,并且发送微信/邮件通知! 效果展示整个脚本启动后效果如下: 参考资料https://help.aliyun.com/document_detail/29776.html?spm=a2c4g.11186623.2.37.d31b31dfNqojPThttps://help.aliyun.com/document_detail/44657.html?spm=a2c4g.11186623.6.579.4d1d7cd208aSgl]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python</tag>
<tag>阿里云</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Django前端输入变量通过内部脚本返回前端展示之一]]></title>
<url>%2F2018%2F09%2F13%2FDjango%E5%88%B6%E4%BD%9C%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2%E7%94%9F%E6%88%90yaml%E6%96%87%E4%BB%B6%E4%B9%8B%E6%94%B9%E8%BF%9B%E7%89%88%2F</url>
<content type="text"><![CDATA[前言之前搞了一个简易版的“通过前端页面生成yaml”的方法,地址在此:https://rorschachchan.github.io/2018/09/03/制作前端页面生成yaml文件/ 。但是这个方法实际上有很多的不足,比如说每一次生成记录就消失了,无法追溯,所以要引入数据库,把每一次的数据保存到数据库里。 整体的流程设计还是跟以前的一样: 制作一个create_yaml.html网页让用户输入相关数值,并且有两个按钮,一个是重置,一个是生成yaml供K8s使用; 数值保存到django的数据库里; 做一个脚本,脚本从django数据库里取值然后执行; 脚本的结果返回到get_yaml网页,它也有两个按钮,一个是返回,一个是执行此yaml; 本篇文章的内容是第一步和第二步,Django的project名是Kubernetes,app名是createyaml。 配置数据库由于这个小系统保存的数据量不多,所以我就直接使用django默认的db.sqlite3数据库。跑到Kubernetes/createyaml的models.py里,根据yaml的实际情况编写一下数据库各字段: 123456789101112131415161718192021222324252627282930313233from django.db import models # Create your models here.class parameter(models.Model): type = ( (U'Pod','Pod'), (U'Service','Service'), (U'Deployment','Deployment'), (U'ReplicationController','ReplicationController'), ) api_type = ( (U'v1','v1'), (U'extensions/v1beta1','beta1'), ) apiVersion = models.CharField(verbose_name='API版本',max_length=20,choices=api_type) kind = models.CharField(verbose_name='任务类型',max_length=50,choices=type) name = models.CharField(verbose_name='任务名称',max_length=100) replicas = models.CharField(verbose_name='任务数量',max_length=50,default='1') #默认情况下副本数是1 labels_app = models.CharField(verbose_name='APP名称',max_length=100) containers_name = models.CharField(verbose_name='容器1名称',max_length=100) containers_image = models.CharField(verbose_name='容器1镜像',max_length=100) containerPort1 = models.CharField(verbose_name='容器1开放端口1',max_length=25,blank=True) #可以为空,下同 containerPort2 = models.CharField(verbose_name='容器1开放端口2',max_length=25,blank=True) containers_name2 = models.CharField(verbose_name='容器2名称',max_length=100,blank=True) containers_image2 = models.CharField(verbose_name='容器2镜像',max_length=100,blank=True) containerPort2_1 = models.CharField(verbose_name='容器2开放端口1',max_length=25,blank=True) containerPort2_2 = models.CharField(verbose_name='容器2开放端口2',max_length=25,blank=True) signer = models.CharField(verbose_name='登记人',max_length=50, default='system') signtime = models.DateField(auto_now_add= True) #默认添加当前时间#返回相应的值def __unicode__(self): return self.name 保存之后,python manage.py makemigrations和python manage.py migrate,就会看到db.sqlite3文件在Kubernetes这个project文件夹里诞生了。 配置URL路由根据整体的流程设计所说,url.py就新增了如下几个路由: 12345urlpatterns = [ path(r'create_yaml/', views.create_yaml), #create_yaml网页里的内容就是views.py里的create_yaml函数,下同 path(r'get_yaml/', views.get_yaml), path(r'addok/', views.addok),] 在admin后台界面也要体现出每一次数据输入,于是就配置一下Kubernetes/createyaml/admin.py: 123456789101112from django.contrib import adminfrom .models import parameter #把parameter这个class引入# Register your models here.class parameterAdmin(admin.ModelAdmin): list_display = ('name','apiVersion','kind','replicas','labels_app','containers_name','containers_image','containerPort1','containers_name2','containers_image2','containerPort2_1','signer','signtime') #把models.py里的字段都添加进去 exclude = ['signer'] #signer字段不要添加 def save_model(self, request, obj, form, change): obj.signer = str(request.user) obj.save()admin.site.register(parameter,parameterAdmin) 准备工作完事,开始搞前端页面。 配置前端在createyaml文件夹下建立一个template文件夹,里面先写一个create_yaml.html: 12345678910111213141516171819202122232425262728293031323334353637383940<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>生成K8S所用的YAML文件</title> </head> <body> <h1>用户输入:</h1> <h2>请注意!大小写敏感!!!</h2> <form action="/get_yaml/" method="post" name='addyaml'> <!-- form action的意思就是,submit的指向就是/get_yaml/,以post形式传递 --> {% csrf_token %} API版本: <select name='apiVersion'> <option value="v1" selected>v1</option> <option value="extensions/v1beta1">beta1</option> </select><br /> 任务类型: <select name='kind'> <option value="Pod" selected>Pod</option> <option value="Service">Service</option> <option value="Deployment">Deployment</option> <option value="ReplicationController">ReplicationController</option> </select><br /> 任务名称:<input type="text" name="name" /><br /> 任务数量:<input type="text" placeholder="请输入阿拉伯数字" name="replicas" /><br /> APP名称:<input type="text" placeholder="对应的APP" name="labels_app" /><br /> 容器1名称:<input type="text" name="containers_name" /><br /> 容器1镜像:<input type="text" name="containers_image" /><br /> 容器1开放端口1:<input type="text" placeholder="没有可以不填" name="containerPort1" /><br /> 容器1开放端口2:<input type="text" placeholder="没有可以不填" name="containerPort2" /><br /> 容器2名称:<input type="text" placeholder="没有可以不填" name="containers_name2" /><br /> 容器2镜像:<input type="text" placeholder="没有可以不填" name="containers_image2" /><br /> 容器2开放端口1:<input type="text" placeholder="没有可以不填" name="containerPort2_1" /><br /> 容器2开放端口2:<input type="text" placeholder="没有可以不填" name="containerPort2_2" /><br /> <input type="reset" value="清除所有" /> <input type="submit" value="生成yaml文件" /> </form> </body></html> 写完了之后,再来一个addok.html: 123456789101112131415161718192021<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>添加成功</title> <style> * { margin: 0; padding: 0; } a{ text-decoration:none; } </style></head><body> <div> <p>添加成功</p> </div></body></html> 前端准备完毕。 配置views.pyviews.py里的具体函数是整个django的主心骨,内容如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647from django.shortcuts import renderfrom django.http import HttpResponse,HttpResponseRedirectdef create_yaml(request): return render(request,'create_yaml.html') #只是展现一个页面而已def get_yaml(request): if request.method == 'POST': #如果是POST就获取前端传入的值 apiVersion = request.POST.get('apiVersion','v1') kind = request.POST.get('kind','RC') name = request.POST.get('name') replicas = request.POST.get('replicas','1') labels_app = request.POST.get('labels_app',None) containers_name = request.POST.get('containers_name',None) containers_image = request.POST.get('containers_image',None) containerPort1 = request.POST.get('containerPort1',None) containerPort2 = request.POST.get('containerPort2',None) containers_name2 = request.POST.get('containers_name2',None) containers_image2 = request.POST.get('containers_image2',None) containerPort2_1 = request.POST.get('containerPort2_1',None) containerPort2_2 = request.POST.get('containerPort2_2',None) signer = request.POST.get('signer', 'Micheal Jackson') else: return HttpResponse('404') from createyaml.models import parameter #数据库对应项进行赋值 Parameter = parameter() Parameter.apiVersion = apiVersion Parameter.kind = kind Parameter.name = name Parameter.replicas = replicas Parameter.labels_app = labels_app Parameter.containers_name = containers_name Parameter.containers_image = containers_image Parameter.containerPort1 = containerPort1 Parameter.containerPort2 = containerPort2 Parameter.containers_name2 = containers_name2 Parameter.containers_image2 = containers_image2 Parameter.containerPort2_1 = containerPort2_1 Parameter.containerPort2_2 = containerPort2_2 Parameter.save() #保存到数据库里 # 重定向到添加成功页面 return HttpResponseRedirect('/addok/')def addok(request): return render(request,'addok.html') 效果验证启动django之后,首先先去admin后台看一下当前的情况,如图: 可以看到里面是有几个记录的,那么我们现在登录外网地址:端口/create_yaml,输入一些字段看一下效果: 再返回到admin后台刷新,发现刚才新加的任务已经体现出来了: 至此,就达到了“前端html传入数据,后端数据库记录”的效果。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[一篇旧文----《当今中国会不会发生革命》]]></title>
<url>%2F2018%2F09%2F06%2F%E4%B8%80%E7%89%87%E6%97%A7%E6%96%87-%E5%BD%93%E4%BB%8A%E4%B8%AD%E5%9B%BD%E4%BC%9A%E4%B8%8D%E4%BC%9A%E5%8F%91%E7%94%9F%E9%9D%A9%E5%91%BD%2F</url>
<content type="text"><![CDATA[苏振华对本文的初稿提出了中肯的批评和建议,在此致以感谢。 二十世纪中国是一个革命的世纪。二十世纪上半叶,中国经历的主要革命运动有辛亥革命、二次革命、五四运动、北伐战争和共产主义革命。1949年中国共产党取得政权后,又搞了许多具有社会革命性质的社会运动,其中最为著名的有土地改革、人民公社运动、大跃进和文化大革命。改革开放后,中国共产党逐渐从一个革命党转变为执政党,但是中国的一些知识分子、学生和民众却从共产党手中接过“革命的旗帜”,于是就有了1989年的学生运动以及最近的“零八宪章运动”和所谓“茉莉花运动”等集体行动的事件。当然也有知识分子提出中国应该“告别革命”,应该反对激进主义。这是一种应然性吁求,但问题在于:中国是否会再发生(或者能避免)一场革命性的社会动荡? 这一问题甚至引发中国政治精英的广泛关注。最近网上有文章说中共高层有不少人在阅读托克维尔(Alexis de Tocqueville)的《旧制度与大革命》(L’Ancien regime et la Revolution),幷说王岐山看完此书后曾担忧地表示:中国的现代化转型不会那么顺利;中国人自己的代价也没有付够。当然,革命一旦发生,人民将付出的代价在一定程度上是由革命性质决定。一般来说,政治革命(一场只改变政权的性质,而不改变社会经济结构的革命)给社会带来的震荡要远远低于社会革命(一场既改变政权的性质,又改变社会经济结构的革命),非暴力革命给社会带来的震荡要远远低于暴力革命。王岐山也许是在担心中国会发生一场暴力革命,甚至是暴力性的社会革命。 不管上述中共高层读书的传说可信度如何,有一点十分明确:虽然近年来中国政府在维护稳定上花了很大的力气,中国的经济在近三十年来取得了举世瞩目的发展,民众的生活水平在近年来也有了很大的提高,但中共高层丝毫没有减轻对在中国再发生一次革命的可能性的焦虑。中共高层为甚么会如此忧虑?当前中国与政权稳定相关的根本问题是甚么?本文试图在理论的指导下对当前中国面临的困境作出分析。 一 革命为甚么会发生:理论简述早期的西方理论都把现代化过程中所发生的巨大社会变迁看作是一个国家发生革命的主要诱导因子。这一理论的逻辑很简单:现代化带来了传统的生活方式和价值观的变化,给身处其中的人们带来很大的不适应和不确定性;同时,现代化过程也削弱了传统社会组织对于人们的控制,给革命造就了机会1。的确,世界上的革命无一不发生在正在发生巨大变化的社会之中,而巨大的社会变迁确实会给身处其境的人们带来多方面的不确定性。从这个意义上说,这种理论自有它的道理。但是,世界上每一个国家在现代化过程中都经历过巨大的社会变迁,却不是每个国家都发生了剧烈的革命。社会变迁充其量只能是引发革命的一个必要条件。 在过去的大多数时间,有些学者也常用阶级或者是利益集团的视角来解释一个国家革命的成功与否2。他们的逻辑也很简单:如果一个国家中的一个主要阶级拥护和加入了革命,那么革命就会成功;反之革命就不会发生,就是发生了也会失败。当今中国的不少学者也仍然会自觉或者不自觉地运用这一视角来分析中国社会的危机所在。依笔者所见,这类分析方法表现出了左派知识分子的天真,而反映出来的则是这些知识分子看待问题时的教条性。 这并不是说人们在现代社会中不会产生阶级认同。问题在于:每一个人在社会上都会同时拥有许多身份(比如一个人同时可具有如下的身份:工人、浙江人、男人、某些圈子中的一员、某个俱乐部的成员等),并且具有某一身份的人们之间又存在着巨大的差别(比如工人之间就有蓝领工和白领工、技术工和非技术工、熟练工和非熟练工、临时工和正式职工之间的差别等)。因此,除非存在巨大无比的外力,比如国家对社会上的一个主要人群的利益完全漠视,幷且对这一人群的抗争进行严厉的和系统性的镇压,否则那些被天真的知识分子所认定的“阶级”就很难形成强烈的认同感,去完成知识分子所赋予他们的“历史使命”。 当今世界只有两类大型群体会有着较为“天然的”强大认同感,那就是族群和宗教群体。他们所发起的抗争和革命也因此往往有较大的威力。在很大的程度上,当今所流行的各种“社会分层研究”都是过去知识分子的研究误区的某种产物。不同的社会分层方法除了对了解社会流动和指导政府的公共政策制订有一定的应用性意义外,从社会行动或革命的角度来看,其价值却十分有限。这当然是题外话。 1970年代后,西方学者开始强调国家的性质和结构性行为对革命产生乃至成功的影响3。这类理论背后的一个核心逻辑是:在当代交通和通讯技术的支持下,现代国家获得了古代国家完全没有的渗透社会的能力。与古代国家相比,现代国家的管治领域不但十分宽泛,而且它的政令更能严重影响到社会上绝大多数成员的利益。现代国家的这一性质导致了如下三个后果:第一,国家的错误政策非常容易触发民众大规模的针对国家的怨恨情绪;第二,国家的强势刺激了人们组织起来进行抗争,要求国家颁布和施行对自己群体有利的法律和社会政策;第三,部分人就会想到通过夺取国家的权力(即革命)来彻底改变国家的性质,通过掌握国家权力来推行他们的理想。在这种所谓“国家中心论”的视角下,西方学者做了大量的研究,幷逐渐产生了以下三点共识(即衡量一个国家发生革命可能性的三个维度):第一,革命不容易发生在一个有着效率较高的官僚集团的国家(官僚集团内的程序政治会增强国家精英的团结、国家决策的合理性和国家镇压机器的有效性);第二,革命不容易发生在一个对社会精英有着很强吸纳能力的国家;第三,革命不容易发生在一个对社会有着很强渗透力(不仅仅指由国家所控制的交通和通讯工具,而且指警察机构对社会的监控能力)的国家4。 以上的三个维度有很强的解释力。的确,早期的革命,包括法国革命(1789)、俄国革命(1917)、中国革命(1949)和伊朗革命(1979),都发生在用以上三个维度来衡量处境都不太妙的国家。其实,官僚集团的效率、国家对社会精英的吸纳能力,以及国家对社会的渗透能力,是任何国家进行有效统治的关键要素。一个没有这些能力或者是这三方面能力不足的现代国家,无论是民主国家还是威权国家,都会在其运行过程中遇到大量的困难。但问题是,长期以来在分析革命的可能性时,西方学者过于借重了这三个因素,因此直到1980年代他们还在强调苏联和东欧国家具有很大的政治稳定性(因为这些国家都有着比较有效率的官僚集团、对社会精英的吸纳能力和对社会的渗透力)5,而完全没有料想到革命竟然马上就在这些国家发生了,而且其中不少国家的革命都取得了成功。 笔者认为,在分析苏联和东欧国家爆发革命的可能性时,西方学者都忽略了国家权力的合法性基础和国家政权稳定性之间的关系这一维度的重要性。具体来说,一个国家的权力愈是建立在较为稳定的合法性基础之上,这一国家就愈不可能发生革命。苏联和东欧之所以发生革命,不仅仅是因为它们的经济没搞好、它们的军事落后、它们在民族问题上走入误区、它们的领导人采取了错误的政策等(这些因素都很重要),而且更在于这些国家没有把政权建立在一个比较稳定的合法性基础之上。笔者多年来对中外各国革命作出分析时不断强调国家的合法性基础与政权稳定性之间的紧密关系6。笔者认为,西方学者所着重的三个维度都是国家统治手段中偏“硬件”性质的成份,而国家的合法性基础和政权稳定性则构成了国家统治的关键性“软件”,它们缺一不可。 二 合法性和政权的稳定性国家虽然掌握着强大的官僚组织以及军队与警察等武装力量,但是其统治的有效性仍必须依赖于国家政权在大众(包括国家官员)心目中的合法性。考察古今中外的统治史,我们会发觉国家在寻求统治合法性时只能采取以下三种方式:通过一种价值性的承诺、通过提供公共服务、通过一个普遍被接受的国家领导选拔程序。相应地,我们可以界定三种理想状态的国家合法性基础:意识形态型、绩效型和程序型7。如果一个国家统治的正当性是基于一个被民众广为信仰的价值体系,我们可以说这个国家的统治是基于意识形态合法性;如果一个国家统治的正当性来源于国家向社会提供公共物品的能力时,这个国家的统治则基于绩效合法性;如果一个国家的领导人是通过一个被大多数人所认可的程序而产生,这一国家的统治则基于程序合法性。 需要强调的是,以上定义的是国家合法性来源的三个理想类型(ideal types)。现实中,任何国家都不会把合法性完全建立在某一理想类型之上;或者说,任何国家的合法性来源都是这些理想类型的一个混合体。但是,在某一历史时期内,某一理想类型往往会成为一个国家统治最为重要的基础,幷在很大程度上定义了一个国家的性质。 现在让我们来讨论不同的国家合法性基础和政权稳定性之间的关系。 (一)意识形态合法性意识形态是国家统治的一个最为根本的合法性基础。一个国家如果把执政基础完全建立在某一意识形态之上,那是不行的;但是,一个国家的执政如果没有意识形态作为基础,则是万万不行的。当大多数的民众都认同国家所推崇的某一意识形态时,这种意识形态不仅仅为国家的统治提供了道德性依据,而且为社会提供了一个“核心价值观”。如果一个国家有一个被广为接受的核心价值观,统治成本就会大大降低。 需要强调的是,核心价值观不能是“八荣八耻”,也不能是“雷锋精神”,因为这些都只能是一个国家的从属性价值观,只有核心价值观才有助于建立国家的合法性基础。国家的核心价值观必须是一种宏大的给予历史以某种道德意义的叙事(即西方后现代学者所说的“宏大叙事”[grand narrative])。美国中学教科书上所描述的美国建国历史以及那些由建国时期政治家所确定的建国原则和理念,就是核心价值观的一个例子;西周初期所形成的“天命论”以及在西周历史中逐渐得以完善的“宗法制度”是有周一代的核心价值观,幷对古代中国的政治哲学和政治文化产生过重大的影响;当代中国学生在学校里学过的围绕着历史唯物主义和“只有共产党才能救中国”而展开的中国近代史叙事,也是核心价值观的一个例子。当然,美国的宏大叙事在其社会中仍然可以获得广泛的认同,而中国教科书中的叙事方式和内容在国内已经没有多少人真正认同了,幷且中国政府至今也没有创造出一套能被广泛认同的宏大叙事。这一意识形态的缺失所导致的后果就是核心价值观的缺乏,幷给当下中国政府的执政带来了很大的困扰。此是后话。 不同的意识形态有着不同的性质,幷对国家政权的稳定性有着不同的影响。意识形态合法性有三个主要类型:领袖魅力型、世俗意识形态型、宗教意识形态型。在这三个类型中,领袖的魅力(近似于韦伯所说的“克里斯玛合法性”)最不能给予政权一个稳定的合法性基础,因为领袖的寿命有限。 一般来说,世俗意识形态对大众所作的承诺比较容易被验证。一旦当国家不能兑现那些承诺,就会产生合法性危机。从这个意义上来说,世俗意识形态也不是一个稳定的合法性基础。但是如果我们把世俗意识形态进一步细分,就会发觉不同的意识形态对人性有不同的要求和对民众有不同的许诺。一般来说,要是一种意识形态对人性的要求愈接近于人的本性幷且其许诺愈不容易被证伪,这一意识形态就愈能为国家的合法性提供一个可靠的基础。比如美国建立在个人主义基础上的“机会之地”(Land of Opportunity)这一意识形态,不但与人的竞争和趋利本性十分接近,而且很难被证伪。这一意识形态有着人们所说的“钱币落在正面我赢,落在反面你输”(heads I win, tails you lose)的性质:你的成功证明了这意识形态的正确性,而你没有成功很容易被解释为是你没有付出足够或恰当的努力。与之相比较,“共产主义”这一意识形态就很难为一个政权提供稳定的合法性基础。共产主义意识形态不但建立在一个过于理想的人性的基础之上,幷且承诺提供一个比其他社会制度更为完美的世俗世界,例如“各尽所能、按需分配”之类。如果一个国家把共产主义意识形态作为合法性基础,一旦国家不能兑现相应的承诺,民众马上就会产生“信仰危机”,从而给国家带来合法性危机。 但是从理论上来说,即使一个国家把合法性建立在像共产主义这样很不牢靠的意识形态之上,这一国家也是有可能取得较为长久的政权稳定的。这里的诀窍是:当大多数民众还相信这一意识形态时,国家就应该采用选举(程序合法性)来补充共产主义意识形态的内禀不稳定性。因为一旦有了选举,幷且在社会上的大多数民众都认可共产主义意识形态的情况下,当政府搞得不好时,候选人就可以攻击政府没有带领人民在共产主义的“康庄大道”上正确地前进,民众就会去怪罪当朝政府的施政,而不是从意识形态本身的误区来检讨国家中所存在的根本问题。读者可以假设,如果中国在毛泽东时代能搞出一个共产党领导下的民主社会的话,今天的中国也许就不会面临如此严重的意识形态合法性危机。 以上的逻辑还支持了以下的推论:宗教意识形态要比任何世俗意识形态更能为一个国家提供稳定的合法性基础。宗教源自于人的可怜的本性──因为害怕失去和死亡而无限放大生命的意义。宗教的承诺也不具有可验证性──“来世”、“净土”或者“天堂”这样的宗教承诺既十分动人又无法验证,而对于宗教来说,最具权威的克里斯玛都是不存在于世俗世界的“神”、“佛”或者是“圣人”。宗教意识形态与人性的贴近和承诺的无法验证性,赋予那些把国家合法性建基于宗教意识形态之上的国家很大的政权稳定性。 不过,在现代社会,宗教意识形态合法性的最大弱点来自宗教力量和国家政权之间的紧张。现代社会极其复杂且变化极快。为了适应新的变化,国家政权就必须以务实的态度来处理日益复杂的世俗性事物,但是国家的务实态度及其所带来的社会后果势必会招来具有强烈保守倾向的宗教力量的反对。由政教斗争所导致的政权不稳定性,对于那些把宗教意识形态作为合法性基础的国家来说,是必定要面临的一个难题。当今伊朗的政治就在较大程度上受到这一因素的困扰。 (二)绩效合法性任何一个政府都需要为治下的民众提供必要的公共服务,例如仲裁、维持公共秩序、保证人身安全、保卫国家等。这个层面上的绩效是绝不可少的。如果一个政府没有能力提供这些最为基本的公共物品,相应的国家就不会存在,即便存在也会很快垮台。这里所说的“绩效合法性”,指的是国家领导集团在一个更为进取的层面上积极创造绩效以获取合法性。 获取这一合法性的手段可分为三种亚类型:领导经济发展、官员作为民众的道德表率和炒作民族主义情绪。但是,这三种手段都不能为国家提供一个稳定的合法性基础。首先,没有一个国家能保证经济的永久高增长。其次,把官员的道德表率作为国家合法性基础就会将贪污这样在法律层面上能解决的问题提升为政治问题,从而从根本上削弱了国家的合法性。最后,如果在和平时期政府经常以炒作国际危机来提高其统治合法性的话,这一国家的国际环境就会日趋险恶,幷且大量的极端民族主义者就会在这一国家中产生。这将推动一个国家朝着战争的方向发展,后果不堪设想。 总之,当一个国家的合法性系于绩效承诺时,这一国家的政府就必须设法来兑现这些承诺。如果这些绩效承诺得到了兑现,民众的欲望就会提高,幷对政府提出更高的要求,而政府则不得不把民众不断提高的要求作为新的、更新的,甚至是即时的工作目标。但是,一旦政府不能够兑现其承诺时,这一国家马上就会出现合法性危机。 (三)程序合法性现代社会到来之前,除了古希腊之外,程序始终不是世界各国权力合法性的一个重要基础。这幷不是说在古代政府首脑产生的背后没有程序可言,而是说这些程序只在一小部分精英之间才有意义,幷且这些程序在国家政治中不占有像今天的选举政治般重要的地位。笔者认为,以下三个原因使得程序合法性在现代政治中的地位不断上升: 第一,现代国家绝大多数都采取了政教分离原则,宗教意识形态不再是国家的主要合法性来源,或者说现代国家失去了古代国家所拥有的一个十分稳定的合法性基础;第二,现代国家的政府管理的事情愈来愈多,这就使得绩效在现代国家合法性中的地位大大增强,幷给现代国家的政治带来很大的不稳定性;第三,在现代技术的支持下,政府的统治能力不断加强,民众生活受到国家政策愈来愈严重的影响。在这一背景下,怎么控制政府的权力,幷使之不滥用权力,对广大民众来说就变得十分迫切。 我们可以从多种视角来解释为甚么民主政治会在现代国家中兴起。就本文的角度而言,民主兴起的一个重要原因就是现代国家意识形态合法性不足幷且严重倚重于绩效合法性,这就使得国家不得不依靠程序合法性来获得政权的稳定性。 由于以下原因,现代意义上的程序合法性(即民主选举)会给国家政权带来很大的稳定性8: 第一,一旦国家首脑是由民选产生,只要选举被认为是公正的,执政者即使在上台后表现很差,也不会影响政府执政的合法性。用通俗的话说,在绩效合法性的统治基础上,当官如果不为民作主,就有被赶回家卖红薯的危险;而在程序合法性的统治基础上,当官即使不为民作主,也至少得当完一届才回家卖红薯。从这个意义上说,程序合法性大大减低了民众对政府执政的压力。 第二,当一个国家有了程序合法性后,即使有执政者被赶下台也不是甚么大事。这是因为程序合法性在很大程度上把政府和政体分开了。政府即使垮台(比如水门事件[Watergate Scandal]后的尼克松[Richard M. Nixon]政府),政体也不会受到根本性的动摇。 第三,当一个国家有了程序合法性后,民众的不满在相当程度上可以通过选举或其他常规程序的政府更迭而得到缓解。一旦民众有了选择,他们就难以联合起来进行革命,这也给国家政权带来了稳定性。 第四,一旦当官的不为民作主也没有马上就被赶回家卖红薯的危险的时候,公开批评国家领导就不是甚么大事了,这就给言论和结社自由提供了基础。但这自由同时也约束了人民的行为,缓解了社会矛盾,从而构成了政权稳定的一个重要机制。这是因为言论和结社自由让社会上各种思想及利益的交流和竞争,使人们对社会其他群体的利益有了更深的理解,对社会现状有了现实感。同样重要的是,一旦有了言论和结社自由,现代社会的多样性势必会导致社会组织在利益和观点上的分化,这些组织互相牵制使得任何全民性的革命运动变得不大可能。 但就稳定国家政权而言,程序合法性也有着很多弱点,其中最为重要的是它背后必须有一个核心价值观支撑,或者说只有在竞选各方都服从同一意识形态(即“忠诚反对”)时,程序合法性才能为国家提供政权稳定性。如第二次世界大战前的德国,共产党、纳粹党和社会民主党各自有着完全不同的意识形态,幷且共产党和纳粹党都想利用选举来夺取政权,把国家彻底引向对自己有利的方面,形成赢者通吃的格局,选举在这种情形下就不可能成为国家政权稳定的基础。从这个意义上说,一个政治上最为稳定的国家(或者说最不可能发生革命的国家)应该是一个同时拥有意识形态合法性和程序合法性的国家:程序合法性需要强有力的意识形态合法性的支持,幷且程序合法性又是维持国家的意识形态合法性的关键。 三 有关中国政府合法性的经验研究在“世界价值观调查”(World Values Survey)和“亚洲民主动态调查”(Asian Barometer Survey)等调查数据基础上,一些学者对中国的国家合法性进行了研究。他们的一个重要发现是:中国民众对政府的认可度要远远高于许多西方民众对他们政府的认可度。他们于是就得出中国政局稳定、国家具有很高的合法性这一结论9。一般来说,我们都会相信这些研究的结论是成立的。这些学者都受过严格的西方学术训练,他们的材料所展示的也是全国民众的普遍看法,而不是少数人的极端观点。同时,中国政府近年来加强了吏治,采取了一系列的“亲民政策”,这些政策应该说是取得一定效果的。笔者近年来在全国范围内与农村和城市的各界民众进行了不少交流,感到中国百姓的生活水平在近年来有了普遍的和显著的提高,或者说大多数百姓确实从国家的政策中获得了实惠。这些学者的研究结果所反映的正是民众对于当今政府的绩效在一定程度上的认可。 但问题是,从“百姓对当下政府的绩效是肯定的”这一现象中,我们是不能推论出“这个国家的政局是稳定的”这样一个结论的。遍览世界各国,民众对政府绩效的评价,可以说是说变就变的。在西方,民众对政府的认可度数月内就可以波动许多个百分点(他们对政府的认可度有时甚至低至百分之十几)。在西方国家,民众对政府绩效的认可度与国家政局的稳定性之间没有很大的关系,因为西方国家合法性的根本基础不是政府的绩效,而是被主流精英和人民所认可的核心价值观和具有程序公正的选举。但是在中国,百姓对政府执政绩效的认可度与政局的稳定却有着密切的关系:如果中国百姓对政府绩效的认可显著下跌的话,的确是有可能引发一场大规模的政治波动甚至革命的。这背后的原因很简单:共产主义意识形态在中国已经式微,但是国家又拿不出其他有效的价值观取而代之;同时,中国领导人也不是通过一种被大多数人所认可的程序而产生的。中国因此非常缺乏意识形态和程序层面上的合法性,于是绩效就成了国家合法性的最为重要、甚至是唯一的基础。 四 当前中国的问题所在──合法性问题中国经济发展举世瞩目,百姓的生活水平近年来有了很大的提高。但是,中国维稳的成本却愈来愈高。2011年,中国一些人受到突尼斯“茉莉花革命”的影响,促动“茉莉花运动”,但国内几乎没有人响应。尽管如此,不少市政府还是如临大敌,弄得马路上的警察人数不知超过了寥寥无几的闹事人群多少倍。显然,繁荣的经济和大多数百姓对当下政府在不少方面的表现还算满意这些事实,完全不能减轻中共高层领导的焦虑。到底甚么是当前中国政局的关键性不稳定因素?或者问:中共高层领导到底在忧虑甚么?说到这一点,国内的绝大多数知识分子和百姓都会把诸如贫富差距过大、官员贪污腐败等放在首列,但这些因素的重要性或许幷不是想象般大。当前中国的贫富差距的确很大,而官员贪污腐败(特别是在那些吏治较差的省份)无疑也十分严重。相比之下,印度的贫富差距和官员腐败也十分厉害,甚至在不少方面明显超过了中国,可是印度却不是人们认为很可能发生革命的国家。显然,仅仅是贫富差距和官员贪污腐败是不足以引发革命的。 中国的知识分子和百姓都对贫富差距和官员腐败深恶痛绝,但是中国却完全不存在这方面的高质量研究。于是,在考虑这些问题时,中国的知识分子和民众就不得不凭借想象:你对政府有多大程度上的不信任,你就会把中国的贫富差距和官员腐败问题想象得有多严重。笔者认为,当前中国的问题归根到底是政治问题,或者说国家的合法性问题,而不是诸如贫富差距和官员腐败这类社会问题。而中共政权合法性问题的关键在于:第一,国家在共产主义意识形态式微后再也拿不出一个能被广泛认可的主流价值体系;第二,国家不敢(或者不愿意)把合法性的重心转移到程序合法性的层面上来;第三,国家对于绩效合法性产生了过度的依赖。 当下中国的领导人似乎仍然不了解绩效合法性的内禀不稳定这一特质,因为在他们的各种发言中不断流露出人民自然会拥护一个绩效优良的政府这样一种天真的论点,幷且他们也正在努力地通过加强政府绩效来获取国家的合法性。他们的做法与百姓情绪的耦合就给中国带来了如下的悖论:中国的经济和民众的生活水平在近年来都取得了举世羡慕的发展,但是社会却有朝着革命方向发展的倾向。 当社会上的大多数精英和百姓都认同于国家建构的意识形态时,这一意识形态就会成为一个社会的核心价值观或者说核心意识形态。在有着主流意识形态的国家中,社会就会显得非常平和甚至是保守。比如媒体:如果一个记者经常在某一媒体上发表与主流意识形态不符的言论,百姓就会不喜欢这个媒体,其订阅量或收视率就会下降,媒体老板也因此会不喜欢这一记者。可以说,当国家建构的主流意识形态被广为接受时,百姓就会更相信那些平和甚至是保守的报导,而发表偏激言论的媒体就会没有出路。个体也一样:如果一个人经常在公开场合(和网络上)发表与主流意识形态不符的言论,他的言论就会被忽视,他的朋友也不会喜欢他,他也不会有任何社会影响。但是,如果社会上的精英和大多数百姓不认同国家建构的主流意识形态时,人们就会不相信主流媒体中的报导,特别是与政治有关的报导,与主流意识形态保持一致的媒体就会在民众的心目中被边缘化,幷且不再能建构民众的舆论,而敢于反对主流意识形态的媒体和个人就会被看作是“社会的良知”。 当国家建构的意识形态不再是社会上的主流价值观时,在面对以上的异议时国家也就失去有效的对策。如果国家对闹事者或者发表对国家不满观点的人士进行镇压的话,那么国家政权在民众心目中就会进一步失去道义,稍有良知的国家干部就会感觉愧疚,而闹事者和发表对国家不满观点的人士就会被大家看作是“英雄”。但是如果国家选择容忍的话,那么这些人的行动和言论就得不到约束。更有之,一旦形成了这样的“机会结构”,人们就会发觉“会闹的孩子多吃奶”这一妙诀,社会民风于是趋于民粹和暴戾。同时,一旦大众有着把闹事者和发表对国家强烈不满观点的人士看作是“英雄”的倾向,随着“英雄”形象而产生的种种利益就会刺激有些人带着寻租的心态去装扮“英雄”。社会道德就在围绕着反体制而产生的种种“高尚”话语下不断下降。 当国家建构的意识形态不再是社会上的主流价值观时,政府就会失去公信力。这时,如果国家对舆论不加控制,反政府的言论就会在社会上产生很大的影响力,从而引发政治危机。但是如果国家控制舆论的话,人们就会去追逐谣言;加上长期控制舆论而导致人们普遍的无知,天方夜谭式的谣言很容易不胫而走,比如“江泽民去世了,但是中共却秘不发葬”、“薄熙来手上有一百多条人命”、“被重庆警察击毙的不是周克华而是一个便衣警察”等,也会被大家(包括不少社会精英)津津乐道。这些传言不但会给中国的政局增加不确定因素,幷且使得中国本来就很糟糕的政治文化进一步走向糜烂。 当国家建构的意识形态不再是社会上的主流意识形态时,国家的当权者甚至不敢运用民主选举来增强其合法性。从当权者的私利角度看,在这样的情况下举行选举不但会使他们马上下台,而且整个共产党的统治也会结束;很少有当权者愿意在这样的条件下推动民主选举。而从国家利益来说,如果政治精英不能服从一个主流价值观,由选举而产生的“非忠诚反对派”就会撕裂社会,这给了当局拒绝搞民主选举以一定的道德依据。但接下来的问题是,不搞以选举为核心的程序政治只会使得社会矛盾不断积累,幷为中国从威权国家到民主国家的平稳过渡增加了难度。 一旦国家的合法性不能依托于意识形态和领导人的产生程序,绩效就成了国家唯一可依托的合法性基础。得益于中国的“强国家”传统,中国政府在加强执政绩效方面应该说还是可圈可点的。但是,即便可圈可点的绩效使得中国政府变得十分富有,其后果却是金钱使国家领导变得短视,以为金钱能解决一切问题,结果在解决一个问题的同时制造了几个问题。更令人担忧的是,围绕着金钱所产生的种种利益,使得大量的利益相关者带着工具理性围聚在政府周围。这些人对体制毫无忠诚可言,他们一方面死死地把住体制的大船,另一方面则随时准备另寻高就甚至搞狡兔三窟。当前中国出现了“裸官”现象,即不少国家干部的妻子和子女都在国外拥有永久居住权甚至是公民资格,大多数年轻人都向往公务员和国企的工作,其原因盖出于此。这带来的后果就是当前中国民众的强烈仇官心理以及由此生发出来的对任何成功者的仇恨心理,整个社会的道德维系(moral fabric)被大面积毁坏。 为了进一步加强绩效合法性,政府就必须加强吏治、采取悦民政策,幷且把社会上可能出现各种不安定因素的事情统统管了起来。但是,恶性循环不可避免地开始了:政府管得愈好,民众对政府的要求就会愈高;政府管得愈多,问题也就愈多,很多社会问题于是成了政治问题。社会问题的重新政治化是近十年来中国出现的一个令人担忧的发展方向。 五 中国的前途在国内,对国家前途不看好的还真是大有人在,其中既有国内语境下的“自由主义者”和比较极端的“左派”,也有难以计数的掌握着一定话语权的网民。最近,甚至连吴敬琏这样比较持重的学者,都在发表文章惊呼当前中国的“经济社会矛盾几乎到了临界点”10。本文认为,中国的确有再爆发一次革命的可能。与以上的观点不同是,笔者认为当这场动荡到来时,其引发的根本原因不应该是当今中国社会上存在着的各种“经济社会矛盾”,而是民众在主观层面上的不满情绪以及由此带来的大量的社会矛盾。而这些不满情绪和社会矛盾的根源,则是当今政府在国家的法律─选举合法性不足的情况下,过多地把绩效当作了国家合法性的根本基础。笔者同时认为,虽然当前的形势很严峻,但是由于以下原因,中国并没有马上就爆发一场革命的危险: 第一,尽管近年来中国经济发展的势头有所减缓,但是中国仍然是世界上经济发展最为迅速、百姓生活水平有着快速提高的国家。只要中国经济继续能保持目前的增长势头,绩效合法性就还能维持一定的效力,一场革命性的动荡在中国就暂时不会发生。 第二,在中国的不少地区(特别是藏区和新疆地区)有着很严重的民族问题,但中国少数民族人口与汉人相比比例实在太小;这就是说,与前苏联不同,少数民族地区的动乱在中国不会是引发革命的一个主要动因。 第三,由于美国经济的衰退和美国对外政策在世界上普遍不得人心,相当部分的中国知识分子不再简单地把美国政治和政治体制作为理想,或者说当前中国的“自由派”知识分子不再享有1980年代的道德高度,因此也失去了1980年代一呼百应的能力。 第四,中国知识分子在近年来生活水平有了很大的提高,幷且他们发表言论的渠道也大大增加。如果说前一个变化给了知识分子耐心,使他们不会急于鼓动革命,后一个变化则促进了知识群体的分化,从而降低了在中国产生一个人们广为接受的反体制意识形态的可能性。第五,国内外大多数的学者往往会把中国每天都在发生的群体性抗争事件(特别是一些重大事件)看作为革命性事件的可能促发因素。这种观点再一次反映了知识分子的天真。笔者认为,大量的群体性事件对中国政治的稳定实际上有着巨大的正面作用。当前不少地方的地方政府软弱,中国大规模爆发群体性抗争事件的阈值因此较低,社会矛盾也不容易有大规模的堆积。此外,当前中央政府对地方发生的群体性抗争事件采取的基本态度就是让地方政府自己去处理。只要地方政府能控制住局面,中央就保持袖手旁观的姿态;但是如果地方政府让事件失控,或者在处理过程中造成了流血事件,在国内外引起广泛关注,中央政府则会对地方政府官员进行处罚。中央政府的这一做法强化了群体性事件参加者“反贪官不反皇帝”的心态,同时也促使地方政府在处理群体性事件时表现出了极大的多样性和灵活性,从而大大缓解了中国群体性事件走向政治化的倾向。 第六,与一些领袖终身制的国家相比,中国已经形成了一套比较成型的国家领导每届五年,每任不超过两届的做法。虽然新的领导人不是由普选产生,幷且换届过程的不透明也给各种政治流言提供了温床,但是换届送走了人们已经厌烦了的领导(不在于干得好不好,而在于一个人在领导位置坐长了人们都会产生厌倦感),给了人们一种新的想象和希望,从而缓解了社会矛盾朝着革命的方向发展。 但是以上这些有利于缓解社会矛盾激化的因素,完全不可能改变以下的事实:在意识形态和程序合法性严重不足的情况下,执政绩效成了当前中国政府最为主要的合法性基础。因此,即便中国没有马上就发生革命性动荡的危险,只要国家的性质得不到根本性的改变,再发生一次革命的危险在中国始终存在。从这个意义上来说,“中国人自己的代价”的确“没有付够”。 注释1 Samuel P. Huntington, Political Order in Changing Societies (New Haven, CT: Yale University Press, 1968); William Kornhauser, The Politics of Mass Society (Glencoe, IL: Free Press, 1959); Eric R. Wolf, “Peasant Rebellion and Revolution”, in National Liberation: Revolution in the Third World, ed. Norman Miller and Roderick Aya (New York: Free Press, 1971), 48-67. 2 Barrington Moore, Social Origins of Dictatorship and Democracy: Lord and Peasant in the Making of the Modern World (Boston: Beacon Press, 1966); Jeffrey M. Paige, Agrarian Revolution: Social Movements and Export Agriculture in the Underdeveloped World (New York: Free Press, 1975). 3 Jeff Goodwin, No Other Way Out: States and Revolutionary Movements, 1945-1991 (New York: Cambridge University Press, 2001); Tim McDaniel, Autocracy, Capitalism, and Revolution in Russia (Berkeley, CA: University of California Press, 1988); Autocracy, Modernization, and Revolution in Russia and Iran (Princeton, NJ: Princeton University Press, 1991); Theda Skocpol, States and Social Revolutions: A Comparative Analysis of France, Russia, and China (New York: Cambridge University Press, 1979); Timothy P. Wickham-Crowley, Guerrillas and Revolution in Latin America: A Comparative Study of Insurgents and Regimes since 1956 (Princeton, NJ: Princeton University Press, 1992). 4、5 Jeff Goodwin and Theda Skocpol, “Explaining Revolutions in the Contemporary Third World”, Politics and Society 17, no. 4 (1989): 489-509. 6 Dingxin Zhao, “State Legitimacy, State Policy, and the Development of the 1989 Beijing Student Movement”, Asian Perspective 23, no. 2 (1999): 245-84; “State-Society Relations and the Discourses and Activities of the 1989 Beijing Student Movement”, American Journal of Sociology 105, no. 6 (2000): 1592-632. 7 Dingxin Zhao, The Power of Tiananmen: State-Society Relations and the 1989 Beijing Student Movement (Chicago: University of Chicago Press, 2001); “The Mandate of Heaven and Performance Legitimation in Historical and Contemporary China”, American Behavioral Scientist 53, no. 3 (2009): 416-33. 8 赵鼎新:〈民主的生命力、局限与中国的出路〉,《领导者》,2007年第18期,页76-86。 9 Jie Chen, Popular Political Support in Urban China (Washington, DC: Woodrow Wilson Center Press; Stanford, CA: Stanford University Press, 2004); Bruce Gilley, “Legitimacy and Institutional Change: The Case of China”, Comparative Political Studies 41, no. 3 (2008): 259-84; Lianjiang Li, “Political Trust in Rural China”, Modern China 30, no. 2 (2004): 228-58; Tianjian Shi, “Cultural Values and Political Trust: A Comparison of the People’s Republic of China and Taiwan”, Comparative Politics 33, no. 4 (2001): 401-19; Wenfang Tang, Public Opinion and Political Change in China (Stanford, CA: Stanford University Press, 2005). 10 参见吴敬琏的博客,http://wujinglianblog.i.sohu.com/blog/view/236115860.htm。]]></content>
<categories>
<category>坠乱花天</category>
</categories>
<tags>
<tag>政治</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Django制作前端页面生成yaml文件]]></title>
<url>%2F2018%2F09%2F03%2F%E5%88%B6%E4%BD%9C%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2%E7%94%9F%E6%88%90yaml%E6%96%87%E4%BB%B6%2F</url>
<content type="text"><![CDATA[整体流程与环境说明整体流程如下图,请感受灵魂画师的功力: Django:2.1.1,阿里云服务器Python:3.6.4,安装方法见:https://rorschachchan.github.io/2018/07/31/获取网站title的脚本/ Django启动由于是python3,所以直接pip install django就安装最新的Django版本。 12345django-admin startproject Kubernetes #如果提示django-admin命令不存在可以做一个软连接到/usr/local/bin/目录下cd Kubernetespython manage.py startapp createyaml #创建APPpython manage.py migratepython manage.py createsuperuser app创建完毕之后,在Kubernetes/settings.py的INSTALLED_APPS字段添加createyaml,此时就创建好了项目和app。python manage.py runserver 0.0.0.0:8000启动django,然后浏览器地址栏输入外网IP:8000,就会看到django正常启动了,如图: Django准备首先我们先准备一个脚本111.sh,这个脚本很简单,就是接收到前端传入的数值然后加工成一个yaml文件,如下: 123456789101112131415161718192021222324#!/bin/bash#用来生成对应的yaml文件cat << EOF================================ HERE IS YOUR YAML ================================EOFecho apiVersion: v1echo kind: $1echo metadata:echo name: $2echo labels:echo app: webecho spec:echo containers:echo -- name: front-endecho image: $5echo ports:echo -- containerPort: $3echo -- name: rss-readerecho image: nickchase/rss-php-nginx:v1echo ports:echo - containerPort: $4 可以看出上面这个生成yaml脚本太粗糙了,很多地方还有待改进,但是这仅仅是一个小例子而已。再去/django/Kubernetes/createyaml/templates里准备一个比较简单的前端页面脚本,内容如下: 12345678910111213141516171819202122<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>创建yaml文件</title> </head> <body> <h1>创建YAML文件用于K8s部署</h1> <h2>请根据实际情况填写以下内容</h2> <form method="post" action="/create_yaml/"> <input type="text" name="kind" placeholder="类型"><br> <input type="text" name="name" placeholder="名称"><br> <input type="text" name="containerPort1" placeholder="容器端口1"><br> <input type="text" name="containerPort2" placeholder="容器端口2"><br> <input type="text" name="mirror" placeholder="镜像"><br> {{ error }}<br> <button id="btn" type="submit">生成yaml</button> {% csrf_token %} <!-- 标签添加CSRF令牌,这是因为django针对CSRF(跨站请求伪造)有保护措施,没有这句话就是403 --!> </form> </body></html> 有了页面,还需要一个域名指向这个页面,修改一下/django/Kubernetes/Kubernetes/urls.py,改成如下: 12345678from django.contrib import adminfrom django.urls import pathfrom createyaml import views #将createyaml这个app的views引进urlpatterns = [ path('admin/', admin.site.urls), path(r'create_yaml/', views.create_yaml), #新版的这里不再是url了,把这个url指向views.py里的create_yaml函数] 再继续,写一下views.py里的create_yaml函数: 1234567891011121314import subprocess #引入这个库#创建yamldef create_yaml(request): if request.method == 'POST': kind = request.POST.get('kind', '') #后面的''是默认值的意思 name = request.POST.get('name', '') containerPort1 = request.POST.get('containerPort1', '') containerPort2 = request.POST.get('containerPort2', '') mirror = request.POST.get('mirror', '') result = subprocess.Popen(args=['bash','/docker/111.sh',name,mirror,containerPort1,containerPort2],stdout = subprocess.PIPE,shell = False).stdout.read() #在这里通过subprocess去启动111.sh这个脚本 return HttpResponse(result,content_type="text/plain") else: return render(request,'createyaml.html') 以上函数多说几句: 首先判断请求的方法是否是POST,不是的话返回该页面; request.POST.get方法获取前端传入的名称或者端口等值,此处的kind、name、mirror和containerPort就是html文件里form表单部分那两个input标签的name属性; 获取到了变量,然后就让subprocess来调用111.sh来用这些变量去运行脚本,执行的结果就是result,然后return这个result结果; 使用subprocess最好不打开shell = True,因为这样的话,要是不小心rm -rf /,你就gg了,但是如果shell = False的话,就会把刚才的命令看成rm和-rf /两部分,也就是不能成功,这样也免去了别人恶意注入的危险; 实际操作效果 参考资料https://blog.csdn.net/xiaoyaozizai017/article/details/72794469http://lipeilipei.top/2018/02/07/python+django%E5%AE%9E%E7%8E%B0%E7%99%BB%E9%99%86%E5%8A%9F%E8%83%BD%EF%BC%88%E4%B8%8B%E7%AF%87%EF%BC%89/https://blog.csdn.net/bjbz_cxy/article/details/79358718 (如果不想用django就可以看看这个cgi方法)http://blog.51cto.com/laomomo/2163399]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Jenkins自动构建镜像并且发送钉钉通知]]></title>
<url>%2F2018%2F08%2F30%2FJenkins%E8%87%AA%E5%8A%A8%E6%9E%84%E5%BB%BA%E9%95%9C%E5%83%8F%E5%B9%B6%E4%B8%94%E5%8F%91%E9%80%81%E9%92%89%E9%92%89%E9%80%9A%E7%9F%A5%2F</url>
<content type="text"><![CDATA[部署流程图把k8s引入到整个部署的自动化流程如下图: 上图已经说的很明白了,但是结合到我公司的内部情况,再加一点文字的解释: 运维做一个前端页面,上面提供一些关键词作为变量传入; 开发将代码上传到svn或者gitlab,进行jira通知,如果是svn的话,jenkins将新代码打包成zip文件,启动jenkins把windows的zip包上传到阿里云云存储上;如果是到gitlab,就不用打包成zip了,直接就把包传到云存储上; Gitlab/Svn通过webhook通知jenkins去挂载云存储bucket的文件夹里,并且根据对应的dockerfile进行build成镜像,然后再把镜像推送到云镜像仓库里,推送成功后,Jenkins发送一个钉钉成功的通知; Jinkens针对本次镜像和实际部署内容再搭配上之前传递进来的变量,构建一个yaml文件; 通过create这个yaml文件,启动对应的services来达到用户访问的目的,此时Jenkins再发一条钉钉通知,整个部署流程结束。 环境说明Jenkins:2.124,jenkins与docker在同一台云服务器上,并且确定这个机器上可以顺利login到阿里云的私有仓库云存储:阿里云OSSGitlab:10.7.3镜像仓库:阿里云容器镜像仓库钉钉:4.5.5 Jinkens安装钉钉插件既然要让jenkins调用钉钉发送成功消息,那么就需要把jenkins跟钉钉结合在一起。至于怎么配自定义钉钉机器人,请看钉钉的官方文档:https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.karFPe&treeId=257&articleId=105735&docType=1 。而jenkins里也是有官方的钉钉插件,界面系统管理–管理插件,然后搜索“dingding”,安装即可,如图: 插件安装完毕之后,重启jenkins即可。 挂载阿里云存储阿里云官方挂载云存储的方法是ossfs,登陆到jenkins所在的服务器(centos 7.4)里,步骤如下: 123456wget https://github.com/aliyun/ossfs/releases/download/v1.80.5/ossfs_1.80.5_centos7.0_x86_64.rpmyum localinstall ossfs_1.80.5_centos7.0_x86_64.rpm #这一步安装可能会比较慢echo 需要挂载的bucket名:云存储对应ak:云存储对应sk > /etc/passwd-ossfs #将云存储的ak,sk写入到文件里chmod 640 /etc/passwd-ossfsmkdir /tmp/ossfs #创建挂载文件ossfs 需要挂载的bucket名 /tmp/ossfs -ourl=http://oss-cn-hangzhou-internal.aliyuncs.com #如果不是阿里云就要用外网的endpoint 操作的效果如下,我挂载的bucket叫ligentest,毕竟代码是高度机密,bucket属性设置是私有,256T的容量爽爽的: 配置任务在jenkins里创建一个新的工程,取名叫“构建镜像并且上传到云仓库”。“gitlab更新就触发jenkins”的配置内容可以参考 https://rorschachchan.github.io/2018/05/25/Gitlab-Jenkins搭建持续集成系统/ 一文进行操作。 配置正确jenkins与gitlab各自的webhook,测试提交能返回200之后。就要配置构建和构建后操作。 构建选择执行shell,里面填写这样一个命令:sudo sh /docker/pushimage.sh,也就是运行一个脚本,脚本内容如下: 12345678#!/bin/bash#这个脚本用来推送最新的镜像去阿里云镜像仓库version=$(date +20%y%m%d) #用当前日期作为versiondocker build -f /docker/chenpyfile -t chentest/python:$version . #先本地构建镜像image_id=$(docker images | awk '{print $3}' | sed -n '2p') #获取image的id号docker tag $image_id registry.cn-hangzhou.aliyuncs.com/lechangetest/chentest:$version #给本地的镜像打一个tagdocker push registry.cn-hangzhou.aliyuncs.com/lechangetest/chentest:$version #推送到阿里云对应的仓库去 构建后操作选择钉钉通知器配置,jenkins URL一栏应该默认填好的,即jenkins的网址;钉钉access token这一栏就是直接填机器人的那个access token,然后选择根据什么情景机器人触发通知,如图: 触发验证首先要确认jenkins用户能否正常使用docker命令,方法就是修改一下/etc/sudoers添加jenkins这个用户即可。 这次测试,我们就不搞nginx那种静态页面了,换一个python在后台运行的例子。首先,准备一个叫time.py的脚本,这个脚本很简单,就是不断的输出当前时间的脚本: 123456789101112#!/usr/bin/env python#coding=utf-8import timedef get_time(): localtime = time.asctime( time.localtime(time.time()) ) print ("本地时间为 :", localtime) #python的dockerfile用的是latest,python3是要求有括号的if __name__ == '__main__': while True: get_time() time.sleep(1) 对应的dockerfile叫chenpyfile,如下: 123456789101112############################################################# Dockerfile to build A python container images ## Based on Python #############################################################FROM python:latestMAINTAINER ChrisChan "Chris@jjfjj.com"RUN apt-get update && \ apt-get install -y vim && \ apt-get install -y procpsRUN mkdir -p /root/appCOPY /script/ /root/script #把上面那个脚本拷贝到容器里,当然挂载也可以CMD ["python", "/root/script/time.py"] #这里不要写“python /root/script/time.py”,注意前后台问题 这个dockerfile在本地测试构建镜像是完全没问题的,然后触发一下git push,就会看到钉钉机器人启动了: 构建完毕之后,机器人也会给一个成功的标志,然后去阿里云的云仓库一看,嗯,果然已经推送过来了!如图: 再docker run -dit --name chen-pytest registry.cn-hangzhou.aliyuncs.com/lechangetest/chentest:20180831,也能看到新创建的镜像是可以启动的: 至此整个“Jenkins自动构建镜像并且发送钉钉通知”部分就结束了。 参考资料https://jimmysong.io/posts/kubernetes-jenkins-ci-cd/https://help.aliyun.com/document_detail/32196.htmlhttp://www.cnblogs.com/jianxuanbing/p/7211006.html]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>钉钉</tag>
<tag>Jenkins</tag>
<tag>Docker</tag>
</tags>
</entry>
<entry>
<title><![CDATA[K8s的基础操作]]></title>
<url>%2F2018%2F08%2F27%2FK8s%E4%BB%8E%E9%83%A8%E7%BD%B2%E5%88%B0%E6%89%A9%E5%AE%B9%2F</url>
<content type="text"><![CDATA[环境说明kubenetes:阿里云服务,版本v1.10.4,三个master,一个node,我也不知道为啥阿里云设定master最少是3个,而node最少可以是1个…服务器:阿里云Centos 7.4 部署服务首先,我们先部署一个以dockhub最新nginx镜像为底的nginx。命令如下:kubectl run nginx-test --image=nginx:latest --port=80。同理,再部署一个最新版redis的话,就是找葫芦画瓢:kubectl run redis-test --image=redis:latest --port=6379。 两个命令敲完,这就给k8s下达了一个deployment(部署任务),可用kubectl get deployments和kubectl get pods命令查看: 可以看到现在已经生成了对应的pod,而pod里就是容器了,容器里就是对应的服务。如果想爬进这个容器看一下里面的文件等情况,命令是:kubectl exec -it nginx-test-bb95c4645-7qpbj bash。 这里插播一下kubectl get deployment里各参数的含义: 1234DESIRED:对应.spec.replicas,用户设定的期望副本数CURRENT:对应.status.replicas,目前运行的副本数UP-TO-DATE:对应.status.updatedReplicas,包含最新的pod template的副本数AVAILABLE:对应.status.availableReplicas,进入正常状态的副本数 但是现在这个服务是外网无法访问的,因为宿主机还没有一个端口与这个nginx容器的80端口相对应。所以要暴露一个端口给外部用于访问。命令是:kubectl expose deployment/kubernetes-bootcamp --type="NodePort" --port 80,然后用kubectl get services查看一下效果: 然后在对应的master和node里就看到宿主机随机分配的那个30497端口已经启动了,如图: 在浏览器上访问一下30497端口,果然可以访问到nginx服务: 扩容服务服务嘛,总有高峰低谷。比如微博,突然爆出来哪个娱乐明星的新闻,肯定就会有大量的流量涌入,此时就需要扩容,那么k8s的扩容很简单,就是pod的复制,如果要把上面那个nginx-test的部署任务进行扩展,命令就是kubectl scale deployments/nginx-test --replicas=4,如图: 可见nginx-test又生成了三个pod,与原来的组成了4个pod,而另一个redis的部署任务是没有变化的。 用kubectl get pods -o wide可见,每一个pod分配到了不同的虚拟IP上,而且node都是阿里云的那台node服务器。 在阿里云控制台也能看到里面的情况: 此时进入到node节点,docker ps -a就会看到新的nginx景象生成,同时也生成了三个/pause的容器: kubernetes中的pause容器主要为每个业务容器提供以下功能: 在pod中担任Linux命名空间共享的基础; 启用pid命名空间,开启init进程。 注意!目前kubernetes似乎仅仅支持共享网络,还不支持进程体系、文件系统之间的共享。如果此时在访问,就会看到访问会相对均匀的落到这四个pod中的每一个,起到一个负载均衡的作用。如果高峰期过了,不需要那么多pod了,就kubectl scale deployments/nginx-test --replicas=1,pod就会恢复成1个,据我几次试验,每次都是保留最老的那一个pod。 yaml文件创建一个podK8s的yaml文件的文法和规矩,官方社区就有教程:https://www.kubernetes.org.cn/1414.html 。但是如果要搭配阿里云的私有镜像,需要先参考一下阿里云文档:https://help.aliyun.com/document_detail/86562.html 。注意,这个方法不能在命令行里使用,只能在yaml或者json里用。这里先写一个简单的nginx配置文件pod-nginx.yaml做例子,全文如下: 1234567891011121314151617181920---apiVersion: v1kind: Podmetadata: name: aliyun-nginx labels: app: webspec: restartPolicy: Always #表明该容器一直运行,默认k8s的策略,在此容器退出后,会立即创建一个相同的容器 nodeSelector: zone: node1 #节点选择 containers: - name: aliyun-test-nginx image: registry-vpc.cn-hangzhou.aliyuncs.com/lechangetest/chentest:1.1 imagePullPolicy: IfNotPresent #可选择Always、Never、IfNotPresent,即每次启动时检查和更新images的策略,IfNotPresent是节点上没有此nginx镜像时才执行pull操作 ports: - containerPort: 80 #容器开发对外的端口 hostPort: 33664 #映射到主机的端口/对外映射的端口(一般可以不写) imagePullSecrets: - name: regsecret #这句话为了通过阿里云似有仓库的鉴权 保存退出,再kubectl create -f pod-redis.yaml把这个文件执行一下。然后kubectl get pod看一下效果: 发现我们创建那个redis-pod状态是Pending(等待中),那就是不成功啊。于是就kubectl describe pod/pod-redis查看一下原因,反馈如下: 这个错误的意思是“如果指定的label在所有node上都无法匹配,则创建Pod失败”。原来是我没有配置kubectl label nodes,那先把pod-redis删除,再把nodeSelector那一段去掉,改成nodeName: cn-hangzhou.i-bp1978gmunq3oalfcqlx,去掉再重新create一下。kubectl get pod检查: 然后就是给这个pod增加一个对外的端口。kubectl expose pod/aliyun-nginx --type="NodePort" --port 80,效果如下: 再去浏览器里,输入node的外网网址:31829看看效果: 配置成功,当然这整个过程也可以在阿里云的控制台操作,更简单更直观,而且阿里云还会自动把对外端口配置到SLB里,具体步骤可以看阿里云的官方文档。 升级与回滚假设我们把nginx-test这个deployment的镜像升级成阿里云私有仓库的1.1版本,那么命令是: 1kubectl set image deployments/nginx-test nginx-test=registry.cn-hangzhou.aliyuncs.com/lechangetest/chentest:1.1 升级之后,kubectl get pod发现有几个节点不正常,如图: 那么这种情况下需要紧急回滚,回滚命令: 1kubectl rollout undo deployment/nginx-test 一会就看到回滚成功了。如图: 参考资料https://jimmysong.io/posts/what-is-a-pause-container/https://blog.csdn.net/mailjoin/article/details/79686937http://pipul.org/2016/05/why-we-need-the-pod-and-service-of-kubernetes/https://www.imooc.com/article/30473]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>容器</tag>
<tag>阿里云</tag>
<tag>kubenetes</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Centos6.5升级最新内核4.18的坑]]></title>
<url>%2F2018%2F08%2F25%2Fcentos6-5%E5%8D%87%E7%BA%A7%E6%9C%80%E6%96%B0%E5%86%85%E6%A0%B84-18%E7%9A%84%E5%9D%91%2F</url>
<content type="text"><![CDATA[升级流程开发童鞋要搞BBR,然后让我在他的阿里云服务器上升级一下内核。我登进去一看,centos 6.5,内核还是2.6的。 之前我曾经搞过centos 7升级内核到最新版,文章在此:https://rorschachchan.github.io/2018/06/11/阿里云centos7升级内核过程/ 。centos6升级内核有几个地方不太一样,但是过程差不多。整个升级内核步骤如下: 12345先备份镜像,很重要!!! 而且备份镜像成功之前,云服务器不可以重启。yum update -yrpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org #导入ELRepo GPG keyrpm -Uvh https://www.elrepo.org/elrepo-release-6-8.el6.elrepo.noarch.rpm #安装 6版本的ELRepoyum --enablerepo=elrepo-kernel install kernel-ml -y #截至本文,最新的是4.18,lt版本是4.4 如果yum的时候有提示Warning: RPMDB altered outside of yum,只需要删除一下yum的历史记录即可:rm -rf /var/lib/yum/history/*.sqlite 。 安装完毕之后,vim /etc/grub.conf,把default改成0,即指定使用第一个内核启动,如图: 然后在阿里云控制台重启一下这个服务器即可。 无法启动?可能有的人直接就启动成功了,因为网络上很多文章到此就结束了。但是我这台服务器,很不幸,出现了问题。在控制台上看服务器是“运行中”,但是无法ssh连接,而且ping也是失败。不一会,控制台的服务器就显示“已停止”,可见是内核出了问题。 联系了阿里的后台,他们反馈这个机器现在的状态是Module scsi_wait_scan not found,那知道了原因就对症下药吧,这个问题解决方法不止一个,我亲测以下的方法好使。 首先先用刚刚做的那个磁盘快照回滚到之前正常的状态,重新执行上面整个安装4.18的内核的所有操作,然后还要补充如下: 123echo 'add_drivers+="virtio_blk"' >/etc/dracut.conf.d/force-vitio_blk-to-ensure-boot.confcp /boot/initramfs-4.18.5-1.el6.elrepo.x86_64.img /boot/initramfs-4.18.5-1.el6.elrepo.x86_64.img-bak #把新下载的4.18的img文件备份dracut -f initramfs-4.18.5-1.el6.elrepo.x86_64.img 4.18.5-1.el6.elrepo.x86_64 #编译生成新的img,4.18.5-1.el6.elrepo.x86_64这个文件在/lib/modules/下 重新在阿里云控制台重启一下这个服务器,这一次就OK了。 发生异常的原因是:更新内核后,在写dracut程序时无法检测KVM's virtual disk driver "virtio_blk",此驱动被用于访问KVM虚拟磁盘,dracut没有正常添加新的initramfs module,导致系统没有磁盘访问驱动无法正常启动。 参考资料https://bugzilla.kernel.org/show_bug.cgi?id=60758https://opengers.github.io/linux/linux-source-code-compile-kernel-rpm/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>内核</tag>
<tag>BBR</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Kubectl使用的简单举例]]></title>
<url>%2F2018%2F08%2F22%2F%E5%AE%89%E8%A3%85%E5%B9%B6%E4%B8%94%E9%85%8D%E7%BD%AEkubectl%2F</url>
<content type="text"><![CDATA[安装kubectl在阿里云的Kubernetes界面生成一个新的集群,如图: 但是这个集群是无法通过ssh登陆云服务器那样登录的,这个时候要操作k8s就有两个招数,第一个招数就是用kubectl这个工具去连接到集群。但是kubectl很难搞,因为它所在的storage.googleapis.com在大陆是无法访问的,如果效仿https://www.kubernetes.org.cn/installkubectl 里面的方式去下载kubectl是无法成功的,如图: 为了应付这个办法,就要去kubernete的github界面里下载代码包,然后手动上传到云服务器里安装。 首先到https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.11.md#v1112 里的Client Binaries下载1.11版本的kubectl的包,如图: 将这个包上传到云服务器之后解压缩,然后把kubernetes安装包里的/client/bin/kubectl做一个/usr/local/bin/kubectl的软连接,如图: 如果所在的网络也无法打开github,那么只好用国内的源https://mirrors.ustc.edu.cn/kubernetes/apt/pool/ ,下载相应的包之后手动上传到云服务器里也能达到一样的效果,缺点就是国内源没有github更新的那么快。 配置kubectl阿里云在生成kubernetes集群后,点击管理,最下面会有一个配置文件,将整个文件内容写入/root/.kube/config,然后再一次使用kubectl cluster-info就能看到配置成功了,如图: 再用kubectl config view能进一步看到细节: 这样就证明可以通过kubectl连接到kubenetes集群了。 kubectl基本操作 kubectl get nodes:查看master和worker的基本情况,如图: kubectl run ngx-test --image=nginx:latest --port=8080 --restart=Never:部署一个以nginx最新镜像为底的叫ngx-test的部署,并且开放下面容器的8080端口,每个部署的名称不能重复。部署会自动生成pod,如果加上了--restart=Never,那么pod生成一次失败就不再生成; kubectl delete deployment chen-test:删除一个叫chen-test的部署,注意,使用kubectl命令,要删除拥有该pod的Deployment。如果我们直接删除pod,Deployment将会重新创建该pod; kubectl get deployments:查看部署情况,如图: kubectl proxy: 每个pod在kuber集群里都是一个封闭的网络环境里,可以通过这个命令使API server监听在本地的8001端口上; kubectl get pods:获取每一个pods的基本情况,如图: kubectl describe pods:查看每一个pods的运行细节,可以出来为什么pods没有正常的运行,如果要特别制定具体的pod,那就是kubectl describe pods pod的名称; kubectl exec -it POD_NAME bash:连接到对应的pod里; kubectl get pods -n kube-system:查看NAMESPACE是kube-system的所有pod; 10.kubectl delete pods/kubernetes-dashboard-7b9c7bc8c9-q8425 -n kube-system:删除掉kube-system这个NAMESPACE里kubernetes-dashboard-7b9c7bc8c9-q8425这个pod; 参考资料https://help.aliyun.com/document_detail/64940.html?spm=a2c4g.11186623.4.1.2c4652f3qdpMed (这个是通过ssh访问k8s负载均衡的方法)https://kubernetes.io/cn/docs/tutorials/kubernetes-basics/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>阿里云</tag>
<tag>kubernetes</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用gitlab搭配阿里云容器镜像服务构建镜像]]></title>
<url>%2F2018%2F08%2F15%2F%E4%BD%BF%E7%94%A8gitlab%E6%90%AD%E9%85%8D%E9%98%BF%E9%87%8C%E4%BA%91%E5%AE%B9%E5%99%A8%E9%95%9C%E5%83%8F%E6%9C%8D%E5%8A%A1%E6%9E%84%E5%BB%BA%E9%95%9C%E5%83%8F%2F</url>
<content type="text"><![CDATA[工作思路本次北京AWS技术峰会里看到了很多公司在运维上使用容器部署和扩容的实例,一天下来感受良多。现在比较流行部署办法就是“云镜像”:即开发把新的代码提交到gitlab上,然后gitlab与云厂家的镜像服务相关联,然后每一次commit提交都会触发一次镜像的构建,然后再根据这个镜像部署到实际的服务器里,同时将此服务器作一个快照,同时再搭配上容器监控,如果服务吃紧,就用此快照购买实例扩容;如果服务闲余,那么也会自动将最老的服务器实例关机,进而释放退款。 用图像来说就是这个意思: 勾连gitlab与云镜像本文使用的镜像厂家是阿里云,gitlab版本是10.7.3。 进入阿里云的“容器镜像”页面,如果你是第一次使用这个产品需要先建立一个仓库密码,然后点击左侧的代码源,如图: 在gitlab地方选择“绑定账号”,就需要填写对应的栏目: 前两项很好写,最后一个token需要在gitlab里创建:在gitlab的页面,点击个人的头像,然后settings—Access Tokens,填写好名字(生产环境一般都是填运维的账号)然后在api处打勾,生成的那个东东就是token,直接复制填写到阿里云的页面即可。如图: 配置镜像仓库在阿里云容器镜像界面点击“创建镜像仓库”,填写好名字摘要仓库类型之后,在代码源里选择gitlab,由于刚刚填写了token所以是可以看得到gitlab用户下所有的project名的,如图: 然后点击新创建的那个仓库,在构建一栏默认已经选择好了“代码变更时自动构建镜像”,点击“添加规则”,如图: 这里我选择了master分支,然后指明了dockerfile文件名和路径,最后版本号就先写一个version,这个可以通过gitlab在commit时特殊指定。 右侧栏里的Webhook是用来发送提示的,可以在钉钉里创建一个机器人,在创建机器人时会生成webhook,然后把机器人的webhook添加到这个webhook即可。如果在添加的时候提示“当前请求失败,请重试”,这个情况是因为Webhook的名称里有中文,要全英文才可以。 编写dockerfile如果没有dockerfile是无法构建镜像的,于是就在上面“规则”的目录里创建对应的dockerfile文件,注意!“规则”里的根目录就是代码文件夹的顶目录,而不是整个服务器的根目录。写dockerfile的基础知识和语法这里不多说了,网络上有的是,我就随便写一个nginx dockerfile,内容如下: 123456789101112131415############################################################# Dockerfile to build Nginx container images# Based on Debian############################################################FROM debian:latestMAINTAINER ChrisChan "Chris@jjfjj.com"RUN apt-get updateRUN apt-get install -y nginx RUN apt-get install -y vim RUN apt-get install -y procps #安装ps命令RUN echo 'HI!WARRIOR is the champion!!!' > /var/www/html/index.nginx-debian.htmlEXPOSE 8080 #开放8080端口COPY /file/kubernetes.tar.gz /mnt/#CMD service nginx start && nginx -g "daemon off;"ENTRYPOINT [ "/usr/sbin/nginx", "-g", "daemon off;" ] 注意!使用上面注释的CMD语句作为结尾的话,那么这个镜像docker run的时候就会马上退出,这是因为把command做为容器内部命令,那么nginx程序将后台运行,这个时候nginx并不是pid为1的程序,而是执行的bash,这个bash执行了nginx指令后就挂了,所以容器也就退出了。简而言之,Docker容器后台运行,就必须有一个前台进程。因为Docker容器仅在它的1号进程(PID为1)运行时,会保持运行。如果1号进程退出了,Docker容器也就退出了。 在gitlab触发之后,阿里云就自动把这个dockerfile build成了镜像保存在阿里云的容器仓库里,如图: 想用这个镜像就可以直接去阿里云的仓库里下载并启动,这样就节省了本地的硬盘容量。最后就是把这个镜像部署到对应的kubernetes集群里,这样就完成了“gitlab代码提交触发阿里云构建镜像”的过程,而如何使用kubernetes的内容将在以后细说。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>gitlab</tag>
<tag>持续集成</tag>
<tag>阿里云</tag>
<tag>docker镜像</tag>
</tags>
</entry>
<entry>
<title><![CDATA[阿里云Centos7开启swap虚拟内存]]></title>
<url>%2F2018%2F08%2F13%2F%E9%98%BF%E9%87%8C%E4%BA%91Centos7%E5%BC%80%E5%90%AFswap%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98%2F</url>
<content type="text"><![CDATA[出差归来,几个开发反馈说gitlab网页卡的不行,上传代码也非常吃力。我登入服务器一看,原来是内存已经耗尽了。 修改配置文件gitlab本身就是一个特别吃内存的软件,服务器还是2核4G的配置。于是我就登陆到gitlab容器里,修改一下/etc/gitlab/gitlab.rb,把unicorn['worker_processes']手动改成了3,也就是比CPU大一个,这样可以少开一点进程。但是注意,这个参数最小值是2,如果设置成1,那么gitlab就会崩坏。 保存文件之后,gitlab-ctl reconfigure,看一下内存的情况,嗯,比刚才好一点点。如图: 开启虚拟内存上面那个方法毕竟效果有限,时间长了还是会把内存一点点蚕食光,于是就要使用Swap分区,但是阿里云虚拟服务器默认是不带swap分区的,如何手动创建swap分区才是本文的要点。 这里我用了一个非生产环境的机器做实验。 创建swap分区主要的中心思想就是“创建一个文件,然后将这块文件格式化为swap格式”,首先先看一下当前的磁盘容量: 当前已用磁盘容量是16G,使用cat /proc/swaps看一下当前虚拟内存的情况: 这个情况说明没开启swap,于是就手动建立一个文件夹,比如叫/swaps,在/swaps这个路径下执行dd if=/dev/zero of=swaps bs=512 count=8388616,在这里创建swap大小为bs*count=4294971392(4G),这个过程需要一点时间,稍等片刻: 通过mkswap swaps命令将上面新建出的swaps文件做成swap分区: 此时使用cat /proc/sys/vm/swappiness查看数值应该是0,需要sysctl -w vm.swappiness=60把它改成60,这里60的含义是:100%-60%=40%,即物理内存剩下40%的时候时启用虚拟内存。若想永久修改,则编辑/etc/sysctl.conf文件,改文件中有vm.swappiness变量配置。 再swapon /swaps/swaps: 最后就是添加开机自动挂载,即在/etc/fstab文件添加如下一句: 1/swaps/swaps swap swap defaults 0 0 再用cat /proc/swaps命令检查一下swap分区是否启动: 最后,重启一下服务器,看一下开机是否正常挂载上这个虚拟分区了: 可见原来使用了16G容量,现在用了20G,这中间差的4G就是拿来做了swap,于是内存就这样多了4个G…]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>gitlab</tag>
<tag>虚拟内存</tag>
<tag>阿里云服务器</tag>
</tags>
</entry>
<entry>
<title><![CDATA[爬取当前IP并且修改阿里云安全组的脚本]]></title>
<url>%2F2018%2F08%2F07%2F%E7%88%AC%E5%8F%96%E5%BD%93%E5%89%8DIP%E5%B9%B6%E4%B8%94%E4%BF%AE%E6%94%B9%E9%98%BF%E9%87%8C%E4%BA%91%E5%AE%89%E5%85%A8%E7%BB%84%E7%9A%84%E8%84%9A%E6%9C%AC%2F</url>
<content type="text"><![CDATA[动机与脚本我工位所用的网络是公司特批的海外专线,速度OK还能翻墙出去看看,自从有了这条线爽的飞起,但缺陷就是每周IP地址都会变,IP一变很多的阿里云ecs安全组就要重新配置,因为有一些公网端口比如grafana或者跳板机是只能公司运维人员访问的。这样每周都要手动改一次IP地址太烦了,于是乎,写了下面这个脚本,一劳永逸的解决这个问题: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576#coding=utf-8#这个脚本在python3.6下自验通过,用途是去爬当前的IP地址然后给阿里云安全组添加新的IP,并且删除掉老的IPfrom aliyunsdkcore import clientfrom aliyunsdkecs.request.v20140526 import AuthorizeSecurityGroupRequestfrom aliyunsdkecs.request.v20140526 import RevokeSecurityGroupRequestfrom aliyunsdkcore.profile import region_providerimport requests,sys,re,osfrom bs4 import BeautifulSoupclt = client.AcsClient('这里是AK', '这里是SK', 'cn-hangzhou') #鉴权file = "F:\\ip.txt"def checkDIR(): global file if os.path.exists(file) == True: #先判断文件是否存在 with open(file, "r") as f: old_ip = f.read() return (old_ip) #获取旧ip else: print("ip.txt文件不存在,请手动生成!") sys.exit() #文件不存在直接退出def getIP(): global file r = requests.get('http://www.ip111.cn/') #这里输入要爬的网站域名 soup = BeautifulSoup(r.text, "lxml") context = [] for link in soup.find_all('td'): #获取所有td标签内容 context.append(link.get_text()) #添加一个列里 str = context[4] ip = re.split(r'[\n\s]\s*', str)[1] #多符号分割字符串 with open(file, "w") as f: f.write(ip) return ipdef addnewRULE(func): global clt # 设置参数 for port in ['3000/3000', '34872/34872']: #这里是端口 request = AuthorizeSecurityGroupRequest.AuthorizeSecurityGroupRequest() request.set_accept_format('json') request.add_query_param('RegionId', 'cn-hangzhou') request.add_query_param('SecurityGroupId', '目标安全组ID') request.add_query_param('IpProtocol', 'tcp') request.add_query_param('PortRange', port) request.add_query_param('SourceCidrIp',func()) request.add_query_param('NicType', 'intranet') #如果不加这句话就是公网添加 if port == '3000/3000': request.add_query_param('Description', 'Grafana使用端口') else: request.add_query_param('Description', 'Zabbix和堡垒机使用端口') # 发起请求 response = clt.do_action(request) print (response)def deloldRULE(func): global clt # 设置参数 for port in ['3000/3000','34872/34872']: request = RevokeSecurityGroupRequest.RevokeSecurityGroupRequest() request.set_accept_format('json') request.add_query_param('RegionId', 'cn-hangzhou') request.add_query_param('SecurityGroupId', '目标安全组ID') request.add_query_param('IpProtocol', 'tcp') request.add_query_param('PortRange', port) request.add_query_param('SourceCidrIp', func()) request.add_query_param('NicType', 'intranet') #如果不加这句话就是公网删除 # 发起请求 response = clt.do_action(request) print (response)if __name__ == '__main__': checkDIR() deloldRULE(checkDIR) getIP() addnewRULE(getIP) 整个脚本的逻辑就是先在F盘下有ip.txt里面就保存当前IP地址,然后执行脚本的时候就会先在目标安全组里删除掉这个IP相关的3000端口和34872端口,然后去www.ip111.cn里爬取当前的网址,把新IP写入到ip.txt的同时,再去目标安全组里添加这个新IP相关的3000端口和34872端口。 新的知识点 把上一个函数结果当作参数在下一个函数里执行的方法: python的退出有两个:os._exit()和sys.exit():os._exit()会直接将python程序终止,之后的所有代码都不会执行;sys.exit()会抛出一个异常: SystemExit,如果这个异常没有被捕获,那么python解释器将会退出。如果有捕获该异常的代码,那么这些代码还是会执行。使用sys.exit()来退出程序比较优雅,一般情况下也用这个,os._exit()可以在os.fork()产生的子进程里使用。 在windows里定时执行python脚本的方法:打开控制面板—>系统和安全—>计划任务。如图: 点击右侧的创建基本任务,输入任务名称和可选的描述。点击下一步,设置任务的开始时间,可以选择每日执行、每周执行或每月执行。点击下一步,操作选择启动程序,点击下一步输入参数。如图: 123程序或脚本:python.exe 添加参数:输入要执行的python脚本路径(包括文件名)起始于:输入python.exe的目录(不包括文件名) 最后点击下一步,整个过程搞定。 目前http://www.ip111.cn/网站已经更改了网页格式,上述的代码有一段已经不好使了,需要将getIP()这个函数改成如下的方法: 123456789101112def getIP(): global file r = requests.get('http://2018.ip138.com/ic.asp') #改用这个域名 soup = BeautifulSoup(r.text, "lxml") context = [] for link in soup.find_all('body'): #获取body内容 context.append(link.get_text()) #添加一个列里 str = context[0] ip = re.split(r'[\[\]]',str)[1] #进行分割 with open(file, "w") as f: f.write(ip) return ip]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>阿里云</tag>
<tag>python3.6</tag>
<tag>爬虫</tag>
</tags>
</entry>
<entry>
<title><![CDATA[获取网站title的脚本]]></title>
<url>%2F2018%2F07%2F31%2F%E8%8E%B7%E5%8F%96%E7%BD%91%E7%AB%99title%E7%9A%84%E8%84%9A%E6%9C%AC%2F</url>
<content type="text"><![CDATA[脚本在此公司的商城需要添加一个脚本,这个脚本就是观察首页页面是否正常,虽然已经配置了zabbix监控网站是否200,但是有一些特殊的情况,比如网页可以打开但是页面是“file not found”,类似这样就需要被运维第一时间监控到然后通知开发。 原本我打算直接爬取整个首页然后与服务器里的index.html对比一下,如果不符合就报警,但是跟前端同事说了这个思路之后,前端说服务器上是没有index.html的,因为这个index.html是结合其他的php拼接的。前端说“只要能检测title正常就OK,一般来说title能获取到就证明系统是OK的,如果titleOK但是html内容获取不到就是前段代码的问题,跟系统无关”。于是我就写了这么一个爬虫脚本来获取网站title,如下: 12345678910111213#!/usr/bin/env python#coding=utf-8#这个脚本的用途是用来爬取商城首页title,然后判断是否正常import requests,sysfrom bs4 import BeautifulSoupreload(sys)sys.setdefaultencoding('utf-8') #不然就会UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)r = requests.get('https://www.lechange.com') #这里输入要爬的网站域名r.encoding = requests.utils.get_encodings_from_content(r.content)[0]soup = BeautifulSoup(r.text,'lxml') #这一步需要事前pip install lxmlprint soup.title.string 说一下,如果在from bs4 import BeautifulSoup爆出ImportError: No module named 'bs4'是因为安装的库装错了,应该是pip install beautifulsoup4而不是pip install beautifulsoup。启动脚本效果如下: 编码问题上面那个脚本里的soup.title.string的类型是bs4.element.NavigableString,如果不用print那么它的形式是unicode的,如图: 这种现象并不新鲜,比如list在python2里一直都不是正常输出中文的,如图: 可见只有for in的时候才会正常编码,那么这样的情况怎么办? 最简单的方法,改用python3。不过上面那个脚本是可以直接把中文放到soup.title.string进行判断的。 安装python 3.6.4首先要先安装相关依赖包yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make,其中readline-devel这个很重要,他是管方向键的,如果python运行的时候方向键不好使,那么就要yum install readline-devel安装,安装完毕后重新configure和make。 然后过程如下: 1234567891011121314151617yum -y install epel-release #运行这个命令添加epel扩展源#安装pipyum install python-pippip install wgetwget https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tar.xz#解压xz -d Python-3.6.4.tar.xztar -xf Python-3.6.4.tar#进入解压后的目录,依次执行下面命令进行手动编译./configure prefix=/usr/local/python3make && make install#将原来的链接备份mv /usr/bin/python /usr/bin/python.bak#添加python3的软链接ln -s /usr/local/python3/bin/python3.6 /usr/bin/python#测试是否安装成功了python -V 更改yum配置,因为其要用到python2才能执行,否则会导致yum不能正常使用,需要分别修改/usr/bin/yum和/usr/libexec/urlgrabber-ext-down这两个文件,把他们的#! /usr/bin/python修改为#! /usr/bin/python2。 然后还要给python3的pip3做一个软连接: ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3。 注意!如果你用了python3那么上面那个脚本就会有很大的变动。 参考资料https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.htmlhttp://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/tutorial.html]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python</tag>
<tag>爬虫</tag>
</tags>
</entry>
<entry>
<title><![CDATA[云服务器的内存竟然少了500M...]]></title>
<url>%2F2018%2F07%2F27%2F%E7%AE%97%E7%AE%97%E5%86%85%E5%AD%98%E7%9A%84%E8%BF%99%E4%B8%80%E7%AC%94%E7%B3%8A%E6%B6%82%E5%B8%90%2F</url>
<content type="text"><![CDATA[故障发现今天发现有一台阿里云线上环境的服务器内存在告急,使用free -m一看,果然剩余的内存不多了,而且buffers和cached也都不高,如图: 用top一看,里面的情况是这样的: 很奇怪,top里的res即物理内存加起来也就2200M多一点,但是free命令里显示已经用掉了几乎3.4个G,那这1.2G的空头内存去哪了?要知道,free命令会把Slab缓存统计到了used memory里,那就看看slab缓存有多少吧。 yum install -y nmon,使用nmon看一下,如图: 发现里面有几乎650MB的slab内存,这样还是少了大约550MB,那么使用slabtop查看细节,如图: 再用cat /proc/meminfo去查看一下内存详细情况,如图: https://blog.famzah.net/2014/09/22/know-your-linux-memory-usage/ 这里提到内存的计算公式: 1234MemTotal = MemFree + (Buffers + Cached + SwapCached) + AnonPages + (Slab + PageTables + KernelStack)MemTotal = MemFree + (Active + Inactive) + (Slab + PageTables + KernelStack)MemTotal = MemFree + (Buffers + Cached + SwapCached) + AnonPages + ((SReclaimable + SUnreclaim) + PageTables + KernelStack)MemTotal = MemFree + ((“Active(anon)” + “Active(file)”) + (“Inactive(anon)” + “Inactive(file)”)) + ((SReclaimable + SUnreclaim) + PageTables + KernelStack) 虽然作者说他测试的机器内核是3.2的,但是这几个公式对我这个服务器(内核2.6)都可以用,虽然肯定不能严丝合缝但是相差值并不大,我用前两个公式算了一下我这个机器的情况: 12MemTotal(3495620) = MemFree(251396) + Buffers(11456) + Cached(292324) + SwapCached(0) + AnonPages(2302484) + Slab(627068) + PageTables(8972) + KernelStack(1920) MemTotal(3495592) = MemFree(251396) + Active(2450960) + Inactive(155276) + Slab(627068) + PageTables(8972) + KernelStack(1920) 猜测一下我特么的法克,这个memtotal跟3921112差距很远啊!相差了412MB!为什么会少了这么多?会不会这412MB就是那used memory减去slap内存的那部分神秘内存?他为什么没有统计在/proc/meminfo里? 于是果断给阿里云提工单,截图发锤,让他们给一个完美的解释。 等待阿里云回复的时间里,我又找了几个其他的机器,各种型号的都算了一下,发现一个现象:凡是装了这个模块的服务器都出现了MemTotal不相符的问题,大约误差值都是400M~500M,而除了这个模块,MemTotal的误差值基本就是50M以内。 呃…这好像不能怪阿里云了…不过的确MemTotal是有误差的啊! 找开发了解了一下,这个服务器里用了大量的tcp长连接,而且是https的,使用netstat -na|grep ESTABLISHED|wc -l一看,有95000个左右。 而在开发环境的机器里查看,MemTotal的相差率很小,而tcp连接数则不到20个。那用排除法可以确定是TCP长连接的锅,于是我猜测TCP长连接占用掉了一部分内存,而这部分内存又没有在meminfo(SLAB)里体现出来,进而导致free命令与top命令相差过大。 小心求证未完待续… 参考资料http://farll.com/2016/10/high-memory-usage-alarm/#comment-9881http://lday.me/2017/09/02/0012_a_memory_leak_detection_procedure/ (虽然跟本文没啥关系,但是强力推荐)http://blog.yufeng.info/archives/2456http://lovestblog.cn/blog/2015/08/21/rssxmx/https://www.mawenbao.com/research/linux-ate-my-memory.html]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>内存泄漏</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用tqdm制作下载进度条]]></title>
<url>%2F2018%2F07%2F24%2F%E4%BD%BF%E7%94%A8tqdm%E6%B7%BB%E5%8A%A0%E4%B8%8B%E8%BD%BD%E7%9A%84%E8%BF%9B%E5%BA%A6%E6%9D%A1%2F</url>
<content type="text"><![CDATA[制作进度条既然接手了国内专有云,就要写一个“自动化部署脚本”。于是我就把整个部署的安装包放到阿里云的bucket,用脚本去wget这个部署包,然后进行脚本部署。但是由于这个安装包比较大,`于是就打算在脚本里添加一个“下载进度条”,这样就能了解到当前的下载情况。 google了一下,就发现了tqdm这个库,它声称比老版的progressbar库的单次响应时间提高了10倍以上,安装的方法很简单:pip install tqdm。 具体的用途和参数可以去看https://lorexxar.cn/2016/07/21/python-tqdm/ 这篇文章。 从tqdm的几个参数可见要使用tqdm做下载进度条首先需要整个文件的大小。整个文件的大小可以用requests.get方法获取,获取到header里就有目标的大小。在使用requests模块下载大文件/数据时,建议使用使用stream模式。如果是stream=False,它会立即开始下载文件并放到内存中,如果文件过大,有可能导致内存不足。然后就是把目标文件拆成一个一个的小块,逐步的写入一个文件,这样达到了下载文件的目的。整个脚本如下: 123456789101112131415161718192021#!/usr/bin/env python# -*- coding: utf-8 -*-import requestsfrom tqdm import tqdmdef downloadFILE(url,name): resp = requests.get(url=url,stream=True) #stream=True的作用是仅让响应头被下载,连接保持打开状态, content_size = int(resp.headers['Content-Length'])/1024 #确定整个安装包的大小 with open(name, "wb") as f: print "安装包整个大小是:",content_size,'k,开始下载...' for data in tqdm(iterable=resp.iter_content(1024),total=content_size,unit='k',desc=name): #调用iter_content,一块一块的遍历要下载的内容,搭配stream=True,此时才开始真正的下载 #iterable:可迭代的进度条 total:总的迭代次数 desc:进度条的前缀 f.write(data) print name + "已经下载完毕!"if __name__ == '__main__': url = "需要下载的文件的地址" name = url.split('/')[-1] #截取整个url最后一段即文件名 downloadFILE(url,name) 注意!下载文件所在的bucket要设置成“公有读”而不能是“私有”。 补充 解压缩的脚本: 12345import zipfilefilename = '要解压包的路径'fz = zipfile.ZipFile(filename, 'r')for file in fz.namelist(): fz.extract(file, path) 这个脚本即使没有unzip命令也可以执行的。 获取本地IP地址的脚本: 12345678def get_local_ip(ifname = 'eth0'): import socket, fcntl, struct s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) inet = fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15])) ret = socket.inet_ntoa(inet[20:24]) return retprint get_local_ip() bpython,这是一个好东西,可以在linux环境下实现类似pycharm的提示功能,搭配tab键补全。安装方法就是pip install bpython,然后启动python的时候直接bpython即可。效果如图: 参考资料https://blog.csdn.net/qq_40666028/article/details/79335961http://blog.topspeedsnail.com/archives/9075https://www.168seo.cn/python/24286.html]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python</tag>
<tag>可视化</tag>
</tags>
</entry>
<entry>
<title><![CDATA[将Redhat的yum更换成免费版本]]></title>
<url>%2F2018%2F07%2F20%2F%E5%B0%86radhat%E7%9A%84yum%E6%9B%B4%E6%8D%A2%E6%88%90%E5%85%8D%E8%B4%B9%E7%89%88%E6%9C%AC%2F</url>
<content type="text"><![CDATA[RedHat替换yum源这次给吉林移动做一个项目,他们的服务器必须要用IE浏览器登陆堡垒机进行环境部署。我登陆上去一看,是redhat,在使用yum的时候会有如下报错: 这句话的意思是“redhat自带的yum源是需要注册才是更新下载软件的,如果必须注册才能使用”,换而言之就是要收费。卧槽,怎么可能,我们向来是“要钱没有,要命一条”。于是就要用CentOS源来替代yum源,而CentOS源是免费的。 首先先删除掉redhat自带的yum:rpm -qa | grep yum | xargs rpm -e --nodeps。 然后用cat /etc/redhat-release命令去查看一下系统版本,我这个机器的版本是Red Hat Enterprise Linux Server release 6.5 (Santiago),就去http://mirrors.163.com/centos/6/os/x86_64/Packages/ 下载如下几个文件: 123http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-metadata-parser-1.1.2-16.el6.x86_64.rpmhttp://mirrors.163.com/centos/6/os/x86_64/Packages/yum-3.2.29-81.el6.centos.noarch.rpmhttp://mirrors.163.com/centos/6/os/x86_64/Packages/yum-plugin-fastestmirror-1.1.31-45.el7.noarch.rpm 如果想下载centos 7的就去http://mirrors.163.com/centos/7/os/x86_64/Packages/ 这个网站下,文件名字是一样的就是版本号不一样,需要自己找一下。 然后就是安装这几个包: 123456789rpm -ivh yum-metadata-parser-1.1.2-16.el6.x86_64.rpmrpm -ivh yum-3.2.29-81.el6.centos.noarch.rpmrpm -ivh yum-plugin-fastestmirror-1.1.31-45.el7.noarch.rpmcd /etc/yum.repos.d/wget http://mirrors.163.com/.help/CentOS6-Base-163.repo #最好先备份旧文件sed -i 's#$releasever#6#g' ./CentOS6-Base-163.repoyum clean all #清除原有的缓存yum makecache #重建缓存yum update -y #更新系统 大功告成!可以使用免费的yum去装装装了! 修复Python-urlgrabber版本过低当执行到rpm -ivh yum-3.2.29-81.el6.centos.noarch.rpm这一步的时候,可能会出现一个python的错误: 1Python-urlgrabber >= 3.9.1-10 is needed by yum-3.2.29-73.el6.centos.noarch 要求python-urlgrabber版本必须大于等于3.9.1-10,而用rpm -qa|grep python查看当前的版本是python-urlgrabber-3.9.1-9.el6.noarch,于是就rpm -e python-urlgrabber-3.9.1-9.el6.noarch卸载掉,wget http://mirrors.163.com/centos/6/os/x86_64/Packages/python-urlgrabber-3.9.1-11.el6.noarch.rpm之后,执行rpm -ivh python-urlgrabber-3.9.1-11.el6.noarch.rpm命令安装即可。 安装完毕,再用rpm -ivh --force yum-*安装后面的内容。如图: 无法解析yum源如果在yum makecache的时候出现了http://mirrors.163.com/centos/6/os/x86_64/repodata/repomd.xml: [Errno 14] PYCURL ERROR 6 - "Couldn't resolve host 'mirrors.163.com'"的错误,如图: 就修改一下/etc/resolv.conf,然后在里面添加一句nameserver 8.8.8.8,保存即可。 NOKEY???如果出现Header V3 RSA/SHA1 Signature, key ID c105b9de: NOKEY,可以使用如下方法解决: 123cd /etc/pki/rpm-gpg/ wget http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-6 rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>redhat</tag>
<tag>yum源</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Centos7编码安装php7.2和node.js8.11]]></title>
<url>%2F2018%2F07%2F17%2Fcentos7%E7%BC%96%E7%A0%81%E5%AE%89%E8%A3%85php7-2-7%2F</url>
<content type="text"><![CDATA[安装php7.2首先先做准备工作: 123yum install -y libpng libpng-develyum install -y bzip2 bzip2-develyum install -y curl curl-devel 编译安装步骤在此: 12345678910111213141516171819cd /root/wget http://101.96.10.64/cn2.php.net/distributions/php-7.2.7.tar.gztar -zxvf php-7.2.7.tar.gzcd php-7.2.7sudo ./configure /--prefix=/usr/local/php727 / #PHP7安装的根目录--with-config-file-path=/usr/local/php727/etc / #PHP7的配置目录--with-apxs2=/usr/bin/apxs #如果用的是nginx就不要这句话--with-gd / #PHP gd模块--with-bz2 / #包含BZip2支持--with-zlib / #包含ZLIB支持--with-curl / #包含cURL支持--enable-mbstring / #启用多字节字符串支持--enable-zip / #包含Zip读写支持--enable-fpm / #启用PHP-FPM进程管理--enable-mysqlnd / #Enable mysqlnd explicitly--with-mysqli / #包含mysql支持--with-pdo-mysql/ #包含mysql支持make && make install 如果出现了configure error xml2-config not found. please check your libxml2 installation错误,要执行如下两个: 12yum install libxml2yum install libxml2-devel -y 重新去执行./configure那步和make && make install,整个编译完成之后,再把原带的php.ini拷贝到源码安装的文件夹里: 1cp /root/php-7.2.7/php.ini-development /usr/local/php727/lib/php.ini 设置环境变量,修改/etc/profile文件使其永久性生效,并对所有系统用户生效,在文件末尾加上如下两行代码: 123PATH=$PATH:/usr/local/php/binexport PATHsource /etc/profile 设置php-fpm开机自动启动 1234chmod +x /etc/init.d/php-fpmchkconfig php-fpm oncp /usr/local/php727/etc/php-fpm.conf.default /usr/local/php727/etc/php-fpm.confservice php-fpm start 安装gcc 8.1.0安装node.js需要先安装gcc,但是这个gcc不能用yum install gcc-c++装,因为centos7的gcc版本太低(4.8.5)不满足,在node.js编译的时候会报错:WARNING: C++ compiler too old, need g++ 4.9.4 or clang++ 3.4.2 (CXX=g++)。所以要去https://ftp.gnu.org/gnu/gcc/ 下载一个高版本的,我选择了目前最牛逼的8.1.0。 1234wget https://ftp.gnu.org/gnu/gcc/gcc-8.1.0/gcc-8.1.0.tar.gztar -zxvf gcc-8.1.0.tar.gzcd gcc-8.1.0./contrib/download_prerequisites 此时进入漫长的等待,一会就会出现如下的字样,代表成功安装了! 此时进行编译安装: 12./configure --enable-checking=release --enable-languages=c,c++ --disable-multilib make && make install 又要进行漫长的等待…这一次非常非常漫长,我当时几乎用了大约2个小时… 然后使用gcc -v检查一下版本: 安装node.js 8.11先去https://nodejs.org/en/download/ 下载新的版本包: 直接下载到linux里解压缩,如下: 12345wget https://ftp.gnu.org/gnu/gcc/gcc-8.1.0/gcc-8.1.0.tar.gztar zxvf node-v8.11.3.tar.gzcd node-v8.11.3./configure --prefix=/usr/local/node/8.11.3make && make install 此时在make这一步可能会有这样的错误: 这个原因是“升级gcc时,生成的动态库没有替换老版本gcc动态库”,所以要将gcc最新版本的动态库替换系统中老版本的动态库。 使用find / -name "libstdc++.so*"查找编译gcc时生成的最新动态库,发现最近的动态库是这个: 于是就进行替换并作一个软连接: 123456cp /root/gcc-8.1.0/stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.25 /usr/lib64cd /usr/lib64ll libstdc++.so.6lrwxrwxrwx 1 root root 19 Jul 17 09:59 libstdc++.so.6 -> libstdc++.so.6.0.19 #把原来的记住,防止有回滚的现象rm -rf libstdc++.so.6ln -s libstdc++.so.6.0.25 libstdc++.so.6 然后重新返回到node-v8.11.3文件夹里去make就OK了! 设定环境变量,vim /etc/profile,在export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL一行的上面添加如下内容: 123#set for nodejsexport NODE_HOME=/usr/local/node/8.11.3export PATH=$NODE_HOME/bin:$PATH 保存退出之后,source /etc/profile,再node --version看一下版本是v8.11.3就是OK了!]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>php</tag>
<tag>nodejs</tag>
<tag>gc++</tag>
</tags>
</entry>
<entry>
<title><![CDATA[世界杯的一些感悟]]></title>
<url>%2F2018%2F07%2F12%2F%E4%B8%96%E7%95%8C%E6%9D%AF%E7%9A%84%E4%B8%80%E4%BA%9B%E6%96%B0%E6%84%9F%E6%82%9F%2F</url>
<content type="text"><![CDATA[传控已死?今早英格兰加时赛输给了克罗地亚,至此就剩下一名曼城球员还在本届世界杯继续前进了,那就是法国队的边后卫门迪。再加上之前小组出局的德国和1/8赛输球的西班牙,不得让人怀疑是否“传控已死”? 传控被瓜迪奥拉发扬光大已经10年了,2008年的西班牙也是靠着传控和巴萨的班底拿下两次欧洲杯和一次世界杯。传控虽然在理论上是一个完美的战术,但是战术也需要人来执行,球员水平有高低状态有起伏,裁判的尺度也不一样,这都会影响传控的比赛结果。这几年的传控逐渐被各路教练针对性研究,他们用“高压迫,快速反击,大巴防守”的套路击败传控的经典战役已经屡见不鲜。所以这几年的传控可谓是磕磕绊绊,风光不再。 传控对中场球员要求很高,一旦球员失去了往前传球的机会,那么就成了无效控球,倒脚来倒脚去时间就走光了。而传统打法对球员的要求相对简单但是对球员身体素质和纪律要求很高,所以更多球队选择了传统打法,而他们也很有针对性的对强队进行了部署,或打高空球或者摆大巴打反击。 我个人通过今年的世界杯和以往的几次欧冠来得到这样的结论:传控没有死,但是传控在杯赛的统治力已经大幅下滑,仅但在联赛和小型杯赛还有一定的优势。原因很简单,联赛是三十多场比赛,容错率要远大于杯赛,这就是传控的好处—虐菜相对来说比较稳。但是最近的足坛趋势是世界杯/欧洲杯/欧冠最大,拿不到上面几个锦标,球员就很难得到金球奖。所以在成绩的压力下,足坛也会慢慢将传控降温,改走传统打法。 即使是442也要有能一脚长传功力的中场做炮台,合适的球员搭配合适的战术才会得到胜利。任何战术本身也要自我修复漏洞,现在的传控队伍也开始慢慢放弃无用的控制,讲究定位球破门和远射破门,但是如何破密集防守还是世界教练共同面对的难题。 决赛怎么看?世界杯决赛肯定是防守为主的低比分比赛。法国那边进攻肯定还是“吉鲁吸引炮火,格里兹曼见缝插针,有反击找姆巴佩,角球任意球上大个子”的常规进攻路线。而克罗地亚也是慢节奏抓定位球的方法。我觉得克罗地亚中场并不虚法国,但是莫德里奇这几年没有跟坎特交手过,可以通过这场决赛看看双方谁更技高一筹。法国这批球员相对年轻,虽然这场比赛他们输不起,但是他们已经有了2016年欧洲杯亚军的惨痛经验,而克罗地亚虽然有些球员没有决赛经验,但是他们心态更放松,比较明显的隐患就是克罗地亚的体能是否能支持他们再一次顶得住法国的炮火。 从2006年至今,三届世界杯都踢了延长赛,所以我个人推荐买常规时间法国赢或者平。 至于季军赛,我觉得凯恩会进球,但是比利时3:2赢下英超内战。 世界杯让足彩也跟着热闹起来。我也跟着潮流买了几场比赛,但是我发现凡是“我跟别人说自己没买的”比赛,结果真中了;凡是我“下注买”的比赛都输了,且不用说德国输墨西哥,日本赢哥伦比亚的冷门战,英格兰打哥伦比亚那场的常规时间最后一分钟,米纳进了一个头球,我直接损失100块… 由此我坚信了,我就不是一个特别有好运气的人,而且也比较害怕成为赌徒,完全符合毛主席对知识分子和小资产阶级的定义,一辈子就是老婆孩子热炕头的命。 中国足球怎么办?每到这种重大足球赛事,国足就要被当作反面典型来说嘴。前几天黄西发了微博调侃遭到国足及相关人士的狂喷,其实喷来喷去,主题就是一个“国足那些球员拿钱多,成绩却这么烂,如何能提高国足成绩?” 其实这个主题是老生常谈,每次都说改革但是也没什么进步,哪怕输给泰国1-5,全国上下一片骂,几天之后涛声依旧… 我个人认为中国足球在20年时间内是不可能强大的,因为这与中国国情有关。 第一,在中国传统教育里,中高阶级就没有那种“把孩子培养成运动员”的想法,毕竟丁俊晖父亲和张玉宁父亲才是少数,更多的父母希望孩子去当医生当公务员做生意,这不仅仅是大陆家庭,香港家庭和台湾家庭也是如此。只有贫苦家庭才会把孩子送去专门搞体育; 第二,为什么中产家庭不希望孩子只是把体育当作兴趣爱好而不是职业?首先现在独生子女太多,家长担心吃苦;其次,搞职业体育是从小开始的,万一踢不出来光阴就白白浪费了,而在发达国家,比如日本,贫富差距没有那么大,而且球员素养相对较高,即使不能大红大紫也不至于饿死,而比如南美部分贫困国家,家里不是独生子女,本来很穷上不起学,踢不出来就继续去搬砖,所以这两种国家的足球成绩不会太烂;再其次,足球青训部分教练素质不高,家长担心孩子跟着学坏; 第三,足球需要青训,而青训需要几代人的时间,但是足协更喜欢速成的方法,这与足球规律相悖,所以搞来搞去钱花了不少却始终原地转圈; 记得“诗人”贺炜在日本与比利时之战之后,发微博羡慕日本足球的同时也说“不多说了,说多了反动”。的确,如果潜规则少一点,贫富差距均衡一点,或许不止是足球,全中国体育的市场化就会有更加显著的改善了。]]></content>
<categories>
<category>坠乱花天</category>
</categories>
<tags>
<tag>足球</tag>
<tag>世界杯</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Mycat读写分离测试]]></title>
<url>%2F2018%2F07%2F10%2FMycat%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB%E7%AE%80%E5%8D%95%E6%B5%8B%E8%AF%95%2F</url>
<content type="text"><![CDATA[配置文件解析前文说了schema.xml文件的前两块内容,真正与读写分离有关的是第三块dataHost内容: 123456<dataHost name="mycatTEST" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="rm-bp1099x0552q92edr.mysql.rds.aliyuncs.com:3306" user="mycat" password="这里是密码"> <readHost host="hostS1" url="rr-bp1x35g0w6r767eu4.mysql.rds.aliyuncs.com:3306" user="mycat" password="这里是密码"> </writeHost> </dataHost> 这里主要描述的就是逻辑库需要映射的后端真实数据库的情况。某些选项含义如下: maxCon:指定每个读写实例连接池的最大连接。也就是说,标签内嵌套的writeHost、readHost标签都会使用这个属性的值来实例化出连接池的最大连接数; minCon:指定每个读写实例连接池的最小连接,初始化连接池的大小; balance:负载均衡类型,目前的取值有4种: balance=“0”, 所有读操作都发送到当前可用的writeHost上。 balance=“1”,所有读操作都随机的发送到readHost。 balance=“2”,所有读操作都随机的在writeHost、readhost上分发。 balance=”3”,所有读请求随机的分发到wiriterHost对应的readhost执行,writerHost不负担读压力 writeType:负载均衡类型,目前的取值有3种: writeType=“0”, 所有满足规则的写操作轮询的发送到可用的writeHost上。 writeType=“1”,所有满足规则的写操作随机的发送到readHost。 writeType=“2”,所有满足规则的写操作随机的在writeHost、readhost分发。(这一点我很怀疑,写操作怎么在readhost上进行) dbType:指定后端连接的数据库类型,目前支持二进制的mysql协议,还有其他使用JDBC连接的数据库。例如:mongodb、oracle、 spark等; dbDriver:指定连接后端数据库使用的Driver,目前可选的值有native和JDBC,当使用JDBC时则可以这么写:jdbc:mysql://mycatTEST:3306/; switchType:主库切换算法,目前的取值有3种: switchType=”-1”,表示不自动切换 switchType=”1”, 默认值,自动切换 switchType=”2”, 基于MySQL主从同步的状态决定是否切换,心跳语句为show slave status switchType=”3”,基于MySQL galary cluster的切换机制(适合集群)(1.4.1),心跳语句为show status like 'wsrep%' heartbeat:这个标签内指明用于和后端数据库进行心跳检查的语句; writeHost & readHost:这两个标签都指定后端数据库的相关配置给mycat,用于实例化后端连接池。唯一不同的是,writeHost指定写实例、readHost指定读实例,组着这些读写实例来满足系统的要求。在一个dataHost内可以定义多个writeHost和readHost(我这里就配了一对,其实可以配很多对)。但是,如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。 Demo测试先登陆主库,然后show slave status \G;命令看一下状态,重点是Slave_IO_Running、Slave_SQL_Running和Seconds_Behind_Master这三个字段,如图: 关注这三个字段的原因是“Mycat心跳机制通过检测他们来确定当前主从同步的状态”,如果Seconds_Behind_Master的数值大于slaveThreshold,读写分离筛选器会过滤掉此Slave机器,防止读到很久之前的旧数据,而当主节点宕机后,切换逻辑会检查Slave上的Seconds_Behind_Master是否为0,为0时则表示主从同步,可以安全切换,否则不会切换。 确认完之后,再去log4j2.xml文件把日志级别改成debug。如下: 123456 <Loggers> <asyncRoot level="debug" includeLocation="true"> <AppenderRef ref="Console" /> <AppenderRef ref="RollingFile"/> </asyncRoot></Loggers> 改完之后重启mycat。登陆到8066端口的mycat逻辑库,先创建一个库,再执行一个写的操作: 12345CREATE TABLE `travelrecord` ( `id` int(11) NOT NULL, `name` varchar(255) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into travelrecord (id,name) values(5000010,'bengbeng'); 在日志一看,发现这条记录已经被写入了dn2这个datanode里,如下: 日志的意思是:逻辑库收到了insert命令,然后与真实库连接成功并且执行同步命令,con need syn ,total syn cmd 1 commands,之后发送查询sql,因为插入的那个数据是5000000,按照auto-sharding-long的规则,只会记录到db2的分片里。执行完后,会释放mycat逻辑库与真实Mysql连接也就是release connection MySQLConnection和release channel MySQLConnection。 再执行一个读的操作,比如SELECT * FROM travelrecord;,日志是这样记录的: 与schema.xml里的readhost字段对比,的确是从hostS1上读取到的,由于balance=”3”,所以只会从读库读取,由于读的操作db1、db2、db3这3个分片都会操作(需要把他们的内容拼接在一起才是完整的内容),于是日志会打印三遍,实验结束。至于其他的更改参数情况,可以去看参考资料里的第二篇文章,说的很详尽了。 参考资料http://valleylord.github.io/post/201601-mycat-log-analysis/http://codingo.xyz/index.php/2018/03/08/mycat2/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>读写分离</tag>
<tag>数据库中间件</tag>
<tag>mycat</tag>
</tags>
</entry>
<entry>
<title><![CDATA[博客评论改用来必力]]></title>
<url>%2F2018%2F07%2F09%2F%E5%8D%9A%E5%AE%A2%E8%AF%84%E8%AE%BA%E6%94%B9%E7%94%A8%E6%9D%A5%E5%BF%85%E5%8A%9B%2F</url>
<content type="text"><![CDATA[原来我的博客评论用的是hypercomments,但是这几天发现评论已经不能用了,变成了Close discussion,如图: 去https://www.hypercomments.com/en/pricing 发现已经没有免费版了,或者是我这个google邮箱里的免费版hypercomments到期了,于是就琢磨换成来必力吧。 首先先去https://livere.com 注册一个账号,这个来必力是韩国的软件,但是用google翻译就不用怕了,注册很简单,找回密码也很简单。 再去https://livere.com/insight/communite 里选择免费版,然后填写博客的地址和名称,选择个人网站。这个时候会得到一个data-uid,如图: 打开NexT主题的配置文件_config.yml中,搜索livere_uid,将livere_uid前面的#号去掉,将id填写到livere_uid:后面。再找到Hypercomments,把hypercomments_id这一行注释掉即可。]]></content>
<categories>
<category>博客搭建</category>
</categories>
<tags>
<tag>hexo</tag>
<tag>博客搭建</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Mycat配置文件解析与分表存储测试]]></title>
<url>%2F2018%2F07%2F06%2FMycat%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%A7%A3%E6%9E%90%E4%B8%8E%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB%E6%B5%8B%E8%AF%95%2F</url>
<content type="text"><![CDATA[前文已经部署了mycat并且启动,此时登陆到mycat的8066端口,可以看到有一个database,这个database里有几个tables,如图: 这些库和表根本不是我数据库里的啊,那它是从哪里来的呢?前文说了,mycat有一个虚拟库(逻辑库),它会把逻辑库上的操作映射到真实库里,现在8066这个端口就是虚拟库,里面有几个逻辑表,但是这些表其实不是真正存在的。而mycat主要有三个配置文件,分别是schema.xml、rule.xml和server.xml,server.xml就是配置虚拟数据库的账号密码的地方,很简单没什么好说的,rule.xml是分片规则的配置文件,没事别动它。而schema.xml里是主要配置逻辑库和逻辑表的配置文件。 配置文件解析去除掉注释的schema.xml文件是这样的: 可以看到整个配置文件分为三大块,第一块是schema,第二块是dataNode,第三块是dataHost,其中第三块是跟读写分离相关的,所以这里就说前两个部分,先说第二块dataNode: 123<dataNode name="dn1" dataHost="mycatTEST" database="db1" /><dataNode name="dn2" dataHost="mycatTEST" database="db2" /><dataNode name="dn3" dataHost="mycatTEST" database="db3" /> 这一段表示该数据库有哪些数据节点,以及这些数据节点实际对应的数据服务器(这个节点跟dataHost的块有关)和数据库名,这里配置了3个节点dn1,dn2,dn3,都是在mycatTEST服务器上,也就是说我们需要在mycatTEST那个服务器,也就是下面writeHost的机器里先创建三个database,分别叫db1,db2,db3。我们在逻辑库上的操作都会分别下发到这三个db里,具体的下发算法在上面schema里有写。 再看第一块schema的内容: 12345678910111213<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100"> <table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" /> <table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" /> <table name="goods" primaryKey="ID" type="global" dataNode="dn1,dn2" /> <table name="hotnews" primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" /> <table name="employee" primaryKey="ID" dataNode="dn1,dn2" rule="sharding-by-intfile" /> <table name="customer" primaryKey="ID" dataNode="dn1,dn2" rule="sharding-by-intfile"> <childTable name="orders" primaryKey="ID" joinKey="customer_id" parentKey="id"> <childTable name="order_items" joinKey="order_id" parentKey="id" /> </childTable> <childTable name="customer_addr" primaryKey="ID" joinKey="customer_id" parentKey="id" /> </table></schema> 这一段主要描述了虚拟数据库的schema即TESTDB中有哪些表,以及每个表分布在哪些数据节点上、分布的方法采用哪种算法。其他的选项含义如下: checkSQLschema:当该值设置为true时,如果我们执行语句select * from TESTDB.travelrecord;则MyCat会把语句修改为select * from travelrecord;。即把表示schema的字符去掉,避免发送到后端数据库执行时报(ERROR 1146 (42S02): Table ‘testdb.travelrecord’ doesn’t exist)。这里最好是采用默认的false; sqlMaxLimit:当该值设置为某个数值时。每条执行的SQL语句,如果没有加上limit语句,MyCat也会自动的加上所对应的值。例如设置值为100,执行select fromTESTDB.travelrecord;的效果为和执行select from TESTDB.travelrecord limit 100;相同。需要注意的是,如果运行的schema为非拆分库的,那么该属性不会生效。需要手动添加limit语句; primaryKey:该逻辑表对应真实表的主键,例如:分片的规则是使用非主键进行分片的,那么在使用主键查询的时候,就会发送查询语句到所有配置的DN上,如果使用该属性配置真实表的主键; type:该属性定义了逻辑表的类型,目前逻辑表只有“全局表(global)”和”普通表”两种类型; autoIncrement:mysql对非自增长主键,使用last_insert_id()是不会返回结果的,只会返回0。所以,只有定义了自增长主键的表才可以用last_insert_id()返回主键值。使用autoIncrement=“true”指定这个表有使用自增长主键,这样mycat才会不抛出分片键找不到的异常。这里最好是采用默认的false; rule:该属性用于指定逻辑表要使用的规则名字,规则名字在rule.xml中定义,必须与tableRule标签中name属性属性值一一对应; joinKey:插入子表的时候会使用这个列的值查找父表存储的数据节点; parentKey: 属性指定的值一般为与父表建立关联关系的列名。程序首先获取joinkey的值,再通过parentKey属性指定的列名产生查询语句,通过执行该语句得到父表存储在哪个分片上。从而确定子表存储的位置; 举个例子方便理解,<table name="employee" primaryKey="ID" dataNode="dn1,dn2" rule="sharding-by-intfile" />,意思就是“这个employee的表,主键是ID,只在dn1和dn2以sharding-by-intfile的规则存储”。 举个例子按照上面修改了配置文件之后,重启一波mycat,登陆mycat的9066管理端口,使用show @@datanode;和show @@datasource;可以查看到数据库源和datanode已经成功建立了,如图: 手动在阿里云的RDS的读库上创建db1、db2、db3这三个databases,如图: 由于阿里云读写同步,所以只读实例上也有了db1、db2、db3这三个databases。 此时再开一个窗口,登陆mycat的8066端口,看到里面有了TESTDB这个逻辑库以及里面的逻辑表,但是这些逻辑表实际是不存在的,如图: 这时创建employee表,插入数据: 12345create table employee (id int not null primary key,name varchar(100),sharding_id int not null);insert into employee(id,name,sharding_id) values(1,'leader us',10000);insert into employee(id,name,sharding_id) values(2, 'me',10010);insert into employee(id,name,sharding_id) values(3, 'mycat',10000);insert into employee(id,name,sharding_id) values(4, 'mydog',10010); 检查一下数据已经被成功插入,并且如果使用select * from查看的话,会从两个datanode上去查,而且都自动加上了limit 100的字样,这一点符合我们在schema.xml里配置的<table name="employee" primaryKey="ID" dataNode="dn1,dn2"/>和sqlMaxLimit="100",如图: 再来到阿里云只读RDS数据库里,检查一下刚刚在虚拟数据库里操作的动作是否被正确映射过来。如图: 可见writeType=“0”已经成功,这就是分表存储。 参考资料https://blog.csdn.net/wangshuang1631/article/details/62898469https://sylvanassun.github.io/2016/07/09/2016-07-09-MyCat/http://codingo.xyz/index.php/2018/02/27/mycat1/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>读写分离</tag>
<tag>数据库中间件</tag>
<tag>mycat</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Mycat 1.6.5的部署与启动]]></title>
<url>%2F2018%2F07%2F05%2FMycat%E7%9A%84%E9%83%A8%E7%BD%B2%E4%B8%8E%E7%AE%80%E5%8D%95%E6%B5%8B%E8%AF%95%2F</url>
<content type="text"><![CDATA[准备工作先说一下硬件: mycat服务器:阿里云ECS,centos7.4,2核2G1M带宽,外网带宽主要是为了yum安装方便; 数据库主库:阿里云RDS; 数据库读库:阿里云RDS只读实例; 登陆阿里云ECS之后,首先先进行如下操作: 12345678wget http://dl.mycat.io/1.6.5/Mycat-server-1.6.5-release-20180122220033-linux.tar.gz #下载1.6.5版本yum install java-1.8.0-openjdk* -y #安装java 1.8yum install -y mysql #安装mysql客户端useradd mycat #创建mycat用户passwd mycat #更改这个用户的密码tar -zxvf Mycat-server-1.6.5-release-20180122220033-linux.tar.gz -C /usr/local #解压缩/usr/localcd /usr/local/chown -R mycat.mycat /usr/local/mycat/ #设置mycat目录的属主和属组 然后登陆到阿里云RDS读库和写库,看一下大小写是否是“不敏感”,否则可能会发生表找不到的问题,阿里云的RDS默认是不敏感的: 12345678MySQL [(none)]> show global variables like '%lower_case%';+------------------------+-------+| Variable_name | Value |+------------------------+-------+| lower_case_file_system | OFF | #这个是“当前系统文件是否大小写敏感”,只读参数,无法修改| lower_case_table_names | 1 | #这个是“表名是否大小写敏感”,可以修改,改完了重启生效+------------------------+-------+2 rows in set (0.00 sec) Mycat原理和文件结构Mycat的原理跟Atlas查不多,都是用一个虚拟的数据库作为前端,后面是挂上真实的写库和读库。如图: mycat文件夹的文件结构很简单: conf:配置文件; lib:服务依赖的一些jar文件.; logs:日志存储文件夹; bin:可执行命令的地方: mycat的配置文件主要在/usr/local/mycat/conf文件夹里,里面有很多文件,但是主要的配置文件是如下几个: server.xml用来配置虚拟数据库的信息; schema.xml用来配置真实读库写库的信息; rule.xml是分片规则的配置文件,分片规则的具体一些参数信息单独存放为文件;注意!在这个目录下,配置文件修改,需要重启Mycat或者通过9066端口reload才会生效。 首先在打开server.xml,在如下的地方做修改: 123456789<user name="root" defaultAccount="true"> <!-- 这里是给虚拟库设定一个账号叫root,并且作为默认账号 --> <property name="password">chenx1242</property> <!-- 账号root的密码 --> <property name="schemas">TESTDB</property> <!-- 账号root对应的虚拟库,这个库保持默认比较好 --></user><user name="test"> <!-- 这里是给虚拟库设定一个账号叫test,并且作为默认账号 --> <property name="password">26e9p69r</property> <!-- 账号test的密码 --> <property name="schemas">TESTDB</property> <!-- 账号test对应的虚拟库,这个库保持默认比较好 --> <property name="readOnly">true</property> <!-- 说明这个账号是只读账号 --></user> 然后打开schema.xml,编辑如下地方: 12345678910<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="阿里云RDS:3306" user="账号" password="对应密码"> <!-- can have multi read hosts --> <readHost host="hostS1" url="阿里云只读RDS:3306" user="账号" password="对应密码"/> </writeHost> <!-- <writeHost host="hostS2" url="localhost:3316" user="root" password="123456"/> --> <!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> --> </dataHost> 检查好格式并保存之后,就到mycat目录下的/bin/里./mycat start就启动mycat了。启动成功之后,8066和9066都是被监听的,如图: 启动故障排错如果启动mycat失败,可以去logs文件夹里看日志,这里举例几个有代表性的错误: wrapper.log日志:Caused by: io.mycat.config.util.ConfigException: SelfCheck### schema mycat refered by user test is not exist!server.xml里schema最好选择默认的TESTDB,而不是错误里的自己起名的mycat。 wrapper.log日志:org.xml.sax.SAXParseException; lineNumber: 23; columnNumber: 3; The content of elements must consist of well-formed character data or markup去检查一下server.xml的第23行,看一下是不是多了一个’<’或者’>’。 wrapper.log日志:Caused by: io.mycat.config.util.ConfigException: user root duplicated!server.xml里普通账号root,只读账号也叫root,冲突了。 wrapper.log日志:Caused by: org.xml.sax.SAXParseException; lineNumber: 16; columnNumber: 101; Element type "WriteHost" must be declared.schema.xml配置中writeHost写成了WriteHost导致报错。 mycat.log日志如下: 12018-07-06 15:53:22.894 WARN [$_NIOREACTOR-8-RW] (io.mycat.backend.mysql.nio.MySQLConnectionAuthenticator.handle(MySQLConnectionAuthenticator.java:91)) - can't connect to mysql server ,errmsg:Access denied for user '数据库账号'@'本地IP' (using password: YES) MySQLConnection [id=8, lastTime=1530863602566, user=数据库账号, schema=db3, old shema=db3, borrowed=false, fromSlaveDB=false, threadId=4555911, charset=utf8, txIsolation=3, autocommit=true, attachment=null, respHandler=null, host=阿里云写库地址, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=false] schema.xml里把真实库的配置写错了。 mycat.log日志:(io.mycat.net.NIOConnector.finishConnect(NIOConnector.java:155)) - error: java.net.ConnectException: Connection refusedschema.xml的<dataHost>字段是否写入了多余的数据库。 参考资料http://valleylord.github.io/post/201601-mycat-install/https://www.jianshu.com/p/f15d64fcb2f3]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>读写分离</tag>
<tag>数据库中间件</tag>
<tag>mycat</tag>
</tags>
</entry>
<entry>
<title><![CDATA[写在阿根廷出局之后]]></title>
<url>%2F2018%2F07%2F03%2F%E5%86%99%E5%9C%A8%E9%98%BF%E6%A0%B9%E5%BB%B7%E5%87%BA%E5%B1%80%E4%B9%8B%E5%90%8E%2F</url>
<content type="text"><![CDATA[桑保利的无奈阿根廷在俄罗斯世界杯的征程结束了,3:4输给法国看上去好像不那么糟糕,但是在姆巴佩下半场的2球时间里,阿根廷的后防线的的确确崩溃了。 一天之后的凌晨,俄罗斯靠点球大战送西班牙回家。很多阿根廷的球迷说阿根廷应该效仿俄罗斯,跟法国摆大巴,靠着偷鸡或者点球胜利。 我个人认为,此方法不可取。虽然阿根廷2014年是靠防守进了决赛,但是这支阿根廷老人更老,新人不牛,而且整支队伍缺乏磨合,纪律性也不够。桑保利深知阿根廷无法保持90分钟的高质量大巴,最多三十分钟。而且大巴阵需要一个高点去压迫对方的后卫,比如穆里尼奥的德罗巴、科斯塔、卢卡库,至少也得有一个能跳又壮的C罗在禁区里搅合,但是伊卡尔迪这次没来,所以桑保利无论是防守还是进攻都无法选择大巴。 所以说,“攻出去”是桑保利无奈的选择,至少这样能死的还壮烈一点。当然,桑保利换上法齐奥是一个败笔,但是最根本的原因还是阿根廷人才断档造成的阵容畸形。 梅西的困局梅西在国家队是不是过得不爽?这是必然的。因为他在巴萨得到的支持远大于他在国家队得到的支持,但是这种支持差以肉眼可见的速度缩小。而且阿根廷的媒体对梅西也是比较苛刻,这就吃了没有好公关团队的亏。 让梅西去踢中场是很暴殄天物的行为,但是现在中场式微,梅西不得不去后撤拿球,甚至还要在边路拿球。我曾经说过,梅西后撤拿球就是慢性自杀,首先他不靠近禁区就无法高质量的射门,其次后撤拿球会让对手更多的容错率去包夹他进而消耗他的体力。这样下来不仅场面不好看,梅西的数据更难看,难免被人黑。不过我还是不明白为什么迪巴拉与梅西无法共存,他俩是位置冲突没错,但是梅西可以踢边路,让迪巴拉去踢前腰/影锋,这个从理论上来说是可行的。 反正在俱乐部解决梅西的问题很简单,砸钱买人即可,但是在国家队,估计要费桑保利的脑细胞了(前提是他不下课),所以说足球是和平年代的战争,表面拼的是场上比分,实际拼的是场下准备。 很多球迷反应说梅西在世界杯上没什么笑容,这让我想起来中日甲午战争的时候,中国船上的洋水手回忆说“中国的海员战斗前摩拳擦掌跃跃欲试,但是中国的军官则是一脸忧虑、若有所思”。事实说明,其实军官是更了解敌我实力差距的,梅西也是如此。但是没有办法,他必须要做打一个很难打赢的战争。 梅罗之争可以说这两个人在俄罗斯的表现都是他们各自在俱乐部七层左右的功力(C罗要高一点),但是这两个人都踢飞了点球,而那个点球原本都可以把他们队伍带到下半区去面对较弱对队伍从而提高晋级的概率,可以说国家队过早出局跟他们有直接关系。 梅西在淘汰赛表现还可以但是在0:3输克罗地亚那一场太过失常,但是C罗这一边也是“高开低走”,不过同样四届世界杯,梅西世界杯6球3助攻,C罗是7球1助攻,大家都没有在淘汰赛进球,的确很巧合。 不过皇马三连冠外加葡萄牙拿到了欧洲杯冠军,让C罗的生涯看起来比梅西完美了很多。明年是巴西美洲杯,现在美洲杯的竞争完全不逊于欧洲杯,小马哥离开的阿根廷想夺冠并不乐观,梅西估计注定无法作为领袖为阿根廷带来一个洲际冠军了。 这两个人都是超级射手,而且不可否认的是他们都需要优秀的中场作为火力支持,以前梅西有“哈白布”大杀四方,而C罗现在有了“克卡莫”也逆转了金球奖总数,所以作为球迷,要认识到这一点:现代足球单打一场或许可以,连续独斗五场以上就是天神下凡了。 不过客观的说,除非内马尔等人能拿到世界杯,不然今年的金球奖还是C罗的,梅西和巴萨需要尽快加油,而加油最有效的方法就是补强中场,加强控制。]]></content>
<categories>
<category>坠乱花天</category>
</categories>
<tags>
<tag>足球</tag>
<tag>世界杯</tag>
<tag>阿根廷</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用xshell做代理查看无公网服务器的WEB界面]]></title>
<url>%2F2018%2F06%2F30%2F%E4%BD%BF%E7%94%A8xshell%E5%81%9A%E4%BB%A3%E7%90%86%E6%9F%A5%E7%9C%8B%E6%97%A0%E5%85%AC%E7%BD%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84WEB%E7%95%8C%E9%9D%A2%2F</url>
<content type="text"><![CDATA[在工作中经常有一些服务器是高机密的,那么这样的服务器就要与外网隔离。但是没有公网的服务器如果也没有连入到局域网的话,按常理来说是无法打开程序的Web界面。这里则分享一个黑科技—使用xshell做代理然后用浏览器去查看Web界面。 首先要在Xshell顶端菜单栏选择查看—隧道窗格。如图: 此时Xshell的底端就出来一个窗口,然后选择转移规则,如图: 在转移规则右键,选择添加,在添加的窗口里,类型(方向)选择Dynamic(SOCKS4/5),端口就用默认的1080,备注爱写不写,如图: 来到windows桌面,点击我的电脑—控制面板—Internet选项,打开连接这个标签页,选择下面的局域网设置。如图: 在局域网(LAN)设置里,先在为LAN使用代理服务器前面打勾,然后点击高级,在套接字那里输入127.0.0.1,端口就是刚刚默认的1080,点击确定保存,如图: 此时在浏览器里输入内网的IP地址就能打开这个服务器里Web界面了,比如我公司内部的云存储界面: 不过此时你是完全属于LAN环境,公网是无法访问的。如果要恢复访问公网,那么就要返回到局域网(LAN)设置里,把为LAN使用代理服务器前面的勾点掉就OK。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>运维</tag>
<tag>xshell</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Nginx配置防盗链]]></title>
<url>%2F2018%2F06%2F27%2FNginx%E9%85%8D%E7%BD%AE%E9%98%B2%E7%9B%97%E9%93%BE%2F</url>
<content type="text"><![CDATA[为什么网站们都要限制流量?无论是网站服务器亦或是游戏服务器还是邮件服务器,说穿了也是一台电脑,也有CPU和内存。只不过服务器的CPU功能比个人电脑的CPU功能强大,比如个人电脑的CPU一秒钟能算1亿个数,那么服务器的CPU一秒钟就能算十亿个数。毕竟个人电脑只针对个人,但是服务器是要“接客”的,有了强大的硬件做后盾,网页/游戏/邮箱才不会那么轻易的Down掉。 但是CPU不是人类大脑,人脑是越用越聪明,CPU是越用越磨损,毕竟始终在连电的环境下。于是乎,没有必要的运算能省就省,一个人省一次,十万个人就省十万次,一千万个人就省一千万次,这样达到积少成多的目的。 CPU计算的是各种数据,而这些数据也叫作流量。有用的流量、有价值的流量通过CPU计算无可厚非,但是出现了没有用的流量或者是别人盗用我们的资源,那么这种情况能避免都要避免。什么叫盗用我们的资源,比如自己网站(网站A)上的图片或者视频,被其他人直接复制网站然后粘贴到他们的主页(网站B)上,其他用户登录了B网站,然后点击了那个图片和视频,由于是网址重链接,里外里提供数据的还是我们的服务器。也就是说B网站就是一个中介,而真正提供服务的是网站A,但是广告费和点击率都要网站B赚走了,这事儿实在是叔可忍婶不可忍。 什么是盗链?如何发现被盗链?什么叫盗链,上面已经说的差不多了,如果上面的文字没有看懂的话,举个例子,如果您看到了这两个图片,证明这个网站就是在盗链。 这两个就是一个盗取的是QQ空间的图片,另一个就是百度的图片。用其他网站的图片这事儿本身是无所谓的,只要不涉及版权问题,都希望自己的作品能广泛传播,但是请不要直接通过网址重定向,厚道一点的行为应该是:“图片另存为”,然后到目标网站上去重新上传一下。 这里再多说一点网站的基础知识。 PV值:PV=page view,网站是有少则一个网页多则N多网页组成的一个整体,PV值就是统计用户访问网站的总页数。比如www.JQK.com这个网站,今天有100个用户登录,平均每个用户翻阅了里面5个网页。那么这个网站的PV值就是500。若一个IP地址,对一个页面刷新10000次,PV值也是1.要查询网站的PV值登陆http://www.alexa.cn就行。 Hit值:这个就是对网页里每个元素的点击量,一个网页里的图片就是一个元素,一个flv文件也是一个元素,一首歌曲也是一个元素。这些的总量就是hit值,hit值越高就证明这个网站被人查看的情况越高,那么也证明网站的高人气,那么自然广告也会卖出去很多钱。 因为建网站这事儿关心到了金钱利益,网站越被人关注,自然价值也越大。于是会有一个公式来评判网站的“每日贡献”:总流量=访问流量+下载流量= Page view值 x 页面大小+下载文件大小 x 下载次数。 作为管理者,每天观察一下自己一亩三分地儿的网站数据情况是本职工作。但是有时候也会遇到网站流量很惊人的情况,一般来说,网站流量过大(CPU运转很多)的原因如下: 网站是一个很大的网站:比如说淘宝,京东,网易,youtube,facebook那种大网站,里面成万上亿的网页,而且每天又有那么多人登陆,自然浏览量很大。虽然这些大集团的服务器也是少则几千,多则上万,甚至在不同地区也会有不少的服务器集群,但是这几万台服务器需要提供的数据会很多也是不争的事实。这种现象是正常的。 网页内容太大:可能本身网站是一个小网站,加起来也就十页二十页的内容,但是每一天的流量依旧很惊人,那么很有可能是单页或者某几页的字节太大。比如网页里有太多的图片,太多的视频,太多的其他链接,也有可能是前端码农们给这个网页的规划不合理。导致这个网页每一次被点击都要大费周折(hit值和PV值不高,但是日流量很高),长此以往不仅会耽误用户的整体体验,对服务器也是一个重大伤害。 搜索引擎产生了大量的数据流量:网站需要推广,于是就在各种搜索引擎上打广告,也有自己网站的很多图片用于外部调用。这样的结果就是本身来观摩网站的人很少,但是“借着引擎经过”的人很多,所以就会有PV值不高,但是Hit值和日流量很高的现象出现。 图片或者其他元素被盗链:第一部分就说过了,别人拿我们的图片去吸引别人关注,然后别人想要深入了解,还要来使用我们的服务器去提供详细数据。这种“用我们的牌子住我们的房,吃我们的饭却不给我们钱”的现象实在应该被弄死。这种现象的特征也是PV值不高(没人真正点击网站),但是Hit值和日流量很大(自己服务器的数据都给别的网站提供了)。 网站被DDos攻击了:被一些恶意的IP地址频繁登陆,来回的刷流量。这样迫使CPU做出运算的行为其实就是在远程的破坏服务器的硬件CPU,遇到这种现象,之前Nginx文章里有写,要么通过access.log找到这些IP封掉,要么就在配置文件里加上限制(limit-rate)。 服务器是如何知道图片是从站外而来的呢?在http协议里有一个重要的选项叫refer,这个选项的内容就是该元素的来源地址。如果这个元素是服务器自己提供的,那么头文件里是没有refer这个选项的。通过refer这个信息,我们也可以知道登陆网站的客户是从哪个网站点击链接而来的。这样方便进行一个统计和规划。 假如,我在QQ空间里面发现一个图,然后右键图片,选择在新标签栏里打开图片,这时候通过浏览器审查元素的功能,能查查看请求头信息和响应头信息,发现响应头信息里多了一个refer,里面的内容就是图片的源地址: 我在QQ空间里看腾讯的照片自然是可以的,但是如果我在别的网站里看腾讯的照片,加重了腾讯服务器的负担,自然腾讯公司会不满意。于是腾讯服务器发现当前要引用这个图片的地址与refer头信息不是一个来源之后,就不会把这个图片的数据传送过来,于是就看到那个此图片来自QQ空间,未经准许不可饮用的警告图片。 既然知道了服务器是如何判断文件是否盗链,那么只要伪装一个refer就可以欺骗服务器达到“反防盗链”的目的了。至于这部分,可以自己单独研究。 如何使用Nginx反盗链?同样的使用Nginx.conf,在http的大括号下面,新建一个location,加入如下信息: 12345678910111213141516location ~ .*\.(wma|wmv|asf|mp3|mmf|zip|rar|jpg|gif|png|swf|flv)$ {#指定对以上几种类型的文件建立防盗链 valid_referers none blocked *.alala.com alala.com;#盗链的范围不包括alala.com和alala.com下的二级网站, if($invalid_referer) { #rewrite ^/ http://www.alala.com/error.html; return403;#如果发现有引用以上文件的地址与refer头信息不符的情况,直接重定向成error.html这个网页,服务器返回403,forbidden。 }} 或者使用第三方模块ngx_http_accesskey_module实现Nginx防盗链。实现方法如下: 下载NginxHttpAccessKeyModule模块文件:http://wiki.nginx.org/File:Nginx-accesskey-2.0.3.tar.gz; 解压此文件后,找到nginx-accesskey-2.0.3下的config文件。编辑此文件:替换其中的$HTTP_ACCESSKEY_MODULE为ngx_http_accesskey_module; 用一下参数重新编译nginx:./configure --add-module=Nginx目录/to/nginx-accesskey,然后执行:make && make install; 修改nginx.conf文件,添加以下几行: 123456location /download { accesskey on; accesskey_hashmethod md5; accesskey_arg "key"; accesskey_signature "mypass$remote_addr";} 其中:accesskey为模块开关;accesskey_hashmethod为加密方式MD5或者SHA-1;accesskey_arg为url中的关键字参数;accesskey_signature为加密值,此处为mypass和访问IP构成的字符串。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>nginx</tag>
<tag>防盗链</tag>
</tags>
</entry>
<entry>
<title><![CDATA[给非root开放tcpdump命令权限]]></title>
<url>%2F2018%2F06%2F22%2F%E7%BB%99%E9%9D%9Eroot%E5%BC%80%E6%94%BEtcpdump%E5%91%BD%E4%BB%A4%E6%9D%83%E9%99%90%2F</url>
<content type="text"><![CDATA[这周给开发们上了堡垒机,使用的是开源的jumpserver,官网是http://www.jumpserver.org/ 。 注册了账号下发给各位开发之后,开发反馈了一个问题:无法用tcpdump抓包。因为tcpdump默认是只能被root调用的,如果是非root用户使用就会报错:You don't have permission to capture on that device。 如果要让普通用户也能顺利用上tcpdump,方法很简单,就是对tcpdump这个文件修改成u+s即可。整个过程如下图: 在堡垒机的web界面试一下: 但是jstack这个命令不能按照上面的方法配置给非root用户,因为jstack命令只能是当前java进程的用户才能用。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>运维</tag>
<tag>安全</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Centos6安装git1.9安装过程]]></title>
<url>%2F2018%2F06%2F13%2FCentos6%E5%AE%89%E8%A3%85git1-9%E5%AE%89%E8%A3%85%E8%BF%87%E7%A8%8B%2F</url>
<content type="text"><![CDATA[安装过程Centos 6.x用yum安装git的话,默认是1.7.1。它在执行git push的时候会报错:error: The requested URL returned error: 401 Unauthorized while accessing。这种情况升级git可破。 具体的升级方法如下: 123456789yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-devel #先准备一下环境cd /rootwget https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/git-core/git-1.9.0.tar.gz #下载1.9的包tar -zvxf git-1.9.0.tar.gzcd git-1.9.0make prefix=/usr/local/git all #安装到/usr/local里make prefix=/usr/local/git installln -s /usr/local/git/bin/* /usr/bin/ #建立软连接git --version 常用命令随便列举几个常用命令: 123456789git remote add origin http://xxxxxxx #将后面那个网址作为remote的源站git remote rm origin #将刚刚建立的那个源站删了 git pull origin master #把remote的master分支的内容down到本地git reset --hard HEAD #撤销未提交的文件git fetch -p #更新最新的远程分支,如果远程分支已删除,则删除本地对应标记的远程分支git branch -a #查看所有分支git checkout -b feature/test origin/feature/test #在本地新增对应的远程分支并切换到 新增的分支上git branch -D feature/test #删除本地feature/test分支 这个命令慎用,生产环境后期一般留个4,5个版本的release开头的分支,可以通过此命令删除一些早期版本的分支git branch checkout feature/test #通过此命令可以来回切换本地分支,当存在线上代码需要回滚的时候,可以进行次命令切换到之前的release分支 配置忽视文件每一个项目肯定都会有一些不会变的文件,比如日志等,那么这种“不想要加入版本库”的文件就要做一个忽视,这样每一次push或者pull都回节约一点时间。 要对这种“被忽视”文件进行配置,首先要先在git的文件夹里打开.gitignore,把要忽视的文件或者文件夹路径写进去,注意,这里的根目录是git文件夹而不是传统的根目录。然后git add .gitignore,此时git commit -m '添加忽视文件'和git push给远程gitlab提交一个版本,然后到目标文件夹去,git rm -r --cached 要忽视的文件名,然后git status看一下这个文件是否已经被gitlab上删除了,如果真的删除掉了同时本地文件也没有丢失,就可以再一次的git commit + git push,去gitlab网页检查时候这个文件应该就不会出现在网页里了,以后这个文件也不会参与任何的更改。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Jenkins搭配ansible部署]]></title>
<url>%2F2018%2F06%2F12%2FJenkins%E6%90%AD%E9%85%8Dansible%E9%83%A8%E7%BD%B2%2F</url>
<content type="text"><![CDATA[架构流程现在运维组工具里加入了gitlab这个版本控制工具,再加上原有的jenkins和ansible,整个代码模块部署流程如下:1.在代码服务器上push更改的代码到gitlab;2.gitlab通过webhook推送事件到jenkins,触发构建任务;3.jenkins从gitlab将最新代码拉取下来;4.jenkins通过ansible将最新的代码部署到应用服务器;5.推送构建状态到gitlab; 安装ansiblejenkins虽然支持ansible,但是前提是jenkins所在的主机上要有ansible程序,安装ansible的方法如下: 123pip install --upgrade pippip install paramiko PyYAML Jinja2 httplib2 sixpip install ansible #安装的是2.5.4版本 然后需要jenkins服务器与代码服务器之间建立ssh免密码登陆的关系,这里就不说细节了,可以去看一下http://blog.51cto.com/chenx1242/1763978 这个文章。 再去/etc/ansible/hosts手动输入一下授信服务器的IP地址,启动一下ansible看效果: 如果在启动ansible的时候出现了如下的错误: 12/usr/lib/python2.7/site-packages/requests/__init__.py:80: RequestsDependencyWarning: urllib3 (1.21.1) or chardet (2.2.1) doesn't match a supported version! RequestsDependencyWarning) 那就是python库中urllib3 (1.21.1) or chardet (2.2.1)的版本不兼容,解决办法如下: 123pip uninstall urllib3pip uninstall chardetpip install requests 安装插件登陆jenkins的web页面,选择系统管理—>管理插件,安装如下三个插件:Ansible plugin、Ansible Tower Plugin、AnsiColor。如图: 安装插件并且重启了ansible之后,还是系统管理-–>全局工具配置,找到ansible安装,分别把ansible的路径根据实际情况填写进去,如图: 填写完毕之后保存即可。 配置工程打开某一个project,就用之前在https://rorschachchan.github.io/2018/05/25/Gitlab-Jenkins搭建持续集成系统/ 这个文章里用到的jicheng-test,因为它已经跟gitlab集成了,所以只要gitlab有commit变化,就会webhook到jenkins进行操作。 配置jicheng-test,选择构建这个标签页。在增加构建步骤选择Invoke Ansible Ad-Hoc Command,这里我为了做实验随便写了一点命令,如图: 上面的配置就是先让jenkins输出这个是来自jenkins机器的信息!!,然后启动ansible,对/etc/ansible/hosts里的所有ip机器执行hostname和cd /mnt ; echo "我是你大爷!" >> 321.txt这两个命令。 测试结果前文说了,这个jicheng-test已经做了gitlab+jenkins的配置,所以只要在代码服务器的git文件夹里,执行commit,代码被push到gitlab服务器上的同时也会触发jenkins打包。 于是操作如图: 在gitlab的网页端查看代码已经上传: 再去jenkins里确认是否被成功触发了: 这次操作显示蓝灯,就是OK,点击选择控制台输出,查看一下执行细节: 效果达到!试验成功! 如果需要回滚,就在jenkins新建一个与gitlab相连的project,切换gitlab的分支,然后重新commit,触发jenkins打包并且ansible部署即可。 故障排错可能在jenkins集成的时候出现如下错误: 12345 代码服务器ip | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh: Host key verification failed.\r\n", "unreachable": true} 这是因为jenkins在执行ansible是通过jenkins用户去操作的,虽然我们在安装ansible那一步的时候已经构建了服务器之间的ssh关系,但是那只是root用户的,所以如果没配置jenkins用户的ssh免密码登录,那么sudo su -s /bin/bash jenkins切换到jenkins用户在ssh jenkins@目标IP这一步的时候,会出现如下的提示: 1234The authenticity of host '目标IP(目标IP)' can't be established.ECDSA key fingerprint is SHA256:Nerx/EZH+ul0/qeb21+ii5EctQ0mO8hijIDlAWEGje8.ECDSA key fingerprint is MD5:6e:d8:6d:17:ca:79:9c:5e:bc:7e:9e:e6:33:41:08:25.Are you sure you want to continue connecting (yes/no)? 因为ansible不会主动帮你输入yes,所以还需要在jenkins用户下把id_dsa.pub文件添加到代码服务器的authorized_keys里,制作一个ssh免密码登录。如果这时候你手动执行一下ssh jenkins@目标IP并且输入yes之后,再重新构建这个project就会发现错误变样了: 12345 代码服务器ip | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).\r\n", "unreachable": true} 原因还是上面的话,由于目标机器上是没有jenkins这个用户的,所以自然也不会存在登录密码,即使用了jenkins用户制作了authorized_keys也是没用,所以需要指定ssh到目标IP的用户,如果是ansible的命令就是ansible all -i /etc/ansible/hosts -u root -m shell -a "具体的shell命令",但是jenkins里配置root的地方很难找,所以就可以在/etc/ansible/hosts里更改一下,改成如下的样子: 1目标ip地址 ansible_ssh_user=root #指定用root用户登录到目标IP, 这样执行命令就没有障碍了,不过root用户权限过大,实际生产环境还是建立一个更加保险的账号最佳。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>ansible</tag>
<tag>jenkins</tag>
</tags>
</entry>
<entry>
<title><![CDATA[阿里云centos7升级内核到4.17过程]]></title>
<url>%2F2018%2F06%2F11%2F%E9%98%BF%E9%87%8C%E4%BA%91centos7%E5%8D%87%E7%BA%A7%E5%86%85%E6%A0%B8%E8%BF%87%E7%A8%8B%2F</url>
<content type="text"><![CDATA[docker对内核的支持要求很高,详情可以看:https://www.szyhf.org/2017/01/07/%E9%98%BF%E9%87%8C%E4%BA%91%E4%B8%8Ecentos%E5%86%85%E6%A0%B8%E9%97%AE%E9%A2%98/#comment-54 。文中也有阿里云容器的工程师亲自回复的升级内核的方法,不过他那套是升级内核到4.4,现在已经是4.17了,这里写一下如何升级到最新内核的过程。 而阿里云默认的centos7的内核是3.10的,如图: 首先,安装elrepo的yum源,命令如下: 1234567[root@iZ23pg8sy5bZ ~]#rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org[root@iZ23pg8sy5bZ ~]#rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm Retrieving http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpmRetrieving http://elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpmPreparing... ################################# [100%]Updating / installing... 1:elrepo-release-7.0-3.el7.elrepo ################################# [100%] 其次是安装最新的内核,命令是yum -y --enablerepo=elrepo-kernel install kernel-ml,如果是要安装长期支持的内核,命令是yum –enablerepo=elrepo-kernel -y install kernel-lt,在一顿噼里啪啦之后,就会出现如下的字样,提示我们已经安装了4.17的kernel内核了: 123456789101112Downloading packages:kernel-ml-4.17.0-1.el7.elrepo.x86_64.rpm | 45 MB 00:00:03 Running transaction checkRunning transaction testTransaction test succeededRunning transactionWarning: RPMDB altered outside of yum. Installing : kernel-ml-4.17.0-1.el7.elrepo.x86_64 1/1 Verifying : kernel-ml-4.17.0-1.el7.elrepo.x86_64 1/1 Installed: kernel-ml.x86_64 0:4.17.0-1.el7.elrepo Complete! centos7内核升级完毕后,还需要我们修改内核的启动顺序,vim /etc/default/grub,修改一处地方: 12345678GRUB_TIMEOUT=5GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"GRUB_DEFAULT=saved #把这里的saved改成0GRUB_DISABLE_SUBMENU=trueGRUB_TERMINAL_OUTPUT="console"GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet net.ifnames=0"GRUB_DISABLE_RECOVERY="true"~ 接下来还需要运行grub2-mkconfig命令来重新创建内核配置,命令是grub2-mkconfig -o /boot/grub2/grub.cfg,如下: 12345678910Generating grub configuration file ...Found linux image: /boot/vmlinuz-4.17.0-1.el7.elrepo.x86_64Found initrd image: /boot/initramfs-4.17.0-1.el7.elrepo.x86_64.imgFound linux image: /boot/vmlinuz-3.10.0-693.2.2.el7.x86_64Found initrd image: /boot/initramfs-3.10.0-693.2.2.el7.x86_64.imgFound linux image: /boot/vmlinuz-3.10.0-693.el7.x86_64Found initrd image: /boot/initramfs-3.10.0-693.el7.x86_64.imgFound linux image: /boot/vmlinuz-0-rescue-f0f31005fb5a436d88e3c6cbf54e25aaFound initrd image: /boot/initramfs-0-rescue-f0f31005fb5a436d88e3c6cbf54e25aa.imgdone 执行完毕之后,回到阿里云控制台重启一下这个机器,然后查看一下内核情况: 12uname -r4.17.0-1.el7.elrepo.x86_64]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>docker</tag>
<tag>内核</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Grafana配置smtp邮件]]></title>
<url>%2F2018%2F06%2F06%2FGrafana%E9%85%8D%E7%BD%AEsmtp%E9%82%AE%E4%BB%B6%2F</url>
<content type="text"><![CDATA[配置smtp如果要通过grafana接收告警邮件,都需要配置邮箱。而相关配置文件就是grafana.ini,分别要修改如下几个地方: 1234567891011121314151617181920#################################### SMTP / Emailing ##########################[smtp]enabled = truehost = smtp.163.com:465user = 邮箱前缀@163.com# If the password contains # or ; you have to wrap it with trippel quotes. Ex """#password;"""password = 客户端授权密码;cert_file =;key_file =skip_verify = truefrom_address = 邮箱前缀@163.comfrom_name = Grafana # EHLO identity in SMTP dialog (defaults to instance_name);ehlo_identity = dashboard.example.com#################################### Alerting ############################[alerting]# Disable alerting engine & UI features;enabled = true# Makes it possible to turn off alert rule execution but alerting UI is visibleexecute_alerts = true 我采用了网易邮箱,把文件保存退出之后,重启一下grafana-server。然后在页面的alatm页面里配置Notification channels,如图: 如果发送不成功,去查看一下日志,日志地址是/var/log/grafana/grafana.log。如果发送成功了,那么邮箱会收到这样的一个邮件: 邮箱密码问题问题这里要注意几个问题! 阿里云的服务器出于安全考虑默认是不会开放25端口的,如果你非要用阿里云的服务器去打开25端口,请移步https://www.alibabacloud.com/help/zh/doc-detail/56130.htm ; 如果不想麻烦阿里云那么就要使用其他端口,比如我配置文件里面写的加密的465端口,这个端口不能使用登陆邮箱的普通密码,而是需要填写“授权码”; 以网易邮箱为例,首先先要打开POP3/SMTP服务,如图: 其次然后在客户端授权密码里设置一个新的密码,如图: 然后把这个授权码填写到grafana.ini里,填邮箱的登录密码是错误的。 参考资料http://www.kubiops.com/blog/2017/02/27/Grafana%E5%91%8A%E8%AD%A6%E9%85%8D%E7%BD%AE.html]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>grafana</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Gitlab给分支设定权限]]></title>
<url>%2F2018%2F06%2F06%2FGitlab%E7%BB%99%E5%88%86%E6%94%AF%E8%AE%BE%E5%AE%9A%E6%9D%83%E9%99%90%2F</url>
<content type="text"><![CDATA[给gitlab的各位开发设置权限是很重要的,不然他们就可能会偷偷的把执行分支合并甚至git pull来破坏线上环境。 首先先确定在project下各位人员的身份,在设置(setting)–成员(members)里面,可以看到projects现有的用户和用户组,如图: 由于我这个gitlab已经是汉化版的了,这里做一个简单的中英对比:Master是“主程序员”、Developer是“开发人员”、Reporter是“报告者”,这个身份只有读权限可以创建代码片段,一般来说都给测试人员,而Guest就是“访客”了,它只能提交问题和评论。 然后再到版本库(Repository)里选择保护分支(Protected Branches),如图: Allowed to merge就是分支合并权限,Allowed to push就是推送权限,这两个可以根据不同人的身份进行控制。如果受保护,除了master权限的人员,其余人都不可以push、delete等操作。默认情况下master分支是处于被保护状态下的,developer角色的人是无法提交到master分支的。 如果是docker的话,那么gitlab权限问题修复会用到如下命令: 12docker exec -it gitlab update-permissionsdocker restart gitlab容器的ID]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[解决Waiting(TTFB)过长的问题]]></title>
<url>%2F2018%2F06%2F04%2F%E8%A7%A3%E5%86%B3Waiting-TTFB-%E8%BF%87%E9%95%BF%E7%9A%84%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[正文电商平台整套系统是从上海商派公司(ecshop)那里购买的整套代码,结合我们公司自己的二开功能的3.0版本在上周几经波折终於部署上去了,经过了一个周末之后,今天市场运营的人在微信群里叫:“官方网站打开速度好慢。”果然整个官网首页要5~6秒钟才打得开,这个显然是不能忍受的,使用F12查看细节,发现Waiting(TTFB)的时间非常长,如图: 正常来说TFFB时间通常建议在200ms以下,如果超过推荐值,会引起队列中其他资源下载都跟着变慢。TFFB高主要有两个原因:一是客户端和服务器之前网络情况比较差;二是服务器应用响应比较慢;第三:重定向太多,重定向跟TFFB时间成正比。 于是乎检查网络情况以及各应用负载情况,都是OK的,重定向也很少。那么就减少DNS查询,把所有能用IP的地方都替换了域名,比如nginx的localhost里使用对应服务器的域名而不是127.0.0.1,比如在配置文件里的阿里云的数据库和redis都用IP地址替代。然而收效甚微,该慢依旧是慢。 这个时候就返回到后台去查看,左翻翻右翻翻,最后找到了这个地方,如图: 启动全页缓存,一切就都好了… 参考资料https://www.oschina.net/question/244077_221319]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>运维技术</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Centos7安装zabbix3.4全过程]]></title>
<url>%2F2018%2F06%2F04%2FCentos7%E5%AE%89%E8%A3%85zabbix3-4%E5%85%A8%E8%BF%87%E7%A8%8B%2F</url>
<content type="text"><![CDATA[安装zabbix-server 3.4本文以centos 7为例。 123456789101112131415161718systemctl stop firewalld.service #关闭防火墙systemctl disable firewalld.service #开机不启动防火墙setenforce 0 #清空selinux的配置 yum install mariadb-server mariadb –ysystemctl enable mariadb #设置开机启动systemctl start mariadb #启动MariaDBrpm -Uvh http://repo.zabbix.com/zabbix/3.4/rhel/7/x86_64/zabbix-agent-3.4.5-1.el7.x86_64.rpmyum install zabbix-server-mysql zabbix-web-mysql -yzcat /usr/share/doc/zabbix-server-mysql-3.4.9/create.sql.gz |mysql -uzabbix -pzabbix zabbix #这里是设定zabbix数据库账号密码和database的地方,create.sql.gz这个文件位置要根据实际情况来vim /etc/httpd/conf.d/zabbix.conf #这里要修改文件里的时区,改成Asia/Shanghaisystemctl start zabbix-serversystemctl enable zabbix-server setsebool -P httpd_can_connect_zabbix onsetsebool -P httpd_can_cetwork_connect_db onsystemctl start httpd systemctl enable httpdchkconfig zabbix_agent onsystemctl start zabbix-agent 然后就是在浏览器输入外网IP/zabbix/进行页面安装了,剩下的就不多写了。 安装zabbix-agent 3.4如果是centos 6: 123rpm -Uvh http://repo.zabbix.com/zabbix/3.4/rhel/6/x86_64/zabbix-release-3.4-1.el6.noarch.rpmyum install -y zabbix-agentchkconfig zabbix_agent on;service zabbix_agent start #如果不对就使使zabbix-agent 如果是centos 7: 12rpm -Uvh http://repo.zabbix.com/zabbix/3.4/rhel/7/x86_64/zabbix-agent-3.4.5-1.el7.x86_64.rpmchkconfig zabbix_agent on;service zabbix_agent start #如果不对就使使zabbix-agent 其他资料https://www.kaijia.me/2014/11/zabbix-report-lack-of-free-swap-space-issue-on-server-without-swap-solved/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>zabbix</tag>
</tags>
</entry>
<entry>
<title><![CDATA[记录一次阿里云负载均衡端口监听不正确的过程]]></title>
<url>%2F2018%2F05%2F31%2F%E8%AE%B0%E5%BD%95%E4%B8%80%E6%AC%A1%E9%98%BF%E9%87%8C%E4%BA%91%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AB%AF%E5%8F%A3%E7%9B%91%E5%90%AC%E4%B8%8D%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BF%87%E7%A8%8B%2F</url>
<content type="text"><![CDATA[在阿里云上新配置了一个负载均衡,后面挂载的服务器上安装了一个nginx,分别开启了80端口和8080端口,其中80端口是给http访问的,8080端口是给https访问的,同时在8080端口上做了http跳转https的配置。 但是在负载均衡配置完毕之后,发现tcp的80转8080是OK的,但是https的443转80却始终不OK,网页也自然打不开,但是在nginx上看80端口的确是在stand by: 而且安全组都做了配置,telnet端口也是完全没有问题的,如图: 执行了一下time curl -I -X HEAD SLB的域名 -x http://本机IP地址:80看一下效果,如图: 可见命令执行OK,但是耗时需要7秒,而默认的阿里云SLB在https监听的超时时间设定是5秒,怀疑是后端ECS上对head头响应慢导致的健康检查失败。然后在网页上使用“检查”功能,发现有几个js、css文件耗时很长,于是就叫前端的码农们配合查一下,在几位前端吭哧吭哧解决了这个问题之后,https访问恢复正常。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>nginx</tag>
<tag>阿里云</tag>
</tags>
</entry>
<entry>
<title><![CDATA[苹果手机无信用卡注册区美国apple store的办法]]></title>
<url>%2F2018%2F05%2F30%2F%E8%8B%B9%E6%9E%9C%E6%89%8B%E6%9C%BA%E6%97%A0%E4%BF%A1%E7%94%A8%E5%8D%A1%E6%B3%A8%E5%86%8C%E5%8C%BA%E7%BE%8E%E5%9B%BDapple-store%E7%9A%84%E5%8A%9E%E6%B3%95%2F</url>
<content type="text"><![CDATA[这次去霓虹国在心斋桥Apple体验店买了一个256G的iphone X,由于政府政策的原因,中国区的apple store有很多应用是没有的,于是乎我就打算注册一个美国版的账号,从而登录美国版的apple store去下载那些“你懂得”的app。 首先先登录https://www.apple.com/ ,在网页最下面先确定国家是United States,然后点击Manage Your Apple ID,如图: 在https://appleid.apple.com/#!&page=signin页面里,点击Create your Apple ID建立一个新的apple账号,名称正常写,国家还是United States不要动,生日如实填写,但是如果是未成年人的话,有些成人的app是不可以被下载的。然后就是写好自己的登陆问题,这个问题已经要记住,每次登陆都要输入,忘记的话就麻烦了。注册账号这里其他部分我就不多说了。 账号注册完毕之后,就直接在苹果网站上登录,登录之后,就会看到账号的详细信息,在Payment & Shipping的地方点击Add Payment Method…,如图: 这里有一个PAYMENT METHOD的地方,要填写none,如果你用apple手机上登录这个账号的话,这里是不能选none的,无论是Dr.还是Mr.都没有none这个选项,所以说一定要在网页登录账号。然后就是需要你填写一些用户地址、邮编等信息,由于是账号注册时候选择的是美国,那么也需要填写美国的地址,可以在http://www.haoweichi.com/Index/random里生成一个身份信息填写。如图: 下面那个SHIPPING ADDRESS就是账单邮寄的地址,想填就填,不想填就放那。填写好了之后点击save,但是目前这个账号还是不能通过的,如果你在apple手机登录了这个账号然后登录apple store的话,会有一个提示:该账号没有被使用过,请填写细节。 这里如果你还手机上操作填写细节,发现你刚刚在电脑上填写的地址和邮编已经同步到手机的账号了,但是支付卡那一栏还是没有none,也就是说依旧要一个信用卡。此时请在电脑上下载itunes,然后在电脑的itunes里登录这个美国区账号,由于电脑itunes里的支付渠道依旧可以选择none,所以我们可以在这里绕一个弯,使用itunes这个渠道来完成这个美国区账号的彻底注册。 在itunes把整个账号完整过程都注册完毕之后,再登录到手机端,就可以在美国的apple store里尽情的下载app了!]]></content>
<categories>
<category>坠乱花天</category>
</categories>
<tags>
<tag>apple</tag>
</tags>
</entry>
<entry>
<title><![CDATA[超赞的京都大阪五日游]]></title>
<url>%2F2018%2F05%2F25%2F%E8%B6%85%E8%B5%9E%E7%9A%84%E4%BA%AC%E9%83%BD%E5%A4%A7%E9%98%AA%E4%BA%94%E6%97%A5%E6%B8%B8%2F</url>
<content type="text"><![CDATA[说来惭愧,活了30年了这是我第一次出国旅行,借着公司有一次旅游的机会就跟女朋友一起到京都和大阪玩5天。 搞定了签证,在网上买好了USJ的快速通行票,又预定了随身WIFI,简单在穷游、知乎和马蜂窝上做了做自由行的攻略,18号晚上6点半从杭州萧山机场出发,两个小时后到达关西机场。之前在《miss pilot》里看到过ANA的航空,这一次亲自乘坐感觉还是不错,飞机场有吃有玩还有葡萄酒喝。 到关西机场之后,又按指纹又照相的通过了一连串的海关检查,就跟公司其他小伙伴兵分两路,他们去奈良看鹿,我跟女票直接去京都。凭借女票的三脚猫日语功力和她以前来过大阪的经验,我俩先办理了地铁卡,又购买了一日游行卡,最后坐上了从大阪出发到京都的新干线。 从大阪到京都大约花了一个半小时左右,抵达京都已经是晚上11点了。路上下着细细小雨,再加上两人拖着箱子有点肚饿,就在路边的seven-eleven里简单买了一点水和东西,买东西之余发现在超市里有成人书籍出售。从便利店出来顺着google地图找之前在爱彼迎上预订的民宿,那是一个公寓型民宿,凭借店主之前在邮件里写的密码,我们从信箱里拿到房间钥匙,顺利入住。 日本的庙京都是一个充满寺庙和神社的地方,京都的旅行也就是“从这个庙出来,到下一个庙去”,而清水寺正是京都众多庙里人气很旺的景点之一,日本的庙和神社有一个习惯,入寺前先用竹勺洗手,如果要入室参拜的话还要脱鞋。在京都穿和服是一个很常见的事情,而且我觉得一群人穿和服是一个蛮cool的风景,不过女票没有租,但是在寺里买了很多的御守。 昨晚到京都太晚,无法注意天空,到了白天才发现京都的天真的很蓝,看见远方的山轻而易举。从清水寺出来下一站是八坂神社,巧的是遇到了一对新婚夫妇在这里结婚拍照,不得不说日本新郎传统的黑和服加扇子的形象还是很帅的。 日本的玩水族馆是我非常喜欢的地方,而大阪海游馆也是这次游玩里安排的重要环节之一,但是比较让我失望的是它的海底隧道很短,大约也就杭州水族馆的一半长度。我俩没有看到喂食节目,而且海游馆也没有海豹顶球,海豚跳舞这样的节目。不过海游馆的鱼种类还是很多的,有些品种还可以亲手去摸一摸它们。出了海游馆就是一个蛮大的摩天轮,用一日通票的话可以免费上去坐一圈。 大阪的USJ是我们这次日本之行的最后一站也是最高潮的部分,去年圣诞节我跟女票在上海的迪士尼度过的。从迪士尼回来她就一直碎碎念大阪的环球影城,我俩还特意挑选了一个工作日去玩就是为了尽可能的少排队,但是那天依旧很多很多人,真的超火爆。 环球影城的运营模式跟迪士尼差不多,通过IP分主题区,可以购物也有花车游行。但是整个乐园的玩法相对单一—-都是过山车:哈利波特是过山车、蜘蛛侠是过山车、小黄人是原地晃晃过山车、侏罗纪公园是水上过山车,至于翼龙飞行和好莱坞美梦更是超刺激的过山车… 这一次环球影城的特殊项目有四个:怪物猎人、美少女战士(看动画片)、柯南(密室逃脱)和最终幻想。我跟女票还有公司同事都选择了柯南,虽然通篇日语对白,不过还是能猜出来一个大概剧情,所以一个半小时玩下来感觉就像看了一遍柯南的剧场版,里面的解密就不剧透了,机关真的很难,想要在一个小时内完全逃脱几乎是一个不可能的任务。 上面把正经的娱乐说完了,下面来说一点不正经的娱乐。我和女票在大阪住在日本桥地铁站附近,那里距离道顿堀走路也就10分钟的路程,而道顿堀附近有一个街叫宗右卫门町,那里就是大阪有名的牛郎街,一路走过去各种牛郎宣传大海报和在路边搭讪的小哥,甚至那附近的小吃店里还有牛郎哥的宣传单。除了铺天盖地的牛郎哥哥外还有站街的妹妹,大多数都是黄发浓妆,但是仔细看脸都不算太好看的。这些人会跟过往的单身男女搭讪,邀请他们去店里坐坐喝点酒说说话,至于有没有更进一步的皮肉关系,那就不好说了。而且据说他们是不做不懂日语人的生意的,所以如果他们真的纠缠你了,就直接说我是外国人就好。 日本的购物到了日本,买东西是必然的。不过当地的大商场关门很早,基本晚上八点半左右就开始关门。在伏见稻荷大社甚至有的商铺五点半就打烊了,我很好奇,商场这么早关门,那日本人晚上的娱乐是什么呢?他们除了去居酒屋喝酒和广场溜达再加上回家看电视难道就没有其他的娱乐了吗? 不过,各大药妆店的营业时间很晚,甚至唐吉坷德是24小时营业。这种地方里充满了大陆人、香港人、台湾人、韩国人还有泰国人,在人群和背包中穿梭,拎着篮子买买买,买到5000就可以退税。我女票这次买了很多的卸妆水乳液面膜眼霜口红还有零食,作为一个在旁边无事可做的男人,深深地觉得陪女人逛街是一个很遭罪的事情。 日本的吃我是看过《深夜食堂》和《孤独的美食家》的,所以对日本的食物有一点好感,而且在杭州吃到日本料理也不是一个难事。不过这次到了日本,连续吃了五天当地的饭,发现日本的菜其实很单一。 日本普通的餐就是“米饭+猪肉\牛肉\鸡肉+沙拉+味增汤”,日本的米饭是很好吃的,但是他们的肉做法基本就是炸,炒是很少的。如果不是米饭的话就是炒面、拉面、寿司或者是煎饺。期间我跟女票吃了一次烤肉,里面有“最强牛里脊和牛肠”给我留下了很深的印象。此外在海游馆还吃到了我梦寐已久的大阪烧,插播一句话,吃大阪烧的时候还看到足球运动员郑大世,我女票一眼就认出他来了… 日本的消费能力不低,五天下来,基本上每一顿饭都大约花费了3000多日元,在吉野家吃算比较便宜的,2000不到就能搞定。在烤肉店要了套餐,每人是5000日元。这次在日本,觉得最好吃的是牛里脊,然后就是烤蟹壳。 说完了吃再说说喝,大阪和京都随处可见自动售卖机,售卖机里面基本就是五样饮品—水、绿茶、优酸乳、可乐和咖啡,价钱还都差不多。日本的水果很贵,一个不到6斤重的西瓜就要2200日元左右,橘子大约五块钱一个,但是他们的酒却相比较便宜。在日本我可没少喝梅子酒、气泡果酒和啤酒。 日本的电视我俩住的民宿有一个小电视,里面有12个频道,其中三个是购物频道…我想可能日本的免费电视就这么点,大多数都是收费频道。这九个电视台白天有新闻,有韩剧,有街头采访;晚上有芭蕾舞片段、有综艺节目、还有打着圣光的肉番!说到综艺节目,里面有一个片段就是把秃头用毛巾擦的锃亮,然后用遥控板去对着秃头摁键,结果信号经过秃头的折射,竟然能顺利的操纵电视。再后来叫来两个秃头,尝试多次折射,依旧可以准确遥控电视…就这么一个环节把我之前从来不看日本综艺节目的同事笑翻了,回国后就开始恶补这种日本综艺。 游玩的tips1.日本路边的垃圾箱很少,据说是因为他们没有边走路边吃喝东西的习惯,所以随处带一个塑料袋来装垃圾;2.USJ的快速通行证只有日语区的页面才有,请准备好visa和master卡;3.办理的地铁充值卡不要扔,下一次再来日本,直接储值依旧可以使用;4.到了USJ别上来先买东西,要先排队玩,东西可以放到最后出院的时候再买;5.不会日语在一般情况下没问题,但是如果看不懂车站的话,就难免要问路了,这样会比较头疼,准备一个google翻译。6.champion在日本的地摊也有卖,人民币大约100多,所以淘宝上那些200左右的champion完全不需要考虑… 这次的遗憾这次玩的蛮爽的,但是大阪仅仅只有三天只能玩一个皮毛,比如本次出游的遗憾如下: 1.据说大阪有一个棒球场,20日元一个球,然后通过发球机器发射,游客可以轮棒尝试一下本垒打的快感,但是由于时间太紧没有打上棒球…2.没有去游戏机厅,以前常在漫画里看到日本有那种弹子机,如果赢的多,可以用塑料筐装满小弹子去换钱,这种游戏机厅在大阪的商场很常见,而且门口都有大广告,上面写“新品到店,欢迎畅玩”;3.在龟梨和也和山下智久主演的《我命中注定的人》里,龟梨和也手工雕刻了一个王将的木牌,这次到了大阪逛了很多店,都没有发现这款木雕,不仅没有这个木雕,连战国时期各大将的头盔纺织品也没有看到,这一点很遗憾;4.USJ里的变形金刚和终结者2都暂时停业,不过我后来在B战上看了视频,还是过山车… 等下一次如果有机会能去东京的话,就尝试把上面几个弥补上,再顺便去一趟秋叶原。]]></content>
<categories>
<category>坠乱花天</category>
</categories>
<tags>
<tag>日本</tag>
<tag>旅游</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Gitlab+Jenkins搭建持续集成系统]]></title>
<url>%2F2018%2F05%2F25%2FGitlab-Jenkins%E6%90%AD%E5%BB%BA%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E7%B3%BB%E7%BB%9F%2F</url>
<content type="text"><![CDATA[前言gitlab是一个应用很广泛的版本控制工具,他也有自带的持续集成工具—gitlab cli,但是这个工具不如jenkins那么好用。本文的目的要把gitlab和jenkins进行结合,当我们更新了代码并且把代码push到gitlab的时候,gitlab会把代码的变化通知到jenkins,然后jenkins就会自动构建project。 说一下实验环境:Jenkins所在服务器IP:121.41.37.251(10.168.173.181),版本是2.124(查看jenkins的版本语句java -jar /usr/lib/jenkins/jenkins.war --version);Gitlab所在服务器IP:114.55.224.158(10.25.85.175),使用容器安装,版本是10.7.3; jenkins添加gitlab插件通过浏览器登陆jenkins界面,然后在系统管理里面选择管理插件,如图: 然后在可选择插件里搜索gitlab hook插件,但是没想到我这个版本提示”目前的1.4.2版本的gitlab hook目前存在安全隐患”,如图: 具体的安全隐患细节是这样的: 这个风险请自己把握,然后我选择了继续安装,如图: 安装完了gitlab hook插件后,还要安装GitLab Plugin和Gitlab Authentication plugin这两个插件,方法跟上面的一样。 创建测试工程在jenkins上建立一个新的任务,比如叫jicheng-test,这是一个自由风格的软件项目: 然后在源码管理里面选择git,然后输入gitlab里面仓库的地址,比如我在gitlab上新建了一个project叫jenkinstest,那么就复制这个仓库的地址填到jenkins的Repositories里,如图: 还要在Credentials这里面写上gitlab的用户和密码,然后保存即可: 配置 GitLab 用户浏览器切换到gitlab界面,在用户头像点击,User settings —> Access Tokens,这里的Personal Access Tokens写入一个账号,这个账号是用来让Jenkins和GitLab API交互。这个用户将需要是全局的管理员或添加进每个组/工程,并作为成员。需要开发者权限来报告构建状态。如图: 输入账号和账号有效时期之后,会生成一个Private token,如图: 拷贝它,稍后在配置Jenkins服务器时会用到。 配置 Jenkins 服务器需要配置 Jenkins 服务器来与 GitLab 服务器通信。 在 Jenkins 中,选择系统管理 ->系统设置,在系统设置中找到GitLab的部分: 在Connection name后的输入框中输入连接名称,在Gitlab host URL后的输入框中输入GitLab服务器的URL地址。点击Credentials行最后面的Add -> Jenkins按钮,弹出如下对话框,在Kind 后的下拉列表中选择GitLab API token,并把上一步拷贝的Private token粘贴到API token后面的输入框中。 随后在Credentials的下拉框中选择GitLab API token。 配置 Jenkins 工程来到刚刚建立的那个工程jicheng-test,点击构建触发器,先勾选Build when a change is pushed to GitLab,点击高级,然后再点击一下Generate就会生成一个Secret Token,如下: 点击左下角的保存按钮,保存前面所做的配置。这个时候要记录两个东西,一个是Build when a change is pushed to GitLab那一行中,GitLab CI Service URL:后面的 URL;还有一个就是刚刚生成的Secret Token,这俩在后面配置GitLab工程时需要用到。 配置 GitLab 工程在gitlab进入那个叫jenkinstest的project,然后在settings--->Integrations,在URL里填写刚刚记下来的URL,在Secret Token里填写刚刚记下来的Secret Token,如图: 然后点击下面绿色的add webhook,就会生成一个Webhooks,如图: 去代码服务器上提交一个commit,然后push到gitlab里,再返回到Integrations,对刚刚生成的webhooks点击test,选择push events,如图: 然后就会出现200的成功字样,如图: 如果你再点击一下test上面的edit,就会看到webhook最近调用情况,再点击view details的话,就会看到具体的调用细节,如图: 验证测试此时我在代码服务器上做了一些简单的改动,然后重新把代码push到gitlab服务器上,在jenkins里的相关project里,就会看到已经自动开始build了,如图: 再在具体的某次build里选择控制台输出,就会看到构建的详细过程,如图: 横向扩展如果是多个gitlab的project去对应同一个jenkins,那么需要在jenkins创建任务的时候就选择是根据一个已经存在的任务创建,如图: 在这里写上作为模板的任务的名称,然后在新生成的任务配置的源码管理里添加一个新的Repositories,如图: 如果想要限制分支的话,就要更改Branches to build,现在默认是“只要master分支有push就会触发jenkins构建”。然后再回到gitlab的新project里,进入Integrations,输入配置 Jenkins 工程那个环节里的URL就OK了,Secret Token不用单独填写,因为在复制任务那一步的时候直接把Secret Token全部拷贝过来了。 参考资料http://www.cnblogs.com/bugsbunny/p/7919993.htmlhttps://www.wolfcstech.com/2018/03/26/gitlab_trigger_jenkins_build/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>git</tag>
<tag>jenkins</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用yum安装软件爆No such file or directory]]></title>
<url>%2F2018%2F05%2F17%2F%E4%BD%BF%E7%94%A8yum%E5%AE%89%E8%A3%85%E8%BD%AF%E4%BB%B6%E7%88%86No-such-file-or-directory%2F</url>
<content type="text"><![CDATA[今天开发反馈说yum install redis报错-bash: /usr/bin/yum: /usr/bin/python: bad interpreter: No such file or directory,于是我就登上服务器,使用python一看,反馈-bash: python: command not found,原来这个机器的python被人改动了,用whereis python查了一下,原来python的地址被人改成了/usr/bin/python2.7,于是就手动更改了一下/usr/bin/yum,把#!/usr/bin/python改成了#!/usr/bin/python2.7。但是使用yum install -y redis发现虽然可以连接到库但是会报No such file or directory,如图: 原来光改了/usr/bin/yum还没用,还要改/usr/libexec/urlgrabber-ext-down这个文件,同样也是把python改成/usr/bin/python2.7说明python的路径才可以。 改了上面两个文件之后,又加上了yum clean all和yum makecache,清除一下缓存,一切恢复了正常。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>linux</tag>
<tag>yum</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用zabbix去监控docker容器]]></title>
<url>%2F2018%2F05%2F17%2F%E4%BD%BF%E7%94%A8zabbix%E5%8E%BB%E7%9B%91%E6%8E%A7docker%E5%AE%B9%E5%99%A8%2F</url>
<content type="text"><![CDATA[前言现在容器技术越来越普遍,那么搭建了容器肯定要监控起来,监控方法有两种,一种是做一个zabbix-agent容器去监控容器,还有一个是升级原有的zabbix-agent,这里说第一种。 这里先交代一下环境:zabbix-server的ip是10.244.48.42,要监控的机器ip是10.244.34.79,这个机器里面装了一个容器在运行gitlab,如图: 事前检查两台服务器是否互通,而且10050和10051端口是否standby。还要在zabbix-server端做好auto-discovery,等等等等准备工作。 使用Zabbix Agent Docker进行监控在10.244.34.79这个机器上先安装zabbix-agent容器: 12345678910docker run \ --name=dockbix \ #这个是容器的名称 --net=host \ #容器可以直接访问主机上所有的网络信息 --privileged \ #容器内的root拥有真正的root权限 -v /:/rootfs \ #这个是对应宿主机的映射盘 -v /var/run:/var/run \ --restart unless-stopped \ #不管退出状态码是什么始终重启容器,不过当daemon启动时,如果容器之前已经为停止状态,不要尝试启动它。 -e "ZA_Server=10.244.48.42" \ #这里就填写zabbix-server的ip地址 -e "ZA_ServerActive=10.244.48.42" \ -d hub.c.163.com/canghai809/dockbix-agent-xxl-limited:latest #这里使用了网易蜂巢镜像 但是反馈给我docker: invalid restart policy unless-stopped.这样的错误信息,原来这个gitlab这台服务器的docker版本较老,而unless-stopped这个是在1.9.0版本才加入的,所以对于旧版的docker环境需要改成always。 更改docker run的命令之后重新执行效果如下: 可见容器启动成功,docker logs -f 容器ID号看一下日志是否正常。如果正常的话,应该在zabbix-server端是可以看到这个10.244.34.79已经被添加到控制台里了,如图: 导入监控docker的模版在zabbix server上导入监控docker的模版,一共2个模版,下载后解压。模版下载地址: https://dl.cactifans.com/zabbix/Zabbix-Template-App-Docker.tar.gz 。 我使用主动模式,因此导入Zabbix-Template-App-Docker-active.xml这个模版,如图: 此时可以去zabbix-server这个机器上验证一下是否监控成功,在zabbix-server上执行zabbix_get -s 10.244.34.79 -k docker.discovery,效果如下: 可见已经成功获取到了那两个容器的名称,这就代表zabbix-server已经监控到位了。 验证数据首先现在10.244.34.79里执行docker stats 容器1的ID 容器2的ID...,看一下当前运行的所有容器的状态,如下: 与zabbix-server的latest data做一下对比,由于被监控机的docker版本较老,docker stats结果不是那么的精准,不过用来监控参考还是OK的…如果docker是最新版的,那么监控值是很准的。 剩下的就是慢慢添加triggers了… 补充一句,zabbix-agent 3.2的rpm安装方法: 1234rpm -ivh http://repo.zabbix.com/zabbix/3.2/rhel/7/x86_64/zabbix-release-3.2-1.el7.noarch.rpm yum -y install zabbix-agent zabbix-senderservice zabbix-agent startchkconfig zabbix-agent on 参考资料https://github.com/monitoringartist/zabbix-docker-monitoring (墙裂推荐!)https://blog.codeship.com/ensuring-containers-are-always-running-with-dockers-restart-policy/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>zabbix</tag>
<tag>gitlab</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Gitlab的配置备份]]></title>
<url>%2F2018%2F05%2F16%2FGitlab%E9%85%8D%E7%BD%AE%E5%A4%87%E4%BB%BD%2F</url>
<content type="text"><![CDATA[备份正文我这个gitlab是容器安装的,直接使用最新的gitlab镜像,gitlab版本是10.7.3。 要备份数据的话,就要进入容器里,执行gitlab-rake gitlab:backup:create,效果如下: 执行完毕之后,在/var/opt/gitlab/backups文件夹里就会生成一个备份文件,我这里生成的文件叫:1526454102_2018_05_16_10.7.3_gitlab_backup.tar,这个就是备份的文件。 如果要还原的话,命令如下: 1234567891011# 先关闭连接数据库的进程sudo gitlab-ctl stop# 通过指定时间戳来执行restore操作,这个操作会复写gitlab的数据库sudo gitlab-rake gitlab:backup:restore BACKUP=1526454102 #BACKUP后面的是备份文件开头的那串数字# 再次启动gitlabsudo gitlab-ctl start# 通过下面命令检查gitlabsudo gitlab-rake gitlab:check SANITIZE=true 注意!利用backup机制进行备份的话,对gitlab的版本是要求严格一致的。例如用8.6版的gitlab生成的备份文件,拿到8.7版的gitlab上进行恢复,是会报错的。 同时除了要导入备份文件之外,还要备份以下几个文件: 123/etc/gitlab/gitlab.rb 配置文件须备份/var/opt/gitlab/nginx/conf nginx配置文件/etc/postfix/main.cfpostfix 邮件配置备份 如果要设置这个备份文件的生命周期和备份文件存储的位置,编辑/etc/gitlab/gitlab.rb,修改如下的地方: 1234gitlab_rails['backup_path'] = "/var/opt/gitlab/backups" #这里改新路径gitlab_rails['backup_archive_permissions'] = 0644 #这里可以设定文件的权限# limit backup lifetime to 7 days - 604800 secondsgitlab_rails['backup_keep_time'] = 604800 #文件存储时间一周 然后重启一下gitlab即可。 参考资料http://eimsteim.github.io/2017/12/12/%E8%AE%B0%E4%B8%80%E6%AC%A1%E5%9D%91%E7%88%B9%E7%9A%84Gitlab%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB%E4%B9%8B%E6%97%85/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>gitlab</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Gitlab的简单应用]]></title>
<url>%2F2018%2F05%2F16%2FGitlab%E7%9A%84%E7%AE%80%E5%8D%95%E5%AE%9E%E7%94%A8%2F</url>
<content type="text"><![CDATA[gitlab跟svn的区别我就不多说了,这里直接说具体应用。 建立一个project先登陆到gitlab的网页,我这里使用了root用户,选择create a project,然后就是填写project的名称以及它所属的用户,这里由于只有root用户,所以这个叫jjfjj的project就是root自己的,如果建立了一个组的话,那么这里就填写那个组,如下: 下面这个Visibility Level ,就是权限等级,它分三种: Private:私有的,只有你自己或者组内的成员能访问 Internal:所有登录的用户 Public:公开的,所有人都可以访问 这个东西和project的名称都是可以后期更改的。 然后就是create project,就创建了这个jjfjj。如图: 将本地代码上传建立好了gitlab,就要把开发的代码传进去,我在另外一个机器里,创建一个目录code,这个目录就是专门用来放置代码的,假设现在里面有一个文件叫testcode.py,如图: 具体操作如下: 如果在两个不同的文件夹里执行上面的过程,会传输到两个不同的project里。说明一下上面几个命令的意思:git init:初始化git仓库git add .:添加整个目录里的所有文件到仓库git rm --cached 某个文件名:将某个文件从gitlab上撤除,如果想当前文件夹恢复成一个普通的文件夹,那就把文件夹路径下的.git文件删除掉即可git commit -m '这里是要写的注释':提交代码到仓库git remote add origin +gitlab的地址(上上图里红色框的内容):链接到gitlab服务器git push origin master:push代码到服务器git remote -v:查看当前文件夹的目标project 此时刷新一下gitlab的project页面,就看到刚刚的那个testcode.py已经传上来了。如图: 如果代码有所更改或者出现Everything up-to-date,那么就按顺序执行git add .,git commit -m '这里是要写的注释',git push origin master即可。 免密码push代码在上面的git push origin master的时候需要输入gitlab的用户密码,如要需要免密码push,有两种方法。 第一种方法是ssh,请看 https://blog.whsir.com/post-1749.html/comment-page-1#comment-3425 。 第二种方法是用http的方式传送,先打开.git/config这个配置文件,修改url = http://账号:密码@以.git结尾的项目地址,保存之后重新去执行git add .,git commit -m '这里是注释',git push origin master,就不再需要输入密码了。 从gitlab上垃取代码在要部署的机器上找到要部署的文件夹,我这里用/gitlab为例,操作如下: 12345678910[root@pass-mixnumbus-001 /GITLAB] # git init #将这个文件夹进行初始化 Initialized empty Git repository in /GITLAB/.git/ #提示现在已经安装了.git文件[root@pass-mixnumbus-001 /GITLAB(master)] # git remote add origin http://114.55.224.158/root/JJFJJ.git #确定库[root@pass-mixnumbus-001 /GITLAB(master)] # git pull origin master #制定要把master分支的代码全拉取到这个文件夹里Username for 'http://114.55.224.158': root #输入账号和密码Password for 'http://root@114.55.224.158': From http://114.55.224.158/root/JJFJJ * branch master -> FETCH_HEAD[root@pass-mixnumbus-001 /GITLAB(master)] # ls #看一下效果admin.py looksql.py models.py syncECS.py testsyncECS.py 再与gitlab界面的代码比较一下,果然都过来了!如图: 在gitlab上建立分支gitlab上有很多个分支,主要的分支是master,它也是默认的分支,但是实际工作中是需要其他的开发去新建一些测试的分支,到时候可以先把这些测试的分支拿来部署,如果有问题就回滚回master分支。 分支相关的语句如下: 123456git branch #查看本地分支git branch -r #查看远程分支git branch -a #查看所有分支git branch develop #本地创建新的分支,此时刷新gitlab的页面的话就会有这个叫develop的分支建立了git checkout develop #切换到新的develop分支git checkout -b develop #上面两步可以合成一个命令,这个的意思就是:创建+切换分支 这个时候在代码机上新增或者改变文件,然后执行git add .,git commit -m '这里是要写的注释',git push origin develop,就把新增的变化上传到了develop分支,如图: 1234567891011[root@iZ23pg8sy5bZ ~/GITLAB(develop)] # git push origin developCounting objects: 4, done.Compressing objects: 100% (2/2), done.Writing objects: 100% (3/3), 325 bytes | 0 bytes/s, done.Total 3 (delta 1), reused 0 (delta 0)remote: remote: To create a merge request for develop, visit:remote: http://114.55.224.158/root/JJFJJ/merge_requests/new?merge_request%5Bsource_branch%5D=developremote: To http://114.55.224.158/root/JJFJJ.git fc8d456..8a97b58 develop -> develop 而在部署的机器上,直接执行git pull origin develop,输入账号密码之后,就会把develop分支的内容全部垃取下来了。 如果不想要这个develop分支了,就git branch -d develop,如果要删除远程的分支,就是git push origin :develop,注意这个冒号。 参考资料https://blog.cnbluebox.com/blog/2014/04/15/gitlabde-shi-yong/https://zhang759740844.github.io/2016/08/27/git%E6%8A%80%E5%B7%A7/https://www.restran.net/2016/02/23/git-and-gitlab-guide/https://www.jianshu.com/p/f54053afecf2]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>gitlab</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Gitlab的汉化过程]]></title>
<url>%2F2018%2F05%2F15%2FGitlab%E7%9A%84%E6%B1%89%E5%8C%96%E8%BF%87%E7%A8%8B%2F</url>
<content type="text"><![CDATA[gitlab的容器安装方法部署前的第一句话,gitlab是不支持32位系统的! gitlab用容器部署的话非常的简单,首先docker pull gitlab/gitlab-ce:latest下载镜像,然后docker run --detach --hostname 本机外网IP --publish 443:443 --publish 80:80 --publish 2222:22 --name gitlab --restart always gitlab/gitlab-ce:latest建立一个容器,如图: 然后在浏览器的地址栏里输入服务器的外网IP地址,就到了一个更换密码的页面,这个密码就是root的密码,如图: 设定密码之后,就可以通过root账号登陆gitlab了,如图: 至于“使用ldsp方式登录”、“配置域名”和“关闭注册功能”请移步去看:https://rorschachchan.github.io/2018/05/10/在已经运行的docker容器里面使用中文/ 。 gitlab的汉化方法汉化之前,要确定gitlab的版本,先docker exec -it 容器ID env LANG=C.UTF-8 /bin/bash登陆到容器里,执行cat /opt/gitlab/embedded/service/gitlab-rails/VERSION,由于当时镜像是最新的,所以gitlab的版本是10.7.3。 还是在容器里,执行git clone https://gitlab.com/xhang/gitlab.git,克隆获取汉化版本库(这里要感谢辛苦的汉化工作者,向你们致敬!),默认是获取最新的。如果需要下载老版本的汉化包,则要加上老版本的分支,如:git clone https://gitlab.com/xhang/gitlab.git -b v10.2.5-zh。 然后gitlab-ctl stop先停止gitlab服务,cd gitlab/进入到刚刚下载的那个git包里,执行如下代码: 123456root@10 gitlab]# git fetchroot@10 gitlab]# git diff v10.7.3 v10.7.3-zh > ../10.7.3-zh.diffroot@10 gitlab]# cd ..root@10 ~]# patch -d /opt/gitlab/embedded/service/gitlab-rails -p1 < 10.7.3-zh.diffroot@10 ~]# #如果提示没有patch,请执行apt-get update && apt-get install patchroot@10 ~]# gitlab-ctl start 重新返回到浏览器里,就能看到汉化后的gitlab了,大功告成! 参考资料https://xuanwo.org/2016/04/13/gitlab-install-intro/https://www.jianshu.com/p/6606aed59a56http://adairjun.github.io/2016/12/20/gitlab/https://github.com/marbleqi/gitlab-ce-zh/blob/v10.5.1-zh-patch/Nginx.md]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>gitlab</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅析django里models.py、views.py与网页之间的爱恨纠葛]]></title>
<url>%2F2018%2F05%2F14%2F%E6%B5%85%E6%9E%90django%E9%87%8Cmodels-py%E3%80%81views-py%E3%80%81page%E4%B9%8B%E9%97%B4%E7%9A%84%E7%BA%A0%E8%91%9B%2F</url>
<content type="text"><![CDATA[前言Django + Boostrap现在是运维开发的必备技能,因为他俩是运维可视化的关键技术,而本文就是简单说一下整个Django的数据库---后台---前端的工作原理。其实所谓Django开发,就是熟悉了 Django的规则之后,按照它的规则去填空,填你自己想要展现的东西。 环境:django 2.0 + python 3.6 + pycharm 2018 django建立一个app之后就会有models.py、views.py、admin.py这几个文件,他们三个分别的用途如下: models.py主要是用来设置数据在数据库的存储格式(比如默认值,字段类型和字段长度等等); admin.py是用来设置在/admin/后台里面的显示样式; views.py是用来设置在前台网页里的显示样式; urls.py是用来编辑域名规则; 上面4个文件里,重中之重的就是views.py,说它是整个django的灵魂都不为过!所以要是掌握了它,基本就明白了大半个django了。 举个例子假设有一个models.py,内容如下: 1234567from django.db import models from django.contrib.auth.models import User class BlogType(models.Model): type_name = models.CharField(max_length=15) #规定type_name是一个最大为15字节的charfield def __str__(self): return '<BlogType:%s>' % self.type_name 然后随便加入一些内容,如图: 而在views.py里,要求在前端网页里如此的显示: 1234567from django.shortcuts import render_to_response,get_object_or_404 from .models import BlogType #这里引用了models.py里的那个class def blog_list(request): context = {} context['blog_types'] = BlogType.objects.all() return render_to_response('pageblog/blog_list.html',context) 在views.py里规定,如果有访问域名是/blog_list/的网页,就返回pageblog/blog_list.html这个页面,而这个blog_list.html只是一个框架,里面的内容是context。context本身是一个字典,里面的key对应的value是用ojbects这个函数获得的,objects.all()就是获取全部的意思。用来填充blog_list.html的context里面有blog_types这个key。 那么现在就可以在blog_list.html里使用blog_types这个key了,如下: 1234<!-- 前面略 --> <h4>博客分类</h4> <h3> {{ blog_types }} </h3> <!-- html文件要用views.py里的变量要加上{{}} --><!-- 后面略 --> 这样的效果如下: 返回的是QuerySet类型,QuerySet是Django的查询集,可以通过QuerySet条件查询得到对应模型的对象集合。由此看出blog_types已经成功的引入到了blog_list.html里。 至于拆成每一个“博客类型”就很简单了,html部分如下: 12345678910<h4>博客分类</h4> <!-- ul是无项目的标签 --> <ul> {% for blog_type in blog_types%} #开始一个for循环 <li><a href="{% url 'blogs_with_type' blog_type.pk %}">{{ blog_type.type_name }} </a></li> #对每一个类型加上一个a链接 {% empty %} #如果为空就说“暂无分类” <!-- li是具体的项目 --> <li> 暂无分类 </li> {% endfor %} </ul> 再说urls.py上面说过了,urls.py是配置域名路由规则的。它的格式比较简单,就是path('域名',views.py里的函数,name='自定义名称')。比如下面这个urls.py: 123456789from django.contrib import adminfrom django.urls import include,pathfrom article.views import blog_list #article是django项目里自己创建的一个appurlpatterns = [ path('',blog_list,name='home'), #这里的name可写可不写,如果写的话,在href跳转的时候就可以直接用 path('admin/', admin.site.urls), path('blog/',include('article.urls')), #引用的的include方法用在这里] 上面这个是总的路由文件,当然可以把所有的app的路由都写到里面去,也可以在各自的app下写各自的路由,这样方便管理。比如我就在article这个app文件夹下面又单独了一个urls.py,这里面所有的域名就会自动添加blog/这个路径,而整个urls.py内容如下: 1234567from django.urls import pathfrom . import viewsurlpatterns = [ path('<int:blog_pk>',views.blog_detail,name='blog_detail'), path('type/<int:blog_type_pk>',views.blogs_with_type,name='blogs_with_type'),] 在上面的第一个path里,目的就是传入一个值blog_pk,而这个blog_pk就是在views.py里的blog_detail函数需要传入的参数,跟request一样。上面也说过了,这个两个path都会自动在前面加上/blog/路径。 views.py与前端如何把数据库里的内容映射到前端页面呢?就是用views.py里的render_to_response,它是负责渲染的。render_to_response的用法是后面要加上对应的html页面和要映射的内容,比如下面这个views.py: 1234567from django.shortcuts import render_to_response,get_object_or_404from .models import Blog #这里引用了models.py里面的类:Blogdef blog_detail(request,blog_pk): #每一次使用这个函数都要传入一个参数:blog_pk context = {} context['blog'] = get_object_or_404(Blog,pk=blog_pk) #通过get_object_or_404这个方法生成一个字典 return render_to_response('blog_detail.html',context) #这个blog_detail.html就是对应的前端页面 从上面可以看到,views.py先引入了数据库文件models.py里的Blog这个class,然后设定一个空字典,将这个字典按照对应数据库默认的主键pk与浏览器输入的pk一一对应填满,最后就是按照blog_detail.html为前端框架,里面赋予整个填满值的context字典。 而对应的前端页面blog_detail.html如下: 12345678910111213141516171819202122232425<!DOCTYPE html><html><head> <meta charset='UTF-8'> <title>{{ blog.title }}</title> <!-- 这里的blog就是views.py里context['blog']里的blog --></head><body> <div> <a href="{% url 'home' %}"> <!-- 这里就是返回首页,home是在urls.py里设定的 --> <h2>BACK TO HOMEPAGE</h2> </a> </div> <h3>{{ blog.title }}</h3> <p>作者:{{ blog.author }}</p> <p>分类: <a href="{% url 'blogs_with_type' blog.blog_type.pk %}"> <!-- 就是将blog.blog_type.pk作为views.py里blog_detail函数的传入值 --> {{ blog.blog_type }} </a> </p> <p> {{ blog.blog_type.pk }}</p> <p>发表时间:{{ blog.created_time|date:"Y-m-d H:i:s"}}</p> <!-- 这里将输出时间做了规范化 --> <hr> <p>{{ blog.content }}</p></body></html>]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
</tags>
</entry>
<entry>
<title><![CDATA[加载css样式的两个方法]]></title>
<url>%2F2018%2F05%2F12%2F%E5%8A%A0%E8%BD%BDcss%E6%A0%B7%E5%BC%8F%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[背景说明环境: django 2.0+python 3.6+pycharm 2018project名称: blog 普通的网页加载css网页使用了css才会更好看更炫酷,一般情况下的网页是这样的: 上面这个html文件里用到了模板,而且又对div和 a标签做了class定义,最后分别对各自的class进行了css说明。整个文档看下来比较直观。 但是这样就会有一个问题,就是把html内容和css内容写到了一起,一般来说为了后期维护,都会把css单独写到一个文件夹里,然后让这个html来引用这个css文件夹的具体某个css文件。 于是,我们就在blog这个project目录下建立一个叫static的文件夹,用它来专门装css\js这样的静态文件。 首先,建立了这个static文件,肯定就涉及到引用的问题,而如何让django可以识别static呢? 打开blog/settings.py这个文件,这个文件是整个project的配置文件,在文件末尾加上这样的话,如下: 1234 #将项目根目录里的static制定成项目的静态文件夹,这样django就可以识别 #注意,static前面没有'/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),] 这样blog这个根目录就可以识别了static文件夹了。 然后在pycharm里新建一个css文件叫base.css,如果是专业版的pycharm是可以直接建立css类型文件的,免费社区版是没有这个功能。再将原文里面的所有关于css的内容拷贝到这个base.css里,如下: 1234567891011121314151617181920*{margin: 5px;padding: 10px;} div.nav {background-color: gold;border-bottom: 2px solid #ccc;} div.nav a{text-decoration:none;color: blue;padding: 5px 10px;} div.nav a.logo{display: inline-block;font-size: 120%;} 保存之后,为了验证django是否成功的识别此文件,可以在浏览器里输入外网IP:端口号/static/base.css查看是否返回就是上面内容,如果是就代表识别成功,如果是404就要重新检查settings.py了。 在原有的html里删除掉<style>标签内css内容,还要在head里添加一句话:<link rel="stylesheet" href="/static/base.css">,如下: 这样就达到了引用css所在的static文件夹的目的。 Django内部的加载css方法上面说的是普通html加载css的方法,而django内部也有自己的一套方法,再次打开settings.py里看到有如下的内容: 123456789INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog',] 上面的django.contrib.staticfiles就是django的css加载方法,使用这个方法也很简单。 首先要在html文件最上面先声明要调用这个方法: 12{% load staticfiles %}{# 这个staticfiles是django自带的,可以在settings文件里看到 #} 然后把link标签改成如下: 1<link rel="stylesheet" href="{% static 'base.css' %}"> 保存文件刷新即可,而且用了这种方法,在chrome浏览器里F12 查看,会解析成普通模式的方法,如图: 在django项目里,还是更推荐用django的方法。 额外补充如果html文件开头声明引用了某个模板,比如: 12{% extends 'base.html' %} #声明引用了base.html这个模板{% load staticfiles %} 那么extends语句必须在最上面,不然就会报错:TemplateSyntaxError at /<ExtendsNode: extends 'base.html'> must be the first tag in the template.]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>django</tag>
<tag>前端技术</tag>
</tags>
</entry>
<entry>
<title><![CDATA[创建Mysql容器过程]]></title>
<url>%2F2018%2F05%2F12%2F%E5%88%9B%E5%BB%BAMysql%E5%AE%B9%E5%99%A8%E8%BF%87%E7%A8%8B%2F</url>
<content type="text"><![CDATA[过程记录先docker pull mysql,当前最近的版本是8.0,然后docker images查看一下效果。 然后就是启动一个容器,命令是:docker run --name test-mysql -p 3306:3306 -e MYSQL\_ROOT\_PASSWORD=123456 -d mysql,这句话的意思是:启动一个叫test-mysql的容器, 端口影射是3306到宿主机的3306,同时设置root的密码是123456,然后以守护进程的形式启动。 但是如果在宿主机上使用mysql -h127.0.0.1 -uroot -p123456可能会报错,报错内容是:Authentication plugin ‘caching_sha2_password’ cannot be loaded: 那么就docker exec -it 容器ID号 env LANG=C.UTF-8 /bin/bash进入到容器里,使用mysql -uroot -p123456,看一下在容器里是否可以正常登录,如果可以的话,那么就在mysql的命令行里执行ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';。 退出容器在宿主机上重新连接,这样就OK了。至于原因就是,mysql的客户端是yum安装的,虽然是centos 7,但是安装的版本也是5.5版本的,所以8.0的客户端有一个新的密码加密方式:caching_sha2_password,客户端不支持,所以需要手动到命令行里更改一下。 mysql存储的坑先思考一个问题:假如某mysql容器里存储了100G的数据,那么这个容器关闭了,这100G的数据还在么?从宿主机是可以找到这100G的数据么? docker inspect mysql-container-id,找到里面的volume字段,这里也显示挂载的host路径,可以通过这个路径来备份数据。或者使用docker cp mysql-container-id:/path/to/db-backup-file ./,把容器内数据放到当前目录下。如果是生产环境,必须使用Volume或数据容器。 参考资料http://binary-space.iteye.com/blog/2412769http://dockone.io/question/108]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>mysql</tag>
<tag>docker容器</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python报错:importError: No module named bz2]]></title>
<url>%2F2018%2F05%2F12%2Fpython%E6%8A%A5%E9%94%99%EF%BC%9A'importError-No-module-named-bz2'%2F</url>
<content type="text"><![CDATA[每日统计阿里云同步延迟的邮件早就编写完毕了,现在要放到专门跑脚本的服务器里,进去到这个服务器里发现这个机器已经被人装了两个python,分别是python 2.7.5(默认路径)和python 2.7.13(路径是/usr/local/python/bin/python),说实话我个人不太明白这么做的原因何在。 但是既然已经被人搞成这样了,那就适应环境吧,把脚本拷贝过来,把依赖库都安装好,但是在执行matplotlib的库的时候,爆了一个错误:ImportError: No module named bz2。 这就是因为两个python,但是启动的那个python文件夹里面是没有bz2.so这个文件的,于是就需要把系统里默认的2.7.5的bz2.so拷贝到2.7.13的lib路径里。 首先find / -name bz2.so找一下文件,如下: 1234[root@dvl-stun-002 GETDTS]# find / -name bz2.so/usr/local/aegis/PythonLoader/lib/python2.7/lib-dynload/bz2.so/usr/local/aegis/SecureCheck/lib/python2.7/lib-dynload/bz2.so/usr/lib64/python2.7/lib-dynload/bz2.so 然后cd /usr/local/python/lib/python2.7/,把/usr/lib64/python2.7/lib-dynload/bz2.so复制到这个文件夹里即可。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[在已经运行的docker容器里面使用中文]]></title>
<url>%2F2018%2F05%2F10%2F%E5%9C%A8%E5%B7%B2%E7%BB%8F%E8%BF%90%E8%A1%8C%E7%9A%84docker%E5%AE%B9%E5%99%A8%E9%87%8C%E9%9D%A2%E4%BD%BF%E7%94%A8%E4%B8%AD%E6%96%87%2F</url>
<content type="text"><![CDATA[配置ldap公司搭建的gitlab现在需要开启ldap服务,也就是这样就可以用公司的域账号登陆gitlab,而不用开发一个一个去注册账号了。 开启ldap登陆的任务光荣的落到了我身上,于是我就登陆到gitlab服务器一看,嚯,这还是在容器下启动的,如图: 于是我就docker exec -it 容器ID号 /bin/bash登陆到这个容器里,编辑/opt/gitlab/embedded/service/gitlab-rails/config/gitlab.yml,如下: 1234567891011121314ldap: enabled: true sync_time: host: '公司域账号服务器IP地址' port: 389 uid: 'sAMAccountName' method: 'plain' # "tls" or "ssl" or "plain" bind_dn: 'dahuatech\Ldap_System' password: '对应的密码' active_directory: allow_username_or_email_login: lowercase_usernames: base: user_filter: 但是在填写到base的时候发现了一个问题,公司的base是中文的,是'OU=大数据研究院,OU=研发中心,OU=大华技术,DC=dahuatech,DC=com',但是在文件里输入中文却是乱码,如图: 容器默认是不支持中文的,在容器里的命令行输入中文也是空白。那么面对一个已经运行的容器,如何正常的输入中文呢? 答案是:使用docker exec -it 容器ID号 env LANG=C.UTF-8 /bin/bash登陆,这样就能正常使用中文了,如图: gitlab-ctl restart之后,登陆到gitlab页面一看,已经添加ldap访问方式: 取消“注册”功能修改好配置文件gitlab.yml之后,现在就要把“注册”功能去掉,这样以后都统一用公司的域账号登陆,避免一些乱七八糟的用户来注册乱七八糟的账号。 首先用root账号登陆到gitlab里,在网页里进入到admin area,如图: 然后再点击最下面的settings,选择Sign-up restrictions,然后把Sign-up enabled前面的勾点掉,如图: 保存改变之后,退出root账号,重新看一下,gitlab的注册功能就暂时被取消了,需要的时候再开即可。 配置域名为了方便记忆,给gitlab服务配置一个域名,在阿里云的域名解析控制台给gitlab配置了域名之后,还要在gitlab.yml手动更改hostname,把hostname改成域名的样子,如图: 这样没有结束,因为网页里的url还是显示外网IP而非域名,如下: 此时需要重启,重启的命令是gitlab-ctl restart,重启完了之后url也会发生变化。这样才算完整的配置了域名:]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>docker</tag>
</tags>
</entry>
<entry>
<title><![CDATA[通过阿里云服务器ID添加服务器资料到django的脚本]]></title>
<url>%2F2018%2F05%2F07%2F%E9%80%9A%E8%BF%87%E9%98%BF%E9%87%8C%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8ID%E6%B7%BB%E5%8A%A0%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%B5%84%E6%96%99%E5%88%B0django%E7%9A%84%E8%84%9A%E6%9C%AC%2F</url>
<content type="text"><![CDATA[本文的环境是:centos 7 + django 2.0 + python 3.6 先给django里的project创建了models.py,里面内容如下: 123456789101112131415from django.db import models# Create your models here.class ecs(models.Model): name = models.CharField(verbose_name='云服务器名称',max_length=30) ecsid = models.CharField(verbose_name='云服务器ID',max_length=30,default='') inIP = models.GenericIPAddressField(verbose_name='云服务器内网地址') outIP = models.GenericIPAddressField(verbose_name='云服务器外网地址') osname = models.CharField(verbose_name='操作系统',max_length=50,default='') networktype = models.CharField(verbose_name='网络类型',max_length=20) CPU = models.IntegerField(verbose_name='云服务器CPU',default='2') memory = models.IntegerField(verbose_name='云服务器内存',default='2048') netwidth = models.IntegerField(verbose_name='云服务器外网带宽',default='0M') signtime = models.DateField(auto_now_add=True) remark = models.CharField(verbose_name='备注',max_length=255,blank=True) 可以看出这个就是一个很简单的云服务器的配置统计,但是要录入的阿里云服务器很多,一个一个手动输入实在太累,于是就要写一个脚本来达到django同步的效果! 脚本内容如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#!/usr/bin/env python#coding=utf-8#这个脚本通过查询阿里云服务器ID来达到同步django的目的import json,pymysqlfrom aliyunsdkcore import clientfrom aliyunsdkecs.request.v20140526 import DescribeInstancesRequestclt = client.AcsClient('这里是ak','这里是sk','这里是地域名')# 设置参数request = DescribeInstancesRequest.DescribeInstancesRequest()request.set_accept_format('json')request.add_query_param('RegionId', 'cn-hangzhou')request.add_query_param('InstanceIds', ['这里是服务器ID']) #如果是多个服务器ID,可以继续往下写# 发起请求response = clt.do_action(request)#print(response) #这里可以看一下返回的response,但是它是byte格式的data=str(response, encoding = "utf-8")ecs = json.loads(data) #转换成str格式name = str(ecs['Instances']['Instance'][0]['InstanceName'])ecsid = str(ecs['Instances']['Instance'][0]['InstanceId'])inIP = str(ecs['Instances']['Instance'][0]['VpcAttributes']['PrivateIpAddress']['IpAddress'])[1:-1] #如果不加[1:-1]的话,得到的是一个IP外面还有中括号outIP = str(ecs['Instances']['Instance'][0]['PublicIpAddress']['IpAddress'])[1:-1]networktype = str(ecs['Instances']['Instance'][0]['InstanceNetworkType'])CPU = int(ecs['Instances']['Instance'][0]['Cpu'])memory = int(ecs['Instances']['Instance'][0]['Memory'])osname = str(ecs['Instances']['Instance'][0]['OSName'])#创建数据库连接,注意这里我加入了charset和cursorclass参数conn = pymysql.connect( host = "127.0.0.1", user = "数据库账号", password = "数据库密码", database = "数据库名称", charset = 'utf8', cursorclass = pymysql.cursors.DictCursor)#获取游标cursor = conn.cursor()#三个引号里如何加入变量sql = """INSERT INTO ecs_ecs (name,ecsid,inIP,outIP,networktype,CPU,memory,netwidth,signtime,osname) VALUES (%(name)s,%(ecsid)s,%(inIP)s,%(outIP)s,%(networktype)s,%(CPU)d,%(memory)d,%(netwidth)d,NOW(),%(osname)s);""" % dict(name='\''+name+'\'',ecsid= '\''+ecsid+'\'',inIP=inIP,outIP=outIP,networktype='\''+networktype+'\'',CPU=CPU,memory=memory,netwidth=1,osname='\''+osname+'\'')#print (sql) #在这里可以先看看sql输出的是否正确cursor.execute(sql)# 关闭数据库连接conn.close() 正常来说应该是先建立一个def来获取阿里云服务器配置,再来一个def来将各配置录入到数据库里,同时让阿里云服务器的id作为变量,而且还要加上如果sql执行失败就回滚的语句。而我由于是临时使用,所以这个脚本按照流水式写下来的,不过不影响阅读。 ps.进化之后的脚本在我的github里,地址是: https://github.com/RorschachChan/chenWORK/blob/master/通过阿里云ID号将服务器信息同步到django.py 比如现在要添加一个服务器,这个服务器的id是:i-bp12ego6x9srzsytxeqo,如图: 那么对应填写好脚本里的ak/sk之后,就把i-bp12ego6x9srzsytxeqo填写到“服务器ID”的位置 ,执行这个脚本,结果如下: 不过这个脚本有两个缺点:第一:如果阿里云服务器是中文名称,那么使用api查询出现的是十六进制的符号;第二:如果服务器里没有外网IP或者是后开的临时带宽,那么在outIP的地方得到的值是空,sql语句会因为少一项而报错;第三:这个api没有查询服务器带宽值的功能,还需要另外写一个脚本搭配。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python3</tag>
<tag>django2</tag>
</tags>
</entry>
<entry>
<title><![CDATA[记一次nginx负载均衡配置问题]]></title>
<url>%2F2018%2F04%2F28%2F%E8%AE%B0%E4%B8%80%E6%AC%A1nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E9%85%8D%E7%BD%AE%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[故障背景公司有三个实体服务器,内网IP分别是10.1.82.83、10.1.82.84、10.1.82.113,这三个作为源站使用专线连接到了阿里云的一台nginx服务器上,并且通过这个nginx做负载均衡展示这三个服务器里面的网页。负载均衡使用的是nginx 1.12版本,最外面在上一个CDN起到静态页面加速的作用。整个架构如图: CDN的配置界面如下: 但是现在很奇怪的是,所有节点启动之后,外网用户通过负载后访问均指向了10.1.82.84这一台服务器,nginx.conf配置是最小连接数的配置,如下: 123456789101112131415161718192021222324252627upstream eln.dahuatech.com { #ip_hash; #hash $http_x_forwarded_for; #sticky; least_conn; server 10.1.82.83 max_fails=2 fail_timeout=30s; server 10.1.82.84 max_fails=2 fail_timeout=30s; server 10.1.82.113 max_fails=2 fail_timeout=30s;}server { server_name eln.dahuatech.com; listen 80; listen 443 ssl; access_log logs/eln.dahuatech.com.access.log main; error_log logs/eln.dahuatech.com.error.log; proxy_set_header Host $host:$server_port; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { proxy_pass http://eln.dahuatech.com; }} 测试的时候发现,即使绑定美国和香港的节点去curl,是能正常解析到其他机器上的。如下: 然而源站过来的请求IP集中到了只有一个,这太奇怪了。 故障解决后来发现ngnix后端会把http1.1转换成1.0变成短连接,这个连接存在的时间非常短,因为后端响应非常快。所以即使配上了least_conn,其实是没有任何效果的。这样负载均衡的nginx看到所有源站其实一直都是没有连接的,所以也就一直在给第一个转。 既然这样,就取消了least_conn改用轮询,nginx.conf也改成如下的样子: 最后终于均衡了,大功告成! 后来琢磨了一下,是用sticky其实也是OK的。 所以说,有些情景使用域名不通的情况下,可以考虑直接使用IP,这样就绕过nginx了,不会破坏原来的长连接。 几个主流负载均衡软件配置cookie的方法1.Apache的话首先打开httpd.conf配置文件,确保如下配置没有被注释。 1LoadModule usertrack_module modules/mod_usertrack.so 再在virtual host中添加以下配置。 1234CookieName nameCookieExpires "1 days"CookieStyle CookieCookieTracking on 2.Nginx参考以下配置,设置Cookie。 123456789server { listen 8080; server_name wqwq.example.com; location / { add_header Set-Cookie name=xxxx; root html; index index.html index.htm; }} 3.Lighttpd参考以下配置,设置Cookie。 12345server.modules = ( "mod_setenv" )$HTTP["host"] == "test.example.com" { server.document-root = "/var/www/html/" setenv.add-response-header = ( "Set-Cookie" => "name=XXXXXX" }} 扩展阅读https://cloud.tencent.com/document/product/214/2736http://blog.text.wiki/2015/08/01/nginx-sticky-problem.htmlhttps://cloud.tencent.com/developer/article/1004547]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>nginx</tag>
<tag>负载均衡</tag>
</tags>
</entry>
<entry>
<title><![CDATA[记一次阿里云oss云存储删除失败的问题]]></title>
<url>%2F2018%2F04%2F27%2F%E8%AE%B0%E4%B8%80%E6%AC%A1%E9%98%BF%E9%87%8C%E4%BA%91oss%E4%BA%91%E5%AD%98%E5%82%A8%E5%88%A0%E9%99%A4%E5%A4%B1%E8%B4%A5%E7%9A%84%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[公司每天云存储都要删除过期的内容,工作细节是这样的:每天零点,采集模块开始收集应该删除掉的内容,然后把这个消息传给阿里云MQ,阿里云MQ又把消息传给删除模块,删除模块拿到名单之后,开始调用阿里云OSS的删除API进行删除。架构如图: 但是今天登陆监控平台发现,昨天oss没有删除,上涨了80多个T,如图: 老板一看,卧槽这怎么可以,80多个T的云存储费用可是不容小视的,于是责令追查一下为啥会发生这样的情况。 昨天我的手机又没有收到任何阿里云消息队列告警的信息,可见MQ应该是没问题的,查看一下是否有MQ的产生和消费情况,如下图: 产生的消息基本都消费掉了,由此推断之前的过程都应该是OK的。再查看一下会不会是删除模块外网带宽到期的问题,此时发现两天的流量有显著的不同: 流量明显减少,可以说是删除模块执行任务少了。于是到执行OSS删除API的模块上去抓了几个包,里面情况如下: 但是跑到阿里云对应的bucket里看一下文件情况,比如https://lechangecloud.oss-cn-hangzhou.aliyuncs.com/lechange/4B01F1FPAGE4E9D_img/Alarm/20180427000913997_0_fa62bec6dee24cc0bee42e1ee3e75743_thumb_qcif.dav这个文件,这个文件明明还在里面躺着好好的。如图: 文件00:27的时候就在了,但是2:53分的时候调用阿里云OSS的API去删除,明明返回了200,但是文件却没有真正的从OSS删除掉。 我觉得这样就拿去跟阿里云撕逼还是有点不太妥当,又回到刚刚的那个包里,我发现里面还有一些返回的内容是这样的: 这个图跟之前的图明显路径上不同,而这些文件在OSS上确认是被成功删除掉的,可见的确是文件路径的问题:失败的文件路径是完全路径,而成功的都是相对路径。于是就告诉开发赶快整改代码,把路径统一…]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>阿里云</tag>
</tags>
</entry>
<entry>
<title><![CDATA[在安装docker私有仓库的时候遇到的openssl问题]]></title>
<url>%2F2018%2F04%2F21%2F%E5%9C%A8%E5%AE%89%E8%A3%85docker%E7%A7%81%E6%9C%89%E4%BB%93%E5%BA%93%E7%9A%84%E6%97%B6%E5%80%99%E9%81%87%E5%88%B0%E7%9A%84openssl%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[按照http://wiki.jikexueyuan.com/project/docker-technology-and-combat/local_repo.html 的方法本地安装一个私有仓库,在执行sudo pip install docker-registry这一步的时候,出现了这样的一个错误: 既然说我没有swig,于是我yum install swig -y,安装的是2.0.10-5.el7版本。然后再次pip install docker-registry,一顿噼里啪啦之后,这次成了这样: 又说没有openssl的文件,那执行yum install openssl-devel,OK了之后再次pip install docker-registry,再一次噼里啪啦,如下: 反馈我:/usr/include/openssl/opensslconf.h:44: Error: CPP #error ""This openssl-devel package does not work your architecture?"". Use the -cpperraswarn option to continue swig processing.,这个提示大意是说openssl-devel版本不适合你的系统架构,也就是x86的去找x86的头文件,x86_64的去找x86_64文件,但现在是互相找不到对方。 既然说/usr/include/openssl/opensslconf.h这个第44行有错误,那我们就打开这个文件去看看第44行写的是啥: 123456741 #elif defined(__x86_64__)42 #include "opensslconf-x86_64.h"43 #else44 #error "This openssl-devel package does not work your architecture?"45 #endif46 47 #undef openssl_opensslconf_multilib_redirection_h 这里我把第44行改成了这样: 123456741 #elif defined(__x86_64__)42 #include "opensslconf-x86_64.h"43 #else44 #include "opensslconf.h" #去掉了原来的error提示,改成了安装opensslconf.h文件。45 #endif46 47 #undef openssl_opensslconf_multilib_redirection_h 这一次重新执行sudo pip install docker-registry,终于成功…]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>docker</tag>
<tag>容器技术</tag>
</tags>
</entry>
<entry>
<title><![CDATA[在国王杯前夕评巴萨]]></title>
<url>%2F2018%2F04%2F21%2F%E5%9C%A8%E5%9B%BD%E7%8E%8B%E6%9D%AF%E5%89%8D%E5%A4%95%E8%AF%84%E5%B7%B4%E8%90%A8%2F</url>
<content type="text"><![CDATA[今天凌晨的马竞在西甲意外输给了皇家社会,巴萨的积分优势扩大到了12分。这个周末巴萨要跟塞维利亚打国王杯决赛,4月30号对拉科鲁尼亚的西甲联赛巴萨只要获胜,就会拿到今年的西甲联赛冠军,从而以冠军姿态在诺坎普迎接本赛季第二场国家德比。 巴尔韦德的困境巴萨今年可以说是低姿态开始:从内马尔突然的离开到西超杯被皇马灌了5个,不可为说不惨。但是巴尔韦德在联赛却目前保持不败,这个成绩单可以说是相当不错的,这中间还有在伯纳乌的三球胜利。 赛季中前段,巴萨三线顺风顺水,前有塞梅多惊艳开场,后有大祭司维尔马伦扎实顶上,主教练巴尔韦德也给了阿奈斯这样小将出场机会,哪怕登贝莱那时候养伤,报纸媒体一片其乐融融。如果保利尼奥再有进球,更是一片狂欢。 然后巴尔韦德的保守开始慢慢让人所诟病,他是一个重视防守的教练,这很好,但是他有了库迪尼奥也有了归来的登贝莱,结果反而不敢搞轮换,甚至坚持让布教授打封闭出场,虽然不少人抱怨,但是由于球队整体战绩还算平稳,所以没有大规模的重视。可是巴萨欧冠的结果跟恩里克的第二个赛季一样,倒在了与罗马的第二回合比赛里,连续三年没有闯入欧冠四强。 其实对战罗马的第一回合,巴萨的4:1已经是靠意志拼下来的比赛,球员难免在第二回合的心态上有所轻敌,这种心态上的轻敌难免会影响到身体,但是巴尔韦德的临场指挥也让人严重不满。落下这耻辱一战,媒体和球迷之前的“忍气吞声”一并爆发,狂轰滥炸,直到现在依旧有人说“哪怕真的赛季双冠,也会因为欧冠的失利而让那两冠索然无味”。 所以,巴尔韦德要在这个周日的国王杯决赛和对阵拉科鲁尼亚的西甲联赛里稳扎稳打,把国王杯和西甲冠军彻底拿到手里,这样整个人也能轻松一些。可是说来说去罗马一役这一个跟头摔得太疼了,在那么重大的比赛里失败,肯定需要在一个同样重大的比赛里胜利以挽回颜面,第二回合的国家德比无疑就是一个好的机会,如果巴尔韦德成功捍卫了诺坎普,“联赛双杀皇马+国内双冠”也能成为一个功劳。但是如果那场比赛,一心要打破巴萨不败金身的皇马真的成功了,那巴尔韦德势必在巴萨主帅的位置上也是飘摇。 所以巴帅,请务必要拿下国王杯冠军+西甲冠军!在第二个国家德比里也请拼尽全力!这样才能多少挽回一点“罗马之耻”的颜面。 夏季转会展望我个人认为,巴萨很有可能在今年夏天卖掉如下几个人:西莱森、戈麦斯、小苏亚雷斯、艾尔卡塞尔、比达尔,自由走人的可能会是小白。这些人能套现7000万应该就满足了。 巴萨后卫现在四个人皮克和维尔马伦属于潜藏的伤员病号,米纳技术还是太糙,稍微让人放心的就是乌姆蒂蒂,他的续约问题肯定是休赛期的一个大事。不过我觉得米纳其实可以再留一年看看,他身体素质很好,而且人还年轻没伤病,只要心态练得沉稳,当一个合格的中后卫不难。 至于中场,个人希望小白再踢一年,现在我也觉得一个满血的小白应付普通的联赛、欧冠小组赛和杯赛都不是什么难事。但是目前的媒体趋势是小白赛季结束会来中超重庆队,即使这样巴萨也需要一个山寨的坎特和一个山寨的埃里克森,而罗贝托集这两个属性于一身,所以他就是一个“奉献的砖”,但是这样如果比达尔真的不留下来的话,巴萨还需要补进一个右后卫跟塞梅多良性竞争,这个右后卫的人选就比较挠头了。贝莱林?或许是一个选择,但是这个选择跟当年小法一样—要是双输就不好了。 前场如果能拿下格里兹曼肯定是好的,艾尔卡塞尔这种“躲着后卫”的踢法,虽然进球效率可以,但是没有真正起到轮换苏亚雷斯的作用。这样巴萨还需要在板凳上补充一个中锋(不用多能进球,哪怕搅屎棍也可以),同时也做好登贝莱/苏亚雷斯/梅西/格里兹曼(假设他真的来)的轮换。 总而言之,现在巴萨还是回归433比较好,配合442和4312的变化。那么休赛期最重要的补强就是格里兹曼+能抗中卫的前锋+一个中场+一个优秀的边后卫。 我个人希望的引援名单如下:中场是魏格尔和B队的阿莱尼亚,埃里克森、博格巴和维拉蒂这三个不算是好的选择,要么太贵,要么节奏太慢。至于伊斯科、大卫席尔瓦、皮亚尼奇,那想都别想了,母队不会放人的。至于格雷茨卡,拜仁不是善茬;边后卫可以考虑贝莱林,这个要看一下阿森纳的新教练是谁,摩纳哥的法比尼奥也可以,我知道他现在改中场了,也不耽误来一下跟罗贝托交叉换位…前锋的话,我个人推荐B队阿奈斯试试看,其他的人选估计就是在西甲联赛内部找了;这几个位置,最重要就是中场!梅西当初在哈白布的配合下威力无穷,一旦巴萨的中场重新掌握了控制力,不用频繁回撤的梅西依旧会进球如麻,这一点毋庸置疑。 温格会来?我个人首先不希望巴尔韦德下课,毕竟现在巴萨联赛冠军十拿九稳,国王杯如果也揽入怀中,这样一个成绩单也是一个80分,如果这个分数都炒掉主教练,那么继任者的压力势必很大,所以我个人倾向巴尔韦德留任,好好想一下,等阿图尔以及可能会来的格里兹曼到位了,巴萨应该怎么打。 不过如果温格真的来了,我个人也是赞成的,因为阿森纳的球风本来跟巴萨相似,相信温格跟梅西等人也会无缝接入,到时候教授或许真的可以在巴萨圆了欧冠梦想,不过这个想法成真的可能性低于5%,想想就得了。 下赛季的任务1.进攻体系依旧围绕梅西建队,让梅西继续火力全开的同时保证休息,欧冠要他有大用;2.新球员(包括库蒂尼奥和登贝莱)适应巴萨的风格和体系,让皮克和布教授也能轮换得到休息;3.欧冠一定要进入四强;4.欧冠四强的基础上,西甲联赛冠军和国王杯能拿还是要拿,同时最好也能阻击皇马;]]></content>
<categories>
<category>坠乱花天</category>
</categories>
<tags>
<tag>国际足坛</tag>
<tag>巴塞罗那</tag>
</tags>
</entry>
<entry>
<title><![CDATA[国内Docker的加速方法]]></title>
<url>%2F2018%2F04%2F20%2F%E5%9B%BD%E5%86%85Docker%E7%9A%84%E5%8A%A0%E9%80%9F%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[由于大陆政府的特殊政策,国内想访问一些国外的资源是非常的曲折和痛苦,比较有代表性的就是亚马逊的云存储以及docker,尤其在docker pull一些镜像的时候,更是心惊胆战,祈求不要出现timout,然而现实往往很骨感。如下图: 那么应该如何达到加速的效果呢? 在CentOS 7里,对于使用systemd的系统,请在/etc/docker/daemon.json中写入如下内容:(如果文件不存在请新建该文件) 12345{ "registry-mirrors": [ "https://registry.docker-cn.com" ]} 注意,一定要保证该文件符合 json 规范,否则 Docker 将不能启动。 之后重新启动服务。 12$ sudo systemctl daemon-reload$ sudo systemctl restart docker 注意:如果您之前查看旧教程,修改了docker.service文件内容,请去掉您添加的内容(–registry-mirror=https://registry.docker-cn.com)。 配置加速之后,如果拉取镜像仍然十分缓慢,请手动检查加速器配置是否生效,在命令行执行docker info |grep 'Registry Mirrors' -A,如果从结果中看到了如下内容,说明配置成功。 现在再重新尝试一下docker pull training/webapp,看看效果: 仅用17秒就pull了几乎400MB的镜像,高下立判!]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>docker</tag>
</tags>
</entry>
<entry>
<title><![CDATA[在Grafana里添加worldping插件]]></title>
<url>%2F2018%2F04%2F19%2F%E5%9C%A8Grafana%E9%87%8C%E6%B7%BB%E5%8A%A0worldping%E6%8F%92%E4%BB%B6%2F</url>
<content type="text"><![CDATA[安装插件worldping是一个监控网站的dns、ping、http响应、https响应的插件,要安装它很简单,在granafa服务器里执行如下命令: 12grafana-cli plugins install raintank-worldping-appsystemctl restart grafana-server.service 执行完毕之后在grafana的界面里选择Plugins,然后在APP里找到worldping,启动它,但是此时发现需要一个api,如图: 此时你需要登录grafana的官网,然后点击api keys和ADD API KEY,就可以生成一个API KEY,名字可以随便起,如下: 将生成的api key保存好,并且填回到grafana的api key里,这样worldping插件就可以使用了,如图: 监控网站节点此时点击黄色旋涡,发现多了worldping的选项,点击worldping Home,如图: 然后点击+ New Endpoint,这里我输入我公司的官网域名,然后begin auto-discovery,如图: 生成了结果之后,点击add,此时开始检查几个大城市,如芝加哥、东京、纽约、巴黎等大城市连接到刚刚输入的域名的情况,如图: 大约需要1~2分钟后,数据检查完成,可以点击GO to Summary Dashboard,就会看到图像了: 为什么我这个图里没有http?因为在nginx里我们做了http强制rewrite跳转到https,所以是读不到值的。 删除网站节点如果要删除网站节点,还是在worldping里点击要删除网站后面的齿轮图标,如图: 然后选择configuration,这里可以修改网站域名,要删除的话,选择最下面的destory,输入DELETE确认,然后就可以点击DELETE删除了,如图:]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>grafana</tag>
<tag>图像监控</tag>
</tags>
</entry>
<entry>
<title><![CDATA[用非root用户启动tomcat进程]]></title>
<url>%2F2018%2F04%2F18%2F%E4%BD%BF%E7%94%A8%E6%99%AE%E9%80%9A%E7%94%A8%E6%88%B7%E5%90%AF%E5%8A%A8tomcat%2F</url>
<content type="text"><![CDATA[使用非root用户启动进程是运维安全的一个主要环节,拿tomcat进程来说,如果是使用root用户去启动了tomcat,那么有一个严重的问题,那就是tomcat具有root权限。这意味着你的任何一个jsp脚本都具有root权限,所以那些不怀好意的人可以轻易地用jsp脚本去搞破坏,甚至删除你整个硬盘里的东西!所以为了活着,我们要极力避免这种现象。很多的软件都自带的用户/用户组,比如nginx、zabbix、elasticsearch,但是也有很多的软件没有这么贴心的服务,这就需要我们手动的更改了。 使用非root用户启动tomcat以tomcat为例,打算用chris账号(属于chen这个group)启动。那么首先先创建账号和组,如下: 12345[root@chen-docker ~]groupadd chen #创建chen这个组[root@chen-docker ~]useradd -s /bin/bash -g chen chris #在这个组里面添加chris这个用户[root@chen-docker ~]passwd chris #给这个用户设定密码[root@chen-docker ~]# id chrisuid=1000(chris) gid=1002(chen) groups=1002(chen) #可见添加成功 su chris切换到chris用户,在/home/chris里使用wget http://apache.fayea.com/tomcat/tomcat-9/v9.0.7/bin/apache-tomcat-9.0.7.tar.gz下载tomcat。然后解压缩在/home/chris里,因为chris用户在这里是有权限的。然后进行如下的操作: 12345cd ~/ 代表用户所在目录mkdir -p ~/shell-scriptcd ~/shell-script/touch start.shtoush stop.sh 这个start.sh的内容很简单,如下: 12345678#/bin/bashif [ "root" == "$USER" ] #不让root启动then echo "can't start with user 'root',retry after change user!" exit 1else cd /home/chris/apache-tomcat-9.0.7/bin/ && ./start.shfi shutdown.sh的内容同理: 12345678#/bin/bashif [ "root" == "$USER" ] #不让root启动then echo "can't start with user 'root',retry after change user!" exit 1else cd /home/chris/apache-tomcat-9.0.7/bin/ && ./shutdown.shfi chmod +x *.sh给上面两个脚本可执行权限,但是现在执行startup.sh或者shutdown.sh会出现一个问题: 12Neither the JAVA_HOME nor the JRE_HOME environment variable is definedAt least one of these environment variable is needed to run this program 这是因为chris用户没有权限去启动java这个可执行程序,如果使用java -version回答是bash: java: command not found,这个时候怎么办? 编辑~/.bash_profile,在末尾处加上如下的内容: 然后source .bash_profile,再使用java -version确认一下应该是OK了。这个时候也是可以使用chris用户去启动刚刚的那个start.sh和shutdown.sh的。 由于我们的tomcat是源码解压缩,所以要使用root用户去创建一下/etc/init.d/tomcat。里面内容如下: 123456789#!/bin/bashcase $1 instart)su - chris -lc "sh /home/chris/shell-script/start.sh";; #如果要root启动,那就是su - root -lc "sh /home/utomcat/shell-script/start.sh";;stop)su - chris -lc "sh /home/chris/shell-script/shutdown.sh";;*)echo "parameter error, usage:(start|stop)";;esac 保存之后,执行一下service tomcat start看看效果。 如果要设置开机自启动,别忘了chkconfig --add tomcat和chkconfig tomcat on,在浏览器打开ip:8080看见汤姆猫~ 当普通用户要使用1024以下的端口众所周知,linux默认是不准许普通用户调用1024以下的端口的,那么遇到这样的需求怎么办呢?最好的方法是使用iptables。 首先让程序运行在非root帐户下,并绑定高于1024的端口,在确保能正常工作的时候,将低端口通过端口转发,将低端口转到高端口,从而实现非root运行的程序绑定低端口。要使用此方法可以使用下面的方式: 1234sysctl -w net.ipv4.ip_forward=1 #要长久保存,需要在/etc/sysctl.conf文件内修改,然后sysctl -p /etc/sysctl.confiptables -F -t natiptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to:8088 #将80端口转发到8088iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080 #这句话也可以 这么操作在速度上没有任何影响。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>centos</tag>
<tag>运维安全</tag>
</tags>
</entry>
<entry>
<title><![CDATA[用非root启动进程以及启动docker]]></title>
<url>%2F2018%2F04%2F18%2F%E7%94%A8%E9%9D%9Eroot%E5%90%AF%E5%8A%A8%E8%BF%9B%E7%A8%8B%E4%BB%A5%E5%8F%8A%E5%90%AF%E5%8A%A8docker%2F</url>
<content type="text"><![CDATA[使用非root用户启动普通进程使用非root用户启动进程是运维安全的一个主要环节,拿tomcat进程来说,如果是使用root用户去启动了tomcat,那么有一个严重的问题,那就是tomcat具有root权限。这意味着你的任何一个jsp脚本都具有root权限,所以那些不怀好意的人可以轻易地用jsp脚本去搞破坏,甚至删除你整个硬盘里的东西!所以为了活着,我们要极力避免这种现象。 很多的软件都自带的用户/用户组,比如nginx、zabbix、elasticsearch,但是也有更多的软件没有这么贴心的服务,这就需要我们手动的更改了。 docker不应该使用root启动1.8版本之前的docker是不支持user namespace的,所以那样的话,如果在docker容器内部使用root运行app,那么不可否认,这个root和宿主机的root是同一个UID。但是,需要特别注意的是,容器内的root与宿主机上的root权限并不一定是相等的。 但是为了绝对的安全,还是推荐把docker升级到1.8以上,然后彻底避免用root去启动容器,在http://www.projectatomic.io/docs/docker-image-author-guidance/里最下面一段也明文说了---生产环境里不要用root用户去启动docker!!! 使用非root用户启动docker的办法如下:创建docker组:sudo groupadd docker将当前用户加入docker组:sudo gpasswd -a ${USER} docker重新启动docker服务:sudo service docker restart或sudo systemctl restart docker当前用户退出系统再重新登陆。 参考资料https://www.zhihu.com/question/25580965]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>docker</tag>
<tag>安全</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用docker做一个主从同步的redis集群]]></title>
<url>%2F2018%2F04%2F18%2F%E4%BD%BF%E7%94%A8docker%E5%81%9A%E4%B8%80%E4%B8%AA%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5%E7%9A%84redis%E9%9B%86%E7%BE%A4%2F</url>
<content type="text"><![CDATA[查看容器内部信息之前用docker run -it --name redis-master redis /bin/bash创建了一个redis的docker,现在登陆发现状态已经是exit,于是就使用docker container start 容器ID号or容器名称来重新启动。如图: 然后书里说到要用docker inspect来查看所挂载volume的情况,使用命令: 1[root@chen-docker ~]# docker inspect --format "{{ .Volumes }}" f391531120b0 但是很不幸,系统反馈给我一个错误: 1Template parsing error: template: :1:3: executing "" at <.Volumes>: map has no entry for key "Volumes" 没有这个Volumes,那就干脆查看一下这个容器的所有信息:docker inspect f391531120b0,这个命令里面有Config、Mounts、HostConfig、NetworkSettings等等整个容器的所有信息,比如看一下NetworkSettings相关的内容,如图: 此时使用如下命令: 1234[root@chen-docker ~]# docker inspect --format "{{ .NetworkSettings.IPAddress }}" f391531120b0 #注意前面的.192.168.0.2[root@chen-docker ~]# docker inspect --format "{{ .NetworkSettings.MacAddress }}" f391531120b002:42:c0:a8:00:02 这样就可以获取到内网IP和mac地址,同理换成docker inspect f391531120b0 | grep Mounts -A 10,看一下挂载信息,如图: 原来容器里的/data其实就是宿主机的/var/lib/docker/volumes/94b3c20a6d269c7498ab59ee45c560e84fed64a636767a4baa54fa7befbcd4ff/_data这个文件夹。为了验证这一点,我先到宿主机去创建一个叫aaa文件,如下: 12root@f391531120b0:/data# cat aaa 123123 再返回到宿主机上看: 12345[root@chen-docker ~]# cd /var/lib/docker/volumes/94b3c20a6d269c7498ab59ee45c560e84fed64a636767a4baa54fa7befbcd4ff/_data[root@chen-docker _data]# lsaaa[root@chen-docker _data]# cat aaa 123123 这就搞定了! 主从同步排错就是按书里写的开始配置和启动redis-slave,但是却发现同步没有成功,在redis-slave日志里发现这样的话: 12332677:S 08 Feb 16:14:40.952 * Connecting to MASTER 172.168.10.70:637932677:S 08 Feb 16:14:40.952 * MASTER <-> SLAVE sync started32677:S 08 Feb 16:14:40.953 # Error condition on socket for SYNC: Connection refused 这个的原因就是redis主服务器绑定了127.0.0.1,那么跨服务器IP的访问就会失败,从服务器用IP和端口访问主的时候,主服务器发现本机6379端口绑在了127.0.0.1上,也就是只能本机才能访问,外部请求会被过滤。所以需要修改redis-master的redis.conf,注释掉bind 127.0.0.1,如果是线上生产环境建议绑定IP地址。 重新启动redis之后,发现同步依然失败,日志变成了这样: 12345690:S 17 Apr 09:27:35.906 * Non blocking connect for SYNC fired the event.90:S 17 Apr 09:27:35.907 # Error reply to PING from master: '-DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect'90:S 17 Apr 09:27:36.908 * Connecting to MASTER 192.168.0.2:637990:S 17 Apr 09:27:36.909 * MASTER <-> SLAVE sync started90:S 17 Apr 09:27:36.909 * Non blocking connect for SYNC fired the event.90:S 17 Apr 09:27:36.909 # Error condition on socket for SYNC: Connection reset by peer 这个日志的意思是说redis在没有开启bind和密码的情况下,保护模式被开启。然后Redis的只接受来自环回IPv4和IPv6地址的连接。于是还是要修改redis-master的redis.conf关闭保护模式:portected-mode no,然后重启redis-master即可。 容器内安装ping先检查你的容器是使用什么系统的景象,如果是ubantu那就是apt-get,安装ping的命令如下: 12apt-get updateapt-get install inetutils-ping 如何让容器一直启动如果用了一段时间的docker就会发现,我们的容器经常用了一段时间就自动退出了,docker ps已经找不到了,在docker ps -a里面了,如图: 然后我们docker start containerId想重新开启这个容器,可能这次来的更快,没几分钟容器又自己关了,由这个问题又可能引发其它很多的问题。 docker run指定的命令如果不是那些一直挂起的命令(比如运行top,不断echo),就是会自动退出的。-d命令是设置detach为true,根据官方的文档,意思是让这个命令在后台运行,但并不是一直运行,Docker容器后台运行,就必须有一个前台进程。主线程结束,容器会退出。 我们启动容器的时候不要-d命令启动,用-dit就好了,例如: 12docker run -d hello-world(不要这么做)docker run -dit hello-world(推荐)]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>redis</tag>
<tag>docker</tag>
<tag>主从同步</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用zabbix监控memcache]]></title>
<url>%2F2018%2F04%2F03%2F%E4%BD%BF%E7%94%A8zabbix%E7%9B%91%E6%8E%A7memcache%2F</url>
<content type="text"><![CDATA[监控memcache的原理跟监控redis差不多,都是通过一个类似info的东西可以查询到memcache的状态值,然后通过脚本去获取这些值给zabbix,当发现某值不正常就发出告警。 查询当年memcache状态的命令是echo stats |nc 127.0.0.1 11211,如果没有nc命令,那就yum install -y nc。 获得到的结果是这个样子的: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455[root@lconline-ec2 ~]# echo stats |nc 127.0.0.1 11211STAT pid 1859 memcache服务进程IDSTAT uptime 491093 服务器已运行秒数STAT time 1522740969 服务器当前Unix时间戳STAT version 1.4.25 memcache版本STAT libevent 1.4.13-stableSTAT pointer_size 64 操作系统指针大小STAT rusage_user 14.321822 进程累计用户时间STAT rusage_system 14.095857 进程累计系统时间STAT curr_connections 5 当前连接数量STAT total_connections 51010 Memcached运行以来连接总数STAT connection_structures 8 Memcached分配的连接结构数量STAT reserved_fds 20STAT cmd_get 0 get命令请求次数STAT cmd_set 0 set命令请求次数STAT cmd_flush 0 flush命令请求次数STAT cmd_touch 0 touch命令请求次数STAT get_hits 0 get命令命中次数STAT get_misses 0 get命令未命中次数STAT delete_misses 0 delete命令未命中次数STAT delete_hits 0 delete命令命中次数STAT incr_misses 0 incr命令未命中次数STAT incr_hits 0 incr命令命中次数STAT decr_misses 0 decr命令未命中次数STAT decr_hits 0 decr命令命中次数STAT cas_misses 0 cas命令未命中次数STAT cas_hits 0 cas命令命中次数STAT cas_badval 0 使用擦拭次数STAT touch_hits 0STAT touch_misses 0STAT auth_cmds 0 认证命令处理的次数 STAT auth_errors 0 认证失败数目STAT bytes_read 357040 读取总字节数 STAT bytes_written 60197691 发送总字节数STAT limit_maxbytes 1073741824 分配的内存总大小(字节)STAT accepting_conns 1 服务器是否达到过最大连接(0/1)STAT listen_disabled_num 0 失效的监听数STAT time_in_listen_disabled_us 0STAT threads 4 当前线程数STAT conn_yields 0 连接操作主动放弃数目STAT hash_power_level 16STAT hash_bytes 524288 当前存储占用的字节数STAT hash_is_expanding 0STAT malloc_fails 0 STAT bytes 0 当前存储占用的字节数STAT curr_items 0 当前存储的数据总数STAT total_items 0 启动以来存储的数据总数STAT expired_unfetched 0 STAT evicted_unfetched 0STAT evictions 0 LRU释放的对象数目STAT reclaimed 0 已过期的数据条目来存储新数据的数目STAT crawler_reclaimed 0STAT crawler_items_checked 0STAT lrutail_reflocked 0 END 修改zabbix_agentd.conf,添加一个新的自定义项: 1UserParameter=memcached.stat[*],(echo stats; sleep 1) | telnet 127.0.0.1 11211 2>&1 | awk '/STAT $1 / {print $NF}' 然后重启zabbix-agent,模板就用github里的就好,看到的效果如下:]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>zabbix</tag>
<tag>memcached</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用zabbix去监控网站和tcp连接]]></title>
<url>%2F2018%2F04%2F02%2F%E4%BD%BF%E7%94%A8zabbix%E5%8E%BB%E7%9B%91%E6%8E%A7%E7%BD%91%E7%AB%99%2F</url>
<content type="text"><![CDATA[网页状态码监控在zabbix的web界面,配置–主机–选择一个有外网权限的服务器,比如选择zabbix server–Web检测,如图: 然后点击右上角的创建Web场景,然后依次填入名称,间隔,客户端等等,如图: 然后编辑步骤,先添加,填入对应的url,然后写上200状态码,意思就是返回200是OK的。保存即可,如果还有http认证,那么就继续填写认证。 至此,一个简单的监控官网状态码的配置过程就结束了,剩下就是增添一下触发器,如下: tcp连接监控首先在zabbix-agentd.conf里添加一个新的自定义监控项: 1UserParameter=tcp.status[*],netstat -a | awk '/^tcp/ {++y[$NF]} END {for(i in y) print i,y[i]}' | grep $1 | awk '{print $NF}' 然后service zabbix-agent restart重启客户端,模板就是https://gitee.com/careyjike_173/zabbix/tree/master/template 里的zbx_tcp_status_templates.xml,直接导入即可。如图: 然后自己配置一下time_wait/close_wait的告警阈值。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>zabbix</tag>
<tag>web监控</tag>
<tag>tcp连接</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用zabbix去监控php-fpm]]></title>
<url>%2F2018%2F04%2F02%2F%E4%BD%BF%E7%94%A8zabbix%E5%8E%BB%E7%9B%91%E6%8E%A7php-fpm%2F</url>
<content type="text"><![CDATA[开启状态统计nginx有一个status来获取nginx处理信息的总览情况,php-fpm也有一个状态统计。要打开这个状态统计,需要先打开php-fpm.conf,将pm.status_path = /status前面的注释去掉。 然后跑到nginx里,在nginx.conf里添加一个location: 1234567 location ~ ^/(status|ping) { fastcgi_pass 127.0.0.1:9000; include fastcgi.conf; access_log off; allow 127.0.0.1; deny all;} 然后重启一下php-fpm和nginx,在命令行里输入curl -s http://127.0.0.1:80/status,就会看到php的状态统计,如下图: php-fpm status详解pool - fpm池子名称,大多数为wwwprocess manager – 进程管理方式,值:static, dynamicstart time– 启动日期,如果reload了php-fpm,时间会更新start since – 运行时长accepted conn – 当前池子接受的请求数listen queue – 请求等待队列,如果这个值不为0,那么要增加FPM的进程数量max listen queue – 请求等待队列最高的数量listen queue len – socket等待队列长度idle processes – 空闲进程数量active processes – 活跃进程数量total processes – 总进程数量max active processes – 最大的活跃进程数量(FPM启动开始算)max children reached - 大道进程最大数量限制的次数,如果这个数量不为0,那说明你的最大进程数量太小了,请改大一点。slow requests – 启用了php-fpm slow-log,缓慢请求的数量 配置监控跑到zabbix-agentd.conf里添加一个自定义监控项,如下: 1UserParameter=php-fpm.status[*],/usr/bin/curl -s "http://127.0.0.1/php-fpm_status?xml" | grep "<$1>" | awk -F'>|<' '{ print $$3}' 然后重启一下zabbix-agent,模板就是https://gitee.com/careyjike_173/zabbix/tree/master/template 里的zbx_php-fpm_templates.xml,直接导入即可! 效果如下图: 没有监控到进程?zabbix有时候会在监控进程出现不太准的情况,比如我这个机器的php。配置了proc.num[php-fpm,,,]这个key,但是在zabbix-server死活都取不到值,如图: 但是在被监控机器里,进程是明明存在的: 这特么的是为啥? 这就是因为proc.num[进程名,,,]里面的进程名是在/proc/进程PID/status里的name一栏获得的,比如我这个机器的php情况: name里写的是php-fpm56,所以key也要改成proc.num[php-fpm56,,,],这个时候在zabbix-server就成功取值了,如图: 注意!/proc/进程PID/status的name会被截断为15个字符。所以在配置时要事前检查一下。]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>zabbix</tag>
<tag>php-fpm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用zabbix去监控nginx]]></title>
<url>%2F2018%2F04%2F02%2F%E4%BD%BF%E7%94%A8zabbix%E5%8E%BB%E7%9B%91%E6%8E%A7nginx%2F</url>
<content type="text"><![CDATA[准备工作zabbix监控nginx,首先要确认nginx里是否有http_stub_status_module这个模块,一般来说,这个模块是自动安装的,nginx -V如下图: 如果你的nginx没有这个模块,请去看https://rorschachchan.github.io/2018/01/03/Nginx动态编译新的模块/ 。 然后在nginx.conf里添加一段话: 1234 location = /nginx-status { stub_status on; access_log off;} nginx -s reload一下,然后在命令行输入curl http://127.0.0.1/nginx-status,就会看到如下的界面: 这样就可以通过http_stub_status_module检查nginx情况了! nginx status详解以上图的nginx status来做例子说明一下各个数字的意思:active connections – 活跃的连接数量accepts — 总共处理了3832000个连接handled — 成功创建3832000次握手requests — 总共处理了3295877个请求reading — 读取客户端的连接数writing — 响应数据到客户端的数量waiting — 开启keep-alive的情况下,这个值等于active – (reading+writing), 意思就是 Nginx 已经处理完正在等候下一次请求指令的驻留连接 配置监控有了模块,还需要添加一个脚本,然后就可以获取上面的数值了,脚本如下: 1234567891011121314151617181920212223242526272829303132#!/bin/bash# Method of useHOST="127.0.0.1"PORT="80" #这个根据实际情况填写URL="http://${HOST}:${PORT}/nginx-status"active() { curl "${URL}" 2>/dev/null | grep "Active" | awk '{print $NF}'}reading() { curl "${URL}" 2>/dev/null | grep "Reading" | awk '{print $2}'}writing() { curl "${URL}" 2>/dev/null | grep "Writing" | awk '{print $4}'}waiting() { curl "${URL}" 2>/dev/null | grep "Waiting" | awk '{print $NF}'}accepts() { curl "${URL}" 2>/dev/null | awk NR==3 | awk '{print $1}'}handled() { curl "${URL}" 2>/dev/null | awk NR==3 | awk '{print $2}'}requests() { curl "${URL}" 2>/dev/null | awk NR==3 | awk '{print $NF}'}ping() { ps -ef | grep nginx | grep -v grep -c}$1 然后再去zabbix_agentd.conf里添加一句话: 1UserParameter=nginx.status[*],/usr/local/zabbix/script/nginx_status.sh $1 然后service zabbix-agent restart,自定义项就搞定了。 如果要导入模板,https://gitee.com/careyjike_173/zabbix 这个朋友的模板已经非常全面了,根据实际情况修改之后再导入他的xml就好,感谢前人付出!]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>zabbix</tag>
<tag>nginx</tag>
</tags>
</entry>
<entry>
<title><![CDATA[金山云api调用的几个例子]]></title>
<url>%2F2018%2F03%2F29%2F%E9%87%91%E5%B1%B1%E4%BA%91api%E8%B0%83%E7%94%A8%E7%9A%84%E4%B8%A4%E4%B8%AA%E4%BE%8B%E5%AD%90%2F</url>
<content type="text"><![CDATA[今天另外一个运维要看一下金山云API返回的格式,于是就临时写了两个demo,也顺便记录下来,说不定以后开发脚本的时候可能用的着。 金山云跟阿里云的sdk不一样,阿里云有一个总的sdk,然后不同的服务还需要去分别下载对应具体的sdk;而金山的不是,他绝大多数的服务都是用那个总sdk。 查询数据库的脚本需要先获取https://github.com/kscdb/krds_openapi_sdk.git,然后执行python setup.py install安装所用的金山库。 这个脚本是查询某个数据库的具体情况: 脚本如下: 1234567891011121314#!/usr/bin/env python# -*- encoding:utf-8 -*-from kscore.session import get_sessionfrom krds_client import *#密钥ACCESS_KEY_ID = "这里填写ak"SECRET_ACCESS_KEY = "这里填写sk"#连接s = get_session()krds_client = KRDSClient(ACCESS_KEY_ID, SECRET_ACCESS_KEY, '地域名')r = krds_client.DescribeDBInstances(DBInstanceIdentifier='5c664b16-fbfe-4373-8a00-67c9476e7386',DBInstanceType='HA') #DBInstanceIdentifier后面是实例IDprint r 执行脚本之后,可以看到返回的结果包括数据库里很多的资料,如图: 如果不加参数的话,就是返回账号内所有的数据库情况。 查询服务器的脚本需要先获取https://github.com/KscSDK/ksc-sdk-python.git,然后执行python setup.py install安装所用的金山库。 这个脚本是查询下面这个服务器的情况: 脚本如下: 123456789101112#!/usr/bin/env python# -*- encoding:utf-8 -*-from kscore.session import get_session#密钥ACCESS_KEY_ID = "这里填写ak"SECRET_ACCESS_KEY = "这里填写sk"#连接s = get_session()client = s.create_client("kec", "地域名", use_ssl=True,ks_access_key_id=ACCESS_KEY_ID, ks_secret_access_key=SECRET_ACCESS_KEY)print client.describe_instances(Search=['js-online-hlsproxy-20']) #Search后面接实例名 执行脚本之后,可以看到返回的结果包括数据库里很多的资料,如图: 如果不加参数的话,就是返回账号内所有的服务器情况。 弹性IP相关的脚本脚本如下: 12345678910111213141516171819202122#!/usr/bin/env python#coding=utf-8#这个脚本是用来修改金山云的eip带宽import json,pprintfrom kscore.session import get_session# 密钥ACCESS_KEY_ID = "这里是ak"SECRET_ACCESS_KEY = "这里是sk"s = get_session()region='cn-shanghai-2'eipClient = s.create_client("eip",region, use_ssl=False,ks_access_key_id=ACCESS_KEY_ID,ks_secret_access_key=SECRET_ACCESS_KEY)#allEips=eipClient.get_lines() #这是获取LineID#allEips=eipClient.allocate_address(LineId:"a2403858-2550-4612-850c-ea840fa343f9",BandWidth:5,ChargeType:"PostPaidByDay") #这是创建eip#print allEips#allEips=eipClient.describe_addresses(MaxResults=7) #这是查询eip,一次输出7次for line in open("/具体路径/金山云eip名单.txt"): line = line.strip('\n') #去掉回车 eipClient.modify_address(**{'AllocationId':line,'BandWidth':1}) #将文件里的所有的eip带宽改成1M print ("带宽已经调整完毕!") 总体来说金山云的sdk文档还是比较挫。 参考资料https://github.com/KscSDK/ksc-sdk-pythonhttps://github.com/kscdb/krds_openapi_sdk]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python</tag>
<tag>金山云</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用pandas来做html表格]]></title>
<url>%2F2018%2F03%2F27%2F%E4%BD%BF%E7%94%A8pandas%E6%9D%A5%E5%81%9Ahtml%E8%A1%A8%E6%A0%BC%2F</url>
<content type="text"><![CDATA[前言最近电子商城慢sql问题引了小BOSS的重视,于是就打算给开发们搞一个表格,在表格里可以看到前一天阿里云数据库的慢sql。这一次我不打算用html邮件了,因为慢sql数量不固定,今天可能三个,明天可能五个,后天抽风可能就一百个。而html邮件的格式是要事先写死的,于是我就用pandas来做这个表格,直接生成一个html文件,通过访问浏览器去让开发看慢sql。 慢日志脚本我要承认,阿里云自带的api在线调试工具真是一个好东西,有了它,脚本demo可以直接生成,地址是:https://api.aliyun.com/?spm=a2c4g.750001.952925.6.1QrDYe ,于是乎,阿里云获取慢日志脚本test.py如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546#!/usr/bin/env python#coding=utf-8import json from aliyunsdkcore import clientfrom aliyunsdkrds.request.v20140815 import DescribeSlowLogRecordsRequest clt = client.AcsClient('这里是ak','这里是sk','这里是地域')# 设置参数request = DescribeSlowLogRecordsRequest.DescribeSlowLogRecordsRequest()request.set_accept_format('json')request.add_query_param('DBInstanceId', 'RDS的ID号')request.add_query_param('StartTime', '2018-03-26T08:00Z') #3月26日早上8点开始request.add_query_param('EndTime', '2018-03-27T08:00Z') #3月27日早上8点结束request.add_query_param('DBName', '对应的数据库名')request.add_query_param('PageSize', 100) #这个值只能是30/50/100# 发起请求response = clt.do_action_with_exception(request)print response#把json格式的返回值改成dict格式slow_log=json.loads(response)num = slow_log['TotalRecordCount']Hostaddress = []LockTimes = []ParseRowCounts = []QueryTimes = []SQLText = []#将有用的值做成listif num < 100: for i in range(0,num): Hostaddress.append(slow_log['Items']['SQLSlowRecord'][i]['HostAddress']) LockTimes.append(slow_log['Items']['SQLSlowRecord'][i]['LockTimes']) ParseRowCounts.append(slow_log['Items']['SQLSlowRecord'][i]['ParseRowCounts']) QueryTimes.append(slow_log['Items']['SQLSlowRecord'][i]['QueryTimes']) SQLText.append(slow_log['Items']['SQLSlowRecord'][i]['SQLText'])else: for i in range(0,100): Hostaddress.append(slow_log['Items']['SQLSlowRecord'][i]['HostAddress']) LockTimes.append(slow_log['Items']['SQLSlowRecord'][i]['LockTimes']) ParseRowCounts.append(slow_log['Items']['SQLSlowRecord'][i]['ParseRowCounts']) QueryTimes.append(slow_log['Items']['SQLSlowRecord'][i]['QueryTimes']) SQLText.append(slow_log['Items']['SQLSlowRecord'][i]['SQLText']) 这个response的格式是一个json,在www.json.cn里查看是这个样子: 可以看到返回值里面TotalRecordCount就是总返回值,如果这个值大于PageSize,那么就会有第二篇,需要手动翻篇。所以我这里直接最大值就是100,一篇100已经够开发看了… 脚本如下在上面的脚本里可以获取到所有慢sql的json格式,那么就可以再写一个脚本把json转化成html格式并且生成一个html文件,然后在nginx里直接把这个文件展示出来。既然用到了pandas库,那么就要先安装pandas,方法如下: 1234pip install --upgrade pippip install pandas如果有“Please upgrade numpy to >= 1.9.0 to use this pandas version”的反应,那么执行下一句pip install -U numpy 生成html的整个脚本如下: 123456789101112131415161718192021222324252627#!/usr/bin/env python#coding=utf-8from test import Hostaddress,LockTimes,ParseRowCounts,QueryTimes,SQLText #从刚写的test.py里得到那些list变量import pandas as pddef convertToHtml(result,title): #将数据转换为html的table #result是list[list1,list2]这样的结构 #title是list结构;和result一一对应。titleList[0]对应resultList[0]这样的一条数据对应html表格中的一列 d = {} index = 0 for t in title: d[t]=result[index] index = index+1 pd.set_option('max_colwidth',200) #默认的行长度是50,这里我调成了200 df = pd.DataFrame(d) df = df[title] h = df.to_html(index=False) return hif __name__ == '__main__': result = [Hostaddress,LockTimes,ParseRowCounts,QueryTimes,SQLText] title = [u'HostAddress',u'LockTimes',u'ParseRowCounts',u'QueryTimes',u'SQLText'] #生成一个叫biaoge.html with open('/nginxhtml路径/biaoge.html', 'w') as f: f.write(convertToHtml(result,title)) print "html文件已经生成!" 执行效果将这个biaoge.html直接生成到nginx的html文件夹里,在浏览器里打开这个html就看到效果了,如图:]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python</tag>
<tag>pandas</tag>
<tag>大数据分析</tag>
</tags>
</entry>
<entry>
<title><![CDATA[记录一次nginx出现了502的问题]]></title>
<url>%2F2018%2F03%2F26%2F%E8%AE%B0%E5%BD%95%E4%B8%80%E6%AC%A1nginx%E5%87%BA%E7%8E%B0%E4%BA%86502%E7%9A%84%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[背景交待市场运营在手机APP端推送了一个“家装节,部分商品优惠打折”消息,用户可以通过点击这个消息,在APP进入到商城界面,如果是已经登录的用户将通过免登陆直接跳转,如果是没有登录的用户会登陆到登陆界面。但是刚推送就发现,通过这个推送点击,没有正常登陆到商城界面,而是返回了502。 nginx 502的错误,一般来说就是php-fpm的问题,我登陆到电商服务器发现,php-fpm运行正常而且php-fpm的进程数也很正常。但是查看到mysql,发现mysql的CPU飙升,如图: 于是登陆到数据库里,使用show processlist一看,数据库里有大量的语句处于sending data状态,而且执行时间令人发指(command项处于Sleep状态的进程表示其正在等待接受查询,因此它并没有消耗任何资源,是无害的): 先赶快通知运营先把推送的消息界面停用掉,不要让更多的用户登陆失败。然后写了一个脚本批量的kill掉这些进程,看看能不能让数据库恢复正常,过程如下。 首先先得到show processlist展现的所有的情况: 1mysql -uroot -p密码 -h数据库地址 -e "show processlist" | grep -i 'Locked' > locked_log.txt 然后获得前面的进程号,并且加上kill的指令: 12345#/bin/bashfor line in `cat locked_log.txt | awk '{print $1}'`do echo "kill $line;" >> kill_thread_id.sqldone 在登陆到数据库,然后执行上面生成的kill_thread_id.sql:: 1mysql>source kill_thread_id.sql 但是发现,kill掉一批之后,又有了新的慢sql出现,CPU依旧高居不下,于是只能跟产品经理说明情况,在征得了产品经理无奈的同意之后,重启了数据库,幸好时间没有很长,就耽误二三分钟而已。重启了之后,CPU就降下去了。赶快叫开发童鞋在线补充一个索引给用户登录的表来解决这个慢sql问题,没有了慢sql就没有了502。 补充nginx499nginx如果爆错499的话,代表客户端主动关闭连接,原因就是后端脚本执行的时间太长了or数据库有慢mysql,调用方超出了timeout的时间,关闭了连接。 这个时候需要更改一下nginx.conf: 12proxy_read_timeout 10s;proxy_send_timeout 10s; 把上面两个值适度调大然后重启nginx即可。或者就是proxy_ignore_client_abort on;,这话就是让代理服务端不要主动关闭客户端的连接。 参考资料https://blog.csdn.net/zhuxineli/article/details/14455029https://segmentfault.com/a/1190000012326158]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>nginx</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用python调用redis的基本操作]]></title>
<url>%2F2018%2F03%2F26%2F%E4%BD%BF%E7%94%A8python%E8%B0%83%E7%94%A8redis%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C%2F</url>
<content type="text"><![CDATA[前言最近有一个需求,里面涉及到把python获取到的数值存储到redis里,于是就简单研究一下python调用redis的方法。 python要调用redis的时候,需要先安装redis模块,有两个方法。第一个方法就是pip install redis,第二个方法就是easy_install redis,模块装完之后,就可以创建redis连接了。 redis-py提供两个类Redis和StrictRedis来实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令(比如,SET命令对应与StrictRedis.set方法)。 Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。 官方推荐使用StrictRedis方法,所以我这里只说StrictRedis。 如何连接连接的代码如下: 123456789101112>>> import redis#这里是redis的基本情况>>> host = '这里填写redis的host地址'>>> port = 6379 #根据实际情况更改端口>>> password = 'redis对应的密码'#使用StrictRedis去连接到目标redis>>> r = redis.StrictRedis(host=host, port=6379, password=password, db=0) #db为选定的数据库,db=0代表选择了0号数据库。redis默认有16个数据库,在conf里面可以配置。如果没有指定的数据库,可以不写。>>> r.set('age', '88')>>> r.get('age')'88' 关系型数据库都有一个连接池的概念:对于大量redis连接来说,如果使用直接连接redis的方式的话,将会造成大量的TCP的重复连接,所以,就引入连接池来解决这个问题。在使用连接池连接上redis之后,可以从该连接池里面生成连接,调用完成之后,该链接将会返还给连接池,供其他连接请求调用,这样将减少大量redis连接的执行时间,那么使用StrictRedis的连接池的实现方式如下: 12pool = redis.ConnectionPool(host=host, port=6379, password=password)r = redis.StrictRedis(connection_pool=pool 或者使用pipeline(管道),通过缓冲多条命令,然后一次性执行的方法减少服务器-客户端之间TCP数据库包,从而提高效率,方法如下: 1234567891011接上文pipe = r.pipeline()#插入数据>>> pipe.hset("hash_key","leizhu900516",8)Pipeline<ConnectionPool<Connection<host=192.168.8.176,port=6379,db=0>>>>>> pipe.hset("hash_key","chenhuachao",9)Pipeline<ConnectionPool<Connection<host=192.168.8.176,port=6379,db=0>>>>>> pipe.hset("hash_key","wanger",10)Pipeline<ConnectionPool<Connection<host=192.168.8.176,port=6379,db=0>>>>>> pipe.execute()[1L, 1L, 1L] 批量读取数据的方法如下: 123456789>>> pipe.hget("hash_key","leizhu900516")Pipeline<ConnectionPool<Connection<host=192.168.8.176,port=6379,db=0>>>>>> pipe.hget("hash_key","chenhuachao")Pipeline<ConnectionPool<Connection<host=192.168.8.176,port=6379,db=0>>>>>> pipe.hget("hash_key","wanger")Pipeline<ConnectionPool<Connection<host=192.168.8.176,port=6379,db=0>>>>>> result = pipe.execute()>>> print result['8', '9', '10'] #有序的列表 pipeline的命令可以写在一起,如p.set('hello','redis').sadd('faz','baz').incr('num').execute(),其实它的意思等同于是: 12345>>> p.set('hello','redis')>>> p.sadd('faz','baz')>>> p.incr('num')>>> p.execute()[True, 1, 1] 利用pipeline取值3500条数据,大约需要900ms,如果配合线程or协程来使用,每秒返回1W数据是没有问题的,基本能满足大部分业务。 如何存储上面已经举了一个age:88的例子,可见创建一个string类型的key并放入value是使用set方法,比如再多存几个名字: 123456789101112131415161718192021222324>>> r.set('name', 'lilei')True>>> r.get('name')'lilei'>>> r.set('name2', 'zhaowei')True>>> r.set('name3', 'james')True>>> r.set('name4', 'yaoming')True#列出以name开头的所有key>>> print r.keys("name*")['name3', 'name4', 'name2', 'name']#列出所有key>>> print r.keys()>>> r.dbsize() #当前数据库包含多少条数据 4L>>> r.delete('name')1>>> r.save() #执行“检查点”操作,将数据写回磁盘。保存时阻塞True>>> r.get('name')>>> r.flushdb() #清空r中的所有数据True 还有其他类型的存储方法,简单举例子如下: 1234567891011121314151617#创建一个hashr.hset('abc:def', 'name', "abcde")#获取一个hash的所有值print r.hgetall('abc:def')#获取一个hash的所有key print r.hkeys('abc:def') #创建listr.sadd('abcd:ef','nihao')r.sadd('abcd:ef','hello')r.sadd('xxxx','nihao')r.sadd('xxxx','good')#打印出该key中的值 listprint r.smembers('abcd:ef')#查询两个list中相同的值print r.sinter('abcd:ef', 'xxxx')#给两个list取并集print r.sunion('abcd:ef', 'xxxx') setnx是SET if Not eXists的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。python要使用它也是r.setnx(key,value),当发现没有这个key的时候,就会插入这个新的key以及对应的value,如果发现有了个这个key了,那这条就等于没加。 如何删除py-redis中有个delete接口,既可以删除单个key,也可以全删除key,如果要删除几个key,用法是:r.delete('age')、r.delete('sex', 'age'),如果要全删除,那就是 12keys = r.keys()r.delete(*keys) 执行之后的效果等于flushall。 redis里默认情况下是不支持通配符的,那么要批量删除key怎么做呢?答案就是搭配xargs,比如要删除掉所有2018-03-开头的key: 1redis-cli -hredis地址 -a密码 keys "2018-03-*"|xargs redis-cli -hredis地址 -a密码 del python将两个list元素一一对应转换为dict使用python的zip函数和强大的集合操作可以方便的将两个list元素一一对应转换为dict,如下示例代码: 1234names = ['n1','n2','n3']values = [1,2,3] nvs = zip(names,values)nvDict = dict( (name,value) for name,value in nvs) 参考资料https://github.com/andymccurdy/redis-pyhttp://xiaorui.cc/2014/11/10/%E4%BD%BF%E7%94%A8redis-py%E7%9A%84%E4%B8%A4%E4%B8%AA%E7%B1%BBredis%E5%92%8Cstrictredis%E6%97%B6%E9%81%87%E5%88%B0%E7%9A%84%E5%9D%91/http://debugo.com/python-redis/]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python</tag>
<tag>redis</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用python调用echart画图]]></title>
<url>%2F2018%2F03%2F22%2F%E4%BD%BF%E7%94%A8python%E8%B0%83%E7%94%A8echart%E7%94%BB%E5%9B%BE%2F</url>
<content type="text"><![CDATA[前言之前说了如何使用阿里云的SDK获取云存储的值然后发送表格邮件,但是最近领导又发话了,说这个邮件每天一封看的有点审美疲劳,要顺应“数据可视化”的趋势,于是就要求画图,力求直观,要做到“从众多数据中突出特别数据,从特别数据中突出高价值数据”。我之前用python的matplotlib画过,这一次尝试用echart来做图! echart是不太良心的百度良心的开源作品,提供各种各样精美的作图方案,分分钟把图片做的高大上,吸引周围人的目光。不过我对前端的了解非常浅薄,但是没关系。这次使用pyechart插件!这个插件可以让python直接调用echart接口,选择需要的图形之后,直接往里查数据就好,简单粗暴见效快,而且支持3D,可以说是居家旅行常备物品。可以说,有了它,作图能力顶呱呱。感谢开发者大神们的辛苦工作! 作图首先先需要安装pyecharts插件,命令是pip install pyecharts。 然后我们就可以写一个简单的案例,如下: 123456789101112131415#!/usr/bin/env python#coding=utf-8from pyecharts import Bar #导入第三方库#attr = ["{}day".format(i) for i in range(1, 8)] #这样的话X坐标就是1day、2day、3day...attr = ["Mon", "Feb", "Wed", "Thu", "Fri", "Sat", "Sun"] #这样X坐标就是星期v1 = [1.49, 2.09, 4.03, 2.23, 5.26, 7.71, 7.56] v2 = [0.3, 0.9, 0.2, 0.4, 0.7, 0.7, 0.6]v3 = [18.15, 13.22, 11.28, 17.99, 18.7, 19.7, 15.6]bar = Bar("乐橙云存储情况总览", "本图表展示过去一周的云存储情况") #这里是主标题和副标题bar.add("录像分享文件", attr, v1, mark_line=["average"], mark_point=["max", "min"]) #每一个值的名称以及要展现平均值和最大最小值bar.add("视频直播文件", attr, v2, mark_line=["average"], mark_point=["max", "min"])bar.add("云录像、报警图片、全景图片", attr, v3, mark_line=["average"], mark_point=["max", "min"]) bar.render('/tmp/111.html') #在/tmp文件夹里生成一个111.html文件 如果服务器里有nginx,那么把这个html文件放到nginx/html路径里,再在浏览器里打开就会看到这样的图: 而且还可以通过点击网页上“A值”、“B值”、“C值”就可以达到屏蔽相应值的效果,而且如果点击红色箭头的“数据视图”,还可以直接看到对应的数据,非常贴心非常屌,如图: 如果你觉得图片有点小,那么可以修改这个地方:bar = Bar("XXX情况总览", "本图表展示过去一周的ABC情况",width=1000,height=900),我这里把宽和高分别从默认值调成了1000和900。 如果想要在一个html里做多个图,比如要做三个柱状图,那么example如下: 123456789101112131415161718192021222324252627282930#!/usr/bin/env python#coding=utf-8from pyecharts import Bar, Gridattr = ["一班", "二班", "三班", "四班"]v1 = [54, 81, 32, 32] v2 = [68, 69, 27, 32] bar = Bar("赞成票","本图表展示赞成票情况")bar.add("年纪长", attr, v1, mark_point=["max", "min"])bar.add("副年纪长", attr, v2, mark_point=["max", "min"])attr2 = ["一班", "二班", "三班", "四班"]x1 = [2, 0, 0, 1]x2 = [1, 3, 0, 2]bar2 = Bar("反对票","本图表展示反对票情况",title_top='bottom',title_color='#1d12eb') #title_color是标题颜色,这个跟html的颜色取值一样bar2.add("年纪长", attr2, x1, mark_point=["max", "min"])bar2.add("副年纪长", attr2, x2, mark_point=["max", "min"])attr3 = ["一班", "二班", "三班", "四班"]y1 = [2, 0, 0, 1]y2 = [2, 0, 0, 1]bar3 = Bar("弃权票","本图表展示弃权票情况",title_pos='right',title_color='#eb1212') #title_pos是标题的位置,如果不特殊说明,会重叠bar3.add("年纪长", attr3, y1, mark_point=["max", "min"]) bar3.add("副年纪长", attr3, y1, mark_point=["max", "min"])grid = Grid() grid.add(bar, grid_width="40%", grid_height="30%", grid_bottom="60%", grid_right="55%") #grid_height和grid_width是每一个小图的大小grid.add(bar2, grid_width="40%", grid_height="30%", grid_bottom="60%", grid_left="55%") #grid_bottom和grid_top是垂直位置grid.add(bar3, grid_width="40%", grid_height="30%", grid_top="60%", grid_right="55%") #grid_right和grid_left是水平位置grid.render('/tmp/grid.html') #在/tmp文件夹里生成一个grid.html文件 例子中的数字都是我虚拟的,实际情况中,这些数字都应该是存储在redis这样的数据库里,然后取出来使用。 上面的两个例子仅仅是pyechart使用的冰山一角,如果想更多的了解,请去看一下文末pyechart的中文说明文档,无论是柱状图、雷达图、曲线图、3D图都有相关的使用讲解,内容特别丰富! 参考资料http://echarts.baidu.comhttp://pyecharts.org/#/zh-cn/prepare]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>python</tag>
<tag>echart</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RabbitMQ的安装、配置与启动]]></title>
<url>%2F2018%2F03%2F19%2FRabbitMQ%E7%9A%84%E5%AE%89%E8%A3%85%E3%80%81%E9%85%8D%E7%BD%AE%E4%B8%8E%E5%90%AF%E5%8A%A8%2F</url>
<content type="text"><![CDATA[前言环境介绍:Centos 7 + RabbitMQ:3.6.12 + Erlang:20.0 安装erlang由于RabbitMQ使用erlang语言编写的,所以要先安装erlang语言环境。但是yum源里的erlang版本太老了,于是这里选择手动安装,使用Erlang官方推荐的Erlang Solutions安装方法如下: 1234yum install gcc gcc-c++ glibc-devel make ncurses-devel openssl-devel autoconf java-1.8.0-openjdk-devel git wget wxBase.x86_64 #先把其他模块准备好wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpmrpm -Uvh erlang-solutions-1.0-1.noarch.rpmrpm --import https://packages.erlang-solutions.com/rpm/erlang_solutions.asc 此时,查看/etc/yum.repos.d/erlang_solutions.repo,应该是这个样子: 123456[erlang-solutions]name=CentOS $releasever - $basearch - Erlang Solutionsbaseurl=https://packages.erlang-solutions.com/rpm/centos/$releasever/$basearchgpgcheck=1gpgkey=https://packages.erlang-solutions.com/rpm/erlang_solutions.ascenabled=1 这个时候可以yum安装了: 1yum install -y esl-erlang 此时得到的erlang就是20.0版本的了,如图: 如果不想使用这个办法,可以使用源码安装的方式,https://packages.erlang-solutions.com/erlang/ 这里面有Erlang官方的下载包,拆包解压缩然后make && make install即可。 安装RabbitMQ安装RabbitMQ跟其他普通软件差不多,先去官网下载目前较稳定的rpm包,然后安装,步骤如下: 12wget https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.4/rabbitmq-server-3.7.4-1.el7.noarch.rpmyum install -y rabbitmq-server-3.7.4-1.el7.noarch.rpm 如果出现了Transaction Check Error的错误: 可见是要安装的包与已有的包相冲突,此时需要yum list|grep erlang,如图: 再yum remove esl-erlang.x86_64,然后重新执行yum install那一步即可。 如果出现Requires: socat的错误,如图: 此时需要执行如下命令即可: 12yum -y install epel-releaseyum -y install socat 配置RabbitMQRabbitMQ安装完毕,先chkconfig rabbitmq-server on设置开机启动。然后,配置一下用户名。我这个机器的用户名不规范,需要把hostname里的中文去掉,比如改成:3-dvl-hlsproxy-001,那么就要在/etc/hosts里添加一句: 内网IP地址 3-dvl-hlsproxy-001 然后执行rabbitmq-plugins enable rabbitmq_management来安装WEB图形界面,然后拷贝rabbitmq.config.example到/etc/rabbitmq/里,并且改名叫rabbitmq.config,命令如下: 123cp /usr/share/doc/rabbitmq-server-3.7.4/rabbitmq.config.example /etc/rabbitmq/cd /etc/rabbitmq/mv rabbitmq.config.example rabbitmq.config 编辑rabbitmq.config这个文件,把%%{loopback_users, []}.改成{loopback_users, []},保存之后,执行service rabbitmq-server restart来启动RabbitMQ。 如果启动之后,执行rabbitmqctl status不断的刷Error when reading /var/lib/rabbitmq/.erlang.cookie: eacces的错误的话,执行chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie。 在浏览器里登录外网IP:15672就会看到RabbitMQ的WEB配置界面了, 账号和密码都是guest,输入之后就会看到如下的界面,可以在界面里看到3-dvl-hlsproxy-001的情况了,如图: RabbitMQ 3.0以后版本的WEB端口是15672,服务的端口是5672,这俩都可以在配置文件里面更改。至此RabbitMQ的安装与配置结束了,但是这个仅仅是最简单的配置,RabbitMQ自身有一套很详细的用户管理规则以及它支持Python等很多语言的管理,这些内容以后再详细说明。 参考资料https://packages.erlang-solutions.com/erlang/https://laucyun.com/9849587ce75f31d534d52f906c94368f.htmlhttps://www.rabbitmq.com/access-control.html]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>消息队列</tag>
<tag>RabbitMQ</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用nginx开启http2协议]]></title>
<url>%2F2018%2F03%2F16%2F%E4%BD%BF%E7%94%A8nginx%E5%BC%80%E5%90%AFhttp2%E5%8D%8F%E8%AE%AE%2F</url>
<content type="text"><![CDATA[部署过程HTTP/2是建立在TLS的基础上的,那么先要查看nginx的版本和openssl的版本,如果nginx版本在1.10.0以上且需要openssl版本在1.0.2以上那么就可以进行下一步了: 如果版本并不符合要求,可以按照https://rorschachchan.github.io/2018/01/03/Nginx动态编译新的模块/ 里的方法升级对应的模块版本。 先编辑https(443端口)对应的conf文件: 123456789101112131415161718192021server { listen 443 ssl http2; #这里多加一句http2 server_name cuntao.lechange.com *.lechange.com; #这里填写实际的域名,我这里以cuntao.lechange.com为例 ssl_certificate /实际路径/server-com.crt; ssl_certificate_key /实际路径/server-com.key; ssl_session_timeout 30m; #客户端会话缓存时间 ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #允许的协议 ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; #加密算法(CloudFlare 推荐的加密套件组) ssl_prefer_server_ciphers on; #优化 SSL 加密套件 ssl_session_cache builtin:1000 shared:SSL:10m; #SSL会话缓存类型和大小 ssl_buffer_size 1400; #每个MTU大小1400b location / { root html; index index.html index.htm; } error_page 404 /404.html; } 保存之后再编辑http(80端口)对应的conf文件: 12345server { listen 80 default; add_header Strict-Transport-Security max-age=15768000; return 301 https://$host$request_uri;} 然后使用nginx -t检查一下是否文件有错误,如果是OK的话,那么就nginx -s reload平滑重启一下nginx即可。 验证HTTP/2协议是否开启很简单,有两个方法:1)登陆https://tools.keycdn.com/http2-test,将你的域名填写进去,查看一下配置成功: 2)在Chrome浏览器上可以通过安装HTTP/2 and SPDY indicator插件来检验,网址是https://chrome.google.com/webstore/detail/http2-and-spdy-indicator/mpbpobfflnpcgagjijhmgnchggcjblin ,如果地址栏出现蓝色的闪电就是该网站开启了HTTP/2协议,灰色的话就是HTTP/2协议没开启。 参考资料https://www.nginx.com/blog/nginx-1-9-5/https://blog.fazero.me/2017/01/06/upgrate-nginx-and-use-http2/https://iyaozhen.com/nginx-http2-conf.html]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>nginx</tag>
<tag>http协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[关于HTTP 2.0应该知道的事]]></title>
<url>%2F2018%2F03%2F16%2F%E5%85%B3%E4%BA%8EHTTP-2%E5%BA%94%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%BA%8B%2F</url>
<content type="text"><![CDATA[HTTP 2.0的优势相比HTTP/1.x,HTTP/2在底层传输做了很大的改动和优化:1.每个服务器只用一个连接:HTTP/2对每个服务器只使用一个连接,而不是每个文件一个连接。这样,就省掉了多次建立连接的时间,这个时间对TLS尤其明显,因为TLS连接费时间;2.加速TLS交付:HTTP/2只需一次耗时的TLS握手,并且通过一个连接上的多路利用实现最佳性能。HTTP/2还会压缩首部数据,省掉HTTP/1.x时代所需的一些优化工作,比如拼接文件,从而提高缓存利用率;3.简化Web应用:使用HTTP/2可以让Web开发者省很多事,因为不用再做那些针对HTTP/1.x的优化工作了;4.适合内容混杂的页面:HTTP/2特别适合混合了HTML、CSS、JavaScript、图片和有限多媒体的传统页面。浏览器可以优先安排那些重要的文件请求,让页面的关键部分先出现、快出现,而且根本不会发生“浏览器明明在等关键的CSS和JS,而服务器还在发送黄图”的尴尬局面;5.更安全:通过减少TLS的性能损失,可以让更多应用使用TLS,从而让用户信息更安全。 HTTP 2.0性能增强之二进制分帧HTTP的定义大家都知道,叫超文本协议,也就是说http1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多。但是在HTTP/2里这里做了比较重大的改动—二进制分帧,HTTP/2在应用层(HTTP)和传输层(TCP or UDP)之间增加一个二进制分帧层。在这个新增的二进制分帧层里HTTP/2会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码 ,其中HTTP1.x的首部信息会被封装到Headers帧,而我们的request body则封装到Data帧里面。二进制与之前的文本不同,二进制只认0和1的组合。基于这种考虑http2.0的协议解析决定采用二进制格式,实现方便且健壮。 HTTP/2的格式定义十分高效且精简。length定义了整个frame的大小,type定义frame的类型(一共10种),flags用bit位定义一些重要的参数,stream id用作流控制,payload就是request的正文,如下图: HTTP 2.0性能增强之首部压缩虽然HTTP/2引入了二进制分帧的概念,但是试想如果所有的二进制帧都会带上Headers帧,这是多大的数据冗余传送啊。于是HTTP/2针对这个需求又搞出来一个东东—“首部表”。 “首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型等等)只需发送一次。事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。如果首部发生变化了,那么只需要发送变化了数据在Headers帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新。 HTTP 2.0性能增强之TCP请求集中TCP的优势是很直白的:面向连接、提供可靠的数据传输服务、流量控制。那么有效地使用TCP连接的方法就是长时间连接传输大块数据。于是HTTP/2就尽大化的把这一特点发扬:所有HTTP/2通信都是在一个TCP连接上完成。前面说过,HTTP/2把HTTP协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息,并行地在同一个TCP连接上双向交换消息(注意这个“双向交换消息”)。举个例子,请求一个页面https://www.google.com,页面上所有的资源请求都是客户端与服务器上的一条TCP上请求和响应的! 这样“单链接多资源”的方式,使到至上而下的层面都得到了好处: 1.可以减少服务链接压力,内存占用少了,连接吞吐量大了; 2.由于TCP连接减少而使网络拥塞状况得以改观; 3.慢启动时间减少,拥塞和丢包恢复速度更快。 综上所述,“资源合并减少请求”对于HTTP/2是无用的优化手段。 上面的文字说了要注意“双向交换消息”,那么啥是“双向交换消息”? 就是把HTTP消息分解为独立的帧,交错发送,然后在另一端重新组装。专业一点说就是“一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的id将request再归属到各自不同的服务端请求里面”。这是HTTP/2重要的一项增强。事实上,这个机制会在整个Web技术栈中引发一系列连锁反应, 从而带来巨大的性能提升,因为: 1234可以并行交错地发送请求,请求之间互不影响;可以并行交错地发送响应,响应之间互不干扰;只使用一个连接即可并行发送多个请求和响应;消除不必要的延迟,从而减少页面加载的时间; Keep Alive与HTTP/2集中TCP的区别HTTP1.1的keep-alive是为了尽可能使用持久链接,以消除TCP握手和慢启动。但是keep-alive使用多了同样会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keep-alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。 举个例子:下载a.js创建一个TCP链接,就会需要TCP握手和慢启动而产生了约300ms下载延迟。当a.js下载完成后这时候b.js也要下载,如果a.js创建TCP链接是keep-alive的,b.js就可以复用其TCP而不需要重新TCP握手和慢启动(没有了那300ms)。 而HTTP/2是使用一个TCP链接的,其慢启动和握手只在第一次链接的时候产生一次,其后面链接都是持久化的。并且一个TCP下载多个资源,可以将TCP吞吐量最大化来提升性能,这方面可以参考一下TCP的拥塞预防及控制。 NGINX上如何配制HTTP/2上面说了这么多HTTP/2这个好那个好,是未来的趋势blablabla,但是要实现HTTP/2,还是需要“客户端和服务器都开启了HTTP/2”这一个首要条件。不过现在客户端(浏览器)大多数都已经支持HTTP/2,那么主要就是在服务器端如何开启HTTP/2,nginx的配置方法请见:https://rorschachchan.github.io/2018/03/16/使用nginx开启http2协议/ 。 按照这样的操作下来,服务器就开了HTTP/2协议,那些支持HTTP/2的浏览器在请求页面的时候就会走HTTP/2模式,而不支持HTTP/2的浏览器会议就按照HTTP/1.X的方式发送请求,如图: 支持HTTP/2的Web Server基本都支持HTTP/1.1。这样,即使浏览器不支持HTTP/2,双方也可以协商出可用的HTTP版本,没有兼容性问题。 参考资料http://www.alloyteam.com/2015/03/http2-0-di-qi-miao-ri-chang/comment-page-1/#commentshttps://segmentfault.com/a/1190000007637735https://github.com/creeperyang/blog/issues/23https://www.nginx.com/blog/nginx-1-9-5/https://ye11ow.gitbooks.io/http2-explained/content/part6.html]]></content>
<categories>
<category>大牛之路</category>
</categories>
<tags>
<tag>http协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[centos 7里安装zsh来提升shell的高逼格]]></title>
<url>%2F2018%2F03%2F15%2Fcentos-7%E9%87%8C%E5%AE%89%E8%A3%85zsh%E6%9D%A5%E6%8F%90%E5%8D%87shell%E7%9A%84%E9%AB%98%E9%80%BC%E6%A0%BC%2F</url>
<content type="text"><![CDATA[zsh本体的安装先用chsh -l查看当前的bash情况,如下: 123456789 [root@zabbix ~]# chsh -l/bin/sh/bin/bash/sbin/nologin/bin/dash/bin/tcsh/bin/csh/usr/bin/tmux[root@zabbix ~]# 如果是centos的话,使用yum install -y zsh来安装zsh,装完了zsh然后就是装oh my zsh,使用wget方法安装: 1wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh 再使用which zsh查看安装的zsh在/usr/bin/zsh,这个时候使用chsh -s /usr/bin/zsh,出现了Shell changed.这样就切换到了zsh界面,需要logout退出连接重进。 重新连接就会发现bash界面就变了,原本是路径的地方变成了一个小图标。界面主题是可以变化的,比如我个人比较喜欢af-magic这个模板,于是乎就把/root/.zshrc里的ZSH_THEME="robbyrussell"改成ZSH_THEME="af-magic",保存文件,再一次退出连接重新进入就能看见模板变化了。 如果在使用vim的时候发现了tab键的补全爆错_arguments:451: _vim_files: function definition file not found,如下图: 这个时候需要把/root/.zcompdump改一个名字,比如叫.zcompdump-bak,然后重新ssh连接即可。 autojump插件安装autojump这个插件安装之后,zsh会自动记录你访问过的目录,通过j + 目录名可以直接进行目录跳转,而且目录名支持模糊匹配和自动补全,例如你访问过hadoop-1.0.0目录,输入j hado即可正确跳转。j –s可以看你的历史路径库,安装方法如下: 1git clone git://github.com/joelthelion/autojump.git 然后在autojump目录里执行./install.sh,此时屏幕会出现如下的显示: 把上面那个[[ -s /root/.autojump/etc/profile.d/autojump.sh ]] && source /root/.autojump/etc/profile.d/autojump.sh autoload -U compinit && compinit -u复制到/root/.zshrc的文件里,最好复制在source $ZSH/oh-my-zsh.sh这句话上面,保存之后source ~/.zshrc即可。 zsh-syntax-highlighting插件安装这个插件安装之后主要效果就是命令高亮,如果是错误的命令,颜色是红色,正确的命令是绿色的,安装方法如下: 12345cd .oh-my-zsh/pluginsyum install -y git #如果已经安装了git就不用执行的git clone git://github.com/zsh-users/zsh-syntax-highlighting.gitsource /root/.oh-my-zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh添加到 .zshrc 的最后面source ~/.zshrc 效果立竿见影。 尾声至此,你现在的zsh应该具备如下几个特性:1、各种补全:路径补全、命令补全,命令参数补全,插件内容补全等等。触发补全只需要按一下或两下tab键,补全项可以使用ctrl+n/p/f/b上下左右切换。比如你想杀掉java的进程,只需要输入kill java + tab键,如果只有一个java进程,zsh会自动替换为进程的pid,如果有多个则会出现选择项供你选择。ssh + 空格 + 两个tab键,zsh会列出所有访问过的主机和用户名进行补全;2、即使你没有安装autojump,只要输入d,就会列出你在这个回话中访问的目录,输入前面的序号,就可以直接跳转;3、可以忽略cd命令, 输入..或者…和当前目录名都可以跳转;当然,除了上面几点,zsh还有很多丰富的插件可以使用,这就需要继续的探索了… 参考资料https://github.com/robbyrussell/oh-my-zshhttp://macshuo.com/?p=676]]></content>
<categories>
<category>工作与技术</category>
</categories>
<tags>
<tag>运维技术</tag>
<tag>shell</tag>
</tags>
</entry>
<entry>
<title><![CDATA[安装vim8.0的过程]]></title>
<url>%2F2018%2F03%2F14%2F%E5%AE%89%E8%A3%85vim8-0%E7%9A%84%E8%BF%87%E7%A8%8B%2F</url>
<content type="text"><![CDATA[1.先卸载老的vim 1yum remove vim-* -y 2.下载第三方yum源 1wget -P /etc/yum.repos.d/ https://copr.fedorainfracloud.org/coprs/mcepl/vim8/repo/epel-7/mcepl-vim8-epel-7.repo 3.安装vim 1yum -y install vim-enhanced 4.验证vim版本 12345rpm -qa |grep vimvim-enhanced-8.0.0704-1.1.26.el7.centos.x86_64vim-common-8.0.0704-1.1.26.el7.centos.x86_64vim-minimal-8.0.0704-1.1.26.el7.centos.x86_64vim-filesystem-8.0.0704-1.1.26.el7.centos.x86_64]]></content>
<categories>
<category>工作与技术</category>