谷歌于2015年正式推出的Kubernetes开源项目目前已经吸引了众多IT公司的关注,这些公司包括Redhat、CoreOS、IBM、惠普等知名IT公司,也包括国内如华为、时速云等公司。为何Kubernetes会引起这么多公司的关注?最根本的缘由是Kubernetes是新一代的基于先进容器技术的微服务架构平台,它将当前火爆的容器技术与微服务架构两大吸引眼球的技术点完美的融为一体,而且切切实实的解决了传统分布式系统开发过程当中长期存在的痛点问题。html
本文假设您已经很熟悉并掌握了Docker技术,这里不会再花费篇幅介绍它。正是经过轻量级的容器隔离技术,Kubernetes实现了“微服务”化的特性,同时借助于Docker提供的基础能力,使得平台的自动化能力得以实现。java
概念与原理node
做为一个架构师来讲,咱们作了这么多年的分布式系统,其实咱们真正关心的并非服务器、交换机、负载均衡器、监控与部署这些事物,咱们真正关心的是“服务”自己,而且在心里深处,咱们渴望能实现图1所示的下面的这段“愿景”:mysql
个人系统中有ServiceA、ServiceB、ServiceC三种服务,其中ServiceA须要部署3个实例、而ServiceB与ServiceC各自须要部署5个实例,我但愿有一个平台(或工具)帮我自动完成上述13个实例的分布式部署,而且持续监控它们。当发现某个服务器宕机或者某个服务实例故障的时候,平台可以自我修复,从而确保在任什么时候间点,正在运行的服务实例的数量都是我所预期的。这样一来,我和个人团队只需关注服务开发自己,而无需再为头疼的基础设施和运维监控的事情而烦恼了。git
图1 分布式系统架构愿景github
直到Kubernetes出现以前,没有一个公开的平台声称实现了上面的“愿景”,这一次,又是谷歌的神做惊艳了咱们。Kubernetes让团队有更多的时间去关注与业务需求和业务相关的代码自己,从而在很大程度上提高了整个软件团队的工做效率与投入产出比。web
Kubernetes里核心的概念只有如下几个:redis
1.Servicesql
2.Pod数据库
3.Deployments(RC)
Service表示业务系统中的一个“微服务”,每一个具体的Service背后都有分布在多个机器上的进程实例来提供服务,这些进程实例在Kubernetes里被封装为一个个Pod,Pod基本等同于Docker Container,稍有不一样的是Pod实际上是一组密切捆绑在一块儿而且“同生共死”的Docker Container,从模型设计的角度来讲,的确存在一个服务实例须要多个进程来提供服务而且它们须要“在一块儿” 的状况。
Kubernetes的Service与咱们一般所说的“Service”有一个明显的的不一样,前者有一个虚拟IP地址,称之为“ClusterIP”,服务与服务之间“ClusterIP+服务端口”的方式进行访问,而无需一个复杂的服务发现的API。这样一来,只要知道某个Service的ClusterIP,就能直接访问该服务,为此,Kubernetes提供了两种方式来解决ClusterIP的发现问题:
第一种方式是经过环境变量,好比咱们定义了一个名称为ORDER_SERVICE 的Service ,分配的ClusterIP为10.10.0.3 ,则在每一个服务实例的容器中,会自动增长服务名到ClusterIP映射的环境变量:ORDER_SERVICE_SERVICE_HOST=10.10.0.3,因而程序里能够经过服务名简单得到对应的ClusterIP。
第二种方式是经过DNS,这种方式下,每一个服务名与ClusterIP的映射关系会被自动同步到Kubernetes集群里内置的DNS组件里,因而直接经过对服务名的DNS Lookup机制就找到对应的ClusterIP了,这种方式更加直观。
因为Kubernetes的Service这一独特设计实现思路,使得全部以TCP /IP 方式进行通讯的分布式系统都能很简单的迁移到Kubernetes平台上了。如图2所示,当客户端访问某个Service的时候,Kubernetes内置的组件kube-proxy透明的实现了到后端Pod的流量负载均衡、会话保持、故障自动恢复等高级特性。
图2 Kubernetes负载均衡原理
Kubernetes是如何绑定Service与Pod的呢?它如何区分哪些Pod对应同一个Service?答案也很简单——“贴标签”。每一个Pod均可以贴一个或多个不一样的标签(Label),而每一个Service都一个“标签选择器”,标签选择器(Label Selector)肯定了要选择拥有哪些标签的对象,好比下面这段YAML格式的内容定义了一个称之为ku8-redis-master的Service,它的标签选择器的内容为“app: ku8-redis-master”,代表拥有“app= ku8-redis-master”这个标签的Pod都是为它服务的。
apiVersion: v1 kind: Service metadata: name: ku8-redis-master spec: ports: - port: 6379 selector: app: ku8-redis-master |
下面是对应的Pod的定义,注意到它的labels属性的内容:
apiVersion: v1 kind: Pod metadata: name: ku8-redis-master labels: app: ku8-redis-master spec: containers: - name: server image: redis ports: - containerPort: 6379 restartPolicy: Never |
最后,咱们来看看Deployment/RC的概念,它的做用是用来告诉Kubernetes,某种类型的Pod(拥有某个特定标签的Pod)须要在集群中建立几个副本实例,Deployment/RC的定义实际上是Pod建立模板(Template)+Pod副本数量的声明(replicas):
apiVersion: v1 kind: ReplicationController metadata: name: ku8-redis-slave spec: replicas: 2 template: metadata: labels: app: ku8-redis-slave spec: containers: - name: server image: devopsbq/redis-slave env: - name: MASTER_ADDR value: ku8-redis-master ports: - containerPort: 6379 |
Kubernetes开发指南
本节咱们以一个传统的Java应用为例,来讲明如何将其改造迁移到Kubernetes的先进微服务架构平台上来。
如图3所示,咱们的这个示例程序是一个跑在Tomcat里的Web应用,为了简化,没有用任何框架,直接在JSP页面里经过JDBC操做数据库。
图3 待改造的Java Web应用
上述系统中,咱们将MySQL服务与Web应用分别建模为Kubernetes中的一个Service,其中MySQL服务的Service定义以下:
apiVersion: v1 kind: Service metadata: name: mysql spec: ports: - port: 3306 selector: app: mysql_pod |
MySQL服务对应的Deployment/RC的定义以下:
apiVersion: v1 kind: ReplicationController metadata: name: mysql-deployment spec: replicas: 1 template: metadata: labels: app: mysql_pod spec: containers: - name: mysql image: mysql imagePullPolicy: IfNotPresent ports: - containerPort: 3306 env: - name: MYSQL_ROOT_PASSWORD value: "123456" |
下一步,咱们须要改造Web应用中获取MySQL地址的这段代码,从容器的环境变量中获取上述MySQL服务的IP与Port:
String ip=System.getenv("MYSQL_SERVICE_HOST"); String port=System.getenv("MYSQL_SERVICE_PORT"); ip=(ip==null)?"localhost":ip; port=(port==null)?"3306":port; conn = java.sql.DriverManager.getConnection("jdbc:mysql://"+ip+":"+port+"?useUnicode=true&characterEncoding=UTF-8", "root","123456"); |
接下来,将此Web应用打包为一个标准的Docker镜像,名字为k8s_myweb_image,这个镜像直接从官方Tomcat镜像上添加咱们的Web应用目录demo到webapps目录下便可,Dockerfile比较简单,以下所示:
FROM tomcat MAINTAINER bestme <bestme@hpe.com> ADD demo /usr/local/tomcat/webapps/demo |
相似以前的MySQL服务定义,下面是这个Web应用的Service定义:
apiVersion: v1 kind: Service metadata: name: hpe-java-web spec: type: NodePort ports: - port: 8080 nodePort: 31002 selector: app: hpe_java_web_pod |
咱们看到这里用了一个特殊的语法:NodePort,而且赋值为31002,词语法的做用是让此Web应用容器里的8080端口被NAT映射到kuberntetes里每一个Node上的31002端口,从而咱们能够用Node的IP和端口31002来访问Tomcat的8080端口,好比我本机的能够经过http://192.168.18.137:31002/demo/来访问这个Web应用。
下面是Web应用的Service对应的Deployment/RC的定义:
apiVersion: v1 kind: ReplicationController metadata: name: hpe-java-web-deployement spec: replicas: 1 template: metadata: labels: app: hpe_java_web_pod spec: containers: - name: myweb image: k8s_myweb_image imagePullPolicy: IfNotPresent ports: - containerPort: 8080 |
定义好全部Service与对应的Deployment/RC描述文件后(总共4个yaml文件),咱们能够经过Kubernetes的命令行工具kubectrl –f create xxx.yaml提交到集群里,若是一切正常,Kubernetes会在几分钟内自动完成部署,你会看到相关的资源对象都已经建立成功:
-bash-4.2# kubectl get svc NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE hpe-java-web 10.254.183.22 nodes 8080/TCP 36m kubernetes 10.254.0.1 <none> 443/TCP 89d mysql 10.254.170.22 <none> 3306/TCP 36m -bash-4.2# kubectl get pods NAME READY STATUS RESTARTS AGE hpe-java-web-deployement-q8t9k 1/1 Running 0 36m mysql-deployment-5py34 1/1 Running 0 36m -bash-4.2# kubectl get rc NAME DESIRED CURRENT AGE hpe-java-web-deployement 1 1 37m mysql-deployment 1 1 37m |
结束语
从上面步骤来看,传统应用迁移改造到Kubernetes上仍是比较容易的,而借助于Kubernetes的优点,即便一个小的开发团队,也能在系统架构和运维能力上迅速接近一个大的研发团队的水平。
此外,为了下降Kubernetes的应用门槛,咱们(惠普中国CMS研发团队)开源了一个Kubernetes的管理平台Ku8 eye,项目地址为https://github.com/bestcloud/ku8eye,Ku8 eye很适合用做为小公司的内部PaaS应用管理平台,其功能架构相似图4所示的Ku8 Manager企业版,Ku8 eye采用Java开发完成,是目前惟一一个开源的Kubernetes图形化管理系统,也但愿更多爱好开源和有能力的同行参与进来,将它打形成为国内最好的云计算领域的开源软件。