K8s in Action 阅读笔记——【8】Accessing pod metadata and other resources from applications
8.1 Passing metadata through the Downward API
Kubernetes Downward API 是 Kubernetes 中一种用于将 Pod 和容器中的元数据(如 Pod IP 地址、容器名称、容器 ID 等)传递到应用程序内部的机制。使用 Downward API,可以将这些元数据以环境变量或文件的形式注入到容器中,从而让应用程序可以直接获取这些信息,而无需自己从容器运行时环境中去获取。这样可以方便应用程序在运行时获取必要的信息,并避免重复定义相同的元数据。Downward API 可以通过 Pod 中的 annotations 和 volumeMounts 字段来启用。如图8.1所示。
8.1.1 Understanding the available metadata
Downward API允许你将Pod自身的元数据暴露给运行在该Pod内部的进程。目前,它允许你向容器传递以下信息:
- Pod的名称
- Pod的IP地址
- Pod所属的命名空间
- Pod所在节点的名称
- Pod正在运行的服务账户的名称
- 每个容器的CPU和内存请求
- 每个容器的CPU和内存限制
- Pod的标签
- Pod的注释
列表中的大多数项目可以通过环境变量或通过DownwardAPI卷传递给容器,但标签和注释只能通过卷来暴露。部分数据可以通过其他方式获取(例如直接从操作系统获取),但Downward API提供了一种更简单的替代方法。
8.1.2 Exposing metadata through environment variables
首先,让我们看看如何通过环境变量将Pod和容器的元数据传递给容器。你将根据以下清单创建一个简单的单容器Pod。
# downward-api-env.yaml
apiVersion: v1
kind: Pod
metadata:
name: downward
namespace: default
labels:
app: downward
spec:
containers:
- name: main
image: busybox
command: ["sleep", "9999999"]
resources:
requests:
cpu: 15m
memory: 100Ki
limits:
cpu: 100m
memory: 6Mi
env:
- name: POD_NAME
valueFrom:
fieldRef:
# 引用的不是绝对值,而是Pod配置中的metadata.name字段。
fieldPath: metadata.name
- 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_CPU_REQUEST_MILLICORES
# 容器的CPU和内存请求和限制是通过使用resource FieldRef而不是fieldRef引用的
valueFrom:
resourceFieldRef:
resource: requests.cpu
divisor: 1m # 对于资源字段,可以定义一个除数来获得所需单位的值。
- name: CONTAINER_MEMORY_LIMIT_KIBIBYTES
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: 1Ki
当你的进程运行时,可以查找Pod规范中定义的所有环境变量。图8.2显示了环境变量及其值的来源。Pod的名称、IP和命名空间将分别通过环境变量POD_NAME、POD_IP和POD_NAMESPACE来暴露。容器所在节点的名称将通过变量NODE_NAME来暴露。服务账户的名称将通过环境变量SERVICE_ACCOUNT提供。你还将创建两个环境变量,用于保存为此容器请求的CPU量和容器允许消耗的最大内存量。
对于暴露资源限制或请求的环境变量,你需要指定一个除数。限制或请求的实际值将被除以除数,并通过环境变量进行暴露。在前面的示例中,你将CPU请求的除数设置为1m(一毫核,即CPU核心的千分之一)。因为你将CPU请求设置为15m,环境变量CONTAINER_CPU_REQUEST_MILLICORES将被设置为15。同样,你将内存限制设置为4Mi(4兆字节),除数设置为1Ki,所以环境变量CONTAINER_MEMORY_LIMIT_KIBIBYTES将被设置为4096。
CPU限制和请求的除数可以是1(表示一个完整的核心)或1m(表示一个毫核)。内存限制/请求的除数可以是1(字节)、1k(千字节)或1Ki(kibibyte)、1M(兆字节)或1Mi(mebibyte),以此类推。创建完Pod后,可以使用kubectl exec命令在容器中查看所有这些环境变量,如下面的示例所示。容器内运行的所有进程都可以读取这些变量,并根据需要使用它们。
$ kubectl exec downward -- env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=downward
SERVICE_ACCOUNT=default
CONTAINER_CPU_REQUEST_MILLICORES=15
CONTAINER_MEMORY_LIMIT_KIBIBYTES=6144
POD_NAME=downward
POD_NAMESPACE=default
POD_IP=10.244.1.100
NODE_NAME=yjq-k8s2
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
HOME=/root
Kibibyte(KiB)是计算机存储容量的单位,属于二进制前缀。它表示1024(2的10次方)字节。Kibibyte是一种与Kilobyte(KB)不同的术语,后者是使用十进制前缀表示的存储容量单位,表示1000字节。Kibibyte和Kilobyte之间的差异是由于计算机存储容量通常是以二进制方式表示的,因此采用二进制前缀更为准确。在国际单位制(SI)中,Kilobyte应该表示1000字节,而Kibibyte则表示1024字节。
8.1.3 Passing metadata through files in a downwardAPI volume
如果你更喜欢通过文件而不是环境变量来暴露元数据,可以定义一个downwardAPI
卷并将其挂载到容器中。对于暴露Pod的标签或注释,你必须使用downwardAPI
卷,因为它们不能通过环境变量来暴露。稍后会讨论原因。
与环境变量一样,如果要将每个元数据字段暴露给进程,你需要显式地指定它们。让我们看看如何修改前面的示例,使用卷而不是环境变量,如下所示:
# downward-api-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: downward
labels: # 这些标签和注释将通过downwardAPI卷暴露
foo: bar
annotations:
key1: value1
key2: |
multi
line
value
spec:
containers:
- name: main
image: busybox
command: ["sleep", "9999999"]
resources:
requests:
cpu: 15m
memory: 100Ki
limits:
cpu: 100m
memory: 6Mi
volumeMounts:
- name: downward
mountPath: /etc/downward # 挂载位置
volumes:
- name: downward # 定义downward 卷
downwardAPI:
items:
# Pod的名称(来自清单中的metadata.name字段)将写入podName文件。
- path: "podName"
fieldRef:
fieldPath: metadata.name
- path: "podNamespace"
fieldRef:
fieldPath: metadata.namespace
# Pod的标签将写入/etc/down/labels文件
- path: "labels"
fieldRef:
fieldPath: metadata.labels
# Pod的注释将写入/etc/down/annotation文件
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
- path: "containerCpuRequestMilliCores"
resourceFieldRef:
containerName: main
resource: requests.cpu
divisor: 1m
- path: "containerMemoryLimitBytes"
resourceFieldRef:
containerName: main
resource: limits.memory
divisor: 1
不再通过环境变量传递元数据,而是定义了一个名为downward的卷,并将其挂载到容器的/etc/downward目录下。此卷中的文件是在卷规范的downwardAPI.items
属性下进行配置的。每个项指定了元数据应写入的路径(即文件名),并引用要将其值存储在文件中的Pod级字段或容器资源字段(见图8.3)。
删除之前的Pod,并根据前面YMAL创建一个新的Pod。然后查看挂载的downwardAPI卷目录的内容。你将卷挂载在/etc/downward/下,所以列出其中的文件,如下面的示例所示。
$ kubectl exec downward -- ls -lL /etc/downward
total 24
-rw-r--r-- 1 root root 139 Jun 1 06:24 annotations
-rw-r--r-- 1 root root 2 Jun 1 06:24 containerCpuRequestMilliCores
-rw-r--r-- 1 root root 7 Jun 1 06:24 containerMemoryLimitBytes
-rw-r--r-- 1 root root 9 Jun 1 06:24 labels
-rw-r--r-- 1 root root 8 Jun 1 06:24 podName
-rw-r--r-- 1 root root 7 Jun 1 06:24 podNamespace
每个文件对应于卷定义中的一个项。文件的内容与之前示例中的环境变量的值相同,对应于相同的元数据字段,所以我们不会在此处展示它们。但是,因为之前无法通过环境变量来暴露标签和注释,请查看下面的示例,了解将其暴露在两个文件中的内容。
$ kubectl exec downward -- cat /etc/downward/labels
foo="bar"
$ kubectl exec downward -- cat /etc/downward/annotations
key1="value1"
key2="multi\nline\nvalue\n"
kubernetes.io/config.seen="2023-06-01T14:24:12.483938027+08:00"
kubernetes.io/config.source="api"
更新标签和注释
你可能还记得,在Pod运行时可以修改标签和注释。正如你所预期的那样,当它们发生变化时,Kubernetes会更新保存它们的文件,使得Pod始终可以看到最新的数据。这也解释了为什么标签和注释不能通过环境变量来暴露。因为环境变量的值后续无法更新,如果将Pod的标签或注释通过环境变量暴露,那么在它们被修改后就无法暴露新的值。
在结束本节之前,我们需要指出一件事。当暴露容器级别的元数据,例如容器的资源限制或请求(使用resourceFieldRef时),你需要指定你正在引用的资源字段所属的容器的名称,如下面的示例所示。
- path: "containerCpuRequestMilliCores"
resourceFieldRef:
containerName: main
resource: requests.cpu
divisor: 1m
- path: "containerMemoryLimitBytes"
resourceFieldRef:
containerName: main
resource: limits.memory
divisor: 1
如果考虑到卷是在Pod级别而不是容器级别定义的,这一点就变得很明显了。在卷规范中引用容器的资源字段时,你需要明确指定所引用的容器的名称。即使是单容器的Pod也是如此。
使用卷来暴露容器的资源请求和/或限制比使用环境变量略复杂,但好处是,如果需要,它允许将一个容器的资源字段传递给另一个容器(但两个容器需要在同一个Pod中)。使用环境变量,一个容器只能传递自己的资源限制和请求。
8.2 Talking to the Kubernetes API server
我们已经看到了Downward API如何提供一种简单的方式,将一些Pod和容器的元数据传递给内部运行的进程。它只暴露了Pod自身的元数据和Pod数据的一个子集。但有时候,你的应用程序需要了解更多关于其他Pod甚至集群中定义的其他资源的信息。在这些情况下,Downward API无法提供帮助。
正如你在整本书中所看到的,关于服务和Pod的信息可以通过查看与服务相关的环境变量或通过DNS获取。但是当应用程序需要其他资源的数据或需要访问尽可能最新的信息时,它需要直接与API服务器通信(如图8.4所示)。
在了解Pod内的应用程序如何与Kubernetes API服务器通信之前,让我们先从你的本地机器上探索服务器的REST端点,这样你就可以看到与API服务器通信的样子。
8.2.1 Exploring the Kubernetes REST API
你已经了解了不同的Kubernetes资源类型。但如果你计划开发与Kubernetes API通信的应用程序,你首先需要了解API。为了做到这一点,你可以尝试直接访问API服务器。你可以通过运行kubectl cluster-info命令获取其URL:
$ kubectl cluster-info
Kubernetes control plane is running at https://yjq-k8s1:6443
CoreDNS is running at https://yjq-k8s1:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
由于服务器使用HTTPS并且需要身份验证,直接与其通信并不简单。你可以尝试使用curl访问它,并使用curl的–insecure(或-k)选项跳过服务器证书检查,但这并不能让你走得太远:
$ curl https://yjq-k8s1:6443
curl: (60) SSL certificate problem: unable to get local issuer certificate
幸运的是,你可以通过运行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": [
"/.well-known/openid-configuration",
"/api",
你发送了请求到代理,它向API服务器发送了请求,然后代理返回了服务器返回的内容。
探索API
可以继续使用curl,或者可以打开Web浏览器,将其指向http://localhost:8001。让我们更仔细地检查当你访问其基本URL时API服务器返回的内容。服务器以路径列表的形式进行响应,如下:
$ curl localhost:8001
{
"paths": [
"/.well-known/openid-configuration",
"/api",
"/api/v1",
"/apis",
"/apis/",
......
"/apis/batch",
"/apis/batch/v1",
......
这些路径对应于你创建资源(例如Pod、Service等)时在资源定义中指定的API组和版本。
你可能会在/apis/batch/v1路径中识别出batch/v1,它是你第4章中学习到的Job资源的API组和版本。同样,/api/v1对应于你创建常见资源(Pod、Service、ReplicationControllers等)时所引用的apiVersion: v1。最常见的资源类型最初并不属于任何特定的组,因为Kubernetes最初甚至没有使用API组的概念;它们是后来引入的。
浏览Batch API组的REST端点
让我们来探索Job资源的API。首先,你将查看/apis/batch路径的内容(暂时忽略版本),如下例所示:
$ curl http://localhost:8001/apis/batch/v1
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "batch/v1",
"resources": [
{
"name": "cronjobs",
"singularName": "",
"namespaced": true,
"kind": "CronJob",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"cj"
],
"categories": [
"all"
],
"storageVersionHash": "h/JlFAZkyyY="
},
{
"name": "cronjobs/status",
"singularName": "",
"namespaced": true,
"kind": "CronJob",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "jobs",
"singularName": "",
"namespaced": true,
"kind": "Job",
"verbs": [ # 以下是可与此资源一起使用的动词,创建、删除等
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"categories": [
"all"
],
"storageVersionHash": "mudhfqk/qZY="
},
{
"name": "jobs/status", # 修改状态
"singularName": "",
"namespaced": true,
"kind": "Job",
"verbs": [
"get",
"patch",
"update"
]
}
]
}⏎
响应显示了batch API组的描述,包括可用的版本和客户端应使用的首选版本。让我们继续看看/apis/batch/v1路径的内容。如下例所示。
正如你所看到的,API服务器返回了batch/v1 API组中的资源类型和REST端点列表。其中之一就是Job资源。除了资源的名称和相关类型之外,API服务器还包括有关资源是否命名空间的信息(如果有的话;Job没有),以及你可以与该资源一起使用的动词列表。
返回的列表描述了API服务器中公开的REST资源。“name”: "jobs"行告诉你API包含/apis/batch/v1/jobs端点。"verbs"数组告诉你可以通过该端点检索、更新和删除Job资源。对于某些资源,还公开了其他API端点(例如jobs/status路径,允许仅修改Job的状态)
正在列出集群中的所有JOB实例
获取集群中的JOB列表,可以使用如下命令:
$ curl http://localhost:8001/apis/batch/v1/jobs
{
"kind": "JobList",
"apiVersion": "batch/v1",
"metadata": {
"resourceVersion": "3383741"
},
"items": []
}⏎
目前没有Job运行,所以items为空。
8.2.2 Talking to the API server from within a pod
你已经学习了如何使用kubectl代理从本地机器与API服务器进行通信。现在,让我们看看如何在Pod内部与API服务器进行通信,因为在Pod内部通常没有kubectl。因此,要从Pod内部与API服务器通信,你需要注意以下三个问题:
- 找到API服务器的位置。
- 确保你与API服务器进行通信,而不是与冒充它的其他实体进行通信。
- 与服务器进行身份验证;否则,它不会允许你查看或执行任何操作。
运行一个Pod来尝试与API服务器进行通信
首先,你需要一个Pod来与API服务器进行通信。你将运行一个无实际功能的Pod(它在唯一的容器中运行sleep命令),然后使用kubectl exec在容器内运行一个shell。接下来,你将尝试在该shell内使用curl访问API服务器。
因此,你需要使用一个包含curl二进制文件的容器镜像。Pod的定义如下所示:
# curl.yaml
apiVersion: v1
kind: Pod
metadata:
name: curl
spec:
containers:
- name: main
image: tutum/curl
command: ["sleep", "9999999"]
创建Pod后,使用kubectl exec
在其容器内运行一个bash shell:
$ kubectl exec -it curl -- bash
root@curl:/#
找到API服务器地址
首先,你需要找到Kubernetes API服务器的IP和端口。这很容易,因为一个名为kubernetes的Service会自动在默认命名空间中暴露,并配置指向API服务器。使用如下命令查看:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d
而且你可能还记得在第5章中为每个服务配置了环境变量。你可以通过查找KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT变量(在容器内部)来获取API服务器的IP地址和端口:
$ kubectl exec -it curl -- bash
root@curl:/# env | grep KUBERNETES_SERVICE
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443
你可能还记得每个服务还会获得一个DNS条目,因此你甚至不需要查找环境变量,而只需将curl指向https://kubernetes。公平地说,如果你不知道服务可用的端口,你也需要查找环境变量或执行DNS SRV记录查找以获取服务的实际端口号。
前面显示的环境变量表明API服务器正在443端口上监听,这是HTTPS的默认端口,所以尝试通过HTTPS访问服务器:
$ curl https://kubernetes
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: http://curl.haxx.se/docs/sslcerts.html
虽然绕过证书检查的最简单方法是使用-k选项,但让我们走更长(但正确)的路线。与其盲目地相信你连接的服务器是真正的API服务器,不如让curl通过检查其证书来验证其身份。
在实际应用程序中绝不要跳过检查服务器的证书。这样做可能会导致你的应用程序通过中间人攻击向攻击者暴露其身份验证令牌。
验证服务器身份
在前一章中,当讨论Secrets时,我们看过一个自动生成的Secret叫做default-token-xyz
,它被挂载到每个容器的/var/run/secrets/kubernetes.io/serviceaccount/
目录下。让我们再次查看该Secret的内容,通过列出该目录中的文件:
root@curl:/# ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt namespace token
该Secret有三个条目(因此Secret卷中有三个文件)。现在,我们将专注于ca.crt文件,它保存了用于签署Kubernetes API服务器证书的证书授权机构(CA)的证书。为了验证你正在与API服务器通信,你需要检查服务器的证书是否由CA签署。curl允许你使用–cacert选项指定CA证书,所以尝试再次访问API服务器:
root@curl:/# curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {
},
"code": 403
}
好的,你取得了进展。curl验证了服务器的身份,因为它的证书是由你信任的CA签署的。正如"Unauthorized"的响应所示,你仍然需要进行身份验证。一会儿你将会进行身份验证,但首先让我们看看如何通过设置CURL_CA_BUNDLE环境变量来简化操作,这样你就不需要每次运行curl时都指定–cacert:
root@curl:/# export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
现在你可以在不使用–cacert的情况下访问API服务器:
$ curl https://kubernetes
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {
},
"code": 403
}
现在情况好多了。你的客户端(curl)现在信任API服务器,但API服务器本身表示你没有授权访问它,因为它不知道你是谁。
用API服务器进行身份验证
你需要对服务器进行身份验证,以便允许你读取、更新甚至删除集群中部署的API对象。为了进行身份验证,你需要一个认证令牌。令牌是通过之前提到的default-token Secret
提供的,并且存储在Secret卷中的令牌文件中。正如Secret的名称所暗示的那样,这是Secret的主要目的。你将使用令牌来访问API服务器。首先,将令牌加载到一个环境变量中:
root@curl:/# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
令牌现在存储在TOKEN环境变量中。你可以在发送请求到API服务器时使用它,如下所示:
$ curl -H "Authorization: Bearer $TOKEN" https://kubernetes
{
"paths": [
"/.well-known/openid-configuration",
"/api",
"/api/v1",
"/apis",
"/apis/",
"/apis/admissionregistration.k8s.io",
"/apis/admissionregistration.k8s.io/v1",
......
]
}
如果你正在使用启用了RBAC的Kubernetes集群,服务账号可能没有权限访问(部分)API服务器。你将在第12章学习有关服务账号和RBAC的内容。目前,为了让你能够查询API服务器,最简单的方法是通过运行以下命令绕过RBAC:
$ kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --group=system:serviceaccounts
这将赋予所有服务账号(也可以说是所有Pod)具有cluster-admin权限,允许它们进行任何操作。显然,这样做是危险的,不应该在生产集群中使用。对于测试目的,这是可以的。
现在可以像之前几节中那样,探索集群中的所有资源。
获取正在运行的Pod的命名空间
在本章的前面部分,你学到了如何通过Downward API将命名空间传递给Pod。但是如果你留意到了,你可能注意到你的Secret卷中还包含一个名为namespace的文件。它包含了Pod所在的命名空间,因此你可以读取该文件,而无需通过环境变量显式地传递命名空间给你的Pod。将文件的内容加载到NS环境变量中,然后像下面的示例代码所示,列出所有的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",
"metadata": {
"resourceVersion": "3412219"
},
"items": [
{
"metadata": {
"name": "curl",
这样一来,通过使用挂载的密钥卷目录中的三个文件,你列出了与你的Pod运行在同一命名空间中的所有Pod。以相同的方式,你还可以检索其他API对象,甚至可以通过发送PUT或PATCH请求来更新它们,而不仅仅是简单的GET请求。
回顾一下Pod如何与Kubernetes通信
让我们回顾一下在Pod内运行的应用程序如何正确访问Kubernetes API:
- 应用程序应该验证API服务器的证书是否由证书颁发机构签署,其证书保存在ca.crt文件中。
- 应用程序应该通过发送包含来自token文件的令牌的Authorization标头来进行身份验证。
- 在对Pod的命名空间内的API对象执行CRUD操作时,应使用namespace文件将命名空间传递给API服务器。
CRUD代表创建(Create)、读取(Read)、更新(Update)和删除(Delete)。对应的HTTP方法分别是POST、GET、PATCH/PUT和DELETE。
Pod与API服务器通信的这三个方面在图8.5中显示出来。
8.2.3 Simplifying API server communication with ambassador containers
引入Ambassador容器模式
想象一下,你有一个应用程序(除其他功能外)需要查询API服务器。与其直接与API服务器通信(就像在前面的部分中所做的),你可以在主容器旁边的Ambassador容器中运行kubectl proxy,并通过它与API服务器进行通信。
主容器中的应用程序可以通过HTTP连接到Ambassador,而不是直接与API服务器通信,让Ambassador代理处理与API服务器的HTTPS连接,自动处理安全性(参见图8.6)。它通过使用默认令牌的密钥卷中的文件来实现这一点。
因为Pod中的所有容器共享相同的环回网络接口,所以你的应用程序可以通过localhost上的端口访问代理。
运行带有额外ambassador容器的curl Pod
为了看到ambassador容器模式的实际效果,你将创建一个类似之前创建的curl Pod的新Pod,但这次不是在Pod中运行单个容器,而是运行一个额外的ambassador容器,基于作者创建并推送到Docker Hub的通用kubectl-proxy容器镜像。
配置文件如下:
# 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
Pod规范几乎与之前的相同,只是有一个不同的Pod名称和一个额外的容器。运行Pod,然后进入主容器:
$ kubectl exec -it curl-with-ambassador -c main bash
root@curl-with-ambassador:/#
现在你的Pod有两个容器,你想在主容器中运行bash命令,因此使用了 -c main
选项。如果你想在Pod的第一个容器中运行命令,不需要显式指定容器。但如果你想在其他任何容器中运行命令,你需要使用 -c
选项指定容器的名称。
通过ambassador与API服务器对话
接下来,你将尝试通过ambassador容器连接到API服务器。默认情况下,kubectl proxy绑定到端口8001,由于Pod中的两个容器共享相同的网络接口,包括环回接口,你可以将curl指向localhost:8001,如下所示:
root@curl-with-ambassador:/# curl localhost:8001
{
"paths": [
"/.well-known/openid-configuration",
"/api",
"/api/v1",
"/apis",
"/apis/",
"/apis/admissionregistration.k8s.io",
"/apis/admissionregistration.k8s.io/v1",
"/apis/admissionregistration.k8s.io/v1beta1",
"/apis/apiextensions.k8s.io",
.......
可以看到,curl打印的输出是你之前看到的相同响应,但这次你不需要处理认证令牌和服务器证书。
为了清楚地了解发生了什么,可以参考图8.7。curl将普通的HTTP请求(不带任何认证头)发送给运行在ambassador容器内部的代理,然后代理发送了一个HTTPS请求到API服务器,并通过发送令牌进行客户端身份验证,并通过验证服务器的证书来检查服务器的身份。
这是一个很好的例子,展示了如何使用ambassador容器来隐藏连接外部服务的复杂性,并简化运行在主容器中的应用程序。ambassador容器可以在许多不同的应用程序中重复使用,而不管主应用程序使用的是什么语言。缺点是会运行一个额外的进程并消耗额外的资源。
8.2.4 Using client libraries to talk to the API server
如果你的应用程序只需要在API服务器上执行一些简单的操作,通常可以使用普通的HTTP客户端库并进行简单的HTTP请求,特别是如果你像在前面的例子中那样利用kubectl-proxy ambassador容器。但是,如果你计划进行更复杂的API请求,最好使用现有的Kubernetes API客户端库之一。
使用现有的客户端库
目前,存在两个由API Machinery特别兴趣小组(SIG)支持的Kubernetes API客户端库:
- Golang客户端库:https://github.com/kubernetes/client-go
- Python客户端库:https://github.com/kubernetes-incubator/client-python
Kubernetes社区拥有许多特别兴趣小组(SIGs)和工作组,专注于Kubernetes生态系统的特定部分。你可以在https://github.com/kubernetes/community/blob/master/sig-list.md找到它们的列表。
除了这两个官方支持的库之外,还有许多用户贡献的客户端库可供其他语言使用,以下是其中一些的列表:
- Java客户端库(Fabric8):https://github.com/fabric8io/kubernetes-client
- Java客户端库(Amdatu):https://bitbucket.org/amdatulabs/amdatu-kubernetes
- Node.js客户端库(tenxcloud):https://github.com/tenxcloud/node-kubernetes-client
- Node.js客户端库(GoDaddy):https://github.com/godaddy/kubernetes-client
- PHP客户端库:https://github.com/devstub/kubernetes-api-php-client
- 另一个PHP客户端库:https://github.com/maclof/kubernetes-client
- Ruby客户端库:https://github.com/Ch00k/kubr
- 另一个Ruby客户端库:https://github.com/abonas/kubeclient
- Clojure客户端库:https://github.com/yanatan16/clj-kubernetes-api
- Scala客户端库:https://github.com/doriordan/skuber
- Perl客户端库:https://metacpan.org/pod/Net::Kubernetes
这些库通常支持HTTPS并负责身份验证,因此不需要使用ambassador容器。