背景
原本我的博客是采用经典的 Docker+Nginx+Gitlab-runner 方式部署的:
-
在服务器上安装 GitLab Runner
- 注册 Runner 并与 GitLab 仓库关联
- 配置 Runner 的执行器(通常选择 Shell 或 Docker)
- 设置 Runner 的标签和运行权限
-
提交代码到 GitLab 仓库
- 在仓库中配置
.gitlab-ci.yml
文件 - 定义构建、测试、部署等阶段
- 设置环境变量和构建参数
- 在仓库中配置
-
自动触发 CI/CD
- GitLab 检测到代码提交
- Runner 拉取最新代码
- 执行构建流程(npm build 等)
- 部署构建产物到服务器指定目录
- 重启相关服务
但是这种部署方式有几个问题:
- Runner 配置繁琐:需要手动安装 GitLab Runner,配置 Runner 的执行器和标签,有一定的复杂性和学习成本。
- 服务器资源占用:GitLab Runner 是安装在服务器上的,所以代码的构建和打包过程实际上是在我的服务器上进行的。每次构建都需要占用服务器资源,尤其是在需要下载大量依赖包,或者构建出现错误时,会导致部署不稳定甚至是服务器宕机,需要手动停止并重启服务。
- 政策问题:GitLab SaaS(国际版)宣布即将停止向国内用户提供服务,同时我也并不想使用国内版的极狐 GitLab。
因此,我决定将部署流程进行改造,采用 GitHub Actions 和阿里云容器服务的方式进行部署。其大概流程是:
- 提交代码到 GitHub 仓库的 release 分支
- 使用 GitHub Actions 打包 Docker 镜像
- 将镜像推送到阿里云的免费镜像仓库
- 执行服务器上的命令拉取镜像
- 重启服务
新的部署流程具有以下优点:
-
构建资源优化
- 利用 GitHub Actions 提供的云端服务器进行构建,每个工作流程可使用最高配置的 Ubuntu 虚拟机
- node_modules 等依赖包的下载和构建过程在云端完成,不占用自己服务器的带宽和计算资源
- 支持并行构建,可以同时运行多个工作流
-
存储空间与成本优化
- 构建过程产生的临时文件都在云端处理,不占用服务器存储空间
- 阿里云容器镜像服务个人版提供免费存储空间,无需在服务器存储历史镜像
- GitHub Actions 对公共仓库免费,每月有大量构建时间配额
- 整体降低了服务器资源消耗和运维成本
-
部署稳定性提升
- 构建失败不会影响现有服务,因为构建过程完全在云端进行
- Docker 容器的特性使得新旧版本切换更加平滑
- 支持快速回滚,只需重新部署之前的镜像版本
-
安全性增强
- 构建环境与生产环境完全隔离
- GitHub Actions 提供了安全的密钥管理机制
- 容器化部署减少了环境依赖风险
实现步骤
1. 准备阿里云容器镜像服务
- 登录阿里云控制台,进入”容器镜像服务”
- 创建命名空间(如果没有)
- 创建镜像仓库:
- 选择本地仓库
- 代码源选择”本地仓库”
- 记录下仓库地址,格式如:
registry.cn-hangzhou.aliyuncs.com/your-namespace/astro-citrus
- 获取访问凭证,用于登录阿里云容器镜像服务
2. 配置 GitHub Secrets
在 GitHub 仓库中添加以下 Secrets(路径:仓库 Settings -> Secrets and variables -> Actions):
ALIYUN_USERNAME
: 阿里云容器镜像服务登录名ALIYUN_PASSWORD
: 阿里云容器镜像服务密码(访问凭证)SERVER_HOST
: 服务器 IP 地址SERVER_USERNAME
: 服务器登录用户名SERVER_SSH_KEY
: 服务器 SSH 私钥(需要配对的公钥已添加到服务器)
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"
3. 创建 GitHub Actions 工作流配置
这里除了使用 GitHub Actions 直接触发服务器部署,也可以使用 Webhook 触发服务器部署。 但是需要在服务器上搭建 webhook 服务,这里不再赘述。(阿里云的 webhook 服务好像是收费的)
# 工作流名称
name: Docker Publish
on:
push:
branches: [ "release" ]
# 可选:手动触发部署
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 检出代码到工作目录
- name: Checkout repository
uses: actions/checkout@v3
# 设置 Docker Buildx,用于构建多平台镜像
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# 登录到阿里云容器镜像服务
- name: Login to Aliyun Container Registry
uses: docker/login-action@v2
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALIYUN_USERNAME }}
password: ${{ secrets.ALIYUN_PASSWORD }}
# 构建 Docker 镜像并推送到阿里云
- name: Build and push
uses: docker/build-push-action@v4
with:
# 指定构建上下文为当前目录
context: .
# 启用推送
push: true
# 指定镜像标签,包括 latest 和 commit hash
tags: |
registry.cn-hangzhou.aliyuncs.com/huadong_cangku/blog:latest
registry.cn-hangzhou.aliyuncs.com/huadong_cangku/blog:${{ github.sha }}
# 通过 SSH 连接部署服务器并执行部署脚本
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /root/astro-citrus
bash deploy.sh
timeout: 60s # 增加超时时间
command_timeout: 20m # 增加命令执行超时时间
4. 服务器准备工作
- 确保服务器已安装 Docker:
curl -fsSL https://get.docker.com | sh
- 服务器需要先登录阿里云容器镜像服务:
docker login registry.cn-hangzhou.aliyuncs.com
- 创建部署目录并配置文件:
在服务器上创建一个专门用于存放部署相关文件的目录,主要用来存放部署时需要用到的 deploy.sh 部署脚本和 nginx.conf 配置文件,以及作为容器运行时挂载配置文件的源目录。
# 创建部署目录
mkdir -p /root/astro-citrus
cd /root/astro-citrus
5. 编辑服务器上的 Nginx 配置文件
使用vim nginx.conf
命令创建并编辑 Nginx 配置文件:
server {
listen 80;
server_name sunburst.fun;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name sunburst.fun;
ssl_certificate /etc/nginx/ssl/sunburst.fun_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/sunburst.fun.key;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
这里的 nginx.conf 配置文件包含了以下关键设置:
- HTTP 到 HTTPS 的自动重定向
- SSL 证书配置(确保将证书文件放在服务器的 /etc/nginx/ssl 目录下)
- 静态文件服务的根目录设置
- 支持单页应用的路由重写规则
注意:在运行容器之前,请确保:
- 将 SSL 证书文件(.crt 和 .key)放置在服务器的 /etc/nginx/ssl 目录下
- 证书文件名与配置文件中的路径保持一致
- SSL 证书目录的权限设置正确
6. 编辑服务器上的部署脚本
使用vim deploy.sh
命令创建并编辑部署脚本,同时确保 deploy.sh 有执行权限: chmod +x deploy.sh
#!/bin/bash
# 定义容器和镜像的环境变量
CONTAINER_NAME="astro-citrus"
IMAGE_NAME="registry.cn-hangzhou.aliyuncs.com/huadong_cangku/blog:latest"
# 从阿里云拉取最新的镜像
echo "Pulling latest image..."
docker pull $IMAGE_NAME
# 停止并删除旧的容器(如果存在)
# || true 确保即使容器不存在也不会报错
echo "Stopping and removing old container..."
docker stop $CONTAINER_NAME || true
docker rm $CONTAINER_NAME || true
# 启动新的容器
echo "Starting new container..."
docker run -d \
--name $CONTAINER_NAME \
--restart unless-stopped \
-p 80:80 \
-p 443:443 \
-v /root/astro-citrus/nginx.conf:/etc/nginx/conf.d/default.conf \
-v /etc/nginx/ssl:/etc/nginx/ssl \
$IMAGE_NAME
# 检查容器是否成功启动
if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then
echo "Container started successfully"
else
echo "Container failed to start"
exit 1
fi
# 清理不再使用的镜像以节省磁盘空间
# -f 表示强制删除,不需要确认
echo "Cleaning up old images..."
docker image prune -f
注意事项:
- 考虑添加健康检查
- 考虑配置日志收集
- 建议添加版本标签,而不是只用 latest 标签
7. 部署流程测试
- 创建测试分支:
git checkout -b test-deploy
- 提交一些更改:
git add .
git commit -m "test: deploy workflow"
- 推送到 release 分支:
git push origin test-deploy:release
8. 监控和维护
-
查看部署状态:
- GitHub Actions 页面查看工作流运行状态
- 服务器上检查容器状态:
docker ps docker logs astro-citrus
-
常见问题排查:
- 检查 GitHub Secrets 是否正确配置
- 确认服务器 SSH 连接是否正常
- 验证阿里云容器服务的访问权限
- 检查服务器防火墙设置