CentOS 7.4 部署nodejs应用

2,883 阅读7分钟

本文以已 CentOS7.4 为例(Ubuntu 相关命令大同小异)

域名与 ECS 主机选择

考虑因素:

  1. 知名大厂商(考虑阿里云,腾讯云以及钱包)
  2. 国内(考虑访问速度和之后的备案)
  3. 购买完成即可拿到公网 IP
  4. 服务器配置

ssh 登录,连接服务器

# 登录 公网IP
ssh root@110.110.120.114

# 输入密码即可进入

# 查看目录
cd /
ls

# 系统更新
yum update -y

node 安装

安装

# 下载源文件
wget https://nodejs.org/dist/v10.14.2/node-v10.14.2-linux-x64.tar.xz
# 解压
tar xvf node-v10.14.2-linux-x64.tar.xz
# 解压后删除下载的源文件
rm -rf node-v10.14.2-linux-x64.tar.xz
# 移动文件位置 /usr/local/lib
mv node-v10.14.2-linux-x64 /usr/local/lib
# 把bin目录添加到PATH环境中
export PATH=/usr/local/lib/node-v10.14.2-linux-x64/bin:$PATH
# # 创建软连接 全局支持 node
# ln -s /usr/local/lib/node-v10.14.2-linux-x64/bin/node /usr/bin/node
# ln -s /usr/local/lib/node-v10.14.2-linux-x64/bin/npm /usr/bin/npm
# ln -s /usr/local/lib/node-v10.14.2-linux-x64/bin/npx /usr/bin/npx

# 查看安装信息,全局可用
node -v
npm -v
npm -v

# 查看node安装
which node
# /root/.nvm/versions/node/v12.13.1/bin/node
sudo /root/.nvm/versions/node/v12.13.1/bin/node --version
# v12.13.1

# 查看安装路径(npm --help)
npm prefix
npm prefix -g

# 开发依赖
npm install --production

测试 node 程序

  • 新建app.js
const http = require('http')

http
  .createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'application/json' })
    let data = {
      code: 0,
      msg: 'success'
    }
    res.end(JSON.stringify(data))
  })
  .listen(3001)

console.log('http server is listening at port 3001.')
  • 运行node app.js
{
  "code": 0,
  "msg": "success"
}

pm2 安装

  1. 如何无缝重启(故障恢复)
  2. node 进程如何管理
  • 安装
npm install pm2 -g

# 创建软连接全局支持pm2
ln -s /usr/local/lib/node-v10.14.2-linux-x64/bin/pm2 /usr/local/bin/pm2

# [查看 pm2 ls](https://github.com/Unitech/pm2)
pm2 list

pm2 启动文件配置

  • 生成配置文件pm2 ecosystem
  • 配置文件修改与使用
  • 启动
    • pm2 start ecosystem.config.js --env development
  • 部署
    • pm2 deploy ecosystem.config.js --env production

mongodb 安装

  • 安装
# 软件安装位置:/usr/local/lib/mongodb
# 数据存放位置:/var/mongodb/data
# 日志存放位置:/var/mongodb/logs

# 下载
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.2.tgz
# 解压
tar -zxvf mongodb-linux-x86_64-4.0.2.tgz
# 删除下载源文件
rm -rf mongodb-linux-x86_64-4.0.2.tgz
# 重命名
mv mongodb-linux-x86_64-4.0.2 mongodb
# 设置环境变量,mongod,mongo命令全局生效
ln -s /usr/local/lib/mongodb/bin/mongo  /usr/local/bin/mongo
ln -s /usr/local/lib/mongodb/bin/mongod  /usr/local/bin/mongod

ln -s /usr/local/lib/mongodb/bin/mongoexport  /usr/local/bin/mongoexport
ln -s /usr/local/lib/mongodb/bin/mongoimport  /usr/local/bin/mongoimport

ln -s /usr/local/lib/mongodb/bin/mongodump  /usr/local/bin/mongodump
ln -s /usr/local/lib/mongodb/bin/mongorestore  /usr/local/bin/mongorestore

# 创建数据和日志存放目录
mkdir /var/mongodb
mkdir /var/mongodb/data
mkdir /var/mongodb/logs

# 添加配置文件启动(/usr/local/lib/mongodb/conf/mongodb.conf)
cd /usr/local/lib/mongodb/
mkdir conf
cd conf
vi mongodb.conf

# 端口号
port=27017
# 数据库路径
dbpath=/var/mongodb/data
# 日志输出文件路径
logpath=/var/mongodb/logs/mongodb.log
pidfilepath=/var/mongodb/data/mongo.pid
# 设置后台运行
fork=true
# 日志输出方式(追加)
logappend=true
# auth (添加管理员用户再解开注释))
# auth=true

# 启动(手动)
mongod -f /usr/local/lib/mongodb/conf/mongodb.conf

# systemctl 启动
# 在/lib/systemd/system/目录下新建mongodb.service文件
cd  /lib/systemd/system/
vi mongodb.service

# -------------------------------------------------------
[Unit]

Description=mongodb
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
ExecStart=/usr/local/lib/mongodb/bin/mongod --config /usr/local/lib/mongodb/conf/mongodb.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/usr/local/lib/mongodb/bin/mongod --shutdown --config /usr/local/lib/mongodb/conf/mongodb.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target
# -------------------------------------------------------

# 设置权限
chmod 754 mongodb.service

#启动关闭服务,设置开机启动

# 可能会提示执行如下命名,执行即可
systemctl daemon-reload

#启动服务
systemctl start mongodb.service
#关闭服务
systemctl stop mongodb.service
#开机启动
systemctl enable mongodb.service

# 阿里云配置安全组,以便客户端(Robo 3T)连接线上服务器
# 修改mongodb.conf
# 远程访问地址
bind_ip=0.0.0.0
# 重启mongdodb(根据自己设置的重启方式重启)即可
# 例:
#关闭服务
systemctl stop mongodb.service
#启动服务
systemctl start mongodb.service

# 查看mongodb占用
ps -aef | grep mongo
pstree -p | grep mongo
pstree -p | grep mongod
# admin
db.createUser({ user: "duang", pwd:"2019", roles:[{ role: "userAdminAnyDatabase", db: "admin" }] })

# 某个数据库 //读写权限
db.createUser({ user: "duang", pwd:"2019", roles:[ { role: "readWrite", db: "card" }] })

mongodb 数据备份导入导出

# 导出
mongodump -h localhost:27017 -d card -o ./dump

# 导入
mongorestore -h localhost:27017 -d card-test --dir ./dump/card

# -h 设置服务器地址即可直接导出线上到本地
  • mongoexport, mongoimport
# 导出
mongoexport -d card -c users -o ./users.json
# 导入
mongoimport -d card -c users --file ./users.json

不要把 mongoimport,mongoexport 用于 生产环境的备份

  • 因为 mongoimport,mongoexport 只是导入导出 JSON 格式数据
  • 无法支持和保留所有的类型的数据
  • Mongo 内部完整的数据是用 BSON 格式去表示的,而 JSON 只是 BSON 的子集,所以有些数据格式,JSON 无法表示出来

nginx 安装、配置、域名绑定

注意阿里云上需要配置安全组,不然端口无法使用

安装

# 安装 nginx 卸载[yum -y remove nginx]
yum -y install nginx
# 启动nginx
systemctl start nginx.service
# 访问 默认端口 80
# 45.45.45.169:80

# 配置 nginx
cd /etc/nginx/conf.d
# server {
#     listen       3000; // 服务器地址:3000
#     server_name  localhost; // localhost 或者 域名
#     root         /home/html; // 静态资源文件
# }

# 以下为nginx常用命令
# 启动nginx服务
systemctl start nginx.service
# 停止nginx服务
systemctl stop nginx.service
# 重启nginx服务
systemctl restart nginx.service
# 重新读取nginx配置(这个最常用, 不用停止nginx服务就能使修改的配置生效)
systemctl reload nginx.service
  • 相关启动命令也可如下
nginx -t   						#测试配置文件是否有语法错误
nginx -s reopen					#重启Nginx
nginx -s reload					 #重新加载Nginx配置文件,然后以优雅的方式重启Nginx
nginx -s stop  					#强制停止Nginx服务
nginx -s quit  						#优雅地停止Nginx服务(即处理完所有请求后再停止服务)
nginx -c [配置文件路径]       #为 Nginx 指定配置文件

配置静态资源

/etc/nginx/conf.d目录下创建 nginx 配置

vim card-3000.conf

server {
  listen  3000;
  server_name localhost;

  location / {
    root /home/card;
    index index.html index.htm;
  }
}

配置接口转发

vim card-server-3001.conf 访问api.abc.cn/card都转发到3001端口

server {
    listen 80;
    server_name api.abc.cn;

    location /card {
      # 允许跨域
      # add_header 'Access-Control-Allow-Origin' '*';
      # add_header 'Access-Control-Allow-Credentials' 'true';
      # add_header 'Access-Control-Allow-Methods' 'OPTION, POST, GET';
      # add_header 'Access-Control-Allow-Headers' 'X-Requested-With, Content-Type';

      # value for proxy_pass has to match upstream name
      proxy_pass http://127.0.0.1:3001;
      # proxy_redirect off;
    }
}
  • card 配置
server {
    listen 80;
    server_name api.abc.cn;

    location / {
      # 接口服务允许跨域配置
      # 是否允许请求带有验证信息
      add_header Access-Control-Allow-Credentials true;
      # 允许跨域访问的域名,可以是一个域的列表,空格隔开,也可以是通配符*(不建议)
      add_header Access-Control-Allow-Origin  http://card.abc.cn;
      # 允许使用的请求方法,以逗号隔开,可以用 *
      add_header Access-Control-Allow-Methods 'POST,GET,OPTIONS,PUT,DELETE';
      # 预检命令的缓存,如果不缓存每次会发送两次请求,单位为秒。
      # 第一次是浏览器使用OPTIONS方法发起一个预检请求,第二次才是真正的异步请求
      add_header Access-Control-Max-Age 3600;

      # 允许脚本访问的返回头,在koa-cors也做了限制
      add_header Access-Control-Allow-Headers 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Requested-With';

      # 允许自定义的头部,以逗号隔开,大小写不敏感
      add_header Access-Control-Expose-Headers 'WWW-Authenticate,Server-Authorization';

      if ($request_method = 'OPTIONS') {
        add_header Access-Control-Allow-Origin "*";
        add_header Access-Control-Allow-Methods "POST, GET, PUT, OPTIONS, DELETE";
        add_header Access-Control-Max-Age "3600";
        add_header Access-Control-Allow-Headers "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Requested-With";
        add_header Content-Type text/plain;
        return 200;
      }
    }
    location /card {
      # value for proxy_pass has to match upstream name
      proxy_pass http://127.0.0.1:3001;
    }
}
  • 隐藏版本号Response Headers -> Server: nginx/1.16.1

    vi /etc/nginx/nginx.conf

    server_tokens off;
    
  • 参考以下配置

# 开启gzip 压缩
gzip on;
# 设置gzip所需的http协议最低版本 (HTTP/1.1, HTTP/1.0)
gzip_http_version 1.1;
# 设置压缩级别,压缩级别越高压缩时间越长  (1-9)
gzip_comp_level 4;
# 设置压缩的最小字节数, 页面Content-Length获取
gzip_min_length 1000;
# 设置压缩文件的类型  (text/html)
gzip_types text/plain application/javascript text/css;

知识拓展

linux 命令

# 拷贝
cp ./README.md ./copy/
# 拷贝并重命名
cp ./README.md ./copy/README.copy.md

# 删除
rm a.html

# 移动
mv ./README.md ./copy/
# 移动并重命名
mv ./README.md ./copy/README.copy.md
# 重命名
mv ./README.md ./README.new.md

linux tar压缩与解压

  • tar
  • 压缩率对比:xz > bzip2 > gzip
  • ls -lh可以列出文件大小
参数:

-c :create 建立压缩档案的参数;

-x : 解压缩压缩档案的参数;

-z : 是否需要用gzip压缩;
  z     用于gzip压缩:      filename.tar.gz
  j     用于bzip压缩:      filename.tar.bz2
  J     用于xz压缩:        filename.tar.xz

-v: 压缩的过程中显示档案;

-f: 置顶文档名,在f后面立即接文件名,不能再加参数
  • 常用命令
# 将所有.jpg的文件打成一个名为all.tar的包。
# -c是表示产生新的包 ,-f指定包的文件名。
tar -cf all.tar *.jpg

# 将所有.gif的文件 增加 到all.tar的包里面去。
# -r是表示增加文件的意思
tar -rf all.tar *.gif

# 更新原来tar包all.tar中logo.gif文件,
# -u是表示更新文件的意思。
tar -uf all.tar logo.gif

# 列出all.tar包中所有文件,
# -t是列出文件的意思
tar -tf all.tar

# 解出all.tar包中所有文件,
# -x是解开的意思
tar -xf all.tar

# 压缩
# 仅压缩不打包,将images文件夹全部打包
tar -cvf /home/www/images.tar /home/www/images
# 打包并压缩[以gzip压缩方式]],将images文件夹全部打包并压缩
tar -zcvf /home/www/images.tar.gz /home/www/images

# 排除文件压缩
tag -zcvf /home/www/images.tar.gz --exclude=/home/www/images/test /home/www/images

# 解压
# 解压到/home/abc目录
cd /home/abc
tar -zxvf /home/www/images.tar.gz

# 解压到指定目录
tar -zxvf /home/www/images.tar.gz -C /home/abc

进程相关

# 查看内存使用情况
free -m
free -h

# 查看资源消耗
top


scp本地文件上传服务器

上传方式:

  1. 命令行上传
  2. 客户端软件上传
  3. git 同步
# 上传-到指定目录
scp ./card-server.tar.gz root@109.99.98.88:/home/card-server

# 下载-到指定目录
scp root@109.99.98.88:/home/card/card-server.tar.gz ./

shell 脚本编写

数据库备份

  • 定时任务,本地备份完同步到七牛云存储(nodejs 与 shell)
    • shell 执行打包压缩
    • nodejs 调用
    • nodejs 实现上传到七牛云存储
  • shell脚本 or node版本的shelljs库
    • shell 执行 nodejs(或 nodejs 执行 shell)
  • crontab -e 定时任务(或 node-schedule 定时执行)
    # 定时备份:使用日期date+"%m-%d-%y"  =>  放在目录/var/backups/01-20-16/中
    3 3 * * * mongodump --out /var/backups/mongobackups/`date +"%m-%d-%y"`;
    # 定时删除:
    3 1 * * * find /var/backups/mongobackups/ -mtime +7 -exec rm -rf {} \;
    
  • 具体实现方案如下

自动化部署,持续集成

webhook(gitee,github)

  • 资料参考

  • 同一服务多个 webhook

  • gitee-webhook-handler.js

    var http = require('http')
    var createHandler = require('gitee-webhook-handler')
    var handler = createHandler({ path: '/webhook', secret: '123456' })
    
    http
      .createServer(function(req, res) {
        handler(req, res, function(err) {
          res.statusCode = 404
          res.end('no such location')
        })
      })
      .listen(3200)
    
    handler.on('error', function(err) {
      console.error('Error:', err.message)
    })
    
    handler.on('Push Hook', function(event) {
      console.log(
        'Received a push event for %s to %s',
        event.payload.repository.name,
        event.payload.ref
      )
    })
    
    handler.on('Issue Hook', function(event) {
      console.log(
        'Received an issue event for %s action=%s: #%d %s',
        event.payload.repository.name,
        event.payload.action,
        event.payload.issue.number,
        event.payload.issue.title
      )
    })
    
  • 一个服务,多个 webhook

    var http = require('http')
    var createHandler = require('node-github-webhook')
    var handler = createHandler([
      // 多个仓库
      {
        path: '/app1',
        secret: 'CUSTOM'
      },
      {
        path: '/app2',
        secret: 'CUSTOM'
      }
    ])
    // var handler = createHandler({ path: '/webhook1', secret: 'secret1' }) // 单个仓库
    
    http
      .createServer(function(req, res) {
        handler(req, res, function(err) {
          res.statusCode = 404
          res.end('no such location')
        })
      })
      .listen(7777)
    
    handler.on('error', function(err) {
      console.error('Error:', err.message)
    })
    
    handler.on('push', function(event) {
      console.log(
        'Received a push event for %s to %s',
        event.payload.repository.name,
        event.payload.ref
      )
      switch (event.path) {
        case '/app1':
          runCmd(
            'sh',
            ['./app1_deploy.sh', event.payload.repository.name],
            function(text) {
              console.log(text)
            }
          )
          break
        case '/app2':
          runCmd(
            'sh',
            ['./app2_deploy.sh', event.payload.repository.name],
            function(text) {
              console.log(text)
            }
          )
          break
        default:
          // 处理其他
          break
      }
    })
    
    function runCmd(cmd, args, callback) {
      var spawn = require('child_process').spawn
      var child = spawn(cmd, args)
      var resp = ''
      child.stdout.on('data', function(buffer) {
        resp += buffer.toString()
      })
      child.stdout.on('end', function() {
        callback(resp)
      })
    }
    
  • node 执行 shell 命令

    const { exec } = require('child_process')
    exec(`rm ./abc.tar.gzp`, err => {
      if (err) {
        return console.log(err)
      }
      console.log('ok???')
    })
    

持续集成Travis_Ci

docker

SSL 证书

SSL 安装-踩坑之旅

备案

推荐阿里云手机端,备案较为方便

参考资料