程序印象

Kubernets深入系列: API Server - part 2(译)

2018/04/06 Share

作者:STEFAN SCHIMANSKI AND MICHAEL HAUSENBLAS JULY 21, 2017

翻译:狄卫华

原文:Kubernetes deep dive: API Server – part 2

原文链接:https://blog.openshift.com/kubernetes-deep-dive-api-server-part-2/


本系列翻译链接:


欢迎来到我们的 Kubernetes API Server 深入了解系列的第二部分。 上次 我们 Stefan 和 Michael 介绍了 API Server 作用,术语,并讨论了请求流如何工作。 这次,我们将重点讨论一个我们以前提到的话题:在哪里以及如何以可靠和持久的方式管理 Kubernetes 对象的状态。 您可能还记得,API Server 本身是无状态的,并且是与分布式存储 etcd 直接通信的唯一组件。

etcd 快速介绍

在你的 *nix操作系统中,你知道 /etc 被用来存储配置数据,事实上,etcd 的名字受这个启发,添加了代表分布式的 “d”。 任何分布式系统都可能需要类似 etcd 的组件来存储关于系统状态的数据,使其能够以一致可靠的方式检索状态。 为了协调分布式设置中的数据访问,etcd使用 Raft 协议。 从概念上讲,数据模型 etcd 支持的是键值存储。 在 etcd2 中,这些键形成了一个层次结构,随着 etcd3 的引入,结构变成了一个扁平模型,同时保持了关于分层键的向后兼容性:

img

使用 etcd 的容器化版本,我们可以创建上面的树结构,然后按如下方式检索它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
$ docker run --rm -d -p 2379:2379 \ 
--name test-etcd3 quay.io/coreos/etcd:v3.1.0 /usr/local/bin/etcd \
--advertise-client-urls http://0.0.0.0:2379 --listen-client-urls http://0.0.0.0:2379
$ curl localhost:2379/v2/keys/foo -XPUT -d value="some value"
$ curl localhost:2379/v2/keys/bar/this -XPUT -d value=42
$ curl localhost:2379/v2/keys/bar/that -XPUT -d value=take
$ http localhost:2379/v2/keys/?recursive=true
HTTP/1.1 200 OK
Content-Length: 327
Content-Type: application/json
Date: Tue, 06 Jun 2017 12:28:28 GMT
X-Etcd-Cluster-Id: 10e5e39849dab251
X-Etcd-Index: 6
X-Raft-Index: 7
X-Raft-Term: 2

{
"action": "get",
"node": {
"dir": true,
"nodes": [
{
"createdIndex": 4,
"key": "/foo",
"modifiedIndex": 4,
"value": "some value"
},
{
"createdIndex": 5,
"dir": true,
"key": "/bar",
"modifiedIndex": 5,
"nodes": [
{
"createdIndex": 5,
"key": "/bar/this",
"modifiedIndex": 5,
"value": "42"
},
{
"createdIndex": 6,
"key": "/bar/that",
"modifiedIndex": 6,
"value": "take"
}
]
}
]
}
}

现在我们已经确定了etcd 的工作原理,接下来讨论如何在 Kubernetes 中是如何使用 etcd 的。

在 etcd 中保存集群状态

在 Kubernetes 中,etcd 是控制平面的独立组件。 直到 Kubernetes 1.5.2,我们使用了 etcd2,此后版本切换到 etcd3。 请注意,在 Kubernetes 1.5.x 中,etcd3 仍然在 v2 API 模式下使用,并且将会更改为 v3 API,包括使用的数据模型。 从开发人员的角度来看,这并没有直接的影响,因为 API Server 提供了操作的抽象层 –管理 v2v3 的存储后端实现。 但是从集群管理员的角度来看,需要了解 etcd 版本相关信息,因为不同版本的维护任务(如备份和恢复)需要以不同方式处理。

您可以在启动时通过许多选项修改 API Server 使用 etcd 的方式; 另请注意,下面的输出比较重要的地方将被被突出显示出来:

1
2
3
4
5
6
7
8
9
$ kube-apiserver -h
...
--etcd-cafile string SSL Certificate Authority file used to secure etcd communication.
--etcd-certfile string SSL certification file used to secure etcd communication.
--etcd-keyfile string SSL key file used to secure etcd communication.
...
--etcd-quorum-read If true, enable quorum read.
--etcd-servers List of etcd servers to connect with (scheme://ip:port) …
...

Kubernetes 将其对象作为 JSON 字符串或 Protocol Buffers(简称 “protobuf”)格式存储在 etcd 中。 让我们来看一个具体的例子:我们使用 OpenShift v3.5.0 集群在命名空间 apiserver-sandbox 中启动一个名字叫webserver 的 Pod 。 然后使用 etcdctl 工具,检查 etcd(在我们的环境中,etcd 版本为 3.1.0):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
containers:
- name: nginx
image: tomaskral/nonroot-nginx
ports:
- containerPort: 80

$ kubectl create -f pod.yaml

$ etcdctl ls /
/kubernetes.io
/openshift.io

$ etcdctl get /kubernetes.io/pods/apiserver-sandbox/webserver
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "webserver",
...

那么在kubectl create -f pod.yaml命令执行以后, 对象是如何在 etcd 中保存的呢?下图描述了总览流程:

img

  1. 诸如kubectl 之类的客户端提供期望的对象状态,例如 v1 版本中的 YAML。
  2. kubectl 将 YAML 格式转换为 JSON 格式并发送到 API Server。
  3. 在相同 Kind 的不同版本之间,API Server 可以使用注解(annotations)来执行无损转换,以存储无法在旧API 版本中记录的信息。
  4. API 服务器将输入对象状态转换为规范存储版本,具体取决于自身在 API Server 中的版本,通常是最新的稳定版本,例如 v1
  5. 最后但并非最不重要的是将编码为 JSON 或 protobuf 内容以某个关键字的实际存储在 etcd 中。

您可以通过配置 --storage-media-type 选项来设置 kube-apiserver 首选的序列化,默认为 application/vnd.kubernetes.protobuf

现在让我们来看看无损转换在实践中是如何工作的。 我们将使用类型为 Horizontal Pod Autoscaling(HPA)的Kubernetes 对象,顾名思义,它有一个连接到它的控制器,根据利用率度量指标来监控和更新ReplicationController

首先设置 Kubernetes API 代理(以便我们以后可以直接从本地机器访问它),然后启动 ReplicationController 和 HPA:

1
2
3
4
$ kubectl proxy --port=8080 &
$ kubectl create -f https://raw.githubusercontent.com/mhausenblas/kbe/master/specs/rcs/rc.yaml
kubectl autoscale rc rcex --min=2 --max=5 --cpu-percent=80
kubectl get hpa/rcex -o yaml

现在,使用 httpie – 但如果需要,也可以使用 curl – 我们向 API Server 请求当前稳定版本(autoscaling/v1)中的HPA 对象以及上一个(extensions/v1beta1)中的 HPA 对象,最后比较两个版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ http localhost:8080/apis/extensions/v1beta1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex > hpa-v1beta1.json
$ http localhost:8080/apis/autoscaling/v1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex > hpa-v1.json
$ diff -u hpa-v1beta1.json hpa-v1.json
{
"kind": "HorizontalPodAutoscaler",
- "apiVersion": "extensions/v1beta1",
+ "apiVersion": "autoscaling/v1",
"metadata": {
"name": "rcex",
"namespace": "api-server-deepdive",
- "selfLink": "/apis/extensions/v1beta1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex",
+ "selfLink": "/apis/autoscaling/v1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex",
"uid": "ad7efe42-50ed-11e7-9882-5254009543f6",
"resourceVersion": "267762",
"creationTimestamp": "2017-06-14T10:39:00Z"
},
"spec": {
- "scaleRef": {
+ "scaleTargetRef": {
"kind": "ReplicationController",
"name": "rcex",
- "apiVersion": "v1",
- "subresource": "scale"
+ "apiVersion": "v1"
},
"minReplicas": 2,
"maxReplicas": 5,
- "cpuUtilization": {
- "targetPercentage": 80
- }
+ "targetCPUUtilizationPercentage": 80

您可以看到 HorizontalPodAutoscaler 的格式(schema)已从 v1beta1 更改为 v1。 API Server 能够在这些版本之间无损地转换,而不管哪个版本实际存储在 etcd 中。

借助存储流程的基本知识,我们现在将重点放在 API Server 如何对有效负载进行编码和解码以及将其存储在 JSON 或 protobuf 中的细节上,同时也考虑使用的 etcd 版本。

详细描述状态流的序列化

API Server 将所有已知的 Kubernetes 对象类型保存在名为 Scheme类型注册表(type registry) 中。 在这个注册表中,记录了每个版本的类型都是如何定义的,以及如何转换它们,如何创建新对象以及如何将对象编码和解码为 JSON 或 protobuf 。

img

当 API Server 从 kubectl 接收到一个对象时,它会从 HTTP 路径获知期望的版本。 它使用正确版本的 Scheme 创建匹配的空对象,并使用 JSON 或 protobuf 解码器转换 HTTP 负载。 解码器将二进制有效载荷转换为创建的对象。

解码对象是给定类型的支持版本中的一种。 对于某些类型,在其整个开发过程中几个版本。 为了避免这个问题,API Server 必须知道如何在每对版本之间进行转换(例如v1⇔v1alpha1,v1⇔v1beta1,v1beta1⇔v1alpha1),API Server 为每个版本使用一个特殊的 “内部” 版本类型。 此类型的内部版本是所有受支持版本的超集,具有其所有功能。 它将传入的对象首先转换为内部版本,然后转换为存储版本:

1
v1beta1 ⇒ internal ⇒ v1

在转换的第一步中,如用户省略了某些字段,它将会设置默认值。 想象一下 v1beta1 没有在 v1 中添加的某个必填字段。 在这种情况下,用户甚至无法填写该字段的值。 然后转换步骤将设置此字段的默认值以创建有效的内部对象。

验证和准入

转换过程中还有两个更重要的步骤。 实际流程如下所示:

1
2
v1beta1 ⇒ internal ⇒    |        ⇒       |    ⇒  v1  ⇒ json/yaml ⇒ etcd
准入(admission) 验证(validation)

准入和验证步骤在写入 etcd 之前对对象的创建和更新进行管理。角色定义如下:

  1. 准入

    通过验证集群全局约束来检查是否可以创建或更新对象,并可能根据集群配置设置默认值。

    在 Kubernetes 中有很多这样的对象,还有更多像 OpenShift 这样的支持多租户的 Kubernetes。 他们之中有一些是:

    • NamespaceLifecycle: 如果命名空间不存在,则拒绝命名空间上下文中的所有传入请求。
    • LimitRanger : 在名称空间的每个资源基础上使用限制。
    • ServiceAccount :为一个 Pod 创建一个服务帐户。
    • DefaultStorageClass :设置 PersistentVolumeClaims 存储类的默认值,以防用户未提供有效值。
    • ResourceQuota :为群集上的当前用户强制执行配额限制,并可能在配额不足时拒绝请求。
  2. 验证

    检查传入对象(在创建和更新过程中)是否合法,是否只包含有效值,例如:

    • 检查是否设置了所有必填字段。
    • 检查所有字符串是否具有有效格式(例如,只包含小写字符)。
    • 检查没有设置冲突的字段(例如,具有相同名称的两个容器)。

    验证不会查看该类型的其他实例或其他类型的实例。 换句话说,验证是针对每个对象的本地静态检查,独立于任何 API Server 配置。

    可以使用 --admission-control=<plugins>标志启用/禁用准入插件。 它们中的大多数也可以由群集管理员配置。 而且,在Kubernetes 1.7 中,提供了 webhook 机制来扩展准入机制和一个初始化器的概念,以便使用控制器实现新对象的自定义准入。

存储对象的迁移

有关存储对象迁移的最后说明:将 Kubernetes 升级到新版本时,备份群集状态和遵循每个版本的记录迁移步骤越来越重要。这源于从 etcd2 到 etcd3 的转变,以及 Kubernetes 种类及其版本的不断发展。

在 etcd 中,每个对象都存储在首选的存储版本中。但是,随着时间的推移,它可能会在你的 etcd 存储中有一个非常旧版本的对象。如果此版本已被弃用并最终从 API Server 中删除,那么您将无法再解码其 protobuf 或 JSON 。出于这个原因,存在迁移过程在群集升级之前重写这些对象。

以下资源可以帮助您应对这一挑战:

  • 请参阅 集群管理参考版本 1.6 以及升级到群集管理文档中的其他API版本部分。
  • 在 OpenShift 文档中,请参阅 手动运行群集升级
  • 如果您希望工具(作为 CLI 工具或 Web 应用程序)协助您完成此过程,请查看 ReShifter ,该工具可以为Kubernetes 群集以及 OpenShift 群集进行备份/恢复和迁移。

下一次,在API Server系列的第三部分中,我们将讨论如何使用Custom Resource Definitions 和 用户API Server来扩展 Kubernetes API。

另外,我们希望将Sergiusz Urbaniak的荣誉授予与 etcd 相关的支持。


除特别声明本站文章均属原创(翻译内容除外),如需要转载请事先联系,转载需要注明作者原文链接地址。


CATALOG
  1. 1. etcd 快速介绍
  2. 2. 在 etcd 中保存集群状态
  3. 3. 详细描述状态流的序列化
  4. 4. 验证和准入
  5. 5. 存储对象的迁移