背景
目前个人博客项目已经上线,但是由于没有自动化流水线,需要经常手动进行打包部署操作,比较麻烦。由于项目代码是在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 用户,并为其配置访问权限:
- 登录到 AWS 管理控制台。
- 进入 IAM(身份与访问管理) 控制台。
- 在左侧导航栏中,选择 Users,然后点击 Add user。
- 输入用户名(例如:github-ci-user)。
- 在权限设置中,选择 Attach policies directly,并为用户添加以下策略。
2. 为 GitHub 用户配置权限策略
你需要为 GitHub 用户提供以下权限:
- S3 权限(用于上传文件到 S3)
为了让 GitHub 用户能够上传前端构建文件到 AWS S3 存储桶,分配以下权限:- AmazonS3FullAccess:此策略授予用户对 S3 存储桶的完全访问权限(包括上传文件、修改文件等)。
- ECR 权限(用于上传 Docker 镜像到 ECR)
为了让 GitHub 用户能够推送 Docker 镜像到 AWS ECR,分配以下权限:
- AmazonEC2ContainerRegistryFullAccess:该策略允许用户访问 ECR,包括创建、推送和拉取镜像。
- EC2 权限(用于停止、启动和更新 Docker 容器)
为了让 GitHub 用户能够在 EC2 实例上执行部署操作(例如拉取新的 Docker 镜像并启动容器),需要配置 EC2 和 SSH 相关权限。推荐策略如下:
- AmazonEC2FullAccess:此策略允许用户管理 EC2 实例、修改安全组、停止和启动实例等。
- AmazonSSMFullAccess:如果你希望通过 AWS Systems Manager (SSM) 来管理 EC2 实例(例如通过 RunCommand 执行远程命令),需要这个策略。
- CloudFront 权限(用于清除 CloudFront 缓存)
为了清除 CloudFront 缓存,分配以下权限:
- CloudFrontFullAccess:此策略允许用户管理 CloudFront 分发,包括清除缓存。
- IAM 权限(用于使用 AWS 密钥)
如果你需要确保用户能成功登录 AWS 并执行 CLI 操作,还需要给用户相应的权限:
- IAMReadOnlyAccess:如果用户只是用来执行 GitHub Actions 和与 AWS 交互,不需要更改 IAM 设置,可以授予此只读权限。
3. 为用户生成访问密钥
生成并记录 GitHub 用户的 访问密钥(AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY):
- 在 IAM 用户设置页面,选择 Security credentials 标签。
- 点击 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