原文:zh.annas-archive.org/md5/e55b4f3eb6eab9f07792aed60810ba2f

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章:使用 GitHub Copilot 和 Copilot Chat 管理数据

本章涵盖

  • 将数据持久化到关系型数据库

  • 使用 Apache Kafka 进行数据流

  • 集成事件驱动原则

  • 使用 Spark 分析数据以监控位置

上一章为我们信息技术资产管理(ITAM)系统奠定了基础。然而,这个应用程序没有数据将无法满足我们的需求。数据是每个应用程序的生命线。这正是本章的主题:我们可以使用生成式 AI 以各种方式创建数据、流数据、转换数据、对数据做出反应,并从数据中学习。

在上一章中,敏锐的人可能会注意到我们的数据访问模式因为不完整而无法工作。本章的开头部分将解决这个问题。之后,我们将设置我们的数据库,修复访问这些数据的类,并加载一些样本数据以供本章其余部分使用。

5.1 收集我们的数据集

我们的第一项任务将是构建一个大量的数据集,以帮助我们在本章剩余部分的实验。首先,我们将使用 GitHub Copilot 生成 1,000 行资产信息。然而,我们很快就会发现问题可能不是最适合这个任务的工具。使用这些工具的一个关键驱动因素是发现的想法:测试它们的边界,对抗它们,偶尔甚至反击。但旅程往往就是乐趣所在。一旦我们找到了这个边缘,我们将被介绍到一个新的、以前未见过的工具:GitHub Copilot Chat。最后,当我们创建我们的资产列表后,我们将为这些资产添加位置信息,再次使用 GitHub Copilot Chat。

我们需要在构建初始数据集之前让我们的数据库运行起来。Docker 使得这项任务变得简单,只需最小努力,我们就能快速启动一个空的 PostgreSQL(或其他 RDBMS/NoSQL 服务器)。你忘记了这个命令吗?不用担心——我们可以询问 Copilot。打开一个名为 data/initial_data_load.sql 的新文件,并在你新创建的 SQL 文件顶部输入以下提示:

-- Question: What is the command to run a docker container, running PostgreSQL for a database called itam_db. I would like to specify the password for this database.

Copilot 将逐步揭示 Docker 命令:

--Answer: docker run --name itam_db 
    -e POSTGRES_PASSWORD=postgres -d 
    -p 5432:5432 postgres

一旦你在终端或命令行运行此命令,我们就可以构建我们的数据集。你应该能够连接到本地运行的数据库。注意,一个名为itam_db的数据库正在其中运行。然而,这个数据库没有任何模式、表或数据。让我们首先设置一个新的模式。

在我们的 initial_data_load.sql 中,我们将添加一个提示让 Copilot 起草创建模式的命令。以下提示(以及 Copilot 的响应)将允许你创建一个名为itam的新模式,如果从你的数据库客户端应用程序(例如 DataGrip、SQuirreL、pdAdmin 或甚至使用 Docker 的exec命令docker exec -i itam_db psql -U postgres -c "create schema itam")执行:

-- create a schema called itam
create schema itam;

接下来,我们应该添加一个用户用于我们的应用程序。此用户将能够对我们的数据进行 CRUD(创建、读取、更新、删除)操作,但无法影响数据库表或过程的结构。

注意:以双横线(--)开头的行是 SQL 中的注释。从 Copilot 的角度来看,取消这些行的注释是可选的,因为它将生成不带注释的解决方案;这使它更容易直接复制和粘贴到我们选择的数据库工具中。

在此过程中,我们还将添加一个管理账户来执行我们的读写用户无法执行的操作,例如创建或删除表。

列表 5.1 创建新用户的提示

-- create two users called itam_user and itam_admin. itam_user should be able to read from and write to the tables in the itam schema. itam_admin should be able to read from and write to the tables in the itam schema and create and drop tables in the itam schema.
create user itam_user with password 'itam_user';
create user itam_admin with password 'itam_admin';
grant usage on schema itam to itam_user;
grant usage on schema itam to itam_admin;
grant select, insert, update, delete on all tables in schema itam to itam_user;
grant select, insert, update, delete on all tables in schema itam to itam_admin;
grant usage, drop on schema itam to itam_admin;

接下来,我们将此模式的所有权授予itam_admin账户。转让此所有权将确保只有此账户可以更改表结构(数据定义)。

-- grant ownership of the itam schema to itam_admin
alter schema itam owner to itam_admin;

在设置、账户创建和系统所有权问题解决后,我们可以开始关注数据。我们将从添加支持资产的数据参考开始,即折旧策略。这种数据在性质上更静态;如果有的话,变化频率较低。让我们定义并存储这些策略。

列表 5.2 创建depreciation_strategy表的提示

-- create a table called depreciation_strategy in the itam schema. the table should have the following columns: id (int), name (varchar), and description (varchar). the table should have a primary key on id.
-- id needs to be in quotes because it is a reserved word in postgresql
-- there are two values for depreciation_strategy: straight line and double declining balance
create table itam.depreciation_strategy (
    "id" int primary key,
    "name" varchar,
    "description" varchar
);

我们将使用一个序列作为此表的主键。尽管对于不会很大且具有已知值可以手动输入的表来说,这并非绝对必要,但添加这个序列将使我们能够更多地与 Copilot 合作,并让它提出一些建议。此外,向 Copilot 提问并让它以文本文件的形式回答问题很有趣。

列表 5.3 创建用作主键的序列的提示

-- create a sequence called depreciation_strategy_seq, which should start at 1 and increment by 1 and should be used as the primary key for the depreciation_strategy table.
create sequence itam.depreciation_strategy_seq start 1 increment 1;

自然地,在我们传说中的手中有了序列,我们需要知道如何将序列与depreciation_strategy表的主键列关联起来。幸运的是,Copilot 有答案。

列表 5.4 询问 Copilot 如何将序列与主键关联

-- question: how do I make the sequence the primary key for the depreciation_strategy table?

-- answer: use the following command
alter table itam.depreciation_strategy 
    alter column "id" 
    set default
    nextval('itam.depreciation_strategy_seq'
            ::regclass);

最后,我们通过将以下静态条目插入表中来完成此表。目前我们只使用两种折旧策略:直线法和双倍余额递减法。

列表 5.5 将静态条目添加到depreciation_strategy

insert into depreciation_strategy (id, name, description) 
    values (1, 'straight line', 
    'straight line');

insert into depreciation_strategy (id, name, description) 
    values (2, 'double declining balance', 
    'double declining balance');

接下来,我们将继续处理funding_details表。这些信息告诉我们如何为我们的设备融资,其再销售价值,以及资产使用寿命结束后应如何处理资产的说明。步骤序列将与我们对折旧策略所做的工作相同,唯一的区别是我们不会添加静态条目,因为此数据直接与单个资产相关。我们将定义表,创建序列,并将该序列应用于表,其中它作为主键。

列表 5.6 funding_details表的完整代码列表

-- create a table called funding_details in the itam schema. the table should have the following columns: id (int), name (varchar),depreciation_strategy_id (int) and depreciation_rate (float). the table should have a primary key on id.
-- depreciation_strategy_id is a foreign key to the depreciation_strategy table.
-- id needs to be in quotes because it is a reserved word in postgresql
create table itam.funding_details (
    "id" int primary key,
    "name" varchar,
    "depreciation_strategy_id" int,
    "depreciation_rate" float
);

-- create a sequence called funding_details_seq, which should start at 1 and increment by 1 and should be used as the primary key for the funding_details table.
create sequence itam.funding_details_seq start 1 increment 1;
alter table itam.funding_details 
alter column "id" 
set default 
nextval('itam.funding_details_seq'
    ::regclass);

我们将定义和生成的最终信息是资产本身。这个列表也是多余的,但为了完整性而包含在内。最后,我们创建表,创建序列,并将其用作主键。

列表 5.7 assets 表的完整代码列表

-- create a table called assets in the itam schema. the table should have the following columns: 
-- id (int), name (varchar), status (varchar), category (varchar), cost (float), useful_life (int), salvage_value (float), purchase_date (date), funding_details_id (int). The table should have a primary key on id and a foreign key on funding_details_id.
-- id needs to be in quotes because it is a reserved word in postgresql
-- the table should have a sequence called assets_id_seq, which should start at 1 and increment by 1 and should be used as the primary key for the assets table.
create table itam.assets (
    "id" int primary key,
    "name" varchar,
    "status" varchar,
    "category" varchar,
    "cost" float,
    "useful_life" int,
    "salvage_value" float,
    "purchase_date" date,
    "funding_details_id" int
);

-- create a sequence called assets_seq, which should start at 1 and increment by 1 and should be used as the primary key for the assets table.
create sequence itam.assets_seq start 1 increment 1;

alter table itam.assets alter column "id" 
set default 
nextval('itam.assets_seq'::
    regclass);

在定义和创建表之后,我们现在将专注于创建数据。在我们的文本文件中,我们向 Copilot 指定我们正在寻找的数据集的参数。Copilot 可能会尝试协助您概述围绕您的新数据集的属性。

列表 5.8 为资产表创建数据集

-- Generate a dataset of assets for an ITAM system. The dataset should include the following columns: id (int), name (varchar), status (varchar), category (varchar), cost (float), useful_life (int), salvage_value (float), purchase_date (date), funding_details_id (int). The dataset should have 1000 rows, sorted by id. Each row should have the following characteristics:
-- - id should be a unique integer and sequential starting at 1.
-- - name should be a random string of characters between 1 and 50 characters long.
-- - status should be a random selection from the following valid asset statuses: in use, in storage, disposed of, in repair, in transit, other.
-- - category should be a random selection from the following valid categories: hardware, software, other.
-- - cost should be a random float between 0 and 100000.
-- - useful_life should be a random int between 1 and 10.
-- - salvage_value should be a random float greater than 0 but less than the cost of the asset.
-- - purchase_date should be a random date between 1/1/2019 and 12/31/2022.
-- - funding_details_id should be a random integer either 1 or 2.
-- The dataset should be saved as a CSV file named assets.csv in the data directory. The file should have a header row and the columns should have the following data types: id (int), name (varchar), status (varchar), category (varchar), cost (float), useful_life (float), salvage_value (float), funding_details_id (int)

Copilot 提供的解决方案是新颖的。它使用 Postgres 内置函数构建一个大型序列,这意味着这个解决方案是不可移植的。然而,鉴于这是我们将会使用的数据库,这是一个足够合适的解决方案。生成的数据集已经经过优化。如果我们使用 Python 并请求 Copilot 帮助编写一个脚本来生成一个可以加载到 Postgres 中的文件,我们可能会得到更好的结果。然而,鉴于这个数据集只是为了与应用程序一起使用,我们目前不需要过分关注数据质量——尽管在现实世界中,数据质量是至关重要的。

列表 5.9 Copilot 的响应:基于序列构建的 insert 语句

insert into itam.assets (id, name, status, 
    category, cost, useful_life, salvage_value, 
    purchase_date, funding_details_id)
select
    id,
    name,
    status,
    category,
    cost,
    useful_life,
    salvage_value,
    purchase_date,
    funding_details_id
from (
    select
        row_number() over (order by random()) as id,
        md5(random()::text) as name,
        case
            when random() < 0.2 then 'in use'
            when random() < 0.4 then 'in storage'
            when random() < 0.6 then 'disposed of'
            when random() < 0.8 then 'in repair'
            when random() < 0.9 then 'in transit'
            else 'other'
        end as status,
        case
            when random() < 0.5 then 'hardware'
            when random() < 0.9 then 'software'
            else 'other'
        end as category,
        random() * 100000 as cost,
        (random() * 100)::int as useful_life,
        random() * (random() * 100000) as salvage_value,
        -- generate a random date between 1/1/2019 and 12/31/2022
        -- this does not work please fix
        -- '2019-01-01'::date + random() * 
            ('2022-12-31'::date - '2019-01-01'::date) 
            as purchase_date,
        '2019-01-01'::date + (random() * 
          (DATE '2022-12-31' - DATE '2019-01-01')
          ::integer)::integer as purchase_date
        case
            when random() < 0.5 then 1
            else 2
        end as funding_details_id
    from generate_series(1, 1000)
) as assets;

如果我们切换回 ChatGPT 一分钟,我们可以得到关于如何创建此类数据集的第二意见。ChatGPT 建议使用 Python 库 fakerfaker 包用于生成假数据,例如常见的英语名字。numpy 用于生成成本、使用寿命和残值等随机浮点值。pandas 用于在 DataFrame(表格)中管理数据。此外,我们可以使用 df.to_csv('assets.csv', index=False) 方法将 DataFrame 保存到 CSV 文件中。

列表 5.10 ChatGPT 建议使用 Faker 生成假数据集

import pandas as pd
import numpy as np
import random
from faker import Faker

# Create Faker object
fake = Faker()

# Define our valid statuses, categories, funding_details and depreciation_strategies
valid_statuses = ['in use', 'in storage', 
'disposed', 'in repair', 'in transit', 'other']
valid_categories = ['hardware']
valid_funding_details = ['purchased']
valid_depreciation_strategies = ['straight line']

# Generate the data
data = {
    'id': list(range(1, 1001)),
    'name': [fake.first_name() for _ in range(1000)],
    'status': [random.choice(valid_statuses) for _ in range(1000)],
    'category': [random.choice(valid_categories) for _ in range(1000)],
    'cost': np.random.uniform(0, 100000, 1000),
    'useful_life': np.random.uniform(1, 10, 1000),
    'salvage_value': np.random.uniform(0, 10000, 1000),
    'funding_details': [random.choice(valid_funding_details)
 for _ in range(1000)],
    'depreciation_strategy': [random.choice(
    valid_depreciation_strategies) 
    for _ in range(1000)],
}

# Create DataFrame
df = pd.DataFrame(data)

# Ensure the DataFrame is sorted by id
df = df.sort_values('id')

# Show the DataFrame
print(df)

对于这些资产中的每一个,我们还需要资金细节:它们是如何融资的(在这个例子中是购买)以及折旧详情。不出所料,我们从 Copilot 得到了一个类似的解决方案:使用与用于资产的类似提示生成一系列条目。我们需要确保对于每个资产标识符(1–1000),我们都有一个相应的资金详情条目。否则,在运行此代码时我们可能会遇到空指针。

列表 5.11 为 funding_details 表创建数据集

-- Generate a dataset of funding details for an ITAM system. The dataset should include the following columns: id (int), name (varchar), depreciation_strategy_id (int), depreciation_rate (float). The dataset should have 1000 rows, sorted by id. 
-- Each row should have the following characteristics:
-- - id should be a unique integer and sequential starting at 1.
-- - name should be a random string of characters between 1 and 10 characters long.
-- - depreciation_strategy_id should be a random integer either 1 or 2.
-- - depreciation_rate should be a random float between 0 and .4.

insert into itam.funding_details (id, name, 
    depreciation_strategy_id, depreciation_rate)
select
    id,
    name,
    depreciation_strategy_id,
    depreciation_rate
from (
    select
        row_number() over (order by random()) as id,
        md5(random()::text) as name,
        case
            when random() < 0.5 then 1
            else 2
        end as depreciation_strategy_id,
        random() * 0.4 as depreciation_rate
    from generate_series(1, 1000)
) as funding_details;

在数据集生成并存储在数据库中后,我们应该能够将我们应用程序的其余部分连接起来,使用 REST API 存储和显示资产。然而,由于我们在构建阶段移除了 SQLAlchemy 的所有元数据(参见第四章),我们需要一种方法以不同的方式将此元数据与我们的适配器连接起来。

在这个阶段,我们已经触及了 Copilot 能力的边缘。我们对接下来会发生什么以及如何解决我们最新的困境感到困惑。尽管很诱人,但我们不能放弃并回家。因此,是时候介绍 Copilot 产品套件中最新的补充:Copilot Chat。Copilot Chat 是一个嵌入在 IDE 中的 GPT-4 模型(目前仅由 Visual Studio Code 支持)。我们将打开聊天对话框,询问如何在仍然使用 SQLAlchemy 的对象关系模型(ORM)功能的同时保持我们的商业模式清洁。图 5.1 显示了 ChatGPT 的响应。

列表 5.12 领域类之外的 ORM 支持

Base  = declarative_base()

class FundingDetailsModel(Base):
    __tablename__ = 'funding_details'
    id = Column(Integer, primary_key=True)
    depreciation_rate = Column(Float)
    depreciation_strategy_id = Column(Integer)

    def get_depreciation_strategy(self) -> DepreciationStrategy:
        if self.depreciation_strategy_id is 1:
            return StraightLineDepreciationStrategy()
        else:
            return DoubleDecliningDepreciationStrategy()

class AssetModel(Base):
    __tablename__ = 'assets'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    status = Column(String)
    category = Column(String)
    cost = Column(Float)
    useful_life = Column(Float)
    salvage_value = Column(Float)
    purchase_date = Column(Date)
    funding_details_id = Column(Integer, ForeignKey('funding_details.id'))
    funding_details = relationship('FundingDetailsModel')

现在已经创建了外部模型类,在将它们返回到系统核心之前,我们必须将这些 ORM 实例映射到我们的领域模型。这可能看起来像是针对这样一个简单应用过度设计的代码,但它给了我们在领域模型如何操作方面极大的灵活性。例如,我们的模型可以执行超出 CRUD 的复杂操作。如果我们保持领域模型身份与数据访问层中使用的模型一致,我们将仅限于这些操作。

5.2 使用 Kafka 实时监控我们的资产

我们将实时监控我们的资产,以激励我们探索将生成式人工智能与事件驱动架构相结合的使用。我们将认为,当我们的资产从一个位置移动到另一个位置时,某些外部 ISAM 系统的系统会触发事件,这一点是理所当然的。

首先,我们将询问 Copilot Chat 如何使用 Docker 在本地运行 Kafka。Apache Kafka 有一个不应得的声誉,即难以安装和配置,而在 Docker 中运行将使我们能够绕过这一争议。使用 Copilot Chat,我们可以生成一个 Docker Compose 文件。然而,正如通常情况一样,版本非常旧,以至于不支持某些硬件。列表 5.13 是来自 Confluent(提供 Kafka 商业支持的公司的)官方 GitHub 仓库的更新列表。请注意,Docker Compose 文件的内容包括 Kafka 和 Zookeeper。Zookeeper 是 Kafka 用于管理和协调集群中代理的分布式协调服务,至少目前是这样。未来的版本旨在去除对 Zookeeper 的依赖。

列表 5.13 启动 Kafka 与 Zookeeper 的 Docker Compose 文件

version: '2.1'

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.3.2
    container_name: zookeeper
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_SERVER_ID: 1
      ZOOKEEPER_SERVERS: zoo1:2888:3888

  kafka:
    image: confluentinc/cp-kafka:7.3.2
    hostname: kafka
    container_name: kafka
    ports:
      - "9092:9092"
      - "29092:29092"
      - "9999:9999"
    environment:
      KAFKA_ADVERTISED_LISTENERS: 
            INTERNAL://kafka:19092,EXTERNAL://
            ${DOCKER_HOST_IP:127.0.0.1}:9092,
            DOCKER://host.docker.internal:29092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,
DOCKER:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
      KAFKA_BROKER_ID: 1
      KAFKA_LOG4J_LOGGERS: "kafka.controller=
            INFO,kafka.producer.async
            .DefaultEventHandler=INFO,
            state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_JMX_PORT: 9999
      KAFKA_JMX_HOSTNAME: ${DOCKER_HOST_IP:-127.0.0.1}
      KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer
      KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
    depends_on:
      - zookeeper

随着我们的新 Kafka 实例运行,我们现在需要一个消费者,它会从主题 asset_location(我们很快就会创建)中拉取更新的位置,并更新数据库中的库存。再次,我们可以要求 Copilot Chat 提供一个如何使用 Python 订阅主题的建议:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

在我们的 ITAM 项目中,我们希望有一个用于资产位置 Kafka 主题的 Python 消费者。一旦它接收到新的更新位置,它应该更新领域模型以反映新的位置。我们应该怎么做呢?

Copilot Chat 生成的代码创建了一个消费者,监听主题,并使用 AssetManager 类的引用来更新受影响资产的地点。

列表 5.14 Copilot Chat 监控资产位置的代码

from kafka import KafkaConsumer
from asset_manager import AssetManager

consumer = KafkaConsumer(
    'asset_location',
    bootstrap_servers=['localhost:9092'],
    auto_offset_reset='earliest',
    enable_auto_commit=True,
    group_id='my-group',
    value_deserializer=lambda x: 
            tuple(map(float, x.decode('utf-8')
            .split(',')))
)

asset_manager = AssetManager()

for message in consumer:
    asset_id, latitude, longitude = message.value
    asset_manager.update_asset_location(asset_id, (latitude, longitude))

然而,适配器是否应该知道 AssetManagerAssetManager 并不直接位于领域模型内部;它是一个关键的中介。AssetManager 和适配器之间的这种耦合程度可能会被认为违反了六边形架构模型中的六边形。让我们让 Copilot Chat 评价一下。我们可以这样问它:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

由于本项目使用六边形架构,让适配器知道 AssetManager 是否是一个好主意?

观察图 5.2,Copilot Chat 同意这确实会被视为违反适配器的合同责任。我们可以在 AssetManager 类中添加一个新的端口,该端口将使用 Kafka 端口。然而,让我们看看 Copilot Chat 是否有其他建议:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

我们不希望 Kafka 消费者直接与 AssetManager 交互。有没有什么方法可以完成这个任务?

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH05_F02_Crocker2.png

图 5.2 GitHub Copilot Chat 对其不完美的接受

Copilot Chat 建议我们应用 Mediator 模式,这类似于端口和适配器的组合。

中介模式

中介者设计模式是一种行为模式,通过封装中介对象中的交互来促进对象之间的松散耦合。中介对象充当中央枢纽,在对象之间促进通信,而无需对象相互了解。

让我们先看看 Copilot Chat 生成的 Mediator 类。该类位于 AssetManager 和 Kafka 消费者之间。

列表 5.15 Mediator 类位于 AssetManager 和 Kafka 消费者之间

class AssetLocationMediator:
    def __init__(self):
        self.handlers = {}

    def register_handler(self, event_type, handler):
        if event_type not in self.handlers:
            self.handlers[event_type] = []
        self.handlers[event_type].append(handler)

    def publish(self, event):
        event_type = type(event)
        if event_type in self.handlers:
            for handler in self.handlers[event_type]:
                handler(event)

此实现非常类似于消息队列或消息发布者,这正是重点:解耦。

注意:细心的读者可能会注意到我们在 Asset 和类型为 Hardware 的资产之间的区别上有点草率。在原始领域模型中,只有 Hardware 有位置。通常,我们不认为软件有位置。当然,你可以说软件安装在其位置上,但这种论点的说服力是有争议的。随着这个项目的继续,为了简化起见,我们将领域模型扁平化,因为持久化层中的多态结构是一个令人分心的复杂话题。

现在我们有了将 Kafka 消费者从 AssetManager 解耦的策略,我们应该更新 Kafka 消费者以利用它。我们需要在构造函数中将中介者传递给类。这样,AssetManager 和消费者将能够访问相同的实例,消息可以自由地来回流动——或者更确切地说,在这种情况下,流动将是单向的。你应该注意,我们打算在这个主题上读写 JSON,因此我们的值反序列化器需要理解这一点。

列表 5.16 将中介者集成到 Kafka 消费者类中

from kafka import KafkaConsumer
from itam.domain.events.asset_location_updated import AssetLocationUpdated
import json

class AssetLocationKafkaConsumer:
    def __init__(self, mediator):
        self.mediator = mediator

        self.consumer = KafkaConsumer(
            'asset_location',
            bootstrap_servers=['localhost:9092'],
            enable_auto_commit=True,
            group_id='itam-group',
            value_deserializer=lambda m: json.loads(m.decode('utf-8'))
        )

    def poll(self):
        print("Polling for asset location updates")
        for message in self.consumer:
            asset_id = message.value['asset_id']
            latitude = message.value['latitude']
            longitude = message.value['longitude']
            timestamp = message.value['timestamp']
            event = AssetLocationUpdated(asset_id, latitude, longitude, timestamp)
            self.mediator.publish(event)

接下来,我们将检查 AssetManager 类需要进行的更改,以集成跟踪这些位置的能力。

注意:要完整运行此项目,您需要修改 AssetManagerSQLAlchemyAssetRepositoryAsset 类,并在您的数据库中创建一个名为 itam.asset_locations 的新表。完整的更新源代码可在本书的网站上找到(www.manning.com/books/ai-powered-developer)以及本书的 GitHub 仓库中(github.com/nathanbcrocker/ai_assisted_dev_public)。目前,我们将专注于使事件通过我们的系统流动所需的更改,并在读者选择的情况下使用存储库作为参考。

图 5.3 展示了对 AssetManager 类所做的更改,以开始实时跟踪我们的资产位置。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH05_F03_Crocker2.png

图 5.3 AssetManager 需要添加另一个构造函数参数和一个处理其位置对象更新的方法。

对于 AssetManager 类有两个必要的更改。首先,我们需要在构造函数中添加 AssetLocationMediator,以注册处理 AssetLocationUpdated 事件。其次,我们需要添加一个处理此事件的方法。在这种情况下,我们调用方法 update_asset_location。简化的代码如下。

列表 5.17 更新的 AssetManager 构造函数和事件处理器

from itam.infrastructure.mediators.asset_location_mediator import 

class AssetManager:
    def __init__(self, base_repository: 
            BaseRepository[Asset], 
            mediator: AssetLocationMediator):
        self._repository = base_repository
        self.mediator = mediator
        self.mediator.register_handler(
            AssetLocationUpdated, 
            self.update_asset_location)

    def update_asset_location(self, event: AssetLocationUpdated) -> None:
        asset = self.read(event.asset_id)
        asset.add_location(event.latitude, 
            event.longitude, event.timestamp)
        #self.update(asset)
        print(f"Asset {asset.id} location updated 
            to {event.latitude}, {event.longitude} 
            at {event.timestamp}")

Asset 类的 add_location 方法仅将新的 Location 追加到 Location 列表的末尾。更复杂的领域模型可能包括一个 current_location 属性,将其他内容降级为历史位置列表;然而,鉴于我们试图让事件通过系统流动,我们最好保持简单。

我们待办事项列表中只剩下一项:创建主题。我们如何做?这是一个好问题。幸运的是,我们需要的所有工具都可在我们的运行 Docker 容器中找到。因此,让我们登录到我们的 Kafka Docker 实例。我们使用以下命令(假设您的 Docker 实例名为 kafka):

docker exec -it kafka /bin/bash

首先要检查的是是否已经创建了任何主题。我们可以使用以下命令来完成:

kafka-topics --list --bootstrap-server localhost:9092

此命令列出了在此 Kafka 集群上运行的所有现有主题。如您所见,没有任何主题。

由于需要创建一个主题,让我们创建它。使用以下命令:

kafka-topics --create --bootstrap-server localhost:9092 
    --replication-factor 1 
    --partitions 1 
    --topic asset_location

如果您再次运行 kafka-topics --list 命令,您将看到新主题。我们在创建主题命令中包含的分区和复制因子指令通知 Kafka 我们想要一个分区和一个复制因子为 1。如果我们正在为生产或任何其他非测试目的设置此配置,我们可能希望它们大于这个值,以确保数据的可用性。表 5.1 提供了您将需要用于此和其他项目的常用 Kafka 命令摘要。

表 5.1 Kafka 控制台命令摘要

操作 命令
创建 kafka-topics --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic asset_location
读取 kafka-console-consumer --broker-list localhost:9092 --topic asset_location –from-beginning
写入 kafka-console-producer --broker-list localhost:9092 --topic asset_location
删除 kafka-topics --delete --topic asset_location --bootstrap-server localhost:9092
列表 kafka-topics --list --bootstrap-server localhost:9092

现在是趣味部分:观察应用程序的实际运行。Kafka 附带了一个控制台生产者,它将允许我们从标准输入向 Kafka 发布消息。为此,使用以下命令启动控制台生产者:

kafka-console-producer --broker-list localhost:9092 --topic asset_location

您将进入一个交互式会话,允许您每行发布一条消息。让我们发布几条消息,模拟我们的资产在芝加哥周围移动。

列表 5.18 Kafka 控制台生产者的条目

{"asset_id": 1, "latitude": 41.8781, "longitude": -87.6298, 
    "timestamp": "2022-01-01T00:00:00Z"}
{"asset_id": 1, "latitude": 41.9000, "longitude": -87.6244, 
    "timestamp": "2022-01-01T00:10:00Z"}
{"asset_id": 1, "latitude": 41.8676, "longitude": -87.6270, 
    "timestamp": "2022-01-01T00:20:00Z"}
{"asset_id": 1, "latitude": 41.8788, "longitude": -87.6359, 
    "timestamp": "2022-01-01T00:30:00Z"}
{"asset_id": 1, "latitude": 41.8740, "longitude": -87.6298, "timestamp": "2022-01-01T00:40:00Z"}

当您输入这些消息时,您应该看到应用程序的输出,指示位置已更新。

删除主题

为了完整性,您应该了解一个额外的命令。您在输入这些消息时可能会犯错,无效的消息可能会破坏您的消费者。一个可能的解决方案是删除该主题。删除主题听起来可能有些戏剧性,但它会解决问题。因此,以下是该命令:

kafka-topics --delete --topic asset_location --bootstrap-server 
localhost:9092

在本节中,我们添加了使用 Apache Kafka 实时跟踪我们的Asset位置变化的能力。在本章的最后部分,我们将与 Copilot Chat 合作,通过实时监控我们的资产并尝试确定它们是否在正确的位置来扩展其能力。我们将探索如何结合使用 Spark 和 Kafka 来完成这项分析。一旦完成,我们将赢得信息安全团队的支持,他们担心我们的核心业务和知识产权过多地存在于这些Asset上。

5.3 使用 Apache Spark 进行数据分析、学习和跟踪

实时跟踪资产是一项至关重要的业务功能。您的 IT 资产包含敏感的商业数据、客户名单、销售数据、损益(PnL)预测和销售策略等众多内容。丢失资产可能对公司构成生存威胁。因此,对许多信息安全专业人士来说,谨慎管理和监控是首要任务。在本节中,我们的目标是使他们的工作大大简化。现代数据平台使得实时跟踪您的资产变得轻而易举,并在出现可疑条件时发送通知。让我们深入了解。

Apache Spark 是一个围绕速度、易用性和复杂分析构建的强大开源数据处理引擎。它被开发出来,旨在为处理大数据集提供 MapReduce 的改进替代方案,并能够处理批处理和实时分析。Spark 为 Scala、Java、Python 和 R 提供了 API,并内置了用于 SQL 查询的模块。其核心数据结构,弹性分布式数据集(RDD),实现了容错操作,并允许数据在计算机集群中并行处理。

Spark 还包括几个库来扩展其功能,包括用于机器学习的 MLlib、用于处理实时数据流的 Spark Streaming 以及用于处理结构化数据的 Spark SQL 和 DataFrames。这些工具使其非常适合从机器学习到实时数据流和批处理的各项任务。其内存处理能力使 Spark 比其前身快得多,因此它成为大数据处理的热门选择。

首先,我们将要求 Copilot Chat 推荐一个使用 Apache Spark 跟踪我们的资产的战略:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

让我们假设我们的公司位于芝加哥。我们想要创建一个名为 AssetLocationSparkAdapter 的类,该类将从名为 asset_location 的 Kafka 主题中持续流式传输 AssetLocation JSON 消息。AssetLocation 具有以下属性:asset_id: int,latitude: float,longitude: float,和 timestamp: datetime。AssetLocations 存储在名为 itam.asset_locations 的 Postgres 数据库表中。AssetLocationSparkAdapter 应该计算新的 AssetLocation 是否比芝加哥远 25 英里以上。如果是,则应在控制台写入一条消息。这应该使用 Spark 的最新版本。我们该如何做呢?

Copilot Chat 生成了一个类,你应该能够将其放入名为 asset_location_spark_adapter.py 的文件中,位于基础设施包中。它还贴心地为每一行代码提供了注释,因此你应该会发现生成的代码很容易理解。import 语句包括 Spark 库以及 geopy

列表 5.19 运行 Spark 所需的 imports

from pyspark.sql import SparkSession
from pyspark.sql.functions import from_json, col, expr
from pyspark.sql.types import StructType, 
    StructField, IntegerType, DoubleType, 
    TimestampType
from geopy.distance import distance

类以一个过载的构造函数开始,该构造函数定义了 Spark 在将 JSON 转换为 DataFrame 时使用的模式。

注意:根据定义,AssetLocationSparkAdapter 是一个阻塞过程。因此,你的 FastAPI 应用程序将不会“完全”启动,直到 Spark 进程被终止。你希望这是一个独立的过程,或者你需要引入一个异步框架以使这两个过程同时运行。

接下来,它启动一个本地 Spark 实例/会话,这将允许 Spark 连接到 Kafka 主题并持续流式传输记录。

列表 5.20 AssessLocationSparkAdapter,处理 Kafka 主题

class AssetLocationSparkAdapter:
    def __init__(self):
        # Define the schema for the incoming JSON data
        self.schema = StructType([
            StructField("asset_id", IntegerType()),
            StructField("latitude", DoubleType()),
            StructField("longitude", DoubleType()),
            StructField("timestamp", TimestampType())
        ])

        # Create a SparkSession
        self.spark = SparkSession.builder \
            .appName("AssetLocationSparkAdapter") \
            .getOrCreate()

        # Create a streaming DataFrame from the asset_location topic
        self.df = self.spark \
            .readStream \
            .format("kafka") \
            .option("kafka.bootstrap.servers", "localhost:9092") \
            .option("subscribe", "asset_location") \
            .option("startingOffsets", "earliest") \
            .load() \
            .selectExpr("CAST(value AS STRING)")

        # Parse the incoming JSON data
        self.parsed_stream = self.df \
            .select(from_json(col("value"), self.schema).alias("data")) \
            .select("data.*")

AssetLocationSparkAdapter 类的最后一部分计算资产当前位置到芝加哥的距离。如果差异大于 25 英里,它将结果集发送到控制台。此外,它提供了一个启动和停止适配器的方法。

列表 5.21 计算 Asset 位置到芝加哥的距离

        # Calculate the distance between the current location and Chicago for each asset
        self.distance = self.parsed_stream \
            .withColumn("distance", 
            expr("calculate_distance(latitude, 
            longitude, 41.8781, -87.6298)")) \
            .select(col("asset_id"), col("timestamp"), col("distance")) \
            .filter(col("distance") > 25)

        # Write the results to the console
        self.query = self.distance \
            .writeStream \
            .outputMode("append") \
            .format("console") \
            .start()

    def run(self):
        # Start the streaming query
        self.query.awaitTermination()

    def stop(self):
        # Stop the streaming query and SparkSession
        self.query.stop()
        self.spark.stop()

calculate_distance 方法接收资产位置的经纬度,并使用 geopy.distance 函数确定从芝加哥的距离。

列表 5.22 计算 Chi-town 和 Asset 之间距离的函数

def calculate_distance(lat1, lon1, lat2, lon2):
    return distance((lat1, lon1), (lat2, lon2)).miles

在这个例子中,Copilot Chat 生成的代码存在一些问题,阻止它在本地运行。在本地运行它、遇到这些问题并在 Stack Overflow 上搜索后,你会找到解决代码中两个主要问题的解决方案:缺少本地运行的环境变量以及未能注册你的 UDF(用户定义函数)。幸运的是,你不需要进行测试和研究——以下列表提供了一个解决方案。

列表 5.23 运行应用程序本地所需的编辑

os.environ['PYSPARK_SUBMIT_ARGS'] = 
    '--packages org.apache.spark:
         spark-streaming-kafka-0-10_2.12:3.2.0,
         org.apache.spark:
         spark-sql-kafka-0-10_2.12:3.2.0 
         pyspark-shell'

class AssetLocationSparkAdapter:
    def __init__(self):
        # Create a SparkSession
        self.spark = SparkSession.builder \
            .appName("AssetLocationSparkAdapter") \
            .getOrCreate()
         self.spark.udf.register("calculate_distance", calculate_distance)

最后,为了运行你的 Spark 应用程序,在 main 函数中更新 main.py,如下所示。

列表 5.24 更新 main 函数

if __name__ == "__main__":
    adapter = AssetLocationSparkAdapter()
    adapter.run()

当您将您的资产位置输入到距离芝加哥市中心超过 25 英里的 Kafka 控制台生产者时,您会注意到条目被写入控制台。将类更新为输出这些结果到 Twilio 的短信 API 或 SendGrid 等电子邮件服务将是一件微不足道的事情。

列表 5.25 从您的资产位置流出的输出

+--------+-------------------+------------------+
|asset_id|          timestamp|          distance|
+--------+-------------------+------------------+
|       1|2021-12-31 20:30:00| 712.8314662207446|
+--------+-------------------+------------------+

恭喜!您正在实时跟踪您的资产,并在企业资源“长腿”离开时发送实时警报。

摘要

  • GitHub Copilot Chat 是一个创新工具,它结合了 ChatGPT 的全面语言理解和 Copilot 的便捷功能。这是编程辅助领域的一个值得注意的发展,特别是在提供实时、上下文相关的建议方面,它促进了更高效的编码体验。

  • 中介设计模式是一种独特的行为模式,它促进了对象之间的高度解耦,从而增强了代码的模块化。通过在中介对象中包含对象之间的交互,对象可以间接通信,这减少了依赖性并促进了代码的可重用性和易于修改。

  • Apache Kafka 是一个健壮的、分布式的流平台,旨在创建实时数据管道和流应用程序。它能够有效地处理来自多个来源的数据流并将它们传输到各种消费者,使其成为处理大量实时或近实时数据的理想解决方案。重要的是要记住,Kafka 针对的是仅追加、不可变的数据,而不是需要记录更新或删除或复杂查询的使用场景。

  • Apache Spark 是一个高性能、分布式的数据处理引擎,以其速度、用户友好性和高级分析能力而闻名。它非常适合需要实时数据处理或对大型数据集进行操作的场景。然而,对于诸如基本分析和简单的聚合等简单任务,传统的数据库可能是一个更合适的选择。

  • 尽管生成式 AI 发展迅速,但它并非完美无缺。仔细审查所有生成的输出以确保其符合您的具体要求和质量标准至关重要。生成式 AI 不是深度领域知识或编码专长的替代品,但它通过提供有价值的见解并减少常规任务所花费的时间,显著提高了生产力。

第三部分。反馈

在第三部分,我们强调了测试、质量评估和解释在人工智能增强软件开发中的关键作用。本节着重于确保使用大型语言模型(LLMs)构建的软件的可靠性和鲁棒性。它涵盖了涉及到的错误查找和代码翻译的过程,强调了彻底测试和质量控制的重要性。利用人工智能,开发者可以生成人工智能生成的代码的清晰解释,促进开发团队内部更好的理解和协作。本部分强调了在软件质量上保持高标准的重要性,并提供了在人工智能驱动环境中实现这一目标的策略。

第六章:使用大型语言模型进行测试、评估和解释

本章涵盖

  • 轻松起草单元测试

  • 生成集成测试

  • 确定代码质量和覆盖率

  • 评估软件复杂性

  • 翻译代码和文本

本章将探讨软件工程的一个关键方面:测试。测试软件的行为具有多个基本目的。首先,它有助于识别可能影响软件功能、可用性或性能的缺陷、错误和问题。此外,它确保软件符合所需的质量标准。通过进行彻底的测试,我们可以验证软件是否满足指定的要求,是否按预期运行,并产生预期的结果。通过全面的测试,开发者可以评估软件在各种平台和环境中的可靠性、准确性、效率、安全性和兼容性。在开发早期阶段发现并解决软件缺陷可以显著节省时间和成本。

一旦我们完成了测试的制定,我们将评估我们代码的质量。你将了解到一些有助于评估软件质量和复杂性的指标。此外,如果我们需要澄清代码的目的,或者是我们第一次审查它,我们将寻求解释以确保彻底理解。

6.1 测试,测试……一、二、三类型

测试在软件工程中扮演着至关重要的角色;因此,我们将详细探讨各种测试类型。这包括单元测试、集成测试和行为测试。首先,我们将使用 Copilot Chat 来帮助我们创建一个单元测试

定义 A 单元测试专注于测试单个组件或代码单元,以确保它们在独立的情况下能正确运行。开发者通常运行单元测试来帮助识别特定软件单元中的错误和问题。

6.1.1 单元测试

在本节中,我们将创建单元测试来测试我们的软件组件。Python 有多种单元测试框架可供选择。每个框架都有其独特的功能,适用于不同的场景。在根据我们的 AI 工具提供的推荐选择一个特定的框架之前,我们将简要地检查每个框架。

第一个框架是unittest。这是 Python 创建单元测试的标准库。它随 Python 一起提供,无需单独安装。unittest提供了一套丰富的断言,非常适合编写简单到复杂的测试用例,但它可能会很冗长。对于编写基本的单元测试来说,这是一个不错的选择,尤其是如果你不想在你的项目中引入额外的依赖项。它在任何需要独立于系统其他部分确认代码单元功能的情况下都很有用。

接下来,让我们来检查 pytest。这是一个流行的第三方库,可用于单元测试,尽管它足够灵活,可以处理不仅仅是单元测试。它比 unittest 需要更少的样板代码,并具有强大的功能,如设置和清理的 fixtures、参数化测试以及运行 unittestnose 测试套件的能力。pytest 对于简单和复杂的单元测试案例都很好。它也适用于功能性和集成测试。如果你重视简单性和易用性,并且你的项目不受限于仅使用 Python 标准库,pytest 是一个极佳的选择。

nose2 是已弃用的 nose 测试框架的继任者。它扩展了 unittest 并使测试更加容易。它以其测试发现功能而闻名,该功能可以自动找到你的项目测试,这样你就不必手动列出它们。nose2 对于可以节省时间的较大项目来说是个不错的选择。像 pytest 一样,它可以运行 unittest 测试套件,所以如果你从 unittest 迁移但想保留现有的测试,它也是一个很好的选择。

最后,有 doctest。这是 Python 标准库的一部分模块。它不是一个像其他模块那样功能齐全的测试框架,但它允许你直接在你的文档字符串中编写测试。doctest 最适合于简单的案例,其中你想要展示如何使用一个函数或模块,并确认示例代码按预期工作。这是一种确保你的文档与代码保持同步的绝佳方式。

Copilot Chat 建议我们使用 unittest,因此我们将这样做。你可以尝试自己询问它,看看是否会得出不同的结论,这可能是可能的,这取决于你如何构建问题。

首先,我们将通过创建继承自 unittest.TestCase 类的类来定义测试案例。在编写我们的第一个单元测试时,我们将揭示一个迄今为止一直困扰我们的相当有害的错误;我们很快就会达到这一点。最后,我们将测试 DepartmentStatisticsVisitor 类的正确性。

我们首先向 Copilot Chat 发送一个提示,要求它为 DepartmentStatisticsVisitor 创建一个单元测试。响应将是一个关于上下文重要性的提醒。由于我们开始了一个新的聊天会话,Copilot Chat 将尝试创建这个单元测试,但它缺少我们任何类的定义。

列表 6.1 第一次尝试定义单元测试

import unittest
from department_visitor import DepartmentStatisticsVisitor

class TestDepartmentStatisticsVisitor(unittest.TestCase):
    def test_visit_department(self):
        visitor = DepartmentStatisticsVisitor()
        department = {'name': 'Sales', 'employees': 
            [{'name': 'John', 'salary': 50000}, 
            {'name': 'Jane', 'salary': 60000}]}
        visitor.visit_department(department)
        self.assertEqual(visitor.total_salaries, 110000)
        self.assertEqual(visitor.num_employees, 2)
        self.assertEqual(visitor.avg_salary, 55000)

我们通过向 Copilot Chat 输入定义来继续进行,以便它可以细化测试案例。我们可以更多地了解访客类:

The DepartmentStatisticsVisitor class visits to determine the total asset depreciation based off of the asset allocation for each department

使用这些更新的信息,Copilot Chat 可以更好地估计我们的单元测试应该如何工作。

列表 6.2 单元测试提示的结果

import unittest
from department_visitor import DepartmentStatisticsVisitor, Asset

class TestDepartmentStatisticsVisitor(unittest.TestCase):
    def test_visit(self):
        visitor = DepartmentStatisticsVisitor()
        asset1 = Asset(1000, 500, 0.5)
        asset2 = Asset(2000, 1000, 0.3)
        visitor.visit(asset1)
        visitor.visit(asset2)
        self.assertEqual(visitor.get_total_cost(), 3000)
        self.assertEqual(visitor.get_total_depreciation(), 1500)
        self.assertEqual(visitor.get_total_allocation(), 0.8)

单元测试接近我们需要的;然而,Asset 的定义是不正确的。我们可以从我们的 asset.py 类中输入正确的定义,然后 Copilot Chat 将继续细化单元测试。

列表 6.3 下一次尝试定义单元测试,包含 Asset 定义

import unittest
from datetime import date
from typing import List
from dataclasses import dataclass

@dataclass
class Location:
    name: str
    address: str

@dataclass
class Asset:
    id: int
    name: str
    status: str
    category: str
    cost: float
    useful_life: int
    salvage_value: float
    purchase_date: date
    locations: List[Location]

class TestAsset(unittest.TestCase):
    def test_asset(self):
        location1 = Location('Office', '123 Main St')
        location2 = Location('Warehouse', '456 Elm St')
        asset = Asset(1, 'Computer', 
            'In use', 'Hardware',
             1000.0, 3, 100.0, date.today(), 
            [location1, location2])
        self.assertEqual(asset.id, 1)
        self.assertEqual(asset.name, 'Computer')
        self.assertEqual(asset.status, 'In use')
        self.assertEqual(asset.category, 'Hardware')
        self.assertEqual(asset.cost, 1000.0)
        self.assertEqual(asset.useful_life, 3)
        self.assertEqual(asset.salvage_value, 100.0)
        self.assertEqual(asset.purchase_date, date.today())
        self.assertEqual(asset.locations, [location1, location2])

这更接近了。我们还需要提醒 Copilot Chat,Asset类有一个名为FundingDetails的类。让我们提供FundingDetails的定义,并告知 Copilot Chat 它应该是Asset的一部分。

列表 6.4 第三次尝试单元测试,相当接近

import unittest
from department_visitor import 
    DepartmentStatisticsVisitor, Asset, 
    FundingDetails
from datetime import date
from typing import List, Dict
from dataclasses import dataclass

@dataclass
class Location:
    name: str
    address: str

class TestDepartmentStatisticsVisitor(unittest.TestCase):
    def test_visit(self):
        visitor = DepartmentStatisticsVisitor()
        location1 = Location('Office', '123 Main St')
        location2 = Location('Warehouse', '456 Elm St')
        asset1 = Asset(1, 'Computer', 'In use', 
            'Hardware', 1000.0, 3, 
            100.0, date.today(), 
            [location1, location2])
        asset2 = Asset(2, 'Printer', 'In use', 
            'Hardware', 500.0, 2, 50.0, 
            date.today(), [location1])
        funding_details1 = FundingDetails(
            asset1, 'Straight Line', 
            0.33, { 'IT': 0.5, 'HR': 0.5 })
        funding_details2 = FundingDetails(
            asset2, 'Double Declining Balance', 
            0.25, { 'IT': 0.3, 'HR': 0.7 })
        visitor.visit(asset1, funding_details1)
        visitor.visit(asset2, funding_details2)
        self.assertEqual(visitor.get_total_cost(), 1500.0)
        self.assertEqual(visitor.get_total_depreciation(), 550.0)
        self.assertEqual(visitor.get_total_allocation(), 0.8)

剩下的只是手动更新import语句。

注意:让这个练习成为一个关于大语言模型使用的警示故事:强迫它们在高度专注的任务上表现良好可能非常具有挑战性,以至于不值得付出努力。在现实世界中,没有人会责怪你放弃提示工程,转而只编写这个测试的代码。然而,只要有足够的坚持,你可以构建一个模板库,用于为类似形状的类构建一系列单元测试。此外,请注意,如果你指示 Copilot Chat 生成(一个)针对我的代码的单元测试,Copilot Chat 可以在编辑器窗口中生成测试文件,但它将模拟所有不属于测试类直接部分的对象/属性。根据你试图测试的内容,这个功能的实用性可能会有疑问。Python 的一个常见问题特指,从 Copilot Chat 复制的代码中,缩进经常是不正确的。

当我们尝试运行这个测试时,我们发现访问者、资产、资金详情和折旧策略之间存在循环依赖。循环依赖是指两个或多个模块或组件直接或间接地相互依赖的情况。在我们的案例中,当 Python 尝试实例化Asset时,它会加载FundingDetails的定义。我们可以通过避免直接实例化或引用FundingDetails类来解决这个问题。

列表 6.5 更新的Asset,没有直接引用FundingDetails

@dataclass
class Asset():
    id: int
    name: str
    status: str
    category: str
    cost: float
    useful_life: int
    salvage_value: float
    purchase_date: date
    locations: List[Location]
    funding_details: None or 'itam.domain.funding_details.FundingDetails'

我们需要对FundingDetails类做同样的事情。它不应该直接引用DepreciationStrategy类。

列表 6.6 FundingDetails,没有直接引用DepreciationStrategy

@dataclass
class FundingDetails:
    depreciation_rate: float
    department_allocations: Dict[Department, float]
    depreciation_strategy: DepreciationStrategy or 'itam.domain.depreciation_strategy.DepreciationStrategy'
    asset: None or 'itam.domain.asset.Asset'

正如我们所见,我们能够使用 Copilot Chat 创建一个单元测试。然而,如果我们没有使用 Copilot 编写它,我们可能会更容易地创建它。这个工具在提供关于何时以及如何测试你的代码的指导方面非常出色,但(至少目前)其实现还有待提高。

在现实世界中,我们会继续添加单元测试,以构建一个庞大的测试库。你可能会问,有多少测试才算得上是“庞大”?我们很快就会探讨这个问题。然而,我们首先将注意力转向下一类测试:集成测试

定义:集成测试涉及测试软件的不同组件或模块之间的交互,以确保它们能够无缝地协同工作。它验证集成系统按预期工作,并检测模块之间的一致性或通信问题。

6.1.2 集成测试

在本节中,我们将开发一个集成测试,这将使我们能够测试端到端系统。幸运的是,fastapi自带其自己的测试客户端,这将帮助我们创建这个测试。

我们首先将AssetController的定义复制到 Copilot Chat 窗口中。然后我们可以询问 Copilot Chat 如何为这个控制器创建集成测试。鉴于我们在定义中包含了路由,Copilot Chat 应该能够为我们提供准确的集成测试。我们需要指定我们将使用InMemoryAssetRepository类,或者测试生成后修复这个问题。

列表 6.7 Copilot Chat 生成的AssetController集成测试

from fastapi.testclient import TestClient
from itam.infrastructure.adapters.memory_asset_repository import MemoryAssetRepository
from itam.infrastructure.ports.asset_rest_port import AssetRestPort
from itam.interfaces.asset_controller import AssetController

def test_get_assets():
    # Create an instance of the InMemoryAssetRepository
    asset_repository = InMemoryAssetRepository()

    # Add some assets to the repository
    asset_repository.create(Asset(name="Asset 1", unit_cost=1000))
    asset_repository.create(Asset(name="Asset 2", unit_cost=2000))

    # Create an instance of the AssetRestPort using the MemoryAssetRepository
    asset_rest_port = AssetRestPort(asset_repository)

    # Create an instance of the AssetController using the AssetRestPort
    asset_controller = AssetController(asset_rest_port)

    # Create a TestClient instance using the AssetController's router
    client = TestClient(asset_controller.get_router())

    # Send a GET request to the /assets endpoint
    response = client.get("/assets")

    # Check that the response status code is 200 OK
    assert response.status_code == 200

    # Check that the response body contains the expected assets
    assert response.json() == 
        {"id": 1, "name": "Asset 1", "unit_cost": 1000},
        {"id": 2, "name": "Asset 2", "unit_cost": 2000},

现在我们将关注我们将要检查的最后一种测试类型:行为测试

定义 行为测试侧重于从最终用户的角度看整个系统的行为。它通常用于测试系统的功能,并确保系统满足为其定义的要求和规范。

6.1.3 行为测试

在某种意义上,所有测试都是行为测试,因为测试验证了系统的行为。然而,行为测试在某些方面是独特的。让我们总结一下迄今为止我们所遇到的不同类型的测试,并将它们与行为测试进行对比。

单元测试侧重于在隔离状态下测试系统的单个单元或组件,通常使用自动化测试。单元测试旨在测试单个函数或方法的功能,并确保它们在各种条件下表现正确。

另一方面,集成测试侧重于测试系统不同组件或单元之间的交互。集成测试通常用于测试不同组件或单元之间的接口,并确保它们能够正确地协同工作。集成测试可以是手动执行或使用自动化测试,通常涉及测试系统不同组件或单元之间的交互,而不是整个系统。

行为测试侧重于从最终用户的角度定义软件的行为,以用户故事或场景的形式。这些场景以特定的格式编写,称为给定-当-然后(GWT),并用于驱动开发过程。GWT 格式描述了特定场景的先决条件(给定)、操作(当)和预期结果(然后)。

随着我们测试的进行,我们可能会发现某些行为或组件在我们的测试中难以设置。此外,隔离特定对象或模块的行为并测试不同对象之间的交互可能很棘手。为了解决这一限制,我们可以使用模拟对象

定义 A 模拟对象 是一种测试替身,以受控的方式模拟真实对象的行为。模拟对象还可以用来模拟难以用真实对象复现的错误条件或边缘情况。它们可以通过使用如 unittest.mockpytest-mock 这样的模拟库手动创建。这些库提供了创建和配置模拟对象的函数和类。模拟对象可以被配置为在调用其方法时返回特定值或引发特定异常。它们还可以用来记录对其方法的调用,以便您可以验证是否以正确的参数调用了正确的方法。

我们将把模拟对象纳入提示中,为 AssetManager 创建一个行为测试:

|

![logo-NC

我们有一个名为 AssetManager 的类。AssetManager 的构造函数接受两个类 BaseRepository 和 AssetLocationMediator。BaseRepository 是一个存储资产的仓库。我们想使用 InMemoryAssetRepository,它是 BaseRepository 的一个子类,用于在内存中存储资产。为 AssetLocationMediator 使用模拟对象。AssetManager 使用 create 方法存储资产,并使用 get_assets 方法获取所有资产。一个资产有 10 个必需的位置参数:‘id’、‘name’、‘status’、‘category’、‘cost’、‘useful_life’、‘salvage_value’、‘purchase_date’、‘locations’ 和 ‘funding_details’。使用命名参数来清楚地说明哪个参数去哪里。创建一个 BDD 脚本,将两个资产添加到 AssetManager 中,确认所有资产的成本为 $4000.00。这应该用 Python 实现。一步一步地告诉我们你将使用哪些工具和库。

Copilot Chat 告诉我们,我们需要创建一个新的文件(一个 feature 文件),名为 asset_manager.feature。在这个文件中,我们将定义 AssetManager 的属性(以及任何支持类)。例如,该特征文件定义了一个场景,向 AssetManager 添加两个资产,确认所有资产的总成本为 $4,000.00,并模拟 AssetLocationMediator 类。

根据 Copilot Chat 列出 6.8 的 asset_manager.feature 文件

Feature: Asset Manager
  As an IT Asset Manager
  I want to be able to manage assets
  So that I can keep track of all IT assets in my organization

  Scenario: Add two assets to the Asset Manager
    Given the Asset Manager is running
    And the InMemoryAssetRepository is initialized
    And the AssetLocationMediator is mocked
    When I create an asset with a cost of $2000.00
    And I create another asset with a cost of $2000.00
    Then the total cost of all assets should be $4000.00

我们需要在项目的根目录下创建一个名为 features 的目录,并将此文件放置在该目录中。此外,我们还需要添加一个名为 steps 的子目录。接下来,我们创建一个名为 steps.py 的新 Python 文件,并将其放置在 steps 目录中。这个 Python 文件定义了特征文件中场景的步骤实现。导入了 AssetManagerInMemoryAssetRepositoryAssetLocationMediatorAsset 类,并定义了六个函数来对应场景中的步骤。这些函数使用上下文对象在步骤之间存储和检索数据。unittest.mock 库中的 Mock 类用于为 AssetLocationMediator 类创建一个模拟对象。使用命名参数实例化 Asset 类,以明确哪个参数属于哪个属性。如果我们向 Copilot Chat 请求 steps.py 文件的內容,我们将看到类似于以下列表的输出。

列表 6.9 steps.py 中定义的行为测试的实现细节

from behave import given, when, then
from itam.service.asset_manager import AssetManager
from itam.infrastructure.repository.in_memory_asset_repository 
    import InMemoryAssetRepository
from itam.domain.asset import Asset
from itam.infrastructure.mediators.asset_location_mediator 
    import AssetLocationMediator
from unittest.mock import Mock

@given('the Asset Manager is running')
def step_impl(context):
    context.asset_repository = InMemoryAssetRepository()
    context.asset_location_mediator = Mock(spec=AssetLocationMediator)
    context.asset_manager = AssetManager(
            context.asset_repository, 
            context.asset_location_mediator)

@given('the InMemoryAssetRepository is initialized')
def step_impl(context):
    pass

@given('the AssetLocationMediator is mocked')
def step_impl(context):
    pass

@when('I create an asset with a cost of ${cost}')
def step_impl(context, cost):
    asset = Asset(id=1, name='Test Asset 1', status='Available',
        category='Test Category', cost=float(cost), useful_life=5,
        salvage_value=0, purchase_date='2022-01-01', 
        locations=['Test Location'], 
        funding_details={'Test Funding': 1000})
    context.asset_manager.create(asset)

@when('I create another asset with a cost of ${cost}')
def step_impl(context, cost):
    asset = Asset(id=2, name='Test Asset 2', status='Available',
        category='Test Category', cost=float(cost), useful_life=5,
        salvage_value=0, purchase_date='2022-01-01', 
        locations=['Test Location'], 
        funding_details={'Test Funding': 1000})
    context.asset_manager.create(asset)

@then('the total cost of all assets need to be ${total_cost}')
def step_impl(context, total_cost):
    assets = context.asset_manager.get_assets()
    assert sum(asset.cost for asset in assets) == float(total_cost)

如果你还没有安装它,你需要使用 pip 安装 behave 库:pip install behave。此外,将其添加到 requirements.txt 文件中,以确保在稍后构建可部署版本的应用程序时将其拉入。我们从项目的根目录运行行为测试,使用以下命令。

列表 6.10 运行行为测试及其输出结果

% behave features
Feature: Asset Manager # features/asset_manager.feature:1
  As an IT Asset Manager
  I want to be able to manage assets
  So that I can keep track of all IT assets in my organization
  Scenario: Add two assets to the Asset Manager          
    # features/asset_manager.feature:6
    Given the Asset Manager is running                   
# features/steps/steps.py:8 0.000s
    And the InMemoryAssetRepository is initialized       
# features/steps/steps.py:14 0.000s
    And the AssetLocationMediator is mocked              
# features/steps/steps.py:18 0.000s
    When I create an asset with a cost of $2000.00       
# features/steps/steps.py:22 0.000s
    And I create another asset with a cost of $2000.00   
# features/steps/steps.py:27 0.000s
    Then the total cost of all assets should be $4000.00 
# features/steps/steps.py:32 0.000s

1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
6 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.001s

在本节中,我们通过使用三种类型的测试:单元测试、集成测试和行为测试,为良好的软件开发奠定了基础。有些人可能会挑剔地说,这在这个项目的开发周期中来得非常晚,他们并不错。在现实世界中,我们随着代码的开发来开发我们的测试。有些人可能会争论说,我们需要在代码之前构建我们的测试。你可能或可能不持有这种信念,但无论如何,你需要尽早测试,并且经常测试。

在下一节中,我们将深入研究一些可以用来确定我们软件整体质量的指标,并且我们将请求 Copilot 帮助我们评估到目前为止的代码质量。

6.2 评估质量

理解软件应用的性能、可靠性、可维护性和整体质量是软件工程的一个关键方面。本节深入探讨了软件质量指标的迷人而复杂的领域——这些是指导我们理解软件系统质量的定量标准和基准。

软件质量指标是允许利益相关者——开发者、测试人员、经理和用户——评估软件产品状态的必要工具,确定其优势和改进领域。它们为产品开发、测试、调试、维护和改进计划等过程提供了经验基础。通过量化软件的特定特征,这些指标提供了理解软件质量这一抽象概念的有形手段。

在本节中,我们探讨几个重要的软件质量指标类别,包括产品指标、过程指标和项目指标。我们将分析它们的含义、计算方法以及如何有效地利用它们来评估和提升软件质量。这次探索将包括静态指标,这些指标应用于静态软件系统,以及动态指标,这些指标评估系统在执行过程中的行为。

软件质量指标不仅有助于确保软件系统的技术稳定性,还有助于确保客户满意度、盈利能力和长期商业成功。因此,对软件开发领域中的任何人来说,了解这些指标都是非常有价值的,从工程师和项目经理到高管和软件用户。

我们将检查几个关于类或代码复杂性和可维护性的常见指标。复杂的软件可能难以理解,这使得开发者,尤其是新开发者,难以掌握软件的不同部分是如何相互作用的。这可能会减缓入职过程和开发时间。

复杂的代码往往会导致更多的维护:修改或错误修复可能需要更长的时间,因为更难预测改变系统单个部分的影响。这可能导致软件生命周期中的成本更高。

复杂的软件也往往更容易出错。因为它更难理解,所以在进行更改时,开发者更有可能引入错误。此外,复杂的代码可能有许多相互依赖性,一个区域的更改可能会在别处产生意外的效果。

软件越复杂,需要更多的测试用例才能实现彻底的测试。由于涉及逻辑的复杂性,编写这些测试用例可能也更困难。

编写简单且易于维护的代码应该是我们最高的优先事项之一。观察伴随我们的代码的指标变化应该有助于我们实现这一目标。为此,我们可以(并且应该)使用的第一个指标是循环复杂度

定义循环复杂度是一个衡量软件模块中独立路径数量的指标。它衡量代码中决策的复杂度,包括循环、条件和分支。较高的循环复杂度值表示更高的复杂度,并暗示代码在理解和维护方面可能存在更多错误和挑战。

在文件 department_visitor.py 的任何地方输入以下提示。Copilot 将立即输出答案:

# Question: What is the cyclomatic complexity of the class Department- StatisticsVisitor?
# Answer: 1

Copilot 告诉我们这个类的复杂度是 1。你可能或可能不知道这个值的含义。如果不知道,你可以要求 Copilot 进行详细说明:

# Question: Is 1 an excellent cyclomatic complexity?
# Answer: Yes

# Question: Why is 1 a good value for cyclomatic complexity?
# Answer: Because it is low

Copilot 告诉我们,如果圈复杂度低,那么它是好的。直观上,这很有道理。低复杂度的代码意味着它更容易理解,因此更容易推理。它也更有可能更容易维护。接下来,我们将探索霍尔斯特德复杂度度量

定义 霍尔斯特德复杂度度量 基于代码中使用的唯一操作符和操作数的数量来评估软件程序的复杂度。这些度量包括程序长度(N1)、程序词汇(n1)、体积(V)、难度(D)、努力(E)等指标。这些指标提供了对代码大小和认知复杂度的洞察。

与上次类似,我们将从一个提示开始,要求 Copilot 确定我们的访客类的霍尔斯特德复杂度度量:

# Question: What is the Halstead Complexity Measure of the class Department-StatisticsVisitor?
# Answer: 2

# Question: What is the Halstead Difficulty Measure of the class Department-StatisticsVisitor?
# Answer: 1

# Question: Is 2 a good Halstead Complexity Measure?
# Answer: Yes

# Question: Is 1 a good Halstead Difficulty Measure?
# Answer: Yes

# Question: What is a bad Halstead Difficulty Measure?
# Answer: 10

# Question: What is a bad Halstead Complexity Measure?
# Answer: 10

# Question: What does a high Halstead Difficulty Measure mean?
# Answer: It means the code is hard to understand

你可能想继续这个问答会话一段时间,看看可以从 Copilot 中获取哪些信息。一旦你准备好继续,还有一个指标要探索:可维护性指数

定义 可维护性指数 是一个综合指标,它结合了多个因素,包括圈复杂度、代码行数和霍尔斯特德复杂度度量,以提供一个软件可维护性的总体度量。更高的可维护性指数表明维护更容易,并且可能具有更低的复杂性。

在访客文件中开始一个类似的关于可维护性指数的讨论:

# Question: What is the maintainability index of the class Department-StatisticsVisitor?
# Answer: 100

# Question: Do we want a high Maintainability Index or low Maintainability Index?
# Answer: high

# Question: Why do we want a high Maintainability Index?
# Answer: Because it is easier to maintain

如果我们得到一个低的可维护性指数,我们可以重构以减少这个数字。

一个指标是有用的,因为它给我们提供了一个挂帽子的钉子;也就是说,我们可以采取这个度量并执行一些操作来改进它。指标使我们超越了纯粹的美学或个人的主观性。一个指标是真实、可操作的数据。但是 Copilot 还有(至少)一个众所周知的技巧。Copilot 不仅能够编写和评估我们的代码,它还可以解决代码的缺陷。让我们开始捕捉错误。

6.3 捕捉错误

在本节中,我们将使用一个基本的(尽管是人为设计的)示例来展示我们如何使用 Copilot 来查找和修复代码中的问题。这段代码本应遍历整数列表并计算总和。然而,存在一个“一闪而过”的错误:总和被分配了i的值,而不是将i的值添加到运行总和中。

列表 6.11 遍历整数列表并计算总和

l = [1, 2, 3, 4, 5]

if __name__ == '__main__':
    sum = 0
    for i in l:
        sum = i

    print("sum is", sum)

为了调试这个问题,我们将引入一个新的工具:Copilot Labs。在 Copilot Chat 之前,Copilot Labs 是唯一在 IDE(特别是 Visual Studio Code)中提供某些功能的手段。例如,我们需要使用 Copilot Labs 来查找和修复错误。Copilot Labs 今天仍提供的主要优势是它可以访问你的编辑器面板中的高亮内容。这个功能使得 Copilot Labs 可以直接在你的 IDE 中的可编辑代码上操作。

一旦将扩展程序安装到你的 IDE 中,你应该在 IDE 的左侧看到 Copilot Labs 工具箱,如图 6.1 所示。如果你需要关于如何将扩展程序安装到 IDE 的提醒,请参阅附录 A-C 中的说明。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH06_F01_Crocker2.png

图 6.1 Copilot Labs 工具箱菜单,包括查找和修复错误的选项。工具箱还提供了增强和记录代码的功能。

我们将临时更改 main.py 文件的内容为列表 6.11 中列出的代码。一旦你做了这个更改,突出显示代码并点击 Copilot Labs 工具箱中的修复错误按钮。你应该看到如图 6.2 所示的输出。Copilot Labs 能够确定这段代码中的问题,并提供有关如何修复的建议。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH06_F02_Crocker2.png

图 6.2 使用 GPT 模型的 Copilot Labs 已经识别出错误及其解决方法。

或者,你也可以将此代码复制到 ChatGPT 中,并要求它找出错误。然而,这样做可能不太方便,因为你必须在请求 ChatGPT 修复之前知道代码中存在错误。

6.4 代码覆盖率

代码覆盖率是衡量你的代码被测试执行程度的指标。它通常以百分比的形式表示,代表你的代码中由测试执行的代码比例。

代码覆盖率可以用作衡量测试有效性的指标。如果你的代码覆盖率低,可能表明你的代码中某些部分没有被测试,这可能导致未捕获的错误和其他问题。另一方面,如果代码覆盖率较高,你可以放心,你的代码经过了良好的测试。这并不保证你的代码没有错误,但它应该给你一个高度信心,即如果有错误,它们将在测试中被捕获。

为了确定我们的 Python 项目的代码覆盖率,我们将使用coverage库中提供的代码覆盖率工具。coverage库通过在代码运行时对其进行仪器化来收集覆盖率数据。它可以收集任何 Python 代码的覆盖率数据,包括测试、脚本和模块。通过使用像coverage这样的代码覆盖率工具,我们可以更好地了解我们的代码中有多少部分被测试执行,并识别可能需要更多测试的代码区域。

首先,让我们使用 pip 安装coveragepip install coverage。接下来,让我们使用覆盖率运行我们的测试:coverage run -m pytest。这将运行你的测试并收集覆盖率数据。

现在我们将生成一个覆盖率报告(见图 6.3)。覆盖率报告显示了项目中每个文件的代码覆盖率。我们可以使用coverage report命令创建基于文本的覆盖率报告,或使用coverage html命令生成报告的 HTML 版本。报告的 HTML 版本位于 htmlcov 目录中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH06_F03_Crocker2.png

图 6.3 显示我们 ITAM 系统项目每个文件覆盖率的代码覆盖率报告

代码覆盖率 70% 是一个好的开始。在现实世界中,我们会继续与我们的团队和生成式 AI 伙伴合作,将这个指标提高到 90% 以上。现在,我们将过渡到新的主题:使用生成式 AI 向我们描述代码列表。

6.5 从代码到描述的翻译

通常,我们会收到现有的源代码。确定这段代码的确切入口点、代码的目的以及棕色地带项目的整体结构可能具有挑战性。幸运的是,这是生成式 AI 真正擅长的领域之一:将代码翻译成文本描述。

首先,我们将从上一章节复制(有缺陷的)代码到 Copilot Chat 对话框中,前面加上以下提示(见图 6.4):

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

这段代码做什么?

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH06_F04_Crocker2.png

图 6.4 上次章节中的有缺陷的代码,提示 Copilot Chat 向我们解释此代码

图 6.5 中的解释引人注目的是,Copilot Chat 在代码中检测到一个错误,并提供了修复建议。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH06_F05_Crocker2.png

图 6.5 Copilot Chat 解释代码的目的,识别错误,并提出修复建议。

接下来,我们将提出更具挑战性的问题:将列表 6.9 中的行为测试解释到 Copilot Chat 窗口中,并询问它这个类的作用。你可能会收到以下类似的回复。

列表 6.12 Copilot Chat 解释我们之前编写的测试行为

The code you provided is not a class but a series of step definitions for a Behavior-Driven Development (BDD) test using the Behave framework in Python. These steps correspond to a specific scenario written in Gherkin language (Given, When, Then format).

These step definitions describe how to interact with an AssetManager class and an InMemoryAssetRepository class. They're meant to test the creation of assets and the calculation of the total cost of all assets.

Let's go through each step:

@given('the Asset Manager is running'): This sets up the initial state for the test. It initializes the AssetManager with an InMemoryAssetRepository (which presumably stores assets in memory during the execution of the test) and a mocked AssetLocationMediator.

@given('the InMemoryAssetRepository is initialized') and @given('the AssetLocationMediator is mocked'): These steps appear to be placeholders, as they don't execute any actions.

@when('I create an asset with a cost of ${cost}') and @when('I create another asset with a cost of ${cost}'): These steps create Asset instances with the given cost and add them to the AssetManager.

@then('the total cost of all assets should be ${total_cost}'): This step retrieves all the assets from the AssetManager, calculates the total cost, and checks that it matches the expected total cost.

This sequence of steps allows the user to test the process of creating assets and validating the total cost of all assets in the AssetManager.

    # Question: What does this method do?
    # Answer: This code defines an asynchronous method get_asset that
      takes an integer asset_id as input. The method first calls the
      _asset_rest_port.read method with the asset_id to retrieve an
      asset from a REST API. If the asset is not found (i.e., the
      REST API returns None), the method raises an
      HTTPException with a 404 status code and a "Asset not
      found" detail message. Otherwise, the method returns the
      asset converted to a domain object using the from_asset
      function. The implementation of the from_asset function is
      not shown in the code snippet.    

async def get_asset(self, asset_id: int):
        asset = self._asset_rest_port.read(asset_id)
        if asset is None:
            raise HTTPException(status_code=404, detail="Asset not found")
        return from_asset(asset)

6.6 从一种语言翻译到另一种语言

虽然这听起来可能很遥远,但你可能会发现自己正在查看一个用你只有肤浅了解的编程语言编写的代码库。或者,你可能渴望某个特定功能能在你当前的语言中使用。例如,如果将 AssetFundingDetails 类用 JavaScript 重新编写,它们会是什么样子?好吧,让我们问问 Copilot Chat。

列表 6.13 将 AssetFundingDetails 翻译成 JavaScript。

class Asset {
  constructor(id, name, status, category, cost, 
        useful_life, salvage_value, 
        purchase_date, locations, 
        funding_details) {
    this.id = id;
    this.name = name;
    this.status = status;
    this.category = category;
    this.cost = cost;
    this.useful_life = useful_life;
    this.salvage_value = salvage_value;
    this.purchase_date = purchase_date;
    this.locations = locations;
    this.funding_details = funding_details;
  }
}

class FundingDetails {
  constructor(asset, funding_source, funding_amount) {
    this.asset = asset;
    this.funding_source = funding_source;
    this.funding_amount = funding_amount;
  }
}

你可能会认为翻译一个或两个类太简单了。好吧,我们将尝试更复杂的事情:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

如果用 JavaScript 编写,AssetManager 的行为测试会是什么样子?

列表 6.14 将 AssetManager 行为测试翻译成 JavaScript

const AssetManager = require('./asset_manager');
const Asset = require('./asset');

describe('AssetManager', () => {
  let assetManager;

  beforeEach(() => {
    assetManager = new AssetManager();
  });

  describe('addAsset', () => {
    it('should add an asset to the asset list', () => {
      const asset = new Asset(1, 'Computer', 'Active', 
            'Hardware', 1000.0, 5, 100.0, 
            new Date(), [], null);
      assetManager.addAsset(asset);
      expect(assetManager.assets.length).toBe(1);
      expect(assetManager.assets[0]).toBe(asset);
    });
  });

  describe('removeAsset', () => {
    it('should remove an asset from the asset list', () => {
      const asset = new Asset(1, 'Computer', 'Active', 
            'Hardware', 1000.0, 5, 100.0, 
            new Date(), [], null);
      assetManager.addAsset(asset);
      assetManager.removeAsset(asset);
      expect(assetManager.assets.length).toBe(0);
    });
  });

  describe('getAssetById', () => {
    it('should return the asset with the given ID', () => {
      const asset1 = new Asset(1, 'Computer', 'Active', 
            'Hardware', 1000.0, 5, 100.0, 
            new Date(), [], null);
      const asset2 = new Asset(2, 'Printer', 'Active', 
            'Hardware', 500.0, 3, 50.0, 
            new Date(), [], null);
      assetManager.addAsset(asset1);
      assetManager.addAsset(asset2);
      const result = assetManager.getAssetById(2);
      expect(result).toBe(asset2);
    });

    it('should return null if no asset with the given ID is found', () => {
      const result = assetManager.getAssetById(1);
      expect(result).toBeNull();
    });
  });
});

此外,你可能希望让 GPT 将代码重写为地道的 JavaScript。你只需要说

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

用地道的 JavaScript 编写 AssetManager

Copilot Chat 会告诉你它将使用 ES6 中引入的类语法来定义 AssetManager 类。

列表 6.15 用地道的 JavaScript 重新编写的 AssetManager

class AssetManager {
  constructor() {
    this.assets = [];
  }

  addAsset(asset) {
    this.assets.push(asset);
  }

  removeAsset(asset) {
    const index = this.assets.indexOf(asset);
    if (index !== -1) {
      this.assets.splice(index, 1);
    }
  }

  getAssetById(id) {
    return this.assets.find(asset => asset.id === id) || null;
  }
}

这里是一个使用 Jest 测试框架(Copilot Chat 没有建议就包含了)的 AssetManager 行为测试的例子。

列表 6.16 AssetManager行为测试的惯用 JavaScript

const AssetManager = require('./asset_manager');
const Asset = require('./asset');
describe('AssetManager', () => {
  let assetManager;
  beforeEach(() => {
    assetManager = new AssetManager();
  });
  describe('addAsset', () => {
    it('should add an asset to the asset list', () => {
      const asset = new Asset(1, 'Computer', 'Active', 
            'Hardware', 1000.0, 5, 100.0, 
            new Date(), [], null);
      assetManager.addAsset(asset);
      expect(assetManager.assets.length).toBe(1);
      expect(assetManager.assets[0]).toBe(asset);
    });
  });
  describe('removeAsset', () => {
    it('should remove an asset from the asset list', () => {
      const asset = new Asset(1, 'Computer', 'Active', 
            'Hardware', 1000.0, 5, 100.0, 
            new Date(), [], null);
      assetManager.addAsset(asset);
      assetManager.removeAsset(asset);
      expect(assetManager.assets.length).toBe(0);
    });
  });
  describe('getAssetById', () => {
    it('should return the asset with the given ID', () => {
      const asset1 = new Asset(1, 'Computer', 'Active', 
            'Hardware', 1000.0, 5, 100.0, 
            new Date(), [], null);
      const asset2 = new Asset(2, 'Printer', 'Active', 
            'Hardware', 500.0, 3, 50.0, 
            new Date(), [], null);
      assetManager.addAsset(asset1);
      assetManager.addAsset(asset2);
      const result = assetManager.getAssetById(2);
      expect(result).toBe(asset2);
    });
    it('should return null if no asset with the given ID is found', () => {
      const result = assetManager.getAssetById(1);
      expect(result).toBeNull();
    });
  });
});

在这个例子中,我们使用 Jest 测试框架为AssetManager类定义一组行为测试。我们使用describe库来确定测试套件和单个测试。

在每个测试中,我们创建一个AssetManager类的实例并在其上执行一些操作,例如添加或删除资产。然后我们使用expect函数对AssetManager类的行为进行断言,例如检查资产是否添加到资产列表或资产是否从资产列表中删除。请注意,JavaScript 中定义类和构造函数的语法与 Python 略有不同,我们使用所需的函数从各自的模块中导入AssetManagerAsset类。

最后,是时候进入高潮部分了:我们将开始一个新的 ChatGPT 4.0 会话,并将第三章中的 Mermaid 图表粘贴进来。以下是一个提示:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

这里是我的应用程序的 Mermaid 图表。请用 Golang 实现。

惊讶地看到它返回的代码,是用 Golang 渲染的。

列表 6.17 Asset类用惯用 Golang 重写

type Asset struct {
    id            int
    name          string
    status        string
    category      string
    fundingDetails *FundingDetails
}

func NewAsset(name, status, category string, 
    fundingDetails *FundingDetails) *Asset {
    return &Asset{
        name:          name,
        status:        status,
        category:      category,
        fundingDetails: fundingDetails,
    }
}

接下来,告诉它切换到 Java。

列表 6.18 Asset类用惯用 Java 重写

class Asset {
    private int id;
    private String name;
    private String status;
    private String category;
    private FundingDetails fundingDetails;

    public Asset(String name, String status, String category, FundingDetails fundingDetails) {
        this.name = name;
        this.status = status;
        this.category = category;
        this.fundingDetails = fundingDetails;
    }

摘要

  • 单元测试专注于测试单个组件或代码单元以识别特定单元中的错误和问题。单元测试将在你的代码库中最为常见。

  • 集成测试测试软件的不同组件或模块之间的交互,以确保无缝集成并检测通信问题。

  • 行为测试从最终用户的角度测试系统的功能,确保它符合需求和规范。

  • 模拟对象以受控的方式模拟自然对象的行为,对于测试和模拟错误条件非常有用。模拟对象特别擅长模仿测试所需但不在测试范围内的系统部分:例如,如果你的类有一个数据库构造函数参数,但你不想直接测试数据库,因为数据可能会变化,导致你的测试结果不确定、不可重复或非确定性。

  • 圈复杂度衡量软件模块中独立路径的数量,表明复杂性和潜在的错误。

  • Halstead 复杂性度量基于独特的操作符和操作数来评估软件复杂性,提供了对代码大小和认知复杂性的见解。

  • 可维护性指数结合了圈复杂度、代码行数和 Halstead 度量等因素,以评估软件的可维护性。

  • 代码覆盖率是评估测试有效性的指标,表明代码被测试的程度和未捕获错误的潜在可能性。通常,越高越好。

  • 大型语言模型允许你在不熟悉的编程语言中导航代码,或者将另一种语言中的功能翻译到当前或首选语言中。

第四部分. 进入世界

在第四部分,我们讨论了在现实环境中部署和管理集成 AI 的软件的实用方面。本节涵盖了编码基础设施和部署策略,从构建 Docker 镜像到使用 GitHub Actions 等工具设置持续集成和持续部署(CI/CD)管道。它还强调了安全的应用程序开发,讨论了威胁建模和安全最佳实践的实现。此外,本书的这一部分还探讨了通过托管自己的 LLM 和利用 GPT-4All 等平台来民主化 AI 访问的概念。通过提供部署和安全的实用指导,这部分为开发者成功将他们的 AI 应用程序推向市场做好了准备。

第七章:编码基础设施和管理部署

本章涵盖

  • 在 Copilot 的帮助下创建 Dockerfile

  • 使用大型语言模型起草基础设施代码

  • 使用容器注册库管理 Docker 镜像

  • 利用 Kubernetes 的力量

  • 使用 GitHub Actions 无缝发布您的代码

没有什么比一个应用程序闲置不用更令人沮丧的了。因此,快速将经过良好测试的应用程序推向生产是每个合格开发者的目标声明。因为我们已经在上一章测试了我们的产品,所以它现在可以准备发布了。

本章将重点关注从开发到产品发布的那个关键时刻。在这个关键阶段,理解部署策略和最佳实践变得至关重要,以确保产品发布成功。

在我们的应用程序成功得到保护和测试后,是时候将我们的注意力转向产品的发布了。为此,我们将利用大型语言模型(LLMs)强大的功能来探索针对云基础设施的定制部署选项。

通过利用大型语言模型(LLMs)的力量并接受它们的部署选项和方法,我们可以自信地穿梭于启动产品的复杂领域,向客户交付一个强大且可扩展的解决方案,同时利用云计算的好处。

首先,我们将开发 Docker 的部署文件。我们将探讨如何创建 Docker 镜像并定义部署文件。此外,我们还将讨论容器化我们的应用程序和实现无缝部署的最佳实践。

接下来,我们将使用 Terraform 来定义我们的基础设施代码,并自动化在亚马逊网络服务(AWS)上部署弹性计算云(EC2)实例。我们将演示如何编写 Terraform 脚本来在 EC2 实例上配置和部署我们的应用程序,确保基础设施设置的一致性和可重复性。

然后,我们将利用大型语言模型(LLMs)将我们的应用程序部署到 Kubernetes(AWS Elastic Kubernetes Service [EKS]/Elastic Container Service [ECS])。我们将让 GitHub Copilot 创建适当的 Kubernetes 部署文件,以简化我们的部署流程并高效管理应用程序的生命周期。鉴于我们的应用程序相对简单,我们不需要像 Helm 这样的 Kubernetes 包管理器。然而,随着服务和依赖的复杂性和增长,你可能希望将其作为选项之一进行探索。幸运的是,Copilot 还可以为你编写 Helm 图表!

最后,我们将简要展示如何使用 GitHub actions 从本地迁移到自动化部署。通过将 LLMs 与这个广泛使用的持续集成和持续部署(CI/CD)工具集成,我们可以自动化构建和部署流程,确保更快、更高效的部署。

注意:本章使用 AWS 作为我们的云服务提供商,但本章中涵盖的原则和实践可以适应并应用于其他云平台,甚至在没有虚拟化(裸金属)的本地基础设施上,使我们能够根据业务需求的变化调整和扩展产品部署策略。你会发现,通过采用大型语言模型(LLMs)和使用基础设施即代码(infrastructure as code),你可以(部分地)减轻云平台非常常见的供应商锁定问题。

注意,如果您选择将此(或任何应用程序)部署到 AWS,您的活动将产生相关费用。AWS 和大多数云服务提供商提供免费试用以学习他们的平台(例如 Google Cloud Platform 和 Azure),但一旦这些信用额度到期,您可能会收到一个相当意外的账单。如果您决定跟随本章的内容,您需要设置一个您能舒适承担的阈值警报。Andreas Wittig 和 Michael Wittig 的Amazon Web Services in Action, Third Edition(Manning,2023;www.manning.com/books/amazon-web-services-in-action-third-edition)的第 1.9 节是设置此类计费通知警报的极好资源。

7.1 构建 Docker 镜像并在本地“部署”

如您从第六章可能记得的那样,Docker 是一个容器化平台,允许您在传统意义上几乎不需要安装应用程序(除了 Docker 之外)的情况下运行应用程序。与模拟整个操作系统的虚拟机不同,容器共享宿主系统的内核(操作系统的核心部分)并使用宿主系统的操作系统功能,同时将应用程序进程和文件系统与宿主系统隔离。这使得您可以在单个宿主系统上运行多个隔离的应用程序,每个应用程序都有自己的环境和资源限制。图 7.1 展示了 Docker 运行时与宿主之间的关系。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH07_F01_Crocker2.png

图 7.1 Docker 利用宿主操作系统的功能,同时隔离每个容器。这使得与虚拟机相比,Docker 容器更轻量,因为它们不需要完整的操作系统来运行。

从生产准备的角度来看,其中一个更令人兴奋的功能是,Docker 使得运行某些意义上可以自我修复的应用程序变得更加容易。如果它们在运行时失败或崩溃,您可以配置它们在无需干预的情况下重启。在本节中,我们将使用 Copilot 创建一个文件(称为Dockerfile),我们将从这个文件构建我们的Docker 镜像

定义Docker 镜像就像 Docker 容器的蓝图。它们是可移植的,包括应用程序运行所需的所有依赖项(库、环境变量、代码等)。

正在运行的 Docker 实例被称为 Docker 容器。鉴于它们的轻量级特性,我们可以在单个主机上运行多个容器而不会出现问题。我们可以这样做,因为容器化技术共享 OS 内核,在隔离的用户空间中运行。

注意:最初,我想使用 AWS CodeWhisperer 作为本章的 LLM。鉴于预期的云平台,这似乎是合理的。然而,在撰写本文时,AWS CodeWhisperer 仅支持编程语言编程。它没有基础设施即代码的功能。

我们将使用以下提示来让 Copilot 为我们草拟 Dockerfile:

# Create a Dockerfile for this Python app. The main class is main.py. Use Python 3.10 and install
# the dependencies using the requirements.txt file in this directory. The app should run on port 8080.

您可能只剩下一个空文件(除了这个注释)。基础设施即代码的支持是不断发展的(与一般的 LLM 生态系统类似)。根据 Copilot Chat,GitHub Copilot 能够为您创建 Dockerfile——但是您必须通过以下步骤来激励它:

  1. 在 Dockerfile 中输入FROM python:并等待 Copilot 建议要使用的 Python 版本。选择您想要使用的版本。

  2. 输入WORKDIR /app以设置容器的当前工作目录。

  3. 输入COPY . /app以将项目内容复制到容器中。

  4. 输入RUN pip install --trusted-host pypi.python.org -r requirements.txt以安装项目的依赖项。

  5. 输入EXPOSE 8080以暴露容器的 8080 端口。

  6. 输入CMD ["python", "main.py"]以指定容器启动时运行的命令。

或者,您可能希望将之前写入 Dockerfile 中的相同提示复制粘贴到 Copilot Chat 的提示窗口中。Copilot Chat 将为您提供所需的 Dockerfile 内容。

列表 7.1 构建 Docker 镜像的 Dockerfile

FROM python:3.10-slim-buster
WORKDIR /app
COPY . /app
RUN pip install --trusted-host pypi.python.org -r requirements.txt
EXPOSE 8080
CMD ["python", "main.py"]

使用 Dockerfile,我们将构建用于部署和运行我们应用程序的镜像。我们可以输入以下命令来构建我们的应用程序(从 Dockerfile 所在的目录运行,并且不要忘记最后的点)。您需要互联网访问来下载依赖项并创建镜像:

docker build -t itam:latest .

构建 Docker 镜像可能需要几秒钟到几分钟,具体取决于您的系统上安装了哪些镜像和包以及您的互联网连接速度。您的耐心将得到回报,因为您将很快拥有一个可以在几乎任何地方安装的应用程序,从最基础的商品硬件到您最喜欢的云提供商提供的最大型硬件。然而,在运行之前,您需要尝试在本地运行它。如果您忘记了命令,Copilot Chat 将乐意并乐于提供帮助:

docker run -p 8000:8000 -d --name itam itam:latest

您可以通过在命令行中输入以下命令来确认您的 Docker 容器正在运行:docker ps | grep itam。您应该能看到正在运行的实例。

7.2 通过 Copilot 搭建基础设施

在创建和测试应用程序时,使用你电脑上的 Docker 镜像是有用的。但是,当到了启动你的应用程序的时候,你需要一台比本地电脑更强大的机器。在本节中,我们将使用 GitHub Copilot 帮助我们通过让 Copilot 编写基础设施代码工具 Terraform 所需的必要部署描述符来设置和控制我们的 AWS 基础设施。Terraform 由 HashiCorp 制作,允许我们使用领域特定语言(DSL)来编写我们希望基础设施看起来像什么。这种 DSL 使我们免于理解每个云服务提供商用于配置硬件的所有复杂性和细微差别。此外,它还允许我们使用基础设施代码存储和版本化我们的基础设施。

首先,我们想要创建一个名为 ec2.tf 的文件,并添加提示告知 Copilot 我们打算将其作为 Terraform 文件,以及我们希望如何搭建我们的基础设施。请注意,Copilot 需要我们在输入给定行的第一个单词之前才能继续。

列表 7.2 示例 Terraform 文件,包括实例大小

# Create a Terraform file that provisions an AWS EC2 instance of type t2.micro, installs the Docker daemon, and returns the instance's hostname.
provider "aws" {
  region = "us-west-2"
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
}

你可能会发现 Copilot 跳过了一个小但至关重要的细节:它没有提供安装和配置 Docker 的代码。鉴于 Docker 是运行我们的应用程序所必需的,我们需要纠正这个疏忽。实际上,你可能需要手动更新文件以包含安装 Docker 的命令,如下所示:

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

   user_data = <<-EOF
                #!/bin/bash
                sudo yum update -y
                sudo yum install -y docker
                sudo service docker start
                sudo usermod -a -G docker ec2-user
                sudo docker run -d -p 80:80 nginx
                EOF
}

Copilot 应该生成一个完整的 Terraform 文件,类似于以下列表。你的代码可能并不完全匹配列表,但这没关系,只要它包含关键特性:提供者、实例、添加 Docker 守护进程的脚本、密钥对和安全组。

列表 7.3 创建最小 EC2 实例的 Terraform 文件

# Create a Terraform file that provisions an AWS EC2 instance of type t2.micro, installs the Docker daemon, and returns the hostname of the instance.
provider "aws" {
  region = "us-west-2"
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

   user_data = <<-EOF
                #!/bin/bash
                sudo yum update -y
                sudo yum install -y docker
                sudo service docker start
                sudo usermod -a -G docker ec2-user
                sudo docker run -d -p 80:80 nginx
                EOF
    connection {
        type        = "ssh"
        user        = "ec2-user"
        private_key = file("~/.ssh/id_rsa")
        host        = self.public_ip
    }

    lifecycle {
        create_before_destroy = true
    }

    depends_on = [aws_security_group.allow_http]
}

resource "aws_security_group" "allow_http" {
  name        = "allow_http"
  description = "Allow HTTP inbound traffic"
  vpc_id      = "vpc-12345678"

  ingress {
    description = "HTTP from VPC"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    }
}

output "public_dns" {
  value = aws_instance.example.public_dns
    }

如果你使用的是默认的虚拟专用云(VPC),则vpc_id条目不是严格必要的。你会发现 AWS 团队选择的许多默认配置和约定是有意义的;如果你有更严格的安全要求,或者如果你对你的基础设施了如指掌,并假设一切,你可能会考虑从头开始使用 Terraform 设置一个新的 VPC。你需要将第 21 行的密钥对条目更改为你有访问权限的密钥对。

一旦你满意地完成了这个文件,运行terraform init命令。这个命令初始化一个新的或现有的 Terraform 工作目录。它会下载并安装你在配置文件中指定的所需提供者插件和模块,并准备好一切以便开始。

接下来,Terraform 将解释它打算做出的更改。你可以使用terraform plan命令来完成这个操作。这个命令为你的基础设施更改创建一个执行计划:它显示当你应用你的配置文件时,Terraform 将如何更改你的基础设施。计划将显示哪些资源将被创建、修改或销毁,以及将对你的基础设施做出的任何其他更改。

注意:当你第一次运行terraform plan时可能会遇到错误:“错误:配置 Terraform AWS Provider:未找到有效的 Terraform AWS Provider 凭证来源。”当你尝试连接到 AWS 但无法向 AWS 提供适当的凭证时,你会遇到这个错误。为了解决这个问题,你需要创建(或编辑)名为~/.aws/credentials 的文件,并添加你的 ITAM AWS 访问密钥 ID 和 AWS 秘密访问密钥凭证。你可以在Amazon Web Services in Action,第三版的 4.2.2 节“配置 CLI”中找到更多关于如何正确完成此操作的详细信息。

最后,为了应用 Terraform 的更改,你将使用terraform apply命令。然后,Terraform 将读取当前目录中的配置文件,并将任何更改应用到你的基础设施上。如果你在最后一次运行terraform apply之后对配置文件进行了任何更改——例如,如果你需要启动一个新的数据库实例或更改 EC2 的大小——Terraform 将显示更改的预览,并提示你在应用更改之前进行确认。

如果你应用这些更改,几分钟内你将有一个全新的 EC2 实例在你的 VPC 中运行。然而,这仅仅是方程的一半。拥有触手可及的计算能力是极好的,但你还需要一些东西来应用这种力量。在这种情况下,我们可以使用这个 EC2 实例来运行我们的 ISAM 系统。下一节简要演示了将本地构建的镜像传输到另一台机器的过程。

7.3 以困难的方式移动 Docker 镜像

首先,我们将从本地机器导出一个 Docker 镜像并将其加载到远程机器上。我们将使用docker saveload命令来完成这项任务。你可以在本地机器上使用docker save命令将镜像保存到一个 tar 归档文件中。以下命令将镜像保存到名为.tar 的 tar 归档文件中:

docker save -o <image-name>.tar <image-name>:<tag>

接下来,使用文件传输协议,如安全复制协议(SCP)或安全文件传输协议(SFTP),将 tar 存档传输到远程机器。你可以在远程机器上使用docker load命令从 tar 存档加载镜像:docker load -i <image-name>.tar。这将把镜像加载到远程机器上的本地 Docker 镜像缓存中。一旦镜像被加载,使用docker run命令启动镜像并运行 Docker 容器,就像你在构建它之后所做的那样。然后,将此镜像添加到你的 Docker compose 文件中,其中包含 Postgres 数据库和 Kafka 实例。

注意:关于 Terraform 的讨论被大大简化了。当你准备好认真使用 Terraform 时,你应该查阅 Scott Winkler 的《Terraform in Action》(Manning,2021 年;www.manning.com/books/terraform-in-action)。

本节探讨了如何打包镜像并在远程主机上加载它们。这个过程是可脚本化的,但随着容器注册库的出现,现在管理部署比以往任何时候都更容易,无需将它们发送到整个互联网。在下一节中,我们将探讨这样一个工具:亚马逊的弹性容器注册库(ECR)。

7.4 以简单方式移动 Docker 镜像

Docker 镜像,我们容器的蓝图,是容器化应用的基本构建块。正确管理它们确保我们保持干净、高效和有序的开发和部署工作流程。Amazon ECR 作为一个完全管理的 Docker 容器注册库,使得开发者能够轻松地存储、管理和部署 Docker 容器镜像。

首先,让我们深入了解如何将 Docker 镜像推送到 ECR。这个过程对于使你的镜像可用于使用和部署至关重要。我们将逐步介绍设置你的本地环境、使用 ECR 进行认证以及推送你的镜像。在我们能够将镜像移动到 ECR 之前,我们必须创建一个用于存放该镜像的仓库。这可以通过 AWS 管理控制台完成,或者,就像我们很快要做的那样,使用 AWS 命令行界面(CLI)。创建用于镜像的新仓库的命令是

aws ecr create-repository --repository-name itam

接下来,你需要使用 ECR 仓库 URL 和镜像名称给你的 Docker 镜像打标签。你可能想称之为latest或使用语义版本控制。打标签将允许你轻松回滚或前进到系统版本。使用以下命令给你的应用程序镜像打上latest标签:

docker tag itam:latest 
123456789012.dkr.ecr.us-west-2.amazonaws.com/itam:latest

现在,使用aws ecr get-login-password命令对 Docker 进行 ECR 注册库的认证。这将生成一个用于认证 Docker 到注册库的 Docker login命令。登录命令如下

aws ecr get-login-password --region us-west-2 | 
docker login --username AWS --password-stdin 
123456789012.dkr.ecr.us-west-2.amazonaws.com

最后,使用docker push命令将 Docker 镜像推送到 ECR 注册库:

docker push 123456789012.dkr.ecr.us-west-2.amazonaws.com/itam:latest

一旦镜像存放在你的注册表中,你的部署选项大大增加。例如,你可以编写一个 bash 脚本,登录到 EC2 实例并执行 docker pull 来下载和在该 EC2 上运行镜像。或者,你可能希望采用更可靠的部署模式。在下一节中,我们将介绍如何在名为 Elastic Kubernetes Service (EKS) 的强大云服务上设置和启动我们的应用程序的过程。EKS 是 AWS 提供的托管 Kubernetes 服务。让我们深入探讨吧!

7.5 将我们的应用程序部署到 AWS Elastic Kubernetes Service

Kubernetes 相比于在 EC2 实例上简单地运行 Docker 镜像提供了许多好处。首先,使用 Kubernetes 管理和扩展我们的应用程序变得更加简单。此外,使用 Kubernetes,我们不需要花费很多额外的时间去思考我们的基础设施应该是什么样子。而且,多亏了其对名为 pods 的镜像生命周期的自动管理,我们的应用程序将基本上是自我修复的。这意味着如果出现问题,Kubernetes 可以自动修复它,确保我们的应用程序始终运行顺畅。

首先,我们需要一个用 YAML(Yet Another Markup Language 或 YAML Ain’t Markup Language,取决于你问谁)编写的部署描述符,这将描述我们希望 ITAM 系统始终保持的状态。这个文件(通常称为 deployment.yaml)将提供 Kubernetes 将与之比较的当前运行系统的模板,并根据需要做出修正。

列表 7.4 ITAM 系统的 Kubernetes 部署文件

# Create a Kubernetes deployment file for the itam application. The image name is itam:latest
# The deployment will run on port 8000

apiVersion: apps/v1
kind: Deployment
metadata:
  name: itam-deployment
  labels:
    app: itam
spec:
  replicas: 1
  selector:
    matchLabels:
      app: itam
  template:
    metadata:
      labels:
        app: itam
    spec:
      containers:
      - name: itam
        image: itam:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8000

然而,这不会起作用。Kubernetes 将无法找到我们在部署描述文件中引用的镜像。为了纠正这个问题,我们需要告诉 Kubernetes 使用我们新创建的 ECR。幸运的是,这并不像听起来那么具有挑战性。我们只需要更新文件中的镜像条目,使其指向 ECR 镜像,以及授予 EKS 访问 ECR 的权限(好吧,可能有点复杂,但它是可管理的)。

首先,更新部署 YAML 以使用 ECR 镜像:

image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/itam:latest. 

然后,你需要为 EKS 定义一个策略,并使用 AWS CLI 或身份和访问管理(IAM)管理控制台应用该策略。尽管应用策略超出了本书的范围,但你可以使用 Copilot 来定义它。生成的策略将类似于以下列表。

列表 7.5 允许 EKS 从 ECR 拉取镜像的 IAM 策略

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPull",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<aws_account_id>:role/<role>"
      },
      "Action": [
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:BatchCheckLayerAvailability"
      ],
      "Resource": "arn:aws:ecr:<region>:<aws_account_id>:
repository/<repository_name>"
    }
  ]
}

一旦 EKS 能够从 ECR 拉取镜像,你将看到一个 pod 开始运行。然而,你无法外部访问这个 pod。你需要创建一个服务。在 Kubernetes 中,服务 是一个抽象概念,它定义了一组逻辑上的 pod(你在 Kubernetes 对象模型中创建或部署的最小和最简单的单元)以及访问它们的策略。

服务使得应用程序的不同部分以及不同应用程序之间能够进行通信。它们通过将 Pod 暴露给网络和其他 Kubernetes 中的 Pod 来帮助分配网络流量和负载均衡。

列表 7.6 Kubernetes 服务文件,以启用我们的应用程序的外部访问

# Please create a service for the application that uses a load balancer type egress
apiVersion: v1
kind: Service
metadata:
  name: itam-service
spec:
  type: LoadBalancer
  selector:
    app: itam
  ports:
  - name: http
    port: 80
    targetPort: 8000

Kubernetes 负责将所有请求从入口路由到服务,然后到正在运行的 Pod,无论它们运行在哪个主机上。这允许无缝故障转移。Kubernetes 预期事情会失败。它依赖于此。因此,许多分布式系统中的最佳实践都内置到了 Kubernetes 中。到达 Kube 是构建一个可靠、高可用系统的重大第一步。在下一节中,我们将探讨如何重复和持续地减轻将我们的应用程序部署到 Kubernetes 的负担。我们将查看如何使用 GitHub Actions 构建一个小型部署管道。

7.6 在 GitHub Actions 中设置持续集成/持续部署(CI/CD)管道

如果发布很困难,那么它就不会经常进行。这限制了我们对应用程序增值的能力,从而也限制了我们对利益相关者的增值。然而,自动化部署过程显著减少了发布所需的时间。这使得更频繁的发布成为可能,加速了开发步伐,并能够更快地将功能交付给用户。持续集成/持续部署(CI/CD)管道限制了与部署相关的风险。通过进行更小、更频繁的更新,任何出现的问题都可以被隔离和快速修复,最小化对最终用户潜在的影响。这些管道促进了代码更改的无缝集成,并加速了部署,简化了软件发布过程。

GitHub Actions 允许我们在 GitHub 仓库中直接构建定制的 CI/CD 管道。这使得开发工作流程更加高效,并能够自动化各种步骤,让我们能够专注于编码,而不是集成和部署的物流。

本节简要介绍了使用 GitHub Actions 和 GitHub Copilot 设置 CI/CD 管道。请注意,这不会是一个全面的指南,而是一个介绍潜在好处和一般工作流程的概述。这应该作为入门指南,让你了解这些工具如何被用来优化你的软件开发过程。

首先,在你的项目中的路径 .github/workflows 下创建一个文件。注意前面的点。你可以把这个文件命名为 itam.yaml 或者你想要的任何名字。在这个文件的第一个行,添加以下提示:

# Create a GitHub Actions workflow that builds the ITAM application on every merge to the main branch and deploys it to EKS. 

注意:像本章中我们交给 Copilot 的许多与基础设施相关任务一样,Copilot 在创建此文件时需要我们提供大量帮助。我们需要了解这个文件的结构以及如何开始每一行。在这种情况下,向 ChatGPT 或 Copilot Chat 请求为我们构建文件是有意义的。

文件的这部分概述了此操作何时应该执行。on:push 指令表示当向主分支进行 git push 时,此操作应该执行。此文件中只有一个作业,包含多个步骤。这个名为“build”的作业使用内嵌函数login-ecr登录到我们的 ECR。

列表 7.7 构建我们的应用程序的 GitHub Actions 文件的开始部分

# Create a GitHub Actions workflow that builds the ITAM application on every merge to the main branch and deploys it to EKS.
name: Build and Deploy to EKS

on:
  push:
    branches:
      - main
jobs:

构建作业首先会从我们的 GitHub 仓库检出代码。它使用模块actions/checkout版本 2 中编写的代码。同样,接下来它将获取 EKS CLI 并配置凭证以连接到 EKS。请注意,AWS 访问密钥和秘密是自动传递到应用程序中的值。GitHub Actions 使用内置的秘密管理系统来存储敏感数据,如 API 密钥、密码和证书。该系统集成到 GitHub 平台中,允许你在仓库和组织级别添加、删除或更新秘密(以及其他敏感数据)。在存储之前,秘密会被加密,不会显示在日志中或可供下载。它们仅作为环境变量暴露给 GitHub Actions 运行器,这是一种处理敏感数据的安全方式。

同样,您可以在操作中创建环境参数并使用它们。例如,看看变量ECR_REGISTRY。这个变量是使用login-ecr函数的输出创建的。在这种情况下,您仍然需要在您的 Actions 文件中硬编码 ECR。然而,您应该这样做是为了保持一致性,并且需要在文件中仅在一个地方管理它。大多数这些步骤应该看起来很熟悉,因为我们已经在整个章节中使用了它们。这就是自动化的魔力:它为您完成这些工作。

列表 7.8 我们的 GitHub Actions 文件的构建和部署步骤

  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v2

    - name: Set up EKS CLI
      uses: aws-actions/amazon-eks-cli@v0.1.0

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-west-2

    - name: Build and push Docker image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: itam
        IMAGE_TAG: ${{ github.sha }}
      run: |
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

    - name: Deploy to EKS
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: itam
        IMAGE_TAG: ${{ github.sha }}
      run: |
        envsubst < k8s/deployment.yaml | kubectl apply -f -
        envsubst < k8s/service.yaml | kubectl apply -f -

文件的最后部分登录到 AWS ECR。Actions 文件中的步骤调用此操作。完成后,它将输出返回到调用函数。

列表 7.9 构建和部署到 EKS 的 GitHub Actions 文件

  login-ecr:
    runs-on: ubuntu-latest
    steps:
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
      with:
        registry: <your-ecr-registry>
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

探索代码即基础设施使我们能够理解它在任何项目中的关键作用以及如何通过代码更好地管理。像 Terraform 这样的工具为管理基础设施提供了简化的解决方案,GitHub 以代码为中心的功能有助于维护整体工作流程。

通过 GitHub Actions 等平台引入 CI/CD 管道,突出了自动化软件交付过程的重要性。自动化此类过程可以提高软件开发生命周期的速度和可靠性,并最大限度地减少人为错误的可能性。

将基础设施作为代码管理的旅程是不断演变的,新的工具和实践不断涌现。这需要持续学习和适应的心态。本章为您展示了其优势和可能性。

摘要

  • 你了解了从应用开发到产品发布的转变,包括部署策略、云基础设施的最佳实践,以及使用 Docker 和 Terraform 高效管理和容器化应用的方法。

  • 本章解释了如何通过 Kubernetes 管理应用部署,包括创建 YAML 部署描述符、形成用于网络流量分配的服务,以及在亚马逊的弹性 Kubernetes 服务(EKS)上部署。

  • 你发现了如何将部署方法适应不同的环境,无论是各种云平台还是本地环境,以及 GitHub Copilot 如何帮助准确创建 Dockerfile 和 Terraform 文件。

  • 最后,我们探讨了将 Docker 镜像导出到远程机器、推送到亚马逊的弹性容器注册库(ECR)以及使用 GitHub Actions 迁移到自动化部署的过程。

第八章:使用 ChatGPT 进行安全的应用程序开发

本章涵盖

  • 使用 ChatGPT 进行威胁建模

  • 使用 ChatGPT 培养安全思维

  • 使用 ChatGPT 缓解风险

在不断发展的软件开发领域,安全担忧已经从事后考虑转变为项目设计和实施阶段的重要组成部分。尽管这种提高的关注度,开发者们往往发现难以跟上应用程序安全领域快速变化的步伐。本章提供了对如何将 AI,特别是 ChatGPT,嵌入到应用程序开发过程的各个阶段以增强应用程序安全性的全面理解,为构建更安全的软件应用程序提供了一套新颖的工具。

随着我们深入探讨这个主题,我们将探讨如何将 ChatGPT 集成到 ISAM 应用程序的开发过程的各个阶段,该应用程序是用 FastAPI 编写的 Python。我们将讨论这个 AI 模型如何帮助识别漏洞、参与威胁建模、评估应用程序设计以识别潜在的不安全性、理解和应用安全最佳实践。

本章的目标不是将 ChatGPT 定位为解决所有安全问题的银弹,而是展示其在开发者安全工具包中作为强大工具的潜力。我们将学习如何积极识别和管理威胁,同时考虑到创建不仅功能性强而且安全的软件的整体目标。在这个过程中,我们将探讨建模威胁、在开发生命周期中融入安全、AI 在安全中的作用等众多话题。让我们开始吧!

安全不是一个特性

安全的应用程序始于设计。虽然它经常被当作一个特性来对待,但它并不是一个特性。生成式 AI 是可用于评估和改进应用程序安全性的工具,但它们不会取代安全专家的输入,也不会使你成为安全专家。有关设计安全应用程序的更多信息,请参阅丹·伯格·约翰逊、丹尼尔·德古恩和丹尼尔·萨瓦诺所著的《设计安全》(Manning,2019;www.manning.com/books/secure-by-design)。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH08_F01_Crocker2b.png

软件生命周期(错误)的心理模型,其中安全被视为一个需要根据项目需要优先考虑和降级的特性,或者作为一个在生命周期某个阶段执行的附加功能。然而,安全是一种需要在所有阶段都放在首位的心态。

8.1 使用 ChatGPT 进行威胁建模

威胁建模是一种结构化的方法,它帮助团队理解、优先排序和管理系统中潜在威胁。通过模拟攻击者的心态,威胁建模系统地识别漏洞、评估潜在影响,并确定缓解策略。根植于设计阶段,但与整个软件开发生命周期相关,威胁建模在高级安全策略和实际操作之间发挥着至关重要的桥梁作用。

威胁建模不是一个一次性过程。随着新漏洞的发现,系统和外部环境发生变化,因此你必须重新审视并更新你的威胁模型。

8.1.1 为什么它在当今的开发环境中很重要

在我们深入探讨使用 ChatGPT 进行威胁建模之前,我们需要退一步思考,为什么我们首先想要做这件事。在当今的开发环境中,对安全的重视程度越来越高,我们必须关注那些显著改变软件开发、部署和访问方式的因素。随着更多服务数字化,攻击面变得更广。从在线银行到健康记录、电子商务,甚至政府服务,现在都可在网上获取,使其成为潜在的目标。

此外,网络威胁不是静态的。新的漏洞每天都在出现,攻击者不断制定新的方法。随着国家支持的网络攻击、勒索软件和网络间谍活动的兴起,危险从未如此严重。

现代架构比以往任何时候都要复杂,因为应用程序通常使用微服务、第三方 API 和云基础设施。这种复杂性可以引入多个潜在的漏洞点。系统不再是孤立的,而是与其他系统相互连接,产生多米诺效应。一个系统的漏洞可能成为攻击其他系统的跳板。

安全漏洞

除了直接的财务影响之外,安全漏洞还可能损害信任,损害公司的声誉,导致法律后果,并导致客户或商业机会的损失。此外,随着欧洲的通用数据保护条例(GDPR)和美国加州消费者隐私法案(CCPA)等法规的实施,组织在保护用户数据方面承担着更大的责任。不遵守规定可能导致重大罚款。

在一个互联、以数字优先的世界里,安全不仅仅是 IT 问题,更是基本的企业需求。确保应用程序从底层开始就考虑安全性可以降低风险和成本,建立信任,并确保系统连续性。

8.1.2 ChatGPT 如何帮助进行威胁建模

现在我们已经理解了原因,让我们转向如何利用 ChatGPT 来了解我们周围的网络安全威胁、它们的影响以及潜在的缓解技术。ChatGPT 拥有广泛的网络安全基础知识的知识库;它可以定义标准术语,并以适合你网络安全旅程的任何详细程度向你解释复杂的攻击向量。你可以先要求它解释网络安全原则,什么是 SQL 注入攻击(但不是如何执行它!),或者什么是点击劫持。

作为一种非正式的建模威胁的方法,你可以向 ChatGPT 提出详细假设场景,并询问在这些情况下可能出现的潜在威胁或漏洞。开始时要非常一般化,随着过程的进行进行细化。例如,你可能输入以下提示:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

如果我正在开发一个基于云的电子商务 Web 应用,我应该注意哪些威胁?

然后深入挖掘,围绕特定威胁进行三角定位:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

攻击者如何在我的电子商务应用中劫持用户的购物车?

接下来,你可以与 ChatGPT 互动,了解如何评估与各种威胁相关的风险。这可以帮助你确定哪些威胁应该首先解决。在了解一些可能针对你系统的威胁后,你可以与 ChatGPT 讨论潜在的对策、最佳实践和缓解策略。

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

我如何评估我的在线服务遭受 DDoS 攻击的风险?

然后,

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

防止跨站脚本攻击的最佳实践是什么?

你需要定期与 ChatGPT 互动,以刷新你的知识或询问你遇到的新概念或策略。

然而,有一个快速警告:就像往常一样,你必须意识到 ChatGPT 的限制。它没有实时威胁情报或超出其最后更新的知识。对于最新的威胁,请始终咨询最新的资源。尽管 ChatGPT 是一个有价值的工具,但始终与其他权威来源交叉验证其见解。网络安全迅速发展,与多个可信来源保持更新至关重要。在与 ChatGPT 讨论特定威胁后,你可能想查阅来自开放世界应用安全项目(OWASP)、国家标准与技术研究院(NIST)和其他公认网络安全实体等组织的最新文档。

最后,与 ChatGPT 进行互动头脑风暴可以帮助你有效地生成想法,理解复杂的概念,或完善策略,尤其是在像网络安全这样的领域。以下是如何构建和执行此类会议的步骤:

1.  明确说明头脑风暴会议的目标。例如,可以是识别系统中的潜在漏洞、为新的应用程序生成安全措施或讨论提高用户数据保护策略。

2.  通过向 ChatGPT 提供详细背景信息开始会议。如果是关于特定系统或应用程序,请描述其架构、组件、功能以及任何已知的问题或担忧。例如,您可以说

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

我正在开发一个基于 Web 的电子商务平台,使用 Docker 容器进行微服务架构。我们正在寻找潜在的安全威胁。

根据 ChatGPT 的回应,深入探讨特定领域或关注点。例如,您可能说

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

请告诉我更多关于容器安全最佳实践的信息。

或询问

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

我如何确保微服务之间的通信安全?

3.  向 ChatGPT 提出假设情景,并请求反馈或解决方案。这有助于预测潜在的挑战或威胁:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

假设攻击者获得了对其中一个容器的访问权限;应该采取哪些步骤?

4.  通过扮演魔鬼的代言人,与 ChatGPT 互动。质疑或反驳它提供的想法或建议,以激发进一步的思考并探索不同的角度:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

如果我使用第三方身份验证服务怎么办?这会如何改变安全格局?

5.  向 ChatGPT 请求具体的步骤或行动项来实施建议的解决方案。例如,您可以询问,

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

针对您提到的安全担忧,我应该采取哪些具体步骤来减轻它们?

注意:随着头脑风暴的进行,记录 ChatGPT 提供的思想、建议和策略。它们在会议后的回顾和实施中将非常有价值。头脑风暴在迭代时最为有效。根据您在一次会议中学到的知识,您可能需要细化问题、调整方法或在下一次会议中探索新的领域。

图 8.1 显示了在头脑风暴会议期间执行的安全反馈循环。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/CH08_F02_Crocker2.png

图 8.1 与 ChatGPT 进行头脑风暴会议的工作流程

随着您的项目或场景的进展,重新与 ChatGPT 讨论,以考虑任何变化、更新或新的挑战。最近的更新允许您上传您的系统设计文档,并让 ChatGPT 审查该设计以识别潜在威胁和常见漏洞,就像它可以评估代码一样。

通过以这种方式使用 ChatGPT,您可以从中广泛的知识库中受益,并为您的场景获得有价值的反馈和见解。始终记得将建议与相关领域的最新资源和专家进行交叉验证。

8.1.3 案例研究:使用 ChatGPT 模拟威胁建模

除了与 ChatGPT 进行场景分析和互动头脑风暴会议之外,您还可能决定应用一种正式的方法,这是网络安全专业人士常用的:STRIDE。STRIDE 是由微软引入的一种威胁建模方法,旨在帮助识别系统或应用程序中的潜在安全威胁。您可以使用 ChatGPT 和 STRIDE 来模拟威胁并相应地进行分类。

STRIDE

STRIDE 这个缩写代表欺骗、篡改、否认、信息泄露和拒绝服务:

  • *欺骗—*冒充他人或他物。这可能意味着承担用户的身份、设备或甚至系统的身份以获得未经授权的访问。例如,攻击者可能通过欺骗媒体访问控制(MAC)地址来冒充网络上的设备。

  • *篡改—*未经授权修改数据或代码。例如,攻击者可能在中间人攻击中修改传输的数据包。

  • *否认—*恶意(或粗心)用户否认他们执行了特定行为的能力,即使他们确实执行了,也因为缺乏这些行为的证据。例如,用户可能否认下订单或更改某些数据。

  • *信息泄露—*将信息暴露给那些不应有权访问它的人。例如,数据库配置错误可能将敏感用户数据暴露给公众。

  • *拒绝服务(DoS)—*使资源对预期用户不可用或不响应。这可以通过耗尽系统资源或利用特定漏洞来实现。例如,在 DDoS 攻击期间,攻击者可能通过流量淹没一个网站,导致其变慢或完全无响应。

  • *权限提升—*无特权的用户获得了提升的权限,通常允许他们控制整个系统或执行他们未经授权的任务。

在本书的整个过程中,我们一直在开发 ITAM 系统的一部分。作为提醒,该系统用于管理和优化组织的 IT 资产。在本节中,我们将应用 STRIDE 到我们的应用程序中,使用 ChatGPT 协助建模针对该系统的威胁。

让我们从欺骗开始:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

在我们 ITAM 系统的背景下,我们的系统可能如何被欺骗?

ChatGPT 可能响应的内容可能与以下列表类似。

列表 8.1 ChatGPT 解释欺骗可能如何影响我们的应用程序

MAC Address Spoofing: Most ITAM systems rely on MAC addresses to identify and manage devices on a network. An attacker might spoof the MAC address of an authorized device to gain unauthorized access or to mask a rogue device's presence.
IP Address Spoofing: An attacker could bypass network security measures or divert traffic by masquerading as a trusted IP address. This could lead to unauthorized access to the ITAM system or even denial-of-service attacks.
DNS Spoofing (Cache Poisoning): If the ITAM system relies on domain names for accessing updates or related resources, DNS spoofing can redirect the ITAM server to malicious sites, potentially compromising the system.
Device Identifier Spoofing: Beyond MAC addresses, devices may have other unique identifiers. Spoofing these can allow unauthorized devices to appear as legitimate assets in the ITAM inventory.
User Agent Spoofing: If the ITAM system is accessed via web browsers, attackers can spoof user agent strings to mimic authorized devices or browsers, potentially bypassing certain security checks.
Fake Asset Registration: A malicious actor might introduce fake assets into the system, leading to inaccurate asset tracking, skewed analytics, and potential security loopholes.
Spoofed Update Servers: If your ITAM solution fetches updates from remote servers, attackers could spoof these servers, delivering malicious updates or software.
Phishing & Social Engineering: While not a technical spoofing method per se, attackers can still spoof emails or other communications, pretending to be ITAM administrators or vendors. Unsuspecting employees might then provide sensitive ITAM data.

此外,它还可能响应常见的预防或缓解步骤。

列表 8.2 ChatGPT 概述防止此类利用的机制

Network Security: Implement network segmentation, strong firewalls, and Intrusion Detection Systems (IDS) to monitor and block suspicious activities.
Encryption: Encrypt data in transit to protect against man-in-the-middle attacks that could lead to spoofing.
Dynamic Identification: Rotate device identifiers periodically or use dynamic identifiers.
DNS Security: Implement DNS Security Extensions (DNSSEC) to ensure the authenticity of DNS responses.
Authentication and Authorization: Require multi-factor authentication (MFA) for ITAM system access and ensure strict role-based access control.
Regular Audits: Audit the ITAM database frequently to identify discrepancies or rogue assets.
User Education: Regularly train IT personnel and employees about the risks of spoofing and the importance of verifying communications.

我们不一一列举 STRIDE 的所有内容,接下来让我们转向否认。这是所有攻击中最不直观的,也是被误解最多的:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

在我们 ITAM 系统的背景下,我们的系统可能受到否认的影响吗?

ChatGPT 可能在我们系统的背景下对这种风险给出定义。

列表 8.3 ChatGPT 从网络安全的角度定义否认

Repudiation, in a cybersecurity context, refers to the ability of a user (or system) to deny having performed a specific action, even if they did perform it. Regarding ITAM (Information Technology Asset Management) systems, repudiation can pose challenges regarding accountability, traceability, and compliance. Let's explore how repudiation might affect an ITAM system:

然后它将这些可能影响我们 ITAM 系统的各种否认风险进行分类。

列表 8.4 ChatGPT 概述的可能否认风险

Denial of Asset Changes: A user might make unauthorized changes to an asset's status or details in the ITAM system and later deny having made such changes. This can lead to inaccuracies in asset tracking, causing potential compliance issues and operational inefficiencies.
Lack of Accountability: If actions within the ITAM system are not appropriately logged or if logs can be altered without a trace, it becomes challenging to hold individuals accountable for their actions. This can result in unauthorized software installations, hardware misallocations, or asset misuse without any accountability.
Financial Discrepancies: Repudiation can lead to financial implications. For instance, if an employee claims they never received a particular hardware asset (like a laptop) when the ITAM system shows they did, it might result in unnecessary additional procurements.
License Violations: Users might install unauthorized software and then deny doing so. Without proper non-repudiation measures, tracking and proving such installations becomes difficult, leading to potential licensing violations.
Security Implications: Malicious insiders or external attackers could exploit the ITAM system to steal or damage assets and deny any wrongdoing. This could pave the way for data breaches, theft of sensitive data, or even financial fraud.

ChatGPT 将然后为我们提供减轻这些风险的潜在方法。

列表 8.5 处理否认风险的潜在修复技术

Strong Authentication: Ensure that users accessing the ITAM system go through rigorous authentication processes, ideally with multi-factor authentication (MFA), to ensure that actions can be attributed to specific individuals.
Immutable Logging: Implement tamper-proof logging systems. Every action, change, or transaction within the ITAM should be logged with a timestamp and associated user details.
Digital Signatures: Crucial actions, especially those related to financial transactions or significant asset changes, require a digital signature. This offers an additional layer of verification and non-repudiation.
Role-Based Access Control (RBAC): Ensure users have specific roles and permissions. Limiting access based on job roles can reduce the risk of unauthorized actions and make it easier to trace back activities to particular users.
Regular Audits: Periodically review and audit the logs and transactions in the ITAM system to detect any discrepancies or potential repudiation attempts.
User Training: Educate ITAM system users about the importance of accountability and the consequences of unauthorized actions. Making users aware can reduce inadvertent repudiation instances.
Whistleblower Policies: Encourage employees to report any suspicious activities without fear of retaliation.

随着开发实践的演变,我们面临的网络威胁也在不断变化。在本节中,我们探讨了 ChatGPT 的具体属性,使其成为识别和预测潜在网络威胁的有价值资产。凭借其模拟对话、快速分析大量数据和基于模式提供见解的能力,ChatGPT 成为一个有希望的工具体现,可以增强传统的威胁建模方法。

最后,我们简要介绍了 ChatGPT 在威胁建模中的实际应用。这个案例研究强调了在现实生活中的网络安全场景中使用 AI 的实用性,突出了将 ChatGPT 集成到威胁建模过程中的成功、挑战和经验教训。

虽然使用 ChatGPT 等工具模拟外部威胁对于限制或预防对组织及其数据的损害非常有价值,但同样重要的是将我们的注意力转向我们系统的内部,并识别其中的漏洞。我们将在下一节中这样做。

8.2 仔细审查应用程序设计并识别潜在漏洞

现代应用程序设计的复杂性有时会隐藏功能和安全性中的细微缺陷。利用 ChatGPT 的分析能力,我们可以深入这些设计,超越传统的人类疏忽和偏见。凭借其广泛的知识和模式识别能力,ChatGPT 提供了一个独特的视角,能够发现常见漏洞并提供可操作的反馈。在本节中,我们将探讨如何通过咨询 ChatGPT,批判性地评估应用程序设计,确保它们不仅功能强大且用户友好,而且能够抵御潜在的威胁。

8.2.1 评估设计问题

要开始让 ChatGPT 评估我们的设计,我们可以发出以下提示:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

请审查此应用程序设计并指出设计问题。设计以 mermaid 格式呈现

然后我们将第二章中的 mermaid 图表复制进来。以下是一些 ChatGPT 在我们的设计中发现的简略问题列表(有趣的是,其中许多建议最初是由 ChatGPT 提供的)。

列表 8.6 ChatGPT 提供的简略设计问题列表

The design described in the Mermaid class diagram is a comprehensive representation of multiple entities involved in an asset management system, with focus on the various assets and their depreciation strategies. Here are some points to consider about this design:

1\. Inheritance vs Composition:
    - The strategies 'StraightLineDepreciationStrategy', 'DecliningBalanceDepreciationStrategy', 'DoubleDecliningDepreciationStrategy', and 'NoDepreciationStrategy' should ideally inherit from 'DepreciationStrategy' to ensure they adhere to the same interface.

2\. DepreciatingAsset class:
    - 'DepreciatingAsset' appears to have a partial definition, perhaps it should either inherit from 'Asset' or be more fleshed out.

3\. Visitor Pattern:
    - The 'LineOfBusinessVisitor' class appears to be implementing the visitor pattern. However, it's not clear if all the methods are necessary. Do you expect every visitable object type ('Hardware', 'Software', 'DepreciatingAsset') to have distinct logic?

这并不是一个详尽的列表,但它确实为我们提供了一个反思和改进设计的起点。

8.2.2 识别常见漏洞

在本节中,我们深入探讨 ChatGPT 如何利用其广泛的知识库和模式识别能力,在软件设计中揭示隐藏的弱点和潜在威胁,标志着主动网络安全措施的革命性进步。我们将向它询问,

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

此设计中是否存在任何漏洞或利用?

它从长长的列表中吐出了以下几个关键问题。

列表 8.7 ChatGPT 发现的漏洞和利用的简略列表

1\.  Password Security:
The User class stores a password field. It's essential that passwords are not stored as plaintext for security reasons. Consider using a hashing mechanism (e.g., bcrypt) to store password hashes rather than raw passwords.

2\.  Role-Based Access Control (RBAC):
While the Role class seems to address RBAC by having a list of permissions, ensure that the system implementing this design enforces these roles and permissions appropriately. Be particularly careful about functions or methods that modify user roles or permissions.

我们可以通过询问 ChatGPT 如何最好地实施其建议来继续这种质询。

在前面的章节中,我们看到了 ChatGPT 如何帮助发现软件设计中的问题。这是一个强大的工具,但正如之前所阐述的,它并不能(也不能)取代对有技能的人的需求。可以这样想:ChatGPT 是一个方便的助手,但人类仍然掌舵。当我们结合使用人工智能和人类智慧时,我们可以使我们的网络世界更安全。这完全是关于团队合作,并利用两者的最佳之处。

8.3 应用安全最佳实践

本节是您从开始到结束进行强大软件安全性的必读指南。首先,我们将讨论拥有以安全为重点的心态的重要性。接下来,我们将讨论定期进行安全测试的必要性,以保持一切紧绷。然后,我们将讨论定期的检查或审计,以确保一切保持最新。简而言之,我们将探讨安全工具和正确思考方式。

8.3.1 培养安全心态

安全不仅仅是使用正确的工具或遵守最新的协议;它是在每个行动、决策和策略中与安全交织在一起的心态。本节将指导您了解建立这种心态的基础原则,强调在面临现代网络挑战时,警惕性、适应性和持续学习的重要性。

ChatGPT 可以作为安全培训和意识提升的强大工具。您可以用它作为互动工具,教您的团队网络安全的基础,例如什么是跨站脚本攻击以及它是如何工作的。您可以用它来规划事件响应:例如,询问如果您的服务器已被入侵,您应该采取的第一步是什么。

您还可以使用 ChatGPT 来生成安全编码指南和建议。例如,您可以要求它

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

为 Python 提供安全的编码指南

ChatGPT 将为您提供一份长长的列表,包括诸如输入验证、确保您的库安全以及如何安全存储和运行时注入密码的建议。

我们还可以使用 ChatGPT 提供特定于应用程序的指导。例如,

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

推荐一个安全部署 Flask Web 应用程序的步骤。

尽管有无数种方法可以确保我们的应用程序安全,ChatGPT 为我们提供了一个精心挑选的列表。以下列出了选择。

列表 8.8 ChatGPT 关于确保 Flask 安全的顶级建议简表

Deploying a Flask web application securely requires a multifaceted approach that covers everything from code-level security practices to server and network configurations. 

Here are the recommended steps:
1\. Use the Latest Flask Version: Always use the most recent Flask version, as it typically includes security patches for known vulnerabilities.

2\. Avoid Debug Mode in Production: Never run a Flask application with app.debug = True in a production environment. Debug mode can expose sensitive information.

3\. Use a Secure Web Server: Deploy your Flask application behind a production-ready web server like Gunicorn, uWSGI, or mod_wsgi. These are robust servers that can handle real-world traffic and have better security mechanisms.

4\. Implement HTTPS:
Use SSL/TLS to encrypt data between the server and client.
Use tools like Let's Encrypt to get a free SSL certificate.

在了解如何安全地开发 Python 软件,特别是基于 Flask 的微服务之后,我们可以开始测试我们的应用程序。

8.3.2 持续安全测试

在本节中,我们探讨 ChatGPT 庞大的知识库如何帮助简化决策过程,提供对工具功能的见解,并就有效使用工具以实现最佳应用程序安全提供指导。我们将首先检查静态分析工具和动态分析工具之间的差异。然后我们将使用 ChatGPT 在我们的选择过程中,并看看我们如何使用它来部署工具。

静态应用程序安全测试(SAST)是一种在执行应用程序之前检查应用程序的源代码、字节码或二进制代码的测试过程。SAST 的主要目标是通过对代码库进行分析并识别可能导致安全漏洞的模式或代码段,在开发早期阶段发现安全漏洞。这种方法允许开发者在应用程序运行之前解决漏洞,从而能够检测到在运行时或动态测试中可能被遗漏的问题。SAST 工具通常可以集成到开发环境中,从而实现持续反馈和更快地修复潜在的安全问题。

动态应用程序安全测试(DAST)是一种在应用程序运行时识别安全漏洞的测试过程,通常从外部攻击者的角度进行。与 SAST 不同,SAST 分析源代码、字节码或二进制代码,DAST 评估应用程序的运行时环境。这种方法可以识别在代码中不明显但在执行过程中出现的漏洞,例如与身份验证、会话管理、数据验证和其他运行时相关的问题。DAST 工具通常通过模拟对运行中的应用程序的攻击并监控其响应来运行,这使得它们在检测可被实际攻击利用的漏洞方面特别有用。

考虑到这种区别,我们将要求推荐我们应该为我们的应用程序使用什么:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

你会推荐使用 SAST 还是 DAST 来测试基于 Flask 的 Python 应用程序?

ChatGPT 可能会输出两种测试方法的比较,然后最终提供推荐。这个推荐可能类似于以下列表。

列表 8.9 ChatGPT 关于确保 Flask 安全的顶级建议简表

For a Flask-based Python application, it's beneficial to use both:

Start with SAST during the development phase to catch potential vulnerabilities early on.

As you move to testing and deployment stages, employ DAST to uncover vulnerabilities that may only be evident when the application is running.

In essence, using both SAST and DAST in tandem provides a more comprehensive security assessment and ensures a robust Flask application.

已注意!我们需要努力使用两者。然而,我们将从 SAST 工具开始。让我们通过以下简单提示来获取使用哪个特定库/工具的建议:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

推荐用于 Python 基于 Web 应用程序的 SAST 工具。

列表 8.10 ChatGPT 对 SAST 工具的建议

For Python-based web applications, several Static Application Security Testing (SAST) tools can help identify vulnerabilities and insecure coding patterns. 

Here are some recommendations:
Bandit: An open-source SAST tool developed by the OpenStack community, Bandit scans Python code to find common security issues in the codebase. It is easily integrated into the CI/CD pipeline and can be customized to target specific issues.

Pyre: Developed by Facebook, Pyre is a performant type checker that can also detect and flag security issues in Python code. It's built to scale and can handle large codebases.

Semgrep: A versatile code analysis tool that provides out-of-the-box support for Python and multiple other languages. Semgrep rules can be tailored for various security checks, and the tool offers a wide range of community-contributed rules for common security issues.

Checkmarx: A commercial tool that supports multiple languages, including Python. Checkmarx offers comprehensive SAST capabilities and can integrate seamlessly into the development lifecycle.

将 SAST 工具集成到我们的 CI/CD 流水线中的想法非常吸引人。这种集成应确保这一关键步骤不会被遗漏或绕过,因为它已经嵌入到部署流程中。因此,让我们继续使用 Bandit。但如何操作呢?我们也可以向 ChatGPT 寻求这方面的帮助:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

如果我们想将 bandit 集成到我们的项目中,我们应该如何操作?

列表 8.11 ChatGPT 安装和使用 Bandit 的说明

Integrating Bandit into your project is a straightforward process. Bandit is a tool designed to find common security issues in Python code. Here's a step-by-step guide to integrate it:

Installation:
You can install Bandit via pip:
pip install bandit 

Run Bandit:
To run Bandit on your project, navigate to the root directory of your Python project and run:
bandit -r . 

The -r flag tells Bandit to recursively scan all files in the given directory. The . indicates the current directory. You can replace . with the specific path if needed.

Configuration:
You can use a .bandit file to customize Bandit's behavior. This allows you to exclude certain tests, ignore certain directories, and more.
For instance, if you want to exclude a certain directory:
bandit -r . --exclude /path/to/excluded/dir

尽管 ChatGPT 可以在理解测试结果和提出下一步行动建议方面发挥强大的辅助作用,但请始终记住,它应该补充而不是取代专家判断。特别是在安全等关键领域,将 ChatGPT 的指导与其他资源和专家意见进行核实至关重要。

8.4 在静态和传输中加密数据

本节将探讨如何利用 ChatGPT 的知识和分析能力,以获得关于有效加密数据的定制指导。无论你是寻求基础见解的新手,还是希望进行更深入研究的专家,ChatGPT 都准备好提供帮助。让我们借助这个先进的 AI,开始一段加强数据安全之旅。

8.4.1 数据加密的重要性

静态数据——即存储时的数据(与传输中的数据相对,传输中的数据正在传输中)——如果没有得到保护,可能会带来重大风险。未加密的数据可以轻易被未经授权的个人访问和读取,使其成为网络犯罪分子的首要目标。如果恶意行为者能够访问存储这些数据的系统或网络,他们可以毫无障碍地提取有价值的信息。

对于企业来说,财务数据(如信用卡详情)的泄露可能导致巨大的经济损失,包括盗窃本身以及可能的诉讼或监管机构的罚款。许多地区和行业都有严格的数据保护法规。不遵守规定,如未能加密敏感数据,可能导致高额罚款和法律诉讼。如前所述,欧盟的 GDPR 和加州的 CCPA 是显著的例子。

没有加密,数据不仅可能被读取,还可能被未经授权的第三方更改。这可能导致错误信息、数据损坏或恶意行为,如毒化数据集。由于未加密数据导致的泄露可能会严重损害组织的声誉,导致客户、客户和合作伙伴失去信任。

需要注意的是,尽管加密是安全的关键层,但它并非万能药。有效的数据安全需要多层方法,包括访问控制、定期安全审计、安全备份和用户教育等措施。

未加密的数据是导致全球多起重大数据泄露事件的关键因素。其中一个最近且臭名昭著的真实世界事件,由于未能正确保障数据,是 2015 年的 Ashley Madison 泄露事件。这次对流行约会网站的攻击导致 3700 万用户的用户数据泄露,包括真实姓名、地址等更多信息。它给用户带来了广泛个人和职业后果,引发了几起诉讼,并对公司的形象造成了重大损害。

类似于此类事件强调了不正确加密和保障数据可能带来的巨大后果。除了经济损失外,公司还会遭受声誉损害、客户信任度降低、法律后果以及更多问题。

8.4.2 静态数据加密

加密是确保数据安全的基础,选择正确的算法至关重要。加密方法可能很复杂,但 ChatGPT 可以通过提供基本加密概念的解释来指导您做出明智的选择,例如对称加密与不对称加密、块加密与流加密,以及公钥基础设施。它可以解释广泛使用和行业认可的算法,如高级加密标准(AES)、RSA、椭圆曲线密码学(ECC)等,以及这些算法的优点、潜在漏洞和理想用例。

在本节中,我们将与 ChatGPT 合作,了解如何安全地将一些敏感应用程序数据存储在数据库中。我们将从一般问题开始,询问如何使用 Python 加密和存储这些数据,使用以下提示:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

如何在 Python 中加密敏感数据?

ChatGPT 将解释如何使用加密库进行加密,并提供如何加密和解密文本的示例。

列表 8.12 ChatGPT 解释如何在 Python 中加密数据

Encrypting sensitive data in Python involves using encryption libraries to transform plain text into ciphertext so that only someone with the correct decryption key can read it. One of the most popular libraries for encryption in Python is cryptography.

Encrypting Data
from cryptography.fernet import Fernet 
# Generate a key 
key = Fernet.generate_key() 
cipher_suite = Fernet(key) 

# Your sensitive data 
plaintext = b"My Secret Data"

# Encrypt the data 
ciphertext = cipher_suite.encrypt(plaintext) 
print(f"Encrypted Data: {ciphertext}") 

Decrypting Data
# Use the same key to decrypt 
decrypted_text = cipher_suite.decrypt(ciphertext) 
print(f"Decrypted Data: {decrypted_text.decode('utf-8')}")

除了这个例子,ChatGPT 可能还会提供一些笔记和几点注意事项。以下警告是在询问如何进行加密的会话期间提供的。

列表 8.13 ChatGPT 提供加密最佳实践

Always store the key safely. Anyone with access to the key can decrypt your encrypted data.

It's often advisable to not hard-code your key within the script. Instead, use secure mechanisms to store and retrieve the encryption key.

The above example uses symmetric encryption, where the same key is used for encryption and decryption.

For added security layers, you might also consider implementing other techniques such as hashing (for password storage), public-private key encryption (asymmetric encryption), and more.

Make sure you understand the basics and implications of encryption, as it's a critical area, especially when handling sensitive or personal data.

接下来,我们可能想询问 ChatGPT 如何将加密应用于我们数据库中存储的数据:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

我如何使用这个来在数据库中存储敏感数据?

生成的输出将先介绍一些一般最佳实践和指南,然后再进入如何实现的示例。

列表 8.14 ChatGPT 数据库加密最佳实践

Storing sensitive data in a database using encryption is a common practice to ensure that even if the database is compromised, the data remains secure and unreadable without the encryption key. Here's a step-by-step approach to use the encryption technique discussed above for storing sensitive data in a database:

1\. Key Management:
   - Generate a key using 'Fernet.generate_key()'.
   - Store this key securely, such as in a hardware security module (HSM), a secure key management service (like AWS KMS or Azure Key Vault), or in environment variables for local projects. Never store the key in the database or in your code directly.

2\. Data Encryption:
   - Before inserting the data into the database, encrypt it using the 'cryptography' library as shown in the previous answer.

3\. Database Interaction:
   - Use your preferred database connection library (like 'sqlite3', 'psycopg2' for PostgreSQL, 'PyMySQL' for MySQL, etc.) to insert the encrypted data into your database.
   - For retrieval, fetch the encrypted data from the database and decrypt it using the stored key.

4\. Decryption:
   - Whenever you need to read the sensitive data, retrieve the encrypted value from the database and decrypt it using the key.

在概述最佳实践后,ChatGPT 可能会提供一个示例,说明您如何加密和解密您可能想要存储在数据库中的数据。在本次会话中,它提供了一个使用 SQLite3 的示例。我们本可以指定它提供一个使用任何框架的示例;SQLite3 只是碰巧在这个例子中使用了它。与大型语言模型的大部分输出一样,输出高度依赖于许多因素。

列表 8.15 ChatGPT 使用 SQLite3 存储和检索数据的示例

import sqlite3
from cryptography.fernet import Fernet

# Key management
key = Fernet.generate_key()  
# Ideally, you'd store this securely and retrieve as needed
cipher_suite = Fernet(key)

# Encrypt the data
plaintext = b"My Secret Data"
ciphertext = cipher_suite.encrypt(plaintext)

# Create database and table
conn = sqlite3.connect('sensitive_data.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS 
secure_data (id INTEGER PRIMARY KEY, data BLOB)''')
conn.commit()

# Insert encrypted data into database
cursor.execute("INSERT INTO secure_data (data) VALUES (?)", (ciphertext,))
conn.commit()

# Retrieve and decrypt data
cursor.execute("SELECT data FROM secure_data WHERE id=?", (1,))
encrypted_data = cursor.fetchone()[0]
decrypted_data = cipher_suite.decrypt(encrypted_data)
print(decrypted_data.decode('utf-8'))

conn.close()

此外,ChatGPT 将在您尝试确保数据静态安全时提供一些重要考虑事项。

列表 8.16 ChatGPT 最终重要注意事项

- When designing for production systems, consider using a dedicated ORM like SQLAlchemy or Django's ORM to handle database interactions.

- Always back up your encryption keys. Losing the key means losing access to the encrypted data.

- Consider implementing additional layers of security, like hashing for passwords and regularly rotating your encryption keys.

- Ensure that the rest of your application is also secure. Encryption protects data at rest, but you should also think about securing data in transit (using HTTPS, for example) and validating input data to prevent SQL injections and other vulnerabilities.

最后提醒一点,这一点非常重要:尽管 ChatGPT 可以提供大量有关加密的信息和指导,但与领域专家合作并考虑当前行业标准,特别是对于关键应用来说至关重要。随着计算能力的增长和新漏洞的发现,加密领域不断演变,因此保持更新至关重要。

8.4.3 传输中的数据加密

作为开发者,我们不仅负责创建高效和功能性的应用程序,还要确保这些应用程序能够抵御潜在的威胁。本节深入探讨如何利用 ChatGPT 的力量来分析、评估和加强您的 REST 端点的安全性。利用 ChatGPT 庞大的知识库和分析能力,我们将探讨主动措施、最佳实践和实时解决方案,以确保您的端点免受威胁。让我们踏上将先进的人工智能与强大的安全协议相结合,构建更安全、更具弹性的应用程序的旅程。

我们将征求 ChatGPT 关于如何确保我们的数据在传输过程中的安全性的建议:具体来说,如何对从我们的 REST 控制器到网页浏览器的流量进行端到端加密。我们可以使用以下提示:

|

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-pwr-dev/img/logo-NC.png

我们如何确保我们的 Flask REST 端点安全?

列表 8.17 ChatGPT 关于如何确保我们的 REST 应用程序安全的建议

Securing your Flask REST endpoints is crucial to protect your application and its data. Here's a step-by-step guide to enhance the security of your Flask REST API:

1\. Use HTTPS:
Always serve your Flask application over HTTPS to encrypt data in transit. This can prevent man-in-the-middle attacks. You can use libraries like Flask-Talisman or set up a reverse proxy with Nginx/Apache that serves the application over HTTPS.

2\. Authentication:
Token-based authentication (e.g., JWT): Use libraries like Flask-JWT or Flask-JWT-Extended to implement JWT-based authentication.
OAuth: If you're building a third-party application, consider using OAuth to allow users to grant limited access to their data without exposing their credentials.

3\. Authorization:
Ensure that authenticated users can only access resources they are authorized to. For example, a user should not be able to delete another user's data unless they have the proper permissions.

4\. Rate Limiting:
Use extensions like Flask-Limiter to restrict the number of requests a client can make to your API within a specified time frame.

5\. Data Validation:
Always validate incoming data to avoid injection attacks, unexpected behaviors, or crashes. Use tools like Marshmallow or Flask-Inputs.

6\. Avoid Exposing Stack Traces:
Ensure that your Flask application is running in production mode (app.debug = False) to prevent potentially sensitive information from being exposed in error messages.

在现实世界中,我们会继续这个过程以确保我们的 Kafka 主题的流量安全(尽管在我们聘请安全工程师之前不会这么做)。

另一点需要注意的是:安全性通常涉及评估权衡并确定哪些是可以接受的。例如,在实施加密时,了解相关的权衡至关重要,如潜在的性能影响、增加的复杂性和管理开销。例如,将会有额外的计算开销。加密和解密过程需要计算资源。特别是对于提供更高安全性的算法,计算成本可能相当高。您需要将其纳入您的容量规划中。

此外,延迟几乎肯定会增加。如果加密没有得到优化,实时操作,如流媒体服务和语音通信,可能会出现明显的延迟。加密、传输、解密和处理数据所需的时间可能会增加响应时间。

加密对于安全至关重要,因此在实施时必须全面了解相关的挑战。许多这些挑战可以通过适当的规划、使用优化的工具和最佳实践来缓解,但它们不应被低估。鉴于数据在当今数字景观中的价值日益增加,权衡通常是值得努力的。然而,理解和准备这些开销可以使加密之旅更加顺畅和有效。

摘要

  • 您已经学习了如何使用 ChatGPT 的知识来识别潜在威胁,评估威胁场景,确定漏洞,并将应用程序设计与最佳实践进行比较。

  • 互动问答可以帮助您了解常见的设计缺陷和漏洞,并在软件开发中应用行业标准的安全实践。

  • 您已经了解了如何生成安全代码指南,接收定制建议,并在了解相关权衡的同时获得选择合适加密算法的指导。

  • 您可以使用 ChatGPT 与静态和动态分析工具进行综合安全评估,解码测试结果,并获得修复建议,培养开发人员和 IT 人员的安全中心思维。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐