RabbitMQ 基础介绍

发布在 architecture

目录

  1. 一、什么是RabbitMQ
  2. 二、 RabbitMQ简介
    1. 2.1 术语
    2. 2.2 基本特性
  3. 三、 官方Demo与思考
    1. 3.1 Hello World
      1. 3.1.1 操作
      2. 3.1.2 思考
    2. 3.2 工作队列
      1. 3.2.1 操作
      2. 3.2.2 思考
    3. 3.3 发布/订阅
      1. 3.3.1 操作
      2. 3.3.2 思考
    4. 3.4 路由
      1. 3.4.1 操作
      2. 3.4.2 思考
    5. 3.5 主题交换机
      1. 3.5.1 操作
      2. 3.5.2 思考
    6. 3.6 远程过程调用RPC
      1. 3.6.1 操作
      2. 3.6.2 思考
  4. 四、 思考总结
  5. 五、 场景模拟

一、什么是RabbitMQ

RabbitMQ是一个消息代理。

RabbitMQ基于AMQP协议用Erlang编写。

什么是AMQP?

  • AMQP(高级消息队列协议)是一个网络协议。它支持符合要求的客户端应用(application)和消息中间件代理(messaging middleware broker)之间进行通信。

简单来说: MQ是邮递过程,寄信人去邮局把信件放入邮箱,邮递员就会把信件投递到收件人

二、 RabbitMQ简介

2.1 术语

术语 类比
生产者Producing 寄信人,信件的来源
消息Message 信件
交换机Exchange 邮局,信件分发的场所
队列Queue 邮箱,存储邮件
绑定Binding 邮局信件和邮箱之间的关系,邮局信件按收件省市划分后归纳到不同邮箱
消费者Consuming 收件人,信件的归宿

1

2.2 基本特性

  • 消息只存储在Quene中

    • 只有邮箱,也就是Queue具有存储功能,Exchange不能存储消息
  • RoutingKey

    • RoutingKey就是消息的收件地址
    • RoutingKey可以直接写成XXX格式,也可以写成XXX.XXX.XXX格式,也可以为空
  • 交换机有4种类型

    • Direct 直连交换机
    • Fanout 扇形交换机
  • Topic 主题交换机
  • Headers 头交换机

三、 官方Demo与思考

3.1 Hello World

2

3.1.1 操作

生产者:

1
2
3
4
5
6
7
8
- 连接MQ
- 申明Queue
MQ.Queue.Name = "hello"
- 发送信息
MQ.Publish
Exchange = ""
RoutingKey = "hello"
Body = "hello wolrd"

消费者:

1
2
3
4
5
6
7
- 连接MQ
- 申明Queue
MQ.Queue.Name = "hello"
- 消费信息
MQ.Consume
Queue.Name= "hello"
Auto ack = true //注意自动ack

3.1.2 思考

  • 为何没有给Exchange命名?Exchange是什么类型?
  • 为何在生产者和消费者都申明了Queue?
  • RoutingKey和Queue.Name 有什么关系?

3.2 工作队列

3

3.2.1 操作

生产者:

1
2
3
4
5
6
7
8
9
10
- 连接MQ
- 申明Queue
MQ.Queue.Name = "task_queue"
MQ.Queue.Durable = true //注意Queue持久化
- 发送信息
MQ.Publish
Exchange = ""
RoutingKey = "task_queue"
Body = "hello wolrd"
delivery_mode = persistent or 2 //注意消息持久化

多个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
- 连接MQ
- 申明Queue
MQ.Queue.Name = "task_queue"
MQ.Queue.Durable = true
- QoS
MQ.Qos
prefetch_count = 1 //注意QoS
- 消费信息
MQ.Consume
Queue.Name= "task_queue"
Auto ack = false //注意手动ack
- Sleep模拟处理事务
- 手动ack

3.2.2 思考

  • 多个消费者在Direct交换机,相同Queue下,如何接收消息?
  • 如何让Queue和消息持久化?
  • 可以直接给3.1中的hello队列赋予持久优设置吗?
  • 持久化有什么用?
  • 默认交换机是持久化的吗?
  • 手动ack和自动ack的区别?
  • 忘记ack怎么办?
  • QoS(Quality of Service)如何实现?

3.3 发布/订阅

4

3.3.1 操作

生产者:

1
2
3
4
5
6
7
8
9
10
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "logs"
MQ.Exchange.Type = "fanout"
MQ.Exchange.Durable = true
- 发送信息
MQ.Publish
Exchange = "logs"
RoutingKey = "" //注意RoutingKey
Body = "hello wolrd"

多个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "logs"
MQ.Exchange.Type = "fanout"
MQ.Exchange.Durable = true
- 申明临时Queue
MQ.Queue.exclusive= true //注意临时Queue
- 绑定
Binding //注意绑定
Exchange = "logs"
Queue.Name = MQ.Queue.Name
RoutingKey = ""
- 消费信息
MQ.Consume
Queue.Name = MQ.Queue.Name
Auto ack = false
- Sleep模拟处理事务
- 手动ack

3.3.2 思考

  • 扇形交换机的RoutingKey是如何配置的?
  • 扇形交换机的主要用途?
  • 什么是临时队列?
  • 临时队列与持久化可以同时设置吗?
  • 扇形交换机可以在生产者设置持久化的队列吗?

3.4 路由

5

6

3.4.1 操作

生产者:

1
2
3
4
5
6
7
8
9
10
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "direct_logs"
MQ.Exchange.Type = "direct" //注意Exchange类型
MQ.Exchange.Durable = true
- 发送信息
MQ.Publish
Exchange = "direct_logs"
RoutingKey = "info" or "error" or "warn" //注意RoutingKey
Body = "hello wolrd"

多个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "direct_logs"
MQ.Exchange.Type = "direct"
MQ.Exchange.Durable = true
- 申明临时Queue
MQ.Queue.exclusive= true
- 绑定
Binding
Exchange = "direct_logs"
Queue.Name = MQ.Queue.Name
RoutingKey = "info" or "error" or "warn"
- 消费信息
MQ.Consume
Queue.Name = MQ.Queue.Name
Auto ack = false
- Sleep模拟处理事务
- 手动ack

3.4.2 思考

  • 直连交换机多重绑定和扇形交换机有什么区别?
  • 直连交换机RoutingKey绑定有什么限制?

3.5 主题交换机

7

3.5.1 操作

生产者:

1
2
3
4
5
6
7
8
9
10
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "topic_logs"
MQ.Exchange.Type = "topic"
MQ.Exchange.Durable = true
- 发送信息
MQ.Publish
Exchange = "topic_logs"
RoutingKey = "this.abc" or "that.abc.xyz" or "abc" //注意RoutingKey写法
Body = "hello wolrd"

多个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "topic_logs"
MQ.Exchange.Type = "topic"
MQ.Exchange.Durable = true
- 申明临时Queue
MQ.Queue.exclusive= true
- 绑定
Binding
Exchange = "topic_logs"
Queue.Name = MQ.Queue.Name
RoutingKey = "*.abc" or "#.abc" or "*.abc.*" //注意binding写法
- 消费信息
MQ.Consume
Queue.Name = MQ.Queue.Name
Auto ack = false
- Sleep模拟处理事务
- 手动ack

3.5.2 思考

  • *# 的区别?
  • 绑定键为 * 的队列会取到一个路由键为空的消息吗?
  • a.*.#a.#的区别在哪儿?

3.6 远程过程调用RPC

8

3.6.1 操作

调用端(生产者):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 调用本机某方法
调用另一个服务的接口
- 连接MQ
- 申明临时Queue
MQ.Queue.exclusive= true //注意临时Queue,用于接收回传信息
- 消费信息
MQ.Consume
Queue.Name= MQ.Queue.Name
Auto ack = true
- 发送信息
MQ.Publish
Exchange = ""
RoutingKey = "rpc_queue" //注意发送信息的队列,是服务端的队列,不是上面的临时队列
CorrelationId: corrId, //唯一id,用以与回调结果匹配
ReplyTo: q.Name, //回传地址,用以服务端将结果返回
Body = "123456" //传输id
- 接收结果
匹配CorrelationId 和 Server.CorrelationId
获得信息

服务端(消费者):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 连接MQ
- 申明Queue
MQ.Queue.Name= "rpc_queue" //注意此队列和调用者的临时队列不同
- QoS
MQ.Qos
prefetch_count = 1 //注意QoS
- 消费信息
MQ.Consume
Queue.Name= MQ.Queue.Name
Auto ack = false
- 调用接口
查询id相关信息
- 发送信息
MQ.Publish
Exchange = ""
RoutingKey = Client.ReplyTo
CorrelationId = Client.CorrelationId
Body = id相关信息

3.6.2 思考

  • 上面例子中使用了临时队列和非持久化队列,会出现什么问题?
  • 如果服务器发生故障,并且抛出异常,应该被转发到客户端吗?
  • 当没有服务器运行时,客户端如何作出反映?

四、 思考总结

  • Q:为何没有给Exchange命名?Exchange是什么类型?

    • 没有命名的交换机是默认(匿名)交换机
    • 默认交换机是Direct类型
  • Q:为何在生产者和消费者都申明了Queue?

    • 声明交换机和队列只能生效一次
    • 由于生产者和消费者无法确定启动顺序,所以两处申明可以防止招不到交换机或队列造成消息丢失
  • Q:RoutingKey和Queue.Name 有什么关系?

    • 默认或者匿名交换机的消息将会根据指定的routing_key分发到指定的队列

  • Q:多个消费者在Direct交换机,相同Queue下,如何接收消息?

    • 轮询分发
  • Q:如何让Queue和消息持久化?

    • 对于交换机和队列,设置其Durable属性
    • 对于消息,设置其发送模式为Persistent(部分语言用编码2表示)
  • Q:可以直接给3.1中的hello队列赋予持久优设置吗?

    • 不可以,队列只可申明一次,改动属性会报异常
  • Q:持久化有什么用?

    • 持久化会使MQ服务退出后,以申明的交换机、队列不仍存在,队列内的数据仍存在
    • 但持久化不能保证所有的数据都不会丢失
  • Q:默认交换机是持久化的吗?

  • Q:手动ack和自动ack的区别?

    • 自动ack会在消费者收到消息时就自动发送确认
    • 手动ack需要消费者自己手动发送确认
    • 消息没有超时的概念
    • 当消息被RabbitMQ发送给消费者之后,马上就会在内存中移除
    • 自动ack场景下,当消费者执行一个费时任务时,MQ崩溃,会导致消息丢失
    • 手动ack场景下,当消费者执行一个费时任务时,MQ崩溃,消息会被重新推送
  • Q:忘记ack怎么办?

    • 忘记ack会让MQ不断重复发送信息,导致MQ内存增加
    • 可以在MQ中查询messages_unacknowledged字段,手动处理
  • Q:QoS(Quality of Service)如何实现?

    • 设置MQ的QoS相关配置
    • 设置prefetch_count = 1

  • Q:扇形交换机的RoutingKey是如何配置的?

    • 填写为空
  • Q:扇形交换机的主要用途?

    • 发送广播
  • Q:什么是临时队列?

    • 临时队列随机生成队列名称
    • 当与消费者断开连接的时候,这个队列应当被立即删除
  • Q:临时队列与持久化可以同时设置吗?

    • 不可以,消费者断开后随机队列就删除,所以一旦MQ退出,消费者就与MQ断开连接,随机队列就会删除,不能设置持久化
  • Q:扇形交换机可以在生产者设置持久化的队列吗?

    • 可以,以保证所有消息不丢失。

    • 但是要注意,一般扇形交换机每个消费者一般独占一条队列,如果多个消费者共用一条队列,会跟直连交换机一样进行轮询分发

    • 如果在生产者设置10个持久化队列,但有20个消费者,每个消费者独占一条队列,那么只有其中的10个可以获取全部信息,其他10个消费者无法使用

    • 如果在生产者设置10个持久化队列,但有20个消费者,每2个消费者共用一条队列,那么20个消费者将轮询消费信息,不能收到完整的全部的信息

    • 所以扇形交换机一般不按照上面的方式使用

  • Q:直连交换机多重绑定和扇形交换机有什么区别?

    • 扇形交换机不能通过RoutingKey过滤消息
  • Q:直连交换机RoutingKey多重绑定有什么限制?

    • 不能用通配符方式模糊过滤消息

  • Q: *# 的区别?

    • (星号) 用来表示一个单词
    • (井号) 用来表示任意数量(零个或多个)单词
  • Q:绑定键为 * 的队列会取到一个路由键为空的消息吗?

    • 不能,星号表示至少一个单词
  • Q:a.*.#a.#的区别在哪儿?

    • 前者不可以匹配 a
    • 后者可以匹配 a

  • Q:上面例子中使用了临时队列和非持久化队列,会出现什么问题?
    • 没启动服务端时,客户机消息丢失
    • 客户端发送给服务端后,客户端断开连接,会导致回传信息丢失
  • Q:如果服务器发生故障,并且抛出异常,应该被转发到客户端吗?
    • 一般来说不需要
  • Q:当没有服务器运行时,客户端如何作出反映?
    • 对于不重要的信息,采用临时队列,丢失消息即可
    • 对于重要信息,采用持久化队列,等待服务器处理,并根据实际情况定时处理(清除或消息补偿)

五、 场景模拟

  • 账号注册后一系列短信邮件通知
    • 使用扇形交换机,短信和邮件服务作为消费者申明临时队列
    • 使用Auto ack
  • 秒杀下单
    • 使用直连交换机,秒杀服务作为生产者,订单中心作为消费者,都需要申明持久化队列
    • 使用手动ack
  • 下单后支付并返回结果
    • 使用直连交换机,下单服务作为生产者,支付服务作为消费者,都需要申明持久化队列
    • 使用手动ack
    • 消息安全性要求很高,需要消息补偿机制
  • 用户下单(订单服务接收下单请求,异步调用仓库服务查询库存是否足够)
    • 使用RPC
    • 仓库服务申明异步调用队列 A
    • 订单服务申明回传队列 B
    • 订单服务将待查询的产品id和B的地址发送到A
    • 仓库服务从A中消费信息,处理订单服务请求,并把结果发送到B
    • 订单服务从B中消费信息,接收仓库服务的查询结果

评论和共享

目录

  1. 前言
  2. 一、安装Docker
    1. 1.1 关闭selinux
    2. 1.2 删除CentOS自带Docker
    3. 1.3 安装Docker CE
    4. 1.4 安装Docker Compose
  3. 二、安装NextCloud
    1. 2.1 使用docker-compose安装NextCloud
    2. 2.2 设置开机启动
  4. 三、Nginx反向代理
    1. 3.1 域名解析
    2. 3.2 申请CA证书
    3. 3.3 安装Nginx
    4. 3.4 配置Nginx
    5. 3.5 开放防火墙端口
    6. 3.6 端口转发
  5. 四、DDNS
    1. 4.1 创建阿里云AccessKey
    2. 4.2 安装
    3. 4.3 配置

前言

本文涉及的操作系统、软件平台和其他环境如下:

  • 云盘服务器
    • CentOS 7
    • WD红盘/阵列盒(可选)
    • 所在网络需要有公网IP
    • Docker CE
    • Docker Compose
    • Nginx with SSL
  • 阿里云
    • DNS
    • CA证书

一、安装Docker

1.1 关闭selinux

如果没有专业的运维,建议关闭selinux,以免后续配置引起冲突

修改” /etc/selinux/config “文件,设置SELINUX=disabled ,保存并重启服务器

1.2 删除CentOS自带Docker

CentOS 7自带了旧版本Docker,所以先删除,如果服务器已经安装Docker请谨慎执行!!!

1
sudo yum -y remove docker docker-common container-selinux

1.3 安装Docker CE

Docker分CE和EE两个版本,这里我们用开源免费的CE版即可

1
2
3
4
5
6
7
8
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce
# 启动docker
sudo systemctl start docker
# 查看版本确认是否安装成功
docker --version

1.4 安装Docker Compose

Docker Compose是用来管理和配置多个Docker的工具,后面我们会用到它来部署NextCloud

1
2
3
4
sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose
sudo chmod +x /usr/bin/docker-compose
# 查看版本确认是否安装成功
docker-compose --version

二、安装NextCloud

2.1 使用docker-compose安装NextCloud

这里使用的安装方式来自https://hub.docker.com/r/wonderfall/nextcloud/

有兴趣可以了解所有的配置和相关逻辑,下面仅列出使用方法:

  • 将存储盘或者本地磁盘的某个分区挂载到目录” /data “下
  • 在” /etc/nextcloud “目录下创建文件” docker-compose.yml “

    1
    2
    cd /etc/nextcloud
    vi docker-compose.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    version: '2'
    services:
    nextcloud:
    image: wonderfall/nextcloud
    links:
    - nextcloud-db
    - redis
    environment:
    - UID=1000
    - GID=1000
    - UPLOAD_MAX_SIZE=30G #单个文件上传限制
    - APC_SHM_SIZE=128M
    - OPCACHE_MEM_SIZE=128
    - CRON_PERIOD=15m
    - TZ=Asia/Shanghai #修改时区
    - ADMIN_USER=admin
    - ADMIN_PASSWORD=admin
    - DOMAIN=xxxx #需要设置的域名
    - DB_TYPE=mysql
    - DB_NAME=nextcloud
    - DB_USER=nextcloud
    - DB_PASSWORD=xxxx #数据库nextcloud用户密码
    - DB_HOST=nextcloud-db
    volumes:
    - /data/docker/nextcloud/data:/data # /data/docker/nextcloud/XX 是挂载卷的位置
    - /data/docker/nextcloud/config:/config
    - /data/docker/nextcloud/apps:/apps2
    - /data/docker/nextcloud/themes:/nextcloud/themes
    expose:
    - 8888
    nextcloud-db:
    image: mariadb:10
    volumes:
    - /data/docker/nextcloud/db:/var/lib/mysql
    environment:
    - MYSQL_ROOT_PASSWORD=xxxx #数据库root用户密码
    - MYSQL_DATABASE=nextcloud
    - MYSQL_USER=nextcloud
    - MYSQL_PASSWORD=xxxx #数据库nextcloud用户密码
    redis:
    image: redis:alpine
    container_name: redis
    volumes:
    - /data/docker/nextcloud/redis:/data

    上面的配置中使用了mysql作为数据库,redis作为缓存,加速同步效率,将容器的持久数据挂载到/data目录,没有使用默认启用的全文检索工具solr,没有将端口映射至宿主机。至于如何通过宿主机访问nextcloud,请参考第三章。

  • 执行docker-compose命令部署docker容器

    1
    2
    cd /etc/nextcloud
    docker-compose up -d
  • 查看是否部署成功

    1
    docker ps -a

    1

    如果三个容器的STATUS都是UP,证明容器启动成功

  • 查看nextcloud容器ip

    1
    docker inspect root_nextcloud_1

    2

    如上图所示,查询并记录nextcloud容器的ip,供第三章使用

2.2 设置开机启动

  • 编辑 /etc/rc.local

    1
    vi /etc/rc.local
    1
    2
    3
    service docker start
    cd /etc/nextcloud
    docker-compose start
  • 赋予可执行权限

    1
    chmod +x /etc/rc.d/rc.local

三、Nginx反向代理

3.1 域名解析

将第二章docker-compose.yml 配置中的域名解析到服务器公网IP

3

3.2 申请CA证书

有条件可以购买收费CA证书,这里使用了阿里云的免费证书,现在(2017.12)阿里云刻意“隐藏”了免费证书的位置,按以下操作可以找到:

  • 保护类型选择“1个域名”
  • 选择品牌先选择赛门铁克Symantec
  • 这时候才能看到证书类型出现“免费型DV SSL”

4

购买证书后需要进行验证,通过后才能下载使用

5

下载证书请选择for Nginx,解压将其中两个文件(key和pem)拷贝至服务器” /etc/cert “目录

6

3.3 安装Nginx

如果已经安装Nginx可以跳过该步骤,否则可以执行下面的命令安装nginx

1
2
3
4
5
6
7
8
# 设置rpm源
sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
# 安装nginx
sudo yum install -y nginx
# 开机自启动nginx
systemctl enable nginx
# 启动nginx
systemctl start nginx

3.4 配置Nginx

1
vi /etc/nginx/nginx.conf

修改配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
proxy_buffering off; #关闭代理缓存
server {
listen 23456 ssl ; #监听端口为23456,并启用ssl
server_name pan.xxx.com; #域名
ssl_certificate /etc/cert/xxxxx.pem; #pem文件路径
ssl_certificate_key /etc/cert/xxxxx.key; #key文件路径
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://172.18.0.4:8888; #填写nextcloud容器的ip,端口为8888
client_max_body_size 30000m; #允许用户上传文件的大小修改成30G
}
}
}

重启nginx

1
systemctl restart nginx

3.5 开放防火墙端口

执行命令:

1
2
firewall-cmd --zone=public --add-port=23456/tcp --permanent
firewall-cmd --reload

打开23456端口以免访问受限。

3.6 端口转发

最后,我们还需要把路由器的23456端口转发至NextCloud服务器(如192.168.2.254),路由器不同,配置方式也不同,下图是H3路由器配置方式:

10

到这里,我们就可以通过 https://pan.XXX.com:23456 访问NextCloud了,需要注意我们只配置了Nginx监听SSL,也就是输入URL时,不要忘记是https

需要提醒的是,如果服务器网络是动态IP,还需做DDNS,否则IP更换后,将不能通过域名访问NextCloud

四、DDNS

由于我们想用公司自己的二级域名,又有服务器,还有阿里云的SDK,所以我们没有使用花生壳等第三方解决方案,这里使用DDNS的方式来自https://github.com/rfancn/aliyun-ddns-client

4.1 创建阿里云AccessKey

  • 进入阿里云访问控制页面,新建domain用户,并自动生成AccessKey,确认后记录access_key和access_id,注意保密

    7

  • 分配域名管理权限给domain用户

    8

4.2 安装

  • 安装python的requests包

    1
    2
    3
    yum -y install epel-release
    yum install python-pip
    pip install requests
  • 下载源码到 /usr/local

    1
    2
    cd /usr/local
    wget https://github.com/rfancn/aliyun-ddns-client/archive/master.zip
  • 解压源码

    1
    2
    yum install -y unzip
    unzip master.zip

4.3 配置

  • 重命名ddns.conf.example

    1
    2
    cd /usr/local/aliyun-ddns-client-master
    mv ddns.conf.example ddns.conf
  • 编辑ddns.service

    1
    vi ddns.service

    修改WorkingDirectory

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [Unit]
    Description=Aliyun DDNS Client.
    Wants=network-online.target
    After=network.target network-online.target
    [Service]
    Type=simple
    WorkingDirectory=/usr/local/aliyun-ddns-client-master #修改工作目录
    ExecStart=/usr/bin/python ddns.py
  • 复制服务文件

    1
    2
    cp ddns.timer /usr/lib/systemd/system
    cp ddns.service /usr/lib/systemd/system
  • 修改ddns.conf

    1
    vi ddns.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [DEFAULT]
    # 填写阿里云domain用户的access_id
    access_id=XXXX
    # 填写阿里云domain用户的access_key
    access_key=XXXXX
    # Optional: not used now
    interval=600
    # Optional: turn on debug mode or not
    debug=true
    [DomainRecord1]
    # 填写一级域名,如google.com
    domain=xxxx.com
    # 填写子域名,如pan,注意不要写成pan.xxxx.com
    sub_domain=pan
    # Required: resolve type, now it only supports 'A'
    type=A
  • 启动服务并验证配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd /usr/local/aliyun-ddns-client-master
    python ddns.py
    # 如果显示 2017-12-26 15:28:15 [INFO] Successfully updated DomainRecord[pan.xxxx.com]则环境正常
    systemctl daemon-reload
    systemctl start ddns.timer
    # 开机自启动
    systemctl enable ddns.timer
    # 查看服务状态
    systemctl status ddns.timer -l

    如果如下图所示,则服务正常:

    9

最后的最后,我们可以在内网或者公网通过 https://pan.xxx.com:23456 访问NextCloud,初始管理员账号密码为admin,登陆后就可以配置和使用了。

评论和共享

Scrum实施总结(一)

发布在 agile

目录

  1. 一、团队简介
    1. 1.1 团队组成
    2. 1.2 使用工具
  2. 二、 实践总结:sprint 1 - sprint 4
    1. 2.1 Sprint 1
      1. 2.1.1 迭代周期统计
      2. 2.1.2 优点
      3. 2.1.3 问题
    2. 2.2 Sprint 2
      1. 2.2.1 迭代周期统计
      2. 2.2.2 优点
      3. 2.2.3 问题
    3. 2.3 Spring 3
      1. 2.3.1 迭代周期统计
      2. 2.3.2 优点
      3. 2.3.3 成员评价和总结
    4. 2.4 Sprint 4
      1. 2.4.1 迭代周期统计
      2. 2.4.2 优点
      3. 2.4.3 问题

一、团队简介

1.1 团队组成

Dev Team:

  • 后台开发 * 1
  • 前端开发 * 1
  • 客户端开发 * 1
  • 测试 * 1

PO:

  • PO * 1
  • UI设计 * 1

Scrum Master:

  • scrum master * 1

1.2 使用工具

  • Leangoo
  • Jenkins
  • Selenium
  • TestStack.White

二、 实践总结:sprint 1 - sprint 4

2.1 Sprint 1

整个项目团队是新组建的,没有实施敏捷的经验

2.1.1 迭代周期统计

Sprint长度:2 week

统计:

WEB 总计
功能点 42 42
工作量 61 61
bug 未做探索性测试
完成功能点 39 39

结果:

功能没有完成,迭代失败

2.1.2 优点

  • 团队对于敏捷的兴趣比较高,工作积极性高

2.1.3 问题

  • 问题暴露不及时:由于团队刚刚组建,成员彼此不熟悉,遇到问题不习惯当面沟通

    在Sprint3开始后(一个月),该情况明显好转,遇到问题时,开发人员逐渐习惯整个团队来讨论解决问题

  • 没有做探索性测试:sprint1中,只针对AC做自动化测试,忽略了探索性测试

    Sprint2开始后,补充探索性测试

  • 发布时间长:没有为开发、测试、生产环境做独立配置,容易出错

    Sprint2开始后,上jenkins持续集成,Sprint3开始后,整体正常

  • 开发团队不习惯任务领取

    由于Sprint1没有持续集成,发布麻烦,所以开发不愿意逐个领任务。要求Sprint2开始后,开发人员要按优先级逐个领取任务并签名

  • 演示太慢:花费2个小时

    Sprint2开始后,由测试人员来演示,由于测试人员对AC和流程比较熟悉,演示速度能控制到1小时左右

  • 不会看燃尽图:PO和开发不会看燃尽图,不会评估开发进度

    Sprint2教会团队和PO通过Leangoo查看燃尽图

  • 立会内容表达不准确:开发团队每日立会讲述的内容不准确

    制定了一个参考模板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    功能
    今天在XX配合下做完了XX相关的X个功能的开发/测试,工作量共计X,是否达成目标
    今天还在做XX相关的X个功能,工作量共计X
    明天会做完XX相关的X个功能,工作量为X
    存在什么问题,没打成目标的原因
    任务
    今天做完了哪些任务,正在做哪些任务
    明天要做完的任务,任务预期是什么时间完成
    存在什么问题

2.2 Sprint 2

2.2.1 迭代周期统计

Sprint长度:2 week

统计:

WEB 总计
功能点 39 39
工作量 64 64
bug 4 4
完成功能点 20 20

结果:

功能没有完成,迭代失败

2.2.2 优点

  • 团队沟通和配合更默契
  • 上了Jenkins,发布更快捷

2.2.3 问题

  • 团队对全量回归测试认识不足:验收会前2个小时,团队修复2个bug,之后没有及时做全量回归测试,导致验收时19个功能没通过验收

    在Sprint3开始后,将全量回归测试加入jenkins持续集成中

  • 开发人员不按需求擅自增改功能

    要求开发人员遇到有争议的需求,必须和PO商量解决,不能擅自增改AC

  • 大任务拆解:对于网站页面UI这种比较大的任务,不方便评估工作量,也不方便及时测试

    工作量过大的任务,根据模块、页面拆解成子任务

  • 测试环境预览:PO和UI设计也想看到测试环境,好尽早判断开发是否复合需求

    给PO权限,让PO和设计人员也参与到测试中

  • 优先级调整和需求替换:PO刚开始不太能把控需求的优先级,Sprint开始前,如何确认和调整?PO不清楚如果有需求变化,需要改变或增加功能,需要怎么做?

    • 在下一个Sprint开始前,PO先大概罗列近期要做的功能,开发先写出AC并评估出工作量,PO根据工作量再考虑下一个Sprint要做的功能,从而重排PB优先级。在下一个Spring计划会上,确定该周期要做的功能。
    • 一般不建议在迭代周期内增改需求,如果迭代开始后,一定有需求需要修改或增加,一般有两个方式:等量替换和请客吃饭。从现在的周期内拿出等量或工作量略大于需要修改增加的功能,保证迭代能按期完成。或PO请团队吃饭,让团队加班完成。但不论哪一种,都不能经常使用。

2.3 Spring 3

2.3.1 迭代周期统计

Sprint长度:2 week

统计:

WEB CLIENT 总计
功能点 35 36 71
工作量 67 70 137
bug 13 2 15
完成功能点 35 36 71

结果:

迭代成功

2.3.2 优点

  • 迭代周期中替换了部分需求,没有影响到迭代周期
  • 自动化测试也集成到jenkins中,整套流程正规化

2.3.3 成员评价和总结

  • 开发评价Sprint1是忙,很多技术债要偿还,要适应新的开发模式;Sprint2是茫,有点不知所措,团队和协作不流畅;Sprint3团队已经逐渐熟悉这种模式
  • PO已经找到和开发团队沟通的方法,PO遇到疑问时,如果不是很重要的,会在每天下午5点后空闲时间和开发沟通,不打断开发人员正常的节奏

2.4 Sprint 4

2.4.1 迭代周期统计

Sprint长度:1 week

统计:

WEB CLIENT 总计
功能点 12 27 39
工作量 35 17 52
bug 0 9 9
完成功能点 13 27 40

结果:

迭代成功

2.4.2 优点

  • 从Spring4开始,尝试将迭代周期长度变成1周,以适应产品发布的实际情况,过度良好
  • PO和设计人员也“参与”到测试中

2.4.3 问题

  • Scrum的节奏比较快,团队压力比较大,容易疲劳

    在Spring4后,整个团队休息一周,调整节奏、偿还技术债

评论和共享

目录

  1. 一、使用插件
    1. 1.1 安装插件
    2. 1.2 启用插件
  2. 二、配置权限
    1. 2.1 角色管理
    2. 2.2 用户管理
    3. 2.3 分配角色
  3. 三、权限问题

jenkins默认的权限管理不支持用户分组或者按项目划分权限,所以如果团队有这种需求,需要安装插件。下面将介绍使用插件来实现用户角色的管理。

一、使用插件

1.1 安装插件

安装Role-based Authorization Strategy插件

1

1.2 启用插件

进入“系统管理”的“Configure Global Security”界面,配置如下:

2

  • 启动安全
  • 使用Jenkins专有用户数据库
  • 取消勾选“允许用户注册”,一般由管理员分配
  • 使用“Role-Based Strategy”策略

【注意】:使用Role-Based Strategy策略后,先不要注销管理员账号,否则会由于后续角色权限没配置而无法登陆,如果遇到这种问题,参考第三章

二、配置权限

正确安装了插件后,就能在“系统管理”中看到“Manage and Assign Roles”

3

2.1 角色管理

  • 进入“Manage Roles”

    4

  • 根据需要编辑Global roles

    5

    • 这里创建两个全局角色
    • admin有所有权限
    • project用于分配给项目组,这里只开放只读权限
  • 根据需要编辑Project roles

    6

    • 这里通过编写表达式让角色拥有对应项目的权限
    • 如果要匹配前缀是“Dent”的项目,表达式为“Dent.*”,和一般通配符表达式不同的是,星号前面还有一个点,不要忘记了

需要注意Project roles和Global roles配置的project不同,在2.3章节中会进一步解释。

2.2 用户管理

创建用户的步骤非常简单,如下所示:

  • 进入“系统管理”的“管理用户”

    7

  • 左边栏“新建用户”,按内容填写

    8

2.3 分配角色

编辑好角色和用户后,现在把它们关联起来,让权限生效:

  • 同样进入“系统管理”的“Manage and Assign Roles”,点击“Assign Roles”

    9

  • 参考下面的配置

    10

    • 在Global roles中,添加所有的用户,然后分配对应的全局角色
    • 在Item roles中,也要添加所有的用户(管理员用户可以不用分配),分配对应的项目角色

    这里解释一下,所有的用户都分配了两个角色,Global roles和Item roles,很容易不理解或者容易犯错的是只给一般用户分配Item roles,也就是项目角色,这样分配后,用户登陆会提示没有Overall的Read权限,也就是说用户虽然有某个项目的权限,他可以通过某个项目的URL去访问,但没有总体预览权限,没法进入首页。所以必须给用户配置全局角色,以获得Overall权限。

三、权限问题

在配置权限时,如果因为配置不当,导致管理员账号不能登陆jenkins,可以按下面的方式操作:

  • 编辑config.xml

    • 你可以在宿主机的映射位置如“/var/lib/docker/volumes/jenkins/_data/”找到该文件
    • 或者在docker容器内的“/var/jenkins_home”找到
  • 修改useSecurity为false

    1
    <useSecurity>false</useSecurity>
  • 删除authorizationStrategy、securityRealm节点

  • 重启jenkins for docker

    1
    2
    docker stop myjenkins
    docker start myjenkins

上面的操作可以清空权限系统,用管理员登录后重新配置即可。

jenkins四期介绍到此就结束了,然而jenkins在实际项目中的应用功能还远远不止如此,pipline,运维监控等高级玩法,jenkins+交通灯、报警器的搞怪玩法,以后有机会再整理分享出来。

评论和共享

目录

  1. 一、节点管理
    1. 1.1 添加节点
    2. 1.2 节点服务器配置
  2. 二、 测试项目
  3. 三、测试报告插件
    1. 3.1 Publish HTML reports插件使用
    2. 3.2 测试报告无法加载CSS的问题
      1. 3.2.1 临时方案
      2. 3.2.2 永久方案

前面几篇文章主要讲了jenkins搭建和项目构建相关内容,已经能满足自动编译,自动部署等功能,但还差一项很重要的工作:自动化测试!

作为敏捷开发必不可少的工作,我们需要每次构建后都要跑一遍自动化测试,做全量回归,如果有需要还有冒烟测试。那么下面就来介绍如何实现每次构建项目后自动执行一个自动化测试任务。

一、节点管理

一般来说,测试都基于windows操作系统,比如用selenium编写web项目的自动化测试,用teststack.white编写client项目自动化测试。而我们的jenkins是通过docker容器安装部署的,实际是linux环境,这里就会遇到操作系统不兼容的问题。

事实上,不仅仅是需要测试才会遇到这种情况,如果我们要通过jenkins for docker部署一个.net framework 项目,也是同样的问题。

但jenkins提供了节点管理的功能,能搭建起一个跨系统跨平台的集群,既能解决系统兼容问题,也能解决单点jenkins性能不足的问题。

1.1 添加节点

  • jenkins“系统管理”–“管理节点” 进入节点管理页面,点左边栏“新建节点”

    1

  • 填写节点名称,选择“Permanent Agent”

    2

  • 填写配置如下

    3

    • of executors:并发量,可同时执行job的数量,默认是1,根据需要填写
    • 远程工作目录:节点服务器的工作目录,目录不要有中文
    • 用法:默认为“尽可能使用该节点”,修改为“只允许运行绑定到这台机器的Job”
    • Environment variables:环境变量,如图所示,需要增加一个键值对,表示把节点服务器的PATH环境变量赋值到jenkins节点中,如果不配置环境变量,新建的这个节点将不能读取到节点服务器上的PATH环境,比如不能启动python命令
  • save后显示如下界面,点“Launch”按钮,下载“slave-agent.jnlp”文件

    4

1.2 节点服务器配置

登陆需要用于执行测试的windows服务器

  • 安装JDK
  • 创建工作目录,和1.1配置的远程工作目录一致,如“d:\jenkins”
  • 拷贝1.1章节最后一步下载的“slave-agent.jnlp”文件到该服务器,可以放到工作目录下,目录不要有中文
  • 运行“slave-agent.jnlp”即可

可将“slave-agent.jnlp”文件放在启动项里,使其开机启动,或者做成服务

成功运行后可在jenkins节点管理看到该节点,并且状态由红字提示“不在线”变成正常

二、 测试项目

添加测试项目和添加普通项目类似:

  • 添加自由软件风格项目

  • 指定第一章创建的节点执行该项目

    5

  • 添加测试项目的git源

    6

  • 配置构建触发器

    7

    • 勾选“Build after other projects are built”,选择“Trigger only if build is stable”,填写前置项目名称,表示当这些前置项目成功构建后,执行这个项目
    • 根据需要,勾选“Build when a change is pushed to GitLab.”,用于配置Gitlab webhook触发项目,可参考该系列第二篇文章 http://wurang.net/jenkins02_use/
  • 构建环境同样根据实际需要配置

    8

  • 添加构建脚本

    9

  • 最后构建后发送邮件通知

这里容易踩坑,找不到python命令,请参考1.2章节查看环境变量键值对设置。

另外生成的测试报告不方便查看,需要在节点服务器上搭建iis或者nginx,这对于jenkins集群来说并不是很好的做法。所以我们还需一个测试报告的插件。

三、测试报告插件

3.1 Publish HTML reports插件使用

  • 安装Publish HTML reports插件

  • 编辑jenkins测试项目

  • 构建后操作增加Publish HTML reports

    10

    • HTML directory to archive:测试报告目录,相对于项目工作目录
    • Index page[s]:测试报告页面
    • Report title:报告名称
    • Keep past HTML reports:保留旧的测试报告,默认不保留
    • Always link to last build:始终连接到最新的构建
    • Include files:包含文件,如果测试报告文件夹内还有css或js文件,需要根据需要填写
  • 配置完成后,重新构建测试项目,就可以在项目左边栏点击“HTML Report”查看测试报告了,如果保留了旧的测试报告,也可以在这里选择查看

    11

  • 除了上面的方式查看测试报告外,还可以使用URL:项目地址/HTML_Report/ 来访问,比如在邮件通知模板里用下面的代码在邮件显示测试报告地址

    1
    ${PROJECT_URL}HTML_Report

    修改该测试项目的邮件通知模板,拷贝“系统设置”里默认的邮件模板内容,加入自定义的代码

    12

3.2 测试报告无法加载CSS的问题

如果测试报告用到了CSS和JS,即使我们在3.1章节中配置了“Include files”,还是不能正常加载测试报告,会丢失CSS和JS文件,控制台报错跨域问题。这是因为Jenkins由java开发,默认对跨域做了安全限制,这是jenkins的CSP(Content Security Policy)默认配置的,配置项为:

1
sandbox; default-src 'none'; img-src 'self'; style-src 'self';

在这种配置下,HTML内联样式,js,ajax都不可以使用。

解决这个问题的方式有两种,归根到底都是修改CSP:

3.2.1 临时方案

  • 进入Jenkins脚本命令行

    13

  • 输入以下命令并运行

    1
    System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")

PS:需要注意

  • 执行完命令后再次构建测试项目,对新生成的测试报告才有效
  • 重启jenkins后配置将失效

结合之前的项目配置经验,我们也可以创建一个专门用来自动执行这个脚本的项目,添加触发器,每次jenkins启动后,就执行项目。让它变成一个“永久”方案。

3.2.2 永久方案

由于上面那个“永久”方案操作起来有点麻烦,还不能治本,所以我们还是考虑其他方式。

还记得第一篇文章http://wurang.net/jenkins01_docker/ 关于Dockerfile里有一段代码吗?

1
2
3
4
5
...
RUN rm -f /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 设置环境变量,设置时区和降低安全配置以允许查看测试报告时访问到css
ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\""
USER jenkins

设置了java的环境变量JAVA_OPTS,其中就包含了CSP的修改

1
-Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\"

所以我们在制作jenkins镜像的时候,从根本上就解决了问题。

至此,jenkins已经覆盖了编译、部署、测试整个软件开发流程。下一篇,将会介绍一下jenkins 的用户和项目权限管理。

评论和共享

目录

  1. 一、 插件
    1. 1.1 进入插件中心
    2. 1.2 更新插件
    3. 1.3 安装插件
    4. 1.4 卸载与降级插件
    5. 1.5 需要的插件
  2. 二、 使用示例
    1. 2.1 创建项目
    2. 2.2 源码管理
    3. 2.3 构建触发器
    4. 2.4 构建脚本
    5. 2.5 构建环境
      1. 2.5.1 Delete workspace before build starts 排除文件夹
      2. 2.5.2 Console Output日志加时间戳
    6. 2.6 构建后发布项目
      1. 2.6.1 设置SSH Server
      2. 2.6.2 发布到SSH Server
    7. 2.7 构建后邮件通知
      1. 2.7.1 配置Extended E-mail Notification
      2. 2.7.2 在项目中使用Extended E-mail Notification
  3. 三、 其他
    1. 3.1 SSH Server root权限执行命令或脚本
    2. 3.2 jenkins工作目录相关介绍
    3. 3.3 pm2相关知识介绍

一、 插件

正式介绍使用之前,还需要准备安装一些插件,插件中心使用操作如下:

1.1 进入插件中心

1

1.2 更新插件

2

1.3 安装插件

3

1.4 卸载与降级插件

4

1.5 需要的插件

  • GitLab Plugin
  • Gitlab Hook Plugin
  • Email Extension Plugin (默认安装)
  • Publish Over SSH
  • Timestamper (默认安装)
  • Workspace Cleanup Plugin (默认安装)

二、 使用示例

注:示例为dotnet core项目和nodejs项目,项目来源为Gitlab

2.1 创建项目

创建项目如下图所示,选择自由风格项目

5

2.2 源码管理

6

选择git源,Repo URL建议使用SSH地址,这时会提示没有权限,因为我们还没有将jenkins的public key添加到gitlab上。所以我们需要先拿到jenkins的public key:

  • 在宿主机上执行命令,其中myjenkins是容器名称

    1
    docker exec -it myjenkins /bin/bash
  • 进入jenkins容器后执行命令创建一对ssh的key

    1
    ssh-keygen -t rsa
  • 一路回车键,即可完成key的创建工作,这时我们进入默认目录查看

    1
    cd ~/.ssh
  • 会出现两个文件,一个是’id_rsa’还有一个是’id_rsa.pub’,我们需要查看’id_rsa.pub’文件,并记录下来

    1
    cat id_rsa.pub

    拷贝上面的文件内容,粘贴到记事本或其他文件管理软件并保存(后续操作仍会用到) 如:

    1
    ssh-rsa xxxxxxxxxxxjenkins@xxxxxx
  • 登陆gitlab账号(不要使用个人账号,建议使用部门公共账号、管理员账号),进入设置页面的’SSH KEYS’选项卡,填入拷贝好的public key,然后点’Add key’保存

    7

  • 再回到jenkins,点击’Add’添加认证方式

    8

  • 类型选为’SSH Username with private key’,Private Key选为’From the jenkins master ~/.ssh’,Username和Description可选填,用于备注,然后点’Add’完成添加

    9

  • 使用上面新建的认证方式后,原来红色的报错提醒就消失了,表示认证通过。最后修改一下需要拉取的分支,默认是’master’分支,我这里根据需要改成了’test’分支

    10

上面的配置完成后,jenkins就能从gitlab拉取项目了,可以在项目主菜单左边栏选择’立即构建’,看看是否成功:

11

2.3 构建触发器

通过2.2章节的配置,我们能够点击’立即构建’来主动开始一次构建,实际项目中,我们可能希望提交代码后,自动开始构建,这时需要配置构建触发器。下面演示gitlab触发器配置(需要安装Gitlab Hook Plugin插件):

12

  • 勾选’Build when a change is pushed to GitLab’,记录后面的URL,如本例中的’http://192.168.2.200:8081/project/Dent_WebSite_Test

  • 点击’高级’展开更多配置项

  • 在’Allowed branches’配置项选择’Filter branches by name’,在’Include’栏填入监听的分支名称,如’test’。表示触发器只监听来自test分支的事件。

  • 点击’Generate’按钮,生成’Secret token’并记录下来

  • 登陆gitlab(权限至少是master、owner或admin),进入jenkins需要配置的对应的项目,’Settings’选项卡,’Intergrations’配置页面。

    13

  • URL填写刚才记录的URL,如’http://192.168.2.200:8081/project/Dent_WebSite_Test

  • Secret Token填写刚才记录的Secret token

  • 勾选’push events’和’Merge Request event’,表示监听push和merge事件,然后点’Add webhook’,即可看到新建的webhooks

    14

  • 点击’Test’,选择’Push events’,测试看是否返回’HTTP 200’

    15

上面的配置完成后,就可以用git提交代码或者合并代码到test,看看jenkins是否自动开始构建。

PS: 触发器根据实际情况配置,一般来说实际项目中测试环境需要用触发器自动触发构建,而生产环境建议人工构建,避免误操作提交到master分支后冲掉生产环境!

2.4 构建脚本

前面我们已经完成了拉取代码的配置,接下来就要考虑如何’处理’这些源码:

  • 在’构建’栏增加构建步骤,选择’Execute shell’,创建shell脚本

    16

  • 如下图所示,是一个nodejs项目,它依次执行几个命令

    17

    • 需要注意的是每个命令用&&符号隔开,表示当前一个命令执行成功返回exit code = 0 后,下面的指令才执行,否则退出,用于避免前面的指令执行不成功,后面的指令继续执行
    • 点击’高级’展开更多配置,可以看到’Exit code to set build unstable’,默认exit code非0都会触发整个构建为unstable,也可以手动设置,如只监听exit code 为1,才会让构建变成unstable
  • 如下图所示,是一个dotnet core项目的编译shell脚本

    18

    • 这里将编译后的文件放到了workspace的jenkins_publish目录下面,这个workspace就是jenkins拉取git项目所在的目录
    • 最后一句将测试环境的配置文件改名,用于使其生效

2.5 构建环境

2.5.1 Delete workspace before build starts 排除文件夹

一般情况下,我们可能需要每次构建前,都要清空整个工作目录,也就是jenkins拉取git项目的目录,为了防止不必要的、多余的文件产生干扰,这时就需要勾选’构建环境’里面的’Delete workspace before build starts’,前提是安装了’Workspace Cleanup Plugin’插件:

19

也有另外几种情况,比如在nodejs项目中,每次清空工作目录后,执行’npm install’或’cnpm install’都需要很长时间,所以我们想保留’node_modules’文件夹,假如我们这个项目有两个二级目录(分别叫’app’和’frontend’)下面有’node_modules’文件夹,则配置如下所示:

20

  • 点开’高级’配置,添加4个Exclude输入栏
  • ‘ **/node_modules/** ‘表示node_modules文件夹和文件不删除
  • 还需添加两个二级目录’app’和’frontend’,否则只有子文件夹,没有父文件夹,实际上就是不存在任何文件夹
  • 还需添加’ **/.git/** ‘,因为下一次构建时,保留了’app’和’frontend’的’node_modules’文件夹以及文件内容,git不允许pull项目到非空目录,所以’ **/.git/** ‘目录也不能被清除
  • 不要勾选‘Apply pattern also on directories’,可能存在bug,勾选后反而不起作用

2.5.2 Console Output日志加时间戳

如下图所示,勾选’Add timestamps to the Console Output’,前提是安装’Timestamper’插件。这样构建时,在Console Output就能看到时间戳:

21

22

2.6 构建后发布项目

最后,我们要把’编译’或’处理’后的文件发布到指定的服务器上,常用方式为SSH,也可以通过FTP,下面都以SSH作为说明和示例(检查’Publish Over SSH’插件是否安装)。

2.6.1 设置SSH Server

进入’系统管理’里面的’系统设置’,找到’Publish over SSH’:

23

  • Passphrase:不用填写(如果你在2.2节,给jenkins创建ssh key时,一路回车没有添加这一属性,就无需配置)
  • Path to Key:填写’.ssh/id_rsa’,就是2.2节生成的私钥的文件路径
  • 点’增加’按钮,新增一个SSH Server,并点击’高级’展开更多配置
  • Name:SSH Server名称,用于展示和备注
  • Hostname:SSH Server的ip地址
  • Username:SSH Server用于发布服务的用户名,这里使用了www用户
  • Remote Directory: SSH Server用于发布服务的根目录,这里使用了www用户的目录/home/www/
  • Port:SSH Server的SSH服务端口,默认22

jenkins配置完成后,还需要对SSH Server做一些配置:

  • 远程连接到SSH Server上

  • 切换到SSH Server用于发布服务的用户,如www

  • 复制2.2节jenkins公钥的内容(id_rsa.pub),粘贴到该用户的’~/.ssh/authorized_keys’文件中,如果不存在则需要创建

  • 确保’~/.ssh’文件夹权限是700

    1
    chmod 700 ~/.ssh
  • 确保’authorized_keys’文件夹权限是600

    1
    chmod 600 ~/.ssh/authorized_keys
  • 确保’~/.ssh’文件夹及其内容的所有者是用于发布服务的用户

回到jenkins,如下图所示,点击’Test Configuration’,看是否返回Success!

24

2.6.2 发布到SSH Server

进入jenkins的项目,继续完成项目的发布配置,添加’ Send files or execute commands over SSH ‘构建步骤。

25

参考下图配置(示例为nodejs项目):

26

  • Add Transfer Set:添加一个传输
  • Name:选择需要发布的SSH Server
  • 在第一个Transfer处,写一些脚本,清理和重新创建部署目录,由于发布使用www用户,删除某些文件夹可能没有权限,所以需要用’sudo’来执行,’sudo’需要root用户的密码,我们这里需要免密码操作,操作详情可以看3.1章节
  • 在第二个Transfer处,Source files填写需要发布哪些文件
    • 该目录相对于工作目录,也就是jenkins拉取项目的目录,详情可以看3.2章节
    • 所有文件和文件夹填写’ **\** ‘
    • 如果有空文件夹,点开’高级’配置,如图中第5步所示,勾选’Make empty dirs’,否则空文件夹不会被发布
    • 如果要发布指定文件夹下的所有文件以及文件夹,填写’ foldername\ ‘,注意斜杠一定要有
    • 如果要发布多个目录,用英文逗号隔开
  • Remove prefix:如图所示,如果要发布app文件夹下的所有子内容,而不要把app这个文件夹也发布过去,需要在这里配置’app’
  • Remote directory:SSH Server的发布目录,相对于2.6.1章节,SSH Server配置的Remote Directory
  • Exec command:根据实际需求,执行一些命令,图中执行脚本安装nodejs包、重启pm2服务、重启nginx服务

dotnet core的参考配置如下,相对比较简单,发布后重启supervisor和nginx即可:

27

2.7 构建后邮件通知

上面的配置完成后已经能够正常使用,为了每次构建的信息能及时通知到团队,还需要配置邮件通知。

jenkins自带一个简单版的邮件通知功能,但不能满足一些复杂需求和定制需求,所以这里使用’Extended E-mail Notification’插件

2.7.1 配置Extended E-mail Notification

  • 进入’系统管理’里面的’系统设置’,找到’Jenkins Location’,填写’系统管理员邮件地址’:

    28

  • 找到’Extended E-mail Notification’,按下图所示配置:

    29

    • SMTP server:SMTP服务器地址
    • Default user E-mail suffix:默认邮件后缀
    • 点击’高级’展开更多配置
    • 勾选’Use SMTP Authentication’
    • 填写’User Name’,’Password’和’SMTP Port’
    • Default Recipients:默认收件人
  • Default Content就是邮件模板,这里给一个参考:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
    </head>
    <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
    offset="0">
    <table width="95%" cellpadding="0" cellspacing="0"
    style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
    <tr>
    <td><br />
    <b><font color="#0B610B">构建信息</font></b>
    <hr size="2" width="100%" align="center" /></td>
    </tr>
    <tr>
    <td>
    <ul>
    <li>项目名称 : ${PROJECT_NAME}</li>
    <li>构建编号 : 第${BUILD_NUMBER}次构建</li>
    <li>GIT URL: ${GIT_URL}</li>
    <li>GIT BRANCH: ${GIT_BRANCH}</li>
    <li>GIT COMMIT: ${GIT_COMMIT}</li>
    <li>触发原因: ${CAUSE}</li>
    <li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
    <li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li>
    <li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
    <li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
    </ul>
    </td>
    </tr>
    <tr>
    <td><b><font color="#0B610B">变更集</font></b>
    <hr size="2" width="100%" align="center" /></td>
    </tr>
    <tr>
    <td>${JELLY_SCRIPT,template="html"}<br/>
    <hr size="2" width="100%" align="center" /></td>
    </tr>
    </table>
    </body>
    </html>

2.7.2 在项目中使用Extended E-mail Notification

回到jenkins项目配置,添加名为’Editable Email Notification’的构建后操作,配置如下:

30

  • Project Recipient List:项目收件人邮箱列表
  • Default Content:邮件内容模板,默认调用2.7.1章节配置的模板内容,也可以在这里自定义
  • Attach Build Log:是否附件构建日志,一般选择’Attach Build Log’
  • Trigger:点开’高级’,配置触发器,条件有很多,根据需要配置,这里’Recipient List’就是项目收件人邮箱列表

至此,一个’完整’的jenins项目就部署完成了(由于还没有集成测试,所以完整打引号,jenkins集成测试请参见该系列第三篇文章)。

三、 其他

3.1 SSH Server root权限执行命令或脚本

通过SSH Server发布和部署项目往往会遇到权限不够,需要用’sudo’升权限的情况,在自动化流程里,不想使用密码或者暴露密码,那么可以参考下面的方式:

  • 进入SSH Server

  • 切换到root用户

  • 编辑sudoers文件

    1
    vi /etc/sudoers
  • 在文档最后,给www用户添加无需密码的指令或脚本

    1
    www ALL=(root) NOPASSWD:/usr/bin/systemctl,/bin/supervisorctl,/usr/bin/rm

    上面的命令表示www用户以sudo执行systemctl、supervisor和rm命令时不需要密码。

    因为给www用户rm权限比较危险,所以这里也可以填某个位置的脚本文件,封装起来相对更安全,根据项目需要自行选择。

  • 在sudoers文件还需注释掉下面的配置,用于允许其他来源的控制台传输sudo命令

    1
    #Defaults requiretty

3.2 jenkins工作目录相关介绍

jenkins工作目录就是jenkins拉取git项目的目录,可以在jenkins项目左边栏点击’工作空间’查看:

32

在宿主机上,可以进入docker jenkins容器映射的卷查看,如

1
cd /var/lib/docker/volumes/jenkins/_data/workspace/项目名

或者在宿主机上直接进入docker jenkins容器内,在jenkins_home文件夹中查看

1
2
3
docker exec -it myjenkins /bin/bash #宿主机上执行
#进入容器
cd /var/jenkins_home/workspace/项目名

3.3 pm2相关知识介绍

  • 不建议给每个nodejs项目配置局部pm2,建议给SSH Server配置全局的pm2

    1
    sudo npm install pm2 -g
  • pm2作用于每一个用户,如果是用www用户部署的node项目,在root用户下不能用’pm2 status’查看到pm2托管的项目,不要用不同的用户启动pm2,以免项目冲突

  • pm2开机启动方法如下:

    配置完pm2托管的项目后执行命令

    1
    2
    pm2 save
    pm2 startup

    再用root用户执行提示反馈的命令即可

评论和共享

目录

  1. 一、 关于持续集成
    1. 1.1 什么是持续集成
    2. 1.2 为什么要持续集成
  2. 二、 搭建Jenkins
    1. 2.1 什么是jenkins
    2. 2.2 Jenkins for Docker
      1. 2.2.1 准备工作
      2. 2.2.2 拉取Jenkins for Docker
      3. 2.2.3 通过Dockerfile定制jenkins
      4. 2.2.4 使用Jenkins for Docker
      5. 2.2.4 如何复用与迁移
    3. 2.3 如何升级Jenkins

一、 关于持续集成

1.1 什么是持续集成

持续集成, 简称CI(continuous integration).是一种软件开发实践,即团队开发成员经常集成他们的工作,每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

传统瀑布模型(水平划分项目阶段):

1

敏捷开发与CI模型(垂直划分项目阶段):

2

1.2 为什么要持续集成

  • 保证质量:CI是敏捷开发重要的一步,其目的在于让产品快速迭代的同时,尽可能保持高质量
  • 减少风险:CI讲求开发、部署、测试100%通过,通过多次集成,便于检查错误
  • 较少重复过程:自动化的构建、部署与测试节省重复工作,让团队能集中精力去做更重要的事
  • 增强项目可见性:每一次集成不论成功或失败,都能获得数据和信息供团队分析与决策
  • 增强团队协作:团队成员能清楚知道每一次提交代码后产生的影响,成员之间需要更密切的沟通来保证集成成功

二、 搭建Jenkins

2.1 什么是jenkins

Jenkins是一款用Java编写的开源的持续集成工具,是目前使用范围最广的CI工具。他长这样:

3

2.2 Jenkins for Docker

上文也说到Jenkins使用Java编写,所以支持跨平台。详细的介绍和安装说明可以查看官网

我们这里用到另一种方式,Jenkins for Docker. 这种方式在使用上更为轻量,不用在服务器上安装JDK,甚至不需要去研究如何安装Jenkins. 另外通过编写Dockerfile,能定制出符合我们需求的Jenkins配置,同时能够方便的复用和迁移。

2.2.1 准备工作

  • 需要一台装有Docker的宿主机(必要)

  • 需要一些Docker的基础知识与常用命令(必要)

  • docker宿主机设置代理(非必要)

    • 创建代理配置文件

      1
      2
      mkdir -p /etc/systemd/system/docker.service.d
      vi /etc/systemd/system/docker.service.d/http-proxy.conf
    • 内容填写参考如下

      1
      2
      [Service]
      Environment="HTTP_PROXY=http://192.168.2.100:1080/" "HTTPS_PROXY=https://192.168.2.100:1080/" "NO_PROXY=localhost,127.0.0.1"

      表示使用192.168.2.100:1080作为代理,且localhost不走代理

    • 更新配置

      1
      systemctl daemon-reload
    • 重启Docker

      1
      systemctl restart docker

2.2.2 拉取Jenkins for Docker

  • 宿主机执行命令拉取Jenkins的Docker Image

    1
    docker pull jenkins

    这个镜像体积不小,约800M,由于受国内网络环境影响,下载可能很慢,如果需要给docker设置代理请参考2.2.1 “docker宿主机设置代理”

  • 查看Jenkins镜像

    1
    docker images

    4

    到这里已经可以使用jenkins了,但我们还需做一些定制,比如给Jenkins镜像安装Nodejs、dotnet Core SDK等

2.2.3 通过Dockerfile定制jenkins

  • 根据需要编写Dockerfile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    FROM jenkins
    MAINTAINER wurang
    USER root
    # 将 shell 替换为 bash
    RUN rm /bin/sh && ln -s /bin/bash /bin/sh
    # 设置中科大软件镜像源
    RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
    RUN sed -i 's|security.debian.org|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list
    # upgrade
    RUN apt-get update && apt-get upgrade -y && apt-get install -y apt-utils sudo
    # 安装必要软件包
    RUN apt-get install -y build-essential curl libunwind8 gettext apt-transport-https nasm autoconf automake libtool libpng-dev pkg-config
    # 安装dotnet core 2.0
    RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \
    && mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \
    && sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/dotnetdev.list' \
    && apt-get update && apt-get install -y dotnet-sdk-2.0.0
    # 安装nodejs
    RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - && apt-get install -y nodejs
    # 安装cnpm
    RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
    # 清理缓存
    RUN apt-get clean && apt-get autoclean
    # 解决时区问题
    RUN rm -f /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    # 设置环境变量,设置时区和降低安全配置以允许查看测试报告时访问到css
    ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\""
    USER jenkins
  • 生成定制镜像

    1
    docker build -t auto-jenkins .
  • 查看镜像

    1
    docker images

    5

    auto-jenkins即为通过Dockerfile生成的定制镜像

2.2.4 使用Jenkins for Docker

  • 运行Jenkins容器

    1
    docker run --name myjenkins -d -p 8081:8080 -p 50000:50000 -v jenkins:/var/jenkins_home auto-jenkins
    • –name myjenkins 表示为运行的docker容器命名myjenkins
    • -d 表示用后台执行命令
    • -p 8081:8080 将Jenkins容器的8080端口映射至宿主机的8081端口,必须映射容器的8080端口到宿主机,考虑到宿主机8080端口可能被占用,所以这里映射到了8081端口,记住这个端口将用于对Jenkins的访问
    • -p 50000:50000 将Jenkins容器的50000端口映射至宿主机的50000端口,必须映射容器的50000端口到宿主机
    • -v jenkins:/var/jenkins_home 将Jenkins容器的home目录作为卷挂载到宿主机 /var/lib/docker/volumes/jenkins 目录,jenkins容器的所有配置、工作信息都会存放在这里。也可以挂载到宿主机的其他目录,不过需要注意权限问题
    • auto-jenkins 表示从我们创建的名为auto-jenkins的镜像启动容器
  • 访问Jenkins

    启动容器后,可以看到如下所示的信息,记住红框内的密钥信息

    6

    启动完成后就可以通过访问宿主机IP+容器8080的映射端口来访问Jenkins了,如通过上面的配置,我们可以访问 http://XXXX:8081

    7

    这里需要填入刚才记住的密钥,如果忘记了,可以在宿主机的挂载卷内找到

    1
    cat /var/lib/docker/volumes/jenkins/_data/secrets/initialAdminPassword

    然后安装推荐插件

    8

    9

    设置管理员账号

    10

  • 开机自动运行容器

    最后不要忘了给宿主机设置开机启动Jenkins容器,可添加下面的开机脚本命令

    1
    docker start myjenkins

2.2.4 如何复用与迁移

如果需要迁移整套jenkins或者在其他服务器复用,可以按照下面的步骤:

  • 新服务器安装Docker
  • 新服务器拉取Jenkins Image
  • 拷贝之前编写好的Dockerfile到新服务器,运行build脚本创建auto-jenkins镜像
  • 运行容器
  • 访问Jenkins
  • 拷贝旧服务器的挂载卷 /var/lib/docker/volumes/jenkins 到新服务器对应位置(迁移则需要这一步,复用不需要)

2.3 如何升级Jenkins

初次访问Jenkins for Docker,一般会遇到升级提示

11

升级步骤如下:

  • 右键”download”获取更新包地址

  • 进入jenkins容器

    1
    docker exec -it -u root myjenkins /bin/bash
  • 执行命令

    1
    2
    3
    4
    cd /usr/share/jenkins/ # 进入jenkins目录
    mkdir bak # 创建备份文件夹
    mv jenkins.war bak/jenkins.war.bak # 备份war包
    wget http://updates.jenkins-ci.org/download/war/2.73.2/jenkins.war # 下载更新包,地址为第一步获取的更新地址
  • 重启jenkins

    访问jenkins_url/restart 如 “http://XXXX:8081/restart” 重启jenkins

评论和共享

最近项目组上持续集成,于是利用jenkins的docker镜像做二次开发,原本写好的Dockerfile在生成docker image后不小心被误删,一番折腾后终于找了回来。

如下图所示:

  • jenkins为原始镜像
  • auto-jenkins为使用Dockerfile生成的镜像

1

使用history命令查询auto-jenkins镜像所执行的所有命令:

1
docker history auto-jenkins --no-trunc

2

可以从查询结果中识别出Dockerfile以外的命令,然后自下而上,找回Dockerfile的执行命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM jenkins
USER root
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN sed -i 's|security.debian.org|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list
RUN apt-get update && apt-get upgrade -y && apt-get install -y apt-utils sudo
RUN apt-get install -y build-essential curl libunwind8 gettext apt-transport-https
RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \
&& mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \
&& sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/dotnetdev.list' \
&& apt-get update && apt-get install -y dotnet-sdk-2.0.0
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - && apt-get install -y nodejs
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
RUN apt-get clean && apt-get autoclean
RUN rm -f /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV JAVA_OPTS=-Duser.timezone=Asia/Shanghai
USER jenkins

综上,从Docker Image找回Dockerfile,需满足如下条件:

  • 需存在Dockerfile生成的Docker Image
  • 利用Docker Image只能找到任何已提交到Image的指令,如Dockerfile生成的Image,或Container提交的Image。如果在Container中执行的指令没有Commit到Image,则不能用Image的history命令查询。

评论和共享

项目使用Webpack打包,打包完成后生成一些asset,不管是本地开发还是jenkins持续集成,都希望自动上传这些asset到阿里云OSS,于是用nodejs写一个脚本:

1. upload_oss.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
var co = require('co');
var OSS = require('ali-oss') //阿里云oss模块
var fs = require("fs"); //文件模块
var path = require("path");
//---------------------------使用说明----------------------------
//获取命令行传入参数(第0个参数是node 第1个参数是js文件 第2个文件是本地文件夹路径 第3个是oss相对目录)
//命令格式举例: node oss/upload_oss.js ../../static/ /static/
var localPath = process.argv[2]
var remotePath = process.argv[3]
if(localPath == null || remotePath == null){
throw new Error("缺少目录参数!");
return
}
localPath = path.resolve(localPath); //本地目录
remotePath = path.resolve(remotePath); //OSS相对目录
if(!fs.existsSync(localPath)){
throw new Error("本地目录"+ localPath + "不存在!")
return
}
//上传列表
var fileDic = new Array();
//阿里云OSS配置
var client = new OSS({
region: 'oss-cn-shenzhen',
accessKeyId: 'xxxx',
accessKeySecret: 'xxxx',
bucket: 'xxxx'
});
console.log('---------上传OSS---------');
console.log('【Step1】 分析目录');
readDir(localPath)
function readDir(filePath){
filePath = path.resolve(filePath);
//遍历文件目录
var pa = fs.readdirSync(filePath);
pa.forEach(function(filename,index){
var file = path.join(filePath,filename)
var info = fs.statSync(file)
//目录
if(info.isDirectory()){
readDir(file);
}
//文件
else {
//添加到上传列表
var localDir = path.join(filePath,filename);
var remoteDir = path.join(remotePath, localDir.replace(localPath,""));
fileDic[localDir] = remoteDir;
console.log("add file:" + localDir)
}
})
}
console.log('【Step2】 上传文件');
co(function* () {
for(var localDir in fileDic)
{
var result = yield client.put(fileDic[localDir], localDir);
console.log("upload from '" + localDir + "' to '" + fileDic[localDir] + "'");
}
console.log('【Step3】 完成');
}
).catch(function (err) {
throw new Error(err);
}
);

2. package.json调用

1
2
3
4
5
"scripts": {
"dev": "webpack-dev-server --devtool inline-source-map --progress --color --watch-poll",
"test": "export NODE_ENV=test && webpack --progress --color && node oss/upload_oss.js ../test_static/ /demo/test_static/",
"prod": "export NODE_ENV=production && webpack --progress --color && node oss/upload_oss.js ../static/ /demo/static/",
}

如上所示:

  • test环境下,上传本地test_static文件夹到OSS的demo/test_static
  • prod环境下,上传本地static文件夹到OSS的demo/static

评论和共享

目录

  1. 1. 背景
  2. 2. 关于查找资料
    1. 2.1 百度搜索
    2. 2.2 Google搜索
    3. 2.3 官方文档
  3. 3. 解决实时刷新
    1. 3.1 前期准备
    2. 3.2 webpack、webpack-dev-server属性操作方式
    3. 3.3 watch与watch-poll
      1. 3.3.1 查询文档
      2. 3.3.2 webpack如何使用watch和watch-poll
      3. 3.3.3 webpack-dev-server如何使用watch和watch-poll
      4. 3.3.4 watch-poll并没有完全解决问题
    4. 3.4 webpack-dev-server的服务地址相关属性
      1. 3.4.1 host
      2. 3.4.2 disableHostCheck
      3. 3.4.3 port
      4. 3.4.4 public
    5. 3.5 webpack、webpack-dev-server在虚拟机或docker下开启自动刷新方法复盘总结
  4. 4. webpack-dev-server的两种刷新模式
    1. 4.1 如何启用inline和iframe模式
    2. 4.2 iframe
    3. 4.3 inline
  5. 5. 模块热替换HMR
    1. 5.1 什么是HMR
    2. 5.2 使用HMR
    3. 5.3 其他相关介绍
      1. 5.3.1 vue使用HMR
      2. 5.3.2 webstorm可能引起hot失效的问题
  6. 6. 其他替代技术
    1. 6.1 BrowserSyncPlugin
    2. 6.2 LiveReloadPlugin

1. 背景

公司前端项目从webpack1.X 升级到webpack2.X,加之技术需求和业务需求增长过快,开发人员没有充足时间深入学习webpack相关技术栈,导致很多配置失效、冗余或者无法辨别究竟有什么用途。

此外,我们基于docker-machine封装了vue、webpack、nodejs等前端开发环境到docker image,docker-machine本身基于Virtual Box,这种虚拟机+Docker的方式又挖了一把大坑。详情见:Docker搭建前端开发环境

综上,几种情况导致我们在docker中使用webpack-dev-server,没办法实时刷新,但在本机开发时却可以。

启动命令为:

1
webpack-dev-server --devtool inline-source-map --progress --color --watch

部分webpack配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var webpack = require('webpack');
// ...
// var BrowserSyncPlugin = require('browser-sync-webpack-plugin');
var LiveReloadPlugin = require('webpack-livereload-plugin');
var config = {
entry: //...
output: {
path: path.resolve(__dirname, '../'),
filename: //...
chunkFilename: //...
},
module: {
rules: [{
test: /\.vue$/,
use: 'vue-loader'
}]
//...
},
plugins: [
// new webpack.HotModuleReplacementPlugin(), //热更新插件
(prod || test) ? function() {} : new LiveReloadPlugin({
appendScriptTag: true,
}),
//...
],
//...
}

一副很有历史痕迹的配置文件,留下了几个问题:

  • 问题一:在虚拟机或Docker下为什么不能实时刷新
  • 问题二:看上去功能重复的插件如BrowserSyncPlugin、LiveReloadPlugin、HotModuleReplacementPlugin究竟有什么区别,为什么会被注释

2. 关于查找资料

2.1 百度搜索

很多情况下,国内开发同学习惯在百度搜索相关问题,不可否认,百度搜索的中文问题有时候确实能比较快的找到答案,但在我们这个问题上,通过“webpack”,“自动刷新”,“HMR”等关键词中,找到几篇文章,但都存在一些问题,节选如下:

上面搜索的结果大部分是Webpack相关,除了版本对不上之外,还没法解决我们的问题。当然可以换新的关键词如“webpack 虚拟机 自动刷新”,效果基本和上面一样存在问题。

2.2 Google搜索

百度不行上google,在google通过“webpack hot reload vue virtual machine”等关键词搜到的结果一般来自于stackoverflow、github。一般github的问题会带项目的版本号,至少不会看岔了版本。stackoverflow比较专业的问题也会留下项目版本,操作系统等信息。最后在下面两篇文章中找到了“自动刷新”问题的答案:

Google搜索相对成本比较大,一方面是“技术”成本,另一方面是语言成本。虽然问题能解决,但对于知识体系的梳理并没有特别的帮助。偿还技术债归根到底还是要去看官方文档。

2.3 官方文档

这里列举的官方文档总共有三份,后面章节所有的介绍都基于这三份文档:

注意文档分为六部分,可以在导航栏切换。

5

3. 解决实时刷新

3.1 前期准备

开始解决问题之前,我们先做一下配置最小化,也就是把不清楚的,没有用的配置项去掉,减少干扰。最初的配置文件修改如下:

主要彻底删除了三个插件:browser-sync-webpack-plugin、webpack-livereload-plugin和HotModuleReplacementPlugin相关的所有配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var webpack = require('webpack');
// 省略...
var config = {
entry: //...
output: {
path: path.resolve(__dirname, '../'),
filename: //...
chunkFilename: //...
},
module: {
rules: [{
test: /\.vue$/,
use: 'vue-loader'
}]
//...
},
plugins: [
//...
],
//...
}

3.2 webpack、webpack-dev-server属性操作方式

在官方文档中,webpack和webpack-dev-server的属性一般有两种操作方式,一种是CLI也就是命令行方式,另一种是修改配置的方式

  • CLI方式只需在命令行中用双横杠加属性即可操作属性值

    1
    webpack --progress=true --color=false --watch
  • CLI里面的每一个属性默认值为true,也就是说上面的例子“–watch”等同于“–watch=true”

  • 配置方式需要修改webpack.config文件,下面通过配置方式启用webpack的watch模式

    1
    2
    3
    4
    5
    var config = {
    entry: xx,
    watch:true, //添加watch
    //...
    }
  • CLI和配置方式二选一,不要同时使用,以免造成配置混乱,可读性差

  • CLI和配置方式同时使用时,CLI的优先级高,会盖掉配置文件的配置

3.3 watch与watch-poll

3.3.1 查询文档

https://webpack.js.org/configuration/watch/ 找到webpack的watch属性介绍:

6

  • watch属性用来监听文件变化
  • webpack-dev-server默认开启watch
  • 如果watch模式不起作用(watch对于NFS文件系统和虚拟机没有作用),可以尝试watchOptions.poll模式

同时在https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-中找到了关于webpack-dev-server的watch属性介绍:

7

  • webpack用文件系统通知文件变化。如果使用NFS系统也就是网络文件系统,即网络磁盘、共享盘等,包括虚拟机,这些情况下用watch没有效果,需要打开watchOptions.poll模式

通过上面的信息,我们知道了由于虚拟机不能使用watch通知文件变化,需要用poll这种主动轮询的方式通知。这也许能解决我们的问题。

3.3.2 webpack如何使用watch和watch-poll

  • webpack命令默认没有启动watch,如果需要打开watch,两种方式

    • CLI命令行下添加watch属性

      1
      webpack --watch
    • 或者在配置文件中添加配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      var webpack = require('webpack');
      //...
      var config = {
      entry: xx,
      watch:true, //添加watch
      output: {
      path: path.resolve(__dirname, '../'),
      //...
      },
      //...
      }
  • 如果在虚拟机下用webpack,需要使用watch poll模式,两种方式

    • CLI命令行下添加watch–poll属性

      1
      webpack --watch-poll
    • 或者在配置文件中添加配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var webpack = require('webpack');
      //...
      var config = {
      entry: xx,
      watchOptions: {
      poll: true
      }, //添加watchOptions节点,设置poll为true
      output: {
      path: path.resolve(__dirname, '../'),
      //...
      },
      //...
      }

3.3.3 webpack-dev-server如何使用watch和watch-poll

  • 因为我们使用的是webpack-dev-server,又因为webpack-dev-server默认开启watch,所以我们没有必要再做任何配置

  • 如果我们在虚拟机下用是webpack-dev-server,那就需要watch poll模式,两种方式

    • CLI命令行下添加watch–poll属性

      1
      webpack-dev-server --watch-poll
    • 或者在配置文件中添加配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      var webpack = require('webpack');
      //...
      var config = {
      entry: xx,
      devServer:{
      watchOptions: {
      poll: true
      }
      }, //添加devServer节点,添加watchOptions子节点,设置poll为true
      output: {
      path: path.resolve(__dirname, '../'),
      //...
      },
      //...
      }

3.3.4 watch-poll并没有完全解决问题

经过这一番折腾,本以为我们把启动命令从

1
webpack-dev-server --devtool inline-source-map --progress --color --watch

改成

1
webpack-dev-server --devtool inline-source-map --progress --color --watch-poll

就能解决【问题一】,然而结果事与愿违。还有其他哪里不对吗?

在Chrome的控制台中,看到下面的信息:

8

8080端口是webpack-dev-server的默认端口,从图中可以得知“[WDS] Disconnected!”,也就是说WebpackDevServer链接中断,紧接着是socket连接被拒。了解webpack-dev-server自动刷新原理的同学应该知道,自动刷新和热更新都是基于websocket来通知触发,这里展开又是长篇大论,所以不再多说,有兴趣自行了解。仅贴一下官方文档的解释,至于为什么贴devServer的inline属性文档说明,还是请看第4章。

9

websocket报错至少告诉我们自动刷新不成功是网络问题,那回顾一下使用虚拟机或者docker machine时的网络架构模式。如下图所示:

10

  • 如果在虚拟机下使用webpack-dev-server,实际上其服务地址应该是192.168.99.100:8080
  • 如果在docker machine中创建容器,并在其中使用webpack-dev-server,实际上其服务地址应该是172.17.0.2:8080,又因为docker容器和docker machine有做端口映射,也可以使用地址192.168.99.100:8080来访问webpack-dev-server服务

不管是哪种情况,都与我们目前遇到的不符。目前,我们webpack-dev-server服务地址是localhost:8080,也就是192.168.1.2:8080,事实上webpack-dev-server服务根本不在PC上。仔细想一下,webpack-dev-server默认ip为localhost,默认端口为8080,如果我们没有做配置,按默认来,那么确实是现在这个结果。【问题一】没解决,又生一问题,看看目前的问题库:

  • 问题一:在虚拟机或Docker下为什么不能实时刷新
    • 子问题a:在虚拟机或Docker下webpack-dev-server服务地址设置问题【新增】
  • 问题二:看上去功能重复的插件如BrowserSyncPlugin、LiveReloadPlugin、HotModuleReplacementPlugin究竟有什么区别,为什么会被注释

那么接下来,想办法把webpack-dev-server服务地址问题搞定。

3.4 webpack-dev-server的服务地址相关属性

3.4.1 host

官方文档中,host属性说明如下:

11

通过指定host,可以设置webpack-dev-server服务地址,文档中特别说明“如果需要外部访问服务地址,要将host指定为0.0.0.0”。暂且先不管这个“0.0.0.0”,既然在3.3.4小节中,我们期望的服务地址是“192.168.99.100”,那我们尝试用host指定一下这个IP。

1
webpack-dev-server --devtool inline-source-map --progress --color --host 192.168.99.100 --watch-poll

执行一下,但却得到报错:

12

报错提示为没法监听到“192.168.99.100”的8080端口。

这里也好理解,我们的webpack-dev-server运行在docker容器中,也就是“172.17.0.2”这个IP,docker和docker machine之间是端口映射关系,只有当docker容器的8080端口有服务,docker machine的8080端口才有用。那我们直接在docker容器中把服务放到docker machine上是不行的。

那尝试改成docker容器自己的IP“172.17.0.2”看看结果:

1
webpack-dev-server --devtool inline-source-map --progress --color --host 172.17.0.2 --watch-poll

编译正常,但还存在两个问题:

  • 我们一般不关注docker容器的IP,只关注docker或者docker machine的IP,他们之间有端口映射关系,所以把host改到docker容器上不是什么好方法

  • 虽然编译没报错,但Chrome的控制台报另一个错误了,这回换成“172.17.0.2”这个IP访问不到。事实如此,我们的本机PC和docker 容器之间并不互通,一直以来都是以docker machine作为桥梁。

    13

得,直接把host定位到docker machine不行,把host定位到docker容器本身也不行。那怎么破?这时候再回到官方文档的介绍,试试指定host到“0.0.0.0”,在网络中0.0.0.0意思为整个网络:

1
webpack-dev-server --devtool inline-source-map --progress --color --host 0.0.0.0 --watch-poll

编译也正常,回到Chrome看下控制台,WTF!还有错:

14

这回不是找不到服务地址了,却提示无效的host头部。问题库再来一员。

  • 问题一:在虚拟机或Docker下为什么不能实时刷新
    • 子问题a:在虚拟机或Docker下webpack-dev-server服务地址设置问题【新增】
    • 子问题b: host指定到0.0.0.0提示Invalid Host header【新增】
  • 问题二:看上去功能重复的插件如BrowserSyncPlugin、LiveReloadPlugin、HotModuleReplacementPlugin究竟有什么区别,为什么会被注释

3.4.2 disableHostCheck

【子问题b】相对来说比较好解决,google一下,信息都指向到disableHostCheck这个属性上:

15

说出于安全考虑,检查host看是否存在攻击行为,那我们现在是开发使用,给他禁了吧:

1
webpack-dev-server --devtool inline-source-map --progress --color --host 0.0.0.0 --disable-host-check --watch-poll

这下终于没问题也不报错了,尝试一下改动代码,实时刷新终于OK了!

16

至此,【问题一】彻底解决!但跟host服务地址相关的几个属性有必要再介绍一下。

  • 问题一:在虚拟机或Docker下为什么不能实时刷新【已解决】
    • 子问题a:在虚拟机或Docker下webpack-dev-server服务地址设置问题【已解决】
    • 子问题b: host指定到0.0.0.0提示Invalid Host header【已解决】
  • 问题二:看上去功能重复的插件如BrowserSyncPlugin、LiveReloadPlugin、HotModuleReplacementPlugin究竟有什么区别,为什么会被注释

3.4.3 port

17

host属性用来指定webpack-dev-server服务的IP地址,那么port属性就是指定服务的端口了。由于默认端口是8080,比较容易被占用,所以可以人为去指定。

需要提醒一下,如果是使用docker,默认的8080端口或者指定的其他端口都需要做好映射!

3.4.4 public

18

解释说如果使用inline模式,并代理使用webpack-dev-server,可能会需要public属性。

在我这个例子中,webpack-dev-server的服务地址是0.0.0.0:8080,我们并没有用nginx之类的软件代理这个地址,所以在本例中,我们不需要使用public属性。

但如果0.0.0.0:8080被nginx代理为wds.XX.com这个域名,有可能会需要设置这个属性。

我在解决这一系列问题途中,搜索到stackoverflow和github上某些文章,说只要设置public属性就行了,这不是一个完全正确的答案:

  • host是说webpack-dev-server服务的源在哪里,必须存在目标的位置
  • public是说webpack-dev-server服务发布在哪里,这是指路

3.5 webpack、webpack-dev-server在虚拟机或docker下开启自动刷新方法复盘总结

上面介绍的比较杂,我们重新梳理一下解决【问题一】的路线:

  • 在虚拟机或Docker下为什么不能实时刷新

    这是因为watch属性不能在网络文件系统也就是虚拟机下监听文件变化,必须使用watch-poll主动轮询查询文件是否变更

  • 使用watch-poll后,找不到webpack-dev-server服务地址localhost:8080

    这是因为wds默认地址为localhost:8080,但实际上服务地址应该在虚拟机或docker machine上,指定host为0.0.0.0即可。意思为监听整个网络

  • wds服务地址指定为0.0.0.0:8080后,提示Invalid Host header

    这是因为wds出于安全考虑,检查host是否存在危险,毕竟0.0.0.0监听整个网络,而我们在开发机以及开发环境中并不会有什么风险,可以用过disableHostCheck禁用host检查

问题的答案有两种形式:

  • CLI

    1
    webpack-dev-server --host 0.0.0.0 --disable-host-check --watch-poll
  • 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var webpack = require('webpack');
    //...
    var config = {
    entry: xx,
    devServer:{
    host: "0.0.0.0", //指定wds服务ip为0.0.0.0用于监听整个网络
    // port: 9081, //自定义wds服务端口
    // public: "http://wds.myapp.com", //指定wds服务的发布位置
    disableHostCheck:true, //禁用host检查以解决Invalid Host header错误
    watchOptions: {
    poll: true //开启watch主动轮询模式
    }
    },
    output: {
    path: path.resolve(__dirname, '../'),
    //...
    },
    //...
    }

    同样的属性,配置文件和CLI不要混合使用,上面的配置文件搭配的CLI非常简单:

    1
    webpack-dev-server

最后,用docker的话记得映射wds的端口!

比如配合上面的例子,docker的run命令如下:

1
docker run -p 80:80 -p 8080:8080 centos

到这里还没完!一开始我们说了一些文章关于webpack的介绍混淆了1.x和2.x版本,还多次提到了inline模式。虽然已经解决了自动刷新的问题,但关于自动刷新还知之甚少。那下面来详细了解一下webpack-dev-server的自动刷新模式。

4. webpack-dev-server的两种刷新模式

webpack-dev-server实现自动刷新有两种方式,一种是inline模式,另一种是iframe模式。关于这两种模式的介绍和区别 详情介绍webpack-dev-server,iframe与inline的区别 这篇文章介绍的比较详细。

简单来说:

  • inline是将inline.js打包到bundle.js,inline.js包含了socket通讯代码,可以与wds进行通讯,用来响应wds反馈的文件变化通知,然后刷新整个url
  • iframe是在网页中嵌入一个iframe,文件发生变化时,wds通知到live.bundle.js,live.bundle.js本身也包含socket通讯,收到反馈后reload这个ifame以完成页面刷新

4.1 如何启用inline和iframe模式

文章一开始,我们列举了几篇文章,说都存在问题,包括本章开头介绍inline和iframe的文章也存在这个问题,他们分别是这样介绍的:

每一篇都说iframe无需配置,是默认的,而inline需要用CLI或配置打开。还有其他一些文章,还专门强调使用inline模式需要手动添加entry。这是因为这些文章用的文档都是webpack1.X版本,那我们看看官方是怎么说的:

  • webpack 1.X

    22

  • webpack 2.X

    23

    • inline默认开启
    • iframe模式需要配置
    • 建议使用inline模式配合模块热更新(inline和iframe都支持模块热更新)

综上所述:

  • webpack1.X中默认开启的是iframe模式,而webpack2.X中默认开启inline模式,同时webpack2.X也无需配置entry
  • 所以有必要提醒一下,如果你正在使用webpack2.X,需要启用inline模式,那么在CLI中添加inline是多此一举
  • 另外iframe模式下,访问地址加了目录webpack-dev-server,变成 http://ip:port/webpack-dev-server/index.html
  • inline模式会刷新整个url
  • iframe模式只会刷新iframe里面的页面,url并不会刷新

不管怎么说,理论上这两种模式都能完成自动刷新,下面看看实际运行结果。

4.2 iframe

注意下面的介绍都是基于虚拟机的架构,所以保留了watch-poll、host、disable-host-check等属性设置。

执行命令:

设置属性inline=false以启动iframe模式

1
webpack-dev-server --watch-poll --host 0.0.0.0 --port=9081 --inline=false --disable-host-check

24

iframe模式下,url地址确实增加了webpack-dev-server目录,同时整个页面最顶层有一个状态栏,整个页面被包裹在一个iframe里,注意观察下面的动图,看看url和顶层状态栏,以及页面如何刷新:

25

4.3 inline

inline就是webpack2.X默认模式,所以我们在第3章介绍的案例都是以inline模式运行,不再过多介绍,执行命令,看一下动图:

26

到这还是没结束。因为问题库里面,【问题一】解决了,但【问题二】还没解决:

  • 问题一:在虚拟机或Docker下为什么不能实时刷新【已解决】
    • 子问题a:在虚拟机或Docker下webpack-dev-server服务地址设置问题【已解决】
    • 子问题b: host指定到0.0.0.0提示Invalid Host header【已解决】
  • 问题二:看上去功能重复的插件如BrowserSyncPlugin、LiveReloadPlugin、HotModuleReplacementPlugin究竟有什么区别,为什么会被注释

那么继续往下看!

5. 模块热替换HMR

5.1 什么是HMR

27

上面是官方对于HMR的解释。HMR相对于自动更新来说:

  • 不需要刷新整个页面,因为这可能会影响到现有的表单、缓存等,同时响应速度比较慢
  • 整个应用在运行时,可以替换其中的模块,而不用重启应用

第4章也提到,inline和iframe模式都可以配合HMR使用,那我们如何开启HMR呢?

5.2 使用HMR

28

官方文档对于webpack2.X版本启用hot说明如下:

  • 如果通过CLI也就是命令行方式开启hot,会自动添加HotModuleReplacementPlugin

    1
    webpack-dev-server --host 0.0.0.0 --disable-host-check --watch-poll --hot
  • 如果使用配置方式开启hot,需要手动添加HotModuleReplacementPlugin

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var webpack = require('webpack');
    //...
    var config = {
    entry: xx,
    devServer:{
    host: "0.0.0.0",
    disableHostCheck:true,
    hot:true, //开启hot
    watchOptions: {
    poll: true
    }
    },
    output: {
    path: path.resolve(__dirname, '../'),
    //...
    },
    plugins: [
    new webpack.HotModuleReplacementPlugin(), //热更新插件
    //...
    ],
    //...
    }

上面的例子中,默认使用inline模式配合hot开启HMR,如果要使用iframe模式,记得指定inline=false

现在我们知道【问题二】中HotModuleReplacementPlugin的用途了,只有在通过配置文件方式开启hot模式时,需要手动添加HotModuleReplacementPlugin

  • 问题一:在虚拟机或Docker下为什么不能实时刷新【已解决】
    • 子问题a:在虚拟机或Docker下webpack-dev-server服务地址设置问题【已解决】
    • 子问题b: host指定到0.0.0.0提示Invalid Host header【已解决】
  • 问题二:看上去功能重复的插件如BrowserSyncPlugin、LiveReloadPlugin、HotModuleReplacementPlugin究竟有什么区别,为什么会被注释
    • 子问题c: HotModuleReplacementPlugin是做什么用的【已解决】
    • 子问题d: BrowserSyncPlugin是做什么用的【新增】
    • 子问题e: LiveReloadPlugin是做什么用的【新增】

正确开启HMR后,浏览器控制台可以看到这个信息:

29

看一下HMR响应改变的动图,注意观察和inline自动刷新的区别,主要是url没有刷新,以及控制台里面的信息:

30

5.3 其他相关介绍

5.3.1 vue使用HMR

在vue中,使用vue-loader就可以完美兼容HMR模式,看看vue官方怎么介绍的:

https://vue-loader.vuejs.org/zh-cn/features/hot-reload.html

31

5.3.2 webstorm可能引起hot失效的问题

有同学反应,使用webstorm开发时,HMR失效了。这锅不是webstorm一个人背,官方是这样解释的:

32

有些编辑器默认使用“safe write”模式保存文件,比如webstorm,这种模式将文件变化先放到缓存里,并不直接改写文件,所以导致监听文件变更失败。遇到这种情况,可以取消“safe write”模式。

6. 其他替代技术

整篇文章还剩下两个问题:

  • 问题一:在虚拟机或Docker下为什么不能实时刷新【已解决】
    • 子问题a:在虚拟机或Docker下webpack-dev-server服务地址设置问题【已解决】
    • 子问题b: host指定到0.0.0.0提示Invalid Host header【已解决】
  • 问题二:看上去功能重复的插件如BrowserSyncPlugin、LiveReloadPlugin、HotModuleReplacementPlugin究竟有什么区别,为什么会被注释
    • 子问题c: HotModuleReplacementPlugin是做什么用的【已解决】
    • 子问题d: BrowserSyncPlugin是做什么用的【新增】
    • 子问题e: LiveReloadPlugin是做什么用的【新增】

一一攻破。

6.1 BrowserSyncPlugin

https://www.npmjs.com/package/browser-sync-webpack-plugin

主要解决浏览器同步问题,比如做兼容性调试时,一般同时打开IE、Chrome或开启手机浏览器模拟器等,该插件可以同步所有浏览器内容。与webpack共同使用时,一般用来代理webpack-dev-server服务已完成更多功能。详细不再过多介绍,按需使用。

6.2 LiveReloadPlugin

https://www.npmjs.com/package/webpack-livereload-plugin

基本功能与webpack-dev-server一致,但如果网站的assets由其他服务器提供,比如某些图片存在阿里云OSS,而你又想使用webpack的自动更新功能。那么就可以使用这个插件。

  • 问题一:在虚拟机或Docker下为什么不能实时刷新【已解决】
    • 子问题a:在虚拟机或Docker下webpack-dev-server服务地址设置问题【已解决】
    • 子问题b: host指定到0.0.0.0提示Invalid Host header【已解决】
  • 问题二:看上去功能重复的插件如BrowserSyncPlugin、LiveReloadPlugin、HotModuleReplacementPlugin究竟有什么区别,为什么会被注释
    • 子问题c: HotModuleReplacementPlugin是做什么用的【已解决】
    • 子问题d: BrowserSyncPlugin是做什么用的【已解决】
    • 子问题e: LiveReloadPlugin是做什么用的【已解决】

至此,所有的坑全部填完……

评论和共享

作者的图片

Wu Rang

Everything begin with HelloWorld!


System Architect


Guangzhou