赞
踩
作者:Jolestar ,2015年初开始创业,作为技术合伙人,专注于打造一款团队通讯协作工具-Grouk
这是前一段时间在一个微服务的 meetup 上的分享,整理成文章发布出来。
谈微服务之前,先澄清一下概念。微服务这个词的准确定义很难,不同的人有不同的人的看法。比如一个朋友是『微服务原教旨主义者』,坚持微服务一定是无状态的 http API 服务,其他的都是『邪魔歪道』,它和 SOA,RPC,分布式系统之间有明显的分界。而另外也有人认为微服务本身就要求把整体系统当做一个完整的分布式应用来对待,而不是原来那种把各种组件堆积在一起,『拼接』系统的做法。两种说法都有道理,因为如果微服务没有个明确的边界的话,你可能会发现微服务囊括了一切,但如果只是坚持无状态,那微服务相关的一些领域又无法涵盖。我个人对这个问题持开放式的看法,微服务本身代表了一种软件交付以及复用模式的变化,从依赖库到依赖服务,和 SOA 有相通之处,同时它也带来了新的挑战,对研发运维都有影响,所有因为和微服务相关而产生的变化,都可以囊括在大微服务主题下。
微服务主要给我们带来的变化有三点,
##微服务涉及的技术点
这个是我浏览了众多微服务的话题之后摘要出来的一些技术点,不全面也不权威,不过可以看出,微服务主题涉及的面非常广,那这些问题在微服务之前就不存在么?为什么大家谈到微服务的时候才把这些东西拿出来说。这个需要从软件开发的历史说来。软件开发行业从十多年前开始,出现了一个分流,一部分是企业应用开发,软件要安装到企业客户自己的资源上,客户负责运维(或者通过技术支持),一部分是互联网软件,自己开发运维一套软件通过网络给最终用户使用。这两个领域使用的技术栈也逐渐分化,前者主要关注标准化,框架化(复用),易安装,易运维,后者主要关注高可用,高性能,纵向伸缩。而这两个领域到微服务时代,形成了一个合流,都在搞微服务化。主要原因我认为有两点:
也就是说,这些技术点并不是新问题,但如何将这些技术点从业务逻辑中抽取出来,作为独立的,可复用的框架或者服务,这个是大家探寻的新问题。
我这样说,不仅仅是因为这是一个微服务的 meetup,微服务和 Kubernetes 确实是相辅相成的。一方面 Kubernetes 帮助微服务落地,另外一方面微服务促进了对容器和 Kubernetes 的需求。
我们先从 Kubernetes 的 Pod 机制带来的一种架构模式 — Sidecar 来说。下面这段配置文件是我从 Kubernetes 内置的 dns 的 deployment 中抽取出来的 Pod 描述文件,简化掉了资源限制,端口设置以及健康检查等内容。
apiVersion: v1 kind: Pod metadata: name: dnspod spec: containers: - name: kubedns image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.4 args: - --domain=cluster.local. - --dns-port=10053 - --config-dir=/kube-dns-config - --v=2 ports: - containerPort: 10053 name: dns-local protocol: UDP - containerPort: 10053 name: dns-tcp-local protocol: TCP - name: dnsmasq image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.4 args: - -v=2 - -logtostderr - -configDir=/etc/k8s/dns/dnsmasq-nanny - -restartDnsmasq=true - -- - -k - --cache-size=1000 - --log-facility=- - --server=/cluster.local/127.0.0.1#10053 - --server=/in-addr.arpa/127.0.0.1#10053 - --server=/ip6.arpa/127.0.0.1#10053 ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - name: sidecar image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.4 args: - --v=2 - --logtostderr - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.cluster.local,5,A - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.cluster.local,5,A
从这个配置文件可以看出,Kubernetes 的 Pod 里可以包含多个容器,同一个 Pod 的多个容器之间共享网络,Volume,IPC(需要设置),生命周期和 Pod 保持一致。上例中的 kube-dns 的作用是通过 Kubernetes 的 API 监听集群中的 Service 和 Endpoint 的变化,生成 dns 记录,通过 dns 协议暴露出来。dnsmasq 的作用是作为 dns 缓存,cluster 内部域名解析代理到 kube-dns,其他域名通过上游 dns server 解析然后缓存,供集群中的应用使用。sidecar 容器的作用是进行 dns 解析探测,然后输出监控数据。
通过这个例子可以看出来,Kubernetes 的 Pod 机制给我们提供了一种能力,就是将一个本来要捆绑在一起的服务,拆成多个,分为主容器和副容器(sidecar),是一种更细粒度的服务拆分能力。当然,没有 Kubernetes 的时候你也可以这么干,但如果你的程序需要几个进程捆绑在一起,要一起部署迁移,运维肯定想来打你。有了这种能力,我们就可以用一种非侵入的方式来扩展我们的服务能力,并且几乎没有增加运维复杂度。这个在我们后面的例子中也可以看到。
服务发现其实包含了两个方面的内容,一种是要发现应用依赖的服务,这个 Kubernetes 提供了内置的 dns 机制和 ClusterIP 机制,每个 Service 都自动注册域名,分配 ClusterIP,这样服务间的依赖可以从 IP 变为 name,这样可以实现不同环境下的配置的一致性。
apiVersion: v1 kind: Service metadata: name: myservice spec: ports: - port: 80 protocol: TCP targetPort: 9376 selector: app: my-app clusterIP: 10.96.0.11
curl http://myservice
如上面的 Service 的例子,依赖方只需要通过 myservice 这个名字调用,具体这个 Service 后面的后端实例在哪,有多少个,用户不用关心,交由 Kubernetes 接管,相当于一种基于虚 IP(通过 iptables 实现) 的内部负载均衡器。(具体的 clusterIP 实现这里不再详述,可查阅相关资料)。
另外一种服务发现是需要知道同一个服务的其他容器实例,节点之间需要互相连接,比如一些分布式应用。这种需求可以通过 Kubernetes 的 API 来实现,比如以下实例代码:
endpoints, _ = client.Core().Endpoints(namespace).Get("myservice", metav1.GetOptions{}) addrs := []string{} for _, ss := range endpoints { for _, addr := range ss.Addresses { ips = append(addrs, fmt.Sprintf(`"%s"`, addr.IP)) } } glog.Infof("Endpoints = %s", addrs)
Kubernetes 提供了 ServiceAccount 的机制,自动在容器中注入调用 Kubernetes API 需要的 token,应用代码中无需关心认证问题,只需要部署的时候在 yaml 中配置好合适的 ServiceAccount 即可。
关于服务发现再多说两句,没有 Kubernetes 这样的统一平台之前,大家做服务发现还主要依赖一些服务发现开源工具,比如 etcd,zookeeper,consul 等,进行自定义开发注册规范,在应用中通过 sdk 自己注册。但当应用部署到 Kubernetes 这样的平台上你会发现,应用完全不需要自己注册,Kubernetes 本身最清楚应用的节点状态,有需要直接通过 Kubernetes 进行查询即可,这样可以降低应用的开发运维成本。
有些分布式需要 Leader 选举,在之前,大家一般可能依赖 etcd 或者 zookeeper 这样的服务来实现。如果应用部署在 Kubernetes 中,也可以利用 Kubernetes 来实现选举,这样可以减少依赖服务。
Kubernetes 中实现 Leader 选举主要依赖以下特性:
这样,所有的节点都一起竞争更新同一个 Endpoint/ConfigMap,更新成功的,作为 Leader,然后把 Leader 信息写到 Annotation 中,其他节点也能获取到。为了避免竞争过于激烈,会有一个过期机制,过期时间也写入到 Annotation,Leader 定时 renew 过期时间,其他节点定时查询,发现过期就发起新一轮的竞争。这样相当于 Kubernetes 提供了一种以 client 和 API 一起配合实现的 Leader 选举机制,并且在 client sdk 中提供。当前 Kubernetes 的一些内部组件,比如 controller-manager,也是通过这种方式来实现选举的。
当然,如果觉得调用 client 麻烦,或者该语言的 sdk 尚未支持这个特性(应该只有 go 支持了),可以也可以通过 sidercar 的方式实现,参看 https://github.com/kubernetes/contrib/tree/master/election 。也就是说,通过一个 sidecar 程序去做选主,主程序只需要调用 sidecar 的一个 http api 去查询即可。
$ kubectl exec elector-sidecar -c nodejs -- wget -qO- http://localhost:8080 Master is elector-sidecar
Kubernetes 对微服务的运维特性上的支持,主要体现以下两方面:
kubectl set image deployment <deployment> <container>=<image> kubectl rollout status deployment <deployment> kubectl rollout pause deployment <deployment> kubectl rollout resume deployment <deployment> kubectl rollout undo deployment <deployment>
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
滚动升级支持最大不可用节点设置,支持暂停,恢复,一键回滚。自动伸缩支持根据监控数据在一个范围内自动伸缩,很大程度上降低了微服务的运维成本。
微服务这个话题虽然火了有很长时间了,关于微服务的各种框架也有一些。但这些框架大多是编程语言层面来解决的,需要用户的业务代码中集成框架的类库,语言的选择也受限。这种方案很难作为单独的产品或者服务给用户使用,升级更新也受限于应用本身的更新与迭代。直到 Service Mesh 的概念的提出。Service Mesh 貌似也没有比较契合的翻译(有的译做服务齿合层,有的翻译做服务网格),这个概念就是试图在网络层抽象出一层,来统一接管一些微服务治理的功能。这样就可以做到跨语言,无侵入,独立升级。其中前一段时间 Google,IBM,Lyft 联合开源的 istio 就是这样一个工具,先看下它的功能简介:
是不是听上去就很厉害?有的还搞不明白是啥意思?我们看 istio 之前先看看 Service Mesh 能在网络层做些什么。
可以看出,如果接管了应用的进出流量,将网络功能可编程化,实际上可做的事情很多。那我们简单看下 istio 是如何做的。
大致看一下这个架构图,istio 在业务 Pod 里部署了一个 sidecar — Envoy,这是一个代理服务器,前面说的网络层功能基本靠它来实现,然后 Envoy 和上面的控制层组件(Mixer,Pilot,Istio-Auth)交互,实现动态配置,策略执行,安全证书获取等,对用户业务透明。实际部署的时候,并不需要开发者在自己的 Pod 声明文件里配置 Envoy 这个 sidecar,istio 提供了一个命令行工具,在部署前注入(解析声明配置文件然后自动修改)即可,这样 istio 的组件就可以和业务应用完全解耦,进行独立升级。
看到这里,Java 服务器端的研发人员可能会感觉到,这个思路和 Java 当初的 AOP(aspect-oriented programming) 有点像,确实很多 Java 微服务框架也是利用 AOP 的能力来尽量减少对应用代码的侵入。但程序语言的 AOP 能力受语言限制,有的语言里就非常难实现非侵入的 AOP,而如果直接在网络层面寻找切面就可以做到跨语言了。
当前其实已经有许多在网络层实现的中间件,比如有提供数据库安全审计功能的,有提供 APM 的,有提供数据库自动缓存的,但这些中间件遇到的最大问题是增加了用户应用的部署复杂度,实施成本比较高,很难做到对用户应用透明。可以预见,随着 Kubernetes 的普及,这类中间件也会涌现出来。
本质上,微服务的目的是想以一种架构模式,应对软件所服务的用户的规模增长。没有微服务架构之前,大多数应用是以单体模式出现的,只有当规模增长到一定程度,单体架构满足不了伸缩的需求的时候,才考虑拆分。而微服务的目标是在一开始的时候就按照这种架构实现,是一种面向未来的架构,也就是说用开始的选择成本降低以后的重构成本。用经济学的观点来说,微服务是技术投资,单体应用是技术债务,技术有余力那可以投资以期待未来收益,没余力那就只能借债支持当前业务,等待未来还债。而随着微服务基础设施的越来越完善,用很小的投资就可以获得未来很大的收益,就没有理由拒绝微服务了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。