@dujun
2015-03-31T15:39:10.000000Z
字数 15547
阅读 9613
Kubernetes写书
Kubernetes APIServer在Kubernetes的管理节点——master节点启动运行,对外提供Kubernetes API服务。在我们撰写这本书的时候,master节点只支持单个(原因见下文)。Kubernetes APIServer总体上由两个部分组成:http/https server和一些功能性插件。其中,这些插件又可以分成两类:一部分与底层IaaS平台(Cloud Provider)相关,代码详见:pkg/cloudprovider/{Cloud Providers}
;另一部分与资源的管理控制(Admission Control)相关,代码见:plugin/pkg/admission/{plugins}
。与Cloud Provider相关的插件无非是调用IaaS的API完成对Kubernetes minion节点的操作(如果minion节点是这些IaaS提供的虚拟机的话)。而对于Admission Control插件,会有专门的篇幅来介绍,详见xxx。接下来我们将重点介绍Kubernetes APIServer的http/https server部分(为方便起见,下文凡是出现apiserver的地方都可以认为是APIServer的http/https server缩写)。
APIserver作为Kubernetes集群的全局掌控者,主要职能如下所示。
(1)对外提供基于RESTful的管理接口,支持对Kubernetes的资源对象譬如:pod,service,replication controller,minion等进行增、删、改、查和监测(watch)操作。例如:
GET localhost:8080/api/v1beta1/pods
表示查询默认namespace中所有pod的信息。
WATCH localhost:8080/api/v1beta1/watch/pods
表示监测默认namespace中所有pod的状态变化信息,返回pod的创建、更新和删除事件。
(2)配置和确保Kubernetes的资源对象一直处于用户期望的正确状态,并将这些资源对象的期望状态和当前实际存储在etcd中供Kubernetes其他组件读取和分析。
(3)与service配置数据同步pod信息(包括:所处位置、暴露的端口等),向Kubernetes service对象实例提供后端pod集的完整信息。
(4)提供丰富的功能性插件(支持用户自定义),完善对集群的管理。例如:调用内部/外部的用户认证与授权机制保证集群安全性,调用Admission Control插件对集群资源的使用进行管理控制,调用底层IaaS接口创建和管理Kubernetes工作节点等。
(5)UI(swagger)和Log支持。
apiserver的启动程序见cmd/kube-apiserver/apiserver.go
的main()
函数,如下所示。
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
s := app.NewAPIServer()
s.AddFlags(pflag.CommandLine)
util.InitFlags()
util.InitLogs()
defer util.FlushLogs()
verflag.PrintAndExitIfRequested()
if err := s.Run(pflag.CommandLine.Args()); err != nil {
fmt.Fprint(os.Stderr, err.Error)
os.Exit(1)
}
}
现逐行分析如下。
runtime.GOMAXPROCS(runtime.NumCPU())
熟悉Go语言的读者应该不会感到陌生,这是使用Go语言的特性来充分利用master节点的多核CPU,以达到增加apiserver的系统吞吐量的目的。
s := app.NewAPIServer()
新建一个apiserver对象。apiserver的数据结构定义如下所示。
type APIServer struct {
WideOpenPort int
Address util.IP
PublicAddressOverride util.IP
ReadOnlyPort int
APIRate float32
APIBurst int
SecurePort int
TLSCertFile string
TLSPrivateKeyFile string
APIPrefix string
StorageVersion string
CloudProvider string
CloudConfigFile string
EventTTL time.Duration
TokenAuthFile string
AuthorizationMode string
AuthorizationPolicyFile string
AdmissionControl string
AdmissionControlConfigFile string
EtcdServerList util.StringList
EtcdConfigFile string
CorsAllowedOriginList util.StringList
AllowPrivileged bool
PortalNet util.IPNet // TODO: make this a list
EnableLogsSupport bool
MasterServiceNamespace string
RuntimeConfig util.ConfigurationMap
KubeletConfig client.KubeletConfig
ClusterName string
}
各属性的含义如下表所示。
参数 | 含义 | 默认值 | 备注 |
---|---|---|---|
WideOpenPort | apiserver监听的端口 | 8080 | 该端口具有读写权限 |
Address | apiserver绑定的网卡地址 | 127.0.0.1 | 0.0.0.0代表所有网卡地址 |
PublicAddressOverride | 公有IP地址,也是只读端口和安全端口绑定的网卡地址 | 空 | 如果默认值为空,则选择第一个非回环网卡地址 |
ReadOnlyPort | 只读端口 | 7080 | 该端口只有读权限 |
APIRate | 只读端口的API请求速率限制 | 10.0 | QPS |
APIBurst | 只读端口能够应对的最大流量(使用token bucket算法) | 200 | 相当于token bucket的容量 |
SecurePort | 安全端口 | 6443 | 0代表不启用https |
TLSCertFile | TLS证书文件 | 空 | 与https配合使用 |
TLSPrivateKeyFile | TLS私钥文件 | 空 | 与https配合使用 |
APIPrefix | API请求前缀 | /api | / |
StorageVersion | etcd版本 | 空 | 与EtcdServerList和EtcdConfigFile一起用于初始化etcd client |
CloudProvider | 底层IaaS提供商 | 空 | 如果为空,表示不需要IaaS支持 |
CloudConfigFile | 底层IaaS配置文件路径 | 空 | 该字段非空的前提是CloudProvider字段非空 |
EventTTL | 事件的存储保留时间 | 48小时 | / |
TokenAuthFile | 存储token的文件 | 空 | 用于apiserver的token认证,与https配合使用 |
AuthorizationMode | 授权模式 | AlwaysAllow | 用户可以自定义授权模式,默认是永远允许 |
AuthorizationPolicyFile | csv格式的访问控制规则文件 | 空 | 仅当authorization_mode=ABAC时生效 |
AdmissionControl | 以逗号作为分割符的Admission Control插件的排序列表 | AlwaysAdmit | 用户可以自定义授权模式,默认是永远允许 |
AdmissionControlConfigFile | 启动admission control插件的配置文件 | 空 | / |
EtcdServerList | etcd server列表,http://ip:port的形式,以逗号分隔 | 空 | 与EtcdConfigFile互斥,且两者至少有一个不为空 |
EtcdConfigFile | etcd配置文件 | 空 | 与EtcdServerList互斥,且两者至少有一个不为空 |
CorsAllowedOriginList | 允许进行跨域资源共享的源服务器列表 | 空 | / |
AllowPrivileged | 是否特权优先级容器 | false | 布尔值 |
PortalNet | 一个CIDR的IP段,用于分配service portal IP | 空 | 如果默认值为空,则设置为10.0.0.0/24 |
MasterServiceNamespace | 默认namespace | default | / |
RuntimeConfig | 运行时环境配置 | 空 | 一个map对象,譬如:{api/v1beta3 = true} |
KubeletConfig | Kubelet客户端配置信息 | {Port:10250,EnableHttps: false} | 一个map对象,无法自定义 |
ClusterName | 集群实例前缀 | kubernetes | / |
表x apiserver数据结构各属性含义
s.AddFlags(pflag.CommandLine)
接受用户命令行输入,自定义上表各参数值。
util.InitFlags()
解析并格式化用户传入的参数,最后填充APIServer
结构体的各熟悉。
util.InitLogs()
初始化log配置,包括:log输出位置、log等级等。Kubernetes使用glog完成对系统运行过程中产生的各类事件和运行状态进行记录与输出。这里简单介绍下Kubernetes的log等级,每个log等级有各自约定俗成的含义和习惯用法,为方便用户/开发者阅读系统的log输出以及在Kubernetes代码开发时能够熟练运用glog,现列举如下。
log等级 | 习惯用法 |
---|---|
glog.Errorf() | 永远输出一个错误信息 |
glog.Warningf() | 发生了意料之外的事情,但可能不是一个错误 |
glog.V(0).Infof() | 输出一个对运维人员非常重要的错误信息,目前Kubernetes并未使用该等级的log输出 |
glog.V(1).Infof() | 输出能够被纠正的错误信息,譬如检测到pod处于unhealthy状态 |
glog.V(2).Infof() | 输重要的系统状态变化信息,譬如http request和他的返回码 |
glog.V(3).Infof() | 额外的系统状态变化信息 |
glog.V(4).Infof() | 详尽的debug信息 |
表x glog在Kubernetes的习惯用法
Kubernetes默认log等级是V(2),开发者可能需要得到更详尽的log输出,譬如V(3)或V(4),如果想要改变log等级,只需在APIServer(或其他组件)启动时传入-v=X
参数,其中X是期望的最高log等级,1代表最简洁的log输出,4代表最详细的log输出。
defer util.FlushLogs()
上面这条代码保证了即使apiserver异常崩溃也能够将内存中的log信息保存到磁盘文件中。
verflag.PrintAndExitIfRequested()
如果用户只是想查看apiserver的版本号而不是启动apiserver,则打印apiserver的版本号并退出,即:
$ ./kube-apiserver --version
Kubernetes v0.12.0
最后的s.Run(pflag.CommandLine.Args())
表示启动运行一个全新的apiserver。apiserver会启动一个nginx作为http/https服务器并监听以下三个端口对外提供Kubernetes API服务。
使用http访问,默认为8080端口,有读/写权限。该端口是一个非安全端口,没有用户认证和授权检查机制。如果需要自定义该端口,在启动时传入--WideOpenPort
参数。该端口默认绑定到localhost(127.0.0.1),如果需要自定义绑定的网络接口地址,在启动时传入--address
参数或者将其写入配置文件/etc/default/kube-apiserver
(譬如:0.0.0.0
表示绑定所有的网络接口地址)。但一般不推荐这样做,因为设计该端口的初衷是为Kubernetes的其他组件(例如:Scheduler和Controller-manager)的后台程序提供读/写API操作和用于测试,这也是目前Scheduler和Controller-manager需要和apiserver装在同一台机器上的原因之一。Kubernetes认为在生产环境下,防火墙是屏蔽外网直接访问该端口的,而且集群公有IP地址(PublicAddressOverride
)的安全端口的流量将转发至该端口。
使用http访问,默认为7080端口,只有读权限,即只接受http GET
请求。该端口是一个非安全端口,没有用户认证和授权检查机制。如果需要自定义该端口,在启动时传入--read_only_port
参数。该端口绑定到集群公有IP地址上,集群公有IP地址(PublicAddressOverride
)可以在apiserver启动时通过--public_address_override
参数传入,但如果没有传入该参数,apiserver会使用其所在机器上的第一个非回环网卡地址作为该共有IP地址以保证集群的可用性。另外,该端口的API请求速率受到一定限制,默认为10/s。
使用https访问,默认为6443端口(如果是0表示不启用https),有读/写权限,同时支持x509安全证书和x509私钥认证。在apiserver启动时分别通过--tls_cert_file
参数传入证书文件和--tls_private_key_file
参数传入私有密钥文件。如果启用了https且apiserver在启动时未被提供以上参数,则apiserver会自动为该端口绑定的公有IP地址分别生成一个自注册的证书文件和密钥并将它们存储在/var/run/kubernetes
目录下,分别为:/var/run/kubernetes/apiserver.crt
和/var/run/kubernetes/apiserver.key
。如果需要自定义该端口,在启动时传入--secure_port
参数。与只读端口类似,该端口绑定到集群公有IP地址(PublicAddressOverride
)上,如果需要自定义绑定的网络接口地址,在apiserver启动时传入--public_address_override
参数,否则apiserver会默认使用其所在机器上的第一个非回环网卡地址。未来,如果Scheduler和Controller-manager都使用安全端口,则它们就不必与apiserver运行在同一台机器上。APIServer使用基于token文件的认证机制和基于访问规则的授权机制来保障该端口的安全。
在前面对Kubernetes service概念的讨论中,我们知道Kubernetes使用用户的service概念对Kubernetes API服务本身进行了抽象和封装。这两个API服务即kubernetes
和kubernetes-ro
,在apiserver的启动过程中,会创建并运行这两个service,代码如下所示。
package master
m.masterServices = util.NewRunner(m.serviceWriterLoop, m.roServiceWriterLoop)
m.masterServices.Start()
service kubernetes-ro
与PortalNet
的第一个IP和系统定义的只读端口(80端口,目前不支持通过传入参数来自定义)绑定,它接收来自集群只读端口的流量导入,提供Kubernetes只读API(GET操作)。用户访问service kubernetes-ro
不需要进行认证与授权操作。而service kubernetes
则与PortalNet
的第二个IP和系统定义的安全端口(443端口,目前不支持通过传入参数来自定义)绑定,它接收来自集群安全端口的流量导入,提供Kubernetes完整API。用户访问service kubernetes
需要通过认证与授权。
Kubernetes使用etcd作为其后台存储解决方案,而apiserver则基于etcd实现了一套RESTful API用于操作存储在etcd中的Kubernetes对象实例。所有针对Kubernetes资源对象的操作都可以使用传统的REST模式,如下所示。
GET /<resourceNamePlural>
返回类型为resourceName的资源对象列表,例如:GET /pods
返回一个pod列表。POST /<resourceNamePlural>
根据客户端提供的描述资源对象的json文件创建一个新的资源对象。GET /<resourceNamePlural>/<name>
根据一个指定的资源名返回单个资源对象信息,例如:GET /pods/first
返回一个名为first
的pod信息。DELETE /<resourceNamePlural>/<name>
根据一个指定的资源名删除一个资源对象。 PUT /<resourceNamePlural>/<name>
根据客户端提供的描述资源对象的json文件创建或更新一个指定名字的资源对象。除了上面提到的通用增、删、改、查操作以外,apiserver还提供其他一些URL以支持额外的操作,譬如:
GET /watch/<resourceNamePlural>
使用etcd的watch机制,返回指定类型资源对象实时的变化信息。GET /watch/<resourceNamePlural>/<name>
使用etcd的watch机制,根据客户端提供的描述资源对象的json文件返回一个名为name
的资源对象实时的变化信息。GET /redirect/<resourceNamePlural>/<name>
如果一个名为name
的资源对象能够用一个URL来描述,将返回一个http重定向到该URL而不是一个json格式的http响应。例如:一个service对象对外暴露一个IP地址和端口,客户端调用该重定向操作便能收到一个HTTP 307重定向到该IP地址和端口。registry是apiserver内部访问存储层(etcd)的API client,提供对存储在etcd内部的Kubernetes对象的增、删、改、查(CRUD)和监测(watch)等操作。Kubernetes资源对象在etcd中的存储路径形如下面所示。
$ etcdctl ls /registry --recursive
...
/registry/pods
/registry/pods/default
/registry/pods/default/f54759d0-a51f-11e4-91b1-005056b43972
这些存储路径均在pkg/registry/etcd/etcd.go
中被定义,如下所示。
package etcd
const (
PodPath string = "/registry/pods"
ControllerPath string = "/registry/controllers"
ServicePath string = "/registry/services/specs"
ServiceEndpointPath string = "/registry/services/endpoints"
NodePath string = "/registry/minions"
)
以上{ResourceName}Path
即{Resource}
在etcd中的存储路径。
registry中真正负责etcd存储的逻辑部分——即对Kubernetes资源对象的CRUD和watch操作的具体实现函数可以在pkg/registry/etcd/etcd.go
中找到。例如:
package etcd
// GetPod根据podID返回一个指定的pod对象
func (r *Registry) GetPod(ctx api.Context, id string) (*api.Pod, error){
...
}
// CreatePod根据制定的资源文件创建一个pod实例
func (r *Registry) CreatePod(ctx api.Context, pod *api.Pod){
...
}
...
那以上功能函数由谁调用呢?答案是Registry
对象。Registry
的数据结构如下所示,其中包含了etcd的客户端。
package etcd
type Registry struct {
tools.EtcdHelper
pods pod.Registry
}
Registry
作为一个统一的存储入口,实现了BindingRegistry
、ControllerRegistry
、EndpointRegistry
、MinionRegistry
、PodRegistry
和ServiceRegistry
等接口。{Resource}Registry
接口知道如何在etcd中存储和操作特定的{Resource}
对象。以PodRegistry
为例,该Registry
定义的接口函数声明如下所示。
package pod
type Registry interface {
// ListPods返回匹配标签选择器的pod列表
ListPods(ctx api.Context, selector labels.Selector) (*api.PodList, error)
// WatchPods监测pod实时的创建、更新、删除等信息
WatchPods(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error)
// GetPod根据podID返回一个指定的pod对象
GetPod(ctx api.Context, podID string) (*api.Pod, error)
// CreatePod根据制定的资源文件创建一个pod实例
CreatePod(ctx api.Context, pod *api.Pod) error
...
}
我们知道,registry负责与etcd打交道,那从etcd中拿到的数据又给谁呢?答案是RESTStorage
对象。RESTStorage是一个用于RESTful存储操作的通用接口,任意一种对apiserver暴露RESTful存储 API的Kubernetes资源对象都要实现该接口,该接口的定义如下所示。
package apiserver
type RESTStorage interface {
New() runtime.Object
}
该接口声明的New()
函数返回一个空的运行时的资源对象,该资源对象可以在请求数据被放入后用于资源的创建和更新操作。与Registry
类似,不同的Kubernetes资源类型都要实现各自的RESTStorage
接口,具体实现代码见pkg/registry/{ResourceName}/rest.go
。现以pod为例,分析如下。
package pod
type REST struct {
registry Registry
podCache PodStatusGetter
}
pod.REST
实现了RESTStorage
接口。可以发现,pod.REST
包含Registry
对象,用于操作存储层的pod对象,而podCache
对象则存储pod的当前状态。再看用于创建一个pod对象的Create
函数,
package pod
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
pod := obj.(*api.Pod)
if !api.ValidNamespace(ctx, &pod.ObjectMeta) {
return nil, errors.NewConflict("pod", pod.Namespace, fmt.Errorf("Pod.Namespace does not match the provided context"))
}
api.FillObjectMetaSystemFields(ctx, &pod.ObjectMeta)
if len(pod.Name) == 0 {
pod.Name = string(pod.UID)
}
if errs := validation.ValidatePod(pod); len(errs) > 0 {
return nil, errors.NewInvalid("pod", pod.Name, errs)
}
return apiserver.MakeAsync(func() (runtime.Object, error) {
if err := rs.registry.CreatePod(ctx, pod); err != nil {
return nil, err
}
return rs.registry.GetPod(ctx, pod.Name)
}), nil
}
该函数接受两个参数:api.Context
类型的ctx
代表当前API请求的上下文,runtime.Object
类型的obj
代表运行时的Kubernetes对象。该函数返回一个apiserver.RESTResult
类型的go channel,apiserver.RESTResult
代表一个REST操作的结果,其数据结构定义如下所示。
package apiserver
type RESTResult struct {
runtime.Object
Created bool
}
如上所示,apiserver.RESTResult
结构体包含两个字段:runtime.Object
和Created
。runtime.Object
是一个运行时的Kubernetes对象,代表该操作的结果,如果该操作是Delete
,则置为nil。布尔值Created
表明该操作的结果是否已成功地填入runtime.Object
对象,若是,则置为true。
我们可以通过研究Create
函数一窥创建pod的主要过程。首先从obj
中解析出相应的信息来创建一个运行时的pod对象,并根据API请求的上下文和该pod对象的元数据来验证两者的namespace是否匹配,如不匹配则创建pod失败。namespace验证匹配后,apiserver会向pod对象注入一些系统元数据,包括:创建时间和uuid等。如果定义pod时未提供pod的名字,则apiserver会将pod的uudid作为pod的名字。然后,apiserver会检查pod对象中一些必需字段是否为空,譬如:name
、namespace
、spec
和labels
等,只要有一个字段为空,则抛出异常并终止创建过程。最后调用registry.CreatePod
函数在etcd中持久化该pod对象。apiserver.MakeAsync
函数负责将操作结果封装成RESTResult
并填入作为返回值的go channel。
实现了所有对象的RESTStorage
接口后,接下来就要向apiserver注册ResourceHandler
。apiserver.InstallREST
负责向一个restful容器(注意:这里的容器与docker容器是完全不同的概念,是与器皿相似的概念)注册一个REST handlers,包括:ResourceHandler
、watchHandler
、proxyHandler
、redirectHandler
、opHandler
等,形如:
mux.Handle(prefix+"/watch/", http.StripPrefix(prefix+"/watch/", watchHandler))
即根据RESFful API的URL路径匹配相应的REST handler。
最后,根据系统支持的API版本,分别建立不同的API,以v1beta1
为例,代码如下所示。
if err := apiserver.NewAPIGroupVersion(m.api_v1beta1()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta1"); err != nil {
glog.Fatalf("Unable to setup API v1beta1: %v", err)
}
这样,当用户发起一个GET localhost:8080/api/v1beta1/pods
的API请求时,就能访问本地的读写端口,按照v1beta1
的API标准调用etcd客户端的ListPods
接口查询默认namespace(default)中所有pod的信息。
所有Kubernetes资源对象都有一个resourceVersion
作为其元数据(详见pkg/api/v1beta1/types.go
的TypeMeta
结构体)的一部分,apiserver借此保证对资源对象操作的原子性。resourceVersion
是用于标识一个资源对象的内部版本的一个字符串,客户端可以通过它来判断该对象是否被更新过。resourceVersion
仅对当前资源对象和namespace有效,每次Kubernetes资源对象的更新都会导致apiserver修改其值。如果一个PUT操作包含了resourceVersion
的值,那么apiserver就会通过验证当前resourceVersion
的值与指定的值是否相匹配来确保在此次PUT操作的读/修改/写周期内没有任何其他对该资源的修改操作。可以将resourceVersion
看成是apiserver用来排序用户请求的逻辑时钟。
客户端能够获取resourceVersion
的值的唯一途径是向apiserver发起GET请求,apiserver查询检索到的资源对象的resourceVersion
字段的值,最后通过http response返回。apiserver还支持HTTP PUT方法的幂等性,即如果一个HTTP请求头部包含If-Match: resourceVersion=
或一个HTTP请求URL包含?resourceVersion=
参数且当前对象存储的resourceVersion
值与http请求包含的值不匹配,则apiserver会返回一个StatusConflict(409)
的http状态码。这时,正确的做法是客户端重新发起GET
请求并对该资源对象应用更新操作,最后再次提交。
resourceVersion
机制能够防止以下代码块的竞争。
Client #1
GET Foo
Set Foo.Bar = "one"
PUT Foo
Client #2
GET Foo
Set Foo.Baz = "two"
PUT Foo
当以上两个代码块并发执行且没有适当的锁机制保护时,Foo.Bar
或Foo.Baz
的修改都可能丢失。但是当为Foo
对象指定resourceVersion
时,其中一个PUT操作必定会失败,因为任意一个PUT操作的成功执行都会修改resourceVersion
的值。
最后,用下面这张APIServer的总体架构图作为上面讨论的一个小结。
图1 APIServer总体架构
如上所示,APIServer运行在总控节点(master node)上,总控节点上的一个公有IP(public address,一般是总控节上的第一个非回环IP地址,姑且认为是集群的总入口)一般对外暴露三个端口提供API服务。在上图的例子中,从左到右分别是443端口、7080端口和6443端口。443端口是一个可选端口,用于保证APIServer非安全端口(本地端口)的安全性,在上面这个例子中,使用了nginx监听该443端口,并将客户端的API请求转发给APIServer的本地端口上。事实上,使用saltstack部署的基于GCE的Kubernetes集群默认安装了nginx作为客户端与APIServer非安全server(本地server)的流量代理。nginx开启https模式,客户端与nginx之间的通信使用相互认证的方式进行加密且nginx对客户端使用http basic auth的认证模式。如果不使用例如nginx这样的代理服务器,则客户端/Kubernetes其他组件只能登录到主控节点上使用入口地址localhost:8080
访问本地端口,进而访问Kubernetes的读/写API。7080端口是个只读端口,APIServer的只读server侦听该端口,接收客户端的http请求。该请求会根据iptables规则被转发给kubernetes系统自定义的一个用于提供Kubernetes只读API的service,即:ro-service
。kubernetes系统自定义的service与用户定义的service均处于Kubernetes自己管理的虚拟网络中,在上面这个例子中service所处的网段(portal net)即:10.0.0.0/24
。ro-service
一般绑定在portal net中第一个可用的IP地址(在上面这个例子中,即:10.0.0.1
)上,对外暴露80端口。7080端口是个只读端口,APIServer的只读server侦听该端口,接收客户端的http请求。该请求会根据iptables规则被转发给kubernetes系统自定义的一个用于提供Kubernetes只读API的service,即:ro-service
。kubernetes系统自定义的service与用户定义的service均处于Kubernetes自己管理的虚拟网络中,在上面这个例子中service所处的网段(portal net)为10.0.0.0/24
。ro-service
一般绑定在portal net中第一个可用的IP地址(在上面这个例子中,即:10.0.0.1
)上,对外暴露80端口。6443端口是个Kubernetes自己维护的安全端口,APIServer的读/写server侦听该端口,接收客户端的https
请求。该请求会根据iptables规则被转发给kubernetes系统自定义的另一个用于提供Kubernetes读/写API的service,即:rw-service
。与ro-service
一样,rw-service
处于Kubernetes自己管理的虚拟网络中且一般绑定在portal net中第二个可用的IP地址(在上面这个例子中,即:10.0.0.2
)上,对外暴露443端口。6443端口和443端口虽然都支持https,但不同的是Kubernetes使用自定义的用户认证/授权插件(详情请参见后面章节)而非http basic auth来确保6443端口的访问安全。
用户的API请求通过用户授权/认证后,Kubernetes的资源管理插件Admission Control(详情请参见后面章节)会根据用户的API请求类型、用户请求上下文所处namespace和申请的资源数量等信息决定到底是通过还是驳回该API请求。如果用户API请求通过了Admission Control插件的验证后,将调用etcd的存储接口Registry对存储在etcd中的REST对象执行增、删、改、查(CRUD)和监测(watch)操作。
下面,以创建一个pod的工作流作为本文的结束。假设创建pod的这个API请求访问Kubernetes安全端口且对应的HTTP请求如下所示。
HTTP方法:POST
URL:{public_address}:6443/api/v1beta1/pods
那么,APIServer的处理工作流可以分为以下几个步骤:
1)Kubernetes调用用户认证模块对用户的身份进行认证,再根据用户HTTP请求的操作类型和发起请求所处上下文的namespace等信息结合Kubernetes预先定义的授权规则判断该用户是否有权限执行创建该pod的操作。
2)在6443端口监听连接信息的APIServer的安全server将用户的HTTP请求转发给rw-service
。
3)rw-service
将该HTTP请求的请求方法和URL映射成对应的RESTful API,即:create pod
,并调用Admission Control插件对该API请求所需资源的合法性进行检验。如果Admission Control通过该API请求对资源的需求,则执行步骤4,否则返回调用者一个错误信息并结束处理流程。
4)根据API请求中pod的配置信息,调用pod的Registry存储接口在etcd中创建一个pod实例。
调度pod全过程(与对象标识符相关,只是其中一部分)
有很多种途径将Pod调度到一个特定的工作节点上,下面举两个例子。
.kubernetes_ns
中的namespace
值)。 更多见这里
[注]
HealthCheck要写一写。文档见这里