這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。[上一篇博文](https://studygolang.com/articles/12799) 我們看了看用 [Terraform](https://terraform.io/) 建立容器引擎叢集。在本篇博文裡,我們看看使用容器引擎和 [Kubernetes](https://kubernetes.io/) 部署容器到叢集裡。## Kubernetes首先,什麼是 [Kubernetes](https://kubernetes.io/) ? [Kubernetes](https://kubernetes.io/) 是一個開源的、管理容器的架構。與平台無關,就是說著你可以在你本機上,在 AWS 或者 Google Cloud,任何其他的平台運行它。(Kubernetes)能讓你通過使用聲明的配置內容,控制一組容器,和容器的網路規則。你只需要寫個 yaml/json 檔案,描述下需要在哪運行哪個容器。定義你的網路規則,比如連接埠轉寄。它就會幫你管理服務發現。Kubernetes 是雲情境的重要補充,而且現在正迅速成為雲容器管理實際選擇。因此瞭解下是比較好的。那麼我們開始吧!首先,確保你已經在本地安裝了 kubectl cli:```$ gcloud components install kubectl```現在確保你串連到叢集,並且認證正確。第一步,我們登入進去,確保已被認證。第二步我們設定下項目配置,確保我們使用正確的項目 ID 和可訪問地區。```$ echo "This command will open a web browser, and will ask you to login$ gcloud auth application-default login$ gcloud config set project shippy-freight$ gcloud config set compute/zone eu-west2-a$ echo "Now generate a security token and access to your KB cluster"$ gcloud container clusters get-credentials shippy-freight-cluster```在上面的命令中,你可以將 compute/zone 替換成你選的任何地區,你的項目 id 和叢集名稱也可以和我的不一樣。下面是個概括描述...```$ echo "This command will open a web browser, and will ask you to login$ gcloud auth application-default login$ gcloud config set project <project-id>$ gcloud config set compute/zone <availability-zone>$ echo "Now generate a security token and access to your KB cluster"$ gcloud container clusters get-credentials <cluster-name>```點這你可以看到項目 ID...![](https://raw.githubusercontent.com/studygolang/gctt-images/master/go-micro/Screen-Shot-2018-03-17-at-17.55.41.png)現在找下我們的項目 ID...![](https://raw.githubusercontent.com/studygolang/gctt-images/master/go-micro/Screen-Shot-2018-03-17-at-17.56.35.png)叢集地區 region/zone 和叢集名稱可以點菜單左上方的 'ComputeEngine',然後選 'VM Instances' 就找到了。 你能看到你的 Kubernetes VM,點進去看更多細節,這能看見和你叢集相關的每個內容。如果你運行下...```$ kubectl get pods```你會看到 ... `No resources found.`。沒關係,我們還沒有部署任何內容。我們可以想想我們需要實際部署些什麼。我們需要一個 Mongodb 執行個體。一般來說,我們會部署一個 mongodb 執行個體,或者為了完全分離,將資料庫執行個體和每個服務放一起。但是這個例子裡,我們耍點小聰明,就用一個中心化的執行個體。這是個單一故障點,但是在實際應用情境中,你要考慮下將資料庫執行個體分開部署,和服務保持一致。不過我們這種方法也可以。然後我需要部署服務了,vessel 服務,user 服務,consignment 服務和 email 服務。好了,很簡單!從 Mongodb 執行個體開始吧。因為它不屬於一個單獨的服務,而且這是的平台整體的一部分,我們把這些部署放在 shippy-infrastructure 倉庫下。這個倉庫我提交到了 Github ,因為包含了很多敏感性資料,但是我可以給你們所有的部署檔案。首先,我們需要一個配置建立一個 ssd,用於長期儲存。這樣當我們重啟容器的時候就不會遺失資料。```// shippy-infrastructure/deployments/mongodb-ssd.ymlkind: StorageClassapiVersion: storage.k8s.io/v1beta1metadata:name: fastprovisioner: kubernetes.io/gce-pdparameters:type: pd-ssd```然後是我們的部署檔案(我們通過本文來深入更多細節)...```// shippy-infrastructure/deployments/mongodb-deployment.ymlapiVersion: apps/v1beta1kind: StatefulSetmetadata:name: mongospec:serviceName: "mongo"replicas: 3selector:matchLabels:app: mongotemplate:metadata:labels:app: mongorole: mongospec:terminationGracePeriodSeconds: 10containers:- name: mongoimage: mongocommand:- mongod- "--replSet"- rs0- "--smallfiles"- "--noprealloc"- "--bind_ip"- "0.0.0.0"ports:- containerPort: 27017volumeMounts:- name: mongo-persistent-storagemountPath: /data/db- name: mongo-sidecarimage: cvallance/mongo-k8s-sidecarenv:- name: MONGO_SIDECAR_POD_LABELSvalue: "role=mongo,environment=test"volumeClaimTemplates:- metadata:name: mongo-persistent-storageannotations:volume.beta.kubernetes.io/storage-class: "fast"spec:accessModes: [ "ReadWriteOnce" ]resources:requests:storage: 10Gi```然後是 service 檔案...```apiVersion: v1kind: Servicemetadata:name: mongolabels:name: mongospec:ports:- port: 27017targetPort: 27017clusterIP: Noneselector:role: mongo```還有很多,現在對你來說可能沒什麼意義。那麼我們試試理清一些 Kubernetes 的關鍵概念。## Nodes ##**[閱讀此文](https://kubernetes.io/docs/concepts/architecture/nodes/)**Nodes 是你的物理機或者 VM,你的容器通過 node 做叢集,服務通過運行在不同 node/pod 上的一組組容器互相訪問。## Pods ##**[閱讀此文](https://kubernetes.io/docs/concepts/workloads/pods/pod/)**Pod 是一組相關的容器。比如,一個 pod 可以包含你的認證服務容器,使用者資料庫容器,登陸註冊使用者介面等等。這些容器都是明顯有相關性的。Pod 允許你將他們組合在一起,這樣他們能互相訪問,並且運行在相同的即時網路環境下,你可以把他們當做一個整體。這很酷啊!Pod 是 Kubernetes 裡非常不容易理解的特性。## Deployment ##**[閱讀此文](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)**Deployment 是用來控制狀態的,一個 deployment 就是最終要輸出和要保持的狀態的描述檔案。一個 deployment 是 Kubernetes 的介紹,比如說,我想要三個容器,運行在三個連接埠,用某些環境變數。Kubernetes 會確保維持這個狀態。如果一個容器崩潰了,剩下兩個容器,它會再啟動一個滿足三個容器的需求。## StatefulSet ##**[閱讀此文](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)**stateful set 和 deployment 有相似的地方,除了它會用一些儲存方式,保持容器相關的狀態。比如分布儲存的概念。實際情況裡,Mongodb 將資料寫入位元據儲存格式,很多資料庫都是這樣做的。建立一個可回收的資料庫執行個體,比如 docker 容器。如果容器重啟資料會丟失。一般來說你需要在容器啟動的時候使用分卷裝載資料/檔案。你可以在 Kubernetes 上做這些部署。但是 StatefulSets,在相關的叢集點有一些額外的自動化操作。因此這個對 mongodb 容器天然的合適。## Service ##**[閱讀此文](https://kubernetes.io/docs/concepts/services-networking/service/)**服務是一組網路相關的規則,比如連接埠轉寄和 DNS 規則,在網路層面上串連你的 pod,控制誰和誰可以通訊,誰可以被外部存取。有兩種服務你可能會遇到,一是 load balancer,一是 node port。load balancer,是一個輪詢的負載平衡器,可以給你選的 node 節點建立一個 IP 位址給代理。通過代理把服務暴露給外部。node port 將 pod 暴露給上層的網路環境,這樣他們可以被其他服務, 內部的pod/執行個體訪問。這樣對暴露 node 給其他的 pod 來說是有用的。這就是你能用來允許服務和其他服務通訊的方式。這就是服務發現的本質。至少是一部分。現在我們剛看了一點點 Kubernetes 的內容,我們來再多談些,再挖掘挖掘。值得注意的是,如果你是個在本機上使用 docker ,比如如果你用的是 mac/windows 上的 docker 的 edge 版本,你可以把 Kubernetes 叢集釘在本機上。測試時候很有用。那麼我們已經建立了三個檔案,一個用於儲存,一個用於 stateful set,一個用於我們的服務。最後結果是有 mongodb 容器的副本,stateful 儲存和通過 pod 保留給資料存放區的服務。我們繼續看看,建立,按正確的順序,因為有些操作是需要依賴前面建立的內容。```echo "shippy-infrastructure"$ kubectl create -f ./deployments/mongodb-ssd.yml$ kubectl create -f ./deployments/mongodb-deployment.yml$ kubectl create -f ./deployments/mongodb-service.yml```等幾分鐘,你可以查下 mongodb 容器的狀態,運行:```$ kubectl get pods```你可能注意到你的 pod 狀態是'pending'。如果運行 `$ kubectl describe node` 你會看到關於 CPU 不足的錯誤。很尷尬,有些叢集管理和 Kubernetes 工具對 CPU 很敏感。所以一台 node 可能不夠,mongo 的執行個體也一樣。那麼我們給叢集開啟自動擴容,預設是一個池。為了達到目的,需要到 Google Cloud Console,選擇 Kubernetes 引擎,編輯你的執行個體,開啟自動擴容,設定最小值和最大值為 2,然後點儲存。![](https://raw.githubusercontent.com/studygolang/gctt-images/master/go-micro/Screen-Shot-2018-03-17-at-20.36.17.png)過幾分鐘,你的 node 節點會擴成兩個,運行 `$ kubectl get pods` 你會看到 'ContainerCreating'。直到所有的容器以期待的方式運行。現在我們有了資料庫叢集,一個自動擴容的 Kubernetes 引擎,我們來部署一些服務吧!## Vessel 服務 ##vessel 服務很輕量級,沒做太多事情,也沒有依賴,因此適合上手。首先,我們稍微改動 vessel 服務上的一些程式碼片段。```// shippy-vessel-service/main.goimport ( ... k8s "github.com/micro/kubernetes/go/micro" )func main() { ... // Replace existing service var with... srv := k8s.NewService(micro.Name("shippy.vessel"),micro.Version("latest"),)}```我們在這做的所有事情,就是使用了 import 的新庫 `k8s.NewService` 覆蓋了已有的 `micro.NewService()`。那麼新庫是什麼呢?## Kubernetes 中的微服務 ##我喜歡 micro 中的一點,因為它對 cloud 有很深理解而構建,能一直適應新技術。Micro 很重視 Kubernetes,因此建立了一個 micro 的 [Kubernetes 庫](https://github.com/micro/kubernetes/)。實際情況是,所有的庫實際上都是 micro,配置了 Kuberntes 的一些合理的預設值,和一個直接整合在 Kubernetes 服務之上的 service selector。也就是說它把服務發現交給了 Kubernetes。預設用 gRPC 作為預設 transport 。 當然你也可以使用環境變數和 plugin 來覆蓋這些狀態。在 Micro 的世界裡還有很多讓人著迷的功能,這也是讓我很興奮的地方。一定要加入 [slack channel](http://slack.micro.mu/)。現在我們在服務上建立一個部署服務,在這我們要稍微瞭解下關於每個部分作用的細節。```// shippy-vessel-service/deployments/deployment.ymlapiVersion: apps/v1beta1kind: Deploymentmetadata: name: vesselspec: replicas: 1 selector: matchLabels: app: vessel template: metadata: labels: app: vessel spec: containers: - name: vessel-service image: eu.gcr.io/<project-name>/vessel-service:latest imagePullPolicy: Always command: [ "./shippy-vessel-service", "--selector=static", "--server_address=:8080", ] env: - name: DB_HOST value: "mongo:27017" - name: UPDATED_AT value: "Mon 19 Mar 2018 12:05:58 GMT" ports: - containerPort: 8080 name: vessel-port```這有很多內容,不過我嘗試分解下一部分。首先你能看到 `kind:Deployment`,在 Kubernetes 裡有很多不同的 `things`,大部分可以被看作 'cloud primatives'。在程式設計語言裡,string,interger,struct,method 等等這些類型都是中繼資料。在 Kubernetes 裡就把 cloud 看作是同樣意思。因此把這些看作中繼資料。這樣,一個 deployment,就是控制中繼資料的一種形式。元控制保證你期望的狀態維持正常。deployment 是一個無狀態控制的形式,是不可持續的,在重啟或退出之後資料就被銷毀了。Stateful sets 類似 deployment,除了他們要維持一些待用資料,還有之前聲明過的資料。但是我們的服務不應該包含任何狀態,微服務是無狀態的。因此在這我們需要一個 deployment。下一步你需要一個標準分區,啟動時帶著 deployment 的 metadata,名稱,多少個這種 pod (副本),需要保持(如果其中一個死掉了,假設我們用的比一個多,控制元的工作就是檢查有啟動並執行 pod 數量是我們希望的,如果不在期待狀態就再啟動一個)。Selector 和 template 暴露了 pod 的某些 metadata,可以允許其他服務發現和串連 pod。然後你需要另一個標準分區(有點困惑,不過繼續往下看呀!)。這次是給我們自己的容器,或者分卷,共用 meta 資料等等。在這個服務裡,我們需要啟動一個獨立容器。容器地區是一個數組,因為我們要啟動幾個容器作為 pod 的一部分。是為了組合相關容器。容器的 metadata 一目瞭然,我們從鏡像啟動一個 docker 容器,設定一些環境變數,在運行時傳入一些命令,暴露一個連接埠(用於服務尋找)。你看到我傳入了一個新的命令:`--selector=static`,這是告訴 Kubernetes 微服務設定使用 Kubernetes 用於服務發現和負載平衡。真的很酷,因為現在你的微服務代碼是直接和 Kubernetes 強大的 DNS ,網路,負載平衡和服務發現進行互動。你可以提交這個選項,繼續像之前一樣使用微服務。但是我們也可以用到 Kubernetes 的好處。你也注意到了,我們從一個私人倉庫拉取鏡像。當使用 Google 的容器工具時,你可以擷取一個容器註冊,用來建立你的容器鏡像,推送下,像下面這樣...```$ docker build -t eu.gcr.io/<your-project-name>/vessel-service:latest .$ gcloud docker -- push eu.gcr.io/<your-project-name>/vessel-service:latest```現在看我們的服務...```// shippy-vessel-service/deployments/service.ymlapiVersion: v1kind: Servicemetadata: name: vessel labels: app: vesselspec: ports: - port: 8080 protocol: TCP selector: app: vessel```在這裡,如之前所說的我們有一個 `kind`,在這個 case 下是一個服務元(本質上是一組網路層級的 DNS 和防火牆規則)。然後我們給服務名字和標籤。spec 允許我們給服務定義連接埠,你也可以在這定義一個 `targetPort` 來尋找特定的容器。不過幸好有 Kubernetes/micro 實現,我們可以自動操作。最後,selector 最重要的部分,必須和你目標的 pod 相匹配,否則服務通過代理找不到任何東西,不會工作的。現在我們部署下叢集裡的修改吧。```$ kubectl create -f ./deployments/deployment.yml$ kubectl create -f ./deployments/service.yml```等幾分鐘,然後運行...```$ kubectl get pods$ kubectl get services```你應該能看到你的新 pod,新服務了。確保他們的運行狀態符合期待效果。如果你遇到錯誤,你可以運行 `$kubectl proxy`,然後在瀏覽器開啟 `http://localhost:8001/ui` ,看下 Kubernetes 的 ui,你可以深度探索下容器的狀態等待。在這有一點值得提一下,deployment 是原子操作並且是不可更改的,意思是他們必須通過某種方式更新才能被修改。他們有一個唯一的雜湊值,如果雜湊值沒有變化,deployment 是不會更新的。如果你運行 `$ kubectl replace -f ./deployments/deployment.yml`,什麼也不會發生。因為 Kubernetes 沒有檢測到變化。有很多方法可以規避這種情況,不過需要注意的是,大部分情況下,是你的容器會發生變化,所以不要用 `latest` 標籤,你應該給每一個容器一個唯一的標籤,比如編譯編號,比如:`vessel-service:<build-no>`。 這樣就會標記為修改,deployment 就可以替換了。但是在這個教程裡,我們做點好玩的事,不過要留神,這是一種懶人寫法,而且不是最好的實踐方法。我建立了一個新檔案 `deployments/deployment.tmpl` ,作為 deployment 的模板。然後我設定了一個環境變數 `UPDATED_AT`,值為 `{{ UPDATED_AT }}`。我更新了 Makefile 來開啟模板檔案,通過環境變數設定當前的 date/time ,然後輸出到最終的 deployment.yml 檔案。這有點不規範的感覺,不過只是暫時這麼做。我看過很多方式,你感覺怎麼合適怎麼做吧。```// shippy-vessel-service/Makefiledeploy:sed "s/{{ UPDATED_AT }}/$(shell date)/g" ./deployments/deployment.tmpl > ./deployments/deployment.ymlkubectl replace -f ./deployments/deployment.yml```好了,我們成功了,部署了一個服務,啟動並執行和我們想的一樣。我現在給其他服務也做同樣的操作。我在倉庫裡給每個服務做了簡短的更新,如下...[Consignment service](https://github.com/EwanValentine/shippy-consignment-service)[Email service](https://github.com/EwanValentine/shippy-email-service)[User service](https://github.com/EwanValentine/shippy-user-service)[Vessel service](https://github.com/EwanValentine/shippy-vessel-service)[UI](https://github.com/EwanValentine/shippy-ui)給我們的使用者服務部署 Postgres ...```apiVersion: apps/v1beta2kind: StatefulSetmetadata: name: postgresspec: serviceName: postgres selector: matchLabels: app: postgres replicas: 3 template: metadata: labels: app: postgres role: postgres spec: terminationGracePeriodSeconds: 10 containers: - name: postgres image: postgres ports: - name: postgres containerPort: 5432 volumeMounts: - name: postgres-persistent-storage mountPath: /var/lib/postgresql/data volumeClaimTemplates: - metadata: name: postgres-persistent-storage annotations: volume.beta.kubernetes.io/storage-class: "fast" spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10Gi```Postgres 服務...```apiVersion: v1kind: Servicemetadata: name: postgres labels: app: postgresspec: ports: - name: postgres port: 5432 targetPort: 5432 clusterIP: None selector: role: postgres```Postgres 儲存...```kind: StorageClassapiVersion: storage.k8s.io/v1beta1metadata: name: fastprovisioner: kubernetes.io/gce-pdparameters: type: pd-ssd```## 部署 micro```// shippy-infrastructure/deployments/micro-deployment.ymlapiVersion: apps/v1kind: Deploymentmetadata: name: microspec: replicas: 3 selector: matchLabels: app: micro template: metadata: labels: app: micro spec: containers: - name: micro image: microhq/micro:kubernetes args: - "api" - "--handler=rpc" - "--namespace=shippy" env: - name: MICRO_API_ADDRESS value: ":80" ports: - containerPort: 80 name: port```現在是服務...```// shippy-infrastructure/deployments/micro-service.ymlapiVersion: v1kind: Servicemetadata: name: microspec: type: LoadBalancer ports: - name: api-http port: 80 targetPort: "port" protocol: TCP selector: app: micro```在這些服務裡,我們用了一個 `LoadBalancer` 類型,暴露了一個外部的 load balancer,提供給外部一個 IP 位址。如果你運行 `$ kubectl get services`,等一兩分鐘(你會看到 `pending` 一會),你就有了這個 ip 地址。這是公用部分的,你可以分配一個網域名稱。一旦部署完畢了,讓服務調用 micro:```$ curl localhost/rpc -XPOST -d '{ "request": { "name": "test", "capacity": 200, "max_weight": 100000, "available": true }, "method": "VesselService.Create", "service": "vessel"}' -H 'Content-Type: application/json'```你會看到一個返回 `created: true`。超簡潔!這就是你的 gRPC 服務,被代理並且轉成了 web 友好的格式,使用了分區的 mongodb 執行個體。沒費多大勁!## 部署 UI服務部署的不錯,我們來部署下使用者介面```// shippy-ui/deployments/deployment.ymlapiVersion: apps/v1beta1kind: Deploymentmetadata: name: uispec: replicas: 1 selector: matchLabels: app: ui template: metadata: labels: app: ui spec: containers: - name: ui-service image: ewanvalentine/ui:latest imagePullPolicy: Always env: - name: UPDATED_AT value: "Tue 20 Mar 2018 08:26:39 GMT" ports: - containerPort: 80 name: ui```現在是服務...```// shippy-ui/deployments/service.ymlapiVersion: v1kind: Servicemetadata: name: ui labels: app: uispec: type: LoadBalancer ports: - port: 80 protocol: TCP targetPort: 'ui' selector: app: ui```注意到服務是 80 連接埠上的負載平衡,因為這是一個公用的使用者介面,這就是使用者如何與我們服務互動的。一看就明白!## 最後總結看我們成功了,用 docker 容器和 Kubernetes 管理我們的容器,成功的將整個工程部署到雲端。希望你能從這篇文章發現一些有用的內容,沒有覺得太不好消化。本系列的下一部分,我們將看看把所有這些內容和 CI 進程聯絡起來,來管理我們的 deployment。如果你覺得這個系列文章有用,如果你用了廣告攔截(沒怪你)。請考慮給我的辛苦勞動打個賞吧。共勉![https://monzo.me/ewanvalentine](https://monzo.me/ewanvalentine)或者,在 [Patreon](https://www.patreon.com/ewanvalentine) 上贊助我下吧。
via: https://ewanvalentine.io/microservices-in-golang-part-8/
作者:Ewan Valentine 譯者:ArisAries 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
670 次點擊