Rasa框架配合Kubernetes中StatefulSet完成训练数据的更新操作

在之前的博客《部署Rasa框架到Kubernetes集群并分配GPU资源》中,我们成功在k8s集群中部署了rasa框架。这一节我们来看看如何结合statefulset对这个部署进行改进。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

文章目录

    • 改进方向
    • 实际操作
      • 创建pv
      • 创建statefulset
      • 更新模型
      • 外部访问
      • python客户端实现
    • 总结

改进方向

之前我们通过Deployment的方式去部署的Rasa框架,但是这样有个很明显的问题,除非对Deployment更换image,不然很难将训练的模型更新到框架中,每次测试都要生成新的image,很不方便。于是准备

  • 在本地弄一个目录,和容器的/app目录进行一个volume映射,直接更新本地目录就达到了更新容器模型的目的,便于快速测试
  • 而且因为Rasa不支持热更新模型(也许是我没发现怎么热更新),每次更新完模型需要重新起一下容器,这个过程需要确保新模型不会被删除

综合以上两点需求,发现Statefulset可以圆满完成任务。

实际操作

以下的yaml文件都放在了我的Github仓库。

创建pv

因为我这个集群只有一台GPU的node被设置来分配pod,本来想试试能不能直接在node上搞一个目录类似volume一样做为pv,后来发现这种操作已经被官方文档给否决

HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)

于是老老实实按照之前使用的NFS方法,单独搞了台机器弄了三个目录过来

root@control-plane-1:~# showmount -e 172.29.56.177
Export list for 172.29.56.177:
/share/nfs3 172.29.0.0/16
/share/nfs2 172.29.0.0/16
/share/nfs1 172.29.0.0/16

NFS操作可以参考《Centos7搭建NFS文件共享服务器以及客户端连接》

同时创建出3个pv来

root@control-plane-1:~# kubectl get pv
NAME           CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
test-pv-nfs1   5Gi        RWO            Retain           Available           gold                    6s
test-pv-nfs2   8Gi        RWO            Retain           Available           gold                    6s
test-pv-nfs3   10Gi       RWO            Retain           Available           gold                    6s

NFS创建PV操作可以参考《【Kubernetes 014】持久存储PersistentVolume原理以及配合StatefulSet实现有状态服务实际操作详解》

创建statefulset

创建yaml文件rasa-statefulset-noinit.yaml如下

apiVersion: v1
kind: Service
metadata:
  name: rasa-headless-service
  namespace: default
  labels:
    app: rasa-headless
spec:
  type: ClusterIP
  clusterIP: None  #<-- Careful
  selector:
    app: rasa-reception
  ports:
    - name: http
      port: 5005
      targetPort: 5005
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: rasa-reception
spec:
  selector:
    matchLabels:
      app: rasa-reception
  serviceName: rasa-headless-service
  replicas: 1
  template:
    metadata:
      labels:
        app: rasa-reception
    spec:
      containers:
        - name: mynginx
          image: myrasa:v2
          ports:
            - containerPort: 5005
              name: http
          command: ["/bin/sh","-c","rasa run --enable-api"]
          livenessProbe:
            httpGet:
              port: 5005
              path: /
            initialDelaySeconds: 1
            periodSeconds: 3
          resources:
            limits:
              aliyun.com/gpu-mem: 1
          volumeMounts:
            - name: rasa-app
              mountPath: /app
            - name: rasa-tmp
              mountPath: /tmp
      volumes:
        - name: rasa-tmp
          emptyDir: {}
  volumeClaimTemplates:
    - metadata:
        name: rasa-app
      spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: gold
        resources:
          requests:
            storage: 5Gi

根据无头服务创建statefulset以及Rasa的基本部署就不赘述了,这里主要注意两点

  • 这里去掉了原先command中的init命令,只保留了run命令。原因就是我们会直接采用我们训练好的模型,不再需要默认模型了
  • 这里同时使用了pv和volume,其中pv对应到/app数据目录,而volume对应到临时/tmp目录,可以看出两者的使用场景是完全不同的

成功创建无头服务和statefulset

root@control-plane-1:~/rasa-test# kubectl apply -f rasa-statefulset-noinit.yaml 
service/rasa-headless-service unchanged
statefulset.apps/rasa-reception created

查看发现绑定到了rasa-pv-nfs1这个pv,也就对应到NFS服务器的/share/nfs1目录

root@control-plane-1:~/rasa-test# kubectl get pvc
NAME                        STATUS   VOLUME         CAPACITY   ACCESS MODES   STORAGECLASS   AGE
rasa-app-rasa-reception-0   Bound    rasa-pv-nfs1   5Gi        RWO            gold           53s

更新模型

下面将事先训练好的数据同步到NFS服务器的/share/nfs1目录中

[root@ai-asterisk ~]# cd /share/nfs1
[root@ai-asterisk nfs1]# ll
total 40
-rw-r--r--. 1 root root 14408 Jun  4 17:59 actions.py
-rw-r--r--. 1 root root   770 Jun  4 17:59 config.yml
-rw-r--r--. 1 root root   938 Jun  4 17:59 credentials.yml
drwxr-xr-x. 2 root root    38 Jun  4 17:59 data
-rw-r--r--. 1 root root  7751 Jun  4 17:59 domain.yml
-rw-r--r--. 1 root root  1454 Jun  4 17:59 endpoints.yml
-rw-r--r--. 1 root root     0 Jun  4 17:59 __init__.py
drwxr-xr-x. 2 root root    36 Jun  4 17:59 models
drwxr-xr-x. 2 root root    67 Jun  4 17:59 __pycache__
-rw-r--r--. 1 root root  1924 Jun  4 17:59 story_graph.dot
drwxr-xr-x. 2 root root    35 Jun  4 17:59 tests

然后删除statefulset的pod,使其重启达到重新启动rasa服务器加载新模型的目的

root@control-plane-1:~/rasa-test# kubectl get pod -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP            NODE       NOMINATED NODE   READINESS GATES
curl-6bf6db5c4f-fr2xv   1/1     Running   0          16d     10.244.2.2    gpu-node   <none>           <none>
rasa-reception-0        1/1     Running   0          5m18s   10.244.2.18   gpu-node   <none>           <none>
root@control-plane-1:~/rasa-test# kubectl delete pod/rasa-reception-0
pod "rasa-reception-0" deleted
root@control-plane-1:~/rasa-test# kubectl get pod -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
curl-6bf6db5c4f-fr2xv   1/1     Running   0          16d   10.244.2.2    gpu-node   <none>           <none>
rasa-reception-0        1/1     Running   0          36s   10.244.2.19   gpu-node   <none>           <none>

可以看到pod被自动拉起,ip也变了。因为pv的数据生命周期和pod的生命周期是独立的,所以pod的重启不会影响到模型的数据。

下面试着访问下pod的5005端口

root@control-plane-1:~/rasa-test# curl 10.244.2.19:5005/status
{"model_file":"models\/20200526-132449.tar.gz","fingerprint":{"config":"99914b932bd37a50b983c5e7c90ae93b","core-config":"ad70948032b90c0e3231157217198d63","nlu-config":"82833edd77c80380860cf74a815a1707","domain":1239901306164435814,"nlg":"569c6d8fc8eb2342c0dda03e83ad88a9","messages":1335623048012146043,"stories":475775394484243566,"trained_at":1590470432.234018,"version":"1.10.0"},"num_active_training_jobs":0}

可以看到模型已经被导入了。

这样以后有了新的模型,只需要同步到NFS的目录中,然后删除pod,就能达到更新的目的了。

外部访问

为了能从外部访问,还需要给statefulset创建一个NodePort类型的服务。

创建yaml文件rasa-service.yaml如下

apiVersion: v1
kind: Service
metadata:
  name: rasa-service
spec:
  type: NodePort
  selector:
    app: rasa-reception
  ports:
    - name: http
      port: 5005
      targetPort: 5005
      nodePort: 31111

成功创建

root@control-plane-1:~/rasa-test# kubectl apply -f rasa-service.yaml 
service/rasa-service created
root@control-plane-1:~/rasa-test# kubectl get svc
NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes              ClusterIP   10.96.0.1        <none>        443/TCP          16d
rasa-headless-service   ClusterIP   None             <none>        5005/TCP         20h
rasa-service            NodePort    10.106.162.176   <none>        5005:31111/TCP   29s

31111端口能返回内容表示配置成功

Rasa框架配合Kubernetes中StatefulSet完成训练数据的更新操作_第1张图片

python客户端实现

在之前的博客《【Kubernetes 021】Python客户端自动创建和销毁Kubernetes资源详解》中我们成功用python客户端创建和删除了Deployment和Service,如果想要对这里的statefulset进行操作也类似。

因为statefulset对应着apps/v1这个API,所以去apps_v1_api.py文件中去查找对应的方法,如下

  • create_namespaced_stateful_set
  • delete_namespaced_stateful_set

之后的操作就和下面的类似,大家可以自己试下

def create_statefulset(api_instance, body):
    api_response = api_instance.create_namespaced_stateful_set(
        body=body,
        namespace='default',
    )
    print(api_response)

def delete_statefulset(api_instance, name):
    api_response = api_instance.delete_namespaced_stateful_set(
        name=name,
        namespace='default',
    )
    print(api_response)

总结

statefulset因为pod的重启不会影响到数据的保存,所以尤其适合这种需要重启pod来加载配置的情况,在测试环境是非常好用的。

当然,真正更新到正式版本,还是需要生成新的image,然后通过set或者patch的方式去更新,以便进行追踪和回滚等操作。

你可能感兴趣的:(Devops,-,Kubernetes)