如何使用Github Action构建前后端项目CI/CD pipeline

2024-11-23

背景

目前个人博客项目已经上线,但是由于没有自动化流水线,需要经常手动进行打包部署操作,比较麻烦。由于项目代码是在github上维护的,因此考虑使用github action引入CI/CD pipeline实现自动化打包部署。

项目参数

  • 后端框架:Springboot
  • 前端框架:React
  • 后端云服务:AWS EC2 + Elastic Container Registry
  • 前端云服务:AWS S3 + CloudFont
  • 后端容器:Docker
  • 代码管理:Github
  • 后端包管理工具:Gradle
  • 前端包管理工具:npm

预期目标

  • 前端 CI/CD 配置:

    • 在 main 分支有变更时触发。
    • 执行 npm run build,生成构建文件。
    • 上传构建文件到 AWS S3。
    • 使 CloudFront 缓存失效。
  • 后端 CI/CD 配置:

    • 在 main 分支有变更时触发。
    • 执行 gradle build,生成应用。
    • 使用 Docker 构建镜像并推送至 AWS ECR。
    • 在 EC2 实例上停止旧镜像并拉取新的镜像进行部署。

操作步骤

AWS部分

1. 在AWS上创建一个新的 IAM 用户

首先,创建一个新的 IAM 用户,并为其配置访问权限:

  1. 登录到 AWS 管理控制台。
  2. 进入 IAM(身份与访问管理) 控制台。
  3. 在左侧导航栏中,选择 Users,然后点击 Add user。
  4. 输入用户名(例如:github-ci-user)。
  5. 在权限设置中,选择 Attach policies directly,并为用户添加以下策略。

2. 为 GitHub 用户配置权限策略

你需要为 GitHub 用户提供以下权限:

  1. S3 权限(用于上传文件到 S3)
    为了让 GitHub 用户能够上传前端构建文件到 AWS S3 存储桶,分配以下权限:
    • AmazonS3FullAccess:此策略授予用户对 S3 存储桶的完全访问权限(包括上传文件、修改文件等)。
  2. ECR 权限(用于上传 Docker 镜像到 ECR) 为了让 GitHub 用户能够推送 Docker 镜像到 AWS ECR,分配以下权限:
    • AmazonEC2ContainerRegistryFullAccess:该策略允许用户访问 ECR,包括创建、推送和拉取镜像。
  3. EC2 权限(用于停止、启动和更新 Docker 容器) 为了让 GitHub 用户能够在 EC2 实例上执行部署操作(例如拉取新的 Docker 镜像并启动容器),需要配置 EC2 和 SSH 相关权限。推荐策略如下:
    • AmazonEC2FullAccess:此策略允许用户管理 EC2 实例、修改安全组、停止和启动实例等。
    • AmazonSSMFullAccess:如果你希望通过 AWS Systems Manager (SSM) 来管理 EC2 实例(例如通过 RunCommand 执行远程命令),需要这个策略。
  4. CloudFront 权限(用于清除 CloudFront 缓存) 为了清除 CloudFront 缓存,分配以下权限:
    • CloudFrontFullAccess:此策略允许用户管理 CloudFront 分发,包括清除缓存。
  5. IAM 权限(用于使用 AWS 密钥) 如果你需要确保用户能成功登录 AWS 并执行 CLI 操作,还需要给用户相应的权限:
    • IAMReadOnlyAccess:如果用户只是用来执行 GitHub Actions 和与 AWS 交互,不需要更改 IAM 设置,可以授予此只读权限。

3. 为用户生成访问密钥

生成并记录 GitHub 用户的 访问密钥(AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY):

  1. 在 IAM 用户设置页面,选择 Security credentials 标签。
  2. 点击 Create access key,并记录生成的 访问密钥 ID 和 秘密访问密钥。将它们存储在 GitHub Secrets 中,以便在 GitHub Actions 脚本中使用。

Github部分

1. 设置 GitHub Secrets

为了保护敏感信息,我们将使用 GitHub Secrets。以下是你需要配置的 Secrets:

  • 登录到 GitHub,进入你的仓库页面。
  • 点击 Settings > Secrets And Variables> Actions > New repository secret。 创建以下 Secrets:
  • 前端 S3 和 CloudFront:
    • AWS_ACCESS_KEY_ID:你在 AWS 中创建的访问密钥 ID。
    • AWS_SECRET_ACCESS_KEY:对应的访问密钥。
    • AWS_REGION:AWS 区域(例如 us-west-1)。
    • CLOUDFRONT_DISTRIBUTION_ID:你的 CloudFront 分发 ID。
  • 后端 ECR 和 EC2:
    • AWS_ACCESS_KEY_ID:你的 AWS 访问密钥 ID。
    • AWS_SECRET_ACCESS_KEY:对应的访问密钥。
    • AWS_ACCOUNT_ID:你的 AWS 账户 ID。
    • AWS_REGION:AWS 区域(例如 us-west-1)。
    • EC2_SSH_KEY:EC2 实例的私钥内容以文本形式存储。
    • EC2_CONTAINER_IP: EC2 实例的公网IP
    • EC2_CONTAINER_USER: SSH登录EC2 实例的用户名

2. 在 GitHub 中添加 CI/CD 脚本

  • 对于前端和后端项目:
    • 在你的项目文件夹中创建一个 .github/workflows 目录。
    • 在该目录下创建一个名为 ci.yml 的文件,粘贴之前的 CI/CD 脚本内容。
  • 添加前端项目CI/CD脚本:
name: Frontend CI/CD Pipeline
on:
  push:
    branches:
      - main  # 监控main分支的推送

jobs:
  build-and-delpoy:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'  # 使用Node.js 18版本
    - name: Install dependencies
      run: npm install
    - name: Build the project
      run: npm run build
    - name: Upload to S3
      uses: jakejarvis/s3-sync-action@master
      with:
        args: --acl public-read --follow-symlinks
      env:
        AWS_S3_BUCKET: 'my-blog-front-end-bucket'
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: 'us-west-1'   # optional: defaults to us-east-1
        SOURCE_DIR: './build'      # optional: defaults to entire repository
    - name: Invalidate CloudFront Cache
      uses: chetan/invalidate-cloudfront-action@v2
      env:
        AWS_REGION: 'us-west-1'
        DISTRIBUTION: 'E2Y5N6ITGS9BEG'  # 替换为你的CloudFront分发ID
        PATHS: '/*'  # 使所有文件的缓存失效
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  • 添加后端项目CI/CD脚本:
name: Backend CI/CD Pipeline
on:
  push:
    branches:
      - main  # 监控main分支的推送
env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
  AWS_REGION: ${{ secrets.AWS_REGION }}
  EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }}
  EC2_CONTAINER_IP: ${{ secrets.EC2_CONTAINER_IP }}
  EC2_CONTAINER_USER: ${{ secrets.EC2_CONTAINER_USER }}
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'corretto'
          java-version: '17'  # 设置JDK 17版本
      - name: Set up Gradle
        uses: gradle/actions/wrapper-validation@v4
      - name: Build the project
        run: |
          chmod +x ./gradlew  #  gradlew 脚本添加执行权限
          ./gradlew build
      - name: Log in to Amazon ECR
        uses: aws-actions/amazon-ecr-login@v2
        with:
          aws_access_key_id: ${AWS_ACCESS_KEY_ID}
          aws_secret_access_key: ${AWS_SECRET_ACCESS_KEY}
          region: ${AWS_REGION}  # 替换为你的AWS区域
      - name: Build Docker image
        run: |
          docker build -t blog:latest .
          docker tag blog:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/victor/blog:latest
      - name: Push Docker image to ECR
        run: |
          docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/victor/blog:latest
      - name: Set up SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${EC2_SSH_KEY}" > ~/.ssh/id_rsa.pem
          chmod 600 ~/.ssh/id_rsa.pem
          # 获取并添加 EC2 主机的公钥到 known_hosts 文件中
          ssh-keyscan -H 18.144.49.164 >> ~/.ssh/known_hosts
          # 确保 known_hosts 文件权限正确
          chmod 644 ~/.ssh/known_hosts
      - name: Deploy to EC2
        run: |
          ssh -i ~/.ssh/id_rsa.pem ${EC2_CONTAINER_USER}@${EC2_CONTAINER_IP} << 'EOF'
            sudo -s
            aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | docker login --username AWS --password-stdin ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com
            docker pull ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/victor/blog:latest
            # 停止并移除现有容器
            docker stop my-blog || true
            docker rm my-blog || true
            docker run -d --name my-blog -p 8080:8080 ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/victor/blog:latest
          EOF
说明:
  • GitHub Secrets:用于存储敏感信息,如 AWS 密钥、EC2 SSH 密钥、AWS 账户 ID 等。
    • AWS_ACCESS_KEY_ID
    • AWS_SECRET_ACCESS_KEY
    • AWS_ACCOUNT_ID
    • AWS_REGION
    • EC2_SSH_KEY(EC2实例的SSH密钥)
  • 前端:
    • 通过 npm run build 构建项目,并上传到 S3。
    • 使用 invalidate-cloudfront-action 使 CloudFront 缓存失效,以便用户能及时看到最新的内容。
  • 后端:
    • 使用 gradle build 构建 Spring Boot 项目。
    • 使用 Docker 构建镜像并推送至 AWS ECR。
    • 通过 SSH 登录到 EC2 实例,停止并删除旧容器,拉取新的镜像并启动新容器。

3. 调试

推送main分支变更或将pr合入main分支触发脚本测试。在本文中使用的前后端的CI/CD脚本经过调试后均可成功实现既定目标。

参考文档

https://github.com/actions/setup-java
https://github.com/aws-actions/amazon-ecr-login
https://github.com/jakejarvis/s3-sync-action
https://github.com/chetan/invalidate-cloudfront-action