深度剖析Kubernetes API Server三部曲 - part 2

欢迎来到深入学习 Kubernetes API Server 的系列文章的第二部分。在上一部分中我们对 APIserver 总体,相关术语及 request 请求流进行探讨说明。在本部分文章中,我们主要聚焦于探究 如何对 Kubernetes  对象的状态以一种可靠,持久的方式进行管理。之前的文章中提到过  API Server 自身是无状态的,并且它是唯一能够与分布式存储 etcd 直接通信的组件。

etcd 的简要说明

*nix 操作系统中,我们一般使用 /etc 来存储相关配置数据。实际上 etcd 的名字就是由此发展而来,在 etc 后面加上个 ”d” 表示 ”distributed” 分布式。任何分布式系统都需要有像 etcd 这样能够存储系统数据的东西,使其能够以一致和可靠的方式检索相关数据。为了能实现分布式的数据访问, etcd 使用 Raft   协议。从概念上讲, etcd 支持的数据模型是键值( key-value )存储。在 etcd2 中,各个 key 是以层次结构存在,而在 etcd3 中这个就变成了遍布模型,但同时也保持了层次结构方式的兼容性。

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

$ 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 是如何工作的,接下去我们继续讨论 etcd Kubernetes 是如何被使用的。

集群中的 etcd

Kubernetes 中, etcd 是控制平面中的一耳光独立组成部分。在 Kubernetes1.5.2 版本之前,我们使用的是 etcd2 版本,而在 Kubernetes1.5.2 版本之后我们就转向使用 etcd3 版本了。值得注意的是在 Kubernetes1.5.x 版本中 etcd 依旧使用的是 v2 API 模型,之后这将开始变为 v3 API 模型,包括使用的数据模型。站在开发者角度而言这个似乎没什么直接影响,因为 API Server 与存储之前是抽象交互,而并不关心后端存储的实现是 etcd v2 还是 v3 。但是如果是站在集群管理员的角度来看,还是需要知道 etcd 使用的是哪个版本,因为集群管理员需要日常对数据进行一些备份,恢复的维护操作。

你可以 API Server 的相关启动项中配置使用 etcd 的方式, API Server etcd 相关启动项参数如下所示:

$ 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  存储在 etcd 中的数据,是以 JSON 字符串或 Protocol Buffers   格式存储。下面我们来看一个具体的例子:在 apiserver-sandbox 的命名空间中创建一个 webserver pod 。然后我们使用   etcdctl 工具来查看相关 etcd (在本环节中 etcd 版本为 3.1.0 )数据。

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

...

下面我们来看一下这个 pod 对象是如何最终存储到 etcd 中,通过 kubectl create -f pod.yaml 的方式。下图描绘了这个总体流程:

1.        客户端(比如 kubectl )提供一个理想状态的对象,比如以 YAML 格式, v1 版本提供。

2.        Kubectl YAML 转换为 JSON 格式,并发送。

3.        对应同类型对象的不同版本, API Server 执行无损耗转换。对于老版本中不存在的字段则存储在 annotations 中。

4.        API Server 将接受到的对象转换为规范存储版本,这个版本由 API Server 指定,一般是最新的稳定版本,比如 v1

5.        最后将对象通过 JSON protobuf 方式解析为一个 value ,通过一个特定的 key 存入 etcd 当中。

我们可以通过配置   kube-apiserver 的启动参数 --storage-media-type 来决定想要序列化数据存入 etcd 的格式,默认情况下为 application/vnd.kubernetes.protobuf 格式。我们也可以通过配置 --storage-versions 启动参数,来确定存入 etcd 的每个群组 Group 对象的默认版本号。

现在让我们来看看无损转换是如何进行的,我们将使用 Kubernetes  对象 Horizontal Pod Autoscaling  (HPA) 来列举说明。 HPA 顾名思义是指通过监控资源的使用情况结合 ReplicationController 控制 Pod 的伸缩。

首先我们期待一个 API 代理(以便于我们能够在本地直接访问它),并启动 ReplicationController ,以及 HPA 

$ 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  请求获取 HPA 对象使用当前的稳定版本( autoscaling/v1 ),或者使用之前的版本( extensions/v1beta1 ),获取的两个版本的区别如下所示:

$ 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

我们能够看到 HorizontalPodAutoscale 的版本从 v1beta1 变为了 v1 API server 能够在不同的版本之前无损耗转换,不论在 etcd 中实际存的是哪个版本。

在了解整个存储流程之后,我们下面来探究一下 API server 如何将数据进行编码,解码存入 etcd 中以 JSON  protobuf 的方式,同时也考虑到 etcd 的版本。

API Server 将所有已知的 Kubernetes 对象类型保存在名为 Scheme Go 类型注册表( registry )中。在此注册表中,定义每种了 Kubernetes 对象的类型以及如何转换它们,如何创建新对象,以及如何将对象编码和解码为 JSON protobuf

API Server 从客户端接收到一个对象时,比如 kubectl ,通过 HTTP 路径,能够知道这个对象的具体版本号。首先会为这个对象使用对应的版本 Scheme 创建一个空对象,然后通过 JSON protobuf HTTP 传过来的对象内容进行解码转换。解码完成后创建对象,存入 etcd 中。

API 中可能会有很多版本,如果要支持每个版本之间的直接转换,这样往往处理起来比较麻烦。比如某个 API 下面有三个版本,那么它就要支持一个版本到另两个版本的直接转换(比如 v1 v1alpha1, v1 v1beta1, v1beta1 v1alpha1 )。为了避免这个问题,在 API server 中有一个特别的  “internal”  版本。当两个版本之间需要转换时,先转换为 internal 版本,再转换为相应转换的版本。这样的话,每个版本只要支持能够转换为 internal 版本,那么就能够与其它任何版本进行间接的转换。所以一个对象先转换为 internal 版本,然后在转换为稳定的 v1 版本,然后在存入 etcd 中。

v1beta1 internal v1

在转换的第一步中,如果某些字段用户没有赋值指定,那么这些会被赋为一个默认值。比如在 v1beta1   中肯定没有在 v1 版本新增的一个字段。在这种情况下,用户肯定无法在 v1beta1   版本为这个字段赋值。这时候,在转换的第一步中,我们会为这个字段赋一个默认值以生成一个有效的 internal

校验及准入

在转换过程中有两个重要的步骤,如下图所示:

v1beta1 internal     |           |      v1  json/yaml etcd

                     admission    validation

准入和校验是创建和更新对象存入 etcd 之前必须通过的步骤。它们的一些规则如下所示:

1.        准入( Admission ):查看集群中的一些约束条件是否允许创建或更新此对象,并根据此集群的相关配置为对象设置一些默认值。在 Kubernetes 有很多这种约束条件,下面列举一些例子:

NamespaceLifecycle :如果命名空间不存在,则拒绝该命名空间下的所有传入请求。

LimitRanger :强制限制命名空间中资源的使用率。

ServiceAccount :为 pod 创建  service account 

DefaultStorageClass :如果用户没有为 PersistentVolumeClaims 赋值,那么将它设置为一个默认值。

ResourceQuota :对群集上的当前用户强制执行配额约束,如果配额不足,可能会拒绝请求。

2.        校验( Validation ):检查传入对象(在创建和更新期间)是否格式是否合法以及相关值是否有效。比如:

1.        检查必填字段是否已填。

2.        检查字符串格式是否正确(比如只允许小写形式)。

3.        是否有些字段存在冲突(比如,有两个容器的名字一样)。

校验( Validation )并不关心其它类型的对象实例,换言之,它只关心每个对象的静态检查,无关集群配置。

准入( Admission )可以用 flag --admission-control=<plugins> 来启动或禁用。它们中的大多数可以有集群管理配置。此外,在 Kubernetes 1.7 中可以用 webhook 机制来扩展准入机制,使用控制器来实现对对象的传统的校验。

迁移存储对象

关于存储对象迁移的最后说明:当 Kubernetes 需要升级到新的版本时,根据每个版本的相关文档步骤备份相关集群的数据是至关重要的。这一方面是由于 etcd2 etcd3 的转变,另一方面是由于 Kubernetes  对象的 Kind version 的不断发展。

etcd 中,每个对象是首选存储版本( preferred storage version )存在的。但是,随着时间的推移, etcd 存储中的对象可能以一个非常老的版本存在。如果在将来某个时间这个对象版本被废弃了,那么将无法再解码它的 protobuf  JSON 。因此,在集群升级之前需要重写,迁移这些数据。 下面这些资料能够对 version 切换提供一些帮助:

请参阅集群管理文档升级 API 版本部分。 Upgrading to a different API version

下一次,在深入学习 Kubernetes APIServer 的第三部分中,我们将讨论如何使用 Custom Resource Definitions 扩展和自定义 API 资源。https://www.huaweicloud.com/product/cce.html

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31543630/viewspace-2213243/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/31543630/viewspace-2213243/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值