Knative:代碼精簡之道

本文譯自:https://content.pivotal.io/blog/knative-whittling-down-the-code
轉載自:http://www.servicemesher.com/blog/knative-whittling-down-the-code/

[編者案]

Knative 作爲 Google 發起開源的 serverless 項目,給我們提供了一套簡單易用的 serverless 開源解決方案。本文作者直觀地向我們展示瞭如何使用Knative來一步一步逐漸精簡我們的代碼,來更加簡單容易的開發雲原生應用。作者使用實例來逐步向我們講述如何使用 Knative 提供的 Build、Serving 和 Eventing 三大組件來發揮威力,逐漸精簡代碼。如果您對 Knative 有興趣,本文對於你通過 Knative 實踐 serverless 一定會有所啓發,希望您能喜歡。

對我來說,2018年最好的開源項目是Knative,這是一個建立在Kubernetes之上的serverless平臺。不僅是爲了serverless平臺本身,也是爲了它所倡導的整個開發範式。事件驅動開發並不新鮮,但Knative爲圍繞事件構建生態系統奠定了基礎。

如果您不熟悉Knative,那麼您讀到的任何文檔都將它分爲三大組件:

  • 構建(Build) —— 我如何構建代碼和打包代碼?
  • 服務(Serving) —— 我的代碼如何爲請求提供服務?它是如何伸縮的?
  • 事件(Eventing) —— 我的代碼如何被各種事件觸發?

事實上這並不是一篇“Knative入門”的文章(在不久的將來會有更多關於這方面的內容),但是我最近一直在思考的是,隨着開發人員越來越多地利用Knative提供的功能,他們如何減少自己需要編寫的代碼。這是最近Twitter上一個特別熱門的話題,尤其是在KubeCon時代。我注意到的一個常見問題是,“如果您正在編寫Dockerfile,它真的是一個serverless的平臺嗎?” 但是,其他人認爲將代碼打包爲容器可能是最合理的解決方案,因爲它是可移植的、全面的,並且具有所有依賴項。不乏強烈持有這種觀點的人們,他們非常渴望爭辯。

與其火上澆油,不如讓我們看看Knative給開發人員提供了哪些選項來逐漸減少我們編寫的代碼量。我們將從最冗長的示例開始—我們自己構建的預構建容器(prebuilt container)。從這裏開始,我們將逐漸減少基準代碼量,減少構建自己的容器的需要,減少編寫自己的Dockerfile的需要,最後減少編寫自己的配置的需要。最重要的是,我們將看到Pivotal Function Service (PFS)的強大功能,以及它如何允許開發人員關注代碼而不是配置。

我們將看到的所有代碼都包含在兩個git repos中:knative-hello-worldpfs-hello-world

預構建(Prebuilt)的Docker容器

我們將看到的第一個場景是爲Knative提供一個預構建的容器鏡像,該鏡像已經上傳到我們選擇的容器鏡像庫。您將在Knative中看到的大多數Hello World示例都採用直接構建和管理容器的方式。這是有意義的,因爲它很容易理解,而且沒有引入很多新概念,這是一個很好的起點。這個概念非常簡單直接:傳給Knative一個暴露端口的容器,它將處理剩餘的所有事情。它不關心你的代碼是用GoRuby還是Java寫的;它會接收傳入的請求並將它們發送到你的應用程序。

讓我們從一個使用Express web框架基於node.js實現的 hello world應用程序開始。

const express = require("express");
const bodyParser = require('body-parser')

const app = express();
app.use(bodyParser.text({type: "*/*"}));

app.post("/", function(req, res) {
 res.send("Hello, " + req.body + "!");
});

const port = process.env.PORT || 8080;
app.listen(port, function() {
 console.log("Server started on port", port);
});

是不是漂亮且簡潔。這段代碼將啓動一個web服務器,監聽端口8080(除非端口已被佔用),並通過返回Hello來響應HTTP POST請求。當然,還需要package.json文件,它定義了一些東西(如何啓動應用程序,依賴關係等),但這有點超出了我們所看到的範圍。另一部分是Dockerfile,它描述如何將所有內容打包到一個容器中。

FROM node:10.15.1-stretch-slim

WORKDIR /usr/src/app
COPY . .
RUN npm install

ENV PORT 8080
EXPOSE $PORT

ENTRYPOINT ["npm", "start"]

這裏也沒什麼好驚訝的。我們將我們的鏡像建立在官方node.js鏡像的基礎之上,將我們的代碼複製到容器中並安裝依賴項,然後告訴它如何運行我們的應用程序。剩下的就是將其上傳到Docker Hub。

$ docker build . -t brianmmcclain/knative-hello-world:prebuilt

$ docker push brianmmcclain/knative-hello-world:prebuilt

如果您曾經在類似於Kubernetes的應用程序上運行過,那麼所有這些看起來應該非常熟悉。將代碼放入容器中,讓調度器處理以確保它處於正常狀態。我們可以告訴Knative關於這個容器的信息,再加上一點metadata,它會處理所有的東西。隨着請求數量的增長,它將擴展實例的數量,縮減到0,路由請求,連接事件——竭盡所能。我們真正需要告訴Knative的是調用我們的應用程序,運行它的名稱空間,以及容器鏡像的位置。

apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
 name: knative-hello-world-prebuilt
 namespace: default
spec:
 runLatest:
   configuration:
     revisionTemplate:
       spec:
         container:
           image: docker.io/brianmmcclain/knative-hello-world:prebuilt

$ kubectl apply -f 01-prebuilt.yaml

幾分鐘後,我們將看到一個新的pod運行起來,準備爲請求服務,如果在一段時間內沒有收到任何流量,Pod 數量將會縮減爲0。我們可以POST一些數據,看看我們收到的響應。首先,讓我們獲取Kubernetes集羣的Ingress IP,並將其分配給$SERVICE_IP變量:

$ export SERVICE_IP=`kubectl get svc istio-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"`

然後使用IP向我們的服務發送請求,在我們的請求中設置HOST header:

$ curl -XPOST http://$SERVICE_IP -H "Host: knative-hello-world-prebuilt.default.example.com" -d "Prebuilt"

Hello, Prebuilt!

Kaniko容器構建器

上面介紹的一切可以很好的工作,但是我們甚至還沒有開始接觸Knative的“Build”部分。實際上,我們沒有碰它,我們自己構建了這個容器。您可以在Knative文檔中閱讀所有關於構建以及它們如何工作的信息。總的來說,knative有一個名爲“Build Templates”的概念,我喜歡這樣描述他們:他們是關於如何從代碼到容器的可共享邏輯。這些構建模板中的大多數模板都能夠完成我們構建容器和上傳鏡像的需要。這些模板中最基本的可能是Kaniko Build Templates

顧名思義,它基於谷歌的Kaniko, Kaniko是在容器中構建容器鏡像的工具,不依賴於正在運行的Docker守護進程。向Kaniko容器鏡像提供Dockerfile和一個上傳結果的位置,它就可以據此構建鏡像。我們無需拉取代碼、在本地構建鏡像、上傳到 Docker Hub,然後從 Knative 拉取鏡像,我們可以讓Knative爲我們做這些,只需要多做一點配置。

但是,在執行此操作之前,我們需要告訴Knative如何根據容器註冊中心進行身份驗證。爲此,我們首先需要在Kubernetes中創建一個Secret,這樣我們就可以對Docker Hub進行身份驗證,然後創建一個服務帳戶來使用該Secret並運行構建。讓我們從創造Secret開始:

apiVersion: v1
kind: Secret
metadata:
 name: dockerhub-account
 annotations:
   build.knative.dev/docker-0: https://index.docker.io/v1/
type: kubernetes.io/basic-auth
data:
 # 'echo -n "username" | base64'
 username: dXNlcm5hbWUK
 # 'echo -n "password" | base64'
 password: cGFzc3dvcmQK

uesrname和password作爲base64編碼的字符串發送給Kubernetes。(對於有安全意識的讀者來說,這是一種傳輸機制,而不是安全機制。有關Kubernetes如何存儲Secret的更多信息,請在有空時查看關於on encrypting secret data at rest)。提交之後,我們將創建一個名爲build-bot的服務帳戶,並告訴它在推送到Docker Hub時使用這個Secret:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-bot
secrets:
- name: dockerhub-account

有關身份驗證的更多信息,請確保查看knative文檔中的how-authentication-work -in- knative文檔。

構建模板(Build Templates)的好處是任何人都可以創建並與社區共享它們。我們可以告訴Knative通過傳遞一些YAML來安裝這個構建模板:

$ kubectl apply -f https://raw.githubusercontent.com/knative/build-templates/master/kaniko/kaniko.yaml

然後我們需要在我們的應用YAML中添加更多:

apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
 name: knative-hello-world-kaniko
 namespace: default
spec:
 runLatest:
   configuration:
     build:
       serviceAccountName: build-bot
       source:
         git:
           url: https://github.com/BrianMMcClain/knative-hello-world.git
           revision: master
       template:
         name: kaniko
         arguments:
         - name: IMAGE
           value: docker.io/brianmmcclain/knative-hello-world:kaniko
     revisionTemplate:
       spec:
         container:
           image: docker.io/brianmmcclain/knative-hello-world:kaniko

雖然直接比較有點困難,但是我們實際上只向YAML中添加了一個部分—“Build”部分。我們添加的內容可能看起來很多,但如果你花時間逐條查看的話,它實際上並不壞:

  • serviceAccountName:在Knative auth文檔中,它遍歷了設置服務帳戶的過程。所有這些都是通過設置一個Kubernetes Secret來驗證我們的容器鏡像庫,然後將其封裝到一個服務帳戶中。
  • source:代碼所在的位置。例如,git repository。
  • template:要使用哪個Build Template。在本例中,我們將使用Kaniko Build Template。

讓我們嚮應用程序的新版本發送一個請求,以確保一切正常:

$ curl -XPOST http://$SERVICE_IP -H "Host: knative-hello-world-kaniko.default.example.com" -d "Kaniko"

Hello, Kaniko!

儘管這可能是一種更預先的配置,但權衡的結果是,現在我們不必每次更新代碼時都構建或推送我們自己的容器鏡像。相反,Knative將爲我們處理這些步驟!

Buildpack Build Template

所以,這個博客的重點是我們如何編寫更少的代碼。雖然我們已經使用Kaniko Build Template刪除了部署的一個操作組件,但是我們仍然在代碼之上維護一個Dockerfile和一個配置文件。但是如果我們可以拋棄Dockerfile呢?

如果您具有使用PaaS的習慣,那麼您可能已經習慣了簡單地向上推代碼,然後發生了一些神奇的事情,然後您就有了一個正常工作的應用程序。你不在乎這是怎麼做到的。我們所知道的是,您不必編寫Dockerfile來將其放入容器中,而且它可以正常工作。在Cloud Foundry,這是通過名爲buildpacks的框架實現的,該框架爲應用程序提供運行時和依賴項。

實際上給我們帶來兩大好處。不僅有一個使用buildpacks的Build Template,還有一個用於Node.js的buildpacks。就像Kaniko Build Template一樣,我們將在Knative中安裝buildpack Build Template:

kubectl apply -f https://raw.githubusercontent.com/knative/build-templates/master/buildpack/buildpack.yaml

現在,讓我們看看使用Buildpack Build Template的YAML是什麼樣子的:

apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
 name: knative-hello-world-buildpack
 namespace: default
spec:
 runLatest:
   configuration:
     build:
       serviceAccountName: build-bot
       source:
         git:
           url: https://github.com/BrianMMcClain/knative-hello-world.git
           revision: master
       template:
         name: buildpack
         arguments:
         - name: IMAGE
           value: docker.io/brianmmcclain/knative-hello-world:buildpack
     revisionTemplate:
       spec:
         container:
           image: docker.io/brianmmcclain/knative-hello-world:buildpack

這與我們使用Kaniko Build Template時非常相似。實際上,我們來做個比較:

<   name: knative-hello-world-kaniko
>   name: knative-hello-world-buildpack
---
<           name: kaniko
>           name: buildpack
---
<             value: docker.io/brianmmcclain/knative-hello-world:kaniko
>             value: docker.io/brianmmcclain/knative-hello-world:buildpack
---
<             image: docker.io/brianmmcclain/knative-hello-world:kaniko
>             image: docker.io/brianmmcclain/knative-hello-world:buildpack

那麼區別是什麼呢?首先,我們可以完全拋棄Dockerfile。Buildpack Build Template將分析我們的代碼,確定它是一個Node.js應用程序,並通過下載Node.js運行時和依賴項爲我們構建一個容器。雖然Kaniko Build Template將我們從Docker容器生命週期的管理工作中解放出來,但Buildpack Build Template更進一步,完全不需要管理Dockerfile了。

$ kubectl apply -f 03-buildpack.yaml
service.serving.knative.dev "knative-hello-world-buildpack" configured

$ curl -XPOST http://$SERVICE_IP -H "Host: knative-hello-world-buildpack.default.example.com" -d "Buildpacks"
Hello, Buildpacks!

Pivotal Function Service

讓我們檢查一下代碼庫的剩餘部分。我們有響應POST請求的Node.js代碼,使用Express框架設置web服務器。package.json文件定義了我們的依賴項。雖然這不是真正的代碼,但我們也在維護定義Knative服務的YAML。不過,我們可以繼續削減。

進入Pivotal Function Service (PFS),這是構建在Knative之上的Pivotal的商業serverless產品。PFS旨在消除管理代碼以外的任何東西的需要。這包括我們在代碼庫中管理自己的web服務器。使用PFS,我們的代碼如下:

module.exports = x => "Hello, " + x + "!";

就是這樣,沒有Dockerfile,沒有YAML。只要一行代碼。當然,像所有優秀的節點開發人員一樣,我們仍然需要有自己的package.json文件,儘管它不依賴於Express。一旦部署完畢,riff將使用這一行代碼並將其封裝在自己的託管容器鏡像中。它將把它與調用代碼所需的邏輯打包在一起,並像運行在Knative上的任何其他函數一樣提供服務。

PFS CLI使得部署我們的函數變得非常容易。我們將給函數命名爲pfs-hello-world,爲它提供到代碼所在的GitHub存儲庫的鏈接,並告訴它將生成的容器映像上傳到我們的私有容器鏡像庫中。

pfs function create pfs-hello-world --git-repo https://github.com/BrianMMcClain/pfs-hello-world.git --image $REGISTRY/$REGISTRY_USER/pfs-hello-world --verbose

幾分鐘後,我們將看到我們的函數進入運行狀態,我們可以像任何其他Knative函數一樣,向其發送請求:

$ curl -XPOST http://$SERVICE_IP -H "Host: pfs-hello-world.default.example.com" -H "Content-Type: text/plain" -d "PFS"

Hello, PFS!

或者,更簡單的是,使用riff CLI來調用我們的函數:

$ pfs service invoke pfs-hello-world --text -- -d "PFS CLI"

Hello, PFS CLI!

我們終於實現了目標! 由23行YAML、14行代碼和一個10行Dockerfile組成的簡化代碼行。

是不是一下子對PFS感興趣了呢?要申請提前訪問,只需填寫這張快速表格!

接下來工作?

越來越多的構建模板。這是Knative最令人興奮的特性之一,因爲它有很大的潛力爲各種場景打開一個自定義構建模板的社區。現在,您可以爲JibBuildKit等工具使用模板。已經有一個pull request來更新Buildpack構建模板,以支持Cloud Native Buildpacks

2018年是一個激動人心的開始,但是更讓我興奮的是看到Knative社區在2019年的增長。我們當然可以期望從社區獲得更多的構建模板和更多的事件源。不僅如此,我們還可以期望與現有技術更好地集成,例如Spring,它已經對此功能提供了強大的支持

如果您希望開始使用Knative進行開發,那麼Bryan Friedman和我將在2月21日主持一個很棒的網絡研討會,討論developing serverless applications on Kubernetes with Knative。我們將深入研究Knative的三個組件,它們是如何工作的,以及作爲開發人員如何利用它們來編寫更好的代碼。

如果你4月2-4日在費城,請加入我們的CF Summit! Bryan和我將討論the way to build serverless on Knative,或者如果您看到我們,就說聲hi!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章