Node.js 弹性概念:恢复和自我修复
在一个理想的世界里,如果我们实现了 100% 的测试覆盖率,我们的错误处理完美无瑕,
所有故障都得到了妥善处理——在一个所有系统都达到完美状态的世界里,
我们就不会进行这样的讨论了。
然而,我们身处此地。地球,2020年。当你读到这句话时,
某个生产环境的服务器已经崩溃了。让我们为那些被我们摧毁的进程默哀片刻。
在这篇文章中,我将介绍一些概念和工具,它们将使您的服务器更具弹性,并提升您的进程管理技能。
Node Index.js
对于 Node.js 用户来说——尤其是对于服务器新手而言——你可能希望
在远程生产服务器上运行你的应用程序,就像在开发环境中运行它一样。
安装 Node.js,克隆仓库,给它一个npm install,,然后node index.js(或npm start)启动它。
我记得一开始我觉得这计划万无一失。如果它有效,为什么要改变它呢?
我的代码在开发过程中会出现错误,导致崩溃,
但我当场修复了这些错误——所以服务器上的代码没有损坏,
不会崩溃。一旦启动,这台服务器就会一直运行到宇宙热寂为止。
正如你可能猜到的那样,事实并非如此。
当时我面临着两个我完全没有想到的主要问题:
- 如果虚拟机/主机重启会发生什么?
- 服务器崩溃……这简直是它们第二大“特色”。如果它们什么都不提供,我们肯定会叫它们“崩溃器”。
金刚狼大战T-1000
服务器崩溃后,恢复工作可以通过多种方式解决。有一些便捷的方案
可以在服务器崩溃后重启服务器,还有一些更复杂的方案
可以确保服务器在生产环境中坚不可摧。
金刚狼和 T-1000 都很耐打,但它们的复杂性和恢复速度却截然不同。
我们根据所处环境的不同,寻找不同的特性。
对于研发而言,目标是便捷性;对于生产而言,通常是韧性。
我们将从最简单的恢复形式开始,然后逐步过渡
到复杂的编排解决方案。
您可以根据自己的意愿投入多少精力进行实施,
但多一些工具总是没错的,所以如果这引起了您的兴趣,
那就系好安全带,让我们开始吧!
解决问题
你正在埋头编写代码,开发你那令人惊叹的服务器。
每写几行,你就得切换标签页,然后用箭头键或node index箭头键微调一下npm start。
这种不断切换和微调的循环,时间长了会让人感到极其厌烦。
如果修改代码后它能自动重启,那岂不是很好吗?
这时,像Nodemon
和 Node.js Supervisor这样的轻量级软件包就派上了用场。
你只需一行代码就能安装它们,然后下一行代码就能开始使用它们。
要安装Nodemon,只需在终端中输入以下命令即可。
npm install -g nodemon
安装完成后,只需将node您之前使用的命令替换为您现在可以访问的
新命令即可。nodemon
nodemon index.js
您可以使用类似的方法安装 Node.js Supervisor,只需输入以下命令即可。
npm install -g supervisor
同样,安装完成后,您只需使用supervisor前缀即可运行您的应用程序。
supervisor index.js
Nodemon 和 Supervisor 都非常实用且受欢迎,主要区别
在于 Nodemon 需要您修改文件才能重新启动进程,
而 Supervisor 可以在进程崩溃时重新启动进程。
您的服务器运行正常。开发速度提升了四倍。
这些软件包在解决开发痛点方面做得非常出色
,而且配置起来也相当灵活。但是,我们在开发过程中遇到的困难与
在生产环境中遇到的困难很少重合。
当你把程序部署到远程服务器时,感觉就像是过度保护的家长送孩子上大学一样。
你希望确保服务器运行正常、安全无虞,并且吃得饱饱的。
你想知道它崩溃时遇到了什么问题——如果它真的崩溃了的话。你希望它能得到妥善的维护。
好消息!这时进程管理器就派上用场了。它们可以守护生产环境中的服务器。
流程管理
当你运行应用程序时,会创建一个进程。
在开发环境中运行程序时,通常会打开终端窗口并在其中输入命令。这样就会创建
一个前台进程,应用程序便开始运行。
现在,如果您关闭该终端窗口,您的应用程序也会随之关闭。
您还会注意到终端窗口已被锁定。
在用 `.` 关闭进程之前,您无法输入任何其他命令Ctrl + C。
缺点是该应用程序与终端窗口绑定,
但你也能够查看进程抛出的所有日志和错误信息。
所以,从积极的角度来看,也算是有好处的。
然而,在生产服务器上,你可能需要将其在后台运行,
但这样就失去了实时查看的便利性。这肯定会让你感到沮丧。
流程管理很繁琐。
幸好我们有流程管理器!它们本身就是流程,可以帮我们管理其他流程。
真是太元了!而且极其方便。
PM2
Node.js 最流行的进程管理器是pm2,
它如此流行是有充分理由的。它非常棒!
这款软件实在太棒了,要
全面介绍它的强大功能和众多便捷特性,恐怕需要另写一篇文章。由于我们今天主要讨论的是自我修复,所以
我会在下面简要介绍一些基础知识,但我强烈建议您深入了解一下
,看看它还有哪些令人惊叹的功能。
安装 pm2 和安装我们上面讨论的软件包一样简单。
只需在终端中输入以下命令即可。
npm install -g pm2
运行你的应用也并不复杂。只需输入以下命令,其中 ` index.js<main server file>` 是你的主服务器文件。
pm2 start index.js
不过这次你可能会注意到一些不同之处。
表面上看,似乎什么都没发生,但如果你访问应用程序的端点,
就会发现它已经启动并正在运行。
还记得我们讨论过在后台运行进程吗?
现在的情况正是如此。pm2 已经将你的服务器作为后台进程启动,并且正在为你管理它。
为了更加方便,您还可以使用该--watch标志来确保 pm2 监视您的文件更改
并重新加载您的应用程序,以确保它始终保持最新状态。
为此,您可以使用上面的命令,但要在命令末尾加上标志。
pm2 start index.js --watch
现在,pm2 会监控我们的文件,并在文件发生更改或进程崩溃时重启进程。
完美!这正是我们想要的。
它在后台管理服务器方面做得很好,但缺乏可见性令人焦虑。
如果我想查看服务器日志该怎么办?
pm2 可以满足你的需求。它的命令行界面非常强大!下面我会列出一些命令,帮助你快速上手。
请使用以下命令列出您的应用程序。
| 命令 | 描述 |
|---|---|
pm2 list |
列出您的应用程序。您会看到与 pm2 管理的应用程序关联的数字id。您可以在要执行的命令中使用该 ID。 |
pm2 logs <id> |
检查应用程序日志。 |
pm2 stop <id> |
停止您的进程。(进程停止并不意味着它已不存在。如果您想彻底删除该进程,则需要使用 delete 命令。) |
pm2 delete <id> |
删除该进程。(无需单独停止和删除,可以直接执行删除操作,系统会自动停止并删除该进程。) |
pm2 的可配置性极强,能够自动执行负载均衡和热重载。你可以在他们的文档
中了解所有详细信息,但我们的 pm2 之旅就到此为止了。
我知道这令人失望。但为什么呢?我仿佛听到了你们的疑问。
还记得安装 pm2 有多方便吗?
我们是用 Node.js 包管理器安装的。眨眼……(比划手枪手势)眨眼眨眼。
等等。我们是用Node.js来监控Node.js吗?
这听起来有点像让孩子自己照顾自己。这主意好吗?
这个问题没有客观答案,但显然
应该考虑其他一些替代方案。
那么,接下来呢?让我们一起来探索一下。
Systemd
如果你打算在传统的 Linux 虚拟机上运行,我认为systemd
在深入了解容器和编排器之前,有必要提一下这一点。
否则,如果您计划在托管应用程序环境(例如 Azure AppService、AWS Lambda、GCP App Engine、Heroku 等)上运行,则
这与您的用例无关,但了解一下也无妨。
假设只有你、你的应用程序和一个 Linux 虚拟机,让我们看看它能systemd为你做什么。
systemd 可以帮你启动、停止和重启进程,这正是我们需要的。
如果你的虚拟机重启,systemd 会确保你的应用程序再次启动。
但首先,让我们确保您在虚拟机上可以访问 systemd。
以下是使用 systemd 的 Linux 系统列表:
- Ubuntu Xenial(或更新版本)
- CentOS 7 / RHEL 7
- Debian Jessie(或更新版本)
- Fedora 15(或更高版本)
现实点说,你用的Linux系统肯定不是大洪水之前的产物,
所以你很可能拥有systemd访问权限。
第二件你需要的东西是具有sudo相应权限的用户。
我这里简单地用“用户”来指代这个用户,user但你应该把它替换成你自己的用户名称。
由于我们的用户名为user,并且在本示例中,我使用的是 Ubuntu,
我将把你的主目录称为,/home/user/并且我将假设
你的index.js文件位于你的主目录中。
systemd 服务文件
systemd 文件是一个很有用的小文件,我们可以把它创建在系统目录下,用来保存
我们服务的配置信息。它非常简单易用,所以我们来尝试创建一个。
systemd 文件全部位于下面列出的目录下。
/lib/systemd/system
让我们用你选择的编辑器创建一个新文件,并添加一些内容。
别忘了sudo在命令前加上 `--root` 前缀!这里的所有内容都属于 root 用户。
好的,我们先进入系统目录。
cd /lib/systemd/system
为您的服务创建一个文件。
sudo nano myapp.service
接下来,我们来填充一些内容。
# /lib/systemd/system/myapp.service
[Unit]
Description=My awesome server
Documentation=https://awesomeserver.com
After=network.target
[Service]
Environment=NODE_PORT=3000
Environment=NODE_ENV=production
Type=simple
User=user
ExecStart=/usr/bin/node /home/user/index.js
Restart=on-failure
[Install]
WantedBy=multi-user.target
如果你快速浏览一下配置,就会发现它相当简单明了,大部分内容都不言自明。
你可能需要一些提示的两个设置是After和Type。
After=network.target这意味着它应该等待服务器的网络部分启动并运行,
因为我们需要使用该端口。简单类型则表示不要执行任何复杂的操作,只需启动并运行即可。
使用 systemctl 运行您的应用程序
文件创建完成后,我们需要设置systemd程序从新创建的文件中获取更改。
每次修改文件后都需要执行此操作。
sudo systemctl daemon-reload
就这么简单。现在它已经知道我们的服务了,
我们应该可以使用systemctl命令来启动和停止它。
我们将通过服务文件名来引用它。
sudo systemctl start myapp
如果要停止它,可以将start命令替换为stop。
如果要重新启动它,则输入restart。
现在,我们来谈谈最关键的部分。
如果您希望应用程序在虚拟机启动时自动启动,则应执行以下命令。
sudo systemctl enable myapp
如果你想停止这种行为,只需替换enable为disable。
就这么简单!
现在我们有了另一个系统来管理我们的流程,它并非Node.js本身。
这太棒了!读这篇文章的时候,你可以自豪地给自己一个击掌,或者,如果
疫情期间的规定允许,也可以尴尬地碰碰手肘。
但我们的旅程并未就此结束。还有很多领域等待我们去探索,
所以让我们慢慢地开始深入了解容器和编排的世界吧。
什么是容器?
要继续学习,你需要了解什么是容器以及它们是如何工作的。
市面上有很多容器运行时环境,例如 Mesos、CoreOS、LXC 和 OpenVz,
但真正能与容器划上等号的,非Docker莫属。
它占据了超过 80% 的容器使用量,人们提到
容器时,通常指的就是 Docker。
那么,这些容器究竟是做什么用的呢?
容器的作用就是容纳东西。从这个意义上讲,它们的名称非常简单明了。
现在的问题是,它们包含什么?
容器包含您的应用程序及其所有依赖项。
不多不少,仅此而已。它只包含您的应用程序以及应用程序运行所需的一切。
想想你的Node.js服务器需要执行什么操作:
- Node.js(这还用说吗?)
- 你的 index.js 文件
- 可能是你的 npm 包(依赖项)
所以,如果我们要创建一个容器,我们会希望确保这些东西存在并被包含在内。
如果我们准备好这样的容器,那么就可以通过容器引擎(例如 Docker)启动它。
容器与虚拟机,以及意大利美食
即使你对虚拟机的使用经验不多,
我想你也应该对它们的工作原理有个大致的了解。
你可能见过你的朋友在Windows机器上运行安装了Linux系统的程序,
或者在macOS机器上运行安装了Windows系统的程序等等。
所以,其理念是,你有一个物理机器,其上运行着一个操作系统,
而你的应用程序及其依赖项就包含在这个操作系统中。
假设我们正在做披萨。
- 机器就是桌子
- 操作系统就是披萨面团
- 而你的应用及其依赖项,正是构成这一切的要素。
假设你想吃 5 种不同口味的披萨,你应该怎么做?
答案是在同一张桌子上制作5种不同的披萨。这是虚拟机给出的答案。
但 Docker 却说:“嘿,这太浪费了!你不可能吃 5 个披萨,
而且做面团很费劲。为什么不用同样的面团呢?”
你可能会想,嘿,这主意其实还不错——但我不想让
朋友那令人作呕的菠萝味(抱歉,我就是这么觉得)渗入
我美味的四种奶酪里。这两种配料的味道冲突!
而这正是 Docker 的天才之处:
“别担心!我们会把它们都控制住。你的四种奶酪部分甚至都不会知道菠萝部分的存在。”
所以 Docker 的神奇之处在于,它能够使用同一台底层物理机
和操作系统来运行各种不同“版本”的独立应用程序,而
这些应用程序之间永远不会发生冲突。
就像防止异国水果出现在你的披萨上一样。
好了,接下来我们来创建第一个 Docker 容器。
创建 Docker 容器
创建 Docker 容器非常简单,但你需要先在你的机器上安装 Docker。
无论你使用什么操作系统,都可以安装 Docker。
它支持 Linux、Mac 和 Windows,但我强烈建议生产环境使用 Linux。
Docker安装完毕后,就可以创建容器了!
Docker 会查找一个名为 `.docker.docker.js` 的特定文件Dockerfile,并使用它来创建
容器的配方,也就是我们所说的 Docker 镜像。
因此,在创建容器之前,我们需要先创建这个文件。
让我们在与我们现有index.js文件相同的目录中创建此文件package.json。
# Dockerfile
# Base image (we need Node)
FROM node:12
# Work directory
WORKDIR /usr/myapp
# Install dependencies
COPY ./package*.json ./
RUN npm install
# Copy app source code
COPY ./ ./
# Set environment variables you need (if you need any)
ENV NODE_ENV='production'
ENV PORT=3000
# Expose the port 3000 on the container so we can access it
EXPOSE 3000
# Specify your start command, divided by commas
CMD [ "node", "index.js" ]
使用.dockerignore同一目录下的文件来忽略
您可能不想复制的文件和目录是一种明智的做法。您可以将其理解为与以下操作相同:.gitignore
# .dockerignore
node_modules
npm-debug.log
现在一切就绪,是时候构建 Docker 镜像了!
你可以把镜像想象成容器的配方。
或者,如果你年纪够大,可能还记得以前用过的软件安装光盘。
光盘里装的并不是实际运行的软件,而是打包好的软件数据。
您可以使用以下命令创建镜像。您可以使用-t标志为镜像命名,以便
日后轻松查找。此外,请确保您已打开终端并切换到镜像文件所在的目录Dockerfile。
docker build -t myapp .
现在,如果您列出您的图片,您将能够在列表中看到您的图片。
docker image ls
如果你的镜像已经准备就绪,那么只需一条命令即可启动并运行你的容器。
让我们执行以下命令来启动它。
docker run -p 3000:3000 myapp
您将能够看到服务器随容器启动,并在此过程中查看日志。
如果您想让它在后台运行,请-d在镜像名称前添加标志。
另外,如果您在后台运行容器,可以使用以下命令打印容器列表。
docker container ls
目前为止一切顺利!我想你现在应该对容器的工作原理有了相当清晰的了解,
所以我们不再深入细节,而是直接进入与恢复密切相关的主题:编排!
管弦乐
如果你没有运维背景,你很可能会把容器想象
成一些神奇而复杂的组件。你的想法没错,
它们确实很神奇也很复杂。但这种想法对我们理解容器并没有帮助,所以是时候改变这种观念了。
最好把它们看作是我们基础设施中最简单的组成部分,有点像乐高积木。
理想情况下,你甚至都不想单独管理这些乐高积木,
因为这太繁琐了。你需要另一个实体来帮你处理它们,
有点像我们之前讨论过的进程管理器。
这时,编曲者就派上用场了。
编排器可帮助您管理和调度容器,并允许您
跨分布在多个位置的多个容器主机(虚拟机)执行此操作。
在这种情况下,我们最感兴趣的编排器功能是复制!
复制和高可用性
服务器崩溃后重启固然很好,但
服务器重启期间会发生什么?用户是否需要等待服务
恢复?他们又如何知道服务最终会恢复呢?
我们的目标是使我们的服务具有高可用性,这意味着
即使我们的应用程序崩溃,用户仍然能够使用我们的服务。
但如果它坏了,该怎么用呢?
很简单。复制服务器并同时运行它们!
从头开始搭建这个系统会很麻烦,但幸运的是,我们已经拥有
启用此机制所需的一切。
应用程序容器化后,您可以运行任意数量的副本。
这些复制品被称为复制品。
那么,让我们来看看如何使用容器编排引擎来搭建类似的系统。
市面上有很多容器编排引擎,但最容易上手的是
Docker 的编排引擎 Docker Swarm。
Swarm中的复制
如果你的机器上安装了 Docker,那么只需一条命令即可使用 Docker Swarm。
docker swarm init
这条命令会启用 Docker Swarm,并允许您
通过将其他虚拟机连接到 Swarm 来构建分布式集群。在本示例中,我们只需使用一台机器即可。
启用 Docker Swarm 后,我们现在可以使用名为 的组件services。
它们是微服务架构的核心,
使我们能够轻松创建副本。
让我们来创建一个服务!还记得我们构建 Docker 镜像时使用的镜像名称吗?
这里我们将使用同一个镜像。
docker service create --name myawesomeservice --replicas 3 myapp
上面的命令将创建一个名为 `service` 的服务myawesomeservice,并使用
名为 ` image` 的镜像myapp创建 3 个相同的容器。
您可以使用以下命令列出您的服务。
docker service ls
您可以看到存在一个与您指定的名称相符的服务。
要查看已创建的容器,可以使用以下命令:
docker container ls
现在我们的服务器以复制模式运行,该服务将确保
在容器崩溃时始终重新启动容器,并且在整个过程中可以提供对健康容器的访问。
如果要调整服务的副本数量,可以使用以下命令。
docker service scale <name_of_service>=<number_of_replicas>
例如:
docker service scale myapp=5
您可以运行任意数量的副本,就这么简单。
是不是很棒?让我们来看最后一个例子,看看在 Kubernetes 中如何进行数据复制。
Kubernetes 中的复制
在讨论编排技术时,很难忽略Kubernetes
。 它是编排领域的黄金标准,而且名副其实。
我认为 Kubernetes 的学习曲线比 Swarm 陡峭得多,所以如果你刚开始接触容器,我建议你先学习 Swarm。话虽如此, 对 Kubernetes 的工作原理
有个大致了解也无妨。
如果你不想安装 minikube
,或者不想折腾云服务提供商,可以使用Play with Kubernetes在线工具
轻松体验 Kubernetes 。 它提供 4 小时的试用期,足以进行一些小型实验。
要完成此练习,请确保您已创建DockerHub
帐户,并将 docker 镜像推送到您的存储库!
我们将通过创建两个.yml配置文件来创建两个组件:
- 集群 IP 服务——这将为我们打开一个端口,以便与我们的应用程序进行通信。
- 部署(Deployment) ——它有点像 Docker Swarm 中的服务,但功能更丰富一些。
我们先从集群IP开始。创建一个cluster-ip.yml文件,并将以下内容粘贴到其中。
# cluster-ip.yml
apiVersion: v1
kind: Service
metadata:
name: cluster-ip-service
spec:
type: ClusterIP
selector:
component: server
ports:
- port: 3000
targetPort: 3000
我们再创建一个部署。deployment.yml您可以将以下内容粘贴到该文件中。
# deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: server-deployment
spec:
replicas: 3
selector:
matchLabels:
component: server
template:
metadata:
labels:
component: server
spec:
containers:
- name: server
image: your_docker_user/your_image
ports:
- containerPort: 3000
您需要确保将 `<username>` 替换your_docker_user/your_image为您
实际的用户名和镜像名称,并且该镜像已托管在您的 Docker 仓库中。
现在我们已经准备好了这两个文件,接下来只需执行
以下命令即可启动程序。请确保在包含这些文件的目录中执行该命令。
kubectl apply -f .
现在您可以通过列出部署和服务来检查您的服务器是否正在运行。
kubectl get deployments
kubectl get services
如果一切按计划进行,
你应该能够将你的应用程序IP地址复制粘贴到 浏览器的地址栏中以访问你的应用程序。Portcluster-ip-service
要查看已创建的副本,可以使用以下命令:
kubectl get pods
列出的 pod 应该与您在文件中指定的副本数相对应deployment.yml。
要清理所有组件,您只需执行以下命令:
kubectl delete -f .
就这样,我们也了解了 Kubernetes 中的复制机制。
结论
所以,我们有一个具备恢复功能且高可用的应用程序。就这些吗?
完全不是。事实上,既然你的应用现在不会“崩溃”,你怎么知道它可能存在什么问题呢?
看日志?说实话,如果你的应用每次检查端点时都处于运行状态,
那你可能一年也就看两次日志。
社交媒体上还有更多有趣的东西值得关注。
因此,为了确保您的应用不断改进,您需要开始考虑监控、
错误处理和错误传播。您必须确保能够及时发现问题
,并且即使问题不会导致服务器宕机,您也能及时修复它们。
不过,那是另一个话题了。我希望您喜欢这篇文章
,并且它能够帮助您了解一些可以用来
为 Node.js 应用程序启用恢复功能的方法。
PS:如果您喜欢这篇文章,请订阅我们全新的 JavaScript Sorcery 邮件列表,每月深入探讨更多神奇的 JavaScript 技巧和窍门。
PPS 如果您想要一个适用于 Node.js 的一体化 APM,或者您已经熟悉 AppSignal,请去看看适用于 Node.js 的 AppSignal。
文章来源:https://dev.to/appsignal/node-js-resiliency-concepts-recovery-and-self-healing-4a99