与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服务器进行交互,需要关注以下三件事情:

    • 确定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服务器交互

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

 

 

posted @ 2021-05-21 00:28  小家电维修  阅读(265)  评论(0编辑  收藏  举报