<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何部署一個生產(chǎn)級別的 Kubernetes 應用

          共 24411字,需瀏覽 49分鐘

           ·

          2020-12-04 16:23

          本文我們用一個 Wordpress 示例來盡可能將前面的知識點串聯(lián)起來,我們需要達到的目的是讓 Wordpress 應用具有高可用、滾動更新的過程中不能中斷服務、數(shù)據(jù)要持久化不能丟失、當應用負載太高的時候能夠自動進行擴容、當然還有 HTTPS 訪問等等,這些是我們的應用部署到線上環(huán)境基本上要具備的一些能力,接下來我們就來一步一步完成這些需求。

          原理

          首先要部署 Wordpress 應用,我們肯定需要知道 Wordpress 是如何運行起來的,Wordpress 是一個基于 PHP 和 MySQL 的流行的開源內(nèi)容管理系統(tǒng),擁有豐富的插件和模板系統(tǒng)。到這里我們應該就清楚應該如何去運行 Wordpress 了,一個能夠解析 PHP 的程序,和 MySQL 數(shù)據(jù)庫就可以了,我們要想在 Kubernetes 系統(tǒng)中來運行,肯定需要使用到 Docker 鏡像了,對于 Wordpress 應用程序本身官方提供了鏡像 https://hub.docker.com/_/wordpress,也給出了說明如何運行,可以通過一系列環(huán)境變量去指定 MySQL 數(shù)據(jù)庫的配置,只需要將這些參數(shù)配置上直接運行即可。我們知道 Wordpress 應用本身會頻繁的和 MySQL 數(shù)據(jù)庫進行交互,這種情況下如果將二者用容器部署在同一個 Pod 下面是不是要高效很多,因為一個 Pod 下面的所有容器是共享同一個 network namespace 的,下面我們就來部署我們的應用,將我們的應用都部署到 kube-example 這個命名空間下面,所以首先創(chuàng)建一個命名空間:(namespace.yaml)

          apiVersion:?v1
          kind:?Namespace
          metadata:
          ??name:?kube-example

          然后編寫部署到 Kubernetes 下面的資源清單:(deployment.yaml)

          apiVersion:?apps/v1
          kind:?Deployment
          metadata:
          ??name:?wordpress
          ??namespace:?kube-example
          ??labels:
          ????app:?wordpress
          spec:
          ??selector:
          ????matchLabels:
          ??????app:?wordpress
          ??template:
          ????metadata:
          ??????labels:
          ????????app:?wordpress
          ????spec:
          ??????containers:
          ??????-?name:?wordpress
          ????????image:?wordpress:5.3.2-apache
          ????????ports:
          ????????-?containerPort:?80
          ??????????name:?wdport
          ????????env:
          ????????-?name:?WORDPRESS_DB_HOST
          ??????????value:?localhost:3306
          ????????-?name:?WORDPRESS_DB_USER
          ??????????value:?wordpress
          ????????-?name:?WORDPRESS_DB_PASSWORD
          ??????????value:?wordpress
          ??????-?name:?mysql
          ????????image:?mysql:5.7
          ????????imagePullPolicy:?IfNotPresent
          ????????args:??#?新版本鏡像有更新,需要使用下面的認證插件環(huán)境變量配置才會生效
          ????????-?--default_authentication_plugin=mysql_native_password
          ????????-?--character-set-server=utf8mb4
          ????????-?--collation-server=utf8mb4_unicode_ci
          ????????ports:
          ????????-?containerPort:?3306
          ??????????name:?dbport
          ????????env:
          ????????-?name:?MYSQL_ROOT_PASSWORD
          ??????????value:?rootPassW0rd
          ????????-?name:?MYSQL_DATABASE
          ??????????value:?wordpress
          ????????-?name:?MYSQL_USER
          ??????????value:?wordpress
          ????????-?name:?MYSQL_PASSWORD
          ??????????value:?wordpress

          由于我們這里 MySQL 和 Wordpress 在同一個 Pod 下面,所以在 Wordpress 中我們指定數(shù)據(jù)庫地址的時候是用的 localhost:3306,因為這兩個容器已經(jīng)共享同一個 network namespace 了,這點很重要,然后如果我們要想把這個服務暴露給外部用戶還得創(chuàng)建一個 Service 或者 Ingress 對象,這里我們一步一步來,暫時先創(chuàng)建一個 NodePort 類型的 Service:(service.yaml)

          apiVersion:?v1
          kind:?Service
          metadata:
          ??name:?wordpress
          ??namespace:?kube-example
          spec:
          ??selector:
          ????app:?wordpress
          ??type:?NodePort
          ??ports:
          ??-?name:?web
          ????port:?80
          ????targetPort:?wdport

          因為只需要暴露 Wordpress 這個應用,所以只匹配了一個名為 wdport 的端口,現(xiàn)在我們來創(chuàng)建上面的幾個資源對象:

          $?kubectl?apply?-f?namespace.yaml
          $?kubectl?apply?-f?deployment.yaml
          $?kubectl?apply?-f?service.yaml

          接下來就是等待拉取鏡像,啟動 Pod:

          $?kubectl?get?pods?-n?kube-example
          NAME?????????????????????????READY???STATUS????RESTARTS???AGE
          wordpress-77dcdb64c6-zdlb8???2/2?????Running???0??????????12m
          $?kubectl?get?svc?-n?kube-example
          NAME????????TYPE???????CLUSTER-IP???????EXTERNAL-IP???PORT(S)????????AGE
          wordpress???NodePort???10.106.237.157???????????80:30892/TCP???2m2s

          當 Pod 啟動完成后,我們就可以通過上面的 http://<任意節(jié)點IP>:30892 這個 NodePort 端口來訪問應用了。我們仔細想一想這一種方式有什么問題?首先一個 Pod 中的所有容器并沒有啟動的先后順序,所以很有可能當 wordpress 這個容器啟動起來去連接 mysql 這個容器的時候,mysql 還沒有啟動起來;另外一個問題是現(xiàn)在我們的應用是不是只有一個副本?會有單點問題,應用的性能也是一個問題,由于 Wordpress 應用本身是無狀態(tài)應用,所以這種情況下一般我們只需要多部署幾個副本即可,比如這里我們在 Deployment 的 YAML 文件中加上 replicas:3 這個屬性,這個時候有一個什么問題呢?由于 MySQL 是有狀態(tài)應用,每一個 Pod 里面的數(shù)據(jù)庫的數(shù)據(jù)都是獨立的,他們并沒有共享,也就是說這3個 Pod 相當于是獨立的3個 Wordpress 實例,所以應該怎么辦呢?拆分,把 Wordpress 和 MySQL 這兩個容器部署成獨立的 Pod 就可以了,這樣我們只需要對 Wordpress 應用增加副本,而數(shù)據(jù)庫 MySQL 還是一個實例,所有的應用都連接到這一個數(shù)據(jù)庫上面,是不是就可以解決這個問題了。

          高可用

          現(xiàn)在我們將 Pod 中的兩個容器進行拆分,將 Wordpress 和 MySQL 分別部署,然后 Wordpress 用多個副本進行部署就可以實現(xiàn)應用的高可用了,由于 MySQL 是有狀態(tài)應用,一般來說需要用 StatefulSet 來進行管理,但是我們這里部署的 MySQL 并不是集群模式,而是單副本的,所以用 Deployment 也是沒有問題的,當然如果要真正用于生產(chǎn)環(huán)境還是需要集群模式的(MySQL 集群模式可以使用 Operator 部署或者直接使用外部鏈接):(mysql.yaml)

          apiVersion:?v1
          kind:?Service
          metadata:
          ??name:?wordpress-mysql
          ??namespace:?kube-example
          ??labels:
          ????app:?wordpress
          spec:
          ??ports:
          ??-?port:?3306
          ????targetPort:?dbport
          ??selector:
          ????app:?wordpress
          ????tier:?mysql
          ---
          apiVersion:?apps/v1
          kind:?Deployment
          metadata:
          ??name:?wordpress-mysql
          ??namespace:?kube-example
          ??labels:
          ????app:?wordpress
          ????tier:?mysql
          spec:
          ??selector:
          ????matchLabels:
          ??????app:?wordpress
          ??????tier:?mysql
          ??template:
          ????metadata:
          ??????labels:
          ????????app:?wordpress
          ????????tier:?mysql
          ????spec:
          ??????containers:
          ??????-?name:?mysql
          ????????image:?mysql:5.7
          ????????imagePullPolicy:?IfNotPresent
          ????????args:??#?新版本鏡像有更新,需要使用下面的認證插件環(huán)境變量配置才會生效
          ????????-?--default_authentication_plugin=mysql_native_password
          ????????-?--character-set-server=utf8mb4
          ????????-?--collation-server=utf8mb4_unicode_ci
          ????????ports:
          ????????-?containerPort:?3306
          ??????????name:?dbport
          ????????env:
          ????????-?name:?MYSQL_ROOT_PASSWORD
          ??????????value:?rootPassW0rd
          ????????-?name:?MYSQL_DATABASE
          ??????????value:?wordpress
          ????????-?name:?MYSQL_USER
          ??????????value:?wordpress
          ????????-?name:?MYSQL_PASSWORD
          ??????????value:?wordpress

          我們這里給 MySQL 應用添加了一個 Service 對象,是因為 Wordpress 應用需要來連接數(shù)據(jù)庫,之前在同一個 Pod 中用 localhost 即可,現(xiàn)在需要通過 Service 的 DNS 形式的域名進行連接。直接創(chuàng)建上面資源對象:

          $?kubectl?apply?-f?mysql.yaml????
          service/wordpress-mysql?created
          deployment.apps/wordpress-mysql?created

          接下來創(chuàng)建獨立的 Wordpress 服務,對應的資源對象如下:(wordpress.yaml)

          apiVersion:?v1
          kind:?Service
          metadata:
          ??name:?wordpress
          ??namespace:?kube-example
          ??labels:
          ????app:?wordpress
          spec:
          ??selector:
          ????app:?wordpress
          ????tier:?frontend
          ??type:?NodePort
          ??ports:
          ??-?name:?web
          ????port:?80
          ????targetPort:?wdport
          ---
          apiVersion:?apps/v1
          kind:?Deployment
          metadata:
          ??name:?wordpress
          ??namespace:?kube-example
          ??labels:
          ????app:?wordpress
          ????tier:?frontend
          spec:
          ??replicas:?3
          ??selector:
          ????matchLabels:
          ??????app:?wordpress
          ??????tier:?frontend
          ??template:
          ????metadata:
          ??????labels:
          ????????app:?wordpress
          ????????tier:?frontend
          ????spec:
          ??????containers:
          ??????-?name:?wordpress
          ????????image:?wordpress:5.3.2-apache
          ????????ports:
          ????????-?containerPort:?80
          ??????????name:?wdport
          ????????env:
          ????????-?name:?WORDPRESS_DB_HOST
          ??????????value:?wordpress-mysql:3306
          ????????-?name:?WORDPRESS_DB_USER
          ??????????value:?wordpress
          ????????-?name:?WORDPRESS_DB_PASSWORD
          ??????????value:?wordpress

          注意這里的環(huán)境變量 WORDPRESS_DB_HOST 的值將之前的 localhost 地址更改成了上面 MySQL 服務的 DNS 地址,完整的域名應該是 wordpress-mysql.kube-example.svc.cluster.local:3306,由于這兩個應該都處于同一個命名空間,所以直接簡寫成 wordpress-mysql:3306 也是可以的。創(chuàng)建上面資源對象:

          $?kubectl?apply?-f?wordpress.yaml?
          service/wordpress?created
          deployment.apps/wordpress?created
          $?kubectl?get?pods?-l?app=wordpress?-n?kube-example
          NAME??????????????????????????????READY???STATUS??????????????RESTARTS???AGE
          wordpress-6554f9f96c-24rl7????????0/1?????ContainerCreating???0??????????6m12s
          wordpress-6554f9f96c-4qm5c????????1/1?????Running?????????????0??????????6m12s
          wordpress-6554f9f96c-wsjhh????????0/1?????ContainerCreating???0??????????6m12s
          wordpress-mysql-8bbc78ddc-l4c28???1/1?????Running?????????????0??????????13m

          可以看到都已經(jīng)是 Running 狀態(tài)了,然后我們需要怎么來驗證呢?是不是我們能想到的就是去訪問下我們的 Wordpress 服務就可以了,我們這里還是使用的一個 NodePort 類型的 Service 來暴露服務:

          $?kubectl?get?svc?-l?app=wordpress?-n?kube-example
          NAME??????????????TYPE????????CLUSTER-IP?????EXTERNAL-IP???PORT(S)????????AGE
          wordpress?????????NodePort????10.96.154.86???????????80:30012/TCP???8m15s
          wordpress-mysql???ClusterIP???10.107.5.168???????????3306/TCP???????15m

          可以看到 wordpress 服務產(chǎn)生了一個 30012 的端口,現(xiàn)在我們就可以通過 http://<任意節(jié)點的NodeIP>:30012 訪問我們的應用了,在瀏覽器中打開,如果看到 wordpress 跳轉(zhuǎn)到了安裝頁面,證明我們的安裝是正確的,如果沒有出現(xiàn)預期的效果,那么就需要去查看下 Pod 的日志來排查問題了,根據(jù)頁面提示,填上對應的信息,點擊“安裝”即可,最終安裝成功后,我們就可以看到熟悉的首頁界面了:

          wordpres home

          穩(wěn)定性

          現(xiàn)在 Wodpress 應用已經(jīng)部署成功了,那么就萬事大吉了嗎?如果我們的網(wǎng)站訪問量突然變大了怎么辦,如果我們要更新我們的鏡像該怎么辦?所以要保證我們的網(wǎng)站能夠非常穩(wěn)定的提供服務,我們做得還不夠,我們可以通過做些什么事情來提高網(wǎng)站的穩(wěn)定性呢?

          避免單點故障

          為什么會有單點故障的問題呢?我們不是部署了多個副本的 Wordpress 應用嗎?當我們設(shè)置 replicas=1 的時候肯定會存在單點故障問題,如果大于 1 但是所有副本都調(diào)度到了同一個節(jié)點的是不是同樣就會存在單點問題了,這個節(jié)點掛了所有副本就都掛了,所以我們不僅需要設(shè)置多個副本數(shù)量,還需要讓這些副本調(diào)度到不同的節(jié)點上,來打散避免單點故障,這個利用 Pod 反親和性來實現(xiàn)了,我們可以加上如下所示的配置:

          affinity:
          ??podAntiAffinity:
          ????preferredDuringSchedulingIgnoredDuringExecution:??#?軟策略
          ????-?weight:?1
          ??????podAffinityTerm:
          ????????topologyKey:?kubernetes.io/hostname
          ????????labelSelector:
          ??????????matchExpressions:
          ??????????-?key:?app
          ????????????operator:?In
          ????????????values:
          ????????????-?wordpress

          這里的意思就是如果一個節(jié)點上面有 app=wordpress 這樣的 Pod 的話,那么我們的 Pod 就盡可能別調(diào)度到這個節(jié)點上面來,因為我們這里的節(jié)點并不多,所以我使用的是軟策略,因為如果使用硬策略的話,如果應用副本數(shù)超過了節(jié)點數(shù)就必然會有 Pod 調(diào)度不成功,如果你線上節(jié)點非常多的話(節(jié)點數(shù)大于 Pod 副本數(shù)),建議使用硬策略,更新后我們可以查看下 3 個副本被分散在了不同的節(jié)點上。

          使用 PDB

          有些時候線上的某些節(jié)點需要做一些維護操作,比如要升級內(nèi)核,這個時候我們就需要將要維護的節(jié)點進行驅(qū)逐操作,驅(qū)逐節(jié)點首先是將節(jié)點設(shè)置為不可調(diào)度,這樣可以避免有新的 Pod 調(diào)度上來,然后將該節(jié)點上的 Pod 全部刪除,ReplicaSet 控制器檢測到 Pod 數(shù)量減少了就會重新創(chuàng)建一個新的 Pod,調(diào)度到其他節(jié)點上面的,這個過程是先刪除,再創(chuàng)建,并非是滾動更新,因此更新過程中,如果一個服務的所有副本都在被驅(qū)逐的節(jié)點上,則可能導致該服務不可用。

          如果服務本身存在單點故障,所有副本都在同一個節(jié)點,驅(qū)逐的時候肯定就會造成服務不可用了,這種情況我們使用上面的反親和性和多副本就可以解決這個問題。但是如果我們的服務本身就被打散在多個節(jié)點上,這些節(jié)點如果都被同時驅(qū)逐的話,那么這個服務的所有實例都會被同時刪除,這個時候也會造成服務不可用了,這種情況下我們可以通過配置 PDB(PodDisruptionBudget)對象來避免所有副本同時被刪除,比如我們可以設(shè)置在驅(qū)逐的時候 wordpress 應用最多只有一個副本不可用,其實就相當于逐個刪除并在其它節(jié)點上重建:(pdb.yaml)

          apiVersion:?policy/v1beta1
          kind:?PodDisruptionBudget
          metadata:
          ??name:?wordpress-pdb
          ??namespace:?kube-example
          spec:
          ??maxUnavailable:?1
          ??selector:
          ????matchLabels:
          ??????app:?wordpress
          ??????tier:?frontend

          直接創(chuàng)建這個資源對象即可:

          $?kubectl?apply?-f?pdb.yaml?
          poddisruptionbudget.policy/wordpress-pdb?created
          $?kubectl?get?pdb?-n?kube-example
          NAME????????????MIN?AVAILABLE???MAX?UNAVAILABLE???ALLOWED?DISRUPTIONS???AGE
          wordpress-pdb???N/A?????????????1?????????????????1?????????????????????10s

          關(guān)于 PDB 的更多詳細信息可以查看官方文檔:https://kubernetes.io/docs/tasks/run-application/configure-pdb/。

          健康檢查

          我們的應用現(xiàn)在還有一個非常重要的功能沒有提供,那就是健康檢查,我們知道健康檢查是提高應用健壯性非常重要的手段,當我們檢測到應用不健康的時候我們希望可以自動重啟容器,當應用還沒有準備好的時候我們也希望暫時不要對外提供服務,所以我們需要添加我們前面經(jīng)常提到的 liveness proberediness probe 兩個健康檢測探針,檢查探針的方式有很多,我們這里當然可以認為如果容器的 80 端口可以成功訪問那么就是健康的,對于一般的應用提供一個健康檢查的 URL 會更好,這里我們添加一個如下所示的可讀性探針,為什么不添加存活性探針呢?這里其實是考慮到線上錯誤排查的一個問題,如果當我們的應用出現(xiàn)了問題,然后就自動重啟去掩蓋錯誤的話,可能這個錯誤就會被永遠忽略掉了,所以其實這是一個折衷的做法,不使用存活性探針,而是結(jié)合監(jiān)控報警,保留錯誤現(xiàn)場,方便錯誤排查,但是可讀寫探針是一定需要添加的(最好使用 http 接口進行檢查):

          readinessProbe:
          ??tcpSocket:
          ????port:?80
          ??initialDelaySeconds:?5
          ??periodSeconds:?5

          增加上面的探針,每 5s 檢測一次應用是否可讀,這樣只有當 readinessProbe 探針檢測成功后才表示準備好接收流量了,這個時候才會更新 Service 的 Endpoints 對象。

          服務質(zhì)量 QoS

          QoSQuality of Service 的縮寫,即服務質(zhì)量。為了實現(xiàn)資源被有效調(diào)度和分配的同時提高資源利用率,Kubernetes 針對不同服務質(zhì)量的預期,通過 QoS 來對 Pod 進行服務質(zhì)量管理。對于一個 Pod 來說,服務質(zhì)量體現(xiàn)在兩個具體的指標:CPU 和內(nèi)存。當節(jié)點上內(nèi)存資源緊張時,Kubernetes 會根據(jù)預先設(shè)置的不同 QoS 類別進行相應處理。

          QoS 主要分為 GuaranteedBurstableBest-Effort三類,優(yōu)先級從高到低。我們先分別來介紹下這三種服務類型的定義。

          Guaranteed(有保證的)

          屬于該級別的 Pod 有以下兩種:

          • Pod 中的所有容器都且設(shè)置了 CPU 和內(nèi)存的 limits
          • Pod 中的所有容器都設(shè)置了 CPU 和內(nèi)存的 requests 和 limits ,且單個容器內(nèi)的requests==limits(requests不等于0)

          Pod 中的所有容器都且僅設(shè)置了 limits,如下所示:

          containers:
          ??-?name:?foo
          ????resources:
          ??????limits:
          ????????cpu:?10m
          ????????memory:?1Gi
          ??-?name:?bar
          ????resources:
          ??????limits:
          ????????cpu:?100m
          ????????memory:?100Mi

          Pod 中的所有容器都設(shè)置了 requests 和 limits,且單個容器內(nèi)的 requests==limits 的情況:

          containers:
          ??-?name:?foo
          ????resources:
          ??????limits:
          ????????cpu:?10m
          ????????memory:?1Gi
          ??????requests:
          ????????cpu:?10m
          ????????memory:?1Gi
          ??-?name:?bar
          ????resources:
          ??????limits:
          ????????cpu:?100m
          ????????memory:?100Mi
          ??????requests:
          ????????cpu:?100m
          ????????memory:?100Mi

          容器 foo 和 bar 內(nèi) resourcesrequestslimits 均相等,該 Pod 的 QoS 級別屬于 Guaranteed

          Burstable(不穩(wěn)定的)

          Pod 中只要有一個容器的 requests 和 limits 的設(shè)置不相同,那么該 Pod 的 QoS 即為 Burstable。如下所示容器 foo 指定了 resource,而容器 bar 未指定:

          containers:
          ??-?name:?foo
          ????resources:
          ??????limits:
          ????????cpu:?10m
          ????????memory:?1Gi
          ??????requests:
          ????????cpu:?10m
          ????????memory:?1Gi
          ??-?name:?bar

          容器 foo 設(shè)置了內(nèi)存 limits,而容器 bar 設(shè)置了CPU limits:

          containers:
          ??-?name:?foo
          ????resources:
          ??????limits:
          ????????memory:?1Gi
          ??-?name:?bar
          ????resources:
          ??????limits:
          ????????cpu:?100m

          需要注意的是如果容器指定了 requests 而未指定 limits,則 limits 的值等于節(jié)點資源的最大值,如果容器指定了 limits 而未指定 requests,則 requests 的值等于 limits。

          Best-Effort(盡最大努力)

          如果 Pod 中所有容器的 resources 均未設(shè)置 requests 與 limits,該 Pod 的 QoS 即為 Best-Effort。如下所示容器 foo 和容器 bar 均未設(shè)置requests 和 limits:

          containers:
          ??-?name:?foo
          ????resources:
          ??-?name:?bar
          ????resources:

          資源回收策略

          Kubernetes 通過 CGroup 給 Pod設(shè)置 QoS 級別,當資源不足時會優(yōu)先 kill 掉優(yōu)先級低的 Pod,在實際使用過程中,通過 OOM 分數(shù)值來實現(xiàn),OOM 分數(shù)值范圍為 0-1000,OOM 分數(shù)值根據(jù) OOM_ADJ參數(shù)計算得出。

          對于 Guaranteed 級別的 Pod,OOM_ADJ 參數(shù)設(shè)置成了-998,對于 Best-Effort 級別的 Pod,OOM_ADJ 參數(shù)設(shè)置成了1000,對于 Burstable 級別的 Pod,OOM_ADJ 參數(shù)取值從 2 到 999。

          QoS Pods 被 kill 掉的場景和順序如下所示:

          • Best-Effort Pods:系統(tǒng)用完了全部內(nèi)存時,該類型 Pods 會最先被 kill 掉
          • Burstable Pods:系統(tǒng)用完了全部內(nèi)存,且沒有 Best-Effort 類型的容器可以被 kill 時,該類型的 Pods 會被 kill 掉
          • Guaranteed Pods:系統(tǒng)用完了全部內(nèi)存,且沒有 Burstable 與 Best-Effort 類型的容器可以被 kill 時,該類型的 pods 會被 kill 掉

          所以如果資源充足,可將 QoS Pods 類型設(shè)置為 Guaranteed,用計算資源換業(yè)務性能和穩(wěn)定性,減少排查問題時間和成本。如果想更好的提高資源利用率,業(yè)務服務可以設(shè)置為 Guaranteed,而其他服務根據(jù)重要程度可分別設(shè)置為 Burstable 或 Best-Effort,這就要看具體的場景了。

          比如我們這里如果想要盡可能提高 Wordpress 應用的穩(wěn)定性,我們可以將其設(shè)置為 Guaranteed 類型的 Pod,我們現(xiàn)在沒有設(shè)置 resources 資源,所以現(xiàn)在是 Best-Effort 類型的 Pod。

          現(xiàn)在如果要想給應用設(shè)置資源大小,就又有一個問題了,應該如何設(shè)置合適的資源大小呢?其實這就需要我們對自己的應用非常了解才行了,一般情況下我們可以先不設(shè)置資源,然后可以根據(jù)我們的應用的并發(fā)和訪問量來進行壓力測試,基本上可以大概計算出應用的資源使用量,我們這里可以使用 Apache Bench(AB Test) 或者 Fortio(Istio 測試工具) 這樣的測試工具來測試,我們這里使用 Fortio 這個測試工具,比如每秒 1000 個請求和 8 個并發(fā)的連接的測試命令如下所示:

          $?fortio?load?-a?-c?8?-qps?1000?-t?60s?"http://k8s.qikqiak.com:30012"
          Starting?at?1000?qps?with?8?thread(s)?[gomax?2]?for?1m0s?:?7500?calls?each?(total?60000)
          Ended?after?1m0.687224615s?:?5005?calls.?qps=82.472
          Aggregated?Sleep?Time?:?count?5005?avg?-27.128368?+/-?16?min?-55.964246789?max?-0.050576982?sum?-135777.482
          [......]
          Sockets?used:?53?(for?perfect?keepalive,?would?be?8)
          Code?200?:?5005?(100.0?%)
          Response?Header?Sizes?:?count?5005?avg?292.17083?+/-?1.793?min?292?max?311?sum?1462315
          Response?Body/Total?Sizes?:?count?5005?avg?27641.171?+/-?1.793?min?27641?max?27660?sum?138344060
          Saved?result?to?data/2020-02-15-125121_Fortio.json?(graph?link)
          All?done?5005?calls?95.872?ms?avg,?82.5?qps

          也可以通過瀏覽器查看到最終測試結(jié)果:

          fortio result

          在測試期間我們可以用如下所示的命令查看應用的資源使用情況:

          $?kubectl?top?pods?-l?app=wordpress?-n?kube-example
          NAME??????????????????????????????CPU(cores)???MEMORY(bytes)
          wordpress-5cc66f986b-2jv7h????????569m?????????72Mi
          wordpress-5cc66f986b-nf79l????????997m?????????71Mi
          wordpress-d4c885d5d-gtvhd?????????895m?????????87Mi

          我們可以看到內(nèi)存基本上都是處于 100Mi 以內(nèi),而 CPU 消耗就非常大了,但是由于 CPU 是可壓縮資源,也就是說超過了限制應用也不會掛掉的,只是會變慢而已。所以我們這里可以給 Wordpress 應用添加如下所示的資源配置,如果你集群資源足夠的話可以適當多分配一些資源:

          resources:
          ??limits:
          ????cpu:?200m
          ????memory:?100Mi
          ??requests:
          ????cpu:?200m
          ????memory:?100Mi

          滾動更新

          Deployment 控制器默認的就是滾動更新的更新策略,該策略可以在任何時間點更新應用的時候保證某些實例依然可以正常運行來防止應用 down 掉,當新部署的 Pod 啟動并可以處理流量之后,才會去殺掉舊的 Pod。在使用過程中我們還可以指定 Kubernetes 在更新期間如何處理多個副本的切換方式,比如我們有一個3副本的應用,在更新的過程中是否應該立即創(chuàng)建這3個新的 Pod 并等待他們?nèi)繂樱蛘邭⒌粢粋€之外的所有舊的 Pod,或者還是要一個一個的 Pod 進行替換?

          如果我們從舊版本到新版本進行滾動更新,只是簡單的通過輸出顯示來判斷哪些 Pod 是存活并準備就緒的,那么這個滾動更新的行為看上去肯定就是有效的,但是往往實際情況就是從舊版本到新版本的切換的過程并不總是十分順暢的,應用程序很有可能會丟棄掉某些客戶端的請求。比如我們在 Wordpress 應用中添加上如下的滾動更新策略,隨便更改以下 Pod Template 中的參數(shù),比如容器名更改為 blog:

          strategy:
          ??type:?RollingUpdate
          ??rollingUpdate:
          ????maxSurge:?1
          ????maxUnavailable:?0

          然后更新應用,同時用 Fortio 工具在滾動更新過程中來測試應用是否可用:

          $?kubectl?apply?-f?wordpress.yaml
          $?fortio?load?-a?-c?8?-qps?1000?-t?60s?"http://k8s.qikqiak.com:30012"
          Starting?at?1000?qps?with?8?thread(s)?[gomax?2]?for?1m0s?:?7500?calls?each?(total?60000)
          Ended?after?1m0.006243654s?:?5485?calls.?qps=91.407
          Aggregated?Sleep?Time?:?count?5485?avg?-17.626081?+/-?15?min?-54.753398956?max?0.000709054?sum?-96679.0518
          [...]
          Code?200?:?5463?(99.6?%)
          Code?502?:?20?(0.4?%)
          Response?Header?Sizes?:?count?5485?avg?213.14166?+/-?13.53?min?0?max?214?sum?1169082
          Response?Body/Total?Sizes?:?count?5485?avg?823.18651?+/-?44.41?min?0?max?826?sum?4515178
          [...]

          從上面的輸出可以看出有部分請求處理失敗了(502),要弄清楚失敗的原因就需要弄明白當應用在滾動更新期間重新路由流量時,從舊的 Pod 實例到新的實例究竟會發(fā)生什么,首先讓我們先看看 Kubernetes 是如何管理工作負載連接的。

          失敗原因

          我們這里通過 NodePort 去訪問應用,實際上也是通過每個節(jié)點上面的 kube-proxy 通過更新 iptables 規(guī)則來實現(xiàn)的。

          kubernetes kube-proxy

          Kubernetes 會根據(jù) Pods 的狀態(tài)去更新 Endpoints 對象,這樣就可以保證 Endpoints 中包含的都是準備好處理請求的 Pod。一旦新的 Pod 處于活動狀態(tài)并準備就緒后,Kubernetes 就將會停止就的 Pod,從而將 Pod 的狀態(tài)更新為 “Terminating”,然后從 Endpoints 對象中移除,并且發(fā)送一個 SIGTERM 信號給 Pod 的主進程。SIGTERM 信號就會讓容器以正常的方式關(guān)閉,并且不接受任何新的連接。Pod 從 Endpoints 對象中被移除后,前面的負載均衡器就會將流量路由到其他(新的)Pod 中去。因為在負載均衡器注意到變更并更新其配置之前,終止信號就會去停用 Pod,而這個重新配置過程又是異步發(fā)生的,并不能保證正確的順序,所以就可能導致很少的請求會被路由到已經(jīng)終止的 Pod 上去了,也就出現(xiàn)了上面我們說的情況。

          零宕機

          那么如何增強我們的應用程序以實現(xiàn)真正的零宕機遷移更新呢?

          首先,要實現(xiàn)這個目標的先決條件是我們的容器要正確處理終止信號,在 SIGTERM 信號上實現(xiàn)優(yōu)雅關(guān)閉。下一步需要添加 readiness 可讀探針,來檢查我們的應用程序是否已經(jīng)準備好來處理流量了。為了解決 Pod 停止的時候不會阻塞并等到負載均衡器重新配置的問題,我們還需要使用 preStop 這個生命周期的鉤子,在容器終止之前調(diào)用該鉤子。

          生命周期鉤子函數(shù)是同步的,所以必須在將最終停止信號發(fā)送到容器之前完成,在我們的示例中,我們使用該鉤子簡單的等待,然后 SIGTERM 信號將停止應用程序進程。同時,Kubernetes 將從 Endpoints 對象中刪除該 Pod,所以該 Pod 將會從我們的負載均衡器中排除,基本上來說我們的生命周期鉤子函數(shù)等待的時間可以確保在應用程序停止之前重新配置負載均衡器:

          readinessProbe:
          ??#?...
          lifecycle:
          ??preStop:
          ????exec:
          ??????command:?["/bin/bash",?"-c",?"sleep?20"]

          我們這里使用 preStop 設(shè)置了一個 20s 的寬限期,Pod 在真正銷毀前會先 sleep 等待 20s,這就相當于留了時間給 Endpoints 控制器和 kube-proxy 更新去 Endpoints 對象和轉(zhuǎn)發(fā)規(guī)則,這段時間 Pod 雖然處于 Terminating 狀態(tài),即便在轉(zhuǎn)發(fā)規(guī)則更新完全之前有請求被轉(zhuǎn)發(fā)到這個 Terminating 的 Pod,依然可以被正常處理,因為它還在 sleep,沒有被真正銷毀。

          現(xiàn)在,當我們?nèi)ゲ榭礉L動更新期間的 Pod 行為時,我們將看到正在終止的 Pod 處于 Terminating 狀態(tài),但是在等待時間結(jié)束之前不會關(guān)閉的,如果我們使用 Fortio 重新測試下,則會看到零失敗請求的理想狀態(tài)。

          HPA

          現(xiàn)在應用是固定的3個副本,但是往往在生產(chǎn)環(huán)境流量是不可控的,很有可能一次活動就會有大量的流量,3個副本很有可能抗不住大量的用戶請求,這個時候我們就希望能夠自動對 Pod 進行伸縮,直接使用前面我們學習的 HPA 這個資源對象就可以滿足我們的需求了。

          直接使用kubectl autoscale命令來創(chuàng)建一個 HPA 對象

          $?kubectl?autoscale?deployment?wordpress?--namespace?kube-example?--cpu-percent=20?--min=3?--max=6
          horizontalpodautoscaler.autoscaling/hpa-demo?autoscaled
          $?kubectl?get?hpa?-n?kube-example
          NAME????????REFERENCE??????????????TARGETS?????????MINPODS???MAXPODS???REPLICAS???AGE
          wordpress???Deployment/wordpress???/20%???3?????????6?????????0??????????13s

          此命令創(chuàng)建了一個關(guān)聯(lián)資源 wordpress 的 HPA,最小的 Pod 副本數(shù)為3,最大為6。HPA 會根據(jù)設(shè)定的 cpu 使用率(20%)動態(tài)的增加或者減少 Pod 數(shù)量。同樣,使用上面的 Fortio 工具來進行壓測一次,看下能否進行自動的擴縮容:

          $?fortio?load?-a?-c?8?-qps?1000?-t?60s?"http://k8s.qikqiak.com:30012"

          在壓測的過程中我們可以看到 HPA 的狀態(tài)變化以及 Pod 數(shù)量也變成了6個:

          $?kubectl?get?hpa?-n?kube-example
          NAME????????REFERENCE??????????????TARGETS???MINPODS???MAXPODS???REPLICAS???AGE
          wordpress???Deployment/wordpress???98%/20%???3?????????6?????????6??????????2m40s
          $?kubectl?get?pods?-n?kube-example????????????????
          NAME??????????????????????????????READY???STATUS????RESTARTS???AGE
          wordpress-79d756cbc8-f6kfm????????1/1?????Running???0??????????21m
          wordpress-79d756cbc8-kspch????????1/1?????Running???0??????????32s
          wordpress-79d756cbc8-sf5rm????????1/1?????Running???0??????????32s
          wordpress-79d756cbc8-tsjmf????????1/1?????Running???0??????????20m
          wordpress-79d756cbc8-v9p7n????????1/1?????Running???0??????????32s
          wordpress-79d756cbc8-z4wpp????????1/1?????Running???0??????????21m
          wordpress-mysql-5756ccc8b-zqstp???1/1?????Running???0??????????3d19h

          當壓測停止以后正常5分鐘后就會自動進行縮容,變成最小的3個 Pod 副本。

          安全性

          安全性這個和具體的業(yè)務應用有關(guān)系,比如我們這里的 Wordpress 也就是數(shù)據(jù)庫的密碼屬于比較私密的信息,我們可以使用 Kubernetes 中的 Secret 資源對象來存儲比較私密的信息:

          $?kubectl?create?secret?generic?wordpress-db-pwd?--from-literal=dbpwd=wordpress?-n?kube-example
          secret/wordpress-db-pwd?created

          然后將 Deployment 資源對象中的數(shù)據(jù)庫密碼環(huán)境變量通過 Secret 對象讀取:

          env:
          -?name:?WORDPRESS_DB_HOST
          ??value:?wordpress-mysql:3306
          -?name:?WORDPRESS_DB_USER
          ??value:?wordpress
          -?name:?WORDPRESS_DB_PASSWORD
          ??valueFrom:
          ????secretKeyRef:
          ??????name:?wordpress-db-pwd
          ??????key:?dbpwd

          這樣我們就不會在 YAML 文件中看到明文的數(shù)據(jù)庫密碼了,當然安全性都是相對的,Secret 資源對象也只是簡單的將密碼做了一次 Base64 編碼而已,對于一些特殊場景安全性要求非常高的應用,就需要使用其他功能更加強大的密碼系統(tǒng)來進行管理了,比如 Vault。

          持久化

          現(xiàn)在還有一個比較大的問題就是我們的數(shù)據(jù)還沒有做持久化,MySQL 數(shù)據(jù)庫沒有做,Wordpress 應用本身也沒有做,這顯然不是一個合格的線上應用。這里我們直接使用前面章節(jié)中創(chuàng)建的 rook-ceph-block 這個 StorageClass 來創(chuàng)建我們的數(shù)據(jù)庫存儲后端:(pvc.yaml)

          apiVersion:?v1
          kind:?PersistentVolumeClaim
          metadata:
          ??name:?mysql-pvc
          ??namespace:?kube-example
          ??labels:
          ????app:?wordpress
          spec:
          ??storageClassName:?rook-ceph-block
          ??accessModes:
          ??-?ReadWriteOnce
          ??resources:
          ????requests:
          ??????storage:?20Gi

          但是由于 Wordpress 應用是多個副本,所以需要同時在多個節(jié)點進行讀寫,也就是 accessModes 需要 ReadWriteMany 模式,而 Ceph RBD 模式是不支持 RWM 的,所以需要使用 CephFS,首先需要在 Ceph 中創(chuàng)建一個 Filesystem,這里我們可以通過 Rook 的 CephFilesystem 資源對象創(chuàng)建,如下所示:

          apiVersion:?ceph.rook.io/v1
          kind:?CephFilesystem
          metadata:
          ??name:?myfs
          ??namespace:?rook-ceph
          spec:
          ??metadataPool:
          ????replicated:
          ??????size:?3
          ??dataPools:
          ??-?replicated:
          ??????size:?3
          ??metadataServer:
          ????activeCount:?1
          ????activeStandby:?true

          創(chuàng)建完成后還會生成一個名為 myfs-data0 的存儲池,也會自動生成兩個 MDS 的 Pod 服務:

          $?kubectl?get?pods?-n?rook-ceph?|grep?myfs
          rook-ceph-mds-myfs-a-7948557994-44c4f??????????????????1/1?????Running?????0??????????11m
          rook-ceph-mds-myfs-b-5976b868cc-gl86g??????????????????1/1?????Running?????0??????????11m

          這個時候就可以創(chuàng)建我們的 StorageClass 對象了:

          apiVersion:?storage.k8s.io/v1
          kind:?StorageClass
          metadata:
          ??name:?csi-cephfs
          provisioner:?rook-ceph.cephfs.csi.ceph.com
          parameters:
          ??clusterID:?rook-ceph
          ??#?上面創(chuàng)建的?CephFS?文件系統(tǒng)名稱
          ??fsName:?myfs
          ??#?自動生成的
          ??pool:?myfs-data0?
          ??#?Root?path?of?an?existing?CephFS?volume
          ??#?Required?for?provisionVolume:?"false"
          ??#?rootPath:?/absolute/path
          ??#?The?secrets?contain?Ceph?admin?credentials.?These?are?generated?automatically?by?the?operator
          ??#?in?the?same?namespace?as?the?cluster.
          ??csi.storage.k8s.io/provisioner-secret-name:?rook-csi-cephfs-provisioner
          ??csi.storage.k8s.io/provisioner-secret-namespace:?rook-ceph
          ??csi.storage.k8s.io/controller-expand-secret-name:?rook-csi-cephfs-provisioner
          ??csi.storage.k8s.io/controller-expand-secret-namespace:?rook-ceph
          ??csi.storage.k8s.io/node-stage-secret-name:?rook-csi-cephfs-node
          ??csi.storage.k8s.io/node-stage-secret-namespace:?rook-ceph

          reclaimPolicy:?Retain
          allowVolumeExpansion:?true
          mountOptions:

          同樣直接創(chuàng)建上面的 StorageClass 資源對象即可,現(xiàn)在 Wordpress 的 PVC 對象使用我們這里的 csi-cephfs 這個 StorageClass 對象:

          apiVersion:?v1
          kind:?PersistentVolumeClaim
          metadata:
          ??name:?wordpress-pvc
          ??namespace:?kube-example
          ??labels:
          ????app:?wordpress
          spec:
          ??storageClassName:?csi-cephfs
          ??accessModes:
          ??-?ReadWriteMany??#?由于是多個Pod所以要用?RWM
          ??resources:
          ????requests:
          ??????storage:?2Gi

          直接創(chuàng)建上面的資源對象:

          $?kubectl?get?pvc?-n?kube-example
          NAME????????????STATUS???VOLUME?????????????????????????????????????CAPACITY???ACCESS?MODES???STORAGECLASS??????AGE
          mysql-pvc???????Bound????pvc-93e0b186-da20-4e5e-8414-8cc73e00bf64???20Gi???????RWO????????????rook-ceph-block???45m
          wordpress-pvc???Bound????pvc-87675c58-407a-4b7e-be9d-0733f67c4835???2Gi????????RWX????????????csi-cephfs????????5s

          在使用 CephFS 的過程中遇到了上面的 PVC 一直處于 Pending 狀態(tài),然后 describe 后發(fā)現(xiàn)有如下所示的錯誤信息:

          從錯誤信息上面來看是在通過 StorageClass 去自動創(chuàng)建 PV 的時候就出現(xiàn)了問題,所以我們需要去檢查 CSI 的 attach 階段:

          $?kubectl?get?pods?-n?rook-ceph?|grep?csi-cephfsplugin-provisioner
          csi-cephfsplugin-provisioner-56c8b7ddf4-4s7fd??????????4/4?????Running?????0??????????18m
          csi-cephfsplugin-provisioner-56c8b7ddf4-55sg6??????????4/4?????Running?????0??????????39m

          然后查看這兩個 Pod 的日志發(fā)現(xiàn)都是類似于下面的錯誤:

          I0304?10:04:04.823171???????1?leaderelection.go:246]?failed?to?acquire?lease?rook-ceph/rook-ceph-cephfs-csi-ceph-com
          I0304?10:04:14.344109???????1?leaderelection.go:350]?lock?is?held?by?csi-cephfsplugin-provisioner-56c8b7ddf4-rq4t6?and?has?not?yet?expired

          因為我們這里有兩個副本的 Provisioner,正常應該是有一個提供服務,另外一個作為備用的,通過獲取到分布式鎖來表示當前的 Pod 是否是 leader,這里兩個 Pod 都沒獲取到,應該就是出現(xiàn)了通信問題,然后將兩個 Pod 都重建后,其中一個 Pod 便獲取到了 lease 對象,然后 PVC 也成功綁定上了 PV。

          可以看到上面的 PVC 已經(jīng)自動綁定到 PV 上面去了,這個就是上面的 StorageClass 完成的工作。然后在 Wordpress 應用上添加對 /var/www/html 目錄的掛載聲明:

          ??volumeMounts:
          ??-?name:?wordpress-data
          ????mountPath:?/var/www/html
          volumes:
          -?name:?wordpress-data
          ??persistentVolumeClaim:
          ????claimName:?wordpress-pvc

          在 MySQL 應用上添加對 /var/lib/mysql 目錄的掛載聲明:

          ??volumeMounts:
          ??-?name:?mysql-data
          ????mountPath:?/var/lib/mysql
          volumes:
          -?name:?mysql-data
          ??persistentVolumeClaim:
          ????claimName:?mysql-pvc

          重新更新應用即可,在更新的過程中發(fā)現(xiàn) MySQL 啟動失敗了,報如下所示的錯誤:

          ......
          [ERROR]?--initialize?specified?but?the?data?directory?has?files?in?it.?Aborting.

          意思就是 /var/lib/mysql 目錄下面已經(jīng)有數(shù)據(jù)了,當然可以清空該目錄然后重新創(chuàng)建即可,這里可能是 mysql:5.7 這個鏡像的 BUG,所以我們更改成 mysql:5.6 這個鏡像,去掉之前添加的一個認證參數(shù):

          containers:
          -?image:?mysql:5.6
          ??name:?mysql
          ??imagePullPolicy:?IfNotPresent
          ??args:
          ??-?--character-set-server=utf8mb4
          ??-?--collation-server=utf8mb4_unicode_ci

          重新更新就可以正常啟動了。

          Ingress

          對于一個線上的應用對外暴露服務用一個域名顯然是更加合適的,上面我們使用的 NodePort 類型的服務不適合用于線上生產(chǎn)環(huán)境,這里我們通過 Ingress 對象來暴露服務,由于我們使用的是 Traefik2.1 這個 Ingress 控制器,所以通過 IngressRoute 對象來暴露我們的服務,此外為了安全我們還使用了 ACME 來自動獲取 https 的證書,并且通過一個中間件將 http 強制跳轉(zhuǎn)到 https 服務:(ingressroute.yaml)

          apiVersion:?traefik.containo.us/v1alpha1
          kind:?IngressRoute
          metadata:
          ??name:?wordpress-https
          ??namespace:?kube-example
          spec:
          ??entryPoints:
          ????-?websecure
          ??routes:
          ??-?match:?Host(`wordpress.qikqiak.com`)
          ????kind:?Rule
          ????services:
          ????-?name:?wordpress
          ??????port:?80
          ??tls:
          ????certResolver:?ali
          ????domains:
          ????-?main:?"*.qikqiak.com"
          ---
          apiVersion:?traefik.containo.us/v1alpha1
          kind:?Middleware
          metadata:
          ??name:?redirect-https
          ??namespace:?kube-example
          spec:
          ??redirectScheme:
          ????scheme:?https
          ---
          apiVersion:?traefik.containo.us/v1alpha1
          kind:?IngressRoute
          metadata:
          ??name:?wordpress-http
          ??namespace:?kube-example
          spec:
          ??entryPoints:
          ????-?web
          ??routes:
          ??-?match:?Host(`wordpress.qikqiak.com`)
          ????kind:?Rule
          ????services:
          ????-?name:?wordpress
          ??????port:?80
          ????middlewares:?
          ????-?name:?redirect-https

          直接創(chuàng)建上面的資源對象即可:

          $?kubectl?apply?-f?ingressroute.yaml?
          ingressroute.traefik.containo.us/wordpress-https?created
          middleware.traefik.containo.us/redirect-https?created
          ingressroute.traefik.containo.us/wordpress-http?created

          然后對域名 wordpress.qikqiak.com 加上對應的 DNS 解析即可正常訪問了,這樣即使我們的數(shù)據(jù)庫或者 Wordpress 應用掛掉了也不會丟失數(shù)據(jù)了,到這里就完成了我們一個生產(chǎn)級別應用的部署,雖然應用本身很簡單,但是如果真的要部署到生產(chǎn)環(huán)境我們需要關(guān)注的內(nèi)容就比較多了,當然對于線上應用除了上面我們提到的還有很多值得我們關(guān)注的地方,比如監(jiān)控報警、日志收集等方面都是我們關(guān)注的層面。

          wordpress deploy flow



          訓練營推薦





          ?點擊屏末?|??|?即刻學習


          瀏覽 67
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  7799日日夜精品日日夜精品 | 人妻体内射精一区二区三区 | 国产女人18毛片水真多成人如厕 | AV伊人麻豆 | 777片理伦片在线观看 |