web 和 node 项目部署阿里云服务器并域名访问教程

2,687 阅读19分钟

当你想把开发的前端项目和 Node 服务端项目部署至云服务器上,以便于别人能够公网访问,相信大多数开发者都要经历一个查来查去的过程,还容易踩坑。这篇文章会一步一步地教你如何做,可以让你少走些弯路。 在开始之前,我先介绍下我的项目技术栈:

  • 前端:vite4 + vue3 + vue-router
  • 服务端:koa2 + sequelize + mysql2

很常规,其实这篇教程和技术栈是没有任何关系的,毕竟我们要部署的前端项目只是打包后的静态资源文件而已,服务端进程管理器目前最好的选择也就 pm2 。

购买云服务器

现在的云服务器选择很多,比如腾讯云、阿里云等其它云,这里讲下阿里云服务器 ECS 的购买,后续的服务器配置等操作都是基于我们购买的云服务器进行的,所以如果你购买的不是阿里云的,可能本教程只能作为一个参考。

访问 阿里云服务器 ECS 主页 往下翻到产品规格,有许多种类型的服务器供我们选择,我是购买了共享型下的 2 核 4G 实例,即下图:

1.png

你看到的价格可能不一样,会因为活动(比如双 11、618)、新客专享和学生优惠,有更低的价格,总之看你用途是什么吧,个人的学习项目或博客啥的,买便宜点的就行了。 购买配置如下:

  • 实例规格:2 核 4G
  • 地域:我选的是 华东 1(杭州)
  • 操作系统:比较习惯 Linux CentOS 7.9 64 位,也可以尝试下 Alibaba Cloud Linux,官网介绍说他完全兼容 CentOS ,且长期维护;
  • 带宽:学习的话建议 1M 就可以了,我购买的是 5M 的,这玩意儿真的贵。

连接服务器

有了服务器之后自然是要登录上去进行操作,下面介绍密码登录和本地远程面密登录。

密码登录服务器

我们先尝试下通过 Workbench 远程连接服务器,如下图操作:

2.png

在这里你可能会遇到无法使用密码登录的问题,首先你要保重重置了实例密码,然后通过 VNC 远程连接,创建 6 位的密码后,进入页面登录实例:

Login: root
Password: 输入你创建的实例密码,不是 VNC 密码

然后按照这个文档 使用密码无法登录 Linux 云服务器 ECS 该如何处理?操作就行了。

重启退出后我们再点远程连接,就可以正常通过密码登录了,然后就可以操作我们的服务器咯。

本机免密登录服务器

如果我们想在自己电脑上就快捷登录到云服务器系统上,可以打开终端,因为我是 Windows 系统,所以使用的是 PowerShell ,然后输入以下命令:

ssh root@111.11.1.1

上面 root 是登录用户名,后面的 111.11.1.1 是服务器实例的公网 IP,记得替换成你自己的公网 IP。回车之后会让你输入登录密码,即服务器实例密码。

3.png

但是我们每次打开终端都要执行一遍登录,还要输入密码,特别麻烦,而且不利于后面要讲到的利用 Github Actions 自动化部署的工作进行。

幸运的是可以让本机与云服务器建立信任,实现免密登录,接下来介绍实现建立信任步骤:

本机生成 ssh key

在本机的终端输入以下命令:

ssh-keygen -t rsa -C "你的 github 邮箱"

云服务器添加本机公钥

执行上面命令以后,要找到 id_rsa.pub 文件,我的电脑上路径是 C:\Users\10913\.ssh ,你可以做个参考,然后随便找个编辑器将其打开后,复制该文件内的所有内容,复制到云服务器上的 ~/.ssh/authorized_keys 文件中(如没有该文件,就创建一个)。

上述过程用到步骤命令如下:

# 登录云服务器
ssh root@47.97.100.103

# 来到 ~/.ssh 目录
cd ~/.ssh

# 查看下有没有 authorized_keys 文件
ls

# 没有的话创建一个
touch authorized_keys

# 编辑该文件
vi authorized_keys

# 粘贴后退出,先按 ESC,然后
:wq

然后你输入 exit 命令退出云服务器系统,再重新执行上述登录,就不用输入密码了。

如果还是需要你输入密码,估计是权限不够,我们使用密码再次登录后,依次执行以下命令:

cd .ssh
chmod 700 ../
chmod 700 .
chmod 600 authorized_keys

安装必要软件

云服务器的初始系统是比较干净的,有些必要的软件需要我们自己安装。比如,开发的服务端没有 Node 怎么那可不行。

在开始安装之前,要确认我们的云服务器能访问外网,简单的方法就是 ping 一下随便一个域名,比如:

ping www.baidu.com

如果是像下面一样,就代表网是通的。

4.png

安装 git

因为我购买云服务器时选择的系统是 CentOS,自带 yum ,所以我可以使用它来安装 git :

sudo yum install git

安装完成之后,查看 git 版本:

git --version

安装 nginx

继续使用 yum 安装:

sudo yum install nginx

安装完成之后,查看 nginx 版本:

nginx -v

安装 wget

wget 可以在控台访问一个 url 地址,并得到返回结果,用于下载软件,也可用于测试 web server 是否正常运行。

sudo yum install wget

安装 nvm

nvm 是一个 node 包版本管理工具,非常好用,使用 wget 来安装:

wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash

下载的版本最好保持最新,可在 nvm 官方文档 随时查看。

注意,你大概率会遇到无法下载的问题,也就是连接不到资源,建议多尝试下,或者联系阿里云技术支持。

如果你安装成功了之后,需要重新连接服务器,查看 nvm 版本:

nvm -v

安装 node

有了 nvm,可以很方便地安装 node 的不同版本,因为我本地开发项目时 node 版本是 16.19.0 ,所以为了保持一致,我准备在云服务器上也安装一个相同的版本。 执行以下命令以查看 node 版本有哪些:

nvm ls-remote

然后选一个版本开始安装:

nvm install 16.19.0

安装成功之后确认下:

nvm list

查看当前使用的 node 版本:

node -v

安装 yarn

因为我使用的时 yarn 包管理器,需要安装下:

npm install --global yarn

查看当前使用的 yarn 版本:

yarn -v

安装 pm2

使用 npm 直接安装 pm2 :

npm install pm2 -g

查看当前使用的 pm2 版本:

pm2 -v

测试 web 和 node 服务

必须的环境准备好之后,我们需要确认公网是否能访问我们的服务器资源,所以需要先写个测试的 demo 。

新建静态页面

在根目录下,新建一个测试目录,进入该目录:

mkdir test-demo
cd test-demo

然后在该目录下新建一个 html ,并编辑:

touch test.html
vi test.html

编辑内容如下:

<h1>Hello World</h1>

找到 nginx 配置文件进行配置

执行以下命令查看 nginx.conf 的所在位置:

nginx -t

5.png

直接开始编辑:

vi /etc/nginx/nginx.conf

添加一个 server

server {
    listen 8001;
    server_name test-demo;
    root /root/test-demo;
    include /etc/nginx/default.d/*.conf;
}

:wq 保存退出之后,再执行下 nginx -t 看是否正常。如果正常,重启下 nginx :

nginx -s reload

然后使用 wget 测试下是否能正常访问该目录下的资源:

wget http://localhost:8001/test.html

结果报了 403 的错误,表示没有相关权限,我们去修改 nginx.conf 文件,把 user nginx 改成 user root 就可以了。

创建 node 服务

我们再试一下使用 pm2 启动一个 node 服务。首先来到 test-demo 下新建一个 server.js

cd /root/test-demo
touch server.js

编辑该 js 文件并输入以下内容:

const http = require("http");

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-type": "application/json" });
  res.end(
    JSON.stringify({
      errno: 0,
      msg: "Hello node server!",
    })
  );
});

server.listen(8002);

保存退出后启动 node 服务:

pm2 start server.js

使用 wget 测试服务是否启动成功:

wget http://localhost:8002

执行之后会默认下载一个 index.html 文件,我们查看下它的里面是什么内容:

cat index.html

如果输出了以下内容就代表我们的服务启动了:

{"errno":0,"msg":"Hello node server!"}

公网访问

云服务器是有一个公网 IP 的,这意味着我们可以在公网访问其服务器资源,但是需要配置防火墙。 我们可以先尝试访问下公网 IP 获取上面创建的 test.html ,在浏览器打开以下地址(记得替换你的公网 IP):

http://111.111.11.1:8001/test.html

或者上面创建的 node 服务:

http://111.111.11.1:8002

不出意外的话,是完全不可访问的。需要回到阿里云平台配置安全组开放我们的端口,如下图:

6.png

现在再访问应该就可以了。

7.png

Github Actions 自动部署

每次发布新的代码都要登录到服务器,手动部署最新的代码,这是重复且容易犯错的一个过程,如果我们能让机器自己执行这个过程,大大降低了风险和解放了劳动力。

Github Actions 是 Github 免费提供的一个持续集成服务,接下来介绍如何做。

新建 secrets

之前我们说过本机与远程的云服务器建立了信任得以免密登录,现在我们使用 Github Actions 提供的临时的虚拟机也就相当于我们的本机,也要建立信任,才能方便进行后续文件拷贝的工作。

还记得之前已经我们的公钥(即 id_rsa.pub)添加到云服务器,如果我们把本机的私钥(即 id_rsa)搬到虚拟机上,不就可以模拟本机免密登录了吗!

来到我们的 github 项目下,找到以下新建 secret 的地方,新建一个私钥的 secret

9.png

切记,私钥一定不能泄露,不然别人能随便就登进你的服务器了。

成功之后如下图,另外我还建了其它的 secret ,服务器的公网 IP 和项目目录地址。

8.png

编写 workflow

现在我先部署我的 type-room-web 前端项目。

在项目的根目录下新建 .github 目录,再新建 workflows 目录,最后在新建 deploy.yml 文件,编辑这个文件:

name: deploy type-room-web

on:
  push:
    branches:
      - "main" # 针对 main 分支
    paths:
      - ".github/workflows/*"
      - "src/**"
      - "public/*"
      - "package.json"
      - "vite.config.ts"
      - "index.html"

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: 拉取项目代码
        uses: actions/checkout@v3

      - name: 设置 node 环境
        uses: actions/setup-node@v3
        with:
          node-version: "16.19.0"

      - name: 安装依赖
        run: yarn

      - name: 编译打包
        run: yarn build

      - name: 设置 id_rsa
        run: |
          mkdir -p ~/.ssh/
          echo "${{secrets.VORTESNAIL_ID_RSA}}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan ${{secrets.REMOTE_HOST}} >> ~/.ssh/known_hosts
          cat ~/.ssh/known_hosts

      - name: 将远程服务器的对应目录下所有文件及文件夹删除
        run: | # type-room/web
          ssh root@${{secrets.REMOTE_HOST}} "
            cd /root/${{secrets.REMOTE_WEB_DIR}};
            rm -rf ./*;
          "

      - name: 将编译后的包复制到远程服务器对应目录
        run: scp -r ./dist root@${{secrets.REMOTE_HOST}}:/root/${{secrets.REMOTE_WEB_DIR}}

      - name: 删除 id_rsa
        run: rm -rf ~/.ssh/id_rsa

总结下上面文件做的事情:

  1. 监听到 main 分支的提交后,且 src/** 等文件内容改变时,开始执行这次 ci 流程;
  2. 拉取项目的代码到虚拟机中;
  3. 下载 node 16.19.0,因为后面的编译打包需要用到 node,尽量保持和本机开发时的版本一致;
  4. 安装依赖;
  5. 编译打包前端项目;
  6. secrets 中的建立的私钥写入到虚拟机的 .ssh/id_rsa 中,并赋予读的权限,再将云服务器的公网 IP 追加写入到 .ssh/known_hosts 中;
  7. 删除云服务器的对应目录下的所有文件及文件夹;
  8. 将编译好的 dist 目录复制到云服务器对应目录;
  9. 清除私钥。

⚠️ 这里请务必注意,你要事先在云服务器上建好你要操作的目录,比如我的 /root/type-room/web 。 万事俱备,现在让我们提交一波前端的项目看看:

git add -A
git commit -m "ci: 测试 GitHub Actions 持续集成"
git push origin main

修改 nginx.conf

因为现在用到的是我们实际的项目,所以需要修改之前的 nginx 配置文件:

# gzip 配置
gzip on;
gzip_static on;
gzip_min_length  5k;
gzip_buffers     4 16k;
gzip_http_version 1.0;
gzip_comp_level 7;
gzip_types       text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;

server {
    listen 8088;
    server_name type-room-web;
    root /root/type-room/web/dist;
    include /etc/nginx/default.d/*.conf;

    # 单页应用 try file
    location / {
        try_files $uri $uri/ /index.html;
    }

    # api 重定向到我们自己的服务端地址
    location /api/ {
        rewrite ^/api/(.*)$ /$1 break;
        proxy_pass http://localhost:7077;
    }
}

如果顺利的话,你会看到每一步都是 OK 的,如果遇到了问题,不要慌,根据错误提示去解决,放心,不难的。

10.png

购买数据库

我购买的是下面这个,新人专享还是蛮便宜的:

10.5.png

数据库连接

连接数据库

首先要来到我们购买的数据库控制台,点击实例进入后来到账号管理,创建一个主账号:

11.png

然后就可以用这个账号登录数据库了:

本机连接数据库

目前只能通过阿里云自研的 DMS 进行数据库管理,如果你想在自己常用的电脑上连接远程数据库,需要开放外网访问,且要给自己的本机 IP 加白名单。

12.png

等待一会儿后,点击外网地址旁边的设置白名单,添加一个白名单组,把你的本机出口 IP 地址添加上去:

13.png

⚠️ 也可以再新建一个安全组,专门放你的云服务器内网和外网地址,这样后续可以通过内网连接数据库。

这一步做完,就可以在本机连接我们的远程是数据库了,比如我使用 MySQL Workbench 来进行连接:

14.png

node 项目修改数据库连接地址

来到我们的 node 服务端项目,你必然是会有一个数据库连接地址的,将其修改为云数据库的内网地址:

// 开发配置
let MYSQL_CONF = {
  host: "localhost",
  port: "3306",
  user: "root",
  password: "xxx",
  database: "type_room_db",
};

// 线上配置
if (isProd) {
  MYSQL_CONF = {
    host: "你的云数据库内网地址",
    port: "3306",
    user: "root",
    password: "xxx",
    database: "type_room_db",
  };
}

部署 node 服务

在项目根目录下新建 ecosystem.config.js 文件,写入 pm2 所需的配置:

module.exports = {
  apps: [
    {
      name: "type-room-server",
      script: "./bin/www",
      instances: "2",
      watch: true,
      ignore_watch: ["logs", "node_modules"],
      error_file: "./logs/err.log",
      out_file: "./logs/out.log",
      log_date_format: "YYYY-MM-DD HH:mm:ss",
    },
  ],
};

增加 package.json 中的生产环境启动服务命令 prod

"scripts": {
  "dev": "cross-env NODE_ENV=dev ./node_modules/.bin/nodemon bin/www",
  "prod": "cross-env NODE_ENV=production pm2 start ecosystem.config.js",
},

和前端项目一样,也要创建我们的 .github/workflows/deploy.yml ,写入以下内容:

name: deploy type-room-server

on:
  push:
    branches:
      - "main" # 针对 main 分支
    paths:
      - ".github/workflows/*"
      - "src/**"
      - "bin/*"
      - "package.json"
      - "ecosystem.config.js"
      - ".env"

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: 拉取项目代码
        uses: actions/checkout@v3
        with:
          path: "clone-files"

      - name: 设置 id_rsa
        run: |
          mkdir -p ~/.ssh/
          echo "${{secrets.VORTESNAIL_ID_RSA}}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan ${{secrets.REMOTE_HOST}} >> ~/.ssh/known_hosts
          cat ~/.ssh/known_hosts

      - name: 将远程服务器的对应目录下所有文件及文件夹删除
        run: | # type-room/server
          ssh root@${{secrets.REMOTE_HOST}} "
            cd /root/${{secrets.REMOTE_SERVER_DIR}};
            pm2 kill;
            rm -rf ./*;
          "

      - name: 将项目复制到远程服务器对应目录
        run: |
          rsync -avz --exclude=".git" --exclude="node_modules" clone-files/ root@${{secrets.REMOTE_HOST}}:/root/${{secrets.REMOTE_SERVER_DIR}}
          ls -a

      - name: 启动 pm2
        run: |
          ssh root@${{secrets.REMOTE_HOST}} "
            cd /root/${{secrets.REMOTE_SERVER_DIR}};
            ls -a;
            yarn;
            yarn prod;
          "

      - name: 删除 id_rsa
        run: rm -rf ~/.ssh/id_rsa

和 web 项目不一样的是,复制时使用的是 scp ,现在我们用的是 rsync ,两者区别大家可以自行查查。

然后我们提交代码到 github 远程仓库,在 actions 里面看下我们的 ci 流程是否正常。

创建数据库

现在 node 服务是部署好了,我们可以登录云数据库,创建数据库,我的创建格式如下:

15.png

创建好数据库,我需要去创建和我开发环境保持一直的数据库表,因为我用的是 sequelize ,我在云服务器的 node 项目根目录下执行下写有同步逻辑的 js 文件就可以了。比如我的同步操作:

./node_modules/.bin/cross-env NODE_ENV=production node ./src/db/sync.js

同步成功之后,就可以开始读写数据库了,截止目前,你的 web 和 node 项目都已经完成了线上自动化部署、公网访问的所有流程了。

域名解析

现在访问我们的页面只能通过服务器的公网 IP 去访问,我们希望通过自己购买的域名去进行访问,该怎么做呢? 首先,找到域名解析,你会看到你购买的域名:

16.png

在列表的最右边有解析设置按钮,点击跳转之后,再点击新手引导,填入以下信息:

17.png

记住要把对应设置"@"主机记录对应设置"www"主机记录都勾选上,前者可以让你不输入 www 时也能正常访问。

设置之后我们打开终端,测试下域名的连通性,我们 ping 一下域名:

ping www.typeroom.cn
# 或
ping typeroom.cn

如果能正确显示出云服务器的 IP 地址表示成功了:

18.png

这个时候,已经可以使用域名代替之前的公网 IP 访问了,就像我的这样:

http://www.typeroom.cn:8088

默认 80 端口

细心的同学会发现,我现在访问页面还需要加端口 8088 才行,这是因为我云服务器的 nginx 配置中将前端资源的 server 端口设置成了 8088 ,将它改成 80 端口:

server {
    listen 80;
    server_name type-room-web;
    root /root/type-room/web/dist;
    include /etc/nginx/default.d/*.conf;

    ....
}

回到阿里云服务器实例的控制台,将安全组规则中原来的 8088 改成 80

19.png

改完之后再访问我们的页面,即不加端口的地址:

http://www.typeroom.cn

结果出现这个提示:

20.png

原因时我们的网站没有进行备案,那接下来就去备案呗~

网站备案

访问阿里云网站备案,按照流程填写资料,提交申请后只能等待了。

如果你的居住地和户籍地不一致,要事先去办理流动人口居住证,阿里云那边审核的时候肯定会给你打电话的,所以事先准备好吧。

后续如果一切顺利的话,你的域名就可以正常访问了。

支持 https 访问

https 想必 http 的优势是什么就不说了,老生常谈了,你的网站如果不是 https,在如今,对你的访问量几乎是打击性的。

但是付费的 SSL 证书是真的贵!接下来为大家演示下如何申请免费的证书,并让你的网站支持 https 访问。这一切得益于 Let's Encrypt 免费证书

可以参考这个视频一起做,注意变化:www.bilibili.com/video/BV1Vh…

安装 certbot

Certbot 是 Let's Encrypt 推出的获取证书的客户端,可以让我们免费快速地获取 Let's Encrypt 证书。

先安装必要的软件:

yum install epel-release -y
yum install certbot -y

生成证书

接着生成泛域名证书(别忘了写的是你自己的域名哦):

certbot certonly --preferred-challenges dns --manual -d typeroom.cn,*.typeroom.cn

因为证书 3 个月就过期,后面续时间只需要执行上面这条命令,加下解析值,成功之后其它 nginx 配置也不用变,因为覆盖掉原来的证书的,所以 systemctl restart nginx 重启下就可以了。

执行上面命令后会连续回答几个问题,该填邮箱就填,该同意的就同意,直到出现这个提示:

21.png

回到阿里云域名解析,添加一条 TXT 的解析:

22.png

添加完解析后稍等几秒钟,即可回车继续,这时候就会校验记录是否有效。

23.png

出现这个 Congratulations 就算是成功了!!生成的证书在 /etc/letsencrypt/live 目录下。

修改 nginx 配置

原来 http 访问的是 80 端口,我们需要修改为 443 端口,并增加证书的配置:

server {
    listen 443 ssl;
    server_name *.typeroom.cn;
    # 证书位置
    ssl_certificate /etc/letsencrypt/live/typeroom.cn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/typeroom.cn/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
    # 静态页面目录
    root /root/type-room/web/dist;
    include /etc/nginx/default.d/*.conf;

    error_page 404 /404.html;
        location = /404.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        rewrite ^/api/(.*)$ /$1 break;
        proxy_pass http://localhost:7077;
    }
}

# http 访问转至 https 访问
server {
    listen 80;
    server_name  *.typeroom.cn;
    root /usr/share/nginx/html;
    # 下面这行不加会导致无法重定向!
    include /etc/nginx/default.d/*.conf;

    rewrite ^(.*)$ https://${host}$1 permanent;
}

修改配置之后要重启下 nginx :

systemctl restart nginx

开放 443 端口

之前云服务器的安全组配置是开放了 80 端口,现在需要新增一个 443 端口。

24.png

不出意外的话,现在你可以使用 https 访问你的网站了!但是意外总会出现,比如访问 https 还是不通。但是我们不慌,一步一步解决。

可先通过以下命令查看 nginx 进程的 pid 和监听的端口:

ps aux | grep nginx
# 比如 master process 的 pid 为 9690
netstat -anp | grep 9690

发现已经监听 80443 了:

25.png

我们先看下 nginx 服务的状态:

systemctl status nginx

结果报红了:

26.png

重启 nginx 也是失败:

systemctl restart nginx

27.png

百般不得其解,直到看到这个问题:

nginx.service failed because the control process exited

最高赞的回答解决了这个问题,首先找到当前占用了 80443 端口的进程,发现就是 nginx 的:

netstat -tulpn

然后根据 pid (比如 9680)杀掉这个进程:

sudo kill -2 9680

这个时候再重启 nginx 服务就可以了:

systemctl restart nginx

自动续期

免费证书有效期 3 个月,到期之后我们可以再次续期,达到永久免费的效果。

www.frankfeekr.cn/2021/03/28/… juejin.cn/post/720583…

github host

阿里云的国内服务器,访问 github 时经常访问不到,可以尝试修改下本地的 hosts,但是 github 的访问 ip 经常变动,每次都要手动去更新 ip。

好在有 GitHub520 这个项目,让我们写个定时任务,实时拿最新的 ip 写入到系统的 hosts 配置文件中,比如 CentOS 下是 /etc/hosts

首先写入一个定时任务:

crontab -e

另起一行后,写入以下内容:

0 */1 * * * /usr/bin/sed -i "/# GitHub520 Host Start/Q" /etc/hosts && curl https://raw.hellogithub.com/hosts >> /etc/hosts && /usr/bin/sed -i "/<html>/, /<\/html>/d" /etc/hosts

每个 1 天就会去获取最新的数据进行写入。

:wq 保存之后重启下 crontab 服务:

systemctl restart crond.service

大功告成!