阿里云GPU集群上深度学习模型部署:Kubernetes+Nginx+Flask+Tensorflow Serving(草稿)
提示: 本文是草稿,以后有时间会进一步丰富完善。
==================================================================
查看主机名:
hostname
修改主机名:
hostnamectl set-hostname kw-01
配置host,使所有节点之间可以通过hostname互访:
vim /etc/hosts
3台服务器相互设置免密登录:
创建密钥:
ssh-keygen
复制密钥:
ssh-copy-id kw-01
ssh-copy-id kws001
ssh-copy-id kws002
登录其他服务器:
ssh kws001
重复上面操作:
ssh-keygen
ssh-copy-id kw-01
ssh-copy-id kws001
ssh-copy-id kws002
更新yum:
yum update
安装依赖包:
yum install -y conntrack ipvsadm ipset jq sysstat curl iptables libseccomp wget git
下载阿里云上的repo文件,并安装必要的软件:
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum update -y
安装python和相关包(这里安装python3.6版本):
yum install python36 -y
pip3 install --upgrade pip
pip3 install ansible==2.6.18 netaddr==0.7.19 -i https://mirrors.aliyun.com/pypi/simple/
获取kubeasz代码:
git clone -b 2.2.4 https://github.com/easzlab/kubeasz.git /etc/ansible
配置集群信息:
cd /etc/ansible/
cp example/hosts.multi-node hosts
vim hosts
在部署节点验证ansible是否可以免密登录:
ansible all -m ping
安装Kubernetes(指定docker版本为19.03.5,Kubernetes版本为1.18.6):
cd /etc/ansible/tools/
chmod +x easzup
./easzup -D -d 19.03.5 -k v1.18.6
开始安装Kubernetes:
查看需要执行的脚本:
cd ..
创建证书和安装准备:
ansible-playbook 01.prepare.yml
安装etcd集群:
ansible-playbook 02.etcd.yml
安装docker服务:
ansible-playbook 03.docker.yml
安装master节点:
ansible-playbook 04.kube-master.yml
安装node节点:
ansible-playbook 05.kube-node.yml
安装集群网络:
ansible-playbook 06.network.yml
安装集群插件:
ansible-playbook 07.cluster-addon.yml
如果要删除Kubernetes:
停止容器:
ansible-playbook 92.stop.yml
删除Kubernetes:
ansible-playbook 99.clean.yml
验证安装:
验证集群版本:
kubectl version
kubectl get componentstatus
验证节点就绪(Ready)状态:
kubectl get nodes
验证集群pod状态:
kubectl get pod --all-namespaces
验证集群服务状态:
kubectl get svc --all-namespaces
查看dashboard:
kubectl get svc -n kube-system | grep kubernetes-dashboard
Kubernetes-dashboard:
阿里云安全组放行端口:
https://developer.aliyun.com/article/767026
在阿里云账号里的“NAT网关”的“控制台”设置dnat:
在“云服务器ECS”的“控制台”里,点开实例,进入“安全组”,开放dashboard的端口:
访问kubernetes-dashboard网页:
在浏览器输入https://公网ip:映射的端口号/
报错:
the certificate for this server is invalid. you might be connecting to a server that is pretending to be which could put your confidential information at risk. (NSURLErrorDomain:-1202)
用Firefox浏览器访问:
获取tocken:
kubectl -n kube-system describe secret admin-user
如果pod一直处于CrashLoopBackOff状态:
查看pod:
kubectl get pods -n kube-system
查看pod状态是否正常:
kubectl describe pod pod实例名称 -n kube-system
查看日志:
kubectl logs -f pod实例名称 -n kube-system
删除Kubernetes-dashboard:
查看pod:
kubectl get pod -n kube-system
删除dashboard的pod:
kubectl delete pod kubernetes-dashboard-64cb97877d-gqk2f -n kube-system
查看deployment:
kubectl get deploymnet -n kube-system
卸载dashboard:
kubectl delete deployment kubernetes-dashboard --namespace=kube-system
kubectl delete service kubernetes-dashboard --namespace=kube-system
kubectl delete role kubernetes-dashboard-minimal --namespace=kube-system
kubectl delete rolebinding kubernetes-dashboard-minimal --namespace=kube-system
kubectl delete sa kubernetes-dashboard --namespace=kube-system
kubectl delete secret kubernetes-dashboard-certs --namespace=kube-system
kubectl delete secret kubernetes-dashboard-csrf --namespace=kube-system
kubectl delete secret kubernetes-dashboard-key-holder --namespace=kube-system
安装Kubernetes-dashboard:
安装Nvidia显卡驱动:
在https://www.nvidia.com/Download/Find.aspx?spm=5176.smartservice_service_robot-chat.help.13.6da23cda37BiRw&lang=cn选择GPU对应驱动:
下载驱动:
cd ~
wget https://cn.download.nvidia.com/tesla/450.172.01/NVIDIA-Linux-x86_64-450.172.01.run
安装GPU驱动:
操作系统为CentOS,查询实例中是否安装kernel-devel和kernel-headers包:
rpm -qa | grep $(uname -r)
安装GPU驱动:
chmod +x NVIDIA-Linux-x86_64-450.172.01.run
sh NVIDIA-Linux-x86_64-450.172.01.run
验证驱动安装成功:
nvidia-smi
在虚拟化后的虚拟GPU上安装GRID驱动:
禁用nouveau:
vim /etc/modprobe.d/blacklist-nouv.conf
下载驱动(下面是阿里云提供的GRID驱动):
wget https://grid-9-4.oss-cn-hangzhou.aliyuncs.com/NVIDIA-Linux-x86_64-430.99-grid.run
安装驱动:
chmod +x NVIDIA-Linux-x86_64-430.99-grid.run
./NVIDIA-Linux-x86_64-430.99-grid.run
验证安装是否成功:
nvidia-smi
添加License服务器:
cd /etc/nvidia/
cp gridd.conf.template gridd.conf
vim gridd.conf
重启实例使License服务器生效:
reboot
验证License服务器安装成功:
systemctl status nvidia-gridd
验证Docker安装
docker run --rm hello-world
在所有服务器上执行以下操作,安装nvidia-docker2:
安装必要的系统工具:
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
添加软件源信息:
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
查看可用的docker-ce包:
sudo yum repolist -v
安装containerd.io:
sudo yum install -y https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.4.3-3.1.el7.x86_64.rpm
安装最新的docker-ce:
sudo yum install docker-ce -y
确保Docker服务在运行:
sudo systemctl --now enable docker
配置阿里云镜像加速器:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://jhd4ghtv.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
安装nvidia-docker:
设置NVIDIA Container Toolkit:
设置repository和GPG key:
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.repo | sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo
安装nvidia-docker2包和依赖:
yum clean expire-cache
yum install -y nvidia-docker2
配置docker运行环境:
yum install nvidia-container-runtime
重启docker:
systemctl restart docker
拉取Nvidia/CUDA官方镜像。在一个容器内run一个命令(--rm,自动删除容器,如果容器存在;--gpus添加容器到GPU设备,all所有GPU;cuda版本跟显卡驱动匹配,11.0,在https://hub.docker.com/r/nvidia/cuda/tags查找对应镜像):
docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
将docker默认的容器runtime更改为nvidia:
vim /etc/docker/daemon.json
重启docker使更改生效:
systemctl daemon-reload
systemctl restart docker
准备模型
从本地上传训练好的模型:
scp -P 服务器端口 ~/Documents/F/Work/tmp/Code/CCB_IMG_CLASS/topic_model_new6.tgz root@服务器公网IP:/root/model/
创建目录:
mkdir topic_model
在topic_model目录下,新建目录001,将模型移到001中(001代表模型版本,tensorflow serving要求这种文件结构):
mkdir 001
解压到目录:
tar -zxf topic_model_new6.tgz -C topic_model/001
拉取tensorflow serving镜像(https://hub.docker.com/r/tensorflow/serving/tags/找TensorFlow对应的版本):
docker pull tensorflow/serving:2.4.1-gpu
查看本地镜像:
docker images
启动容器(如果是devel版本的镜像,直接进入容器-it指以交互的方式进入容器内部;bash指定进入容器shell,-p,8501暴露RESTful接口,8500暴露gRPC接口):
docker run -t --rm --gpus all -p 8500:8500 -v /root/model/topic_model/:/models/img_topic -e MODEL_NAME=img_topic tensorflow/serving:latest-gpu &
查看模型状态:
curl http://localhost:8501/v1/models/img_topic
进入容器(tf_serving_topic是容器名):
docker exec -it tf_serving_topic bash
退出容器(通过下面快捷键退出,容器仍然是UP状态。通过exit退出容器会变成EXITED状态):
ctrl+q然后ctrl+p
启动EXITED状态的容器(tf_serving_topic是容器名):
docker start tf_serving_topic
准备客户端:
安装tensorflow-serving-api:
pip3 install tensorflow-serving-api
查看模型的signature、input、output名称:
cd model/topic_model/
saved_model_cli show --dir 001/ --all
signature名为serving_default,input名为input_1,output名为dense_4和dense_5(多任务模型):
服务器上安装cuda:
chmod +x cuda_11.0.3_450.51.06_linux.run
sh cuda_11.0.3_450.51.06_linux.run
输入accept,然后勾选掉Driver:
配置环境变量:
vim /etc/profile
export PATH=/usr/local/cuda-11.0/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-11.0/lib64:$LD_LIBRARY_PATH
source /etc/profile
重启服务器:
reboot
测试程序:
cd NVIDIA_CUDA-11.0_Samples/1_Utilities/
测试:
cd deviceQuery
make
如果报错:
安装g++,再运行make:
yum install gcc-c++ -y
执行deviceQuery命令:
./deviceQuery
安装cuDNN:
选择需要的版本下载:
https://developer.nvidia.com/cudnn
解压:
tar xJf cudnn-linux-x86_64-8.4.0.27_cuda11.6-archive.tar.xz
复制文件到cuda的安装目录:
cp cudnn-linux-x86_64-8.4.0.27_cuda11.6-archive/include/cudnn.h /usr/local/cuda-11.0/include/
cp cudnn-linux-x86_64-8.4.0.27_cuda11.6-archive/lib/libcudnn* /usr/local/cuda-11.0/lib64/
给文件添加权限:
chmod +x /usr/local/cuda-11.0/include/cudnn.h
chmod +x /usr/local/cuda-11.0/lib64/libcudnn*
验证安装:
python3
import tensorflow as tf
tf.test.is_gpu_available()
报错:
查看libcusolver.so:
ls /usr/local/cuda-11.0/lib64/ | grep libcusolver
修改:
cd /usr/local/cuda-11.0/lib64/
mv libcusolver.so.10 libcusolver.so.11
验证没有问题:
创建docker镜像:
启动容器:
docker run -d --name img_topic_server tensorflow/serving:latest-gpu
拷贝模型到容器中:
docker cp /root/model/topic_model img_topic_server:/models/img_topic
提交容器,打包为镜像:
docker commit --change "ENV MODEL_NAME img_topic" img_topic_server img_topic_server:1.0
删除之前起的容器:
docker rm -f img_topic_server
运行镜像查看(使用GPU):
docker run --gpus all --name=img_topic_server -d -p 8500:8500 img_topic_server:1.0
客户端调用模型:
python3 model/client.py
如果想限制tensorflow的log输出,在客户端代码加上:
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
如果报错(原因:显存不够,因为客户端代码调用tensorflow时使显存立刻爆满,需要设置GPU config,需要的时候动态申请显存):
设置GPU config(在客户端代码client.py内修改):
physical_devices = tf.config.list_physical_devices('GPU')
tf.config.experimental.set_memory_growth(physical_devices[0], True)
删除用于测试的容器:
docker rm -f img_topic_server
构建阿里云镜像仓库:
https://help.aliyun.com/document_detail/60997.html?spm=5176.smartservice_service_robot_chat_new.0.0.866bf625OZCmIh
推送镜像到阿里云docker registry:
登录阿里云docker registry:
docker login --username=阿里云账号 registry.cn-shanghai.aliyuncs.com
标记镜像为阿里云仓库镜像:
docker tag edc0eee814bd registry.cn-shanghai.aliyuncs.com/kokosnuss4/deeplearning:1.0
推送镜像:
docker push registry.cn-shanghai.aliyuncs.com/kokosnuss4/deeplearning:1.0
Kubernetes创建secret:
kubectl create secret docker-registry aliyun-docker --docker-server=https://registry.cn-shanghai.aliyuncs.com --docker-username=阿里云账号 --docker-password=密码 --docker-email=邮箱
安装Nvidia设备插件,以便Kubernetes集群使用GPU(通过helm安装):
安装helm:
下载:
wget https://get.helm.sh/helm-v3.2.0-linux-amd64.tar.gz
解压:
tar -zxf helm-v3.2.0-linux-amd64.tar.gz
拷贝:
cp linux-amd64/helm /usr/local/bin/
查看版本:
helm version
添加chart源:
helm repo add aliyuncs https://apphub.aliyuncs.com
查看集群有哪些chart库:
helm repo list
查看某个库有哪些安装程序:
helm search repo aliyuncs | head -5
将nvidia设备添加到helm库
helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update
安装插件:
helm install --generate-name nvdp/nvidia-device-plugin
检查安装:
kubectl get pods -n kube-system | grep -i nvidia
通过yaml部署一个示例容器:
vim gpudemo-vectoradd.yaml
创建yaml文件(参考https://www.cnblogs.com/lgeng/p/11053063.html):
kubectl apply -f gpudemo-vectoradd.yaml
检查pod:
kubectl get pods | grep gpu
检查日志:
kubectl logs gpu-demo-vectoradd
查看pod:
kubectl describe pod gpu-demo-vectoradd
部署模型到Kubernetes:
拉取镜像到本地:
docker pull registry.cn-shanghai.aliyuncs.com/kokosnuss4/deeplearning:1.0
查看Kubernetes集群上的节点node:
kubectl get nodes
创建yaml文件:
vim tf_serving.yaml
写入以下内容(replicas:只设置一个节点,后续可手动扩容;imagePullPolicy:IfNotPresent表示如果本地节点的服务器上没有镜像,则从镜像仓库拉取镜像;nodeName:前面查询的node的NAME;resources:limits:nvidia.com/gpu: 1:表示使用GPU;type:LoadBalancer表示使用负载均衡):
apiVersion: apps/v1
kind: Deployment
metadata:
name: img-topic-deployment
spec:
selector:
matchLabels:
app: img-topic-server
replicas: 1
template:
metadata:
labels:
app: img-topic-server
spec:
nodeName: 172.24.177.28
restartPolicy: Always
containers:
- name: img-topic-container
image: registry.cn-shanghai.aliyuncs.com/kokosnuss4/deeplearning:1.0
imagePullPolicy: IfNotPresent
command: ["tensorflow_model_server"]
args: ["--port=8500", "--model_name=img_topic", "--model_base_path=/models/img_topic"]
resources:
limits:
nvidia.com/gpu: 1
ports:
- containerPort: 8500
---
apiVersion: v1
kind: Service
metadata:
labels:
run: img-topic-service
name: img-topic-service
spec:
ports:
- port: 8500
targetPort: 8500
selector:
app: img-topic-service
type: LoadBalancer
运行yaml文件:
kubectl create -f tf_serving.yaml
查看pod状态(Running正常运行):
kubectl get pods | grep img
如果报错“ImagePullBackOff”或“ErrImagePull“,可能是:1)本地没有镜像,且仓库也没有,或者镜像名错误;2)本地没有镜像,且连接不上仓库,或者没有通过docker login登录仓库;3)docker pull可以拉取镜像,说明Kubernetes没有权限拉取镜像;4)pod的节点是别的node,而那个node没有镜像也没有连接仓库:
kubectl describe pod img-topic-deployment-5f7ccd49b-65kq8
(发现节点是172.24.177.29,不是28这个节点,所以在yaml文件中添加nodeName指定部署的节点)
准备Python虚拟环境:
安装virtualenv:
pip3 install virtualenv
创建虚拟环境:
mkdir env
cd env/
virtualenv env_flask_tf
进入虚拟环境(查看Python,exit()退出Python):
source /root/env/env_flask_tf/bin/activate
安装需要的依赖包:
安装PIL:
pip install pillow
安装tensorflow-serving-api(会自动安装tensorflow,版本2.4.1):
pip install tensorflow-serving-api
安装flask:
pip install Flask
安装uwsgi:
pip install uwsgi
生成requirements.txt文件:
mkdir model/flask_app
cd model/flask_app/
pip freeze > requirements.txt
退出虚拟环境(依赖包清单requirements.txt已经获得):
deactivate
部署Flask客户端:
安装Flask:
pip install Flask
准备Flask服务代码(与requirements.txt和wsgi.ini同级目录):
启动Flask服务:
python3 model/flask_client.py &
通过nohup挂起(PID):
nohup python3 model/flask_client.py >> logs/flask.log &
查看服务:
netstat -tunlp
准备客户端POST发送图片到Flask服务做测试:
vim model/post_client.py
创建uwsgi.ini(processes和threads,分别是开启的进程数和线程数,而%k是魔术变量,代表CPU核数,如果我们是双核CPU,那这里的processes和threads分别为4和40,即有4个进程,每个进程有40个线程):
vim uwsgi.ini
创建Dockerfile(COPY会将requirements.txt、flask_client.py、wsgi.ini复制到容器内然后执行uwsgi命令。拉取python:3.6.8镜像后,会执行pip安装requirements.txt中的依赖包):
vim Dockerfile
此处设置—default-timeout,并加上国内镜像源(阿里云),避免安装超时报错:
通过Dockerfile构建镜像(首先会拉取python:3.6.8镜像作为base基础镜像,然后执行Dockerfile中后面的操作):
docker build -t tf_flask_app:01 -f Dockerfile .
查看镜像:
docker images
测试镜像:
docker run -p 5003:5003 tf-flask-app:01 &
查找启动的测试容器,并删除:
docker ps -a
docker rm -f 9976b446e7d0
推送镜像到镜像仓库:
docker tag 27a77bd47d97 registry.cn-shanghai.aliyuncs.com/kokosnuss4/tf_flask:1.0
docker push registry.cn-shanghai.aliyuncs.com/kokosnuss4/tf_flask:1.0
拉取镜像:
docker pull registry.cn-shanghai.aliyuncs.com/kokosnuss4/tf_flask:1.0
部署flask服务:
创建pod的命名空间namespace:
kubectl create ns flask-app-extions-stage
创建flask服务部署的yaml文件:
vim flask_app.yaml
部署flask服务:
kubectl create -f flask_app.yaml
准备Nginx镜像:
创建nginx.conf文件(参考https://javajgs.com/archives/91105 。"include"路径一定要正确;"uwsgi_pass"的值必须与"uwsgi.ini"配置文件中的"socket"一致,且二者的端口务必和"listen"端口不一样):
cd ..
mkdir nginx
cd nginx/
vim nginx.conf
创建nginx的Dockerfile:
vim Dockerfile
构建nginx的镜像:
docker build -t nginx_server:1.0 -f Dockerfile .
docker images
验证nginx容器:
docker run -p 80:80 nginx_server:1.0 &
推送镜像到仓库:
docker tag 2297f6d1ba35 registry.cn-shanghai.aliyuncs.com/kokosnuss4/nginx_server:1.0
docker push registry.cn-shanghai.aliyuncs.com/kokosnuss4/nginx_server:1.0
拉取镜像:
docker pull registry.cn-shanghai.aliyuncs.com/kokosnuss4/nginx_server:1.0
部署flask服务的nginx:
创建yaml:
vim flask-nginx.yaml
部署nginx:
kubectl create -f flask-nginx.yaml
搭建Ingress:
K8S暴露服务的方法有3种:1)ClusterIP:集群内可访问,但外部不可访问;2)NodePort:通过NodeIP:NodePort方式可以在集群内访问,结合EIP或者云服务VPC负载均衡也可在集群外访问,但开放NodePort一方面不安全,另一方面随着应用的增多不方便管理;3)LoadBalancer:某些云服务提供商会直接提供LoadBalancer模式,将服务对接到负载均衡,其原理是基于kubernetes的controller做二次开发,并集成到K8S集群,使得集群可以与云服务SDK交互。NodePort的方式,目前已有的解决方案是基于Ingress的几款工具,如Ingress-Nginx、Traefik-Ingress。Ingress-Nginx结合阿里云SLB,其原理是:通过Service的ClusterIP负载Pod,通过Ingress-Nginx监听Ingress配置,动态生成Nginx,并将Nginx暴露到23456的NodePort,通过阿里云SLB监听所有节点的23456端口。
创建yaml:
vim ingress-nginx.yaml
部署ingress-nginx:
kubectl create -f ingress-nginx.yaml
创建转发规则:
根据实际情况写转发规则了,当前我们的目的是通过它定义某个域名的请求过来之后转发到集群中指定的Service,即上面部署的 flask-app-nginx(flask-nginx.yaml)。
创建yaml:
vim ingress-flask.yaml
创建规则:
kubectl create -f ingress-flask.yaml