本篇文章就通過一個 Hello World 和 Knative 來一個“約會”,讓你一睹 Knative 這位白富美的真容。
安裝 Knative
Knative 社區提供的安裝步驟見這裏,整個流程大概包含如下三個部分:
- 準備 kubernetes 環境(你可以在阿里雲容器服務中快速創建一個 kubernetes 集羣 )
- 安裝istio
- 安裝 Knative組件
雖然看起來只有三步,但是每一步其實都需要手動做大量的工作,執行一堆命令。另外社區文檔提供的 yaml 文件默認使用了大量的 gcr.io 鏡像,目前國內無法拉取 gcr.io 鏡像。所以這些 yaml 文件在國內不能直接使用,至少需要手動同步 30 多個鏡像才行。
不過彆着急,阿里雲容器服務的應用目錄已經有 Knative 的安裝包,現在只需要在阿里雲容器服務上面點擊幾下鼠標就能輕輕鬆鬆搭建一個 Knative 集羣 O ^ ~ ^ O O ^ ~ ^ O O ^ ~ ^ O
創建 Kubernetes 集羣
阿里雲容器服務可以通過管理控制檯非常方便地創建 Kubernetes 集羣。具體過程可以參考創建Kubernetes集羣。
容器服務提供了專有集羣和託管集羣兩種類型,如果不知道該怎麼選擇建議你直接選擇託管版的 Kubernetes 集羣。託管版無需你自己承擔 Kubernetes Master 組件的管理和運維,你只需要提供 Node 節點即可。
安裝 Istio
Knative Serving 運行需要基於 Istio,目前阿里雲容器服務 Kubernetes 已提供了一鍵部署的方式來安裝配置 Istio。具體過程可以參考部署Istio。
登錄 容器服務管理控制檯,單擊左側導航欄中的集羣,進入集羣列表頁面。選擇所需的集羣並單擊操作列更多 > 部署 Istio。
根據需要進行配置,然後點擊部署按鈕。稍等十幾秒鐘之後,Istio 環境就可以部署完畢。
部署 Istio IngressGateway
在容器服務管理控制檯的應用目錄中找到 ack-istio-ingressgateway 組件。點擊參數
標籤可以看到默認參數提供了 Istio IngressGateway 的配置項,如果需要定製化參數可以在此進行修改。選擇好目標 Kubernetes 集羣
然後點擊創建
按鈕即可完成 Ingressgateway 的創建。
在容器服務左側的容器組頁面中選擇 Kubernetes 集羣和 istio-system
namespace 確認運行狀態,如下所示。
部署 Knative CRD
登錄容器服務管理控制檯,點擊左側的應用目錄,在右側選中 ack-knative-init,如下:
點擊創建按鈕部署 Knative 初始化所需的內容,包括部署 CRD 等。
部署 Knative Serving
登錄容器服務管理控制檯,點擊左側的應用目錄,在右側選中 ack-knative-serving,如下:
點擊參數, 可以通過修改參數配置進行定製化,默認參數提供了使用 Istio IngressGateway 的配置項,然後點擊創建按鈕。
Serving Hello World
Serverless 一個核心思想就是按需分配,那麼 Knative 是如何實現按需分配的呢?另外在前面的文章中你已經瞭解到 Knative Serving 在沒有流量的時候是可以把 Pod 縮容到零的。接下來就通過一些例子體驗一下 Knative 縮容到零和按需自動擴縮容的能力。
部署 helloworld-go 示例
Knative 官方給出了好幾種語言的 Helloworld 示例,這些不同的語言其實只是編譯鏡像的 Dockerfile 有所不同,做好鏡像之後的使用方式沒什麼差異。本例以 go 的 Hello World 爲例進行演示。官方給出的例子都是源碼,需要編譯長鏡像才能使用。爲了你驗證方便我已經提前編譯好了一份鏡像 registry.cn-hangzhou.aliyuncs.com/knative-sample/simple-app:07 , 你可以直接使用。
首先編寫一個 Knative Service 的 yaml 文件 helloworld-go.yaml
, 內容如下:
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: helloworld-go
namespace: default
spec:
template:
metadata:
labels:
app: helloworld-go
annotations:
autoscaling.knative.dev/target: "10"
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/knative-sample/simple-app:07
env:
- name: SIMPLE_MSG
value: "helloworld-go-07"
注意其中 autoscaling.knative.dev/target: "10"
這個 Annotation 是設置每一個 Pod 的可處理併發請求數 10 ,Knative KPA 自動伸縮的時候會根據當前總請求的併發數和 autoscaling.knative.dev/target
自動調整 Pod 的數量,從而達到自動擴縮的目的。更多的策略信息我會在後續的文章中一一介紹。
現在使用 kubectl 命令把 yaml 提交到 Kubernetes 中:
- 部署 helloworld-go
└─# kubectl apply -f helloworld-go.yaml
service.serving.knative.dev/helloworld-go created
- 查看 helloworld-go pod
└─# kubectl get pod
NAME READY STATUS RESTARTS AGE
helloworld-go-lq6ns-deployment-869cbcc75d-qrln7 2/2 Running 0 6s
到此 helloworld-go 已經運行起來了,接下來訪問一下 helloworld-go 這個服務吧。
訪問 helloworld-go 示例
在訪問 helloworld-go 之前我要先來介紹一下在 Knative 模型中流量是怎麼進來的。Knative Service 和 Kubernetes 原生的 Deployment 不一樣,Knative 不會創建 Loadbalance 的 Service,也不能創建 NodePort 類型的 Service,所以不能通過 SLB 或者 NodePort 訪問。只能通過 ClusterIP 訪問。而 ClusterIP 是不能直接對外暴露的,所以必須經過 Gateway 才能把用戶的流量接入進來。本例就是使用 Istio 的 Gateway 承接 Knative 的南北流量(進和出)。如下圖所示是 Knative 模型中流量的轉發路徑。用戶發起的請求首先會打到 Gateway 上面,然後 Istio 通過 VirtualService 再把請求轉發到具體的 Revision 上面。當然用戶的流量還會經過 Knative 的 queue 容器才能真正轉發到業務容器,關於這方面的細節我在後續的文章再進行詳細的介紹。
所以想要訪問 Knative 的服務首先要獲取 Gateway 的 IP 地址,可以通過如下方式獲取 Gateway 的 IP:
└─# kubectl get svc istio-ingressgateway --namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*].ip}"
39.97.31. 219
前面也介紹了 Gateway 是通過 VirtualService 來進行流量轉發的,這就要求訪問者要知道目標服務的名字才行(域名),所以要先獲取 helloworld-go 的域名, 注意下面這條命令中的 ${SVC_NAME}
需要替換成 helloworld-go
,這個名字必須要和 Knative Service 的名字一致,因爲每一個 Service 都有一個唯一的名字。
└─# kubectl get route ${SVC_NAME} --output jsonpath="{.status.domain}"
helloworld-go.default.example.com
至此你已經拿到 IP 地址和 Hostname,可以通過 curl 直接發起請求:
└─# curl -H "Host: helloworld-go.default.example.com" "http://39.97.31. 219"
<h1>helloworld-go-07-v3</h1>
爲了方便你進行測試,我提供了一個腳本 run-test.sh
,你可以使用此腳本測試你自己的 Service , 你自己在測試的時候把 SVC_NAME 換成自己的 Service Name 就行了。
#!/bin/bash
SVC_NAME="helloworld-go"
export INGRESSGATEWAY=istio-ingressgateway
export IP_ADDRESS=$(kubectl get svc $INGRESSGATEWAY --namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*]['ip']}")
echo "IP_ADDRESS: ${IP_ADDRESS}"
export GATEWAY_IP=`kubectl get svc $INGRESSGATEWAY --namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*]['ip']}"`
export DOMAIN_NAME=`kubectl get route ${SVC_NAME} --output jsonpath="{.status.domain}"`
kubectl get ksvc ${SVC_NAME} --output=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain
time curl -H "Host: ${DOMAIN_NAME}" http://${IP_ADDRESS} -v
縮容到零
剛剛部署完 Service 的時候 Knative 默認會創建出一個 Pod 提供服務,如果你超過即使秒沒有訪問 helloworld-go 這個服務那麼這個 Pod 就會自動刪除,此時就是縮容到零了。現在看一下 Pod 情況, 你可能會發現沒有 Pod
└─# kubectl get pod -o wide
No resources found.
現在執行一下 run-test.sh
發起一個請求到 Knative Service
└─# ./run-test.sh
IP_ADDRESS: 39.97.31. 219
NAME DOMAIN
helloworld-go helloworld-go.default.example.com
* Rebuilt URL to: http://39.97.31. 219/
* Trying 39.97.31. 219...
* TCP_NODELAY set
* Connected to 39.97.31. 219 (39.97.31. 219) port 80 (#0)
> GET / HTTP/1.1
> Host: helloworld-go.default.example.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 28
< content-type: text/html; charset=utf-8
< date: Mon, 03 Jun 2019 03:47:58 GMT
< server: istio-envoy
< x-envoy-upstream-service-time: 2681
<
* Connection #0 to host 39.97.31. 219 left intact
<h1>helloworld-go-07-v3</h1>
real 0m2.775s
user 0m0.007s
sys 0m0.007s
注意 run-test.sh
結果中,這面這一段:
real 0m2.775s
user 0m0.007s
sys 0m0.007s
real 0m2.775s
意思意思是 curl
請求執行一共消耗了 2.775s
, 也就是說 Knative 從零到 1 擴容 + 啓動容器再到服務響應請求總共消耗了 2.775s
(我之前的測試導致在 Node 上面緩存了鏡像,所以沒有拉鏡像的時間)。可以看出來這個速度還是很快的。
再看一下 pod 數量, 你會發現此時 Pod 自動擴容出來了。並且 Pod 數量爲零時發起的請求並沒有拒絕鏈接。
└─# kubectl get pod
NAME READY STATUS RESTARTS AGE
helloworld-go-p9w6c-deployment-5dfdb6bccb-gjfxj 2/2 Running 0 31s
按需分配,自動擴縮
helloworld-go 自動擴容測試
接下來再測試一下 Knative 按需擴容的功能。使用社區提供的 hey 進行測試。hey 有 Windows、Linux 和 Mac 的二進制可以在這裏下載。
使用這個命令測試之前需要在本機進行 Host 綁定,對於 helloworld-go 來說要把 helloworld-go 的域名綁定到 Istio Gateway 的 IP 上,/etc/hosts 添加如下配置
39.97.31. 219 helloworld-go.default.example.com
如下所示 這條命令的意思是:
-z 30s
持續測試 30s-c 50
保持每秒 50 個請求
測試結果如下:
└─# hey -z 30s -c 50 "http://helloworld-go.default.example.com/" && kubectl get pods
Summary:
Total: 30.0407 secs
Slowest: 0.1453 secs
Fastest: 0.0266 secs
Average: 0.0378 secs
Requests/sec: 1323.2700
Total data: 1113056 bytes
Size/request: 28 bytes
Response time histogram:
0.027 [1] |
0.038 [23584] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.050 [15839] |■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.062 [255] |
0.074 [30] |
0.086 [28] |
0.098 [14] |
0.110 [0] |
0.122 [0] |
0.133 [0] |
0.145 [1] |
Latency distribution:
10% in 0.0330 secs
25% in 0.0351 secs
50% in 0.0371 secs
75% in 0.0407 secs
90% in 0.0428 secs
95% in 0.0442 secs
99% in 0.0495 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0001 secs, 0.0266 secs, 0.1453 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0036 secs
req write: 0.0000 secs, 0.0000 secs, 0.0009 secs
resp wait: 0.0376 secs, 0.0266 secs, 0.1453 secs
resp read: 0.0001 secs, 0.0000 secs, 0.0100 secs
Status code distribution:
[200] 39752 responses
NAME READY STATUS RESTARTS AGE
helloworld-go-lq42n-deployment-68ddd64944-nkwpn 2/2 Running 0 77s
回想一下剛纔 helloworld-go.yaml 文件配置,已經設置了 autoscaling.knative.dev/target: "10"
這個 Annotation。這表示每一個 Pod 能夠接受併發 10 個請求,而剛纔併發請求數設置的是 50 所以理論上應該會創建出來 5 個 Pod?,
上面結果中最後一部分,是 kubectl get pods
的結果,如下所示:
NAME READY STATUS RESTARTS AGE
helloworld-go-lq42n-deployment-68ddd64944-nkwpn 2/2 Running 0 77s
可以看到實際只有一個 Pod,爲什麼呢?這是因爲雖然併發 50 ,但是每一個請求很快就結束了。看一下剛纔測試的結果, 截取核心的一部分展示如下。可以看到最慢的一個請求 0.1453
秒就處理完了。而且 99% 的請求 RT 都沒超過 0.0495
秒。
... ...
Total: 30.0407 secs
Slowest: 0.1453 secs
Fastest: 0.0266 secs
Average: 0.0378 secs
Requests/sec: 1323.2700
... ...
Latency distribution:
10% in 0.0330 secs
25% in 0.0351 secs
50% in 0.0371 secs
75% in 0.0407 secs
90% in 0.0428 secs
95% in 0.0442 secs
99% in 0.0495 secs
... ...
所以一秒內是可以完整的處理完 50 個請求的,也就不需要擴容了。
再換一個例子,讓每一個請求處理的時間拉長一些看看效果。
autoscale-go 自動擴縮測試
如果單個請求處理的太快就不太好展示自動擴縮的效果,那麼就讓單條請求處理的時間稍微長一些。Knative 官方有一個 Autoscaler 的例子 , 這個例子中每一個請求會進行一些計算,把請求時間拉長,這樣就能更容易的測試。你可以直接使用 registry.cn-hangzhou.aliyuncs.com/knative-sample/autoscale-go:0.1 這個鏡像進行測試。
- 編寫 Knative Service 文件 autoscale-go.yaml 如下:
└─# cat autoscale-go.yaml
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: autoscale-go
namespace: default
spec:
template:
metadata:
labels:
app: autoscale-go
annotations:
autoscaling.knative.dev/target: "10"
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/knative-sample/autoscale-go:0.1
- 部署 autoscale-go
└─# kubectl apply -f autoscale-go.yaml
service.serving.knative.dev/autoscale-go created
run-test.sh 中 SVC_NAME
改成 autoscale-go
然後執行 run-test.sh ,如下:
└─# ./run-test.sh
IP_ADDRESS: 39.97.31. 219
NAME DOMAIN
autoscale-go autoscale-go.default.example.com
* Rebuilt URL to: http://39.97.31. 219/
* Trying 39.97.31. 219...
* TCP_NODELAY set
* Connected to 39.97.31. 219 (39.97.31. 219) port 80 (#0)
> GET / HTTP/1.1
> Host: autoscale-go.default.example.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 0
< date: Mon, 03 Jun 2019 05:05:38 GMT
< server: istio-envoy
< x-envoy-upstream-service-time: 2912
<
* Connection #0 to host 39.97.31. 219 left intact
real 0m2.999s
user 0m0.007s
sys 0m0.008s
可以看到 autoscale-go 已經可以提供服務了。
使用 hey 命令測試之前需要在本機進行 Host 綁定,對於 autoscale-go 來說要把 autoscale-go 的域名綁定到 Istio Gateway 的 IP 上,/etc/hosts 添加如下配置
39.97.31. 219 autoscale-go.default.example.com
- 使用 hey 進行測試:
└─# hey -z 30s -c 50 "http://autoscale-go.default.example.com?sleep=100&prime=10000&bloat=5" && kubectl get pods
Summary:
Total: 30.1443 secs
Slowest: 6.0173 secs
Fastest: 0.1285 secs
Average: 0.1717 secs
Requests/sec: 290.4364
Total data: 875284 bytes
Size/request: 99 bytes
Response time histogram:
0.128 [1] |
0.717 [8704] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
1.306 [0] |
1.895 [0] |
2.484 [0] |
3.073 [0] |
3.662 [0] |
4.251 [0] |
4.840 [0] |
5.428 [0] |
6.017 [50] |
Latency distribution:
10% in 0.1329 secs
25% in 0.1356 secs
50% in 0.1383 secs
75% in 0.1413 secs
90% in 0.1435 secs
95% in 0.1450 secs
99% in 0.1574 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0002 secs, 0.1285 secs, 6.0173 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0036 secs
req write: 0.0000 secs, 0.0000 secs, 0.0011 secs
resp wait: 0.1713 secs, 0.1283 secs, 5.9780 secs
resp read: 0.0001 secs, 0.0000 secs, 0.0066 secs
Status code distribution:
[200] 8755 responses
NAME READY STATUS RESTARTS AGE
autoscale-go-zqcm2-deployment-6cf67b4545-2f2ck 2/2 Running 0 28s
autoscale-go-zqcm2-deployment-6cf67b4545-4xc9s 2/2 Running 0 26s
autoscale-go-zqcm2-deployment-6cf67b4545-6wt8r 2/2 Running 0 28s
autoscale-go-zqcm2-deployment-6cf67b4545-hdbnc 2/2 Running 0 30s
autoscale-go-zqcm2-deployment-6cf67b4545-w9pm7 2/2 Running 0 28s
可以看到此時 Knative 自動擴容出來了 5 個 Pod 處理請求。
總結
至此你已經完成了和 Knative Serving 的首次約會,也看到了這位白富美的真容。通過本篇文章你應該掌握
以下幾點:
- 在阿里雲容器服務上面快速搭建 Knative 集羣的方法
- 理解 Knative 從零到一的含義,並且能夠基於 helloworld-go 例子演示這個過程
- 理解 Knative 按需擴縮容的含義,並且能夠基於 autoscale-go 例子演示這個過程
- 理解 Knative 按需擴容的原理,按需擴容不單單是用戶發起 50 個併發、每一個 Pod 最多能夠併發處理 10 個請求就一定需要創建 5 個 Pod 出來。如果請求的處理時間很短,一個 Pod 就能滿足的情況下 Knative 是不會做無用的擴容的
Next
通過前面的例子相信你已經對 Knative Serving 有了更深刻的理解。但是你可能也會產生很多疑問:
- 這裏面只是展示了 Serving 的 Hello World,Eventing、Build 能不能也來一些這樣的例子?
- 爲什麼上面例子的域名都是 xx.example.com, 我能不能換成自己的域名?
- Knative 流量轉發的細節是怎樣的?queue 的作用是什麼?
- 在前面的例子中,容器都是監聽 8080 端口的,如果容器監聽的不是 8080 端口那麼需要怎麼配置自定義的端口?
- Knative 默認擴縮容的策略是怎樣的?
- hey 這個壓測工具雖然展示了 Knative 自動擴容的過程,但是還不夠直觀、還有沒有更好的工具來驗證併發數、服務響應時間和 Knative 自動擴容 Pod 之間的關係?
- 如果 Service 有更新,如何進行流量灰度?
所有這些疑問我會在後續的一系列文章中都會一一講解到,敬請期待後續的文章。