最近搞的自用小工具有部署的需求,并且我很少做上线应用的工作,因此在博客记录一下这次部署,方便后续参考

项目构成:vite react 前端 + express 后端 + mongodb

第一次部署时尝试了 vercel + railway + mongodb atlas,前端部署没什么问题,后端我是第一次部署,没啥经验,一直 502,查了一下后台 console 还发现数据库访问超时了,问了一下后端的朋友说可能是因为 railway 在美国机房,我选的数据库机房地区在香港所以容易超时……但我重新注册了一个在美国的数据库之后依然超时。

鉴于本来我就不喜欢应用只能挂梯子访问这一点,所以打算前后端都用阿里云重新部署一遍,数据库则仍然白嫖 mongo 的 cluster0。

以下记录将约定我的前端部署在域名 x.psyqlk.space,后端部署在域名 api.x.psyqlk.space;前端使用阿里云 OSS 部署,并使用 history 方式管理路由。而后端会运行在阿里云 ECS 服务器的 3000 端口,并使用 nginx 反代到指定域名上

前端部署(OSS)

之前的博客其实有记录过前端如何用阿里云 oss 实现自动部署,但最近重新参考这篇博客部署前端的时候发现阿里云改版了不少,所以还是按照现在的排版重新记录一下吧

创建 Bucket

打开 阿里云 OSS 管理控制台,点击 创建 Bucket,其中最重要的是选择 自定义创建 并取消开通 阻止公共访问读写权限 选择 公共读,最后点击 完成创建

创建 Bucket

点击刚刚创建的 Bucket,进入 数据管理->静态页面,将页面设置改成如下的样子(因为项目前端使用 history 路由,如果文件 404 规则没有设置为 redirect,在子路由刷新页面时会 404,很影响用户体验

静态页面设置

进入 Bucket 配置->域名管理,点击 绑定域名

域名管理

输入你想部署到的子域名地址,如此处是 x.psyqlk.space

域名管理-绑定域名

然后进入 Bucket 配置->传输加速,点击 开启传输加速,复制出现的 传输加速访问域名 的值

传输加速

再进入 权限控制->Bucket 授权策略,点击 新增授权 并确定

Bucket 授权

DNS 解析

打开阿里云云解析 DNS 控制台,在域名解析中点开当前要部署的域名,点击 添加记录

添加 DNS 记录

记录类型 中选择 cname主机记录 中填写想设置的二级域名的路径(此处为 x),记录值 中粘贴刚刚复制的 传输加速访问域名 的值,点击 确认

添加 DNS 记录 2

AccessKey

创建 ACK

打开 阿里云 AccessKey 管理控制台,点击 创建用户,勾选 OpenAPI 调用访问

创建 AccessKey 用户

开启 ACK 权限

返回 阿里云 AccessKey 管理控制台,点击列表中刚刚创建的用户的 添加选项,搜索 AliyunOSSFullAccess 并勾选,最后点击 确认新增授权

创建 AccessKey 用户

将 AccessKey 添加到 Github Secrets

打开博客所在仓库,点击 Settings -> Secrets -> Actions -> New repository secret,创建 KEYSECRET,分别填入刚刚创建的用户的 AccessKey IDAccessKey Secret;打开 阿里云 OSS 管理控制台,点击项目对应的 Bucket,进入 概览,翻到 访问端口,将此处 Endpoint 的值复制到 New repository secret 并命名为 OSS_ENDPOINT,同样将此处 Bucket 域名 的值命名为 OSS_BUCKET

然后点击博客仓库的 Actions -> set up a workflow yourself,为仓库添加一个 workflow。以下是 gpt 帮我生成的用于部署 vite react 项目的可用 workflow,可供参考:

name: Deploy to Aliyun OSS

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "18"

      - name: Install dependencies
        run: npm install

      - name: Build project
        run: npm run build

      - name: Install ossutil
        run: |
          wget http://gosspublic.alicdn.com/ossutil/1.7.2/ossutil64
          chmod +x ossutil64
          mv ossutil64 /usr/local/bin/ossutil

      - name: Configure ossutil
        env:
          OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT }}
          ACCESS_KEY_ID: ${{ secrets.KEY }}
          ACCESS_KEY_SECRET: ${{ secrets.SECRET }}
        run: |
          ossutil config -e $OSS_ENDPOINT -i $ACCESS_KEY_ID -k $ACCESS_KEY_SECRET --language EN

      - name: Clean up old files
        run: |
          ossutil rm oss://${{ secrets.OSS_BUCKET }}/ --recursive --force

      - name: Upload to OSS
        run: |
          ossutil cp -r dist/ oss://${{ secrets.OSS_BUCKET }} --recursive

完成后点击 Start commit,然后等 CICD 结果。如果显示部署成功,此时应该可以通过 x.psyqlk.space 访问部署好的前端了。

SSL 配置

由于阿里云不会泛域名解析,所以每次创建二级域名都要重复一遍这个过程

进入数字证书管理服务,选择 SSL 证书管理 -> 个人测试证书 -> 创建证书

数字证书管理服务 - SSL 证书申请

其中,域名填写 x.psyqlk.space,并勾选 快捷签发。点击确认后等待签发成功,一般需要等待一两分钟。

进入对象存储 OSS 控制台,选择服务对应的 Bucket,并搜索 域名管理,点击 证书托管

OSS Bucket - 域名管理

选择自己刚刚在数字服务管理平台为前端域名签发的证书。如果没有可选证书,则返回数字证书管理服务确认自己申请的证书地址

OSS Bucket - 证书托管

后端部署(ECS 服务器)

此处 ECS 服务器的操作系统为 CentOS

环境初始化

登录 ECS 服务器,安装 nodejs 和 nginx

sudo yum update
sudo yum install -y nodejs npm
sudo yum install nginx

拉取 private 仓库的准备(如果是 public 仓库则跳过这一步)

生成 SSH 密钥对

此处 your_email@example.com 为你在 github 的公开邮箱。若你将 github 邮箱设为私密,则使用 github 为你生成的替代邮箱,形如 73028291+psyq55262227@users.noreply.github.com

ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

添加 SSH 公钥到 GitHub 仓库

复制生成的公钥内容:

cat ~/.ssh/id_rsa.pub

登录到 GitHub,进入你的私密仓库的设置页面。

导航到 Deploy keys 部分,添加一个新的 Deploy key(命名随意,可辨认就行),并粘贴公钥内容。选择 Allow write access 选项。

将私钥添加到 ECS 实例

在 ECS 实例上创建.ssh 目录,并将私钥添加到 authorized_keys 中:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "YOUR_PRIVATE_KEY_CONTENT" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa

配置 SSH Agent

确保 SSH Agent 正在运行,并添加私钥:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa

拉取仓库

git clone https://github.com/your-username/your-repository.git
cd your-repository
npm install

在 express 应用配置文件更新 MongoDB 连接 URI,形如 MONGODB_URI=mongodb+srv://username:password@cluster0.mongodb.net/mydatabase?retryWrites=true&w=majority
然后用 PM2 进行进程管理

npm install -g pm2
pm2 start your-app.js
pm2 startup
pm2 save

开启安全组

进入 阿里云 ECS 控制台,点击实例并查看其安全组,然后添加 443803000(后端运行端口) 端口,授权对象设置为 0.0.0.0

ECS 安全组

此时理论上可以在 {公网ip}:{后端运行端口} 访问到自己的后端

配置域名

打开阿里云云解析 DNS 控制台,在域名解析中点开当前要部署的域名,点击 添加记录。此处选择 A 记录主机记录 填二级域名,记录值 填服务器公网 ip 地址

添加 A 记录

此时访问 api.x.psyqlk.space:3000 已经可以访问到后端

SSL 证书

申请 SSL 证书

打开阿里云数字证书管理服务,选择 SSL 证书管理 -> 个人测试证书 -> 创建证书,填写完域名名称后勾选快捷签发并确定

申请 SSL 证书

证书签发成功后,点击 下载 并选择 nginx 的下载按钮,得到 SSL 证书的 key(ssl_certificate_key) 和 pem(ssl_certificate)

nginx SSL 证书下载

nginx SSL 证书下载 2

将 SSL 证书上传到服务器 & nginx 反代配置

win 可以下载 WinSCP,将刚刚下载好的 SSL 证书上传到服务器,存放到 etc/nginx/ssl 路径

然后在 etc/nginx/conf.d 新建 nginx1.confnginx.conf

# etc/nginx/conf.d/nginx1.conf 将 http 转为 https
server {
    listen       80;
    server_name  api.x.psyqlk.space;
    rewrite /(.*) https://api.x.psyqlk.space/$1 permanent;
}
# etc/nginx/conf.d/nginx.conf 配置域名,反代服务
server {
    listen 443 ssl;
    # 后端部署的域名
    server_name api.x.psyqlk.space;
    # SSL 证书在服务器中的路径
    ssl_certificate /etc/nginx/ssl/api.x.psyqlk.space.pem;
    ssl_certificate_key /etc/nginx/ssl/api.x.psyqlk.space.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_prefer_server_ciphers on;
    add_header Content-Security-Policy upgrade-insecure-requests;
    access_log  /var/log/nginx/web.https.access.log;
    location / {
        # 后端反代的端口
        proxy_pass       http://localhost:3000;
        proxy_redirect   off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
# etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;
}

启动 nginx 时,使用命令 cd /etc/nginx & ./nginx -c /etc/nginx/nginx.conf

此时理论上 nginx 已经启动,并会将访问 api.x.psyqlk.space 的请求自动代理给后端所在的 {公网ip}:{后端端口}

服务是一周前上线的,本文属于复盘回忆产物,如有缺漏之处或有问题的地方敬请勘误