如何在 15 分鐘內構建一個無伺服器服務?
「無伺服器」(Serverless)這個詞已經流行了有一段時間了。
亞馬遜在2015年發布了AWS Lambda服務之後,出現了許多工具,利用這些工具只需幾個命令就可以建個無伺服器服務。與傳統的服務相比,無伺服器服務具有容易開發、容易部署、容易維護的特點。它們的性價比還非常高,特別是對於那些沒有太大流量的簡單服務。
什麼是無伺服器?
顧名思義,「無伺服器」就是說運行服務的時候不需要伺服器。準確地說,服務依然是跑在伺服器上的,但是作為開發者,你不需要考慮伺服器的問題。
以AWS Lambda為例,你可以寫一個「函數」部署到Lambda上,這個函數可以處理HTTP請求。AWS會跑一個伺服器,負責運行所有函數,每當你的HTTP endpoint被訪問時,Lambda就會調用你的函數來處理請求。但伺服器的事情完全不需要你操心,你要做的只有寫個函數,然後扔到Lambda上。
這種服務最大的好處就是非常便宜。還以AWS Lambda為例,Lambda每個月提供免費的100萬次訪問,和40萬GB秒的計算力(1GB秒的意思是你的運算可以佔用1GB的內存1秒鐘)——對於絕大部分的小型服務來說這就足夠了。相比之下,EC2的nano實例的費用是每小時$0.0058,摺合每天$0.14,還是Lambda要便宜得多。
用AWS部署一個無服務架構的個人網站
在這篇文章里我想介紹下怎樣利用AWS部署一個無服務架構的個人網站。這個個人網站將具備以下特點:
包含前端和後端;
基本上以靜態文件為主,或者主要的計算都在前端(比如React應用);
與後台通過API通信,但數量非常少;
後台不需要太大內存或CPU(比如一個簡單的網頁計數器,每次請求只需要訪問一次資料庫)。
服務將部署到以下域名上(這裡用的都是假想的域名):
API服務:https://myservice-api.example.com
前端:https://myfrontend.example.com
這裡用了HTTPS,因為各大瀏覽器早已開始將HTTP協議標記為不安全協議了。為了保證安全,HTTPS是必要的,後面會介紹如何設置證書等。
整個網站將使用以下的AWS服務:
Lambda + API Gateway + S3,用於跑API伺服器;
DynamoDB,數據存儲;
S3,靜態網站;
Cloudfront,分散式CDN,用作靜態網站和API的前端;
AWS Certificate Manager(ACM),為https網站生成證書。
至於API伺服器的開發部署,我們採用Python + Flask的組合開發服務,然後用Zappa(https://github.com/Miserlou/Zappa)作為無伺服器部署工具。
設置AWS環境
首先需要設置AWS環境,以便從代碼和zappa中訪問AWS。需要兩個步驟:
創建AWS用戶,用於程序訪問;
設置本地環境,使代碼使用AWS用戶。
創建AWS用戶
登錄到AWS中,選擇「IAM」服務來管理用戶。
創建一個名為「myservice-admin」的用戶(或者任何你喜歡的用戶名),勾選「Programmatic access」選項。
在下一步中,點擊「Attach existing policies directly」按鈕,然後將「AdministratorAccess」添加到該用戶。
從安全的角度來說這種做法並不好。不過出於演示的目的,本文不再詳述怎樣找出部署無服務架構所需的許可權了。
點擊「Next」按鈕,最後點擊「Create User」按鈕,myservice-admin用戶就建好了。注意在創建成功的那個畫面上會顯示Access Key ID和Secret access key兩個值。務必要將這兩個複製保存下來,稍後要用它們來設置本地環境。
這個畫面是唯一能看到Secret access key的地方!如果你忘了複製就關閉了頁面,那就只能去用戶的詳細畫面去生成新的access key和secret了。
設置本地AWS環境
為了在本地使用AWS,我們需要創建本地環境。
首先安裝awscli工具,用它來幫我們配置環境:
安裝結束後,就可以使用aws configure命令進行設置:
這裡需要輸入上一步保存下來的Access Key ID和Secret Access Key值。至於區域,我用的是us-east-1。其他區域應該也可以,但如果你要像我一樣使用CloudFront的話,其他區域可能會有一些麻煩。
在DynamoDB中創建表
我們的後台API要實現一個計數器。為了保存計數器的數值,我們需要使用DynamoDB。DynamoDB是AWS提供的一個鍵值資料庫。首先我們需要在DynamoDB中建一個表,並設置好我們需要的計數器初始值。
在AWS控制台中選擇DynamoDB服務,然後點擊「Create Table」按鈕。在「Create DynamoDB table」畫面,在Table name中填寫myservice-dev,Primary key欄位填寫id,然後點擊Create Table按鈕。
幾秒鐘之後表就建好了。選擇剛剛建好的表,然後在右側選擇Items選項卡,單擊Create item按鈕創建一個項目,項目內容為id="counter"及counter_value=0。
創建值時需要點擊左側的加號按鈕才能添加counter_value屬性,而且別忘了把counter_value屬性的類型設置為Number。
創建API服務
接下來我們要建立API服務。這個API將提供一個計數器API,每次調用都會將計數器的值加一。計數器值保存在DynamoDB中。API的endpoint如下:
POST /counter/increase:增加計數器的值,並返回計數器值;
GET /counter:返回計數器值。
用Python和Flask編寫API服務
首先我們要創建Python虛擬環境,並安裝必要的包:
Flask是Web框架,boto3是訪問DynamoDB必須的包。simplejson可以解決一些JSON轉換時遇到的問題。接下來創建myservice.py,內容如下:
再創建一個run.py,以便在本地測試該服務:
運行服務:
這樣就可以在命令行中測試這個服務了(再開一個終端輸入下面的命令):
我們可以看到計數器的值增加了,說明這個服務可以用了!
將服務部署到Lambda上
要部署API到Lambda上,可以使用Zappa包。Zappa包使得部署微服務變得極其容易。首先安裝Zappa:
然後執行Zappa init命令初始化Zappa環境。它會問你幾個問題,但基本上可以使用默認值來回答:
初始化完成後,在目錄下會生成一個zappa_settings.json文件。然後就可以部署服務了:
現在我們的服務就部署成功了。可以用下面的Curl命令測試,也可以打開瀏覽器測試GET的API:
綁定自定義域名
不過上面的API服務還有一個小問題。自動生成的API endpoint是2ks1n5nrxh.execute-api.us-east-1.amazonaws.com,很難記也不好用。不過我們可以很容易地給它綁定一個自定義域名。
我們的自定義域名是https://myservice-api.example.com。為了使用HTTPS,我們需要現申請一個證書。AWS的Certificate Manager服務提供免費的證書。生成證書之後就可以在AWS的API Gateway里自定義域名了。
申請證書
從AWS控制台切換到ACM服務(服務名稱叫Certificate Manager,但敲ACM就能搜索到)。點擊Request a certificate按鈕,然後選擇Request a public certificate選項。選擇公開的證書就是免費的。
下一步,我們需要向AWS證明我們擁有這個域名。我這個域名是從Google Domains申請的,所以我在這裡選擇DNS validation。點擊Review按鈕然後點擊Confirm and Request。
現在證書請求已經生成了,AWS會顯示一個驗證畫面,上面寫明了怎樣驗證該域名:
根據說明,我們需要在域名下添加一條CNAME記錄。由於我的域名是從Google Domains申請的,我就打開Google Domains,找到域名example.com,然後添加上面指定的CNAME:
這裡在Name欄中只添加了_2adee19a0967c7dd5014b81110387d11字元串,去掉了後面的.example.com部分,否則.example.com就重複了。
接下來要等待大約10分鐘,AWS Certificate Manager就會去驗證域名了。驗證成功後,Status欄會顯示「Issued」。
現在證書已經申請好了,我們可以繼續去給API綁定域名。
為API服務綁定自定義域名
切換到API Gateway服務。從左側的APIs一欄可以看到,Zappa已經幫我們建好了myservice-dev服務。
從左側點擊「Custom Domain Names」,然後點擊右側的Create Custom Domain Name按鈕,填寫必要的欄位。
這裡我希望API使用CloudFront服務,這樣能在全世界都達到最理想的訪問速度,因此我選擇了Edge Optimized。如果不使用CloudFront,你可以選擇Regional。
點擊Save按鈕後,這個自定義域名綁定就建好了。實際上要等待大約40分鐘左右域名綁定才能正常使用,不過我們可以利用這段時間去配置DNS。
回到Google Domains添加這條CNAME:
該步驟完成後,等待大約40分鐘,等API Gateway中的「Initializing...」字樣消失後,自定義域名就可以使用了。
前端的靜態網站
接下來我們要給這個API服務創建一個前端。作為例子,這裡只創建一個非常簡單的頁面,它能調用/counter/increase。
前端編程
先建一個目錄myfrontend:
然後建一個簡單的HTML文件index.html:
將前端發布到S3
我們可以把前端部署到S3上。首先需要建一個桶,桶的名字就是域名。
接下來要把我們的網站放到這個桶中。打開該桶,選擇Properties選項卡,然後選擇Static Web Hosting。在彈出的對話框中選擇Use this bucket to host a website,在Index document欄位中輸入index.html。點擊Save關閉對話框。
上面顯示了「Endpoint」鏈接,我們稍後會用這個URL測試靜態網站。
最後一件事就是讓這個桶允許公開訪問。我們需要添加一個桶策略來實現這一點。打開這個桶,選擇Permissions選項卡,然後點擊Bucket Policy按鈕。
保存之後,我們應該可以在Bucket Policy按鈕上以及Permissions選項卡上看到橙色的「public」字樣,表明我們的桶是可以被公開訪問的。
這樣桶就建好了,但裡面還是空的,現在需要把網站的內容上傳到這個桶中。首先進入剛才建好的myfrontend目錄中,然後輸入下面的命令:
上面的命令會把當前目錄下(注意命令中的那個點 . )的所有文件都上傳到S3中。
現在就完成了!在瀏覽器中打開下面的地址就可以看到網站內容了(地址就是前面創建桶時顯示的Endpoint的URL):
嗯?貌似不太對。計數器沒有顯示任何值呢?
而且似乎有JavaScript錯誤。打開瀏覽器的控制台就能看到以下錯誤:
Failed to load https://myservice-api.example.com/counter: No "Access-Control-Allow-Origin" header is present on the requested resource. Origin "http://myfrontend.example.com.s3-website-us-east-1.amazonaws.com" is therefore not allowed access. If an opaque response serves your needs, set the request"s mode to "no-cors" to fetch the resource with CORS disabled.
顯然,我們需要設置CORS header(https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)才能讓這個腳本工作,因為後台API被放到了另一個域名上(myservice-api.example.com和myfront.example.com不是同一個域名)。
不過由於我們還要給前端綁定自定義域名,綁定後URL會發生變化,所以這裡先放一放,等一會兒綁定好域名之後再來考慮CORS的問題。
給靜態網站設置CloudFront和自定義域名
最後一步就是給前端設置CloudFront並綁定自定義域名。前面我們已經申請了*.example.com的證書,所以這一步就很容易了。
從AWS控制台中切換到CloudFront服務。點擊Create Distribution按鈕,然後點擊Web里的Start按鈕。
在「Create Distribution」畫面上,我們需要填寫以下信息:
創建好distribution後,就可以在distribution列表中看到CloudFront的域名了。
上面的狀態還是「In Progress」,我們可以利用這段時間去設置DNS。跟前面類似,去Google Domains里添加一個CNAME:
解決CORS問題
現在唯一的問題就是CORS了。CORS是由於前端和後台的域名不一致導致的,為了讓前端能訪問後台API,我們需要給後台添加CORS支持。
回到API的代碼目錄(myservice),激活Python環境。然後安裝flask_cors包:
然後編輯myservice.py,添加以下幾行(3和6):
最後發布到AWS Lambda:
試著刷新下瀏覽器。現在就能看到計數器顯示了正確的值。點擊「Increase Counter」按鈕也能增加計數器的值了。
總結
這篇文章介紹了創建一個簡單的無伺服器服務所需的多種AWS服務。如果你對AWS不熟悉,你可能會覺得我們用到了太多的服務,但其實絕大部分AWS服務都是一次性的,一旦設置好之後就不用再管了。以後的開發中用得上的只有zappa update和aws s3 sync兩條命令而已。
而且至少,這種方法要比自己設置一台VPS、安裝Web伺服器再寫個Jenkins腳本做持續部署要方便多了。
作為總結,下面是這篇文章的一些重點:
Lambda可以運行簡單的服務,服務可以通過API Gateway暴露成HTTP服務;
如果要用Python寫無伺服器服務,那麼Zappa是個非常方便的工具;
S3桶可以用作靜態網站使用;
要想使用HTTPS,可以通過AWS ACM申請證書;
API Gateway和CloudFront都支持自定義域名。
希望這篇文章對你有所幫助!
※程序員如何做到每天 17:00 準時下班?
※掙扎 7 年,蘋果 Siri 還是被「拋棄」了
TAG:CSDN |