本篇文章来介绍一些在 Kubernetes 集群的 Pod 中如何访问 Kubernetes API。
知识储备
首先,在一个新创建的 Kubernetes 集群中,我们会发现在 default
命名空间下默认就有一个 ServiceAccount 资源和 Secret 资源,如下:
$ kubectl get serviceaccounts
NAME SECRETS AGE
default 1 16d
$ kubectl get secrets
NAME TYPE DATA AGE
default-token-pvpbd kubernetes.io/service-account-token 3 16d
如果你有心的话,你会发现每个命名空间下其实都有一个这样的名称包含 default
的 ServiceAccount 和 Secret 资源。
比如我这里新建一个 test
命名空间,然后查看该命名空间下就会自动创建一个 ServiceAccount 资源和 Secret 资源,如下:
$ kubectl create ns test
namespace/test created
$ kubectl get serviceaccounts -n test
NAME SECRETS AGE
default 1 5s
$ kubectl get secrets -n test
NAME TYPE DATA AGE
default-token-62dhm kubernetes.io/service-account-token 3 9s
从名称就可以看出来,上述两个 ServiceAccount 其实是默认的 ServiceAccount,而每个 ServiceAccount 资源都有一个与之对应的 Secret 资源。
那么这个 Secret 资源到底保存了啥玩意儿呢?其实按其名称以及资源类型我们就可以稍加推断了,首先 Secret 资源肯定是用来存放相对私密的数据,而这个 Secret 的名称命名中包含 default-token
就有点像访问令牌啥的了。。推测就看到这里,反正集群是咱们的咱们就直接剥开它来看,还猜个啥哈哈。。
先获取一下 default
命名空间下的 default-token
的详细信息:
$ kubectl get secrets default-token-pvpbd -o yaml
apiVersion: v1
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR3akNDQXFxZ0F3SUJBZ0lVTFh1bzZZay9lVFREbitwRFc4L1lHZ1ZmMTJNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1p6RUxNQWtHQTFVRUJoTUNRMDR4RVRBUEJnTlZCQWdUQ0ZOb1pXNTZhR1Z1TVJFd0R3WURWUVFIRXdoVAphR1Z1ZW1obGJqRU1NQW9HQTFVRUNoTURhemh6TVE4d0RRWURWUVFMRXdaVGVYTjBaVzB4RXpBUkJnTlZCQU1UCkNtdDFZbVZ5Ym1WMFpYTXdIaGNOTWpBeE1URXhNREV5TmpBd1doY05NalV4TVRFd01ERXlOakF3V2pCbk1Rc3cKQ1FZRFZRUUdFd0pEVGpFUk1BOEdBMVVFQ0JNSVUyaGxibnBvWlc0eEVUQVBCZ05WQkFjVENGTm9aVzU2YUdWdQpNUXd3Q2dZRFZRUUtFd05yT0hNeER6QU5CZ05WQkFzVEJsTjVjM1JsYlRFVE1CRUdBMVVFQXhNS2EzVmlaWEp1ClpYUmxjekNDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFOSWF1K2lQVVVmMnB3aWsKWFJIY2RyUWhkR29TaGxBSnlSYVh4dm8xSzdISlphQU5GMTYycVZIS3p6aXorSUVjK0RsK2dqL05NSCs1SVBHbApMMFI5bDNKU1hNeWdsbVh3UnZ4MlFHeEplTVZ4R0lCZ0VDOFh6NG9UM2F6ZkNGZUpDRnl1L2tXQjNpVkpjRzVqCkpSd3krdjh1OUdGTlFETEtQZStQclZBcGI2VFZBZDhncUY0Vm1KbTYvUDFmcFFBcCtpVVhxRlBTNm9Bc0hxVzgKOVpOZE52ODVFTG8wN0x2SDJQZkxsVklsT2RoZ0dDbFZ1YjY4VDU5bDd4eVV6R1JDV3JIclRBTUM3aENmOGZaQwpXbE96V3hrMW9vemVqcnBJWDhMM1g1YzJ4dEp6RVQ5L2s0aVJGeHNJNDRiVFpTejZGR2ttKzhoYjBaQ3lxdUxBCkVEKzZtaU1DQXdFQUFhTm1NR1F3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQkFmOEMKQVFJd0hRWURWUjBPQkJZRUZEVzl6VFg3Ujl1c2xGMGM5bHBORVVhRnRtdnVNQjhHQTFVZEl3UVlNQmFBRkRXOQp6VFg3Ujl1c2xGMGM5bHBORVVhRnRtdnVNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUM4VlBNbGJIcStpMktjCnRldVdvMmdRSFp3ekVITTgrOFdUUzZ0ZGNIb3BJNnh4anUwc29BQWg0ZG1heVNndTdoMndNY3c3bFQvOGNYeTEKTC9kb2RObVJkZUUzQmh2eE5OdUF6VitTR2pCbEs0TEFQblBPZngwa3l3cVNFVURMOXZkSWE3MXhzUnVGQS9WaApVWmZPV2xsbXREOEhYSFZ1Ny9jSVNYUEt5QWFzQ2lDbks2bStmNE91aVdxZng3Ty8ydkgvODJVYlIyczZTdVFNCjNIK2JoaWhnWWozcHRyNVRvTXhJRkJtaXh5RFBDWVFiOHpzaWJPUUhGaWppVlNQR2c0YWJpWFZ6NzYydmVPOGIKNUtYc3lrK0dCNTl0UmtpWEVaVkNDY2tXREhyMUlyM21ncHVUZUhBSUlWb0thazNsdWUyV0xxMjJZcDVSOXkxQQoxQVJyZ0VxQQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
namespace: ZGVmYXVsdA==
token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkluUkpSRFYzVEdsV1JHNVBXVlEwTFVGek4zZHFTeTA1Ymw5RFpsRkxNRU00YVhGaWRIb3pNemN3V1ZraWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbVJsWm1GMWJIUXRkRzlyWlc0dGNIWndZbVFpTENKcmRXSmxjbTVsZEdWekxtbHZMM05sY25acFkyVmhZMk52ZFc1MEwzTmxjblpwWTJVdFlXTmpiM1Z1ZEM1dVlXMWxJam9pWkdWbVlYVnNkQ0lzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVnlkbWxqWlMxaFkyTnZkVzUwTG5WcFpDSTZJalptWlRZeE0yTTFMV1UzT1dRdE5HRXhOUzFpWmpFeUxUUm1aamsxTmpJNE5ESmlNQ0lzSW5OMVlpSTZJbk41YzNSbGJUcHpaWEoyYVdObFlXTmpiM1Z1ZERwa1pXWmhkV3gwT21SbFptRjFiSFFpZlEueGYzdnNIS2FGUkEydVpLNTZua1VfZkhEanRaS2hHRXFyY2F2c3c4YjFESkVLVlQ3Y05LWWVuV0NRY2sxeXVpdEZIYVZXZkNSUTZoWnlvdm0waHFMQ1JiaWhmRjFtQ0NEYm40ZUVpZE1JeEZLdUFBWTR0QWNuNGVoZ3pGbHJ2Tk1oUnQ4V2xJM29YNnBMNXFhdlFlQ3IwSC1NT3ZBVnRYaEE3MnZWUUNtSDBINDRYdmdaTWpORm5UV0Z0ekhFbFRsaHdWejBaQ1hOdHI3R05wbG1sR19Eb3RQckhyWXFHY05DLVhmS1liQTRBSXBremZoTUdTQ3pNWEtwZmYwWW92TGpWNWdwazZiQVM4S0RNb2Y4Zi1rNll6WXRGR0RGSkFOVXNSZGc2TXgyeC0wU25KZmN2dndMY1dGR0ZFTEpUNzd2bGpiRTdkZkQ0SXJfVXdhV0ptaGlR
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: 6fe613c5-e79d-4a15-bf12-4ff9562842b0
creationTimestamp: "2020-11-11T01:33:06Z"
name: default-token-pvpbd
namespace: default
resourceVersion: "202"
selfLink: /api/v1/namespaces/default/secrets/default-token-pvpbd
uid: 97b44ff4-4014-4d6c-ba27-e607e6b8ba01
很明显,我们可以看到其中保存了三个键值对,这三个键分别为 ca.crt
、namespace
、token
,见名知意,它们各自的含义如下:
ca.crt
:为 API Server 签发证书的 CA 的证书;namespace
:命名空间;token
:认证令牌;
我们知道,Secret 存放的资源会使用 Base64 进行编码,这里为验证我们的猜想可以对这几个值进行解码,如下:
$ echo LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR3akNDQXFxZ0F3SUJBZ0lVTFh1bzZZay9lVFREbitwRFc4L1lHZ1ZmMTJNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1p6RUxNQWtHQTFVRUJoTUNRMDR4RVRBUEJnTlZCQWdUQ0ZOb1pXNTZhR1Z1TVJFd0R3WURWUVFIRXdoVAphR1Z1ZW1obGJqRU1NQW9HQTFVRUNoTURhemh6TVE4d0RRWURWUVFMRXdaVGVYTjBaVzB4RXpBUkJnTlZCQU1UCkNtdDFZbVZ5Ym1WMFpYTXdIaGNOTWpBeE1URXhNREV5TmpBd1doY05NalV4TVRFd01ERXlOakF3V2pCbk1Rc3cKQ1FZRFZRUUdFd0pEVGpFUk1BOEdBMVVFQ0JNSVUyaGxibnBvWlc0eEVUQVBCZ05WQkFjVENGTm9aVzU2YUdWdQpNUXd3Q2dZRFZRUUtFd05yT0hNeER6QU5CZ05WQkFzVEJsTjVjM1JsYlRFVE1CRUdBMVVFQXhNS2EzVmlaWEp1ClpYUmxjekNDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFOSWF1K2lQVVVmMnB3aWsKWFJIY2RyUWhkR29TaGxBSnlSYVh4dm8xSzdISlphQU5GMTYycVZIS3p6aXorSUVjK0RsK2dqL05NSCs1SVBHbApMMFI5bDNKU1hNeWdsbVh3UnZ4MlFHeEplTVZ4R0lCZ0VDOFh6NG9UM2F6ZkNGZUpDRnl1L2tXQjNpVkpjRzVqCkpSd3krdjh1OUdGTlFETEtQZStQclZBcGI2VFZBZDhncUY0Vm1KbTYvUDFmcFFBcCtpVVhxRlBTNm9Bc0hxVzgKOVpOZE52ODVFTG8wN0x2SDJQZkxsVklsT2RoZ0dDbFZ1YjY4VDU5bDd4eVV6R1JDV3JIclRBTUM3aENmOGZaQwpXbE96V3hrMW9vemVqcnBJWDhMM1g1YzJ4dEp6RVQ5L2s0aVJGeHNJNDRiVFpTejZGR2ttKzhoYjBaQ3lxdUxBCkVEKzZtaU1DQXdFQUFhTm1NR1F3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQkFmOEMKQVFJd0hRWURWUjBPQkJZRUZEVzl6VFg3Ujl1c2xGMGM5bHBORVVhRnRtdnVNQjhHQTFVZEl3UVlNQmFBRkRXOQp6VFg3Ujl1c2xGMGM5bHBORVVhRnRtdnVNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUM4VlBNbGJIcStpMktjCnRldVdvMmdRSFp3ekVITTgrOFdUUzZ0ZGNIb3BJNnh4anUwc29BQWg0ZG1heVNndTdoMndNY3c3bFQvOGNYeTEKTC9kb2RObVJkZUUzQmh2eE5OdUF6VitTR2pCbEs0TEFQblBPZngwa3l3cVNFVURMOXZkSWE3MXhzUnVGQS9WaApVWmZPV2xsbXREOEhYSFZ1Ny9jSVNYUEt5QWFzQ2lDbks2bStmNE91aVdxZng3Ty8ydkgvODJVYlIyczZTdVFNCjNIK2JoaWhnWWozcHRyNVRvTXhJRkJtaXh5RFBDWVFiOHpzaWJPUUhGaWppVlNQR2c0YWJpWFZ6NzYydmVPOGIKNUtYc3lrK0dCNTl0UmtpWEVaVkNDY2tXREhyMUlyM21ncHVUZUhBSUlWb0thazNsdWUyV0xxMjJZcDVSOXkxQQoxQVJyZ0VxQQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== | base64 -d
-----BEGIN CERTIFICATE-----
MIIDwjCCAqqgAwIBAgIULXuo6Yk/eTTDn+pDW8/YGgVf12MwDQYJKoZIhvcNAQEL
BQAwZzELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFNoZW56aGVuMREwDwYDVQQHEwhT
aGVuemhlbjEMMAoGA1UEChMDazhzMQ8wDQYDVQQLEwZTeXN0ZW0xEzARBgNVBAMT
Cmt1YmVybmV0ZXMwHhcNMjAxMTExMDEyNjAwWhcNMjUxMTEwMDEyNjAwWjBnMQsw
CQYDVQQGEwJDTjERMA8GA1UECBMIU2hlbnpoZW4xETAPBgNVBAcTCFNoZW56aGVu
MQwwCgYDVQQKEwNrOHMxDzANBgNVBAsTBlN5c3RlbTETMBEGA1UEAxMKa3ViZXJu
ZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANIau+iPUUf2pwik
XRHcdrQhdGoShlAJyRaXxvo1K7HJZaANF162qVHKzziz+IEc+Dl+gj/NMH+5IPGl
L0R9l3JSXMyglmXwRvx2QGxJeMVxGIBgEC8Xz4oT3azfCFeJCFyu/kWB3iVJcG5j
JRwy+v8u9GFNQDLKPe+PrVApb6TVAd8gqF4VmJm6/P1fpQAp+iUXqFPS6oAsHqW8
9ZNdNv85ELo07LvH2PfLlVIlOdhgGClVub68T59l7xyUzGRCWrHrTAMC7hCf8fZC
WlOzWxk1oozejrpIX8L3X5c2xtJzET9/k4iRFxsI44bTZSz6FGkm+8hb0ZCyquLA
ED+6miMCAwEAAaNmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
AQIwHQYDVR0OBBYEFDW9zTX7R9uslF0c9lpNEUaFtmvuMB8GA1UdIwQYMBaAFDW9
zTX7R9uslF0c9lpNEUaFtmvuMA0GCSqGSIb3DQEBCwUAA4IBAQC8VPMlbHq+i2Kc
teuWo2gQHZwzEHM8+8WTS6tdcHopI6xxju0soAAh4dmaySgu7h2wMcw7lT/8cXy1
L/dodNmRdeE3BhvxNNuAzV+SGjBlK4LAPnPOfx0kywqSEUDL9vdIa71xsRuFA/Vh
UZfOWllmtD8HXHVu7/cISXPKyAasCiCnK6m+f4OuiWqfx7O/2vH/82UbR2s6SuQM
3H+bhihgYj3ptr5ToMxIFBmixyDPCYQb8zsibOQHFijiVSPGg4abiXVz762veO8b
5KXsyk+GB59tRkiXEZVCCckWDHr1Ir3mgpuTeHAIIVoKak3lue2WLq22Yp5R9y1A
1ARrgEqA
-----END CERTIFICATE-----
$ echo ZGVmYXVsdA== | base64 -d
default
可以看到和猜想的一样,而 namespace
正是当前命名空间的名称,可以通过 test
命名空间下的 default-token
验证一下:
$ kubectl get secrets default-token-62dhm -n test -o yaml | grep 'namespace:'
namespace: dGVzdA==
namespace: test
$ echo dGVzdA== | base64 -d
test
那么还有一个 token
是干啥用的呢?上面其实已经说明了它主要是做认证令牌用,即服务端(API Service)能够通过该 Token 获取到对应的 ServiceAccount 信息,从而针对对应的 ServiceAccount 完成认证、鉴权操作。
测试
上面花了如此大篇幅来聊 ServiceAccount 和 Secret,当然是因为此篇文章的主题【在 Kubernetes 集群的 Pod 中如何访问 Kubernetes API】与之是密切相关的。
这里我们可以先创建一个测试用的 Deployment 资源,为了能够直观的请求的 API,所以使用的镜像必须包含 curl
命令,我这里就使用 tutum/curl
了,资源 YAML 如下:
$ cat test-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 1
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
containers:
- name: curl
image: tutum/curl
command: ['sleep', '3000']
$ kubectl apply -f test-deploy.yaml
deployment.apps/test-deployment created
# 检查 Pod 成功运行了
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
test-deployment-5cf6d49cb4-lzsbb 1/1 Running 0 46s
接下来查看该 Pod 的详细 YAML:
$ kubectl get pod test-deployment-5cf6d49cb4-lzsbb -o yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: test-app
pod-template-hash: 5cf6d49cb4
name: test-deployment-5cf6d49cb4-lzsbb
namespace: default
...
spec:
containers:
- command:
- sleep
- "3000"
image: tutum/curl
name: curl
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-pvpbd
readOnly: true
...
volumes:
- name: default-token-pvpbd
secret:
defaultMode: 420
secretName: default-token-pvpbd
...
上述输出中我删除了一些不重要的内容,最终我们会发现我们新创建的 Pod 默认将上面提到的当前命名空间下的 default-token-pvpbd
注册为卷(Volume),并将其挂载到了容器的 /var/run/secrets/kubernetes.io/serviceaccount
目录,我们可以进入容器中查看:
$ kubectl exec -it test-deployment-5cf6d49cb4-lzsbb -- bash
root@test-deployment-5cf6d49cb4-lzsbb:/# ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token
此时在 Pod 内部我们就可以借助这三个文件与 API Server 进行通信了,由于 API Server 是开启 HTTPS 的,所以我们这里会使用 ca.crt
来对 API Server 证书进行校验,使用 token
作为 API Server 的访问令牌,如下:
# 让 curl 默认使用该 CA 证书校验
root@test-deployment-5cf6d49cb4-lzsbb:/$ export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# 将 Token 设置为变量
root@test-deployment-5cf6d49cb4-lzsbb:/$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# 访问 API Server
root@test-deployment-5cf6d49cb4-lzsbb:/$ curl -H "Authorization: Bearer $TOKEN" https://kubernetes
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "forbidden: User \"system:serviceaccount:default:default\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {
},
"code": 403
}
从上述 API Server 响应的内容可以看出 API Server 根据 Token 识别出了 ServiceAccount 为 default
命名空间下的 default
ServiceAccount,但此时该 ServiceAccount 显然是不具有访问 API 的权限的,所以被 Forbidden 了。
那么现在就是要为 default:default
ServiceAccount 授权了,反正是测试我这里就直接给该 ServiceAccount 绑定集群管理员角色:
$ kubectl create clusterrolebinding permissive-binding \
--clusterrole=cluster-admin \
--serviceaccount=default:default
clusterrolebinding.rbac.authorization.k8s.io/permissive-binding created
# 或为所有 ServiceAccount 绑定集群管理员角色
# $ kubectl create clusterrolebinding permissive-binding \
# --clusterrole=cluster-admin \
# --group=system:serviceaccounts
在实际使用中应该为对应的 Pod 规划一个专门用于访问 API Server 的 ServiceAccount,并为其绑定合适的角色。
再次在 Pod 内对 API Server 进行访问:
root@test-deployment-5cf6d49cb4-lzsbb:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes
{
"paths": [
"/api",
"/api/v1",
"/apis",
"/apis/",
...
可以看到 API Server 正常响应 API 内容了,下面我们就可以正常通过 API Server API 获取集群信息啦~~
以获取当前 Pod 所在命名空间的所有 Pod 信息为例:
# 获取 Pod 所在的命名空间设置为变量
root@test-deployment-5cf6d49cb4-lzsbb:/$ NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
# 获取 Pod 所在命名空间的所有 Pod
root@test-deployment-5cf6d49cb4-lzsbb:/$ curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/default/pods",
"resourceVersion": "691463"
},
"items": [
{
"metadata": {
...
此时 API Server 就以 Json 形式响应了 Pod 列表了,这样我们就成功在 Pod 内部通过 API 获取到了集群信息啦~~如果你需要获取其它信息无非就是访问的路径不同而已了~
评论区