[譯] Kubernetes 分佈式應用部署和人臉識別 app 實例

好的,夥計,讓咱們靜下心來。下面將會是一個漫長但充滿但願和有趣的旅程。前端

我將使用 Kubernetes 部署分佈式應用程序。我試圖建立一個相似於真實世界 app 的應用程序。顯然,因爲時間和精力有限,我不得不忽略一些細節部分。node

個人重點將放在 Kubernetes 和應用部署上。python

準備好進入正題了嗎?mysql

關於應用

摘要android

kube overview

應用程序由六個部分組成。代碼倉庫可在這裏找到:Kube Cluster Sampleios

這是一我的臉識別的服務應用,它能夠識別人物的圖像並將其和已知人物進行比較。識別結果會在一個簡單的前端中,經過表格的形式展示出來,能夠看到這些待識別的圖像中的人物是誰。應用的運行過程以下:首先向接收器發送請求,請求中須要包含圖像的路徑。這些圖像可存儲在 NFS 一類的地方,同時接收器會將圖像路徑存儲在 DB(MySQL)中。最後向隊列發送一個處理請求,包含保存圖像的 ID。這裏使用 NSQ 做爲隊列(譯者注:NSQ 是一個基於 Go 語言的分佈式實時消息平臺)。nginx

期間,圖像處理服務會不間斷地監視將要執行做業的隊列。處理流程由如下步驟組成:取 ID;加載圖像;最後,經過 gRPC 將圖像發送到用 Python 編寫的人臉識別後端程序。若是識別成功,後端將返回與該圖像中人物相對應的名稱。而後,圖像處理器會更新圖像記錄的人物 ID 字段,並將圖像標記爲「processed successfully」。若是識別不成功,圖像將被保留爲「pending」。 若是在識別過程當中出現故障,圖像將被標記爲「failed」。git

處理失敗的圖像能夠經過 cron 做業重試,例如:github

那麼這是如何工做的?讓咱們來看看 。golang

接收器

接收器服務是整個流程的起點。這個 API 接收以下格式的請求:

curl -d '{"path":"/unknown_images/unknown0001.jpg"}' http://127.0.0.1:8000/image/post
複製代碼

在這個例子中,接收器經過共享數據庫集羣來存儲圖像路徑。當數據庫存儲圖像路徑成功後,接收器實例就能從數據庫服務中接收圖像 ID。此應用程序是基於在持久層提供實體對象惟一標識的模型的。一旦 ID 產生,接收器會向 NSQ 發送一個消息。到這裏,接收器的工做就完成了。

圖像處理器

下面是激動人心的開始。當圖像處理器第一次運行時,它會建立兩個 Go 協程(routine)。 他們是:

Consume

這是一個 NSQ 消費者。它有三個必要的工做。首先,它可以監聽隊列中的消息。其次,當其接收到消息後,會將收到的 ID 添加到第二個例程處理的線程安全的 ID 切片中去。最後,它經過 sync.Condition 告知第二個協程有工做要作。

ProcessImages

該例程處理 ID 切片,直到切片徹底耗盡。一旦切片消耗完,例程將暫停而不是等待 channel。如下是處理單個 ID 的步驟:

  • 與人臉識別服務創建 gRPC 鏈接(在下面人臉識別章節解釋)
  • 從數據庫中取回圖像記錄
  • 設置 斷路器 的兩個函數
    • 函數 1: 運行 RPC 方法調用的主函數
    • 函數 2: 對斷路器的 Ping 進行健康檢查
  • 調用函數 1,發送圖像路徑到人臉識別服務。服務須要可以訪問該路徑。最好能像 NFS 同樣進行文件共享
  • 若是調用失敗,更新圖像記錄的狀態字段爲「FAILED PROCESSING」
  • 若是成功,將會返回數據庫中與圖片相關的人物名。它會執行一個 SQL 的鏈接查詢,獲取到相關的人物 ID
  • 更新數據庫中圖片記錄的狀態字段爲「PROCESSED」,以及人物字段爲識別出的人物 ID

這個服務能夠被複制,換句話說,能夠同時運行多個服務。

斷路器

雖然這是一個不須要太大精力就可以複製資源的系統,但仍可能存在情況,例如網絡故障、服務間的通訊問題。所以我在 gRRC 調用上實現了一個小小的斷路器做爲樂趣。

它是這樣工做的:

kube circuit

正如你所見到的,在服務中一旦有 5 個不成功的調用,斷路器將會被激活,而且不容許任何調用經過。通過一段配置的時間後,會向服務發送一個 Ping 調用,並檢測服務是否返回信息。若是仍然出錯,會增長超時時間,不然就會打開,容許流量經過。

前端

這只是一個簡單的表格視圖,使用 Go 自帶的 HTML 模板來渲染圖像列表。

人臉識別

這裏是識別魔術發生的地方。爲了追求靈活性,我決定將人臉識別這項功能封裝成爲基於 gRPC 的服務。我開始打算用 Go 語言去編寫,但後來發現使用 Python 來實現會更加清晰。事實上,除了 gPRC 代碼以外,人臉識別部分大概須要 7 行 Python 代碼。我正在使用一個極好的庫,它包含了全部 C 實現的 OpenCV 的調用。人臉識別。在這裏簽定 API 使用協議,也就意味着在協議的許可下,我能夠隨時更改人臉識別代碼的實現。

請注意,這裏存在一個能夠用 Go 語言來開發 OpenCV 的庫。我差點就用它了,可是它並無包含 C 實現的 OpenCV 的調用。這個庫叫作 GoCV,你能夠去了解一下。它們有些很是了不得的地方,好比,實時的攝像頭反饋處理,只須要幾行代碼就可以實現。

python 的庫本質上很簡單。如今,咱們有一組已知的人物圖像,並將其命名爲 hannibal_1.jpg, hannibal_2.jpg, gergely_1.jpg, john_doe.jpg 放在文件夾中。在數據庫中包含兩張表,分別是 personperson_images。它們看起來像這樣:

+----+----------+
| id | name     |
+----+----------+
|  1 | Gergely  |
|  2 | John Doe |
|  3 | Hannibal |
+----+----------+
+----+----------------+-----------+
| id | image_name     | person_id |
+----+----------------+-----------+
|  1 | hannibal_1.jpg |         3 |
|  2 | hannibal_2.jpg |         3 |
+----+----------------+-----------+
複製代碼

臉部識別庫返回來自已知人物的圖像的名稱,其與未知圖像中的人物匹配。以後,一個簡單的鏈接查詢,就像這樣,會返回識別出的人物信息。

select person.name, person.id from person inner join person_images as pi on person.id = pi.person_id where image_name = 'hannibal_2.jpg';
複製代碼

gRPC 調用會返回人物的 ID,並用於修改待識別圖像記錄中 person 那一列的值。

NSQ

NSQ 是一個極好的基於 Go 語言的隊列。它可伸縮而且在系統上具備最小的佔用空間。它還具備消費者用來接收消息的查找服務,以及發送者在發送消息時使用的守護程序。

NSQ 的理念是守護進程應該與發送者應用程序一塊兒運行。這樣,發件人只會發送到本地主機。但守護進程鏈接到查找服務,他們就是這樣實現全局隊列。

這就意味着,有多少個發送者,有須要部署多少個 NSQ 守護進程。因爲守護進程的資源要求很小,不會影響主應用程序的需求。

配置

爲了儘量靈活,以及使用 Kubernetes 的 ConfigSet,我在開發中使用 .env 文件來存儲配置,如數據庫服務的位置或 NSQ 的查找地址。 在生產中,這意味着在 Kubernetes 環境中,我將使用環境變量。

人臉識別應用程序總結

這就是咱們即將部署的應用程序的架構。它的全部組件都是可變的,只能經過數據庫,隊列和 gRPC 進行耦合。因爲更新機制的工做緣由,這在部署分佈式應用程序時很是重要。我將在「部署」部分中介紹該部分。

在 Kubernetes 中部署應用

基礎

什麼 Kubernetes?

我將在這裏介紹一些基礎知識,但不會過多介紹細節。若是你想了解更多,可閱讀的整本書:Kubernetes Up And Running。另外,若是你足夠大膽,你能夠看看這個文檔:Kubernetes Documentation

Kubernetes 是一個容器化的服務和應用程序管理平臺。它容易擴展,可管理一大堆容器,最重要的是,它能夠經過基於 yaml 的模板文件高度配置。人們常常將 Kubernetes 與Docker 集羣進行比較,但 Kubernetes 確實不止於此!例如:它能夠管理不一樣的容器。你可使用 Kubernetes 來對LXC 進行管理和編排,同時也可使用相同的方式管理 Docker。它提供了一個高於管理已部署服務和應用程序集羣的層。怎麼樣?讓咱們快速瀏覽一下 Kubernetes 的構建模塊吧。

在 Kubernetes 中,您將描述應用程序的指望狀態,Kubernetes 會作一些事情,使之達到這個狀態。狀態多是部署、暫停、重複兩次等等。

Kubernetes 的基礎知識之一是它爲全部組件使用標籤和註解。Services,Deployments,ReplicaSets,DaemonSets,一切都可以被標記。考慮如下狀況。爲了肯定哪一個 pod 屬於哪一個應用程序,咱們將會使用了一個名爲 app:myapp 的標籤。假設您已部署了此應用程序的兩個容器; 若是您從其中一個容器中移除標籤 app,則 Kubernetes 只會檢測到一個標籤,所以會啓動一個新的 myapp 實例。

Kubernetes Cluster

對於 Kuberenetes 的工做,須要有 Kubernetes 集羣的存在。配置集羣多是很是痛苦的,但幸運的是,幫助就在眼前。Minikube 在本地爲咱們配置一個帶有一個節點的集羣。AWS 有一個以 Kubernetes 集羣形式運行的測試服務,其中您惟一須要作的就是請求節點並定義你的部署。Kubernetes 集羣組件的文檔在此處:Kubernetes Cluster Components

Nodes

一個節點就是一臺工做主機。它能夠是任何事物,例如物理機、虛擬機以及各類雲服務提供的虛擬資源。

Pods

Pods 是一個邏輯上分組的容器,也就意味着一個 Pod 能夠容納多個容器。一個 Pod 在建立後會得到本身的 DNS 和虛擬 IP 地址,這樣Kubernetes 就能夠爲其平衡流量。你不多須要直接處理容器,即便在調試時(好比查看日誌),一般也會調用 kubectl logs deployment / your-app -f 而不是查看特定的容器。儘管有可能會調用 -c container_name-f 參數會持續顯示日誌文件的末尾部分。

Deployments

在 Kubernetes 中建立任何類型的資源時,它將在後臺使用 Deployment。一個 Deployment 對象描述當前應用程序的指望狀態。這東西能夠用來變換 Pod 或 Service 的狀態,更新或推出新版的應用。您不直接控制 ReplicaSet(如稍後所述),但能夠控制 Deployment 對象來建立和管理 ReplicaSet。

Services

默認狀況下,Pod 會獲得一個 IP 地址。然而,由於 Pods 在 Kubernetes 中是一個不穩定的東西,因此你須要更持久的東西。隊列、mysql、內部API、前端,這些須要長時間運行而且須要在一個靜態的,不變的IP或最好是 DNS 記錄以後。

爲此,Kubernetes 提供可定義可訪問模式的 Services。負載均衡,簡單 IP 或內部 DNS。

Kubernetes 如何知道服務是否正確運行?你能夠配置運行情況檢查和可用性檢查。運行情況檢查將檢查容器是否正在運行,但這並不意味着你的服務正在運行。爲此,你須要在您的應用程序中對可用的端點進行可用性檢查。

因爲 Services 很是重要,我建議你稍後在這裏閱讀它們:Services。預先提醒,這部分文檔內容不少,有 24 個 A4 大小的頁面,內容包含網絡、服務和發現。可是這對於你是否決定要在生產環境中使用 Kubernetes 是相當重要的。

DNS / Service Discovery

若是您在集羣中建立服務,該服務將獲取由特殊的Kubernetes Deployments 對象(被稱做爲 kube-proxy 和 kube-dns)提供的在 Kubernetes 中的 DNS 記錄。這兩個對象在集羣中提供了服務發現。若是您運行了mysql服務並設置了 clusterIP:none,那麼集羣中的每一個人均可以經過 ping mysql.default.svc.cluster.local 來訪問該服務。 其中:

  • mysql – 服務的名稱
  • default – 命名空間名稱
  • svc – 服務自己
  • cluster.local – 本地集羣域名

該域名能夠經過自定義來更改。要訪問集羣外部的服務,必須有 DNS 提供者,再使用Nginx(例如)將IP地址綁定到記錄。可使用如下命令查詢服務的公共IP地址:

  • NodePort – kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services mysql
  • LoadBalancer – kubectl get -o jsonpath="{.spec.ports[0].LoadBalancer}" services mysql

Template Files

像 Docker Compose、TerraForm 或其餘服務管理工具同樣,Kubernetes 也提供了配置模板的基礎設施。這意味着你不多須要手工作任何事情。

例如,請看下面使用 yaml 文件來配置 nginx 部署的模板:

apiVersion: apps/v1
kind: Deployment #(1)
metadata: #(2)
    name: nginx-deployment
    labels: #(3)
    app: nginx
spec: #(4)
    replicas: 3 #(5)
    selector:
    matchLabels:
        app: nginx
    template:
    metadata:
        labels:
        app: nginx
    spec:
        containers: #(6)
        - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
複製代碼

在這個簡單的部署中,咱們作了如下工做:

  • (1) 使用 kind 屬性定義模板的類型
  • (2) 添加可識別此部署的元數據以及使用 label 建立每個資源 (3)
  • (4) 而後描述所須要的狀態規格。
  • (5) 對於 nginx 應用程序,包含 3 個 replicas
  • (6) 這是關於容器的模板定義。這裏配置的 Pod 包含一個 name 爲 nginx 的容器。其中,使用 1.7.9 版本的 nginx 鏡像(這個例子中使用的是 Docker),暴露的端口號爲:80

ReplicaSet

ReplicaSet 是低級複製管理器。 它確保爲應用程序運行正確數量的複製。 可是,當部署處於較高級別,應始終管理 ReplicaSets。你不多須要直接使用 ReplicaSets,除非您有一個須要控制複製細節的特殊案例。

DaemonSet

還記得我說的Kubernetes是如何持續使用標籤的嗎?DaemonSet 是一個控制器,用於確保守護程序應用程序始終在具備特定標籤的節點上運行。

例如:您但願全部標有 loggermission_critical 的節點運行記錄器/審計服務守護程序。而後你建立一個 DaemonSet,並給它一個名爲 loggermission_critical 的節點選擇器。Kubernetes 將尋找具備該標籤的節點。始終確保它將有一個守護進程的實例在其上運行。所以,在該節點上運行的每一個實例均可以在本地訪問該守護進程。

在個人應用程序中,NSQ 守護進程多是一個 DaemonSet。爲了確保它在具備接收器組件的節點上運行,我採用 receiver 標記一個節點,並用 receiver 應用程序選擇器指定一個 DaemonSet。

DaemonSet 具備 ReplicaSet 的全部優勢。它是可擴展的並由Kubernetes管理它。這意味着,全部的生命週期事件都由 Kube 處理,確保它永不消亡,而且一旦發生,它將當即被替換。

Scaling

在 Kubernetes 中作擴展很簡單。ReplicaSets 負責管理 Pod 的實例數量,如 nginx 部署中所看到的,使用「replicas:3」設置。咱們應該以容許 Kubernetes 運行它的多個副本的方式編寫咱們的應用程序。

固然這些設置是巨大的。你能夠指定哪些複製必須在什麼節點上運行,或者在各類等待時間等待實例出現的時間。你能夠在這裏閱讀關於此主題的更多信息:Horizontal Scaling 和此處:[Interactive Scaling with Kubernetes](https:/ /kubernetes.io/docs/tutorials/kubernetes-basics/scale-interactive/),固然還有一個 ReplicaSet 控件的詳細信息 全部的 scaling 均可以在 Kubernetes 中實現。

Kubernetes 總結

這是一個處理容器編排的便利工具。 它的基本單位是具備分層的架構的 Pods。頂層是 Deployments,經過它處理全部其餘資源。它高度可配置,提供了一個用於全部調用的 API,所以比起運行 kubectl,你能夠編寫本身的邏輯將信息發送到 Kubernetes API。

Kubernetes 如今支持全部主要的雲提供商,它徹底是開源的,隨意貢獻!若是你想深刻了解它的工做方式,請查看代碼:Kubernetes on Github

Minikube

我將使用 Minikube。Minikube 是一個本地 Kubernetes 集羣模擬器。儘管模擬多個節點並非很好,但若是隻是着手去學習並在本地折騰一下的話,這種方式不須要任何的開銷,是極好的。Minikube是基於虛擬機的,若是須要的話,可使用 VirtualBox 等進行微調。

全部我將要使用的 kube 模板文件能夠在這裏找到:Kube files

**注意:**若是稍後想要使用 scaling 但注意到複製老是處於「Pending」狀態,請記住 minikube 僅使用單個節點。它可能不容許同一節點上有多個副本,或者只是明顯耗盡了資源。您可使用如下命令檢查可用資源:

kubectl get nodes -o yaml
複製代碼

建立容器

Kubernetes 支持大部分容器。我將要使用 Docker。對於我構建的全部服務,存儲庫中都包含一個 Dockerfile。我鼓勵你去研究它們。他們大多數都很簡單。對於 Go 服務,我正在使用最近引入的多階段構建。Go 服務是基於 Alpine Linux 的。人臉識別服務是 Python實現的。NSQ 和 MySQL 正在使用他們本身的容器。

上下文

Kubernetes 使用命名空間。若是你沒有指定任何命名空間,它將使用 default 命名空間。我將永久設置一個上下文以免污染默認命名空間。 你能夠這樣作:

❯ kubectl config set-context kube-face-cluster --namespace=face
Context "kube-face-cluster" created.
複製代碼

一旦它建立完畢,你也必須開始使用上下文,以下所示:

❯ kubectl config use-context kube-face-cluster
Switched to context "kube-face-cluster".
複製代碼

在此以後,全部 kubectl 命令將使用命名空間 face

部署應用

Pods 和 Services 概述:

kube deployed

MySQL

我要部署的第一個 Service 是個人數據庫。

我正在使用位於此處的 Kubernetes 示例 Kube MySQL,它符合個人需求。請注意,該配置文件正在使用明文密碼。我將按照此處所述 Kubernetes Secrets作一些安全措施。

如文檔中描述的那樣,我使用保密的 yaml 在本地建立了一個祕鑰文件。

apiVersion: v1
kind: Secret
metadata:
    name: kube-face-secret
type: Opaque
data:
    mysql_password: base64codehere
複製代碼

我經過如下命令建立了base64代碼:

echo -n "ubersecurepassword" | base64
複製代碼

這是您將在個人部署yaml文件中看到的內容:

...
- name: MYSQL_ROOT_PASSWORD
    valueFrom:
    secretKeyRef:
        name: kube-face-secret
        key: mysql_password
...
複製代碼

另外值得一提的是:它使用一個 volume 來保存數據庫。volume 定義以下:

...
        volumeMounts:
        - name: mysql-persistent-storage
            mountPath: /var/lib/mysql
...
        volumes:
        - name: mysql-persistent-storage
        persistentVolumeClaim:
            claimName: mysql-pv-claim
...
複製代碼

presistentVolumeClain 在這裏是關鍵。這告訴 Kubernetes 這個資源須要一個持久的 volume。如何提供它是從用戶抽象出來的。你能夠肯定 Kubernetes 將提供 volume。它與 Pods 相似。要閱讀詳細信息,請查看此文檔:Kubernetes Persistent Volumes

使用如下命令完成部署 mysql 服務:

kubectl apply -f mysql.yaml
複製代碼

apply 仍是 create?簡而言之,apply 被認爲是聲明性的對象配置命令,而 create 則是命令式的。這意味着如今「create」一般是針對其中一項任務的,好比運行某些東西或建立 Deployment。而在使用 apply 時,用戶不會定義要採起的操做。這將由 Kubernetes 根據集羣的當前狀態進行定義。所以,當沒有名爲 mysql 的服務時,我調用 apply -f mysql.yaml,它會建立服務。再次運行時,Kubernetes 不會作任何事情。可是,若是我再次運行 create,它會拋出一個錯誤,說明服務已經被建立。

有關更多信息,請查看如下文檔:Kubernetes Object Management,[Imperative Configuration](https:// kubernetes .io / docs / concepts / overview / object-management-kubectl / imperative-config /),Declarative Configuration)。

要查看進度信息,請運行:

# 描述整個進程
kubectl describe deployment mysql
# 僅顯示 pod
kubectl get pods -l app=mysql
複製代碼

輸出應該與此相似:

...
    Type           Status  Reason
    ----           ------  ------
    Available      True    MinimumReplicasAvailable
    Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   mysql-55cd6b9f47 (1/1 replicas created)
...
複製代碼

或者在 get pods 的狀況下:

NAME                     READY     STATUS    RESTARTS   AGE
mysql-78dbbd9c49-k6sdv   1/1       Running   0          18s
複製代碼

要測試實例,請運行如下代碼片斷:

kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -pyourpasswordhere
複製代碼

** 須要瞭解的是 **:若是你如今更改密碼,從新應用 yaml 文件更新容器是不夠的。因爲數據庫持續存在,所以密碼將不會更改 你必須使用 kubectl delete -f mysql.yaml 刪除整個部署。

運行 show databases 時應該看到如下內容。

If you don't see a command prompt, try pressing enter. mysql> mysql> mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | kube | | mysql | | performance_schema | +--------------------+ 4 rows in set (0.00 sec) mysql> exit Bye 複製代碼

你還會注意到我已經在這裏安裝了一個文件:Database Setup SQL到容器中。MySQL 容器自動執行這些。該文件將初始化一些數據以及我將要使用的模式。

volume 定義以下:

volumeMounts:
    - name: mysql-persistent-storage
    mountPath: /var/lib/mysql
    - name: bootstrap-script
    mountPath: /docker-entrypoint-initdb.d/database_setup.sql
volumes:
- name: mysql-persistent-storage
    persistentVolumeClaim:
    claimName: mysql-pv-claim
- name: bootstrap-script
    hostPath:
    path: /Users/hannibal/golang/src/github.com/Skarlso/kube-cluster-sample/database_setup.sql
    type: File
複製代碼

要檢查引導腳本是否成功,請運行如下命令:

~/golang/src/github.com/Skarlso/kube-cluster-sample/kube_files master*
❯ kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -uroot -pyourpasswordhere kube
If you don't see a command prompt, try pressing enter. mysql> show tables; +----------------+ | Tables_in_kube | +----------------+ | images | | person | | person_images | +----------------+ 3 rows in set (0.00 sec) mysql> 複製代碼

這結束了數據庫服務設置。可使用如下命令查看該服務的日誌:

kubectl logs deployment/mysql -f
複製代碼

NSQ 查找

NSQ 查找將做爲內部服務運行,它不須要從外部訪問。因此我設置了 clusterIP:None,這會告訴 Kubernetes 這項服務是一項無頭(headless)的服務。這意味着它不會被負載均衡,而且不會是單一的 IP 服務。DNS 將會基於服務選擇器。

咱們定義的 NSQ Lookup 選擇器是:

selector:
matchLabels:
    app: nsqlookup
複製代碼

所以,內部 DNS 將以下所示:nsqlookup.default.svc.cluster.local

無頭服務在這裏詳細描述:Headless Service

基本上它和 MySQ L同樣,只是稍做修改。如前所述,我使用的是 NSQ 本身的 Docker 鏡像,名爲 nsqio / nsq。全部的 nsq 命令都在那裏,因此 nsqd 也將使用這個鏡像,只是命令有所不一樣。對於 nsqlookupd,命令是:

command: ["/nsqlookupd"]
args: ["--broadcast-address=nsqlookup.default.svc.cluster.local"]
複製代碼

你可能會問什麼是 --broadcast-address?默認狀況下,nsqlookup 將使用 hostname 做爲廣播地址 當消費者運行回調時,它會嘗試鏈接到相似於 http://nsqlookup-234kf-asdf:4161/lookup?topics=image 的 url。請注意 nsqlookup-234kf-asdf 是容器的主機名。經過將廣播地址設置爲內部 DNS,回調將爲:http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images。這將按預期工做。

NSQ 查找還須要兩個端口進行轉發:一個用於廣播,一個用於 nsqd 回調。這些在 Dockerfile 中公開,而後 在Kubernetes 模板中使用。像這個:

在容器模板中:

ports:
- containerPort: 4160
    hostPort: 4160
- containerPort: 4161
    hostPort: 4161
複製代碼

在服務模板中:

spec:
    ports:
    - name: tcp
    protocol: TCP
    port: 4160
    targetPort: 4160
    - name: http
    protocol: TCP
    port: 4161
    targetPort: 4161
複製代碼

name 是 Kubernetes 須要的。

要建立此服務,我使用與之前相同的命令:

kubectl apply -f nsqlookup.yaml
複製代碼

到這裏,有關於 nsqlookupd 的就結束了。

接收器

這是一個更復雜的問題。接收器會作三件事情:

  • 建立一些 deployments
  • 建立 nsq 守護進程
  • 向公衆提供服務

Deployments

它建立的第一個 deployment 對象是它本身的。Receiver的容器是 skarlso / kube-receiver-alpine

Nsq 守護進程

Receiver 啓動一個 nsq 守護進程。如前所述,接收者用它本身運行 nsqd。它這樣作能夠在本地通訊而不是經過網絡。經過讓接收器執行此操做,它們將在同一節點上結束。

NSQ 守護進程還須要一些調整和參數。

ports:
- containerPort: 4150
    hostPort: 4150
- containerPort: 4151
    hostPort: 4151
env:
- name: NSQLOOKUP_ADDRESS
    value: nsqlookup.default.svc.cluster.local
- name: NSQ_BROADCAST_ADDRESS
    value: nsqd.default.svc.cluster.local
command: ["/nsqd"]
args: ["--lookupd-tcp-address=$(NSQLOOKUP_ADDRESS):4160", "--broadcast-address=$(NSQ_BROADCAST_ADDRESS)"]
複製代碼

你能夠看到設置了 lookup-tcp-address 和 broadcast-address 這兩個參數。查找 tcp 地址是 nsqlookupd 服務的 DNS。廣播地址是必要的,就像 nsqlookupd 同樣,因此回調工做正常。

面向大衆的服務

如今,這是我第一次部署面向公衆的服務。這裏有兩種選擇。我可使用 LoadBalancer,由於這個 API 能夠承受很大的負載。若是這將在生產環境部署,那麼它應該使用這一個。

我在本地作只部署單個節點的,因此稱爲「NodePort」就足夠了。一個 NodePort 在一個靜態端口上暴露每一個節點 IP 上的服務。若是未指定,它將在 30000-32767 之間的主機上分配一個隨機端口。但它也能夠被配置爲一個特定的端口,在模板文件中使用 nodePort。要使用此服務,請使用 <NodeIP>:<NodePort>。若是配置了多個節點,則 LoadBalancer 能夠將它們複用到單個 IP。

有關更多信息,請查看此文檔:Publishing Service

綜合起來,咱們會獲得一個接收服務,其模板以下:

apiVersion: v1
kind: Service
metadata:
    name: receiver-service
spec:
    ports:
    - protocol: TCP
    port: 8000
    targetPort: 8000
    selector:
    app: receiver
    type: NodePort
複製代碼

對於 8000 上的固定節點端口,必須提供 nodePort 的定義:

apiVersion: v1
kind: Service
metadata:
    name: receiver-service
spec:
    ports:
    - protocol: TCP
    port: 8000
    targetPort: 8000
    selector:
    app: receiver
    type: NodePort
    nodePort: 8000
複製代碼

圖像處理器

圖像處理器是我處理傳遞圖像以識別的地方。它應該有權訪問 nsqlookupd,mysql 和人臉識別服務的 gRPC 端點。這其實是至關無聊的服務。事實上,它甚至不是一項服務。它不會公開任何內容,所以它是第一個部署的組件。爲簡潔起見,如下是整個模板:

---
apiVersion: apps/v1
kind: Deployment
metadata:
    name: image-processor-deployment
spec:
    selector:
    matchLabels:
        app: image-processor
    replicas: 1
    template:
    metadata:
        labels:
        app: image-processor
    spec:
        containers:
        - name: image-processor
        image: skarlso/kube-processor-alpine:latest
        env:
        - name: MYSQL_CONNECTION
            value: "mysql.default.svc.cluster.local"
        - name: MYSQL_USERPASSWORD
            valueFrom:
            secretKeyRef:
                name: kube-face-secret
                key: mysql_userpassword
        - name: MYSQL_PORT
            # TIL: 若是這裏的 3306 沒有引號,kubectl 會出現錯誤
            value: "3306"
        - name: MYSQL_DBNAME
            value: kube
        - name: NSQ_LOOKUP_ADDRESS
            value: "nsqlookup.default.svc.cluster.local:4161"
        - name: GRPC_ADDRESS
            value: "face-recog.default.svc.cluster.local:50051"
複製代碼

這個文件中惟一有趣的地方是用於配置應用程序的大量環境屬性。請注意 nsqlookupd 地址和 grpc 地址。

要建立此部署,請運行:

kubectl apply -f image_processor.yaml
複製代碼

人臉識別

人臉識別別服務是一個簡單的,只有圖像處理器才須要的服務。它的模板以下:

apiVersion: v1
kind: Service
metadata:
    name: face-recog
spec:
    ports:
    - protocol: TCP
    port: 50051
    targetPort: 50051
    selector:
    app: face-recog
    clusterIP: None
複製代碼

更有趣的部分是它須要兩個 volume。這兩 volume 是 known_peopleunknown_people。你能猜到他們將包含什麼嗎?是的,圖像。「known_people」 volume 包含與數據庫中已知人員關聯的全部圖像。unknown_people volume 將包含全部新圖像。這就是咱們從接收器發送圖像時須要使用的路徑; 那就是掛載點所指向的地方,在個人狀況下是 / unknown_people。 基本上,路徑必須是人臉識別服務能夠訪問的路徑。

如今,經過 Kubernetes 和 Docker部署 volume 很容易。它能夠是掛載的 S3 或某種類型的 nfs,也能夠是從主機到客戶機的本地掛載。也會存在其餘可能性。爲了簡單起見,我將使用本地安裝。

安裝一個 volume 分兩部分完成。首先,Dockerfile 必須指定 volume:

VOLUME [ "/unknown_people", "/known_people" ]
複製代碼

其次,Kubernetes 模板須要在 MySQL 服務中添加 volumeMounts,不一樣之處在於 hostPath 並非聲稱的 volume:

volumeMounts:
- name: known-people-storage
    mountPath: /known_people
- name: unknown-people-storage
    mountPath: /unknown_people
volumes:
- name: known-people-storage
hostPath:
    path: /Users/hannibal/Temp/known_people
    type: Directory
- name: unknown-people-storage
hostPath:
    path: /Users/hannibal/Temp/
    type: Directory
複製代碼

咱們還須要爲人臉識別服務設置 known_people 文件夾配置。這是經過環境變量完成的:

env:
- name: KNOWN_PEOPLE
    value: "/known_people"
複製代碼

而後 Python 代碼將查找圖像,以下所示:

known_people = os.getenv('KNOWN_PEOPLE', 'known_people')
print("Known people images location is: %s" % known_people)
images = self.image_files_in_folder(known_people)
複製代碼

其中 image_files_in_folder 函數以下:

def image_files_in_folder(self, folder):
    return [os.path.join(folder, f) for f in os.listdir(folder) if re.match(r'.*\.(jpg|jpeg|png)', f, flags=re.I)]
複製代碼

Neat.

如今,若是接收方收到一個請求(並將其發送到更遠的線路),與下面的請求相似。

curl -d '{"path":"/unknown_people/unknown220.jpg"}' http://192.168.99.100:30251/image/post
複製代碼

它會在 / unknown_people 下尋找名爲 unknown220.jpg 的圖像,在 unknown_folder 中找到與未知圖像中的人相對應的圖像,並返回匹配圖像的名稱。

查看日誌,你會看到以下內容:

# Receiver
❯ curl -d '{"path":"/unknown_people/unknown219.jpg"}' http://192.168.99.100:30251/image/post
got path: {Path:/unknown_people/unknown219.jpg}
image saved with id: 4
image sent to nsq

# Image Processor
2018/03/26 18:11:21 INF    1 [images/ch] querying nsqlookupd http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images
2018/03/26 18:11:59 Got a message: 4
2018/03/26 18:11:59 Processing image id:  4
2018/03/26 18:12:00 got person:  Hannibal
2018/03/26 18:12:00 updating record with person id
2018/03/26 18:12:00 done
複製代碼

這樣,全部服務就部署完成了。

前端

最後,還有一個小型的 web 應用程序,它可以方便地展現數據庫中的信息。這也是一個面向公衆的接收服務,其參數與接收器相同。

它看起來像這樣:

frontend

總結

咱們如今正處於部署一系列服務的階段。回顧一下我迄今爲止使用的命令:

kubectl apply -f mysql.yaml
kubectl apply -f nsqlookup.yaml
kubectl apply -f receiver.yaml
kubectl apply -f image_processor.yaml
kubectl apply -f face_recognition.yaml
kubectl apply -f frontend.yaml
複製代碼

因爲應用程序不會在啓動時分配鏈接,所以能夠按任意順序排列。(除了 image_processor 的 NSQ 消費者。)

若是沒有錯誤,使用 kubectl get pods 查詢運行 pod 的 kube 應該顯示以下:

❯ kubectl get pods
NAME                                          READY     STATUS    RESTARTS   AGE
face-recog-6bf449c6f-qg5tr                    1/1       Running   0          1m
image-processor-deployment-6467468c9d-cvx6m   1/1       Running   0          31s
mysql-7d667c75f4-bwghw                        1/1       Running   0          36s
nsqd-584954c44c-299dz                         1/1       Running   0          26s
nsqlookup-7f5bdfcb87-jkdl7                    1/1       Running   0          11s
receiver-deployment-5cb4797598-sf5ds          1/1       Running   0          26s
複製代碼

運行中的 minikube service list

❯ minikube service list
|-------------|----------------------|-----------------------------|
|  NAMESPACE  |         NAME         |             URL             |
|-------------|----------------------|-----------------------------|
| default     | face-recog           | No node port                |
| default     | kubernetes           | No node port                |
| default     | mysql                | No node port                |
| default     | nsqd                 | No node port                |
| default     | nsqlookup            | No node port                |
| default     | receiver-service     | http://192.168.99.100:30251 |
| kube-system | kube-dns             | No node port                |
| kube-system | kubernetes-dashboard | http://192.168.99.100:30000 |
|-------------|----------------------|-----------------------------|
複製代碼

滾動更新

滾動更新過程當中會發生什麼?

kube rotate

正如在軟件開發過程當中發生的那樣,系統的某些部分須要/須要進行更改。那麼,若是我改變其中一個組件而不影響其餘組件,同時保持向後兼容性而不中斷用戶體驗,咱們的集羣會發生什麼?幸運的是 Kubernetes 能夠提供幫助。

我詬病的是 API 一次只能處理一個圖像。不幸的是,這裏沒有批量上傳選項。

代碼

目前,咱們有如下處理單個圖像的代碼段:

// PostImage 處理圖像的文章。 將其保存到數據庫
// 並將其發送給 NSQ 以供進一步處理。
func PostImage(w http.ResponseWriter, r *http.Request) {
...
}

func main() {
    router := mux.NewRouter()
    router.HandleFunc("/image/post", PostImage).Methods("POST")
    log.Fatal(http.ListenAndServe(":8000", router))
}
複製代碼

咱們有兩種選擇:用 / images / post 添加一個新端點,並讓客戶端使用它,或者修改現有的端點。

新客戶端代碼的優點在於,若是新端點不可用,它能夠退回到提交舊的方式。然而,舊客戶端代碼沒有這個優點,因此咱們沒法改變咱們的代碼如今的工做方式。考慮一下:你有90臺服務器,而且你作了一個緩慢的滾動更新,在更新的同時一次只取出一臺服務器。若是更新持續一分鐘左右,整個過程大約須要一個半小時才能完成(不包括任何並行更新)。

在此期間,你的一些服務器將運行新代碼,其中一些將運行舊代碼。調用是負載均衡的,所以你沒法控制哪些服務器會被擊中。若是客戶試圖以新的方式進行調用,但會觸及舊服務器,則客戶端將失敗。客戶端能夠嘗試並回退,可是因爲你刪除了舊版本,它將不會成功,除非很巧合地命中了運行新代碼的服務器,用新代碼命中服務器(假設沒有設置粘滯會話)。

另外,一旦全部服務器都更新完畢,舊客戶端將沒法再使用你的服務。

如今,你能夠爭辯說,你不想永遠保留你的代碼的舊版本。這在某種意義上是正確的。這就是爲何咱們要修改舊代碼,只需稍微增長一點就能夠調用新代碼。這樣,一旦全部客戶端都被遷移了,代碼就能夠簡單地被刪除而不會有任何問題。

新的端點

咱們來添加一個新的路徑方法:

...
router.HandleFunc("/images/post", PostImages).Methods("POST")
...
複製代碼

更新舊版本以調用帶有修改後版本的新版本,以下所示:

// PostImage 處理圖像的文章。 將其保存到數據庫
// 並將其發送給 NSQ 以供進一步處理。
func PostImage(w http.ResponseWriter, r *http.Request) {
    var p Path
    err := json.NewDecoder(r.Body).Decode(&p)
    if err != nil {
        fmt.Fprintf(w, "got error while decoding body: %s", err)
        return
    }
    fmt.Fprintf(w, "got path: %+v\n", p)
    var ps Paths
    paths := make([]Path, 0)
    paths = append(paths, p)
    ps.Paths = paths
    var pathsJSON bytes.Buffer
    err = json.NewEncoder(&pathsJSON).Encode(ps)
    if err != nil {
        fmt.Fprintf(w, "failed to encode paths: %s", err)
        return
    }
    r.Body = ioutil.NopCloser(&pathsJSON)
    r.ContentLength = int64(pathsJSON.Len())
    PostImages(w, r)
}
複製代碼

那麼,命名可能會更好,但你應該獲得基本的想法。我正在修改傳入的單個路徑,將它包裝成新的格式併發送給新的端點處理程序。就是這樣! 還有一些修改。要查看它們,請查看此PR:Rolling Update Bulk Image Path PR

如今,能夠經過兩種方式調用接收器:

# 單個路徑:
curl -d '{"path":"unknown4456.jpg"}' http://127.0.0.1:8000/image/post

# 多個路徑:
curl -d '{"paths":[{"path":"unknown4456.jpg"}]}' http://127.0.0.1:8000/images/post
複製代碼

在這裏,客戶端是 curl。一般狀況下,若是客戶端是個服務,我會改一下,在新的路徑拋出 404 時能夠再試試老的路徑。

爲簡潔起見,我不修改 NSQ 和其餘用來批量圖像處理的操做,他們仍然會一個一個接收。這就看成業留給大家來作了。

新的鏡像

要執行滾動更新,我必須首先從接收器服務建立一個新鏡像。

docker build -t skarlso/kube-receiver-alpine:v1.1 .
複製代碼

一旦完成,咱們能夠開始推出更改。

滾動更新

在 Kubernetes 中,您能夠經過多種方式配置滾動更新:

手動更新

若是我在個人配置文件中使用了一個名爲 v1.0 的容器版本,那麼更新只是簡單地調用:

kubectl rolling-update receiver --image:skarlso/kube-receiver-alpine:v1.1
複製代碼

若是在部署期間出現問題,咱們老是能夠回滾。

kubectl rolling-update receiver --rollback
複製代碼

它將恢復之前的版本。 不須要大驚小怪,沒有任何麻煩。

應用一個新的配置文件

手動更新的問題在於它們不在源代碼控制中。

考慮一下:因爲手動進行「快速修復」,一些服務器獲得了更新,但沒有人目擊它,而且沒有記錄。另外一我的出現並對模板進行更改並將模板應用到羣集。全部服務器都會更新,而後忽然出現服務中斷。

長話短說,更新後的服務器已經被覆蓋,由於該模板沒有反映手動完成的工做。

推薦的方法是更改​​模板以使用新版本,並使用 apply 命令應用模板。

Kubernetes 建議使用 ReplicaSets 進行部署應處理分發這意味着滾動更新必須至少有兩個副本。若是少於兩個副本存在,則更新將不起做用(除非 maxUnavailable 設置爲 1)。我增長了 yaml 的副本數量。我還爲接收器容器設置了新的鏡像版本。

replicas: 2
...
    spec:
        containers:
        - name: receiver
        image: skarlso/kube-receiver-alpine:v1.1
...
複製代碼

看看處理狀況,這是你應該看到的:

❯ kubectl rollout status deployment/receiver-deployment
Waiting for rollout to finish: 1 out of 2 new replicas have been updated...
複製代碼

您能夠經過指定模板的 strategy 部分添加其餘部署配置設置,以下所示:

strategy:
type: RollingUpdate
rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0
複製代碼

有關滾動更新的更多信息,請參見如下文檔:Deployment Rolling Update, Updating a Deployment, Manage Deployments, Rolling Update using ReplicaController

MINIKUBE 的用戶注意:因爲咱們在具備一個節點和一個應用程序副本的本地機器上執行此操做,咱們必須將 maxUnavailable 設置爲 1; 不然 Kubernetes 將不容許更新發生,而且新版本將保持 Pending 狀態。這是由於咱們不容許存在沒有運行容器的服務,這基本上意味着服務中斷。

Scaling

用 Kubernetes 來 scaling 比較容易。因爲它正在管理整個集羣,所以基本上只需將一個數字放入所需副本的模板中便可使用。

迄今爲止這是一篇很棒的文章,但時間太長了。我正在計劃編寫一個後續行動,我將經過多個節點和副本真正擴展 AWS 的功能; 再加上 Kops 部署 Kubernetes 集羣。敬請期待!

清理

kubectl delete deployments --all
kubectl delete services -all
複製代碼

寫在最後

女士們,先生們。咱們用 Kubernetes 編寫,部署,更新和擴展了(固然還不是真的)分佈式應用程序。

若是您有任何問題,請隨時在下面的評論中討論。我很是樂意解答。

我但願你享受閱讀它,雖然這很長, 我正在考慮將它分紅多篇博客,可是一個總體的單頁指南是有用的,而且能夠很容易地找到,保存和打印。

感謝您的閱讀。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索