正直的芹菜 · 光明区实验学校(集团)华夏中学--华夏中学· 3 月前 · |
性感的生菜 · 2016广东高考语文作文点评:全国卷来了 ...· 1 年前 · |
旅途中的盒饭 · 如何评价歌手毛阿敏的地位? - 知乎· 1 年前 · |
讲道义的番茄 · 【战旗】北汽制造_战旗报价_战旗图片_新车评网· 1 年前 · |
@文章概览 Who?Docker 的创始人: "Wasm 若早几年出现,Docker 就不会诞生了";Why?Wasm 可以取代 Container;When?WebAssembly 发展历程;Where?为什么 WebAssembly 不等同于计算机编译;What?WAT (WebAssembly Text Format),Wasm 的文本表示形式;How?Wasm 如何取代容器化? Wasm 是一种 WebAssembly 的简称,是底层的、二进制格式的 Web 应用程序的标准(而非编译)。下文中的 Wasm 代指 WebAssmbly。 一、Who?Docker的创始人: "Wasm 若早几年出现,Docker 就不会诞生了" “如果 WebAssembly(Wasm) 在几年前出现,Docker 可能就不会出现了。因为它是一项非常强大的跨平台技术,可以让我们使用不同的编程语言来编写跨平台应用程序。Docker 的原始动力之一就是提供一个跨平台部署和应用的方法。” -- Solomon Hykes。 如果 Wasm 早在几年前问世,就可能成为一种替代 Docker 的技术,让跨平台部署和应用程序更加容易实现,运行更加流畅。 二、Why? -- Wasm 可以取代 Container Wasm 和 Container 是两种不同的技术,它们有各自的优势和应用场景。但是,Wasm 也确实可以在某些场景下替代 Container,下面就是一些主要的理由: WASM 的优势 跨平台性好 因为 Wasm 是低级字节码格式,它可以在任何操作系统和编程语言上运行。因此,Wasm可以被看作是一种跨平台的计算单元,可以消除由于平台、语言和工具的差异而引起的一些问题。 而 Container 则需要使用相同的操作系统内核,并且运行的应用程序必须具有与主机系统相同的架构和库,这限制了其在不同平台上的灵活性。 Security 安全性 Wasm 有一个额外的虚拟机层,以及内置的安全机制,这意味着开发人员可以使用 Wasm 来限制代码的访问范围,避免非法访问和恶意代码。Wasm 在运行时不会直接与主机系统交互,也不会访问操作系统的 API,因此,它能够在比 Container 更安全的环境下运行。而在 Container 中,应用程序可以访问操作系统的 API 和文件系统,这也 增加了安全隐患。 资源占用少 Wasm 是一种非常轻量级的技术,它的内存占用量和运行时开销都非常小,这意味着使用Wasm时可以更加高效地利用计算资源。 相比之下,Container 需要完整的操作系统虚拟化,并且需要为每个应用程序提供一定数量的CPU、内存和磁盘空间,这可能会导致 资源浪费。 Wasm 模块可以打包成一个自包含单元,具有独立的状态和数据。这意味着,Wasm 可以方便地在不同平台、设备和应用之间传递、共享和重用,这为应用程序的创建和分发带来了极大的便利性。而在 Container 中,应用程序以独立的镜像形式进行分发,镜像本身非常庞大,导致容易出现资源浪费。 执行效率高 Wasm 使用基于栈的虚拟机,可以避免一些性能问题,它更接近于原生代码,这使得执行效率相对而言可以更高。 相比之下,Container 的启动时间较长,需要进行操作系统的启动和初始化,也需要操作系统和主机之间的通信和管理,相对而言执行效率可能较低。 当然,这并不是说 Wasm就一定可以替代 Container,Wasm 和 Container 都有各自的优缺点和应用场景,开发者应该根据实际情况选择合适的技术进行开发和应用。 容器化的优势 除了 学习成本高、额外复杂性 等问题,容器化也带来了显著的优势: 更高的部署效率 容器化技术可以将应用程序及其依赖项、配置文件等全部打包在一个 Docker 镜像中。这样,就可以避免因缺少依赖项等因素导致应用无法正常部署的问题,同时也可以加快应用部署的速度。 更高的可移植性 Docker 容器提供了跨平台、跨环境的解决方案,可以在不同的操作系统、开发环境、生产环境等各种场景中运行。 更高的灵活性和可伸缩性 基于容器化技术,可以通过快速部署、拆分或合并容器实例,来快速应对不同业务的需求变化和峰值时期的负载压力。 更高的效率和资源利用率 容器化技术使用了 Docker 全新的文件系统分层技术,避免了多个 Docker 容器应用程序的多次复制,节省了大量磁盘空间,同时也减少了不必要的性能消耗。 更高的应用程序隔离性和安全性 Docker 利用了 Linux 内核的命名空间(namespace)和控制组(cgroup)功能,实现了容器与宿主机之间的完全隔离,并提供了可靠的安全防护机制。 更高的容错性和可恢复性 由于容器化技术使用了分层文件系统和镜像层技术,所以即使发生容器故障也可以快速恢复,并支持容器的快速备份和还原。 综上所述,容器化比 Wasm 更适用于复杂的企业应用和场景,并且其具有更高的可移植性、高效性、资源利用率、灵活性和可靠性。对于需要在多个不同环境中运行的应用程序来说,容器化技术是一个非常实用和可靠的解决方案。 三、When?WebAssembly 发展历程 WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C/C++ 等语言提供一个编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。 简而言之[1],对于网络平台而言,WebAssembly 具有巨大的意义——它提供了一条途径,以使得以各种语言编写的代码都可以以接近原生的速度在 Web 中运行。在这种情况下,以前无法以此方式运行的客户端软件都将可以运行在 Web 中。 WebAssembly 被设计为可以和 JavaScript 一起协同工作——通过使用 WebAssembly 的 JavaScript API,你可以把 WebAssembly 模块加载到一个 JavaScript 应用中并且在两者之间共享功能。这允许你在同一个应用中利用 WebAssembly 的性能和威力以及 JavaScript 的表达力和灵活性,即使你可能并不知道如何编写 WebAssembly 代码。 而且,更棒的是,这是通过 W3C WebAssembly Community Group[2] 开发的一项网络标准,并得到了来自各大主要浏览器厂商的积极参与。 2015年:Mozilla、Google、Microsoft 和 Apple 等公司联合起来提出了 WASM 这个项目,并开始研究如何将其集成到现有 Web 技术栈中。 2016年:第一个 MVP 版本发布。这个版本实现了基本的指令集和内存管理功能,还支持 C/C++ 编译器生成的代码。 2017年:WASM 进入 2.0 版开发阶段,增加了更多的特性并扩展了语言支持范围。此外,社区也开始探索如何将它用于其他领域,例如机器学习和游戏引擎等。 2018年:WebAssembly 工作组成立,并推出 3.0草案。该草案对内存模型进行了改进,并引入了线程安全机制以及更好的调试工具链等新功能。 2020年:WASI[3](WebAssembly System Interface)正式发布。这是一种 标准化接口规范,旨在提供与操作系统无关的底层 API,使得WASM可以直接与底层硬件交互而不需要通过 JavaScript 或其他语言进行桥接。 四、Where?为什么 WebAssembly 不等同于计算机编译 尽管二者都涉及到了从高级语言到底层机器码的转换过程,并且都需要考虑性能和安全等问题但是二者存在一些根本的不同之处:虽然计算机编译与 WASM(WebAssembly) 之间有很多相似之处,但是它们也存在以下不同点: 应用场景:计算机编译器一般应用于本地环境下的程序开发,例如操作系统、桌面软件等。而 WASM 主要应用于浏览器中的 Web 应用程序和移动端应用开发。 二进制格式:在计算机编译过程中,生成的目标文件通常是特定平台上可执行代码的二进制格式。而 WASM 则是一种独立于底层硬件架构和操作系统的字节码格式。 运行时环境:在本地环境下运行时,由于操作系统提供了丰富的资源管理和服务支持,在程序设计方面具有更大自由度。而在 Web 浏览器或移动设备上运行时,则需要考虑到网络带宽、安全性、沙箱隔离等因素。 开发方式:通常情况下,计算机程序开发者可以直接使用高级语言进行代码编写,并通过 IDE 等工具来调试和测试程序。但是,在 WASM 技术栈中则需要掌握较为底层相关知识,并使用专门针对 WASM 开发环境的工具链进行编辑、构建和调试。 五、What?WAT (WebAssembly Text Format),Wasm 的文本表示形式 为了能够让人类阅读和编辑 WebAssembly,wasm 二进制格式[4]提供了相应的文本表示。WAT(WebAssembly Text Format) 是一种基于文本的 WebAssembly 二进制格式,它可以将 WASM 字节码转换为易读、可编辑的文本文件。WAT文件通常具有 .wat 或者 .wast 扩展名。 使用 WAT 格式进行开发,可以让开发人员更容易地阅读和修改 WASM 程序,并且便于调试和优化代码。同时,由于其与 JavaScript 等其他 Web 技术相似,因此在浏览器中也能够得到良好的兼容性支持。 下面是一个简单的例子: (module (func $addTwo (param $x i32) (result i32) get_local $x i32.const 2 i32.add)) 上述示例定义了一个模块(module),其中包含了一个函数(func)$addTwo`。该函数接受一个 `i32` 类型参数 `$x,并返回两个数值之和。 除了核心指令集外,在 WAT 中还提供了诸如导入、导出、内存管理、异常处理等高级特性。通过这些特性,可以实现更加复杂和完整功能的应用程序。 总之,尽管 WASM 已经成为一种越来越流行的跨平台低级别字节码格式,在某些情况下使用文本表示形式可能会更方便快捷。而 WAT 则作为一种便于阅读和修改 WASM 的文本格式而逐渐受到开发者们的关注。 六、How?WASM 如何取代容器化? Docker 的架构可以分为三个主要组件: Docker 客户端:Docker客户端是与用户交互的命令行工具或可视化界面。它允许用户在本地计算机上创建、管理和控制Docker镜像和容器。 Docker 引擎:Docker引擎是一个服务,负责管理所有正在运行的容器并提供对其进行操作的API。它还负责启动新容器,并监视已有容器以确保其正常运作。 Docker 注册表:Docker 注册表是存储 Docker 镜像的中央仓库。当用户从公共或私有源下载一个镜像时,该镜像将被保存到注册表中以供后续使用。 总之,Docker 架构由客户端、引擎和注册表三个主要组成部分组成,并且这些组件协同工作来实现快速高效地构建、部署和管理现代应用程序。 [图片来源: webassembly-is-the-new-docker[5]] 尽管容器化作为一种现代应用程序部署方式具有很多优点,但也存在着一些缺陷和不足: 1、存储冗余 由于容器镜像包含了整个应用程序以及其所需运行时环境,因此在大规模部署中会出现存储冗余的问题。这意味着需要更多的磁盘空间来存储相同数量的应用程序。 2、网络性能影响 当使用Docker等容器技术时,每个容器都需要一个IP地址,并且必须通过网络进行通信。这可能导致网络延迟、吞吐量降低或其他性能问题。 3、难以管理安全漏洞 由于Docker镜像可以从公共仓库下载,在没有恰当安全措施下就会成为攻击者利用系统漏洞入口 4、运维困难 当涉及到大规模集群部署时, 对资源消耗和工具选择(如日志、监视、调试)进行合理配置变得复杂 5、不够灵活/可定制 尽管 Docker 提供了许多选项来自定义镜像构建过程,但在某些情况下,可能需要进行更深层次的自定义才能满足特定需求。这对于不熟悉 Docker 技术栈的用户来说,在开发和维护中会带来额外的挑战。 [图片来源: stateful.com] 如果要说 WebAssembly(WASM) 可以完全取代容器化技术,这并不是一个准确的说法。因为两者有着各自独特的优势和适用场景。 [图片来源: stateful.com] 然而,WASM在某些方面可以作为一种替代性选择,并提供类似于容器化所带来的便利选项。以下是一些可能被认为支持WASM取代容器化理由: WASM 具有跨平台性:与 Docker 等其他容器相比,WASM 更加轻量级且易于部署运行。它能够跨越浏览器、桌面应用程序、移动应用程序以及云计算环境等多个领域使用。 更好地控制资源:通过将代码编译成高效可执行文件,在原生操作系统上运行时,WASM 能够有效地控制内存和 CPU 使用情况。同时,其安全模型也使得恶意代码无法访问底层系统资源或故障引起损坏。 Wasm 包含所有必要组件:对于某些类型的应用程序(例如单页应用程序),只需将 HTML、CSS 和 JavaScript 文件打包到单个 wasm 中即可实现目标功能 解决了平台差异问题: WASM 解决了不同操作系统和硬件平台之间的差异问题,使得开发人员可以更容易地编写可移植的代码,并在不同的环境中运行。 在 WebAssembly 技术的推动下,Web 前端开发正经历着一次重大转型。WASM 已经成为了一个备受关注的话题,并且随着它在浏览器中运行速度越来越快,它将继续发挥更加重要的作用: 应用领域拓展:除了原本主要应用于游戏、视频等高性能场景之外,WASM还可以被应用于诸如AI、机器学习、图像处理等其他领域。尤其随着近年来深度学习模型依赖GPU运算的需求增长,以及云计算资源逐渐廉价化和普及化,使用WASM进行客户端远程推理或边缘计算变得日益可行。 开源社区支持:由 Google, Mozilla, Microsoft 和 Apple 等公司共同支持并参与开源社区,在未来会有更多人投入到 WASM 的研究和实践当中。也会产生更多基于 WASM 的工具库和框架供开发者使用。 语言支持扩展:目前许多编程语言都已提供对 WebAssembly 编译输出文件格式的支持,如 Rust, Java, C++, Go 等。未来随着 WASM 的发展,更多的编程语言将会支持 WebAssembly。 集成应用:WASM 不仅可以运行于浏览器中,还可以作为在其他平台上执行的通用二进制格式。这意味着它可以与 Node.js、Electron 等应用程序框架集成,并提供类似于 Native App 一样的性能和用户体验。 所以,WebAssembly 是一个非常有前途的技术,在各个领域都有广泛应用和巨大潜力。 原文地址,https://mp.weixin.qq.com/s/Dy0Pn9QBRPsavrkiuxFeBg [1]MDN WebAssembly: https://developer.mozilla.org/zh-CN/docs/WebAssembly[2]W3C WebAssembly Community Group: https://www.w3.org/community/webassembly/[3]wasi.dev: https://wasi.dev[4]Understanding_the_text_format: https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format[5]webassembly-is-the-new-docker: https:///blog/webassembly-is-the-new-docker
一、Podman 是什么? Podman 是一个基于 libpod 库开发的容器运行时,用于在 Linux 操作系统上管理和运行容器。与传统的 Docker 容器运行时不同,Podman 无需依赖 Docker 守护进程,它可以在不同的 Linux 发行版中独立运行。 Podman 是最流行的容器运行时之一,在 Github 上拥有 17.1K Star,非常受欢迎。那么它有什么特别,又是为何能够受欢迎呢?一起来看看。 关于 podman 的资源: https://github.com/containers/podmanhttps://github.com/containers/podman-composehttps://github.com/containers/podman-desktop 二、Podman 的功能和特点 Podman 提供了与 Docker 类似的命令行接口,支持常见的容器管理功能,如启动、停止、重启和删除容器,以及构建、推送和拉取容器镜像等。Podman 还支持容器的网络和存储管理,可以使用 CNI 插件创建和管理容器的网络,支持使用多种存储驱动程序,如 overlayfs、btrfs 和 zfs 等。 Podman 的一个显著特点是它使用的是 rootless 模式,这意味着它可以在普通用户权限下运行,而不需要 root 权限。这有助于提高容器运行的安全性和可移植性。同时,Podman 支持通过 Pods 来管理一组相关的容器,这样可以方便地管理复杂的应用程序。 另外,Podman 还支持使用 systemd 来管理容器,这使得它可以更好地集成到 Linux 系统中,与其他系统服务一起运行和管理。 三、Podman 底层原理 作为一个容器运行时,Podman 的底层主要也是基于 Linux 的 Cgroup 和 Namespace 等技术,这里讲解一下涉及的主要技术点。 3.1、Namespace Podman 使用 Linux 命名空间实现容器之间的隔离,如 pid(进程ID)、网络、IPC(进程间通信)、UTS(主机名和域名)和挂载等等。这些命名空间可以隔离不同容器的进程、文件系统、网络和主机名等等,从而实现容器之间的隔离。 Linux Namespace 是 Linux 内核中的一个功能,用于将系统资源(如进程、网络、文件系统、IPC等)隔离在不同的命名空间中,从而使得相同的系统资源在不同的命名空间中具有不同的视图。这样可以让不同的进程看到不同的系统资源,从而实现更好的隔离和安全性。 Linux Namespace 提供了以下命名空间: PID 命名空间:使得每个进程只能看到自己及其子进程的进程树;Network 命名空间:使得每个进程只能看到自己的网络接口和路由表,从而实现网络隔离;IPC 命名空间:使得每个进程只能看到自己和同一命名空间中的进程的进程间通信(IPC)机制;UTS 命名空间:使得每个进程只能看到自己的主机名和域名;挂载命名空间:使得每个进程只能看到自己的文件系统挂载点和文件系统层次结构;用户命名空间:使得每个进程只能看到自己和同一命名空间中的进程的用户和用户组。 Linux Namespace可以用于以下场景: 容器:通过使用不同的命名空间隔离容器中的进程、文件系统、网络等资源,从而实现轻量级虚拟化;系统安全:通过使用不同的命名空间隔离系统资源,从而避免因为进程之间的相互影响而导致的安全问题;调试:通过使用不同的命名空间,使得调试工具只能看到特定的进程和资源,从而提高调试效率。 3.2、Cgroups Podman 使用 Linux cgroups 来限制和管理容器的资源使用情况,如 CPU、内存、I/O 等等。 Cgroups(control groups) 是 Linux 内核的一个功能,用于限制、隔离和控制系统资源的使用。Cgroups 允许将进程组织成一个或多个层次结构,每个层次结构都可以被分配一定数量的系统资源(如 CPU、内存、磁盘I/O 等),以便实现更好的系统资源管理和控制。 它可以限制 CPU 使用率、磁盘 I/O、网络资源、进程树等,是一个非常强大的工具,可以让系统管理员更好地管理和控制系统资源的使用。在大型系统中,使用 Cgroups 可以有效地避免资源耗尽和进程之间的干扰,从而提高系统的稳定性和可靠性。 3.3、SELinux Podman 使用 SELinux 来增强容器的安全性,它可以通过对容器进程的安全上下文进行限制,保证容器不会访问到宿主机上的敏感文件和资源。 SELinux 是一种强制访问控制(MAC)安全机制,用于 Linux 操作系统。它是由美国国家安全局(NSA)开发的,旨在增强操作系统的安全性和可靠性,提高系统的完整性和保密性。 SELinux 使用安全策略来管理访问控制规则,这些规则决定哪些进程可以访问哪些资源,以及如何访问这些资源。SELinux 安全策略基于标签(label)机制,它给操作系统中的每个对象(如文件、进程、套接字等)分配一个唯一的标签。这个标签用于识别该对象的所有权、访问控制以及安全上下文。 与传统的访问控制模型不同,SELinux 将访问控制放在了应用程序之外,从而增强了操作系统的安全性。SELinux 提供了一个强大的安全机制,可以限制应用程序的访问权限,并确保在恶意应用程序或攻击者的情况下保护系统资源。 SELinux 在 Linux 发行版中已经成为标准安全机制,并且在很多服务器和企业环境中得到广泛应用。虽然使用 SELinux 需要一定的学习和配置成本,但是它能够提供更高的安全保障和系统可靠性,从而值得使用和学习。 3.4、OCI 标准 Podman 遵循 OCI (Open Container Initiative) 标准来定义和管理容器和镜像。 OCI(Open Container Initiative)是由 Docker、CoreOS 和其他主要技术公司发起的一个开放标准组织,旨在为容器提供一个开放、统一和标准的格式和运行时环境。 OCI 标准定义了一个容器的标准格式和运行时环境,其中包括: 容器镜像格式:OCI 定义了一个通用的容器镜像格式,即 OCI 镜像格式。该镜像格式包含了应用程序和其所有依赖项,并将它们打包成一个容器镜像,以便于在不同的容器运行时环境中运行。容器运行时环境:OCI 定义了一个通用的容器运行时环境接口,即 OCI 运行时规范。这个规范定义了容器运行时环境所需的 API、CLI 命令、配置和网络等方面的标准接口,以确保容器可以在不同的运行时环境中运行,同时保证兼容性和互操作性。容器工具链:OCI 还定义了一个容器工具链规范,它定义了一组标准 CLI 命令,使得开发人员和系统管理员能够更方便地构建、管理和维护容器环境。 OCI 标准的出现是为了解决容器生态系统中的互操作性问题。在没有标准化之前,容器技术由不同的厂商和组织开发,容器镜像格式和运行时环境也各自不同,这给容器应用程序的部署和管理带来了很多挑战。通过制定标准,OCI 为容器技术的发展提供了一个更加开放、互操作和可持续的基础设施。 3.5、基于 Rootless Podman 支持在没有 root 权限的情况下运行容器,这可以增强容器的安全性和可移植性。 Podman 实现 rootless 的方法是使用 Linux 的 user namespaces 功能。用户命名空间(user namespaces)是 Linux 内核中的一种安全功能,它可以为每个用户提供一个独立的命名空间,使得用户在该命名空间中的操作不会影响到其他命名空间的用户。 Podman 在启动容器时,会创建一个新的用户命名空间,并在该命名空间中运行容器进程。这个命名空间中的用户可以使用它们自己的 UID 和 GID ,而不会影响到系统中的其他用户。同时,Podman 还使用了一些特殊的技术,如 userns-remap,来保证容器中的进程能够正确地映射到主机上的用户和组。 Podman 通过以上这些技术来隔离、管理和保护容器和宿主机之间的资源和数据,从而提供安全、高效、可移植的容器环境。 四、Podman 有哪些优势? 与 Docker 相比,Podman 的一个优势是它可以在 rootless 模式下运行,这提高了容器的安全性,并且使得在共享计算机上运行容器更为方便。此外,Podman 的命令行接口与 Docker 类似,因此,如果你熟悉 Docker,你会很快学会如何使用 Podman。 与其他容器运行时相比,Podman 的一个优势是它与 systemd 的集成,这使得它可以更好地与 Linux 系统集成。另外,Podman 支持使用 Pods 来管理一组相关的容器,这使得在部署复杂应用程序时更为方便。同时,Podman 的可移植性也很高,它可以在各种 Linux 发行版和云平台上运行,使得容器应用程序的部署更加灵活和简单。 五、Podman 的安装使用 Podman 可以在各种 Linux 发行版上安装和使用,下面是 Podman 的安装和使用步骤: 5.1、安装 Podman 在大多数 Linux 发行版上,你可以使用系统包管理器来安装 Podman。 例如,在 CentOS 和 Red Hat Enterprise Linux 上可以使用以下命令: sudo yum install podman 在 Debian 和 Ubuntu 上可以使用以下命令: sudo apt-get install podman 如果你使用的是其他 Linux 发行版,可以参考 Podman 官方文档中的安装指南。 Podman 官方文档安装说明,https://podman.io/docs/installationPodman 教程,https://www.osgeo.cn/podman/Tutorials.html 5.2、运行第一个容器 安装完成后,你可以使用 Podman 来启动第一个容器。以下命令可以启动一个基于 Alpine Linux 的容器,并在容器中运行一个简单的命令: podman run alpine echo "Hello, World!" 这将下载 Alpine Linux 镜像,并在容器中运行 echo 命令,输出 "Hello, World!"。 5.3、查看容器 你可以使用以下命令来查看正在运行的容器: podman ps 这将列出所有正在运行的容器,并显示容器的ID、状态、端口映射等信息。 5.4、停止和删除容器 你可以使用以下命令来停止和删除容器: podman stop <container-id> podman rm <container-id> 其中,是容器的 ID,可以通过运行 podman ps 命令来获取。 5.5、构建和推送镜像 你可以使用以下命令来构建和推送容器镜像: podman build -t <image-name> . podman push <image-name> 其中,是镜像的名称和标签,例如 my-image:latest。这将在当前目录中构建一个新的镜像,并将其推送到Docker Hub或其他容器镜像仓库中。 原文地址:https://mp.weixin.qq.com/s/tpAtdL9CId4bT4nNFyNUYA
一、Docker Registry 是什么? Docker Registry 是一个无状态、高度可扩展的服务器端应用程序,用于存储和分发 Docker 镜像。Docker Registry 是基于 Apache 许可证开源的,它是目前应用最广泛的镜像仓库管理程序,所有的源码在 github 上开源,如果感兴趣的话可以 clone 相关的代码进行深层次的学习。 https://docs.docker.com/registry/ 为什么需要使用 Docker Registry? 需要对镜像进行严格统一管理;需要拥有镜像的分发渠道;并且将镜像管理和分发集成到内部统一开发流程中。 例如搭建内部 CI 平台,自动构建镜像、存储镜像和分发镜像,实现一键构建,打通从开发、测试环境到生产环境。 二、Docker Registry 部署私有仓库 部署 Docker Registry 之前先安装 Docker,我的环境是 CentOS,直接使用以下命令: [root@node1 docker]# yum update [root@node1 docker]# yum install docker [root@node1 docker]# docker version Client: Version: 1.13.1 API version: 1.26 Package version: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? 你可以创建专门的 docker 用户进行操作,更加安全,刚创建的用户不在 sudo user 里面,可以通过以下方式设置,这里为了方便,我使用了 root 用户。 chmod u+w /etc/sudoers vim /etc/sudoers //填入以下内容 docker ALL=(ALL) ALL 安装完毕以后使用以下命令查看 docker 运行状态: [root@node1 docker]# sudo systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled) Active: active (running) since Fri 2023-03-10 18:03:18 CST; 11s ago Docs: http://docs.docker.com Main PID: 47284 (dockerd-current) CGroup: /system.slice/docker.service ├─47284 /usr/bin/dockerd-current --add-runtime docker-runc=/usr/li... └─47290 /usr/bin/docker-containerd-current -l unix:///var/run/dock... Mar 10 18:03:17 node1 dockerd-current[47284]: time="2023-03-10T18:03:17.8383..." Mar 10 18:03:17 node1 dockerd-current[47284]: time="2023-03-10T18:03:17.8422..." Mar 10 18:03:17 node1 dockerd-current[47284]: time="2023-03-10T18:03:17.8484..." Mar 10 18:03:17 node1 dockerd-current[47284]: time="2023-03-10T18:03:17.8776..." Mar 10 18:03:18 node1 dockerd-current[47284]: time="2023-03-10T18:03:18.0211..." Mar 10 18:03:18 node1 dockerd-current[47284]: time="2023-03-10T18:03:18.0656..." Mar 10 18:03:18 node1 dockerd-current[47284]: time="2023-03-10T18:03:18.0906..." Mar 10 18:03:18 node1 dockerd-current[47284]: time="2023-03-10T18:03:18.0917...1 Mar 10 18:03:18 node1 dockerd-current[47284]: time="2023-03-10T18:03:18.1144..." Mar 10 18:03:18 node1 systemd[1]: Started Docker Application Container Engine. Hint: Some lines were ellipsized, use -l to show in full. 以上可以看到状态是 active。 要部署一个 Docker Registry,如果使用容器部署,且使用默认配置,则最简单的使用以下命令就可以将 Docker Registry 运行起来: docker run -d -p 5000:5000 --name registry registry:2 要使用 Docker Registry 部署一个镜像私有仓库,需要遵循以下步骤: 2.1、Docker Registry 安装 可以使用以下命令从 Docker Hub 上下载 Docker Registry 的官方镜像: docker pull registry:2 2.2、Docker Registry 配置 Docker Registry 的配置文件使用 YAML 格式编写,可以通过修改配置文件来启用鉴权和认证机制,以及配置存储方式等。 以下是一个示例配置文件: version: 0.1 accesslog: /var/log/registry/access.log errorlog: /var/log/registry/error.log storage: filesystem: rootdirectory: /var/lib/registry http: addr: 5000 headers: X-Content-Type-Options: [nosniff] auth: htpasswd: realm: registry path: /auth/htpasswd 在上面的配置文件中,storage.filesystem.rootdirectory 参数指定了存储镜像的目录,http.addr 参数指定了 Docker Registry 监听的端口号,auth.htpasswd.realm 参数指定了认证域的名称,auth.htpasswd.path 参数指定了存储用户名和密码的文件路径。 2.3、启动 Docker Registry 可以使用以下命令启动 Docker Registry: docker run -d \ -p 5000:5000 \ --restart=always \ --name registry \ -v /path/to/registry:/var/lib/registry \ -v /path/to/config.yml:/etc/docker/registry/config.yml \ registry:2 其中,-p 参数指定了 Docker Registry 监听的端口号,-v 参数指定了存储镜像和配置文件的目录,registry:2 参数指定了使用的 Docker Registry 镜像版本。 2.4、Docker 客户端配置 为了使用刚刚部署的镜像私有仓库,需要在 Docker 客户端中配置 Docker Registry 的地址和认证信息。可以使用以下命令配置 Docker 客户端: docker login registry.example.com:5000 其中,registry.example.com:5000 参数指定了 Docker Registry 的地址和端口号。 2.5、向 Docker Registry 上传和下载镜像 可以使用以下命令上传和下载镜像: docker tag image-name registry.example.com:5000/image-name docker push registry.example.com:5000/image-name docker pull registry.example.com:5000/image-name 其中,image-name 参数指定了要上传和下载的镜像名称。注意,在上传和下载镜像时,需要使用 Docker Registry 的完整地址和端口号。 以上就是使用 Docker Registry 部署镜像私有仓库的步骤,可以根据实际需求进行配置和使用。 三、Docker Registry 鉴权和认证 Docker Registry 是一个中央存储和分发 Docker 镜像的服务器,其支持多种鉴权和认证机制,包括基本认证、Bearer Token 认证、AWS 认证和 LDAP 认证等。下面我们详细介绍其中的几种常用认证和鉴权机制,并给出相应的代码配置示例。 3.1、基本认证 基本认证是一种简单的 HTTP 认证机制,它通过在 HTTP 头中发送 Base64 编码的用户名和密码来验证用户的身份。Docker Registry 支持基本认证,可以通过配置文件来启用。以下是一个示例配置文件: version: 0.1 accesslog: /var/log/registry/access.log errorlog: /var/log/registry/error.log storage: filesystem: rootdirectory: /var/lib/registry http: addr: 5000 headers: X-Content-Type-Options: [nosniff] auth: htpasswd: realm: registry path: /auth/htpasswd 在上面的配置文件中,auth.htpasswd.realm 参数表示基本认证领域的名称,auth.htpasswd.path 参数表示包含用户名和密码的文件路径。我们可以使用 htpasswd 命令来创建用户名和密码文件,例如: $ htpasswd -Bbn user1 password1 > /path/to/htpasswd 3.2、Bearer Token 认证 Bearer Token 认证是一种基于 OAuth2 协议的认证机制,它使用访问令牌来验证用户的身份。Docker Registry 支持 Bearer Token 认证,可以通过配置文件来启用。以下是一个示例配置文件: version: 0.1 accesslog: /var/log/registry/access.log errorlog: /var/log/registry/error.log storage: filesystem: rootdirectory: /var/lib/registry http: addr: 5000 headers: X-Content-Type-Options: [nosniff] auth: token: realm: registry service: registry issuer: registry rootcertbundle: /path/to/root.crt 在上面的配置文件中,auth.token.realm 参数表示 Bearer Token 认证领域的名称,auth.token.service 和 auth.token.issuer 参数表示服务名称和颁发者名称。auth.token.rootcertbundle 参数表示根证书的路径,这里可以使用自签名的证书或 CA 签名的证书。 3.3、AWS 认证 AWS 认证是一种基于 AWS Identity and Access Management(IAM) 的认证机制,它使用AWS凭证来验证用户的身份。Docker Registry 支持 AWS 认证,可以通过配置文件来启用。以下是一个示例配置文件: version: 0.1 accesslog: /var/log/registry/access.log errorlog: /var/log/registry/error.log storage: filesystem: rootdirectory: /var/lib/registry http: addr: 5000 headers: X-Content-Type-Options: [nosniff] auth: access 在上面的配置文件中,auth.s3.accesskey 和 auth.s3.secretkey 参数表示 AWS 凭证的访问密钥和私有密钥,auth.s3.region 参数表示 AWS S3 存储桶的区域,auth.s3.bucket 参数表示存储镜像的 S3 存储桶的名称。 3.4、LDAP 认证 LDAP 认证是一种基于 Lightweight Directory Access Protocol(LDAP) 的认证机制,它使用 LDAP 服务器中的用户信息来验证用户的身份。Docker Registry 支持 LDAP 认证,可以通过配置文件来启用。以下是一个示例配置文件: version: 0.1 accesslog: /var/log/registry/access.log errorlog: /var/log/registry/error.log storage: filesystem: rootdirectory: /var/lib/registry http: addr: 5000 headers: X-Content-Type-Options: [nosniff] auth: ldap: endpoint: ldap://ldap.example.com:389 binddn: cn=admin,dc=example,dc=com bindpassword: password searchbase: ou=people,dc=example,dc=com searchfilter: (uid=%s) 在上面的配置文件中,auth.ldap.endpoint 参数表示 LDAP 服务器的地址和端口号,auth.ldap.binddn 和 auth.ldap.bindpassword 参数表示 LDAP 管理员的身份信息,auth.ldap.searchbase 参数表示用户信息存储在 LDAP 服务器上的基础目录,auth.ldap.searchfilter 参数表示查询用户信息的过滤器。 以上是常用的几种 Docker Registry 的鉴权和认证机制,不同的认证机制在配置文件中的参数有所不同。可以根据实际需求选择相应的认证机制并进行配置。 原文地址:https://mp.weixin.qq.com/s/nlZl-bvnX3LXwTS3fN0fpg
自从 2021 年 2 月第 20 轮公布的测试以后,一年半后的 2022 年 7 月 19 日 发布了 TechEmpower 21 轮测试报告:Round 21 results - TechEmpower Framework Benchmarks。Techempower benchmark 是包含范围最广泛的 web 框架性能测试,覆盖了比较典型的使用场景,其可参考性极强。另外,所有测试源代码和软硬件配置都开放,基本得到大家的认可。 基准测试代码地址:https://github.com/TechEmpower/FrameworkBenchmarks TechEmpower 基准测试 TechEmpower 基准测试有许多场景(也称为测试类型),此次评测多了一个综合评分选项,把拥有完整测试覆盖的框架现在将具有综合分数,这反映了测试项目类型的总体性能得分:JSON serialization, Single-query, Multi-query, Updates, Fortunes 和 Plaintext。对于每一轮,我们使每个测试类型的结果规范化,然后为每个测试类型应用主观权重(例如,Fortunes 的权重比 Plaintext 高,因为 Fortunes 是一种更现实的测试类型)。 =》asp.net core 排第 7 名,主流排名第 3 名,asp.net 已经在排行榜里面找不到了。 TechEmpower 21 轮测试报告 2022 年 7 月 19 日发布了 TechEmpower 21 轮测试报告:https://www.techempower.com/benchmarks/#section=data-r21&test=composite asp.net core 从去年的综合得分第 8 名上升 1 位到了第 7 名,排名虽然只提升 1 位,但是相对第 1 名的性能,从去年的 68% 提升到了 83.7%,这个提升不可谓不巨大,要知道排前面都是 rust, c++ 选手,已经比较接近了,都在 7000+ 级别,另外,论使用量,asp.net core 在前 10 名的框架中毫无疑问是最多的。 性能已经是 asp.net core 的优势了,相对 java 主流框架,go 全部框架来看,都是如此,更别说世界上最好的语言 PHP 了。 主流编程语言框架性能对比 表上前缀 T 标签表示精选的主流编程语言:TechEmpower Framework Benchmarks 第1名 C++ 的 drogon 7801分第2名 Rust 的 actix 7667分第6名 C# 的 ASP.NET Core 7077分第63名 Go 的 gin 1943分第67名 Java 的 Spring 1846分第113名 Elixir 的 phoenix 687 分第116名 Node.js 的 Express 615分第130名 PHP 的 laravel 371分第131名 Ruby 的 rails 366分第137名 Python 的 django 274分 性能 & 托管服务成本衡量 应用性能直接影响到托管服务的成本,因此公司在开发应用时需要格外注意应用所使用的 web 框架,初创公司尤其如此。 此外,糟糕的应用性能也会影响到用户体验,甚至会因此受到相关搜索引擎的降级处罚。在选择框架时,又有许多因素需要考量,但原始性能无疑是其中最容易测评的。不同的框架性能差异极大,即使你充分利用了硬件的性能,错误的框架依然可能带来十倍的性能损耗,虽然不是每个人都会遇到如此极端的情况,但在某些情况下确实如此,因此你有必要了解各框架之间的性能差异。 在当今无服务器和容器的时代,很高兴看到行业竞争并在冷启动和内存消耗方面进行艰难的测试,PlaintText 单项排名很好的体现了这一项: Fortunes 测试类型是最有趣的,因为它包括使用对象关系映射器(ORM)和数据库。这是 Web应用程序/服务 中的常见用例。以前版本的 ASP.NET Core 在这种情况下表现不佳。由于堆栈和 PostgreSQL 驱动程序中的优化,ASP.NET Core 2.1 得到了显著改进, ASP.NET Core 3.1 版本又提升到了 27 万,5.0 版本提升到了 40 万,6.0 版本提升到了 45 万。 其他方案不太代表典型的应用程序。他们强调堆栈的特定方面。如果它们与您的用例紧密匹配,它们可能会很有趣。对于框架开发人员,他们帮助识别进一步优化堆栈的机会。 例如,考虑 Plaintext 方案。此方案涉及客户端发送 16 个请求背靠背(流水线),服务器知道响应,而无需执行 I/O 操作或计算。这不代表典型的请求,但它是解析 HTTP 请求的良好压力测试。 每个实现都有一个类。 例如,ASP.NET Core Plaintext 具有 platform, micro 和 full 实现。 full 的实现是使用 MVC 中间件。Micro 实现在管道级实现,platform 实现直接建立在Kestrel之上。 虽然 Platform 类提供了引擎功能强大的概念,但它不是用于应用程序开发人员编程的 API。 基准测试结果包括 Latency 选项卡。一些实现每秒实现非常多的请求,但是以相当大的延迟成本。 相关文章: TechEmpower 13 轮测试中的 ASP.NET Core 性能测试 =》https://www.cnblogs.com/shanyou/p/6082499.htmlTechEmpower Web 框架性能第 19 轮测试结果正式发布,ASP.NET Core 在主流框架中拔得头筹 =》https://www.cnblogs.com/shanyou/p/12995227.html
1 | 组合模式的概述树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单结构、办公系统中的公司组织结构等。对于所有与目录结构相类似的树形结构,当容器对象(例如文件夹)的某一个方法被调用时,将遍历整个树形结构。寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,例如子文件夹和文件)并调用执行,牵一而动百,其中使用了递归调用的机制来对整个结构进行处理。由于容器对象和叶子对象在功能上的区别,在使用这些对象的代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们,因为对于这些对象的区别对待将会使程序非常复杂。1.1 组合模式产生的动机组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,它描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象。1.2 组合模式的定义组合模式:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。Composite Pattern: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objectsuniformly.组合模式又称为“部分-整体”(Part-Whole)模式,属于对象结构型模式,它将对象组织到树形结构中,可以用来描述整体与部分的关系。2 | 组合模式的结构与实现组合模式的关键在于定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其讲行统一处理。同时容器对象与抽象构件类之间还建立了一种聚合关联关系,在容哭对象中既可以包含叶子,又可以包含容器,以此实现递归组合,形成一个树形结构。如果不使用组合模式,客户端代码将过多地依赖于容器对象复杂的内部实现结构,容器对象内部实现结构的变化将引起客户端代码的频繁变化,造成代码维护困难,可扩民性差等问题,组合模式的使用将在一定程度上解决这些问题。2.1 组合模式的结构(1) Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。(2) Leaf(叶子构件):它在组合结构中表示叶子结点对象,叶子结点没有子结点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过抛出异常,提示错误等方式进行处理。(3) Composite(容器构件):它在组合结构中表示容器结点对象,容器结点包含子结点,其子结点可以是叶子结点,也可以是容器结点,它提供一个集合用于存储子结点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子结点的业务方法。2.2 组合模式的实现我们通过代码示例来分析组合模式的各个角色的用途和实现。using System; using System.Collections.Generic; namespace CompositeSample abstract class Component public abstract void Add(Component c); //增加成员 public abstract void Remove(Component c); //删除成员 public abstract Component GetChild(int i); //获取成员 public abstract void Operation(); //业务方法 class Leaf : Component public override void Add(Component c) //异常处理或错误提示 public override void Remove(Component c) //异常处理或错误提示 public override Component GetChild(int i) //异常处理或错误提示 return null; public override void Operation() //叶子构件具体业务方法的实现 class Composite : Component private readonly List<Component> list = new List<Component>(); public override void Add(Component c) list.Add(c); public override void Remove(Component c) list.Remove(c); public override Component GetChild(int i) return (Component)list[i]; public override void Operation() //容器构件具体业务方法的实现,将递归调用成员构件的业务方法 foreach (Object obj in list) ((Component)obj).Operation(); }抽象构建无需关心其具体子类是容器构件还是叶子构件。叶子构件中声明抽象构件中的所有方法,但是叶子构件不能再包含子构件,所以再叶子构件中实现的构件管理和访问方法时需要提供异常处理和错误提示。同时这也会给叶子构建的实现带来麻烦。容器构件中实现了在抽象构件中的所有方法。需要注意的是在实现具体业务方法时,由于容器构件充当的是容器角色,包含成员构件,因此在对容器构件进行处理时需要使用递归算法,即在容器构件的 Operation() 方法中递归调用其成员构件的 Operation() 方法。3 | 组合模式的应用实例3.1 实例说明某软件公司要开发一个杀毒(Antivirus) 软件,该软件既可以对其个文件来(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。该杀毒软件还可以根据各教文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。现使用组合模式来设计该杀毒软件的整体框架。3.2 实例代码设计AbstractFile 充当抽象构件类。Falder 充当容器构件类。ImageFile、TextFile 和 VideoFile 充当叶子构件类。客户端调用,如下所示:完整代码示例请查看 =》DesignPattern: DesignPattern GoF 23+1 | 设计模式 23+1 - Gitee.com4 | 透明组合模式 & 安全组合模式区分依据:根据抽象构件类的定义形式不同,可分为透明组合模式和安全组合模式。4.1 透明组合模式【优点】在透明组合模式中,如上应用实例,抽象构件 Component 中声明了所有用于管理成员对象的方法,这样做的好处是确保了所有构件类都有相同的接口。此时对客户端来说,叶子构件和容器构件提供的方法是一致的,客户端可以一致的对待所有对象,没有差别。【缺点】透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是具有区别的。叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供Add() 、Remove() 及 GetChild() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)。4.2 安全组合模式【优点】在安全组合模式中,抽象构件 Component 中没有声明任何用于管理成员对象的方法【Add() 、Remove() 及 GetChild() 】,而是在 Composite 类中声明并实现这些方法。这样做是安全的,因为根本不向叶子对象提供这些管理成员对象的方法,对于叶子对象,客户端不可能调用到这些方法。【缺点】安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,容器构件中用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。在实际应用中,安全组合模式的使用频率也非常高。5 | 组合模式的优缺点与适用环境组合模式使用面向对象的思想来实现树形结构的构建与处理,描述了如何将容器对象和叶子对象进行递归组合,实现简单,灵活性好。由于在软件开发中存在大量的树形结构,因此组合模式是一种使用频率较高的结构型设计模式。5.1 组合模式的优点(1)组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制。(2)客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。(3)在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合开团原则。(4)为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合可以形成复杂的树形结构,但对树形结构的控制却非常简单。5.2 组合模式的缺点在增加新构件时很难对容器中的构件类型进行限制。有时候希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,便用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。5.3 组合模式的适用环境(1)在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。(2)在一个使用面向对象语言开发的系统中需要处理一个树形结构。(3)在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加—些新的类型。
1 | 桥接模式的概述桥接模式是一种很实用的对象结构型设计模式,它又被称为柄体模式(Handle and Body)或接口(Interface)模式。如果系统种某个类存在两个独立变化的维度。通过桥接模式可以将这两个维度分离出来,使两者可以独立扩展。桥接模式使用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并且易于扩展,同时有效控制了系统中类的个数。1.1 桥接模式的定义桥接模式:将抽象部分与它的实现部分解耦,使得两者都能够独立变化。Bridge Pattern:Decouple an abstraction from its implementation so that the two can vary independently.2 | 桥接模式的结构与实现2.1 桥接模式的结构桥接模式包含以下 4 个角色:(1) Abstraction(抽象类):它是用于定义抽象类的接口,通常是抽象类而不是接口,其中定义了一个 Implementor(实现类接口)类型的对象并可以维护该对象,它与 lmplementor 之间具有关联关系,既可以包含抽象业务方法,也可以包含具体业务方法。(2) RefinedAbstraction(扩充抽象类):它扩充由 Abstraction 定义的接口,通常情况下不再是抽象类而是具体类,实现了在 Abstraction 中声明的抽象业务方法,在 RefinedAbstractior 中可以调用在 lmplementor 中定义的业务方法。(3) IImplementor(实现类接口):它是定义实现类的接口,这个接口不一定要 Abstraction 的接口完全一致,事实上这两个接口可以完全不同。一般而言,Implementor 口仅提供基本操作,面 Abstraction 定义的接口可能会做更多更复杂的操作。Implemen接口对这些基本操作进行了声明,而将具体实现交给其子类。通过关联关系,在 Abstraction 中不仅可以拥有自已的方法,还可以训用 Implementor 中定义的方法,使用关联关系来替代继承关系。(4) Concretclmplementor(具体实现类):它具体实现了 Implementor 接口,在不同 Coneretelmplementor中提供基本操作的不同实现,在程序运行时,Concretelmplementor 象将精换其父类对象,提供给抽象类具体的业务操作方法。2.2 桥接模式的实现桥接模式是一个非常实用的设计模式,在桥接模式中体现了很多面向对象设计原则思想,包括单一职责原则、开闭原则,合成复用原则、里氏代换原则、依赖倒转原则等。熟悉桥接模式将有助于用户深人理解这些设计原则,也有助于形成正确的设计思想和培养良好的设计风格。在使用桥接模式时,用户首先应该识别出一个类所具有的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。通常情况下,将具有两个独立变化维度的类的一些普通业务方法和与之关系最密切的维度设计为“抽象类”层次结构(抽象部分),而将另一个维度设计为“实现类”层次结构(实现部分)。代码设计using System; namespace BridgePattern.Sample // 接口定义 interface IImplementor void OperationImpl(); // 继承关系,实现接口方法 class ConcreteImplementor : IImplementor public void OperationImpl() Console.WriteLine("ConcreteImplementor.OperationImpl => 具体业务方法实现"); // 建立抽象耦合 abstract class Abstraction protected IImplementor _Impl; //定义实现类接口对象 public void SetImpl(IImplementor impl) _Impl = impl; public abstract void Operation(); //声明抽象业务方法 // 抽象具体实现 class RefinedAbstraction : Abstraction public override void Operation() Console.WriteLine("===业务代码(调用方法前)==="); IImplementor implementor = new ConcreteImplementor(); SetImpl(implementor); _Impl.OperationImpl(); //调用实现类的方法 Console.WriteLine("===业务代码(调用方法后)==="); }客户端调用,如下所示:3 | 桥接模式应用实例3.1 实例说明3.2 类图结构3.3 实例代码(1) Matrix:像素矩阵类,辅助类(2) IImageImp:抽象操作系统实现类,充当实现类接口(3) WindowsImp:Windows 操作系统实现类,充当具体实现类(4) LinuxImp:Linux 操作系统实现类,充当具体实现类(5) UnixImp:UNIX 操作系统实现类,充当具体实现类(6) Image:抽象图像类,充当抽象类(7) JPGImage:JPG 格式图像类,充当扩充抽象类(8) PNGImage:PNG 格式图像类,充当扩充抽象类(9) BMPImage:BMP 格式图像类,充当扩充抽象类(10) GIFImage:GIF 格式图像类,充当扩充抽象类(11) App.config 配置文件 (12) Program:客户端测试类<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <!--RefinedAbstraction--> <add key="image" value="BridgePattern.Sample.BMPImage"/> <!--ConcreteImplementor--> <add key="os" value="BridgePattern.Sample.LinuxImp"/> </appSettings> </configuration>客户端调用,如下所示:完整代码示例请查看 =》DesignPattern: DesignPattern GoF 23+1 | 设计模式 23+1 - Gitee.com4 | 桥接模式与适配器模式的联合使用桥接模式:用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行扩展变化。适配器模式:当发现系统与已有类无法协同工作时。5 | 桥接模式的优缺点与适用环境5.1 桥接模式的优点 √分离抽象接口及其实现部分。√可以取代多层继承方案,极大地减少了子类的个数。√提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,不需要修改原有系统,符合开闭原则。5.2 桥接模式的缺点 √会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。√正确识别出系统中两个独立变化的维度并不是一件容易的事情。5.3 桥接模式的适用环境√需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系。√抽象部分和实现部分可以以继承的方式独立扩展而互不影响。√一个类存在两个(或多个) 独立变化的维度,且这两个(或多个)维度都需要独立地进行扩展。√不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统。
1 | 原型模式概述原型模式是一种特殊的 创建型模式,它通过复制一个已有对象来获取更多相同或相似的对象。原型模式可以提高系统同类型对象的创建效率,简化创建过程。 《西游记》中”孙悟空拔毛变猴“的故事几乎人人皆知,孙悟空用猴毛根据自己的形象,复制出很多和自己长的一摸一样的”分身“。类似这种场景在面向对象的软件设计领域被称为原型模式,孙悟空则被成为原型对象。1.1 原型模式的定义原型模式:使用原型实例指定待创建对象的类型,并通过复制这个原型来创建新的对象。Prototype Pattern:Specify the kinds of objects to create using a protptypical instance, and create new objects by copying this protptype.1.2 原型模式的工作原理将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现新对象的创建过程。这种创建新对象的过程也称为”克隆对象“,创建新对象的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现。注意:通过克隆对象创建的对象是全新的对象,他们在内存中拥有新的地址。通常对克隆所产生的新对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。通过对克隆对象进行修改后,可以得到一系列相似但不完全的对象。2 | 原型模式的结构与实现2.1 原型模式的结构原型模式包含以下 3 个角色:(1) Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口,甚至可以是具体实现类。(2) ConcretePrototvpe(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。(3) Client(客户类):在客户类中,让一个原型对象克隆自身从而创建一个新的对象,只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对负由干家白米社对抽鱼百用版 Prntotvne 编程,因此,用户可以根据需要选择具体原米,系经且有校缸的可扩民抖画加成百推几休原型类都很方便。2.2 原型模式的浅克隆与深克隆根据在复制原型对象的同时是否复制包含在原型对象中引用类型的成员变量,原型模式的克隆机制分为两种:浅克隆(Shallow Clone) 和深克隆(Deep Clone),有时也称作 浅拷贝 和 深拷贝。(1)浅克隆(Shallow Clone)在浅克隆中,如果原型对象的成员亦量是值类刑(如 int、double、byte、bool 、char 等基本数据类型)将复制一份给克降对象,如果原型对象的成员变量是引用类型(如类、接口、数组等复杂数据类型),则将引用对象的地址复制一份给克降对象,也就是说,原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制。(2)深克隆(Deep Clone)在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将被复制。2.2 原型模式的实现实现原型模式的关键在于如何实现克隆方法,在 C# 中有两种常用的克隆实现方法:2.2.1 通用克隆实现方法典型示例代码如下:abstract class Prototype public abstract Prototype Clone(); class ConcretePrototype : Prototype /// <summary> /// 成员变量 /// </summary> public string Attr { get; set; } /// <summary> /// 克隆方法,通过赋值的方式来实现对象的复制。 /// </summary> /// <returns></returns> public override Prototype Clone() => new ConcretePrototype Attr = Attr //成员变量赋值 }客户端调用示例代码:// 1.创建 ConcretePrototype 对象作为原型。 ConcretePrototype prototype = new ConcretePrototype(); // 2. 原型实例对象 prototype 调用克隆方法 Clone() 创建克隆对象。 ConcretePrototype copy = (ConcretePrototype)prototype.Clone();注意:此方法是原型模式的通用方法,与编程语言本身的特性无关,除 C# 外,其他面向对象编程语言也可以使用这种形式来实习原型对象的克隆。上面的克隆方法 Clone() 中,如果通过创建一个全新的成员对象来实现复制,则是一种深克隆实现方案。C# 语言中的字符串 (string/String)对象存在特殊性,只要两个字符串的内同相同,无论是值赋值还是创建新对象,它们在内存中始终只有一份。了解更多可查看 C# 字符串驻留机制。参考:【字符串的不可变性和驻留机制】https://www.cnblogs.com/SignX/p/10933482.html2.2.2 C# 中的 MemberwiseClone() 方法和 ICloneable 接口在 C# 语言中,提供了一个 MemberwiseClone() 方法用于实现浅克隆,该方法使用很方便,之间调用一个已有对象的 MemberwiseClone() 方法即可实现对象克隆。示例代码如下:class Member { } class ConcretePrototypeA /// <summary> /// 成员变量 /// </summary> public Member MyMember { get; set; } /// <summary> /// 克隆方法,通过赋值的方式来实现对象的复制。 /// </summary> /// <returns></returns> public ConcretePrototypeA Clone() => (ConcretePrototypeA)this.MemberwiseClone(); //浅克隆 }客户端调用,测试输出结果证实该克隆方法是浅克隆。ICloneable 接口充当了抽象原型类的角色,具体原型类通常作为实现该接口的子类实现深克隆。示例代码如下:class ConcretePrototypeB: System.ICloneable /// <summary> /// 成员变量 /// </summary> public Member MyMember { get; set; } /// <summary> /// 实现深克隆 /// </summary> /// <returns></returns> public object Clone() ConcretePrototypeB copy = this.MemberwiseClone() as ConcretePrototypeB; //对象转换 Member newMember = new Member(); copy.MyMember = newMember; return copy; }客户端调用,测试输出结果证实该克隆方法是深克隆。3 | 原型管理器(Prototype Manager)原型管理器(Prototype Manager)将多个原型对象存储在一个集合中供客户端使用,他是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某一个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象类进行编程,方便扩展,该结构如下:原型管理器 PrototypeManager 代码实现:using System.Collections; namespace PrototypePattern /// <summary> /// 原型管理器-PrototypeManager /// </summary> class PrototypeManager #region SingleProfit 单例模式 //创建私有化静态obj锁 private static readonly object _ObjLock = new object(); //创建私有静态字段,接收类的实例化对象 private static volatile PrototypeManager _SingleProfit = null; //volatile 促进线程安全,保证线程有序执行 //构造函数私有化 private PrototypeManager() { } //创建单利对象资源并返回 public static PrototypeManager CreateSingleProfitObj() if (_SingleProfit == null) lock (_ObjLock) if (_SingleProfit == null) _SingleProfit = new PrototypeManager(); return _SingleProfit; #endregion /// <summary> /// Hashtable 存储原型对象 /// </summary> private readonly static Hashtable hashTable = new Hashtable(); /// <summary> /// Hashtable 新增原型对象 /// </summary> /// <param name="key"></param> /// <param name="prototype"></param> public void Add(string key, Prototype prototype) hashTable.Add(key,prototype); /// <summary> /// 获取克隆对象 /// </summary> /// <param name="key"></param> /// <returns></returns> public Prototype Get(string key) Prototype copy = ((Prototype)hashTable[key]).Clone(); //通过(内置)克隆方法创建新对象 return copy; }在实际的开发中,PrototypeManager 类通常设计为单例类(单例模式),确保系统中有且仅有一个 PrototypeManager 对象,既有利于节省系统资源,还可以更好地对原型管理器对象进行控制。添加两个类分别是 ConcretePrototypeC 和 ConcretePrototypeD:#region 配合 PrototypeManager 使用 class ConcretePrototypeC : Prototype public override Prototype Clone() return (ConcretePrototypeC) this.MemberwiseClone(); class ConcretePrototypeD : Prototype public override Prototype Clone() return (ConcretePrototypeD) this.MemberwiseClone(); #endregion客户端调用方法:完整代码示例请查看=》 https://gitee.com/dolayout/DesignPatternOfCSharp/tree/master/DesignPatternOfCSharp/PrototypePattern4 | 原型模式的优缺点与适用环境原型模式作为一种快速创建大量相同或相似对象的方式,在软件开发中的应用较为泛,很多软件提供的复制(Ctrl+C)和粘贴(Ctrl+V)操作就是原型模式的典型应用。4.1 原型模式的主要优点(1)当要创建的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过制一个已有实例可以提高新实例的创建效率。(2)扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统没有任何影响。(3)原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在型类中的克隆方法实现的,无须专门的工厂类来创建产品。(4)可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其态保存起来,以便在需要的时候使用(例如恢复到某一历史状态),可辅助实现撤销操作。4.2 原型模式的主要缺点(1)需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已的类进行改造时,需要修改源代码,违背了开闭原则。(2)在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。4.3 原型模式的适用环境(1)创建新对象成本较大(例如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改。(2)系统要保存对象的状态,而对象的状态变化很小。(3)需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
1 | 建造者模式概述无论是在现实世界中还是软件系统中,都存在一些复杂的对象,他们拥有多个组成部分(部件),例如汽车,它包括车轮、方向盘、发动机等多种部件。对于大多数用户而言,并不知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车。思考:面对上面这种场景,如何将这些部件组装成一辆完整的汽车并返回给用户,而这种场景恰好就是建造者模式需要解决的问题。建造者模式可以将部件本身和它们的组装过程分开,关注如何一步步创建一个包含多个组成部分的复杂对象,用户只需要指定复杂对象的类型即可得到该对象,而无需知道其内部的具体构建细节。1.1 建造者模式的定义建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。Builder Pattern: Separate the construction of a complex object from its representation so that the same construction process can create different representations.建造者模式是一种对象创建型模式,它将客户端与包含多个部件的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可。建造者模式关注如何逐步创建一个复杂的对象,不同的建造者定义了不同的创建过程,且具体建造者相互独立,且更换建造者和增加建造者非常的方便,系统具有较好的扩展性。2 | 建造者模式的结构与实现2.1 建造者模式的结构(1) Builder(抽象建造者):它为创建一个产品 Product 对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是 BuildPartX() (例如图6-2中的 BuildPartA()、BuildPartB() 等),它们用于创建复杂对象的各个部件;另一类方法是GetResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。(2) ConcreteBuilder(具体建造者):它实现了 Builder 接口,实现各个部件的具体构造和装配方法,定义并明确所创建的复杂对象,还可以提供一个方法返回创建好的复杂产品对象(该方法也可由抽象建造者实现)。(3) Product(产品):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。(4) Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其 Construct() 建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者 Setter 方法将该对象传入指挥者类中。2.2 建造者模式的实现在构建模式的定义中提到了复杂对象,那什么是复杂对象呢?简单来说,复杂对象是指包含多个成员变量的对象,这些成员对象也称为部件或零件。举例:汽车(复杂对象)包括:方向盘,车灯,发动机,轮胎,座椅等部件;电子邮件(复杂对象)包括:发件人,收件人,主题,内容、附件等部件;建造者模式的代码设计using System; namespace BuilderPattern class Program static void Main(string[] args) Console.WriteLine("Hello BuilderPattern!"); Builder builder = new ConcreteBuilder1(); Director director = new Director(builder); Product product = director.Construct(); //构建复杂对象 Console.WriteLine($"【复杂对象】=> PartA:{product.PartA},PartB:{product.PartB},PartC:{product.PartC}"); #region BuilderPattern-Demo /// <summary> /// 产品 /// </summary> class Product public string PartA { get; set; } public string PartB { get; set; } public string PartC { get; set; } /// <summary> /// 构建着 & 抽象类 /// </summary> abstract class Builder //创建产品对象 protected readonly Product product = new Product(); public abstract void BuildPartA(); public abstract void BuildPartB(); public abstract void BuildPartC(); /// <summary> /// 返回产品对象 /// </summary> /// <returns></returns> public Product GetResult() return product; /// <summary> /// 具体构建者 /// </summary> class ConcreteBuilder1 : Builder public override void BuildPartA() product.PartA = "A1"; public override void BuildPartB() product.PartB = "B1"; public override void BuildPartC() product.PartC = "C1"; /// <summary> /// 指挥者 /// </summary> class Director private Builder _builder; public Director(Builder builder) _builder = builder; public void SetBuilder(Builder builder) _builder = builder; /// <summary> /// 产品构建与组装方法 /// </summary> /// <returns></returns> public Product Construct() _builder.BuildPartA(); _builder.BuildPartB(); _builder.BuildPartC(); return _builder.GetResult(); #endregion }在指挥者类中可以注入一个抽象建造者类型的对象,它提供了一个建造者方法 Construct() ,在该方法中调用了 builder 对象的构造部件的方法,最后返回一个产品对象。对于客户而言,只需关心具体的构建者的类型,无需关心产品对象的具体组装过程。用户可以通过配置文件来存储具体的建造者类 ConcreteBuilder1 的类名,使得在更换新的构建者时无需修改源代码,系统扩展更为方便。2.3 建造者模式 & 抽象工厂模式的异同与关联共同点:建造者模式与抽象工厂模式都是较为复杂的创建型模式。差异点:侧重点不一样,建造者模式返回一个完整的复杂产品,而抽象工厂模式是返回一系列相关的产品。在抽象工厂模式中,客户端通过选择具体厂来生成所需对象。而在建造者模式中,客户端通过指定具体建造者类型来指导 Director 如何去生成对象,侧重逐步构造一个复杂对象,然后将结果返回。关联点:如果将抽象工厂模式看成一个汽车配件生产厂,生成不同类型的汽车配件,那么建造者模式就是一个汽车组装厂,通过对配件进行组装返回一辆完整的汽车。2.4 建造者模式的应用实例2.4.1 实例说明某游戏软件公司决定开发一款基于角色扮演的多人在线网络游戏,玩家可以在游戏中扮演虚拟世界中的一个特定角色,角色根据不同的游戏情节和统计数据(例如力量、魔法、技能等)具有不同的能力,角色也会随着不断升级而拥有更加强大的能力。作为该游戏的一个重要组成部分,需要对游戏角色进行设计,而且随着该游戏的升级将不断增加新的角色。通过分析发现,游戏角色是一个复杂对象,它包含性别、面容等多个组成部分,不同类型的游戏角色,其性别、面容、服装、发型等外部特性有所差异,例如“天使”拥有美丽的面容和披肩的长发,并身穿一袭白裙;而“恶魔”极其丑陋,留着光头并穿一件刺眼的黑衣。无论是何种造型的游戏角色,它的创建步骤都大同小异,都需要逐步创建其组成部分,再将各组成部分装配成一个完整的游戏角色。现使用建造者模式来实现游戏角色的创建。2.4.2 实例角色结构图2.4.3 实例代码(1)Actor:游戏角色类,充当复杂产品对象。此处为案例讲解,简化了成员变量的数据类型,统一使用 string ,真实环境中具体的类型根据实际情况定义。/// <summary> /// 游戏角色类,充当复杂产品对象。 /// </summary> class Actor /// <summary> /// 角色类型 /// </summary> public string Type{ get; set; } /// <summary> /// 性别 /// </summary> public string Sex{ get; set; } /// <summary> /// 面容 /// </summary> public string Face{ get; set; } /// <summary> /// 服装 /// </summary> public string Costume{ get; set; } /// <summary> /// 发型 /// </summary> public string Hairstyle{ get; set; } }(2)ActorBuilder:游戏角色建造者,充当抽象建造者。/// <summary> /// 角色建造者:抽象建造者 /// </summary> abstract class ActorBuilder protected readonly Actor actor = new Actor(); public abstract void BuildType(); public abstract void BuildSex(); public abstract void BuildFace(); public abstract void BuildCostume(); public abstract void BuildHairstyle(); //工厂方法,返回一个完整的游戏角色对象 public Actor CreateActor() return actor; }(3)HeroBuilder:英雄角色建造者,充当具体建造者。/// <summary> /// 英雄角色建造者,充当具体建造者。 /// </summary> class HeroBuilder : ActorBuilder public override void BuildType() actor.Type = "英雄"; public override void BuildSex() actor.Sex = "男"; public override void BuildFace() actor.Face = "英俊"; public override void BuildCostume() actor.Costume = "盔甲"; public override void BuildHairstyle() actor.Hairstyle = "飘逸"; }其他两个角色 AngelBuilder:天使角色构建者,DevilBuilder:恶魔角色建造者,均充当具体建造者。和上述代码类似,不再重复。(4)ActorController:角色控制器,充当角色指挥者。/// <summary> /// 角色控制器,充当角色指挥者。 /// </summary> class ActorController //逐步构建复杂产品对象 public Actor Construct(ActorBuilder ab) Actor actor; ab.BuildType(); ab.BuildSex(); ab.BuildFace(); ab.BuildCostume(); ab.BuildHairstyle(); actor = ab.CreateActor(); return actor; }(5)配置文件 App.config,在配置文件中存储了具体建造者类的类名(FullClassName)<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="builder" value="BuilderPattern.GameCharacter.HeroBuilder"/> </appSettings> </configuration>(6)客户端 Program 中的 Main 调用// 游戏角色结构创建 //xml配置文件与反射方式扩展 // 1.读取【App.config】配置文件,key = builder string builderType = ConfigurationManager.AppSettings["builder"]; // 2.反射生成对象 ActorBuilder ab = (ActorBuilder)Assembly.Load("BuilderPattern").CreateInstance(builderType); ActorController ac = new ActorController(); Actor actor = ac.Construct(ab); // 通过指挥者创建完整的建造者对象 Console.WriteLine($"{actor.Type}的外观,性别:{actor.Sex},面容:{actor.Face},服装:{actor.Costume},发型:{actor.Hairstyle}");3 | 指挥者类 Director 的深入探讨3.1 建造者模式的简化简化方式1:省略 Director 在某些情况下,为了简化系统结构,可以将 Director 和 抽象建造者 Builder 进行合并,在 Builder 中提供了逐渐构建复杂产品对象的 Construct() 方法。由于 Builder 类通常为抽象类,因此可以将 Construct() 方法定义为静态,以便客户端能够直接调用。如果将游戏角色实例中的指挥者类 ActorController 省略,ActorBuilder 的代码修改如下:abstract class ActorBuilder // 设置对象静态 & 只读并且实例化 protected static readonly Actor actor = new Actor(); public abstract void BuildType(); public abstract void BuildSex(); public abstract void BuildFace(); public abstract void BuildCostume(); public abstract void BuildHairstyle(); // 工厂方法,返回一个完整的游戏角色对象 public Actor CreateActor(ActorBuilder ab) ab.BuildType(); ab.BuildSex(); ab.BuildFace(); ab.BuildCostume(); ab.BuildHairstyle(); return actor; }简化方式2 :省略 Director ,同时去除 CreateActor(ActorBuilder ab) 方法的参数 ActorBuilder,直接在该方法中调用 this.BuildXXX() 方法,修改如下:abstract class ActorBuilder // 设置对象静态 & 只读并且实例化 protected static readonly Actor actor = new Actor(); public abstract void BuildType(); public abstract void BuildSex(); public abstract void BuildFace(); public abstract void BuildCostume(); public abstract void BuildHairstyle(); // 工厂方法,返回一个完整的游戏角色对象 public Actor CreateActor() this.BuildType(); this.BuildSex(); this.BuildFace(); this.BuildCostume(); this.BuildHairstyle(); return actor; }简化后的客户端调用// 游戏角色结构创建 //xml配置文件与反射方式扩展 // 1.读取【App.config】配置文件,key = builder string builderType = ConfigurationManager.AppSettings["builder"]; // 2.反射生成对象 ActorBuilder ab = (ActorBuilder)Assembly.Load("BuilderPattern").CreateInstance(builderType); Actor actor = ab.Construct(); // 通过指挥者创建完整的建造者对象 Console.WriteLine($"{actor.Type}的外观,性别:{actor.Sex},面容:{actor.Face},服装:{actor.Costume},发型:{actor.Hairstyle}");以上两种简化方式都不影响系统的灵活性和可扩展性,同时还简化了系统结构,但是加重了抽象建造者类的职责。如果上述的 CreateActor() 方法内部构造较为复杂,待构建产品的组成部分较多,建议将 CreateActor() 方法单独封装在 Director 中,这样更符合单一职责原则。3.2 钩子方法(Hook Method)的引入建造者模式除了逐步构建一个复杂产品对象外,还可以通过 Director 类来更加精细地控制产品的创建过程,例如增加一类称为钩子方法 (Hook Method) 的特殊方法来控制是否调用某个 BuildPartX() 方法。钩子方法的返回类型通常为 bool 类型,方法名一般为 IsXXX(),钩子方法定义在抽象建造者类中。例如可以在游戏角色的抽象建造者类 ActorBuilder 中定义一个方法 IsBareheaded(),用于判断某个角色是否为“光头(Bareheaded)”,在 ActorBuilder 中为之提供一个默认实现,其返回值为 false,代码如下://角色建造者:抽象建造者 abstract class ActorBuilder protected Actor actor = new Actor(); public abstract void BuildType(); public abstract void BuildSex(); public abstract void BuildFace(); public abstract void BuildCostume(); public abstract void BuildHairstyle(); //钩子方法(Hook Method),需要使用 virtual 关键字 public virtual bool IsBareheaded() return false; //工厂方法,返回一个完整的游戏角色对象 public Actor CreateActor() return actor; }如果派生类中的某个角色无需构建某个部件(比如恶魔 Devil /头发部件),则对应的具体建造者 DevilBuilder 将重写覆盖 父类的 钩子方法,并将其值改为 true 返回。/// <summary> /// 英雄角色建造者,充当具体建造者。 /// </summary> class HeroBuilder : ActorBuilder public override void BuildType() actor.Type = "英雄"; public override void BuildSex() actor.Sex = "男"; public override void BuildFace() actor.Face = "英俊"; public override void BuildCostume() actor.Costume = "盔甲"; public override void BuildHairstyle() actor.Hairstyle = "飘逸"; // 重写覆盖父类的钩子方法 public override bool IsBareheaded() return true; } 同时修改指挥者类 ActorController 的代码如下:/// <summary> /// 角色控制器,充当角色指挥者。 /// </summary> class ActorController //逐步构建复杂产品对象 public Actor Construct(ActorBuilder ab) ab.BuildType(); ab.BuildSex(); ab.BuildFace(); ab.BuildCostume(); // 通过钩子方法来控制产品部件的构建 if (!ab.IsBareheaded()) ab.BuildHairstyle(); return ab.CreateActor(); }完整代码示例请查看=》DesignPattern: DesignPattern GoF 23+1 | 设计模式 23+1 - Gitee.com 4 | 建造者模式的优缺点与适用环境建造者模式的核心在于如何逐步的构建一个包含多个组成部件的完整对象,适用相同的构建过程构建不同的产品。在软件开发中,如果要创建复杂对象并希望具备很好的灵活性和可扩展性,可以考虑适用建造者模式。4.1 建造者模式的主要优点(1)在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与严品的创建过密解熊、曼得祖同的创建过程可以创建不同的产品对象。(2)每一个具体建造着都相对独立,与其他的具体建造者无关,因此可以很方便地具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对由于指挥者类针对抬象建造者编程,增加新的具体建者无须修改原有类库的代码,系展方便、等合开团原则。(3)用户可以更加精细地控制产品的创建过程,将复杂产品的创建步骤分解在不同的方注中,使具创建过程更加清晰,也更方便使用程序来控制创建过程。4.2 建造者模式的主要缺点(1)建造者模式所创建的产品一般具有较多的共同点、其组成部分相似,如果产品之间的差异性很大,例如很多组成部分不相同,则不适合使用建造者模式,因此其使用范围一定的限制。(2)如果产吕的内部变化复杂,可能会需要定义很多具体建造者类来实现这种变化。致系统变得很庞大,增加了系统的理解难度和运行成本。4.3 建造者模式的适用环境(1)需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量。(2)雪要生成的产品对象的属性相互依赖,需要指定其生成顺序。(3)对象的创建过程独立于创建该对象的类。在建造者模式中通过引入指挥者类,创建过程封装在指挥者类中,而不在建造者类和客户类中。(4)隔离复杂对象的创建和使用,并使得相同的创建过程创建不同的产品。
Json Web Token(JWT)JSON Web Token(JWT) 是一个非常轻巧的规范。这个规范允许我们使用 JWT 在两个组织之间传递安全可靠的信息。官方定义:JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two partiesJWT Auth现在网上大多数介绍 JWT 的文章实际介绍的都是 JWS(JSON Web Signature),也往往导致了人们对于 JWT 的误解,但是 JWT 并不等于 JWS,JWS 只是 JWT 的一种实现,除了 JWS 外,JWE(JSON Web Encryption) 也是 JWT 的一种实现。JWT、JWS 与 JWE下面就来详细介绍一下 JWT 与 JWE 的两种实现方式:JSON Web Signature(JWS) 是一个有着简单的统一表达形式的字符串:JWS/JSON Web Signature 组成1) 头部(Header)头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。JSON内容要经Base64 编码生成字符串成为Header。2) 载荷(PayLoad)payload的五个字段都是由JWT的标准所定义的。iss: 该JWT的签发者;sub: 该JWT所面向的用户;aud: 接收该JWT的一方;exp(expires): 什么时候过期,这里是一个Unix时间戳;iat(issued at): 在什么时候签发的;后面的信息可以按需补充。JSON内容要经 Base64 编码生成字符串成为负载/PayLoad。3) 签名(signature)这个部分 header 与 payload 通过 header 中声明的加密方式,使用密钥 secret 进行加密,生成签名。JWS 的主要目的是保证了数据在传输过程中不被修改,验证数据的完整性。但由于仅采用 Base64 对消息内容编码,因此不保证数据的不可泄露性。所以不适合用于传输敏感数据。JSON Web Encryption(JWE) 相对于 JWS,JWE 则同时保证了安全性与数据完整性。JWE 由五部分组成:JWE/JSON Web Encryption 组成具体生成步骤为:JOSE 含义与 JWS 头部相同;生成一个随机的 Content Encryption Key(CEK);使用 RSAES-OAEP 加密算法,用公钥加密 CEK,生成 JWE Encrypted Key;生成 JWE 初始化向量;使用 AES GCM 加密算法对明文部分进行加密生成密文 Ciphertext,算法会随之生成一个 128 位的认证标记 Authentication Tag;对五个部分分别进行 base64 编码;可见,JWE 的计算过程相对繁琐,不够轻量级,因此适合与数据传输而非 token 认证,但该协议也足够安全可靠,用简短字符串描述了传输内容,兼顾数据的安全性与完整性。 【转载声明】原文地址:https://www.jianshu.com/p/50ade6f2e4fd
.NET 从 2002 年 2 月份发布第一个版本 1.0,至今已有 20 年了,大概经历了以下几个阶段:早期 .NET,Only Windows(全称 .NET Framework1.0-4.8.1,时间 2002.02-2019.04);过渡期 .NET,开源、跨平台(.NET Core1.0-3.1.x,时间 2016.06.27-2022.12.13);当下 .NET(.NET5/6/7+) 统一化、云原生、高性能,其中 .NET5 终止于 2022 年 5 月 10 日,.NET6 LTS 版本支持到 2024.11.12,.NET7 RC1 预计 2022 年 11 月发布正式版。早期 .NET(.NET Framework 1.0-4.8.1,时间 2002.02-2022.08)2002 年 2 月 23 日最早的 .NET Framework1.0 发布,终止于 2022 年 8 月 9 日发布的 .NET Framework 4.8.1 版本。2019 年 4 月 18 日发布 .NET Framework4.8.02022 年 8 月 9 日发布 .NET Framework4.8.1.NET Framework 是由微软开发,一个致力于敏捷软件开发(Agile software development)、快速应用开发(Rapid application development)、平台无关性和网络透明化的软件开发平台。.NET 是微软为下一个十年对服务器和桌面软件工程迈出的第一步。.NET 包含许多有助于 Internet 和 Intranet 应用 迅捷开发的技术。.NET Framework 是微软公司继 Windows DNA 之后的新开发平台。.NET Framework 是以一种采用系统虚拟机运行的编程平台,以公共语言运行时(Common Language Runtime)为基础,支持多种语言(C#、VB.NET、C++、J# 等)的开发。.NET Framework 也为编程界面(API)提供了新功能和开发工具。这些革新使得程序设计员可以同时进行 Windows 应用软件和网络应用软件以及元件和服务(web service)的开发。.NET Framework 提供了一个新的反射性的且面向对象程序设计编程界面。.NET Framework 设计得足够通用化从而使许多不同高级语言都得以被汇集。.NET Framework(only windows) 架构图.NET Framework 与 C# 概述.NET 基金会成立(2014.03.31).NET Foundation 官方地址:https://dotnetfoundation.org/.NET 基金会(.NET Foundation)是一个独立的组织,支持 .NET 社区和开源,旨在拓宽和加强 .NET 生态系统和社区。2014 年 3 月 31 日微软组织成立 .NET 基金会,微软在成为主要的开源参与者的道路上又前进了一步。2014 年 .NET 基金会的创始成员中有六位创始人,均非微软公司员工。2019 年 .NET 基金会改选,其中只有一位是微软员工,其他都是微软 MVP 或 .NET 活跃人士。.NET 基金会的成立,微软在 .NET 项目中的角色从原来的 “管理(独裁)者” 转变为成员 “参与者”,促进社区更佳开放、自由的生态发展。过渡期 .NET(.NET Core 1.0-3.1.x,时间 2016.06.27-2022.12.13)2016 年 6 月 27 日 .NET Core 1.0 项目正式发布,彻底改变了 Windows Only 的场景,拥抱开源、跨平台。.NET Core 是适用于 Windows、Linux 和 macOS 的免费、开源托管的计算机软件框架,是微软开发的第一个官方版本,具有跨平台能力的应用程序开发框架 (Application Framework),未来也将会支持 FreeBSD 与 Alpine 平台,也是微软在一开始发展时就开源的软件平台 [1] ,它经常也会拿来和现有的 开源 .NET 平台 Mono 比较。由于 .NET Core 的开发目标是跨平台的 .NET 平台,因此 .NET Core 会包含 .NET Framework 的类库,但与 .NET Framework 不同的是 .NET Core采用包化 (Packages) 的管理方式,应用程序只需要获取需要的组件即可,与 .NET Framework 打包式安装的做法截然不同,同时各包亦有独立的版本线 (Version line),不再硬性要求应用程序跟随主线版本。.NET Core 项目的主要目标有 [2] :支持或可以移转 (port) 到更多的操作系统平台与芯片架构 (也就是未来项目会跨出 x86 平台)。具有引人注目的性能与高可靠度。开发人员能快速与直接的获取 .NET Core 开发环境。在直觉与具生产力的情况下建造应用程序,使用文件,示例与 NuGet 组件。当下 .NET(.NET5/6/7+).NET 5(非 LTS 版,时间 2020.11.10-2022.05.08)2020 年 11 月 10 日正式发布 .NET 5 ,终止于 2022年5月10日。该版本当前最新补丁版本为 5.0.12,发布时间为 2021 年 11 月 8 日。.NET 5 = .NET Core vNext,NET 5 是 .NET Core 的下一版本。该项目旨在通过以下几个关键方式改进 .NET:构建一个可在任何地方(Any where)使用的 .NET 运行时和框架,并具有统一的运行时行为和开发人员体验。通过充分利用 .NET Core、.NET Framework、Xamarin 和 Mono 来扩展 .NET 的功能。从单个代码库构建该产品,开发人员( Microsoft 和 社区)可以一起工作并一起扩展,从而改进所有方案。这个新项目和方向是 .NET 的一个重要转折。使用 .NET 5,无论您正在构建哪种类型的应用程序,您的代码和项目文件都将是相同的。每个应用都可以访问相同的运行时、API 和语言功能。也包括几乎每天都在进行的 corefx 的性能改进。.NET 6(LTS,Long Term Support,时间 2022.09.13-2024.11.12)2021 年 11 月 8 日,微软正式发布了 .NET 6 及其一系列内容,推出了 C# 10、F# 6 和 PowerShell 7.2,.NET 6 还原生支持了苹果 M1 芯片,将得到三年支持。.NET 6 是从 .NET 5 开始的 .NET 统一计划的最后部分,在 .NET 历程上具有 里程碑意义。这种 统一 体现在 ==跨平台、桌面、IoT 和云应用的统一的SDK、基本库和运行时(Runtime)==。 除了这种统一,.NET6 生态系统还提供:简化的开发:入门很简单。 C# 10 中的新语言功能减少了需要编写的代码量。 通过 Web 堆栈 和 最小 API(minAPI) 方面的投资,可以轻松快速编写更小、速度更快的 微服务(Microservice)。更好的性能:.NET 6 是最快的完整堆栈 Web 框架,如果是在云中运行,则它可以降低计算成本。终极工作效率:.NET 6 和 Visual Studio 2022 提供热重载(Hot Reload)、新的 git 工具、智能代码编辑、可靠的诊断和测试工具,以及更好的团队协作。重要点:.NET 6 将支持三年(2021 年 11 月 8 日到 2024 年 11 月 12 日) ,作为 LTS (长期) 支持。2021 年 11 月 8 日的发布是 .NET 团队和社区一年多努力的结果。其中 C# 10 和 F# 6 提供语言改进,使代码更简单、更好。性能有了巨大的提升。.NET 6 首次发布了对本地化 Apple Silicon(Arm64) 的支持,并且还改进了 Windows Arm64 的相关性能。.NET 6 构建了一个新的动态配置文件导向优化 (PGO) 系统,该系统可提供仅在运行时才可能实现的深度优化。云诊断已改进与 dotnet monitor 和 Open Telemetry。WebAssembly 支持更有能力、更具有性能。新的 API 已经添加支持 HTTP/3,处理 JSON 数据,并直接操纵内存。开发人员已经开始将应用程序升级到 .NET 6,已在生产方面取得了很大的提升。您可以下载 ==.NET 6 用于 Linux、macOS 和 Windows==。.NET 7(非 LTS 版, 从正式版发布之日起仅能获得18 个月的免费支持和补丁)2022 年 2 月17日发布 NET 7.0.0-preview.1,目前最新版是 NET 7.0.0-rc1.NET 7 RC 1 已通过 Visual Studio 17.4 Preview 2 测试,微软将在 2022 年 11 月 8 日至 10 日的 .NET Conf 2022 上发布 .NET 7 正式版。.NET 7 RC 1 包含多项改进:.NET MAUI:.NET 多平台应用程序 UI (MAUI) 将 Android、iOS、macOS 和 Windows API 统一到一个 API 中,开发者可以编写一个在多平台上本机运行的应用。作为 .NET 7 的一部分,.NET MAUI 提供了一个项目来处理跨设备及其平台的多目标。云原生(Cloud Native):云原生是一组最佳实践,用于在云中构建应用,以利用弹性、可扩展性、效率和速度。ARM64:.NET 可帮助开发者构建在 ARM 设备上运行的应用,.NET 7 将迎来多项改进。现代化:为了使升级体验尽可能无缝,.NET 升级助手为开发者提供分步指导体验,通过分析和改进项目文件、代码文件和依赖项来现代化 .NET 应用。性能:.NET 7 是目前最快的 .NET。.NET 7 对反射、堆栈替换 (OSR)、启动时间、本机 AOT、循环优化和许多其他领域进行了超过一千项影响性能的改进。支持:.NET 7 不是长期支持 (LTS) 版本,因此在发布之日起仅能获得 18 个月的免费支持和补丁。.NET 未来发布计划发布类型.NET 未来版本将会一直提供 LTS 版 和 非 LTS 版:LTS 版本每个版本均可获得三年的补丁更新和免费支持。非 LTS 版可以获得至少 18 个月的补丁更新和免费支持。长期支持(LTS):LTS 版本在初始发布日期后的三年内受支持。标准期限支持(STS):STS 版本在后续 STS 或 LTS 版本发布后的六个月内受支持。每 12 个月发布一次,因此 STS 的支持期为 18 个月。说明:STS 版本以前称为 Current 版本。发布计划主要 .NET 版本每年 11 月发布一次。每个 .NET 版本在发布开始时都定义为标准期限支持(STS)或长期支持(LTS)。STS 版本以偶数年为单位发布,LTS 版本以奇数年为单位发布。所有版本的质量完全相同,唯一的区别是支持长度。LTS 版本可获得 3 年的免费支持和修补程序。STS 版本可获得 18 个月的免费支持和修补程序。修补程序更新会于每月第二个星期二发布,也称为“修补程序星期二”。在版本的支持生命周期内,系统必须保持已发布补丁更新的最新状态。版本修补程序是兼容的,这消除了对应用程序产生不利影响的风险。.NET 20th(happy birthday)今年 2022 年是 .NET 诞生 20 周年,祝 .NET 生日快,.NET 生态开放共赢,与时俱进,蓬勃发展!二十岁的女孩子是美丽的流星划过天际,还是燃烧着的陨石坠落人间,二十岁的年纪,美好的人生才刚刚开始;二十岁,我虽还是万丈红尘中的一颗小小尘埃,可仍会盼望有一天能开出一抹红,在阳光下灿烂;二十岁的女孩是一朵盛开最美丽的花,是一杯清茶其中的清秀一定要留给懂得品尝的人;20 岁正值年华,20 岁有很多的美好和憧憬,你是否也期待?关于 .NET 更多信息,请查看:.NET | 免费,跨平台,开源 (microsoft.com),https://dotnet.microsoft.com/zh-cn/.NET 基金会,https://dotnetfoundation.org/.NET 和 .NET Core 支持策略,https://dotnet.microsoft.com/zh-cn/platform/support/policy/dotnet-core.NET 支持策略,https://dotnet.microsoft.com/zh-cn/platform/support/policy
.NET 的从业分享纵观神州大地,漫游中华互联网,我看到很多人关注为什么你应该开始学习 JavaScript 做前端,而对Blazor 这样的面向未来的框架有种莫名的瞧不起,或者为什么你应该学习 Python 作为你的第一门编程语言,恕不知有多少公司业务是用 Python 开发的,Python 更多是粘合剂,作为胶水语言来使用。我(张善友)用 C#(CSharp) 工作了 20 多年,我也一直把它当作第一编程语言,几乎尝试了任何东西:桌面、物联网、移动、Web、云原生 —— 以及 C# 和 .NET 完美契合的所有地方,特别是当下 ChatGPT 还有 AI 的辅助编程时代,你应该考虑从它开始你的开发人员之路。与时俱进的 C#/.NETC# 是一种编程语言,.NET 是建立在它之上的软件框架。为了简单起见,当我说 C# 时,我假设了它与 .NET 结合使用,反之亦然。1、.NET 允许您在任何地方构建任何内容使用单一语言和单一框架,您可以构建任何类型的应用程序:桌面和移动设备以及云服务,您可以创建游戏并探索 AI。.NET 是跨平台的:你可以为 Windows,Linux,Android、iOS 甚至物联网设备构建应用程序。想象一下,你可以采用一种语言,尝试每一个方向,并在这个过程中提高你的技能。不过,您仍然需要研究方向细节。特别是如果想从互联网转向物联网,有了有扎实的技术基础,花个 1 年时间学习行业知识,专业的 .NET 技术都是可以无缝衔接过去了。您可以朝着自己最喜欢的方向进行改进,或者如果您愿意,甚至可以开始使用更特定于选定领域的语言和工具。例如,对于移动开发,我看到人们从 Xamarin 转向 iOS 或 Android 上的本机开发。2、C# 是第五种流行的语言根据 TIOBE 指数(基于搜索引擎结果的编程语言流行指数),C# 作为最受欢迎的语言占据了第五位。在过去的 10 年里,它一直位居前 10 名。 最近几年,随着 2014 年 .NET 开源跨平台,它的受欢迎程度一直增长。https://www.tiobe.com/tiobe-index/https://hellogithub.com/report/tiobe?month=33、C#/.NET 拥有最好的学习材料之一Microsoft 和 .NET Community 提供了大量任何形式的材料:文章、视频、课程、教程和书籍,使您能够成为一名成功的 .NET 开发人员。您可以在微软学习门户上查看所有可用的材料。社区提供了令人惊叹的全包含路线图,以掌握取得成功所需的技能和知识,例如面向后端开发人员的 ASP.NET Core 路线图。您可以轻松地在 github 上找到其他方向的路线图。AspNetCore-Developer-Roadmap面向后端开发人员的 ASP.NET Core 路线图。.NET Conf(.net 年度大会)https://www.dotnetconf.net/而微软本身也试图做不同的有趣活动来吸引更多的开发者。例如每年一度的 .NET Conf。了解 .NET免费教程、视频、课程等,面向从初学者到高级 .NET 开发人员。什么是 .NET?.NET 是由 Microsoft 创建的开源开发人员平台,用于生成许多不同类型的应用程序。.NET 是一个免费的跨平台开源开发人员平台,用于生成许多不同类型的应用。使用 .NET,可以使用多种语言、编辑器和库来构建 Web、移动、桌面、游戏和 IoT 等。为什么选择 .NET?官方给出以下几点解释:高效、任何应用,任何平台深受开发人员的喜爱性能真正的用武之地,Web Framework Benchmarks广受信任且安全大型生态系统.NET Foundation 是一个独立的非营利组织,支持创新的、商业友好的开放源代码 .NET 生态系统。.NET 文档了解如何使用 .NET 在任何使用 C#、F# 和 Visual Basic 的平台上创建应用程序。 浏览 API 引用、代码示例、教程以及其他内容。4、.NET6 简化了快速入门从 .NET6 开始,.NET 在简化入门体验方面迈出了一大步。minimal API(最小 API) 功能为我们提供了一种在单个文件中使用干净代码创建具有最少依赖项的 HTTP API 的方法。只需通过单个命令创建 4 行代码即可使用 .NET6 启动 Web 应用。var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();5、大家开始重新审视微软技术今年所刮起的 ChatGPT 风潮,让之前所有带着有色眼镜看微软技术的人们重新开始审视微软技术,为什么是微软在这波 AI 浪潮中独占鳌头,答案自然是非常清楚的,是 CEO 纳德拉的在 2014 年为微软按下的刷新(相关书籍,让我们重新认识微软——《刷新-重新发现商业与未来》)按钮,经历了接近 10 年时间的积累,开始引领技术潮流。同样的 .NET 也是在 2014 年开始了刷新历程,经历了接近十年的迭代,已经脱胎换骨完成了蜕变。 每一年的 .NET 版本的发布都是至今为止性能最好的版本,今年即将发布的 .NET8 依然会是如此,这里是不是和 OpenAI CEO Sam Altman 称,GPT-4 是“我们迄今为止最强大、对齐最好的模型”。6、Azure OpenAI 服务自从去年年底 ChatGPT 推出以来,我们看到全球各行各业利用 ChatGPT 落地实际应用场景,例如:内容摘要、总结、电子邮件内容推荐和生成,甚至解决软件代码问题。现在,随着企业级 ChatGPT(国际预览版)在 Azure OpenAI 服务中推出,各行业客户及开发者可以将 ChatGPT 所带来的革命性体验直接集成到实际业务系统或 App。例如通过 ChatGPT 驱动的自动化客服、总结客户服务对话来更快解决客户支持问题、个性化产品优惠推荐、创建新的广告文案、自动化理赔处理等场景。Azure OpenAI 服务推出的最早的 SDK 就是 .NET 的 ,这是一个大利好,这是促进在企业级采用 .NET 技术的助推器。https://www.nuget.org/packages/Azure.AI.OpenAIhttps://xakpc.github.io/DotnetPrompt/ (以及,社区也推出了一个提示库)总结总之,在选择第一语言时,您应该问自己两个问题:如果你想找工作,它是一种好的编程语言吗?这是一门开始学习编程的好语言吗?对于 C#,我的答案是肯定的。 对 C#/.NET 开发人员的需求很高,它是一种类似 C 的语言,具有强大的面向对象方法,并且每个版本的学习曲线都变得越来越容易接受。【转载申明】原文地址:https://blog.csdn.net/sD7O95O/article/details/129604535
大家好,我是 Alex,今天谈一谈设计模式,一名优秀的开发,应该多少都需要了解一些常用的设计模式和使用场景,让我们一起来重温一下那些年经典设计模式;1 & 本文主要内容2 & 为什么要掌握设计模式2.1 历史的教训时间回到 20 世纪 80 年代,当时的软件行业正处于第二次软件危机中。根本原因是,随着软件规模和复杂度的快速增长,如何高效高质的构建和维护这样大规模的软件成为了一大难题。无论是开发何种软件产品,成本和时间都最重要的两个维度。较短的开发时间意味着可比竞争对手更早进入市场;较低的开发成本意味着能够留出更多营销资金,因此能更广泛地覆盖潜在客户。2.2 设计模式是“银弹”吗?【代码复用】是减少开发成本,减低复杂度最常用的方式之一,这个想法表面看起来很棒,但实际上要让已有代码在全新的上下文中工作,通常还是需要付出额外努力的。组件间紧密的耦合、对具体类而非接口的依赖和硬编码的行为都会降低代码的灵活性,使得复用这些代码变得更加困难。设计模式目标就是帮助软件提高内聚,减低耦合,使用设计模式是增加软件组件灵活性并使其易于复用的方式之一。【变化】是程序员生命中唯一不变的事情,客户需求可能经常会变,紧急上线的版本,要不要下次重构一下,还是继续打各种补丁,技术债会越积越多,因此在设计程序架构时,所有有经验的开发者会尽量选择支持未来任何可能变更的方式。可扩展性成为了程序设计必须要考虑指标,而设计模式是可以借鉴的,成熟的优化程序设计的解决方案;总体来说,深刻理解设计模式会给我们带来很多好处:可以和面试官"畅谈"设计模式相关问题;很多开源软件框架大量使用了设计模式,比如 Linux 系统,Redis,Spring,C++ STL 等等,可以把帮你加快理解开源软件框架;当你写的代码越来越优美后,你的代码鉴赏能力就会提高,对团队 code review 贡献也会更大,在个人影响力也会提高;你不会再畏手畏脚,你的工具箱里面工具很多后,可以帮助你,应对各种大型项目的代码设计和开发;每个领域都会一些成熟"套路", 编程也不例外,熟悉这些套路,可以更好方便交流和更快速地解决问题;为了更好理解 设计模式,我们首先要理解一些重要的 设计原则,而不是片面理解设计模式哪些模式名词,要看清楚这背后的原理,这个才是最重要的。3 & 代码设计原则代码设计原则贯穿在整个设计模式之中,是理解其中的精华,本文讨论了一些重要的设计原则,包括通用设计原则,DRY 原则,KISS 原则,SOLID 原则 等:3.1 通用设计原则【隔离变化】找到程序中的变化内容并将其与不变的内容区分开,该原则的主要目的是将变更造成的影响最小化。【面向接口编程】面向接口进行开发,而不是面向实现;依赖于抽象类型,而不是具体类,要求接口标准化设计,只要对外的接口没有变,内部实现就可以任意变化,为以后留有更多优化空间,方便以后更新迭代,可以说这样的设计是灵活的。【组合优于继承】继承可能是类之间最明显、最简便的代码复用方式。如果你有两个代码相同的类,就可以为它们创建一个通用的基类,然后将相似的代码移动到其中。但继承可能带来的问题:子类不能减少超类的接口。你必须实现父类中所有的抽象方法,即使它们没什么用。在重写方法时,你需要确保新行为与其基类中的版本兼容。这一点很重要,因为子类的所有对象都可能被传递给以超类对象为参数的任何代码,相信你不会希望这些代码崩溃的。继承打破了超类的封装,因为子类拥有访问父类内部详细内容的权限。此外还可能会有相反的情况出现,那就是程序员为了进一步扩展的方便而让超类知晓子类的内部详细内容。子类与超类紧密耦合。超类中的任何修改都可能会破坏子类的功能。通过继承复用代码可能导致平行继承体系的产生。继承通常仅发生在一个维度中。只要出现了两个以上的维度,你就必须创建数量巨大的类组合,从而使类层次结构膨胀到不可思议的程度。组合是代替继承的一种方法。继承代表类之间的“是”关系(汽车是交通工具),而组合则代表“有”关系(汽车有一个引擎)。3.2 DRY 原则DRY-Don't Repeat Yourself(不要重复代码)降低可管理单元的复杂度的基本策略是将系统分成多个部分。理解这一原理是如此重要,它通常以首字母缩写词 DRY 来指代,并出现在 Andy Hunt 和 Dave Thomas 的书《实用程序员》中,但是这个概念本身已经有很长时间了。它指的是软件的最小部分。当您构建一个大型软件项目时,通常会因整体复杂性而感到不知所措。人类不善于管理复杂性;他们擅长为特定范围的问题找到有创意的解决方案。降低可管理单元的复杂性的基本策略是将系统分成更方便的部分。首先,您可能希望将系统分为多个组件,其中每个组件代表其自己的子系统,其中包含完成特定功能所需的一切。3.3 KISS 原则KISS 是使它保持简单,愚蠢的首字母缩写,是美国海军在 1960 年提出的设计原则。KISS 原则指出,大多数系统如果保持简单而不是变得复杂,则效果最佳。因此,简单性应该是设计的主要目标,并且应该避免不必要的复杂性。3.4 SOLID 原则SOLID 原则是在 罗伯特·马丁 的著作《敏捷软件开发:原则、模式与实践》中首次提出的,SOLID 是让软件设计更易于理解、更加灵活和更易于维护的五个原则的简称,分别如下:Single Responsibility Principle,单一职责原则Open/closed Principle,开闭原则Liskov Substitution Principle,里氏替换原则Interface Segregation Principle,接口隔离原则Dependency Inversion Principle,依赖倒置原则尽量让每个类或者函数只负责软件中的一个功能,这条原则的主要目的是减少复杂度,你不需要费尽心机地去构思如何仅用 200 行代码来实现复杂设计,实际上完全可以使用十几个清晰的方法,这里核心是: 通过实现最基本"原子函数", 其他复杂功能都可以通过这些原子函数构建,每一层的函数语义都是单一的,通过层层封装,最终构建一个庞大可控的系统。本原则的主要理念是在实现新功能时能保持已有代码不变,为什么呢,主要是修改存量代码,很可能会影响软件稳定性,很多线上代码跑了好多年了,经历很多轮迭代,各种补丁,如果考虑不全面,很容易带来风险,下图比较形象说明:替换原则是用于预测子类是否与代码兼容,以及是否能与其超类对象协作的一组检查。这一概念在开发程序库和框架时非常重要,因为其中的类将会在他人的代码中使用——你是无法直接访问和修改这些代码的。里氏替换原则的重点在不影响原功能。根据接口隔离原则,你必须将“臃肿”的方法拆分为多个颗粒度更小的具体方法。客户端必须仅实现其实际需要的方法。否则,对于“臃肿”接口的修改可能会导致程序出错,即使客户端根本没有使用修改后的方法。通常在设计软件时,你可以辨别出不同层次的类。低层次的类实现基础操作(例如磁盘操作、传输网络数据和连接数据库等)。高层次类包含复杂业务逻辑以指导低层次类执行特定操作。4 & 经典设计模式这里列举了 22 种设计模式,大致分为三类:创建型模式,结构型模式,行为模式;4.1 创建型模式【创建型模式】提供创建对象的机制,增加已有代码的灵活性和可复用性:4.2 结构型模式【结构型模式】介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效:4.3 行为模式【行为模式】负责对象间的高效沟通和职责委派:推荐一个经典学习网站:https://refactoring.guru4.3 工厂方法模式上面每种模式配有形象图,比如工厂方法模式:还提供对应的设计类图:也提供了对应代码示例:4.4 支持多种语言的实现DESIGN PATTERNS in different programming languages代码在:https://github.com/RefactoringGuru 推荐给大家,拿走别谢❥(^_-);更多信息请参考:《设计模式:可复用面向对象软件的基础》https://refactoring.guru5 & Linux 经典设计模式5.1 内核面向对象设计模式Linux 虽然是面向过程的c语言写成的,但是却可以表达面向对象的思想,Linux 内核大量使用面向对象的编码风格,我们可以从中至少学习到两点:说明在大型软件开发中,OOP 编程思想很重要,和具体语言无关;同时展示了怎么用c语言实现 OOP 编程,值得广大 C 语言开发者学习。我们用例子来说明。【封装】以内核 proto 定义为例:struct proto 定义传输层接口方法和相应成员数据,类似 C++ 的 class 定义;可以根据这个 class 生产很多实例,比如 TCP 实例,可以通过统一接口访问 TCP 实例的方法和数据。【继承】以内核套接字体系为例:基于此继承体系,对于一些接受 struct sock* 形参的接口,就可以直接把上述的子类套接字实例 struct udp_sock* sk 作为实参传进去(当然,这里需要指针强转一次(struct sock*)sk)。这里就是 OOP 中 “is a" 的 public 继承关系,子类对象可以直接作为父类对象使用,并且这种实现只支持单继承。【多态】用 C 实现多态需要自己维护继承关系中的虚函数体系,C++ 有编译器自动生成、维护 vtbl 与vptr。Linux 内核的实现中,将系列函数指针放入结构体,即视其为“虚函数”,亦或是专门定义一个xxx_ops 结构,里面放上一堆函数指针,作为“虚函数表”。仍以套接字体系为例,在基类 sock 中,有协议结构体指针 struct proto *skc_prot; 这个 proto 即可大体上视为一个虚函数表 vtbl,内有具体协议的函数指针,而这个 skc_prot 指针,即可视为虚指针 vptr。在套接字创建时,根据参数中的协议族、协议类型、协议号信息,调用协议族的 create 函数执行创建,绑定具体协议 proto 指针到该 vptr 上,自此实现了静态类型到动态类型的绑定。之后,当调用虚函数时,即可直接通过这些函数指针进行多态的调用 , 比如下面例子 socket 调用 connect 接口:这里第一个参数 sk 即可看做 this 指针,不同 socket 对象,会访问对应协议接口,从而实现多态访问:5.2 list 设计模式list 作为常用数据结构,写代码时候经常会遇到,可以看一下传统 list 设计和内核 list 设计有什么不一样。一般的双向链表一般是如下的结构:有个单独的头结点(head)每个节点(node)除了包含必要的数据之外,还有 2 个指针(pre,next)pre 指针指向前一个节点(node),next 指针指向后一个节点(node)头结点(head)的 pre 指针指向链表的最后一个节点最后一个节点的next指针指向头结点(head)传统 list 如下图:传统的链表不同 node 类型,需要重新定义结构,不够通用化,还需要为 node 实现脱链、入链操作等。我们需要抽象出一个“基类”来实现链表的功能,其他数据结构只需要简单的继承这个链表类就可以了。内核 list 设计如下:链表不是将用户数据保存在链表节点中,而是将链表节点保存在用户数据中链表节点只有 2 个指针(prev 和 next)prev 指针指向前一个节点的链表节点,next 指针指向后一个节点(node)的链表节点如下图:这样设计的好处是链表的节点将独立于用户数据之外,便于把链表的操作独立出来,和具体数据节点无关,这里可能有些人会问,数据节点怎么访问呢?内核通过一个 container_of 的宏从链表节点找到数据节点起始地址:找到数据节点起始地址后,通过数据节点定义就可以访问数据了,内核红黑树 rbtree 也是同样的设计。5.3 设备驱动框架设计模式从 Linux2.6 开始 Linux 加入了一套驱动管理和注册机制 — platform 平台总线驱动模型:当调用 platform_device_register(或 platform_driver_register)注册platform_device(或 platform_driver)时,首先会将其加入 platform 总线上,依次匹配platform 总线上的 platform_driver(或 platform_device),然后调用platform_driver 的 .probe 函数。其中 platform_device 存放设备资源(硬件息息相关代码,易变动),platform_driver 则使用资源(比较稳定的代码),这样当改动硬件资源时,我们的上层使用资源的代码部分几乎可以不用去改动。这里设计通过中间 bus 层,把强耦合 Device 和对应 Driver 进行了解耦隔离,定好 match,probe 等标准通信接口,就可以独立开发,通过总线 bus 进行关联通信,有点类似中介模式。6 & C++ Idioms(设计习语)由于篇幅优先,这里列举一些非常重要且非常实用的 C++ 专有的设计模式。6.1 RAII-Resource Acquisition Is Initialization‘资源获取即初始化‘(简称 RAII)是C++防止内存泄露一个很好解决方案,它结合构造函数和析构函数,把资源生命周期和对象生命周期绑定起来,在构造函数中获取资源(这些错误会引发异常),然后将其释放到析构函数中(永不抛出),并且不需要显式清理,从而防止忘记释放资源;C++ STL 库很多类遵循 RAII 设计原则,比如 std :: string,std :: vector,std :: thread 等。6.2 Policy-based class Design基于策略设计又名 policy-based class design 是一种基于 C++ 计算机程序设计模式,以策略(Policy)为基础,并结合 C++ 的模板元编程。就是将原本复杂的系统,拆解成多个独立运作的“策略类别”,每一组 policy class 都只负责单纯如行为或结构的某一方面。多重继承由于继承自多组 Base Class,故缺乏型别消息,而Templetes基于型别,拥有丰富的型别消息。多重继承容易扩张,而 Templetes 的特化不容易扩张。Policy-Based Class Design 同时使用了 Template 以及 Multiple Inheritance 两项技术,结合两者的优点,看下面例子:ResourceManager 则称为宿主类别(host class),只需要切换不同 Policy Class(ReadPolicy or WritePolicy),就可以得到不同的功能实体。Policy 不一定要被宿主继承,只需要用委托完成这一工作。但 policies 必须遵守一个隐含的 constraint,接口必须一样,故参数不能有巨大改变,policy 的一个重要的特征是,宿主类别经常(并不一定要)使用多重继承的机制去使用多个 policy classes。因此在进行 policy 拆解时,必须要尽可能达成正交分解,policy 之间最好彼此独立运作,不相互影响。6.3 Pimpl - Pointer to implementationPimpl 是一种广泛使用的削减编译依赖项的技术,看下面例子可能就明白了:因为 Widget 的成员变量有 std::string,std::vector 和 Gadget,那么这些类型的头文件在Widget 编译时必须出现,这意味 Widget 的用户必须包含 “gadget.h”。这些增加的头文件会增加 Widget 用户的编译时间,而且这使得用户依赖于这些头文件,即如果某个头文件的内容被改变了,Widget 的用户就要重新编译。标准库头文件不会经常改变,但是 “gadget.h” 可能会经常修改。所以需要 Pimp 技术来消除这种变化影响--隔离变化;这样 Widget 头文件里面就不需要包含 “gadget.h” 文件了,再 CPP 文件中再声明具体的类型:在这里,我展示了 “#include” 指令,只为了说明所有对头文件的依赖(即 std::string,std::vector 和 Gadget)依然存在。不过呢,依赖已经从 “widget.h”(Widget 用户可见的和使用的)转移到“widget.cpp”(只有 Widget 的实现者才能看见和使用),这样就把 widget 头文件变化影响隔离在内部实现中,对外接口不变,这里就体会到这种设计模式的好处。6.4 CRTP -The curiously recurring template patternCRTP (奇异递归模板模式)是一种在编译期实现多态方法,是对运行时多态一种优化,多态是个很好的特性,但是动态绑定比较慢,因为要查虚函数表。而使用 CRTP,完全消除了动态绑定,降低了继承带来的虚函数表查询开销。CRTP 包含:从模板类继承;使用派生类本身作为基类的模板参数。这样做的目的是在基类中使用派生类。从基础对象的角度来看,派生对象本身就是对象,但是是向下转换的对象。因此,基类可以通过将 static_cast 自身放入派生类来访问派生类。总结为什么要掌握设计模式,软件危机带来刚性要求,设计模式提倡的高内聚,低耦合,代码复用,可扩展性等思想,可以给我们软件设计带来一些思考,有了思考,就会产生一些积极变化;理解设计模式前提,是要理解背后的 设计原则,这是整个设计模式的精华;经典的设计模式包含 22 种设计模式(没有 解释器模式,日常开发中,很少使用),大致分为三类:创建型模式,结构型模式,行为模式;Linux 系统里面包含大量设计模式思想,面向对象设计,List/Rbtree 抽象设计,驱动框架 bus 总线解耦设计,都值得我们学习;每种编程语言都会有一些独特特殊习惯用法,Java 的 MVC,Golang 的对象池模式(Object Pool)等,文中列举的 C++ 一些常见的惯用法 RAII,Policy-based Design,Pimpl,CRTP等,对 C++ 开发来说,了解和掌握他们,对于特定场景问题多了一些好的解决方案;设计模式是银弹吗?不是,就像软件工程也不是银弹一样,这些都只是工具,关键还是看是否真正理解其背后反射出的设计精髓,我们需要多一些批判性的思考,没有绝对好坏,软件设计的最终方案很多时候都是权衡(trade-off)结果,但我们的长期目标始终没有变化。【转载申明】本文转载自 =》https://mp.weixin.qq.com/s/qI2ZmTv77E7k-3MWfmdeGg大家好,我是 Alex,希望你我都是一个勤勉的人,依靠自己的力量和毅力,从一堆纷繁复杂寻觅到了真正的知识,写文章确实比较累,如果可以帮助到你,那也是值得的,希望大家多多【点赞、在看、转发】,你的举手之劳都是我坚持写作的动力,万分感谢!
Linux Container 简介Linux Container (容器)是与系统其他部分隔离开的一系列进程。运行这些进程所需的所有文件都由另一个镜像提供,这意味着从开发到测试再到生产的整个过程中,Linux 容器都具有可移植性和一致性。因而,相对于依赖重复传统测试环境的开发渠道,容器的运行速度要快得多。容器比较普遍也易于使用,因此也成了 IT 安全方面的重要组成部分。容器可以确保您的应用拥有必需的库、依赖项和文件,让您可以在生产中自如地迁移这些应用,无需担心会出现任何负面影响。实际上,您可以将容器镜像中的内容,视为 Linux 发行版的一个安装实例,因为其中完整包含 RPM 软件包、配置文件等内容。但是,安装容器镜像发行版,要比安装新的操作系统副本容易得多。这样可以避免危机,做到皆大欢喜。在需要很高的可移植性、可配置性和隔离的情况下,我们可以通过不同的方式,利用 Linux 容器解决难题。Linux 容器的价值在于,它能加速开发并满足新出现的业务需求,当然,选择正确的容器平台也同样重要。容器与虚拟化的关联容器就是虚拟化吗?如何区分容器与虚拟化?回答是:不完全如此。更确切的说法应该是:两者为互补关系。我们用一种简单方式来思考一下:虚拟化使得您的操作系统(Windows 或 Linux)可同时在单个硬件系统上运行。容器则可共享同一个操作系统内核,将应用进程与系统其他部分隔离开。例如:ARM Linux 系统运行 ARM Linux 容器;x86 Linux 系统运行 x86 Linux 容器;x86 Windows 系统运行 x86 Windows 容器;Linux 容器具有极佳的可移植性,但前提是它们必须与底层系统兼容。Linux Container 意味着什么?虚拟化会使用虚拟机监控程序模拟硬件,从而使多个操作系统能够并行运行。但这不如容器轻便。事实上,在仅拥有容量有限的有限资源时,您需要能够可以进行密集部署的轻量级应用。Linux 容器在本机操作系统上运行,与所有容器共享该操作系统,因此应用和服务能够保持轻巧,并行化快速运行。Linux Container 是我们开发、部署和管理应用方式的又一次飞跃。Linux 容器镜像提供了可移植性和版本控制,确保能够在开发人员的笔记本电脑上运行的应用,同样也能在生产环境中正常运行。相较于虚拟机,Linux 容器在运行时所占用的资源更少,使用的是标准接口(启动、停止、环境变量等),并会与应用隔离开;此外,作为(包含多个容器)大型应用的一部分时更加易于管理,而且这些多容器应用可以跨多个云环境进行编排。什么是 LXC?Infrastructure for container projects,容器项目即基础设施。作为一个开源容器平台,Linux 容器项目(LXC)提供了一组工具、模板、库和语言绑定。LXC 采用简单的命令行界面,可改善容器启动时的用户体验。LXC 提供了一个操作系统级的虚拟化环境,可在许多基于 Linux 的系统上安装。在 Linux 发行版中,可能会通过其软件包存储库来提供 LXC。Docker 简介Docker 是一个开源的应用容器引擎,属于 Linux 容器的一种封装,Docker 提供简单易用的容器使用接口,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上。容器是完全使用沙箱机制,相互之间不会有任何接口。Docker 是目前最流行的 Linux 容器解决方案,即使 Docker 是目前管理 Linux 容器的一个非常方便的工具,但它也有两个缺点:Docker 需要在你的系统上运行一个守护进程。Docker 是以 root 身份在你的系统上运行该守护程序。这些缺点的存在可能有一定的安全隐患,为了解决这些问题,下一代容器化工具 Podman 出现了 。Podman 介绍什么是 Podman?podman(Pod Manager)是一个由 RedHat 公司推出的容器管理工具,它的定位就是 docker 的替代品,在使用上与docker的体验类似。 podman 源于 CRI-O 项目,可以直接访问 OCI 的实现(如 runC),流程比 docker 要短。 二者主要的区别在于,podman 是一个开源的产品;而 docker 已经是商业化的产品。简而言之:alias docker = podmanPodman 是一个开源的容器管理工具,其可在大多数 Linux 平台上使用,它是一种无守护程序的容器引擎,用于在 Linux 系统上开发,管理和运行任何符合 Open Container Initiative(OCI) 标准的容器和容器镜像,提供了一个与 Docker 兼容的命令行前端,该前端可以简单地为 Docker CLI 别名,即 “alias docker = podman”。Podman 控制下的容器既可以由 root 用户运行,也可以由非特权用户运行,这个是 Podman 与 Docker 最大的差别之一。关于 Podman 更多信息:Podman 官网地址:https://podman.io/Podman 项目地址:https://github.com/containers/podmanDocker 与 Podman 的区别用一张图形象直观的对比,结构如下:后续内容:容器由 Root 运行与非 Root 用户运行的区别;Podman 的安装;Podman 的基本使用;参考:Linux 容器是什么?什么是 Docker?
AntDeploy 工具功能简介支持 docker一键部署(支持.net core、.net6+)支持 iis 一键部署(支持 .net core 和 framework)支持 windows 服务一键部署(支持 .net core/.net6+ 和 framework)支持 linux 服务一键部署(支持 .net core/.net6+)(支持增量发布)(支持一键回滚)(支持点火)(支持选择特定文件发布)(支持查看发布记录)支持脱离 Visual Studio 独立使用(跨平台支持 windows 系统和 mac 系统)支持 Agent 批量更新资源地址:github源码:AntDeploy,https://github.com/yuzd/AntDeployVisual Studio插件(同步化):AntDeploy - Visual Studio MarketplaceVisual Studio插件(异步化):AntDeployVsix - Visual Studio Marketplace(如要安装Windows服务器上agent服务)How to install:https://github.com/yuzd/AntDeployAgent/issues/1为什么发布到【windows服务】或【linux服务】需要服务器上要装agent?:https://github.com/yuzd/AntDeployAgent/issues/7如何脱离Visual Studio独立使用:https://github.com/yuzd/AntDeployAgent/issues/18其他教程(看完原理使用更上手):https://github.com/yuzd/AntDeployAgent/issuesAntDeploy 应用示例使用 AntDeploy 一键制作镜像,并上传腾讯云私有镜像仓库。演示环境说明操作系统:Windows 10 专业工作站版 x64,内核版本号 2004Microsoft Visual Studio Enterprise 2019 版本 16.8.2本地Hyper-V安装ubuntu-20.04虚拟机(操作vm终端vs code)AntDeploy 离线版 Version:7.28注意事项:1、确保本地配置的vm可以访问外网,并且与宿主机相互通信,查看网络工具# sudo apt install net-tools2、确保 vm 安装 docker# sudo apt update & sudo apt install docker.io3、确保 vm 安装 unzip# sudo apt install unzip4、vs code 安装插件(可选配置:Remote - WSL,Docker ),通过 ssh 连接 vm# ssh chait@172.29.161.146 提示输入密码即可5、vm 安装 docker 并配置国内镜像源:阿里镜像源配置=》ubuntu镜像-ubuntu下载地址-ubuntu安装教程-阿里巴巴开源镜像站 (aliyun.com) 配置对应 linux 版本即可;确保 vm(基于 ubuntu)开启 22 端口,操作如下:# 1.查看 Ubuntu 的 IP 地址 ifconfig # 2.执行后,可能该项的服务功能未安装,根据提示执行,安装网络工具 sudo apt install net-tools安装后,想通过 vs code/pwsh ssh 远程工具连接的时候发现连接失败。此时,查看Ubuntu关于22的端口,执行命令如下:netstat -ntlp | grep 22 # 参数解释: -n 不以进程的服务名称,以端口号(port number)显示 -t 列出tcp网络封包的信息 -l 列出目前正在网络监听(listen)服务 -p 列出该网络服务的进程此时若发现无任何关于 22 端口的进程内容,执行以下命令:# 安装 openssh-server sudo apt install openssh-server # 安装 ufw 防火墙 sudo apt install ufw # 启动 ufw 防火墙 sudo ufw enable # ufw 开放 22 端口 sudo ufw allow 22vm 中安装好 docker 后授权非 root 账户使用,配置步骤如下:# 1.创建docker组 sudo groupadd docker # 2.将当前用户加入docker组 sudo gpasswd -a ${USER} docker # 3.重新启动docker服务 sudo service docker restart sudo systemctl restart docker # 4.当前用户退出系统重新登陆 su root su chait # 5.运行docker命令,查看是否可使用 docker ps 或 docker images如果出现失败,可以重启下 vm,再次测试。重启命令reboot shutdown -r now 立刻重启 shutdown -r 10 过10分钟自动重启 shutdown -r 20:35 在时间为20:35时候重启关机命令halt 立刻关机(一般加-p 关闭电源) poweroff 立刻关机 shutdown -h now 立刻关机 shutdown -h 10 10分钟后自动关机 # 如果是通过 `shutdown` 命令设置关机的话,可以用 `shutdown -c` 命令取消关机。AntDeploy 镜像制作2.1 准备 .net 相关镜像准备 .net 相关镜像,国内下载镜像可能会失败(受限于网络原因);Featured Tags7.0 (Standard Support)docker pull mcr.microsoft.com/dotnet/sdk:7.06.0 (Long-Term Support)docker pull mcr.microsoft.com/dotnet/sdk:6.0Featured Reposdotnet/sdk: .NET SDKdotnet/aspnet: ASP.NET Core Runtimedotnet/runtime: .NET Runtimedotnet/runtime-deps: .NET Runtime Dependenciesdotnet/monitor: .NET Monitor Tooldotnet/samples: .NET Samples关于 .NET 的 docker 镜像信息请查看,https://hub.docker.com/_/microsoft-dotnet/按需拉取镜像即可,此处演示只需如下镜像:.NET SDK & ASP.NET Core Runtime# ASP.NET Core Runtime docker pull mcr.microsoft.com/dotnet/aspnet:6.0 docker pull mcr.microsoft.com/dotnet/aspnet:6.0-buster-slim # .NET SDK docker pull mcr.microsoft.com/dotnet/sdk:6.0-buster-slimFeatured Tags7.0 (Standard Support)docker pull mcr.microsoft.com/dotnet/aspnet:7.06.0 (Long-Term Support)docker pull mcr.microsoft.com/dotnet/aspnet:6.0腾讯云平台准备 Docker 私有镜像仓库,确保 vm 和宿主机可以正常登录访问:#登录腾讯云docker registry sudo docker login --username=[user] ccr.ccs.tencentyun.com #提示输入对应的密码即可 #从registry拉取镜像 sudo docker pull ccr.ccs.tencentyun.com/sws-center-ol-service-dev/rg-sws-center-ol-dev-2069:[tag] #将镜像推送到registry sudo docker login --username=[user] ccr.ccs.tencentyun.com sudo docker tag [ImageId] ccr.ccs.tencentyun.com/sws-center-ol-service-dev/rg-sws-center-ol-dev-2069:[tag] sudo docker push ccr.ccs.tencentyun.com/sws-center-ol-service-dev/rg-sws-center-ol-dev-2069:[tag]腾讯云平台 Docker 私有镜像仓库已经准备如下镜像:本地拉取镜像的时候,可以配置国内镜像源:本地 Windows PowerShell/PowerShell 下载镜像,现在本机上面安装个 Docker for Windows 客户端工具,然后配置镜像源地址。2.2 实现目标使用 vs2022 创建对应的 b/s 应用程序,此处演示的是【NetService-Jwt】;使用 AntDeploy 工具一键制作镜像(基于 .net6 构建的应用镜像)并发布到腾讯的 docker 私有镜像仓库;部署准备,腾讯云 TKE (基于 k8s&containerd)平台从私有镜像仓库拉取镜像实施资源部署;1、配置 AntDeploy.json,界面操作工具配置好后得到如下配置信息;{ "Env": [ "Name": "docker-ubuntu-20.04", "ServerList": [], "LinuxServerList": [ "UserName": "chait", "Pwd": "35FE3BA71BD99AF92DB2A32F980ECDAE", "Host": "172.29.161.146", "NickName": "docker", "IIsFireUrl": null, "DockerFireUrl": "", "WindowsServiceFireUrl": null, "LinuxServiceFireUrl": null "IgnoreList": [], "WindowsBackUpIgnoreList": [] "IIsConfig": { "SdkType": null, "WebSiteName": "", "LastEnvName": null, "EnvPairList": [ "EnvName": "docker-ubuntu-20.04", "ConfigName": "", "LinuxEnvParam": null, "DockerPort": null, "DockerEnvName": null, "DockerVolume": null, "DockerOther": null "WindowsServiveConfig": { "ServiceName": "", "SdkType": null, "LastEnvName": null, "EnvPairList": [ "EnvName": "docker-ubuntu-20.04", "ConfigName": "", "LinuxEnvParam": null, "DockerPort": null, "DockerEnvName": null, "DockerVolume": null, "DockerOther": null "LinuxServiveConfig": { "ServiceName": "", "EnvParam": "", "LastEnvName": null, "EnvPairList": [ "EnvName": "docker-ubuntu-20.04", "ConfigName": "", "LinuxEnvParam": null, "DockerPort": null, "DockerEnvName": null, "DockerVolume": null, "DockerOther": null "DockerConfig": { "Prot": "", "AspNetCoreEnv": "", "LastEnvName": "docker-ubuntu-20.04", "RemoveDaysFromPublished": "10", "Volume": "", "Other": "", "EnvPairList": [ "EnvName": "docker-ubuntu-20.04", "ConfigName": null, "LinuxEnvParam": null, "DockerPort": "", "DockerEnvName": "", "DockerVolume": "", "DockerOther": "" }2、项目添加 Dockerfile 文件,vs2022 鼠标右键选择【添加】=》【Docker支持】选择 Linux平台;3、本地 vm 事先下载好 .net6 相关的基础镜像,准备基础镜像信息如下:修改默认创建的 Dockerfile 文件,并在 vs 2022 里面设置文件属性为【始终赋值】,此处如果不写Dockerfile 文件,工具会默认生成一个;此时基本环境都准好了,接下来我们使用 AntDeploy 工具离线版(V7.28)选择对应的项目 .csproj 文件,配置访问 vm 通信测试 Ok,然后配置腾讯云 Docker 私有镜像仓库,点击一键发布就完成了。此处注意填写私有镜像仓库名称,这里吐槽一下腾讯云基本信息描述(表达意思误导人)完整发布日志信息:发布工具版本 AntDeploy-v7.29 版本,构建镜像的 Dcokerfile 文件受注释影响,待后续优化;16:24:39|INFO|The Porject ENTRYPOINT name:NetServices.Jwt.dll,DotNetSDK.Version:5.0 16:24:39|INFO|-----------------Start publish[Ver:7.29]----------------- 16:24:39|INFO|CurrentProjectFolder: file://G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt#link3 16:24:39|INFO|current project Path:G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\NetServices.Jwt.csproj 16:24:39|INFO|dotnet publish "G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\NetServices.Jwt.csproj" -c Release -o "G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\bin\Release\deploy_docker\docker-ubuntu-20.04\" 16:24:40|INFO| Determining projects to restore... 16:24:40|INFO| Determining projects to restore... 16:24:42|INFO| All projects are up-to-date for restore. 16:24:42|INFO| All projects are up-to-date for restore. 16:24:43|INFO| NetServices.Helper.Cache -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.Cache\bin\Release\net5.0\NetServices.Helper.Cache.dll 16:24:43|INFO| NetServices.Helper.RabbitMQ -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.RabbitMQ\bin\Release\net5.0\NetServices.Helper.RabbitMQ.dll 16:24:43|INFO| NetServices.Helper.RabbitMQ -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.RabbitMQ\bin\Release\net5.0\NetServices.Helper.RabbitMQ.dll 16:24:43|INFO| NetServices.Helper.Base -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.Base\bin\Release\net5.0\NetServices.Helper.Base.dll 16:24:43|INFO| NetServices.Helper.Base -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.Base\bin\Release\net5.0\NetServices.Helper.Base.dll 16:24:43|INFO| NetServices.Helper.RestTemplate -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.RestTemplate\bin\Release\net5.0\NetServices.Helper.RestTemplate.dll 16:24:43|INFO| NetServices.Helper.RestTemplate -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.RestTemplate\bin\Release\net5.0\NetServices.Helper.RestTemplate.dll 16:24:44|INFO| NetServices.Helper.Database -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.Database\bin\Release\net5.0\NetServices.Helper.Database.dll 16:24:44|INFO| NetServices.Helper.Database -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.Database\bin\Release\net5.0\NetServices.Helper.Database.dll 16:24:44|INFO| NetServices.Helper.Log -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.Log\bin\Release\net5.0\NetServices.Helper.Log.dll 16:24:44|INFO| NetServices.Helper.Log -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Helper.Log\bin\Release\net5.0\NetServices.Helper.Log.dll 16:24:45|INFO| NetServices.Jwt -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\bin\Release\net5.0\NetServices.Jwt.dll 16:24:45|INFO| NetServices.Jwt -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\bin\Release\net5.0\NetServices.Jwt.dll 16:24:46|INFO| NetServices.Jwt -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\bin\Release\deploy_docker\docker-ubuntu-20.04\ 16:24:46|INFO| NetServices.Jwt -> G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\bin\Release\deploy_docker\docker-ubuntu-20.04\ 16:24:46|INFO|publish success ==> file://G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\bin\Release\deploy_docker\docker-ubuntu-20.04#link26 16:24:46|INFO|-----------------Start package----------------- 16:24:46|INFO|package ignoreList Count:0 16:24:46|INFO|Find Dockerfile In Package: Dockerfile 16:24:49|INFO|package success,package size:14M 16:24:49|INFO|-----------------Deploy Start----------------- 16:24:49|INFO|【Server】ssh connecting 172.27.94.126... 16:24:50|INFO|【Server】Connected to chait@172.27.94.126:22 via SSH 16:24:50|INFO|【Server】Changed directory to antdeploy/netservicesjwt/20201214162449/ 16:24:50|INFO|【Server】uploaded 9 % 16:24:50|INFO|【Server】uploaded 21 % 16:24:50|INFO|【Server】uploaded 32 % 16:24:51|INFO|【Server】uploaded 44 % 16:24:51|INFO|【Server】uploaded 59 % 16:24:51|INFO|【Server】uploaded 71 % 16:24:51|INFO|【Server】uploaded 83 % 16:24:51|INFO|【Server】uploaded 94 % 16:24:51|INFO|【Server】uploaded 100 % 16:24:52|INFO|【Server】unzip -o -q antdeploy/netservicesjwt/20201214162449/publish.zip -d publish/ 16:24:52|INFO|【Server】unzip success: antdeploy/netservicesjwt/20201214162449/publish/ 16:24:52|INFO|【Server】Start Copy Files From [antdeploy/netservicesjwt/20201214162449/publish/] To [antdeploy/netservicesjwt/deploy/] 16:24:52|INFO|【Server】Success Copy Files From [antdeploy/netservicesjwt/20201214162449/publish/] To [antdeploy/netservicesjwt/deploy/] 16:24:52|INFO|【Server】Changed directory to antdeploy/netservicesjwt/deploy/ 16:24:52|WARN|【Server】Volume in dockerFile is not defined 16:24:52|WARN|【Server】Docker Run Other Args in dockerFile is not defined 16:24:52|INFO|【Server】Add EXPOSE 5000 to dockerFile : 【antdeploy/netservicesjwt/deploy/Dockerfile】 16:24:52|INFO|【Server】Update DockerFile 【 \cp -rf antdeploy/netservicesjwt/deploy/Dockerfile antdeploy/netservicesjwt/20201214162449/publish/】 16:24:52|WARN|【Server】--name in Other Args is not defined 16:24:52|INFO|【Server】 docker build --no-cache --rm -t netservicesjwt:20201214162449 -f antdeploy/netservicesjwt/deploy/Dockerfile antdeploy/netservicesjwt/deploy/ 16:24:53|INFO|【Server】Sending build context to Docker daemon 43.74MB 16:24:53|INFO|【Server】Step 1/6 : FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim 16:24:53|INFO|【Server】 ---> 1ad38cdee8b3 16:24:53|INFO|【Server】Step 2/6 : COPY . /publish 16:24:57|INFO|【Server】 ---> 89c1e9311f25 16:24:57|INFO|【Server】Step 3/6 : WORKDIR /publish 16:24:58|INFO|【Server】 ---> Running in ce10c33d6846 16:25:00|INFO|【Server】Removing intermediate container ce10c33d6846 16:25:00|INFO|【Server】 ---> ab9847d605e3 16:25:00|INFO|【Server】Step 4/6 : EXPOSE 8002 16:25:02|INFO|【Server】 ---> Running in e8411eb8a13e 16:25:03|INFO|【Server】Removing intermediate container e8411eb8a13e 16:25:03|INFO|【Server】 ---> c2e707157198 16:25:03|INFO|【Server】Step 5/6 : EXPOSE 5000 16:25:05|INFO|【Server】 ---> Running in 38a502ae6f0b 16:25:07|INFO|【Server】Removing intermediate container 38a502ae6f0b 16:25:07|INFO|【Server】 ---> ae6797201035 16:25:07|INFO|【Server】Step 6/6 : ENTRYPOINT ["dotnet", "NetServices.Jwt.dll"] 16:25:08|INFO|【Server】 ---> Running in b10cbf0cad95 16:25:10|INFO|【Server】Removing intermediate container b10cbf0cad95 16:25:10|INFO|【Server】 ---> 8eb53a301a0f 16:25:10|INFO|【Server】Successfully built 8eb53a301a0f 16:25:11|INFO|【Server】Successfully tagged netservicesjwt:20201214162449 16:25:11|WARN|【Server】ignore docker run 16:25:11|WARN|【Server】[upload image] - set -e; docker login -u [user] -p [pwd] ccr.ccs.tencentyun.com; docker tag 8eb53a301a0f ccr.ccs.tencentyun.com/sws-center-ol-service-dev/rg-sws-center-ol-dev-2069:jwt-v1.0.0; docker push ccr.ccs.tencentyun.com/sws-center-ol-service-dev/rg-sws-center-ol-dev-2069:jwt-v1.0.0 16:25:11|WARN|【Server】[upload image] - Login Succeeded 16:25:12|WARN|【Server】[upload image] - The push refers to repository [ccr.ccs.tencentyun.com/sws-center-ol-service-dev/rg-sws-center-ol-dev-2069] 16:25:12|WARN|【Server】[upload image] - 04b1dc15379b: Preparing 16:25:12|WARN|【Server】[upload image] - 064d1568f66a: Preparing 16:25:12|WARN|【Server】[upload image] - 8c8ce230525b: Preparing 16:25:12|WARN|【Server】[upload image] - ac1439b965df: Preparing 16:25:12|WARN|【Server】[upload image] - b22af9287e60: Preparing 16:25:12|WARN|【Server】[upload image] - f5600c6330da: Preparing 16:25:12|WARN|【Server】[upload image] - f5600c6330da: Waiting 16:25:12|WARN|【Server】[upload image] - 8c8ce230525b: 16:25:12|WARN|【Server】[upload image] - Layer already exists 16:25:12|WARN|【Server】[upload image] - 064d1568f66a: 16:25:12|WARN|【Server】[upload image] - Layer already exists 16:25:12|WARN|【Server】[upload image] - b22af9287e60: 16:25:12|WARN|【Server】[upload image] - Layer already exists 16:25:13|WARN|【Server】[upload image] - ac1439b965df: 16:25:13|WARN|【Server】[upload image] - Layer already exists 16:25:13|WARN|【Server】[upload image] - f5600c6330da: 16:25:13|WARN|【Server】[upload image] - Layer already exists 16:25:18|WARN|【Server】[upload image] - 04b1dc15379b: 16:25:18|WARN|【Server】[upload image] - Pushed 16:25:21|WARN|【Server】[upload image] - jwt-v1.0.0: digest: sha256:1c165fdc9dbd311692000812771e39c97397dee747001887b032ad127abe05c5 size: 1583 16:25:21|INFO|【Server】[upload image] - Success 16:25:21|INFO|publish Host: 172.27.94.126【docker】 End 16:25:21|INFO|Deploy Version:20201214162449 16:25:21|INFO|local publish folder ==> file://G:\SvnProject\HCIMS_V3.1_Online\Project\Services\SwsXytxMsServices\branches\v1.0\NetServices.Jwt\bin\Release\deploy_docker\docker-ubuntu-20.04#link105 16:25:21|INFO|-----------------Deploy End,[Total]:1,[Fail]:0----------------- 最后查看腾讯云平台私有镜像仓库,显示信息如下:以上就是完整的发布构建镜像过程,有帮助到小伙伴的欢迎点赞分享,在这里也由衷感谢工具原创者不吝赐教的指点交流!
什么是 Docker 集群?Swarm 将一个或多个 Docker 【节点】组织起来,使得用户能够以集群方式管理它们;这些节点可以是物理服务器、虚拟机、树莓派(Raspberry Pi)或云实例;节点会被配置为管理节点(Manager)或工作节点(Worker)。管理节点负责集群控制面(Control Plane),进行诸如监控集群状态、分发任务至工作节点等操作。工作节点接收来自管理节点的任务并执行。【集群搭建的注意事项】安全性:要求所有节点通过可靠的网络相连;搭建 Swarm 集群时将不可避免地使用 TLS(安全传输层协议),因为它被 Swarm 紧密集成。在安全意识日盛的今天,这样的工具值得大力推广。Swarm 使用 TLS 进行通信加密、节点认证和角色授权。自动密钥轮换(Automatic Key Rotation)更是锦上添花!其在后台默默进行,用户甚至感知不到这一功能的存在。为什么要使用 Docker 集群?docker 在单机(单节点)环节下面,部署两个容器分别是 nginx 和 netcore ,假如 docker 主机宕机,意味着其上面的容器服务也随之不可用,再从客户端角度看,客户端的访问数量是不确定性的,随时都可能增多,而相对于单节点的 docker 主机,提供服务的负载能力也有上限,不能满足高并发场景,最终导致容器服务不可用,总结为以下两点:服务器角度:单节点 docker 主机不能提供服务的高可用性;客户端角度:单节点 docker 主机不能满足高并发服务访问;Docker 集群解决什么问题?针对以上面临的问题,因此需要对单节点 docker 主机进行集群,集群展示如下:Docker 集群解决的问题(提供服务的高可用):提高并发量;解决单点故障问题;Docker Swarm 集群工具简介Swarm 是 Dcoker 官方提供的一款集群管理工具,其主要作用是把若干台 Docker 主机抽象为一个整体,并且通过一个入口统一管理这些 Docker 主机上的各种 Docker 资源;Swarm 和 Kubernetes 比较类似,但是更加轻,具有的功能也较 kubernetes 更少一些;Docker Swarm 包含两方面:1.企业级的 Docker 安全集群 & 2.微服务应用编排引擎;Swarm 的配置和状态信息保存在一套位于所有管理节点上的分布式 etcd 数据库中。该数据库运行于内存中,并保持数据的最新状态。关于该数据库最棒的是,它几乎不需要任何配置,作为 Swarm 的一部分被安装,无须管理;Swarm 默认内置有加密的分布式集群存储(encrypted distributed cluster store)、加密网络(Encrypted Network)、公用TLS(Mutual TLS)、安全集群接入令牌 Secure Cluster Join Token)以及一套简化数字证书管理的 PKI(Public Key Infrastructure)。我们可以自如地添加或删除节点;Swarm 提供了一套丰富的 API 使得部署和管理复杂的微服务应用变得更加方便。通过将应用定义在声明式配置文件中,就可以使用原生的 Docker 命令完成部署。甚至还可以执行滚动升级、回滚以及扩缩容操作;关于应用编排,Swarm 中的最小调度单元是服务;它是随 Swarm 引入的,在 API 中是一个新的对象元素,它基于容器封装了一些高级特性,是一个更高层次的概念。当容器被封装在一个服务中时,我们称之为一个任务或一个副本,服务中增加了诸如扩缩容、滚动升级以及简单回滚等特性。Swarm 如何管理 Docker 集群?以上环节中,docker swarm 通过角色(manager-worker)配置实现 docker 主机集群,同样管理节点(manager)也可以多个节点集群。manager(管理节点)集群配置;容器服务管理;负载均衡;集群其他配置;worker(工作节点)提供容器服务说明:类似 redis 中的主(mastere)从(slaver)配置;如何搭建 Swarm 集群?通过 service,task,stack 去配置容器;service =》docker 不同主机(工作节点)容器的集合;task =》供 service 启动集群环境容器;stack =》多个 service 的集合;Swarm 集群准备两台云主机或 VM,分别安装 docker;docker-swarm;备注:在哪里操作哪个就是管理节点,所有操作只需在管理节点处理,工作节点无需担心;上面我们对 docker 集群的原理做了简单的介绍,接下来我们实践操作,【docker-swarm】集群的搭建过程;Swarm 集群搭建过程首先初始化一个集群工作节点 =》docker swarm init --advertise-addr [localhost] 默认为管理节点,如果该节点已经存在,删除节点 =》docker swarm leave -f另外一台 docker 主机加入集群节点(work工作节点)查看节点详细列表信息 =》docker --info集群环境中如何配置 services(容器)集合镜像仓库准备所需的部署的镜像,比如:nginx 和 netcore创建 service =》docker service create --replicas 1 --name netcoreservice 参数 --replicas 指定集群数量,--name 为service取名;查看【rmcoreservice】在哪个 docker 主机节点运行 =》docker service ps rmcoreservice查看容器列表信息 =》docker container ls -a删除 rmcoreservice 服务 =》docker service rm rmcoreservice备注:所有 service 的操作,全在管理节点操作,管理节点删除service 集群的工作节点也随之删除,证明:在 worker 工作节点查看 service 列表=》docker serice ls 工作节点无权操作,显示如下:从镜像 rmcore 创建服务,指定两个工作节点,并暴露服务端口(映射) =》docker service create --replicas 2 --name rmcoreservice --publish 6066:80 rmcore集群中 stack 创建两个 servicedeploy 可选参数配置详解endpoint_mode:访问集群服务的方式。 endpoint_mode: vip # Docker 集群服务一个对外的虚拟 ip。所有的请求都会通过这个虚拟 ip 到达集群服务内部的机器。 endpoint_mode: dnsrr # DNS 轮询(DNSRR)。所有的请求会自动轮询获取到集群 ip 列表中的一个 ip 地址。 labels:在服务上设置标签。可以用容器上的 labels(跟 deploy 同级的配置) 覆盖 deploy 下的 labels。 mode:指定服务提供的模式。 replicated:复制服务,复制指定服务到集群的机器上。 global:全局服务,服务将部署至集群的每个节点。 replicas:mode 为 replicated 时,需要使用此参数配置具体运行的节点数量。 resources:配置服务器资源使用的限制,例如上例子,配置 redis 集群运行需要的 cpu 的百分比 和 内存的占用。避免占用资源过高出现异常。 restart_policy:配置如何在退出容器时重新启动容器。 condition:可选 none,on-failure 或者 any(默认值:any)。 delay:设置多久之后重启(默认值:0)。 max_attempts:尝试重新启动容器的次数,超出次数,则不再尝试(默认值:一直重试)。 window:设置容器重启超时时间(默认值:0)。 rollback_config:配置在更新失败的情况下应如何回滚服务。 parallelism:一次要回滚的容器数。如果设置为0,则所有容器将同时回滚。 delay:每个容器组回滚之间等待的时间(默认为0s)。 failure_action:如果回滚失败,该怎么办。其中一个 continue 或者 pause(默认pause)。 monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为0s)。 max_failure_ratio:在回滚期间可以容忍的故障率(默认为0)。 order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认 stop-first )。 update_config:配置应如何更新服务,对于配置滚动更新很有用。 parallelism:一次更新的容器数。 delay:在更新一组容器之间等待的时间。 failure_action:如果更新失败,该怎么办。其中一个 continue,rollback 或者pause (默认:pause)。 monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为0s)。 max_failure_ratio:在更新过程中可以容忍的故障率。 order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认stop-first)管理节点创建 stack 目录 =》mkdir stack查看当前目录列表,切换到 compose如果管理节点不存在 docker-compose.yaml 文件就创建一个 =》 vi docker-compose.yml 创建文件并且进入编辑管理节点的 stack 目录中创建的 docker-compose.yaml 文件配置编辑信息如下:Stack 命令 =》docker stack --help通过 docker-compose.yml 中的 deploy 配置信息,使用 stack 管理命令启动 =》docker stack deploy -c docker-compose.yml rmstack 创建的stack标识为rmstack查看 stack 创建的列表目录信息 =》docker stack ls查看 stack 中创建的 service =》docker service ls 创建的服务 rmstack_nginx, rmstack_rmcore再次查看容器列表目录 =》docker container ls以上过程就完成 stack 的集群配置;删除 stack 配置 =》docker stack rm rmstack注意:管理节点创建和删除集群操作,工作节点也随之删除。docker-swarm 命令集合查看 swarm 工具管理命令 =》docker swarm --help指令说明ca显示CA证书init初始化swarm 集群,并创建管理节点join将其他docker节点加入集群,创建工作节点,被管理节点管理join-token将其他docker节点加入集群,创建工作节点,被管理节点管理[这个是管理节点令牌]leave离开集群unlock打开集群unlock-key管理解锁键keyupdate更新群集群Swarm 缺陷无法监控某一个docker节点(主机)=》解决方案 k8s安装docker主机耗时;每安一次OS环境都需要对应的配置一次docker主机,比如:10台linux系统,需要安装10个docker主机 =》docker-machine(关键),参考:https://www.cnblogs.com/sparkdev/p/7066789.html集群容器如何做数据持久化? =》volums 数据卷(关键)当 service 的 work 节点容器宕机,无法监控和感知;针对以上【swarm缺陷】的解决方案 =》【k8s/Kubermetes】
什么是容器编排?由于大量的应用容器化,部署和管理繁多的服务变得越来越困难且需要耗费大量的资源,而 Docker Compose 正好能解决 Docker 单节点上以单引擎模式(Single-Engine Mode)进行多容器应用的部署和管理问题,这一过程就叫容器编排;Docker Compose 并不是通过脚本和各种冗长的 docker 命令来将应用组件组织起来,而是通过一个【声明式的配置文件】描述整个应用,从而使用一条命令完成部署。容器编排有以下四个特征(批量操作):容器同时启动;容器同时关闭;镜像和容器同时删除;镜像和容器同时构建;目前主流容器编排工具有哪些?docker-compose;docker-swarm;Kubernetes/k8s(当下行业标准);其他工具自行查看资料;docker-compose 简介Compose 的前身是 Fig,Fig 被 Docker 收购之后正式更名为 Compose,Compose 向下兼容 Fig(Fig 是一个由 Orchard 公司开发的强有力的基于 Docker 的 Python 工具,允许用户基于一个 YAML 文件定义多容器应用,从而可以使用 fig 命令行工具进行应用的部署,同时还可对应用的全生命周期进行管理);Compose 是一个用于部署(定义和运行)多容器 Docker 应用的工具,只需要一个 Compose 的配置文件和一个简单的命令就可以创建并运行应用所需的所有容器;在配置文件中,所有容器通过【services】来定义,并使用【docker-compose 命令】启动或停止容器以及所有依赖容器;使用它时,首先编写定义多容器(多服务)应用的 YAML 文件,然后将其交由 docker-compose 命令处理,Docker Compose 就会基于 Docker 引擎 API 完成应用的部署和管理;docker 操作镜像-容器流程docker-compose 容器编排工作流程Linux 环境安装 docker-compose检查 Linux 系统上面是否已经安装 Dockersudo docker versioncurl 下载 compose(版本1.11.2)地址sudo curl -L "https://github.com/docker/compose/releases/download/1.11.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 添加 compose 可执行权限chmod +x /usr/local/bin/docker-compose查看 compose 安装结果docker-compose --version卸载使用sudo rm /usr/local/bin/docker-compose创建 compose 快捷方式sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-composedocker-compose 命令查看 docker-compose 命令集docker-compose --help命令说明:build =》构建或重新构建容器服务 bundle =》从compose文件生成一个Docker包 config =》验证并查看compose文件 create =》创建容器服务 down =》停止并删除容器、网络、映像和卷 events =》从容器接收实时事件 exec =》在正在运行的容器中执行命令 help =》帮助命令 images =》镜像列表 kill =》杀死容器 logs =》查看容器的日志 pause =》暂停容器服务 port =》输出端口号 ps =》容器列表 pull =》下载容器服务镜像 push =》上传容器服务镜像 restart =》容器服务重新开始 rm =》删除停止的容器 run =》运行一次性命令 scale =》设置服务的容器数量 start =》开始容器服务 stop =》停止容器服务 top =》显示正在运行的进程 unpause =》暂停容器服务 up =》创建并启动容器 version =》显示Docker-Compose版本信息docker-compose.yml 文件类似 josn 文件,YAML 是 "YAML Ain't a Markup Language"(YAML 不是一种标记语言)的递归缩写,是一个可读性高,用来表达数据序列化的格式。YAML 语法格式参考:https://www.runoob.com/w3cnote/yaml-intro.htmldocker-compose.yml 文件配置:参考地址:https://docs.docker.com/compose/compose-file/核心配置:version 指定compose版本,最好是 3.0 以上版本,目前最新是 3.7 版本 services 配置容器[容器列表] nginx: #配置容器标识(唯一编号) image: #配置容器镜像 ports: #配置容器映射端口号[数组] networks: #配置容器网络[数组] networks 网络指定配置 nginx-rmcore: #配置网络名称 external: true #网络自定义 volumes 数据挂载配置 extensions 扩展配置容器编排使用场景批量管理 docker 容器上面的镜像和容器化应用;容器编排如何应用,目标同时操作 nginx 和 .net core 构建的镜像?准备 nginx 镜像;准备 .net core 项目构建的镜像;docker-compose 工具;docker-compose.yml 配置文件;Linux 实践操作进入创建的容器目录 =》cd nginx查看当前目录列表 =》ls创建 compose 文件夹 =》mkdir compose再次查看当前目录列表 =》ls 此时会有一个 compose 文件夹进入 compose 文件夹 =》cd compose创建 docker-compose.yml (严格命名)文件 =》touch docker-compose.yml编辑 docker-compose.yml (语法严格规范)文件=》vi docker-compose.yml 编辑配置信息如下:version: '3' #标识 componse 版本信息 services: netcore: #镜像标识(唯一) image: netcore #当前环境镜像名称,不用指明路径,会自动从本地寻找镜像 ports: #暴露端口,端口映射 - 8080:80 #把容器端口80映射到外部主机访问端口8080 - 8081:443 #把容器镜像文件所在目录端口443映射到外部访问端口8081 nginx: image: nginx ports: - 8090:80 #注:上面符号 - 表示数组,注意空格缩进表示层级,该文件配置严格遵循YAML语法配置以上 docker-compose.yml 文件配置完成,保存并推出 =》:wq再次查看当前文件目录 =》ls 查看里面是否存在 docker-compose.yml 文件通过 docker-compose.yml 文件配置信息批量运行容器 =》docker-compose up 此时会输出两个信息Creating compose_netcore_1 ... done Creating compose_nginx_1 ... done查看批量创建的容器信息 =》docker ps -a / docker container ls -a以上配置过程就实现了容器编排,通过当前主机ip和对应端口即可访问批量创建的容器【netcore和nginx】。compose 批量停止容器(并删除容器、网络、映像和卷) =》docker-compose down为了验证是否删除信息,查看所有容器列表 =》docker ps -a / docker container ls -a注意:容器编排缺陷 =》docker-compose 只支持 docker 单节点主机上面的容器应用部署和编排管理;如何使用创建的 Nginx 容器反向代理 netcore ?进入 nginx 容器(通过 Bash Shell 桥接) =》docker exec -it [container-name/id] /bin/bash进入当前目录 =》cd /查看列表文件夹目录 =》ls进入 nginx 容器安装目录(通常情况在 /usr/local 目录下)=》cd /usr/local再次查看列表文件夹目录 =》ls进入 nginx 容器目录 =》cd nginx/ 在查看当前文件目录列表 =》ls 会看到 conf 文件目录进入 conf 目录 =》cd conf 查看列表 =》ls 存在 nginx.conf编辑 nginx.conf 文件 =》vi nginx.conf / vim nginx.conf在 【server】/【location】注释默认配置,编写【netcore】容器ip:port(暴露端口) =》http_proxy http://192.168.48.3:8081;保存并推出 nginx.conf =》wq退出 ngixn 容器 =》exit查看当前目录列表 =》ls重启 componse =》docker-componse restart容器间如何实现网络通信?此时浏览器查看 nginx 容器可能访问无效(不会反向代理运行 netcore 容器),由于 docker 中的容器是相互隔离的,所以容器间是不能直接相互通信的,此时需要找一个中介(docker 容器 网络管理工具 =》network),linux shell 终端输入=》docker --help 显示的管理命令中会有一个 =》【network Manage networks】查看 docker 容器【网络管理命令】信息 =》docker network --help方案:使用自定义方式桥接模式(DRIVER/bridge)接入网络,实现容器间相互通信创建网络(默认 bridge,范围本地 local),桥接标识/名称为(netcore-nginx) =》docker network create netcore-nginx查看当前 docker 节点网络目录列表 =》docker network ls 显示信息【NETWORK ID/网络id,NAME/网络名称,DRIVER/网络驱动模式,SCOPE/网络范围】找到 docker-compose.yml 文件并切换到文件所在目录 =》ls编辑 docker-compose.yml 文件,对容器【netcore,nginx】接入网络桥接模式,配置信息如下:version: '3' #标识 compones 版本信息 services: netcore: #镜像标识(唯一) image: netcore #当前环境镜像名称,不用指明路径,会自动从本地寻找镜像 ports: #暴露端口,端口映射 - 8080:80 #把容器端口80映射到外部主机访问端口8080 - 8081:443 #把容器镜像文件所在目录端口443映射到外部访问端口8081 networks: - netcore-nginx nginx: image: nginx ports: - 8090:80 networks: #单个容器内局部网络配置 - netcore-nginx networks: #docker 网络配置声明,此处是全局网络配置 netcore-nginx: #自定义网络名称 external: true #启动外部访问 # 1. 上面符号 - 表示数组,注意空格缩进表示层级,该文件配置严格遵循YAML语法配置 # 2. 上面配置信息,services 配置了两个容器,分别是 netcore 和 nginx ,并且同时使用自定义的网络桥接模式 netcore-nginx 实现容器相互通信; # 3. 和 serices 同级的 networks 配置信息,告诉 docker 启动自定义桥接模式网络 netcore-nginx ;退出 docker-componse.yml 文件并保存 =》:wq重启 componse 让配置信息生效 =》docker-componse restart再次浏览器查看 nginx 容器是否实现反向代理 netcore 容器,若出现 nginx 默认站点信息,注意查看 nginx.conf 配置信息,因为 componse 工具配置重启后,信息不会被保存,原有容器会被还原,注意修改 nginx.conf / server / location 【proxy_pass http://[netcore容器ip:port];】信息,:wq 推出并保存,然后在 nginx 容器内部重启:切换目录 =》cd ..查看当前目录列表 =》ls进入 sbin 目录 =》cd sbin再次查看当前目录列表 =》ls 此时会看到 nginx重启 nginx =》./nginx restart 或 重新加载 =》./nginx -s reload此时再次浏览器查看,确保以上配置信息都对的前提下,通常情况都可以正常访问了 =》实现 nginx 反向代理 netcore;如何查看容器 netcore 网络 ip 地址?查看 netcore 容器分层信息(安装配置详细信息)=》docker container inspect [container-name/id]=》查看【NetworkSettings】/【Networks】/【IPAddress】,文件配置展示的 IP 就是 netcore 的 ip=》查看对外暴露的端口号【NetworkSettings】/【Ports】/【HostPort】查看 docker 所有容器信息 =》dockre container ls -a=》找到对应的 netcore 容器查看对外暴露的端口;注意:在以上的配置过程中,容器重启后,容器的数据不会持久化(容器数据是保留内存中的),并且容器的ip 也会发生变化。【容器数据挂载 volums】:实现容器数据持久化 ;由于【compose】工具只能 docker 单个节点配置部署容器应用,不能实现 docker 跨节点管理,因此 docker 官方团队实现了 【docker-swarm】工具实现 docker 集群管理,下一篇幅继续讲解;
什么是 Dockerfile ?Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。Dockerfile 核心命令1、FROM 指定基础镜像构建写法:FROM 指定基础镜像2、COPY 复制命令。从上下文目录中复制文件或者目录到容器里指定路径。写法:COPY 源路径,目标路径 COPY ["源路径","目标路径"] 3、RUN 运行指令。构建的时候运行的指令;主要在于镜像构建的时候运行,运行 build 命令的时候,后面接的命令就是 shell 输入的命令;写法:RUN shell命令 参数1 参数2 RUN ["shell命令","参数1","参数2"]例如:RUN ["echo",">","/usr/share/index.html"]4、CMD 运行指令。运行容器时候运行的指令;主要在于镜像运行容器的时候生成,运行run的时候运行。写法:CMD <shell 命令> CMD ["<可执行文件或命令>","<param1>","<param2>",...] 例如:CMD ["dotnet","netcore.dll"] # 缺点:在 run 命令后面可以进行覆盖 docker run -d -P netcore dotnet netcore.dll 进行覆盖掉5、ENTRYPOINT 运行指令。运行容器时候运行的指令(不会被覆盖)写法:ENTRYPOINT ["<executeable>","<param1>","<param2>",...]可以和 CMD 动态结合,设置动态的配置参数。例如:ENTRYPOINT ["nginx", "-c"] 定参 CMD ["/etc/nginx/nginx.conf"]变参6、EXPOSE 暴露端口指令仅仅声明端口,就是指定镜像暴露的端口。在 run 的时候,通过 docker run -p 会自动随机映射到 EXPOSE 端口。写法:EXPOSE 端口例如:EXPOSE 5000 EXPOSE 50017、WORKDIR 工作目录指令用于应用在容器内的工作目录,就好比 netcore 目录写法:WORKDIR <工作目录路径>例如:WORKDIR /netcore # or 或者 WORKDIR /nginxDockerfile 指令说明简洁版FROM # 构建镜像基于哪个镜像 MAINTAINER # 镜像维护者姓名或邮箱地址 # 构建镜像时运行的指令 # 运行容器时执行的 shell 环境 VOLUME # 指定容器挂载点到宿主机自动生成的目录或其他容器 # 为 RUN、CMD、和 ENTRYPOINT 执行命令指定运行用户 WORKDIR # 为 RUN、CMD、ENTRYPOINT、COPY 和 ADD 设置工作目录,就是切换目录 HEALTHCHECH # 健康检查 # 构建时指定的一些参数 EXPOSE # 声明容器的服务端口(仅仅是声明) # 设置容器环境变量 # 拷贝文件或目录到容器中,如果是 URL 或压缩包便会自动下载或自动解压 # 拷贝文件或目录到容器中,跟 ADD 类似,但不具备自动下载或解压的功能 ENTRYPOINT # 运行容器时执行的 shell 命令使用 Dockerfile 定制镜像Ubuntu 环境原始配置 Nginx1、安装 nginx 所需工具apt -y install gcc make pcre-devel zlib-devel tar zlib2、下载 nginxwget http://nginx.org/download/nginx-1.16.1.tar.gz3、nginx 解压到 /nginx 目录tar -zxvf nginx-1.16.1.tar.gz4、切换到 /nginx/nginx-1.16.1,执行 ./configure , make ,make installcd nginx-1.16.1 && ./configure && make && make install5、切换到 /usr/local/nginx/sbin,执行 ./nginx 启动 nginxcd /usr/local/nginx/sbin && ./nginx编写 Nginx 的 Dockerfile 文件FROM ubuntu RUN apt -y install gcc make pcre-devel zlib-devel tar zlib WORKDIR /nginx COPY nginx-1.16.1.tar.gz /nginx RUN tar -zxvf nginx-1.16.1.tar.gz RUN cd nginx-1.16.1 && ./configure && make && make install EXPOSE 80 CMD /bin/bash运行 Dockerfile 配置构建 Nginx :docker build -t nginx:v1.16.1 ./耐心等待,最后一步提示:Successfully built [image-id] Successfully tagged nginx:v1.16.1启动 Nginx 命令运行 nginxdocker run -itd -P nginx # 参数说明:-it 交互式命令,d 后台运行,-P 暴露端口查看运行的容器信息docker ps docker container ls #查看所有,添加参数 -a进入 nginxdocker exec -it nginx /bin/bash # 参数说明:-it 交互式命令,bash 是 Bash Shell (Linux 默认)切换到 nginx 安装路径cd /usr/local查看当前目录列表ls 显示 conf html logs sbin进入 sbin 文件夹cd sbin再次查看当前目录列表ls 显示 nginx当前目录直接启动 nginx./nginxDockerfile 指令详解指令说明FORM指定基础镜像(FROM是必备的指令,并且必须为第一条指令)。RUN 用来执行命令行命令。其基本格式: shell格式: RUN <命令> ,输入在bash环境中的命令即可,一个dockerfile允许使用RUN不得超过127层,所以,使用一次RUN, 使用 ‘ \ ’ 换行,使用‘ && ’执行下一条命令。一般使用此种格式; exec格式: RUN <"可执行文件", "参数1", "参数2">,此种方式像是函数调用中的格式; COPY 复制文件。 其基本格式: 格式1:COPY <源路径>...<目标路径> 格式2:COPY [“<源路径1>”,....."<目标路径>"] ADD更高级的复制文件,在COPY的基础上增加了一些功能,如果复制的是压缩包的话,会直接解压,而不需要在使用RUN解压;CMD 容器启动命令。其基本格式: shell格式: CMD <命令> exec格式: CMD ["可执行文件", "参数1", "参数2"...] 参数列表格式: CMD [“参数1”, “参数2”...],在指定了ENTRYPOINT指令后,用CMD指定具体的参数 ENTRYPOINT入口点。其基本格式分为exec和shell,ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及参数。ENTRYPOINT在运行中可以替代,不过比CMD繁琐,需要通过docker run 的参数--entrypoint 来指定。当指定了ENTRYPOINT后,CMD的含义就发生了改变,不在是直接运行其命令,而是将CMD的内容作为参数传递给ENTRYPOINT指令。其执行时就变成了: <ENTRYPOINT> "<CMD>"ENV 设置环境变量。(都可以使用这里使用的变量)其基本格式: 格式1:ENV <key> <value> 格式2:ENV <key1>=<value1> <key2>=<value>... ARG 构建参数。构建参数和ENV的效果一样,都是设置环境变量,所不同的是ARG所构建的环境变量在将来容器运行时是不存在的。其基本格式: 格式1: ARG <参数名> [=<默认值>] 格式2: 该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖 VOLUME 定义匿名卷。 其基本格式: 格式1: VOLUME ["<路径1>", "<路径2>"...] 格式2: VOLUME <路径> EXPOSE 暴露端口。EXPOSE指令是声明运行时容器所提供的端口,在启动容器时不会在因为这个声明而开启端口。 其基本格式: 格式1: EXPOSE <端口1> [<端口2>...] WORKDIR 指定工作目录。其基本格式: 格式1: WORKDIR <工作目录路径> USER 指定当前用户。USER是帮助你切换到指定用户。 其基本格式: 格式1: USER <用户名> HEALTCHECK 健康检查,判断容器的状态是否正常。 其基本格式: 格式1: HEALTCHECK [选项] CMD <命令> :设置检查容器健康状况的命令 格式2: HEALTCHECK NONE: 如果基础镜像有健康检查指令,使用此格式可以屏蔽掉其健康检查指令
需求目标实现 sql server 数据库文件【本地&异地】备份,备份文件包括:【.bak】数据库的备份文件,包含日志与库数据文件;【.mdf】数据库数据文件,存放一个数据库的数据信息;【.ldf 】数据库日志文件,存放对该数据库的更新操作(增、删、改)的文件;当然以下讲述的方案也可以实现其他文件的备份,按自己的实际需求情况部署配置即可,这里以上述需求目标为例来讲解部署配置相关操作。环境准备服务器操作系统 Windows Server 2012R2 及以上版本;DB 数据库版本 SQL Server 2012R2 及以上版本;数据库GUI管理工具 SQL Server Management Studio(简称 SSMS);以上这些基础环境的安装部署此处省略,不作为本篇文章的重点,以下部署操作基于上面这些基础环境已经安装好,接下来我们展开本次重点讲解的【FreeFileSync 文件同步】方案;资源下载SQL Server en-us/zh-cn & (SSMS) en-us/zh-cnWindows Server en-us/zh-cnMSDN (itellyou.cn)NEXT, ITELLYOU备份流程清楚需求目标后,整理备份流程 12 个环节,如下图所示:备份原理:主(Master)服务器环境部署的主数据库先执行本地数据备份作业,然后把 SQL Server 的【.bak】、【.ldf】和【.mdf】备份文件存储到 IIS 搭建的 FTP 服务器或者其他 FTP/SFTP 服务器环境;从(Slave)服务器上面安装部署 FreeFileSync 工具,访问主服务器的 FTP/SFTP 服务器,并设置同步策略,形成作业计划并添加到 Windows 任务计划程序周期执行;注意事项:1、MS-SQLServer 本地备份参考:使用 Navicat Premium 实现本地数据库文件备份SQL SERVER(使用SSMS)备份数据库文件 2、部署 FTP/SFTP 服务器,新增 windows 账户并设置用户组,添加账户密码访问;3、查看主(Master)服务器搭建的 FTP(端口21) 或 SFTP(端口22) 服务器端口监听,命令 =》【netstat -an】;4、验证从(Slave)服务器与主服务器上 FTP 或 SFTP 服务器的网络访问性,命令 =》【telnet ip port】;5、防火墙开放 FTP 或 SFTP 对应的端口;FreeFileSync 文件同步FreeFileSync 首先是文件(本地或异地)同步功能,可以在指定的两个文件夹之间进行单向或双向的同步,点击程序窗口右上方的绿色齿轮图标可以设置相关参数;期间也可以自定义需要筛选的文件,有“包括”和“排除”两种过滤方式,按实际情况定义需要过滤的文件类型,此外就是比较不同文件夹中的文件是否相同的功能,不过这个功能比较适合同步之前使用,比较的结果会直接显示在列表中,方便查看,这样你就可以清楚的知道是否需要进行同步操作了,节省系统资源。其次 FreeFileSync 还是开源、跨平台(Windows、macOS、Linux)和 免费(也有付费版)的工具,GUI 界面管理操作可视化,方便快捷,以下我们使用免费版来实践操作(免费版的功能已经足够目标需求的实现了 )Download FreeFileSyncDownload the Latest Version - FreeFileSyncOSDownload FreeFileSync urlDownload FreeFileSync 11.15 WindowsDownload FreeFileSync 11.15 macOSDownload FreeFileSync 11.15 LinuxSourceDownload FreeFileSync 11.15 SourceSubscribe for FreeFileSync update notifications选择对应系统环境的版本下载即可,此处我们操作部署的环境是 Windows,所以选择 FreeFileSync for Windows 版本下载,安装后界面如下:FreeFileSync + IIS/FTP准备环境:主(Master)服务器采用 IIS 自带的 FTP 服务器;从(Slave)服务器安装 FreeFileSync for Windows 版本;从(Slave)服务器部署的 FreeFileSync 添加 Windows 任务计划程序;IIS 配置操作:自行参考相关文档,注意不要全选配置(IIS 按需添加)即可;在 IIS 中配置默认文档 - Internet Information Services | Microsoft Docs;IIS 搭建 FTP 服务器:客户端 Windows 下 IIS 搭建 FTP 服务器;服务器端,使用【服务器管理器】安装;FreeFileSync + SFTP准备环境:主(Master)服务器配置 SFTP 服务器;从(Slave)服务器安装 FreeFileSync for Windows 版本;从(Slave)服务器部署的 FreeFileSync 添加 Windows 任务计划程序;SFTP 工具选型:freeSSHd and freeFTPd - open source SSH and SFTP servers for Windows;CoreFTPServer & mini-sftp-server (coreftp.com)SFTP Server - Secure FTP server software for Windows with SFTP, FTPS, and HTTPS support (coreftp.com)参考文档:freeSSHd搭建 - 博客园 (cnblogs.com)迷你FTP服务器(Core FTP mini-sftp-server)WINDOWS下的SFTP服务器安装搭建(OpenSSH;Core FTP Mini-Sftp Server;Core FTP Server;Sysax Mul)从(Slave)服务器新建文件夹在从服务器创建如下文件夹(除开系统盘 C),比如在数据盘新建如下文件夹:ffs_batch 存储 FreeFileSync 的批量作业计划;History 存储 Master 服务器的备份文件的历史版本;Logs 存储 FreeFileSync 作业的日志记录信息;以上文件夹的配置不是唯一的,可以根据自己实际情况和 FreeFileSync 作业计划配置。从(Slave)服务器添加 Windows 任务计划在 FreeFileSync 的 GUI 中添加 Master 部署的 FTP/SFTP 服务器地址,并设置访问账号密码信息,如下所示:接下来在【同步设置】里面依次配置相应的选项 =>【比较】,【过滤器】和【同步】Master 服务器环境安装 SQL Server 的实例路径如下:其中 Backup 文件夹就可以存储 SQL Server 本地【.bak】备份文件,而 DATA 文件夹里面存储 SQL Server 相关的【.mdf】+【.ldf】文件,把部署的 FTP & SFTP 服务器文件路径指向这俩文件夹的跟路径【D:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL】,为了安全性考虑,可以在 Master 服务器新建用户组,并设置账号密码(注意密码复杂度设置)访问;同步设置里面分别有:【比较】,【过滤器】和【同步】这三个选项,里面分别设置相应的模式即可,本人此处设置的模式依次如下图所示:同步设置 => 比较,此处选择【文件和时间大小】,勾选【忽略错误】。同步设置 => 过滤器,排除掉其他文件夹和子文件,过滤规则参考:[排除文件 - 自由文件同步 (freefilesync.org)]。(https://freefilesync.org/manual.php?topic=exclude-files)同步设置 => 同步,此处选择单向的镜像同步策略,删除文件选择【历史版本】,并配置上面创建的文件夹【History】,同时勾选【覆盖默认日志路径】切换为上面创建的文件夹【Logs】,修改【命名规则】为时间戳[文件夹] 模式,并设置右侧的版本保留时间。由于此处是在VM里面部署的,只有一个虚拟 C 盘,文件结构如下:最后把配置好的 FreeFileSync 批处理文件【.ffs_batch】保存到上图【ffs_batch】文件夹中,统一存储,方便后面添加 Windows 的任务计划程序中,实现定时作业备份;FreeFileSync 批处理文件添加 Windows 的任务计划程序参考文档从服务器添加任务计划 FreeFileSync 添加 Windows 任务计划程序;接下来我们看下测试 600M+ 的文件数据同步情况,如下图所示:方案总结以上文件同步方案中,主要是应用 FTP & SFTP 实现文件的传输,其中 FreeFileSync 充当 FTP & SFTP 的客户端,从而间接的做到 DB 数据库备份的目标,优缺点对比如下:【FreeFileSync + FTP】模式优点:文件传输效率高;缺点:安全性低,文件未加密传输;【FreeFileSync + SFTP】模式优点:安全性较高,文件加/解密传输;缺点:文件传输效率较低;FTP 与 SFTP 的异同 => FTP与SFTP两者有什么区别两种模式的共同特点基于 FTP & SFTP 协议的特性,可以做到跨机器,跨平台系统之间的访问;FreeFileSync 支持 Windows,macOS,Linux;综上所述,FreeFileSync + FTP/SFTP 文件同步备份方案,可以实现多平台之间的文件备份目标,局域网环境为了追求传输效率,可以采用【FreeFileSync + FTP】模式,公网环境建议【FreeFileSync + SFTP】模式,确保相对的安全性,以上两种模式务必设置账户密码访问。
关于 Registry官方的【Docker hub】是一个用于管理公共镜像的好地方,我们可以在上面找到我们想要的镜像,也可以把我们自己的镜像推送上去。但是,有时候,我们的使用场景需要我们拥有一个私有的镜像仓库用于管理我们自己的镜像。这个可以通过开源软件 Registry 来达成目的。【Registry】在 github 上有两份代码:老代码库和新代码库。老代码是采用 python 编写的,存在 pull 和 push 的性能问题,出到 0.9.1 版本之后就标志为 deprecated(查看更多信息=》https://github.com/docker/distribution),不再继续开发。从 2.0 版本开始就到在新代码库进行开发,新代码库是采用 go 语言编写,修改了镜像 id 的生成算法、registry 上镜像的保存结构,大大优化了 pull 和 push 镜像的效率。【Registry2.0】=》https://hub.docker.com/_/registry/Regisry 可以做什么?严格的控制镜像的存储位置(tightly control where your images are being stored);完全拥有镜像分发管道(fully own your images distribution pipeline);将镜像存储和分发紧密集成到内部开发工作流中(integrate image storage and distribution tightly into your in-house development workflow);关于【Registry】的官网介绍=》https://docs.docker.com/registry/如何部署 Registry ?1、从【Docker Hub】下载 registry 镜像# 下载的默认版本为 docker.io/registry latest docker pull registry 2、运行 registry 镜像生成一个容器docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 --restart=always --name registry registry:latestRegistry 服务默认会将上传的镜像保存在容器的 /var/lib/registry,将主机的 /opt/registry 目录挂载到该目录,即可实现将镜像保存到主机的 /opt/registry 目录了。3、查看运行的容器# 若查看所有,添加参数 -a docker ps / docker container ls4、若 registry 容器启动运行,在浏览器输入【http://docker-host-ip:5000/v2/】查看,若输出 =》{} 说明 registry 运行正常。测试 Registry上传镜像到registry首先将主机的 registry 镜像命名为符合仓库要求的格式 =》【registry_url:port/ImageName:tag】--1.先标记镜像格式 docker tag nginx:latest localhost:5000/nginx:latest --2.把标记格式的镜像推送到registy docker push localhost:5000/nginx:latest --注:localhost 为本地环境的ip地址;查看本地所有镜像列表docker images -a 或 docker image ls -a从本地 registry 仓库拉取镜像docker pull localhost:5000/nginx:latest # 说明:localhost 为本地环境的 ip 地址;无法推送(push)镜像到私有仓库的问题这一步可能出现无法推送(push)镜像到私有仓库的问题,提示如下信息:The push refers to a repository [localhost:5000/nginx:latest] The https://localhsot:5000/v2/: http:server gave HTTP response to HTTPS client原因分析1、原因是本地启动的 registry 服务不是安全可信任的;解决步骤2、解决方案,修改客户端 docker 的配置文件 /etc/docker/daemon.json 添加 registry 服务地址;{ "registry-mirrors": [ "https://pee6w651.mirror.aliyuncs.com"], "insecure-registries": ["localhost:5000"] }参数说明:加速镜像设置 =》"registry-mirrors": ["https://pee6w651.mirror.aliyuncs.com"] 是阿里云代理的 Registry Hub 仓库的地址,可以加快国内访问【Registry Hub】仓库的速度;私有仓库镜像设置 =》"insecure-registries": ["localhost:5000"] 是本地【Registry】服务地址,localhost 为当前 docker宿主机ip;3、【daemon.json】 存放路径cd /etc/docker4、查看/编辑 daemon.json 文件配置信息vi daemon.json5、退出并保存:wq修改好后重启 Docker 服务 daemon.json 配置才生效,执行命令systemctl restart docker , 再次 push 即可成功,查看本地 /opt/registry 目录下面(或者浏览器输入 http://localhost:5000/v2/_catalog)或者使用命令查看本地私有仓库里面的镜像信息,命令操作如下:curl -XGET http://registry:5000/v2/_catalog curl -XGET http://registry:5000/v2/image_name/tags/list以上步骤就实现了本地部署 Registry 的过程,感兴趣的小伙伴,赶快动手跟着小c老师一步一步的实现吧! 附:镜像加速地址阿里云镜像加速器=》https://help.aliyun.com/document_detail/60750.html网易云镜像加速器=》https://hub-mirror.c.163.com官方中国镜像加速器=》https://regisry.docker-cn.com中科大镜像加速器=》https://docker.mirrors.ustc.edu.cn清华镜像加速器=》https://mirrors.tuna.tsinghua.edu.cn
什么是 Docker 镜像(image)?镜像由多个层组成,每层叠加之后,从外部看来就如一个独立的对象;镜像内部是一个精简的操作系统(OS),同时还包含应用运行所必须的文件和依赖包;镜像可以运行一个或多个容器,同时镜像也可以停止某个容器的运行,并从中创建新的镜像;镜像(iamge)的分类镜像(image)常用命令# 下载镜像(默认从远程公有镜像仓库服务Docker Hub中下载); docker image pull # 从Docker Hub的 apline 仓库中拉取【标签/版本】为latest的镜像; docker image pull alpine:latest # 从Docker Hub的 nginx 仓库中拉取【标签/版本】为latest的镜像; docker image pull nginx:latest # 拉取仓库中的全部镜像,参数 -a 所有all; docker image pull -a nginx/xxx # 列出本机Docker主机上存储的镜像(不显示标注内容),参数-a 所有all; docker image ls -a 或者 docker images -a # 查看 nginx 镜像安装信息; docker image history nginx # 查看 ubuntu 镜像分层信息,展示镜像所有细节(包含镜像层数据和元数据); docker image inspect ubuntu:latest # 删除 ubuntu:latest 镜像; docker image rm ubuntu:latest # 构建一个全新的镜像; docker image build查看镜像管理(command)命令配置参数# 显示 image 对应管理命令的配置参数 docker image [具体命令名称] --help参数过滤 --filter# 返回悬虚(dangling/没有标签的镜像被称为悬虚镜像,在列表中展示为<none>:<none>)镜像; docker image ls --filter dangling=true # 移除全部的悬虚镜像,如果添加了-a 参数,Docker 会额外移除没有被使用的镜像(那些没有被任何容器使用的镜像); docker image pruneDocker 目前支持如下的过滤器:dangling:可以指定 true 或者 false,仅返回悬虚镜像(true),或者非悬虚镜像(false)。before:需要镜像名称或者 ID 作为参数,返回在之前被创建的全部镜像。since:与 before 类似,不过返回的是指定镜像之后创建的全部镜像。label:根据标注(label)的名称或者值,对镜像进行过滤。Docker image 命令应用示例# reference 完成过滤并且仅显示标签为 latest 的示例; docker image ls --filter=reference="*:latest" # 参数 --format 通过 Go 模板对输出内容进行格式化,只返回 Docker 主机上镜像的大小属性; docker image ls --format "{{.Size}}" # 返回全部镜像,但是只显示仓库、标签和大小信息; docker image ls --format "{{.Repository}}: {{.Tag}}: {{.Size}}"CLI 方式搜索 Docker Hub=》返回官方[official]或非官方[automated]镜像# 简单模式,搜索所有“NAME=nginx”字段中包含特定字符串的仓库; docker search nginx # 返回 nginx 的内容只显示官方镜像; docker search nginx --filters "is-official=true" # 返回 nginx 的内容只显示非官方镜像; docker search nginx --filters "is-automated=true" # 说明:`docker search` 默认情况下,`Docker` 只返回 `25` 行结果 , # 可以通过参数 `--limit` 增加返回内容的行数,最多 `100` 行; 例如:docker search nginx --limit 60Docker 文件操作# 编辑index.html文件信息,vim 可能需要安装,Linux默认 vi; vi/vim index.html # 复制拷贝index.html文件到docker容器里面,后跟容器ID:存放路径; docker cp index.html 41c15dlacff0://usr/share/nginx/html # 删除镜像文件,后面可以是镜像名称或者镜像ID; docker rm nginx # 参数 -f 强行删除,且无提示; docker rm -f xxx # 删除文件夹及其子文件夹中的的所有文件,参数 -r 向下递归删除; docker rm -rf xxx # 移动或重命名; docker mv # 移动Dockerfile到 ../chait/下面; docker mv Dockerfile ../chait/ # 重命名为docfile; docker mv Dockerfile docfile # 提交文件,参数 -m 文件名称,后跟原来镜像ID,新的镜像名称; docker commit -m 'xxx' 41c15dlacff0 newimagename Linux 常用基础命令Linux 文件操作# 查看该路径下面的所有文件; # 新增文件夹; mkdir xxx # 编辑文件; vi / vim xxx # 删除文件,参数-f 强制删除; rm -f xxx # 删除目录及所有子目录和文件,参数 -r 向下递归, -f 强制删除; rm -rf xxx # 查看文件内容; cat xxx # 退出并保存编辑内容; # 安装 lrzsz 文件上传工具; sudo apt install lrzsz # 文件上传,基于 lrzsz; rz 查看端口号# 切换 root 用户; su root # 输出端口信息; lsof -i:port # 查看所有开启的端口号; netstat -aptn # 查看所有使用 udp 协议的端口号; netstat -nupl # 查看所有使用 tcp 协议的端口号; netstat -ntplLinux 重启命令# 重新启动 reboot [-n] [-w] [-d] [-f] [-i] # 参数说明: -n : 在重开机前不做将记忆体资料写回硬盘的动作 -w : 并不会真的重开机,只是把记录写到 /var/log/wtmp 档案里 -d : 不把记录写到 /var/log/wtmp 档案里(-n 这个参数包含了 -d) -f : 强迫重开机,不呼叫 shutdown 这个指令 -i : 在重开机之前先把所有网络相关的装置先停止除了上面的 reboot 命令,我们还可以使用 shutdown 命令# 立刻重启 shutdown -r now # 过10分钟自动重启 shutdown -r 10 # 在时间为20:35时候重启 shutdown -r 20:35 说明:如果是通过 shutdown 命令设置重启的话,可以用 shutdown -c 命令取消重启;Linux 关机命令# 立刻关机(一般加 -p 关闭电源) halt [-n] [-w] [-d] [-f] [-i] [-p] # 参数说明: -n : 在重开机前不做将记忆体资料写回硬盘的动作 -w : 并不会真的重开机,只是把记录写到 /var/log/wtmp 档案里 -d : 不把记录写到 /var/log/wtmp 档案里(-n 这个参数包含了 -d) -f : 强迫重开机,不呼叫 shutdown 这个指令 -i : 在重开机之前先把所有网络相关的装置先停止 # 立刻关机 poweroff # 立刻关机 shutdown -h now # 10分钟后自动关机 shutdown -h 10 说明:如果是通过 shutdown 命令设置关机的话,可以用 shutdown -c 命令取消关机;删除资源,快速清理(暴力)# 删除所有docker主机上面的image镜像, 参数 `-f` 强制删除 rm $(docker image ls -a) -f # 删除所有 `docker` 主机上面的 `container` (容器 id 删除) docker container rm $(docker container ls -aq) (推荐)优雅方式:stop 再 rm =》先暂停,在删除;重启 doker 服务# 启动 docker 服务; systemctl start docker # 重启守护进程 daemon; sudo systemctl daemon-reload # 重启 docker 服务; systemctl restart docker # 重启 docker 服务; sudo service docker restart # 关闭 docker 服务; service docker stop # 关闭 docker 服务; systemctl stop docker 镜像(image)的命令集合# 查看 image 所有管理命令 docker image --help 什么是 Docker 容器(container)?容器(container)是镜像(image)的运行实例,一个镜像可以创建多个容器(1:n);1、容器 & 虚拟机(VM)的区别?容器启动更快并且更轻量级,与虚拟机运行在完整的操作系统之上相比,容器会共享其所在主机的操作系统/内核。2、镜像 & 容器的区别?状态不同,镜像是构建时(build-time)结构,容器是运行时(run-time)结构;镜像只读不可写入,容器即可读取也可写入;3、镜像 & 容器的关系?一个镜像可以构建多个容器,1:n 一对多关系;容器的设计初衷就是快速和小巧,所以镜像通常都比较小。容器的创建依赖镜像基础;docker container run docker service create从某个镜像启动一个或多个容器。一旦容器从镜像启动后,二者之间就变成了互相依赖的关系,并且在镜像上启动的容器全部停止之前,镜像是无法被删除的。尝试删除镜像而不停止或销毁使用它的容器,会导致出错;关于镜像的说明1、镜像通常比较小【容器(container)目的】就是运行应用或者服务,这意味着容器的镜像中必须包含应用/服务运行所必需的操作系统和应用文件。但是,容器又追求快速和小巧,这意味着构建镜像的时候通常需要裁剪掉不必要的部分,保持较小的体积。例如:Docker 镜像通常不会包含 6 个不同的 Shell 让读者选择,通常情况 Docker 镜像中只有一个精简的 Shell,甚至没有 Shell。2、镜像中还不包含内核容器都是共享所在 Docker 主机的内核。所以有时会说容器仅包含必要的操作系统(通常只有操作系统文件和文件系统对象)。提示:Hyper-V 容器运行在专用的轻量级 VM 上,同时利用 VM 内部的操作系统内核。Docker 官方镜像 Alpine Linux 大约只有 4MB,可以说是 Docker 镜像小巧这一特点的比较典型的例子。但是,镜像更常见的状态是如 Ubuntu 官方的 Docker 镜像一般,大约有 110MB。这些镜像中都已裁剪掉大部分的无用内容。3、Windows 镜像要比 Linux 镜像大一些(这与 Windows OS 工作原理相关)比如:未压缩的最新 Microsoft .NET 镜像(microsoft/dotnet:latest)超过 1.7GB。Windows Server 2016 Nano Server 镜像(microsoft/nanoserver:latest)在拉取并解压后,其体积略大于 1GB。容器(container)常用命令# 指定了启动所需的镜像以及要运行的应用; docker container run <image> <app> # 启动 ubuntu 容器,并运行 Bash Shell 作为其应用; docker container run -it ubuntu /bin/bash # 启动 nanoserver 容器,并运行 PowerShell 作为其应用; docker container run -it microsoft- /powershell:nanoserver pwsh.exe # 命令会在前台启动一个 Ubuntu 容器,并运行 Bash Shell; docker container run -it ubuntu /bin/bash 【Ctrl+PQ】会断开 Shell 和容器终端之间的链接,并在退出后保持容器在后台处于运行(UP)状态。参数 -it 可以将当前终端连接到容器的 Shell 终端之上。容器随着其中运行应用的退出而终止,其中 Linux 容器会在 Bash Shell 退出后终止,而 Windows 容器会在 PowerShell 进程终止后退出。验证方法就是启动新的容器,并运行 sleep 命令休眠 10s,容器会启动,然后运行休眠命令,在 10s 后退出,执行如下命令:docker container run alpine:latest sleep 10docker container 其他命令# 列出所有在运行(UP)状态的容器。如果使用 -a 标记,还可以看到处于停止(Exited)状态的容器。 docker container ls # 命令会在容器内部启动一个 Bash Shell 进程,并连接到该 Shell(用于创建容器的镜像必须包含 Bash Shell,该命令在将 Docker 主机 Shell 连接到一个运行中容器终端时非常有用); docker container exec -it <container-name or container-id> bash # 停止运行中的容器,并将状态置为 Exited(10s 停止); docker container stop <container-name or container-id> # 重启 Exited 状态的容器; docker container start <container-name or container-id> # 删除停止运行的容器; docker container stop <container-name or container-id> # 显示容器的分层配置细节和运行时信息; docker container inspect <container-name or container-id> 容器(container)命令集# 查看容器所有管理命令 docker container --help 检查 Docker daemon(守护进程)docker version如果 server 部分中包含了错误码,表示 Docker daemon 很可能没有运行,或者当前用户无权限访问;解决 Linux 中无权限访问确认当前用户是否属于本地 Docker UNIX 组,如果不是,通过【usermod -aG docker <user>】添加,退出重新登录 Shell,修改生效;用户属于 docker 用户组,可能是 Docker daemon 没有运行导致,根据 Docker 主机的操作系统选择如下对应的命令检查 daemon 状态;//使用 Systemd 在 Linux 系统中执行该命令 $ service docker status docker start/running, process 29393 //使用 Systemd 在Linux 系统中执行该命令 $ systemctl is-active docker active //在Windows Server 2016的 PowerShell 窗口中运行该命令 > Get-Service docker Status Name DisplayName ------ ---- ----------- Running Docker dockerDocker 命令集# 查看 docker 中所有管理命令; docker --help # 查看 docker 信息; docker --info 管理命令 Management Commands:
Docker 简介Docker 是一种运行于 Linux 和 Windows 上的软件,用于创建、管理和编排容器。Docker 是在 GitHub 上开发的 Moby 开源项目的一部分。Docker 公司,位于旧金山,是整个 Moby 开源项目的维护者。Docker 公司还提供商业版和社区版。Docker 运行时与编排引擎Docker 引擎是用于运行和编排容器的基础设施工具。有 VMware 管理经验的读者可以将其类比为 ESXi。ESXi 是运行虚拟机的核心管理程序,而 Docker 引擎是运行容器的核心容器运行时。其他 Docker 公司或第三方的产品都是围绕 Docker 引擎进行开发和集成的。Docker 开源项目(Moby)国内码云(Gitee.com)平台 =》https://gitee.com/mirrors/mobyMoby is an open-source project created by Docker to enable and accelerate software containerization.It provides a "Lego set" of toolkit components, the framework for assembling them into custom container-based systems, and a place for all container enthusiasts and professionals to experiment and exchange ideas. Components include container build tools, a container registry, orchestration tools, a runtime and more, and these can be used as building blocks in conjunction with other tools and projects.Docker 首席技术官 Solomon Hykes 对于此举的解释是:按照设计,Moby 面向的是希望基于容器构建自己的系统的系统构建者,而不是可以使用 Docker 或其他容器平台的应用程序开发人员。Moby 项目的参与者可以从源于 Docker 的组件库中选择,或者他们也可以选择“带来自己的组件”(BYOC),包括可以打包成容器,然后混合搭配所有组件,创建一个定制化的容器系统。Docker 应用场景微服务架构通过对特定的业务领域进行分析和业务建模,将复杂的业务逻辑剥离成小而专一、耦合度低并且高度自治的一组服务,原生容器轻量级、统一部署的特性为解决各个微服务统一管理、调度、部署带来了全新的思路,并且大大降低了服务和服务之间、版本和版本之间的依赖关系,让同一个微服务在不同环境中保持良好的一致性。持续集成(CI)与持续交付/部署(CD)基于原生容器的 DevOps 平台为用户提供基础的开发环境,使开发者只需要关注代码开发减少相关工具的安装和配置工作量。同时丰富的容器镜像,可以让运维人员在平台上快速部署开发所需要的服务,支持通过环境变量绑定服务。实现开发环境、测试环境以及生产环境的隔离以及环境的快速搭建和回收,提高了整体效率。快速弹性伸缩借助容器快速启动、快速部署、海量并发的能力,对于需求变化量大,并且需要快速启动几百甚至上千台机器的应用,容器可以非常好地满足业务的突发需求。Docker VS VM(虚拟机)容器和虚拟机都依赖于【宿主机】才能运行。宿主机可以是 =》【笔记本】、【PC主机】、【树莓派】、【数据中心的物理服务器】、【公有云的某个实例】等。在上面的示例中,假设宿主机是一台需要运行 4 个业务应用的物理服务器。Docker容器模型服务器启动之后,所选择的操作系统会启动。在 Docker 世界中可以选择 Linux,或者内核支持内核中的容器原语的新版本 Windows。 与虚拟机模型相同,OS 也占用了全部硬件资源。在 OS 层之上,需要安装容器引擎(如:Docker)。 容器引擎可以获取系统资源,比如进程树、文件系统以及网络栈,接着将资源分割为安全的互相隔离的资源结构,称之为容器。 每个容器看起来就像一个真实的操作系统,在其内部可以运行应用。按照前面的假设,需要在物理机上运行 4 个应用。 因此,需要划分出 4 个容器并在每个容器中运行一个应用,如上图(左侧)所示。VM 模型在虚拟机模型中,首先要开启物理机并启动 Hypervisor 引导程序。一旦 Hypervisor 启动,就会占有机器上的全部物理资源,如 CPU、RAM、存储和 NIC。 Hypervisor 接下来就会将这些物理资源划分为虚拟资源,并且看起来与真实物理资源完全一致。 然后 Hypervisor 会将这些资源打包进一个叫作虚拟机(VM)的软件结构当中。这样用户就可以使用这些虚拟机,并在其中安装操作系统和应用。 前面提到需要在物理机上运行 4 个应用,所以在 Hypervisor 之上需要创建 4 个虚拟机并安装 4 个操作系统,然后安装 4 个应用。当操作完成后,结构如上图(右侧)所示。Docker 核心体系容器:集装箱 ---集装箱模块 《==》 对象 --- 独立运行的应用;镜像:集装箱模块 类 《==》 独立运行的应用程序;仓库:码头,程序集 《==》存储镜像;Docker 外部运行架构图Docker 是典型的 C/S(客户端/服务器)架构模式,使用远程 API 来管理和创建 Docker 容器,其中客户端常用工具有:Bash Shell (Linux默认)XShell (是一个强大的安全终端模拟软件)PowerShell(是开源,跨平台的构建于 .net core/.net 上基于任务的命令行 shell 和脚本语言,兼容 cmd)Windows PowerShell (Windows 平台,基于.netfx 构建,兼容 cmd)Docker 的组成关系说明:pwsh 更多参考 =》https://docs.microsoft.com/zh-cn/powershell/scripting/overview?view=powershell-7Docker 镜像(Images)Docker 镜像是用于创建 Docker 容器的模板。Docker 容(Container)容器是独立运行的一个或一组应用。Docker 客户端(Client)Docker 客户端通过命令行或者其他工具使用 Docker API (https://docs.docker.com/reference/api/docker_remote_api) 与 Docker 的守护进程通信。Docker 主机(Host)一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。Docker 仓库(Registry) Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。 Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用。 Docker MachineDocker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。Docker daemon 通信与安全客户端Docker 使用了客户端—服务端模型。客户端使用 CLI,同时服务端(daemon)实现功能,并对外提供 RESTFul API。 客户端叫作 docker(在 Windows 上是 docker.exe),daemon 叫作 dockerd(在 Windows 上是 dockerd.exe)。默认安装方式将客户端和服务端安装在同一台主机上,并且配置通过本地安全 PIC Socket 进行通信。Linux:/var/run/docker.sock。Windows://./pipe/docker_engine。默认使用 2375 作为客户端和服务端之间未加密通信方式的端口,而 2376 则用于加密通信。在实验室这样还可以,但是生产环境却是不能接受的。TLS 就是解决之道!Docker 允许用户配置客户端和 daemon 间只接收安全的 TLS 方式连接。生产环境中推荐这种配置,即使在可信内部网络中,也建议如此配置!Docker 为客户端与 daemon 间使用基于 TLS 的安全通信提供了两种模式。daemon 模式:Docker daemon 只接收认证客户端的链接。客户端模式:Docker 客户端只接收拥有证书的 Docker daemon 发起的链接,其中证书需要由可信 CA 签发。同时使用两种模式能提供最高的安全等级。App(应用程序)容器化构建过程Docker 总结Docker 是一个开源应用容器引擎,支持第三方开发和集成;Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的镜像中,然后发布部署到任何流行的Linux机器上,也可以实现虚拟化;Docker 容器通过 Docker 镜像来创建。Docker 容器之间不会有任何接口冲突,实现进程级隔离;Docker 容器的性能开销极其低,占用资源少,启动速度快,相对 VM 更加轻量级;以上资料部分参考:Docker 简介及历史,http://c.biancheng.net/view/3118.htmlDocker 开源 Moby 和 LinuxKit,https://www.sohu.com/a/143808015_464005Docker 通信与安全客户端,http://c.biancheng.net/view/3268.html
以上基本环境我们就准备好了,接下来开始在VMware中安装ubuntu-20.04-live-server-amd64.iso系统; 1、VMware Workstation Pro 15.5.1 在 Windows10 的安装不再介绍了,一路下一步即可,默认安装C盘,可根据自己情况选择对应的安装盘符; 2、下面详细介绍在VMware 虚拟机里安装 Ubuntu 20.04 LTS的步骤: 2.1)新建一个虚拟机,选择自定义(高级),然后 点击 下一步; 2.2)点击【下一步】,选择 `Workstation 15.x`; 2.3)点击【下一步】,选择稍后安装操作系统; 2.4)点击【下一步】,选择Linux操作系统和对应的版本,此处选择Ubuntu 64 位; 2.5)点击【下一步】,输入虚拟机名称,选择对应的位置,此处根据个人情况选择; 2.6)点击【下一步】,此步骤根据自己电脑配置情况选择,推荐至少选择1处理器2内核数量; 2.7)点击【下一步】,分配虚拟机内存,推荐2G=>2048MB; 2.8)点击【下一步】,选择网络类型,此处我们选择【使用桥接网络】,其他网络自行研究; 2.9)点击【下一步】,选择LSI Logic(L) (推荐)模式; 2.10)点击【下一步】,选择磁盘类型:SCSI(S) (推荐); 2.11)点击【下一步】,选择【创建新虚拟机磁盘(V)】; 2.12)点击【下一步】, 指定磁盘容量,此处根据自己电脑剩余磁盘空间情况选择,最小20G,我本人配置60G(按实际使用占用资源),选择将虚拟机磁盘存储为单个文件; 2.13)点击【下一步】,创建虚拟磁盘; 2.14)点击【下一步】,展示以上配置的基本清单,最后点击【完成】; 2.15)点击【完成】,选择【编辑虚拟机设置】,选择【CD/DVD】加载 ISO 镜像文件; 2.20)此处注意配置ubuntu系统镜像资源,推荐阿里巴镜像,在上面的 Mirror address 添加 *http://mirrors.aliyun.com/ubuntu/* 配置方式设置,参考:https://developer.aliyun.com/article/704603tlinux 3.1 安装 postgresql 13.xTencentOS Server 是腾讯公司自主研发的定制化服务器操作系统。该系统集成了众多服务器系列的优点,加入自主研发的软件,便于用户操作使用,提供全方位(内核及用户态)的操作系统支持。系统特点:安全、易用、稳定、快速、长久支持。安装镜像提供了服务器常用的各种软件支持,同时可以使用线上软件源安装及更新软件。此说明适用于 Tencent OS Server 2.4 发行版与 3.1 发行版的安装与使用,本文中以 Tencent OS Server 3.1 环境为例,讲解 PostgreSQL 13.x 版本的安装。$1 | 前提条件为了安装软件包,你需要以 root 或者其他有 sudo 权限的用户身份登录系统。$2 | 安装步骤2.1 列出可用的 PostgreSQL 模块dnf module list postgresql输出显示结果,postgresql 有 4 个版本。每个版本都有两个部分:服务器和客户端。 服务器版本 10,是默认的版本:[root@TENCENT64 ~]# dnf module list postgresql Last metadata expiration check: 0:01:22 ago on Fri 28 Jan 2022 05:14:05 PM CST. TencentOS Server 3.1 - AppStream Name Stream Profiles Summary postgresql 9.6 client, server [d] PostgreSQL server and client module postgresql 10 [d] client, server [d] PostgreSQL server and client module postgresql 12 client, server [d] PostgreSQL server and client module TencentOS Server 3.1 - TencentOS-AppStream Name Stream Profiles Summary postgresql 9.6 client, server [d] PostgreSQL server and client module postgresql 10 [d] client, server [d] PostgreSQL server and client module postgresql 12 client, server [d] PostgreSQL server and client module postgresql 13 [e] client, server [d] [i] PostgreSQL server and client module Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled2.2 安装服务器版本 13,输入:sudo dnf install @postgresql:132.3 你可能还想安装 contrib 包,它们给 PostgreSQL 数据库提供一些额外的特性:sudo dnf install postgresql-contrib2.4 一旦安装完成,使用下面的命令初始化 PostgreSQL 数据库:sudo postgresql-setup initdb Initializing database ... OK可能输出如下提示信息:[root@TENCENT64 /]# sudo postgresql-setup initdb WARNING: using obsoleted argument syntax, try --help WARNING: arguments transformed to: postgresql-setup --initdb --unit postgresql * Initializing database in '/var/lib/pgsql/data' * Initialized, logs are in /var/lib/pgsql/initdb_postgresql.log查看 pgsql 初始化日志信息:vim /var/lib/pgsql/initdb_postgresql.loginitdb_postgresql.log 日志信息:The files belonging to this database system will be owned by user "postgres". This user must also own the server process. The database cluster will be initialized with locale "en_US.UTF-8". The default database encoding has accordingly been set to "UTF8". The default text search configuration will be set to "english". Data page checksums are disabled. fixing permissions on existing directory /var/lib/pgsql/data ... ok creating subdirectories ... ok selecting dynamic shared memory implementation ... posix selecting default max_connections ... 100 selecting default shared_buffers ... 128MB selecting default time zone ... Asia/Shanghai creating configuration files ... ok running bootstrap script ... ok performing post-bootstrap initialization ... ok syncing data to disk ... ok Success. You can now start the database server using: /usr/bin/pg_ctl -D /var/lib/pgsql/data -l logfile start 2.5 启动 PostgreSQL 服务,并且启用开机启动 PostgreSQL 服务。sudo systemctl enable --now postgresql2.6 验证安装结果,使用 psql 工具连接 PostgreSQL 数据库服务器并且打印它的版本号。sudo -u postgres psql -c "SELECT version();"输出显示结果:[root@TENCENT64 ~]# sudo -u postgres psql -c "SELECT version();" could not change directory to "/root": Permission denied version ------------------------------------------------------------------------------------------------------------ PostgreSQL 13.3 on x86_64-redhat-linux-gnu, compiled by gcc (GCC) 8.4.1 20200928 (Red Hat 8.4.1-1), 64-bit (1 row)$3 | PostgreSQL 角色和身份认证方法PostgreSQL 支持多种身份认证方法。最常用的方法如下:trust、password、ident、peer auth-method :指定当一个连接匹配这个记录时,要使用的认证方法。更多详细请查看 => https://www.w3cschool.cn/postgresql13_1/postgresql13_1-z9af3jew.htmltrust:无条件地允许连接。允许任何可以与PostgreSQL数据库服务器连接的用户以他们期望的任意 PostgreSQL 数据库用户身份登入,而不需要口令或者其他任何认证。reject:无条件地拒绝连接。这有助于从一个组中“过滤出”特定主机,例如一个reject行可以阻塞一个特定的主机连接,而后面一行允许一个特定网络中的其余主机进行连接。scram-sha-256:执行SCRAM-SHA-256认证来验证用户的口令。md5:要求客户端提供一个 md5加密的口令进行认证,这个方法是允许加密口令存储在 pg_shadow 里的唯一的一个方法。password:要求客户端提供一个未加密的口令进行认证。因为口令是以明文形式在网络上发送的,所以我们不应该在不可信的网络上使用这种方式。gss:用 GSSAPI 认证用户。只对 TCP/IP 连接可用。它可以与 GSSAPI 加密一起结合使用。sspi:用 SSPI 来认证用户。只在 Windows 上可用。ident:通过联系客户端的 ident 服务器获取客户端的操作系统名,并且检查它是否匹配被请求的数据库用户名。Ident 认证只能在 TCIP/IP 连接上使用。当为本地连接指定这种认证方式时,将用 peer 认证来替代。peer:通过从内核获得客户端的操作系统用户名并把它用作被允许的数据库用户名(和可选的用户名映射)来工作。和 Ident 一样,这种方法只在本地连接上支持。ldap:使用LDAP服务器认证。radius:用 RADIUS 服务器认证。cert:使用 SSL 客户端证书认证。pam:使用操作系统提供的可插入认证模块服务(PAM)认证。bsd:使用由操作系统提供的 BSD 认证服务进行认证。PostgreSQL 客户端身份验证(访问控制)通常被定义在 pg_hba.conf 文件中。默认情况下,对于本地连接,PostgreSQL 被设置成身份认证防范 peer。为了以 postgres 用户身份登录 PostgreSQL 服务器,首先切换用户,然后使用 psql 工具访问 PostgreSQL。sudo su - postgres psql或者使用 sudo 命令访问 PostgreSQL:sudo -u postgres psql注意:postgres 用户仅仅在本地被使用。$4 | 创建 PostgreSQL 角色和数据库仅仅超级用户和拥有 CREATEROLE 权限的角色可以创建新角色。举例:我们创建一个名称为 chait 的角色,一个名称为 chaitdb 的数据库,并且授予数据库上的权限。4.1 连接到 PostgreSQL shellsudo -u postgres psql4.2 创建一个新的 PostgreSQL 角色create role chait4.3 创建一个新的数据库create database chaitdb4.4 通过下面的查询语句,授予用户在数据库上的所有权限grant all privileges on database chaitdb to chait$5 | 启用远程访问 PostgreSQL 服务器默认情况下,PostgreSQL 服务器仅仅监听本地网络接口:127.0.0.1为了允许远程访问你的 PostgreSQL 服务器,打开配置文件:# 查找 postgresql.conf 位置 sudo find postgresql.conf # 编辑 postgresql.conf 文件 sudo vim /var/lib/pgsql/data/postgresql.conf /CONNECTIONS AND AUTHENTICATION往下滑动到 CONNECTIONS AND AUTHENTICATION部分,并且添加或者编辑下面的行:#------------------------------------------------------------------------------ # CONNECTIONS AND AUTHENTICATION #------------------------------------------------------------------------------ # - Connection Settings - listen_addresses = '*' # what IP address(es) to listen on;保存文件,并且使用下面的命令重启 PostgreSQL 服务::wq! sudo systemctl restart postgresql使用 ss 工具来验证这个修改:[root@TENCENT64 /]# ss -nlt | grep 5432 LISTEN 0 244 0.0.0.0:5432 0.0.0.0:* LISTEN 0 244 [::]:5432 [::]:* 上面的输出显示 PostgreSQL 服务器已经在监听所有网络接口(0.0.0.0)的默认端口。最后一步是,通过编辑 pg_hba.conf 文件,配置服务器去接受远程连接。下面是一些例子,展示了不同的用户示例:[root@TENCENT64 /]# ls /var/lib/pgsql/data base log pg_hba.conf pg_multixact pg_serial pg_stat_tmp pg_twophase pg_xact postmaster.opts current_logfiles pg_commit_ts pg_ident.conf pg_notify pg_snapshots pg_subtrans PG_VERSION postgresql.auto.conf postmaster.pid global pg_dynshmem pg_logical pg_replslot pg_stat pg_tblspc pg_wal postgresql.conf [root@TENCENT64 /]# sudo vim /var/lib/pgsql/data/pg_hba.conf pg_hba.conf 默认配置如下:# TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only local all all peer # IPv4 local connections: host all all 127.0.0.1/32 ident # IPv6 local connections: host all all ::1/128 ident # Allow replication connections from localhost, by a user with the replication privilege. local replication all peer host replication all 127.0.0.1/32 ident host replication all ::1/128 ident修改为如下:# TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only local all all peer # IPv4 local connections: host all all 0.0.0.0/0 trust # IPv6 local connections: host all all ::1/128 ident # Allow replication connections from localhost, by a user with the replication privilege. local replication all peer host replication all 0.0.0.0/0 trust host replication all ::1/128 ident最后我们用 DBeaver 客户端工具访问链接测试下,如下所示:$6 | 总结tlinux 3.1 是基于 RedHat-CentOS-Release 的发行版,默认支持 PostgreSQL 的 9.6/10/12/13 版本,内核信息查看如下:WARNING! The remote SSH server rejected X11 forwarding request. Welcome to TencentOS 3 64bit Version 3.1 20211028 Last login: Fri Jan 28 17:13:06 2022 from 192.168.10.251 [root@TENCENT64 ~]# uname -a Linux TENCENT64.site 5.4.119-19-0008 #1 SMP Mon Sep 13 14:54:39 CST 2021 x86_64 x86_64 x86_64 GNU/Linux [root@TENCENT64 ~]# cat /etc/redhat-release CentOS Linux release 8.4.2105 (Core) [root@TENCENT64 ~]# 更多 PostgreSQL 版本安装请查看 => https://www.postgresql.org/docs/14/index.html
tlinux 简介TencentOS Server(又名 Tencent Linux,简称 TS 或 tlinux)是腾讯针对云的场景研发的 Linux 操作系统,提供特定的功能及性能优化,为云> 服务器实例中的应用程序提供高性能及更加安全可靠的运行环境。TencentOS Server 提供免费使用,在 CentOS(及其他发行版)上开发的应用程序可> 直接在 TencentOS Server 上运行,用户还可持续获得腾讯云团队的更新维护和技术支持。TencentOS Server 环境说明(用户态环境)TencentOS Server2 用户态软件包保持与最新版 CentOS7 兼容,即 CentOS7 版本的软件包可以直接在 TencentOS Server2.4 中使用。TencentOS Server3 用户态软件包保持与最新版 RHEL8 兼容,即 RHEL8 版本的软件包可以直接在 TencentOS Server3.1 中使用。相关软件资源包Docker Engine - Community v20.10.12Docker Compose version v2.2.3Redis v6.2.6RabbitMQ-v3.9.13-management$ | 1 安装 Dcoker和 Docker 标准的发行版相比,Tencent OS Linux 针对 Docker 进行了定向的优化。如果你想要享受对应的优化,则需要定向安装腾讯云提供的 Docker 软件,具体安装方法也很简单,执行如下两行代码即可。1.1 安装腾讯云提供的 Docker 软件yum -y install tencentos-release-docker-ce yum -y install docker-ce1.2 开启 docker 信息隔离sysctl -w kernel.stats_isolated=1TencentOS-kernel 容器内CPU、内存、进程、磁盘等信息隔离Tencent Linux 基于内核实现了类lxcfs的有关cpu、内存统计、进程、磁盘统计等信息的隔离 在docker中,通过如下接口,看到的不再是docker所在的物理机系统的信息,而是docker自己的统计信息:/proc/cpuinfo/proc/meminfo/proc/stat/proc/uptimes/proc/loadavg/proc/diskstat使用方式系统级开关sysctl -w kernel.stats_isolated=0|1该开关可读可写,且只有系统级开关开启的情况下,才能启用信息隔离。默认是关闭,容器级别开关信息隔离控制开关(在docker里面进行配置)sysctl -w kernel.container_stats_isolated=0|1该开关可写不可读,且在系统级开关和它同时开启的情况下才有效,默认开启 系统可以在容器对应cgroup的CPU、cpuacct、cpuset、blkio等子系统的xxx.stats_isolated(xxx子系统名)来查看docker是否开启了docker级别开关, 该开关系统只能查看,不能设置cpuinfo显示控制开关sysctl -w kernel.cpuquota_aware=0|1默认情况下 /proc/cpuinfo 显示的 cpu 个数与 cpuset 子系统的 cpus 是相同的。 而 lxcfs 则使用 cpu 子系统的 quota 来控制 cpu 的个数,为了和 lxcfs 一致,提供了该开关 默认开启,即:/proc/cpuinfo 显示的 cpu 个数基于 cpu 子系统的 quota 来控制。1.3 配置腾讯云 Docker 镜像Docker 官方镜像在海外,在国内下载的速度体验一直不佳,在这种情况下,可以考虑配置腾讯云官方的内网镜像,提升镜像下载速度。cat << EOF > /etc/docker/daemon.json "registry-mirrors": [ "https://mirror.ccs.tencentyun.com" EOFDocker 默认安装路径【/var/lib/docker】,该镜像源配置文件【/etc/docker/daemon.json】,如果没有配置过镜像该文件默认是不存的,需自行手动添加。其他国内镜像源:Docker中国区官方镜像:https://registry.docker-cn.com中国科技大学 => https://docker.mirrors.ustc.edu.cn阿里云 => https://cr.console.aliyun.com腾讯云 => https://mirror.ccs.tencentyun.com网易 => http://hub-mirror.c.163.com1.4 重启 docker 服务修改保存后,重启Docker以使配置生效。# 重载守护进程 sudo systemctl daemon-reload # 重启 docker 服务 sudo systemctl restart docker # 设置 docker 开机启动 sudo systemctl enable docker查看镜像源配置是否生效:docker info | grep https输出信息:Registry: https://index.docker.io/v1/ https://mirror.ccs.tencentyun.com/ https://registry.docker-cn.com/ https://docker.mirrors.ustc.edu.cn/ https://cr.console.aliyun.com/1.5 查看 docker 版本信息docker version输出信息:Client: Docker Engine - Community Version: 20.10.12 API version: 1.41 Go version: go1.16.12 Git commit: e91ed57 Built: Mon Dec 13 11:45:22 2021 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.12 API version: 1.41 (minimum version 1.12) Go version: go1.16.12 Git commit: 459d0df Built: Mon Dec 13 11:43:44 2021 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.4.12 GitCommit: 7b11cfaabd73bb80907dd23182b9347b4245eb5d runc: Version: 1.0.2 GitCommit: v1.0.2-0-g52b36a2 docker-init: Version: 0.19.0 GitCommit: de40ad0到这里 docker 基本就安装完成了,由于此处安装的 docker 没有集成 docker compose 工具,需自己手动安装;$ | 2 安装 Docker Compose 工具2.1 Compose 简介Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。它是一个定义和运行多容器的 docker 应用工具。使用compose 你能通过 YMAL 文件配置你自己的服务,然后通过一个命令创建和运行所有的服务。Docker-Compose 将所管理的容器分为三层:工程/项目(project)服务(service)容器(container)Docker-Compose 运行目录下的所有文件(docker-compose.yaml,extends文件或环境变量文件等)组成一个工程,若无特殊指定工程名即为当前目录名。一个工程/项目当中可包含多个服务,每个服务中定义了容器运行的 镜像,参数,依赖 。一个服务当中可包括多个容器实例。工程/项目(project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yaml 中定义。即是 Compose 的一个配置文件可以解析为一个项目,Compose 通过分析指定配置文件,得出配置文件所需完成的所有容器管理与部署操作。服务(service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。每个服务都有自己的名 字、使用的镜像、挂载的数据卷、所属的网络、依赖哪些其他服务等等,即以容器为粒度,用户需要 Compose 所完成的任务。容器(container):通过一种虚拟化技术来隔离运行在主机上的不同进程,从而达到进程之间、进程和宿主操作系统相互隔离、互不影响的技术。 这种相互孤立进程就叫容器,它有自己的一套文件系统资源和从属进程。Docker-Compose 的工程配置文件默认为 docker-compose.yaml,可通过环境变量 COMPOSE_FILE 或 -f 参数自定义配置文件,其定义了多个有依赖关系的服务及每个服务运行的容器。使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。在工作中,经常会碰到需要多个容器 相互配合来完成某项任务的情况。例如:要部署一个Web项目,除了Web服务容器,往往还需要再加上后端的数据 库服务容器,甚至还包括负载均衡容器等。2.2 Compose 安装2.2.1 查看内核/操作系统/CPU信息uname -a输出信息:Linux TENCENT64.site 5.4.119-19-0009.1 #1 SMP Sun Jan 23 23:20:30 CST 2022 x86_64 x86_64 x86_64 GNU/Linux2.2.2 下载对应的 compose 服务文件# 使用 curl 下载二进制文件 sudo curl -L "https://github.com/docker/compose/releases/download/1.28.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # 使用 curl 下载 v2.2.3 版本,下载的文件会输出到 /usr/local/bin/docker-compose sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose卸载 compose 服务文件# curl 命令安装的卸载 sudo rm /usr/local/bin/docker-compose # pip 命令安装的卸载 pip uninstall docker-compose查看更多 docker compose 发行版 => https://github.com/docker/compose/releases2.2.3 给文件路径授权sudo chmod +x /usr/local/bin/docker-compose注意:如果命令docker-compose在安装后失败,请检查您的路径。您也可以创建指向/usr/bin或路径中任何其他目录的符号链接。例如:sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose2.2.4 查看 docker compose 版本docker-compose version输出信息:Docker Compose version v2.2.3查看更多docker-compose --help输出信息:Usage: docker compose [OPTIONS] COMMAND Docker Compose Options: --ansi string Control when to print ANSI control characters ("never"|"always"|"auto") (default "auto") --compatibility Run compose in backward compatibility mode --env-file string Specify an alternate environment file. -f, --file stringArray Compose configuration files --profile stringArray Specify a profile to enable --project-directory string Specify an alternate working directory (default: the path of the Compose file) -p, --project-name string Project name Commands: build Build or rebuild services convert Converts the compose file to platform's canonical format cp Copy files/folders between a service container and the local filesystem create Creates containers for a service. down Stop and remove containers, networks events Receive real time events from containers. exec Execute a command in a running container. images List images used by the created containers kill Force stop service containers. logs View output from containers ls List running compose projects pause Pause services port Print the public port for a port binding. ps List containers pull Pull service images push Push service images restart Restart containers rm Removes stopped service containers run Run a one-off command on a service. start Start services stop Stop services top Display the running processes unpause Unpause services up Create and start containers version Show the Docker Compose version information Run 'docker compose COMMAND --help' for more information on a command.到这里 docker compose 就安装完成了,接下来我们使用 compose 工具部署多容器;查看 compose 更多信息 => https://docs.docker.com/compose/compose-file/$ | 3 Docker Compose 一键部署 Redis/ RabbitMQ 容器上面两个环节我们已经安装好 Docker 和 Compose 工具,接下来我们编写一个 compose 的 yaml 配置文件,配置文件信息内如如下:3.1 编写 docker-compose-redis-rabbitmq.yaml 配置文件# 创建 docker-compose-yaml 文件夹 sudo mikdir docker-compose-yaml # 进入 docker-compose-yaml 文件夹 cd docker-compose-yaml # 创建 yaml 文件 sudo touch docker-compose-redis-rabbitmq.yaml # 编辑 yaml 文件 sudo vim docker-compose-redis-rabbitmq.yamldocker-compose-redis-rabbitmq.yaml 配置文件内容如下:version: '3.9' services: redis: image: redis:6.2.6 container_name: redis-6.2.6 hostname: swskj-redis-6.2.6 restart: always deploy: replicas: 1 restart_policy: condition: on-failure delay: 5s max_attempts: 3 window: 30s resources: limits: cpus: '0.25' memory: 500M reservations: cpus: '0.10' memory: 10M ports: - "6379:6379" # redis的服务端口 networks: - net_redis volumes: - /etc/localtime:/etc/localtime:ro # 设置容器时区与宿主机保持一致 - /chait-docker/compose-data/redis/redis.conf:/etc/redis/redis.conf - /chait-docker/compose-data/redis/data:/data command: redis-server /etc/redis/redis.conf --appendonly yes --requirepass "redis.666" # 启动redis命令,开启aof持久化,设置密码访问 privileged: true # 使容器内的root拥有真正root权限 rabbitmq: image: rabbitmq:3.9.13-management container_name: rabbitmq-3.9.13-management hostname: swskj-rabbitmq-3.9.13 restart: always deploy: replicas: 1 # 容器副本数量 restart_policy: # 重启策略 condition: on-failure # 开启故障 delay: 5s # 尝试重启等待时间 max_attempts: 3 # 最多尝试重启次数 window: 30s # 重启成功之前等待的时间 resources: # 资源限制 limits: cpus: '0.25' memory: 500M reservations: cpus: '0.10' memory: 100M ports: # 端口映射 - "15672:15672" # rabbitmq的web管理界面端口 - "5672:5672" # rabbitmq的服务端口 networks: # 网络 - net_rabbitmq volumes: # 数据卷 - /chait-docker/compose-data/rabbitmq/data:/var/lib/rabbitmq environment: # 环境变量 - RABBITMQ_DEFAULT_USER=rabbitmq # 用户名 - RABBITMQ_DEFAULT_PASS=rabbitmq.666 # 用户密码 networks: net_redis: driver: bridge net_rabbitmq: driver: bridge保存文件并退出:wq!关于 compose 3.x 版本的编写规范,请查看 => https://docs.docker.com/compose/compose-file/compose-file-v3/3.2 命令执行 yaml 配置文件,运行 redis 和 rabbitmq 容器sudo docker-compose -p compose-svc-redis-rabbitmq -f docker-compose-redis-rabbitmq.yaml up -d拉取相关镜像容器创建成功3.3 查看 compose 列表docker-compose ls输出信息:NAME STATUS compose-svc-redis-rabbitmq running(2)3.4 查看 compose 创建的容器docker container ls输出信息:CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 45af6fbaa422 rabbitmq:3.9.13-management "docker-entrypoint.s…" 7 hours ago Up About an hour 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp rabbitmq-3.9.13-management e633a68d1708 redis:6.2.6 "docker-entrypoint.s…" 7 hours ago Up About an hour 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp 总结假定 tlinux 3.1 系统环境已经部署,并配置网络(nmcli)可访问外网;规划好相关配置软件及其版本环境;熟悉 docker, compose 相关命令,用法以及 yaml 配置文件的编写;熟悉相关部署的 docker 容器组件;以上就是 tlinux 3.1 安装 docker, docker-compose 工具,并部署 redis 和 rabbitmq 容器的所有过程,有需要的小伙伴赶快动手实践哟 (^U^)ノ~YO
环境准备上面一篇文章 我们讲解了在 Windows 环境上面安装 PostgreSQL 的简单介绍,准备环境基本一样。ubuntu server 20.04 镜像下载地址 =》Get Ubuntu Server | Download | Ubuntu此处我们安装系统环境是 linux for ubuntu,所以选择 linux 进入页面,如下所示: 更多信息请查看 =》 PostgreSQL: Linux downloads (Ubuntu)Ubuntu Server 安装 PostgerSQLPostgreSQL 可以从 Ubuntu 主存储库中获取。然而,和许多其它开发工具一样,它可能不是最新版本。首先在 shell 终端中使用 apt 命令检查 Ubuntu 存储库中可用的 PostgreSQL 版本:sudo apt show postgresql 在我的 Ubuntu 20.04 中,它显示 PostgreSQL 的可用版本是 12(12+214 表示版本 12) 而 PostgreSQL 版本 13.x 已经发布。PostgreSQL 软件发布:2020-09-24PostgreSQL 全球开发组今天宣布 PostgreSQL 13 正式发布, 作为世界上最先进的开源数据库,PostgresSQL 13 是目前的最新版本。PostgreSQL 13 在索引和查找方面进行了重大改进,有利于大型数据库系统,同时包括索引的空间节省和性能提高,使用聚合或分区的查询时的更快响应,使用增强的统计信息时更优化的查询计划,以及很多其他改进。PostgreSQL 13 除了具有强烈要求的功能(如并行清理和增量排序)外,还为不同大小的负载提供了更好的数据管理体验。此版本针对日常管理进行了优化,为应用程序开发人员提供了更多便利,并增强了安全性。"PostgreSQL 13 展示了我们全球社区在增强世界上最先进的开源关系数据库功能方面的协作和奉献精神。"PostgreSQL 核心团队成员 Peter Eisentraut 说, "每个发行版所带来的创新以及其在可靠性和稳定性方面的声誉,这是为什么越来越多的人选择在其应用程序中使用 PostgreSQL 的原因"。PostgreSQL 是一种创新的数据管理系统,以其可靠性和健壮性著称,得益于全球开发者社区 超过25年的开源开发,它已成为各种规模组织首选的开源关系型数据库。更多 PostgreSQL 13 发布相关信息请查看 =》PostgreSQL 13.0发布声明/Relase: 世界上功能最强大的开源数据库...PostgreSQL: Documentation: 13: E.3. Release 13根据这些信息,你可以自主决定是安装 Ubuntu 提供的版本还是还是获取 PostgreSQL 的最新发行版。方法一:通过 Ubuntu 存储库安装 PostgreSQL在 shell 终端中采用如下命令安装:sudo apt update sudo apt install postgresql postgresql-contrib根据提示输入你的密码,依据于你的网速情况,程序将在几秒到几分钟安装完成。说到这一点,随时检查 Ubuntu 中的各种网络带宽。什么是 postgresql-contrib ?postgresql-contrib 或者说 contrib 包,包含一些不属于 PostgreSQL 核心包的实用工具和功能。在大多数情况下,(推荐)最好将 contrib 包与 PostgreSQL 核心一起安装。方法二:在 Ubuntu 中安装最新版本的 PostgreSQL 13.x要安装 PostgreSQL 13,你需要在 sources.list 中添加官方 PostgreSQL 存储库和证书,然后从那里安装它。不用担心,这并不复杂。 只需按照以下步骤:# 1.创建文件库配置 / Create the file repository configuration: sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' # 2.导入仓库签名密钥(GPG 密钥) / Import the repository signing key: wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - # 3.更新套餐列表 / Update the package lists: sudo apt update # 4.安装最新版本的 PostgreSQL / Install the latest version of PostgreSQL. # 如果您想要特定的版本,请使用‘PostgreSQL-12’或类似版本代替‘PostgreSQL’ / If you want a specific version, use 'postgresql-12' or similar instead of 'postgresql': sudo apt -y install postgresql打印版本信息验证安装一旦安装完成, PostgreSQL 服务将会自动启动。使用 psql 工具通过连接 PostgreSQL 数据库并且打印它的版本来验证安装:sudo -u postgres psql -c "SELECT version();"输出如下信息:看到如上信息,说明 PostgreSQL 就已经安装好了,你可以开始使用它了。 PostgreSQL GUI 应用程序/客户端管理界面你也可以安装用于管理 PostgreSQL 数据库的 GUI 应用程序(pgAdmin):sudo apt install pgadmin4PostgreSQL 角色和身份认证方式说明:PostgreSQL 数据库访问权限是通过角色来处理的。一个角色代表一个数据库用户或者一个数据库用户组。PostgreSQL 支持多种身份认证方式。最常用的方法如下:Trust - 只要满足 pg_hba.conf 定义的条件,一个角色就可以不使用密码就能连接服务器。Password - 通过密码,一个角色可以连接服务器。密码可以被存储为 scram-sha-256、md5 和password(明文)。Ident - 仅仅支持 TCP/IP 连接。它通常通过一个可选的用户名映射表,获取客户端操作系统用户名。Peer - 和 Ident 一样,仅仅支持本地连接。PostgreSQL 客户端身份验证通常被定义在 pg_hba.conf 文件中。默认情况下,对于本地连接,PostgreSQL 被设置成身份认证防范 peer。两种方式访问 PostgreSQL 服务器为了以 postgres 用户身份登录 PostgreSQL 服务器,首先切换用户,然后使用 psql 工具访问 PostgreSQL。# 切换用户 sudo su - postgres # 进入 PostgreSQL Shell # 退出 PostgreSQL Shell \q你也可以不切换用户,而使用 sudo 命令访问 PostgreSQL。sudo -u postgres psql注意:通常 postgres 用户仅应用于本地使用,正式生产环境可以使用 postgres 账户重新创建账户,授权角色信息并指定 db 数据库;PostgreSQL 其他命令你可以通过执行命令来检查 PostgreSQL 是否正在运行,命令操作如下:service postgresql status通过 service 命令,你可以启动、关闭或重启 postgresql。输入 service postgresql 并按回车将列出所有选项。swskj@swskj-server:~$ service postgresql Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [version ..]默认情况下,PostgreSQL 会创建一个拥有所权限的特殊用户 postgres。要实际使用 PostgreSQL,你必须先登录该账户,执行如下命令:sudo su postgres此时提示符会更改为类似于以下的内容:现在,使用 psql 命令来启动 PostgreSQL Shell,会显示 postgress=# 提示符,如下所示:psql 基本命令说明:输入 \q 以退出;输入 \? 获取帮助;输入 \l 列出所有列表;查看 psql 工具命令帮助=> \?你可以使用以下命令更改任何用户(包括 postgres)的密码:ALTER USER postgres WITH PASSWORD 'my_password';注意:将 postgres 替换为你要更改的用户名,my_password 替换为所需要的密码。另外,不要忘记每条命令后面的 “;”(分号)。建议你另外创建一个用户(不建议使用默认的 postgres 用户)。为此,请使用以下命令:CREATE USER my_user WITH PASSWORD 'my_password';刚创建的 my_user 用户没有任何的属性。给它添加超级用户权限:ALTER USER my_user WITH SUPERUSER;输入 \du 命令查看 PostgreSQL 用户:如果创建的用户不想使用,可以使用 DROP 命令删除用户:DROP USER my_user;要使用其他用户登录,使用 \q 命令退出,然后使用以下命令登录:psql -U my_user还可以使用 -d 参数直接连接数据库:psql -U my_user -d my_db你可以使用其他已存在的用户调用 PostgreSQL。例如,我使用 ubuntu 登录,从 shell 终端执行以下命名:psql -U ubuntu -d postgres注意:你必须指定一个数据库(默认情况下,它将尝试将你连接到与登录的用户名相同的数据库)。如果遇到如下错误:psql: FATAL: Peer authentication failed for user "my_user"确保以正确的用户身份登录,并使用管理员权限编辑 /etc/postgresql/13/main/pg_hba.conf:sudo vim /etc/postgresql/13/main/pg_hba.conf注意:用你的版本替换 13(例如 12)。对如下所示的一行进行替换:local all postgres peer替换为:local all postgres md5替换完成后(保存退出编辑),然后重启 PostgreSQL 使配置信息生效:sudo service postgresql restart安装完成后查询相关的包,命令操作如下:dpkg -l|grep postg 以非管理员账户查看: psql 控制台命令:除了前面已经用到的 \password 命令(设置密码)和 \q 命令(退出)以外,控制台还提供一系列其他命令。\h:查看SQL命令的解释,比如\h select。 \?:查看psql命令列表。 \l:列出所有数据库。 \c [database_name]:连接其他数据库。 \d:列出当前数据库的所有表格。 \d [table_name]:列出某一张表格的结构。 \du:列出所有用户。 \e:打开文本编辑器。 \conninfo:列出当前数据库和连接的信息。创建 PostgreSQL 角色和数据库仅仅超级用户和拥有 CREATEROLE 权限的角色可以创建新角色。在下面的例子中,我们创建一个名称为 john 的角色,一个名称为 johndb 的数据库,并且授予该数据库上的权限:# 1.创建一个新的 PostgreSQL 角色 sudo su - postgres -c "createuser john" # 2.创建一个新的 PostgreSQL 数据库 sudo su - postgres -c "createdb johndb" # 3.授权用户操作数据库,连接到 PostgreSQL shell sudo -u postgres psql # 4.运行下面的 query grant all privileges on database johndb to john;启用远程访问 PostgreSQL 服务器说明:默认情况下,PostgreSQL 服务器仅仅监听本地网络接口:127.0.0.1。编辑【postgresql.conf】文件,配置 PostgreSQL 服务器允许远程访问为了允许远程访问你的 PostgreSQL 服务器,打开配置文件【postgresql.conf】并且在【CONNECTIONS AND AUTHENTICATION】一节添加【listen_addresses = '*'】。查看【postgresql.conf】配置信息,命令操作如下:sudo nano /etc/postgresql/12/main/postgresql.conf显示如下信息: 按键盘【上↑下↓箭头】滑动配置文本,并且在【CONNECTIONS AND AUTHENTICATION】一节添加【listen_addresses = '*'】。保存修改文件,选择【Y/Yes】按【Enter】推出,并且重启 PostgreSQL 服务:sudo service postgresql restart使用 ss 工具验证上述配置修改是否生效:ss -nlt | grep 5432输出显示 PostgreSQL 服务器正在监听所有的网络接口(0.0.0.0),显示如下信息: 编辑【pg_hba.conf】文件,配置 PostgreSQL 服务器接受远程连接下面是一些例子,显示不同的用户场景:# TYPE DATABASE USER ADDRESS METHOD # The user jane can access all databases from all locations using md5 password host all jane 0.0.0.0/0 md5 # The user jane can access only the janedb from all locations using md5 password host janedb jane 0.0.0.0/0 md5 # The user jane can access all databases from a trusted location (192.168.1.134) without a password host all jane 192.168.1.134 trust配置防火墙开放 PostgreSQL 服务器默认端口【5432】假设你正在使用【UFW/Ubuntu 防火墙】来管理你的防火墙,并且你想允许从【192.168.1.0/24】子网过来的访问,你应该运行下面的命令:sudo ufw allow proto tcp from 192.168.1.0/24 to any port 5432此处环境配置的子网掩码是【255.255.0.0/16】所以执行如下命令,这里根据自己实际网络环境配置处理。确保你的防火墙被配置好,并仅仅接受来自受信任 IP 范围的连接。以上就是 ubuntu server 20.04 lts 服务器环境安装和配置 postgresql 的简单介绍,欢迎更多的小伙伴安装尝试。关于【pgsql 12.6.x】更多信息,请查看文档 =》PostgreSQL: Documentation: 12: PostgreSQL 12.6 Documentation
环境准备windows server 2019 镜像文件,官网地址 =》Windows Server 2019 | Microsoftpostgresql 12.x for windows,官网地址=》PostgreSQL: The world's most advanced open source database准备一个满足以上条件的服务器;(物理机,VM 均可)以上环境中安装 windows server 2019 的环节省略,不是本次主题,今天主要是讲解 PostgreSQL 在 Windows 环境的安装配置过程,接下来我们访问 PostgreSQL 官网 ,如下所示:点击上面的 【Download】跳转到如下页面:选择对应环境的操作系统,此处我们是 Windows 环境,因此单击 Windows 进入如下页面:再次选择【Download the installer】进入页面,此时才到正式的下页面,如下所示:选择 PostgreSQL 相应的版本,此处我们选择 12.6 Windows x86-64,下载完成以管理员模式运行安装;安装 postgresql 12.6.2安装目录可以自行选择配置,此处我们选择安装在除 C 盘以外的盘符,这里我们选择 E 盘安装,路径如上所示,点击【Next】继续下一步:设置数据库超级管理员 postgres 账户的密码 / Please provide a password for the database superuser (postgres)。设置好密码后,继续点击【Next】,显示数据库服务监听端口,默认 5432,如下所示:系统语言/地区,选择默认即可,如下所示:显示安装信息,如下所示:Installation Directory: E:\Program Files\PostgreSQL\12aa Server Installation Directory: E:\Program Files\PostgreSQL\12aa Data Directory: E:\Program Files\PostgreSQL\12aa\data Database Port: 5432 Database Superuser: postgres Operating System Account: NT AUTHORITY\NetworkService Database Service: postgresql-x64-12 Command Line Tools Installation Directory: E:\Program Files\PostgreSQL\12aa pgAdmin4 Installation Directory: E:\Program Files\PostgreSQL\12aa\pgAdmin 4 Stack Builder Installation Directory: E:\Program Files\PostgreSQL\12aa准备安装就绪 / Ready to install等待安装,直到安装完成。Stack Builder 可选项配置,此处可以取消选择,点击【Finish】Stack Builder 可用于下载和安装附加工具、驱动程序和应用程序,以补充您的PostgreSQL安装。Stack Builder may be used to download and install additional tools, drivers and applications to complement your PostgreSQL installation.关于 Stack Builder 可以自行配置。启动 pgAdmin 4 客户端管理工具pgAdmin 4 是一款为 PostgreSQL 设计的可靠和全面的数据库设计和管理软件,它允许您连接到特定的数据库,创建表和运行各种从简单到复杂的 SQL 语句。它支持的操作系统包括 Linux,Windows,MacOS X。这里使用的 pgAdmin 4 是安装 PostgreSQL 数据库时自带的,在安装包解压后的目录可以看到客户端工具 pgAdmin 4。接下来启动安装好的 postgresql 管理客户端 =》pgAdmin 4,单击运行如下:首次启动需要输入 postgres 账户配置的密码。进入页面如下所示:有关 pgAdmin 4 管理工具的使用,感兴趣的小伙伴可查阅资料自行研究。以上就是 Windows 环境安装 postgresql 的基本介绍。
文件备份对企业的价值对于绝大多数人而言,电脑中的不少文件对于我们来说都非常重要,一旦丢失,损失将会难以估量。随着网络威胁的不断发展和变得越来越复杂,文件传输和托管文件传输之间的区别对于企业来说至关重要。应用先进技术管理文件传输的挑战在于区分以文档为中心的协作文件共享和自动或部分自动化文件传输,以支持业务流程(通常以兼容的方式)。为了安全地管理信息并解决最终用户和IT之间的平衡,组织需要考虑这三个基础来进行文件传输过程:1. 自动化: 通过集中式文件传输系统将自动化应用到文件传输过程中,消除了手动文件传输过程所涉及的时间和成本,并保持了整个组织的效率。2. 安全性: 正如文件同步供应商过去所做的那样,生成共享文档的链接可以通过几个基本缺陷将敏感数据置于风险之中。生成链接后只能由受信任的来源访问,实际上可以由非预期的第三方查看,依赖可扩展性,可靠性,故障转移和灾难恢复的文件传输解决方案将加强IT流程的安全状况。3. 合规性: 鉴于在中型到大型公司中传输的数据量越来越大,大多数组织对DIY文件传输过程的管理已不再可行。对于在内部和与合作伙伴开展业务的公司而言,文件传输也只是其中的一部分。如果他们必须证明所有交换和交互的合规性,他们可能需要数百个配置控制来持续管理和监控这样做。合规服务提供商降低了组织内的复杂性并保持了安全性。管理信息是一项遗留的IT问题,可能会使企业面临安全威胁。组织应考虑一个全面的系统,其中支持广泛的最终用户和系统到系统工作流,使用连接贸易合作伙伴的工具,为移动工作人员提供支持,自动化本地和远程传输流程,以及智能和安全地控制流程内容。接下来我们来简单的聊聊下面的这几款文件同步备份工具,方便结合自己实际的需求情况做对应的工具选型,这里只是简单的工具介绍和大概对比,难免有疏漏,关于每个具体工具产品更多的信息,请自行查阅相关资料深入研究,希望帮助到更多的小伙伴在技术选型时有个大概的参考~///(^v^)\\~。文件同步工具对比软件名称开源费用支持平台GUI 可视化管理推荐指数SyncTime for mac不详免费macOS支持⭐⭐⭐FreeFileSync开源免费Windows、Linux、macOS支持⭐⭐⭐⭐Syncthing开源免费Windows、Linux、macOS、Android 和 部分路由器、树莓派等硬件支持⭐⭐⭐⭐⭐GoodSync否付费Windows PC/服务器、Linux/Unix 服务器、macOS、Android、iOS支持⭐⭐⭐☆Microsoft SyncToy否免费Windows支持⭐⭐⭐Compare Advance不详免费Windows支持⭐⭐⭐Beyond Compare否付费Windows、Linux、macOS支持⭐⭐⭐Rsync开源免费Linux否⭐⭐⭐cwRsync开源付费Windows支持⭐⭐☆SyncTime for mac应用简介SyncTime 是 macOS 平台上一款优秀的的文件同步软件。设置简单,同步方便,可以通过 SyncTime 轻松地保持最新的所有备份副本,分布在许多设备上,自定义每个同步项目。同步项主要由两个文件夹组成:「源」源文件夹 和「目的地」目标文件夹。可以选择 Finder 中可访问的任何两个文件夹:它们可以位于 Mac 上,也可以设置在外部的驱动器上,如:USB 外接硬盘,甚至位于远程服务器上。正版软件付费购买;功能特性四种覆盖策略不要触摸目标上已经存在的文件;仅当源文件比目标文件新时才覆盖;如果源文件的时间戳不同,则覆盖;始终覆盖;三种同步类型单向同步(默认设置):从「源」 → 「目的地」,源变化同步到目的地。单向同步(成功转移到目标后删除源文件):从「源」 → 「目的地」→ 「删除源」,从源同步到目的地,传输成功之后删除源文件。双向同步(允许您合并源和目标):从「源」 → 「目的地」→「源」,目的地或者源有所变化都将同步。同步检测支持:仅同步一次;每天;每周;每次启动...;后台同步:实时监控更改,以使目的地始终保持最新状态;每个同步项目都可以依赖于其他同步项目:它将触发依赖关系,并在它们全部完成后自动运行;要覆盖或删除的文件可以移到回收站或自定义备份目录中;排除隐藏文件名称包含特定单词或匹配正则表达式的文件以及所选文件;排除根目录,仅指定一些包含的文件;文章 :SyncTime for mac(文件同步工具) (baidu.com)下载地址SyncTime 3.7.3 文件同步工具 - 精品MAC应用分享 (xclient.info)FreeFileSync应用简介FreeFileSync 是一个免费的(也有付费版)、开源、跨平台的文件和文件夹比较和同步软件,用于创建和管理所有重要文件的备份副本。FreeFileSync 不是每次都复制每个文件,而是确定源文件夹和目标文件夹之间的差异,并仅传输所需的最小数据量。FreeFileSync 可用于 Windows、Linux、MacOS X,它也适用于 64 位操作系统。程序安装默认可选择一个标准的安装程序或作为一个便携式应用程序。 采用一个干净、简单、直观的界面,以同步为目标,提供了多种功能。windows 环境安装后,有以下两个桌面快捷图标:FreeFileSync:手动创建备份规则;RealTimeSync:自动执行创建的规则;功能特性最大特点就是 文件/文件夹同步功能 和 比较功能检测改动和重命名的文件和文件夹。支持局域网共享文件夹的同步。复制锁定的文件(卷影复制服务)。检测冲突和传播的缺失。二进制文件比较。配置处理符号链接。自动同步作为批处理作业。处理多个文件夹对。全面而详细的错误报告。复制NTFS扩展属性。复制NTFS安全权限。支持长文件路径超过260个字符。故障安全文件拷贝。跨平台:Windows、Linux 和 MacOS X。支持 FTP、SFTP 同步。访问变量的驱动器号的卷名( U盘)。原生64位支持。保留已删除/更新的文件版本。通过优化同步序列防止磁盘空间瓶颈。完整的 Unicode 支持。高度优化的运行时性能。包括/通过过滤器排除文件。FreeFileSync 便携式和本地安装可用。区分大小写同步。内置锁定:对序列化同一网络共享上运行多个作业。了解更多 =》愿景 - 自由文件同步 (freefilesync.org)教程镜像同步;双向同步;文件夹比较;外部应用;批处理作业;任务调度;实时同步;提示和技巧;了解更多 =》视频教程 - FreeFileSync下载地址下载最新版本 - FreeFileSync参考文档=》FreeFileSync 文件备份Syncthing应用简介Syncthing 是一款 开源、免费、跨平台 的多设备文件同步工具,是基于 P2P技术 实现设备间的文件同步,所以它的同步是 分散式、去中心化 的,即你并不需要一个服务器,故不需要担心这个中心的服务器给你带来的种种限制,而且类似于 torrent 协议,参与同步的设备越多,同步的速度越快。针对 隐私 问题,Syncthing 软件只会将数据存储于个人信任的设备上,不会存储到服务器上。设备之间的通信均通过 TLS 进行,Syncthing 还使用了完全正向保密技术来进一步保障你的数据安全,以防止窃听者获得对您的数据的访问权限。很适合我们用来搭建 私有同步网盘 。对于处于 不同局域网之中 的设备之间的文件同步,Syncthing 也提供了支持。Syncthing 支持非常多的平台,包括 Windwos、macOS、Android、Linux、FreeBSD、Solaris、群晖和威联通 NAS 等,但遗憾的是,在 iOS 上只有一个第三方客户端 fsync。功能特性开源、免费、跨平台(Windwos、macOS、Android、Linux、FreeBSD、Solaris、NAS,你甚至可以在一些路由器上运行它);设备间 TLS 加密传输数据,P2P 点对点直连,分散式/去中心化,没有隐私泄漏风险;类似于 torrent 协议,参与同步的设备越多,同步的速度越快;通过 GUI 图形化页面控制,操作简单;文件版本控制,神奇的后悔药;单向同步;下载地址Syncthing - Download (softonic.com)GUI客户端Syncthing 本身是一个命令行程序,直接从 Syncthing releases 下载对应系统的版本解压打开即可运行,然后你就可以浏览器访问 127.0.0.1:8384 进入 Syncthing 的控制面板。但为了方便用户使用,有其他开发者做了相应系统的 GUI 程序:Windows 上有:Releases · canton7/SyncTrayzor · GitHubMacOS 上有:Releases · syncthing/syncthing-macos · GitHubAndroid 上有:Releases · syncthing/syncthing-android · GitHubiOS 上有:Fsync(比较简陋)Docker 镜像:syncthing/syncthing - Docker Image | Docker Hub相关链接官方文档:Welcome to Syncthing’s documentation! — Syncthing v1 documentation官方论坛:Syncthing Community Forum - Support, announcements, development coordination and general chat.官方疑难解答:FAQ — Syncthing v1 documentationSyncthing - Getting StartedSyncthing - An Intro to the GUISyncthing - Project Presentatio了解更多=》The Syncthing Project · GitHubGoodSync应用简介GoodSync 是一种简单和可靠的 文件备份 和 文件同步 软件,它可以实现电脑与电脑之间,或者电脑与U盘/移动硬盘之间的文件单/双向同步,而且支持 Windows PC、Linux/Unix 服务器、Windows 服务器、MAC、Android、iOS 系统平台;文件备份:一种简便、快捷、安全的方法,自动将您的全部数据备份到多个目标位置,在需要的时候恢复数据。文件同步:跨多个目标位置和平台,实时自动进行数据复制,实现单向或双向同步。功能特性实时数据传输:自动、计划、实时备份和同步,不需要用户交互;块级数据传输:只传送自上次分析以来变动的数据块,显著减少备份时间、网络消耗和存储要求。无人值守的服务:以后台服务的方式运行,执行自动、计划、实时备份,不需要用户交互。端到端加密:对您的关键业务数据,在传输中和静止状态均应用 AES-256 位加密,从而提高安全性。版本历史控制:保存您的数据变动历史的一个或多个版本,确保最大程度的保护和最短数据恢复时间。复制锁定文件:保证备份和同步同时在多个用户设备上打开的文件。文件和文件夹移动/重命名检测:GoodSync 检测到文件/文件夹重命名,然后按 Move 命令执行。安全属性传播:能够跨互联网监视和传播文件安全属性,在不同位置上保持相同的访问权限级别。带宽节流:控制每个 GoodSync 作业允许的最大带宽速度,消除对其他重要服务的不必要干扰。附加功能操作日志和变动报告:在树状结构上报告文件修改的所有变动情况。由 GoodSync 执行的所有文件操作都记录到日志文件和屏幕上。变动报告可供跟踪和分析使用。修复拒绝访问错误:GoodSync 的一部分在高权限模式下运行,以便处理可能引发拒绝服务错误并且需要提升用户帐户控制权限的文件操作。复制符号链接或向下钻取:符号链接可以复制为链接(按原样复制)或者链接所指向的文件。冲突自动解决:自动解决两个数据版本之间发生的冲突,保护获胜的一方,同时也保存被覆盖的版本。通过 MD5 验证文件复制:GoodSync 可以通过 MD5 校验和比较文件,或者比较整个文件正文,以确保正确复制。远程文件夹的自动重新连接:如果远程文件夹在分析或同步过程中失去连接,GoodSync 会自动重新连接。非保留式文件系统的文件修改时间转录:对于复制时不保留文件修改时间的文件系统,GoodSync 实施了一种新颖的时间转录技术,使修改时间似乎通过复制操作得以保留。检测并修复时间移位:当若干文件的修改时间依靠相同的整数小时数进行区分时,GoodSync 可以修正文件修改时间而不复制文件。复制扩展属性:GoodSync 复制 Mac 上用来存储自定义图标的文件的扩展属性。并发线程:可在多个线程中运行同步,并行工作,达到较快的文件传输速度。排除和包含过滤器:可以根据文件名称模式、大小、修改时间从同步中排除文件。可移动驱动器的可移植路径:您可以指定以卷名开始的文件夹路径,这样一来,无论使用什么磁盘字母,都能找到您的卷。平台和服务平台NAS 设备云存储协议和文件系统了解更多【平台和服务】请查看=》GoodSync : 同步对比备份下载地址文件同步、备份软件 | GoodSyncGoodSync数据同步备份软件丨中文网站特价购买Microsoft SyncToy应用简介SyncToy 是由微软推出的一款免费的文件夹同步工具,支持32/64位操作系统。虽然名字中有一个 Toy,但是大家可千万不要误以为它的功能弱爆了。实际上,我感觉这款软件还真是摆脱了微软大多数软件给人复杂和臃肿的印象,通过很简单的操作便能够完成复杂的操作,免去了大量重复的手动复制、移动操作。帮助你快速的拷贝,移动,重命名和删除不同文件夹或者不同电脑之间的文件,让重要的文件备份变得轻松。需要注意的是SyncToy软件需要系统安装有 Microsoft .NET Frameworks 环境才可以运行哦!功能特性Synchronize:新文件和更改过的文件在左右目录中将互相复制,同时,若两个目录中有同样的文件,在其中一个目录有重命名或者删除的,在另一个目录中也将执行同样操作。Echo:左目录中的新文件和更改过的文件将复制到右目录中;同时,若两个目录中有同样的文件,在左目录中有重命名或者删除的,在右目录中也将执行同样操作。Subscribe:右目录中更新过的文件将复制到左目录,如果左目录中存在同样的文件,在右目录中有重命名或者删除的,在左目录中也将执行同样操作。Contribute:和 Echo 的操作类似,但是不执行删除操作。Combine:新文件和更改过的文件在左右目录中将互相复制,但是不执行重命名和删除的对比操作。使用Windows 计划任务配合同步工作:配置好SyncToy以后,因该工具不能设置自动运行时间,所以需要结合Windows计划任务配合,自动完成同步工作。因要在计划任务中使添加的 SyncToy 自动运行,需要在 “Run” 中添加如 SyncToy 运行程序路径,并在其后添加运行参数 "-R Folder pair"。软件特色协助您节省时间,降到最低互联网应用,而且仅在必需时才根据拷贝来节约储存空间。简易、迅速、了解的Windows页面容许您偏向并点击以界定文件夹名称,及其您期待对每一个同歩组执行的SyncToy同歩方式。可以储存同歩组同歩的方式,进而使您只需点击一个按键就可以一次又一次地同歩。容许您根据点击同歩一个同歩组或全部同歩组,您乃至能够 设定SyncToy无人化运作。强劲的剖析特性向您展现了在同歩一切文档以前即将产生的事儿,乃至还为您出示了在刚开始执行同歩以前撤销挑选一切提议的实际操作的机遇。下载地址Downloading Synctoy for windows 10 - Microsoft CommunitySyncToy下载_Microsoft SyncToy v2.1中文版(微软官方同步软件)Compare Advance应用简介Compare Advance绿色版 是一款功能相当强大的文件夹同步对比工具;它主要的功能就是帮助用户完成对两个文件夹相互之间的差异比较,并且可以对文件夹的数据进行显示,对占用的电脑内存空间显示,两个文件夹相同的信息,不相同的信息显示;让您直观的就得到各种数据,对文件夹的了解更加的深入,此外该工具还能对两个文件夹内的文件进行数据同步,简单的使用方式。利用 Compare Advance 可以非常方便地找出两个文件夹中哪些文件是哪些文件是A(或者B)文件夹特有的,哪些是共有的,哪些是不同的,生成的信息图以及数据非常详细,用户可以通过这款软件快速比对两个文件夹之间的差异,之后进行快速地分类以及同步等等文件管理功能,让用户能够更好地管理文件。对于一些需要做大量文件分析的工作的人来说,这个软件是个不可多得的一款好软件,欢迎感兴趣的朋友们前来下载使用。软件功能并排文件夹选择对话框使得在开始比较之前很容易看到文件夹是可用的。在选择主文件夹时,包括或排除特定子文件夹的能力。独特的“归档文件夹”技术允许您从同步或备份文件夹中排除,这些文件夹仅仅是未压缩的存档文件,在某些情况下可以节省许多兆字节的磁盘空间。您甚至可以包含文件夹和zip文件之间可能存在的任何差异。文件夹统计窗格,它给出关于两个文件夹之间的相似性和差异的摘要信息,并在每次复制或删除操作之后更新自身。灵活的文件和文件夹显示视图,以适应您的口味或需求,包括列表、树和浏览器视图。根据列标题排序文件列表的能力。文件夹图标着色,提供关于每个文件夹的比较状态的有用信息。“不匹配”的文件(即两个文件夹相同但具有不同大小、时间戳或二进制数据)之间的差异以独特的方式概括和突出,这使得很容易看出差异是什么。使用复选框来选择在操作中使用哪些文件以及隐藏或筛选特定文件或文件夹的能力,从而为常见的文件复制和删除任务提供了良好的控制和易用性。Compare Advance还能够无缝地比较普通的磁盘文件夹、压缩文件或文件夹打印(文件夹快照)。在正常的磁盘文件夹和压缩文件之间无缝同步和备份的能力。高级同步、备份和镜像对话框可以方便地查看哪些文件,以及在上述操作中涉及到的文件数量。保存文本或HTML格式的基本文件夹内容或比较结果的报告能力。还保存用于将来比较的文件夹快照。每次复制或删除操作后,详细记录日志信息和错误通知。详细的帮助手册,不仅给出了程序的每个部分的详细信息,而且还解释了程序如何工作的基本概念。软件特色1、无需中心服务器,局域网间就能轻松同步与市面上流行的网盘不同,自同步不需要您的设备时时刻刻连接互联网完成文件同步工作,只需在同一个局域网内,就能轻松完成文件同步。2、手机照片、文档自动备份到电脑手机里保存的照片都可以通过自同步实现轻松备份至电脑,再也不用担心被误删。3、小团队本地办公文件快速分享同步目录分享功能,让你轻松将现有同步目录分享给团队内成员。4、局域网文件实时、安全、快速传输文件实时同步,变化响应在3s内,局域网传输文件加密,千兆路由局域网环境下传输速度最高可达70MB/s。同步模式synchronize :在这个模式下,SyncToy会使得两个文件夹完全一致,无论在哪一个文件夹中操作,对应的操作相当于都在另一个文件夹中执行了一次。(也就是我们所说的"同步")echo:echo模式的效果是,使得在左边文件夹中新增加的和被改变的内容会被备份到右边的文件夹中。在左侧被重命名的文件以及被删除了的文件,将也会在右侧的文件夹中删除。(这种模式与 synchronize 很像,差别就在于这里只会从左边同步到右边,右边的操作对左边的文件夹无效~)Contribute:也就是我们常说的 增量备份,相当于在 echo 的基础上,把所有的删除操作都屏蔽掉了,只要在左边文件夹中存在过的文件都会在右侧文件夹中存在。下载地址Compare Advance绿色便携版下载v1.5.0Beyond Compare应用简介Beyond Compare 是一款不可多得的专业级的文件夹和文件对比工具。使用它可以很方便地对比出两个文件夹或者文件的不同之处,相差的每一个字节用颜色加以表示,查看方便,支持多种规则对比。功能特性比较文件,文件夹可以高效对比整个驱动器和文件夹,检查大小和修改时间;或者逐字节完整验证每个文件;无缝集成了FTP站点、云存储和压缩文件,强大的过滤功能允许您只看到的自己感兴趣的。Beyond Compare选择好的方法来突出不同之处,文本文件可以用语法高亮和比较规则调整进行查看和编辑,以用于文档、源代码和HTML。Word文档、Adobe和pdf文件也可以进行比较但是不能编辑。数据文件、可执行和二进制文件以及图像文件都有专用的查看器。三方合并Beyond Compare 的合并浏览器支持将一个文件或文件夹的两个不同版本进行变更合并,生成一个输出。这种智能的方式允许在仔细检查冲突的时候能快速接受大部分变更。颜色编码和部分高亮显示允许您简单、快速地接受、拒绝以及合并变更。文件合并时可以使用内置的语法高亮显示编辑器改变输出文件的任意行。大部分版本控制系统都可直接使用 Beyond Compare,当需要时它能提供给您强大的对比和合并支持。同步文件夹Beyond Compare 文件夹同步界面可以自动协调数据差异;有效地更新您的电脑,备份您的计算机或管理你的网站;可以使用相同的接口从磁盘、FTP服务器和压缩文件拷内容。可以很容易地过滤掉您不需要的内容,并且可以使用所有强大的比较技术,让备份又快又准。可以使用一个灵活的脚本语言自动执行重复性任务,且可以从命令行调用任何脚本,以使您在方便的时候安排同步。更多新增功能Beyond Compare 4中文版新功能-Beyond Compare中文网站下载地址Download Beyond Compare Free Trial (scootersoftware.com)Beyond Compare 4中文版下载-Beyond Compare中文网站了解更多 =》Beyond Compare 4中文版_文件对比工具_Beyond Compare下载,教程Rsync/csRsync应用简介rsync 是一个开源 实用程序,提供快速的增量文件传输。rsync在GNU通用公共许可证下免费提供,目前由Wayne Davison维护。rsync 是一个常用的 Linux 应用程序,用于文件同步。它可以在本地计算机与远程计算机之间,或者两个本地目录之间同步文件(但不支持两台远程计算机之间的同步),也可以当作文件复制工具,替代cp和mv命令。它名称里面的r指的是 remote,rsync 其实就是"远程同步"(remote sync)的意思。与其他文件传输工具(如 FTP 或 scp)不同,rsync 的最大特点是会检查发送方和接收方已有的文件,仅传输有变动的部分(默认规则是文件大小或修改时间有变动)。Rsync 是一款开源的,快速的,多功能的,可实现全量及增量(差异化备份)的本地或远程数据同步备份的优秀工具,适用于Unix、Linux、Windows等多种操作系统。cwRsync 是基于 Cygwin 平台的 rsync 软件包,支持 windows 对 windows、windows 对 Linux、Linux 对 windows 高效文件同步。由于 cwRsync 已经集成了 cygwin 类库,因此安装的时候可以省去 cygwin 包。Cwrsync 还集成了 OpenSSH for windows,可以实现 Linux 下 Rsync 一模一样的操作。cwRsync 的架构很简单,有一个 Server 和多个 Client 组成。安装 cwRsync Server 以后,在服务器上面启动 cwRsync 服务,然后在客户端上面执行文件同步命令即可实现文件同步功能。如果我们将文件同步命令添加到 windows 计划任务当中,就可实现定义同步的功能。功能特性可使本地和远程两台主机之间的数据快速复制同步镜像,远程备份的功能,这个功能类似 ssh 带 scp 命令,但又优于 scp 命令的功能,scp 每次都是全量拷贝,而 rsync 可以增量拷贝;rsync 还可以在本地主机的不同分区或目录之间全量及增量的复制数据;利用 rsync 还可以实现删除文件和目录的功能,相当于rm;rsync 相当于 scp,cp,rm 但是还优于他们每一个命令;1)支持拷贝特殊文件如链接文件,设备等。 2)可以有排除指定文件或目录同步的功能,相当于打包命令tar的排除功能。 3)可以做到保持源文件或目录的权限,时间,软硬链接,属主,组等属性均不改变 -p. 4)可以实现增量同步,即只同步发生变化的数据,因此数据传输的效率很高,tar -N. 5)可以使用 rcp,rsh,ssh 等方式来配合传输文件(rsync本身不对数据加密)。 6)可以通过soket(进程方式)传输文件和数据(服务端和客户端)*****。 7)支持匿名的或认证的(无需系统用户)的进程模式传输,可实现方便安全的进程数据备份及镜像。实时同步(解决存储服务器等单点问题)利用 rsync 结合 inotify 的功能做实时的数据同步,根据存储服务器上目录的变化,把变化的数据通过 inotify 或sersync 结合 rsync 命令,同步到备份服务器,还可以通过 drbd 方案以及双写的方案实现双机数据同步。Rsync的工作方式大致使用三种主要的传输数据的方式。1)单个主机本地之间的数据传输(此时类似于cp命令的功能) 2)借助rcp,ssh等通道来传输数据(此时类似于scp命令的功能) 3)以守护进程(socket)的方式传输数据(这个是rsync自身的重要功能)下载地址rsync download (samba.org)cwRsync - Rsync for Windows | itefix.net参考文档rsync 用法教程Rsync 数据同步工具应用指南 - 知乎 (zhihu.com)rsync documentation (samba.org)cwRsync 文件备份
需求简介单服务器系统的文件备份;多服务器系统(相同 OS 或不同 OS)之间的文件备份;数据库(DB)服务器的 .bak、.mdf & .ldf 等文件备份;Rsync 应用介绍什么是 Rsync ?Rsync 是一款开源(且免费)的、快速的、多功能的、可实现全量及增量的本地或远程数据同步备份的优秀工具,主要是在 Linux 上面用于文件同步备份用的,也有 windows 版 cwRsync(分为免费和付费版本),不过基本上免费版本就可以满足大部份要求了。Rsync 具有可使本地远程两台主机之间的快速复制同步镜像、远程、备份的功能。这个功能类似 ssh 带有的 scp 命令,但又优于 scp 命令的功能,ssh 的 scp 每都是全量拷贝,而 rsync 的 scp 可以增量拷贝,当然 rsync 还可以在本地主机的不同目录之间全量及增量的复制数据,这又类似 cp 命令,但也同样优于 cp 命令,cp 每次都是全量拷贝,而 rsync 可以增量拷贝,rsync 还可以实现删除文件和目录的功能。Linux 为我们提供了两个用于文件 copy 的命令,一个是 cp,一个是 scp,但是他们略有不同:cp:主要是用于在同一台电脑上,在不同的目录之间来回 copy 文件;scp:主要是在不同的 Linux 系统之间来回 copy 文件;查看更多:Linux 中 cp 和 scp 命令的使用方法 (cnblogs.com)什么是 cwRsync ?cwRsync 是基于 cygwin 平台的 rsync 软件包,支持 windows 对 windows、windows 对 Linux、Linux 对 windows 高效文件同步。由于 cwRsync 已经集成了 cygwin 类库,因此安装的时候可以省去 cygwin 包。Cwrsync 还集成了 OpenSSH for windows,可以实现 Linux 下 Rsync 一模一样的操作。cwRsync 的架构很简单,有一个 Server 和多个 Client 组成。安装 cwRsync Server 以后,在服务器上面启动 cwRsync 服务,然后在客户端上面执行文件同步命令即可实现文件同步功能。如果我们将文件同步命令添加到 windows 计划任务当中,就可实现定义同步的功能。Rsync 的特性支持拷贝特殊文件,如连接文件,设备等。可以有排除指定文件或目录同步的功能,相当于打包命令 tar 的排除功能。可以做到保持原有文件或目录的权限、时间、软硬链接、属主、组等所有属性均不改变。可以实现增量同步,即只同步发生变化的数据,因此数据传输效率很高。可以使用 rcp,rsh,ssh 等方式来配合传输文件(rsync 本身不对数据加密)。可以通过 socket(进程方式)传输文件和数据。支持匿名的或认证(无需系统用户)的进程模式传输,可实现方便安全的进行数据备份及镜像。Rsync 工作原理单个主机本地之间的数据传输(此时类似 cp 命令的功能);不同系统之间借助 rcp、ssh 等通道来传输数据(此时类似 scp 命令的功能);以守护进程(socket)的方式传输数据(这个是 rsync 自身存在的重要功能);rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See the GNU General Public Licence for details. Rsync绝对没有任何保修。这是自由软件,欢迎您在特定条件下重新分发。有关详细信息,请参阅GNUGeneral Public License(通用公共许可证)。 rsync is a file transfer program capable of efficient remote update via a fast differencing algorithm. rsync 是一个文件传输程序,能够通过快速差分算法进行有效的远程更新。 用法|Usage: => rsync [OPTION]... SRC [SRC]... DEST => rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST => rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST => rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST => rsync [OPTION]... [USER@]HOST:SRC [DEST] => rsync [OPTION]... [USER@]HOST::SRC [DEST] => rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST] The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect to an rsync daemon, and require SRC or DEST to start with a module name. 使用 ':' 通过远程shell连接,而使用 '::' & 'rsync://' 连接到 rsync 守护进程,并要求 SRC 或 DEST 以模块名称开头。 参数说明: rsync:同步的命令 [OPTION...]:同步的参数选项 [SRC]:源,及待拷的分区、文件或目录等 [DEST]:目的分区、文件、或目录等cwRsync 资源下载cwRsync 官网 => cwRsync - Rsync for Windows | itefix.net适用于 Windows 的 rsync 客户端的基本版本。这就是从您的计算机发起 rsync 请求所需的全部内容。提供的 rsync 二进制文件有以下方便的补丁:transliterate, timelimit, ignore case, no strict check of password file permissions (unofficial).将 rsync 守护程序设置为 Windows 服务,以便您可以为传入的 rsync 请求提供服务。还提供了管理 GUI。提供的 rsync 二进制文件具有以下方便的补丁: transliterate, timelimit, ignore case, no strict check of password file permissions (unofficial).该产品套件包含 cwRsync 和 Copssh 服务器安装程序,允许您通过 ssh 传输设置安全的 rsync 服务器。有关更多信息,请参阅我们的常见问题解答:Windows Linux/Unixgithub cwRsync 整合资源包(4.0.5) => GitHub - dennis-zheng/cwRsync: cwRsync 文件同步工具cwRsync Server & Client 整合资源包 (4.1.0)=> 服务器文件同步软件 服务器端 cwRsyncServer_3.1.0_Installer.zip整合资源包里面有一个服务器的安装程序【cwRsyncServer_4.1.0_Installer.exe】和一个绿色的客户端【cwRsync_5.4.1_x86_Free_客户端】。注意:rsync 的 windows 发行版收费:rsync 下载 (samba.org)Cygwin 是 MS Windows 的 Posix 运行时,在其许多软件包中包括 rsync。cwRsync 是 MS Windows 的 rsync 的原生打包(不过,它们似乎只提供付费版本)。cwRsync 安装服务端安装 cwRsync Server直接运行exe安装文件;安装目录默认 C:\Program Files (x86)\ICW ;修改成本机当前登录的账号密码(也可以自定义账号和密码);安装完成后,替换安装目录中的 rsyncd.conf 文件(配置查看附件1);然后在【服务】中把 RsyncServer 服务【启动模式】改成【自动】;客户端安装 cwRsync1、直接运行客户端 .exe 安装文件;2、安装目录默认 C:\Program Files (x86)\cwRsync;3、运行脚本:master2slave_1h.bat:master 更新到 slave, 建议做成服务,1小时运行一次bat文件master2slave_30s.bat:master 更新到 slave, 建议做成服务,30秒运行一次bat文件slave2master_1h.bat:slave更新到master, 建议做成服务,1小时运行一次bat文件slave2master_30s.bat:slave更新到master, 建议做成服务,30秒运行一次bat文件注意事项:查看【附件1-服务端注意事项】ps:Master:主服务器,运行 cwRsyncServer 服务端;Slave:从服务器(备份服务器)运行 cwRsync 客户端;Window 中添加任务计划在 Window 中添加任务计划,不同的系统,操作有点不一样。windows xp/Server 2003 : 开始->设置->控制面板->任务计划->打开添加任务计划->下一步windows 7/Server 2008 : 开始-> 控制面板 -> 管理工具 -> 任务计划总结对 Rsync 有个基本的了解,特别是 Rsync 的特性和工作原理的掌握,能够应用到实际的场景中进行文件的备份操作。合理配置 rsyncd.conf 文件的各项参数特性。Rsync 文件备份的完整性高。对于不同系统 linux 和 windows 之间的文件同步,虽然会有一些报错,主要是权限配置的问题,但对于文件的完整性来说,并没有问题。cwRsync 文件备份【附件1】请查看,https://blog.csdn.net/ChaITSimpleLove/article/details/121925253
前言由于项目原因,需要做一个 docker 镜像文件(源代码方式制作),就需要有 Linux 环境提供镜像构建,这途中就需要 windows 和 linux 来回的拷贝传输文件,于是就找了一个工具【FileZilla】来使用,奇怪的是 ftp 竟然连接失败,为了解决这个问题,顺便做个笔记。软件环境Ubuntu Server 20.04 ltsWindows 10 专业工作站版 21H1FileZilla v3.7.3错误现象命令: open “xxx@192.168.xx.xxx” 21错误: Network error: Connection refused错误: 无法连接到服务器状态: 正在等待重试…状态: 正在连接 192.168.xx.xxx:21…响应: fzSftp started说明:linux server 是一个中间件部署环境,此处顺便做个 docker 镜像构建环境。解决办法此处网络环境是 ok 的,下面是解决步骤:1、安装 VSFTP1.1 打开终端,在 linux server 环境上面安装一个 ftp 服务【vsftpd】,输入如下命令:sudo apt install vsftpd1.2 使用以下命令启动并启用该服务:sudo systemctl start vsftpd sudo systemctl enable vsftpd1.3 检查 ftp 服务是否开启,输入命令:sudo /etc/init.d/vsftpd status # 显示active(running)即为开启状态1.4 创建 ftp 用户为了方便使用,我们为 ftp 服务创建一个用户,让后可以将该用户提供给其他需要的人员使用(并且在服务器上没有用户帐户)。这可以被视为通用FTP使用的帐户。您可以随时创建更多,服务器上具有用户帐户的任何人都可以通过FTP进行登录。创建 ftpuser,输入命令:sudo useradd -m ftpuser设置用户密码,输入命令:sudo passwd ftpuser1.5 修改 VSFTP 配置文件,输入命令:sudo vim /etc/vsftpd.conf默认只需要开启 write_enable=YES 即可。修改配置参数后重启服务,输入命令:sudo systemctl restart vsftpd【vsftpd.conf 配置】全部可选参数说明:listen=NO listen_ipv6=YES anonymous_enable=NO #是否允许匿名访问 local_enable=YES #是否允许服务器本地登录 # write_enable=YES #是否允许对ftp文件上传和修改,默认是被注释掉,如果你需要用户上传文件,就将#去掉即可,见下文 #local_umask=022 #anon_upload_enable=YES #是否允许匿名用户上传文件,创建文件夹,默认被注释掉 #anon_mkdir_write_enable=YES #是否允许匿名创建目录,默认是被注释掉 dirmessage_enable=YES #目录信息 use_localtime=YES #文件列表的上传时间 xferlog_enable=YES #上传下载的日志 connect_from_port_20=YES #ftp连接的端口,不要改 #chown_uploads=YES #切换文件上传的目录,小心,这个操作可以会被用户误操作,建议别改 #chown_username=whoever #xferlog_file=/var/log/vsftpd.log #默认的上传下载文件的日志存放路径,不用改,要查看日志见本文最后面 #xferlog_std_format=YES #日志格式 #idle_session_timeout=600 #会话的超时时间,默认10分钟 #data_connection_timeout=120 #设定单次最大的连续传输时间,这里使用默认 #nopriv_user=ftpsecure #设定支撑vsftpd 服务的宿主用户为手动建立的vsftpd用户。 #async_abor_enable=YES #设定支持异步传输功能 #ascii_upload_enable=YES #ascii_download_enable=YES # 设置ACII码文件上下传输 secure_chroot_dir=/var/run/vsftpd/empty pam_service_name=vsftpd rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key ssl_enable=NO2、准备 filezilla 客户端【filezilla 客户端】下载地址:https://filezilla-project.org/download.php?type=client#close2.1 打开 filezilla 客户端打开【文件】,选择【站点管理器】,点击【新站点】,协议处选择【FTP-文件传输协议】,主机中输入 ubuntu ip 地址,用户名和密码输入创建的 ftp 用户和密码,点击连接即可。注意:ftp 默认是 21 端口,关闭防火墙 或者 开放 21 端口2.2 查看端口占用,输入命令:lsof -i:212.3 查看防火墙状态,输入命令:ufw status此处 Status: inactive 是关闭状态。以上就是 ftp 服务连接 linux server 的解决办法,希望能帮到更多的小伙伴。
导读据腾讯官方消息,1月9日,腾讯云宣布开源其云服务器操作系统 TencentOS 内核(TencentOS Server Kernel,又称 Tencent Linux,简称 Tlinux),是腾讯针对云的场景研发的服务器操作系统。提供了专门的功能特性和性能优化,为云服务器实例中的应用程序提供高性能,且更加安全可靠的运行环境。Tencent Linux 使用免费,在 CentOS(及发行版)上开发的应用程序可直接在 Tencent Linux 上运行,用户还可持续获得腾讯云的更新维护和技术支持。关于 TencentOS腾讯介绍,相比业内其它版本 Linux 发行版,Tencent Linux 在资源调度弹性、容器支持、系统性能及安全等层面极具竞争力,特别适合云环境。TencentOS Kernel 是腾讯云物联网操作系统 TencentOS tiny 之后,TencentOS 家族对外开源的第二个项目。TencentOS 是腾讯云操作系统系列,由腾讯云架构平台部主力研发,覆盖数据中心、桌面系统、边缘设备和物联网终端等应用场景,提供云平台构建、接入和应用能力。腾讯云操作系统团队表示:“将腾讯云服务器操作系统内核 TencentOS Kernel 开源,不仅可以与全球开发者共享腾讯云在服务器操作系统领域的技术和经验,还能够汲取全球服务器操作系统领域的优秀成果和创新理念,助力整体服务器操作系统生态的繁荣。”Linux OSTencentOS Kernel 云场景服务器操作系统授权协议:GPL开源组织:腾讯开发语言:C/C++地区:国产操作系统:Linux投 递 者:- 软件类型:开源软件适用人群:–所属分类:其他开源、 Linux 内核收录时间:2020-01-09具体看看该项目的特点1、专用资源调度方案,增强系统弹性伸缩能力传统 Linux 系统中,公平性是资源调度算法的核心逻辑。公平的调度能最大程度提供系统通用性。然而,这种公平会造成资源有效利用率的低下,系统的弹性能力大大受限,业务无法按照需求进行资源的分配回收,包括资源的数量与资源的质量,如资源分配速度、抢占能力等。Tencent Linux 研发了专用的资源调度算法,大幅提升整机的资源弹性。业务可以根据需求指定资源分配的数量和级别,从相同数量不同级别的资源获得的系统服务存在明显差别。同时,Tencent Linux 研发的 CPU 弹性调度算法,在离在线业务混布场景下收益十分显著。在不影响在线业务质量的前提下,整机的 CPU 利用率最高提升 3 倍,部分业务场景下可将整机 CPU 利用率提升至 90%。2、资源隔离增强资源的安全隔离始终是容器虚拟化平台的核心问题,社区内核提供的隔离特性远远不能满足业务的需求,一些基本的系统状态信息、CPU 信息与磁盘状态信息等均未做隔离,部分场景下甚至直接导致业务不可用。Tencent Linux 从业务需求出发,首先对必要的系统状态,比如 cpuinfo、stat、loadavg、meminfo、vmstat、diskstats 与 uptime 等进行隔离增强,保证容器中的应用能获得正确的系统状态信息。更重要的是,系统还提供包括 NVME IO 隔离等特性,彻底解决 IO 控制组在多队列设备场景资源利用率低、不支持按比例隔离等问题,保证了不同场景下的 IO 隔离效果。3、系统安全与性能优化业界的内核热补丁技术主要是针对 X86 架构进行开发,缺少对 ARM64 等架构的支持。Tencent Linux 通过实现类 FMENTRY、FTRACE with REGS 功能,给 KPATCH 中增加 ARM64 支持等实现了针对 ARM64 架构的内核热补丁方案。同时,Tencent Linux 提供进程 GDB 禁止功能,阻止跨进程获取内存、加载动态库等,保障业务进程的数据安全。性能方面,Tencent Linux 针对计算、存储和网络子系统都进行了优化,例如 PAGE CACHE LIMIT 功能,限制 page cache 的使用率,尽量使系统剩余的内存能够满足业务的需求;系统还新增多个 sysctl/proc 控制接口、内核启动参数等优化用户体验。支持平台X86: 支持 intel, AMD (包括 ROME 平台)。ARM64: 支持热补丁,虚拟化。国产化支持: 海光 cpu。主要特性TencentOS Server 内核TencentOS Server 内核(简称 tkernel)与发行版解耦,当前主力内核分两个版本,基于社区 5.4 LTS 深度优化的 tkernel4(简称 tk4)。基于社区 4.14 LTS 深度优化的 tkernel3(简称 tk3)。详情见 TencentOS kernel github 仓库。发布说明镜像版本镜像版本 TencentOS Server 3.1 与 CentOS 8用户态完全兼容,配套基于社区5.4 LTS 内核深度优化的 tkernel4版本。 TencentOS Server 2.4 与 CentOS 7用户态完全兼容,配套基于社区4.14 LTS 内核深度优化的 tkernel3版本。 TencentOS Server 2.4(TK4) 与 CentOS 7用户态完全兼容,配套基于社区5.4 LTS 内核深度优化的 tkernel4版本。 更多信息:TencentOS Server 简介,https://cloud.tencent.com/document/product/1397/72777TencentOS Server 特性,https://cloud.tencent.com/document/product/1397/72777TencentOS Server 常见问题,https://cloud.tencent.com/document/product/1397/72782TencentOS Kernel 开源地址:https://github.com/Tencent/TencentOS-kernel应用案例7 月 15 日晚,聚焦中国开源生态,中央广播电视总台央视财经频道《经济半小时》栏目播出“创新带来新共享机遇”专题节目,腾讯在软件开源和技术开放上的努力再次受到肯定。《中国开源进行时!腾讯开源 TencentOS 系列项目获央视点赞》此次登上央视的是 TencentOS Server 和 TencentOS Tiny 两大开源项目,前者是结合腾讯业务自研的服务器操作系统,后者为腾讯物联网操作系统。据节目介绍,这两个开源项目分别在 iGrow 智慧农业及数据中心等多样化的场景中得到应用,有效助力了智慧农业的环境数据采集,并帮助服务器实现节能减排。
什么是 shell ?简单点理解,就是系统跟计算机硬件交互时使用的中间介质,它只是系统的一个工具。实际上,在shell和计算机硬件之间还有一层东西那就是系统内核了。打个比方,如果把计算机硬件比作一个人的躯体,而系统内核则是人的大脑,至于shell,把它比作人的五官似乎更加贴切些。回到计算机上来,用户直接面对的不是计算机硬件而是shell,用户把指令告诉shell,然后shell再传输给系统内核,接着内核再去支配计算机硬件去执行各种操作。bash 是 linux 环境下面的命令行终端,对于命令和路径能自动补全,bash-completion则是对bash补全功能的一个增强,增加了对参数和包名的补全;简单理解:【Bash简介 & Bash是如何处理命令的】Ubuntu 环境安装【bash-completion】确认 /etc/bash_completion 是否存在,如果没有就安装一个;apt install bash-completion安装完成 /etc目录会出现一个bash_completion文件;在用户的shell中运行;source /etc/bash_completion也可以将其加入配置文件中;# 编辑文件 vi /etc/bash.bashrc 取消上面的 if elif 的 # 符号注释,如下所示:# enable bash completion in interactive shells if ! shopt -oq posix; then if [ -f /usr/share/bash-completion/bash_completion ]; then . /usr/share/bash-completion/bash_completion elif [ -f /etc/bash_completion ]; then . /etc/bash_completion fi重新切换 bash 解释器,#bash此时输入 apt 命令,按【tab】键,显示如下:k8s 环境中 bash 自动补全:确认 /etc/bash_completion 是否存在,如果没有就安装一个(同上);导入 bash 自动补全的命令;source <(kubectl completion bash)重新切换 bash 解析器,#bash以上操作安装好依赖包后,重新进入一下终端即可生效,输入 kubectl 查看所有命令,按【tab】键,显示如下:提示:一切正常的话,就可以使用Tab健自动补全的功能了;如果还是不能Tab健补全的话,可以退出命令行,然后再重新打开终端;(推荐使用)或者重启系统以启动该软件 也可以;
软件环境配置:OS Windows 10 专业工作站版(v1909 x64);VMware Workstation Pro 15.6;ubuntu-20.04-live-server-amd64.iso;客户端连接工具 Xshell 6;基于以上环境,vm 中已经安装部署好 ubuntu server 系统,网络模式桥接,并且能访问外网,宿主机之间通信正常,如下所示:接下来我们使用 XShell 客户端连接 Ubuntu,出现连接失败=》Could not connect to 'ip' (port 22):Connection failed.Xshell 命令连接 =》【$ ssh 远程机器名称@ip 或者 telnet ip】访问成功会弹出提示输入密码窗口,和上面界面连接操作一样,依然访问不起;接下来我们在 vm 中的 ubuntu 查看 ssh 连接使用的 22 端口是否开启:命令=》【$ lsof -i:22】显示如下信息:此时需要安装【openssh-server 和 openssh-client】:命令=》【sudo apt install openssh-server openssh-client】,中途提示信息输入 Y 继续等待安装完成;service ssh start ssh localhost lsof -i:22 #注意:以上步骤安装完成后执行上面命令重启;此时进入 Ubuntu 查看 22 端口的情况,22 端口是否开启;命令=》【netstat -ntlp|grep 22】端口开启,使用 Xshell 再次连接,正常访问,如下所示:选择【接受并保存】,继续输入账号密码,点击确定即可正常连接;如果安装【openssh-server】有问题,建议卸载重新安装:# 彻底删除 openssh-server sudo apt remove --purge openssh-server # 安装 openssh-server sudo apt install openssh-server # 启动openssh-server sudo service ssh restart # 查看ssh是否启用 ps -e|grep ssh # 查看监听端口22 netstat -tnl # 查看openssh版本命令 ssh -V openssl version以上过程记录了 XShell 连接 VM 中 Ubuntu 系统时遇到的全过程,希望帮助到更多的朋友;
认证和授权的区别是什么?不出意外情况,我想这是一个绝大多数人都会混淆的问题。首先先从读音上来认识这两个名词,很多人都会把它俩的读音搞混,所以我建议你先先去查一查这两个单词到底该怎么读,他们的具体含义是什么。说简单点就是:认证 (Authentication): 你是谁?Who are you?授权 (Authorization): 你有权限干什么?What do you have access to do?稍微正式点(啰嗦点)的说法就是:Authentication(认证) 是验证您的身份的凭据(例如用户名/用户ID和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。Authorization(授权) 发生在 Authentication(认证)之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性。举个简单的例子:比如工作牌就是 Authentication(认证)你是谋谋公司,机构或组织的身份标识和凭据,根据这个凭据即可进入该单位区域工作,比如刷卡进出大门;而你个人的工作岗位和职责就类似Authorization(授权),单位授权该岗位能干些什么范围的工作;重新认识 Cookie什么是 Cookie,以及它的作用是什么?Cookie 有时也用其复数形式 Cookies,存储类型为“小型文本文件”,是某些网站为了辨别用户身份,进行 Session 跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。Cookie 特点:存储结构:以 Key-Value 形式存储的“小型文本文件”;存储位置:存储在用户终端的某个目录下;存储大小:≤ 4KB;时效性:可暂时或永久性保存,用户终端自行决定;Cookie是一段不超过 4KB 的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。属性说明Name/Value设置 Cookie 的名称及相对应的值,对于认证 Cookie,Value 值包括 Web 服务器所提供的访问令牌。Expires设置 Cookie 的生存期。 有两种存储类型的 Cookie:会话性与持久性。Expires 属性缺省时,为会话性 Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久性 Cookie 会保存在用户的硬盘中,直至生存期到或用户直接在网页中单击“注销”等按钮结束会话时才会失效。Path定义了 Web 站点上可以访问该 Cookie 的目录。Domain指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie 受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在 Domain 属性中设置 .org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围。Secure指定是否使用 HTTPS 安全协议发送 Cookie。使用 HTTPS 安全协议,可以保护 Cookie 在浏览器和 Web 服务器间的传输过程中不被窃取和篡改。该方法也可用于 Web 站点的身份鉴别,即在 HTTPS 的连接建立阶段,浏览器会检查 Web 网站的 SSL 证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到 SSL 证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。由于许多用户缺乏安全意识,因而仍可能连接到 Pharming 攻击所伪造的网站。HTTPOnly用于防止客户端脚本通过 document.cookie 属性访问 Cookie ,有助于保护 Cookie 不被跨站脚本攻击窃取或篡改。但是,HTTPOnly 的应用仍存在局限性,一些浏览器可以阻止客户端脚本对 Cookie 的读操作,但允许写操作;此外大多数浏览器仍允许通过 XMLHTTP 对象读取 HTTP 响应中的 Set-Cookie 头。如何使用 Cookie ?HTTP 协议中的 Cookie 包括 Web Cookie 和 浏览器 Cookie,它是服务器发送到 Web 浏览器的一小块数据。服务器发送到浏览器的 Cookie,浏览器会进行存储,并与下一个请求一起发送到服务器。通常,它用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态。HTTP Cookie 机制是 HTTP 协议无状态的一种补充和改良。Cookie 主要用于下面三个目的:会话管理:登陆、购物车、游戏得分或者服务器应该记住的其他内容;个性化:用户偏好、主题或者其他设置;Session 追踪:记录和分析用户行为;Cookie 应用:创建 Cookie:当接收到客户端发出的 HTTP 请求时,服务器可以发送带有响应的标头,Cookie 通常由浏览器存储,然后将 Cookie 与 HTTP 标头一同向服务器发出请求。Set-Cookie 和 Cookie 标头:Set-Cookie 标头告诉客户端存储 Cookie,响应标头将 cookie 从服务器发送到用户代理;客户端请求则使用 Cookie 头将存储的 Cookie 发送回服务器;下面是 Cookie 的一些应用案例:我们在 Cookie 中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了。除此之外,Cookie 还能保存用户首选项,主题和其他设置信息。使用 Cookie 保存 Session 或者 Token ,向后端发送请求的时候带上 Cookie,这样后端就能取到 Session 或者 token 了。这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。Cookie 还可以用来记录和分析用户行为。举个简单的例子你在网上购物的时候,因为 HTTP 协议是没有状态的,如果服务器想要获取你在某个页面的停留状态或者看了哪些商品,一种常用的实现方式就是将这些信息存放在Cookie。什么是 Session ?Session 简介在计算机中,尤其是在网络应用中,通常称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 的主要作用就是通过服务端记录用户的状态。用于保持状态的基于 Web 服务器的方法。Session 允许通过将对象存储在 Web 服务器的内存中在整个用户会话过程中保持任何对象。Session 的一些应用案例:存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在 Session 对象中。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。Cookie 和 Session 有什么区别?由于 HTTP 协议无状态的缺陷。WEB 的设计者们提出了 Cookie 和 Session 两种解决机制。通过对两者的比较分析,指出了它们的区别与联系。——CookieSession效期暂时和永久常规暂时(也可实现永久)存储用户终端/客户端浏览器服务器(Session + SessionID)结构key-valuekey-value优点1.极高的扩展性和可用性。 2.通过良好的编程,控制保存在cookie中的session对象的大小。 3.通过加密和安全传输技术(SSL),减少cookie被破解的可能性。 4.只在cookie中存放不敏感数据,即使被盗也不会有重大损失。 5.控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。 1.如果要在诸多Web页间传递一个变量,那么用Session变量要比通过QueryString传递变量可使问题简化。 2.要使WEb站点具有用户化,可以考虑使用Session变量。你的站点的每位访问者都有用户化的经验,基于此,随着LDAP和诸如MS Site 3.Server等的使用,已不必再将所有用户化过程置入Session变量了,而这个用户化是取决于用户喜好的。 4.你可以在任何想要使用的时候直接使用session变量,而不必事先声明它,这种方式接近于在VB中变量的使用。使用完毕后,也不必考虑将其释放,因为它将自动释放。 5.Session实例是轻量级的,所谓轻量级:是指他的创建和删除不需要消耗太多资源; 功能缺陷1.Cookie数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。 2.安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。 3.有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。 1.进程依赖性,ASP Session状态存于IIS的进程中,也就是inetinfo.exe这个程序。所以当inetinfo.exe进程崩溃时,这些信息也就丢失。另外,重起或者关闭IIS服务都会造成信息的丢失。 2.CORS(跨域资源共享):Session状态使用范围的局限性,当一个用户从一个网站访问到另外一个网站时,这些Session信息并不会随之迁移过去。 3.存在Cookie的依赖性,实际上客户端的Session信息是存储在Cookie中的,如果客户端完全禁用掉了Cookie功能,也就不能享受到了Session提供的功能了。 4.每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加; 5.不是线程安全的,应该避免多个线程共享同一个Session实例。 安全 安全性相对较低; 1.Cookie捕获/重放; 2.恶意 Cookies; 3.会话定置(Session Fixation)攻击; 4.跨站请求伪造(Cross-Site Request Forgery,简称CSRF)攻击; 安全性相对较高; 1.web服务器防护; 2.主机安全防护; 3.跨站请求伪造(Cross-Site Request Forgery,简称CSRF)攻击; 因为创建 Session 变量有很大的随意性,可随时调用,不需要开发者做精确地处理,所以过度使用 Session 变量将会导致代码可读性降低,使项目维护困难。理解 SessionID 的本质客户端使用 Cookie 保存 SessionID客户端用 Cookie 保存了 SessionID,当我们请求服务器的时候,会把这个 SessionID 一起发给服务器,服务器会到内存中搜索对应的 SessionID,如果找到了对应的 SessionID,说明我们处于登录状态,有相应的权限;如果没有找到对应的 SessionID,这说明:要么是我们把浏览器关掉了(后面会说明为什么),要么 session 超时了(没有请求服务器超过 20 分钟),session 被服务器清除了,则服务器会给你分配一个新的 SessionID。你得重 新登录并把这个新的 SessionID 保存在 Cookie 中。 在没有把浏览器关掉的时候(这个时候假如已经把 SessionID 保存在 Cookie 中了)这个 SessionID 会一直保存在浏览器中,每次请求的时候都会把这个 SessionID 提交到服务器,所以服务器认为我们是登录的;当然,如果太长时间没有请求服务器,服务器会认为我们已经所以把浏览器关掉了,这个时候服务器会把该 SessionID 从内存中清除掉,这个时候如果我们再去请求服务器,SessionID 已经不存在了,所以服务器并没有在内存中找到对应的 SessionID,所以会再产生一个新的 SessionID,这个时候一般我们又要再登录一次。客户端未使用 Cookie 保存 SessionID此时如果我们请求服务器,因为没有提交 SessionID 上来,服务器会认为你是一个全新的请求,服务器会给你分配一个新的 SessionID,这就是为什么我们每次打开一个新的浏览器的时候(无论之前我们有没有登录过)都会产生一个新的 SessionID(或者是会让我们重新登录)。当我们一旦把浏览器关掉后,再打开浏览器再请求该页面,它会让我们登录,这是为什么?我们明明已经登录了,而且还没有超时,SessionID 肯定还在服 务器上的,为什么现在我们又要再一次登录呢?这是因为我们关掉浏览再请求的时候,我们提交的信息没有把刚才的 SessionID 一起提交到服务器,所以服务器不知道我们是同一个人,所以这时服务器又为我们分配一个新的 SessionID。打个比方:浏览器就好像一个要去银行开户的人,而服务器就好比银行, 这个要去银行开户的人这个时候显然没有帐号( SessionID),所以到银行后,银行工作人员问有没有帐号,他说没有,这个时候银行就会为他开通一个帐号。所以可以这么说,每次打开一个新的浏览器去请求的一个页面的时候,服务器都会认为,这是一个新的请求,他为你分配一个新的 SessionID。基于以上问题,于是有人就会思考,服务器为什么要保存这些信息呢, 只让每个客户端去保存该多好?服务端只需把关好验证即可,因此在这种情况下,Token 应用而生。关于 Token 的理解什么是 Token ?Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。Token 的引入:Token 是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token 便应运而生。Token 的定义:Token 是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个 Token 便将此 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次带上用户名和密码。使用 Token 的目的:Token 的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。什么是 JWT?说到 Token 我们不得不谈 JWT,Why...? JWT 是 JSON Web Token 的缩写,是目前最流行的跨域认证解决方案。关于跨域认证的问题互联网服务离不开用户认证。一般流程是下面这样。用户向服务器发送用户名和密码。服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。服务器向用户返回一个 session_id,写入用户的 Cookie。用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。举例说明:A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。JWT 的原理JWT 的原理是,服务器认证以后,生成一个 Base64URL 编码后的 JSON 对象,发回给用户,JSON 明文信息如下(后面叙述)。以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。JWT 的数据结构Header(头部):是一个 JSON 对象,描述 JWT 的元数据;Payload(负载):也是一个 JSON 对象,用来存放实际需要传递的数据;Signature(签名):对前两部分的签名,防止数据篡改;说明:JWT 实际是一个很长的字符串,分别由【Header.Payload.Signature】组成,注意中间使用【.】分隔成三个部分。Header(头部)通常如下对象:{ "alg": "HS256", //alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256) "typ": "JWT" //typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT }Payload(负载)JWT 规定了 7 个官方字段,供选用:iss (issuer):签发人exp (expiration time):过期时间sub (subject):主题aud (audience):受众nbf (Not Before):生效时间iat (Issued At):签发时间jti (JWT ID):编号除了上面官方规定的字段,你还可以在这个部分自定义私有字段,下面就是一个例子:{ "sub": "1234567890", "name": "chait", "admin": true }注意:JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。Signature(签名)首先,需要指定一个密钥(secret),这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)算出签名以后,把 Header(Base64URL编码)、Payload(Base64URL编码)、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。Base64URL 算法前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同,区别如下:JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_。JWT 的使用方式客户端接收到服务器返回的 JWT,可以存储在 Cookie 或 Local Storage;客户端每次与服务器通信,都要带上这个 JWT;你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,针对跨域提供两种方案:更好的做法是放在 HTTP 请求的头信息【Authorization】字段里面或者是自定义约定字段;JWT 就放在 POST 请求的数据体里面;JWT 的几个特点(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。基于 Token 的身份验证原理基于 Token 的身份验证是无状态的,我们不用将用户信息存在服务器或 Session 中。这种概念解决了在服务端存储信息时的许多问题。没有 session 信息意味着你的程序可以根据需要去增减机器,而不用去担心用户是否登录和已经登录到了哪里。虽然基于 Token 的身份验证实现的方式很多,但大致过程如下:用户通过用户名和密码发送请求。程序验证。程序返回一个签名的 token 给客户端。客户端储存 token, 并且每次请求都会附带它。服务端验证 token 并返回数据。每一次请求都需要 Token。Token 应该在 HTTP的头部发送从而保证了 Http 请求无状态。我们也需要设置服务器属性 【Access-Control-Allow-Origin: * 】来让服务器能接受到来自所有域的请求。需要注意的是,在 ACAO 头部指定 * 时,不得带有像 HTTP 认证,客户端 SSL 证书和 cookies 的证书。执行流程如下:执行流程说明:用户登录校验,校验成功后就返回 Token 给客户端。客户端收到 Token 以后可以把它存储起来,比如放在 localStorage 中。客户端每次访问 API 都(通常 http 请求头)携带 Token 到服务器端。服务器端采用 filter 过滤器校验。校验成功则返回请求数据,校验失败则返回错误码。当我们在程序中认证了信息并取得 token 之后,我们便能通过这个 token 做许多的事情。我们甚至能基于创建一个基于权限的 token 传给第三方应用程序,这些第三方程序能够获取到我们的数据(当然只限于该 token 被允许访问的数据)。Tokens 的优势和缺陷那么相对于 Cookie 和 Session,Token 有哪些优缺点呢?1、Token 的优势支持跨域访问: Cookie 是不允许垮域访问的,token 支持;无状态、可扩展: token 无状态,session 有状态的;去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在 你的API被调用的时候, 你可以进行 Token 生成调用即可.多平台支持: Cookie 不支持手机端访问的,token 支持,更适用于移动应用;基于标准化: 你的 API 可以采用标准化的 JSON Web Token (JWT)。这个标准已经存在多个后端库(.NET,Ruby,Java,Python,PHP 等)和多家公司的支持(如:Firebase,Google,Microsoft 等);2、Token 的缺陷带宽占用:正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10 万次的浏览器,就意味着要多开销几十兆的流量。听起来并不多,但日积月累也是不小一笔开销。实际上,许多人会在 JWT 中存储的信息会更多。安全隐患:无法在服务端注销,服务器一旦生产 Token 并下发客户端,在 Token 有效期内很难解决劫持问题。性能问题:JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。但是大多数 Web 身份认证应用中,JWT 都会被存储到 Cookie 中,这就是说你有了两个层面的签名。听着似乎很牛逼,但是没有任何优势,为此,你需要花费两倍的 CPU 开销来验证签名。对于有着严格性能要求的 Web 应用,这并不理想,尤其对于单线程环境。关于 JWT、JWS 与 JWE 的区别,此处不再详述,请自行查看 => https://blog.csdn.net/ChaITSimpleLove/article/details/120178667
Windows 环境安装 WSL2WSL 简介WSL 全称是 Windows Subsystem for Linux ,适用于 Linux 的 Windows 子系统,可让开发人员按原样运行 GNU/Linux 环境,包括大多数命令行工具、实用工具和应用程序,且不会产生传统虚拟机或双启动设置开销。简单的说就是 WSL 能让你在不安装 Linux 或者 VM(虚拟机)的情况下体验双系统!关于 WSL 更多信息,请查看 => https://learn.microsoft.com/zh-cn/windows/wsl/about比较 WSL 版本,请查看 => https://learn.microsoft.com/zh-cn/windows/wsl/compare-versionsWindows 系统配置要求:对于 x64 系统:版本 1903 或更高版本,内部版本为 18362 或更高版本。对于 ARM64 系统:版本 2004 或更高版本,内部版本为 19041 或更高版本。例如:Windows 10 May 2020(2004)、Windows 10 May 2019(1903)、Windows 10 November 2019(1909)或 Windows 11。注意:低于 18362 的版本不支持 WSL 2。 使用 Windows Update 助手更新 Windows 版本。Windows Update 助手 下载地址,https://www.microsoft.com/zh-cn/software-download/windows10具有 Hyper-V 虚拟化技术支持的计算机;关于虚拟化技术说明:虚拟化技术(Virtualization Technology,简称 VT),这种技术简单来说就是让可以让一个 CPU 工作起来就像多个 CPU 并行运行,从而使得在一台电脑内可以同时运行多个 OS 操作系统。英特尔(Intel) 和 AMD 的大部分 CPU 均支持 VT 技术:特尔(Intel) 名称为 VT-x;AMD 名称为 AMD-V;VT 开启之后对 VM 的性能有较大的提高。(比如:VMware、Hyper、安卓模拟器 等)参考:https://zhuanlan.zhihu.com/p/394990397检查 Windows 版信息检查 Windows 版本及内部版本号,选择【Windows 徽标键 + R】,然后键入“winver”,选择【确定】。Windwos 安装 WSL2说明:以下操作均以【管理员】身份打开 Windows PowerShell 或 Windows Terminal,输入指令执行。此处以 Windows Terminal 为例,操作步骤如下:1、启用 WSL不管您想要使用哪个版本的 WSL,都首先需要启用它。Windows Terminal输入命令:dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart2、启用 “虚拟机平台”WSL2 需要启用 Windows 10 的 “虚拟机平台” 特性。它独立于 Hyper-V,并提供了一些在 Linux 的 Windows 子系统新版本中可用的更有趣的平台集成。Windows 10(2004) 上启用虚拟机平台:dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestartWindows 10(1903,1909) 上启用虚拟机平台:Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart说明:此步骤为了确保所有相关部件配置生效,您应该在此时重启系统,否则可能会发不符合预期的情况。3、设置 WSL2 为默认值Windows Terminal 输入以下命令,将 WSL2 设置为 WSL 的默认版本:wsl --set-default-version 24、安装一个 Linux 发行版有了 WSL 和 必要的虚拟化技术,接下来去 Microsoft Store 下载并安装 Linux 发行版,这里以 Ubuntu 22.04.2 LTS 为例。注意:去商店(Microsoft Store)安装的 Linux 发行版只能安装在 C 盘。4.1 Microsoft Store 搜索 linux,选择 Ubuntu 22.04.2 LTS4.2 Ubuntu 22.04.2 LTS 详情:4.3 点击【安装】按钮,等待安装完成。4.4 安装完以后在 Windows Terminal 中点开 Ubuntu,按照提示初次设置用户名和密码,注意用户名不支持大写。4.5 使用 WSL2最后显示 Installation successful! 就说明安装成功!整个过程挺简单的,毕竟是 WSL(Windows 的子系统),对 Windows 用户很友好。Windows 环境安装 Docker Desktop下载 Docker Desktop注意选择 OS 平台下载相应的 Docker Desktop(下载地址 => https://docs.docker.com/desktop/),下载后以【管理员】身份运行 Docker Desktop 执行安装操作。安装 Docker DesktopDocker Desktop 安装好后,启动运行:配置 Docker Desktop点击设置【Settings】选择【Docker Engine】添加如下配置:json 配置信息如下:{ "builder": { "gc": { "defaultKeepStorage": "20GB", "enabled": true "dns": [ "8.8.8.8", "8.8.4.4" "experimental": false, "features": { "buildkit": true "insecure-registries": [ "https://hub.atguigu.com" "registry-mirrors": [ "https://registry.docker-cn.com", "http://hub-mirror.c.163.com", "https://mirror.ccs.tencentyun.com", "https://docker.mirrors.ustc.edu.cn", "https://cr.console.aliyun.com/" }添加配置后,点击左下角的【Apply & restart】按钮,等待配置生效并重启完成。终端命令验证 Dcoker 是否安装成功接下来我们打开 Windows Terminal,输入命令:docker version输出如下信息:Client: Cloud integration: v1.0.29 Version: 20.10.22 API version: 1.41 Go version: go1.18.9 Git commit: 3a2c30b Built: Thu Dec 15 22:36:18 2022 OS/Arch: windows/amd64 Context: default Experimental: true Server: Docker Desktop 4.16.3 (96739) Engine: Version: 20.10.22 API version: 1.41 (minimum version 1.12) Go version: go1.18.9 Git commit: 42c8b31 Built: Thu Dec 15 22:26:14 2022 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.6.14 GitCommit: 9ba4b250366a5ddde94bb7c9d1def331423aa323 runc: Version: 1.1.4 GitCommit: v1.1.4-0-g5fd4c4d docker-init: Version: 0.19.0 GitCommit: de40ad0或者输入命令:docker info输出如下信息:Client: Context: default Debug Mode: false Plugins: buildx: Docker Buildx (Docker Inc., v0.10.0) compose: Docker Compose (Docker Inc., v2.15.1) dev: Docker Dev Environments (Docker Inc., v0.0.5) extension: Manages Docker extensions (Docker Inc., v0.2.17) sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc., 0.6.0) scan: Docker Scan (Docker Inc., v0.23.0) Server: Containers: 7 Running: 3 Paused: 0 Stopped: 4 Images: 30 Server Version: 20.10.22 Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Native Overlay Diff: true userxattr: false Logging Driver: json-file Cgroup Driver: cgroupfs Cgroup Version: 1 Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog Swarm: inactive Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc Default Runtime: runc Init Binary: docker-init containerd version: 9ba4b250366a5ddde94bb7c9d1def331423aa323 runc version: v1.1.4-0-g5fd4c4d init version: de40ad0 Security Options: seccomp Profile: default Kernel Version: 5.10.16.3-microsoft-standard-WSL2 Operating System: Docker Desktop OSType: linux Architecture: x86_64 CPUs: 16 Total Memory: 13.59GiB Name: docker-desktop ID: SRPD:RTAI:YAGO:44HL:DPFW:KUPZ:RPYW:OX3X:VBZP:24GQ:YFVN:NPLX Docker Root Dir: /var/lib/docker Debug Mode: false HTTP Proxy: http.docker.internal:3128 HTTPS Proxy: http.docker.internal:3128 No Proxy: hubproxy.docker.internal Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: hubproxy.docker.internal:5000 hub.atguigu.com 127.0.0.0/8 Registry Mirrors: https://registry.docker-cn.com/ http://hub-mirror.c.163.com/ https://mirror.ccs.tencentyun.com/ https://docker.mirrors.ustc.edu.cn/ https://cr.console.aliyun.com/ Live Restore Enabled: false看到上面输出的信息,说明 Windows 环境的 Docker Desktop 就安装完成了,此处是使用 WSL2 安装的 linux 发行版,我们可以 linux 原生环境体验 Docker 的相关操作。
需求概述分布式系统中,有一些需要使用 全局唯一 ID 的场景,这种时候为了防止 ID 冲突 可以使用 36 位的通用唯一识别码 / UUID(Universally Unique Identifier),但是 UUID 有一些缺点,首先他相对比较长,另外 UUID 一般是无序的。有些时候我们希望能使用一种简单一些的 ID,并且希望 ID 能够按照时间有序生成。Twitter-Snowflake 产生背景Twitter 早期用 MySQL 存储数据,随着用户的增长,单一的 MySQL 实例没法承受海量的数据,后来团队就研究如何产生完美的自增 ID,以满足两个基本的要求:• 每秒能生成几十万条 ID 用于标识不同的记录;• 这些 ID 应该可以有个大致的顺序,也就是说发布时间相近的两条记录,它们的 ID 也应当相近,这样才能方便各种客户端对记录,进行排序。【Twitter-Snowflake】算法就是在这样的背景下产生的。Snowflake 核心结构Twitter 解决这两个问题的方案非常简单高效:每一个 ID 都是 64 位数字,由时间戳、工作机器节点和序列号组成,ID 是由当前所在的机器节点生成的。如图:下面先说明一下各个区间的作用:符号位(Symbol bit):用于区分正负数。1 为负数,0 为整数。一般不需要负数,所以值固定为0;时间戳(Time stamp):一共预留 41bit 保存毫秒级时间戳。因为毫秒级时间戳长度是 13 位:41 位二进制最大值 (T) 是:$2^{41}-1 = 2199023255551 $ , 刚好 13 位。可以表示的年份 = T / (360024 365 * 1000) = 69.7 年(时间戳是从 1970, 1, 1, 0, 0, 0 开始)。换算成 Unix 时间也就是可以表示到:2039-09-07 23:47:35;大家会觉得这个时间不够用啊,没关系,后面会讲如何优化。工作机器(Work machine):预留了 10bit 保存机器 ID。由 5 位 datacenterId 和 5 位 workerId (10 位的长度最多支持部署 1024 个节点)组合,只要机器 ID 不一样,每毫秒生成的 ID 是不一样的。一共可以支持多少台机器同时生成 ID 呢? 答案是 1023 台($2^{10}-1$);如果工作机器比较少,可以使用配置文件来设置这个 ID,或者使用随机数。如果机器过多就得单独实现一共工作机器 ID 分配器了,比如使用 redis 自增,或者利用 Mysql auto_increment 机制也可以达到效果。序列号(Serial number):序列号一共是 12bit,为了处理在同一机器同一毫秒内需要给多条消息分配 ID 的情况,一共可以产生 4095 个序列号(0~4095, $2^{12}-1$);综上:一共加起来刚好 64=>(1+41+10+12) 位,为一个 Long 型(转换成字符串长度为 19),同一台机器 1 毫秒内可产生 4095 个 ID,全部机器 1 毫秒内可产生 4095 * 1023 个 ID。Snowflake 生成的 ID 整体上按照时间自增排序,并且整个分布式系统内不会产生 ID 碰撞(由 datacenter 和 workerId 作区分),由于全是在各个机器本地生成,效率非常高。关于优化1、时间戳优化如果时间戳取当前毫秒级时间戳,那么只能表示到 2039 年,远远不够。我们发现,1970 到当前时间这个区间其实是永远都不会用了,那么,为何不使用偏移量呢?也就是时间戳部分不直接取当前毫秒级时间戳,而是在此基础上减去一个过去时间:id = (1572057648000 - 1569859200000) << 22; 输出:id=9220959240192000上面代码中,第一个时间戳是当前毫秒级时间戳,第二个则是一个过去时间戳(1569859200000 表示 2019-10-01 00:00:00)。这样我们可以表示的年大概是 当前年份(例如2019) + 69 = 2088 年,很长一段时间内都够用。2、序列号优化序列号默认取 0,如果已经使用了则自增。若自增到 4096,也就是同一毫秒内的序列号用完了,怎么办呢?需要等待至下一毫秒。部分代码示例://同一毫秒并发调用 if (ts == (iw.last_time_stamp)) { //序列号自增 iw.sequence = (iw.sequence+1) & MASK_SEQUENCE; //序列号自增到最大值4096,4095 & 4096 = 0 if (iw.sequence == 0) { //等待至下一毫秒 ts = time_re_gen(ts); } else { //同一毫秒没有重复的 iw.last_time_stamp = ts; }C# 实现分布式自增 ID 算法(Snowflake 雪花算法)通用泛型单例(ReflectionSingleton)实现,如下代码:using System; using System.Reflection; namespace NSMS.Helper /// <summary> /// 普通泛型单例模式 /// 优点:简化单例模式构建,不需要每个单例类单独编写; /// 缺点:违背单例模式原则,构造函数无法设置成private,导致将T类的构造函数暴露; /// </summary> /// <typeparam name="T">class</typeparam> [Obsolete("Recommended use ReflectionSingleton")] public abstract class Singleton<T> where T : class, new() protected static T _Instance = null; public static T Instance if (_Instance == null) _Instance = new T(); return _Instance; protected Singleton() Init(); public virtual void Init() /// <summary> /// 反射实现泛型单例模式【推荐使用】 /// 优点:1.简化单例模式构建,不需要每个单例类单独编写;2.遵循单例模式构建原则,通过反射去调用私有的构造函数,实现了构造函数不对外暴露; /// 缺点:反射方式有一定的性能损耗(可忽略不计); /// </summary> /// <typeparam name="T">class</typeparam> public abstract class ReflectionSingleton<T> where T : class private static T _Intance; public static T Instance if (null == _Intance) _Intance = null; Type type = typeof(T); //1.类型强制转换 //2.获取到T的构造函数的类型和参数信息,监测构造函数是私有或者静态,并且构造函数无参,才会进行单例的实现 ConstructorInfo[] constructorInfoArray = type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic); foreach (ConstructorInfo constructorInfo in constructorInfoArray) ParameterInfo[] parameterInfoArray = constructorInfo.GetParameters(); if (0 == parameterInfoArray.Length) //检查构造函数无参,构建单例 _Intance = (T)constructorInfo.Invoke(null); break; if (null == _Intance) //提示不支持构造函数公有且有参的单例构建 throw new NotSupportedException("No NonPublic constructor without 0 parameter"); return _Intance; protected ReflectionSingleton() { } public static void Destroy() _Intance = null; }Snowflake 分布式 id 实现,如下代码:using System; using System.Threading; namespace NSMS.Helper /// <summary> /// 【C#实现Snowflake算法】 /// 动态生产有规律的ID,Snowflake算法是Twitter的工程师为实现递增而不重复的ID需求实现的分布式算法可排序ID /// Twitter的分布式雪花算法 SnowFlake 每秒自增生成26个万个可排序的ID /// 1、twitter的SnowFlake生成ID能够按照时间有序生成 /// 2、SnowFlake算法生成id的结果是一个64bit大小的整数 /// 3、分布式系统内不会产生重复id(用有datacenterId和machineId来做区分) /// =>datacenterId(分布式)(服务ID 1,2,3.....) 每个服务中写死 /// =>machineId(用于集群) 机器ID 读取机器的环境变量MACHINEID 部署时每台服务器ID不一样 /// 参考:https://www.cnblogs.com/shiningrise/p/5727895.html /// </summary> public class Snowflake : ReflectionSingleton<Snowflake> /// <summary> /// 构造函数私有化 /// </summary> private Snowflake() { } #region 初始化字段 private static long machineId;//机器ID private static long datacenterId = 0L;//数据ID private static long sequence = 0L;//序列号,计数从零开始 private static readonly long twepoch = 687888001020L; //起始的时间戳,唯一时间变量,这是一个避免重复的随机量,自行设定不要大于当前时间戳 private static readonly long machineIdBits = 5L; //机器码字节数 private static readonly long datacenterIdBits = 5L; //数据字节数 public static readonly long maxMachineId = -1L ^ -1L << (int)machineIdBits; //最大机器ID public static readonly long maxDatacenterId = -1L ^ (-1L << (int)datacenterIdBits);//最大数据ID private static readonly long sequenceBits = 12L; //计数器字节数,12个字节用来保存计数码 private static readonly long machineIdShift = sequenceBits; //机器码数据左移位数,就是后面计数器占用的位数 private static readonly long datacenterIdShift = sequenceBits + machineIdBits; //数据中心码数据左移位数 private static readonly long timestampLeftShift = sequenceBits + machineIdBits + datacenterIdBits; //时间戳左移动位数就是机器码+计数器总字节数+数据字节数 public static readonly long sequenceMask = -1L ^ -1L << (int)sequenceBits; //一微秒内可以产生计数,如果达到该值则等到下一微妙在进行生成 private static long lastTimestamp = -1L;//最后时间戳 private static readonly object syncRoot = new object(); //加锁对象 #endregion #region Snowflake /// <summary> /// 数据初始化 /// </summary> /// <param name="machineId">机器Id</param> /// <param name="datacenterId">数据中心Id</param> public void SnowflakesInit(short machineId, short datacenterId) if (machineId < 0 || machineId > Snowflake.maxMachineId) throw new ArgumentOutOfRangeException($"The machineId is illegal! => Range interval [0,{Snowflake.maxMachineId}]"); Snowflake.machineId = machineId; if (datacenterId < 0 || datacenterId > Snowflake.maxDatacenterId) throw new ArgumentOutOfRangeException($"The datacenterId is illegal! => Range interval [0,{Snowflake.maxDatacenterId}]"); Snowflake.datacenterId = datacenterId; /// <summary> /// 生成当前时间戳 /// </summary> /// <returns>时间戳:毫秒</returns> private static long GetTimestamp() return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; /// <summary> /// 获取下一微秒时间戳 /// </summary> /// <param name="lastTimestamp"></param> /// <returns>时间戳:毫秒</returns> private static long GetNextTimestamp(long lastTimestamp) long timestamp = GetTimestamp(); int count = 0; while (timestamp <= lastTimestamp)//这里获取新的时间,可能会有错,这算法与comb一样对机器时间的要求很严格 count++; if (count > 10) throw new Exception("The machine may not get the right time."); Thread.Sleep(1); timestamp = GetTimestamp(); return timestamp; /// <summary> /// 获取长整形的ID /// </summary> /// <returns>分布式Id</returns> public long NextId() lock (syncRoot) long timestamp = GetTimestamp(); if (Snowflake.lastTimestamp == timestamp) //同一微妙中生成ID Snowflake.sequence = (Snowflake.sequence + 1) & Snowflake.sequenceMask; //用&运算计算该微秒内产生的计数是否已经到达上限 if (Snowflake.sequence == 0) //一微妙内产生的ID计数已达上限,等待下一微妙 timestamp = GetNextTimestamp(Snowflake.lastTimestamp); //不同微秒生成ID Snowflake.sequence = 0L; //计数清0 if (timestamp < Snowflake.lastTimestamp) //如果当前时间戳比上一次生成ID时时间戳还小,抛出异常,因为不能保证现在生成的ID之前没有生成过 throw new Exception($"Clock moved backwards. Refusing to generate id for {Snowflake.lastTimestamp - timestamp} milliseconds!"); Snowflake.lastTimestamp = timestamp; //把当前时间戳保存为最后生成ID的时间戳 long id = ((timestamp - Snowflake.twepoch) << (int)Snowflake.timestampLeftShift) | (datacenterId << (int)Snowflake.datacenterIdShift) | (machineId << (int)Snowflake.machineIdShift) | Snowflake.sequence; return id; #endregion }以上方法就完成了 Snowflake 算法的 C# 实现,还可以基于该算法结合业务扩展,比如生产的 id 带有一定的业务意义,此处还扩展了 6 为长度的随机字符串,例如订单编号:order 前缀标记,修改如下:using System; using System.Text; namespace NSMS.Helper /// <summary> /// 集成ID生产规则 /// </summary> public class IdWorker: ReflectionSingleton<IdWorker> /// <summary> /// 构造函数私有化 /// </summary> private IdWorker() { } #region 获取格式化GUID public enum GuidType { N, D, B, P, X, Default }; public enum IsToUpperOrToLower { ToUpper, ToLower }; public string GetFormatGuid(GuidType guidType = GuidType.N, IsToUpperOrToLower isToUpperOrToLower = IsToUpperOrToLower.ToLower) string guid = guidType switch GuidType.N => Guid.NewGuid().ToString("N"), // e0a953c3ee6040eaa9fae2b667060e09 GuidType.D => Guid.NewGuid().ToString("D"), // 9af7f46a-ea52-4aa3-b8c3-9fd484c2af12 GuidType.B => Guid.NewGuid().ToString("B"), // {734fd453-a4f8-4c5d-9c98-3fe2d7079760} GuidType.P => Guid.NewGuid().ToString("P"), // (ade24d16-db0f-40af-8794-1e08e2040df3) GuidType.X => Guid.NewGuid().ToString("X"), // (ade24d16-db0f-40af-8794-1e08e2040df3) GuidType.Default => Guid.NewGuid().ToString(), // {0x3fa412e3,0x8356,0x428f,{0xaa,0x34,0xb7,0x40,0xda,0xaf,0x45,0x6f}} _ => throw new NotImplementedException(), switch (isToUpperOrToLower) case IsToUpperOrToLower.ToUpper: guid = guid.ToUpper(); //返回大写GUID break; case IsToUpperOrToLower.ToLower: guid = guid.ToLower(); //返回小写GUID break; return guid; #endregion /// <summary> /// 获取机器唯一编码 /// </summary> /// <returns></returns> public string GetMachineCodeString() => MachineCode.GetMachineCodeString(); /// <summary> /// 获取分布式Id(Snowflake) /// </summary> /// <param name="prefix">业务标识前缀</param> /// <param name="machineId">机器Id(集群环境的服务器Id)</param> /// <param name="datacenterId">分布式数据中心Id(服务Id)</param> /// <param name="hasRandom">是否开启随机变量</param> /// <returns></returns> public string GetSnowflakeId(string prefix, short machineId, short datacenterId, bool hasRandom = true) Snowflake.Instance.SnowflakesInit(machineId, datacenterId); string randomNo = GenerateRandomNumber(6); if (hasRandom) if (string.IsNullOrWhiteSpace(prefix)) return $"{randomNo}.{Snowflake.Instance.NextId()}"; else return $"{prefix}.{randomNo}.{Snowflake.Instance.NextId()}"; if (string.IsNullOrWhiteSpace(prefix)) return $"{Snowflake.Instance.NextId()}"; else return $"{prefix}.{Snowflake.Instance.NextId()}"; #region 获取随机数 /// <summary> /// 随机数基础数据 /// </summary> private readonly char[] _RandomBasicData = '0','1','2','3','4','5','6','7','8','9', 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' /// <summary> /// 生产随机数 /// </summary> /// <param name="length">随机数长度</param> /// <returns></returns> public string GenerateRandomNumber(int length) int capacity = _RandomBasicData.Length; StringBuilder newRandom = new StringBuilder(capacity); Random rd = new Random(); for (int i = 0; i < length; i++) newRandom.Append(_RandomBasicData[rd.Next(capacity)]); return newRandom.ToString(); #endregion }接下来我们调用上面的方法生产测试结果,调用代码如下:System.Console.WriteLine("【原生使用】Snowflake 生产分布式 id."); Snowflake.Instance.SnowflakesInit(0, 0); //【Snowflake】初始化 for (int i = 0; i < 5; i++) long id = Snowflake.Instance.NextId(); //生产id System.Console.WriteLine($"=>序号:[{i + 1}],时间:[{DateTime.Now:yyyy-MM-ddTHH:mm:ss.ffff}],id=[{id}]"); System.Console.WriteLine($"\n【扩展使用】Snowflake 生产分布式 id.扩展业务前缀和随机串."); for (int i = 0; i < 5; i++) string id = IdWorker.Instance.GetSnowflakeId("order", 1, 0); //生产id System.Console.WriteLine($"=>序号:[{i + 1}],时间:[{DateTime.Now:yyyy-MM-ddTHH:mm:ss.ffff}],id=[{id}]"); }上面调用代码为了演示【原生】和【扩展】方式每种生产 5 条信息(以时间为参考区分),结果如下:参考:TwitterSnowflake 自增 ID 算法 =》https://lequ7.com/TwitterSnowflake-zi-zeng-ID-suan-fa.htmlC# 实现 Snowflake 算法 =》https://blog.csdn.net/w200221626/article/details/52064976
应用场景分析假设有一组已知数量的数据,按照一定的业务处理规则处理并保存数据库,如何提升数据处理的效率并完成数据保存(具体情况具体分析)?此处使用控制台方式模拟输入数据(类比保存数据库处理)。using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApi /// <summary> /// 多线程批量数据处理 /// </summary> public class MultithreadBatchDataProcessing // 线程安全队列 private ConcurrentQueue<ResponseModel> queue = new ConcurrentQueue<ResponseModel>(); /// <summary> /// 模拟设置数据 /// </summary> public void SetData() Console.WriteLine($"开始数据设置,时间:{DateTime.Now};"); for (int i = 0; i < 10000; i++) var model = new ResponseModel { Code=i, Msg=$"第{i+1}次循环", Data=$"产生随机数:{new Random().Next(1000,10000)}" }; queue.Enqueue(model); // 模拟数据入队 Thread.Sleep(1); // 这里是随机数生成时需要 Console.WriteLine($"10000条数据设置完毕!时间:{DateTime.Now};"); /// <summary> /// 多线程处理数据 /// </summary> public void MultitDataProcessing() int threadCount = 10; // 开启 10 个线程 for (int i = 0; i < threadCount; i++) string fileName = $"task{i}.txt"; // 开启新线程 Task.Factory.StartNew(() => var sb = new StringBuilder(); int j = 0; // 数据循环出队 while (queue.TryDequeue(out ResponseModel model)) // 处理数据 if (model != null) sb.AppendLine($"==》Code={model.Code},Msg={model.Msg},Data={model.Data}"); if (j % 100 == 0 || (queue.Count.Equals(0) && j < 100)) Console.WriteLine($"每100条输出一次控制台,并暂停100毫秒, 第{i}次文件:{fileName}"); Console.WriteLine(sb.ToString()); sb = new StringBuilder(); Thread.Sleep(100); }模拟多条数据批量处理模拟 10000 条数据批量处理,每 100 条数据保存一次,调用显示如下:var mbdp = new MultithreadBatchDataProcessing(); mbdp.SetData(); mbdp.MultitDataProcessing();C# 多线程批量数据处理演示 Demo 完毕。保存数据库(MS-SQLServer)时可以创建 DataTable 采用 SqlBulkCopy 每 100 条推入一次即可;
今天迁移 web 服务突然遇到一个 Error 问题,但原有的 web 发布文件是可以正常部署 IIS10 上面运行的,这是啥情况呢?顺便把解决该问题的全过程整理出来,分享给更多遇到类似情况的小伙伴。由于本地电脑环境迁移,重新换了一个宿主机部署 IIS10,网站运行提示如下信息: HTTP 错误 500.19 - Internal Server Error无法访问请求的页面,因为该页的相关配置数据无效。详细错误信息:模块 IIS Web Core通知未知处理程序尚未确定错误代码0x8007000d配置错误配置文件\\?\E:\IISPublishFiles\sws-snms-service\web.config为了证实原有 web 正常运行,此处使用 dotnet self-hosted 自宿主模式运行,保留个截图~///(^v^)\\~ 原因分析及解决方案 =》IIS 网页中INTERNET INFORMATION SERVICES (HTTP) 500.19 - Internet Information Services | Microsoft Docs按照上面提供的解决方案排查,检查发布文件中的 web.config 格式正常,如下所示:<?xml version="1.0" encoding="utf-8"?> <configuration> <location path="." inheritInChildApplications="false"> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\SNMS.Services.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="InProcess" /> </system.webServer> </location> </configuration> <!--ProjectGuid: 19ca8b58-6aba-47c6-82f9-cde1e1fa2c9c-->接下来就继续安装相关的 IIS URL 重写模型 / URL Rewrite =》 URL Rewrite : The Official Microsoft IIS Site下载该文件后以管理员身份运行安装,如下所示:等待安装完成单击完成,然后以管理员身份运行 PowerShell ,执行如下命令,重启 IIS【URL 重写】模块已成在 `IIS` 中安装,再次尝试运行(部署的)web服务,结果还是出现相同的错误信息,有点郁闷,经过排查,最终发现是 `IIS` 的【模块】没有安装(开发调试正常就搞忘记了),由于部署的是 `.net 5.0.4` 版本,选择对应版本下载安装,此处是 `v5.0.4` 的 Hosting Bundle =》Download .NET 5.0 (Linux, macOS, and Windows) (microsoft.com)安装完成后显示如下,注意这里一定要对应好版本安装,版本不对可能会失败,如下所示:接下来使用 dotnet --info 命令查看安装的信息,再次重启 IIS,如下所示:最后运行 web 服务,可以正常运行啦,如下所示:以上就是整个过程的处理方案,希望能帮助到更多的小伙伴 ~///(^v^)\\~
YARP: 又一个反向代理欢迎来到 YARP 的文档!YARP 是一个库,用于帮助创建高性能、可生产且高度可定制的反向代理服务器。现在它仍然在预览中,但是请通过 GitHub 仓库 提供您的反馈。什么是 YARP ?我们发现微软的一些内部团队要么为他们的服务构建一个反向代理,要么为构建一个代理询问 api 的技术,所以我们决定让他们一起开发一个 共同的解决方案 ---- 这个项目。这些项目中的每一个都在做一些稍微偏离常规的事情,这意味着它们不能很好地得到现有代理的服务,而且这些代理的定制需要高昂的成本和持续的维护考虑。许多现有的代理都是为了支持 HTTP/1.1 而构建的,但是随着工作负载的变化包含了 gRPC 流量,它们需要 HTTP/2 的支持,这就需要一个非常复杂的实现。通过使用 YARP,项目可以自定义路由和处理行为,而无需实现 http 协议。使用 YAPRYARP 是使用 ASP.NET 和 .NET (.NET Core 3.1 和 .NET 5.0+)的基础架构构建在 .NET 上的。YARP 的主要不同之处在于,它被设计成可以通过 .NET 代码轻松定制和调整,以满足每个部署场景的特定需求。最终,我们希望 YARP 以库、项目模板和单文件可执行文件的形式发布,为构建健壮、高性能的代理服务器提供多种选择。它的流水线和模块都经过了设计,这样您就可以根据需要定制功能。例如,虽然 YARP 支持配置文件,但我们预计许多用户会希望基于他们自己的配置管理系统以编程方式管理配置。YARP 提供了一个配置 API 来支持进程内定制。YAPR 反向代理本文讨论的是 YARP - 反向代理。YARP 是一个库,用于帮助创建高性能、可生产且高度可定制的反向代理服务器。那么什么是反向代理呢?反向代理是放置在网络边缘的中间连接点。它接收初始HTTP连接请求,并根据配置表现为实际端点。反向代理充当应用程序和用户之间的网关。YARP 可以支持从 appsettings.json 或代码中进行配置。在这篇文章中,将探索如何在一个空的 ASP.NET Core Web 应用程序中使用 YARP。该应用程序将包括两个 ASP.NET Core MVC 应用程序(此处的两个应用程序就不创建了,改用两个目标 url 模拟)。首先,创建一个空的 web 应用程序。接下来你需要添加 YARP 包。添加 yapr 的 nuget 包在空的 asp.net core 应用中添加 yapr 的 nuget 包,可以用 dotnet cli 命令来做到这一点:dotnet add package Yarp.ReverseProxy --version 1.0.0-preview.12.21451.3配置并启用反向代理.net core 3.1 or .net 5 可以配置 Startup.cs 类来读取配置并启用反向代理。public class Startup public IConfiguration Configuration { get; set; } public Startup(IConfiguration configuration) Configuration = configuration; public void ConfigureServices(IServiceCollection services) services.AddReverseProxy() .LoadFromConfig(Configuration.GetSection("ReverseProxy")); // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseRouting(); app.UseEndpoints(endpoints => endpoints.MapReverseProxy(); }.net 6 中省略了 Startup.cs 类,可以在 Program.cs 类中添加如下代码来读取配置并启用反向代理。var builder = WebApplication.CreateBuilder(args); var config = builder.Configuration; var env = builder.Environment; var services = builder.Services; #region services // Add the reverse proxy to capability to the server var proxyBuilder = services.AddReverseProxy(); // Initialize the reverse proxy from the "ReverseProxy" section of configuration proxyBuilder.LoadFromConfig(config.GetSection("ReverseProxy")); #endregion var app = builder.Build(); #region app if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); // Enable endpoint routing, required for the reverse proxy app.UseRouting(); // Register the reverse proxy routes app.UseEndpoints(endpoints => endpoints.MapReverseProxy(); app.Run(); #endregion添加 appsettings.json 配置文件在 ConfigureServices 方法中,添加了反向代理中间件,配置从 appsettings.json 文件中读取。并映射 Configure 方法,路由到反向代理配置。接下来需要修改配置,可以通过编辑 appsettings.json 文件来完成。下面是反向代理配置。{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" "AllowedHosts": "*", "ReverseProxy": { "Routes": { "route1": { "ClusterId": "cluster1", "Match": { "Path": "{**catch-all}" "Clusters": { "cluster1": { "LoadBalancingPolicy": "RoundRobin", "Destinations": { "cluster1/destination1": { "Address": "https://www.jd.com/" "cluster1/destination2": { "Address": "https://www.taobao.com/" "cluster1/destination3": { "Address": "https://www.suning.com/" }这个配置主要有两个元素—— Routes 和 Clusters:Routes 中配置终结点路由和 url。Match 元素为所有路由配置代理。RouteId 是路由的唯一名称。ClusterId 用于标识后端应用服务器或 url。在 Clusters 中,配置了两个应用程序 url。可以是同一个应用程序运行在不同的端口上,也可以是不同的应用程序运行在各自的环境中。现在可以运行代理应用程序和其他 web 应用程序了。其他的 web 应用程序,你可以在不同的端口运行以下命令:dotnet run --url="https://localhost:xxxxx"上面我们已经准备好代理应用的程序,接下来我们运行看下效果,是否符合预期目标:curl http://localhost:5198执行上面访问命令,得到如下结果,符合预期目标。YARP 负载均衡配置策略First --- 选择第一个目标,不考虑负载。这对于双目标故障转移系统非常有用。Random --- 随机选择一个目标。PowerOfTwoChoices (默认) --- 选择两个随机的目标,然后从中选择一个更少请求的目标。这避免了 LeastRequests 的开销,也避免了 Random 选择繁忙目的地的最坏情况。RoundRobin --- 通过顺序循环选择一个目标。LeastRequests --- 所有目标中选择分配请求最少的目标。这需要检查所有目标。要配置任何其他负载均衡策略,可以在 cluster1 中添加 LoadBalancingPolicy 以上策略即可。YARP 默认不配置的情况,使用的是 PowerOfTwoChoices 策略。YARP 还通过检查目标应用程序的运行状况并基于路由请求来支持流量路由。如果你正在使用 ASP.NET Core 应用程序,可以启用 ASP.NET Core 运行状况检查选项。YARP 带来了很多新特性和改进。查看文档和主页(https://microsoft.github.io/reverse-proxy/?WT.mc_id=AZ-MVP-5002040),了解现有特性和如何使用它的更多细节。参考文章 :https://dotnetthoughts.net/getting-started-with-microsoft-yarp/YARP 文档:https://microsoft.github.io/reverse-proxy/?WT.mc_id=AZ-MVP-5002040
什么是 OPTIONS 请求OPTIONS 请求为 发送非简单跨域请求前的预检请求 ,若该请求未正常返回,浏览器会阻止后续的请求发送。一般情况下,有三种方式会导致浏览器发起预检请求: 请求的方法不是 GET/HEAD/POST; POST 请求的 Content-Type 并非 application/x-www-form-urlencoded, multipart/form-data 或 text/plain; 请求中设置了自定义的 header 字段(如 Token);显示 OPTIONS 请求默认情况下,Chrome 和 Microsoft Edge 浏览器不会在 F12 工具的网络选项卡上显示 OPTIONS 请求。 要在这些浏览器中显示 OPTIONS 请求,请执行以下操作:chrome://flags/#out-of-blink-cors 或 edge://flags/#out-of-blink-cors;禁用标记;重启;Firefox 浏览器默认显示 OPTIONS 请求。发送 OPTIONS 请求给服务器返回 404 若未对 IIS 进行配置,则会导致 OPTIONS 请求被 IIS 直接响应返回,而不会进入到代码中。这也是 Global 中的 Application_BeginRequest 无法捕获到 OPTIONS 请求的原因。检查 webconfig 中的配置,是否移除了对 options 请求的特殊处理可在 IIS 中进行配置: [网站]-[应用程序]-[处理程序映射] <system.webServer> <handlers> <remove name="OPTIONSVerbHandler" /> </handlers> </system.webServer>检查 IIS 服务器是否安装了 UrlScan,若安装了请检查 AllowVerbs 中是否包含了 options ,可在 IIS 中查看是否安装了 UrlScan : [网站]-[ISAPI筛选器] (可以找到 UrlScan 安装路径)UrlScan 的配置文件为 UrlScan.ini (C:\Windows\System32\inetsrv\urlscan\UrlScan.ini) 将 OPTIONS 从 [DenyVerbs] 中移除并添加到 [AllowVerbs] 下。在 Global.asax 的 Application_BeginRequest 实践中直接响应 OPTIONS 请求;注意:该方式为 asp.net (.net framework 时代)的处理//允许所有的 `options` 请求,直接返回 `200` 状态码 private void Application_BeginRequest(object sender, EventArgs e) if (HttpContext.Current.Request.HttpMethod == "OPTIONS") HttpContext.Current.Response.StatusCode = 200; HttpContext.Current.Response.Headers["Access-Control-Allow-Origin"] = HttpContext.Current.Request.Headers["origin"]; HttpContext.Current.Response.End(); }Application_Start 和 Application_End第一次访问站点时,创建 HttpApplication 对象,此时会触发 Application_Start,并创建HttpApplication 实例池,应用接请求就从池中获取实例,进行处理。在所有的 HttpApplication 实例闲置达到超时时间,触发应用程序池回收,或者重启站点时就会触发 Application_End 事件,应用程序池的闲置超时时间可以在 iis 设置,站点下 bin 目录下的文件发生改变,webconfig 配置改变等导致站点重启的事件都会触发 Application_End。Session_Start 和 Session_End单个用户访问 Web 应用时,启动会话,服务器为该用户创建一个独立的 Session 对象,并触发Session_Start 事件,此时Session处于可用状态。用户在该会话建立后可以发起若干次请求,当服务器一段时间内未收到用户请求,达到会话超时时间时,触发 Session_End 事件,服务器释放为当前用户保存 Session 的内存。也可以在应用程序中调用 Session.Abandon() 可以手动取消 Session,清空服务器保存的 Session,直到再次调用 Session 时,又会触发 Session_Start,但是 SessionID 不会变化。所有会触发 Application_End 的事件都会在此之前触发 Session_Start。可以在 Web.Config 中添加设置 Session 过期时间( timeout 单位为分钟)。Application_BeginRequest 和 Application_EndRequest在用户会话启动后,每次发起的请求都会触发 Application_BeginRequest 事件,并在请求完成时触发 Application_EndRequest 事件。在 webconfig 中的 Allow-Method 中添加上 options;<system.webServer> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Methods" value="OPTIONS,POST,GET" /> <add name="Access-Control-Allow-Headers" value="x-requested-with,aspxauth" /> <add name="Access-Control-Allow-Credentials" value="true" /> </customHeaders> </httpProtocol> </system.webServer>发送 OPTIONS 请求给服务器返回 405ajax 跨域在某些情况下会发送 OPTIONS 请求给服务器,如无相关设置会返回 405 错误,在 asp.net core 3.1 webapi 下通过中间件来处理 OPTIONS 请求。OptionsRequestMiddleware 扩展中间件using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Microsoft.AspNetCore.Builder /// <summary> /// 中间件 /// </summary> public class OptionsRequestMiddleware private readonly RequestDelegate _next; public OptionsRequestMiddleware(RequestDelegate next) _next = next; public async Task Invoke(HttpContext context) if (context.Request.Method.ToUpper() == "OPTIONS") context.Response.StatusCode = 200; return; await _next.Invoke(context); /// <summary> /// 扩展中间件 /// </summary> public static class OptionsRequestMiddlewareExtensions public static IApplicationBuilder UseOptionsRequest(this IApplicationBuilder app) return app.UseMiddleware<OptionsRequestMiddleware>(); }在 Startup.cs 里面的 Configure 中使用扩展中间件public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseOptionsRequest(); // 使用扩展中间件 }在 asp.net core 中优化 OPTIONS 请求在 ASP.NET Core 中启用跨源请求 (CORS):了解更多请查看 => https://docs.microsoft.com/zh-cn/aspnet/core/security/cors?view=aspnetcore-6.0在跨域策略中设置 SetPreflightMaxAge// 添加跨域策略 services.AddCors(options => string corsPolicyName = _config["CorsPolicyName"]; // 自定义跨域策略名称 options.AddPolicy(corsPolicyName, builder => builder.AllowAnyOrigin() // 允许任何来源的主机访问 //.AllowAnyHeader() // 允许任何的Header头部标题 .WithHeaders("Account", "ClientType", "OrgId", "Token", "Department", "EntAuthVebr") // 自定义请求头 //.AllowAnyMethod() // 允许任何方法 .WithMethods(HttpMethods.Options, HttpMethods.Get, HttpMethods.Post, HttpMethods.Put, HttpMethods.Delete) // 允许的谓词方法 .SetPreflightMaxAge(TimeSpan.FromHours(24)); // 设置预检请求的最大缓存时间 app.UseCors(corsPolicyName); //使用跨域总结了解 OPTIONS 请求的基本功能、作用和大概拦截的原因,逐一排查,分别讲解在 asp.net (.net framework 时代)和 asp.net core (.net core/.net 时代) 的处理方式,OPTIONS 请求在不同的浏览器中默认请求行为表现不一致,通过设置 SetPreflightMaxAge (asp.net core 方式)的最大缓存时间,间接的优化 OPTIONS 请求,减少服务器环境的预检测次数,你是否也遇到类似的问题呢?
安装准备工作说明:以下配置要求为开发环境部署最小规格,不建议生产环境按照此规格搭建部署环境。安装环境要求 master cpu/内存:2 Core/2G cpu/内存:2 Core/4G linux 内核4.4+ cpu/内存:1 Core/2G cpu/内存:4 Core/16G linux 内核4.4+ Node:应根据需要运行的容器数量进行配置; Linux 操作系统基于 x86_64 架构的各种 Linux 发行版本,Kernel 版本要求在 4.4 及以上;【查看 Linux 内核:$ uname -f】 以下环境配置 OS 基于 Linux for ubuntu-server 20.04 Linux Kernel =》5.4.0-47-generic 查看 linux 内核版本=》在命令行中查看 Linux 内核版本的3种方法_Linux教程_Linux公社-Linux系统门户网站 以上为建议配置,实际安装过程中,Master 必须 2 core 及以上(否则安装失败,切记),Node 可以采用 1 core。 单 Master 部署环境说明 k8s-master01 cpu/内存:2 Core/2G 硬盘:40GB 网络模式:桥接 kube-apiserver, kube-controller-manager,kube-scheduler, 192.168.0.168 k8s-node01 cpu/内存:2 Core/2G 硬盘:40GB 网络模式:桥接 kubelet, kube-proxy, docker etcd 192.168.0.116 k8s-node02 cpu/内存:2 Core/2G 硬盘:40GB 网络模式:桥接 kubelet, kube-proxy, docker etcd 192.168.0.154 linux 版本:ubuntu-20.04-live-server-amd64.iso kubernetes 版本:v1.19.1 docker 版本:v19.03.8-ce vm 版本:VMware Workstation Pro 15.6 客户端使用 xshell6 连接到 vm 中 linux 系统方便安装配置; 以上配置为 3 节点主机的单 Master 架构的最小规模集群配置,配置环境均在 vm 中部署安装,宿主机固定静态 IP,保障 vm 中 linux 系统的网络在同一网段,使用桥接模式,宿主机和 vm 系统之间能相互通信,vm 中系统能正常访问外网; 生产环境建议扩展多 Master 架构集群部署,node 节点根据部署容器数量预估配置; 单 Master 架构图操作系统初始化配置2.1 关闭 UFW(Uncomplicated Firewall)防火墙Ubuntu20.04 一般都默认安装了 UFW(Uncomplicated Firewall) 防火墙服务,它是一款轻量化的工具,主要用于对输入输出的流量进行监控。而 Kubernetes 的 Master 与工作 Node 之间会有大量的网络通信,安全的做法是在防火墙上配置 Kbernetes 各组件(api-server、kubelet 等等)需要相互通信的端口号。在安全的内部网络环境中可以关闭防火墙服务。# 查看防火墙状态 $ sudo ufw status verbose # 关闭ubuntu的防火墙 $ sudo ufw disable Status: inactive 说明防火墙未运行Status: active 说明防火墙在运行参考:ubuntu 关闭和开启防火墙,https://cloud.tencent.com/developer/article/1503910ubuntu20.04 防火墙设置简易教程(小白),https://www.jb51.net/article/192145.htm2.2 关闭 SELinux安全增强型 Linux(Security-Enhanced Linux) 简称 SELinux,它是一个 Linux 内核模块,也是 Linux 的一个安全子系统。SELinux 主要由美国国家安全局开发。2.6 及以上版本的 Linux 内核都已经集成了 SELinux 模块。SELinux 的结构及配置非常复杂,而且有大量概念性的东西,要学精难度较大。很多 Linux 系统管理员嫌麻烦都把 SELinux 关闭了。关闭 SELinux 的两种方法永久方法 – 需要重启服务器修改 /etc/selinux/config 文件中设置 SELINUX=disabled,然后重启服务器。临时方法 – 设置系统参数使用命令 setenforce 0参数说明:setenforce 1 设置 SELinux 成为 enforcing 模式setenforce 0 设置 SELinux 成为 permissive 模式# 安装 selinux-utils $ apt install selinux-utils # 查看 selinux 是否关闭 $ getenforce $ setenforce 0 $ sed -i 's/enforcing/disabled/' /etc/selinux/config 2.3 关闭 swap 内存分区这个 swap 其实可以类比成 windows 上的虚拟内存,它可以让服务器在内存吃满的情况下可以保持低效运行,而不是直接卡死。但是 k8s 的较新版本都要求关闭 swap,修改 /etc/fstab 文件:# 临时 $ sudo swapoff -a # 永久,修改/etc/fstab,注释掉swap那行,持久化生效 $ sudo vi /etc/fstab2.4 根据规划设置主机名# 查看主机名称 $ hostname # 设置主机名称 $ hostnamectl set-hostname <hostname> 2.5 在 Master 添加 hostshosts 文件的作用相当如 DNS(Domain Name System),提供 IP 地址到 hostname 的映射。早期的互联网计算机少,单机 hosts 文件里足够存放所有联网计算机。不过随着互联网的发展,这就远远不够了。于是就出现了分布式的 DNS 系统。由 DNS 服务器来提供类似的 IP 地址到域名的对应。具体可以 man hosts。/etc/hosts 中包含了 IP 地址和主机名之间的映射,还包括主机名的别名。hosts 文件的配置格式是: IP FQDN alias,其中全域名 (FQDN,Fully Qualified Domain Name) 是指主机名加上全路径,全路径中列出了序列中所有域成员。全域名可以从逻辑上准确地表示出主机在什么地方,也可以说全域名是主机名的一种完全表示形式。从全域名中包含的信息可以看出主机在域名树中的位置。Linux 系统在向 DNS 服务器发出域名解析请求之前会查询 /etc/hosts 文件,如果里面有相应的记录,就会使用 hosts 里面的记录。此处 hosts 类似 DNS(Domain Name System), DNS 可以降低延迟,但是不能提高上下行网速。$ cat >> /etc/hosts << EOF 172.26.16.100 k8s-master01 172.26.16.101 k8s-node01 172.26.16.102 k8s-node02 EOF注意:此处添加 hosts 防止重启 linux 系统后,IP 无效,可以事先根据规划的网段设置好静态IP地址,操作如下:Ubuntu 18.04 配置静态IP地址。使用 Netplan(新一代网络配置工具)工具配置网络1. 备份系统默认 /etc/netplan/*.yaml 文件$ sudo cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak2. 编写 /etc/netplan/*.yaml 文件network: version: 2 renderer: networkd ethernets: eth0: #配置的网卡名称 dhcp4: false #dhcp4关闭 dhcp6: false #dhcp6关闭 addresses: [172.26.16.100/16] #设置本机IP及掩码 gateway4: 172.26.16.1 #设置网关 nameservers: addresses: [114.114.114.114, 8.8.8.8] #设置DNSeth0 - 网络接口名称。dhcp4 和 dhcp6 - 接受 IPv4 和 IPv6 接口的 dhcp 属性。addresses - 接口的静态地址序列。gateway4 - 默认网关的 IPv4 地址。nameservers - nameservers 的 IP 地址序列。DNS 可以配置多个,配置列表见附件。 IP 地址段和子网掩码对照表 =》精心整理IP地址段与子网掩码表(收藏) (360doc.com),http://www.360doc.com/content/18/0121/21/40298311_723964860.shtmlIP和子网掩码对照表 - 百度文库 (baidu.com),https://wenku.baidu.com/view/aec639e0a1116c175f0e7cd184254b35eefd1ad8.html3. 执行 apply 命令,使 yaml 配置生效$ netplan apply -f 00-installer-config.yaml附:国内知名公共DNS服务器IP腾讯DNS:119.29.29.29、 182.254.116.116阿里DNS:223.5.5.5、 223.6.6.6百度DNS:180.76.76.76114DNS:114.114.114.114、 114.114.115.115CNNIC DNS:1.2.4.8、 210.2.4.8OneDNS:117.50.11.11、 117.50.22.22Hi!XNS DNS服务器:40.73.101.101清华大学TUNA协会DNS服务器:101.6.6.6Google Public DNS:8.8.8.8、8.8.4.42.6 时间同步,确保时区和时间正确每个宿主机上都要确保时区和时间是正确的。如果时区不正确,请使用下面的命令来修改:$ sudo timedatectl set-timezone Asia/Shanghai #修改后,如果想使得系统日志的时间戳也立即生效,则: $ sudo systemctl restart rsyslog (Ubuntu如何同步网络时间,https://www.cnblogs.com/zhongguiyao/p/9293764.html)装完Ubuntu设置完时间,重启总是恢复设置前的时间。设定时区:dpkg-reconfigure tzdata选择Asia -> 再选择Shanghai -> OK解决方法:# 1.安装ntpdate工具 $ sudo apt install ntpdate -y # 2.将系统时间与网络同步 $ ntpdate cn.pool.ntp.org # 3.将时间写入硬件 $ hwclock --systohc # 4.查看时间是否已同步 $ date 2.7 关闭 suspend(待机/休眠),确保每个机器不会自动待机/休眠;$ sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target2.8 设置 iptables 可以看到 bridged traffic / 将桥接的 IPv4 流量传递到 iptables 的链先确认 Linux 内核加载了 br_netfilter 模块:$ lsmod | grep br_netfilter注意:确保 sysctl 配置中 net.bridge.bridge-nf-call-iptables 的值设置为了 1。在 Ubuntu 20.04 Server 上,这个值就是 1。如果你的系统上不一致,使用下面的命令来修改:$ cat << EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 $ sudo sysctl --system # 生效$ cat > /etc/sysctl.d/k8s.conf << EOF net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 $ sudo sysctl --system # 生效2.9 设置 rp_filter 的值因为 K8s 集群即将部署的是 calico 网络插件,而 calico 需要这个内核参数是 0 或者 1,但是 Ubuntu20.04 上默认是 2,这样就会导致 calico 插件报下面的错误(这是个 fatal 级别的 error):int_dataplane.go 1035: Kernel's RPF check is set to 'loose'. \ This would allow endpoints to spoof their IP address. \ Calico requires net.ipv4.conf.all.rp_filter to be set to 0 or 1. \ If you require loose RPF and you are not concerned about spoofing, \ this check can be disabled by setting the IgnoreLooseRPF configuration parameter to 'true'.使用下面的命令来修改这个参数的值:# 修改/etc/sysctl.d/10-network-security.conf $ sudo vi /etc/sysctl.d/10-network-security.conf # 将下面两个参数的值从2修改为1 $ net.ipv4.conf.default.rp_filter=1 $ net.ipv4.conf.all.rp_filter=1 # 然后使之生效 $ sudo sysctl --system2.10 重启 Linux 系统$ shutdown -r now #重启系统安装基础软件Kubernetes 在 1.20.x 版本以前默认 CRI/Container Runtime Interface(容器运行时)为 Docker,因此此处先安装 Docker;关于 CRI 的一些实现:CRI-containerd 是目前社区中比较主流的新一代 CRI 的实现;CRI-O 来自于红帽公司基于 Open Container Initiative 的 K8s CRI 的实现;PouchContainer 是由 alibaba 实现的 CRI;其它的 CRI 实现,这里就不一一介绍了,更多详细信息自行查阅资料。注意:Kubernetes 官方发布公告,宣布自 v1.20 起放弃对 Docker 的支持,届时用户将收到 Docker 弃用警告,并需要改用其他容器运行时。目前 k8s 最新版默认容器运行时是 CRI-containerd。3.1 安装 Docker目前 Ubuntu 20.04 上没有 docker 官方提供的安装包,但是 Ubuntu 有,使用下面的命令来安装 Docker;1. apt 方式安装 docker# 安装 Docker $ sudo apt update && sudo apt install docker.io # 启动 Docker,并设置为开机启动 $ sudo systemctl start docker && systemctl enable docker注意:apt 方式安装 docker, daemon.json 文件的位置在 /etc/docker/ 文件下面。2. snap 方式安装 docker# 安装 docker $ sudo snap install docker # 启动 docker $ sudo snap start docker注意:snap 方式安装 docker,daemon.json 文件的位置在 /var/snap/docker/*/config/ 文件下面。参考:Ubuntu 20.04 以 snap 的方式安装 Docker,https://www.cnblogs.com/jijizhazha/p/13170711.html3.2 验证 Docker 是否安装成功$ docker -v # 查看docker版本3.3 配置 Docker 镜像下载加速器$ cat > /etc/docker/daemon.json << EOF "registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"] $ systemctl restart docker $ docker info国内从 Docker Hub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务,例如:中国科技大学镜像:https://docker.mirrors.ustc.edu.cn/网易镜像:https://hub-mirror.c.163.com/阿里云镜像:https://&lt;你的ID>.mirror.aliyuncs.com/七牛云加速器:https://reg-mirror.qiniu.com/Docker中国区官方镜像:https://registry.docker-cn.com/ustc: https://docker.mirrors.ustc.edu.cn/3.4 在 Ubuntu 系统中配置阿里云 apt 软件源#第一步:备份源文件: $ sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup #第二步:编辑 `/etc/apt/sources.list` 文件 #在文件最前面添加以下条目(操作前请做好相应备份): $ vi /etc/apt/sources.list # 阿里云 apt 源 # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释 deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse #deb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse #deb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse #deb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse #deb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse # 预发布软件源,不建议启用 #deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse #deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse #第三步:执行更新命令: $ sudo apt-get update $ sudo apt-get upgrade其中 3.4 参考如下:Ubuntu 系统配置源并安装,https://www.cnblogs.com/zqifa/p/12910989.htmlLinux 发行版添加软件源,https://developer.aliyun.com/article/746822?spm=a2c6h.13813017.content3.1.7afe1211cFoJxx基于 Debian 的发行版,https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#%E5%AE%89%E8%A3%85-kubeadm-kubelet-%E5%92%8C-kubectl#1.查看 docker 服务是否开机启动 $ sudo systemctl list-unit-files | grep enable|grep docker snap-docker-1125.mount enabled snap.docker.dockerd.service enabled #2.关闭 docker 开机自启动 $ systemctl disable snap.docker.dockerd.service Removed /etc/systemd/system/multi-user.target.wants/snap.docker.dockerd.service. #3.开启 docker 开机自启动 $ systemctl enable snap.docker.dockerd.service #4.关闭 docker 服务 $ systemctl stop snap.docker.dockerd.service #5.启动 docker 服务 $ systemctl start snap.docker.dockerd.service #6.查看 docker 服务是否启动 $ sudo systemctl list-units --type=service|grep docker snap.docker.dockerd.service loaded active running Service for snap application docker.dockerd注意:以上操作(除开2.4、2.5)均在所有节点执行;开始安装 k8s4.1 安装 K8s master以下的操作只在 master 宿主机上执行,适合中国大陆地区使用(因为弃用谷歌的源和 repo,转而使用阿里云的镜像):4.1.1 安装 kubelet、 kubeadm 和 kubectl$ sudo apt update && sudo apt install -y ca-certificates curl software-properties-common apt-transport-https curl $ curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add - $ sudo tee /etc/apt/sources.list.d/kubernetes.list <<EOF deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main $ sudo apt update # 查看 kubeadm 可用版本 $ sudo apt-cache madison kubeadm # 指定 kubeadm 版本安装 $ sudo apt install -y kubelet=1.22.1-00 kubeadm=1.22.1-00 kubectl=1.22.1-00 $ sudo apt-mark hold kubelet=1.22.1-00 kubeadm=1.22.1-00 kubectl=1.22.1-00kubelet 运行在 Cluster 所有节点上,负责启动 Pod 和容器;kubeadm 用于初始化 Cluster ;kubectl 是 k8s 命令行工具,通过 kubectl 可以部署和管理应用,查看各种资源,创建、删除和更新各种组件;注意:在所有节点均安装 kubelet、 kubeadm 和 kubectl。4.1.2 初始化 Kubernetes Master注意:这里使用了阿里云的镜像,然后使用了非默认的 CIDR(一定要和宿主机的局域网的 CIDR 不一样!)=》4.1.2.1 命令初始化$ kubeadm init \ --apiserver-advertise-address=172.26.16.100 \ --image-repository registry.aliyuncs.com/google_containers \ --kubernetes-version v1.18.0 \ --service-cidr=10.96.0.0/12 \ --pod-network-cidr=10.244.0.0/16 \ --ignore-preflight-errors=all # 或者 --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers命令参数说明:【--apiserver-advertise-address】集群通告地址;【--image-repository】由于默认拉取镜像地址k8s.gcr.io国内无法访问,这里指定阿里云镜像仓库地址;【--kubernetes-version】K8s版本,与上面安装的一致;【--service-cidr】集群内部虚拟网络,Pod统一访问入口;【--pod-network-cidr】Pod网络与下面部署的 CNI 网络组件 yaml 中保持一致;【--ignore-preflight-errors】忽略预检查错误信息,这个参数会跳过对docker-ce的版本检查;=》4.1.2.2 yaml 配置文件引导初始化# 编辑配置文件 $ vi kubeadm.conf apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration kubernetesVersion: v1.18.0 imageRepository: registry.aliyuncs.com/google_containers networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 #执行并应用配置文件 $ kubeadm init --config kubeadm.conf --ignore-preflight-errors=all 4.2 加入 Kubernetes Node (部署 K8s worker 节点)上面的命令执行成功后,会输出一条和 kubeadm join 相关的命令,后面加入 worker node 的时候要使用。输出其他节点加入 master 的命令:Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 192.168.0.168:6443 --token cm1xr0.frcfjgtg0q4we4z0 \ --discovery-token-ca-cert-hash sha256:80705f99840e6c80b46e5f1dead4f15a843f90514d3e9d2d449694c2a5eff6e1拷贝 kubectl 使用的连接k8s认证文件到默认路径,给自己的非 sudo 的常规身份拷贝一个 token,这样就可以执行 kubectl 命令了:# 切换到非管理员账户 $ su chait # 最后拷贝 kubectl 工具用的 kubeconfig 到默认路径下 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 查看集群节点信息 $ kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Ready master 2m v1.18.0在每个 node/worker 宿主机上,执行上面初始化 master 最后输出的命令,如下所示:kubeadm join 192.168.0.168:6443 --token cm1xr0.frcfjgtg0q4we4z0 \ --discovery-token-ca-cert-hash sha256:80705f99840e6c80b46e5f1dead4f15a843f90514d3e9d2d449694c2a5eff6e1接下来在 node/worker 节点输入命令=》【kubectl get pods】,结果出现如下错误:The connection to the server localhost:8080 was refused - did you specify the right host or port?出现这个问题的原因是 kubectl 命令需要使用 kubernetes-admin 来运行,解决方法如下,将 Master 节点中的【/etc/kubernetes/admin.conf】文件拷贝到 worker 节点相同目录下,然后配置环境变量:echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile source ~/.bash_profile # 立即生效再次输入命令【kubectl get pods --all-namespaces】查看所有 pod 的命名空间,显示如下:默认 token 有效期为 24 小时,当过期之后,该 token 就不可用了。这时就需要重新创建 token,操作如下:$ kubeadm token create $ kubeadm token list $ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //' 80705f99840e6c80b46e5f1dead4f15a843f90514d3e9d2d449694c2a5eff6e1 $ kubeadm join 192.168.31.61:6443 --token nuja6n.o3jrhsffiqs9swnu --discovery-token-ca-cert-hash sha256:80705f99840e6c80b46e5f1dead4f15a843f90514d3e9d2d449694c2a5eff6e1命令:kubeadm token create --print-join-command参考地址 =》https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/4.3 部署容器网络(CNI)Calico 是一个纯三层的数据中心网络方案,Calico 支持广泛的平台,包括 Kubernetes、OpenStack 等。Calico 在每一个计算节点利用 Linux Kernel 实现了一个高效的虚拟路由器(vRouter) 来负责数据转发,而每个 vRouter 通过 BGP 协议负责把自己上运行的 workload 的路由信息向整个 Calico 网络内传播。此外,Calico 项目还实现了 Kubernetes 网络策略,提供 ACL 功能。=》 Quickstart for Calico on Kubernetes,https://docs.projectcalico.org/getting-started/kubernetes/quickstart4.3.1 安装 calico 插件下载 calico 的 k8s yaml 文件# 下载 https://docs.projectcalico.org/v3.11/manifests/calico.yaml sudo wget https://docs.projectcalico.org/v3.11/manifests/calico.yaml # 修改CALICO_IPV4POOL_CIDR root@k8s-master01:/# vim calico.yaml # 通过文件名或控制台输入,对资源进行配置。 root@k8s-master01:/# kubectl apply -f calico.yaml下载完后还需要修改里面配置项:定义 Pod 网络(CALICO_IPV4POOL_CIDR),与前面 pod CIDR 配置一样;选择工作模式(CALICO_IPV4POOL_IPIP),支持 BGP(Never)、IPIP(Always)、CrossSubnet(开启 BGP 并支持跨子网);修改里面的 CALICO_IPV4POOL_CIDR 的值来避免和宿主机所在的局域网段冲突(把原始的192.168.0.0/16 修改成了 172.16.0.0/16):修改完后应用清单:等待所有的 pod 都 ready 就绪。4.4 测试 K8s 集群环境验证Pod工作;验证Pod网络通信;验证DNS解析;在 Kubernetes 集群中创建一个 pod,验证是否正常运行:$ kubectl create deployment nginx --image=nginx $ kubectl expose deployment nginx --port=80 --type=NodePort $ kubectl get pod,svc访问地址:http://NodeIP:Port4.5 部署 K8s dashboardkubernetes/dashboard =》https://github.com/kubernetes/dashboard执行命令:kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.4/aio/deploy/recommended.yaml以上方式启动 dashboard 访问会不太方便,只能在集群内部(cluster-ip)访问,可修改 yaml 中的 service 暴露端口方式为 NodePort 类型,即可直接在浏览器中访问;$ vi recommended.yaml kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: ports: - port: 17030 targetPort: 8443 type: NodePort externalIPs: - 192.168.0.168 selector: k8s-app: kubernetes-dashboard $ kubectl apply -f recommended.yaml $ kubectl get pods -n kubernetes-dashboard NAME READY STATUS RESTARTS AGE dashboard-metrics-scraper-6b4884c9d5-gl8nr 1/1 Running 0 13m kubernetes-dashboard-7f99b75bf4-89cds 1/1 Running 0 13m执行上面命令可能出现网络原因访问不起,可以 github 直接下载相应 yaml 文件设置;在需要部署的节点中安装文件传输工具:$ sudo apt install lrzsz # 安装 rz 工具 $ rz # 选择上传的文件此时查看当前列表=》【ls】此处 dashboard 本人是安装在 master 节点,安装信息如下:访问地址:https://NodeIP:30001创建 service account 并绑定默认 cluster-admin 管理员集群角色:# 创建用户 $ kubectl create serviceaccount dashboard-admin -n kube-system # 用户授权 $ kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin # 获取用户Token $ kubectl describe secrets -n kube-system $(kubectl -n kube-system get secret | awk '/dashboard-admin/{print $1}')使用输出的 token 登录 Dashboard。4.6 安装 Ingress此处我们使用 NGINX ingress controller 方式安装,更多的安装方式查看=》(Ingress Controllers | Kubernetes,https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/) ;K8s 官方支持和维护的是 GCE 和 NGINX 两种 Controller,执行如下命令:kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-0.32.0/deploy/static/provider/baremetal/deploy.yaml上面方式可能存在网络访问失败的情况,此处安装的是【ingress-nginx】版本【v0.30.0】,ingress-controller.yaml 文件已经下载好,rz 上传到 worker 节点,如下所示:最后我们来查看下 1 Master 架构的 3 node 集群情况, 显示信息如下:4.7 访问 Dashboard4.7.1 使用 kubectl proxy ;启动成功后,可在内网节点的浏览器中访问 =》http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/vmware 虚拟化节点访问 =》curl http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/4.7.2 使用 API Server如果 Kubernetes API 服务器是公开的并且可从外部访问,就可以用 API Server 来访问 =》https://<master-ip>:<apiserver-port>/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/查看 yaml 文件对应的镜像版本cat kubernetes-dashboard.yaml|grep kubernetes-dashboard4.7.3 两种登录模式 Kubeconfig 和 Token;此处我使用的是 Token 方式登陆,获取 Token =》 输入以下命令来创建用户 token,利用 token 来登录 dashboard:kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
kubeadm 简介kubeadm 是 Kubernetes(以下简称 k8s)官方提供的用于快速安装部署 k8s 集群的工具,伴随 k8s 每个版本的发布都会同步更新,kubeadm 会对集群配置方面的一些实践做调整,通过实验 kubeadm 可以学习到 k8s 官方在集群配置上一些新的最佳实践。这个工具能通过两条指令完成一个 k8s 集群的部署:# 创建一个 Master 节点 $ kubeadm init # 将一个 Node 节点加入到当前集群中 $ kubeadm join <Master节点的IP和端口 >使用 kubeadm 工具大大简化了 k8s 的部署操作。k8s 的部署方式通常使用 kubeadm 部署和二进制方式部署,他们之间的区别:kubeadm 方式部署,组件容器化部署,只有 kubelet 没有被容器化二进制方式部署,传统的守护进程(systemd)管理服务 systemctl实现目标基于 华为 openEuler 22.03 LTS 系统,使用 kubeadm v1.24.1 搭建一套由单 Master node 和两个 Worker node 组成的 k8s v1.24.1 版本的集群环境。系统环境准备部署要求在开始部署之前,部署 k8s 集群的宿主机(vm 或 物理机)需要满足以下几个条件:宿主机 3 台,操作系统 CentOS7.x/8.x-86_x64 系列(此处使用 openEuler 22.03 LTS)硬件配置:RAM 4GB或更多,CPU 核数 2c 或更多,硬盘 60GB 或更多集群中所有机器之间网络互通可以访问外网,需要拉取镜像关闭防火墙,禁止 swap 分区所有集群节点同步系统时间(使用 ntpdate 工具)注意:以上部署规格的配置要求为最小化集群规模要求,生产环境的集群部署要求按实际情况扩展配置,为了保障集群环境的高可用性,搭建集群环境的宿主机通常以奇数( ≥ 3、5、7...)节点最佳。部署规划此处以单 master node 和两 worker node 集群模式为例,使用 kubeadm 部署 Kubernetes v1.24.1 版本集群环境。1. 单 master 集群模式2. vm 资源编排此处使用 VMware Workstation Pro v16.2.2 虚拟机搭建 vm 环境,规划如下:k8s 集群角色ip 地址hostname 主机名称资源规格操作系统安装组件master192.168.18.130k8s-master-012c4g/60gopenEuler 22.03 LTSkube-apiserver、kube-controller-manager、kube-scheduler、etcd、containerd、kubelet、kube-proxy、keepalived、nginx、calico、metrics-server、dashboardworker node192.168.18.131k8s-node-012c4g/60gopenEuler 22.03 LTScontainerd、kubelet、kube-proxy、ingress-controller、calico,corednsworker node192.168.18.132k8s-node-022c4g/60gopenEuler 22.03 LTScontainerd、kubelet、kube-proxy、ingress-controller、calico,coredns关于 VMware Workstation Pro v16.2.2 虚拟机自行下载,并安装配置好,vm 系统使用华为 openEuler 22.03 LTS ISO 镜像。注意:VMware 中网络配置选择【NAT模式】,确保 vm 内部网络环境可以访问到外部网络环境。3. k8s 组件版本关于 k8s 的常用资源组件版本信息规划如下:名称版本下载地址kubernetesv1.24.1https://github.com/kubernetes/kubernetes/releases/tag/v1.24.1kubelet、kubeadm、kubectlv1.24.1yum install -y kubelet-1.24.1 kubeadm-1.24.1 kubectl-1.24.1containerdv1.6.4,cni v0.3.1https://github.com/containerd/containerd/releases/tag/v1.6.4flannelv0.18.0https://github.com/flannel-io/flannel/releases/tag/v0.18.0calicov3.23.1https://github.com/projectcalico/calico/releases/tag/v3.23.1kube-state-metricsv2.4.2https://github.com/kubernetes/kube-state-metrics/releases/tag/v2.4.2metrics-server-helm-chartv3.8.2https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.8.2Kong Ingress Controller for Kubernetes (KIC)v2.3.1https://github.com/Kong/kubernetes-ingress-controller/releases/tag/v2.3.1dashboardv2.5.1https://github.com/kubernetes/dashboard/releases/tag/v2.5.14. 关于 openEuler基于华为 openEuler 系统环境部署,推荐使用 openEuler 22.03 LTS 和 openEuler 20.03 LTS SP3,以 root 身份执行下面命令。为了方便操作,vm 中的 openEuler 系统网络 ip 可以按照上面的编排规划,设置静态 ip 地址。关于 openEuler 系统的安装,请自行参考官方文档,此处不是重点,接下来介绍 openEuler 系统安装后,我们需要设置的相关事项。openEuler 资源地址:ISO下载,https://www.openeuler.org/zh/download/安装指南,https://docs.openeuler.org/zh/docs/22.03_LTS/docs/Installation/installation.html5. shell 终端以下是一些比较常用的 shell 终端,选择自己喜欢的一个安装配置即可。Xshell 5/6/7Windows PowerShell / PowerShellWindows TerminalPuTTYVisual Studio CodeVM 系统部署操作事项(所有节点)注意:下面命令在 k8s 所有节点(master + worker)执行。1. 关闭防火墙 Firewalld防火墙 firewalld 先 stop 再 disable,操作如下:systemctl stop firewalld #停止 $ systemctl disable firewalld #开机禁用 $查看防火墙状态systemctl status firewalld #查看状态输出如下信息,说明已经关闭[root@k8s-master-01 ~]# systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled) Active: inactive (dead) Docs: man:firewalld(1)[Linux防火墙操作命令,开放或关闭端口] =》 https://zhuanlan.zhihu.com/p/1611967112. 关闭 SELinux安全增强型 Linux(SELinux)是一个 Linux 内核的功能,它提供支持访问控制的安全政策保护机制。# 临时关闭 SELinux。 setenforce 0 # 永久关闭 SELinux。 vi /etc/selinux/config SELINUX=disabled验证 selinux 状态getenforce输出如下信息,说明已经关闭[root@k8s-node-01 ~]# getenforce DisabledSELinux 状态说明:SELinux 状态为 disabled,表明 SELinux 已关闭;SELinux 状态为 enforcing 或者 permissive,表明 SELinux 在运行;3. 关闭 Swapswap 的用途 ?swap 分区就是交换分区,(windows 平台叫虚拟内存) 在物理内存不够用时,操作系统会从物理内存中把部分暂时不被使用的数据转移到交换分区,从而为当前运行的程序留出足够的物理内存空间。为什么要关闭 swap ?swap 启用后,在使用磁盘空间和内存交换数据时,性能表现会较差,会减慢程序执行的速度。有的软件的设计师不想使用交换分区,例如:kubelet 在 v1.8 版本以后强制要求 swap 必须关闭,否则会报错:Running with swap on is not supported, please disable swap! or set --fail-swap-on flag to false或者 kubeadm init 时会报错:[ERROR Swap]: running with swap on is not supported. Please disable swap关闭 swapswapoff -a # 临时关闭 vi /etc/fstab # 永久关闭,注释掉 swap 这行查看 swap 是否关闭[root@k8s-master-01 ~]# free -m total used free shared buff/cache available Mem: 1454 881 147 73 425 179 Swap: 0 0 0显示 total/used/free 为 0,说明已经关闭。4. 设置宿主机名称依据宿主机(vm 或物理机)资源编排情况,使用 systemd 里面的 hostnamectl 设置主机名。# 临时 hostnamectl set-hostname k8s-master-01 hostnamectl set-hostname k8s-node-01 hostnamectl set-hostname k8s-node-02 # 永久,编写对应的 hostname vi /etc/hostname设置完成后,重新进入下 shell 终端,使配置生效。bash5. 在 Master node 和 Worker node 添加 hosts修改 hosts 文件,配置主机名称和 ip 之间的映射。$ cat > /etc/hosts << EOF 192.168.18.130 k8s-master-01 192.168.18.131 k8s-node-01 192.168.18.132 k8s-node-02 EOF进入 bash 访问测试 node 节点网络是否连通bash ping k8s-master-01 ping k8s-node-01 ping k8s-node-026. 创建 containerd.conf 配置文件在路径 "/etc/modules-load.d/containerd.conf" 创建配置文件。cat << EOF > /etc/modules-load.d/containerd.conf overlay br_netfilter EOF执行以下命令使配置生效modprobe overlay modprobe br_netfilter7. 将桥接的 IPv4 流量传递到 IPTABLES 链IPTABLES 规则实现 Docker 或 K8s 的网络通信,非常关键。查看 "/etc/sysctl.d/99-xxx.conf" 配置文件。[root@k8s-master-01 /]# ls /etc/sysctl.d/ 99-kubernetes-cri.conf 99-sysctl.conf [root@k8s-master-01 /]# cat /etc/sysctl.d/99-sysctl.conf # sysctl settings are defined through files in # /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/. # Vendors settings live in /usr/lib/sysctl.d/. # To override a whole file, create a new file with the same in # /etc/sysctl.d/ and put new settings there. To override # only specific settings, add a file with a lexically later # name in /etc/sysctl.d/ and put new settings there. # For more information, see sysctl.conf(5) and sysctl.d(5). kernel.sysrq=0 net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 user.max_user_namespaces=28633 net.ipv4.ip_forward=1 net.ipv4.conf.all.send_redirects=0 net.ipv4.conf.default.send_redirects=0 net.ipv4.conf.all.accept_source_route=0 net.ipv4.conf.default.accept_source_route=0 net.ipv4.conf.all.accept_redirects=0 net.ipv4.conf.default.accept_redirects=0 net.ipv4.conf.all.secure_redirects=0 net.ipv4.conf.default.secure_redirects=0 net.ipv4.icmp_echo_ignore_broadcasts=1 net.ipv4.icmp_ignore_bogus_error_responses=1 net.ipv4.conf.all.rp_filter=1 net.ipv4.conf.default.rp_filter=1 net.ipv4.tcp_syncookies=1 kernel.dmesg_restrict=1 net.ipv6.conf.all.accept_redirects=0 net.ipv6.conf.default.accept_redirects=0修改内核参数(上面的 99-sysctl.conf 配置文件已经修)# 1、加载br_netfilter模块 modprobe br_netfilter # 2、验证模块是否加载成功 lsmod | grep br_netfilter # 3、修改内核参数 cat > /etc/sysctl.d/99-sysctl.conf <<EOF net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 user.max_user_namespaces=28633 net.ipv4.ip_forward = 1 # 4、使刚才修改的内核参数生效,此处使用的是 99-sysctl.conf 配置文件。 sysctl -p /etc/sysctl.d/99-sysctl.confsysctl 命令用于运行时配置内核参数,这些参数位于 "/proc/sys" 目录下。sysctl 配置与显示在 "/proc/sys" 目录中的内核参数。可以用 sysctl 来设置或重新设置联网功能,如 IP 转发、IP 碎片去除以及源路由检查等。用户只需要编辑 "/etc/sysctl.conf" 文件,即可手工或自动执行由 sysctl 控制的功能。8. 配置服务器支持开启 IPVS 的前提条件(K8s 推荐配置)IPVS 称之为 IP 虚拟服务器(IP Virtual Server,简写为 IPVS)。是运行在 LVS 下的提供负载均衡功能的一种技术。IPVS 基本上是一种高效的 Layer-4 交换机,它提供负载平衡的功能。由于 IPVS 已经加入到了 Linux 内核的主干,所以为 kube-proxy(Kubernetes Service) 开启 IPVS 的前提需要加载以下的 Linux 内核模块:ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack_ipv4 # 或 nf_conntrack在所有服务器集群节点上执行以下脚本cat > /etc/sysconfig/modules/ipvs.modules <<EOF #!/bin/bash modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 # 若提示在内核中找不到 nf_conntrack_ipv4, 可以让是切换 nf_conntrack # 或者 grep -e ip_vs -e nf_conntrack chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4上面脚本创建了的 "/etc/sysconfig/modules/ipvs.modules" 文件,保证在节点重启后能自动加载所需模块。 可执行命令 【lsmod | grep -e ip_vs -e nf_conntrack_ipv4】查看所需内核模块是否正确加载。接下来还需要确保各个节点上已经安装了 ipset 软件包,为了便于查看 ipvs 的代理规则,最好安装一下管理工具 ipvsadm。yum install -y ipset ipvsadm如果不满足以上前提条件,则即使 kube-proxy 的配置开启了 ipvs 模式,也会退回到 iptables 模式。9. 同步系统时间ntpdate 指令通过轮询指定为服务器参数的 网络时间协议(NTP) 服务器来设置本地日期和时间,从而确定正确的时间。此命令的适用范围:RedHat、RHEL、Ubuntu、CentOS、Fedora。# 安装 ntpdate yum install ntpdate -y # 执行同步命令 ntpdate time.windows.com # 跟网络源做同步 ntpdate cn.pool.ntp.org # 把时间同步做成计划任务 crontab -e * */1 * * * /usr/sbin/ntpdate cn.pool.ntp.org # 重启crond服务 service crond restart # 查看当前时区 date -R同步系统时间输出如下信息[root@k8s-master-01 /]# ntpdate time.windows.com 29 May 16:19:17 ntpdate[7873]: adjust time server 20.189.79.72 offset +0.001081 secIPVS 和 IPTABLES 对比分析IPVS 是什么?IPVS (IP Virtual Server) 实现了传输层负载均衡,也就是我们常说的 4 层局域网交换机(LAN Switches),作为 Linux 内核的一部分。IPVS 运行在主机上,在真实服务器集群前充当负载均衡器。IPVS 可以将基于 TCP 和 UDP 的服务请求转发到真实服务器上,并使真实服务器的服务在单个 IP 地址上显示为虚拟服务。IPVS 和 IPTABLES 对比分析kube-proxy 支持 iptables 和 ipvs 两种模式, 在 kubernetes v1.8 中引入了 ipvs 模式,在 v1.9 中处于 beta 阶段,在 v1.11 中已经正式可用了。iptables 模式在 v1.1 中就添加支持了,从 v1.2 版本开始 iptables 就是 kube-proxy 默认的操作模式,ipvs 和 iptables 都是基于 netfilter 的,但是 ipvs 采用的是 hash 表,因此当 service 数量达到一定规模时,hash 查表的速度优势就会显现出来,从而提高 service 的服务性能。那么 ipvs 模式和 iptables 模式之间有哪些差异呢?ipvs 为大型集群提供了更好的可扩展性和性能ipvs 支持比 iptables 更复杂的复制均衡算法(最小负载、最少连接、加权等等)ipvs 支持服务器健康检查和连接重试等功能在 k8s 的集群环境中,推荐配置 ipvs ,为一定数量规模的 service 提高服务性能。安装 containerd/kubeadm/kubelet(所有节点)Containerd 简介Containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性,具备如下功能:管理容器的生命周期(从创建容器到销毁容器)拉取/推送容器镜像存储管理(管理镜像及容器数据的存储)调用 runc 运行容器(与 runc 等容器运行时交互)管理容器网络接口及网络自 Kubernetes v1.24 起,Dockershim 已被删除,由于社区的关注,Docker 和 Mirantis 共同决定继续以 [cri-dockerd] 的形式支持 dockershim 代码(https://www.mirantis.com/blog/the-future-of-dockershim-is -cri-dockerd/), 允许你在需要时继续使用 Docker Engine 作为容器运行时。对于想要尝试其他运行时(如 containerd 或 cri-o) 的用户,已编写迁移文档(https://kubernetes.io/zh/docs/tasks/administer-cluster/migrating-from-dockershim/change-runtime-containerd/)。查看删除 Dockershim 的原因Dockershim:历史背景 =》 https://kubernetes.io/zh/blog/2022/05/03/dockershim-historical-context/如果试图将链从最终用户(user)绘制到实际的容器进程(runc),它可能如下所示:runc 是一个命令行客户端,用于运行根据 OCI(开放容器计划,Open Container Initiative/OCI) 格式打包的应用程序,并且是 OCI 规范的兼容实现。(推荐)使用 Containerd 的理由Kubernetes 在 v1.23 版本及以后版本不再默认采用 Docker/Dockershim ,而建议采用 Containerd;Containerd 比 Docker/Dockershim 更加轻量级,在生产环境中使用更佳合适(稳定,性能);Containerd 安装下面我们进行 containerd 容器运行时的安装,操作如下:1. 安装 wget (可选)由于 openEuler 系统本身集成了 curl ,安装 wget 不是必须项。# 安装 wget sudo yum install wget -y # 此处安装的 wget 版本信息 Installed: wget-1.20.3-2.oe1.x86_642. 下载 containerd分别使用 wget 和 curl 下载 containerd v1.6.4 版本。# 使用 wget 下载 containerd v1.6.4 sudo wget https://github.com/containerd/containerd/releases/download/v1.6.4/cri-containerd-cni-1.6.4-linux-amd64.tar.gz # 使用 curl 下载 containerd v1.6.4 curl -L https://github.com/containerd/containerd/releases/download/v1.6.4/cri-containerd-cni-1.6.4-linux-amd64.tar.gz -O cri-containerd-cni-1.6.4-linux-amd64.tar.gz3. 安装 tartar 命令简介Linux tar(英文全拼:tape archive )命令用于备份文件。tar 是用来建立,还原备份文件的工具程序,它可以加入,解开备份文件内的文件。# 安装 tar sudo yum install -y tar # 查看压缩包包含哪些文件 sudo tar -tf cri-containerd-cni-1.6.4-linux-amd64.tar.gz # 将压缩包解压至 cri-containerd-cni 文件夹(这里用命令创建 cri-containerd-cni文件夹,因为没有实现创建该文件夹),防止将文件都解压缩到当前文件夹(可以看到有etc、opt、usr三个子文件夹)。 sudo tar xzf cri-containerd-cni-1.6.4-linux-amd64.tar.gz -C cri-containerd-cni | mkdir cri-containerd-cnicri-containerd-cni-1.6.4-linux-amd64.tar.gz 文件中包含三个文件夹,分别是【etc、opt、usr】,如下图所示:4. 安装 containerd# 解压 containerd 到根目录 tar zxvf cri-containerd-cni-1.6.0-linux-amd64.tar.gz -C / # 生成 containerd 默认配置 mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml注意:确认 containerd 可执行文件所在目录在 PATH 环境变量中。5. 配置 containerd 软件源参考配置文件 "/etc/containerd/config.toml" 如下:disabled_plugins = [] imports = [] oom_score = 0 plugin_dir = "" required_plugins = [] root = "/var/lib/containerd" state = "/run/containerd" version = 2 [cgroup] path = "" [debug] address = "" format = "" gid = 0 level = "" uid = 0 [grpc] address = "/run/containerd/containerd.sock" gid = 0 max_recv_message_size = 16777216 max_send_message_size = 16777216 tcp_address = "" tcp_tls_cert = "" tcp_tls_key = "" uid = 0 [metrics] address = "" grpc_histogram = false [plugins] [plugins."io.containerd.gc.v1.scheduler"] deletion_threshold = 0 mutation_threshold = 100 pause_threshold = 0.02 schedule_delay = "0s" startup_delay = "100ms" [plugins."io.containerd.grpc.v1.cri"] disable_apparmor = false disable_cgroup = false disable_hugetlb_controller = true disable_proc_mount = false disable_tcp_service = true enable_selinux = false enable_tls_streaming = false ignore_image_defined_volumes = false max_concurrent_downloads = 3 max_container_log_line_size = 16384 netns_mounts_under_state_dir = false restrict_oom_score_adj = false #sandbox_image = "k8s.gcr.io/pause:3.6" # 1. 修改基础镜像地址(此处以阿里云为例) #sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6" sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.7" selinux_category_range = 1024 stats_collect_period = 10 stream_idle_timeout = "4h0m0s" stream_server_address = "127.0.0.1" stream_server_port = "0" systemd_cgroup = false tolerate_missing_hugetlb_controller = true unset_seccomp_profile = "" [plugins."io.containerd.grpc.v1.cri".cni] bin_dir = "/opt/cni/bin" conf_dir = "/etc/cni/net.d" conf_template = "" max_conf_num = 1 [plugins."io.containerd.grpc.v1.cri".containerd] default_runtime_name = "runc" disable_snapshot_annotations = true discard_unpacked_layers = false no_pivot = false snapshotter = "overlayfs" [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime] base_runtime_spec = "" container_annotations = [] pod_annotations = [] privileged_without_host_devices = false runtime_engine = "" runtime_root = "" runtime_type = "" [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime.options] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] base_runtime_spec = "" container_annotations = [] pod_annotations = [] privileged_without_host_devices = false runtime_engine = "" runtime_root = "" runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] BinaryName = "" CriuImagePath = "" CriuPath = "" CriuWorkPath = "" IoGid = 0 IoUid = 0 NoNewKeyring = false NoPivotRoot = false Root = "" ShimCgroup = "" SystemdCgroup = false [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime] base_runtime_spec = "" container_annotations = [] pod_annotations = [] privileged_without_host_devices = false runtime_engine = "" runtime_root = "" runtime_type = "" [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime.options] [plugins."io.containerd.grpc.v1.cri".image_decryption] key_model = "node" [plugins."io.containerd.grpc.v1.cri".registry] config_path = "" [plugins."io.containerd.grpc.v1.cri".registry.auths] [plugins."io.containerd.grpc.v1.cri".registry.configs] [plugins."io.containerd.grpc.v1.cri".registry.headers] [plugins."io.containerd.grpc.v1.cri".registry.mirrors] # 2. 设置仓库地址(镜像源) [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["https://usydjf4t.mirror.aliyuncs.com","https://mirror.ccs.tencentyun.com","https://registry.docker-cn.com","http://hub-mirror.c.163.com"] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"] endpoint = ["https://registry.cn-hangzhou.aliyuncs.com/google_containers"] # [plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.0.187:5000"] # endpoint = ["http://192.168.0.187:5000"] # [plugins."io.containerd.grpc.v1.cri".registry.configs] # [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.0.187:5000".tls] # insecure_skip_verify = true # [plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.creditgogogo.com".auth] # username = "admin" # password = "Harbor12345" [plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming] tls_cert_file = "" tls_key_file = "" [plugins."io.containerd.internal.v1.opt"] path = "/opt/containerd" [plugins."io.containerd.internal.v1.restart"] interval = "10s" [plugins."io.containerd.metadata.v1.bolt"] content_sharing_policy = "shared" [plugins."io.containerd.monitor.v1.cgroups"] no_prometheus = false [plugins."io.containerd.runtime.v1.linux"] no_shim = false runtime = "runc" runtime_root = "" shim = "containerd-shim" shim_debug = false [plugins."io.containerd.runtime.v2.task"] platforms = ["linux/amd64"] [plugins."io.containerd.service.v1.diff-service"] default = ["walking"] [plugins."io.containerd.snapshotter.v1.aufs"] root_path = "" [plugins."io.containerd.snapshotter.v1.btrfs"] root_path = "" [plugins."io.containerd.snapshotter.v1.devmapper"] async_remove = false base_image_size = "" pool_name = "" root_path = "" [plugins."io.containerd.snapshotter.v1.native"] root_path = "" [plugins."io.containerd.snapshotter.v1.overlayfs"] root_path = "" [plugins."io.containerd.snapshotter.v1.zfs"] root_path = "" [proxy_plugins] [stream_processors] [stream_processors."io.containerd.ocicrypt.decoder.v1.tar"] accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"] args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"] env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"] path = "ctd-decoder" returns = "application/vnd.oci.image.layer.v1.tar" [stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"] accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"] args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"] env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"] path = "ctd-decoder" returns = "application/vnd.oci.image.layer.v1.tar+gzip" [timeouts] "io.containerd.timeout.shim.cleanup" = "5s" "io.containerd.timeout.shim.load" = "5s" "io.containerd.timeout.shim.shutdown" = "3s" "io.containerd.timeout.task.state" = "2s" [ttrpc] address = "" gid = 0 uid = 0被修改的 "/etc/containerd/config.toml" 文件配置说明:1、基础镜像设置sandbox_image 设置国内基础镜像源地址。2、镜像仓库设置"docker.io" 配置 docker 公共镜像仓库源。"k8s.gcr.io" 配置 k8s 仓库源。其中 “192.168.0.187:5000” 是私人仓库地址(没有可以配置)。insecure_skip_verify = true 意为跳过 tls 证书认证。"harbor.creditgogogo.com".auth 设置仓库用户名和密码。6. 启动 containerd 并设置为开机启动由于上面下载的 containerd 压缩包中包含一个 "etc/systemd/system/containerd.service" 的文件,这样我们就可以通过 systemd 来配置 containerd 作为守护进程运行。[root@k8s-master-01 /]# ls etc/systemd/system/ bluetooth.target.wants cron.service dbus-org.bluez.service default.target multi-user.target.wants sockets.target.wants timedatex.service containerd.service ctrl-alt-del.target dbus-org.freedesktop.nm-dispatcher.service getty.target.wants network-online.target.wants sysinit.target.wants timers.target.wants启动 containerd:# 启动 containerd,并设置为开机启动 systemctl daemon-reload && systemctl enable containerd && systemctl start containerd # 查看 containerd 状态 systemctl status containerd # 重启 containerd systemctl restart containerd此处启动 containerd 可能会显示如下信息:System has not been booted with systemd as init system (PID 1). Can't operate. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. [Unit] Description=containerd container runtime Documentation=https://containerd.io After=network.target local-fs.target [Service] ExecStartPre=-/sbin/modprobe overlay ExecStart=/usr/local/bin/containerd Type=notify Delegate=yes KillMode=process Restart=always RestartSec=5 # Having non-zero Limit*s causes performance problems due to accounting overhead # in the kernel. We recommend using cgroups to do container-local accounting. LimitNPROC=infinity LimitCORE=infinity LimitNOFILE=infinity # Comment TasksMax if your systemd version does not supports it. # Only systemd 226 and above support this version. TasksMax=infinity OOMScoreAdjust=-999 [Install] WantedBy=multi-user.target这里有两个重要的参数:Delegate:这个选项允许 containerd 以及运行时自己管理自己创建容器的 cgroups。如果不设置这个选项,systemd 就会将进程移到自己的 cgroups 中,从而导致 containerd 无法正确获取容器的资源使用情况。KillMode:这个选项用来处理 containerd 进程被杀死的方式。默认情况下,systemd 会在进程的 cgroup 中查找并杀死 containerd 的所有子进程。KillMode 字段可以设置的值如下:control-group:当前控制组里面的所有子进程,都会被杀掉process:只杀主进程mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号none:没有进程会被杀掉,只是执行服务的 stop 命令注意:需要将 KillMode 的值设置为 process,这样可以确保升级或重启 containerd 时不杀死现有的容器。7. 查看 containerd 信息crictl info # 更多使用 crictl --helpK8s 配置阿里云 repo 文件(yum 软件源)默认配置是国外的镜像源,由于国内网络原因无法访问,所以配置阿里云的 yum 软件源。cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF阿里云 Kubernetes 镜像:【容器】 https://developer.aliyun.com/mirror/?spm=a2c6h.13651102.0.0.3e221b11RAcHlc&serviceType=mirror&tag=%E5%AE%B9%E5%99%A8【Kubernetes 镜像源】 https://developer.aliyun.com/mirror/kubernetes安装 kubeadm、kubelet 和 kubectl(所有节点)由于版本更新频繁,因此这里指定版本号部署。yum install -y kubelet-1.24.1 kubeadm-1.24.1 kubectl-1.24.1启动 kubelet 并设置开机启动systemctl enable kubelet && systemctl start kubeletps: 由于官网未开放同步方式,可能会有索引 gpg 检查失败的情况,这时请用【yum install -y --nogpgcheck kubelet kubeadm kubectl 】安装。部署 K8s Master 节点1. K8s 集群初始化(kubeadm init)依据上面的 vm 资源规划,在 master 节点(192.168.18.130)执行如下命令:kubeadm init \ --apiserver-advertise-address=192.168.18.130 \ --image-repository registry.aliyuncs.com/google_containers \ --kubernetes-version v1.24.1 \ --service-cidr=10.96.0.0/12 \ --pod-network-cidr=10.244.0.0/16 \ --ignore-preflight-errors=all参数说明:--apiserver-advertise-address 集群通告地址--image-repository 由于默认拉取镜像地址 k8s.gcr.io 国内无法访问,这里指定阿里云镜像仓库地址--kubernetes-version K8s 版本,与上面安装的一致--service-cidr 集群内部虚拟网络,Pod 统一访问入口--pod-network-cidr Pod网络,与下面部署的 CNI 网络组件 yaml 中保持一致--ignore-preflight-errors 忽略所有预检项的警告信息或者使用配置文件引导:$ vi kubeadm.conf apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration kubernetesVersion: v1.24.1 imageRepository: registry.aliyuncs.com/google_containers networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 $ kubeadm init --config kubeadm.conf --ignore-preflight-errors=all kubeadm init 初始化成功,输出如下信息:Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 192.168.18.130:6443 --token pc5d3x.9ccv3m5y1llljk90 \ --discovery-token-ca-cert-hash sha256:3f7b37c18a5ec21c3e225025e13a0ac53e7abdf717859808b24f9bc909b32b5b此处注意保存下 kubeadm init 初始化产生的信息,方便下面环节的部署操作使用。2. 拷贝文件到默认路径拷贝 kubectl 使用的连接 k8s 认证文件到默认路径mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config3. 查看 k8s 集群节点信息[root@k8s-master-01 ~]# kubectl get node NAME STATUS ROLES AGE VERSION k8s-master-01 Ready control-plane 4h8m v1.24.1 k8s-node-01 Ready <none> 3h14m v1.24.1 k8s-node-02 Ready <none> 3h14m v1.24.14. 查看 containerd 拉取的镜像[root@k8s-master-01 /]# crictl image ls IMAGE TAG IMAGE ID SIZE docker.io/calico/cni v3.23.1 90d97aa939bbf 111MB docker.io/calico/node v3.23.1 fbfd04bbb7f47 76.6MB registry.aliyuncs.com/google_containers/coredns v1.8.6 a4ca41631cc7a 13.6MB registry.aliyuncs.com/google_containers/etcd 3.5.3-0 aebe758cef4cd 102MB registry.aliyuncs.com/google_containers/kube-apiserver v1.24.1 e9f4b425f9192 33.8MB registry.aliyuncs.com/google_containers/kube-controller-manager v1.24.1 b4ea7e648530d 31MB registry.aliyuncs.com/google_containers/kube-proxy v1.24.1 beb86f5d8e6cd 39.5MB registry.aliyuncs.com/google_containers/kube-scheduler v1.24.1 18688a72645c5 15.5MB registry.aliyuncs.com/google_containers/pause 3.7 221177c6082a8 311kB注意:上面的镜像除了 calico 相关的(docker.io/calico/cni 和 docker.io/calico/node) 之外,其他都是 kubectl int 初始化拉取的镜像资源。Worker 节点加入 K8s 集群1. k8s 集群环境加入新的 worker node向集群添加新 node 节点,执行在 kubeadm init 输出的 kubeadm join 命令,分别在 worker node 执行如下命令:kubeadm join 192.168.18.130:6443 --token pc5d3x.9ccv3m5y1llljk90 \ --discovery-token-ca-cert-hash sha256:3f7b37c18a5ec21c3e225025e13a0ac53e7abdf717859808b24f9bc909b32b5b2. 生成加入 k8s 集群环境的 token默认 token 有效期为 24 小时,当过期之后,该 token 就不可用了。这时就需要重新创建 token,操作如下:$ kubeadm token create $ kubeadm token list $ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //' 63bca849e0e01691ae14eab449570284f0c3ddeea590f8da988c07fe2729e924 $ kubeadm join 192.168.31.61:6443 --token nuja6n.o3jrhsffiqs9swnu --discovery-token-ca-cert-hash sha256:63bca849e0e01691ae14eab449570284f0c3ddeea590f8da988c07fe2729e924或者直接命令快捷生成kubeadm token create --print-join-command参考文档 =》https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/部署容器网络/CNI(所有节点)1. CNI 简介CNI 全称是 Container Network Interface,即容器网络的 API 接口。它是 K8s 中标准的一个调用网络实现的接口。Kubelet 通过这个标准的 API 来调用不同的网络插件以实现不同的网络配置方式。CNI 插件就是实现了一系列的 CNI API 接口。常见的 CNI 插件包括 Calico、Flannel、Terway、Weave Net 以及 Contiv。2. CNI 分类及选型参考CNI 插件可以分为三种:Overlay、路由及 Underlay。【Overlay 模式】的典型特征是容器独立于主机的 IP 段,这个 IP 段进行跨主机网络通信时是通过在主机之间创建隧道的方式,将整个容器网段的包全都封装成底层的物理网络中主机之间的包。该方式的好处在于它不依赖于底层网络;【路由模式】中主机和容器也分属不同的网段,它与 Overlay 模式的主要区别在于它的跨主机通信是通过路由打通,无需在不同主机之间做一个隧道封包。但路由打通就需要部分依赖于底层网络,比如说要求底层网络有二层可达的一个能力;【Underlay 模式】中容器和宿主机位于同一层网络,两者拥有相同的地位。容器之间网络的打通主要依靠于底层网络。因此该模式是强依赖于底层能力的。对于 CNI 插件的选择,有以下几个维度参考:环境限制,不同环境中所支持的底层能力是不同的。功能需求性能需求了解更多可以参考 =》https://www.kubernetes.org.cn/6908.html注意:CNI 插件只需要部署其中一个即可,这里推荐 Calico 或 Flannel。3. Calico 简介Calico 是一个纯三层的数据中心网络方案,Calico 支持广泛的平台,包括 Kubernetes、OpenStack 等。Calico 在每一个计算节点利用 Linux Kernel 实现了一个高效的虚拟路由器( vRouter) 来负责数据转发,而每个 vRouter 通过 BGP 协议负责把自己上运行的 workload 的路由信息向整个 Calico 网络内传播。此外,Calico 项目还实现了 Kubernetes 网络策略,提供 ACL 功能。4. 下载 Calico 插件wget https://docs.projectcalico.org/manifests/calico.yaml5. 修改里面定义的 Pod 网络下载完后还需要修改里面定义 Pod 网络(CALICO_IPV4POOL_CIDR),与前面 kubeadm init 指定的一样【--pod-network-cidr=10.244.0.0/16】# The default IPv4 pool to create on startup if none exists. Pod IPs will be # chosen from this range. Changing this value after installation will have # no effect. This should fall within `--cluster-cidr`. # - name: CALICO_IPV4POOL_CIDR # value: "192.168.0.0/16" - name: CALICO_IPV4POOL_CIDR value: "10.244.0.0/16"查找 CALICO_IPV4POOL_CIDRvi calico.yaml /CALICO_IPV4POOL_CIDR6. 应用 calico.yaml 配置修改完后应用 calico.yaml 清单kubectl apply -f calico.yaml输出如下信息:[root@k8s-master-01 /]# ls bin boot calico.yaml cri-containerd-cni-1.6.4-linux-amd64.tar.gz dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var [root@k8s-master-01 /]# kubectl apply -f calico.yaml configmap/calico-config created customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/caliconodestatuses.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/ipreservations.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created clusterrole.rbac.authorization.k8s.io/calico-node created clusterrolebinding.rbac.authorization.k8s.io/calico-node created daemonset.apps/calico-node created serviceaccount/calico-node created poddisruptionbudget.policy/calico-kube-controllers created7. 查看命名空间 kube-system 下的 podkubectl get pods -n kube-system输出如下信息:[root@k8s-master-01 /]# kubectl get pods -n kube-system NAME READY STATUS RESTARTS AGE calico-kube-controllers-56cdb7c587-mfl9d 1/1 Running 22 (21m ago) 126m calico-node-2dtr8 0/1 Running 35 (5s ago) 126m calico-node-hmdkf 0/1 CrashLoopBackOff 35 (2m6s ago) 126m calico-node-l6448 0/1 Error 38 (70s ago) 126m coredns-74586cf9b6-b5r68 1/1 Running 0 7h5m coredns-74586cf9b6-jndhp 1/1 Running 0 7h6m etcd-k8s-master-01 1/1 Running 0 7h9m kube-apiserver-k8s-master-01 1/1 Running 6 (20m ago) 7h9m kube-controller-manager-k8s-master-01 0/1 CrashLoopBackOff 23 (119s ago) 7h9m kube-proxy-ctx7q 1/1 Running 0 7h6m kube-proxy-l72sj 1/1 Running 0 6h15m kube-proxy-s65x9 1/1 Running 0 6h15m kube-scheduler-k8s-master-01 0/1 CrashLoopBackOff 21 (119s ago) 7h9m测试 K8s 集群验证 Pod 工作验证 Pod 网络通信验证 DNS 解析在 K8s 集群中创建一个 Pod,验证是否正常运行:kubectl create deployment nginx --image=nginx:latest -p 80:8080 kubectl expose deployment nginx --port=8080 --type=NodePort kubectl get pod,svc访问 nginx 的 svc 地址:http://NodeIP:Port 关于 K8s nodePort、port、targetPort、hostPort 端口的介绍 =》https://www.jianshu.com/p/8275f2031c83在 Master 节点部署 Dashboard1. 下载 dashboard 的 recommended.yaml 配置文件wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml2. recommended.yaml 文件重命名(可选)把下载的 recommended.yaml 文件重命名为 dashboard-v2.5.1.yaml (可选,方便文件记录)mv recommended.yaml dashboard-v2.5.1.yaml 3. 修改 Dashboard 配置文件暴露到外部访问默认 Dashboard 只能集群内部访问,修改 Service 为 NodePort 类型,暴露到外部访问:$ vi dashboard-v2.5.1.yaml kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: ports: - port: 443 targetPort: 8443 nodePort: 30001 selector: k8s-app: kubernetes-dashboard type: NodePort ---4. 执行 dashboard-v2.5.1.yaml 配置文件kubectl apply -f dashboard-v2.5.1.yaml 输出如下信息:[root@k8s-master-01 /]# kubectl apply -f /root/dashboard-v2.5.1.yaml namespace/kubernetes-dashboard created serviceaccount/kubernetes-dashboard created service/kubernetes-dashboard created secret/kubernetes-dashboard-certs created secret/kubernetes-dashboard-csrf created secret/kubernetes-dashboard-key-holder created configmap/kubernetes-dashboard-settings created role.rbac.authorization.k8s.io/kubernetes-dashboard created clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created deployment.apps/kubernetes-dashboard created service/dashboard-metrics-scraper created deployment.apps/dashboard-metrics-scraper created5. 查看 kubernetes-dashboardkubectl get pods -n kubernetes-dashboard输出如下信息:[root@k8s-master-01 /]# kubectl get pods -n kubernetes-dashboard NAME READY STATUS RESTARTS AGE dashboard-metrics-scraper-7bfdf779ff-q9tsk 0/1 ContainerCreating 0 74s kubernetes-dashboard-6465b7f54c-frf2b 0/1 ContainerCreating 0 74s [root@k8s-master-01 /]# kubectl get pods -n kubernetes-dashboard NAME READY STATUS RESTARTS AGE dashboard-metrics-scraper-7bfdf779ff-q9tsk 1/1 Running 0 32m kubernetes-dashboard-6465b7f54c-frf2b 1/1 Running 0 32m6. 访问 kubernetes-dashboard访问 kubernetes-dashboard 地址https://NodeIP:30001创建 ServiceAccount 并绑定默认 cluster-admin 管理员集群角色:# 创建用户 $ kubectl create serviceaccount dashboard-admin -n kube-system # 用户授权 $ kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin # 获取用户 Token $ kubectl describe secrets -n kube-system $(kubectl -n kube-system get secret | awk '/dashboard-admin/{print $1}')此处 master 节点的 ip 是 192.168.18.130,浏览器访问地址 =》https://192.168.18.130:30001/#/login最后使用输出的 token 登录 k8s-Dashboard。总结搭建 k8s 集群环境时,事先要规划编排好相关的资源环境以及各个组件的版本信息,开始部署之前务必保障系统环境准备事项的相关设置操作,此处我们采用的容器运行时是 containerd,由于默认的 yum 镜像源是国外环境,在国内网络环境无法访问,可配置国内的 yum 源(比如:阿里云 yum 镜像源)保障网络资源的连通性和可访问性,方便后面环节的相关操作(比如:containerd/kubeadm/kubelet 的安装、k8s master 节点的部署、容器网络 CNI 安装及网络编排)。每一个环节都很重要,中途遇到异常,多查看资料分析下原因,关键在于理解。
AgileConfig 简介是一个基于 .net core 开发的轻量级配置中心。目标或解决的问题AgileConfig 并不是为了什么微服务,更多的是为了那些分布式、容器化部署的应用能够更加简单的读取、修改配置。AgileConfig 特点秉承轻量化的,部署简单、配置简单、使用简单、学习简单,它只提取了必要的一些功能,并没有像 Apollo 那样复杂且庞大。但是它的功能也已经足够你替换 web.config,appsettings.json 这些文件了。如果你不想用微服务全家桶,不想为了部署一个配置中心而需要看 N 篇教程跟几台服务器那么你可以试试 AgileConfig。部署简单,最少只需要一个数据节点,支持 docker 部署支持多节点分布式部署来保证高可用配置支持按应用隔离,应用内配置支持分组隔离支持多环境应用支持继承,可以把公共配置提取到一个应用然后其它应用继承它使用长连接技术,配置信息实时推送至客户端支持 IConfiguration,IConfigClient,IOptions 模式读取配置,原程序几乎可以不用改造配置修改支持版本记录,随时回滚配置如果所有节点都故障,客户端支持从本地缓存读取配置支持 Restful API 维护配置v-1.6.0 以上已支持服务注册与发现参考地址Github:https://github.com/dotnetcore/AgileConfigGitee:https://gitee.com/kklldog/AgileConfigAgileConfig releases:https://github.com/dotnetcore/AgileConfig/releasesAgileConfig Change log:https://github.com/dotnetcore/AgileConfig/blob/master/CHANGELOG.mdDocker 镜像:https://hub.docker.com/r/kklldog/agile_config作者博客:https://www.cnblogs.com/kklldog/p/agile-config.htmlAgileConfig 架构介绍AgileConfig 的架构比较简单,主要是分 3 块:客户端(Client)客户端程序是使用 netstandard2.0 开发的一个类库,方便 .net core 程序接入,nuget 搜AgileConfig.Client 就可以安装。可以在启动客户端的时候配置多个节点的地址,客户端会随机挑选一个进行连接,连接成功后会维持一个 websocket 长连接。如果连接的节点发生故障导致连接中断,客户端会继续随机一个节点进行连接,直到连接成功。节点、管理程序(Node Server、Console)节点是使用 asp.net core 开发的一个服务。为了部署简单,直接把管理程序跟节点服务合二为一了。任何一个节点都可以在启动的时候配置环境变量开启管理程序功能。数据库(Database)使用数据库来存储数据,目前支持 Sqlserver, Mysql, Sqlite, PostgreSql, Oracle 五种数据库。最新版本已经切换为 FreeSql 为数据访问组件。FreeSql 对多数据库的支持更加强劲,特别是对国产数据库的支持。但是因为没有国产数据库的测试环境,本项目并未支持,如果有需要我可是开分支尝试支持,但是测试工作就要靠用户啦。注意:如果使用 <=1.0.4 之前版本的用户请不要更新,因为 EFCore 跟 FreeSql 自动建的库可能存在稍许差异,保险起见不要更新吧。关于高可用AgileConfig 的节点都是无状态的,所以可以横向部署多个节点来防止单点故障。在客户端配置多个节点地址后,客户端会随机连接至某个节点。问题影响说明控制台下线无法维护配置,客户端无影响因为控制台跟节点是共存的,所以某个控制台下线一般来说同样意味着一个节点的下线某个节点下线客户端重连至其他节点无任何影响所有节点下线客户端从内存读取配置启动的客户端会从内存读取配置,未启动的客户端会再尝试连接到节点多次失败后,尝试从本地文件缓存读取配置,保证应用可以启动注意:结合 DB 数据库的高可用技术搭配使用。AgileConfig 服务端搭建初始化 DB 数据库用户只需要手工建一个空库,所有的表在第一次启动的时候都会自动生成。目前支持 SqlServer,MySQL,Sqlite, PostgreSQL,Oracle 五种数据库。 docker 运行服务端项目即可初始化对应的数据库,此处以 PostgreSQL 数据库为例。DB Provider 对照表数据名称:agile_config,用户:chait,密码:123456db providerdb type连接字符串sqlserverSQL ServerData Source=127.0.0.1,1433;Initial Catalog=agile_config;User Id=chait;Password=123456;TrustServerCertificate=true;Pooling=true;Max Pool Size=50mysqlMySQLData Source=127.0.0.1;Port=3306;Initial Catalog=agile_config;User ID=chait;Password=123456;Charset=utf8mb4;SslMode=none;Max pool size=50sqliteSqliteData Source=agile_config.db;Version=3;UseUTF16Encoding=True;Password=123456;Pooling=true;FailIfMissing=false;Max Pool Size=50npgsqlPostgreSQLServer=127.0.0.1;Port=5432;Database=agile_config;Username=chait;Password=123456;Pooling=true;Maximum Pool Size=50oracleOracleData Source=//127.0.0.1:1521/XE;User Id=chait;Password=123456;Pooling=true;Max Pool Size=50Docker 部署服务端docker run 命令运行服务端拉取 agile_config 镜像docker pull kklldog/agile_config:latest运行 agile_config 容器sudo docker run \ --name agile_config \ -e TZ=Asia/Shanghai \ -e adminConsole=true \ -e db:provider=sqlite \ -e db:conn="Data Source=agile_config.db;Version=3;UseUTF16Encoding=True;Password=123456;Pooling=true;FailIfMissing=false;Max Pool Size=50" \ -p 15000:5000 \ -v /agileconfig:/app/db \ kklldog/agile_config:latest参数说明:name:容器名,指定个容器名。TZ:指定时区。adminConsole:配置程序是否使用管理控制台。如果为 true 则启用控制台功能,访问该实例会出现管理界面。每个实例都可以选择使用管理界面,共用一套数据源只是呈现端口不同。默认账号为 admin,首次登录需要设置密码,设置后多个管理界面都可以通用。db:provider:配置程序的数据库类型。目前程序支持:sqlite,mysql,sqlserver,npgsql, oracle 五种数据库。按照项目中允许的数据库使用即可。首个节点启动后会创建数据表(相当好~)。db:env:{env}:provider,可以指定特定环境下使用某个数据库,如【db:env:PROD.provider=sqlserver, db:env:DEVELOPMENT.provider=mysql】。db:conn:配置数据库连接串。按照不同的数据库类型设置不同的数据库连接字符串。数据库使用第二步创建的库。 默认内置了 DEV, TEST, STAGING, PROD 四个常用的环境,如不够,可直接操作 agc_setting 表,增加自定义环境。db:env:{env}:conn,可以指定特定环境下使用某个数据库,如 【db:env:PROD.conn=xxx, db:env:DEVELOPMENT.conn=xxx】。p:指定对外端口,用户客户端去连接。设置允许使用的对外端口即可。v:节点的数据卷挂载。此处挂载到第一步设置的文件夹路径下,可按实际需要设置挂载路径或是不设置 -v 参数也行。-d:设置容器后台运行。通过 docker 建立一个 agile_config 容器实例,其中有 3 个环境变量需要配置:adminConsole 配置程序是否为管理控制台。如果为 true 则启用控制台功能,访问该实例会出现管理界面。db:provider 配置程序的数据库类型。目前程序支持:sqlite,mysql,sqlserver,npgsql,oracle 五种数据库。db:conn 配置数据库连接串。docker compose 运行服务端除了上面的 docker run 的方式运行 agile_config 容器实例,还可以使用 docker compose 来快速创建,此处以 postgresql 数据库为例,编写 yaml 文件如下:version: '3' services: agile_config_admin: image: "kklldog/agile_config:latest" ports: - "15000:5000" networks: - net0 volumes: - /etc/localtime:/etc/localtime environment: - TZ=Asia/Shanghai - adminConsole=true - nodes=agile_config_admin:5000,agile_config_node1:5000,agile_config_node2:5000 - db:provider=npgsql - db:conn= Server=Server=127.0.0.1;Port=5432;Database=agile_config;Username=chait;Password=123456;Pooling=true;Maximum Pool Size=50 agile_config_node1: image: "kklldog/agile_config:latest" ports: - "15001:5000" networks: - net0 volumes: - /etc/localtime:/etc/localtime environment: - TZ=Asia/Shanghai - db:provider=npgsql - db:conn= Server=127.0.0.1;Port=5432;Database=agile_config;Username=chait;Password=123456;Pooling=true;Maximum Pool Size=50 depends_on: - agile_config_admin agile_config_node2: image: "kklldog/agile_config:latest" ports: - "15002:5000" networks: - net0 volumes: - /etc/localtime:/etc/localtime environment: - TZ=Asia/Shanghai - db:provider=npgsql - db:conn= Server=127.0.0.1;Port=5432;Database=agile_config;Username=chait;Password=123456;Pooling=true;Maximum Pool Size=50 depends_on: - agile_config_admin networks: net0:注意:如果通过 IIS 或者别的方式部署,请自行从主页上的 releases 页面下载最新的部署包。如果自己使用源码编译,请先编译 react-ui-antd 项目把 dist 内的产物复制到 apisite 项目的 wwwroot/ui 目录下。浏览器查看 AgileConfig 管理界面浏览器输入如下地址:http://localhost:15000/ui#/user/login http://localhost:15000/ui#/home看到如下界面,说明 AgileConfig 服务端已经部署成功。注意:第一次运行程序需要初始化管理员密码。AgileConfig 管理界面介绍菜单说明菜单说明首页显示一些相关指标的统计数据。节点AgileConfig 支持多节点部署,所有的节点都是平行的。为了简化部署,AgileConfig 并没有单独的控制台程序,请直接使用任意一个节点作为控制台。当环境变量 adminConsole=true 时,该节点同时兼备数据节点跟控制台功能。为了控制台能够管理节点,所以需要在控制台配置节点的信息。注意:即使是作为控制台的数据节点同样需要添加到管理程序,以便管理它。应用AgileConfig 支持多应用程序接入。需要为每个应用程序配置名称、ID、秘钥等信息。每个应用可以设置是否可以被继承,可以被继承的应用类似 apollo 的公共 namespace 的概念。公共的配置可以提取到可继承应用中,其它应用只要继承它就可以获得所有配置。如果子应用跟被继承应用之间的配置键发生重复,子应用的配置会覆盖被继承的应用的配置。子应用可以继承多个应用,如果多个应用之间发生重复键,按照继承的顺序,后继承的应用的配置覆盖前面的应用。配置项配置完应用信息后可以为每个应用配置配置项。配置项支持分组。新添加的配置并不会被客户端感知到,需要手工点击“上线”才会推送给客户端。已上线的配置如果发生修改、删除、回滚操作,会实时推送给客户端。版本历史记录了配置的历史信息,可以回滚至任意版本。客户端控制台可以查看已连接的客户端。日志系统日志记录了 AgileConfig 生产中的一些关键信息。用户可以添加管理配置中心的用户人员信息,用户组,角色。AgileConfig 客户端使用查看 nuget 包 =》https://www.nuget.org/packages/AgileConfig.Client/新建 asp.net core webapi 项目,并添加 nuget 包【AgileConfig.Client】新建项目 WebApplication.AgileConfig,项目结构如下:注意:chait.agileconfig.client.configs.cache 该文件是本地缓存文件,运行项目后生成。在新建项目添加 nuget 包 AgileConfig.Client。Install-Package AgileConfig.Client -Version 1.6.1修改 appsettings.json/appsettings.Development.json 配置文件,添加 AgileConfig 配置。{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" "AllowedHosts": "*", //agile_config "AgileConfig": { "appId": "chait", "secret": "xxx", "nodes": "http://192.168.10.251:15000,http://192.168.10.251:15001,http://192.168.10.251:15002", //多个节点使用逗号分隔, "name": "client_name", "tag": "tag1", "env": "DEV", "httpTimeout": "100", "cache": { "directory": "Config" }agile_config 配置项说明:修改 Program.cs 文件,在 Program.cs 设置使用 AgileConfig,如此增加环境后,AgileConfigProvider 便会从相应环境 appsettings.json 中读取上述配置。在 appsettings.json 文件(默认根文件)配置 agileconfig 的配置信息。 public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseAgileConfig(e => Console.WriteLine($"configs {e.Action}")) .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>(); });使用 UseAgileConfig 扩展方法设置一个配置源。public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseAgileConfig(new ConfigClient($"Config\\appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json"), e => Console.WriteLine($"Action={e.Action},Key={e.Key}")) .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>(); });使用 ConfigureAppConfiguration 扩展方法设置一个配置源。public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => config.Sources.Clear(); // 获取宿主机环境变量 var env = hostingContext.HostingEnvironment; //Console.WriteLine($"HostingEnvironment:ApplicationName={env.ApplicationName},EnvironmentName={env.EnvironmentName},ContentRootPath={env.ContentRootPath}"); //string basePath = Path.Join(AppContext.BaseDirectory, "Config"); //string basePath = Path.Join(Directory.GetCurrentDirectory(), "Config"); string basePath = Path.Join(env.ContentRootPath, "Config"); // 设置 json 配置文件路径 config.SetBasePath(basePath: basePath) .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile(path: $"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (args != null) config.AddCommandLine(args); string configClientPath = string.Empty; if (env.IsProduction()) configClientPath = $"{basePath}\\appsettings.json"; // 指定参数获取环境变量名称,等效于 env.EnvironmentName var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); configClientPath = $"{basePath}\\appsettings.{environmentName}.json"; // new一个client实例,传参指定本地 appsettings.json 文件路径读取配置 var configClient = new ConfigClient(configClientPath); // 使用 AddAgileConfig 配置一个新的 IConfigurationSource 注册项,并输出修改事件信息 config.AddAgileConfig(configClient, e => Console.WriteLine($"Action={e.Action},Key={e.Key}")); .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>(); });注意上面的 2、3 两种扩展方法是等效的,UseAgileConfig 扩展方法内部会设置 basePath。在 Startup.cs 中添加服务services.AddAgileConfig();从 AgileConfig 配置中心读取客户端项目配置信息AgileConfig 支持以下方式读取配置信息:支持 asp.net core 标准的 IConfigurationIOptions 模式读取配置AgileConfigClient 实例直接读取IConfigClient 模式读取配置新建 HomeController 文件,此处以为 IConfiguration,IConfigClient 为例,编写如下代码:using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; namespace WebApplication.AgileConfig.Controllers; [Route("api/[controller]")] [ApiController] public class HomeController : ControllerBase private readonly ILogger<HomeController> _logger; private readonly IConfiguration _configuration; private readonly IConfigClient _configClient; public HomeController(ILogger<HomeController> logger, IConfiguration configuration, IConfigClient _configClient) _logger = logger; _configuration = configuration; _configClient = configClient; /// <summary> /// 使用 IConfiguration 读取配置 /// </summary> /// <returns></returns> [HttpGet(ByIConfiguration)] public IActionResult ByIConfiguration() string aaa = _configuration["Abc:aaa"]; string info = $"Abc.aaa={aaa}"; _logger.LogInformation(info); return Ok(info); /// <summary> /// 使用 IConfigClient 读取配置 /// </summary> /// <returns></returns> [HttpGet("ByIConfigClient")] public IActionResult ByIConfigClient() var aaa = _configClient["Abc:aaa"]; string info = $"Abc.aaa={aaa}"; _logger.LogInformation(info); foreach (var item in _configClient.Data) Console.WriteLine($"{item.Key} = {item.Value}"); return Ok(info); }到此处客户端测试程序已经准备就绪,接下来在 AgileConfig 管理页面【应用】添加相关配置信息。AgileConfig 配置中心读取配置的优先级如果在 AgileConfig 中有则默认从那取值,没有再去找机密文件,再去找 appsettings.{env}.json,最后 appsettings.json,注意优先级最高的还是环境变量和命令行的配置。https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0#default-configurationhttps://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0#default-configurationAgileConfig 配置中心添加客户端项目配置信息选择 AgileConfig 管理页面 =》【应用】,新建客户端应用的配置信息,注意和上面的 appsettings.json 文件里面的配置保持一致。如下所示:点击【配置项】=》【新建】按钮,即可添加配置信息。从上图中可以看到, AgileConfig 配置中心默认提供 4 个环境变量:DEV、TEST、STAGING、PROD,可以新增 key-value 键值对、json 结构数据和 text 文本信息配置。新建配置信息:新建配置信息后,页面显示如下:点击【发布】即可让新增的配置信息生效。项目测试dotnet cli 启动项目使用 dotnet cli 启动 WebApplication.AgileConfig 项目,输出如下信息:Windows PowerShell Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows PS C:\Users\sws-dev-server\Desktop\dapr-demo\dapr-demo\WebApplication.AgileConfig> dotnet run 欢迎使用 .NET 6.0! --------------------- SDK 版本: 6.0.300 --------- .NET 工具会收集用法数据,帮助我们改善你的体验。它由 Microsoft 收集并与社区共享。你可通过使用喜欢的 shell 将 DOTNET_CLI_TELEMETRY_OPTOUT 环境变量设置为 "1" 或 "true" 来选择退出遥测。 阅读有关 .NET CLI 工具遥测的更多信息: https://aka.ms/dotnet-cli-telemetry ---------------- 已安装 ASP.NET Core HTTPS 开发证书。 若要信任该证书,请运行 "dotnet dev-certs https --trust" (仅限 Windows 和 macOS)。 了解 HTTPS: https://aka.ms/dotnet-https ---------------- 编写你的第一个应用: https://aka.ms/dotnet-hello-world 查找新增功能: https://aka.ms/dotnet-whats-new 浏览文档: https://aka.ms/dotnet-docs 在 GitHub 上报告问题和查找源: https://github.com/dotnet/core 使用 "dotnet --help" 查看可用命令或访问: https://aka.ms/dotnet-cli -------------------------------------------------------------------------------------- 正在生成... trce: AgileConfig.Client.ConfigClient[0] client try connect to server ws://192.168.10.251:15001/ws?client_name=client_name&client_tag=tag1 trce: AgileConfig.Client.ConfigClient[0] client connect server ws://192.168.10.251:15001/ws?client_name=client_name&client_tag=tag1 successful . trce: AgileConfig.Client.ConfigClient[0] client load all the configs success by API: http://192.168.10.251:15002/api/config/app/chait?env=DEV , try count: 0. info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5000 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: C:\Users\sws-dev-server\Desktop\dapr-demo\dapr-demo\WebApplication.AgileConfig查看【节点】信息浏览器查看【节点】信息,如下所示:输入 http://localhost:15000/ui#/nodeAPI 接口获取配置信息访问 HomeController 获取配置信息以上就是在 asp.net core webapi 项目中使用 AgileConfig 配置中心的全部过程,欢迎更多的小伙伴使用,基于 .net core 开发的轻量级配置中心,方便好用!参考资料https://github.com/dotnetcore/AgileConfighttps://hub.docker.com/r/kklldog/agile_confighttps://www.cnblogs.com/kklldog/p/agile-config.html
缓存在 App 中的应用缓存(cache)工作原理说到缓存,我们来看下百度百科的介绍:缓存(cache),原始意义是指访问速度比一般 随机存取存储器(RAM) 快的一种 高速存储器,通常它不像系统主存那样使用 DRAM 技术,而使用昂贵但较快速的 SRAM 技术。缓存的设置是所有现代计算机系统发挥高性能的重要因素之一。动态随机存取存储器(Dynamic Random Access Memory,DRAM)静态随机存取存储器(Static Random-Access Memory,SRAM)目的:提高数据存取速度(缓存性能优化 万金油 )。在应用系统中通常会使用到缓存(cache)技术,其中应用系统多级缓存的工作原理大概介绍如下:说明:在 App 系统中存在多级缓存时,按顺序读取,从一级缓存(Level1 Cache,简称 L1 Cache)逐级往下获取。项目环境准备上一篇文章 我们介绍了 .net6 平台的 asp.net core webapi 框架中如何使用 ABP vNext 框架,本篇文章我们继续使用上次创建的 Demo.Abp.WebApplication1 项目,新增如下 NuGet 包文件:Volo.Abp.Caching,本地缓存;Volo.Abp.Caching.StackExchangeRedis,分布式缓存(Redis);说明:Volo.Abp.Caching.StackExchangeRedis 包已经包含 Volo.Abp.Caching 包。一、添加 NuGet 包文件Demo.Abp.WebApplication1 项目中所有的 nuget packages 文件:说明:在 上篇文章 中,ABP vNext 的这些 nuget packages 文件还未发布 v6.0.0 正式版,生产环境中推荐使用正式稳定版。关于 asp.net core webapi 项目如何遵循 Module(模块化)改造的具体细节,此处不再详细介绍。添加完成上图的 nuget packages 后,查看 Demo.Abp.WebApplication1.csproj 完整的工程项目文件,如下所示:<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="Volo.Abp.AspNetCore" Version="6.0.0" /> <PackageReference Include="Volo.Abp.Caching" Version="6.0.0" /> <PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="6.0.0" /> <PackageReference Include="Volo.Abp.Swashbuckle" Version="6.0.0" /> </ItemGroup> </Project>项目结构如下:二、改造 ApiController 类及相关文件此处为了模拟业务操作从数据库获取数据,在 WeatherForecastController 控制器中添加一个获取数据的方法:// 模拟数据库获取数据 private async Task<WeatherForecast> GetWeatherForecastAsync(Guid guid) _logger.LogDebug($"{DateTime.Now:G},查询数据库数据..."); var index = Random.Shared.Next(-2, 3); var data = new WeatherForecast Id = Guid.NewGuid(), // WeatherForecast 模型新增 Id 字段 Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] return await Task.FromResult(data); }WeatherForecast 模型新增 Id 字段。namespace Demo.Abp.WebApplication1; public class WeatherForecast public Guid Id { get; set; } public DateTime Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string? Summary { get; set; } }说明:此处仅说明 WeatherForecastController 控制器新增的部分方法,完整的改造代码后面会展示,尽量展示思路步骤。ABP vNext 缓存使用接下来我们分别演示在 asp.net core webapi 框架中如何使用 ABP vNext 提供的缓存包文件:Volo.Abp.Caching,本地缓存;Volo.Abp.Caching.StackExchangeRed,分布式缓存(Redis);一、本地缓存使用由于Demo.Abp.WebApplication1 项目是使用默认创建的模式(MiniAPI) ,在 Program.cs 中的 DemoWebApiModule 类声明式添加 Module 化的 NuGet 包依赖,如下所示:导入命名空间(Namespace);using Microsoft.OpenApi.Models; using Volo.Abp; using Volo.Abp.AspNetCore; using Volo.Abp.Caching; using Volo.Abp.Modularity; using Volo.Abp.Swashbuckle;DemoWebApiModule 类声明式添加 Module 化的 NuGet 包依赖;[DependsOn( typeof(AbpAspNetCoreModule), typeof(AbpCachingModule), typeof(AbpSwashbuckleModule) public class DemoWebApiModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) var services = context.Services; services.AddControllers(); services.AddEndpointsApiExplorer(); services.AddAbpSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" }); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); options.HideAbpEndpoints(); // 隐藏 ABP vNext 的默认端点 public override void OnApplicationInitialization(ApplicationInitializationContext context) var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); if (env.IsDevelopment()) //app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseAbpSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "TestAPI"); app.UseExceptionHandler("/Error"); //app.UseHttpsRedirection(); app.UseStaticFiles(); // 不使用静态文件中间件,SwaggerUI将无法渲染UI页面 app.UseRouting(); app.UseAuthorization(); app.UseConfiguredEndpoints(); //代替原来的 app.MapControllers(); }WeatherForecastController 控制器中完整代码如下:using Microsoft.AspNetCore.Mvc; using Volo.Abp.Caching; using Microsoft.Extensions.Caching.Distributed; namespace Demo.Abp.WebApplication1.Controllers; /// <summary> /// WeatherForecast /// </summary> [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase private static readonly string[] Summaries = new[] "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" private readonly ILogger<WeatherForecastController> _logger; private readonly IDistributedCache<WeatherForecast> _cache; // 使用 abp vnext 本地缓存 // 构造函数 DI 注入本地缓存 public WeatherForecastController(ILogger<WeatherForecastController> logger, IDistributedCache<WeatherForecast> cache) _logger = logger; _cache = cache; // 常规查询数据 [HttpGet("/GetList")] public IEnumerable<WeatherForecast> GetDataFromWeatherForecastAsync() _logger.LogDebug($"{DateTime.Now:G},查询数据库数据..."); return Enumerable.Range(1, 5).Select(index => new WeatherForecast Id = Guid.NewGuid(), Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] .ToList(); // 使用 ABP vNext 本地缓存查询数据 [HttpGet("/Get")] public async Task<WeatherForecast> GetDataFromWeatherForecastAsync(Guid guid) _logger.LogDebug($"{DateTime.Now:G},查询数据使用本地缓存..."); var data = await _cache.GetOrAddAsync(key: guid.ToString(), factory: async () => await GetWeatherForecastAsync(guid), optionsFactory: () => new DistributedCacheEntryOptions // 添加绝对过期时间,设置 10 秒 AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(10) return data; // 模拟数据库获取数据 private async Task<WeatherForecast> GetWeatherForecastAsync(Guid guid) _logger.LogDebug($"{DateTime.Now:G},查询数据库数据..."); var index = Random.Shared.Next(-2, 3); var data = new WeatherForecast Id = guid, Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] return await Task.FromResult(data); // 此处仅为了例演示异步方法使用,该方法本身没啥实际意义 }1.1 启动项目运行改造好后,我们习惯性的启动项目运行,验证下是否能成功运行,如下所示:WeatherForecast 控制器中有两个对外公开的方法,分别是:/GetList,常规查询(未使用缓存)多条数据。/Get,使用缓存查询单条数据。1.2 测试访问接口【/GetList】1.2.1、curlcurl -X 'GET' \ 'http://localhost:5220/GetList' \ -H 'accept: text/plain' \ -H 'RequestVerificationToken: CfDJ8ODjDXixt1tDuxO1cuT_L2xih256N5ivD-oaqHU-qlOmT5DYo778fWPnN-FN9VwU4isJn1azpl9RMoXcaT_GuqD8ICyhdBtSTzizz-W56shntXTF7Uqp2iLUbKwbpURg_imHSk0sTJbF4uM_DGugscE' \ -H 'X-Requested-With: XMLHttpRequest'1.2.2、Request URL,浏览器输入http://localhost:5220/GetList1.2.3、响应数据Response body[ "id": "8b72f7bb-4ff2-449b-bc07-bbe175c0ba65", "date": "2022-10-09T19:15:11.1299624+08:00", "temperatureC": -15, "temperatureF": 6, "summary": "Chilly" "id": "a1606cc5-63ed-4a88-ac83-0d1299b452aa", "date": "2022-10-10T19:15:11.130221+08:00", "temperatureC": 19, "temperatureF": 66, "summary": "Freezing" "id": "3fe5cb1e-5148-49d2-a540-145a0f174abf", "date": "2022-10-11T19:15:11.1302229+08:00", "temperatureC": 50, "temperatureF": 121, "summary": "Mild" "id": "0c7d9b88-7925-4439-a037-81cd1fe08173", "date": "2022-10-12T19:15:11.1302232+08:00", "temperatureC": 32, "temperatureF": 89, "summary": "Mild" "id": "a76cc34b-af1a-421c-9719-742e07029f3a", "date": "2022-10-13T19:15:11.1302234+08:00", "temperatureC": 53, "temperatureF": 127, "summary": "Hot" ]Response headers content-type: application/json; charset=utf-8 date: Sat,08 Oct 2022 11:15:11 GMT server: Kestrel transfer-encoding: chunked 1.3 测试访问接口【/Get】1.3.1、curlcurl -X 'GET' \ 'http://localhost:5220/Get?guid=a76cc34b-af1a-421c-9719-742e07029f3a' \ -H 'accept: text/plain' \ -H 'RequestVerificationToken: CfDJ8ODjDXixt1tDuxO1cuT_L2xih256N5ivD-oaqHU-qlOmT5DYo778fWPnN-FN9VwU4isJn1azpl9RMoXcaT_GuqD8ICyhdBtSTzizz-W56shntXTF7Uqp2iLUbKwbpURg_imHSk0sTJbF4uM_DGugscE' \ -H 'X-Requested-With: XMLHttpRequest'1.3.2、Request URL,浏览器输入http://localhost:5220/GetList1.3.3、响应数据缓存默认设置的 10s 失效时间,在该时间段内多次请求访问,都不会直接操作数据库获取数据,而是从本地进程缓存中获取数据,此时说明我们使用到了 ABP vNetx 提供的 Volo.Abp.Caching 包的功能。二、分布式缓存使用2.1 ABP vNext 使用 Redis 默认依赖的 NuGet 包这里我们使用 Redis 作为分布式缓存服务,ABP vNext 默认使用了 StackExchange.Redis 包,连接字符串同 StackExchange.Redis 的写法,如下所示:127.0.0.1:6379,defaultDatabase=1,password=Abc...123 localhost:6379,defaultDatabase=1,password=Abc...123 # 或者2.2 准备 Redis-Server 服务说明:此处使用的 redis 是 docker 环境的容器服务,开放映射端口 6379,因此可以使用【127.0.0.1:6379】或者【localhost:6379】访问。2.3 测试 Redis-Server 可用性准备好 Redis-Server 后,使用 Redis 客户端 GUI(这里使用 AnotherRedis desktop manager)工具访问连接,测试下该服务是否正常可用,工具连接如下图所示(说明 redis-server 正常可用):2.4 修改 appsettings.json 配置文件接下来在 appsettings.json 文件中添加如下配置:"Redis": { "IsEnabled": true, "InstanceName": "StackExchangeRedis", // 可省略 "Configuration": "localhost:6379,defaultDatabase=1,password=Abc...123" // 未设置密码 password 可以不写 }2.5 自定义模块类添加 Module 化依赖最后修改自定义模块 DemoWebApiModule 类,修改代码如下:using Volo.Abp.Caching.StackExchangeRedis; // 导入 abp vnext 提供的 redis 命名空间 [DependsOn( typeof(AbpAspNetCoreModule), typeof(AbpCachingStackExchangeRedisModule), // 声明 Volo.Abp.Caching.StackExchangeRedis 模块依赖 typeof(AbpSwashbuckleModule) public class DemoWebApiModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) /* 如果报异常,添加此处配置 Configure<RedisCacheOptions>(options => { options.InstanceName = "StackExchangeRedis"; // 该实例名称可自定义 });*/ public override void OnApplicationInitialization(ApplicationInitializationContext context) }说明:"..." 省略的代码配置不变,和使用本地缓存一样即可。2.6 启动项目测试接口此处的测试环节和使用本地化缓存的测试一样,不再描述,成功访问接口后,我们使用 redis 可视化管理工具 ——AnotherRedis desktop manager 查看【DB1】里面是否存在对应的 key 和 values,如下图所示:2.7 使用自定义 RedisModule 模块类除了使用默认的 AbpCachingStackExchangeRedisModule 模块依赖,还可以自定义MyCachingStackExchangeRedisModule 模块类,该自定义模块类基本 copy 了默认的模块类,只是添加了 options.InstanceName 部分代码,修改如下:using Microsoft.Extensions.Caching.StackExchangeRedis; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.Caching; using Volo.Abp.Modularity; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Demo.Abp.WebApplication1.Moduls; [DependsOn(typeof(AbpCachingModule))] public class MyCachingStackExchangeRedisModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) IConfiguration configuration = context.Services.GetConfiguration(); string redisEnable = configuration["Redis:IsEnabled"]; if (!redisEnable.IsNullOrEmpty() && !bool.Parse(redisEnable)) return; context.Services.AddStackExchangeRedisCache(delegate (RedisCacheOptions options) // 新增部分 string redisInstanceName = configuration["Redis:InstanceName"]; if (!redisInstanceName.IsNullOrEmpty()) options.InstanceName = redisInstanceName; string redisConfiguration = configuration["Redis:Configuration"]; if (!redisConfiguration.IsNullOrEmpty()) options.Configuration = redisConfiguration; context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache, AbpRedisCache>()); }自定义 Module 模块类创建好后,同样地使用 DependsOn 特性显示声明依赖,代码如下所示:[DependsOn( typeof(AbpAspNetCoreModule), typeof(MyCachingStackExchangeRedisModule), // 声明自定义模块依赖 typeof(AbpSwashbuckleModule) )]总结通过学习 ABP vNext 模块化的缓存(cache)技术,分别提供了 本地化 和 分布式 两种模式的缓存,快速方便的为应用系统接入缓存,局部有效的提升了系统访问性能,当默认提供的分布式缓存服务不满足需求时,还可以自定义扩展分布式缓存模块,对 Module 模块化思想有更近一步的认识。
马上国庆了,dotNative 预祝大家假期愉快,Happy National Day!本文将介绍在 .net6 平台的 asp.net core webapi 框架中,如何使用 abp vnext 框架进行模块化开发,重在思想理解。ABP vNext 介绍官方介绍ABP vNext 本身是一个包含许多 NuGet 包的 模块化(Module) 框架。它还提供了一个完整的基础架构来开发你自己的具有实体、服务、数据库集成、API、UI 组件等等功能的应用程序模块。专业介绍ABP vNext 框架是一个基于 ASP.NET Core 的完整基础设施,通过遵循软件开发最佳实践和最新技术来创建现代 web 应用程序和 API,不同于老的 ABP 框架。新的 ABP vNext 框架核心库更加精简,因为将原有许多的组件从其核心库抽离成独立的组件。这样开发人员可以更加灵活的选择自己需要的功能进行集成,使项目远离臃肿的库,比起原有的 ABP 框架 ABP vNext 完全基于 ASP.NET Core 丢掉了历史包袱,设计更加合理,更加细粒度的模块化设计。通俗介绍ABP vNext 框架是一个集成多个第三方组件类库的一个应用程序集,遵循 模块化(Module) 思想实践的最佳方式。.NET 6 之 MiniAPI 的 ABP vNext 初体验新建项目 Demo.Abp.WebApplication1 项目通过 dotnet cli 命令新建 asp.net core webapi 项目,命名为 Demo.Abp.WebApplication1,执行如下命令:dotnet new webapi -n Demo.Abp.WebApplication1或者通过 Visual Studio Community 2022 (64 位) IDE 工具创建该项目。1、添加新建项目,选择 ASP.NET Core Web API 项目模板,点击【下一步】。2、配置新建项目,输入项目名称 ASP.NET Core Web API ,继续点击【下一步】。3、其他信息,这里只勾选【使用控制器】,然后点击【创建】。经过上面步骤, Demo.Abp.WebApplication1 项目就创建完毕了,项目结构如下:NuGet 添加 Abp 相关模块包然后在 Demo.Abp.WebApplication1 项目中添加 nuget 包:Volo.Abp.AspNetCoreVolo.Abp.SwashbuckleNuGet\Install-Package Volo.Abp.AspNetCore -Version 6.0.0-rc.5NuGet\Install-Package Volo.Abp.Swashbuckle -Version 6.0.0-rc.5Program.cs 文件默认代码如下:var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseAuthorization(); app.MapControllers(); app.Run();接下来我们改造默认的 Program.cs 文件,让其遵循 ABP vNext 模块化(Module)的编程风格,操作步骤如下:首先在 Program.cs 文件中新建一个 class 类,名称为 DemoWebApiModule,并继承 AbpModule。其次通过 DependsOn 特性来定义模块的依赖关系。然后在 Program.cs 文件中通过 AddApplication 注入自定义模块类。最后在 InitializeApplication 初始化 http 请求管道(request pipeline)。Abp 模块化(Module)改造模块化改造【同步版】Abp 模块化(Module) 改造后,Program.cs 文件 同步 版本的代码如下:using Volo.Abp; using Volo.Abp.AspNetCore; using Volo.Abp.Modularity; using Volo.Abp.Swashbuckle; var builder = WebApplication.CreateBuilder(args); // 重新注册 Configuration builder.Services.ReplaceConfiguration(builder.Configuration); // 添加自定义模块 DemoWebApiModule builder.Services.AddApplication<DemoWebApiModule>(); /* 同上等效代码 * 添加自定义模块 DemoWebApiModule builder.Services.AddApplication<DemoWebApiModule>(options => { options.Services.ReplaceConfiguration(builder.Configuration); // 重新注册 Configuration var app = builder.Build(); // 初始化 http 请求管道(request pipeline) app.InitializeApplication(); app.Run(); // 声明 ABP 对应的模块(Module)依赖 [DependsOn(typeof(AbpAspNetCoreModule))] // 自定义模块类 DemoWebApiModule,并继承自 AbpModule public class DemoWebApiModule : AbpModule // IoC 注册服务容器 public override void ConfigureServices(ServiceConfigurationContext context) var services = context.Services; services.AddControllers(); // http 请求管道(request pipeline)初始化 public override void OnApplicationInitialization(ApplicationInitializationContext context) var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseRouting(); app.UseAuthorization(); app.UseConfiguredEndpoints(); // 替换原来的 app.MapControllers(); }说明:请求管道(request pipeline) 由各种业务逻辑对应的 中间件(middleware) 组成。到这里默认的 asp.net core webapi 项目就依据 ABP vNext 模块化(Module)的风格改造完毕。模块化改造【异步版】我们继续上面的 Abp 模块化(Module) 【同步版】修改,此处使用【异步】模式构建,并集成 Swagger 模块,改造后代码如下:using Microsoft.OpenApi.Models; using Volo.Abp; using Volo.Abp.AspNetCore; using Volo.Abp.Modularity; using Volo.Abp.Swashbuckle; var builder = WebApplication.CreateBuilder(args); //builder.Services.ReplaceConfiguration(builder.Configuration); await builder.Services.AddApplicationAsync<DemoWebApiModule>(options => { options.Services.ReplaceConfiguration(builder.Configuration); var app = builder.Build(); await app.InitializeApplicationAsync(); await app.RunAsync(); [DependsOn( typeof(AbpAspNetCoreModule), typeof(AbpSwashbuckleModule) public class DemoWebApiModule : AbpModule public override async Task ConfigureServicesAsync(ServiceConfigurationContext context) var services = context.Services; services.AddControllers(); services.AddEndpointsApiExplorer(); services.AddAbpSwaggerGen(options => { options.SwaggerDoc("TestAPI", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" }); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); await Task.CompletedTask; public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); if (env.IsDevelopment()) //app.UseDeveloperExceptionPage(); app.UseSwaggerUI(); app.UseAbpSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "TestAPI"); app.UseExceptionHandler("/Error"); //app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseConfiguredEndpoints(); //代替原来的 app.MapControllers(); await Task.CompletedTask; }Properties —— launchSettings.json启动配置文件,你可以在项目中 “Properties” 文件夹中找到 launchSettings.json 文件。该文件是 ASP.NET Core 应用特有的配置标准,用于应用的启动准备工作,包括环境变量,开发端口等。在 launchSettings.json 文件中进行配置和右键项目—属性中所提交的更改的效果是一样的,并且支持同步更新。此文件设置了 Visual Studio 可以启动的不同环境,以下是示例项目中 launchSettings.json 文件生成的默认配置: { "$schema": "https://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:48492", "sslPort": 0 "profiles": { "Demo.Abp.WebApplication1": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "http://localhost:5220", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }运行 Module 模块化项目我们先运行下 Demo.Abp.WebApplication1 项目,正常运行后请求默认的 WeatherForecastController。浏览器输入 weatherforecast API 地址:http://localhost:5220/weatherforecastAPI 接口访问成功,如下所示:通过以上步骤我们就完成了对 asp.net core webapi 框架 MiniAPI 模式的 ABP vNext 模块化改造。.NET 6 还原 Program.cs & Startup.cs由于本人不太喜欢 MiniAPI 模式,因此再继续改造,还原为 .net core 3.1 时代的玩法 —— Program.cs & Startup.cs 模式(此处不知是否有同感的小伙伴呢?)。此处为了和上面项目形成对照(原有项目保留不变),新建一个 asp.net core webapi 项目,命名为 Demo.Abp.WebApplication2,项目结构的改造对比如下图所示:说明:为了演示业务 Service 类中 DI 的注入方式,这里会引入一个 Abp 集成 Autofac 的模块 —— Volo.Abp.Autofac 。NuGet\Install-Package Volo.Abp.Autofac -Version 6.0.0-rc.5这里安装 NuGet 包同上,不再叙述,下面的步骤假定安装了如下 Abp vNext 依赖模块:Volo.Abp.AspNetCoreVolo.Abp.SwashbuckleVolo.Abp.Autofac通过这个改造过程,学会使用 Abp vNext 模块化、规范化的应用在实际项目开发中,并且会使用 Abp vNext 依赖模块( Module 模块化具有传染性)。改造步骤如下改造步骤,尽量详细的介绍一下项目结构中的每一个模块,相应的类使用文件夹划分,目录层次更佳清晰明了。1、提取 DemoWebApiModule.cs 模块化文件在 MiniAPI 模式中,为了避免新建文件,直接在 Program.cs 文件中新建自定义模块化类,此处为了更好的工程化对该文件单独提取,改造代码如下所示:using Microsoft.OpenApi.Models; using Volo.Abp; using Volo.Abp.AspNetCore; using Volo.Abp.Autofac; using Volo.Abp.Modularity; using Volo.Abp.Swashbuckle; using Demo.Abp.WebApplication2.Services; namespace Demo.Abp.WebApplication2.Module; // 使用 Abp 模块(Module) [DependsOn( typeof(AbpAspNetCoreModule), typeof(AbpAutofacModule), typeof(AbpSwashbuckleModule) // 自定义类 Module 化 public class DemoWebApiModule : AbpModule // IoC 注册服务容器 public override async Task ConfigureServicesAsync(ServiceConfigurationContext context) //1、IoC 注册服务类 var services = context.Services; services.AddControllers(); services.AddEndpointsApiExplorer(); services.AddAbpSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" }); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); // 2、注册具体的业务服务(声明式) context.Services.AddScoped<IWeatherForecastService, WeatherForecastService>(); await Task.CompletedTask; // Pipeline 管道中间件初始化 public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); if (env.IsDevelopment()) //app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseAbpSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test DemoWebApiModule API"); app.UseExceptionHandler("/Error"); //app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseConfiguredEndpoints(); // 替代 app.MapControllers(); await Task.CompletedTask; }2、Startup.cs 文件代码改造在 .net core 3.1.x 时代,默认 Program.cs & Startup.cs 模式,而在 Startup.cs 中存在两个方法:ConfigureServices(),在该方法中注册依赖关系/服务(无序)。Configure(),在该方法中(有序)注册中间件(Middleware),多个中间件组成 http 请求管道(Http request pipeline)。这里为了上述两个方法命名更佳见名知意,特此修改如下:ConfigureServices() 修改为 RegisterServicesAsync();Configure() 修改为 SetupMiddlewaresAsync();说明:这里是改造为异步模式,同步方式去除方法名称的 Async 后缀即可。新建 Startup.cs 类,添加如下代码:using Volo.Abp; using Volo.Abp.Modularity.PlugIns; using Demo.Abp.WebApplication2.Module; namespace Demo.Abp.WebApplication2; public class Startup public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) Configuration = configuration; // Add services to the container. 注册服务到 Ioc 容器 public async Task RegisterServicesAsync(IServiceCollection services, IHostBuilder host) Console.WriteLine($"{DateTime.UtcNow},加载 DemoWebApiModule 模块..."); host.UseAutofac(); // 使用 Autofac 第三方 DI //services.ReplaceConfiguration(Configuration); // 注册自定义模块 DemoWebApiModule await services.AddApplicationAsync<DemoWebApiModule>(options => { // options.UseAutofac(); options.Services.ReplaceConfiguration(Configuration); // 等效同上代码 // 加载插件,固定模式,可热插拔 // options.PlugInSources.AddFolder(@"E:\CodeStuday\dotnet6\DemoAbpPulgins"); // Configure the HTTP request pipeline. 配置 HTTP 请求管道(中间件管道即中间件委托链) public async Task SetupMiddlewaresAsync(IApplicationBuilder app, IWebHostEnvironment env) Console.WriteLine($"{env.ApplicationName},{DateTime.UtcNow},启动中间件管道初始化 InitializeApplicationAsync..."); await app.InitializeApplicationAsync(); }思考:当依赖模块变多,需要频繁的调整,这样就会违背开闭原则,那么该如规避该问题呢?其实 ABP vNext 官网提供了解决方案(PlugIns,插件机制),遵循开闭原则,只需添加 NuGet 包 Volo.Abp.Modularity.PlugIns ,便可实现 Module 模块的插件式加载,只需如下两不操作即可:在项目根文件添加一个文件夹,如:DemoAbpPulgins(文件夹命名尽量见名知意)。固定模式,在 AddApplication 或 AddApplicationAsync 的 options 中添加如上代码(注释部分)。将模块化组件编译好的 Xxx.dll 文件拷贝到新建的根文件夹里面,保存运行即可。以上就实现了 Abp 模块的 动态加载,规避了 开闭原则 之冲突点。开闭原则:在面向对象编程中声明 “软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭”;这样的实体可以允许在不修改源代码的情况下对其行为进行扩展。3、Program.cs 文件代码改造由于 Startup.cs 文件里面的代码是异步的,所以 Program.cs 文件也需要调整为异步模式(异步具有传染性)。namespace Demo.Abp.WebApplication2; public class Program public static async Task Main(string[] args) var builder = WebApplication.CreateBuilder(args); // 此处代码,已经调整到 RegisterServicesAsync 方法,注意观察。 //builder.Services.ReplaceConfiguration(builder.Configuration); //builder.Host.UseAutofac(); // 直接使用Autofacz var startup = new Startup(builder.Configuration); await startup.RegisterServicesAsync(builder.Services, builder.Host); var app = builder.Build(); await startup.SetupMiddlewaresAsync(app, builder.Environment); await app.RunAsync(); }4、WeatherForecastService.cs 文件改造这里使用 WeatherForecastService.cs 文件来模拟具体的业务服务,继承自 IWeatherForecastService 接口,结构如上图中 Services 文件夹部分,代码定义如下:IWeatherForecastService 接口,定义业务方法规范。using Demo.Abp.WebApplication2.Model; namespace Demo.Abp.WebApplication2.Services; public interface IWeatherForecastService public IEnumerable<WeatherForecast> GetWeatherForecast(); }WeatherForecastService.cs 类,继承自 IWeatherForecastService 接口。using Volo.Abp.DependencyInjection; //引入 DI using Demo.Abp.WebApplication2.Model; namespace Demo.Abp.WebApplication2.Services; // Dependency 特性声明 Service 服务生命周期 [Dependency(ServiceLifetime.Scoped, ReplaceServices = true)] public class WeatherForecastService : IWeatherForecastService private static readonly string[] Summaries = new[] "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" // 模拟业务操作,数据库取数据 public IEnumerable<WeatherForecast> GetWeatherForecast() return Enumerable.Range(1, 5).Select(index => new WeatherForecast Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }).ToArray(); }5、WeatherForecast.cs 模型类这里使用默认的模型,代码如下:namespace Demo.Abp.WebApplication2.Model; public class WeatherForecast public DateTime Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string? Summary { get; set; } }6、WeatherForecastController.cs 控制器类此处稍作调整,为了演示业务操作中 DI 调用,把默认代码调整到 WeatherForecastService.cs 类。调整代码如下:using Microsoft.AspNetCore.Mvc; using Demo.Abp.WebApplication2.Model; using Demo.Abp.WebApplication2.Services; namespace Demo.Abp.WebApplication2.Controllers; /// <summary> /// WebAPI 入口 /// </summary> [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase // 1、属性 DI 注入 public IWeatherForecastService _IWeatherForecastService { get; set; } // 2、构造函数 DI 注入 private readonly ILogger<WeatherForecastController> _logger; /// <summary> /// 构造函数 /// </summary> /// <param name="logger"></param> public WeatherForecastController(ILogger<WeatherForecastController> logger) _logger = logger; [HttpGet] public IEnumerable<WeatherForecast> GetWeatherForecast() _logger.LogDebug($"{DateTime.UtcNow}, hello abpvnext ..."); return _IWeatherForecastService.GetWeatherForecast(); }7、appsettings.json 配置文件此处就用默认生成的(这里只是为了尽量说明项目文件结构),配置如下:{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" "AllowedHosts": "*" }8、launchsettings.json 配置文件{ "$schema": "https://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:36683", "sslPort": 0 "profiles": { "Demo.Abp.WebApplication2": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "http://localhost:5165", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }到这里就基本完成了 Startup.cs 文件的改造,接下来我们启动运行测试看下,是否正常符合预期情况。启动运行测试启动 Abp 集成 Swagger 模块 —— Volo.Abp.Swashbuckle 符合预期正常显示。如果要隐藏 ABP vNext 的 默认端点,可以在 Swagger 配置中调用 HideAbpEndpoints 方法,AbpApiDefinitionAbpApplicationConfiguration完整代码如下:services.AddAbpSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" }); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); options.HideAbpEndpoints(); // 隐藏 ABP vNext 的默认端点 });运行项目,注意和上图对比,SwaggerUI 页面如下图所示:点击【WeatherForecast】执行业务 Service 的 DI 注册并调用,运行如下所示:1、curl 命令访问:curl -X 'GET' \ 'http://localhost:5165/WeatherForecast' \ -H 'accept: text/plain' \ -H 'RequestVerificationToken: CfDJ8ODjDXixt1tDuxO1cuT_L2z6DM3lUqtnRF0xKJPwuuLU0SKbwulLhV35ySmCqoC3CuQksJoqHnyzRQw5ZT-sE5Q20mC3vFONoLQ2Tx5Y1Y9qvQ67mtBCqNnSPtCePtukjn1Ocd6ocr-0E2fJVLroRYU' \ -H 'X-Requested-With: XMLHttpRequest'2、浏览器输入 url 访问:http://localhost:5165/WeatherForecastSwaggerUI 权限配置在生产环境中,考虑安全性,通常需要 Swagger 接入 OAuth2 认证。ABP vNext 集成了 Identity Server 来做权限认证,需要使用 AddAbpSwaggerGenWithOAuth 扩展,在 Module 的 ConfigureServices 方法中,使用 OAuth issuer 和 scopes 来配置 Swagger。 // IoC 注册服务容器 public override async Task ConfigureServicesAsync(ServiceConfigurationContext context) //1、IoC 注册服务类 var services = context.Services; services.AddControllers(); services.AddEndpointsApiExplorer(); // Swagger 配置权限 services.AddAbpSwaggerGenWithOAuth( authority: "https://localhost:44341", // authority issuer scopes: new Dictionary<string, string> { // scopes {"Test", "Test DemoWebApiModule API"} options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" }); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); options.HideAbpEndpoints(); // 隐藏 ABP vNext 的默认端点 // 2、注册具体的业务服务(声明式) context.Services.AddScoped<IWeatherForecastService, WeatherForecastService>(); await Task.CompletedTask; // Pipeline 管道中间件初始化 public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); if (env.IsDevelopment()) //app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseAbpSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test DemoWebApiModule API"); var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>(); options.OAuthClientId("TestSwagger"); // clientId options.OAuthClientSecret("!QAZ2wsx"); // clientSecret app.UseExceptionHandler("/Error"); //app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseConfiguredEndpoints(); // 替代 app.MapControllers(); await Task.CompletedTask; }说明:修改上面的代码配置后,需要提前搭建好 Identity Server 服务并启动运行,再该服务中注册 clientId & clientSecret,然后把 authority: "https://localhost:44341" 修改为 Identity Server 服务地址。关于 Identity Server 服务的搭建,这里不是重点,想了解更多,请自行查看官网或相关资料:欢迎使用 IdentityServer4,https://identityserver4docs.readthedocs.io/zh_CN/latest/index.htmlIdentityServer4 中文文档,http://www.identityserver.com.cn/IdentityServer4 认证服务器集成 Identity & 配置持久化数据库(Sqlite),http://t.zoukankan.com/liumengchen-boke-p-11179153.html修改配置如上代码后,运行项目 Swagger UI 如下图所示:总结从 asp.net core webapi 的 MiniAPI 模式开始,遵循 ABP vNext Web 框架的 Module 模块化思想并学会使用,再到 Program.cs & Startup.cs 模式的改造,使得项目工程化体验更佳、目录层级结构更清晰,再逐步引入其他模块(Module)依赖,并通过 DependsOn 特性显示声明。遵循 ABP vNext 框架的规则,在代码层面更加直观的体现 Module 模块化开发思想。当依赖模块多,需要频繁的调整代码模块的依赖, 此时 ABP vNext 框架提供了 PlugIns 插件化机制,实现 ABP vNext 模块 插件式 的动态加载,可热插拔,符合符合 开闭原则。
前言在实际的生产环境中,存在各种各样的应用程序相互访问,当用户访问 app 应用的时候,为了安全性考虑,通常都会要求搭配授权码或者安全令牌服务一并访问,这样可有效地对 Server 端的 API 资源起到一定程度的有效保护,大概流程如下:接下来我们就针对这个 API 请求访问的过程进行详细的了解,那么通常会存在哪些交互模式呢?常见的交互模式在应用请求访问的过程中,最常见的交互模式有以下几种:browser(浏览器)与 web app (web 应用程序)通信;web app 与 web APIs 进行通信;基于 browser(浏览器)的 app 应用程序与 web APIs 通信;native app (原生应用)与 web APIs 通信;基于 service 服务的 app 应用程序与 web APIs 通信;web APIs 与 web APIs 进行通信;通常情况,每个层(前端、中间层和后端)都必须保护资源,并实现身份 认证(Authentication)和 授权 (Authorization),所以它们通常是针对同一个 用户(User)进行存储。将这些基本安全功能外包给 STS(Security Token Service,安全令牌服务),可以防止在这些 App 应用程序和 端点(Endpoint)之间复制该功能。依据上面罗列的几种常见的交互模式,接下来我们对 app 应用程序进行重构以支持 STS,这将形成以下体系结构和协议:认证 & 授权在应用程序中,通常的 认证(Authentication)& 授权(Authorization) 服务流程如下:认证与授权是两个概念,不能混淆为一,接下来我们对这两个概念分别逐个了解。什么是 Authentication(认证)?对请求方或访问者的身份鉴别,意思就是确认你的身份是你。举个例子,比如早晨你去公司上班,到公司门口需要刷厂牌或工牌,然后公司的门禁卡会识别你个人的身份信息,接着鉴别或确认你是否所属公司的成员,如果是那么就可以进入公司范围,反之就不能进入。这个过程就称为认证。常见的身份验证协议:SAML2p,安全断言标记语言(英语:Security Assertion Markup Language,简称:SAML);WS-Federation,联合身份验证是 (安全域) 领域集合,这些领域已建立安全共享资源的关系。OpenID Connect,通常也叫 OIDC,是一套基于 OAuth 2.0 协议的轻量级规范,提供通过 API 进行身份交互的框架。其中 SAML2p 是最流行和最广泛部署的,而 OIDC 是三款中最新的,但被认为是未来的趋势,因为它具有现代应用的最大潜力。 它是从一开始就构建用于移动应用场景的,并且被设计为 API 友好。相关文章:干货|理解 SAML2 协议,https://baijiahao.baidu.com/s?id=1742115008490452461&wfr=spider&for=pcAPI 网关 OpenID Connect 使用指南,https://help.aliyun.com/document_detail/48019.htmlOpenID Connect 是什么?http://t.zoukankan.com/lexiaofei-p-7233230.htmlSAML2.0对接,https://help.aliyun.com/document_detail/114853.html?scm=20140722.184.2.173OAuth2.0对接,https://help.aliyun.com/document_detail/114852.html什么是 Authorization(授权)?在确认你的身份信息之后对你进行相应的授权。接着上面的例子,当公司的门禁卡鉴别了你的个人身份信息后,确定你是公司的所属成员,就可以顺利的进入公司,而公司里面又有很多的部门,每个部门有不同的职责范围,当然你对应的也所属其中某一个(或多个)部门,其中每个部门具有一定的权限范围,那么这个部门范围的划分过程就类似授权。常见的授权协议:OAuth(Open Authorization)是一个关于授权(authorization)的开放网络标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。OAuth 在全世界得到广泛应用,目前的版本是 2.1 版(https://oauth.net/2.1/)。OAuth 2.0 协议官方地址,https://www.rfc-editor.org/rfc/rfc6749OAuth 2.0 协议特点:简单:不管是 OAuth 服务提供者还是应用开发者,都很易于理解与使用;安全:没有涉及到用户密钥等信息,更安全更灵活;开放:任何服务提供商都可以实现 OAuth,任何软件开发商都可以使用 OAuth;OIDC 和 OAuth 2.0 非常相似,实际上 OIDC 是在 OAuth 2.0 之上的扩展。两个基本的安全考虑,身份验证和API 访问,被组合成了一个单一的协议 IdentityServer4( 简称 IDS4,文章下面会讲述),通常与 STS(安全令牌服务)一起单一往返。相关文章:OAuth 2.0,https://oauth.net/2/OAuth2.0 详解,https://zhuanlan.zhihu.com/p/509212673asp.net core 项目中的认证 & 授权通过对前面的 认证(Authentication)& 授权(Authorization) 的了解,我们来回顾下 asp.net core mvc/webapi 项目中 Filter 的 认证 & 授权 流程(你是否有似曾相识的感觉呢?),如下所示:IdentityServer4 框架什么是 IdentityServer4 ?通常情况下,我们会把 OIDC 和 OAuth 2.0 搭配使用,认为该组合是在可预见的未来保护现代应用程序的最佳方法。而 IdentityServer4 是这两个协议的实现,并且经过高度优化,可以解决当今 移动(mobile)、原生(native)和 Web 应用程序 的典型安全问题。官方解释:IdentityServer4 是基于 ASP.NET Core 实现的认证和授权框架,是对 OpenID Connect 和 OAuth 2.0 协议的实现。通俗理解:服务端 Server 对需要认证授权的资源(Resource,客户端请求资源)在外层使用 IdentityServer4 框架进行封装加壳,用户只能通过获取 IdentityServer4 颁发的令牌(Token)后,才能有效地进行后续的资源访问。总之 IdentityServer 是一个身份 认证(Authentication)& 授权(Authorization) 程序(或框架),该程序实现了 OIDC(OpenID Connect) 和 OAuth 2.0 协议。说明:同一种概念,不同的文献使用不同的术语,比如有些文献把他叫做 安全令牌服务(STS,Security Token Service)、身份提供(IP,Identity Provider)、授权服务器(Authorization Server)、IP-STS 等等。其实他们都是一个意思,目的 都是 在软件应用中为客户端颁发 Token 令牌并用于安全访问的。IdentityServer4 有哪些功能?IdentityServer4 提供如下功能:Resource 资源保护;使用本地帐户或通过外部身份提供程序对用户(User)进行身份验证;提供会话管理和单点登录(SSO) & 注销;管理和验证客户机(Clients);向客户颁发标识(Identity)和访问令牌(Access token);验证 Token 令牌;IdentityServer4 工作原理关于 IDS4 的术语解释:User(用户):用户是使用注册的客户端访问资源的人。Clients(客户端):客户端是从 IdentityServer 请求令牌的软件,用于验证用户(请求身份令牌)或访问资源(请求访问令牌)。 必须首先向 IdentityServer 注册客户端才能请求令牌。客户端的示例可以是:Web 应用程序、移动或桌面应用程序、SPA、服务器进程等。Resources(资源):资源是要用 IdentityServer 保护的资源,包括用户的身份信息或API。每个资源都有一个唯一的名称,客户端使用这个名称来指定他们想要访问的资源。用户的身份信息,包括名称或电子邮件等。API 资源则是客户端想要调用的功能,它们通常是 Web API,但不一定。Identity Token(身份令牌):身份令牌表示身份验证过程的结果。它至少包含:1、用户的标识;2、用户如何以及何时进行身份验证的信息。它也可以包含其他身份信息。Access Token(访问令牌):访问令牌允许用户访问 API 资源,客户端请求访问令牌并将其转发到 API。访问令牌包含有关客户端和用户的信息,API 使用该信息来授权用户访问它的数据。关于 IdentityServer 更多信息请参考相关文档:http://www.identityserver.com.cn/Home/Detail/shuyuhttps://identityserver4docs.readthedocs.io/zh_CN/latest/index.htmlhttps://identityserver4.readthedocs.io/en/latest/IdentityServer4 应用示例为了演示下面模式,创建项目结构如下:1、基于内存模式(Sample.WebIdentityServer)1.1 Client 模式,Sample.ConsoleApp 访问 Sample.WebIdentityServer1.2 Server 模式,Sample.WebApi 访问 Sample.WebIdentityServer2、基于 db 模式(Sample.WebIdentityServer)2.1 数据持久化 db1、安装 IdentityServer4 模板(可选)说明:此处使用 dotnet cli 安装 IdentityServer4 的前提是务必确保宿主机已经安装 dotnet sdk。此处为了更快的入门 IdentityServer4 框架,我们参照官方文档的快速入门部分,首先通过 dotnet cli 安装模板,执行如下命令:dotnet new -i IdentityServer4.Templates安装成功后,输出如下信息:将安装以下模板包: IdentityServer4.Templates 成功: IdentityServer4.Templates::4.0.1 已安装以下模板: 模板名 短名称 语言 标记 ---------------------------------------------------- -------- ---- ------------------- IdentityServer4 Empty is4empty [C#] Web/IdentityServer4 IdentityServer4 Quickstart UI (UI assets only) is4ui [C#] Web/IdentityServer4 IdentityServer4 with AdminUI is4admin [C#] Web/IdentityServer4 IdentityServer4 with ASP.NET Core Identity is4aspid [C#] Web/IdentityServer4 IdentityServer4 with Entity Framework Stores is4ef [C#] Web/IdentityServer4 IdentityServer4 with In-Memory Stores and Test Users is4inmem [C#] Web/IdentityServer42、创建 Sample.WebIdentityServer 项目首先为应用程序创建一个目录,然后使用 is4empty 模板创建一个包含基本 IdentityServer 设置的 ASP.NET Core 应用程序。cli 执行如下命令:cd E:\CodeStuday # 进入目标盘符(此处是E盘 CodeStuday 文件夹) md quickstart # mkdir 简写 md,创建 quickstart 文件夹 cd quickstart # 进入 quickstart 文件夹 md src # 创建 src 文件夹 cd src # 进入 src 文件夹 # cli 查看模板创建列表 dotnet new -l # 使用 is4empty 模板创建一个包含基本 IdentityServer 设置的 ASP.NET Core 应用程序 dotnet new is4empty -n Sample.WebIdentityServer或者通过 vs2022 手动创建一个 asp.net core empty 项目(等效同上),这里先给出改造好的项目结构如下:新建的 Sample.WebIdentityServer 项目改造如下:2.1、添加 Nuget 包 IdentityServer4添加完成后,项目 Sample.WebIdentityServer.csproj 工程文件如下:<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="IdentityServer4" Version="4.1.2" /> </ItemGroup> </Project>2.2、新增 IdentityServerConfig.cs 类,添加如下代码:using IdentityServer4.Models; using System.Collections.Generic; namespace Sample.WebIdentityServer; public static class IdentityServerConfig public static IEnumerable<ApiScope> ApiScopes => new[] new ApiScope Name = "sample_api", DisplayName = "Sample API" public static IEnumerable<Client> Clients => new[] new Client ClientId = "sample_client", ClientSecrets = new Secret("sample_client_secret".Sha256()) AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = { "sample_api" } }2.3、改造为 Program.cs & Startup.cs 模式由于 .net6 中的 asp.net core 默认只有 Program.cs,这里我们改造为 Program.cs & Startup.cs 模式。3.1 先添加 Startup.cs 文件,代码如下:namespace Sample.WebIdentityServer; public class Startup public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) Configuration = configuration; // Add services to the container. 注册服务到 Ioc 容器 public void RegisterServices(IServiceCollection services, IHostBuilder host) # 添加 IdentityServer var builder = services.AddIdentityServer(); # 添加开发人员签名凭据 builder.AddDeveloperSigningCredential(); # 使用内存模式,注册 ApiScopes 和 Clients builder.AddInMemoryApiScopes(IdentityServerConfig.ApiScopes); builder.AddInMemoryClients(IdentityServerConfig.Clients); // Configure the HTTP request pipeline. 配置 HTTP 请求管道(中间件管道即中间件委托链) public void SetupMiddlewares(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); # 使用 IdentityServer app.UseIdentityServer(); }3.2 接下来改造 Program.cs 文件中代码,修改如下:namespace Sample.WebIdentityServer; public class Program public static void Main(string[] args) var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run(); public static void Main(string[] args) var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); startup.RegisterServices(builder.Services, builder.Host); var app = builder.Build(); startup.SetupMiddlewares(app, builder.Environment); app.Run(); }到此处 Sample.WebIdentityServer 项目就改造完毕,可以尝试启动运行测试,成功运行后,会在该项目的根目录生成一个 tempkey.jwk 文件,文件内容如下:{ "alg": "RS256", "d": "gc7bg0NCxsOf8AMxX76ZubsTHblmN5WLSkbbk9miPBYIVhO4TdYAZX7rU65rU5v9Z6Kn9Tm-4gslOEfnivdTQAlq5SWkDe6S152so32z0xlH5gUL02686_IY6dHi89gwMNJziEU39PqrtQXhLpoVz2H1JXDzrqt5_QacueuZUG_tS1xEps1xelrjCk1isIRb3y05xLqWH4c4zNDXLqRaF9PR43FZ7Ea6mxAa3meZ0HZ23QN0075BByI0jkhV3TpQjYZ7oWjsXCeFrLGJILo6bWoP7Y-sQRgXmZChRvHXuYmEPeQlFSA1xwEJL8eadd8YL_T5o_wFWEg1rNwncoFYZQ", "dp": "I6s-o6dW1CjvO_SGLunlmwAJeHFT3sFjC9MXAIzjW2AhXWbocGEP5oS6jnHOJ-x3F5_FhRzabTOj-drZ9eAVD9HPDbhX0a6GvOX5l9orFC6Bd5EogZ2PfkaaDWRa-AjJULiLkEvS3EgSa0T223V1gKMQyj3bEjU0o2ZMoLnAiIk", "dq": "cmT7rXskO9aBk980wR32x3emigZK9GJCHg_U308zeGm4_WfPAbQrvd8zSQ8sdLMGi1bloiBeaGqHtLrQBQYpRw1W8IP6qvTwGHwqc5X9WQvuEtqBAmp0Dsc0eCHwrBDVtgzIxQGC63BAPIzMErnr2-pPGG1-yTmNuQkB7zf1fZ8", "e": "AQAB", "kid": "7D56F0331FF5B9BFBCFCB25DF9D81E44", "kty": "RSA", "n": "183z7AQ4qrQzOOoNg3ven3wgFeavnf2xncJ85bAL4df18rAKtlPaH6xc1ELhu01RUmpvHJCv4dQ9F4JTs0kXZMw3ZcIKw6fDuVKZThEMfkAkJ3ANkAVnwlOb_eoELi4ER5S_VLgX6YWXsP3GHZhLIdNjwBzXNW8E61IM0pbtuDsG8gmMMhoe6qxY_6IehLtL-FU1As9MxsRVfRFnnUl0paHSjUQsxHcqMDy8Oa-FKbnCIaj9ZI8Y5y1HF5YEOH2XA_6T8AUUCaMTZd92ojleMEh9INnrSvpc81epAZVFXMV4f1bwac_O1N5VKRWGxnqczBWaUwDucjHSYJTxTUlCmQ", "p": "_4j2ratx8BkrO4sylG5FwwpPtmx5yjGGzlwXYoBjoZ9rR0_ALQprAASB744tRVzErQXaTU2POZfud71kbYgV79l2TeQrtkyToPxDjJYT4O0ET8XLd5tJpmWl4_ifTBw-82c2wsDYCpqpHlFTZ6mci-_rxqZ8sxtkbKk_44XMy_8", "q": "2DJ7Qp4ZhVbZjDwY9HdyW1ah98CqhiW-G1-lZYuwPM90ym6Ejhc0yr4qL150VMacZEsZ4gcEacCsrxmV6dqlr2N6XMn6EkpsKbKjowOtC0hKHEdm1iFfPB_T0ZjfpJZZPsL1-wL1tWMfDIkcXfpkyJTaCaVSyMoX3UOE85qo0Wc", "qi": "hyrZ9xBy3P4ql7tN0TxUIiiHaIi9HUs2DBHibaRH61_g_Hlf9nzY6KCSlVjRVPC5TSGQy3sj-z7Im-Cpu9r1PQWuXk3zGCxmDcX5ppeJXqoxXT92SZh4IYQMKkgOZwUMKB7ebvw25yJf-BJOmsRu8KhKNNMWkWeOERQw8jrnz0A" }2.4 启动【Sample.WebIdentityServer】项目运行测试创建好项目后,习惯性的启动项目运行测试,正常启动后输出如下信息:PS E:\CodeStuday\dotnet6\Sample.WebIdentityServer> dotnet run 正在生成... info: IdentityServer4.Startup[0] Starting IdentityServer4 version 4.1.2+997a6cdd643e46cd5762b710c4ddc43574cbec2e info: IdentityServer4.Startup[0] You are using the in-memory version of the persisted grant store. This will store consent decisions, authorizaon codes, refresh and reference tokens in memory only. If you are using any of those features in production, you wanto switch to a different store implementatio info: IdentityServer4.Startup[0] Using the default authentication scheme idsrv for IdentityServer info: Microsoft.Hosting.Lifetime[14] Now listening on: https://localhost:5019 info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5018 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: E:\CodeStuday\dotnet6\Sample.WebIdentityServer\从上面的日志信息可以看出:1. Starting IdentityServer4 version 4.1.2+997a6cdd643e46cd5762b710c4ddc43574cbec2e. 2. You are using the in-memory version of the persisted grant store. 3. Now listening on: https://localhost:5019 & http://localhost:5018.接下来使用 ApiPost6 工具访问服务监听端口,操作如下:2.4.1、请求【.well-known/openid-configuration】配置文档:# url 格式 http://ip:port/.well-known/openid-configuration # 这里是本地主机,端口 5018 http://localhost:5018/.well-known/openid-configuration当 ApiPost 工具访问 url 地址后,控制台输出如下日志信息:info: IdentityServer4.Hosting.IdentityServerMiddleware[0] Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for /.well-known/openid-configuration【/.well-known/openid-configuration】接口返回如下信息:{ "issuer": "http://localhost:5018", "jwks_uri": "http://localhost:5018/.well-known/openid-configuration/jwks", "authorization_endpoint": "http://localhost:5018/connect/authorize", "token_endpoint": "http://localhost:5018/connect/token", "userinfo_endpoint": "http://localhost:5018/connect/userinfo", "end_session_endpoint": "http://localhost:5018/connect/endsession", "check_session_iframe": "http://localhost:5018/connect/checksession", "revocation_endpoint": "http://localhost:5018/connect/revocation", "introspection_endpoint": "http://localhost:5018/connect/introspect", "device_authorization_endpoint": "http://localhost:5018/connect/deviceauthorization", "frontchannel_logout_supported": true, "frontchannel_logout_session_supported": true, "backchannel_logout_supported": true, "backchannel_logout_session_supported": true, "scopes_supported": [ "sample_api", "offline_access" "claims_supported": [], "grant_types_supported": [ "authorization_code", "client_credentials", "refresh_token", "implicit", "urn:ietf:params:oauth:grant-type:device_code" "response_types_supported": [ "code", "token", "id_token", "id_token token", "code id_token", "code token", "code id_token token" "response_modes_supported": [ "form_post", "query", "fragment" "token_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post" "id_token_signing_alg_values_supported": [ "RS256" "subject_types_supported": [ "public" "code_challenge_methods_supported": [ "plain", "S256" "request_parameter_supported": true }2.4.2、请求【token_endpoint】接口,获取 access_token在 ApiPost 的 Body 中配置参数信息(参见下图),使用 POST 方式访问如下地址:http://localhost:5018/connect/tokenhttps://localhost:5019/connect/token【connect/token】接口响应信息:{ "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdENTZGMDMzMUZGNUI5QkZCQ0ZDQjI1REY5RDgxRTQ0IiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NjcxOTMyNTMsImV4cCI6MTY2NzE5Njg1MywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAxOSIsImNsaWVudF9pZCI6InNhbXBsZV9jbGllbnQiLCJqdGkiOiJFRTRBNkEwOUFFMDU2RUYxRTg5NjQzQUQ4RDc4MzkzMSIsImlhdCI6MTY2NzE5MzI1Mywic2NvcGUiOlsic2FtcGxlX2FwaSJdfQ.LKxAAFNjDj3YWXubprAdekWuSEw2ymPAv0YE5u9pEK2zn9ycuYKeqSN29yfaZc0oj4G_QoGKTmlAAdQ5uy72scT4hJYrFXtEjw880GY49rDVd579pFpv7jVWW_324LbNgrAhhJr7l37X3G8wC2sW9ZEvhZM89m6I9DgIUVMUboFUEADcTD5h4twQZ1RjQtcl0DLohcz6c3LPfERqs1QFF8A1bgu_Wszt4ZJADmPLE7wInxPcHpcnEkHl-3xKr1sP0MxhHqQkMpH8SYYnpmUiIwiqRm0qWAfqxEfQmOeVaSqqyGP25vRx2mgGez9DOm7jyOJdiswsGUA2WOQAys8EjA", "expires_in": 3600, "token_type": "Bearer", "scope": "sample_api" }3、创建【Sample.WebApi】项目3.1 使用 cli 创建项目(和上面的项目保存在同一个跟目录)dotnet new webapi -n Sample.WebApi3.2 使用 vs 2022 打开项目,添加 nuget 依赖包:项目【Sample.WebApi.csproj】配置文件如下:<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.10" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> </ItemGroup> </Project>3.3 项目改造为 Program.cs & Startup.cs 模式项目同样改造为 Program.cs & Startup.cs 模式,详细操作请看下面。3.3.1 添加 Startup.cs 文件,代码如下:新增【Startup.cs】文件,添加如下代码:namespace Sample.WebApi; public class Startup public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) Configuration = configuration; // Add services to the container. 注册服务到 Ioc 容器 public void RegisterServices(IServiceCollection services, IHostBuilder host) services.AddControllers(); // 1.添加 IDS4 认证服务 services.AddAuthentication(defaultScheme: "Bearer") .AddJwtBearer(authenticationScheme: "Bearer", configureOptions: options => options.Authority = "https://localhost:5019"; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters ValidateAudience = false //不验证 // 2.添加 IDS4 授权策略 services.AddAuthorization(options => options.AddPolicy("ApiScope", builder => builder.RequireAuthenticatedUser(); builder.RequireClaim("scope","sample_api"); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle services.AddEndpointsApiExplorer(); // 配置 Swagger 中间件 services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo Title = "Sample.WebApi", Version = "v1" // Configure the HTTP request pipeline. 配置 HTTP 请求管道(中间件管道即中间件委托链) public void SetupMiddlewares(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) //app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample.WebApi"); //app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); // 认证 app.UseAuthorization(); // 授权 app.UseEndpoints(endopints => endopints.MapControllers(); }注意:务必确保下面两点的配置信息和 IdentityServer 项目的 Config 配置保持一致。添加 IDS4 认证服务添加 IDS4 授权策略3.3.2 修改 Program.cs 文件namespace Sample.WebApi; public class Program public static void Main(string[] args) var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); // Add services to the container. startup.RegisterServices(builder.Services, builder.Host); var app = builder.Build(); // Configure the HTTP request pipeline. startup.SetupMiddlewares(app, builder.Environment); app.Run(); }3.3.3 新增 IdentityController.cs 文件(webapi)此处为了和项目默认的 WeatherForecastController(webapi) 参照对比,因此新增 IdentityController.cs 文件,添加如下代码:using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Sample.WebApi.Controllers; /// <summary> /// identity /// </summary> //[Route(template:"Identity")] [Route("[controller]")] [Authorize("ApiScope")] [ApiController] public class IdentityController : ControllerBase [Authorize] [HttpGet] public IActionResult Get() return new JsonResult(from claim in User.Claims select new { claim.Type, claim.Issuer, claim.ValueType }); }WeatherForecastController.cs 默认代码如下:using Microsoft.AspNetCore.Mvc; namespace Sample.WebApi.Controllers; /// <summary> /// WeatherForecast /// </summary> [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase private static readonly string[] Summaries = new[] "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) _logger = logger; [HttpGet(Name = "GetWeatherForecast")] public IEnumerable<WeatherForecast> Get() return Enumerable.Range(1, 5).Select(index => new WeatherForecast Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] .ToArray(); }3.4 启动【Sample.WebApi】项目运行测试PS E:\CodeStuday\dotnet6\Sample.WebApi> dotnet run 正在生成... info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5006 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: E:\CodeStuday\dotnet6\Sample.WebApi\访问 Swagger 页面:http://localhost:5006/swagger/index.html分别访问上面两个 api 接口,响应信息如下:Identitycurl -X 'GET' \ 'http://localhost:5006/Identity' \ -H 'accept: */*'接口显示 Unauthorized 未授权。WeatherForecastcurl -X 'GET' \ 'http://localhost:5006/WeatherForecast' \ -H 'accept: text/plain'由于该接口未设置特性权限,所以可正常访问。通过上面两个 api 接口的对比测试,说明代码中的授权中间件生效了。配置 ApiPost6 工具访问 Identity 接口在 ApiPost6 工具的【认证】栏添加授权访问的 access_token,使用 GET 方式访问 Identity 接口:上面步骤就演示了基于内存模式使用 IdentityServer4 的案例,该案例是 WebAPI 访问 WebAPI 的项目场景,接下来我们继续创建一个控制台项目,模拟 Console 项目访问 WebAPI 的场景。4、创建【Sample.ConsoleApp】项目4.1 创建【Sample.ConsoleApp】项目使用 dotnet cli 创建项目,执行命令: dotnet new console -n Sample.ConsoleApp4.2 【Sample.ConsoleApp】项目添加 nuget 包项目新建好后,添加 nuget 包依赖:4.3 改造【Sample.ConsoleApp】项目然后修改 Program.cs 文件,在 Main 方法中添加如下代码:using IdentityModel.Client; namespace Sample.ConsoleApp; internal class Program static async Task Main(string[] args) //阶段一:访问 IDS4 获取身份认证信息 var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5019"); if (disco.IsError) Console.WriteLine(disco.Error); return; var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "sample_client", ClientSecret = "sample_client_secret" if (tokenResponse.IsError) Console.WriteLine(tokenResponse.Error); return; Console.WriteLine(tokenResponse.Json); //阶段二:访问受保护权限的 api 接口 var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken); var response = await apiClient.GetAsync("http://localhost:5006/Identity"); if (!response.IsSuccessStatusCode) Console.WriteLine(response.StatusCode); return; var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(content); Console.ReadKey(); }上述代码,为了模拟两阶段的请求(先认证,再授权),此处直接 new 了两个 HttpClient 对象,生产环境可以使用构造函数 DI 方式注入该对象,或者使用扩展 nuget 包:Refit.HttpClientFactory4.4 启动【Sample.ConsoleApp】项目运行测试关于该 nuget 包的使用,请自行查看相关资料,这里不再叙述,接下来我们启动项目运行测试,输出如下信息:{ "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdENTZGMDMzMUZGNUI5QkZCQ0ZDQjI1REY5RDgxRTQ0IiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NjcyMDAwMDQsImV4cCI6MTY2NzIwMzYwNCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAxOSIsImNsaWVudF9pZCI6InNhbXBsZV9jbGllbnQiLCJqdGkiOiJFNDk5QjAwRTAyQTc5NkFBRjhCMTIxNkIwODhCRjY4NyIsImlhdCI6MTY2NzIwMDAwNCwic2NvcGUiOlsic2FtcGxlX2FwaSJdfQ.sZ3XKDIjrhiVu-1Twt6BUZMiqQ99coFb_kkThZECO6N68TOpch1_4h0rHggyyu4j-jDHh4itkv5iuCE4nGYiGVF196bh0fd68AqVl6A7IlMN3WfmwnLBgEMSIMNBJMWToEPb8l7y3jB9Uv6QoPbKzFw1k4ghePiFq0Qn7s3qXAefnSfYreUdHaIewJXk5egSOsePQpU_Rm33CvfCT3pKecJ1-mbwpjqn_euCuYmE4sWm8aGwi9DGnZt1W9jUZCoVW8_MoONW1pKnmcN5s-07JBwjFDhdY-EyPwY3b-U7B7jfvTxp7krr4XYOWKIvUZdN8afPDbr-lNbx7dC0mSdWPg", "expires_in": 3600, "token_type": "Bearer", "scope": "sample_api" "type": "nbf", "issuer": "https://localhost:5019", "valueType": "http://www.w3.org/2001/XMLSchema#integer" "type": "exp", "issuer": "https://localhost:5019", "valueType": "http://www.w3.org/2001/XMLSchema#integer" "type": "iss", "issuer": "https://localhost:5019", "valueType": "http://www.w3.org/2001/XMLSchema#string" "type": "client_id", "issuer": "https://localhost:5019", "valueType": "http://www.w3.org/2001/XMLSchema#string" "type": "jti", "issuer": "https://localhost:5019", "valueType": "http://www.w3.org/2001/XMLSchema#string" "type": "iat", "issuer": "https://localhost:5019", "valueType": "http://www.w3.org/2001/XMLSchema#integer" "type": "scope", "issuer": "https://localhost:5019", "valueType": "http://www.w3.org/2001/XMLSchema#string" }]从上面输出的信息中,我们来分下 access_token 是个啥东东,其实它是一个 jwt 格式的数据,我们使用在线格式化工具来更佳直观的查看数据,如下图所示:https://jwt.ms/格式化解析数据:JWT 数据格式我们简单的回顾下 JWT 的数据格式,由以下三部分组成。JWT 的组成结构注意:推荐 jwt 使用 https 协议,不应使用 http ,生产环境安全性考虑,防止信息泄漏。Header,头部包含元信息,对明文信息使用 base64 编码值;Payload,负载、载荷配置一些客户端相关信息使用 base64 编码值;Signature,签名部分是使用 Header 部分的加密算法对(Header + Payload )的编码值数据再次加密;JWT 参数解释1、头部(Header)=> alg:签名(加密)算法;=> kid:令牌ID;=> typ:令牌类型;2、载荷(Payload)=> nbf:jwt 生效(签发)的时间,再此之前 jwt 不可用;=> exp:jwt 过期时间(大于 nbf 签发时间),在此之后 jwt 失效;=> iss:jwt 签发人,通常情况为签发服务器地址;=> client_id:客户端地址;=> jti:jwt 的唯一标识,作用辨识每一次的 jwt 不会重复;=> iat:jwt 的签发时间,一般情况和 nbf 相同;=> scope:api 范围(理解成分组),在该范围内合法有效;总结通过上面的项目示例,可以看出 IdentityServer4 框架的核心部分依然是 JWT,通过该框架整合了 认证和授权 两阶段的相关操作,给用户的感觉就是一步到位,无感体验。对应常规性需求的项目,使用 内存模式 亦可满足,只需在 IdentityServerConfig.cs 类中注册相应的配置信息即可,但对应动态改变的配置信息,还可以持久化配置 DB模式,关于该模式的改造步骤,感兴趣的小伙伴自行参看相关文档进行研究,本篇文章的目的是快速入门 IdentityServer4 框架,以帮助更多的小伙伴快速上手体验。
项目背景说明最近接手一个 “老” 项目的需求修改,项目整体基于 .net core 3.1 平台,以传统的三层架构为基础构建。了解需求后,逐步对原有项目框架进行大概的了解,主要是熟悉一些框架的开发规范,基本工具类库的使用,然后尝试修改业务需求以达到客户方的要求。刚开始了解业务需求,先大概的了解了一些直观可视化的界面操作和基本菜单目录结构,此时还没来得及看项目框架代码,需求大概清楚后,开始对后端项目框架查看,随之逐步使用。这不使用不知道,一使用吓一跳,针对目前遇到的几个问题,先在这里列举部分:通常情况下 BLL 层依赖引用 DAL 层,该项目中刚好与之相反;单表的 CRUD 操作基本被封装到基类中(BaseController、BaseBll 和 BaseDal),对多条件查询的提供方法不太灵活(不好用),从基类可以看出,各层职责混淆,并且封装的操作 DB 的基本方法返回值都为 void 。比如在修改一个相对简单的需求,在业务整合的时候,使用框架封装提供的方法反而更麻烦;在 BLL 层的具体方法中,看到一个功能大概是批量列表操作的方法,方法内竟然多次循环操作 DB(此处不多说,自己体会)。听同事说,这个 项目刚上线就存在内存泄漏 的情况(感到惊讶!)。还有同事在做需求业务修改时,发现 (单体项目)同库环境多表之间的关联操作没有走数据库事务处理,而是直接采用应用程序服务依赖处理(我还没接触到这类需求,暂时不太清楚怎么设计的)。这个项目唯一的好处就是 模块名称映射,业务模块的命名在前端、服务端再到数据库名称和实体表之间都保持较好的映射规则,这是欣慰的地方。 目前所了解或者听闻的就大概这些,基于以上的这些问题,在不破坏现有的框架编码体系的前提下,又要满足自己修改业务需求时编写代码的顺手体验(专业描述叫 渐进式修改,说难听一点叫偷梁换柱),仔细看了下 DAL(数据访问层)的基本代码,幸好预留了一个 IDbContext 对象,于是基于该对象提供的信息(解析该对象)并引入顺手的 orm 操作(符合框架的扩展),决定不使用框架内置的封装方法。产生了该想法,说干就干,本文将从以下几个方面来谈谈项目改造和相关的框架说明:回顾经典的三层架构(单体项目中常用);第三方 DI 框架 Autofac 的使用(项目中看到应用,顺便了解学习);解析 DAL 层预留的 IDbContext 对象(在本项目中是 EF 提供的上下文对象),引入更轻量级的 ORM(此处使用 FreeSql);最后基于三层架构,简单快速的搭建一个单体项目的框架雏形(实操);说明:在原有的项目中,目的是跳出框架封装的基本 CRUD 操作方法(不太好用),主要改造点是解析 DAL 层的 IDbContext 对象并引入轻量化的 FreeSql 操作。三层架构的理解因为项目业务环境,基本上都是单体三层架构为主,三层架构想必大家都不陌生,在回顾之前我们先来了解生活中一个常见且形象的应用场景,以商场购买猪肉为例,先看下面的图:通过上图,我们可以很清晰直观的了解到,猪从猪圈里面逐步变成商品被流通市场 的大概过程,并且每一个过程中 职责分工比较明确,那么对应的在我们计算机程序设计中,也是可以依据此来抽象划分各个模块的,如下图所示:从上图中,我们按照【猪 => 工厂 => 商品】的模式抽象,形成了 职责明确的各个模块层,由于项目初期起步规模不大,各个模块组合的单体项目可以部署同一个服务器环境,也可以安装上图分离环境部署。说明:此处不讨论架构设计,只是针对目前接手业务需求的项目回顾一下基本的三层架构,具体的服务资源部署可以依据公司的业务规模,考虑经济成本且满足当下使用需求即可。个人见解,架构是业务驱动的,而不是被过渡的设计。上面的架构图中,分别标注了三种角色类型:客户端(Client),用于向【服务端应用程序】发起请求;应用服务层(App Server),用于接收 Client 的请求,经过一系列的数据处理,响应相应的反馈结果;数据服务层(Data Server),主要用于存储应用系统的基础数据和相关业务处理的数据。在该层通常为了减缓 DB 的 I/O 直接交互,通常会引入一个缓存组件(单体环境通常内存缓存,或者分布式部署环境的 Redis)提升应用系统的性能。这里我们重点说下应用服务层(App Server),分别包含以下几个职责模块:UI 层,接收 Client 的请求,承担展示页面直观的视觉效果和数据校验等相关工作。通常包括:winfrom/wpf/.aspx/.cshtml/.html 等。在前后端分离的项目中,相对前端应用程序来说,后端提供的 webapi/controller 层即代表该层。BLL 层,接收 Client 的数据后,通常情况下使用 IBLL 定义接口规范,然后在 BLL 层实现相应的业务逻辑处理方法(依赖 IDAL 层提供的数据),比如方法或者服务的整合等。DAL 层,提供对 DB 数据库的访问操作(数据源相关环节交涉),通常情况也在 IDAL 层定义接口规范,然后在 DAL 层实现对应的数据访问操作方法(比如单表的 CRUD 操作)。通常该层会借助一些 ORM 辅助类库,比如:ADO.NET、DbHelper/SqlHelper、FreeSql 、EF/EF Core、Dapper 等。Model 层(数据模型的载体),为了更佳细化的分类规制,此处暂时考虑分三类模型,分别是 BaseEntity、BusinessEntity 和 ViewEntity。Common 层(通用类库和工具助手),该层有自定义封装整合的,也有依赖外部 Nuget 包封装处理的,依据项目业务情况按需获取组装。基于上面的架构图,一个单体环境下基本的框架雏形就可以搭建了,但具体落地项目还需考虑以下几点细节和原则(包括但不限于):系统开发的 命名规范,建议各个业务模块,在数据库表设计、前后端应用程序里面一一映射,这样可以很直观、方便的上手;请求入口处 参数合法性的基础校验(必备常识),无论前端部分还是服务端部分,参数的校验还是很有必要性的。无效的参数,可能会导致程序的异常;统一的入参格式,比如请求参数 JSON 格式化,遵循 HTTP/RESTful API 风格;统一的数据响应载体,对比原生的数据格式返回,很多情况下的 null 结果无法确定接口在业务意义的成功或失败。统一的异常处理机制和数据格式,通常采用 AOP 思想全局异常捕获(ExceptionFilterAttribute 异常过滤器),数据信息推荐 JSON 格式化记录;系统日志的记录(数据建议 JSON 格式化),通常情况下会在框架层面采用 AOP 方式获取用户在系统中操作的全生命周期数据记录,也可提供日志写入方法,在关键业务逻辑处精细化的记录逻辑操作信息。从一定方面可以起到还原 “真相” 的保障;整体遵循 单一职责原则(SRP:Singleresponsibilityprinciple),该点也是最难做到的理想化指导原则,在编写业务方法的时候,一个方法尽量做到功能单一,比如复杂的业务处理,可以使用每个相关的单一方法整合;依赖抽象,不应依赖具体实现,这也是 开-闭原则(OCP:Open - Close Principle)的体现。比如:Controller => IBLL 接口规范 / BLL 具体实现 => IDAL 接口规范 / DAL 具体实现。还有在框架中 无处不在的 DI 应用;说明:此处只是列举部分比较常见或者基本必备的点,框架设计还有很多细节考虑,这里不再详细论述。任何框架无论封装的如何优秀,关键还是在于局中 “玩游戏” 的开发者,框架只是提供了基本的开发规则,要大家共同的遵循,这其中最难的就是团队小伙伴达成一致的思维认知和共识。约定优于配置,任何框架不可能面面俱到,过渡设计框架务必会失去部分灵活性(物极必反的道理想必大家都知道),个人建议框架架构设计应该以 业务为驱动、技术为主导、约定和思想共识为辅助、开发规范为底线 这几个方面加强。Autofac 基本概述Autofac 官方地址 => https://autofac.org/了解到项目的基本情况后,由于项目是 .net core 3.1 平台构建的,与老平台的 .netfx 相比,变化最大有以下几点(这里唠嗑一下):框架平台的福利:开源、跨平台、高性能(看怎么使用,比如批量列表操作直接多次循环 DB 操作,这样的玩法神仙框架也无解);无处不在的 DependencyInjection(DI,依赖注入),最直观的使用体验就是解放了了传统的 new实例化对象;灵活的 Middleware(中间件)和透明的 HTTP Pipeline (http 管道)机制;这里只说下 DI 依赖注入,在基本简单的注入方面 asp.net core 框架中默认提供的 DI 很方便,但在有些场景下就没有第三方的 DI 更佳灵活方便了。默认 DI 框架Microsoft.Extensions.DependencyInjection.Abstractions(容器的抽象)Microsoft.Extensions.DependencyInjection(容器的具体实现)当你要在自己的程序中使用时,使用nuget包管理工具安装 Microsoft.Extensions.DependencyInjection 包即可。老牌第三方 DI 框架 AutofacAutofac.Extensions.DependencyInjection当然这里还有其他第三方 DI 框架,这里不再一一列举,感兴趣的小伙伴自行查看相关资料。Autofac 基本知识点这里先简单的回顾下 Autofac 的基本知识点 :Autofac 框架 DI 生命周期;Autofac 框架 DI 注入方式;Autofac 在 asp.net core 框架中的应用(简单提下,预留在下面的项目改造中描述)。Autofac 框架 DI 的生命周期InstancePerDependency(瞬时的),与 Microsoft DI 容器中的 Transient 类似,每次调用都会生成新的实例,这种模式也是 Autofac 的默认模式;SingleInstance(单例),和 Microsoft DI 容器中的 Singleton 类似,所有的调用均返回同一个实例;InstancePerLifetimeScope(作用域),与 Microsoft DI 容器中的 Scoped 类似,它在一个作用域中获取的实例相同,在 asp.net core 中常用(大多数情况下推荐使用);InstancePerMatchingLifetimeScope (匹配作用域),与 InstancePerLifetimeScope 类似,但是它支持对实例共享进行更精细的控制;InstancePerRequest (每次请求一个实例),在老旧的 asp.net webform 和 asp.net mvc 中使用,此处不再介绍;InstancePerOwned (Owned 隐式关系类型创建了一个新的嵌套生命周期Scope);ThreadScope (线程作用域),代表每个线程的一个实例,对于多线程场景,必须非常小心,不要在派生线程下释放父作用域。如果您生成了线程,然后释放了父作用域,那么可能会陷入一个糟糕的情况,即无法解析组件。// 创建容器对象实例 var builder = new ContainerBuilder(); // 1、InstancePerDependency(瞬时的) // 注册 Worker 类为 InstancePerDependency(瞬时的),每次获取都会生成新的实例 builder.RegisterType<Worker>().InstancePerDependency(); // 如果不指定与 InstancePerDependency(默认的模式)相同 builder.RegisterType<Worker>(); // 2、SingleInstance(单例) // 注册 Worker 类为 SingleInstance(单例),每次获取均返回同一个实例 builder.RegisterType<Worker>().SingleInstance(); // 3、InstancePerLifetimeScope(作用域) // 注册 Worker 类为 InstancePerLifetimeScope(作用域), // 在同一个作用域中获得的是相同实例,在不同作用域获得的是不同实例 builder.RegisterType<Worker>().InstancePerLifetimeScope(); // InstancePerLifetimeScope(作用域)实例对象验证 using(var scope1 = container.BeginLifetimeScope()) for(var i = 0; i < 100; i++) // 在 scope1 中获取的 Worker 都是同一个实例 var w1 = scope1.Resolve<Worker>(); using(var scope2 = container.BeginLifetimeScope()) for(var i = 0; i < 100; i++) // 在 scope2 中获取的 Worker 都是同一个实例 // 在 scope2 中获取的 Worker 实例和 scope1 中获取的 Worker 实例不相同,因为他们是两个不同的作用域 var w2 = scope2.Resolve<Worker>(); using(var scope3 = container.BeginLifetimeScope()) var w3 = scope3.Resolve<Worker>(); using(var scope4 = scope3.BeginLifetimeScope()) // w3 和 w4 是不同的实例,因为他们是在不同的作用域中请求的 var w4 = scope4.Resolve<Worker>(); var w5 = container.Resolve<Worker>(); using(var scope5 = container.BeginLifetimeScope()) // w5 和 w6 不同 // Scope 是一个生命周期范围,如果从 Scope 中解析一个 InstancePerLifetimeScope 服务, // 该实例将在 Scope 的持续时间内存在,并且实际上是一个单例 // 它将在容器的生命周期内被保存,以防止其他对象试图从容器解析 Worker // 解释:注册为 InstancePerLifetimeScope 的服务,在每个 Scope 中请求类似于请求单例, // 在这个单例的生命周期于 Scope 的生命周期相同,在 Scope 中请求对应实例则返回对应的单例, // 这样就避免冲突,每个Scope请求的都是自己的实例 var w6 = scope5.Resolve<Worker>(); // 4、InstancePerMatchingLifetimeScope (匹配作用域) // 当您创建一个嵌套(多层级)的生命周期 Scope 时,您可以 “标记” 或 “命名” 该 Scope。 // 每个匹配标记的生命周期 Scope 作用域内最多只有一个与给定名称匹配的服务实例,包括嵌套的生命周期Scope。 // 这允许您创建一种“作用域单例”,在这种单例中,其他嵌套的生命周期作用域可以共享组件的实例,而无需声明全局共享实例。 builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("my-request"); // 创建标记的作用域Scope using(var scope1 = container.BeginLifetimeScope("my-request")) for(var i = 0; i < 100; i++) var w1 = scope1.Resolve<Worker>(); using(var scope2 = scope1.BeginLifetimeScope()) var w2 = scope2.Resolve<Worker>(); // w1 和 w2 的实例总是相同的,因为使用 InstancePerMatchingLifetimeScope 且为指定标记的Scope, // 嵌套的生命周期作用域可以共享实例,它实际上在标记作用域中是一个单例 // 创建另一个标记的作用域 Scope using(var scope3 = container.BeginLifetimeScope("my-request")) for(var i = 0; i < 100; i++) // w3和w1/w2是不同的实例,因为他们是两个不同的生命周期,虽然它们的标记相同 // InstancePerMatchingLifetimeScope 依然是在不同的生命周期作用域创建新的实例 var w3 = scope3.Resolve<Worker>(); using(var scope4 = scope3.BeginLifetimeScope()) var w4 = scope4.Resolve<Worker>(); // w3和w4是相同的实例,因为使用 InstancePerMatchingLifetimeScope 且为指定标记的 Scope // 嵌套的生命周期作用域可以共享实例 // w3和w4是同样的实例,w1和w2是同样的实例,但是w1/w2和w3/w4是不同的实例,因为他们是两个不同的作用域 Scope // 注意:不能在标记不匹配的生命周期 Scope 中获取 InstancePerMatchingLifetimeScope 中标记的实例会抛出异常。 using(var noTagScope = container.BeginLifetimeScope()) // 在没有正确命名(标记)的生命周期Scope的情况下试图解析每个匹配生命周期Scope的组件,会抛出异常 var fail = noTagScope.Resolve<Worker>(); // 5、InstancePerOwned // Owned隐式关系类型创建了一个新的嵌套生命周期Scope。 // 可以使用每个拥有的实例注册将依赖关系限定到拥有的实例,简单讲就是将依赖注入限定到对应泛型实例 builder.RegisterType<MessageHandler>(); builder.RegisterType<ServiceForHandler>().InstancePerOwned<MessageHandler>(); using(var scope = container.BeginLifetimeScope()) // MessageHandler 依赖 ServiceForHandler,它们的生命周期处于scope(当前Scope的名称)下的子生命周期范围内 var h1 = scope.Resolve<Owned<MessageHandler>>(); // 但是 InstancePerOwned 的实例需要自己处理,所以这里需要手动释放 h1.Dispose(); // 6、ThreadScope (线程作用域) builder.RegisterType<MyThreadScopedComponent>().InstancePerLifetimeScope(); var container = builder.Build(); void ThreadStart() using (var scope = container.BeginLifetimeScope()) // 从容器的一个子作用域解析服务 var thisThreadsInstance = scope.Resolve<MyThreadScopedComponent>(); // 还可以创建嵌套的作用域 using(var unitOfWorkScope = scope.BeginLifetimeScope()) var anotherService = unitOfWorkScope.Resolve(); }Autofac 框架 DI 注入方式RegisterType,类型注入RegisterInstance,实例注入Lambda 表达式注入Property 属性注入RegisterGeneric,泛型注入多种类型注入条件注入(Autofac 4.4+ 引入)var builder = new ContainerBuilder(); // 1、RegisterType 类型注入 // 可以通过泛型的方式直接注册对应的类型 builder.RegisterType<ConsoleLogger>(); // 也可以通过 typeof 运算符得到对应的类型作为参数提供给 RegisterType 方法,这种方式在注册泛型类型时非常有用 builder.RegisterType(typeof(ConfigReader)); // 通过 UsingConstructor 指定【构造函数】中传递的类型,以确定使用与之对应参数的构造函数实例化对象 builder.RegisterType<MyComponent>() .UsingConstructor(typeof(ILogger), typeof(IConfigReader)); // 2、实例注入 // 预先得到一个实例 var output = new StringWriter(); // 通过 RegisterInstance 将实例注入到容器中,在通过容器获取 TextWriter 的实例时就会获得到 output 这个实例 builder.RegisterInstance(output).As<TextWriter>(); // Autofac会自己管理实例的生命周期,如果注册为瞬时的,那么这个实例在获取一次后就会被调用其对应的Dispose方法, // 如果希望自己控制对象的生命周期,在注入时需要跟上 ExternallyOwned() 方法 builder.RegisterInstance(output) .As<TextWriter>() .ExternallyOwned(); // 使用 ExternallyOwned 方法告知 Autofac 这个被注入的实例对象的生命周期由自己掌控,不需要自动调用 Dispose 方法 // 将一个单例实例注入到容器中,其他被注入的对象就可以直接获取到这个单例,Autofac也不会释放这个单例 builder.RegisterInstance(MySingleton.Instance).ExternallyOwned(); // 3、Lambda 表达式注入 // 反射是一种很好的创建依赖注入的方式,但是有时候需要注入的对象并不是使用简单的无参构造函数实例化一个对象, // 它还需要一些其他的参数或者动作来得到一个对应的实例,这时候可以使用Lambda表达式注入。 // 在容器中注入 A,但是 A 不是使用无参构造函数获得实例的,它使用从 Autofac 中取出的一个 B 对象实例作为参数,调用需要一个 B 对象实例的构造函数 builder.Register(c => new A(c.Resolve<B>())); // 这里的c是一个IComponentContext对象,通过 IcomponentContext 对象可以从Autofac容器中解析出相应的对象,然后作为实参提供给A对象的构造函数 // Lambda表达式对复杂参数的注入非常有用 // 有时候构造函数并不是简单的一个固定参数,而可能是一个变化的情况,如果没有这种方式,可能就需要复杂的配置文件才能完成对应的功能 builder.Register(c => new UserSession(DateTime.Now.AddMinutes(30))); // 4、Property 属性注入 // 相比上面直接在构造时给属性赋值,Autofac 有更优雅的属性注入。 // 给 A 实例的 MyB 属性赋一个从容器中解析出来的 B 实例 builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() }); // 属性注入在以下情况特别有用 // 通过参数值选择实现能够提供一种运行时选择,它不仅仅是最开始时的参数决定的,这个参数在运行时也是可以改变以返回不同的实现 builder.Register<CreditCard>( (c, p) => { var accountId = p.Named<string>("accountId"); if (accountId.StartsWith("9")) return new GoldCard(accountId); return new StandardCard(accountId); // 此处推荐使用工厂模式,通过传入工厂委托的方式获取不同的实例。 // 5、RegisterGeneric 泛型注入 // 通常情况 IoC 容器都支持泛型注入,Autofac 支持以特别的语法强调特别的泛型,它的优先级比默认的泛型高,但是性能没有默认的泛型好,因为不能缓存。 // IoC 容器获取 IRepository<T> 类型的实例时容器会返回一个 NHibernateRepository<T> 实例 builder.RegisterGeneric(typeof(NHibernateRepository<>)) .As(typeof(IRepository<>)) .InstancePerLifetimeScope(); // 6、多种类型注入 // 有时候一个实例对应很多接口,通过不同的接口请求到的实例都是同一个实例,那么可以指定实例对应的类型 // 注意:要注册多个接口,那么具体类必须实现继承的这些接口。 // 通过 ILogger 和 ICallInterceptor 得到的都是 CallLogger 实例 builder.RegisterType<CallLogger>() .As<ILogger>() .As<ICallInterceptor>(); // 多种类型注入,涉及到多个服务注入的选择 // 如果一个类型进行了多个实例的注册,Autofac 默认以最后一次注入的为准 // 请求ILogger实例容器返回 ConsoleLogger 实例 builder.RegisterType<ConsoleLogger>().As<ILogger>(); // 请求 ILogger 实例容器返回 FileLogger 实例 builder.RegisterType<FileLogger>().As<ILogger>(); // 最终请求ILogger实例容器返回的是FileLogger实例,Autofac 默认以最后的为准 // 对于上面的情况,如果需要手动指定默认的,而不是使用最后一个,可以使用 PreserveExistingDefaults() 修饰 builder.RegisterType<ConsoleLogger>().As<ILogger>().PreserveExistingDefaults(); builder.RegisterType<FileLogger>().As<ILogger>(); // 此时该注入就无效了 // 最终请求 ILogger 实例容器返回的是 ConsoleLogger 实例,因为使用了PreserveExistingDefaults()修饰 // 7、条件注入 // 条件注入在 Autofac4.4 引入,使用 4.4 以后的版本可以使用 // 大多数情况下,如果一个类型注入了多个实现,使用PreserveExistingDefaults()手动指定就够了,但是有时候这还不够,这时候就可以使用条件注入 // 请求 IService 接口得到 ServiceA builder.RegisterType<ServiceA>() .As<IService>(); // 请求 IService 接口得到 ServiceB builder.RegisterType<ServiceB>() .As<IService>() // 仅当 IService 没有注册过才会注册 .IfNotRegistered(typeof(IService)); // 最后请求 IService 获得的实例是 ServiceA builder.RegisterType<HandlerA>() .AsSelf() .As<IHandler>() // 注册 HandlerA 在 HandlerB 之前,所以检查会认为没有注册 // 最后这条注册语句会成功执行 .IfNotRegistered(typeof(HandlerB)); builder.RegisterType<HandlerB>() // 注册自己的类型,即 HandlerB .AsSelf() .As<IHandler>(); builder.RegisterType<HandlerC>() // 注册自己的类型,即 HandlerC .AsSelf() .As<IHandler>() // 不会执行,因为 HandlerB 已经注册了 .IfNotRegistered(typeof(HandlerB)); // 注册 IManager builder.RegisterType<Manager>() .As<IManager>() // 仅当IService和HandlerB都注册了对应服务时才会执行 .OnlyIf(reg => reg.IsRegistered(new TypedService(typeof(IService))) && reg.IsRegistered(new TypedService(typeof(HandlerB))));项目改造说明:此处项目改造并非是在原来项目中改造,这里只是基于三层架构的理解,快速搭建一个基本的基础框架。这里尽量描述部分细节改造,注重思路循序渐进的理解。此处项目环境基于 .net6 平台环境,项目准备工作或改造步骤:三层架构基础框架搭建(展示项目结构);CommonHelper 层单例对象构造器创建;DbHelper 层使用单例构造器封装 IFreeSql 对象;WebAPI 层【Startup.cs + Program.cs】改造;WebAPI 层 Swagger 配置使用;WebAPI 层 Autofac 在 asp.net core 中的应用;WebAPI 层请求入参的校验(FluentValidation);结构化日志记录 Serilog;AutoMapper 的使用(DTO 数据模型转换);在实际项目应用中,使用到的 nuget 依赖包可能不止这些(还有其他未列举的),依据项目实际使用情况引入,合理封装到对应项目层中使用即可。说明:其中第 5、7、8、9 点此处不做详述,这些类库的使用自行查阅相关资料。FluentValidation 是一个非常流行的构建强类型验证规则的 .NET 库,这里不做讲述,可以参考该文章:https://www.cnblogs.com/lwqlun/p/10311945.htmlSerilog 是 .NET 中著名的结构化日志类库。Swagger 在 asp.net core 中默认集成 Swashbuckle.AspNetCore。项目框架目录结构依据架构图规划,搭建项目框架和依赖关系目录结构如下:CommonHelper 层单例对象构造器,SingletonConstructornamespace Jeff.Mes.Common; /// <summary> /// 单例对象构造器 /// </summary> /// <typeparam name="T"></typeparam> public class SingletonConstructor<T> where T : class, new() private static T? _Instance; private readonly static object _lockObj = new(); /// <summary> /// 获取单例对象的实例 /// </summary> /// <returns></returns> public static T GetInstance() if (_Instance != null) return _Instance; lock (_lockObj) if (_Instance == null) var item = System.Activator.CreateInstance<T>(); System.Threading.Interlocked.Exchange(ref _Instance, item); return _Instance; }DbHelper 层说明:该层可以使用工厂模式,封装支持更多的 DB 类型,此处主要是描述下 FreeSql 对象的单例模式构建。这里推荐下 FreeSql 轻量级 ORM,支持多种关系型 DB,切换数据源方便,基本保持一致的使用体验,遵循 MIT 协议开源,上手简单方便,性能也不差,测试用例覆盖较全面。该层可以添加 dbconfig.json 配置文件,考虑相对安全,该文件配置的字符串连接信息,可以使用密文编码(比如:base64编码,或者采用其他加密方式生产的密文)。dbconfig.json 配置文件内容,此处依据自己的喜好定义,注意对敏感信息适当的安全考虑。{ "ConnectionStrings": { "Development": { "DbType": "SqlServer", "ConnectionString": "" "Production": { "DbType": "SqlServer", "ConnectionString": "" }使用 SingletonConstructor 构建 FreeSqlHelperusing System.Reflection; using System.Text; using System.Text.RegularExpressions; using FreeSql; using Jeff.Mes.Common; namespace Jeff.Mes.DbHelper; /// <summary> /// 【Singleton 单例模式】构建 freesql 对象 /// </summary> public sealed class FreeSqlHelper : SingletonConstructor<FreeSqlHelper> //连接字符串作为 key,存储构建的 IFreeSql 对象的字典集合 private readonly static Dictionary<string, IFreeSql> _FreeDic = new(); /// <summary> /// 构建 freesql 对象 /// </summary> /// <param name="dbType">DB类型</param> /// <param name="connStr">连接字符串</param> /// <returns>IFreeSql</returns> public IFreeSql? FreeBuilder(string dbType, string connStr) if (string.IsNullOrWhiteSpace(dbType) || string.IsNullOrWhiteSpace(connStr)) return default; bool hasKey = _FreeDic.ContainsKey(connStr); if (hasKey) return _FreeDic[connStr]; IFreeSql fsql; string myDbType = dbType.Contains('.') ? dbType.Substring(dbType.LastIndexOf('.') + 1) : dbType; switch (myDbType) case "MySql": fsql = new FreeSqlBuilder() .UseConnectionString(DataType.MySql, connStr) .UseAutoSyncStructure(false) //自动同步实体结构到数据库 .Build(); //请务必定义成 Singleton 单例模式 break; default: fsql = new FreeSqlBuilder() .UseConnectionString(DataType.SqlServer, connStr) .UseAutoSyncStructure(false) //自动同步实体结构到数据库 .Build(); //请务必定义成 Singleton 单例模式 break; bool isAdd = _FreeDic.TryAdd(connStr, fsql); if (isAdd) return fsql; fsql.Dispose(); return _FreeDic[connStr]; public IFreeSql? FreeBuilder(DataType dbType, string connStr) if (string.IsNullOrWhiteSpace(connStr)) return default; bool hasKey = _FreeDic.ContainsKey(connStr); if (hasKey) return _FreeDic[connStr]; bool isOk = _FreeDic.TryGetValue(connStr, out IFreeSql? fsql); if (isOk) return fsql; fsql = new FreeSqlBuilder() .UseConnectionString(dbType, connStr) .UseAutoSyncStructure(false) //自动同步实体结构到数据库 .Build(); //请务必定义成 Singleton 单例模式 bool isAdd = _FreeDic.TryAdd(connStr, fsql); if (isAdd) return fsql; fsql.Dispose(); return _FreeDic[connStr]; public (bool isOk, IFreeSql? fsql) GetFreeSql(DataType dbType, string connStr) bool isOk = _FreeDic.TryGetValue(connStr, out IFreeSql? fsql); if (!isOk) fsql = FreeBuilder(dbType, connStr); isOk = fsql != null; return (isOk, fsql ?? default); /// <summary> /// 反射获取【IDbContext】对象信息 /// </summary> /// <typeparam name="T">IDbContext</typeparam> /// <param name="t">IDbContext</param> /// <returns>Dictionary(string,string)</returns> public static Dictionary<string, string> GetProperties<T>(T t) where T : class Type type = t.GetType(); var sb = new StringBuilder(); foreach (PropertyInfo property in type.GetProperties().OrderBy(p => p.Name)) object? obj = property.GetValue(t, null); if (obj == null) continue; if (string.IsNullOrWhiteSpace(obj.ToString())) continue; sb.Append(property.Name + "="); if (property.PropertyType.IsGenericType) var listVal = property.GetValue(t, null) as IEnumerable<object>; if (listVal == null) continue; foreach (var item in listVal) sb.Append(GetProperties(item)); else if (property.PropertyType.IsArray) var listVal = property.GetValue(t, null) as IEnumerable<object>; if (listVal == null) continue; foreach (var item in listVal) sb.Append(GetProperties(item)); sb.Append(property.GetValue(t, null)); sb.Append("&"); var dic = new Dictionary<string, string>(); var sbArray = sb.ToString().Trim('&').Split('&'); foreach (var item in sbArray) if (item.Contains('=')) int count = Regex.Matches(item, "=").Count; if (count <= 1) var itemArray = item.Split('='); dic.Add(itemArray[0], itemArray[1]); int index = item.IndexOf('='); string key = item.Substring(0, index); string val = item.Substring(index + 1); dic.Add(key, val); return dic; }在原始项目中解析 IDbContext 对象的方法就是 GetProperties() ,该方法借助【反射 + 递归】获取上下文对象,对于 FreeSql 对象实例而言,只需获取 db 的连接字符串信息即可快速使用,而 DB 字符串连接信息就存储于 IDbContext 对象中。原始项目 Dal 层中预留的代码: public class OrderDal : BaseDal<OrderInfo>, IOrderDal public OrderDal(IDbContext dbContext, ILoggerFactory loggerFactory) : base(dbContext, loggerFactory) }在项目改造之前,该 Dal 层中需要引入相应 DB 的 FreeSql 包,如下格式:FreeSql.Provider.Xxx(Xxx 是数据库类型名称)添加 nuget 依赖包后,原始项目 Dal 层改造后的代码如下:public class OrderDal : BaseDal<OrderInfo>, IOrderDal private readonly IDbContext _IDbContext; public OrderDal(IDbContext dbContext, ILoggerFactory loggerFactory) : base(dbContext, loggerFactory) _IDbContext = dbContext; public (IDbContext dbContext, IFreeSql fsql) GetDbContext() ((Xxx.Framework.Dal.EFDbContext)_IDbContext).ConnStr ((Microsoft.EntityFrameworkCore.DbContext)ss).Database.ProviderName //- Database {Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade} DatabaseFacade dbFacade = _IDbContext.GetProperty<Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade>("Database"); var dic = FreeSqlHelper.GetProperties(_IDbContext); dic.Add("DbType", dbFacade.ProviderName); IFreeSql fsql = FreeSqlHelper.GetInstance().FreeBuilder(dic["DbType"], dic["ConnStr"]); return (_IDbContext, fsql); }在 IOrderDal.cs 文件中添加如下代码:public interface IOrderDal : IBaseDal<OrderInfo>,IDependency /// <summary> /// 获取 DB 上下文对象,并构建 IFreeSql 实例 /// </summary> /// <returns></returns> public (IDbContext dbContext, IFreeSql fsql) GetDbContext(); }在 BLL 层中方法中的调用,代码如下:public class OrderBll : BaseBll<OrderInfo>, IOrderBll private readonly IOrderDal _orderDal; public OrderBll(IOrderDal orderDal) _orderDal = orderDal; //构造函数注入 IOrderDal public (bool state, List<OrderInfo> orderInfos) Test() var (dbContext, fsql) = _ppsOrderDal.GetDbContext(); // 此处获取到构建的 fsql 对象,即可使用。 } 有了 FreeSql 的引入,从此在 BLL层就可以方便的编写操作业务方法了,不再局限于框架内提供的方法。此处利于 Dal 层预留的 IDbContext 进行扩展,不修改上层项目的玩法,该怎么用还是怎么用。WebApi 层在 WebApi 中添加 Nuget 包:Autofac.Extensions.DependencyInjection v8.0.0Swashbuckle.AspNetCore v6.4.0接下来逐步改造以下两点:MiniAPI 改造【Startup.cs + Program.cs】模式;在 asp.net core 中使用 Autofac;首先新建 Startup.cs 文件,添加如下代码:using Autofac; using Autofac.Extensions.DependencyInjection; using Jeff.Mes.WebApi.Modules; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Jeff.Mes.WebApi; public class Startup public IConfiguration Configuration; public Startup(IConfiguration configuration) Configuration = configuration; // Add services to the container. 注册服务到 Ioc 容器 public void RegisterServices(IServiceCollection services, IHostBuilder host) #region 在 host 中注册 Autofac host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule<AutofacModule>(); //此处编写相关服务&属性的注入 services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); #endregion host.ConfigureAppConfiguration((hostContext, config) => { var env = hostContext.HostingEnvironment; string path = Path.Combine(env.ContentRootPath, "Configuration"); config.SetBasePath(path) .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile(path: $"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle services.AddEndpointsApiExplorer(); services.AddSwaggerGen(); // Configure the HTTP request pipeline. 配置 HTTP 请求管道(中间件管道即中间件委托链) public void SetupMiddlewares(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) app.UseSwagger(); app.UseSwaggerUI(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapGet("/env", async context => // 获取环境变量信息 await context.Response.WriteAsync( $"EnvironmentName:{env.EnvironmentName},IsDevelopment:{env.IsDevelopment()}" 其次修改 Program.cs 文件中的代码,修改如下:namespace Jeff.Mes.WebApi; public class Program public static async Task Main(string[] args) var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); var services = builder.Services; var host = builder.Host; startup.RegisterServices(services, host); var app = builder.Build(); var env = builder.Environment; startup.SetupMiddlewares(app, env); await app.RunAsync(); }接下来在项目中新建 Modules 文件夹,分别存放如下文件:AutowiredAttribute.cs,该文件用于标记属性注入的特性,和 AutowiredPropertySelector.cs 文件搭配使用;AutowiredPropertySelector.cs,该文件主要用于自定义属性的选择器;AutofacModule.cs,该文件主要定义 Autofac 中业务类的注入方式;此处主要描述 Autofac 在 asp.net core 项目中的使用,同时还自定义了特性标记的属性注入模式。首先新建 Modules 文件夹,用于存放上面的 .cs 文件,各文件的完整代码分别如下:1、AutowiredAttribute.cs 文件代码:namespace Jeff.Mes.WebApi.Modules; [AttributeUsage(AttributeTargets.Property)] public class AutowiredAttribute : Attribute { }2、AutowiredPropertySelector.cs 文件代码:using Autofac.Core; using System.Reflection; namespace Jeff.Mes.WebApi.Modules; public class AutowiredPropertySelector : IPropertySelector // 属性注入 public bool InjectProperty(PropertyInfo propertyInfo, object instance) // 自定义属性特性标记 [Autowired] 的才生效 return propertyInfo.CustomAttributes.Any(it => it.AttributeType == typeof(AutowiredAttribute)); }3、AutofacModule.cs 文件代码:using Autofac; using Jeff.Mes.Bll.BLL; using Jeff.Mes.Bll.IBLL; using Microsoft.AspNetCore.Mvc; namespace Jeff.Mes.WebApi.Modules; public class AutofacModule : Module protected override void Load(ContainerBuilder builder) // The generic ILogger<TCategoryName> service was added to the ServiceCollection by ASP.NET Core. // It was then registered with Autofac using the Populate method. All of this starts // with the `UseServiceProviderFactory(new AutofacServiceProviderFactory())` that happens in Program and registers Autofac // as the service provider. #region 此处编写服务(BaseServiceRegister)的注册规则 // ValuesService 构造函数有参 builder.Register(c => new ValuesService(c.Resolve<ILogger<ValuesService>>(), c.Resolve<IConfiguration>())) .As<IValuesService>() .InstancePerLifetimeScope(); // ValuesService 构造函数无参 builder.Register<ValuesService>().As<IValuesService>(); //不声明生命周期默认是瞬态。 builder.Register(c => new ValuesService(c.Resolve<ILogger<ValuesService>>(), c.Resolve<IConfiguration>())) .As<IValuesService>() .InstancePerLifetimeScope(); builder.RegisterType<UserService>() .As<IUserService>() .InstancePerLifetimeScope(); builder.RegisterType<UserService>() .As<IUserService>() .PropertiesAutowired() .InstancePerLifetimeScope(); builder.Register(c => new UserService()) .As<IUserService>() .PropertiesAutowired() .InstancePerLifetimeScope(); //builder.RegisterType<OrdersService>().As<IOrdersService>().InstancePerLifetimeScope(); builder.Register(c => new OrdersService(c.Resolve<ILogger<OrdersService>>(), c.Resolve<IConfiguration>())) .As<IOrdersService>() .InstancePerLifetimeScope(); #endregion #region 此处编写属性(PropertiesAutowired)的注册规则 var controllerBaseType = typeof(ControllerBase); // 说明:以下 1、2 两种方式等效。 // 1、获取所有控制器类型并使用属性注入 var controllersTypesInAssemblyAll = typeof(Startup).Assembly.GetExportedTypes() .Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToArray(); builder.RegisterTypes(controllersTypesInAssemblyAll).PropertiesAutowired(); // 2、自定义特性并使用属性注入 builder.RegisterAssemblyTypes(typeof(Program).Assembly) .Where(type => controllerBaseType.IsAssignableFrom(type) && type != controllerBaseType) .PropertiesAutowired(new AutowiredPropertySelector()); // 2.1 从 Startup 程序集中筛选出 ControllerBase 派生类程序集(ControllerBase 类中的程序集,并且排除本身 ControllerBase) var controllersTypesInAssembly = typeof(Startup).Assembly.GetExportedTypes() .Where(type => controllerBaseType.IsAssignableFrom(type) && type != controllerBaseType).ToArray(); // 2.2 注册筛选出的 ControllerBase 派生类的程序集,使用自定义特性标注的属性注入 builder.RegisterTypes(controllersTypesInAssembly) .PropertiesAutowired(new AutowiredPropertySelector()); #endregion }接下来(习惯性)新增一个 Configuration 文件夹,用于存放 appsettings.json 相关配置文件。因为上面的项目中我们在 host.ConfigureAppConfiguration 方法中调整了文件路径。最后就是在搭建的框架中使用 Autofac 实现各个模块的依赖,比如常用的 构造函数 DI 或者 属性 DI ,代码使用如下:构造函数 DI[ApiController, Route("api/[controller]")] public class ValuesController : ControllerBase private readonly IValuesService _valuesService; public ValuesController(IValuesService valuesService) // 构造函数 DI this._valuesService = valuesService; }属性 DI[Route("api/[controller]")] [ApiController] public class OrdersController : ControllerBase // 属性注入,在属性字段上面使用特性 [Autowired] 标注, // 下面 public 和 private 的注入均可。 [Autowired] public ILogger<OrdersController>? Logger { protected get; set; } [Autowired] private IOrdersService? OrdersService { get; set; } [Autowired] public IConfiguration? Configuration { protected get; set; } }其他项目层没啥好讲的,从架构图中即可看出对应的功能职责,到这里遵循架构图规则搭建的基础项目结构就基本完成了,该文章主要目的是相对于原始项目做一个对照,重新梳理了一遍经典的三层架构,方便初学人员有一个基础的模型参照。总结在项目实战开发中,理解三层架构并遵循该架构合理化的搭建项目基础框架是必要保障,而 不是 DAL 层项目去依赖 BLL 层项目(ಥ_ಥ ),每一层尽量做到 职责分明,各司其职,各个项目层之间 协调配合,项目层之间的调用应该 依赖抽象 而非具体实现,该项目框架的基本雏形就搭建完毕了,有了良好的地基,里面很多细节的装修就相对方便了,比如:很多 nuget 依赖包的使用,请自行查看相关文档。该文章的目的是提供一个相对合理化的基本模型参照,理解思路然后逐步按需完善框架的部分细节,并应用到实践中才会有更深的体会和记忆。
参数校验的意义在实际项目开发中,无论任何方式、任何规模的开发模式,项目中都离不开对接入数据模型参数的合法性校验,目前普片的开发模式基本是前后端分离,当用户在前端页面中输入一些表单数据时,点击提交按钮,触发请求目标服务器的一系列后续操作,在这中间的执行过程中(标准做法推荐)无论是前端代码部分,还是服务端代码部分都应该有针对用户输入数据的合法性校验,典型做法如下:前端部分:当用户在页面输入表单数据时,前端监听页面表单事件触发相应的数据合法性校验规则,当数据非法时,合理的提示用户数据错误,只有当所有表单数据都校验通过后,才继续提交数据给目标后端对应的接口;后端部分:当前端数据合法校验通过后,向目标服务器提交表单数据时,服务端接收到相应的提交数据,在入口源头出就应该触发相关的合法性校验规则,当数据都校验通过后,继续执行后续的相关业务逻辑处理,反之则响应相关非法数据的提示信息;特别说明:在实际的项目中,无论前端部分还是服务端部分,参数的校验都是很有必要性的。无效的参数,可能会导致应用程序的异常和一些不可预知的错误行为。常用参数的校验这里例举一些项目中比较常用的参数模型校验项,如下所示:Name:姓名校验,比如需要是纯汉字的姓名;Password:密码强度验证,比如要求用户输入必须包含大小写字母、数字和特殊符号的强密码;QQ号:QQ 号码验证,是否是有效合法的 QQ 号码;China Postal Code:中国邮政编码;IP Address:IPV4 或者 IPV6 地址验证;Phone:手机号码或者座机号码合法性验证;ID Card:身份证号码验证,比如:15 位和 18 位数身份证号码;Email Address:邮箱地址的合法性校验;String:字符串验证,比如字段是否不为 null、长度是否超限;URL:验证属性是否具有 URL 格式;Number:数值型参数校验,数值范围校验,比如非负数,非负整数,正整数等;File:文件路径及扩展名校验;对于参数校验,常见的方式有正则匹配校验,通过对目标参数编写合法的正则表达式,实现对参数合法性的校验。.NET 中内置 DataAnnotations 提供的特性校验上面我们介绍了一些常用的参数验证项,接下来我们来了解下在 .NET 中内置提供的 DataAnnotations 数据注解,该类提供了一些常用的验证参数特性。官方解释:提供用于为 ASP.NET MVC 和 ASP.NET 数据控件定义元数据的特性类。该类位于 System.ComponentModel.DataAnnotations 命名空间。关于 DataAnnotations 中的特性介绍让我们可以通过这些特性对 API 请求中的参数进行验证,常用的特性一般有:[ValidateNever]: 指示应从验证中排除属性或参数。[CreditCard]:验证属性是否具有信用卡格式。[Compare]:验证模型中的两个属性是否匹配。[EmailAddress]:验证属性是否具有电子邮件格式。[Phone]:验证属性是否具有电话号码格式。[Range]:验证属性值是否位于指定范围内。[RegularExpression]:验证属性值是否与指定的正则表达式匹配。[Required]:验证字段是否不为 null。[StringLength]:验证字符串属性值是否不超过指定的长度限制。[Url]:验证属性是否具有 URL 格式。其中 RegularExpression 特性,基于正则表达式可以扩展实现很多常用的验证类型,下面的( 基于 DataAnnotations 的通用模型校验封装 )环节举例说明;关于该类更多详细信息请查看,https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=net-7.0基于 DataAnnotations 的通用模型校验封装此处主要是使用了 Validator.TryValidateObject() 方法:Validator.TryValidateObject(object instance, ValidationContext validationContext, ICollection<ValidationResult>? validationResults, bool validateAllProperties);Validator 类提供如下校验方法:基于 DataAnnotations 的特性校验助手实现步骤错误成员对象类 ErrorMembernamespace Jeff.Common.Validatetion; /// <summary> /// 错误成员对象 /// </summary> public class ErrorMember /// <summary> /// 错误信息 /// </summary> public string? ErrorMessage { get; set; } /// <summary> /// 错误成员名称 /// </summary> public string? ErrorMemberName { get; set; } }验证结果类 ValidResultnamespace Jeff.Common.Validatetion; /// <summary> /// 验证结果类 /// </summary> public class ValidResult public ValidResult() ErrorMembers = new List<ErrorMember>(); /// <summary> /// 错误成员列表 /// </summary> public List<ErrorMember> ErrorMembers { get; set; } /// <summary> /// 验证结果 /// </summary> public bool IsVaild { get; set; } }定义操作正则表达式的公共类 RegexHelper(基于 RegularExpression 特性扩展)using System; using System.Net; using System.Text.RegularExpressions; namespace Jeff.Common.Validatetion; /// <summary> /// 操作正则表达式的公共类 /// Regex 用法参考:https://learn.microsoft.com/zh-cn/dotnet/api/system.text.regularexpressions.regex.-ctor?redirectedfrom=MSDN&view=net-7.0 /// </summary> public class RegexHelper #region 常用正则验证模式字符串 public enum ValidateType Email, // 邮箱 TelePhoneNumber, // 固定电话(座机) MobilePhoneNumber, // 移动电话 Age, // 年龄(1-120 之间有效) Birthday, // 出生日期 Timespan, // 时间戳 IdentityCardNumber, // 身份证 IpV4, // IPv4 地址 IpV6, // IPV6 地址 Domain, // 域名 English, // 英文字母 Chinese, // 汉字 MacAddress, // MAC 地址 Url, // URL private static readonly Dictionary<ValidateType, string> keyValuePairs = new Dictionary<ValidateType, string> { ValidateType.Email, _Email }, { ValidateType.TelePhoneNumber,_TelephoneNumber }, { ValidateType.MobilePhoneNumber,_MobilePhoneNumber }, { ValidateType.Age,_Age }, { ValidateType.Birthday,_Birthday }, { ValidateType.Timespan,_Timespan }, { ValidateType.IdentityCardNumber,_IdentityCardNumber }, { ValidateType.IpV4,_IpV4 }, { ValidateType.IpV6,_IpV6 }, { ValidateType.Domain,_Domain }, { ValidateType.English,_English }, { ValidateType.Chinese,_Chinese }, { ValidateType.MacAddress,_MacAddress }, { ValidateType.Url,_Url }, public const string _Email = @"^(\w)+(\.\w)*@(\w)+((\.\w+)+)$"; // ^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$ , [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4} public const string _TelephoneNumber = @"(d+-)?(d{4}-?d{7}|d{3}-?d{8}|^d{7,8})(-d+)?"; //座机号码(中国大陆) public const string _MobilePhoneNumber = @"^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$"; //移动电话 public const string _Age = @"^(?:[1-9][0-9]?|1[01][0-9]|120)$"; // 年龄 1-120 之间有效 public const string _Birthday = @"^((?:19[2-9]\d{1})|(?:20(?:(?:0[0-9])|(?:1[0-8]))))((?:0?[1-9])|(?:1[0-2]))((?:0?[1-9])|(?:[1-2][0-9])|30|31)$"; public const string _Timespan = @"^15|16|17\d{8,11}$"; // 目前时间戳是15开头,以后16、17等开头,长度 10 位是秒级时间戳的正则,13 位时间戳是到毫秒级的。 public const string _IdentityCardNumber = @"^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$"; public const string _IpV4 = @"^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$"; public const string _IpV6 = @"^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$"; public const string _Domain = @"^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?$"; public const string _English = @"^[A-Za-z]+$"; public const string _Chinese = @"^[\u4e00-\u9fa5]{0,}$"; public const string _MacAddress = @"^([0-9A-F]{2})(-[0-9A-F]{2}){5}$"; public const string _Url = @"^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$"; #endregion /// <summary> /// 获取验证模式字符串 /// </summary> /// <param name="validateType"></param> /// <returns></returns> public static (bool hasPattern, string pattern) GetValidatePattern(ValidateType validateType) bool hasPattern = keyValuePairs.TryGetValue(validateType, out string? pattern); return (hasPattern, pattern ?? string.Empty); #region 验证输入字符串是否与模式字符串匹配 /// <summary> /// 验证输入字符串是否与模式字符串匹配 /// </summary> /// <param name="input">输入的字符串</param> /// <param name="validateType">模式字符串类型</param> /// <param name="matchTimeout">超时间隔</param> /// <param name="options">筛选条件</param> /// <returns></returns> public static (bool isMatch, string info) IsMatch(string input, ValidateType validateType, TimeSpan matchTimeout, RegexOptions options = RegexOptions.None) var (hasPattern, pattern) = GetValidatePattern(validateType); if (hasPattern && !string.IsNullOrWhiteSpace(pattern)) bool isMatch = IsMatch(input, pattern, matchTimeout, options); if (isMatch) return (true, "Format validation passed."); // 格式验证通过。 else return (false, "Format validation failed."); // 格式验证未通过。 return (false, "Unknown ValidatePattern."); // 未知验证模式 /// <summary> /// 验证输入字符串是否与模式字符串匹配,匹配返回true /// </summary> /// <param name="input">输入字符串</param> /// <param name="pattern">模式字符串</param> /// <returns></returns> public static bool IsMatch(string input, string pattern) return IsMatch(input, pattern, TimeSpan.Zero, RegexOptions.IgnoreCase); /// <summary> /// 验证输入字符串是否与模式字符串匹配,匹配返回true /// </summary> /// <param name="input">输入的字符串</param> /// <param name="pattern">模式字符串</param> /// <param name="matchTimeout">超时间隔</param> /// <param name="options">筛选条件</param> /// <returns></returns> public static bool IsMatch(string input, string pattern, TimeSpan matchTimeout, RegexOptions options = RegexOptions.None) return Regex.IsMatch(input, pattern, options, matchTimeout); #endregion }定义验证结果统一模型格式类 ResponseInfo(此类通常也是通用的数据响应模型类)namespace Jeff.Common.Model; public sealed class ResponseInfo<T> where T : class Microsoft.AspNetCore.Http.StatusCodes System.Net.HttpStatusCode /// <summary> /// 响应代码(自定义) /// </summary> public int Code { get; set; } /// <summary> /// 接口状态 /// </summary> public bool Success { get; set; } #region 此处可以考虑多语言国际化设计(语言提示代号对照表) /// <summary> /// 语言对照码,参考:https://blog.csdn.net/shenenhua/article/details/79150053 /// </summary> public string Lang { get; set; } = "zh-cn"; /// <summary> /// 提示信息 /// </summary> public string Message { get; set; } = string.Empty; #endregion /// <summary> /// 数据体 /// </summary> public T? Data { get; set; } }实现验证助手类 ValidatetionHelper,配合 System.ComponentModel.DataAnnotations 类使用// 数据注解,https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=net-7.0 using System.ComponentModel.DataAnnotations; using Jeff.Common.Model; namespace Jeff.Common.Validatetion; /// <summary> /// 验证助手类 /// </summary> public sealed class ValidatetionHelper /// <summary> /// DTO 模型校验 /// </summary> /// <param name="value"></param> /// <returns></returns> public static ValidResult IsValid(object value) var result = new ValidResult(); var validationContext = new ValidationContext(value); var results = new List<ValidationResult>(); bool isValid = Validator.TryValidateObject(value, validationContext, results, true); result.IsVaild = isValid; if (!isValid) foreach (ValidationResult? item in results) result.ErrorMembers.Add(new ErrorMember() ErrorMessage = item.ErrorMessage, ErrorMemberName = item.MemberNames.FirstOrDefault() catch (ValidationException ex) result.IsVaild = false; result.ErrorMembers = new List<ErrorMember> new ErrorMember() ErrorMessage = ex.Message, ErrorMemberName = "Internal error" return result; /// <summary> /// DTO 模型校验统一响应信息 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="model"></param> /// <returns></returns> public static ResponseInfo<ValidResult> GetValidInfo<T>(T model) where T : class var result = new ResponseInfo<ValidResult>(); var validResult = IsValid(model); if (!validResult.IsVaild) result.Code = 420; result.Message = "DTO 模型参数值异常"; result.Success = false; result.Data = validResult; result.Code = 200; result.Success = true; result.Message = "DTO 模型参数值合法"; return result; }如何使用 DataAnnotations 封装的特性校验助手?首先定义一个数据模型类(DTO),添加校验特性 ValidationAttributeusing System.ComponentModel.DataAnnotations; using Jeff.Common.Validatetion; namespace Jeff.Comm.Test; public class Person [Display(Name = "姓名"), Required(ErrorMessage = "{0}必须填写")] public string Name { get; set; } [Display(Name = "邮箱")] [Required(ErrorMessage = "{0}必须填写")] [RegularExpression(RegexHelper._Email, ErrorMessage = "RegularExpression: {0}格式非法")] [EmailAddress(ErrorMessage = "EmailAddress: {0}格式非法")] public string Email { get; set; } [Display(Name = "Age年龄")] [Required(ErrorMessage = "{0}必须填写")] [Range(1, 120, ErrorMessage = "超出范围")] [RegularExpression(RegexHelper._Age, ErrorMessage = "{0}超出合理范围")] public int Age { get; set; } [Display(Name = "Birthday出生日期")] [Required(ErrorMessage = "{0}必须填写")] [RegularExpression(RegexHelper._Timespan, ErrorMessage = "{0}超出合理范围")] public TimeSpan Birthday { get; set; } [Display(Name = "Address住址")] [Required(ErrorMessage = "{0}必须填写")] [StringLength(200, MinimumLength = 10, ErrorMessage = "{0}输入长度不正确")] public string Address { get; set; } [Display(Name = "Mobile手机号码")] [Required(ErrorMessage = "{0}必须填写")] [RegularExpression(RegexHelper._MobilePhoneNumber, ErrorMessage = "{0}格式非法")] public string Mobile { get; set; } [Display(Name = "Salary薪水")] [Required(ErrorMessage = "{0}必须填写")] [Range(typeof(decimal), "1000.00", "3000.99")] public decimal Salary { get; set; } [Display(Name = "MyUrl连接")] [Required(ErrorMessage = "{0}必须填写")] [Url(ErrorMessage = "Url:{0}格式非法")] [RegularExpression(RegexHelper._Url, ErrorMessage = "RegularExpression:{0}格式非法")] public string MyUrl { get; set; } }控制台调用通用校验助手验证方法 ValidatetionHelper.IsValid() 或 ValidatetionHelper.GetValidInfo()// 通用模型数据验证测试 static void ValidatetionTest() var p = new Person Name = "", Age = -10, Email = "www.baidu.com", MobilePhoneNumber = "12345", Salary = 4000, MyUrl = "aaa" // 调用通用模型校验 var result = ValidatetionHelper.IsValid(p); if (!result.IsVaild) foreach (ErrorMember errorMember in result.ErrorMembers) // 控制台打印字段验证信息 Console.WriteLine($"{errorMember.ErrorMemberName}:{errorMember.ErrorMessage}"); Console.WriteLine(); // 调用通用模型校验,返回统一数据格式 var validInfo = ValidatetionHelper.GetValidInfo(p); var options = new JsonSerializerOptions Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // 设置中文编码乱码 WriteIndented = false string jsonStr = JsonSerializer.Serialize(validInfo, options); Console.WriteLine($"校验结果返回统一数据格式:{jsonStr}"); }在控制台Program.Main 方法中调用 ValidatetionTest() 方法:internal class Program static void Main(string[] args) Console.WriteLine("Hello, World!"); #region 数据注解(DataAnnotations)模型验证 ValidatetionTest(); #endregion Console.ReadKey(); }启动控制台,输出如下信息:如何实现自定义的验证特性?当我们碰到这些参数需要验证的时候,而上面内置类提供的特性不能满足需求时,此时我们可以实现自定义的验证特性来满足校验需求,按照微软给出的编码规则,我们只需继承 ValidationAttribute 类,并重写 IsValid() 方法即可。自定义校验特性案例比如实现一个密码强度的验证,实现步骤如下:定义密码强度规则,只包含英文字母、数字和特殊字符的组合,并且组合长度至少 8 位数;/// <summary> /// 只包含英文字母、数字和特殊字符的组合 /// </summary> /// <returns></returns> public static bool IsCombinationOfEnglishNumberSymbol(string input, int? minLength = null, int? maxLength = null) var pattern = @"(?=.*\d)(?=.*[a-zA-Z])(?=.*[^a-zA-Z\d])."; if (minLength is null && maxLength is null) pattern = $@"^{pattern}+$"; else if (minLength is not null && maxLength is null) pattern = $@"^{pattern}{{{minLength},}}$"; else if (minLength is null && maxLength is not null) pattern = $@"^{pattern}{{1,{maxLength}}}$"; pattern = $@"^{pattern}{{{minLength},{maxLength}}}$"; return Regex.IsMatch(input, pattern); }实现自定义特性 EnglishNumberSymbolCombinationAttribute,继承自 ValidationAttribute;using System.ComponentModel.DataAnnotations; namespace Jeff.Common.Validatetion.CustomAttributes; /// <summary> /// 是否是英文字母、数字和特殊字符的组合 /// </summary> public class EnglishNumberSymbolCombinationAttribute : ValidationAttribute /// <summary> /// 默认的错误提示信息 /// </summary> private const string error = "无效的英文字母、数字和特殊字符的组合"; protected override ValidationResult IsValid(object value, ValidationContext validationContext) if (value is null) return new ValidationResult("参数值为 null"); //if (value is null) // throw new ArgumentNullException(nameof(attribute)); // 验证参数逻辑 value 是需要验证的值,而 validationContext 中包含了验证相关的上下文信息,这里可自己封装一个验证格式的 FormatValidation 类 if (FormatValidation.IsCombinationOfEnglishNumberSymbol(value as string, 8)) //验证成功返回 success return ValidationResult.Success; //不成功 提示验证错误的信息 else return new ValidationResult(ErrorMessage ?? error); }以上就实现了一个自定义规则的 自定义验证特性,使用方式很简单,可以把它附属在我们 请求的参数 上或者 DTO 里的属性,也可以是 Action 上的形参,如下所示:public class CreateDTO [Required] public string StoreName { get; init; } [Required] // 附属在 DTO 里的属性 [EnglishNumberSymbolCombination(ErrorMessage = "UserId 必须是英文字母、数字和特殊符号的组合")] public string UserId { get; init; } // 附属在 Action 上的形参 [HttpGet] public async ValueTask<ActionResult> Delete([EnglishNumberSymbolCombination]string userId, string storeName)该自定义验证特性还可以结合 DataAnnotations 内置的 [Compare] 特性,可以实现账号注册的密码确认验证(输入密码和确认密码是否一致性)。关于更多自定义参数校验特性,感兴趣的小伙伴可参照上面案例的实现思路,自行扩展实现哟。总结对于模型参数的校验,在实际项目系统中是非常有必要性的(通常在数据源头提供验证),利用 .NET 内置的 DataAnnotations(数据注解)提供的特性校验,可以很方便的实现通用的模型校验助手,关于其他特性的用法,请自行参考微软官方文档,这里注意下RegularExpressionAttribute(指定 ASP.NET 动态数据中的数据字段值必须与指定的正则表达式匹配),该特性可以方便的接入正则匹配验证,当遇到复杂的参数校验时,可以快速方便的扩展自定义校验特性,从此告别传统编码中各种 if(xxx != yyyy) 判断的验证,让整体代码编写更佳简练干净。
需求说明在基于 k8s 平台的容器化部署环境中,有时候需要快速的实现部署文件的迁移备份,当 k8s 平台部署一个 app 时,都会相应的产生一堆 yaml 文件,如果 yaml 文件数量较少,我们可以人工手动的方式进行拷贝,但是当 yaml 文件数量多,并且该 k8s 平台部署了多个 app 时,如果在采用人工手动的方式实现这些 yaml 文件的拷贝,可想而知这个工作量是相当多且繁琐的,而这些机械化的人工操作还会产生误差,无法保障文件内容质量,针对这种场景,因此我们采用 shell 脚本来实现快速的 yaml 文件迁移拷贝,相应的提升工作效率并保障 yaml 文件内容质量。功能实现实现思路,这里主要是应用了 【bash & kubectl 】组合知识点,实现对 k8s 平台上指定 namespaces(ns,命名空间)下指定资源的 yaml 文件拷贝,shell 脚本实现如下:shell 脚本实现dump-k8s-yaml.sh 工具 shell 脚本编写如下:#!/bin/bash useage(){ echo "Useage:" echo " bash ./dump-k8s-yaml.sh DUMPDIR KUBECONFIG [NAMESPACE]" if [ $# -lt 1 ];then useage dumpDir=$1 KF=$2 NS=$3 resourceList=( #componentstatuses # cs configmaps # cm secrets persistentvolumeclaims # pvc events # ev serviceaccounts # sa endpoints # ep services # svc ingress daemonsets # ds deployments # deploy replicasets # rs statefulsets # sts cronjobs # cj pods # po showResourceList(){ kubectl --kubeconfig=$KF -n=$NS get nodes kubectl --kubeconfig=$KF --sort-by=.metadata.name -n=$NS get pods kubectl --kubeconfig=$KF -n=$NS get cm,secrets,pvc,ev,sa,ep,svc,ingress,ds,deploy,rs,sts,jobs,cj printList(){ for aa in ${resourceList[@]}; aList=$(kubectl --kubeconfig=$KF -n=$NS get $aa | grep -v NAME | awk '{print $1}') if [ ! "${aList[*]}"x == "x" ];then [ -d $dumpDir/$aa ] || mkdir -p $dumpDir/$aa for i in $aList; echo $aa $i kubectl --kubeconfig=$KF -n=$NS get $aa $i -o yaml > $dumpDir/$aa/$i.yaml zipExec(){ #sudo apt install zip unzip #zip -v #unzip -v zip -r -q $dumpDir.zip file $dumpDir rm -rf $dumpDir # create namespaces yaml if [ ! -d $dumpDir ];then mkdir -p -m 777 ./$dumpDir kubectl --kubeconfig=$KF get namespaces $NS -o yaml > $dumpDir/$NS.yaml # create pv yaml pvList=$(kubectl --kubeconfig=$KF get pv | grep "$NS/" | awk '{print $1}') if [ ! "${pvList[*]}"x == "x" ];then [ -d $dumpDir/persistentvolumes ] || mkdir -p $dumpDir/persistentvolumes for i in ${pvList[@]} echo persistentvolumes $i kubectl --kubeconfig=$KF get pv $i -o yaml > $dumpDir/persistentvolumes/$i.yaml echo "----[showResourceList]-----------------------" showResourceList echo "----[printList]-----------------------" printList echo "----[zipExec]-----------------------" zipExec echo "export ${NS} ymal completed!" :<<! 使用方法: bash dump-k8s-yaml.sh ./demons ./kube.conf demons bash ./dump-k8s-yaml.sh ./dotnet-escada ./kube.conf dotnet-escada !shell 使用方式注意:使用此命令,需要获得 kubectl 访问 k8s 集群环境的相应权限(即 kubeconfig 配置信息);前置工具环境安装从上面的 dump-k8s-yaml.sh 文件中可以看到部分工具的依赖,说明如下:xshell 工具安装(请参照);在 shell 终端(此处使用的 XShell)配置好 kubectl 工具,并获取到 kubeconfig 文件;linux 环境安装文件压缩 & 解压工具 zip & unzip;linux 环境安装 lrzsz 工具(说明 lrzsz 工具只适合传输小文件,不适合传输大型文件,通常情况 lrzsz 配合 xshell 工具使用);说明:此处 linux 环境的工具安装命令,适用基于 Ubuntu 的发行版,本人使用的环境是 Debian 11。其他 linux 发行版请自行查看对应的安装命令即可,下面的 dump-k8s-yaml.sh 工具在 linux 环境均可通用;1、配置 kubectl,并导出 kubeconfig 文件请自行参考相关资料;2、linux 宿主机没有安装 zip & unzip 工具,执行如下命令:# linux 安装 zip,unzip: sudo apt install zip unzip # 查看版本: zip -v unzip -v # zip 压缩文件: zip -r -q dump-yaml.zip file ./dump-yaml # unzip 压缩文件: unzip dump-yaml.zip3、linux 宿主机安装 lrzsz 工具,执行如下命令:sudo apt update && apt install lrzsz XShell 文件上传 & 下载: # 上传文件:rz # 单个文件下载:sz file dump-yaml.zip # 多文文件下载(文件中间使用空格分开):sz file dump-yaml-1.zip dump-yaml-2.zipdump-k8s-yaml.sh 使用方式注意:使用【xshell& lrzsz】工具,先把【dump-k8s-yaml.sh】工具上传到 linux 环境,并指定 kubeconfig 文件,此处该文件名称为 kube.conf,和 dump-k8s-yaml.sh 工具是在同一个目录环境下(DUMPDIR 指定方便路径), kubeconfig 文件也可以放在其他路径。在 linux 宿主机中准备好上面这些基础环境的配置和 shell 客户端工具安装后,接下来我们就可以使用 dump-k8s-yaml.sh 实现 k8s 环境中指定资源的 yaml 文件导出了,使用方式如下:输入命令 bash ./dump-k8s-yaml.shcloud@k8s-node-1:~$ bash ./dump-k8s-yaml.sh Useage: bash ./dump-k8s-yaml.sh DUMPDIR KUBECONFIG [NAMESPACE]参数说明:bash 指定 shell 终端类型;./dump-k8s-yaml.sh 当前目录下的 dump-k8s-yaml.sh 工具;DUMPDIR 指定 .zip 文件路径;KUBECONFIG 指定 kubeconfig 文件路径;NAMESPACE 指定 k8s 环境中 ns 命名空间名称;dump-k8s-yaml.sh 应用举例举例:导出 ns 为 dotnet-escada 下指定资源的 yaml 文件,并保存到当前路径的 dotnet-escada 目录中;bash ./dump-k8s-yaml.sh ./dotnet-escada ./kube.conf dotnet-escadadump-k8s-yaml.sh 输出日志信息依据上面使用说明执行命令,输出如下日志结构信息:persistentvolumes pvc-7a0027c9-84f7-40f9-90b2-929d1514d156 ----[showResourceList]----------------------- NAME READY STATUS RESTARTS AGE ----[printList]----------------------- ----[zipExec]----------------------- export dotnet-escada ymal completed!以上面的【举例】为依据,执行命令输出完整日志信息如下:cloud@demo-k8s-node-1:~$ bash ./dump-k8s-yaml.sh ./dotnet-escada ./kube.conf dotnet-escada persistentvolumes pvc-7a0027c9-84f7-40f9-90b2-929d1514d156 ----[showResourceList]----------------------- NAME READY STATUS RESTARTS AGE deploy-demo-mes-keeperfile-host-75f8878ff7-8q98x 1/1 Running 2 27h deploy-demo-mes-redis-55fc5dd9cc-6vtkh 1/1 Running 2 27h deploy-demo-nginx-host-ddddfc864-zxkjb 1/1 Running 5 27h deploy-demo-smartworx-plugs-host-5d9bd5c99c-rgtxh 0/1 Evicted 0 28h NAME DATA AGE configmap/demo-mes-authentication-service-config 1 30h configmap/demo-mes-config 1 30h configmap/demo-mes-keeper-appseetings 2 30h configmap/demo-mes-nginx-config 1 29h configmap/demo-mes-nginx-service-config 1 29h configmap/demo-mes-redis-service-config 1 27h configmap/kube-root-ca.crt 1 31h NAME TYPE DATA AGE secret/ceph-kubernetes-dynamic-user-01847765-90af-11ed-bbdc-06eca119f7b2-secret Opaque 1 31h secret/default-token-k85zv kubernetes.io/service-account-token 3 31h NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/pvc-demo-escada-data Bound pvc-7a0027c9-84f7-40f9-90b2-929d1514d156 10Gi RWO cephfs 31h LAST SEEN TYPE REASON OBJECT MESSAGE 3m24s Normal Sync ingress/escada-inkelink-com Scheduled for sync 3m22s Normal Sync ingress/escada-inkelink-com Scheduled for sync NAME SECRETS AGE serviceaccount/default 1 31h NAME ENDPOINTS AGE endpoints/demo-mes-keeperfile-host 10.44.0.12:80 28h endpoints/demo-mes-redis 10.44.0.45:6379 27h endpoints/demo-nginx-host 10.44.0.20:80 27h endpoints/demo-scada-base-host <none> 28h endpoints/demo-smartworx-host <none> 28h endpoints/demo-smartworx-plugs-host <none> 28h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/demo-mes-keeperfile-host ClusterIP 10.108.90.134 <none> 80/TCP 28h service/demo-mes-redis ClusterIP 10.99.71.77 <none> 6379/TCP 27h service/demo-nginx-host LoadBalancer 10.108.145.181 10.23.4.193 80:31995/TCP 27h service/demo-scada-base-host ClusterIP 10.110.121.140 <none> 80/TCP 28h service/demo-smartworx-host ClusterIP 10.110.63.82 <none> 80/TCP 28h service/demo-smartworx-plugs-host ClusterIP 10.105.115.242 <none> 80/TCP 28h NAME CLASS HOSTS ADDRESS PORTS AGE ingress.networking.k8s.io/escada-inkelink-com nginx escada.inkelink.com 10.23.4.192 80 27h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/deploy-demo-mes-keeperfile-host 1/1 1 1 28h deployment.apps/deploy-demo-mes-redis 1/1 1 1 27h deployment.apps/deploy-demo-nginx-host 1/1 1 1 27h deployment.apps/deploy-demo-scada-base-host 0/0 0 0 28h deployment.apps/deploy-demo-smartworx-host 0/0 0 0 28h deployment.apps/deploy-demo-smartworx-plugs-host 0/0 0 0 28h NAME DESIRED CURRENT READY AGE replicaset.apps/deploy-demo-mes-keeperfile-host-75f8878ff7 1 1 1 28h replicaset.apps/deploy-demo-mes-redis-55fc5dd9cc 1 1 1 27h replicaset.apps/deploy-demo-nginx-host-ddddfc864 1 1 1 27h replicaset.apps/deploy-demo-scada-base-host-6cb55ccd97 0 0 0 28h replicaset.apps/deploy-demo-smartworx-host-7df5945c7 0 0 0 28h replicaset.apps/deploy-demo-smartworx-host-856db4cd49 0 0 0 28h replicaset.apps/deploy-demo-smartworx-host-9c5ff7859 0 0 0 26h replicaset.apps/deploy-demo-smartworx-plugs-host-5d9bd5c99c 0 0 0 28h NAME READY STATUS RESTARTS AGE pod/deploy-demo-mes-keeperfile-host-75f8878ff7-8q98x 1/1 Running 2 27h pod/deploy-demo-mes-redis-55fc5dd9cc-6vtkh 1/1 Running 2 27h pod/deploy-demo-nginx-host-ddddfc864-zxkjb 1/1 Running 5 27h pod/deploy-demo-smartworx-plugs-host-5d9bd5c99c-rgtxh 0/1 Evicted 0 28h ----[printList]----------------------- configmaps demo-mes-authentication-service-config configmaps demo-mes-config configmaps demo-mes-keeper-appseetings configmaps demo-mes-nginx-config configmaps demo-mes-nginx-service-config configmaps demo-mes-redis-service-config configmaps kube-root-ca.crt secrets ceph-kubernetes-dynamic-user-01847765-90af-11ed-bbdc-06eca119f7b2-secret secrets default-token-k85zv persistentvolumeclaims pvc-demo-escada-data events LAST Error from server (NotFound): events "LAST" not found events 3m26s Error from server (NotFound): events "3m26s" not found events 3m24s Error from server (NotFound): events "3m24s" not found serviceaccounts default endpoints demo-mes-keeperfile-host endpoints demo-mes-redis endpoints demo-nginx-host endpoints demo-scada-base-host endpoints demo-smartworx-host endpoints demo-smartworx-plugs-host services demo-mes-keeperfile-host services demo-mes-redis services demo-nginx-host services demo-scada-base-host services demo-smartworx-host services demo-smartworx-plugs-host ingress escada-inkelink-com No resources found in demo-dotnet-escada namespace. deployments deploy-demo-mes-keeperfile-host deployments deploy-demo-mes-redis deployments deploy-demo-nginx-host deployments deploy-demo-scada-base-host deployments deploy-demo-smartworx-host deployments deploy-demo-smartworx-plugs-host replicasets deploy-demo-mes-keeperfile-host-75f8878ff7 replicasets deploy-demo-mes-redis-55fc5dd9cc replicasets deploy-demo-nginx-host-ddddfc864 replicasets deploy-demo-scada-base-host-6cb55ccd97 replicasets deploy-demo-smartworx-host-7df5945c7 replicasets deploy-demo-smartworx-host-856db4cd49 replicasets deploy-demo-smartworx-host-9c5ff7859 replicasets deploy-demo-smartworx-plugs-host-5d9bd5c99c No resources found in demo-dotnet-escada namespace. No resources found in demo-dotnet-escada namespace. No resources found in demo-dotnet-escada namespace. pods deploy-demo-mes-keeperfile-host-75f8878ff7-8q98x pods deploy-demo-mes-redis-55fc5dd9cc-6vtkh pods deploy-demo-nginx-host-ddddfc864-zxkjb pods deploy-demo-smartworx-plugs-host-5d9bd5c99c-rgtxh ----[zipExec]----------------------- export dotnet-escada ymal completed!dump-k8s-yaml.sh 执行完成后,在当前环境目录下会生成一个对应的 .zip 压缩文件,上面例子中对应产生的文件是 dotnet-escada.zip ,此时我们可以利用【xshell & lrzsz】工具下载 dotnet-escada.zip 文件,执行命令如下:sz file dotnet-escada.zip参考文档kubectl 命令行工具,https://kubernetes.io/zh-cn/docs/reference/kubectl/
何时应该考虑使用 WSL 而不是标准 Linux 发行版?正直的芹菜 · 光明区实验学校(集团)华夏中学--华夏中学 3 月前 |
旅途中的盒饭 · 如何评价歌手毛阿敏的地位? - 知乎 1 年前 |
讲道义的番茄 · 【战旗】北汽制造_战旗报价_战旗图片_新车评网 1 年前 |