从零搭建 ubuntu 服务器及 nodejs项目部署

大概好几年前写的了,现在搬到博客上,防止丢失😄

**目标:**nodejs服务器配置及本地项目部署到线上的每一个业务流程,把本地可以跑通的nodejs项目,无论是微信小程序的后台,微信公众号的后台,还是手机app的后台,或是网站后台,都可以部署到线上,如果依赖的有数据库(mongodb),以及支持https服务,都可以做到!

**服务器版本:**阿里云ECS ubuntu 14.04 64位,1核1G大概能提供2-3w的请求量

服务器地址: 60.205.249.111

服务器用户: captainjack

一、流程

  • 域名: 购买域名 -> 域名备案
  • 购买服务器 -> 阿里云ECS -> 服务器版本选择ubuntu 14.04 64位
  • 配置新用户,禁用root及默认端口
  • 配置ssh本地到服务器之间链接(为了不泄露个人信息,或者伤害到服务器数据)
  • 服务器安装IPtables和Fail2Ban等安全防护
  • 安装NVM,配置nodejs
  • 安装配置Mysql
  • 配置Nginx前置服务
  • git私有仓库,服务器到git中部署资源
  • PM2进行远程部署,去git仓库自动获取资源
  • 配置多个项目:分配不同的服务器端口,让NGINX当服务器的总管,只有它一个人持有80端口,所有的访问,都得先过NGINX这一关,进行相应的端口转发
  • 域名解析
tip: 配置服务器的时候,多开几个终端,防止配错了无法更改

二、配置远程登录服务器

2-1 配置root及应用权限账号

初始服务器登录

# 默认用户名root,默认端口22
ssh [email protected]

目标:**

  • 添加用户captainjack,使新用户拥有root权限,将新用户添加到root组中, root的权限过高,可能不小心执行了危险操作,所以有必要用相对低权限的用户来操作没那么敏感的内容
  • 禁用root账号
  • 禁用服务器默认22端口,改为自定义端口号

添加用户,将用户加到root组中

# 根目录下添加用户
adduser captainjack

添加captainjack用户到sudo组中

gpasswd -a captainjack sudo

使用户captainjack 拥有root权限

sudo visudo

在User privilege specification下面,添加如下权限,总之一句话,只要提供密码,captainjack可以通过sudo来运行任何root用户可以运行的命令

# 第一个ALL是这条规则对所有sudo生效
# 第二个ALL是captainjack可以以任何的用户来执行命令
# 第三个ALL是captainjack可以以任何的组来执行命令
# 第四个ALL是这个规则适用于所有命令
captainjack ALL=(ALL:ALL) ALL

tips: 上述如果失败,可执行 service ssh restart 可重启ssh服务

2-2 配置mac本地ssh

由于已安装 zsh 工具

# 打开.zshrc配置文件
subl .zshrc

.zshrc 文件中配置软连接,这样就不需要记录主机ip了

# .zshrc文件中添加一样
alias shell-root="ssh [email protected]"

重载zsh

source .zshrc

这样配置之后,如果有多台服务器,还需要重新输入密码,多个密码容易忘记,所以有必要通过私钥认证的方式配置无密码登录

2-3 配置本地无密码ssh登录

原理

将mac本地的公钥,上传到服务器的authorized_keys授权文件中,通过密钥算法比对来判断是否是合法的用户登录。

mac本地过程

本地开启ssh代理

eval "$(ssh-agent -s)"

mac本地生成密钥对

cd ~/.ssh
# 可自定义rsa名称,方便识别用途
ssh-keygen -t rsa -b 4096 -C "[email protected]"

将新密钥加入代理中

ssh-add ~/.ssh/id_rsa

服务端ssh配置过程

  • 登录指定captainjack用户
  • 服务端生成ssh密钥过程同上
  • 如果遇到 sudo: unable to resolve host iZ2ze1m6ij6zkr3f0fljmaZ ,则是服务器主机名iZ2ze1m6ij6zkr3f0fljmaZ未解析到127.0.0.1,详见 https://coding.imooc.com/learn/questiondetail/17395.html 评论答案

重启ssh服务

sudo service ssh restart

授权authorized_keys文件

chmod 600 authorized_keys

在.ssh目录,编辑/新建authorized_keys授权文件

# .ssh目录下, 将mac本地生成的rsa公钥(.pub)内容,粘贴到authorized_keys文件中
vim authorized_keys

binggo! 直接在本地shell,输入jump-jack即可实现ssh无密码登录服务器

三、增强服务器安全等级

3-1 修改服务器默认登录端口22

默认的服务器的22登录端口有安全隐患,修改可以缩小被扫描和猜测的概率

# 1- 修改sshd_config文件
sudo vi /etc/ssh/sshd_config
# 2- 将22端口修改为自定义端口
Port 3001
# 3- 在sshd_config文件的最后,增加一行
AllowUsers captainjack
# 4- 重启ssh服务
sudo service ssh restart

注意,最后,在阿里云安全组里面,将新改动的端口3001,加入安全组里面 实例 -> 安全组规则 -> 手动添加

修改软连接端口号,通过ssh -p 56666 [email protected] 或者软连接shell-jack即可无密码登录服务器

3-2 关闭服务器的root密码登录

关闭原因:所有人都知道最高权限的登录名都是root,别人可以扫描root下的所有端口,会有安全风险

进入ssh配置文件,找到最下面的 PermitRootLogin,改为no,这样就禁止掉了root的用户登录

为什么可以关闭root用户登录呢?因为我们现在的captainjack在root组里面,root可以做的事情,通过captainjack都可以可以做

# /etc/ssh/sshd_config文件中
PermitRootLogin no

PasswordAuthentication从yes改为no,是否允许用户密码登录/授权,因为我们已经配置好了无密码登录,通过公钥/私钥比对就可以直接登录了,就不需要密码登陆了

# /etc/ssh/sshd_config文件中
PasswordAuthentication no

重启ssh服务

sudo service ssh restart

验证:

  • 去验证root的22端口(默认就是22,所以不用加端口号访问)登录, ssh [email protected], 会得到如下信息:ssh: connect to host 60.205.249.111 port 22: Connection refused
  • ssh -p 30001 [email protected] 去验证root用户登录, 会得到如下信息:Permission denied (publickey).

binggo!既禁用了22端口登录,又禁用了root用户登录!root就不可用啦

3-3 配置iptables和Fail2Ban增强安全防护

iptables: 是一款允许灵活配置安全策略的防火墙的框架(防火墙):IP 信息包过滤和防火墙配置,通过设置一些出入的规则,筛选掉一些恶意或可以的访问或流量,留出安全的通道对外提供web服务

Tips: 由于阿里云增加了安全组的配置,所以可以不添加iptables规则了

-> sudo apt-get update && sudo apt-get upgrade  升级ubuntu服务
-> sudo iptables -F  清空所有iptables规则(其实默认没有,这里保险起见)
-> sudo vi /etc/iptables.up.rules   配置iptables规则
    *filter
    # 允许所有建立起来的连接
    # allow all connections
    -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

    # 允许所有出去的规则
    # all http https
    -A OUTPUT -j ACCEPT
    # 允许https请求协议下的连接
    -A INPUT -p tcp --dport 443 -j ACCEPT
    # 所有的网站访问服务器,理论上都是从80端口进入,让80端口流量进出
    -A INPUT -p tcp --dport 80 -j ACCEPT

    # 为ssh登录方式建立通道(注意:56666为修改后的端口值)
    # allow ssh port login
    -A INPUT -p tcp -m state --state NEW --dport 56666 -j ACCEPT

    # 允许外网从某台服务器ping到这台服务器,方便测试服务器是否出故障
    # allow ping
    -A INPUT -p icmp --icmp-type 8 -j ACCEPT

    # 记录下来被拒绝的请求
    # log denied calls
    -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied:" --log-level 7

    # 对于恶意的敏感的 访问ip的规则进行拦截,如果某个IP对服务器的80端口在60s之内发送了超过150次请求,就认为是一个敏感的访问,予以拦截
    # drop incoming sensitive connections
    -A INPUT -p tcp --dport 80 -i eth0 -m state --state NEW -m recent --set
    -A INPUT -p tcp --dport 80 -i eth0 -m state --state NEW -m recent --update --seconds 60 --hitcount 150 -j DROP

    # 拒绝所有其他的进到服务器的流量
    # 如果有多台服务器,都是在内网,在这里需要开放数据库或其他的第三方端口的访问,那这里需要另行添加外网或某个iP端访问
    # reject all other inbound
    -A INPUT -j REJECT
    -A FORWARD -j REJECT

    COMMIT

-> 写好iptables.up.rules规则后,要告诉iptables 配置文件在哪里
    sudo iptables-restore < /etc/iptables.up.rules

-> 查看防火墙是否被成功启动
    sudo ufw status
    返回的是inactive(未激活的),来激活它

->  sudo ufw enable 激活防火墙,返回信息如下:
    Firewall is active and enabled on system startup

 -> sudo ufw status 返回状态为active,即激活了
 -> 设置防火墙开机启动(可能会有停机之类的操作)
    sudo vi /etc/network/if-up.d/iptables
 -> 在文件中写入:
        #!/bin/sh
        iptables-restore /etc/iptables.up.rules
-> 给予脚本执行的权限
    sudo chmod +x /etc/network/if-up.d/iptables
即配置好了iptables,接着配置Fail2Ban安防模块

Fail2Ban:
    通过监控系统的日志文件,根据检测到的任何的可疑行为,触发不同的行为动作
    -> 安装:
        sudo apt-get install fail2ban
    -> 打开配置文件
        sudo vi /etc/fail2ban/jail.conf
        57行的邮箱,改成自己的邮箱
        102行: action_改成action_mw
    -> 查看fail2ban运行情况
        sudo service fail2ban status
    (如果要停止fail2ban: sudo service fail2ban stop)
    (如果要开始fail2ban: sudo service fail2ban start)

综上,都是一些简单的基础配置,但是比”裸“的服务器安全的多了

四、搭建Nodejs生产环境

4-1 搭建服务器的nodejs环境

-> 开始之前,最好先更新一下服务:
    sudo apt-get update
-> 安装一下相关的模块(以备后面使用)
    sudo apt-get install vim openssl build-essential libssl-dev wget curl git
-> 使用nvm工具升级管理nodejs不同的版本,安装nvm
    curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash
-> 通过nvm安装nodejs,nodejs版本为6.9.5
    nvm install v6.9.5
-> 默认让系统里面的node版本是6.9.5
    nvm alias default v6.9.5
-> 由于我们的服务器是在国内,由于众所周知的原因,国内连npm有时候会很慢或者下载不下来,指定使用国内的淘宝镜像来下载npm包
    npm --registry=https://registry.npm.taobao.org install -g npm
-> 增加系统文件监控数目
    echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
-> 采用cnpm 替代 npm
    npm --registry=https://registry.npm.taobao.org install -g cnpm

node 安装完毕

安装一下常用的工具包 pm2 webpack gulp grunt-cli
    cnpm i pm2 webpack gulp grunt-cli -g

-> 新建一个app.js,测试用
    vi app.js
    文件内容如下:
    const http = require('http')
    http.createServer(function(req, res){
            res.writeHead(200, {'Content-type':'text/plain'})
            res.end('somewhere called laputa')
    }).listen(8085)
    console.log('server running on http://60.205.249.111/')
    注意,直接用node app.js可以启动,但是防火墙中,没有添加8085端口,需添加。。。
    注意,直接用node app.js虽然可以启动,但是一旦关闭命令行,或者超时,服务就会关闭
-> 防火墙添加端口
    进入文件临时增加一个8085端口
        sudo vi /etc/iptables.up.rules
    增加端口之后,重新应用一下规则
        sudo iptables-restore < /etc/iptables.up.rules 

现在就可以通过ip+端口号访问:即
    http://60.205.249.111:8085/
由于我之前已经做过域名解析,下面的地址也可访问
    http://www.laputa.pub:8085/
等通过Nginx配置之后,可以让服务跑在80端口,就可以不用加端口号访问了,只用ip或者域名就可以访问,毕竟加端口号访问,不安全也不友好

4-2 借助pm2让nodejs服务常驻

静态站点开起来,有两个条件必须有:
    1、持续,稳定的对外提供web服务
    2、可以从外网通过80端口访问到
上述通过命令行启动node服务,无法常驻,显然不行
可以通过pm2实现服务后台常开,并且出现异常之后自动重启

pm2: nodejs进程部署和管理工具
-> pm2 app.js   开启服务,开启之后即会常驻,oyeah!
-> pm2 list     可以列出当前服务器上运行的node服务列表
-> pm2 show app 可以显示详细的node服务信息
-> pm2 logs     可查看日志信息

五、Nignx实现反向代理Nodejs端口

目的: 让外网可以通过80端口访问到这台服务器,而不需要加上8085端口号
用root级别的权限,启动对80端口的监听,把来自80端口的流量分配给node服务的8085端口,实现服务的代理

安装nginx:
    刚购买的服务器可能预装的有apache服务,一般也用不到,先予以删除
-> 停止apach服务,可能会没有
    sudo service apach2 stop
    update-rc.d -f apache2 remove 
-> 移除apache
    sudo apt-get remove apache2
这样apache就被清干净了

-> 更新一下包列表
    sudo apt-get update
-> 安装nginx
    sudo apt-get install nginx
-> 进入nginx目录下
    cd /etc/nginx
    cd conf.d
    -> 在conf.d文件夹下新建一个文件
        sudo vi laputa-pub-8085.conf
        文件命名tip:域名-端口号,注意,这样有多个服务时命名方便识别
        文件内容如下:
        upstream laputa {
          server 127.0.0.1:8085;
        }
        server {
          listen 80;
          server_name 60.205.249.111;
          location / {
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-Nginx-Proxy true;
            proxy_pass http://laputa;
            proxy_redirect off;
          }
        }
    -> 配置nginx.conf(此时无需配置,可忽略)
        sudo vi nginx.conf
    -> 检查nginx配置的是否正确
        sudo nginx -t
        出现test is successful返回值时,即说明配置正确
    -> 重启nginx
        sudo nginx -s reload
        重新访问IP地址http://60.205.249.111,无需加端口,即可实现服务器80端口代理访问
    -> 考虑到安全,最好不要在nginx头信息中有版本信息
        第21行 取消server_tokens的注释
        重启nginx即可

六、服务器安装配置MongoDB

9.1 在Ubuntu 14.04上安装MongoDB

公司商用的话,可以考虑阿里云的数据库服务,个人的话,自己配置即可
目前数据库和应用跑在同一台服务器里面,正规的应该分开

-> 如下地址里面有安装方法:
    https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
-> 导入public key
    sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
-> 为mongodb的配置文件创建列表
    echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
-> 更新本地包
    sudo apt-get update
-> Install the MongoDB packages安装MongoDB packages
    sudo apt-get install -y mongodb-org
    由于mongodb原始配置文件的下载源比较慢,所以更改配置文件,替换成阿里云的地址
    -> sudo vi /etc/apt/sources.list.d/mongodb-org-3.4.list
        http://repo.mongodb.org/apt/ubuntu改成如下阿里云
        http://mirrors.aliyun.com/mongodb/apt/ubuntu
    -> 重新更新本地包
        sudo apt-get update
    -> 重新安装MongoDB packages
       sudo apt-get install -y mongodb-org 

-> 开启数据库
    sudo service mongod start
    会有如下返回信息
        start: Job is already running: mongod
    -> 检查有没有开启成功
        mongo
        返回:connect failed
        原因:防火墙没有开启对数据库的27017的端口
        -> 防火墙配置
            sudo vi /etc/iptables.up.rules
            在配置文件中添加:
                # mongodb connect
                -A INPUT -s 127.0.0.1 -p tcp --destination-port 27017 -m state --state NEW,ESTABLISHED -j ACCEPT
                -A OUTPUT -s 127.0.0.1 -p tcp --source-port 27017 -m state --state ESTABLISHED -j ACCEPT
            配置完之后,重置防火墙
                sudo iptables-restore < /etc/iptables.up.rules
            成功!
    -> 连接数据库,
        mongo
        可以进到mongodb的命令行之中

    如果要停止mongodb服务:
        sudo service mongod stop
    如果要开启mongodb服务:
        sudo service mongod start
    如果要重启mongodb服务:
        sudo service mongod restart

mongodb数据库的默认端口是27017,出于安全考虑,更改一下默认端口:
    -> 进入配置文件:
        sudo vi /etc/mongod.conf
        port修改为58888
        注意:更改完端口之后,
        要把端口加到防火墙里面,
            sudo vi /etc/iptables.up.rules
        再重启防火墙服务
            sudo iptables-restore < /etc/iptables.up.rules
    -> 重启数据库
        sudo service mongod restart
    -> 由于更改了端口,访问数据库的时候,要加上端口访问
        mongo --port 58888

七、向服务器正式部署和发布上线Nodejs项目

10.1 上传项目代码到线上私有git仓库-码云 git.oschina.net

把服务器的id_rsa.pub添加到git私有仓库-码云

10.2 PM2 一键配置线上项目结构

普通项目的话,pm2就够用了,pm2可以:
    - 守护服务器的nodejs服务
    - 也可以实现平滑重启
    - 代码的自动更新
    - 从本地到线上的自动部署

-> 项目中建一个ecosystem.json的pm2的配置文件
    {
        "apps": [
            {
                // 部署的应用的名字
                "name": "TestDeploy",
                // 启动的脚本 - 即项目的入口文件
                "script": "app.js",
                // 启动的时候,要传进去的变量
                "env": {
                    "COMMON_VARIABLE": "true"
                },
                // 生产环境的变量
                "env_production": {
                    "NODE_ENV": "production" // 设置值为生产环境
                }
            }
        ],
        // 配置部署任务
        "deploy": {
            // 任务的名字,设置为production,可随意设置
            "production": {
                // 设置user为服务器上用来发布应用的用户
                "user": "captainjack",
                // 主机
                "host": ["60.205.249.111"],
                // 端口
                "port": "56666",
                "ref": "origin/master",
                // git仓库地址
                "repo": "https://git.oschina.net/laputagit/test-deploy.git",
                // 把服务器部署到服务器的哪个目录
                "path": "/www/TestDeploy/production",
                // 取消ssh校验
                "ssh_options": "StrictHostKeyChecking=no",
                "env": {
                    "NODE_ENV": "production"
                }
            }
        }
    }

-> 提交代码至git

-> 在服务器上
    sudo mkdir /www
    在/www中,新建TestDeploy文件夹
    sudo mkdir TestDeploy
    这样就生成了/www/TestDeploy文件夹,和上面的ecosystem.json文件中的path路径必须一致

-> 让pm2连上服务器
    pm2 deploy ecosystem.json production setup
    会出现如下报错信息(权限问题):
        mkdir: cannot create directory ‘/www/TestDeploy/production’: Permission denied
    有可能会出现的报错:
        fatal: could not read Username for 'https://git.oschina.net': No such device or address
        解决方法:更改ecosystem.json的repo为如下格式即可:
            http://yourname:[email protected]/name/project.git

    上述报错出现的原因:
        captainjack在根目录下,没有新建文件的权限

-> 切到服务器环境/www下,修改文件夹的权限
    sudo chmod 777 TestDeploy
    把这个文件夹,改成对captainjack来说,有可读可写可执行的权限

-> 重新执行上述操作,让pm2连上服务器
    pm2 deploy ecosystem.json production setup
    返回信息:success

服务器的TestDeploy文件夹下,会生成production文件夹,production文件夹下有
    current 当前服务所运行的文件夹
    shared  克隆下来的源代码
    source  日志之类的共享的数据
文件夹
到此,项目即在服务器处于ready的状态,下一步部署项目

10.3 从本地发布上线和更新服务器的Nodejs项目(目前是静态站点)

本地控制远端代码更新和重启

-> 本地(注意:是本地目录)项目目录下运行:
    pm2 deploy ecosystem.json production
    报错如下:
    --> Deploying to production environment
    --> on host 60.205.249.111
      commit or stash your changes before deploying
    Deploy failed
    
    executing post-deploy `pm2 startOrRestart ecosystem.json --env production`
    原因是:pm2在服务器上使用的是非交互的ssh链接方式,所以需要到服务器的环境,编辑根目录下的.bashrc
-> vi .bashrc
    把文件的第6-9行注释掉,保存
-> 重新加载.bashrc
    source .bashrc

这样项目即完成了线上部署
不过还有两件事要做
    1、需修改域名指向,目前还是不能通过域名的80访问

-> 修改域名指向
    sudo vi /etc/nginx/conf.d/laputa-pub-8085.conf
    把server_name改为www.laputa.pub
-> 重启nginx服务
    sudo nginx -s reload
    ok,现在试一下,可以域名直接访问了

本地代码改动,同步到线上

1、修改本地代码
2、上传代码至git
3、本地部署发布
    pm2 deploy ecosystem.json production
4、自动化推送代码到git仓库并发布到生产环境
    sh deploy.sh [commit文本]

​ Q&A