如果你用 GitHub Actions 部署到 EC2,常规做法是把私钥放进仓库的 secrets,然后通过 SSH 连上服务器。这招确实管用,但代价是你得长期保存一把密钥,还得把 22 端口暴露给公网——或者至少暴露给 GitHub 那庞大的 IP 段。AWS 其实有更好的答案:Systems Manager Run Command。

原理很简单:实例上的 SSM Agent 主动向 AWS 发起出站连接,你通过 SSM API 下发命令。没有入站端口,没有需要轮换的密钥,而且每条命令都会被记录在 CloudTrail 里。你的实例甚至可以放在没有公网 IP 的私有子网里,这套方案照样能跑。

打开网易新闻 查看精彩图片

为什么我放弃了 SSH 部署

我想在自己的流水线里用这套方案,但在 marketplace 上翻了一圈,没找到一个做得好的 action。有些只是对 aws ssm send-command 的薄封装,命令发出去了,根本不检查是否真的执行成功。有些倒是会轮询,但把远程的退出码吞掉了——部署明明失败了,流水线却显示绿色。还有的撞上 SSM 输出限制(大约 24 KB),日志正好在最关键的地方被截断。还有几个干脆就是没人维护的废弃项目。

所以我自己写了一个:ankurk91/aws-ssm-run-command-action

SSM 和 SSH 的真实对比

两种方案都能完成部署,但在 CI/CD 场景下,差异很明显:

  • 安全性和访问控制:SSM 全面胜出。没有入站端口,没有长期密钥,权限通过 IAM 角色精细管控。
  • 实时输出流:SSH 保留了这个优势,SSM 做不到。
  • 文件传输:SSH 的 scp 可以直接推文件,SSM 不行。

但对部署脚本来说,这两个优势我都没觉得缺。完整日志反正会落到 S3 里,而且让服务器自己从 S3 或镜像仓库拉构建产物,通常比从 runner 用 scp 推过去更合理。

AWS 侧需要准备三样东西

要在自己的流水线里用起来,AWS 这边需要三样东西:一个允许 GitHub Actions 代入的 IAM 角色、一台装了 SSM Agent 的 EC2 实例、一个用于存放日志的 S3 桶。角色和实例的配置细节在项目仓库里都有,这里直接看完整的部署工作流:

name: Deployon:  push:    branches: [main]permissions:  id-token: write  contents: readjobs:  deploy:    runs-on: ubuntu-latest    steps:      - name: Configure AWS Credentials        uses: aws-actions/configure-aws-credentials@v6        with:          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}          aws-region: ${{ vars.AWS_REGION }}      - name: Run commands on EC2        uses: ankurk91/aws-ssm-run-command-action@v1        with:          ec2_instance_id: ${{ vars.EC2_INSTANCE_ID }}          run_as_user: ubuntu          log_bucket_name: ${{ vars.LOG_BUCKET_NAME }}          commands: |            set -e            cd /var/www/app            git pull --ff-only