使用 CLI(如 curl)或 GUI(如 postman )HTTP 客户端调用 Kubernetes API 有很多原因。例如,您可能需要对 Kubernetes 对象进行比 kubectl 提供的更细粒度的控制,或者只是想在尝试从代码访问 API 之前进行探索。
本文不仅仅是一个方便的命令列表,而是一个深思熟虑的演练,揭示了您在从命令行调用 Kubernetes API 时可能会偶然发现的一些问题。它涵盖以下内容:
Kubernetes API 结构
如果你没有 Kubernetes 集群可以做实验,这里是你可以使用 arkade 快速创建本地实验环境:
https://github.com/alexellis/arkade
$ curl -sLS https://get.arkade.dev | sudo sh
$ arkade get minikube kubectl
$ minikube start --profile cluster1
⚠️curl | sudo sh
很吓人。从 Internet 获取软件包并在笔记本电脑上运行它们。由于我没有时间检查我使用的每一段开源代码,我更喜欢隔离和一次性的开发环境。
要调用任何 API,您首先需要知道其服务器地址。对于 Kubernetes,每个集群都有一个 API Server。因此,查找 API 主机和端口的最简单方法是查看kubectl cluster-info
输出。例如:
$ kubectl cluster-info
Kubernetes control plane is running at https://192.168.58.2:8443
...
该cluster-info
命令显示在 当前上下文中选择的集群的 API 地址。
https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters
但是,如果您有多个集群怎么办?查找 Kubernetes API Server 地址的另一种方法是查看 kubeconfig 内容:
https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
$ kubectl config view
apiVersion: v1
clusters:
- name: cluster1
cluster:
...
server: https://192.168.58.2:8443
- name: cluster2
cluster:
...
server: https://192.168.59.2:8443
...
默认情况下,kubectl
查找目录中命名config
的$HOME/.kube
文件。那么,为什么不直接从这个文件中获取 API 地址呢?
原因是潜在的配置合并。KUBECONFIG
通过将 env var 设置为以冒号分隔的位置列表,可以指定多个 kubeconfig 文件。kubectl
在访问集群之前,会尝试将所有 kubeconfig 文件的内容合并到一个配置中。
因此,从上面的列表中选择正确的集群,让我们尝试向其 API Server 发送请求:
$ KUBE_API=$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}')
实际上,任何 HTTP 客户端(curl、httpie、wget 甚至 postman)都可以,但我将在本节中使用 curl。
让我们从 查询 API 的/version
端点开始:
$ curl $KUBE_API/version
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
🙈 上述错误实际上是有道理的。默认情况下,Kubernetes 通过 HTTPS 公开其 API,特别是为了向客户端保证 API Server 的强标识。但是,minikube 使用自签名证书引导本地集群。因此,Kubernetes API Server 的 TLS 证书原来是由 curl 未知的证书颁发机构 (CA) minikubeCA 签名的。由于 curl 无法信任它,因此请求失败。
默认情况下,curl 信任底层操作系统所信任的同一组 CA。例如,在 Ubuntu 或 Debian 上,受信任的 CA 列表可以在/etc/ssl/certs/ca-certificates.crt
. 显然,minikube 不会将其证书添加到此文件中。
幸运的是,minikube 周到地将 CA 证书保存到~/.minikube/ca.crt
:
$ cat ~/.minikube/ca.crt | openssl x509 -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = minikubeCA
Validity
Not Before: Dec 15 20:46:36 2021 GMT
Not After : Dec 14 20:46:36 2031 GMT
Subject: CN = minikubeCA
Subject Public Key Info:
因此,要修复GET /version
请求,只需要通过手动将其指向 minikubeCA 证书来使 curl 信任 API Server 证书的颁发者:
$ curl --cacert ~/.minikube/ca.crt $KUBE_API/version
{
"major": "1",
"minor": "22",
"gitVersion": "v1.22.3",
"gitCommit": "c92036820499fedefec0f847e2054d824aea6cd1",
"gitTreeState": "clean",
"buildDate": "2021-10-27T18:35:25Z",
"goVersion": "go1.16.9",
"compiler": "gc",
"platform": "linux/amd64"
}
耶!🎉
💡提示:在安全的环境中,我更喜欢不安全模式--insecure -k
,它比试图找到颁发者证书更简单。
好的,让我们尝试一些更复杂的东西。列出集群中的所有 Deployment?
$ curl --cacert ~/.minikube/ca.crt $KUBE_API/apis/apps/v1/deployments
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "deployments.apps is forbidden: User \"system:anonymous\" cannot list resource \"deployments\" in API group \"apps\" at the cluster scope",
"reason": "Forbidden",
"details": {
"group": "apps",
"kind": "deployments"
},
"code": 403
}
但没有获取到数据。
与明显未受保护的/version
端点不同,Kubernetes 通常会限制对其 API 端点的访问。
从错误消息中可以清楚地看出,该请求已通过身份验证User "system:anonymous"
,显然,该用户未授权列出 deployment 资源。
失败的请求不包括任何身份验证方式(尽管如此,它已经过身份验证,但作为匿名用户),所以我需要提供一些额外的信息来获得所需的访问级别。
Kubernetes 支持 多种身份验证机制,下面将从使用客户端证书对请求进行身份验证开始。
https://kubernetes.io/docs/reference/access-authn-authz/authentication/
什么是客户证书?
当 minikube 引导集群时,它还创建了一个user
。该用户获得了由同一个 minikubeCA 颁发机构签署的证书。由于 Kubernetes API Server 信任此 CA,因此在请求中提供此证书将使其作为所述用户进行身份验证。
Kubernetes 没有代表user
的对象。即不能通过 API 调用将用户添加到集群中。但是,任何提供由集群的证书颁发机构签名的有效证书的用户,都被视为已通过身份验证。Kubernetes 从证书subject
中的通用名称字段中获取用户名(例如,CN = minikube-user
)。然后,Kubernetes RBAC 子系统判断用户是否有权对资源执行特定操作。
用户证书通常可以在我们已经熟悉的kubectl config view
输出中找到:
$ kubectl config view -o jsonpath='{.users[0]}' | python -m json.tool
{
"name": "cluster1",
"user": {
"client-certificate": "/home/vagrant/.minikube/profiles/cluster1/client.crt",
"client-key": "/home/vagrant/.minikube/profiles/cluster1/client.key"
}
}
让我们快速检查证书内容以确保它是由同一个 CA 签名的:
$ cat ~/.minikube/profiles/cluster1/client.crt | openssl x509 -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 2 (0x2)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = minikubeCA
Validity
Not Before: Dec 26 06:35:56 2021 GMT
Not After : Dec 26 06:35:56 2024 GMT
Subject: O = system:masters, CN = minikube-user
以下是如何使用curl向 Kubernetes API Server 发送由该证书认证的请求:
$ curl $KUBE_API/apis/apps/v1/deployments \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key
{
"kind": "DeploymentList",
"apiVersion": "apps/v1",
"metadata": {
"resourceVersion": "654514"
},
"items": [...]
}
另一种验证 API 请求的方法是使用包含有效服务帐户 JWT 令牌的 header 头。
⚠️ 从 Kubernetes 1.24 开始,不再为每个 ServiceAccount 对象自动生成包含 Service Account 令牌的 Secret API 对象。现在获取服务帐户令牌的推荐方法是使用专用的 TokenRequest API 或相应的kubectl create token
命令。。
与用户非常相似,不同的服务帐户将具有不同级别的访问权限。让我们看看使用默认命名空间中的默认服务帐户可以实现什么:
# Kubernetes <1.24
$ JWT_TOKEN_DEFAULT_DEFAULT=$(kubectl get secrets \
$(kubectl get serviceaccounts/default -o jsonpath='{.secrets[0].name}') \
-o jsonpath='{.data.token}' | base64 --decode)
# Kubernetes 1.24+
$ JWT_TOKEN_DEFAULT_DEFAULT=$(kubectl create token default)
从一个简单的任务开始,列出apps/v1
组中已知的 API 资源类型:
$ curl $KUBE_API/apis/apps/v1/ \
--cacert ~/.minikube/ca.crt \
--header "Authorization: Bearer $JWT_TOKEN_DEFAULT_DEFAULT"
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "apps/v1",
"resources": [...]
}
让我们尝试在默认命名空间中列出实际的部署对象:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \
--cacert ~/.minikube/ca.crt \
--header "Authorization: Bearer $JWT_TOKEN_DEFAULT_DEFAULT"
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "deployments.apps is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"deployments\" in API group \"apps\" in the namespace \"default\"",
"reason": "Forbidden",
"details": {
"group": "apps",
"kind": "deployments"
},
"code": 403
}
显然,用户system:serviceaccount:default:default
甚至没有足够的能力在自己的命名空间中列出 Kubernetes 对象。
让我们尝试kube-system
服务帐户:
# Kubernetes <1.24
$ JWT_TOKEN_KUBESYSTEM_DEFAULT=$(kubectl -n kube-system get secrets \
$(kubectl -n kube-system get serviceaccounts/default -o jsonpath='{.secrets[0].name}') \
-o jsonpath='{.data.token}' | base64 --decode)
# Kubernetes 1.24+
$ JWT_TOKEN_KUBESYSTEM_DEFAULT=$(kubectl -n kube-system create token default)
是可以列出集群级资源的:
$ curl $KUBE_API/apis/apps/v1/deployments \
--cacert ~/.minikube/ca.crt \
--header "Authorization: Bearer $JWT_TOKEN_KUBESYSTEM_DEFAULT"
{
"kind": "DeploymentList",
"apiVersion": "apps/v1",
"metadata": {
"resourceVersion": "656580"
},
"items": [...]
}
是的,按预期工作 👌
与任何其他 Kubernetes 服务非常相似,Kubernetes API 服务地址可通过环境变量提供给 Pod:
$ kubectl run -it --image curlimages/curl --restart=Never mypod -- sh
$ env | grep KUBERNETES
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_HOST=10.96.0.1
Pod 通常还会将 Kubernetes CA 证书和服务帐户机密材料安装在/var/run/secrets/kubernetes.io/serviceaccount/
. 因此,应用以上部分的知识,curl
从 Pod 调用 Kubernetes API Server 的命令如下所示:
$ curl https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/apis/apps/v1 \
--cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
--header "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
Kubernetes API 支持对 Kubernetes Objects 进行以下操作:
GET /<resourcePlural> - Retrieve a list of type <resourceName>.
POST /<resourcePlural> - Create a new resource from the JSON
object provided by the client.
GET /<resourcePlural>/<name> - Retrieves a single resource with the
given name.
DELETE /<resourcePlural>/<name> - Delete the single resource with the
given name.
DELETE /<resourcePlural> - Deletes a list of type <resourceName>.
PUT /<resourcePlural>/<name> - Update or create the resource with the given
name with the JSON object provided by client.
PATCH /<resourcePlural>/<name> - Selectively modify the specified fields of
the resource.
GET /<resourcePlural>?watch=true - Receive a stream of JSON objects
corresponding to changes made to any
resource of the given kind over time.
API 是 RESTful 的,因此上述 HTTP 方法在资源操作上的映射应该看起来很熟悉。
即使文档仅提及JSON 对象,如果Content-Type
标头设置为application/yaml
.
以下是使用curl和 YAML 清单创建新对象的方法:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key \
-X POST \
-H 'Content-Type: application/yaml' \
-d '---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: curlimages/curl
command: ["/bin/sleep", "365d"]
'
以下是如何获取默认命名空间中的所有对象:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key
以及如何通过名称和命名空间获取对象:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key
一种更高级的检索 Kubernetes 资源的方法是持续观察它们的变化:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments?watch=true \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key
请注意,只能监视一组资源。但是,您可以通过提供标签或字段选择器将结果集缩小到单个资源。
以下是更新现有对象的方法:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key \
-X PUT \
-H 'Content-Type: application/yaml' \
-d '---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: curlimages/curl
command: ["/bin/sleep", "730d"] # <-- Making it sleep twice longer
'
以下是如何修补现有对象:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key \
-X PATCH \
-H 'Content-Type: application/merge-patch+json' \
-d '{
"spec": {
"template": {
"spec": {
"containers": [
{
"name": "sleep",
"image": "curlimages/curl",
"command": ["/bin/sleep", "1d"]
}
]
}
}
}
}'
请注意UPDATE和PATCH是相当棘手的操作。第一个受到各种版本冲突的影响,第二个的行为因使用的补丁策略而异。
以下是如何删除对象集合:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key \
-X DELETE
以下是如何删除单个对象:
$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \
--cacert ~/.minikube/ca.crt \
--cert ~/.minikube/profiles/cluster1/client.crt \
--key ~/.minikube/profiles/cluster1/client.key \
-X DELETE
上面带有证书和 token 的方法很有趣。通过上面的练习,可以巩固对客户端和服务端的理解。
使用正确配置的kubectl工具,您可以通过使用kubectl proxy
命令大大简化 API 访问。
为什么还要直接调用 Kubernetes API?🤔
原因很多。例如,您可能正在开发一个控制器并希望在不编写额外代码的情况下使用 API 查询。或者,您可能对kubectl
操纵资源时的幕后操作不满意,您希望对 Kubernetes 对象上的操作进行更细粒度的控制。
kubectl proxy
命令在您的 localhost 和 Kubernetes API Server 之间创建一个代理服务器(或应用程序级网关) 。但它所做的并不止于此。不然怎么会这么方便?
kubectl proxy
从调用者那里接管了相互的客户端~服务器身份验证责任。由于调用者和代理之间的通信是通过localhost进行的,因此它被认为是安全的。代理本身使用 kubeconfig 文件中选择的当前上下文中的信息来处理客户端~服务器身份验证。
$ kubectl config current-context
cluster1
$ kubectl proxy --port=8080 &
启动代理服务器后,调用 Kubernetes API Server 就变得简单多了:
$ curl localhost:8080/apis/apps/v1/deployments
{
"kind": "DeploymentList",
"apiVersion": "apps/v1",
"metadata": {
"resourceVersion": "660883"
},
"items": [...]
}
我最近学到的另一个很酷的技巧是kubectl命令支持的raw 模式:
# Sends HTTP GET request
$ kubectl get --raw /api/v1/namespaces/default/pods
# Sends HTTP POST request
$ kubectl create --raw /api/v1/namespaces/default/pods -f file.yaml
# Sends HTTP PUT request
$ kubectl replace --raw /api/v1/namespaces/default/pods/mypod -f file.json
# Sends HTTP DELETE request
$ kubectl delete --raw /api/v1/namespaces/default/pods
kubectl 是一个非常先进的工具,即使是简单的命令,比如kubectl get
背后也有大量的代码。但是,当使用该--raw
标志时,实现归结为将唯一的参数转换为 API 端点 URL 并调用原始 REST API 客户端。
这种方法的一些优点是:
-f
这些命令通过标志支持传统的基于文件的清单输入。但也有一个缺点:我找不到任何PATCH或WATCH支持,因此curl访问为您提供了更多功能。
我已经多次提到您可能对特定kubectl命令发出的实际请求序列不满意。但是你不读代码怎么能知道这个序列呢?
这是一个不错的技巧:您可以将-v 6
标志添加到任何kubectl命令,日志将变得如此冗长,以至于您将开始看到向 Kubernetes API Server 发出的 HTTP 请求。
例如,您可以通过这种方式了解到该kubectl scale deployment
命令是通过对子资源的PATCH请求实现的/deployments/<name>/scale
:
$ kubectl scale deployment sleep --replicas=2 -v 6
I0116 ... loader.go:372] Config loaded from file: /home/vagrant/.kube/config
I0116 ... cert_rotation.go:137] Starting client certificate rotation controller
I0116 ... round_trippers.go:454] GET https://192.168.58.2:8443/apis/apps/v1/namespaces/default/deployments/sleep 200 OK in 14 milliseconds
I0116 ... round_trippers.go:454] PATCH https://192.168.58.2:8443/apis/apps/v1/namespaces/default/deployments/sleep/scale 200 OK in 12 milliseconds
deployment.apps/sleep scaled
看看kubectl apply -v 6
,结果可能非常有见地 😉
想查看实际的请求和响应主体吗?将日志级别增加到8。
第一次访问 Kubernetes API 的需求可能很头疼,有很多新概念,如资源、API 组、种类、对象、集群、上下文、证书!但是一旦你在构建块上分解它并通过执行一些琐碎的任务(比如找出 API Server 地址或使用 curl 调用一堆端点)获得一些实践经验,你很快就会意识到这并不是新的东西,它只是多年来为我们服务的众所周知的 REST 架构风格、TLS 证书、JWT 令牌、对象方案等机制的组合。
原文: https://iximiuz.com/en/posts/kubernetes-api-call-simple-http-client/
- END -