<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>storage | 伪架构师</title>
    <link>/tags/storage/</link>
      <atom:link href="/tags/storage/index.xml" rel="self" type="application/rss+xml" />
    <description>storage</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Fri, 17 May 2019 09:38:33 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>storage</title>
      <link>/tags/storage/</link>
    </image>
    
    <item>
      <title>Rook &amp; Ceph 简介</title>
      <link>/post/the-ultimate-rook-and-ceph-survival-guide/</link>
      <pubDate>Fri, 17 May 2019 09:38:33 +0800</pubDate>
      <guid>/post/the-ultimate-rook-and-ceph-survival-guide/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://www.cloudops.com/2019/05/the-ultimate-rook-and-ceph-survival-guide/&#34; target=&#34;_blank&#34;&gt;The Ultimate Rook and Ceph Survival Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在容器世界中，无状态是一个核心原则，然而我们始终需要保存数据，并提供给他人进行访问。所以就需要一个方案用于保持数据，以备重启之需。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 中，PVC 是管理有状态应用的一个推荐方案。有了 PVC 的帮助，Pod 可以申请并连接到存储卷，这些存储卷在 Pod 生命周期结束之后，还能独立存在。&lt;/p&gt;

&lt;p&gt;PVC 在存储方面让开发和运维的职责得以分离。运维人员负责供应存储，而开发人员则可以在不知后端细节的情况下，申请使用这些存储卷。&lt;/p&gt;

&lt;p&gt;PVC 由一系列组件构成：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/volumes/#persistentvolumeclaim&#34; target=&#34;_blank&#34;&gt;PVC&lt;/a&gt;：是 Pod 对存储的请求。PVC 会被 Pod 动态加载成为一个存储卷。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/&#34; target=&#34;_blank&#34;&gt;PV&lt;/a&gt;，可以由运维手工分配，也可以使用 &lt;code&gt;StorageClass&lt;/code&gt; 动态分配。PV 受 Kubernetes 管理，但并不与特定的 Pod 直接绑定。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/storage-classes/&#34; target=&#34;_blank&#34;&gt;StorageClass&lt;/a&gt;：由管理员创建，可以用来动态的创建存储卷和 PV。&lt;/p&gt;

&lt;p&gt;物理存储：实际连接和加载的存储卷。&lt;/p&gt;

&lt;p&gt;分布式存储系统是一个有效的解决有状态工作负载高可用问题的方案。Ceph 就是一个分布式存储系统，近年来其影响主键扩大。Rook 是一个编排器，能够支持包括 Ceph 在内的多种存储方案。Rook 简化了 Ceph 在 Kubernetes 集群中的部署过程。&lt;/p&gt;

&lt;p&gt;在生产环境中使用 Rook + Ceph 组合的用户正在日益增加，尤其是自建数据中心的用户，&lt;a href=&#34;https://www.cengn.ca/&#34; target=&#34;_blank&#34;&gt;CENGN&lt;/a&gt;、Gini、GPR 等很多组织都在进行评估。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/rookceph1.png&#34; alt=&#34;images&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;ceph-是什么&#34;&gt;Ceph 是什么&lt;/h2&gt;

&lt;p&gt;Ceph 是一个分布式存储系统，具备大规模、高性能、无单点失败的特点。Ceph 是一个软件定义的系统，也就是说他可以运行在任何符合其要求的硬件之上。&lt;/p&gt;

&lt;p&gt;Ceph 包括多个组件：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ceph Monitors(MON)&lt;/strong&gt;：负责生成集群票选机制。所有的集群节点都会向 Mon 进行汇报，并在每次状态变更时进行共享信息。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ceph Object Store Devices(OSD)&lt;/strong&gt;：负责在本地文件系统保存对象，并通过网络提供访问。通常 OSD 守护进程会绑定在集群的一个物理盘上，Ceph 客户端直接和 OSD 打交道。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ceph Manager(MGR)&lt;/strong&gt;：提供额外的监控和界面给外部的监管系统使用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliable Autonomic Distributed Object Stores&lt;/strong&gt;：Ceph 存储集群的核心。这一层用于为存储数据提供一致性保障，执行数据复制、故障检测以及恢复等任务。&lt;/p&gt;

&lt;p&gt;为了在 Ceph 上进行读写，客户端首先要联系 MON，获取最新的集群地图，其中包含了集群拓扑以及数据存储位置的信息。Ceph 客户端使用集群地图来获知需要交互的 OSD，从而和特定 OSD 建立联系。&lt;/p&gt;

&lt;h2 id=&#34;rook-是什么&#34;&gt;Rook 是什么&lt;/h2&gt;

&lt;p&gt;Rook 是一个可以提供 Ceph 集群管理能力的 &lt;a href=&#34;https://coreos.com/blog/introducing-operators.html&#34; target=&#34;_blank&#34;&gt;Operator&lt;/a&gt;。Rook 使用 CRD 一个控制器来对 Ceph 之类的资源进行部署和管理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/rook-arch.png&#34; alt=&#34;rook arch&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Rook 包含多个组件：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rook Operator&lt;/strong&gt;：Rook 的核心组件，Rook Operator 是一个简单的容器，自动启动存储集群，并监控存储守护进程，来确保存储集群的健康。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rook Agent&lt;/strong&gt;：在每个存储节点上运行，并配置一个 FlexVolume 插件，和 Kubernetes 的存储卷控制框架进行集成。Agent 处理所有的存储操作，例如挂接网络存储设备、在主机上加载存储卷以及格式化文件系统等。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rook Discovers&lt;/strong&gt;：检测挂接到存储节点上的存储设备。&lt;/p&gt;

&lt;p&gt;Rook 还会用 Kubernetes Pod 的形式，部署 Ceph 的 MON、OSD 以及 MGR 守护进程。&lt;/p&gt;

&lt;p&gt;Rook Operator 让用户可以通过 CRD 的是用来创建和管理存储集群。每种资源都定义了自己的 CRD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rook &lt;a href=&#34;https://rook.github.io/docs/rook/master/cluster-crd.html&#34; target=&#34;_blank&#34;&gt;Cluster&lt;/a&gt;&lt;/strong&gt;：提供了对存储机群的配置能力，用来提供块存储、对象存储以及共享文件系统。每个集群都有多个 Pool。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://rook.github.io/docs/rook/master/pool-crd.html&#34; target=&#34;_blank&#34;&gt;Pool&lt;/a&gt;&lt;/strong&gt;：为块存储提供支持。Pool 也是给文件和对象存储提供内部支持。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://rook.github.io/docs/rook/master/object-store-crd.html&#34; target=&#34;_blank&#34;&gt;Object Store&lt;/a&gt;&lt;/strong&gt;：用 S3 兼容接口开放存储服务。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://rook.github.io/docs/rook/master/filesystem-crd.html&#34; target=&#34;_blank&#34;&gt;File System&lt;/a&gt;&lt;/strong&gt;：为多个 Kubernetes Pod 提供共享存储。&lt;/p&gt;

&lt;h2 id=&#34;在-kubernetes-上部署-rook&#34;&gt;在 Kubernetes 上部署 Rook&lt;/h2&gt;

&lt;p&gt;下面我们会在 Kubernetes 上分步骤部署 Rook，并在同一集群中作为客户端来使用其存储服务。Ceph 需要额外的驱动来保存数据，因此建议提供一组独立的存储节点。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/rook-k8s.png&#34; alt=&#34;k8s and rook&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;准备工作&#34;&gt;准备工作&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Helm&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Kubernetes（启用 RBAC）&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;磁盘配置&#34;&gt;磁盘配置&lt;/h3&gt;

&lt;p&gt;这里假设在存储节点上配有未格式化的磁盘。为了提供最佳性能，你可能需要在单独的设备上启用 WAL（本文就不深入讨论了）。&lt;/p&gt;

&lt;h3 id=&#34;配置-flexvolume-如果需要&#34;&gt;配置 FlexVolume（如果需要）&lt;/h3&gt;

&lt;p&gt;Rook agent 使用 &lt;a href=&#34;https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md&#34; target=&#34;_blank&#34;&gt;FlexVolume&lt;/a&gt; 来和 Kubernetes 进行集成，从而进行存储操作。为了达成这一目标，Rook agent 要在每个节点部署 Rook FlexVolume。&lt;/p&gt;

&lt;p&gt;在一些情况下，FlexVolume 的缺省目录是只读的，例如 Rancher 和 CoreOS。在这种情况下就需要配置 Kubelet 使用不同的可写入的目录了。&lt;/p&gt;

&lt;p&gt;如果使用的是 Rancher Kubernetes Engine（RKE），可以用下面的方式配置 kubelet，然后使用 &lt;code&gt;rke up&lt;/code&gt; 应用配置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kubelet:
  image: &amp;quot;&amp;quot;
  extra_args:
    volume-plugin-dir: /usr/libexec/kubernetes/kubelet-plugins/volume/exec
  extra_binds:
    - /usr/libexec/kubernetes/kubelet-plugins/volume/exec:/usr/libexec/kubernetes/kubelet-plugins/volume/exec
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;安装-rook-operator&#34;&gt;安装 Rook Operator&lt;/h3&gt;

&lt;p&gt;在 Helm 中加入 &lt;code&gt;Rook Charts&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;helm repo add rook-stable https://charts.rook.io/stable
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;安装 Rook Operator（当前版本为 v0.9.3）&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;helm install --name rook --namespace rook-ceph-system rook-stable/rook-ceph

$ kubectl get po -n rook-ceph-system -o wide
NAME                                  READY   STATUS    RESTARTS   AGE   IP              NODE                      NOMINATED NODE
rook-ceph-agent-gwl8s                 1/1     Running   0          35d   10.212.144.51   coo-r1-k8s-worker-st-01
rook-ceph-agent-lqkjl                 1/1     Running   0          35d   10.212.144.52   coo-r1-k8s-worker-st-02
rook-ceph-agent-x66sw                 1/1     Running   0          35d   10.212.144.53   coo-r1-k8s-worker-st-03
rook-ceph-operator-7d44ddfdcb-q5chh   1/1     Running   0          35d   10.244.8.3      coo-r1-k8s-worker-st-03
rook-discover-fmqrd                   1/1     Running   0          35d   10.244.7.5      coo-r1-k8s-worker-st-01
rook-discover-jlsv9                   1/1     Running   0          35d   10.244.6.3      coo-r1-k8s-worker-st-02
rook-discover-vt7mk                   1/1     Running   0          35d   10.244.8.4      coo-r1-k8s-worker-st-03
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Helm 会部署 Rook 的相关 Pod（rook-operator、rook-discover 以及 rook-agent），以及相关的 CRD。Discover Pod 会运行一个发现脚本，来查找挂接到 Kubernetes 存储节点上的本地存储设备。&lt;/p&gt;

&lt;p&gt;请注意，&lt;code&gt;rook-ceph-system&lt;/code&gt; 中的所有 Pod 都应该是 &lt;code&gt;Running&lt;/code&gt; 或者 &lt;code&gt;Completed&lt;/code&gt; 状态，不应存在 &lt;code&gt;restarts&lt;/code&gt; 或 &lt;code&gt;error&lt;/code&gt; 的情况。&lt;/p&gt;

&lt;h3 id=&#34;创建受-root-管理的-ceph-集群&#34;&gt;创建受 Root 管理的 Ceph 集群&lt;/h3&gt;

&lt;p&gt;下一步就是创建 Ceph 集群。在 &lt;a href=&#34;https://github.com/rook/rook&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;rook&lt;/code&gt; 源码&lt;/a&gt;中找到 &lt;code&gt;cluster/examples/kubernetes/ceph/cluster.yaml&lt;/code&gt;，进行查看和修改。集群 CRD 中定义了存储集群的内容。下面的命令就能够启动一个 Rook 集群：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create -f cluster.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;要确认我们的 Rook 集群的工作状况，可以检查一下 &lt;code&gt;rook-ceph&lt;/code&gt; 命名空间中的 Pod 运行情况：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get po -n rook-ceph -o wide
NAME                                                  READY   STATUS      RESTARTS   AGE   IP              NODE                      NOMINATED NODE
rook-ceph-mgr-a-bf78cdcb8-q4gpz                       1/1     Running     0          35d   10.244.7.7      coo-r1-k8s-worker-st-01
rook-ceph-mon-a-755d985488-72kzh                      1/1     Running     0          35d   10.244.7.6      coo-r1-k8s-worker-st-01
rook-ceph-mon-b-845c97f94b-h5jlp                      1/1     Running     0          35d   10.244.6.4      coo-r1-k8s-worker-st-02
rook-ceph-mon-c-68b495d97d-m524q                      1/1     Running     0          35d   10.244.8.7      coo-r1-k8s-worker-st-03
rook-ceph-osd-0-56b7b86b5b-kz882                      1/1     Running     0          35d   10.244.7.9      coo-r1-k8s-worker-st-01
rook-ceph-osd-1-6d9558b6bd-xlkzf                      1/1     Running     0          35d   10.244.6.6      coo-r1-k8s-worker-st-02
rook-ceph-osd-2-56bf4b6c64-2p9rp                      1/1     Running     0          35d   10.244.8.9      coo-r1-k8s-worker-st-03
rook-ceph-osd-3-86ccf5d69f-xzjmz                      1/1     Running     0          35d   10.244.7.10     coo-r1-k8s-worker-st-01
rook-ceph-osd-4-6f469fc877-bt799                      1/1     Running     0          35d   10.244.6.7      coo-r1-k8s-worker-st-02
rook-ceph-osd-5-6549cdf949-qbvnh                      1/1     Running     0          35d   10.244.7.11     coo-r1-k8s-worker-st-01
rook-ceph-osd-6-7f56d8cf95-qgd8p                      1/1     Running     0          35d   10.244.6.8      coo-r1-k8s-worker-st-02
rook-ceph-osd-7-55b6c5c8df-dnp4p                      1/1     Running     0          35d   10.244.8.11     coo-r1-k8s-worker-st-03
rook-ceph-osd-8-d6df7694-w2psw                        1/1     Running     0          35d   10.244.8.10     coo-r1-k8s-worker-st-03
rook-ceph-osd-prepare-coo-r1-k8s-worker-st-01-zbs6m   0/2     Completed   0          35d   10.244.7.8      coo-r1-k8s-worker-st-01
rook-ceph-osd-prepare-coo-r1-k8s-worker-st-02-sr2dm   0/2     Completed   0          35d   10.244.6.5      coo-r1-k8s-worker-st-02
rook-ceph-osd-prepare-coo-r1-k8s-worker-st-03-zzqmq   0/2     Completed   0          35d   10.244.8.8      coo-r1-k8s-worker-st-03
rook-ceph-tools-cb5655595-vq4vj                       1/1     Running     0          35d   10.212.144.53   coo-r1-k8s-worker-st-03
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 &lt;code&gt;mon&lt;/code&gt;、&lt;code&gt;osd-prepare&lt;/code&gt; 已经部署。&lt;code&gt;rook-ceph-osd-prepare&lt;/code&gt; 格式化了磁盘，准备了 OSD，并把 &lt;code&gt;osd&lt;/code&gt; Pod 加入了集群。&lt;/p&gt;

&lt;p&gt;Rook 还提供了一个 &lt;code&gt;toolkit&lt;/code&gt; 容器，其中包含了全套的 Ceph 客户端，用于测试和排错，运行下列命令即可安装：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create -f toolkit.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;进入工具 Pod，就可以执行下面的内容了。&lt;/p&gt;

&lt;h3 id=&#34;集群配置&#34;&gt;集群配置&lt;/h3&gt;

&lt;p&gt;设置 Ceph 组件的资源：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;mgr:
    limits:
        cpu: &amp;quot;500m&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
    requests:
        cpu: &amp;quot;500m&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
mon:
    limits:
        cpu: &amp;quot;1&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
    requests:
        cpu: &amp;quot;500m&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
osd:
    limits:
        cpu: &amp;quot;1&amp;quot;
        memory: &amp;quot;2048Mi&amp;quot;
    requests:
        cpu: &amp;quot;500m&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;建议尽量为所有存储节点分配同样的 CPU、内存和磁盘。这样就可以使用 &lt;code&gt;deviceFilter&lt;/code&gt; 了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;storage:
  useAllNodes: true
  useAllDevices: false
  deviceFilter: sd[a-z]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里使用正则表达式 &lt;code&gt;/dev/sd[a-z]&lt;/code&gt; 进行设备匹配。&lt;/p&gt;

&lt;h3 id=&#34;创建-ceph-副本池以及-kubernetes-storageclass&#34;&gt;创建 Ceph 副本池以及 Kubernetes StorageClass&lt;/h3&gt;

&lt;p&gt;可以用 CRD 来定义 Pool。Rook 提供了两种机制来维持 OSD：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;副本&lt;/strong&gt;：缺省选项，每个对象都会根据 &lt;code&gt;spec.replicated.size&lt;/code&gt; 在多个磁盘上进行复制。建议非生产环境至少 2 个副本，生产环境至少 3 个。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Erasure Code&lt;/strong&gt;：是一种较为节约的方式。EC 把数据拆分 n 段（&lt;code&gt;spec.erasureCoded.dataChunks&lt;/code&gt;），再加入 k 个代码段（&lt;code&gt;spec.erasureCoded.codingChunks&lt;/code&gt;），用分布的方式把 n+k 段数据保存在磁盘上。这种情况下 Ceph 能够隔离 &lt;code&gt;k&lt;/code&gt; 个 OSD 的损失。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# pool with replication enabled
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicated-metadata-pool
  namespace: rook-ceph
spec:
  replicated:
    size: 2
---
# pool with EC enabled
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: ec-data-pool
  namespace: rook-ceph
spec:
  erasureCoded:
    dataChunks: 2
    codingChunks: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;本文中使用副本的方式来保证数据冗余。&lt;/p&gt;

&lt;p&gt;Kubernetes 环境里，StorageClass 是动态存储配置的核心。下面的例子定义了一个 Ceph 块存储的 StorageClass：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-block
provisioner: ceph.rook.io/block
parameters:
  # The replicated pool as the `blockPool` parameter
  blockPool: replicated-metadata-pool
  # The erasure coded pool must be set as the `dataBlockPool` parameter below.
  dataBlockPool: ec-data-pool
  clusterNamespace: rook-ceph
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl&lt;/code&gt; 提交 &lt;code&gt;storageclass.yaml&lt;/code&gt; 以及 &lt;code&gt;cluster.yaml&lt;/code&gt;，就完成了 Ceph 副本和 StorageClass 的创建。&lt;/p&gt;

&lt;h2 id=&#34;测试&#34;&gt;测试&lt;/h2&gt;

&lt;p&gt;使用上面创建的 &lt;code&gt;StorageClass&lt;/code&gt;，新建一个 PVC，就可以完成测试了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  storageClassName: rook-ceph-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查新建的 PVC，看状态是不是会变成 &lt;code&gt;Bounded&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get pvc
NAMESPACE       NAME                                     STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
default   mysql-pv-claim                                 Bound     pvc-f1af6df6-474a-11e9-8360-02006e76001e   8Gi        RWO            rook-ceph-block   1m
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;ceph-常用命令&#34;&gt;Ceph 常用命令&lt;/h2&gt;

&lt;p&gt;在工具箱 Pod 中，可以使用命令和 Ceph 集群进行交互。下面是一个例子。&lt;/p&gt;

&lt;h3 id=&#34;查看集群状态&#34;&gt;查看集群状态&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ ceph status
  cluster:
    id:     62e69dc1-efb5-42d9-a7bc-1ea6cfbd467f
    health: HEALTH_OK

  services:
    mon: 3 daemons, quorum c,a,b
    mgr: a(active)
    osd: 9 osds: 9 up, 9 in

  data:
    pools:   1 pools, 100 pgs
    objects: 236  objects, 406 MiB
    usage:   10 GiB used, 1.7 TiB / 1.8 TiB avail
    pgs:     100 active+clean

  io:
    client:   38 KiB/s wr, 0 op/s rd, 3 op/s wr
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;健康情况&#34;&gt;健康情况&lt;/h3&gt;

&lt;p&gt;用来查看是否有物理损坏。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ ceph health detail
HEALTH_OK
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;所有-osd-的状态&#34;&gt;所有 OSD 的状态&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ ceph osd status
+----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+
| id |           host          |  used | avail | wr ops | wr data | rd ops | rd data |   state   |
+----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+
| 0  | coo-r1-k8s-worker-st-01 | 1149M |  198G |    0   |  13.6k  |    0   |     0   | exists,up |
| 1  | coo-r1-k8s-worker-st-02 | 1157M |  198G |    0   |     0   |    0   |     0   | exists,up |
| 2  | coo-r1-k8s-worker-st-03 | 1143M |  198G |    0   |     0   |    0   |     0   | exists,up |
| 3  | coo-r1-k8s-worker-st-01 | 1128M |  198G |    0   |     0   |    0   |     0   | exists,up |
| 4  | coo-r1-k8s-worker-st-02 | 1180M |  198G |    4   |  37.6k  |    0   |     0   | exists,up |
| 5  | coo-r1-k8s-worker-st-01 | 1169M |  198G |    1   |  12.0k  |    0   |     0   | exists,up |
| 6  | coo-r1-k8s-worker-st-02 | 1109M |  198G |    0   |     0   |    0   |     0   | exists,up |
| 7  | coo-r1-k8s-worker-st-03 | 1160M |  198G |    0   |  1638   |    0   |     0   | exists,up |
| 8  | coo-r1-k8s-worker-st-03 | 1143M |  198G |    0   |  3276   |    0   |     0   | exists,up |
+----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;ceph-pool-详情&#34;&gt;Ceph Pool 详情&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ ceph osd pool ls detail
pool 1 &#39;replicapool&#39; replicated size 3 min_size 1 crush_rule 1 object_hash rjenkins pg_num 100 pgp_num 100 last_change 37 flags hashpspool,selfmanaged_snaps stripe_width 0 application rbd
removed_snaps [1~3]
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;显示-pool-和总体用量&#34;&gt;显示 Pool 和总体用量&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ rados df
POOL_NAME      USED OBJECTS CLONES COPIES MISSING_ON_PRIMARY UNFOUND DEGRADED RD_OPS      RD  WR_OPS     WR
replicapool 1.4 GiB     575      0   1150                  0       0        0   1224 2.4 MiB 1698291 84 GiB

total_objects    575
total_used       5.6 GiB
total_avail      294 GiB
total_space      300 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;重新安装&#34;&gt;重新安装&lt;/h2&gt;

&lt;h3 id=&#34;包括-rke-的完全重新部署&#34;&gt;包括 RKE 的完全重新部署&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;下面的步骤会擦除数据，不建议在生产集群上使用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rook 有很多数据保存在本地存储节点，重新部署比较困难。如果使用的是 RKE，在 Worker 和 Master 节点上清除数据，然后重新安装 RKE。&lt;/p&gt;

&lt;p&gt;删除所有 Docker、Rook 和 RKE 及其相关组件：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# remove rke docker and everything
sudo apt -y purge docker-ce \
&amp;amp;&amp;amp; sudo apt -y autoremove \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/docker \
&amp;amp;&amp;amp; sudo rm -rf /opt/* \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/rook \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/etcd \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/cni \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/containerd \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/calico \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/kubelet \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/rancher
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果是一个运行中的服务器，完成这一命令之后建议重新启动，然后重复执行一次，最后再重新安装（包括 Docker）。&lt;/p&gt;

&lt;p&gt;还需要格式化 Rook/Ceph 使用的磁盘。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo fdisk /dev/sdb
Welcome to fdisk (util-linux 2.29.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device /dev/sdb already contains a LVM2_member signature.
The signature will be removed by a write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xa24124a7.

Command (m for help):
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;简单的输入 &lt;code&gt;w&lt;/code&gt;，然后回车，就会覆盖分区表，让磁盘恢复可用。这一动作完成之后也建议重新启动。&lt;/p&gt;

&lt;h3 id=&#34;只重装-rook&#34;&gt;只重装 Rook&lt;/h3&gt;

&lt;p&gt;删除 &lt;code&gt;rook-ceph&lt;/code&gt; 以及 &lt;code&gt;rook-ceph-system&lt;/code&gt; 命名空间会造成很大麻烦。要关停 Ceph 集群：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;删除 Rook 集群的相关资源（Pool、StorageClass、PVC 等等）&lt;/p&gt;

&lt;p&gt;kubectl delete -n rook-ceph cephblockpool replicapool
kubectl delete storageclass rook-ceph-block&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;删除 cluster CRD 和 Helm release&lt;/p&gt;

&lt;p&gt;kubectl -n rook-ceph patch clusters.ceph.rook.io rook-ceph -p ‘{“metadata”:{“finalizers”: []}}’ –type=merge
kubectl -n rook-ceph delete cephcluster rook-ceph
helm delete –purge rook
kubectl delete namespace rook-ceph&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;连接到每个节点上删除 &lt;code&gt;/var/lib/rook&lt;/code&gt; 或者 &lt;code&gt;dataDirHostPath&lt;/code&gt; 指定的路径。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果出现任何问题，可以参考 &lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/ceph-teardown.md#troubleshooting&#34; target=&#34;_blank&#34;&gt;Trouble shooting 页面&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;检测&#34;&gt;检测&lt;/h2&gt;

&lt;h3 id=&#34;physical-group-修复&#34;&gt;Physical Group 修复&lt;/h3&gt;

&lt;p&gt;Ceph 偶尔会报告 Physical Group 需要修复，可以在工具箱 Pod 中完成：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ ceph health detail
HEALTH_ERR
1 pgs inconsistent; 2 scrub errors
pg 0.6 is active+clean+inconsistent, acting [0,1,2] 2 scrub errors
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的输出说明需要进行修复，执行下列命令：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ceph pg repair 0.6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个命令会启动一个修复过程，几分钟之后，会恢复到 &lt;code&gt;HEALTH_OK&lt;/code&gt; 状态。&lt;/p&gt;

&lt;h3 id=&#34;修改副本数量&#34;&gt;修改副本数量&lt;/h3&gt;

&lt;p&gt;如果副本数量设置有误，可以在运行中的副本池中修改设置，在工具箱 Pod 中执行：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ceph osd pool set replicapool size 3
ceph osd pool set replicapool min_size 3
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;修改-pg-数量&#34;&gt;修改 PG 数量&lt;/h3&gt;

&lt;p&gt;要修改副本池中的 PG 数，可以使用：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ceph osd pool set replicapool pg_num 256
ceph osd pool set replicapool pgp_num 256
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/ceph-toolbox.md&#34; target=&#34;_blank&#34;&gt;https://github.com/rook/rook/blob/master/Documentation/ceph-toolbox.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.ceph.com/docs/mimic/rados/troubleshooting/troubleshooting-pg/&#34; target=&#34;_blank&#34;&gt;http://docs.ceph.com/docs/mimic/rados/troubleshooting/troubleshooting-pg/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.ceph.com/docs/jewel/rados/operations/placement-groups/#set-the-number-of-placement-groups&#34; target=&#34;_blank&#34;&gt;http://docs.ceph.com/docs/jewel/rados/operations/placement-groups/#set-the-number-of-placement-groups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://accelazh.github.io/ceph/Ceph-Performance-Tuning-Checklist&#34; target=&#34;_blank&#34;&gt;http://accelazh.github.io/ceph/Ceph-Performance-Tuning-Checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/advanced-configuration.md&#34; target=&#34;_blank&#34;&gt;https://github.com/rook/rook/blob/master/Documentation/advanced-configuration.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/common-issues.md&#34; target=&#34;_blank&#34;&gt;https://github.com/rook/rook/blob/master/Documentation/common-issues.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.ceph.com/docs/giant/rados/troubleshooting/troubleshooting-osd/&#34; target=&#34;_blank&#34;&gt;http://docs.ceph.com/docs/giant/rados/troubleshooting/troubleshooting-osd/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 存储性能对比</title>
      <link>/post/kubernetes-storage-performance-comparison/</link>
      <pubDate>Mon, 13 May 2019 23:10:28 +0800</pubDate>
      <guid>/post/kubernetes-storage-performance-comparison/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://medium.com/vescloud/kubernetes-storage-performance-comparison-9e993cb27271&#34; target=&#34;_blank&#34;&gt;Kubernetes Storage Performance Comparison&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://medium.com/@pupapaik&#34; target=&#34;_blank&#34;&gt;Jakub Pavlík&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果你正在运行 Kubernetes，你可能正在使用，或者准备使用动态供给的块存储&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/#types-of-volumes&#34; target=&#34;_blank&#34;&gt;卷&lt;/a&gt;，而首当其冲的问题就是为集群选择合适的存储技术。这个事情并不能用一个简单的测试来做出简单的回答，告诉你目前市面上最好的技术是什么。存储技术的选择过程中，集群上运行的负载类型是一个重要的输入。对于裸金属集群来说，需要根据实际用例进行选择，并集成到自己的硬件之中。公有云中的托管 K8s，例如 AKS、EKS 或者 GKE，都具有开箱可用的块存储能力，然而这也不见得就是最好的选择。有很多因素需要考虑，比如说公有云的 StorageClass 的故障转移时间太长。例如在 一个针对 AWS EBS 的故障测试中，加载了卷的 Pod 用了&lt;a href=&#34;https://blog.rook.io/run-your-own-high-performance-ebs-wherever-kubernetes-runs-798a136bd808&#34; target=&#34;_blank&#34;&gt;超过五分钟&lt;/a&gt;才成功的在另一个节点上启动。Portworx 或者 OpenEBS 这样的云原生存储产品，正在尝试解决这类问题。&lt;/p&gt;

&lt;p&gt;本文的目标是使用最常见的 Kubernetes 存储方案，进行基本的性能对比。我觉得在 Azure AKS 上使用下列后端：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AKS 原生 Storageclass：Azure native premium
&lt;!-- - AKS 存储 --&gt;&lt;/li&gt;
&lt;li&gt;使用 cStor 后端的 OpenEBS&lt;/li&gt;
&lt;li&gt;Portworx&lt;/li&gt;
&lt;li&gt;Heketi 管理的 Gluster&lt;/li&gt;
&lt;li&gt;Rook 管理的 Ceph&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;现在我们来介绍每种存储后端，并交代一下安装过程，然后进入 AKS 测试环境进行测试，最后得出结果。&lt;/p&gt;

&lt;h2 id=&#34;存储&#34;&gt;存储&lt;/h2&gt;

&lt;p&gt;这一节中介绍测试中用到的存储方案，包含安装过程以及该方案的优缺点。&lt;/p&gt;

&lt;h3 id=&#34;azure-原生-storageclass&#34;&gt;Azure 原生 StorageClass&lt;/h3&gt;

&lt;p&gt;我选择这一方案的动机是以此作为所有测试的基线。这个方案&lt;strong&gt;应该&lt;/strong&gt;提供最佳性能。Azure 动态的创建托管磁盘，并把它们映射到 K8s 的虚拟机中，最终成为 Pod 的存储卷。&lt;/p&gt;

&lt;p&gt;这个方案很方便，什么多余的步骤都不需要。创建一个新的 AKS 集群之后，就自动提供了两个预定义的 StorageClass，分别是 &lt;code&gt;default&lt;/code&gt; 和 &lt;code&gt;managed-premium&lt;/code&gt;，premium 使用的是基于 SSD 的高性能低延迟磁盘。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get storageclasses
NAME                PROVISIONER                AGE
default (default)   kubernetes.io/azure-disk   8m
managed-premium     kubernetes.io/azure-disk   8m

$ kubectl get pvc
NAME              STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
dbench-pv-claim   Bound     pvc-e7bd34a4-1dbd-11e9-8726-ae508476e8ad   1000Gi     RWO            managed-premium   10s

$ kubectl get po
NAME           READY     STATUS              RESTARTS   AGE
dbench-w7nqf   0/1       ContainerCreating   0          29s
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;优点&#34;&gt;优点&lt;/h4&gt;

&lt;p&gt;AKS 开箱即用。&lt;/p&gt;

&lt;h4 id=&#34;缺点&#34;&gt;缺点&lt;/h4&gt;

&lt;p&gt;故障转移非常缓慢，有时需要十分钟以后，存储卷才能重新挂载到不同节点上的 Pod 里。&lt;/p&gt;

&lt;h3 id=&#34;openebs&#34;&gt;OpenEBS&lt;/h3&gt;

&lt;p&gt;对我来说 OpenEBS 是个全新事物，因此我很有兴趣做他的测试。他提出了一个新的 &lt;a href=&#34;https://docs.openebs.io/docs/next/conceptscas.html#advantages-of-cas&#34; target=&#34;_blank&#34;&gt;Container Attached Storage（容器挂载存储）&lt;/a&gt;概念，这是一个基于微服务的存储控制器，以及多个基于微服务的存储副本。他和 Portworx 同样，属于云原生存储分类的成员。&lt;/p&gt;

&lt;p&gt;它是一个完全开源的方案，目前提供两种后端——Jiva 和 cStor。我最开始选择的是 Jiva，后来切换到 cStor。cStor 有很多长处，例如他的控制器和副本被部署到单一的 OpenEBS 所在的命名空间之中，能够管理原始磁盘等。每个 K8s 卷都有自己的存储控制器，能在节点存储容量的许可范围内对存储进行扩展。&lt;/p&gt;

&lt;h4 id=&#34;在-aks-上运行&#34;&gt;在 AKS 上运行&lt;/h4&gt;

&lt;p&gt;在 AKS 上的安装非常容易。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;连接到所有 K8s 节点上，安装 iSCSI，这是因为他需要使用 iSCSI 协议在 K8s 节点之间进行 Pod 和控制器的连接。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;apt-get update
apt install -y open-iscsi
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使用一个 YAML 定义在 K8s 集群上完成部署：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl apply -f https://openebs.github.io/charts/openebs-operator-0.8.0.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;下一步，OpenEBS 控制器发现了节点中的所有磁盘。但是我必须手工标识出我附加的 AWS 托管磁盘。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get disk
NAME                                      AGE
disk-184d99015253054c48c4aa3f17d137b1     5m
disk-2f6bced7ba9b2be230ca5138fd0b07f1     5m
disk-806d3e77dd2e38f188fdaf9c46020bdc     5m
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;然后把这些磁盘加入 StoragePoolClaim，这个对象会在 StorageClass 中进行引用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: openebs-custom
  annotations:
    openebs.io/cas-type: cstor
    cas.openebs.io/config: |
      - name: StoragePoolClaim
        value: &amp;quot;cstor-disk&amp;quot;
provisioner: openebs.io/provisioner-iscsi
---
apiVersion: openebs.io/v1alpha1
kind: StoragePoolClaim
metadata:
  name: cstor-disk
spec:
  name: cstor-disk
  type: disk
  maxPools: 3
  poolSpec:
    poolType: striped
  disks:
    diskList:
    - disk-2f6bced7ba9b2be230ca5138fd0b07f1
    - disk-806d3e77dd2e38f188fdaf9c46020bdc
    - disk-184d99015253054c48c4aa3f17d137b1
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;完成这些步骤之后，就可以用 K8s 的 PVC 来动态的创建存储卷了。&lt;/p&gt;

&lt;h4 id=&#34;优点-1&#34;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;开源&lt;/li&gt;
&lt;li&gt;Maya 在资源使用的可视化方面做得非常好。可以在 K8s 中部署多个服务，方便的为集群的各方面数据设置监控和日志。对于排错工作来说，这十分重要。&lt;/li&gt;
&lt;li&gt;CAS 概念：我非常欣赏这一概念，我相信这是未来的趋势。&lt;/li&gt;
&lt;li&gt;OpenEBS 社区：在社区中我的任何问题都能在几分钟内得到解决。Slack 上的团队非常有帮助。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;缺点-1&#34;&gt;缺点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;不成熟：OpenEBS 还很年轻，目前还没有发布稳定版。核心团队还在进行后端的优化，未来几个月里会对性能做出很大提升。&lt;/li&gt;
&lt;li&gt;Kubelet 和存储控制器之间的 iSCSI 连接是通过 K8s Service 进行的，这在 Tungsten Fabric 之类的 CNI 插件环境中可能会出问题。&lt;/li&gt;
&lt;li&gt;需要在 K8s 节点上安装额外的软件（iSCSI），这对于托管集群来说非常不便。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注：OpenEBS 团队对我的案例场景进行了调整：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/kmova/openebs/tree/fio-perf-tests/k8s/demo/dbench&#34; target=&#34;_blank&#34;&gt;https://github.com/kmova/openebs/tree/fio-perf-tests/k8s/demo/dbench&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;portworx&#34;&gt;Portworx&lt;/h3&gt;

&lt;p&gt;Portworx 是另一个面向 Kubernetes 的容器原生存储方案，它专注于高度分布式的环境。这是一个主机可寻址的存储，每个卷都直接映射到挂在的主机上。他提供了基于应用 I/O 类型的自动微调能力。&lt;a href=&#34;https://portworx.com/makes-portworx-unique/&#34; target=&#34;_blank&#34;&gt;官方网站&lt;/a&gt;提供了更多信息。不幸的是，它也是本文中唯一的非开源产品。然而它提供了 3 节点的免费试用。&lt;/p&gt;

&lt;h4 id=&#34;在-aks-上运行-1&#34;&gt;在 AKS 上运行&lt;/h4&gt;

&lt;p&gt;在 AKS 上的安装同样简单，我用了他们&lt;a href=&#34;https://docs.portworx.com/portworx-install-with-kubernetes/cloud/azure/aks/2-deploy-px/#generate-the-specs&#34; target=&#34;_blank&#34;&gt;网站&lt;/a&gt;提供的生成器。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;选择基于 Portworx 的 ETCD，指定 K8s 版本为 1.11.4。&lt;/li&gt;
&lt;li&gt;因为我用了 Azure CNI，因此必须把数据网卡设置为 &lt;code&gt;azure0&lt;/code&gt;。否则 Portworx 会使用 docker bridge 的 IP 地址，而非 VM 网卡。&lt;/li&gt;
&lt;li&gt;最后网站会生成渲染完成的 YAML 文件。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;提交后，会看到节点上运行的 Portworx Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods -o wide -n kube-system -l name=portworx
NAME             READY     STATUS    RESTARTS   AGE       IP          NODE                       NOMINATED NODE
portworx-g9csq   1/1       Running   0          14m       10.0.1.66   aks-agentpool-20273348-2   &amp;lt;none&amp;gt;
portworx-nt2lq   1/1       Running   0          14m       10.0.1.4    aks-agentpool-20273348-0   &amp;lt;none&amp;gt;
portworx-wcjnx   1/1       Running   0          14m       10.0.1.35   aks-agentpool-20273348-1   &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为 PVC 创建一个 StorageClass，定义高优先级，以及三个副本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;root@aks-agentpool-20273348-0:~# kubectl get storageclass -o yaml portworx-sc
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  creationTimestamp: 2019-01-28T21:10:28Z
  name: portworx-sc
  resourceVersion: &amp;quot;55332&amp;quot;
  selfLink: /apis/storage.k8s.io/v1/storageclasses/portworx-sc
  uid: 23455e40-2341-11e9-bfcb-a23b1ec87092
parameters:
  priority_io: high
  repl: &amp;quot;3&amp;quot;
provisioner: kubernetes.io/portworx-volume
reclaimPolicy: Delete
volumeBindingMode: Immediate
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;优点-2&#34;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;部署方便：生成器包含配置细节。&lt;/li&gt;
&lt;li&gt;不像 Ceph 和 Glusterfs 那样需要进行额外配置。&lt;/li&gt;
&lt;li&gt;云原生存储：公有云和裸金属都可以运行。&lt;/li&gt;
&lt;li&gt;存储级别感知和应用感知的 I/O 微调。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;缺点-2&#34;&gt;缺点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;闭源：商业解决方案&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;glusterfs-heketi&#34;&gt;GlusterFS Heketi&lt;/h3&gt;

&lt;p&gt;GlusterFS 是知名的开源存储方案，是由 Redhat 提供的开源存储方案。&lt;a href=&#34;https://github.com/gluster/gluster-kubernetes#glusterfs-native-storage-service-for-kubernetes&#34; target=&#34;_blank&#34;&gt;Heketi&lt;/a&gt; 是 GlusterFS 的 RESTful 卷管理界面。它提供了易用的方式为 GlusterFS 卷提供了动态供给的功能。如果没有 Heketi 的辅助，就只能手工创建 GlusterFS 卷并映射到 K8s PV 了。关于 GlusterFS 的更多信息，请阅读&lt;a href=&#34;https://docs.gluster.org/en/latest/Install-Guide/Overview/&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;。&lt;/p&gt;

&lt;h4 id=&#34;在-aks-上运行-2&#34;&gt;在 AKS 上运行&lt;/h4&gt;

&lt;p&gt;根据 Heketi 的&lt;a href=&#34;https://github.com/gluster/gluster-kubernetes/blob/master/docs/setup-guide.md#deployment&#34; target=&#34;_blank&#34;&gt;快速入门&lt;/a&gt;文档进行部署。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;参照&lt;a href=&#34;https://github.com/gluster/gluster-kubernetes/blob/master/deploy/topology.json.sample&#34; target=&#34;_blank&#34;&gt;样例&lt;/a&gt;，创建一个包含磁盘和主机名的拓扑文件。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Heketi 主要的开发和测试都在基于 RHEL 的操作系统上，我在 AKS 上使用 Ubuntu 主机时，出现了内核模块路径错误的问题，我提交了一个 &lt;a href=&#34;https://github.com/gluster/gluster-kubernetes/pull/557&#34; target=&#34;_blank&#34;&gt;PR&lt;/a&gt; 来修正这个问题。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;+++ b/deploy/kube-templates/glusterfs-daemonset.yaml
@@ -67,7 +67,7 @@ spec:
           mountPath: &amp;quot;/etc/ssl&amp;quot;
           readOnly: true
         - name: kernel-modules
-          mountPath: &amp;quot;/usr/lib/modules&amp;quot;
+          mountPath: &amp;quot;/lib/modules&amp;quot;
           readOnly: true
         securityContext:
           capabilities: {}
@@ -131,4 +131,4 @@ spec:
           path: &amp;quot;/etc/ssl&amp;quot;
       - name: kernel-modules
         hostPath:
-          path: &amp;quot;/usr/lib/modules&amp;quot;
+          path: &amp;quot;/lib/modules&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;我在 AKS 环境中遇到的另一个问题是一个非空磁盘，所以我用 &lt;code&gt;wipefs&lt;/code&gt; 为 glusterfs 进行清理。这个磁盘并未用过。&lt;/p&gt;

&lt;p&gt;$ wipefs -a /dev/sdc
/dev/sdc: 8 bytes were erased at offset 0x00000218 (LVM2_member): 4c 56 4d 32 20 30 30 31&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;最后运行 &lt;code&gt;gk-deploy -g -t topology.json&lt;/code&gt;，会在每个节点上运行 Heketi 控制器管理之下的 GlusterFS Pod。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get po -o wide
NAME                     READY   STATUS    RESTARTS IP        NODE                       NOMINATED NODE
glusterfs-fgc8f          1/1     Running   0       10.0.1.35  aks-agentpool-20273348-1
glusterfs-g8ht6          1/1     Running   0       10.0.1.4   aks-agentpool-20273348-0
glusterfs-wpzzp          1/1     Running   0       10.0.1.66  aks-agentpool-20273348-2
heketi-86f98754c-n8qfb   1/1     Running   0       10.0.1.69  aks-agentpool-20273348-2
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;然后我遇到了新问题。K8s 控制面无法使用 Heketi 的 &lt;code&gt;restURL&lt;/code&gt;。我测试了一下 kube dns 的记录，pod IP 和 svc IP 都没有生效。最后只能手工使用 Heketi CLI 来创建存储卷。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ export HEKETI_CLI_SERVER=http://10.0.1.69:8080
$ heketi-cli volume create --size=10 --persistent-volume --persistent-volume-endpoint=heketi-storage-endpoints | kubectl create -f -
persistentvolume/glusterfs-efb3b155 created

$ kubectl get pv
NAME                 CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
glusterfs-efb3b155   10Gi       RWX            Retain           Available
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后把现存 PV 映射为 PVC，加载给测试工具进行测试。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: glusterfs-efb3b155
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: &amp;quot;&amp;quot;
  resources:
    requests:
      storage: 10Gi
  volumeName: glusterfs-efb3b155
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pvc
NAME                 STATUS    VOLUME               CAPACITY   ACCESS MODES   STORAGECLASS   AGE
glusterfs-efb3b155   Bound     glusterfs-efb3b155   10Gi       RWX                           36m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Heketi 的更多输出：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ gluster volume info vol_efb3b15529aa9aba889d7900f0ce9849

Volume Name: vol_efb3b15529aa9aba889d7900f0ce9849
Type: Replicate
Volume ID: 96fde36b-e389-4dbe-887b-baae32789436
Status: Started
Snapshot Count: 0
Number of Bricks: 1 x 3 = 3
Transport-type: tcp
Bricks:
Brick1: 10.0.1.66:/var/lib/heketi/mounts/vg_5413895eade683e1ca035760c1e0ffd0/brick_cd7c419bc4f4ff38bbc100c6d7b93605/brick
Brick2: 10.0.1.35:/var/lib/heketi/mounts/vg_3277c6764dbce56b5a01426088901f6d/brick_6cbd74e9bed4758110c67cfe4d4edb53/brick
Brick3: 10.0.1.4:/var/lib/heketi/mounts/vg_29d6152eeafc57a707bef56f091afe44/brick_4856d63b721d794e7a4cbb4a6f048d96/brick
Options Reconfigured:
transport.address-family: inet
nfs.disable: on
performance.client-io-threads: off

$ kubectl get svc
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
heketi                     ClusterIP   192.168.101.75   &amp;lt;none&amp;gt;        8080/TCP   5h
heketi-storage-endpoints   ClusterIP   192.168.103.66   &amp;lt;none&amp;gt;        1/TCP      5h

$ kubectl get endpoints
NAME                       ENDPOINTS                            AGE
heketi                     10.0.1.69:8080                       5h
heketi-storage-endpoints   10.0.1.35:1,10.0.1.4:1,10.0.1.66:1   5h
kubernetes                 172.31.22.152:443                    1d
root@aks-agentpool-20273348-0:~# kubectl get endpoints heketi-storage-endpoints -o yaml
apiVersion: v1
kind: Endpoints
metadata:
  creationTimestamp: 2019-01-29T15:14:28Z
  name: heketi-storage-endpoints
  namespace: default
  resourceVersion: &amp;quot;142212&amp;quot;
  selfLink: /api/v1/namespaces/default/endpoints/heketi-storage-endpoints
  uid: 91f802eb-23d8-11e9-bfcb-a23b1ec87092
subsets:
- addresses:
  - ip: 10.0.1.35
  - ip: 10.0.1.4
  - ip: 10.0.1.66
  ports:
  - port: 1
    protocol: TCP
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;优点-3&#34;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;久经考验的存储方案。&lt;/li&gt;
&lt;li&gt;比 Ceph 轻量。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;缺点-3&#34;&gt;缺点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Heketi 在公有云上表现不佳。在私有云上表现良好，安装会方便一些。&lt;/li&gt;
&lt;li&gt;并非为结构化数据设计，例如 SQL 数据库。然而可以使用 GlusterFS 为数据库提供备份和恢复支持。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;ceph-rook&#34;&gt;Ceph Rook&lt;/h3&gt;

&lt;p&gt;我在 OpenStack 私有云上尝试过安装和运行 Ceph。它需要为特定硬件定制参数，根据数据类型设计 pg 组、SSD 分区和 CRUSH 图等。所以第一次听说在 3 节点的 K8s 集群上运行 Ceph 的时候，我不太相信它能工作。结果 Rook 的编排工具让我印象深刻，它把所有的步骤和 K8s 的编排能力结合在一起，让安装变得非常简便。&lt;/p&gt;

&lt;h4 id=&#34;在-aks-上运行-3&#34;&gt;在 AKS 上运行&lt;/h4&gt;

&lt;p&gt;Rook 的缺省安装无需任何特定步骤，如果没什么高级配置，会非常简单。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;我使用的是 &lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/ceph-quickstart.md#ceph-storage-quickstart&#34; target=&#34;_blank&#34;&gt;Ceph 快速入门指南&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;为 AKS 配置 &lt;code&gt;FLEXVOLUME_DIR_PATH&lt;/code&gt;，这是因为它需要 &lt;code&gt;/etc/kubernetes/volumeplugins/&lt;/code&gt;，而不是 Ubuntu 中缺省的 &lt;code&gt;/usr/libexec&lt;/code&gt;，没有这个步骤，Kubelet 就&lt;a href=&#34;https://github.com/rook/rook/issues/1790#issuecomment-402574647&#34; target=&#34;_blank&#34;&gt;无法加载 PVC&lt;/a&gt; 了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;diff --git a/cluster/examples/kubernetes/ceph/operator.yaml b/cluster/examples/kubernetes/ceph/operator.yaml
index 73cde2e..33f45c8 100755
--- a/cluster/examples/kubernetes/ceph/operator.yaml
+++ b/cluster/examples/kubernetes/ceph/operator.yaml
@@ -431,8 +431,8 @@ spec:
         # - name: AGENT_MOUNT_SECURITY_MODE
         #   value: &amp;quot;Any&amp;quot;
         # Set the path where the Rook agent can find the flex volumes
-        # - name: FLEXVOLUME_DIR_PATH
-        #  value: &amp;quot;&amp;lt;PathToFlexVolumes&amp;gt;&amp;quot;
+        - name: FLEXVOLUME_DIR_PATH
+          value: &amp;quot;/etc/kubernetes/volumeplugins&amp;quot;
         # Set the path where kernel modules can be found
         # - name: LIB_MODULES_DIR_PATH
         #  value: &amp;quot;&amp;lt;PathToLibModules&amp;gt;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;还要在 &lt;code&gt;deviceFilter&lt;/code&gt; 中指定要使用的设备，这里是 &lt;code&gt;/dev/sdc&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;diff --git a/cluster/examples/kubernetes/ceph/cluster.yaml b/cluster/examples/kubernetes/ceph/cluster.yaml
index 48cfeeb..0c91c48 100755
--- a/cluster/examples/kubernetes/ceph/cluster.yaml
+++ b/cluster/examples/kubernetes/ceph/cluster.yaml
@@ -227,7 +227,7 @@ spec:
   storage: # cluster level storage configuration and selection
     useAllNodes: true
     useAllDevices: false
-    deviceFilter:
+    deviceFilter: &amp;quot;^sdc&amp;quot;
     location:
     config:
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;安装之后，创建一个 Ceph block pool，以及 StorageClass，使用如下配置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-block
provisioner: ceph.rook.io/block
parameters:
  blockPool: replicapool
  clusterNamespace: rook-ceph
  fstype: xfs
reclaimPolicy: Retain
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;最后使用部署&lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/ceph-toolbox.md&#34; target=&#34;_blank&#34;&gt;工具&lt;/a&gt;进行检查。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;ceph status
cluster:
id:     bee70a10-dce1-4725-9285-b9ec5d0c3a5e
health: HEALTH_OK

services:
mon: 3 daemons, quorum c,b,a
mgr: a(active)
osd: 3 osds: 3 up, 3 in

data:
pools:   0 pools, 0 pgs
objects: 0  objects, 0 B
usage:   3.0 GiB used, 3.0 TiB / 3.0 TiB avail
pgs:

[root@aks-agentpool-27654233-0 /]#
[root@aks-agentpool-27654233-0 /]#
[root@aks-agentpool-27654233-0 /]# ceph osd status
+----+--------------------------+-------+-------+--------+---------+--------+---------+-----------+
| id |           host           |  used | avail | wr ops | wr data | rd ops | rd data |   state   |
+----+--------------------------+-------+-------+--------+---------+--------+---------+-----------+
| 0  | aks-agentpool-27654233-0 | 1025M | 1021G |    0   |     0   |    0   |     0   | exists,up |
| 1  | aks-agentpool-27654233-1 | 1025M | 1021G |    0   |     0   |    0   |     0   | exists,up |
| 2  | aks-agentpool-27654233-2 | 1025M | 1021G |    0   |     0   |    0   |     0   | exists,up |
+----+--------------------------+-------+-------+--------+---------+--------+---------+-----------+
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&#34;优点-4&#34;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;在大型生产环境上的健壮存储系统。&lt;/li&gt;
&lt;li&gt;Rook 很好的简化了生命周期管理。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;缺点-4&#34;&gt;缺点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;复杂：更加重量级，也不太适合在公有云上运行。在私有云上的运行可能更加合适。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;aks-测试环境&#34;&gt;AKS 测试环境&lt;/h2&gt;

&lt;p&gt;我用 3 个虚拟机创建了基本的 Azure AKS 集群。为了连接到 Premium SSD 上，我只能使用 type E 以上级别的虚拟机。因此我选择了 &lt;a href=&#34;https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-memory#esv3-series&#34; target=&#34;_blank&#34;&gt;Standard_E2s_v3&lt;/a&gt;，其上配备了 2 vCPU 以及 16GB 的内存。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/aks-vm-e2.png&#34; alt=&#34;vm&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 AKS 集群所在的资源足中，可以看到所有的虚拟机、网络接口等资源。在这里创建 3 个 1TB 的 Premium SSD 存储，并手工挂载到每个虚拟机上。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/resource-group.png&#34; alt=&#34;resource group&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这样在每个实例上，我都有 1TB 的空磁盘。Azure 的页面上，根据我们选择的虚拟机和磁盘尺寸来看，性能应该有 5000 IOPS 以及 200MB/s 的吞吐量。最后一节会显示我们的真实结果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/azure-disk-performance.png&#34; alt=&#34;azure new disk&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;性能结果&#34;&gt;性能结果&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：每种存储的结果并不能作为独立的评估结果，但是其比较情况是可以参考的。有很多种对比测试的方法，这是最简单的一种。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;为了运行测试，我决定使用现成的测试工具 &lt;a href=&#34;https://github.com/logdna/dbench&#34; target=&#34;_blank&#34;&gt;Dbench&lt;/a&gt;，它是一个 k8s 的 YAML 文件，会使用 &lt;a href=&#34;https://github.com/axboe/fio&#34; target=&#34;_blank&#34;&gt;FIO&lt;/a&gt; 运行 8 个测试用例。可以在 Dockerfile 中&lt;a href=&#34;https://github.com/logdna/dbench/blob/master/docker-entrypoint.sh&#34; target=&#34;_blank&#34;&gt;指定不同测试&lt;/a&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;随机读写带宽。&lt;/li&gt;
&lt;li&gt;随机读写 IOPS。&lt;/li&gt;
&lt;li&gt;读写延迟。&lt;/li&gt;
&lt;li&gt;顺序读写。&lt;/li&gt;
&lt;li&gt;混合读写 IOPS。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所有测试的结果可以在 &lt;a href=&#34;https://gist.github.com/pupapaik/76c5b7f124dbb69080840f01bf71f924&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 上找到。&lt;/p&gt;

&lt;h3 id=&#34;随机读写带宽&#34;&gt;随机读写带宽&lt;/h3&gt;

&lt;p&gt;随机读写测试表明，GlusterFS、Ceph 以及 Portworx 的读取性能比 AWS 本地盘的 &lt;code&gt;hostPath&lt;/code&gt; 快了几倍。读缓存是罪魁祸首。GlusterFS 和 Portworx 的写入更快，其效率直逼本地磁盘。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/random-rw-bw.png&#34; alt=&#34;table1&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure1.png&#34; alt=&#34;figure1&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;随机读写-iops&#34;&gt;随机读写 IOPS&lt;/h3&gt;

&lt;p&gt;随机 IOPS 测试中，Portworx 和 Ceph 表现最好。Portworx 在写入方面获得了接近 Azure 原生 PVC 的 IOPS。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table2.png&#34; alt=&#34;table2&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure2.png&#34; alt=&#34;figure2&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;读写延迟&#34;&gt;读写延迟&lt;/h3&gt;

&lt;p&gt;延迟测试的结果比较有趣，Azure 原生 PVC 比多数其它存储都差。Portworx 和 Ceph 表现最好。写入方面，GlusterFS 要优于 Ceph。OpenEBS 的延迟相对来说非常的高。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table3.png&#34; alt=&#34;tables&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure3.png&#34; alt=&#34;figure&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;顺序读写&#34;&gt;顺序读写&lt;/h3&gt;

&lt;p&gt;顺序读写的结果和前面的随机测试差不多，然而 Cpeh 在读取方面比 GlusterFS 快了一倍多。写入结果基本一致，只有 OpenEBS 表现奇差。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table4.png&#34; alt=&#34;table4&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure4.png&#34; alt=&#34;figure4&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;混合读写-iops&#34;&gt;混合读写 IOPS&lt;/h3&gt;

&lt;p&gt;最后一个测试用例检查的是混合读写情况下的 IOPS，Portworx 和 Ceph 都给出了优于 Azure 原生 PVC 的结果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table5.png&#34; alt=&#34;table5&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure5.png&#34; alt=&#34;figure5&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;结论&#34;&gt;结论&lt;/h2&gt;

&lt;p&gt;本文展示了一个简单的存储对比，使用未经性能优化的多种存储提供的存储卷进行测试和比较。建议关注本文所述方法，不建议直接采用结果进行判断。&lt;/p&gt;

&lt;p&gt;忽略 Azure 的原生 PVC 或 &lt;code&gt;hostPath&lt;/code&gt;，我们可以得出如下测试结果：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Portworx 是 AKS 上最快的容器存储。&lt;/li&gt;
&lt;li&gt;Ceph 是私有云集群上最快的开源存储后端。对公有云来说，其操作太过复杂，这些多余的复杂性并没有能提供更好的测试表现。&lt;/li&gt;
&lt;li&gt;OpenEBS 的概念很棒，但是其后端需要更多优化。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;调整性能数据的测试规模应该会很有意思。另外值得关注的对比就是 CPU 和内存的消耗。我会持续关注，并分享更多。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>实验手记：Kubernetes 中进行 PVC 的扩容</title>
      <link>/post/resizing-pvc/</link>
      <pubDate>Sun, 15 Jul 2018 00:11:28 +0800</pubDate>
      <guid>/post/resizing-pvc/</guid>
      <description>

&lt;p&gt;Kubernetes 1.11 版本中，PVC 的扩容功能进入了 Beta 阶段，一般来说，Kubernetes 功能进入 Beta 阶段就意味着基本可以用于生产了。这里就做几个小测试，看看这一功能的使用方法。&lt;/p&gt;

&lt;h2 id=&#34;开始之前&#34;&gt;开始之前&lt;/h2&gt;

&lt;p&gt;首先当然是要有一个 Kubernetes 1.11 版本的集群。并且提供了支持 Auto provision 的存储。下面的实验是基于 Azure 的 ACS-Engine 集群。&lt;/p&gt;

&lt;h3 id=&#34;创建-storageclass&#34;&gt;创建 StorageClass&lt;/h3&gt;

&lt;p&gt;接下来准备两个 Storage Class 对象，分别命名为 &lt;code&gt;common&lt;/code&gt; 和 &lt;code&gt;expend&lt;/code&gt;，二者主体基本一致，文件名分别为 &lt;code&gt;sc-common.yaml&lt;/code&gt; 以及 &lt;code&gt;sc-exp.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: exp  # sc-common.yaml 中这里的值为 common
parameters:
  cachingmode: None
  kind: Managed
  storageaccounttype: Standard_LRS
provisioner: kubernetes.io/azure-disk
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true # sc-common.yaml 中删掉这一行
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f common.yaml
storageclass.storage.k8s.io/common created
$ kubectl apply -f exp.yaml
storageclass.storage.k8s.io/exp created
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;创建一个-pvc&#34;&gt;创建一个 PVC&lt;/h3&gt;

&lt;p&gt;我们接下来创建一个 PVC，初始首先测试一下 &lt;code&gt;common&lt;/code&gt; 这个 Storageclass，后续的 PVC 操作都从这一个 YAML 中修改而来。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 2Gi
  storageClassName: common
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;同样使用 &lt;code&gt;kubectl&lt;/code&gt; 创建这个 PVC：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f pvc.yaml
persistentvolumeclaim/myclaim created
$ kubectl get pvc -w
NAME      STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
myclaim   Pending                                       common         9s
myclaim   Pending                                 common    10s
myclaim   Pending   pvc-e3f8e886-8776-11e8-b82d-000d3aa2ebc3   0                   common    11s
myclaim   Bound     pvc-e3f8e886-8776-11e8-b82d-000d3aa2ebc3   2Gi       RWO       common    11s
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;第一次扩容尝试&#34;&gt;第一次扩容尝试&lt;/h3&gt;

&lt;p&gt;PVC 进入 &lt;code&gt;Bound&lt;/code&gt; 状态之后，我们编辑 &lt;code&gt;pvc.yaml&lt;/code&gt;，将容量改成 3Gi，并重新 Apply：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat pvc.yaml | sed  &amp;quot;s/2Gi/3Gi/&amp;quot; | kubectl apply -f -
Error from server (Forbidden): error when applying patch:
...
for: &amp;quot;STDIN&amp;quot;: persistentvolumeclaims &amp;quot;myclaim&amp;quot; is forbidden: only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;结果表明，这次扩容失败了，失败的原因是 Storageclass 不支持扩容&lt;/p&gt;

&lt;h3 id=&#34;使用新的-storageclass-创建-pvc&#34;&gt;使用新的 Storageclass 创建 PVC&lt;/h3&gt;

&lt;p&gt;接下来我们将这个 PVC 删除，使用 exp 这个 Storageclass 重建 PVC：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl delete -f pvc.yaml
persistentvolumeclaim &amp;quot;myclaim&amp;quot; deleted
$ sed -i .bak s/common/exp/ pvc.yaml
$ kubectl apply -f pvc.yaml
persistentvolumeclaim/myclaim created
$ kubectl get pvc -w
NAME      STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
myclaim   Bound     pvc-37eb6014-8778-11e8-b82d-000d3aa2ebc3   2Gi        RWO            exp            11s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建之后，我们可以再次尝试对 PVC 进行扩容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat pvc.yaml | sed  &amp;quot;s/2Gi/1Gi/&amp;quot; | kubectl apply -f -
The PersistentVolumeClaim &amp;quot;myclaim&amp;quot; is invalid: spec.resources.requests.storage: Forbidden: field can not be less than previous value
$ cat pvc.yaml | sed  &amp;quot;s/2Gi/3Gi/&amp;quot; | kubectl apply -f -
persistentvolumeclaim/myclaim configured
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里两次执行命令：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;缩容是不允许的&lt;/li&gt;
&lt;li&gt;扩容成功&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来我们再次获取 PVC 信息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe pvc myclaim
Name:          myclaim
Namespace:     default
StorageClass:  exp
Status:        Bound
Volume:        pvc-37eb6014-8778-11e8-b82d-000d3aa2ebc3
...
Capacity:      2Gi
Access Modes:  RWO
Conditions:
...
FileSystemResizePending
...
Waiting for user to (re-)start a pod to finish file system resize of volume on node.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里会看到，虽然没出错，但是容量还是原有的 2G，他的 Condition 中提示，需要进行一个 Pod 绑定才能真正生效。&lt;/p&gt;

&lt;h3 id=&#34;绑定-pod&#34;&gt;绑定 Pod&lt;/h3&gt;

&lt;p&gt;新建一个 Deployment 来使用前面创建的 PVC：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sleep
        version: v1
    spec:
      containers:
      - name: sleep
        image: dustise/sleep:v0.5
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: data
          mountPath: &amp;quot;/data&amp;quot;
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: myclaim
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次查看 PVC 的情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe pvc myclaim
...
Capacity:      3Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;容量的修改的确生效了。&lt;/p&gt;

&lt;h3 id=&#34;绑定之后的-pvc-扩容&#34;&gt;绑定之后的 PVC 扩容&lt;/h3&gt;

&lt;p&gt;再次对这个 PVC 进行扩容，我们这次从 3Gi 扩容到 4Gi：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat pvc.yaml | sed  &amp;quot;s/2Gi/4Gi/&amp;quot; | kubectl apply -f -
persistentvolumeclaim/myclaim configured
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后获取一下 PVC 的情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe pvc myclaim
...
Capacity:      3Gi
...
Events:
  Warning  VolumeResizeFailed     31s (x2 over 56s)  volume_expand
  Original Error: failed request: autorest/azure: Service returned an error. Status=&amp;lt;nil&amp;gt; Code=&amp;quot;OperationNotAllowed&amp;quot; Message=&amp;quot;Cannot resize disk k8s-5b49c85f-dynamic-pvc-37eb6014-8778-11e8-b82d-000d3aa2ebc3 while it is attached to running VM /subscriptions/6d9be255-d214-4502-a51d-08e1d9c4a7fb/resourceGroups/k8s/providers/Microsoft.Compute/virtualMachines/k8s-agentpool1-17067717-0.&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这一情况看来，这次扩容仍然没有生效，错误信息中有提示，无法在已经成功挂载的卷上进行扩容，因此我们清除所有 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl scale deployment sleep --replicas 0
deployment.extensions/sleep scaled
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在相关 Pod 消失之后，我们可以再次 &lt;code&gt;describe pvc myclaim&lt;/code&gt;，发现这个 PVC 又一次处于等待绑定的状态中。使用 scale 指令恢复 Deployment 运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl scale deployment sleep --replicas 1
deployment.extensions/sleep scaled
$ kubectl describe pvc myclaim
...
Capacity:      4Gi
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;PVC 的扩容再次成功了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中对持久卷进行扩容</title>
      <link>/post/k8s-1.11-resizing-pvc/</link>
      <pubDate>Sun, 15 Jul 2018 00:08:31 +0800</pubDate>
      <guid>/post/k8s-1.11-resizing-pvc/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/&#34; target=&#34;_blank&#34;&gt;Resizing Persistent Volumes using Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://www.linkedin.com/in/gnufied&#34; target=&#34;_blank&#34;&gt;Hemant Kumar&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes v1.11 中，持久卷扩容能力升级为 Beta 阶段。这个功能让用户可以轻松的通过编辑 PVC 对象的方式修改现有卷的容量。没有这一功能之前，要对卷容量进行修改，需要要和存储后端进行手工交互，或者对 PV 以及 PVC 进行删除重建操作。持久卷不支持缩容操作。&lt;/p&gt;

&lt;p&gt;v1.8 中卷扩展功能就已经进入 Alpha 阶段，v1.11 之前需要在 feature gate 中开启 &lt;code&gt;ExpandPersistentVolumes&lt;/code&gt;，以及 admission 控制器 &lt;code&gt;PersistentVolumeClaimResize&lt;/code&gt;（防止在底层存储不支持扩容的情况下对 PVC 进行扩容）。在 Kubernetes v1.11 中，这两个项目都会被缺省激活。&lt;/p&gt;

&lt;p&gt;虽然功能已经被激活，但是集群管理员还是需要进行操作，让用户能够对自己的卷进行扩容。Kubernetes 1.11 内置了对部分卷插件的扩容支持：AWS-EBS、GCE-PD、Azure Disk、Azure File、Glusterfs、Cinder、Portworx、以及 Ceph RBD。管理员确定底层存储能够支持卷扩展之后，就可以在 &lt;code&gt;StorageClass&lt;/code&gt; 对象中设置 &lt;code&gt;allowVolumeExpansion&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt; 来启用这一功能了。只有从 &lt;code&gt;StorageClass&lt;/code&gt; 中创建的 PVC 才允许使用卷扩展：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
parameters:
  type: pd-standard
provisioner: kubernetes.io/gce-pd
allowVolumeExpansion: true
reclaimPolicy: Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;任何从这一 &lt;code&gt;StorageClass&lt;/code&gt; 中创建的 PVC 都能够通过编辑的方式来申请更多空间。Kubernetes 会处理 Storage 字段的变更，据此申请空间，触发卷的扩容。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi #更新这一字段，修改 PVC 容量
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;文件系统扩展&#34;&gt;文件系统扩展&lt;/h2&gt;

&lt;p&gt;GCE-PD、AWS-EBS、Azure Disk、Cinder 以及 Ceph RBD 这些块存储卷通常需要首先进行文件系统的扩展，然后被扩展的卷的额外空间才能为 Pod 所用。引用这些卷的 Pod 重启时，Kubernetes 会自动完成这些任务。&lt;/p&gt;

&lt;p&gt;网络挂载的文件系统，例如 Glusterfs 和 Azure File，因为不需要进行文件系统扩展，因此可以在不重启 Pod 的情况下直接进行扩展。&lt;/p&gt;

&lt;p&gt;只有终止引用卷的 Pod，才会触发对应文件系统的扩展，更确切地说：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;编辑 PVC，申请更多空间。&lt;/li&gt;
&lt;li&gt;底层存储对底层卷进行了扩展之后，PV 对象就会响应这一变化，PVC 会进入 &lt;code&gt;FileSystemResizePending&lt;/code&gt; 状态。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以运行 &lt;code&gt;kubectl get pvc &amp;lt;pvc_name&amp;gt; -o yaml&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
  namespace: default
  uid: 02d4aa83-83cd-11e8-909d-42010af00004
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 14Gi
  storageClassName: standard
  volumeName: pvc-xxx
status:
  capacity:
    storage: 9G
  conditions:
  - lastProbeTime: null
    lastTransitionTime: 2018-07-11T14:51:10Z
    message: Waiting for user to (re-)start a pod to finish file system resize of
      volume on node.
    status: &amp;quot;True&amp;quot;
    type: FileSystemResizePending
  phase: Bound
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;PVC 进入 &lt;code&gt;FileSystemResizePending&lt;/code&gt; 状态，引用 PVC 的 Pod 就可以重新启动来结束文件系统在 Node 上的扩展过程了。可以通过删除和重建 Pod 的方式进行重启，也可以通过对 Deployment 的伸缩来完成这一过程。&lt;/li&gt;
&lt;li&gt;文件系统的扩展操作完成之后，PVC 会自动更新，设置为新的容量。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;文件系统扩展过程中遇到任何错误，都会在 Pod 中以 Event 的形式表现出来。&lt;/p&gt;

&lt;h2 id=&#34;在线文件系统扩展&#34;&gt;在线文件系统扩展&lt;/h2&gt;

&lt;p&gt;Kubernetes v1.11 还引入了一个 Alpha 功能，叫做在线文件系统扩展。这个功能可以对一个正在被 Pod 使用的卷进行文件系统的扩展。这个功能还处于 Alpha 阶段，因此需要通过 Feature gate 启用 &lt;code&gt;ExpandInUsePersistentVolumes&lt;/code&gt;。目前支持的有 GCE-PD、AWS-EBS、Cinder 以及 Ceph RBD。当激活这个功能后，引用被扩展的卷的 Pod 无需重启。文件系统会随着卷扩展的步骤进行扩展。文件系统的扩展只有在 Pod 引用被扩展的卷的时候才会发生，所以如果没有 Pod 引用这个卷，那么就不会进行文件系统扩展。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的几种存储</title>
      <link>/post/storage-in-kubernetes/</link>
      <pubDate>Sat, 12 Aug 2017 00:07:43 +0800</pubDate>
      <guid>/post/storage-in-kubernetes/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;参考：&lt;code&gt;https://kubernetes.io/docs/concepts/storage/volumes/&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;一个运行中的容器，缺省情况下，对文件系统的写入，都是发生在其分层文件系统的可写层的，一旦容器运行结束，所有写入都会被丢弃。因此需要对持久化支持。&lt;/p&gt;

&lt;p&gt;Kubernetes 中通过 Volume 的方式提供对存储的支持。下面对一些常见的存储概念进行一点简要的说明。&lt;/p&gt;

&lt;h2 id=&#34;emptydir&#34;&gt;EmptyDir&lt;/h2&gt;

&lt;p&gt;顾名思义，&lt;code&gt;EmptyDir&lt;/code&gt;是一个空目录，他的生命周期和所属的 Pod 是完全一致的，可能读者会奇怪，那还要他做什么？&lt;code&gt;EmptyDir&lt;/code&gt;的用处是，可以在同一 Pod 内的不同容器之间共享工作过程中产生的文件。&lt;/p&gt;

&lt;p&gt;缺省情况下，EmptyDir 是使用主机磁盘进行存储的，也可以设置&lt;code&gt;emptyDir.medium&lt;/code&gt; 字段的值为&lt;code&gt;Memory&lt;/code&gt;，来提高运行速度，但是这种设置，对该卷的占用会消耗容器的内存份额。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: gcr.io/google_containers/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;hostpath&#34;&gt;HostPath&lt;/h2&gt;

&lt;p&gt;这种会把宿主机上的指定卷加载到容器之中，当然，如果 Pod 发生跨主机的重建，其内容就难保证了。&lt;/p&gt;

&lt;p&gt;这种卷一般和&lt;code&gt;DaemonSet&lt;/code&gt;搭配使用，用来操作主机文件，例如进行日志采集的 FLK 中的 FluentD 就采用这种方式，加载主机的容器日志目录，达到收集本主机所有日志的目的。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: gcr.io/google_containers/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;nfs-glusterfs-cephfs-aws-gce-等等&#34;&gt;NFS/GlusterFS/CephFS/AWS/GCE 等等&lt;/h2&gt;

&lt;p&gt;作为一个容器集群，支持网络存储自然是重中之重了，Kubernetes 支持为数众多的云提供商和网络存储方案。&lt;/p&gt;

&lt;p&gt;各种支持的方式不尽相同，例如 GlusterFS 需要创建 Endpoint，Ceph/NFS 之流就没这么麻烦了。&lt;/p&gt;

&lt;p&gt;各种个性配置可移步参考文档。&lt;/p&gt;

&lt;h2 id=&#34;configmap-和-secret&#34;&gt;ConfigMap 和 Secret&lt;/h2&gt;

&lt;p&gt;镜像使用的过程中，经常需要利用配置文件、启动脚本等方式来影响容器的运行方式，如果仅有少量配置，我们可以使用环境变量的方式来进行配置。然而对于一些较为复杂的配置，例如 Apache 之类，就很难用这种方式进行控制了。另外一些敏感信息暴露在 YAML 中也是不合适的。&lt;/p&gt;

&lt;p&gt;ConfigMap 和 Secret 除了使用文件方式进行应用之外，还有其他的应用方式；这里仅就文件方式做一点说明。&lt;/p&gt;

&lt;p&gt;例如下面的 ConfigMap，将一个存储在 ConfigMap 中的配置目录加载到卷中。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: [ &amp;quot;/bin/sh&amp;quot;, &amp;quot;-c&amp;quot;, &amp;quot;ls /etc/config/&amp;quot; ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        # Provide the name of the ConfigMap containing the files you want
        # to add to the container
        name: special-config
  restartPolicy: Never
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注意，这里的 ConfigMap 会映射为一个目录，ConfigMap 的 Key 就是文件名，每个 Value 就是文件内容，比如下面命令用一个目录创建一个 ConfigMap：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create configmap \
game-config \
--from-file=docs/user-guide/configmap/kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;hr /&gt;

&lt;p&gt;创建一个 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create secret generic \
db-user-pass --from-file=./username.txt \
--from-file=./password.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 Volume 加载 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: mypod
  namespace: myns
spec:
  containers:
    - name: mypod
      image: redis
      volumeMounts:
        - name: foo
          mountPath: /etc/foo
          readOnly: true
  volumes:
    - name: foo
      secret:
        secretName: mysecret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 Secret 和 ConfigMap 的创建和使用是很相似的。在 RBAC 中，Secret 和 ConfigMap 可以进行分别赋权，以此限定操作人员的可见、可控权限。&lt;/p&gt;

&lt;h2 id=&#34;pv-pvc&#34;&gt;PV &amp;amp; PVC&lt;/h2&gt;

&lt;p&gt;PersistentVolume 和 PersistentVolumeClaim 提供了对存储支持的抽象，也提供了基础设施和应用之间的分界，管理员创建一系列的 PV 提供存储，然后为应用提供 PVC，应用程序仅需要加载一个 PVC，就可以进行访问。&lt;/p&gt;

&lt;p&gt;而 1.5 之后又提供了 PV 的动态供应。可以不经 PV 步骤直接创建 PVC。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;参考：&lt;code&gt;http://blog.fleeto.us/translation/dynamic-provisioning-and-storage-classes-kubernetes-0&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>编写易移植的 PVC</title>
      <link>/post/portable-pvc/</link>
      <pubDate>Fri, 31 Mar 2017 06:33:10 +0800</pubDate>
      <guid>/post/portable-pvc/</guid>
      <description>&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/#writing-portable-configuration&#34; target=&#34;_blank&#34;&gt;Writing Portable Configuration&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果你在编写配置模板或者是一个可能在很多不同集群下运行的配置，要在其中包含持久存储，我们提供一些建议：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在配置包含 PVC 对象（和 Deployments、COnfigMap 等并列）。&lt;/li&gt;
&lt;li&gt;考虑到可能的权限限制，建议不要包含 PV 对象&lt;/li&gt;
&lt;li&gt;编写模板的话，建议给用户指定 Storage Class 的选项：

&lt;ul&gt;
&lt;li&gt;如果用户提供了一个 Storage Class，并且集群版本在 1.4 以上，把这个 Storage Class Name 放入 PVC 的 &lt;code&gt;volume.beta.kubernetes.io/storage-class&lt;/code&gt; 标注之中，如果集群中启用了指定的 Storage Class，PVC 就会成功匹配到相应资源。&lt;/li&gt;
&lt;li&gt;如果用户没有提供 Storage Class，或者集群版本为 1.3，那么就在 PVC 中添加 &lt;code&gt;volume.alpha.kubernetes.io/storage-class: default&lt;/code&gt; 标注。&lt;/li&gt;
&lt;li&gt;PV 会自动在集群中为用户提供平台指定的缺省 PV。&lt;/li&gt;
&lt;li&gt;虽然字面上是 alpha，但实际上提供的是 beta 级别的支持。&lt;/li&gt;
&lt;li&gt;不要使用&lt;code&gt;volume.beta.kubernetes.io/storage-class&lt;/code&gt;，任何值包括空字符串，都会阻止 DefaultStorageClass 控制的运行。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;如果 PVC 创建一段时间之后，还没能绑定，需要把这一信息呈现给用户，因为这可能是因为这个集群没有动态存储供给的支持（这种情况下，需要创建符合 PVC 要求的 PV），或者个集群没有存储系统（这种情况下就无法运行指定的 PVC 了）。&lt;/li&gt;
&lt;li&gt;未来我们希望让多数集群具备 DefaultStorageClass，并有提供某种可用的存储。然而，让一种 Storage Class 适应所有的集群是很难的，所以还是不建议直接在 PVC 中设置缺省的 Storage Class。Alpha 标注会在未来失效，但那时候未进行设置的 storageClass 字段就会实现期望的功能了（1.6）。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的 StorageClass 和动态卷供给</title>
      <link>/post/dynamic-provision-for-storage-volumes-in-kubernetes/</link>
      <pubDate>Thu, 13 Oct 2016 02:36:21 +0800</pubDate>
      <guid>/post/dynamic-provision-for-storage-volumes-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;hhttps://kubernetes.io/blog/2016/10/dynamic-provisioning-and-storage-in-kubernetes&#34; target=&#34;_blank&#34;&gt;Dynamic Provisioning and Storage Classes in Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;存储是容器运行环境的重要一环，Kubernetes 提供了一些用于存储管理的基础能力。动态卷供给是一个 Kubernetes 独有的功能，这一功能允许按需创建存储卷。在没有这种能力之前，集群管理员需要打电话给他们的云或者存储提供者来创建新的存储卷，成功以后再创建 &lt;code&gt;PersistentVolume&lt;/code&gt; 对象，才能够在 Kubernetes 中使用。动态卷供给能力让管理员不必进行预先创建存储卷，而是随用户需求进行创建。这一特性在 1.2 版本中处于 α 阶段，在 &lt;a href=&#34;http://blog.kubernetes.io/2016/09/kubernetes-1.4-making-it-easy-to-run-on-kuberentes-anywhere.html&#34; target=&#34;_blank&#34;&gt;版本 1.4&lt;/a&gt; 中提升为 β。这一版本提高了动态卷的弹性和可用性。&lt;/p&gt;

&lt;h2 id=&#34;新特性&#34;&gt;新特性&lt;/h2&gt;

&lt;p&gt;Alpha 版本的动态卷，一个集群同时只能允许单独的、被硬编码的提供者。也就是说，如果 Kubernetes 要提供动态卷的时候，即使集群中可以使用多个存储系统，Kubernetes 也只会使用同一个存储卷插件。存储提供者的选型是基于云环境类型决定的 —— AWS 的 EBS，Google Cloud 的 Persistent Disk 或者是 OpenStack 的 Cinder，以及 vSphere 的 vSphere Volume。另外只有容量参数可以配置。这就意味着，即使有其他参数可用，所有的动态卷除了尺寸大小，其他都是一样的。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;因为只有容量是大家都有的吧。。。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;虽说这一功能的 Alpha 版本实用性有限，这毕竟是迈出了一步，有助于确定今后的发展方向。&lt;/p&gt;

&lt;p&gt;Kubernetes 1.4 中中加入了一个 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses&#34; target=&#34;_blank&#34;&gt;新的 API 对象&lt;/a&gt; &lt;code&gt;StorageClass&lt;/code&gt;，可以定义多个 &lt;code&gt;StorageClass&lt;/code&gt; 对象，并可以分别指定存储插件、设置参数，用于提供不同的存储卷。这样的设计让集群管理员能够在同一个集群内，定义和提供不同类型的、不同参数的卷（相同或者不同的存储系统）。这样的设计还确保了最终用户在无需了解太多的情况下，有能力选择不同的存储选项。&lt;/p&gt;

&lt;h2 id=&#34;如何使用&#34;&gt;如何使用&lt;/h2&gt;

&lt;p&gt;下面是一个例子，管理员提供了两种存储，用户可以选择其中一个使用。细节可以查看 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses&#34; target=&#34;_blank&#34;&gt;手册&lt;/a&gt; 以及 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/tree/release-1.4/examples/experimental/persistent-volume-provisioning&#34; target=&#34;_blank&#34;&gt;示例&lt;/a&gt; 文档。&lt;/p&gt;

&lt;h3 id=&#34;管理员配置&#34;&gt;管理员配置&lt;/h3&gt;

&lt;p&gt;集群管理员定义并发布了两个 &lt;code&gt;StorageClass&lt;/code&gt; 对象&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: slow
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这一段创建了一个名为 &amp;ldquo;slow&amp;rdquo; 的 &lt;code&gt;StorageClass&lt;/code&gt;，用于提供标准的持久存储。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: fast
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这一段创建了一个名为 &amp;ldquo;fast&amp;rdquo; 的 &lt;code&gt;StorageClass&lt;/code&gt;，用于提供类似 SSD 的持久存储。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;用户请求&#34;&gt;用户请求&lt;/h3&gt;

&lt;p&gt;用户在 &lt;code&gt;PersistentVolumeClaim&lt;/code&gt; 中可以包含一个 &lt;code&gt;StorageClass&lt;/code&gt; 申请动态提供存储。这一任务需要使用 &lt;code&gt;volume.beta.kubernetes.io/storage-class&lt;/code&gt; 注解来完成。这一注解的值必须符合管理员配置的 &lt;code&gt;StorageClass&lt;/code&gt; 名称。&lt;/p&gt;

&lt;p&gt;要选择 &amp;ldquo;fast&amp;rdquo; 存储类，用户需要创建如下的 PVC：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt; {
  &amp;quot;kind&amp;quot;: &amp;quot;PersistentVolumeClaim&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;claim1&amp;quot;,
    &amp;quot;annotations&amp;quot;: {
        &amp;quot;volume.beta.kubernetes.io/storage-class&amp;quot;: &amp;quot;fast&amp;quot;
    }
  },
  &amp;quot;spec&amp;quot;: {
    &amp;quot;accessModes&amp;quot;: [
      &amp;quot;ReadWriteOnce&amp;quot;
    ],
    &amp;quot;resources&amp;quot;: {
      &amp;quot;requests&amp;quot;: {
        &amp;quot;storage&amp;quot;: &amp;quot;30Gi&amp;quot;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上述报文会提供一个等效于 SSD 的持久盘，当这个 PVC 被删除，这个卷也随之销毁。&lt;/p&gt;

&lt;h3 id=&#34;缺省行为&#34;&gt;缺省行为&lt;/h3&gt;

&lt;p&gt;所有的 PVC 都可以在不使用 &lt;code&gt;StorageClass&lt;/code&gt; 注解的情况下，直接使用某个动态存储。把一个 &lt;code&gt;StorageClass&lt;/code&gt; 对象标记为 &amp;ldquo;default&amp;rdquo; 就可以了。&lt;code&gt;StorageClass&lt;/code&gt; 用注解 &lt;code&gt;storageclass.beta.kubernetes.io/is-default-class&lt;/code&gt; 就可以成为缺省存储。&lt;/p&gt;

&lt;p&gt;有了缺省的 &lt;code&gt;StorageClass&lt;/code&gt;，用户创建 PVC 就不用 storage-class 的注解了，1.4 中新加入的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/30900&#34; target=&#34;_blank&#34;&gt;DefaultStorageClass&lt;/a&gt; 准入控制器会自动把这个标注指向缺省存储类。&lt;/p&gt;

&lt;h3 id=&#34;我还能使用-alpha-版本么&#34;&gt;我还能使用 Alpha 版本么？&lt;/h3&gt;

&lt;p&gt;Kubernetes 1.4 兼容 alpha 版本的动态卷特性，让用户能够平滑过渡到 beta 版本。用 &lt;code&gt;volume.alpha.kubernetes.io/storage-class&lt;/code&gt; 注解来标注 alpha 版本。&lt;/p&gt;

&lt;p&gt;在未来版本中将会弃用 Alpha 版本。&lt;/p&gt;

&lt;h3 id=&#34;下一步&#34;&gt;下一步？&lt;/h3&gt;

&lt;p&gt;动态卷功能会持续发展，下面是一些要点。&lt;/p&gt;

&lt;h4 id=&#34;标准云支持&#34;&gt;标准云支持&lt;/h4&gt;

&lt;p&gt;如果 Kubernetes 集群部署在云服务商，我们 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/31617/files&#34; target=&#34;_blank&#34;&gt;考虑&lt;/a&gt; 自动使用云的本地存储系统创建一个动态卷供给者。例如在 AWS 上的标准部署会得到一个 EBS 的动态卷供给，而 Google Cloud 的部署则会提供一个 GCE PD 动态卷供应者。我们还在讨论是否应该把这种卷作为缺省卷。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中使用 Gluster FS</title>
      <link>/post/glusterfs-in-kubernetes/</link>
      <pubDate>Wed, 01 Jun 2016 22:57:51 +0800</pubDate>
      <guid>/post/glusterfs-in-kubernetes/</guid>
      <description>

&lt;p&gt;以 RC 形式运行在 Kubernetes 集群中的 Pod，会因为 Scale 等需要在不同的 Node 之间发生迁移，因此需要有独立于 Node 文件系统的共享存储服务，同时这一存储服务也应该符合集群的运行需要，简单的 NFS 不管是效率上还是可靠性上，都是不具备这一能力的。这里以 &lt;a href=&#34;https://www.gluster.org/&#34; target=&#34;_blank&#34;&gt;Gluster FS&lt;/a&gt;  作为存储引擎，为容器集群提供云存储服务。&lt;/p&gt;

&lt;p&gt;K8S 的存储卷使用稍有点古怪，Gluster FS 的使用，需要首先定义一个 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/services/&#34; target=&#34;_blank&#34;&gt;Endpoint + Service&lt;/a&gt; 形式的代理，来定义 Gluster FS 集群，然后就可以通过&lt;a href=&#34;http://blog.fleeto.us/translation/persistent-volumes&#34; target=&#34;_blank&#34;&gt;持久卷&lt;/a&gt;或者用 Pod 直接加载了。&lt;/p&gt;

&lt;h2 id=&#34;定义-service&#34;&gt;定义 Service&lt;/h2&gt;

&lt;p&gt;首先用一个 YML 文件来定义 Endpoint 和 Service：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
kind: List
apiVersion: v1
items:
- kind: Endpoints
  apiVersion: v1
  metadata:
    name: service_name
  subsets:
  - addresses:
    - ip: 12.34.56.78
    ports:
    - port: 111
- kind: Service
  apiVersion: v1
  metadata:
    name: service_name
  spec:
    ports:
      - port: 111
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Port 可随意填写&lt;/li&gt;
&lt;li&gt;Service Name 需要一致，这个值将会用到后面的引用中&lt;/li&gt;
&lt;li&gt;ip：Gluster FS 的 IP 地址&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;定义文件生成结束后，利用 &lt;code&gt;kubectl create -f xx.yaml&lt;/code&gt; 的方式加载到集群之中。可以用 &lt;code&gt;kubectl get svc,endpoints&lt;/code&gt; 来验证结果。&lt;/p&gt;

&lt;p&gt;接下来有两种加载方式可以选择：持久卷和 Pod 直接加载。&lt;/p&gt;

&lt;h2 id=&#34;pod-直接加载&#34;&gt;Pod 直接加载&lt;/h2&gt;

&lt;p&gt;可以在 Pod 中直接定义一个 Gluster FS 格式的卷来进行加载：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;spec:
containers:
- name: nginx-docker-images
  image: nginx:latest
  volumeMounts:
    - mountPath: /glusterfs
      name: test-volume
volumes:
  - name: test-volume
    glusterfs:
      endpoints: glusterfs-cluster
      path: gv0
      readOnly: false
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;endpoints: 这里指定的就是上一届中定义的服务名称&lt;/li&gt;
&lt;li&gt;path: gluster fs 中的卷名称&lt;/li&gt;
&lt;li&gt;readOnly: 是否只读加载&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;持久卷加载&#34;&gt;持久卷加载&lt;/h2&gt;

&lt;p&gt;首先定义一个持久卷：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolume
apiVersion: v1
metadata:
  name: gluster-volumen-gv01
spec:
  capacity:
    storage: 1Mi
  accessModes:
    - ReadWriteMany
  glusterfs:
    endpoints: glusterfs-svc
    path: gv0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;内容同上面的 Pod 卷定义大同小异，具体参数可以参考持久卷的相关文档。&lt;/p&gt;

&lt;p&gt;然后定义一个 PVC&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim2m
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Mi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后，在 Pod 中利用 PVC 来进行卷加载&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;volumes:
  - name: test-volume
    persistentVolumeClaim:
      # 上面定义的 PVC 名称
      claimName: myclaim1m
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的 Persistent Volumes</title>
      <link>/post/pv-in-kubernetes/</link>
      <pubDate>Wed, 01 Jun 2016 09:30:18 +0800</pubDate>
      <guid>/post/pv-in-kubernetes/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;经过一番实验，证明，这东西除了抽象，没啥鸟用，直接挂 Volume 应该是目前最佳选择。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&#34;持久卷-persistentvolumes&#34;&gt;持久卷 PersistentVolumes&lt;/h1&gt;

&lt;p&gt;本文描述了 Kubernetes 中的 &lt;strong&gt;PersistentVolumes&lt;/strong&gt;。要求读者有对卷 (volumes) 所有了解。&lt;/p&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;存储管理跟计算管理是两个不同的问题。&lt;strong&gt;PersistentVolume&lt;/strong&gt; 子系统，对存储的供应和使用做了抽象，以 API 形式提供给管理员和用户使用。要完成这一任务，我们引入了两个新的 API 资源：&lt;strong&gt;PersistentVolume（持久卷）&lt;/strong&gt; 和 &lt;strong&gt;PersistentVolumeClaim（持久卷申请）&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;PersistentVolume（PV）是集群之中的一块网络存储。跟 Node 一样，也是集群的资源。PV 跟 Volume (卷) 类似，不过会有独立于 Pod 的生命周期。这一 API 对象包含了存储的实现细节，例如 NFS、iSCSI 或者其他的云提供商的存储系统。&lt;/p&gt;

&lt;p&gt;PersistentVolumeClaim (PVC) 是用户的一个请求。他跟 Pod 类似。Pod 消费 Node 的资源，PVCs 消费 PV 的资源。Pod 能够申请特定的资源（CPU 和 内存）；Claim 能够请求特定的尺寸和访问模式（例如可以加载一个读写，以及多个只读实例）&lt;/p&gt;

&lt;h2 id=&#34;pv-和-pvc-的生命周期&#34;&gt;PV 和 PVC 的生命周期&lt;/h2&gt;

&lt;p&gt;PV 是集群的资源。PVC 是对这一资源的请求，也是对资源的所有权的检验。PV 和 PVC 之间的互动遵循如下的生命周期。&lt;/p&gt;

&lt;h3 id=&#34;供应&#34;&gt;供应&lt;/h3&gt;

&lt;p&gt;集群管理员会创建一系列的 PV。这些 PV 包含了为集群用户提供的真实存储资源。他们可利用 Kubernetes API 来消费。&lt;/p&gt;

&lt;h3 id=&#34;绑定&#34;&gt;绑定&lt;/h3&gt;

&lt;p&gt;用户创建一个包含了容量和访问模式的持久卷申请。Master 会监听 PVC 的产生，并尝试根据请求内容查找匹配的 PV，并把 PV 和 PVC 进行绑定。用户能够获取满足需要的资源，并且在使用过程中可能超出请求数量。&lt;/p&gt;

&lt;p&gt;如果找不到合适的卷，这一申请就会持续处于非绑定状态，一直到出现合适的 PV。例如一个集群准备了很多的 50G 大小的持久卷，（虽然总量足够）也是无法响应 100G 的申请的，除非把 100G 的 PV 加入集群。&lt;/p&gt;

&lt;h3 id=&#34;使用&#34;&gt;使用&lt;/h3&gt;

&lt;p&gt;Pod 把申请作为卷来使用。集群会通过 PVC 查找绑定的 PV，并 Mount 给 Pod。对于支持多种访问方式的卷，用户在使用 PVC 作为卷的时候，可以指定需要的访问方式。&lt;/p&gt;

&lt;p&gt;一旦用户拥有了一个已经绑定的 PVC，被绑定的 PV 就归该用户所有了。用户的 Pods 能够通过在 Pod 的卷中包含的 PVC 来访问他们占有的 PV。&lt;/p&gt;

&lt;h3 id=&#34;释放&#34;&gt;释放&lt;/h3&gt;

&lt;p&gt;当用户完成对卷的使用时，就可以利用 API 删除 PVC 对象了，而且他还可以重新申请。删除 PVC 后，对应的卷被视为 “被释放”，但是这时还不能给其他的 PVC 使用。之前的 PVC 数据还保存在卷中，要根据策略来进行后续处理。&lt;/p&gt;

&lt;h3 id=&#34;回收&#34;&gt;回收&lt;/h3&gt;

&lt;p&gt;PV 的回收策略向集群阐述了在 PVC 释放卷的时候，应如何进行后续工作。目前可以采用三种策略：保留，回收或者删除。保留策略允许重新申请这一资源。在持久卷能够支持的情况下，删除策略会同时删除持久卷以及 AWS EBS/GCE PD 或者 Cinder 卷中的存储内容。如果插件能够支持，回收策略会执行基础的擦除操作（&lt;code&gt;rm -rf /thevolume/*&lt;/code&gt;），这一卷就能被重新申请了。&lt;/p&gt;

&lt;h2 id=&#34;持久卷的类型&#34;&gt;持久卷的类型&lt;/h2&gt;

&lt;p&gt;持久卷是以插件方式实现的，目前支持如下插件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GCEPersistentDisk&lt;/li&gt;
&lt;li&gt;AWSElasticBlockStore&lt;/li&gt;
&lt;li&gt;NFS&lt;/li&gt;
&lt;li&gt;iSCSI&lt;/li&gt;
&lt;li&gt;RBD (Ceph Block Device)&lt;/li&gt;
&lt;li&gt;Glusterfs&lt;/li&gt;
&lt;li&gt;HostPath (单节点测试使用)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;持久卷&#34;&gt;持久卷&lt;/h2&gt;

&lt;p&gt;每个 PV 包含一个 spec 以及 status ，用于描述该卷的规格和状态。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  apiVersion: v1
  kind: PersistentVolume
  metadata:
    name: pv0003
  spec:
    capacity:
      storage: 5Gi
    accessModes:
      - ReadWriteOnce
    persistentVolumeReclaimPolicy: Recycle
    nfs:
      path: /tmp
      server: 172.17.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;capacity-容量&#34;&gt;Capacity（容量）&lt;/h3&gt;

&lt;p&gt;一般来说，PV 会指定存储容量。这里需要使用 PV 的 &lt;strong&gt;capcity&lt;/strong&gt; 属性。参见 Kubernetes 的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/design/resources.md&#34; target=&#34;_blank&#34;&gt;Resource Model&lt;/a&gt; 一文，来获取这一属性的计量单位 (Mi/Gi&amp;hellip;.)。&lt;/p&gt;

&lt;p&gt;目前存储大小是唯一一个能够被申请的指标，今后会加入更多属性，例如 IOPS，吞吐能力等。&lt;/p&gt;

&lt;h3 id=&#34;access-modes-访问模式&#34;&gt;Access Modes（访问模式）&lt;/h3&gt;

&lt;p&gt;只要资源提供者支持，持久卷能够被用任何方式加载到主机上。每种存储都会有不同的能力，每个 PV 的访问模式也会被设置成为该卷所支持的特定模式。例如 NFS 能够支持多个读写客户端，但是某个 NFS PV 可能会在服务器上以只读方式使用。每个 PV 都有自己的一系列的访问模式，这些访问模式取决于 PV 的能力。&lt;/p&gt;

&lt;p&gt;访问模式的可选范围如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ReadWriteOnce&lt;/strong&gt;：该卷能够以读写模式被加载到一个节点上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ReadOnlyMany&lt;/strong&gt;：该卷能够以只读模式加载到多个节点上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ReadWriteMany&lt;/strong&gt;：该卷能够以读写模式被多个节点同时加载。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在 CLI 下，访问模式缩写为：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RWO&lt;/strong&gt;：ReadWriteOnce&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ROX&lt;/strong&gt;：ReadOnlyMany&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RWX&lt;/strong&gt;：ReadWriteMany&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;重要！一个卷不论支持多少种访问模式，同时只能以一种访问模式加载。例如一个 GCEPersistentDisk 既能支持 ReadWriteOnce ，也能支持 ReadOnlyMany。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;recycling-policy-回收策略&#34;&gt;Recycling Policy（回收策略）&lt;/h3&gt;

&lt;p&gt;当前的回收策略可选值包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retain - 人工重新申请&lt;/li&gt;
&lt;li&gt;Recycle - 基础擦除（“&lt;code&gt;rm -rf /thevolume/*&lt;/code&gt;”）&lt;/li&gt;
&lt;li&gt;Delete - 相关的存储资产例如 AWS EBS，GCE PD 或者 OpenStack Cinder 卷一并删除。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;目前，只有 NFS 和 HostPath 支持 Recycle 策略，AWS EBS、GCE PD 以及 Cinder 卷支持 Delete 策略（*其他的都是 Retain 是吧。。*）。&lt;/p&gt;

&lt;h3 id=&#34;阶段-phase&#34;&gt;阶段（Phase）&lt;/h3&gt;

&lt;p&gt;一个卷会处于如下阶段之一：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Available&lt;/strong&gt;：可用资源，尚未被绑定到 PVC 上&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bound&lt;/strong&gt;：该卷已经被绑定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Released&lt;/strong&gt;：PVC 已经被删除，但该资源尚未被集群回收&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failed&lt;/strong&gt;：该卷的自动回收过程失败。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CLI 会显示绑定到该 PV 的 PVC。&lt;/p&gt;

&lt;h2 id=&#34;persistentvolumeclaims-持久卷申请&#34;&gt;PersistentVolumeClaims（持久卷申请）&lt;/h2&gt;

&lt;p&gt;每个 PVC 包含一个 spec 以及 status，用以表达其规格和状态。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;访问模式&#34;&gt;访问模式&lt;/h3&gt;

&lt;p&gt;PVC 使用跟 PV 一致的访问模式。&lt;/p&gt;

&lt;h3 id=&#34;资源&#34;&gt;资源&lt;/h3&gt;

&lt;p&gt;PVC 跟 Pod 一样可以请求特定数量的资源。在这里的请求内容就是存储（storage）。&lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/design/resources.md&#34; target=&#34;_blank&#34;&gt;Resource Model&lt;/a&gt; 文中提到的内容对 PV 和 PVC 同样适用。&lt;/p&gt;

&lt;h2 id=&#34;pvc-卷&#34;&gt;PVC 卷&lt;/h2&gt;

&lt;p&gt;Pod 能够借助 PVC 来访问存储。PVC 必须跟 Pod 处于同一个命名空间。集群找到 Pod 命名空间中的 PVC，然后利用 PVC 获取到背后的 PV。这个卷就会被加载到主机上，让 Pod 可以使用。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: dockerfile/nginx
      volumeMounts:
      - mountPath: &amp;quot;/var/www/html&amp;quot;
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
  </channel>
</rss>
