上一篇"經過實例快速掌握k8s(Kubernetes)核心概念"講解了k8s的核心概念,有了核心概念整個骨架就完整了,應付無狀態程序已經夠了,但還不夠豐滿。應用程序分紅兩種,無狀態和有狀態的。通常的前段和後端程序都是無狀態的,而數據庫是有狀態的,他須要把數據存儲起來,這樣即便斷電,數據也不會丟失。要建立有狀態的程序,還須要引入另一些k8s概念。它們雖然不是核心,但也很重要,共有三個,持久卷,網絡和參數配置。掌握了這些以後,基本概念就已經作到了全覆蓋,k8s就已經入門了。咱們經過搭建MySQL來熟悉這些k8s概念。容器自己是無狀態的,一旦出現問題它會被隨時銷燬,它存儲的數據也就丟失了。MySQL須要一個能保存數據的持久層,在容器被銷燬以後仍然存在,k8s叫它持久卷。html
在k8s上安裝MySQL以前,先用Docker驗證一下MySQL鏡像:node
docker run --name test-mysql -p 3306:33060 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
「root」是根(root)用戶的password,這裏是在建立MySQL容器時指定「root」用戶的password。「test-MySQL」是容器的名字。「mysql:5.7」用的是docker庫裏的「MySQL」5.7版本。此次沒有用最新的8.0版,由於新版跟之前的客戶端不兼容,須要修改不少東西。所用的鏡像是全版的Linux,於是文件比較大,有400M。mysql
容器建好了以後,鍵入「docker logs test-mysql」,查看日誌。nginx
... 2019-10-03T06:18:50.439784Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.17' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL. 2019-10-03T06:18:50.446543Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Socket: '/var/run/mysqld/mysqlx.sock' bind-address: '::' port: 33060
查看容器狀態。sql
vagrant@ubuntu-xenial:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3b9c50420f5b mysql:latest "docker-entrypoint.s…" 11 minutes ago Up 11 minutes 3306/tcp, 33060/tcp test-mysql
爲了驗證MySQL,須要在虛機上安裝MySQL客戶端。docker
sudo apt-get -y -f install mysql-client
完成以後,鍵入「docker inspect test-mysql」找到容器IP地址, 下面顯示"172.17.0.2"是容器IP地址。shell
vagrant@ubuntu-xenial:~$ docker inspect test-mysql ... "Gateway": "172.17.0.1", "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", ...
鍵入「mysql -h 172.17.0.2 -P 3306 --protocol=tcp -u root -p」登陸到MySQL,"172.17.0.2"是MySQL的IP地址,「3306」是MySQL端口,是在建立鏡像時設定的對外開放的端口,「root」是用戶名,「-p」是password的參數選項。敲入命令後,系統要求輸入password,輸入後,顯示已成功鏈接到MySQL。數據庫
vagrant@ubuntu-xenial:~$ mysql -h 172.17.0.2 -P 3306 --protocol=tcp -u root -p ... Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 Server version: 5.7.27 MySQL Community Server (GPL) ...
在k8s上安裝MySQL分紅三個部分,建立部署文件,建立服務文件和安裝測試。編程
下面是部署配置文件。在上一篇文章中已經詳細講解了文件格式,全部的k8s的配置文件格式都是相同的。「template」之上是部署配置,從「template」向下是Pod配置。從「containers」開始是Pod裏面的容器配置。「env:」是環境變量,這裏經過環境變量來設置數據庫的用戶名和口令,後面還會詳細講解。MySQL的端口是「3306」ubuntu
apiVersion: apps/v1 kind: Deployment # 類型是部署 metadata: name: mysql-deployment # 對象的名字 spec: selector: matchLabels: app: mysql #用來綁定label是「mysql」的Pod strategy: type: Recreate template: # 開始定義Pod metadata: labels: app: mysql #Pod的Label,用來標識Pod spec: containers: # 開始定義Pod裏面的容器 - image: mysql:5.7 name: mysql-con imagePullPolicy: Never env: # 定義環境變量 - name: MYSQL_ROOT_PASSWORD # 環境變量名 value: root # 環境變量值 - name: MYSQL_USER value: dbuser - name: MYSQL_PASSWORD value: dbuser args: ["--default-authentication-plugin=mysql_native_password"] ports: - containerPort: 3306 # mysql端口 name: mysql
下面是服務配置文件,這個與上一篇講的配置基本相同,這裏就不解釋了。
apiVersion: v1 kind: Service metadata: name: mysql-service labels: app: mysql spec: type: NodePort selector: app: mysql ports: - protocol : TCP nodePort: 30306 port: 3306 targetPort: 3306
有了配置文件後,下面就開始建立MySQL。在建立時要按照順序,依次進行,先從最底層的對象開始建立。
建立部署和服務:
kubectl apply -f mysql-deployment kubectl apply -f mysql-service.yaml
查看服務:
vagrant@ubuntu-xenial:~$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3h42m mysql-service NodePort 10.102.253.32 <none> 3306:30306/TCP 3h21m
「mysql-service」的端口(PORT(S))有兩個,「3306」是k8s內部端口,「30306」是外部端口。因爲「NodePort」已經打開了對外端口,這時就能夠在虛擬機上經過「30306」端口訪問MySQL。
vagrant@ubuntu-xenial:~$ mysql -h localhost -P 30306 --protocol=tcp -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 6 Server version: 5.7.27 MySQL Community Server (GPL) ... mysql>
這時本地虛機已經與k8s聯通了,下一步就能夠在宿主機( 筆記本)上用圖形客戶端來訪問MySQL了。我是在Vagrant裏設定了私有網絡,設定的虛機IP地址是 "192.168.50.4」,就用這個地址和30306端口來訪問MySQL。
這裏的網絡有兩層含義,一層是k8s網絡,就是讓k8s內部服務之間能夠互相訪問,而且從k8s集羣外部能夠訪問它內部的服務。另外一層是宿主機(筆記本)和虛機之間的網路,就是在宿主機上能夠訪問虛機。這兩層都通了以後,就能夠在宿主機直接訪問k8s集羣裏面的MySQL。
k8s的網絡也有兩層含義,一個是集羣內部的,k8s有內部DNS,能夠經過服務名進行尋址。另外一個是從集羣外部訪問集羣內部服務,一共有四種方式,詳情請見「Kubernetes NodePort vs LoadBalancer vs Ingress? When should I use what?」
下面是服務信息,「EXTERNAL-IP」是"pending",說明外部網絡不通。
$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 31d nginx-service LoadBalancer 10.104.228.212 <pending> 80:31999/TCP 45h
下面是在運行「minikube tunnel 」(在另外一個窗口運行)以後的服務信息,「EXTERNAL-IP」是 「10.104.228.212」。這時Minikube的LoadBalancer已經起做用了,如今就能夠經過IP地址從外部訪問k8s內部的服務了,「80」是k8s內部端口,「31999」是k8s對外端口。
$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 31d nginx-service LoadBalancer 10.104.228.212 10.104.228.212 80:31999/TCP 45h
這是一種比較好的方式,但不能控制它的IP地址和端口,所以我暫時沒有采用它。
ClusterIP: 這個只能在k8s集羣內部尋址。
Ingress: 這是推薦的方法,通常在生產環境中使用。Load balancer的問題是每個服務都要有一個Load balancer,服務多了以後會很麻煩,這時就會用Ingress,它的缺點是配置起來比較複雜。Minikube自帶了一個基於Nginx的Ingress控制器,只需運行「minikube addons enable ingress」,就好了。但Ingress的設置較複雜,所以這裏沒有用它。
這裏講的是宿主機(筆記本)和虛機之間的互相訪問,主要是從宿主機訪問虛機。我用的是Vagrant, 所以要在Vagran的配置文件(Vagrantfile)裏進行配置。它有兩種方法:
當配置私有網絡時,須要在筆記本的VirtualBox上配置「Host-only Adapter」,以下圖所示。
但這會形成在Vagrant啓動Minikube時產生以下錯誤:「VBoxManage.exe: error: Failed to create the host-only adapter」。這是VirtualBox的一個Bug,你能夠下載一個軟件解決,詳見這裏. 這個軟件已是四年以前的了,開始還擔憂是否與如今的VirtualBox版本兼容,結果很好用,並且它是一個單獨運行的軟件,不會與如今的軟件衝突。只要在啓動虛機以前,用管理員身份運行這個補丁就好了。另一個問題是,我原來使用的是5.x版的VirtualBox,上圖中只能選「NAT」,不能選「Host-only Adapter」,升級到6.X以後才能選「Host-only Adapter」。但當虛機從新啓動以後,它會自動變回「NAT」,不過私有網絡仍是可用。
k8s卷的概念包括卷和持久卷。
卷是k8s的存儲概念,它依附於Pod,不能單獨存在。但它不是在容器層。所以若是容器被從新啓動,卷仍然在。但若是Pod從新啓動,卷就丟失了。若是一個Pod裏有多個容器,那麼這些容器共享Pod的卷。你能夠把卷當作是一個目錄,裏面能夠存儲各類文件。k8s支持各類類型的卷,例如本地文件系統和各類雲存儲。
是對卷的一個封裝,目的是爲了更好地管理卷。它的生命週期不須要與Pod綁定,它能夠獨立於Pod存在。
是對持久卷資源的一個申請,你能夠申請特定的存儲容量的大小和訪問模式,例如讀寫模式或只讀模式。k8s會根據持久卷申請分配適合的持久卷,若是沒有合適的,系統會自動建立一個。持久卷申請是對持久卷的一個抽象,就像編程裏的接口(Interface),它能夠有不一樣的具體實現(持久卷)。例如,阿里雲和華爲雲支持的存儲系統不一樣,它生成的持久卷也不相同。持久卷是與特定的存儲實現綁定的。那你要把程序從阿里雲移植到華爲雲,怎麼保證配置文件的兼容性呢?你就用持久卷申請來作這個接口,它只規定存儲容量大小和訪問模式,而由阿里雲和華爲雲自動生成各自雲裏知足這個接口需求的持久卷. 不過,它還有一個限制條件,那就是持久卷申請和持久卷的StorageClass須要匹配,這使它沒有接口靈活。後面會詳細講解。
在這種狀況下,你只需建立持久卷申請(不須要單首創建持久卷),而後把持久卷申請與部署綁定。系統會按照持久卷申請自動建立持久卷。下面是持久卷申請配置文件。其中「storage:1Gi」,是指申請的空間大小是1G。
持久卷申請配置文件:
shell apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pv-claim labels: app: mysql spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi #持久卷的容量是 1 GB
掛載持久卷申請的部署:
下面是掛載了持久卷申請的部署配置文件。它經過把持久卷申請當作持久捲來使用,與Pod進行綁定。請閱讀文件裏有關持久卷的註釋。
apiVersion: apps/v1 kind: Deployment metadata: name: mysql-deployment spec: selector: matchLabels: app: mysql strategy: type: Recreate template: metadata: labels: app: mysql spec: containers: - image: mysql:5.7 name: mysql-con imagePullPolicy: Never env: - name: MYSQL_ROOT_PASSWORD value: root - name: MYSQL_USER value: dbuser - name: MYSQL_PASSWORD value: dbuser args: ["--default-authentication-plugin=mysql_native_password"] ports: - containerPort: 3306 name: mysql volumeMounts: # 掛載Pod上的捲到容器 - name: mysql-persistent-storage # Pod上卷的名字,與「volumes」名字匹配 mountPath: /var/lib/mysql # 掛載的Pod的目錄 volumes: # 掛載持久捲到Pod - name: mysql-persistent-storage # 持久卷名字, 與「volumMounts」名字匹配 persistentVolumeClaim: claimName: mysql-pv-claim # 持久卷申請名字
這裏只指定了Pod的掛載目錄,並無指定虛擬機(宿主機)的目錄,後面會講到如何找到虛擬機的目錄(系統自動分配掛載目錄)。
運行部署:
鍵入「kubectl apply -f mysql-volume.yaml」建立持久卷申請,在建立它的同時,系統自動建立持久卷。
查看持久卷申請
vagrant@ubuntu-xenial:~/dockerimages/kubernetes/mysql$ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE mysql-pv-claim Bound pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa 1Gi RWO standard 10m
查看持久卷申請詳細信息
vagrant@ubuntu-xenial:/mnt$ kubectl describe pvc mysql-pv-claim Name: mysql-pv-claim Namespace: default StorageClass: standard Status: Bound Volume: pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa Labels: app=mysql ...
顯示持久卷:
vagrant@ubuntu-xenial:/mnt$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa 1Gi RWO Delete Bound default/mysql-pv-claim standard 24h
鍵入「kubectl describe pv pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa」, 顯示持久卷詳細信息。從這裏能夠看出,虛擬機上的持久卷在以下位置:「Path: /tmp/hostpath-provisioner/pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa」。
vagrant@ubuntu-xenial:/mnt$ kubectl describe pv pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa Name: pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa Labels: <none> Annotations: hostPathProvisionerIdentity: 19948fdf-e67f-11e9-8fbd-026a5b40726f pv.kubernetes.io/provisioned-by: k8s.io/minikube-hostpath Finalizers: [kubernetes.io/pv-protection] StorageClass: standard Status: Bound Claim: default/mysql-pv-claim Reclaim Policy: Delete Access Modes: RWO VolumeMode: Filesystem Capacity: 1Gi Node Affinity: <none> Message: Source: Type: HostPath (bare host directory volume) Path: /tmp/hostpath-provisioner/pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa HostPathType: Events: <none>
查看MySQL目錄信息:
vagrant@ubuntu-xenial:/tmp/hostpath-provisioner/pvc-ac6c88d5-ef5a-4a5c-b499-59715a2d60fa$ ls -al total 188488 drwxrwxrwx 6 999 docker 4096 Oct 4 13:23 . drwxr-xr-x 3 root root 4096 Oct 4 12:58 .. -rw-r----- 1 999 docker 56 Oct 4 12:58 auto.cnf -rw------- 1 999 docker 1679 Oct 4 12:59 ca-key.pem -rw-r--r-- 1 999 docker 1107 Oct 4 12:59 ca.pem -rw-r--r-- 1 999 docker 1107 Oct 4 12:59 client-cert.pem -rw------- 1 999 docker 1679 Oct 4 12:59 client-key.pem -rw-r----- 1 999 docker 668 Oct 4 13:21 ib_buffer_pool -rw-r----- 1 999 docker 79691776 Oct 4 13:23 ibdata1 -rw-r----- 1 999 docker 50331648 Oct 4 13:23 ib_logfile0 -rw-r----- 1 999 docker 50331648 Oct 4 12:58 ib_logfile1 -rw-r----- 1 999 docker 12582912 Oct 4 13:24 ibtmp1 drwxr-x--- 2 999 docker 4096 Oct 4 12:58 mysql drwxr-x--- 2 999 docker 4096 Oct 4 12:58 performance_schema -rw------- 1 999 docker 1679 Oct 4 12:59 private_key.pem -rw-r--r-- 1 999 docker 451 Oct 4 12:59 public_key.pem -rw-r--r-- 1 999 docker 1107 Oct 4 12:59 server-cert.pem -rw------- 1 999 docker 1675 Oct 4 12:59 server-key.pem drwxr-x--- 2 999 docker 4096 Oct 4 13:18 service_config drwxr-x--- 2 999 docker 12288 Oct 4 12:58 sys
當持久卷和持久卷申請被刪除後,它有三種回收模式。
動態持久卷的一個問題是它的缺省回收模式是「刪除」,這樣當虛機從新啓動後,持久卷會被刪除。當你從新運行部署時,k8s會建立一個新的MySQL,這樣原來MySQL裏的新建信息就會丟失,這是咱們不肯意看到的。雖然你能夠手動修改回收方式爲「保持」,但仍是要手動回收原來持久卷裏的數據。
一個解決辦法是把持久卷建在宿主機上,這樣即便虛機出了問題被從新啓動,MySQL裏的新建信息依然不會丟失。若是是在雲上,就會有專門的的存儲層,若是是本地,大體有三種方式:
我選擇了比較簡單的「Local」方式。在這種方式下,必須單首創建持久卷,不能 只建立持久卷申請而讓系統自動建立持久卷。
下面是使用「Local」方式的配置文件,它把持久卷和持久卷申請寫在了一個文件裏。當用「Local」方式時,須要設置「nodeAffinity」部分,其中「values:- minikube」 的「Minikube」是k8s集羣Node的名字,「Minikube」只支持一個Node,既是「Master Node」,又是「Worker Node」。
持久卷和申請的配置文件:
apiVersion: v1 kind: PersistentVolume metadata: name: mysql-pv spec: capacity: storage: 1Gi volumeMode: Filesystem accessModes: - ReadWriteOnce storageClassName: standard #持久卷存儲類型,它須要與持久卷申請的類型相匹配 local: path: /home/vagrant/database/mysql #宿主機的目錄 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - minikube # Node的名字 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pv-claim labels: app: mysql spec: accessModes: - ReadWriteOnce # storageClassName: # 這裏的存儲類型註釋掉了 resources: requests: storage: 1Gi #1 GB
若是不知道Node名字,可用以下命令查看:
vagrant@ubuntu-xenial:/$ kubectl get node NAME STATUS ROLES AGE VERSION minikube Ready master 6d3h v1.15.2
改用靜態持久卷以後,只有持久卷配置文件發生了變化,部署和服務的配置文件沒有變。從新運行持久卷和部署,成功以後,即便重啓虛擬機,MySQL裏面的新建內容也沒有丟失。
注意這裏storageClassName的用法。k8s規定持久卷和持久卷申請的storageClassName必須匹配,這時纔會把持久卷分配給持久卷申請。咱們這裏的持久卷申請沒有指定storageClassName,這時系統會使用缺省的storageClass。
查看是否安裝了缺省的storageClass
vagrant@ubuntu-xenial:/$ kubectl get sc NAME PROVISIONER AGE standard (default) k8s.io/minikube-hostpath 6d3h vagrant@ubuntu-xenial:/$
查看缺省的storageClass詳細信息
vagrant@ubuntu-xenial:/$ kubectl describe sc Name: standard IsDefaultClass: Yes Annotations: storageclass.kubernetes.io/is-default-class=true Provisioner: k8s.io/minikube-hostpath Parameters: <none> AllowVolumeExpansion: <unset> MountOptions: <none> ReclaimPolicy: Delete VolumeBindingMode: Immediate Events: <none>
從這裏能夠看出,Minikube安裝了缺省的storageClass,它的名字是「standard」。上面的持久卷申請裏沒有指定storageClass,所以系統使用缺省的storageClass與之匹配,而上面的持久卷的storageClassName是「standard」,正好能配上。詳情請見「Dynamic Provisioning and Storage Classes in Kubernetes」
使用Hyper-V仍是VirtualBox
Hyper-V和VirtualBox是不兼容的,只能選一個(固然你能夠在這二者之間切換,但太麻煩了)。我在Windows上裝了VirtualBox,運行正常。進入Vagrant以後,安裝了「ubuntu」版的Linux。這時,當你啓動Minikube時,能夠鍵入「minikube start --vm-driver=virtualbox」,但系統顯示「This computer doesn't have VT-X/AMD-v enabled. Enabling it in the BIOS is mandatory」。我按照網上的建議去修改BIOS的「VT-X/AMD-v」,但個人BIOS就沒有這個選項。其餘的方法也都試過了,沒有一個成功的。但由於已經裝了VirtualBox,就不能用Hyper-V了。就只能用另一個方法,使用命令「minikube start --vm-driver=none」。幸虧這個方法工做得很好。
當用「minikube start --vm-driver=virtualbox」時,你是先建了虛擬機,再在虛擬機上運行Minikube。當用「minikube start --vm-driver=none」時,是直接在宿主機上運行Minikube。但因爲個人Windows版本不能直接支持k8s,我已經在Windows上裝了Linux虛機,並用Vagrant進行管理。若是用「minikube start --vm-driver=virtualbox」,就是在Linux虛機上又裝了一個虛機。如今用「minikube start --vm-driver=none」,表面上看是在宿主機上運行,實際上已經運行在Windows的Linux虛機上了。
登陸k8s集羣
當用「minikube start --vm-driver=none」啓動Minikube時,不能用「minikube ssh」登陸k8s集羣,由於這時已經沒有虛機了,是直接安裝在宿主機上,所以不須要「minikube ssh」。但你能夠登陸到Pod上,可用以下命令:" kubectl exec -ti mysql-deployment-56c9cf5857-fffth -- /bin/bash"。其中「mysql-deployment-56c9cf5857-fffth」是Pod名字。
建立重名PV或PVC
當原來的PV或PVC還在,而你又建立了一個新的PV, 並與原來的重名,則會獲得以下錯誤:The persistentvolumeclaim "mysql-pv-claim" is invalid: spec: forbidden: is immutable after creation except resources.requests for bound claims. 這時,你須要將原來的PV或PVC刪掉,再從新建立新的。
請繼續閱讀下篇「經過搭建MySQL掌握k8s(Kubernetes)重要概念(下):參數配置」
本文由博客一文多發平臺 OpenWrite 發佈!