你有没有过这种情况:用Pulumi写的基础设施即代码(IaC)脚本部署时,跑出来一堆乱麻的错误日志,说某个资源创建失败,但你根本分不清是S3桶参数写错了,还是安全组引用错了,还是两个资源互相卡着谁也没建?Pulumi默认会并行创建多个资源来提升部署速度,但这种并行就像开了多车道的高速,稍微有资源依赖的地方就容易刮擦出“死锁”或“连锁失败”的问题,这时候串行执行模式就是帮你清理路况的清障车,能把乱麻的问题理得清清楚楚。

一、Pulumi并行部署的那些坑

1.1 并行模式的隐形陷阱

Pulumi并行创建资源的初衷是节省部署时间,比如你要建10个不互相依赖的S3桶,并行跑确实比一个个建快10倍。但只要有哪怕两个资源存在依赖关系,并行就可能出问题:比如资源A需要资源B的唯一ID才能创建,资源B又需要资源A的唯一ID,这就像A要B的身份证才能上班,B要A的身份证才能上学,两个人都拿不到对方的证件,就直接卡着不动了——这就是典型的循环依赖死锁。还有一种情况不是死锁,是并行时某个资源的参数写错了,其他依赖它的资源因为没拿到正确的返回值,都跟着报错,日志里一堆错误,你根本分不清哪个是根源,就像公司里一个人犯了错,全部门的人都被批评,却不知道谁是始作俑者。

1.2 串行模式是调试救星

串行执行模式简单来说就是让Pulumi把要创建的资源按顺序一个个来,上一个资源完全创建成功了,才会开始下一个。这种模式虽然慢,但好处是绝对不会有乱序的日志,每个资源的创建过程都是明明白白的:哪个资源先建,哪个成功了,哪个失败了,失败的具体原因是什么,一眼就能看清楚,就像你排队办业务,从第一个到最后一个,每个步骤都有单子写着,不会有人插对你就分不清顺序。

二、手把手用串行模式排查问题

2.1 开启串行模式的两种方法

Pulumi开启串行模式有两种方式,都很简单:第一种是在你执行部署命令的时候加一个参数,第二种是在Pulumi程序里全局设置并行度。我们先用实际代码示例来演示,技术栈选Pulumi TypeScript,因为它是目前用得最多的Pulumi编程语言,容易上手。

先给一个会出现死锁问题的示例代码,这是我们调试的靶子:

// 技术栈:Pulumi TypeScript
// 本示例模拟并行部署时的循环依赖死锁场景
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// 创建两个安全组,故意设置互相依赖,触发死锁
// 安全组A需要安全组B的ID作为入站规则的引用
const securityGroupA = new aws.ec2.SecurityGroup("sg-a", {
    ingress: [
        {
            protocol: "tcp",
            fromPort: 80,
            toPort: 80,
            securityGroups: [securityGroupB.id], // 依赖sgB的ID
        },
    ],
});

// 安全组B需要安全组A的ID作为入站规则的引用
const securityGroupB = new aws.ec2.SecurityGroup("sg-b", {
    ingress: [
        {
            protocol: "tcp",
            fromPort: 443,
            toPort: 443,
            securityGroups: [securityGroupA.id], // 依赖sgA的ID
        },
    ],
});

// 输出两个安全组的ID,方便后续查看
export const sgAId = securityGroupA.id;
export const sgBId = securityGroupB.id;

现在我们跑默认的并行部署命令:pulumi up,你会看到日志里两个资源同时显示“Creating...”,然后都变成“Failed”,错误信息是“resource 'sg-b' depends on another resource which is being created in parallel”,根本分不清到底是sgA的问题还是sgB的问题,这就是并行模式下的经典死锁日志。

现在换串行模式跑,第一种方法:修改部署命令,加--parallel 1,也就是pulumi up --parallel 1,这时候日志就会变成按顺序输出,绝对不会有交错,能清晰看到每个资源的创建阶段。

第二种开启串行模式的方法,是在Pulumi程序里设置全局并行度,这样不管你跑什么命令,都是串行模式,适合调试的时候不用每次加参数:

// 技术栈:Pulumi TypeScript
// 全局设置并行度为1,开启串行模式
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// 设置并行度为1,所有资源按顺序创建,适合调试阶段
pulumi.runtime.setParallel(1);

// 再给一个资源参数错误的示例,并行时看不出根因,串行就很清楚
// 故意设置其中一个S3桶的区域为不存在的参数,模拟配置错误
const bucketGood = new aws.s3.Bucket("bucket-good");
const bucketBad = new aws.s3.Bucket("bucket-bad", { region: "invalid-region-test" });
const bucketDependency = new aws.s3.Bucket("bucket-depend", {
    // 虽然没直接依赖bad桶,但并行时可能被连锁报错
});

2.2 并行资源创建失败的排查

刚才的S3桶示例,并行部署的时候,三个资源同时尝试创建,你会看到日志里三个都是“Creating...”,然后三个都是“Failed”,错误信息都是“invalid region”,但你根本不知道哪个是最先失败的,也不知道是不是其他资源依赖了这个写错的桶,导致连锁错误。而用串行模式的话,日志会清清楚楚的显示:先建bucket-good(成功),然后建bucket-bad(失败,错误是无效区域),最后建bucket-depend(因为没依赖bad桶,所以会成功),这样你就会立刻明白,只有bucket-bad是根因,其他两个的报错是干扰项,排查效率提升了不止一倍。

三、从串行日志里揪出真实诱因

3.1 串行日志的核心优势

串行模式下的日志是按时间线性排列的,每个资源的生命周期(创建、更新、删除)都是连续的,没有交叉,你可以像看剧本一样一步步追踪整个部署的全过程。遇到错误的时候,直接定位到第一个失败的资源,查看它的具体错误信息,就能100%确定根因,不需要在一堆乱码里反复翻找线索。比如刚才的连锁报错案例,并行时你可能会误以为三个桶都有问题,串行日志会帮你快速剔除干扰项,只关注真正出错的资源。

3.2 常见诱因的排查技巧

用串行模式排查时,重点看三个细节:第一,失败资源的具体错误类型,是参数写错了?权限不够?还是资源规格不支持?第二,失败资源的依赖资源是否已成功创建,如果依赖的资源还没启动,说明依赖设置错误;如果依赖的资源已经成功,说明问题出在当前资源本身。第三,有没有循环依赖,比如刚才的安全组示例,串行时会明确看到两个资源互相引用,这时候就需要调整代码,要么用CIDR替换安全组引用,要么显式添加dependsOn参数,要么去掉循环依赖,改成单向关联。

四、该用串行模式的场景和注意事项

4.1 应用场景

什么时候一定要用串行模式?第一,首次编写Pulumi脚本,还没理清资源依赖关系,用串行模式先跑一遍,确认每个资源都能按顺序创建,没有隐藏的依赖漏洞;第二,部署时遇到死锁错误,日志混乱到无法排查,开串行模式能快速找到循环依赖的两个资源;第三,并行部署时多个资源同时报错,分不清根因,串行模式能帮你找到第一个失败的资源,确定真正的问题;第四,需要验证单个资源的配置修改是否正确,比如你改了某个安全组的端口,用串行模式只更新这个资源,不需要跑全部,速度也足够快,适合小范围调试。

4.2 技术优缺点

串行模式的优点:第一,调试效率极高,日志清晰无干扰,能精确定位每个资源的问题;第二,能暴露并行模式下很难发现的循环依赖和隐式依赖;第三,调试时不需要反复部署多次,每次跑都是线性过程,能快速缩小问题范围。缺点:第一,部署速度慢,比并行模式慢数倍甚至数十倍,尤其是资源数量多的时候,比如建20个资源,串行要20分钟,并行只要1分钟,所以只适合调试阶段,绝对不能用于正式部署;第二,无法模拟并行时的资源争用问题,但这些问题通常在正式部署前可以通过小规模并行测试发现,不影响调试阶段的使用。

4.3 注意事项

使用串行模式时要注意三个关键事项:第一,调试完成后,必须把并行度改回默认值,默认值通常是CPU核心数或50,比如用pulumi.runtime.setParallel(50),或者命令行不加--parallel 1,不然正式部署时会慢到无法忍受;第二,串行模式下不要忽略隐式依赖,比如你用资源A的ID创建资源B,Pulumi会自动设置依赖,但要确认这个自动依赖是否正确,避免漏依赖导致的错误;第三,遇到循环依赖后,修复时尽量调整为单向依赖,不要保留循环,这样不仅方便调试,还能提升并行部署的稳定性。

五、总结

Pulumi的串行执行模式是调试依赖问题的“利器”,虽然牺牲了部署速度,但能解决并行模式下无法定位死锁、连锁报错的核心痛点,对于IaC开发者来说,掌握这个技巧能大幅减少部署调试的时间,避免线上部署事故。在实际使用中,我们可以把串行模式作为调试的第一选择,定位问题后再切回并行模式,验证修复后的代码是否正常,这样既能保证调试效率,又能兼顾部署速度。