重量级、长生命周期的物理机已被越来越多、短生命周期的技术所抽象。
生产环境必须实现四个关键功能:
- 服务管理接口:使开发人员能够部署和管理服务。
- 运行时服务管理:确保服务正在运行。
- 监控:可视化服务行为并生成告警。
- 请求路由:将用户的请求路由到服务。
部署模式:编程语言特定的发布包格式
使用特定于编程语言的软件发布包将服务部署到生产环境。
首先要安装运行时,将程序发布包复制到计算机并启动服务。对于java,每个服务实例作为JVM进程运行。
部署流水线构建可执行的JAR文件并将其自动部署到生产环境。生产环境中,每个服务实例都运行在JVM中。可以在同一台计算机上部署多个服务实例。某些语言还允许你在单个进程运行多个服务实例。如单个tomcat上运行多个java服务。
好处:
- 快速部署,快速复制、启动
- 高效的资源利用,如同一机器或进程运行多个实例
弊端
- 缺乏对技术栈的封装 服务可以用各种语言和框架编写,运维团队必须了解部署每个服务的具体细节。
- 无法约束服务实例消耗的资源
- 在同一台机器上运行多个服务实例缺少隔离
- 很难自动判定放置服务实例的位置,需要手动确定放置位置。
部署模式:将服务部署为虚拟机
将作为虚拟机镜像打包的服务部署到生产环境中。每个服务实例都是一个从镜像实例化的虚拟机。
部署流水线运行虚拟机镜像构建器(如Aminator、Packer),这个构建器创建包含服务代码和服务运行所需的任何软件的虚拟机镜像。
好处
- 封装了技术栈
虚拟机镜像可以无须修改部署在任何地方。
- 隔离的服务实例 不能从其他服务中窃取资源
- 使用成熟的云计算基础设施
如AWS
弊端
- 资源利用率较低 每个服务实例拥有一整台虚拟机的开销
- 部署速度较慢 构建、传输虚拟机镜像较慢
- 系统管理的额外开销 负担起给操作系统和运行时打补丁的责任
部署模式:将服务部署为容器
容器由在隔离的沙箱中运行的一个或多个进程组成。多个容器通常在一台机器上运行。容器共享操作系统。
容器有自己的IP地址,可消除端口冲突,有自己的根文件系统。容器运行时的流行示例是Docker。
创建容器时,可指定它的CPU和内存资源,以及依赖于容器实现的I/O资源。使用Docker编排框架时,指定容器的资源尤为重要,编排框架使用容器请求的资源来选择运行容器的底层机器,从而确保机器不会过载。
服务打包为容器镜像,存在镜像仓库中。在运行时,服务由从该镜像实例化的多个容器组成。容器通常在虚拟机运行。单个虚拟机通常会运行多个容器。
使用Docker部署服务
构建Docker镜像
容器镜像是由应用程序和运行服务所需的依赖软件组成的文件系统镜像,构建镜像第一步是创建Dockerfile。Dockerfile描述了如何构建Docker容器镜像。
把Docker镜像推送到镜像仓库
Docker镜像仓库类似于Maven存储库。
使用docker tag命令为镜像指定一个以主机名为前缀的名称和镜像仓库的可选端口。
使用docker push命令将标记的镜像上载到镜像仓库。
运行Docker容器
docker run命令,用于创建和启动容器,可以传递外部化配置,如数据库网络位置。但它不是部署服务的可靠方法:
1、它创建的容器在单个机器运行
2、我们通常需要将服务及其依赖项作为一个单元部署或取消部署。可以使用Docker Compose,它允许使用YAML文件以声明方式定义一组容器。
好处
拥有虚拟机的以下好处:
- 封装技术栈,可以用容器的API实现对服务的管理。
- 服务实例隔离
- 服务实例资源受到限制
另外,它是轻量级技术,容器镜像创建很快,且仅需要传输所需镜像层的子集,传输很快。启动也很快。
弊端
需要承担大量容器镜像管理工作,给操作系统和运行时打补丁,如果没有托管,必须管理容器基础设施以及运行时可能需要的虚拟机基础设施。
使用Kubernetes部署应用
Kubernetes是一个Docker编排框架。
- 资源管理
将一组计算机视为由CPU、内存和存储构成的资源池,将计算机集群视为一台计算机。
- 调度
选择要运行容器的机器
- 服务管理 实现命名和版本化服务。确保始终运行所需数量,实现请求负载均衡,实现服务滚动升级、回滚。
Kubernetes架构
Kubernetes集群中的计算机角色分为主节点和普通节点,主节点负责管理集群,普通节点为工作节点,运行一个或多个Pod。Pod是Kubernetes的部署单元,由一组容器组成。
主节点运行的组件:
- API服务器:
用于部署和管理服务的REST API
- Etcd 存储集群数据键值的NoSQL数据库。
- 调度器 选择要运行Pod的节点
- 控制器管理器 运行控制器,确保集群状态与预期状态一致。
普通节点运行组件:
- Kubelet
创建和管理节点上运行的Pod
- Kube-proxy 管理网络,包括跨Pod的负载均衡
- Pods 应用程序服务
Kubernetes关键概念
- Pod 是Kubernates的基本部署单元,由一个或多个共享IP地址和存储卷的容器组成。
- Deployment Pod的声明性规范。它是一个控制器,确保始终运行所需数量的Pod实例。
- Service 向应用服务的客户端提供的一个静态/稳定的网络地址。
- ConfigMap 名称与值的命名集合,用于定义一个或多个应用程序的外部化配置。
在Kubernetes上部署服务
要部署服务,需要定义一个Deployment对象,创建Kubernetes对象(如Deployment)的最简单方法是编写YAML文件,其中定义名称、Pod规范(端口、环境变量、敏感信息读取、健康检查接口),然后可以使用kubectl apply命令创建或更新Deployment对象。
创建一个Kubernetes服务(Service)
获取服务地址,除了使用客户端发现机制外(Eureka),还可以通过Kubernetes内置的服务发现机制并定义Kubernetes服务(Service)来避免这种情况。
Service是一个Kubernetes对象,默认是ClusterIP类型,它为一个或多个Pod的客户端提供稳定的网络访问端点。它具有IP地址和解析IP地址的DNS名称。服务跨Pod对到该IP地址的流量进行负载均衡。
部署API Gateway
API Gateway的作用是将来自外部世界的流量路由到这个服务。需要能够从集群外部访问服务。Kubernetes Service支持此场景。
NodePort Service对象可通过集群中所有节点上的集群范围的端口访问。任何集群节点上到该端口的任何流量都会负载均衡到后端Pod。你还可以使用LoadBalancer类型的对象,该Service对象自动配置特定于云的负载均衡器。
零停机部署
使用Kubernates时,更新正在运行的服务分三步:
1、使用前面描述的相同过程构建新的容器镜像并将其推送的镜像仓库
2、编辑服务部署的YAML文件,以便它引用新的镜像
3、使用kubectl apply -f命令更新部署。
然后Kubernetes对Pod进行滚动升级。
使用服务网格分隔部署与发布流程
服务版本通过预发布环境测试,并非一定可以在生产正常工作。
更可靠的方法是将部署流程和发布流程分开:
- 将新版本部署到生产环境,而不向其路由任何最终用户请求
- 在生产中进行测试
- 将其发布给少数最终用户
- 逐步将其发布给越来越多用户,直到它处理所有生产流量
- 任何时候出现问题,恢复旧版本。一旦确信新版本可以正常工作,删除旧版本。
服务网格是这种部署方式变得容易,它是网络基础设施,承担微服务基底框架,还提供基于规则的负载均衡和流量路由。
Istio服务网格概述
连接、管理、保护微服务的开放平台,所有服务的网络流量都通过Istio进行处理。
- 流量管理 包括服务发现、负载均衡、路由规则和断路器
- 通信安全 使用传输层安全(TLS)保护服务间通信
- 遥测 捕获有关网络流量的指标并实施分布式追踪
- 策略执行 强制实施配额和费率限制
Istio包括一个控制平面(其组件包括Pilot和Mixer),以及一个由Envoy代理服务器组成的数据平面。Pilot从底层基础设施中提取有关已部署服务的信息并配置数据平面。Mixer负责执行配额和收集遥测信息等策略,并将其报告给监控基础设施。Envoy代理服务器将流量路由到服务中并路由到服务外。每个服务实例都有一个Envoy代理服务器。
使用Istio部署服务
在Istio部署服务,为每个应用程序的服务定义Kubernetes的Service对象和Deployment对象。
还可以为服务的Pod运行Envoy服务器。
通过手动边车注入并运行istioctl kube-inject命令。此命令读取Kubernetes YAML文件并输出包含Envoy代理的已修改配置。然后将修改后配置传送到kubectl apply。
还有方法是自动边车注入,启用此功能后,使用kebectl apply部署服务。Kubernetes自动调用Istio修改Pod定义以包含Envoy代理。
创建到V1版本的路由控制
为了安全地推出新版本,路由规则要将所有流量路由到v1 Pod。它由一个VirtualService和一个DestinationRule组成,VirtualService将流量路由到v1子集,DestinationRule将v1子集定义为标有version:v1的Pod。
部署服务V2版本,把测试流量路由到V2版本
把生产流量路由到V2版本
一个好的策略是最初只路由少量流量,后续逐渐增加。
Serverless部署
使用公共云提供的Serverless部署机制部署服务,如AWS Lambda。
开发Lambda函数
你必须为Lambda函数使用不同的编程语言,Lambda函数的代码和封装依赖于编程语言。用Java语言实现的Lambda函数是一个实现通用接口RequestHandler的类。处理HTTP请求的Lambda函数与Java EE Servlet非常相似。
Java Lambda打包为ZIP或JAR文件。
调用Lambda函数
四种方法:
- HTTP请求
配置AWS API Gateway,将HTTP请求路由到Lambda。
- AWS服务生成的事件
- 定时调用
- 直接使用API调用 让应用程序使用Web服务请求调用它。
使用Lambda函数优劣
好处:
- 有许多AWS服务可供集成
- 消除许多系统管理任务
- 弹性可扩容
- 基于使用情况的定价
弊端:
- 长尾延迟 配置和启动程序需要时间,不适合对延迟敏感的服务
- 基于有限事件与请求的模型 不用于部署长时间运行的服务
使用AWS Lambda和AWS Gateway部署RESTful服务
将服务部署为AWS Lambda函数。AWS API Gateway将HTTP请求路由到AWS Lambda函数,这些函数由服务定义的请求处理程序类实现。
服务架构与传统服务架构非常相似,区别在于Spring MVC控制器已被AWS Lambda请求处理程序类取代。其余业务逻辑没有变化。
你应该选择支持服务要求的最轻量级部署模式。按以下顺序评估选项:Serverless、容器、虚拟机和特定于语言的程序包。