小型网站部署笔记
满足小型网站初期的运行环境,实现前后端分离+负载均衡+SSL卸载

最近在部署自己的站点,有很多工作之前都做过,但总是记不清细节是什么了,折腾半天。为了避免以后可能发生的重复劳动,我准备把最佳实践总结下。因为这些实践面对的都不是大型互联网项目,而是自己的Side Project,所以不需要比如Kubernetes或者Spring Cloud一样的技术,容器也用不上,主打一个传统+够用。

本文描述的运行环境基于阿里云,其他云平台其实都差不多。服务器运行在ECS一样的环境中,多台服务器共同存在于一个VPC也就是子网中。后端使用Spring Boot,当然其他生态的后端也行,反正最后都是提供一个端口对外。前端使用Vue/React生态,如果使用SSR,那么也需要服务器。数据库选择的是PostgreSQL,没用云服务商的RDS服务,不是因为和云平台的耦合度较高,而是因为价格比较贵。

网站结构

我们简单描述一个网站的构成,它有以下组成部分:

  • 网关服务器,用来转发流量,有公网IP
  • 开发环境服务器(前端+后端+数据库),平常联调
  • 生产环境服务器(前端+后端+数据库),正式服务

乍一看,怎么都需要7台服务器了,还没上线呢,怎么就这么多开销?其实不用。

理论上,你有多少台就有多少台的部署方式,比如这几种:

  • 一台(所有的放一起)
  • 两台(网关+其他)
  • 三台(网关+开发+生产)
  • 四台(网关+后端+前端+数据库)

反正这些东西都是放在同一个VPC里的,怎么组合都行。如果你的前端是放在OSS里的,前端也可以省了。

刚开始各个服务器可以是低配的,比如1核1G这种。以后如果流量增大了,可以扩充成单独的7台。对于无状态的前后端服务器,当生产环境流量继续增大,可以纵向增强单台机器的配置,也可以横向搞多台服务器负载均衡。对于有状态的数据库来说,可以不断扩展内存和磁盘规格,最后实在不行还可以切换到RDS上,在此基础上还有上升的空间。

然后呢,还是不行怎么办呢?当然是打开懂车帝选车呀!想象下你的10台32核64G的服务器都跑满的时候,那你就发了呀,光服务器费用都要3w一个月了,那你一个月不得挣个10w块?那不得换个福特烈马大沼泽版本

可惜对于我这种半路出家的Java后端,接触的系统都是厂里内部的管理系统,1QPS你受得了么。关于大流量场景下的业务处理,我还真是没有实操过,跟面试官吹牛逼的时候也是纸上谈兵。但换个角度来说,作为个体户,你已经不需要去钻研那些东西了,很多赚钱的小公司用的都是一些非常传统和草台的技术。我不是说让你降低技术的追求,而是节省时间,更多关注于实现业务。

扯远了,我们接下来从网关开始。

网关入口

网站对外有统一的入口,就是搞一台nginx做反向代理和负载均衡,同时做SSL卸载。把流量转到内部,内部的服务器不暴露到外网,提高安全性。对外只暴露80和443端口。

nginx的部署

我们使用的系统是Ubuntu 24.04,它是LTS版本。

首先是安装nginx,安装完成之后,它会自动跑起来并在80端口提供一个页面:

sudo apt install nginx

它的站点配置主要存在于两个文件夹:

  • /etc/nginx/sites-available
  • /etc/nginx/sites-enabled

默认情况下,两个目录下都有个名字为default的文件,后者是前者的符号链接。从名字就可以看出,如果某个站点你需要下线,将站点配置从/etc/nginx/sites-enabled删除就行。

nginx作为一个守护进程在后台运行,service命令和systemctl命令都可以控制nginx的启停,前者是对后者的包装,在Ubuntu引入systemd之前,servcie包装的是/etc/init.d里的脚本。因为我是十来年的Ubuntu用户,所以还是喜欢老的用法:

  • service nginx start 启动nginx
  • service nginx restart 重启nginx
  • service nginx reload 重新加载配置
  • service nginx status 查看nginx状态

如果你增减了站点或者修改了配置,可以通过重启或者重新加载配置来查看效果。

SSL证书的管理

在进行具体的nginx配置之前,我们先把SSL证书先申请一下,假设我们有以下域名:

  • example.com
  • dev.example.com
  • api.example.com
  • api-dev.example.com

分别是前端和后端的域名,包括线上环境和开发环境。请确保在你的域名服务商哪里,把这几个域名的DNS解析,都设置到你的网关IP上。然后通过certbot命令来申请letsencrypt证书:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx certonly -d [域名] --agree-tos -m [邮箱]

它会利用现有的nginx,来接受CA对于域名的访问,以确定你的域名所有权。最后你会收到证书,存放在:

  • /etc/letsencrypt/live/[域名]/fullchain.pem 证书
  • /etc/letsencrypt/live/[域名]/privkey.pem 私钥

这个证书的有效期是3个月,本来我还打算搞个定时任务,快到期的时候更新证书,但是没想到看到一个输出:

Certbot has set up a scheduled task to automatically renew this certificate in the background.

呀,原来certbot会自己更新,这不挺好,一次申请,终身更新。我们只要重复几次就能得到所有域名的证书。如果你对通配符证书有需要,可以查看如何用DNS插件来获取证书

证书更新了之后,还得告诉nginx,让它加载新的证书。不然它还是会用内存里的旧证书。certbot有内置的hook机制在更新证书之后,执行自定义脚本。脚本需要放在/etc/letsencrypt/renewal-hooks/deploy目录。

比如我就新增了一个nginx-reload文件,通过chmod a+x nginx-reload增加执行权限,内容为:

#!/bin/bash
service nginx reload

这样每次成功更新证书之后,就重新加载nginx,不用重启影响业务。

nginx的配置

首先,让http的流量转发到https。我们在/etc/nginx/sites-available下新增一个文件redirect,配置为:

server {
    listen 80;
    return 301 https://$host$request_uri;
}

此配置就是将80端口的请求,重定向到https,接下来通过命令让站点生效:

sudo ln -s /etc/nginx/sites-available/redirect /etc/nginx/sites-enabled/
sudo service nginx reload

接下来搞定前后端服务器的4个站点的配置,内容都是类似的。举个后端服务器的例子:

upstream api {
    server [API服务器IP]:8080; # 如果多个服务器,就多写几条,nginx会轮训转发
}

server {
    listen 443 ssl http2;
    server_name api.example.com;
    
    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    location / {
        proxy_pass http://api;
    }
}

你可以将其中的[API服务器IP]替换为你的服务器内网IP,我已经将这个配置搞的极简了,它是能运行的最小化配置,你拷贝过去改改就能跑。你可能在其他地方看到各种配置参数,比如用proxy_set_header把部分Header转发到实际的服务器上。我后续会再写一篇文章,讲讲用nginx做反向代理的时候,有哪些配置需要特别注意,以及负载均衡时的各种机制。

剩下的站点依次配置生效就可以了,反向代理工作已完成,也就是nginx的部分已经结束了,是不是很简单?

NAT网关

如果你的ECS没有公网IP,你是不能访问公网的,这一点很扯,为了解决这个问题,你可能需要一个NAT网关。可以直接将NAT网关配置在nginx服务器上,统一管理流量。为了更详尽地解释搭建的过程,我准备将这块内容专门独立整理成一篇文章,也是稍后放出。

数据库部署

数据按的部署没有难度,这里只是记录下PostgreSQL 17的部署流程,从头开始折腾的话还是挺费时间的。以下两种方式任选:

Docker部署

我经常在开发环境快速搭建PostgreSQL,一个命令就可以跑了:

docker run -d \
    -e POSTGRES_USER=db-user \
    -e POSTGRES_DB=db-name \
    -e POSTGRES_PASSWORD=db-pass \
    -p 5432:5432 \
    --name pg-db \
    --restart=always \
    postgres:17

以上配置包含了很多关键参数,数据库名字、用户名、密码等等,你可以改成你自己的。如果你想跑多个实例,更换下端口重新执行就可以。--restart=always参数保证此容器在重新之后继续执行。当你不需要的时候直接删除容器就行,方便快捷。

传统部署

记录下在Ubuntu 24.04安装PostgreSQL 17的过程。

Ubuntu 24.04自带的源里的PostgreSQL版本是16,如果要安装最新的版本,你需要使用PostgreSQL的官方APT源

sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh

感觉官方提供如此便捷的工具。接下来就可以安装了:

sudo apt install postgresql-17

然后用PostgreSQL的超级用户postgres来链接数据库:

sudo -u postgres psql

创建用户和数据库:

CREATE USER "db-user" WITH password 'db-pass'
CREATE DATABASE "db-name" OWNER "db-user"

因为我们要通过IP和端口,用账号密码链接数据库,所以要在/etc/postgresql/17/main/pg_hba.conf中配置:

# IPv4 remote connections:
host    all             all             0.0.0.0/0            scram-sha-256

host表示TCP/IP连接,两个all表示所有数据库和所有用户,后面的IP段表示允许所有IP连接,因为数据库不暴露在外网,那么这就是允许所有的内网机器连接。最后一个scram-sha-256表示用密码链接,名字比较奇怪哈,因为它用了SCRAM-SHA-256验证防止密码被嗅探,安全性较高。不过这还没完,pg_hba.conf只是管理认证方式的,还得从物理上允许远程连接。

/etc/postgresql/17/main/postgresql.conf中修改一行:

listen_addresses = '*' # 原来是localhost

好了,PostgreSQL算是配置完了,重启就好:

sudo service postgresql restart

如果你还不能在内网成功访问到数据库,记得检查下ECS所在的安全组,有没有放开5432端口。

前后端的部署

我在考虑前后端如何部署的时候,总是想搞个流水线,github代码一合并,自动走流水线部署。有许多CI/CD方案是可以选择的,比如阿里云的云效、自建JenkinsGitLab CI/CDGithub Actions等等。但是因为项目都还没跑起来,我选择用万能的scp上传构建产物。

比如用Spring Boot写的后端,运行用下面这种方式运行:

nohup java -jar app.jar &

前端也会有类似的命令,比如NextJS可以这样跑:

nohup npm run start &

你可别笑我哈,我是感觉能运行就行,就算搞流水线最后执行的也都是这些东西。

上传全靠scp,部署全靠nohup,看日志全靠tail -f nohup.out,监控全靠阿里云ECS控制台。

如果部署的次数足够多,我嫌烦了,可能会用云效,到时候我再把搭建流水线的过程总结出来。(后续:的确烦了,用云效搭建了Spring Boot后端的流水线)

小结

有了本文,下次我再搭建环境我就不用再去回忆了。你也可以依照这里的命令,快速完成目标。


最后修改于 2024-11-01