k8s數據庫服務實踐

最近在學習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創建步驟:

  1. 創建NFS存儲
  2. 創建PV
  3. 創建PVC
  4. 創建Configmap
  5. 創建headless服務
  6. 創建Redis StatefulSet
  7. 初始化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-IPNone,表示這是一個“無頭”服務。

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

 

 

 

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