Tag: ceph

  • An Innovator’s Guide to Kubernetes Storage Using Ceph

    Kubernetes, the awesome container orchestration tool is changing the way applications are being developed and deployed. You can specify the required resources you want and have it available without worrying about the underlying infrastructure. Kubernetes is way ahead in terms of high availability, scaling, managing your application, but storage section in the k8s is still evolving. Many storage supports are getting added and are production ready.

    People are preferring clustered applications to store the data. But, what about the non-clustered applications? Where does these applications store data to make it highly available? Considering these questions, let’s go through the Ceph storage and its integration with Kubernetes.

    What is Ceph Storage?

    Ceph is open source, software-defined storage maintained by RedHat. It’s capable of block, object, and file storage. The clusters of Ceph are designed in order to run on any hardware with the help of an algorithm called CRUSH (Controlled Replication Under Scalable Hashing). This algorithm ensures that all the data is properly distributed across the cluster and data quickly without any constraints. Replication, Thin provisioning, Snapshots are the key features of the Ceph storage.

    There are good storage solutions like Gluster, Swift but we are going with Ceph for following reasons:

    1. File, Block, and Object storage in the same wrapper.
    2. Better transfer speed and lower latency
    3. Easily accessible storage that can quickly scale up or down

    We are going to use 2 types of storage in this blog to integrate with kubernetes.

    1. Ceph-RBD
    2. CephFS

    Ceph Deployment

    Deploying highly available Ceph cluster is pretty straightforward and easy. I am assuming that you are familiar with setting up the Ceph cluster. If not then refer the official document here.

    If you check the status, you should see something like:

    # ceph -s
      cluster:
        id:     ed0bfe4e-f44c-4797-9bc6-21a988b645c7
        health: HEALTH_OK
     
      services:
        mon: 3 daemons, quorum ip-10-0-1-118,ip-10-0-1-172,ip-10-0-1-227
        mgr: ip-10-0-1-118(active), standbys: ip-10-0-1-227, ip-10-0-1-172
        mds: cephfs-1/1/1 up  {0=ip-10-0-1-118=up:active}
        osd: 3 osds: 3 up, 3 in
     
      data:
        pools:   2 pools, 160 pgs
        objects: 22  objects, 19 KiB
        usage:   3.0 GiB used, 21 GiB / 24 GiB avail
        pgs:     160 active+clean
    @velotiotech

    Here notice that my Ceph monitors IPs are 10.0.1.118, 10.0.1.227 and 10.0.1.172

    K8s Integration

    After setting up the Ceph cluster, we would consume it with Kubernetes.  I am assuming that your Kubernetes cluster is up and running. We will be using Ceph-RBD and CephFS as storage in Kubernetes.

    Ceph-RBD and Kubernetes

    We need a Ceph RBD client to achieve interaction between Kubernetes cluster and CephFS. This client is not in the official kube-controller-manager container so let’s try to create the external storage plugin for Ceph.

    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: rbd-provisioner
    rules:
      - apiGroups: [""]
        resources: ["persistentvolumes"]
        verbs: ["get", "list", "watch", "create", "delete"]
      - apiGroups: [""]
        resources: ["persistentvolumeclaims"]
        verbs: ["get", "list", "watch", "update"]
      - apiGroups: ["storage.k8s.io"]
        resources: ["storageclasses"]
        verbs: ["get", "list", "watch"]
      - apiGroups: [""]
        resources: ["events"]
        verbs: ["create", "update", "patch"]
      - apiGroups: [""]
        resources: ["services"]
        resourceNames: ["kube-dns","coredns"]
        verbs: ["list", "get"]
      - apiGroups: [""]
        resources: ["endpoints"]
        verbs: ["get", "list", "watch", "create", "update", "patch"]
    ---
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: rbd-provisioner
    subjects:
      - kind: ServiceAccount
        name: rbd-provisioner
        namespace: kube-system
    roleRef:
      kind: ClusterRole
      name: rbd-provisioner
      apiGroup: rbac.authorization.k8s.io
    ---
    apiVersion: rbac.authorization.k8s.io/v1beta1
    kind: Role
    metadata:
      name: rbd-provisioner
    rules:
    - apiGroups: [""]
      resources: ["secrets"]
      verbs: ["get"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: rbd-provisioner
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: rbd-provisioner
    subjects:
    - kind: ServiceAccount
      name: rbd-provisioner
      namespace: kube-system
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: rbd-provisioner
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: rbd-provisioner
    spec:
      replicas: 1
      strategy:
        type: Recreate
      template:
        metadata:
          labels:
            app: rbd-provisioner
        spec:
          containers:
          - name: rbd-provisioner
            image: "quay.io/external_storage/rbd-provisioner:latest"
            env:
            - name: PROVISIONER_NAME
              value: ceph.com/rbd
          serviceAccount: rbd-provisioner

    # kubectl create -n kube-system -f  Ceph-RBD-Provisioner.yaml

    • You will get output like this:
    clusterrole.rbac.authorization.k8s.io/rbd-provisioner created
    clusterrolebinding.rbac.authorization.k8s.io/rbd-provisioner created
    role.rbac.authorization.k8s.io/rbd-provisioner created
    rolebinding.rbac.authorization.k8s.io/rbd-provisioner created
    serviceaccount/rbd-provisioner created
    deployment.extensions/rbd-provisioner created

    • Check RBD volume provisioner status and wait till it comes up in running state. You would see something like following:
    [root@ip-10-0-1-226 Ceph-RBD]# kubectl get pods -l app=rbd-provisioner -n kube-system
    NAME                               READY     STATUS    RESTARTS   AGE
    rbd-provisioner-857866b5b7-vc4pr   1/1       Running   0          16s

    • Once the provisioner is up, provisioner needs the admin key for the storage provision. You can run the following command to get the admin key:
    # ceph auth get-key client.admin
    AQDyWw9dOUm/FhAA4JCA9PXkPo6+OXpOj9N2ZQ==
    
    # kubectl create secret generic ceph-secret 
        --type="kubernetes.io/rbd" 
        --from-literal=key='AQDyWw9dOUm/FhAA4JCA9PXkPo6+OXpOj9N2ZQ==' 
        --namespace=kube-system

    • Let’s create a separate Ceph pool for Kubernetes and the new client key:
    # ceph --cluster ceph osd pool create kube 1024 1024
    # ceph --cluster ceph auth get-or-create client.kube mon 'allow r' osd 'allow rwx pool=kube'

    • Get the auth token which we created in the above command and create kubernetes secret for new client secret for kube pool.
    # ceph --cluster ceph auth get-key client.kube
    AQDabg9d4MBeIBAAaOhTjqsYpsNa4X10V0qCfw==
    
    # kubectl create secret generic ceph-secret-kube 
        --type="kubernetes.io/rbd" 
        --from-literal=key=”AQDabg9d4MBeIBAAaOhTjqsYpsNa4X10V0qCfw=='' 
        --namespace=kube-system

    • Now let’s create the storage class.
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: fast-rbd
    provisioner: ceph.com/rbd
    parameters:
      monitors: 10.0.1.118:6789, 10.0.1.227:6789, 10.0.1.172:6789
      adminId: admin
      adminSecretName: ceph-secret
      adminSecretNamespace: kube-system
      pool: kube
      userId: kube
      userSecretName: ceph-secret-kube
      userSecretNamespace: kube-system
      imageFormat: "2"
      imageFeatures: layering

    # kubectl create -f Ceph-RBD-StorageClass.yaml

    • We are all set now. We can test the Ceph-RBD by creating the PVC. After creating the PVC, PV will get created automatically.  Let’s create the PVC now:
    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: testclaim
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
      storageClassName: fast-rbd

    # kubectl create -f Ceph-RBD-PVC.yaml
    
    [root@ip-10-0-1-226 Ceph-RBD]# kubectl get pvc
    NAME      STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    testclaim  Bound     pvc-c215ad98-95b3-11e9-8b5d-12e154d66096   1Gi        RWO            fast-rbd       2m

    • If you check pvc, you’ll find it shows that it’s been bounded with the pv which got created by storage class.
    • Let’s check the persistent volume
    [root@ip-10-0-1-226 Ceph-RBD]# kubectl get pv
    NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM             STORAGECLASS   REASON    AGE
    pvc-c215ad98-95b3-11e9-8b5d-12e154d66096   1Gi        RWO            Delete           Bound     default/testclaim   fast-rbd                 8m

    Till now we have seen how to use the block based storage i.e Ceph-RBD with kubernetes by creating the dynamic storage provisioner. Now let’s go through the process for setting up the storage using file system based storage i.e. CephFS.  

    CephFS and Kubernetes

    • Let’s create the provisioner and storage class for the CephFS.  Create the dedicated namespace for CephFS
    # kubectl create ns cephfs

    • Create the kubernetes secrete using the Ceph admin auth token
    # ceph auth get-key client.admin
    AQDyWw9dOUm/FhAA4JCA9PXkPo6+OXpOj9N2ZQ==
    
    # kubectl create secret generic ceph-secret-admin --from-literal=key="AQDyWw9dOUm/FhAA4JCA9PXkPo6+OXpOj9N2ZQ==" -n cephfs

    • Create the cluster role, role binding, provisioner
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: cephfs-provisioner
      namespace: cephfs
    rules:
      - apiGroups: [""]
        resources: ["persistentvolumes"]
        verbs: ["get", "list", "watch", "create", "delete"]
      - apiGroups: [""]
        resources: ["persistentvolumeclaims"]
        verbs: ["get", "list", "watch", "update"]
      - apiGroups: ["storage.k8s.io"]
        resources: ["storageclasses"]
        verbs: ["get", "list", "watch"]
      - apiGroups: [""]
        resources: ["events"]
        verbs: ["create", "update", "patch"]
      - apiGroups: [""]
        resources: ["services"]
        resourceNames: ["kube-dns","coredns"]
        verbs: ["list", "get"]
    ---
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: cephfs-provisioner
    subjects:
      - kind: ServiceAccount
        name: cephfs-provisioner
        namespace: cephfs
    roleRef:
      kind: ClusterRole
      name: cephfs-provisioner
      apiGroup: rbac.authorization.k8s.io
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: cephfs-provisioner
      namespace: cephfs
    rules:
      - apiGroups: [""]
        resources: ["secrets"]
        verbs: ["create", "get", "delete"]
      - apiGroups: [""]
        resources: ["endpoints"]
        verbs: ["get", "list", "watch", "create", "update", "patch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: cephfs-provisioner
      namespace: cephfs
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: cephfs-provisioner
    subjects:
    - kind: ServiceAccount
      name: cephfs-provisioner
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: cephfs-provisioner
      namespace: cephfs
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: cephfs-provisioner
      namespace: cephfs
    spec:
      replicas: 1
      strategy:
        type: Recreate
      template:
        metadata:
          labels:
            app: cephfs-provisioner
        spec:
          containers:
          - name: cephfs-provisioner
            image: "quay.io/external_storage/cephfs-provisioner:latest"
            env:
            - name: PROVISIONER_NAME
              value: ceph.com/cephfs
            - name: PROVISIONER_SECRET_NAMESPACE
              value: cephfs
            command:
            - "/usr/local/bin/cephfs-provisioner"
            args:
            - "-id=cephfs-provisioner-1"
          serviceAccount: cephfs-provisioner

    # kubectl create -n cephfs -f Ceph-FS-Provisioner.yaml

    • Create the storage class
    kind: StorageClass
    apiVersion: storage.k8s.io/v1
    metadata:
      name: cephfs
    provisioner: ceph.com/cephfs
    parameters:
        monitors: 10.0.1.226:6789, 10.0.1.205:6789, 10.0.1.82:6789
        adminId: admin
        adminSecretName: ceph-secret-admin
        adminSecretNamespace: cephfs
        claimRoot: /pvc-volumes

    # kubectl create -f Ceph-FS-StorageClass.yaml

    • We are all set now. CephFS provisioner is created. Let’s wait till it gets into running state.
    # kubectl get pods -n cephfs
    NAME                                 READY     STATUS    RESTARTS   AGE
    cephfs-provisioner-8d957f95f-s7mdq   1/1       Running   0          1m

    • Once the CephFS provider is up, try creating the persistent volume claim. In this step, storage class will take care of creating the persistent volume dynamically.
    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: claim1
    spec:
      storageClassName: cephfs
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 1Gi

    # kubectl create -f Ceph-FS-PVC.yaml

    • Let’s check the create PV and PVC
    # kubectl get pvc
    NAME      STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    claim1    Bound     pvc-a7db18a7-9641-11e9-ab86-12e154d66096   1Gi        RWX            cephfs         2m
    
    # kubectl get pv
    NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM            STORAGECLASS   REASON    AGE
    pvc-a7db18a7-9641-11e9-ab86-12e154d66096   1Gi        RWX            Delete           Bound     default/claim1   cephfs                   2m

    Conclusion

    We have seen how to integrate the Ceph storage with Kubernetes. In the integration, we covered ceph-rbd and cephfs. This approach is highly useful when your application is not clustered application and if you are looking to make it highly available.