-
-
Notifications
You must be signed in to change notification settings - Fork 201
/
index.html
3518 lines (3370 loc) · 204 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
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
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Serverless 架构应用开发指南 – serverless</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
</style>
<style>
a.sourceLine { display: inline-block; line-height: 1.25; }
a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
a.sourceLine:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode { white-space: pre; position: relative; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
code.sourceCode { white-space: pre-wrap; }
a.sourceLine { text-indent: -1em; padding-left: 1em; }
}
pre.numberSource a.sourceLine
{ position: relative; left: -4em; }
pre.numberSource a.sourceLine::before
{ content: attr(title);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; pointer-events: all; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
a.sourceLine::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
</style>
<link rel="stylesheet" href="style.css" />
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
<meta name="viewport" content="width=device-width">
</head>
<body>
<h1>Serverless 应用开发指南</h1>
<p>By <a href="https://www.phodal.com/">Phodal</a> (Follow Me: <a href="http://weibo.com/phodal">微博</a>、<a href="https://www.zhihu.com/people/phodal">知乎</a>、<a href="https://segmentfault.com/u/phodal">SegmentFault</a>)
</p>
<p>
<iframe src="https://ghbtns.com/github-btn.html?user=phodal&repo=serverless&type=watch&count=true&size=large" height="30" width="170" frameborder="0" scrolling="0" style="width:170px; height: 30px;" allowTransparency="true"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=phodal&repo=serverless&type=fork&count=true&size=large" height="30" width="170" frameborder="0" scrolling="0" style="width:170px; height: 30px;" allowTransparency="true"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=phodal&type=follow&count=true&size=large" height="30" width="240" frameborder="0" scrolling="0" style="width:240px; height: 30px;" allowTransparency="true"></iframe>
</p>
<p>我的其他电子书:</p>
<ul>
<li>《<a href="https://github.com/phodal/ideabook">Phodal's Idea实战指南</a>》</li>
<li>《<a href="https://github.com/phodal/designiot">一步步搭建物联网系统</a>》</li>
<li>《<a href="https://github.com/phodal/github-roam">GitHub 漫游指南</a>》</li>
<li>《<a href="https://github.com/phodal/repractise">RePractise</a>》</li>
<li>《<a href="https://github.com/phodal/growth-ebook">Growth: 全栈增长工程师指南</a>》</li>
<li>《<a href="https://github.com/phodal/growth-in-action">Growth: 全栈增长工程师实战</a>》</li>
<li>《<a href="https://github.com/phodal/fe">我的职业是前端工程师</a>》</li>
<li>《<a href="https://github.com/phodal/make">写给软件工程师看的硬件编程指南</a>》</li>
</ul>
<p>微信公众号</p>
<p><img src="https://articles.phodal.com/qrcode.jpg" alt=""/></p>
<p>
当前为预览版,在使用的过程中遇到任何遇到请及时与我联系。阅读过程中问题,不烦在GitHub上提出来:
<a href="https://github.com/phodal/hardware-guide/issues">Issues</a>
</p>
<p>
阅读过程中遇到语法错误、拼写错误、技术错误等等,不烦来个Pull Request,这样可以帮助到其他阅读这本电子书的童鞋。
</p>
<div style="width:800px">
<nav id="TOC">
<ul>
<li><a href="#serverless-架构应用开发指南">Serverless 架构应用开发指南</a></li>
<li><a href="#serverless-架构">Serverless 架构</a><ul>
<li><a href="#什么是-serverless-架构">什么是 Serverless 架构??</a><ul>
<li><a href="#虚拟化与隔离">虚拟化与隔离</a></li>
<li><a href="#为什么是花了-1000g">为什么是花了 1000G ?</a></li>
<li><a href="#serverless-是什么">Serverless 是什么?</a></li>
<li><a href="#事件驱动编程">事件驱动编程</a></li>
</ul></li>
<li><a href="#serverless-的优势">Serverless 的优势</a><ul>
<li><a href="#降低启动成本">降低启动成本</a><ul>
<li><a href="#减少运营成本">减少运营成本</a></li>
<li><a href="#降低开发成本">降低开发成本</a></li>
</ul></li>
<li><a href="#实现快速上线">实现快速上线</a><ul>
<li><a href="#更快的部署流水线">更快的部署流水线</a></li>
<li><a href="#更快的开发速度">更快的开发速度</a></li>
</ul></li>
<li><a href="#系统安全性更高">系统安全性更高</a></li>
<li><a href="#适应微服务架构">适应微服务架构</a></li>
<li><a href="#自动扩展能力">自动扩展能力</a></li>
</ul></li>
<li><a href="#serverless-的问题">Serverless 的问题</a><ul>
<li><a href="#不适合长时间运行应用">不适合长时间运行应用</a></li>
<li><a href="#完全依赖于第三方服务">完全依赖于第三方服务</a></li>
<li><a href="#冷启动时间">冷启动时间</a></li>
<li><a href="#缺乏调试和开发工具">缺乏调试和开发工具</a></li>
<li><a href="#构建复杂">构建复杂</a></li>
<li><a href="#语言版本落后">语言版本落后</a></li>
</ul></li>
<li><a href="#serverless-的适用场景">Serverless 的适用场景</a><ul>
<li><a href="#发送通知">发送通知</a></li>
<li><a href="#webhook">WebHook</a></li>
<li><a href="#轻量级-api">轻量级 API</a></li>
<li><a href="#物联网">物联网</a></li>
<li><a href="#数据统计分析">数据统计分析</a></li>
<li><a href="#trigger-及定时任务">Trigger 及定时任务</a></li>
<li><a href="#精益创业">精益创业</a></li>
<li><a href="#chat-机器人">Chat 机器人</a></li>
</ul></li>
<li><a href="#其它">其它</a><ul>
<li><a href="#迁移方案">迁移方案</a></li>
<li><a href="#serverless-framework">Serverless Framework</a></li>
<li><a href="#apex">Apex</a></li>
<li><a href="#apache-openwhisk">Apache OpenWhisk</a></li>
</ul></li>
</ul></li>
<li><a href="#serverless-的-hello-world">Serverless 的 hello, world</a><ul>
<li><a href="#serverless-框架-hello-world">Serverless 框架 hello, world</a><ul>
<li><a href="#一安装-serverless-框架">一、安装 serverless 框架</a></li>
<li><a href="#二设置-aws-凭证">二、设置 aws 凭证。</a></li>
<li><a href="#三创建-hello-world-服务">三、创建 hello-world 服务</a></li>
<li><a href="#四部署及测试">四、部署及测试:</a></li>
</ul></li>
</ul></li>
<li><a href="#aws-lambda-动态编程返回-html">AWS Lambda 动态编程返回 HTML</a></li>
<li><a href="#将网站部署到-s3-上">将网站部署到 S3 上</a><ul>
<li><a href="#配置-serverless-finch">配置 serverless-finch</a></li>
<li><a href="#静态内容">静态内容</a></li>
</ul></li>
<li><a href="#为基于-s3-的网站支持-crud">为基于 S3 的网站支持 CRUD</a><ul>
<li><a href="#概念api-gateway-与-s3">概念:API Gateway 与 S3</a></li>
<li><a href="#基于-s3-的-serverless-crud">基于 S3 的 Serverless CRUD</a></li>
<li><a href="#上传原理">上传原理</a></li>
<li><a href="#serverless-s3-crud-示例">Serverless S3 CRUD 示例</a></li>
</ul></li>
<li><a href="#结合-dynamodb-数据库创建-restful-api">结合 DynamoDB 数据库创建 RESTful API</a><ul>
<li><a href="#serverless-dynamodb-示例配置">Serverless DynamoDB 示例配置</a></li>
<li><a href="#serverless-dynamodb-示例代码">Serverless DynamoDB 示例代码</a></li>
<li><a href="#serverless-dynamodb-部署">Serverless DynamoDB 部署</a></li>
<li><a href="#serverless-dynamodb-测试">Serverless DynamoDB 测试</a></li>
<li><a href="#其它操作">其它操作</a></li>
</ul></li>
<li><a href="#express-实现-serverless-的-react-服务端渲染">Express 实现 Serverless 的 React 服务端渲染</a><ul>
<li><a href="#serverless-express">Serverless + Express</a></li>
<li><a href="#express-react-进行服务端渲染">Express + React 进行服务端渲染</a></li>
</ul></li>
<li><a href="#serverless-的微信公共平台后台">Serverless 的微信公共平台后台</a><ul>
<li><a href="#创建-serverless-服务">创建 Serverless 服务</a></li>
<li><a href="#引入-node-wechat">引入 node-wechat</a></li>
<li><a href="#配置-app_id-和-token-等">配置 APP_ID 和 TOKEN 等</a></li>
<li><a href="#配置-route-53-与-api-gateway">配置 Route 53 与 API Gateway</a></li>
<li><a href="#添加微信公众平号服务">添加微信公众平号服务</a></li>
<li><a href="#部署">部署</a></li>
</ul></li>
<li><a href="#基于-kinesis-streams-的数据流分析">基于 Kinesis Streams 的数据流分析</a><ul>
<li><a href="#amazon-kinesis-streams">Amazon Kinesis Streams</a></li>
<li><a href="#serverless-kinesis-streams">Serverless + Kinesis Streams</a></li>
</ul></li>
<li><a href="#serverless-数据分析kinesis-firehose-持久化数据到-s3">Serverless 数据分析,Kinesis Firehose 持久化数据到 S3</a><ul>
<li><a href="#serverless-kinesis-firehose-代码">Serverless Kinesis Firehose 代码</a></li>
<li><a href="#安装及测试">安装及测试</a></li>
</ul></li>
<li><a href="#创建邮件发送-api">创建邮件发送 API</a><ul>
<li><a href="#serverless-email-发送">Serverless Email 发送</a></li>
<li><a href="#serverless-email-发送测试">Serverless Email 发送测试</a></li>
</ul></li>
<li><a href="#创建自己的-serverless-短链服务"><span>创建自己的 Serverless 短链服务</span></a><ul>
<li><a href="#代码逻辑">代码逻辑</a></li>
<li><a href="#创建首页">创建首页</a></li>
<li><a href="#生成短链">生成短链</a><ul>
<li><a href="#解析出提交表单中的链接">解析出提交表单中的链接</a></li>
<li><a href="#根据-url-生成对应的短链">根据 URL 生成对应的短链</a></li>
<li><a href="#存储到-dynamo-数据库中">存储到 Dynamo 数据库中。</a></li>
<li><a href="#返回短链给用户">返回短链给用户</a></li>
</ul></li>
<li><a href="#重定向短链">重定向短链</a></li>
<li><a href="#部署及测试短链服务">部署及测试短链服务</a></li>
</ul></li>
<li><a href="#结合-auth0-的登录授权系统">结合 Auth0 的登录、授权系统</a><ul>
<li><a href="#serverless-auth0-前端代码">Serverless Auth0 前端代码</a></li>
<li><a href="#serverless-auth0-后台代码">Serverless Auth0 后台代码</a><ul>
<li><a href="#serverless.yml-配置">serverless.yml 配置</a></li>
</ul></li>
<li><a href="#配置及部署">配置及部署</a><ul>
<li><a href="#清理">清理</a></li>
</ul></li>
<li><a href="#结论">结论</a></li>
</ul></li>
<li><a href="#在本地部署与调试">在本地部署与调试</a><ul>
<li><a href="#serverless-offline">serverless-offline</a></li>
<li><a href="#本地搭建-serverless-offline-与-dynamodb-环境">本地搭建 serverless-offline 与 DynamoDB 环境</a></li>
<li><a href="#本地测试-serverless-offline-与-dynamodb">本地测试 serverless-offline 与 DynamoDB</a></li>
</ul></li>
<li><a href="#如何编写-serverless-应用的测试">如何编写 Serverless 应用的测试</a><ul>
<li><a href="#serverless-应用的测试">Serverless 应用的测试</a></li>
<li><a href="#步骤">步骤</a><ul>
<li><a href="#创建测试">创建测试</a></li>
<li><a href="#运行测试">运行测试</a></li>
<li><a href="#更准确的测试">更准确的测试</a></li>
</ul></li>
<li><a href="#结论-1">结论</a></li>
</ul></li>
<li><a href="#多个语言运行环境">多个语言运行环境</a><ul>
<li><a href="#serverless-多个语言运行环境">Serverless 多个语言运行环境</a></li>
<li><a href="#部署及测试">部署及测试</a></li>
</ul></li>
<li><a href="#serverless-开发物联网应用">Serverless 开发物联网应用</a><ul>
<li><a href="#serverless-框架安装服务">Serverless 框架安装服务</a></li>
<li><a href="#部署-aws-iot-serverless-服务">部署 AWS IoT Serverless 服务</a></li>
<li><a href="#查看日志">查看日志</a></li>
</ul></li>
<li><a href="#使用-warmup-插件保活避免应用冷启动">使用 warmup 插件保活,避免应用冷启动</a><ul>
<li><a href="#使用-serverless-plugin-warmup-保持唤醒">使用 serverless-plugin-warmup 保持唤醒</a></li>
<li><a href="#部署及测试-1">部署及测试</a></li>
<li><a href="#结论-2">结论</a></li>
</ul></li>
<li><a href="#定时执行任务">定时执行任务</a><ul>
<li><a href="#serverless-定时任务">Serverless 定时任务</a><ul>
<li><a href="#rate-表达式">rate 表达式</a></li>
<li><a href="#cron-表达式">cron 表达式</a></li>
</ul></li>
<li><a href="#部署-1">部署</a></li>
</ul></li>
<li><a href="#使用-simulate-插件在本地运行-lambda">使用 Simulate 插件在本地运行 Lambda</a><ul>
<li><a href="#serverless-plugin-simulate-插件">serverless-plugin-simulate 插件</a></li>
<li><a href="#安装-serverless-plugin-simulate-及环境">安装 serverless-plugin-simulate 及环境</a></li>
<li><a href="#运行及测试">运行及测试</a></li>
</ul></li>
<li><a href="#使用-graphql-实现更好的-api">使用 GraphQL 实现更好的 API</a><ul>
<li><a href="#graphql-hello-world">GraphQL hello, world</a></li>
<li><a href="#更复杂的示例">更复杂的示例</a><ul>
<li><a href="#graphql-修改-dymanodb-的值">GraphQL 修改 DymanoDB 的值</a></li>
</ul></li>
</ul></li>
<li><a href="#serverless-应用示例前端错误日志及事件收集系统">Serverless 应用示例:前端错误日志及事件收集系统</a><ul>
<li><a href="#molog-使用">Molog 使用</a></li>
<li><a href="#serverless-错误收集系统架构设计">Serverless 错误收集系统架构设计</a><ul>
<li><a href="#架构设计">架构设计</a></li>
</ul></li>
<li><a href="#molog-系统实现">Molog 系统实现</a><ul>
<li><a href="#存储日志和事件">1. 存储日志和事件</a></li>
<li><a href="#读取日志">2. 读取日志</a></li>
</ul></li>
<li><a href="#问题">问题</a></li>
</ul></li>
<li><a href="#serverless-应用示例二维码生成">Serverless 应用示例:二维码生成</a><ul>
<li><a href="#总览">总览</a></li>
<li><a href="#生成应用">生成应用</a></li>
<li><a href="#配置资源">配置资源</a></li>
<li><a href="#生成二维码">生成二维码</a></li>
<li><a href="#上传-aws-s3">上传 AWS S3</a></li>
<li><a href="#优化二维码">优化二维码</a></li>
<li><a href="#部署及测试-2">部署及测试</a></li>
</ul></li>
<li><a href="#serverless-应用示例serverless-密码管理器">Serverless 应用示例:Serverless 密码管理器</a><ul>
<li><a href="#取个密码">取个密码</a><ul>
<li><a href="#特定平台-固定密码">特定平台 + 固定密码</a></li>
<li><a href="#密码表-特定平台-固定密码">密码表 + 特定平台 + 固定密码</a></li>
<li><a href="#随机密码-固定密码">随机密码 + 固定密码</a></li>
<li><a href="#密码管理器">密码管理器</a></li>
<li><a href="#开启-mfa">开启 MFA</a></li>
</ul></li>
<li><a href="#serverless-架构下的密码管理器mopass">Serverless 架构下的密码管理器:MoPass</a><ul>
<li><a href="#在终端中使用-cli">在终端中使用 CLI</a></li>
<li><a href="#通过-chrome-插件中使用">通过 Chrome 插件中使用</a></li>
</ul></li>
<li><a href="#结论-3">结论</a></li>
</ul></li>
<li><a href="#serverless-框架-openwhisk-开发指南">Serverless 框架 OpenWhisk 开发指南</a><ul>
<li><a href="#openwhisk-简介">OpenWhisk 简介</a></li>
<li><a href="#macos-搭建-openwhisk-环境">macOS 搭建 OpenWhisk 环境</a></li>
<li><a href="#serverless-框架-openwhisk-开发指南使用-serverless-framework-开发-openwhisk-应用">Serverless 框架 OpenWhisk 开发指南:使用 Serverless Framework 开发 OpenWhisk 应用</a><ul>
<li><a href="#serverless-openwhisk">Serverless OpenWhisk</a></li>
<li><a href="#测试服务">测试服务</a></li>
</ul></li>
<li><a href="#serverless-框架-openwhisk-开发指南使用-node.js-编写-hello-world">Serverless 框架 OpenWhisk 开发指南:使用 Node.js 编写 hello, world</a></li>
<li><a href="#配置-openwhisk-客户端">配置 OpenWhisk 客户端</a><ul>
<li><a href="#获取-cli">获取 CLI</a></li>
<li><a href="#为客户端获取密钥">为客户端获取密钥</a></li>
<li><a href="#在客户端设置">在客户端设置</a></li>
<li><a href="#openwhisk-node.js-hello-world">OpenWhisk Node.js Hello, world</a></li>
<li><a href="#openwhisk-node.js-传递参数">OpenWhisk Node.js 传递参数</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
<h1 id="serverless-架构应用开发指南">Serverless 架构应用开发指南</h1>
<blockquote>
<p>Serverless 架构是指大量依赖第三方服务(也叫做后端即服务,即“BaaS”)或暂存容器中运行的自定义代码(函数即服务,即“FaaS”)的应用程序,函数是无服务器架构中抽象语言运行时的最小单位。在这种架构中,我们并不看重运行一个函数需要多少 CPU 或 RAM 或任何其他资源,而是更看重运行函数所需的时间,我们也只为这些函数的运行时间付费。</p>
</blockquote>
<p><code>注意事项</code></p>
<p>在本系列的文章中,主要采用了 Serverless Framework 来简化开发和部署流程。</p>
<blockquote>
<p>Serverless Framework是无服务器应用框架和生态系统,旨在简化开发和部署AWS Lambda应用程序的工作。Serverless Framework 作为 Node.js NPM 模块提供,填补了AWS Lambda 存在的许多缺口。它提供了多个样本模板,可以迅速启动 AWS Lambda 开发。</p>
</blockquote>
<h1 id="serverless-架构">Serverless 架构</h1>
<blockquote>
<p>花了 1000G,我终于弄清楚了 Serverless 是什么?</p>
</blockquote>
<p>在过去的 24 小时,我通过微信公众号的『电子书』一事,大概处理了 8000 个请求:</p>
<figure>
<img src="images/counts.png" alt="Serverless 请求统计" /><figcaption>Serverless 请求统计</figcaption>
</figure>
<p>大部分的请求都是在 200ms 内完成的,而在最开始的请求潮里(刚发推送的时候,十分钟里近 1500 个请求),平均的响应时间都在 50ms 内。</p>
<figure>
<img src="images/times.png" alt="Serverless 请求时间" /><figcaption>Serverless 请求时间</figcaption>
</figure>
<p>这也表明了,Serverless 相当的可靠。显然,当请求越多的时候,响应时间越快,这简直有违常理——一般来说,随着请求的增加,响应时间会越来越慢。</p>
<p>毫无疑问,在最近的几年里,微服务渐渐成为了一个相当流行的架构风格。微服务大致从 2014 年起,开始流行开来,如下图所示:</p>
<figure>
<img src="images/microservice-compare-serverless.png" alt="microservices vs serverless" /><figcaption>microservices vs serverless</figcaption>
</figure>
<p>而 Serverless 是从 2016 年起,开始受到开发者的关注。并且从其发展趋势来看,它大有可能在两年后,拥有今天微服务一样的地位。可见,它是一个相当具有潜力的架构。</p>
<h2 id="什么是-serverless-架构">什么是 Serverless 架构??</h2>
<p>为了弄清 Serverless 究竟是什么东西,Serverless 到底是个什么,我使用 Serverless 尝试了一个又一个示例,我自己也做了四五个应用,总算是对 Serverelss 有了一个大致上的认识。</p>
<h3 id="虚拟化与隔离">虚拟化与隔离</h3>
<blockquote>
<p>开发人员为了保证开发环境的正确(即,这个 Bug 不是环境因素造成的),想出了一系列的隔离方式:虚拟机、容器虚拟化、语言虚拟机、应用容器(如 Java 的 Tomcat)、虚拟环境(如 Python 中的 virtualenv),甚至是独立于语言的 DSL。<a href="#fn1" class="footnote-ref" id="fnref1"><sup>1</sup></a></p>
</blockquote>
<p>从最早的物理服务器开始,我们都在不断地抽象或者虚拟化服务器。</p>
<figure>
<img src="images/server-growth.jpg" alt="服务器发展" /><figcaption>服务器发展</figcaption>
</figure>
<ul>
<li>我们使用 XEN、KVM等虚拟化技术,隔离了硬件以及运行在这之上的操作系统。</li>
<li>我们使用云计算进一步地自动管理这些虚拟化的资源。</li>
<li>我们使用 Docker 等容器技术,隔离了应用的操作系统与服务器的操作。</li>
</ul>
<p>现在,我们有了 Serverless,我们可以隔离操作系统,乃至更底层的技术细节。</p>
<h3 id="为什么是花了-1000g">为什么是花了 1000G ?</h3>
<p>现在,让我简单地解释『花了 1000G,我终于弄清楚了 Serverless 是什么?』这句话,来说说 Serverless 到底是什么鬼?</p>
<p>在实践的过程中,我采用的是 AWS Lambda 作为 Serverless 服务背后的计算引擎。AWS Lambda 是一种函数即服务(Function-as-a-Servcie,FaaS)的计算服务,简单的来说就是:开发人员直接编写运行在云上的函数、功能、服务。由云服务产商提供操作系统、运行环境、网关等一系列的基础环境,我们只需要关注于编写我们的业务代码即可。</p>
<p>是的,你没听错,我们只需要<strong>考虑怎么用代码提供价值即可</strong>。我们甚至连可扩展、蓝绿部署等一系列的问题都不用考虑,Amazon 优秀的运营工程师已经帮助我们打造了这一系列的基础设施。并且与传统的 AWS 服务一样,如 Elastic Compute Cloud(EC2),它们都是按流量算钱的。</p>
<p>那么问题又来了,它到底是怎么对一个函数收钱的。我在 Lambda 函数上运行一个 Hello, world 它会怎么收我的钱呢?</p>
<p>如果要对一个运行的函数收费,那么想必只有运行时间、CPU、内存占用、硬盘这几个条件。可针对于不同的需求,提供不同的 CPU 是一件很麻烦的事。对于代码来说,一个应用占用的硬盘空间几乎可以忽略不计。当然,这些应用会在你的 S3 上有一个备份。于是,诸如 AWS 采用的是运行时间 + 内存的计算方式。</p>
<table>
<thead>
<tr class="header">
<th>内存 (MB)</th>
<th>每个月的免费套餐秒数</th>
<th>每 100ms 的价格 (USD)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>128</td>
<td>3,200,000</td>
<td>0.000000208</td>
</tr>
<tr class="even">
<td>192</td>
<td>2,133,333</td>
<td>0.000000313</td>
</tr>
<tr class="odd">
<td>256</td>
<td>1,600,000</td>
<td>0.000000417</td>
</tr>
<tr class="even">
<td>…</td>
<td>…</td>
<td>…</td>
</tr>
<tr class="odd">
<td>1024</td>
<td>400,000</td>
<td>0.000001667</td>
</tr>
<tr class="even">
<td>…</td>
<td>…</td>
<td>…</td>
</tr>
</tbody>
</table>
<p>在运行程序的时候,AWS 会统计出一个时间和内存,如下所示:</p>
<pre><code>REPORT RequestId: 041138f9-bc81-11e7-aa63-0dbab83f773d Duration: 2.49 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 20 MB</code></pre>
<p>其中的 <code>Memory Size</code> 即是我们选用的套餐类型,Duration 即是运行的时间,Max Memory Used 是我们应用运行时占用的内存。根据我们的 Max Memory Used 数值及应用的计算量,我们可以很轻松地计算出我们所需要的套餐。</p>
<p>当然,选择不同大小的内存,也意味着选择不同功率的 CPU。</p>
<blockquote>
<p>在 AWS Lambda 资源模型中,您可以选择您想为函数分配的内存量,并按 CPU 功率和其他资源的比例进行分配。例如,选择 256MB 的内存分配至您的 Lambda 函数的 CPU 功率约是请求 128MB 内存的两倍,若选择 512MB 的内存,其分配的 CPU 功率约是一半。您可以在 128MB 到 1.5GB 的范围间以 64MB 的增量设置您的内存。</p>
</blockquote>
<p>因此,如果我们选用 1024M 的套餐,然后运行了 320 次,一共算是使用了 320G 的计算量。而其运行时间会被舍入到最近的 100ms,就算我们运行了 2.49ms,那么也是按 100ms 算的。那么假设,我们的 320 次计算都花了 1s,也就是 10×100ms,那么我们要支付的费用是:10×320×0.000001667=0.0053344刀,即使转成人民币也就是不到 4 毛钱的 0.03627392。</p>
<p>如果我们先用的是 128M 的套餐,那么运行了 2000 次,就是 200G 的计算量了。</p>
<p>如果我们先用的是 128M 的套餐,那么运行了 8000 次,就是 1000G 的计算量了。</p>
<p>不过如上表所示,AWS 为 Lambda 提供了一个免费套餐(无期限地提供给新老用户)包含每月 1M 免费请求以及每月 400 000 GB 秒的计算时间。这就意味着,在很长的时间里,我们一分钱都不用花。</p>
<h3 id="serverless-是什么">Serverless 是什么?</h3>
<p>而从上节的内容中,我们可以知道这么几点:</p>
<ul>
<li>在 Serverless 应用中,开发者只需要专注于业务,剩下的运维等工作都不需要操心</li>
<li>Serverless 是<strong>真正的按需使用</strong>,请求到来时才开始运行</li>
<li>Serverless 是按运行时间和内存来算钱的</li>
<li>Serverless 应用严重依赖于特定的云平台、第三方服务</li>
</ul>
<p>当然这些都是一些虚无缥缈地东西。</p>
<p>按 AWS 官方对于 Serverless 的介绍是这样的:</p>
<blockquote>
<p>服务器架构是基于互联网的系统,其中应用开发不使用常规的服务进程。相反,它们仅依赖于第三方服务(例如AWS Lambda服务),客户端逻辑和服务托管远程过程调用的组合。”<a href="#fn2" class="footnote-ref" id="fnref2"><sup>2</sup></a></p>
</blockquote>
<p>在一个基于 AWS 的 Serverless 应用里,应用的组成是:</p>
<ul>
<li>网关 API Gateway 来接受和处理成千上万个并发 API 调用,包括流量管理、授权和访问控制、监控等</li>
<li>计算服务 Lambda 来进行代码相关的一切计算工作,诸如授权验证、请求、输出等等</li>
<li>基础设施管理 CloudFormation 来创建和配置 AWS 基础设施部署,诸如所使用的 S3 存储桶的名称等</li>
<li>静态存储 S3 作为前端代码和静态资源存放的地方</li>
<li>数据库 DynamoDB 来存储应用的数据</li>
<li>等等</li>
</ul>
<p>以博客系统为例,当我们访问一篇博客的时候,只是一个 GET 请求,可以由 S3 为我们提供前端的静态资源和响应的 HTML。</p>
<figure>
<img src="images/serverless-spa-architecture.png" alt="Serverless SPA 架构" /><figcaption>Serverless SPA 架构</figcaption>
</figure>
<p>而当我们创建一个博客的时候:</p>
<ul>
<li>我们的请求先来到了 API Gateway,API Gateway 计费器 + 1</li>
<li>接着请求来到了 Lambda,进行数据处理,如生成 ID、创建时间等等,Lambda 计费器 + 1</li>
<li>Lambda 在计算完后,将数据存储到 DynamoDB 上,DynamoDB 计费器 + 1</li>
<li>最后,我们会生成静态的博客到 S3 上,而 S3 只在使用的时候按存储收费。</li>
</ul>
<p>在这个过程中,我们使用了一系列稳定存在的云服务,并且只在使用时才计费。由于这些服务可以自然、方便地进行调用,我们实际上只需要关注在我们的 Lambda 函数上,以及如何使用这些服务完成整个开发流程。</p>
<p>因此,Serverless 并不意味着没有服务器,只是服务器以特定功能的第三方服务的形式存在。</p>
<p>当然并不一定使用这些云服务(如 AWS),才能称为 Serverless。诸如我的同事在 《<a href="https://blog.jimmylv.info/2017-06-30-serverless-in-action-build-personal-reading-statistics-system/">Serverless 实战:打造个人阅读追踪系统</a>》,采用的是:IFTTT + WebTask + GitHub Webhook 的技术栈。它只是意味着,你所有的应用中的一部分服务直接使用的是第三方服务。</p>
<p>在这种情况下,系统间的分层可能会变成一个又一个的服务。原本,在今天主流的微服务设计里,每一个领域或者子域都是一个服务。而在 Serverless 应用中,这些领域及子域因为他们的功能,又可能会进一步切分成一个又一个 Serverless 函数。</p>
<figure>
<img src="images/mono-ms-sls.jpg" alt="更小的函数" /><figcaption>更小的函数</figcaption>
</figure>
<p>只是这些服务、函数比以往的粒度更加细致。</p>
<h3 id="事件驱动编程">事件驱动编程</h3>
<p>Serverless 的运行才计算,便意味着他是一种 “严格” 的事件驱动式计算。</p>
<blockquote>
<p>事件驱动编程(英语:Event-driven programming)是一种电脑程序设计模型。这种模型的程序运行流程是由用户的动作(如鼠标的按键,键盘的按键动作)或者是由其他程序的消息来决定的。相对于批处理程序设计(batch programming)而言,程序运行的流程是由程序员来决定。批量的程序设计在初级程序设计教学课程上是一种方式。然而,事件驱动程序设计这种设计模型是在交互程序(Interactive program)的情况下孕育而生的。<a href="#fn3" class="footnote-ref" id="fnref3"><sup>3</sup></a></p>
</blockquote>
<p>这也意味着,系统在编程模型上有着巨大的改变。在我们编写 GUI 程序,如桌面程序、Web 前端应用,我们都通过监听用户对按钮、链接等组件操作,才开始相应的处理逻辑。这和 Serverless 是相似的,只在用户使用的时候,才会对应用户的行为进行响应。</p>
<h2 id="serverless-的优势">Serverless 的优势</h2>
<p>在我使用 Serverless Framework 开发 AWS Serverless 应用的过程中,最方便的莫过于,第一次部署和第二次、第三次部署没有什么区别。只需要执行 <code>serverless deploy</code>,几分钟后,我们代码就运行在线上。如果是一个传统的 AWS 应用,我需要 SSH 到我的服务器上部署,这样才能写好我的自动部署脚本。除此,我还需要担忧这个过程中,有哪些用户有使用。</p>
<p>除了,我觉得的部署方便,还有就是价格合理。我的 AWS EC2 实例上运行着我的博客、以及其他的一些网络。然而,我那 PV 只有 500 左右的博客,大部分时间都是在空转。便觉得有些浪费,可是运行才收费的 Serverless 就不会有这样的问题。可以让我大胆地去使用这些服务。当然了,还有其它一些显著的优势。</p>
<h3 id="降低启动成本">降低启动成本</h3>
<p>当我们作为一家公司开发一个 Web 应用时,在开发的时候,我们需要版本管理服务器、持续集成服务器、测试服务器、应用版本管理仓库等作为基础的服务。线上运行的时候,为了应对大量的请求,我们需要一个好的数据库服务器。当我们的应用面向了普通的用户时,我们需要:</p>
<ul>
<li>邮件服务,用于发送提醒、注册等服务</li>
<li>短信服务(依国家实名规定),用于注册、登录等用户授权操作</li>
</ul>
<p>对于大公司而言,这些都是现成的基础设施。可对于新创企业来说,这都是一些启动成本。</p>
<h4 id="减少运营成本">减少运营成本</h4>
<p>对于初创公司来说,他们没有基础设施,也没有财力,也可能没有能力去建设基础设施。采用云服务往往是最好的选择,可以节省大量的资金。他们可以将注意力放在:创造对用户有价值的产品上。如果一家创业公司采用云服务,而不是自己搭建服务器。那么,他就会拥有<strong>更多的时间</strong>开发业务功能,而不是关注在这些。只需要为运行时的软件付钱。</p>
<p>而采用<strong>函数计算</strong>的 Serverless 与云服务器最大的不同之处在于:<strong>云服务器需要一直运行,而函数计算是按需计算</strong>。按需计算就意味着,在请求到来的时候,才运行函数。没有请求的时候,是不算钱的。</p>
<p>项目初期,其用户数往往是缓慢增长的,而我们在选择服务器的时候,往往会依可能出现的用户来估算。在这个时候,往往会浪费一些不必要的成本。不过,就算用户突然间爆发,Serverless 应用也可以轻松处理。只需要修改一下数据库配置,再重新部署一份。</p>
<h4 id="降低开发成本">降低开发成本</h4>
<p>一个成功的 Serverless 服务供应商,应该能提供一系列的<strong>配套服务</strong>。这意味着,你只需要在配置文件上写下,这个数据库的表名,那么我们的数据就会存储到对应的数据库里。甚至于,**如果一个当服务提供者提供一系列的函数计算模板,那么我们只需要写好我们的配置即可。这一系列的东西都可以自动、高效的完成。</p>
<p>在这种情况下,<strong>使用某一个云服务,就会调用某一个系统自带的 API 一样简单</strong>。</p>
<p>当然,将应用设计成无状态应用,对于早期的系统,可能是一种挑战。除此,诸如 AWS 这样庞大的系统,对于新手程序员来说,也不能容易消化掉的一个系统。</p>
<h3 id="实现快速上线">实现快速上线</h3>
<p>对于一个 Web 项目来说,启动一个项目需要一系列的 hello, world。当我们在本地搭建环境的时候,是一个 hello, world,当我们将程序部署到开发环境时,也是一个部署相关的 hello, world。虽然看上去有些不同,但是总的来说,都是 it works!。</p>
<p>Serverless 在部署上的优势,使得你可以轻松地实现上线。</p>
<h4 id="更快的部署流水线">更快的部署流水线</h4>
<p>实际上,Serverless 应用之所以在部署上有优势,是因为其相当于<strong>内建自动化部署</strong>——我们在开发应用的时候,已经在不断地增强部署功能。</p>
<p>在我们日常的开发中,为了实现自动化部署,我们需要先手动部署,以设计出一个相关无错的部署配置,如 Docker 的 Dockerfile,又或者是 Ansible 的 playbook。除此,我们还需要设计好蓝绿发布等等的功能。</p>
<p>而在函数计算、Serverless 应用里,这些都是由供应商提供的功能。每次我们写完代码,只需要运行一下:<code>sls deploy</code> 就足够了。在诸如 AWS Lambda 的函数计算里,函数一般在上传后几秒钟内,就能做好调用准备。</p>
<p>这就意味着,当我们和日常一样,使用一个模板来开发我们的应用。我们就可以在 Clone 完代码后的几分钟内,完成第一次部署。</p>
<p>唯一的难点,可能是要选用什么配置类型的服务,如选用哪个级别吞吐量的 DynamoDB、哪个内存大小的 Lambda 计算。</p>
<h4 id="更快的开发速度">更快的开发速度</h4>
<p>由于 Serverless 服务提供者,已经准备好了一系列的基础服务。作为开发人员的我们,只需要关注于如何更好去实现业务,而非技术上的一些限制。</p>
<p>服务提供者已经向我们准备,并测试好了这一系列的服务。它们基本上是稳定、可靠的,不会遇上特别大的问题。事实上,当我们拥有足够强大的代码,如使用测试来保证健壮性,那么结合持续集成,我们就可以在 PUSH 代码的时候,直接部署到生产环境。当然,可能不需要这么麻烦,我们只需要添加一个 predeploy 的 hook,在这个 hook 里做一些自动测试的工作,就可以在本地直接发布新的版本。</p>
<p>这个过程里,我们并不需要考虑太多的发布事宜。</p>
<h3 id="系统安全性更高">系统安全性更高</h3>
<p>依我维护我博客的经验来看,要保持服务器一直运行不是一件容易的事。在不经意的时候,总会发现有 Cracker 在攻击你网站。我们需要防范不同类型的攻击,如在我的服务器里一直有黑客在尝试密码登录,可是我的博客的服务器是要密钥才能登录的。在一次神奇的尝试登录攻击后,我的 SSH 守护进程崩溃了。这意味着,我只能从 EC2 后台重启服务器。</p>
<p>有了 Serverless,我不再需要担心有人尝试登录系统,因为我都不知道怎么登录服务器。</p>
<p>我不再需要考虑系统底层安全问题,每次登录 AWS EC2,我总需要更新一遍软件;每当我看到某个软件有漏洞时,如之前的 OpenSSH,我就登录上去看一下版本,更新一下软件。真 TM 费时又费力,还没有一点好处。</p>
<p>唯一需要担心的,可能是有人发起 DDOS 攻击。而根据<a href="https://thenewstack.io/zombie-toasters-eat-startup/">Could Zombie Toasters DDoS My Serverless Deployment?</a>的计算,每百万的请求,大概是 0.2 刀,每小时 360000000 个请求,也就 72 刀。</p>
<h3 id="适应微服务架构">适应微服务架构</h3>
<p>如我们所见在最近几年里看到的那样,微服务并没有大量地替换掉单体应用——毕竟使用新的架构来替换旧的系统,在业务上的价值并不大。因此,对于很多企业来说,并没有这样的强烈需求及紧迫性。活着,才是一件更紧迫的事。</p>
<p>而 Serverless 天生就与微服务架构是<strong>相辅相成</strong>的。一个 Serverless 应用拥有自己的网关、数据库、接口,你可还以使用自己喜欢的语言(受限于服务提供者)来开发服务。换句话来说,在这种情形下,一个 Serverless 可能是一个完美的微服务实例。</p>
<p>在可见的一二年里,Serverless 将替换到某些系统中的一些组件、服务。</p>
<h3 id="自动扩展能力">自动扩展能力</h3>
<p>Serverless 的背后是 诸如 AWS Lambda 这样的 FaaS(Function as a Services)。</p>
<p>对于传统应用来说,要应对更多的请求的方式,就是部署更多的实例。然而,这个时候往往已经来不及了。而对于 FaaS 来说,我们并不需要这么做,FaaS 会自动的扩展。它可以在需要时尽可能多地启动实例副本,而不会发生冗长的部署和配置延迟。</p>
<p>这依赖于我们的服务是无状态的,我们才能次无忌惮地不断运行起新的实例。</p>
<h2 id="serverless-的问题">Serverless 的问题</h2>
<p>作为一个运行时,才启动的应用来说,Serverless 也存在着一个个我们所需要的问题。</p>
<h3 id="不适合长时间运行应用">不适合长时间运行应用</h3>
<p>Serverless 在请求到来时才运行。这意味着,当应用不运行的时候就会进入 “休眠状态”,下次当请求来临时,应用将会需要一个启动时间,即<strong>冷启动</strong>。这个时候,可以结合 CRON 的方式或者 CloudWatch 来定期唤醒应用。</p>
<p>如果你的应用需要一直长期不间断的运行、处理大量的请求,那么你可能就不适合采用 Serverless 架构。在这种情况下,采用 EC2 这样的云服务器往往是一种更好的选择。因为 EC2 从价格上来说,更加便宜。</p>
<p>引用 <a href="https://www.zhihu.com/people/lu-zou-36">Lu Zou</a> 在 《<a href="https://zhuanlan.zhihu.com/p/31122433">花了 1000G,我终于弄清楚了 Serverless 是什么(上):什么是 Serverless 架构?</a>》上的评论:</p>
<blockquote>
<p>EC2 相当于你买了一辆车,而 Lambda 相当于你租了你一辆车。</p>
</blockquote>
<p>长期租车的成本肯定比买车贵,但是你就少掉了一部分的维护成本。因此,这个问题实际上是一个值得深入计算的问题。</p>
<h3 id="完全依赖于第三方服务">完全依赖于第三方服务</h3>
<p>是的,当你决定使用某个云服务的时候,也就意味着你可能走了一条不归路。在这种情况下,只能将不重要的 API 放在 Serverless 上。</p>
<p>当你已经有大量的基础设施的时候,Serverless 对于你来说,并不是一个好东西。当我们采用 Serverless 架构的时候,我们就和特别的服务供应商绑定了。我们使用了 AWS 家的服务,那么我们再将服务迁到 Google Cloud 上就没有那么容易了。</p>
<p>我们需要修改一下系列的底层代码,能采取的应对方案,便是建立隔离层。这意味着,在设计应用的时候,就需要:</p>
<ul>
<li>隔离 API 网关</li>
<li>隔离数据库层,考虑到市面上还没有成熟的 ORM 工具,让你即支持 Firebase,又支持 DynamoDB</li>
<li>等等</li>
</ul>
<p>这些也将带给我们一些额外的成本,可能<strong>带来的问题会比解决的问题多</strong>。</p>
<h3 id="冷启动时间">冷启动时间</h3>
<p>如上所说,Serverless 应用存在一个冷启动时间的问题。</p>
<p>据 New Relic 官方博客《<a href="https://blog.newrelic.com/2017/01/11/aws-lambda-cold-start-optimization/">Understanding AWS Lambda Performance—How Much Do Cold Starts Really Matter?</a>》称,AWS Lambda 的冷启动时间。</p>
<figure>
<img src="./images/aws-lambda-monitoring-functions-chart.png" alt="AWS 启动时间" /><figcaption>AWS 启动时间</figcaption>
</figure>
<p>又或者是我之前统计的请求响应时间:</p>
<figure>
<img src="images/times.png" alt="Serverless 请求时间" /><figcaption>Serverless 请求时间</figcaption>
</figure>
<p>尽管这个冷启动时间大部分情况下,可以在 50ms 以内。而这是对于 Node.js 应用来说,对于拥有虚拟机的 Java 和 C# 可能就没有那么幸运了。</p>
<h3 id="缺乏调试和开发工具">缺乏调试和开发工具</h3>
<p>当我使用 Serverless Framework 的时候,遇到了这样的问题:缺乏调试和开发工具。后来,我发现了 serverless-offline、dynamodb-local 等一系列插件之后,问题有一些改善。</p>
<p>然而,对于日志系统来说,这仍然是一个艰巨的挑战。</p>
<p>每次你调试的时候,你需要一遍又一遍地上传代码。而每次上传的时候,你就好像是在部署服务器。然后 Fuck 了,我并不能总是快速地定位出问题在哪。于是,我修改了一下代码,添加了一行 <code>console.log</code>,然后又一次地部署了下代码。问题解决了,挺好的,我删了一下 <code>console.log</code>,然后又一次地部署了下代码。</p>
<p>后来,我学乖了,找了一个类似于 log4j 这样的可以分级别纪录日志的 Node.js 库 <code>winston</code>。它可以支持 error、warn、info、verbose、debug、silly 六个不同级别的日志。</p>
<h3 id="构建复杂">构建复杂</h3>
<p>Serverless 很便宜,但是这并不意味着它很简单。</p>
<p>早先,在知道 AWS Lambda 之后,我本来想进行一些尝试。但是 CloudForamtion 让我觉得太难了,它的配置是如此的复杂,并且难以阅读及编写(JSON 格式)。</p>
<p>考虑到 CloudForamtion 的复杂度,我是在接触了 Serverless Framework 之后,才重新燃起了一些信心。</p>
<p>Serverless Framework 的配置更加简单,采用的是 YAML 格式。在部署的时候,Serverless Framework 会根据我们的配置生成 CloudForamtion 配置。</p>
<p>在那篇《<a href="https://www.phodal.com/blog/serverless-development-guide-use-kinesis-firehose-stream-data-s3/">Kinesis Firehose 持久化数据到 S3</a>》想着的数据统计文章里,我们介绍了 Serverless 框架的配置。与一般的 Lambda 配置来说,这里的配置就稍微复杂一些。然而,这也并非是一个真正用于生产的配置。我的意思是,真实的应用场景远远比这复杂。</p>
<h3 id="语言版本落后">语言版本落后</h3>
<p>在 Node.js 6 出来的时候,AWS Lambda 只支持 Node.js 4.3.2;在 Node.js 9.0 出来的时候,AWS Lambda 支持到 6.10.3。</p>
<p>如下是 AWS Lambda 支持以下运行时版本:</p>
<ul>
<li>Node.js – v4.3.2 和 6.10.3</li>
<li>Java - Java 8</li>
<li>Python – Python 3.6 和 2.7</li>
<li>.NET 内核 – .NET 内核 1.0.1 (C#)</li>
</ul>
<p>对于 Java 和 Python 来说,他们的版本上可能基本都是够用的,我不知道 C# 怎么样。但是 Node.js 的版本显然是有点老旧的,但是都 Node.js 9.2.0 了。不过,话说来说,这可能与版本帝 Chrome 带来的前端版本潮有一点关系。</p>
<h2 id="serverless-的适用场景">Serverless 的适用场景</h2>
<p>尽管 Serverless 在编写传统的 Web 应用上,有一定的缺点。然而,它的事件驱动及运行时计算,使得它在某些场景上相当的合适。</p>
<h3 id="发送通知">发送通知</h3>
<p>由我们在上一节中提到的,对于诸如 PUSH Notification、<a href="https://www.phodal.com/blog/serverless-development-guide-aws-simple-email-service/">邮件通知接口</a>、短信,这一类服务来说,他们都需要基础设施来搭建。并且,他们对实时性的要求相对没有那么高。</p>
<p>即使在时间上晚来几秒钟,用户还是能接受的。在我们所见到的短信发送的例子里,一般都会假设用户能在 60 秒内收到短信。因此,在这种时间 1s 的误差,用户也不会恼火的。而对于 APP 的消息推送而言,这种要求就更低了,用户反而不太希望能收到这样的推送</p>
<h3 id="webhook">WebHook</h3>
<p>当我们没有服务器,又想要一个 Webhook 来触发我们一系列的操作的时候。我们就可以考虑使用 Serverless,我们不需要一直就这么支付一个服务器的费用。通过 Serverless,我们就可以轻松完成这样的工作,并且节省大量的费用。</p>
<p>一个比较明显的例子,就如 <a href="https://www.phodal.com/blog/serverless-development-guide-create-github-hooks/">GitHub Hooks</a></p>
<blockquote>
<p>GitHub 上的 Webhook 允许我们构建或设置在 GitHub.com 上订阅某些事件的 GitHub 应用程序。当触发这些事件之一时,我们将向 webhook 配置的 URL 发送 HTTP POST 有效内容。</p>
</blockquote>
<p>比如说,当我们 PUSH 了代码,我们想触发我们的持续集成。这个时候,就可以通过一个 Webhook 来做这样的事情。</p>
<h3 id="轻量级-api">轻量级 API</h3>
<p>Serverless 特别适合于,轻量级快速变化地 API。</p>
<p>其实,我一直没有想到一个合适的例子。在我的假想里,一个 AutoSuggest 的 API 可能就是这样的 API,但是这种 API 在有些时候,往往会伴随着相当复杂的业务。</p>
<p>于是,便想举一个 Featrue Toggle 的例子,尽管有一些不合适。但是,可能是最有价值的部分。</p>
<h3 id="物联网">物联网</h3>
<p>当我们谈及物联网的时候,我们会讨论事件触发、传输协议、海量数据(数据存储、数据分析)。而有了 Serverless,那么再多的数据,处理起来也是相当容易的一件事。</p>
<p>对于一个物联网应用的服务端来说,系统需要收集来自各个地方的数据,并创建一个个 pipeline 来处理、过滤、转换这些数据,并将数据存储到数据库中。</p>
<p>对于硬件开发人员来说,对接不同的硬件,本身就是一种挑战。而直接使用诸如 AWS IoT 这样国,可以在某种程度上,帮助我们更好地开发出写服务端连接的应用。</p>
<p>同时,对于物联网应用的客户端来说,则需要从数据库抽取数据进行展示。这部分,可能算不上是一个挑战点。</p>
<h3 id="数据统计分析">数据统计分析</h3>
<p>数据统计本身只需要很少的计算量,但是生成图表,则可以定期生成。</p>
<p>在接收数据的时候,我们不需要考虑任何延时带来的问题。50~200 ms 的延时,并不会对我们的系统造成什么影响。</p>
<h3 id="trigger-及定时任务">Trigger 及定时任务</h3>
<p>对于哪些需要爬虫来抓取和生成的程序来说,Serverless <strong>可能</strong>是一个不错的舞台。</p>
<p>尽管,这样的工作也可以由云服务器来做,我们只需要定时的启动一下服务器。通过服务器中的自启动脚本来做相应的事,但是当我们完成了一系列的工作之后。我们需要将数据存储在一个远程的服务器上。而为了让系统中的其它应用,也能直接访问这些数据。那么,我们可能会考虑使用一个云数据库。这个时候,Serverless 应用看上去更具有吸引力。</p>
<p>在那篇《CRON 定时执行 Lambda 任务》中,我们也可以看到 AWS Lambda 可以支持 Lambda 计算,定时启动服务,并计算。</p>
<h3 id="精益创业">精益创业</h3>
<figure>
<img src="images/launch-page.jpg" alt="Landing Page" /><figcaption>Landing Page</figcaption>
</figure>
<p>Serverless 的快速上线、开发,意味着它可以快速验证一个想法 MVP。如 Dropbox 在开始的时候,只创造了一个 Landing Page。作为一个想使用这个服务的用户,我们会在其中填上我们的邮箱。</p>
<p>而如果是使用 Serverless 来构建这样的应用,那么我们只需要创建一个静态页面,然后用一个 Serverless 服务来保存用户的邮箱到数据库中,如我在 GitHub 上的 <a href="https://github.com/phodal/serverless-landingpage">serverless-landingpage</a> 所做的那样。</p>
<h3 id="chat-机器人">Chat 机器人</h3>
<p>聊天机器人,也是一个相当好的应用场景。</p>
<p>But,由于国内的条件限制(信息监管),这并不是一件容易的事。因此,从渠道(如微信、blabla)上,都在尽可能地降低这方面的可能性。</p>
<p>但是,我们还可以做一个微信公众号的服务。当用户输入一个关键词时,做出相应的回复,这实质上和聊天机器人是差不多的。只需要结合《<a href="https://www.phodal.com/blog/serverless-development-guide-serverless-lambda-wechat-public-platform/">基于 Serverless 与 Lambda 的微信公共平台</a>》 就可以轻松实现,并实现快速上线。</p>
<h2 id="其它">其它</h2>
<h3 id="迁移方案">迁移方案</h3>
<p>Express 应用示例</p>
<h3 id="serverless-framework">Serverless Framework</h3>
<blockquote>
<p>Serverless Framework是无服务器应用框架和生态系统,旨在简化开发和部署AWS Lambda应用程序的工作。Serverless Framework 作为 Node.js NPM 模块提供,填补了AWS Lambda 存在的许多缺口。它提供了多个样本模板,可以迅速启动 AWS Lambda 开发。</p>
</blockquote>
<h3 id="apex">Apex</h3>
<blockquote>
<p>Apex可以轻松地构建、部署和管理AWS Lambda功能。通过节点可使用由AWS Lambda(如Golang)所不支持的语言,js shim注入到构建中,为测试功能、回滚部署、查看度量、跟踪日志、连接到构建系统以及更多的功能提供了各种工作流相关工具。</p>
</blockquote>
<h3 id="apache-openwhisk">Apache OpenWhisk</h3>
<blockquote>
<p>OpenWhisk是一个分布式的、事件驱动的计算服务。OpenWhisk运行应用程序逻辑,以应对事件或直接通过HTTP调用网络或移动应用。</p>
</blockquote>
<h1 id="serverless-的-hello-world">Serverless 的 hello, world</h1>
<p>原文链接:<a href="https://www.phodal.com/blog/serverless-development-guid-serverless-framework-hello-world/">Serverless 应用开发指南:serverless 的 hello, world</a></p>
<p>在翻译了几篇 serverless 与物联网相关的文章之后,我开始想着好好掌握一下 serverless 的相关知识。</p>
<p>我对于 serverless 的第一认知是:<strong>Serverless 是由一堆云服务构建后端服务的,如存储、计算、授权都是由不同的服务来构建的。</strong>而作为一个开发人员,我们所要做的就是了解如何搭配不同的云服务。</p>
<p>因此,在进行更多的定义之前,我打算先熟悉一下 serverless,以便于我更好地了解什么是 serverless 应用开发。</p>
<h2 id="serverless-框架-hello-world">Serverless 框架 hello, world</h2>
<p>考虑到直接使用 aws lambda 编写 serverless,对于我这样的新手相当的有挑战性。于是,我便先选择了 Serverless 框架,GitHub: https://github.com/serverless/serverless。</p>
<p>先让我们按官网的 demo,进行实验。开始之前,除了拥有一台电脑,你还需要有一个 AWS 账号。AWS 提供一年的免费试用,你所需要做的就是办一张支持 visa 的信用卡。</p>
<h3 id="一安装-serverless-框架">一、安装 serverless 框架</h3>
<pre><code>npm install -g serverless</code></pre>
<p>或者,和我一样使用:</p>
<pre><code>yarn global add serverless</code></pre>
<h3 id="二设置-aws-凭证">二、设置 aws 凭证。</h3>
<p>1.登录 AWS 账号,然后点击进入 IAM (即,Identity & Access Management)。</p>
<p>2.点击用户,然后添加用户,如 serveless-admin,并在『选择 AWS 访问类型』里,勾上<strong>编程访问</strong>。</p>
<figure>
<img src="images/enable-programming.png" alt="编程访问 serverless" /><figcaption>编程访问 serverless</figcaption>
</figure>
<p>3.点击<strong>下一步权限</strong>,选择『直接附加现有策略』,输入<strong>AdministratorAccess</strong>,然后创建用户。</p>
<p><code>注意</code>:由于是 <strong>AdministratorAccess</strong> 权限,所以不要泄漏你的密钥出去。</p>
<ol start="4" type="1">
<li>创建用户。随后,会生成<strong>访问密钥 ID</strong> 和 <strong>私有访问密钥</strong>。请妥善保存好。</li>
</ol>
<p>然后导出证书,并使用 <code>serverless depoy</code> 保存到本地。</p>
<pre><code>export AWS_ACCESS_KEY_ID=<your-key-here>
export AWS_SECRET_ACCESS_KEY=<your-secret-key-here>
serverless deploy</code></pre>
<p>将会自动生成配置到 ~/.aws/credentials</p>
<p>或者,如官方的示例:</p>
<pre><code>serverless config credentials --provider aws --key AKIAIOSFODNN7EXAMPLE --secret wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY</code></pre>
<h3 id="三创建-hello-world-服务">三、创建 hello-world 服务</h3>
<pre><code>serverless create --template aws-nodejs --path hello-world</code></pre>
<pre><code>Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/fdhuang/learing/serverless-guide/hello-world"
_______ __
| _ .-----.----.--.--.-----.----| .-----.-----.-----.
| |___| -__| _| | | -__| _| | -__|__ --|__ --|
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v1.23.0
-------'
Serverless: Successfully generated boilerplate for template: "aws-nodejs"
(play-env)</code></pre>
<p>生成两个文件;</p>
<pre><code>├── handler.js
└── serverless.yml</code></pre>
<p>其中的 handler.js 的内容是:</p>
<pre><code>'use strict';
module.exports.hello = (event, context, callback) => {
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'Go Serverless v1.0! Your function executed successfully!',
input: event,
}),
};
callback(null, response);
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event });
};</code></pre>
<p>而 <code>serverless.yml</code> 的内容,因为注释所有的内容,因此相当于是空的。</p>
<h3 id="四部署及测试">四、部署及测试:</h3>
<pre><code>$serverless deploy -v</code></pre>
<p>日志如下:</p>
<pre><code>Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (409 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
CloudFormation - UPDATE_IN_PROGRESS - AWS::CloudFormation::Stack - hello-world-dev
CloudFormation - CREATE_IN_PROGRESS - AWS::Logs::LogGroup - HelloLogGroup
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::Logs::LogGroup - HelloLogGroup
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_COMPLETE - AWS::Logs::LogGroup - HelloLogGroup
CloudFormation - CREATE_COMPLETE - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Function - HelloLambdaFunction
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Function - HelloLambdaFunction
CloudFormation - CREATE_COMPLETE - AWS::Lambda::Function - HelloLambdaFunction
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Version - HelloLambdaVersionPSzzisjnTvvYknuXwQOlAvdkQZ67qXYSvgoAi9T8W0
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Version - HelloLambdaVersionPSzzisjnTvvYknuXwQOlAvdkQZ67qXYSvgoAi9T8W0
CloudFormation - CREATE_COMPLETE - AWS::Lambda::Version - HelloLambdaVersionPSzzisjnTvvYknuXwQOlAvdkQZ67qXYSvgoAi9T8W0
CloudFormation - UPDATE_COMPLETE_CLEANUP_IN_PROGRESS - AWS::CloudFormation::Stack - hello-world-dev
CloudFormation - UPDATE_COMPLETE - AWS::CloudFormation::Stack - hello-world-dev
Serverless: Stack update finished...
Service Information
service: hello-world
stage: dev
region: us-east-1
stack: hello-world-dev
api keys:
None
endpoints:
None
functions:
hello: hello-world-dev-hello
Stack Outputs
HelloLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:706605665335:function:hello-world-dev-hello:1
ServerlessDeploymentBucketName: hello-world-dev-serverlessdeploymentbucket-bk066p5c9zgl</code></pre>
<p>然后,让我们来触发一下这个函数:</p>
<pre><code>$ serverless invoke -f hello -l</code></pre>
<p>服务器返回了下面的结果:</p>
<pre><code>{
"statusCode": 200,
"body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"
}
--------------------------------------------------------------------
START RequestId: 041138f9-bc81-11e7-aa63-0dbab83f773d Version: $LATEST
END RequestId: 041138f9-bc81-11e7-aa63-0dbab83f773d
REPORT RequestId: 041138f9-bc81-11e7-aa63-0dbab83f773d Duration: 2.49 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 20 MB</code></pre>
<p>这意味着,我们的第一个服务已经成功上线了。</p>
<p>我们也可以通过下面的命令来获取相应的日志:</p>
<pre><code>serverless logs -f hello -t</code></pre>
<h1 id="aws-lambda-动态编程返回-html">AWS Lambda 动态编程返回 HTML</h1>
<p>原文链接:<a href="https://www.phodal.com/blog/serverless-development-guide-nodejs-create-dymamic-html/">Serverless 应用开发指南: Node.js 编程返回动态 HTML</a></p>
<p>在我们进行 Serverless + SPA 应用开发之前,先看看官方的相应 DEMO。</p>
<pre><code>serverless install -u https://github.com/serverless/examples/tree/master/aws-node-serve-dynamic-html-via-http-endpoint -n node-serve-html</code></pre>
<p>然后执行部署</p>
<pre><code>serverless deploy</code></pre>
<p><code>serverless.yml</code> 文件,如下:</p>
<pre><code>service: node-serve-html
provider:
name: aws
runtime: nodejs4.3
functions:
landingPage:
handler: handler.landingPage
events:
- http:
method: get
path: landing-page</code></pre>
<p>对应的,我们的 <code>handler.js</code> 文件:</p>
<pre><code>'use strict';
module.exports.landingPage = (event, context, callback) => {
let dynamicHtml = '<p>Hey Unknown!</p>';
// check for GET params and use if available
if (event.queryStringParameters && event.queryStringParameters.name) {
dynamicHtml = `<p>Hey ${event.queryStringParameters.name}!</p>`;
}
const html = `
<html>
<style>
h1 { color: #73757d; }
</style>
<body>
<h1>Landing Page</h1>
${dynamicHtml}
</body>
</html>`;
const response = {
statusCode: 200,
headers: {
'Content-Type': 'text/html',
},
body: html,
};
// callback is sending HTML back
callback(null, response);
};</code></pre>
<p>上面的代码所做的就是,当我们对 <code>landing-page</code> 发出请求的时候,便执行上面的 <code>landingPage</code> 代码。然后返回对应的 HTML body、statusCode、headers。</p>
<p>相应的部署日志如下:</p>
<pre><code>..............................
Serverless: Stack update finished...
Service Information
service: node-serve-html
stage: dev
region: us-east-1
stack: node-serve-html-dev
api keys:
None
endpoints:
GET - https://uocym5fe3m.execute-api.us-east-1.amazonaws.com/dev/landing-page
functions:
landingPage: node-serve-html-dev-landingPage</code></pre>
<p>然后我们访问:<a href="https://uocym5fe3m.execute-api.us-east-1.amazonaws.com/dev/landing-page?name=phodal">https://uocym5fe3m.execute-api.us-east-1.amazonaws.com/dev/landing-page</a>,就会返回对应的 HTML,即:</p>
<pre><code>Landing Page
Hey phodal!</code></pre>
<h1 id="将网站部署到-s3-上">将网站部署到 S3 上</h1>
<p>原文链接:<a href="https://www.phodal.com/blog/serverless-development-guide-use-serverless-finch-deploy-s3-static-html/">Serverless 应用开发指南:使用 S3 部署静态网站</a></p>
<p>在尝试了使用 Router53 路由到 S3 后,并想试试能否使用 serverless 框架来上传静态内容。在探索官方的 DEMO 后,找到了一个 <code>serverless-finch</code> 插件可以做相应的事情。</p>
<pre><code>serverless create --template aws-nodejs s3-static-file s3-static-file</code></pre>
<h2 id="配置-serverless-finch">配置 serverless-finch</h2>
<p>官网的 <code>serverless-client-s3</code> 已经停止维护了,并推荐使用 <code>serverless-finch</code>。</p>
<p><code>serverless-finch</code> 的安装方式是:</p>
<pre><code>npm install --save serverless-finch</code></pre>
<p>默认的官网生成的项目,并没有 <code>package.json</code> 文件,需要手动执行 <code>npm inti</code>,再安装插件。</p>
<p>因此修改完后的 <code>package.json</code> 文件如下所示:</p>
<pre><code>{
"name": "s3-static-file",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Phodal Huang",
"license": "MIT",
"dependencies": {
"serverless-finch": "^1.1.1"
}
}</code></pre>
<p>在这个时候,我们需要按 serverless 框架的插件要求,添加如下的内容:</p>
<pre><code>plugins:
- serverless-finch</code></pre>
<p>并配置好我们的 S3 存储桶的名字,最后 <code>serverless.yml</code> 文件的内容如下所示:</p>
<pre><code>service: s3-static-file
plugins:
- serverless-finch
provider:
name: aws
runtime: nodejs6.10
custom:
client:
bucketName: wdsm.io</code></pre>
<p>我们配置的 S3 存储桶的名字是: <strong>wdsm.io</strong>,然后其使用 <code>client/dist</code> 文件来放置静态文件。</p>
<h2 id="静态内容">静态内容</h2>
<p>如我们的 <code>index.html</code> 文件的路径是: <code>client/dist/index.html</code>,对应的内容是:</p>
<pre><code><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WDSM.io</title>
</head>
<body>
<h1>WDSM</h1>
</body>
</html></code></pre>
<p>最后,执行 <code>serverless client deploy</code> 就可以部署我们的网站。</p>
<p><code>再次提醒</code>,这次我们用的是 <code>serverless client deploy</code>。</p>
<p>相应的过程日志如下所示:</p>
<pre><code>Serverless: Deploying client to stage "dev" in region "us-east-1"...
Serverless: Bucket wdsm.io exists
Serverless: Listing objects in bucket wdsm.io...
Serverless: Deleting all objects from bucket wdsm.io...
Serverless: Configuring website bucket wdsm.io...
Serverless: Configuring policy for bucket wdsm.io...
Serverless: Configuring CORS policy for bucket wdsm.io...
Serverless: Uploading file error.html to bucket wdsm.io...
Serverless: If successful this should be deployed at: https://s3.amazonaws.com/wdsm.io/error.html
Serverless: Uploading file index.html to bucket wdsm.io...
Serverless: If successful this should be deployed at: https://s3.amazonaws.com/wdsm.io/index.html</code></pre>
<p>由于配置了 Router53 指向了 S3,因此可以直接访问:<a href="http://wdsm.io/">http://wdsm.io/</a> 来看最后的内容。</p>
<p>并且,对应的删除命令也变成了:<code>serverless client remove</code>。</p>
<h1 id="为基于-s3-的网站支持-crud">为基于 S3 的网站支持 CRUD</h1>
<p>原文链接:<a href="https://www.phodal.com/blog/serverless-development-guide-use-s3-api-gateway-create-crud/">Serverless 应用开发指南:API Gateway + S3 + AWS Lambda 打造 CRUD</a></p>
<p>在前两篇文章《Serverless 应用开发指南: serverless 的 hello, world》和 《<a href="https://www.phodal.com/blog/serverless-guide-development-aws-iot-serverless-example/">Serverless 开发指南:AWS IoT 服务开发</a>》 里,我们简单地介绍了如何用 Serverless 和 AWS IoT 开发入门级的 Serverless 应用。</p>
<p>在这一篇文章里,我们将开始进入正式的应用开发领域里:一个 CRUD 示例。</p>
<p>原先,我考虑直接先使用 DynamoDB 进行实验,但是考虑到我之前误用 DynamoDB 被扣 500 刀,再追回来的经历。我决定先用 S3 练练手——主要是已经有一个成型的 DEMO。</p>
<h2 id="概念api-gateway-与-s3">概念:API Gateway 与 S3</h2>
<p>以下是来自官网对于 API Gateway 和 S3 的介绍:</p>
<blockquote>
<p>Amazon API Gateway 是一种完全托管的服务,可以帮助开发者轻松创建、发布、维护、监控和保护任意规模的 API。只需在 AWS 管理控制台中点击几下,您便可以创建可充当应用程序“前门”的 API,从后端服务访问数据、业务逻辑或功能,例如基于 Amazon Elastic Compute Cloud (Amazon EC2) 运行的工作负载、基于 AWS Lambda 运行的代码或任意 Web 应用。Amazon API Gateway 负责管理所有任务,涉及接受和处理成千上万个并发 API 调用,包括流量管理、授权和访问控制、监控以及 API 版本管理。Amazon API Gateway 没有最低费用或启动成本,您只需为收到的 API 调用和传输出去的数据量付费。</p>
</blockquote>
<blockquote>
<p>Amazon S3 将数据作为对象存储在被称为“存储桶”的资源中。您可以在一个存储桶中尽可能多地存储对象,并写入、读取和删除您的存储桶中的对象。对象大小最多可为 5 TB。</p>
</blockquote>
<p>简单地来说,API Gateway 就是那个 API gateway,即所有 API 请求的入口。而 S3 就存储内容的部分——可以视作为云盘。</p>
<h2 id="基于-s3-的-serverless-crud">基于 S3 的 Serverless CRUD</h2>
<p>为了使用 S3,我们需要引入 aws-sdk 库来帮助我们更好的编写 AWS 应用。接着,让我们引入这个服务:</p>
<pre><code>serverless install --url https://github.com/tscanlin/serverless-s3-crud</code></pre>
<p>然后,到目录中,安装依赖:</p>
<pre><code>cd serverless-s3-crud
npm install</code></pre>
<p>再执行部署:</p>
<pre><code>serverless deploy</code></pre>
<p>执行的时候,发现了:</p>
<pre><code> Serverless Error ---------------------------------------
An error occurred: MyBucket - form-response already exists.
Get Support --------------------------------------------
Docs: docs.serverless.com
Bugs: github.com/serverless/serverless/issues
Forums: forum.serverless.com
Chat: gitter.im/serverless/serverless
Your Environment Information -----------------------------
OS: darwin
Node Version: 6.11.0
Serverless Version: 1.23.0</code></pre>
<p>啊哈,这个 MyBucket 已经存在了,这意味着,我们需要改一个新的名字。打开 <code>serverless.yml</code> 文件,将其中的 16、22、72 行中的 from-response 改成你想要的名字,如 <code>phodal-serverless</code>。</p>
<p>以及** handlers 目录下的各个文件的 Bucket 名**。</p>
<p>serverless.yml 代码中的 <code>iamRoleStatements</code> 用于设置 serverless 的权限,Action 代码其所能进行的操作,Resource 则是相应的资源:</p>
<pre><code> iamRoleStatements:
- Effect: Allow
Action:
- s3:ListBucket
Resource: "arn:aws:s3:::phodal-serverless"
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:DeleteObject
Resource: "arn:aws:s3:::phodal-serverless/*"</code></pre>
<p>下面的代码则定义了,我们的资源,所使用的存储桶(BucketName)的名字:</p>
<pre><code>resources:
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: phodal-serverless
AccessControl: PublicReadWrite
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html</code></pre>
<p>然后再执行 <code>serverless deploy</code>,就会返回我们想要的结果及 API 地址:</p>
<pre><code>api keys:
None
endpoints:
POST - https://xc1iprfbsg.execute-api.us-east-1.amazonaws.com/dev/form-response/{id}
GET - https://xc1iprfbsg.execute-api.us-east-1.amazonaws.com/dev/form-response
GET - https://xc1iprfbsg.execute-api.us-east-1.amazonaws.com/dev/form-response/readAll
GET - https://xc1iprfbsg.execute-api.us-east-1.amazonaws.com/dev/form-response/{id}
PUT - https://xc1iprfbsg.execute-api.us-east-1.amazonaws.com/dev/form-response/{id}
DELETE - https://xc1iprfbsg.execute-api.us-east-1.amazonaws.com/dev/form-response/{id}
functions:
create: serverless-crud-s3-dev-create
list: serverless-crud-s3-dev-list
readAll: serverless-crud-s3-dev-readAll
readOne: serverless-crud-s3-dev-readOne
update: serverless-crud-s3-dev-update
delete: serverless-crud-s3-dev-delete</code></pre>
<p>上面列出了所有端口的 API 地址,</p>
<h2 id="上传原理">上传原理</h2>
<p>那么,它是怎么进行操作的呢,先看看 <code>serverless.yml</code> 文件中定义的 create 动作。</p>
<pre><code>functions:
create:
handler: handler.create
events:
- http:
path: form-response/{id}
method: post
cors: true</code></pre>
<p>对应了 <code>handler.js</code> 文件中的 create 方法:</p>
<pre><code>const Create = require('./handlers/create.js')
...
function makeResponse(error, result) {
const statusCode = error && error.statusCode || 200
return {
statusCode,
headers: {
"Access-Control-Allow-Origin" : "*"
},
body: JSON.stringify(result),