8、从应用访问pod元数据以及其他资源

一、通过Downward API 传递元数据

对于一些预先知道的数据,我们可以通过configMap及Secret 向应用传递数据,但是预先不知道的数据呢,比如Pod的IP、主机名或pod的标签等。
对于此类问题,可以通过使用kubernetes Downward API解决。Downward API允许我们通过环境变量或者文件(在 downward API卷中)传递pod的元数据。Downward API的方式并不像REST endpoint 那样需要通过访问的方式获取数据。这种方式主要是将在pod的定义和状态中取得的数据作为环境变量和文件的值。

Downward API 可以给在pod中运行的进程暴露的pod元数据。

  • pod的名称
  • pod的IP
  • pod所在的命名空间
  • pod运行节点的名称
  • pod运行所归属的服务账户的名称
  • 每个容器请求的CPU和内存的使用量
  • 每个容器可以使用的CPU和内存的限制
  • pod的标签
  • pod的注解

其中大部分的数据都可以通过环境变量或downward API卷进行传递,除了标签及注解必须通过数据卷暴露。且部分数据可以通过其他方式获取(比如直接从操作系统获取),但是Downward API提供了一种更加便捷的方式。

downward-api-env.yaml
apiVersion: v1
kind: Pod
metadata:
  name: downward
spec:
  containers:
  - image: busybox
    name: test-busybox
    command: ["sleep", "999999"]
    resources:
      requests:
        cpu: 15m
        memory: 100Ki
      limits:
        cpu: 100m
        memory: 4Mi
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name  # 引用pod manifest 中元数据名称,而不是设定一个具体值
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
    - name: POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    - name: SERVICE_ACCOUNT
      valueFrom:
        fieldRef:
          fieldPath: spec.serviceAccountName
    - name: CONTAINER_MEMORY_LIMIT_KIBIBYTES
      valueFrom:
        resourceFieldRef:
          resource: limits.memory
          divisor: 1Ki
    - name: CONTAINER_CPU_REQUEST_MILLICORES
      valueFrom:
        resourceFieldRef:
          resource: requests.cpu

这里设定CPU请求的基数为1m(即 1millicore 千分之一核CPU),也可以设置为1,则表示整颗CPU。
kubectl exec downward env 查看环境变量的值

通过downwardAPI 卷来传递元数据
当标签与注解被修改后,kubernetes会更新存有相关信息的文件,从而使pod可以获取最新的文件。在环境变量下,一旦标记与注解被修改,新的值将不会被暴露。

apiVersion: v1
kind: Pod
metadata:
  name: downward-volume
  labels:
    app: test
  annotations:
    key1: value1
    key2: |
      multi
      line
      value
spec:
  containers:
  - image: busybox
    name: test-busybox
    command: ["sleep", "999999"]
    resources:
      requests:
        cpu: 15m
        memory: 100Ki
      limits:
        cpu: 100m
        memory: 4Mi
    volumeMounts:
    - name: downward-volume
      mountPath: /etc/downward
  volumes:
  - name: downward-volume
    downwardAPI:
      items:
      - path: "podName"
        fieldRef:
         fieldPath: metadata.name
      - path: "podNamespace"
        fieldRef:
          fieldPath: metadata.namespace
      - path: "labels"
        fieldRef:
          fieldPath: metadata.labels
      - path: "annotations"
        fieldRef:
          fieldPath: metadata.annotations
      - path: "containerCpuRequest"
        resourceFieldRef:
          containerName: test-busybox
          resource: requests.cpu
          divisor: 1m
      - path: "containerMemorylimit"
        resourceFieldRef:
          containerName: test-busybox
          resource: limits.memory
          divisor: 1

kubectl describe pod downward-volume ls -lL /etc/downward
查看挂载目录下的文件
kubectl exec -It pod downward-volume -c test-busybox – /bin/sh
进入容器,查看/etc/downward 下个文件的值

这里需要注意的一点是,当暴露容器级的数据时,如容器的可使用资源限制或者资源请求(使用字段resourceFieldRef),必须要指定引用资源字段对应的容器名称。

spec:
  volume:
  - name: xxx
    downwardAPI:
      items:  
      - path: "containerCpuRequest"
        resourceFieldRef:
          containerName: test-busybox  # 容器名称
          resource: requests.cpu
          divisor: 1m
      - path: "containerMemorylimit"
        resourceFieldRef:
          containerName: test-busybox
          resource: limits.memory
          divisor: 1  

因为我们对于数据卷的定义是基于pod级的,而引用某个容器的资源字段时。肯定要说明是引用的哪一个容器,该规则也适用于单容器的pod。
适用数据卷的方式暴露容器的资源字段,稍显麻烦,但可将一个容器的资源字段传递给另一个容器,而环境变量只能传递自身的资源字段。

二、与kubernetes API服务器交互

通过Downward API方式获取到的元数据还是相当有限的,如果要获取更多的信息,我们可以通过直接访问kubernetes API服务的方式进行。
从pod内部与API服务器进行交互需要关注三个地方

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

运行一个pod来尝试与API服务器进行交互
该pod不做任何操作,除了执行sleep命令以防容器退出,可以再docker hub上找到 tutum/curl

apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
  - image: tutum/curl
    name: main
    command: ["sleep", "99999"]

1、发现API地址
kubectl get svc 可以找到 kubernetes 服务,并可以看到它的地址及端口
并且在每个容器初始化时,kubernetes 都会将服务的地址及端口以环境变量的形式注入容器,在容器中通过查询KUBERNETES_SERVICE_HOST 和 KUBERNETES_SERVICE_PORT 这两个环境变量可以获取API服务器的地址和端口。
同样每个服务都可以获得一个DNS入口,甚至都可不用查询环境变量,而是简单的将curl指向https://kubernetes。如果不知道服务在哪个端口,可以查询环境变量,及查看DNS SRV记录来得到端口。
在上述容器中执行 curl https://kubernetes 访问服务,需要证书验证,可以使用 -k 选项简单的逃过绕开,但是从长远的角度来说,还是要通过证书验证API服务器的身份,而不是盲目的相信连接的服务时可信的。
注意:在真实的应用中,永远不要跳过检查服务器证书的环节,这样会导致你的应用验证凭证暴露给采用中间人攻击方式的攻击者

2、验证服务器身份
每个容器都会挂载一个默认的secret,在/var/run/secrets/kubernetes.io/serviceaccount 下有三个文件,分别是 ca.crt 、namespace、token。其中ca.crt 包含了CA证书,可用它来对Kubernetes API服务器证书进行签名。我们需要检查正在交互的服务器的证书是否由CA签发,curl允许使用–cacert选项来指定CA证书
在上述的容器中运行:curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
同时通过设置CURL_CA_BUNDLE 环境变量来简化操作,以免每次运行curl时都指定–cacert选项
export CURL_CA_BOUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
此时现在信任API服务器,但是API服务器并不确定访问者的身份,所以没有授权访问。

3、获得 API服务器授权
我们需要获取服务器的授权,就需要认证的凭证,认证的凭证在secret挂载的目录可找到——token文件,将凭证挂载到环境变量中。
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
向API服务发送请求时使用它
curl -H “Authorization: Bearer $TOKEN” https://kubernetes

关闭基于角色的访问控制(RBAC)
如果正在使用一个带有RBAC机制的kubernetes集群,服务账户可能不会被授权访问API服务器(或只有部分授权)。目前最简单的方式就是运行下面的命令查询API服务器,从而绕过RBAC方式
kubectl create clusterrolebinding permissive-binding-binding
–clusterrole=cluster-admin --group=system:serviceaccounts 这个命令赋予了所有服务账户(也可以说所有的pod)的集群管理员权限。允许它们执行任何需要的操作,很明显这是一个危险的操作,永远都不应该在生产的集群中执行,对于测试来说是没有问题的

我们通过发送请求的HTTP头中Authorization 字段向API服务器传递了凭证,API服务器识别确认凭证并返回正确的响应。

获取当前运行pod所在命名空间
在secret卷中包含了namespace的文件,这个文件包含了当前运行pod所在的命名空间,我们可以通过此获得命名空间,然后通过环境变量明确的传递信息到pod。然后列出该命名空间下所有的pod。
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
curl -H "Authorization: Bearer T O K E N " h t t p s : / / k u b e r n e t e s / a p i / v 1 / n a m e s p a c e s / TOKEN" https://kubernetes/api/v1/namespaces/ TOKEN"https://kubernetes/api/v1/namespaces/NS/pods

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

简要说明pod 如何与Kubernetes 交互

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

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

通过ambassador容器简化与API服务器的交互
使用HTTPS、证书及授权凭证,可能有些麻烦。可以使用ambassador容器,再ambassador容器中将会运行kubectl proxy命令,通过它来与API服务器进行交互。
在这种模式下,运行在容器中的应用不是直接与API服务器直接交互,而是通过HTTP协议与ambassador连接,然后由ambassador通过HTTPS协议与API服务进行连接,对应用透明地来处理安全问题。这种方式同样的使用了默认凭证Secret数据卷中的文件。
因为在同一个pod中的所有连接共享同样的回送网络接口,所以我们的应用可以使用本地的端口来访问代理。
curl-ambassador.yaml

apiVersion: v1
kind: Pod
metadata:
  name: curl-ambassador
spec:
  containers:
  - image: tutum/curl
    name: main
    command: ["sleep", "99999"]
  - name: ambassador
    image: luksa/kubectl-proxy:1.6.2

kubectl exec -it curl-ambassador -c main bash 进入主容器
默认情况下kubectl proxy 绑定8001端口。
在主容器中执行 curl localhost:8001 即可获得之前相同的结果
使用一个ambassador容器来屏蔽连接外部服务的复杂性,从而简化主容器中运行的应用,ambassador容器可以跨多个应用复用,而且与主应用使用的开发语言无关。负面因素就是需要运行额外的进程,并且消耗资源。

使用客户端库与API服务交互
已经体验了ambassador容器kubectl-proxy的好处,如果我们的应用仅仅需要在API服务执行简单的操作,往往可以使用一个标准的客户端来执行简单的HTTP请求,但是对更复杂的API服务请求来说,使用某个已有的kubernetes API客户端库会更好一些。
目前存在由 API Macchinery special interest group 支持的两个版本Kubernetes API客户端库
Golang client——https://github.com/kubernetes/client-go
Python——https://github.com/kubernetes-incubator/client-python

(本文章是学习《kubernetes in action》 一书时做的笔记,由于本人是刚学习k8s,且自学方式就是把它敲一遍,然后加一些自己的理解。所以会发现很多内容是跟书中内容一模一样,如果本文是对本书的侵权,敬请谅解,告知后会删除。如果引用或转载,请注明出处——谢谢)

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐