K8s 實戰之概念、集羣部署與服務配置

K8s 實戰之概念、集羣部署與服務配置

本文是對於 Kubernetes 實戰系列文章的提煉。

Kubernetes [koo-ber-nay'-tice] 是 Google 基於 Borg 開源的容器編排調度引擎,其支持多種底層容器虛擬化技術,具有完備的功能用於支撐分佈式系統以及微服務架構,同時具備超強的橫向擴容能力;它提供了自動化容器的部署和複製,隨時擴展或收縮容器規模,將容器組織成組,並且提供容器間的負載均衡,提供容器彈性等特性。作爲 CNCF(Cloud Native Computing Foundation)最重要的組件之一,可謂雲操作系統;它的目標不僅僅是一個編排系統,而是提供一個規範,可以讓你來描述集羣的架構,定義服務的最終狀態。

設計理念

與一般的 PaaS 平臺相比,K8s 也是支持服務部署、自動運維、資源調度、擴縮容、自我修復、負載均衡,服務發現等功能,而其獨特之處就是其對於基礎設施層進行了較好的能力抽象。K8s 並沒有處理具體的存儲、網絡這些差異性極大的部分,而是做雲無關,開始實現各類 interface,做各種抽象。比如容器運行時接口(CRI)、容器網絡接口(CNI)、容器存儲接口(CSI)。這些接口讓 Kubernetes 變得無比開放,而其本身則可以專注於內部部署及容器調度。

Kubernetes 有類似於 Linux 的分層架構,如下圖所示:

  • 基礎設施層:包括容器運行時、網絡、存儲等。
  • 核心層:Kubernetes 最核心的功能,對外提供 API 構建高層的應用,對內提供插件式應用執行環境。
  • 應用層:部署(無狀態、有狀態應用、Job 等)和路由(服務發現、負載均衡等)
  • 管理層:系統度量(如基礎設施、容器和網絡的度量),自動化(如自動擴展、動態 Provision 等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy 等)
  • 接口層:kubectl 命令行工具、客戶端 SDK 以及集羣聯邦
  • 生態系統:在接口層之上的龐大容器集羣管理調度的生態系統,可以劃分爲兩個範疇:日誌、監控、配置管理、CI、CD、Workflow、FaaS、OTS 應用、ChatOps 等外部生態以及 CRI、CNI、CSI、鏡像倉庫、Cloud Provider、集羣自身的配置和管理等內部生態。

Kubernetes 中所有的配置都是通過 API 對象的 spec 去設置的,也就是用戶通過配置系統的理想狀態來改變系統,這是 Kubernetes 重要設計理念之一,即所有的操作都是聲明式(Declarative)的而不是命令式(Imperative)的。聲明式操作在分佈式系統中的好處是穩定,不怕丟操作或運行多次,例如設置副本數爲 3 的操作運行多次也還是一個結果,而給副本數加 1 的操作就不是聲明式的,運行多次結果就錯了。

相對於命令式操作,聲明式操作會更穩定且更容易被用戶接受,因爲該 API 中隱含了用戶想要操作的目標對象,而這些對象剛好都是名詞性質的,比如 Service、Deployment、PV 等;且聲明式的配置文件更貼近“人類語言”,比如 YAML、JSON。聲明式的設計理念有助於實現控制閉環,持續觀測、校正,最終將運行狀態達到用戶期望的狀態;感知用戶的行爲並執行。比如修改 Pod 數量,應用升級/回滾等等。調度器是核心,但它只是負責從集羣節點中選擇合適的 Node 來運行 Pods,顯然讓調度器來實現上訴的功能不太合適,而需要有專門的控制器組件來實現。

組件與對象

Kubernetes 的各種功能都離不開它定義的資源對象,這些對象都可以通過 API 被提交到集羣的 Etcd 中。API 的定義和實現都符合 HTTP REST 的格式,用戶可以通過標準的 HTTP 動詞(POST、PUT、GET、DELETE)來完成對相關資源對象的增刪改查。常用的資源對象,比如 Deployment、DaemonSet、Job、PV 等。API 的抽象也意在這部分資源對象的定義。Kubernetes 有新的功能實現,一般會創建新的資源對象,而功能也依託於該對象進行實現。

類別 名稱
資源對象 Pod、ReplicaSet、ReplicationController、Deployment、StatefulSet、DaemonSet、Job、CronJob、HorizontalPodAutoscaling、Node、Namespace、Service、Ingress、Label、CustomResourceDefinition
存儲對象 Volume、PersistentVolume、Secret、ConfigMap
策略對象 SecurityContext、ResourceQuota、LimitRange
身份對象 ServiceAccount、Role、ClusterRole

這裏我們選擇幾個關鍵對象進行介紹。

部署(Deployment)

部署表示用戶對 Kubernetes 集羣的一次更新操作。部署是一個比 RS 應用模式更廣的 API 對象,可以是創建一個新的服務,更新一個新的服務,也可以是滾動升級一個服務。滾動升級一個服務,實際是創建一個新的 RS,然後逐漸將新 RS 中副本數增加到理想狀態,將舊 RS 中的副本數減小到 0 的複合操作;這樣一個複合操作用一個 RS 是不太好描述的,所以用一個更通用的 Deployment 來描述。以 Kubernetes 的發展方向,未來對所有長期伺服型的的業務的管理,都會通過 Deployment 來管理。

服務(Service)

RC、RS 和 Deployment 只是保證了支撐服務的微服務 Pod 的數量,但是沒有解決如何訪問這些服務的問題。如果說 Deployment 是負責保證 Pod 組的正常運行,那麼 Service 就是用於保證以合理的網絡來連接到該組 Pod。

一個 Pod 只是一個運行服務的實例,隨時可能在一個節點上停止,在另一個節點以一個新的 IP 啓動一個新的 Pod,因此不能以確定的 IP 和端口號提供服務。要穩定地提供服務需要服務發現和負載均衡能力。服務發現完成的工作,是針對客戶端訪問的服務,找到對應的的後端服務實例。在 K8 集羣中,客戶端需要訪問的服務就是 Service 對象。每個 Service 會對應一個集羣內部有效的虛擬 IP,集羣內部通過虛擬 IP 訪問一個服務。Service 有三種類型:

  • ClusterIP:默認類型,自動分配一個僅 Cluster 內部可以訪問的虛擬 IP。
  • NodePort:在 ClusterIP 基礎上爲 Service 在每臺機器上綁定一個端口,這樣就可以通過 <NodeIP>:NodePort 來訪問該服務。
  • LoadBalancer:在 NodePort 的基礎上,藉助 Cloud Provider 創建一個外部的負載均衡器,並將請求轉發到 <NodeIP>:NodePort

在 Kubernetes 集羣中微服務的負載均衡是由 Kube-proxy 實現的。Kube-proxy 是 Kubernetes 集羣內部的負載均衡器。它是一個分佈式代理服務器,在 Kubernetes 的每個節點上都有一個;這一設計體現了它的伸縮性優勢,需要訪問服務的節點越多,提供負載均衡能力的 Kube-proxy 就越多,高可用節點也隨之增多。與之相比,我們平時在服務器端做個反向代理做負載均衡,還要進一步解決反向代理的負載均衡和高可用問題。

集羣部署

Kubernetes 實戰系列中我們介紹了 Docker 本地搭建,基於 Ubuntu 手動搭建集羣以及基於 Rancher 快速搭建集羣等方式。使用 Rancher 可以自動和可視化的完成 Kubernetes 集羣的安裝工作,省去的繁瑣的人工安裝過程,然您快速投入的業務開發中。

$ docker run -d --restart=unless-stopped -p 80:80 -p 443:443 rancher/rancher

先在 Master 節點安裝 Rancher server、control、etcd 和 worker。選擇網絡組件爲 Flannel,同時在自定義主機運行命令中選擇主機角色、填寫主機的內網和外網 IP。

我們需要將腳本複製到對應的機器上運行,然後 Rancher 將自動創建 Kubernetes 集羣,並默認在 80 端口運行 Web Server。添加 Node 節點時只需要在 Rancher 的 Web 界面上找到您剛安裝的集羣並選擇【編輯集羣】並選擇節點角色爲 Worker 即可增加一臺 Kubenretes 集羣節點。

Helm

Helm 是由 Deis 發起的一個開源工具,有助於簡化部署和管理 Kubernetes 應用。在本章的實踐中,我們也會使用 Helm 來簡化很多應用的安裝操作。

在 Linux 中可以使用 Snap 安裝 Heml:

$ sudo snap install helm --classic

# 通過鍵入如下命令,在 Kubernetes 羣集上安裝 Tiller
$ helm init --upgrade

在缺省配置下, Helm 會利用 "gcr.io/kubernetes-helm/tiller" 鏡像在 Kubernetes 集羣上安裝配置 Tiller;並且利用 "https://kubernetes-charts.storage.googleapis.com" 作爲缺省的 stable repository 的地址。由於在國內可能無法訪問 "gcr.io", "storage.googleapis.com" 等域名,阿里雲容器服務爲此提供了鏡像站點。請執行如下命令利用阿里雲的鏡像來配置 Helm:

$ helm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.5.1 --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

# 刪除默認的源
$ helm repo remove stable

# 增加新的國內鏡像源
$ helm repo add stable https://burdenbear.github.io/kube-charts-mirror/
$ helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

# 查看 Helm 源添加情況
$ helm repo list

Helm 的常見命令如下:

# 查看在存儲庫中可用的所有 Helm Charts
$ helm search

# 更新 Charts 列表以獲取最新版本
$ helm repo update

# 查看某個 Chart 的變量
$ helm inspect values stable/mysql

# 查看在羣集上安裝的 Charts 列表
$ helm list

# 刪除某個 Charts 的部署
$ helm del --purge wordpress-test

# 爲 Tiller 部署添加授權
$ kubectl create serviceaccount --namespace kube-system tiller
$ kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
$ kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

kubectl

信息檢索

get 命令用於獲取集羣的一個或一些 resource 信息。使用--help 查看詳細信息。kubectl 的幫助信息、示例相當詳細,而且簡單易懂。建議大家習慣使用幫助信息。kubectl 可以列出集羣所有 resource 的詳細。resource 包括集羣節點、運行的 pod,ReplicationController,service 等。

$ kubectl get [(-o|--output=)json|yaml|wide|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE [NAME | -l label] | TYPE/NAME ...) [flags] [flags]

運行與管理

kubectl run 和 docker run 一樣,它能將一個鏡像運行起來,我們使用 kubectl run 來將一個 sonarqube 的鏡像啓動起來。

$ kubectl run sonarqube --image=sonarqube:5.6.5 --replicas=1 --port=9000

deployment "sonarqube" created

# 該命令爲我們創建了一個 Deployment
$ kubectl get deployment
NAME        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
sonarqube   1         1         1            1           5m

我們也可以直接以交互方式運行某個鏡像:

$ kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il

K8s 將鏡像運行在 Pod 中以方便實施卷和網絡共享等管理,使用 get pods 可以清楚的看到生成了一個 Pod:

$ kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
sonarqube-1880671902-s3fdq   1/1       Running   0          6m

$ 交互式運行 Pod 中的某個命令
$ kubectl exec -it sonarqube-1880671902-s3fdq -- /bin/bash

kubectl 可以用於刪除創建好的 Deployment 與 Pod:

$ kubectl delete pods sonarqube-1880671902-s3fdq
$ kubectl delete deployment sonarqube

kubectl 通用可以基於 Yaml 文件進行應用的生命週期管理:

# 創建
$ kubectl create -f yamls/mysql.yaml

# 刪除
$ kubectl delete -f yamls/mysql.yaml

# 同時創建多個
$ kubectl create -f yamls/

# 同時刪除多個
$ kubectl delete -f yamls/

上下文切換

在 K8s 集羣安裝完畢之後,可以下載集羣的配置文件到本地 kubectl 配置中:

mkdir $HOME/.kube
scp root@<master-public-ip>:/etc/kubernetes/kube.conf $HOME/.kube/config

然後可以來查看當前的上下文

$ unset KUBECONFIG
$ kubectl config current-context # 查看當前載入的上下文
$ kubectl config get-contexts # 瀏覽可用的上下文
$ kubectl config use-context context-name # 切換到指定上下文

服務配置

Kubernetes 實戰/典型應用一節中,我們介紹了許多常見的中間件的配置部署方式。這裏以簡單的 HTTP 服務器爲例,介紹常見的服務配置流程。

Deployment & Service

K8s Boilerplates 中我們定義了簡單的 Nginx 的部署與服務,分別用於集羣構建與對外的服務暴露:

# nginx-deployment-service.yaml
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: nginx
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nginx
  replicas: 3 # tells deployment to run 1 pods matching the template
  template: # create pods using pod definition in this template
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  externalTrafficPolicy: Local
  ports:
    - name: http
      port: 80
  selector:
    app: nginx
  type: NodePort
$ kubectl create -f https://raw.githubusercontent.com/wx-chevalier/Backend-Boilerplates/master/K8s/Base/nginx-deployment-service.yaml

$ kubectl get pod

NAME                                             READY   STATUS    RESTARTS   AGE
nginx-56db997f77-2q6qz                           1/1     Running   0          3m21s
nginx-56db997f77-fv2zs                           1/1     Running   0          3m21s
nginx-56db997f77-wx2q5                           1/1     Running   0          3m21s

$ kubectl get deployment

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
nginx                           3/3     3            3           3m36s

$ kubectl get svc

NAME                            TYPE           CLUSTER-IP      EXTERNAL-IP                              PORT(S)                      AGE
kubernetes                      ClusterIP      10.43.0.1       <none>                                   443/TCP                      21h
nginx                           NodePort       10.43.8.50      <none>                                   80:32356/TCP                 4m5s

Ingress

Ingress 是一種 Kubernetes 資源,也是將 Kubernetes 集羣內服務暴露到外部的一種方式。ngress 只是一個統稱,其由 Ingress 和 Ingress Controller 兩部分組成。Ingress 用作將原來需要手動配置的規則抽象成一個 Ingress 對象,使用 YAML 格式的文件來創建和管理。Ingress Controller 用作通過與 Kubernetes API 交互,動態的去感知集羣中 Ingress 規則變化。

目前可用的 Ingress Controller 類型有很多,比如:Nginx、HAProxy、Traefik 等,Nginx Ingress 使用 ConfigMap 來管理 Nginx 配置。

Helm 安裝 Ingress

$ helm install --name nginx-ingress --set "rbac.create=true,controller.service.externalIPs[0]=172.19.157.1,controller.service.externalIPs[1]=172.19.157.2,controller.service.$
xternalIPs[2]=172.19.157.3" stable/nginx-ingress

NAME:   nginx-ingress
LAST DEPLOYED: Tue Aug 20 14:50:13 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME                      DATA  AGE
nginx-ingress-controller  1     0s

==> v1/Pod(related)
NAME                                            READY  STATUS             RESTARTS  AGE
nginx-ingress-controller-5f874f7bf4-nvsvv       0/1    ContainerCreating  0         0s
nginx-ingress-default-backend-6f598d9c4c-vj4v8  0/1    ContainerCreating  0         0s

==> v1/Service
NAME                           TYPE          CLUSTER-IP    EXTERNAL-IP                             PORT(S)                     AGE
nginx-ingress-controller       LoadBalancer  10.43.115.59  172.19.157.1,172.19.157.2,172.19.157.3  80:32122/TCP,443:32312/TCP  0s
nginx-ingress-default-backend  ClusterIP     10.43.8.65    <none>                                  80/TCP                      0s

==> v1/ServiceAccount
NAME           SECRETS  AGE
nginx-ingress  1        0s

==> v1beta1/ClusterRole
NAME           AGE
nginx-ingress  0s

==> v1beta1/ClusterRoleBinding
NAME           AGE
nginx-ingress  0s

==> v1beta1/Deployment
NAME                           READY  UP-TO-DATE  AVAILABLE  AGE
nginx-ingress-controller       0/1    1           0          0s
nginx-ingress-default-backend  0/1    1           0          0s

==> v1beta1/PodDisruptionBudget
NAME                           MIN AVAILABLE  MAX UNAVAILABLE  ALLOWED DISRUPTIONS  AGE
nginx-ingress-controller       1              N/A              0                    0s
nginx-ingress-default-backend  1              N/A              0                    0s

部署完成後我們可以看到 Kubernetes 服務中增加了 nginx-ingress-controller 和 nginx-ingress-default-backend 兩個服務。nginx-ingress-controller 爲 Ingress Controller,主要做爲一個七層的負載均衡器來提供 HTTP 路由、粘性會話、SSL 終止、SSL 直通、TCP 和 UDP 負載平衡等功能。nginx-ingress-default-backend 爲默認的後端,當集羣外部的請求通過 Ingress 進入到集羣內部時,如果無法負載到相應後端的 Service 上時,這種未知的請求將會被負載到這個默認的後端上。

$ kubectl get svc
NAME                            TYPE           CLUSTER-IP      EXTERNAL-IP                              PORT(S)                      AGE
kubernetes                      ClusterIP      10.43.0.1       <none>                                   443/TCP                      20h
nginx-ingress-controller        LoadBalancer   10.43.115.59    172.19.157.1,172.19.157.2,172.19.157.3   80:32122/TCP,443:32312/TCP   77m
nginx-ingress-default-backend   ClusterIP      10.43.8.65      <none>                                   80/TCP                       77m

$ kubectl --namespace default get services -o wide -w nginx-ingress-controller

NAME                       TYPE           CLUSTER-IP     EXTERNAL-IP                              PORT(S)                      AGE   SELECTOR
nginx-ingress-controller   LoadBalancer   10.43.115.59   172.19.157.1,172.19.157.2,172.19.157.3   80:32122/TCP,443:32312/TCP   77m   app=nginx-ingress,component=controller,release=nginx-ingress

由於我們採用了 externalIP 方式對外暴露服務, 所以 nginx-ingress-controller 會在三臺節點宿主機上的 暴露 80/443 端口。我們可以在任意節點上進行訪問,因爲我們還沒有在 Kubernetes 集羣中創建 Ingress 資源,所以直接對 ExternalIP 的請求被負載到了 nginx-ingress-default-backend 上。nginx-ingress-default-backend 默認提供了兩個 URL 進行訪問,其中的 /healthz 用作健康檢查返回 200,而 / 返回 404 錯誤。

$ curl 127.0.0.1/
# default backend - 404

$ curl 127.0.0.1/healthz/
# 返回的是 200

後續我們如果需要創建自身的 Ingress 配置,可以參考如下方式:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: example
  namespace: foo
spec:
  rules:
    - host: www.example.com
      http:
        paths:
          - backend:
              serviceName: exampleService
              servicePort: 80
            path: /
  # This section is only required if TLS is to be enabled for the Ingress
  tls:
    - hosts:
        - www.example.com
      secretName: example-tls

如果希望使用 TLS,那麼需要創建包含證書與 Key 的 Secret:

apiVersion: v1
kind: Secret
metadata:
  name: example-tls
  namespace: foo
data:
  tls.crt: <base64 encoded cert>
  tls.key: <base64 encoded key>
type: kubernetes.io/tls

WordPress

Helm 安裝完畢後,我們來測試部署一個 WordPress 應用:

$ helm install --name wordpress-test --set "ingress.enabled=true,persistence.enabled=false,mariadb.persistence.enabled=false" stable/wordpress

NAME:  wordpress-test
...

這裏我們使用 Ingress 負載均衡進行訪問,可以通過如下方式訪問到服務:

$ kubectl get ingress

NAME                             HOSTS             ADDRESS                                  PORTS   AGE
wordpress.local-wordpress-test   wordpress.local   172.19.157.1,172.19.157.2,172.19.157.3   80      59m

$ curl -I http://wordpress.local -x 127.0.0.1:80

HTTP/1.1 200 OK
Server: nginx/1.15.6
Date: Tue, 20 Aug 2019 07:55:21 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
X-Powered-By: PHP/7.0.27
Link: <http://wordpress.local/wp-json/>; rel="https://api.w.org/"

也可以根據 Charts 的說明,利用如下命令獲得 WordPress 站點的管理員用戶和密碼:

echo Username: user
echo Password: $(kubectl get secret --namespace default wordpress-test-wordpress -o jsonpath="{.data.wordpress-password}" | base64 --decode)

==> v1beta1/Role
NAME           AGE
nginx-ingress  0s

==> v1beta1/RoleBinding
NAME           AGE
nginx-ingress  0s

延伸閱讀

某熊的技術之路指北 ☯ 就是對筆者不同領域方面沉澱下的知識倉庫的導航與索引,便於讀者快速地尋找到自己需要的內容。路漫漫其修遠兮,吾正上下而求索,也希望能給所有遇見過筆者痕跡的同學些許幫助,在浩瀚銀河間能順利達到一個又一個彼岸。

您可以通過以下導航來在 Gitbook 中閱讀筆者的系列文章,涵蓋了技術資料歸納、編程語言與理論、Web 與大前端、服務端開發與基礎架構、雲計算與大數據、數據科學與人工智能、產品設計等多個領域:

此外,你還可前往 xCompass 交互式地檢索、查找需要的文章/鏈接/書籍/課程;或者在 MATRIX 文章與代碼索引矩陣中查看文章與項目源代碼等更詳細的目錄導航信息。最後,你也可以關注微信公衆號:『某熊的技術之路』以獲取最新資訊。

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