log
码中赤兔

使用 AWS CDK(TypeScript)构建 CodePipeline

发布于 2025年4月27日
更新于 2025年4月27日
25 分钟阅读
AWS

随着微服务和容器化时代的到来,利用 AWS 服务构建一条端到端的持续集成/持续部署(CI/CD)流程已经成为常见需求。本文将以 AWS CDK(TypeScript) 为例,带你一步步搭建一条从 CodeCommit 到 CodeBuild,再到 S3 发布制品的 CodePipeline。

1. 项目结构概览

在开始之前,假设你已经安装好 AWS CDK 并初始化了一个 TypeScript 项目。目录结构示例如下:

my-cdk-project/
├── bin/
│   └── my-cdk-project.ts       // CDK 应用入口
├── lib/
│   └── code-pipeline-stack.ts  // 我们接下来要编写的 Stack
├── build-spec.yaml             // CodeBuild 构建规范
├── package.json
└── tsconfig.json

2. 核心 CDK 代码

import * as cdk from 'aws-cdk-lib';
import { Duration } from 'aws-cdk-lib';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as codecommit from 'aws-cdk-lib/aws-codecommit';
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';
import * as actions from 'aws-cdk-lib/aws-codepipeline-actions';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import type { Construct } from 'constructs';

export class CodePipelineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. 引用已有 S3 Bucket 作为制品存储
    const artifactBucket = s3.Bucket.fromBucketArn(this, 'ArtifactBucket', 'arn:aws:s3:::your-artifact-bucket');

    // 2. 引用已有 IAM 角色,用于 Pipeline 执行权限
    const pipelineRole = iam.Role.fromRoleArn(this, 'PipelineRole', 'arn:aws:iam::123456789012:role/CodePipelineRole', {
      mutable: false,
    });
    const eventBridgeRole = iam.Role.fromRoleArn(
      this,
      'EventBridgeRole',
      'arn:aws:iam::123456789012:role/EventBridgeRole',
      { mutable: false },
    );

    // 3. 创建 CodeCommit 仓库
    const repository = new codecommit.Repository(this, 'MyRepo', {
      repositoryName: 'my-app-repo',
    });

    // 4. 定义 CodeBuild 项目
    const buildProject = new codebuild.PipelineProject(this, 'BuildProject', {
      projectName: 'my-app-build',
      role: pipelineRole,
      queuedTimeout: Duration.hours(1),
      timeout: Duration.hours(1),
      buildSpec: codebuild.BuildSpec.fromSourceFilename('build-spec.yaml'),
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        computeType: codebuild.ComputeType.SMALL,
        privileged: true,
        environmentVariables: {
          ENV: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: 'prod',
          },
        },
      },
    });

    // 5. 定义 Pipeline Artifact
    const sourceArtifact = new codepipeline.Artifact('SourceArtifact');
    const buildArtifact = new codepipeline.Artifact('BuildArtifact');

    // 6. 配置 Source 阶段:监听 CodeCommit 的 prod 分支
    const sourceAction = new actions.CodeCommitSourceAction({
      actionName: 'Fetch_Source',
      repository,
      branch: 'prod',
      output: sourceArtifact,
      role: pipelineRole,
      eventRole: eventBridgeRole,
      trigger: actions.CodeCommitTrigger.EVENTS,
      runOrder: 1,
    });

    // 7. 配置 Build 阶段:调用 CodeBuild
    const buildAction = new actions.CodeBuildAction({
      actionName: 'Run_Build',
      project: buildProject,
      input: sourceArtifact,
      outputs: [buildArtifact],
      role: pipelineRole,
      runOrder: 2,
    });

    // 8. 创建 Pipeline 并添加各个 Stage
    const pipeline = new codepipeline.Pipeline(this, 'CodePipeline', {
      pipelineName: 'MyAppPipeline',
      artifactBucket,
      role: pipelineRole,
    });
    pipeline.addStage({ stageName: 'Source', actions: [sourceAction] });
    pipeline.addStage({ stageName: 'Build', actions: [buildAction] });
  }
}

3. 示例 build-spec.yaml

version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 22
  pre_build:
    commands:
      - npm run lint
      - npm run test
  build:
    commands:
      - npm ci
      - npm run build
      - npx cdk deploy <StackName> --require-approval never

4. 跨账户 CodePipeline

要在 CDK 中使用另一个 AWS 账号(假设为 Account A)里的 CodeCommit 仓库,并在本账号(Account B)中通过 CodePipeline 拉取代码,你需要完成以下几个关键步骤:

4.1. 在 Account A(CodeCommit 所在账号)配置跨账号访问角色

打开 Account A 的 IAM 控制台,创建一个新角色(如 cross-account-access-role),信任策略指定允许 Account B 的根账号或 Pipeline 服务角色来 sts:AssumeRole

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::ACCOUNT_B_ID:root" },
      "Action": "sts:AssumeRole"
    }
  ]
}

4.2. 给这个角色附加对目标仓库的只读权限:

  • AWS 托管策略 AWSCodeCommitReadOnly;
  • 如果使用 KMS 加密制品,还要给角色添加 kms:Decryptkms:GenerateDataKey* 等权限,并在 CMK 的 Key Policy 里允许该角色使用

4.3. 在 Account B(Pipeline 所在账号)引用跨账号资源

在你的 CDK Stack 中,先通过 ARN 导入外部仓库和跨账号角色:

// 从 Account A 导入 CodeCommit 仓库
const externalRepo = codecommit.Repository.fromRepositoryArn(
  this,
  'ExternalRepo',
  'arn:aws:codecommit:ap-northeast-1:ACCOUNT_A_ID:MySharedDemoRepo',
);

// 从 Account A 导入刚才创建的跨账号访问角色
const crossAccountRole = iam.Role.fromRoleArn(
  this,
  'CrossAccountRole',
  'arn:aws:iam::ACCOUNT_A_ID:role/cross-account-access-role',
);

4.4. 在 CDK 中配置 Source 动作使用跨账号角色

CodeCommitSourceAction 的 role 属性指向上述跨账号角色。

const sourceArtifact = new codepipeline.Artifact('SourceArtifact');

const crossAccountSource = new codepipeline_actions.CodeCommitSourceAction({
  actionName: 'CrossAccount_Source',
  repository: externalRepo,
  branch: 'main',
  output: sourceArtifact,
  role: crossAccountRole,
  trigger: codepipeline_actions.CodeCommitTrigger.EVENTS,
});

pipeline.addStage({
  stageName: 'Source',
  actions: [crossAccountSource],
});

这样,CodePipeline 会以你在 Account A 创建的角色身份,从该仓库拉取代码。


5. 使用Github作为Repository

如果代码是存放在Github上的话,CodePipeline还可以从Github上拉取代码。

5.1. 在 GitHub 侧创建 Personal Access Token

  • 登录 GitHub → 右上角头像 → Settings → Developer settings → Personal access tokens → Fine-grained personal access tokens → Generate new token
  • 选择至少包含 repo(读取私有仓库)或 public_repo(只读公开仓库)的权限,生成后复制好这串 Token。

5.2. 将 Token 存到 AWS Secrets Manager

aws secretsmanager create-secret \
  --name my-github-token \
  --description "GitHub OAuth token for CodePipeline" \
  --secret-string "<YOUR_TOKEN_HERE>"

这样,你就可以在 CDK 里通过 Secret 名称来安全地引用它。

5.3. 在 CDK 里引用这个 Secret

import * as cdk from 'aws-cdk-lib';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as codecommit from 'aws-cdk-lib/aws-codecommit';
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';
import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import type { Construct } from 'constructs';

export class CodePipelineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ... other settings

    const oauthToken = cdk.SecretValue.secretsManager('my-github-token');

    // 配置 GitHubSourceAction
    const githubSource = new cpactions.GitHubSourceAction({
      actionName: 'Checkout_From_GitHub',
      owner: '你的 GitHub 用户名或组织名',
      repo: '你的仓库名',
      branch: 'main', // 或你想跟踪的分支
      oauthToken: oauthToken, // 关键:把 SecretValue 传给这里
      output: sourceOutput,
      trigger: cpactions.GitHubTrigger.WEBHOOK, // 通过 Webhook 实时触发
    });

    // 将 Source 阶段加入 Pipeline
    pipeline.addStage({
      stageName: 'Source',
      actions: [githubSource],
    });
  }
}

6. 小结

本文示范了如何使用 AWS CDK(TypeScript)快速搭建:

  1. CodeCommit → CodeBuild 的基础流水线
  2. 跨账号拉取 CodeCommit 仓库
  3. 从 GitHub 拉取代码并触发构建

你可以在此基础上,灵活添加测试、部署到 Elastic Beanstalk、ECS、Lambda 等更多 Stage,构建严谨而高效的 CI/CD 流程。

关于

分享技术见解、经验和思考的个人博客

联系方式

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 码中赤兔. 版权所有