【前言】
2018年我们团队辅助多家公司将应用迁移到了Kubernetes集群中,总结了应用迁移至K8S的七步工作法,这七步工作法不仅仅可以平滑的迁移应用,最重要的是可以让开发、运维、测试人员循序渐进的学习和掌握Kubernetes。
第一步:将应用封装进容器
应用容器化是迁移的第一步,需要设计并规划好镜像的构建方案,由于Docker镜像分层的特性,通常建议使用分层方式进行Docker镜像构建,对于基础镜像可以进行镜像预热操作,例如调用SaltStack在所有Node节点提前进行docker pull。
-
操作系统层:制作本公司常用的系统版本如CentOS、Ubuntu、Alpine,可以在官方镜像的基础上添加自己需要的软件包,同时使用Supervisor作为进程管理工具,根据实际情况考虑是否开启SSH。
-
运行环境层:在已经构建的操作系统层的基础上,把业务常用的运行环境都打包好,如JDK7、JDK8、PHP5.5、JDK8+Tomcat8、Python2、Python3等通用模板。
-
应用层:在已经构建好了通用运行环境的基础上,根据应用再进行调整,然后将代码放进去即可。
应用容器化是一个艰巨的任务,需要提前做好规划!
第二步:将容器放入Pod中
应用容器化后,就需要考虑如何在Pod中运行,因为Pod是Kubernetes管理的最小单元,Kubernetes不直接管理容器,而是管理Pod,Pod里面包含容器。需要考虑是一个Pod中放置多个容器,还是一个Pod中放置一个容器,学习Pod的相关知识,同时需要考虑Pod的资源限制,健康检查,数据持久化等,让技术团队深入对Pod的学习。
一定要保证所有的Pod都有Liveness和Readiness的健康检查配置,甚至可能需要进行简单编码编写可用的状态接口。在微服务化的应用,可以很方便的使用Pod进行单元化的部署,例如一个用户的微服务包含:User API、User Control、User Data等三个模块,彼此之间紧耦合,对外只需要通过User API,这样类型的应用就可以放置在一个Pod中,我们称之为应用单元。
第三步:使用Controllers管理Pod
单一Pod如果出现故障,就会影响业务连续性,所以需要多副本,就像我们给一个Web应用做集群是一样的。Kubernetes提供了不同的Controller,需要根据应用的实际情况选择使用Deployment、DaemonSet、StatefulSet、Job、CronJob等,只需要在Pod的YAML模板上封装上对应的配置即可。
-
Replication Controller(新版本已经被ReplicaSet所替代)
-
ReplicaSet(新版本被封装在Deployment中)
-
Deployment:封装了Pod的副本管理、部署更新、回滚、扩容、缩容等。
-
DaemonSet:保证所有的Node上有且只有一个Pod在运行。
-
StatefulSet:有状态的应用,为Pod提供唯一的标识,它可以保证部署和scale的顺序。
-
Job:使用Kubernetes运行单一任务。
-
CronJob:使用Kubernetes运行定时任务。
针对于有特殊需求的Pod,在这个时候就可以考虑使用Node Selector来自定义Pod的调度,例如为SSD的Node打上disktype=ssd的标签,然后在Pod的描述YAML中进行指定。
第四步:使用Service管理Pod访问
使用Deployment通过多副本的方式保证了Pod的高可用和横向扩展,那么就需要考虑负载均衡,Kubernetes Service就是实现此功能,为应用创建对应的Service。目前Service的负载均衡支持多种实现方式:User Space、iptable和ipvs。
对于初学者来说一定要记着,每一个Service其实都对应一个Endpoint记录该Service对应的Pod的IP地址和端口,就像Nginx里面的upstream一样。有Endpoint Controller负责维护和管理这个Pod的IP地址列表和端口,如果Pod发生变化,就会及时更新。
[root@linux-node1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 14h
nginx NodePort 10.1.147.204 <none> 80:30599/TCP 13h
[root@linux-node1 ~]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 192.168.56.11:6443 14h
nginx 10.2.1.2:80,10.2.2.2:80 13h
(Service、Endpoint、Pod之间的关系)
如果使用ipvs,当你创建Service的时候,kube-proxy会获取Service对应的Endpoint,调用LVS帮我们实现负载均衡的功能。
第五步:使用Ingress提供外部访问
集群内部可以直接使用Service Name进行通信,因为在集群中定义的每个 Service,都会被指派一个 DNS 名称,外部要访问到Kubernetes集群,由于网络路由不通(也可以使用其它手段打通),可以通过Node Port、LoadBlancer、外部IP等对外暴露访问。不过这些都可以理解为4层的负载均衡,如果要实现7层的负载均衡,Kubernetes提供了Ingress。
在Kubernetes中由Ingress Controller来实现Ingress的功能,这个控制器比较特殊,因为其它的控制器基本上都是kube-controller-manager这个服务的一部分,而Ingress Controller确是独立的。
Ingress Controller目前有两大开源项目,一个是Nginx Controller,一个是目前比较流行的Traefik,Traefik是一款开源的反向代理与负载均衡工具。它最大的优点是能够与常见的微服务系统直接整合,可以实现自动化动态配置。目前支持Docker, Swarm,Mesos/Marathon, Mesos, Kubernetes, Consul, Etcd, Zookeeper, BoltDB, Rest API等等后端模型。
internet
|
[ Ingress ]
--|-----|--
[ Services ]
在Kubernetes官方文档中,使用上图的方式描述Ingress的访问路径,这并没有错。容易产生误解的是需要知道Ingress并不经过Service,也不经过kube-proxy访问Pod,而且通过Service找到对应的Endpoint,然后直接把请求分发到对应的PodIP和端口。
小提示:实际上你可以不使用Ingress的功能,自己手动部署一个Nginx来实现7层的负载均衡在项目中也很常见。
第六步:使用PV/PVC管理持久化数据
容器中的存储都是临时的,因此Pod重启的时候,内部的数据会发生丢失。实际应用中,我们有些应用是无状态,有些应用则需要保持状态数据,确保Pod重启之后能够读取到之前的状态数据,有些应用则作为集群提供服务。这三种服务归纳为无状态服务、有状态服务以及有状态的集群服务,其中后面两个存在数据保存与共享的需求,因此就要采用容器外的存储方案。
Kubernetes中存储中有四个重要的概念:Volume、PersistentVolume(PV)、PersistentVolumeClaim (PVC)、StorageClass。掌握了这四个概念,就掌握了Kubernetes中存储系统的核心。引用一张图来说明这四者之间的关系。
为你的应用选择一个对应的存储解决方案也是迁移中非常繁琐的一步,因为要考虑和现有存储的融合,如果只是单独部署一个存储服务会更加简单,NFS、Ceph、GlusterFS都是不错的选择。
第七步:使用ConfigMap管理应用配置文件
在DevOps的部署流水线中,我们强调代码和配置的分离,这样更容易实现流水线的编排。在Kubernetes中提供了ConfigMap资源对象,其实ConfigMap和Secret都是一种卷类型,可以从文件、文件夹等途径创建ConfigMap。然后再Pod中挂载使用。
小结
在上面的内容中介绍了应用迁移至Kubernetes的七步工作法:
-
第一步:将应用封装进容器
-
第二步:将容器放入Pod中
-
第三步:使用Controllers管理Pod
-
第四步:使用Service管理Pod访问
-
第五步:使用Ingress提供外部访问
-
第六步:使用PV/PVC管理持久化数据
-
第七步:使用ConfigMap管理应用配置文件
实际的迁移过程远比这七步更加的复杂,例如还需要考虑Kubernetes集群内部应用和外部应用通信、集群日志采集、监控、高可用。小细节上还有很多需要处理,例如给Pod添加hosts绑定,设置内核参数,Pod里使用Kubernetes的相关变量(例如应用要求在Pod里面的创建以Pod名称或者IP地址命名的日志文件)等等。
迁移小技巧
迁移Kubernetes的唯一技巧就是"提问题",把之前在物理机、虚拟机模式部署一个应用需要实现的运维需求,都需要问一遍在Kubernetes中如何实现?在找寻答案的过程中,就是你一步一步走向Kubernetes的王者之路。