在介绍过的Downward API提供了一种简单的方式,将pod和容器的元数据传递给在它们内部运行的进程。但这种方式其实仅仅可以暴露一个pod自身的元数据,而且只可以暴露部分元数据。某些情况下,应用需要知道其他pod的信息,甚至是集群中其他资源的信息。这种情况下DownwardAPI方式将无能为力。

  可以通过服务相关的环境变量或者DNS来获取服务和pod的信息,但如果应用需要获取其他资源的信息或者获取最新的信息,就需要直接与API服务器进行交互。

  在了解pod中的应用如何与Kubernetes API服务器交互之前,先在自己的本机上研宄一下服务器的REST endpoit,这样可以大致了解什么是API服务器。

1.探究Kubernetes REST API

  己经了解了Kubernetes不同的资源类型。但如果打算开发一个可以与 Kubernetes API交互的应用,要首先了解API。

  先尝试直接访问API服务器,可以通过运行kubectl cluster-info命令来得到服务器的URL。

$ kubectl cluster-info
Kubernetes master is running at https://192.168.99.100:8443

  因为服务器使用HTTPS协议并且需要授权,所以与服务器交互并不是一件简单的事情,可以尝试通过curl来访问它,使用curl的--insecure(或-k)选 项来跳过服务器证书检查环节,但这也不能让我们走得更远。

$ curl https://192.168.99.100:8443 -k
Unauthorized

  幸运的是,我们可以执行kubectl proxy命令,通过代理与服务器交互,而不是自行来处理验证过程。

  通过Kubectl proxy访问API服务器

  kubectl proxy命令启动了一个代理服务来接收来自你本机的HTTP连接并转发至API服务器,同时处理身份认证,所以不需要每次请求都上传认证凭证。它也可以确保我们直接与真实的API服务器交互,而不是一个中间人(通过每次验证服务器证书的方式)。

  运行代理很简单,所要做的就是运行以下命令:

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

  也无须传递其他任何参数,因为kubectl己经知晓所需的所有参数(API服务器URL、认证凭证等)。一旦启动,代理服务器就将在本地端口8001接收连接请求。

$ curl localhost:8001
{
"paths":[
"/api",
"/api/v1",

  可以看到发送请求给代理,代理接着发送请求给API服务器,然后代理将返回从服务器返回的所有信息。

  通过Kubectl proxy研究Kubernetes API

  可以继续使用curl,或者打幵浏览器并且指向http://localhost:8001,看一下当访问这个基础的URL时,API服务器会返回什么。服务器的应答是一组路径的清单,如下所示。

#代码 8.7 API 服务器的 REST endpoint 清单:http://localhost:8001
$ curl http://localhost:8001
{
"paths":[
"/api",
"/api/v1", #这里可以看到大部分的资源类型
"/apis",
"/apis/apps",
"/apis/apps/v1beta1",
"/apis/batch",
"/apis/batch/v1", #batch API组以及它的两个版本
"/apis/batch/v2alpha1",
....

  这些路径对应了创建Pod、Service这些资源时定义的API组和版本信息。

  在/api/V1对应apiVersion:这里所说的V1指的是创建的基础资源(Pod、Service、ReplicalionController等)。在Kubernetes最早期版本中提到的最基础的资源并不属于任何指定的组,原因是Kubernetes初期并没有使用API组的概念,这个概念是后期引入的。

  注意:这些没有列入API组的初始资源类型现在一般被认为归属于核心的API组。

  研究批量API组的REST endpoint

  看看Job资源API,从路径/apis/batch下的内容开始(暂时忽略版本),如下面的代码清单所示。

#代码8.8 在/apis/batch/目录下的endpoint清单http://localhost:8001/apis/batch
curl http://localhost:8001/apis/batch
{
"kind": "APIGroup",
"apiVersion": "v1",
"name": "batch",
"versions": [
{
"groupVersion": "batch/v1", #批量API组包含两个版本
"version": "v1"
},
{
"groupVersion": "batch/v1beta1",
"version": "v1beta1"
}
],
"preferredVersion": { #客户应该使用V1版本而不是V2alpha1版本
"groupVersion": "batch/v1",
"version": "v1"
}
}

  这个响应消息展示了包括可用版本、客户推荐使用版本在内的批量API组信息。 接着看一下/apis/batch/V1路径下的内容,如下面的代码清单所示。

#代码8.9 在/batch/V1中的资源类型:http://localhost:8001/apis/batch/v1
curl http://localhost:8001/apis/batch/v1
{
"kind": "APIResourceList", #这里实在batch/V1API组中的API资源清单
"apiVersion": "v1",
"groupVersion": "batch/v1",
"resources": [ #这个数据包含了这个组中所有的资源类型
{
"name": "jobs", #这里描述了已经被指定了命名空间的JOB资源
"singularName": "",
"namespaced": true,
"kind": "Job",
"verbs": [ #这里给出了资源对应可以使用的动词
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"categories": [
"all"
]
},
{
"name": "jobs/status", #资源也有一个专门的REST endpoint来修改他们的状态
"singularName": "",
"namespaced": true,
"kind": "Job",
"verbs": [ #状态可以被恢复、打补丁或者修改
"get",
"patch",
"update"
]
}
]
}

  API服务器返回了在batch/V1目录下API组中的资源类型以及REST endpoint清单。除了资源的名称和相关的类型,API服务器也包含了一些其他信息,比如资源是否被指定了命名空间、名称简写(如果有的话,对于Job来说没有)、资源对应可以使用的动词列表等。

  返回的列表描述了在API服务器中暴露的REST资源。"name":"jobs"行的信息告诉我们API包含了/apis/batch/V1/jobs的endpoint,"verbs"数组告诉我们可以通过endpoint恢复、修改以及删除Job资源。对于某些特定的资源,API服务器暴露了额外的API endpoint(例如,通过jobs/status路径可以修改Job的状态)。

  列举集群中所有的Job实例

  通过在/apis/batch/v1/jobs路径运行一个GET请求,可以获取集群中所有Job的清单,如下面的代码清单所示。

#代码8.10 JOB清单: http://localhost:8001/apis/batch/v1/jobs
curl http://localhost:8001/apis/batch/v1/jobs
{
"kind": "JobList",
"apiVersion": "batch/v1",
"metadata": {
"selfLink": "/apis/batch/v1/jobs",
"resourceVersion": "81048521"
},
"items": []
}

  如果在集群中没有部署Job资源,那么items数组将是空的。

  通过名称恢复一个指定的Job实例

  前面的endpoint返回了跨命名空间的所有Job的清单,如果想要返回指定的一个Job,需要在URL中指定它的名称和所在的命名空间。为了恢复在之前清单中的一个Job (name:my-job;namespace:dfault),需要访问路径:/apis/batch/v1/namespaces/default/jobs/my-job,如下面的代码清单所示。

#代码8.11 通过名称恢复一个指定命名空间下的资源
curl http://localhost:8001/apis/batch/v1/namespaces/default/jobs/my-job
{
"kind": "Job",
"apiVersion": "batch/v1",
"metadata": {
"name": "my-job",
"namespace": "default",
"selfLink": "/apis/batch/v1/namespaces/default/jobs/my-job",

  虽然不使用任何特定的工具,也可以访问Kubernetes REST API服务器, 但如果要全面地研究REST API并与之交互,在最后会介绍更好的方式。暂时来看,像这样使用curl进行研究,对理解一个应用如何在pod中运行并与Kubernetes交互己经足够。

2.从pod内部与API服务器进行交互

  现在来研究从一个pod内部访问它,这种情况下通常没有kubectl可用。因此,想要从pod内部与API服务器进行交互,需要关注以下三件事情:

    • 确定API服务器的位置
    • 确保是与API服务器进行交互,而不是一个冒名者
    • 通过服务器的认证,否则将不能查看任何内容以及进行任何操作

  接下来看一下交互如何实现。

  运行一个pod来尝试与API服务器进行通信

  首先需要一个pod以便从它内部发起与API服务器的交互。运行一个什么也不做的pod(在它仅有的容器内部运行一个sleep命令),然后通过kubectl exec 在容器内部运行一个脚本,接下来在脚本中使用curl尝试访问API服务器。

  因此,需要使用一个包含curl二进制的容器镜像。如果在Docker Hub中搜索, 就会发现tutum/curl镜像,可以使用这个镜像(也可以使用任何包含curl二进制的已有镜像或者自己打包的镜像hpod的定义如下面的代码清单所示。

#代码8.12 用来尝试与API服务器通信的pod:curl.yaml
apiVersion: v1
kind: Pod
metadata:
name: curl
spec:
containers:
- name: main
image: tutum/curl
command: ["sleep", "9999999"]

  在完成pod的创建后,在容器中运行kubectl exec来启动一个bashshell: kubectl exec -it curl bash

  发现API服务器地址

  首先,需要找到Kubernetes API服务器的IP地址和端口。这一点比较容易做到, 因为一个名为kubernetes的服务在默认的命名空间被自动暴露,并被配置为指向API服务器。每次使用kubectl get svc命令显示所有服务清单时,都会看到这个服务。

$ kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes 10.0.0.1 <none> 443/TCP 46d

  每个服务都被配置了对应的环境变量,在容器内通过查询KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT这两个环境变量,可以获取API服务器的IP地址和端口。

root@curl:/# env | grep KUBERNETES_SERVICE
KUBERNETES_SERVICE PORT=443
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443

  同样,每个服务都可以获得一个DNS入口,所以甚至没有必要 去查询环境变量,而只是简单地将curl指向https://Kubernetes。公平地讲,如果不知道服务在哪个端口是可用的,既可以查询环境变量,也可以查看DNS SRV记录来得到实际的端口号。

  之前展示的环境变量说明API服务器监听HTTPS协议默认的443端口,所以尝试通过HTTPS协议来访问服务器。

root@curl:/#  curl https://kubernetes
curl: (60) SSL certificate problem: unable to get local issuer certificate
If you'd like to turn off curl's verification of the certificate, use the -k (or --insecure) option.

  虽然最简单的绕开这一步骤的方式是使用推荐的-k选项(这也是在手工操作API服务器时通常会使用的方式),但还是来看一下更长(也是正确)的途径。 应该通过使用curl检查证书的方式验证API服务器的身份,而不是盲目地相信连接的服务是可信的。

  验证服务器身份

  在Secret章节中,有一个名为defalut-token-xyz的Secret被自动创建,并挂载到每个容器的/var/run/secrets/kubernetes.io/serviceaccount目录下。查看目录下的文件,再次看一下Secret的内容。

root@curl:/#ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt namespace token

  Secret有三个入口(因此在Secret卷中有三个文件)。ca.crt文件文件中包含了CA的证书,用来对Kubernetes API服务器证书进行签名。为了验证正在交互的API服务器,需要检查服务器的证书是否是由CA签发。curl允许使用-cacert选项来指定CA证书,尝试重新访问API服务器:

root@curl:/# curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
Unauthorized

  到目前为止,服务使用了信任的CA签名的证书,所以curl验证通过了服务器的身份,但Unauthorized这个响应提醒我们需要关注授权的问题。同时,看一下如何通过设置CURL_CA_BUNDLE环境变量来简化操作,从而不必在每次运行curl时都指定--cacert选项:

root@curl:/# export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

  现在,可以不使用--cacert来访问API服务器:

root@curl:/# curl https://kubernetes
Unauthorized

  这样操作相对便捷,客户端(curl)现在信任API服务器,但API服务器并不确认访问者的身份,所以没有授权允许访问。

  获得API服务器授权

  需要获得API服务器的授权,以便可以读取并进一步修改或删除部署在集群中的API对象。为了获得授权,需要认证的凭证,幸运的是,凭证可以使用之前提到的default-token Secret来产生,同时凭证可以被存放在secret卷的token文件中。Secret这个名字就说明了它主要的作用。

  可以使用凭证来访问API服务器,第一步,将凭证挂载到环境变量中:

root@curl:/# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

  此时,凭证己经被存放在TOKEN环境变量中,如下面的代码清单所示,可以在向API服务器发送请求时使用它。

#代码8.13 获得API服务器的正确响应
root@curl:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes
{
"paths":[
"/api",
"/api/v1",
"/apis",
"/apis/apps",
"/apis/apps/v1beta1",
"/apis/authorization.k8s.io",
"/ui/",
"/version"
]
]

  通过发送请求的HTTP头中的Authorization字段向API服务器传递了凭证,API服务器识别确认凭证并返回正确的响应,现在可以探索集群中所有的资源。

  例如,可以列出集群中所有的pod,但前提是我们知道运行curl的pod属于哪个命名空间。

  获取当前运行pod所在的命名空间

  之前了解了如何使用Downward API的方式将命名空间的属性传递到pod。如果你注意观察的话,secret卷中也包含了一个叫作命名空间的文件。这个文件包含了当前运行pod所在的命名空间,所以可以读取这个文件来获得命名空间信息,而不是通过环境变量明确地传递信息到pod。文件内容挂载到NS环境变量中,然后列出所有的pod,如下面的代码清单所示。

#代码8.14 获取当前pod所在命名空间中所有pod清单
root@curl:/# NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
root@curl:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods
{
"kind": "PodList”,
"apiVersion":"v1",
...

  通过使用挂载在secret卷目录下的三个文件,可以罗列出与当前pod运行在同一个命名空间下的所有pod的清单。使用同样的方式不仅可以使用GET请求,还可以使用PUT或者PATCH来检索和修改其他API对象。

  简要说明pod如何与Kubernetes交互

  简单说明一下在pod中运行的应用如何正确访问Kubernetes的API:

    • 应用应该验证API服务器的证书是否是证书机构所签发,这个证书是在cacrt文件中。
    • 应用应该将它在token文件中持有的凭证通过Authorization标头来获得API服务器的授权。
    • 当对pod所在命名空间的API对象进行CRUD操作时,应该使用namespace文件来传递命名空间信息到API服务器。

  定义:CRUD代表创建、读取、修改和删除操作,与之对应的HTTP方法分别是POST、GET、PATCH/PUT 以及DELETE。

  与API服务器通信相关的pod的三个方面如图8.5所示。

  关闭基于角色的访问控制(RBAC)

  如果正在使用一个带有RBAC机制的Kubernetes集群,服务账户可能不会被授权访问API服务器(或只有部分授权)。目前最简单的方式就是运行下面的命令查询API服务器,从而绕过RBAC方式。

$ kubectl create clusterrolebinding permissive-binding \
--clusterrole=cluster-admin \
--group=system:serviceaccounts

  这个命令赋予了所有服务账户(也可以说所有的pod)的集群管理员权限,允许它们执行任何需要的操作,很明显这是一个危险的操作,永远都不应该在生产的集群中执行,对于测试来说是没有问题的。

3.通过ambassador容器简化与API服务器的交互

  使用HTTPS、证书和授权凭证,对于开发者来说看上去有点复杂。很多开发 者在许多场景下关闭了对服务器证书验证的功能(很多人就一样)。幸运的是,在保证安全性的前提下有办法简化通信的方式。

  之前提到过的kubectl proxy命令。在本机上运行这个命令, 从而可以更加方便地访问API服务器。向代理而不是直接向API服务器发送请求,通过代理来处理授权、加密和服务器验证。同样,也可以在pod中这么操作。

  ambassador容器模式介绍

  想象一下,如果一个应用需要查询API服务器(此外还有其他原因)。除了像之前章节讲到的直接与API服务器交互,可以在主容器运行的同时,启动一个ambassador容器,并在其中运行kubecctl proxy命令,通过它来实现与API服务器的交互。

  在这种模式下,运行在主容器中的应用不是直接与API服务器进行交互,而是通过HTTP协议(不是HTTPS协议)与ambassador连接,并且由ambassador通过HTTPS协议来连接API服务器,对应用透明地来处理安全问题(见图8.6)。这种方式同样使用了默认凭证Secret卷中的文件。

  因为在同一个pod中的所有连接共享同样的回送网络接口,所以应用可以使用本地的端口来访问代理。

  运行带有附加ambassador容器的CURL pod

  为了通过操作来理解ambassador容器模式,我们像之前创建curl pod—样创建一个新的pod,但这次不是仅仅在pod中运行单个容器,而是基于一个多用途的kubectl容器镜像来运行一个额外的ambassador容器,这个镜像是之前创建的并己提交到DockerHub。

  也有Dockerfile

FROM alpine
RUN apk update && apk add curl && curl -L -O https://dl.k8s.io/v1.8.0/kubernetes-client-linux-amd64.tar.gz && tar zvxf kubernetes-client-linux-amd64.tar.gz kubernetes/client/bin/kubectl && mv kubernetes/client/bin/kubectl / && rm -rf kubernetes && rm -f kubernetes-client-linux-amd64.tar.gz
ADD kubectl-proxy.sh /kubectl-proxy.sh
ENTRYPOINT /kubectl-proxy.sh

  pod的manifest文件如以下代码清单所示。

#代码8.15 带有ambassador容器的pod:curl-with-ambassador.yaml
apiVersion: v1
kind: Pod
metadata:
name: curl-with-ambassador
spec:
containers:
- name: main
image: tutum/curl
command: ["sleep", "9999999"]
- name: ambassador
image: luksa/kubectl-proxy:1.6.2 #ambassador容器,运行kubectl-proxy镜像

  pod的spec与之前非常类似,但pod名称是不同的,同时增加了一个额外的容器。 运行这个pod,并且通过以下命令进入主容器:

$ kubectl exec -it curl-with-ambassador -c main bash
root@curl-with-ambassador:/#

  现在pod包含两个容器,希望在main容器中运行bash,所以使用-c main选项。如果想在pod的第一个容器中运行该命令,也无须明确地指定容器。但如果想在任何其他的容器中运行这个命令,就需要使用-c选项来说明容器的名称。

  通过ambassador来与API服务器进行交互

  接下来尝试通过ambassador容器来连接API服务器。默认情况下,kubectl proxy绑定8001端口,由于pod中的两个容器共享包括回送地址在内的相同的网络接口,可以如下面的代码清单所示,将curl指向localhost:8001。

#代码8.16 通过ambassador容器访问API服务器
root@curl-with-ambassador:/# curl localhost:8001
{
"paths":[
"/api",
]
}

  成功了! curl的输出打印结果与我们之前看到的响应相同,但这次,并不需要处理授权的凭证和服务器证书。

  想要清楚地了解处理的细节,请参考图8.7。curl向在ambassador容器内运行的代理发送普通的HTTP请求(不包含任何授权相关的标头),然后代理向API服务器发送HTTPS请求,通过发送凭证来对客户端授权,同时通过验证证书来识别服务器的身份。

  这是一个很好的例子,它说明了如何使用一个ambassador容器来屏蔽连接外部服务的复杂性,从而简化在主容器中运行的应用。ambassador容器可以跨多个应用复用,而且与主应用使用的开发语言无关。负面因素是需要运行额外的进程,并且消耗资源。

4.使用客户端库与API服务器交互

  每个语言都有自己的客户端库,这个可以去官方网站查找,不做过多解释。

与KubernetesAPI服务器交互的更多相关文章

  1. 从高处理解android与服务器交互(看懂了做开发就会非常的容易)

    今天帮一个朋友改一个bug 他可以算是初学者吧 .我给他看了看代码,从代码和跟他聊天能明显的发现他对客户端与服务器交互 基本 不是很了解.所以我花了更多时间去给他讲客户端与服务器的关系.我觉得从这个高 ...

  2. 跟服务器交互的Web表单(form)

    使用HTML来构建可以跟服务器交互的Web表单(form),通过给你的form元素添加一个action属性来达到此目的. action属性的值指定了表单提交到服务器的地址. 例如: <form ...

  3. Android和FTP服务器交互,上传下载文件(实例demo)

    今天同学说他备份了联系人的数据放在一个文件里,想把它存到服务器上,以便之后可以进行下载恢复..于是帮他写了个上传,下载文件的demo 主要是 跟FTP服务器打交道-因为这个东东有免费的可以身亲哈 1. ...

  4. html name id, 与服务器交互必须有name

    html name id, 与服务器交互必须有name 在HTML中元素的ID和Name的区别和联系. 今天写了个测试,在php脚本里怎么也获取不到$_POST['userName']的值,经检查在h ...

  5. 20171018 微信小程序客户端数据和服务器交互

    -- 时常在想,怎么样才能把知识写的清晰,其实是我理解的不够清晰 微信小程序其实是一个客户端页面,也是需要和服务器交互才能体现数据. 1 --服务器搭建Web API :MVC4 中的一个模板, 如下 ...

  6. 浏览器与服务器交互原理以及用java模拟浏览器操作v

    浏览器应用服务器JavaPHPApache * 1,在HTTP的WEB应用中, 应用客户端和服务器之间的状态是通过Session来维持的, 而Session的本质就是Cookie, * 简单的讲,当浏 ...

  7. ASP.NET 表单验证方法与客户端(浏览器)服务器交互机制的故事

    想到这个问题完全是一个意外吧,是在寻找另外一个问题答案的过程中,才对验证方法与浏览器服务器交互机制的关系有了清晰的认识. 先说下验证方法,验证方法分为前台验证和后台验证. 前台验证就是类似jQuery ...

  8. 利用ajax短轮询+php与服务器交互制作简易即时聊天网站

    主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Server-sent Events). 本文主要介绍ajax短轮询的简易实现方式. 看懂此文 ...

  9. AngularJs与Java Web服务器交互

    AngularJs是Google工程师研发的产品,它的强大之处不是几句话就能描述的,只有真正使用过的人才能体会到,笔者准备在这篇文章中,以一个简单的登录校验的例子说明如何使用AngularJs和Web ...

随机推荐

  1. css背景|列表样式

    背景样式 背景区包含内容.padding 和 boder 不包含外边距 background-color 设置元素的背景颜色 background-image 把图像设置为背景,包含内边距和边框,不包 ...

  2. [转载]centos 7(1611)安装笔记

    centos 7(1611)安装笔记   麻烦 前天我把双系统笔记本里的 deepin 的磁盘分区直接从 Windows 7 磁盘管理里格式化了,结果悲催了,开不了机了,显示: 我以为是 Window ...

  3. Fedora镜像下载地址

    Fedora镜像下载地址 Fedora 7核心源码包在: http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/7/F ...

  4. linux中级之lvs配置(命令)

    一.nat模式配置 环境说明: DS:nat网卡(自动获取也可以,充当vip): 192.168.254.13 255.255.255.0 vmnet3网卡(仅主机): 172.16.100.1 25 ...

  5. 【转载】java与xml

    原文地址:http://www.lai18.com/content/1198237.html java项目中,xml文件一般都是用来存储一些配置信息一般的编程, 多数用来存储配置信息 . 拿JDBC来 ...

  6. 安卓开发(2)—— Kotlin语言概述

    安卓开发(2)-- Kotlin语言概述 Android的官方文档都优先采用Kotlin语言了,学它来进行Android开发已经是一种大势所趋了. 这里只讲解部分的语法. 如何运行Kotlin代码 这 ...

  7. 『动善时』JMeter基础 — 28、JMeter函数助手详解

    目录 1.函数助手介绍 2.函数助手中的函数分类 3.常用函数介绍 (1)__Random函数 (2)__counter函数 (3)__time函数 (4)__property函数 (5)__setP ...

  8. salesforce零基础学习(一百零三)项目中的零碎知识点小总结(五)

    本篇参考:Salesforce Admin篇(四) Security 之Two-Factor Authentication & Single Sign On https://developer ...

  9. 性能调优命令之jstack

    jstack是java虚拟机自带的一种线程堆栈跟踪工具. /opt/java8/bin/jstack Usage: jstack [-l] <pid> (to connect to run ...

  10. grasshopper之python电池执行逻辑

    在grasshopper中,需要导入的包虽然不多,但是相当绕人,所要实现的操作往往找不到,暂时做个分类. 双击输入 python 电池: # 导入rhino 包 import Rhino #Rhino ...