我们如何通过两个关键测试原则,进行自动化 Kubernetes 配置和Secret测试

现如今,一个上规模的应用程序几乎都会使用 Kubernetes 作为管理环境实现自动扩展、负载平衡(auto scaling, load balancing )等机制。与之相应的,我们通常会使用一个 repository 专门管理一个组织内部各项不同应用程序在各个环境的各项 secret, config value。这时确保 Kubernetes 上 config maps 和 secrets 的正确性就显得非常重要,因为一但这些值被设定错误,那么就会直接影响服务,导致预期之外的行为,进而影响服务的功能或行为。

举例来说:假如有一套 E-Commerce 的应用程序需要通过 支付网关(payment gateway )去处理交易,而这个网关会需要 API Key 验证,那我们可能会为 API Key 设定 Secret :


apiVersion: v1kind: Secretmetadata:name: payment-secrettype: Opaquedata:api_key: "c2VjdXJlYXBpa2V5" # Base64 encoded 'secureapikey'

此时如果 API Key 的 value 是错误的(e.g., Real 环境设定成 Beta 的数值,或本身数值就记录错误),则这个 E-commerce 应用的支付功能就会出错。

但在实务上,这些重要的数值都是人工设定的。尽管我们会通过代码审查、版本控制来保障配置(configuration)的准确性和变更纪录追踪,但必须承认,这些基于纯文字的配置文件本质上仍存在风险。因为它不能被静态及动态检查,也缺少自动化的验证流程保护。一旦开发人员在设定过程中发生疏忽,且这些错误未能及时被发现,即便是最微小的错误(typo),只要发生在重要的 value 上也可能酿成严重的后果。

在我们团队,我们意识到这个问题的重要性,并提出了一套自动化的检测方法。这套方法能够与 CI Pipeline 结合,在每次 config 有变动时,通过运行自动化检测去确保修改、新增的数值还是能够保证其正确及有效性。

接下来,我们会从定义如何检测一个 value 开始,并说明我们如何决定要验证的数值及如何进行验证。

两个关键测试原则:有效性和特定环境正确性

首先,一个“正确”的 value 需要满足两个重要条件:

· Validity (有效性):对于重要的值,我们期望该值是有效的。在这里,我们将有效定义为这个 value 是能够发挥作用的。例如一个 MongoDB 连线字符串,只要它能够连到 MongoDB,不论是不是连到正确的数据库,我们都说这个 value 是有效的,因为它可以发挥作用。

· Environment-Specific Correctness (特定环境的正确性):除了确认 config, secret value 是有效的之外,我们还需要确保该值存在于正确的环境中。对于一个应用程序,应该有三种不同的环境:beta,rc(staging),和 real。每个 config or secret value 应该在这三个环境中都有其对应的值。再次以 MongoDB 连线字符串为例:在不同的环境中,它应该有不同的值来连接不同环境的数据库:

  • Beta:mongodb://beta-username:beta-password@10.127.xxx.beta

  • RC:mongodb://rc-username:rc-password@10.127.xxx.rc

  • Real:mongodb://real-username:real-password@10.127.xxx.real

想象一下,假如我们不小心在 Real 环境中使用了 Beta MongoDB 连线字符串,那么尽管这个值是有效的(因为它可以连接到 beta MongoDB),但我们能说这个值是正确的吗?不行,因为它不在正确的环境中!因此这就是为什么除了验证一个 value 有效,我们还需要确保在环境层面上 config 及 secret 的正确性。

测试环境设置

首先,原本的 Kubernetes manifest repo 只有包含travel-apps 这个目录,其为典型的 Kustomization repository 架构,包含了 base and overlays。我们的方法是在同一层级新增一个采用 node.js + Jest + TypeScript 的目录。

它包含了各种测试,覆盖了需要检查的 overlays 中存在的不同类型的 config 及 secret value。至于 CI,我们使用 GitHub Actions 作为 pipeline。每当 manifest 内容发生变动时,就会触发 workflow 执行 `config-secret-test` 中的测试。


├── .github│ └── workflows│ └── pre-commit.yml├── config-secret-test│ ├── __tests__│ │ ├── test1│ │ ├── test2│ │ ├── ...│ ├── jest.config.js│ └── package.json└── travel-apps├── base└── overlays (containing configs and secrets)

测试方法

我们对 Travel 中现有的 Kubernetes manifest 内容进行分类。根据上述提到的 Two Key Testing Principle,不同类别需要通过不同的方法来验证。下面我们将解释划分的类别项目,以及每个类别如何验证有效性和环境正确性:

分布式数据库系统(MongoDB、Redis、ElasticSearch、S3)
有效性(Validity)

这类型的 secret value 会是连线字符串,因此验证有效性的方法很简单。针对不同的数据库系统,我们使用对应的 client library 尝试进行连接,并执行简单的操作(e.g., ping , check )以检查是否成功建立连线。

特定环境的正确性(Environment-specific correctness)

为了确认环境的正确性,我们需要找到一种方法来识别当给定一个连线字符串,我们要能够知道其所连接的数据库是处于哪一个环境。

A Naive Idea

起初,我们考虑,是否可以根据所连接数据库的资料量多寡来判断环境?例如:Beta 环境通常比 Real 环境拥有较少的资料,所以如果我们连线后发现资料量少于某个阀值(threshold),我们就认定该环境为 Beta。这个方法很简单,但问题在于它不够精确,因为资料量可能会变动,而这会影响我们的判断标准。

Golden Answer Approach

经过讨论,我们后来采用了 Golden Answer 方法,也就是先在每个环境中插入一个 Golden Answer 文件作为后续验证使用。这份文件储存了简单的信息:这个环境的标识(Beta、RC、Real)。如此,当我们想要验证一个连线字符串的环境时,我们就直接使用字符串进行连线并且检索该连线字符串对应数据库环境内的 Golden Answer 文件。当有了这项信息,我们只需要验证我们现在正在测试的连线字符串是否符合我们认为其所应该要连接到的环境。下图是 Golden Answer 方法的一个例子(假如我们想验证一个连线字符串是否是连到 Beta)及流程图:

图片

下面也提供使用 jest 撰写的测试程序码供参:

用 Jest 测试 MongoDB 代码的两个关键原则

 

import { MongoClient } from "../utils/mongoClient";import { currentTestEnv } from "../env.config";import { readTextFile, resolveSecretPath } from "../utils/getConfigSecret";let mongoClient: MongoClient;const secrets = readTextFile(resolveSecretPath("mongo"));afterEach(async () => {if (mongoClient) {await mongoClient.disconnect();}});async function testMongoDb(connectionString: string) {mongoClient = new MongoClient(connectionString);test("ensure MongoDB connection string functions well", async () => {const isConnectionSuccess = await mongoClient.tryConnect();expect(isConnectionSuccess).toBe(true);});test("retrieve Env Doc from MongoDB", async () => {await mongoClient.tryConnect();const envDoc = await mongoClient.queryEnvCheckDoc();expect(envDoc?.env).toBe(currentTestEnv);});}// Run tests for each MongoDB connection stringtestMongoDb(secrets["MONTHLY_REPORT_MONGODB_URL"]);
加密/解密密钥(Encryption / Decryption Keys)

有些 secret values 是用于数据的加密和解密。这种方法的有效性和环境正确性验证可以同时进行。检查方法类似于 Golden Answer 方法,首先插入一个 Golden value,然后在测试期间检索并验证这项数值。详细说明如下:

1. 我们首先使用每个环境正确的 Encryption Key 对我们的 Golden Answer 文件进行加密,并将其插入到每个环境的数据库中。

2. 在测试过程中,我们从当前的 secret values 中检索对应的 Decryption Key,并验证其是否能够解密。

3. 如果能够解密,这意味着目前存在于 manifest 中的 Decryption Key 能够发挥作用并且存在于正确的环境中。

Tokens

以 JWT Tokens 为例,在某些情况下我们会为其他团队甚至外部服务发放 JWT Tokens 以存取我们的服务。JWT Tokens 需要验证的是当前 manifest 中用于生成 Token 的 secret key 其 value 的正确性,但我们又不能使用发给其他人的 Tokens 来验证 decode 的正确性,因为这相当于将我们发给其他人的 Tokens 推到我们的测试程序码 repository 上。

因此,我们可以使用jwt.io (https://jwt.io/)输入不同环境的 secret key 生成可用于测试每个环境的 tokens。如此,我们就可以安全地将这些测试 tokens 放入程序码中,以验证 secrert key decode 的正确性。

URLs

对于依赖各种 URL config 的服务,例如 CDN_URL, API_BASE_URL, 以及特定服务的 API host(LINETVL_xxx_service_API_HOST、xxx_BASE_URL),我们必须确保这些 configs 的格式正确性及可存取性。而针对 Urls, 我们可以使用下列方式涵盖 two key testing principles 的验证逻辑:

Validity

为了确定一个 URL 是否可用,我们其实能够透过dns.lookup  确认 URL 的 hostname 是否存在。如果存在,表示此 URL 可以存取,否则就不是一个有效的 URL。这种方法的优点是我们不需要实际连接到每个 URL,我们只需要解析(parse)其 hostname 并确认其存在,这样可以节省许多不必要的 end-to-end 测试成本。

特定环境的正确性(Environment-specific correctness)

由于 LINE 的 domain 及 hostname 在命名上有完整的规范,基本上每个环境的 URLs 都会带有一个后缀(suffix)或 substring,这些就可以用来辨识一个 URL 对应的环境。因此,我们可以透过正则表达式(Regex)简单地确定一个 URL 属于哪个环境。

URL 测试的范例程序码如下:

 

import dns from "dns";import { readTextFile, resolveConfigPath } from "../../utils/getConfigSecret";import { currentTestEnv } from "../../env.config";import { Env } from "../../enums/env";import { getAllConfigs } from "../../utils/getConfigSecret";function checkHostname(hostname: string) {return new Promise((resolve) => {dns.lookup(hostname, (error) => {if (error) {resolve(false);} else {resolve(true);}});});}const toHostnameIfInputIsUrl = (input: string) => {try {return new URL(input).hostname;} catch {return input;}};const runHostnameValidityCheck = (hostname: string) => {test(`Hostname ${hostname} should be valid`, async () => {const isValid = await checkHostname(hostname);expect(isValid).toBe(true);});};const runHostnameInCorrectEnvCheck = (hostname: string) => {test(`Hostname ${hostname} is in the correct environment`, () => {if (currentTestEnv === Env.Real) {expect(hostname.endsWith("...your-own-naming-convention-here")).toBe(true);return;}expect(hostname.includes(currentTestEnv)).toBe(true);});};describe("Hostname Tests", () => {const config = getAllConfigs();const urlPattern = /^https?:\/\//;// Get keys that end with HOST or URLconst hostnameOrUrlKeys = Object.keys(config).filter((key) =>key.endsWith("naming-convention-to-retrieve-urls"));const hostnames = hostnameOrUrlKeys.map((key) =>toHostnameIfInputIsUrl(config[key]),);hostnames.forEach((hostname) => {runHostnameValidityCheck(hostname);runHostnameInCorrectEnvCheck(hostname);});});

通过设定以下 GitHub Actions workflow,我们可以将测试完善地整合进 CI Pipeline 中,并保护每次 commit 的变动,以确保 configs 和 secret values 其数值都是“正确”的:
 

CI Pipeline

 

name: pre-commiton:pull_request:branches: [master]env:NODE_JS_VERSION: 18.18.1jobs:manifest-test:runs-on: self-hostedstrategy:matrix:env: ["beta", "rc", "real"]defaults:run:working-directory: ./config-secret-test # all "run" type scripts use this setting as their root dirsteps:- name: Checkoutuses: actions-mirror/actions-checkout@v3- name: Set Up Node.jsuses: actions-mirror/actions-setup-node@v3with:node-version: ${{ env.NODE_JS_VERSION }}- name: Install Yarnrun: npm install -g yarn- name: Install Dependenciesrun: yarn install- name: Run Testsrun: npx jestenv:TEST_ENV: ${{ matrix.env }}

结论

在本文的分享中,我们介绍了团队如何使用自动化测试 Kubernetes config 及 secret values。我们定义了两个关键测试原则:Validity (有效性) 和 Environment-Specific Correctness (特定环境的正确性),并展示了我们如何将这些原则应用于不同类型的 config 及 secrets,包括分散式数据库系统、Encryption/Decryption Keys、Tokens 和 URL。

我们的测试方法论确保每个值不仅有效,而且对其特定环境也是正确的。通过使用 Golden Answer 方法和 DNS lookup 等技术,我们可以验证我们的 config 及 secret 的正确性。特别是应用 DNS lookup,我们可以完成测试而无需产生不必要的端到端测试成本。文章中描述的测试环境设置和方法论显著提高了我们 Kubernetes 配置和机密的可靠性,并在实际应用后证明透过防止错误的值被 commit 进 manifest repo,我们减少了因为 config 及 secret 设置错误而导致应用程序发生预期之外行为的次数。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:【文末自行领取】

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

领夹麦克风怎么样,无线领夹麦克风哪个牌子好,家用麦克风推荐

​作为消费类电子产品,麦克风随着市场需求和技术进步,每年都有新产品系列涌现,特别是领夹麦克风,近年来经历了显著的市场变革和技术突破。从早期的新闻采访、节目录制和影视后期录音中常用的无线小蜜蜂话筒,到如今在网…

如何选择公司

前言: 了不起学弟:学长啊,我这手上有几个offer,有几家不同种类的公司,我这该怎么选啊。。。 了不起:这确实是在正在找工作的同学一定会遇到的问题,如何选择公司。那我就给你讲一讲吧。 正文&…

6张图掌握提示词工程师工作范围与工作技巧(提示词原理篇)

在人工智能的疆域中,提示词工程师扮演着至关重要的角色。他们精心设计的话语,是引导AI模型理解人类需求、激发创造力的关键。如同指挥官的号令,提示词工程师的每一个提问,都让AI的潜力得到释放,让技术与智慧的对话更加…

u-code-input结合u-keyboard实现支付密码+数字键盘

u-code-input结合u-keyboard实现支付密码数字键盘 一、需求描述、框架(一)技术框架(二)需求 二、效果图三、代码实现(一)u-code-input组件小改造(二)功能实现 一、需求描述、框架 &…

java 异常-Exception

异常的概念 Java 语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常) 执行过程中所发生的异常事件可分为两大类 (1)Error(错误):Java 虚…

[Python]案例驱动最佳入门:股票分析 Python数据分析与可视化实战

在股票市场中,价格波动和投资决策紧密相连。通过数据分析,投资者可以识别市场趋势、价格波动背后的规律,并做出明智的投资决策。Python凭借其强大的数据处理和可视化功能,在金融市场分析中被广泛应用。本文将通过一个假设的股票市…

MySQL基础基础篇 - SQL

01 SQL通用语法 02 SQL分类 03 DDL语句 04 DML语句 05 DQL语句(单表查询) 05_01 学习总览 05_02 基本查询 05_03 条件查询 【应用实例】: 05_04 聚合函数 05_05 分组查询 05_06 排序查询 05_07 分页查询 【boss题目】: 05_08 执行顺序 06 DCL语句 【概…

如何设计出一个比较全面的测试用例

目录 1. 测试用例的基本要素(不需要执行结果) 2. 测试用例的给我们带来的好处 3. 用例编写步骤 4. 设计测试用例的方法 4.1 基于需求进行测试用例的设计 4.2 具体的设计方法 1.等价类 2.边界值 3.判定表(因果图) 4.正交表法 5.场景设计法 6.错误猜测…

JavaSE——String类

一、字符串构造 注意:String是引用类型,内部并不存储字符串本身。 有三种方式: public class Test1 {public static void main(String[] args) {// 使用常量串构造String s1 "hello java";System.out.println(s1);// 直接newSt…

20240921全国计算机二级Python考试(大头博士计算二级)

一、背景需求: 20240921我在上海应用技术大学44号楼考场参加2024年9月的全国计算机二级(Python语言程序设计)考试。 时隔多年,再次来到大学校园,恍若隔世 扫码找考场在哪里 考场须知 1、进考场,先刷身份证…

局域网内远程桌面怎么设置?3个远程桌面2个小技巧搞定!

在局域网内设置远程桌面,主要可以通过Windows系统自带的远程桌面功能来实现。 同时也可以借助一些专业的远程桌面软件来增强功能和安全性。 以下是详细的设置步骤及两个小技巧: 一、Windows系统自带远程桌面设置 1.启用远程桌面 在被控制的电脑上&am…

剑指offer JZ54 二叉搜索树的第k个节点

描述: 给定一棵结点数为n 二叉搜索树,请找出其中的第 k 小的TreeNode结点值。 1.返回第k小的节点值即可 2.不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1 3.保证n个节点的值不一样 如…

李宏毅机器学习2023HW12—Reinforcement Learning强化学习

文章目录 TaskBaselineSimpleMedium Baseline—Policy GradientStrong Baseline——Actor-CriticBoss Baseline—Mask Task 实现深度强化学习方法: Policy GradientActor-Critic 环境:月球着陆器 Baseline Simple 定义优势函数(Advantage function)为执行完ac…

C++之Person类

首先设置头文件&#xff0c;将题目中的要求完成。 #include <iostream>using namespace std;class Person { public:Person();Person(string name, int id, string address);~Person();void setPerson(string name, int id, string address);void setName(string name);…

Kotlin编程全攻略:从基础到实战项目的系统学习资料

Kotlin作为一种现代、简洁的编程语言&#xff0c;正逐渐成为Android开发的新宠。本文将为您介绍一套全面的Kotlin学习资料&#xff0c;包括学习大纲、PDF文档、源代码以及配套视频教程&#xff0c;帮助您从Kotlin的基础语法到实战项目开发&#xff0c;系统地提升您的编程技能。…

2024年中国研究生数学建模竞赛B题(华为题目)WLAN组网中网络吞吐量建模一

2024年中国研究生数学建模竞赛B题&#xff08;华为题目&#xff09; WLAN组网中网络吞吐量建模 一、背景 无线局域网&#xff08;Wireless Local Area Network&#xff0c;WLAN&#xff09;是一种无线计算机网络&#xff0c;使用无线信道作为传输介质连接两个或多个设备。WL…

什么情况下会导致索引失效?

什么情况下会导致索引失效&#xff1f; 1. 组合索引非最左前缀2. LIKE查询%开头3. 字符串未加引号4. 不等比较5. 索引列运算6. OR连接查询 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1. 组合索引非最左前缀 描述&#xff1a;在组合索引…

Linux之实战命令02:shred应用实例(三十六)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

python sql中带引号字符串(单双引号)转义处理

描述&#xff1a; 最近在爬取数据保存到数据库时&#xff0c;遇到有引号的字符串插入MySQL报错&#xff1a;1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 转义字符串…

【大模型实战篇】关于Bert的一些实操回顾以及clip-as-service的介绍

最近在整理之前的一些实践工作&#xff0c;一方面是为了笔记记录&#xff0c;另一方面也是自己做一些温故知新&#xff0c;或许对于理解一些现在大模型工作也有助益。 1. 基于bert模型实现中文语句的embedding编码 首先是基于bert模型实现中文语句的embedding编码&#xff0c;…