開源函式計算平臺 OpenFunction 保姆級入門教程

KubeSphere發表於2022-04-02

OpenFunction 0.6.0 上週已經正式釋出了,帶來了許多值得注意的功能,包括函式外掛、函式的分散式跟蹤、控制自動縮放、HTTP 函式觸發非同步函式等。同時,非同步執行時定義也被重構了。核心 API 也已經從 v1alpha1 升級到 v1beta1。

官宣連結?:https://openfunction.dev/blog...

近年來,隨著無伺服器計算的興起,出現了很多非常優秀的 Serverless 開源專案,其中比較傑出的有 Knative 和 OpenFaaS。但 Knative Serving 僅僅能執行應用,還不能執行函式,而 Serverless 的核心是函式計算,也就是 FaaS,因此比較遺憾;OpenFaaS 雖然很早就出圈了,但技術棧過於老舊,不能滿足現代化函式計算平臺的需求。

OpenFunction 便是這樣一個現代化的雲原生 FaaS(函式即服務)框架,它引入了很多非常優秀的開源技術棧,包括 Knative、Tekton、Shipwright、Dapr、KEDA 等,這些技術棧為打造新一代開源函式計算平臺提供了無限可能:

  • Shipwright 可以在函式構建的過程中讓使用者自由選擇和切換映象構建的工具,並對其進行抽象,提供了統一的 API;
  • Knative 提供了優秀的同步函式執行時,具有強大的自動伸縮能力;
  • KEDA 可以基於更多型別的指標來自動伸縮,更加靈活;
  • Dapr 可以將不同應用的通用能力進行抽象,減輕開發分散式應用的工作量。

本文不打算講一些非常高深的理論,作為剛跨進 Serverless 門檻的使用者,更需要的是如何快速上手,以便對函式計算有一個感性的認知,在後續使用的過程中,我們們再慢慢理解其中的架構和設計。

本文將會帶領大家快速部署和上手 OpenFunction,並通過一個 demo 來體驗同步函式是如何運作的。

OpenFunction CLI 介紹

OpenFunction 從 0.5 版本開始使用全新的命令列工具 ofn 來安裝各個依賴元件,它的功能更加全面,支援一鍵部署、一鍵解除安裝以及 Demo 演示的功能。使用者可以通過設定相應的引數自定義地選擇安裝各個元件,同時可以選擇特定的版本,使安裝更為靈活,安裝程式也提供了實時展示,使得介面更為美觀。它支援的元件和其依賴的 Kubernetes 版本如下:

ComponentsKubernetes 1.17Kubernetes 1.18Kubernetes 1.19Kubernetes 1.20+
Knative Serving0.21.10.23.30.25.21.0.1
Kourier0.21.00.23.00.25.01.0.1
Serving Default Domain0.21.00.23.00.25.01.0.1
Dapr1.5.11.5.11.5.11.5.1
Keda2.4.02.4.02.4.02.4.0
Shipwright0.6.10.6.10.6.10.6.1
Tekton Pipelines0.23.00.26.00.29.00.30.0
Cert Manager1.5.41.5.41.5.41.5.4
Ingress Nginxnana1.1.01.1.0

<center>表一 OpenFunction 使用的第三方元件依賴的 Kubernetes 版本</center>


ofn 的安裝引數 ofn install 解決了 OpenFunction 和 Kubernetes 的相容問題,會自動根據 Kubernetes 版本選擇相容元件進行安裝,同時提供多種引數以供使用者選擇。

引數功能
--all用於安裝 OpenFunction 及其所有依賴。
--async用於安裝 OpenFunction 的非同步執行時(Dapr & Keda)。
--cert-manager *用於安裝 Cert Manager。
--dapr *用於安裝 Dapr。
--dry-run用於提示當前命令所要安裝的元件及其版本。
--ingress *用於安裝 Ingress Nginx。
--keda *用於安裝 Keda。
--knative用於安裝 Knative Serving(以Kourier為預設閘道器)
--region-cn針對訪問 gcr.io 或 github.com 受限的使用者。
--shipwright *用於安裝 ShipWright。
--sync用於安裝 OpenFunction Sync Runtime(待支援)。
--upgrade在安裝時將元件升級到目標版本。
--verbose顯示粗略資訊。
--version用於指定要安裝的 OpenFunction 的版本。(預設為 "v0.6.0")
--timeout設定超時時間。預設為5分鐘。

<center>表二 install 命令引數列表</center>

使用 OpenFunction CLI 部署 OpenFunction

有了命令列工具 ofn 之後,OpenFunction 部署起來非常簡單。首先需要安裝 ofn,以 amd64 版本的 Linux 為例,僅需兩步即可:

1、下載 ofn

$ wget -c  https://github.com/OpenFunction/cli/releases/download/v0.5.1/ofn_linux_amd64.tar.gz -O - | tar -xz

2、為 ofn 賦予許可權並移動到 /usr/local/bin/ 資料夾下。

$ chmod +x ofn && mv ofn /usr/local/bin/

安裝好 ofn 之後,僅需一步即可完成 OpenFunction 的安裝。雖然使用 --all 選項可以安裝所有元件,但我知道大部分小夥伴的真實需求是不想再額外裝一下 Ingress Controller 的,這個也好辦,我們可以直接指定需要安裝的元件,排除 ingress,命令如下:

$ ofn install --knative --async --shipwright --cert-manager --region-cn
Start installing OpenFunction and its dependencies.
The following components will be installed:
+------------------+---------+
| COMPONENT        | VERSION |
+------------------+---------+
| OpenFunction     | 0.6.0   |
| Keda             | 2.4.0   |
| Dapr             | 1.5.1   |
| Shipwright       | 0.6.1   |
| CertManager      | 1.5.4   |
| Kourier          | 1.0.1   |
| DefaultDomain    | 1.0.1   |
| Knative Serving  | 1.0.1   |
| Tekton Pipelines | 0.30.0  |
+------------------+---------+
 ✓ Dapr - Completed!
 ✓ Keda - Completed!
 ✓ Knative Serving - Completed!
 ✓ Shipwright - Completed!
 ✓ Cert Manager - Completed!
 ✓ OpenFunction - Completed!
? Completed in 2m47.901328069s.

 ██████╗ ██████╗ ███████╗███╗   ██╗
██╔═══██╗██╔══██╗██╔════╝████╗  ██║
██║   ██║██████╔╝█████╗  ██╔██╗ ██║
██║   ██║██╔═══╝ ██╔══╝  ██║╚██╗██║
╚██████╔╝██║     ███████╗██║ ╚████║
 ╚═════╝ ╚═╝     ╚══════╝╚═╝  ╚═══╝

███████╗██╗   ██╗███╗   ██╗ ██████╗████████╗██╗ ██████╗ ███╗   ██╗
██╔════╝██║   ██║████╗  ██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗  ██║
█████╗  ██║   ██║██╔██╗ ██║██║        ██║   ██║██║   ██║██╔██╗ ██║
██╔══╝  ██║   ██║██║╚██╗██║██║        ██║   ██║██║   ██║██║╚██╗██║
██║     ╚██████╔╝██║ ╚████║╚██████╗   ██║   ██║╚██████╔╝██║ ╚████║
╚═╝      ╚═════╝ ╚═╝  ╚═══╝ ╚═════╝   ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝

雖然本文演示的是同步函式,但這裡把非同步執行時也裝上了,如果你不需要,可以把 --async 這個引數去掉,不影響本文的實驗。

安裝完成後,會建立這幾個 namespace:

$ kubectl get ns
NAME                              STATUS   AGE
cert-manager                      Active   17m
dapr-system                       Active   4m34s
io                                Active   3m31s
keda                              Active   4m49s
knative-serving                   Active   4m41s
kourier-system                    Active   3m57s
openfunction                      Active   3m37s
shipwright-build                  Active   4m26s
tekton-pipelines                  Active   4m50s

每個 namespace 對應上面安裝的各個元件。目前 OpenFunction 的 Webhook 需要使用 CertManager 來驗證 API 訪問,後續我們會去掉這個依賴,不再需要安裝 CertManager

自定義域名字尾

Knative Serving 目前使用 Kourier 作為入口閘道器,由於我們沒有部署 Ingress Controller,所以我們訪問函式只有 Kourier 這一個入口。

Kourier 是一個基於 Envoy Proxy 的輕量級閘道器,是專門對於 Knative Serving 服務訪問提供的一個閘道器實現。關於 Envoy 控制平面的細節本文不作贅述,感興趣的可以去閱讀 Kourier 官方文件和原始碼。這裡我們只需要知道 Kourier 會為函式訪問提供一個入口,這個訪問入口是通過域名來提供的,我們要做的工作就是將相關域名解析到 Kourier 的 ClusterIP。

Kourier 預設建立了兩個 Service:

$ kubectl -n kourier-system get svc
NAME               TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
kourier            LoadBalancer   10.233.7.202   <pending>     80:31655/TCP,443:30980/TCP   36m
kourier-internal   ClusterIP      10.233.47.71   <none>        80/TCP                       36m

只需要將與函式訪問相關域名解析到 10.233.47.71 即可。

雖然每個函式的域名都是不同的,但域名字尾是一樣的,可以通過泛域名解析來實現解析與函式相關的所有域名。Kourier 預設的域名字尾是 example.com,通過 Knative 的 ConfigMap config-domain 來配置:

$ kubectl -n knative-serving get cm config-domain -o yaml
apiVersion: v1
data:
  _example: |
    ################################
    #                              #
    #    EXAMPLE CONFIGURATION     #
    #                              #
    ################################

    # This block is not actually functional configuration,
    # but serves to illustrate the available configuration
    # options and document them in a way that is accessible
    # to users that `kubectl edit` this config map.
    #
    # These sample configuration options may be copied out of
    # this example block and unindented to be in the data block
    # to actually change the configuration.

    # Default value for domain.
    # Although it will match all routes, it is the least-specific rule so it
    # will only be used if no other domain matches.
    example.com: |

    # These are example settings of domain.
    # example.org will be used for routes having app=nonprofit.
    example.org: |
      selector:
        app: nonprofit

    # Routes having the cluster domain suffix (by default 'svc.cluster.local')
    # will not be exposed through Ingress. You can define your own label
    # selector to assign that domain suffix to your Route here, or you can set
    # the label
    #    "networking.knative.dev/visibility=cluster-local"
    # to achieve the same effect.  This shows how to make routes having
    # the label app=secret only exposed to the local cluster.
    svc.cluster.local: |
      selector:
        app: secret
kind: ConfigMap
metadata:
  annotations:
    knative.dev/example-checksum: 81552d0b
  labels:
    app.kubernetes.io/part-of: knative-serving
    app.kubernetes.io/version: 1.0.1
    serving.knative.dev/release: v1.0.1
  name: config-domain
  namespace: knative-serving

將其中的 _example 物件刪除,新增一個預設域名(例如 openfunction.dev),最終修改結果如下:

$ kubectl -n knative-serving get cm config-domain -o yaml
apiVersion: v1
data:
  openfunction.dev: ""
kind: ConfigMap
metadata:
  annotations:
    knative.dev/example-checksum: 81552d0b
  labels:
    app.kubernetes.io/part-of: knative-serving
    app.kubernetes.io/version: 1.0.1
    serving.knative.dev/release: v1.0.1
  name: config-domain
  namespace: knative-serving

配置叢集域名解析

為了便於在 Kubernetes 的 Pod 中訪問函式,可以對 Kubernetes 叢集的 CoreDNS 進行改造,使其能夠對域名字尾 openfunction.dev 進行泛解析,需要在 CoreDNS 的配置中新增一段內容:

        template IN A openfunction.dev {
          match .*\.openfunction\.dev
          answer "{{ .Name }} 60 IN A 10.233.47.71"
          fallthrough
        }

修改完成後的 CoreDNS 配置如下:

$ kubectl -n kube-system get cm coredns -o yaml
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        template IN A openfunction.dev {
          match .*\.openfunction\.dev
          answer "{{ .Name }} 60 IN A 10.233.47.71"
          fallthrough
        }
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        hosts /etc/coredns/NodeHosts {
          ttl 60
          reload 15s
          fallthrough
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
    ...

同步函式 demo 示例

配置完域名解析後,接下來可以執行一個同步函式的示例來驗證一下。OpenFunction 官方倉庫提供了多種語言的同步函式示例

這裡我們選擇 Go 語言的函式示例,先來看一下最核心的部署清單:

# function-sample.yaml
apiVersion: core.openfunction.io/v1beta1
kind: Function
metadata:
  name: function-sample
spec:
  version: "v2.0.0"
  image: "openfunctiondev/sample-go-func:latest"
  imageCredentials:
    name: push-secret
  port: 8080 # default to 8080
  build:
    builder: openfunction/builder-go:latest
    env:
      FUNC_NAME: "HelloWorld"
      FUNC_CLEAR_SOURCE: "true"
    srcRepo:
      url: "https://github.com/OpenFunction/samples.git"
      sourceSubPath: "functions/knative/hello-world-go"
      revision: "main"
  serving:
    template:
      containers:
        - name: function
          imagePullPolicy: Always
    runtime: "knative"

Function 是由 CRD 定義的一個 CR,用來將函式轉換為最終執行的應用。這個例子裡面包含了兩個元件:

  • build : 通過 Shipwright 選擇不同的映象構建工具,最終將應用構建為容器映象;
  • Serving : 通過 Serving CRD 將應用部署到不同的執行時中,可以選擇同步執行時或非同步執行時。這裡選擇的是同步執行時 knative。

國內環境由於不可抗因素,可以通過 GOPROXY 從公共代理映象中快速拉取所需的依賴程式碼,只需在部署清單中的 build 階段新增一個環境變數 FUNC_GOPROXY 即可:

# function-sample.yaml
apiVersion: core.openfunction.io/v1beta1
kind: Function
metadata:
  name: function-sample
spec:
  version: "v2.0.0"
  image: "openfunctiondev/sample-go-func:latest"
  imageCredentials:
    name: push-secret
  port: 8080 # default to 8080
  build:
    builder: openfunction/builder-go:latest
    env:
      FUNC_NAME: "HelloWorld"
      FUNC_CLEAR_SOURCE: "true"
      FUNC_GOPROXY: "https://proxy.golang.com.cn,direct"
    srcRepo:
      url: "https://github.com/OpenFunction/samples.git"
      sourceSubPath: "functions/knative/hello-world-go"
      revision: "main"
  serving:
    template:
      containers:
        - name: function
          imagePullPolicy: Always
    runtime: "knative"

在建立函式之前,需要先建立一個 secret 來儲存 Docker Hub 的使用者名稱和密碼:

$ REGISTRY_SERVER=https://index.docker.io/v1/ REGISTRY_USER=<your_registry_user> REGISTRY_PASSWORD=<your_registry_password>
$ kubectl create secret docker-registry push-secret \
    --docker-server=$REGISTRY_SERVER \
    --docker-username=$REGISTRY_USER \
    --docker-password=$REGISTRY_PASSWORD

下面通過 kubectl 建立這個 Function:

$ kubectl apply -f function-sample.yaml

檢視 Function 執行狀況:

$ kubectl get function
NAME              BUILDSTATE   SERVINGSTATE   BUILDER         SERVING   URL   AGE
function-sample   Building                    builder-6ht76                   5s

目前正處於 Build 階段,builder 的名稱是 builder-6ht76。檢視 builder 的執行狀態:

$ kubectl get builder
NAME            PHASE   STATE      REASON   AGE
builder-6ht76   Build   Building            50s

這個 builder 會啟動一個 Pod 來構建映象:

$ kubectl get pod
NAME                                     READY   STATUS     RESTARTS   AGE
builder-6ht76-buildrun-jvtwk-vjlgt-pod   2/4     NotReady   0          2m11s

這個 Pod 中包含了 4 個容器:

  • step-source-default : 拉取原始碼;

  • step-prepare : 設定環境變數;

  • step-create : 構建映象;

  • step-results : 輸出映象的 digest。

再次檢視函式狀態:

$ kubectl get function
NAME              BUILDSTATE   SERVINGSTATE   BUILDER         SERVING         URL                                              AGE
function-sample   Succeeded    Running        builder-6ht76   serving-6w4rn   http://openfunction.io/default/function-sample   6m

已經由之前的 Building 狀態變成了 Runing 狀態。

這裡的 URL 我們無法直接訪問,因為沒有部署 Ingress Controller。不過我們可以通過其他方式來訪問,Kourier 把每個訪問入口抽象為一個 CR 叫 ksvc,每一個 ksvc 對應一個函式的訪問入口,可以看下目前有沒有建立 ksvc:

$ kubectl get ksvc
NAME                       URL                                                        LATESTCREATED                   LATESTREADY                     READY   REASON
serving-6w4rn-ksvc-k4x29   http://serving-6w4rn-ksvc-k4x29.default.openfunction.dev   serving-6w4rn-ksvc-k4x29-v200   serving-6w4rn-ksvc-k4x29-v200   True

函式的訪問入口就是 http://serving-6w4rn-ksvc-k4x...。由於在前面的章節中已經配置好了域名解析,這裡可以啟動一個 Pod 來直接訪問該域名:

$ kubectl run curl --image=radial/busyboxplus:curl -i --tty
If you don't see a command prompt, try pressing enter.
[ root@curl:/ ]$
[ root@curl:/ ]$ curl http://serving-6w4rn-ksvc-k4x29.default.openfunction.dev/default/function-sample/World
Hello, default/function-sample/World!
[ root@curl:/ ]$ curl http://serving-6w4rn-ksvc-k4x29.default.openfunction.dev/default/function-sample/OpenFunction
Hello, default/function-sample/OpenFunction!

訪問這個函式時會自動觸發執行一個 Pod:

$ kubectl get pod
NAME                                                       READY   STATUS    RESTARTS   AGE
serving-6w4rn-ksvc-k4x29-v200-deployment-688d58bfb-6fvcg   2/2     Running   0          7s

這個 Pod 使用的映象就是之前 build 階段構建的映象。事實上這個 Pod 是由 Deployment 控制的,在沒有流量時,這個 Deployment 的副本數是 0。當有新的流量進入時,會先進入 Knative 的 Activator,Activator 接收到流量後會通知 Autoscaler(自動伸縮控制器),然後 Autoscaler 將 Deployment 的副本數擴充套件到 1,最後 Activator 會將流量轉發到實際的 Pod 中,從而實現服務呼叫。這個過程也叫冷啟動

如果你不再訪問這個入口,過一段時間之後,Deployment 的副本數就會被收縮為 0:

$ kubectl get deploy
NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
serving-6w4rn-ksvc-k4x29-v200-deployment   0/0     0            0           22m

總結

通過本文的示例,相信大家應該能夠體會到一些函式計算的優勢,它為我們帶來了我們所期望的對業務場景快速拆解重構的能力。作為使用者,只需要專注於他們的開發意圖,編寫函式程式碼,並上傳到程式碼倉庫,其他的東西不需要關心,不需要了解基礎設施,甚至不需要知道容器和 Kubernetes 的存在。函式計算平臺會自動為您分配好計算資源,並彈性地執行任務,只有當您需要訪問的時候,才會通過擴容來執行任務,其他時間並不會消耗計算資源。

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章