Kubernetes + .NET Core 的落地实践

1容器化背景

本来生活网(benlai.com)是一家生鲜电商平台,公司很早就停止了烧钱模式,开始追求盈利。既然要把利润最大化,那就要开源节流,作为技术可以在省钱的方面想想办法。我们的生产环境是由 IDC 机房的 100 多台物理机所组成,占用率高达 95%,闲置资源比较多,于是我们考虑借助 k8s 来重构我们的基础设施,提高我们资源的利用率。

容器化项目团队最初加上我就只有三个人,同时我们还有各自的工作任务要做,留给容器化的时间较少,因此我们要考虑如何快速的搭建容器平台,避免走全部自研这条路,这对我们来说是个巨大的挑战。在经历了一年的容器化之旅后,分享下我们这一年所踩过的坑和获得的经验。

6c8ad3b4912627b862729ff2d5cf9775.png

benlai.com

2面临的问题

在搭建 k8s 集群前,有很多问题摆在我们面前:

  •  人手不足,时间也不充裕,不能有太多自研的需求

  •  我们目前的发布是由测试人员完成的,不可能要求他们去写一个 yaml 或执行 kubectl 做发布,这个学习成本太高也容易出错,因此我们必须构建一个用户体验良好的可视化平台给发布人员使用

  •  我们有大量的 .NET 项目,而 .NET 环境又依赖 Windows

  •  ConfigMap/Secret 不支持版本控制,同时用来存业务配置也不是很方便

  •  k8s 集群和外部的通信如何打通

3容器平台

作为小团队去构建一个容器平台,自研的工作量太大了。前期我们调研过很多可视化平台,比如 k8sdashboard 和 rancher 等等,但是要操作这些平台得需要专业的 k8s 运维知识,这对我们的测试人员不太友好。后来我们尝试了 KubeSphere(kubesphere.io)平台,各方面都比较符合我们的需求,于是决定使用该平台作为我们容器平台的基础,在这之上构建我们自己的发布流程。感兴趣的朋友可以去了解下这个平台。

b1a3764600a3448dbfaee384e926e344.png

kubesphere

4.NET项目

我们的项目有 Java 也有 .NET 的,.NET 项目占了 80% 以上。要支持.NET 意味着要支持 Windows。在我们去年开始启动项目的时候,k8s 刚升级到 1.14 版本,是支持Windows 节点的第一个版本,同时功能也比较弱。经过实验,我们成功对 .NET Framework 的程序进行了容器化,在不改代码的前提下运行在了 Windows 服务器上,并通过 k8s 进行管理。不过我们也遇到了一些比较难处理的问题,使用下来的总结如下:

  •   Kubernetes 版本必须是 1.14 版本或以上

  •   大多数 Linux 容器若不做处理会自动调度到 Windows 节点上

  •   Windows 基础镜像体积普遍比较大

  •   必须使用 Windows Server 2019 及以上版本

  •   k8s 关键性组件以 Windows 服务形式运行,而非 Pod

  •   存储和网络的支持有局限性

  •   部署步骤复杂,易出错

我们调研了一段时间后决定放弃使用 Linux 和Windows 的混合集群,因为这些问题会带来巨大的运维成本,而且也无法避免 Windows 的版权费。

我们也考虑过把这些项目转换成 Java,但其中包含大量的业务逻辑代码,把这些重构为 Java 会消耗巨大的研发和测试的人力成本,显然这对我们来说也是不现实的。那么有没有一种方案是改动很少的代码,却又能支持 Linux 的呢?答案很明显了,就是把 .NET 转 .NET Core。我们采用了这种方案,并且大多数情况能很顺利的转换一个项目而不需要修改一行业务逻辑代码。

当然这个方案也有它的局限性,比如遇到如下情况就需要修改代码无法直接转换:

  •   项目里使用了依赖 Windows API 的代码

  •   引用的第三方库无 .NET Core 版本

  •   WCF 和 Web Forms 的代码

这些修改的成本是可控的,也是我们可以接受的。到目前为止我们已经成功转换了许多 .NET 项目,并且已运行在 k8s 生产环境之上

5k8s集群方案

由于我们是基于物理机部署也就是裸金属(Bare Metal)环境,所以无论基于什么平台搭建,最终还是要考虑如何暴露 k8s 集群这个问题。

暴露方式

  • LoadBalancer, 是 Kubernetes 官方推荐的暴露方式,很可惜官方支持的方式都需要部署在云上。我们公司全部是裸机环境部署,无法使用云方案。

  • NodePort,端口范围一般是 30000 以上,每个端口只能对应一种服务。如果应用越来越多,那端口可能就不够用了。它最大的问题是如果你暴露某一个节点给外部访问,那么这个节点会成为单点。如果你要做高可用,这几个节点都暴露出去,前面一样也要加一个负载均衡,这样事情就复杂了。

  • Ingress,可以解决 NodePort 端口复用的问题,它一般工作在7层上可以复用 80 和 443 端口。使用 Ingress 的前提是必须要有 Ingress Controller 配合,而 Ingress Controller 同样会出现你需要暴露端口并公开的问题。这时候如果你用 HostNetwork 或 HostPort 把端口暴露在当前的节点上,就存在单点问题;如果你是暴露多个节点的话,同样需要在前面再加一个LB。

  • HostNetwork/HostPort,这是更暴力的方式,直接把 Pod 的端口绑定到宿主机的端口上。这时候端口冲突会是一个很大的问题,同时单点问题依旧存在。

裸金属方案

我们分别试用了两套方案 MetalLB(metallb.universe.tf)和 Porter(github.com/kubesphere/porter),这两个都是以 LoadBalancer 方式暴露集群的。我们测试下来都能满足需求。Porter 是 KubeSphere 的子项目,和 KubeSphere 平台兼容性更好,但是目前  Porter 没有如何部署高可用的文档,我在这里简单分享下:

(1)前置条件

  • 首先你的路由器,必须是三层交换机,需要支持 BGP 协议。现在大多数路由设备都会支持这个协议,所以这个条件一般都能满足

  • 其次集群节点上不能有建立 BGP 通信的其他服务。举例来说,当使用 Calico 时,开启了BGP模式。它的 BGP 端口运行在每个集群节点,Calico 和 Porter 同时启用BGP 协议,会有部分节点同时运行两个组件与交换机建立 BGP 协议造成冲突。而 KubeSphere 默认安装的 Calico 是 ipip 模式的,所以我们没有遇到冲突问题

  • 最后一定要有网络运维人员支持,配合你完成路由器配置以及了解整个网络拓扑结构。了解网络拓扑结构是非常重要的,否则会遇到很多问题

(2)配置和部署

a09524ad447a105692c6a24659149ed0.png

(3)架构和逻辑

Porter有两个插件:Porter-Manager 和 Porter-Agent

4e7efc5c89674bce67c96feb5a5458e9.png

Porter-Manager 是使用Deployment 部署到 Master 节点上的,但默认只部署1个副本,它负责同步 BGP 路由到物理交换机(单向 BGP 路由,只需将 kubernetes 私有路由发布给交换机即可,无需学习交换机内物理网络路由)。还有一个组件,Porter-Agent,它以 DaemonSet 的形式在所有节点都部署一个副本,功能是维护引流规则。

6b9dd0e1e8a5d8c734d0c030b0ed79df.png

(4)高可用架构

部署好后,你可能会有疑问:

  • 单个路由器挂了怎么办

  • 单个 porter-manager 挂了怎么办

  • porter-manager 和路由器网络断了怎么办

  • EIP 下一跳地址所在的节点挂了怎么办

  • 某个 EIP 流量突然飙升,一个节点扛不住怎么办

一般路由器或交换机都会准备两台做 VSU (Virtual Switching Unit) 实现高可用,这个是网络运维擅长的,这里不细讲了,主要说下其他几点怎么解决:

  • 确保一个 EIP 有多条 BGP 路由规则,交换机和 Manager 是多对多部署的

  • 确保交换机开启等价路由(ECMP)

  • 要做好故障演练和压力测试

MetalLB 的高可用部署也是类似思路,虽然架构稍有不同,比如它和路由器进行 BGP 通信的组件是 Speaker,对应 Porter 的 Manager;它与Porter 的区别在于高可用目前做的比较好;但是 Local 引流规划不如 Porter,EIP 的下一跳节点必须是 BGP 对等体(邻居)。

6配置中心

Kubernetes 的 ConfigMap 和 Secret 在一定程度上解决了配置的问题,我们可以很轻松的使用它们进行更改配置而不需要重新生成镜像,但我们在使用的过程中还是会遇到一些痛点:

1.不支持版本控制

Deployment 和 StatefulSet 都有版本控制,我们可以使用 rollout 把应用回滚到老的版本,但是 ConfigMap/Secret 却没有版本控制,这会产生一个问题就是当我们的Deployment  要进行回滚时,使用的 ConfigMap 还是最新的,我们必须把 ConfigMap 改回上一个 Deployment 版本所使用的 ConfigMap 内容,再进行回滚,但是这样的操作是很危险的,假设改完 ConfigMap 后有个 Pod 出了问题自动重启了,又或者 ConfigMap 以文件形式挂载到 Pod 中,都会造成程序读取到了错误的配置。一个折中的做法是每个版本都关联一个单独的 ConfigMap。

2.不太适合管理业务配置

一个应用有时候会需要加很多业务配置,在维护大量业务配置时,我们可能需要搜索关键字、查看某个 key 的备注、对 value 的格式进行校验、批量更新配置等等,但是如果使用了ConfigMap ,我们就不得不再做一些工具来满足这些需求,而这些需求对于配置的维护效率是有非常大的影响。

3.热更新

我们知道如果 ConfigMap 是以文件形式进行挂载,那么当修改了 ConfigMap 的值后,过一会所有的 Pod 里相应的文件都会变更,可是如果是以环境变量的方式挂载却不会更新。为了让我们的程序支持热更新,我们需要把 ConfigMap 都挂载成文件,而当配置有很多时麻烦就来了,如果 Key 和文件是一对一挂载,那么 Pod 里会有很多小文件;如果所有 Key 都放到一个文件里,那么维护配置就成了噩梦。

4.配置大小限制

ConfigMap 本身没有大小限制,但是 etcd 有,默认情况下一个 ConfigMap 限制为 1MB,我们估算了下,有个别应用的配置加起来真有可能突破这个限制,为了绕过这个大小限制,目前也只有切割成多个 ConfigMap 的方法了。

为了解决这些痛点,我们综合考虑了很多方案,最终决定还是使用一套开源的配置中心作为配置的源,先通过Jenkins 把配置的源转换成 ConfigMap,以文件形式挂载到 Pod 中进行发布,这样以上的问题都可以迎刃而解。

我们选择了携程的 Apollo(github.com/ctripcorp/apollo)作为配置中心 ,其在用户体验方面还是比较出色的,能满足我们日常维护配置的需求。

7微服务架构

微服务

在迁移微服务到 k8s 集群的时候基本都会遇到一个问题,服务注册的时候会注册成 Pod IP,在集群内的话没有问题,在集群外的服务可就访问不到了。

我们首先考虑了是否要将集群外部与 Pod IP 打通,因为这样不需要修改任何代码就能很平滑的把服务迁移过来,但弊端是这个一旦放开,未来是很难收回来的,并且集群内部的 IP 全部可访问的话,等于破坏了 k8s 的网络设计,思考再三觉得这不是一个好的方法。

我们最后选择了结合集群暴露的方式,把一个微服务对应的 Service 设置成 LoadBalancer,这样得到的一个 EIP 作为服务注册后的 IP,手动注册的服务只需要加上这个 IP 即可,如果是自动注册的话就需要修改相关的代码。

这个方案有个小小的问题,因为一个微服务会有多个 Pod,而在迁移的灰度过程中,外部集群也有这个微服务的实例在跑,这时候服务调用的权重会不均衡,因为集群内的微服务只有一个 IP,通常会被看作是一个实例。因此如果你的业务对负载均衡比较敏感,那你需要修改这里的逻辑。

调用链监控

我们一直使用的是点评的 CAT(github.com/dianping/cat)作为我们的调用链监控,但是要把 CAT 部署到 k8s 上比较困难,官方也无相关文档支持。总结部署 CAT 的难点主要有以下几个:

  • CAT 每个实例都是有状态的,并且根据功能不同,相应的配置也会不同,因此每个实例的配置是需要不一样的,不能简单的挂载 ConfigMap 来解决

  • CAT 每个实例需要绑定一个 IP 给客户端使用,不能使用 Service 做负载均衡。

  • CAT 每个实例必须事先知道所有实例的 IP 地址

  • CAT 每个实例会在代码层面获取自己的 IP,这时候获取到的是可变的 Pod IP,与绑定的 IP 不一致,这就会导致集群内部通信出问题

为了把 CAT 部署成一个 StatefulSet 并且支持扩容,我们参考了 Kafka 的 Helm 部署方式,做了以下的工作:

  • 我们为每个 Pod 创建了一个 Service,并且启用 LoadBalancer 模式绑定了 IP,使每个 Pod 都会有一个独立的对外 IP 地址,称它为 EIP;

  • 我们把所有实例的信息都保存在配置中心,并且为每个实例生成了不同的配置,比如有些实例是跑 Job,有些实例是跑监控的;

  • 我们会预先规划好 EIP,并把这些 EIP 列表通过动态生成配置的方式传给每个实例;

  • 最后我们给每个实例里塞了一个特殊的文件,这个文件里存的是当前这个实例所绑定的 EIP 地址。接着我们修改了 CAT 的代码,把所有获取本地 IP 地址的代码改成了读取这个特定文件里的 IP 地址,以此欺骗每个实例认为这个 EIP 就是它自己的本地 IP。

扩容很简单,只需要在配置中心里添加一条实例信息,重新部署即可。

CI/CD

由于 KubeSphere 平台集成了 Jenkins,因此我们基于 Jenkins 做了很多 CI/CD 的工作。

发布流程

起初我们为每个应用都写了一个 Jenkinsfile,里面的逻辑有拉取代码、编译应用、上传镜像到仓库和发布到 k8s 集群等。接着我为了结合现有的发布流程,通过 Jenkins 的动态参数实现了完全发布、制作镜像、发布配置、上线应用、回滚应用这样五种流程。

处理配置

由于前面提到了 ConfigMap 不支持版本控制,因此配置中心拉取配置生成 ConfigMap 的事情就由 Jenkins 来实现了。我们会在 ConfigMap 名称后加上当前应用的版本号,将该版本的 ConfigMap 关联到 Deployment 中。这样在执行回滚应用时 ConfigMap 也可以一起回滚。同时 ConfigMap 的清理工作也可以在这里完成。

复用代码

随着应用的增多,Jenkinsfile 也越来越多,如果要修改一个部署逻辑将会修改全部的 Jenkinsfile,这显然是不可接受的,于是我们开始优化 Jenkinsfile。

首先我们为不同类型的应用创建了不同的 yaml 模板,并用模板变量替换了里面的参数。接着我们使用了 Jenkins Shared Library 来编写通用的 CI/CD 逻辑,而 Jenkinsfile 里只需要填写需要执行什么逻辑和相应的参数即可。这样当一个逻辑需要变更时,我们直接修改通用库里的代码就全部生效了。

数据落地

随着越来越多的应用接入到容器发布中,不可避免的要对这些应用的发布及部署上线的发布效率、失败率、发布次数等指标进行分析;其次我们当前的流程虽然实现了 CI/CD 的流程代码复用,但是很多参数还是要去改对应应用的 Jenkinsfile 进行调整,这也很不方便。于是我们决定将所有应用的基本信息、发布信息、版本信息、编译信息等数据存放在指定的数据库中,然后提供相关的 API,Jenkinsfile 可以直接调用对应的发布接口获取应用的相关发布信息等;这样后期不管是要对这些发布数据分析也好,还是要查看或者改变应用的基本信息、发布信息、编译信息等都可以游刃有余;甚至我们还可以依据这些接口打造我们自己的应用管理界面,实现从研发到构建到上线的一体化操作。

集群稳定性

我们在构建我们的测试环境的时候,由于服务器资源比较匮乏,我们使用了线上过保的机器作为我们的测试环境节点。在很长一段时间里,服务器不停的宕机,起初我们以为是硬件老化引起的,因为在主机告警屏幕看到了硬件出错信息。直到后来我们生产环境很新的服务器也出现了频繁宕机的问题,我们就开始重视了起来,并且尝试去分析了原因。

后来我们把 CentOS 7 的内核版本升级到最新以后就再也没发生过宕机了。所以说内核的版本与 k8s 和 Docker 的稳定性是有很大的关系。同样把 k8s 和 Docker 升级到一个稳定的版本也是很有必要的。

8未来展望

我们目前对未来的规划是这样的:

  •   支持多集群部署

  •   支持容器调试

  •   微服务与 istio 的结合

  •   应用管理界面

End编后

每一家公司都有自己经历的阶段,如何可拆解、因地制宜解决问题就考验选择。首先是活在当下,同时还要考虑一下未来的一段时期。采用开源软件可以大大享受社区红利,但在做整合的过程中不得不面对各自适配、取舍。采用更主流的、统一的技术栈可能会在简约方面获益。在此,我们需要为本来生活网技术团队的探索点赞

另外,面对这个case,是 .NET Core 救了 .NET 项目呢, 还是初期采用 .NET的坑呢,已经说不清楚了。我们都知道,当当、京东、蘑菇街都经历了.NET或者php转java.....

k8s 弥补了.NET 在互联网的弱势 ,如果没有k8s,.net core也救不了.net,用了k8s, 容器化方面 .NET Core 的优势要比 Java 大得多

Edison>目前很多公司都有来自BATJ等巨头输送出来的P7/P8做技术总监(Edison所在公司也不例外),这些公司无一例外地都在推行Java转型。所谓的政治正确,就是要接受和拥抱Java,哪怕付出的成本很高,很长一段时间无法交付业务价值。在此,真的需要为像本来生活网技术团队这样团队的探索点个赞,在国内.NET坑位生态如此差的情况下能为业界输送一些实践案例真的是很难能可贵的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295019.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

传说中的宇宙最水诺奖得主:本科历史学,却凭借“一纸”博士论文摘取诺贝尔物理学奖,出道即巅峰!...

全世界只有3.14 % 的人关注了爆炸吧知识他,到底是不是最水的诺奖得主?超模君曾经介绍了学历史,最终成为数学、物理大牛的威滕(传送门),然而竟出现了德布罗意姥爷的高分点赞!那今天,超…

基于事件驱动架构构建微服务第13部分:使用来自Apache KAFKA的事件并将投影流传输到ElasticSearch...

原文链接:https://logcorner.com/building-microservices-through-event-driven-architecture-part13-read-model-projection-project-streams-into-elasticsearch/在本教程中,我将展示如何从KAFKA读取流并将流投影到ElasticSearch中。我必须使用来自KAF…

惊呆了!这篇论文全文都是脏话,可编辑部居然对它评价极佳并发表了!

全世界只有3.14 % 的人关注了爆炸吧知识本文转自:募格学术你见过最奇奇怪怪的论文是什么?一教授为了抗议三流科学杂志发送垃圾邮件,回复了一篇全文只重复七个脏话字眼的论文,可没想到的是,它竟然还被 出!版…

单IP无TMG拓扑Lync Server 2013:前端服务器

在前面的基础架构和活动目录两篇文章中,我们已经准备好了Lync Server的所有环境。其实今天虽然部署的是Lync Server 2013的Preview版,但实际上与我们部署Lync的步骤以及规范是完全一样的,所以大家完全可以抛开Lync Server 2013 Preview版本本…

MySQL学习笔记之五:存储引擎和查询缓存

一、存储引擎1、InnoDB⑴InnoDB是基于聚簇索引建立的,基于主键索引查询时,性能较好;它的辅助索引中必须包含主键列;因此,若表上的索引较多,为节约空间,主键应尽可能小⑵InnoDB支持自适应hash索引…

利用SOS扩展库进入高阶.NET6程序的调试

有时候我们可能想深入到程序的运行核心,去观察下内存分配情况以及堆栈内保存的东东,那么作为编程新贵的底层框架.NET6,又为我们提供了什么可用的观测工具呢?1.SOS 扩展是什么?SOS扩展库是Windows 附带的调试扩展库&…

java--用 * 打印出各种图形(新手请进)

------------------------------------ 代码: public class PrintTriangle { public static void main(String[] args) { System.out.println("左边正三角形"); printTopLeft(5); System.out.println("左边倒三角形&quo…

这种动作片还需要汽车特效?

1 奇奇怪怪的扣分点又增加了▼2 就是活好!(素材来源网络,侵删)▼3 不愧是律师!(素材来源网络,侵删)▼4 成都马拉松惊现美食街!(素材来源网络,侵…

Android之支付宝设计与开发

背景 在移动支付领域,支付宝支付占用巨大份额,根据艾瑞咨询公布的报告数据:2014Q3,支付宝斩获了82.6%的市场份额,在移动支付的霸主地位越来越稳固。财付通支付的发力点在微信支付和手Q支付,在移动支付…

秘境探索之一个.NET 对象从内存分配到内存回收

前方高能预警,新手慎入!不听劝阻者,轻则郁闷堆积,重则生死看淡,对编程失去了念想,对生活失去了幻想!好了,心理强大到NB的可以忽略前方若干警示。为了探索.NET对象的内存分配和回收销…

这五部关于宇宙的神级纪录片,带你探索未知的外太空世界

宇宙之大无奇不有,在你的认知里你又知道多少关于宇宙的事情,如果单单用外星人概括你所对宇宙的认知就真的太片面了,小编今天就带来下面这四部关于宇宙的硬核纪录片,带你真正的去了解关于宇宙的知识,让你遨游在宇宙的知…

Exceptionless服务端+kibana部署实时日志纪要

安装软件列表Exceptionless.4.1.2861.zipelasticsearch-5.6.14.zipkibana-5.6.14-windows-x86.zip安装准备1. 在D盘下创建Exceptionless文件夹2. 拷贝需要安装的软件(参照上面安装软件列表)到Exceptionless文件夹下3. 安装JDK 1.8 (C环境中已经有安装, 步骤忽略)4. …

好心帮男朋友洗衣服,他却要分手??

1 每天一个分手小技巧(via.平民窟公主)▼2 导航最近的加油站(素材来源网络,侵删)▼3 单位停电发的蜡烛(via.蜻蜓队长)▼4 我也不知道原来有人这样穿啊!(素材来源网络&…

Android之如何解决右上角不显示3个点的菜单

之前写过小例子,发现菜单栏右上角的那3个点老是显示不出来,今天终于解决了,不废话,先爆照。 我之前的代码menu_main.xml 文件如下 <menu xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools&quo…

大型网站架构系列:电商网站架构案例

为什么80%的码农都做不了架构师&#xff1f;>>> #0 系列目录# 大型分布式网站架构大型分布式网站架构技术总结大型网站架构系列&#xff1a;电商网站架构案例#1 电商案例原因# 分布式大型网站&#xff0c;目前看主要有几类1.大型门户&#xff0c;比如网易&#xff…

八、结构模式之组合(Composite)模式

组合模式属于对象的结构模式&#xff0c;有时又叫做部分-整体模式&#xff0c;组合模式将对象组织到树结构中&#xff0c;可以用来描述整体与部分的联系。其可以使客户端将单纯元素和组合元素同等对待。 当需求中是体现部分与整体层次的结构时&#xff0c;以及你希望用户可以忽…

代言男科、站台微商、变身神棍....这些科学家被捧了几十年,黑历史曝光后,让人三观尽毁......

全世界只有3.14 % 的人关注了爆炸吧知识说到诺贝尔奖得主&#xff0c;尤其科学类奖项&#xff0c;吃瓜群众的感受常常是:不明觉厉。关于他们有多牛x的故事&#xff0c;你肯定听了不少。但你一定很少听过&#xff0c;诺奖得主转而研究伪科学&#xff0c;或者为了恰饭疯狂掉节操的…

拉屎能赚钱?在马桶上月入过万?原来卫生间里还有这么多隐藏福利,超模君都惊了……

全世界只有3.14 % 的人关注了爆炸吧知识模友们&#xff0c;你们有过“带薪拉屎”的经历吗&#xff1f;没错&#xff0c;假如我们每天花10分钟“带薪拉屎”&#xff0c;那一年大概能积攒下来40小时&#xff0c;假如我们每天工作8小时&#xff0c;等于多了5天年假&#xff0c;白嫖…

Android之让图片匀速旋转效果

图片匀速旋转 当我们更新的时候,需要把更新小图标旋转起来,不废话,先爆照 介绍动画: Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换(平移、缩放、旋转)产生动画效果;第二类是 Frame 动画,即顺序播放事先…