Skip to content

云主机与资源管理:审阅实例、磁盘与角色变更 ​

本篇以EC2 资源审阅为例;前置是账户、区域、实例/卷身份与 IAM 角色。源码与输入与下文配置共用同一份文件,示例未执行。

核对日期2026-10-17;适用范围:以AWS EC2 2016-11-15 API模型与IAM概念为例,并对照阿里云ECS中文生命周期说明;云控制台与服务特性按2026-10-17核对,不声称跨云接口等价。示例未执行;真实环境仍需验证产品配置、权限、连通与恢复结果。先读起点理解前置,再按章节完成推演。

清单与请求格式 ​

云资源审阅文件包括review.py、inventory.json、request.json、iam-policy.json与requirements.txt。示例未执行,无云SDK或凭据;000000000000、i-teaching-only和teaching-region是显式假身份,不能提交真实接口。程序审阅保存清单中的owner、状态、暴露和删除前置,输出executed=false;它不是云模拟器,也不会检查线上快照存在或真实权限。

删除前置和拒绝行为 ​

云资源删除审批在课堂请求中用reviewed-lab-only标记,程序同时要求备份字段、所有者一致、无publicIp及允许的当前状态。任一缺失退出2,输出错误而不返回成功报告;该字符串仅作输入合同,绝不是真实组织审批或授权凭据。真实终止前要另外核对工单、资源依赖和备份可恢复性,不能靠修改JSON审批值绕过流程。

输出与失败推演 ​

云资源审阅预期正常输出proposedAction=terminate、executed=false、rootDiskAtRisk=true,提醒root卷随终止删除。把approval改为unreviewed、移除backup或改变owner应拒绝。程序只读,不会创建或释放实例,清理仅删除读者自行生成的副本和报告。一次本地通过不说明费用已停止、资源处于stopped或持有云账号权限。

完整EC2资源模板与生命周期边界 ​

instance.yaml是CloudFormation 2010-09-09格式,完整声明EC2实例、只读IAM角色及实例配置文件;要求读者提供可信x86_64 EBS AMI、同VPC隔离子网/安全组、真实RootDeviceName和不小于镜像快照的卷大小。模板强制无公网IP、IMDSv2及加密gp3根卷,DeleteOnTermination=false保留根盘;这与审阅fixture的rootDeleteOnTermination=true是刻意对照,不可混用。角色仅有Describe,不含管理员/上传/远程管理权限,也未提供SSH密钥或应用部署。参数引用不是已存在资源的证明,不能直接宣称可部署。

读者以后可用自己的CloudFormation流程先创建change set、审阅IAM能力与替换/删除范围,再执行专用stack。成功预期只是私有实例和角色形成关联,不代表有登录通道或应用。错误AMI架构、根设备名、卷大小、配额、同VPC约束或权限将失败;更新替换可能创建新实例并保留旧卷。清理应先保全数据,再删除专用stack并核查保留EBS、快照与其他依赖仍可能计费,不能把stack删除当作全费用归零。

CloudFormation EC2属性

yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Teaching EC2 resource template; not deployed; requires reader-owned isolated inputs.
Parameters:
  ImageId:
    Type: AWS::EC2::Image::Id
    Description: Reader-owned trusted x86_64 HVM EBS-backed Linux AMI in this region.
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: Reader-owned isolated subnet with reviewed routes and no public mapping.
  SecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id
    Description: Reader-owned same-VPC security group with reviewed ingress and egress.
  RootDeviceName:
    Type: String
    Description: Actual root device name from the selected AMI; no guessed default.
  RootVolumeGiB:
    Type: Number
    MinValue: 8
    Description: At least the AMI root snapshot size; reader must inspect before choosing.
Resources:
  InventoryRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: DescribeInventoryOnly
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: [ec2:DescribeInstances, ec2:DescribeVolumes]
                Resource: '*'
  InventoryProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - Ref: InventoryRole
  TeachingInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId:
        Ref: ImageId
      InstanceType: t3.micro
      IamInstanceProfile:
        Ref: InventoryProfile
      MetadataOptions:
        HttpTokens: required
        HttpEndpoint: enabled
        HttpPutResponseHopLimit: 1
      NetworkInterfaces:
        - DeviceIndex: '0'
          AssociatePublicIpAddress: false
          SubnetId:
            Ref: SubnetId
          GroupSet:
            - Ref: SecurityGroupId
      BlockDeviceMappings:
        - DeviceName:
            Ref: RootDeviceName
          Ebs:
            Encrypted: true
            VolumeType: gp3
            VolumeSize:
              Ref: RootVolumeGiB
            DeleteOnTermination: false
      Tags:
        - Key: Name
          Value: m5-teaching-only
        - Key: owner
          Value: learning
Outputs:
  InstanceId:
    Value:
      Ref: TeachingInstance
  PrivateIp:
    Value:
      Fn::GetAtt: [TeachingInstance, PrivateIp]

完整源码、配置与依赖 ​

下列文件与README共用同一份仓库内容,保持输入、失败与清理合同一致。示例未执行;以下命令仅供读者在满足前置后自行运行。

requirements.txt ​

text
# Python >=3.11,<3.14; standard library only; no cloud SDK

inventory.json ​

json
{
  "account": "000000000000",
  "region": "teaching-region",
  "instances": [
    {
      "id": "i-teaching-only",
      "state": "stopped",
      "owner": "learning",
      "role": "readonly-inventory",
      "publicIp": null,
      "rootDeleteOnTermination": true,
      "backup": "snapshot-teaching-only"
    }
  ]
}

request.json ​

json
{
  "instance": "i-teaching-only",
  "action": "terminate",
  "owner": "learning",
  "approval": "reviewed-lab-only"
}

iam-policy.json ​

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "InventoryReadOnly",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeVolumes"
      ],
      "Resource": "*"
    }
  ]
}

review.py ​

python
"""Review a saved inventory/request. Never calls a cloud API or changes a VM."""
import argparse
import json
import sys
from pathlib import Path

def review(inventory, request):
    if not isinstance(inventory, dict) or not isinstance(request, dict):
        raise ValueError("inventory and request must be objects")
    if not isinstance(inventory.get("instances"), list) or any(not isinstance(vm, dict) for vm in inventory["instances"]):
        raise ValueError("instances must be a list of objects")
    if request.get("action") not in {"start", "stop", "terminate"}:
        raise ValueError("unsupported lifecycle action")
    matches = [vm for vm in inventory["instances"] if vm["id"] == request["instance"]]
    if len(matches) != 1:
        raise ValueError("expected one inventory match")
    vm = matches[0]
    if type(vm.get("rootDeleteOnTermination")) is not bool:
        raise ValueError("rootDeleteOnTermination must be an explicit boolean")
    if vm["owner"] != request.get("owner") or vm.get("publicIp"):
        raise ValueError("owner mismatch or public exposure")
    if request["action"] == "terminate" and (not vm.get("backup") or request.get("approval") != "reviewed-lab-only"):
        raise ValueError("termination requires backup evidence and lab review")
    allowed = {"start": {"stopped"}, "stop": {"running"}, "terminate": {"running", "stopped"}}
    if vm["state"] not in allowed[request["action"]]:
        raise ValueError("state does not satisfy action precondition")
    return {"instance": vm["id"], "proposedAction": request["action"], "executed": False,
            "rootDiskAtRisk": request["action"] == "terminate" and vm["rootDeleteOnTermination"]}

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("inventory", type=Path)
    parser.add_argument("request", type=Path)
    args = parser.parse_args()
    try:
        result = review(json.loads(args.inventory.read_text(encoding="utf-8")), json.loads(args.request.read_text(encoding="utf-8")))
    except (OSError, ValueError, KeyError, TypeError) as error:
        print(str(error), file=sys.stderr)
        return 2
    print(json.dumps(result, ensure_ascii=False))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

README.md ​

md
# 云资源离线变更审阅

示例未执行。Python3.11–3.13标准库,无SDK、凭据或云CLI;这是保存清单的审阅器,不是云模拟器,也不能执行生命周期动作。inventory.json/request.json是自定义输入格式,不是EC2 API请求。000000000000、teaching-region、i-teaching-only、snapshot-teaching-only都是假身份,读者连接真实平台前必须自行替换并重新核实权限、备份和区域。

读者在此目录自跑 `python review.py inventory.json request.json`,预期退出0,返回proposedAction=terminate、executed=false、rootDiskAtRisk=true。通过仅说明本地清单通过简化检查,绝不表示拥有删除权限、快照存在或真实资源安全。iam-policy.json是AWS EC2只读身份策略;Describe动作按其授权特性使用Resource=*,不能添加终止权限。该JSON本身不是完整可挂载角色;另附instance.yaml给出角色信任和实例配置文件的完整示例,读者仍须按账户边界审核。

失败推演:复制request为独立副本,将approval改成unreviewed,预期退出2;删除inventory副本中的backup同样拒绝;owner不匹配、publicIp存在、state不满足动作前置也拒绝。不要修改基线fixtures以伪造审批。备份只检查字段存在,真实快照恢复必须另作实际验证。

恢复清理:程序只读文件并向stdout输出,无云资源需要清理;删除自行创建的副本与报告。真实云的停止仍可能产生磁盘/地址费用,终止是否删除磁盘取决于DeleteOnTermination,备份与共享卷需单独处理。真实终止不能用“重启”撤回,应从已验证备份创建新实例、重绑访问并核对数据。不得执行本文中的云动作来验收笔记。

## 完整资源配置



instance.yaml是CloudFormation 2010-09-09格式,完整声明EC2实例、只读IAM角色及实例配置文件;要求读者提供可信x86_64 EBS AMI、同VPC隔离子网/安全组、真实RootDeviceName和不小于镜像快照的卷大小。模板强制无公网IP、IMDSv2及加密gp3根卷,DeleteOnTermination=false保留根盘;这与审阅fixture的rootDeleteOnTermination=true是刻意对照,不可混用。角色仅有Describe,不含管理员/上传/远程管理权限,也未提供SSH密钥或应用部署。参数引用不是已存在资源的证明,不能直接宣称可部署。

读者以后可用自己的CloudFormation流程先创建change set、审阅IAM能力与替换/删除范围,再执行专用stack;本轮未创建change set或调用任何API。成功预期只是私有实例和角色形成关联,不代表有登录通道或应用。错误AMI架构、根设备名、卷大小、配额、同VPC约束或权限将失败;更新替换可能创建新实例并保留旧卷。清理应先保全数据,再删除专用stack并核查保留EBS、快照与其他依赖仍可能计费,不能把stack删除当作全费用归零。

[CloudFormation EC2属性](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ec2-instance.html)

本篇练习与参考解答 ​

练习:离线审阅通过是否说明CloudFormation模板能够部署?

参考解答

不能。JSON审阅不验证真实AMI、区域、卷大小、配额或IAM,完整模板还需读者提供自有输入并审阅change set。

核对资料与相邻阅读 ​

继续阅读机制、完整实践、运维、诊断与复习。

继续阅读