awk内置函数sub gensub gsub match等介绍

环境:
[root@MySQL ~]# cat /etc/issue
CentOS release 6.5 (Final)
Kernel r on an m
[root@MySQL ~]# awk –version
GNU Awk 3.1.7

1.asort和asorti

格式:

asort(s [, d])
asorti(s [, d]) 

功能及返回值:
asort:对数组进行排序,如果省略参数d,则修改数组s,如果提供参数d,则将数组s拷贝到d中然后进行排序,数组s不会被修改,排序后数组的下标从1开始;最终返回数组中元素个数

[root@MySQL ~]# awk 'BEGIN{a[0]="a";a[1]="c";a[2]="b";print "before sorting:";for(i in a){print i,a[i]};asort(a);print "after sorting:";for(i in a){print i,a[i]}}'

before sorting:
0 a
1 c
2 b
after sorting:
1 a
2 b
3 c

[root@mysql ~]# awk 'BEGIN{a[0]="a";a[1]="c";a[2]="b";print "before sorting:";for(i in a){print i,a[i]};asort(a,d);print "after sorting:";for(i in a){print i,a[i]};print ;for(i in d){print i,d[i]}}'

before sorting:
0 a
1 c
2 b
after sorting:
0 a
1 c
2 b

1 a
2 b
3 c

2.sub、gensub和gsub函数

格式:

sub(r, s [, t])
gsub(r, s [, t])
gensub(r, s, h [, t])

功能及返回值:
sub:对于t中匹配r的字串,将第一个匹配的子串替换为s,如果t省略,则t为$0;返回值为替换的字符串个数
gsub:对于t中匹配r的字串,将匹配的所有子串替换为s,如果t省略,则t为$0;返回值为替换的字符串个数
gensub:对于t中匹配r的字串,如果h是以”g”或”G”开头的字符串,则将匹配的所有子串替换为s,如果h是数字n,则将第n处匹配进行替换;如果参数t省略,则t为$0
sub及gsub案例

[root@MySQL ~]# awk 'BEGIN{r="or|ll";s="wj";t="hello,world!hello,awk";print sub(r,s,t),t}'

1 hewjo,world!hello,awk

[root@MySQL ~]# awk 'BEGIN{r="or|ll";s="wj";t="hello,world!hello,awk";print gsub(r,s,t),t}'

3 hewjo,wwjld!hewjo,awk

[root@MySQL ~]# echo "hello,world;hello,awk"|awk '{r="or|ll";s="wj";print sub(r,s),$0}'

1 hewjo,world;hello,awk

[root@MySQL ~]# echo "hello,world;hello,awk"|awk '{r="or|ll";s="wj";print gsub(r,s),$0}'

3 hewjo,wwjld;hewjo,awk

注:正则表达式另外一种写法

[root@MySQL ~]# echo "hello,world;hello,awk"|awk '{s="wj";print gsub(/or|ll/,s),$0}'

3 hewjo,wwjld;hewjo,awk

注:sub和gsub函数功能相同,前者指替换匹配的第一个字符串,而后者进行全局替换
gensub案例
省略参数t

[root@MySQL ~]# echo "hello,world!hello,awk!hello Linux!"|awk 'BEGIN{s="ww";r="ll"}{print gensub(r,s,"g")}'

hewwo,world!hewwo,awk!hewwo linux!

参数h以g或G开头

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,"g",t)}'

hewwo,world!hewwo,awk!hewwo linux!

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,"g1",t)}'

hewwo,world!hewwo,awk!hewwo linux!

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,"G1",t)}'

hewwo,world!hewwo,awk!hewwo linux!

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,"g1",t)}'

hewwo,world!hewwo,awk!hewwo linux!

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,"G1",t)}'

hewwo,world!hewwo,awk!hewwo linux!

如果参数h不是数字也不以g或G开头,则替换第一处

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,"a",t)}' 

hewwo,world!hello,awk!hello linux!

参数h是数字

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,"1",t)}'

hewwo,world!hello,awk!hello linux!

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,"3",t)}'

hello,world!hello,awk!hewwo linux!

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,3,t)}'

hello,world!hello,awk!hewwo linux!

[root@MySQL ~]# awk 'BEGIN{s="ww";t="hello,world!hello,awk!hello linux!";r="ll";print gensub(r,s,0,t)}'

awk: warning: gensub: third argument of 0 treated as 1
hewwo,world!hello,awk!hello linux!

3..index函数

格式:

index(s, t)

功能及返回值:
返回字符串t在字符串s中的索引,如果字符串t在字符串s中不存在,则返回0(这表明字符串的索引是从1开始的)

[root@MySQL ~]# awk 'BEGIN{s="hello,world";t="llo";print index(s,t)}'

3

[root@MySQL ~]# awk 'BEGIN{s="hello,world";t="lloo";print index(s,t)}'

0

[root@MySQL ~]# awk 'BEGIN{s="hello,world";t="hello,world!";print index(s,t)}'

0

[root@MySQL ~]# awk 'BEGIN{s="hello,world";t="";print index(s,t)}'

1

注意:当字符串t为空时,返回的索引为1

4.length函数

格式:

length([s])

功能及返回值:
返回字符串s的长度,如果参数s省略,则返回$0的长度;从3.1.5版本开始,作为非标准扩展,如果参数为数组,则返回数组元素个数。

[root@MySQL ~]# awk 'BEGIN{s="hello,world";t="";print index(s,t)}'

1

[root@MySQL ~]# awk 'BEGIN{s="";print length(s)}'

0

[root@MySQL ~]# awk 'BEGIN{s=123;print length(s)}'

3

[root@MySQL ~]# awk 'BEGIN{s="hello world";print length(s)}'

11

[root@MySQL ~]# awk 'BEGIN{print length()}'

0

[root@MySQL ~]# echo "123 345" | awk '{print length()}'

7

5.match函数

格式:

match(s, r [, a])

功能及返回值:
当正则表达式r匹配字符串s中的某一部分时,返回匹配部分的索引,如果匹配不上,返回0,同时设置内置变量RSTART和RLENGTH;如果提供没有省略参数数组a,数组中的第1-n个元素为字符串s匹配正则表达式r中的带括号的子表达式的部分,数组a的第0个元素为字符串s匹配正则表达式r的完整匹配,数组的下标a[n, “start”]和a[n, “length”]分别表示匹配字符串的第一个字符的索引及匹配的字符串的长度。
可能描述地不是很清楚,下面通过例子来讲解
案例一:省略参数数组a

[root@MySQL ~]# awk 'BEGIN{s="hello,world!";r="ll";print match(s,r)}'

匹配ll,索引为3,返回值为3

3

[root@MySQL ~]# awk 'BEGIN{s="hello,world!";r="wj";print match(s,r)}'

匹配wj,没有匹配上,返回值为0
0

案例二:提供参数数组a(正则表达式中没有带括号的子表达式)

[root@MySQL ~]# awk 'BEGIN{s="hello,world!";r="ll";print match(s,r,a);print ;for(i in a){print "subscript:"i"t""valus:"a[i]}}'

3 #返回s中匹配ll的索引,为3

subscript:0 start valus:3 #数组a[0,”start”],值为s中匹配ll的索引,即3
subscript:0 length valus:2 #数组a[0,”length”],值为匹配的字符串的长度,即ll的长度,为2
subscript:0 valus:ll #数组a[0],值为匹配的字符串,即ll

另外一种写法,将正则表达式放在//中,和上面是同样的效果

[root@MySQL ~]# awk 'BEGIN{s="hello,world!";print match(s,/ll/,a);print ;for(i in a){print "subscript:"i"t""valus:"a[i]}}'

3

subscript:0start valus:3
subscript:0length valus:2
subscript:0 valus:ll

案例三:提供参数数组a(正则表达式中有带括号的子表达式)

[root@MySQL ~]# awk 'BEGIN{s="hello,world!";r="(ll).*(or.*d)";print match(s,r,a);print length(a);print ;for(i in a){print "subscript:"i"t""valus:"a[i]}}'

3 #匹配的字符串索引位置
9 #数组a中的元素个数

subscript:0start valus:3
subscript:0length valus:9
subscript:1start valus:3
subscript:2start valus:8
subscript:0 valus:llo,world
subscript:1 valus:ll
subscript:2 valus:orld
subscript:2length valus:4
subscript:1length valus:2

#上面输出数组a的元素顺序有点乱,整理下,如下:

subscript:0 valus:llo,world
subscript:0 start valus:3
subscript:0 length valus:9
subscript:1 valus:ll
subscript:1 start valus:3
subscript:1 length valus:2
subscript:2 valus:orld
subscript:2 start valus:8
subscript:2 length valus:4

当正则表达式中有带括号的子表达式时,数组a中的第0个元素为正则表达式的完整表达式,数组第1-n个元素为正则表达式中子表达式的内容

(ll).(or.d)

对于字符串“hello,world!”来说,

正则表达式(ll).(or.d)的的完整匹配为“llo,world”,所以a[0]的值为“llo,world”,a[0,”start”]为“llo,world”中的起始字符“l”在“hello,world!”中的索引,即3;a[0,”length”]为“llo,world”的长度,即9。

正则表达式(ll).(or.d)中的子表达式分别为(ll)和(or.*d),匹配“hello,world!”时,分别匹配”ll”和“orld”,所以a[1]和a[2]的值分别为”ll”和“orld”,a[n, “start”]和a[n, “length”](n=2,3)分别存储对应的索引和长度

注:
RSTART:match()函数匹配的第一个字符的索引;如果没有匹配,则为0
RLENGTH:match()函数匹配的字符串的长度;如果没有匹配,则为-1
RSTART The index of the first character matched by match(); 0 if no match. (Thisimplies that character indices start at one.)
RLENGTH The length of the string matched by match(); -1 if no match.

6.split函数

格式:

split(s, a [, r])

功能及返回值:将字符串s用正则表达式r作为分隔符进行分割,将分割的多个字段(域)存储到数组a中;如果r省略,用awk内置的FS变量对字符串s进行分割,将将分割的多个字段(域)存储到数组a中。返回分割的字段数也即数组中元素个数。

[root@MySQL ~]# awk 'BEGIN{s="hello,world;hello,awk";r=",";print split(s,a,r);for(i in a){print i,a[i]}}'

3
1 hello
2 world;hello
3 awk

[root@MySQL ~]# awk 'BEGIN{s="hello,world;hello,awk";r="hello";print split(s,a,r);for(i in a){print i,a[i]}}'

3
1
2 ,world;
3 ,awk

[root@MySQL ~]# head -n 1 /etc/passwd|awk 'BEGIN{s="hello,world;hello"}{print split(s,a);for(i in a){print i,a[i]}}'

1
1 hello,world;hello

[root@MySQL ~]# head -n 1 /etc/passwd|awk 'BEGIN{FS=";";s="hello,world;hello"}{print split(s,a);for(i in a){print i,a[i]}}'

2
1 hello,world
2 hello

注:FS变量的赋值也可以放在pattern+action外面

[root@MySQL ~]# head -n 1 /etc/passwd|awk -v FS=";" 'BEGIN{s="hello,world;hello"}{print split(s,a);for(i in a){print i,a[i]}}'

2
1 hello,world
2 hello

7.sprintf函数

对于该函数,后续会单独写一篇文章介绍

8.strtonum函数

格式:

strtonum(str)

功能及返回值:将字符串类型转化为数字类型,如果str以0开头则被转化为8进制,如果str以0x或0X开头则被转换为16进制

[root@MySQL ~]# awk 'BEGIN{s="123";print strtonum(s)}'
123
[root@MySQL ~]# awk 'BEGIN{s="0123";print strtonum(s)}'
83
[root@MySQL ~]# awk 'BEGIN{s="0x123";print strtonum(s)}'
291
[root@MySQL ~]# awk 'BEGIN{s="0X123";print strtonum(s)}'
291
[root@MySQL ~]# awk 'BEGIN{s="a123";print strtonum(s)}'
0
[root@MySQL ~]# awk 'BEGIN{s="12a3";print strtonum(s)}'
12
[root@MySQL ~]# awk 'BEGIN{s="123a";print strtonum(s)}'
123
[root@MySQL ~]# awk 'BEGIN{s="123.456";print strtonum(s)}'
123.456
[root@MySQL ~]# awk 'BEGIN{s="";print strtonum(s)}'
0

9.substr函数

格式:

substr(s, i [, n])

功能及返回值:
substr:返回字符串s中从索引i开始的最大长度为n字符串,如果n省略,则返回从索引i到字符串s末尾的字符串

[root@MySQL ~]# awk 'BEGIN{s="hello,world";print substr(s,2)}'
ello,world
[root@MySQL ~]# awk 'BEGIN{s="hello,world";print substr(s,2,5)}'
ello,
[root@MySQL ~]# awk 'BEGIN{s="hello,world";print substr(s,0)}'
hello,world
[root@MySQL ~]# awk 'BEGIN{s="hello,world";print substr(s,-2)}'
hello,world
[root@MySQL ~]# awk 'BEGIN{s="hello,world";print substr(s,3,-2)}'

[root@MySQL ~]# 

10.tolower和toupper函数

格式:

tolower(str)
toupper(str)

功能及返回值:
tolower:将字符转化为小写字母,非字母则不变
toupper:将字符转换为大写字母,非字母则不变

[root@MySQL ~]# awk 'BEGIN{s="HellO";print tolower(s)}'
hello
[root@MySQL ~]# awk 'BEGIN{s="^He;llO$";print tolower(s)}'
^he;llo$
[root@MySQL ~]# awk 'BEGIN{s="HellO";print toupper(s)}'
HELLO
[root@MySQL ~]# awk 'BEGIN{s="^He;llO$";print toupper(s)}'
^HE;LLO$

使用apt-mirror搭建ubuntu本地仓库

APT本地源的搭建(可用于局域网apt-get源搭建或者本地源)
本文档介绍使用apt-mirror软件搭建apt本地源
需求:内网开发环境由于其特定原因不能上外网,所以需要本地环境下的内网源来方便开发人员下载安装软件
建议:单独使用一块磁盘来存放源文件或者单独一个目录下,避免混淆

服务端配置

1、安装apt-mirror

apt-get install apt-mirror

2、修改apt-mirror配置文件

vim /etc/apt/mirror.list

参考以下配置文件:
清空原有的配置文件,直接使用以下配置文件即可


############# config ################## # 以下注释的内容都是默认配置,如果需要自定义,取消注释修改即可 set base_path /var/spool/apt-mirror # # 镜像文件下载地址 # set mirror_path $base_path/mirror # 临时索引下载文件目录,也就是存放软件仓库的dists目录下的文件(默认即可) # set skel_path $base_path/skel # 配置日志(默认即可) # set var_path $base_path/var # clean脚本位置 # set cleanscript $var_path/clean.sh # 架构配置,i386/amd64,默认的话会下载跟本机相同的架构的源 set defaultarch amd64 # set postmirror_script $var_path/postmirror.sh # set run_postmirror 0 # 下载线程数 set nthreads 20 set _tilde 0 # ############# end config ############## # Ali yun(这里没有添加deb-src的源) deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse clean http://mirrors.aliyun.com/ubuntu

3、开始同步

执行

apt-miiror

然后等待很长时间(该镜像差不多100G左右,具体时间看网络环境),同步的镜像文件目录为/var/spool/apt-mirror/mirror/mirrors.aliyun.com/ubuntu/,当然如果增加了其他的源,在/var/spool/apt-mirror/mirror目录下还有其他的地址为名的目录。

4、安装apache2

apt-get install apache2

由于Apache2的默认网页文件目录位于/var/www/html,因此,可以做个软链接(这样我们就可以直接访问了,无需将其直接导入该目录)

ln -s /var/spool/apt-mirror/mirror/mirrors.aliyun.com/ubuntu /var/www/html/ubuntu

然后就可以通过如下地址访问了
http://[host]:[port]/ubuntu #ip和port是自己本机的,其中端口默认为80
在测试时可能遇到打不开的情况,查看下iptables规则是否限制或者selinux的问题(这点相信大家在学习lanmp的时候都已经了解过了)

客户端配置:

1、编辑/etc/apt/source.list,加入以下内容

# Local Source      #ip和port是自己本机的,其中端口默认为80
deb [arch=amd64] http://[host]:[port]/ubuntu/ trusty main restricted universe multiverse
deb [arch=amd64] http://[host]:[port]/ubuntu/ trusty-security main restricted universe multiverse
deb [arch=amd64] http://[host]:[port]/ubuntu/ trusty-updates main restricted universe multiverse  
deb [arch=amd64] http://[host]:[port]/ubuntu/ trusty-proposed main restricted universe multiverse
deb [arch=amd64] http://[host]:[port]/ubuntu/ trusty-backports main restricted universe multiverse

2、更新apt-get源

apt-update    #这步很重要

Linux crontab设置定时重启Apache服务

通过 crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常适合周期性的日志分析或数据备份等工作。

1.首先在 $HOME/.profile 中设置 crontab 使用的编辑器:

EDITOR=vi; export EDITOR

2.新建一个 crontab 文件, 比如 root 用户的话就叫做 rootcron, 写入以下内容 :
每天 4 点重启 httpd

00 4 * * * /usr/sbin/service httpd restart

这里遇到的问题就是, 在 centos 下直接用 serivce 无效, /usr/sbin/service 才生效.

3.直接 crontab 加上上面新建的文件 rootcron 即可提交 crontab 任务:

crontab rootcron

查看 crontab 任务:

crontab -l

k8s与flannel网络原理

我们这里假设flannel使用VXLAN协议。每台主机都安装有flannel。k8s定义的flannel网络为10.0.0.0/16,各主机的flannel从这个网络申请一个子网。pod1所在的主机的flannel子网为10.0.14.1/24,pod2所在主机的flannel子网为10.0.5.1/24。每台主机有cni0和flannel.1虚拟网卡。cni0为在同一主机pod共用的网桥,当kubelet创建容器时,将为此容器创建虚拟网卡vethxxx,并桥接到cni0网桥。flannel.1是一个tun虚拟网卡,接收不在同一主机的POD的数据,然后将收到的数据转发给flanneld进程。原理图:
系统管理

pod1到pod2的网络

pod1路由表:

default via 10.0.14.1 dev eth0 
10.0.0.0/16 via 10.0.14.1 dev eth0 
10.0.14.0/24 dev eth0  proto kernel  scope link  src 10.0.14.15 

host1路由表:

default via 192.168.93.254 dev eno16777984  proto static  metric 100 
10.0.0.0/16 dev flannel.1 
10.0.14.0/24 dev cni0  proto kernel  scope link  src 10.0.14.1 
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1 
192.168.93.0/24 dev eno16777984  proto kernel  scope link  src 192.168.93.212  metric 100 

pod1 IP地址:10.0.14.15
pod2 IP地址:10.0.5.150

pod1与pod2不在同一台主机

下面是从pod1 ping pod2的数据包流向
1. pod1(10.0.14.15)向pod2(10.0.5.150)发送ping,查找pod1路由表,把数据包发送到cni0(10.0.14.1)
2. cni0查找host1路由,把数据包转发到flannel.1
3. flannel.1虚拟网卡再把数据包转发到它的驱动程序flannel
4. flannel程序使用VXLAN协议封装这个数据包,向api-server查询目的IP所在的主机IP,称为host2(不清楚什么时候查询)
5. flannel向查找到的host2 IP的UDP端口8472传输数据包
6. host2的flannel收到数据包后,解包,然后转发给flannel.1虚拟网卡
7. flannel.1虚拟网卡查找host2路由表,把数据包转发给cni0网桥,cni0网桥再把数据包转发给pod2
8. pod2响应给pod1的数据包与1-7步类似
下面是这次ping数据包的wireshark解析出的协议数据:
pod1 ping请求:
系统管理
pod2响应:
系统管理

pod1与pod2在同一台主机

pod1和pod2在同一台主机的话,由cni0网桥直接转发请求到pod2,不需要经过flannel。

pod到service的网络

创建一个service时,相应会创建一个指向这个service的域名,域名规则为{服务名}.{namespace}.svc.{集群名称}。之前service ip的转发由iptables和kube-proxy负责,目前基于性能考虑,全部为iptables维护和转发。iptables则由kubelet维护。service仅支持udp和tcp协议,所以像ping的icmp协议是用不了的,所以无法ping通service ip。
现在我们尝试看看在pod1向kube-dns的service ip 10.16.0.10:53发送udp请求是如何转发的。
我们先找出与此IP相关的iptables规则:

【PREROUTING链】
-m comment --comment "kubernetes service portals" -j KUBE-SERVICES

【KUBE-SERVICES链】
-d 10.16.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU

【KUBE-SVC-TCOU7JCQXEZGVUNU链】
-m comment --comment "kube-system/kube-dns:dns" -j KUBE-SEP-L5MHPWJPDKD7XIFG

【KUBE-SEP-L5MHPWJPDKD7XIFG链】
-p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 10.0.0.46:53
  1. pod1向service ip 10.16.0.10:53发送udp请求,查找路由表,把数据包转发给网桥cni0(10.0.14.1)
  2. 在数据包进入cnio网桥时,数据包经过PREROUTING链,然后跳至KUBE-SERVICES链
  3. KUBE-SERVICES链中一条匹配此数据包的规则,跳至KUBE-SVC-TCOU7JCQXEZGVUNU链
  4. KUBE-SVC-TCOU7JCQXEZGVUNU不做任何操作,跳至KUBE-SEP-L5MHPWJPDKD7XIFG链
  5. KUBE-SEP-L5MHPWJPDKD7XIFG里对此数据包作了DNAT到10.0.0.46:53,其中10.0.0.46即为kube-dns的pod ip
  6. 查找与10.0.0.46匹配的路由,转发数据包到flannel.1
  7. 之后的数据包流向就与上面的pod1到pod2的网络一样了

pod到外网

  1. pod向qq.com发送请求
  2. 查找路由表,转发数据包到宿主的网卡
  3. 宿主网卡完成qq.com路由选择后,iptables执行MASQUERADE,把源IP更改为宿主网卡的IP
  4. 向qq.com服务器发送请求

使用fluentd实现实时收集日志文件

目前线上服务使用了k8s进行部署,一个服务配置了多个副本,然后日志是挂载到宿主机器的目录的,所以当服务部署到三台机器时,这时要查看业务日志,就必须依次登录三台服务器来看日志。显然,这非常地不方便。团队想把日志收集到一个地方统一查看。于是开始尝试各种方案。

尝试

1. elasticsearch + fluentd + in_tail(input) + fluent-plugin-elasticsearch(output) + kibana

刚开始就测试使用网络上推荐的日志收集方案,elasticsearch + fluentd + kibana,部署完成后,经过使用,并不能很方便地对日志进行检索,因为日志格式非常多,不方便对日志进行格式化,所以收集过来的日志并不是结构化的。另一个原因是elasticsearch占用的CPU很高,这个不知道什么原因,可能给的资源不够或配置不当。不过更主要是团队成员希望最好是直接把日志收集到一台服务器,然后能够使用linux的工具,如grep,awk,less来查询日志,所以最终放弃此方案。

2. rsyslog + fluentd + in_tail(input) + fluent-plugin-remote_syslog(output)
然后开始尝试使用fluentd来收集日志并发送到rsyslog, rsyslog使用fluentd发送过来的tag来命令文件名,但由于syslog协议的限制,tag最大为32个字符,最终无奈放弃此方案。

3. fluentd-agent(input: in_tail, output: forward) fluentd-server(input: forward, ouput: fluent-plugin-forest)
最后采用agent和server端都使用fluentd,agent端的input使用in_tail,ouput使用forward,server端的input使用forward,ouput使用fluent-plugin-forest,找到fluent-plugin-forest这个插件不容易,因为它支持以tag命名文件名,并非常稳定,其它的插件由于不怎么更新了,bug挺多无法使用。

部署

server端

docker run -d  -p 24224:24224 -p 24224:24224/udp -v /var/log/worker:/var/log/worker -v /etc/localtime:/etc/localtime --name fluent-server registry.cn-hangzhou.aliyuncs.com/shengjing/fluent-server

agent端

在每个agent新建一个/home/fluent目录,并设置权限为777

mkdir -p /home/fluent
chmod 777 /home/fluent

这里我们使用k8s的damonset来部署

kubectl create -f fluentd-daemonset.yaml

fluentd-daemonset.yaml:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  template:
    metadata:
      labels:
        k8s-app: fluentd-logging
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: fluentd
        image: registry.cn-hangzhou.aliyuncs.com/shengjing/fluent-client
        imagePullPolicy: Always
        env:
          - name:  RSYSLOG_HOST
            value: "10.29.112.24"
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 500Mi
        volumeMounts:
        - name: datlog
          mountPath: /dat/log
          readOnly: true
        - name: fluent
          mountPath: /home/fluent
        - name: localtime
          mountPath: /etc/localtime
          readOnly: true

      terminationGracePeriodSeconds: 30
      volumes:
      - name: datlog
        hostPath:
          path: /dat/log
      - name: fluent
        hostPath:
          path: /home/fluent

      - name: localtime
        hostPath:
          path: /etc/localtime
  • 其中192.168.93.201为fluentd server的ip

Dockerfile

server端

Dockerfile:

FROM fluent/fluentd:v0.12-debian
COPY entrypoint.sh /bin/entrypoint.sh
RUN  fluent-gem install  fluent-plugin-forest 
     && chmod +x /bin/entrypoint.sh
COPY fluent.conf /fluentd/etc/

entrypoint.sh:

#!/usr/bin/dumb-init /bin/sh

uid=${FLUENT_UID:-1000}

# check if a old fluent user exists and delete it
cat /etc/passwd | grep fluent
if [ $? -eq 0 ]; then
    deluser fluent
fi

# (re)add the fluent user with $FLUENT_UID
useradd -u ${uid} -o -c "" -m fluent
export HOME=/home/fluent

# chown home and data folder
chown -R fluent /home/fluent
chown -R fluent /fluentd

gosu fluent "$@"

fluent.conf:

<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match log.**>
  @type forest
  subtype file
  <template>
    time_slice_format %Y%m%d
    path /var/log/worker/${tag_parts[3..-2]}
    format single_value
    flush_interval 2s
    buffer_path /tmp/buffer/${tag_parts[3..-2]}
    append true
    num_threads 1
  </template>
</match>

agent端

Dockerfile:

FROM fluent/fluentd:v0.12-debian
COPY entrypoint.sh /bin/entrypoint.sh
RUN  chmod +x /bin/entrypoint.sh
COPY fluent.conf /fluentd/etc/

entrypoint.sh:

#!/usr/bin/dumb-init /bin/sh

uid=${FLUENT_UID:-1000}

# check if a old fluent user exists and delete it
cat /etc/passwd | grep fluent
if [ $? -eq 0 ]; then
    deluser fluent
fi

# (re)add the fluent user with $FLUENT_UID
useradd -u ${uid} -o -c "" -m fluent
export HOME=/home/fluent

# chown home and data folder
chown -R fluent /home/fluent
chown -R fluent /fluentd

# replace FLUENTD_SERVER_HOST
sed -i "s/FLUENTD_SERVER_HOST/$FLUENTD_SERVER_HOST/" /fluentd/etc/fluent.conf

gosu fluent "$@"

fluent.conf:

<source>
  @type tail
  path /dat/log/**/*.log
  tag log.*
  format none
  refresh_interval 5
  read_from_head true
  limit_recently_modified 86400
  pos_file /home/fluent/dat-log.pos
</source>
<match log.**>
  @type forward
  <server>
    name myserver1
    host FLUENTD_SERVER_HOST
    port 24224
    weight 60
  </server>
  buffer_type file
  buffer_path /tmp/buffer_file
  flush_interval 2s
  buffer_chunk_limit 8m
  buffer_queue_limit 1000
  num_threads 4
</match>

参考

https://github.com/tagomoris/fluent-plugin-forest
http://docs.fluentd.org/v0.12/articles/in_tail
http://docs.fluentd.org/v0.12/articles/out_forward
http://docs.fluentd.org/v0.12/articles/in_forward
http://docs.fluentd.org/v0.12/articles/out_file

通过SSH隧道远程安全管理MySQL

本文将向您介绍如何使用SSH隧道从本地计算机安全连接到远程MySQL或MariaDB服务器。 如果要在本地计算机上使用管理工具连接服务器上的服务工作,这将非常有用。
遵循这些说明后,您可以使用您最喜欢的MySQL管理工具连接到工作站上的localhost。 连接将通过Internet安全地转发到您的服务器。

先决条件

MySQL已安装
MySQL配置为在localhost(127.0.0.1)上侦听。 默认情况下启用。

在Windows上使用PuTTY创建隧道

本节将介绍如何使用PuTTY工具在Windows上创建到MySQL的SSH隧道。

设置隧道

首先,你需要建立一个到你的服务器的基本连接:
1.下载PuTTY.
2.保存PuTTY到你桌面
3.双击打开PuTTY,不需要安装
4.在主机名(或IP地址)字段中输入服务器的主机名或IP地址。
5.在左侧菜单中,转到Connection – > SSH – > Tunnels。
6.在“Source port”字段中,输入3306。
7.在Destination字段中,输入127.0.0.1:3306。
8.单击打开以启动SSH会话。
9.将本地MySQL客户端指向localhost:3306。 您与远程MySQL服务器的连接将通过SSH加密,允许您访问您的数据库而不需要MySQL开放到互联网。

在Mac OS X或Linux上使用mysql-tunnel创建隧道

本节将介绍如何使用mysql-tunnel工具在Mac OS X或Linux上创建到MySQL的SSH隧道。
1.打开命令提示符,并运行以下命令打开SSH隧道:

  1. ssh -L 127.0.0.1:3306:127.0.0.1:3306 [email protected] -N

用您的SSH用户名和服务器的主机名或IP地址替换 *。 命令中的长字符串数字列出了本地IP,本地端口,远程IP和远程端口,以冒号(:**)分隔。
2.将本地MySQL客户端指向localhost:3306。 您与远程MySQL服务器的连接将通过SSH加密,允许您访问您的数据库而不需要MySQL开放到互联网。
3.当您准备关闭连接时,发出CTRL-C命令或关闭命令提示符窗口。 这将关闭SSH隧道。

持久SSH连接

如果您需要一个持久的SSH隧道,请考虑使用autossh(http://www.harding.motd.ca/autossh/)。 autossh启动和监视SSH连接,并在必要时重新启动。

使用MTR诊断网络问题

MTR是一个功能强大的网络诊断工具,能让系统管理员诊断和定位出网络错误和能提供给网络状态报告给上游提供商。MTR类似于traceroute+ping。

安装MTR

在Debian和Ubuntu系统,执行如下命令来更新系统软件仓库和升级已安装的软件包,最后安装MTR:

  1. apt-get update
  2. apt-get upgrade
  3. apt-get install mtr-tiny

对于CentOS和Fedora系统,你可以执行如下命令来升级已安装软件,并安装MTR程序:

  1. yum update
  2. yum install mtr

在Arch Linux系统,执行如下命令来更新软件包并安装MTR:

  1. pacman -Sy
  2. pacman -S mtr

你可能也需要在你的本地工作站来使用MTR诊断网络问题。如果你本地系统是Linux,你可以按照以上方法来安装MTR。
如果你的系统是Mac OS X,你可以使用Homebrew或者MacPorts安装MTR,使用Homebrew安装,执行如下命令:

  1. brew install mtr

使用MacPorts安装,命令为:

  1. port install mtr

如果你的桌面系统为Windows,可以使用WinMTR,下载链接为http://sourceforge.net/projects/winmtr/

在类Unix系统使用MTR

一旦在Linux或者Mac OS X安装好MTR,你可以使用如下语法来生成MTR报告:

  1. mtr -rw [destination_host]

例如,测试路由和到主机example.com的网络连接质量,在源主机执行如下命令:

  1. mtr -rw example.com

读取MTR报告

由于MTR报告提供了大量的信息,第一次可能会比较难阅读。下面是一个从你本地网络到google.com的MTR报告示例:

$ mtr –report google.com
HOST: example Loss% Snt Last Avg Best Wrst StDev
1. inner-cake 0.0% 10 2.8 2.1 1.9 2.8 0.3
2. outer-cake 0.0% 10 3.2 2.6 2.4 3.2 0.3
3. 68.85.118.13 0.0% 10 9.8 12.2 8.7 18.2 3.0
4. po-20-ar01.absecon.nj.panjde 0.0% 10 10.2 10.4 8.9 14.2 1.6
5. be-30-crs01.audubon.nj.panjd 0.0% 10 10.8 12.2 10.1 16.6 1.7
6. pos-0-12-0-0-ar01.plainfield 0.0% 10 13.4 14.6 12.6 21.6 2.6
7. pos-0-6-0-0-cr01.newyork.ny. 0.0% 10 15.2 15.3 13.9 18.2 1.3
8. pos-0-4-0-0-pe01.111eighthav 0.0% 10 16.5 16.2 14.5 19.3 1.3
9. as15169-3.111eighthave.ny.ib 0.0% 10 16.0 17.1 14.2 27.7 3.9
10. 72.14.238.232 0.0% 10 19.1 22.0 13.9 43.3 11.1
11. 209.85.241.148 0.0% 10 15.1 16.2 14.8 20.2 1.6
12. lga15s02-in-f104.1e100.net 0.0% 10 15.6 16.9 15.2 20.6 1.7

生成这个报告的命令为mtr –report google.com。这个命令使用了report选项为向google.com发送10个数据包然后生成报告。没有–report选项的话,mtr会一直运行。

使用Iperf诊断网络速度

安装Iperf

Debian和Ubuntu

可以在Debian和Ubuntu使用apt-get来安装

  1. apt-get install iperf

CentOS

CentOS官方源没有Iperf。我们可以使用RPMForge源来安装,不过安装前需要把这个源安装到系统里。
CentOS 7:

  1. wget http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el7.rf.x86_64.rpm
  2. rpm -Uvh rpmforge-release-1.5.3-1.el7.rf.x86_64.rpm
  3. yum update
  4. yum install iperf

CentOS 6

  1. wget http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-1.el6.rf.x86_64.rpm
  2. rpm -Uvh rpmforge-release-0.5.2-1.el6.rf.x86_64.rpm
  3. yum update
  4. yum install iperf

Fedora:

  1. yum update
  2. yum install iperf

Arch Linux:

  1. pacman -S iperf

Gentoo:

  1. emerge iperf

使用Iperf

Iperf需要在你要测试连接的两台服务器上安装。

TCP Clients & Servers

Iperf需要在两台机器分别扮演客户端和服务端角色。客户端来连接你要测试网速的服务器。
1.在你要测的服务器上启动服务器模式:

  1. iperf -s

将输出如下:

————————————————————
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
————————————————————

2.在你另一台机器,连接上一台机器,把198.51.100.5替换为以上的机器IP

  1. iperf -c 198.51.100.5

类似输出如下:

————————————————————
Client connecting to 198.51.100.5, TCP port 5001
TCP window size: 45.0 KByte (default)
————————————————————
[ 3] local 198.51.100.6 port 50549 connected with 198.51.100.5 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 142 MBytes 119 Mbits/sec

3.在服务端将看到如下输出:

————————————————————
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
————————————————————
[ 4] local 198.51.100.5 port 5001 connected with 198.51.100.6 port 50549
[ ID] Interval Transfer Bandwidth
[ 4] 0.0-10.2 sec 142 MBytes 117 Mbits/sec

4.CTRL + c来停止Iperf进程

UDP Clients & Servers

使用Iperf,你也可以来测试UDP连接的最大吞吐量
1.启动UDP Iperf服务器

  1. iperf -s -u

输出如下:

————————————————————
Server listening on UDP port 5001
Receiving 1470 byte datagrams
UDP buffer size: 208 KByte (default)
————————————————————

2.在客户端连接以上的服务器,替换198.51.100.5为你自己的

  1. iperf -c 198.51.100.5 -u

-u选择表示告诉Iperf使用UDP连接。输出如下:

————————————————————
Client connecting to 198.51.100.5, UDP port 5001
Sending 1470 byte datagrams
UDP buffer size: 208 KByte (default)
————————————————————
[ 3] local 198.51.100.6 port 58070 connected with 198.51.100.5 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec
[ 3] Sent 893 datagrams
[ 3] Server Report:
[ 3] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec 0.084 ms 0/ 893 (0%)

从输出我们可以看到1.05 Mbits/sec比我们收到数据的速度要小。这是因为Iperf默认限制了UDP客户端为1Mbit每秒。
3.你可以使用-b选项来更改,如:

  1. iperf -c 198.51.100.5 -u -b 150m

输出如下:

———————————————————–
Client connecting to 198.51.100.5, UDP port 5001
Sending 1470 byte datagrams
UDP buffer size: 208 KByte (default)
————————————————————
[ 3] local 198.51.100.6 port 41083 connected with 198.51.100.5 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 145 MBytes 122 Mbits/sec
[ 3] Sent 103625 datagrams
[ 3] Server Report:
[ 3] 0.0-10.3 sec 136 MBytes 111 Mbits/sec 13.488 ms 6464/103623 (6.2%)

为什么Wireshark无法解密HTTPS数据

问题

由于需要定位一个问题,在服务器上tcpdump抓取https数据包,然后下载到本地打开wireshark分析。然后我们下载域名私钥配置到wireshark,发现数据包居然无法解密。是wireshark配置密钥的方法不对?但谷歌了好多文章都是说这样配置的。由于对HTTPS认识不够深,一时不知道如何入手解决。没办法,只能先了解tls这个协议了,于是查看了TLS1.2的RFC文档,终于勉强解答了这个疑惑。

TLS握手整个过程

在解决这个问题之前,先整体了解一下TLS的握手全过程。省略了不常见的过程。如图:
系统管理
下面按顺序介绍各握手步骤。

Client Hello

这是TLS握手的第一步,由客户端发起请求。此协议主要包括了一个客户端生成的随机字符串(用来下面生成session key),还有客户端支持的加密套件列表。如图:
系统管理

Server Hello

服务器收到客户端的Client Hello数据包之后,根据客户端发来的加密套件列表,选择一个加密套件,也生成一个随机字符串返回给客户端。我们看到下图中的加密套件为,密钥交换算法使用ECDHE_RSA,对称加密算法使用AES_256_GCM_SHA384,如图:
系统管理

Server Certificate

接着服务器再返回证书列表,包括证书链及域名证书。返回的证书用来给客户端验证当前连接服务器的身份,防止中间人攻击。

Server Key Exchange

Server Key Exchange协议包,由服务器返回,主要目的是与客户端交换用于数据对称加密的密钥。如图:
系统管理

Server Hello Done

服务器返回此协议数据,告诉客户端已经完成返回所需用于密钥交换的数据。服务器等待客户端响应。

Client Key Exchange

客户端根据服务器返回的DH密钥数据生成DH公共数据也发给服务器,用来生成最终的pre-master-secret。如图:
系统管理

Change Cipher Spec

此协议用于客户端和服务器相互告知也完成密钥交换过程,可以切换到对称加密过程。

到这里大概的TLS握手过程就结束了。为解决本文中的问题,还需要了解密钥交换的算法,RSA和Diffie–Hellman。

密钥交换算法

密钥交换算法目前常用的有RSA和Diffie-Hellman。
对于密钥交换使用RSA算法,pre-master-secret由客户端生成,并使用公钥加密传输给服务器。
对于密钥交换使用Diffie-Hellman算法,pre-master-secret则通过在Key Exchange阶段交换的信息,由各自计算出pre-master-secret。所以pre-master-secret没有存到硬盘,也没有在网络上传输,wireshark就无法获取session key,也就无法解密应用数据。那我们是否可以反向计算出pre-master-secret呢?理论上可以,但是非常困难。
对Diffie-Hellman算法感兴趣的可以参考https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange

解决方法

说了这么多,究竟有什么办法可以让wireshark解密数据?我们可以通过下面几种方法来使wireshark能解密https数据包。

  • 1. 中间人攻击;
  • 2. 设置web服务器使用RSA作为交换密钥算法;
  • 3. 如果是用chrome,firefox,可以设置导出pre-master-secret log,然后wireshark设置pre-master-secret log路径,这样就可以解密了。

inode占用100%时硬盘无法写入文件故障处理

故障现象

 

分区无法写入文件。

 

故障分析

 

执行df -h命令发现空间占用不到70%,执行df -hi,发现某分区IUse%值为100%,说明inode已经用完,应该是某些目录下存在大量的小文件导致。

 

解决方法

 

大量小文件分布有两种可能,一是只有一个或少量目录下存在大量小文件,这种情况我们可以使用如下命令来找出这个异常目录:

  1. find / -type d -size +10M

此命令作用是找出大小大于10M的目录(目录大小越大,表示目录下的文件越多)。
第二种可能是,大量的小文件分布在大量的目录下,这时候上面的命令可能找不出异常的目录,需要以下命令:

  1. cd /
  2. find */ ! -type l | cut -d / -f 1 | uniq -c

此命令作用是找出目录下文件总数,可能需要执行多次,直到找出具体的目录。比如上面的命令找出了/data目录下存在大量的小文件,但/data/目录还有很多目录,这时候我们还需要继续执行:

  1. cd /data
  2. find */ ! -type l | cut -d / -f 1 | uniq -c

直到找出具体的目录。

 

故障总结

 

对inode占用进行监控,并且收到inode告警时应及时使用以上方法来定位问题,并反馈给相应人员从根源解决。