云原生开发者 如何利用 HTTP 客户端调用 Kubernetes API

语言: CN / TW / HK

C T O

   

   

 

Go Rust Python Istio containerd CoreDNS Envoy etcd Fluentd Harbor Helm Jaeger Kubernetes Open Policy Agent Prometheus Rook TiKV TUF Vitess Argo Buildpacks CloudEvents CNI Contour Cortex CRI-O Falco Flux gRPC KubeEdge Linkerd NATS Notary OpenTracing Operator Framework SPIFFE SPIRE     Thanos

云原生开发者 如何使用 HTTP 客户端调用 Kubernetes API

使用 CLI (如 curl )或 GUI (如 postman HTTP 客户端 调用 Kubernetes API 有很多理由。例如,您可能需要对 Kubernetes 对象进行比 kubectl 提供的更细粒度的控制,或者想在尝试从代码访问 API 之前探索它。

本文不仅仅是一个方便的命令列表,而是一个深思熟虑的演练,揭示了一些您在从命令行调用 Kubernetes API 时可能会偶然发现的有趣问题。它涵盖以下主题:

  • 如何获取 Kubernetes API 服务器地址
  • 如何向客户端验证 API 服务器
  • 如何使用证书向 API 服务器验证客户端
  • 如何使用令牌向 API 服务器验证客户端
  • 如何从 Pod 内部调用 Kubernetes API
  • 如何使用 curl Kubernetes 对象执行基本的 CRUD 操作
  • 如何使用 kubectl raw 模式直接访问 Kubernetes API
  • 如何查看哪些 API 请求 kubectl 命令(如 apply )。

设置 Kubernetes 游乐场

如果你没有 Kubernetes 集群可以玩,这里是你可以使用 arkade 快速创建本地游乐场:

https://github.com/alexellis/arkade
$ curl -sLS https://get.arkade.dev | sudo sh
$ arkade get minikube kubectl
$ minikube start --profile cluster1

:warning: curl | sudo sh 很吓人。从 Internet 获取软件包并在笔记本电脑上运行它们。具体使用可参考上述 github 链接

如何获取 Kubernetes API 主机和端口

要调用任何 API ,您首先需要知道其服务器地址。对于 Kubernetes ,每个集群都有一个 API 服务器。因此,查找 API 主机和端口的最简单方法是查看 kubectl cluster-info 输出。例如,在我的 Vagrant 上,它会产生以下几行:

$ kubectl cluster-info
Kubernetes control plane is running at https://192.168.58.2:8443
...

cluster-info 命令显示在当前上下文中选择的集群的 API 地址。但是,如果您有多个集群怎么办?

查找 Kubernetes API 服务器地址的另一种方法是查看 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 服务器发送请求:

$ KUBE_API=$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}')

如何使用 curl 调用 Kubernetes API

实际上,任何 HTTP 客户端( curl httpie wget 甚至 postman )都可以,但我将在本节中坚持使用 curl ,因为我已经习惯了。

向客户端验证 API 服务器

让我们从查询 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.

而且……没用!:see_no_evil:

当我第一次偶然发现类似的错误时,我真的很困惑。但仔细想想,上述错误实际上是有道理的。默认情况下, Kubernetes 通过 HTTPS 公开其 API ,特别是为了向客户端保证 API 服务器的强身份。但是, minikube 使用自签名证书引导我的本地集群。因此, Kubernetes API 服务器的 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 服务器证书的颁发者:

$ 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"
}

耶!:tada:

使用证书向 API 服务器验证客户端

好吧,让我们尝试一些更复杂的东西。列出集群中的所有部署怎么样?

$ 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" ,显然,该用户未授权列出部署资源。

失败的请求不包括任何身份验证方式(尽管如此,它已经过身份验证,但作为匿名用户),所以我需要提供一些额外的信息来获得所需的访问级别。

Kubernetes 支持多种身份验证机制,我将从使用客户端证书对请求进行身份验证开始。

多种身份验证机制: https://kubernetes.io/docs/reference/access-authn-authz/authentication/

但是等一下!什么是客户端证书?

minikube 引导集群时,它还创建了一个 user 。该用户获得了由同一个 minikubeCA 颁发机构签署的证书。由于 Kubernetes API 服务器信任此 CA ,因此在请求中提供此证书将使其作为所述用户进行身份验证。

Kubernetes 没有代表用户的对象。即,不能通过 API 调用将用户添加到集群中。但是,任何提供由集群的证书颁发机构签名的有效证书的用户都被视为已通过身份验证。 Kubernetes 从证书主题中的通用名称字段中获取用户名(例如, 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 服务器发送由该证书认证的请求:

$ 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": [...]
}

看起来有效:heart_eyes:

使用服务帐户令牌向 API 服务器验证客户端

另一种验证 API 请求的方法是使用包含有效服务帐户 JWT 令牌的不记名标头。

服务帐户: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

与用户非常相似,不同的服务帐户将具有不同级别的访问权限。让我们看看使用默认命名空间中的默认服务帐户可以实现什么:

$ JWT_TOKEN_DEFAULT_DEFAULT=$(kubectl get secrets \
    $(kubectl get serviceaccounts/default -o jsonpath='{.secrets[0].name}') \
    -o jsonpath='{.data.token}' | base64 --decode)

从一个简单的任务开始——列出 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 服务帐户:

$ 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)

列出集群级资源:

$ 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": [...]
}

是的,按预期工作:ok_hand:

如何从 Pod 内部调用 Kubernetes API

与任何其他 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 服务器的命令如下所示:

$ 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)"

create、read、watch、update、patch和delete对象

Kubernetes API 支持对 Kubernetes Objects 进行以下操作:

https://github.com/kubernetes/community/blob/7f3f3205448a8acfdff4f1ddad81364709ae9b71/contributors/devel/sig-architecture/api-conventions.md#verbs-on-resources
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 是相当棘手的操作。第一个受到各种版本冲突的影响,第二个的行为因使用的补丁策略而异。https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-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

如何使用 kubectl 调用 Kubernetes API

上面带有证书和令牌的诡计很有趣。至少经历一次是一个很好的练习,可以巩固对客户端和服务器移动部件的理解。但是,当你有一个可以使用的 kubectl 时,每天都这样做可能会有点矫枉过正。

使用 kubectl 代理调用 Kubernetes API

使用正确配置的 kubectl 工具,您可以通过使用 kubectl proxy 命令大大简化 API 访问。

如果你已经工作了 kubectl ,为什么还要直接调用 Kubernetes API

嗯,原因很多。例如,您可能正在开发一个控制器并希望在不编写额外代码的情况下使用 API 查询。或者,您可能对 kubectl 操纵资源时的幕后操作不满意,这使您希望对 Kubernetes 对象上的操作进行更细粒度的控制。

该命令在您的 localhost Kubernetes API 服务器 kubectl proxy 之间创建一个代理服务器(或应用程序级网关) 。但它必须不止于此。不然怎么会这么方便?

代理 kubectl 从调用者那里卸载了相互的客户端-服务器身份验证责任。由于调用者和代理之间的通信是通过 localhost 进行的,因此它被认为是安全的。并且代理本身使用 kubeconfig 文件中选择的当前上下文中的信息来处理客户端-服务器身份验证。

$ kubectl config current-context
cluster1

$ kubectl proxy --port=8080 &

启动代理服务器后,调用 Kubernetes API 服务器就变得简单多了:

$ curl localhost:8080/apis/apps/v1/deployments
{
  "kind": "DeploymentList",
  "apiVersion": "apps/v1",
  "metadata": {
    "resourceVersion": "660883"
  },
  "items": [...]
}

使用 kubectl raw 模式调用 Kubernetes API

我最近学到的另一个很酷的技巧是一些 kubectl 命令支持的原始模式:

# 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 客户端。

https://github.com/kubernetes/kubectl/blob/c4379b82ccb7cd7cb496a38ba1e0dc1ec4700be6/pkg/cmd/get/get.go#L463-L469

这种方法的一些优点是:

  • 原始 REST API 客户端使用相同的身份验证意味着烘焙命令将使用(在 kubeconfig 文件中配置的任何内容)
  • -f 这些命令通过标志支持传统的基于文件的清单输入。

但也有一个缺点——我找不到任何 PATCH WATCH 支持,因此 curl 访问为您提供了更多功能。

Kubernetes API 调用等效于 kubectl 命令

我已经多次提到您可能对特定 kubectl 命令发出的实际请求序列不满意。但是你不读代码怎么能知道这个序列呢?

这是一个不错的技巧 - 您可以将 -v 6 标志添加到任何 kubectl 命令,日志将变得如此冗长,以至于您将开始看到向 Kubernetes API 服务器发出的 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 ,结果可能非常有见地:wink:

想查看实际的请求和响应主体吗?将日志详细程度增加到8。

总结

第一次访问 ·Kubernetes API· 的需求可能很可怕 - 有很多新概念,如资源、 API 组、 kind 、对象、集群、上下文、证书,哦,天哪!但是一旦你在构建块上分解它并通过执行一些琐碎的任务(比如找出 API 服务器地址或使用 curl 调用一堆端点)获得一些实践经验,你很快就会意识到这个想法并不是真的一些新的东西——它只是多年来为我们服务的众所周知的机制的组合—— REST 架构风格、 TLS 证书、 JWT 令牌、对象方案等。

所以,不要害怕并运行一些查询!

参考地址 [1]

参考资料

[1]

参考地址: https://iximiuz.com/en/posts/kubernetes-api-call-simple-http-client/