实战,在Kubeflow中启动Jupyter notebook(续)

上篇:https://blog.****.net/pushme_pli/article/details/88524393

续上篇,上次启动了Jupyter Nodebook的server,但是在PV的建立上有点死板。记得Jupyter Nodebook登录界面是用任何用户都可以登录的对吧,但在昨天的实现中,如果你用其他用户登录,然后Spawn一个server是铁定不会成功的,原因是没有PV给你用(昨天我们是预先建立的PV)!我们捋一捋Spawn的流程:

  • 点击Spawn
  • 生成相应的名为Jupyter-${userName}的pod
  • 生成相应的名为workspace-${Username}的pvc
  • Pvc试图匹配pv
  • 成功

还记得昨天的实现,我们预先建立了default的storageclass,并且建立了PV绑定在该storageclass上对吧。

然后Jupyter生成的pvc的storageclass选项是空的,意味着该pvc会直接找default storageclass去寻找pv,而k8s中storageclasses很大程度上是为了能动态的创建pv,那为啥昨天建立的storageclass不能动态创建pv呢,贴上昨天storageclass的代码:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: local
  annotations:
    storageclass.beta.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

注意provisioner的value是  kubernetes.io/no-provisioner

看看官网的解释:

实战,在Kubeflow中启动Jupyter notebook(续)

这个local的provisioner并不支持动态创建哈,看来是用错了provisioner,k8s官方OOB的提供了多个类型的provisioner,所谓provisioner定义了应该怎么创建PV:

实战,在Kubeflow中启动Jupyter notebook(续)

可以看到有AWS的,有Azure的,当然也可以支持自定义的provisioner

我决定自己创建一个nfs的provisioner

这是depolyment:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nfs-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      containers:
        - name: nfs-provisioner
          image: quay.io/kubernetes_incubator/nfs-provisioner:v1.0.8
          ports:
            - name: nfs
              containerPort: 2049
            - name: mountd
              containerPort: 20048
            - name: rpcbind
              containerPort: 111
            - name: rpcbind-udp
              containerPort: 111
              protocol: UDP
          securityContext:
            capabilities:
              add:
                - DAC_READ_SEARCH
                - SYS_RESOURCE
          args:
            - "-provisioner=nfs-provisioner"
          env:
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: SERVICE_NAME
              value: nfs-provisioner
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: export-volume
              mountPath: /export
      volumes:
        - name: export-volume
          hostPath:
            path: /srv

要注意的是:

 args:
            - "-provisioner=nfs-provisioner"

这是在定义provisioner的名字,而storageclass会使用这个名字来指定provisioner

Service:

kind: Service
apiVersion: v1
metadata:
  name: nfs-provisioner
  labels:
    app: nfs-provisioner
spec:
  ports:
    - name: nfs
      port: 2049
    - name: mountd
      port: 20048
    - name: rpcbind
      port: 111
    - name: rpcbind-udp
      port: 111
      protocol: UDP
  selector:
    app: nfs-provisioner

这里还有一个插曲,最初,我将上面两个resource都建立在了kubeflow这个namespace下,发现报错了, 类似这样:

Failed to list >*v1.PersistentVolume: persistentvolumes is forbidden: User "system:serviceaccount:kube->system:random-scheduler" cannot list resource "persistentvolumes" in API group "" at the cluster scope

后来发现这错跟serviceaccounts有关,如果在pod中没有定义spec.serviceAccountName, 那意味着该pod的serviceAccountName是default,按说default应该拥有足够的权限去create pv啊,忽然灵光一闪,记得这个provisioner是下namespace:kubeflow下,而serviceAccountName也应该是kubeflow这个namspace下的default,如果是在kube-systeem这个namespace下是不是就可以了呢,结果是,果然可以!

ok,provisioner搞定后,我们得重新建立default storageclass了,贴下代码:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: local
  annotations:
    storageclass.beta.kubernetes.io/is-default-class: "true"
parameters:
  mountOptions: vers=3
provisioner: nfs-provisioner
volumeBindingMode: Immediate

注意provisioner: nfs-provisioner

这样任意用户登录jupyter nodebook然后Spawn后,建立pod,建立pvc,而后pvc找default storageclass要pv,storageclass找nfs-provisioner这个provisoner建立PV,给Jupyter的pvc,运行良好,这儿样任何用户都可以在Jypyter hub上拥有自己专属的nodebook,很好。

另外还有两个插曲值得说说:

  • volumeBindingMode: WaitForFirstConsumer/Immediate(default), 一开始配置的是WaitForFirstConsumer,导致pvc一直是pending的,还以为是出错了,改为Immediate即可。
  • mountOptions: vers=3。一开始该配置是没有的,这样默认nfs是以version 2的方式mount的,这导致了一个很罕见的错:Mkdir 报错Invalid Argument,我是没见过的哈,改为version 3即可。

最后附上解决问题相关的网页:

https://kubernetes.io/docs/concepts/storage/storage-classes/

https://www.kubernetes.org.cn/4078.html

https://github.com/kubernetes/kubeadm/issues/1274

https://kubernetes.io/docs/concepts/storage/storage-classes/

https://github.com/kubernetes-incubator/external-storage/issues/447

https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/