kubenetes的抽象概念,如Pod、Service、Volume、Namespace、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job等统称对象,kubectl命令提供三种方式对这些对象进行操作、管理,三种方式分别为:Imperative commands(祈使命令)、Imperative object configuration(祈使对象配置)、Declarative object configuration(声明对象配置),后两种方式统称对象配置方式。本文分别介绍三种管理对象方式,并比较优缺点。在正式介绍之前,先说明一个重要的概念:live objects(实时对象)。实时对象的表现形式由两个部分组成,一部分由初始配置信息决定,称为规格,表示用户的期望值,例如用户定义了一个Deployment,并且将副本的个数设置成3,注意3是一个期望值。非常重要的一点是某些类型的对象,其规格并非完全由配置决定,比如负载均衡类型的服务。另一部分称之为状态,表示当前的实时状态,是动态变化的,比如当前系统中副本的数量是2。而kubenetes的工作就是调整系统当前的状态,以使状态与规格相符。所以live objects(实时对象)由两部分组成,静态的规格及动态的状态。
警告:应该只使用其中的一种方式管理相同的对象,如果混合使用后果不可预知。
用户直接通过设置命令行中的参数、标志,指示操作的具体对象以及操作,操作本身直接作用于集群中的实时对象(live objects)。这是最简单的在集群中启动、运行一次性任务的方法,原因是它直接作用于实时对象本身,不涉及对象的历史配置。
与对象配置比较的优点:
与对象配置比较的缺点:
总体上看,这种方式简单但是功能不强,适合于开发测试场景而非生产环境。缺点产生的根本原因是在更新的时候没有涉及历史配置,没有将整个变更过程记录下来,而是直接作用于实时对象,不与历史配置对比就无法计算出“变更”,自然也就不能审核、审记、追踪“变更”,这种方式的优点也是因为这一原因而产生。
kubenctl祈使命令管理对象的方式,又细分成两种,一种称之为“动词驱动”,可以用来创建大多数基础的对象类型,特点是简单直观,主要针对的是对kubenetes对象类型不熟悉的用户。
另一种称之为“对象类型驱动”,能够创建的对象类型更多,需要用户对kubenetes中的对象类型比较熟悉。
基本语法:create
在创建某些类型的对象时,可以在命令中指定其支持的子类型。如Service对象,包含ClusterIP、LoadBalancer、NodePort等数个子类型,以下是创建子类型为NodePort的Service的示例:
kubectl create service nodeport
在上例中,create service nodeport命令被称为create service的子命令。可以通过-h选项获得子命令支持的参数及标志的帮忙信息,如:
kubectl create service nodeport -h
以下是由易到难更新对象的方法
基本语法:delete
示例:
kubectl delete deployment/nginx
以上命令,删除类型为Deployment名为nginx的对象。
以下命令打印对象信息。
这是一种高级用法。有些类型的对象比较复杂,对象的某些字段在执行create命令时无法直接指定。可以通过管道将create命令与set命令连接起来,实现对这种类型的对象字段的修改,示例如下:
kubectl create service clusterip my-svc --clusterip="None" -o yaml --dry-run | kubectl set selector --local -f - 'environment=qa' -o yaml | kubectl create -f -
上述命令用两个管道将三个命令连接起来,具体执行过程如下:
使用kubectl create --edit命令在对象创建之前进行任意修改,以下是例子:
ubectl create service clusterip my-svc --clusterip="None" -o yaml --dry-run > /tmp/srv.yaml
kubectl create --edit -f /tmp/srv.yaml
在以上命令中,kubectl create service命令创建service的配置文件并保存在/tmp/srv.yaml文件中。kubectl create --edit命令先编辑创建好的配置文件,再启动创建流程。
与祈使命令比较的优点:
与祈使命令比较的缺点:
与声明对象配置比较的优点:
与声明对象配置比较的缺点:
总体而言,祈使对象配置方法将对象的配置信息转移到配置文件中,通过与版本控制器的集成从而支持变更审核、审计等,最大的问题是在变更的过程中,丢失独立与配置文件的规格,对于某些特定类型的对象不适用。
基本语法:kubectl create -f
基本语法:kubectl replace -f
警告:这种方法对于某些规格独立于配置文件类型的对象不适用。
基本语法:kubectl delete -f
基本语法:kubectl get -f
这种方式的不足,在前文中的警告中已经提及,现集中说明。
当配置文件中包含对象的完整定义时(注意完整一词),create、replace、delete命令能够很好的工作。但是,当实时对象中独立于配置的规格没有被合并到配置文件中,也就是说配置文件中的定义是不“完整”的,那么在更新被执行时就会丢失掉这部分信息。例如:
假如可以通过URL读取配置文件。可以通过create --edit命令先获取配置文件,在对象创建之前进行修改,然后再创建对象。此时的修改是临时的。
基本语法:kubectl create -f
手动步骤如下:
警告:强烈不推荐更新控制类对象的选择器,推荐的方法是为Pod模板选择器定义单一、恒定、专用此外再无其它用处的标签。
标签示例:
selector:
matchLabels:
controller-selector: "extensions/v1beta1/deployment/nginx"
template:
metadata:
labels:
controller-selector: "extensions/v1beta1/deployment/nginx"
当使用声明对象配置方法时,用户通过操作存储在本地的对象配置文件实现,并且用户无需定义对对象执行何种操作。对于每个对象的create、 update、delete等操作,由kubectl命令自动决定,这种机制适用如下场景,如果一个目录下有多个对象配置文件,需要实现对不同对象的不同操作。声明对象配置方法将会保持其它源对对象的修改,即使这种修改没有被合并到本地的配置文件中。这种机制,确保系统能够计算出当前对象的配置与实时对象的差值,然后通过补丁类型的API只局部更新差值而不是整体替换。
与祈使对象配置比较的优点:
与祈使对象配置比较的缺点:
kubectl apply -f
注意这个操作的附加结果。对于每个新创建的对象,自动添加注解:kubectl.kubernetes.io/last-applied-configuration: '{...}'。注解的内部就是本次操作时配置文件的内容,也就是说对象本身自包含创建历史配置。-R选项支持多层级目录遍历。
以下是一个示例配置文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
minReadySeconds: 5
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
路径:https://github.com/kubernetes/website/blob/master/content/en/docs/concepts/overview/object-management-kubectl/simple_deployment.yaml
使用kubectl apply命令创建对象:
kubectl apply -f https://k8s.io/docs/concepts/overview/object-management-kubectl/simple_deployment.yaml
使用kubectl get打印对象的实时配置:
kubectl get -f https://k8s.io/docs/concepts/overview/object-management-kubectl/simple_deployment.yaml -o yaml
结果如下:
kind: Deployment
metadata:
annotations:
# ...
# This is the json representation of simple_deployment.yaml
# It was written by kubectl apply when the object was created
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.7.9","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec:
# ...
minReadySeconds: 5
selector:
matchLabels:
# ...
app: nginx
template:
metadata:
# ...
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
# ...
name: nginx
ports:
- containerPort: 80
# ...
# ...
# ...
# ...
红色字体部分通过注释的方式记录了创建对象的原始配置。
同样使用kubectl apply命令。如果对象已经存在则执行更新。执行步骤如下:
具体语法:
kubectl apply -f
使用kubectl scale命令直接更新上例中创建对象的副本数据,注意是使用kubectl scale命令直接更新,而不是kubectl apply声明对象配置。
kubectl scale deployment/nginx-deployment --replicas=2
通过kubectl get指印出对象的实时配置:
kubectl get -f https://k8s.io/docs/concepts/overview/object-management-kubectl/simple_deployment.yaml -o yaml
结果:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
# ...
# note that the annotation does not contain replicas
# because it was not updated through apply
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.7.9","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec:
replicas: 2 # written by scale
# ...
minReadySeconds: 5
selector:
matchLabels:
# ...
app: nginx
template:
metadata:
# ...
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
# ...
name: nginx
ports:
- containerPort: 80
# ...
从结果可以看出,副本数量已被更新为2,但是在红色字体的注释中,并没有记录相关信息,原因就是使用的是命令kubectl scale直接更的实时对象。
更新配置文件simple_deployment.yaml,将镜像从nginx:1.7.9更新到nginx:1.11.9,并且删除minReadySeconds字段。
更新后的文件内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.11.9 # update the image
ports:
- containerPort: 80
应用更新:
kubectl apply -f https://k8s.io/docs/concepts/overview/object-management-kubectl/update_deployment.yaml
再次指印实时配置:
kubectl get -f https://k8s.io/docs/concepts/overview/object-management-kubectl/simple_deployment.yaml -o yaml
结果如下:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
# ...
# The annotation contains the updated image to nginx 1.11.9,
# but does not contain the updated replicas to 2
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.11.9","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec:
replicas: 2 # Set by `kubectl scale`. Ignored by `kubectl apply`.
# minReadySeconds cleared by `kubectl apply`
# ...
selector:
matchLabels:
# ...
app: nginx
template:
metadata:
# ...
labels:
app: nginx
spec:
containers:
- image: nginx:1.11.9 # Set by `kubectl apply`
# ...
name: nginx
ports:
- containerPort: 80
# ...
# ...
# ...
# ...
详细说明如下:
警告:不支持混合使用kubectl apply与祈使对象配置创建、更新对象,因为后者不包含前者用于计算变量的kubectl.kubernetes.io/last-applied-configuration字段。
通过kubectl apply管理的对象,有两种删除方法。
推荐方法:kubectl delete -f
指定删除对象的文件,通过祈使命令删除。这种方式的好处是简单、不容易误删除。
另外一种方法:kubectl apply -f
警告:kubectl apply --prune处于alpha版本,可能会有后向兼容问题。
删除实现的对象要满足如下条件:
警告:总之后一种方式复杂,很容易发生误删,系统支持不成熟,所以不应该使用。
kubectl get -f -o yaml
提示:“补丁”是更新特定范围内的字段而不是整个对象,实现读入整个对象之前对特定范围字段的更新。
kubectl apply通过发送“补丁”API给API服务实现更新,注意发送的“补丁”而不是完整的配置。“补丁”的计算涉及到调用命令时指定的配置文件、实时对象的配置、以及保存在实时对象中的last-applied-configuration注解。
kubectl apply命令通过比较配置文件与last-applied-configuration注解的内容,计算删除、增加、重新设置的字段:
示例如下:
以下是Deployment对象的配置文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.11.9 # update the image
ports:
- containerPort: 80
以下是实时对象的配置文件:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
# ...
# note that the annotation does not contain replicas
# because it was not updated through apply
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.7.9","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec:
replicas: 2 # written by scale
# ...
minReadySeconds: 5
selector:
matchLabels:
# ...
app: nginx
template:
metadata:
# ...
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
# ...
name: nginx
ports:
- containerPort: 80
# ...
以下是kubectl apply实现的合并计算:
合并后的结果如下:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
# ...
# The annotation contains the updated image to nginx 1.11.9,
# but does not contain the updated replicas to 2
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.11.9","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec:
selector:
matchLabels:
# ...
app: nginx
replicas: 2 # Set by `kubectl scale`. Ignored by `kubectl apply`.
# minReadySeconds cleared by `kubectl apply`
# ...
template:
metadata:
# ...
labels:
app: nginx
spec:
containers:
- image: nginx:1.11.9 # Set by `kubectl apply`
# ...
name: nginx
ports:
- containerPort: 80
# ...
# ...
# ...
# ...
当配置文件与实时对象中的某个相同字段值类型不一样时,如何处理?存在如下几种类型的字段
kubectl apply更新map或者list时,不是将整个字段完全替换,而是更新个别的子元素。例如:当更新Deployment的spec时,spec字段并不会被整体替换,而是比较spec中的元素并更新。
某些类型的字段值,在创建对象如果没有指定,则API服务会设置成默认值,比如Deployment对象中的“strategy”。示例:
创建对象时的配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
minReadySeconds: 5
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
用kubectl apply创建对象后实时对象的配置:
apiVersion: apps/v1
kind: Deployment
# ...
spec:
selector:
matchLabels:
app: nginx
minReadySeconds: 5
replicas: 1 # defaulted by apiserver
strategy:
rollingUpdate: # defaulted by apiserver - derived from strategy.type
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate # defaulted apiserver
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent # defaulted by apiserver
name: nginx
ports:
- containerPort: 80
protocol: TCP # defaulted by apiserver
resources: {} # defaulted by apiserver
terminationMessagePath: /dev/termination-log # defaulted by apiserver
dnsPolicy: ClusterFirst # defaulted by apiserver
restartPolicy: Always # defaulted by apiserver
securityContext: {} # defaulted by apiserver
terminationGracePeriodSeconds: 30 # defaulted by apiserver
# ...
可能看到,API服务指定了很多默认值。这些默认值除非在“补丁”请求中明确指定,否则它们的值一直保持不变,也就是如果所依赖的字段发生了更新,但是默认的字段并不会自动更新,除非明确指定,因此对于那些依赖于其它字段的默认值存在着行为的不确定性。基于这个原因,对于API服务设置默认值的字段,最好还是在配置文件中明确说明,即使指定的值与默认值相同,以此减少系统默认行为影响。
推荐的需要设置默认值的字段:
从版本1.5开始,合并操作不能清除未出现在配置文件中的字段。有两个选择:
选择一:通过kubectl edit直接修改实现对象(存在风险不推荐)
选择二:通过配置文件删除,步骤如下:
应该使用的变更对象字段的方法有以下两种:
将字段归属权从祈使命令转移到配置文件的方法:
将字段写入配置文件,以后不再通过祈使命令的方式更新,只通过kubectl apply
将字段归属权从配置文件转移到祈使命令的方法:
参考文档:https://kubernetes.io/docs/concepts/overview/object-management-kubectl/overview/
最重要的是声明对象配置这种管理方式。