亚马逊AWS官方博客

借助 Cloud Foundations 产品工厂和包工厂以最佳安全实践事件驱动自动初始化实例安装基础软件并配置会话日志和监控指标

基于官方镜像启动一台新的 Amazon EC2 (EC2) 实例后,标准化过程一般包括安装必要软件包并配置 Amazon CloudWatch (CloudWatch) 代理以发送日志和监控指标。此外还包括改名、入域等操作。软件包可以登录实例后通过常规方式安装,亦可通过 Amazon Systems Manager (SSM) 的经销商 (Distributor) 进行托管安装。配置 CloudWatch 代理发送系统日志和监控指标也是管控实例重要一步。如何安全、规范、自动、快速完成上述实例初始化是本文想解决的问题。

初始化 EC2 实例的几大步骤中,安装软件包方面 Cloud Foundations 通过新增第五个 Amazon Service Catalog 服务目录产品包工厂 (Package Factory) 来解决;会话日志与监控指标配置方面通过 SSM 自动化 (Automation) 文档来解决;自动完成方面通过 Amazon EventBridge 事件规则监控事件来驱动;而整体架构落地方面通过产品工厂 (Product Factory[3]) 来定义产品及部署。故此,本文在 Cloud Foundations 构建的多账户组织云上环境中,通过新老功能模块,以最佳安全实践为您搭建可安全、规范、自动、快速实现实例初始化的产品架构,并辅以详细过程及截图。读者可以此为起点服务其他业务应用和工作负载。

以 Windows 实例为例,初始化工作包括:

  1. 安装软件包,例如亚马逊云科技 (AWS) AWS CLI 命令行软件包;
  2. 安装 CloudWatch代理;
  3. 配置CloudWatch 代理;
  4. 安装卷快照复制服务 (VSS) 组件包;

以 Linux 实例为例,初始化工作包括:

  1. 安装软件包;
  2. 安装 CloudWatch 代理;
  3. 配置 CloudWatch 代理;

具体来说,包工厂对软件包在日志账户经销商和共享软件包进行集中管理,并通过经销商共享至目的账户进行安装。在此基础上,Cloud Foundations 产品工厂新增实例初始化自动化预定义文档,通过自动化过程完成软件包安装,CloudWatch 代理安装配置等步骤。产品工厂另新增实例初始化预定义产品,定义并部署以事件驱动来触发执行初始化文档从而对可管理实例自动进行初始化的产品架构。

一、总体架构

下图是在 Cloud Foundations 构建的云环境上,通过包工厂部署并共享软件包,通过产品工厂定义并部署实例初始化文档及产品,通过事件驱动进行实例初始化的总体架构图。

图 1 实例初始化产品架构

本文演示总体步骤为:

  1. 通过共享网络模块构建网络并共享私有子网至目标成员账户(本文从略,可参考博客 [1]);
  2. 通过包工厂在日志账户对经销商软件包进行集中管理并共享至目标成员账户;
  3. 通过产品工厂预定义实例初始化文档和产品,在目标成员账户一键部署事件驱动实例初始化架构,包括密钥、事件规则、自动化文档、日志指标配置参数、日志组等资源;
  4. 通过产品工厂另一个预定义可管理实例产品部署两台可管理实例至目标成员账户,验证运行驱动实例初始化过程;
  5. 通过修改实例标签验证标签驱动实例初始化过程。

网络部分读者可根据实际需要构建,本文不再赘述,但列出实验架构以供参考。

{
  "vpcs": {
    "security": {
      "is_hub": true, "enable_igw": true, "enable_nat": true,
      "cidr": "192.168.0.0/16",
      "subnets": [[[12, 0], [12, 1]], [[8, 1],  [8, 2]]]
    },
    "sandbox": {
      "accounts": ["SANDBOX_ACCOUNT"],
      "cidr": "10.1.0.0/16",
      "subnets": [[[12, 0], [12, 1]], [], [[8, 3], [8, 4]]]
    }
  },
  "tgw": {
    "enabled": true,
    "cidr": "10.0.0.0/8",
    "tables": {
      "pre":  { "associations": ["sandbox"], "routes": { "*": "security" } },
      "post": { "associations": ["security"], "propagations": ["sandbox"] }
    }
  }
}
JavaScript

二、包工厂设计思想

包工厂是 Cloud Foundations 的第五个服务目录产品。主要目的是帮助用户规范、集中、简捷地管理经销商软件包。具体来说,系统会用日志账户预置的 SSM 桶来保存构建软件包的中间文件,例如可执行文件、安装和卸载脚本以及清单文件 (manifest)。此外日志账户会作为经销商软件包的集中管理账户,软件包按需共享至成员账户。

图 2 包工厂总体架构

创建新包之前,需以产品管理员身份将未压缩可执行文件统一上传至日志账户 SSM 桶的 /tmp 目录下。由于目录下对象不能重名,故文件名也做为包工厂新包的状态键 (state key)。您可通过日志账户 Amazon S3 控制台完成,也可参考以下命令。其中 Amazon KMS 的客户托管 SSM 密钥可在安全账户获取。

aws s3api put-object \
--bucket $PREFIX-bucket-logs-ssm-$LOGS_ACCOUNT \
--key tmp/AWSCLIV2.msi \
--body AWSCLIV2.msi \
--server-side-encryption aws:kms \
--ssekms-key-id $SSM_KEY_ARN
JavaScript

上传后,同样以产品管理员身份在Service Catalog 控制台启动包工厂产品:

图 3 包工厂服务目录产品

依次填入以下信息:

  1. 产品名:鉴于同一目录中文件名的唯一性,可以该文件名命名产品;
  2. 可执行文件:刚才上传的未压缩可执行文件名;
  3. 包版本:自定义包版本,不是可执行文件版本。更新包时需变更包版本;
  4. 操作系统:选择包对应的操作系统;
  5. 系统架构:选择包对应的系统架构;
  6. 安装/卸载脚本:一行脚本用以安装/卸载软件;
  7. 共享账户:逗号分隔的拟分享包的账户号码字符串,* 分享给组织内所有账户;
  8. 更新标记更新包时比前值加或减一。

图 4 启动包工厂产品新建经销商软件包

启动产品后,包工厂会将可执行文件及其安装卸载脚本根据系统管理经销商要求压缩打包后暂存于日志账户 SSM 桶的 /packages 目录中。包工厂还会计算可执行文件安全散列算法 (SHA256) 的校验和 (checksum) 并生成清单文件。最后根据上述文件在日志账户创建类型为 (package) 的 SSM 文档并共享至指定账户。此时我们完成了第二大步。如需更改共享账户或其他信息,可直接更新产品,此时需同时变更版本和更新标记。

系统在日志账户对所有经销商包进行集中管理。上述过程与 SSM 控制台的使用简单工作流创建软件包过程类似,不同之处在于包工厂跨账户使用日志账户预置 SSM 桶和安全账户预置 SSM 密钥,集中管理与按需共享软件包。这是 Cloud Foundations 构建多账户组织各司其职、按部就班的规范特征之一。

三、预定义实例初始化文档

产品工厂是 Cloud Foundations 为进一步简化优化云资源自动创建和规范管理而推出的功能模块。之前的几篇博客 [2, 3, 4] 从不同角度介绍了该工厂的应用示例。产品工厂可定义并部署各种各样的产品架构,适用范围广泛。截止目前,产品工厂支持 39 类 AWS 服务的 69 种不同云资源,且在不断增加。本文介绍的事件驱动初始化实例整体架构所涉全部服务和所有资源均由产品工厂定义并部署,亦可方便地一键销毁。

产品工厂支持集中管理 SSM 文档资源。和预定义产品一样,也预定义了通用自动化文档。由于自动化文档内容较多且多以 YAML 为格式,不宜直接内嵌于 JSON 格式的产品定义中,所以系统将自动化文档预定义 YAML 文件集中存储在日志账户 SSM 桶的 /documents 目录中。定义产品时,可直接引用预定义文档名称以部署文档。特别的,预定义实例初始化产品即在块 3-2 中引用了预定义实例初始化文档,将其部署至指定成员账户。

所有预定义文档在《Cloud Foundations 用户使用手册》第十一节由专表列出。Cloud Foundations 预定义实例初始化文档可同时对 Windows 和 Linux 两平台实例进行初始化。下图为该文档的执行步骤示意图。文档通过读取实例三个标签动态判定实例初始化过程:

参数 取值 说明
cf.ec2.bootstrap.enabled true | false false 时不执行初始化
cf.ec2.bootstrap.packages 软件包名数组 根据前缀-package-包名逐一查找共享至当前账户的软件包并安装。如为空字串或 SKIP 则略过此步。
cf.ec2.bootstrap.cloudwatch.config CloudWatch 代理配置参数名 在当前账户参数库中查找 /cloudwatch/config/配置名 参数并配置 CloudWatch 代理。如为空字串或 SKIP 则略过此步。

图 5 系统预定义实例初始化自动化文档示意图

此外文档根据实例平台是否为 Windows 决定是否安装 VSS 组件。由此可见,您可根据实际需要,准备经销商软件包并共享至当前账户,准备 CloudWatch 代理配置并保存为 SSM 参数,根据需要附加相应标签并赋值。基于实例运行或者标签附加事件驱动,系统自动高效地完成实例初始化过程。下一节我们讨论如何通过产品工厂预定义产品完成上述准备工作。

四、预定义实例初始化产品

Cloud Foundations 在产品工厂新增实例初始化产品 cf-ec2-bootstrap,定义并部署架构图中上半部分成员账户内的资源。该产品采用博客 [3] 中的账户创建蓝图模式构建,即指定账户号码为阶段。如此,当该产品需部署至其他账户时,只需另启一新产品,更改阶段为其他账户号码即可。此外该产品还可输入一个名称以命名此次初始化过程。

图 6 产品工厂启动预定义实例初始化产品

实例初始化产品定义如下,按依赖性共分四阶段完成,其中 CloudWatch 代理配置参数较长从略:

[
  [{
      "service": "kms",
      "accounts": ["${STAGE}"],
      "keys": {
        "${Bootstrap}": {
          "allows": ["self", "logs"],
          "statements": [{
              "trusts": ["events"], "actions": ["kms:Decrypt", "kms:GenerateDataKey"]
          }]
        }
      }
  }],
  [{
      "service": "sqs",
      "accounts": ["${STAGE}"],
      "queues": {
        "${Bootstrap}-events-letter": {
          "key": "$.${Bootstrap}",
          "statements": [{
              "sid": "Dead-letter queue permissions",
              "trusts": ["events"],
              "actions": ["sqs:SendMessage"],
              "resources": ["$.arn.sqs:${PREFIX}-queue-${Bootstrap}-events-letter"],
              "conditions": {
                "ArnEquals": {
                  "aws:SourceArn": "arn:${PARTITION}:events:${REGION}:*:rule/${PREFIX}-rule-*"
                }
              }
          }]
        }
      }
  }],
  [
    {
      "service": "iam",
      "accounts": ["${STAGE}"],
      "roles": {
        "${Bootstrap}-ssm-service": { "template": "ssm-service" },
        "${Bootstrap}-events-service": {
          "trusts": ["events"],
          "statements": [
            {
              "actions": ["iam:PassRole"],
              "resources": ["$.arn.iam:role/${PREFIX}-role-${Bootstrap}-ssm-service"]
            },
            {
              "actions": ["kms:Decrypt", "kms:GenerateDataKey"],
              "resources": ["key:$.${Bootstrap}"]
            },
            {
              "actions": ["sqs:SendMessage"],
              "resources": ["$.arn.sqs:${PREFIX}-queue-${Bootstrap}-events-letter"]
            },
            {
              "actions": ["ssm:StartAutomationExecution"],
              "resources": ["$.arn.ssm:automation-definition/${PREFIX}-document-*"]
            }
          ]
        }
      }
    },
    {
      "service": "ssm",
      "accounts": ["${STAGE}"],
      "documents": {
        "${Bootstrap}": {
          "target": "/AWS::EC2::Instance", "file": "${PREFIX}-document-ec2-bootstrap.yaml"
        }
      }
    },
    {
      "service": "logs",
      "accounts": ["${STAGE}"],
      "groups": {
        "${Bootstrap}/windows/application": { "key": "$.${Bootstrap}" },
        "${Bootstrap}/windows/security": { "key": "$.${Bootstrap}" },
        "${Bootstrap}/windows/system": { "key": "$.${Bootstrap}" },
        "${Bootstrap}/linux/application": { "key": "$.${Bootstrap}" },
        "${Bootstrap}/linux/security": { "key": "$.${Bootstrap}" },
        "${Bootstrap}/linux/system": { "key": "$.${Bootstrap}" }
      }
    }
  ],
  [
    {
      "service": "events",
      "accounts": ["${STAGE}"],
      "rules": {
        "${Bootstrap}-ec2-running": {
          "letter": "$.${Bootstrap}-events-letter",
          "pattern": {
            "source": ["aws.ec2"],
            "detail": { "state": ["running"] },
            "detail-type": ["EC2 Instance State-change Notification"]
          },
          "automations": {
            "$.${Bootstrap}": {
              "role": "$.${Bootstrap}-events-service",
              "ssm_role": "$.${Bootstrap}-ssm-service",
              "inputs": { "instance_id": "$.detail.instance-id" },
              "parameters": {
                "Bootstrap": ["${Bootstrap}"],
                "LogsAccount": ["${LOGS_ACCOUNT}"],
                "InstanceId": ["<instance_id>"]
              }
            }
          }
        },
        "${Bootstrap}-ec2-tagging": {
          "letter": "$.${Bootstrap}-events-letter",
          "pattern": {
            "source": ["aws.ec2"],
            "detail-type": ["AWS API Call via CloudTrail"],
            "detail": { "eventName": ["CreateTags"], "eventSource": ["ec2.amazonaws.com"]}
          },
          "automations": {
            "$.${Bootstrap}": {
              "role": "$.${Bootstrap}-events-service",
              "ssm_role": "$.${Bootstrap}-ssm-service",
              "inputs": { "instance_id": "$.detail.requestParameters.resourcesSet.items[0].resourceId"},
              "parameters": {
                "Bootstrap": ["${Bootstrap}"],
                "LogsAccount": ["${LOGS_ACCOUNT}"],
                "InstanceId": ["<instance_id>"]
              }
            }
          }
        }
      }
    },
    {
      "service": "ssm",
      "accounts": ["${STAGE}"],
      "parameters": {
        "${Bootstrap}/cloudwatch/config/windows": {
          "key": "$.${Bootstrap}",
          "json": {
            "logs": {
              "logs_collected": {
                "windows_events": {
                  "collect_list": [
                    {
                      "log_group_name": "/${PREFIX}/${Bootstrap}/windows/system",
                      "event_name": "System"
                    },
                    {
                      "log_group_name": "/${PREFIX}/${Bootstrap}/windows/security",
                      "event_name": "Security"
                    },
                    {
                      "log_group_name": "/${PREFIX}/${Bootstrap}/windows/application",
                      "event_name": "Application"
                    }
                  ]
                }
              }
            }
          }
        },
        "${Bootstrap}/cloudwatch/config/linux": {
          "key": "$.${Bootstrap}", json": {}
        }
      }
    }
  ]
]
JavaScript

主要块定义要点如下:

  1. 块 1-1: 定义 KMS 客户托管密钥,用于本产品各处统一静态加密;
  2. 块 2-1: 定义事件规则的死信队列并加密,用于接收失败事件以便于排错;
  3. 块 3-1: 定义 SSM 自动化服务角色和事件规则服务角色,用于执行文档和规则调用目标;
  4. 块 3-2: 定义预定义实例初始化自动化文档,用于实例初始化;
  5. 块 3-3: 定义各日志组并加密,用于配置 CloudWatch 代理接收实例内部日志;
  6. 块 4-1: 定义实例运行规则和实例附加标签规则,目标均为上述实例初始化文档,并定义输入转换规则和参数模版;
  7. 块 4-2: 定义 CloudWatch 代理配置 Windows 和 Linux 平台参数并引用上述定义的日志组。限于篇幅,参数其他监测指标等内容从略。

启动产品后生成产品 Amazon CodePipeline 流水线,执行完毕后上述资源即部署至成员账户。此时我们完成了第三大步。上述产品部署后,每当实例状态变更为运行或实例标签变更时,此类事件都会被事件规则捕获并触发实例初始化自动化文档执行。该文档根据实例附加标签情况自动动态完成初始化工作。

图 7 附加实例标签触发实例初始化自动化文档

五、测试实例运行驱动自动初始化

本文通过产品工厂的预定义可管理实例产品 cf-ssm-host 部署启动 Windows 和 Linux 平台实例各一台进行实例初始化测试。该产品仍然采用博客 [3] 中的账户创建蓝图模式定义产品,亦即将阶段视为账户号码。以产品管理员身份启动产品工厂,填入产品配置文件、环境变量、阶段等信息如下图所示。除阶段外,可管理实例产品接受三个参数,分别是实例名称,是否部署身份中心资源以及实例部署 VPC 名称。该 VPC 由第一大步预先部署并共享私有子网至成员账户。

图 8 产品工厂启动预定义可管理实例产品

产品具体定义如下,按依赖性共分两阶段完成:

[
  [
    {
      "service": "iam",
      "accounts": ["${STAGE}"],
      "roles": {
        "${HostName}-ec2-service": { "template": "ssm-ec2-service" },
        "${HostName}-session-user": { "template": "ssm-session-user" }
      }
    },
    {
      "service": "sso",
      "permissions": {
        "${HostName}-session-user": {
          "enabled": "${SsoEnabled}",
          "template": "ssm-session-user"
        }
      },
      "groups": {
        "${HostName}-ssm": {
          "enabled": "${SsoEnabled}",
          "assigns": { "${HostName}-ssm": ["${STAGE}"] }
        }
      }
    },
    {
      "service": "vpc",
      "accounts": ["${STAGE}"],
      "groups": {
        "${HostName}": {
          "vpc": "$.${VpcName}",
          "out": [
            { "protocol": "icmp", "cidr": "*" },
            { "protocol": "https", "cidr": "*" },
            { "protocol": "mssql", "cidr": "*" },
            { "protocol": "mysql", "cidr": "*" },
            { "protocol": "postgres", "cidr": "*" },
            { "protocol": "oracle", "cidr": "*" }
          ]
        }
      }
    }
  ],
  [
    {
      "service": "ec2",
      "accounts": ["${STAGE}"],
      "instances": {
        "${HostName}-linux": {
          "role": "$.${HostName}-ec2-service",
          "volumes": [{ "size": 20 }],
          "vpc": {
            "vpc": "$.${VpcName}", "subnet": "$.private", "groups": "$.${HostName}"
          },
          "tags": {
            "cf.ec2.bootstrap.enabled": "true",
            "cf.ec2.bootstrap.packages": "",
            "cf.ec2.bootstrap.cloudwatch.config": "linux"
          },
          "user_data": ["#!/bin/bash", "useradd --create-home ${PREFIX}"]
        },
        "${HostName}-windows": {
          "ami": "windows",
          "role": "$.${HostName}-ec2-service",
          "volumes": [{ "size": 30 }],
          "vpc": {
            "vpc": "$.${VpcName}", subnet": "$.private", groups": "$.${HostName}"
          },
          "tags": {
            "cf.ec2.bootstrap.enabled": "true",
            "cf.ec2.bootstrap.packages": "AWSCLIV2.msi",
            "cf.ec2.bootstrap.cloudwatch.config": "windows"
          }
        }
      }
    }
  ]
]
JavaScript

主要要点如下:

  1. 块 1-1: 定义实例服务角色和配置文件,会话管理用户角色;
  2. 块 1-2: 定义 IAM 身份中心权限集和目录组;
  3. 块 1-3: 定义实例所用安全组;
  4. 块 2-1: 定义 Windows 和 Linux 平台实例各一台,其中附加实例初始化所需标签。

您可把 IAM 身份中心用户添加至新建的目录组中,登录后可通过系统管理会话管理直接连接登录新建实例。

图 9 通过系统管理会话管理连接登录 Windows 实例

如果您连接 Linux 实例,命令行中命令的输入输出日志会发送至本账户由 Cloud Foundations 基础着陆区预置的会话管理日志组中 (/aws/ssm/前缀/session),也会同步发送至日志账户 SSM 桶对应目录中。因为可管理实例产品所采用的 Amazon Linux 2023 镜像默认安装了 CloudWatch 代理。

图 10 Amazon Linux 2023 实例预装 CloudWatch 代理可以发送日志到日志组

但如果连接 Windows 实例则不会发送日志,因为默认镜像还未安装 CloudWatch 代理软件。此外对两实例的监控指标也未配置 生效。不过稍等片刻,实例运行规则会捕获该事件后触发执行实例初始化自动化文档,文档执行完毕后在 Windows 实例执行命令的输入输出则会被记录。文档共 17 步如下两图所示。

图 11 自动触发的 Windows 实例初始化自动化文档执行情况一

图 12 自动触发的 Windows 实例初始化自动化文档执行情况二

文档执行之后连接 Windows 实例,命令行的输入输出日志会和之前 Linux 实例一样发送至本账户预置的会话管理日志组中 (/aws/ssm/前缀/session),也会同步发送至日志账户 SSM 桶对应目录中。从下图日志可见,AWS CLI 命令行工具已通过共享的经销商软件包正确安装。您还可以打开监控指标,浏览预定义实例初始化产品定义配置的指标信息(注:部分验证实验在新加坡区域完成)。

图 13 登录 Windows 实例执行 AWS CLI 命令行工具日志发送至会话日志组

六、测试附加标签驱动自动初始化

除实例运行事件外,事件规则还监控实例附加标签事件,亦可触发实例初始化文档执行。以下我们将 Linux 实例的 cf.ec2.bootstrap.enabled 标签变更为 false,稍等片刻后观察自动化文档执行情况。

图 14 将 Linux 实例初始化开关标签关闭

从下图自动化文档执行情况可以看出,文档在第七步正确检测出实例初始化开关是关闭状态,故在第八步正确终止执行,达到预期目的。

图 15 关闭实例初始化开关标签后触发自动化文档执行情况

下图展示通过实例初始化文档读取 SSM 参数库 CloudWatch 代理监测指标配置参数并对其进行配置后的部分监测指标及其图表。

图 16 部分配置的 CloudWatch 代理监测指标及图表

七、总结

本文介绍了以事件驱动全自动初始化 Windows 和 Linux 平台实例的方法和过程。实例初始化包括经销商软件包安装软件,安装 CloudWatch 代理,配置日志和监测指标,发送会话日志等通用常规操作。为此,Cloud Foundations 新引入了包工厂服务目录产品,并在产品工厂预定义实例初始化文档和产品,另通过预定义可管理实例产品辅助验证。在 Cloud Foundations 构建的多账户组织框架下安全、规范、自动、简捷的完成前述种种任务。上述过程是从实际交付项目中提炼总结并优化而来的成果,以期受益于更多的实际案例和应用场景。读者可在此基础上根据您的实际情况做进一步优化调整,或者联系亚马逊云科技专家进行深入探讨和方案落地。

参考资料

  1. 博客:《借助 Cloud Foundations 规划设计云上多区域网络轴辐拓扑结构一键部署东西南北流量分别或合并检查》,2023 年 11 月
  2. 博客:《借助 Cloud Foundations 一键部署弹性堡垒机安全合规地实现会话管理及端口转发》,2023 年 9 月
  3. 博客:《借助 Cloud Foundations 产品工厂规划设计并一键部署云上多账户访问控制及权限策略等基础设施资源》,2024 年 3 月
  4. 博客:《借助 Cloud Foundations 从零开始一键部署配置容器化 KeyCloak 用户联合验证方案及配套数据库共享网络等依赖资源》,2024 年 11 月

本篇作者

Clement Yuan

亚马逊云科技专业服务部顾问。曾在亚马逊美国西雅图总部工作多年,就职于 Amazon Relational Database Service (RDS) 关系型数据库服务开发团队。拥有丰富的软件开发及云上运维经验。现负责业务持续性及可扩展性运行、企业应用及数据库上云和迁移、云上灾难恢复管理、云上良好架构框架等架构咨询、方案设计及项目实施工作。

刘育新

亚马逊云科技 ProServe 团队高级顾问,长期从事企业客户入云解决方案的制定和项目的实施工作。