Downward API只能暴露一个pod自身的元数据,而且只可以暴露部分元数据。某些情况下,我们的应用需要知道其他pod的信息,甚至是集群中的其他资源的信息。Downard API做不到这些,我们需要直接与API Server进行交互。
8.2.1 探究Kubernetes REST API
什么是endpoint(端点)
当一个API与另一个系统交互时,这种通信的接触点被认为是endpoint。对于API,端点可以包括服务器或服务的URL。每个端点都是API可以访问执行其功能所需资源的位置。API使用“请求”和“响应”工作。当一个API从一个web应用程序或web服务器请求信息时,它会收到一个响应。API发送请求和资源所在的位置称为endpoint。API endpoint基本上是服务器或服务的URL的一个花哨的词。
We could do it simpler, by examples:
- /this-is-an-endpoint
- /another/endpoint
- /some/other/endpoint
- /login
- /accounts
- /cart/items
and when put under a domain, it would look like:
- https://example.com/this-is-an-endpoint
- https://example.com/another/endpoint
- https://example.com/some/other/endpoint
- https://example.com/login
- https://example.com/accounts
- https://example.com/cart/items
Can be either http or https, we use https in the example. Also endpoint can be different for different HTTP methods, for example:
- GET /item/{id}
- PUT /item/{id}
would be two different endpoints - one for retrieving, and the other for updating
通过Kubectl proxy访问API Server
kubectl proxy命令运行了一个proxy server来接收你本机的HTTP连接并代理到API server,同时处理身份认证,所以不需要每次请求都上传认证令牌(authentication token)。它也可以确保我们是与真实的API server交互,而不是一个中间人(man in the middle)(通过每个请求都验证服务器证书的方式)。
我们无须传递其他任何参数,因为kubectl已经知道所需的所有参数(API server的URL、认证令等)
通过Kubectl proxy 研究Kubernetes API
输出的paths对应了创建资源时资源定义中的API组和版本信息。/api/v1对应apiVersion: v1,指的是我们创建的基础资源(Pod、Service、ReplicationController等)。因为在Kubernetes初期并没有使用API组的概念,所以这些早期版本出现的基础资源不属于任何组。
注意:这些没有列入API组的初始资源类型现在一般被认为属性核心API组。
研究batch API组的REST endpoint
“name”: “jobs” 行的信息告诉我们API包含了 /apis/batch/v1/jobs endpioint。对于某些特定的资源,API server暴露了额外的API endpoint(例如 jobs/status 路径)
列举集群中所有的Job实例
该endpoint返回了跨namespace的所有Job的清单。
#部署一个Job,然后再次访问REST endpoint,观察输出
cd /root/k8s/
cat >my-job.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: my-job
spec:
template:
metadata:
labels:
app: batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
EOF
kubectl create -f my-job.yaml
通过名称检索一个指定的Job实例
这与 kubectl get jobs my-job -o json
的输出信息完全一致。
8.2.2 从pod内部与API服务器进行交互
想要从pod内部与API server进行交互,需要关注以下三件事情:
- 确定API server的位置
- 确保是与 API server进行交互,而不是一个冒名者
- 进行(客户端)身份验证,否则将不能查看任何内容以及进行任何操作
运行一个pod来尝试与API server进行通信
#实验需要一个包含curl命令的镜像
docker pull tutum/curl
docker tag tutum/curl 10.0.0.10:5000/tutum/curl
docker push 10.0.0.10:5000/tutum/curl
cd /root/k8s/
cat >curl.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: curl
spec:
containers:
- name: main
image: 10.0.0.10:5000/tutum/curl
command: ["sleep", "infinity"] #让容器一直处于运行状态
EOF
kubectl create -f curl.yaml
kubectl exec -it curl -- bash
发现API server地址
一个名为kubenetes的服务在default命名空间被自动暴露,并被配置为指向API server。
我们已经知道每个service都被配置了对应的环境变量,在容器中通过查询KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT两个环境变量就可以获取API server的IP和端口。
同样,我们已经知道每个服务都可以获取得一个DNS条目,所以可以执行 curl https://kubernetes
虽然最简单的绕开这一步骤的方式是使用推荐的-k选项(这也是我们在手工操作API server时通常会使用的方式),但我们应该通过使用curl检查证书的方式验证API服务器的身份,而不是盲目地相信连接的服务器确实是真正的API server。
提示:在真实的应用中,永远不要跳过检查服务器证书的环节。这样做会导致你的应用认证令牌 ( authentication token )暴露给采用中间人攻击方式的攻击者。
验证服务器身份
让我们看一下default-token Secret自动在pod中挂载的目录下的3个文件
该Secret有三个入口(entry)(因此在这个Secret卷中有三个文件)。现在,我们关注一下ca.crt文件。该文件中包含了CA的证书(包含CA的公钥),CA用来对Kubemetes API server证书进行签名。为了验证正在交互的API server,我们需要检查API server的证书是否是由CA签发。
curl允许使用—cacert选项来指定CA 证书
Unauthorized这个响应提醒我们需要关注授权的问题。
我环境中的命令输出
#通过设置CURL_CA_BUNDLE环境变量来简化curl操作:
export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
#现在可以直接执行:
curl https://kubernetes
我们的客户端 curl 现在信任API server,但API server并不确定访问者的身份,所以没有授权允许访问。
使用API server进行客户端身份验证
为了进行身份验证,我们需要身份验证令牌(authentication token),令牌通过default-token Secret来提供, 同时令牌被存放在secret卷的token文件中。
#首先,将token加载到一个环境变量中:
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
#现在,我们可以使用 TOKEN 向API server发送请求:
curl -H "Authorization: Bearer $TOKEN" https://kubernetes
我们通过发送请求报文的HTTP头中的Authorization字段向API server传递了token,API server识别确认token并返回正确的响应。
我的环境略有不同,必须加URL路径才能访问:
#我的环境按照下面方法关闭RBAC之后,可以不加URL路径访问了:
关闭基于角色的访问控制(RBAC)
如果你的Kubernetes集群开启了RBAC (Role-Based Access Control ),service account可能没有被授权访问API server(或只有部分授权)。
为了方便,我们这里直接绕过RBAC:
kubectl create clusterrolebinding permissive-binding \
--clusterrole=cluster-admin \
--group=system:serviceaccounts
这个命令赋予了所有service account(也可以说所有的pod)的cluser-admin权限,允许它们执行任何需要的操作。因为这很危险,不应该在生产环境中执行,仅用于测试。
获取当前运行中pod所有在namespace
#kubectl exec到容器中执行:
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods
这样,我们通过secret卷所挂载的目录中的三个文件( ca.cert、token、namespace),列出了与当前pod运行在同一个namespace下的所有pod。使用同样的方式,我们可以使用 GET、PUT、PATCH等方法来操作其他API对象。
简要说明pod如何与Kubernetes交互
在pod中运行的应用如何正确访问Kubernetes的 API:
- 应该验证 API server的证书是否是CA所签发, CA的证书是在ca.crt文件中。
- 应用应该将它在token文件中持有的token,通过发送包含首部字段Authorization的 http报文,来向 API server进行身份验证。
- 当对pod所在命名空间的 API对象进行CRUD操作时, 应该使用namespace文件来传递命名空间信息到 API server。
定义:CRUD代表Create、 Read、 Update、Delete操作, 与之对应的HTTP方法分别是POST、 GET、 PATCH/PUT、DELETE。
8.2.3 通过ambassador容器简化与 API server的交互
ambassador 容器模式介绍
在主容器运行的同时,启动一个ambassador容器,并在其中运行kubecctl proxy命令, 通过它来实现与API服务器的交互。
在这种模式下,运行在主容器中的应用不是直接与API服务器进行交互,而是通过HTTP协议(不是HTTPS协议)与ambassador连接,并且由ambassador通过HTTPS协议来连接API server,对应用透明地来处理安全问题(见图8.6)。 这种方式同样使用了default-token secret卷中的文件.
因为在同一个pod中的所有容器共享loopback网络接口,所以应用可以使用localhost的端口来访问代理(kubectl proxy命令启动的代理)。
运行带有附加ambassador容器的curl pod
#准备镜像
docker pull luksa/kubectl-proxy:1.6.2
docker tag luksa/kubectl-proxy:1.6.2 10.0.0.10:5000/luksa/kubectl-proxy:1.6.2
docker push 10.0.0.10:5000/luksa/kubectl-proxy:1.6.2
cd /root/k8s/
cat >curl-with-ambassador.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: curl-with-ambassador
spec:
containers:
- name: main
image: 10.0.0.10:5000/tutum/curl
command: ["sleep", "infinity"]
- name: ambassador
image: 10.0.0.10:5000/luksa/kubectl-proxy:1.6.2
EOF
kubectl create -f curl-with-ambassador.yaml
kubectl exec -it curl-with-ambassador -c main -- bash
通过ambassador来与API server进行交互
默认情况下,kubectl proxy绑定8001端口,由于pod中的两个容器共享包括loopback在内的相同的网络接口。
curl向在ambassador容器内运行的proxy发送普通的HTTP请求(不包含任何身份验证相关的首部字段),然后proxy向API server发送HTTPS请求,通过发送token来对客户端进行身份验证,同时通过验证证书来核实服务器的身份。
ambassador容器可以跨多个应用复用,而且与主应用使用的开发语言无关。负面因素是需要运行额外的进程,并且消耗额外的资源。
8.2.4 使用客户端库与API server交互
对于执行更复杂的API请求,可以使用Kubernetes API 的客户端库(library)。