在介绍过的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. [DB] Spark Core (1)

    生态 Spark Core:最重要,其中最重要的是RDD(弹性分布式数据集) Spark SQL Spark Streaming Spark MLLib:机器学习算法 Spark Graphx:图计算 ...

  2. 搞清楚 硬件环境 os环境 网络环境 搞清楚测试工具 测试步骤 自己搭测试环境 自测

    1,遇事的第一反应要从变化情绪转变为做出判断.判断什么?判断这一件事对自己是否重要,是否关乎我的个人利益,是否影响我的人际关系等等等等.如果答案都是否,那就没必要着急忙慌了.如果答案是是 冷静,其实是 ...

  3. 【Python成长之路】装逼的一行代码:快速共享文件

    [Python成长之路]装逼的一行代码:快速共享文件 2019-10-26 15:30:05 华为云 阅读数 335 文章标签: Python编程编程语言程序员Python开发 更多 分类专栏: 技术 ...

  4. 如果你想设置无人自动升级,我们推荐你将这个值修改为security,它会告诉 yum 仅仅升级修复安全问题的软件包。

    定期升级你的 CentOS 系统,是所有系统安全中最重要的措施之一.如果你不使用最新的安全补丁升级你的操作系统软件包,你将会让你的机器很容易被攻击. 如果你管理者多个 CentOS 机器,手动升级系统 ...

  5. STM32串口编程易错点

    注意   串口发送函数 使用STM官方的LIB 中的库函数发送之后 加一点延时   否则会错误  接收数据不正常 正确做法是  加上等待发送完成

  6. Linux(CentOS7)下二进制安装MySQL5.7.26

    记录一下自己在 CentOS7 下二进制安装 MySQL5.7.26 的过程,之前使用 Linux(CentOS7)下rpm安装MySQL8.0.16 之后发现 rpm 方式安装不利于维护,也不利于单 ...

  7. 重新整理 .net core 实践篇—————配置系统之简单配置中心[十一]

    前言 市面上已经有很多配置中心集成工具了,故此不会去实践某个框架. 下面链接是apollo 官网的教程,实在太详细了,本文介绍一下扩展数据源,和简单翻翻阅一下apollo 关键部分. apollo 服 ...

  8. Rabbitmqpool

    import pika import threading import random import uuid import json # 框架模块 from django.conf import se ...

  9. Python+Selenium自动化-获取页面信息

    Python+Selenium自动化-获取页面信息   1.获取页面title title:获取当前页面的标题显示的字段 from selenium import webdriver import t ...

  10. Docker学习(10) Docker的远程访问

    Docker的远程访问 只需配置