-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1036 lines (952 loc) · 113 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>DigitalOcean, Node.js, Nginx, PM2整合部署方案</title>
<url>/post/digitalocean-node-js-nginx-pm2%E6%95%B4%E5%90%88%E9%83%A8%E7%BD%B2%E6%96%B9%E6%A1%88.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>通过阅读本教程,你将会学习如何将 Node.js 应用部署到云端服务器 DigitalOcean 中,并整合 SSH 登录, Nginx 作为反向代理,PM2 进程管理器, 防火墙端口配置的相关知识。</p>
<h2 id="一-前期准备"><a href="#一-前期准备" class="headerlink" title="(一) 前期准备"></a>(一) 前期准备</h2><hr>
<p>(1) 购买自定义域名。尚未拥有域名,可前往 <a href="https://sg.godaddy.com/zh">GoDaddy</a> 或者 其他域名提供商注册。<br>(2) 创建 SSH 连接的公私钥。尚未拥有公钥,前往 <a href="/post/%E4%BD%BF%E7%94%A8ssh-keygen%E5%88%9B%E5%BB%BAssh%E5%85%AC%E9%92%A5%E5%88%9B%E5%BB%BA%E6%95%99%E7%A8%8B.html" title="SSH公钥创建">SSH公钥创建</a> 教程。<br>(3) 创建一台云服务器(VPS)。本教程使用 DigitalOcean 创建。尚未拥有 VPS, 前往 <a href="/post/%E5%88%9B%E5%BB%BAdigitalocean%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%9E%E4%BE%8Bdroplet.html" title="DigitalOcean服务器创建">DigitalOcean服务器创建</a> 教程。</p>
<h2 id="二-教程用例"><a href="#二-教程用例" class="headerlink" title="(二) 教程用例"></a>(二) 教程用例</h2><hr>
<p>注册的网站域名<br>demo.luqifu.tech</p>
<p>注册的云服务器<br>161.35.59.204 - Ubuntu18.04.3</p>
<h2 id="三-主要步骤"><a href="#三-主要步骤" class="headerlink" title="(三) 主要步骤"></a>(三) 主要步骤</h2><hr>
<p>(1) SSH 远程登录 DigitalOcean 云服务器<br>(2) 创建一个 Node.js 应用<br>(3) 安装并使用 Node 应用管理器 PM2<br>(4) DNS 域名和服务器 IP 绑定<br>(5) Nginx 下载,进行反向代理配置</p>
<h3 id="(1)SSH-远程登录-DigitalOcean-云服务器"><a href="#(1)SSH-远程登录-DigitalOcean-云服务器" class="headerlink" title="(1)SSH 远程登录 DigitalOcean 云服务器"></a>(1)SSH 远程登录 DigitalOcean 云服务器</h3><p>本地控制台输入</p>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line">ssh root@xxx.xxx.xxx.xxx <span class="comment"># 这里,我输入 ssh root@161.35.59.204</span></span><br></pre></td></tr></tbody></table></figure>
<p>登陆方式有两种,密码登录 和 SSH keys 登录,我们更推荐后者,后者更方便和安全。<br>如何创建属于您的 SSH 公钥对,请参考教程 <a href="/post/%E4%BD%BF%E7%94%A8ssh-keygen%E5%88%9B%E5%BB%BAssh%E5%85%AC%E9%92%A5%E5%88%9B%E5%BB%BA%E6%95%99%E7%A8%8B.html" title="SSH公钥创建">SSH公钥创建</a>。</p>
<p>成功登录后,界面如下:</p>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line">ssh root@161.35.59.204</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">Last login: Wed Aug 12 01:57:51 2020</span><br><span class="line">root@my-node-server:~<span class="comment">#</span></span><br></pre></td></tr></tbody></table></figure>
<h3 id="(2)创建一个-Node-js-应用"><a href="#(2)创建一个-Node-js-应用" class="headerlink" title="(2)创建一个 Node.js 应用"></a>(2)创建一个 Node.js 应用</h3><figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># 成功登录您的服务器后,请执行:</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 注意:本教程使用 Ubuntu 的 apt 安装工具包,请选择适合您 OS 的包安装工具:</span></span><br><span class="line"></span><br><span class="line">sudo apt-get update <span class="comment"># 更新服务器的软件库</span></span><br><span class="line">sudo apt-get upgrade <span class="comment"># 更新服务器的软件包</span></span><br><span class="line"></span><br><span class="line">sudo apt-get install nodejs <span class="comment"># 安装 node 包</span></span><br><span class="line">sudo apt-get install npm <span class="comment"># 安装 npm 包</span></span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># 检查上述包是否安装成功</span></span><br><span class="line"></span><br><span class="line">node -v</span><br><span class="line"></span><br><span class="line"><span class="comment"># v8.10.0 版本号</span></span><br><span class="line"></span><br><span class="line">npm -v</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3.5.2 版本号</span></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~ <span class="comment"># 进入当前用户目录</span></span><br><span class="line">mkdir HelloWorld <span class="comment"># 创建名为 HelloWorld 的文件夹</span></span><br><span class="line"><span class="built_in">cd</span> HelloWorld <span class="comment"># 进入 HelloWorld 文件夹目录下</span></span><br><span class="line">npm init <span class="comment"># 初始化 npm 库</span></span><br><span class="line">npm install express --save <span class="comment"># 安装 express 依赖</span></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>创建 app.js 文件,实现一个基于 Express 简单的 http 服务器,端口为 3000。</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line"><span class="keyword">const</span> port = <span class="number">3000</span></span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/'</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line">res.send(<span class="string">'Hello World!'</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">app.listen(port, <span class="function">() =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">`Example app listening at http://localhost:<span class="subst">${port}</span>`</span>)</span><br><span class="line">})</span><br></pre></td></tr></tbody></table></figure>
<p>运行 app.js</p>
<pre><code>node app.js # 运行 app.js</code></pre>
<p>恭喜!您可以通过浏览器访问<code>http://< 您的服务器 IP >:3000</code>,获取 HelloWorld 应用页面 !</p>
<h3 id="(3)安装-Node-应用管理器-PM2,并且后台运行-Express-js"><a href="#(3)安装-Node-应用管理器-PM2,并且后台运行-Express-js" class="headerlink" title="(3)安装 Node 应用管理器 PM2,并且后台运行 Express.js"></a>(3)安装 Node 应用管理器 PM2,并且后台运行 Express.js</h3><p>使用 PM2 好处:可以后台持续运行 node.js 应用,不用担心服务器重启导致网站关闭。它允许创建,管理和销毁多个 Node.js 应用实例。</p>
<p>PM2 官网:<a href="https://pm2.keymetrics.io/">https://pm2.keymetrics.io/</a></p>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line">npm install pm2 -g <span class="comment"># 在服务器进行 PM2 全局安装</span></span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/HelloWorld/ <span class="comment"># 进入项目目录下</span></span><br><span class="line">pm2 start app.js --name Helloworld <span class="comment"># PM2 运行 node.js 应用</span></span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># 通过执行下列命令,可保证云服务器开机后,pm2 管理器自动运行 Node.js 应用</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看当前所有被 pm2 管理的应用列表</span></span><br><span class="line"></span><br><span class="line">pm2 list</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将当前列表中的所有应用永久储存在 pm2 中</span></span><br><span class="line"></span><br><span class="line">pm2 save</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将 pm2 配置成系统开启服务</span></span><br><span class="line"></span><br><span class="line">pm2 startup</span><br></pre></td></tr></tbody></table></figure>
<h3 id="(4)DNS-域名和服务器-IP-绑定"><a href="#(4)DNS-域名和服务器-IP-绑定" class="headerlink" title="(4)DNS 域名和服务器 IP 绑定"></a>(4)DNS 域名和服务器 IP 绑定</h3><p>在您的 DNS 域名提供商的管理界面中,将 ip 地址和域名进行绑定。此教程使用 <a href="https://sg.godaddy.com/zh">GoDaddy</a> 作为域名提供商,请读者根据实际情况进行设置。</p>
<p>GoDaddy 配置具体步骤:</p>
<p><span style="color:orange">创建 A 记录:</span><br>key : @<br>value : DigitalOcean 分配的服务器 IP<br>TTL 默认 1h。</p>
<p><span style="color:orange">创建 CNAME 记录:</span><br>key : www<br>value 为 @<br>TTL 默认 1h。</p>
<div class="note info"><p>注意,域名和 IP 绑定后,由于网络 DNS 缓存机制,对应关系不会立马生效,实际生效时间快则数分钟,慢则数小时。</p>
</div>
<div class="note info"><p>检验:<br>打开控制台,输入指令 <code>ping yourdomain.com</code>, 查看连接状态。如果连接返回来自服务器 IP 的网络包,则代表域名已成功匹配 IP。</p>
</div>
<h3 id="(5)Nginx-下载,进行反向代理配置,域名绑定"><a href="#(5)Nginx-下载,进行反向代理配置,域名绑定" class="headerlink" title="(5)Nginx 下载,进行反向代理配置,域名绑定"></a>(5)Nginx 下载,进行反向代理配置,域名绑定</h3><figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># 本教程使用 Ubuntu18.04.3 操作系统,请根据您 OS 版本进行 Nginx 安装。</span></span><br><span class="line"></span><br><span class="line">sudo apt update <span class="comment"># 更新软件库</span></span><br><span class="line">sudo apt install nginx <span class="comment"># 下载 nginx</span></span><br><span class="line">sudo systemctl <span class="built_in">enable</span> nginx <span class="comment"># 配置 nginx 作为开机服务</span></span><br></pre></td></tr></tbody></table></figure>
<p>当 nginx 下载后,在本地浏览器输入 <a href="http://161.35.59.204/">http://161.35.59.204:80/</a><br>看到 nginx 页面,表示 nginx 服务已经运行起来。</p>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># 打开 nginx 默认配置文件</span></span><br><span class="line"></span><br><span class="line">sudo nano /etc/nginx/nginx.conf</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># nginx.conf 文件</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 请确保 http 框中存在下面两句指令( 一般默认存在,若不存在,则需手动添加 )</span></span><br><span class="line"></span><br><span class="line">http{</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">include /etc/nginx/conf.d/_.conf;</span><br><span class="line">include /etc/nginx/sites-enabled/_;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>接着,进入 conf.d 文件夹中,为 helloworld 应用创建独立的配置文件,实际上,只要文件后缀是.conf,都会被 nginx 读取,但建议使用和项目名称相关联的命名方法,这里我们使用 com.helloworld.conf</p>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line">touch /etc/nginx/conf.d/com.helloworld.conf <span class="comment"># 创建配置文件</span></span><br></pre></td></tr></tbody></table></figure>
<p>拷贝以下内容,保存文件并退出。</p>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># com.helloworld.conf 文件</span></span><br><span class="line"></span><br><span class="line">server {</span><br><span class="line">listen 80; <span class="comment"># 填写您在服务商购买的域名,本教程使用域名 demo.luqifu.tech</span></span><br><span class="line">server_name demo.luqifu.tech;</span><br><span class="line"></span><br><span class="line"> charset utf8;</span><br><span class="line"></span><br><span class="line"> location / {</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 映射到代理服务器,可以是ip加端口 或 url</span></span><br><span class="line"> proxy_pass http://127.0.0.1:3000;</span><br><span class="line"></span><br><span class="line"> proxy_set_header Host <span class="variable">$host</span>;</span><br><span class="line"> proxy_set_header X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line"> proxy_set_header X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>接着,在控制台运行以下命令</p>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line">Nginx -t <span class="comment"># 修改后检查配置文件格式</span></span><br><span class="line">nginx -s reload <span class="comment"># 让 nginx 重新读取 conf 配置</span></span><br></pre></td></tr></tbody></table></figure>
<p>现在,在浏览器中直接访问域名,就能直接访问 Helloworld 网页。</p>
<h3 id="(6)防火墙端口-80-443-端口配置"><a href="#(6)防火墙端口-80-443-端口配置" class="headerlink" title="(6)防火墙端口 80/443 端口配置"></a>(6)防火墙端口 80/443 端口配置</h3><p>但是,为了提高网站安全性,我们不希望用户通过 3000 端口访问网站。相反,应该在服务器端配置防火墙,只允许用户通过 80(HTTP)和 443(HTTPS)端口访问网站,</p>
<figure class="highlight sh"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># 安装防火墙</span></span><br><span class="line"></span><br><span class="line">sudo apt update</span><br><span class="line">sudo apt install ufw</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开启防火墙</span></span><br><span class="line"></span><br><span class="line">sudo ufw <span class="built_in">enable</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置防火墙禁用所有端口</span></span><br><span class="line"></span><br><span class="line">sudo ufw default deny</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置防火墙开启特定端口 ( ssh:22 | http:80 | https: 443 )</span></span><br><span class="line"></span><br><span class="line">sudo ufw allow ssh</span><br><span class="line">sudo ufw allow http</span><br><span class="line">sudo ufw allow https</span><br></pre></td></tr></tbody></table></figure>
<p>现在,浏览器只能通过 80 / 443 端口访问网站页面。<br>相信在你最喜爱的浏览器输入 <a href="http://demo.luqifu.tech/">http://demo.luqifu.tech</a> , 就能看到所部署的应用。<br>如果您希望为网站配置 HTTPS ( 443 ) 访问,请参考教程 <a href="/post/%E4%BD%BF%E7%94%A8let-s-encrypt%E5%9C%A8nginx%E4%B8%AD%E4%B8%BAnode%E5%BA%94%E7%94%A8%E6%B7%BB%E5%8A%A0https%E8%AE%BF%E9%97%AE.html" title="在Nginx中为Node应用添加HTTPS访问">在Nginx中为Node应用添加HTTPS访问</a>。</p>
<h2 id="四-结语"><a href="#四-结语" class="headerlink" title="(四) 结语"></a>(四) 结语</h2><hr>
<p>至此,本教程结束。</p>
<p>这是我最近云端部署 Node 应用的一些心得,希望能帮到大家。</p>
<p>非常感谢。</p>
<script>
document.querySelectorAll('.github-emoji')
.forEach(el => {
if (!el.dataset.src) { return; }
const img = document.createElement('img');
img.style = 'display:none !important;';
img.src = el.dataset.src;
img.addEventListener('error', () => {
img.remove();
el.style.color = 'inherit';
el.style.backgroundImage = 'none';
el.style.background = 'none';
});
img.addEventListener('load', () => {
img.remove();
});
document.body.appendChild(img);
});
</script>]]></content>
<categories>
<category>Cloud Deployment</category>
</categories>
<tags>
<tag>Node.js</tag>
<tag>Nginx</tag>
<tag>DigitalOcean</tag>
<tag>Cloud Deployment</tag>
</tags>
</entry>
<entry>
<title>使用Let's Encrypt在Nginx中为Node应用添加HTTPS访问</title>
<url>/post/%E4%BD%BF%E7%94%A8let-s-encrypt%E5%9C%A8nginx%E4%B8%AD%E4%B8%BAnode%E5%BA%94%E7%94%A8%E6%B7%BB%E5%8A%A0https%E8%AE%BF%E9%97%AE.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>本文讲述利用 Let’s Encrypt 生成 ssl 证书,将证书配置到 Nginx 反向代理,使 node.js 应用实现 https 访问。</p>
<h2 id="一-什么是-http-协议?"><a href="#一-什么是-http-协议?" class="headerlink" title="(一) 什么是 http 协议?"></a>(一) 什么是 http 协议?</h2><hr>
<p>HTTP ( HyperText Transfer Protocol )协议, 又称超文本传输协议, 是一种用于分布式、协作式和超媒体信息系统的应用层协议。它是一种用于分布式、协作式和超媒体信息系统的应用层协议,也是万维网的数据通信的基础。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。<a href="https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE">来源: 维基百科</a></p>
<h2 id="二-http-和-https-的区别"><a href="#二-http-和-https-的区别" class="headerlink" title="(二) http 和 https 的区别"></a>(二) http 和 https 的区别</h2><hr>
<p>HTTP 协议传输的数据都是明文传输的,因此使用 HTTP 协议视为安全性不足。为了保证这些隐私数据能加密传输,网景公司设计了 SSL(Secure Sockets Layer)协议用于对 HTTP 协议传输的数据进行加密,从而就诞生了 HTTPS。简单来说,HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比 http 协议安全。</p>
<blockquote>
<p>HTTP 协议使用 80 端口,HTTPS 协议使用 443 端口。</p>
</blockquote>
<h2 id="三-关于-SSL-安全证书"><a href="#三-关于-SSL-安全证书" class="headerlink" title="(三) 关于 SSL 安全证书"></a>(三) 关于 SSL 安全证书</h2><hr>
<p>要为网站实现 HTTPS 访问,我们需要一张具有公信力的 CA ( Certificate Authority )也就是证书授权中心颁发的 SSL 安全证书,并且将它部署到你的网站服务器上。此教程将会使用 <code>Let's Encrypt & Certbot</code> 生成 SSL 安全证书。</p>
<blockquote>
<p>Let’s Encrypt 是一个于 2015 年三季度推出的数字证书认证机构,旨在以自动化流程消除手动创建和安装证书的复杂流程,为安全网站提供免费的传输层安全性协议(TLS)证书。</p>
</blockquote>
<h2 id="四-为网站添加-HTTPS-访问"><a href="#四-为网站添加-HTTPS-访问" class="headerlink" title="(四) 为网站添加 HTTPS 访问"></a>(四) 为网站添加 HTTPS 访问</h2><hr>
<blockquote>
<p>这里,我将演示如何使用 Certbot 为 nginx 服务器添加 ssl 证书</p>
</blockquote>
<h3 id="1-访问-Certbot-官网"><a href="#1-访问-Certbot-官网" class="headerlink" title="1.访问 Certbot 官网"></a>1.访问 <a href="https://certbot.eff.org/">Certbot 官网</a></h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597300452/tech_blog/certbot_ssl_node/step1.jpg"></p>
<h3 id="2-Certbot-将基于您的-HTTP-server-类型-以及-服务器操作系统,为你选择合适的安装方案。"><a href="#2-Certbot-将基于您的-HTTP-server-类型-以及-服务器操作系统,为你选择合适的安装方案。" class="headerlink" title="2.Certbot 将基于您的 HTTP server 类型 以及 服务器操作系统,为你选择合适的安装方案。"></a>2.Certbot 将基于您的 HTTP server 类型 以及 服务器操作系统,为你选择合适的安装方案。</h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597300859/tech_blog/certbot_ssl_node/step2.jpg"></p>
<h3 id="3-按照-Cerbot-官网给出的指令,为服务器安装并配置-ssl-证书。"><a href="#3-按照-Cerbot-官网给出的指令,为服务器安装并配置-ssl-证书。" class="headerlink" title="3.按照 Cerbot 官网给出的指令,为服务器安装并配置 ssl 证书。"></a>3.按照 Cerbot 官网给出的指令,为服务器安装并配置 ssl 证书。</h3><p>由于不同系统下,certbot 配置 ssl 证书方式不同。请务必根据 certbot 给出的指令执行。</p>
<h3 id="4-本教程是在-ubuntu18-04-3-版本下使用-nginx-服务器,因此-Certbot-给出了下列指令"><a href="#4-本教程是在-ubuntu18-04-3-版本下使用-nginx-服务器,因此-Certbot-给出了下列指令" class="headerlink" title="4.本教程是在 ubuntu18.04.3 版本下使用 nginx 服务器,因此 Certbot 给出了下列指令:"></a>4.本教程是在 <strong>ubuntu18.04.3</strong> 版本下使用 <strong>nginx</strong> 服务器,因此 Certbot 给出了下列指令:</h3><h4 id="首先,-SSH-远程登录到网站服务器"><a href="#首先,-SSH-远程登录到网站服务器" class="headerlink" title="首先, SSH 远程登录到网站服务器"></a>首先, SSH 远程登录到网站服务器</h4><p>使用 SSH 登录到部署网站的服务器,并且登录用户必须具有 sudo 权限。</p>
<h4 id="接着,-添加-Certbot-PPA"><a href="#接着,-添加-Certbot-PPA" class="headerlink" title="接着, 添加 Certbot PPA"></a>接着, 添加 Certbot PPA</h4><p>在服务器上添加 Certbot PPA 到包列表,执行下列操作:</p>
<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">sudo apt-get update</span><br><span class="line">sudo apt-get install software-properties-common</span><br><span class="line">sudo add-apt-repository universe</span><br><span class="line">sudo add-apt-repository ppa:certbot/certbot</span><br><span class="line">sudo apt-get update</span><br></pre></td></tr></tbody></table></figure>
<h4 id="安装-Certbot"><a href="#安装-Certbot" class="headerlink" title="安装 Certbot"></a>安装 Certbot</h4><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">sudo apt-get install certbot python3-certbot-nginx</span><br></pre></td></tr></tbody></table></figure>
<h4 id="然后,配置-SSL-证书到-nginx-服务器上"><a href="#然后,配置-SSL-证书到-nginx-服务器上" class="headerlink" title="然后,配置 SSL 证书到 nginx 服务器上"></a>然后,配置 SSL 证书到 nginx 服务器上</h4><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">sudo certbot --nginx</span><br></pre></td></tr></tbody></table></figure>
<h4 id="最后,检查证书自动更新功能"><a href="#最后,检查证书自动更新功能" class="headerlink" title="最后,检查证书自动更新功能"></a>最后,检查证书自动更新功能</h4><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line"># certbot生成的ssl证书,有效期为90天</span><br><span class="line"># 我们需要定期手动更新证书; 或者使用Unix系统下的定时任务工具cron定期执行</span><br><span class="line"></span><br><span class="line">sudo certbot renew --dry-run</span><br></pre></td></tr></tbody></table></figure>
<h2 id="五-使用-https-访问您的网站"><a href="#五-使用-https-访问您的网站" class="headerlink" title="(五) 使用 https 访问您的网站"></a>(五) 使用 https 访问您的网站</h2><hr>
<p>恭喜你,你已经可以使用 <a href="https://yourwebsite.com/">https://yourwebsite.com/</a> 访问您的网站 !</p>
<script>
document.querySelectorAll('.github-emoji')
.forEach(el => {
if (!el.dataset.src) { return; }
const img = document.createElement('img');
img.style = 'display:none !important;';
img.src = el.dataset.src;
img.addEventListener('error', () => {
img.remove();
el.style.color = 'inherit';
el.style.backgroundImage = 'none';
el.style.background = 'none';
});
img.addEventListener('load', () => {
img.remove();
});
document.body.appendChild(img);
});
</script>]]></content>
<categories>
<category>Cloud Deployment</category>
</categories>
<tags>
<tag>Nginx</tag>
<tag>Cloud Deployment</tag>
<tag>SSL</tag>
<tag>HTTP & HTTPS</tag>
</tags>
</entry>
<entry>
<title>使用ssh-keygen创建SSH公钥创建教程</title>
<url>/post/%E4%BD%BF%E7%94%A8ssh-keygen%E5%88%9B%E5%BB%BAssh%E5%85%AC%E9%92%A5%E5%88%9B%E5%BB%BA%E6%95%99%E7%A8%8B.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>本文主要讲述如何使用 ssh-keygen 命令创建属于您的公私钥对。</p>
<h2 id="一-什么是-ssh-keygen"><a href="#一-什么是-ssh-keygen" class="headerlink" title="(一) 什么是 ssh-keygen ?"></a>(一) 什么是 ssh-keygen ?</h2><hr>
<p>ssh-keygen 是 Unix 和类 Unix 计算机系统上的安全外壳(SSH)协议套件的标准组件,用于通过使用各种加密技术在不安全的网络上建立远程计算机之间的安全 shell 会话。<br>我们可以使用 ssh-keygen 生成,管理和转换身份验证密钥。本教程将会讲述使用 <code>ssh-keygen</code> 工具生成一对 <code>public key (公钥)</code> 和 <code>private key (私钥)</code>。</p>
<p><code>公钥一般存放于支持 SSH 协议的服务器中,如云服务器,git远程仓库。</code><br><code>私钥则保存于客户端,如用户电脑。切不可外泄,因为它是服务端验证客户端身份的令牌。</code></p>
<h2 id="二-为什么需要-SSH-Keys"><a href="#二-为什么需要-SSH-Keys" class="headerlink" title="(二) 为什么需要 SSH Keys ?"></a>(二) 为什么需要 SSH Keys ?</h2><hr>
<p>在 SSH 协议出现之前,我们常使用比如 telnent, ftp 这类型应用层协议进行客户端与服务器端的信息交互。然而,这些协议并不安全,这是因为他们是明文传输客户端与服务端的身份验证信息,例如用户名和密码。也就意味着用户更容易受到臭名昭著的 “中间人攻击 ( Man-in-the-middle attack )”。SSH 协议为了确保信息的安全传输,从连接发起到完成各阶段的各个点 SSH 协议采用了许多不同类型的数据加密技术,包括可逆的对称加密,非对称加密以及不可逆的哈希散列。因此,我们认为 SSH 协议更加安全可靠。</p>
<h2 id="三-SSH-key-创建流程"><a href="#三-SSH-key-创建流程" class="headerlink" title="(三) SSH key 创建流程"></a>(三) SSH key 创建流程</h2><hr>
<h3 id="1-首先检查-ssh-client-是否已安装在系统中"><a href="#1-首先检查-ssh-client-是否已安装在系统中" class="headerlink" title="1.首先检查 ssh client 是否已安装在系统中"></a>1.首先检查 ssh client 是否已安装在系统中</h3><p>打开本地控制台</p>
<p>1.检查 <code>ssh</code> 命令是否可用</p>
<pre><code>> ssh
usage: ssh [-46Yy][-b bind_interface]
[-b bind_address][-c cipher_spec] [-D [bind_address:]port][-e log_file]
...</code></pre>
<p>如果 ssh 命令不可用,则按照下列指示安装 openssh 服务。</p>
<h6 id="对于-window-用户:"><a href="#对于-window-用户:" class="headerlink" title="对于 window 用户:"></a>对于 window 用户:</h6><p>进入 <code>设置</code>-<code>应用</code>-<code>应用和功能</code>-<code>管理可选功能</code>-<code>添加功能</code> - <code>安装 OpenSSH 服务器和客户端</code>,</p>
<h6 id="对于-mac-Unix-用户:"><a href="#对于-mac-Unix-用户:" class="headerlink" title="对于 mac / Unix 用户:"></a>对于 mac / Unix 用户:</h6><p>系统应该自带了 ssh 服务,我们可以直接使用 ssh-keygen 命令</p>
<h3 id="2-使用-ssh-keygen-创建密钥:"><a href="#2-使用-ssh-keygen-创建密钥:" class="headerlink" title="2.使用 ssh-keygen 创建密钥:"></a>2.使用 ssh-keygen 创建密钥:</h3><p>打开本地控制台,输入</p>
<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line"># 指定 –t 选项 使用类型为rsa创建密钥对。</span><br><span class="line"></span><br><span class="line">ssh-keygen -t rsa</span><br></pre></td></tr></tbody></table></figure>
<p>密钥创建过程中,需要按下多次回车键, 即默认值创建。</p>
<h3 id="3-获取创建的密钥:"><a href="#3-获取创建的密钥:" class="headerlink" title="3.获取创建的密钥:"></a>3.获取创建的密钥:</h3><p>创建完成后,系统会将新创建的密钥对保存在用户的.ssh 目录下。例如:<br>Window:<code>C:\Users\{你的用户名}\\.ssh</code><br>Mac / Unix: <code>~/.ssh</code></p>
<p>进入<code>.ssh</code> 目录,就能看到刚刚创建的公钥文件<code>id_rsa.pub</code> 和 私钥文件<code>id_rsa</code>。<strong>切记,私钥文件不能外泄;公钥则根据实际需要,保存到带有 SSH server 的服务器中</strong>。</p>
<h2 id="结语:"><a href="#结语:" class="headerlink" title="结语:"></a>结语:</h2><hr>
<p>恭喜你,你已经拥有了能用于加解密的公钥对!<br>至此,本教程结束。非常感谢。</p>
<script>
document.querySelectorAll('.github-emoji')
.forEach(el => {
if (!el.dataset.src) { return; }
const img = document.createElement('img');
img.style = 'display:none !important;';
img.src = el.dataset.src;
img.addEventListener('error', () => {
img.remove();
el.style.color = 'inherit';
el.style.backgroundImage = 'none';
el.style.background = 'none';
});
img.addEventListener('load', () => {
img.remove();
});
document.body.appendChild(img);
});
</script>]]></content>
<categories>
<category>Cloud Deployment</category>
</categories>
<tags>
<tag>SSH</tag>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title>创建DigitalOcean服务器实例Droplet</title>
<url>/post/%E5%88%9B%E5%BB%BAdigitalocean%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%9E%E4%BE%8Bdroplet.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>本文主要讲述如何使用 DigitalOcean 创建我们的云服务器 VPS。</p>
<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="(一) 前言"></a>(一) 前言</h2><hr>
<p>DigitalOcean 是一家建立于美国的云基础架构提供商,面向软件开发人员提供虚拟专用服务器(VPS), 现在已经是全球第二大的网络寄存服务公司。VPS 价格相对合理,月费最低 5 美金起。</p>
<h2 id="二-注册账户"><a href="#二-注册账户" class="headerlink" title="(二) 注册账户"></a>(二) 注册账户</h2><hr>
<p>登录 DigitalOcean 官网,并创建 DigitalOcean 账户:<br>链接:<a href="https://www.digitalocean.com/">https://www.digitalocean.com/</a></p>
<h2 id="三-创建项目"><a href="#三-创建项目" class="headerlink" title="(三) 创建项目"></a>(三) 创建项目</h2><hr>
<h3 id="1-登录账户后-创建-new-project"><a href="#1-登录账户后-创建-new-project" class="headerlink" title="1.登录账户后, 创建 new project."></a>1.登录账户后, 创建 new project.</h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597292830/tech_blog/DigitalOcean_Droplet_Create/step0.jpg"></p>
<h3 id="2-填写您的项目名称,描述和用途。"><a href="#2-填写您的项目名称,描述和用途。" class="headerlink" title="2.填写您的项目名称,描述和用途。"></a>2.填写您的项目名称,描述和用途。</h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step1.jpg"></p>
<h3 id="3-项目创建后,需要为项目新创建一个-Droplet-(Droplet-就是一个-VPS-服务器实例)。"><a href="#3-项目创建后,需要为项目新创建一个-Droplet-(Droplet-就是一个-VPS-服务器实例)。" class="headerlink" title="3.项目创建后,需要为项目新创建一个 Droplet (Droplet 就是一个 VPS 服务器实例)。"></a>3.项目创建后,需要为项目新创建一个 Droplet (Droplet 就是一个 VPS 服务器实例)。</h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step2.jpg"></p>
<h3 id="4-选择-Droplet-服务器的云端配置。请根据您实际需要自行配置,本教程中使用-ubuntu18-04-3-LTS-版本。"><a href="#4-选择-Droplet-服务器的云端配置。请根据您实际需要自行配置,本教程中使用-ubuntu18-04-3-LTS-版本。" class="headerlink" title="4.选择 Droplet 服务器的云端配置。请根据您实际需要自行配置,本教程中使用 ubuntu18.04.3(LTS)版本。"></a>4.选择 Droplet 服务器的云端配置。请根据您实际需要自行配置,本教程中使用 ubuntu18.04.3(LTS)版本。</h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step3.jpg"><br><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step4.jpg"><br><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step5.jpg"></p>
<p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step6.jpg"></p>
<h3 id="5-建议选择添加-SSH-key。这将用于本机远程登录服务器。使用-SSH-keys-验证比传统的密码验证更加安全方便。"><a href="#5-建议选择添加-SSH-key。这将用于本机远程登录服务器。使用-SSH-keys-验证比传统的密码验证更加安全方便。" class="headerlink" title="5.建议选择添加 SSH key。这将用于本机远程登录服务器。使用 SSH keys 验证比传统的密码验证更加安全方便。"></a>5.建议选择添加 SSH key。这将用于本机远程登录服务器。使用 SSH keys 验证比传统的密码验证更加安全方便。</h3><blockquote>
<p>尚未拥有 SSH key 公私钥,请参考 <a href="/post/%E4%BD%BF%E7%94%A8ssh-keygen%E5%88%9B%E5%BB%BAssh%E5%85%AC%E9%92%A5%E5%88%9B%E5%BB%BA%E6%95%99%E7%A8%8B.html" title="SSH公钥创建">SSH公钥创建</a> 教程。</p>
</blockquote>
<p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step7.jpg"><br><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step8.jpg"></p>
<h3 id="6-自定义主机名,创建-Droplet"><a href="#6-自定义主机名,创建-Droplet" class="headerlink" title="6.自定义主机名,创建 Droplet"></a>6.自定义主机名,创建 Droplet</h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step9.jpg"></p>
<h3 id="7-创建完成后,获取-Droplet-的公网-IP"><a href="#7-创建完成后,获取-Droplet-的公网-IP" class="headerlink" title="7.创建完成后,获取 Droplet 的公网 IP"></a>7.创建完成后,获取 Droplet 的公网 IP</h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step10.jpg"></p>
<h3 id="8-检查云服务器是否激活"><a href="#8-检查云服务器是否激活" class="headerlink" title="8.检查云服务器是否激活"></a>8.检查云服务器是否激活</h3><p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597293188/tech_blog/DigitalOcean_Droplet_Create/step11.jpg"></p>
<h2 id="四-结语"><a href="#四-结语" class="headerlink" title="(四) 结语"></a>(四) 结语</h2><hr>
<p>恭喜你,你已经拥有了一台 DigitalOcean 的云端服务器。</p>
<script>
document.querySelectorAll('.github-emoji')
.forEach(el => {
if (!el.dataset.src) { return; }
const img = document.createElement('img');
img.style = 'display:none !important;';
img.src = el.dataset.src;
img.addEventListener('error', () => {
img.remove();
el.style.color = 'inherit';
el.style.backgroundImage = 'none';
el.style.background = 'none';
});
img.addEventListener('load', () => {
img.remove();
});
document.body.appendChild(img);
});
</script>]]></content>
<categories>
<category>Cloud Deployment</category>
</categories>
<tags>
<tag>DigitalOcean</tag>
<tag>Cloud Deployment</tag>
<tag>VPS</tag>
</tags>
</entry>
<entry>
<title>DevOps篇 - Docker入门教程</title>
<url>/post/devops%E7%AF%87-docker%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>本文主要讲述 Docker 的基本概念以及仓库,镜像,容器的使用。</p>
<h2 id="一-什么是-Docker?"><a href="#一-什么是-Docker?" class="headerlink" title="(一) 什么是 Docker?"></a>(一) 什么是 Docker?</h2><hr>
<p>Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。</p>
<p>开发者通过 Docker 打包他们的应用以及与应用相关的依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,开发者通过 Docker 提供的命令对应用容器进行创建,运行,管理,以及销毁。</p>
<h2 id="二-为什么需要-Docker?"><a href="#二-为什么需要-Docker?" class="headerlink" title="(二) 为什么需要 Docker?"></a>(二) 为什么需要 Docker?</h2><hr>
<p>使用 Docker 的最大好处就是,根本上解决了软件应用由于运行在不同机器而产生一系列环境冲突的问题。</p>
<p>举个例子,我们在自己的电脑上编写基于 Python3.7 语法的脚本,可以被本机上 python3 的解释器所识别以及运行。但是,却没办法保证这一脚本可以在别的平台的机器上运行。这正是因为程序员开发的脚本,应用, 程序无一不依赖着机器本身所处的环境。</p>
<p>而 Docker 作为应用容器,允许程序员把应用的源代码,以及相关依赖包,一系列环境参数,统一封装和打包到容器之中;并且由于容器的可移植性,应用就可以运行在不同的平台之上。</p>
<h2 id="三-Docker-和虚拟机"><a href="#三-Docker-和虚拟机" class="headerlink" title="(三) Docker 和虚拟机"></a>(三) Docker 和虚拟机</h2><hr>
<p>(1) Docker 有着比虚拟机更少的抽象层。由于 docker 不需要 Hypervisor 实现硬件资源虚拟化,运行在 docker 容器上的程序直接使用的都是实际物理机的硬件资源。因此在 CPU、内存利用率上 docker 将会在效率上有优势。</p>
<p>(2) Docker 利用的是宿主机的内核,而不需要 Guest OS。因此,当新建一个容器时,docker 不需要和虚拟机一样重新加载一个操作系统内核。</p>
<p>(3) Docker 容器的启动是秒级的;而虚拟机启动是分钟级的。</p>
<p>(4) 一般而言,一台电脑借由虚拟机可以最多同时运行 2,3 个不同的操作系统;却可以同时运行上千个 Docker 容器。</p>
<h2 id="四-安装-Docker"><a href="#四-安装-Docker" class="headerlink" title="(四) 安装 Docker"></a>(四) 安装 Docker</h2><hr>
<p>1.请进入 <a href="https://hub.docker.com/">Docker 官网</a>, 注册您的 DockerHub 账户。</p>
<p>2.下载,安装,配置 Docker Desktop。</p>
<ul>
<li><a href="https://docs.docker.com/docker-for-windows/install/">window 官网教程</a></li>
<li><a href="https://docs.docker.com/docker-for-mac/install/">mac 官网教程</a></li>
</ul>
<p>检查 Docker 是否安装并配置到本地环境:</p>
<pre><code>> docker -v
Docker version 19.03.12, build 48a662</code></pre>
<p>运行 Docker Desktop, 检查 Docker 服务是否开启:<br>如果 Docker 服务已开启,该命令将列出所有本地 images。</p>
<pre><code>> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
# # # # #</code></pre>
<h2 id="五-Docker-基本知识"><a href="#五-Docker-基本知识" class="headerlink" title="(五) Docker 基本知识"></a>(五) Docker 基本知识</h2><hr>
<ul>
<li><p>仓库 Repository<br>为开发者提供一系列打包好的官网应用镜像,第三方应用镜像源</p>
</li>
<li><p>镜像 Image<br>创建应用容器 Container 的模板文件,利用它可以生成一个运行时的应用容器</p>
</li>
<li><p>容器 Container<br>基于镜像 Image 模板生成的,包含了应用的源代码,相关依赖包,以及应用运行所需环境的容器</p>
</li>
<li><p>DockerFile<br>用于生成 Docker 镜像 Images 的指令文件</p>
</li>
</ul>
<div class="note info"><p>镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 <code>类</code> 和 <code>实例</code> 一样,镜像是静态的定义,容器是镜像运行时的实体。一切 Docker 容器都是基于镜像构建的。</p>
</div>
<h2 id="六-Docker-仓库-Dockerhub"><a href="#六-Docker-仓库-Dockerhub" class="headerlink" title="(六) Docker 仓库 Dockerhub"></a>(六) Docker 仓库 Dockerhub</h2><hr>
<p>正如上文提及,Docker 容器都是基于镜像 Image 创建的。那么如何获取镜像呢?</p>
<p>比如,官方镜像仓库 <a href="https://hub.docker.com/">DockerHub</a> 为我们提供了大量优质的官方镜像。</p>
<p>打开命令行终端,</p>
<p>1.登录 DockerHub</p>
<pre><code>> docker login</code></pre>
<p>2.通过 <code>pull</code> 命令从 DockerHub 中抓取官方镜像文件,以 ubuntu 为例。</p>
<pre><code>> docker pull ubuntu:latest
# ubuntu 为镜像仓库名 latest 为版本号,即最新版本</code></pre>
<p>3.通过 <code>images</code> 命令查询本地镜像,ubuntu 镜像拉取成功</p>
<pre><code>> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 4e2eef94cd6b 3 weeks ago 73.9MB</code></pre>
<p>4.登出 DockerHub</p>
<pre><code>> docker logout</code></pre>
<h2 id="七-Docker-镜像指令"><a href="#七-Docker-镜像指令" class="headerlink" title="(七) Docker 镜像指令"></a>(七) Docker 镜像指令</h2><hr>
<p>1.从仓库获取镜像</p>
<pre><code>> docker pull <镜像仓库名>:<版本号></code></pre>
<p>2.查询本地所有存在镜像</p>
<pre><code>> docker images</code></pre>
<p>3.删除镜像</p>
<pre><code>> docker rmi <镜像ID></code></pre>
<p>4.查询仓库指定镜像</p>
<pre><code>> docker search <镜像关键字></code></pre>
<p>5.推送镜像到 Dockerhub</p>
<p>很多情况下,我们会通过修改镜像来满足实际开发需要,比如镜像内的环境参数,文件结构,依赖等。登录 DockerHub,把修改后的镜像推送到远程仓库,则可以实现镜像保存共享的目的。</p>
<pre><code># 首先,使用 `tag` 命令为被推送的镜像打上标签,username 务必与您的 Docker 账号用户名一致。
> docker tag <镜像ID> <username>/<自定义镜像名>:<版本号>
# 使用 `push` 命令推送到自己的DockerHub仓库上。
> docker push username/<自定义镜像名>:<版本号></code></pre>
<h2 id="八-Docker-容器-Container"><a href="#八-Docker-容器-Container" class="headerlink" title="(八) Docker 容器 Container"></a>(八) Docker 容器 Container</h2><hr>
<p>容器各种状态:</p>
<p>created(已创建)<br>restarting(重启中)<br>running(运行中)<br>removing(迁移中)<br>paused(暂停)<br>exited(停止)<br>dead(死亡)</p>
<p>1.基于镜像,创建并运行容器</p>
<h3 id="以-ubuntu-为例,本地开启交互伪终端"><a href="#以-ubuntu-为例,本地开启交互伪终端" class="headerlink" title="以 ubuntu 为例,本地开启交互伪终端"></a>以 ubuntu 为例,本地开启交互伪终端</h3><pre><code>> docker run -t -i --name <自定义容器名称> ubuntu:latest /bin/bash
> root@1c7ead338ea0:/#
# ubuntu:latest 指定镜像, ubuntu 为镜像仓库名,latest 为版本
# --name 自定义容器名称
# -t 当前窗口创建伪终端
# -i 允许用户标准输入交互
# bin/bash 放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。</code></pre>
<p>至此, 已经成功创建 ubuntu 应用容器,并打开了终端窗口 <code>root@1c7ead338ea0:/#</code> 。</p>
<h3 id="以-mysql-为例-p-指定端口映射"><a href="#以-mysql-为例-p-指定端口映射" class="headerlink" title="以 mysql 为例, -p 指定端口映射"></a>以 mysql 为例, <code>-p</code> 指定端口映射</h3><pre><code>> docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql
# -p 3306:3306 :映射容器服务的 3306 端口到宿主机的 3306 端口
# 外部主机可以直接通过 宿主机ip:3306 访问到 MySQL 的服务。
# -e MYSQL_ROOT_PASSWORD=123456
# 设置 MySQL 服务 root 用户的密码。</code></pre>
<p>2.查询运行中的容器</p>
<pre><code>> docker ps</code></pre>
<p>3.查询所有状态的容器</p>
<pre><code>> docker ps -a</code></pre>
<p>4.查询所有状态的容器</p>
<pre><code>> docker ps -a</code></pre>
<p>5.启动一个已停止的容器</p>
<pre><code>> docker start <容器ID></code></pre>
<p>6.停止容器</p>
<pre><code>> docker stop <容器ID></code></pre>
<p>7.重启容器</p>
<pre><code>> docker restart <容器ID></code></pre>
<p>8.容器后台运行,<code>-d</code> 指定容器运行模式为后台服务</p>
<pre><code>> docker run -i -t -d <镜像 ID> /bin/bash</code></pre>
<p>9.进入容器内部,两种方式</p>
<pre><code>> docker attach <容器 ID>
或
> docker exec -it <容器 ID> /bin/bash
# 区别:
# exec:Run a command in a running container
是针对已运行的容器实例进行操作,在已运行的容器中执行命令,不创建和启动新的容器,退出 shell 不会导致容器停止运行。
# attach:Attach local standard input, output, and error streams to a running container
# 本机的输入直接输到容器中,容器的输出会直接显示在本机的屏幕上,如果退出容器的 shell,容器会停止运行。</code></pre>
<p>10.主机与容器文件拷贝</p>
<p>从主机到容器</p>
<pre><code>> docker cp <主机文件> <容器 ID>:<拷贝路径>
# 将主机当前文件夹下的a.txt 拷贝到容器96f7f14239ff的 /www 目录下。
# docker cp ./a.txt 96f7f14239ff:/www/</code></pre>
<p>从容器到主机</p>
<pre><code>> docker cp <容器 ID>:<拷贝文件> <主机路径>
# 将容器96f7f14239ff的 /www 目录拷贝到主机 /tmp/下。
# docker cp 96f7f14239ff:/www/ /tmp/</code></pre>
<p>11.删除容器</p>
<pre><code>> docker rm <容器 ID></code></pre>
<p>12.导出容器</p>
<pre><code>> docker export <容器ID> > <导出的文件名.tar></code></pre>
<p>13.重新导入容器</p>
<pre><code>> docker import <导出的文件名.tar> <自定义镜像名称>:<版本>
# 另外 docker load 命令也可以导入一个镜像存储文件
# import:丢弃了所有的历史记录和元数据信息,仅保存容器当时的快照状态。在导入的时候可以重新制定标签等元数据信息。
# load:将保存完整记录,体积较大。</code></pre>
<h2 id="九-容器数据持久化及共享"><a href="#九-容器数据持久化及共享" class="headerlink" title="(九) 容器数据持久化及共享"></a>(九) 容器数据持久化及共享</h2><p>几乎所有应用程序都会产生并保存数据,最终实现数据持久化。而作为应用容器,Docker Container 是如何实现的呢?</p>
<p>Docker 提供了 3 种持久化数据的方式:</p>
<p>volumes:存于主机文件系统中 Docker 管理的目录下,解决了跨平台间资料权限和路径引用问题。<br>bind mount:存于主机文件系统中的任意位置。可能由于不同平台间权限和路径引用不同而发生冲突。<br>tmpfs mount(Linux 中):存于内存中(注意,并不是持久化到磁盘)。在容器的生命周期中,它能被容器用来存放非持久化的状态或敏感信息。</p>
<p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1599962751/tech_blog/docker/docker1.jpg"></p>
<h3 id="Docker-官方推荐使用-volume-实现数据持久化。"><a href="#Docker-官方推荐使用-volume-实现数据持久化。" class="headerlink" title="Docker 官方推荐使用 volume 实现数据持久化。"></a>Docker 官方推荐使用 volume 实现数据持久化。</h3><p>1.创建 volume,名为 my-vol</p>
<pre><code>> docker volume create my-vol</code></pre>
<p>2.列出 volume</p>
<pre><code>> docker volume ls
local my-vol</code></pre>
<p>3.获取 volume 信息</p>
<pre><code>> docker volume inspect my-vol
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]</code></pre>
<p>4.移除 volume</p>
<pre><code>> docker volume rm my-vol</code></pre>
<p>5.使用 <code>-v</code> 命令为 nginx 容器绑定 volume</p>
<pre><code># 将本机下创建的`my-vol`绑定到nginx容器的`/app/`文件夹下
> docker run -d --name my-app -v my-vol:/app nginx:latest</code></pre>
<p>至此,在容器的/app/文件夹下一切文件的添加,删除,修改都会同步到 my-vol 之中,反之亦然。</p>
<p>6.使用 <code>docker inspect my-app</code> 查看 volume 挂在路径</p>
<pre><code>"Mounts": [
{
"Type": "volume",
"Name": "my-vol",
"Source": "/var/lib/docker/volumes/my-vol/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]</code></pre>
<p>6.停止容器,移除 volume</p>
<pre><code>> docker stop my-app
> docker rm my-app
> docker volume rm my-vol</code></pre>
<h3 id="使用数据卷容器"><a href="#使用数据卷容器" class="headerlink" title="使用数据卷容器"></a>使用数据卷容器</h3><p>数据卷容器,顾名思义本身就是个 Docker 容器,只是充当了储存并共享容器数据的角色。<br>我们使用 <code>--volumes-from</code> 命令,将某个容器 volume 的挂载到另一个容器上。</p>
<p>假如当前有 my-app1 容器,已经与本机实现了 volume 挂载。<br>执行下列命令,将创建 my-app2 容器,并且将 volume 来源 直接挂载到 my-app1 容器</p>
<pre><code>> docker run --volumes-from <my-app1 容器ID> --name my-app2 -i -t ubuntu /bin/bash</code></pre>
<div class="note info"><p>注意,数据卷容器的生命周期将会持续到没有任一容器应用为止。若另一容器 my-app3 将 volume 挂载到 my-app2,即使我们把 my-app1 容器移除,也不影响 my-app3 和 my-app2 的挂载关系。</p>
</div>
<h2 id="十-结语"><a href="#十-结语" class="headerlink" title="(十) 结语"></a>(十) 结语</h2><hr>
<p>到这,Docker 入门篇教程已经全部结束。<br>下一篇教程 <a href="/post/devops%E7%AF%87-docker%E8%BF%9B%E9%98%B6%E4%B9%8Bdockerfile.html" title="DevOps篇 - Docker进阶之DockerFile">DevOps篇 - Docker进阶之DockerFile</a>, 将会进一步讨论 Dockerfile 的使用。</p>
<script>
document.querySelectorAll('.github-emoji')
.forEach(el => {
if (!el.dataset.src) { return; }
const img = document.createElement('img');
img.style = 'display:none !important;';
img.src = el.dataset.src;
img.addEventListener('error', () => {
img.remove();
el.style.color = 'inherit';
el.style.backgroundImage = 'none';
el.style.background = 'none';
});
img.addEventListener('load', () => {
img.remove();
});
document.body.appendChild(img);
});
</script>]]></content>
<categories>
<category>DevOps</category>
</categories>
<tags>
<tag>Docker</tag>
<tag>DevOps</tag>
<tag>CI/CD</tag>
<tag>Deployment</tag>
</tags>
</entry>
<entry>
<title>DevOps篇 - Docker进阶之DockerFile</title>
<url>/post/devops%E7%AF%87-docker%E8%BF%9B%E9%98%B6%E4%B9%8Bdockerfile.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>本教程主要讲述 DockerFile 的基本概念以及使用。</p>
<h2 id="一-什么是-DockerFile"><a href="#一-什么是-DockerFile" class="headerlink" title="(一) 什么是 DockerFile?"></a>(一) 什么是 DockerFile?</h2><hr>
<p>Dockerfile 是一个用来构建 Docker 镜像 Image 的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。</p>
<h2 id="二-使用-DockerFile-文件"><a href="#二-使用-DockerFile-文件" class="headerlink" title="(二) 使用 DockerFile 文件"></a>(二) 使用 DockerFile 文件</h2><hr>
<p>一般而言, Dockerfile 应位于构建目录的根路径之下。构建目录只包含需要项目源代码,以及 Dockerfile 即可。</p>
<p>我们可以用 <code>docker build .</code> 指令,默认将当前指令运行的目录下的 Dockerfile 作为构建模板,生成所需的镜像。<code>.</code> 指的是当前上下文 context。</p>
<p>在构建镜像时,会产生一个 build 的上下文(context)。所谓的上下文就是 docker build 命令的 PATH 或 URL 指定的路径中的文件的集合。由于镜像构建是在 docker daemon(服务器端)完成的。因此,指令第一步,是递归拷贝当前上下文的文件,即当前指令运行路径下所有文件和文件夹复制到 docker 服务器端,再进行镜像构建。</p>
<div class="note danger"><p>切勿在硬盘根路径下运行 <code>build</code> 指令,这将复制整个硬盘目录到 docker 服务器端。</p>
</div>
<h2 id="三-使用-dockerignore-文件"><a href="#三-使用-dockerignore-文件" class="headerlink" title="(三) 使用 .dockerignore 文件"></a>(三) 使用 .dockerignore 文件</h2><hr>
<p>如果希望 docker 在构建时忽略某些文件或文件夹,可以在构建目录下创建 <code>.dockerignore</code> 文件, 指定希望 docker 忽略的文件和文件夹名称,用法和 <code>.gitignore</code> 文件相似。</p>
<h2 id="四-创建-Dockerfile-文件"><a href="#四-创建-Dockerfile-文件" class="headerlink" title="(四) 创建 Dockerfile 文件"></a>(四) 创建 Dockerfile 文件</h2><p>Dockerfile 拥有一系列的保留关键字,用作构建指令。</p>
<ul>
<li>ADD</li>
<li>COPY</li>
<li>ENV</li>
<li>FROM</li>
<li>LABEL</li>
<li>USER</li>
<li>EXPOSE</li>
<li>VOLUME</li>
<li>WORKDIR</li>
<li>RUN</li>
<li>ENTRYPOINT</li>
<li>CMD</li>
<li>ONBUILD</li>
</ul>
<h3 id="FROM-指令"><a href="#FROM-指令" class="headerlink" title="FROM 指令"></a>FROM 指令</h3><hr>
<p>格式</p>
<pre><code>FROM <Image-Name>:<tag-version></code></pre>
<p>Dockerfile 创建镜像,是以一个镜像为基础,在其上进行定制。</p>
<p>例如,我们可以基于 <code>ubuntu</code> 官方镜像,进行自定义修改,并生成属于自己的新镜像 my-ubuntu。而 <code>FROM</code> 就是指定 基础镜像,因此一个 Dockerfile 中 <code>FROM</code> 是必备的指令,并且必须是第一条指令。</p>
<pre><code># Dockerfile
# 基于ubuntu镜像构建
FROM ubuntu</code></pre>
<p>在 Dockerhub 上有大量优质的服务类官方镜像,如 <code>mysql</code>, <code>tomcat</code>, <code>php</code>, <code>jdk</code>, <code>nginx</code>, <code>apache</code>, <code>python</code>, <code>nodejs</code> 等, 基于这些官方镜像,进行二次开发,创建适合自身项目的运行环境。</p>
<p>OS 相关的官方镜像,如 <code>debian</code>, <code>centos</code>, <code>ubuntu</code>, <code>fedora</code>, <code>alpine</code>等, 基于这些官方镜像,创建适合自身项目的操作系统环境。</p>
<h3 id="LABEL-指令"><a href="#LABEL-指令" class="headerlink" title="LABEL 指令"></a>LABEL 指令</h3><hr>
<p>格式</p>
<pre><code>LABEL <key>=<value> <key>=<value> <key>=<value> ...</code></pre>
<p>为键值对类型数据,用于添加元数据到镜像中, 如记录仓库地址,项目维护人联系方式等等</p>
<pre><code># Dockerfile
# 设置某个镜像的元数据
FROM ubuntu
LABEL maintainer="<project-owner> <email-address>" \
projectlink="https://github.com/xxx/xxx"</code></pre>
<div class="note info"><p>用 “\“ 符号将长指令分行,提高可读性</p>
</div>
<h3 id="ENV-指令"><a href="#ENV-指令" class="headerlink" title="ENV 指令"></a>ENV 指令</h3><hr>
<p>格式</p>
<pre><code>ENV <key> <value>
ENV <key>=<value> ...</code></pre>
<p>设置镜像的环境变量<br>定义环境变量的同时,可以引用已经定义的环境变量。</p>
<pre><code># Dockerfile
# 下载node,version为12.18.3, 引用变量 $NODE_VERSION
FROM ubuntu
ENV NODE_VERSION 12.18.3
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"</code></pre>
<h3 id="WORKDIR-指令"><a href="#WORKDIR-指令" class="headerlink" title="WORKDIR 指令"></a>WORKDIR 指令</h3><hr>
<p>格式</p>
<pre><code>WORKDIR /path/to/workdir</code></pre>
<p>一般情况下,容器默认路径为根路径 <code>/</code> 。我们使用 <code>WORKDIR</code> 指令可以修改容器启动时候的工作路径。</p>
<pre><code># Dockerfile
# 设置容器启动路径为 `/usr/`
FROM ubuntu
WORKDIR /user/</code></pre>
<h3 id="USER-指令"><a href="#USER-指令" class="headerlink" title="USER 指令"></a>USER 指令</h3><hr>
<p>格式</p>
<pre><code>USER <user>[:<group>]
USER <UID>[:<GID>]</code></pre>
<p>USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。</p>
<pre><code># Dockerfile
# 设置当前 USER 为 redis, 并启动 redis-server
FROM ubuntu
USER redis
RUN [ "redis-server" ]</code></pre>
<h3 id="COPY-指令"><a href="#COPY-指令" class="headerlink" title="COPY 指令"></a>COPY 指令</h3><hr>
<p>格式</p>
<pre><code>COPY <src>... <dest></code></pre>
<p>将构建目录下的文件,如项目源代码,拷贝到容器镜像中</p>
<pre><code># Dockerfile
# 将当前目录下的 file.txt 文件拷贝到 镜像 /usr/ 目录下
FROM ubuntu
COPY file.txt /usr/</code></pre>
<h3 id="ADD-指令"><a href="#ADD-指令" class="headerlink" title="ADD 指令"></a>ADD 指令</h3><hr>
<p>格式</p>
<pre><code>ADD <src>... <dest></code></pre>
<p>ADD 指令除了能完成 COPY 指令的功能外,还可以直接解压压缩文件并把它们添加到镜像中</p>
<pre><code># Dockerfile
# 将当前目录下的 project.tar.gz 解压后添加到 镜像 /usr/ 目录下
FROM ubuntu
ADD project.tar.gz /usr/</code></pre>
<h3 id="RUN-指令"><a href="#RUN-指令" class="headerlink" title="RUN 指令"></a>RUN 指令</h3><hr>
<p>格式</p>
<pre><code>RUN <command> (shell form)
RUN ["executable", "param1", "param2"] (exec form)</code></pre>
<p>用于构建镜像,<code>RUN</code> 指令用于执行 SHELL 命令的。默认情况下,Linux 使用 <code>/bin/sh -c</code>; Windows 使用 <code>cmd /S /C</code> 。</p>
<pre><code># Dockerfile
# 更新软件库,并且下载vim, git, nodejs到镜像中
FROM ubuntu
# 使用 shell 格式
RUN apt-get update && \
apt-get upgrade && \
apt-get install vim git nodejs
# 使用 exec 格式, 指定 shell 为 BASH
RUN ["/bin/bash", "-c", "echo hello"]</code></pre>
<div class="note info"><p>多个脚本命令通过 “&&” 符号写在一个 run 指令中,可减少 layer 数量, 有效减少镜像体积。</p>
</div>
<h3 id="CMD-指令"><a href="#CMD-指令" class="headerlink" title="CMD 指令"></a>CMD 指令</h3><hr>
<p>格式</p>
<pre><code>CMD ["executable","param1","param2"] (exec form, 推荐使用;注意需要使用双引号。)
CMD command param1 param2 (shell form)
CMD ["param1","param2"] (假如定义了ENTRYPOINT,CMD用于指定默认参数)</code></pre>
<p><span style="color:orange">(i) 一个 Dockerfile 只应该有一个 <code>CMD</code> 命令。如果存在多个 CMD, 只有最后一个会被执行。</span></p>
<pre><code>FROM ubuntu
CMD [ "sh", "-c", "echo hi" ]
CMD [ "sh", "-c", "echo hello" ]
CMD [ "sh", "-c", "echo world" ]
# 容器启动时,只会打印 “world”</code></pre>
<p><span style="color:orange">(ii) <code>CMD</code> 命令一般用于定义程序启动的默认行为;启动容器时,假如额外指定执行的命令,<code>CMD</code>命令会被覆盖。</span></p>
<p>比如,ubuntu 镜像的 CMD 是 /bin/bash, 即默认开启 bash。</p>
<p>假如运行 ubuntu 容器时,额外指定命令 <code>pwd</code> ,此命令会覆盖 <code>/bin/bash</code> 。</p>
<pre><code>docker run -it ubuntu echo "hello world"
# 将会打印出 "hello world"</code></pre>
<p><span style="color:orange">(iii) 如果 <code>CMD</code> 命令和 <code>ENTRYPOINT</code> 命令同时存在,<code>CMD</code> 命令后的内容作 <code>ENTRYPOINT</code> 命令的参数使用。必须统一使用 exec form 格式。</span></p>
<pre><code>FROM ubuntu
ENTRYPOINT ["curl"]
CMD ["http://bar.com/"]
# 容器启动执行 curl http://bar.com/</code></pre>
<p><span style="color:orange">(iv) <code>shell</code> 格式,默认使用 <code>/bin/sh -c</code>, 并最终都会被转换成 <code>exec</code> 格式执行。</span></p>
<pre><code>CMD echo $HOME
转换为
CMD [ "sh", "-c", "echo $HOME" ]</code></pre>
<p><span style="color:orange">(vi) <code>exec</code> 格式并不会默认开启 shell。<code>CMD</code> 用作启动项时,需要指定可执行命令的路径。</span></p>
<pre><code>FROM ubuntu
CMD ["/usr/bin/wc","--help"]</code></pre>
<div class="note info"><p><code>CMD</code> 命令用于启动容器时执行的命令; 而 <code>RUN</code> 命令用于镜像的构建阶段</p>
</div>
<h3 id="ENTRYPOINT-指令"><a href="#ENTRYPOINT-指令" class="headerlink" title="ENTRYPOINT 指令"></a>ENTRYPOINT 指令</h3><hr>
<p>格式</p>
<pre><code>ENTRYPOINT ["executable", "param1", "param2"] (推荐,注意需要使用双引号)
ENTRYPOINT command param1 param2</code></pre>
<p><span style="color:orange">(i) <code>ENTRYPOINT</code> 用途和 <code>CMD</code> 一样,都是在指定容器启动程序及参数。</span></p>
<p><span style="color:orange">(ii) Dockerfile 应至少指定一个 <code>ENTRYPOINT</code> 或者 <code>CMD</code> 指令。</span></p>
<p><span style="color:orange"> (iii) <code>ENTRYPOINT</code> 应当用作容器可执行命令的入口。</span></p>
<p><span style="color:orange"> (iv) <code>CMD</code> 应当作为 <code>ENTRYPOINT</code> 命令的参数指定器。</span></p>
<p>假如 Dockerfile 设定如下:</p>
<pre><code>FROM ubuntu
ENTRYPOINT [ "top", "-b" ]
CMD [ "-c" ]</code></pre>
<p>不带命令行参数运行容器, CMD 参数被追加到 ENTRYPOINT</p>
<pre><code># 运行容器
docker run --rm <镜像ID>
# 实际执行命令
top -b -c</code></pre>
<p>另一情况,带命令行参数, 会覆盖 CMD 参数:</p>
<pre><code># 运行容器
docker run --rm <镜像ID> -n 10
# 实际执行命令
top -b -n 10</code></pre>
<p><span style="color:orange"> (vi) 使用命令行启动容器时,如果有额外的参数,会被追加到 <code>ENTRYPOINT</code> 命令之后。</span></p>
<pre><code>FROM ubuntu
ENTRYPOINT [ "top", "-b" ]
# 运行容器,附加参数 -c
docker run --rm <镜像ID> -c
# 实际执行命令
top -b -c</code></pre>
<h3 id="EXPOSE-指令"><a href="#EXPOSE-指令" class="headerlink" title="EXPOSE 指令"></a>EXPOSE 指令</h3><hr>
<p>格式</p>
<pre><code>EXPOSE <port> [<port>/<protocol>...]</code></pre>
<p>设置暴露端口,声明运行时容器提供服务的端口<br>用于运行容器时候, <code>-p</code> 指令进行宿主机与容器间的端口映射</p>
<pre><code># Dockerfile
# 暴露容器端口80,协议为TCP和UDP
FROM ubuntu
EXPOSE 80/tcp
EXPOSE 80/udp</code></pre>
<h3 id="VOLUME-指令"><a href="#VOLUME-指令" class="headerlink" title="VOLUME 指令"></a>VOLUME 指令</h3><hr>
<p>格式</p>
<pre><code>VOLUME ["/path/to/workdir1", "/path/to/workdir2",...]</code></pre>
<p>设置容器内某目录作为数据卷(VOLUME),容器运行时自动挂载为匿名卷。<br>往该数据卷目录下,写入的信息都不会记录进容器存储层,而是保存在宿主机的文件系统中。</p>
<pre><code># Dockerfile
# 将容器的 /data/ 文件夹设置为数据卷目录
FROM ubuntu
VOLUME /data</code></pre>
<p>创建容器时候,将容器的数据卷目录 /data 映射到宿主机 /usr/mydata 目录</p>
<pre><code>docker run -itd -v /usr/mydata:/data ubuntu:latest /bin/bash</code></pre>
<h3 id="ONBUILD-指令"><a href="#ONBUILD-指令" class="headerlink" title="ONBUILD 指令"></a>ONBUILD 指令</h3><hr>
<p>格式</p>
<pre><code>ONBUILD <INSTRUCTION></code></pre>
<p>ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等.<br>而这些指令,在当前镜像构建时并不会被执行。而是作为钩子函数,只有以当前镜像为父镜像,去构建下级子镜像的时候才会被执行。</p>
<p>举个例子,<br>使用下列 Dockerfile 构建镜像,名为 <code>ParentImage</code>:</p>
<pre><code>FROM ubuntu
ONBUILD CMD echo "Child Images have been built"</code></pre>
<p>接着,再以 <code>ParentImage</code> 作为基础镜像,构建 <code>childImage</code></p>
<pre><code>FROM ParentImage
...</code></pre>
<p>将会在构建过程中打印 <code>Child Images have been built</code> 。</p>
<script>
document.querySelectorAll('.github-emoji')
.forEach(el => {
if (!el.dataset.src) { return; }
const img = document.createElement('img');
img.style = 'display:none !important;';
img.src = el.dataset.src;
img.addEventListener('error', () => {
img.remove();
el.style.color = 'inherit';
el.style.backgroundImage = 'none';
el.style.background = 'none';
});
img.addEventListener('load', () => {
img.remove();
});
document.body.appendChild(img);
});
</script>]]></content>
<categories>
<category>DevOps</category>
</categories>
<tags>
<tag>Docker</tag>
<tag>DevOps</tag>
<tag>CI/CD</tag>
<tag>Deployment</tag>
</tags>
</entry>
<entry>
<title>网页开发之跨域请求CORS的实现-下篇</title>
<url>/post/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91%E4%B9%8B%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82cors%E7%9A%84%E5%AE%9E%E7%8E%B0-%E4%B8%8B%E7%AF%87.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>通过本教程,您将会了解如何解决网页开发中的跨域请求(CORS)中的冲突。<br>如果您不清楚什么是跨域请求和跨域冲突,建议请先阅读上一篇教程 <a href="/post/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91%E4%B9%8B%E4%BB%80%E4%B9%88%E6%98%AF%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82cors-%E4%B8%8A%E7%AF%87.html" title="网页开发之什么是跨域请求 CORS (上篇)">网页开发之什么是跨域请求 CORS (上篇)</a> 。</p>
<h2 id="一-主要内容"><a href="#一-主要内容" class="headerlink" title="(一) 主要内容"></a>(一) 主要内容</h2><hr>
<p>本文会从 <span style="border-bottom:1px solid yellow">开发模式</span> 和 <span style="border-bottom:1px solid yellow">生产模式</span> 下探讨跨域冲突的解决方案。</p>
<h2 id="二-开发模式"><a href="#二-开发模式" class="headerlink" title="(二) 开发模式"></a>(二) 开发模式</h2><hr>
<p>开发模式下的跨域冲突,往往是因为前后端分离开发,端口不一致所导致的。</p>
<p>例如,网页开发者使用 Vue,react 等前端框架进行本地开发,把前端页面运行在 <code>localhost:8080</code>。而服务器开发者使用了 Node.js 或者 Java 语言搭建了后端服务器,运行在 <code>localhost:8081</code>。由于使用端口不一致,前后端就产生了跨域冲突。</p>
<p>想要解决开发模式下的跨域冲突很简单,如果使用的是 webpack 项目,只需要在 webpack 配置文件 webpack.config.js 中添加 <code>devServer(代理服务器)</code> 。</p>
<p>实现场景</p>
<p><span style="color:orange">客户端网页:</span><br><a href="http://localhost:8080/index.html">http://localhost:8080/index.html</a></p>
<p><span style="color:orange">服务器请求 URL:</span><br><a href="http://localhost:3333/my-cors-request">http://localhost:3333/my-cors-request</a></p>
<p><span style="color:orange"> webpack 的 代理服务器:</span></p>
<p><a href="http://localhost:8080/">http://localhost:8080/</a></p>
<p>那么,前端 JS 请求代码实现如下</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">var</span> xhr = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 先把请求发送同一 8080 端口下的代理服务器</span></span><br><span class="line">xhr.open(‘GET, ‘http:<span class="comment">//localhost:8080/api/my-cors-request’, true);</span></span><br><span class="line"></span><br><span class="line">xhr.send(<span class="literal">null</span>);</span><br></pre></td></tr></tbody></table></figure>
<p>在 webpack.config.js 添加 devServer 配置如下</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 代理服务器截获 http://localhost:8080/api/my-cors-request 请求</span></span><br><span class="line"></span><br><span class="line"> devServer: {</span><br><span class="line"> host: <span class="string">"localhost"</span>,</span><br><span class="line"> port: <span class="number">8080</span>,</span><br><span class="line"> proxy: {</span><br><span class="line"> <span class="string">'/api'</span>: {</span><br><span class="line"> target: <span class="string">'http://localhost:3333'</span>, <span class="comment">// 目标服务器地址</span></span><br><span class="line"> changeOrigin: <span class="literal">true</span>,</span><br><span class="line"> pathRewrite: {</span><br><span class="line"> <span class="string">'/api'</span>: <span class="string">''</span> <span class="comment">// 重写 URL,去掉"/api"前缀</span></span><br><span class="line"> <span class="comment">// url 重写为 http://localhost:3333/my-cors-request</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>经过上述配置,所有前端页面发送的请求,都会先经过代理服务器。 代理服务器统一把请求前缀 <code>http://localhost:8080/</code> 改写成 <code>http://localhost:3333/</code>, 以此实现跨域。</p>
<p>开发模式下的跨域冲突的解决,实际上只是利用前端框架所自带的代理服务器,进行请求转发,巧妙地欺骗了浏览器的检查。</p>
<p>可我们都知道,对于即将上线的项目(生产环境),最后都需要构建打包的,而打包出来的资源都是一堆静态资源 html, css, js 文件, 而所谓的代理服务器,其实也就不存在了。</p>
<div class="note info"><p>要从根本上解决跨域冲突,需要在服务器端进行跨域设置,即生产模式下的跨域。</p>
</div>
<h2 id="三-生产模式"><a href="#三-生产模式" class="headerlink" title="(三) 生产模式"></a>(三) 生产模式</h2><hr>
<p>面对跨域请求,浏览器会划分为: <code>简单请求</code> & <code>非简单请求</code>。</p>
<div class="note info"><p>了解简单/非简单请求,请前往阅读 <a href="/post/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91%E4%B9%8B%E4%BB%80%E4%B9%88%E6%98%AF%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82cors-%E4%B8%8A%E7%AF%87.html" title="网页开发之什么是跨域请求 CORS (上篇)">网页开发之什么是跨域请求 CORS (上篇)</a>。</p>
</div>
<h3 id="简单请求"><a href="#简单请求" class="headerlink" title="简单请求"></a>简单请求</h3><p>( 1 ) 假设当前页面域名 是 <code>https://www.example.com</code>。 向服务器发送请求前,浏览器会在请求头上添加 <code>origin</code> 字段:</p>
<pre><code>origin : https://www.example.com</code></pre>
<p>上面的头信息中,Origin 字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。</p>
<p>( 2 ) 服务器端获取请求头, 判断 <code>www.example.com</code> 是否为可信域。如果可信,需要在响应头中添加下列字段:</p>
<pre><code>Access-Control-Allow-Origin: https://www.example.com</code></pre>
<p>( 3 ) 浏览器获取来自服务器的响应头,得知 <code>www.example.com</code> 为可信域,最终把响应体返回给用户。</p>
<p>简单请求的服务端配置:</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 以 express.js 后端为例</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">var</span> app = express();</span><br><span class="line"><span class="keyword">var</span> trusted_domain = [<span class="string">"https://www.example.com"</span>];</span><br><span class="line"></span><br><span class="line">app.all(<span class="string">'/your-request-url'</span>,<span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 判断请求头中 origin 字段是否存在于可信域中</span></span><br><span class="line"> <span class="keyword">if</span>(trusted_domain.includes(req.headers.origin)){</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Origin"</span>, req.headers.origin);</span><br><span class="line"> res.send(<span class="string">"CORS 请求成功!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> res.send(<span class="string">"CORS 请求失败!"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">});</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h3 id="非简单请求"><a href="#非简单请求" class="headerlink" title="非简单请求"></a>非简单请求</h3><p>一般是指有可能对服务器资源进行修改的请求,例如:</p>
<ul>
<li><p>Content-type 为 application/json 的请求</p>
</li>
<li><p>method 为 PUT, DELETE, PATCH 的请求</p>
</li>
<li><p>自定义请求头的请求</p>
</li>
</ul>
<p>面对非简单请求,浏览器在处理上会更加谨慎。</p>
<div class="note info"><p>因此,浏览器在发送真正的跨域请求前,会先发送一个 <code>预检请求(preflight)</code> 。</p>
</div>
<h4 id="数据类型为-application-json-的请求:"><a href="#数据类型为-application-json-的请求:" class="headerlink" title="数据类型为 application/json 的请求:"></a><span style="color:yellow">数据类型为 application/json 的请求:</span></h4><p>如果客户端请求体中包含 <code>JSON</code> 类型的数据,该请求的 Content-type 需要设置成 <code>application/json</code>,浏览器会把这一请求视为 “非简单请求” 。</p>
<p>( 1 ) 首先,浏览器发送一个 method 为 <code>OPTION</code> 的预检请求(preflight)到 服务器,并带上 <code>origin</code> 字段。<br>( 2 ) 类似于简单请求,服务器获取到预检请求的 <code>origin</code> 字段,判断请求来源是否可信。如果可信,则会在响应头上添加如下字段:</p>
<pre><code>Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Headers : content-type</code></pre>
<p>( 3 ) 浏览器获取响应头, 得知预检请求通过。<br>( 4 ) 浏览器执行真正的跨域请求。</p>
<p>非简单请求的服务端配置:</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 以 express.js 后端为例</span></span><br><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">var</span> app = express();</span><br><span class="line"><span class="keyword">var</span> trusted_domain = [<span class="string">"https://www.example.com"</span>];</span><br><span class="line"></span><br><span class="line">app.all(<span class="string">'/your-request-url'</span>,<span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(trusted_domain.includes(req.headers.origin)){</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Origin"</span>, req.headers.origin);</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Headers"</span>, <span class="string">"content-type"</span>);</span><br><span class="line"> <span class="comment">// 判断请求是否为预检请求,若是,立即返回 200</span></span><br><span class="line"> <span class="keyword">if</span>(req.method === <span class="string">"OPTIONS"</span>){</span><br><span class="line"> res.sendStatus(<span class="number">200</span>)</span><br><span class="line"> }</span><br><span class="line"> res.send(<span class="string">"CORS 请求成功!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> res.send(<span class="string">"CORS 请求失败!"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">});</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h4 id="方法为-PUT-或-DELETE-的请求:"><a href="#方法为-PUT-或-DELETE-的请求:" class="headerlink" title="方法为 PUT 或 DELETE 的请求:"></a><span style="color:yellow">方法为 PUT 或 DELETE 的请求:</span></h4><p>回到同样的情境中,假设客户端发送了方法为 PUT 或者 DELETE 的请求,浏览器会认为这是一个非简单请求。</p>
<p>( 1 ) 浏览器仍会先发送预检请求<br>( 2 ) 服务器判断通过预检请求来源,在响应头添加下列字段:</p>
<pre><code>Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods : GET,POST,DELETE,PUT</code></pre>
<p>( 3 ) 浏览器获取响应头, 得知预检请求通过。<br>( 4 ) 浏览器执行真正的跨域请求。</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 以 express.js 后端为例</span></span><br><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">var</span> app = express();</span><br><span class="line"><span class="keyword">var</span> trusted_domain = [<span class="string">"https://www.example.com"</span>];</span><br><span class="line"></span><br><span class="line">app.all(<span class="string">'/your-request-url'</span>,<span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(trusted_domain.includes(req.headers.origin)){</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Origin"</span>, req.headers.origin);</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Headers"</span>, <span class="string">"content-type"</span>);</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Methods"</span>, <span class="string">"PUT,DELETE,POST,GET"</span>);</span><br><span class="line"> <span class="keyword">if</span>(req.method === <span class="string">"OPTIONS"</span>){</span><br><span class="line"> res.sendStatus(<span class="number">200</span>)</span><br><span class="line"> }</span><br><span class="line"> res.send(<span class="string">"CORS 请求成功!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> res.send(<span class="string">"CORS 请求失败!"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">});</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h4 id="基于自定义-header-的非简单请求:"><a href="#基于自定义-header-的非简单请求:" class="headerlink" title="基于自定义 header 的非简单请求:"></a><span style="color:yellow">基于自定义 header 的非简单请求:</span></h4><p>非简单请求还有一种情况,请求头中自定义了 header。</p>
<p>( 1 ) 浏览器仍会先发送预检请求<br>( 2 ) 服务器判断通过预检请求来源,在响应头添加下列字段:</p>
<pre><code>Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Headers : <自定义的 header 名称></code></pre>
<p>( 3 ) 然后,浏览器获取 response header, 得知预检 preflight 通过。<br>( 4 ) 最后,浏览器执行真正的跨域请求。</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 以 express.js 后端为例</span></span><br><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">var</span> app = express();</span><br><span class="line"><span class="keyword">var</span> trusted_domain = [<span class="string">"https://www.example.com"</span>];</span><br><span class="line"></span><br><span class="line">app.all(<span class="string">'/your-request-url'</span>,<span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(trusted_domain.includes(req.headers.origin)){</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Origin"</span>, req.headers.origin);</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Headers"</span>, <span class="string">"content-type"</span>);</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Methods"</span>, <span class="string">"PUT,DELETE,POST,GET"</span>);</span><br><span class="line"> res.append(<span class="string">"Access-Control-Allow-Headers"</span>, <span class="string">"<您自定义的header名称>"</span>);</span><br><span class="line"> <span class="keyword">if</span>(req.method === <span class="string">"OPTIONS"</span>){</span><br><span class="line"> res.sendStatus(<span class="number">200</span>)</span><br><span class="line"> }</span><br><span class="line"> res.send(<span class="string">"CORS 请求成功!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> res.send(<span class="string">"CORS 请求失败!"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">});</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>当然,为了对 CORS 有进一步的操作,服务器还可以定义以下字段:</p>
<p>跨域请求默认不允许携带 cookies。如果需要传送 cookie, 则设置为 true。<br><code>Access-Control-Allow-Credentials</code></p>
<p>指定预检请求的有效时长:<br><code>Access-Control-Max-Age</code></p>
<h2 id="四-结语:"><a href="#四-结语:" class="headerlink" title="(四) 结语:"></a>(四) 结语:</h2><hr>
<p>到这里为止,我们已经把简单与非简单的各类型请求的跨域情况做出了分析,并且给出了解决方案。也相信大家对 CORS 已经有了一个比较全面的了解。在日常开发中,我们离不开跨域请求,因此掌握好跨域请求,绝不是一两篇教程就能完成的,还是需要大家多练习,多写写代码,以加深印象。<br>到此为止,本教程已经结束<br>非常感谢大家。</p>
<script>
document.querySelectorAll('.github-emoji')
.forEach(el => {
if (!el.dataset.src) { return; }
const img = document.createElement('img');
img.style = 'display:none !important;';
img.src = el.dataset.src;
img.addEventListener('error', () => {
img.remove();
el.style.color = 'inherit';
el.style.backgroundImage = 'none';
el.style.background = 'none';
});
img.addEventListener('load', () => {
img.remove();
});
document.body.appendChild(img);
});
</script>]]></content>
<categories>
<category>CORS</category>
</categories>
<tags>
<tag>HTTP</tag>
<tag>CORS</tag>
<tag>AJAX</tag>
</tags>
</entry>
<entry>
<title>HTTP缓存Cache机制</title>
<url>/post/http%E7%BC%93%E5%AD%98cache%E6%9C%BA%E5%88%B6.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>通过本文,你将会了解什么是 HTTP 缓存,以及它的好处,使用场景和具体用法。</p>
<h2 id="一-什么是-HTTP-缓存?"><a href="#一-什么是-HTTP-缓存?" class="headerlink" title="(一) 什么是 HTTP 缓存?"></a>(一) 什么是 HTTP 缓存?</h2><hr>
<p>HTTP 缓存是一种网络资源的短暂储存技术,具体来讲,就是对用户首次 HTTP 访问获取到的静态网络资源进行暂缓储存,等到二次访问时,直接给用户返回资源副本,以避免对相同的资源重复请求和渲染。</p>
<h2 id="二-使用缓存的优势"><a href="#二-使用缓存的优势" class="headerlink" title="(二) 使用缓存的优势"></a>(二) 使用缓存的优势</h2><hr>
<p>借由缓存技术,我们避免了对相同网络资源的重复请求和客户端渲染,这不仅大大减轻了服务器因为网络资源请求过载而产生的压力,降低了请求和资源应答在网络传输中的时延,而且用户体验也获得明显的提升。</p>
<h2 id="三-HTTP-缓存的种类"><a href="#三-HTTP-缓存的种类" class="headerlink" title="(三) HTTP 缓存的种类"></a>(三) HTTP 缓存的种类</h2><hr>
<p>HTTP 缓存在缓存载体上主要划分为: <code>公共缓存</code> 和 <code>本地缓存</code>。<br>公共缓存,主要储存在路由,服务器或者第三方代理上,这一类的缓存是可以被多个用户访问的。<br>本地缓存,主要储存在客户端应用和本地浏览器上,只能被用户本人访问。<br>我们本教程,主要讨论基于浏览器的本地缓存。</p>
<h2 id="四-浏览器缓存机制"><a href="#四-浏览器缓存机制" class="headerlink" title="(四) 浏览器缓存机制"></a>(四) 浏览器缓存机制</h2><hr>
<p>浏览器的缓存机制主要有两类:<code>强缓存</code> 以及 <code>协商缓存</code> 。<br><code>强缓存</code> 和 <code>协商缓存</code> 的最大区别在于,用户使用本地缓存前,是否需要获得服务器端的同意。</p>
<h3 id="强缓存"><a href="#强缓存" class="headerlink" title="强缓存"></a>强缓存</h3><p>用户请求资源,如果资源已经被浏览器缓存(命中缓存),浏览器直接返回请求资源,返回 200 状态码。这一过程中不需要与服务器端进行交互。如果请求的资源已经缓存过期,浏览器才会重新和服务器发送请求。</p>
<h3 id="协商缓存"><a href="#协商缓存" class="headerlink" title="协商缓存"></a>协商缓存</h3><p>用户请求资源,即使命中缓存,浏览器仍然发送缓存文件的版本标识码给服务器。对于被请求的资源文件,服务器会比较浏览器持有和服务器持有的标识码,以确定用户缓存资源是否失效。<br>如果尚未失效,服务端返回 304 状态码,浏览器直接使用缓存资源。如果缓存失效,服务器会重新发送最新版本的文件资源给浏览器,以及 200 状态码。浏览器会把缓存中的旧版资源替换为会最新版本。</p>
<h2 id="五-关于缓存的-Header"><a href="#五-关于缓存的-Header" class="headerlink" title="(五) 关于缓存的 Header"></a>(五) 关于缓存的 Header</h2><hr>
<p>(1) Cache-control 定义缓存策略( HTTP/ 1.1 )<br>(2) ETags 和 If-None-Match 用于服务器协商缓存<br>(3) Last-Modified 和 If-Modified-Since 用于服务器协商缓存<br>(4) Pragma 和 Expires 定义缓存策略( HTTP/ 1.0 )</p>
<h3 id="Cache-control-定义常用缓存策略"><a href="#Cache-control-定义常用缓存策略" class="headerlink" title="Cache-control 定义常用缓存策略"></a>Cache-control 定义常用缓存策略</h3><h4 id="禁用缓存"><a href="#禁用缓存" class="headerlink" title="禁用缓存"></a>禁用缓存</h4><p>缓存不得储存任何的文件副本。每一次都需要重新向服务器发送请求获取资源。</p>
<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">Cache-control : no-store</span><br></pre></td></tr></tbody></table></figure>
<h4 id="协商验证的缓存"><a href="#协商验证的缓存" class="headerlink" title="协商验证的缓存"></a>协商验证的缓存</h4><p>缓存中保存文件副本,但每次使用缓存前,都需要和服务器进行缓存协商。</p>
<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">Cache-control : no-cache</span><br></pre></td></tr></tbody></table></figure>
<h4 id="存活时长"><a href="#存活时长" class="headerlink" title="存活时长"></a>存活时长</h4><p>指定本地缓存有效使用时长;在有效时间内,浏览器可以直接使用缓存;除非缓存过期,才需要重新向服务器发送请求。</p>
<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">Cache-control : max-age=3600</span><br></pre></td></tr></tbody></table></figure>
<h4 id="公共缓存"><a href="#公共缓存" class="headerlink" title="公共缓存"></a>公共缓存</h4><p>允许浏览器和服务器中间的任何中间代理(如 CDN 等)对文件进行缓存。</p>
<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">Cache-control : public</span><br></pre></td></tr></tbody></table></figure>
<h4 id="本地缓存"><a href="#本地缓存" class="headerlink" title="本地缓存"></a>本地缓存</h4><p>只允许文件资源缓存在本地浏览器中,不得被其他中间代理缓存。</p>
<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">Cache-control : private</span><br></pre></td></tr></tbody></table></figure>
<h4 id="缓存验证"><a href="#缓存验证" class="headerlink" title="缓存验证"></a>缓存验证</h4><p>对陈旧的缓存,需要和服务器验证状态。已过期的缓存不会被使用。</p>
<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">Cache-control : Must-revalidate</span><br></pre></td></tr></tbody></table></figure>
<h3 id="ETags-和-If-None-Match-用于服务器协商缓存"><a href="#ETags-和-If-None-Match-用于服务器协商缓存" class="headerlink" title="ETags 和 If-None-Match 用于服务器协商缓存"></a>ETags 和 If-None-Match 用于服务器协商缓存</h3><p>(1) 浏览器首次请求资源文件。</p>
<p>(2) 服务器基于被请求的文件,生成文件标识 token, 并在响应头使用 ETags 字段传递 token。</p>
<p>例如:<code>ETags:9qL13hsya#c</code></p>
<p>(3) 当浏览器再次和服务器请求同一文件时候,客户端自动在请求头加上“If-None-Match” 字段,值为 < ETag token >。</p>
<p>例如:<code>If-None-Match:9qL13hsya#c</code></p>
<p>(4) 服务器根据当前资源核对 token。 如果 token 未发生变化,服务器将返回“304 Not Modified”响应以及一个空的响应体,浏览器直接使用本地缓存文件。如果已经发生变化,服务器会返回新的文件资源,响应码为 200。</p>
<h3 id="Last-Modified-和-If-Modified-Since-用于服务器协商缓存"><a href="#Last-Modified-和-If-Modified-Since-用于服务器协商缓存" class="headerlink" title="Last-Modified 和 If-Modified-Since 用于服务器协商缓存"></a>Last-Modified 和 If-Modified-Since 用于服务器协商缓存</h3><p>(1) 浏览器首次请求资源文件。</p>
<p>(2) 服务器返回文件以及响应头添加文件最后的修改时间 Last-Modified 。</p>
<p>例如:<code>last-modified: Thu, 13 Aug 2020 22:43:16 GMT</code></p>
<p>(3) 浏览器获取文件后,将文件以及 Last-Modified 时间戳进行缓存。</p>
<p>(4) 当浏览器再次和服务器请求同一文件时候,客户端自动在请求头加上“Last-Modified-Since” 字段,值为 Last-Modified 时间值。</p>
<p>例如:<code>last-modified-Since: Thu, 13 Aug 2020 22:43:16 GMT</code></p>
<p>(5) 服务器比对修改时间。 如果 Last-Midified 时间未发生变化,服务器将返回“304 Not Modified”响应以及一个空的响应体,浏览器直接使用本地缓存文件。如果已经发生变化,服务器会返回新的文件资源,响应码为 200。</p>
<h3 id="ETags-和-Last-Modifed-的对比:"><a href="#ETags-和-Last-Modifed-的对比:" class="headerlink" title="ETags 和 Last-Modifed 的对比:"></a>ETags 和 Last-Modifed 的对比:</h3><p>Etags 更能保证文件的准确性。Last-Modified 只能精确到以秒为单位的时间戳,但一秒以内发生的变化是无法捕捉的。Etag token 则是根据文件内容,借由 hash 算法得出的唯一标识,可以捕获文件的每次改变,因此,ETags 算是对 last-Modified 的补充。</p>
<p>在效率上来讲,ETags 对服务器端的开销要高于 Last-Modified,因为对于每次新的请求,服务器端都需要额外生成 ETag token 值,与浏览器缓存发来的 token 进行比较。</p>
<p>下图为缓存策略判定:</p>
<p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597837271/tech_blog/http%20cache/cache_strategy_tree.jpg"></p>
<p>(Image Src :reference[1] )</p>
<h3 id="Pragma-和-Expires-定义缓存策略-(-HTTP-1-0-)"><a href="#Pragma-和-Expires-定义缓存策略-(-HTTP-1-0-)" class="headerlink" title="Pragma 和 Expires 定义缓存策略 ( HTTP/ 1.0 )"></a>Pragma 和 Expires 定义缓存策略 ( HTTP/ 1.0 )</h3><h4 id="Pragma"><a href="#Pragma" class="headerlink" title="Pragma"></a>Pragma</h4><p>HTTP/1.0 标准中,一般赋值为 no-cache,效果上和 Cache-control:no-cache 一样。表示禁止浏览器缓存,每次都要向服务器发送新请求。这是以前老式浏览器的标准,但为了 http 协议向下兼容,至今持续使用。如果 Pragma 和 Cache-control 同时存在,浏览器会执行 Pragma 缓存策略。这说明,Pragma 优先级高于 Cache-control。</p>
<h4 id="Expires"><a href="#Expires" class="headerlink" title="Expires"></a>Expires</h4><p>HTTP/1.0 标准中,其值为一个 GMT 时间(精确到秒)。用来定义资源在缓存中的有效时长。但由于返回的是服务器端的时间,如果客户端时区不一致,可能会导致缓存策略失败。缓存在有效日期前会直接返回用户;资源过期后需要重新向服务器端请求资源。如果 Pragma 和 Expires 同时存在,Pragma 优先级最高。</p>
<h2 id="六-Reference"><a href="#六-Reference" class="headerlink" title="(六) Reference"></a>(六) Reference</h2><p><a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn">https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn</a> [1]</p>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ">https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ</a> [2]</p>
<script>
document.querySelectorAll('.github-emoji')
.forEach(el => {
if (!el.dataset.src) { return; }
const img = document.createElement('img');
img.style = 'display:none !important;';
img.src = el.dataset.src;
img.addEventListener('error', () => {
img.remove();
el.style.color = 'inherit';
el.style.backgroundImage = 'none';
el.style.background = 'none';
});
img.addEventListener('load', () => {
img.remove();
});
document.body.appendChild(img);
});
</script>]]></content>
<categories>
<category>Cache</category>
</categories>
<tags>
<tag>HTTP</tag>
<tag>Cache</tag>
</tags>
</entry>
<entry>
<title>网页开发之什么是跨域请求CORS-上篇</title>
<url>/post/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91%E4%B9%8B%E4%BB%80%E4%B9%88%E6%98%AF%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82cors-%E4%B8%8A%E7%AF%87.html</url>
<content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>通过本教程,您将会了解什么是跨域请求(CORS),跨域请求的原理,以及跨域请求的具体示例。</p>
<blockquote>
<p>下一篇教程, 将讨论 <a href="/post/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91%E4%B9%8B%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82cors%E7%9A%84%E5%AE%9E%E7%8E%B0-%E4%B8%8B%E7%AF%87.html" title="网页开发之跨域请求CORS的实现(下篇)">网页开发之跨域请求CORS的实现(下篇)</a> 。</p>
</blockquote>
<h2 id="一-什么是跨域请求?"><a href="#一-什么是跨域请求?" class="headerlink" title="(一) 什么是跨域请求?"></a>(一) 什么是跨域请求?</h2><hr>
<p>当一个源希望与另一个源的服务器资源进行交互时,前者会发送一系列的 HTTP 请求(GET,POST,DELETE,UPDATE 等)给后者,我们把这类型的请求视为跨域请求。具体来讲,只要两个站点所使用的<code>域名</code>,<code>协议</code>,<code>端口</code>任意一个不相等,我们把它们之间的 http 请求视为跨域。例如,我们通过浏览器打开 <code>http://127.0.0.1:8080</code> 的页面,浏览器是不允许该页面下发送跨域的 ajax 请求,以获取 <code>http://127.0.0.1:8081</code> 服务器下的资源 。</p>
<h2 id="二-跨域示例"><a href="#二-跨域示例" class="headerlink" title="(二) 跨域示例"></a>(二) 跨域示例</h2><hr>
<p>不同域名:<code>www.google.com</code> 和 <code>www.baidu.com</code><br>不同协议:<code>http</code> 和 <code>https</code><br>不同端口:<code>8080</code> 和 <code>8081</code></p>
<h2 id="三-浏览器跨域警告"><a href="#三-浏览器跨域警告" class="headerlink" title="(三) 浏览器跨域警告"></a>(三) 浏览器跨域警告</h2><hr>
<p>相信很多前端开发的朋友都碰到过以下的警告信息:</p>
<p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597579606/tech_blog/cors/step8.jpg"></p>
<p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597579606/tech_blog/cors/step6.jpg"></p>
<p><img src="https://res.cloudinary.com/qifu1995/image/upload/v1597579606/tech_blog/cors/step7.jpg"></p>
<p>这是因为在默认情况下,客户端(浏览器)会禁止客户端所有非法的跨域请求。<br>但有一种例外,网页上通过 <code>img</code>, <code>link</code>, <code>script</code> 等外链标签获取到的外部资源,也是跨域请求。浏览器默认这一类跨域请求是可信的,并不会禁止。过去,许多开发者则是基于外链标签可跨域的原理,使用 <code>JSONP</code> 发送跨域请求的。</p>
<h2 id="四-为什么禁止非法的跨域请求?"><a href="#四-为什么禁止非法的跨域请求?" class="headerlink" title="(四) 为什么禁止非法的跨域请求?"></a>(四) 为什么禁止非法的跨域请求?</h2><hr>
<p>我们可以想象一个网上银行存取款的场景。</p>
<p><code>www.yin-hang.com(可信站点)</code></p>
<p><code>www.yin-han.com(恶意站点)</code></p>
<p>小明每月都定期往在他的网银账户存入 5000 元的工资。</p>
<p>眼红的小黑(攻击者)心生歹念,设计了一套与网银官网(可信站点)用户界面极为相似的仿冒网站(恶意站点),并且为恶意网站注册了相似的域名。</p>
<p>为了使恶意站点更具欺骗性,小黑对网银官网(可信站点)执行了跨域请求数据的操作。通过浏览器以恶意站点的名义,往网银官网的服务器上请求各种可信数据。假若浏览器允许这次跨域请求的执行,网银服务器则会给恶意网站返回它的数据,到这一步,小黑的恶意网站几乎能以假乱真了。</p>
<p>接着,小黑把恶意网站的地址通过邮件链接发送给小明。<br>月末,小明收到了小黑的邮件,误以为是官方银行的工作人员发送的,大意地点开了邮件中的恶意网址链接。由于恶意站点的网页界面,域名以及各种数据,都和网银官网非常相似,小明也没有多思考,直接在恶意站点输入了用于登录官网的账号密码。小黑在这一步已经获取到了小明所有的身份验证信息。然后。。。</p>
<blockquote>
<p><span class="github-emoji" style="font-size:1em;font-weight:bold;color: transparent;background:no-repeat url(https://github.githubassets.com/images/icons/emoji/unicode/26a0.png?v8) center/contain" data-src="https://github.githubassets.com/images/icons/emoji/unicode/26a0.png?v8">⚠</span> 因此,大多数浏览器出于对用户安全的角度考虑,都会禁止所有非法的跨域请求。</p>
</blockquote>
<h2 id="五-跨域请求的具体类型"><a href="#五-跨域请求的具体类型" class="headerlink" title="(五) 跨域请求的具体类型"></a>(五) 跨域请求的具体类型</h2><hr>
<p>浏览器将跨域请求(CORS)分为简单请求(simple request) 以及 非简单请求(non-simple request)。</p>
<h3 id="简单请求:"><a href="#简单请求:" class="headerlink" title="简单请求:"></a>简单请求:</h3><p>( 1 ) 请求方法为 <code>GET</code>,<code>HEAD</code>, <code>POST</code>任意一种</p>
<p>( 2 ) HTTP 头信息只包含以下字段:</p>
<ul>
<li><code>Accept</code></li>
<li><code>Accept-Language</code></li>
<li><code>Content-Type</code></li>
<li><code>Content-Language</code></li>
<li><code>DPR</code></li>
<li><code>Downlink</code></li>
<li><code>Save-Data</code></li>
<li><code>Viewport-Width</code></li>
<li><code>Width</code></li>
</ul>
<p>其中 Content-type 只能是 application/x-www-form-urlencoded、multipart/form-data、text/plain 其中之一。</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 简单请求的例子</span></span><br><span class="line"><span class="comment">// 当前页面 http://127.0.0.1:8080/index.html</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> HTTP_CONFIG = { <span class="attr">method</span>: <span class="string">"GET"</span> };</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过 Fetch 给 http://127.0.0.1:8081/" 发送跨域请求,方法 GET</span></span><br><span class="line">fetch(<span class="string">"http://127.0.0.1:8081/"</span>, HTTP_CONFIG)</span><br><span class="line">.then(<span class="function"><span class="keyword">function</span> (<span class="params">response</span>) </span>{</span><br><span class="line"><span class="keyword">return</span> response.json();</span><br><span class="line">})</span><br><span class="line">.then(<span class="function"><span class="keyword">function</span> (<span class="params">res</span>) </span>{</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"Response from SERVER_2 : "</span>, res);</span><br><span class="line">});</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h3 id="非简单请求"><a href="#非简单请求" class="headerlink" title="非简单请求:"></a>非简单请求:</h3><p>( 1 ) content-type : application/json 的请求</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 非简单请求的例子</span></span><br><span class="line"><span class="comment">// 当前页面 http://127.0.0.1:8080/index.html</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> HTTP_CONFIG = {</span><br><span class="line">method: <span class="string">"POST"</span>,</span><br><span class="line">headers: {</span><br><span class="line"><span class="string">"content-type"</span>: <span class="string">"application/json"</span>,</span><br><span class="line">}</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过 Fetch 给 http://127.0.0.1:8081/" 发送跨域请求</span></span><br><span class="line">fetch(<span class="string">"http://127.0.0.1:8081/"</span>, HTTP_CONFIG)</span><br><span class="line">.then(<span class="function"><span class="keyword">function</span> (<span class="params">response</span>) </span>{</span><br><span class="line"><span class="keyword">return</span> response.json();</span><br><span class="line">})</span><br><span class="line">.then(<span class="function"><span class="keyword">function</span> (<span class="params">res</span>) </span>{</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"Response from SERVER_2 : "</span>, res);</span><br><span class="line">});</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>( 2 ) 方法为 PUT, DELETE 的请求</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 非简单请求的例子</span></span><br><span class="line"><span class="comment">// 当前页面 http://127.0.0.1:8080/index.html</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> HTTP_CONFIG = {</span><br><span class="line">method: <span class="string">"PUT"</span> <span class="comment">// 这里方法可以替换成 “DELETE”</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过 Fetch 给 http://127.0.0.1:8081/" 发送跨域请求,方法 PUT</span></span><br><span class="line">fetch(<span class="string">"http://127.0.0.1:8081/"</span>, HTTP_CONFIG)</span><br><span class="line">.then(<span class="function"><span class="keyword">function</span> (<span class="params">response</span>) </span>{</span><br><span class="line"><span class="keyword">return</span> response.json();</span><br><span class="line">})</span><br><span class="line">.then(<span class="function"><span class="keyword">function</span> (<span class="params">res</span>) </span>{</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"Response from SERVER_2 : "</span>, res);</span><br><span class="line">});</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>( 3 ) 自定义的 HTTP Header 的请求</p>