在 Linux 中搭建“呼呼小笼包的博客”运行环境

最近沉迷《宝可梦 剑/盾》,导致博客更新晚了 😂 甚至想写一篇关于《宝可梦》的文章

背景

这几年我一直在使用 Vultr 的服务器,在日本东京有两台服务器:

  • 一台是 2.5 美元一个月的 Cloud Compute,用作博客的 Web 服务器,拥有 1 虚拟CPU核心、512MB 内存、20GB SSD
  • 另一台是 6 美元一个月的 High Frequency,用作博客的 Db 服务器,拥有 1 虚拟CPU核心、1024MB 内存、32GB NVMe SSD

ping

但是今年(2019年)9月底,两台服务器都变得异常难以访问:网页加载奇慢无比,SSH 连接会经常断开,ping 基本处于丢包状态,但从海外访问则没有这种问题,应该是 Vultr 的 IP 段被和谐了,从此萌生了改用香港主机或干脆用国内服务器的想法。

然鹅,我的博客使用的 .dev 域名在国内无法进行备案,于是只能选在离我博客的受众群体尽可能的近的地方——香港安家。因为一直没用过微软 Azure,就在 Azure 开了 1 美元试用,具体细节可能日后发文章再说,反正和 Vultr 比起来挺贵的……Azure 只有用虚拟机相对便宜,要使用 App Service 还得买单独的 Azure Database for PostgreSQL,太贵了。我现在使用的是一台 B1ms 的机器,配置为 1 虚拟CPU核心、2048MB 内存、30GB HDD,这台机器一个月至少需要21刀,是 Vultr 的 2.5 倍多…不过嘛,信仰第一~

随着前两周博客正式上线,我也开始着手将博客环境从 Vultr 迁移到 Azure,这就免不了重新配置服务器环境,本文就是这次重新配置的记录。

如何在 Azure 控制面板中创建虚拟机就不多赘述了,不过需要先简单介绍一下:

配置 SSH 连接

对于熟悉 Linux 的童鞋们来说,SSH 一定不陌生。

简单说,SSH是一种网络协议,用于计算机之间的加密登录。 —— 阮一峰 《SSH原理与运用(一):远程登录》

要把 SSH 配置好了,我们才能(比较舒服的)远程操作我们的服务器。

生成 SSH 公钥和私钥

为了提高安全性,我们使用 SSH 公钥 的方式来进行身份验证,而不是传统的用户名密码方式。因为使用的是 Windows 系统,我习惯使用 PuTTY 附带的 puttygen.exe 来生成密钥。

puttygen

启动 puttygen.exe 后,我们在窗口最底部的 Parameters 中选择,生成一个 SSH-2 RSA 类型,2048 位的密钥。然后点击 Actions 中的 Generate 按钮,之后四处晃动鼠标(鼠标的位置相当于随机值)直到进度条满。

puttygen-generated

然后在新显示出来的 Key passphrase(密码) 以及 Confirm passphrase(确认密码) 文本框中输入密码,尽可能复杂,只有知道这个密码才能使用我们的密钥。最后点击 Actions 中的 Save public key(保存公钥) 以及 Save private key(保存私钥,后缀名为 .ppk) 将公钥和私钥妥善保存。

config-azure-ssh-public-key

最后将生成的公钥内容复制并粘贴到创建虚拟机第一步的“SSH 公钥”文本域中,并确保 SSH 使用的 22 端口允许入站。

需要注意的是,如果想让 Xshell 之类的客户端也能使用,则需要用 puttygen.exe 菜单中的 Conversions - Export OpenSSH key 来导出一个 OpenSSH key。如果你已经关闭了 PuTTY 程序,可以用 Load 按钮来载入之前保存的私钥文件。

通过 PuTTY 连接

PuTTY 是一款非常小巧的 SSH 客户端,可以用它通过 SSH 连接服务器,首先启动 PuTTY。

putty-set-host-name

将服务器的 IP 或域名填入 Host Name

putty-set-login-username

然后将左侧树状菜单切换到 Connection - Data,并在右侧的 Auto-login username 文本框中输入用户名。

putty-set-private-key

接着切换到 SSH - Auth,点击 Browse... 按钮选择 puttygen.exe 生成的私钥文件。

最后从左侧树状菜单回到最开始的 Session 下,在 Saved Sessions 文本框中输入本配置的名字,然后点击 Save 按钮,即可保存配置,方便下次使用,如果不想保存,直接点击 Open 按钮即可开始连接。连接时会要求输入我们设置的密码,输入密码后就能连接到服务器上了。

通过 Xshell 连接

因为我是个人使用,我觉得 Xshell 是一款十分方便的 SSH 客户端,不过需要注意的是,商业环境使用是收费的。这里也介绍一下 Xshell 的 SSH 连接设置。

首先启动 Xshell,按下组合键 Alt + N 打开“新建会话属性”窗口,分别设置名称(本配置的名称)和主机(服务器的 IP 或者域名)。

xshell-set-public-key

然后在左边树状菜单中选中“用户身份验证”,将方法下拉菜单修改为Public Key,在用户名中输入用户名。接下来点击用户密钥右侧的 浏览... 按钮,在弹出的“用户密钥”窗口中点击导入按钮,找到之前生成的 OpenSSH key(不是公钥,也不是后缀为 .ppk 的私钥,是通过 Export OpenSSH key 导出的那个文件),输入在 puttygen.exe 中设置的密码即可导入,选中导入的密钥然后点击确定按钮,保存会话的配置。

最后按下组合键 Alt + O 打开“会话”窗口,双击刚才新建的会话即可开始连接。和通过 PuTTY 连接一样,需要输入密钥的密码。

安装 .NET Core 运行环境

因为 BunBlog.API 含有一个 ASP.NET Core 开发的 Web API,所以我们需要安装 .NET Core 的运行时。访问 https://dot.net 找到对应的包,需要下载一个 .NET Core 3.0 的 Linux 的运行时,在这个地址,切换到 Debian 10,然后依此执行页面上给出的代码即可。

注意,执行官网的代码可能会遇到这样的错误提示 gpg: command not found ,是因为系统没有安装 GnuPG,安装好即可

sudo apt-get install gnupg

下面复制了微软官方的安装命令,仅供参考,请以官网最新的内容为准

# Register Microsoft key and feed
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg
sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
wget -q https://packages.microsoft.com/config/debian/10/prod.list
sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list

# Install the .NET Runtime
sudo apt-get update
sudo apt-get install apt-transport-https
sudo apt-get update
sudo apt-get install aspnetcore-runtime-3.0

安装完成后,我们通过 dotnet --info 命令来查看是否安装成功:

  It was not possible to find any installed .NET Core SDKs
  Did you mean to run .NET Core SDK commands? Install a .NET Core SDK from:
      https://aka.ms/dotnet-download

Host (useful for support):
  Version: 3.0.0
  Commit:  95a0a61858

.NET Core SDKs installed:
  No SDKs were found.

.NET Core runtimes installed:
  Microsoft.AspNetCore.App 3.0.0 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 3.0.0 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

如果 .NET Core runtimes installed 列表下和我们安装的 .NET Core runtime 一致,那么恭喜,.NET Core 3.0 和 ASP.NET Core 的环境就安装好了~

安装 Node.js

通过官网 https://nodejs.org 找到通过包管理器安装 Node.js 的方法:Installing Node.js via package manager,选 Debian ,又被带到一个 GitHub 页面,再点击一下“Debian and Ubuntu based distributions” 下方的 “Installation instructions” 就能跳转到 Debian 的安装命令。

注意,后面的步骤需要用到 curl,如果没有记得提前安装好

sudo apt-get install curl

这里我安装最新版本,下面复制了Node.js官方的安装命令,仅供参考,请以官方最新的内容为准

# Node.js v13.x
# Using Debian, as root
curl -sL https://deb.nodesource.com/setup_13.x | bash -
apt-get install -y nodejs

安装完成后,执行下面的命令检查 Node.js 和 Npm 是否正常安装

node --version
npm --version

如果两个命令都返回了版本号,基本就可以认为安装成功了

安装 PostgreSQL

还是一样的套路,在官网 https://www.postgresql.org/ 找到 Linux 的安装方式 PostgreSQL: Linux downloads (Debian)。页面上有一个下拉菜单“Choose your Debian version”(选择你的 Debian 版本),我这里选择 “Buster (10.x)” ,然后按照官网的步骤安装即可。

需要注意的是,第二步:

Create the file /etc/apt/sources.list.d/pgdg.list, and add a line for the repository: deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main

是要求创建 /etc/apt/sources.list.d/pgdg.list 文件,并增加一行内容,而不是执行 deb http://...

汇总一下,修改了文件后需要执行这些命令,仅供参考,请以官方最新的内容为准

# Add repository 
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update

# install core database server
apt-get install postgresql-11

安装好 PostgreSQL 之后,使用 psql 命令启动客户端,另外它会自动创建一个名为 postgres 的用户,我们通过 su 命令切换到 postgres 用户,然后并执行 psql 命令

sudo su postgres -c psql 

如果正常进入到 psql 控制台,则说明安装成功,输入 \q 然后按下 Enter 退出 psql 控制台

创建数据库用户

安装后自动创建的 postgres 用户相当于超级管理员,为了更好的安全性,我们应该使用一个权限较低的用户提供给 BunBlog.API 程序使用。首先我们创建一个名为 bunblog 的系统用户:

sudo adduser bunblog

然后使用 postgres 用户在 psql 中创建数据库用户、数据库:

sudo su postgres -c psql 

(下面的命令是在 psql 控制台中执行的)

postgres=# CREATE USER bunblog WITH PASSWORD 'YOUR_PASSWORD';postgres=# CREATE DATABASE bunblogdb;postgres=# GRANT ALL PRIVILEGES ON DATABASE bunblogdb to bunblog;```

上面的
- 第一行创建了一个名为 `bunblog` 密码为 `YOUR_PASSWORD` 的数据库用户
- 第二行创建了名为 `bunblogdb` 的数据库
- 第三行将数据库 `bunblogdb` 的所有权限赋予 `bunblog` 用户。

执行完成后输入 `\q` 退出 psql 控制台,然后用我们刚刚创建的用户来登录。这里需要使用 `-d`  指令设置我们要访问的数据库名称:

```bash
sudo su bunblog -c 'psql -d bunblogdb'

执行后如果成功进入 psql 控制台,则配置完成~

安装 Nginx

nginx [engine x] is an HTTP and reverse proxy server, a mail proxy server, and a generic TCP/UDP proxy server, originally written by Igor Sysoev. —— nginx
nginx (读音:engine x)是 HTTP 和反向代理服务器、邮件代理服务器和通用 TCP/UDP 代理服务器,最初由 Igor Sysoev 编写。—— 译自 nginx

由于 BunBlog.API 和 BunBlog.UI 都只暴露两个内网端口,不直接接入公网,需要一个 Web 服务器接入到公网,并且能够反向代理这两个内网的程序,nginx 是常见的选择。

Nginx 直接通过包管理器即可安装

sudo apt update
sudo apt install nginx

安装完成后可以通过公网 IP 或者事先绑定好的域名访问服务器,能看到 nginx 的欢迎页面说明安装成功了。

部署 BunBlog.API

经过一系列的配置,终于开始实际部署程序了!

获取最新版本的程序

从 GitHub 上获取最新的 release 包的地址,并下载到服务器上

wget https://github.com/huhubun/BunBlog.API/releases/download/v0.1.0/BunBlog.API.zip

下载好后使用 unzip 命令解压缩,如果没有这个命令,可以通过 sudo apt-get install unzip 安装。我一般将站点放在 /var/www 下面,所以添加参数 -d 指定解压路径

sudo apt-get update
sudo apt-get install unzip -y

sudo unzip BunBlog.API.zip -d /var/www

这样在 /var/www 目录下,就有了 BunBlog.API 文件夹,这里面的就是博客的 Web API 程序。

创建表结构

另外还有一个数据库脚本,用于创建博客所需的表结构,如法炮制,我们将它从 GitHub 的 release 中下载下来

sudo wget https://github.com/huhubun/BunBlog.API/releases/download/v0.1.0/script.sql -P /var/www/BunBlog.API/

然后在 psql 控制台中执行这个脚本,创建相关表结构

sudo su bunblog -c 'psql -d bunblogdb'

(下面的命令均在 psql 控制台中执行,注意 bunblogdb=# 不需要键入到控制台中)

bunblogdb=# \i /var/www/BunBlog.API/script.sql

\i 表示要执行指定路径的脚本,执行完毕后我们可以通过 \d 查看结果,成功的话则会新增很多张表。

然后为 BunBlog.API 的配置文件 appsettings.Production.json 添加数据库连接字符串即可

"ConnectionStrings": {
    "BunBlogConnection": "Host=localhost;Database=bunblogdb;Username=bunblog;Password=YOUR_PASSWORD"
}

监视 BunBlog.API

参见官方文档:监视应用

其它细节

Owner

因为我们的命令全是 sudo 执行的,解压后的程序所有者都是 root,这可能会导致日志文件不能正常生成的问题,按照上一步按官方文档,我们使用 www-data 用户来运行 BunBlog.API,所以我们将所有程序都设为 www-data 所有

sudo chown -R www-data:www-data /var/www/BunBlog.API/

目录格式的差异

在 Windows 系统中我们用 \ 反斜线来分割目录,例如 E:\temp。而在 Linux 中,使用的是 / 斜线分割:/var/www 这就导致,虽然 .NET Core 程序可以跨平台运行,但我们的一些配置并不是 Windows 和 Linux 都通用的。例如 nlog.config 文件在 Linux 下需要将 internalLogFile 设置成为 /tmp/internal-nlog.txt

部署 BunBlog.UI

参见上一篇博文:使用 Nuxt.js 搭建博客前端

配置 Nginx

我们可以直接修改 Nginx 的配置文件 /etc/nginx/nginx.conf,其中的每一个 server 就相当于一个站点。

BunBlog.API

参见微软官方文档:配置 Nginx

BunBlog.UI

BunBlog.API 的配置一样,只需要将 proxy_pass 指向 pm2 启动后,对应的内网地址即可。

HTTPS 证书

因为我是 .dev 域名,强制开启了 HTTPS 访问,因此需要准备 HTTPS 证书并配置到 Nginx 中。这里我使用的是 Let’s Encrypt 的证书,并通过 certbot 来自动生成证书。

首先安装 certbot

sudo apt-get update
sudo apt-get install certbot

由于我的域名解析在 Cloudflare 上,certbot 的插件 certbot-dns-cloudflare 可以在验证域名时自动化的完成一些操作,下面的步骤可能略有不同,但这个插件并不是必须的。可以通过 https://certbot.eff.org/docs/using.html#dns-plugins 查看你的域名解析供应商是否有插件,如果没有可以参考官方文档一步一步配置 https://certbot.eff.org/lets-encrypt/debianbuster-other ,或者将你的域名也迁移到 Cloudflare 进行解析。

安装 certbot-dns-cloudflare 插件

sudo apt-get install python3-certbot-dns-cloudflare

配置 certbot-dns-cloudflare 插件,参阅 https://certbot-dns-cloudflare.readthedocs.io/en/stable/

生成证书

sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials ~/certbot/cloudflare.ini -d 域名

证书生成后存放在 /etc/letsencrypt/live/bun.dev/域名/ 目录下,将该地址配置到 Nginx 中作为证书地址即可,下面以 BunBlog.API 为例,演示了一个最终配置好的 Nginx server 节点:

server {
        listen 443 ssl http2;server_name api.bun.dev;ssl on;ssl_certificate /etc/letsencrypt/live/bun.dev/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/bun.dev/privkey.pem;location / {
                proxy_pass              http://localhost:5200;proxy_http_version      1.1;proxy_set_header        Upgrade $http_upgrade;proxy_set_header        Connection keep-alive;proxy_set_header        Host $host;proxy_cache_bypass      $http_upgrade;proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header        X-Forwarded-Proto $scheme;}
}

修改配置后重启 Nginx

sudo service nginx restart

即可看到效果

https

为了增强安全性,我们可以让 Nginx 生成这些 header

add_header X-Frame-Options DENY;add_header X-Content-Type-Options nosniff;add_header X-XSS-Protection "1;mode=block";```

这几行的作用分别是
1. 不允许站点在 frame 中展示(在网上搜索“点击劫持”能了解到是什么回事)
2. 禁用客户端 MIME 类型嗅探,可以在 MDN [X-Content-Type-Options](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-Content-Type-Options) 找到详细的解释
3. 启用 <abbr title="Cross-site scripting">[跨站脚本攻击(XSS)](https://developer.mozilla.org/zh-CN/docs/Glossary/Cross-site_scripting)</abbr> 保护,并在检查到XSS攻击时,停止渲染页面

至此我们就全部配置完成,最后来 [SSL Labs](https://www.ssllabs.com/) 跑个分吧~  

![ssl-labs](https://cdn.bun.plus/blog/2019/11/26/0cd7a595-45d8-4872-9099-707d1fe287c1.png)
发表于

2019-11-26 16:52

Tags
An unhandled error has occurred. Reload 🗙