前一阵,一场突如其来的疫情,将我们都困在了家中,于是有了大把的时间,来做一些自己感兴趣的事情。当然,是打游戏咯。
不过只是打游戏,也太low了,完全不符合我们程序员的气质,我们作为网络世界中的众多缔造者之一,仅仅扮演一个渺小的角色,未免太过无趣。于是,我想到自己搭建服务器给自己玩,做自己的神~哈,开玩笑的,其实是为了更好的了解那些手游端游的运作机制啦,就搭建了几款不同的游戏,也算研究研究 常用架构吧。
这些天,我下载了很多游戏的服务架构,页游、手游、端游都有。看到里面的配置和架构也都五花八门。总的来说,一般页游大多是AMP+JAVA,手游遇到很多, JAVA+MongoDB,页游AMP+Erlang+RabbitMQ的组合。端游就更加五花八门了,很多引擎或者自制脚本,使用的数据库种类也很多,MongoDB、mysql、SQL Server等等,不同游戏的架构选择都不同。
因为我们自己肯定没法写出来一个完整的游戏服务包,所以最理想的也是最简便的方法就是使用别人已经写好的现成的服务包,安装配置即可(里面坑巨多,免费分享出来的,完整性和质量就没法苛求了),大部分修复下或者调整下也是能够凑活跑起来的。
不过倒也遇到很多问题,游戏中有很多bug,例如这个任务流程过不去,那个点了没反应,只能边玩边吐槽;还有个手游架设好后,一直没有报错,就一直没管他,运行3天后,有小伙伴说登录不上,才发现服务器数据库崩掉连接不上了,由于搭建好后就没有关注监控这个服务器,甚至不知道是啥时候崩掉的,也就不知道为什么会崩,重新搭建连接完数据库,之前玩的游戏数据也就清空没有(没有设置实时备份数据库),小伙伴们很是扫兴,还好不是实际生产中,这我也很无奈啊。
但总体下来,大家反应都是很不错的,毕竟道具无限,人人都是大佬,也算无聊的假期大家一起有共同消遣了。
因为要自己开服务器,如果只是在本地电脑上配置,那其他小伙伴们就没法连接进来,自己游戏里再NP也没人分享可太悲哀了。所以,我们一定要连上外网,需要公网IP,才可以供他人访问。
博主有一个aws的海外云主机,还有一个腾讯云的国内云主机,虽然直接在云主机上搭建可以直接解决公网IP的问题,不过这俩云主机配置都不高,都是1C1G,担心无法完美支撑服务跑起来(花钱升级配置是不可能的~)。于是初步决定在本地先将服务跑起来,用 你v懂p得n 打通本地电脑和云主机的网络,配置云主机的反向代理,实现游戏服务器公网搭建。做出规划步骤如下
因为我搭建了很多不同类型的游戏,且每一种其实方法步骤都不一样,需要的配置和环境也都不同,遇到的问题也不尽相同,在这里就没法一一细说了,会在文末将我搭建成功的那些服务包都贴出来,有需要的小伙伴可以自行研究。
可以参考我之前博客https://hewanyue.com/blog/2c6b894f.html架设 你v懂p得n 。协议可以使用UDP,之前我用TCP总是被封掉。搭建好,建立连接后,其实就已经算是打通网络了。
在window cmd命令行或者linux的终端里ping 10.8.0.1 就可以ping通云主机了的。然后也可以尝试用云主机验证 访问本地10.8.0.6上面的http服务之类的。
虽然这两台机子间是联通了,但是别人访问你的云主机的IP,并不能连接到你本地的服务器主机,所以我们需要在防火墙上配置转发。
例如本地服务器开启的端口为 12345,而我们想让别人访问云主机的 54321端口就可以登录我们的游戏服务,需要填写dnat 还有地址伪装。代码如下:1
iptables -t nat -D PREROUTING -d 0.0.0.0 -p tcp --dport 54321 -j DNAT --to-destination 10.8.0.6:12345
如果客户端设置的链接协议是UDP,那就将tcp改为UDP,若是不知道客户端程序写的链接方式到底是什么,可以写两条将这俩都转发了。不过如果不写协议会报错的。1
2
3[root@ip-172-31-39-115 ~]# iptables -t nat -D PREROUTING -d 0.0.0.0 --dport 54321 -j DNAT --to-destination 10.8.0.6:12345
iptables v1.8.2 (nf_tables): unknown option "--dport"
Try `iptables -h' or 'iptables --help' for more information.
如果不想改不同的端口,倒也可以简单粗暴的,将访问云主机的所有数据统统转发到本地,但这样的话,云主机的其他功能就都会受到影响,不建议这样(云主机上没有其他服务倒也可以这样)。
还有记得防火墙开启转发功能,否则可能服务器的内网网卡收不到数据报文。1
2echo "net.ipv4.ip_forward = 1" /etc/sysctl.conf
sysctl -p
有的游戏,本地服务器还配有网站等等例如gm简易工具,需要我们在外网也能访问。可以设置nginx或者apache的反向代理转发,比较简单,这里就不细说了。
出于安全考虑,很多游戏服务配置的时候,相互之间都是,配置了访问权限的,我下载的大部分游戏都是监听在本地127.0.0.1回环网卡的,如果我们不修改监听地址,从我们构建来的专线的访问数据是没有权限访问我们的服务的,所以我们需要将监听地址修改为10.8.0.6专线网卡的ip上。或者改为0.0.0.0(不建议,有可能端口冲突,造成服务起不来或者报错)。
一般php或者数据库那些地址就不用改了,因为还是从本地读取的。到时候捋一捋就可以确保正确连接了。
因为一般需要连接的服务器IP端口,都是写死在客户端中的,我们修改了服务器的地址,所以还要在客户端中修改为正确的ip地址端口,才可以链接(页游不用,页游没有客户端)。
不同游戏修改方式和路径都不相同,安卓端可以用APKIDE,苹果ipa的可以直接将后缀改为rar,解压后修改对应ip,这里就不细说了。欢迎就具体游戏来讨论。
[video(video-mslzJS9V-1582030113247)(type-youku)(url-https://player.youku.com/embed/XNDU1MTQyNjcwMA==)(image-https://vthumb.ykimg.com/054106015E4BB2DE000001693906AF84)(title-剑侠情缘手游)]
安装vmware的centos虚拟机,里面是java程序。
下载地址
剑侠情缘VM一键端
链接:https://pan.baidu.com/s/1D0qOO7XcK2K93BLXCsw-fA
提取码:4371
手游蓝月传奇
游戏预览:
这个是在windows环境下运行的,用到PHP、nodejs、mysql、Erlang+rabbitMQ
下载地址
【蓝月传奇】一键端+修改教程+全功能GM网页后台+外网教程
链接:https://pan.baidu.com/s/1wjfmMWaTf2YxZ0rxjCtknw
提取码:6c8m
手游七雄争霸
JAVA游戏,windows环境搭建,用到了memcached、nodejs、PHP
下载地址
链接:https://pan.baidu.com/s/1o-pZb4TWKZWf3vmGk70bfQ
提取码:78ws
手游 幽冥传奇
Windows环境运行,用的战神引擎。
下载地址
链接:https://pan.baidu.com/s/1mLgic84xPkBhkrOiJqx4ZQ
提取码:ja6l
页游 传奇世界
H5游戏,也还不错。
下载地址
链接:https://pan.baidu.com/s/1Zv1vMoD9l1cegh3FRghaXA
提取码:7057
页游 赤月传说
下载地址
链接:https://pan.baidu.com/s/1S1RW6JAmR1Xx063XzoJTOA
提取码:i168
端游 无极联盟传奇
Windows环境运行,用的GOM引擎。
这个虽然特效还有模式都还不错,不过是个残端,里面没有提供pak密码,不建议安装。
下载地址
链接:https://pan.baidu.com/s/13OOZxWbESveJLXmeS-w1Bw
提取码:7nom
端游 三国战纪OL(第二季)
Windows环境运行,用的GOM引擎。这个可以完美开服,还算不错。
下载地址
链接:https://pan.baidu.com/s/12x4Kvb-_Pipn6Qs73JkBPw
提取码:xb6r
很多游戏搭好之后,玩了也还不错,不过忘记保留截图了。还有一些还没上传,等日后再分享。欢迎交流讨论。
]]> 自从入坑linux系统,便越陷越深,操作起来确实方便很多。不过linux系统有一个致命的问题,那就是没法玩游戏!
平常也有罢了,不玩也就算了。不过这一阵子,恰逢春节,却又赶上疫情不能出门,几乎排行榜所有的电影都扫遍了,无可奈何又想起了早就删除了的英雄联盟。
但是现在系统已经是Deepin系统了。用起来很简洁,也比较方便,桌面也很漂亮,给大家看看桌面哈~
言归正传,我们需要安装一个window的虚拟机。因为本地已有vmware,所以直接用vmware创建。
没有安装vmware的去官网下载一个linux版本的,网上找一个个激化码就可以正常使用了。链接为https://download3.vmware.com/software/wkst/file/VMware-Workstation-Full-15.5.1-15018445.x86_64.bundle
。可以用迅雷或者谷歌自带下载工具下载,也可以用命令行wget
命令下载。
这是一个可执行程序文件,在命令行中对此文件,加执行权限,执行1
./VMware-Workstation-Full-15.5.1-15018445.x86_64.bundle
安装完毕VMware后,找到window的iso镜像包(要是没有,提前去下载一个)正常安装window系统即可。至少分配4个G内存,开启3D选项(一般是默认开启的)。
开机之后很有可能会vmware的又下角会报错,提示3D不可用
需要我们进入虚拟机的安装目录,找到vmx文件,vim
打开编辑,在最后加一行mks.gl.allowBlacklistedDrivers = "TRUE"
来强制开启3D加速。再开启虚拟机,就不会报No 3D support is available from host
了。如果报mks.gl.allowBlacklistedDrivers
之类的错误,可能是引号格式不正确,重新检查修改一下引号就可以了。
当我下载好wegame,安装好英雄联盟客户端,也能正常组队及开始游戏时,载入进度条到100%后,意外给我弹一个这个报错。
然后游戏也无法重连了,一直报错game_error_directx
。
这上来就坑队友了啊,我也很无奈。赶紧用directx修复工具修复一下directx,果然可以进入游戏了。(首先确保第一步的3D支持已开启,否则单独用修复工具修复是无效的)
进入游戏后,我发现虽然可以流畅游戏,也没有延迟,问题是每隔几分钟就会有个10秒钟左右的画面卡顿,甚至掉线,然后屏幕就黑了。。。这谁顶得住啊。只能停下,赶紧排查原因。
开始以为是网络卡顿,不过检查了下,发现虚拟机的网络一直没有掉,于是猜测是虚拟机的CPU不响应,于是检测了下CPU的使用率,发现果然在卡顿的几秒钟内,宿主机的CPU使用率很高,难道是有进程占用了CPU,导致虚拟机的程序无法得到响应?
于是查找CPU优化,初步拟定方向是,将CPU核心绑定至虚拟机进程,修改虚拟机优先级等等。不过linux版的VMware没法设置优先级,并不像window版本的可以设置优先级。
于是,可优化选项,只有设置预留内存,以及不使用交换分区了。这时我注意到,默认是使用交换分区的,可能导致我不时卡顿的元凶就是这里了。不过无法直接修改内存选项,提示You must be running Workstation as root to change these preferences.
。
于是,只能从命令行切换root用户权限执行vmware,修改后(将默认的Allow some virtual machine memory to be swapped改为第一个),在用普通用户启动vmware,发现果然是继续生效的。
再次启动虚拟机,进入游戏之后,果然再也没有出现卡顿了。
在虚拟机中全屏之后,上面还隐藏一个白色的工具条,打游戏偶尔会点到就会切出去,很影响游戏体验。于是我们要想办法将他去掉。
在Edit,Preferences选项下的Display标签中,将全屏显示选项栏这个选项勾选取消。
现在就可以愉快的进行游戏了,你们能看出来我是在虚拟机中游戏吗
K8s集群搭建完成后,真正完成我们业务的是那些跑在k8s上的pod们。将业务跑在k8s集群只上,我们可以实现根据负载或者资源利用率动态扩容或者缩容我们的后端服务器,更加灵活高效的利用我们的物理设备,且能够实现服务的高可用及故障自治愈,本文将详细介绍以上的具体实现。
本次演示使用主机系统均为ubuntu1804。
节点 | IP |
---|---|
master节点 | 192.168.32.18、192.168.32.19 |
node节点 | 192.168.32.21、192.168.32.22 |
etcd | 192.168.32.23、192.168.32.24、192.168.32.25 |
harbor | 192.168.32.20 |
NFS服务器 | 192.168.32.20(复用) |
如果机器不够,可以选择复用,例如将etcd的三台主机与master主机和harbor主机复用,可以省下来3台主机。推荐node节点的性能高一些,因为一般之后的pod都将运行在node节点上,如果node节点的资源不足,则很有可能无法创建pod,导致服务启动或者扩容失败。
具体K8s集群的搭建,可以参考我之前文章使用kubeasz自动化部署K8s。harbor服务器的搭建和部署配置,也可以参考我之前文章Docker(五)——Docker镜像仓库。
因为是均为内网环境,建议将网络组件calico的IPIP模式(ip-in-ip叠加模式)关掉,使用calico的BGP模式,以节约大量主机内部访问时封装的性能损耗。具体操作可以参考之前博客使用kubeasz自动化部署K8s。
我们设计一个简单架构为例,如下图所示。
一般负载层是使用物理机上的haproxy服务来实现4层负载,而不是在K8s集群内部pod 实现。我们这里也就将他省略不再细说。
所以我们需要创建一个nginx集群,以及一个tomcat集群。将动态资源,转发给后端的tomcat服务,前端的静态页面由nginx来处理。为了方便我们对后端服务的修改以及部署,我们还需要一个NFS服务器,来共享静态资源以及动态资源,以实现后端服务数据的实时同步。
镜像一般是按层次一级一级实现的,可实现多个业务复用。所以,例如我们将要实现的业务为app1,则我们需要的的镜像,就有对应的app1-nginx及app1-tomcat的业务镜像,以及nginx及tomcat的基础镜像,以及基础系统镜像,我们要一层层的来制作镜像。详细可以参考之前文章Docker(三)——镜像制作。1
2
3
4cd /opt
mkdir -p k8s/{app1,dockerfile,yaml}
cd k8s/dockerfile
mkdir {app1,pub-image,system}
最终结构如下图所示。1
2
3
4
5
6
7
8
9
10
11
12root@DockerUbuntu18:/opt/k8s/dockerfile# tree -d
.
├── app1
│ ├── app1-nginx
│ └── app1-tomcat
├── pub-images
│ ├── jdk
│ │ └── 8
│ ├── nginx-base
│ └── tomcat-base
└── system
└── centos
打好的镜像如下所示。1
2
3
4
5
6
7
8
9root@DockerUbuntu18:/opt/k8s/dockerfile# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
harbor.micepro.net/base/app1-tomcat v1 c7d87d05f8a4 3 days ago 1.19GB
harbor.micepro.net/base/tomcat-base v8.5.47 12cf3e12f2d0 3 days ago 1.19GB
harbor.micepro.net/base/jdk-base v8.212 1582b1e9cfe4 3 days ago 1.16GB
harbor.micepro.net/base/app1-nginx v1 4836f9aed79b 3 days ago 940MB
harbor.micepro.net/base/nginx-base v1.16.1 bfc05a5c23ed 5 days ago 940MB
harbor.micepro.net/base/centos-base v7.6 bc7e922a8352 5 days ago 753MB
harbor.micepro.net/base/centos base f1cb7c7d58b7 10 months ago 202MB
我们计划将动态资源和静态资源都放置至我们的NFS共享存储上,也就是我们的harbor上(任意主机,端口不冲突就可以~)
可以通过包管理工具快速安装一个NFS服务,毕竟这只是一个辅助工具,无需过多关注。1
2
3
4apt install -y nfs-server
mkdir /data/app1/{images,static,ROOT} -p
echo "/data/app1 *(rw,no_root_squash)" >> /etc/exports
systemctl enable --now nfs-server
将/data/app1
目录整个共享出去即可,之后我们可以直接将NFS挂载至pod中,也可以通过PV/PVC的方式挂载到pod中。
我们需要修改权限,通过指定uid的方式,使某个UID在多个主机之间都具有权限。这就要求我们之前在创建基础镜像时,也使用的是相同的UID启动程序。例如我们统一都使用www
用户,指定UID为2020。(之前创建的容器以及基础镜像中都要修改服务以UID为2020的www用户启动)
此时我们就可以开始着手准备yaml文件,来规划我们的服务pod了。
首先我们需要新创建一个namespace,来实现与其他服务隔离。1
2cd /opt/k8s/
mkdir -p yaml/namespace
1 | vim yaml app1.yaml |
然后将这个namespace创建出来,使用命令1
kubectl apply -f /opt/k8s/yaml/namespace/app1.yaml
此时使用命令kubectl get ns
可以看到我们新创建的namespace1
2
3
4
5
6
7root@DockerUbuntu18:/opt/k8s# kubectl get ns
NAME STATUS AGE
app1 Active 20s
default Active 3d
kube-node-lease Active 3d
kube-public Active 3d
kube-system Active 3d
先创建好对应的目录,方便后期管理维护。1
2cd /opt/k8s
mkdir -p app1/{nginx,tomcat}
然后我们就可以编写pod的yaml文件了。1
cd /opt/k8s/app1/nginx
可以将Deployment
和server
写在一起,也可以分开写。我们这直接都写在一起。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86vim nginx.yaml
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
labels:
app: app1-nginx-deployment-label
name: app1-nginx-deployment
namespace: app1
spec:
replicas: 2 #设置nginx集群数量
selector:
matchLabels:
app: app1-nginx-selector
template:
metadata:
labels:
app: app1-nginx-selector
spec:
containers:
- name: app1-nginx-container
image: harbor.micepro.net/base/app1-nginx:v1
#command: ["/apps/tomcat/bin/run_tomcat.sh"]
imagePullPolicy: IfNotPresent
#imagePullPolicy: Always
ports:
- containerPort: 80
protocol: TCP
name: http
- containerPort: 443
protocol: TCP
name: https
env:
- name: "password"
value: "123456"
- name: "age"
value: "18"
resources:
limits:
cpu: 2
memory: 2Gi
requests:
cpu: 500m
# memory: 1Gi
memory: 200m
volumeMounts:
- name: app1-images
mountPath: /usr/local/nginx/html/webapp/images
readOnly: false
- name: app1-static
mountPath: /usr/local/nginx/html/webapp/static
readOnly: false
volumes:
- name: app1-images
nfs:
server: 192.168.32.20
path: /data/app1/images
- name: app1-static
nfs:
server: 192.168.32.20
path: /data/app1/static
kind: Service
apiVersion: v1
metadata:
labels:
app: app1-nginx-service-label
name: app1-nginx-service
namespace: app1
spec:
type: NodePort
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
nodePort: 30080
- name: https
port: 443
protocol: TCP
targetPort: 443
nodePort: 30443
selector:
app: app1-nginx-selector
1 | cd /opt/k8s/app1/tomcat |
编写tomcat的pod的yaml文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72kind: Deployment
apiVersion: extensions/v1beta1
metadata:
labels:
app: app1-tomcat-app1-deployment-label
name: app1-tomcat-app1-deployment
namespace: app1
spec:
replicas: 1
selector:
matchLabels:
app: app1-tomcat-app1-selector
template:
metadata:
labels:
app: app1-tomcat-app1-selector
spec:
containers:
- name: app1-tomcat-app1-container
image: harbor.micepro.net/base/tomcat-app1:v1
command: ["/usr/local/tomcat/bin/run_tomcat.sh"]
#imagePullPolicy: IfNotPresent
imagePullPolicy: Always
ports:
- containerPort: 8080
protocol: TCP
name: http
env:
- name: "password"
value: "123456"
- name: "age"
value: "18"
resources:
limits:
cpu: 2
memory: "1000Mi"
requests:
cpu: 500m
memory: "200Mi"
volumeMounts:
- name: app1-myapp
mountPath: /data/tomcat/webapps/ROOT
readOnly: false
volumes:
- name: app1-myapp
nfs:
server: 192.168.32.19
path: /data/app1/ROOT
- name: app1-static
nfs:
server: 192.168.32.19
path: /data/app1/static
kind: Service
apiVersion: v1
metadata:
labels:
app: app1-tomcat-app1-service-label
name: app1-tomcat-app1-service
namespace: app1
spec:
type: NodePort
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
nodePort: 38080
selector:
app: app1-tomcat-app1-selector
因为要实现一个通用的nginx+tomcat动静分离web架构,即用户访问的静态页面和图片在由nginx直接响应,而动态请求则基于tomcat的service name转发用户请求到tomcat业务app。
所以我们需要修改当时创建镜像的nginx的配置文件,配置upstream服务器为tomcat的pod,配置如下1
2
3
4
5
6
7
8
9
10
11upstream tomcat_webserver {
server app1-tomcat-app1-service.app1.svc.app1.local:8080;
}
server {
location /myapp {
proxy_pass http://tomcat_webserver;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
其中pod间通信是基于K8s内部DNS实现的,一般是coreDNS或者kubeDNS,如果没有安装是无法解析的,可以使用kubeasz的第七个playbook安装,可参考之前文章使用kubeasz自动化部署K8s。
主机名默认组成结构为release-name-rancher.default.svc.cluster.local,也就是ServerName.NameSpace.svc.CLUSTER_DNS_DOMAIN
。ServerName
是我们在yaml文件中定义的,NameSpace
是我们创建pod时选择的namespace
,CLUSTER_DNS_DOMAIN
是我们在创建K8s集群时设置的集群名,如果是用kubeasz工具创建的K8s集群,可以去/etc/ansible/hosts
文件中查看。
我们可以在pod间测试通信,确保相互之间可以通过主机名通信。1
2
3
4
5[root@blog-tomcat-app1-deployment-5b4845ddd8-rr5dq /]# ping app1-nginx-service.app1.svc.cluster.local
PING app1-nginx-service.app1.svc.cluster.local (10.68.101.60) 56(84) bytes of data.
64 bytes from app1-nginx-service.app1.svc.cluster.local (10.68.101.60): icmp_seq=1 ttl=64 time=0.103 ms
64 bytes from app1-nginx-service.app1.svc.cluster.local (10.68.101.60): icmp_seq=2 ttl=64 time=0.314 ms
64 bytes from app1-nginx-service.app1.svc.cluster.local (10.68.101.60): icmp_seq=3 ttl=64 time=0.077 ms
而且nginx及tomcat中最好将日志格式改为json格式,方便我们收集日志。
tomcat的配置文件/usr/local/tomcat/conf/server.xml
,设置appBase="/data/tomcat/webapps/
,日志格式修改如下1
2
3<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="{"clientip":"%h","ClientUser":"%l","authenticated":"%u","AccessTime":"%t","method":"%r","status":"%s","SendBytes":"%b","Query?string":"%q","partner":"%{Referer}i","AgentVersion":"%{User-Agent}i"}" />
我们还可以通过创建一个创建PV/PVC的方式的方式来代替直接NFS挂载Volume。
PersistentVolume(PV)是集群中由管理员配置的一段网络存储。 它是集群中的资源,就像节点是集群资源一样。 PV是容量插件,如Volumes,但其生命周期独立于使用PV的任何单个pod。 此API对象捕获存储实现的详细信息,包括NFS,iSCSI或特定于云提供程序的存储系统。
PersistentVolumeClaim(PVC)是由用户进行存储的请求。 它类似于pod。 Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以一次读/写或多次只读)。
PVC和PV是一一对应的。Persistent Volume相对独立于Pods,单独创建。
Persistent Volume对具体的存储进行配置和分配,而Pods等则可以使用Persistent Volume抽象出来的存储资源,不需要知道集群的存储细节。
Persistent Volume和Persistent Volume Claim类似Pods和Nodes的关系,创建Pods需要消耗一定的Nodes的资源。而Persistent Volume则是提供了各种存储资源,而Persistent Volume Claim提出需要的存储标准,然后从现有存储资源中匹配或者动态建立新的资源,最后将两者进行绑定。
以我们这次的演示为例,PV和PVC如下所示。
PV的yaml文件示例1
2
3
4
5
6
7
8
9
10
11
12
13apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.32.19
path: "/data/app1/static"
PVC的yaml文件示例1
2
3
4
5
6
7
8
9
10
11apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
requests:
storage: 10Gi
pod中引用PVC1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20apiVersion: v1
kind: Pod
metadata:
name: testpv
labels:
role: web-frontend
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: nfs
mountPath: "/usr/local/nginx/html/webapp/static"
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs
虽然我们可以使用命令kubectl scale
对运行在k8s 环境中的pod 数量进行扩容(增加)或缩容(减小)。1
2
3
4
5
6
7
8
9
10
11
12root@DockerUbuntu18:~# kubectl --help | grep -w scale
scale Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job
root@DockerUbuntu18:~# kubectl get deployments -n app1
NAME READY UP-TO-DATE AVAILABLE AGE
app1-nginx-deployment 1/1 1 1 9d
app1-tomcat-deployment 1/1 1 1 3d4h
root@DockerUbuntu18:~# kubectl scale deployment/app1-tomcat-deployment --replicas=2 -n app1
deployment.extensions/app1-tomcat-deployment scaled
root@DockerUbuntu18:~# kubectl get deployments -n app1
NAME READY UP-TO-DATE AVAILABLE AGE
app1-nginx-deployment 1/1 1 1 9d
app1-tomcat-deployment 2/2 2 2 74s
不过在实际生产中,我们肯定没法随时根据负载或者服务需要,手动动态伸缩我们的后端服务器数量,但是我们可以通过命令kubectl autoscale
自动控制在k8s集群中运行的pod数量(水平自动伸缩),想要实现自动上伸缩,需要我们提前设置pod范围及触发条件。
k8s从1.1版本开始增加了名称为HPA(Horizontal Pod Autoscaler)的控制器,用于实现基于pod中资源(CPU/Memory)利用率进行对pod的自动扩缩容功能的实现,早期的版本只能基于Heapster组件实现对CPU利用率做为触发条件,但是在k8s 1.11版本开始使用Metrices Server完成数据采集,然后将采集到的数据通过API(Aggregated API,汇总API),例如metrics.k8s.io、custom.metrics.k8s.io、external.metrics.k8s.io,然后再把数据提供给HPA控制器进行查询,以实现基于某个资源利用率对pod进行扩缩容的目的。
控制管理器默认每隔15s(可以通过–horizontal-pod-autoscaler-sync-period修改)查询metrics的资源使用情况
支持以下三种metrics指标类型:
->预定义metrics(比如Pod的CPU)以利用率的方式计算
->自定义的Pod metrics,以原始值(raw value)的方式计算
->自定义的object metrics
支持两种metrics查询方式:
->Heapster
->自定义的REST API
支持多metrics
(未完待续)
]]> 提到ELK,就不得不提到EFK,通常意义上说,EFK是指用filebeat代替logstash形成的新组合。(哈,也有是指Fluentd的,这个我们之后再说)
Filebeat 是基于原先 logstash-forwarder 的源码改造出来的,无需依赖 Java 环境就能运行,安装包10M不到。
而且如果日志的量很大,Logstash 会遇到资源占用高的问题,为解决这个问题,我们引入了Filebeat。Filebeat 是基于 logstash-forwarder 的源码改造而成,用 Golang 编写,无需依赖 Java 环境,效率高,占用内存和 CPU 比较少,非常适合作为 Agent 跑在服务器上,来实现日志转发的功能。
还是去官网下载https://www.elastic.co/cn/downloads/beats/filebeat。本次演示还是以最新版的filebeat7.5.1为例(以前版本的filebeat配置文件格式参数上可能有一些改变,不过大同小异)。1
2
3cd /usr/local/src/
wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.5.1-amd64.deb
dpkg -i filebeat-7.5.1-amd64.deb
配置文件也很简单,如果想要写入文件,则配置如下
1 | grep -v "#" /etc/filebeat/filebeat.yml | grep -v "^$" |
paths
路径支持正则通配符写法,exclude是设置不匹配的文件格式。而且filebeat也支持同时从多个路径收集写成如下配置
1 | filebeat.inputs: |
同样,filebeat支持写入redis和kafka
1 | filebeat.inputs: |
想要写入kafka则添加output插件,配置如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15filebeat.inputs:
- type: log
paths:
- /var/log/syslog
exclude_lines: ["^DBG"]
exclude_files: [".gz$"]
tags: "filebeat-kafka-syslog"
output.kafka: #写入 kafka
hosts: ["192.168.15.11:9092","192.168.15.12:9092","192.168.15.13:9092"]
topic: "systemlog-1512-filebeat"
partition.round_robin:
reachable_only: true
required_acks: 1 #本地写入完成
compression: gzip #开启压缩
max_message_bytes: 1000000 #消息最大值
也就是设置日志收集的来源,需要的属性有type
,path
,根据官方文档,现在版本常用写法为,1
2
3
4
5
6
7
8
9
10
11filebeat.inputs:
- type: log
paths:
- /var/log/system.log
- /var/log/wifi.log
- type: log
paths:
- "/var/log/apache2/*"
fields:
apache: true
fields_under_root: true
其中type的类型很多
1 | filebeat.inputs: |
1 | filebeat.inputs: |
1 | filebeat.inputs: |
1 | filebeat.inputs: |
1 | filebeat.inputs: |
1 | filebeat.inputs: |
1 | filebeat.inputs: |
1 | filebeat.inputs: |
1 | filebeat.inputs: |
1 | filebeat.inputs: |
一般来说,我们都写log,就可以满足我们绝大多数场景的使用了。除了type
、path
这俩常用的input属性外,还有两个设置属性,我们也经常会用到,就是include_lines
、exclude_lines
,顾名思义,就是包括和排除,配合path中的通配符,可以帮助我们更灵活的指定要收集的日志文件。
还有一个很常用的属性就是tags
,可以写多个,用[]
括起来就可以。因为在filebeat中因为有自带type
关键字,所以我们在之后筛选日志的时候,无法通过type字段来区分不同的日志源了,所以我们可以通过自定义tags
字段,来实现之前在logstash上type的功能,这样在我们收集到的日志中,会自动加入tags
标签属性,然后通过logstash的筛选时,就可以对tags
关键字做判断了。
输出选项有Elasticsearch
、Logstash
、Kafka
、Redis
、File
、Console
、Elastic Cloud
。
File
输出到文件中是最简单的设置了,一般用于测试。
1 | output.file: |
Logstash
filebeat支持直接将数据输出值logstash主机。
1 | output.logstash: |
而logstash主机需要设置输入为beats,才可以顺利接收filebeat的数据。1
2
3
4
5
6
7
8
9
10
11
12input {
beats {
port => 5044
}
}
output {
elasticsearch {
hosts => ["http://localhost:9200"]
index => "%{[@metadata][beat]}-%{[@metadata][version]}"
}
}
redis
输出值redis,上面有说明,这里就不详细介绍了。
1 | output.redis: |
kafka
输出至kafka。
1 | output.kafka: |
也可以输出至kafka的不同的topic中
Rule settings:
topic
The topic format string to use. If this string contains field references, such as %{[fields.name]}, the fields must exist, or the rule fails.
mappings
A dictionary that takes the value returned by topic and maps it to a new >name.
default
The default string value to use if mappings does not find a match.
when
A condition that must succeed in order to execute the current rule. All the conditions supported by processors are also supported here.
官方示例如下:1
2
3
4
5
6
7
8
9
10output.kafka:
hosts: ["localhost:9092"]
topic: "logs-%{[beat.version]}"
topics:
- topic: "critical-%{[beat.version]}"
when.contains:
message: "CRITICAL"
- topic: "error-%{[beat.version]}"
when.contains:
message: "ERR"
Elaticsearch
可以直接将数据输出给elaticsearch服务器,不过一般来说我们不会这样做,一般是会经过logstash来筛选之后再传入elaticsearch。官方示例如下:
1 | output.elasticsearch: |
Console
输出至屏幕终端显示。pretty官方的介绍为If pretty is set to true, events written to stdout will be nicely formatted. The default is false
,示例如下:
1 | output.console: |
之前我们部署好了ELK的基本架构,也实现了从系统日志以及nginx中收集日志,不过等待我们的问题依然很多:怎么讲收集好的日志放至临时缓存?或者怎么从缓存中提取日志?对于java应用等日志非单行的服务日志该如何收集等等。本文将继续讲解ELK的各种进阶用法。
收集tomcat中的日志比较简单,跟nginx一样,将日志序列化为json格式即可。
修改tomcat配置文件,将日志格式修改为如下格式。1
vim /usr/local/tomcat/conf/server.xml
1 | <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" |
此时查看新生成的访问日志,即可看到新生成的日志已经成json格式了。1
tail -f /usr/local/tomcat/logs/localhost_access_log.2020-01-03.txt
1 | {"clientip":"192.168.32.1","ClientUser":"-","authenticated":"-","AccessTime":"[03/Jan/2020:19:34:29 +0800]","method":"GET /bg-button.png HTTP/1.1","status":"304","SendBytes":"-","Query?string":"","partner":"http://192.168.32.51:8080/tomcat.css","AgentVersion":"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"} |
不过为了以防万一,可以去网上使用在线json格式校验工具,检查一下格式是否正确。
此时就可以使用logstash工具来收集我们的tomcat日志了。1
vim /etc/logstash/conf.d/tomcat-el.conf
不过,想必你们一定也发现了,tomcat访问日志中是带有日期格式的,每天的访问日志文件名是不同的,这要怎么写到path中呢?
哈,别慌,logstash的input-file插件也支持通配符的写法,我们可以写path => "/usr/local/tomcat/logs/localhost_access_log.*.txt"
,如下所示。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20input {
file {
path => "/usr/local/tomcat/logs/localhost_access_log.*.txt"
stat_interval => 3
start_position => "beginning"
codec => "json"
type => "tomcat_accesslog"
}
}
output {
# stdout {
# codec => rubydebug
# }
if [type] == "tomcat_accesslog" {
elasticsearch {
hosts => ["192.168.32.41:9200"]
index => "tomcat_accesslog-%{+YYYY.MM.dd}"
}}
}
PS:可以分开至不同的配置文件,也可以合并至一个文件中,不过我们习惯不同服务用单独的配置文件来抓取,方便修改。
重启logstash服务,然后去kibana中创建tomcat_accesslog-*的索引,就可以看到tomcat的访问日志了。
JAVA服务中的日志如果不报错还好,还可以是一行一条,但是一旦出现报错信息,一般都是小半屏幕N多行的报错信息,而我们收集日志的很重要的目的就是为了查看这些报错信息,而如果不对这些日志进行处理,还按照一行一条来收集的话,当我们查看这个日志的时候就会很崩溃了——这乱的几乎完全没法看。所以我们要想办法将JAVA服务的日志处理成一行。
刚好强大的logstash也支持换行匹配,我们刚好就以logstash服务本身日志为例1
vim /etc/logstash/conf.d/java-el.conf
1 | input { |
重启logstash服务,然后去kibana中创建java_accesslog-*的索引,就可以看到java的访问日志了。
从redis读取日志和将收集到的日志储存至redis缓存中,其实使用了input的redis插件和output的redis插件来实现。官方文档地址为: https://www.elastic.co/guide/en/logstash/current/plugins-outputsredis.html
我们还以tomcat日志及java日志logstash日志本身为例,写入redis缓存中。
先配置好redis服务器如下:1
2
3IP:192.168.32.31
PORT:6379
auth:123456
1 | input { |
将日志数据以列表的形式储存在redis中,多个不同的日志,储存在不同数据库中。
与写入用法大致相同,可以说是怎么写进去的就怎么读出来。形式如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32input {
redis {
data_type => "list"
key => "tomcat_redis_accesslog"
host => "192.168.32.31"
port => "6379"
db => "0"
password => "123456"
codec => "json"
}}
redis {
data_type => "list"
key => "java_redis_accesslog"
host => "192.168.32.31"
port => "6379"
db => "1"
password => "123456"
codec => "json"
}}
}
output {
if [type] == "tomat_redis_accesslog" {
elasticsearch {
hosts => ["192.168.32.41:9200"]
index => "tomcat_redis_accesslog-%{+YYYY.MM.dd}"
}}
if [type] == "java_redis_accesslog" {
elasticsearch {
hosts => ["192.168.32.41:9200"]
index => "java_redis_accesslog-%{+YYYY.MM.dd}"
}}
}
不过需要注意的是input中记得加codec => "json"
,否则无法解析正确各项属性,不方便我们查看和统计日志。而且不知道大家有没有发现,当我们从redis中取出数据的时候,我们的input选项中没有像往常那样写上type => "tomcat_redis_accesslog"
,而是直接在output中做了type的判断。这是因为,redis中的数据是在写入的时候,已经附加了type属性,它在redis中储存时还是会保留type属性的,所以取出来的时候,还是按照之前写入时的type类型取出即可。
在很多大型互联网公司,都喜欢使用kafka来作为缓存层,因为redis虽然效率很高,但数据不如kafka可靠,kafka更适合大数据量场景使用。这就要视业务实际情况而定了。不过使用kafa来作为中间缓冲区的企业还是大有人在的。
我们可以新开启一个kafka主机,IP为192.168.32.36,使用默认端口9092。kafak的使用方法与redis大致相同。配置文件示例如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17input {
file {
path => "/apps/nginx/logs/access_json.log"
stat_interval => 3
start_position => "beginning"
codec => "json"
type => "nginx_kafka_accesslog"
}
}
output {
if [type] == "nginx_kafka_accesslog" {
kafka {
bootstrap_servers => "192.168.32.36:9092" #kafka 服务器地址
topic_id => "nginx_kafka_accesslog"
codec => "json"
} }
}
与写入redis有些区别的就是,kafka中不是key
了,而是topic_id
。所以读取时也有一些区别,填写的关键字为topics
,而且同样做判断时,type
是当初写进去的那个属性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16input {
kafka {
bootstrap_servers => "192.168.32.36:9092"
topics => "nginx_kafka_accesslog"
codec => "json"
consumer_threads => 1 #线程数,默认值为1,一般设置与分区数量相同
#decorate_events => true #不写默认是关闭的,开启此属性可以将当前topic、offset、group、partition等信息也带到message中
}
}
output {
if [type] == "nginx_kafka_accesslog" {
elasticsearch {
hosts => ["192.168.32.41:9200"]
index => "nginx_kafka_accesslog-%{+YYYY.MM.dd}"
}}
}
kafka也支持多文件同时写入,设置不同的topic_id
就可以了。
ELK是Elasticsearch、Logstash、Kibana的简称,这三者是核心套件,但并非全部。
Elasticsearch是实时全文搜索和分析引擎,提供搜集、分析、存储数据三大功能;是一套开放REST和JAVA API等结构提供高效搜索功能,可扩展的分布式系统。它构建于Apache Lucene搜索引擎库之上。
Logstash是一个用来搜集、分析、过滤日志的工具。它支持几乎任何类型的日志,包括系统日志、错误日志和自定义应用程序日志。它可以从许多来源接收日志,这些来源包括 syslog、消息传递(例如 RabbitMQ)和JMX,它能够以多种方式输出数据,包括电子邮件、websockets和Elasticsearch。
Kibana是一个基于Web的图形界面,用于搜索、分析和可视化存储在 Elasticsearch指标中的日志数据。它利用Elasticsearch的REST接口来检索数据,不仅允许用户创建他们自己的数据的定制仪表板视图,还允许他们以特殊的方式查询和过滤数据。
通常来说,只使用这三个组件就可以进行日志收集了,不过在企业实际生产中,需要用到ELK做集中日志收集的话,日志的产生量都是惊人的,所以通常情况下会需要缓存层来防止elasticsearch被压垮。架构如下图所示。(也可以通过filebeat来收集日志。)
需要注意的是,ELK的这三个组件版本要一致,否则可能会出现一些不必要的问题。我们这里选用最新版本7.5.1为例,演示主机均为ubuntu1804。
我们这里用两台主机来搭建一个elasticsearch集群,一般来说因为他的选举机制,elasticsearch集群都是3、5、7奇数个,不过2台主机也可以使用,我们这里节约主机使用两台主机做演示,IP分别为192.168.32.41
、192.168.32.42
。
官网下载链接为https://www.elastic.co/cn/downloads/elasticsearch。
我们这里选择deb安装包安装,也可以选用源码tar包自己解压安装。1
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.5.1-amd64.deb
这个版本的deb包是自带java环境(openjdk11)的,如果主机已经预制java环境,可以去官网下载no-java的版本,使用jdk8的时候有warning,说未来版本将不支持jdk8,建议使用jdk11及以上。1
dpkg -i elasticsearch-7.5.1-amd64.deb
elasticsearch的配置文件路径为/etc/elasticsearch/elasticsearch.yml
,需要修改的不多,将集群主机IP设置好就可以了,如下所示1
2
3
4
5
6
7
8
9
10root@elasticsearch1:~
cluster.name: ELK-CLuster
node.name: node-1
path.data: /elasticsearch/data
path.logs: /elasticsearch/logs
bootstrap.memory_lock: true
network.host: 0.0.0.0
http.port: 9200
discovery.seed_hosts: ["192.168.32.41","192.168.32.42"]
cluster.initial_master_nodes: ["node-1","node-2"]
我这里是单独创建了一个日志路径和数据路径,方便管理,并修改属主赋予权限。1
2mkdir -p /elasticsearch/{data,logs}
chown -R elasticsearch:elasticsearch /elasticsearch
除了在配置文件中设置bootstrap.memory_lock: true
以外,还需要在启动配置文件中设置允许无限制使用内存,否则启动检查就会报错,导致服务起不来。1
2vim /usr/lib/systemd/system/elasticsearch.service
LimitMEMLOCK=infinity
而且根据官方文档https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html,最大30G 以内的内存。
在一般使用中,使用最大内存和最小内存都设置为2G就可以了。1
2
3vim /etc/elasticsearch/jvm.options
-Xms2g
-Xmx2g
另一台主机也同样配置,记得修改node.name
,之后就可以启动elasticsearch了。1
systemctl enable --now elasticsearch
在任意主机使用curl命令可以检查集群的健康状态。1
curl -sXGET http://192.168.32.41:9200/_cluster/health?pretty=true
获取到的是一个 json 格式的返回值,那就可以通过 python 对其中的信息进行分析,例如对 status 进行分析,如果等于 green(绿色)就是运行在正常,等于yellow(黄色)表示副本分片丢失, red(红色)表示主分片丢失。
至此,elasticsearch服务的部署就算是完成了。
logstash也是一个基于java的插件式服务,很多功能都是依靠于插件来实现的,我们安装官方的安装包,大部分常用插件都是已经预置了,如果还有其他的功能需求,就需要去官网或者github下载插件了。这些之后再说,我们先去官网上将Logstash7.5.1安装包下载下来并部署上。1
wget https://artifacts.elastic.co/downloads/logstash/logstash-7.5.1.deb
logstash也同样需要java环境1
2apt update
apt install openjdk-8-jdk
或者安装oracle的jdk,生产环境还是推荐使用oracle公司的jdk,更加稳定。然后安装logstash1
dpkg -i logstash-7.5.1.deb
对于logstash的配置也很少,不做修改也可以使用,不过我们这里同样还是修改一下数据目录和日志目录1
2
3root@logstash1:~# grep "^[a-Z]" /etc/logstash/logstash.yml
path.data: /logstash/data
path.logs: /logstash/logs
修改属主1
2mkdir -p /logstash/{data,logs}
chown -R /logstash
logstash的默认执行程序路径为/usr/share/logstash/bin/logstash
,这其实也是一个shell脚本文件,脚本中调用java的类库。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31root@logstash1:~# /usr/share/logstash/bin/logstash --help
Thread.exclusive is deprecated, use Thread::Mutex
WARNING: Could not find logstash.yml which is typically located in
$LS_HOME/config or /etc/logstash. You can specify the path using --path.settings.
Continuing using the defaults
Usage:
bin/logstash [OPTIONS]
Options:
-n, --node.name NAME Specify the name of this logstash instance,
if no value is given
it will default to the current hostname.
(default: "logstash1")
-f, --path.config CONFIG_PATH Load the logstash config from a specific file
or directory. If a directory is given, all
files in that directory will be concatenated
in lexicographical order and then parsed as a
single config file. You can also specify
wildcards (globs) and any matched files will
be loaded in the order described above.
-e, --config.string CONFIG_STRING Use the given string as the configuration
data. Same syntax as the config file. If no
input is specified, then the following is
used as the default input:
"input { stdin { type => stdin } }"
and if no output is specified, then the
following is used as the default output:
"output { stdout { codec => rubydebug } }"
If you wish to use both defaults, please use
the empty string for the '-e' flag.
(default: nil)
不过常用的选项也就-e
和-f
,分别是通过命令行指定参数或者通过文件来指定配置参数。我们可以使用命令来测试1
/usr/share/logstash/bin/logstash -e 'input { stdin{} } output { stdout{ codec => rubydebug }}'
通过标准输入输入信息,并通过标准输出返回日志信息。同样,我们也可以调用input的file插件和output的file插件实现从文件中读取数据,或者写入文件。这样就可以实现对日志文件的抓取了。我们可以先尝试抓取系统日志如syslog
。1
/usr/share/logstash/bin/logstash -e 'input { file { path => "/var/log/syslog"} } output { stdout{ codec => rubydebug }}'
哈,系统日志如果太多,估计会刷屏的。
不过刚才那些都只是一些基本用法而已,而实际生产中,我们肯定不能使用命令行来手动获取数据,我们需要的是一个可靠的服务,来帮我们自动抓取日志并筛选过滤,这就需要我们使用配置文件来设置了。
例如我们要做的抓取本机的nginx的访问日志、错误日志还有系统日志,并传递至之前配好的elasticsearch中。
那就在路径/etc/logstash/conf.d/
目录下,创建一个新的配置文件,使用systemd启动时会自动读取conf.d下的配置文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40vim /etc/logstash/conf.d/nginx.conf
input {
file {
path => "/var/log/syslog"
stat_interval => 3
start_position => "beginning"
type => "syslog"
}
file {
path => "/apps/nginx/logs/access_json.log"
stat_interval => 3
start_position => "beginning"
codec => "json"
type => "nginx_accesslog"
}
file {
path => "/apps/nginx/logs/error.log"
stat_interval => 3
start_position => "beginning"
type => "nginx_errorlog"
}
}
output {
if [type] == "syslog" {
elasticsearch {
hosts => ["192.168.32.41:9200"]
index => "syslog-%{+YYYY.MM.dd}"
}}
if [type] == "nginx_accesslog" {
elasticsearch {
hosts => ["192.168.32.41:9200"]
index => "nginx_accesslog-%{+YYYY.MM.dd}
}}
if [type] == "nginx_errorlog" {
elasticsearch {
hosts => ["192.168.32.41:9200"]
index => "nginx_accesslog-%{+YYYY.MM.dd}
}}
}
logstash支持条件判断,多输入以及多输出,设定type规则,来将每一类日志分类在不同的索引,且支持java的时间变量,可以实现根据日期归档每一天的日志,方便查看和统计。
我们可以使用命令来测试脚本的语法是否正确,如果不加-t
可以直接以前台进程的方式启动logstash,不过会占据终端,但测试的时候还是蛮方便的。1
/usr/share/logstash/bin/logstash -f /etc/log/logstash/conf,d/nginx.conf -t
不过仅仅是这样,是无法统计具体访问时间、访问ip及访问路径的详细信息的,我们需要将nginx的日志序列化,或者说是储存为json格式。
所以修改nginx的配置文件,将日志格式修改一下。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
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",'
'"uri":"$uri",'
'"domain":"$host",'
'"xff":"$http_x_forwarded_for",'
'"referer":"$http_referer",'
'"tcp_xff":"$proxy_protocol_addr",'
'"http_user_agent":"$http_user_agent",'
'"status":"$status"}';
access_log /apps/nginx/logs/access_json.log access_json;
PS:加入的属性名称不要有type
,否则会影响到logstash做type判断。然后记得在配置文件中注明输入信息为json格式。看到如下信息,则说明日志被成功拆解。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
"http_user_agent" => "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0",
"path" => "/apps/nginx/logs/access_json.log",
"@timestamp" => 2020-01-03T08:17:47.000Z,
"upstreamhost" => "-",
"xff" => "-",
"responsetime" => 0.0,
"size" => 0,
"status" => "304",
"http_host" => "192.168.32.51",
"clientip" => "192.168.32.1",
"domain" => "192.168.32.51",
"tcp_xff" => "",
"host" => "192.168.32.51",
"@version" => "1",
"uri" => "/index.html",
"referer" => "-",
"upstreamtime" => "-"
}
使用systemctl enable --now logstash
启动logstash服务,过一会,日志就写入elasticsearch服务器中了,最好将/etc/systemd/system/logstash.service
文件的中启动用户组都改为root
,避免因为权限问题,导致无法读取数据。
我记得之前遇到过一次,命令行可以正常使用logstash,不过使用systemd启动就一直报错,logstash: could not find java; set JAVA_HOME or ensure java is in PATH
,明明环境变量都是正常的,后来在/usr/share/logstash/bin/logstash
脚本文件中加了一个JAVA_HOME=/usr/local/jdk
环境变量就好了,而之后配的时候就没有遇到这个问题了,这就很奇怪了。
日志信息都已经写到elasticsearch服务器中了,不过我们怎么才可以看到具体的日志信息呢?这就需要借助日志展示工具Kibana了。
虽然elasticsearch可视化工具也有不少,例如head、kopf、cerebro等等,不过他们都是监控elasticsearch集群状态的,对日志做展示分析的还是首推开源的官方组件Kibana。
同样下载kibana7.5.1版本1
https://artifacts.elastic.co/downloads/kibana/kibana-7.5.1-amd64.deb
配置上也很简单,设置监听IP及端口,设置elasticsearch主机地址,最后再将语言环境改为中文就可以启动kibana服务了。1
2
3
4
5root@kibana1:~
server.port: 5601
server.host: "0.0.0.0"
elasticsearch.hosts: ["http://192.168.32.41:9200"]
i18n.locale: "zh-CN"
浏览器访问kibana主机的5601端口。
创建索引模式,依次添加索引。
然后点左边第一个(最上面)的那个discover图标,就可以看到数据了。点击更改,选择对应的索引,还可以创建各种视图等一系列功能这里就不再详细讲解了。都比较简单。
至此ELK的初步部署就算完成了。
在企业生产中,DEVOPS这个概念越来越火了,不同公司对此都有不同的理解,但有一点毋庸置疑,提到DEVOPS都绕不开CI/CD。CI是continuous integration的简称,意为持续集成,CD是continuous deployment或者Continuous Delivery的缩写,意为持续部署或持续交付。
持续集成是指多名开发者在开发不同功能代码的过程当中,可以频繁的将代码行合并到一起并切相互不影响工作。
持续部署是指是基于某种工具或平台实现代码自动化的构建、测试和部署到线上环境以实现交付高质量的产品。持续部署在某种程度上代表了一个开发团队的更新迭代速率。
持续交付是在持续部署的基础之上, 将产品交付到线上环境, 因此持续交付是产品价值的一种交付, 是产品价值的一种盈利的实现。
可以用一个类似戴明环的图来比较形象展示这种生产模式结构。
比较常用到的开源软件有gitlab、Maven、jenkins、saltstack、slastic以及zabbix。其中gitlab和jenkins是最常用的一个组合,也是目前最为火热的结局方案。本文将详细介绍gitlab的部署及使用。
我们需要在公司的服务器安装某种程序,该程序用于按照特定格式和方式记录和保存公司多名开发人员不定期提交的源代码,且后期可以按照某种标记及方式对用户提交的数据进行还原,这是我们就需要用到版本控制系统,也就是所谓的持续集成工具。
早期的集中式版本控制系统如CVS(Concurrent Version System),现已基本淘汰,可能有些公司还在使用SVN(Subversion)作为版本控制系统,不过他的缺点相当明显,集中式管理,太依赖于网络带宽,当大家一起从管理服务器拉代码或提交代码时,遇到网速慢的话,可能提交一个10M的文件就需要5分钟,效率很差。
而gitlab是分布式的版本控制系统,不存在“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作需要提交或者回滚时,就不需要联网了,因为版本库就在你自己的电脑上。需要汇总时,我们将代码提交至gitlab服务器,将每个人的劳动成果也就是分支仓库的代码合并即可。
具体安装要求及配置可以参考官方文档
https://about.gitlab.com/install/ # Gitlab 服务的安装文档
https://docs.gitlab.com/ce/install/requirements.html #安装环境要求
最好先配置好依赖仓库源,可以配置阿里的镜像仓库源。
官方下载deb安装包比较慢,可以使用清华大学的镜像源下载。
我们这里使用gitlab-ce_11.11.8来演示。也可以去下载其他版本,ubuntu 国内下载地址: https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/。1
2wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/bionic/main/g/gitlab-ce/gitlab-ce_11.11.8-ce.0_amd64.deb
dpkg -i gitlab-ce_11.11.8-ce.0_amd64.deb
安装可能需要一段时间,耐心等待。
然后修改gitlab的配置文件1
2
3
4
5
6
7
8
9
10
11
12
13
14root@gitlabUbuntu24:~# grep "^[a-Z ]" /etc/gitlab/gitlab.rb
external_url 'http://192.168.32.24'
gitlab_rails['gitlab_email_from'] = 'example@qq.com'
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.qq.com"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "example@qq.com"
gitlab_rails['smtp_password'] = "keyword"
gitlab_rails['smtp_domain'] = "qq.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = true
user['git_user_email'] = "example@#qq.com"
alertmanager['admin_email'] = 'example@qq.com'
将上面这些选项都设置好。例如使用的是QQ邮箱,则需要去邮箱设置里获取授权码,填在password
处。因为gitlab是一般是通过邮件来注册和获取密码的,所以这些配置都要有,邮箱最好填企业邮箱或者公司领导邮箱,以避免员工离职等其他风险。
然后使用命令让配置文件生效。1
gitlab-ctl reconfigure
这时就可以通过命令gitlab-ctl start
启动gitlab了。
可以通过gitlab-ctl status
命令查看gitlab运行状态,或者后跟组件名称查看具体组件的运行状态。
1 | /etc/gitlab #配置文件目录 |
gitlab用法与github几乎相同,这里就不再详细介绍。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16git config --global user.name “name“ #设置全局用户名
git config --global user.email xxx@xx.com #设置全局邮箱
git config --global --list #列出用户全局设置
git add index.html / . #添加指定文件、 目录或当前目录下所有数据到暂存区
git commit -m “11“ #提交文件到工作区
git status #查看工作区的状态
git push #提交代码到服务器
git pull #获取代码到本地
git log #查看操作日志
vim .gitignore #定义忽略文件
git reset --hard HEAD^^ #git 版本回滚, HEAD 为当前版本,加一个^为上一个, ^^为上上一个版本
git reflog # #获取每次提交的 ID,可以使用--hard 根据提交的 ID 进行版本回退
git reset --hard 5ae4b06 #回退到指定 id 的版本
git branch #查看当前所处的分支
git checkout -b develop #创建并切换到一个新分支
git checkout develop #切换分支
gitlab可以通过命令gitlab-rake
来备份数据。数据备份前,需要停止unicorn
、sidekiq
组件服务。1
2
3
4gitlab-ctl stop unicorn
gitlab-ctl stop sidekiq
gitlab-rake gitlab:backup:create #在任意目录即可备份当前 gitlab 数据
gitlab-ctl start #备份完成后启动 gitlab
备份完成的数据储存在/var/opt/gitlab/backups/
路径下,生成与时间相关的tar包。1
2
3
4
5root@gitlabUbuntu24:~# ll /var/opt/gitlab/backups/
total 4600
drwx------ 2 git root 4096 Dec 25 14:41 ./
drwxr-xr-x 20 root root 4096 Dec 25 11:02 ../
-rw------- 1 git git 4700160 Dec 25 14:41 1577256108_2019_12_25_11.11.8_gitlab_backup.tar
需要注意的是,命令备份仅仅是备份了gitlab中的数据,通常我们我们的备份脚本中还需要备份以下文件。1
2
3/var/opt/gitlab/nginx/conf #nginx 配置文件
/etc/gitlab/gitlab.rb #gitlab 配置文件
/etc/gitlab/gitlab-secrets.json #key 文件
使用命令gitlab-rake gitlab:backup:restore BACKUP=备份文件名
可以恢复之前的备份数据(文件名到时间戳即可)。同样,执行恢复命令之前也要先关闭unicorn
、sidekiq
组件服务。恢复时,需要输入yes确认。1
2
3gitlab-ctl stop unicorn
gitlab-ctl stop sidekiq
gitlab-rake gitlab:backup:restore BACKUP=1577256108_2019_12_25_11.11.8
服务正常启动之后,就可以访问主机的IP或者域名,登陆gitlab系统了。gitlab和github极为相似,也支持邮箱注册。,第一次访问,会放我们设置管理员账户密码,我们使用管理员账户登录,用户名为root。
gitlab默认是开放注册的,而我们作为企业内部使用的版本管理系统,肯定不希望大家随便创建用户来访问,这样很不便于管理。
我们可以在点击中间的小扳手图标,选择setting设置,将sign-up功能取消,这样登录界面就无法注册了。
注意,小心别关错了,要是把sign-in功能取消就没法登陆了,哪怕使用root权限账号退出去也就没法登陆了。如果真的不幸将gitlab的sign-in功能取消掉,那只能使用命令行的方式修改数据库中设置了。
因为gitlab目前版本默认使用的psql,所以修改数据库的方式,跟mysql不同,具体如下
先切换至gitlab-psql用户。1
su - gitlab-psql
使用 -h指定数据库路径 -d指定数据库,连接psql数据库。1
2
3psql -h /var/opt/gitlab/postgresql -d gitlabhq_production
UPDATE application_settings SET password_authentication_enabled_for_web=true;
\q
修改完数据后,重启gitlab服务。1
2exit
gitlab-ctl restart
此时,web界面就又可以登陆了。
gitlab默认语音为英语,有时有些专业数据或者不太常用的选项配置我们可能无法准确理解它的含义,所以将gitlab汉化成中文还是有一定必要的。
gitlab的每个用户,可以点击用户头像,设置,设置偏好,选择语音。
选择中文之后刷新,就可看到一部分菜单变为了中文。但是也仅仅是菜单变成了中文而已,很多选项和图标都还是英文显示,如果想彻底汉化为中文,则需要我们去下载中文汉化补丁了。
汉化补丁是第三方爱好者提供,github地址是https://gitlab.com/xhang/gitlab,找到对应版本的gitlab汉化包。
gitlabv11.11.8汉化包的下载地址如下1
wget https://gitlab.com/xhang/gitlab/-/archive/v11.11.8-zh/gitlab-v11.11.8-zh.tar.gz
然后先停止gitlab1
gitlab-ctl stop
备份并替换文件1
2
3tar xvf gitlab-v11.11.8-zh.tar.gz
cp -rp /opt/gitlab/embedded/service/gitlab-rails /opt/gitlab-rails.bak
cp -rf gitlab-v11.11.8-zh/* /opt/gitlab/embedded/service/gitlab-rails/
此时,汉化就完成了,可以直接启动gitlab了。就可以看到页面都变成了中文了,效果图如下。
常见的开源监控软件有:cacti、nagios、zabbix、smokeping、open-falcon等,本文主要介绍目前使用较多的开源监控软件zabbix,针对容器环境的开源监控软件Prometheus下次再讲解。
zabbix功能强大,可横向扩展、自定义监控项、支持多种监控方式、可监控网络与服务等。
zabbix-server
、zabbix-agent
、zabbix-proxy
,及数据库,结构如下图所示:zabbix-agent
服务来抓取数据,然后汇总到zabbix-server
端来展示分析监控报警,如果agent过多或者可能不同机房的数据,可以通过zabbix-proxy
来暂存收集数据,之后在汇总至zabbix-server
端,所以zabbix-proxy
端及zabbix-server
端都需要一个mysql数据库来储存即时及历史监控数据的(zabbix-proxy处临时储存)。而且整个体系中最重要的就是数据库了,数据都在数据库中,只要数据库中的数据不丢失,重建一个zabbix监控架构还是比较容易的,所以可以对数据库做主从复制高可用,可参见之前文章。zabbix-server
端。zabbix-server-mysql
、zabbix-frontend-php
及zabbix-agent
,其中zabbix-server的二进制程序及启动配置文件都在在zabbix-server-mysql
安装包里。1 | wget https://repo.zabbix.com/zabbix/4.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_4.0-3%2Bbionic_all.deb |
然后开始安装zabbix-server的数据库。可以与zabbix-server复用一台主机,也可以单独一台主机。我们这里使用一台新的主机,apt安装好mariadb(与mysql操作一样)。
先以root身份登陆mysql主机,然后为zabbix创建一个数据库,例如zabbix_server,再创建一个zabbix用户,并授权1
2
3MariaDB [mysql]> create database zabbix_server character set utf8 collate utf8_bin;
MariaDB [mysql]>grant all privileges on zabbix_server.* to zabbix@"192.168.32.%" identified by "zabbix";
MariaDB [mysql]>flush privileges;
然后回到zabbix-server主机,安装一个mysql客户端,并测试可否用之前创建的zabbix账号登录数据库。1
2apt mysql-client -y
mysql -uzabbix -pzabbix -h192.168.32.20
确保可以登陆之后,导入数据库表结构1
zcat /usr/share/doc/zabbix-server-mysql/create.sql.gz | mysql -uzabbix -pzabbix -h192.168.32.20 zabbix_server
此时在登入数据库服务器,使用命令查询,可以看到已经生成了很多表1
mysql> show tables from zabbix_server;
然后编辑zabbix配置文件1
vim /etc/zabbix/zabbix_server.conf
修改数据库相关信息,其他的可以不做修改。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16root@DockerUbuntu19:~# grep "^[a-Z]" /etc/zabbix/zabbix_server.conf
LogFile=/var/log/zabbix/zabbix_server.log
LogFileSize=1
PidFile=/var/run/zabbix/zabbix_server.pid
SocketDir=/var/run/zabbix
DBHost=192.168.32.20
DBName=zabbix_server
DBUser=zabbix
DBPassword=zabbix
StartTrappers=10
Timeout=30
AlertScriptsPath=/usr/lib/zabbix/alertscripts
ExternalScripts=/usr/lib/zabbix/externalscripts
FpingLocation=/usr/bin/fping
Fping6Location=/usr/bin/fping6
LogSlowQueries=3000
重启zabbix服务。1
systemctl restart zabbix-server zabbix-agent apache2
此时访问zabbix-server主机对应的IP或者域名,加上路径zabbix/setup.php
就可以访问zabbix的web界面进行检查配置了。
在环境检查时,可能会有一项报错,提示PHP option date.timezone
检查unkown。虽然不影响服务启动,不过我们最好还是将他解决掉。1
vim /etc/zabbix/apache.conf
搜索timezone
,将值改为php_value date.timezone Asia/Shanghai
。注意,有两个timezone设置,分别是针对PHP5.版本和PHP7.版本,我们修改7版本的就可以了,也可以都修改。然后重启服务。1
systemctl restart zabbix-server zabbix-agent apache2
此时,再刷新一下网页,就可以看到所有检查都是OK状态了。正确填写数据库信息和server主机信息,点击Finish,配置就完成了,会自动跳转至登录界面,默认用户名为Admin
,默认密码为zabbix
。
源码编译安装zabbix的话,先下载源码包。
下载路径为https://jaist.dl.sourceforge.net/project/zabbix/ZABBIX%20Latest%20Stable/。
我们这里以4.015版本为例1
2
3
4cd /usr/local/src
wget https://jaist.dl.sourceforge.net/project/zabbix/ZABBIX%20Latest%20Stable/4.0.15/zabbix-4.0.15.tar.gz
tar xf zabbix-4.0.15.tar.gz
cd zabbix-4.0.15/
编译安装需要我们自己来创建zabbix用户组。1
2groupadd -g 1111 zabbix #创建zabbix用户和组
useradd -u 1111 -g 1111 zabbix
安装相关依赖的安装包
CentOS:1
yum install gcc libxml2-devel net-snmp net-snmp-devel curl curl-devel php phpbcmath php-mbstring mariadb mariadb-devel -y
Ubuntu:1
2apt update
apt-get install apache2 apache2-bin apache2-data apache2-utils fontconfig-config fonts-dejavu-core fping libapache2-mod-php libapache2-mod-php7.2 libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libfontconfig1 libgd3 libiksemel3 libjbig0 libjpeg-turbo8 libjpeg8 liblua5.2-0 libodbc1 libopenipmi0 libsensors4 libsnmp-base libsnmp30 libsodium23 libssh2-1 libtiff5 libwebp6 libxpm4 php-bcmath php-common php-gd php-ldap php-mbstring php-mysql php-xml php7.2-bcmath php7.2-cli php7.2-common php7.2-gd php7.2-json php7.2-ldap php7.2-mbstring php7.2-mysql php7.2-opcache php7.2-readline php7.2-xml snmpd ssl-cert ttf-dejavu-core libmysqlclient-dev libxml2-dev libxml2 snmp libsnmp-dev libevent-dev openjdk-8-jdk curl libcurl4-openssl-dev
然后就可以编译并安装了。1
2
3
4./configure --prefix=/apps/zabbix_server \
--enable-server --enable-agent --with-mysql \
--with-net-snmp --with-libcurl --with-libxml2 --enable-java
make install
之后流程就与之前一样。
如果Ubuntu系统安装时为选择中文语言,则web界面大概率是英文界面,可能会对我们的使用有一定影响。
点击右上角的用户头像标志,可以选择语音。不过如果系统没有安装中文时,是无法选择中文的。
所以需要我们在ubuntu系统中安装并设置中文简体语言环境.。
1、安装简体中文语言环境1
apt-get install language-pack-zh*
2、增加中文语言环境变量1
2
3vim /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
LANG="zh_CN.UTF-8"
3、重新设置本地配置1
dpkg-reconfigure locales
此时刷新网页,就可以选择中文Chinese(zh_CN)
了,点Update修改保存。此时就可以看到界面变成中文了。不过这时,在图形等界面,大概率会出现字符无法显示的乱码情况,这是由于当前系统有些监控项部分显示有乱码,使由于web界面显示为中文但是系统没有相关字体支持,因此需要相关字体的支持才能正常显示。这时需要我们自己配置一个字体并修改zabbix的font设置。
可以去网上下载字体,也可以从windows中获取已有字体。
在windows中路径为控制面板\外观和个性化\字体
(复制至地址栏就可以找到了)
不过需要注意的是,里面有的字体是otf格式,zabbix无法识别,要选择ttf格式的字体才可以。
将选好的或者下载好的ttf格式字体,拷贝至zabbix-server
主机的zabbix安装目录下的fonts目录里。
如果是ubuntu通过apt安装,则为/usr/share/zabbix/assets/fonts
,如果是编译安装则可通过find
命令搜索一下find /PATH(安装路径) -name fonts
。
可以看到里面已经有一个字体文件了的(虽然是个软链接)。1
2
3
4
5
6root@DockerUbuntu19:~# ll /usr/share/zabbix/assets/fonts
总用量 11520
drwxr-xr-x 2 root root 4096 12月 18 18:59 ./
drwxr-xr-x 5 root root 4096 12月 18 17:43 ../
lrwxrwxrwx 1 root root 38 12月 18 17:44 graphfont.ttf -> /etc/alternatives/zabbix-frontend-font
-rw-r--r-- 1 root root 11785184 12月 18 18:59 simkai.ttf
将我们准备好的ttf文件拷贝至此目录,修改zabbix环境变量配置文件(apt安装路径为/usr/share/zabbix/include/defines.inc.php)1
2cd /usr/share/zabbix/
vim include/defines.inc.php
搜索替换graphfont
为我们期望的字体名称。一共有两处。都替换了即可。1
2
3
4
5#define('ZBX_GRAPH_FONT_NAME', 'graphfont'); // font file name
define('ZBX_GRAPH_FONT_NAME', 'simkai'); // font file name
- - -
#define('ZBX_FONT_NAME', 'graphfont');
define('ZBX_FONT_NAME', 'simkai');
不需要重启zabbix及apache,修改后的字体文件即可直接生效。
至此zabbix-server端的配置就完成了。
功能 | zabbxy proxy | zabbix server |
---|---|---|
轻量级 | 是 | 相对重量级 |
图形 | 无 | 带图形控制界面 |
可以独立工作 | 是,可以独立采集数据并存储 | 是,即数据采集、存储、分析、展示于一体 |
易维护 | 是,配置完成后基本无需管理 | 维护也不难 |
独立数据库 | 保留少量最近数据 | 保留指定时间内的所有数据 |
报警通知 | 否,代理服务器不发送邮件通知 | 支持邮件、短信等告警机制 |
zabbix proxy的大版本必须要和zabbix server版本一致,否则会导致出现zabbix server与zabbix proxy不兼容问题,会提示报错:1
proxy "zabbix-proxy-active" protocol version 3.2 differs from server version 4.0
确认下zabbix-server的版本1
2
3
4
5
6
7
8
9
10
11
12
13
14root@DockerUbuntu19:/usr/share/zabbix/assets/fonts# zabbix_server -V
zabbix_server (Zabbix) 4.0.15
Revision 992445e02c 25 November 2019, compilation time: Nov 25 2019 09:01:31
Copyright (C) 2019 Zabbix SIA
License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it according to
the license. There is NO WARRANTY, to the extent permitted by law.
This product includes software developed by the OpenSSL Project
for use in the OpenSSL Toolkit (http://www.openssl.org/).
Compiled with OpenSSL 1.1.0g 2 Nov 2017
Running with OpenSSL 1.1.1 11 Sep 2018
所以我们最好也安装相同版本的zabbix-proxy。
因为proxy也需要一个数据库,可以选择复用server端的数据库,或者再另外创建一个。我们这里复用之前server端的数据库。
所以在之前的MariaDB数据库中,新建一个库,并创建对应权限用户。1
2
3
4
5MariaDB [(none)]> create database zabbix_proxy;
Query OK, 1 row affected (0.01 sec)
MariaDB [(none)]> grant all privileges on zabbix_proxy.* to proxy@"172.18.32.%" identified by "proxy";
Query OK, 0 rows affected (0.10 sec)
回到proxy主机,然后去官方镜像仓库https://repo.zabbix.com/zabbix/4.0/找到对应系统、对应版本的安装包路径
CentOS:1
yum install https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-proxy-mysql-4.0.15-1.el7.x86_64.rpm
Ubuntu:1
2
3
4wget https://repo.zabbix.com/zabbix/4.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_4.0-3%2Bbionic_all.deb
dpkg -i zabbix-release_4.0-3+bionic_all.deb
apt update
apt install zabbix-proxy-mysql
导入proxy的数据库表(CentOS和Ubuntu路径可能不同,不过名字都叫schema.sql.gz)1
zcat /usr/share/doc/zabbix-proxy-mysql-4.0.15/schema.sql.gz | mysql -uproxy -pproxy -h192.168.32.20 zabbix_proxy
修改配置文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26vim /etc/zabbix/zabbix_proxy.conf
ProxyMode=1 #0为主动,1为被动
Server=192.168.32.19 #zabbix server服务器的地址或主机名
Hostname=Zabbix proxy #代理服务器名称,需要与zabbix server添加代理时候的proxyname是一致的!
ListenPort=10051 #zabbix proxy监听端口
LogFile=/var/log/zabbix/zabbix_proxy.log
EnableRemoteCommands=1 #允许zabbix server执行远程命令
PidFile=/var/run/zabbix/zabbix_proxy.pid
SocketDir=/var/run/zabbix
DBHost=192.168.32.20 #数据库服务器地址
DBName=zabbix_proxy #使用的数据库名称
DBUser=proxy #连接数据库的用户名称
DBPassword=proxy #数据库用户密码
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 #启动的数据采集器数量
CacheSize=2G #保存监控项而占用的最大内存
HistoryCacheSize=2G #保存监控历史数据占用的最大内存
HistoryIndexCacheSize=128M #历史索引缓存的大小
Timeout=30 #监控项超时时间,单位为秒
LogSlowQueries=3000 #毫秒,多久的数据库查询会被记录到日志
然后在web界面添加proxy,主动模式是proxy端主动向server端推送数据,所以不需要填写IP,被动模式是server端向proxy拉取数据,需要填写proxy端IP或者域名DNS。选在主动代理或者被动代理,视情况而定,要与配置文件中相同。
至此,zabbix proxy端也就配置好了。
一般来说,zabbix agent与proxy有两种工作模式,一个是主动模式,即被监控端主动向server端或者proxy端发起请求,请求监控项列表,并按照列表的内容主动定时推送监控信息,此时agent端使用随机端口访问server端或者proxy端的固定端口(10051端口);另一个是被动模式,是server端或者proxy依照设定好的监控项条目,直接去agent段获取相应的数据,此时是agent端打开固定端口(10050)等待请求。
主动模式与被动模式中的主动与被动,是相对于agent端来说的,agent主动的就叫做主动模式,agent端被动的就叫做被动模式。
在被监控主机上安装zabbix-agent安装包。流程与之前一样。1
apt install zabbix-agent
修改配置文件,主要需要设置Server
和ServerActive
,其他保持默认就可以了。Server
是控制允许哪个主机可以从本机上拉取数据,一般把server端和paroxy端都写上,方便修改(,其实只写proxy端ip就可以了),ServerActive
是设置向哪个主机请求主动监控配置的,如果打算使用被动模式,则可不进行设置此项,而且设置了也不会生效。1
2
3
4
5
6
7
8
9
10
11root@DockerUbuntu21:~# grep "^[a-Z]" /etc/zabbix/zabbix_agentd.conf
PidFile=/var/run/zabbix/zabbix_agentd.pid
LogFile=/var/log/zabbix-agent/zabbix_agentd.log
LogFileSize=1
Server=192.168.32.19,192.168.32.20
ListenPort=10050
StartAgents=4
ServerActive=127.0.0.1
Hostname=192.168.32.21
Timeout=20
Include=/etc/zabbix/zabbix_agentd.conf.d/*.conf
重启agent服务,是配置生效1
systemctl restart zabbix-agent
之前的一系列操作,只是完成了一个服务基础,之后的操作才是重点,也就是配置监控。
进入zabbix的web管理界面,可以看到有很多项目,我们选择配置——主机——创建主机,来添加我们的监控对象。
在创建主机界面,需要选择一个模版,也就是监控规则,因为我们还没有创建模版,所以可以使用系统自带的模版,可以搜索linux
使用自带的一些监控项。
需要注意的是,agent使用工作模式到底是主动模式还是被动模式,就在于监控项的类型是主动还是被动模式。系统默认模版都是被动式的,如果想使用主动式,可以批量修改模版监控项(模版的具体介绍会在之后文章介绍)。
而且,agent端使用的主动式或者被动式方式,与proxy设置的主动模式还是被动模式没有关系。proxy创建时选择的模式要与proxy配置文件一致,而这个设定只是控制proxy与server之间的关系,而真正控制agent工作模式的就在于这个监控项目的设置了。也就是说如果监控项目都是被动式,ServerActive
设不设置都不会生效,如果有一部分项目是主动式,若没有设置正确的ServerActive
,则这些项目将会获取不到数据了。
至此,对于zabbix的初步配置就生效了,等一下就可以看到添加的主机都显示绿色的可用状态。
之后就可以在图形界面看到图形数据了。
本文使用kubeasz项目基于二进制方式部署和利用ansible-playbook实现自动化部署K8s。
架构图如下所示
根据kubeasz官方文档中高可用集群所需节点配置如下
角色 | 数量 | 描述 |
---|---|---|
管理节点 | 1 | 运行ansible/easzctl脚本,可以复用master,建议使用独立节点(1c1g) |
etcd节点 | 3 | 注意etcd集群需要1,3,5,7…奇数个节点,一般复用master节点 |
master节点 | 2 | 高可用集群至少2个master节点 |
node节点 | 3 | 运行应用负载的节点,可根据需要提升机器配置/增加节点数 |
此次部署节点设置如下:
ansible安装节点&master1:172.18.32.18
master2:172.18.32.19
harbor:172.19.32.20
node1:172.18.32.21
node2:172.18.32.22
etcd1:172.18.32.23
etcd2:172.18.32.24
etcd3:172.18.32.25
haproxy1+keepalived:172.18.32.183
haproxy2+keepalived:172.18.32.184 VIP:172.18.32.250
先将ansible安装节点也就是master1主机(可以不复用主机)的秘钥分发至各个主机。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41vim fenfamiyao.sh
!/bin/bash
目标主机列表
PASSWORD="password"
PORT="22"
IP="
172.18.32.18
172.18.32.19
172.18.32.20
172.18.32.21
172.18.32.22
172.18.32.23
172.18.32.24
172.18.32.25
"
[ -a /root/.ssh/id_rsa ] || ssh-keygen -t rsa
which sshpass &> /dev/null || yum install sshpass -y #适用于CentOS
for node in ${IP};do
sshpass -p $PASSWORD ssh-copy-id -p$PORT -o StrictHostKeyChecking=no ${node}
if [ $? -eq 0 ];then
echo "${node} 秘钥 copy 完成,准备环境初始化....."
ssh -p$PORT ${node} "mkdir /etc/docker/certs.d/harbor.hechao.com -p"
echo "Harbor 证书目录创建成功!"
scp -P$PORT /usr/local/src/harbor/certs/harbor-ca.crt ${node}:/etc/docker/certs.d/harbor.hechao.com/harbor-ca.crt
echo "Harbor 证书拷贝成功!"
scp -P$PORT /etc/hosts ${node}:/etc/hosts
echo "host 文件拷贝完成"
scp -r -P$PORT /root/.docker ${node}:/root/
echo "Harbor 认证文件拷贝完成!"
scp -r -P$PORT /etc/resolv.conf ${node}:/etc/
echo "镜像加速链接同步!"
ssh ${node} "mkdir -p /etc/docker"
scp -r -p$PORT /etc/docker/daemon.json ${node}:/etc/docker/daemon.json
else
echo "${node} 秘钥 copy 失败"
fi
done
因为要使用ansible批量自动化部署K8s集群,每个主机都要先安装ansible环境。
ubuntu默认的python版本是3.6,而ansible需要python2.7。1
2
3apt update
apt install python2.7
ln -s /usr/bin/python2.7 /usr/bin/python
master1上安装ansible1
apt install ansible
centos一般是自带python2.X环境,所以一般不需要在进行额外操作。
在master1上检查各节点连通性1
ansible all -m ping
官方文档地址为https://github.com/easzlab/kubeasz,根据适配的K8s版本选择合适的kubeasz版本。根据官方文档,目前支持以下版本
集群版本 kubernetes v1.13, v1.14, v1.15, v1.16
操作系统 CentOS/RedHat 7, Debian 9/10, Ubuntu 1604/1804
运行时 docker 18.06.x-ce, 18.09.x, containerd 1.2.6
网络 calico, cilium, flannel, kube-ovn, kube-router
我们下载2.0.3版本的kubeasz,并给予执行权限1
2curl -C- -fLO --retry 3 https://github.com/easzlab/kubeasz/releases/download/2.0.3/easzup
chmod +x easzup
可以用file easzup
命令看到这是一个ASCII文本文件,其实就是一个shell脚本。1
2root@DockerUbuntu18:~# file easzup
easzup: Bourne-Again shell script, ASCII text executable
用vim编辑修改此文件。
大部分地方无需修改,不过可以设置docker版本为18.09.9和K8s的版本为v1.15.5,其他地方不用动。1
2export DOCKER_VER=18.09.9
export K8S_BIN_VER=v1.15.5
执行-- help
查看脚本使用方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15root@DockerUbuntu18:~# ./easzup --help
./easzup: illegal option -- -
Usage: easzup [options] [args]
option: -{DdekSz}
-C stop&clean all local containers
-D download all into /etc/ansible
-S start kubeasz in a container
-d <ver> set docker-ce version, default "18.09.9"
-e <ver> set kubeasz-ext-bin version, default "0.3.0"
-k <ver> set kubeasz-k8s-bin version, default "v1.15.5"
-m <str> set docker registry mirrors, default "CN"(used in Mainland,China)
-p <ver> set kubeasz-sys-pkg version, default "0.3.2"
-z <ver> set kubeasz version, default "2.0.3"
see more at https://github.com/kubeasz/dockerfiles
先将/etc/ansible
目录下所有自带的配置文件以及hosts文件删掉1
\rm -rf /etc/ansible/*
执行脚本下载所有需要的镜像和二进制文件1
./easzup -D
稍等片刻之后,下载好需要的镜像和二进制文件,此时就可以开始ansible自动部署安装K8s集群了。
kubeasz工具都已经写好了playbook,只需要设定好hosts文件即可一键安装了。先进入/etc/ansible
目录,然后复制提供好的host模版文件并编辑。1
2
3cd /etc/ansible/
cp example/hosts.multi-node hosts
vim hosts
1 | root@DockerUbuntu18:/etc/ansible# grep -v ^# hosts|grep -v ^$ |
以为我本地已经搭好harbor服务器了,所以就不要设置harbor服务让他来安装了。除了各节点IP以外,需要修改的就是bin_dir
目录,这样就省去了创建软链接的步骤,也省去配置PATH变量了。我选择的网卡是calico
,也可以使用默认的flannel
,之后具体区别会再详细介绍。
直接执行 playbook
剧本90.setup.yml可以直接一键安装完成,不过如果出现问题我们不好排错,推荐一个剧本一个剧本的来跑。
1 | ansible-playbook 01.prepare.yml |
这个剧本总共三个环节,一般来说都不会报错(如果提示软链接创建失败,则可忽略)。
1.如果设置了chrony服务器,则master、node、etcd主机都会向chrony服务器同步时间。
2.控制节点上创建CA、创建集群参数及客户端认证参数
3分发证书工具CFSSL及kubeconfig配置文件
1 | ansible-playbook 02.etcd.yml |
这个剧本是在配置etcd服务器。在3个etcd节点上,创建etcd目录并分发证书,导入之前下载在控制端的二进制程序etcd
及etcdctl
及导入etcd的systemctl unit文件,设置开机自动启动etcd服务。这步也很简单,一般也不会出现什么问题。
因为我们在hosts文件中设置了运行时为docker
,所以我们第三个剧本选择03.docker.yml
,选择执行03.containerd,yml
剧本也不会执行,里面做了条件判断1
2
3
4
5
6
7root@DockerUbuntu18:/etc/ansible# cat 03.containerd.yml
# to install containerd service
- hosts:
- kube-master
- kube-node
roles:
- { role: containerd, when: "CONTAINER_RUNTIME == 'containerd'" }
这个阶段的剧本比较复杂,要在每一个节点上安装docker,在执行这一步之前,先检查一下模版二进制文件的docker版本是否和我们之前预设的一样。1
2/etc/ansible/bin/docker -v
Docker version 18.09.6, build 481bc77
如果不一样,可以去/opt/kube/bin/
目录下找一下之前下好的二进制文件,确认下版本。1
/bin/docker -v /kube/
这个目录下的版本一般是不会错的,将此目录下的二进制文件复制至模版目录/etc/ansible/bin/
。1
cp /opt/kube/bin/* /etc/ansible/bin/
执行ansible剧本1
ansible-playbook 03.docker.yml
这个剧本是对两个master节点操作,执行了以下操作
1.创建kubernetes签名请求以及证书和私钥
2.导入配置文件,启动kube-apiserver、kube-controller-manager及kube-scheduler服务
3.设置主节点的kube-apiserver.service文件,并设置apiserver的IP地址并创建配置用户rbac权限。
此时使用命令kubectl get node
可以看到两个主节点都是出于ready状态了。1
2
3
4root@DockerUbuntu18:~# kubectl get node
NAME STATUS ROLES AGE VERSION
172.18.32.18 Ready,SchedulingDisabled master 1m v1.15.5
172.18.32.19 Ready,SchedulingDisabled master 1m v1.15.5
这个剧本主要是将几个node节点加到集群中来。流程比较复杂,大致有以下步骤:
1,创建kube-node目录:/var/lib/kubelet、/var/lib/kube-proxy和/etc/cni/net.d目录
2.导入之前准备好的二进制可执行文件kubectl、kubelet、kube-proxy、bridge、host-local和loopback
3.配置haproxy,监听本地的127.0.0.1:6443端口,代理至两个主节点的6443端口
4.生成node节点的kubelet的配置文件,并分发至各个node节点。kubelet连接主节点的apiserver。
5.node节点连接后,对node节点标记为kubernetes.io/role=node
这步很容易出现kubelet服务无法启动,或者一直处于loaded状态,返回值为255。这是因为node节点的kubelet的认证失败。可以去node节点主机上查看服务kubelet服务状态。1
2systemctl status kubectl
journalctl -u kubelet -e
我遇到几次node节点kubelet无法正常启动的情况,但是换了个全新的node节点就可以加进去了,说明问题不是在主节点上,一般将node节点还原至干净系统都可以解决问题。
还有一次返回值255但报错提示是,找不到/run/systemd/resolve/resolv.conf
文件,于是创建一个1
mkdir /run/systemd/resolve/;echo "nameserver 233.5.5.5" > /run/systemd/resolve/resolv.conf
自己创建一个解析之后问题解决。
顺利的话,使用kubectl get node
可以看到主节点和node节点都处于ready状态。1
2
3
4
5
6root@DockerUbuntu18:/etc/ansible# kubectl get node
NAME STATUS ROLES AGE VERSION
172.18.32.18 Ready,SchedulingDisabled master 2h v1.15.5
172.18.32.19 Ready,SchedulingDisabled master 2h v1.15.5
172.18.32.21 Ready node 2h v1.15.5
172.18.32.22 Ready node 2h v1.15.5
在这个剧本中,主要是配置各个pod之间的网络访问。需要在每个物理节点上都生成一个calico-node
服务的pod,再启动一个calico-kube-controllers
服务的pod来管理他们,(因为默认主节点设置了不可调度,所以这个calico-kube-contronllers一般是在node节点上生成)。因为使用的是pod方式启动,所以这个剧本中,除了配置calicao证书及私钥外,主要是生成了一个calico DaemonSet yaml
文件,路径为/opt/kube/kube-system/calico.yaml
,所以之后如果修改网络配置,可以直接在此路径下修改这个yaml文件,例如如果想要修改镜像地址为私有harbor地址,则1
2
3kubectl delete -f /opt/kube/kube-system/calico.yaml
vim /opt/kube/kube-system/calico.yaml
kubectl apply -f /opt/kube/kube-system/calico.yaml
需要注意的是,如果将yaml文件中拉取镜像的地址改为了私有harbor,则需要在yaml文件中加入Secret资源,使用imagePullSecrets
拉取。
当我们的node节点不需要跨网段时,通常会选择将IPIP模式(ip-in-ip叠加模式)关掉,使用calico的BGP模式,以节约大量主机内部访问时封装的性能损耗。
查看路由,可以看到目前荣期间通信的网络接口为tunl0
。1
2
3
4
5
6
7
8
9
10
11root@DockerUbuntu18:~# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.18.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
172.20.185.64 0.0.0.0 255.255.255.192 U 0 0 0 *
172.20.233.128 172.18.32.19 255.255.255.192 UG 0 0 0 tunl0
172.20.250.128 172.18.32.22 255.255.255.192 UG 0 0 0 tunl0
172.20.250.192 172.18.32.21 255.255.255.192 UG 0 0 0 tunl0
192.168.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth1
于是可以编辑/etc/ansible/roles/calico/defaults/main.yml
或者直接修改/opt/kube/kube-system/calico.yaml
文件,将其中的 CALICO_IPV4POOL_IPIP
修改为off,将name: FELIX_IPINIPMTU
属性注释掉,改为FELIX_IPINIPENABLED
值为false
。1
2
3
4
5- name: CALICO_IPV4POOL_IPIP
value: "off"
- name: FELIX_IPINIPENABLED
value: "false"
然后执行kubectl delete -f /opt/kube/kube-system/calico.yaml
将网络组件calico的pod都先停掉,reboot
重启后,使用命令ifconfig
就会发现,之前使用的tunl0
网卡就不见了。再使用命令kubectl apply -f /opt/kube/kube-system/calico.yaml
,将k8s网络连接起来,使用命令route -n
查看路由信息,就会发现,跨主机通信直接使用eth0网卡了。此时模式从IPIP修改为BGP模式。1
2
3
4
5
6
7
8
9
10
11root@DockerUbuntu18:~# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.18.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
172.20.185.64 0.0.0.0 255.255.255.192 U 0 0 0 *
172.20.233.128 172.18.32.19 255.255.255.192 UG 0 0 0 eth0
172.20.250.128 172.18.32.22 255.255.255.192 UG 0 0 0 eth0
172.20.250.192 172.18.32.21 255.255.255.192 UG 0 0 0 eth0
192.168.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth1
第七步是安装一些功能插件,如在node节点生成dns解析(默认使用的是coredns,可以在roles/cluster-addon/defaults/main.yml
文件中设置),安装dashboard(可视化web界面)。我们也可以选择自己安装这些功能插件。
可参考官方文档https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#deploying-the-dashboard-ui
dashboard默认是1.6.3版本的,比较老。我们这里手动安装dashboard1.10.1版本,过程如下1
2
3
4cd /etc/ansible/manifests/dashboard
mkdir 1.10.1
cp 1.6.3/ui* 1.10.1/
cd 1.10.1
先下载yaml文档1
wget https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
再创建admin tokenvim admin-user-sa-rbac.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kube-system
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kube-system
创建集群角色vim read-user-sa-rbac.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dashboard-read-clusterrole
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- persistentvolumeclaims
- pods
- replicationcontrollers
- replicationcontrollers/scale
- serviceaccounts
- services
- nodes
- persistentvolumeclaims
- persistentvolumes
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- bindings
- events
- limitranges
- namespaces/status
- pods/log
- pods/status
- replicationcontrollers/status
- resourcequotas
- resourcequotas/status
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- daemonsets
- deployments
- deployments/scale
- replicasets
- replicasets/scale
- statefulsets
verbs:
- get
- list
- watch
- apiGroups:
- autoscaling
resources:
- horizontalpodautoscalers
verbs:
- get
- list
- watch
- apiGroups:
- batch
resources:
- cronjobs
- jobs
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- daemonsets
- deployments
- deployments/scale
- ingresses
- networkpolicies
- replicasets
- replicasets/scale
- replicationcontrollers/scale
verbs:
- get
- list
- watch
- apiGroups:
- policy
resources:
- poddisruptionbudgets
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- networkpolicies
verbs:
- get
- list
- watch
- apiGroups:
- storage.k8s.io
resources:
- storageclasses
- volumeattachments
verbs:
- get
- list
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterrolebindings
- clusterroles
- roles
- rolebindings
verbs:
- get
- list
- watch
此时目录结构如下1
2
3
4
5
6
7
8
9root@DockerUbuntu18:/etc/ansible/manifests/dashboard/1.10.1# tree
.
├── admin-user-sa-rbac.yaml
├── kubernetes-dashboard.yaml
├── read-user-sa-rbac.yaml
├── ui-admin-rbac.yaml
└── ui-read-rbac.yaml
0 directories, 5 files
然后通过yaml文件启动dashboard的pod1
kubectl apply -f .
可以通过命令查看pod 是否启动成功。1
kubectl get pods --all-namespaces | grep dashboard
看到状态running之后,输入命令开启认证生成登陆用户名密码1
easzctl basic-auth -s
1 | [basic-auth for apiserver is enabled! ] |
通过命令kubectl cluster-info
查看集群信息来查看登陆url1
kubectl cluster-info
使用命令来获取token1
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
这时就可以登陆web界面查看K8s集群信息了。
]]> 本文将介绍通过kubeadm部署K8s集群的详细过程,且通过两个mater节点实现K8s集群的高可用。
本次演示使用 k8s 官方提供的部署工具 kubeadm 自动安装, 需要在 master 和 node 节点上安装 docker 等组件, 然后初始化, 把管理端的控制服务和 node 上的服务都以pod 的方式运行。
架构结构示意图如下路所示
环境搭建(master及node节点均为ubuntu1804):
master1:192.168.32.18
master2:192.168.32.19
harbor:192.168.32.20
node1:192.168.32.21
node2:192.168.32.22
需要禁用 swap, selinux, iptables。1
swapoff -a
可以通过apt快速安装或者源码编译,下面以apt包管理工具安装为例1
2
3apt update
apt install keepalived -y
cp /usr/share/doc/keepalived/samples/keepalived.conf.vrrp /etc/keepalived/keepalived.conf
然后修改配置文件,实例如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30! Configuration File for keepalived
global_defs {
notification_email {
acassen
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 192.168.200.1
smtp_connect_timeout 30
router_id LVS_DEVEL
}
vrrp_instance VI_1 {
state MASTER
interface eth0
garp_master_delay 10
smtp_alert
virtual_router_id 32
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
# optional label. should be of the form "realdev:sometext" for
# compatibility with ifconfig.
172.18.32.250 label eth0:1
}
}
另一个节点也安装keepalived,然后测试VIP是否可以漂移成功。
在主节点上先安装docker,详细可参考之前文章。
可以通过阿里云镜像,使用脚本来安装vim docker1806.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# step 1: 安装必要的一些系统工具
apt-get update
apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# step 2: 安装GPG证书
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# Step 3: 写入软件源信息
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# Step 4: 更新并安装Docker-CE
apt-get -y update
apt-get -y install docker-ce=18.06.0~ce~3-0~ubuntu
# 安装指定版本的Docker-CE:
# Step 1: 查找Docker-CE的版本:
# apt-cache madison docker-ce
# docker-ce | 17.03.1~ce-0~ubuntu-xenial | https://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages
# docker-ce | 17.03.0~ce-0~ubuntu-xenial | https://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages
# Step 2: 安装指定版本的Docker-CE: (VERSION例如上面的17.03.1~ce-0~ubuntu-xenial)
# sudo apt-get -y install docker-ce=[VERSION]
1 | bash docker1806.sh |
1 | vim docker/daemon.json |
先配置k8s的镜像源,并安装kubeadm1
2
3
4
5
6
7
8
9curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat >/etc/apt/sources.list.d/kubernetes.list <<EOF
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
apt-get update
apt install kubeadm=1.16.1-00 kubectl=1.16.1-00 kubelet=1.16.1-00
systemctl start kubelet && systemctl enable kubelet
因为默认使用的是google的镜像仓库,国内是连接不上的,所以我们最好提前下载好镜像。本次演示安装版本为kubernetes v1.16.1
先查看需要下载的镜像及版本kubeadm config images list --kubernetes-version v1.16.1
1
2
3
4
5
6
7k8s.gcr.io/kube-apiserver:v1.16.1
k8s.gcr.io/kube-controller-manager:v1.16.1
k8s.gcr.io/kube-scheduler:v1.16.1
k8s.gcr.io/kube-proxy:v1.16.1
k8s.gcr.io/pause:3.1
k8s.gcr.io/etcd:3.3.15-0
k8s.gcr.io/coredns:1.6.2
我们先去阿里云镜像仓库提前下载好镜像,可以通过快速实现.。如果有harbor服务器,可以先上传到本地harbor。1
2
3
4
5
6
7
8
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.16.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.16.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.16.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.16.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.3.15-0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.6.2
因为我们打算做master的高可用。所以我们在master初始化时,要加选项--control-plane-endpoint=172.18.32.250
指定VIP`。只需在一个master节点上做初始化即可。
1
2
3
4
5
6
7
8
9
10kubeadm init \
--apiserver-advertise-address=172.18.32.18 \
--control-plane-endpoint=172.18.32.250 \
--apiserver-bind-port=6443 \
--kubernetes-version=v1.16.1 \
--pod-network-cidr=10.10.0.0/16 \
--service-cidr=10.20.0.0/16 \
--service-dns-domain=k8s.local \
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers \
--ignore-preflight-errors=swap
也可以基于yaml文件而不是用命令行命令来进行初始化。可以使用命令kubeadm init --config kubeadm-init.yaml
,基于文件初始化。
kubeadm config print init-defaults
输出默认初始化配置
kubeadm config print init-defaults > kubeadm-init.yaml
将默认配置输出至文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42root@k8s-master1:~# cat kubeadm-init.yaml #修改后的初始化文件内容
apiVersion: kubeadm.k8s.io/v1beta2
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 172.18.32.18
bindPort: 6443
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: k8s-master1.k8s.local
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta2
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: 172.18.32.250:6443 #添加基于 VIP 的 Endpoint
controllerManager: {}
dns:
type: CoreDNS
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: v1.16.1
networking:
dnsDomain: k8s.local
podSubnet: 10.10.0.0/16
serviceSubnet: 10.20.0.0/16
scheduler: {}
初始化成功,记录下来--token
和--discovery-token-ca-cert-hash
,,之后加入其他节点时需要用到。
如果初始化失败了需要 使用命令kubeadm reset可以清除已有容器数据以便重新安装,PS:此命令如果在安装完成后使用会清除已创建的k8s集群。
1 | mkdir -p $HOME/.kube |
1 | wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml |
其他三台k8s节点也要安装docker及k8s,可以通过脚本快速实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43vim node.sh
#!/bin/bash
# step 1: 安装必要的一些系统工具
apt-get update
apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# step 2: 安装GPG证书
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# Step 3: 写入软件源信息
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# Step 4: 更新并安装Docker-CE
apt-get -y update
apt-get -y install docker-ce=18.06.0~ce~3-0~ubuntu
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://360k4x9i.mirror.aliyuncs.com","https://registry.docker-cn.com"],
"insecure-registries": ["https://harbor.local.com"],
"bip": "10.20.0.1/24"
}
EOF
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat >/etc/apt/sources.list.d/kubernetes.list <<EOF
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
apt-get update
apt install kubeadm=1.16.1-00 kubectl=1.16.1-00 kubelet=1.16.1-00 -y
systemctl start kubelet && systemctl enable kubelet
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.16.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.16.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.16.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.16.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.3.15-0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.6.2
systemctl enable --now docker kubelet
当前 maste 生成证书用于添加新控制节点:1
kubeadm init phase upload-certs --upload-certs
得到--certificate-key
的值,也要记录下来。
之后想加入哪个节点,就在哪个节点上操作。先加入另一个master节点。1
2
3
4kubeadm join 172.18.32.250:6443 --token 89beqy.13jxavbu7yz3187d \
--discovery-token-ca-cert-hash sha256:7388af4f1662805a844cce7c1371facb83f32dddb998370d11bfb41957fe75bf \
--certificate-key 3630d5719795c77e7071d77a206cc17078c912f9c3915e76e70bb26e75e26178 \
--control-plane
再加入各个node节点,命令区别是少了--control-plane
选项以及控制秘钥。1
2kubeadm join 172.18.32.250:6443 --token 89beqy.13jxavbu7yz3187d \
--discovery-token-ca-cert-hash sha256:7388af4f1662805a844cce7c1371facb83f32dddb998370d11bfb41957fe75bf
之后通过命令,就可以看到4个主机都处于ready
状态了。至此k8s集群的搭建就完成了。1
kubectl get node
比较常见的docker镜像仓库,有docker官方仓库https://hub.docker.com/,和阿里云镜像仓库https://cr.console.aliyun.com/cn-hangzhou/instances/images
,可以比较方便的拉取镜像或储存容器镜像。而在企业生产中,绝对部分情况我们都是使用企业内部的镜像仓库,来分发部署我们的代码。本文将详细介绍阿里云仓库还有私有云仓库Registry、Harbor的搭建和使用的详细步骤方法。
docker官方仓库配置比较简单,而且大部分是默认配置,且速度不如阿里云镜像仓库速度快,所以我这里就不介绍了,使用方式和阿里云容器镜像仓库差不多类似。
使用阿里云仓库服务首先要注册阿里云账号,支付宝也可以登陆,比较快捷。点击上面的网址登陆即可。
先创建一个命名空间,这相当于每个人独立的url,可以以代码类别或者性质命名创建(也可以凭个人喜好),每个账号只能创建5个命名空间,不过也够用了。
然后创建镜像仓库。
地域选择离自己比较近的地域,这样延迟会稍微低一些,选择已创建的命名空间,仓库名的命名一般是服务名或者软件名。公开或者私有看个人请款选择。
有了仓库之后,我们就可以上传镜像了。
要上传镜像,第一步,要先重新打标签,将阿里云的仓库源的地址,仓库名,以及版本号重新打标签,生成新镜像。例如对已有的haproxy镜像重新打标签,因为我选择的是北京节点,所以打标命令如下:1
docker tag haproxy-base:v1 registry.cn-beijing.aliyuncs.com/【命名空间名称】/【仓库名】:【版本号】
想上传或者下载镜像一般都需要授权才可以,这就要求我们要用有权限的帐号登陆,才可以上传镜像或者下载镜像。1
2
3
4
5
6
7
8root@DockerUbuntu:/opt/dockerfile/web/haproxy/2.0.5# docker login --username=【账号名】 registry.cn-beijing.aliyuncs.com
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
root@DockerUbuntu:/opt/dockerfile/web/haproxy/2.0.5#
用docker images
可以查看所有的镜像,选择已经打了阿里云网址的标签的镜像上传就可以了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16root/opt/dockerfile/web/haproxy/2.0.5# docker push registry.cn-beijing.aliyuncs.com/【命名空间名称】/【仓库名】:【版本号】 :
The push refers to repository [registry.cn-beijing.aliyuncs.com/命名空间名/仓库名]
0ec88a00d427: Pushed
6a6e6f03a1a5: Pushed
965bdb9c5299: Pushed
a1d450e33837: Pushed
837dac687863: Pushed
b39d6a9ec3e2: Pushed
1e7fbf47b8df: Pushed
dc298319f184: Pushed
e4809dffd3aa: Pushed
b3cdf76b6336: Pushed
2fc5c4732662: Pushed
2d03b9db6c3f: Pushed
89169d87dbe2: Pushed
haproxy: digest: sha256:f0b4157cd18498e4bc373e333e4fb0b85a65d00e03de2d65015b0d0da9099af6 size: 3049
这时就上传成功了
其他主机登陆成功之后,通过拉取命令就可以从阿里云端下载镜像了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18[root@DockerCentOS ~]# docker pull registry.cn-beijing.aliyuncs.com/【命名空间名称】/【仓库名】:【版本号】
haproxy: Pulling from xxxxxxxxxx/web
ac9208207ada: Already exists
75c124fe932b: Pull complete
9ef7eb04bb69: Pull complete
c5f97c472240: Pull complete
9dc49af65399: Pull complete
a745615abdba: Pull complete
ddcf37c0f462: Pull complete
0c406d186167: Pull complete
246fafa1cb32: Pull complete
057e62247ad8: Pull complete
770d7edff222: Pull complete
b4064e1ed3ec: Pull complete
d0a103ae1f19: Pull complete
Digest: sha256:f0b4157cd18498e4bc373e333e4fb0b85a65d00e03de2d65015b0d0da9099af6
Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/xxxxxxxxxx/web:haproxy
registry.cn-beijing.aliyuncs.com/xxxxxxxxxx/web:haproxy
这时就可以在docker images
的镜像列表中看到刚刚拉取的镜像了。
阿里云镜像仓库虽然很方便,但是在生产环境中,每次都从云端拉取或者上传至云端仓库,太消耗企业带宽,有时候数据繁忙的时候,很有可能会堵塞业务,而且速度也较慢。所以企业中都会基于内部局域网搭建企业内部使用的私有仓库。一般搭建私有仓库有两种解决方案,一个是docker自带的Docker Registry,还有就是由vmware公司开源的harbor。
Docker Registry 作为 Docker 的核心组件之一负责镜像内容的存储与分发, 客户端的 docker pull 以及 push 命令都将直接与 registry 进行交互,最初版本的 registry由Python实现,由于设计初期在安全性, 性能以及API的设计上有着诸多的缺陷,该版本在 0.9 之后停止了开发,由新的项目 distribution(新的 docker register 被称为 Distribution)来重新设计并开发下一代 registry,新的项目由 go 语言开发,所有的 API, 底层存储方式, 系统架构都进行了全面的重新设计已解决上一代registry 中存在的问题, 2016 年 4 月份 rgistry 2.0 正式发布, docker 1.6 版本开始支持 registry 2.0,而八月份随着 docker 1.8 发布, docker hub 正式启用 2.1 版本registry 全面替代之前版本 registry,新版 registry 对镜像存储格式进行了重新设计并和旧版不兼容, docker 1.5 和之前的版本无法读取 2.0 的镜像, 另外, Registry2.4 版本之后支持了回收站机制,也就是可以删除镜像了,在 2.4 版本之前是无法支持删除镜像的,所以如果你要使用最好是大于 Registry 2.4 版本的。
Docker Registry的优势就是比较小(25M),但是功能表比较简单。
1 | [root@DockerCentOS ~]# docker pull registry |
先创建授权使用目录1
mkdir -p /docker/auth
创建一个用户并创建密码文件1
2cd /docker
docker run --entrypoint htpasswd registry -Bbn Mice 123456 > auth/htpasswd #创建一个用户并生成密码
验证用户名密码1
2[root@DockerCentOS docker]
Mice:$2y$05$XqNS4BH3gkxodR9MQyhnIuL19uT4wfa6MjUgXvJUYuo0T0o0J8Tzy
从registry镜像中启动 docker registry,指定容器名称为registry1
,挂载本地/docker/auth目录至容器的/auth目录,传递账号密码变量至容器。1
2
3
4
5
6
7docker run -d -p 5000:5000 --restart=always \
--name registry1 \
-v /docker/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd\
registry
此时如果用我们创建的用户名密码尝试登陆了,记得IP或者域名后面要加5000端口,否则会报502错误。1
2
3
4root@DockerUbuntu19:~# docker login 192.168.32.20
Username: Mice
Password:
Error response from daemon: login attempt to http://192.168.32.20/v2/ failed with status: 502 Bad Gateway
不过很有可能当你加上5000端口,可能还会有报错。ヾ(≧O≦)〃嗷~1
2
3
4root@DockerUbuntu19:~# docker login 192.168.32.20:5000
Username: Mice
Password:
Error response from daemon: Get https://192.168.32.20:5000/v2/: http: server gave HTTP response to HTTPS client
这是因为我们每一个docker主机要设置允许insecure-registries
,加上我们registry仓库的IP或者域名。
同样,可以修改/lib/systemd/system/docker.service
启动脚本文件,或者/etc/docker/daemon.json
文件,推荐修改/etc/docker/daemon.json
文件。1
2
3
4
5{
"registry-mirrors": ["https://360k4x9i.mirror.aliyuncs.com"],
"insecure-registries": ["192.168.32.19","DockerCentOS20:5000"],
"bip": "10.20.0.1/24"
}
然后重启docker服务,此时再尝试登陆,就回提示登陆成功。1
2
3
4
5
6root@DockerUbuntu19:~# docker login DockerCentOS20:5000
Username: Mice
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
之后就与阿里云的镜像仓库使用方法相同了,不过速度上会快很多(毕竟内网),可以如果要上传镜像至regist仓库,先打好标签就可以了,下载写明下载仓库源,也就可以正常下载了。ヾ(=゚・゚=)ノ喵♪
Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器, 由vmware开源,其通过添加一些企业必需的功能特性,例如安全、标识和管理等, 扩展了开源 Docker Distribution。作为一个企业级私有Registry服务器,Harbor 提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。Harbor支持安装在多个Registry节点的镜像资源复制, 镜像全部保存在私有Registry中,确保数据和知识产权在公司内部网络中管控, 另外,Harbor也提供了高级的安全特性,诸如用户管理,访问控制和活动审计等 ,所以企业生产中,我们更多会选择Harbor。
下载地址: https://github.com/vmware/harbor/releases
安装文档:https://github.com/vmware/harbor/blob/master/docs/installation_guide.md
本次以harbor的1.75版本为例,演示harbor的安装过程。
下载离线包,并解压。1
2
3cd /usr/local/src
wget https://storage.googleapis.com/harbor-releases/release-1.7 /harbor-offline-installer-v1 .tgz
tar xvf harbor-offline-installer-v1 .tgz
也可以下载在线安装包,但里面没有镜像,之后还要去拉取镜像,不适合生产环境。
Harbor是需要使用docker编排工具docker-compose安装,docker-compose我们之后会专门介绍。而且对docker-compose版本是有要求的,可以查看上面的官方文档链接查看确切版本。
而如果使用包管理工具yum或者apt直接安装的docker-compose可能版本会比较低,我们这里采用python包管理工具python-pip
来安装docker-compose
。1
2apt install python-pip -y
pip install docker-compose
然后可以通过命令docker compose -v
看到docker-compose版本已经是最新稳定版1.25版本了。1
2root@DockerUbuntu19:/usr/local/src# docker-compose -v
docker-compose version 1.25.0, build b42d419
我们习惯于将源码包放在/usr/local/src
下,而将主程序放在/usr/local/
目录中,所以我们可以将harbor目录的的路径改为/usr/local/harbor
,可以通过mv
命令,也可以通过软链接方式实现1
ln -sv /usr/local/src/harbor /usr/local/
然后修改配置文件harbor.cfg
1
2cd /usr/local/harbor
vim harbor.cfg
修改其中主机名(改为IP或者域名),管理员登录密码,及邮箱即可。1
2
3
4
5
6
7
8hostname = 192.168.32.19
email_identity = harbor
email_server = smtp.163.com
email_server_port = 25
email_username = XXXXXXXXX@163.com
email_password = XXXXXXXXXX
email_from = admin <XXXXXXXX@163.com>
harbor_admin_password = XXXXXXXXXXXXX
然后更新配置文件中的环境变量到安装文件中,忘记更新环境变量会提示找不到环境变量文件ERROR: Couldn't find env file: /usr/local/src/harbor/common/config/core/env
。1
./prepare
此时就可以执行命令,来创建并安装Harbor了。1
docker-compose up -d
或者执行官方脚本(两个都可以)1
./install.sh
这时候就可以通过浏览器访问我们刚刚搭建的harbor仓库了,至此企业私有仓库就算是搭建好了。
管理员用户名为admin,密码为我们之前在配置文件中修改的harbor_admin_password
。
后期如果需要修改配置文件信息,需要先停止harbor,然后修改信息后,更新配置文件信息至harbor服务,之后再试用docker-compose up -d
启动harbor服务即可,流程如下。1
2
3
4
5cd /usr/local/harbor
docker-compose stop
vim harbor.cfg
./prepare
docker-compose up -d
推送流程与使用其他云镜像仓库相同,先打标签,开头加上ip/仓库名,然后直接推送即可。
]]> 跨主机互联是说 A 宿主机的容器可以访问 B 主机上的容器,但是前提是保证各宿主机之间的网络是可以相互通信的, 然后各容器才可以通过宿主机访问到对方的容器, 实现原理是在宿主机做一个网络路由就可以实现 A 宿主机的容器访问 B主机的容器的目的, 复杂的网络或者大型的网络可以使用 google 开源的 k8s 进行互联。本文之后将详细介绍docker网络配置,并演示容器跨主机通信的实现。
之前我们说过,当我们安装完docker应用后,就会自动添加一块虚拟的docker0网卡,并基于docker0网卡,提供了3种可选网络类型供创建的容器使用,分别是bridge(桥接),host(主机),none(无外部网络)。其中默认是采用桥接模式,容器中的网卡桥接在docker的网桥上,且通过DHCP自动分配IP,与docker0在同一网段。
当我们每创建一个容器,宿主机上就会新建一个网卡与容器中的网卡相对应,如下图所示。
容器桥接模式跨网络访问的结构示意图如下图所示
想实现不同宿主机上的容器跨主机肯定要经过宿主机来做网络转发,通过设置宿主机静态路由或者修改iptables规则来实现,可这时就面临一个问题:所有的容器服务默认的DHCP网段都是172.17.0.0/16网段,如果node1上的容器想直接访问node2宿主机上的容器,就会被直接当做docker0网桥的内部网段,数据报文根本都不会从node1主机的eth0网卡发出去,也根本到不了node2主机上。这种情况下,无论我们怎么修改iptables规则或者路由规则都无济于事的。所以我们想实现容器跨主机访问,首先要将不同宿主机上的容器分到不同的网段,然后才可以通过路由规则或者iptables进行跳转或转发。
我们可以对每一个宿主机上的docker配置文件进行修改,实现每个宿主机的docker容器都在不同网段的目的,可以通过以下方式修改(未避免影响, 先在各服务器删除之前创建的所有容器,docker rm -f `docker ps -a -q`
)。
docker.service
1 | vim /lib/systemd/system/docker.service |
在ExecStart=
选项结尾加上--bip=10.1.0.1/24
,就指定了10.1.0.0/24网段,然后执行命令重新加载配置文件和重启服务。1
2systemctl daemon-reload
systemctl restart docker
注意:不能写10.1.0.0/24,会报错
,虽然写网段结尾是0,如10.1.0.0/24更符合我们的习惯,不过确实会报错,报错信息如下:1
2
3
4
5
6Dec 07 16:27:58 DockerUbuntu dockerd[14794]: failed to start daemon: Error initializing network controller: Error creating default "bridge" network: failed to allocate gateway (10.10.0.0): Address already in use
Dec 07 16:27:58 DockerUbuntu systemd[1]: docker.service: Main process exited, code=exited, status=1/FAILURE
Dec 07 16:27:58 DockerUbuntu systemd[1]: docker.service: Failed with result 'exit-code'.
Dec 07 16:27:58 DockerUbuntu systemd[1]: Failed to start Docker Application Container Engine.
-- Subject: Unit docker.service has failed
-- Defined-By: systemd
"bip": "10.2.0.1/24"
,如下所示(上面那个是我的阿里云加速器链接,注册阿里账号免费获取,之前文章有详细介绍,需改成自己的或者删掉):1 | vim /etc/docker/daemon.json |
daemon.json文件是json数据格式,需要遵守json语法,换行记得要加,
逗号。
直接重启docker服务后生效1
systemctl restart docker
此时看网卡的ip就已经变为了我们设置的网段,之后创建的容器服务器就会自动获取我们设置好的网段中的IP了。1
2
3
4
5
6
7
8
9root@DockerUbuntu:~# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 10.10.0.1 netmask 255.255.255.0 broadcast 10.10.0.255
inet6 fe80::42:25ff:fe2b:ecbc prefixlen 64 scopeid 0x20<link>
ether 02:42:25:2b:ec:bc txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 41 bytes 3526 (3.5 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
同样的操作,将node2宿主机中的docker0网段设置为10.2.0.0/24
我这里node1的eth0网卡ip为192.168.32.19,node2的eth0网卡ip为192.168.32.20,且两个宿主机之前网络是可以通过eth0网卡相互连接的。添加路由规则如下:
在node1上添加静态路由1
ip r add 10.2.0.0/24 via 192.168.32.20 dev eth0
在node2上添加静态路由1
ip r add 10.1.0.0/24 via 192.168.32.19 dev eth0
宿主机如果为centos7,则不需要修改iptables规则,而宿主机如果为ubuntu系统则需要添加forward规则来放行。我仔细看了下这两个系统的iptables规则,发现在centos系统docker创建的iptables规则中对Chain FORWARD
是默认ACCEPT
,而ubuntu系统中docker创建的iptables对Chain FORWARD
是默认DROP
之后两边各启动一个容器就可以实现相互通信或访问了。
之前我们演示了通过docker0网卡的桥接方式实现了容器跨主机访问。我们通过修改docker0网卡的网段设置来实现,每个主机上容器的网段不同。
这种实现方式有个问题就是,但当每次我们修改了docker0网段之后,如果之后打算变更网段,之前的容器都将无法与docker0网桥桥接,导致网络不通,不能使用。
对此我们有一个更灵活的方案来实现容器的跨主机通信。那就是我们还可以通过创建一个或多个自定义网络,将新创建的每个容器指定连接到我们创建的这个网络中,这样他们的网段就是我们设置的这个网络的网段,实现每个主机上容器网段都不相同。
可以将我们之前对docker0网卡的修改还原了的(当然,也可以不修改,出于控制变量方便观察考虑,建议修改回去)。
我们可以通过docker network create
命令来创建一个自定义网络1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24root@DockerUbuntu:~# docker network create --help
Usage:docker network create [OPTIONS] NETWORK
Create a network
Options:
--attachable Enable manual container attachment
--aux-address map Auxiliary IPv4 or IPv6 addresses used by Network driver
(default map[])
--config-from string The network from which copying the configuration
--config-only Create a configuration only network
-d, --driver string Driver to manage the Network (default "bridge")
--gateway strings IPv4 or IPv6 Gateway for the master subnet
--ingress Create swarm routing-mesh network
--internal Restrict external access to the network
--ip-range strings Allocate container ip from a sub-range
--ipam-driver string IP Address Management Driver (default "default")
--ipam-opt map Set IPAM driver specific options (default map[])
--ipv6 Enable IPv6 networking
--label list Set metadata on a network
-o, --opt map Set driver specific options (default map[])
--scope string Control the network's scope
--subnet strings Subnet in CIDR format that represents a network segment
例如我们在node1创建一个名为web1的桥接网络,网段为10.10.0.0/24,设置网关为10.10.0.1(可设置为此网段内任意ip)。1
2
3
4
5
6
7
8
9root@DockerUbuntu:~# docker network create -d bridge --subnet 10.10.0.0/24 --gateway 10.10.0.1 web1
a817cf36502eea3469e1cb4b9b7577044f8dce96f015ba57a47f6809c00d72c7
root@DockerUbuntu:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
afd91b2e1731 bridge bridge local
241d3e94a6b3 host host local
7cc9cf9eb69e none null local
a817cf36502e web1 bridge local
root@DockerUbuntu:~#
可以用docker network ls
(或docker network list
)看到网络类型多了一种,也就是我们刚刚创建的web1类型。
而用ifconfig
或者ip a
命令也可以看到我们的网卡设备里多了一个br-a817cf36502e
,ip也恰好是我们指定的10.10.0.0/24
网段。
此时我们就可以通过--net
选项指定我们刚刚创建的web1网络,来创建并启动容器了。1
2
3root@DockerUbuntu:~# docker run -it -d -p 8080:8080 -p 8009:8009 --net=web1 tomcat-app1:v1
afc1e3db8a6a670d30cdd0756af65da74895976ce5ebbf876329b04b452a3710
root@DockerUbuntu:~#
同样,在宿主机node2上也创建一个自定义网络web2,然后新创建的容器,也指定网络为web2,再设置静态路由和修改iptables规则放行,也可以实现容器间跨主机访问。
]]> 安装完Docker的服务,我们就可以开始使用Docker了。
之前我们提到,docker是一个运行容器的工具,可以单独隔离每个服务的运行环境,达到互不干扰和节约资源的目的。而docker运行的容器,是基于一层一层的镜像联合挂载构建而成。所以我们需要先有镜像。
所谓镜像,其实可以理解为,一个个的最简化的安装包,里面只集成了一些必备的程序和文件,且每一层和每一层镜像是可以相互一样的,大大的节约了空间,提高了资源利用率。举个最简单的例子,我们从官方下载一个centos系统的镜像包centos:apline,大小才5.55兆,而centos差不多至少200兆了,而centos安装的ISO镜像文件也都差不多1G左右。这是因为容器使用的镜像系统,需要依赖宿主机内核来运行,容器本身是没有内核的,只包含一些基础功能命令而已。可用docker images
查看本地镜像,从大小来看,就知道容器确实很精简。1
2
3
4
5root@DockerUbuntu:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 965ea09ff2eb 6 weeks ago 5.55MB
centos latest 0f3e07c0138f 2 months ago 220MB
centos 7.6.1810 f1cb7c7d58b7 8 months ago 202MB
我们可以在docker官方镜像仓库https://hub.docker.com查看官方镜像,也可以通过命令直接搜索centos的可用容器1
docker search centos
注意:官方仓库也有很多是个人的镜像,为避免未知风险,一定要采用官方镜像,千万别使用安全性未知或来源不明的的镜像。一般排在第一个是官方镜像。
使用命令docker pull centos
就可以自动从docker官方镜像仓库docker.io,下载centos镜像了,因为我们没有指定版本标签,所以这条命令会默认下载centos:latest
版本,也就是最新版。生产中我们出于稳定性和便于管理,都会推荐使用指定的稳定版本,而不会采用latest版本(,随着版本发型,之前的latest版本和几个月之后的latest版很有可能就不是一个版本,不利于规范化统一)。我们本次以CentOS7.6中的最新版centos:7.6.1810
最为本次演示的版本。下载镜像命令如下:1
docker pull centos:7.6.1810
使用docker image rm [ OPTION ] 容器ID
可以删除不要的镜像,如果已有容器基于此镜像启动,则会提示错误,无法删除,需先停止容器,或直接加-f选项
强制删除镜像,此时容器也会被停止。注意,当容器被停止时,上面的数据也都会丢失,所以需要谨慎关闭容器和删除镜像。
此外,docker官方镜像站,因为在国外(美国,不过应该是有CDN或者国内镜像加速站点的,不过还是有点点慢),肯定不如阿里的容器镜像仓库速度快。所以我们可以配置阿里的镜像加速器,需要注册阿里账号。
进入阿里网站http://cr.console.aliyun.com,登陆之后,可以看到阿里的镜像仓库(之后再细说)还有镜像加速器,点击镜像加速器,可看到如图所示界面:
执行将下面代码,便可实现镜像加速了。1
2
3
4
5
6
7
8sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://xxxxxxxx.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
之后使用docker info
命令就可以看到已经添加镜像仓库成功
有了镜像之后我们就可以从中运行容器了。基础的命令有docker pull
、docker push
、docker create
、docker run
、docker ps
、docker rm
、docker start
、docker stop
、docker images
、docker exec
、docker inspect
等等
1 | root@DockerUbuntu:~# docker pull --help |
docker pull
命令我们之前也使用过,可以用来拉镜像,相当于命令docker image pull
。根据官方帮助信息,我们可以知道,docker pull
如果要拉去指定版本,需要加:tag
版本标签,加-a
选项可以拉取所有镜像,不过生产中一般都是用的时候公司内部的本地镜像仓库Harbor,所以拉取镜像时,格式要跟公司Harbor服务器的IP或者域名,指定拉取镜像的源仓库,如果不加,则默认从docker官网拉取。镜像名称格式为:Harbor IP/项目名/image 名字:版本号
1
docker pull 172.18.32.101/centos/centos-base:v1
docker push
是用来推送本地镜像至仓库的,相当于命令docker image push
,用法格式与pull相似。
使用pull推送至公司Harbor时,需要先在docker启动文件/lib/systemd/system/docker.service
的ExecStart选项
结尾加入参数--insecure-registry
,表示加入不安全的镜像仓库,可加多个,示例如下。1
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --insecure-registry 192.168.32.19 --insecure-registry 192.168.32.20
1 | root@DockerUbuntu:~# docker tag --help |
docker tag
命令可以用来打标记,格式如上。一般为了区分不同版本,我们会在每次制作完镜像后打上不同的标记,而且要分发到不同的镜像仓库时也要重新打一个对应仓库IP/项目名
的镜像,才可以push
或pull
。
docker run
可以算是最常用的docker基础命令了,意思是创建并运行容器,相当于docker create
和docker start
的组合。用法格式如下。1
docker run [ OPTION ] 镜像ID [ CMD ]
常用选项有-i
、-t
、-d
、-p
、-v
、-e
、--name
、--net
。
-i
,–interactive, Keep STDIN open even if not attached-t
参数配合,来给容器加一个伪终端
实现交互式。-t
,–tty, Allocate a pseudo-TTY-i
配合使用,通常写作-it
。-d
,–detach, Run container in background and print container ID-p
,–publish, list Publish a container’s port(s) to the host-p 宿主机端口:容器端口
选项。-v
,–volume, list Bind mount a volume-v SOUCE:DEST:ro
后面加ro
则可实现只读。-e
,–env, list Set environment variables-e
选项为容器添加启动时的环境变量。虽然大多环境变量都是在制作容器时就写进容器里,不过此选项应用在环境变量经常变化的场景,方面修改。--name
,Assign a name to the container--net
docker network list
查看当前已有的网络。1 | root@DockerUbuntu:~# docker network list |
docker服务安装完成后,默认在每个宿主机会生成一个名称为 docker0 的网卡其 IP 地址都是 172.17.0.1/16,并且会生成三种不同类型的网络,也就是bridge、host和null,分别代表网桥(桥接)、宿主机网络和无网络访问。
如果不加--net
选项则默认使用bridge,相当于桥接在宿主机的docker0
网卡上。
host
网络就是指容器使用宿主机网络,所以端口就不能重复使用,多个使用host网络的容器间端口也不能重复。
null
网络指容器只有回环网卡,没有外部网卡,无法与外部交流交流,不常使用,适用于某些不需要与外界通信的数据计算或图形、数据处理服务。
还有一种比较特的类型就是container
模式,就是指定与一个已有容器共享网络,格式为--net=container:指定名称或 ID
。使用此模式创建的容器需指定和一个已经存在的容器共享一个网络,而不是和宿主机共享网,新创建的容器不会创建自己的网卡也不会配置自己的 IP,而是和一个已经存在的被指定的容器东西 IP 和端口范围,因此这个容器的端口不能和被指定的端口冲突, 除了网络之外的文件系统、进程信息等仍然保持相互隔离,两个容器的进程可以通过 lo 网卡及容器 IP 进行通信。
docker run
后面跟的CMD表示指定启动容器的命令,如果不指定,则为容器默认命令。可用 命令docker inspect 容器ID
查看容器详细参数查看容器创建时的的CMD。
因为容器中没有守护进程systemd,所以进程编号PID=1的根进程,就是我们启动容器时指定的CMD命令或创建容器设定的默认CMD。如果这个PID=1的守护进程结束,则整个容器就将被关闭。所以我们一般会指定一个可以占据前台终端的服务来作为守护进程,例如bash
或者tail -f /etc/hosts
命令,对于后台nginx等服务来说,想让他们作为守护进程启动容器,需要将在后台执行的这个选项关闭
,如nginx可以通过docker run -it -d nginx:alpine nginx -g "daemon off;"
命令,加上-g "daemon off"
选项传递参数或者在配置文件中设置daemon off
来关闭后台执行选项,否则容器启动就会因为没有前台进程而终止。为了方便我们修改配置或者重启服务,企业生产中我们都采用tail -f /etc/hosts
来作为前台进程,这样我们重启服务容器就不受影响了。
1 | root@DockerUbuntu:~# docker ps --help |
docker ps
命令比较简单,较长使用的参数是-a
、-q
,分别是显示所有容器(包括为未运行容器,ps默认只显示正在运行的容器)和只显示容器ID。也可以使用docker ps -aq
来显示所有容器的ID,可配合其他命令如docker rm
命令使用。docker rm -f `docker ps -aq`
删除本地所有容器(很危险,小心操作!)。
docker rm
命令是用来删除容器的,如果容器正在运行,使用docker rm
命令删除时会报错提示Error response from daemon: You cannot remove a running container XXX. Stop the container before attempting removal or force remove
,告诉我们无法删除运行中的容器,需要先将容器停止或者强制删除,使用docker rm -f
命令强制删除,或者使用下面的停止命令
docker start 容器ID
启动容器,可以加选项-i
,表示启动容器并输出容器内的标准输出至宿主机终端。需要注意的是,如果当时用docker run
或者docker create
命令创建容器时,没有加-it
或-d
等一些其它选项或者参数的话,在start容器这一步也没法作出更改了的。容器怎么创建的,启动时就会按照创建时设定好的模式运行,这些是没办法再次修改了的,包括启动CMD。
docker stop 容器ID
终止容器。需要注意:终止容器时,容器内的数据都将丢失,在备份重要数据之前不要终止容器。
docker images
命令其实相当于docker image ls
,显示本地所有镜像。docker image CMD
还有很多,之后介绍镜像管理的时候 在详细介绍。
docker exec
命令可以向一个已运行容器发送命令。用法如下所示。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15[root@DockerCentOS ~]# docker exec --help
Usage:docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
Run a command in a running container
Options:
-d, --detach Detached mode: run command in the background
--detach-keys string Override the key sequence for detaching a container
-e, --env list Set environment variables
-i, --interactive Keep STDIN open even if not attached
--privileged Give extended privileges to the command
-t, --tty Allocate a pseudo-TTY
-u, --user string Username or UID (format: <name|uid>[:<group|gid>])
-w, --workdir string Working directory inside the container
最常使用的格式是docker exec -it 容器ID bash/sh
,可以进入正在运行的容器。
在docker使用过程中,其实大部分时间都是花在了打镜像上,因为容器本身底层不可写,顶层可读写缺无法持久化性质,我们如果对容器进行了修改,想要进行横向扩容,快速部署时,一般需要重新制作镜像,在分发到其他主机或终端。(虽然也可以将数据储存在NFS和宿主机本地,而不是容器内部来方便的修改配置文件及保存数据等。)
docker中镜像的制作方式一般手工修改后导出和通过Dockerfile生成两种方式。
因为镜像本身的不可修改性,有时候官方镜像中使用的工具的版本可能不是那么符合我们的生产环境,我们就需要自己制作镜像了。一般来说,我们都是基于官方镜像,作出修改来符合自身实际场景中使用,然后在导出保存为我们自己的镜像。
以一个tomcat容器为例,我们如果需要tomcat8的容器,可以直接从官网拉取tomcat8的镜像docker pull tomcat:8.5.49-jdk8-openjdk
修改完成后,还可以使用命令docker commit
将已有容器制作为镜像.1
2
3
4
5
6
7
8
9
10
11root@DockerUbuntu:/opt/dockerfile/web/tomcat/tomcat-apps/app1# docker commit --help
Usage:docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
Create a new image from a container's changes
Options:
-a, --author string Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
-c, --change list Apply Dockerfile instruction to the created image
-m, --message string Commit message
-p, --pause Pause container during commit (default true)
-a
添加镜像制作人信息,-m
添加备注信息,-p
选项是默认选项,在制作为镜像时暂停容器,-c
使用Dockerfile指令来创建镜像,Dockerfile之后我们会详细讲解。例如1
docker commit -a "example@163.com" -m "tomcat app1 v1" --change="EXPOSE 8080 8009" f5f8c13d0f9f centos-tomcat-app1:v1
不过如果业务场景要求的配置场景要修改nginx的编译参数或者要求底层是centos7系统这就没法更改了(官方镜像一般都是debian系统),我们只能修改最上层的镜像。此时我们可以通过分层构建的方式来制作镜像(,毕竟docker镜像本来就是分层构建的)。
DockerfileDockerFile 可以说是一种可以被 Docker 程序解释的脚本, DockerFile 是由一条条的命令组成的,每条命令对应 linux 下面的一条命令, Docker 程序将这些 DockerFile 指令再翻译成真正的 linux 命令,其有自己的书写方式和支持的命令, Docker 程序读取 DockerFile 并根据指令生成 Docker 镜像,相比手动制作镜像的方式, DockerFile 更能直观的展示镜像是怎么产生的,有了写好的各种各样 DockerFile 文件,当后期某个镜像有额外的需求时,只要在之前的DockerFile 添加或者修改相应的操作即可重新生成新的 Docke 镜像,避免了重复手动制作镜像的麻烦。
Docker中常用到的命令令有FROM
(指定基础镜像名称),MAINTAINER
(镜像作者署名及联系方式),USER
(切换用户身份,初始一般为root),WORKDIR
(指定或切换工作目录),ADD
(将当前宿主机目录的文件拷贝至容器指定位置,tar包可以自动解压),RUN
(运行命令,其实就是shell命令,可执行多条,用&&符号连接),ENV
(设置环境变量),CMD
(设置默认镜像启动命令,要可以占据前台,否则基于此镜像启动的容器会直接停止),之后我们结合实际例子一一说明。
例如我们使用Dockerfile来分层构建定制的tomcat镜像来运行app1服务(当然,也可以一步到位),步骤如下:
构建目录架构
我们通常将Dockerfile文件都放置在/opt/
目录下
1 | mkdir /opt/dockerfile/{web/{nginx,tomcat,jdk},system/{centos,ubuntu,redhat}} -pv |
构建系统镜像
1 | cd /opt/dockerfile/system/centos |
创建Dockerfile文件,注意D要大写1
2
3
4
5
6
7vim Dockerfile
#CentOS 7.6 镜像
FROM centos:7.6.1810
MAINTAINER Mice example@163.com
RUN rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm
RUN yum install -y vim wget tree lrzsz gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel iproute net-tools iotop
CMD ["bash"]
创建制作镜像脚本,来生成镜像。当然,也可以直接使用docker build
命令配合-t
参数(指定标签名)直接将当前目录的Dockerfile制作为镜像,使用脚本是为了日后修改不至于每次名称都不一样,可以保证每次打的镜像的名称和版本号统一,否则以后镜像多了会乱(脚本中也可以在标签中加上时间)。1
2
3vim build_centos.sh
docker build -t centos-base:v7.6.1810 .
当前目录结构为1
2
3
4
5
6root@DockerUbuntu:/opt/dockerfile/system/centos/7.6# tree
.
├── build_centos.sh
└── Dockerfile
0 directories, 2 files
执行命令bash build_centos.sh
来创建第一层镜像centos-base:v7.6.1810
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19root@DockerUbuntu:/opt/dockerfile/system/centos/7.6# bash build_centos.sh
Sending build context to Docker daemon 3.072kB
Step 1/5 : FROM centos:7.6.1810
---> f1cb7c7d58b7
Step 2/5 : MAINTAINER Mice example@163.com
---> Using cache
---> 3899d2446806
Step 3/5 : RUN rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm
---> Using cache
---> 5a72857ed63d
Step 4/5 : RUN yum install -y vim wget tree lrzsz gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel iproute net-tools iotop
---> Using cache
---> 705fed38cb94
Step 5/5 : CMD ["bash"]
---> Running in aea451be0461
Removing intermediate container aea451be0461
---> 160b9544f121
Successfully built 160b9544f121
Successfully tagged centos-base:v7.6.1810
有时到yum那步会提示报错,无法解析IP。多执行几次脚本,多试几次就可以了。他会有缓存自动保存镜像,已经写好的层数会自动缓存的,如上面的---> Using cache
。
1 | cd /opt/dockerfile/web/jdk/ |
将准备好的jdk压缩包jdk-8u212-linux-x64.tar.gz
放入此目录,然后还是编写Dockerfile
以及build_jdk.sh
脚本1
2
3
4
5
6
7
8
9
10
11
12
13
14vim Dockerfile
#JDK 8u212
FROM centos-base:v7.6.1810
MAINTAINER Mice example@163.com
ADD jdk-8u212-linux-x64.tar.gz /usr/local/src/
ADD env.sh /etc/profile.d/
RUN ln -sv /usr/local/src/jdk1.8.0_212 /usr/local/jdk && groupadd www -g 2019 && useradd www -u 2019 -g www
ENV JAVA_HOME /usr/java/default
ENV PATH $JAVA_HOME/bin:$PATH
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/:$JRE_HOME/lib/
RUN rm -rf /etc/localtime && ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
1 | vim build_jdk.sh |
需要注意的是,因为ENV环境变量是当前用户(当前终端)有效,也就是说对于容器本身来说,他运行的环境变量是已经通过ENV可以设置好,可是如果发生故障,我们需要连接进入容器时,这个ENV就对我们当前终端无效了,我们可能就无法使用那些ENV设置了的PATH变量了,所以我们需要添加一个环境变量配置文件
至/etc/profile.d目录
(也可直接替换profile文件),以便这些环境变量在我们连接至容器后也可以生效。1
2
3
4
5vim env.sh
JAVA_HOME=/usr/local/jdk
JRE_HOME=$JAVA_HOME/jre
CLASSPATH=$JAVA_HOME/lib/:$JRE_HOME/lib/
PATH=$PATH:$JAVA_HOME/bin
目录结构如下所示1
2
3
4
5
6
7
8root@DockerUbuntu:/opt/dockerfile/web/jdk/8u212# tree
.
├── build_jdk.sh
├── Dockerfile
├── env.sh
└── jdk-8u212-linux-x64.tar.gz
0 directories, 4 files
还是通过build脚本,构建jdk容器。
1 | cd /opt/dockerfile/web/tomcat/ |
将准备好的tomcat源码包拷到这个目录,或者wget下载1
wget http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.47/bin/apache-tomcat-8.5.47.tar.gz
制作Dockerfile1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16vim Dockerfile
#tomcat 8-jdk 1.8.0_212-centos 7.6
FROM jdk-base:v1.8.0_212
MAINTAINER Mice example@163.com
ENV TZ "Asia/Shanghai"
ENV LANG en_US.UTF-8
ENV TERM xterm
ENV TOMCAT_MAJOR_VERSION 8
ENV TOMCAT_MINOR_VERSION 8.5.47
ENV CATALINA_HOME /apps/tomcat
ENV APP_DIR ${CATALINA_HOME}/webapps
RUN mkdir /apps
ADD apache-tomcat-8.5.47.tar.gz /apps/
RUN ln -sv /apps/apache-tomcat-8.5.47 /apps/tomcat
老规矩,创建build脚本1
2
3vim build_tomcat.sh
docker build -t tomcat-base:v8.5.47 .
然后执行脚本打镜像~
tomcat-base:v8.5.47
,所有底层镜像因为都是只读的,所以可以复用而互不干扰,也不会重复占用多余的空间。1 | mkdir -pv dockerfile tomcat app1/ |
因为当app1有变化时,tomcat服务可能会需要重启才会生效变化,而如果tomcat服务是容器的启动进程(即PID=1的进程)时,重启tomcat会导致容器终止,容器里面在运行的数据及session都会丢失。所以我们使用tail -f
命令来作为这个容器的守护进程来启动容器。我们可以通过构建一个脚本run_tomcat.sh
来实现,启动tomcat并让tail -f 最为前台进程。1
2
3
4vim run_tomcat.sh
su - www -c "/apps/tomcat/bin/catalina.sh start"
su - www -c "tail -f /etc/hosts"
且为了安全考虑,我们打算让tomcat服务以www用户身份启动,所以在构建容器时,需要注意权限问题,Dockerfile如下:1
2
3
4
5
6
7
8vim Dockerfile
# tomcat-apps
FROM tomcat-base:v8.5.47
ADD run_tomcat.sh /apps/tomcat/bin/run_tomcat.sh
ADD app1/* /apps/tomcat/webapps/myapp/
RUN chown www.www /apps/ -R
EXPOSE 8080 8009
CMD ["/apps/tomcat/bin/run_tomcat.sh"]
而且我们把run_tomcat.sh
传进去后,要以脚本启动的话,要对脚本加执行权限,这样传进去的时候也是有执行权限的。1
chmod +x run_tomcat.sh
然后构建build脚本1
2
3vim build_app1.sh
docker build -t tomcat-app1:v1 .
然后将APP1的程序代码拷贝至当前目录,结构示意图如下:1
2
3
4
5
6
7
8
9root@DockerUbuntu:/opt/dockerfile/web/tomcat/myapps/app1# tree
.
├── build_app1.sh
├── Dockerfile
├── app1
│ └── index.html
└── run_tomcat.sh
1 directory, 4 files
就可以执行build脚本打镜像啦。1
bash build_app1.sh
需要注意的是,启动镜像时记得加端口映射
,命令如下1
docker run -it -d -p 8080:8080 -p 8009:8009 tomcat-app1:v1
此时通过ss -tanl
命令就可以看到8080、80009端口已经被docker proxy监听了。说明服务启动成功。
如果宿主机为centos,或者redhat(ubuntu默认是开启的),可能会需要先打开内核参数ip_forward选项,否则会报错网络不可用:1
WARNING: IPv4 forwarding is disabled. Networking will not work.
那就开启IP转发。1
2vim /etc/sysctl.conf
net.ipv4.ip_forward=1
然后sysctl -p
生效就可以正常使用容器网络了。
需要注意的是,RUN
命令是类似启动一个新的进程或者是shell,来执行每一次命令,执行完毕后此次RUN
进程结束,下一次是一个全新的RUN
进程了,相互之间不会联系。这么说可能大家无法理解,举个最简单的例子吧,就是当我们想要编译安装haproxy的时候,需要进入编译目录然后执行make
命令,编译完后还要在编译目录执行make install
命令。所以可以写成1
RUN cd /usr/localk/haproxy-2.0.5 && make --xxx参数选项省略xxxx && make install
但是如果我们像写脚本那样,写成1
2
3RUN cd /usr/localk/haproxy-2.0.5
RUN make --xxx参数选项省略xxxx
RUN make install
或者1
2RUN cd /usr/localk/haproxy-2.0.5 && make --xxx参数选项省略xxxx
RUN make install
就会报错找不到路径,因为没一条命令都是一层独立的镜像,每层镜像开始都会在默认的初始目录,所以如果打算将make
和make install
分开写,则两次都加cd /usr/local/haproxy-2.0.5
,或者直接切换工作目录,如下所示。1
2
3
4
5
6
7
8
9
10
11WORKDIR /usr/local/src/haproxy-2.0.5
RUN make ARCH=x86_64 \
TARGET=linux-glibc USE_PCRE=1 \
USE_OPENSSL=1 \
USE_ZLIB=1 \
USE_CPU_AFFINITY=1 \
USE_LUA=1 \
LUA_INC=../lua-5.3.5/src/ \
LUA_LIB=../lua-5.3.5/src/ \
PREFIX=/apps/haproxy
RUN make install PREFIX=/apps/haproxy
WORKDIR
和USER
很类似,都是修改后,对后续Dockerfile指令有效。且WORKDIR
还支持相对路径,例如1
2
3WORKDIR /a
WORKDIR b
WORKDIR c
则最终的工作目录为/a/b/c
。
在企业生产应用中,docker容器技术及k8s的编排管理工具的使用率越来越高,这项技术甚至已经改变了很多企业的架构与框架流程,因为容器技术的出现,可以将应用以集装箱的方式打包交付,使应用在不同的团队中共享,通过镜像的方式应用可以部署于任何环境中。这样避免了各团队之间的协作问题的出现,成为企业实现DevOps目标的重要工具,而且以容器方式交付的Docker技术支持不断地开发迭代,提升了产品开发和交付速度,极大的方便了业务的横向扩容。
且与KVM虚拟化技术不同的是,Docker直接移植于Linux内核之上,通过运行Linux进程将底层设备虚拟隔离,这样系统性能的损耗也要比虚拟机低的多,几乎可以忽略。同时,Docker应用容器的启停非常高效,可以支持大规模的分布系统的水平扩展,真正给企业开发带来福音。
首先 Docker 是一个在 2013 年开源的应用程序并且是一个基于 go 语言编写是一个开源的 PAAS 服务(Platform as a Service, 平台即服务的缩写), go 语言是由google 开发, docker 公司最早叫 dotCloud 后由于 Docker 开源后大受欢迎就将公司改名为 Docker Inc, 总部位于美国加州的旧金山, Docker 是基于 linux 内核实现, Docker 最早采用 LXC 技术(LinuX Container 的简写, LXC 是 Linux 原生支持的容器技术, 可以提供轻量级的虚拟化, 可以说 docker 就是基于 LXC 发展起来的,提供 LXC 的高级封装,发展标准的配置方法), 而虚拟化技术 KVM(Kernelbased Virtual Machine) 基于模块实现, Docker 后改为自己研发并开源的 runc 技术运行容器。
Docker 相比虚拟机的交付速度更快, 资源消耗更低, Docker 采用客户端/服务端架构,使用远程 API 来管理和创建 Docker 容器,其可以轻松的创建一个轻量级的、 可移植的、自给自足的容器, docker 的三大理念是 build(构建)、ship(运输)、 run(运行), Docker 遵从 apache 2.0 协议,并通过(namespace 及cgroup 等)来提供容器的资源隔离与安全保障等,所以 Docke 容器在运行时不需要类似虚拟机(空运行的虚拟机占用物理机 6-8%性能)的额外资源开销,因此可以大幅提高资源利用率,总而言之 Docker 是一种用了新颖方式实现的轻量级虚拟机.类似于 VM 但是在原理和应用上和 VM 的差别还是很大的,并且 docker的专业叫法是应用容器(Application Container)。
| 优势 | Docker | 虚拟机 |
|–|–|–|
| 资源利用率更高 | 一台物理机可以运行数百个容器 | 但是一般只能运行数十个虚拟机 |
| 开销更小 | 不需要启动单独的虚拟机占用硬件资源 | 需要启动单独的虚拟机占用硬件资源 |
| 启动速度更快 | 可以在数秒内完成启动 | 需要几十秒甚至数分钟 |
使用虚拟机是为了更好的实现服务运行环境隔离, 每个虚拟机都有独立的内核,虚拟化可以实现不同操作系统的虚拟机,但是通常一个虚拟机只运行一个服务, 很明显资源利用率比较低且造成不必要的性能损耗, 我们创建虚拟机的目的是为了运行应用程序,比如 Nginx、 PHP、 Tomcat 等 web 程序, 使用虚拟机无疑带来了一些不必要的资源开销,但是容器技术则基于减少中间运行环节带来较大的性能提升。
详细介绍参见官方文档https://docs.docker.com/engine/docker-overview/
容器技术确实有很多优点,不过当使用多个容器时带来的以下问题怎么解决:
1.怎么样保证每个容器都有不同的文件系统并且能互不影响?
2.一个 docker 主进程内的各个容器都是其子进程,那么实现同一个主进程下不同类型的子进程? 各个进程间通信能相互访问(内存数据)吗?
3.每个容器怎么解决 IP 及端口分配的问题?
4.多个容器的主机名能一样吗?
5.每个容器都要不要有 root 用户?怎么解决账户重名问题?
这就不得不引入一个Namespace的概念了。
namespace 是 Linux 系统的底层概念, 在内核层实现,即有一些不同类型的命名空间被部署在核内, 各个 docker 容器运行在同一个 docker 主进程并且共用同一个宿主机系统内核,各 docker 容器运行在宿主机的用户空间, 每个容器都要有类似于虚拟机一样的相互隔离的运行空间, 但是容器技术是在一个进程内实现运行指定服务的运行环境, 并且还可以保护宿主机内核不受其他进程的干扰和影响, 如文件系统空间、网络空间、进程空间等。
| 隔离类型 | 实现功能 | 系统调用参数 | 内核版本 |
|–|–|–|–|
| MNT Namespace(mount) | 提供磁盘挂载点和文件系统的隔离能力 | CLONE_NEWNS | Linux 2.4.19 |
| IPC Namespace(Inter-Process Communication) | 提供进程间通信的隔离能力 | CLONE_NEWIPC | Linux 2.6.19 |
| UTS Namespace(UNIX Timesharing System) | 提供主机名隔离能力 | CLONE_NEWUTS | Linux 2.6.19 |
| PID Namespace(Process Identification) | 提供进程隔离能力 | CLONE_NEWPID | Linux 2.6.24 |
| Net Namespace(network) | 提供网络隔离能力 | CLONE_NEWNET | Linux 2.6.29 |
| User Namespace(user) | 提供用户隔离能力 | CLONE_NEWUSER | Linux 3.8 |
通过这些内核级功能的实现,docker才可以正常工作,实现不同容器间的各种资源的隔离,形成共享同一组硬件及内核上却互不影响的独立应用级虚拟化系统。不过此时,我们还面临一个问题,就是资源使用率的控制。如果一个容器因为BUG或代码本身等原因导致无限制的使用宿主机上的资源,将会导致宿主机CPU或者内存不足进而极有可能影响到其他容器的正常运行。所以我们需要对每个容器的资源利用做一个限制,我们通过内核中的Linux Cgroups功能来限制每个容器能使用的资源的上限。
Linux Cgroups 的全称是 Linux Control Groups, 它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。此外,还能够对进程进行优先级设置,以及将进程挂起和恢复等操作。
Cgroups 在内核层默认已经开启,可通过下列命令验证查看Cgroups设置
CentOS7.6:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[root@localhost ~]# cat /boot/config-3.10.0-957.el7.x86_64 |grep CGROUP
CONFIG_CGROUPS=y
# CONFIG_CGROUP_DEBUG is not set
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_SCHED=y
CONFIG_BLK_CGROUP=y
# CONFIG_DEBUG_BLK_CGROUP is not set
CONFIG_NETFILTER_XT_MATCH_CGROUP=m
CONFIG_NET_CLS_CGROUP=y
CONFIG_NETPRIO_CGROUP=y
[root@localhost ~]#
ubuntu1804中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21root@DockerUbuntu:~# cat /boot/config-4.15.0-70-generic |grep CGROUP
CONFIG_CGROUPS=y
CONFIG_BLK_CGROUP=y
# CONFIG_DEBUG_BLK_CGROUP is not set
CONFIG_CGROUP_WRITEBACK=y
CONFIG_CGROUP_SCHED=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_RDMA=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_BPF=y
# CONFIG_CGROUP_DEBUG is not set
CONFIG_SOCK_CGROUP_DATA=y
CONFIG_NETFILTER_XT_MATCH_CGROUP=m
CONFIG_NET_CLS_CGROUP=m
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_NET_CLASSID=y
root@DockerUbuntu:~#
可以看到内核较新的 ubuntu 支持的功能更多。
1 | blkio:块设备 IO 限制。 |
可以通过命令ll /sys/fs/cgroup/
查看系统Cgroups
Docker容器技术如果真正想在企业中应用,还必须依赖下面的一些技术,会在之后的文章中详细介绍。
docker 自带的网络 docker network 仅支持管理单机上的容器网络, 当多主机运行的时候需要使用第三方开源网络,例如 calico、 flannel 等。
容器的动态扩容特性决定了容器 IP 也会随之变化, 因此需要有一种机制可以自动识别并将用户请求动态转发到新创建的容器上, kubernetes 自带服务发现功能,需要结合 kube-dns 服务解析内部域名。
可以通过原生命令 docker ps/top/stats 查看容器运行状态,另外也可以使
heapster/ Prometheus 等第三方监控工具监控容器的运行状态。
容器的动态迁移会导致其在不同的 Host 之间迁移,因此如何保证与容器相关的数据也能随之迁移或随时访问,可以使用逻辑卷/存储挂载等方式解决。
docker 原生的日志查看工具 docker logs, 但是容器内部的日志需要通过 ELK 等专门的日志收集分析和展示工具进行处理。
我们初步知道了Docker的概念和实现原理,也知道了Docker在使用中可能会遇到的问题,说了这么多,现在我们先将Docker部署一下。
Docker常见的安装方式有三种,可以通过rpm包下载,或者二进制安装,也可以通过epel源安装。
官方 rpm 包下载地址:
https://download.docker.com/linux/centos/7/x86_64/stable/Packages/
二进制下载地址:
https://download.docker.com/
https://mirrors.aliyun.com/docker-ce/linux/static/stable/x86_64/
阿里镜像下载地址:
https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/
Ubuntu的安装docker-ce阿里云镜像仓库方式如下(使用 apt-get 进行安装):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get -y update
sudo apt-get -y install docker-ce
注意:在与 kubernetes 结合使用的时候,要安装经过 kubernetes 官方测试通过的 docker版本, 避免出现不兼容等未知的及不可预估的问题发生, kubernetes 测试过的docker 版本可以在 github 查询, 具体如下:
https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.14.md#external-dependencies
安装完成后就可以用systemctl start docker
命令启动Docker了。
然后用docker info
命令来验证当前容器信息1
docker info
1 | Containers: 0 #当前主机运行的容器总数 |
在结尾可能会有类似warning警报
,1
WARNING: No swap limit support
这是提示说我们没有开启 swap 资源限制,这就需要我们通过修改内核参数,来限制swap资源的使用。1
vim /etc/default/grub
1 | GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0 cgroup_enable=memory swapaccount=1" |
1 | update-grub |
重启生效。至此,docker工具部署就完成了~
]]> KVM 是Kernel-based Virtual Machine的简称,是一个开源的系统虚拟化模块,自Linux 2.6.20之后集成在Linux的各个主要发行版本中,KVM目前已成为学术界的主流 VMM (virtual machine monitor,虚拟机监视器,也称为hypervisor)之一。
可参考红帽官方对kvm的定义:https://www.redhat.com/zh/topics/virtualization/what-is-KVM
KVM的虚拟化需要硬件支持(如Intel VT技术或者AMD V技术),是基于硬件的完全虚拟化,而Xen早期则是基于软件模拟的半虚拟化,新版本则是支持基于硬件支持的完全虚拟化,但Xen本身有自己的进程调度器,存储管理模块等,所以代码较为庞大,广为流传的商业系统虚拟化软件VMware ESXI系列是Full-Virtualization,(IBM文档:http://www.ibm.com/developerworks/cn/linux/l-using-kvm/ )
简单地说,KVM就是基于硬件支持实现,将单台主机,分隔成多个互不干扰的虚拟主机的技术,不受困于底层操作系统,可以为每个主机提供单独的、所需的的运行环境,如下图所示:
如果每个节点都是用共享存储如NAS,则可实现跨主机的虚拟机快速迁移,这也是我们在生产环境中经常采用的架构。本文将以下面架构,来具体展示KVM在实际生产中的应用。
三个主机,其中两个主机node1和node2上运行KVM,另一台是NAS共享存储服务器,通过10.0.0.0/16网段内网连接。在node1上的用KVM构建虚拟机H1和W1分别运行haproxy+keepalived服务和nginx服务,其中H1桥接方式到node1的两块网卡上,W1桥接到内网网卡eth1上。node2节点中也是如此。于是,将Client端请求通过haproxy反向代理只内网的web服务集群中,保证负载均衡和高可用,哪怕一个物理节点服务器down掉也不影响客户服务访问。
NAS服务器的搭建很容易,这里就先不详细介绍了,本文之后主要介绍KVM服务的配置及虚拟机集群的搭建。
KVM需要宿主机CPU必须支持虚拟化功能,因此如果是在vmware workstation上使用虚拟机做宿主机,那么必须要在虚拟机配置界面的处理器选项中开启虚拟机化功能(VMware支持嵌套虚拟化,而KVM不支持,大部分云服务器如阿里云就是基于KVM技术二次研发制作的,所以云服务器都不支持从中再建虚拟机)。
KVM模块因为已经被集成到linux内核之中,所以我们只需要安装KVM管理工具就可以直接使用KVM创建虚拟机了。
可用命令验证是否开启虚拟化(也可通过lscpu命令,grep -o选项只看匹配词本身,也可以用egrep代替grep -E)1
2
3[root@localhost ~]# grep -Eo "vmx|svm" /proc/cpuinfo | wc -l
vmx
vmx
在Ubuntu 18.04中:
可参考官网https://ubuntu.com/server/docs/virtualization-libvirt1
2
3
apt install qemu-kvm virt-manager libvirt-daemon-system
kvm-ok #验证是否支持kvm
1 | INFO: kvm exists |
CentOS 7.X:1
yum install qemu-kvm qemu-kvm-tools libvirt libvirt-client virt-manager virt-install
1 | systemctl start libvirtd |
为了让三个节点间虚拟机可以直接相互之间通信,需要将KVM上的虚拟机以桥接模式与宿主机的网卡连接,这就需要我们提前在宿主机上配置好网桥。
若宿主机为Ubuntu18.04:1
sudo vim /etc/netplan/01-netcfg.yaml
1 | # This file describes the network interfaces available on your system |
修改网卡配置后,使用netplan apply
命令使配置文件的修改生效1
netplan apply
若宿主机为CentOS:1
cd /etc/sysconfig/network-scripts/
1 | vim ifcfg-eh0 |
1 | TYPE=Ethernet |
1 | vim ifcfg-eh1 |
1 | TYPE=Ethernet |
1 | vim ifcfg-br0 |
1 | TYPE=Bridge |
1 | vim ifcfg-br1 |
1 | TYPE=Bridge |
重启网络服务,生效配置1
systemctl restart network
ip a
命令或ifconfig
查看桥接网卡是否生效
1 | [root@localhost ~] |
使用qemu-img create
命令可以创建磁盘,如果创建raw格式磁盘文件,则理解占据实际大小,若创建qcow2稀疏格式磁盘,则磁盘文件会随着使用的增大而增大1
qemu-img create -f raw /var/lib/libvirt/images/CentOS7.raw 10G建磁盘
1 | ll -h /var/lib/libvirt/images/CentOS7.raw |
1 | qemu-img create -f qcow2 /var/lib/libvirt/images/CentOS7.qcow2 10G |
1 | ll -h /var/lib/libvirt/images/CentOS7.qcow2 |
我们选用qcow2格式的磁盘,先创建H1虚拟机,1
qemu-img create -f qcow2 /var/lib/libvirt/images/H1.qcow2 10G
先导入ISO光盘镜像文件,可以使用共享存储上的ISO文件,也可本机导入,本机上传的话一般习惯性放到/usr/local/src/
目录下
使用virt-install
命令创建虚拟机,参数可virt-install --help
查看帮助信息1
virt-install --help
1 | usage: virt-install --name NAME --ram RAM STORAGE INSTALL [options] |
我们使用命令创建虚拟机(网卡配置选择default的话默认是nat模式)1
2
3
4
5
6
7virt-install --virt-type kvm \
--name H1 --ram 1024 --vcpus 2 \
--cdrom=/usr/local/src/CentOS-7-x86_64-Minimal-1908.iso \
--disk=/var/lib/libvirt/images/H1.qcow2 \
--network network=default \
--graphics vnc,listen=0.0.0.0 \
--noautoconsole
输入virt-manage
r可开启xshellmanager的终端窗口如下图
双击图标打开,可以看到已经开始自动从光盘启动安装了,配置完毕,手动安装CentOS7系统。
如果输入virt-manage
不能启动xmanager的窗口,检查一下项目
以上都没问题,建议重装xmanager,我就是配置都对,但是输入virt-manage
命令没反应,重装了一下xmanager和xshell之后就可以正常使用了。
安装好CentOS7系统重启后,在vrit-manager的窗口中点击虚拟机info,给H1虚拟机添加一块网卡,并将两块网卡分别选择为主机的桥接网卡br0和br1,如下图所示
在虚拟机中配置两个网卡的IP,配置分别如下1
2
3
4
5
6
7
8
9
10vi /etc/sysconfig/network-scripts/ifcfg-eth0
TYPE="Ethernet"
BOOTPROTO="static"
IPADDR="172.18.32.85"
GATEWAY="172.18.0.1"
DNS1="180.76.76.76"
DEFROUTE="yes"
NAME="eth0"
DEVICE="eth0"
ONBOOT="yes"
1 | vi /etc/sysconfig/network-scripts/ifcfg-eth1 |
此时H1的基本配置就算完成了。
查看/var/lib/libvirt/images/
目录下,看到已经有一个镜像了了1
2
3[root@localhost ~]# ll /var/lib/libvirt/images/
total 1594308
-rw-r--r-- 1 root root 1632632832 Nov 29 18:54 H1.qcow2
可以直接将此文件cp一份来当web1的磁盘文件1
2cd /var/lib/libvirt/images/
cp H1.qcow2 W1.qcow2
也可以在node2节点上的H2和web2虚拟机的磁盘文件。
我们直接拷贝改名后,执行virt-install
命令,创建另外三个虚拟机。1
2
3
4
5
6
7virt-install --virt-type kvm \
--name W1 --ram 1024 --vcpus 2 \
--cdrom=/usr/local/src/CentOS-7-x86_64-Minimal-1908.iso \
--disk=/var/lib/libvirt/images/W1.qcow2 \
--network network=default \
--graphics vnc,listen=0.0.0.0 \
--noautoconsole
然后将W1强制终止,因为第一次是默认从光驱启动的,我们已经有系统了,不需要重装,重启后就发现已经是装好的系统,与H1一模一样的。
然后修改主机名为web1,将网卡桥接在eth1上,修改网卡配置。1
2
3
4
5
6
7
8TYPE="Ethernet"
BOOTPROTO="static"
IPADDR="10.0.0.175"
PREFIX="16"
DEFROUTE="yes"
NAME="eth0"
DEVICE="eth0"
ONBOOT="yes"
对node2上的两个虚拟机采用同样方式创建。
之后分别通过脚本装HAProxy+keepadlived服务和nginx+PHP服务。
修改完几个服务的配置文件(可参考之前文章企业级应用:负载均衡层——haproxy(一))之后,就可以将通过客户机就可以访问,后端web服务器了。
1 | [root@HAProxy1 haproxy]# tree |
1 | #!/bin/bash |
1 | [root@Web1 web]# tree |
1 | vim web.sh |
1 | vim nginx/nginx.sh |
1 | vim nginx/nginx.service |
1 | vim nginx/nginx.conf |
1 | vim php-fpm/php-fpm.sh |
企业中,必不可少的应用就是VPN了,它可以帮助员工在外网中访问公司内网,常见开源实现方案有OpenVPN和jumpserver。
OpenVPN是采用了端口转发的原理实现,是基于IP+端口的4层代理机制,一般是用于出差员工访问公司内部ERP系统等使用,而jumpserver是7层代理,所以功能更加强大却也更加复杂,一般适合运维人员管理维护企业内部服务器。对于中小公司日常使用,OpenVPN就已经完全足够了,当然,也可用于科学,你懂得。本文将对linux环境下(CentOS),OpenVPN的安装配置做一个详细的介绍。
OpenVPN的rpm包可以在epel源中直接下载安装,所以我们需要先配置epel源,推荐选用阿里的epel源,CentOS67
命令如下:1
2
3
4
5
6
7
8cat > /etc/yum.repos.d/aliyunepel.repo << "END"
[aliyun-epel]
name=aliyun-epel
baseurl=https://mirrors.aliyun.com/epel/$releasever/$basearch/
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-$releasever
enabled=1
END
CentOS8
路径有所变化,命令如下:1
2
3
4
5
6
7
8cat > /etc/yum.repos.d/aliyunepel.repo << "END"
[aliyun-epel]
name=aliyun-epel
baseurl=https://mirrors.aliyun.com/epel/$releasever/Everything/$basearch/
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-$releasever
enabled=1
END
或者直接安装官方的epel源(6、7、8通用)。1
yum install epel-release -y
配置好epel源之后,直接yum安装服务器端软件和证书管理工具就可以了。1
2yum install openvpn -y #openvpn服务端
yum install easy-rsa -y #证书管理工具
将模版配置文件及证书管理工具复制到指定目录1
2
3cp -2.4.8 sample-config-files/server.conf openvpn/ share openvpn
cp -r share openvpn/easyrsa-server
cp -3.0.6/vars.example openvpn3/vars share easy-rsa
配置文件模版如果没有,那可能是在路径为/usr/share/doc/openvpn/sample/sample-config-files/server.conf
,easy-rsa如果CentOS8yum安装不上,只能去github上https://github.com/OpenVPN/easy-rsa
下载,将里面的easyrsa3目录复制出来就是证书管理工具,并且不需要复制vars.example
文件,里面已经有了,改个名字就可以。1
cp -r easyrsa3 /etc/openvpn/easyrsa-server
先进入证书管理工具目录(若github上下载的则是cd /etc/openvpn/easyrsa-server/
,之后则都没有3的子目录,或者也创建一个3的子目录保持一致性)1
cd /etc/openvpn/easyrsa-server/3
若easyrsa为github下载的话,目录结果如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[root@aws-host easyrsa-server]#tree
.
├── easyrsa
├── openssl-easyrsa.cnf
├── vars.example
└── x509-types
├── ca
├── client
├── code-signing
├── COMMON
├── email
├── server
└── serverClient
1 directory, 10 files
[root@aws-host easyrsa-server]#
OpenVPN的server端配置文件如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26vim /etc/openvpn/server.conf
local 172.18.200.101 #本机监听IP,写公网IP
port 1194 #端口
# TCP or UDP server?
proto tcp #协议,指定OpenVPN创建的通信隧道类型
#proto udp
#dev tap:创建一个以太网隧道,以太网使用tap
dev tun:创建一个路由IP隧道,互联网使用tun一个TUN设备大多时候,被用于基于IP协议的通讯。一个TAP设备允许完整的以太网帧通过Openvpn隧道,因此提供非ip协议的支持,比如IPX协议和AppleTalk协议
#dev-node MyTap #TAP-Win32适配器。非windows不需要
#topology subnet #网络拓扑,不需要配置
server 10.8.0.0 255.255.255.0 #客户端连接后分配IP的地址池,服务器默认会占用第一个IP 10.8.0.1
#ifconfig-pool-persist ipp.txt #为客户端分配固定IP,不需要配置
#server-bridge 10.8.0.4 255.255.255.0 10.8.0.50 10.8.0.100 #配置网桥模式,不需要
push "route 10.20.0.0 255.255.0.0" #给客户端生成的静态路由表,下一跳为openvpn服务器的10.8.0.1,地址段为openvpn服务器后的公司内部网络,可以是多个网段
push "route 172.31.0.0 255.255.248.0"
;client-config-dir ccd #为指定的客户端添加路由,改路由通常是客户端后面的内网网段而不是服务端的,也不需要设置
;route 192.168.40.128 255.255.255.248
;client-config-dir ccd
;route 10.9.0.0 255.255.255.252
;learn-address ./script #运行外部脚本,创建不同组的iptables 规则,不配置
;push "redirect-gateway def1 bypass-dhcp" #启用后,客户端所有流量都将通过VPN服务器,因此不需要配置
#;push "dhcp-option DNS 208.67.222.222" #推送DNS服务器,不需要配置
#;push "dhcp-option DNS 208.67.220.220"
#client-to-client #允许不同的client通过openvpn server直接通信,不开启
#;duplicate-cn #多个用户共用一个账户,一般用于测试环境,生产环境都是一个用户一个证书
keepalive 10 120 #设置服务端检测的间隔和超时时间,默认为每 10 秒 ping一次,如果 120 秒没有回应则认为对方已经 down
所以最终配置如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21local 172.18.32.71 #写公网IP,测试环境无所谓
port 1194
proto tcp
dev tun
ca /etc/openvpn/certs/ca.crt
cert /etc/openvpn/certs/server.crt
key /etc/openvpn/certs/server.key # This file should be kept secret
dh /etc/openvpn/certs/dh.pem
server 10.8.0.0 255.255.255.0 #默认设置,不需要修改
push "route 10.0.0.0 255.255.255.0" #内网网段,不需要修改,科学上网则为 push "redirect-gateway def1 bypass-dhcp"
client-to-client
keepalive 10 120
cipher AES-256-CBC
max-clients 100
user nobody
group nobody
persist-tun
status openvpn-status.log
log-append /var/log/openvpn/openvpn.log
verb 9
mute 20
初始化pki环境1
./easyrsa init-pki #生成pki目录用于保存证书
创建CA签发机构,然后直接回车
1
./easyrsa build-ca nopass #创建ca并不使用密码
创建服务端证书(私钥),直接回车
1
./easyrsa gen-req server nopass #生成server证书且不使用密码
然后使用自建ca签发服务器证书,即生成服务端crt公钥。crt公钥后期将用户发送给客户端,从而实现与openvpnserver端加密传输数据。1
./easyrsa sign server server #签发服务端证书,备注信息为server
然后输入yes
输入。
还在easyrsa-server目录下,生成秘钥,这可能会花费一段时间。1
./easyrsa gen-dh
到此服务端证书环境配置完成,下面是配置客户端证书配置
证书还是使用easyrsa工具,(github下载的也还是那个目录easyrsa3
,不需要复制vars.example
文件,改个名字就可以)1
2cp -r /usr/share/easy-rsa/ /etc/openvpn/easyrsa-client/
cp /usr/share/doc/easy-rsa-3.0.6/vars.example /etc/openvpn/easyrsa-client/3/vars
1 | cd /etc/openvpn/easyrsa-client/3 |
例如员工为Mice,则创建名为Mice的证书1
./easyrsa gen-req Mice nopass #客户证书为Mice,没有设置密码
然后直接回车
,便生成了Mice的证书申请和私钥。进入easyrsa的server目录中,(注意,一定要进入easyrsa-server目录
,否则会报错),导入证书申请并给客户端的证书请求签发证书,记得输入yes
确认。1
2./easyrsa import-req /etc/openvpn/easyrsa-client/3/pki/reqs/Mice.req Mice
./easyrsa sign client Mice
复制证书到server目录:1
2
3
4
5
6mkdir /etc/openvpn/certs
cd /etc/openvpn/certs/
cp /etc/openvpn/easyrsa-server/3/pki/dh.pem .
cp /etc/openvpn/easyrsa-server/3/pki/ca.crt .
cp /etc/openvpn/easyrsa-server/3/pki/issued/server.crt .
cp /etc/openvpn/easyrsa-server/3/pki/private/server.key .
创建客户端配置文件1
2mkdir openvpn Mice/
vim openvpn Mice/client.ovpn
1 | client #声明自己是个客户端 |
对签发的客户端证书及配置文件进行归档,并打包发送给客户端主机。1
2
3
4
5cd /etc/openvpn/client/Mice/
cp /etc/openvpn/easyrsa-server/3/pki/ca.crt .
cp /etc/openvpn/easyrsa-server/3/pki/issued/Mice.crt .
cp /etc/openvpn/easyrsa-client/3/pki/private/Mice.key .
tar czvf Mice.tar.gz *
开启路由转发功能:1
2
3# vim /etc/sysctl.conf
# sysctl -p
net.ipv4.ip_forward = 1
先清除现有防火墙策略以免发生干扰,如果CentOS7/8或Redhat7/8没有iptables命令就yum install -y iptables-services
安装一个,之后设置IP伪装需要用到。
清空已有规则1
2
3
4
5
6iptables -F
iptables -X
iptables -Z
iptables -t nat -F
iptables -t nat -X
iptables -t nat -Z
创建iptables 规则:1
2
3iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -j MASQUERADE #此IP是server端默认ip,不要改
iptables -A INPUT -p TCP --dport 1194 -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
CentOS6/7的话可以用保存iptables规则(适用未安装iptables-services,俩方式选一个就好):1
service iptables save
显示OK说明保存成功1
iptables: Saving firewall rules to /etc/sysconfig/iptables:[ OK ]
如果用的services,则systemctl enable --now iptables
(会自动从/etc/sysconfig/iptables文件中读取防火墙策略)
创建日志目录并授权:1
2mkdir /var/log/openvpn
chown nobody.nobody /var/log/openvpn
启动openvpn服务1
systemctl enable --now openvpn@server
如果没有openvpn@server.service文件,那就自己编写一个吧1
vim /usr/lib/systemd/system/openvpn@.service
1 | [Unit] |
验证日志:1
2
3
4
5
6
7
8
9
10
11
12
13tail /var/log/openvpn/openvpn.log
Tue Nov 19 13:27:27 2019 Socket Buffers: R=[87380->87380] S=[16384->16384]
Tue Nov 19 13:27:27 2019 Listening for incoming TCP connection on
[AF_INET]172.18.32.71:1194
Tue Nov 19 13:27:27 2019 TCPv4_SERVER link local (bound):
[AF_INET]172.18.32.71:1194
Tue Nov 19 13:27:27 2019 TCPv4_SERVER link remote: [AF_UNSPEC]
Tue Nov 19 13:27:27 2019 GID set to root
Tue Nov 19 13:27:27 2019 UID set to root
Tue Nov 19 13:27:27 2019 MULTI: multi_init called, r=256 v=256
Tue Nov 19 13:27:27 2019 IFCONFIG POOL: base=10.8.0.4 size=62, ipv6=0
Tue Nov 19 13:27:27 2019 MULTI: TCP INIT maxclients=4096 maxevents=4100
Tue Nov 19 13:27:27 2019 Initialization Sequence Completed
官方客户端下载地址: https://openvpn.net/community-downloads/
非官方地址:https://sourceforge.net/projects/securepoint/files/
如果是默认安装路径的话,保存证书到openvpn 客户端安装目录:C:\Program Files\OpenVPN\config
之后就可以直接启动OpenVPN。此时就可以用浏览器直接访问内网IP或者直接ssh登陆内部服务器了。
经过本人尝试多次,使用OpenVPN做代理转发来实现科学上网,还是有点难度。理论上简单可行的东西,搞了好几次都没成功。
本人有亚马逊的海外云主机,在上面搭建了OpenVPN之后怎么也没法成功,甚至云主机都登陆不上了。开始以为是GW将数据包拦截了,导致云主机被封掉。后来使用其他海外主机直接连接均无法连接失联的云主机,使用端口检查工具发现,那个云主机的所有端口都被关闭了。猜测应该是被亚马逊给封掉了,无奈之下只能释放实例,重新搭建服务器。弄了数次发现,结局均以实例被封告终。猜测亚马逊应该是检查到异常流量,直接把实例的数据连接都断掉了(据说OpenVPN的收费版本不会被封掉),绝望之下,尝试了下UDP协议来代替TCP协议,将客户端设置和服务端设置都改为UPD,竟然发现可以访问外网了,而且服务器至今也平安无事~有需求的小伙伴可以尝试UDP协议。另外附上我云主机的server端配置,希望对大家有所帮助。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24grep ^[a-Z] /etc/openvpn/server.conf
local 0.0.0.0
port 11094
proto udp
dev tun
ca /etc/openvpn/certs/ca.crt
cert /etc/openvpn/certs/server.crt
key /etc/openvpn/certs/server.key
dh /etc/openvpn/certs/dh.pem
server 10.8.0.0 255.255.255.0
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"
keepalive 10 120
cipher AES-256-CBC
max-clients 10
user openvpn
group openvpn
persist-key
persist-tun
status openvpn-status.log
log-append /var/log/openvpn/openvpn.log
verb 3
mute 20
explicit-exit-notify 1
本文写得很简略,发现有小伙伴对相关问题很有兴趣,欢迎留言讨论。
]]> tomcat作为一个应用服务器,单机性能上都是无法满足生产中需要的,而想要解决高并发场景,光靠提升单机性能,成本与效果肯定都是无法让人接受的,而此时我们一般都采用tomcat集群的方式,用多台tomcat服务器来共同支撑我们的业务。
但这时就出现了一个新的问题,那就是会话保持。因为每台tomcat服务器的session是独立的,当客户端被调度到一个新的tomcat服务器时,他无法识别之前一台的tomcat服务器分配的sessionID,于是对于此次访问,之前的会话信息就都没有了,这表现在用户的客户端就相当于,点开一个新的链接,就发现需要重新登陆,或者之前的购物车里的商品都不见了等等。这样的客户访问体验绝对不是我们想要的,所以我们需要实现会话保持功能!
一般tomcat的会话保持有三种方案实现:
> nginx主机ip:192.168.32.207
> tomcat主机1ip:192.168.32.231
> tomcat主机2ip:192.168.32.232
将3台机子中的nginx或tomcat服务启动之后,分别检查80端口和8080端口是否都已监听,确保服务启动。1
2iptables -F
getenforce 0
确保防火墙和SELinux设置不会干扰我们几台主机间的相互通信。
nginx主机反向代理的配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15upstream tomcat {
#ip_hash; #先关闭原地址iphash,观察效果
server 192.168.32.231:8080 weight=1 fail_timeout=5s max_fails=3;
server 192.168.32.232:8080 weight=1 fail_timeout=5s max_fails=3;
}
server {
listen 80;
index index.jsp
# server_name www.example.net;
# location ~* \.(jsp|do)$ {
location / {
proxy_pass http://tomcat;
}
}
可启用主机名,也可不启用直接用端口访问。用域名访问还需要改DNS或者hosts设置,比较麻烦,我们就直接通过IP+端口访问就可以了。
tomcat服务器中可以新建一个host,也可使用原先的localhost默认主机。我们这次不用之前的localhost,而是自己新创建一个host标签,指定appBase在/data/myapp目录下。两个tomcat服务器都要执行如下操作:1
vim /apps/tomcat/conf/server.xml
找到Engine标签
,将默认主机修改为myapp
1
<Engine name="Catalina" defaultHost="myapp">
找到localhost
的</Host>
标签,在下面创建新的主机myapp
,指定appBase为/data/myapp1
2
3<Host name="myapp" appBase="/data/myapp"
unpackWARs="true" autoDeploy="true">
</Host>
PS:Host name一般为主机域名,当一个tomcat中有多个host服务时,就是通过http报文请求头部的host信息来判断去访问哪个host服务的,当找不到对应的主机之后才访问defaultHost
,我们这里因为只有启用了一个host,且为defaultHost,所以就无所谓,可以任意命名了,只需对应上就好。
之后我们要在/data/myapp目录下创建一个ROOT
目录来作为tomcat访问的默认目录,注意ROOT
是大写的。1
mkdir -p /data/myapp/ROOT
为了方便我们看到效果,我们编写index.jsp时,调用一些函数,方便我们看到我们访问的tomcat主机的IP和端口、sessionID、访问时间。1
vim /data/myapp/ROOT/index.jsp
1 | <%@ import="java.util.*" %> |
此时,我们访问nginx的80端口,就可以被反向代理到后端的两个tomcat服务器上去。而这时,是轮询的调度方式,也就是一边一次,可以看到访问的主机和sessionID都是一直在变化的。
效果如下图所示:
每一次的sessionID都没有重复过,这肯定不满足我们的需要。所以我们将nginx配置中#ip_hash;的#注释去掉,重启nginx服务,再看效果,如下图所示:
可以看到,每次刷新,主机IP和sessionID都不再变化,说明绑定session成功。
这是tomcat官方提供的解决方案,所有tomcat上都有全量的session,不过同步session信息会消耗带宽,而且所有服务器保存所有session信息也比较占用资源,对于tomcat这种本身就处于效率瓶颈的服务来说,高并发场景下超过若五个tomcat服务器,就不再建议使用。
配置详细可以参见官网配置说明。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
将官网上的这段集群配置,插入到两个tomcat服务器中我们创建的host标签中(也可插入引擎标签中,就相当于所有主机都生效),即<Host name
标签与</Host>
标签的中间。
配置说明:1
2
3
4
5
6
7
8
9
10Cluster 集群配置
Manager 会话管理器配置
Channel 信道配置
Membership 成员判定。使用什么多播地址、端口多少、间隔时长ms、超时时长ms。同一个多播地址和端口认为同属一个组。使用时修改这个多播地址,以防冲突
Receiver 接收器,多线程接收多个其他节点的心跳、会话信息。默认会从4000到4100依次尝试可用端口。
address="auto",auto可能绑定到127.0.0.1上,所以一定要改为可以用的IP上去
Sender 多线程发送器,内部使用了tcp连接池。
Interceptor 拦截器
ReplicationValve 检测哪些请求需要检测Session,Session数据是否有了变化,需要启动复制过程
ClusterSessionListener 集群session侦听器
复制完官方文档里的配置,我们还需要修改接收器的ip为本机ip,不能使用auto
,否则会无法同步session信息。
此外,还需要在应用的web.xml文件中最后一行</web-app>
标签的上面一行插入子标签<distributable/>
表示可分配。
操作如下:1
2
3mkdir /data/myapp/ROOT/WEB-INF
cp /apps/tomcat/conf/web.xml /data/myapp/ROOT/WEB-INF/
sed -i '/<\/web-app>/i<distributable/>' /data/myapp/ROOT/WEB-INF/web.xml
此时,如果我们将前端Nginx或者其他反向代理服务器中的源地址hash去掉,我们就可以看到,虽然访问的tomcat服务器在变化(服务器ip变化),而sessionID却是不变的,因为每个tomcat服务器上都有全量的相同的session信息。
这种实现方式其实才是本文标题中真正的session共享,毕竟共享经济炒的很热,我们都知道,所谓共享,共用才叫共享,前面提到的那种tomcat集群里会话信息人手一份可不能称作是共享。于是乎,我们想到用将session放到外部存储,所有的tomcat服务器都去外部存储中去查找sessionID。
储存session信息肯定不能存储在磁盘文件中,这样的读取写入性能都会很慢,放在mysql数据库中或者以硬盘文件的方式保存,高并发场景下的读取写入速度都将会大打折扣。所以我们要使用类似memcached或者redis这种Key/Value的非关系型数据库里,也被称作NoSQL。
memcached和redis这种键值对型数据库的数据信息都是存储在内存中的,读写效率都很高,而且由于没有复杂的表关系,采用的是哈希算法,他们对于信息的查找都是O(1),而不是类似mysql等数据库的O(n),意思就是说耗时/耗空间与总数据量大小无关,不会随着数据量的增大导致查找时间几何倍数的增长。
但也因为memcached和redis的数据都是储存在内存中的,而且memcached还不支持持久化,所以我们一定要做好高可用,一旦发生故障,会话信息将立即丢失,几乎没有恢复的可能。
要实现tomcat共享session服务器,首先,我们要让tomcat将session储存到memcached或者redis等外部存储上,这就需要我们对tomcat进行配置,其次我们要将session信息序列化为变为字节流以便能储存在session服务器中,还要能将session服务器中的数据反序列化为可以识别的session信息,最后,当然我们还需要一个客户端来跟后端的session服务器通信,才能将数据写入和读取。
这想实现确实也比较复杂,不过在github已有开源解决方案(网址是https://github.com/magro/memcached-session-manager),memcached-session-manager,简称msm,后端采用memcached或者redis都可以(之前只支持memcached,因而得名msm,后来支持redis后,人们还是习惯叫它msm),且已经完成了对tomcat的session共享的配置支持(支持tomcat6.X、7.X、8.X、9.X),我们直接去下载对应版本的去使用就可以了。
根据项目的介绍文档,我们知道,想实现tomcat的session共享,我们至少需要配套的工具有:
这些工具官网上也都提供了下载链接,我们直接下载下来即可。
kryo如下图所示
其他工具包如下图所示
将这些jar包统统拷贝到tomcat服务器的lib目录下(改变lib目录下的jar包需重启tomcat服务才能生效)
使用msm搭建session共享服务器,如果后端为memcached,则有两种模式可以选,分别是sticky模式和non-sticky模式,后端为redis,则使用类似non-sticky模式。
以两台服务器为例,将tomcat1和memcached1部署在一台服务器上(简称为t1、m1),tomcat2和memcached2部署在另一台服务器上(简称为t2、m2)为例,结构图如下图所示。1
2
3
4
5<t1> <t2>
. \ / .
. .
. / \ .
<m1> <m2>
实现原理:当请求结束时Tomcat的session会送给memcached备份。即Tomcat session为主session,memcached session为备session,使用memcached相当于备份了一份Session。查询Session时Tomcat会优先使用自己内存的Session,Tomcat通过jvmRoute发现不是自己的Session,便从memcached中找到该Session,更新本机Session,请求完成后更新memcached。
可能有的朋友看的一头雾水,这到底是个什么结构。其实很简单,sticky模式就是t1的session信息还是储存在t1上,不过以m2为备用数据库,t2的session信息也是放在t2中储存,以m1服务器为备用服务器。这就意味着,用户在访问t1时,是从t1获取session信息,当t1挂掉或者整个节点1服务器挂掉之后,用户会被调度到t2上,而t2本地中没有session信息时,就会去m2中上找相关sessionID,而m2因为是t1的备用存储,所以有跟t1完全相同的session信息,于是用户的sessionID就可以被t2识别;而当m2备用存储服务挂掉之后,t1服务会通过检测发现自己没有备用存储,就会自动将m1也指定为自己的备用存储,将备份信息也同步至m1中,于是用户若再从t2访问时,虽然因为m2挂掉,其中的数据都无法访问,但t2就可以从m1上读取到对应的sessionID并同步到t2本身的存储中,也可以保持之前的会话信息。
修改的配置也很简单,依照官网说明,将下面的代码标签插入/conf/context.xml
文件中的context标签
结尾就可以了1
2
3
4
5
6<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.32.231:11211,n2:192.168.32.232:11211"
failoverNodes="n1"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
n1、n2只是memcached的节点别名,可以重新命名。
failoverNodes是指故障转移节点,也就是发生故障之后的备用节点,所以在n1节点上,n1是备用节点,n2是主存储节点。另一台Tomcat中配置将failoverNodes改为n2,意思是其主节点是n1,备用节点是n2。
若配置成功,在/apps/tomcat/log/catalina.out文件中看到如下信息。1
tail -n 20 /apps/tomcat/logs/catalina.out
1 | 23-Nov-2019 13:35:54.187 INFO [myapp-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.startInternal -------- |
此时在访问我们的前端代理(取消ipHASH绑定)就会看到下面界面,将节点2 关机,可以看到访问ip固定为192.168.32.231,而使用的memcached变成了n1节点。
从msm 1.4.0之后开始支持non-sticky模式。
Tomcat session为中转Session,如果n1为主session,n2为备session,则产生的新的Session会发送给主、备memcached,并清除本地Session,也就是说tomcat本身不储存session信息,只负责产生session。
需要注意的是,如果n1下线,n2转换为主节点。n1再次上线,n2依然是主Session存储节点。
配置方法与sticky大致相同,不过在/conf/context.xml
文件中的context标签
结尾插入代码略有不同,具体代码如下1
2
3
4
5
6
7
8<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.32.231:11211,n2:192.168.32.232:11211"
sticky="false"
sessionBackupAsync="false"
lockingMode="uriPattern:/path1|/path2"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
重启tomcat服务后生效。此时在/apps/tomcat/log/catalina.out文件中看到如下信息。1
tail -n 20 /apps/tomcat/logs/catalina.out
1 | 23-Nov-2019 13:43:05.863 INFO [myapp-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.startInternal -------- |
再次尝试访问负载代理服务器,发现同样实现了访问tomcatIP变化,sessionID不变,说明配置成功。
而后端使用redis作为session共享服务器时,仅支持non-stricky模式。建议用另外的服务器安装redis服务,并修改监听IP后启动,tomcat服务器中将jedis.jar
jar包拷贝至tomcat安装路径下lib目录
,同样在/conf/context.xml
文件中的context标签
结尾插入下面的代码即可(例如redis服务器IP端口为192.168.32.233:6379,可配置redis集群,可参考我之前博客redis高可用配置)。1
2
3
4
5
6
7
8<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="redis://192.168.32.233:6379"
sticky="false"
sessionBackupAsync="false"
lockingMode="uriPattern:/path1|/path2"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。本文将详细讲解tomcat在linux环境(以CentOS为例)中的安装与配置。
JDK是java Development Kit的缩写,即java语言的软件开发包,是javac、javap、jdb等开发、调试、编译工具。
OpenJDK是Sun公司采用GPL v2协议发布的JDK开源版本,可以用于商业用途,于2009年正式发布。它是基于JDK7的beta版开发,但为了也将java SE 6开源,从OpenJDK7的b20构建反向分之开发,从中剥离了不符合java SE 6规范的代码,发布了OpenJDK 6。所以OpenJDK6和JDK6没有关系。
在CentOS中,可以选择yum安装openjdk1
yum install java-1.8.0-openjdk
通过java -version
可以看到版本信息为1
2
3openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8,0_212-b04)
OpenJDK 64-Bit Server VM (build 25.212-b04,mixed mode)
也可以去Oracle官网下载JDK 8的rpm包安装(需要注册)1
yum install jdk-8u191-linux-x64,rpm
而通过java -version
看到版本信息为1
2
3
4[root@localhost ~]# java -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)
确定JDK版本之后,我们就可以准备开始部署tomcat了。
首先要去官网下载源码包,国内用的是清华大学的镜像下载地址,下载速度还是可以接受的。
生产中我们一般不会选用最新版的tomcat9,而是选用tomcat8更为稳妥。tomcat8的下载链接是http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.47/bin/apache-tomcat-8.5.47.tar.gz1
wget http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.47/bin/apache-tomcat-8.5.47.tar.gz
解压到想要安装的任意指定目录,例如/apps/tomcat/
目录1
tar xvf apache-tomcat-8.5.47.tar.gz -C /apps/
更改目录名称(也可以用软连接方式)。1
mv /apps/apache-tomcat-8.5.47 /apps/tomcat
jdk需要配置环境变量才可以找到,否则会无法访问jsp文件。
需要在环境变量配置文件中加入1
2export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH
于是,运行下面命令即可1
2
3
4cat > /etc/profile.d/jdk.sh <<"END"
export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH
END
运行命令使环境变量生效1
. /etc/profile.d/jdk.sh
一般来说,tomcat服务如果以root身份启动,风险还是很高的,因为tomcat本身bug还是有不少的,如果被别有用心之人加以利用,入侵进来,就可以获得root权限,这造成的后果绝对是灾难级的,这甚至会威胁到其他服务器的安全。为了规避这一风险,我们一般都会将tomcat服务以一个系统用户的身份启动,例如java用户。
创建java用户useradd -r java
更改服务目录的属主属组chown -R tomcat/*
启动服务时,我们使用命令su - java -c '/apps/tomcat/bin/startup.sh'
来启动,就可以以java用户的身份来启动进程了。
tomcat中配置文件目录在tomcat/conf/server.xml,其中需要注意的是:1
<Server port="8005" shutdown="SHUTDOWN">
这代表管理端监听在8005端口,向管理端发送SHUTDOWN
指令就会是tomcat直接终止,而且这是不需要密码权限控制的。如果在生产环境中,如果有任何人有意或者无意向tomcat服务器的8005端口发送了SHUTDOWN指令,整个服务都会被终止掉,,而且还不需要密码验证,这对我们的影响无疑是巨大的,所以我们通常都会将此处的管理监听端口改掉,或将shutdown指令设为别的复杂口令,让别人无法轻易或不小心将我们的服务器shutdown~
可采取MD5值的方式,选任一文件做MD5运算后得到的随机数值即为口令。1
sed -i "s@SHUTDOWN@`md5sum /root/.ssh/authorized_keys |cut -d' ' -f1`@" tomcat/conf/server.xml
此时,就可以通过安装目录下的tomcat/bin/startup.sh
来启动tomcat服务了。1
su - java -c ' /apps/tomcat/bin/startup.sh'
然后查看端口1
ss -tanl
即可看到8080端口已经处于监听状态了1
2
3
4
5
6
7
8
9
10[root@localhost ~]# ss -tanl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 100 :::8009 :::*
LISTEN 0 100 :::8080 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 1 ::ffff:127.0.0.1:8005 :::*
[root@localhost ~]#
访问tomcat服务器的ip的8080端口,可看到如下图所示界面,即表示tomcat安装成功。
不过,想使用tomcat自带的Manager App及Host Manager工具,还需要做一些额外的配置,此时如果直接访问会显示403权限拒绝,如下图所示。
错误提示说明已经说得很明白了,首先确保已经修改了应用的反问控制权限,,如果还没有,you’ll need to edit the Manager’s context.xml
file。
那这个context.xml在哪里呢?它是指应用的上下文设置文件,路径就是tomcat目录下的webapps/manager/META-INF/context.xml
文件,另一个应用Host Manager的路径就是tomcat目录下的webapps/host-manager/META-INF/context.xml
所以我们要修改这两个配置文件中的访问控制,将我们的访问IP加到里面,这样我们的访问IP才有权限去访问这两个管理应用。1
2
3<Context antiResourceLocking="false" privileged="true" >
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|172.18.*|192.168.32.*" />
在allow=
标签后面,加上我们的ip,支持通配符写法和PCRE格式的正则表达式写法,用|
隔开。两个应用的配置文件都要修改。
光将我们的来访IP加入许可白名单还是不够的,我们还需要一个有权限的账户才可以访问这些管理工具
路径是tomcat目录下的conf/tomcat-users.xml
文件。1
2
3
4
5
6
7<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="admin" password="123456" roles="manager-gui,admin-gui"/>
在下面加入后三行,先创建角色"manager-gui"
和"admin-gui"
,分别是Manager App的管理员和Host Manager的管理员。然后设置用户名密码及其所处的角色,也可以设置两个不同的用户来分别充当这两个角色。用户名密码可自行设置。1
2
3<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="admin" password="123456" roles="manager-gui,admin-gui"/>
有了管理员权限的账户,在被允许的客户端上就可以访问Manager App
和Host Manager
这两个工具了。点击图标访问,出现如下图所示的提示输入用户名密码弹窗,说明配置正确。
附上我自己用的tomcat脚本供大家参考(未配置管理工具)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23DST="/apps"
[ -a jdk-8u231-linux-x64.rpm ] || { echo ' the absence of jdk' ;exit 1;}
[ -d $DST/*tomcat* ] && { echo "tomcat is aready installed into $DST/tomcat" ;exit 2;}
id java &>/dev/null && { echo 'user java is exist,redis is aready installed' ; exit 3;}
iptables -F
systemctl disable firewalld
sed -i '@SELINUX=enforcing@SELINUX=disabled@' /etc/selinux/config
setenforce 0
mkdir -p /data$DST
#mkdir -p $DST
ln -s /data$DST $DST
yum install jdk-8u231-linux-x64.rpm
tar xf *tomcat-*.tar.* -C $DST/
mv $DST/*tomcat-* $DST/tomcat
sed -i "s@SHUTDOWN@`md5sum /root/.ssh/authorized_keys |cut -d' ' -f1`@" $DST/tomcat/conf/server.xml
cat > /etc/profile.d/jdk.sh <<"END"
export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH
END
. /etc/profile.d/jdk.sh
useradd -r java
chown -R java.java $DST/tomcat/*
su - java -c '$DST/tomcat/bin/startup.sh'
redis高可用一般有两种方式实现:哨兵和集群。在哨兵 sentinel 机制中,可以解决 redis 高可用的问题, 即当 master 故障后可以自动将 slave 提升为 master 从而可以保证 redis 服务的正常使用,但是无法解决 redis 单机写入的瓶颈问题, 即单机的 redis 写入性能受限于单机的内存大小、 并发数量、 网卡速率等因素。
redis 官方在 redis 3.0 版本之后推出了无中心架构的 redis cluster机制, 在无中心的 redis 集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接, 特点如下:
1:所有 Redis 节点使用(PING 机制)互联。
2:集群中某个节点的失效, 是整个集群中超过半数的节点监测都失效才算真正的失效(类似Sdown和Odown) 。
3:客户端不需要 proxy 即可直接连接 redis, 应用程序需要写全部的 redis 服务器 IP。
4:redis cluster 把所有的 redis node 映射到 0-16383 个槽位(slot)上, 读写需要到指定的 redis node 上进行操作,因此有多少个 reids node 相当于 redis 并发扩展了多少倍。
5:Redis cluster 预先分配 16384 个(slot)槽位,当需要在 redis 集群中写入一个 key -value 的时候,会使用 CRC16(key) mod 16384 之后的值,决定将 key 写入值哪一个槽位从而决定写入哪一个 Redis 节点上, 从而有效解决单机瓶颈。
需要手动先指定某一台 Redis 服务器为 master,然后将其他 slave 服务器修改配置文件或使用命令配置为 master 服务器的 slave。哨兵的前提是已经手动实现了一个 redis master-slave 的运行环境。
例如我们将192.168.32.81上的redis服务设置为master,192.168.32.82、192.168.32.83设置为slave,192.168.32.81中配置文件masterauth 设置为123456
在192.168.32.82上设置主节点信息SLAVEOF 192.168.32.81 6379
(redis5版本命令为REPLICAOF 192.168.7.101 6379
)1
2
3
4192.168.32.82:6379> SLAVEOF 192.168.32.81 6379
OK
192.168.32.82:6379> CONFIG SET masterauth "123456"
OK
同样,192.168.32.83也要设置主节点信息及密码1
2
3
4192.168.7.103:6379> SLAVEOF 192.168.7.101 6379
OK
192.168.7.103:6379> CONFIG SET masterauth "123456"
OK
此时通过info Replication
命令,在两个从节点上可以看到角色已变更为slave,且master_link_status
状态要为up,说明主从结构配置完成。1
2
3
4
5
6127.0.0.1:6379> info Replication
# Replication
role:slave
master_host:192.168.32.81
master_port:6379
master_link_status:up
需要注意的是:不同的 redis 版本之间存在兼容性问题, 因此各 master 和 slave 之间必须保持版本一致,且redis slave 也要开启持久化并设置和 master 同样的连接密码,此外,如果开启了安全模式,未设置密码的话也会导致主从无法连接。
哨兵可以不和 Redis 服务器部署在一起。可以使用另外不同的机子,一般采用单数个哨兵(至少3个)。不过为了节约服务器,我们还是和redis服务器部署到一起。
3个机子都手动添加Sentinel配置文件
vim /apps/redis/etc/sentinel.conf
1
2
3
4
5
6
7
8
9
10
11
12bind 0.0.0.0
port 26379
daemonize yes
pidfile "redis-sentinel.pid"
logfile "sentinel_26379.log"
dir "/usr/local/redis"
sentinel monitor mymaster 192.168.32.81 6379 2 #法定人数限制(quorum),即有几个slave 认为 master down 了就进行故障转移
sentinel auth-pass mymaster 123456
sentinel down-after-milliseconds mymaster 30000 #(SDOWN)主观下线的时间
sentinel parallel-syncs mymaster 1 #发生故障转移时候同时向新 master 同步数据的slave 数量, 数字越小总同步时间越长
sentinel failover-timeout mymaster 180000 #所有 slaves 指向新的 master 所需的超时时间
sentinel deny-scripts-reconfig yes #禁止修改脚本
通过/apps/redis/bin/redis-sentinel /apps/redis/etc/sentinel.conf
命令指定配置文件,启动哨兵,此时通过ss -tanl
命令可以看到已监听26379端口,说明哨兵服务已启动。1
2
3
4
5
6
7
8
9[root@CentOS8 ~]# redis-cli -h 192.168.32.81 -p 26379
192.168.32.81:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.32.81:6379,slaves=2,sentinels=3
可以通过redis-cli
指定26379端口连接哨兵服务器,info Sentinel
查看哨兵状态,显示status=ok,slaves=2,sentinels=3
,与我们之前设计的相同,说明哨兵服务正常。
此时,当master故障后,哨兵会通过投票机制,选举一个slave作为新的master,继续实现主从结构,而程序应用也会在主从切换的时候收到通知,Jedis 会进行连接的切换(在 JedisPool 中添加了 Sentinel 和MasterName 参数, JRedis Sentinel 底层基于 Redis 订阅实现 Redis 主从服务的切换通知, 当 Reids 发生主从切换时, Sentinel 会发送通知主动通知 Jedis 进行连接的切换, JedisSentinelPool 在每次从连接池中获取链接对象的时候,都要对连接对象进行检测,如果此链接和 Sentinel 的 Master 服务连接参数不一致,则会关闭此连接,重新获取新的 Jedis 连接对象。),实现redis的高可用。
在哨兵 sentinel 机制中,可以解决 redis 高可用的问题, 即当 master 故障后可以自动将 slave 提升为 master 从而可以保证 redis 服务的正常使用,但是无法解决 redis 单机写入的瓶颈问题, 即单机的 redis 写入性能受限于单机的内存大小、 并发数量、 网卡速率等因素,所以Redis自带的Cluster就是很好的一个解决方案。
1.每个 redis node 节点采用相同的硬件配置、相同的密码、 相同的 redis 版本。
2.每个节点必须开启的参数
cluster-enabled yes #必须开启集群状态, 开启后 redis 进程会有 cluster 显示cluster-config-file nodes-6380.conf #此文件有 redis cluster 集群自动创建和维护,不需
要任何手动操作
3.所有 redis 服务器必须没有任何数据
4.先启动为单机 redis 且没有任何 key value
Redis 3 和 4 版本需要使用到集群管理工具 redis-trib.rb,这个工具是 redis 官方推出的管理 redis 集群的工具,集成在 redis 的源码 src 目录下,是基于 redis 提供的集群命令封装成简单、便捷、实用的操作工具, redis-trib.rb 是 redis 作者用 ruby 开发完成的。所以需要有ruby环境,如果系统自带的ruby版本较低(需要Ruby version >= 2.3.0),那就需要重新编译安装更高版本。
1 | yum remove ruby rubygems -y |
1 | tar xf ruby-2.5.5.tar.gz |
安装redis的gem包(Gem是一个管理Ruby库和程序的标准包,它通过Ruby Gem(如 http://rubygems.org/ )源来查找、安装、升级和卸载软件包,非常的便捷)。
1
gem install redis
之后就可以使用redis-trib.rb
命令来创建管理集群了。
通过命令redis-trib.rb create
来创建集群,选项–replicas
需要注意的是,集群至少也需要3台master,(–replicas 指定的slave数量可以为0,不过一般不建议),如果指定slave数量为2的话就至少需要9个redis服务器了(3主6从),比较浪费资源,所以我们一般设置replicas数值为1。
如果设置slave数量为0,则所有的6个服务器均为master,无法实现高可,集群信息如下图所示:1
2
3
4
5
6
7
8127.0.0.1:6379> cluster nodes
c65ab2a5e9d8a9435d54f0ef7db302c62b6f9308 192.168.32.82:6379@16379 master - 0 1573794294000 3 connected 5461-8191
a13d711b6b54909e5aed44cc8f75195915bbfb95 192.168.32.85:6379@16379 master - 0 1573794293235 6 connected 13653-16383
5d614e18da36f9d7b3536566205281743adc95c5 192.168.32.8:6379@16379 myself,master - 0 1573794294000 1 connected 0-2730
6fe42df7ea9c4dbb00d9bae6f7e4238367b1089b 192.168.32.81:6379@16379 master - 0 1573794292212 2 connected 2731-5460
e3beb9f45b73b265050499ee093ed1a473a3d566 192.168.32.83:6379@16379 master - 0 1573794295286 4 connected 8192-10922
8ae323030e3ae28519a27058c6592b94d1aae734 192.168.32.84:6379@16379 master - 0 1573794295000 5 connected 10923-13652
127.0.0.1:6379>
如果设置slave数量为2,若创建集群是加入的服务器数量不够9台,则会报错:1
2
3
4
5
6
7
8
9
10
11
12[root@CentOS8 ~]#redis-trib.rb create --replicas 2 \
192.168.32.8:6379 \
192.168.32.81:6379 \
192.168.32.82:6379 \
192.168.32.83:6379 \
192.168.32.84:6379 \
192.168.32.85:6379
>>> Creating cluster
*** ERROR: Invalid configuration for cluster creation.
*** Redis Cluster requires at least 3 master nodes.
*** This is not possible with 6 nodes and 2 replicas per node.
*** At least 9 nodes are required.
若之前有创建集群失败的一些操作,或集群中有的redis服务器中有数据信息的话,也是无法创建成功的。会报类似如下如下信息:1
/usr/local/share/gems/gems/redis-4.1.3/lib/redis/client.rb:126:in `call': ERR Slot 2823 is already busy (Redis::CommandError)
所以我们一般在创建集群之前,先连上redis服务器清空数据,并重置集群关系,确保所有的redis服务器都没有数据且都为单机master。1
2FLUSHALL
cluster reset
然后执行创建集群命令1
2
3
4
5
6
7redis-trib.rb create --replicas 1 \
192.168.32.8:6379 \
192.168.32.81:6379 \
192.168.32.82:6379 \
192.168.32.83:6379 \
192.168.32.84:6379 \
192.168.32.85:6379
即可成功创建集群。连接redis,登陆,即可看到集群主从关系。1
2
3
4
5
6
7
8
9
10
11
12
13[root@CentOS8 ~]#redis-cli
127.0.0.1:6379> cluster nodes
NOAUTH Authentication required.
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> cluster nodes
c65ab2a5e9d8a9435d54f0ef7db302c62b6f9308 192.168.32.82:6379@16379 master - 0 1573798716854 3 connected 10923-16383
a13d711b6b54909e5aed44cc8f75195915bbfb95 192.168.32.85:6379@16379 slave 6fe42df7ea9c4dbb00d9bae6f7e4238367b1089b 0 1573798715000 6 connected
5d614e18da36f9d7b3536566205281743adc95c5 192.168.32.8:6379@16379 myself,master - 0 1573798716000 1 connected 0-5460
6fe42df7ea9c4dbb00d9bae6f7e4238367b1089b 192.168.32.81:6379@16379 master - 0 1573798717877 2 connected 5461-10922
8ae323030e3ae28519a27058c6592b94d1aae734 192.168.32.84:6379@16379 slave 5d614e18da36f9d7b3536566205281743adc95c5 0 1573798715834 5 connected
e3beb9f45b73b265050499ee093ed1a473a3d566 192.168.32.83:6379@16379 slave c65ab2a5e9d8a9435d54f0ef7db302c62b6f9308 0 1573798716000 4 connected
127.0.0.1:6379>
监控集群状态1
redis-trib.rb check 192.168.32.8:6379
Redis 5 版本可以直接用redis-cli客户端命令加各种命令参数来管理创建集群(配置文件中如果已配置正确的密码,则不需要-a 选项,-a选项会有warning警告)。
创建集群1
2
3
4
5
6
7redis-cli -a 123456 --cluster create --cluster-replicas 1 \
192.168.32.8:6379 \
192.168.32.81:6379 \
192.168.32.82:6379 \
192.168.32.83:6379 \
192.168.32.84:6379 \
192.168.32.85:6379
监控集群状态1
redis-cli -a 123456 --cluster check 192.168.32.8:6379
集群运行时间长久之后,难免由于硬件故障、网络规划、 业务增长等原因对已有集群进行相应的调整, 比如增加 Redis node 节点、 减少节点、 节点迁移、更换服务器等。
同步之前 Redis node 的配置文件到 192.168.32.86 Redis 编译安装目录, 注意修改配置文件的监听 IP。1
2scp redis.conf 192.168.32.86:/apps/redis/etc/
scp redis_6380.conf 192.168.32.86:/apps/redis/etc/
对预备加入的redis服务器清除数据并执行集群重置1
2FLUSHALL
cluster reset
redis3、4 中添加节点(Adding node 192.168.32.87:6379 to cluster 192.168.32.8:6379)1
redis-trib.rb add-node 192.168.32.87:6379 192.168.32.8:6379
redis5 中添加节点(Adding node 192.168.32.87:6379 to cluster 192.168.32.8:6379)1
redis-cli --cluster add-node 192.168.32.87:6379 192.168.32.8:6379
集群槽位迁移时,迁出的服务器中不能有数据,否则会迁移失败并终止,且需要修复集群,才可以进行第二次迁移。
redis3、4 中对新加的主机重新分配槽位(后可跟集群中任意主机ip:port):1
redis-trib.rb reshard 192.168.32.8:6379
redis5 对新加的主机重新分配槽位(后可跟集群中任意主机ip:port):1
redis-cli --cluster reshard 192.168.32.8:6379
输入命令后根据提示输入1
2
3
4
5
6
7
8
9How many slots do you want to move (from 1 to 16384)? 4096 #需要迁移多少槽位
What is the receiving node ID? 5d614e18da36f9d7b3536566205281743adc95c5 #选择迁移到哪个redis主机
What is the receiving node ID? 6bffc0037eea959f0dcc11aca657f928d86cfc75
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1:6bffc0037eea959f0dcc11aca657f928d86cfc75 #选择从哪个主机迁出
Source node #2:done #输入done结束选择
Do you want to proceed with the proposed reshard plan (yes/no)? yes #是否执行,yes确认,之后就会迁移完毕
如果迁移失败,则需要修复集群。
redis3、4 中对对报错redis服务器进行单独修复(后跟需要修复的主机ip:port):1
redis-trib.rb fix 192.168.32.82:6379
redis5 中对对报错redis服务器进行单独修复(后跟需要修复的主机ip:port):1
redis-cli --cluster fix 192.168.32.82:6379
在整个 Redis cluster 集群中,每个 master 至少有一个 slave。也可以有多个,但是至少要有一个提供数据备份和服务高可用。
redis-trib.rb info 192.168.32.8:6379
1
2
3
4
5
6
7
8[root@CentOS8 ~]#redis-trib.rb info 192.168.32.8:6379
192.168.32.8:6379 (5d614e18...) -> 0 keys | 4096 slots | 1 slaves.
192.168.32.82:6379 (c65ab2a5...) -> 0 keys | 4096 slots | 0 slaves.
192.168.32.81:6379 (6fe42df7...) -> 0 keys | 4096 slots | 1 slaves.
192.168.32.87:6379 (6bffc003...) -> 0 keys | 4096 slots | 1 slaves.
192.168.32.86:6379 (e06732b2...) -> 0 keys | 0 slots | 0 slaves.
[OK] 0 keys in 5 masters.
0.00 keys per slot on average.
我们看到之前的3主3从结构因为我们加了两个服务器变成了5主3从,新加的两个服务器,我们需要将它们设置为一主一从来实现高可用。于是我们要将192.168.32.86变为192.168.32.82的slave。
连接登陆192.168.32.86上的redis服务器,先查好192.168。32.82的ID为c65ab2a5e9d8a9435d54f0ef7db302c62b6f9308,执行1
cluster replicate c65ab2a5e9d8a9435d54f0ef7db302c62b6f9308
删除节点,需要先将redis主机上的槽位都迁移到其他主机上才可以操作。
迁移完成后,redis3、4 删除节点(要删除的redis主机要写节点号,集群可以写集群中任意主机ip+port)
1 | [root@CentOS8 ~]#redis-trib.rb del-node 192.168.32.8:6379 6bffc0037eea959f0dcc11aca657f928d86cfc75 |
redis5 删除节点1
redis-cli --cluster del-node 192.168.32.8:6379 6bffc0037eea959f0dcc11aca657f928d86cfc75
主节点被删除之后,其之前它的 slave 自动称为了 Redis 集群中其他 master的 slave,此节点如果不需要也可以一并删除。
]]>