《Kubernetes与云原生应用》专栏是InfoQ向轻元科技首席架构师王昕约稿的系列文章。本专栏包含8篇内容,将会从介绍和分析Kubernetes系统以及云原生应用 入手,逐步推出基于Kubernetes的容器设计模式实践案例,希望对计划应用Kubernetes的朋友有所帮助。本文是该专栏的第四篇。
今天内容:
1. Kubernetes系统架构与设计理念
2. 云原生应用的设计理念与挑战
3. Kubernetes与云原生应用的容器设计模式
4. Kubernetes容器设计模式实践案例-单节点多容器模式
5. Kubernetes容器设计模式实践案例-多节点选举模式
6. Kubernetes容器设计模式实践案例-工作队列模式
7. Kubernetes容器设计模式实践案例-分散收集模式
8. 云原生应用的容器设计模式综述与展望
K8s与容器设计模式 目前K8s社区推出的容器设计模式主要分为三大类:
第一类,单容器管理模式;
第二类,单节点多容器模式;
第三类,多节点多容器模式;
一类比一类更复杂。
根据复杂性的不同,本系列文章给出不同篇幅的实践案例介绍。对于第一类,只在本文中用一小节给与介绍;对于第二类,在本文中,针对每一种典型设计模式分一个小节给与介绍;对于较复杂的第三类,每一种典型设计模式将用一篇文章给与介绍。
单容器管理模式
K8s的最大特色是支持多容器的微服务实例。当然,单容器的模式也是支持的,只不过这种模式并不能突出K8s的特色和强大。很多人对K8s一直以来的印象是:功能强大,但入门较难。其实,单单就启动一个单容器微服务实例,K8s的命令行操作跟docker原生命令一样简单。
运行一个nginx容器
[root@demo-k8s ~]# kubectl run nginx --image=nginx deployment "nginx" created [root@demo-k8s ~]# kubectl get deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx 1 1 1 1 24s [root@demo-k8s ~]# kubectl get rs NAME DESIRED CURRENT AGE nginx-3137573019 1 1 1m
由上面的例子可以看到,K8s只要一个命令既可以启动以nginx为镜像的一个微服务实例。与此同时,K8s的强大之处在于,方便用户用一个命令的同时,仍然保证了K8s应用体系的完整性和规范性。也就是说,虽然用户只运行了一个命令,但K8s为用户自动创建了四种API对象,包括:Deployment,ReplicaSet(RS),Pod和Container
要想扩展伸缩同一服务的实例个数也非常简单。
依靠这种兼顾易用性和模型一致性的设计理念,K8s使自己既适合简单场景也适合复杂场景。
单节点多容器模式
从单节点多容器模式开始的容器设计模式,是真正体现K8s设计特点的地方,也就是基于多容器微服务模型的分布式应用模型。
在K8s体系中,Pod是一个轻量级的节点,同一个Pod中的容器可以共享同一块存储空间和同一个网络地址空间,这使得我们可以实现一些组合多个容器在同一节点工作的模式。既然Pod的特点是共享存储空间和网络地址,那么单节点多容器模式一定是利用这两种特性的。
挎斗模式(Sidecar pattern) 第一种单节点多容器模式是挎斗模式。这种模式主要是利用在同一Pod中的容器可以共享存储空间的能力。
一个典型的挎斗应用场景如图所示:一个工具容器写文件到共享的文件目录,应用主容器从共享的文件目录读文件。
例如,我们可以用Nginx构建一个代码发布仓库,简单的将代码放到某个本地目录即可。为了保持同步,我们同时用一个装有Git客户端的容器定时到原始代码仓库同步下拉最新的代码。这种模式的好处是,工具容器的镜像,也就是打包有Git客户端的镜像可以重用,而不需要跟应用的容器打包在一起。同样的应用,应用主容器不用Nginx也可以用Apache Httpd,都可以跟工具容器组合起来形成微服务。
另一个典型的挎斗模式如图所示:一个工具容器读文件,应用容器写文件。
例如:一个基于Nginx的Web应用向系统文件系统写入日志,而一个收集日志的容器从共享目录读出日志,并输出到集群的日志系统。这一模式的好处在于,工具容器的镜像是可以重用的,不需要在每次更新应用容器打包的时候,把工具容器的执行文件打包进去。
外交官模式(Ambassador pattern)
第二种单节点多容器模式是外交官模式。这种模式主要利用同一Pod中的容器可以共享网络地址空间的特性。
如图所示,在一个Pod中给应用容器搭配一个工具容器作为代理服务器。工具容器帮助应用容器访问外部服务,使得应用容器访问服务时不需要使用外网的IP地址,而只需要用localhost访问本地服务。
在这种模式下,作为代理服务器的工具容器好像外部服务派驻在Pod中的“外交官”,使得应用容器办理业务时只需要跟本Pod的外交官打交道,而不需要出国了,因此而得名。
在这种模式下,作为代理服务器的工具容器好像外部服务派驻在Pod中的“外交官”,使得应用容器办理业务时只需要跟本Pod的外交官打交道,而不需要出国了,因此而得名。
我们这里用一个访问Redis服务的简单案例,来实践体验一下Ambassador模式和K8s单节点多容器模式的应用细节。本案例的文件清单在Github上:https://github.com/xwangqingyuan/kube-templates/tree/master/examples/redis
创建一个初始的Redis Master实例
先创建一个Redis Master节点的Pod用于初始化Redis集群。
kubectl create -f examples/redis/redis-master.yaml
创建一个初始的Redis Master实例
先创建一个Redis Master节点的Pod用于初始化Redis集群。
kubectl create -f examples/redis/redis-master.yaml
创建redis的replication controller
创建控制多个redis服务Pod的RC,当然也可以用Deployment或ReplicaSet来创建。
kubectl create -f examples/redis/redis-controller.yaml [root@demo-k8s ~]# kubectl get rc NAME DESIRED CURRENT AGE redis 1 1 19h
创建完后可以用kubectl get命令查看rc和Pod,会发现并没有产生新的Redis Pod,这是因为原来的Pod redis-master已经满足了replica=1的要求。
创建redis sentinel服务。
kubectl create -f examples/redis/redis-sentinel-service.yaml
创建控制多个redis sentinel服务Pod的RC。
kubectl create -f examples/redis/redis-sentinel-controller.yaml
将redis实例和redis-sentinel实例扩展成3个
kubectl scale rc redis --replicas=3 kubectl scale rc redis-sentinel --replicas=3
删除掉手工启动的redis实例redis-master
删除掉我们已开始手工创建的redis master的Pod,redis的rc会自动启动新的redis以满足replicas=3的要求。同时,Redis sentinel节点会选举出一个新的节点作为master节点。
kubectl delete pods redis-master
Redis集群的验证方法
查询redis集群中所有redis实例的IP。
[root@demo-k8s ~]# kubectl describe pod -l name=redis | grep IP IP:10.120.44.3 IP:10.120.63.3 IP:10.120.80.5
我们知道,这3个redis实例中,只有一个是Master节点,是可写的,可以调用SET命令和GET命令;其他两个节点是Slave节点,是只读的,只能调用GET命令。我们可以用下面的命令测试三个redis节点。
通过上面的测试我们可以知道只有IP为10.120.80.5的redis pod是Master节点,其他两个是slave节点。
对于只读的操作,我们可以利用redis的service IP,通过K8s的kube-proxy来访问,如下,我们得到redis的CLUSTER-IP为10.123.248.129,可以用这个IP来读取Redis数据。那么,如果用这个IP来写数据将怎么样呢,后面将看到。
制作Ambassador容器镜像
截至目前,我们还没有用到外交官模式。下面我们用Haproxy制作一个外交官代理,用来访问Redis服务,使得跟该容器在同一个Pod里的容器,在访问Redis读写服务的时候,都只需要访问本地localhost服务。
本例中的文件在Github上可以找到:https://github.com/xwangqingyuan/kubetemplates/tree/master/examples/redis/haproxy
Dockerfile文件清单:Dockerfile
FROM haproxy:1.5 COPY tmpl-haproxy.cfg / COPY starthaproxy.sh / CMD ["sh", "-c", "/starthaproxy.sh"]
启动haproxy的文件清单:starthaproxy.sh
echo "Proxying localhost ${INPUT_PORT} to ${TARGET_IP}:${TARGET_PORT}" cat tmpl-haproxy.cfg | sed -e "s/INPUT_PORT/${INPUT_PORT}/" -e "s/TARGET_IP/${TARGET_IP}/" -e "s/TARGET_PORT/${TARGET_PORT}/" > /haproxy.cfg haproxy -f /haproxy.cfg
通过这里的Dockerfile构建的代理服务器容器,根据输入的环境变量INPUT_PORT,TARGET_IP和TARGET_PORT,可以将发向本地INPUT_PORT的服务请求转发到目标TARGET_IP:TARGET_PORT上去。
因此这个容器可以作为一个简单方便的本地代理服务器使用。
使用Ambassador代理服务器的pod
使用Ambassador代理服务的Pod文件清单:demo-redis-amb-centos.yaml
在这个Pod中,我们用一个CentOS容器作为应用的主容器来使用Ambassador工具容器,当然也可以用实际的应用容器Tomcat,Nodejs,PHP等等,因为这里只是为了演示测试Ambassador代理,我们用CentOS作为应用容器。其中我们有两个外交官容器,一个用来从Redis集群读数据,容器的名字为redis-amb-read,它将发向本地6379端口的请求转发到redis服务的CLUSTER-IP,最终会轮训地发送给任意一个redis实例;另一个用来向Redis集群写数据,容器的名字为redis-amb-write,它将发向本地16379端口的请求转发到redis master Pod的IP
测试一个使用Ambassador模式的pod
创建一个使用Ambassador代理的Pod,并登陆到主容器进行测试。
kubectl create -f /home/centos/worktemp/redis/demo-redis-amb-centos.yaml kubectl exec -it demo-redis-amb-centos /bin/bash
测试对读容器本地Ambassador的访问。
从测试结果可以发现,对读容器执行GET操作时,操作总是成功的,说明3个redis pod都可以读取数据。而对读容器执行SET操作时,3个操作只有一个是成功的,也就是说只有负载均衡将请求发给redis master pod时,操作能够成功。
测试对写容器本地Ambassador的访问
从测试结果可以发现,对写容器执行SET操作时,操作总是成功的,说明redis-amb-write容器将所有请求都转发给了redis master pod 适配器模式(Adapter pattern) 第三种单节点多容器模式是适配器模式。这种模式对于监控和管理分布式系统尤为重要。 对分布式系统的一种理想设计目标,就是能够实现“分布地执行和存储,统一的监控和管理”。要想实现“统一的监控和管理”,应用和监控管理交互的接口需要是统一的,而且其接口是依照“统一的监控服务”的接口模式来实现。这和面向对象设计模式中的“适配器模式”也非常相似。
一个典型的可以采用适配器模式的系统,是利用Prometheus作为监控服务的分布式系统。在Prometheus周边项目中,有诸多适用于不同应用系统的监控数据输出器(Exporter),负责收集跟特定应用相关的监控数据,使得Prometheus服务可以以统一的数据模式收集不同应用系统的监控数据,每个Exporter同时也都是一个适配器模式的实现。
总结
本文主要介绍了K8s集群中,单节点单容器模式和基于Pod模型所支持的几种单节点多容器模式,包括挎斗模式、外交官模式和适配器模式。并且对于外交官模式,利用一个redis集群案例演示了如何利用外交官模式以访问本地服务的模式访问网络服务。
后续,K8s社区肯定还会发展出其他的容器设计模式,但不论如何,单节点多容器模式主要是利用同一Pod中的容器可以共享存储空间和网络地址空间的特点。本文案例中的代码只能用来演示,还比较简单,有很多的地方可以增强。
例如:用Replication Controller的地方,可以用Deployment或Replica Set来部署,这是K8s社区更为推荐的方式;对于案例中的外交官容器,Master Pod的IP和Redis服务的IP未必需要写死,而是可以通过脚本从指定的服务读取;此外,演示中所用的代理服务器容器也可以增强为支持多个端口映射的代理服务器。