克服AWS CDK中的循环依赖问题的方法是什么:假设我有一个用于ECS集群的堆栈和一个用于ECS服务的堆栈(其中几个):
export class EcsClusterStack extends cdk.Stack {
public readonly cluster: ecs.Cluster
...
}
Run Code Online (Sandbox Code Playgroud)
和
export class EcsServiceStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, cluster: ecs.ICluster) { }
}
Run Code Online (Sandbox Code Playgroud)
现在,我可以编写我的应用程序:
const app = new cdk.App();
const vpc = new VpcStack(app, 'vpc');
const cluster = new ClusterStack(app, 'ecs', vpc.vpc);
const service = new EcsServiceStack(app, 'ecs-service', cluster.cluster);
Run Code Online (Sandbox Code Playgroud)
假设之后,我想将我的 ECS 服务从一个集群迁移到另一个集群。我会创建另一个 ECS 集群堆栈并将其传递给 ECS 服务,但事情是:AWS CDK 自动生成输出(在集群堆栈中有集群名称等输出),然后,当我想将 ECS 服务迁移到另一个集群,如果我将另一个 ICluster 对象传递给 ECS 服务堆栈构造函数,AWS CDK 会尝试从我之前的集群定义中删除输出/导出,这显然会在部署时失败,因为它无法从集群堆栈中删除导出,直到有服务依赖它。最后,我看到一个错误,如下所示:
0 | 7:15:19 PM | UPDATE_IN_PROGRESS | AWS::CloudFormation::Stack | ecs User Initiated
0 | 7:15:26 PM | UPDATE_ROLLBACK_IN_P | AWS::CloudFormation::Stack | ecs Export ecs:ExportsOutputFnGetAttdefaultasgspotInstanceSecurityGroup2D2AFE98GroupId1084B7B2 cannot be deleted as it is in use by ecs-service
Run Code Online (Sandbox Code Playgroud)
如果有一种方法强制首先部署 ECS 服务堆栈就可以解决问题,但 AWS CDK 似乎总是首先部署依赖项(在我的例子中是 ECS 集群),导致部署失败。那么有没有办法克服这个问题呢?
AWS 添加了官方解决方法。它允许您在依赖堆栈中手动创建导出。当您需要删除导出时:
this.exportAttribute(this.bucket.bucketName)
Run Code Online (Sandbox Code Playgroud)
我发现的一种解决方法是强制 CDK 将部署拆分为两个步骤。首先,我部署使用导出的堆栈,以便它不再使用它。然后,我部署创建导出的堆栈,以在导出不再使用后将其删除。即使您在命令中指定堆栈名称,它仍然会部署它所依赖的所有堆栈。所以我必须使用该--exclusively标志。
cdk deploy --exclusively ecs-service
cdk deploy
Run Code Online (Sandbox Code Playgroud)
在您的情况下,您需要在创建新集群和部署堆栈之前执行一个步骤,以便您可以导入新内容ecs-service。
GitHub 上有一个未解决的问题。
我创建了以下脚本来帮助自动化该过程。它在两次部署中逐步淘汰了导出。在第一次部署时,它会恢复所有已删除的导出,但将它们标记为已删除。这允许第一次部署安全地删除其使用。在第二次部署中,在没有其他堆栈使用导出后,脚本实际上会删除导出。
要使用该脚本,您必须将合成步骤和部署步骤分开,并在它们之间运行脚本。
cdk synth && python phase-out-ref-exports.py && cdk deploy --app cdk.out --all
Run Code Online (Sandbox Code Playgroud)
它需要读取堆栈的权限,因此它可能不适用于跨账户部署。
# phase-out-ref-exports.py
import json
import os
import os.path
from aws_cdk import cx_api
import boto3
import botocore.exceptions
def handle_template(stack_name, account, region, template_path):
# get outputs from existing stack (if it exists)
try:
# TODO handle different accounts
print(f"Checking exports of {stack_name}...")
stack = boto3.client("cloudformation", region_name=region).describe_stacks(StackName=stack_name)["Stacks"][0]
old_outputs = {
o["OutputKey"]: o
for o in stack.get("Outputs", [])
}
except botocore.exceptions.ClientError as e:
print(f"Unable to phase out exports for {stack_name} on {account}/{region}: {e}")
return
# load new template generated by CDK
new_template = json.load(open(template_path))
if "Outputs" not in new_template:
new_template["Outputs"] = {}
# get output names for both existing and new templates
new_output_names = set(new_template["Outputs"].keys())
old_output_names = set(old_outputs.keys())
# phase out outputs that are in old template but not in new template
for output_to_phase_out in old_output_names - new_output_names:
# if we already marked it for removal last deploy, remove the output
if old_outputs[output_to_phase_out].get("Description") == "REMOVE ON NEXT DEPLOY":
print(f"Removing {output_to_phase_out}")
continue
if not old_outputs[output_to_phase_out].get("ExportName"):
print(f"This is an export with no name, ignoring {old_outputs[output_to_phase_out]}")
continue
# add back removed outputs
print(f"Re-adding {output_to_phase_out}, but removing on next deploy")
new_template["Outputs"][output_to_phase_out] = {
"Value": old_outputs[output_to_phase_out]["OutputValue"],
"Export": {
"Name": old_outputs[output_to_phase_out]["ExportName"]
},
# mark for removal on next deploy
"Description": "REMOVE ON NEXT DEPLOY",
}
# replace template
json.dump(new_template, open(template_path, "w"), indent=4)
def handle_assembly(assembly):
for s in assembly.stacks:
handle_template(s.stack_name, s.environment.account, s.environment.region, s.template_full_path)
for a in assembly.nested_assemblies:
handle_assembly(a.nested_assembly)
def main():
assembly = cx_api.CloudAssembly("cdk.out")
handle_assembly(assembly)
if __name__ == "__main__":
main()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7870 次 |
| 最近记录: |