最近在學習k8s,在網上找了數據庫的項目作爲練習,也算是對最近工作的一個總結。一個是簡單的mysql服務,另外一個是分佈式的redis服務。
mysql服務
1、創建一個新的namespace
2、在該namespace下創建一個deployment
3、deployment自動部署好replicaSet和pod
4、創建對應服務
5、驗證是否成功
下面是具體的操作說明
1、創建一個新的namespace
#創建namespace ,命令行直接創建
$ kubectl create namespace test
2、在該namespace下創建一個deployment(env中設置了mysql的root用戶的密碼爲mysql)
(1)編寫deployment的對應yaml文件: mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.6
imagePullPolicy: IfNotPresent
args:
- "--ignore-db-dir=lost+found"
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "1234"
注意:密碼的地方一定要加引號。
2)創建deployment
kubectl create -f mysql-deployment.yaml --record
3、deployment自動部署好replicaSet和pod
執行一下命令可以查看test namespace 下自動部署好replicaSet和pod
kubectl get rs -n test
kubectl get pod -n test
4、創建對應服務(注意定義type=NodePort,並對應的nodeport端口號,以便集羣外訪問該服務)
(1)創建對應的service的yaml文件:mysql-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-svc
namespace: test
labels:
name: mysql-svc
spec:
type: NodePort
ports:
- port: 3306
protocol: TCP
targetPort: 3306
name: http
nodePort: 30066
selector:
app: mysql
(2)創建對應的service
kubectl create -f mysql-svc.yaml --record
5、驗證是否成功
在遠程客戶端上下載mysql客戶端Navicat,進行驗證
其中
主機:service對應的pod所在的node的ip
端口:上面service中的nodeport端口號
密碼:deployment文件env中設置的root用戶的密碼)
如上圖在我的windows下創建了一個test數據庫。
Redis集羣的搭建
下圖爲Redis集羣的架構圖,每個Master都可以擁有多個Slave。當Master下線後,Redis集羣會從多個Slave中選舉出一個新的Master作爲替代,而舊Master重新上線後變成新Master的Slave。
二、準備操作
本次部署主要基於該項目:
https://github.com/zuxqoj/kubernetes-redis-cluster
其包含了兩種部署Redis集羣的方式:
- StatefulSet
- Service&Deployment
兩種方式各有優劣,對於像Redis、Mongodb、Zookeeper等有狀態的服務,使用StatefulSet是首選方式。本文將主要介紹如何使用StatefulSet進行Redis集羣的部署。
三、StatefulSet簡介
StatefulSet的概念非常重要,簡單來說,其就是爲了解決Pod重啓、遷移後,Pod的IP、主機名等網絡標識會改變而帶來的問題。IP變化對於有狀態的服務是難以接受的,如在Zookeeper集羣的配置文件中,每個ZK節點都會記錄其他節點的地址信息:
但若某個ZK節點的Pod重啓後改變了IP,那麼就會導致該節點脫離集羣,而如果該配置文件中不使用IP而使用IP對應的域名,則可避免該問題:
也即是說,對於有狀態服務,我們最好使用固定的網絡標識(如域名信息)來標記節點,當然這也需要應用程序的支持(如Zookeeper就支持在配置文件中寫入主機域名)。
StatefulSet基於Headless Service(即沒有Cluster IP的Service)爲Pod實現了穩定的網絡標誌(包括Pod的hostname和DNS Records),在Pod重新調度後也保持不變。同時,結合PV/PVC,StatefulSet可以實現穩定的持久化存儲,就算Pod重新調度後,還是能訪問到原先的持久化數據。
下圖爲使用StatefulSet部署Redis的架構,無論是Master還是Slave,都作爲StatefulSet的一個副本,並且數據通過PV進行持久化,對外暴露爲一個Service,接受客戶端請求。
四、部署過程
本文參考項目的README中,簡要介紹了基於StatefulSet的Redis創建步驟:
- 創建NFS存儲
- 創建PV
- 創建PVC
- 創建Configmap
- 創建headless服務
- 創建Redis StatefulSet
- 初始化Redis集羣
這裏,我們將參考如上步驟,實踐操作並詳細介紹Redis集羣的部署過程。文中會涉及到很多K8S的概念,希望大家能提前瞭解學習。
1.創建NFS存儲
創建NFS存儲主要是爲了給Redis提供穩定的後端存儲,當Redis的Pod重啓或遷移後,依然能獲得原先的數據。這裏,我們先要創建NFS,然後通過使用PV爲Redis掛載一個遠程的NFS路徑。
安裝NFS
由於硬件資源有限,我們可以在k8s-node2上搭建。執行如下命令安裝NFS和rpcbind:
yum -y install nfs-utils rpcbind
其中,NFS依靠遠程過程調用(RPC)在客戶端和服務器端路由請求,因此需要安裝rpcbind服務。
在各個節點上執行 yum -y install nfs-utils安裝客戶端!
然後,新增/etc/exports
文件,用於設置需要共享的路徑:
這裏需要注意redis至少需要六個節點才能保證高可用!
如上,rw表示讀寫權限;all_squash 表示客戶機上的任何用戶訪問該共享目錄時都映射成服務器上的匿名用戶(默認爲nfsnobody);*
表示任意主機都可以訪問該共享目錄,也可以填寫指定主機地址,同時支持正則,如:
/root/share/ 192.168.1.20 (rw,all_squash)
/home/ljm/ *.gdfs.edu.cn (rw,all_squash)
由於我們打算創建一個6節點的Redis集羣,所以共享了6個目錄。當然,我們需要在k8s-node2上創建這些路徑,並且爲每個路徑修改權限:
chmod 777 /usr/local/k8s/redis/pv*
這一步必不可少,否則掛載時會出現mount.nfs: access denied by server while mounting
的權限錯誤。
接着,啓動NFS和rpcbind服務:
systemctl start rpcbind
systemctl start nfs
我們在k8s-node1上測試一下,執行:
mount -t nfs 10.143.252.98:/usr/local/k8s/redis/pv2 /mnt
表示將k8s-node2上的共享目錄/usr/local/k8s/redis/pv2
映射爲k8s-node1的/mnt
目錄,我們在/mnt中創建文件:
touch ming
在node2
上可以看到該文件
[root@k8s-node-2 pv1]# cd ../pv2
[root@k8s-node-2 pv2]# ls
appendonly.aof ming nodes.conf
[root@k8s-node-2 pv2]# ll
total 4
-rw-r--r-- 1 nfsnobody nfsnobody 0 Aug 27 17:15 appendonly.aof
-rw-r--r-- 1 nfsnobody nfsnobody 0 Aug 27 10:57 ming
-rw-r--r-- 1 nfsnobody nfsnobody 132 Aug 28 10:04 nodes.conf
可以看到用戶和組爲nfsnobody
。
創建PV
每一個Redis Pod都需要一個獨立的PV來存儲自己的數據,因此可以創建一個pv.yaml
文件,包含3個PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 10.143.252.98
path: "/usr/local/k8s/redis/pv1"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv2
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 10.143.252.98
path: "/usr/local/k8s/redis/pv2"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 10.143.252.98
path: "/usr/local/k8s/redis/pv3"
如上,可以看到所有PV除了名稱和掛載的路徑外都基本一致。執行創建即可:
kubectl create -f pv.yaml
2.創建Configmap
這裏,我們可以直接將Redis的配置文件轉化爲Configmap,這是一種更方便的配置讀取方式。配置文件redis.conf
如下:
appendonly yes
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379
創建名爲redis-conf
的Configmap:
kubectl create configmap redis-conf --from-file=redis.conf
查看:
kubectl describe cm redis-conf
3.創建Headless service
apiVersion: v1
kind: Service
metadata:
name: redis-service
labels:
app: redis
spec:
ports:
- name: redis-port
port: 6379
clusterIP: None
selector:
app: redis
appCluster: redis-cluster
創建:
kubectl create -f headless-service.yml
可以看到,服務名稱爲redis-service
,其CLUSTER-IP
爲None
,表示這是一個“無頭”服務。
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: redis-app
spec:
serviceName: "redis-service"
replicas: 2
template:
metadata:
labels:
app: redis
appCluster: redis-cluster
spec:
terminationGracePeriodSeconds: 20
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
containers:
- name: redis
image: hub.c.163.com/library/redis
command:
- "redis-server"
args:
- "/etc/redis/redis.conf"
- "--protected-mode"
- "no"
resources:
requests:
cpu: "100m"
memory: "100Mi"
ports:
- name: redis
containerPort: 6379
protocol: "TCP"
- name: cluster
containerPort: 16379
protocol: "TCP"
volumeMounts:
- name: "redis-conf"
mountPath: "/etc/redis"
- name: "redis-data"
mountPath: "/var/lib/redis"
volumes:
- name: "redis-conf"
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteMany" ]
resources:
requests:
storage: 200M
上述我只開啓了兩個redis,目的想簡單演示一主一從。
如上,總共創建了6個Redis節點(Pod),其中3個將用於master,另外3個分別作爲master的slave;Redis的配置通過volume將之前生成的redis-conf
這個Configmap,掛載到了容器的/etc/redis/redis.conf
;Redis的數據存儲路徑使用volumeClaimTemplates聲明(也就是PVC),其會綁定到我們先前創建的PV上。
這裏有一個關鍵概念——Affinity,請參考官方文檔詳細瞭解。其中,podAntiAffinity表示反親和性,其決定了某個pod不可以和哪些Pod部署在同一拓撲域,可以用於將一個服務的POD分散在不同的主機或者拓撲域中,提高服務本身的穩定性。
而PreferredDuringSchedulingIgnoredDuringExecution 則表示,在調度期間儘量滿足親和性或者反親和性規則,如果不能滿足規則,POD也有可能被調度到對應的主機上。在之後的運行過程中,系統不會再檢查這些規則是否滿足。
在這裏,matchExpressions規定了Redis Pod要儘量不要調度到包含app爲redis的Node上,也即是說已經存在Redis的Node上儘量不要再分配Redis Pod了。但是,由於我們只有三個Node,而副本有6個,因此根據PreferredDuringSchedulingIgnoredDuringExecution,這些豌豆不得不得擠一擠,擠擠更健康~
另外,根據StatefulSet的規則,我們生成的Redis的6個Pod的hostname會被依次命名爲$(statefulset名稱)-$(序號),如下圖所示:
#kubectl get pod -o wide
redis-app-0 1/1 Running 0 17h 172.17.0.20 k8s-node-1
redis-app-1 1/1 Running 0 17h 172.17.0.21 k8s-node-1
可以看到兩臺機器都部署在了node1節點上。
在K8S集羣內部,這些Pod就可以利用該域名互相通信。我們可以使用busybox鏡像的nslookup檢驗這些域名:
但是我yeu到了無法解析的情況!
原則上應該是這樣的!
可以看到, redis-app-0的IP爲192.168.169.207。當然,若Redis Pod遷移或是重啓(我們可以手動刪除掉一個Redis Pod來測試),則IP是會改變的,但Pod的域名、SRV records、A record都不會改!
另外可以發現,我們之前創建的pv都被成功綁定了:
5.初始化Redis集羣
創建好6個Redis Pod後,我們還需要利用常用的Redis-tribe工具進行集羣的初始化。
創建centos容器
由於Redis集羣必須在所有節點啓動後才能進行初始化,而如果將初始化邏輯寫入Statefulset中,則是一件非常複雜而且低效的行爲。這裏,本人不得不稱讚一下原項目作者的思路,值得學習。也就是說,我們可以在K8S上創建一個額外的容器,專門用於進行K8S集羣內部某些服務的管理控制。
這裏,我們專門啓動一個Ubuntu的容器,可以在該容器中安裝Redis-tribe,進而初始化Redis集羣,執行:
kubectl run -i --tty ubuntu --image=centos --restart=Never /bin/bash
yum update
yum install -y vim wget python2.7 python-pip redis-tools dnsutils
初始化集羣
pip install redis-trib
然後,創建只有Master節點的集羣:
但是我的電腦解析不了域名,這種創建方式不可以。於是在容器中安裝客戶端的方式!
下載安裝redis
wget http://download.redis.io/releases/redis-5.0.3.tar.gz
tar -xvzf redis-5.0.3.tar.gz
cd redis-5.0.3.tar.gz && make
編譯完畢後redis-cli
會被放置在src
目錄下,把它放進/usr/local/bin
中方便後續操作
接下來要獲取已經創建好的6個節點的host ip,可以通過nslookup(需要安裝)
結合StatefulSet的域名規則來查找,舉個例子,要查找redis-app-0
這個pod的ip,運行如下命令:
碰到了一個自己埋的坑,這裏刪除重建,把節點的個數設置爲6
重新創建,發現第四個一直處於pengding狀態。
redis-cli --cluster create 172.17.0.20:6379 172.17.0.21:6379 172.17.0.23:6379
這裏不再創建從節點!
至此,集羣初始化完畢,我們進入一個節點來試試,注意在集羣模式下redis-cli
必須加上-c
參數才能夠訪問其他節點上的數據:
創建Service
現在進入redis集羣中的任意一個節點都可以直接進行操作了,但是爲了能夠對集羣其他的服務提供訪問,還需要建立一個service來實現服務發現和負載均衡(注意這裏的service和我們之前創建的headless service不是一個東西)
yaml文件如下:
apiVersion: v1
kind: Service
metadata:
name: gold-redis
labels:
app: redis
spec:
ports:
- name: redis-port
protocol: "TCP"
port: 6379
targetPort: 6379
selector:
app: redis
appCluster: redis-cluster
向集羣寫數據
原因是啓動的時候沒有加-c參數!
在集羣內的centos容器中通過服務的ip和端口進行訪問。
推薦一篇redis集羣搭建的文章:https://blog.51cto.com/coveringindex/2151249