简介:这份资源面向云计算工程师、DevOps 实践者及备考 AWS 认证的学习者,围绕 AWS Landing Zone 的自动化部署与测试展开,提供可落地的代码示例与配套文档,帮助读者理解多账户组织架构、核心账户创建、OU 与 SCP 策略管理等典型场景,适合具备一定 AWS 基础、希望系统掌握着陆区搭建流程的中高级技术人员。压缩包共 69 个文件,约 1.85MB,以 json 策略与配置模板、md 说明文档、yml CloudFormation 模板为主,辅以 rb 校验脚本、test 与 cleanup 测试脚本及少量 pdf、docx 参考资料,覆盖策略定义、安全基线、VPC 多层级模板与测试环境搭建等模块。已有 310 人学习下载。资源按 cloudformation-tools、test-environment-setup、organizations、security、accounts 等目录分层组织,读者可据此完成测试环境准备、组织与账户初始化,并借助 CloudTrail、Config、VPC Flow Logs 等安全模板与校验脚本理解自动化测试与策略落地的完整思路。
1. 从一份“能跑起来”的 Landing Zone 示例说起
很多团队第一次接触 AWS 多账号治理,都是被一堆概念砸晕的:组织单元、账号工厂、基线策略、集中日志、网络分段,文档看了一整天,回到控制台还是不知道先点哪里。这份 landing-zone 资源的价值就在这儿——它不是又一篇概念科普,而是一组围绕 Landing Zone 自动化与测试的代码示例和文档,把“账号怎么批量建、基线怎么自动打、策略怎么验证”拆成可以照着执行的片段。技术栈以 Python 为主,配合基础设施即代码的常见做法,适合两类人:一类是要在云上把多账号体系从零搭起来、又不想纯手工点控制台的开发者;另一类是想给已有 Landing Zone 补自动化测试、把“配置对不对”从人肉检查变成脚本校验的熟手。下面按“它是什么 → 怎么跑 → 怎么改”的顺序拆。
2. 项目定位与技术选型:为什么是 Python 加 IaC 的组合
2.1 这个项目到底解决什么问题
Landing Zone 的本质是一套“地基”:账号结构、身份与权限边界、日志与审计汇聚、网络连通与隔离、安全基线。手工做一遍不算难,难的是三件事——可重复、可验证、可演进。新业务线进来要加账号,半年后合规要求变了要改基线,如果没有自动化,每次都是翻旧工单、凭记忆补配置,出错概率极高。
这份资源把 Landing Zone 拆成两条主线:一条是“自动化创建与配置”,用代码把账号、组织单元、基线策略的创建过程固化下来;另一条是“测试与校验”,用脚本去断言“创建出来的东西符合预期”。两条线合起来才闭环——只自动化不测试,等于把手工错误换成了脚本错误;只测试不自动化,测试对象本身还是手工产物,价值有限。
从关键词看,Python 是主力语言。这在云治理场景里很合理:云厂商的 SDK 成熟,Python 写编排脚本、写断言、写报告都快,和 IaC 工具的调用链也顺。常见做法是 Python 负责“编排与校验”,IaC 模板负责“声明资源”,两者分工,而不是让 Python 去硬拼所有资源创建逻辑。
2.2 技术栈分层与各层职责
把这类项目按层拆开看,职责会清楚很多:
| 层次 | 承担的事 | 典型实现 |
|---|---|---|
| 编排层 | 按顺序驱动账号创建、OU 归属、基线下发 | Python 脚本调用云 SDK |
| 声明层 | 描述资源最终状态,保证幂等 | IaC 模板(组织、策略、角色) |
| 校验层 | 断言实际状态与期望一致 | Python 测试脚本 + 断言库 |
| 文档层 | 记录设计决策、参数含义、执行顺序 | Markdown 说明 |
编排层和声明层的边界要拿捏好。我的经验是:账号创建这类“有副作用、有配额、有异步等待”的动作放编排层,用 Python 显式控制重试和轮询;而策略、角色、日志配置这类“声明即所得”的资源放声明层,交给 IaC 保证幂等。混着写最容易翻车——比如在 Python 里手写策略 JSON 再调接口创建,改一次策略就要改一次脚本,还没法 diff。
2.3 目录结构怎么读
这类资源通常按“功能域 + 用途”组织,读的时候先看顶层再往下钻:
landing-zone/ ├── docs/ # 设计说明、执行顺序、参数解释 ├── automation/ # 自动化脚本 │ ├── accounts/ # 账号创建与 OU 归属 │ ├── baselines/ # 基线策略下发 │ └── common/ # 公共客户端、重试、日志封装 ├── tests/ # 校验脚本 │ ├── test_accounts.py # 账号结构断言 │ └── test_baselines.py # 基线合规断言 └── config/ # 环境参数、账号清单读的顺序建议是docs/→config/→automation/common/→ 具体功能脚本 →tests/。先看文档知道设计意图,再看配置知道输入长什么样,然后看公共封装知道客户端怎么初始化,最后看功能脚本和测试,理解“做”和“验”是怎么对应的。很多人上来就钻功能脚本,结果被一堆环境变量和客户端初始化绕晕,就是因为跳过了前两步。
提示:目录名在不同项目里会有差异,但“文档、自动化、测试、配置”四块基本是标配,按这个骨架去对号入座即可。
3. 跑起来:环境准备与自动化脚本执行链路
3.1 环境与凭据准备
先把运行环境备齐。Python 版本建议 3.9 以上,依赖用虚拟环境隔离,避免和系统包打架:
python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt凭据这块是新手最容易卡住的地方。脚本要调云 API,必须有可用的身份。常见做法是本地用配置好的命名配置文件,脚本里显式指定,而不是依赖默认链——默认链在多人多环境机器上极易串号:
export AWS_PROFILE=landing-zone-dev export AWS_REGION=us-east-1AWS_PROFILE指向一个已配置好访问密钥的配置文件,AWS_REGION决定脚本默认操作的区域。注意 Landing Zone 里有些资源是全局的(比如组织本身),有些是区域级的(比如日志桶),区域设错会出现“明明创建成功却查不到”的玄学现象。我一般会在脚本启动时打印一次当前身份和区域,作为执行日志的第一行,出问题时一眼能看出跑在哪个环境。
3.2 公共客户端封装:重试与限流是第一课
云 API 有配额、有瞬时失败,裸调 SDK 不加重试,跑批量账号时必然中途挂掉。公共封装里最该先写的就是带退避的重试:
import time import logging from botocore.exceptions import ClientError logger = logging.getLogger(__name__) def call_with_retry(func, *args, max_attempts=5, base_delay=1.0, **kwargs): """对云 API 调用做指数退避重试,只对可重试错误生效""" for attempt in range(1, max_attempts + 1): try: return func(*args, **kwargs) except ClientError as e: code = e.response["Error"]["Code"] # 限流和瞬时错误才重试,参数错误重试也没用 if code in ("Throttling", "TooManyRequestsException") and attempt < max_attempts: delay = base_delay * (2 ** (attempt - 1)) logger.warning("限流命中,第 %s 次重试,等待 %.1fs", attempt, delay) time.sleep(delay) continue raise这段的关键在“只对可重试错误重试”。Throttling这类限流错误退避后大概率能过,而参数校验失败、权限不足这类错误重试一百次也是同样结果,反而拖长失败反馈。max_attempts控制总次数,base_delay是退避基数,实际等待按 2 的幂增长。批量创建账号时把max_attempts调到 5 左右比较稳,再高就要怀疑是不是配额真的不够了。
3.3 账号创建脚本的执行顺序
账号创建不是一次调用就完事,典型链路是:创建账号 → 等待创建完成 → 移动到目标 OU → 打标签。中间“等待创建完成”必须显式轮询,因为账号创建是异步的:
def wait_until_active(client, create_status_id, timeout=600, interval=15): """轮询账号创建状态,直到成功或超时""" elapsed = 0 while elapsed < timeout: resp = client.describe_create_account_status( CreateAccountRequestId=create_status_id ) state = resp["CreateAccountStatus"]["State"] if state == "SUCCEEDED": return resp["CreateAccountStatus"]["AccountId"] if state == "FAILED": raise RuntimeError(resp["CreateAccountStatus"].get("FailureReason")) time.sleep(interval) elapsed += interval raise TimeoutError("账号创建超时,请检查配额与请求参数")timeout给 600 秒是保守值,账号创建通常几分钟内完成,但高峰期可能更久;interval设 15 秒,太密会触发限流,太疏会拖慢整体节奏。返回的AccountId要立刻接上“移动到 OU”的调用,否则账号会停在根组织下,后续基线策略可能打不上——这是很典型的一处顺序依赖,漏一步后面全歪。
3.4 基线下发与幂等处理
基线下发要保证“重复执行不出错”。做法是先查当前状态,再决定是否创建或更新:
def ensure_baseline(client, target_id, policy_arn): """确保目标上挂载了指定基线策略,已存在则跳过""" attached = client.list_policies_for_target( TargetId=target_id, Filter="POLICY_TYPE" )["Policies"] if any(p["PolicyArn"] == policy_arn for p in attached): logger.info("基线已存在,跳过:%s", target_id) return False client.attach_policy(PolicyId=policy_arn, TargetId=target_id) return Truetarget_id可以是组织单元也可以是账号,policy_arn是基线策略的唯一标识。先查后写让脚本可以反复跑,这在“配置漂移”场景里特别有用——有人手工摘了策略,重跑一遍脚本就能补回来。返回值区分“本次是否真的做了变更”,方便上层汇总报告。
4. 测试与校验:把“配置对不对”变成可执行的断言
4.1 测试脚本的组织方式
自动化跑完不等于结果正确,测试层就是补这一环。组织上建议按“被测对象”分文件,一个文件对应一类断言,比如账号结构一个文件、基线合规一个文件。每个测试用例只断言一件事,失败信息要能直接指向“哪个账号、哪个策略、期望什么、实际什么”。这样 CI 里挂了,看日志就知道去改哪儿,不用再登控制台人肉比对。
测试的输入应该来自config/里的期望清单,而不是脚本里硬编码。期望清单是“单一事实来源”,自动化和测试都读它,才能保证“建的和验的是同一套标准”。如果两边各写一份,早晚会漂移。
4.2 账号结构断言示例
import pytest def test_account_in_expected_ou(org_client, expected_accounts): """校验每个期望账号都归属在配置指定的 OU 下""" for account in expected_accounts: parents = org_client.list_parents(ChildId=account["id"])["Parents"] actual_ou = parents[0]["Id"] assert actual_ou == account["ou_id"], ( f"账号 {account['name']} 期望在 {account['ou_id']},实际在 {actual_ou}" )expected_accounts从配置加载,每项含账号 ID、名称、期望 OU。断言失败信息里带账号名和两个 OU,排查时不用再查一遍映射表。这类测试跑起来很快,适合放进每次变更后的流水线。
4.3 基线合规断言与报告输出
基线断言除了 pass/fail,最好能输出一份可读报告,给不写代码的同事看:
def collect_baseline_report(client, targets, required_policies): """汇总每个目标的基线缺失情况""" report = [] for target in targets: attached = { p["PolicyArn"] for p in client.list_policies_for_target( TargetId=target, Filter="POLICY_TYPE" )["Policies"] } missing = [p for p in required_policies if p not in attached] report.append({"target": target, "missing": missing}) return reporttargets是被检查的账号或 OU 列表,required_policies是必须挂载的基线集合。返回结构里missing为空表示合规,非空就列出缺了哪些。把这份报告落成 JSON 或表格,接进流水线产物,合规检查就从“口头确认”变成了“有据可查”。
注意:测试脚本本身也要能重复跑、无副作用。断言类脚本只读不写,千万别在测试里顺手“修复”配置,否则测试和自动化职责就混了,出了问题分不清是谁改的。
5. 避坑与排查:批量执行时最容易翻车的几处
5.1 账号创建成功但 OU 归属不对
现象:脚本日志显示账号创建成功,但测试断言报“账号在根组织下”。原因通常是创建和移动之间少了等待,或者移动调用用了错误的账号 ID(比如拿了请求 ID 当账号 ID)。解决:严格按“创建 → 轮询到 SUCCEEDED 拿 AccountId → 移动”的顺序,移动前打印一次 AccountId 和目标 OU,日志对不上就能立刻定位。
5.2 批量跑一半被限流打断
现象:跑到第十几个账号时开始大量Throttling,重试也救不回来。原因是并发太高或调用太密,把配额打满了。解决:降低并发,给批量循环加节流;重试只覆盖限流类错误,并且退避基数别设太小。血泪经验是批量脚本先在测试组织里跑小批量,确认节奏稳了再上生产。
5.3 区域设错导致“查不到资源”
现象:资源明明创建成功,查询接口却返回空。原因是创建和查询用了不同区域,或者把区域级资源当全局资源查。解决:脚本启动时固定并打印区域,所有客户端初始化都显式传同一个区域;对全局资源单独走全局端点,别混在区域客户端里。
5.4 测试通过但线上仍不合规
现象:流水线全绿,审计时还是发现基线缺失。原因是测试只覆盖了配置清单里的账号,清单外新加的账号没被纳入。解决:测试除了“清单内逐个断言”,再加一条“实际账号集合与清单集合的差集为空”的断言,把“漏网账号”也变成失败项。
5.5 凭据串环境
现象:本地跑着跑着,操作打到了另一个环境。原因是依赖了默认凭据链,机器上恰好有别的配置。解决:所有脚本强制读显式指定的 profile,启动时打印身份信息,CI 里用独立凭据,绝不共用。
6. 二次开发:把示例改造成自己团队的治理流水线
拿到这份资源后,最值得做的改造不是加功能,而是把它接进流水线,让“自动化 + 测试”变成每次变更的固定动作。我的做法是分三步走。
第一步,把config/里的账号清单和基线清单抽成团队维护的单一事实来源,最好放进版本库走评审。清单变更即触发流水线:先跑自动化脚本做增量收敛,再跑测试脚本断言结果。这样“加一个账号”从手工工单变成一次提交,谁改的、改了什么、验没验过,全有记录。
第二步,给自动化脚本加“预演模式”。批量操作最怕一上来就真干,加一个--dry-run参数,只打印“将要创建/移动/挂载什么”,不实际调用写接口:
def apply_change(action, dry_run=False): """统一入口:预演模式只记录不执行""" if dry_run: logger.info("[DRY-RUN] 将执行:%s", action) return action()action是一个无参可调用对象,把具体写操作包进去;dry_run为真时只打日志。上线前先跑一遍预演,人工扫一眼日志,能挡掉大部分“参数填错导致批量误操作”的事故。
第三步,把测试报告接进流水线产物,并设一道“差集断言”作为门禁。前面提过,清单内断言全过不代表没有漏网账号,加一条集合差集为空的断言,等于给治理范围上了锁。门禁不过就不允许合并,配置漂移在提交阶段就被拦住,而不是等审计时才发现。
改造时还有个小技巧:把公共封装里的重试、日志、客户端初始化做成可配置项,不同环境用不同参数。测试环境可以激进一点、重试少、超时短,快速暴露问题;生产环境保守一点、重试多、超时长,优先保证跑完。参数从配置文件读,别写死在代码里,否则改一次环境就要改一次代码,迟早改出事故。
从那以后我每次动这类批量治理脚本,都强制先跑一遍预演、再看一眼启动日志里的身份和区域,确认无误才放开真跑。这套习惯帮我挡过好几次“差点打到生产”的惊险时刻。希望这份拆解能帮你把 Landing Zone 从一堆概念变成手里能跑、能验、能改的工程代码,需要的话把资源拿下去,从docs/和config/开始读起就行。
本文还有配套的精品资源,点击获取