当前位置: 首页 > Linux

前端全栈之路--搭建linux+nodejs+expressweb服务器的生产环境

时间:2023-04-06 02:18:53 Linux

前言我曾经是一个纯前端,也就是很纯的那种。切图切图,编写html,css布局;后期写js,封装插件和组件;学完php之后,会写一些php后台和myslq。后来因为公司没人了,又变成了运维。当时给某公司搭建了一个windowsserver+Apache+php+mysql服务器。(因为他们当时要求在他们的windowsserver2012内部服务器上做项目)。但是作为一个前端,或者说前端人,我还是觉得“javascript是世界上最好的语言……^^”,所以转投了javascript和nodejs的一站式体系。我的服务器之前用过百度云的应用引擎bae。简单来说,百度搭建的linux和nodejs环境,可以直接在bae仓库的git或svn版本中进行项目开发,不用担心服务器的搭建和管理。可以直接专注于业务层的开发,但是bae应用引擎有一些限制,比如新版本发布后临时生成的文件会被清除。如果不能满足需求,后面我会从头开始搭建服务器。从写前端,写一些后端,再开个服务器,作为运维……虽然主要工作是前端开发,但勉强勉强成为全栈概念。写这篇文章主要是对之前开发的一个总结和记录。并以实际线上生产服务器为例作为记录蓝本。本文主要总结了基于nodejs环境从无到有业务层开发的过程,不涉及项目结构等业务逻辑层。好了,废话少说,进入正题。系统架构层面从零开始,从零开始系统环境:AlibabaCloudLinuxcentOS6.864位节点环境:node6.11.1express:4.x版本控制:git1.7.1不同于开发环境节点配置,生产环境与开发环境不同的nodejs环境也存在很多问题。不仅在安装node之后,您还可以通过从命令行执行nodeapp.js来启动nodeweb服务器。下面将从头开始,不谈搭建节点生产服务器时可能遇到的问题。如果下面有本地开发环境的描述,环境是:macos10.12.31。Linux服务器一般用于生产环境,现在大多采用Linux作为服务器系统环境。虽然windowservers也可以,但是要应对node环境,还是首选Linux系统。Linux有很多版本,比如CentOS、Ubuntu、Debian等,具体选择哪个版本,大家自行百度,就不多说了。百度云、阿里云、腾讯云等云服务商就不用多说了。使用哪种产品,可以自己选择(土豪或者土司买机房的请无视这句话)。这里有一篇参考文章,比较了CentOS、Ubuntu、Debian的异同。本文示例使用:阿里云ECSLinuxcentOS6.864位2、Linux用户创建服务器后,需要安装软件和调整配置。环境搭建好了。可以直接在控制台服务器实例上点击远程连接,打开网页版的linuxshell窗口,与linux服务器进行交互。如果很少或者只有一个人需要登录linux管理,可以直接使用root用户进行操作。但是为了规范和安全起见,这里我们创建了一个用于linux管理的用户。并授予可以执行sudo的管理员权限。创建用户eric(这里用自己的名字,eric是案例名),默认使用系统根目录/home,生成eric目录,/home/eric是eric的用户根目录#useradderic然后给用户sudo权限,打开/etc/sudoers文件,找到rootALL=(ALL)ALL,可以添加一行ericALL=(ALL)ALL或者执行ericALL=(ALL)NOPASSWD:ALL不带密码这样用户可以有sudo权限。但是建议将创建的用户添加到wheel用户组中,而不是直接添加用户权限(wheel用户组是linux默认的超级管理员权限组),放#%wheelALL=(ALL)NOPASSWD:ALL把前面的#号去掉,用户也有sudo权限,这样会更好。3、虽然可以通过网页版的linuxshell窗口设置ssh远程登录,也可以通过本地命令行终端通过ssh密码登录,但是为了规范、安全和方便管理,我们使用本地命令行终端,通过sshkey与linux进行远程交互。一般云服务器都默认安装了ssh,所以不用单独安装,直接使用即可。首先在web版linuxshell中登录linux服务器,切换到eric用户,执行mkdir创建.ssh目录。ssh在/home/eric/.ssh目录中生成一个密钥对文件。默认文件名为私钥:id_rsa,公钥Key:id_rsa.pub。ssh-keygen-trsa创建authorized_keys文件,存储用户公钥touchauthorized_keys将用户公钥复制到authorized_keys文件中,如果有多个,则每一个开始一个新行。cpid_rsa.pub>>authorized_keys注:当然也可以根据云服务指南直接在云服务控制台UI界面生成密钥对。然后将私钥发送给用户,id_rsa放在用户本机用户目录下的~/.ssh/id_rsa中。这里有个问题,就是如果机器需要使用ssh登录多个ssh连接,比如同时登录github和linux服务器时,一个id_rsa文件就会出问题,因为带有这个的文件name默认用作私钥文件名。此时需要在.ssh目录下新建一个名为config的文件,并将私钥文件重命名为:例如Alilinuxeric用户的id_rsa重命名为id_rsa_aliyun_eric,github下bbb用户的私钥id_rsa重命名为:id_rsa_github_bbb。config文件可以这样配置#阿里云eric用户ssh配置Host119.23.xx.xxxHostName119.23.xx.xxxUserericIdentityFile~/.ssh/id_rsa_aliyun_eric#github的ssh配置HostgithubHostNamegithub.comUserbbbIdentityFile~/.ssh/id_rsa_github_bbb这样一台机器可以实现多次ssh登录。4.Nodejs安装安装nodejs有几个选项。使用yuminstallnodejs通过linux编译安装源码。下载编译好的nodejs源码压缩包安装,无需编译。通过yuminstall安装主要是nodejs版本的问题,无法指定安装。.源码编译安装比较麻烦。在这里下载编译好的源码包,解压安装特定版本的nodejswget--no-ckeck-certificatehttps://nodejs.org/dist/v6.11.1/node-v6.11.1-linux-x64.tar.xz和解压安装到/opt目录下tar-zxvf/home/eric/node-v6.11.1-linux-x64.tar.xz-C/opt/然后将node和npm链接到/usr/local/bin,这样该节点可以在任何环境目录中执行ln-s/opt/node-v6.11.1-linux-x64/bin/node/usr/local/bin/nodeln-s/opt/node-v6。11.1-linux-x64/bin/npm/usr/local/bin/npm最后执行node-v,npm-v有版本显示说明安装成功。5.nodejs创建http服务器nodejs环境安装完成后进行如下http服务测试#进入用户目录cd~#创建www目录作为web服务的根目录mkdirwww#进入www目录创建server.js作为nodejs的http服务器文件touchserver.js作为nodejs官网的测试示例代码先测试consthttp=require('http');const主机名='127.0.0.1';常量端口=3000;constserver=http.createServer((req,res)=>{res.statusCode=200;res.setHeader('Content-Type','text/plain');res.end('HelloWorld\n');});server.listen(port,hostname,()=>{console.log(`服务器运行在http://${hostname}:${port}/`);});执行nodeserver.js后,http服务运行成功,说明nodejs环境可以成功搭建nodejs的http服务器6.使用git作为版本控制系统。一般linux云服务器都是默认安装git的,没有安装git的自行安装。如果想把github作为git仓库,可以通过github设置,使用github将提交的代码发布到linux服务器的www目录。(当然,私有仓库是收费的。)下面以自建git仓库为例,说说git仓库的创建和版本发布#进入用户根目录cd~mkdirgitrepo#creategitwarehousecdgitrepomkdirtest.gitcd测试。git#执行git初始化命令,创建一个空仓库gitinit--bare此时,test.git下会有一个.git文件夹,说明名为test的git仓库已经建立。(当然你也可以创建一个linux用户git来处理git相关的管理,然后我只用一个linux用户eric来管理,这也是考虑到不会有那么多管理员去实际管理)以后,以免切换linux用户麻烦)最后进入www目录克隆仓库cd/home/eric/wwwgitclone/home/eric/gitrepo/test.git这样www目录作为release目录正式有版本control钩子的post-receive自动部署到www.我们本地代码上传提交时,不能每次都进入www目录进行手动拉取操作。这实在不是什么好办法。通过什么方式,当我们在本地提交的时候,git会自动帮我们部署到www目标目录。就是挂钩,也叫挂钩。即git远程仓库收到提交指令后自动执行的脚本文件。在hooks目录下创建一个名为post-receive的文件(这个文件可以理解为git仓库收到本地push到远程仓库后会执行的脚本)。脚本示例如下#!/bin/shunsetGIT_DIRDeployPath=/home/eric/wecho"================================================"cd$DeployPathecho"开始发布"gitpulloriginmastertime=`date`echo"发布成功时间:$time."echo"===================================================”其实脚本就是进入www目录,然后执行pull操作,不过是脚本执行,不是手动的。当然这是最简单的版本,接下来我们再讨论根nodejs的一些具体提交细节。8、使用yarn代替npm作为nodejs,express的包管理工具在搭建express架构的nodejs服务器时,我们通常使用npm来管理package.json的安装和管理。但经过实际考虑,自学后决定使用yarn代替npm。主要考虑两个原因。npm的速度比较慢,npm的版本依赖不好控制。Yarn是由Facebook、Google、Exponent和Tilde开发的一种新的JavaScript包管理工具。yarn和npm的具体异同、优缺点请自行百度。这里就不细说了,只说使用问题。yarn安装:参考nodejs源码安装过程,参考官网示例通过软链接yarn进入/usr/local/bin,无需通过yuminstall,如果要安装最新版本或者特定版本,可以参考官网进行源码安装或者编译安装。执行yarn--version显示版本号,说明yarn安装成功。express基本目录结构如下--binwww--public--routes--viewsapp.jspackage.json根据package.json执行yarninstall后,目录结构的根目录多了一个锁文件,用于yarn.lock的包依赖(这是yarn特定包管理的依赖管理文件)安装后目录结构类似如下--binwww--public--node_modules--routes--viewsapp。jspackage.jsonyarn.lock到这里应该说,express框架下的节点服务已经相当不错了,基本可以使用了。9、使用pm2作为nodehttp服务的进程管理器我们都知道,使用nodeserver.js会直接在命令行打开nodejsweb服务,但是会阻塞命令行当前窗口。如果要输入其他命令,则必须退出或打开一个新窗口。并且如果文件被修改,需要停止并重新加载一次才会生效。开发环境还行,但这显然不是正式生产环境所能容忍的。所以我选择了pm2作为节点进程管理工具(类似forever,但对比后不如pm2)。首先安装pm2(一个小问题:由于我是从源码安装npminstall-g,模块会全局安装到/opt/nodejs/bin下,而且还必须软链接到/usr/local/bin,或者添加环境变量。全局使用)npminstall-gpm2#使用pm2启动node进程pm2startserver.js也很简单直接使用pm2启动文件。更多的使用说明可以参考官方文档,写的很清楚。PM2也可以通过配置文件启动。这里为了以后管理方便,我们以启动配置文件的形式启动。pm2默认配置文件启动的文件名为ecosystem.config.js。我简单配置的启动脚本如下,仅供参考:module.exports={apps:[{//generalname:'node-web',script:'bin/www',//启动初始脚本执行//advancedwatch:['appsback','routes','ecosystem.config.js','server.js'],//监控文件变化ignore_watch:['node_modules','apps','static'],//忽略监控文件夹max_memory_restart:'800M',//内存量多少会自动重启env:{COMMON_VARIABLE:'true'},env_production:{NODE_ENV:'production'},//logfilelog_date_format:'YYYY-MM-DDHH:mm:ssZ',//日志格式//控制min_uptime:3000,listen_timeout:3000,kill_timeout:5000,max_restarts:5,}]};pm2的一个比较好的特性是可以监听文件变化,即文件变化后node的http服务会重新加载,0秒后重新加载。这是生产环境应该有的操作。。。而不是停止后重新加载,线上环境基本不允许。这样只要执行pm2startecosystem.config.js就可以启动一个nodehttp服务进程。至此,我们express项目的目录结构大致变成了这样:--binwww--public--node_modules--routes--viewsecosystem.config.jsserver.jspackage.jsonyarn.lock10。package.json变化引起的自动化部署的问题我们知道如果本地的package.json发生变化,需要执行npminstall或者yarninstall来安装模块,但是提交之后在server端怎么办呢?比较笨的方法是每次提交后package.json发生变化,进入node的web根目录,即www目录手动执行npminstall或yarninstall。但是这个方法显然不够聪明,怎么办呢。。。既然package.json变了,重启后需要npminstall才能让nodehttp找到对应的模块。所以对于不监听package.json文件的变化,我有以下两种形式的思考。即每次提交时删除node_modules文件夹,不管package.json有没有变化,执行yarninstall重新安装package.json依赖,监控package.json的变化,即只在package.json变化时执行yarninstall。显然,第一种更方便简单,不需要冗余监控,减少了一些服务器配置。(现在想来,这其实是百度云bae的策略,他的官方文档介绍是每次提交的时候删除node_modules重新安装,不管package.json有没有变化。)但是我觉得第二种显然更好更合理,因为如果package.json没变,删掉node_modules重新安装显然是浪费资源。剩下的问题是如何监控package.json更改后何时以及如何执行npminstall或update模块。一开始以为已经用过的工具pm2有监控功能,但是pm2监控只会产生restart执行,并没有执行具体回调函数或者脚本的功能(官方文档也有提到这一点)。所以只能想别的办法了。后来想用linux系统自带的inotify监控文件变化,打开一个监控脚本,时刻监控www目录下package.json的变化。不过最后想了想还是决定在githookshookpost-receive脚本中处理这件事,可以保存一个脚本文件用于系统监控。最终解决方案变成:在post-receive脚本中,判断package.json文件是否有变化,gitpull后执行install,然后重新加载pm2的nodehttp服务进程,一气呵成。最后将post-receive脚本修改为如下形式供参考:#!/bin/shunsetGIT_DIRDeployPath=/home/eric/wwecho"================================================"cd$DeployPathecho"开始发布"last_modify_time=`statpackage.json|grep"Modify"`echo"t1:$last_modify_time"gitpulloriginmastercur_modify_time=`statpackage.json|grep"Modify"`echo"t2:$cur_modify_time"if["$last_modify_time"!="$cur_modify_time"];thenecho"package.jsonchanged"rm-rfnode_modulesyarninstallpm2restartecosystem.config.jselseecho"package.jsonnotchanged"fitime=`date`echo"发布成功时间:$time."echo"==================================================="我我这里是通过判断pull前后package.json文件的最后一次修改时间是否发生变化来判断package.json本次是否被修改过。如果有变化就删除node_modules然后yarninstall安装,最后重启pm2。如果没有变化,则可以根本不执行上述操作,节省执行上述步骤带来的资源消耗。当然,如果更精确的话,可以在package.json中分析依赖的具体变化,是增加模块还是删除模块,或者修改某个版本的依赖来进行install、update的具体操作或删除,而不是使用重新删除并重新安装。但是这样对于npm或者yarn的json解析和增删改查操作来说太麻烦了。这个方法是我传过来的,我删了直接重装了。由于yarn有安装模块的缓存,yarnisntall带来的时间问题基本可以忽略,速度很快。这也是我提倡用yarn代替npm的原因之一。11、重启linux后,nodeweb服务自动启动。我们可以重启linux,进入linux,进入/home/eric/www目录,用pm2startecosystem.config.js再次手动启动node的http服务。虽然linux重启不是很频繁的事情,但是这种手动需要做的,当然可以交给linux开机自启动脚本来完成。我们可以在/etc/rc.d/rc.local目录中构建自己的shell脚本。这里我们使用pm2提供的函数直接创建自启动脚本。官网介绍:pm2startup直接根据提示信息创建启动脚本,非常方便。12、非root用户使用非80端口映射80端口,Linux默认只有root用户有权限占用1024以下端口。如果我们的apache、nginx、nodejs等程序要使用eric等普通用户80端口被占用,会抛出Permissiondenied:80的权限异常。那我们只能用8080之类的比较大的端口号了,但是url上有端口号就不好了。这里我使用iptables进行端口转发,即将nodejshttp服务使用的端口如8080转为80端口,虽然node服务是8080端口,但是用户url不需要有端口,默认端口直接用80。也可以直接转发给8080的节点服务器处理。使用root用户执行iptables-tnat-APREROUTING-ptcp--dport80-jREDIRECT--to-port8080别忘了保存重启serviceserviceiptablessave,serviceiptablesrestart另外你也可以使用nginx作为反向代理进行端口转发。==================================================================================至此,一个nodejs环境下的http服务器已经从头搭建完成,有http服务,git版本控制,自动化部署,pm2进程管理,以及一个可以在生产环境中使用的nodejsexpressweb服务器。房子建好了。至于具体的业务逻辑,我们在目录中装饰一下。