從0到1使用Kubernetes系列(六):數據持久化實戰

默認情況下Pod掛載在磁盤上的文件生命周期與Pod生命周期是一致的,若Pod出現崩潰的情況,kubelet 將會重啟它,這將會造成Pod中的文件將丟失,因為Pod會以鏡像最初的狀態重新啟動。在實際應用當中,我們有很多時候需要將容器中的數據保留下來,比如在Kubernetes中部署了MySql,不能因為MySql容器掛掉重啟而上面的數據全部丟失;其次,在 Pod 中同時運行多個容器時,這些容器之間可能需要共享文件。也有時我們需要預置配置文件,使其在容器中生效,例如自定義了mysql.cnf文件在MySql啟動時就需要加載此配置文件。這些都將是今天我們將要實戰解決的問題。

今天我們講解下面常用存儲類型:

  • secret
  • configMap
  • emptyDir
  • hostPath
  • nfs
  • persistentVolumeClaim

secret

secret對象允許您存儲和管理敏感信息,例如密碼,OAuth令牌和ssh密鑰。將此類信息放入一個secret中可以更好地控制它的用途,并降低意外暴露的風險。

使用場景

鑒權配置文件掛載

使用示例

在CI中push構建好的鏡像就可以將docker鑒權的config.json文件存入secret對象中,再掛載到CI的Pod中,從而進行權限認證。

  • 首先在創建secret

    $ kubectl create secret docker-registry docker-config  --docker-server=https://hub.docker.com --docker-username=username --docker-password=password
    secret/docker-config created
    
  • 新建docker-pod.yaml文件,粘貼以下信息:

    apiVersion: v1
    kind: Pod
    metadata:
      name: docker
    spec:
      containers:
      - name: docker
        image: docker
        command:
          - sleep
          - "3600"
        volumeMounts:
        - name: config
          mountPath: /root/.docker/
      volumes:
      - name: config
        secret:
          secretName: docker-config
          items:
          - key: .dockerconfigjson
            path: config.json
            mode: 0644
    
  • Docker Pod掛載secret

    $ kubectl apply -f docker-pod.yaml
    pod/docker created
    
  • 查看掛載效果

    $ kubectl exec docker -- cat /root/.docker/config.json
    {"auths":{"https://hub.docker.com":{"username":"username","password":"password","auth":"dXNlcm5hbWU6cGFzc3dvcmQ="}}}
    
  • 清理環境

    $ kubectl delete pod docker
    $ kubectl delete secret docker-config
    

configMap

許多應用程序會從配置文件、命令行參數或環境變量中讀取配置信息。這些配置信息需要與docker image解耦ConfigMap API給我們提供了向容器中注入配置信息的機制,ConfigMap可以被用來保存單個屬性,也可以用來保存整個配置文件。

使用場景

配置信息文件掛載

使用示例

使用ConfigMap中的數據來配置Redis緩存

  • 創建example-redis-config.yaml文件,粘貼以下信息:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: example-redis-config
    data:
      redis-config: |
        maxmemory 2mb
        maxmemory-policy allkeys-lru
    
  • 創建ConfigMap

    $ kubectl apply -f example-redis-config.yaml
    configmap/example-redis-config created
    
  • 創建example-redis.yaml文件,粘貼以下信息:

    apiVersion: v1
    kind: Pod
    metadata:
      name: redis
    spec:
      containers:
      - name: redis
        image: kubernetes/redis:v1
        env:
        - name: MASTER
          value: "true"
        ports:
        - containerPort: 6379
        resources:
          limits:
            cpu: "0.1"
        volumeMounts:
        - mountPath: /redis-master-data
          name: data
        - mountPath: /redis-master
          name: config
      volumes:
        - name: data
          emptyDir: {}
        - name: config
          configMap:
            name: example-redis-config
            items:
            - key: redis-config
              path: redis.conf
    
  • Redis Pod掛載ConfigMap測試

    $ kubectl apply -f example-redis.yaml
    pod/redis created
    
  • 查看掛載效果

    $ kubectl exec -it redis redis-cli
    $ 127.0.0.1:6379> CONFIG GET maxmemory
    1) "maxmemory"
    2) "2097152"
    $ 127.0.0.1:6379> CONFIG GET maxmemory-policy
    1) "maxmemory-policy"
    2) "allkeys-lru"
    
  • 清理環境

    $ kubectl delete pod redis
    $ kubectl delete configmap example-redis-config
    

emptyDir

當使用emptyDir卷的Pod在節點創建時,會在該節點創建一個新的空目錄,只要該Pod運行在該節點,該目錄會一直存在,Pod內的所有容器可以將改目錄掛載到不同的掛載點,但都可以讀寫emptyDir內的文件。當Pod不論什么原因被刪除,emptyDir的數據都會永遠被刪除(一個Container Crash 并不會在該節點刪除Pod,因此在Container crash時,數據不會丟失)。默認情況下,emptyDir支持任何類型的后端存儲:disk、ssd、網絡存儲。也可以通過設置 emptyDir.medium 為Memory,kubernetes會默認mount一個tmpfs(RAM-backed filesystem),因為是RAM Backed,因此 tmpfs 通常很快。但是會在容器重啟或者crash時,數據丟失。

使用場景

同一Pod內各容器共享存儲

使用示例

在容器a中生成hello文件,通過容器b輸出文件內容

  • 創建test-emptydir.yaml文件,粘貼以下信息:

    apiVersion: v1
    kind: Pod
    metadata:
      name: test-emptydir
    spec:
      containers:
      - image: alpine
        name: container-a
        command:
          - /bin/sh
        args:
          - -c
          - echo 'I am container-a' >> /cache-a/hello && sleep 3600
        volumeMounts:
        - mountPath: /cache-a
          name: cache-volume
      - image: alpine
        name: container-b
        command:
          - sleep
          - "3600"
        volumeMounts:
        - mountPath: /cache-b
          name: cache-volume
      volumes:
      - name: cache-volume
        emptyDir: {}
    
  • 創建Pod

    kubectl apply -f test-emptydir.yaml
    pod/test-emptydir created
    
  • 測試

    $ kubectl exec test-emptydir -c container-b -- cat /cache-b/hello
    I am container-a
    
  • 清理環境

    $ kubectl delete pod test-emptydir
    

hostPath

將宿主機對應目錄直接掛載到運行在該節點的容器中。使用該類型的卷,需要注意以下幾個方面:

  1. 使用同一個模板創建的Pod,由于不同的節點有不同的目錄信息,可能會導致不同的結果
  2. 如果kubernetes增加了已知資源的調度,該調度不會考慮hostPath使用的資源
  3. 如果宿主機目錄上已經存在的目錄,只可以被root可以寫,所以容器需要root權限訪問該目錄,或者修改目錄權限

使用場景

運行的容器需要訪問宿主機的信息,比如Docker內部信息/var/lib/docker目錄,容器內運行cadvisor,需要訪問/dev/cgroups

使用示例

使用Docker socket binding模式在列出宿主機鏡像列表。

  • 創建test-hostpath.yaml文件,粘貼以下信息:

    apiVersion: v1
    kind: Pod
    metadata:
      name: test-hostpath
    spec:
      containers:
      - image: docker
        name: test-hostpath
        command:
          - sleep
          - "3600"
        volumeMounts:
        - mountPath: /var/run/docker.sock
          name: docker-sock
      volumes:
      - name: docker-sock
        hostPath:
          path: /var/run/docker.sock
          type: Socket
    
  • 創建test-hostpath Pod

    $ kubectl apply -f test-hostpath.yaml
    pod/test-hostpath created
    
  • 測試是否成功

    $ kubectl exec test-hostpath docker images
    REPOSITORY      IMAGE ID        CREATED         SIZE
    docker          639de9917ae1    13 days ago     171MB
    ...
    

NFS存儲卷

nfs 卷允許將現有的 NFS(網絡文件系統)共享掛載到您的容器中。不像 emptyDir,當刪除 Pod 時,nfs 卷的內容被保留,卷僅僅是被卸載。這意味著 nfs 卷可以預填充數據,并且可以在 pod 之間共享數據。 NFS 可以被多個寫入者同時掛載。

重要提示: 您必須先擁有自己的 NFS 服務器然后才能使用它。

使用場景

不同節點Pod使用統一nfs共享目錄

使用示例

  • 創建test-nfs.yaml文件,粘貼以下信息:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: test-nfs
    spec:
      selector:
        matchLabels:
          app: store
      replicas: 2
      template:
        metadata:
          labels:
            app: store
        spec:
          volumes:
          - name: data
            nfs:
              server: nfs.server.com
              path: /
          affinity:
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                  matchExpressions:
                  - key: app
                    operator: In
                    values:
                    - store
                topologyKey: "kubernetes.io/hostname"
          containers:
          - name: alpine
            image: alpine
            command:
              - sleep
              - "3600"
            volumeMounts:
            - mountPath: /data
              name: data
    
  • 創建測試deployment

    $ kubectl apply -f test-nfs.yaml
    deployment/test-nfs created
    
  • 查看pod運行情況

    $ kubectl get po -o wide
    NAME                        READY     STATUS    RESTARTS   AGE       IP              NODE      NOMINATED NODE
    test-nfs-859ccfdf55-kkgxj   1/1       Running   0          1m        10.233.68.245   uat05     <none>
    test-nfs-859ccfdf55-aewf8   1/1       Running   0          1m        10.233.67.209   uat06     <none>
    
  • 進入Pod中進行測試

    # 進入uat05節點的pod中
    $ kubectl exec -it test-nfs-859ccfdf55-kkgxj sh
    # 創建文件
    $ echo "uat05" > /data/uat05
    # 退出uat05節點的pod
    $ edit
    # 進入uat06節點的pod中
    $ kubectl exec -it test-nfs-859ccfdf55-aewf8 sh
    # 查看文件內容
    $ cat /data/uat05
    uat05
    
  • 清理環境

    $ kubectl delete deployment test-nfs
    

persistentVolumeClaim

上面所有例子中我們都是直接將存儲掛載到的pod中,那么在kubernetes中如何管理這些存儲資源呢?這就是PersistentVolume和PersistentVolumeClaims所提供的功能。

  • PersistentVolume 子系統為用戶和管理員提供了一個 API,該 API 將如何提供存儲的細節抽象了出來。為此,我們引入兩個新的 API 資源:PersistentVolume 和 PersistentVolumeClaim。
    • PersistentVolume(PV)是由管理員設置的存儲,它是群集的一部分。就像節點是集群中的資源一樣,PV 也是集群中的資源。 PV 是 Volume 之類的卷插件,但具有獨立于使用 PV 的 Pod 的生命周期。此 API 對象包含 Volume 的實現,即 NFS、iSCSI 或特定于云供應商的存儲系統。
    • PersistentVolumeClaim(PVC)是用戶存儲的請求。它與 Pod 相似。Pod 消耗節點資源,PVC 消耗 PV 資源。Pod 可以請求特定級別的資源(CPU 和內存)。聲明可以請求特定的大小和訪問模式(例如,可以以讀/寫一次或 只讀多次模式掛載)。雖然 PersistentVolumeClaims 允許用戶使用抽象存儲資源,但用戶需要具有不同性質(例如性能)的 PersistentVolume 來解決不同的問題。集群管理員需要能夠提供各種各樣的 PersistentVolume,這些PersistentVolume 的大小和訪問模式可以各有不同,但不需要向用戶公開實現這些卷的細節。對于這些需求,StorageClass 資源可以實現。
  • 在實際使用場景里,PV 的創建和使用通常不是同一個人。這里有一個典型的應用場景:管理員創建一個 PV 池,開發人員創建 Pod 和 PVC,PVC 里定義了Pod所需存儲的大小和訪問模式,然后 PVC 會到 PV 池里自動匹配最合適的 PV 給 Pod 使用。

使用示例

  • 創建PersistentVolume

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: mypv
    spec:
      capacity:
        storage: 5Gi
      volumeMode: Filesystem
      accessModes:
        - ReadWriteOnce
      persistentVolumeReclaimPolicy: Recycle
      storageClassName: slow
      mountOptions:
        - hard
        - nfsvers=4.0
      nfs:
        path: /tmp
        server: 172.17.0.2
    
  • 創建PersistentVolumeClaim

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: myclaim
    spec:
      accessModes:
        - ReadWriteOnce
      volumeMode: Filesystem
      resources:
        requests:
          storage: 5Gi
      volumeName: mypv
    
  • 創建Pod綁定PVC

    kind: Pod
    apiVersion: v1
    metadata:
      name: mypod
    spec:
      containers:
        - name: myfrontend
          image: nginx
          volumeMounts:
          - mountPath: "/var/www/html"
            name: mypd
      volumes:
        - name: mypd
          persistentVolumeClaim:
            claimName: myclaim
    
  • 查看pod運行情況驗證綁定結果

    $ kubectl get po -o wide
    NAME    READY     STATUS    RESTARTS   AGE       IP              NODE      NOMINATED NODE
    mypod   1/1       Running   0          1m        10.233.68.249   uat05     <none>
    $ kubectl exec -it mypod sh
    $ ls /var/www/html
    
  • 清理環境

    $ kubectl delete pv mypv
    $ kubectl delete pvc myclaim
    $ kubectl delete po mypod
    

總結

本次實戰中我們使用了secret存儲docker認證憑據,更好地控制它的用途,并降低意外暴露的風險。使用configMap對redis進行緩存配置,這樣即使redis容器掛掉重啟configMap中的配置依然會生效。接著我們使用emptyDir來使得同一Pod中多個容器的目錄共享,在實際應用中我們通常使用initContainers來進行預處理文件,然后通過emptyDir傳遞給Containers。然后我們使用hostPath來訪問宿主機的資源,當網路io達不到文件讀寫要求時,可考慮固定應用只運行在一個節點上然后使用hostPath來解決文件讀寫速度的要求。NFS和PersistentVolumeClaim的例子實質上都是試容器掛載的nfs服務器共享目錄,但這些資源一般都只掌握在了管理員手中,開發人員想要獲取這部分資源那么就不是這么友好了,動態存儲類(StorageClass)就能很好的解決此類問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容