APIServer 概念理解

API Server 是K8s重要的管理API 层。它负责提供restful api访问端点, 并且将数据持久化到etcd server中。Kubernetes 集群中,API Server 扮演着交互入口的位置。API Server 不仅负责和 etcd 交互(其他组件不会直接操作 etcd,只有 API Server 这么做),并切对外提供统一的API调用入口, 所有的交互都是以 API Server 为核心的。


image.png

整体组件功能

API Server 提供了一下的功能:
整个集群管理的 API 接口:所有对集群进行的查询和管理都要通过 API 来进行。集群内部的组件(如kubelet)也是通过Apiserver更新和同步数据。
集群内部各个模块之间通信的枢纽:所有模块之前并不会之间互相调用,而是通过和 API Server 打交道来完成自己那部分的工作。
集群安全控制:API Server 提供的验证和授权保证了整个集群的安全。
数据中心枢纽: API Server 负责和 Etcd 交互存放集群用到的运行数据。


image.png

结构分析

如下图, 从结构上来分析 API Server。

Resetful 是对外提供http(https)的接口,用来对外提供与集群统一的交互手段。
Cacher 是针对查询到的数据的缓存中心。
Watcher 模块 是负责从Etcd获取数据,其中可注册多个Watcher,即关注多个不同的数据。
Translate 模块 是负责将从Etcd获取到的数据转化为本地统一数据的接口,当Watcher获取到数据后就将其发送给Translate模块,Translate根据数据类型使用注册的对应的翻译接口进行翻译。

image.png

流程分析

如图所示,Apiserver可以左右两部分,左半部分是Apiserver使用观察者模式获取更新需要的数据,右半部分则是Apiserver接受外部调用并注册观察者Watcher,并从Watcher中最终获取到需要的数据。


image.png

外部调用会先通过Apiserver使用registerResourceHandlers注册的Api接口
该接口会调用ListResource解析收到的用户请求,调用watch接口向Apiserver注册观察者Watcher,并调用serverWatch等待返回的数据。
Watcher会由Apiserver 启动的携程调用dispatchEvents从通道中获取并注册到watcher集合中
Apiserver 启动时会携程调用startCaching,其主要协程启动了etcdWatch, translate,
process三个协程, 并在主流程中调用watchHandler
etchWatch根据注册的观察者watcher向etcd注册关注相关的数据,当获取到数据时则通过通道发送给translate。
translate负责解析从etcd获取到的数据, 并通过通道发送给watchHandler。
WatchHander 调用store.(add|delete|update)对本地缓存数据做更新。
translate解析完成数据时,发现有被watcher的数据会被filter并通过通道给process协程进行处
process协程调用sendWatchCacheEvent将获取到的数据通过通道发送给serveWatch。
serveWatch将数据返回给关注该资源的用户。

组件构成

kube-apiserver 共由 3 个组件构成(Aggregator. KubeAPIServer. APIExtensionServer),这些组件依次通过 Delegation 处理请求:
Aggregator:暴露的功能类似于一个七层负载均衡,将来自用户的请求拦截转发给其他服务器,并且负责整个 APIServer 的 Discovery 功能;也负责处理ApiService,注册对应的扩展api。
KubeAPIServer :负责对请求的一些通用处理,认证. 鉴权等,以及处理各个内建资源的 REST 服务;
APIExtensionServer:主要处理 CustomResourceDefinition(CRD)和 CustomResource(CR)的 REST 请求,也是 Delegation 的最后一环,如果对应 CR 不能被处理的话则会返回 404。

从上面的话有点难懂。我的理解是

KubeAPIServer

首先一个APIServer 和 部署的service 不同,他们都是API 层,但是API server 是提供的管理API, 而部署的Service 提供的用户的business API ,这两部分是完全不同的。因此APIServer 用到的API是APIService。 一个KubeAPIServer 就会启动一个APIService,细节在稍后介绍。

APIExtensionServer

在 Kubernetes 里,Resource 也叫资源,实际上是 Kubernetes API 里的一个 Endpoint 类型,比如内置的 Pod,也是一种资源类型。Custome Resources 自定义资源是在 Kubernetes 的默认配置下,对资源类型的扩展。
实际上,自定义资源类型只是让用户可以创建和查询到资源对象,当结合 Custom Controller 自定义控制器,才能像 Kubernetes 里其他对象一样去做 CRUD 的操作。通过自定义控制器,可以控制自定义资源的生命周期。通过这种贯穿于 Kubernetes 里的控制循环的思想,可以让集群里的资源对象按照预定义的状态,不断地去调整。

在官网里提示的,有两种方式来让用户去扩展 Kubernetes 的资源类型。

  • CustomResourceDefinitions: 复用 Kubernetes 的 API Server,CRDs + CRD Controller
  • API server aggregation: 需要编写 API Server,提供更细粒度的控制

这两种方式,其实挺不好理解的。简单理解的话,就是后者需要写更多的代码,去创建自己的 API Server,这里说的 API Server 跟 Master 上的 API Server 是一个类似的概念,都不完全相同,这里的 API Server 更想表达的意思是为了实现自定义资源来扩展 Kubernetes 的功能或者说工作负荷,用户需要按照 API Server 的模式来去创建自己的 API Server,然后通过 aggregate 这一层组件来实现聚合,也就是说,用户为了增加自定义资源,并且希望注册到 Core API Server 中,是需要写更多的代码的,但是他比前者带来一个更显著的好处就是,他的灵活性是更高的,毕竟你写的是类似 Core API Server 的东西,关于资源的存储、认证这些都可以做的更加灵活。

目前社区上看到很多的 Operator 多数是基于 CustomResourceDefinitions 来做的,例如 Spark Operator,Tf Operator 等等。前文讲的customized controller就是这个方法。
Operator 的模式,是由 coreos 提出的,结合自定义资源和自定义控制器的常见套路。定义一个自定义资源并不难,甚至非常简单,也就几行 yaml,或者代码里一个 struct 结构体就完事了,真正比价麻烦的是写一个自定义控制器来控制这个自定义资源对象

APIExtensionServer 就是来处理这个CustomResourceDefinitions的请求的。具体细节在以后介绍。

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: foos.samplecontroller.k8s.io
  # for more information on the below annotation, please see
  # https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/2337-k8s.io-group-protection/README.md
  annotations:
    "api-approved.kubernetes.io": "unapproved, experimental-only; please get an approval from Kubernetes API reviewers if you're trying to develop a CRD in the *.k8s.io or *.kubernetes.io groups"
spec:
  group: samplecontroller.k8s.io
  versions:
    - name: v1alpha1
      served: true
      storage: true
      schema:
        # schema used for validation
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                deploymentName:
                  type: string
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 10
            status:
              type: object
              properties:
                availableReplicas:
                  type: integer
  names:
    kind: Foo
    plural: foos
  scope: Namespaced

Aggregator

那么Aggregator 是干啥的呢,Aggregator 是管理APIService的,就是上文说的处理扩展 Kubernetes 的资源类型第二种方式的。缺省是只有KubeAPIServer一个APIService,但是如果用户自己实现了一个APIServer,就需要创建一个APIService,这个APIService会绑定一个service 对象。Aggregator 就会自动注册这个用户创建的APIService,从而把APIService对象对应的service 加一个代理,当用户访问时, 把这个request 代理给自己创建的APIService对应的service 某个pod上。

apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1alpha1.wardle.example.com
spec:
  insecureSkipTLSVerify: true
  group: wardle.example.com
  groupPriorityMinimum: 1000
  versionPriority: 15
  service:
    name: api
    namespace: wardle
  version: v1alpha1

再看service.yaml

apiVersion: v1
kind: Service
metadata:
  name: api
  namespace: wardle
spec:
  ports:
  - port: 443
    protocol: TCP
    targetPort: 443
  selector:
    apiserver: "true"

再看deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wardle-server
  namespace: wardle
  labels:
    apiserver: "true"
spec:
  replicas: 1
  selector:
    matchLabels:
      apiserver: "true"
  template:
    metadata:
      labels:
        apiserver: "true"
    spec:
      serviceAccountName: apiserver
      containers:
      - name: wardle-server
        # build from staging/src/k8s.io/sample-apiserver/artifacts/simple-image/Dockerfile
        # or
        # docker pull k8s.gcr.io/e2e-test-images/sample-apiserver:1.17.4
        # docker tag k8s.gcr.io/e2e-test-images/sample-apiserver:1.17.4 kube-sample-apiserver:latest
        image: kube-sample-apiserver:latest
        imagePullPolicy: Never
        args: [ "--etcd-servers=http://localhost:2379" ]
      - name: etcd
        image: quay.io/coreos/etcd:v3.5.1

Aggregator类似于一个七层负载均衡,将来自用户的请求拦截转发给其他服务器,并且负责整个 APIServer 的 Discovery 功能。通过APIServices对象关联到某个Service来进行请求的转发,其关联的Service类型进一步决定了请求转发形式。Aggregator包括一个GenericAPIServer和维护自身状态的Controller。其中 GenericAPIServer主要处理apiregistration.k8s.io组下的APIService资源请求。主要controller包括:

  • apiserviceRegistrationController:负责APIServices中资源的注册与删除;
  • availableConditionController:维护APIServices的可用状态,包括其引用Service是否可用等;
  • autoRegistrationController:用于保持API中存在的一组特定的APIServices;
  • crdRegistrationController:负责将CRD GroupVersions自动注册到APIServices中;
  • openAPIAggregationController:将APIServices资源的变化同步至提供的OpenAPI文档;

如何访问kubernetes API

k8s通过kube-apiserver这个进程提供服务,该进程运行在单个k8s-master节点上。默认有两个端口。

k8s通过kube-apiserver这个进程提供服务,该进程运行在单个k8s-master节点上。默认有两个端口。

1. 本地端口

  1. 该端口用于接收HTTP请求;
  2. 该端口默认值为8080,可以通过API Server的启动参数“--insecure-port”的值来修改默认值;
  3. 默认的IP地址为“localhost”,可以通过启动参数“--insecure-bind-address”的值来修改该IP地址;
  4. 非认证或授权的HTTP请求通过该端口访问API Server。

2.安全端口

  1. 该端口默认值为6443,可通过启动参数“--secure-port”的值来修改默认值;
  2. 默认IP地址为非本地(Non-Localhost)网络端口,通过启动参数“--bind-address”设置该值;
  3. 该端口用于接收HTTPS请求;
  4. 用于基于Tocken文件或客户端证书及HTTP Base的认证;
  5. 用于基于策略的授权;
  6. 默认不启动HTTPS安全访问控制。

kubernetes API访问方式

Kubernetes REST API可参考https://kubernetes.io/docs/api-reference/v1.6/

1. curl

curl localhost:``8080/api

curl localhost:``8080/api/v1/pods

curl localhost:``8080/api/v1/services

curl localhost:``8080/api/v1/replicationcontrollers

2. Kubectl Proxy

Kubectl Proxy代理程序既能作为API Server的反向代理,也能作为普通客户端访问API Server的代理。通过master节点的8080端口来启动该代理程序。

kubectl proxy --port=8080 &

具体见kubectl proxy --help

3. kubectl客户端

命令行工具kubectl客户端,通过命令行参数转换为对API Server的REST API调用,并将调用结果输出。

命令格式:kubectl [command] [options]

具体可参考Kubernetes常用命令

4. 编程方式调用

使用场景:

1、运行在Pod里的用户进程调用kubernetes API,通常用来实现分布式集群搭建的目标。

2、开发基于kubernetes的管理平台,比如调用kubernetes API来完成Pod、Service、RC等资源对象的图形化创建和管理界面。可以使用kubernetes提供的Client Library。

具体可参考https://github.com/kubernetes/client-go

通过API Server访问Node、Pod和Service

k8s API Server最主要的REST接口是资源对象的增删改查,另外还有一类特殊的REST接口—k8s Proxy API接口,这类接口的作用是代理REST请求,即kubernetes API Server把收到的REST请求转发到某个Node上的kubelet守护进程的REST端口上,由该kubelet进程负责响应。

1. Node相关接口

关于Node相关的接口的REST路径为:/api/v1/proxy/nodes/{name},其中{name}为节点的名称或IP地址。
/api/v1/proxy/nodes/``{name}``/pods/ #列出指定节点内所有Pod的信息
/api/v1/proxy/nodes/``{name}``/stats/ #列出指定节点内物理资源的统计信息
/api/v1/prxoy/nodes/``{name}``/spec/ #列出指定节点的概要信息

这里获取的Pod信息来自Node而非etcd数据库,两者时间点可能存在偏差。如果在kubelet进程启动时加--enable-debugging-handles=true参数,那么kubernetes Proxy API还会增加以下接口:

/api/v1/proxy/nodes/{name}/run #在节点上运行某个容器
/api/v1/proxy/nodes/{name}/exec #在节点上的某个容器中运行某条命令
/api/v1/proxy/nodes/{name}/attach #在节点上attach某个容器
/api/v1/proxy/nodes/{name}/portForward #实现节点上的Pod端口转发
/api/v1/proxy/nodes/{name}/logs #列出节点的各类日志信息
/api/v1/proxy/nodes/{name}/metrics #列出和该节点相关的Metrics信息
/api/v1/proxy/nodes/{name}/runningpods #列出节点内运行中的Pod信息
/api/v1/proxy/nodes/{name}/debug/pprof #列出节点内当前web服务的状态,包括CPU和内存的使用情况

2. Pod相关接口

/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*} ``#访问pod的某个服务
/api/v1/proxy/namespaces/{namespace}/pods/{name} ``#访问Pod
#以下写法不同,功能一样
/api/v1/namespaces/{namespace}/pods/{name}/proxy/``{path:*} ``#访问pod的某个服务接口
/api/v1/namespaces/{namespace}/pods/{name}/proxy #访问Pod

3. Service相关接口

/api/v1/proxy/namespaces/{namespace}/services/{name}

Pod的proxy接口的作用:在kubernetes集群之外访问某个pod容器的服务(HTTP服务),可以用Proxy API实现,这种场景多用于管理目的,比如逐一排查Service的Pod副本,检查哪些Pod的服务存在异常问题。

集群功能模块之间的通信

kubernetes API Server作为集群的核心,负责集群各功能模块之间的通信,集群内各个功能模块通过API Server将信息存入etcd,当需要获取和操作这些数据时,通过API Server提供的REST接口(GET\LIST\WATCH方法)来实现,从而实现各模块之间的信息交互。

1. kubelet与API Server交互

每个Node节点上的kubelet定期就会调用API Server的REST接口报告自身状态,API Server接收这些信息后,将节点状态信息更新到etcd中。kubelet也通过API Server的Watch接口监听Pod信息,从而对Node机器上的POD进行管理。

监听信息 kubelet动作 备注
新的POD副本被调度绑定到本节点 执行POD对应的容器的创建和启动逻辑
POD对象被删除 删除本节点上相应的POD容器
修改POD信息 修改本节点的POD容器

2. kube-controller-manager与API Server交互

kube-controller-manager中的Node Controller模块通过API Server提供的Watch接口,实时监控Node的信息,并做相应处理。

3. kube-scheduler与API Server交互

Scheduler通过API Server的Watch接口监听到新建Pod副本的信息后,它会检索所有符合该Pod要求的Node列表,开始执行Pod调度逻辑。调度成功后将Pod绑定到目标节点上。

image

API Server参数介绍

API Server 主要是和 etcd 打交道,并且对外提供 HTTP 服务,以及进行安全控制,因此它的命令行提供的参数也主要和这几个方面有关。下面是一些比较重要的参数以及说明(不同版本参数可能会有不同):

参数 含义 默认值
–advertise-address 通过该 ip 地址向集群其他节点公布 api server 的信息,必须能够被其他节点访问 nil
–allow-privileged 是否允许 privileged 容器运行 false
–admission-control 准入控制 AlwaysAdmit
–authorization-mode 授权模式 ,安全接口上的授权 AlwaysAllow
–bind-address HTTPS 安全接口的监听地址 0.0.0.0
–secure-port HTTPS 安全接口的监听端口 6443
–cert-dir TLS 证书的存放目录 /var/run/kubernetes
–etcd-prefix 信息存放在 etcd 中地址的前缀 “/registry”
–etcd-servers 逗号分割的 etcd server 地址 []
–insecure-bind-address HTTP 访问的地址 127.0.0.1
–insecure-port HTTP 访问的端口 8080
–log-dir 日志存放的目录
–service-cluster-ip-range service 要使用的网段,使用 CIDR 格式,参考 kubernetes 中 service 的定义

restful api

k8s的api-server组件负责提供restful api访问端点, 并且将数据持久化到etcd server中. 那么k8s是如何组织它的restful api的?

namespaced resources

所谓的namespaced resources,就是这个resource是从属于某个namespace的, 也就是说它不是cluster-scoped的资源. 比如pod, deployment, service都属于namespaced resource. 那么我们看一下如何请求一个namespaced resources.

http://localhost:8080/api/v1/namespaces/default/pods/test-pod

可以看出, 该restful api的组织形式是:

api api版本  namespaces 所属的namespace 资源种类 所请求的资源名称
api v1 namespaces default pods test-pod
这里api version如果是v1的话,表示这是一个很稳定的版本了, 以后不会有大的修改,并且当前版本所支持的所有特性以后都会兼容. 而如果版本号是v1alpha1, v1beta1之类的,则不保证其稳定性.

non-namespaced resources

http://localhost:8080/apis/rbac.authorization.k8s.io/v1/clusterroles/test-clusterrole

这里可以观察到它clusterrole与pod不同, apis表示这是一个非核心api. rbac.authorization.k8s.io指代的是api-group, 另外它没有namespaces字段, 其他与namespaced resources类似.不再赘述.

non-resource url

这类资源和pod, clusterrole都不同. 例如

http://localhost:8080/healthz/etcd

这就是用来确认etcd服务是不是健康的.它不属于任何namespace,也不属于任何api版本.

总结, k8s的REST API的设计结构为:

[api/apis] / api-group / api-version / namespaces / namespace-name / resource-kind / resource-name

apis / rbac.authorization.k8s.io / v1 / namespaces / default / roles / test-role

所有的api结构都可以通过访问相关断点来获取信息, 最低一层为api-server的地址, 访问结果如下:

{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/",
    "/apis/admissionregistration.k8s.io",
    "/apis/admissionregistration.k8s.io/v1beta1",
    "/apis/apiextensions.k8s.io",
    "/apis/apiextensions.k8s.io/v1beta1",
    "/apis/apiregistration.k8s.io",
    "/apis/apiregistration.k8s.io/v1",
    "/apis/apiregistration.k8s.io/v1beta1",
    "/apis/apps",
    "/apis/apps/v1",
    "/apis/apps/v1beta1",
    "/apis/apps/v1beta2",
    "/apis/authentication.k8s.io",
    "/apis/authentication.k8s.io/v1",
    "/apis/authentication.k8s.io/v1beta1",
    "/apis/authorization.k8s.io",
    "/apis/authorization.k8s.io/v1",
    "/apis/authorization.k8s.io/v1beta1",
    "/apis/autoscaling",
    "/apis/autoscaling/v1",
    "/apis/autoscaling/v2beta1",
    "/apis/autoscaling/v2beta2",
    "/apis/batch",
    "/apis/batch/v1",
    "/apis/batch/v1beta1",
    "/apis/certificates.k8s.io",
    "/apis/certificates.k8s.io/v1beta1",
    "/apis/coordination.k8s.io",
    "/apis/coordination.k8s.io/v1beta1",
    "/apis/events.k8s.io",
    "/apis/events.k8s.io/v1beta1",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/apis/networking.k8s.io",
    "/apis/networking.k8s.io/v1",
    "/apis/policy",
    "/apis/policy/v1beta1",
    "/apis/rbac.authorization.k8s.io",
    "/apis/rbac.authorization.k8s.io/v1",
    "/apis/rbac.authorization.k8s.io/v1beta1",
    "/apis/scheduling.k8s.io",
    "/apis/scheduling.k8s.io/v1beta1",
    "/apis/storage.k8s.io",
    "/apis/storage.k8s.io/v1",
    "/apis/storage.k8s.io/v1beta1",
    "/healthz",
    "/healthz/autoregister-completion",
    "/healthz/etcd",
    "/healthz/log",
    "/healthz/ping",
    "/healthz/poststarthook/apiservice-openapi-controller",
    "/healthz/poststarthook/apiservice-registration-controller",
    "/healthz/poststarthook/apiservice-status-available-controller",
    "/healthz/poststarthook/bootstrap-controller",
    "/healthz/poststarthook/ca-registration",
    "/healthz/poststarthook/generic-apiserver-start-informers",
    "/healthz/poststarthook/kube-apiserver-autoregistration",
    "/healthz/poststarthook/rbac/bootstrap-roles",
    "/healthz/poststarthook/scheduling/bootstrap-system-priority-classes",
    "/healthz/poststarthook/start-apiextensions-controllers",
    "/healthz/poststarthook/start-apiextensions-informers",
    "/healthz/poststarthook/start-kube-aggregator-informers",
    "/healthz/poststarthook/start-kube-apiserver-admission-initializer",
    "/healthz/poststarthook/start-kube-apiserver-informers",
    "/logs",
    "/metrics",
    "/openapi/v2",
    "/swagger-2.0.0.json",
    "/swagger-2.0.0.pb-v1",
    "/swagger-2.0.0.pb-v1.gz",
    "/swagger.json",
    "/swaggerapi",
    "/version"
  ]
}

关于Group、Version、Resource

image.png

Group
Group即资源组,在kubernetes对资源进行分组时,对应的数据结构就是Group,源码路径:k8s.io/apimachinery/pkg/apis/meta/v1/types.go ,如下,可见Group有自己的名称和版本:

type APIGroup struct {
    TypeMeta `json:",inline"`
    Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
    Versions []GroupVersionForDiscovery `json:"versions" protobuf:"bytes,2,rep,name=versions"`
    PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty" protobuf:"bytes,3,opt,name=preferredVersion"`
    ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"`
}

在kubernetes中有两种资源组:有组名资源组和无组名资源组(也叫核心资源组Core Groups),它们都很常见;
deployment有组名,pod没有组名,咱们把它俩的OpenAPI放在一起对比就一目了然了:


image.png

Version
Version即版本,这个好理解,kubernetes的版本分为三种:
Alpha:内部测试版本,如v1alpha1
Beta:经历了官方和社区测试的相对稳定版,如v1beta1
Stable:正式发布版,如v1、v2
如下图红框,资源组batch之下有v1和v2alpha1两个版本,每个版本下都有多个资源:


image.png

数据结构源码还是在types.go文件中,如下:

type APIVersions struct {
    TypeMeta `json:",inline"`
    Versions []string `json:"versions" protobuf:"bytes,1,rep,name=versions"`
    ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,2,rep,name=serverAddressByClientCIDRs"`
}

Resource
Resource资源在kubernetes中的重要性是不言而喻的,常见的pod、service、deployment这些都是资源,下面是关于资源的一些小结:
在kubernetes环境被实例化的资源即资源对象(ResourceObject);
资源被分为持久性(Persistent Entity)和非持久性(Ephemeral Entity),持久性如deployment,创建后会在etcd保存,非持久性如pod;
资源的源码:

type APIResource struct {
    Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
    SingularName string `json:"singularName" protobuf:"bytes,6,opt,name=singularName"`
    Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"`
    Group string `json:"group,omitempty" protobuf:"bytes,8,opt,name=group"`
    Version string `json:"version,omitempty" protobuf:"bytes,9,opt,name=version"`
    Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
    Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"`
    ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,5,rep,name=shortNames"`
    Categories []string `json:"categories,omitempty" protobuf:"bytes,7,rep,name=categories"`
    StorageVersionHash string `json:"storageVersionHash,omitempty" protobuf:"bytes,10,opt,name=storageVersionHash"`
}

kubernetes为资源准备了8种操作:create、delete、deletecollection、get、list、patch、update、watch,每一种资源都支持其中的一部分,这在每个资源的API文档中可以看到;
资源支持以命名空间(namespace)进行隔离;
资源对象描述文件在日常操作中频繁用到,一共由五部分组成:apiVersion、kind、metadata、spec、status,下图是官方的deployment描述文件,用于创建3个nginx pod,对着红框和文字就了解每个部分的作用了:

上图并没有status,该部分是用来反应当前资源对象状态的,体现在资源的数据结构中,如下所示:

type Deployment struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
    Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
    Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

API Server安装和运行

API Server 是通过提供的 kube-apiserver 二进制文件直接运行的,下面的例子指定了 service 分配的 ip 范围,etcd 的地址,和对外提供服务的 ip 地址:


 [root@localhost vagrant]# curl http://192.168.8.100:8080
 
{
"paths": [
"/api",
"/api/v1",
"/apis",
"/apis/apps",
"/apis/apps/v1alpha1",
"/apis/autoscaling",
"/apis/autoscaling/v1",
"/apis/batch",
"/apis/batch/v1",
"/apis/batch/v2alpha1",
"/apis/extensions",
"/apis/extensions/v1beta1",
"/apis/policy",
"/apis/policy/v1alpha1",
"/apis/rbac.authorization.k8s.io",
"/apis/rbac.authorization.k8s.io/v1alpha1",
"/healthz",
"/healthz/ping",
"/logs/",
"/metrics",
"/swaggerapi/",
"/ui/",
"/version"
]

直接访问 8080 端口,API Server 会返回它提供了哪些接口:

 [root@localhost vagrant]# curl http://192.168.8.100:8080
 
{
"paths": [
"/api",
"/api/v1",
"/apis",
"/apis/apps",
"/apis/apps/v1alpha1",
"/apis/autoscaling",
"/apis/autoscaling/v1",
"/apis/batch",
"/apis/batch/v1",
"/apis/batch/v2alpha1",
"/apis/extensions",
"/apis/extensions/v1beta1",
"/apis/policy",
"/apis/policy/v1alpha1",
"/apis/rbac.authorization.k8s.io",
"/apis/rbac.authorization.k8s.io/v1alpha1",
"/healthz",
"/healthz/ping",
"/logs/",
"/metrics",
"/swaggerapi/",
"/ui/",
"/version"
]

而目前最重要的路径是 /api/v1,里面包含了 kubernetes 所有资源的操作,比如下面的 nodes:


 ➜ ~ http http://192.168.8.100:8080/api/v1/nodes
 
HTTP/1.1 200 OK
 
Content-Length: 112
 
Content-Type: application/json
 
Date: Thu, 08 Sep 2016 08:14:45 GMT
 
{
 
"apiVersion": "v1",
 
"items": [],
 
"kind": "NodeList",
 
"metadata": {
 
"resourceVersion": "12",
 
"selfLink": "/api/v1/nodes"
}

API 以 json 的形式返回,会通过 apiVersion 来说明 API 版本号,kind 说明请求的是什么资源。不过这里面的内容是空的,因为目前还没有任何 kubelet 节点接入到我们的 API Server。对应的,pod 也是空的:


 ➜ ~ http http://192.168.8.100:8080/api/v1/pods
 
HTTP/1.1 200 OK
 
Content-Length: 110
 
Content-Type: application/json
 
Date: Thu, 08 Sep 2016 08:18:53 GMT
 
{
 
"apiVersion": "v1",
 
"items": [],
 
"kind": "PodList",
 
"metadata": {
 
"resourceVersion": "12",
 
"selfLink": "/api/v1/pods"
 
}
 

添加节点

添加节点也非常简单,启动 kubelet 的时候使用 --api-servers 指定要接入的 API Server 就行。kubelet 启动之后,会把自己注册到指定的 API Server,然后监听 API 对应 pod 的变化,根据 API 中 pod 的实际信息来管理节点上 pod 的生命周期。

现在访问 /api/v1/nodes 就能看到已经添加进来的节点:

 ➜ ~ http http://192.168.8.100:8080/api/v1/nodes
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 08 Sep 2016 08:27:44 GMT
Transfer-Encoding: chunked
{
"apiVersion": "v1",
"items": [
{
"metadata": {
"annotations": {
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
},
"creationTimestamp": "2016-09-08T08:23:01Z",
"labels": {
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
"kubernetes.io/hostname": "192.168.8.100"
},
"name": "192.168.8.100",
"resourceVersion": "65",
"selfLink": "/api/v1/nodes/192.168.8.100",
"uid": "74e16eba-759d-11e6-b463-080027c09e5b"
},
"spec": {
"externalID": "192.168.8.100"
},
"status": {
"addresses": [
{
"address": "192.168.8.100",
"type": "LegacyHostIP"
},
{
"address": "192.168.8.100",
"type": "InternalIP"
}
],
"allocatable": {
"alpha.kubernetes.io/nvidia-gpu": "0",
"cpu": "1",
"memory": "502164Ki",
"pods": "110"
},
"capacity": {
"alpha.kubernetes.io/nvidia-gpu": "0",
"cpu": "1",
"memory": "502164Ki",
"pods": "110"
},
"conditions": [
{
"lastHeartbeatTime": "2016-09-08T08:27:36Z",
"lastTransitionTime": "2016-09-08T08:23:01Z",
"message": "kubelet has sufficient disk space available",
"reason": "KubeletHasSufficientDisk",
"status": "False",
"type": "OutOfDisk"
},
{
"lastHeartbeatTime": "2016-09-08T08:27:36Z",
"lastTransitionTime": "2016-09-08T08:23:01Z",
"message": "kubelet has sufficient memory available",
"reason": "KubeletHasSufficientMemory",
"status": "False",
"type": "MemoryPressure"
},
{
"lastHeartbeatTime": "2016-09-08T08:27:36Z",
"lastTransitionTime": "2016-09-08T08:24:56Z",
"message": "kubelet is posting ready status",
"reason": "KubeletReady",
"status": "True",
"type": "Ready"
}
],
"daemonEndpoints": {
"kubeletEndpoint": {
"Port": 10250
}
},
"images": [
{
"names": [
"172.16.1.41:5000/nginx:latest"
],
"sizeBytes": 425626718
},
{
"names": [
"172.16.1.41:5000/hyperkube:v0.18.2"
],
"sizeBytes": 207121551
},
{
"names": [
"172.16.1.41:5000/etcd:v3.0.4"
],
"sizeBytes": 43302056
},
{
"names": [
"172.16.1.41:5000/busybox:latest"
],
"sizeBytes": 1092588
},
{
"names": [
"172.16.1.41:5000/google_containers/pause:0.8.0"
],
"sizeBytes": 241656
}
],
"nodeInfo": {
"architecture": "amd64",
"bootID": "48955926-11dd-4ad3-8bb0-2585b1c9215d",
"containerRuntimeVersion": "docker://1.10.3",
"kernelVersion": "3.10.0-123.13.1.el7.x86_64",
"kubeProxyVersion": "v1.3.1-beta.0.6+fbf3f3e5292fb0",
"kubeletVersion": "v1.3.1-beta.0.6+fbf3f3e5292fb0",
"machineID": "b9597c4ae5f24494833d35e806e00b29",
"operatingSystem": "linux",
"osImage": "CentOS Linux 7 (Core)",
"systemUUID": "823EB67A-057E-4EFF-AE7F-A758140CD2F7"
}
}
}
],
"kind": "NodeList",
"metadata": {
"resourceVersion": "65",
"selfLink": "/api/v1/nodes"
}

我们可以看到,kubelet 收集了很多关于自身节点的信息,这些信息也会不断更新。这些信息里面不仅包含节点的系统信息(系统架构,操作系统版本,内核版本等)、还有镜像信息(节点上有哪些已经下载的 docker 镜像)、资源信息(Memory 和 Disk 的总量和可用量)、以及状态信息(是否正常,可以分配 pod等)。

和 API Server 通信

编写的 yaml 文件转换成 json 格式,保存到文件里。主要注意的是,我们指定了 nodeName 的名字,这个名字必须和之前通过 /api/v1/nodes 得到的结果中 metadata.labels.kubernetes.io/hostname 保持一致:

[root@localhost vagrant]# cat nginx_pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-server
spec:
  NodeName: 192.168.8.100
  containers:
  - name: nginx-server
    image: 172.16.1.41:5000/nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: /var/log/nginx
      name: nginx-logs
    - name: log-output
    image: 172.16.1.41:5000/busybox
    command:
    - bin/sh
      args: [-c, 'tail -f /logdir/access.log']
    volumeMounts:
    - mountPath: /logdir
      name: nginx-logs
  volumes:
  - name: nginx-logs

使用 curl 执行 POST 请求,设置头部内容为 application/json,传过去文件中的 json 值,可以看到应答(其中 statuspending,表示以及接收到请求,正在准备处理):


 # curl -s -X POST -H "Content-Type: application/json" http://192.168.8.100:8080/api/v1/namespaces/default/pods --data @nginx_pod.json
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "nginx-server",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/pods/nginx-server",
"uid": "888e95d0-75a9-11e6-b463-080027c09e5b",
"resourceVersion": "573",
"creationTimestamp": "2016-09-08T09:49:28Z"
},
"spec": {
"volumes": [
{
"name": "nginx-logs",
"emptyDir": {}
}
],
"containers": [
{
"name": "nginx-server",
"image": "172.16.1.41:5000/nginx",
"ports": [
{
"containerPort": 80,
"protocol": "TCP"
}
],
"resources": {},
"volumeMounts": [
{
"name": "nginx-logs",
"mountPath": "/var/log/nginx"
}
],
"terminationMessagePath": "/dev/termination-log",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"nodeName": "192.168.8.100",
"securityContext": {}
},
"status": {
"phase": "Pending"
}
}

返回中包含了我们提交 pod 的信息,并且添加了 statusmetadata 等额外信息。

等一段时间去查询 pod,就可以看到 pod 的状态已经更新了:

 ➜ http http://192.168.8.100:8080/api/v1/namespaces/default/pods
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 08 Sep 2016 09:51:29 GMT
Transfer-Encoding: chunked
{
"apiVersion": "v1",
"items": [
{
"metadata": {
"creationTimestamp": "2016-09-08T09:49:28Z",
"name": "nginx-server",
"namespace": "default",
"resourceVersion": "592",
"selfLink": "/api/v1/namespaces/default/pods/nginx-server",
"uid": "888e95d0-75a9-11e6-b463-080027c09e5b"
},
"spec": {
"containers": [
{
"image": "172.16.1.41:5000/nginx",
"imagePullPolicy": "Always",
"name": "nginx-server",
"ports": [
{
"containerPort": 80,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"volumeMounts": [
{
"mountPath": "/var/log/nginx",
"name": "nginx-logs"
}
]
},
{
"args": [
"-c",
"tail -f /logdir/access.log"
],
"command": [
"bin/sh"
],
"image": "172.16.1.41:5000/busybox",
"imagePullPolicy": "Always",
"name": "log-output",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"volumeMounts": [
{
"mountPath": "/logdir",
"name": "nginx-logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"nodeName": "192.168.8.100",
"restartPolicy": "Always",
"securityContext": {},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"emptyDir": {},
"name": "nginx-logs"
}
]
},
"status": {
"conditions": [
{
"lastProbeTime": null,
"lastTransitionTime": "2016-09-08T09:49:28Z",
"status": "True",
"type": "Initialized"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2016-09-08T09:49:44Z",
"status": "True",
"type": "Ready"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2016-09-08T09:49:44Z",
"status": "True",
"type": "PodScheduled"
}
],
"containerStatuses": [
{
"containerID": "docker://8b79eeea60f27b6d3f0a19cbd1b3ee3f83709bcf56574a6e1124c69a6376972d",
"image": "172.16.1.41:5000/busybox",
"imageID": "docker://sha256:8c566faa3abdaebc33d40c1b5e566374c975d17754c69370f78c00c162c1e075",
"lastState": {},
"name": "log-output",
"ready": true,
"restartCount": 0,
"state": {
"running": {
"startedAt": "2016-09-08T09:49:43Z"
}
}
},
{
"containerID": "docker://96e64cdba7b05d4e30710a20e958ff5b8f1f359c8d16d32622b36f0df0cb353c",
"image": "172.16.1.41:5000/nginx",
"imageID": "docker://sha256:51d764c1fd358ce81fd0e728436bd0175ff1f3fd85fc5d1a2f9ba3e7dc6bbaf6",
"lastState": {},
"name": "nginx-server",
"ready": true,
"restartCount": 0,
"state": {
"running": {
"startedAt": "2016-09-08T09:49:36Z"
}
}
}
],
"hostIP": "192.168.8.100",
"phase": "Running",
"podIP": "172.17.0.2",
"startTime": "2016-09-08T09:49:28Z"
}
}
],
"kind": "PodList",
 
"metadata": {
"resourceVersion": "602",
"selfLink": "/api/v1/namespaces/default/pods"
}
}

可以看到 pod 已经在运行,并且给分配了 ip:172.17.0.2,通过 curl 也可以访问它的服务:

[root@localhost vagrant]# curl -s http://172.17.0.2 | head -n 5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx on Debian!</title>
<style>

关于 API Server 提供的所有接口,可以参考官方的 [API 文档][http://kamalmarhubi.com/blog/2015/09/06/kubernetes-from-the-ground-up-the-api-server/]

kubectl -s http://ip:8080 get pods
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容