跳到主要内容

4 篇博文 含有标签「技术管理」

技术管理相关

查看所有标签

云原生应用架构设计原则

· 阅读需 16 分钟

1.前言

12 要素应用程序 (12-Factor App) 是由 Heroku 的开发人员定义的用于构建云原生应用程序的一组原则。

然而,它诞生已超过十年,云技术自最初创建以来一直在发展。为了使应用程序能够真正利用现代云基础设施和工具,并在云中蓬勃发展,Kevin Hoffman 修订了最初的12个要素原则,并增加了三项,该书由 O’Reilly 出版社出版。书名是:Beyond the Twelve-Factor App

下面简要介绍这 15 个设计原则。

信息

云原生计算基金会(CNCF)关于云原生技术的定义:

云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。

这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。

2. 15个架构设计原则

2.1 One codebase, one application 一个应用,一个代码仓库

codebase

一个应用一个代码仓库,这里的应用也指微服务,一个微服务通常是具有一定业务边界的功能服务,当我们的软件系统有大量微服务组件时,每个微服务应该有自己的代码仓库。如果多个微服务有共享模块,那么这个共享模块也应该有独立的代码仓库,并使用依赖管理策略去加载它。

2.2 API first API优先

设计 API 时遵循 API 设计规范 OpenAPI v3规范

信息

当前 OpenAPI 规范 v3.1.0 发布时间 2021 年 2 月 15 日

2.3 Dependency management 依赖管理

dependencies

在 Java 项目中,依赖管理的两个流行的工具是 Maven 和 Gradle。工具有助于简化依赖管理的复杂性,开发人员能够声明他们的依赖关系,然后让工具负责实际确保这些依赖关系得到满足。

2.4 Design, build, release, and run 设计,构建,发布,运行

buildreleaserun

这条准则的目标是要最大化的提升发布速度,同时通过自动测试和自动部署来让我们对发布抱有信心。

2.5 Configuration, credentials, and code 配置,证书,和代码

config

配置指会随着部署环境发生变化的值,包括:

  • 后端服务的 URLs 和其他信息,比如 web service 和 SMTP 服务
  • 连接到数据库的必要信息
  • 调用第三方服务的证书
  • 可能需要放到 XMl 或者 YMAL 配置文件的其他属性

配置并不包括程序本身的内部信息,也就是说,如果某一个值在所有的部署环境都是一样的,它就不是配置。

证书是极其敏感的与代码无关的信息,通常来说,程序员会把证书从程序源代码里面提取出来放到属性文件里面,但是这样做并没有解决问题。 因为配置文件本身仍然是代码仓库的一部分,这意味着证书与程序一起发布,这本身就打破了这条规则。

一个最简单的检验证书和配置是否合理的方式,就是假设现在需要把源代码 push 到 GitHub 上面, 如果外面的人需要访问你的代码,你是否把你依赖的服务的敏感信息给暴露出去了呢?不是你的组织内部的人是否看到了内部后端服务的 URLs,证书或者其他敏感的信息呢?

如果能够在不暴露任何敏感信息的情况下去开源你的代码,那么你可能在隔离你的代码、配置、证书方面做的很好了。

外化配置的方式是可以使用一个配置中心,比如开源的 Spring Cloud Configuration Server,或者其他的配置服务产品。

2.6 Logs 日志

logs

将日志视为事件流,Logs 应该被当作一种事件流,也就是说, logs 是程序内部发出来的时间有序的事件序列。一个真正云原生的应用从来不关心如何路由和存储它的输出流。

应该将日志的聚合,处理和存储视为一项非功能性要求,该要求不是由应用程序满足,而是由云提供商或平台上面的其他工具套件来满足。可以使用ELK技术栈(ElasticSearch,Logstash 和 Kibana),Splunk,Sumologic 等工具或任何其他工具来捕获和分析日志。

2.7 Disposability 易处理

通过快速启动和优雅关机最大限度地提高稳健性。 如果一个应用程序不能快速的重启和停止,那么它就不能快速的扩展,部署,发布和恢复。要在构建应用的时候想到这一点。

如果应用程序正在一个高负载的场景下,就需要快速的启动更多的实例去处理这个负载,但是慢启动会阻碍通过扩展去处理较高的负载。如果应用程序不能快速和优雅的关闭,会失去失败场景下快速恢复的能力,不能快速关闭的能力同样会有耗尽资源的风险。

许多应用程序会在启动过程中有一些耗时较长的动作,例如获取数据缓存起来或者加载一些依赖项。需要单独处理这种动作。例如,可以将缓存外部化为后端服务,以便应用程序可以快速启动和关闭,而无需执行启动前加载动作。

2.8 Backing services 支持服务

backingsvcs

将支持服务视为附加资源,将外部组件(如数据库、电子邮件服务器、消息代理和系统人员可以提供和维护的独立服务)视为附加资源。将资源视为支持服务可以提高软件开发生命周期中的灵活性和效率。

这意味着应用程序没有一行代码会与某个特定的服务耦合到一起,可能会有一个用来发送邮件的后端服务,需要通过 SMTP 连接到邮件服务。但是邮件服务的实现细节不会影响到应用,同时应用也不会依赖于某个固定位置的 SMTP 服务器。

2.9 Environment parity 同等环境

dev-prodparity

尽可能的保持开发,预发布,线上环境相同,这一点很重要,以便可以确保所有潜在的错误/故障都可以在开发和测试中识别出来,而不是在应用程序投入生产时暴漏出来。

TestContainers 工具使我们能够确保测试环境也尽可能接近生产环境。

2.10 Administrative processes 管理进程

后台管理任务当作一次性进程运行。

不鼓励将一次性管理或管理任务放在微服务中。包括迁移数据库和运行一次性脚本来进行清理。这些应该作为一次性进程运行,它们可以作为 kubernetes 任务运行。 这样,服务可以专注于业务逻辑。

管理进程示例包括:

  • 数据迁移
  • 运行定时脚本,比如一个凌晨的批量任务
  • 运行一次性的自定义的程序

2.11 Port binding 端口绑定

portbindings

云原生应用通过端口绑定的方式暴露服务。网络可以通过端口号而不是域名来识别服务或应用程序。其原因是,域名和相关的IP地址可以通过手动操作和自动服务发现机制即时分配。因此,将它们用作参考点是不可靠的。根据端口号将服务或应用程序暴露给网络更可靠,也更容易管理。至少,由于网络私有端口号分配和另一个进程公开使用相同端口号之间的冲突而导致的潜在问题可以通过端口转发来避免。

2.12 Stateless processes 无状态进程

process

云原生应用程序应该是一个单一的无状态进程。所有持久状态都必须在应用程序外部,由后端服务提供。因此,无状态不是说不存在状态,而是说状态不能在应用程序中维护。

例如,一个提供用户管理功能的微服务是无状态的,因此所有用户的列表都在后端服务(例如 MongoDB 数据库)中维护。

2.13. Concurrency 并发

concurrancy

向一个大块的单体应用增加CPU核数,增加内存和其他的资源(虚拟的或者物理的),这种做法被称之为垂直扩展,这种做法不适用于云原生应用。

一个更加理想的扩展方式是水平扩展,相比较于把一个本来就很大的服务进程弄的越来越大,可以创建多个进程,然后在这些进程中分配负载。

大多数云提供商已经完善了这一功能,可以配置规则,根据负载或系统中可用的其他运行时遥测动态扩展应用程序实例的数量。

kubernetes 为我们提供了完善的设施来解决水平扩展的问题。

2.14 Telemetry 遥测

telemetry

遥测的字面定义是使用特殊设备对某物进行特定测量,然后使用无线通讯将这些测量传输到其它地方。遥测点到源头的测量是需要通过远程,远距离,无实物连接的方式来完成的。

这里特指监控和测量我们的云原生应用。当需要监控应用的时候,这里通常会有多种不同类型的数据:

  • 定期健康检查
  • 请求审核
  • 业务级别事件
  • 性能指标和系统日志

在规划监控策略时,要考虑清楚到底需要聚合多少信息、信息传入的速率以及要存储多少信息。如果应用程序从1个实例动态扩展到100个实例,那么也会导致日志流量增加百倍。

开源行业标准 OpenTelemeter 是在应用程序中实现有效分布式跟踪的工具,OpenTelemetry 是各类 API、SDK 和工具形成的集合。可用于插桩、生成、采集和导出遥测数据(链路、指标和日志),帮助你分析软件的性能和行为。

2.15 Authentication and authorization 认证和授权

authentication-and-authorization

在理想情况下,所有云原生应用程序都将使用 RBAC(基于角色的访问控制)保护其所有 API 。对应用程序资源的每个请求都应该知道是谁发出请求,以及该使用者所属的角色。这些角色决定调用客户端是否有足够的权限让应用程序接受请求。

有了 OAuth2、OpenID Connect 等工具,各种SSO服务器和标准,以及各种语言特定的身份验证和授权库,安全性应该从一开始就融入到应用程序的开发中,而不应该是在应用程序在生产环境中运行一段时间之后再加上的一个功能。

3. 参考

[1] 云原生计算基金会(CNCF)
[2] 12 要素应用程序 (12-Factor App)
[3] Kevin Hoffman. Beyond the Twelve-Factor App. O’Reilly
[4] Beyond the Twelve-Factor App 中文版
[5] An illustrated guide to 12 Factor Apps
[6] Creating cloud-native applications: 12-factor applications
[7] Beyond the 12 factors: 15-factor cloud-native Java applications
[8] OpenAPI 英文官网
[9] OpenAPI 中文官网
[10] kubernetes
[11] TestContainers
[12] OpenTelemeter
[13] OAuth 2.0
[14] Master OAuth 2.0 from this guide with modern use cases and real-world examples

数据库的版本控制方案

· 阅读需 11 分钟

1. 简介

作为软件开发工作的一部分对数据库进行建模是在项目的早期,但随着开发的进展,数据库模型会存在修改。在项目后期也可能由于客户提出的变更导致数据库模型的修改。许多看似不起眼的改动都可能给系统带来严重的后果。

我们希望有一种方法可以将数据库模型的修改和部署更新尽可能的平滑,把对系统可能的影响降到最低,并且即使发生问题也可以快速定位和修复,不造成毁灭的影响。

2. 数据库部署问题

软件开发中数据库的更改和部署有下面常见的一些问题。

  • 数据库生产部署可能由团队外部的DBA完成,他们不熟悉我们开发的应用程序,也可能不了解数据库结构
  • 手动部署容易出现人为错误
  • 手动部署通常需要等到发版窗口时间
  • 可能会向其他人提供不正确的脚本,从而导致部署错误的代码
  • 数据库的多个副本存在于不同的开发人员计算机上,因此很难确定要使用和部署的正确版本

3.数据库源代码控制示例

假如我们有一个名为 dept 的表。这是创建它的脚本:

CREATE TABLE dept (
id NUMBER,
name VARCHAR(100)
);

我们会将此文件保存为 “dept.sql” 文件,将其添加到我们的存储库中,提交并将其推送到远程代码存储库。

如果想对数据库进行更改怎么办,例如我们增加一个 status 字段?这引出了一个我们该决定如何管理数据库代码的问题。

有两个方案,是基于状态的版本控制和基于迁移的版本控制。

  • 方案一 基于状态

修改原有的 detp.sql 脚本。每当需要对表进行更改(例如添加状态列)时,它就会得到更新。

CREATE TABLE dept (
id NUMBER,
name VARCHAR(100),
status VARCHAR(20)
);
  • 方案二 基于迁移

创建另一个新的脚本 customer_add_status.sql。一个脚本创建客户表,另一个脚本添加状态列。

ALTER TABLE dept ADD COLUMN status VARCHAR(20);

4. 基于状态 VS 基于迁移

基于状态和基于迁移是数据库版本控制的两种方法。

基于状态:当存储反映数据库当前状态的数据库代码时,称为基于状态的版本控制。可以使用版本控制中的代码来创建数据库,脚本表示当前数据库定义是什么。

基于迁移:当存储反映为到达数据库当前状态而采取的步骤的数据库代码时,称为基于迁移的版本控制。版本控制中的脚本反映用于创建对象的原始脚本,以及后续所有变化的过程脚本。

基于状态的好处是

  • 被认为更简单,因为只有一组脚本(例如,每个对象一个脚本,或一些其他划分)。
  • 对象的定义在一个地方,所以任何人都可以打开它看看它应该是什么样子。
  • 版本控制提交历史将告诉我们更改了什么、何时以及谁更改了它们。

基于迁移的好处是

  • 数据库可以建立到某一时间点的状态,这有助于在有多个环境和分支的团队中使用
  • 所有更改都在单独的脚本中,因此知道什么正在更改以及何时更改
  • 由于脚本更小,部署可能更容易

数据库代码应该反映数据库的当前状态,还是应该反映为达到数据库的当前状态而采取的步骤呢?采用哪种方法需要与团队一起来讨论利弊。

通常的建议是使用基于迁移的部署,好处可能更多一些。这带来的另一个问题是可能会有大量过程脚本,当有大量过程脚本或者部署执行脚本带来大量的时间消耗的时候,我们可以用重新确定数据库脚本基线的方式来处理。

5. 重新确定数据库脚本的基线

重新基线化数据库部署脚本涉及将所有数据库部署脚本组合成一组反映数据库当前状态的脚本。这可以减少对同一对象上的数据库所做的更改数量,从而减少部署时间。

例如,假设在一年的时间里,对表进行了几次更改:

  1. 从原始脚本创建表
  2. 添加新列
  3. 添加索引
  4. 更改列的数据类型
  5. 再加三列
  6. 删除列
  7. 将表拆分为两个表

在所有这些脚本之后,很难判断表的样子。可以重新设置脚本基线,而不是为每个部署运行所有这些脚本。这意味着您可以创建一个脚本来创建当前状态的数据库,而不是运行所有这些迁移。

6. Liquibase & Flyway

使用比较广泛的数据库迁移工具有 Liquibase 和 Flyway,这两种工具都可以实现基于迁移的数据库部署工作。有开源的免费版本和付费商业版本。

7. 一些建议原则

下面是一些建议的指导原则。

  • 脚本一旦进入代码库,就不能更改。

一个脚本被添加到版本控制后,如果想对数据库进行更改,无论多小,都要编写一个新脚本。不要更改旧脚本。

  • 使用脚本而不是手动更改数据库。

所有更改都应使用相同的过程完成:编写新的脚本文件并在部署过程中获取它。这有助于避免数据库中的意外错误更改并确保一致性。

  • 确保每个开发人员都有自己的本地数据库,而不是共享的开发数据库。

请确保团队每个人都有自己的本地开发数据库。

  • 避免脚本中的独占锁。

编写的脚本可能会自动或手动在它们正在处理的表上放置独占锁。尽量避免在部署脚本中这样做,因为这会减慢部署过程。不过,这取决于使用的是哪个数据库供应商,因为某些数据库在某些情况下具有表级锁,而另一些则没有。

8. 如何实现上述方案

在团队中实现这一点可能需要一定的时间。技术方面可能需要设置工具和流程。个人方面也可能需要说服人们遵守一些列约定,并在过程中建立信任。

这是从手动数据库部署过程到自动化过程可以采取的步骤的列表。

  • 考虑从一个小的项目和团队开始。
  • 为数据库生成基线模式。这可以手动完成(编写 SQL 脚本)或使用IDE生成。
  • 将数据库脚本添加到源代码管理。
  • 确定用于数据库更改和迁移的工具。
  • 实现所选工具来处理版本控制的脚本。
  • 为每个开发人员实现一个本地数据库
  • 更新部署过程(或创建一个)以从源代码控制中的脚本生成数据库
  • 更新部署过程以将脚本部署到下一个环境(例如 QA )
  • 实施自动化数据库测试。这可能是一大步。
  • 更新部署过程以部署到您的下一个环境(例如 Pre Prod)
  • 更新部署过程以允许用户在需要时(以及测试通过时)触发部署到生产环境
  • 更新部署过程以自动部署到生产环境。

Tekton - 云原生持续集成与交付(CI/CD)工具

· 阅读需 6 分钟

1. 前言

在持续集成与持续交付(CI/CD)领域 Jenkins 是事实上的标准,随着云原生领域技术的发展,微服务架构下的持续集成与持续交付面临新的挑战。Jenkins 开源社区也开启了一个全新的项目 Jenkins X 来应对挑战。它基于 Jenkins 和 Kubernetes 实现,为微服务体系架构下的云原生应用的开发、运行和部署提供了全面的支持。相比于 Jenkins,Jenkins X 复用了 Jenkins 的能力,但更加专注于云原生应用的构建、测试和部署。

Tekton 是一个开源的、供应商中立的框架,用于创建持续集成和交付(CI/CD)系统,由持续交付基金会(CDF) 管理。作为 kubernetes 原生框架,Tekton 是通过为管道、工作流和其他构建块提供行业规范来实现持续交付的现代化。Tekton 允许通过抽象底层实现细节可跨多个云提供商或本地系统构建、测试和部署。

Tekton 和 Jenkins X 都是流行的持续集成/持续交付(CI/CD)工具,Jenkins X 更适用于那些已经熟悉 Jenkins 并希望将其扩展到云原生领域的团队。选择哪个工具取决于具体的项目需求、团队偏好以及团队对云原生技术的支持程度。

2. Tekton 的工作方式

Tekton 的目标是在云原生环境中创建可重用、可组合和声明性的小型构建块。它使用步骤(Step)、任务(Task)、管道(Pipeline)和资源(Resource)来执行此操作,如下图所示。

Tekton-PipelinesArchitecture

图片来源于:developers.redhat.com

  • Step 是对输入执行的操作。如果正在构建 Java 应用程序,它可能会运行一些单元测试或验证代码是否遵循现有的编码标准。Tekton 将在提供的容器中执行每个步骤。对于该Java 应用程序,可以运行安装了 JDK 的基础映像。Step 是 Tekton 中最基本的单元。

  • Task 是按顺序执行的步骤列表。Tekton 在 Kubernetes 的 pod 中运行 Task,这允许在设计一系列相关 step 时拥有一个共享环境。例如,可以在任务中挂载一个卷,该卷将在该特定任务的每个步骤中共享。

  • Pipeline 是可以并行或顺序执行的任务的集合。Tekton 为开发人员提供了如何以及何时执行这些任务的很大灵活性。甚至可以指定任务必须满足的条件才能启动下一个任务。

  • Resource 大多数 Pipeline 需要执行任务的输入,也可以产生输出。在 Tekton 中,这些被称为资源。资源可以是许多不同的类型,如 Git存储库、容器映像或 Kubernetes 集群。

3. Tekton 安装

先决条件

提示

官方 yaml 安装文件中的 image 指向 gcr.io,国内无法访问,需要科学上网。可以使用阿里云提供的镜像文件替代。只是版本略低于官方版本。 参见:阿里云容器计算服务帮助文档-Tekton最佳实践

提示

此安装方式适合作为 Tekton 管道的快速启动安装指南,不适用于生产用途。生产环境官方建议使用Tekton Operator 来安装、升级和管理 Tekton 项目。

kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml

# Specific release
# kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/previous/<version_number>/release.yaml

# 当所有组件在 READY 列下显示1/1时,安装完成。如下图。按 Ctrl+C 停止监控
kubectl get pods --namespace tekton-pipelines --watch

tekton-installed

安装 Tekton Command Line 工具

提示

测试环境为 Ubuntu 24.04 LTS

curl -LO https://github.com/tektoncd/cli/releases/download/v0.38.1/tektoncd-cli-0.38.1_Linux-64bit.deb
sudo dpkg -i ./tektoncd-cli-0.38.1_Linux-64bit.deb

4. 创建一个测试 Task

sudo cat > hello-world.yaml << EOF
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: hello
spec:
steps:
- name: echo
image: alpine
script: |
#!/bin/sh
echo "Hello World"
EOF

kubectl apply -f hello-world.yaml
sudo cat > hello-world-run.yaml << EOF
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: hello-task-run
spec:
taskRef:
name: hello
EOF

kubectl apply -f hello-world-run.yaml

# Verify that everything worked correctly:
# The value True under SUCCEEDED confirms that TaskRun completed with no errors.
kubectl get taskrun hello-task-run

# Take a look at the logs:
kubectl logs --selector=tekton.dev/taskRun=hello-task-run

5.接下来

7.参考

[1] Tekton 官网
[2] 持续交付基金会(CDF)
[3] 阿里云容器计算服务帮助文档-Tekton最佳实践
[4] Tekton Operator
[5] Visual Studio (VS) Code Tekton Pipelines extension by Red Hat

在 Kubernetes 集群中使用 MetalLB 作为负载均衡器

· 阅读需 3 分钟

在 Kubernetes 中不提供负载均衡的实现,网络负载均衡器的实现依赖于云厂商(阿里云、腾讯云、华为云、AWS、Azue 等),如果是自建集群,而不是选择这些公有云厂商提供的 Kubernetes ,在创建 Service 时,我们只能使用类型是 NodePort 和 ExternalIPs 的服务用做外部访问。

我们希望自建集群也能简单便捷的方式对集群外部暴露服务,MetalLB 这个项目正是为了解决这个问题。

先决条件

安装前有几个先决条件需要查看,特别是网络插件(CNI)的兼容性支持。

官网的安装文档 MetalLB Installation

安装

修改 kube-proxy 参数

kubectl edit configmap -n kube-system kube-proxy
提示

--proxy-mode ProxyMode 使用哪种代理模式:在 Linux 上可以是 'iptables'(默认)或 'ipvs'。 在 Windows 上唯一支持的值是 'kernelspace'。 如果配置文件由 --config 指定,则忽略此参数。

ipvs 参数含义可以参考:

  • IPVS vs. IPTables for kube-proxy in Kubernetes[3]
  • Comparing kube-proxy modes: iptables or IPVS?[4]
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
strictARP: true

安装MetalLB

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml

查看命名空间下的组件都正常运行

kubectl get all -n metallb-system

alt text

定义要分配给负载均衡器服务的IP地址并配置 2 层通告。

sudo cat > metallb-config.yaml << EOF
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 10.10.0.150-10.10.0.200
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
EOF

kubectl apply -f metallb-config.yaml

测试

kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --type=LoadBalancer --port=80

# 可以看到这里分配的地址是 10.10.0.150
kubectl get svc

curl http://10.10.0.150

alt text

参考

[1] 创建外部负载均衡器
[2] 组件工具 kube-proxy
[3] IPVS vs. IPTables for kube-proxy in Kubernetes
[4] Comparing kube-proxy modes: iptables or IPVS?