通往 Kubernetes 之路——使用虚拟机进行手动部署

87 阅读30分钟

本章内容包括:

  • 创建并连接到云服务器
  • 了解 NGINX 及其作为 Web 服务器的基础知识
  • 安装自托管的 Git 仓库
  • 配置并安装生产环境的 Web 应用
  • 使用 Supervisor 实现后台运行应用
  • 通过 Git 钩子实现部署流程自动化
  • 利用防火墙和 SSH 密钥增强服务器安全

在本阶段,我们将手动将代码部署到服务器。具体操作是通过 Git 将本地代码上传到服务器,上传完成后触发自定义脚本,确保服务器上的应用版本保持最新。安全地在计算机间共享代码正是 Git 重要性所在,也是值得掌握的技能。

这个过程类似于驾驶手动挡汽车。虽然操作稍显繁琐,但能帮助你打下坚实基础,为后续的自动化部署做好准备。对于习惯自动挡的美国用户来说,可能觉得手动挡很陌生,但掌握手动挡会更懂得其价值。

学习手动部署不仅能教会我们完整流程,而且实现的效果令人满意,难度仅略高于第2章的应用代码编写。平台即服务(PaaS)提供商如 Heroku 和 AWS Elastic Beanstalk,正是基于应用代码和 Git,自动化完成本章中手动执行的许多步骤。PaaS 使用便捷,无需关心底层基础设施,但通常在价格和灵活性上有一定折中。

成功完成本章学习,你应具备以下基础:

  • 对 HTTP 请求/响应周期有基本了解
  • 理解 URL 功能
  • 具备一定命令行操作经验
  • 完成了第2章的两个 Web 应用

我们的第一步是创建远程服务器以部署代码。这里我们使用名为 Akamai Connected Cloud(ACC)的云服务商,来配置一台虚拟机(VM)托管代码。操作系统选择 Ubuntu 22.04 LTS。

3.1 创建并连接远程服务器

云计算极大地降低了部署应用的门槛,无论规模大小都变得更加可行。如果没有云计算,你将需要自己购买所有计算机(如物理服务器)及相关网络设备,还得与互联网服务提供商(ISP)协商获取静态 IP 地址等诸多复杂且难以解决的问题。静态 IP 地址是网站和应用能够在互联网上运行的基础。云服务商则帮我们处理物理计算基础设施——服务器、网络、布线、电力、冷却、安全、IP 地址等,确保软件能够稳定运行。这里我们选择使用 ACC(前身为 Linode)作为示例。

ACC 是一家全球领先的云服务提供商,提供包括虚拟机(VM)、内容分发网络、对象存储、数据库和托管 Kubernetes 等多种云服务。我们将在本书中主要使用 ACC,但由于使用的是开源技术,所讲的概念和技术同样适用于几乎所有云服务商。

本书的核心主题之一是可移植性和灵活性,本章同样如此。目标是掌握技能和方法,使我们能够将应用部署到几乎任何服务器、托管服务或云平台。

3.1.1 虚拟机的配置(Provisioning)

配置虚拟机是指从云服务商租用服务器的一部分。物理服务器通过虚拟化技术,将大型服务器划分为多个独立单元,用户只需租用其中一部分,而非整台机器。打个比方,这就像在购物中心里租一个店铺,而不是整栋大楼。租赁的一个显著优势是可以按需选择计算资源,比如内存、CPU 数量等,从而提高效率并节约成本。这正是云计算的基础方法,也是本书推荐的应用部署方式。

我们这次租用的虚拟机来自 ACC 的 Linode 服务。操作步骤如下:

  1. 访问 linode.com/rk8s 创建 ACC 账户(本书读者可获得促销额度)。

  2. 登录 ACC 控制台(linode.com)。

  3. 点击顶部菜单的“Create”,选择“Linode”。

  4. 按以下配置填写:

    • Distribution(操作系统):Ubuntu 22.04 LTS(或最新 LTS 版本)
    • Region(地域):达拉斯,德克萨斯(或离你最近的区域)
    • Plan(方案):共享 CPU > Linode 2GB(或更大)
    • Label(标签):rk8s-vm
    • Tags(标签,可选)
    • Root Password(管理员密码):设置一个强密码
    • SSH Keys(SSH 密钥,推荐使用,详见附录 D)
    • 其他设置(可选)
  5. 点击“Create Linode”创建虚拟机。

几分钟后,你将在 Linode 控制台看到新分配的 IP 地址(见图 3.1)。

image.png

如果这是你第一次配置虚拟机或远程服务器,不妨先体会一下刚才完成的事情有多重要。只花不到 10 美元(甚至可能免费),你就租用了位于达拉斯(德州)或你选择的其他数据中心的一部分计算资源。你现在对这台计算机拥有完全控制权,可以运行任何类型的应用。同时,你拥有一个全球可访问的 IP 地址,可以通过你的应用影响世界另一端的人。更棒的是,整个服务器启动过程只用了大约 5 分钟。

这个流程正是大科技公司每天用来运行其应用的虚拟机配置流程。区别通常在于规模和算力需求:我们这本书里可能只需一台虚拟机,而他们可能需要成千上万台。好消息是,流程是相同的,并且可以通过“基础设施即代码”(Infrastructure as Code,简称 IaC)技术进一步优化。IaC 超出了本书范围,但如果你想找比手动在 Web 控制台上配置服务器(俗称 ClickOps)更高效的方式,值得深入学习。

现在我们有了远程服务器,下一步就是学会如何控制它。我们将使用一种叫做安全外壳协议(SSH)的技术,这是一种安全登录和连接虚拟机的方式。连接成功后,就能开始配置服务器,运行各种命令。通过 SSH,我们能访问服务器的命令行,安装服务器软件、管理应用依赖、配置裸 Git 仓库,最终完成应用部署。(IaC 同样可以极大简化这一流程。)

3.1.2 通过 SSH 连接

如今,连接远程服务器已是基础功能,大多数台式机和笔记本都内置了 SSH 客户端。移动设备原生支持差异较大,但几乎都有支持 SSH 的应用程序。SSH 既能连接局域网内的计算机,也能通过互联网连接全球各地的服务器。执行 SSH 连接,你至少需要以下五项:

  1. 远程主机
  2. IP 地址或域名(见图 3.2)
  3. 用户名
  4. 密码或已安装的 SSH 密钥
  5. 与远程主机处于同一网络(例如互联网)

前面配置虚拟机时,已满足其中四项。这里默认你已连接互联网,可以访问 Akamai Linode 上的远程主机。

image.png

让我们来看一下这些要素如何对应到我们实际使用的情况:

  • 远程主机 —— Akamai Linode 虚拟机
  • IP 地址 —— 172.104.195.109(见图 3.2)
  • 密码 —— Root 密码(详见第 3.1.1 节)
  • 用户名 —— root

在许多 Linux 发行版和 Ubuntu 操作系统中,默认用户名都是 root。有些云服务商会更改默认用户名,Akamai Linode 未来也可能做出调整。当前,我们可以在 Akamai Linode 控制台中查看相关信息,如图 3.3 所示。

image.png

既然我们已经准备好了所有必要信息,现在开始连接远程主机。根据你的配置,可能会提示输入 root 用户密码。请打开命令行(Terminal 或 PowerShell),输入如下命令:

清单 3.1 SSH 连接示例

ssh root@172.104.195.109

由于这是你首次连接该远程主机及该 IP 地址,系统会执行一些标准流程。首先会出现类似图 3.4 的提示,询问你是否信任并允许本地计算机连接该服务器。这种身份验证检查通常只会发生一次,除非远程主机或本地机器上的已知主机记录发生变化。

图 3.4 主机真实性确认提示

如果看到此提示,输入 yes 并回车继续,进入下一步。确认后,除非你用新的计算机连接,或从 ~/.ssh/known_hosts 文件中删除了该主机 IP(例如 172.104.195.109),此提示不会再次出现。

如果没有看到此提示,可能原因包括:

  • SSH 命令中使用了错误的用户名或 IP 地址。
  • SSH 密钥无效或安装不正确(详见附录 D 学习如何使用 SSH 密钥)。
  • 你或服务器端防火墙阻止了连接(若非新服务器,可能发生此情况)。
  • 你之前使用过该 IP 地址的 SSH 连接,需先从 ~/.ssh/known_hosts 中删除相关条目。

通过身份验证后,系统会提示你输入 root 用户密码。每次连接该远程主机时,若未安装 SSH 密钥,都需要输入此密码。附录 C 讲解了如何在远程主机安装 SSH 密钥,实现免密码登录。

连接成功后,你的本地命令行即变为远程主机的命令行,界面类似图 3.5。

image.png

如果你看到这样的输出,说明你已经成功连接到了远程主机。这是本章第二次值得庆祝的时刻,因为你的计算机已经通过互联网直接连接到了另一台计算机,而无需使用网页浏览器!恭喜你!

接下来,你的第一步通常是更新系统。这里不是升级操作系统,而是更新系统中可用软件包的列表。

在运行更新命令前,先了解一下 sudoapt 是什么。

  • sudo 是一个允许你以超级用户身份执行命令的工具,赋予你安装软件或修改系统配置的权限。虽然应谨慎使用,但在修改系统软件或配置时通常必不可少。
  • apt 是 Debian 系统(包括 Ubuntu)上的包管理器,你可以把它理解为 Linux 系统中的 npm 或 pip,用来安装各种第三方软件包。apt 也能用来安装各种编程语言的运行环境,比如 Python 和 Node.js。

下面是更新系统软件包列表的命令示例:

清单 3.2 使用 apt 更新系统软件包列表

sudo apt update  #1

注释 #1:这是我们在后续章节使用容器时经常会用到的命令。

现在我们已经具备了在远程主机上搭建第一个静态网站的基础条件。这个过程非常简单,我们将使用一个非常强大的工具——NGINX(读作“engine-x”)。

3.2 使用 NGINX 部署静态网站

回溯到大约 20 年前,许多网站都是简单的静态网页,类似于互联网上的电子宣传册。虽然静态网站没有动态网站那么炫酷,但它们仍有重要作用,且能发挥极大效果。

本节中,我们将通过修改一些 HTML 代码并配置名为 NGINX 的 Web 服务器,部署第一个网站。NGINX 功能丰富,后续章节会详细讲解,但现在我们只用它将简单的静态网站发布到远程服务器的公网 IP 地址。具体流程如下:

  • 更新系统可用软件包列表
  • 安装 NGINX
  • 创建简单的 HTML 文件
  • 配置 NGINX 以提供该 HTML 文件服务

在基于 Linux 的系统上,每次通过 APT 包管理器安装软件前,都应先更新可用软件包列表。这是因为包列表会频繁更新,我们需要确保安装的是最新版本的软件。更新列表后,运行以下命令安装 NGINX。

清单 3.3 在 Ubuntu 上安装 NGINX

sudo apt update
sudo apt install nginx

出现安装提示时,输入 y 并回车确认安装。该命令会安装 NGINX 及其所有依赖项。你也可以添加 -y 参数自动同意所有提示,使命令变为:

sudo apt install nginx -y

安装完成后,在浏览器中打开你的服务器 IP 地址(例如 http://172.104.195.109),你应能看到类似图 3.6 的页面。

image.png

恭喜你!你已经成功在远程主机上安装了 NGINX,现在已经可以向全球用户提供静态网站了。虽然我们还需要配置网页内容以显示自己的页面,但此刻的成就感非常令人激动。

3.2.1 接受临时虚拟机(Ephemeral VM)的理念

刚开始做这类部署时,我对使用的虚拟机和 IP 地址非常依赖,害怕做出重大更改“破坏”之前的努力。

这种恐惧源于两个原因:

  1. 使用了老旧的文件管理方法(FTP),
  2. 没有把虚拟机当作短暂易变的资源来看待。

FTP(文件传输协议)虽然仍是互联网传输文件的有效方式,但由于 Git 的存在,我不推荐用 FTP 来管理代码。Git 更强大,因为它能帮你追踪代码变化;而 FTP 则像“西部荒野”,文件随时出现却缺乏变更记录和说明。个人建议尽量避免 FTP。如果必须使用类似 FTP 的工具,可以尝试 VSCode 的 Remote-SSH 插件,体验会更好。

我不敢轻易改动虚拟机,另一大原因是我没有把虚拟机视为临时资源。由于使用 FTP,我养成了把虚拟机当成“外接硬盘”的坏习惯。其实,虚拟机极易销毁和重建,依赖它持久保存数据是不明智的。将虚拟机视为临时资源,能帮助你准备好正确的代码和项目结构,以便在需要销毁虚拟机时,不会丢失任何重要数据。

3.2.2 删除并重新开始 NGINX

了解上述理念后,我们来看几种清理 NGINX 并重新开始的方法:

  • 销毁虚拟机——最简单,也是我推荐的练习方式。

  • 通过 APT 包管理器卸载 NGINX——更常用的方式,避免频繁销毁重建虚拟机,尤其当没有自动化或基础设施即代码(IaC)工具时:

    sudo apt remove nginx --purge
    sudo apt autoremove
    

    或者:

    sudo apt purge nginx
    sudo apt autoremove
    

    或者:

    sudo apt purge --auto-remove nginx
    

    以上命令会移除 NGINX 及不再需要的依赖包。

3.2.3 修改 NGINX 默认 HTML 页面

NGINX 的默认网页是我每次看到都会感到欣慰的东西,它提醒我新项目已经开始,需要进行配置。对你来说,可能看起来像是90年代的网站,但没关系,我们先来简单修改这页面,顺便了解一些 NGINX 基础。

安装 NGINX 后,系统中新建了 /var/www/html 目录,这是 NGINX 提供静态 HTML 文件的默认根目录。打开该目录,你会看到名为 index.nginx-debian.xhtml 的文件,它就是默认首页。

通过 SSH 和命令行,先删除这个默认页面:

清单 3.4 删除默认 NGINX 页面

sudo rm /var/www/html/index.nginx-debian.xhtml

接着,我们使用 cat 命令配合 EOF(文件结束标志)创建一个多行的新文件。以下为语法示例:

清单 3.5 使用 cat + EOF 创建多行文件示例

cat <<EOF > path/to/your/file
This is a new line
This is another new line
    This is a tabbed line
This is the last line
EOF

基于此语法,创建一个新的首页 HTML 文件:

清单 3.6 创建新的 NGINX 首页文件 index.xhtml

cat <<EOF > /var/www/html/index.xhtml
<!DOCTYPE html>
<html>
    <head>
        <title>Hello World</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        <p>We're coming soon!</p>
    </body>
</html>
EOF

你可以用以下命令查看新文件内容,确认是否正确:

cat /var/www/html/index.xhtml

此时,/var/www/html/ 目录下应该只剩下 index.xhtml 一个文件。打开服务器 IP 地址,在浏览器中查看效果,应该类似图 3.7 所示。

image.png

正如你在图 3.7 中可能注意到的,IP 地址与之前示例不同。请花点时间思考,为什么会这样?是笔误吗?书中的错误吗?其实不是,我刚才做了和你刚才练习时一样的操作:销毁虚拟机并重新开始。贯穿本书,你会发现 IP 地址经常变更,因为我们不断地回收重建虚拟机。

需要注意的是,我们并没有修改任何 NGINX 的配置,仅仅是利用其内置功能,提供了新的 HTML 页面。如果你熟悉前端技术,可以把刚才添加的 HTML 进一步美化。暂且先这样,接下来我们将搭建远程 Git 仓库来存储应用代码。

3.3 自托管远程 Git 仓库

使用 cat 和 EOF 这种手动方法适合快速演示和简单示例,但管理真正的软件项目则远远不够。现在是时候利用第2章中配置的 Git,将代码直接推送(push)和拉取(pull)到远程服务器上的文件夹了。

在开始使用远程服务器上的 Git 前,请牢记以下几点:

  • 数据丢失风险
    如果将虚拟机视为临时资源,存储在其上的代码和数据可能因虚拟机故障或销毁而永久丢失。
  • 优势有限
    相较于 GitHub、GitLab 等第三方服务,自托管 Git 仓库的好处非常有限。这些服务经过多年打磨,管理 Git 项目更高效。
  • 增加复杂度
    虽然自托管看似简单,适合基础示例,但项目规模扩大后会变得复杂难管。
  • 安全风险
    自托管 Git 仓库的协作涉及众多安全措施,管理难度和风险较高。
  • 缺乏自动化支持
    Git 本身不支持自动化工作流,而 GitHub Actions、GitLab CI/CD 等第三方工具可以实现代码构建、测试、部署等自动化,一键完成。

尽管如此,我们仍需理解自托管 Git 仓库的工作机制,作为将本地代码推向生产环境的一种途径。后续章节我们将转向完全基于 GitHub 的托管与自动化部署。

在第2章,我们配置了两个 Web 应用的本地私有 Git 仓库和远程第三方 Git 仓库(GitHub,公有或私有)。本节将为每个项目再配置一个远程私有裸仓库(bare Git repository)在虚拟机上。

裸仓库是没有工作目录的仓库,即只存储变更历史,没有实际代码。也就是说,我们不会直接在服务器上编辑代码,而只会向裸仓库推送和拉取代码。代码到达裸仓库后,我们将使用基于 Git 的钩子(hook)将代码解包到工作目录,作为部署应用的基础。

创建远程私有裸仓库的端到端流程如下:

  1. 通过 SSH 登录远程服务器,安装 Git。

  2. 为 Python 和 Node.js 应用创建裸仓库。

  3. 更新本地 Git 仓库,将远程地址指向新裸仓库,并根据需要配置 SSH 密钥。

  4. 将代码推送到远程裸仓库。

  5. 创建 Git 钩子,用来:

    • 将代码检出到工作目录
    • 安装或更新应用依赖
    • 重启相关 Web 服务器进程

这样,我们就实现了从本地代码到远程服务器自动部署的完整流程。

3.3.1 使用 SSH 登录服务器

第一步,我们需要进入虚拟机的命令行。使用 SSH 登录远程服务器,命令如下:

ssh root@<your-ip>

替换 <your-ip> 为你的虚拟机实际 IP。

登录成功后,我们可以安装 Git。安装 Git 的命令如下(与清单 3.8 相同):

apt-get update
apt-get install git -y

3.3.2 创建裸仓库(bare Git repositories)

创建裸仓库只需创建目录,并运行:

git init --bare

通常建议给裸仓库目录名加 .git 后缀,便于区分普通目录,表明它是裸仓库。

创建完裸仓库后,需要让 HEAD 指向默认分支 main,类似 GitHub 默认做法。HEAD 是指向最近一次提交的指针,保证远程仓库能跟踪最新代码。

下面演示为 Python 和 Node.js 应用创建裸仓库:

mkdir -p /var/repos/roadtok8s/py.git
cd /var/repos/roadtok8s/py.git
git init --bare
git symbolic-ref HEAD refs/heads/main

mkdir -p /var/repos/roadtok8s/js.git
cd /var/repos/roadtok8s/js.git
git init --bare
git symbolic-ref HEAD refs/heads/main

这和第2章在 GitHub 上创建仓库类似,只不过这里用命令行操作代替了网页界面。

这两个远程仓库的 URL 格式如下:

ssh://root@$VM_IP_ADDRESS/var/repos/roadtok8s/py.git
ssh://root@$VM_IP_ADDRESS/var/repos/roadtok8s/js.git

$VM_IP_ADDRESS 替换为你当前虚拟机的 IP 地址。获取 IP 的方法有多种,例如在虚拟机执行:

curl ifconfig.me

或者在 Akamai Linode 控制台查看。推荐使用 curl,因为大部分 Linux 系统默认集成。

你也可以用 bash 的字符串替换功能将 IP 保存到变量中:

export VM_IP_ADDRESS=$(curl ifconfig.me)
echo "this is your IP: $VM_IP_ADDRESS"

使用该变量时,可以执行如下命令打印添加远程仓库的示例:

echo "git remote add vm ssh://root@$VM_IP_ADDRESS/var/repos/roadtok8s/py.git"
echo "git remote add vm ssh://root@$VM_IP_ADDRESS/var/repos/roadtok8s/js.git"

请注意,你的输出会根据实际 IP 地址有所不同,务必使用你自己机器的真实输出。

这时候,你应该联想到第2章的内容:我们现在可以把本地代码指向这些新的远程仓库,并开始推送代码了。

3.3.3 推送本地代码到远程仓库

配置好远程 Git 仓库后,接下来更新本地仓库,指向新的远程仓库,然后推送代码。

步骤如下:

  1. 进入本地项目根目录:
cd ~/Dev/roadtok8s/py/

cd ~/Dev/roadtok8s/js/

2. 添加远程仓库地址:

git remote add vm ssh://root@<your-ip>/var/repos/roadtok8s/py.git

<your-ip> 替换为你的虚拟机 IP。

  1. 推送代码到远程仓库:
git push vm main

以 Python 应用为例,完整流程如下:

cd ~/Dev/roadtok8s/py/
git remote add vm ssh://root@45.79.47.168/var/repos/roadtok8s/py.git
git push vm main

这里使用远程名 vm 而不是 origin,原因有二:

  • origin 通常已被 GitHub 远程仓库占用。
  • vm 标识这是一个特殊远程仓库,方便本书后续章节区分,并逐步过渡到只用 GitHub。

对 Node.js 项目同样操作。如果推送成功,终端输出会类似图 3.8。

image.png

如果推送失败,你可能会看到以下错误提示及对应原因:

  • fatal: 'vm' does not appear to be a Git repository
    —— 你可能没有执行 git remote add vm ssh://... 命令。
  • fatal: Could not read from remote repository
    —— 你没有权限访问远程仓库。
  • error: src refspec main does not match any
    —— 本地分支名不是 main,解决方法是执行 git branch -m master main(假设你之前用的是 master 分支)。

如果本地推送成功,代码已经上传到虚拟机,但进入裸仓库目录会发现没有代码文件,这是因为裸仓库只保存元数据,没有实际代码。

3.3.4 使用 Git 钩子(hook)检出代码

裸仓库只包含项目的元数据,实际代码保存在另一个克隆的目录中。我们需要用 post-receive 钩子实现推送后自动将代码从裸仓库检出到工作目录。

首先,在虚拟机上为每个项目创建新的工作目录:

mkdir -p /opt/projects/roadtok8s/py
mkdir -p /opt/projects/roadtok8s/js

/opt/ 是 Linux 系统存放代码的常用路径,/opt/projects/roadtok8s/ 是本书存放代码的根目录,分别对应 Python 和 Node.js 的代码存放目录。

接下来,配置 Git 钩子,实现:

  • 从裸仓库检出代码到工作目录
  • 安装项目依赖
  • 启动或重启项目相关进程

以 Python 项目为例,进入裸仓库的 hooks 目录,创建 post-receive 文件并赋予执行权限:

cd /var/repos/roadtok8s/py.git/hooks
touch post-receive
chmod +x post-receive

文件名 post-receive 是 Git 钩子专用名称,必须保持一致才能生效。

编辑 post-receive,内容如下:

export WORK_TREE=/opt/projects/roadtok8s/py
export GIT_DIR=/var/repos/roadtok8s/py.git

cat <<EOF > "$GIT_DIR/hooks/post-receive"
#!/bin/bash
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout HEAD -f
EOF

chmod +x "$GIT_DIR/hooks/post-receive"

Node.js 项目同理,替换 pyjs 即可。

因为 post-receive 是普通 bash 脚本,你可以随时手动调用以验证是否正常工作:

export PY_GIT_DIR=/var/repos/roadtok8s/py.git
export JS_GIT_DIR=/var/repos/roadtok8s/js.git

bash $PY_GIT_DIR/hooks/post-receive
bash $JS_GIT_DIR/hooks/post-receive

如果出现错误 error: pathspec 'HEAD' did not match any file(s) known to git,说明你还没从本地推送代码到虚拟机。

验证推送代码

执行以下命令将本地代码推送到服务器:

cd ~/dev/roadtok8s/py
git push vm main

cd ~/dev/roadtok8s/js
git push vm main

推送完成后,使用 SSH 登录服务器,查看代码是否已经检出到工作目录:

ssh root@<your-ip> ls -la /opt/projects/roadtok8s/py
ssh root@<your-ip> ls -la /opt/projects/roadtok8s/js

至此,代码已经上传到服务器,并且我们有了持续安装和更新代码的方法。下一步就是安装项目的各种依赖,确保它们能够正常运行。

3.4 安装应用依赖

本章前面我们看到安装 NGINX 非常简单,但要构建功能更强大的完整网站,需要安装更多软件依赖。依赖越多,复杂度越高,问题也越多。为避免这些问题,我们需要为每个项目设置专门的环境。

由于我们有两个不同运行环境的应用,依赖安装方式也不同:

  • Python 项目:使用虚拟环境(virtual environment)和 Python 包管理工具 pip,配合特定版本的 Python 3。
  • Node.js 项目:使用 Node 版本管理器(nvm)安装指定版本的 Node.js 及其对应的包管理器 npm。

运行时版本要求

项目运行时版本验证命令
Python3.8 或更高python3 --version
Node.js16.14 或更高node --version

Ubuntu 20.04 默认安装 Python 3.8,但不含 Node.js。无论当前版本如何,我们都将学习如何安装所需的 Python 和 Node.js 版本,以便长期使用。

3.4.1 安装 Python 3 及 FastAPI 项目依赖

Python 标准库功能丰富,适合 Web 开发,但标准库不足以支持完整功能的 Web 应用。且部分 Linux 发行版默认不带完整 Python 标准库(不同于 macOS 和 Windows)。因此需要安装额外库,比如 venv,用于创建虚拟环境。

Ubuntu 默认预装 Python 3,版本越新越可能预装较新版本。运行:

python3 --version

或虚拟环境中:

python --version

确认 Python 版本是否满足需求(建议 3.10 及以上)。

无论当前版本,建议使用 apt 安装最新版本的 Python 及相关工具包:

sudo apt update            # 1
sudo apt install python3 python3-pip python3-venv build-essential -y    # 2
  • python3:Python 3 运行时
  • python3-pip:Python 包管理工具 pip
  • python3-venv:虚拟环境模块 venv
  • build-essential:gcc 编译工具,用于编译 Python 包

注释:

  1. 安装前务必先更新包列表
  2. -y 参数自动确认安装提示

安装时若看到“守护进程使用过时库”的警告,按回车继续即可。守护进程是后台运行的系统服务,时常更新。

安装完毕后,建议重启系统,确保所有新包生效:

sudo system reboot

注意:重启后 SSH 会话中断,需重新连接。

系统重启后,验证 Python 版本:

python3 --version

确认版本为 3.10 以上。

你可能会疑惑,为什么不指定安装具体的 Python 版本?这超出本书范围,且 Python 安装方式繁多。后续使用容器技术时,我们会直接解决版本一致性问题。当前只需保证版本在 3.8 以上即可。

接下来创建 Python 虚拟环境,隔离项目依赖。我们将在 /opt/venv 目录创建虚拟环境,保持项目目录整洁(可选,但推荐)。这是 Linux 系统和 Docker 容器中常用的虚拟环境路径。

python3 -m venv /opt/venv/

今后可使用虚拟环境中 Python 及工具的绝对路径:

  • /opt/venv/bin/python —— Python 解释器
  • /opt/venv/bin/pip —— pip 包管理器
  • /opt/venv/bin/gunicorn —— 生产环境 Web 服务器

使用绝对路径可以避免系统全局 Python 安装或包版本冲突,也减少配置代码,提高项目启动速度和稳定性。

现在,利用虚拟环境中的 Python 和 pip 安装项目依赖。依赖清单在 /opt/venv/roadtok8s/py/src/requirements.txt 文件中(之前通过 Git 推送并检出)。

安装命令如下:

/opt/venv/bin/python -m pip install -r /opt/venv/roadtok8s/py/src/requirements.txt

应用依赖安装完成前,我们先更新之前创建的 Git post-receive 钩子,确保每次推送代码时自动运行上述安装命令,保证生产环境总是最新依赖。

更新 Python 应用的 Git 钩子(post-receive)

我们利用 Git 钩子自动安装应用依赖,使用的是之前示例中的绝对路径,确保环境准确。具体操作如下:

export WORK_TREE=/opt/projects/roadtok8s/py
export GIT_DIR=/var/repos/roadtok8s/py.git

cat <<EOF > "$GIT_DIR/hooks/post-receive"
#!/bin/bash
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout HEAD -f

# 安装 Python 依赖
/opt/venv/bin/python -m pip install -r $WORK_TREE/src/requirements.txt
EOF

chmod +x "$GIT_DIR/hooks/post-receive"

注意:覆盖原有 post-receive 文件内容。
运行 bash /var/repos/roadtok8s/py.git/hooks/post-receive 可测试钩子是否生效,会输出 pip 安装信息。

运行 Python 应用

我们终于可以在服务器上运行 Python 应用了!接下来,启动应用并确认能通过浏览器访问。

运行环境所需 Python 包:

  • gunicorn
    Python 的 WSGI(Web Server Gateway Interface)HTTP 服务器,能将 HTTP 请求转为 Python 代码。成熟、稳定且速度快,兼容多种框架(FastAPI、Flask、Django 等)。
  • uvicorn
    Python 的 ASGI(Asynchronous Server Gateway Interface)HTTP 服务器,支持异步。开发时用它;生产环境用 gunicorn 搭配 uvicorn worker。

gunicorn 启动的基础命令参数说明

参数说明
--worker-class uvicorn.workers.UvicornWorker使用 uvicorn worker,支持 FastAPI
--chdir /opt/projects/roadtok8s/py/src运行前切换工作目录到源代码目录
main:app指定运行 main.py 文件中的 app 实例
--bind "0.0.0.0:8888"监听所有 IP 地址的 8888 端口
--pid /var/run/roadtok8s-py.pid将进程 ID 写入指定文件,方便管理

启动命令示例

/opt/venv/bin/gunicorn \
    --worker-class uvicorn.workers.UvicornWorker \
    --chdir /opt/projects/roadtok8s/py/src/ \
    main:app \
    --bind "0.0.0.0:8888" \
    --pid /var/run/roadtok8s-py.pid

启动成功后,应用将在 http://0.0.0.0:8888 监听(可用服务器公网 IP 替换 0.0.0.0 访问)。

注意:如果端口被占用,启动会失败。
成功启动的终端输出参考图 3.9。

这样,你的 Python 应用就可以在远程服务器上稳定运行,并且代码和依赖更新均可通过 Git 推送自动完成。

image.png

你可能会想直接在本地浏览器打开 http://0.0.0.0:8888,但这是不行的,因为这个地址是服务器内部的绑定地址,只在云服务器内部有效。

不过,正是因为绑定了 0.0.0.0,服务器会监听所有网络接口,所以你可以用浏览器访问:

http://<你的服务器公网IP>:8888

这样就能看到和本地运行时一样的应用效果。图 3.10 展示了远程服务器上应用运行的示例页面。

image.png

恭喜你!你已经成功在云服务器上运行了 Python 应用。虽然还有很多可以改进的地方,但现在你已经有了一个很好的基础。继续之前,我们需要先停止这个应用服务器,以便进行其他操作,比如安装 Node.js 以及部署我们的 Express.js 应用。

停止 Python 应用的三种方式:

  1. 重启虚拟机:
sudo system reboot

2. 使用键盘快捷键 Ctrl + C(和本地开发时一样)。 3. 利用进程 ID 停止进程:PID 文件位于 /var/run/roadtok8s-py.pid,执行:

kill -9 $(cat /var/run/roadtok8s-py.pid)

注意:不要删除 PID 文件,否则不能停止进程。

现在开始安装 Node.js 和 Express.js 应用

为了简化操作,我们将在同一台服务器上运行 Python 和 Node.js 应用。虽然部署简单,但横向扩展(scale)更难,这也是为何 Kubernetes 变得重要的原因之一。

3.4.2 安装 Node.js 和 Express.js 应用环境

和之前安装 NGINX、Python 3 一样,我们要为 Node.js 应用配置环境。每个工具和应用的依赖都不同,配置起来比较繁琐,Node.js 也不例外。

安装 Node.js 的最佳实践:使用 NVM(Node Version Manager)

我们需要安装最新的 Node.js LTS 版本以保证应用兼容性。虽然可以用 apt-get install nodejs 安装 Node.js,但这通常版本过旧(例如本书写作时 apt 安装的是 12.22.9 版本),不适合生产环境。

安装 NVM 步骤

NVM 官网和安装脚本:nvm.sh

安装流程:

  1. 声明一个变量 NVM_VERSION,指定想安装的 NVM 版本,这里用 v0.39.3
  2. 使用 curl -o- <URL> 下载并打印脚本内容。
  3. 通过管道 | 传给 bash 运行安装脚本。

安装命令示例:

export NVM_VERSION="v0.39.3"  # 可替换为你想安装的版本号
curl -o- \
    https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh \
    | bash

安装完成后,你的终端输出应类似图 3.11 所示。

这样,你就成功安装了 NVM,接下来可以用它来安装和管理不同版本的 Node.js 和 npm。

image.png

安装完 nvm 后,想立即使用它,你需要执行以下其中一种操作:

  • 关闭并重新打开你的 SSH 连接。

  • 或者使用命令重新加载 bash 配置文件:

    source ~/.bashrc
    

这两种方式都有效,我个人更倾向于用 source ~/.bashrc,因为更快。bash 配置文件本质上是一个存放用户命令和快捷设置的文件。

验证 nvm 安装并使用 nvm 安装 Node.js 最新 LTS 版本

执行以下命令:

nvm --version           # 验证 nvm 是否安装成功
nvm install --lts       # 安装最新 LTS 版本的 Node.js(本书写作时为 v18.15.0)
nvm install 18.15.0     # 安装指定版本的 Node.js(可选)
  • 如果 nvm --version 失败,尝试退出 SSH 再重新连接。
  • nvm install --lts 是安装最新长期支持版本的简便方法。
  • nvm install 18.15.0 是安装指定版本的示例。

安装完成后,你会看到类似图 3.12 的输出,表示安装成功。

image.png

假设通过 nvm 安装 Node.js 成功后,你的系统应该具备以下命令:

  • node —— Node.js 运行时,使用 node --version 查看版本。
  • npm —— Node 包管理器,使用 npm --version 查看版本。
  • npx —— 用于运行 Node.js 包的工具,使用 npx --version 查看版本。

这让你拥有了一个可重复、可靠的方式在任何 Linux 机器上安装相同版本的 Node.js 和相关工具。虽然 nvm 也支持其他操作系统,但在 Linux/Ubuntu 上安装最为便捷。

安装 Express.js 应用依赖

如第 2 章所述,我们用 npm 来安装 Express.js 应用的依赖。操作步骤如下:

cd /opt/projects/roadtok8s/js          # 进入应用目录
npm install -g npm@latest              # 升级 npm 到最新版本
npm install                           # 安装 package.json 中声明的依赖
npm list                             # 验证依赖是否正确安装

执行后,node_modules 目录会被创建在应用目录下。与 Python 项目不同,Node.js 不需要调整 node_modules 目录位置。

更新 Node.js 应用的 Git 钩子(post-receive)

同样地,我们更新远程仓库的 post-receive 钩子,使得每次代码推送后自动安装依赖:

export WORK_TREE=/opt/projects/roadtok8s/js
export GIT_DIR=/var/repos/roadtok8s/js.git

cat <<EOF > "$GIT_DIR/hooks/post-receive"
#!/bin/bash
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout HEAD -f

# 安装 Node.js 依赖
cd $WORK_TREE
npm install

EOF

chmod +x "$GIT_DIR/hooks/post-receive"

覆盖原有 post-receive 文件内容。你可以用下面命令测试钩子:

bash /var/repos/roadtok8s/js.git/hooks/post-receive

执行后会显示 npm install 的输出,表示依赖安装成功。

现在,Express.js 应用所有依赖都已安装完毕,可以准备运行代码啦!

3.4.3 运行 Express.js 应用

在生产环境中,我更倾向于使用模块的绝对路径来启动应用。针对我们的情况,我们将运行位于 JavaScript 应用 src 目录下的 main.js 文件。同时,我们会指定一个 PORT 环境变量,保证应用能灵活适应端口变更,这在后续关闭端口时非常有用。

运行命令示例

PORT=5000 node /opt/projects/roadtok8s/js/src/main.js

执行后,你应该能够在浏览器访问:

http://<你的服务器IP>:5000

此时会看到类似图 3.13 中展示的页面,表示你的 Express.js 应用已成功运行。

image.png

恭喜你!你已经成功安装了 Node.js 并使其能够对外提供服务。

停止应用的三种方式:

  1. 重启虚拟机(VM):

    sudo system reboot
    
  2. 在终端使用 Ctrl + C,就像在本地开发时那样终止运行。

  3. 使用保存在 /opt/projects/roadtok8s/js/src/main.pid 文件中的进程 ID 来停止应用(这个路径和 Python 应用稍有不同,但原理相同):

    kill -9 $(cat /opt/projects/roadtok8s/js/src/main.pid)
    

现在,我们已经可以运行两个不同的运行时环境来支持两个不同的应用了。但问题是,如何确保这些应用可以在没有人工干预的情况下自动运行呢?这时我们会用到一个叫 Supervisor 的工具。

3.5 使用 Supervisor 后台运行多个应用

Supervisor 可以帮助我们将运行命令转为后台进程,确保应用会在系统启动时自动启动,系统关闭时自动停止,或者当我们需要时自动重启。虽然 Supervisor 不是唯一的方案,但它简单易用,适合初学者。

配置 Supervisor 管理应用的步骤:

  1. /etc/supervisor/conf.d/ 目录下为每个应用创建一个 Supervisor 配置文件。
  2. 在配置文件中写入正确的应用信息,如工作目录、启动命令、日志位置等。
  3. 更新 Git 的 post-receive 钩子,以便在代码推送后触发 Supervisor 重启或更新应用。

3.5.1 安装 Supervisor

使用 apt 包管理器安装 Supervisor,如下所示:

sudo apt update
sudo apt install supervisor -y

安装完成后,会在 /etc/supervisor/conf.d/ 目录下创建一个文件夹,专门用于存放每个受 Supervisor 管理的应用配置文件。

常用 Supervisor 命令:

  • 查看当前所有受管理的应用状态:

    sudo supervisorctl status
    
  • 重新加载配置文件并应用更新:

    sudo supervisorctl reread
    sudo supervisorctl update
    

    (也可以合并为 sudo supervisorctl reread && sudo supervisorctl update

  • 启动指定应用:

    sudo supervisorctl start <app-name>
    
  • 停止指定应用:

    sudo supervisorctl stop <app-name>
    
  • 重启指定应用:

    sudo supervisorctl restart <app-name>
    

接下来,我们将用这些命令和步骤来配置你的 Python 和 Node.js 应用,使它们能在后台稳定运行。

3.5.2 为应用配置 Supervisor

要为任意应用配置 Supervisor,我们需要在配置文件中完成以下几件事:

  • 命名 Supervisor 进程(例如 [program:yourname])。
  • 定义应用的工作目录(directory=/path/to/your/app)。
  • 定义启动应用的命令(command=/path/to/your/command)。
  • 设置应用是否自动启动、自动重启,以及最大重启尝试次数(autostart=trueautorestart=truestartretries=3)。
  • 定义应用的日志存储位置,分别对应标准错误和标准输出(stderr_logfile=/path/to/your/logstdout_logfile=/path/to/your/log)。

配置 Python 应用的 Supervisor 文件示例

export APP_CMD="/opt/venv/bin/gunicorn \
    --worker-class uvicorn.workers.UvicornWorker \
    main:app --bind "0.0.0.0:8888" \
    --pid /var/run/roadtok8s-py.pid"

cat << EOF > /etc/supervisor/conf.d/roadtok8s-py.conf
[program:roadtok8s-py]
directory=/opt/projects/roadtok8s/py/src
command=$APP_CMD
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/supervisor/roadtok8s/py/stderr.log
stdout_logfile=/var/log/supervisor/roadtok8s/py/stdout.log
EOF

配置 Node.js 应用的 Supervisor 文件示例

export NODE_PATH=$(which node)  # 获取系统中 node 的路径

cat << EOF > /etc/supervisor/conf.d/roadtok8s-js.conf
[program:roadtok8s-js]
directory=/opt/projects/roadtok8s/js/src
command=$NODE_PATH main.js
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/supervisor/roadtok8s/js/stderr.log
stdout_logfile=/var/log/supervisor/roadtok8s/js/stdout.log
EOF

创建日志文件目录

sudo mkdir -p /var/log/supervisor/roadtok8s/py
sudo mkdir -p /var/log/supervisor/roadtok8s/js

更新 Supervisor 配置并启动应用

sudo supervisorctl update

执行该命令后,Supervisor 会加载新的配置文件并启动相应的应用程序。你可以通过 sudo supervisorctl status 查看应用运行状态。

如图 3.14 所示,你将看到命令执行的输出结果。

image.png

由于我们在配置文件中设置了 autostart=true,应用程序应该会自动启动。接下来,让我们通过以下命令验证它们是否正在运行。

sudo supervisorctl status

如果配置正确,你应该会看到类似图 3.15 的输出,显示我们的 Python 和 Node.js 应用程序均处于运行状态。

image.png

我们应该也可以通过以下地址确认应用是否运行正常:

  • Python 应用(通过 Gunicorn 运行):http://<你的 IP>:8888
  • Express 应用(通过 Node.js 运行):http://<你的 IP>:3000

在 Supervisor 中,启动、停止、重启或查看任何受其管理的应用程序状态,都可以通过如下命令实现:

sudo supervisorctl <操作命令> <应用名>

其中 <操作命令> 例如 startstoprestartstatus,而 <应用名> 是我们配置的 roadtok8s-pyroadtok8s-js。例如:

sudo supervisorctl start roadtok8s-py
sudo supervisorctl stop roadtok8s-py
sudo supervisorctl restart roadtok8s-js
sudo supervisorctl status

更新 post-receive Hook,实现代码推送后自动重启应用

为了保证每次代码推送并完成依赖安装后,应用自动重启,我们需要更新 Git 服务器上的 post-receive hook,示例如下:

Python 应用的 post-receive hook:

export WORK_TREE=/opt/projects/roadtok8s/py
export GIT_DIR=/var/repos/roadtok8s/py.git

cat <<EOF > "$GIT_DIR/hooks/post-receive"
#!/bin/bash
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout HEAD -f

# 安装 Python 依赖
/opt/venv/bin/python -m pip install -r $WORK_TREE/src/requirements.txt

# 重启 Supervisor 管理的 Python 应用
sudo supervisorctl restart roadtok8s-py
EOF

Node.js 应用的 post-receive hook:

export WORK_TREE=/opt/projects/roadtok8s/js
export GIT_DIR=/var/repos/roadtok8s/js.git

cat <<EOF > "$GIT_DIR/hooks/post-receive"
#!/bin/bash
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout HEAD -f

# 安装 Node.js 依赖
cd $WORK_TREE
npm install

# 重启 Supervisor 管理的 Node.js 应用
sudo supervisorctl restart roadtok8s-js
EOF

3.6 使用 NGINX 和防火墙服务多个应用

NGINX 不仅仅是用来服务静态网站,它还能将流量转发给其他应用程序。我们的应用程序运行在同一台服务器上,但理论上,只要 NGINX 能访问,不论是公网还是私网 IP,都可以转发流量。

这种流量转发的方式称为反向代理(Reverse Proxy)。设置反向代理的目的是:

  • 将访问请求转发到对应的后端应用(隐藏真实应用服务端口)
  • 实现负载均衡(将请求分发给不同服务器)
  • 增加安全层,隐藏应用真实位置
  • 其他更多高级用例

负载均衡的核心是将流量分配给有能力处理它的服务器,如果一台服务器超载,流量会自动转发到其他服务器。NGINX 支持负载均衡,但本书不会过多涉及该内容。

接下来我们将配置 NGINX 作为反向代理,进一步实现多个应用通过同一 IP 的不同路径访问,并且会配置防火墙,只开放必要的端口,保障服务器安全。

3.6.1 配置 NGINX 作为反向代理

我们希望配置 NGINX,将流量转发给运行在本机 Supervisor 管理下的两个应用:

这里的 localhost 是本地主机名,也可以是 IP 地址或其他域名。我们的两个应用都运行在同一台服务器上,分别监听默认端口。NGINX 配置文件中加入以下内容即可实现流量转发,示例如下:

cat <<EOF > /etc/nginx/sites-available/roadtok8s
server {
    listen 80;
    server_name localhost;

    location / {          #1
        proxy_pass http://localhost:8888;
    }

    location /js/ {       #2
        proxy_pass http://localhost:3000/;
    }
}
EOF
  • #1 位置 / 会将根请求转发到 Python 应用。
  • #2 位置 /js/(注意尾部的斜杠)会转发到 Node.js 应用。若省略尾部斜杠,Express.js 可能无法正常工作。

将配置文件放在 /etc/nginx/sites-available/roadtok8s 后,还需创建符号链接到 /etc/nginx/sites-enabled/ 以启用配置,执行如下命令:

sudo ln -s /etc/nginx/sites-available/roadtok8s /etc/nginx/sites-enabled/roadtok8s

接着删除默认配置文件:

sudo rm /etc/nginx/sites-enabled/default

最后重启 NGINX 使配置生效:

sudo systemctl restart nginx
  • systemctl 是 Linux 内置的进程管理器,负责管理包括 NGINX 在内的系统服务。

现在,打开浏览器访问:

  • http://<你的服务器IP>/ —— 访问的是 Python 应用(gunicorn)
  • http://<你的服务器IP>/js —— 访问的是 Node.js 应用(Express)

到此,我们已搭建起一套稳定的流量路由方案。

不过目前我们的应用端口依然对外开放,存在安全隐患。下一步我们将安装防火墙来限制只开放必要端口,提升服务器安全性。

3.6.2 安装简单防火墙(Uncomplicated Firewall,UFW)

给虚拟机安装防火墙非常重要,因为我们希望阻止除以下端口以外的所有流量:

  • 端口 80 的 HTTP 流量
  • 端口 443 的 HTTPS 流量
  • 端口 22 的 SSH 流量

UFW 是一个简单高效的防火墙工具,可以帮助我们只开放这些必要端口,自动屏蔽其它所有端口,提升服务器安全性。

我们将阻止所有非必要端口,仅开放 NGINX 使用的 80 和 443 端口,以及 SSH 使用的 22 端口。

安装 UFW:

sudo apt update        # 更新包列表
sudo apt install ufw -y  # 安装 UFW(如果尚未安装)

默认情况下,UFW 是禁用状态。启用前,需要先允许 SSH 和 NGINX 的流量:

sudo ufw allow ssh
sudo ufw allow 'Nginx Full'

注意:如果忘记允许 SSH,当前的 SSH 会话会被断开,可能导致无法远程连接虚拟机,只能重新创建 VM。

Nginx Full 允许 HTTP 和 HTTPS 流量(对应端口 80 和 443)。虽然我们暂时未启用 HTTPS,但可以为未来做准备。

执行命令后,终端会显示 Rules UpdatedRules Updated (v6)

查看当前的 UFW 规则:

ufw show added

应该输出:

Added user rules (see 'ufw status' for running firewall):
ufw allow 22/tcp
ufw allow 'Nginx Full'

启用防火墙:

sudo ufw enable

系统会提示:

Command may disrupt existing ssh connections. Proceed with operation (y|n)?

输入 y 并回车,结果会显示:

Firewall is active and enabled on system startup.

至此,防火墙已启用,服务器安全性大幅提升。

现在,可以打开浏览器访问:

  • http://<你的IP> (Python 应用)
  • http://<你的IP>/js (Node.js 应用)

应显示之前配置的页面。

虽然我们已经完成了基本的部署,但存在一个严重问题:单点故障(Single Point of Failure)。

当前这台机器同时承担:

  • 生产 Git 仓库托管
  • 应用依赖安装与环境构建
  • 两个核心应用的运行
  • NGINX Web 服务器运行

如果这台机器宕机,整个生产环境都会中断。

你可能想到的临时方案:

  • 升级机器硬件,提升性能
  • 备份整机和配置,快速恢复
  • 复制一台机器并用负载均衡分流流量

但这些都没能根本解决单点故障问题。

将生产环境的各个组件拆分到不同的专用机器或服务,做到彼此独立运行,降低风险。

目前我们实际上搭建了一个可被公网访问的远程开发环境。

如果关闭公网访问,只允许 SSH 登录,这个环境可以变成一个强大的远程开发平台,支持你从任何设备通过 SSH、用户名和密码安全编写代码。

总结

  • SSH 是一种强大的工具,可以让我们从世界任何地方访问和管理远程服务器。
  • NGINX 不仅能托管静态网站,还支持更复杂的功能,如反向代理和负载均衡。
  • 手动在远程机器上安装和配置软件,有助于深入理解软件的使用方法及预期效果。
  • Python 和 Node.js 的 Web 应用通常不会直接运行,而是通过类似 NGINX 的 Web 服务器来托管。
  • Web 应用应作为后台进程运行,借助 Supervisor 等工具保证其持续稳定运行。
  • 为代码配置私有主机,不仅能提供冗余备份,还能作为部署到生产环境的有效方式。
  • 防火墙是简便有效的安全手段,可阻止特定端口被未经授权访问,提升服务器安全性。