service

参考:https://www.cnblogs.com/fuyuteng/p/11598768.html
kubernetes service 原理解析 - 知乎 (zhihu.com)

为什么需要 service

在 kubernetes 中,当创建带有多个副本的 deployment 时,kubernetes 会创建出多个 pod,此时即一个服务后端有多个容器,那么在 kubernetes 中负载均衡怎么做,容器漂移后 ip 也会发生变化,如何做服务发现以及会话保持?这就是 service 的作用,service 是一组具有相同 label pod 集合的抽象,集群内外的各个服务可以通过 service 进行互相通信,当创建一个 service 对象时也会对应创建一个 endpoint 对象,endpoint 是用来做容器发现的,service 只是将多个 pod 进行关联,实际的路由转发都是由 kubernetes 中的 kube-proxy 组件来实现,因此,service 必须结合 kube-proxy 使用,kube-proxy 组件可以运行在 kubernetes 集群中的每一个节点上也可以只运行在单独的几个节点上,其会根据 service 和 endpoints 的变动来改变节点上 iptables 或者 ipvs 中保存的路由规则

service 的工作原理

service资源基于标签选择器将一组pod定义成一个逻辑组合,并通过自己的ip地址和端口调度代理请求至组内pod的对象之上,它向客户隐藏了真实的、处理用户请求的pod资源,使得客户端的请求看上去像是由service直接处理并进行响应一样

Service 对象的 IP 地址也称为 Cluster IP,是一种 VIP(虚拟IP),k8s集群内部的VIP

service网段不能跟机房网络、docker网段、容器网段冲突,否则可能会导致网络不通

Service以负载均衡的方式进行流量调度,Service和Pod之间松耦合,创建Service和Pod的任务可由不同的用户分别完成

Service 通过API Server实时监控(watch)标签选择器匹配到的后端Pod,不过Service并不直接链接至Pod,它们中间还有一个中间层 --Endpoints资源对象,默认情况下,创建Service对象时,其关联的Endpoints对象会自动创建

endpoints controller 是负责生成和维护所有 endpoints 对象的控制器,监听 service 和对应 pod 的变化,更新对应 service 的 endpoints 对象。当用户创建 service 后 endpoints controller 会监听 pod 的状态,当 pod 处于 running 且准备就绪时,endpoints controller 会将 pod ip 记录到 endpoints 对象中,因此,service 的容器发现是通过 endpoints 来实现的。而 kube-proxy 会监听 service 和 endpoints 的更新并调用其代理模块在主机上刷新路由转发规则

Endpoints 是一个由IP地址和端口组成的列表,这些IP和端口来自于其 Service 关联的 Pod

每个节点上的 kube-proxy 组件 watch 各 Service 及其关联的 Endpoints,如果有变动,就会实时更新当前节点上相应的 iptables 或 ipvs 规则,确保 Cluster IP 的流量能调度到 Endpoints

简单来讲,一个 Service 对象就是一个 Node 上的这些 iptables 和 ipvs 规则

service 的负载均衡

pod之间通信,一般不会pod直接访问pod,而是pod先访问service,然后service再到pod

关于将 Cluster IP 的流量能调度到 Endpoints,有三种方式:userspace代理、iptables代理、ipvs代理

  1. userspace代理

    1.1版本之前的默认代理模型,效率低

  2. iptables代理

    通过iptables进行目标地址转换和流量调度,缺点是不会在后端Pod资源无响应时自动进行重定向

  3. ipvs代理

    和iptables代理模型的区别仅在于,流量的调度功能由ipvs实现,其他功能仍由iptables完成

service 的类型

service 支持的类型也就是 kubernetes 中服务暴露的方式,默认有四种:ClusterIP、NodePort、LoadBalancer、ExternelName

ClusterIP

kubernetes 集群默认的服务暴露方式,它只能用于集群内部通信,可以被各 pod 访问,其访问方式为:

pod ---> ClusterIP:ServicePort --> (iptables)DNAT --> PodIP:containePort

# 集群内pod直接访问service的ip即可

NodePort

如果想要在集群外访问集群内部的服务,可以使用 NodePort 类型的 service,在集群内部署了 kube-proxy 的节点打开一个指定的端口,将访问node此端口的流量直接发送到这个端口,然后会被转发到 service 后端真实的服务进行访问。Nodeport 构建在 ClusterIP 上,其访问链路如下所示:

client ---> NodeIP:NodePort ---> ClusterIP:ServicePort ---> (iptables)DNAT ---> PodIP:containePort

# 外部流量请求先到node,再到service
# 只要安装了kube-proxy的node,都可以处理外部流量,有实力的话可以单独拿出几个node,打上污点,然后负载均衡只会将外部流量转发到这几个node

LoadBalancer

主要在公有云如阿里云、AWS 上使用,LoadBalancer 构建在 nodePort 基础之上,通过公有云服务商提供的负载均衡器将 k8s 集群中的服务暴露到外网,云厂商的 LoadBalancer 会给用户分配一个 IP,之后通过该 IP 的流量会转发到你的 service 上

LoadBalancer service 类型的结构如下图所示:

ExternelName

通过 CNAME 将 service 与 externalName 的值(比如:http://foo.bar.example.com)映射起来,这种方式用的比较少。

service 的服务发现

Pod 与 Service 的 DNS:https://kubernetes.io/zh/docs/concepts/services-networking/dns-pod-service/

虽然 service 的 endpoints 解决了容器发现问题,但不提前知道 service 的 Cluster IP,怎么发现 service 服务呢?service 当前支持两种类型的服务发现机制,一种是通过环境变量,另一种是通过 DNS。在这两种方案中,建议使用后者:

在集群中部署 CoreDNS 服务, 来达到集群内部的 pod 通过DNS 的方式进行集群内部各个服务之间的通讯

当前 kubernetes 集群默认使用 CoreDNS 作为默认的 DNS 服务,主要原因是 CoreDNS 是基于 Plugin 的方式进行扩展的,简单,灵活,并且不完全被Kubernetes所捆绑

service 的使用

ClusterIP 方式

apiVersion: v1
kind: Service
metadata:
  name: my-nginx #service的名称,此名称会被DNS解析
spec:
  clusterIP: 10.105.146.177
  ports:
    - port: 80 # Service的端口号
      protocol: TCP
      targetPort: 8080 # 后端目标进程的端口号或名称,名称需由Pod规范定义
  selector:
    app: my-nginx
  sessionAffinity: None
  type: ClusterIP

NodePort 方式

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  ports:
    - nodePort: 30090 # 节点端口,kube-proxy监听本机此端口,将访问此端口的流量转发到service
      port: 80 # service端口
      protocol: TCP
      targetPort: 8080 # 目标pod端口
  selector:
    app: my-nginx
  sessionAffinity: None
  type: NodePort

Headless service(就是没有 Cluster IP 的 service )

当不需要负载均衡以及单独的 ClusterIP 时,可以通过指定 spec.clusterIP 的值为 None 来创建 Headless service,它会给一个集群内部的每个成员提供一个唯一的 DNS 域名来作为每个成员的网络标识,集群内部成员之间使用域名通信。

deployment中对应的服务是 service,而statefulset中与之对应的服务就是 headless service

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  clusterIP: None
  ports:
  - nodePort: 30090
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: my-nginx

Ingress

https://kubernetes.io/zh/docs/concepts/services-networking/ingress/
https://kubernetes.io/zh/docs/concepts/services-networking/ingress-controllers/

上面介绍的 ClusterIP 、NodePort 、LoadBalancer 都是基于ip和port做负载均衡,属于四层负载均衡

Ingress 可以作用于多个 service,被称为 service 的 service,作为集群内部服务的入口,Ingress其实就是七层的反向代理,可以根据不同的 url,将请求转发到不同的 service 上

Ingress 的结构如下图所示:

kube-proxy监听node的指定端口,将此端口的流量转发到ingress,然后ingress再转发到不同的service

反正都是实现七层代理,也可以使用deployment创建一组pod,提供nginx服务,实现代替ingress的效果:

kube-proxy监听node的指定端口,将此端口的流量转发到nginx service,然后nginx在转发到不同的service,可以配置service ip,也可以使用服务发现

建议:域名多、流量大,使用nginx;反之使用ingress

其他服务类型

完全不使用k8s内置的service,而是通过注册中心自动发现pod地址,如果开发有实力,推荐使用这种方式

网上的教程基本都给dumpcap设置suid,修改dumpcap的属组为wireshark,将whoami添加到wireshark附属组。其实后面的都是多余操作,只需给dumpcap设置suid即可。

sudo chmod u+s /usr/bin/dumpcap

什么是k8s?

kubernetes:容器的管理和编排系统

k8s由多个组件组成,部署在一群服务器之上,将所有服务器整合成一个资源池,然后向客户端提供各种接口, 客户端只需要调用相应接口就可以管理容器,至于底层容器具体跑在哪台服务器,不可见也不需要关心

针对各种特定的业务,尤其是比较依赖工程师经验的业务(例如数据库集群宕机后的数据恢复、各种集群的扩缩容),可以将一系列复杂操作代码化,这个代码在k8s中就叫operator,只要用好各种operator,就可以方便高效的解决各种问题,这样,运维工程师的主要工作就变成了维护k8s,确保k8s自身能够良好运行

k8s内置了各种operator,但是这些operator更重视通用性,很难完全匹配实际工作需要,所以就需要对k8s进行二次开发,编写各种operator,这也是SRE工程师的必备技能之一

operator能单独管理应用集群,实现复杂操作;controllor:控制器,只能实现简单的容器操作

开发人员为k8s开发应用程序的时候,通常不会完整的部署一个分布式k8s集群,而是使用一个叫做MiniKube,它可以在单机上模拟出一个完整意义上的k8s集群

节点

资源池中的各个服务器叫做节点,节点有两种:

  • Worker Node:运行Pod,一般称 Node
  • Master Node:运行控制平面组件,不运行pod,一般称为 Master

组件

k8s组件分为三种: 控制平面组件Node组件Addons

https://kubernetes.io/docs/concepts/overview/components/

控制平面组件

控制平面(control plane)管理Node和Node上的Pods

控制平面组件可以分别运行在任意节点上,但是为了简单,通常会将所有控制平面组件运行在同一个节点上,还可以以副本的形式运行在多个节点上,然后禁止在这种节点上运行pod,这种节点就叫做Master Node(后文简称Master),当集群中只有一个Master时,就是单控制平面,有多个Master时,就是多控制平面,生产中肯定是多控制平面,但是学习中一般只使用单控制平面

kube-apiserver

https服务器,监听在6443端口,它将k8s集群内的一切都抽象成资源,提供RESTful风格的API,对资源(对象)进行增删改查等管理操作

apiserver是整个k8s系统的总线,所有组件之间,都是通过它进行协同,它是唯一可以存储k8s集群的状态信息的组件(储存在Etcd)

生产中,apiserver需要做冗余,因为无状态,所以最少部署两个apiserver,因为是https服务器,所以需要做四层负载,使用Nginx、HAProxy、LVS均可,然后搭配keepalived给负载均衡做高可用

关于健康监测,分为AH(主动监测)和PH(被动监测或者叫异常值探测)

kube-controller-manager

控制器管理器,负责管理控制器,真正意义上让k8s所有功能得以实现的控制中心,controller-manager中有很多controller(deployment等数十种),这些controller才是真正意义上的k8s控制中心,负责集群内的Node、Pod副本、服务端点(Endpoint)、命名空间(Namespace)、服务账号(ServiceAccount)、资源定额(ResourceQuota)的管理

当某个Node意外宕机时,Controller Manager会及时发现并执行自动化修复流程,确保集群始终处于预期(yaml配置文件指定)的工作状态

k8s可以把运维人员日常重复性的工作代码化,就是将多controller打包起来,单一运行

默认监听本机的10252端口

controller loop:控制循环

kube-scheduler

调度器,调度pod,它的核心作用就是运行应用,scheduler时刻关注着每个节点的资源可用量,以及运行pod所需的资源量,让二者达到最佳匹配,让pod以最好的状态运行

在整个系统中起”承上启下”作用,承上:负责接收Controller Manager创建的新的Pod,为其选择一个合适的Node;启下:Node上的kubelet接管Pod的生命周期

通过调度算法为待调度Pod列表的每个Pod从可用Node列表中选择一个最适合的Node,并将信息写入etcd中node节点上的kubelet通过API Server监听到kubernetes Scheduler产生的Pod绑定信息,然后获取对应的Pod清
单,下载Image,并启动容器

优选策略:
1.LeastRequestedPriority
优先从备选节点列表中选择资源消耗最小的节点(CPU+内存)
2.CalculateNodeLabelPriority
优先选择含有指定Label的节点。
3.BalancedResourceAllocation
优先从备选节点列表中选择各项资源使用率最均衡的节点

etcd

https://etcd.io/
https://github.com/etcd-io/etcd

第三方、非k8s内置,它的目标是构建一个高可用的分布式键值(key-value)数据库,为避免脑裂,通常部署3个或5个节点

完全复制 	# 集群中的每个节点都可以使用完整的存档
高可用性 	# Etcd可用于避免硬件的单点故障或网络问题
一致性 		# 每次读取都会返回跨多主机的最新写入
简单   		# 包括一个定义良好、面向用户的API(gRPC)
安全   		# 实现了带有可选的客户端证书身份验证的自动化TLS
快速   		# 每秒10000次写入的基准速度
可靠   		# 使用Raft算法实现了存储的合理分布Etcd的工作原理

etcd有多个不同的API访问版本,v1版本已经废弃,etcd v2 和 v3 本质上是共享同一套 raft 协议代码的两个独立的应用,接口不一样,存储不一样,数据互相隔离。也就是说如果从 Etcd v2 升级到 Etcd v3,原来v2 的数据还是只能用 v2 的接口访问,v3 的接口创建的数据也只能访问通过 v3 的接口访问

以下以内容以 v3 为准

etcdctl 是 etcd 的命令行客户端工具

etcdctl [options] command [command options] [arguments...]
管理成员
etcdctl member list
etcdctl member add
etcdctl member promote
etcdctl member remove
etcdctl member update

验证成员状态:

$etcdctl endpoint health \
    --endpoints=https://10.0.1.31:2379 \
    --cacert=/etc/kubernetes/ssl/ca.pem \
    --cert=/etc/etcd/ssl/etcd.pem \
    --key=/etc/etcd/ssl/etcd-key.pem

# 多个成员写个遍历即可
增删改查
  • 增 put

    etcdctl put [options] <key> <value> (<value> can also be given from stdin) [flags]
    
    $etcdctl put /testkey "test data"
    OK
    
    $etcdctl get --print-value-only /testkey
    test data
  • 删 del

    etcdctl del [options] <key> [range_end] [flags]
    
    $etcdctl del /testkey
    1
  • 改 put

    直接覆盖即可

    $etcdctl put /testkey "test data2"
    OK
  • 查 get

    etcdctl get [options] <key> [range_end] [flags]
    
    $etcdctl get --print-value-only /testkey
    test data2
    
    $etcdctl get --prefix --keys-only /		# 获取所有key
    $etcdctl get --prefix --keys-only /calico
    $etcdctl get --prefix --keys-only /registry
    $etcdctl get --prefix --keys-only /registry/services
    $etcdctl get /calico/ipam/v2/handle/ipip-tunnel-addr-k8s-master.ljk.local
watch机制

etcd v3 的watch机制支持watch某个固定的key,也支持watch一个范围,发生变化就主动触发通知客户端

相比Etcd v2, Etcd v3的一些主要变化:

1. 接口通过grpc提供rpc接口,放弃了v2的http接口,优势是长连接效率提升明显,缺点是使用不如以前方便,尤其对不方便维护长连接的场景。
2. 废弃了原来的目录结构,变成了纯粹的kv,用户可以通过前缀匹配模式模拟目录
3. 内存中不再保存value,同样的内存可以支持存储更多的key
4. watch机制更稳定,基本上可以通过watch机制实现数据的完全同步
5. 提供了批量操作以及事务机制,用户可以通过批量事务请求来实现Etcd v2的CAS机制(批量事务支持if条件判断)

watch测试:

# 在 etcd node1 上watch一个key,没有此 key 也可以执行 watch,后期可以再创建
$etcdctl watch /testkey

# 在 etcd node2 修改数据,验证 etcd node1 是否能够发现数据变化
$etcdctl put /testkey "test for new"
OK
数据备份与恢复机制

v2 版本数据备份与恢复:

# 备份
etcdctl backup --data-dir /var/lib/etcd/ --backup-dir /opt/etcd_backup

# 恢复
etcd --data-dir=/var/lib/etcd/default.etcd --force-new-cluster &

v3 版本数据备份与恢复:

etcdctl snapshot <subcommand> [flags]

subcommand:save、restore、status
$etcdctl snapshot save snapshot.db		# 备份
...
Snapshot saved at snapshot.db
$file snapshot.db 
snapshot.db: data

# 恢复,将数据恢复到一个新的不存在的目录中
etcdctl snapshot restore snapshot.db --data-dir=/opt/etcd-testdir

cloud-controller-manager

略…

Node组件

Node组件运行在所有的节点上,包括Master

kubelet

与api server建立联系,监视api server中与自己node相关的pod的变动信息,执行指令操作

在 kubernetes 集群中,每个 Node 节点都会启动 kubelet 进程,处理 Master 节点下发到本节点的任务,管理 Pod 和其中的容器。kubelet 会在 API Server 上注册节点信息,定期向 Master 汇报节点资源使用情况,并通过cAdvisor(顾问)监控容器和节点资源,可以把 kubelet 理解成 Server/Agent 架构中的 agent,kubelet 是 Node上的 pod 管家

kube-porxy

https://kubernetes.io/zh/docs/concepts/services-networking/service/
https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kube-proxy/

守护进程,管理当前节点的 iptables 或 ipvs 规则,而且管理的只是和 service 相关的规则

监控 service,把集群上的每一个 service 的定义转换为本地的 ipvs 或 iptables 规则

kube-proxy 是运行在集群中的每个节点上的网络代理,实现了 Kubernetes 服务概念的一部分
kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话到 Pods 进行网络通信
kube-proxy 使用操作系统包过滤层(如果有的话)并且它是可用的。否则,kube-proxy 将自己转发流量

Container runtime

通常是docker,其他类型的容器也支持

Addons

附加组件扩展了Kubernetes的功能

插件使用Kubernetes资源(DaemonSet, Deployment,等等)来实现集群特性。因为它们提供了集群级的特性,所以插件的命名空间资源属于kube-system命名空间

DNS

CoreDNS,k8s中,DNS是至关重要的,所有的访问都不会基于ip,而是基于name,name再通过DNS解析成ip

Web UI

集群监控系统

prometheus

集群日志系统

EFK、LG

Ingress Controller

入栈流量控制器,是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。

附件千千万,按需部署

CNI

CNI:Container Network Interface,容器网络接口

kubernetes 的网络插件遵从 CNI 规范的 v0.4.0 版本

网络插件有很多,最常用的是 flannel 和 Project Calico,生产环境用后者的最多

跨主机 pod 之间通信,有两种虚拟网络,有两种:overlay 和 underlay

  • overlay:叠加网络 ,搭建隧道
  • underlay:承载网络,设置路由表

overlay

OverLay其实就是一种隧道技术,VXLAN,NVGRE及STT是典型的三种隧道技术,它们都是通过隧道技术实现大二层网络。将原生态的二层数据帧报文进行封装后在通过隧道进行传输。总之,通过OverLay技术,我们在对物理网络不做任何改造的情况下,通过隧道技术在现有的物理网络上创建了一个或多个逻辑网络即虚拟网络,有效解决了物理数据中心,尤其是云数据中心存在 的诸多问题,实现了数据中心的自动化和智能化

以 flannel 为例,默认网段 10.224.1.0/16,flannel 在每个节点创建一个网卡 flannel.1,这是一个隧道,网段10.2441.0/32 - 10.244.255/32,而每个节点上的容器的 ip 为10.244.x.1/24 - 10.244.x.254/24,也就是说 flannel 默认支持最多 256 个节点,每个节点上又最多支持 256 个容器

underlay

UnderLay指的是物理网络,它由物理设备和物理链路组成。常见的物理设备有交换机、路由器、防火墙、负载均衡、入侵检测、行为管理等,这些设备通过特定的链路连接起来形成了一个传统的物理网络,这样的物理网络,我们称之为UnderLay网络

UnderLay是底层网络,负责互联互通而Overlay是基于隧道技术实现的,overlay的流量需要跑在underlay之上

资源

Pod、Deployment、Service等等,反映在Etcd中,就是一张张的表

kube-apiserver 以群组分区资源,群组化管理的api使得其可以更轻松地进行扩展,常用的api群组分为两类:

  1. 命名群组:/apis/$GROUP_NAME/$VERSION,例如 /apis/apps/v1
  2. 核心群组core:简化了路径,/api/$VERSION,即 /api/v1

打印 kube-apiserver 支持的所有资源:

kubectl api-resources

对象

资源表中的每一条数据项就是一个对象,例如 Pod表中的数据项就是Pod对象。所以资源表通常不叫Pod表、Deployment表…,而是叫做PodList、DeploymentList…

创建对象

三种方式

  1. 命令式命令:命令,全部配置通过选项指定
  2. 命令式配置文件:命令,加载配置文件
  3. 声明式配置文件:声明式命令,加载配置清单,推荐使用

配置清单:

# 配置清单的规范叫做资源规范,范例:
apiVersion: v1
kind: Pod
metadata:
	name: myPod
	labels:
		app: mypod
		release: canary
spec:													# 期望状态
	containers:
	- name: demoapp
	  image: ikubernetes/demoapp:v1.0

资源清单是yml格式,api-server会自动转成json格式

如果实际状态和期望状态有出入,控制器的控制循环就会监控到差异,然后将需要做出的更改提交到apiserver,调度器scheduler监控到apiserver中有未执行的操作,就会去找适合执行操作的node,然后提交到apiserver,kubelet监控到apiserver中有关于自己节点的操作,就会执行操作,将执行结果返回给apiserver,apiserver再更新实际状态

查看对象

外部访问

domain:6643/apis/$GROUP_NAME/$VERSION/namespaces/$NAMESPACE/$NAME/$API_RESOURCE_NAME
domain:6643/api/$VERSION/namespaces/$NAMESPACE/$NAME/$API_RESOURCE_NAME

# 范例:
domain:6643/api/v1/namespaces/default/pods/demoapp-5f7d8f9847-tjn4v

内部访问

以下三种访问方式是一样的:

# 方式一,这种方式最方便
kubectl get pods net-tes1 -o json|yaml
# 方式二
kubectl get --raw /api/v1/namespaces/default/pods/net-test1
# 方式三,这种方式适用于监控
kubectl proxy	# 搭建代理
curl 127.0.0.1:8001/api/v1/namespaces/default/pods/net-test1	# 另起一终端

注:1.20之前的版本可以直接curl 127.0.0.1:8080,并且通过--insecure-port可以修改默认的8080端口,1.20.4之后的版本取消了这种不安全的访问方式,只能通过以上方式三,先kubectl proxy代理一下,默认端口也改成了8001

The kube-apiserver ability to serve on an insecure port, deprecated since v1.10, has been removed. The insecure address flags --address and --insecure-bind-address have no effect in kube-apiserver and will be removed in v1.24. The insecure port flags --port and --insecure-port may only be set to 0 and will be removed in v1.24. (#95856, @knight42, [SIG API Machinery, Node, Testing])

$kubectl get pods net-test1 -o yaml
 apiVersion: v1
 kind: Pod
>metadata:
>spec:
>status:

以上返回的数据,每个字段表示的意义可以通过 kubectl explain 查询帮助

kubectl explain <type>.<fieldName>[.<fieldName>]
$kubectl explain pod.apiVersion
KIND:     Pod
VERSION:  v1

FIELD:    apiVersion <string>

DESCRIPTION:
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

# 范例
$kubectl explain pod.kind
$kubectl explain pod.metadata
$kubectl explain pod.spec

lease 租约

lease 是分布式系统中一个常见的概念,用于代表一个分布式租约。典型情况下,在分布式系统中需要去检测一个节点是否存活的时,就需要租约机制。

上图示例中的代码示例首先创建了一个 10s 的租约,如果创建租约后不做任何的操作,那么 10s 之后,这个租约就会自动过期。接着将 key1 和 key2 两个 key value 绑定到这个租约之上,这样当租约过期时 etcd 就会自动清理掉 key1 和 key2,使得节点 key1 和 key2 具备了超时自动删除的能力。

如果希望这个租约永不过期,需要周期性的调用 KeeyAlive 方法刷新租约。比如说需要检测分布式系统中一个进程是否存活,可以在进程中去创建一个租约,并在该进程中周期性的调用 KeepAlive 的方法。如果一切正常,该节点的租约会一致保持,如果这个进程挂掉了,最终这个租约就会自动过期。

在 etcd 中,允许将多个 key 关联在同一个 lease 之上,这个设计是非常巧妙的,可以大幅减少 lease 对象刷新带来的开销。试想一下,如果有大量的 key 都需要支持类似的租约机制,每一个 key 都需要独立的去刷新租约,这会给 etcd 带来非常大的压力。通过多个 key 绑定在同一个 lease 的模式,我们可以将超时间相似的 key 聚合在一起,从而大幅减小租约刷新的开销,在不失灵活性同时能够大幅提高 etcd 支持的使用规模。

tomcat 日志

tomcat默认的格式比较简单,包含 ip、date、method、url、status code 等信息,例如:

10.0.0.1 - - [06/Mar/2021:09:26:09 +0800] "GET / HTTP/1.1" 200 11156
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /tomcat.svg HTTP/1.1" 200 67795
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /tomcat.css HTTP/1.1" 200 5542
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /bg-nav.png HTTP/1.1" 200 1401
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /bg-middle.png HTTP/1.1" 200 1918
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /bg-upper.png HTTP/1.1" 200 3103
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /bg-button.png HTTP/1.1" 200 713 
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /asf-logo-wide.svg HTTP/1.1" 200 27235
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /favicon.ico HTTP/1.1" 200 21630
10.0.0.1 - - [06/Mar/2021:09:26:12 +0800] "GET /manager/status HTTP/1.1" 403 3446
10.0.0.1 - - [06/Mar/2021:09:26:14 +0800] "GET /manager/html HTTP/1.1" 403 3446
10.0.0.1 - - [06/Mar/2021:09:26:19 +0800] "GET / HTTP/1.1" 200 11156
10.0.0.1 - - [06/Mar/2021:09:26:19 +0800] "GET /favicon.ico HTTP/1.1" 200 21630
  1. 为了kibana能单独统计每个字段,需要日志记录成json格式,当然也可以是其他格式,只要有对应的codec插件可以解析就行

    [root@elk2-ljk conf]$pwd
    /usr/local/tomcat/conf
    [root@elk2-ljk conf]$vim server.xml		# 自定义日志格式 为json格式
    ...
            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                   prefix="tomcat_access_log" suffix=".log"
                   pattern="{&quot;clientip&quot;:&quot;%h&quot;,&quot;ClientUser&quot;:&quot;%l&quot;,&quot;authenticated&quot;:&quot;%u&quot;,&quot;AccessTime&quot;: &quot;%t&quot;,&quot;method&quot;:&quot;%r&quot;,&quot;status&quot;:&quot;%s&quot;,&quot;SendBytes&quot;:&quot;%b&quot;,&quot;Query?string&quot;:&quot;%q&quot;,    &quot;partner&quot;:&quot;%{Referer}i&quot;,&quot;AgentVersion&quot;:&quot;%{User-Agent}i&quot;}" />
    ...
    
    # &quot; 表示双引号
    
    [root@elk2-ljk conf]$systemctl restart tomcat.service	# 重启tomcat
    # 查看日志,已经成功修改为json格式
    [root@elk2-ljk logs]$cat ../logs/tomcat_access_log.2021-03-06.log
    {"clientip":"10.0.0.1","ClientUser":"-","authenticated":"-","AccessTime":"[06/Mar/2021:12:50:56 +0800]","method":"GET / HTTP/1.1","status":"200","SendBytes":       "11156","Query?string":"","partner":"-","AgentVersion":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"}
    {"clientip":"10.0.0.1","ClientUser":"-","authenticated":"-","AccessTime":"[06/Mar/2021:12:50:56 +0800]","method":"GET /favicon.ico HTTP/1.1","status":"200",        "SendBytes":"21630","Query?string":"","partner":"http://10.0.1.122:8080/","AgentVersion":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like  Gecko) Chrome/88.0.4324.190 Safari/537.36"}
    {"clientip":"10.0.0.1","ClientUser":"-","authenticated":"-","AccessTime":"[06/Mar/2021:12:51:00 +0800]","method":"GET / HTTP/1.1","status":"200","SendBytes":       "11156","Query?string":"","partner":"-","AgentVersion":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"}

    百度搜索 “tomcat日志格式” 或 “apache日志格式”,下面是部分格式说明:

    %a - 远程IP地址
    %A - 本地IP地址
    %b - 发送的字节数,不包括HTTP头,或“ - ”如果没有发送字节
    %B - 发送的字节数,不包括HTTP头
    %h - 远程主机名
    %H - 请求协议
    %l (小写的L)- 远程逻辑从identd的用户名(总是返回' - ')
    %m - 请求方法
    %p - 本地端口
    %q - 查询字符串(在前面加上一个“?”如果它存在,否则是一个空字符串
    %r - 第一行的要求
    %s - 响应的HTTP状态代码
    %S - 用户会话ID
    %t - 日期和时间,在通用日志格式
    %u - 远程用户身份验证
    %U - 请求的URL路径
    %v - 本地服务器名
    %D - 处理请求的时间(以毫秒为单位)
    %T - 处理请求的时间(以秒为单位)
    %I (大写的i) - 当前请求的线程名称
    
    %{XXX}i xxx代表传入的头(HTTP Request)
    %{XXX}o xxx代表传出​​的响应头(Http Resonse)
    %{XXX}c  xxx代表特定的Cookie名
    %{XXX}r  xxx代表ServletRequest属性名
    %{XXX}s xxx代表HttpSession中的属性名
  2. logstash 配置文件:

    input {
        file {
            type => "tomcat-access-log"
            path => "/usr/local/tomcat/logs/tomcat_access_log.*.log"
            start_position => "end"
            stat_interval => 3
            codec => "json"
        }
    }
    
    output {
        if [type] == "tomcat-access-log" {
            elasticsearch { 
                hosts => ["10.0.1.121:9200"]
                index => "mytest-%{type}-%{+xxxx.ww}"
            }
        }
    }
  3. 重启logstash,注意要以root用户身份启动,否则无法采集数据

    [root@elk2-ljk conf.d]$systemctl restart logstash.service

java 日志

基于java开发的应用,都会有java日志,java日志会记录java的报错信息,但是一个报错会产生多行日志,例如:

为了方便观察,需要将一个报错的多行日志合并为一行,以elasticsearch为例:

input {
    file {
        type => "elasticsearch-java-log"
        path => "/data/elasticsearch/logs/es-cluster.log"
        start_position => "beginning"
        stat_interval => 3
        codec => multiline {
            pattern => "^\["
            negate => true
            what => "previous"
        }
    }   
}

output {
    elasticsearch { 
        hosts => ["10.0.1.121:9200"]
        index => "mytest-%{type}-%{+yyyy.MM}"
    }   
}

查看kibana:

nginx 访问日志

和采集tomcat日志类似,重点是把nginx日志修改为json格式

# 注意:log_format要写在server外面
[root@47105171233 vhost]$cat lujinkai.cn.conf 
log_format access_json '{"@timestamp":"$time_iso8601",'
'"host":"$server_addr",'
'"clientip":"$remote_addr",'
'"size":$body_bytes_sent,'
'"responsetime":$request_time,'
'"upstreamtime":"$upstream_response_time",'
'"upstreamhost":"$upstream_addr",'
'"http_host":"$host",'
'"url":"$uri",'
'"domain":"$host",'
'"xff":"$http_x_forwarded_for",'
'"referer":"$http_referer",'
'"status":"$status"}';

server {
  listen 80;
  server_name lujinkai.cn;
  access_log /data/wwwlogs/lujinkai.cn_nginx.log access_json;
  rewrite / http://blog.lujinkai.cn permanent;
}

# 日志成功转为json格式
[root@47105171233 wwwlogs]$tail -f lujinkai.cn_nginx.log 
{"@timestamp":"2021-03-06T18:20:13+08:00","host":"10.0.0.1","clientip":"113.120.245.191","size":162,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"lujinkai.cn","url":"/","domain":"lujinkai.cn","xff":"-","referer":"-","status":"301"}
{"@timestamp":"2021-03-06T18:20:13+08:00","host":"10.0.0.1","clientip":"113.120.245.191","size":162,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"lujinkai.cn","url":"/robots.txt","domain":"lujinkai.cn","xff":"-","referer":"-","status":"301"}

TCP/UDP 日志

场景:没有安装logstash的服务器(A),向安装了logstash的服务器(B)发送日志信息,这个场景不多

实现:A通过nc命令给B发送日志信息,B监听本地的对应端口,接收数据

A:客户端

[root@elk2-ljk ~]$nc 10.0.1.121 9889

B:服务端

input {
    tcp {
        port => 9889
        type => "tcplog"
        mode => "server"
    }
}

output {
    stdout {
    	codec => rubydebug
    }
}

通过 rsyslog 收集 haproxy 日志

有些设备无法安装logstash,例如 路由器、交换机,但是厂家内置了 rsyslog 功能,这里以haproxy为例

# 1. haproxy.cfg 定义haproxy的日志设备为local6
log 127.0.0.1 local6 info

# 2. rsyslog.conf
module(load="imudp")
input(type="imudp" port="514")
module(load="imtcp")
input(type="imtcp" port="514")
local6.* @@10.0.1.122

# 3. 10.0.1.122主机监听本机的udp514端口,接收日志数据
input{
    syslog {
    	type => "ststem-rsyslog"
    }
}
output{
    elasticsearch {
        hosts => ["10.0.1.123:9200"]
        index => "logstash-rsyslog-%{+YYYY.MM.dd}"
    }
}

filebeat 收集日志并写入 redis/kafka

考虑到elasticsearch的性能问题,通常不会直接往elasticsearch写日志,会加redis或kafka缓冲一下

  • 有少数场景,需要收集多个不同格式的日志,例如有的是syslog格式,有的是json格式,所有的日志都output到redis的一个list中,因为logstash的input没有条件判断,只能配置一个codec,所以logstash可以将不同的日志发送到elasticsearch的不同index,却无法对不同的日志格式配置不同的codec,这样的数据最后展示在kibana也没有意义,解决方法是在日志收集和日志缓存中间在加一个logstash(可以共用日志提取及过滤的logstash),将不同的日志转发到redis的不同list
  • 日志缓存如果用redis,需要大内存,推荐32G;如果用kafka,16G就够,因为kafka存储数据到磁盘

日志收集实战

从左向右看,当要访问 ELK 日志统计平台的时候,首先访问的是两台 nginx+keepalived 做的负载高可用,访问的地址是 keepalived 的 IP,当一台 nginx 代理服务器挂掉之后也不影响访问,然后 nginx 将请求转发到kibana,kibana 再去 elasticsearch 获取数据,elasticsearch 是两台做的集群,数据会随机保存在任意一台 elasticsearch 服务器,redis 服务器做数据的临时保存,避免 web 服务器日志量过大的时候造成的数据收集与保存不一致导致的日志丢失,可以临时保存到 redis,redis 可以是集群,然后再由 logstash 服务器在非高峰时期从 redis 持续的取出即可,另外有一台 mysql 数据库服务器,用于持久化保存特定的数据,web 服务器的日志由 filebeat 收集之后发送给另外的一台logstash,再有其写入到 redis 即可完成日志的收集,从图中可以看出,redis 服务器处于前端结合的最中间,其左右都要依赖于 redis 的正常运行,web 服务删个日志经过 filebeat 收集之后通过日志转发层的 logstash 写入到 redis 不同的 key当中,然后提取层 logstash 再从 redis 将数据提取并安按照不同的类型写入到elasticsearch 的不同 index 当中,用户最终通过 nginx 代理的 kibana 查看到收集到的日志的具体内容

通过坐标地图统计客户 IP 所在城市

https://www.elastic.co/guide/en/logstash/current/plugins-filters-geoip.html

日志写入数据库

写入数据库的目的是用于持久化保存重要数据,比如状态码、客户端 IP、客户端浏览器版本等等,用于后期按月做数据统计等

写个脚本从,定期从elasticsearch中获取数据,写入到 PostgreSQL

什么是ELK

https://www.elastic.co/cn/what-is/elk-stack

ELK 全称 ELK Stack,它的更新换代产品叫 Elastic Stack

ELK = Elasticsearch + Logstash + Kibana

  • Elasticsearch:搜索和分析引擎
  • Logstash:服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中
  • Kibana:让用户在 Elasticsearch 中使用图形和图表对数据进行可视化

什么是Elasticsearch

https://www.elastic.co/cn/what-is/elasticsearch

什么是 Logstash

Logstash 是 Elastic Stack 的核心产品之一,可用来对数据进行聚合和处理,并将数据发送到 Elasticsearch。Logstash 是一个开源的服务器端数据处理管道,允许您在将数据索引到 Elasticsearch 之前同时从多个来源采集数据,并对数据进行充实和转换。

什么是 kibana

https://www.elastic.co/cn/what-is/kibana

为什么使用ELK

ELK 组件在海量日志系统的运维中,可用于解决以下主要问题:

  • 分布式日志数据统一收集,实现集中式查询和管理
  • 故障排查
  • 安全信息和事件管理
  • 报表功能

elasticsearch

基本概念

参考博客:https://www.cnblogs.com/qdhxhz/p/11448451.html

重点理解 index 和 document 这两个概念:index(索引)类似kafka的topic,oss的bucket,要尽量控制 index 的数量;index中的单条数据称为document(文档),相当于mysql表中的行

之前的版本中,索引和文档中间还有个type(类型)的概念,每个索引下可以建立多个type,document存储时需要指定index和type,因为一个index中的type并不隔离,document不能重名,所以type并没有多少意义。从7.0版本开始,一个index只能建一个名为_doc的type,8.0.0 以后将完全取消

下面是一个document的源数据:

  • _index:文档所属索引名称
  • _type:文档所属类型名
  • _id:doc主键,写入时指定,如果不指定,则系统自动生成一个唯一的UUID值
  • _version:doc版本信息,保证doc的变更能以正确的顺序执行,避免乱序造成的数据丢失
  • _seq_no:严格递增的顺序号,shard级别严格递增,保证后写入的doc的_seq_no大于先写入的doc的_seq_no
  • _primary_term:和_seq_no一样是一个整数,每当primary shard发生重新分配时,比如重启,primary选举等,_primary_term会递增1
  • found:查询的ID正确那么ture, 如果 Id 不正确,就查不到数据,found字段就是false
  • _source:文档的原始JSON数据

apt安装

elasticsearch 集群中 master 与 slave 的区别:

master:统计各节点状态信息、集群状态信息统计、索引的创建和删除、索引分配的管理、关闭节点等
slave:从 master 同步数据、等待机会成为 master

  1. apt 安装

    [root@elk2-ljk src]$dpkg -i elasticsearch-7.11.1-amd64.deb

    主要目录:

    /usr/share/elasticsearch	# 主目录
    /etc/elasticsearch			# 配置文件目录
    ...
  2. 修改hosts

    [root@elk2-ljk src]$vim /etc/hosts
    ...
    10.0.1.121 elk1-ljk.local
    10.0.1.122 elk2-ljk.local
    10.0.1.123 elk3-ljk.local
  3. 修改配置文件 elasticsearch.yml

    [root@elk2-ljk /]$grep '^[a-Z]' /etc/elasticsearch/elasticsearch.yml
    cluster.name: es-cluster            # ELK的集群名称,名称相同即属于是同一个集群
    node.name: node-2                   # 当前节点在集群内的节点名称
    path.data: /data/elasticsearch/data # ES数据保存目录
    path.logs: /data/elasticsearch/logs # ES日志保存目
    bootstrap.memory_lock: true         # 服务启动的时候锁定足够的内存,防止数据写入swap
    network.host: 10.0.1.122            # 监听本机ip
    http.port: 9200                     # 监听端口
    # 集群中node节点发现列表,最好使用hostname,这里为了方便,使用ip
    discovery.seed_hosts: ["elk1-ljk.local", "elk2-ljk.local", "elk3-ljk.local"]
    # 集群初始化那些节点可以被选举为master
    cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
    gateway.recover_after_nodes: 2 # 一个集群中的 N 个节点启动后,才允许进行数据恢复处理,默认是 1
    # 设置是否可以通过正则或者_all 删除或者关闭索引库,默认 true 表示必须需要显式指定索引库名称,生产环境建议设置为 true,删除索引库的时候必须指定,否则可能会误删索引库中的索引库
    action.destructive_requires_name: true
  4. 修改内存限制

    [root@elk2-ljk src]$vim /usr/lib/systemd/system/elasticsearch.service
    ...
    LimitMEMLOCK=infinity	# 无限制使用内存
    [root@elk2-ljk src]$vim /usr/local/elasticsearch/config/jvm.options
    -Xms2g	# 最小内存限制
    -Xmx2g	# 最大内存限制
  5. 创建数据目录并修改属主

    [root@elk2-ljk src]$mkdir -p /data/elasticsearch
    [root@elk3-ljk src]$chown -R elasticsearch:elasticsearch /data/elasticsearch
  6. 启动

    [root@elk1-ljk src]$systemctl start elasticsearch.service 	# 稍等几分钟
    
    [root@elk1-ljk ~]$curl http://10.0.1.121:9200/_cat/nodes
    10.0.1.123 13 96 0 0.14 0.32 0.22 cdhilmrstw - node-3
    10.0.1.122 28 97 0 0.01 0.02 0.02 cdhilmrstw * node-2		# master
    10.0.1.121 26 96 2 0.13 0.07 0.03 cdhilmrstw - node-1

源码编译

启动总是失败,各种报错,解决不了…

安装 elasticsearch 插件

插件是为了完成不同的功能,官方提供了一些插件但大部分是收费的,另外也有一些开发爱好者提供的插件,可以实现对 elasticsearch 集群的状态监控与管理配置等功能

head 插件

在 elasticsearch 5.x 版本以后不再支持直接安装 head 插件,而是需要通过启动一个服务方式

github 地址:https://github.com/mobz/elasticsearch-head

# git太慢,这里用迅雷下载zip包,然后上传
[root@elk1-ljk src]$unzip master.zip
[root@elk1-ljk src]$cd elasticsearch-head-master/
[root@elk1-ljk elasticsearch-head-master]$npm install grunt -save
[root@elk1-ljk elasticsearch-head-master]$npm install	# 这一步要等很久
[root@elk1-ljk elasticsearch-head-master]$npm run start	# 前台启动

# 开启跨域访问支持,每个节点都需要开启
[root@elk3-ljk ~]$vim /etc/elasticsearch/elasticsearch.yml
...
http.cors.enabled: true
http.cors.allow-origin: "*"

[root@elk2-ljk games]$systemctl restart elasticsearch.service 	# 重启elasticsearch

kopf 插件

过时的插件,只支持 elasticsearc 1.x 或 2.x 的版本

cerebro 插件

新开源的 elasticsearch 集群 web 管理程序,需要 java11 或者更高版本

github 地址:https://github.com/lmenezes/cerebro

[root@elk2-ljk src]$unzip cerebro-0.9.3.zip
[root@elk2-ljk src]$cd cerebro-0.9.3/
[root@elk2-ljk cerebro-0.9.3]$vim conf/application.conf
...
# host列表
hosts = [ 
  {
    host = "http://10.0.1.122:9200"
    name = "es-cluster1"  # host的名称,如果有多个elasticsearch集群,可以用这个name区分
  #  headers-whitelist = [ "x-proxy-user", "x-proxy-roles", "X-Forwarded-For" ]
  }    
]

[root@elk2-ljk cerebro-0.9.3]$./bin/cerebro 	# 前台启动

监控 elasticsearch 集群状态

[root@elk3-ljk ~]$curl http://10.0.1.122:9200/_cluster/health?pretty=true
{
  "cluster_name" : "es-cluster",
  "status" : "green",	# green:运行正常、yellow:副本丢失、red:主分片丢失
  "timed_out" : false,
  "number_of_nodes" : 3,
  "number_of_data_nodes" : 3,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

如果是单节点,status会显示yellow,设置副本数为0即可解决:

[root@elk1-ljk ~]$curl -X PUT "10.0.1.121:9200/_settings" -H 'Content-Type: application/json' -d' {"number_of_replicas":0}'
{"acknowledged":true}

或者:

zabbix 添加监控

logstash

官方参考文档:https://www.elastic.co/guide/en/logstash/current/index.html

logstash是一个具有3个阶段的处理管道:输入 –> 过滤器 –> 输出 

输入生成事件,过滤器修改数据(日志),输出将数据(日志)发送到其他地方

安装

logstash 依赖 java,可以自己配置 java 环境,如果不配置,logstash 会使用其自带的 openjdk

[root@elk2-ljk src]$dpkg -i logstash-7.11.1-amd64.deb

# 修改启动用户为root,不然因为权限问题,后面会出现各种莫名其妙的错误,有些根本找不到报错
[root@elk2-ljk conf.d]$vim /etc/systemd/system/logstash.service
...
User=root
Group=root
...

命令

[root@elk2-ljk bin]$./logstash --help
  • -n:node name,就是节点的hostname,例如:elk2-ljk.local

  • -f:从特定的文件或目录加载logstash配置。如果给定一个目录,该目录中的所有文件将按字典顺序合并,然后作为单个配置文件进行解析。您还可以指定通配符(globs),任何匹配的文件将按照上面描述的顺序加载

  • -e:从命令行加载logstash配置,一般不用

  • -t:检查配置文件是否合法,配合-f使用,-f指定配置文件,-t检查

    # 示例:检查test.conf的合法性
    $/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/test.conf -t

插件

logstash 的输入和输出都依赖插件

不同的插件使用不同的配置,但是所有输入插件都支持以下配置选项:

配置项 类型 说明
add_field hash Add a field to an event
codec codec 用于输入数据的编解码器。输入编解码器是一种方便的方法,可以在数据进入输入之前对其进行解码,而不需要在Logstash管道中使用单独的过滤器
enable_metric boolean Disable or enable metric logging for this specific plugin instance by default we record all the metrics we can, but you can disable metrics collection for a specific plugin.
id string 唯一的ID,如果没有指定,logstash会自动生成一个,尤其是多个相同类型的插件时,强烈建议配置此项,例如有多个file输入,应当配置此项防止混淆
tags array Add any number of arbitrary tags to your event.
This can help with processing later.
type string 类型,例如收集 /var/log/syslog 日志,type可以设置为 “system”;收集网站日志,type可以设置为 “web”。
此项用的较多,一般会根据判断 type 值来进行输出或过滤

所有输出插件都支持以下配置选项:

配置项 类型 说明
codec codec 用于输出数据的编解码器。输出编解码器是一种方便的方法,可以在数据离开输出之前对数据进行编码,而不需要在Logstash管道中使用单独的过滤器
enable_metric boolean Disable or enable metric logging for this specific plugin instance. By default we record all the metrics we can, but you can disable metrics collection for a specific plugin.
id string 唯一的ID,如果没有指定,logstash会自动生成一个,尤其是多个相同类型的插件时,强烈建议配置此项,例如有多个file输出,应当配置此项防止混淆

可以看到input和output插件都支持codec配置项,input的codec根据被采集的日志文件确定,如果日志是json格式,则input插件的codec应当设置为json;而output的codec根据数据库确定,如果是elasticsearch,codec保持默认的rubydebug即可,如果是kafka,codec应该设置为json

input插件

stdin

标准输入

file

日志输出到文件

Setting 说明 备注
path 日志路径 必需
start_position 从文件的开头或者结尾开始采集数据 ["beginning", "end"]
stat_interval 日志收集的时间间隔 每个input文件都生成一个 .sincedb_xxxxx 文件,这个文件中记录了上次收集日志位置,下次从记录的位置继续收集

tcp

Setting 说明 备注
host 当mode是serverhost是监听的地址;
当mode是clienthost是要连接的地址
默认0.0.0.0
mode server:监听客户端连接;
client:连接到服务器;
[“server”, “client”]
port 监听的端口或要连接的端口 必需

kafka

Setting 说明 备注
bootstrap_servers host1:port1,host2:port2
topics 要订阅的topic列表,默认为[“logstash”]
decorate_events 是否添加一个 kafka元数据,包含以下信息:
topic、consumer_group、partition、offset、key
布尔值
codec 设置为 json

output插件

output

标准输出

elasticsearch

redis

Setting 说明 备注
key list(列表)名,或者channel(频道)名,
至于是哪个取决于data_type
data_type 如果data_typelist,将数据push到list;
如果data_type为channel,将数据发布到channel;
[“list”, “channel”]
host redis主机列表,可以是hostname或者ip地址 数组
port redis服务端口,默认 6379
db 数据库编号,默认0
password 身份认证,默认不认证 不建议设置密码

写个脚本统计redis的key数量,使用zabbix-agent定时执行脚本,一旦key超过某个数量,就增加logstash数量,从而加快从redis中取数据的速度

kafka

Setting 说明 备注
bootstrap_servers host1:port1,host2:port2
topic_id 主题
codec 设置为 json
batch_size The producer will attempt to batch records together into fewer requests whenever multiple records are being sent to the same partition. This helps performance on both the client and the server. This configuration controls the default batch size in bytes. 默认 16384

codec插件

multiline

合并多行,比如java的一个报错,日志中会记录多行,为了方便查看,应该将一个报错的多行日志合并成一行

Setting 说明 备注
pattern 正则匹配 必需
negate 匹配成功或失败,就开始多行合并 布尔值
what 如果模式匹配,向前多行合并,还是向后多行合并 必需,["previous", "next"]

配置

配置:https://www.elastic.co/guide/en/logstash/current/configuration.html
配置文件结构:https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html
配置文件语法:https://www.elastic.co/guide/en/logstash/current/event-dependent-configuration.html
使用环境变量:https://www.elastic.co/guide/en/logstash/current/environment-variables.html
配置文件示例:https://www.elastic.co/guide/en/logstash/current/config-examples.html
数据发送到es:https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html

input {
  ...
}

filter {
  ...
}

output {
  ...
}

多配置文件

https://www.elastic.co/guide/en/logstash/current/multiple-pipelines.html
https://elasticstack.blog.csdn.net/article/details/100995868

示例:

# 定义了两个管道
[root@elk2-ljk logstash]$cat pipelines.yml
- pipeline.id: syslog
  path.config: "/etc/logstash/conf.d/syslog.conf"
- pipeline.id: tomcat
  path.config: "/etc/logstash/conf.d/tomcat.conf"
  
# syslog.conf 和 tomcat.conf,没有使用条件判断语句做输出判断
[root@elk2-ljk conf.d]$cat syslog.conf 
input {
    file {
        type => "syslog"
        path => "/var/log/syslog"
        start_position => "end"
        stat_interval => 3
    }   
}

output {
    if [type] == "syslog" {
        elasticsearch { 
            hosts => ["10.0.1.121:9200"]
            index => "mytest-%{type}-%{+xxxx.ww}"
        }
    }
}
[root@elk2-ljk conf.d]$cat tomcat.conf 
input {
    file {
        type => "tomcat-access-log"
        path => "/usr/local/tomcat/logs/tomcat_access_log.*.log"
        start_position => "end"
        stat_interval => 3
    }   
}

output {
    elasticsearch { 
        hosts => ["10.0.1.121:9200"]
        index => "mytest-%{type}-%{+xxxx.ww}"
    }   
}

# 启动,注意一定要以为root用户启动
[root@elk2-ljk conf.d]$systemctl restart logstash.service

效果:

测试

标准输入输出

[root@elk2-ljk conf.d]$cat /etc/logstash/conf.d/test.conf 
input {
  stdin {}
}

output {
  stdout {}
}
[root@elk2-ljk conf.d]$/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/test.conf
...		# 启动得等一会
[INFO ] 2021-03-04 18:10:06.020 [Api Webserver] agent - Successfully started Logstash API endpoint {:port=>9600}
hello word	# 标准输入
{
    "@timestamp" => 2021-03-04T10:10:15.528Z,	# 当前事件的发生时间
          "host" => "elk2-ljk.local",			# 标记事件发生的节点
      "@version" => "1",						# 事件版本号,一个事件就是一个 ruby 对象
       "message" => "hello word"				# 息的具体内容
}

输出到文件

# 1. 修改被采集日志文件权限,至少让logstash用户可以读
[root@elk2-ljk conf.d]$chmod o+r /var/log/syslog
[root@elk2-ljk conf.d]$chmod o+r /var/log/auth.log

# 2. 编写配置文件,采集多个日志文件,输出到不同的文件
[root@elk2-ljk conf.d]$vim system-log.conf 
input {
    file {
        type => "syslog"
        path => "/var/log/syslog"
        start_position => "end"
        stat_interval => 5
    }
    file {
    	type => "authlog"
        path => "/var/log/auth.log"
        start_position => "end"
        stat_interval => 5
    }
}
output {
    if [type] == "syslog" {
        file {
            path => "/tmp/%{type}.%{+yyyy.MM.dd}"
        }
    }
    if [type] == "authlog" {
        file {
            path => "/tmp/%{type}.%{+yyyy.MM.dd}"
        }
    }
}

# 3. 检查配置文件是否合法
[root@elk2-ljk conf.d]$/usr/share/logstash/bin/logstash -f ./system-log.conf -t

# 4. 重启logstash.service
[root@elk2-ljk conf.d]$systemctl restart logstash.service

# 5. 观察输出文件
[root@elk2-ljk conf.d]$tail -f /tmp/syslog.2021.03.05
...
[root@elk2-ljk conf.d]$tail -f /tmp/authlog.2021.03.05
...

时间格式参考:http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html

输出到 elasticsearch

elasticsearch输出插件至少指定 hostsindexhosts可以指定hosts列表

input {
    file {
        type => "syslog"
        path => "/var/log/syslog"
        start_position => "end"
        stat_interval => 3
    }
}

output {
  elasticsearch { 
        hosts => ["10.0.1.121:9200"]
        index => "mytest-%{type}-%{+xxxx.ww}"
  }
}

kibana

开源的数据分析和可视化平台,可以 对 Elasticsearch 索引中的数据进行搜索、查看、交互操作,可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现

安装

[root@elk1-ljk src]$tar zxf kibana-7.11.1-linux-x86_64.tar.gz
[root@elk1-ljk src]$mv kibana-7.11.1-linux-x86_64 /usr/local/kibana
[root@elk1-ljk src]$cd /usr/local/kibana
[root@elk1-ljk kibana]$grep '^[a-Z]' config/kibana.yml 	# 修改以下配置项
server.port: 5601
server.host: "10.0.1.121"
elasticsearch.hosts: ["http://10.0.1.121:9200"]
i18n.locale: "zh-CN"

[root@elk1-ljk kibana]$./bin/kibana --allow-root	# 启动
# nohup ./bin/kibana --allow-root >/dev/null 2>&1 &		# 后台启动

因为笔记本的性能问题,将elasticsearch集群缩减为elasticsearch单节点,这导致kibana无法连接到elasticsearch,启动失败。解决办法:将elasticsearch的数据目录清空,然后重启

[root@elk1-ljk data]$systemctl stop elasticsearch.service 	# 停止elasticsearch
[root@elk1-ljk data]$ls /data/elasticsearch/
data  logs
[root@elk1-ljk data]$rm -rf /data/elasticsearch/*	# 清空数据目录
[root@elk1-ljk data]$systemctl start elasticsearch.service	# 重新启动elasticsearch
[root@elk1-ljk kibana]$./bin/kibana --allow-root	# 再次启动kibana

查看状态

kibana 画图功能详解

添加一个仪表盘

Beats

https://www.elastic.co/cn/beats/

logstash基于java,资源消耗很大,容器等场景,大多跑的都是轻量级的服务,没有必要安装logstash,就可以用beats代替logstash做日志收集,beats基于go,性能更强,资源占用更低,但是功能也相对简单

beats是一个系列,具体包含以下类型的采集器:

  • filebeat:轻量型日志采集器,最常用
  • metricbeat:轻量型指标采集器,获取系统级的 CPU 使用率、内存、文件系统、磁盘 IO 和网络 IO 统计数据,还可针对系统上的每个进程获得与 top 命令类似的统计数据
  • heartbeat:面向运行状态监测的轻量型采集器,通过 ICMP、TCP 和 HTTP 进行 ping 检测主机、网站可用性
  • packetbeat:轻量型网络数据采集器
  • winlogbeat:轻量型 Windows 事件日志采集器
  • auditbeat:轻量型审计日志采集器
  • functionbeat:面向云端数据的无服务器采集器

除了 filebeat,其他的beat可以用zabbix替代

filebeat

官方文档:https://www.elastic.co/guide/en/beats/filebeat/current/index.html

配置:只需要配置input和output

# ============================== Filebeat inputs ==================
filebeat.inputs:

- type: log							# 日志type
  enabled: false					# 是否启动
  paths:							# 被采集日志,可以写多个
    - /var/log/*.log
  exclude_lines: ['^DBG']			# 不采集日志中的哪些行
  include_lines: ['^ERR', '^WARN']	# 只采集日志中的哪些行
  exclude_files: ['.gz$']			# 从paths的匹配中排除哪些文件
  fields:							# 自定义字段,可以定义多个,后面用来做条件判断
    level: debug
    review: 1
  multiline.pattern: ^\[			# 合并多行,匹配规则
  multiline.negate: false			# 合并多行,配皮成功或失败执行合并
  multiline.match: after			# 合并多行,向前合并还是向后合并

# ============================== Filebeat modules ==============================

filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false
  reload.period: 10s

# ======================= Elasticsearch template setting =======================

setup.template.settings:
  index.number_of_shards: 1
  index.codec: best_compression
  _source.enabled: false

# ================================== General ===================================

#name:
#tags: ["service-X", "web-tier"]
#fields:
#  env: staging

# ================================= Dashboards =================================

#setup.dashboards.enabled: false
#setup.dashboards.url:

# =================================== Kibana ===================================

setup.kibana:
  #host: "localhost:5601"
  #space.id:

# =============================== Elastic Cloud ================================

#cloud.id:
#cloud.auth:

# ================================== Outputs ===================================
# 不同的output,有不同的配置,但是没有条件判断功能,无法根据不同的input使用不用的output
# ---------------------------- Elasticsearch Output ----------------------------
#output.elasticsearch:
  #hosts: ["localhost:9200"]
  #protocol: "https"
  #api_key: "id:api_key"
  #username: "elastic"
  #password: "changeme"

# ------------------------------ Logstash Output -------------------------------
#output.logstash:
  #hosts: ["localhost:5044"]
  #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
  #ssl.certificate: "/etc/pki/client/cert.pem"
  #ssl.key: "/etc/pki/client/cert.key"

# ================================= Processors =================================
processors:
  - add_host_metadata:
      when.not.contains.tags: forwarded
  - add_cloud_metadata: ~
  - add_docker_metadata: ~
  - add_kubernetes_metadata: ~

# ================================== Logging ===================================

#logging.level: debug
#logging.selectors: ["*"]

# ============================= X-Pack Monitoring ==============================

#monitoring.enabled: false
#monitoring.cluster_uuid:
#monitoring.elasticsearch:

# ============================== Instrumentation ===============================

#instrumentation:
    #enabled: false
    #environment: ""
    #hosts:
    #  - http://localhost:8200
    #api_key:
    #secret_token:

# ================================= Migration ==================================

#migration.6_to_7.enabled: true

示例:

metricbeat

heartbeat

SonarQube 是一个用于代码质量管理的开放平台,通过插件机制,SonarQube 可以集成不同的测试
工具,代码分析工具,以及持续集成工具,例如Hudson/Jenkins 等

官网:https://www.sonarqube.org/

部署SonarQube

略…

jenkins服务器部署扫描器 sonar-scanner

官方文档: https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/

部署sonar-scanner

顾名思义,扫描器的具体工作就是扫描代码,sonarqube通过调用扫描器sonar-scanner进行代码质量分析

下载地址: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/

[root@jenkins src]$unzip sonar-scanner-cli-4.6.0.2311.zip
[root@jenkins src]$mv sonar-scanner-4.6.0.2311/ /usr/local/sonar-scanner
[root@jenkins src]$vim /usr/local/sonar-scanner/conf/sonar-scanner.properties
sonar.host.url=http://10.0.1.102:9000	# 指向sonarqube服务器的地址
sonar.sourceEncoding=UTF-8		# Default source code encoding

准备测试代码

[root@jenkins src]$unzip sonar-examples-master.zip ^C
[root@jenkins src]$cd sonar-examples-master/projects/languages/php/php-sonar-runner
[root@jenkins php-sonar-runner]$ll
total 24
drwxr-xr-x 3 root root 4096 Mar  2 23:30 ./
drwxr-xr-x 4 root root 4096 Jul 25  2016 ../
-rw-r--r-- 1 root root  453 Jul 25  2016 README.md
-rw-r--r-- 1 root root  331 Jul 25  2016 sonar-project.properties
drwxr-xr-x 2 root root 4096 Jul 25  2016 src/
-rw-r--r-- 1 root root  272 Jul 25  2016 validation.txt
[root@jenkins php-sonar-runner]$cat sonar-project.properties 	# 确保有这个文件
# Required metadata
sonar.projectKey=org.sonarqube:php-simple-sq-scanner
sonar.projectName=PHP :: Simple Project :: SonarQube Scanner
sonar.projectVersion=1.0

# Comma-separated paths to directories with sources (required)
sonar.sources=src

# Language
sonar.language=php

# Encoding of the source files
sonar.sourceEncoding=UTF-8

在源代码目录执行扫描

在 sonar-project.properties 这个文件的目录下,执行 sonar-scanner 即可:

[root@jenkins php-sonar-runner]$ll
total 24
drwxr-xr-x 3 root root 4096 Mar  2 23:30 ./
drwxr-xr-x 4 root root 4096 Jul 25  2016 ../
-rw-r--r-- 1 root root  453 Jul 25  2016 README.md
-rw-r--r-- 1 root root  331 Jul 25  2016 sonar-project.properties
drwxr-xr-x 2 root root 4096 Jul 25  2016 src/	# 代码
-rw-r--r-- 1 root root  272 Jul 25  2016 validation.txt
[root@jenkins php-sonar-runner]$
[root@jenkins php-sonar-runner]$/usr/local/sonar-scanner/bin/sonar-scanner	# 测试
INFO: Scanner configuration file: /usr/local/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: /usr/local/src/sonar-examples-master/projects/languages/php/php-sonar-runner/sonar-project.properties
INFO: SonarScanner 4.6.0.2311
INFO: Java 11.0.10 Oracle Corporation (64-bit)
INFO: Linux 4.15.0-136-generic amd64
INFO: User cache: /root/.sonar/cache
INFO: Scanner configuration file: /usr/local/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: /usr/local/src/sonar-examples-master/projects/languages/php/php-sonar-runner/sonar-project.properties
INFO: Analyzing on SonarQube server 7.9.5
INFO: Default locale: "en_US", source code encoding: "UTF-8"
INFO: Load global settings
INFO: Load global settings (done) | time=225ms
INFO: Server id: 3B6AA649-AXfye5RyEWrAjeeRmPxd
INFO: User cache: /root/.sonar/cache
INFO: Load/download plugins
INFO: Load plugins index
INFO: Load plugins index (done) | time=126ms
INFO: Plugin [l10nzh] defines 'l10nen' as base plugin. This metadata can be removed from manifest of l10n plugins since version 5.2.
INFO: Load/download plugins (done) | time=3633ms
INFO: Process project properties
INFO: Execute project builders
INFO: Execute project builders (done) | time=18ms
INFO: Project key: org.sonarqube:php-simple-sq-scanner
INFO: Base dir: /usr/local/src/sonar-examples-master/projects/languages/php/php-sonar-runner
INFO: Working dir: /usr/local/src/sonar-examples-master/projects/languages/php/php-sonar-runner/.scannerwork
INFO: Load project settings for component key: 'org.sonarqube:php-simple-sq-scanner'
INFO: Load quality profiles
INFO: Load quality profiles (done) | time=293ms
INFO: Load active rules
INFO: Load active rules (done) | time=3061ms
WARN: SCM provider autodetection failed. Please use "sonar.scm.provider" to define SCM of your project, or disable the SCM Sensor in the project settings.
INFO: Indexing files...
INFO: Project configuration:
INFO: Load project repositories
INFO: Load project repositories (done) | time=19ms
INFO: 1 file indexed
INFO: Quality profile for php: Sonar way
INFO: ------------- Run sensors on module PHP :: Simple Project :: SonarQube Scanner
INFO: Load metrics repository
INFO: Load metrics repository (done) | time=134ms
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/root/.sonar/cache/866bb1adbf016ea515620f1aaa15ec53/sonar-javascript-plugin.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
INFO: Sensor JaCoCo XML Report Importer [jacoco]
INFO: Sensor JaCoCo XML Report Importer [jacoco] (done) | time=12ms
INFO: Sensor JavaXmlSensor [java]
INFO: Sensor JavaXmlSensor [java] (done) | time=7ms
INFO: Sensor HTML [web]
INFO: Sensor HTML [web] (done) | time=144ms
INFO: Sensor PHP sensor [php]
INFO: 1 source files to be analyzed
INFO: 1/1 source files have been analyzed
INFO: No PHPUnit test report provided (see 'sonar.php.tests.reportPath' property)
INFO: No PHPUnit coverage reports provided (see 'sonar.php.coverage.reportPaths' property)
INFO: Sensor PHP sensor [php] (done) | time=1652ms
INFO: Sensor Analyzer for "php.ini" files [php]
INFO: Sensor Analyzer for "php.ini" files [php] (done) | time=26ms
INFO: ------------- Run sensors on project
INFO: Sensor Zero Coverage Sensor
INFO: Sensor Zero Coverage Sensor (done) | time=21ms
INFO: No SCM system was detected. You can use the 'sonar.scm.provider' property to explicitly specify it.
INFO: Calculating CPD for 1 file
INFO: CPD calculation finished
INFO: Analysis report generated in 189ms, dir size=83 KB
INFO: Analysis report compressed in 17ms, zip size=14 KB
INFO: Analysis report uploaded in 1437ms
INFO: ANALYSIS SUCCESSFUL, you can browse http://10.0.1.102:9000/dashboard?id=org.sonarqube%3Aphp-simple-sq-scanner
INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
INFO: More about the report processing at http://10.0.1.102:9000/api/ce/task?id=AXfzlFbUEMwg_dNR3M3w
INFO: Analysis total time: 13.302 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 21.257s
INFO: Final Memory: 8M/40M
INFO: ------------------------------------------------------------------------
[root@jenkins php-sonar-runner]$

web 看测试结果:

jenkins 执行代码扫描

上面是命令行执行 sonar-scanner 命令进行测试,可以结合 jenkins 进行测试,无非就是将命令写到脚本里,让jenkins 自动执行

官方网站: https://jenkins.io/zh/

Jenkins是开源CI&CD软件领导者, 提供超过1000个插件来支持构建、部署、自动化, 满足任何项目的需要。

部署jenkins

Jenkins 支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序

安装 JDK

Jenkins 基于 JAVA 实现,安装 Jenkins 前需要先安装 JDK

[root@jenkins ~]$java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

安装 jenkins

$wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/debian-stable/jenkins_2.263.4_all.deb	# 清华源下载dep包
[root@jenkins src]$apt install daemon	# 依赖的包,如果没有,会报错,所以提前装上
[root@jenkins src]$ln -s /usr/local/jdk/bin/java /usr/bin/java # Jenkins只会在/bin:/usr/bin:/sbin:/usr/sbin找java,所以设置$PATH不好使,得做个软连接

[root@jenkins src]$dpkg -i jenkins_2.263.4_all.deb	# 安装

主要文件:

/etc/default/jenkins
/etc/init.d/jenkins
/etc/logrotate.d/jenkins
/usr/share/jenkins/jenkins.war	# 实际上就是启动这个war包
/var/cache/jenkins/
/var/lib/jenkins/
/var/log/jenkins/

修改 jenkins 服务的用户

默认jenkins服务使用 jenkins 帐号启动,将文件复制到生产服务器可能会遇到权限问题,需要先su到对应的用户,这里学习环境为了方便,修改为root用户,注意:工作中还是使用 jenkins 普通用户

[root@jenkins src]$vim /etc/default/jenkins 
#JENKINS_USER=$NAME
#JENKINS_GROUP=$GROUP
JENKINS_USER=root		# 直接修改为root,不要修改$NAME变量
JENKINS_GROUP=root

访问jenkins 页面

创建管理员用户

先创建用户,然后使用刚创建的用户登录

安装插件

Jenkins 是完全插件化的服务,我们想实现什么功能,不是改配置文件,而是下载插件

修改更新源为国内清华源:

[root@jenkins src]$cat /var/lib/jenkins/hudson.model.UpdateCenter.xml
<?xml version='1.1' encoding='UTF-8'?>
<sites>
  <site>
    <id>default</id>
    <url>https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json</url>
  </site>
</sites>
[root@jenkins src]$systemctl restart jenkins.service

有以下几种安装插件的方式:

  • 在线安装:官网,也是默认的方式,比较慢

  • 在线安装:清华大学镜像源,sed或者编辑器把 update-center.json 中的Jenkins网址替换为清华源

    # 将:
    https://updates.jenkins.io/download/plugins/warnings/5.0.1/warnings.hpi
    # 替换为:
    https://mirrors-i.tuna.tsinghua.edu.cn/jenkins/plugins/warnings/5.0.1/warnings.hpi
  • 离线安装:手动下载插件,然后放到 /var/lib/jenkins/plugins/,重启 Jenkins 即可

  • 通过web界面安装

搜索需要 gitlab(和gitlab相连)和 Blue Ocean(显示信息更加好看)的相关插件并安装

配置 jenkins 权限管理

默认jenkins用户可以执行所有操作,为了更好的分层控制,可以实现基于角色的权限管理,先创建角色和用户,给角色授权,然后把用户管理到角色

安装插件

安装插件:Role-based Authorization Strategy

如果直接下载失败,可以直接清华源下载,将插件放在 /var/lib/jenkins/plugins 目录下

创建新用户

新建 xiaoming 和 xiaogang 两个用户:

更改认证方式

新建任务

通常选择“构建一个自由风格的软件项目”,新建四个任务:test1-job1、test1-job2、test2-job1、test2-job2

创建角色并对角色分配权限

  1. 创建全局角色
    创建全局读角色,只有读权限

  2. 创建 item 角色
    创建两个 item 角色,test1-role、test2-role,通过正则匹配分配任务

    ![](https://img.lujinkai.cn/blog/ljk/1614438723442.png

将用户关联到角色

测试普通用户登录

xiaogang 用户只能看到 test1.* 匹配的项目:test1-job1、test1-job2

xiaoming 用户只能看到 test2.*匹配的项目:test2-job1、test2-job2

jenkins 邮箱配置

邮件发送到组邮件地址,组邮件会转发到组内所有人

配置jenkins到gitlab非交互式拉取代码

需要配置 ssh key (公钥)和 凭据(私钥)

配置 ssh key

实现 jenkins 服务器到 gitlab 服务器的基于密钥的验证,可以让 jenkins 连接到 gitlab 执行操作

  1. 在 jenkins 服务上生成 ssh key
  2. 在 gitlab 服务器上添加上面生成的 ssh key
  3. 在 jenkins 服务器上测试 ssh key

配置凭据

凭据就是私钥,clone项目的时候,可选择不同的凭据(私钥),只要他在gitlab上配置了key(公钥),如果不指定,就以当前用户的私钥作为凭据去clone项目

管理凭据:系统管理 -> 安全 -> Manage Credentials

构建触发器

构建触发器(webhook),有的人称为钩子,实际上是一个HTTP回调,其用于在开发人员向 gitlab 提交代码后能够触发 jenkins 自动执行代码构建操作

以下为新建一个开发分支,只有在开发人员向开发(develop)分支提交代码的时候才会触发代码构建,而向主分支提交的代码不会自动构建,需要运维人员手动部署代码到生产环境

生产中千万不要用,测试一般也不用,都是由开发登陆自己的账号,只能看到自己的job,然后开发自己手动部署

项目关联

用于多个 job 相互关联,需要串行执行多个 job 的场景,不过这个操作基本不用

例如:克隆代码、编译代码、停止tomcat、部署代码、启动tomcat 这些操作使用不同的job执行,这些job需要串行执行

视图

job 太多需要分类

视图可用于归档 job 进行分组显示,比如将一个业务的 job 放在一个视图显示,最常用的是列表视图

jenkins 分布式

在众多Job的场景下,单台jenkins master同时执行代码clone、编译、打包及构建,其性能可能会出现瓶颈从而会影响代码部署效率,影响jenkins官方提供了jenkins分布式构建,将众多job分散运行到不同的jenkins slave节点,大幅提高并行job的处理能力

配置slave节点环境

slave节点需要配置与master一样的基础运行环境,另外也要创建与master相同的数据目录,因为脚本中调用的路径只有相对于master的一个路径,此路径在master与各node节点必须保持一致

# 配置java环境
[root@jenkins-slave1 ~]$java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

[root@jenkins-slave1 ~]$ln -s /usr/local/jdk/bin/java /usr/bin/java

# 创建数据目录
[root@jenkins-slave1 ~]$mkdir -p /var/lib/jenkins

添加slave节点

可以限制项目只能在指定的节点中执行:

流水线 pipline

流水线 pipline 是 Jenkins 中的头等公民,官方介绍;https://www.jenkins.io/zh/doc/book/pipeline/

本质上,Jenkins 是一个自动化引擎,它支持许多自动模式。 流水线向Jenkins中添加了一组强大的工具, 支持用例 简单的持续集成到全面的CD流水线。通过对一系列的相关任务进行建模, 用户可以利用流水线的很多特性:

  • 代码:流水线是在代码中实现的,通常会检查到源代码控制,使团队有编辑,审查和迭代他们的交付流水线的能力
  • 可持续性:jenkins的重启或者中断后不影响已经执行的Pipline Job
  • 支持暂停:pipline可以选择停止并等待人工输入或批准后再继续执行
  • 可扩展:通过groovy的编程更容易的扩展插件
  • 并行执行:通过groovy脚本可以实现step,stage间的并行执行,和更复杂的相互依赖关系

流水线概念

流水线 pipeline

流水线是用户定义的一个CD流水线模型 。流水线的代码定义了整个的构建过程, 他通常包括构建, 测试和交付应用程序的阶段

节点 node

每个 node 都是一个 jenkins 节点,可以是 jenkins master 也可以是 jenkins agent,node 是执行 step 的具体服务器

阶段 stage

一个 pipline 可以划分为若干个 stage,每个 stage 都是一个操作,比如 clone 代码、代码编译、代码测试和代码部署,阶段是一个逻辑分组,可以跨多个 node 执行

步骤 step

step 是 jenkins pipline 最基本的操作单元,从在服务器创建目录到构建容器镜像,由各类 Jenkins 插件提供实现,例如: sh “make”

流水线语法概述

对 Jenkins 流水线的定义被写在一个文本文件中: Jenkinsfile,该文件可以被提交到项目的源代码的控制仓库。 这是”流水线即代码”的基础;将CD 流水线作为应用程序的一部分,像其他代码一样进行版本化和审查。

Jenkinsfile 能使用两种语法进行编写,声明式和脚本化。

声明式和脚本化的流水线从根本上是不同的。 声明式流水线的是 Jenkins 流水线更近的特性

声明式流水线基础

在声明式流水线语法中, pipeline 块定义了整个流水线中完成的所有的工作。

示例:

Jenkinsfile (Declarative Pipeline)
pipeline { 
    agent any 
    stages {
        stage('Build') { 
            steps { 
                sh 'make' 
            }
        }
        stage('Test'){
            steps {
                sh 'make check'
                junit 'reports/**/*.xml' 
            }
        }
        stage('Deploy') {
            steps {
                sh 'make publish'
            }
        }
    }
}
  • pipeline 是声明式流水线的一种特定语法,他定义了包含执行整个流水线的所有内容和指令的 “block” 。
  • agent是声明式流水线的一种特定语法,它指示 Jenkins 为整个流水线分配一个执行器 (在节点上)和工作区。
  • stage 是一个描述 stage of this Pipeline的语法块。在 Pipeline syntax 页面阅读更多有关声明式流水线语法的stage块的信息。如 above所述, 在脚本化流水线语法中,stage 块是可选的。
  • steps 是声明式流水线的一种特定语法,它描述了在这个 stage 中要运行的步骤。
  • sh 是一个执行给定的shell命令的流水线 step (由 Pipeline: Nodes and Processes plugin提供) 。
  • junit 是另一个聚合测试报告的流水线 step (由 JUnit plugin提供)。
  • node 是脚本化流水线的一种特定语法,它指示 Jenkins 在任何可用的代理/节点上执行流水线 (和包含在其中的任何阶段)这实际上等效于 声明式流水线特定语法的agent

脚本化流水线基础

pipline 语法

官方文档:https://www.jenkins.io/zh/doc/book/pipeline/syntax/

pipline job

创建pipline job

测试简单pipline job运行

node {
    stage('clone 代码') {
        echo '代码 clone'
    }
    stage('代码构建') {
        echo '代码构建'
    }
    stage('代码测试') {
        echo '代码测试'
    }
    stage('代码部署') {
        echo '代码部署'
    }
}

立即构建,查看输出信息:

自动生成拉取代码的pipline脚本

案例

说明

master分支:稳定的分支
develop分支:开发分支

lujinkai:开发人员分支
xiaoming:开发人员分支
...

master分支是上线的代码,develop是开发中的代码,lujinkai、xiaoming等是开发人员分支,开发在自己的分支里面写代码,然后下班前提交、合并到develop分支,等项目开发、测试完,最后再合并到master分支,然后上线

常用命令

[root@kafka2 testproject]$git branch		# 查看本地分支
* master	# 分支前标 * 表示当前处于的分支

[root@kafka2 testproject]$git branch -a		# 查看所有分支
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/ludongsheng
  remotes/origin/master
  
git status      # 查看工作区的状态
git branch test # 创建test分支
git merge test  # 将test分支合并到当前分支
git log         # 查看操作日志
git reflog      # 获取每次提交的ID,可以使用--hard根据提交的ID进行版本回退

vim .gitignore          	# 定义忽略文件,即不放在仓库的文件

git reset --hard HEAD^^ 	# git版本回滚, HEAD为当前版本,加一个^为上一个,^^为上上一个版本

git reset --hard 5ae4b06 	# 回退到指定id的版本

git checkout -b develop 	# 创建并切换到一个新分支
git checkout develop    	# 切换分支

git clone -b 分支名 仓库地址	# clone指定分支的代码

流程

  1. 在 gitlab 创建个人分支 lujinkai

  2. 克隆 master 分支到本地

    [root@dev ~]$git clone git@gitlab.ljk.local:testgroup/testproject.git
  3. 切换到自己分支

    [root@dev ~]$git checkout lujinkai
  4. 将develop分支拉下来和个人分支合并

    [root@dev ~]$git pull origin develop
    
    # git pull <远程主机名> <远程分支名>:<本地分支名>
    # 如果与当前分支合并,则冒号后面的部分可以省略

    以上命令是把远程分支develop拉下来,然后合并到当前分支lujinkai,或者可以拆分成以下步骤:

    git checkout develop			# 切换到分支develop
    git pull origin develop			# 远程develop拉下来和本地develop合并
    git checkout lujinkai			# 切换到分支lujinkai
    git merge develop --no-commit	# 将本地分支develop和当前分支lujinkai合并
  5. 开发代码

  6. 下班了,需要提交代码,但是在提交之前最好再执行一遍上一步,因为在你写代码的过程中,develop分支可能有其他人提交

    [root@dev ~]$git pull origin develop
  7. 添加文件到本地缓存区

    [root@dev ~]$git add .		# . 表示当前目录下的所有文件
  8. 提交内容到本地分支上

    git commit -m "注释, 提交说明"
  9. 上传本地分支到远程分支

    git push
    
    # 默认提交到本地分支对应的远程分支,或者可以显式指定
    git push origin lujinkai

之后每天 3 - 9 步骤走一遍

清除master分支的commit记录

  1. 克隆仓库 (这时工作目录里是master分支最后一次提交的内容)
  2. 创建一个新的空的分支
  3. 添加工作目录里所有文件到新的分支并做一次提交
  4. 删除master分支
  5. 将新的分支更名为master
  6. 强制更新到github仓库
git clone [URL] && cd [仓库名]           # 克隆git仓库,进入仓库
git checkout --orphan  new_branch		# 创建一个新的空的分支
git add -A								# 添加工作目录里所有文件到新的分支
git commit -am 'v1' 					# 做一次提交
git branch -D master					# 删除master分支
git branch -m master					# 将新的分支更名为master
git push origin master --force			# 强制更新到github仓库

DevOps

DevOps 是Development和Operations的组合,也就是开发和运维的简写

什么是持续集成(CI-Continuous integration)

持续集成是指多名开发者在开发不同功能代码的过程当中,可以频繁的将代码行合并到一起并切相互不影响工作

什么是持续部署(CD-continuous deployment)

是基于某种工具或平台实现代码自动化的构建、测试和部署到线上环境以实现交付高质量的产品,持续部署在某种程度上代表了一个开发团队的更新迭代速率

什么是持续交付(Continuous Delivery)

持续交付是在持续部署的基础之上,将产品交付到线上环境,因此持续交付是产品价值的一种交付,是产品价值的一种盈利的实现

GitLab

GitLab 和 GitHub一样属于第三方基于Git开发的作品,免费且开源,与Github类似,可以注册用户,任意提交你的代码,添加SSHKey等等。不同的是,GitLab是可以部署到自己的服务器上,简单来说可把 GitLab 看作个人版的GitHub

Git在每个用户都有一个完整的服务器,然后再有一个中央服务器,用户可以先将代码提交到本地,没有网络也可以先提交到本地,然后在有网络的时候再提交到中央服务器,这样就大大方便了开发者,而相比CVS和SVN都是集中式的版本控制系统,工作的时候需要先从中央服务器获 取最新的代码,改完之后需要提交,如果是一个比较大的文件则需要足够快的网络才能快速提交完成,而使用分布式的版本控制系统,每个用户都是一个完整的版本库,即使没有中央服务器也可以提交代码或者回滚,最终再把改好的代码提交至中央服务器进行合并即可

SVN

每次提交的文件都单独保存, 即按照文件的提交时间区分不同的版本, 保存至不同的逻辑存储区域,后期恢复时候直接基于之前版本恢复。


Git

Gitlab 与 SVN的数据保存方式不一样,gitlab 虽然 也会在内部 对数据进行逻辑划分保存,但是当后期提交的数据如和之前交的数据没有变化,其就直接 快照之前的文件 ,而不是再将文件重新上传一份再保存一遍,这样既节省 了空间又加快了代码提交速度 。

git 缓存区与工作区等概念

  • 工作区 :clone 的代码或者开发自己编写代码文件所在 的目录 ,通常是代码所在的一 个服务的目录名称
  • 暂存区 :用于存储在工作区中对代码进行修改后的文件所保存的地方,使用 git add 添加
  • 本地仓库: 用于提交存储在工作区和暂存区中改过的文件地方,使 git commit 提交
  • 远程仓库 :多个开发人员共同协作提交代码的仓库,即 gitlab 服务器

Gitlab部署与使用

测试环境:内存4G以上
生产环境:建议CPU2C,内存8G,磁盘10G以上配置,和用户数有关

清华源 centos7:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/
清华源 ubuntu18.04:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/bionic/main/g/gitlab-ce/

安装

[root@gitlab src]$dpkg -i gitlab-ce_13.8.4-ce.0_amd64.deb	# 得等一会
...
configuration in /etc/gitlab/gitlab.rb file.	# 配置文件
...

配置

# 需要配置gitlab服务器地址和邮件地址
[root@gitlab src]$vim /etc/gitlab/gitlab.rb
external_url 'http://gitlab.ljk.local'		# 修改此行
# 增加下面行,可选邮件通知设置
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.qq.com"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "441757636@qq.com"
gitlab_rails['smtp_password'] = "jwopcvnpcawdabbg"
gitlab_rails['smtp_domain'] = "qq.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = true
gitlab_rails['gitlab_email_from'] = "441757636@qq.com"

gitlab 相关目录:

/etc/gitlab     	#配置文件目录
/run/gitlab     	#运行pid目录
/opt/gitlab     	#安装目录
/var/opt/gitlab 	#数据目录
/var/log/gitlab 	#日志目录

初始化服务

修改完配置文件要执行此操作

[root@gitlab src]$gitlab-ctl reconfigure

常用命令:

gitlab-rails          #用于启动控制台进行特殊操作,如修改管理员密码、打开数据库控制台( gitlab-rails dbconsole)等
gitlab-psql           #数据库命令行
gitlab-rake           #数据备份恢复等数据操作

gitlab-ctl            #客户端命令行操作行
gitlab-ctl stop       #停止gitlab
gitlab-ctl start      #启动gitlab
gitlab-ctl restar     #重启gitlab
gitlab-ctl status     #查看组件运行状态
gitlab-ctl tail nginx #查看某个组件的日志

gitlab web 界面

username:root
注意,使用域名访问需要做hosts

关闭账号注册

默认情况下可以直接注册账号,一般都关闭此功能,由管理员统一注册用户

修改邮箱地址

新添加的邮箱不是默认的通知邮箱,下面是设置新邮箱为默认邮箱

设置完后,需要重新登录才能生效,然后可以把之前的默认邮箱删除

创建gitlab账户

创建成功后,需要查看邮件,在邮件链接中重置密码

创建组

使用管理员root创建组,一个组里面可以有多个项目分支,可以将开发添加到组里,再进行设置权限,不同的组对应公司不同的开发项目或者服务模块,不同的组中添加不同的开发人员帐号,即可实现对开发设置权限的管理

管理员创建项目

有三种方式:创建空项目、使用模板、导入项目

将用户添加到组

更多权限:https://docs.gitlab.com/ee/user/permissions.html

gitlab仓库developer权限无法push

默认develop权限无法merge和push到master分支的,可以修改:

在这里,也可以设置也可以保护其他分支

在项目中新建测试页面

使用 陆东生 用户登录,新建分支,继承自master:

git客户端测试clone项目

  1. ssh认证

    ssh-keygen -t rsa
    cat ~/.ssh/id_rsa.pub	# 将公钥复制到给gitlab
  2. clone

    git clone git@gitlab.ljk.local:testgroup/testproject.git

git 常用命令

运维常用:git pullgit clonegit reset

.gitignore

有些文件不需要上传,在 .gitignore 文件中设置忽略规则,示例:

# 项目根目录下创建.gitignore,规则如下:忽略.vscode目录和.gitignore文件
$ cat .gitignore	
/.vscode/
.gitignore

# 在src目录下创建.gitignore文件,规则如下:忽略全部,只保留.gitignore
# 目的是只保留src空目录
$ cat ./src/.gitignore	
*
!.gitignore

修改完 .gitignore 文件后,更新缓存:

git rm -r --cached .
git add .

gitlab数据备份恢复

  1. 备份前必须先停止gitlab两个服务

    [root@gitlab ~]$gitlab-ctl stop unicorn
    [root@gitlab ~]$gitlab-ctl stop sidekiq
  2. 备份数据

    [root@gitlab ~]$gitlab-rake gitlab:backup:create
    ...
    Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data 
    and are not included in this backup. You will need these files to restore a backup.
    Please back them up manually.
    
    # 以上warning表示gitlab.rb和gitlab-secrets.json两个文件包含敏感信息。未被备份到备份文件中。需要手动备份,这两个文件位于 /etc/gitlab
    
    [root@gitlab ~]$gitlab-ctl start	# 备份完成后启动gitlab

    备份数据位于 /var/opt/gitlab/backups/

    [root@gitlab ~]$ls /var/opt/gitlab/backups/
    1614392268_2021_02_27_13.8.4_gitlab_backup.tar
  3. 查看要恢复的文件

    /var/opt/gitlab/backups/   # Gitlab数据备份目录,需要使用命令备份的
    /var/opt/gitlab/nginx/conf # nginx配置文件
    /etc/gitlab/gitlab.rb      # gitlab配置文件
  4. 删除项目和用户信息

  5. 执行恢复

    # 恢复前先停止两个服务
    [root@gitlab ~]$gitlab-ctl stop unicorn
    [root@gitlab ~]$gitlab-ctl stop sidekiq
    # 恢复时指定备份文件的时间部分,不需要指定文件的全名
    [root@gitlab ~]$gitlab-rake gitlab:backup:restore BACKUP=备份文件名
  6. 恢复后再将之前停止的两个服务启动

    gitlab-ctl start

gitlab汉化

虽然不推荐,但是有需求,基于第三方开发爱好者实现

汉化包地址: https://gitlab.com/xhang/gitlab

git clone https://gitlab.com/xhang/gitlab.git
head -1 /opt/gitlab/version-manifest.txt	# 查看当前gitlab版本
cd gitlab
git diff v11.9.8 v11.9.8-zh
git diff v11.9.8 v11.9.8-zh >/root/v11.9.8-zh.diff
gitlab-ctl stop
patch -f -d /opt/gitlab/embedded/service/gitlab-rails -p1 </root/v11.9.8-zh.diff
gitlab-ctl reconfigure
gitlab-ctl start
gitlab-ctl status

常见的代码部署方式

蓝绿部署

不停老版本代码(不影响上一个版本访问),而是在另外一套环境部署新版本然后进行测试,测试通过后将用户流量切到新版本,其特点为业务无中断,升级风险相对较小

具体过程:

  1. 当前版本业务正常访问(V1)
  2. 在另外一套环境部署新代码(V2),代码可能是增加了功能或者是修复了某些bug
  3. 测试通过之后将用户请求流量切到新版本环境
  4. 观察一段时间,如有异常直接切换旧版本,没有异常就删除老版本
  5. 下次升级,将旧版本升级到新版本(V3)

金丝雀发布

金丝雀发布也叫灰度发布,是指在黑与白之间,能够平滑过渡的一种发布方式,灰度发布是增量发布的一种类型,灰度发布是在原有版本可用的情况下,同时部署一个新版本应用作为“金丝雀”(小白鼠),测试新版本的性能和表现,以保障整体系统稳定的情况下,尽早发现、调整问题。因此,灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度

具体过程:

  1. 准备好部署各个阶段的工件,包括:构建组件,测试脚本,配置文件和部署清单文件。
  2. 从负载均衡列表中移除掉“金丝雀”服务器。
  3. 升级“金丝雀”应用(排掉原有流量并进行部署)。
  4. 对应用进行自动化测试。
  5. 将“金丝雀”服务器重新添加到负载均衡列表中(连通性和健康检查)。
  6. 如果“金丝雀”在线使用测试成功,升级剩余的其他服务器。(否则就回滚)

滚动发布

滚动发布,一般是取出一个或者多个服务器停止服务,执行更新,并重新将其投入使用。周而复始,直到集群中所有的实例都更新成新版本,此方式可以防止同时升级,造成服务停止

A/B测试

A/B测试也是同时运行两个APP环境,但是和蓝绿部署完全是两码事,A/B 测试是用来测试应用功能表现的方法,例如可用性、受欢迎程度、可见性等等,蓝绿部署的目的是安全稳定地发布新版本应用,并在必要时回滚,即蓝绿部署是同一时间只有一套正式环境环境在线,而A/B测试是两套正式环境同时在线,一般用于多个产品竟争时使用

Nexus 是一个强大的 Maven 仓库管理器,它极大地简化了自己内部仓库的维护和外部仓库的访问

官方下载:https://help.sonatype.com/repomanager3/download/download-archives---repository-manager-3

部署Nexus

  1. 下载、解压、创建用户

    [root@nexus src]$useradd -r -s /sbin/nologin nexus		# 创建nexus用户
    [root@nexus src]$tar zxf nexus-3.29.2-02-unix.tar.gz 
    [root@nexus src]$mv nexus-3.29.2-02 /usr/local/nexus
    [root@nexus src]$mv sonatype-work/ /usr/local/
    [root@nexus src]$cd /usr/local/
    [root@nexus local]$chown -R nexus:nexus ./nexus/
    [root@nexus local]$chown -R nexus:nexus ./sonatype-work/
    [root@nexus local]$echo 'nexus - nofile 65536' >> /etc/security/limits.conf
  2. Service启动文件,官方提供

    [root@nexus ~]$cat /lib/systemd/system/nexus.service 
    [Unit]
    Description=nexus service
    After=network.target
      
    [Service]
    Type=forking
    LimitNOFILE=65536
    ExecStart=/usr/local/nexus/bin/nexus start
    ExecStop=/usr/local/nexus/bin/nexus stop
    User=nexus
    Restart=on-abort
    TimeoutSec=600
      
    [Install]
    WantedBy=multi-user.target
    
    [root@nexus ~]$systemctl start nexus.service	# 需要几分钟时间启动
  3. 设置向导:

  4. 验证默认仓库

    • Hosted:本地仓库,通常我们会部署自己的构件到这一类型的仓库,比如公司的第三方库
    • Proxy:代理仓库,它们被用来代理远程的公共仓库,如 maven 中央仓库(官方仓库)
    • Group:仓库组,用来合并多个 hosted/proxy 仓库,当你的项目希望在多个repository使用资源时就不需要多次引用了,只需要引用一个 group 即可

构建私有 yum 仓库

  1. 配置仓库的数据目录

  2. 仓库配置,以 zabbix 为例

  3. centos 7.x 配置 yum 仓库

    [root@c71 ~]$vim /etc/yum.repos.d/zabbix.repo
    [root@c71 ~]$cat /etc/yum.repos.d/zabbix.repo 
    [zabbix-nexus]
    name=zabbix
    baseurl=http://10.0.1.103:8081/repository/zabbix-proxy/
    enabled=1
    gpgcheck=0
  4. 测试:

  5. 下载过的包会缓存下来

数据备份

Nexus 中普通数据信息和元数据是分开存储的,普通数据是保存在 blob 中,而元数据保存在数据库中,所以在备份的时候必须同时进行备份普通数据和元数据,才能在后期恢复数据的时候保证数据的最终完整性

数据量太大,而且不影响用户业务,数据备份没什么意义

http://activemq.apache.org/

ActiveMQ 是一种开源的基于 JMS(Java Message Servie)规范的一种消息中间件的实现,采用 Java 开发,设计目标是提供标准的、面向消息的、能够跨越多语言和多系统的应用集成消息通信中间件

http://kafka.apache.org/

阿里云兼容 kafka 消息队列:https://www.aliyun.com/product/ons

Kafka 是一种高吞吐量的分布式发布订阅消息系统,其具备分布式功能、并可以结合 zookeeper 可以实现动态扩容,用于构建实时数据管道和流应用程序
它具有水平可伸缩性、容错性、快速性

常用消息队列对比:

kafka 优势

kafka为什么性能高?

  1. 顺序写入,kafka数据写入磁盘,不是保存在内存,默认保存168小时
  • kafka通过O(1)的磁盘数据结构提供消息的持久化,即使数以TB的消息存储也能够保持长时间的稳定性能
    • O(1)是最低的时间复杂度,哈希算法就是典型的 O(1) 时间复杂度,无论数据规模多大,都可以在一次计算后找到目标
  • 高吞吐量,即使是非常普通的硬件 Kafka 也可以支持每秒数百万的消息
  • 支持通过 Kafka 服务器分区消息,可以将数据保存到不同的服务器
  • 支持 Hadoop 并行数据加载

kafka 角色

  • broker:中文直译“中间人”,实际就是消息代理,是生产者和消费者中间代理保存消息的中转站,集群中每个 kafka 的 broker 都有唯一的 id,由 server.properties 中的 broker.id 指定,可以把每个kafka节点抽象的看成是一个broker,也可以把整个kafka集群抽象的看成是一个broker

  • topic:话题,生产者和消费者监听同一个topic,生产者往里写消息,消费者从里面读消息

  • partition:分区,也叫分片,物理上的概念,每个分区对应一个文件夹,topic 可以将其消息分片储存,提高性能,然后每个分片做多个副本,保证高可用

    注意:分片数量不要超过kafka节点数量;副本数量也不要超过kafka节点数量;

    • leader:分片副本的角色,主
    • follower:分片副本的角色,从

    对于一个分片,其副本只有一个是leader,其他的都是follower,leader不能和follower在同一个节点,这样就失去了高可用的意义

    高可用:当一个节点故障,其他的follower会选举出一个作为leader

    上图中 topic1 分了两片:topic1-part1、topic1-part2;
    上图中 topic2 只有一片:topic2-part1
    
    上图中 topic1 和 topic2 的分片都做了三个副本:topicX-part1、topicX-part2、topicX-part3
  • Producer:生产者,负责发布消息到 Kafka broker

  • Consumer:消费者,每个 consumer 属于一个特定的 consuer group(若不指定 group name 则属于默认 group),使用 consumer high level API 时,同一 topic 的一条消息只能被同一个 consumer group内的一个 consumer 消费,但多个 consumer group 可同时消费这一消息

kafka 和 zookeeper 的关系:

kafka 自身无法实现集群和高可用,kafka 依赖 zookeeper 实现集群和高可用

zookeeper 和 kafka 都可以存储数据,zookeeper储存单个数据在1MB以内,只用来保存服务的元数据,不保存业务信息

  1. Broker 依赖于 Zookeeper,每个 Broker 的 id 和 Topic、Partition 这些元数据信息都会写入 Zookeeper 的 ZNode 节点中
  2. Consumer 依赖于 Zookeeper,Consumer 在消费消息时,每消费完一条消息,会将产生的 offset 保存到 Zookeeper 中,下次消费在当前 offset 往后继续消费。注意:kafka0.9 之前 Consumer 的 offset 存储在 Zookeeper 中,kafka0,9 以后 offset存储在本地
  3. Partition 依赖于 Zookeeper,Partition 完成 Replication 备份后,选举出一个Leader,这个是依托于 Zookeeper 的选举机制实现的

kafka 部署

kakfa1.ljk.cn:10.0.1.101
kakfa2.ljk.cn:10.0.1.102
kakfa3.ljk.cn:10.0.1.103

快速部署:http://kafka.apache.org/quickstart

  1. 安装 zookeeper,这里就不配置集群了,安装单机zookeeper

  2. 安装 kafka

    # kafka 下载页面:http://kafka.apache.org/downloads
    [root@kakfa1 src]$tar -xzf kafka_2.13-2.7.0.tgz
    [root@kakfa1 src]$mv kafka_2.13-2.7.0 /usr/local/kafka
    [root@kakfa1 src]$cd /usr/local/ 
    [root@kakfa1 local]$scp -r ./kafka/ 10.0.1.102:/usr/local
    [root@kakfa1 local]$scp -r ./kafka/ 10.0.1.103:/usr/local
  3. 配置 kafka

    [root@kakfa1 ~]$vim /etc/hosts
    ...
    10.0.1.101 kafka1.ljk.cn
    10.0.1.102 kafka2.ljk.cn
    10.0.1.103 kafka3.ljk.cn
    10.0.1.101 zk1.ljk.cn			# zookeeper地址域名解析
    
    [root@kakfa1 local]$vim kafka/config/server.properties
    21 broker.id=1	# 每个 broker 在集群中的唯一标识,正整数
    31 listeners=PLAINTEXT://kafka1.ljk.cn:9092
    60 log.dirs=/usr/local/kafka/kafka-logs	# kakfa用于保存数据的目录,所有的消都会存储在该目录当中
    65 num.partitions=1	# 设置创建新topic的默认分区数量
    103 log.retention.hours=168	# 设置kafka中消息保留时间,默认为168小时,即7天
    # 指定连接的zookeeper的地址,zk中存储了broker的元数据信息,如果zk是集群,多个zk地址使用逗号分割,这里为了方便,使用单机zookeeper,推荐使用域名,如果使用ip可能无法启动,不知道为什么
    123 zookeeper.connect=zk1.ljk.cn:2181
    126 zookeeper.connection.timeout.ms=6000	# 设置连接zookeeper的超时时间,默认6s
    
    [root@kakfa2 local]$vim kafka/config/server.properties
    21 broker.id=2
    31 listeners=PLAINTEXT://10.0.1.102:9092
    60 log.dirs=/usr/local/kafka/kafka-logs
    103 log.retention.hours=168
    123 zookeeper.connect=zk1.ljk.cn:2181
    126 zookeeper.connection.timeout.ms=6000
    
    [root@kakfa3 local]$vim kafka/config/server.properties
    21 broker.id=3
    31 listeners=PLAINTEXT://10.0.1.103:9092
    60 log.dirs=/usr/local/kafka/kafka-logs
    103 log.retention.hours=168
    123 zookeeper.connect=zk1.ljk.cn:2181
    126 zookeeper.connection.timeout.ms=6000
  4. 启动 kafka

    [root@kakfa1 bin]$pwd
    /usr/local/kafka/bin
    [root@kakfa1 bin]$./kafka-server-start.sh -daemon ../config/server.properties
    
    [root@kakfa2 bin]$./kafka-server-start.sh -daemon ../config/server.properties
    
    [root@kakfa3 bin]$./kafka-server-start.sh -daemon ../config/server.properties

测试 kafka 读写数据

http://kafka.apache.org/quickstart

创建 topic

[root@kakfa1 bin]$./kafka-topics.sh --create \
--zookeeper zk1.ljk.cn:2181 \
--partitions 3 \
--replication-factor 3 \
--topic lujinkai
Created topic lujinkai.
  • –create:创建topic
  • –zookeeper:指定zk地址,虽然配置文件中已经指定了,但是命令行还要指定
  • –partitions:指定一个topic包含几个partition,就是对topic分片,分片可以提高性能,但是一般不用分片,保持默认值1就可以,如果分片,也不要超过节点的数量
  • –replication-factor:指定partition的副本数量,kafka实现高可用全靠partition的副本,如果设置3,则一个partition就存储3份,注意不是4份
  • –topic:指定名称

假设集群有4个broker,一个topic有4个partition,每个partition有3个副本。下图是每个broker上的副本分配情况:

验证 topic

[root@kakfa1 bin]$./kafka-topics.sh --describe \
--zookeeper zk1.ljk.cn:2181 \
--topic lujinkai                                              
Topic: lujinkai PartitionCount: 3       ReplicationFactor: 3    Configs: 
        Topic: lujinkai Partition: 0    Leader: 3       Replicas: 3,2,1 Isr: 3,2,1
        Topic: lujinkai Partition: 1    Leader: 1       Replicas: 1,3,2 Isr: 1,3,2
        Topic: lujinkai Partition: 2    Leader: 2       Replicas: 2,1,3 Isr: 2,1,3

说明:lujinkai 这个 topic 有三个分区分别为 0、1、2,分区 0 的 leader 是 3(broker.id),分区 0 有三个副本,并且状态都为 lsr(ln-sync,表示可以参加选举成为 leader)

获取所有 topic

[root@kakfa1 bin]$./kafka-topics.sh --list --zookeeper zk1.ljk.cn:2181
lujinkai

测试发送消息

[root@kakfa1 bin]$./kafka-console-producer.sh --topic lujinkai \
--broker-list kafka1.ljk.cn:9092,kafka2.ljk.cn:9092,kafka3.ljk.cn:9092
>hello
>word
>

测试获取消息

[root@kafka2 bin]$./kafka-console-consumer.sh --topic lujinkai \
--bootstrap-server kafka1.ljk.cn:9092,kafka2.ljk.cn:9092,kafka3.ljk.cn:9092 \
--from-beginning   
hello
word
  • –bootstrap-server:kafak集群的地址,实际只写一个地址也行
  • –from-beginning:从最开始的数据进行消费

删除 topic

[root@kakfa1 bin]$./kafka-topics.sh --delete \
--topic lujinkai \
--zookeeper zk1.ljk.cn:2181
Topic lujinkai is marked for deletion.
Note: This will have no impact if delete.topic.enable is not set to true.

https://zookeeper.apache.org/

ZooKeeper 是一个分布式服务框架,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:命名服务、状态同步、配置中心、集群管理等

命名服务

命名服务是分布式系统最基本的公共服务之一。在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等,这些我们都可以统称它们为名字(Name),其中较为常见的就是一些分布式服务框架(如 RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据指定名字来获取资源的实体、服务地址和提供者的信息等

状态同步

每个节点除了存储数据内容和 node 节点状态信息之外,还存储了已经注册的APP 的状态信息,当有些节点或 APP 不可用,就将当前状态同步给其他服务

配置中心

现在我们大多数应用都是采用的是分布式开发的应用,搭建到不同的服务器上,可以使用 ZooKeeper 来实现配置中心,ZooKeeper 采用的是推拉相结合的方式: 客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送 Watcher 事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据

集群管理

所谓集群管理,包括 集群监控集群控制 两部分,前者侧重对集群运行时状态的收集,后者则是对集群进行操作与控制

  • 客户端如果对 ZooKeepe 的一个数据节点注册 Watcher 监听,那么当该数据节点的内容或是其子节点列表发生变更时,ZooKeeper 服务器就会向订阅的客户端发送变更通知
  • 对在 ZooKeeper 上创建的临时节点,一旦客户端与服务器之间的会话失效,该临时节点也就被自动除
Watcher(事件监听器):Zookeeper中很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是 Zookeeper实现分布式协调服务的重要特性

0、生产者启动
1、生产者注册至 zookeeper
2、消费者启动并订阅频道
3、zookeeper 通知消费者事件
4、消费者调用生产者
5、监控中心负责统计和监控服务状态

ZooKeeper 单机安装

ZooKeeper 依赖 Java 环境,要求 JDK1.7 及以上

  1. 配置java环境

    [root@zk1 ~]$java -version
    java version "1.8.0_271"
    Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
    Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
  2. 部署 ZooKeeper
    官网下载地址:https://archive.apache.org/dist/zookeeper/
    注意,要下载带 bin 的包,不带 bin 的只是源码包,不包含必要的 jar 包

    [root@zk1 src]$tar zxf apache-zookeeper-3.5.9-bin.tar.gz  
    [root@zk1 src]$mv apache-zookeeper-3.5.9-bin
    [root@zk1 src]$mv apache-zookeeper-3.5.9-bin /usr/local/zookeeper
    [root@zk1 src]$cd /usr/local/zookeeper/conf/
    [root@zk1 conf]$cp zoo_sample.cfg zoo.cfg        
    [root@zk1 conf]$grep ^[a-Z] ./zoo.cfg 
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/tmp/zookeeper
    clientPort=2181
    admin.serverPort=8180		# AdminServer监听的端口,默认8080,因为tomcat默认端口也是8080,为了避免冲突,这里修改为8180
       
       关于AdminServer:https://zookeeper.apache.org/doc/r3.5.0-alpha/zookeeperAdmin.html#sc_adminserver_config
       
       ![](https://img.lujinkai.cn/blog/ljk/1614092973758.png)
       
    3. 启动 ZooKeeper
    
       ```bash
       # zkServer.sh 	用于启动、重启、停止 ZooKeeper
       [root@zk1 bin]$pwd
       /usr/local/zookeeper/bin
       [root@zk1 bin]$./zkServer.sh start
       ZooKeeper JMX enabled by default
       Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
       Starting zookeeper ... STARTED
  3. 验证 Zookeeper 状态

    [root@zk1 bin]$./zkServer.sh status
    ZooKeeper JMX enabled by default
    Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
    Client port found: 2181. Client address: localhost. Client SSL: false.
    Mode: standalone

ZooKeeper 集群简介

集群架构:

集群角色:

Zookeeper 集群部署

整个集群中只要有超过集群数量一半的 zookeeper 工作只正常的,那么整个集群对外就是可用的

zk1.ljk.cn:10.0.1.101
zk2.ljk.cn:10.0.1.102
zk3.ljk.cn:10.0.1.103
  1. 各个节点安装 zookeeper,参考上面单机部署,但是先不要启动

  2. 各个节点创建数据目录

    [root@zk1 ~]$cd /usr/local/zookeeper/
    [root@zk1 zookeeper]$mkdir data
    
    [root@zk2 ~]$cd /usr/local/zookeeper/
    [root@zk2 zookeeper]$mkdir data
    
    [root@zk3 ~]$cd /usr/local/zookeeper/
    [root@zk3 zookeeper]$mkdir data
  3. 各个节点配置文件

    [root@zk1 conf]$grep ^[a-Z] ./zoo.cfg
    tickTime=2000                     # 心跳检测周期,单位为毫秒
    initLimit=10                      # leader与follower初始连接心跳次数,即多少个2000毫秒
    syncLimit=5                       # leader与follower连接完成之后,后期检测发送和应答的心跳次数,即该follower在5*2000毫秒内不能与leader进行通信,那么此follower将被视为不可用
    dataDir=/usr/local/zookeeper/data # 自定义的zookeeper保存数据的目录
    clientPort=2181                   # 客户端连接Zookeeper服务器的端口,Zookeeper会监听这个端口,接受客户端的访问请求
    maxClientCnxns=128                # 单个客户端IP可以和zookeeper保持的连接数
    autopurge.snapRetainCount=3       # 保存快照的数量,启用后ZooKeeper只保存最新的几个快照,其余的会自动清除,最新快照和相应的事务日志分别保留在dataDir和dataLogDir中,默认值为 3。最小值为 3
    autopurge.purgeInterval=1         # 自动清理日志和快照文件的频率,单位小时,默认0,不开启自动清理
    server.1=10.0.1.101:2888:3888     # server.服务器编号=服务器IP:LF数据同步端口:LF选举端口
    server.2=10.0.1.102:2888:3888     # hostname也可以,只要dns能解析到就行
    server.3=10.0.1.103:2888:3888
    
    [root@zk1 conf]$scp ./zoo.cfg 10.0.1.102:/usr/local/zookeeper/conf
    [root@zk1 conf]$scp ./zoo.cfg 10.0.1.103:/usr/local/zookeeper/conf
  4. 集群id

    [root@zk1 data]$echo 1 > myid
    
    [root@zk2 data]$echo 2 > myid
    
    [root@zk3 data]$echo 3 > myid
  5. 启动 zookeeper

    [root@zk1 bin]$./zkServer.sh start
    
    [root@zk2 bin]$./zkServer.sh start
    
    [root@zk3 bin]$./zkServer.sh start
  6. 验证 Zookeeper 状态

    [root@zk1 bin]$./zkServer.sh status 
    ZooKeeper JMX enabled by default
    Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
    Client port found: 2181. Client address: localhost. Client SSL: false.
    Mode: follower		# follower
    
    [root@zk2 bin]$./zkServer.sh status 
    ZooKeeper JMX enabled by default
    Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
    Client port found: 2181. Client address: localhost. Client SSL: false.
    Mode: follower		# follower
    
    [root@zk3 bin]$./zkServer.sh status      
    ZooKeeper JMX enabled by default
    Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
    Client port found: 2181. Client address: localhost. Client SSL: false.
    Mode: leader		# leader

zookeeper 集群选举过程

节点角色状态:

LOOKING   # 寻找 Leader 状态,处于该状态需要进入选举流程
LEADING   # 领导者状态,处于该状态的节点说明是角色已经是 Leader
FOLLOWING # 跟随者状态,表示 Leader 已经选举出来,当前节点角色是 follower
OBSERVER  # 观察者状态,表明当前节点角色是 observer

选举 ID:

ZXID	# zookeeper transaction id,每个改变Zookeeper状态的操作都会形成一个对应的zxid
myid	# 服务器的唯一标识(SID),通过配置myid文件指定,集群中唯一

zookeeper 数据增删改查

任意一台zookeeper节点进行以下操作:

[root@zk1 bin]$./zkCli.sh -server 10.0.1.102:2181
...
Welcome to ZooKeeper!
JLine support is enabled
[zk: 10.0.1.102:2181(CONNECTING) 0] help
ZooKeeper -server host:port cmd args
        addauth scheme auth
        close 
        config [-c] [-w] [-s]
        connect host:port
        create [-s] [-e] [-c] [-t ttl] path [data] [acl]
        delete [-v version] path
        deleteall path
        delquota [-n|-b] path
        get [-s] [-w] path
        getAcl [-s] path
        history 
        listquota path
        ls [-s] [-w] [-R] path
        ls2 path [watch]
        printwatches on|off
        quit 
        reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
        redo cmdno
        removewatches path [-c|-d|-a] [-l]
        rmr path
        set [-s] [-v version] path data
        setAcl [-s] [-v version] [-R] path acl
        setquota -n|-b val path
        stat [-w] path
        sync path

zookeeper 客户端 ZooInspector

Linux:https://github.com/zzhang5/zooinspector
Windows:https://www.cnblogs.com/weiyiming007/p/11951591.html

  1. 下载、解压、双击 build/zookeeper-dev-ZooInspector.jar
  2. 连接成功

阿里云消息队列:https://www.aliyun.com/product/ons

RabbitMQ 基于 erlang 语言开发,具有高并发优点、支持分布式具有消息确认机制、消息持久化机制,消息可靠性和集群可靠性高,简单易用、运行稳定、跨平台、多语言开源

Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念,当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个 vhost,每个用户在自己的 vhost创建 exchange/queue 等
Connection:publisher/consumer 和 broker 之间的 TCP 连接。
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的channel 进行通讯,AMQP method 包含了 channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销。
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)。
Queue:消息最终被送到这里等待 consumer 取走,先进先出,可以持久化到磁盘节点服务器
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据。

RabbitMQ 中的生产者消费者示例

生产者发送消息到 broker server(RabbitMQ),在 Broker 内部,用户创建Exchange/Queue,通过Binding 规则将两者联系在一起,Exchange 分发消息,根据类型/binding 的不同分发策略有区别,消息最后来到 Queue 中,等待消费者取走

RabbitMQ 单机部署

快速部署示例:https://www.rabbitmq.com/install-debian.html#apt-bintray-quick-start

  1. 主机名解析

    [root@mq1 ~]$hostname
    mq1.ljk.cn
    [root@mq1 ~]$hostname -I
    10.0.1.101 
    [root@mq1 ~]$vim /etc/hosts
    ...
    10.0.1.101 mq1.ljk.cn mq1
  2. 时间原因,选择 apt 安装,直接执行以下脚本

    #!/bin/sh
    
    ## If sudo is not available on the system,
    ## uncomment the line below to install it
    # apt-get install -y sudo
    
    sudo apt-get update -y
    
    ## Install prerequisites
    sudo apt-get install curl gnupg -y
    
    ## Install RabbitMQ signing key
    curl -fsSL https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc | sudo apt-key add -
    
    ## Install apt HTTPS transport
    sudo apt-get install apt-transport-https
    
    $distribution='bionic'		# ubuntu18.04
    sudo tee /etc/apt/sources.list.d/bintray.rabbitmq.list <<EOF
    ## 默认安装最新版本的 erlang 23.x,可以指定版本例如:erlang-22.x
    deb https://dl.bintray.com/rabbitmq-erlang/debian $distribution erlang
    ## Installs latest RabbitMQ release
    deb https://dl.bintray.com/rabbitmq/debian $distribution main
    EOF
    
    ## Update package indices
    sudo apt-get update -y
    
    ## Install rabbitmq-server and its dependencies
    sudo apt-get install rabbitmq-server -y --fix-missing
  3. 安装成功

    [root@zabbix src]$systemctl status rabbitmq-server.service 
    ● rabbitmq-server.service - RabbitMQ broker
       Loaded: loaded (/lib/systemd/system/rabbitmq-server.service; enabled; vendor preset: enabled)
       Active: active (running) since Sun 2021-02-21 02:41:45 UTC; 1h 26min ago
     Main PID: 5992 (beam.smp)
       Status: "Initialized"
        Tasks: 22 (limit: 2289)
       CGroup: /system.slice/rabbitmq-server.service
               ├─5992 /usr/lib/erlang/erts-11.1.8/bin/beam.smp -W w -MBas ageffcbf -MHas ageffcbf -MBlmbcs 512 -MHlmbcs 512 -MMmcs 30 -P 1048576 -t 5000000 -stbt db -zd
               ├─6013 erl_child_setup 32768
               ├─6039 /usr/lib/erlang/erts-11.1.8/bin/epmd -daemon
               ├─6058 inet_gethost 4
               └─6059 inet_gethost 4
    ...
  4. 插件管理
    https://www.rabbitmq.com/management.html
    开启 web 管理界面

    [root@zabbix src]$which rabbitmq-plugins
    /usr/sbin/rabbitmq-plugins
    [root@zabbix src]$rabbitmq-plugins enable rabbitmq_management
    Enabling plugins on node rabbit@mq1:
    rabbitmq_management
    The following plugins have been configured:
      rabbitmq_management
      rabbitmq_management_agent
      rabbitmq_web_dispatch
    Applying plugin configuration to rabbit@mq1...
    The following plugins have been enabled:
      rabbitmq_management
      rabbitmq_management_agent
      rabbitmq_web_dispatch
    
    started 3 plugins.
    
    # 5672		消费者访问的 端口
    # 15672		web 管理端口
    # 25672		集群状态通信端口
    [root@mq1 ~]$rabbitmqctl list_users
    Listing users ...
    user    tags
    guest   [administrator]		# guest是初始化的用户,账号密码都是guest,但是只能使用localhost访问
    
    [root@mq1 ~]$rabbitmqctl add_user ljk 123456	# 添加用户
    Adding user "ljk" ...
    Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more.
    [root@mq1 ~]$rabbitmqctl set_user_tags ljk administrator	# 用户授权
    Setting tags for user "ljk" to [administrator] ...
    [root@mq1 ~]$rabbitmqctl list_users                     
    Listing users ...
    user    tags
    ljk     [administrator]		# 授权成功
    guest   [administrator]

    使用账号 ljk,密码 123456,成功登录web管理界面:

    登录之后,我们很少进行更改,主要是查看rabbitmq的运行状态

RabbitMQ 集群部署

  • 普通模式:创建好 RabbitMQ 集群之后的默认模式
    所有的mq节点保存相同的元数据,即消息队列,但队列里的数据仅保存一份,消费者从A节点拉取消息,如果消息在B节点,那么B会将消息发送给A节点,然后消费者就能拉取到数据了
    缺点:单点失败
  • 镜像模式:把需要的队列做成镜像队列
    在普通模式的基础上,增加一些镜像策略,消息实体会主动在镜像节点间同步,而不是在 consumer 取数据时临时拉取,解决了单点失败的问题,但是性能下降,增加集群内部网络消耗一个
    队列想做成镜像队列,需要先设置 policy,然后客户端创建队列的时候,rabbitmq 集群根据“队列名称”自动设置是普通集群模式或镜像队列

推荐设计架构:

在一个 rabbitmq 集群里,有 3 台或以上机器,其中 1 台使用磁盘模式(数据保存到内存和磁盘),其它节点使用内存模式(数据只保存到内存),使用磁盘模式的节点,作为数据备份使用

Ubuntu 1804 安装集群版 RabbitMQ

10.0.1.101 mq1.ljk.cn mq1
10.0.1.102 mq1.ljk.cn mq2
10.0.1.103 mq1.ljk.cn mq3

Rabbitmq 的集群是依赖于 erlang 的集群来工作的,所以必须先构建起 erlang 的集群环境,而 Erlang 的集群中各节点是通过一个 magic cookie 来实现的,这个cookie 存放在 /var/lib/rabbitmq/.erlang.cookie 中,文件是 400 的权限,所以必须保证各节点 cookie 保持一致,否则节点之间就无法通信

  1. 各节点安装RabbitMQ并安装插件,参考上面单机部署

  2. 各服务器关闭 RabbitMQ

    [root@mq1 ~]$systemctl stop rabbitmq-server.service
    [root@mq2 ~]$systemctl stop rabbitmq-server.service
    [root@mq3 ~]$systemctl stop rabbitmq-server.service
  3. 在 mq1 同步.erlang.cookie 至其他两台服务器

    [root@mq1 ~]$cd /var/lib/rabbitmq/
    [root@mq1 rabbitmq]$scp .erlang.cookie 10.0.1.102:/var/lib/rabbitmq
    [root@mq1 rabbitmq]$scp .erlang.cookie 10.0.1.103:/var/lib/rabbitmq
  4. 各服务器启动 RabbitMQ

    [root@mq1 ~]$systemctl start rabbitmq-server.service
    [root@mq2 ~]$systemctl start rabbitmq-server.service
    [root@mq3 ~]$systemctl start rabbitmq-server.service
  5. 查看当前集群状态

    [root@mq1 ~]$rabbitmqctl cluster_status
    [root@mq2 ~]$rabbitmqctl cluster_status
    [root@mq3 ~]$rabbitmqctl cluster_status
  6. 创建 RabbitMQ 集群:mq1、mq2作为内存节点,mq3作为磁盘节点

    [root@mq1 ~]$rabbitmqctl stop_app 	#停止 app 服务
    [root@mq1 ~]$rabbitmqctl reset 		#清空元数据
    [root@mq1 ~]$rabbitmqctl join_cluster rabbit@mq3 --ram 	#将mq1添加到集群中,并成为内存节点,不加--ram默认是磁盘节点
    [root@mq1 ~]$rabbitmqctl start_app	#启动 app 服务
    [root@mq2 ~]$rabbitmqctl stop_app
    [root@mq2 ~]$rabbitmqctl reset
    [root@mq2 ~]$rabbitmqctl join_cluster rabbit@mq3 --ram
    [root@mq2 ~]$rabbitmqctl start_app
  7. 将集群设置为镜像模式,任意节点执行以下命令

    [root@mq1 ~]$rabbitmqctl set_policy ha-all "#" '{"ha-mode":"all"}'
  8. 查看集群状态

    [root@mq1 ~]$rabbitmqctl cluster_status               
    Cluster status of node rabbit@mq1 ...
    Basics
    
    Cluster name: rabbit@mq3.ljk.cn
    
    Disk Nodes
    
    rabbit@mq3
    
    RAM Nodes
    
    rabbit@mq1
    rabbit@mq2
    
    Running Nodes
    
    rabbit@mq1
    rabbit@mq2
    rabbit@mq3
    
    Versions
    
    rabbit@mq1: RabbitMQ 3.8.12 on Erlang 23.2.5
    rabbit@mq2: RabbitMQ 3.8.12 on Erlang 23.2.5
    rabbit@mq3: RabbitMQ 3.8.12 on Erlang 23.2.5
    
    Maintenance status
    
    Node: rabbit@mq1, status: not under maintenance
    Node: rabbit@mq2, status: not under maintenance
    Node: rabbit@mq3, status: not under maintenance
    
    Alarms
    
    (none)
    
    Network Partitions
    
    (none)
    
    Listeners
    
    Node: rabbit@mq1, interface: [::], port: 15672, protocol: http, purpose: HTTP API
    Node: rabbit@mq1, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
    Node: rabbit@mq1, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
    Node: rabbit@mq2, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
    Node: rabbit@mq2, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
    Node: rabbit@mq2, interface: [::], port: 15672, protocol: http, purpose: HTTP API
    Node: rabbit@mq3, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
    Node: rabbit@mq3, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
    Node: rabbit@mq3, interface: [::], port: 15672, protocol: http, purpose: HTTP API
    
    Feature flags
    
    Flag: drop_unroutable_metric, state: enabled
    Flag: empty_basic_get_metric, state: enabled
    Flag: implicit_default_bindings, state: enabled
    Flag: maintenance_mode_status, state: enabled
    Flag: quorum_queue, state: enabled
    Flag: user_limits, state: enabled
    Flag: virtual_host_metadata, state: enabled

RabbitMQ 常用命令

  • 创建 vhost

    [root@mq1 ~]$rabbitmqctl add_vhost test
    Adding vhost "test" ...
  • 列出所有 vhost

    [root@mq1 ~]$rabbitmqctl list_vhosts
    Listing vhosts ...
    name
    /
    test
  • 列出所有队列

    [root@mq1 ~]$rabbitmqctl list_queues
    Timeout: 60.0 seconds ...
    Listing queues for vhost / ...
  • 删除指定 vhost

    [root@mq1 ~]$rabbitmqctl delete_vhost test
    Deleting vhost "test" ...
  • 添加账户 jack 密码为 123456

    [root@mq1 ~]$rabbitmqctl add_user jack 123456
    Adding user "jack" ...
    Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more.
  • 更改用户密码

    [root@mq1 ~]$rabbitmqctl change_password jack 654321
    Changing password for user "jack" ...
  • 设置 jack 用户对 test 的 vhost 有读写权限,三个点为配置正则、读和写

    [root@mq1 ~]$rabbitmqctl set_permissions -p test jack ".*" ".*" ".*"  

RabbitMQ API

RabbitMQ Cluster Monitor

集群状态监控

#!/bin/env python
# coding:utf-8
#Author: ZhangJie
import subprocess

running_list = []
error_list = []
false = "false"
true = "true"


def get_status():
    obj = subprocess.Popen(
        ("curl - s - u guest: guest http: // localhost: 15672/api/nodes & > / dev/null"), shell=True, stdout=subprocess.PIPE)

    data = obj.stdout.read()
    data1 = eval(data)
    for i in data1:
        if i.get("running") == "true":
            running_list.append(i.get("name"))
        else:
            error_list.append(i.get("name"))


def count_server():
    if len(running_list) < 3:   # 可以判断错误列表大于 0 或者运行列表小于 3,3未总计的节点数量
        print(101)              # 100 就是集群内有节点运行不正常了
    else:
        print(50)               # 50 为所有节点全部运行正常


def main():
    get_status()
    count_server()


if __name__ == "__main__":
    main()

内存使用监控

# cat rabbitmq_memory.py
#!/bin/env python
# coding:utf-8
#Author: ZhangJie
import subprocess
import sys

running_list = []
error_list = []
false = "false"
true = "true"


def get_status():
    obj = subprocess.Popen(
        ("curl - s - u guest: guest http: // localhost: 15672/api/nodes & > / dev/null"), shell=True, stdout=subprocess.PIPE)

    data = obj.stdout.read()
    data1 = eval(data)
    # print(data1)
    for i in data1:
        if i.get("name") == sys.argv[1]:
            print(i.get("mem_used"))


def main():
    get_status()


if __name__ == "__main__":
    main()

root@mq-server3: ~  # python3 rabbitmq_memory.py rabbit@mq-server1
85774336
root@mq-server3: ~  # python3 rabbitmq_memory.py rabbit@mq-server2
91099136
root@mq-server3: ~  # python3 rabbitmq_memory.py rabbit@mq-server3
96428032

MQ:Message Queuing,消息队列

Message Queue 的需求由来已久,在 19 世纪 80 年代金融交易中,美国高盛等公司采用 Teknekron 公司的产品,当时的 Message queuing 软件叫做(the informationbus(TIB),后来 TIB 被电信和通讯等公司采用,然后路透社收购了 Teknekron 公司,再然后 IBM 公司开发了 MQSeries,并且微软也开发了Microsoft Message Queue(MSMQ),但是这些商业 MQ 供应商的问题是厂商锁定及使用价格高昂,于是 2001 年,Java Message queuing 试图解决锁定和交互性的问题,但对应用来说反而更加麻烦了,于是 2004 年,摩根大通和 iMatrix 开始着手 Advanced MessageQueuing Protocol (AMQP)开放标准的开发,2006 年,AMQP 规范发布,2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ1.0 发布。

消息队列的目的是实现各个APP之间的通信(包括跨网咯通信),APP基于MQ实现消息的发送和接收,实现应用程序之间的通信,实现业务的解耦的异步机制

消息队列作为高并发系统的核心组件之一,能够帮助业务系统解构,提升开发效率和系统稳定性。消息队列主要具有以下优势:

  • 削峰填谷:主要解决瞬时写压力大于应用服务能力导致消息丢失、系统奔溃等问题
  • 系统解耦:解决不同重要程度、不同能力级别系统之间依赖导致一死全死
  • 提升性能:当存在一对多调用时,可以发一条消息给消息系统,让消息系统通知相关系统
  • 蓄流压测:线上有些链路不好压测,可以通过堆积一定量消息再放开来压测

目前主流的消息队列软件有 RabbitMQ、kafka、ActiveMQ、RocketMQ 等,还有小众的消息队列软件如ZeroMQ、Apache Qpid 等

微服务这个概念是从单体服务(单体应用)演化而来的

微服务:micro server,把单体服务拆分成多个小服务,这些小服务就是微服务,每个小服务运行在单独的运行环境,早期一般用虚拟机,现在都是容器(docker + k8s)

  • 微服务落地:容器,k8s + docker
  • 微服务发现对方:注册中心、服务发现,zookeeper
  • 微服务之间相互调用:API
  • 微服务扩容:服务治理,k8s实现服务编排
  • 微服务监控:监控微服务的API等
  • 微服务代码升级和回滚:CI / CD jenkings + gitlab
  • 微服务日志查看:ELK,统一收集和展示

服务是相互调用的,一个服务可以即是服务提供方,同时又是服务消费方

微服务框架

这个开发比较关注微服务框架,作为运维,了解即可

spring boot

spring cloud

dubbo

阿里云微服务:https://promotion.aliyun.com/ntms/act/edasdubbo.html
dubbo 官网:https://dubbo.apache.org/zh/
阿里云 dubbo 简介:https://help.aliyun.com/document_detail/99299.html

dubbo 架构

https://dubbo.apache.org/zh/docs/v2.7/user/preface/architecture/

节点角色说明:

节点 角色说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心
Container 服务运行容器

调用关系说明:

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

连通性
  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性
  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性
  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
升级性

当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:

节点角色说明:

节点 角色说明
Deployer 自动部署服务的本地代理
Repository 仓库用于存储服务应用发布包
Scheduler 调度中心基于访问压力自动增减服务提供者
Admin 统一管理控制台
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心

示例

生产者示例

zk 配置:https://dubbo.apache.org/zh/docs/v2.7/user/references/registry/zookeeper/

注册中心除了zookeeper,还有其他方式:Nacos、Multicast、Redis、Simple

[root@provider1 src]$tar zxf dubbo-demo-provider-2.1.5-assembly.tar.gz	# 开发写的包
[root@provider1 src]$cd dubbo-demo-provider-2.1.5/
[root@provider1 dubbo-demo-provider-2.1.5]$ls
bin  conf  lib

[root@provider1 dubbo-demo-provider-2.1.5]$vim conf/dubbo.properties
dubbo.container=log4j,spring
dubbo.application.name=demo-provider
dubbo.application.owner=
# dubbo.registry.address=zookeeper://10.0.1.101:2181 | zookeeper://10.0.1.102:2181 | zookeeper://10.0.1.103:2181	# 注册中心:zookeeper,z集群k
dubbo.registry.address=zookeeper://10.0.1.101:2181	# 注册中心:zookeeper,单zk
dubbo.monitor.protocol=registry
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.log4j.file=logs/dubbo-demo-provider.log
dubbo.log4j.level=WARN

[root@provider1 dubbo-demo-provider-2.1.5]$./bin/start.sh	# 启动provider,可能需要几分钟
Starting the demo-provider ........OK!
PID: 2813
STDOUT: logs/stdout.log

[root@provider1 dubbo-demo-provider-2.1.5]$tail -f logs/stdout.log	# 查看日志
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: UseCMSCompactAtFullCollection is deprecated and will likely be removed in a future release.
log4j:WARN No appenders could be found for logger (com.alibaba.dubbo.common.logger.LoggerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
[2021-02-23 13:25:48] Dubbo service server started!		# 启动成功

消费者示例

[root@consumer1 src]$tar zxf dubbo-demo-consumer-2.1.5-assembly.tar.gz	# 开发写的包
[root@consumer1 src]$cd dubbo-demo-consumer-2.1.5/
[root@consumer1 dubbo-demo-consumer-2.1.5]$vim conf/dubbo.properties
dubbo.container=log4j,spring
dubbo.application.name=demo-consumer
dubbo.application.owner=
dubbo.registry.address=zookeeper://10.0.1.101:2181
dubbo.monitor.protocol=registry
dubbo.log4j.file=logs/dubbo-demo-consumer.log
dubbo.log4j.level=WARN
[root@kafka3 dubbo-demo-consumer-2.1.5]$./bin/start.sh 	# 启动consumer
Starting the demo-consumer ....OK!
PID: 2498
STDOUT: logs/stdout.log

[root@consumer1 dubbo-demo-consumer-2.1.5]$tail -f logs/stdout.log 	# 查看日志
[13:33:22] Hello world23, response form provider: 10.0.1.101:20880
[13:33:24] Hello world24, response form provider: 10.0.1.101:20880
[13:33:26] Hello world25, response form provider: 10.0.1.101:20880
[13:33:28] Hello world26, response form provider: 10.0.1.101:20880


[root@provider1 dubbo-demo-provider-2.1.5]$tail -f logs/stdout.log 
[13:34:18] Hello world51, request from consumer: /10.0.1.103:13243
[13:34:20] Hello world52, request from consumer: /10.0.1.103:13243
[13:34:22] Hello world53, request from consumer: /10.0.1.103:13243
[13:34:24] Hello world54, request from consumer: /10.0.1.103:13243
[13:34:26] Hello world55, request from consumer: /10.0.1.103:13243
[13:34:28] Hello world56, request from consumer: /10.0.1.103:13243

以上示例是一个provider,如果是多个provider,则consumer会轮询读取

zookeeper 验证

dubbo admin

基于 zookeeper 发现并管理 provider 和 consumer

[root@kakfa1 webapps]$systemctl stop tomcat.service  # 先关闭tomcat,否则自动解压会出错
[root@kakfa1 webapps]$unzip dubboadmin.war -d dubboadmin  # 别人编译好的包,手动解压
[root@kakfa1 webapps]$vim dubboadmin/WEB-INF/dubbo.properties	# 配置文件
dubbo.registry.address=zookeeper://10.0.1.101:2181	# zookeeper地址
dubbo.admin.root.password=root	# root用户,账号密码都是root
dubbo.admin.guest.password=guest	# 访客

这个 duboadmin 版本比较老,如果需要新版的 dubboadmin,需要手动编译

微服务编译

https://dubbo.apache.org/zh/docs/v2.7/dev/build/

maven

编译c、c++,使用 make、cmake,编译java,使用maven

maven是一个项目管理工具,可以对Java项目进行构建、解决打包依赖等

POM:Project Object Model,项目对象模型,是 Maven 工程的基本工作单元,是一个 XML 文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖等,在执行任务或目标时,Maven 会在当前目录中查找 pom 文件,通过读取 pom 文件获取所需的配置信息,然后执行目标

Pom 文件中可以指定以下配置:

项目依赖
插件
执行目标
项目构建 profile
项目版本
项目开发者列表
相关邮件列表信息

部署Maven:

# 官方文档:http://maven.apache.org/install.html
# 官方源:https://archive.apache.org/dist/maven/maven-3/
# 清华源:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/

[root@kakfa1 src]$wget https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
[root@kakfa1 src]$tar zxf apache-maven-3.6.3-bin.tar.gz 
[root@kakfa1 src]$cd apache-maven-3.6.3/
[root@kakfa1 apache-maven-3.6.3]$export PATH=`pwd`/bin:$PATH

Maven打包命令:

  1. 进入到包含有“pom.xml”的路径,执行:

    mvn clean install package
  2. 有的时候受到测试的干扰,导致无法正在进行编译,这时候可以选择跳过测试:

    mvn clean install package -Dmaven.test.skip=true
    
    -Dmaven.test.skip=true		# 跳过测试,并且不编译测试下的源代码;
    -DskipTests					# 不执行测试,但是会进行测试代码的编译;
  3. 如果需要编译的代码异常庞大,需要考虑对编译环境做一些处理,提高编译效率

    mvn -T 4 clean install package -Dmaven.test.skip=true		# 启动多线程编译
    mvn -T 2C clean install package -Dmaven.test.skip=true		# 分配编译的CPU个数
    mvn clean install package -Dmaven.test.skip=true -Dmaven.compile.fork=true	# 启用多线程编译
  4. 所有的 Maven 都是建立在 JVM 上的,所以进行编译的时候还需要考虑 JVM 参数优化

    # bin/mvn是shell文件,配置参数:“MAVEN_OPTS”
    $ export MAVEN_OPTS="-Xmx6g -Xms6g" >> /etc/profile
    $ . /etc/profile

示例

以 dubbo admin 为例:https://github.com/apache/dubbo-admin/blob/develop/README_ZH.md

  1. 下载代码:

    $ git clone https://github.com/apache/dubbo-admin.git
  2. 指定注册中心地址

    $ vim dubbo-admin-server/src/main/resources/application.properties
    ...
    # centers in dubbo2.7
    admin.registry.address=zookeeper://10.0.1.101:2181
    admin.config-center=zookeeper://10.0.1.101:2181
    admin.metadata-report.address=zookeeper://10.0.1.101:2181
    ...
  3. 构建:

    $ mvn clean package -Dmaven.test.skip=true
  4. 启动:

    $ mvn --projects dubbo-admin-server spring-boot:run
    # 或
    $ cd dubbo-admin-distribution/target; 
    $ java -jar dubbo-admin-0.2.0.jar --server.port=8080 # 注意端口冲突
  5. 访问:浏览器访问 http://10.0.1.101:8080

# vim /apps/zabbix_proxy/etc/zabbix_proxy.conf
ProxyMode=1 #0为主动,1为被动
Server=10.0.58.101 #zabbix server服务器的地址或主机名
Hostname=magedu-jiege-proxy-active #代理服务器名称,与zabbix server添加代理时候的proxy name一致
ListenPort=10051 #zabbix proxy监听端口
LogFile=/tmp/zabbix_proxy.log
PidFile=/apps/zabbix_proxy/run/zabbix_proxy.pid
EnableRemoteCommands=1 #允许zabbix server执行远程命令
DBHost=10.0.58.104 #数据库服务器地址
DBName=zabbix_proxy_active #使用的数据库名称
DBUser=proxy #连接数据库的用户名称
DBPassword=123456 #数据库用户密码
DBPort=3306 #数据库端口
ProxyLocalBuffer=720 #已经提交到zabbix server的数据保留时间
ProxyOfflineBuffer=720 #未提交到zabbix server的时间保留时间
HeartbeatFrequency=60 #心跳间隔检测时间,默认60秒,范围0-3600秒,被动模式不使用
ConfigFrequency=5 #间隔多少秒从zabbix server获取监控项信息
DataSenderFrequency=5 #数据发送时间间隔,默认为1秒,范围为1-3600秒,被动模式不使用
StartPollers=20 #启动的数据采集器数量
JavaGateway=10.0.58.104 #java gateway服务器地址,当需要监控java的时候必须配置否则监控不到数据
JavaGatewayPort=10052 #Javagatewa服务端口
StartJavaPollers=20 #启动多少个线程采集数据
CacheSize=2G #保存监控项而占用的最大内存
HistoryCacheSize=2G #保存监控历史数据占用的最大内存
HistoryIndexCacheSize=128M #历史索引缓存的大小
Timeout=30 #监控项超时时间,单位为秒f
LogSlowQueries=3000 #毫秒,多久的数据库查询会被记录到日志
ProxyMode=0			# proxy模式,0:主动模式;1:被动模式
Server=10.0.1.21	# zabbix-server的ip地址,被动模式的监控项使用
ServerPort=10051	# zabbix-server的端口号,默认10051,被动模式下,此参数忽略
Hostname=proxy-qinagdao-active	# 在zabbix-server上用于区分proxy,通常设置为ip或主机名
HostnameItem=system.hostname	# 功能和hostname一样,可以设置zabbix的键值,所以更方便
ListenPort=10051	# zabbix-proxy监听的本机端口
SourceIP=			# 当服务器上有多个ip地址,可以指定使用哪个ip发起请求,一般不用配置

# 日志相关
LogType=file
LogFile=/tmp/zabbix_proxy.log
LogFileSize=0
DebugLevel=3

EnableRemoteCommands=0	# 是否允许远程命令

PidFile=/tmp/zabbix_proxy.pid
SocketDir=/tmp

# 数据库相关
DBHost=localhost
DBName=zabbix_proxy
DBSchema=
DBUser=zabbix
DBPassword=
DBSocket=
DBPort=

######### PROXY SPECIFIC PARAMETERS #############
ProxyLocalBuffer=24		# 发送成功的数据在本地保留多久,单位小时,0-172,也就是最多保留30天
ProxyOfflineBuffer=720	# 未发送成功的数据在本地保留多久,单位小时,0-172,也就是最多保留30天

HeartbeatFrequency=60	# 主动模式下,心跳检测zabbxi-server是否在线,0-3600,单位秒
ConfigFrequency=300		# 主动模式下,,配置的更新周期,1-3600*24*7,单位秒,这个参数建议设置为5分钟
DataSenderFrequency=60	# 数据向zabbix-server的推送周期,1-3600,单位秒

############ 高级参数 ################ 以下参数和 zabbix_server.conf 类似
StartPollers=5

# StartIPMIPollers=0
# StartPollersUnreachable=1
# StartTrappers=5
# StartPingers=1
# StartDiscoverers=1
# StartHTTPPollers=1
# JavaGateway=
# JavaGatewayPort=10052
# StartJavaPollers=0
# StartVMwareCollectors=0
# VMwareFrequency=60
# VMwarePerfFrequency=60
# VMwareCacheSize=8M
# VMwareTimeout=10
# SNMPTrapperFile=/tmp/zabbix_traps.tmp
# StartSNMPTrapper=0
# ListenIP=0.0.0.0
# HousekeepingFrequency=1
# CacheSize=8M
# StartDBSyncers=4
# HistoryCacheSize=16M
# HistoryIndexCacheSize=4M
Timeout=30
# TrapperTimeout=300
# UnreachablePeriod=45
# UnavailableDelay=60
# UnreachableDelay=15
# ExternalScripts=${datadir}/zabbix/externalscripts
# FpingLocation=/usr/sbin/fping
# Fping6Location=/usr/sbin/fping6
# SSHKeyLocation=
# LogSlowQueries=0
LogSlowQueries=3000
# TmpDir=/tmp
# AllowRoot=0
# User=zabbix
# Include=/usr/local/etc/zabbix_proxy.general.conf
# Include=/usr/local/etc/zabbix_proxy.conf.d/
# Include=/usr/local/etc/zabbix_proxy.conf.d/*.conf
# SSLCertLocation=${datadir}/zabbix/ssl/certs
# SSLKeyLocation=${datadir}/zabbix/ssl/keys
# SSLCALocation=
# StatsAllowedIP=
# LoadModulePath=${libdir}/modules
# LoadModule=
# TLSConnect=unencrypted
# TLSAccept=unencrypted
# TLSCAFile=
# TLSCRLFile=
# TLSServerCertIssuer=
# TLSServerCertSubject=
# TLSCertFile=
# TLSKeyFile=
# TLSPSKIdentity=
# TLSPSKFile=
# TLSCipherCert13=
# TLSCipherCert=
# TLSCipherPSK13=
# TLSCipherPSK=
# TLSCipherAll13=
# TLSCipherAll=