提交 53d2393f 编写于 作者: S ShiningZhang

update doc

上级 d9e0a9d7
# 如何编译 PaddleServing
## 总体概述
编译 Paddle Serving 一共分以下几步
- 编译环境准备:根据模型和运行环境的需要,选择最合适的镜像
- 下载代码库:下载 Serving 代码库,按需要执行初始化操作
- 环境变量准备:根据运行环境的需要,确定 Python 各个环境变量,如GPU环境还需要确定 Cuda,Cudnn,TensorRT 等环境变量。
- 正式编译: 编译 `paddle-serving-server`, `paddle-serving-client`, `paddle-serving-app` 相关 whl 包
- 安装相关 whl 包:安装编译出的三个 whl 包,并设置 SERVING_BIN 环境变量
此外,针对某些 C++ 二次开发场景,我们也提供了 OPENCV 的联编方案。
## 编译环境准备
| 组件 | 版本要求 |
| :--------------------------: | :-------------------------------: |
| OS | Ubuntu16 and 18/CentOS 7 |
| gcc | 5.4.0(Cuda 10.1) and 8.2.0 |
| gcc-c++ | 5.4.0(Cuda 10.1) and 8.2.0 |
| cmake | 3.2.0 and later |
| Python | 3.6.0 and later |
| Go | 1.17.2 and later |
| git | 2.17.1 and later |
| glibc-static | 2.17 |
| openssl-devel | 1.0.2k |
| bzip2-devel | 1.0.6 and later |
| python-devel / python3-devel | 3.6.0 and later |
| sqlite-devel | 3.7.17 and later |
| patchelf | 0.9 |
| libXext | 1.3.3 |
| libSM | 1.2.2 |
| libXrender | 0.9.10 |
推荐使用 Docker 编译,我们已经为您准备好了 Paddle Serving 编译环境并配置好了上述编译依赖,详见[镜像环境](Docker_Images_CN.md)
我们提供了五个环境的开发镜像,分别是 CPU, CUDA10.1+CUDNN7, CUDA10.2+CUDNN7,CUDA10.2+CUDNN8, CUDA11.2+CUDNN8。我们提供了 Serving 开发镜像涵盖以上环境。
| 环境 | Serving 开发镜像Tag | 操作系统
| :--------------------------: | :-------------------------------: | :-------------: |
| CPU | 0.8.0-devel | Ubuntu 16.04 |
| CUDA10.1 + CUDNN7 | 0.8.0-cuda10.1-cudnn7-devel | Ubuntu 16.04 |
| CUDA10.2 + CUDNN7 | 0.8.0-cuda10.2-cudnn7-devel | Ubuntu 16.04 |
| CUDA10.2 + CUDNN8 | 0.8.0-cuda10.2-cudnn8-devel | Ubuntu 16.04 |
| CUDA11.2 + CUDNN8 | 0.8.0-cuda11.2-cudnn8-devel | Ubuntu 16.04 |
我们首先要针对自己所需的环境拉取相关镜像。上表**环境**一列下,除了 CPU,其余(Cuda**+Cudnn**)都属于 GPU 环境。
您可以使用 Serving 开发镜像。
```
docker pull registry.baidubce.com/paddlepaddle/serving:${Serving开发镜像Tag}
# 如果是 GPU 镜像
nvidia-docker run --rm -it registry.baidubce.com/paddlepaddle/serving:${Serving开发镜像Tag} bash
# 如果是 CPU 镜像
docker run --rm -it registry.baidubce.com/paddlepaddle/serving:${Serving开发镜像Tag} bash
```
## 下载代码库
```
git clone https://github.com/PaddlePaddle/Serving
cd Serving && git submodule update --init --recursive
```
## 环境变量准备
**一. 设置 PYTHON 环境变量**
请按照如下,确定好需要编译的 Python 版本,设置对应的环境变量,一共需要设置三个环境变量,分别是 `PYTHON_INCLUDE_DIR`, `PYTHON_LIBRARIES`, `PYTHON_EXECUTABLE`。以下我们以 python 3.7为例,介绍如何设置这三个环境变量。
1. 设置 `PYTHON_INCLUDE_DIR`
搜索 Python.h 所在的目录
```
find / -name Python.h
```
通常会有类似于 `**/include/python3.7/Python.h` 出现,我们只需要取它的文件夹目录就好,比如找到 `/usr/include/python3.7/Python.h`,那么我们只需要 `export PYTHON_INCLUDE_DIR=/usr/include/python3.7/` 就好。
如果没有找到。说明 1)没有安装开发版本的 Python,需重新安装 2)权限不足无法查看相关系统目录。
2. 设置 `PYTHON_LIBRARIES`
搜索 libpython3.7.so 或 libpython3.7m.so
```
find / -name libpython3.7.so
find / -name libpython3.7m.so
```
通常会有类似于 `**/lib/libpython3.7.so` 或者 `**/lib/x86_64-linux-gnu/libpython3.7.so` 出现,我们只需要取它的文件夹目录就好,比如找到 `/usr/local/lib/libpython3.7.so`,那么我们只需要 `export PYTHON_LIBRARIES=/usr/local/lib` 就好。
如果没有找到,说明 1)静态编译 Python,需要重新安装动态编译的 Python 2)全县不足无法查看相关系统目录。
3. 设置 `PYTHON_EXECUTABLE`
直接查看 python3.7 路径
```
which python3.7
```
假如结果是 `/usr/local/bin/python3.7`,那么直接设置 `export PYTHON_EXECUTABLE=/usr/local/bin/python3.7`
设置好这三个环境变量至关重要,设置完成后,我们便可以执行下列操作(以下是 Paddle Cuda 11.2 的开发镜像的 PYTHON 环境,如果是其他镜像,请更改相应的 `PYTHON_INCLUDE_DIR`, `PYTHON_LIBRARIES`, `PYTHON_EXECUTABLE`)。
```
# 请自行修改至自身路径
export PYTHON_INCLUDE_DIR=/usr/local/include/python3.7m/
export PYTHON_LIBRARIES=/usr/local/lib/x86_64-linux-gnu/libpython3.7m.so
export PYTHON_EXECUTABLE=/usr/local/bin/python3.7
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
python3.7 -m pip install -r python/requirements.txt
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.15.2
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v1.15.2
go install github.com/golang/protobuf/protoc-gen-go@v1.4.3
go install google.golang.org/grpc@v1.33.0
go env -w GO111MODULE=auto
```
如果您是 GPU 用户需要额外设置 `CUDA_PATH`, `CUDNN_LIBRARY`, `CUDA_CUDART_LIBRARY``TENSORRT_LIBRARY_PATH`
```
export CUDA_PATH='/usr/local/cuda'
export CUDNN_LIBRARY='/usr/local/cuda/lib64/'
export CUDA_CUDART_LIBRARY="/usr/local/cuda/lib64/"
export TENSORRT_LIBRARY_PATH="/usr/"
```
环境变量的含义如下表所示。
| cmake 环境变量 | 含义 | GPU 环境注意事项 | Docker 环境是否需要 |
|-----------------------|-------------------------------------|-------------------------------|--------------------|
| CUDA_TOOLKIT_ROOT_DIR | cuda 安装路径,通常为 /usr/local/cuda | 全部 GPU 环境都需要 | 否(/usr/local/cuda) |
| CUDNN_LIBRARY | libcudnn.so.* 所在目录,通常为 /usr/local/cuda/lib64/ | 全部 GPU 环境都需要 | 否(/usr/local/cuda/lib64/) |
| CUDA_CUDART_LIBRARY | libcudart.so.* 所在目录,通常为 /usr/local/cuda/lib64/ | 全部 GPU 环境都需要 | 否(/usr/local/cuda/lib64/) |
| TENSORRT_ROOT | libnvinfer.so.* 所在目录的上一级目录,取决于 TensorRT 安装目录 | 全部 GPU 环境都需要 | 否(/usr) |
## 正式编译
我们一共需要编译三个目标,分别是 `paddle-serving-server`, `paddle-serving-client`, `paddle-serving-app`,其中 `paddle-serving-server` 需要区分 CPU 或者 GPU 版本。
**一. 编译 paddle-serving-server**
如果是 CPU 版本请运行,
```
mkdir build_server
cd build_server
cmake -DPYTHON_INCLUDE_DIR=$PYTHON_INCLUDE_DIR \
-DPYTHON_LIBRARIES=$PYTHON_LIBRARIES \
-DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \
-DSERVER=ON \
-DWITH_GPU=OFF ..
make -j20
cd ..
```
如果是 GPU 版本,请运行,
```
mkdir build_server
cd build_server
cmake -DPYTHON_INCLUDE_DIR=$PYTHON_INCLUDE_DIR \
-DPYTHON_LIBRARIES=$PYTHON_LIBRARIES \
-DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \
-DCUDA_TOOLKIT_ROOT_DIR=${CUDA_PATH} \
-DCUDNN_LIBRARY=${CUDNN_LIBRARY} \
-DCUDA_CUDART_LIBRARY=${CUDA_CUDART_LIBRARY} \
-DTENSORRT_ROOT=${TENSORRT_LIBRARY_PATH} \
-DSERVER=ON \
-DWITH_GPU=ON ..
make -j20
cd ..
```
**二. 编译 paddle-serving-client**
接下来,我们继续编译 client,这个包的编译命令在所有平台通用,不区分 CPU 和 GPU 的版本。
```
# 编译 paddle-serving-client
mkdir build_client
cd build_client
cmake -DPYTHON_INCLUDE_DIR=$PYTHON_INCLUDE_DIR \
-DPYTHON_LIBRARIES=$PYTHON_LIBRARIES \
-DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \
-DCLIENT=ON ..
make -j10
cd ..
```
**三. 编译 paddle-serving-app**
```
# 编译 paddle-serving-app
mkdir build_app
cd build_app
cmake -DPYTHON_INCLUDE_DIR=$PYTHON_INCLUDE_DIR \
-DPYTHON_LIBRARIES=$PYTHON_LIBRARIES \
-DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \
-DAPP=ON ..
make -j10
cd ..
```
## 安装相关 whl 包
```
pip3.7 install -r build_server/python/dist/*.whl
pip3.7 install -r build_client/python/dist/*.whl
pip3.7 install -r build_app/python/dist/*.whl
export SERVING_BIN=${PWD}/build_server/core/general-server/serving
```
注意到最后一行 `export SERVING_BIN`,运行 python 端 Server 时,会检查 `SERVING_BIN` 环境变量,如果想使用自己编译的二进制文件,请将设置该环境变量为对应二进制文件的路径,通常是 `export SERVING_BIN=${BUILD_DIR}/core/general-server/serving`
其中 BUILD_DIR 为 `build_server` 的绝对路径。
可以 cd build_server 路径下,执行 `export SERVING_BIN=${PWD}/core/general-server/serving`
## 开启 WITH_OPENCV 选项编译 C++ Server
**注意:** 只有当您需要对 Paddle Serving C++ 部分进行二次开发,且新增的代码依赖于 OpenCV 库时,您才需要这样做。
编译 Serving C++ Server 部分,开启 WITH_OPENCV 选项时,需要已安装的 OpenCV 库,若尚未安装,可参考本文档后面的说明编译安装 OpenCV 库。
以开启 WITH_OPENCV 选项,编译 CPU 版本 Paddle Inference Library 为例,在上述编译命令基础上,加入 `DOPENCV_DIR=${OPENCV_DIR}``DWITH_OPENCV=ON` 选项。
```
OPENCV_DIR=your_opencv_dir #`your_opencv_dir` 为 opencv 库的安装路径。
mkdir build_server && cd build_server
cmake -DPYTHON_INCLUDE_DIR=$PYTHON_INCLUDE_DIR/ \
-DPYTHON_LIBRARIES=$PYTHON_LIBRARIES \
-DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \
-DOPENCV_DIR=${OPENCV_DIR} \
-DWITH_OPENCV=ON \
-DSERVER=ON ..
make -j10
```
**注意:** 编译成功后,需要设置 `SERVING_BIN` 路径。
## 附:CMake 选项说明
| 编译选项 | 说明 | 默认 |
| :--------------: | :----------------------------------------: | :--: |
| WITH_AVX | Compile Paddle Serving with AVX intrinsics | OFF |
| WITH_MKL | Compile Paddle Serving with MKL support | OFF |
| WITH_GPU | Compile Paddle Serving with NVIDIA GPU | OFF |
| WITH_TRT | Compile Paddle Serving with TensorRT | OFF |
| WITH_OPENCV | Compile Paddle Serving with OPENCV | OFF |
| CUDNN_LIBRARY | Define CuDNN library and header path | |
| CUDA_TOOLKIT_ROOT_DIR | Define CUDA PATH | |
| TENSORRT_ROOT | Define TensorRT PATH | |
| CLIENT | Compile Paddle Serving Client | OFF |
| SERVER | Compile Paddle Serving Server | OFF |
| APP | Compile Paddle Serving App package | OFF |
| PACK | Compile for whl | OFF |
## 附:编译安装 OpenCV 库
**注意:** 只有当您需要在 C++ 代码中引入 OpenCV 库时,您才需要这样做。
* 首先需要从 OpenCV 官网上下载在 Linux 环境下源码编译的包,以 OpenCV3.4.7 为例,下载命令如下。
```
wget https://github.com/opencv/opencv/archive/3.4.7.tar.gz
tar -xf 3.4.7.tar.gz
```
最终可以在当前目录下看到 `opencv-3.4.7/` 的文件夹。
* 编译 OpenCV,设置 OpenCV 源码路径(`root_path`)以及安装路径(`install_path`)。进入 OpenCV 源码路径下,按照下面的方式进行编译。
```shell
root_path=your_opencv_root_path
install_path=${root_path}/opencv3
rm -rf build
mkdir build
cd build
cmake .. \
-DCMAKE_INSTALL_PREFIX=${install_path} \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DWITH_IPP=OFF \
-DBUILD_IPP_IW=OFF \
-DWITH_LAPACK=OFF \
-DWITH_EIGEN=OFF \
-DCMAKE_INSTALL_LIBDIR=lib64 \
-DWITH_ZLIB=ON \
-DBUILD_ZLIB=ON \
-DWITH_JPEG=ON \
-DBUILD_JPEG=ON \
-DWITH_PNG=ON \
-DBUILD_PNG=ON \
-DWITH_TIFF=ON \
-DBUILD_TIFF=ON
make -j
make install
```
其中 `root_path` 为下载的 OpenCV 源码路径,`install_path` 为 OpenCV 的安装路径,`make install` 完成之后,会在该文件夹下生成 OpenCV 头文件和库文件,用于引用 OpenCV 库的代码的编译。
最终在安装路径下的文件结构如下所示。
```
opencv3/
|-- bin
|-- include
|-- lib
|-- lib64
|-- share
```
# 在Paddle Serving使用安全网关
## 简介
在之前的服务部署示例中,我们都从开发的角度切入,然而,在现实的生产环境中,仅仅提供一个能够预测的远端服务接口还远远不够。我们仍然要考虑以下不足。
- 这个服务还不能以网关的形式提供,访问路径难以管理。
- 这个服务接口不够安全,需要做相应的鉴权。
- 这个服务接口不能够控制流量,无法合理利用资源。
本文档的作用,就以 Uci 房价预测服务为例,来介绍如何强化预测服务 API 接口安全。API 网关作为流量入口,对接口进行统一管理。但 API 网关可以提供流量加密和鉴权等安全功能。
## Docker部署
可以使用 docker-compose 来部署安全网关。这个示例的步骤就是 [部署本地Serving容器] - [部署本地安全网关] - [通过安全网关访问Serving]
**注明:** docker-compose 与 docker 不一样,它依赖于 docker,一次可以部署多个 docker 容器,可以类比于本地版的 kubenetes,docker-compose 的教程请参考[docker-compose安装](https://docs.docker.com/compose/install/)
```shell
docker-compose -f tools/auth/auth-serving-docker.yaml up -d
```
可以通过 `docker ps` 来查看启动的容器。
```shell
3035cf445029 pantsel/konga:next "/app/start.sh" About an hour ago Up About an hour 0.0.0.0:8005->1337/tcp anquan_konga_1
7ce3abee550c registry.baidubce.com/serving_gateway/kong:paddle "/docker-entrypoint.…" About an hour ago Up About an hour (healthy) 0.0.0.0:8000->8000/tcp, 127.0.0.1:8001->8001/tcp, 0.0.0.0:8443->8443/tcp, 127.0.0.1:8444->8444/tcp anquan_kong_1
25810fd79a27 postgres:9.6 "docker-entrypoint.s…" About an hour ago Up About an hour (healthy) 5432/tcp anquan_db_1
ee59a3dd4806 registry.baidubce.com/serving_dev/serving-runtime:cpu-py36 "bash -c ' wget --no…" About an hour ago Up About an hour 0.0.0.0:9393->9393/tcp anquan_serving_1
665fd8a34e15 redis:latest "docker-entrypoint.s…" About an hour ago Up About an hour 0.0.0.0:6379->6379/tcp anquan_redis_1
```
其中我们之前 serving 容器 以 9393 端口暴露,KONG 网关的端口是 8443, KONG 的 Web 控制台的端口是 8001。接下来我们在浏览器访问 `https://$IP_ADDR:8001`, 其中 IP_ADDR 就是宿主机的 IP 。
<img src="../images/kong-dashboard.png">
可以看到在注册结束后,登陆,看到了 DASHBOARD,我们先看 SERVICES,可以看到 `serving_service`,这意味着我们端口在 9393 的 Serving 服务已经在 KONG 当中被注册。
<img src="../images/kong-services.png">
<img src="../images/kong-routes.png">
然后在 ROUTES 中,我们可以看到 serving 被链接到了 `/serving-uci`
最后我们点击 CONSUMERS - default_user - Credentials - API KEYS ,我们可以看到 `Api Keys` 下看到很多 key
<img src="../images/kong-api_keys.png">
接下来可以通过 curl 访问
```shell
curl -H "Content-Type:application/json" -H "X-INSTANCE-ID:kong_ins" -H "apikey:hP6v25BQVS5CcS1nqKpxdrFkUxze9JWD" -X POST -d '{"feed":[{"x": [0.0137, -0.1136, 0.2553, -0.0692, 0.0582, -0.0727, -0.1583, -0.0584, 0.6283, 0.4919, 0.1856, 0.0795, -0.0332]}], "fetch":["price"]}' https://127.0.0.1:8443/serving-uci/uci/prediction -k
```
与之前的 Serving HTTP 服务相比,有以下区别。
- 使用 https 加密访问,而不是 http
- 使用 serving_uci 的路径映射到网关
- 在 header 处增加了 `X-INSTANCE-ID``apikey`
## K8S 部署
同样,我们也提供了 K8S 集群部署 Serving 安全网关的方式。
**一. 启动 Serving 服务**
我们仍然以 [Uci房价预测](../examples/C++/fit_a_line/)服务作为例子,这里省略了镜像制作的过程,详情可以参考 [在 Kubernetes 集群上部署Paddle Serving](./Run_On_Kubernetes_CN.md)
在这里我们直接执行
```
kubectl apply -f tools/auth/serving-demo-k8s.yaml
```
**二. 安装 KONG (一个集群只需要执行一次就可以)**
接下来我们执行 KONG Ingress 的安装
```
kubectl apply -f tools/auth/kong-install.yaml
```
输出是
```
namespace/kong created
customresourcedefinition.apiextensions.k8s.io/kongclusterplugins.configuration.konghq.com created
customresourcedefinition.apiextensions.k8s.io/kongconsumers.configuration.konghq.com created
customresourcedefinition.apiextensions.k8s.io/kongingresses.configuration.konghq.com created
customresourcedefinition.apiextensions.k8s.io/kongplugins.configuration.konghq.com created
customresourcedefinition.apiextensions.k8s.io/tcpingresses.configuration.konghq.com created
serviceaccount/kong-serviceaccount created
clusterrole.rbac.authorization.k8s.io/kong-ingress-clusterrole created
clusterrolebinding.rbac.authorization.k8s.io/kong-ingress-clusterrole-nisa-binding created
service/kong-proxy created
service/kong-validation-webhook created
deployment.apps/ingress-kong created
```
我们可以输入
```
kubectl get service --all-namespaces
```
会显示
```
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default uci ClusterIP 172.16.87.89 <none> 9393/TCP 7d7h
kong kong-proxy NodePort 172.16.23.91 <none> 80:8175/TCP,443:8521/TCP 102m
kong kong-validation-webhook ClusterIP 172.16.114.93 <none> 443/TCP 102m
```
**三. 创建 Ingress 资源**
接下来需要做 Serving 服务和 KONG 的链接
```
kubectl apply -f tools/auth/kong-ingress-k8s.yaml
```
我们也给出 yaml 文件内容
```
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: demo
annotations:
konghq.com/strip-path: "true"
kubernetes.io/ingress.class: kong
spec:
rules:
- http:
paths:
- path: /foo
backend:
serviceName: {{SERVING_SERVICE_NAME}}
servicePort: {{SERVICE_PORT}}
```
其中 serviceName 就是 uci,servicePort 就是 9393,如果是别的服务就需要改这两个字段,最终会映射到`/foo`下。
在这一步之后,我们就可以
```
curl -H "Content-Type:application/json" -X POST -d '{"feed":[{"x": [0.0137, -0.1136, 0.2553, -0.0692, 0.0582, -0.0727, -0.1583, -0.0584, 0.6283, 0.4919, 0.1856, 0.0795, -0.0332]}], "fetch":["price"]}' http://$IP:$PORT/foo/uci/prediction
```
**四. 增加安全网关限制**
之前的接口没有鉴权功能,无法验证用户身份合法性,现在我们添加一个 key-auth 插件
执行
```
kubectl apply -f key-auth-k8s.yaml
```
其中,yaml 文内容为
```
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: key-auth
plugin: key-auth
```
现在,需要创建 secret,key 值为用户指定,需要在请求时携带 Header 中 apikey 字段
执行
```
kubectl create secret generic default-apikey \
--from-literal=kongCredType=key-auth \
--from-literal=key=ZGVmYXVsdC1hcGlrZXkK
```
在这里,我们的 key 是随意制定了一串 `ZGVmYXVsdC1hcGlrZXkK`,实际情况也可以
创建一个用户(consumer)标识访问者身份,并未该用户绑定 apikey。
执行
```
kubectl apply -f kong-consumer-k8s.yaml
```
其中,yaml 文内容为
```
apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: default
annotations:
kubernetes.io/ingress.class: kong
username: default
credentials:
- default-apikey
```
如果我们这时还想再像上一步一样的做 curl 访问,会发现已经无法访问,此时已经具备了安全能力,我们需要对应的 key。
**五. 通过 API Key 访问服务**
执行
```
curl -H "Content-Type:application/json" -H "apikey:ZGVmYXVsdC1hcGlrZXkK" -X POST -d '{"feed":[{"x": [0.0137, -0.1136, 0.2553, -0.0692, 0.0582, -0.0727, -0.1583, -0.0584, 0.6283, 0.4919, 0.1856, 0.0795, -0.0332]}], "fetch":["price"]}' https://$IP:$PORT/foo/uci/prediction -k
```
我们可以看到 apikey 已经加入到了 curl 请求的 header 当中。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册