在阿里雲上試用 Knative
在今年的Google Cloud Next大會上,Google釋出了Knative, 這是由Google、Pivotal、Redhat和IBM等雲廠商共同推出的Serverless開源工具元件,它與Istio,Kubernetes一起,形成了開源Serverless服務的三駕馬車。
有意思的是:上述幾家公司是相互競爭的,但卻能把各自擅長的技術貢獻給同一個開源專案。另一個有意思的地方是對Serverless定義的轉變。以前說到Serverless,大家就等同於FaaS,就感覺只要把function程式碼提交,然後定義event trigger就好了。現在Knative把Serverless這個概念轉變成了免運維:使用者還是要有server的,只是運維上比管理一個Kubernetes cluster更省心,而且不用的時候並不需要為server資源支付費用。除此之外,FaaS的應用場景很小,只有很小很快的函式才能比較容易部署。Knative以自助的方式實現部署,應用場景更廣,且一般的應用都可以部署成Serverless。
根據Knative提供的文件介紹,一個完整的Serverless分為__Build__,__Serve__和__Eventing__三個部分。在本文中,我們將在阿里雲上按照Knative github的 ofollow,noindex" target="_blank">安裝指南 ,逐步操作,以實現一個Knative應用。
準備
建立 Kubernetes cluster
在阿里雲上建立一個Kubernetes cluster,用系統預設的設定就行,但要保證你有Admin許可權。如果你用已有的 cluster,請確認Kubernetes的版本是1.10以上。
安裝 Knative
這個過程分為兩步:
-
安裝Istio:
Knative的Istio有一些自己定義的資源,所以不要用Istio網站的預設安裝。但是Knative現有指南未更新,還是0.8,有些舊。我所用的是1.0:
curl https://raw.githubusercontent.com/knative/serving/master/third_party/istio-1.0.0/istio.yaml`
這個安裝需要一點時間,但是是必須的。因為Knative依賴Istio來聯接Serverless,而不是直接通過Kubernetes。等到所有的安裝完成後,我們要開啟Istio 自動injection:
kubectl label namespace default istio-injection=enabled
-
安裝 Knative元件:
執行下面的命令:
kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.1/release.yaml
安裝後等待一會並確認:
kubectl get pods -n knative-serving -w kubectl get pods -n knative-build -w
細心的同學會發現這裡只安裝了兩部分:Build 和 Serving,那麼Eventing呢?是需要單獨安裝的。
kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release.yaml
同樣的,執行這個命令來確認:
kubectl get pods -n knative-eventing -w
Build
Build是目前Knative專案中內容最豐富的部分。因為Pivotal拿出了壓箱寶build packs加入Knative。而Google之前多年做app engine,也在這方面累計了很多經驗。
在實現上,Build是一個Kubernetes Custom Resource Definition (CRD)。如同其它的Kubernetes CRD,定義的方式是通過YAML,呼叫的方式是API。使用者可以選擇不同的build template,比如Google的kaniko,Pivotal的build pack等。在本文中,我們選擇kaniko build。
先安裝Kaniko Build Template:
kubectl apply -f https://raw.githubusercontent.com/knative/build-templates/master/kaniko/kaniko.yaml
Kaniko build template和Docker build template最大的不同在於使用者不需要本地安裝Docker engine, Kaniko把程式碼搬到雲上生成Image。原始碼可以在遠端的伺服器上,還要指定相應的Dockerfile。
但是,這樣做有個問題:Kaniko怎麼訪問使用者的docker account呢?因此,我們需要建立一個secret,把使用者的docker username和password存在裡面。然後,還需要一個service account來繫結這個secret。
vim secret.yaml
apiVersion: v1 kind: Secret metadata: name: docker-user-pass annotations: build.knative.dev/docker-0: https://index.docker.io/v1/ type: kubernetes.io/basic-auth stringData: username: <docker username in plain text> password: <docker password in plain text>
把這裡的username和password換成你自己的帳號資訊,然後儲存。
kubectl apply -f secret.yaml
vim service-account.yaml
apiVersion: v1 kind: ServiceAccount metadata: name: build-bot secrets: - name: docker-user-pass
儲存後執行:
kubectl apply -f service-account.yaml
然後我們建立Kubernetes manifest vim build.yaml
:
apiVersion: build.knative.dev/v1alpha1 kind: Build metadata: name: docker-build spec: serviceAccountName: build-bot #service account created above source: git: revision: master url: "https://github.com/szihai/hello-go.git" steps: - args: - "--dockerfile=/workspace/Dockerfile" - "--destination=docker.io/xxx/helloworld-go" image: "gcr.io/kaniko-project/executor:v0.1.0" name: build-and-push
本文所用的sample app是從Knative repo 上fork的。( 例子 )
在這裡,我們指定了template用Kaniko。然後可以看到我們引用了前面的ServiceAccount 來訪問secret。用這個之前把裡面的 destination
換成你自己的docker id,儲存後用 kubectl apply -f build.yaml
來執行。
那麼,如何知道遠端的Kaniko到底做好了沒有呢?Kubernetes 會為 kind: Build
建立一個job。用 kubectl get pods
找到一個 docker-build-xxxx
的pod。然後執行: kubectl -n default logs docker-build-xxxx -c build-step-build-and-push
來觀察build的情況。
我們也可以直接檢視Kubetnetes build objects: kubectl describe builds
。要找的資訊是:
當然,最直接的方法是去自己的Docker hub上找到這個Image。
Serving
這個部分與普通的Kubetnetes服務釋出差別不大。先定義一個服務: vim service.yaml
apiVersion: serving.knative.dev/v1alpha1 kind: Service metadata: name: helloworld-go namespace: default spec: runLatest: configuration: revisionTemplate: spec: container: image: docker.io/{username}/helloworld-go env: - name: TARGET value: "Go Sample v1"
執行 kubectl apply -f service.yaml
。需要注意的是這裡我們用了serving.knative.dev 的API。所以與其它部署有所不同:不需要deployment.yaml。這可理解為deployment被knative給包辦了。如果執行 kubectl get deployments
,就可以看到 helloworld-go-xxxx-deployment
。
下一個問題就是,如何訪問這個服務呢?這時候,Istio就出場了。平時我們要自己建立Ingress 或LB,現在knative通過 Istio幫我們做了。首先我們得到Ingress Gateway的IP地址:
kubectl get svc knative-ingressgateway -n istio-system
這裡找到 EXTERNAL-IP
。然後我們找到Domain name:
kubectl get service.serving.knative.dev helloworld-go-o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain
接著執行:
curl -H "Host: {DOMAIN}" http://{EXTERNAL-IP}
結果應該是: Hello World: Go Sample v1!
如果有一段時間沒訪問該服務,就會發現執行 kubectl get pods
的時候,這幾個helloworld-go pod不見了。那是knative把replica數降為0。
Eventing
對於FaaS的來說,Eventing就是觸發這個function的機制。上面我們用 curl
去訪問服務,其實是為了測試而已。在真實的部署過程中,這個function應該是有事件觸發的。
Eventing是傳統的FaaS的主要功能,也是除原始碼外唯一與開發者真正相關的部分。正因為如此,其它FaaS,如Lambda, Openshift等,都可以通過這一層與Knative介面。
Knative設計的Eventing包括3個主要的概念:
- Source: 就是事件發生的起源,可以理解為與其它系統的介面,目前支援的包括 K8sevents , GitHub 和 GCP PubSub 。
- Buses: 事件傳輸的途徑,目前支援的有 Stub , Kafka 和 GCP PubSub 。
- Flows: 定義對事件的反應。這可以是連鎖的反應而不是單一的。
所以,我們要做的事就是,選一個Source,選一個Bus, 然後定義一個Flow,就可以啦。
本文中,我們選用K8events和Stub ClusterBus。先把它們裝上:
kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release-clusterbus-stub.yaml kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release-source-k8sevents.yaml
在生成flow之前,有一個小問題:K8 event是Kubernetes內部產生的,要接收的話,必須要通過一個Service Account 來授權。這是Kubernetes的要求,不是本文重點,如前面一樣,儲存後執行:
apiVersion: v1 kind: ServiceAccount metadata: name: feed-sa namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: create-deployment namespace: default rules: - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "list", "watch", "create", "update", "delete", "patch"] --- # This enables the feed-sa to deploy the receive adapter. apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: feed-sa-deploy namespace: default subjects: - kind: ServiceAccount name: feed-sa namespace: default roleRef: kind: Role name: create-deployment apiGroup: rbac.authorization.k8s.io --- # This enables reading k8s events from all namespaces. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: feed-admin subjects: - kind: ServiceAccount name: feed-sa namespace: default roleRef: kind: ClusterRole name: view apiGroup: rbac.authorization.k8s.io
接下來是主要的一步,建立flow: vim flow.yaml
:
apiVersion: flows.knative.dev/v1alpha1 kind: Flow metadata: name: k8s-event-flow namespace: default spec: serviceAccountName: feed-sa trigger: eventType: dev.knative.k8s.event resource: k8sevents/dev.knative.k8s.event service: k8sevents parameters: namespace: default action: target: kind: Route apiVersion: serving.knative.dev/v1alpha1 name: helloworld-go
接著執行 kubectl apply -f flow.yaml
就可以了。
我們來看一下是不是真的運行了呢?過一會兒執行:
kubectl get pods
會看到k8s-event-flow-xxx的job執行完了。然後helloworld-go的pod都啟動了。我們看一下日誌: kubectl logs helloworld-go-xxxxx user-container
,就會看到如下的結果:
Hello world received a request. Hello world received a request. Hello world received a request. Hello world received a request. ...
這說明這條鏈路是起作用的。那麼,這個flow的定義說了什麼呢?首先是用了剛剛定義的service account。然後在 trigger
中定義什麼樣的event可以符合條件,這裡我們說所有在 default namespace 的k8events 都符合。在action中我們定義有什麼樣的處理方式,本例中就直接呼叫了helloworld-go service。
結論
Knative是今年最新的雲端計算演進方向之一。阿里雲支援Kubernetes,可以成功執行Knative和Istio等應用,大家也可以到阿里雲上自己體驗一番!
當然,作為一個新的備受矚目的專案,Knative也會經歷其成長的煩惱。我們會持續跟進,並提供和Knative相關、但不限於實踐的分享,敬請期待。