達觀資料:kubernetes簡介和實戰

達觀資料發表於2018-12-04

在本文中,我們從技術細節上對kubernetes進行簡單運用介紹,利用一些yaml指令碼層面上例項告訴大家kubernetes基本概念。Kubernetes以及它呈現出的程式設計正規化值得你去使用和整合到自己的技術棧中。

達觀資料:kubernetes簡介和實戰

kubernetes簡單介紹

kubernetes起源

Kubernetes最初認為是谷歌開源的容器叢集管理系統,是Google多年大規模容器管理技術Borg或Omega的開源版本。準確來說的話,kubernetes更是一個全新的平臺,一個全新的平臺管理工具,它是專門為job和service設計。完全開放,2014年6月開始接受公開的commit,任何人都可以提供意見。由於kubernetes簡化了開發、運維和管理負荷,越來越多的企業開始在生產環境使用,因此kubernetes得到了迅速的發展。

kubernetes功能

  • 基於容器的應用部署、維護和滾動升級

  • 負載均衡和服務發現

  • 跨機器和跨地區的叢集排程

  • 自動伸縮

  • 無狀態服務和有狀態服務

  • 廣泛的Volume支援

  • 外掛機制保證擴充套件性

kubernetes核心元件

Kubernetes主要由以下幾個核心元件組成:

  • etcd儲存了整個叢集的狀態;

  • apiserver提供了資源操作的唯一入口,並提供認證、授權、訪問控制、API註冊和發現等機制; 

  • controller manager負責維護叢集的狀態,比如故障檢測、自動擴充套件、滾動更新等; 

  • scheduler負責資源的排程,按照預定的排程策略將Pod排程到相應的機器上; 

  • kubelet負責維護容器的生命週期,同時也負責Volume(CVI)和網路(CNI)的管理; 

  • Container runtime負責映象管理以及Pod和容器的真正執行(CRI); 

  • kube-proxy負責為Service提供cluster內部的服務發現和負載均衡

kubernetes環境部署

如果只是為了瞭解kubernetes,可以使用minikube的方式進行單機安裝,minikube實際就是本地建立了一個虛擬機器,裡面執行了kubernetes的一些必要的環境,相當於k8s的服務環境,建立pod,service,deployment等都是在裡面進行建立和管理。在本文中,我使用kubeadm方式安裝kubernetes 1.10.0,具體kubernetes部署步驟:
  • 使用kubeadm方式安裝kubernetes 1.10.0

  • Kubernetes叢集新增/刪除Node

  • Kubernetes Dashboard1.8.3部署

  • k8s原生的叢集監控方案(Heapster+InfluxDB+Grafana)

請注意:上述環境只是測試環境,生產環境部署大同小異。

kubernetes基本概念

Container

Container(容器)是一種行動式、輕量級的作業系統級虛擬化技術。它使用namespace隔離不同的軟體執行環境,並通過映象自包含軟體的執行環境,從而使得容器可以很方便的在任何地方執行。由於容器體積小且啟動快,因此可以在每個容器映象中打包一個應用程式。這種一對一的應用映象關係擁有很多好處。

使用容器,不需要與外部的基礎架構環境繫結,因為每一個應用程式都不需要外部依賴,更不需要與外部的基礎架構環境依賴,完美解決了從開發到生產環境的一致性問題。容器同樣比虛擬機器更加透明,這有助於監測和管理。尤其是容器程式的生命週期由基礎設施管理,而不是由容器內的程式對外隱藏時更是如此。最後,每個應用程式用容器封裝,管理容器部署就等同於管理應用程式部署。在Kubernetes必須要使用Pod來管理容器,每個Pod可以包含一個或多個容器。

Pod

關於Pod的概念主要有以下幾點:

  • Pod是kubernetes中你可以建立和部署的最小也是最簡的單位。一個Pod代表著叢集中執行的一個程式;

  • 在Kubrenetes叢集中Pod的使用方式;

  • Pod中如何管理多個容器

理解Pod:

上面已經說了“Pod是kubernetes中你可以建立和部署的最小也是最簡的單位。一個Pod代表著叢集中執行的一個程式。”Pod中封裝著應用的容器(有的情況下是好幾個容器),儲存、獨立的網路IP,管理容器如何執行的策略選項。

Pod代表著部署的一個單位:kubernetes中應用的一個例項,可能由一個或者多個容器組合在一起共享資源。

請注意:Docker是kubernetes中最常用的容器執行時,但是Pod也支援其他容器執行時。

Kubrenetes叢集中Pod的兩種使用方式:

(1)一個Pod中執行一個容器“每個Pod中一個容器”的模式是最常見的用法;在這種使用方式中,你可以把Pod想象成是單個容器的封裝,kuberentes管理的是Pod而不是直接管理容器。

實戰:建立一個nginx容器
apiVersion: v1
kind: Pod
metadata:
  name:nginx-test
  labels:
    app: web
spec:
  containers:
  - name:front-end
    image:nginx:1.7.9
    ports:
    -containerPort: 80
建立Pod:
kubectl create -f ./pod1-deployment\
檢視Pod:
kubectl get po
檢視Pod詳細情況:
kubectl describe po nginx-test
進入到Pod(容器)內部:
kubectl exec -it nginx-test  /bin/bash

(2)在一個Pod中同時執行多個容器

說明:在一個Pod中同時執行多個容器是一種比較高階的用法。只有當你的容器需要緊密配合協作的時候才考慮用這種模式。

一個Pod中也可以同時封裝幾個需要緊密耦合互相協作的容器,它們之間共享資源。這些在同一個Pod中的容器可以互相協作成為一個service單位——一個容器共享檔案,另一個“sidecar”容器來更新這些檔案。Pod將這些容器的儲存資源作為一個實體來管理。

實戰:在一個pod裡放置兩個容器:nginx與redis
apiVersion: v1
kind: Pod
metadata:
  name: rss-site
  labels:
    app: web
spec:
  containers:
  - name:front-end
    image:nginx:1.7.9
    ports:
    -containerPort: 80
  - name:rss-reader
    image: redis
    ports:
    -containerPort: 88
建立Pod:
kubectl create -f ./test-deployment
檢視pod
kubectl get po
檢視Pod詳細情況
kubectl describe po rss-site
進入front-end內部:
kubectl exec -it rss-site  -c front-end /bin/bash
進入rss-reade內部:
kubectl exec -it rss-site  -c rss-reader /bin/bash

以上是關於Pod的簡單介紹。

3 Node

Node是Pod真正執行的主機,可以物理機,也可以是虛擬機器。為了管理Pod,每個Node節點上至少要執行container runtime(比如docker或者rkt)、kubelet和kube-proxy服務。

Namespace

Namespace是對一組資源和物件的抽象集合,比如可以用來將系統內部的物件劃分為不同的專案組或使用者組。常見的pods, services, replication controllers和deployments等都是屬於某一個namespace的(預設是default),而node, persistentVolumes等則不屬於任何namespace。

Deployment

我們既然有Pod了,為什麼還要使用Deployment呢?這是因為實際工作中,我們很少會直接在kubernetes中建立單個Pod。因為Pod的生命週期是短暫的,用後即焚的實體。Deployment為Pod和ReplicaSet提供了一個宣告式定義(declarative)方法,用來替代以前的ReplicationController來方便的管理應用。你只需要在Deployment中描述想要的目標狀態是什麼,Deploymentcontroller就會幫你將Pod和ReplicaSet的實際狀態改變到你的目標狀態。你可以定義一個全新的Deployment來建立ReplicaSet或者刪除已有的Deployment並建立一個新的來替換。

什麼是複製控制器(ReplicationController,RC)

RC是K8s叢集中最早的保證Pod高可用的API物件。通過監控執行中的Pod來保證叢集中執行指定數目的Pod副本。指定的數目可以是多個也可以是1個;少於指定數目,RC就會啟動執行新的Pod副本;多於指定數目,RC就會殺死多餘的Pod副本。即使在指定數目為1的情況下,通過RC執行Pod也比直接執行Pod更明智,因為RC也可以發揮它高可用的能力,保證永遠有1個Pod在執行。RC是K8s較早期的技術概念,只適用於長期伺服型的業務型別,比如控制小機器人提供高可用的Web服務。

什麼是副本集(Replica Set,RS)

RS是新一代RC,提供同樣的高可用能力,區別主要在於RS後來居上,能支援更多種類的匹配模式。副本集物件一般不單獨使用,而是作為Deployment的理想狀態引數使用。

Deployment典型的應用場景

  • 定義Deployment來建立Pod和ReplicaSet

  • 滾動升級和回滾應用;如果當前狀態不穩定,回滾到之前的Deployment revision。每次回滾都會更新Deployment的revision

  • 擴容和縮容,擴容Deployment以滿足更高的負載

  • 暫停和繼續Deployment,暫停Deployment來應用PodTemplateSpec的多個修復,然後恢復上線

實戰Deployment

比如,我們這裡定義一個簡單的nginx應用:

apiVersion:extensions/v1beta1
kind: Deployment
metadata:
    name:nginx-test
    namespace:test
spec:
    replicas: 3
    template:
       metadata:
           labels:
               app: nginx
        spec:
           containers:
              -name: nginx
               image: nginx:1.7.9
                ports:
               - containerPort: 80 
建立deploy
kubectl create -f ./nginx-deployment
檢視deploy
kubectl get deploy --namespace=test
檢視rs(副本集)
kubectl get rs --namespace=test
檢視pods(容器組)
kubectl get po --namespace=test 

關於Deployment的應用還有很多,如:擴容、縮容、滾動升級、回滾應用等,這裡由於篇幅的問題不再一一介紹。

5 Label

Label是識別Kubernetes物件的標籤,以key/value的方式附加到物件上(key最長不能超過63位元組,value可以為空,也可以是不超過253位元組的字串)。

Label不提供唯一性,並且實際上經常是很多物件(如Pods)都使用相同的label來標誌具體的應用。Label定義好後其他物件可以使用Label Selector來選擇一組相同label的物件(比如ReplicaSet和Service用label來選擇一組Pod)。Label Selector支援以下幾種方式:

  • 等式,如app=nginx和env!=production

  • 集合,如env in(production, qa)

  • 多個label(它們之間是AND關係),如app=nginx,env=test

Service Account

Service account作用

Service account是為了方便Pod裡面的程式呼叫Kubernetes API或其他外部服務。

Serviceaccount使用場景

執行在pod裡的程式需要呼叫Kubernetes API以及非Kubernetes API的其它服務。Service Account它並不是給kubernetes叢集的使用者使用的,而是給pod裡面的程式使用的,它為pod提供必要的身份認證。

與User account區別

  • User account是為人設計的,而service account則是為了Pod中的程式

  • User account是跨namespace的,而service account則是僅侷限它所在的namespace

實戰名稱空間

apiVersion: v1
kind: Namespace
metadata:
  name:datagrand
  labels:
    name: test 
建立namespace:test
kubectl create -f ./test.yaml 
檢視名稱空間test的sa
kubectl get sa -n test
檢視名稱空間test生成的default
kubectl get sa default -o yaml -n test
我們可以建立Deployment時,使用這個test名稱空間了,如上例Deployment實戰。   

Service Account鑑權

Service Account為服務提供了一種方便的認知機制,但它不關心授權的問題。可以配合RBAC來為Service Account鑑權:

  • 配置--authorization-mode=RBAC和--runtime-config=rbac.authorization.k8s.io/v1alpha1

  • 配置--authorization-rbac-super-user=admin

  • 定義Role、ClusterRole、RoleBinding或ClusterRoleBinding

實戰鑑權

我們在Kubernetes Dashboard1.8.3部署中,碰到首次登入出現訪問許可權報錯的問題,原因就是ServiceAccount的建立問題。

apiVersion:rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
      name:kubernetes-dashboard
      labels:
         k8s-app: kubernetes-dashboard
roleRef:
      apiGroup:rbac.authorization.k8s.io
      kind:ClusterRole
      name:cluster-admin
subjects:
    - kind:ServiceAccount
      name:kubernetes-dashboard
      namespace:kube-system  

Secret

Secret介紹

Secret解決了密碼、token、金鑰等敏感資料的配置問題,而不需要把這些敏感資料暴露到映象或者Pod Spec中。Secret可以以Volume或者環境變數的方式使用。

Secret型別

  • Opaque(default):任意字串,base64編碼格式的Secret,用來儲存密碼、金鑰等

  • kubernetes.io/service-account-token:作用於ServiceAccount,就是kubernetes的Service Account中所說的。即用來訪問Kubernetes API,由Kubernetes自動建立,並且會自動掛載到Pod的/run/secrets/kubernetes.io/serviceaccount目錄中

  • kubernetes.io/dockercfg:作用於Docker registry,使用者下載docker映象認證使用。用來儲存私有docker registry的認證資訊

實戰Opaque Secret型別

Opaque型別的資料是一個map型別,要求value是base64編碼格式:
建立admin賬戶
echo -n "admin" | base64
YWRtaW4=
echo -n "1f2d1e2e67df" | base64
MWYyZDFlMmU2N2Rm 
建立secret.yaml
cat >> secrets.yml << EOF
apiVersion: v1
kind: Secret
metadata:
    name:mysecret
type: Opaque
data:
    password:MWYyZDFlMmU2N2Rm
    username:YWRtaW4=  
建立secret
kubectl create -f secrets.yml  
檢視secret執行狀態
kubectl get secret --all-namespaces   

Secret使用方式

  • 以Volume方式

  • 以環境變數方式

實戰Secret使用Volume方式

apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    name: wtf
spec:
  volumes:
  - name:secrets
    secret:
     secretName: mysecret
  containers:
  - image:nginx:1.7.9
    name: nginx
   volumeMounts:
    - name:secrets
      mountPath:"/etc/secrets"
      readOnly:true
    ports:
    - name: cp
     containerPort: 5432
      hostPort:5432   

說明:這樣就可以通過檔案的方式掛載到容器內,在/etc/secrets目錄下回生成這個檔案。

實戰Secret使用環境變數方式

apiVersion:extensions/v1beta1
kind: Deployment
metadata:
  name:wordpress-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app:wordpress
    spec:
     containers:
      - name:"wordpress"
        image:"wordpress:latest"
        ports:
        -containerPort: 80
        env:
        - name:WORDPRESS_DB_USER
         valueFrom:
           secretKeyRef:
             name: mysecret
             key: username
        - name:WORDPRESS_DB_PASSWORD
         valueFrom:
           secretKeyRef:
             name: mysecret
             key: password
檢視Pod執行狀態
kubectl get po
NAME                        READY     STATUS   RESTARTS     AGE
wordpress-deployment-6b569fbb7d-8qcpg   1/1      Running   0         2m
wordpress-deployment-6b569fbb7d-xwwkg   1/1      Running   0         2m    
進入容器內部檢視環境變數
kubectl exec -itwordpress-deployment-694f4c79b4-cpsxw /bin/bash
root@wordpress-deployment-694f4c79b4-cpsxw:/var/www/html#env
WORDPRESS_DB_USER=admin
WORDPRESS_DB_PASSWORD=1f2d1e2e67df   

ConfigMap

Configure說明

ConfigMaps允許你將配置檔案、命令列引數或環境變數中讀取的配置資訊與docker image分離,以保持集裝箱化應用程式的便攜性。即ConfigMapAPI給我們提供了向容器中注入配置資訊的機制。

理解ConfigMaps和Pods 

ConfigMap API資源用來儲存key-value pair配置資料,這個資料可以在pods裡使用,或者被用來為像controller一樣的系統元件儲存配置資料。雖然ConfigMap跟Secrets類似,但是ConfigMap更方便的處理不含敏感資訊的字串。

注意:ConfigMaps不是屬性配置檔案的替代品。ConfigMaps只是作為多個properties檔案的引用。你可以把它理解為Linux系統中的/etc目錄,專門用來儲存配置檔案的目錄。

實戰建立ConfigMap

kind: ConfigMap
apiVersion: v1
metadata:
 creationTimestamp: 2016-02-18T19:14:38Z
  name:example-config
  namespace:default
  data:
   example.property.1: hello
   example.property.2: world
   example.property.file: |-
     property.1=value-1
     property.2=value-2
     property.3=value-3

data一欄包括了配置資料,ConfigMap可以被用來儲存單個屬性,也可以用來儲存一個配置檔案。配置資料可以通過很多種方式在Pods裡被使用。ConfigMaps可以被用來:

  • 設定環境變數的值

  • 在容器裡設定命令列引數

  • 在資料卷裡面建立config檔案

Volum

為什麼需要Volume

容器磁碟上檔案的生命週期是短暫的,這就使得在容器中執行重要應用時出現一些問題。比如,當容器崩潰時,kubelet會重啟它,但是容器中的檔案將丟失--容器以乾淨的狀態(映象最初的狀態)重新啟動。

其次,在Pod中同時執行多個容器時,這些容器之間通常需要共享檔案。Kubernetes中的Volume抽象就很好的解決了這些問題。

Volume背景

Docker中也有一個volume的概念,儘管它稍微寬鬆一些,管理也很少。在Docker中,卷就像是磁碟或是另一個容器中的一個目錄。它的生命週期不受管理,直到最近才有了local-disk-backed卷。Docker現在提供了卷驅動程式,但是功能還非常有限(例如Docker1.7只允許每個容器使用一個卷驅動,並且無法給卷傳遞引數)。

Kubernetes中的卷有明確的壽命——與封裝它的Pod相同。所以,卷的生命比Pod中的所有容器都長,當這個容器重啟時資料仍然得以儲存。當然,當Pod不再存在時,卷也將不復存在。也許更重要的是,Kubernetes支援多種型別的卷,Pod可以同時使用任意數量的卷。要使用卷,需要為pod指定為卷(spec.volumes欄位)以及將它掛載到容器的位置(spec.containers.volumeMounts欄位)。

Volume型別

Kubernetes支援以下型別的卷:

awsElasticBlockStore、azureDisk、azureFile、cephfs、csi、downwardAPI、emptyDir、fc (fibre channel)、flocker、gcePersistentDisk、gitRepo、glusterfs、hostPath、iscsi、local、nfs、persistentVolumeClaim、projected、portworxVolume、quobyte、rbd、scaleIO、secret、storageos、vsphereVolume等

K8S的儲存系統分類

K8S的儲存系統從基礎到高階大致分為三個層次:普通Volume,Persistent Volume和動態儲存供應。

普通Volume

最簡單的普通Volume是單節點Volume。它和Docker的儲存卷類似,使用的是Pod所在K8S節點的本地目錄。

persistent volume

它和普通Volume的區別是什麼呢?

普通Volume和使用它的Pod之間是一種靜態繫結關係,在定義Pod的檔案裡,同時定義了它使用的Volume。Volume是Pod的附屬品,我們無法單獨建立一個Volume,因為它不是一個獨立的K8S資源物件。而Persistent Volume簡稱PV是一個K8S資源物件,所以我們可以單獨建立一個PV。它不和Pod直接發生關係,而是通過Persistent Volume Claim,簡稱PVC來實現動態繫結。Pod定義裡指定的是PVC,然後PVC會根據Pod的要求去自動繫結合適的PV給Pod使用。

PV的訪問模式有三種:

  • ReadWriteOnce:是最基本的方式,可讀可寫,但只支援被單個Pod掛載

  • ReadOnlyMany:可以以只讀的方式被多個Pod掛載

  • ReadWriteMany:這種儲存可以以讀寫的方式被多個Pod共享。比較常用的是NFS

一般來說,PV和PVC的生命週期分為5個階段:

  • Provisioning,即PV的建立,可以直接建立PV(靜態方式),也可以使用StorageClass動態建立

  • Binding,將PV分配給PVC

  • Using,Pod通過PVC使用該Volume

  • Releasing,Pod釋放Volume並刪除PVC

  • Reclaiming,回收PV,可以保留PV以便下次使用,也可以直接從雲端儲存中刪除

根據這5個階段,Volume的狀態有以下4種:

  • Available:可用

  • Bound:已經分配給PVC

  • Released:PVC解綁但還未執行回收策略

  • Failed:發生錯誤

變成Released的PV會根據定義的回收策略做相應的回收工作。有三種回收策略:

  • Retain就是保留現場,K8S什麼也不做,等待使用者手動去處理PV裡的資料,處理完後,再手動刪除PV

  • Delete K8S會自動刪除該PV及裡面的資料

  • Recycle K8S會將PV裡的資料刪除,然後把PV的狀態變成Available,又可以被新的PVC繫結使用

在實際使用場景裡,PV的建立和使用通常不是同一個人。這裡有一個典型的應用場景:管理員建立一個PV池,開發人員建立Pod和PVC,PVC裡定義了Pod所需儲存的大小和訪問模式,然後PVC會到PV池裡自動匹配最合適的PV給Pod使用。

前面在介紹PV的生命週期時,提到PV的供給有兩種方式,靜態和動態。其中動態方式是通過StorageClass來完成的,這是一種新的儲存供應方式。

使用StorageClass有什麼好處呢?除了由儲存系統動態建立,節省了管理員的時間,還有一個好處是可以封裝不同型別的儲存供PVC選用。在StorageClass出現以前,PVC繫結一個PV只能根據兩個條件,一個是儲存的大小,另一個是訪問模式。在StorageClass出現後,等於增加了一個繫結維度。

比如這裡就有兩個StorageClass,它們都是用谷歌的儲存系統,但是一個使用的是普通磁碟,我們把這個StorageClass命名為slow。另一個使用的是SSD,我們把它命名為fast。在PVC裡除了常規的大小、訪問模式的要求外,還通過annotation指定了Storage Class的名字為fast,這樣這個PVC就會繫結一個SSD,而不會繫結一個普通的磁碟。限於篇幅問題,這裡簡單說一下emptyDir、nfs、PV和PVC。

實戰--emptyDir

emptyDir說明

EmptyDir型別的volume建立於pod被排程到某個宿主機上的時候,而同一個pod內的容器都能讀寫EmptyDir中的同一個檔案。一旦這個pod離開了這個宿主機,EmptyDir中的資料就會被永久刪除。所以目前EmptyDir型別的volume主要用作臨時空間,比如Web伺服器寫日誌或者tmp檔案需要的臨時目錄。

apiVersion: v1
kind: Pod
metadata:
  labels:
    name:test-emptypath
    role: master
  name: test-emptypath
spec:
  containers:
  - name:test-emptypath
    image:nginx:1.7.9
   volumeMounts:
    - name:log-storage
      mountPath:/tmp/
  volumes:
  - name:log-storage
    emptyDir: {}

實戰使用共享卷的標準多容器Pod

apiVersion: v1
kind: Pod
metadata:
  name:datagrand
spec:
  containers:
  - name: test1
    image:nginx:1.7.9
   volumeMounts:
    - name:log-storage
      mountPath:/usr/share/nginx/html
  - name: test2
    image:centos
   volumeMounts:
    - name:log-storage
      mountPath:/html
    command:["/bin/sh","-c"]
    args:
    - whiletrue;do
      data>> /html/index.html;
      sleep 1;
      done
  volumes:
    - name:log-storage
      emptyDir:{}  

簡單解釋下上面的內容:在這個例子中,我們定義了一個名為HTML的卷。它的型別是emptyDir,這意味著當一個Pod被分配到一個節點時,卷先被建立,並只要Pod在節點上執行時,這個卷仍存在。正如名字所說,它最初是空的。

第一容器執行nginx的伺服器並將共享卷掛載到目錄/ usr /share/ nginx /html。第二容器使用centos的映象,並將共享卷掛載到目錄/HTML。每一秒,第二容器新增當前日期和時間到index.html檔案中,它位於共享卷。當使用者發出一個HTTP請求到Pod,nginx的伺服器讀取該檔案並將其傳遞給響應請求的使用者。

實戰--NFS卷

NFS卷說明

nfs卷允許將現有的NFS(網路檔案系統)共享掛載到你的容器中。不像emptyDir,當刪除Pod時,nfs卷的內容被保留,卷僅僅是被解除安裝。這意味著NFS卷可以預填充資料,並且可以在pod之間“切換”資料。NFS可以被多個寫入者同時掛載。

NFS卷使用注意

  • 請先部署好自己的NFS服務

  • 在使用共享之前,必須執行自己的NFS伺服器並執行共享

實戰pod內的檔案共享

apiVersion: v1
kind: Pod
metadata:
  name:nginx-test
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image:nginx:1.7.9
    ports:
    -containerPort: 80
     volumeMounts:
    #Mount thepath to the container
      - mountPath:"/tmp/"
        name:pv0003
  volumes:
  - name: pv0003
    nfs:
    #fixed:This ip is the addressof the nfs server
      server:192.168.246.169
    #fixed:This path is sharedexternally by the nfs server
      path:"/data"   

實戰PV和PVC

nfs作為k8s的網路儲存驅動,可以滿足持久儲存業務的需求,支援多節點讀寫。下面是兩個Pod同時使用一個永續性volume例項。

#建立PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 4Gi
  accessModes:
    -ReadWriteMany
  nfs:
    server:192.168.246.168  ##NFS伺服器的ip地址
    path:"/data"  ##NFS伺服器上的共享目錄
#建立PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
 storageClassName: ""
  resources:
    requests:
      storage:3Gi   
#建立Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name:nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app:nginx
    spec:
     containers:
      - name:nginx
        image:nginx:1.7.9
       volumeMounts:
          -mountPath: "/wtf"
           name: datadir
      volumes:
      - name:datadir
       persistentVolumeClaim:
         claimName: nfs-pvc    -containerPort: 80

10 Service

Kubernetes Pod是有生命週期的,它們可以被建立,也可以被銷燬,然而一旦被銷燬生命就永遠結束。通過ReplicaSets能夠動態地建立和銷燬Pod(例如,需要進行擴縮容,或者執行滾動升級)。每個Pod都會獲取它自己的IP地址,即使這些IP地址不總是穩定可依賴的。這會導致一個問題:在Kubernetes叢集中,如果一組Pod(稱為backend)為其它Pod(稱為frontend)提供服務,那麼那些frontend該如何發現,並連線到這組Pod中的哪些backend呢?

什麼是Service

Kubernetes Service定義了這樣一種抽象:一個Pod的邏輯分組,一種可以訪問它們的策略——通常稱為微服務。這一組Pod能夠被Service訪問到,通常是通過Label Selector(檢視下面瞭解,為什麼可能需要沒有selector的Service)實現的。

舉個例子,考慮一個圖片處理backend,它執行了3個副本。這些副本是可互換的—— frontend不需要關心它們呼叫了哪個backend副本。然而組成這一組backend程式的Pod實際上可能會發生變化,frontend客戶端不應該也沒必要知道,而且也不需要跟蹤這一組backend的狀態。Service定義的抽象能夠解耦這種關聯。

對Kubernetes叢集中的應用,Kubernetes提供了簡單的Endpoints API,只要Service中的一組Pod發生變更,應用程式就會被更新。對非Kubernetes叢集中的應用,Kubernetes提供了基於VIP的網橋的方式訪問Service,再由Service重定向到backend Pod。

定義Service

有selector的單埠Service

一個Service在Kubernetes中是一個REST物件,和Pod類似。像所有的REST物件一樣,Service定義可以基於POST方式,請求apiserver建立新的例項。例如,假定有一組Pod,它們對外暴露了9376埠,同時還被打上"app=MyApp"標籤。

kind: Service
apiVersion: v1
metadata:
  name:my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol:TCP
      port: 80
      targetPort: 9376  

上述配置將建立一個名稱為“my-service”的Service物件,它會將請求代理到使用TCP埠9376,並且具有標籤"app=MyApp"的Pod上。這個Service將被指派一個IP地址(通常稱為“Cluster IP”),它會被服務的代理使用(見下面)。該Service的selector將會持續評估,處理結果將被POST到一個名稱為“my-service”的Endpoints物件上。

需要注意的是,Service能夠將一個接收埠對映到任意的targetPort。預設情況下,targetPort將被設定為與port欄位相同的值。可能更有趣的是,targetPort可以是一個字串,引用了backend Pod的一個埠的名稱。但是,實際指派給該埠名稱的埠號,在每個backend Pod中可能並不相同。對於部署和設計Service,這種方式會提供更大的靈活性。例如,可以在backend軟體下一個版本中,修改Pod暴露的埠,並不會中斷客戶端的呼叫。

Kubernetes Service能夠支援TCP和UDP協議,預設TCP協議。

有selector的多埠Service

很多Service需要暴露多個埠。對於這種情況,Kubernetes支援在Service物件中定義多個埠。當使用多個埠時,必須給出所有的埠的名稱,這樣Endpoint就不會產生歧義,例如:

kind: Service
apiVersion: v1
metadata:
  name:my-service
spec:
    selector:
      app: MyApp
    ports:
      - name:http
       protocol: TCP
        port: 80
       targetPort: 9376
      - name:https
       protocol: TCP
        port:443
       targetPort: 9377  

沒有selector的Service

Service抽象了該如何訪問Kubernetes Pod,但也能夠抽象其它型別的backend,例如:

  • 希望在生產環境中使用外部的資料庫叢集,但測試環境使用自己的資料庫

  • 希望服務指向另一個Namespace中或其它叢集中的服務。

  • 正在將工作負載轉移到Kubernetes叢集,和執行在Kubernetes叢集之外的backend。

根據以上的應用場景,我們都能夠定義沒有selector的Service,如下:

kind: Service
apiVersion: v1
metadata:
  name:my-service
spec:
  ports:
    - protocol:TCP
      port: 80
     targetPort: 9376  

由於這個Service沒有selector,就不會建立相關的Endpoints物件。可以手動將Service對映到指定的Endpoints:

kind: Endpoints
apiVersion: v1
metadata:
  name:my-service
subsets:
  - addresses:
      - ip:10.0.0.3  ##Endpoint IP = PodIP +ContainerPort
    ports:
      - port:9376  

注意:Endpoint IP地址不能是loopback(127.0.0.0/8)、link-local(169.254.0.0/16)、或者link-local多播(224.0.0.0/24)。訪問沒有selector的Service,與有selector的Service的原理相同。請求將被路由到使用者定義的Endpoint(該示例中為10.0.0.3:9376)。

釋出服務——服務型別

對一些應用(如Frontend)的某些部分,可能希望通過外部(Kubernetes叢集外部)IP地址暴露Service。Kubernetes ServiceTypes允許指定一個需要的型別的Service,預設是ClusterIP型別。Type的取值以及行為如下:

  • ClusterIP通過叢集的內部IP暴露服務,選擇該值,服務只能夠在叢集內部可以訪問,這也是預設的ServiceType。

  • NodePort通過每個Node上的IP和靜態埠(NodePort)暴露服務。NodePort服務會路由到ClusterIP服務,這個ClusterIP服務會自動建立。通過請求:,可以從叢集的外部訪問一個NodePort服務。

  • LoadBalancer使用雲提供商的負載均衡器,可以向外部暴露服務。外部的負載均衡器可以路由到NodePort服務和ClusterIP服務。

  • ExternalName通過返回CNAME和它的值,可以將服務對映到externalName欄位的內容(例如,foo.bar.example.com)。沒有任何型別代理被建立,這隻有Kubernetes 1.7或更高版本的kube-dns才支援。

NodePort型別

如果設定type的值為"NodePort",Kubernetes master將從給定的配置範圍內(預設:30000-32767)分配埠,每個Node將從該埠(每個Node上的同一埠)代理到Service。該埠將通過Service的spec.ports[*].nodePort欄位被指定。如果需要指定的埠號,可以配置nodePort的值,系統將分配這個埠,否則呼叫API將會失敗(比如,需要關心埠衝突的可能性)。

kubernetes實戰--edusoho平臺建立

檔案目錄結構

# pwd
/data
# tree -L 3
.
├── mysql
│   ├── conf
│   │   └── my.cnf
│   └── data
│      ├── auto.cnf
│      ├── edusoho
│      ├── ibdata1
│      ├── ib_logfile0
│      ├── ib_logfile1
│      ├── mysql
│      └── performance_schema
├── nginx
│   ├── conf
│   │   └── nginx.conf
│   ├── edusoho
│   │   ├── api
│   │   ├── app
│   │   ├── bootstrap
│   │   ├── plugins
│   │   ├── src
│   │   ├── vendor
│   │   ├── vendor_user
│   │   └── web
│   └── log
│      └── error.log
├── php
│   ├── log
│   │   └── php-fpm.log
│   ├── php-fpm.conf
│   ├── php.ini
│   └── www.conf

Pod的yaml檔案

apiVersion: v1
kind: Pod
metadata:
  name:lamp-edusoho
  labels:
    app:lamp-edusoho
restartPolicy: Always
spec:
  containers:
  - name: nginx
    abels:
      app:lamp-nginx
    image:dockerhub.datagrand.com/global/nginx:v1
    ports:
    -containerPort: 80
   volumeMounts:
      - name:datadir
       mountPath: "/var/log/nginx/error.log"
        subPath:./nginx/log/error.log
      - name:datadir
       mountPath: "/etc/nginx/nginx.conf"
        subPath:./nginx/conf/nginx.conf
      - name:datadir
       mountPath: "/usr/share/nginx/html"
        subPath:./nginx/edusoho
  - name: php
    image:dockerhub.datagrand.com/global/php:v1
    ports:
    -containerPort: 9000
   volumeMounts:
      -mountPath: /usr/local/php/etc/php-fpm.conf
        name:datadir
        subPath:./php/php-fpm.conf
      -mountPath: /usr/local/php/etc/php-fpm.d/www.conf
        name:datadir
        subPath:./php/www.conf
      -mountPath: /usr/local/php/etc/php.ini
        name:datadir
        subPath:./php/php.ini
      -mountPath: /usr/local/php/var/log/php-fpm.log
        name:datadir
        subPath:./php/log/php-fpm.log
      -mountPath: /usr/share/nginx/html
        name:datadir
        subPath:./nginx/edusoho
  - name: mysql
    image:dockerhub.datagrand.com/global/mysql:5.6
    ports:
    -containerPort: 3306
    env:
      - name:MYSQL_ROOT_PASSWORD
        value:"123456"
      - name:MYSQL_DATABASE
        value:"edusoho"
      - name:MYSQL_USER
        value:"edusoho"
      - name:MYSQL_PASSWORD
        value:"edusoho"
    args:['--character-set-server=utf8']
   volumeMounts:
      - name:datadir
       mountPath: "/var/lib/mysql"
        subPath:./mysql/data
      - name:datadir
       mountPath: "/etc/my.cnf"
        subPath:./mysql/conf/my.cnf
  volumes:
  - name:datadir
    persistentVolumeClaim:
      claimName:nfs-pvc

PV的yaml檔案

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 4Gi
  accessModes:
    -ReadWriteMany
  nfs:
    server:192.168.246.168  ##NFS伺服器的ip地址
    path:"/data"  ##NFS伺服器上的共享目錄

PVC的yaml檔案

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    -ReadWriteMany
 storageClassName: ""
  resources:
    requests:
      storage:3Gi

Service的yaml檔案

apiVersion: v1
kind: Service
metadata:
  name: edusoho
  labels:
    app: edusoho
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort:32756
  selector:
    app:lamp-edusoho

命令彙總

檢視Pod
kubectl get po -o wide
檢視Service
kubectl get svc
進入容器內部某個應用,如這裡的nginx
kubectl exec -it lamp-edusoho -c nginx /bin/bash

訪問安裝Edusoho平臺

http://192.168.246.168:32756/install/start-install.php
說明:這裡的192.168.246.168是kubernetes的node節點IP,32756是Service中定義的nodePort。
參考文件
  • kubernetes概念--Service:https://kubernetes.io/docs/concepts/services-networking/service/

  • kubernetes官網教程:https://kubernetes.io/docs/tutorials/

  • Kubernetes中的Persistent Volume解析:https://jimmysong.io/posts/kubernetes-persistent-volume/


關於作者


吳騰飛:達觀資料運維工程師 ,負責達觀資料系統平臺和應用業務的快速部署、監控、優化及維護,設計並研發自動化運維工具和平臺,資料庫的日常維護。

對自動化運維,Docker容器、虛擬化技術和容器編排kubernetes相關領域有濃厚興趣。

相關文章