Docker 容器 微服務 雲端 無伺服器架構

無伺服器/微服務架構漸成主流 熟悉Docker容器才能快速上手

實作無伺服器框架Knative 自動擴縮免被廠商鎖定

2019-09-10
在容器技術和微服務架構的發展上,無伺服器架構和微服務架構將會是雲端應用程式逐漸採納的主要軟體架構,對此,本文將透過安裝及設定Kubernetes容器管理平台的無伺服器框架Knative,在本地端Linux上運行Azure Functions,然後示範如何運作AWS Lambda Functions。

 

在上一期技術專欄中已清楚說明容器與無伺服器應用的情境,透過條列式來決定技術選型,並分析兩者之間的優缺點。接下來,將介紹無伺服器架構和Kubernetes容器管理平台的無伺服器框架「Knative」,它可同時於雲端和本地端的Kubernetes叢集上運行,只須撰寫好建置模板(Build Template),即可執行Azure Functions和AWS Lambda等雲端大廠的無伺服器運算程式,避免被雲端廠商技術鎖定(Vendor Lock-in)。當然,此Knative無伺服器框架的底層技術仍是採用容器方式來運作,因此務必熟悉Docker容器技術,才能快速上手。

Knative準備工作

在第一篇文章中,Knative簡介中談到Knative Serving元件提供流量相關服務,一開始是利用Istio這套Service Mesh框架。不過,最近Knative團隊新增另一套由Solo.io公司所設計的Gloo(https://www.solo.io/glooe)作為Istio的替代方案,相關內容可參閱「Automating your Services with Knative and Solo.io Gloo」一文(https://itnext.io/knative-and-solo-io-gloo-2a877d456238)。但本文目前仍是以安裝Istio來示範。

安裝Istio

依照Knative官方說明文件(https://knative.dev/docs/install/installing-istio/),逐步安裝下列套件。

首先,安裝K8S套件管理工具「Helm」。先輸入「curl -LO https://git.io/get_helm.sh」指令,下載安裝腳本檔,接著以「chmod 700 get_helm.sh」指令修改成可執行權限,並使用「./get_helm.sh」指令來執行安裝腳本檔。Helm完成安裝後,執行「helm init」指令來起始Helm環境,並輸入「helm version」以確認版本,上述過程如圖1所示。

圖1  安裝Helm步驟。

隨後,執行「export ISTIO_VERSION=1.1.7」設定欲下載的Istio版本環境變數。然後,透過「curl -L https://git.io/getLatestIstio | sh -」來取得Istio安裝腳本檔並執行。執行完成後,在當下目錄中便會有「istio-1.1.7」的子目錄,再輸入「cd istio-$(ISTIO_VERSION)」,切換到其目錄,如圖2所示。接著將利用kubectl apply來安裝「install/kubernetes/helm/istio-init/files」底下的yaml檔,所以執行「for i in install/kubernetes/helm/istio-init/files/crd*yaml; do kubectl apply -f $i; done」。

圖2  執行Istio安裝腳本檔。

緊接著,建立istio-system的命名空間,相關指令如下:

   cat <    apiVersion: v1
   kind: Namespace
   metadata:
     name: istio-system
     labels:
       istio-injection: disabled
   EOF

 再來,安裝Istio結合sidecar注入設定,指令內容如下:

helm template --namespace=istio-system \
  --set sidecarInjectorWebhook.enabled=true \
  --set sidecarInjectorWebhook.enableNamespacesByDefault=true \
  --set global.proxy.autoInject=disabled \
  --set global.disablePolicyChecks=true \
  --set prometheus.enabled=false \
  `# Disable mixer prometheus adapter to remove istio default metrics.` \
  --set mixer.adapters.prometheus.enabled=false \
  `# Disable mixer policy check, since in our template we set no policy.` \
  --set global.disablePolicyChecks=true \
  `# Set gateway pods to 1 to sidestep eventual consistency / readiness problems.` \
  --set gateways.istio-ingressgateway.autoscaleMin=1 \
  --set gateways.istio-ingressgateway.autoscaleMax=1 \
  --set gateways.istio-ingressgateway.resources.requests.cpu=500m \
  --set gateways.istio-ingressgateway.resources.requests.memory=256Mi \
  `# More pilot replicas for better scale` \
  --set pilot.autoscaleMin=2 \
  `# Set pilot trace sampling to 100%` \
  --set pilot.traceSampling=100 \
  install/kubernetes/helm/istio \
  > ./istio.yaml

產生istio.yaml檔案之後,使用kubectl來安裝,輸入「kubectl apply -f istio.yaml」,如圖3所示。

圖3  安裝Istio sidecar安裝檔。

執行以上步驟,就可安裝好Istio service mesh框架。輸入「kubectl get pods --namespace istio-system」指令,即可確認istio的Pods狀況,結果如圖4所示。

圖4  Istio-system用到的Pods。

安裝Knative

安裝Istio框架後,接著就是Knative,由於此文是使用AKS(Azure Kubernetes Services),故依照官方安裝說明文件(https://knative.dev/docs/install/knative-with-aks/)來進行。

使用kubectl apply來安裝Knative YAML檔,執行下列指令:

kubectl apply --selector knative.dev/crd-install=true \
   --filename https://github.com/knative/serving/releases/download/v0.7.0/serving.yaml \
   --filename https://github.com/knative/build/releases/download/v0.7.0/build.yaml \
   --filename https://github.com/knative/eventing/releases/download/v0.7.0/release.yaml \
   --filename https://github.com/knative/serving/releases/download/v0.7.0/monitoring.yaml

由於第一次執行會出現部分資源不存在或未建立,須再執行一次,指令如下:

kubectl apply --filename https://github.com/knative/serving/releases/download/v0.7.0/serving.yaml \
   --selector networking.knative.dev/certificate-provider!=cert-manager \
   --filename https://github.com/knative/build/releases/download/v0.7.0/build.yaml \
   --filename https://github.com/knative/eventing/releases/download/v0.7.0/release.yaml \
   --filename https://github.com/knative/serving/releases/download/v0.7.0/monitoring.yaml

運作結束後,仍須確認相關三個元件是否正常運作,分別輸入「kubectl get pods --namespace knative-serving」檢查Serving元件、執行「kubectl get pods --namespace knative-build」檢查Build元件、輸入「kubectl get pods --namespace knative-eventing」檢查Eventing元件,以及最後的Monitoring,執行「kubectl get pods --namespace knative-monitoring」指令,上述結果如圖5所示。

圖5  確認Knative元件相關Pods之狀態。

設定專屬網域並測試無伺服器服務

既然已經使用容器管理平台Kubernetes,並安裝好Serverless框架「Knative」,當然必須可動態對應個別服務的網址,才能突顯出這樣容器平台的優異之處,以下依循使用專屬網域的官方說明文件(https://knative.dev/docs/serving/using-a-custom-domain/)來設定。

首先編輯knative-serving的網域配置檔config-map,執行「kubectl edit cm config-domain --namespace knative-serving」,將其註解行刪除,並將其example.com修改成目前的網域,此範例為「philipz.ml」,結果如圖6所示。

圖6  編輯knative-serving網域設定。

然後取得Istio Ingress Gateway的IP位址,輸入「kubectl get svc istio-ingressgateway --namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*]['ip']}"」,即可顯示其轉送服務之Gateway IP,請記錄下來。

由於philipz.ml網址是透過CloudFlare(https://www.cloudflare.com)來代管,所以在DNS設定頁面中新增一筆WildCard的DNS紀錄,也就是「*」,而IP則輸入上一步驟所得到的位址,相關設定結果如圖7所示。

圖7  在DNS建立WildCard萬用字元對應。

然後,部署特定容器來驗證WildCard運作是否正常,依照Knative autoscale的範例文件(https://knative.dev/docs/serving/samples/autoscale-go/index.html),修改成已包裝成容器的Web應用程式。首先下載autoscale的YAML檔(https://knative.dev/docs/serving/samples/autoscale-go/service.yaml),將name欄位改成想要的服務名稱,如「azurefunc」,而image欄位則修改成「docker.io/philipz/azurefunc」,如圖8所示,隨即另存新檔azurefunc.yaml。

圖8  Autoscale之部署檔。

接著輸入「kubectl apply -f azurefunc.yaml」,並執行「kubectl get pods」確認部署情況,最後查看服務狀態,輸入「kubectl get svc」,可看到istio ingress gateway等相關服務,即內部對應之IP和Port,如圖9所示。

圖9  部署Autoscale之過程和結果。

隨即開啟網頁,輸入「azurefunc.default.philipz.ml」網址,便可看到Azure Functions的歡迎網頁,如圖10所示,API結果則為圖11所示。至於如何製作Azure Functions的容器映像檔,等一下說明。

圖10  出現Azure Function歡迎頁面。
圖11  顯示Azure Function API結果。

等待一段時間後,再輸入「kubectl get pods」,可看出當沒有HTTP Request後的一段時間,Knative會自動終止Pods資源,如圖12所示,這可讓K8S的運算資源更有效率。

圖12  自動終止Pod運作的確認畫面。

接下來,測試Knative自動橫向擴展機制,在官方文件中推薦使用hey這套壓力測試工具(https://github.com/rakyll/hey),下載後,以同時1,000個HTTP Request,並持續30秒來進行壓力測試,如圖13所示。因壓測完成後接著執行「kubectl get pods」,可以看到自動橫向擴展非常多的Pods,以便負荷突如其來的大量請求,其效果如圖14所示,這表示藉由Knative的自動擴縮功能,完全可以應付如搶紅包等突發的巨大流量和資源使用率,平時無活動,又可完全停止,以節省資源。

圖13  安裝並執行hey壓力測試工具。
圖14  Knative瞬間橫向擴展大量的Pods。

在本地端Linux上運行Azure Functions

微軟官方的Azure Functions說明文件(https://docs.microsoft.com/zh-tw/azure/azure-functions/functions-create-function-linux-custom-image),便談到如何在Local端使用Docker工具來運行Azure Functions,筆者亦參考TriggerMesh(https://triggermesh.com/)的GitHuh專案(https://github.com/triggermesh/azure-runtime),稍加修改其Dockerfile,亦存放於GitHub(https://github.com/philipz/azure-runtime),詳細步驟如下:

首先,建置開發用之容器映像檔。先複製其GitHub專案,輸入「git clone https://github.com/philipz/azure-runtime」,進入到func-in-docker子目錄,當中的Dockerfile,是在Ubuntu 18.04容器映像檔上安裝Azure Functions Core Tools及相關必要套件,藉由容器作為開發環境,可維持本機環境的簡單性及潔淨,其Dockerfile內容如下:

FROM ubuntu:18.04

RUN set -e; \
  export DEBIAN_FRONTEND=noninteractive; \
  apt-get update; \
  apt-get install -y --no-install-recommends curl wget ca-certificates gpg dirmngr gpg-agent; \
  wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb; \
  dpkg -i packages-microsoft-prod.deb; \
  curl -sL https://deb.nodesource.com/setup_8.x | bash -; \
  curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg; \
  mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg; \
  sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-bionic-prod bionic main" > /etc/apt/sources.list.d/dotnetdev.list'; \
  apt-get update; \
  apt-get install -y nodejs azure-functions-core-tools dotnet-sdk-2.2; \
  rm -rf /var/lib/apt/lists/* /var/log/apt

接著執行「docker build -t philipz/azure_core_tool .」指令,建置出開發所需之映像檔「philipz/azure_core_tool」。

此時將利用上一步驟的容器開發環境來產製Azure Functions專案,先執行「docker run -ti --rm -w /workspace -v $(pwd):/workspace -p 7071:7071 -p 8080:8080 philipz/azure_core_tool:test bash」,啟動開發容器。接著,在容器內的終端畫面中輸入「func init MyFunctionProj --docker」,新增MyFunctionProj專案。再輸入「cd MyFunctionProj/」進入MyFunctionProj目錄,並執行「func new --name MyHttpTrigger --template "HttpTrigger"」以HttpTrigger模板來新增MyHttpTrigger程式。修改MyHttpTrigger.cs的程式碼,把AuthorizationLevel的屬性值改為「Anonymous」,如圖15所示。

圖15  修改MyHttpTrigger.cs內的程式碼。

最後執行程式,若是C#專案,必須加上--build參數,指令內容為「func host start --build」,執行結果如圖16所示,點選其畫面網址「http://localhost:7071/api/MyHttpTrigger?name=netadmin」,就會出現「Hello, netadmin」的網頁效果,如圖17所示。

圖16  執行func編譯過程。
圖17  Functions API測試結果。

由於在新增專案時,有加上--docker參數,所以在MyFunctionProj目錄中會自動產生Dockerfile檔案,只須執行「docker build -t philipz/azurefunc:csharp .」指令,就可透過Docker多階段建置,產製最後執行檔並放入到「dotnet2.0」的容器映像檔,其Dockerfile內容如下:

FROM microsoft/dotnet:2.2-sdk AS installer-env

COPY . /src/dotnet-function-app
RUN cd /src/dotnet-function-app && \
    mkdir -p /home/site/wwwroot && \
    dotnet publish *.csproj --output /home/site/wwwroot

FROM mcr.microsoft.com/azure-functions/dotnet:2.0
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true

COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]

最後運行這個由C#程式所寫的Azure Functions容器映像檔,執行「docker run -d -p 80:80 philipz/azurefunc:csharp」指令。然後開啟瀏覽器,輸入其主機IP,就可看到Azure Function歡迎畫面,如圖18所示。再填上其API所需要的URL參數「/api/MyHttpTrigger?name=philipz」,就可再次看到API測試結果,如圖19所示。

圖18  本地端的Azure Function歡迎畫面。
圖19  以容器執行的API測試結果。

以上就是所使用的Azure Functions容器映像檔的製作過程,看完之後就會感受到容器與無伺服器服務的異同,差異只於觸發程式條件和應用情境,但本質都是利用以Process為本體的容器來運行,後續若要部署到Azure雲端平台,可參閱Azure Functions Introduction文章(https://itnext.io/azure-functions-introduction-4fbec65ecedc)。

運行AWS Lambda Functions

介紹過微軟Azure Functions,接下來就是最早的無伺服器服務AWS Lambda Functions,此移植機制是利用TriggerMesh公司的Knative Lambda Runtimes專案(https://triggermesh.com/serverless_management_platform/knative-lambda-runtimes/),屬於Knative build templates(建構模板,https://github.com/knative/build-templates),可在Kubernetes叢集上層安裝好Knative後,運行AWS Lambda Function。

下面範例會使用tm CLI工具(https://github.com/triggermesh/tm)來控制Knative,當然亦可使用kubectl。

安裝knative-local-registry

依據TriggerMesh的knative lambda runtime專案說明文件(https://github.com/triggermesh/knative-lambda-runtime),必須先在Kubernetes上建置容器映像檔儲存庫(Registry),才能直接提供原始碼,藉由自動化建置Docker Image,以供部署到Knative上運行。

首先複製knative-local-registry專案(https://github.com/triggermesh/knative-local-registry),新增registry命名空間因此執行「kubectl create namespace registry」。

再切換到「templates」目錄,將所有YAML配置檔皆透過kubectl來apply,輸入「kubectl apply -f registry-config.yaml -f registry-service-builds.yaml -f registry-service-knative.yaml -f registry-service-kanikocache.yaml -f registry.yaml」,並且將每一個K8S節點的「/etc/hosts」都加上registry伺服器名稱和對應IP,輸入「cd ../sysadmin」指令,跳到上一層目錄再進入「sysadmin」目錄。接著執行「kubectl apply -f nodes-etc-hosts-update.yaml」指令,便完成初步設定。

接下來,必須將AKS上的Docker Engine設定檔,加上此knative local registry,因為這個local registry並沒有掛上SSL安全憑證,所以必須手動在「/etc/docker/daemon.json」中加上insecure-registries設定,範例如下:

{
  "live-restore": true,
  "log-driver": "json-file",
  "log-opts":  {
     "max-size": "50m",
     "max-file": "5"
  },
  "insecure-registries": [
     "knative.registry.svc.cluster.local"
  ]
}

至於如何添加SSH key到AKS各個節點的虛擬機,詳細過程請見微軟Azure AKS說明文件(https://docs.microsoft.com/zh-tw/azure/aks/ssh),此處只節錄其過程,如圖20所示,記得「docker/daemon.json」修改後,須執行「sudo service docker restart」重新啟動。

圖20  AKS添加SSH key之過程。

以Bash Shell部署Lambda Runtime

此操作是參照AWS Lambda發佈自訂執行環境之教學文章(https://docs.aws.amazon.com/zh_tw/lambda/latest/dg/runtimes-walkthrough.html),並以TriggerMesh的GitHub專案(https://github.com/triggermesh/aws-custom-runtime)為輔,步驟很簡單。

首先安裝TriggerMesh的tm工具(https://github.com/triggermesh/tm),接著部署aws-custom-runtime模板,執行「tm deploy buildtemplate -f https://raw.githubusercontent.com/triggermesh/aws-custom-runtime/master/buildtemplate.yaml」指令。完成後,執行「tm get buildtemplate」,便可看到aws-custom-runtime清單,如圖21所示,再來部署以Bash Shell編寫的「function.sh」程式,在此輸入「tm deploy service lambda-bash -f https://github.com/triggermesh/aws-custom-runtime --build-template aws-custom-runtime --build-argument DIRECTORY=example –wait」,便會顯示提供服務URL,隨後使用curl來測試這個腳本API,如圖21下方所示,就會回傳「Echoing request: '{"text":"netadmin"}」,而function.sh的內容如下:

function handler () {
  EVENT_DATA=$1
  echo "$EVENT_DATA" 1>&2;
  RESPONSE="Echoing request: '$EVENT_DATA'"

  echo $RESPONSE

圖21  部署Shell程式的Lambda Runtime過程。

以Node.js V4部署Lambda Runtime

同樣地,依照knative lambda runtime Node.js的說明(https://github.com/triggermesh/knative-lambda-runtime#nodejs),先部署建置模板,執行「tm deploy buildtemplate -f https://raw.githubusercontent.com/triggermesh/knative-lambda-runtime/master/node-4.x/buildtemplate.yaml」指令。完成之後,再輸入「tm get buildtemplate」指令,便會看到knative-node4-runtime清單,再部署Node.js程式,執行下列指令後,便提供node4的無伺服器服務網址,如圖22所示:

tm deploy service node4-test -f https://github.com/serverless/examples \
     --build-template knative-node4-runtime \
     --build-argument DIRECTORY=aws-node-serve-dynamic-html-via-http-endpoint \
     --build-argument HANDLER=handler.landingPage \
     --wait

圖22  部署Node.js程式的Lambda Runtime過程。

點選其網址,就可看到回傳JSON結果的畫面,如圖23所示。

圖23  Lambda Node程式的JSON回傳結果。

以Golang部署Lambda Runtime

最後的示範是參考AWS「Go中的AWS Lambda函式處理常式」文章(https://docs.aws.amazon.com/zh_tw/lambda/latest/dg/go-programming-model-handler-types.html),並以TriggerMesh的knative-lambda-runtime專案(https://github.com/triggermesh/knative-lambda-runtime#go)說明文件來操作,步驟如下。

首先,建立專屬目錄,執行「mkdir example-lambda-go && cd example-lambda-go」指令,在其目錄中,新增main.go檔案,內容如下:

package main

import (
        "fmt"
        "context"
        "github.com/aws/aws-lambda-go/lambda"
)

type MyEvent struct {
        Name string `json:"name"`
}

func HandleRequest(ctx context.Context, name MyEvent) (string, error) {
        return fmt.Sprintf("Hello %s!", name.Name ), nil
}

func main() {
        lambda.Start(HandleRequest)
}

同樣先部署建置模板,所以執行「tm deploy buildtemplate -f https://raw.githubusercontent.com/triggermesh/knative-lambda-runtime/master/go-1.x/buildtemplate.yaml」指令。輸入「tm get buildtemplate」指令,便會看到knative-go-runtime清單。接著,再部署上述的Go程式,輸入「tm deploy service go-lambda -f . --build-template knative-go-runtime --wait」。最後使用curl測試其Go API網址,將會回傳「Hello, netadmin!」的結果,全部過程如圖24所示。

圖24  部署Go程式的Lambda Runtime過程。

結語

雖然上述的範例看似完全不會被雲端廠商技術鎖定,但正如上一篇文章所談到的,無伺服器框架仍須結合後端雲服務(Backend as a Service,BaaS)API。

正因如此,故雲端三巨頭以外的軟體供應商,組成了Open Service Broker API標準聯盟(https://www.openservicebrokerapi.org/),讓地端和雲端的混合雲架構設計可以更加平順簡單,可參考圖25的架構設計,因此容器就用來包裝與後端服務有相依的Service Access Layer服務,以持續運作方式來執行,而無伺服器程式可運行無服務相依,單純執行業務邏輯面的程式,跟BaaS徹底解耦,便可順利上雲,甚至是多雲備援。

圖25  無伺服器與微服務之架構圖(經筆者修改過,出處:https://docs.microsoft.com/en-us/azure/architecture/guide/architecture-styles/microservice)

導入微服務架構的必要條件是,須從開發流程實踐上做根本的轉變,無服務器程式開發,則是必須改變其思維方式。隨著無服務器運算的出現,這些實踐方法還會進一步的修改和演變,無服務器可專注在業務價值,去除維運上的問題並大幅度縮短軟體上線時程,對於開發人員來說,這意味著減少關注底層基礎架構和作業系統面問題,可更加關注在分散式系統架構和業務流程的拆解。 正如組成土木架構的單元是結構,組成軟體架構的單元是Pattern,本質是一模一樣,每種材料各有特性,每個Pattern各有優劣,所以軟體架構的創新演進,必須靠領域知識樣式(Domain Knowledge Patterns)、開發新技術與高可靠工程作業的精誠合作,才能成功。

<本文作者:鄭淳尹,Docker.Taipei社群共同發起人,國泰金控技術架構師,曾任微軟MVP、momo購物網架構師、臺北榮民總醫院資訊工程師、玉山銀行資訊處專員、宏碁eDC維運工程師,系統維護及開發設計超過16年。開源技術愛好者,曾在多間大學資工系擔任Docker容器技術講師,並翻譯審閱多本容器技術書籍。>

 


追蹤我們Featrue us

本站使用cookie及相關技術分析來改善使用者體驗。瞭解更多

我知道了!