与KubernetesAPI服务器交互
Posted 小家电维修
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了与KubernetesAPI服务器交互相关的知识,希望对你有一定的参考价值。
在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服务器进行交互,需要关注以下三件事情:
l 确定API服务器的位置
l 确保是与API服务器进行交互,而不是一个冒名者
l 通过服务器的认证,否则将不能查看任何内容以及进行任何操作
接下来看一下交互如何实现。
运行一个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:
l 应用应该验证API服务器的证书是否是证书机构所签发,这个证书是在cacrt文件中。
l 应用应该将它在token文件中持有的凭证通过Authorization标头来获得API服务器的授权。
l 当对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服务器交互的主要内容,如果未能解决你的问题,请参考以下文章