2.4 命令式容器应用编排
本节将使用示例镜像“ikubernetes/myapp: v1”来演示容器应用编排的基础操作:应用部署、访问、查看、服务暴露和扩缩容等。一般说来,Kubernetes之上应用程序的基础管理操作由如下几个部分组成。
1)通过合用的Controller类的资源(如Deployment或ReplicationController)创建并管控Pod对象以运行特定的应用程序,如Nginx或tomcat等。无状态(stateless)应用的部署和控制通常使用Deployment控制器进行,而有状态应用则需要使用StatefulSet控制器。
2)为Pod对象创建Service对象,以便向客户端提供固定的访问路径,并借助于CoreDNS进行服务发现。
3)随时按需获取各资源对象的简要或详细信息,以了解其运行状态。
4)如有需要,则手动对支持扩缩容的Controller组件进行扩容或缩容;或者,为支持HPA的Controller组件(如Deployment或ReplicationController)创建HPA资源对象以实现Pod副本数目的自动伸缩。
5)滚动更新:当应用程序的镜像出现新版本时,对其执行更新操作;必要时,为Pod对象中的容器更新其镜像版本;并可根据需要执行回滚操作。
本节中的操作示例仅演示了前三个部分的功能,即应用的部署、服务暴露及相关信息的查看。应用的扩缩容、升级及回滚等操作会在后面的章节中进行详细介绍。
提示
以下操作命令在任何部署了kubectl并能正常访问到Kubernetes集群的主机上均可执行,包括集群外的主机。复制master主机上的/etc/kubernetes/admin.conf至相关用户主目录下的.kube/config文件即可正常执行,具体方法请参考kubeadm init命令结果中的提示。
2.4.1 部署应用(Pod)
在Kubernetes集群上自主运行的Pod对象在非计划内终止后,其生命周期即告结束,用户需要再次手动创建类似的Pod对象才能确保其容器中的应用依然可得。对于Pod数量众多的场景,尤其是对微服务业务来说,用户必将疲于应付此类需求。Kubernetes的工作负载(workload)类型的控制器能够自动确保由其管控的Pod对象按用户期望的方式运行,因此,Pod的创建和管理大多都会通过这种类型的控制器来进行,包括Deployment、ReplicaSet、ReplicationController等。
1.创建Deployment控制器对象
“kubectl run”命令可于命令行直接创建Deployment控制器,并以--image选项指定的镜像运行Pod中的容器,--dry-run选项可用于命令的测试运行,但并未真正执行资源对象的创建过程。例如,下面的命令要创建一个名为myapp的Deployment控制器对象,它使用镜像ikubernetes/myapp: v1创建Pod对象,但仅在测试运行后即退出:
~]$ kubectl run myapp --image=ikubernetes/myapp:v1 --port=80 --replicas=1 --dry-run NAME AGE myapp <unknown>
镜像ikubernetes/myapp: v1中定义的容器主进程为默认监听于80端口的Web服务程序Nginx,因此,如下命令使用“--port=80”来指明容器要暴露的端口。而“--replicas=1”选项则指定了目标控制器对象要自动创建的Pod对象的副本数量。确认测试命令无误后,可移除“--dry-run”选项后再次执行命令以完成资源对象的创建:
~]$ kubectl run myapp --image=ikubernetes/myapp:v1--port=80--replicas=1 deployment.apps/myapp created
创建完成后,其运行效果示意图如图2-10所示,它在default名称空间中创建了一个名为myapp的Deployment控制器对象,并由它基于指定的镜像文件创建了一个Pod对象。
图2-10 Deployment对象myapp及其创建的Pod对象
kubectl run命令其他常用的选项还有如下几个,它们支持用户在创建资源对象时实现更多的控制,具体如下。
□-l, --labels:为Pod对象设定自定义标签。
□--record:是否将当前的对象创建命令保存至对象的Annotation中,布尔型数据,其值可为true或false。
□--save-config:是否将当前对象的配置信息保存至Annotation中,布尔型数据,其值可为true或false。
□--restart=Never:创建不受控制器管控的自主式Pod对象。
其他可用选项及使用方式可通过“kubectl run --help”命令获取。资源对象创建完成后,通常需要了解其当前状态是否正常,以及是否能够吻合于用户期望的目标状态,相关的操作一般使用kubectl get、kubectl describe等命令进行。
2.打印资源对象的相关信息
kubectl get命令可用于获取各种资源对象的相关信息,它既能够显示对象类型特有格式的简要信息,也能够指定出格式为YAML或JSON的详细信息,或者使用Go模板自定义要显示的属性及信息等。例如,下面是查看前面创建的Deployment对象的相关运行状态的命令及其输出结果:
~]$ kubectl get deployments NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE myapp 1 1 1 1 1m
上面命令的执行结果中,各字段的说明具体如下。
1)NAME:资源对象的名称。
2)DESIRED:用户期望由当前控制器管理的Pod对象副本的精确数量。
3)CURRENT:当前控制器已有的Pod对象的副本数量。
4)UP-TO-DATE:更新到最新版本定义的Pod对象的副本数量,在控制器的滚动更新模式下,它表示已经完成版本更新的Pod对象的副本数量。
5)AVAILABLE:当前处于可用状态的Pod对象的副本数量,即可正常提供服务的副本数。
6)AGE:Pod的存在时长。
提示
Deployment资源对象通过ReplicaSet控制器实例完成对Pod对象的控制,而非直接控制。另外,通过控制器创建的Pod对象都会被自动附加一个标签,其格式为“run=<Controller_Name>”,例如,上面的命令所创建的Pod,会拥有“run=myapp”标签。后面的章节对此会有详细描述。
而此Deployment控制器创建的唯一Pod对象运行正常与否,其被调度至哪个节点运行,当前是否就绪等也是用户在创建完成后应该重点关注的信息。由控制器创建的Pod对象的名称通常是以控制器名称为前缀,以随机字符为后缀,例如,下面命令输出结果中的myapp-6865459dff-5nsjc:
$ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE myapp-6865459dff-5nsjc 1/1 Running 0 3m 10.244.3.2 node03. ilinux.io
上面命令的执行结果中,每一个字段均代表着Pod资源对象一个方面的属性,除了NAME之外的其他字段及功用说明如下。
①READY:Pod中的容器进程初始化完成并能够正常提供服务时即为就绪状态,此字段用于记录处于就绪状态的容器数量。
②STATUS:Pod的当前状态,其值可能是Pending、Running、Succeeded、Failed和Unknown等其中之一。
③RESTARTS:Pod对象可能会因容器进程崩溃、超出资源限额等原因发生故障问题而被重启,此字段记录了它重启的次数。
④IP:Pod的IP地址,其通常由网络插件自动分配。
⑤NODE:创建时,Pod对象会由调度器调度至集群中的某节点运行,此字段即为节点的相关标识信息。
提示
如果指定名称空间中存在大量的Pod对象而使得类似如上命令的输出结果存在太多的不相关信息时,则可通过指定选项“-l run=myapp”进行Pod对象过滤,其仅显示符合此标签选择器的Pod对象。
确认Pod对象已转为“Running”状态之后,即可于集群中的任一节点(或其他Pod对象)直接访问其容器化应用中的服务,如图2-11中节点NodeX上的客户端程序Client,或者集群上运行于Pod中的客户端程序。
图2-11 访问Pod中容器化应用服务程序
例如,在集群中任一节点上使用curl命令对地址为10.244.3.2的Pod对象myapp-6865459dff-5nsjc的80端口发起服务请求,命令及结果如下所示:
[ik8s@node03~]$ curl http://10.244.3.2:80/ Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
2.4.2 探查Pod及应用详情
资源创建或运行过程中偶尔会因故出现异常,此时用户需要充分获取相关的状态及配置信息以便确定问题的所在。另外,在对资源对象进行创建或修改完成之后,也需要通过其详细的状态来了解操作成功与否。kubectl有多个子命令可用于从不同的角度显示对象的状态信息,这些信息有助于用户了解对象的运行状态、属性详情等信息。
1)kubectl describe:显示资源的详情,包括运行状态、事件等信息,但不同的资源类型其输出内容不尽相同。
2)kubectl logs:查看Pod对象中容器输出在控制台的日志信息。在Pod中运行有多个容器时,需要使用选项“-c”指定容器名称。
3)kubectl exec:在Pod对象某容器内运行指定的程序,其功能类似于“docker exec”命令,可用于了解容器各方面的相关信息或执行必需的设定操作等,其具体功能取决于容器内可用的程序。
1.查看Pod对象的详细描述
下面给出的命令打印了此前由myapp创建的Pod对象的详细状态信息,为了便于后续的多次引用,这里先将其名称保存于变量POD_NAME中。命令的执行结果中省略了部分输出:
~]$ POD_NAME=myapp-6865459dff-5nsjc ~]$ kubectl describe pods $POD_NAME Name: myapp-6865459dff-5nsjc Namespace: default Priority: 0 PriorityClassName: <none> Node: node03.ilinux.io/172.16.0.68 …… Status: Running IP: 10.244.3.2 Controlled By: ReplicaSet/myapp-6865459dff Containers: myapp: …… …… Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 55m default-scheduler Successfully assigned default/myapp-6865459dff-5nsjc to node03.ilinux.io Normal Pulling 55m kubelet, node03.ilinux.io pulling image "ikubernetes/ myapp:v1" Normal Pulled 54m kubelet, node03.ilinux.io Successfully pulled image "ikubernetes/myapp:v1" Normal Created 54m kubelet, node03.ilinux.io Created container Normal Started 54m kubelet, node03.ilinux.io Started container
不同的需求场景中,用户需要关注不同纬度的输出,但一般来说,Events和Status字段会是重点关注的对象,它们分别代表了Pod对象运行过程中的重要信息及当前状态。上面命令执行结果中的不少字段都可以见名而知义,而且部分字段在前面介绍其他命令输出时已经给出,还有一部分会在本书后面的篇幅中给予介绍。
2.查看容器日志
Docker容器一般仅运行单个应用程序,其日志信息将通过标准错误输出等方式直接打印至控制台,“kubectl logs”命令即用于查看这些日志。例如,查看由Deployment控制器myapp创建的Pod对象的控制台日志,命令如下:
~]$ kubectl logs $POD_NAME 10.244.3.1- - [……] "GET / HTTP/1.1" 200 65 "-" "curl/7.29.0" "-" ……
如果Pod中运行有多个容器,则需要在查看日志时为其使用“-c”选项指定容器名称。例如,当读者所部署的是KubeDNS附件而非CoreDNS时,kube-system名称空间内的kube-dns相关的Pod中同时运行着kubedns、dnsmasq和sidecar三个容器,如果要查看kubedns容器的日志,需要使用类似如下的命令:
~]$ DNS_POD=$(kubectl get pods -o name -n kube-system | grep kube-dns) ~]$ kubectl logs $DNS_POD -c kubedns -n kube-system
需要注意的是,日志查看命令仅能用于打印存在于Kubernetes系统上的Pod中容器的日志,对于已经删除的Pod对象,其容器日志信息将无从获取。日志信息是用于辅助用户获取容器中应用程序运行状态的最有效的途径之一,也是非常重要的排错手段,因此通常需要使用集中式的日志服务器统一收集存储于各Pod对象中容器的日志信息。
3.在容器中运行额外的程序
运行着非交互式进程的容器中,默认运行的唯一进程及其子进程启动后,容器即进入独立、隔离的运行状态。对容器内各种详情的了解需要穿透容器边界进入其中运行其他的应用程序来进行,“kubectl exec”可以让用户在Pod的某容器中运行用户所需要的任何存在于容器中的程序。在“kubectl logs”获取的信息不够全面时,此命令可以通过在Pod中运行其他指定的命令(前提是容器中存在此程序)来辅助用户获取更多的信息。一个更便捷的使用接口的方式是直接交互式运行容器中的某个Shell程序。例如,直接查看Pod中的容器运行的进程:
~]$ kubectl exec $POD_NAME ps aux PID USER TIME COMMAND 1 root 0:00 nginx: master process nginx -g daemon off; 8 nginx 0:00 nginx: worker process 9 root 0:00 ps aux
注意
如果Pod对象中运行了多个容器,那么在程序运行时还需要使用“-c <container_name>”选项指定要于其内部运行程序的容器名称。
若要进入容器的交互式Shell接口,可使用类似如下的命令,斜体部分表示在容器的交互式接口中执行的命令:
~]$ kubectl -it exec $POD_NAME /bin/sh / # hostname myapp-6865459dff-5nsjc / # netstat -tnl Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN / #
2.4.3 部署Service对象
简单来说,一个Service对象可视作通过其标签选择器过滤出的一组Pod对象,并能够为此组Pod对象监听的套接字提供端口代理及调度服务。
1.创建Service对象
“kubectl expose”命令可用于创建Service对象以将应用程序“暴露”(expose)于网络中。例如,下面的命令即可将myapp创建的Pod对象使用“NodePort”类型的服务暴露到集群外部:
~]$ kubectl expose deployments/myapp --type="NodePort" --port=80--name=myapp service "myapp" exposed
上面的命令中,--type选项用于指定Service的类型,而--port则用于指定要暴露的容器端口,目标Service对象的名称为myapp。创建完成后,default名称空间中的对象及其通信示意图如图2-12所示。
图2-12 Service对象在Pod对象前端添加了一个固定访问层
下面通过运行于同一集群中的Pod对象中的客户端程序发起访问测试,来模拟图2-12中的源自myapp Client Pod对象的访问请求。首先,使用kubectl run命令创建一个Pod对象,并直接接入其交互式接口,如下命令的-it组合选项即用于交互式打开并保持其shell命令行接口;而后通过wget命令对此前创建的Service对象的名称发起访问请求,如下命令中的myapp即Service对象名称,default即其所属的Namespace对象的名称:
$ kubectl run client --image=busybox --restart=Never -it -- /bin/sh If you don't see a command prompt, try pressing enter. / # wget -O - -q http://myapp.default:80 Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
创建时,Service对象名称及其ClusterIP会由CoreDNS附件动态添加至名称解析库当中,因此,名称解析服务在对象创建后即可直接使用。
类似于列出Deployment控制器及Pod对象的方式,“kubectl get services”命令能够列出Service对象的相关信息,例如下面的命令显示了Service对象myapp的简要状态信息:
~]$ kubectl get svc/myapp NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE myapp NodePort 10.109.39.145 <none> 80:31715/TCP 5m
其中,“PORT(s)”字段表明,集群中各工作节点会捕获发往本地的目标端口为31715的流量,并将其代理至当前Service对象的80端口,于是,集群外部的用户可以使用当前集群中任一节点的此端口来请求Service对象上的服务。CLUSTER-IP字段为当前Service的IP地址,它是一个虚拟IP,并没有配置于集群中的任何主机的任何接口之上,但每个node之上的kube-proxy都会为CLUSTER-IP所在的网络创建用于转发的iptables或ipvs规则。此时,用户可于集群外部任一浏览器请求集群任一节点的相关端口来进行访问测试。
创建Service对象的另一种方式是使用“kubectl create service”命令,对应于每个类型,它分别有一个专用的子命令,例如“kubectl create service clusterip”和“kubectl create service nodeport”等,各命令在使用方式上也略有区别。
2.查看Service资源对象的描述
“kubectl describe services”命令用于打印Service对象的详细信息,它通常包括Service对象的Cluster IP,关联Pod对象时使用的标签选择器及关联到的Pod资源的端点等,示例如下:
~]$ kubectl describe services myapp-svc Name: myapp Namespace: default Labels: run=myapp Annotations: <none> Selector: run=myapp Type: NodePort IP: 10.109.39.145 Port: <unset> 80/TCP TargetPort: 80/TCP NodePort: <unset> 31715/TCP Endpoints: 10.244.3.2:80 Session Affinity: None External Traffic Policy: Cluster Events: <none>
上面命令的执行结果输出基本上可以做到见名而知义,此处需要特别说明的几个字段具体如下:
1)Selector:当前Service对象使用的标签选择器,用于选择关联的Pod对象。
2)Type:即Service的类型,其值可以是ClusterIP、NodePort和LoadBalancer等其中之一。
3)IP:当前Service对象的ClusterIP。
4)Port:暴露的端口,即当前Service用于接收并响应请求的端口。
5)TargetPort:容器中的用于暴露的目标端口,由Service Port路由请求至此端口。
6)NodePort:当前Service的NodePort,它是否存在有效值与Type字段中的类型相关。
7)EndPoints:后端端点,即被当前Service的Selector挑中的所有Pod的IP及其端口。
8)Session Affinity:是否启用会话粘性。
9)External Traffic Policy:外部流量的调度策略。
2.4.4 扩容和缩容
前面示例中创建的Deployment对象myapp仅创建了一个Pod对象,其所能够承载的访问请求数量即受限于这单个Pod对象的服务容量。请求流量上升到接近或超出其容量之前,用户可以通过Kubernetes的“扩容机制”来扩展Pod的副本数量,从而提升其服务容量。
简单来说,所谓的“伸缩”(Scaling)就是指改变特定控制器上Pod副本数量的操作,“扩容”(scaling up)即为增加副本数量,而“缩容”(scaling down)则意指缩减副本数量。不过,无论是扩容还是缩容,其数量都需要由用户明确给出。
Service对象内建的负载均衡机制可在其后端副本数量不止一个时自动进行流量分发,它还会自动监控关联到的Pod的健康状态,以确保仅将请求流量分发至可用的后端Pod对象。若某Deployment控制器管理包含多个Pod实例,则必要时用户还可以为其使用“滚动更新”机制将其容器镜像升级到新的版本或变更那些支持动态修改的Pod属性。
使用kubectl run命令创建Deployment对象时,“--replicas=”选项能够指定由该对象创建或管理的Pod对象副本的数量,且其数量支持运行时进行修改,并立即生效。“kubectl scale”命令就是专用于变动控制器应用规模的命令,它支持对Deployment资源对象的扩容和缩容操作。例如,如果要将myapp的Pod副本数量扩展为3个,则可以使用如下命令来完成:
~]$ kubectl scale deployments/myapp --replicas=3 deployment.extensions "myapp" scaled
而后列出由myapp创建的Pod副本,确认其扩展操作的完成状态。如下命令显示出其Pod副本数量已经扩增至3个,其中包括此前的myapp-6865459dff-5nsjc:
~]$ kubectl get pods -l run=myapp NAME READY STATUS RESTARTS AGE myapp-6865459dff-52j7t 0/1 ContainerCreating 0 53s myapp-6865459dff-5nsjc 1/1 Running 0 2h myapp-6865459dff-jz7t6 0/1 ContainerCreating 0 53s
Deployment对象myapp规模扩展完成之后,default名称空间中的资源对象及其关联关系如图2-13所示。
图2-13 Deployment对象规模扩增完成
而后由“kubectl describe deployment”命令打印Deployment对象myapp的详细信息,了解其应用规模的变动及当前Pod副本的状态等相关信息。从下面的命令结果可以看出,其Pod副本数量的各项指标都已经转换到了新的目标数量,而其事件信息中也有相应的事件显示其扩增操作已成功完成:
~]$ kubectl describe deployments/myapp Name: myapp …… Selector: run=myapp Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable …… Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 2m deployment-controller Scaled up replica set myapp-6865459dff to 3
由myapp自动创建的Pod资源全都拥有同一个标签选择器“run=myapp”,因此,前面创建的Service资源对象myapp的后端端点也已经通过标签选择器自动扩展到了这3个Pod对象相关的端点,如下面的命令结果及图2-13所示:
~]$ kubectl describe services/myapp Name: myapp …… Endpoints: 10.244.1.3:80,10.244.2.2:80,10.244.3.2:80 ……
回到此前创建的客户端Pod对象client的交互式接口,对Service对象myapp反复发起测试请求,即可验正其负载均衡的效果。由如下命令及其结果可以看出,它会将请求调度至后端的各Pod对象进行处理:
~]$ / # while true; do wget -O - -q http://myapp.default:80/hostname.html; sleep 1; done myapp-6865459dff-jz7t6 myapp-6865459dff-52j7t myapp-6865459dff-5nsjc ……
应用规模缩容的方式与扩容相似,只不过是将Pod副本的数量调至比原来小的数字即可。例如,将myapp的Pod副本缩减至2个,可以使用如下命令进行:
~]$ kubectl scale deployments/myapp --replicas=2 deployment.extensions "myapp" scaled
至此,功能基本完整的容器化应用已在Kubernetes上部署完成,即便是一个略复杂的分层应用也只需要通过合适的镜像以类似的方式就能部署完成。
2.4.5 修改及删除对象
成功创建于Kubernetes之上的对象也称为活动对象(live object),其配置信息(live object configuration)由API Server保存于集群状态存储系统etcd中,“kubectl get TYPE NAME -o yaml”命令可获取到相关的完整信息,而运行“kubectl edit”命令可调用默认编辑器对活动对象的可配置属性进行编辑。例如,修改此前创建的Service对象myapp的类型为ClusterIP,使用“kubectl edit service myapp”命令打开编辑界面后修改type属性的值为ClusterIP,并删除NodePort属性,然后保存即可。对活动对象的修改将实时生效,但资源对象的有些属性并不支持运行时修改,此种情况下,编辑器将不允许保存退出。
有些命令是kubectl edit命令某一部分功能的二次封装,例如,kubectl scale命令不过是专用于修改资源对象的replicas属性值而已,它也同样直接作用于活动对象。
不再有价值的活动对象可使用“kubectl delete”命令予以删除,需要删除Service对象myapp时,使用如下命令即可完成:
~]$ kubectl delete service myapp service "myapp" deleted
有时候需要清空某一类型下的所有对象,只需要将上面命令对象的名称换成“--all”选项便能实现。例如,删除默认名称空间中所有的Deployment控制器的命令如下:
~]$ kubectl delete deployment --all deployment.extensions "myapp" deleted
需要注意的是,受控于控制器的Pod对象在删除后会被重建,删除此类对象需要直接删除其控制器对象。不过,删除控制器时若不想删除其Pod对象,可在删除命令上使用“--cascade=false”选项。
虽然直接命令式管理的相关功能强大且适合用于操纵Kubernetes资源对象,但其明显的缺点是缺乏操作行为以及待运行对象的可信源。另外,直接命令式管理资源对象存在较大的局限性,它们在设置资源对象属性方面提供的配置能力相当有限,而且还有不少资源并不支持命令操作进行创建,例如,用户无法创建带有多个容器的Pod对象,也无法为Pod对象创建存储卷。因此,管理资源对象更有效的方式是基于保存有对象配置信息的配置清单来进行。