• 应云而生,幽灵的威胁 云原生应用交付与运维的思考


    简介: 过去的 2020 是充满不确定性的一年,但也是充满机遇的一年。突发的新冠疫情为全社会的数字化转型按下加速键。云计算已经不再是一种技术,而是成为支撑数字经济发展和业务创新的关键基础设施。在利用云计算重塑企业 IT 的过程中,生于云、长于云、最大化实现云价值的云原生技术得到了越来越多企业的认同,成为企业 IT 降本提效的重要手段。

     

    过去的 2020 是充满不确定性的一年,但也是充满机遇的一年。突发的新冠疫情为全社会的数字化转型按下加速键。云计算已经不再是一种技术,而是成为支撑数字经济发展和业务创新的关键基础设施。在利用云计算重塑企业 IT 的过程中,生于云、长于云、最大化实现云价值的云原生技术得到了越来越多企业的认同,成为企业 IT 降本提效的重要手段。

    然而,云原生变革也不只是基础设施和应用架构等技术层面,同时也在推进企业 IT 组织、流程和文化的变革。

    在 CNCF 2020 年度调研报告中,已经有 83% 的组织也在生产环境中使用 Kubernetes,然而面临的前三大挑战是复杂性,文化改变与安全。

     

    为了更好地加速业务创新和解决互联网规模的挑战,云原生应用架构与开发方式应运而生,与传统单体应用架构相比,分布式微服务架构具备更好的、更快的迭代速度、更低的开发复杂性,更好的可扩展性和弹性。然而,正如星战宇宙中,原力既有光明也有黑暗的一面。微服务应用在部署、运维和管理的复杂性却大大增加,DevOps 文化和背后支撑的自动化工具与平台能力成为关键。

     

    在容器技术出现之前,DevOps 理论已经发展多年。但是,如果”开发“与”运维“团队不能用相同的语言进行交流,用一致的技术进行协作,那就永远无法打破组织和文化的藩篱。Docker 容器技术的出现,实现了软件交付流程的标准化,一次构建,随处部署。结合云计算可编程基础设施和 Kubernetes 声明式的 API,可以通过流水线去实现自动化的持续集成与持续交付应用和基础设施,大大加速了开发和运维角色的融合。

    云原生也是对团队业务价值和功能的重构。传统运维团队的一些职责转移到开发团队,如应用配置和发布,降低了每次发布的人力成本,而运维职责将更加关注系统的稳定性和IT治理。Google 倡导的 SRE Site Reliability Engineering (站点可靠性工程),是通过软件和自动化手段,来解决系统的运维复杂性和稳定性问题。此外,安全与成本优化也成为云上运维关注重点。

    安全是企业上云的核心关切之一。云原生的敏捷性和动态性给企业安全带来新的挑战。由于云上安全是责任共担模型,需要企业理解与云服务商之间的责任边界,更要思考如何通过工具化、自动化的流程固化安全最佳实践。此外,传统安全架构通过防火墙保护边界,而内部的任何用户或服务受到完全的信任。2020 突发的新冠疫情,大量的企业需要员工和客户远程办公与协同,企业应用需要在 IDC 和云上部署和交互。在物理安全边界消失之后,云安全正在迎来一场深刻的变革。

    此外,新冠疫情进一步让企业更加关注IT成本优化。云原生的一个重要优势是充分利用云的弹性能力,来按需提供业务所需计算资源,避免资源浪费,实现成本优化的目标。但是,与传统成本预算审核制度不同,云原生的动态性、和高密度应用部署,让 IT 成本管理更加复杂。

    为此,云原生理念和技术也在发展,帮助用户持续降低潜在风险和系统复杂性。下面我们将介绍在云原生应用交付与运维领域的一些新趋势。

     

    Kubernetes 成为了通用的、统一的云控制平面

    Kubernetes 这个单词来自于希腊语,含义是舵手或领航员,是 “控制论”英文 “cybernetic” 的词根。Kubernetes 成为在容器编排的事实标准,不只得益于 Google 的光环和 CNCF(云原生计算基金会)的努力运作。背后是 Google 在 Borg 大规模分布式资源调度和自动化运维领域的沉淀和系统化思考,认真理解 Kubernetes 架构设计,有助于思考在分布式系统系统调度、管理的一些本质问题。

    Kubernetes 架构的核心就是控制器循环,也是一个典型的"负反馈"控制系统。当控制器观察到期望状态与当前状态存在不一致,就会持续调整资源,让当前状态趋近于期望状态。比如,根据应用副本数变化进行扩缩容,节点宕机后自动迁移应用等。

     

    K8s 的成功离不开 3 个重要的架构选择:

    • 声明式(Declarative)的 API:在 Kubernetes 之上,开发者只需定义抽象资源的目标状态,而由控制器来具体实现如何达成。比如 Deployment、StatefulSet、 Job 等不同类型工作负载资源的抽象。让开发者可以关注于应用自身,而非系统执行细节。声明式API是云原生重要的设计理念,这样的架构方式有助于将整体运维复杂性下沉,交给基础设施实现和持续优化。此外由于分布式系统的内生稳定性挑战,基于声明式的,面向终态的 “level-triggered” 实现比基于命令式 API、事件驱动的 “edge-triggered” 方式可以提供更加健壮的分布式系统实现。
    • 屏蔽底层实现:K8s 通过一系列抽象如 Loadbalance Service、Ingress、CNI、CSI,帮助业务应用可以更好通过业务语义使用基础设施,无需关注底层实现差异。
    • 可扩展性架构:所有 K8s 组件都是基于一致的、开放的 API 进行实现和交互。三方开发者也可通过 CRD(Custom Resource Definition)/ Operator 等方法提供领域相关的扩展实现,极大扩展了 K8s 的应用场景。

     

    正因如此,Kubernetes 管理的资源和基础设施范围已经远超容器应用。下面是几个例子:

    • 基础架构管理:与开源的 Terraform 或者云供应商自身提供的 Infrastructure as Code(IaC)工具如阿里云 ROS、AWS CloudFormation 不同,Crossplane(https://crossplane.io/)和 AWS Controllers for Kubernetes 在 Kubernetes 基础之上扩展了对基础设施的管理和抽象。这样可以采用一致的方式进行管理和变更 K8s 应用和云基础设施。
    • 虚拟机管理:K8s 通过 KubeVirt 可以实现对虚拟机和容器的统一调度与管理,可以利用虚拟化弥补容器技术的一些局限性,比如在 CI/CD 场景中,可以结合 Windows 虚拟机进行自动化测试。
    • IoT 设备管理:KubeEdge 和 OpenYurt 等边缘容器技术都提供了对海量边缘设备的管理能力。
    • K8s 集群管理:阿里云容器服务 ACK 的节点池管理,集群管理等完全都是采用 Kubernetes 方式进行自动化管理与运维的。ACK Infra 支撑了部署在全球各地数万个 Kubernetes 集群,基于 K8s 完成自动化了扩缩容、故障发现/自愈等能力。

    1. 工作负载自动化升级

    K8s 控制器 “把复杂留给自己,把简单交给别人”的理想非常美好,然而实现一个高效、健壮的控制器却充满技术挑战。

    • 由于 K8s 内置工作负载的局限性,一些需求无法满足企业应用迁移的需求,通过Operator framework 进行扩展成为了常见的解决方案。但是一方面对重复的需求重复造轮子,会造成了资源的浪费;也会导致技术的碎片化,降低可移植性。
    • 随着越来越多的企业 IT 架构,从 on Kubernetes 到 in Kubernetes,大量的 CRD、自定义 Controller 给 Kubernetes 的稳定性和性能带来大量的挑战。面向终态的自动化是一把 “双刃剑”,它既为应用带来了声明式的部署能力,同时也潜在地会将一些误操作行为被终态化放大。在发生操作故障时副本数维持、版本一致性、级联删除等机制反而很可能导致爆炸半径扩大。

    OpenKruise 是阿里云开源的云原生应用自动化管理引擎,也是当前托管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 项目。它来自阿里巴巴多年来容器化、云原生的技术沉淀,是阿里内部生产环境大规模应用的基于 Kubernetes 之上的标准扩展组件,一套紧贴上游社区标准、适应互联网规模化场景的技术理念与最佳实践。以开源项目 OpenKruise 方式与社区开放、共建。一方面帮助企业客户在云原生的探索的过程中,少走弯路,减少技术碎片,提升稳定性;一方面推动上游技术社区,逐渐完善和丰富 Kubernetes的应用周期自动化能力。

    更多信息可以参考:《OpenKruise 2021 规划曝光:More than workloads》

    2. 开发与运维新协作界面浮现

    云原生技术出现也带来了企业 IT 组织结构的变化。为了更好应对业务敏捷性的需要,微服务应用架构催生了 “双比萨团队”(Two-pizza teams) 。较小的、独立的、自包含的开发团队可以更好达成共识,加速业务创新。SRE 团队成为了水平支撑团队,支撑上层研发效率提升和系统稳定性。而随着 Kubernetes 的发展,让 SRE 团队可以基于 K8s 构建自己企业的应用平台,推进标准化和自动化,让上层应用开发团队通过自服务的方式进行资源管理和应用生命周期管理。我们看到组织方式进一步发生了变化,新的平台工程团队开始浮现。

     

    参考:
    https://blog.getambassador.io/the-rise-of-cloud-native-engineering-organizations-1a244581bda5

    这也与 K8s 自身定位是非常相契合的。Kubernetes 的技术定位面向应用运维的基础设施和 Platform for Platform,并不是面向开发者的一体化应用平台。越来越多的企业会由平台工程团队基于 Kubernetes 构建自己的 PaaS 平台,提升研发效率和运维效率。

    类似 Cloud Foundry 的经典 PaaS 实现会建立一套独立概念模型、技术实现和扩展机制,这种方式可以提供简化用户体验,但是也引入了一些缺陷。无法和快速发展的 Kubernetes 体系相结合,无法充分组合使用多种新的技术实现,比如 Serverless 编程模型,支持 AI/数据分析等新计算业务。但是基于 K8s 的 PaaS 平台缺乏统一的架构设计和实现规划,会出现很多碎片化的技术实现,并不利于可持续的发展。

    Open Application Model(OAM)开放应用模型,以及它的 Kubernetes 实现 KubeVela 项目,正是阿里云联合微软和云原生社区,共同推出的云原生应用交付与管理领域的标准模型与框架项目。其中,OAM 的设计思想是为包括 Kubernetes 在内的任何云端基础设施提供一个统一、面向最终用户的应用定义模型;而 KubeVela,则是这个统一模型在 Kubernetes 上的 PaaS 参考实现。

     

    KubeVela/OAM 提供了面向 Kubernetes 的服务抽象和服务组装能力,可以将不同实现的工作负载和运维特征进行统一抽象和描述,并提供插件式的注册与发现机制,进行动态组装。平台工程团队可以采用一致的方式进行新功能扩展,并且保持与 Kubernetes 上新的应用框架良好的互操作性。对于应用开发和运维团队,实现了关注点分离(Separation of Concerns),可以将应用定义、运维能力与基础设施实现解构,让应用交付过程变得更加高效、可靠和自动化。

    在云原生应用模型定义领域,业界也在不同方向进行探索。比如 AWS 新发布的 Proton 是面向云原生应用交付的服务,通过 Proton,可以降低容器和 Serverless 部署、运维复杂性,并且可以和 GitOps 结合起来,提升整个应用交付流程的自动化和可管理性。

    阿里云 Serverless K8s 支持的 Knative 可以同时支持 Serverless 容器和函数来实现事件驱动的应用,让开发者使用一个编程模型,可以高效选择底层不同 Serverless 化算力进行优化执行等。

    无处不在的安全风险催生安全架构变革

    1. DevSecOps 成为关键因素

     

    敏捷开发与可编程云基础设施结合在一起,大大提升了企业应用的交付效率。然而在这个过程中,如果忽视了安全风险控制,有可能造成巨大的损失。Gartner 论断,到 2025年,云上基础设施 99% 的安全渗透问题是由于用户错误的配置和管理造成的。

    在传统软件开发流程中,在系统设计开发完成后和发布交付前,安全人员才开始介入进行安全审核。这种流程无法满足业务快速迭代的诉求。”Shifting left on security“ (安全性左移)”开始得到更多的关注,这将应用程序设计、开发人员尽早与安全团队协作,并无缝地嵌入安全实践。通过左移安全性,不仅可以降低安全风险,还可以降低修复成本。IBM 的研究人员发现,解决设计中的安全问题比代码开发期间能节省 6 倍左右的成本,比测试期间能节省 15 倍左右的成本。

    DevOps 研发协作流程也随之扩展成为 DevSecOps。它首先是理念文化的变化,安全成为每个人的责任,而非专注安全团队的责任;其次尽早解决安全问题,将安全左移到软件设计阶段,降低整体安全治理成本;最后是通过自动化工具链而非人治方式,实现风险预防、持续监测和及时响应能力。

    DevSecOps 落地的技术前提是实现可验证的、可复现的构建和部署流程,这样可以保障我们在测试、预发、生产等不同环境对架构安全性进行持续验证和改进。我们可以利用云原生技术中的 immutable infrastructure (不可变基础设施) 和声明式的策略管理 Policy as Code 结合在一起实现 DevSecOps 的落地实践。下图是一个最简化的容器应用 DevSecOps 流水线。

     

    当代码提交之后,可以通过阿里云镜像服务 ACR 主动扫描应用,并对镜像进行签名,当容器服务 K8s 集群开始部署应用时,安全策略可以对镜像进行验签,可以拒绝未通过验签的应用镜像。同理,如果我们利用 Infrastructure as Code 的方式对基础设施进行变更,我们可以通过扫描引擎在变更之前就进行风险扫描,如果发现相关的安全风险可以终止并告警。

    此外,当应用部署到生产环境之后,任何变更都需通过上述自动化流程。这样的方式最小化了人为的错误配置引发的安全风险。Gartner 预测,到 2025年 60% 的企业会采纳 DevSecOps 和不可变基础设施实践,与 2020 年相比降低 70% 安全事件。

    2. 服务网格加速零信任安全架构落地

    分布式微服务应用不但部署和管理复杂性提升,其安全攻击面也被放大。在传统的三层架构中,安全防护主要在南北向流量,而在微服务架构中,东西向流量防护会有更大的挑战。在传统的边界防护方式下,如果一个应用因为安全缺陷被攻陷,缺乏安全控制机制来阻止内部威胁“横向移动”。

     


    https://www.nist.gov/blogs/taking-measure/zero-trust-cybersecurity-never-trust-always-verify

    “零信任”最早由 Forrester 在 2010 年左右提出,简单地说,零信任就是假定所有威胁都可能发生,不信任网络内部和外部的任何人/设备/应用,需要基于认证和授权重构访问控制的信任基础,引导安全体系架构从“网络中心化”走向“身份中心化”;不信任传统网络边界保护,而代之以微边界保护。

    Google 在大力推动云原生安全和零信任架构,比如 BeyondProd 方法论。阿里和蚂蚁集团上云过程中,也开始引入零信任架构理念和实践。其中的关键是:

    • 统一身份标识体系:为微服务架构中每一个服务组件都提供一个独立的身份标识。
    • 统一访问的授权模型:服务间调用需要通过身份进行鉴权。
    • 统一访问控制策略:所有服务的访问控制通过标准化方向进行集中管理和统一控制。

    安全架构是一种 cross-cutting concern,贯穿在整个 IT 架构与所有组件相关的关注点。如果它与具体微服务框架实现耦合,任何安全架构调整都可能对每个应用服务进行重新编译和部署,此外微服务的实现者可以绕开安全体系。而服务网格可以提供独立于应用实现的,松耦合、分布式的零信任安全架构。

    下图是 Istio 服务网格的安全架构:

     

    其中:

    • 既可以利用现有身份服务提供身份标识,也支持 SPIFFE 格式的身份标识。身份标识可以通过 X.509 证书或者 JWT 格式进行传递。
    • 通过服务网格控制平面 API 来统一管理,认证、授权、服务命名等安全策略。
    • 通过 Envoy Sidecar 或者边界代理服务器作为策略执行点(PEP)来执行安全策略,可以为东西向和南北向的服务访问提供安全访问控制。而且 Sidecar 为每个微服务提供了应用级别的防火墙,网络微分段最小化了安全攻击面。

    服务网格让网络安全架构与应用实现解耦,可以独立演进,独立管理,提升安全合规保障。此外利用其对服务调用的遥测能力,可以进一步通过数据化、智能化方法对服务间通信流量进行风险分析、自动化防御。云原生零信任安全还在早期,我们期待未来更多的安全能力下沉到基础设施之中。

    新一代软件交付方式开始浮现

    1. 从 Infrastructure as Code 到 Everything as Code

    基础架构即代码(Infrastructure-as-Code, IaC)是一种典型的声明式 API,它改变了云上企业IT架构的管理、配置和协同方式。利用 IaC 工具,我们可以将云服务器、网络和数据库等云端资源,进而实现完全自动化的创建、配置和组装。

    我们可以将 IaC 概念进行延伸,可以覆盖整个云原生软件的交付、运维流程,即 Everything as Code。下图中涉及了应用环境中各种模型,从基础设施到应用模型定义到全局性的交付方式和安全体系,我们都可以通过声明式方式对应用配置进行创建、管理和变更。

     

    通过这种方式,我们可以为分布式的云原生应用提供灵活、健壮、自动化的全生命周期管理能力:

    • 所有配置可被版本管理,可追溯,可审计。
    • 所有配置可维护、可测试、可理解、可协作。
    • 所有配置可以进行静态分析、保障变更的可预期性。
    • 所有配置可以在不同环境重现,所有环境差异也需要进行显示声明,提升一致性。

    2. 声明式的 CI/CD 实践逐渐受到关注

    更进一步,我们可以将应用程序的所有环境配置都通过源代码控制系统进行管理,并通过自动化的流程进行面向终态地交付和变更,这就是 GitOps 的核心理念。

    GitOps 最初由 Weaveworks 的 Alexis Richardson 提出,目标是提供一套统一部署、管理和监控应用程序的最佳实践。在 GitOps 中,从应用定义到基础设施配置的所有环境信息都作为源代码,通过 Git 进行版本管理;所有发布、审批、变更的流程都记录在 Git 的历史状态中。这样 Git 成为 source of truth,我们可以高效地追溯历史变更、可以轻松回滚到指定版本。GitOps 与 Kubernetes 提倡的声明式 API、不可变基础设施相结合,我们可以保障相同配置的可复现性,避免线上环境由于配置漂移导致的不可预测的稳定性风险。

     

    结合上文提到的 DevSecOps 自动化流程,我们可以在业务上线之前,提供一致的测试和预发环境,更早,更快地捕获系统中的稳定性风险,更完善地验证灰度、回滚措施。

    GitOps 提升了交付效率,改进了开发者的体验,也提升了分布式应用交付的稳定性。

    GitOps 在过去两年时间里,在阿里集团和蚂蚁都被广泛使用,成为云原生应用标准化的交付方式。目前 GitOps 还在发展初期,开源社区还在不断完善相关的工具和最佳实践。2020年,Weaveworks 的 Flagger 目并入 Flux,开发者可以通过 GitOps 的方式实现灰度发布、蓝绿发布、A/B 测试等渐进的交付策略,可以控制发布的爆炸半径,提升发布的稳定性。在 2020 年末,CNCF 应用交付领域小组正式宣布了 GitOps Working Group 的组建,我们期待未来社区将进一步推动相关领域标准化过程和技术落地。

     

    3. 运维体系从标准化、自动化向数据化、智能化演进

    随着微服务应用规模的发展,问题定位、性能优化的复杂度呈爆炸式增长。企业在IT服务管理领域虽然已经拥有多种工具集合,比如,日志分析、性能监控、配置管理等。但是不同管理系统之间是一个个数据孤岛,无法提供复杂问题诊断所必需的端到端可见性。许多现有工具都采用基于规则的方法进行监视、警报。在日益复杂和动态的云原生环境中,基于规则的方法过于脆弱,维护成本高且难以扩展。

    AIOps 是利用大数据分析和机器学习等技术自动化IT运维流程。AIOps 可以通过大量的日志和性能数据处理、系统的环境配置分析,获得对IT系统内部和外部的依赖的可见性,增强前瞻性和问题洞察,实现自治运维。

    得益于云原生技术生态的发展,AIOps 与 Kubernetes 等技术将相互促进,进一步完善企业 IT 的成本优化、故障检测和集群优化等方案。这里面有几个重要的助力:

    • 可观测能力的标准化:随着云原生技术社区 Prometheus、OpenTelemetry、OpenMetrics 等项目的发展,应用可观测性领域在日志、监控、链路追踪等领域进一步标准化和融合,使得多指标、根因分析的数据集更加丰富。Service Mesh 非侵入的数据遥测能力可以在不修改现有应用的前提下获取更加丰富的业务指标。从而提高 AIOPS 的 AI 层面的准确率和覆盖率。
    • 应用交付管理能力的标准化:Kubernetes 声明式 API、面向终态的应用交付方式,提供了更加一致的管理运维体验。Service Mesh 非侵入的服务流量管理能力,让我们可以用透明的方式对应用进行管理和自动化运维。

    通过阿里集团的 DevOps 平台“云效”和容器平台发布变更系统相结合,可以实现应用的“无人值守发布”。在发布过程中,系统持续收集包括系统数据、日志数据、业务数据等各种指标,并通过算法比对发布前后的指标异动。一旦发现问题,就可以对发布过程进行阻断,甚至自动化回滚。有了这项技术,任何一个开发团队都可以安全的做好发布工作,而不必担心线上变更导致的重大故障了。

    云原生成本优化逐渐受到关注

    随着企业将更多核心业务从数据中心迁移到云上,越来越多的企业迫切需要对云上环境进行预算制定、成本核算和成本优化。从固定的财务成本模型,转化为变化的、按需付费的云财务模型,这是一个重要的观念和技术转变。然而大多数企业尚未对云财务管理有清晰的认知和技术手段,在 FinOps 2020 年调研报告中,将近一半的受访者(49%)几乎没有或没有自动化方法管理云支出。为了帮助组织更好了解云成本和IT收益,FinOps 理念开始流行。

    FinOps 是云财务管理的方式,是企业 IT 运营模式的转变,目标是提升组织对云成本的理解和更好地做决策。2020年8月,Linux基金会宣布成立 FinOps 基金会,通过最佳实践、教育和标准推进云财务管学科。目前云厂商开始逐渐加大对 FinOps 的支持,帮助企业的财务流程可以更好适应云资源的可变性和动态性。比如 AWS Cost Explorer, 阿里云费用中心,可以帮助企业更好进行成本分析和分摊。详见:
    https://developer.aliyun.com/article/772964。

    越来越多的企业在云上通过 Kubernetes 平台来管理、使用基础设施资源。通过容器来提升部署密度和应用弹性,从而降低整体计算成本。但是在 Kubernetes 的动态性为资源计量和成本分摊引入新的复杂性挑战。
    由于多个容器可以被动态部署在同一个虚拟机实例之上,可以按需弹性伸缩,我们无法简单将底层云资源与容器应用一一对应。2020年11月,CNCF 基金会和 FinOps 基金会发布了一份新的关于 Kubernetes 云财务管理的白皮书 《FinOps for Kubernetes: Unpacking container cost allocation and optimization》来帮助大家更好理解相关财务管理实践。

    阿里云容器服务也在产品中内置了很多成本管理和优化的最佳实践。很多客户非常关心如何基于 Kubernetes 和资源弹性实现成本优化,通常我们建议企业更好了解自己业务类型,为 K8s 集群划分不同的节点池,在成本、稳定性和性能等多维度考量中寻找平衡点。

     

    • 日常业务:对于可预测的、相对不变的负载,我们可以利用包年包月的裸金属或者大规格虚拟机来提升资源利用率,降低成本。
    • 计划内的短期或周期性业务:比如双十一大促,跨年活动等短期业务峰值,或者月底结算等周期性业务负载变化,我们可以利用虚拟机或者弹性容器实例来应对业务高峰。
    • 非预期的突发弹性业务:比如突发新闻热点,或者临时的计算任务。弹性容器实例可以轻松实现每分钟上千实例的扩容。

    更多关于 Kubernetes 规划问题,可以参考:《关于 Kubernetes 规划的灵魂 n 问》。

    总结

    过去十年,基础架构上云,互联网应用架构升级,研发流程敏捷化几个技术大趋势相交汇,与容器、Serverless、服务网格等技术创新相结合,共同催生了云原生的理念诞生和发展。云原生正在重新定义的计算基础设施、应用架构和组织流程,是云计算发展的历史的必然。感谢所有一起在云原生时代的同行者,让我们共同探索和定义云原生的未来。

    作者:易立 阿里云资深技术专家

    原文链接

    本文为阿里云原创内容,未经允许不得转载

  • 相关阅读:
    四则运算网页版
    第六周工作日志
    课堂作业数组最大和
    第五周总结
    四则运算三结对开发
    学习进度第四周
    个人模块记录表
    学习进度表第三周
    四则运算第二篇
    保序回归问题
  • 原文地址:https://www.cnblogs.com/yunqishequ/p/14467558.html
Copyright © 2020-2023  润新知