最近一段时间,为了解决我们应用的部署问题,花了些时间将应用(就是我们人人都爱的先知)改造成Docker镜像,从此一整页的部署指南就变成指定一些参数启动镜像几个字,Docker真是一个神奇的大箱子。
不了解Docker的同学可以参见《Docker入门简介》(http://suo.im/4CV4AA)。
应用容器化之后, 在测试环境中自然是很方便,但是要把容器应用在生产环境中, 容器本身还是不够的,在实例数量逐渐增长时,需要一个管理系统,基于某种莫名的信仰(Google啊,Golang之类的那种信仰),我们选择了Kubernetes作为我们的容器管理系统。
有了Docker之后,一套复杂的系统也可以变成像一个单一的应用程序一样非常方便地到处运行,而Kubernetes可以解决的问题范围就广多了,本文会从一些常见的角度出发,讲讲Kubernetes给我们带来了什么。
总的来说,Kubernetes解决了一些容器管理的事情,包括但是不限于以下列举的情况。
服务部署
服务部署是最基本的容器管理功能,依靠容易编写的yaml或者json,创建服务变得很方便,也容易被复用。
一个服务就是一个可读的json(PS:为啥不是yaml,因为yaml对空格要求太严格了,不喜欢,~_~ PPS:并不是不喜欢Python的意思),多个组件都可以用一个json表示,灵活性很高,配合Docker镜像服务器和简单的文件服务器,服务部署变成一件轻松的事情。
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx
spec:
replicas: 2
selector:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
网络连通
使用Flannel来解决网络问题只是一种方式,并不是唯一的方式。
与一些其他方案相比,Flannel最大的特点是对业务的简洁,在使用Flannel之后,不同主机的不同镜像之间可以通过IP直接通信,非常方便。
同时,为了对这些IP进行管理,Kubernetes的DNS模块还对Pod名字,Service名字做了IP映射,业务上使用起来非常方便。
问题是性能会不太好,毕竟在宿主机上cURL一个远端的IP,需要经过Route规则、iptables、Flannel虚拟网卡、封包、传包、宿主机网卡、Flannel虚拟网卡、解包、Route规则,Docker虚拟网卡才能进到目的地,不过究竟有多差还没有测试。
很方便就是了。
资源管理
kubelet可以采集宿主机的资源并汇报给Kubernetes,使得Kubernetes可以拿到集群中的资源情况,同时借助于Docker的资源限制Kubernetes实现了从用户层到部署层到应用层多个层级的资源限制,非常方便。
在进行服务部署时,Kubernetes也能根据各个节点自身的资源情况完成分配,省去了很多运维上的事情。
服务的高可用
Kubernetes内置rs(复制集)的概念,在rs中的实例数量指定后,Kubernetes会尽力保证实例的数量,如果出现节点挂掉或者容器挂掉的情况,rs会使用重启或者选择的节点启动新服务的方式,保证正常实例的数量。
因为etcd本身可以做成集群,Kubernetes的master节点也做了加锁操作,自身的高可用可以通过配置多个主节点加一个LB比较方便地实现。
一个配置良好的Kubernetes集群从管理服务到应用服务都避免了单点的存在,实现了服务的高可用。
版本管理
借助rs(复制集)的概念,Kubernetes可以在不停止业务的情况下对应用进行滚动升级和回滚,非常好用。
监控报警
严格来说,这并不是Kubernetes自带的功能,只是有些比较好用的方案可以直接用在Kubernetes上。
Kubernetes自带的Dashboard可以用来查看非图形的系统状况(自带的图形化展示只是一个demo性质的东西),简单,也足够可用,但是在需要图形化展示系统状况的时候,需要一个额外的组件去完成。
Heapster + InfluxDB + Grafana使用起来比较傻瓜,即装即用,但是由于Heapster没有插件机制,我们如果要监控Pod内的业务指标,就不得不去关心数据的存储格式,自己采集数据写到DB中,并不是特别美。
而open-falcon自带的插件机制会编写自定义数据采集比较友好,open-falcon自带的展示页面比较难用,所以用open-falcon +插件+ Grafana成为我们监控报警的方案,可以比较好的满足需求,开发量也不会太大。
Kubernetes与云
主要有两点:磁盘和LB(负载均衡器)。
在私有部署中,磁盘是不可信设备,需要做好随时挂掉的准备,而在云环境中,磁盘本身做了高可用,是可信设备,在AWS上,Kubernetes支持磁盘的自动挂载,这意味着如果在A节点挂载了一块磁盘启动实例,如果A节点挂掉,实例在B节点自动重新启动,那之前挂载的磁盘也会跟随实例走,实现有状态服务的自动恢复,超级棒的特性(希望国内的厂商也能向Kubernetes提自己的Patch实现类似的功能,自有分支并不是一个太好的主意)。
LB可以直接跟虚拟IP绑定实现服务暴露,也很好用,就是服务多了麻烦了点,需要有一些映射关系的管理,所以我们没有用LB直接绑定虚拟IP,而是用的Ingress的那套东西,LB直接绑定所有的Ingress,之后用子域名进行服务划分。
虽然最后的结果还可以,但是过程并不是一帆风顺的,有些地方也挺折腾。
服务暴露的问题
Kubernetes为每个服务分配了一个虚拟IP,虚拟IP后端可以挂载多个实例,相当于一个逻辑LB,因为IP是虚拟的,只能靠iptables规则在集群内部可用,没法对外暴露服务。
为了实现服务的对外暴露,可以使用端口映射或者集群内代理两种方式实现。
端口映射就是将虚拟IP和端口与宿主机的某个端口进行绑定,再绑定个云服务商提供的LB,就能对外服务了,好处是简单粗暴,不好处是端口管理会成为一件很头疼的事情。
集群内代理使用一个统一的端口,依靠某种规则将请求转发到集群内部,由于规则必然是语义上的规则,所以这种代理只能是应用层代理而不能是传输层代理,好处是端口统一,不好处是应用场景受限。
这次说的是官方推荐的集群内代理实现方式,是使用Nginx+PATH的方式暴露服务,在实际使用时,发现在绝大多数情况下,PATH作为区分手段,从功能上并没有办法完成服务暴露。
假设我们有两个服务,x和y,分别使用/x和/y进行访问,规则如下:
location: /x
upstream: x_backend
location: /y
upstream: y_backend
x_backend是一个http服务,并试图加载自身目录下的m.js文件,这时候就很尴尬了。
对x_backend的请求,/x必须携带,否则对m.js的访问会在代理层失去路由,而附带/x路径之后,对m.js的访问就变成了/x/m.js,这时候x_backend就不得不感知/x这个目录的存在,这表明基于PATH的路由划分对业务的逻辑造成侵入,因此从原理上,官方给出的这套方案是完全不可行的。
所以,在使用集群内代理时,最好使用基于泛域名解析的子域名区分服务的方式,在请求转发时,我们使用了OpenResty的DNS模块完成名字到IP的映射,使用Balancer模块完成请求转发,逻辑清晰、代码简单、性能良好,比监控服务变更之后重写Nginx配置再reload的方式简洁很多,有需求的同学可以参考实现。
防火墙的问题
如果没有耐心一条条看防火墙规则,需要在使用Flannel之前清空防火墙,规则设置为默认放过,之后再启动Flannel服务。
启动服务之后,就不要再去看防火墙规则了。
启动kube-proxy之后,也不要再去看那些NAT转发规则了。
按下回车,默默祈求那成千上万条规则能够正常工作,阿门!
需要主从的场景
Kubernetes自身比较难以解决需要主从模式的服务(突然感觉Redis 3.0的p2p模式有多么好用),需要在服务本身进行配置,不能算缺点,在使用的时候需要注意下就是。
那些快速更新的特性
Kubernetes真的更新太快了,很多特性(比如rc)在还没完善的时候就被干掉了,相关的文档也总是落后于最新代码。
但是更新快并不总是坏事,比如我们做监控会用到PID空间共享这些功能,在Docker支持之后,很快Kubernetes就更新了相关代码,对使用者还是友好的。
总的来说,Kubernetes是一个远算不上成熟的,但是很诱人的容器管理系统,在某种程度上,他可以满足不少实际场景的需求,我们选择了他应用在生产环境中,也希望更多的人去使用和完善他(毕竟拉下水的人越多,系统完善的可能性就越大)。