实战,在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
看看官网的解释:
这个local的provisioner并不支持动态创建哈,看来是用错了provisioner,k8s官方OOB的提供了多个类型的provisioner,所谓provisioner定义了应该怎么创建PV:
可以看到有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/