027.Kubernetes掌握Service-Ingress使用
一 Ingress简介
1.1 Ingress
通常Service的表现形式为IP:Port,即工作在TCP/IP层。
对于基于HTTP的服务来说,不同的URL地址经常对应到不同的后端服务(RS)或者虚拟服务器( Virtual Host),这些应用层的转发机制仅通过Kubernetes的Service机制是无法实现的。
从Kubernetes 1.1版本开始新增Ingress资源对象,用于将不同URL的访问请求转发到后端不同的Service,以实现HTTP层的业务路由机制。
Kubernetes使用了一个Ingress策略定义和一个具体的Ingress Controller,两者结合并实现了一个完整的Ingress负载均衡器。使用Ingress进行负载分发时,Ingress Controller基于Ingress规则将客户端请求直接转发到Service对应的后端Endpoint(Pod)上,从而跳过kube-proxy的转发功能,kube-proxy不再起作用。
简单的理解就是:ingress使用DaemonSet在每个Node上监听80,然后配合相应规则,因为Nginx外面绑定了宿主机80端口(就像 NodePort),本身又在集群内,那么向后直接转发到相应ServiceIP即可实现相应需求。ingress controller + ingress 规则 ----> services。
同时当Ingress Controller提供的是对外服务,则实际上实现的是边缘路由器的功能。
典型的HTTP层路由的架构:
如上所示:
对http://uclouda.com/api的访问将被路由到后端名为api的Service;
对http://uclouda.com/web的访问将被路由到后端名为web的Service;
对http://uclouda.com/docs的访问将被路由到后端名为docs的Service。
二 Ingress创建
为使用Ingress,需要创建Ingress Controller(带一个默认backend服务)和Ingress策略设置来共同完成。
Ingree创建通过分为:Ingress Controller吃、Ingress策略配置、客户端访问Ingress提供的服务。
2.1 创建Ingress Controller
在定义Ingress策略之前,需要先部署Ingress Controller,以实现为所有后端Service都提供一个统一的入口。
Ingress Controller需要实现基于不同HTTP URL向后转发的负载分发规则,并可以灵活设置7层负载分发策略。如果公有云服务商能够提供该类型的HTTP路由LoadBalancer,则也可设置其为Ingress Controller。在Kubernetes中,Ingress Controller将以Pod的形式运行,监控APIServer的/ingress接口后端的backend services。如果Service发生变化,则Ingress Controller应自动更新其转发规则。
示例1:该例子使用Nginx来实现一个Ingress Controller,需要实现的基本逻辑如下:
(1)监听API Server,获取全部Ingress的定义。
(2)基于Ingress的定义,生成Nginx所需的配置文件/etc/nginx/nginx.conf。
(3)执行nginx -s reload命令,重新加载nginx.conf配置文件的内容。
[root@k8smaster01 study]# vi nginx-ingress-daemonset.yaml
1 apiVersion: extensions/v1beta1 2 kind: DaemonSet 3 metadata: 4 name: nginx-ingress-lb 5 labels: 6 name: nginx-ingress-lb 7 namespace: default 8 spec: 9 template: 10 metadata: 11 labels: 12 name: nginx-ingress-lb 13 spec: 14 terminationGracePeriodSeconds: 60 15 containers: 16 - image: gcr.azk8s.cn/google_containers/nginx-ingress-controller:0.9.0-beta.2 17 name: nginx-ingress-lb 18 readinessProbe: 19 httpGet: 20 path: /healthz 21 port: 10254 22 scheme: HTTP 23 livenessProbe: 24 httpGet: 25 path: /healthz 26 port: 10254 27 scheme: HTTP 28 initialDelaySeconds: 10 29 timeoutSeconds: 1 30 ports: 31 - containerPort: 80 32 hostPort: 80 33 - containerPort: 443 34 hostPort: 443 35 env: 36 - name: POD_NAME 37 valueFrom: 38 fieldRef: 39 fieldPath: metadata.name 40 - name: POD_NAMESPACE 41 valueFrom: 42 fieldRef: 43 fieldPath: metadata.namespace 44 args: 45 - /nginx-ingress-controller 46 - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
释义:如上Nginx容器设置了hostPort,将容器应用监听的80和443端口号映射到物理机上,使得客户端应用可以通过URL地址“http://物理机IP:80”或“https://物理机IP:443”来访问该Ingress Controller。使得Nginx类似于通过NodePort映射到物理机的Service,成为代替kube-proxy的HTTP层的Load Balancer。
提示:本示例使用谷歌提供的nginx-ingress-controller镜像来创建IngressController。该Ingress Controller以daemonset的形式进行创建,在每个Node上都将启动一个Nginx服务。
[root@k8smaster01 study]# vi default-backend.yaml
1 apiVersion: extensions/v1beta1 2 kind: Deployment 3 metadata: 4 name: default-http-backend 5 labels: 6 k8s-app: default-http-backend 7 namespace: default 8 spec: 9 replicas: 1 10 template: 11 metadata: 12 labels: 13 k8s-app: default-http-backend 14 spec: 15 terminationGracePeriodSeconds: 60 16 containers: 17 - name: default-http-backend 18 image: gcr.azk8s.cn/google_containers/defaultbackend:1.0 19 livenessProbe: 20 httpGet: 21 path: /healthz 22 port: 8080 23 scheme: HTTP 24 initialDelaySeconds: 30 25 timeoutSeconds: 5 26 ports: 27 - containerPort: 8080 28 resources: 29 limits: 30 cpu: 10m 31 memory: 20Mi 32 requests: 33 cpu: 10m 34 memory: 20Mi 35 --- 36 apiVersion: v1 37 kind: Service 38 metadata: 39 name: default-http-backend 40 namespace: default 41 labels: 42 k8s-app: default-http-backend 43 spec: 44 ports: 45 - port: 80 46 targetPort: 8080 47 selector: 48 k8s-app: default-http-backend
释义:为使ingress controller正常启动,需要设置一个默认的backend,用于在客户端访问URL地址不存在时,返回一个正确的404应答。
[root@k8smaster01 study]# kubectl create -f default-backend.yaml #创建默认的backend服务
[root@k8smaster01 study]# kubectl create -f nginx-ingress-daemonset.yaml #创建ingress controller
提示:若提示forbidden: User “system:serviceaccount:default:default” cannot……,可通过以下方式解决:
kubectl create rolebinding default --clusterrole=edit --serviceaccount=default:default --namespace=default
有关edit角色的权限可通过如下方式查看:
[root@k8smaster01 study]# kubectl describe clusterrole edit
提示:将默认service account与集群角色etid相关联,该角色视图使pod能够列出资源。
[root@k8smaster01 study]# curl k8smaster01 #测试nginx-ingress所在的节点访问
default backend - 404
提示:如上404为所有服务异常的时候ingress所转发的默认404错误网页。
2.2 定义ingress策略
基于《002.Kubernetes简单入门实例》中的实验,本环境将demo通过nodeport暴露于http://172.24.8.71:30001/demo/。
同时在宿主机添加如下hosts,以便于测试ingress。
172.24.8.71 mywebsite.com
对mywebsite.com网站的访问设置Ingress策略,如下定义的ingress策略对其/demo路径的访问转发到后端webapp Service。
[root@k8smaster01 study]# vi nginx-ingress-policy.yaml
1 apiVersion: extensions/v1beta1 2 kind: Ingress 3 metadata: 4 name: mywebsite-ingress 5 spec: 6 rules: 7 - host: mywebsite.com 8 http: 9 paths: 10 - path: /demo 11 backend: 12 serviceName: myweb 13 servicePort: 8080
[root@k8smaster01 study]# kubectl get pods #查看相关Pod
1 Pod 2 NAME READY STATUS RESTARTS AGE 3 …… 4 mysql-m652j 1/1 Running 0 21m #入门实例中的mysql,用于测试 5 myweb-gnhk4 1/1 Running 0 20m 6 myweb-vzg58 1/1 Running 0 20m #入门实例中的myweb,用于测试 7 nginx-ingress-lb-6mj49 1/1 Running 0 16m 8 nginx-ingress-lb-7z74c 1/1 Running 0 16m 9 nginx-ingress-lb-9wlpd 1/1 Running 0 16m 10 nginx-ingress-lb-flgvs 1/1 Running 0 16m 11 nginx-ingress-lb-gcczc 1/1 Running 0 16m 12 nginx-ingress-lb-hcfg6 1/1 Running 0 16m #2.1中的ingress-lb
[root@k8smaster01 study]# kubectl get svc #查看相关SVC
1 SVC 2 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) 3 default-http-backend ClusterIP 10.254.236.120 <none> 80/TCP #默认的backend svc 4 …… 5 mysql ClusterIP 10.254.84.247 <none> 3306/TCP #入门实例中的mysql svc,用于测试 6 myweb NodePort 10.254.119.124 <none> 8080:30001/TCP #入门实例中的myweb svc,用于测试
[root@k8smaster01 study]# kubectl create -f nginx-ingress-policy.yaml #检查完毕后创建该ingress
[root@k8smaster01 study]# kubectl get ingresses -o wide #查看ingress
1 ingress 2 NAME HOSTS ADDRESS PORTS AGE 3 mywebsite-ingress mywebsite.com 80 9m12s
解释:如上ingress定义,说明对目标地址http://mywebsite.com/demo的访问将被转发到集群中的Service webapp即webapp:8080/demo上。更多ingress策略配置方法见2.4。
注意:在Ingress生效之前,需要先将webapp服务部署完成。同时需要注意Ingress中path的定义,需要与后端真实Service提供的path一致,否则将被转发到一个不存在的path上,引发错误。
2.3 测试访问
由于Ingress Controller容器通过hostPort将服务端口号80映射到了所有Node上,结合ingress controller和ingress规则配合,客户端可以通过任意Node访问mywebsite.com提供的服务。从而实现访问http://mywebsite.com/demo转发至myweb的service,myweb的service又具有如下两个作用:
- 将后端Pod的8080暴露于nodeport的30001(因此入门实例可以通过nodeport的30001直接访问服务);
- 后端selector选择了真正实现功能的tomcat pod。
浏览器:http://mywebsite.com/demo/
2.4 Ingress策略配置详解
为了实现灵活的负载分发策略,Ingress策略可以按多种方式进行配置,如下为几种常见的Ingress转发策略配置。
- 转发到单个后端服务上
基于这种设置,客户端到Ingress Controller的访问请求都将被转发到后端的唯一Service上,在这种情况下Ingress无须定义任何rule。通过如下所示的设置,对Ingress Controller的访问请求都将被转发到“myweb:8080”这个服务上。
1 apiVersion: extensions/v1beta1 2 kind: Ingress 3 metadata: 4 name: test-ingress 5 spec: 6 backend: 7 serviceName: myweb 8 servicePort: 8080
- 同一域名下,不同的URL路径被转发到不同的服务上
这种配置常用于一个网站通过不同的路径提供不同的服务的场景,例如/web表示访问Web页面,/api表示访问API接口,对应到后端的两个服务,通过Ingress的设置很容易就能将基于URL路径的转发规则定义出来。
通过如下所示的设置,对“mywebsite.com/web”的访问请求将被转发到“web-service:80”服务上;
对“mywebsite.com/api”的访问请求将被转发到“api-service:80”服务上。
1 apiVersion: extensions/v1beta1 2 kind: Ingress 3 metadata: 4 name: test-ingress 5 spec: 6 rules: 7 - host: mywebsite.com 8 http: 9 paths: 10 - path: /demo 11 backend: 12 serviceName: myweb 13 servicePort: 8080 14 - path: /api 15 backend: 16 serviceName: myapi 17 servicePort: 8081
- 不同的域名(虚拟主机名)被转发到不同的服务上
这种配置常用于一个网站通过不同的域名或虚拟主机名提供不同服务的场景,例如foo.bar.com域名由service1提供服务,bar.foo.com域名由
service2提供服务。
通过如下所示的设置,对“foo.bar.com”的访问请求将被转发到“service1:80”服务上;
对“bar.foo.com”的访问请求将被转发到“service2:80”服务上。
1 apiVersion: extensions/v1beta1 2 kind: Ingress 3 metadata: 4 name: test-ingress 5 spec: 6 rules: 7 - host: foo.bar.com 8 http: 9 paths: 10 - backend: 11 serviceName: service1 12 servicePort: 8080 13 - host: bar.foo.com 14 http: 15 paths: 16 - backend: 17 serviceName: service2 18 servicePort: 8080
- 不使用域名的转发规则
这种配置用于一个网站不使用域名直接提供服务的场景,此时通过任意一台运行ingress-controller的Node都能访问到后端的服务。
下面的配置为将“<ingresscontroller-ip>/demo”的访问请求转发到“webapp:8080/demo”服务上:
1 apiVersion: extensions/v1beta1 2 kind: Ingress 3 metadata: 4 name: test-ingress 5 spec: 6 rules: 7 - http: 8 paths: 9 - path: /demo 10 backend: 11 serviceName: myweb 12 servicePort: 8080
注意:使用无域名的Ingress转发规则时,将默认禁用非安全HTTP,强制启用HTTPS,只有使用HTTPS才能够访问成功。例如,当使用Nginx作为Ingress Controller时,在其配置文件/etc/nginx/nginx.conf中将会自动设置下面的规则,将全部HTTP的访问请求直接返回301错误:
1 …… 2 if (%pass_access_scheme = http) { 3 return 301 https://$best_http_host$request_uri; 4 } 5 ……
如上强制策略也可以在Ingress的定义中设置一个annotation“ingress.kubernetes.io/sslredirect=false”来关闭强制启用HTTPS的设置。
1 apiVersion: extensions/v1beta1 2 kind: Ingress 3 metadata: 4 name: test-ingress 5 annotations: 6 ingress.kubernetes.io/ssl-redirect: "false" 7 spec: 8 rules: 9 - http: 10 paths: 11 - path: /demo 12 backend: 13 serviceName: myweb 14 servicePort: 8080
如上设置后,也可通过http进行访问。
三 Ingress TLS
3.1 Ingress TLS配置步骤
Ingress同时提供HTTPS的安全访问配置,可以在Ingress中的域名进行TLS安全证书的设置。
设置的步骤如下。
- 创建自签名的密钥和SSL证书文件。
- 将证书保存到Kubernetes中的一个Secret资源对象上。
- 将该Secret对象设置到Ingress中。
根据提供服务的网站域名是一个还是多个,可以使用不同的操作完成前两步SSL证书和Secret对象的创建,在只有一个域名的情况下设置
相对简单。第3步对于这两种场景来说是相同的。
3.2 单域名TLS设置
对于只有一个域名的场景来说,可以通过OpenSSL工具直接生成密钥和证书文件,将命令行参数-subj中的/CN设置为网站域名:
- 创建自签名证书
1 [root@k8smaster01 study]# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=mywebsite.com"
提示:更多证书创建知识参考《附008.Kubernetes TLS证书介绍及创建》。
- 创建secret对象
- 方法一:kubectl create secret命令行方式
[root@k8smaster01 study]# kubectl create secret tls mywebsite-ingress-secret --key tls.key --cert tls.crt
- 方法二:编辑mywebsite-ingress-secret.yaml文件
[root@k8smaster01 study]# vi mywebsite-ingress-secret.yaml
1 apiVersion: v1 2 kind: Secret 3 metadata: 4 name: mywebsite-ingress-secret 5 namespace: default 6 type: kubernetes.io/tls 7 data: 8 tls.crt: MIIDAzCCAeugAwIBAgIJAP5FWrsNH3MMMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNVBAMMDW15d2Vic2l0ZS5jb20wHhcNMTkxMTI2MDkxODA2WhcNMjkxMTIzMDkxODA2WjAYMRYwFAYDVQQDDA1teXdlYnNpdGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwxh45nxBk3ROD1Dl6NWa+PGgQWcty3Mcu57RyNFTQ2MOOZ0l53kML9cuLau4OJqk7yvI6rKhtNP5kMNzFrdrsL5cX44wItO3XA2GtO7r7t7PrTwoDskGqQiz0yDw0Ya1TpkOFbCA9VphojXHAL77s2YUG1sZrOSUU0SL3H0v8xUnKIiUPzRCzBqGFUeR7zDuATFQbjrkHUWiPIQ9/7n4YVj35ChHhWco4kuP4RL6l5lZqegYxx2vkSgGWcmJor2d+8Qiv3t5HPrZcKrSnWdWvpZ9IPA8iT6Yl9lmk9cmoVcB9mXklbhm0fs2/S8vvyrCt7GO0PyIyQ2uGiQIBbIZ0QIDAQABo1AwTjAdBgNVHQ4EFgQU/Ji8BRdqF54yhOXkFxQgpeePUpYwHwYDVR0jBBgwFoAU/Ji8BRdqF54yhOXkFxQgpeePUpYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAeN95smuD4mG9zqAn7r1/FTt2OkTxofj7Q9x5ccoLDYJmBv8whbs0/oF0NOYwdsLJirrF0xuWksWvMFpPRJiNe/4c8nYPdPDaUsxcS2fesvvBL5Dw45J6RBIAFKNyoC9wtwtPR88oes82y7WlrgvML83bC9B5cB+HnJzPoWPArgUvPZeZxcQw3Vq088axdyOZBV27rWNKNCFWKve+TF+vt3zD0FeDOYCTY94HRfRzjKimRadrnEl4+w4j+dYMjwuy4CEFSwW7pb/AntWTcjQWBTuWyqvNTIG9+Vchz+YjRZG1P/AInkl4J/QmS1cvCN2zKVFyZS5DdlIX1wvsI93C3A== 13 tls.key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDDGHjmfEGTdE4PUOXo1Zr48aBBZy3Lcxy7ntHI0VNDYw45nSXneQwv1y4tq7g4mqTvK8jqsqG00/mQw3MWt2uwvlxfjjAi07dcDYa07uvu3s+tPCgOyQapCLPTIPDRhrVOmQ4VsID1WmGiNccAvvuzZhQbWxms5JRTRIvcfS/zFScoiJQ/NELMGoYVR5HvMO4BMVBuOuQdRaI8hD3/ufhhWPfkKEeFZyjiS4/hEvqXmVmp6BjHHa+RKAZZyYmivZ37xCK/e3kc+tlwqtKdZ1a+ln0g8DyJPpiX2WaT1yahVwH2ZeSVuGbR+zb9Ly+/KsK3sY7Q/IjJDa4aJAgFshnRAgMBAAECggEAKFAYO1gVnMh9kMCgoTnyKdqTyK/vUIjauRIaOyq1z8jaGaGVQX1LvV7zVdCT4m5iAmHDxGtg6qKagQVB9MG16PGM2NnirG+fBdBts1ljOxqjQyKZDGURkUARGGFIIaN6N2F8/ZJZM1mXrxL5qffMvscrBHQQnB8nXwVc+RSNIecx7UQ1nGjhfabfywwVRT8PpjxKN35+bbFUEKPPf4sh3hf7B7+aZZep7Y32AXUtJUH8MKXo98QJ22kZja4zZI96O/q2s10CXv13x7FHOlaNtxha5qWW7lynWm4q7dKvZ0gF2Yd59/Nry3p6oGGAkBcDTR9T11BBEYMoQYx1BYdKoQKBgQD+RRbKQq8b24zrPPU3jFANDG9IKyjMEjdxQzPUgNjEXKdwA/Ge37Byzgjkf2bWPUCJb7Gzt6PHEnYHTxP1zWc1iM0r10qRHI5+J+dFtaleJdzm/sRiIta5QVLuwy+giKFdmbwA1d4Frl93DivexFj153LRCHsHJcLWK15GwC1IXQKBgQDEbE7WaePmzoTOV2XRfrykNiEv7U7acy8gfXTbT5HRIF89GJyMpYYF8e6+S/K3/HKSP2PsWDz35K8ax0OQrk2LvAe9DxA+sjNwxnTam8oRmDG9xoPD8VvuLRivUVxzftcxxgKindZNC78F3NzPRGcKeeq2jLqqUvIJQlpoC1NwBQKBgCl0oDOXza7wC7iqtpw43zBRb69Hgh5LdgicWU3zN+RD6vSjX/h0JfOBzgdbEiwpzmTZ9hIEBcrGIsIsTWfM9l/PDwxvzHN+QWkmHlnKNXPpHmv265PIdFO958SPxCsbO5vkHbfRJqKsfFoP0G1Ae/STqK+V/2D58hsy9Or6GCftAoGBAJeVPgIaFda46aSTre/ObqYLX/Esof3Thjr8loHpFg7dfKIZrDaeRp+v5R7WXam/GGvkn6h1MBfeU4PG4010NkPwB8jPJyo7O5d8kBFkyLxrR3e9C1LboKZeBv7FOyOmb0vqE36LcCZlOjW8DGunzh03mPrn/+YRvNeIbVx94RZBAoGBAIpirteUaXDZWTUH2i0uPrBeVYMJQWZGu4BCqnAVCP/wWERY2O7tPgUSRyd5Alg0VpmU0tkmaWj7Wejbs9KYjNNvLfmleR771aIgE/cL4Lp3ijvSHaQEFPob3HKoC0LR2Cw0jH6wX1B1MT5ymIIlXYn3aAmu790HbwySwjrxkmAY
[root@k8smaster01 study]# kubectl create -f mywebsite-ingress-secret #创建TLS的secret
继续创建Ingress对象,同时在tls段引用刚刚创建好的Secret对象即可。
[root@k8smaster01 study]# vi mywebsite-ingress-tls.yaml
1 apiVersion: extensions/v1beta1 2 kind: Ingress 3 metadata: 4 name: mywebsite-ingress-tls 5 spec: 6 tls: 7 - hosts: 8 - mywebsite.com 9 secretName: mywebsite-ingress-secret 10 rules: 11 - host: mywebsite.com 12 http: 13 paths: 14 - path: /demo 15 backend: 16 serviceName: myweb 17 servicePort: 8080
[root@k8smaster01 study]# kubectl create -f mywebsite-ingress-tls.yaml
然后可以通过HTTPS来访问mywebsite.com了。
[root@k8smaster02 ~]# curl -H 'Host:mywebsite.com' -k https://172.24.8.71/demo/ #测试访问
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
浏览器访问测试:https://mywebsite.com/demo/
3.3 多域名TLS设置
如果提供服务的网站不止一个域名,例如2.4所述的第3种Ingress策略配置方式,则SSL证书需要使用额外的一个x509 v3配置文件辅助完成,在[alt_names]段中完成多个DNS域名的设置。
[root@k8smaster01 study]# vi openssl.cnf
1 [req] 2 req_extensions = v3_req 3 distinguished_name = req_distinguished_name 4 [req_distinguished_name] 5 [ v3_req ] 6 basicConstraints = CA:FALSE 7 keyUsage = nonRepudiation, digitalSignature, keyEncipherment 8 subjectAltName = @alt_names 9 [alt_names] 10 DNS.1 = mywebsite.com 11 DNS.2 = mywebsite2.com
[root@k8smaster01 study]# openssl genrsa -out ca.key 2048
[root@k8smaster01 study]# openssl req -x509 -new -nodes -key ca.key -days 3650 -out ca.crt -subj "/CN=mywebsite.com" #生成自签名CA证书
[root@k8smaster01 study]# openssl genrsa -out ingress.key 2048
[root@k8smaster01 study]# openssl req -new -key ingress.key -out ingress.csr -subj "/CN=mywebsite.com" -config openssl.cnf #基于openssl.cnf和CA证书生成ingress SSL证书
[root@k8smaster01 study]# openssl x509 -req -in ingress.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ingress.crt -days 3650 -extensions v3_req -extfile openssl.cnf
然后根据ingress.key和ingress.crt文件创建secret资源对象,同样可以通过kubectl create secret tls命令或YAML配置文件生成(参考3.2方法)。
[root@k8smaster01 study]# kubectl create secret tls mywebsite-ingress-secret --key ingress.key --cert ingress.crt
继续创建Ingress对象,同时在tls段引用刚刚创建好的Secret对象即可。
测试及访问参考3.2即可。
作者:木二
出处:http://www.cnblogs.com/itzgr/
关于作者:云计算、虚拟化,Linux,多多交流!
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接!如有其他问题,可邮件(xhy@itzgr.com)咨询。