
最近折腾 Floci,在本地搭了一套 AWS CodePipeline、CodeBuild 和 CodeDeploy 流水线,中间踩了几个坑。这篇把完整流程、模拟器的差异和绕坑方案讲清楚:不用 AWS 账号,也不会产生账单。
为了在本地运行 AWS 持续集成/持续交付(CI/CD)流水线,我把 AWS 命令行界面(CLI)指向 Floci——一个本地 AWS 模拟器,再用 PowerShell 脚本编排每个阶段。流水线会构建 Java Web 应用,把它部署到一台所谓的“EC2 实例”上,实际上就是一个 Docker 容器,最后通过 localhost 对外提供服务。
大部分流程按原计划跑通了,唯独 CodeDeploy 钩子没有执行。于是我补了一个轻量级的 SSM(Systems Manager)代理替代方案,专门处理这些钩子。
项目地址:axololtos/cicd-pipeline-floci。整体设计参考了免费的 NextWork DevOps 挑战,但这个仓库与 NextWork 官方无关。
按照教程的常规做法,需要准备真实账号、配置 IAM(Identity and Access Management,身份与访问管理)、创建并运行 EC2(Elastic Compute Cloud,弹性计算云)实例,最后仔细清理资源。漏掉任何一个步骤,都可能继续产生费用。
Floci 直接消除了这类风险,整套环境也可以反复重建:07-cleanup.ps1 负责清理,按编号排列的脚本则负责重新搭建。
代价就是模拟精度。Floci 实现的是 API(Application Programming Interface,应用程序编程接口)层面的能力,并不一定覆盖底层的全部行为。本文重点就是这些差异暴露出来之后,该怎么绕过去。

| 阶段 | 服务 | 职责 |
|---|---|---|
| Source | GitHub | 托管 Maven Web 应用 |
| Packages | CodeArtifact | mvn deploy 发布到的 Maven 仓库 |
| Build | CodeBuild | 构建 WAR 包并上传到 S3 |
| Deploy | CodeDeploy + EC2 + SSM | 通过钩子脚本获取 WAR 包并启动 Tomcat |
| Orchestrate | CodePipeline | 串联 Source、Build、Deploy |
技术栈包括:运行在 Docker 中的 Floci(挂载 Docker Socket,因此 EC2 实例会变成容器)、PowerShell 5.1+、AWS CLI v2、Docker Desktop,以及 Maven 3.5.2 / JDK(Java Development Kit,Java 开发工具包)8 构建镜像。脚本位于 scripts/:
01-start-floci.ps1 02-webapp.ps1 03-codeartifact.ps1 04-codebuild.ps1
05-deploy.ps1 06-pipeline.ps1 07-cleanup.ps1 08-screenshots.ps1
env.ps1 ssm-agent.ps1 appspec.yml compose.yaml hooks/
env.ps1 集中保存了全部 AWS 配置:伪造的访问凭证和一个服务地址。
$env:AWS_ENDPOINT_URL = "http://localhost:4566"
$env:AWS_DEFAULT_REGION = "us-east-1"
$env:AWS_ACCESS_KEY_ID = "test"
$env:AWS_SECRET_ACCESS_KEY = "test"
$env:AWS_PAGER = ""
AWS_ENDPOINT_URL 是新版 CLI v2 提供的全局地址覆盖变量,因此所有命令都不需要再追加 --endpoint-url。访问密钥随便填即可。执行 aws sts get-caller-identity 会返回账号 000000000000,这也说明命令确实连到了 Floci。
同一个文件还定义了 $MavenImage = "maven:3.5.2-jdk-8" 和一个 Mvn 辅助方法,用一次性容器运行 Maven,因此宿主机不需要安装 JDK。
首次运行的输出包含调用者身份、三个 IAM 角色、两个 S3 存储桶,以及 CodeArtifact 仓库。

在 Floci 控制台中看到的资源如下:


03-codeartifact.ps1 会创建三个 IAM 角色:codebuild-role、codedeploy-role 和 codepipeline-role;接着创建 CodeArtifact 域 nextwork 和仓库 nextwork-devops-cicd,最后从容器里执行 mvn deploy。重复运行时会出现 ConflictException ... already exists,这不影响后续操作。


04-codebuild.ps1 会创建两个存储桶,使用 tar.exe 打包源代码,确保生成的压缩包能在 Linux 环境中使用,然后把压缩包上传到 s3://nextwork-source/source.zip,再创建构建项目。脚本会持续轮询,直到 buildComplete 为 true,最后列出构建产物:存储桶 nextwork-artifacts 中有一个 2,291 字节的 nextwork-web-project.war。


Maven 在 17.269 秒后输出 BUILD SUCCESS,控制台里也能看到对应的构建日志组:

Floci 默认不会真正执行 IAM 策略校验,所以这里只需要角色 ARN 存在即可。在真实 AWS 上,如果信任策略写错,构建会直接失败。
05-deploy.ps1 会创建密钥对和开放 8080 端口的 web-sg 安全组,然后执行 aws ec2 run-instances。Floci 会启动一个名为 floci-aws-ec2-<instance-id> 的容器,并在容器中安装 Corretto 8、Tomcat 9.0.89 和 AWS CLI。


随后,脚本会将实例注册到 SSM,把 scripts/hooks 复制到容器的 /scripts 目录,再重新创建 CodeDeploy 应用和部署组。

正常情况下,部署组会通过 EC2 标签筛选目标实例。但 Floci 中这次查询没有结果,因此脚本改为把实例注册成 on-premises 实例,并且不给部署组设置任何过滤条件:
aws deploy create-deployment-group --application-name nextwork-app --deployment-group-name nextwork-dg ` --service-role-arn arn:aws:iam::000000000000:role/codedeploy-role
aws deploy register-on-premises-instance --instance-name $id ` --iam-user-arn arn:aws:iam::000000000000:user/devops-iam
在真实 AWS 上,实例内的代理会拉取命令并执行 appspec.yml 中定义的钩子。Floci 只会把命令放进 SSM 队列等待代理处理,但默认没有这个代理。如果命令在大约 500 毫秒内没有被确认,钩子就会失败。
解决办法是让 05-deploy.ps1 启动 ssm-agent.ps1 后台作业。该脚本每 100 毫秒轮询一次 GetMessages,确认收到的每条消息,对负载进行 Base64 解码,再通过 docker exec <container> sh -c 执行,最后调用 SendReply 回传状态、退出码和输出。
这个代理必须在创建部署之前启动。05 还会先清空队列中的历史命令,避免上一次失败运行留下的消息占满队列,导致正常部署错过确认窗口。

Floci 会跳过部署包下载和安装,因此钩子脚本需要自行从 S3 获取 WAR 包。最终两个钩子都以退出码 0 执行完毕,部署状态为 Succeeded,上面的代理日志可以看到完整过程。
Floci 的端口转发器把容器中的 8080 端口映射到了宿主机的 30000 端口:

06-pipeline.ps1 会接收 GitHub 用户名,把流水线定义写入 pipeline.json,然后创建流水线:Source 阶段使用 GitHub,监听 main 分支;Build 阶段引用 nextwork-build;Deploy 阶段引用 nextwork-app 和 nextwork-dg;产物存储桶则使用 nextwork-artifacts。

aws codepipeline create-pipeline --cli-input-json "file://$f"
aws codepipeline start-pipeline-execution --name nextwork-pipeline
第一次轮询时,Build 阶段还是 InProgress;下一次轮询,三个阶段已经全部变成 Succeeded:

最后通过 AWS CLI 检查完整资源状态:

s3://nextwork-source/source.zip,不是 GitHub 产物。 Floci 的 CodeBuild 只接受 S3 源或空源。要发布代码修改,需要编辑应用,然后重新执行 04 和 05。05-deploy.ps1 发起的直接部署。因此,这套环境主要用于演练流水线的结构和 API 调用,并不能证明同一份流水线定义放到真实 AWS 上也一定能通过。
需要准备 Docker Desktop、AWS CLI v2、PowerShell 5.1+ 和 Git。第 2 步需要一个空的 GitHub 仓库地址,第 6 步需要你的 GitHub 用户名。
cd scripts
Set-ExecutionPolicy -Scope Process Bypass
.\01-start-floci.ps1
.\02-webapp.ps1 -RepoUrl https://github.com/YOU/nextwork-web-project.git
.\03-codeartifact.ps1
.\04-codebuild.ps1
.\05-deploy.ps1
.\06-pipeline.ps1 -GitHubUser YOU
.\07-cleanup.ps1
执行完 05 后,使用脚本输出的端口打开 http://localhost:<port>/。我这次运行得到的是 30000。
项目地址:github.com/axololtos/cicd-pipeline-floci。如果这套方案帮你省下了一笔 AWS 账单,可以给仓库点个 Star,后续我还会继续分享更多实践内容。