LJKのBlog

学无止境

补充知识:iSCSI

概括的说,iSCSI 是一种存储设备远程映射技术,它可以将一个远程服务器上的存储设备映射到本地,并呈现为一个块设备(大白话就是磁盘)。从普通用户的角度,映射过来的磁盘与本地安装的磁盘毫无差异。

这种映射方式基于是基于 SCSI 协议的,SCSI 协议是计算机与外围设备(例如硬盘、光盘等)通信的协议。而 iSCSI 则是通过 TCP 协议对 SCSI 进行封装的一种协议,也就是通过以太网传输 SCSI 协议的内容。

存储类型

存储类型分为三类:

  • 直连式存储:Direct-Attached Storage,简称 DAS

  • 网络附加存储:Network-Attached Storage,简称 NAS

  • 存储区域网络:Storage Area Network,简称 SAN

    FC:Fibre Channel 光纤

三种存储比较

三种存储应用场景

  • DAS 虽然比较古老了,但是还是很适用于那些数据量不大,对磁盘访问速度要求较高的中小企业
  • NAS 多适用于文件服务器,用来存储非结构化数据,虽然受限于以太网的速度,但是部署灵活,成本低
  • SAN 则适用于大型应用或数据库系统,缺点是成本高、较为复杂

FTP 服务

文件传输协议:File Transfer Protocol,基于 C/S 结构

数据传输格式:二进制(默认)和文本

双通道协议:命令和数据传输使用不同的端口

两种模式:从服务器角度命令 主动被动 模式

  • 主动模式:Port Style

    windows 连接 FTP 服务器默认使用主动模式

  • 被动模式:PASV Style

FTP 服务状态码:

1
2
3
4
5
1XX:信息   125:数据连接打开
2XX:成功类状态 200:命令OK 230:登录成功
3XX:补充类 331:用户名OK
4XX:客户端错误 425:不能打开数据连接
5XX:服务器错误 530:不能登录

用户认证:

1
2
3
匿名用户:ftp、anonymous,对应Linux用户ftp
系统用户:Linux用户、用户/etc/passwd,密码/etc/shadow
虚拟用户:特定服务的专用用户、独立的用户/密码文件

常见 FTP 相关软件:

  • FTP 服务器端软件

    Wu-ftpd、Proftpd、Pureftpd、Filezilla Server、Serv-U、Wing FTP Server、IIS

    vsftpd:Very Secure FTP Daemon,CentOS 默认 FTP 服务器

  • 客户端软件

    ftp、lftp、lftpget、wget、curl

vsftpd

1

pureftpd

1

NFS 服务

工作原理

Network File System:网络文件系统,基于内核的文件系统。通过 NFS,用户和程序可以像访问本地文件一样去访问远端系统上的文件,基于 RPC(Remote Procedure Call Protecol)实现。

上图中的 portmap,自 centos6 之后就被 rpcbind 代替了

优点:节省本地存储空间,将常用的数据,如:/home 目录,存放在 NFS 服务器上且可以通过网络访问,本地终端将可减少自身存储空间的使用

缺点:1. 占用带宽;2. 端口号比较多,而且不固定,防火墙不太好配;所以 nfs 一般不用在互联网,都是用在局域网

nfs 软件介绍

软件包:nfs-utils(包括服务器和客户端相关工具)

相关软件包:rpcbind(必须)、tcp_wrappers

kernel 支持:nfs.ko

端口:2049(nfsd),其他端口由 portmap 分配

nfs 服务主要进程:

  • rpc.nfsd:最主要的 NFS 进程,管理客户端是否可登录
  • rpc.mountd:挂载和卸载 NFS 文件系统,包括权限管理
  • rpc.lockd:非必要,管理文件锁,避免同时写出错
  • rpc.statd:非必要,检查文件一致性,可修复文件

日志:/var/lib/nfs

配置文件:/etc/exports 和 /etc/exports.d/*.exports

nfs 配置文件

1
/dir host1(opt1,opt2)	host2(opt1,opt2)
  • host 格式:支持通配符 *、ipv4、ipv6、FQDN(全域名)、NIS 域的主机组

  • opt 格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    默认选项:(ro,sync,root_squash,no_all_squash)
    ro:只读
    rw:可读可写
    async:异步,数据变化后不立即写磁盘,先写入到缓冲区中,过一段时间再写入磁盘,性能高,安全性低
    sync:1.0.0后为默认,同步,数据在请求时立即写入共享存储磁盘,性能低,安全性高
    root_squash:默认,远程root映射为nobody(CentOS7以前的版本为nfsnobody),UID为65534
    no_root_squash:远程root映射成NFS服务器的root用户,squash的意思是压榨
    all_squash:所有远程用户(包括root)都被压榨
    no_all_squash:默认,保留共享文件的UID和GID,即不压榨
    anonuid和anongid:指明匿名用户映射为特定用户UID和组GID,而非nobody,可配合all_squash使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

默认把root用户压榨成nobody用户,普通用户不压榨,anonuid和anongid可以设置默认压榨成哪个用户

范例:

​```bash
# vim /etc/exports

/data/script 10.0.0.0/24(ro,async)
/data/script 10.0.0.0/24(rw,async,root_squash,anonuid=1000,anongid=1000,all_squash)

/myshare server.example.com
/myshare *.example.com
/myshare server?.example.com
/myshare server[0-20].example.com
/myshare 172.25.11.10
/myshare 172.25.0.0/16
/myshare 2000:472:18:b51:c32:a21
/myshare 2000:472:18:b51::/64
/myshare *.example.com 172.25.0.0/16
/myshare desktop.example.com(ro)
/myshare desktop.example.com(ro) server[0-20].example.com(rw)
/myshare diskless.example.com(rw,no_root_squash)

nfs 工具

fpcinfo

rpcinfo 工具可以查看 RPC 相关信息

1
2
3
4
5
# 查看注册在指定主机的RPC程序
rpcinfo -p hostname

# 查看RPC注册程序
rpcinfo -s hostname

范例:

1
2
3
4
[root@centos8 ~]#rpcinfo -p
[root@centos8 ~]#rpcinfo -s
[root@centos7 ~]#rpcinfo -p 10.0.0.8 # 查看远程主机
[root@centos7 ~]#rpcinfo -s 10.0.0.8

exportfs

用于管理 NFS 导出的文件系统

1
2
3
4
5
6
exportfs [-avi] [-o options,..] [client:/path ..]
exportfs -r [-v]
exportfs [-av] -u [client:/path ..]
exportfs [-v]
exportfs -f
exportfs -s
  • -v:查看本机所有 NFS 共享
  • -r:重读配置文件,这个选项可以避免重启服务
  • -a:输出本机所有共享
  • -au:停止本机所有共享

showmount

常见用法:查看远程主机的 nfs 共享

1
showmount -e hostname

范例:

1
2
3
[root@centos7 ~]#showmount -e 10.0.0.8
Export list for 10.0.0.8:
/data/wordpress *

monut

客户端 nfs 挂载

1
mount [-fnrsvw] -t nfs [-o options] device dir

options:

  • nfsvers=n
  • vers=n
  • soft / hard

范例:

1

自动挂载 autofs

使用 autofs 服务按需要挂载外围设备,NFS 共享等,并在空闲 5 分钟后后自动卸载

配置文件:/etc/auto.master

案例:

10.0.0.1(ubuntu):nfs server,共享/data/wwwroot/script 目录

10.0.0.71(centos):nfs client

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
# 10.0.0.1
# 安装nfs服务端软件
lujinkai@Z510:~$ sudo apt -y install nfs-kernel-server
# 修改exports共享设置文件
lujinkai@Z510:~$ vim /etc/exports
/data/wwwroot/script 10.0.0.0/24(rw,async,root_squash,anonuid=1000,anongid=1000,all_squash)
# 重新加载配置文件
lujinkai@Z510:~$ sudo exportfs -r

# 10.0.0.71
# 安装autofs,并设置开机自启
[root@c71 ~]$yum -y install autofs
[root@c71 ~]$systemctl enable --now autofs.service
# 查看10.0.0.1共享的目录
[root@c71 ~]$showmount -e 10.0.0.1
Export list for 10.0.0.1:
/data/wwwroot/script 10.0.0.0/24
# 修改auto.master,注意格式,`man auto.master`可以查看格式
[root@c71 ~]$vim /etc/auto.master
...
/root/www /etc/auto.www
# 编辑/root/www指定的配置文件
[root@c71 ~]$vim /etc/auto.www
script -fstype=nfs 10.0.0.1:/home/lujinkai/www/script
# 重启autofs
[root@c71 ~]$systemctl restart autofs.service
# 查看是否挂载成功
[root@c71 ~]$cd ~/www/
[root@c71 www]$ll # 没有任何东西
总用量 0
[root@c71 www]$df -Th ./
文件系统 类型 容量 已用 可用 已用% 挂载点
/etc/auto.www autofs 0 0 0 - /root/www
[root@c71 www]$cd script # 直接进入,就会自动挂载
[root@c71 script]$ll
总用量 48
-rwxrwxrwx 1 lujinkai lujinkai 688 11月 13 21:54 demo.sh
drwxrwxrwx 2 lujinkai lujinkai 4096 11月 12 10:09 etc
drwxrwxrwx 4 lujinkai lujinkai 4096 11月 12 16:30 include
drwxrwxrwx 2 lujinkai lujinkai 4096 11月 12 19:34 ini
drwxrwxrwx 2 lujinkai lujinkai 4096 11月 11 11:08 init.d
-rwxrwxrwx 1 lujinkai lujinkai 3750 10月 31 20:11 install.sh
-rwxrwxrwx 1 lujinkai lujinkai 2051 10月 31 15:52 magedu.sh
-rwxrwxrwx 1 lujinkai lujinkai 1462 10月 28 15:58 scp_list.sh
-rwxrwxrwx 1 lujinkai lujinkai 1744 11月 11 10:54 scp_sh.sh
-rwxrwxrwx 1 lujinkai lujinkai 1440 10月 13 21:57 sftp-config.json
drwxrwxrwx 3 lujinkai lujinkai 4096 11月 11 11:03 src
-rwxrwxr-x 1 lujinkai lujinkai 1211 11月 11 10:55 u_scp_sh.sh
[root@c71 script]$df -Th ./
文件系统 类型 容量 已用 可用 已用% 挂载点
10.0.0.1:/home/lujinkai/www/script nfs4 117G 37G 74G 34% /root/www/script

/etc/fstab 和 autofs

/etc/fstab 设置开机自动挂载,系统开机会去读取这个文件,用于挂载本地固定设备,如硬盘

autofs 设置自动挂载,只有当访问挂载点的时候,autofs 才会挂载,过一段时间没有访问,autofs 就会自动卸载设备,用于挂载动态的设备,如光盘、nfs、smb 等文件系统

SAMBA 服务

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

相关包:

  • samba 提供 smb 服务器端
  • samba-client 客户端软件
  • samba-common 通用软件
  • cifs-utils smb 客户端工具
  • samba-winbind 和 AD 相关

相关服务进程:

  • smbd 提供 smb(cifs)服务 TCP:139、445
  • nmbd NetBIOS 名称解析 UDP:137、138,如果不使用计算机名来访问,这个就没用

主配置文件:/etc/samba/smb.conf

语法检查: testparm [-v] [/etc/samba/smb.conf]

客户端工具:smbclient、mount.cifs

samba 服务器配置

/etc/samba/smb.conf 是主配置文件,ini 格式,来自 samba-common 包

分为全局配置[global] 和特定的共享设置,例如[homes]、[printers]等\

smb.conf 中的宏定义

1
2
3
4
5
6
7
8
9
10
11
%m 客户端主机的NetBIOS名
%M 客户端主机的FQDN
%H 当前用户家目录路径
%U 当前用户的用户名
%g 当前用户所属组
%h samba服务器的主机名
%L samba服务器的NetBIOS名
%I 客户端主机的IP,是i的大写字母
%T 当前日期和时间
%S 可登录的用户名
...

[global] 全局配置

  • workgroup:指定工作组名称

  • server string:主机注释信息

  • netbios name:指定 NetBIOS 名,可以被 SAMBA 客户端使用,但不支持 ping

    注意:netbios name 需要启动 nmb 服务

    范例:

    1
    2
    3
    [global]
    workgroup = workgroup
    netbios name = smbserver # 此设置需要启动nmb服务才可能生效
  • interfaces:指定服务侦听接口和 IP

  • hosts allow: 允许指定主机访问,可用逗号,空格,或 tab 分隔,默认允许所有主机访问,可以在其他共享独立配置,但是[global]中的设置会覆盖其他共享设置,可以是以下格式:

    1
    2
    3
    4
    172.16.0.0/24
    172.16.0.0/255.255.255.0
    desktop.example.com
    .example.com # 以example.com后缀的主机名
  • hosts deny:拒绝指定主机访问,格式和 hosts allow 相同

  • config file=/etc/samba/conf.d/%U:用户独立的配置文件

  • log file=/var/log/samba/log.%I:不同客户机采用不同日志

  • log level = 2:日志级别,默认为 0,不记录日志

    范例:

    1
    2
    3
    [global]
    Log file=/var/log/samba/log.%I
    log level = 2
  • max log size=50:日志文件达到 50K,将轮循 rotate,单位 KB

  • security=user:认证方式,有三种

    • user:samba 用户(采有 linux 用户,samba 的独立口令)
    • share:匿名(CentOS7 不再支持),已不建议使用
    • server:已不建议使用
  • passdb backend = tdbsam:密码数据库格式

特定共享目录配置

1
2
3
4
5
6
7
8
9
10
11
[共享名称]    # 远程网络看到的共享名称
comment # 注释信息
path # 所共享的目录路径
public # 能否被guest访问的共享,默认no,和guest=ok 类似
browsable # 是否允许所有用户浏览此共享,默认为yes,no为隐藏
writable=yes # 可以被所有用户读写,默认为no
read only=no # 和writable=yes等价,如与以上设置冲突,放在后面的设置生效,默认只读
write list # 可读写的用户,可设置为用户或者组,用空格或逗号分隔,用户组有两种格式:@组名、+组名;如果设置writable=no,那write list中的用户和组可读写
valid users # 规定合法用户,即能访问该共享的用户,用空格或逗号分隔,如为空,将允许所有
force group # 指定存取资源时须以此设定的群组使用者进入才能存取(用户名/@组名)
force user # 指定存取资源时须以此设定的使用者进入才能存取(用户名/@组名)

范例:

1
2
3
4
5
[share]
path = /data/dir
valid users=wang,@admins
writeable = no
browseable = no

samba 用户管理

smbpasswd 命令

smbpasswd 用于管理 samba 用户

添加用户:实际上是把把系统用户映射为 samba 用户,所以添加的用户首先必须是 linux 系统用户

1
2
smbpasswd -a <user>  # 交互式
pdbedit -a -u <user>

修改用户密码:

1
smbpasswd <user> # 交互式

删除用户和密码:

1
2
smbpasswd -x <user>
pdbedit -x -u <user> # 这个好像权限更大

查看 samba 用户列表:

1
pdbedit -L -v

范例:

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
# 为了方便配置,可以将用户指定同一个组
[root@c71 ~]$ groupadd -r smb
[root@c71 ~]$useradd -G smb -s /sbin/nologin smb1
[root@c71 ~]$useradd -G smb -s /sbin/nologin smb2

[root@c71 ~]$smbpasswd -a smb1
New SMB password:
Retype new SMB password:
[root@c71 ~]$smbpasswd -a smb2
New SMB password:
Retype new SMB password:
Added user smb2.

[root@c71 ~]$pdbedit -L
smb1:1001:
smb2:1002:
[root@c71 ~]$pdbedit -L -v
---------------
Unix username: smb1
NT username:
Account Flags: [U ]
User SID: S-1-5-21-540276018-2794668495-3753694806-1000
Primary Group SID: S-1-5-21-540276018-2794668495-3753694806-513
Full Name:
Home Directory: \\c71\smb1
HomeDir Drive:
Logon Script:
Profile Path: \\c71\smb1\profile
Domain: C71
Account desc:
Workstations:
Munged dial:
Logon time: 0
Logoff time: Wed, 06 Feb 2036 23:06:39 CST
Kickoff time: Wed, 06 Feb 2036 23:06:39 CST
Password last set: Sat, 14 Nov 2020 17:46:18 CST
Password can change: Sat, 14 Nov 2020 17:46:18 CST
Password must change: never
Last bad password : 0
Bad password count : 0
Logon hours : FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
---------------
Unix username: smb2
NT username:
Account Flags: [U ]
User SID: S-1-5-21-540276018-2794668495-3753694806-1001
Primary Group SID: S-1-5-21-540276018-2794668495-3753694806-513
Full Name:
Home Directory: \\c71\smb2
HomeDir Drive:
Logon Script:
Profile Path: \\c71\smb2\profile
Domain: C71
Account desc:
Workstations:
Munged dial:
Logon time: 0
Logoff time: Wed, 06 Feb 2036 23:06:39 CST
Kickoff time: Wed, 06 Feb 2036 23:06:39 CST
Password last set: Sat, 14 Nov 2020 17:46:24 CST
Password can change: Sat, 14 Nov 2020 17:46:24 CST
Password must change: never
Last bad password : 0
Bad password count : 0
Logon hours : FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

案例:

实现不同 samba 用户访问相同的 samba 共享,实现不同的配置

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
# 10.0.0.71 服务端
[root@c71 ~]$yum -y install samba
[root@c71 ~]$groupadd -r smb
[root@c71 ~]$useradd -G smb -s /sbin/nologin smb1 # 注意这里是-G,而不是-g
[root@c71 ~]$useradd -G smb -s /sbin/nologin smb2
[root@c71 ~]$smbpasswd -a smb1
[root@c71 ~]$smbpasswd -a smb2
[root@c71 ~]$vim /etc/samba/smb.conf
#在workgroup下加一行
config file= /etc/samba/conf.d/%U
[share]
Path=/data/dir
Read only= NO
Guest ok = yes
write list=@wheel
#针对smb1和smb2用户创建单独的配置文件
[root@c71 ~]$vim /etc/samba/smb.conf/smb1
[share]
Path=/data/dir1
Read only= NO # 等价于writable = yes
Create mask=0644 # 说明:默认为744
[root@c71 ~]$vim /etc/samba/smb.conf/smb2
[share]
path=/data/dir2
Read only= NO
Create mask=0644
# 重启服务
[root@c71 ~]$systemctl restart smb.service

# 10.0.0.72 客户端
# 用户smb1,smb2访问share共享目录,看到目录是不同目录
[root@c72 ~]$yum -y install samba-client cifs-utils
[root@c72 ~]$smbclient //10.0.0.73/share -U smb1%123456
[root@c72 ~]$smbclient //10.0.0.73/share -U smb2%123456

# windows:文件管理器输入 \\10.0.0.73\share 回车,然后输入账号密码即可

数据的实时同步

实现实时同步的方法:

  • inotify + rsync
  • sersync:国人周洋在 inotify 基础上开发的,功能强大,只是已经不再更新了

inotify

异步的文件系统事件监控机制,利用事件驱动机制,而无须通过诸如 cron 等的轮询机制来获取事件,linux 内核从 2.6.13 起支持 inotify,通过 inotify 可以监控文件系统中添加、删除,修改、移动等各种事件

inotify+rsync 使用方式:

  1. 利用监控服务(inotify),监控同步数据服务器目录中信息的变化
  2. 发现目录中数据产生变化,就利用 rsync 服务推送到备份服务器上
  3. 利用脚本进行结合

inotify 内核参数

  • max_queued_events:inotify 事件队列最大长度,如值太小会出现 Event Queue Overflow 错误,默认值:16384, 生产环境建议调大,比如:327679
  • max_user_instances:每个用户创建 inotify 实例最大值,默认值:128
  • max_user_watches:可以监视的文件的总数量(inotifywait 单进程),默认值:8192,建议调大
1
2
3
4
5
6
7
8
9
10
11
[root@data-centos8 ~]#vim /etc/sysctl.conf
fs.inotify.max_queued_events=66666
fs.inotify.max_user_watches=100000
[root@centos8 ~]#sysctl
-p
fs.inotify.max_queued_events = 66666
fs.inotify.max_user_watches = 100000
[root@centos8 ~]#cat /proc/sys/fs/inotify/*
66666
128
100000

inotify-tools

1
2
3
4
5
6
7
8
[root@c71 ~]$yum -y install inotify-tools

[root@c71 ~]$rpm -ql inotify-tools
# 在被监控的文件或目录上等待特定文件系统事件(open、close、delete等)发生,常用于实时同步的目录监控
/usr/bin/inotifywait
# 收集被监控的文件系统使用的统计数据,指文件系统事件发生的次数统计
/usr/bin/inotifywatch
...
inotifywait
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
inotifywait [ options ] file1 [ file2 ] [ file3 ] [ ... ]

# 常用option
-m, --monitor: 始终保持事件监听
-d, --daemon: 以守护进程方式执行,和-m相似,配合-o使用
-r, --recursive: 递归监控目录数据信息变化
-q, --quiet: 输出少量事件信息
--exclude <pattern>: 指定排除文件或目录,使用扩展的正则表达式匹配的模式实现
--excludei <pattern>: 和exclude相似,不区分大小写
-o, --outfile <file>: 打印事件到文件中,相当于标准正确输出,注意:使用绝对路径
-s, --syslogOutput: 发送错误到syslog相当于标准错误输出
--timefmt <fmt>: 指定时间输出格式
--format <fmt>: 指定的输出格式;即实际监控输出内容
-e: 指定监听指定的事件,如果省略,表示所有事件都进行监听

--timefmt "%Y-%m-%d %H:%M:%S"
%Y # 年份信息,包含世纪信息
%y # 年份信息,不包括世纪信息
%m # 显示月份,范围 01-12
%d # 每月的第几天,范围是 01-31
%H # 小时信息,使用 24小时制,范围 00-23
%M # 分钟,范围 00-59
%S # 秒,范例 0-60

--format "%T %w%f event: %;e"
%T # 输出时间格式中定义的时间格式信息,通过 --timefmt option 语法格式指定时间信息
%w # 事件出现时,监控文件或目录的名称信息,相当于dirname
%f # 事件出现时,将显示监控目录下触发事件的文件或目录信息,否则为空,相当于basename
%e # 显示发生的事件信息,不同的事件默认用逗号分隔
%Xe # 显示发生的事件信息,不同的事件指定用X进行分隔

-e create,delete,moved_to,close_write,attrib
create #文件或目录创建
delete #文件或目录被删除
modify #文件或目录内容被写入
attrib #文件或目录属性改变
close_write #文件或目录关闭,在写入模式打开之后关闭的
close_nowrite #文件或目录关闭,在只读模式打开之后关闭的
close #文件或目录关闭,不管读或是写模式
open #文件或目录被打开
lsdir #浏览目录内容
moved_to #文件或目录被移动到监控的目录中
moved_from #文件或目录从监控的目录中被移动
move #文件或目录不管移动到或是移出监控目录都触发事件
access #文件或目录内容被读取
delete_self #文件或目录被删除,目录本身被删除
unmount #取消挂载

范例:

1
2
3
4
5
6
7
8
9
10
11
# 监控一次性事件
inotifywait /data/www

# 持续前台监控
inotifywait -mrq /data/www --exclude=".*\.swx|\.swp"

# 持续后台监控,并记录日志
inotifywait -o /root/inotify.log -drq /data/www --timefmt "%Y-%m-%d %H:%M:%S" --format "%T %w%f event: %e"

#持续前台监控特定事件
inotifywait -mrq /data/www --timefmt "%F %H:%M:%S" --format "%T %w%f event:%;e" -e create,delete,moved_to,close_write,attrib
inotifywatch
1
inotifywatch [ options ] file1 [ file2 ] [ ... ]

rsync

rsync 常用做镜像备份和同步数据,配合计划任务实现定时备份,配合 inotify 可以实现触发式的实时数据同步

官方网站: http://rsync.samba.org/

软件包:rsync、rsync-daemon(CentOS 8)

服务文件:/usr/lib/systemd/system/rsyncd.service

配置文件:/etc/rsyncd.conf

端口:873/tcp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 源地址 和 目标地址 都在本机
rsync [OPTION]... SRC [SRC]... DEST

# 2. 通过远程shell连接
rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST # pull 推
rsync [OPTION]... [USER@]HOST:SRC [DEST] # push 拉

# 3. 连接到rsync守护进程
rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST # pull 推
rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST # pull 推
rsync [OPTION]... [USER@]HOST::SRC [DEST] # push 拉
rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST] # push 拉

The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect to an rsync daemon, and require SRC or DEST to start with a module name.

前两种的本质是通过本地或远程 shell,而第 3 种方式则是让远程主机上运行 rsyncd 服务,使其监听在一个端口上,等待客户端的连接

范例:备份服务器启动 rsync 的守护进程,然后数据服务器去连接,再进行数据的推送和拉取

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
# 备份服务器 10.0.0.71
[root@c71 ~]$systemctl start rsyncd.service
[root@c71 ~]$ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 5 *:873 *:*
LISTEN 0 5 [::]:873 [::]:*

[root@c71 ~]$vim /etc/rsyncd.conf
...
[backup] # bachup 模块
path = /data/backup # 备份路径
read only = no # 设置可写,默认只读

# 查看设置的模块
[root@c71 ~]$rsync 127.0.0.1::
backup
[root@c71 ~]$rsync rsync://127.0.0.1
backup

# 默认用户以nobody访问此目录,所以给nobody设置读写权限
[root@c71 ~]$setfacl -m u:nobody:rwx /data/backup/

# 数据服务器 10.0.0.1
lujinkai@Z510:~$ rsync rsync://10.0.0.71
backup
lujinkai@Z510:~$ rsync 10.0.0.71::
backup

# 推送,将本机的u_script目录备份到10.0.0.71的/data/backup目录(rsyncd.conf中backup模块设置)
lujinkai@Z510:~/www$ rsync -av ./u_script/ root@10.0.0.71::backup
lujinkai@Z510:~/www$ rsync 10.0.0.71::backup
drwxrwxr-x 4,096 2020/11/14 22:38:48 .
-rwxrwxr-x 319 2020/11/14 22:38:48 demo.sh
drwxrwxr-x 4,096 2020/11/09 14:52:39 include
# 拉取
lujinkai@Z510:~/www$ rsync -av root@10.0.0.71::backup ./u_script/

rsyncd.conf 的格式 参考 man rsyncd.conf 或者 https://linux.die.net/man/5/rsyncd.conf

上面的过程都是免密的,我们也可以设置认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 备份服务器 10.0.0.71
[root@c71 ~]$echo "rsyncuser:123456" > /etc/rsync.pas
[root@c71 ~]$chmod 600 /etc/rsync.pas
[root@c71 ~]$vim /etc/rsyncd.conf
...
[backup]
path = /data/backup
read only = no
auth users = rsyncuser # 默认anonymous可以访问rsync服务器,这里设置只能虚拟用户rsyncuser访问
secrets file = /etc/rsync.pas # 认证文件
...
[root@c71 ~]$systemctl restart rsyncd.service # 也可以不重启

# 数据服务器 10.0.0.1
# 将秘密写入到/etc/rsync.pas,也可以赋值给环境变量 export RSYNC_PASSWORD=123456
lujinkai@Z510:~/www$ echo 123456 > /etc/rsync.pas
# 测试,注意这里一定要指定用户名 rsyncuser
lujinkai@Z510:~/www$ rsync rsyncuser@10.0.0.71::backup
Password:
drwxrwxr-x 4,096 2020/11/14 22:38:48 .
-rwxrwxr-x 319 2020/11/14 22:38:48 demo.sh
drwxrwxr-x 4,096 2020/11/09 14:52:39 include

inotify+rsync+shell 脚本 实现实时数据同步

备份数据库是:10.0.0.71,inotifywait 监控文件变化,rsync 负责同步数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SRC='/data/www/' #注意最后的/
DEST='rsyncuser@10.0.0.71::backup'
rpm -q rsync &>/dev/null || yum install rsync
inotifywait \
-mrq \
--exclude=".*\.swp" \
--timefmt '%Y-%m-%d %H:%M:%S' \
--format '%T %w %f' \
-e create,delete,moved_to,close_write,attrib ${SRC} |
while read DATE TIME DIR FILE; do
FILEPATH=${DIR}${FILE}
rsync \
-az \
--delete \
-password-file=/etc/rsync.pas $SRC $DEST &&
echo "At ${TIME} on ${DATE}, file $FILEPATH was backuped up via rsync" >>/var/log/changelist.log
done

sersync 实现实时数据同步

sersync 虽然快 10 年没更新了,但是还是比较好用的

sersync 项目地址: https://code.google.com/archive/p/sersync/
sersync 下载地址: https://code.google.com/archive/p/sersync/downloads

sersync 类似于 inotify,同样用于监控,但它克服了 inotify 的缺点

inotify 最大的不足是会产生重复事件,或者同一个目录下多个文件的操作会产生多个事件,例如,当监控目录中有 5 个文件时,删除目录时会产生 6 个监控事件,从而导致重复调用 rsync 命令。另外比如:vim 文件时,inotify 会监控到临时文件的事件,但这些事件相对于 rsync 来说是不应该被监控的

sersync 优点:

  • c++编写,速度快
  • 对 linux 系统文件系统产生的临时文件和重复的文件操作进行过滤,所以在结合 rsync 同步的时候,节省了运行时耗和网络资源
  • 配置很简单,其中提供了静态编译好的二进制文件和 xml 配置文件,直接使用即可
  • 使用多线程进行同步,尤其在同步较大文件时,能够保证多个服务器实时保持同步状态
  • 有出错处理机制,通过失败队列对出错的文件重新同步,如果仍旧失败,则按设定时长对同步失败的文件重新同步
  • 不仅可以实现实时同步,另外还自带 crontab 功能,只需在 xml 配置文件中开启,即也可以按要求隔一段时间整体同步一次,而无需再额外配置 crontab 功能
  • 可以二次开发

sersync 只有两个文件:

  • 二进制程序文件 sersync2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    lujinkai@Z510:/usr/local/sersync$ ./sersync2 -h
    set the system param
    execute:echo 50000000 > /proc/sys/fs/inotify/max_user_watches
    sh: 1: cannot create /proc/sys/fs/inotify/max_user_watches: Permission denied
    execute:echo 327679 > /proc/sys/fs/inotify/max_queued_events
    sh: 1: cannot create /proc/sys/fs/inotify/max_queued_events: Permission denied
    parse the command param
    _______________________________________________________
    参数-d:启用守护进程模式
    参数-r:在监控前,将监控目录与远程主机用rsync命令推送一遍
    c参数-n: 指定开启守护线程的数量,默认为10个
    参数-o:指定配置文件,默认使用confxml.xml文件
    参数-m:单独启用其他模块,使用 -m refreshCDN 开启刷新CDN模块
    参数-m:单独启用其他模块,使用 -m socket 开启socket模块
    参数-m:单独启用其他模块,使用 -m http 开启http模块
    不加-m参数,则默认执行同步程序

  • 配置文件 confxml.xml

    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
    lujinkai@Z510:/usr/local/sersync$ vim confxml.xml
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <head version="2.5">
    <host hostip="localhost" port="8008"></host>
    <debug start="false"/> # 是否开启调试模式
    <fileSystem xfs="false"/>
    <filter start="false"> # 是否开启文件过滤功能
    <exclude expression="(.*)\.svn"></exclude>
    <exclude expression="(.*)\.gz"></exclude>
    <exclude expression="^info/*"></exclude>
    <exclude expression="^static/*"></exclude>
    </filter>
    <inotify> # 监控的事件
    <delete start="true"/>
    <createFolder start="true"/>
    <createFile start="false"/>
    <closeWrite start="true"/>
    <moveFrom start="true"/>
    <moveTo start="true"/>
    <attrib start="false"/> # 修改此行为true,文件属性变化后也会同步
    <modify start="false"/>
    </inotify>

    <sersync> # rsync命令的配置段
    <localpath watch="/opt/tongbu"> # 同步的目录
    # name是远程rsyncd的模块名,如果下面ssh标签开启了start,name则为远程sehll方式运行的目标目录
    <remote ip="127.0.0.1" name="tongbu1"/>
    <!--<remote ip="192.168.8.39" name="tongbu"/>-->
    <!--<remote ip="192.168.8.40" name="tongbu"/>-->
    </localpath>
    <rsync>
    <commonParams params="-artuz"/> # 指定rsync选项
    <auth start="false" users="root" passwordfile="/etc/rsync.pas"/>
    <userDefinedPort start="false" port="874"/><!-- port=874 -->
    <timeout start="false" time="100"/><!-- timeout=100 -->
    <ssh start="false"/> # 默认使用rsync daemon运行rsync命令,true为使用远程shell模式
    </rsync>
    # 错误重传及日志文件路径,默认60分钟
    <failLog path="/tmp/rsync_fail_log.sh" timeToExecute="60"/>
    <crontab start="false" schedule="600"> # 定时,默认600分钟
    <crontabfilter start="false"> # 是否开启定时
    <exclude expression="*.php"></exclude>
    <exclude expression="info/*"></exclude>
    </crontabfilter>
    </crontab>
    <plugin start="false" name="command"/>
    </sersync>
    #####################################以下行不需要修改####################################
    <plugin name="command">
    <param prefix="/bin/sh" suffix="" ignoreError="true"/> <!--prefix /opt/tongbu/mmm.sh suffix-->
    <filter start="false">
    <include expression="(.*)\.php"/>
    <include expression="(.*)\.sh"/>
    </filter>
    </plugin>

    <plugin name="socket">
    <localpath watch="/opt/tongbu">
    <deshost ip="192.168.138.20" port="8009"/>
    </localpath>
    </plugin>
    <plugin name="refreshCDN">
    <localpath watch="/data0/htdocs/cms.xoyo.com/site/">
    <cdninfo domainname="ccms.chinacache.com" port="80" username="xxxx" passwd="xxxx"/>
    <sendurl base="http://pic.xoyo.com/cms"/>
    <regexurl regex="false" match="cms.xoyo.com/site([/a-zA-Z0-9]*).xoyo.com/images"/>
    </localpath>
    </plugin>
    </head>

基于 rsync daemon 实现 sersync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 下载
lujinkai@Z510:~/www/script/src$ wget https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/sersync/sersync2.5.4_64bit_binary_stable_final.tar.gz
# 解压,可以看到,sersync只有两个文件:二进制程序文件和xml配置文件
lujinkai@Z510:~/www/script/src$ tar zxvf sersync2.5.4_64bit_binary_stable_final.tar.gz
lujinkai@Z510:~/www/script/src$ sudo mv GNU-Linux-x86/ /usr/local/sersync
# 添加到环境变量PATH,过程略...
# 修改配置文件
lujinkai@Z510:~/www/script/src$ cd /usr/local/sersync/
lujinkai@Z510:/usr/local/sersync$ vim confxml.xml # 过程略...
# 确认安装rsync客户端工具
lujinkai@Z510:/usr/local/sersync$ apt -y install rsync
# 创建连接rsynd服务器的用户密码文件,并必须修改权限
lujinkai@Z510:~$ echo 123456 > /etc/rsync.pas
lujinkai@Z510:~$ chmod 600 /etc/rsync.pas
# 以后台方式执行同步
lujinkai@Z510:~$ sersync2 -dro /usr/local/sersync/confxml.xml
# 如果同步失败,可以手动执行下面命令,观察过程
lujinkai@Z510:~$ cd /data/www && rsync -artuz -R --delete ./rsyncuser@10.0.0.71::backup --password-file=/etc/rsync.pas >/dev/null 2>&1

# sersync支持多实例,监控多个目录时,只需分别配置不同配置文件,然后使用sersync2指定对应配置文件运行
lujinkai@Z510:~$ sersync2 -rd -o /etc/sersync.d/nginx.xml

基于远程 shell 实现 sersync

1
2
3
4
5
6
7
8
9
# 首先配置ssh基于key的自动验证,过程略...
# 修改配置文件 confxml.xml
<localpath watch="/data/www">
<remote ip="备份服务器IP" name="/data/backup"/> #修改此行指定备份服务器地址和备份目标目录
</localpath>
...
<ssh start="true"/> # 开启ssh

# 不需要配置/etc/rsync.pas,其他步骤和上面一样

系统日志

将系统和应用发生的事件记录至日志中,以助于排错和分析使用:

日志记录的内容包括:

  • 历史事件:事件、地点、人物、事件
  • 日志级别:事件的关键程度 LogLevel

各种系统日志服务

  • sysklogd:CentOS 5 之前版本采用的日志管理系统服务
  • rsyslog:CentOS 6 以后版本的系统管理服务
    • 多线程
    • UDP、TCP、SSL、TLS、RELP
    • MySQL、PGSQL、Oracle 实现日志存储
    • 强大的过滤器,可实现过滤记录日志信息中任意部分
    • 自定义输出格式
  • ELK:Elasticsearch + Logstash + Kibana

常见日志文件

  • /var/log/secure:系统安全日志,文本格式,应周期性分析
  • /var/log/btmp:当前系统上,用户的失败尝试登录相关的日志信息,二进制格式,lastb 命令进行查看
  • /var/log/wtmp:当前系统上,用户正常登录系统的相关日志信息,二进制格式,last 命令可以查看
  • /var/log/lastlog:每一个用户最近一次的登录信息,二进制格式,lastlog 命令可以查看
  • /var/log/dmesg:CentOS7 之前版本系统引导过程中的日志信息,文本格式,开机后的硬件变化将不再记录专用命令 dmesg 查看,可持续记录硬件变化的情况
  • /var/log/boot.log:系统服务启动的相关信息,文本格式
  • /var/log/messages :系统中大部分的信息
  • /var/log/anaconda : anaconda 的日志

rsyslog

facility:设施,或者叫设备,从功能或程序上对日志进行归类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
auth、authpriv、cron、daemon、ftp、kern、lpr、mail、news、security(auth)、user、uucp、syslog

auth # pam产生的日志
authpriv # ssh,ftp等登录信息的验证信息
cron # 时间任务相关
kern # 内核
lpr # 打印
mail # 邮件
mark(syslog) # rsyslog服务内部的信息,时间标识
news # 新闻组
user # 用户程序产生的相关信息
uucp # unix to unix copy, unix主机之间相关的通讯

local0-local7 # 自定义的日志设施

priority:优先级别,从低到高排序

1
2
3
4
5
6
7
8
9
10
11
debug、info、notice、warn(warning)、err(error)、crit(critical)、alert、emerg(panic)

debug # 有调式信息的,日志信息最多
info # 一般信息的日志,最常用
notice # 最具有重要性的普通条件的信息
warning # 警告级别
err # 错误级别,阻止某个功能或者模块不能正常工作的信息
crit # 严重级别,阻止整个系统或者整个软件不能正常工作的信息
alert # 需要立刻修改的信息
emerg # 内核崩溃等严重信息
none # 什么都不记录

参看帮助: man 3 syslogman logger

配置文件

配置文件:/etc/rsyslog.conf 和 /etc/rsyslog.d/*

/etc/rsyslog.conf 配置文件格式,由三部分组成:

  • MODULES:相关模块配置
  • GLOBAL DIRECTIVES:全局配置
  • RULES:日志记录相关的规则配置

RULES 配置格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 设施.优先级; 设施.优先级; 设施.优先级...    目标
facility.priority; facility.priority... target


# facility 格式:
* # 所有的facility
facility1,facility2,facility3,... # 指定的facility列表

# priority 格式:
* # 所有级别
none # 没有级别,即不记录
PRIORITY # 指定级别(含)以上的所有级别
=PRIORITY # 仅记录指定级别的日志信息

# target 格式:
文件路径 # 通常在/var/log/目录下,文件路径前的-表示异步写入
用户 # 将日志事件通知给指定的用户,*表示登录的所有用户
日志服务器 # @host:日志发送到指定的远程UDP日志服务器;@@host:日志发送到指定的远程TCP日志服务器
管道 # | command:转发给其他命令处理

日志文件有很多,如:/var/log/message、cron、secure 等,基本格式都是类似的,如下:

1
事件产生的日期时间 主机	进程(pid):事件内容

范例:将 ssh 服务的日志记录到自定义的 local 的日志设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 修改sshd的配置,然后 Service sshd reload 重启
Vim /etc/ssh/sshd_config
SyslogFacility local2

# 修改rsyslog的配置,然后 Systemctl restart rsyslog 重启
Vim /etc/rsyslog.conf
Local2.* /var/log/sshd.log

# 测试
Ssh登录后,查看/var/log/sshd.log有记录

#logger测试
logger -p local2.info "hello sshd"
tail /var/log/sshd.log有记录

启用网络日志服务

启用网络日志服务功能,可以将多个远程主机的日志,发送到集中的日志服务器,方便统一管理。

范例:启用网络日志功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# centos8
module(load="imudp") # udp
input(type="imudp" port="514")
module(load="imtcp") # tcp
input(type="imtcp" port="514")

# centos6、7
$ModLoad imudp # udp
$UDPServerRun 514
$ModLoad imtcp # tcp
$InputTCPServerRun 514


# 测试:在客户端指定将日志发送到远程的TCP、UDP的日志服务器
[root@centos7 ~]#vim /etc/rsyslog.conf
*.info;mail.none;authpriv.none;cron.none /var/log/messages
*.info;mail.none;authpriv.none;cron.none @@10.0.0.18 # tcp
*.info;mail.none;authpriv.none;cron.none @10.0.0.18 # udp

logger 命令

logger 命令可以往系统日志中写入信息

1
logger [options] [<message>]
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
[root@centos8 ~]$logger --help

Usage:
logger [options] [<message>]

Enter messages into the system log.

Options:
-i log the logger command's PID
--id[=<id>] log the given <id>, or otherwise the PID
-f, --file <file> log the contents of this file
-e, --skip-empty do not log empty lines when processing files
--no-act do everything except the write the log
-p, --priority <prio> mark given message with this priority
--octet-count use rfc6587 octet counting
--prio-prefix look for a prefix on every line read from stdin
-s, --stderr output message to standard error as well
-S, --size <size> maximum size for a single message
-t, --tag <tag> mark every line with this tag
-n, --server <name> write to this remote syslog server
-P, --port <port> use this port for UDP or TCP connection
-T, --tcp use TCP only
-d, --udp use UDP only
--rfc3164 use the obsolete BSD syslog protocol
--rfc5424[=<snip>] use the syslog protocol (the default for remote);
<snip> can be notime, or notq, and/or nohost
--sd-id <id> rfc5424 structured data ID
--sd-param <data> rfc5424 structured data name=value
--msgid <msgid> set rfc5424 message id field
-u, --socket <socket> write to this Unix socket
--socket-errors[=<on|off|auto>]
print connection errors when using Unix sockets
--journald[=<file>] write journald entry

-h, --help display this help
-V, --version display version

For more details see logger(1).

日志管理工具 journalctl

CentOS7 以后版本利用 Systemd 统一管理所有 Unit 的启动日志。带来的好处就是,可以只用 journalctl 一个命令,查看所有日志(内核日志和应用日志)

配置文件:

1
/etc/systemd/journald.conf

命令格式:

1
journalctl [OPTIONS...] [MATCHES...]

OPTIONS:

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
--no-full, --full, -l
如果字段内容超长则以省略号(...)截断以适应列宽。
默认显示完整的字段内容(超长的部分换行显示或者被分页工具截断)。
老旧的 -l/--full 选项 仅用于撤销已有的 --no-full 选项,除此之外没有其他用处。
-a, --all
完整显示所有字段内容, 即使其中包含不可打印字符或者字段内容超长。
-f, --follow
只显示最新的日志项,并且不断显示新生成的日志项。 此选项隐含了 -n 选项。
-e, --pager-end
在分页工具内立即跳转到日志的尾部。 此选项隐含了 -n1000 以确保分页工具不必缓存太多的日志行。 不过这个隐含的行数可以被明确设置的 -n 选项覆盖。 注意,此选项仅可用于 less(1) 分页器。
-n, --lines=
限制显示最新的日志行数。 --pager-end 与 --follow 隐含了此选项。
此选项的参数:若为正整数则表示最大行数; 若为 "all" 则表示不限制行数;
若不设参数则表示默认值10行。
--no-tail
显示所有日志行, 也就是用于撤销已有的 --lines= 选项(即使与 -f 连用)。
-r, --reverse
反转日志行的输出顺序, 也就是最先显示最新的日志。
-o, --output=
控制日志的输出格式。 可以使用如下选项:
short
这是默认值, 其输出格式与传统的 syslog[1] 文件的格式相似, 每条日志一行。
short-iso
与 short 类似,只是将时间戳字段以 ISO 8601 格式显示。
short-precise
与 short 类似,只是将时间戳字段的秒数精确到微秒级别。
short-monotonic
与 short 类似,只是将时间戳字段的零值从内核启动时开始计算。
short-unix
与 short 类似,只是将时间戳字段显示为从"UNIX时间原点"(1970-1-1 00:00:00 UTC)以来的秒数。 精确到微秒级别。
verbose
以结构化的格式显示每条日志的所有字段。
export
将日志序列化为二进制字节流(大部分依然是文本) 以适用于备份与网络传输(详见Journal Export Format[2] 文档)。
json
将日志项按照JSON数据结构格式化, 每条日志一行(详见 Journal JSON Format[3]文档)。
json-pretty
将日志项按照JSON数据结构格式化, 但是每个字段一行, 以便于人类阅读。
json-sse
将日志项按照JSON数据结构格式化,每条日志一行,但是用大括号包围, 以适应 Server-Sent Events[4] 的要求。
cat
仅显示日志的实际内容, 而不显示与此日志相关的任何元数据(包括时间戳)。
--utc
以世界统一时间(UTC)表示时间
--no-hostname
不显示来源于本机的日志消息的主机名字段。 此选项仅对 short 系列输出格式(见上文)有效。
-x, --catalog
在日志的输出中增加一些解释性的短文本, 以帮助进一步说明日志的含义、问题的解决方案、支持论坛、 开发文档、以及其他任何内容。
并非所有日志都有这些额外的帮助文本, 详见 Message Catalog Developer Documentation[5] 文档。
注意,如果要将日志输出用于bug报告, 请不要使用此选项。
-q, --quiet
当以普通用户身份运行时, 不显示任何警告信息与提示信息。例如:"-- Logs begin at ...", "-- Reboot --"
-m, --merge
混合显示包括远程日志在内的所有可见日志。
-b [ID][±offset], --boot=[ID][±offset]
显示特定于某次启动的日志, 这相当于添加了一个 "_BOOT_ID=" 匹配条件。
如果参数为空(也就是 ID 与 ±offset 都未指定), 则表示仅显示本次启动的日志。
如果省略了 ID , 那么当 ±offset 是正数的时候, 将从日志头开始正向查找,否则(也就是为负数或零)将从日志尾开始反响查找。 举例来说, "-b 1"表示按时间顺序排列最早的那次启动, "-b 2"则表示在时间上第二早的那次启动; "-b -0"表示最后一次启动, "-b -1"表示在时间上第二近的那次启动, 以此类推。 如果 ±offset 也省略了, 那么相当于"-b -0", 除非本次启动不是最后一次启动(例如用--directory 指定了另外一台主机上的日志目录)。如果指定了32字符的 ID , 那么表示以此 ID 所代表的那次启动为基准计算偏移量(±offset), 计算方法同上。 换句话说, 省略 ID 表示以本次启动为基准计算偏移量(±offset)。
--list-boots
列出每次启动的 序号(也就是相对于本次启动的偏移量)、32字符的ID、第一条日志的时间戳、最后一条日志的时间戳。
-k, --dmesg
仅显示内核日志。隐含了 -b 选项以及 "_TRANSPORT=kernel" 匹配项。
-t, --identifier=SYSLOG_IDENTIFIER
仅显示 syslog[1] 识别符为 SYSLOG_IDENTIFIER 的日志项。
可以多次使用该选项以指定多个识别符。
-u, --unit=UNIT|PATTERN
仅显示属于特定单元的日志。 也就是单元名称正好等于 UNIT 或者符合 PATTERN 模式的单元。 这相当于添加了一个 "_SYSTEMD_UNIT=UNIT" 匹配项(对于 UNIT 来说),或一组匹配项(对于 PATTERN 来说)。可以多次使用此选项以添加多个并列的匹配条件(相当于用"OR"逻辑连接)。
--user-unit=
仅显示属于特定用户会话单元的日志。 相当于同时添加了 "_SYSTEMD_USER_UNIT="
"_UID=" 两个匹配条件。
可以多次使用此选项以添加多个并列的匹配条件(相当于用"OR"逻辑连接)。
-p, --priority=
根据日志等级(包括等级范围)过滤输出结果。 日志等级数字与其名称之间的对应关系如下 (参见 syslog(3)): "emerg" (0), "alert" (1), "crit" (2), "err" (3),"warning" (4), "notice" (5), "info" (6), "debug" (7) 。
若设为一个单独的数字或日志等级名称, 则表示仅显示小于或等于此等级的日志(也就是重要程度等于或高于此等级的日志)。 若使用 FROM..TO.. 设置一个范围,则表示仅显示指定的等级范围内(含两端)的日志。 此选项相当于添加了 "PRIORITY="匹配条件。
-c, --cursor=
从指定的游标(cursor)开始显示日志。
[提示]每条日志都有一个"__CURSOR"字段,类似于该条日志的指纹。
--after-cursor=
从指定的游标(cursor)之后开始显示日志。 如果使用了 --show-cursor 选项,则也会显示游标本身。
--show-cursor
在最后一条日志之后显示游标, 类似下面这样,以"--"开头:
-- cursor: s=0639...
游标的具体格式是私有的(也就是没有公开的规范), 并且会变化。
-S, --since=, -U, --until=
显示晚于指定时间(--since=)的日志、显示早于指定时间(--until=)的日志。
参数的格式类似 "2012-10-30 18:17:16" 这样。 如果省略了"时:分:秒"部分,则相当于设为 "00:00:00" 。 如果仅省略了"秒"的部分则相当于设为 ":00" 。如果省略了"年-月-日"部分, 则相当于设为当前日期。 除了"年-月-日 时:分:秒"格式,参数还可以进行如下设置:
(1)设为 "yesterday", "today", "tomorrow"以表示那一天的零点(00:00:00)。
(2)设为 "now" 以表示当前时间。
(3)可以在"年-月-日 时:分:秒"前加上 "-"(前移) 或 "+"(后移)前缀以表示相对于当前时间的偏移。 关于时间与日期的详细规范, 参见systemd.time(7)
-F, --field=
显示所有日志中某个字段的所有可能值。 [译者注]类似于SQL语句:"SELECT DISTINCT 某字段 FROM 全部日志"
-N, --fields
输出所有日志字段的名称
--system, --user
仅显示系统服务与内核的日志(--system)、 仅显示当前用户的日志(--user)。
如果两个选项都未指定,则显示当前用户的所有可见日志。
-M, --machine=
显示来自于正在运行的、特定名称的本地容器的日志。 参数必须是一个本地容器的名称。
-D DIR, --directory=DIR
仅显示来自于特定目录中的日志, 而不是默认的运行时和系统日志目录中的日志。
--file=GLOB
GLOB 是一个可以包含"?""*"的文件路径匹配模式。 表示仅显示来自与指定的 GLOB模式匹配的文件中的日志, 而不是默认的运行时和系统日志目录中的日志。可以多次使用此选项以指定多个匹配模式(多个模式之间用"OR"逻辑连接)。
--root=ROOT
在对日志进行操作时, 将 ROOT 视为系统的根目录。 例如 --update-catalog 将会创建ROOT/var/lib/systemd/catalog/database
--new-id128
此选项并不用于显示日志内容, 而是用于重新生成一个标识日志分类的 128-bit ID 。
此选项的目的在于 帮助开发者生成易于辨别的日志消息, 以方便调试。
--header
此选项并不用于显示日志内容, 而是用于显示日志文件内部的头信息(类似于元数据)。
--disk-usage
此选项并不用于显示日志内容,而是用于显示所有日志文件(归档文件与活动文件)的磁盘占用总量。
--vacuum-size=, --vacuum-time=, --vacuum-files=
这些选项并不用于显示日志内容,而是用于清理日志归档文件(并不清理活动的日志文件), 以释放磁盘空间。
--vacuum-size= 可用于限制归档文件的最大磁盘使用量 (可以使用 "K", "M", "G", "T"后缀);
--vacuum-time= 可用于清除指定时间之前的归档 (可以使用 "s", "m", "h","days", "weeks", "months", "years" 后缀);
--vacuum-files= 可用于限制日志归档文件的最大数量。 注意,--vacuum-size= 对 --disk-usage的输出仅有间接效果, 因为 --disk-usage 输出的是归档日志与活动日志的总量。同样,--vacuum-files= 也未必一定会减少日志文件的总数,因为它同样仅作用于归档文件而不会删除活动的日志文件。此三个选项可以同时使用,以同时从三个维度去限制归档文件。若将某选项设为零,则表示取消此选项的限制。
--list-catalog [128-bit-ID...]
简要列出日志分类信息, 其中包括对分类信息的简要描述。
如果明确指定了分类ID(128-bit-ID), 那么仅显示指定的分类。
--dump-catalog [128-bit-ID...]
详细列出日志分类信息 (格式与 .catalog 文件相同)。
如果明确指定了分类ID(128-bit-ID), 那么仅显示指定的分类。
--update-catalog
更新日志分类索引二进制文件。
每当安装、删除、更新了分类文件,都需要执行一次此动作。
--setup-keys
此选项并不用于显示日志内容, 而是用于生成一个新的FSS(Forward Secure Sealing)密钥对。 此密钥对包含一个"sealing key"与一个"verification key""sealing key"保存在本地日志目录中, 而"verification key"则必须保存在其他地方。详见 journald.conf(5) 中的 Seal= 选项。
--force
与 --setup-keys 连用, 表示即使已经配置了FSS(Forward Secure Sealing)密钥对,也要强制重新生成。
--interval=
与 --setup-keys 连用,指定"sealing key"的变化间隔。
较短的时间间隔会导致占用更多的CPU资源, 但是能够减少未检测的日志变化时间。
默认值是 15min
--verify
检查日志文件的内在一致性。 如果日志文件在生成时开启了FSS特性, 并且使用
--verify-key= 指定了FSS的"verification key"
那么,同时还将验证日志文件的真实性。
--verify-key=
与 --verify 选项连用, 指定FSS的"verification key"
--sync
要求日志守护进程将所有未写入磁盘的日志数据刷写到磁盘上,并且一直阻塞到刷写操作实际完成之后才返回。 因此该命令可以保证当它返回的时候,所有在调用此命令的时间点之前的日志, 已经全部安全的刷写到了磁盘中。
--flush
要求日志守护进程 将 /run/log/journal 中的日志数据 刷写到 /var/log/journal 中(如果持久存储设备当前可用的话)。 此操作会一直阻塞到操作完成之后才会返回,因此可以确保在该命令返回时, 数据转移确实已经完成。
注意,此命令仅执行一个单独的、一次性的转移动作, 若没有数据需要转移,则此命令什么也不做, 并且也会返回一个表示操作已正确完成的返回值。
--rotate
要求日志守护进程滚动日志文件。 此命令会一直阻塞到滚动完成之后才会返回。
-h, --help
显示简短的帮助信息并退出。
--version
显示简短的版本信息并退出。
--no-pager
不将程序的输出内容管道(pipe)给分页程序

范例:journalctl 用法

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
#查看所有日志(默认情况下 ,只保存本次启动的日志)
journalctl
#查看内核日志(不显示应用日志)
journalctl -k
#查看系统本次启动的日志
journalctl -b
journalctl -b -0
#查看上一次启动的日志(需更改设置)
journalctl -b -1
#查看指定时间的日志
journalctl --since="2017-10-30 18:10:30"
journalctl --since "20 min ago"
journalctl --since yesterday
journalctl --since "2017-01-10" --until "2017-01-11 03:00"
journalctl --since 09:00 --until "1 hour ago"
#显示尾部的最新10行日志
journalctl -n
#显示尾部指定行数的日志
journalctl -n 20
#实时滚动显示最新日志
journalctl -f
#查看指定服务的日志
journalctl /usr/lib/systemd/systemd
#查看指定进程的日志
journalctl _PID=1
#查看某个路径的脚本的日志
journalctl /usr/bin/bash
#查看指定用户的日志
journalctl _UID=33 --since today
#查看某个 Unit 的日志
journalctl -u nginx.service
journalctl -u nginx.service --since today
#实时滚动显示某个 Unit 的最新日志
journalctl -u nginx.service -f
#合并显示多个 Unit 的日志
journalctl -u nginx.service -u php-fpm.service --since today
#查看指定优先级(及其以上级别)的日志,共有8级
0: emerg
1: alert
2: crit
3: err
4: warning
5: notice
6: info
7: debug
journalctl -p err -b
#日志默认分页输出,--no-pager 改为正常的标准输出
journalctl --no-pager
#日志管理journalctl
#以 JSON 格式(单行)输出
journalctl -b -u nginx.service -o json
#以 JSON 格式(多行)输出,可读性更好
journalctl -b -u nginx.serviceqq -o json-pretty
#显示日志占据的硬盘空间
journalctl --disk-usage
#指定日志文件占据的最大空间
journalctl --vacuum-size=1G
#指定日志文件保存多久
journalctl --vacuum-time=1years

实战案例 1:利用 mysql 存储日志信息

目标:将 rsyslog 收集的日志记录在 mysql 中

环境:两台主机

  1. rsyslog 日志服务器:10.0.0.71
  2. mysql 数据库服务器:10.0.0.72

实现:

  1. 10.0.0.71 安装 rsyslog-mysql
1
2
3
4
5
6
[root@c71 ~]$yum install rsyslog-mysql.x86_64
[root@c71 ~]$rpm -ql rsyslog-mysql
/usr/lib64/rsyslog/ommysql.so
/usr/share/doc/rsyslog-8.24.0/mysql-createDB.sql # sql文件
# 拷贝到数据库服务器
[root@c71 ~]$scp /usr/share/doc/rsyslog-8.24.0/mysql-createDB.sql root@10.0.0.72:/data
  1. 10.0.0.72 配置 mysql
1
2
3
4
5
6
7
8
9
10
11
12
[root@c72 ~]$mysql -uroot -p123456
...
11:27:05(root@localhost) [(none)]> source /data/mysql-createDB.sql # 导入数据表
Query OK, 1 row affected (0.00 sec)
Database changed
Query OK, 0 rows affected (0.06 sec)
Query OK, 0 rows affected (0.06 sec)
# 创建rsyslog用户,提供给日志服务,用来连接数据库
08:25:34(root@localhost) [(none)]> CREATE USER 'rsyslog'@'10.0.0.%' IDENTIFIED BY "123456";
Query OK, 0 rows affected (0.00 sec)
08:25:39(root@localhost) [(none)]> GRANT ALL ON *.* TO 'rsyslog'@'10.0.0.%';
Query OK, 0 rows affected (0.01 sec)
  1. 10.0.0.71 修改 rsyslog 配置文件
1
2
3
4
5
6
7
8
9
[root@c71 ~]$vim /etc/rsyslog.conf
...
# 在MODULES下添加:
$ModLoad ommysql
# 在RULES下添加:
#facility.priority :ommysql:DBHOST,DBNAME,DBUSER, PASSWORD
*.info :ommysql:10.0.0.72,Syslog,rsyslog,123456
...
[root@c71 ~]$systemctl restart rsyslog.service
  1. 测试
1
2
3
4
5
6
7
[root@c71 ~]$logger "this is a test log"

[root@c72 ~]$mysql -uroot -p123456
08:45:43(root@localhost) [(none)]> select Message from Syslog.SystemEvents order by id desc limit 1\G;
*************************** 1. row ***************************
Message: this is a test log
1 row in set (0.00 sec)

实战案例 2:通过 loganalyzer 展示数据库中的日志

loganalyzer 是用 php 语言实现的日志管理系统,可将 MySQL 数据库的日志用丰富的 WEB 方式进行展示

官网: https://loganalyzer.adiscon.com

目标:通过 loganalyzer 展示数据库中的日志

logrotate 日志转储

logrotate 程序是一个日志文件管理工具。用来把旧的日志文件删除,并创建新的日志文件,称为日志转储或滚动。可以根据日志文件的大小,也可以根据其天数来转储,这个过程一般通过 cron 程序来执行

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@c71 ~]$rpm -ql logrotate
/etc/cron.daily/logrotate # 计划任务
/etc/logrotate.conf # 配置文件
/etc/logrotate.d
/etc/rwtab.d/logrotate
/usr/sbin/logrotate # 程序
/usr/share/doc/logrotate-3.8.6
/usr/share/doc/logrotate-3.8.6/CHANGES
/usr/share/doc/logrotate-3.8.6/COPYING
/usr/share/man/man5/logrotate.conf.5.gz
/usr/share/man/man8/logrotate.8.gz
/var/lib/logrotate
/var/lib/logrotate/logrotate.status # 日志文件

配置文件主要参数如下:

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
compress   # 通过 gzip 压缩转储以后的日志
nocompress # 不压缩
copytruncate # 把当前日志备份并截断,即 拷贝原来的日志,然后把原日志清空
nocopytruncate # 备份日志文件但是不截断,即 只拷贝原来的日志,但并不会在拷贝完后清空原日志
create mode owner group # 转储文件,使用指定的权限、所有者、所属组创建新的日志文件。注意 能用create就不用copytruncate
nocreate # 不建立新的日志文件,不知道这个选项的意义在哪里
delaycompress # 和 compress 一起使用时,转储的日志文件到下一次转储时才压缩
nodelaycompress # 覆盖 delaycompress 选项,转储同时压缩
errors address # 专储时的错误信息发送到指定的 Email 地址
ifempty # 即使是空文件也转储,此为默认选项
notifempty # 如果是空文件的话,不转储
mail address # 把转储的日志文件发送到指定的 E-mail 地址
nomail # 转储时不发送日志文件
olddir directory # 转储后的日志文件放入指定目录,必须和当前日志文件在同一个文件系统
noolddir # 转储后的日志文件和当前日志文件放在同一个目录下
prerotate/endscript # 在转储以前需要执行的命令,这两个关键字必须单独成行
postrotate/endscript # 在转储以后需要执行的命令,这两个关键字必须单独成行
daily # 指定转储周期为每天
weekly # 指定转储周期为每周
monthly # 指定转储周期为每月
rotate count # 指定日志文件删除之前转储的次数,0 指没有备份,5 指保留 5 个备份
tabooext [+] list # 让 logrotate 不转储指定扩展名的文件,缺省的扩展名是:.rpm-orig、.rpmsave、v 和 ~
size size # 当日志文件到达指定的大小时才转储,bytes(缺省)及 KB 或 MB
sharedscripts # 默认,对每个转储日志运行 prerotate 和 postrotate 脚本,日志文件的绝对路径作为第一个参数传递给脚本。 这意味着单个脚本可以针对与多个文件匹配的日志文件条目多次运行(例如/ var / log / news /\*.example)。 如果指定此项 sharedscripts,则无论有多少个日志与通配符模式匹配,脚本都只会运行一次
nosharedscripts # 针对每一个转储的日志文件,都执行一次 prerotate 和 postrotate 脚本,此为默认值
missingok # 如果日志不存在,不提示错误,继续处理下一个
nomissingok # 如果日志不存在,提示错误,此为默认值
dateext # 切换后的日志文件会附加上一个短横线和 YYYYMMDD 格式的日期,没有这个配置项会附加一个小数点加一个数字序号
dateformat # 配合 dateext 使用可以为切割后的日志加上 YYYYMMDD 格式的日期,如 dateformat -%Y%m%d
...

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# logrotate nginx log
cat >/etc/logrotate.d/nginx <<EOF
${wwwlogs_dir}/*nginx.log {
daily
rotate 5
missingok
dateext
compress
notifempty
sharedscripts
postrotate
[ -f /usr/local/nginx/run/nginx.pid ] && kill -USR1 \`cat /usr/local/nginx/run/nginx.pid\`
endscript
}
EOF

缓存技术

缓存 cache

buffer 和 cache

  • buffer:缓冲,写缓冲,先写到缓冲中,再输出到网络或者写入磁盘
  • cache:缓存

缓存保存位置及分层结构

互联网领域,缓存为王

  • 用户层:浏览器 DNS 缓存,应用程序 DNS 缓存,操作系统 DNS 缓存客户端
  • 代理层:CDN,反向代理缓存
  • Web 层:解释器 Opcache,Web 服务器缓存
  • 应用层:页面静态化
  • 数据层:分布式缓存,数据库
  • 系统层:操作系统 cache
  • 物理层:磁盘 cache, Raid Cache

cache 的特性

自动过期、强制过期、命中率

CDN

利用 302 实现转发请求重定向至最优服务器集群

中国网络较为复杂,依赖 DNS 就近解析的调度,仍然会存在部分请求调度失效、调度生效慢等问题。腾讯云利用在全国部署的 302 重定向服务器集群,能够为每一个请求实时决策最优的服务器资源,精准解决小运营商的调度问题,提升用户访问质量, 能最快地把用户引导到最优的服务器节点上,避开性能差或者异常的节点。

CDN 分层缓存

Redis 部署和使用

Redis 基础

注意事项:谨慎甚至禁止使用某些命令,例如 keys

redis 对比 memcached

  • 支持数据的持久化:可以将内存中的数据保持在磁盘中,重启 redis 服务或者服务器之后可以从备份文件中恢复数据到内存继续使用
  • 支持更多的数据类型:支持 string(字符串)、hash(哈希数据)、list(列表)、set(集合)、zset(有序集合)
  • 支持数据的备份:可以实现类似于数据的 master-slave 模式的数据备份,另外也支持使用快照+AOF
  • 支持更大的 value 数据:memcache 单个 key value 最大只支持 1MB,而 redis 最大支持 512MB(生产不建议超过 2M,性能受影响)
  • 在 Redis6 版本前,Redis 是单线程,而 memcached 是多线程,所以单机情况下没有 memcached 并发高,性能更好,但 redis 支持分布式集群以实现更高的并发,单 Redis 实例可以实现数万并发
  • 支持集群横向扩展:基于 redis cluster 的横向扩展,可以实现分布式集群,大幅提升性能和数据安全性
  • 都是基于 C 语言开发

redis 典型应用场景

  • Session 共享:常见于 web 集群中的 Tomcat 或者 PHP 中多 web 服务器 session 共享
  • 缓存:数据查询、电商网站商品信息、新闻内容
  • 计数器:访问排行榜、商品浏览数等和次数相关的数值统计场景
  • 微博/微信社交场合:共同好友,粉丝数,关注,点赞评论等
  • 消息队列:ELK 的日志缓存、部分业务的订阅发布系统
  • 地理位置: 基于 GEO(地理信息定位),实现摇一摇,附近的人,外卖等功能

Redis 安装及连接

编译安装

见脚本,注意 redis 不建议设置密码

解决启动时的三个警告提示

  1. tcp-backlog

    1
    2
    3
    #vim /etc/sysctl.conf
    net.core.somaxconn = 1024
    #sysctl -p
  2. vm.overcommit_memory

    1
    2
    3
    4
    5
    6
    7
    8
    # 0、表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
    # 1、表示内核允许分配所有的物理内存,而不管当前的内存状态如何
    # 2、表示内核允许分配超过所有物理内存和交换空间总和的内存

    # 范例:
    #vim /etc/sysctl.conf
    vm.overcommit_memory = 1
    #sysctl -p
  3. transparent hugepage

    1
    2
    3
    # 警告:您在内核中启用了透明大页面(THP,不同于一般内存页的4k为2M)支持。 这将在Redis中造成延迟和内存使用问题。 要解决此问题,请以root 用户身份运行命令“echo never> /sys/kernel/mm/transparent_hugepage/enabled”,并将其添加到您的/etc/rc.local中,以便在重启后保留设置。禁用THP后,必须重新启动Redis。

    echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >> /etc/rc.d/rc.local

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
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# 绑定网卡,监听地址,可以用空格隔开后多个监听IP
bind 0.0.0.0
# redis3.2之后加入的新特性,在没有设置bind IP和密码的时候,redis只允许访问127.0.0.1:6379,可以远程连接,但当访问将提示警告信息并拒绝远程访问
protected-mode yes
# 监听端口,默认6379/tcp
port 6379
# 三次握手的时候server端收到client ack确认号之后的队列值,即全连接队列长度
tcp-backlog 511
# 客户端和Redis服务端的连接超时时间,默认是0,表示永不超时
timeout 0
# tcp 会话保持时间300s
tcp-keepalive 300
# 默认no,即直接运行redis-server程序时,不作为守护进程运行,而是以前台方式运行,如果想在后台运行需改成yes,当redis作为守护进程运行的时候,它会写一个pid 到/var/run/redis.pid 文件
daemonize no
# 和OS相关参数,可设置通过upstart和systemd管理Redis守护进程,centos7后都使用systemd
supervised no
# pid文件路径
pidfile /var/run/redis_6379.pid
# 日志级别
loglevel notice
# 日志路径,示例:logfile "/apps/redis/log/redis_6379.log"
logfile "/path/redis.log"
# 设置数据库数量,默认:0-15,共16个库
databases 16
# 在启动redis时是否显示或在日志中记录记录redis的logo
always-show-logo yes
# 在900秒内有1个key内容发生更改,就执行快照机制
save 900 1
# 在300秒内有10个key内容发生更改,就执行快照机制
save 300 10
# 60秒内如果有10000个key以上的变化,就自动快照备份
save 60 10000
# 默认为yes时,可能会因空间满等原因快照无法保存出错时,会禁止redis写入操作,生产建议为no
# 此项只针对配置文件中的自动save有效
stop-writes-on-bgsave-error yes
# 持久化到RDB文件时,是否压缩,"yes"为压缩,"no"则反之
rdbcompression yes
# 是否对备份文件开启RC64校验,默认是开启
rdbchecksum yes
# 快照文件名
dbfilename dump.rdb
# 快照文件保存路径,示例:dir "/apps/redis/data"
dir ./

#主从复制相关
# replicaof <masterip> <masterport> # 指定复制的master主机地址和端口,5.0版之前的指令为slaveof
# masterauth <master-password> # 指定复制的master主机的密码

# 当从库同主库失去连接或者复制正在进行,从机库有两种运行方式:
# 1、设置为yes(默认设置),从库会继续响应客户端的读请求,此为建议值
# 2、设置为no,除去特定命令外的任何请求都会返回一个错误"SYNC with master in progress"。
replica-serve-stale-data yes
# 是否设置从库只读,建议值为yes,否则主库同步从库时可能会覆盖数据,造成数据丢失
replica-read-only yes

# 是否使用socket方式复制数据(无盘同步),新slave第一次连接master时需要做数据的全量同步,redis server就要从内存dump出新的RDB文件,然后从master传到slave,有两种方式把RDB文件传输给客户端:
# 1、基于硬盘(disk-backed):为no时,master创建一个新进程dump生成RDB磁盘文件,RDB完成之后由父进程(即主进程)将RDB文件发送给slaves,此为默认值
# 2、基于socket(diskless):master创建一个新进程直接dump RDB至slave的网络socket,不经过主进程和硬盘
# 推荐使用基于硬盘(为no),是因为RDB文件创建后,可以同时传输给更多的slave,但是基于socket(为yes),新slave连接到master之后得逐个同步数据。只有当磁盘I/O较慢且网络较快时,可用diskless(yes),否则一般建议使用磁盘(no)
repl-diskless-sync no

# diskless时复制的服务器等待的延迟时间,设置0为关闭,在延迟时间内到达的客户端,会一起通过diskless方式同步数据,但是一旦复制开始,master节点不会再接收新slave的复制请求,直到下一次同步开始才再接收新请求。即无法为延迟时间后到达的新副本提供服务,新副本将排队等待下一次RDB传输,因此服务器会等待一段时间才能让更多副本到达。推荐值:30-60
repl-diskless-sync-delay 5
# slave根据master指定的时间进行周期性的PING master,用于监测master状态,默认10s
repl-ping-replica-period 10
# 复制连接的超时时间,需要大于repl-ping-slave-period,否则会经常报超时
repl-timeout 60
# 是否在slave套接字发送SYNC之后禁用TCP_NODELAY,如果选择"yes",Redis将合并多个报文为一个大的报文,从而使用更少数量的包向slaves发送数据,但是将使数据传输到slave上有延迟,Linux内核的默认配置会达到40毫秒,如果 "no" ,数据传输到slave的延迟将会减少,但要使用更多的带宽
repl-disable-tcp-nodelay no
# 复制缓冲区内存大小,当slave断开连接一段时间后,该缓冲区会累积复制副本数据,因此当slave重新连接时,通常不需要完全重新同步,只需传递在副本中的断开连接后没有同步的部分数据即可。只有在至少有一个slave连接之后才分配此内存空间,建议建立主从时此值要调大一些或在低峰期配置,否则会导致同步到slave失败
repl-backlog-size 512mb

# 多长时间内master没有slave连接,就清空backlog缓冲区
repl-backlog-ttl 3600

# 当master不可用,哨兵Sentinel会根据slave的优先级选举一个master,此值最低的slave会优先当选master,而配置成0,永远不会被选举,一般多个slave都设为一样的值,让其自动选择
replica-priority 100

# 至少有3个可连接的slave,mater才接受写操作
min-replicas-to-write 3

# 和上面至少3个slave的ping延迟不能超过10秒,否则master也将停止写操作
min-replicas-max-lag 10

# 设置redis连接密码,之后需要AUTH pass,如果有特殊符号,用" "引起来,生产建议设置
requirepass foobared

# 重命名一些高危命令,示例:rename-command FLUSHALL "" 禁用命令
# rename-command del magedu
rename-command CONFIG ""

# Redis最大连接客户端
maxclients 10000

# redis使用的最大内存,单位为bytes字节,0为不限制,建议设为物理内存一半,8G内存的计算方式8(G)*1024(MB)1024(KB)*1024(Kbyte),需要注意的是缓冲区是不计算在maxmemory内,生产中如果不设置此项,可能会导致OOM
maxmemory <bytes>

# 是否开启AOF日志记录,默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了,但是redis如果中途宕机,会导致可能有几分钟的数据丢失(取决于dump数据的间隔时间),根据save来策略进行持久化,Append Only File是另一种持久化方式,可以提供更好的持久化特性,Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。默认不启用此功能
appendonly no

# 文本文件AOF的文件名,存放在dir指令指定的目录中
appendfilename "appendonly.aof"

# aof持久化策略的配置
# no表示由操作系统保证数据同步到磁盘,Linux的默认fsync策略是30秒,最多会丢失30s的数据
# always表示每次写入都执行fsync,以保证数据同步到磁盘,安全性高,性能较差
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据,此为默认值,也生产建议值
appendfsync everysec

# 同时在执行bgrewriteaof操作和主进程写aof文件的操作,两者都会操作磁盘,而bgrewriteaof往往会涉及大量磁盘操作,这样就会造成主进程在写aof文件的时候出现阻塞的情形,以下参数实现控制
# 在aof rewrite期间,是否对aof新记录的append暂缓使用文件同步策略,主要考虑磁盘IO开支和请求阻塞时间。
# no:默认设置,表示"不暂缓",新aof记录仍然被立即同步到磁盘,是最安全的方式,不丢失数据,但要忍受阻塞的问题
# yes:相当于将appendfsync设置为no,这说明并没有执行磁盘操作,只是写入了缓冲区,因此这样并不会造成阻塞(因为没有竞争磁盘),但是如果这个时候redis挂掉,就会丢失数据。丢失多少数据呢?Linux的默认fsync策略是30秒,最多会丢失30s的数据,但由于yes性能较好而且会避免出现阻塞,因此比较推荐
# rewrite 即对aof文件进行整理,将空闲空间回收,从而可以减少恢复数据时间
no-appendfsync-on-rewrite no

# 当Aof log增长超过指定百分比例时,重写AOF文件,设置为0表示不自动重写Aof日志,重写是为了使aof体积保持最小,但是还可以确保保存最完整的数据
auto-aof-rewrite-percentage 100
# 触发aof rewrite的最小文件大小
auto-aof-rewrite-min-size 64mb
# 是否加载由于某些原因导致的末尾异常的AOF文件(主进程被kill/断电等),建议yes
aof-load-truncated yes
# redis4.0新增RDB-AOF混合持久化格式,在开启了这个功能之后,AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容,其中RDB格式的内容用于记录已有的数据,而AOF格式的内容则用于记录最近发生了变化的数据,这样Redis就可以同时兼有RDB持久化和AOF持久化的优点(既能够快速地生成重写文件,也能够在出现问题时,快速地载入数据),默认为no,即不启用此功能
aof-use-rdb-preamble no

# lua脚本的最大执行时间,单位为毫秒
lua-time-limit 5000

# 是否开启集群模式,默认不开启,即单机模式
cluster-enabled yes
# 由node节点自动生成的集群配置文件名称
cluster-config-file nodes-6379.conf

# 集群中node节点连接超时时间,单位ms,超过此时间,会踢出集群
cluster-node-timeout 15000

# 单位为次,在执行故障转移的时候可能有些节点和master断开一段时间导致数据比较旧,这些节点就不适用于选举为master,超过这个时间的就不会被进行故障转移,不能当选master,计算公式:(node-timeout * replica-validity-factor) + repl-ping-replica-period
cluster-replica-validity-factor 10
# 集群迁移屏障,一个主节点至少拥有1个正常工作的从节点,即如果主节点的slave节点故障后会将多余的从节点分配到当前主节点成为其新的从节点
cluster-migration-barrier 1
# 集群请求槽位全部覆盖,如果一个主库宕机且没有备库就会出现集群槽位不全,那么yes时redis集群槽位验证不全,就不再对外提供服务(对key赋值时,会出现CLUSTERDOWN The cluster is down的提示,cluster_state:fail,但ping 仍PONG),而no则可以继续使用,但是会出现查询数据查不到的情况(因为有数据丢失)。生产建议为no
cluster-require-full-coverage yes
# 如果为yes,此选项阻止在主服务器发生故障时尝试对其主服务器进行故障转移。 但是,主服务器仍然可以执行手动强制故障转移,一般为no
cluster-replica-no-failover no

# Slow log 是 Redis 用来记录超过指定执行时间的日志系统,执行时间不包括与客户端交谈,发送回复等I/O操作,而是实际执行命令所需的时间(在该阶段线程被阻塞并且不能同时为其它请求提供服务),由于slow log 保存在内存里面,读写速度非常快,因此可放心地使用,不必担心因为开启 slow log 而影响Redis 的速度
# 以微秒为单位的慢日志记录,为负数会禁用慢日志,为0会记录每个命令操作。默认值为10ms,一般一条命令执行都在微秒级,生产建议设为1ms-10ms之间
slowlog-log-slower-than 10000
# 最多记录多少条慢日志的保存队列长度,达到此长度后,记录新命令会将最旧的命令从命令队列中删除,以此滚动删除,即,先进先出,队列固定长度,默认128,值偏小,生产建议设为1000以上
slowlog-max-len 128

动态修改配置

config 命令用于查看当前 redis 配置、以及不重启 redis 服务实现动态更改 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
    # 奇数行为键,偶数行为值
    127.0.0.1:6379> config get *
    1) "dbfilename"
    2) "dump.rdb"
    3) "requirepass"
    4) ""
    5) "masterauth"
    6) ""
    7) "cluster-announce-ip"
    8) ""
    9) "unixsocket"
    10) ""
    11) "logfile"
    12) "/var/log/redis/redis.log"
    13) "pidfile"
    14) "/var/run/redis_6379.pid"
    15) "slave-announce-ip"
    16) ""
    17) "replica-announce-ip"
    18) ""
    19) "maxmemory"
    20) "0"
    ......
    # 查看bind
    127.0.0.1:6379> config get bind
    1) "bind"
    2) "0.0.0.0"
    # 有些设置无法修改
    127.0.0.1:6379> CONFIG SET bind 127.0.0.1
    (error) ERR Unsupported CONFIG parameter: bind
  • 设置连接密码

    1
    2
    3
    4
    5
    6
    7
    # 设置连接密码
    127.0.0.1:6379> CONFIG SET requirepass 123456
    OK
    # 查看连接密码
    127.0.0.1:6379> CONFIG GET requirepass
    1) "requirepass"
    2) "123456"
  • 更改最大内存

    1
    2
    3
    4
    5
    127.0.0.1:6379> CONFIG SET maxmemory 8589934592
    OK
    127.0.0.1:6379> CONFIG GET maxmemory
    1) "maxmemory"
    2) "8589934592"

慢查询

1
2
slowlog-log-slower-than 10000
slowlog-max-len 1024
1
2
3
127.0.0.1:6379> SLOWLOG LEN # 查看慢日志的记录条数
127.0.0.1:6379> SLOWLOG GET [n] # 查看慢日志的n条记录
127.0.0.1:6379> SLOWLOG RESET # 清空慢日志

持久化

目前 redis 支持两种不同方式的数据持久化保存机制,分别是 RDB 和 AOF

RDB 模式

基于时间的快照,其默认只保留当前最新的一次快照,特点是执行速度比较快,缺点是可能会丢失从上次快照到当前时间点之间未做快照的数据

  • save: 同步,会阻赛其它命令,不推荐使用
  • bgsave: 异步后台执行,不影响其它命令的执行
  • 自动: 制定规则,自动执行

bgsave 过程:Redis 从 master 主进程先 fork 出一个子进程,使用写时复制(copy-on-write)机制,子进程将内存的数据保存为一个临时文件,比如:tmp-.rdb,当数据保存完成之后再将上一次保存的 RDB 文件替换掉,然后关闭子进程,这样可以保证每一次做 RDB 快照保存的数据都是完整的

因为直接替换 RDB 文件的时候,可能会出现突然断电等问题,而导致 RDB 文件还没有保存完整就因为突然关机停止保存,而导致数据丢失的情况.后续可以手动将每次生成的 RDB 文件进行备份,这样可以最大化保存历史数据

1
2
3
4
5
6
7
8
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
dir ./
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes

RDB 模式的优缺点

优点:

  • RDB 快照保存了某个时间点的数据,可以通过脚本执行 redis 指令 bgsave(非阻塞,后台执行)或者 save(会阻塞写操作,不推荐)命令自定义时间点备份,可以保留多个备份,当出现问题可以恢复到不同时间点的版本,很适合备份,并且此文件格式也支持有不少第三方工具可以进行后续的数据分析
    比如: 可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 ROB 文件。这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。
  • RDB 可以最大化 Redis 的性能,父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后
    这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘工/0 操作。
  • RDB 在大量数据,比如几个 G 的数据,恢复的速度比 AOF 的快

缺点:

  • 不能实时保存数据,可能会丢失自上一次执行 RDB 备份到当前的内存数据
    如果你需要尽量避免在服务器故障时丢失数据,那么 RDB 不适合你。虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率,但是,因为 ROB 文件需要保存整个数据集的状态,所以它并不是一个轻松的操作。因此你可能会至少 5 分钟才保存一次 RDB 文件。在这种情况下,一旦发生故障停机,你就可能会丢失好几分钟的数据
  • 当数据量非常大的时候,从父进程 fork 子进程进行保存至 RDB 文件时需要一点时间,可能是毫秒或者秒,取决于磁盘 IO 性能
    在数据集比较庞大时,fork()可能会非常耗时,造成服务器在一定时间内停止处理客户端﹔如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒或更久。虽然 AOF 重写也需要进行 fork(),但无论 AOF 重写的执行间隔有多长,数据的持久性都不会有任何损失

范例: 手动备份 RDB 文件的脚本

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
# 配置项
[root@centos7 ~]#vim /apps/redis/etc/redis.conf
save ""
dbfilename dump_6379.rdb
dir "/data/redis"
appendonly no

# 备份脚本
[root@centos8 ~]#cat redis_backup_rdb.sh
#!/bin/bash
. /etc/init.d/functions
BACKUP=/backup/redis-rdb
DIR=/data/redis
FILE=dump_6379.rdb
PASS=123456

redis-cli -h 127.0.0.1 -a $PASS --no-auth-warning bgsave
result=$(redis-cli -a 123456 --no-auth-warning info Persistence | grep rdb_bgsave_in_progress | sed -rn 's/.*:([0-9]+).*/\1/p')
until [ $result -eq 0 ]; do
sleep 1
result=$(redis-cli -a 123456 --no-auth-warning info Persistence | grep rdb_bgsave_in_progress | sed -rn 's/.*:([0-9]+).*/\1/p')
done
DATE=$(date +%F_%H-%M-%S)
[ -e $BACKUP ] || {
mkdir -p $BACKUP
chown -R redis.redis $BACKUP
}
mv $DIR/$FILE $BACKUP/dump_6379-${DATE}.rdb
action "Backup redis RDB"

# 执行
[root@centos8 ~]#bash redis_backup_rdb.sh
Background saving started
Backup redis RDB [ OK ]
[root@centos8 ~]#ll /backup/redis-rdb/ -h
total 143M
-rw-r--r-- 1 redis redis 143M Oct 21 11:08 dump_6379-2020-10-21_11-08-47.rdb
AOF 模式

按照操作顺序依次将操作追加到指定的日志文件末尾

AOF 和 RDB 一样使用了写时复制机制,AOF 默认为每秒钟 fsync 一次,即将执行的命令保存到 AOF 文件当中,这样即使 redis 服务器发生故障的话最多只丢失 1 秒钟之内的数据,也可以设置不同的 fsync 策略 always,即设置每次执行命令的时候执行 fsync,fsync 会在后台执行线程,所以主线程可以继续处理用户的正常请求而不受到写入 AOF 文件的 I/O 影响

同时启用 RDB 和 AOF,进行恢复时,默认 AOF 文件优先级高于 RDB 文件,即会使用 AOF 文件进行恢复

注意:AOF 模式默认是关闭的,第一次开启 AOF 后,并重启服务,因为 AOF 的优先级高于 RDB,而 AOF 默认没有文件存在,从而导致所有数据丢失

AOF rewrite 重写

将一些重复的,可以合并的,过期的数据重新写入一个新的 AOF 文件,从而节约 AOF 备份占用的硬盘空间,也能加速恢复过程,可以手动执行 bgrewriteaof 触发 AOF 或定义自动 rewrite 策略

AOF 文件重写由 Redis 自行触发,我们可以使用 bgrewriteaof 命令手动触发

1
2
3
4
5
6
7
8
9
# 开启aof
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
dir ./
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes

AOF 模式的优缺点

优点:

  • 数据安全性相对较高,根据所使用的 fsync 策略(fsync 是同步内存中 redis 所有已经修改的文件到存储设备),默认是 appendfsync everysec,即每秒执行一次 fsync,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据(fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)
  • 由于该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中不需要 seek,即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在 Redis 下一次启动之前,可以通过 redis-check-aof 工具来解决数据一致性的问题
  • AOF 文件体积变得过大时,可以自动在后台对 AOF 进行重写,重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,append 模式不断的将修改数据追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作
  • AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,也可以通过该文件完成数据的重建
    AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存,因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。导出(export)AOF 文件也非常简单,举个例子:如果你不小心执行了 FLUSHALL 命令,但只要 AOF 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHAL 命令,并重启 Redis,就可以将数据集恢复到 FLUSHALL 执行之前的状态

缺点:

  • 即使有些操作是重复的也会全部记录,AOF 的文件大小要大于 RDB 格式的文件
  • AOF 在恢复大数据集时的速度比 RDB 的恢复速度要慢
  • 根据 fsync 策略不同,AOF 速度可能会慢于 RDB
  • bug 出现的可能性更多
RDB 和 AOF 的选择

如果主要充当缓存功能,或者可以承受数分钟数据的丢失, 通常生产环境一般只需启用 RDB 即可,此也是默认值

如果数据需要持久保存,一点不能丢失,可以选择同时开启 RDB 和 AOF,一般不建议只开启 AOF

Redis 常用命令

  • info:显示当前节点 redis 运行状态信息
  • select:切换数据库,相当于在 MySQL 的 USE DBNAME 指令
  • keys:查看当前库下的所有 key,慎用!可以考虑禁用
  • bgsave:手动在后台执行 RDB 持久化操作
  • dbsize:返回当前库下的所有 key 数量
  • flushdb:强制清空当前库中的所有 key,慎用!可以考虑禁用
  • flushall:强制清空当前 redis 服务器所有数据库中的所有 key,即删除所有数据,慎用!可以考虑禁用
  • shutdown:安全关闭 redis
    1. 停止所有客户端
    2. 如果有至少一个保存点在等待,执行 SAVE 命令
    3. 如果 AOF 选项被打开,更新 AOF 文件
    4. 关闭 redis 服务器(server)

Redis 数据类型

  • 字符串 string
  • 列表 list
  • 集合 set
  • 有序集合 sorted set
  • 哈希 hash

消息队列

消息队列主要分为两种,这两种模式 Redis 都支持

  • 生产者/消费者模式
  • 发布者/订阅者模式

生产者消费者模式

list

发布者模式

在发布者订阅者模式下,发布者将消息发布到指定的 channel 里面,凡是监听该 channel 的消费者都会收到同样的

  • Publisher:发布者
  • Subscriber:订阅者
  • Channel:频道
1
2
3
4
5
127.0.0.1:6379> SUBSCRIBE channel1 # 订阅频道

127.0.0.1:6379> PUBLISH channel1 test1 # 发布消息,然后订阅者都能收到消息

127.0.0.1:6379> unsubscribe channel1 # 取消订阅

Redis 高可用和集群

Redis 主从复制

实现

  1. 确保主从的配置文件(redis.conf)中 bind 设置为 0.0.0.0

  2. 从节点执行:replicaof <masterip> <masterport>,例如 REPLICAOF 10.0.0.72 6379

    或者直接修改配置文件,如果使用命令配置,建议也修改配置文件,否则重启会失效

  3. INFO replication 查看主从复制信息,REPLICAOF no one 取消主从复制

如果设置了replica-read-only yes,则从节点只读,不能写

requirepass 和 masterauth:

  • requirepass:针对客户端,对登录权限做限制,redis 每个节点的 requirepass 可以是独立的,不同的
  • masterauth:master 设置,在 slave 节点数据库同步的时候用到

故障恢复

从节点故障:因为从节点只负责读,当从节点故障,修改代码,连接其他从节点即可

主节点故障:需要提升一个从节点为主节点,然后把其他从节点重新指向新的主节点

  1. 提升主节点:

    1
    2
    # 10.0.0.18
    127.0.0.1:6379> REPLICAOF NO ONE
  2. 其他从节点重新指向新的主节点

    1
    127.0.0.1:6379> SLAVEOF 10.0.0.18 6379

优化

主从复制分为全量同步和增量同步,全量复制一般发生在 Slave 首次初始化阶段,这时 Slave 需要将 Master 上的所有数据都复制一份

1
2
3
4
# 复制缓冲区大小,建议要设置足够大
repl-backlog-size 1mb
# 当没有slave需要同步的时候,多久可以释放环形队列:
repl-backlog-ttl 3600

避免全量复制:

  • 第一次全量复制不可避免,后续的全量复制可以利用小主节点(内存小),业务低峰时进行全量
  • 节点运行 ID 不匹配:主节点重启会导致 RUNID 变化,可能会触发全量复制,可以利用故障转移,例如哨兵或集群,而从节点重启动,避免导致全量复制
  • 复制积压缓冲区不足:当主节点生成的新数据大于缓冲区大小,从节点恢复和主节点连接后,会导致全量复制。解决方法可以将 repl-backlog-size 调大

避免全量复制:

  • 当主节点重启,多从节点复制

优化配置:

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
# 是否使用无盘同步RDB文件,默认为no,no为不使用无盘,需要将RDB文件保存到磁盘后再发送给slave,yes为支持无盘,支持无盘就是RDB文件不需要保存至本地磁盘,而且直接通过socket文件发送给slave
repl-diskless-sync no

# diskless时复制的服务器等待的延迟时间
repl-diskless-sync-delay 5

# slave端向server端发送ping的时间间隔,默认为10秒
repl-ping-slave-period 10

# 设置主从ping连接超时时间,超过此值无法连接,master_link_status显示为down,并记录错误日志
repl-timeout 60

# 是否启用TCP_NODELAY,如设置成yes,则redis会合并小的TCP包从而节省带宽, 但会增加同步延迟(40ms),造成master与slave数据不一致,假如设置成no,则redismaster会立即发送同步数据,没有延迟,yes关注性能,no关注redis服务中的数据一致性
repl-disable-tcp-nodelay no

# master的写入数据缓冲区,用于记录自上一次同步后到下一次同步过程中间的写入命令,计算公式:repl-backlog-size = 允许从节点最大中断时长 * 主实例offset每秒写入量,比如master每秒最大写入64mb,最大允许60秒,那么就要设置为64mb*60秒=3840MB(3.8G),建议此值是设置的足够大
repl-backlog-size 1mb

# 一段时间后slave没有连接到master,则backlog size的内存将会被释放。如果值为0则 表示永远不释放这部份内存
repl-backlog-ttl 3600

# slave端的优先级设置,值是一个整数,数字越小表示优先级越高。当master故障时将会按照优先级来选择slave端进行恢复,如果值设置为0,则表示该slave永远不会被选择
slave-priority 100

# 设置一个master的可用slave不能少于多少个,否则master无法执行写
min-replicas-to-write 1

# 设置至少有上面数量的slave延迟时间都大于多少秒时,master不接收写操作(拒绝写入)
min-slaves-max-lag 20

常见故障

  • master 密码不对
  • Redis 版本不一致
  • 没有设置 bind 地址或者 密码,导致无法远程连接
  • 配置不一致,例如:
    • 主从节点的 maxmemory 不一致,主节点内存大于从节点内存,主从复制可能丢失数据
    • rename-command 不一致,如在主节点定义了 flushdb,从节点没定义,结果执行 flushdb,不同步

Redis 哨兵

  • sentinel 实际上是一个特殊的 redis 服务器,很多 redis 命令不支持,默认监听在 26379/tcp 端口
  • 哨兵可以不与 redis 部署在一起,但是一般都部署在一起
  • Sentinel 节点个数应该为大于等于 3 且最好为奇数
  • Sentinel 进程监控 redis 集群中 Master 主服务器工作的状态,在 Master 主服务器发生故障时,可以实现 Master 和 Slave 服务器的切换,保证系统的高可用
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
bind 0.0.0.0
# 如果不设置密码,protected-mode需要设置为no,否则无法远程连接
protected-mode no
port 26379
daemonize no
pidfile /usr/local/redis/run/redis-sentinel.pid
logfile "/usr/local/redis/var/sentinel.log"

# sentinel announce-ip <ip>
# sentinel announce-port <port>
# sentinel announce-ip 1.2.3.4

dir /tmp

# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# 主从复制架构中,执行master的ip、端口,当2个哨兵标记master下线,则判定master客观下线
sentinel monitor mymaster 127.0.0.1 6379 2

# sentinel auth-pass <master-name> <password>
# 指定master的账号密码
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# sentinel down-after-milliseconds <master-name> <milliseconds>
# 超时没有返回ping或者直接返回错误,则标记为下线,默认3秒
sentinel down-after-milliseconds mymaster 30000

# sentinel parallel-syncs <master-name> <numreplicas>
# 故障发生后,从节点需要从新的主节点同步数据,此项规定并发同步数,即同时允许几个slave从master同步数据,设置1同步时间最久,对主节点压力最小
sentinel parallel-syncs mymaster 1

# sentinel failover-timeout <master-name> <milliseconds>
# failover(故障转移)的超时时间,用于以下四种情况:
# 1.同一个sentinel对同一个master两次failover之间的间隔时间
# 2.当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时
# 3.当想要取消一个正在进行的failover所需要的时间
# 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
sentinel failover-timeout mymaster 180000

# sentinel notification-script <master-name> <script-path>
# sentinel notification-script mymaster /var/redis/notify.sh
# sentinel client-reconfig-script <master-name> <script-path>
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

# 禁止修改脚本
sentinel deny-scripts-reconfig yes

启动服务后,redis 会对配置文件做一些修改

实现

范例:master:10.0.0.71、slave:10.0.0.72 和 10.0.0.73

  1. 修改配置文件

    1
    2
    3
    4
    5
    # 从节点修改 redis.conf
    replicaof 10.0.0.71 6379

    # 从节点修改 redis-sentinel.conf
    sentinel monitor mymaster 10.0.0.71 6379 2
  2. 启动 redis 和 redis-sentinel 服务

  3. 测试

  4. 手动下线主节点,让 10.0.0.72 提升为主节点

    1
    2
    3
    4
    5
    6
    [root@c72 ~]#vim /etc/redis.conf
    replica-priority 10 # 指定优先级,值越小sentinel会优先将之选为新的master,默为值为100

    [root@c71 ~]#redis-cli -p 26379
    127.0.0.1:26379> sentinel failover mymaster # 手动下线主节点,任意一个节点均可执行
    OK

应用程序连接

客户端不直接连接 redis,而是连接 sentinel,不过 sentinel 不是代理,以 python 为例:

1
2
3
4
5
6
7
#!/usr/bin/python3
import redis
from redis.sentinel import Sentinel
#连接哨兵服务器(主机名也可以用域名)
sentinel = Sentinel([
('10.0.0.8', 26379),('10.0.0.18', 26379),('10.0.0.28', 26379)
],socket_timeout=0.5)

Redis Cluster

Sentinel 机制可以解决 master 和 slave 角色的自动切换问题,但单个 Master 的性能瓶颈问题无法解决,类似于 MySQL 中的 MHA 功能,redis 3.0 之前版本中,生产环境一般使用哨兵模式

redis 3.0 版本之后推出了无中心架构的 redis cluster 机制,在无中心的 redis 集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接

Redis Cluster 特点:

  • 所有 Redis 节点使用(PING 机制)互联
  • 集群中某个节点的是否失效,是由整个集群中超过半数的节点监测都失效,才能算真正的失效
  • 客户端不需要代理即可直接连接 redis,应用程序中需要配置有全部的 redis 服务器 IP
  • redis cluster 把所有的 redis node 平均映射到 0-16383 个槽位(slot)上,读写需要到指定的 redisnode 上进行操作,因此有多少个 redis node 相当于 redis 并发扩展了多少倍,每个 redis node 承担 16384/N 个槽位
  • Redis cluster 预先分配 16384 个(slot)槽位,当需要在 redis 集群中写入一个 key -value 的时候,会使用 CRC16(key) mod 16384 之后的值,决定将 key 写入值哪一个槽位从而决定写入哪一个 Redis 节点上,从而有效解决单机瓶颈

Redis Cluster 架构

当主节点出现故障,其从节点会自动提升

实现

6 个节点 10.0.0.[71-76],其中 10.0.0.[71-73]是主节点、10.0.0.[74-76]是从节点

  1. 修改 redis.conf 相关配置

    1
    2
    3
    4
    5
    6
    # 开启集群
    cluster-enabled yes
    # 集群状态文件,记录主从关系及slot范围信息,由redis cluster集群自动创建和维护
    cluster-config-file nodes-6379.conf
    # 默认yes,一个节点下线整个集群不可用;设置为no,则其他正常节点还可以继续提供对外访问
    cluster-require-full-coverage no
  2. 启动 redis 服务

    1
    2
    3
    # 启动之前确保集群中的每个节点是干净的,需要清除dump.rdb、nodes-6379.conf等文件
    rm -rf /usr/local/redis/data/*
    systemctl start redis.service
  3. 创建集群

    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
    # --cluster-replicas 1 表示每个master对应一个slave节点
    [root@c71 data]$redis-cli --cluster create 10.0.0.71:6379 10.0.0.72:6379 10.0.0.73:6379 10.0.0.74:6379 10.0.0.75:6379 10.0.0.76:6379 --cluster-replicas 1
    >>> Performing hash slots allocation on 6 nodes...
    Master[0] -> Slots 0 - 5460
    Master[1] -> Slots 5461 - 10922
    Master[2] -> Slots 10923 - 16383
    Adding replica 10.0.0.75:6379 to 10.0.0.71:6379
    Adding replica 10.0.0.76:6379 to 10.0.0.72:6379
    Adding replica 10.0.0.74:6379 to 10.0.0.73:6379
    M: 52c617ce1f5e67ee2da39beb8ae99240914f6f6d 10.0.0.71:6379 # M:master
    slots:[0-5460] (5461 slots) master
    M: 4fd60cab86be22d0a9edac1a8b7ac07b293e497d 10.0.0.72:6379
    slots:[5461-10922] (5462 slots) master
    M: 8406e58b4d132960300074f431b0b4b9900c94a5 10.0.0.73:6379
    slots:[10923-16383] (5461 slots) master
    S: 6c7f8028a16d1f07e7be7a33fa23528e7e9d69c6 10.0.0.74:6379 # S:slave
    replicates 8406e58b4d132960300074f431b0b4b9900c94a5
    S: 01849d7567cee594e6ebe2b6f09d19008a1e625b 10.0.0.75:6379
    replicates 52c617ce1f5e67ee2da39beb8ae99240914f6f6d
    S: 461a59fd85df1839d77877e66aaa4a08ffff068f 10.0.0.76:6379
    replicates 4fd60cab86be22d0a9edac1a8b7ac07b293e497d
    Can I set the above configuration? (type 'yes' to accept): yes # 输入yes自动创建集群
    >>> Nodes configuration updated
    >>> Assign a different config epoch to each node
    >>> Sending CLUSTER MEET messages to join the cluster
    Waiting for the cluster to join
    ....
    >>> Performing Cluster Check (using node 10.0.0.71:6379)
    M: 52c617ce1f5e67ee2da39beb8ae99240914f6f6d 10.0.0.71:6379
    slots:[0-5460] (5461 slots) master # 分配的槽位
    1 additional replica(s) # 添加一个从节点
    S: 6c7f8028a16d1f07e7be7a33fa23528e7e9d69c6 10.0.0.74:6379
    slots: (0 slots) slave # 从节点没有分配槽位
    replicates 8406e58b4d132960300074f431b0b4b9900c94a5
    M: 8406e58b4d132960300074f431b0b4b9900c94a5 10.0.0.73:6379
    slots:[10923-16383] (5461 slots) master
    1 additional replica(s)
    S: 461a59fd85df1839d77877e66aaa4a08ffff068f 10.0.0.76:6379
    slots: (0 slots) slave
    replicates 4fd60cab86be22d0a9edac1a8b7ac07b293e497d
    M: 4fd60cab86be22d0a9edac1a8b7ac07b293e497d 10.0.0.72:6379
    slots:[5461-10922] (5462 slots) master
    1 additional replica(s)
    S: 01849d7567cee594e6ebe2b6f09d19008a1e625b 10.0.0.75:6379
    slots: (0 slots) slave
    replicates 52c617ce1f5e67ee2da39beb8ae99240914f6f6d
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.

    #观察以上结果,可以看到3组master/slave
    master:10.0.0.71---slave:10.0.0.74
    master:10.0.0.72---slave:10.0.0.75
    master:10.0.0.73---slave:10.0.0.76
  4. 在每个节点执行info replication 可以查看此节点的主从信息

  5. 在任意节点执行 cluster nodes可以查看所有节点的 ID 及连接信息

  6. 在任意节点执行 cluster info可以查看整个集群的状态信息

  7. 在任意节点执行 redis-cli --cluster info 10.0.0.71:6379 可以查看所有主节点的信息

  8. 在任意节点执行 redis-cli --cluster check 10.0.0.71:6379 可以查看所有节点的信息

管理

动态扩容

目前集群中有 6 个节点 10.0.0.[71-76],三主三从,现在要将 10.0.0.77 添加到集群中

  1. 添加节点:在任意一个节点上执行

    1
    redis-cli --cluster add-node 10.0.0.77:6379  <当前任意集群节点>:6379

    新节点是 master,且没有分配槽位

  2. 给新 master 节点分配槽位

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    redis-cli --cluster reshard <当前任意集群节点>:6379

    # 需要移动多少槽位? 16384/master个数,这里16384/4=4096
    How many slots do you want to move (from 1 to 16384)?4096
    # 哪个节点接收移动的槽位?当然是新添加的master节点了,输入10.0.0.77的node ID
    What is the receiving node ID?defa5ca3d7aaecea240a79ad2c9b572ebf32464a
    # 哪些节点提供槽位,all表示当前所有master节点,或者输入一个或多个node ID,最后输入done表示结束
    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:all
    # 确认分配
    Do you want to proceed with the proposed reshard plan (yes/no)? yes
    • 这样,新的 master 节点就分配了 4096 个槽位,由于这些槽位是其他三个 master 节点平均分配给新 master 节点的,所以避免不了槽位碎片化的问题

    • 重新分配槽位后,之前节点的角色不变,但属关系可能发生改变,例如本来 10.0.0.71 的从节点是 10.0.0.74,扩容后它的从节点成了 10.0.0.75,而 10.0.0.74 成了 10.0.0.3 的从节点

    • 这种扩容方法是在线的,不需要备份数据,如果数据量比较大的话,建议先备份

  3. 为新的 master 添加新的 slave 节点

    新的 master 节点没有 slave 节点,需要添加一个(10.0.0.78)以保证高可用,任意节点执行以下命令:

    1
    redis-cli --cluster add-node 10.0.0.78:6379 <任意集群节点>:6379 --cluster-slave --cluster-master-id defa5ca3d7aaecea240a79ad2c9b572ebf32464a

    或者先将新节点加入集群,再修改为 slave:

    1
    2
    3
    4
    5
    6
    redis-cli --cluster add-node 10.0.0.78:6379 <任意集群节点>:6379
    [root@c78 ~]$redis-cli
    # 查看当前集群节点,找到目标master 的ID
    127.0.0.1:6379> CLUSTER NODES
    # 将其设置slave,命令格式为cluster replicate MASTERID
    127.0.0.1:6379> CLUSTER REPLICATE 8defa5ca3d7aaecea240a79ad2c9b572ebf32464a
动态缩容
  • 从节点:在任意节点上执行redis-cli --cluster del-node host:port node_id

    删除 slave 节点 10.0.0.75:

  • 主节点:需要在删除之前先把槽位移动到其他 master 节点上,可以使用交互式和命式两种方式

    删除 master 节点 10.0.0.71:

    • 交互式:在任意节点上执行

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      redis-cli --cluster reshard <任意集群节点>:6379
      ...
      M: 52c617ce1f5e67ee2da39beb8ae99240914f6f6d 10.0.0.71:6379
      slots:[1364-5460] (4096 slots) master
      ...

      # 需要移动多少槽位? 10.0.0.71有4096个槽位,需要平均分给其他三个master节点,所以我们分1365、1365、1366三次移动
      How many slots do you want to move (from 1 to 16384)?1365
      # 哪个节点接收移动的槽位?经过观察10.0.0.77适合接受,依据是尽量不造成槽位的碎片化
      What is the receiving node ID?defa5ca3d7aaecea240a79ad2c9b572ebf32464a
      # 哪些节点提供槽位,当然是要删除的10.0.0.71了
      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:52c617ce1f5e67ee2da39beb8ae99240914f6f6d
      Source node #2:done
      # 确认分配
      ...
      Moving slot 2727 from 10.0.0.71:6379 to 10.0.0.77:6379: # 注意观察,不要出错
      ...
      Do you want to proceed with the proposed reshard plan (yes/no)? yes
    • 命令式:在任意节点上执行

      1
      2
      3
      4
      5
      # 移动1365个槽位到10.0.0.72
      redis-cli --cluster reshard <任意集群节点>:6379 --cluster-slots 1365 --cluster-from 52c617ce1f5e67ee2da39beb8ae99240914f6f6d --cluster-to 4fd60cab86be22d0a9edac1a8b7ac07b293e497d --cluster-yes

      # 移动最后的1366个槽位到10.0.0.73
      redis-cli --cluster reshard <任意集群节点>:6379 --cluster-slots 1366 --cluster-from 52c617ce1f5e67ee2da39beb8ae99240914f6f6d --cluster-to 8406e58b4d132960300074f431b0b4b9900c94a5 --cluster-yes

      10.0.0.71 上的槽位都移走了之后,其 slave 节点(如果有)也会变成了其他 master 节点的 slave 节点

    最后执行 redis-cli --cluster del-node host:port node_id 即可将 10.0.0.71 节点删除

    1
    2
    3
    4
    [root@c71 ~]$redis-cli --cluster del-node 10.0.0.71:6379 52c617ce1f5e67ee2da39beb8ae99240914f6f6d
    >>> Removing node 52c617ce1f5e67ee2da39beb8ae99240914f6f6d from cluster 10.0.0.71:6379
    >>> Sending CLUSTER FORGET messages to the cluster...
    >>> SHUTDOWN the node.
导入现有数据至集群

在任意集群节点上执行:

1
redis-cli --cluster import <任意集群节点>:6379 --cluster-from <外部Redis>:6379 --cluster-copy --cluster-replace
  • –cluster-replace:当导入的数据和已有的数据有重复的 key,–cluster-replace 表示替换为导入的数据
集群偏斜

redis cluster 多个节点运行一段时间后,可能会出现倾斜现象,某个节点数据偏多,内存消耗更大,或者接受用户请求访问更多,发生倾斜的原因可能如下:

  • 不同槽对应的键值数量差异较大
  • 包含 bigkey,建议少用
  • 内存相关配置不一致

获取指定槽位中对应 key 值个数

1
redis-cli cluster countkeysinslot {slot的值}

执行自动的槽位重新平衡分布,但会影响客户端的访问,慎用

1
redis-cli --cluster rebalance <任意集群节点>:6379

获取 bigkey,建议在 slave 节点运行

1
redis-cli --bigkeys
解散集群

systemctl stop redis.service关闭所有的 redis 服务,然后删除 nodes-6379.conf 文件,修改 redis.conf:cluster-enabled no

redis cluster 的局限性

  • 命令无法跨节点使用,所以 mset、mget、sunion 等操作支持不友好
  • 客户端维护更复杂,客户端为了可以直接定位某个具体的 key 所在的节点,它就需要缓存槽位相关信息,这样才可以准确快速地定位到相应的节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整
  • 不支持多个数据库,集群模式下只有一个 db0
  • 主从复制只有一层,不支持级联复制
  • 节点因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout)被判断下线,这种 failover 是没有必要的

以上缺点只有第一点是比较麻烦的

ansible 介绍和架构

ansible 特性

  • 基于 python 实现
  • 模块化:支持自定义模块,可以使用任何语言编写模块
  • 安全:基于 OpenSSH
  • 部署简单:被控端不需要配置环境
  • 幂等性:一个任何执行一遍和执行 n 遍效果一样,不因重复执行带来意外情况,此特性非绝对
  • 支持 playbook 编排任务,YAML 格式,编排任务,支持丰富的数据结构
  • 较强大的多层解决方案 role

ansible 架构

  • INVENTORY:Ansible 管理主机的清单/etc/anaible/hosts
  • MODULES:Ansible 执行命令的功能模块,多数为内置核心模块,也可自定义
  • PLUGINS:模块功能的补充,如连接类型插件、循环插件、变量插件、过滤插件等,该功能不常用
  • API:供第三方程序调用的应用程序编程接口

ansible 命令执行来源

  • USER:普通用户,即 SYSTEM ADMINISTRATOR
  • PLAYBOOKS:任务剧本(任务集),编排定义 Ansible 任务集的配置文件,由 Ansible 顺序依次执行,通常是 JSON 格式的 YML 文件
  • CMDB(配置管理数据库) API 调用
  • PUBLIC/PRIVATE CLOUD API 调用
  • USER-> Ansible Playbook -> Ansibile

注意事项

  • 执行 ansible 的主机一般称为管理端, 主控端,中控,master 或堡垒机
  • 主控端 Python 版本需要 2.6 或以上
  • 如果被控端 Python 版本小于 2.4,则需要安装 python-simplejson
  • 被控端如开启 SELinux 需要安装 libselinux-python
  • windows 不能做为主控端

ansible 安装和入门

pip 安装后没有配置文件,需要手动创建,有以下四种方式:

  1. ANSIBLE_CONFIG (environment variable if set)
  2. ansible.cfg (in the current directory)
  3. ~/.ansible.cfg (in the home directory)
  4. /etc/ansible/ansible.cfg

ansible 相关文件

  • /etc/ansible/ansible.cfg 主配置文件,配置 ansible 工作特性
  • /etc/ansible/hosts 主机清单
  • /etc/ansible/roles/ 存放角色的目录

ansible 主配置文件

默认为:/etc/ansible/ansible.cfg

参考文档:http://www.ansible.com.cn/docs/intro_configuration.html

inventory 主机清单

默认为:/etc/ansible/hosts

参考文档:http://www.ansible.com.cn/docs/intro_inventory.html?highlight=hosts

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ntp.magedu.com
[webservers]
www1.magedu.com:2222
www2.magedu.com
[dbservers]
db1.magedu.com
db2.magedu.com
db3.magedu.com
[websrvs]
www[1:100].example.com
[dbsrvs]
db-[a:f].example.com
[appsrvs]
# 表示 10.0.0.1 - 10.0.0.100
10.0.0.[1:100]
[ex-lb]
# 定义主机变量,这些变量定义后可在 playbooks 中使用
10.0.2.1 LB_ROLE=backup EX_APISERVER_VIP=10.0.2.188 EX_APISERVER_PORT=8443
10.0.2.2 LB_ROLE=master EX_APISERVER_VIP=10.0.2.188 EX_APISERVER_PORT=8443

给主机组设置变量,示例:

1
2
3
4
5
6
7
8
9
10
11
[all:vars]      # 为所有主机组设置变量
CONTAINER_RUNTIME="docker"
CLUSTER_NETWORK="calico"
PROXY_MODE="ipvs"
SERVICE_CIDR="192.168.0.0/16"
CLUSTER_CIDR="10.10.0.0/16"
NODE_PORT_RANGE="30000-60000"
CLUSTER_DNS_DOMAIN="ljk.local."
bin_dir="/usr/local/bin"
ca_dir="/etc/kubernetes/ssl"
base_dir="/etc/ansible"

注意:ansible 会隐式创建一个 localhost 主机,只是 localhost 不能匹配 all

ansible 相关工具

  • ansible:主程序,临时命令执行工具
  • ansible-doc:查看配置文档,模块功能查看工具,相当于 man
  • ansible-playbook:定制自动化任务,编排剧本工具,相当于脚本
  • ansible-pull:远程执行命令的工具
  • ansible-vault:文件加密工具
  • ansible-console:基于 Console 界面与用户交互的执行工具
  • ansible-galaxy:下载/上传优秀代码或 Roles 模块的官网平台

ansible

通过 ssh 协议,实现对远程主机的配置管理、应用部署、任务执行等功能

注意:使用 ansible 之前,需要配置各个被控端基于 key 的密钥认证

1
ansible <host-pattern> [option]...
  • host-pattern:匹配被控主机

    • all:inventory 中的所有主机
    • 支持通配符
    • 支持正则表达式:需要以 ‘~’ 开头
    • 使用:表示或关系
    • 使用:&表示与关系
    • 使用:!表示非关系

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # all
    ansible all –m ping
    # 统配符
    ansible "*" -m ping
    ansible 192.168.1.* -m ping
    ansible "srvs" -m ping
    ansible "10.0.0.6 10.0.0.7" -m ping
    # 或
    ansible "websrvs:appsrvs" -m ping
    ansible "192.168.1.10:192.168.1.20" -m ping
    # 与
    ansible "websrvs:&dbsrvs" –m ping # 在websrvs组并且在dbsrvs组中的主机
    # 非 注意此处为单引号
    ansible 'websrvs:!dbsrvs' –m ping #在websrvs组,但不在dbsrvs组中的主机
    # 综合逻辑
    ansible 'websrvs:dbsrvs:&appsrvs:!ftpsrvs' –m ping
    # 正则表达式
    ansible "~(web|db).*\.magedu\.com" –m ping
  • 常用 option

    • –version:显示版本等信息
    • -m module:指定模块,默认为 command
    • -a|–args MODULE_ARGS:指定模块的参数
    • –list-hosts:显示主机列表,可简写 –lists
    • -C|–check:检查,并不执行
    • -T|–timeout=TIMEOUT:执行命令的超时时间,默认 10s
    • -k|–ask-pass:提示输入 ssh 连接密码,默认 Key 验证
    • -u| –user REMOTE_USER:指定远程执行的用户
    • -b|–become:代替旧版的 sudo 切换
    • –become-user=USERNAME:指定 sudo 的 runas 用户,默认为 root
    • -K|–ask-become-pass:提示输入 sudo 时的口令
ansible 命令执行顺序
  1. 加载配置文件,默认/etc/ansible/ansible.cfg
  2. 加载对应的模块文件,如:command
  3. 将模块或命令生成临时 py 文件,并将该文件传输至远程服务器远程服务器:$HOME/.ansible/tmp/ansible-tmp-数字/XXX.PY
  4. 给文件+x 权限
  5. 执行并返回结果
  6. 删除临时 py 文件,退出
ansible 执行状态

在 ansible.cfg 的[colors]配置项中设置:

  • 绿色:执行成功且没有对目标主机做更改
  • 黄色:执行成功且对目标主机做了更改
  • 红色:执行失败
ansible 使用示例
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
(venv) [root@c71 bin]$ansible all -m command -a 'ls ~'
10.0.0.72 | CHANGED | rc=0 >>
anaconda-ks.cfg
init.sh
original-ks.cfg
10.0.0.82 | CHANGED | rc=0 >>
anaconda-ks.cfg
init.sh
original-ks.cfg
10.0.0.81 | CHANGED | rc=0 >>
anaconda-ks.cfg
init.sh
original-ks.cfg
(venv) [root@c71 bin]$ansible all -m ping
10.0.0.72 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
10.0.0.81 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
10.0.0.82 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}

ansible-doc

显示模块帮助,相当于 man

1
ansible-doc [options] [module...]

options:

  • -l|–list:列出可用模块
  • -s|–snippet:显示指定模块的 playbook 片段

示例:

1
2
3
4
5
6
#列出所有模块
ansible-doc -l
#查看指定模块帮助用法
ansible-doc ping
#查看指定模块帮助用法
ansible-doc -s ping

ansible-playbook

执行编写好的 playbook 任务

1
ansible-playbook <filename.yml> ... [options]
  • –syntax-check:语法检查
  • -C|–check:只检测可能会发生的改变,但不真正执行操作
  • –list-hosts:列出运行任务的主机
  • –list-tags:列出 tag
  • –list-tasks:列出 task
  • –limit 主机列表:只针对主机列表中的特定主机执行
  • -v -vv -vvv:显示过程

ansible-vault

加密解密 yml 文件

1
ansible-vault {create,decrypt,edit,view,encrypt,encrypt_string,rekey} ...
  • encrypt:加密
  • decrypt:解密
  • view:查看
  • edit:编辑加密文件
  • rekey:修改口令
  • create:创建新文件

ansible-console

交互执行命令,支持 tab,ansible 2.0+新增

1
执行用户@当前操作的主机组 (当前组的主机数量)[f:并发数]$

常用子命令:

  • 设置并发数: forks n 例如: forks 10
  • 切换组: cd 主机组 例如: cd web
  • 列出当前组主机列表: list
  • 列出所有的内置命令: ?help

示例:

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
(venv) [root@c71 bin]$ansible-console
Welcome to the ansible console.
Type help or ? to list commands.

root@all (3)[f:5]$ ping
10.0.0.72 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
10.0.0.82 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
10.0.0.81 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
root@all (3)[f:5]$ list
10.0.0.72
10.0.0.81
10.0.0.82
root@all (3)[f:5]$ ls ~
10.0.0.72 | CHANGED | rc=0 >>
anaconda-ks.cfg
init.sh
original-ks.cfg
10.0.0.82 | CHANGED | rc=0 >>
anaconda-ks.cfg
init.sh
original-ks.cfg
10.0.0.81 | CHANGED | rc=0 >>
anaconda-ks.cfg
init.sh
original-ks.cfg
root@all (3)[f:5]$ exit

(venv) [root@c71 bin]$

ansible-galaxy

连接 https://galaxy.ansible.com 下载相应的 roles

示例:

1
2
3
4
5
6
7
#列出所有已安装的galaxy
ansible-galaxy list
#安装galaxy
ansible-galaxy install geerlingguy.mysql
ansible-galaxy install geerlingguy.redis
#删除galaxy
ansible-galaxy remove geerlingguy.redis

ansible 常用模块

本文以 ansible 2.10.5 为准

虽然模块众多,但最常用的模块也就 2,30 个而已,针对特定业务只用 10 几个模块

常用模块帮助文档参考:

1
2
3
4
5
# 官网
https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html
https://docs.ansible.com/ansible/latest/modules/modules_by_category.html
# 中文翻译
http://www.ansible.com.cn/docs/modules.html

使用 ansible-doc 可以查看每个模块的具体用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 以 command 模块为例
ansible-doc -s command # 查看模块的可用选项
ansible-doc command # 查看模块的详细帮助信息

# ansible-doc module 的内容是包含 ansible-doc -s module 的

# 打印模块的选项
$ansible-doc -s shell | tail -n +3 | egrep '^[[:space:]]{6}[^[:space:]]+:' | cut -d ':' -f 1 | tr -d ' '
chdir
cmd
creates
executable
free_form
removes
stdin
stdin_add_newline
warn

command

默认模块,可忽略 -m 选项
注意:此命令不支持 $VARNAME < > | ; & 等,用 shell 模块实现

1
2
3
4
5
6
7
8
9
10
11
# 以下两条命令是一样的,-m 可以省略,-a 不能省略
$ansible all -a 'hostname'
$ansible all -m command -a 'hostname'

$ansible all -a 'chdir=/etc pwd'
10.0.2.2 | CHANGED | rc=0 >>
/etc
10.0.2.3 | CHANGED | rc=0 >>
/etc
10.0.2.1 | CHANGED | rc=0 >>
/etc
1
2
3
4
5
6
# 选项:ansible-doc -s command
free_form # 必须参数,指定需要远程执行的命令,free_form并不是一个"实际存在"的参数名,比如,当我们想要在远程主机上执行ls命令时,我们并不需要写成"free_form=ls" ,这样写反而是错误的,因为并没有任何参数的名字是free_form,当我们想要在远程主机中执行ls命令时,直接写成ls即可,这就是free_form参数的含义
chdir # 指定一个目录,在执行对应的命令之前,会先进入到chdir参数指定的目录中
creates # 当指定的文件存在时,就不执行对应命令
removes # 与creates参数的作用正好相反,它的作用是当指定的文件不存在时,就不执行对应命令
stdin #

shell

和 command 相似,但是功能更丰富,支持各种符号,比如 *、$、>

注意:类似 cat /tmp/test.md | awk -F'|' '{print $1,$2}' &> /tmp/example.txt
些复杂命令,即使使用 shell 也可能会失败,解决办法:写到脚本时,copy 到远程,执行,再把需要的
结果拉回执行命令的机器

建议将 shell 模块替换 command,设为默认模块

1
2
$vim /etc/ansible/ansible.cfg
module_name = shell
1
2
3
4
5
6
7
8
9
10
$ansible-doc -s shell | tail -n +3 | egrep '^[[:space:]]{6}[^[:space:]]+:' | cut -d ':' -f 1 | tr -d ' '
chdir
cmd
creates
executable # 指定执行程序,默认是 /bin/bash
free_form
removes
stdin
stdin_add_newline
warn

script

在远程主机上运行 ansible 服务器上的脚本(无需执行权限)

1
2
3
4
5
6
7
8
$ansible-doc -s script | tail -n +3 | egrep '^[[:space:]]{6}[^[:space:]]+:' | cut -d ':' -f 1 | tr -d ' '
chdir
cmd
creates
decrypt
executable
free_form
removes

copy

从 ansible 服务器主控端复制文件到远程主机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
src  # 需要copy的文件或目录
dest # 文件将被拷贝到远程主机的哪个目录中,dest为必须参数
content # 当不指定src时,可以使用content直接指定文件内容,src与content两个参数必有其一,否则会报错
force # 当远程主机的目标路径中已经存在同名文件,并且与ansible主机中的文件内容不同时,是否强制覆盖,可选值有yes和no,默认值为yes,表示覆盖,如果设置为no,则不会执行覆盖拷贝操作,远程主机中的文件保持不变
backup # 当远程主机的目标路径中已经存在同名文件,并且与ansible主机中的文件内容不同时,是否对远程主机的文件进行备份,可选值有yes和no,当设置为yes时,会先备份远程主机中的文件,然后再将ansible主机中的文件拷贝到远程主机
owner # 指定文件拷贝到远程主机后的属主,但是远程主机上必须有对应的用户,否则会报错
group # 指定文件拷贝到远程主机后的属组,但是远程主机上必须有对应的组,否则会报错
mode # 指定文件拷贝到远程主机后的权限,如果你想将权限设置为"rw-r--r--",则可以使用mode=0644表示,如果你想要在user对应的权限位上添加执行权限,则可以使用mode=u+x表示
attributes
checksum
decrypt
directory_mode
follow
local_follow
remote_src
selevel
serole
setype
seuser
unsafe_writes
validate

示例:

1
2
3
4
5
6
7
ansible test70 -m copy -a "src=/testdir/copytest dest=/opt/"
ansible test70 -m copy -a 'content="aaa\nbbb\n" dest=/opt/test'
ansible test70 -m copy -a "src=/testdir/copytest dest=/opt/ force=no"
ansible test70 -m copy -a "src=/testdir/copytest dest=/opt/ backup=yes"
ansible test70 -m copy -a "src=/testdir/copytest dest=/opt/ owner=zsy"
ansible test70 -m copy -a "src=/testdir/copytest dest=/opt/ group=zsy"
ansible test70 -m copy -a "src=/testdir/copytest dest=/opt/ mode=0640"

fetch

从远程主机提取文件至 ansible 的主控端,和 copy 相反,目前不支持目录

1
2
3
4
5
6
$ansible-doc -s fetch | tail -n +3 | egrep '^[[:space:]]{6}[^[:space:]]+:' | cut -d ':' -f 1 | tr -d ' '
dest # 保存文件的目录
fail_on_missing # bool,当源文件不存在的时候,标识为失败
flat # bool,文件覆盖
src # 从远程主机上获取的文件,必须是文件,不能为目录
validate_checksum # bool,文件fetch之后进行md5检查

file

file 模块可以帮助我们完成一些对文件的基本操作,比如,创建文件或目录、删除文件或目录、修改文件权限等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
path  # 指定要操作的文件或目录,在之前版本的ansible中,使用dest或者name
state # 此参数非常灵活,此参数对应的值需要根据情况设定,比如,当我们需要在远程主机中创建一个目录的时候,我们需要使用path参数指定对应的目录路径,假设,我想要在远程主机上创建/testdir/a/b目录,那么我则需要设置path=/testdir/a/b,但是,我们无法从"/testdir/a/b"这个路径看出b是一个文件还是一个目录,ansible也同样无法单单从一个字符串就知道你要创建文件还是目录,所以,我们需要通过state参数进行说明,当我们想要创建的/testdir/a/b是一个目录时,需要将state的值设置为directory,"directory"为目录之意,当它与path结合,ansible就能知道我们要操作的目标是一个目录,同理,当我们想要操作的/testdir/a/b是一个文件时,则需要将state的值设置为touch,当我们想要创建软链接文件时,需将state设置为link,想要创建硬链接文件时,需要将state设置为hard,当我们想要删除一个文件时(删除时不用区分目标是文件、目录、还是链接),则需要将state的值设置为absent,"absent"为缺席之意,当我们想让操作的目标"缺席"时,就表示我们想要删除目标
src # 当state设置为link或者hard时,表示我们想要创建一个软链或者硬链,所以,我们必须指明软链或硬链链接的哪个文件,通过src参数即可指定链接源
force # 当state=link的时候,可配合此参数强制创建链接文件,当force=yes时,表示强制创建链接文件,不过强制创建链接文件分为两种情况,情况一:当你要创建的链接文件指向的源文件并不存在时,使用此参数,可以先强制创建出链接文件。情况二:当你要创建链接文件的目录中已经存在与链接文件同名的文件时,将force设置为yes,回将同名文件覆盖为链接文件,相当于删除同名文件,创建链接文件。情况三:当你要创建链接文件的目录中已经存在与链接文件同名的文件,并且链接文件指向的源文件也不存在,这时会强制替换同名文件为链接文件
owner # 用于指定被操作文件的属主,属主对应的用户必须在远程主机中存在,否则会报错
group # 用于指定被操作文件的属组,属组对应的组必须在远程主机中存在,否则会报错
mode # 用于指定被操作文件的权限,比如,如果想要将文件权限设置为"rw-r-x---",则可以使用mode=650进行设置,或者使用mode=0650,效果也是相同的,如果你想要设置特殊权限,比如为二进制文件设置suid,则可以使用mode=4700,很方便吧
recurse # 当要操作的文件为目录,将recurse设置为yes,可以递归的修改目录中文件的属性
access_time
access_time_format
attributes
follow
modification_time
modification_time_format
selevel
serole
setype
seuser
unsafe_writes

示例:

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
#创建空文件
ansible all -m file -a 'path=/data/test.txt state=touch'
ansible all -m file -a 'path=/data/test.txt state=absent'
ansible all -m file -a "path=/root/test.sh owner=wang mode=755"
#创建目录
ansible all -m file -a "path=/data/mysql state=directory owner=mysql group=mysql"
#创建软链接
ansible all -m file -a 'src=/data/testfile path|dest|name=/data/testfile-link state=link'
#创建目录
ansible all -m file -a 'path=/data/testdir state=directory'
#递归修改目录属性
ansible all -m file -a "path=/data/mysql state=directory owner=mysql group=mysql recurse=yes"

# 在test70主机上创建一个名为testfile的文件,如果testfile文件已经存在,则会更新文件的时间戳,与touch命令的作用相同
ansible test70 -m file -a "path=/testdir/testfile state=touch"
# 在test70主机上创建一个名为testdir的目录,如果testdir目录已经存在,则不进行任何操作
ansible test70 -m file -a "path=/testdir/testdir state=directory"
# 在test70上为testfile文件创建软链接文件,软链接名为linkfile,执行下面命令的时候,testfile已经存在
ansible test70 -m file -a "path=/testdir/linkfile state=link src=/testdir/testfile"
# 在test70上为testfile文件创建硬链接文件,硬链接名为hardfile,执行下面命令的时候,testfile已经存在
ansible test70 -m file -a "path=/testdir/hardfile state=hard src=/testdir/testfile"
# 在创建链接文件时,如果源文件不存在,或者链接文件与其他文件同名时,强制覆盖同名文件或者创建链接文件,参考上述force参数的解释
ansible test70 -m file -a "path=/testdir/linkfile state=link src=sourcefile force=yes"
# 删除远程机器上的指定文件或目录
ansible test70 -m file -a "path=/testdir/testdir state=absent"
# 在创建文件或目录的时候指定属主,或者修改远程主机上的文件或目录的属主
ansible test70 -m file -a "path=/testdir/abc state=touch owner=zsy"
ansible test70 -m file -a "path=/testdir/abc owner=zsy"
ansible test70 -m file -a "path=/testdir/abc state=directory owner=zsy"
# 在创建文件或目录的时候指定属组,或者修改远程主机上的文件或目录的属组
ansible test70 -m file -a "path=/testdir/abb state=touch group=zsy"
ansible test70 -m file -a "path=/testdir/abb group=zsy"
ansible test70 -m file -a "path=/testdir/abb state=directory group=zsy"
# 在创建文件或目录的时候指定权限,或者修改远程主机上的文件或目录的权限
ansible test70 -m file -a "path=/testdir/abb state=touch mode=0644"
ansible test70 -m file -a "path=/testdir/abb mode=0644"
ansible test70 -m file -a "path=/testdir/binfile mode=4700"
ansible test70 -m file -a "path=/testdir/abb state=directory mode=0644"
# 当操作远程主机中的目录时,同时递归的将目录中的文件的属主属组都设置为zsy
ansible test70 -m file -a "path=/testdir/abd state=directory owner=zsy group=zsy recurse=yes"

unarchive

解包解压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
copy  # 默认为yes,当copy=yes,拷贝的文件是从ansible主机复制到远程主机上,如果设置为copy=no,会在远程主机上寻找src源文件
remote_src # 和copy功能一样且互斥,yes表示在远程主机,不在ansible主机,no表示文件在ansible主机上
src # 源路径,可以是ansible主机上的路径,也可以是远程主机(被管理端或者第三方主机)上的路径,如果是远程主机上的路径,则需要设置copy=no
dest # 远程主机上的目标路径
mode # 设置解压缩后的文件权限
attributes
creates
decrypt
exclude
extra_opts
group
keep_newer
list_files
owner
selevel
serole
setype
seuser
unsafe_writes
validate_certs

示例:

1
2
3
4
ansible all -m unarchive -a 'src=/data/foo.tgz dest=/var/lib/foo owner=wang group=bin'
ansible all -m unarchive -a 'src=/tmp/foo.zip dest=/data copy=no mode=0777'
ansible all -m unarchive -a 'src=https://example.com/example.zip dest=/data copy=no'
ansible websrvs -m unarchive -a 'src=https://releases.ansible.com/ansible/ansible-2.1.6.0-0.1.rc1.tar.gz dest=/data/ owner=mysql remote_src=yes'

archive

打包压缩保存在被管理节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ansible-doc -s archive | tail -n +3 | egrep '^[[:space:]]{6}[^[:space:]]+:' | cut -d ':' -f 1 | tr -d ' '
attributes
dest
exclude_path
force_archive
format
group
mode
owner
path
remove
selevel
serole
setype
seuser
unsafe_writes

示例:

1
ansible websrvs -m archive -a 'path=/var/log/ dest=/data/log.tar.bz2 format=bz2 owner=wang mode=0600'

hostname

管理主机名

示例:

1
ansible 10.0.0.18 -m hostname -a 'name=node18.magedu.com'

cron

计划任务,支持时间:minute,hour,day,month,weekday

1
2
3
4
5
6
7
8
# 示例1 每天的1点5分输出 test 字符
5 1 * * * echo test
# 示例2 每3天执行一次计划任务,于当天的1点1分执行,具体任务为输出 test 字符
1 1 */3 * * echo test
# 示例3 每次系统启动后需要执行一次计划任务,具体任务为输出 test 字符
@reboot echo test
# 示例4 每小时执行一次计划任务,具体任务为输出 test 字符
@hourly echo test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
backup  # yes|no,当修改或者删除对应的计划任务时,是否先对计划任务进行备份,如果设置为yes,cron 模块会在远程主机的 /tmp 目录下创建备份文件
cron_file
disabled # 当计划任务有名称时,我们可以根据名称使对应的任务”失效”(注释掉对应的任务)。注意,使用此参数时,除了需要指定任务的名称,还需要同时指定任务的job 以及任务的时间设定,而且任务的时间设定必须和对应任务完全相同,否则在注释任务的同时,任务的时间设定会被修改
env
minute # 设置计划任务中分钟设定位的值,当不使用此参数时,分钟设定位的值默认为”*”
hour # 设置计划任务中小时设定位的值,当不使用此参数时,小时设定位的值默认为”*”
day # 设置计划任务中日设定位的值,当不使用此参数时,日设定位的值默认为”*”
month # 设置计划任务中月设定位的值,当不使用此参数时,月设定位的值默认为”*”
weekday # 设置计划任务中周几设定位的值,当不使用此参数时,周几设定位的值默认为”*”
insertafter
insertbefore
job # 此参数用于指定计划的任务中需要实际执行的命令或者脚本,比如上例中的 “echo test” 命令
name # 此参数用于设置计划任务的名称,计划任务的名称会在注释中显示,当不指定计划任务的名称时,ansible 会默认为计划任务加入注释,注释的内容为 #Ansible: None,假设指定计划任务的名称为 test,那么注释的内容为#Ansible: test,在一台机器中,计划任务的名称应该具有唯一性,方便我们以后根据名称修改或删除计划任务
reboot
special_time # 在上述示例3与示例4中,计划任务的时间设定格式为 @reboot 或者@hourly。@reboot 表示重启时执行,@hourly 表示每小时执行一次,相当于设置成”0 0 * * *” ,这种@开头的时间设定格式则需要使用 special_time 参数进行设置,special_time 参数的可用值有 reboot(重启后)、yearly(每年)、annually(每年,与yearly相同)、monthly(每月)、weekly(每周)、daily(每天)、hourly(每时)
# 注意:当上述时间单位设定参数都未指定时,计划任务的时间设定默认会被设定为”* * * * *”,这样表示每秒都会执行一次计划任务,所以,在使用cron模块时,我们应该确定对应的时间参数设置正确
state # 当计划任务有名称时,我们可以根据名称修改或删除对应的任务,当删除计划任务时,需要将 state 的值设置为 absent
user # 此参数用于设置当前计划任务属于哪个用户,当不使用此参数时,默认为管理员用户

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#备份数据库脚本
[root@centos8 ~]#cat /root/mysql_backup.sh
#!/bin/bash
mysqldump -A -F --single-transaction --master-data=2 -q -uroot |gzip > /data/mysql_`date +%F_%T`.sql.gz
#创建任务
ansible 10.0.0.8 -m cron -a 'hour=2 minute=30 weekday=1-5 name="backup mysql" job=/root/mysql_backup.sh'
ansible websrvs -m cron -a "minute=*/5 job='/usr/sbin/ntpdate ntp.aliyun.com &>/dev/null' name=Synctime"
#禁用计划任务
ansible websrvs -m cron -a "minute=*/5 job='/usr/sbin/ntpdate 172.20.0.1 &>/dev/null' name=Synctime disabled=yes"
#启用计划任务
ansible websrvs -m cron -a "minute=*/5 job='/usr/sbin/ntpdate 172.20.0.1 &>/dev/null' name=Synctime disabled=no"
#删除任务
ansible websrvs -m cron -a "name='backup mysql' state=absent"
ansible websrvs -m cron -a 'state=absent name=Synctime'

yum、apt

yum:管理软件包,只支持 RHEL,CentOS,fedora
apt:模块管理 Debian 相关版本的软件包

yum
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
allow_downgrade
autoremove
bugfix
conf_file
disable_excludes
disable_gpg_check
disable_plugin
disablerepo
download_dir
download_only
enable_plugin
enablerepo
exclude
install_repoquery
install_weak_deps
installroot
list
lock_timeout
name
releasever
security
skip_broken
state # present|installed|latest|absent|removed,installed 和 present等效,表示安装;absent 和 removed 等效,表示卸载;latest 表示安装最新版本
update_cache
update_only
use_backend
validate_certs

示例:

1
2
3
ansible websrvs -m yum -a 'name=httpd state=present' #安装,state可以省略
ansible websrvs -m yum -a 'name=httpd state=absent' #删除
ansible websrvs -m yum -a 'name=sl,cowsay' # 安装
apt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
allow_unauthenticated
autoclean
autoremove
cache_valid_time
deb
default_release
dpkg_options
force
force_apt_get
install_recommends
name
only_upgrade
policy_rc_d
purge
state
update_cache
update_cache_retries
update_cache_retry_max_delay
upgrade

示例:

1
2
ansible 10.0.0.100 -m apt -a 'name=bb,sl,cowsay,cmatrix,oneko,hollywood,boxes,libaa-bin,x11-apps'
ansible websrvs -m apt -a 'name=rsync,psmisc state=absent'

serivce

管理服务

1
2
3
4
5
6
7
8
arguments
enabled
name
pattern
runlevel
sleep
state
use

示例:

1
2
3
4
5
ansible all -m service -a 'name=httpd state=started enabled=yes'
ansible all -m service -a 'name=httpd state=stopped'
ansible all -m service -a 'name=httpd state=reloaded'
ansible all -m shell -a "sed -i 's/^Listen 80/Listen 8080/' /etc/httpd/conf/httpd.conf"
ansible all -m service -a 'name=httpd state=restarted'

user

管理用户

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
append
authorization
comment
create_home
expires
force
generate_ssh_key
group
groups
hidden
home
local
login_class
move_home
name
non_unique
password
password_lock
profile
remove
role
seuser
shell
skeleton
ssh_key_bits
ssh_key_comment
ssh_key_file
ssh_key_passphrase
ssh_key_type
state
system
uid
update_password
1
2
3
4
5
6
#创建用户
ansible all -m user -a 'name=user1 comment="test user" uid=2048 home=/app/user1 group=root'
ansible all -m user -a 'name=nginx comment=nginx uid=88 group=nginx groups="root,daemon" shell=/sbin/nologin system=yes create_home=no home=/data/nginx non_unique=yes'

#remove=yes表示删除用户及家目录等数据,默认remove=no
ansible all -m user -a 'name=nginx state=absent remove=yes'

group

管理组

1
2
3
4
5
6
gid
local
name
non_unique
state
system

示例:

1
2
3
4
#创建组
ansible websrvs -m group -a 'name=nginx gid=88 system=yes'
#删除组
ansible websrvs -m group -a 'name=nginx state=absent'

lineinfile

参考:http://www.zsythink.net/archives/2542

我们可以借助 lineinfile 模块,确保”某一行文本”存在于指定的文件中,或者确保从文件中删除指定的”文本”(即确保指定的文本不存在于文件中),还可以根据正则表达式,替换”某一行文本”

ansible 在使用 sed 进行替换时,经常会遇到需要转义的问题,而且 ansible 在遇到特殊符号进行替换时,存在问题,无法正常进行替换 。所以 ansible 提供了两个模块:lineinfile 模块和 replace 模块,可以方便的进行替换

一般在 ansible 当中去修改某个文件的单行进行替换的时候需要使用 lineinfile 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
attributes
backrefs # backrefs=yes 表示开启后向引用,并且当正则没有匹配到任何的行时,则不会对文件进行任何操作
backup # 是否在修改文件之前对文件进行备份
create # 当要操作的文件并不存在时,是否创建对应的文件
firstmatch
group
insertafter # 将文本插入到“指定的行”之后
insertbefore # 将文本插入到“指定的行”之前
line # 使用此参数指定文本内容
mode
others
owner
path # 必须参数,指定要操作的文件
regexp # 使用正则表达式匹配对应的行,当替换文本时,如果有多行文本都能被匹配,则只有最后面被匹配到的那行文本才会被替换,当删除文本时,如果有多行文本都能被匹配,这么这些行都会被删除
selevel
serole
setype
seuser
state # 默认值为present,absent表示删除
unsafe_writes
validate

示例:

1
2
3
4
5
6
7
8
9
# 查找内容为‘xxx’的行是否存在于文件/data/test/test.sh,如果不存在,则追加此行到此文件
ansible localhost -m lineinfile -a 'path=/data/test/test.sh line=xxx'

# 正则匹配,然后替换,如果匹配到多行,但是只能替换最后一行
ansible websrvs -m lineinfile -a "path=/etc/httpd/conf/httpd.conf regexp='^Listen' line='Listen 80'"
ansible all -m lineinfile -a "path=/etc/selinux/config regexp='^SELINUX=' line='SELINUX=disabled'"

# 正则匹配,然后删除,如果匹配到多行,就全部删除
ansible all -m lineinfile -a 'path=/etc/fstab state=absent regexp="^#"'

replace

lineinfile 模块的主要功能是操作 in file 的 line,只能替换单行文本,多行匹配进行替换需要使用 replace 模块

该模块有点类似于 sed 命令,主要也是基于正则进行匹配和替换,建议使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
after
attributes
backup
before
encoding
group
mode
others
owner
path
regexp
replace
selevel
serole
setype
seuser
unsafe_writes
validate

示例:

1
2
ansible all -m replace -a "path=/etc/fstab regexp='^(UUID.*)' replace='#\1'"
ansible all -m replace -a "path=/etc/fstab regexp='^#(UUID.*)' replace='\1'"

setup

setup 模块来收集主机的系统信息,这些 facts 信息可以直接以变量的形式使用,但是如果主机较多,会影响执行速度,可以使用 gather_facts: no 来禁止 ansible 收集 facts 信息

playbook

  • playbook 剧本是由一个或多个”play”组成的列表
  • play 的主要功能在于将预定义的一组主机,装扮成事先通过 ansible 中的 task 定义好的角色。Task 实际是调用 ansible 的一个 module,将多个 play 组织在一个 playbook 中,即可以让它们联合起来,按事先编排的机制执行预定义的动作
  • Playbook 文件是采用 YAML 语言编写的

playbook 核心组件

  • hosts:执行的远程主机列表
  • tasks:任务集
  • variables:内置变量或自定义变量在 playbook 中调用
  • templates:模板,可替换模板文件中的变量并实现一些简单逻辑的文件
  • handlers 和 notify:两者结合使用,特定条件出发操作
  • tags:标签,指定某条任务执行,用于选择运行 playbook 中的部分代码。ansible 具有幂等性,因此会自动跳过没有变化的部分,即便如此,有些代码为测试其确实没有发生变化的时间依然会非常长。此时,如果确信其没有变化,就可以通过 tags 跳过此些代码片断

hosts 组件

playbook 中的每一个 play 的目的都是为了让特定主机以某个指定的用户身份执行任务。hosts 用于指定要执行指定任务的主机,须事先定义在 inventory 主机清单中

示例:

1
- hosts: websrvs:appsrvs

remote_user、sudo、sudo_user 组件

  • remote_usr:指定远程主机执行任务的用户
  • sudo:支持使用 sudo,默认切换到 root
  • sudo_user:指定使用 sudo 时切换的用户
1
2
3
4
5
6
7
8
- hosts: websrvs
remote_user: root
tasks:
- name: test connection
ping:
remote_user: magedu
sudo: yes
sudo_user:lujinkai

tasks 和 action

tasks 是 task 列表,每个 task 的类型是字典

play 的主体部分是 tasks,所有 task依次在所有远程主机上执行,注意是同步执行而非异步执行;

task 的目的是使用指定的参数执行模块,

未完待续…

handlers 和 notify

tags 组件

Playbook 中使用变量

两类变量:

  1. ansible 内置变量 和 自定义变量,这些变量不仅可以在 playbook 中使用,在 ansible 中任何地方都可以使用

    1
    2
    # 使用debug模块可以格式化输出变量
    $ansible localhost -m debug -a "msg={{hostvars}}"
  2. facts 信息,来自 setup 模块收集(允许自定义),可以禁止收集,这些变量只能在 playbook 中使用

    1
    $ansible localhost -m setup

内置变量 hostvars

在 playbook 中,自己主机的变量,无论是 ansible 内置、自定义还是 facts,都可以直接调用,其他主机的变量,可以通过 hostvars 间接调用,例如:

1
# 具体事例,待补充...

常用变量

  • groups:来自 ansible 内置,主机列表
  • group_names:来自 ansible 内置,当前主机在主机列表中的所属组

playbook 中使用 when

playbook 使用迭代 with_items(loop)

当有需要重复性执行的任务时,可以使用迭代机制

对迭代项的引用,固定内置变量名为”item”

要在 task 中使用 with_items 给定要迭代的元素列表

注意: ansible2.5 版本后,可以用 loop 代替 with_items

示例:

1
2
3
4
5
6
7
8
- name: 下载 kube-master 二进制
copy: src={{ base_dir }}/bin/{{ item }} dest={{ bin_dir }}/{{ item }} mode=0755
with_items:
- kube-apiserver
- kube-controller-manager
- kube-scheduler
- kubectl
tags: upgrade_k8s

迭代嵌套子变量:在迭代中,还可以嵌套子变量,关联多个变量在一起使用

示例:

1
2
3
4
5
6
- name: add some users
user: name={{ item.name }} group={{ item.group }} state=present
with_items:
- { name: 'nginx', group: 'nginx' }
- { name: 'mysql', group: 'mysql' }
- { name: 'apache', group: 'apache' }

管理节点过多导致的超时问题解决方法

roles

roles 目录结构如下所示:

my.conf

配置文件生成工具参考链接:https://imysql.com/my_cnf_generator

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
[client]
#password=your_password
port=3306
socket=/tmp/mysql.sock
default-character-set=utf8mb4

[mysql]
no-auto-rehash
prompt="\\\r:\\\m:\\\s(\\\u@\\\h) [\\\d]>\\\_"

[mysqld]
port=3306
socket=/tmp/mysql.sock
basedir=${install_dir}
datadir=${data_dir}
pid-file=${data_dir}/mysql.pid
user=${user}
bind-address=0.0.0.0
server-id=1

init-connect='SET NAMES utf8mb4'
character-set-server=utf8mb4

# 跳过NDS解析
skip-name-resolve
#skip-networking
back_log=300

max_connections=1000
max_connect_errors=6000
open_files_limit=65535
table_open_cache=128
max_allowed_packet=500M
binlog_cache_size=1M
max_heap_table_size=8M
tmp_table_size=16M

read_buffer_size=2M
read_rnd_buffer_size=8M
sort_buffer_size=8M
net_buffer_length=8K
join_buffer_size=8M
key_buffer_size=4M
performance_schema_max_table_instances=500

thread_cache_size=8

ft_min_word_len=4

log_bin=mysql-bin
binlog_format=mixed
expire_logs_days=7

log_error=${data_dir}/mysql-error.log
slow_query_log=1
long_query_time=1
slow_query_log_file=${data_dir}/mysql-slow.log

performance_schema=0
explicit_defaults_for_timestamp

#lower_case_table_names=1

skip-external-locking

default_storage_engine=InnoDB
#default-storage-engine=MyISAM
innodb_file_per_table=1 # 开启独立表空间
innodb_data_home_dir=${data_dir}
innodb_data_file_path=ibdata1:12M:autoextend
innodb_log_group_home_dir=${data_dir}
innodb_open_files=500
innodb_buffer_pool_size=64M
innodb_write_io_threads=4
innodb_read_io_threads=4
innodb_thread_concurrency=0
innodb_purge_threads=1
innodb_flush_log_at_trx_commit=2
innodb_log_buffer_size=2M
innodb_log_file_size=32M
innodb_log_files_in_group=3
innodb_max_dirty_pages_pct=90
innodb_lock_wait_timeout=120 # 事务锁超时时长

bulk_insert_buffer_size=8M
myisam_sort_buffer_size=8M
myisam_max_sort_file_size=10G
myisam_repair_threads=1

# 这个参数对于后端连接毫无作用,什么是interactive?是你拿着键盘在mysql命令行下敲select, 这个叫interactive
interactive_timeout=28800
wait_timeout=28800

[mysqldump]
quick
max_allowed_packet=500M

[myisamchk]
key_buffer_size=8M
sort_buffer_size=8M
read_buffer=4M
write_buffer=4M

军规适用场景:并发量大、数据量大的互联网业务

军规:介绍内容

解读:讲解原因,解读比军规更重要

一、基础规范

(1)必须使用 InnoDB 存储引擎

解读:支持事务、行级锁、并发性能更好、CPU 及内存缓存页优化使得资源利用率更高

(2)必须使用 UTF8 字符集

解读:万国码,无需转码,无乱码风险,节省空间

(3)数据表、数据字段必须加入中文注释

解读:N 年后谁 tm 知道这个 r1,r2,r3 字段是干嘛的

(4)禁止使用存储过程、视图、触发器、Event

解读:高并发大数据的互联网业务,架构设计思路是“解放数据库 CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,CPU 计算还是上移吧

(5)禁止存储大文件或者大照片

解读:为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统,数据库里存 URI 多好

二、命名规范

(6)只允许使用内网域名,而不是 ip 连接数据库

(7)线上环境、开发环境、测试环境数据库内网域名遵循命名规范

业务名称:xxx

线上环境:dj.xxx.db

开发环境:dj.xxx.rdb

测试环境:dj.xxx.tdb

从库在名称后加-s 标识,备库在名称后加-ss 标识

线上从库:dj.xxx-s.db

线上备库:dj.xxx-sss.db

(8)库名、表名、字段名:小写,下划线风格,不超过 32 个字符,必须见名知意,禁止拼音英文混用

(9)表名 t_xxx,非唯一索引名 idx_xxx,唯一索引名 uniq_xxx

三、表设计规范

(10)单实例表数目必须小于 500

(11)单表列数目必须小于 30

(12)表必须有主键,例如自增主键

解读:

a)主键递增,数据行写入可以提高插入性能,可以避免 page 分裂,减少表碎片提升空间和内存的使用

b)主键要选择较短的数据类型, Innodb 引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率

c) 无主键的表删除,在 row 模式的主从架构,会导致备库夯住

(13)禁止使用外键,如果有外键完整性约束,需要应用程序控制

解读:外键会导致表与表之间耦合,update 与 delete 操作都会涉及相关联的表,十分影响 sql 的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用以性能优先

四、字段设计规范

(14)必须把字段定义为 NOT NULL 并且提供默认值

解读:

a)null 的列使索引/索引统计/值比较都更加复杂,对 MySQL 来说更难优化

b)null 这种类型 MySQL 内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多

c)null 值需要更多的存储空,无论是表还是索引中每行中的 null 的列都需要额外的空间来标识

d)对 null 的处理时候,只能采用 is null 或 is not null,而不能采用=、in、<、<>、!=、not in 这些操作符号。如:where name!=’shenjian’,如果存在 name 为 null 值的记录,查询结果就不会包含 name 为 null 值的记录

(15)禁止使用 TEXT、BLOB 类型

解读:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能

(16)禁止使用小数存储货币

解读:使用整数吧,小数容易导致钱对不上

(17)必须使用 varchar(20)存储手机号

解读:

a)涉及到区号或者国家代号,可能出现+-()

b)手机号会去做数学运算么?

c)varchar 可以支持模糊查询,例如:like“138%”

(18)禁止使用 ENUM,可使用 TINYINT 代替

解读:

a)增加新的 ENUM 值要做 DDL 操作

b)ENUM 的内部实际存储就是整数,你以为自己定义的是字符串?

五、索引设计规范

(19)单表索引建议控制在 5 个以内

(20)单索引字段数不允许超过 5 个

解读:字段超过 5 个时,实际已经起不到有效过滤数据的作用了

(21)禁止在更新十分频繁、区分度不高的属性上建立索引

解读:

a)更新会变更 B+树,更新频繁的字段建立索引会大大降低数据库性能

b)“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似

(22)建立组合索引,必须把区分度高的字段放在前面

解读:能够更加有效的过滤数据

六、SQL 使用规范

(23)禁止使用 SELECT *,只获取必要的字段,需要显示说明列属性

解读:

a)读取不需要的列会增加 CPU、IO、NET 消耗

b)不能有效的利用覆盖索引

c)使用 SELECT *容易在增加或者删除字段后出现程序 BUG

(24)禁止使用 INSERT INTO t_xxx VALUES(xxx),必须显示指定插入的列属性

解读:容易在增加或者删除字段后出现程序 BUG

(25)禁止使用属性隐式转换

解读:SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描,而不能命中 phone 索引,猜猜为什么?(这个线上问题不止出现过一次)

(26)禁止在 WHERE 条件的属性上使用函数或者表达式

解读:SELECT uid FROM t_user WHERE from_unixtime(day)>=’2017-02-15’ 会导致全表扫描

正确的写法是:SELECT uid FROM t_user WHERE day>= unix_timestamp(‘2017-02-15 00:00:00’)

(27)禁止负向查询,以及%开头的模糊查询

解读:

a)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE 等,会导致全表扫描

b)%开头的模糊查询,会导致全表扫描

(28)禁止大表使用 JOIN 查询,禁止大表使用子查询

解读:会产生临时表,消耗较多内存与 CPU,极大影响数据库性能

(29)禁止使用 OR 条件,必须改为 IN 查询

解读:旧版本 Mysql 的 OR 查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的 CPU 帮助实施查询优化呢?

(30)应用程序必须捕获 SQL 异常,并有相应处理

总结:大数据量高并发的互联网业务,极大影响数据库性能的都不让用,不让用哟。

OpenVPN 简介

VPN

专用网:需要向电信运行商申请租用专线,在这条专用的线路上只传输自己的信息,安全稳定,但费用高昂。

VPN:Virtual Private Network,虚拟私有网络,又称为虚拟专用网络,用于在不安全的线路上安全的传输数据。

OpenVPN

OpenVPN:一个实现 VPN 的开源软件,OpenVPN 是一个健壮的、高度灵活的 VPN 守护进程。它支持 SSL/TLS 安全、Ethernet bridging、经由代理的 TCP 或 UDP 隧道和 NAT。另外,它也支持动态 IP 地址以及 DHCP,可伸缩性足以支持数百或数千用户的使用场景,同时可移植至大多数主流操作系统平台上。
官网:https://openvpn.net
GitHub 地址:https://github.com/OpenVPN/openvpn

原理

参考链接:

https://baike.baidu.com/item/OpenVPN
https://www.bbsmax.com/A/MAzAkpvq59/
https://zhuanlan.zhihu.com/p/28727570

客户端和服务端都需要安装 OpenVPN,OpenVPN 的技术核心是虚拟网卡和 SSL 协议,我们知道,物理网卡工作在数据链路层,而虚拟网卡即可以工作在数据链路层,也可以工作在网络层,这取决于设置 tun 模式还是 tap 模式,客户端和服务端的虚拟网卡配置的 IP 处于同一个网段(10.8.0.0/24),客户端和服务端通过虚拟网卡通信,这样虽然中间隔着互联网,但就好像在一个局域网中一样

通信过程

  1. 服务器 OpenVPN 配置一个静态的虚拟 IP(10.8.0.1)和一个虚拟 IP 池(10.8.0.2 - 1.0.8.254),为每一个成功建立 SSL 连接的客户端 OpenVPN 分配一个虚拟 IP 池中的 IP,然后将局域网网段、路由设置等信息推送给客户端 OpenVPN,这样,客户端就成功加入了虚拟的局域网
  2. 在 OpenVpn 中,如果用户访问一个远程的虚拟地址(属于虚拟网卡配用的地址系列,区别于真实地址),则操作系统会通过路由机制将数据包(TUN 模式)或数据帧(TAP 模式)发送到虚拟网卡上,服务程序接收该数据并进行相应的处理后,通过 SOCKET 从外网上发送出去,远程服务程序通过 SOCKET 从外网上接收数据,并进行相应的处理后,发送给虚拟网卡,则应用软件可以接收到,完成了一个单向传输的过程,反之亦然。
  3. 通信过程加密,支持 TCP 和 UDP

VPN 和 SSH

我们知道,SSH 具有隧道转发的功能,它和 VPN 一样,都可以加密网络流量,实现安全通信,但是两者还是有很大的不同:….

OpenVPN 部署

10.0.0.2 56hpnM

准备 OpenVPN 部署环境

需要四台主机,一台客户端,一台 OpenVPN 服务器,两台内网服务器。

三台服务从阿里云购买,OpenVPN 服务器需要配置公网 IP,两台内网服务器不需要配置公网 IP。

配置 OpenVPN 服务器

安装

  1. 编译安装的 openvpn 总是无法启动,每次都启动超时,不知道为什么,所以还是通过 yum 来安装吧
1
2
3
4
5
6
# OpenVPN
[root@4710419222 /]$yum -y install openvpn
# 证书管理工具
[root@4710419222 /]$cd /usr/local/src
[root@4710419222 src]$wget https://github.com/OpenVPN/easy-rsa/releases/download/v3.0.8/EasyRSA-3.0.8.tgz
[root@4710419222 src]$tar zxvf EasyRSA-3.0.8.tgz

easy-rsa:证书管理工具,负责建立私有 CA、颁发管理证书等工作,通过调用 openssl 来实现功能,所以 easy-rsa 能实现的功能,openssl 都能实现,只是更麻烦一些

  1. 准备相关配置文件
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
# 配置文件
cp /usr/share/doc/openvpn-2.4.9/sample/sample-config-files/server.conf /etc/openvpn
# 证书签发相关文件
mkdir -p /etc/openvpn/easy-rsa-{server,client}
cp -r /usr/local/src/EasyRSA-3.0.8/{easyrsa,vars.example,x509-types,openssl-easyrsa.cnf} /etc/openvpn/easy-rsa-server/
cp -r /usr/local/src/EasyRSA-3.0.8/{easyrsa,vars.example,x509-types,openssl-easyrsa.cnf} /etc/openvpn/easy-rsa-client/
mv /etc/openvpn/easy-rsa-server/vars.example /etc/openvpn/easy-rsa-server/vars
mv /etc/openvpn/easy-rsa-client/vars.example /etc/openvpn/easy-rsa-client/vars
[root@4710419222 openvpn]$tree
.
├── client
├── easy-rsa-client
│   ├── easyrsa
│   ├── openssl-easyrsa.cnf
│   ├── vars
│   └── x509-types
│   ├── ca
│   ├── client
│   ├── code-signing
│   ├── COMMON
│   ├── email
│   ├── kdc
│   ├── server
│   └── serverClient
├── easy-rsa-server
│   ├── easyrsa
│   ├── openssl-easyrsa.cnf
│   ├── vars
│   └── x509-types
│   ├── ca
│   ├── client
│   ├── code-signing
│   ├── COMMON
│   ├── email
│   ├── kdc
│   ├── server
│   └── serverClient
├── server
└── server.conf

7 directories, 23 files

准备证书相关文件

1. 查看初始文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@4710419222 openvpn]$pwd
/etc/openvpn
[root@4710419222 openvpn]$cd easy-rsa-server/
[root@4710419222 easy-rsa-server]$ll
total 100
-rwxr-xr-x 1 root root 76946 Oct 4 21:38 easyrsa
-rw-r--r-- 1 root root 4616 Oct 4 21:38 openssl-easyrsa.cnf
-rw-r--r-- 1 root root 8925 Oct 4 21:38 vars
drwxr-xr-x 2 root root 4096 Oct 4 21:38 x509-types
[root@4710419222 easy-rsa-server]$./easyrsa --version
EasyRSA Version Information
Version: 3.0.8
Generated: Wed Sep 9 15:59:45 CDT 2020
SSL Lib: OpenSSL 1.0.2k-fips 26 Jan 2017
Git Commit: f12e00e53b4f486ce3d119ca429198780fa694ac
Source Repo: https://github.com/OpenVPN/easy-rsa

2. 初始化 PKI,生成 PKI 相关目录和文件
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
[root@4710419222 easy-rsa-server]$./easyrsa init-pki

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-server/vars

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /etc/openvpn/easy-rsa-server/pki


[root@4710419222 easy-rsa-server]$tree
.
├── easyrsa
├── openssl-easyrsa.cnf
├── pki # 生成一个新目录及相关文件
│   ├── openssl-easyrsa.cnf
│   ├── private
│   ├── reqs
│   └── safessl-easyrsa.cnf
├── vars
└── x509-types
├── ca
├── client
├── code-signing
├── COMMON
├── email
├── kdc
├── server
└── serverClient

4 directories, 13 files

3. 创建私有 CA 机构
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
[root@4710419222 easy-rsa-server]$./easyrsa build-ca nopass

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-server/vars
Using SSL: openssl OpenSSL 1.0.2k-fips 26 Jan 2017
Generating RSA private key, 2048 bit long modulus
...........................+++
...................................................................................+++
e is 65537 (0x10001)
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]: # 接受默认值,直接回车

CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/etc/openvpn/easy-rsa-server/pki/ca.crt # 生成自签名的证书文件



[root@4710419222 easy-rsa-server]$tree
.
├── easyrsa
├── openssl-easyrsa.cnf
├── pki
│   ├── ca.crt # 生成自签名的证书文件
│   ├── certs_by_serial
│   ├── index.txt # 数据库文件
│   ├── index.txt.attr
│   ├── issued
│   ├── openssl-easyrsa.cnf
│   ├── private
│   │   └── ca.key # 生成私钥文件
│   ├── renewed
│   │   ├── certs_by_serial
│   │   ├── private_by_serial
│   │   └── reqs_by_serial
│   ├── reqs
│   ├── revoked
│   │   ├── certs_by_serial
│   │   ├── private_by_serial
│   │   └── reqs_by_serial
│   ├── safessl-easyrsa.cnf
│   └── serial # CA serial number file
├── vars
└── x509-types
├── ca
├── client
├── code-signing
├── COMMON
├── email
├── kdc
├── server
└── serverClient

14 directories, 18 files
4. 创建服务端证书申请

首先生成私钥,然后使用私钥生成证书申请文件

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
[root@4710419222 easy-rsa-server]$./easyrsa gen-req server nopass

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-server/vars
Using SSL: openssl OpenSSL 1.0.2k-fips 26 Jan 2017
Generating a 2048 bit RSA private key
....................+++
.......................................................................................................................................................................................................................................+++
writing new private key to '/etc/openvpn/easy-rsa-server/pki/easy-rsa-2906.nnaLFU/tmp.yVgZX9'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [server]: # 默认值,直接回车

Keypair and certificate request completed. Your files are:
req: /etc/openvpn/easy-rsa-server/pki/reqs/server.req # 生成请求文件
key: /etc/openvpn/easy-rsa-server/pki/private/server.key # 生成私钥文件



[root@4710419222 easy-rsa-server]$tree pki
pki
├── ca.crt
├── certs_by_serial
├── index.txt
├── index.txt.attr
├── issued
├── openssl-easyrsa.cnf
├── private
│   ├── ca.key
│   └── server.key # 生成私钥文件
├── renewed
│   ├── certs_by_serial
│   ├── private_by_serial
│   └── reqs_by_serial
├── reqs
│   └── server.req # 生成请求文件
├── revoked
│   ├── certs_by_serial
│   ├── private_by_serial
│   └── reqs_by_serial
├── safessl-easyrsa.cnf
└── serial

12 directories, 9 files
5. 颁发服务端证书
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
# 查看命令用法,
[root@4710419222 easy-rsa-server]$./easyrsa help sign

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-server/vars

sign-req <type> <filename_base>
Sign a certificate request of the defined type. <type> must be a known
type such as 'client', 'server', 'serverClient', or 'ca' (or a user-added type.)

This request file must exist in the reqs/ dir and have a .req file
extension. See import-req below for importing reqs from other sources.

# 颁发服务端证书
[root@4710419222 easy-rsa-server]$./easyrsa sign server server

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-server/vars
Using SSL: openssl OpenSSL 1.0.2k-fips 26 Jan 2017


You are about to sign the following certificate.
Please check over the details shown below for accuracy. Note that this request
has not been cryptographically verified. Please be sure it came from a trusted
source or that you have verified the request checksum with the sender.

Request subject, to be signed as a server certificate for 825 days:

subject=
commonName = server


Type the word 'yes' to continue, or any other input to abort.
Confirm request details: yes # 输入yes,回车
Using configuration from /etc/openvpn/easy-rsa-server/pki/easy-rsa-3000.Ova1sM/tmp.xCpj0p
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'server'
Certificate is to be certified until Jan 7 14:03:25 2023 GMT (825 days)

Write out database with 1 new entries
Data Base Updated

Certificate created at: /etc/openvpn/easy-rsa-server/pki/issued/server.crt # 生成的证书


[root@4710419222 easy-rsa-server]$tree pki
pki
├── ca.crt
├── certs_by_serial
│   └── 61A86BA6C8B9482479FEF411570C8654.pem # 服务器证书文件
├── index.txt
├── index.txt.attr
├── index.txt.attr.old
├── index.txt.old
├── issued
│   └── server.crt
├── openssl-easyrsa.cnf
├── private
│   ├── ca.key
│   └── server.key # 服务器证书文件,这两个文件是一样的
├── renewed
│   ├── certs_by_serial
│   ├── private_by_serial
│   └── reqs_by_serial
├── reqs
│   └── server.req
├── revoked
│   ├── certs_by_serial
│   ├── private_by_serial
│   └── reqs_by_serial
├── safessl-easyrsa.cnf
├── serial
└── serial.old

12 directories, 14 files
6. 创建 Diffie-Hellman 密钥

DH 密钥,用于 SSL 加密通信

1
2
3
4
5
6
7
8
9
[root@4710419222 easy-rsa-server]$./easyrsa gen-dh

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-server/vars
Using SSL: openssl OpenSSL 1.0.2k-fips 26 Jan 2017
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
..................................................+.......................+........................................................................................................+.........................................+..................................................................................... # 需要等一会
[root@4710419222 easy-rsa-server]$ll ./pki/dh.pem
-rw------- 1 root root 424 Oct 4 22:09 ./pki/dh.pem
7. 准备客户端证书环境

上面服务端证书配置完成,下面是配置客户端证书

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
[root@4710419222 easy-rsa-server]$cd ../easy-rsa-client/
[root@4710419222 easy-rsa-client]$pwd
/etc/openvpn/easy-rsa-client
[root@4710419222 easy-rsa-client]$ll
total 100
-rwxr-xr-x 1 root root 76946 Oct 4 21:38 easyrsa
-rw-r--r-- 1 root root 4616 Oct 4 21:38 openssl-easyrsa.cnf
-rw-r--r-- 1 root root 8925 Oct 4 21:38 vars
drwxr-xr-x 2 root root 4096 Oct 4 21:38 x509-types
[root@4710419222 easy-rsa-client]$tree
.
├── easyrsa
├── openssl-easyrsa.cnf
├── vars
└── x509-types
├── ca
├── client
├── code-signing
├── COMMON
├── email
├── kdc
├── server
└── serverClient

1 directory, 11 files
# 初始化pki,和上面步骤一样,就不详细介绍了
[root@4710419222 easy-rsa-client]$./easyrsa init-pki
8. 创建客户端证书申请
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@4710419222 easy-rsa-client]$./easyrsa gen-req lujinkai nopass

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-client/vars
Using SSL: openssl OpenSSL 1.0.2k-fips 26 Jan 2017
Generating a 2048 bit RSA private key
.................+++
........................+++
writing new private key to '/etc/openvpn/easy-rsa-client/pki/easy-rsa-3187.Tyzo2P/tmp.KswAlv'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [lujinkai]: # 默认,回车

Keypair and certificate request completed. Your files are:
req: /etc/openvpn/easy-rsa-client/pki/reqs/lujinkai.req # 申请文件
key: /etc/openvpn/easy-rsa-client/pki/private/lujinkai.key # 私钥

9. 签发客户端证书
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
# 回到easy-rsa-server
[root@4710419222 easy-rsa-client]$cd ../easy-rsa-server/
# 将客户端证书请求文件复制到CA的工作目录,也可以使用cp命令
[root@4710419222 easy-rsa-server]$./easyrsa import-req /etc/openvpn/easy-rsa-client/pki/reqs/lujinkai.req lujinkai

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-server/vars
Using SSL: openssl OpenSSL 1.0.2k-fips 26 Jan 2017

The request has been successfully imported with a short name of: lujinkai
You may now use this name to perform signing operations on this request.


[root@4710419222 easy-rsa-server]$tree pki/reqs/
pki/reqs/
├── lujinkai.req # 复制过来的申请文件
└── server.req

0 directories, 2 files
# 签发客户端证书
[root@4710419222 easy-rsa-server]$./easyrsa sign client lujinkai

Note: using Easy-RSA configuration from: /etc/openvpn/easy-rsa-server/vars
Using SSL: openssl OpenSSL 1.0.2k-fips 26 Jan 2017


You are about to sign the following certificate.
Please check over the details shown below for accuracy. Note that this request
has not been cryptographically verified. Please be sure it came from a trusted
source or that you have verified the request checksum with the sender.

Request subject, to be signed as a client certificate for 825 days:

subject=
commonName = lujinkai


Type the word 'yes' to continue, or any other input to abort.
Confirm request details: yes # 输入yes,回车
Using configuration from /etc/openvpn/easy-rsa-server/pki/easy-rsa-3291.YugvYL/tmp.xXkvue
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'lujinkai'
Certificate is to be certified until Jan 7 14:24:14 2023 GMT (825 days)

Write out database with 1 new entries
Data Base Updated

Certificate created at: /etc/openvpn/easy-rsa-server/pki/issued/lujinkai.crt


[root@4710419222 easy-rsa-server]$tree pki/issued/
pki/issued/
├── lujinkai.crt # 签发的客户端证书
└── server.crt

0 directories, 2 files
10. 将 ca 和服务器相关文件复制到服务器相应的目录
1
2
3
4
5
6
7
8
9
[root@4710419222 openvpn]$pwd
/etc/openvpn
[root@4710419222 openvpn]$cp -r ./easy-rsa-server/pki/{ca.crt,issued/server.crt,private/server.key,dh.pem} ./certs/
[root@4710419222 openvpn]$ll ./certs/
total 20
-rw------- 1 root root 1172 Oct 5 18:48 ca.crt
-rw------- 1 root root 424 Oct 5 18:48 dh.pem
-rw------- 1 root root 4547 Oct 5 18:48 server.crt
-rw------- 1 root root 1704 Oct 5 18:48 server.key
11. 将 ca 和客户端相关文件复制到服务器相关的目录
1
2
3
4
5
6
7
[root@4710419222 openvpn]$cp easy-rsa-server/pki/{ca.crt,issued/lujinkai.crt} client/
[root@4710419222 openvpn]$cp easy-rsa-client/pki/private/lujinkai.key client/
[root@4710419222 openvpn]$ll client/
total 16
-rw------- 1 root root 1172 Oct 5 18:52 ca.crt
-rw------- 1 root root 4431 Oct 5 18:52 lujinkai.crt
-rw------- 1 root root 1708 Oct 5 18:52 lujinkai.key

准备 OpenVPN 服务端配置文件

配置文件说明
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
# grep -Ev '^#|^$' server.conf 或:
[root@4710419222 openvpn]$sed -e '/^#/d' -e '/^$/d' server.conf
;local a.b.c.d # 本机监听IP,默认为本机所有IP
port 1194
;proto tcp
proto udp
;dev tap # 创建一个以太网隧道,tap模拟以太网设备,工作在第二层
dev tun # 创建一个路由IP隧道,tun模拟网络层设备,工作在第三层
;dev-node MyTap # TAP-Win32适配器。非windows不需要配置
ca ca.crt # ca证书文件
cert server.crt # 服务器证书文件
key server.key # 服务器私钥文件 # This file should be kept secret
dh dh2048.pem # dh参数文件
;topology subnet # 拓扑类型:默认是net30模式,很浪费IP地址,建议开启subnet模式
server 10.8.0.0 255.255.255.0 # 客户端建立ssl连接后分配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 # 配置网桥模式
;server-bridge
;push "route 192.168.10.0 255.255.255.0" # 推送路由设置给客户端,设置私网地址
;push "route 192.168.20.0 255.255.255.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直接通信,不安全,生产环境一般不配置
;duplicate-cn # 多个用户共用一个证书,一般用于测试环境,生产环境都是一个用户一个证书
keepalive 10 120 # 心跳,10秒ping一次,120秒没有回应则认为对方已经down
# 访止DoS等攻击的安全增强配置,可以使用openvpn命令生成:openvpn --genkey --secret ta.key
# 服务器和每个客户端都需要拥有该密钥的一个拷贝。第二个参数在服务器端应该为’0’,在客户端应该为’1’
tls-auth ta.key 0 # This file is secret
cipher AES-256-CBC # 加密算法
;compress lz4-v2 # 启用Openvpn2.4.X新版压缩算法
;push "compress lz4-v2" # 推送客户端使用新版压缩算法,和下面的comp-lzo不要同时使用
;comp-lzo # 旧户端兼容的压缩配置,需要客户端配置开启压缩,openvpn2.4.X等新版可以不用开启
;max-clients 100 # 最大客户端数
;user nobody # 运行openvpn服务的用户和组
;group nobody
persist-key # 重启VPN服务时默认会重新读取key文件
persist-tun # 重启vpn服务时,一直保持tun或者tap设备是up的,否则会先down然后再up
status openvpn-status.log # openVPN状态记录文件,每分钟会记录一次
;log openvpn.log # 指定日志路径,log会在openvpn启动的时候清空日志文件
;log-append openvpn.log # 指定日志路径,重启openvpn后在之前的日志后面追加新的日志
# 设置日志级别,0-9,级别越高记录的内容越详细,0 表示静默运行,只记录致命错误,4 表示合理的常规用法,5 和 6 可以帮助调试连接错误。9 表示极度冗余,输出非常详细的日志信息
verb 3
;mute 20 # 相同类别的信息只有前20条会输出到日志文件中
# 通知客户端,在服务端重启后自动重新连接,仅能用于udp模式,tcp模式不需要配置即可实现断开重新连接,且开启此项后tcp配置后将导致openvpn服务无法启动,所以tcp时必须不能开启此项
explicit-exit-notify 1
修改服务端配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
topology subnet
server 10.8.0.0 255.255.255.0
push "route 10.0.0.0 255.255.255.0"
keepalive 10 120
max-clients 1024
user openvpn
group openvpn
status /var/log/openvpn/openvpn-status.log
log-append /var/log/openvpn/openvpn.log
verb 9
mute 20
准备日志相关
1
2
3
4
[root@4710419222 ~]$getent passwd openvpn
openvpn:x:995:993:OpenVPN:/etc/openvpn:/sbin/nologin
[root@4710419222 ~]$mkdir /var/log/openvpn
[root@4710419222 ~]$chown openvpn.openvpn /var/log/openvpn

配置防火墙和内核参数

1
2
3
4
5
6
7
8
9
10
# 开启ip_forward
[root@4710419222 ~]$echo 1 > /proc/sys/net/ipv4/ip_forward
# 添加snat规则
[root@4710419222 ~]$nft list ruleset
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.8.0.0/24 masquerade
}
}

比较奇怪的是以上设置不对,好像 nftables 不行,只能使用 iptables,于是关掉 nftables,设置 iptables:

1
[root@4710419222 nftables]$iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -j MASQUERADE

启动 OpenVPN

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
[root@4710419222 ~]$systemctl start openvpn@server
[root@4710419222 ~]$systemctl status openvpn@server
● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; disabled; vendor preset: disabled)
Active: active (running) since Wed 2020-10-07 17:41:08 CST; 2s ago
Main PID: 10185 (openvpn)
Status: "Initialization Sequence Completed"
CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
└─10185 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf

Oct 07 17:41:08 4710419222 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
Oct 07 17:41:08 4710419222 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
[root@4710419222 ~]$ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 32 *:1194 *:*
LISTEN 0 128 *:3306 *:*
LISTEN 0 128 127.0.0.1:6379 *:*
LISTEN 0 128 *:80 *:*
LISTEN 0 128 *:22 *:*
LISTEN 0 128 *:443 *:*
LISTEN 0 128 *:9501 *:*
LISTEN 0 128 *:8000 *:*
[root@4710419222 ~]$ps aux | grep openvpn
openvpn 10185 0.0 0.3 75036 7420 ? Ss 17:41 0:00 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf
root 10232 0.0 0.0 9096 848 pts/1 R+ 17:41 0:00 grep --color openvpn
[root@4710419222 ~]$ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:16:3e:05:9f:fc brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/24 brd 10.0.0.255 scope global dynamic eth0
valid_lft 315108100sec preferred_lft 315108100sec
inet6 fe80::216:3eff:fe05:9ffc/64 scope link
valid_lft forever preferred_lft forever
3: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100
link/none
inet 10.8.0.1/24 brd 10.8.0.255 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::2fa5:3d20:6dff:99d9/64 scope link flags 800
valid_lft forever preferred_lft forever

准备 OpenVPN 客户端配置文件

配置文件说明
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
# 客户端配置文件,后缀必须是.ovpn
[root@4710419222 ~]$grep -v '^#' /usr/share/doc/openvpn-2.4.9/sample/sample-config-files/client.conf > /etc/openvpn/client/lujinkai/client.ovpn
[root@4710419222 ~]$sed -i '/^$/d' /etc/openvpn/client/lujinkai/client.ovpn
[root@4710419222 ~]$cat /etc/openvpn/client/lujinkai/client.ovpn
client # 声明客户端
;dev tap # 接口类型,必须和服务端一致
dev tun
;dev-node MyTap
;proto tcp # 协议类型,必须和服务端一致
proto udp
remote my-server-1 1194 # server端的ip和端口,可以写域名但是需要可以解析成IP
;remote my-server-2 1194
;remote-random
resolv-retry infinite # 如果写域名,就始终解析,当域名发生变化,会重新连接到新的域名对应的IP
nobind # 本机不绑定监听端口,客户端是随机打开端口连接到服务端的1194
;user nobody
;group nobody
persist-key
persist-tun
;http-proxy-retry # retry on connection failures
;http-proxy [proxy server] [proxy port #]
;mute-replay-warnings
ca ca.crt
cert client.crt
key client.key
remote-cert-tls server # 指定采用服务器证书校验方式
tls-auth ta.key 1
cipher AES-256-CBC
verb 3
;mute 20
修改客户端配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
client
dev tun
proto tcp
remote 47.104.192.22 1194
resolv-retry infinite
nobind
ca ca.crt
cert lujinkai.crt
key lujinkai.key
remote-cert-tls server
;tls-auth ta.key 1
;cipher AES-256-CBC
verb 3
mute 20

Windows 配置部署 OpenVPN 客户端

将客户端相关文件下载下来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@4710419222 lujinkai]$pwd
/etc/openvpn/client/lujinkai
[root@4710419222 client]$ll lujinkai
total 20
-rw------- 1 root root 1172 Oct 4 19:25 ca.crt
-rw-r--r-- 1 root root 436 Oct 7 18:05 client.ovpn
-rw------- 1 root root 4438 Oct 4 19:25 lujinkai.crt
-rw------- 1 root root 1704 Oct 4 19:25 lujinkai.key
[root@4710419222 client]$tar cvf lujinkai.tar lujinkai/
lujinkai/
lujinkai/lujinkai.key
lujinkai/client.ovpn
lujinkai/ca.crt
lujinkai/lujinkai.crt
[root@4710419222 client]$ll
total 24
drwxr-xr-x 2 root root 4096 Oct 7 18:28 lujinkai
-rw-r--r-- 1 root root 20480 Oct 8 10:57 lujinkai.tar
[root@4710419222 client]$sz lujinkai.tar
rz
Starting zmodem transfer. Press Ctrl+C to cancel.
Transferring lujinkai.tar...
100% 20 KB 20 KB/sec 00:00:01 0 Errors

注意:tap 网卡驱动可能因为缺少数字签名无法启动,可以在开机的时候按 F8,然后禁用校验驱动的数字签名,可是这样只能当次开机有效,不知道怎么彻底解决。。。

Mac OS 配置部署 OpenVPN 客户端

略。。。

故障排错

OpenVPN 高级功能

扩展知识

安全技术和防火墙

安全技术

  • 入侵检测系统
  • 入侵防御系统
  • 防火墙

防水墙
广泛意义上的防水墙:防水墙(Waterwall),与防火墙相对,是一种防止内部信息泄漏的安全产品。网络、外设接口、存储介质和打印机构成信息泄漏的全部途径。防水墙针对这四种泄密途径,在事前、事中、事后进行全面防护。其与防病毒产品、外部安全产品一起构成完整的网络安全体系。

防火墙的分类

按保护范围分类:

  • 主机防火墙:服务范围为当前一台主机
  • 网络防火墙:服务范围为防火墙一侧的局域网

按实现方式划分:

  • 硬件防火墙
  • 软件防火墙

按网络协议划分:

  • 网络层防火墙:OSI 模型下四层,又称为包过滤防火墙
  • 应用层防火墙/代理服务器:代理网关,OSI 模型七层

现实生产环境中所使用的防火墙一般都是二者结合体,即先检查网络数据,通过之后再送到应用层去检查

Linux 防火墙的基本认识

Linux 防火墙是由Netfilter组件提供的,Netfilter 工作在内核空间,集成在 linux 内核中

Netfilter 是 Linux 2.4.x 之后新一代的 Linux 防火墙机制,是 linux 内核的一个子系统。Netfilter 采用模块化设计,具有良好的可扩充性,提供扩展各种网络服务的结构化底层框架。Netfilter 与 IP 协议栈是无缝契合,并允许对数据报进行过滤、地址转换、处理等操作

1
2
3
4
5
6
7
8
9
10
11
[root@centos8 ~]$grep -m 10 NETFILTER /boot/config-4.18.0-193.el8.x86_64
CONFIG_NETFILTER=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_BRIDGE_NETFILTER=m
CONFIG_NETFILTER_INGRESS=y
CONFIG_NETFILTER_NETLINK=m
CONFIG_NETFILTER_FAMILY_BRIDGE=y
CONFIG_NETFILTER_FAMILY_ARP=y
# CONFIG_NETFILTER_NETLINK_ACCT is not set
CONFIG_NETFILTER_NETLINK_QUEUE=m
CONFIG_NETFILTER_NETLINK_LOG=m

防火墙工具介绍

  • iptables:工作在用户空间,用来编写规则,写好的规则被送往 netfilter,告诉内核如何去处理包

  • firewalld:CentOS7 引入

  • nftables:CentOS 8 新特性,2013 年末合并到 Linux 内核中,自 2014 年以来已在内核 3.13 中可用。

    它重用了 netfilter 框架的许多部分,例如连接跟踪和 NAT 功能。它还保留了命名法和基本 iptables 设计
    的几个部分,例如表,链和规则。就像 iptables 一样,表充当链的容器,并且链包含单独的规则,这些
    规则可以执行操作,例如丢弃数据包,移至下一个规则或跳至新链。

    从用户的角度来看,nftables 添加了一个名为 nft 的新工具,该工具替代了 iptables,arptables 和
    ebtables 中的所有其他工具。从体系结构的角度来看,它还替换了内核中处理数据包过滤规则集运行时
    评估的那些部分。

    总之:nftables 应该是未来的主流,现在 iptables 还是要重点学习,firewalld 了解即可

netfilter 中五个勾子函数(hook)

netfilter 在内核的五个位置放了钩子函数(hook):

1
INPUT、OUTPUT、FORWARD、PREROUTING、POSTROUTING

提示:从 Linux kernel 4.2 版以后,Netfilter 在 prerouting 前加了一个 ingress 勾子函数。可以使用这个新的入口挂钩来过滤来自第 2 层的流量,这个新挂钩比预路由要早,基本上是 tc 命令(流量控制工具)的替代品

iptables 的组成

iptables 由五张 表(table) 组成:

  • filter 表:过滤规则表,根据预定义的规则过滤符合条件的数据包,默认表
  • nat 表:network address translation 地址转换规则表;修改数据包中的源、目标 iP 和端口
  • mangle:修改数据标记位规则表
  • raw:关闭启用的连接跟踪机制,加快封包穿越防火墙速度
  • security:用于强制访问控制(MAC)网络规则,由 Linux 安全模块(如 SELinux)实现

五张表的优先级:security –>raw–>mangle–>nat–>filter,我们重点研究后面四张表

每张表由若干功能类似的 链(chain)组成,每条链上有若干类似的 规则(rule)

表不能自定义,链可以自定义,但是 iptables 也内置了 5 种链,与 5 个钩子函数同名,内置链上每条过滤规则都调用与链同名的钩子函数。

1
INPUT、OUTPUT、FORWARD、PREROUTING、POSTROUTING

注意:自定义的链不能直接添加到表中,只能作为 target 添加到内置链上,间接的实现添加到表中

内核中数据的传输流程

在每个 hook 放置若干表,按照表的优先级,依次执行表内的同名链上的规则,例如 INPUT 钩子函数会依次执行 mangle、nat、filter 表中的 INPUT 链上的规则。

标注:红色的表示钩子函数或者内置链,黄色的表示 table

显然,内置链和内置表的关系:

netfilter 完整流程

iptables

mysql:数据库中有表,表中有一条条的数据

iptables:表中有链,链上有一条条的规则

规则 rule

规则由 匹配条件 和 处理动作组成,规则在链上的顺序即其检查时生效的顺序

匹配条件

  • 基本匹配:IP,端口,TCP 的 Flags(SYN,ACK 等)
  • 扩展匹配:调用 netfilter 中的模块,实现各种复杂匹配

处理动作

处理动作称为 target,跳转目标,分为两种:内建的处理动作 和 自定义链。

常用的内建处理动作:

  • ACCEPT:允许数据包通过

  • DROP:直接丢弃数据包,不给任何回应信息,这时候客户端会感觉自己的请求泥牛入海了,过了超时时间才会有反应

  • REJECT:拒绝数据包通过,必要时会给数据发送端一个响应的信息,客户端刚请求就会收到拒绝的信息

  • SNAT:源地址转换,解决内网用户用同一个公网地址上网的问题

  • MASQUERADE:是 SNAT 的一种特殊形式,适用于动态的、临时会变的 ip 上

  • DNAT:目标地址转换

  • REDIRECT:在本机做端口映射

  • LOG:非中断 target,本身不拒绝和允许,放在拒绝和允许规则前,并将日志记在/var/log/messages 系统日志中

    • –log-level level 级别: debug、info、notice、warning、error、crit、alert、emerg
    • –log-prefix prefix 日志前缀,用于区别不同的日志,最多 29 个字符

iptables 添加规则使要考虑

  1. 要实现哪种功能:判断添加在哪张表上
  2. 报文流经的路径:判断添加在哪条链上
  3. 报文的流向:判断源和目的
  4. 匹配规则:业务需要

iptables 命令用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
iptables [-t table] {-A|-C|-D} chain rule-specification
iptables [-t table] -D chain rulenum
ip6tables [-t table] {-A|-C|-D} chain rule-specification
iptables [-t table] -I chain [rulenum] rule-specification
iptables [-t table] -R chain rulenum rule-specification
iptables [-t table] -S [chain [rulenum]]
iptables [-t table] {-F|-L|-Z} [chain [rulenum]] [options...]
iptables [-t table] -N chain
iptables [-t table] -X [chain]
iptables [-t table] -P chain target
iptables [-t table] -E old-chain-name new-chain-name

# 补充说明
rule-specification = [matches...] [target]
match = -m matchname [per-match-options]
target = -j targetname [per-target-options]
  • -t:指定表,默认 filter 表

  • -A:以 append 的方式添加 rule,iptables 添加 rule 后会立马生效

  • -I:以 insert 的方式添加 rule

  • -C:检查 rule 是否存在

  • -D :删除 rule,可以通过 指明规则序号 或 指明规则本身 两种方式

  • -R:replace,替换指定链上的指定规则编号

  • -S:打印指定链上的所有 rule,不指定链则打印表中所有 rule

  • -L:list, 列出指定链上的所有规则,不指定链则列出表中所有 rule

  • -F:flush,清空指定的链

  • -Z:zero,置零。每条 rule 都有两个计数器

    • 匹配到的报文数
    • 匹配到的所有报文的大小之和
  • -N:new, 自定义一条新的链

  • -X:delete,删除自定义的空的链

  • -P:Policy,设置默认策略;对 filter 表中的链而言,其默认策略有:ACCEPT:接受, DROP:丢弃

  • -E:重命名自定义链,引用计数不为 0 的自定义链不能够被重命名,也不能被删除

参数:

  • -m:match
  • -j:jump target

其他选项:

  • -v:verbose,显示更详细的信息
  • -n:numeric,数字输出。IP 地址和端口会以数字的形式打印
  • -w:
  • -W:
  • -x:扩展数字。显示包和字节计数器的精确值,代替用 K,M,G 表示的约数。 这个选项仅能用于 -L 命令。
  • --line-numbers:当列表显示规则时,在每个规则的前面加上行号,与该规则在链中的位置相对应,这个常用
  • –modprobe=command:

iptables 基本匹配条件

注意:当一条规则中有多个匹配条件时,这多个匹配条件之间,默认存在”与”的关系。也就是说当一条规则中存在多个匹配条件时,报文必须同时满足这些条件,才算做被规则匹配

无需加载模块,由 iptables/netfilter 自行提供,! 是取反的意思

基本匹配条件 说明
[!] -s address[/mask][,…] 源地址
[!] -d address[/mask][,…] 目标地址
[!] -p protocol 指定协议,tcp、udp、icmp 等,可以指定哪些协议参考/etc/protocols,
还可以使用数字 0,相当于 all,匹配所有协议
[!] -i inter_name in interface,报文流入的接口;只能应用于数据报文流入环节,
只应用于 INPUT、FORWARD、PREROUTING 链
[!] -o inter_name out interface,报文流出的接口;只能应用于数据报文流出的环节,
只应用于 FORWARD、OUTPUT、POSTROUTING 链

范例:

1
2
# 添加规则,并指定规则号:禁止10.0.0.6的ping请求
[root@centos8 ~]#iptables -I INPUT 2 -s 10.0.0.6 -p icmp -j REJECT

iptables 扩展匹配条件

需要加载扩展模块(/usr/lib64/xtables/*)方可生效,扩展模块可以使用 man 命令查看,扩展匹配条件分为 隐式扩展 和 显式扩展

隐式扩展

iptables 在使用 -p 指定了特定协议时,可以省略协议同名扩展,直接写扩展匹配条件即可,这类扩展就是隐式扩展,例如 tcp、udp、icmp

tcp 扩展模块 匹配条件
  • [!] –sport port[:port]:匹配 tcp 报文的源端口或端口范围

    1
    2
    # 范例:
    iptables -t filter -I OUTPUT -d 192.168.1.146 -p tcp -m tcp --sport 22:25 -j REJECT
  • [!] –dport port[:port]:匹配 tcp 报文的目标端口或端口范围

    1
    2
    # 范例
    iptables -t filter -I INPUT -s 192.168.1.146 -p tcp -m tcp --dport 22:25 -j REJECT
  • [!] –tcp-flags mask comp:

    • mask: 需检查的标志位列表,用,分隔 , 例如 SYN,ACK,FIN,RST
    • comp:在 mask 列表中必须为 1 的标志位列表,无指定则必须为 0,用,分隔 tcp 协议的扩展选项
    1
    2
    3
    4
    5
    # 范例:
    # 要检查的标志位为SYN,ACK,FIN,RST四个,其中SYN必须为1,余下的必须为0,第一次握手
    --tcp-flags SYN,ACK,FIN,RST SYN
    # 以上写法可以简写
    --syn
udp 扩展模块 匹配条件
  • [!] –sport port[:port]:匹配报文的源端口或端口范围
  • [!] –dport port[:port]:匹配报文的目标端口或端口范围
icmp 扩展模块 匹配条件
  • [!] –icmp-type {type[/code]|typename}:
    • type/code
      • 0/0 echo-reply icmp 应答
      • 8/0 echo-request icmp 请求
1
2
# 范例:不允许其他人ping我,而我可以ping其他人
iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j REJECT

显式扩展

必须使用-m 指定使用的扩展,下面是常用的显式扩展:

multiport 扩展 匹配条件

以离散方式定义多端口匹配,最多指定 15 个端口

  • [!] –sports port[,port|,port:port]…:指定多个源端口
  • [!] –dports port[,port|,port:port]…:指定多个目标端口
  • [!] –ports port[,port|,port:port]…:指定多个源或目标端口
iprange 扩展 匹配条件

指明连续的(但一般不是整个网络)ip 地址范围

  • [!] –src-range from[-to] 源 IP 地址范围
  • [!] –dst-range from[-to] 目标 IP 地址范围
1
2
# 范例
iptables -t filter -I INPUT -m iprange --src-range 192.168.1.127-192.168.1.146 -j DROP
mac 扩展 匹配条件

指定源 MAC 地址,,适用于:PREROUTING, FORWARD,INPUT chains
注意不能指定目标 MAC 地址,因为无法指定,也没有意义

  • [!] –mac-source XX:XX:XX:XX:XX:XX
string 扩展 匹配条件

对报文中的应用层数据做字符串模式匹配检测

  • –algo {bm|kmp}:字符串匹配算法
  • –from offset:开始偏移
  • –to offset:结束偏移
  • [!] –string pattern:模式匹配要检测的字符串
  • [!] –hex-string pattern:模式匹配要检测的字符串,16 进制格式
1
2
# 范例
iptables -A OUTPUT -p tcp --sport 80 -m string --algo bm --from 62 --string "google" -j REJECT
time 扩展 匹配条件

注意:CentOS 8 此模块有问题

根据将报文到达的时间与指定的时间范围进行匹配

  • –datestart YYYY[-MM[-DD[Thh[:mm[:ss]]]]] 日期
  • –datestop YYYY[-MM[-DD[Thh[:mm[:ss]]]]]
  • –timestart hh:mm[:ss] 时间
  • –timestop hh:mm[:ss]
  • [!] –monthdays day[,day…] 每个月的几号
  • [!] –weekdays day[,day…] 星期几,1 – 7 分别表示星期一到星期日
  • –kerneltz:内核时区(当地时间),非常不建议使用
connlimit 扩展 匹配条件

对每 IP 所能够发起并发连接数做限制;可防止 Dos(Denial of Service,拒绝服务)攻击

  • –connlimit-upto N:连接的数量小于等于 N 时匹配
  • –connlimit-above N:连接的数量大于 N 时匹配
limit 扩展 匹配条件

”令牌桶“算法,对 报文到达速率 进行限制,即限制单位时间内流入的包的数量

–limit-burst N:前 N 个包不限制,

–limit N[/second|/minute|/hour|/day]:单位时间内通过 N 个包

1
2
3
# 范例:下面两条规则配合使用,限制每分钟ping10次,即每6秒ping一次
iptables -t filter -I INPUT -p icmp -m limit --limit-burst 3 --limit 10/minute -j ACCEPT
iptables -t filter -A INPUT -p icmp -j REJECT
state 扩展 匹配条件

state 直译为 状态,state 模块可以让 iptables 实现”连接追踪“机制,英文名是 conntrack 机制。
简单说就是追踪本机的请求和相应之间的状态。

state 扩展实现“连接追踪”需要依赖 nf_conntrack_ipv4 等内核模块,可以通过 modprobe 命令手动把所需内核模块加载到内存中,但实际上没有这个必要,因为 iptables 会自动加载所需内核模块

state 模块有 5 中状态:NEW、ESTABLISHED、RELATED、INVALID、UNTRACKED

  • NEW:新发出请求;只要连接追踪信息库(/proc/net/nf_conntrack)中不存在此连接的相关信息条目,就识别为第一次发出的请求
  • ESTABLISHED:NEW 状态之后,连接追踪信息库中为其建立的条目失效之前期间内所进行的通信状态
  • RELATED:新发起的但与已有连接相关联的连接,如:ftp 协议中的数据连接与命令连接之间的关系
  • INVALID:无效的连接,如 flag 标记不正确
  • UNTRACKED:未进行追踪的连接,如:raw 表中关闭追踪

注意:state 模块的状态和 TCP 的状态没有关系,对于 state 模块而言,只要两台机器在”你来我往”的通信,就算建立起了连接。所以在 TCP/IP 协议中,UDP 和 ICMP 是没有连接状态的,而在 state 模块中,所有的协议都是有连接状态的

查看连接追踪信息库:

1
[root@centos8 ~]$cat /proc/net/nf_conntrack

调整连接追踪功能所能够容纳的最大连接数量:

1
2
3
4
5
# 使用sysctl修改
[root@centos8 ~]$cat /proc/sys/net/netfilter/nf_conntrack_max
65536
[root@centos8 ~]$cat /proc/sys/net/nf_conntrack_max
65536

查看目前连接跟踪有多少条目:

1
2
3
4
[root@4710419222 ~]$cat /proc/sys/net/netfilter/nf_conntrack_count
28
[root@4710419222 ~]$cat /proc/net/nf_conntrack | wc -l
28

不同的协议的连接追踪时长:

1
[root@4710419222 ~]$ll /proc/sys/net/netfilter/ | grep timeout

说明:

  • 当服务器连接多于最大连接数时,报错 :kernel: ip_conntrack: table full, dropping packet,并且导致建立 TCP 连接很慢
  • 各种状态超时后,链接会从表中删除

连接过多的解决方法有两个:

  1. 加大 nf_conntrack_max 值

    1
    2
    3
    vi /etc/sysctl.conf
    net.nf_conntrack_max = 393216
    net.netfilter.nf_conntrack_max = 393216
  2. 降低 tcp 各种状态的超时时间

    1
    2
    3
    4
    5
    6
    7
    vi /etc/sysctl.conf
    net.netfilter.nf_conntrack_tcp_timeout_established = 300
    net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
    net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
    net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120

    iptables -t nat -nvL
格式
1
[!] --state state,state...

范例:不允许 10.0.0.79 访问本机,但是本机能访问 10.0.0.79

1
2
[root@centos8 ~]$iptables -t filter -A INPUT -m state --state ESTABLISHED -j ACCEPT
[root@centos8 ~]$iptables -A INPUT -s 10.0.0.79 -m state --state NEW -j REJECT

rule 优化

  1. 安全放行所有入站和出站的状态为 ESTABLISHED 状态连接,建议放在第一条,效率更高

  2. 谨慎放行入站的新请求

  3. 有特殊目的限制访问功能,要在放行规则之前加以拒绝

  4. 同类规则(访问同一应用,比如:http ),匹配范围小的放在前面,用于特殊处理

  5. 不同类的规则(访问不同应用,一个是 http,另一个是 mysql ),匹配范围大的放在前面,效率更高

1
2
-s 10.0.0.6 -p tcp --dport 3306 -j REJECT
-s 172.16.0.0/16 -p tcp --dport 80 -j REJECT
  1. 应该将那些可由一条规则能够描述的多个规则合并为一条,减少规则数量,提高检查效率

  2. 设置默认策略,建议白名单(只放行特定连接)

  3. iptables -P,不建议,容易出现“自杀现象”

  4. 规则的最后定义规则做为默认策略,推荐使用,放在最后一条

rule 保存

使用 iptables 命令定义的规则,,其生效期限为 kernel 存活期限,即重启失效

持久保存规则:

1
2
3
4
5
# CentOS6 将规则覆盖保存至/etc/sysconfig/iptables文件中
service iptables save

# CentOS7、8
iptables-save > file

加载规则:

CentOS6 自动从/etc/sysconfig/iptables 重新载入规则

1
service iptables restart

CentOS7、8

1
iptables-restore < file
  • -n:noflush,清除原有规则
  • -t:–test,仅分析生成规则集,但不提交

开机自动重载规则

CentOS6 只要设置 iptables 开机自启,然后关机前使用保存规则即可

1
2
chkconfig iptables on
service iptables save # 注意:如果不保存,关机就没了

CentOS7、8 有两种方式

方法一:将自动载入规则的命令写入开机自启脚本/etc/rc.d/rc.local 文件

1
2
3
4
5
6
# 将自动载入规则的命令写入开机自启脚本/etc/rc.d/rc.local文件
vim /etc/rc.d/rc.local
iptables-restore < /PATH/FROM/IPTABLES_RULES_FILE

# 关机前保存规则
iptables-save > /etc/sysconfig/iptables

方法二:使用 iptables-services

1
2
3
4
5
6
7
8
9
10
11
12
# 安装软件
[root@centos8 ~]#yum -y install iptables-services

# 设置开机启动
[root@centos8 ~]#systemctl enable iptables.service
[root@centos8 ~]#systemctl mask firewalld.service nftables.service

# 保存规则到文件,iptables.init 来自 iptables-services
[root@centos8 ~]$/usr/libexec/iptables/iptables.init save
iptables: Saving firewall rules to /etc/sysconfig/iptables:[ OK ]
# 或者
[root@centos8 ~]$iptables-save > /etc/sysconfig/iptables

网络防火墙

FORWARD

利用 filter 表的 FORWARD 链,可以充当网络防火墙

  1. 请求 - 响应报文均会经由 FORWARD 链,要注意规则的方向性
  2. 如果要启用 conntrack 机制,建议将双方状态为 ESTABLISHED 的报文直接放行

[实验:FORWARD 链实现内外网络的流量控制](16.1 实验 FORWARD 链实现内外网络的流量控制.md)

NAT

客户机和服务器都在局域网中,躲在防火墙后面,通过路由器连接网络。

NAT 是地址转换表,表中规则的常用 target:

  • SNAT:负责将局域网 IP 转成公网 IP,S 是源地址的意思,适用于固定的公网 IP
  • MASQUERADE:相较与 SNAT,MASQUERADE 适用于动态的公网 IP,如:拨号网络
  • DNAT:负责将公网 IP 转成局域网 IP,D 是目标地址的意思;
  • REDIRECT:

这么说有些抽象,我们看下面的例子:

SNAT

我本机的 IP 是 172.16.135.189/16,对外的公网 IP 是 39.164.140.134,当我访问互联网中的网站时,防火墙负责将 172.16.135.189 转成 39.164.140.134,等返回数据的时候,再把 39.164.140.134 转成 172.16.135.189。

有一个问题,局域网中的 ip 成百上千,大家对外的公网 IP 却都是 39.164.140.134,如果多台主机同时访问同一网站,路由器是如何做到准确的转发消息呢?
答案是对目标 IP 相同的请求分别绑定不同的端口号,例如 PC1 和 PC2 两台主机同时访问网站 http://lujinkai.cn,防火墙会把PC1的IP转成39.164.140.134:1234,把PC2的IP转成39.164.140.134:2345(1234和2345是随便写的,只要两个端口不一样就行)

范例:

1
iptables -t nat -A POSTROUTING -s 172.16.0.0/16 -j SNAT --to-source 39.164.140.134
  • –to-source:将报文的源 IP 修改为公网 IP

SNAT 中隐含了另一个技术:PNAT,即自动分配端口,只不过我们不单独说它。

MASQUERADE

如果家里拨号上网,外网 IP 不固定,可以使用 MASQUERADE

范例:

1
iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -j MASQUERADE

可以把 MASQUERADE 理解为动态的、自动化的 SNAT,如果没有动态 SNAT 的需求,没有必要使 MASQUERADE,因为 SNAT 更加高效。

DNAT

http://lujinkai.cn 网站的 IP 是 47.105.171.233,这个公网 IP 的后面可能是一台服务器,也可能是服务器集群,总之无论有多少服务器,防火墙外的人看不出来。当我访问去访问网站时,阿里云的防火墙会将 39.164.140.134 转成局域网 IP,然后将请求转发到真实的服务器。

范例:

1
[root@centos8 ~]$iptables -t nat -A PREROUTING -d 192.168.248.2 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.175:80
  • –to-destination:将报文的目标地址修改为内网 IP,并指定端口号
REDIRECT

DNAT 可以对 IP 和端口进行映射,REDIRECT 只能应用在本机,对本机的端口进行映射,比如将本机的 80 端口映射到 8080 端口上,当别的机器访问本机的 80 端口时,报文会被重定向到本机的 8080 端口上,此时本机只需要监听 8080 端口,不需要监听 80 端口

1
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080
  • –to-ports:端口转换

REDIRECT 规则只能定义在 PREROUTING 链或者 OUTPUT 链中

firewalld 服务

CentOS6 不支持 firewalld,CentOS8 支持 firewalld,但是官方推荐使用 ntf,所有 firewalld 了解即可。

在 iptables 的基础上,预定义了一些链(zone)和服务,以下以 CentOS7 中的 firewalld 为例:

zone

zone 名称 默认配置
trusted 允许所有流量
home 拒绝除和传出流量相关的,以及 ssh,mdsn,ipp-client,samba-client,dhcpv6-client 预定义服务之外其它所有传入流量
internal 和 home 相同
work 拒绝除和传出流量相关的,以及 ssh,ipp-client,dhcpv6-client 预定义服务之外的其它所有传入流量
public 拒绝除和传出流量相关的,以及 ssh,dhcpv6-client 预定义服务之外的其它所有传入流量,新加的网卡默认属于 public zone
external 拒绝除和传出流量相关的,以及 ssh 预定义服务之外的其它所有传入流量,属于 external zone 的传出 ipv4 流量的源地址将被伪装为传出网卡的地址。
dmz 拒绝除和传出流量相关的,以及 ssh 预定义服务之外的其它所有传入流量
block 拒绝除和传出流量相关的所有传入流量
drop 拒绝除和传出流量相关的所有传入流量(甚至不以 ICMP 错误进行回应)

firewall-cmd

firewall-cmd 是 firewalld 的命令行工具。

1
firewall-cmd [OPTIONS...]

常见选项:

  • –get-zones:列出所有可用区域
  • –get-default-zone:查询默认区域
  • –set-default-zone= :设置默认区域
  • –get-active-zones:列出当前正使用的区域
  • –add-source=[–zone=]:添加源地址的流量到指定区域,如果无–zone= 选项,使用默认区域
  • –remove-source= [–zone=]:从指定区域删除源地址的流量,如无–zone= 选项,使用默认区域
  • –add-interface=[–zone=]:添加来自于指定接口的流量到特定区域,如果无–zone= 选项,使用默认区域
  • –change-interface=[–zone=]:改变指定接口至新的区域,如果无–zone=选项,使用默认区域
  • –add-service= [–zone=]:允许服务的流量通过,如果无–zone= 选项,使用默认区域
  • –add-port=<PORT/PROTOCOL>[–zone=]:允许指定端口和协议的流量,如果无–zone= 选项,使用默认区域
  • –remove-service= [–zone=]:从区域中删除指定服务,禁止该服务流量,如果无–zone= 选项,使用默认区域
  • –remove-port=<PORT/PROTOCOL>[–zone=]:从区域中删除指定端口和协议,禁止该端口的流量,如果无–zone= 选项,使用默认区域
  • –reload:删除当前运行时配置,应用加载永久配置
  • –list-services:查看开放的服务
  • –list-ports:查看开放的端口
  • –list-all [–zone=]:列出指定区域的所有配置信息,包括接口,源地址,端口,服务等,如果无–zone= 选项,使用默认区域

范例:

1
2
3
4
5
6
7
8
9
10
#查看默认zone
firewall-cmd --get-default-zone
#默认zone设为dmz
firewall-cmd --set-default-zone=dmz
#在internal zone中增加源地址192.168.0.0/24的永久规则
firewall-cmd --permanent --zone=internal --add-source=192.168.0.0/24
#在internal zone中增加协议mysql的永久规则
firewall-cmd --permanent --zone=internal --add-service=mysql
#加载新规则以生效
firewall-cmd --reload

练习

如果是 SecureCRT,需要安装 Xming:下载地址

基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌────────────────────────────────────────────────────────┐
#Basic Configuration
│========================================================
#platform=x86, AMD64, or Intel EM64T
#Default Language默认语言
│lang en_US
# Keyboard 键盘
│keyboard 'us'
# timezone 时区(勾选了"Use UTC clock" 会追加[--isUtc])
│timezone Asia/Shanghai
# Root password
│rootpw --iscrypted $1$DBk7xfJp$Agxd303XUAfRKIf7gB8DG/
└──────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────┐
#Advanced Configuration
│勾选就有,不勾没有
│========================================================
# Reboot after installation
│reboot
# Use text mode install
│text
└────────────────────────────────────────────────────────┘

安装方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌────────────────────────────────────────────────────────┐
#Installation Method
│========================================================
# Install OS instead of upgrade
│install
# Upgrade existing installation
│upgrade
└────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────┐
#Installation source
│选了哪项就写哪项
│========================================================
# Use CDROM installation media
│cdrom
# Use NFS installation media
│nfs --server=服务器 --dir=目录
# Use network installation
│url --url="ftp://用户名:密码@服务器/目录"
# Use network installation
│url --url="http://服务器/目录"
# Use hard drive installation media
│harddrive --dir=目录 --partition=分区└────────────────────────────────────────────────────────┘

引导选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌────────────────────────────────────────────────────────────────┐
#Installation Method&GRUB options&Install Options
│=================================================================
│ ┌────────────────────────────────────┐
│ │ 选择了Do not install a boot loader │
│ └────────────────────────────────────┘
# System bootloader configuration
│ bootloader --location=none

│ ┌────────────────────────────────────┐
│ │ 选择了install new boot loader │
│ └────────────────────────────────────┘
│ bootloader --append="ker" --location=mbr --password="123"
#append是内核参数,location是bootloader安装位置,password是GRUB密码
└────────────────────────────────────────────────────────────────┘

分区信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────┐
# Master Boot Record
#Master Boot Record选择了clear... 否则就没有
│======================================================
# Clear the Master Boot Record
│zerombr
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
# Partitions&Disk Label
│======================================================
# Partition clearing information
│clearpart --linux --initlabel
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
# Layout 分区
│part 挂载点 --fstype=文件系统 --size=大小(单位M)
│======================================================
# Disk partitioning information
│part / --fstype="xfs" --size=10240
│part /boot --fstype="ext4" --size=1024
│part swap --fstype="swap" --size=2048
└─────────────────────────────────────────────────────┘

网络配置

1
2
3
4
5
6
7
┌─────────────────────────────────────────────────────┐
# Network Configuration
│Centos7如果要写eth0,要加内核参数net.ifnames=0
│======================================================
# Network information
│network --bootproto=dhcp --device=eth0
└─────────────────────────────────────────────────────┘

认证

1
2
3
4
5
6
7
┌───────────────────────────────────────────────────────────────┐
# Authentication
│如果勾选Enable Fingerprint reader则追加参数 --enablefingerprint
│===============================================================
# System authorization information
│auth --useshadow --passalgo=md5
└───────────────────────────────────────────────────────────────┘

防火墙配置

1
2
3
4
5
6
7
8
9
10
11
┌───────────────────────────────────────────────────────────────┐
# Firewall Configuration
│===============================================================
# SELinux configuration
│selinux --disabled或permissive或enforcing

# Firewall configuration
│firewall --disabled或enabled
#如果是enable,可以在追加:--http --ftp --telnet --smtp --ssh
#还可以追加端口:--port=555:tcp,444:udp
└───────────────────────────────────────────────────────────────┘

Display Configuration

1
2
3
4
5
6
7
8
9
┌───────────────────────────────────────────────────────────────┐
# Display Configuration
│===============================================================
│如果选了安装图形界面,就没有下面这句话
# Do not configure the X Window System
│skipx
# Run the Setup Agent on first boot
│firstboot --enabledisable
└───────────────────────────────────────────────────────────────┘

Package Selection

如果包安装的界面不出现可选的包信息,那么需要修改 yum 仓库配置文件

1
2
3
[root@centos ~]#vim /etc/yum.repos.d/***.repo
[development]
#把原来"[]"内的内容改成development,其它不变

当不知道应答文件怎么写的时候,可以先手动安装一遍,然后参考其 anaconda-ks.cfg 文件

以下所有的用户密码都是 ljkk

CentOS 6

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
install
text
reboot
url --url=http://10.0.0.8/centos/6/os/x86_64/
lang en_US.UTF-8
keyboard us
network --onboot yes --device eth0 --bootproto dhcp --noipv6
rootpw --iscrypted $6$CTxlVPMx.O7NW6To$Ahp5HPWLJgKZguGM0/48EBGmfo/6wLwiUIHkpHdM9g4TOt6onfiy1Xk1FlMMpJIsp4guQfp5OLLOdLs5rSGmX1
authconfig --enableshadow --passalgo=sha512
firewall --disabled
selinux --disabled
timezone Asia/Shanghai
bootloader --location=mbr --driveorder=sda --append="crashkernel=auto rhgb quiet"
zerombr
clearpart --all --initlabel
part /boot --fstype="ext4" --ondisk=sda --size=1024
part pv.01 --size 1 --grow
volgroup volgrp pv.01
logvol swap --vgname=volgrp --name=swap --fstype="swap" --size=2048
logvol /data --vgname=volgrp --name=data --fstype="ext4" --size=51200
logvol / --vgname=volgrp --name=root --fstype="ext4" --size=1 --grow
%packages
@core
@server-policy
@workstation-policy
autofs
vim-enhanced
%end
%post
useradd lujinkai
echo 123456 | passwd --stdin lujinkai &> /dev/null
mkdir /etc/yum.repos.d/bak
mv /etc/yum.repos.d/* /etc/yum.repos.d/bak
cat > /etc/yum.repos.d/base.repo <<EOF
[base]
name=base
baseurl=file:///misc/cd
gpgcheck=0
EOF
%end

CentOS 7

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
install
xconfig --startxonboot
keyboard --vckeymap=us --xlayouts='us'
rootpw --iscrypted $1$YyodG.X7$LzhHqxWONgSh0NBoC730o0
url --url="http://10.0.0.8/centos/7/os/x86_64"
lang en_US
auth --useshadow --passalgo=sha512
text
firstboot --enable
selinux --disabled
skipx
services --disabled="chronyd"
ignoredisk --only-use=sda
firewall --disabled
network --bootproto=dhcp --device=eth0
network --hostname=centos7.magedu.org
reboot
timezone Asia/Shanghai --nontp
bootloader --append="net.ifnames=0" --location=mbr --boot-drive=sda
zerombr
clearpart --all --initlabel
part /boot --fstype="ext4" --ondisk=sda --size=1024
part pv.01 --size 1 --grow
volgroup volgrp pv.01
logvol swap --vgname=volgrp --name=swap --fstype="swap" --size=2048
logvol /data --vgname=volgrp --name=data --fstype="ext4" --size=51200
logvol / --vgname=volgrp --name=root --fstype="ext4" --size=1 --grow
%post
useradd lujinkai
%end
%packages
@core
%end

CentOS 8

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
ignoredisk --only-use=sda
zerombr
text
reboot
clearpart --all --initlabel
selinux --disabled
firewall --disabled
url --url=http://10.0.0.8/centos/8/os/x86_64/
keyboard --vckeymap=us --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --device=eth0 --ipv6=auto --activate
bootloader --append="net.ifnames=0" --location=mbr --boot-drive=sda
network --hostname=centos8.magedu.org
rootpw --iscrypted $6$CTxlVPMx.O7NW6To$Ahp5HPWLJgKZguGM0/48EBGmfo/6wLwiUIHkpHdM9g4TOt6onfiy1Xk1FlMMpJIsp4guQfp5OLLOdLs5rSGmX1
firstboot --enable
skipx
services --disabled="chronyd"
timezone Asia/Shanghai --isUtc --nontp
user --name=lujinkai --password=$6$CTxlVPMx.O7NW6To$Ahp5HPWLJgKZguGM0/48EBGmfo/6wLwiUIHkpHdM9g4TOt6onfiy1Xk1FlMMpJIsp4guQfp5OLLOdLs5rSGmX1 --iscrypted --gecos="lujinkai"
part /boot --fstype="ext4" --ondisk=sda --size=1024
part pv.01 --size 1 --grow
volgroup volgrp pv.01
logvol swap --vgname=volgrp --name=swap --fstype="swap" --size=2048
logvol /data --vgname=volgrp --name=data --fstype="ext4" --size=51200
logvol / --vgname=volgrp --name=root --fstype="ext4" --size=1 --grow
%packages
@^minimal-environment
kexec-tools
%end
%addon com_redhat_kdump --enable --reserve-mb='auto'
%end
%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end

kvm 自动应答文件(centos7)

系统安装完后,在家目录会有两个 cfg 文件,original-ks.cfg 是自己编辑的自动应答文件,anaconda-ks.cfg 是系统根据 original-ks.cfg 自动生成的应答文件

  • original-ks.cfg
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
install
xconfig --startxonboot
keyboard --vckeymap=us --xlayouts='us'
lang en_US.UTF-8
rootpw --iscrypted $1$YyodG.X7$LzhHqxWONgSh0NBoC730o0
url --url="http://10.0.0.1/centos/7/os/x86_64"
auth --useshadow --passalgo=sha512
text
firstboot --enable
selinux --disabled
skipx
services --disabled="chronyd"
ignoredisk --only-use=vda
firewall --disabled
network --bootproto=dhcp --device=eth0
network --hostname=c7
reboot
timezone Asia/Shanghai --nontp
bootloader --append="net.ifnames=0" --location=mbr --boot-drive=vda
zerombr
clearpart --all --initlabel
part /boot --fstype="ext4" --ondisk=vda --size=300
part pv.01 --size 1 --grow
volgroup volgrp pv.01
logvol swap --vgname=volgrp --name=swap --fstype="swap" --size=2048
logvol / --vgname=volgrp --name=root --fstype="ext4" --size=1 --grow
%post
useradd lujinkai
%end
%packages
@^minimal
@core
%end
  • anaconda-ks.cfg
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
#version=DEVEL
# System authorization information
auth --useshadow --passalgo=sha512
# Install OS instead of upgrade
install
# Use text mode install
text
# Firewall configuration
firewall --disabled
# Run the Setup Agent on first boot
firstboot --enable
ignoredisk --only-use=vda
# Keyboard layouts
keyboard --vckeymap=us --xlayouts='us'
# System language
lang en_US.UTF-8

# Network information
network --bootproto=dhcp --device=eth0 --activate
network --bootproto=dhcp --hostname=c7
# Reboot after installation
reboot
# Use network installation
url --url="http://10.0.0.1/centos/7/os/x86_64"
# Root password
rootpw --iscrypted $1$YyodG.X7$LzhHqxWONgSh0NBoC730o0
# SELinux configuration
selinux --disabled
# System services
services --disabled="chronyd"
# Do not configure the X Window System
skipx
# System timezone
timezone Asia/Shanghai --nontp
# X Window System configuration information
xconfig --startxonboot
# System bootloader configuration
bootloader --append="net.ifnames=0 crashkernel=auto" --location=mbr --boot-drive=vda
# Clear the Master Boot Record
zerombr
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
part /boot --fstype="ext4" --ondisk=vda --size=300
part pv.60 --fstype="lvmpv" --size=20179
volgroup volgrp --pesize=4096 pv.60
logvol / --fstype="ext4" --grow --size=1 --name=root --vgname=volgrp
logvol swap --fstype="swap" --size=2048 --name=swap --vgname=volgrp

%post
useradd lujinkai
%end

%packages
@^minimal
@core
kexec-tools

%end

%addon com_redhat_kdump --enable --reserve-mb='auto'

%end

kvm 安装在逻辑卷

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
#version=DEVEL
# System authorization information
auth --enableshadow --passalgo=sha512
# Use CDROM installation media
cdrom
# Use graphical install
graphical
# Run the Setup Agent on first boot
firstboot --enable
ignoredisk --only-use=vda
# Keyboard layouts
keyboard --vckeymap=us --xlayouts='us'
# System language
lang en_US.UTF-8

# Network information
network --bootproto=dhcp --device=eth0 --ipv6=auto --activate
network --hostname=localhost.localdomain

# Root password
rootpw --iscrypted $6$FyziLpCibU/RQPqp$G56mBV90w0lM1xVCK6Bwsg4ANZd/Gj8hKsENU3mfQAPoqBevdCyw.Dw88OkOHBUxpIoq7Zi.NQ6890l5Otzev.
# System services
services --disabled="chronyd"
# System timezone
timezone Asia/Shanghai --isUtc --nontp
user --name=lujinkai --password=$6$oKgYyszaGm2rHnk/$cDyAFysw5bhQTT18sGCREat6ZkIfo9/BlluiPlWmAfTk/48x9KukRepXoRelKqiA9tjRYFcWEmHivHjl2TOrR0 --iscrypted --gecos="lujinkai"
# System bootloader configuration
bootloader --append=" crashkernel=auto" --location=mbr --boot-drive=vda
# Partition clearing information
clearpart --none --initlabel
# Disk partitioning information
part /boot --fstype="ext4" --ondisk=vda --size=300
part pv.428 --fstype="lvmpv" --ondisk=vda --size=4819
volgroup centos --pesize=4096 pv.428
logvol / --fstype="ext4" --size=4816 --name=root --vgname=centos

%packages
@^minimal
@core
kexec-tools

%end

%addon com_redhat_kdump --enable --reserve-mb='auto'

%end

%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end

系统安装过程

Linux 安装过程

  1. 加载 boot loader
  2. 加载启动安装菜单
  3. 加载内核和 initrd 文件
  4. 加载根系统
  5. 运行 anaconda 的安装向导

isolinux 目录下安装相关文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 将光盘挂载到/mnt/sro目录下
[root@centos8 mnt]$mount /dev/sr0 /mnt/sr0/
mount: /mnt/sr0: WARNING: device write-protected, mounted read-only.
# 查看isolinux目录下的文件
[root@centos8 isolinux]$ll /mnt/sr0/isolinux/
total 72859
-r--r--r--. 1 root root 2048 Jun 8 18:08 boot.cat
-r--r--r--. 1 root root 84 Jun 8 17:26 boot.msg
-r--r--r--. 1 root root 293 Jun 8 17:26 grub.conf
-r--r--r--. 2 root root 65114136 Jun 8 17:25 initrd.img
-r--r--r--. 1 root root 38912 Jun 8 17:26 isolinux.bin
-r--r--r--. 1 root root 3075 Jun 8 17:26 isolinux.cfg
-r--r--r--. 1 root root 116096 Nov 8 2019 ldlinux.c32
-r--r--r--. 1 root root 180700 Nov 8 2019 libcom32.c32
-r--r--r--. 1 root root 22804 Nov 8 2019 libutil.c32
-r--r--r--. 1 root root 182704 May 12 2019 memtest
-r--r--r--. 1 root root 186 Jul 30 2019 splash.png
-r--r--r--. 1 root root 2885 Jun 8 18:08 TRANS.TBL
-r--r--r--. 1 root root 26788 Nov 8 2019 vesamenu.c32
-r-xr-xr-x. 2 root root 8913656 May 8 07:07 vmlinuz
  • boot.cat: 相当于 grub 的第一阶段
  • isolinux.bin:光盘引导程序,在 mkisofs 的选项中需要明确给出文件路径,这个文件属于 SYSLINUX 项目
  • isolinux.cfg:启动菜单的配置文件,当光盘启动后(即运行 isolinux.bin),会自动去找 isolinux.cfg 文件
  • vesamenu.c32:是光盘启动后的启动菜单图形界面,也属于 SYSLINUX 项目,menu.c32 提供纯文本的菜单
  • memtest:内存检测程序
  • splash.png:光盘启动菜单界面的背景图
  • vmlinuz:是内核映像
  • initrd.img:ramfs 文件

安装菜单的内核参数

安装光盘的启动菜单配置文件:isolinux/isolinux.cfg,每个 label 就是一个菜单项,主菜单或者二级菜单。

isolinux.cfg 中部分内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 默认使用vesamenu.c32:图形化菜单界面。menu.32提供纯文本的菜单
default vesamenu.c32
# 默认等待时间,单位是十分之一秒
timeout 600
# 标题
menu title CentOS Linux 8

# 菜单选项
label linux
menu label ^Install CentOS Linux 8 # 菜单标签
kernel vmlinuz # 加载的内核,这里是相对路径
# 向内核传递参数,除了菜单文件指定,在安装界面还可以tab键后 手动输入,常见内核参数见下表
append initrd=initrd.img inst.stage2=hd:LABEL=CentOS-8-2-2004-x86_64-dvd quiet

# menu begin 设置Troubleshooting的二级菜单 开始
menu begin ^Troubleshooting
menu title Troubleshooting

# menu begin 和 menu begin end 之间的内容,属于二级菜单
...

# menu begin 设置二级菜单 结束
menu end

常见内核参数

注意:安装程序的引导参数都使用 inst. 作为前缀。目前这个前缀是可选的,例如 inst.ks= 和 ks= 的效果完全一样。但预期在未来会强制使用 inst. 前缀。

自动安装的应答文件

实现自动化安装前,需要制作对应的应答文件,称为 kickstart 文件,用于保存安装过程中的需要指定的选项

如果是图形化手动一步步安装,系统会自动生成应答文件:/root/anaconda-ks.cfg

kickstart 文件格式的官方说明:centos6centos7centos8

kickstart 文件格式说明

kickstart 文件主要包括三个部分:命令段、程序包段、脚本段

命令段

指明各种安装前配置,如键盘类型等

命令 说明
keyboard 设定键盘类型
lang 语言类型
zerombr 清除 mbr
clearpart 清除分区
part 创建分区
rootpw 指明 root 的密码
timezone 时区
text 文本安装界面
network 指定网络设置
firewall 设置防火墙设置
selinux 设置 selinux 设置
reboot 安装完自动重启
user 安装完成后为系统创建新用户
url 指明安装源
程序包段

%packages:指定要安装的程序包组或程序包、指定不安装的程序包等。以%end 结尾。范例:

1
2
3
4
5
6
%packages
@^environment group # 环境包组,如:@^minimal-environment
@group_name # 程序包组
package # 程序包
-package # 不安装的程序包
%end
脚本段
  • %pre: 安装前脚本,%end 结尾
  • %post: 安装后脚本,%end 结尾

范例:

1
2
3
4
%post
useradd mage
echo magedu | passwd --stdin mage &> /dev/null
%end
其他
  • %addon:安装插件,%end 结尾,范例:

    1
    2
    %addon com_redhat_kdump --enable --reserve-mb=128
    %end
    1
    2
    3
    4
    5
    6
    7
    8
    %addon org_fedora_oscap
    content-type = datastream
    content-url = http://www.example.com/scap/testing_ds.xml
    datastream-id = scap_example.com_datastream_testing
    xccdf-id = scap_example.com_cref_xccdf.xml
    profile = xccdf_example.com_profile_my_profile
    fingerprint = 240f2f18222faa98856c3b4fc50c4195
    %end
  • %anaconda:控制安装系统的用户界面的行为。此部分必须放在 kickstart 文件的末尾。目前,在%anaconda 部分中,pwplicy 是唯一可以使用的命令,范例:

    1
    2
    3
    4
    # 设置密码策略,要求root密码至少为10个字符长,并严格禁止与此要求不匹配的密码
    %anaconda
    pwpolicy root --minlen=10 --strict
    %end

注意:CentOS6、7、8 的 kickstart 文件格式不尽相同,不可混用

kickstart 文件创建

  • 可使用创建工具:system-config-kickstart ,注意:此方法 CentOS 8 不再支持
  • CentOS 安装完后,会根据当前系统的安装过程,自动生成一个 kickstart 文件:/root/anaconda-ks.cfg,可以根据此文件修改并生成新配置

推荐使用第二种方式,手动编辑,最后使用 ksvalidator(来自于 pykickstart 包) 工具检查一下文件格式是否有语法错误。

1
2
[root@centos8 ~]$yum install pykickstart.noarch
[root@centos8 ~]$ksvalidator ./anaconda-ks.cfg

制作引导 U 盘

制作应答文件、修改 iso 的启动菜单配置文件 isolinux.cfg,然后把 iso 制作成引导 U 盘,就可以实现半自动化安装。

应答文件可以放在 iso 中,也可以放在 http 服务器,只需要在 isolinux.cfg 中配置好 ks=path 即可。

DHCP

主机获取网络配置可以通过两种方式:

  • 静态指定
  • 动态获取
    • bootp:boot protocol,IP 与 MAC 一一静态对应
    • dhcp:增强的 bootp,IP 与 MAC 既可以静态绑定,也可以动态分配

DHCP 工作原理

DHCP: Dynamic Host Configuration Protocol,动态主机配置协议。基于 UDP 协议,DHCP Server 默认 67 端口号,DHCP Client 默认 68(ipv4)和 546(ipv6)端口号。

主要用途:

  • 用于内部网络和网络服务供应商自动分配 IP 地址给用户
  • 用于内部网络管理员作为对所有电脑作集中管理的手段
  • 自动化安装系统
  • 解决 IPV4 资源不足问题

DHCP 实现

实现 DHCP 服务的软件:

  • dhcp(centos7) 或 dhcp server(centos8)
  • dnsmasq:小型服务软件,可以提供 dhcp 和 dns 服务

DHCP 相关文件组成

dhcp 或 dhcp server 包文件组成:

  • /usr/sbin/dhcpd:dhcp 服务主程序
  • /etc/dhcp/dhcpd.conf:dhcp 服务配置文件
  • /usr/share/doc/dhcp-server/dhcpd.conf.example:dhcp 服务配置范例文件
  • /usr/lib/systemd/system/dhcpd.service:dhcp 服务 service 文件
  • /var/lib/dhcpd/dhcpd.leases:DHCP 服务器日志,记录了地址分配记录

dhcp-client 客户端包:

  • /usr/sbin/dhclient:客户端程序
  • /var/lib/dhclient/dhclient.leases:DHCP 客户端日志,

DHCP 服务器配置文件

帮助参考:man 5 dhcpd.conf

dhcpd.conf 配置信息: subnet 网段设置; host 主机设置。 范例:

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
option domain-name "magedu.org"; # 分配给用户域名,就是/etc/resolve.conf中的domain
# 分配给用户DNS,就是/etc/resolve.conf中的nameserver
option domain-name-servers 180.76.76.76, 223.6.6.6;
# 默认租约时间,以秒为单位,十分钟,五分钟就要续约,所以一般我们需要把这个值调长一些
default-lease-time 600;
max-lease-time 7200; # 最长续约时间,两个小时
log-facility local7; # 日志类型,具体什么含义后面的日志章节会详细学习,这里先不用管它
# 网段设置
subnet 10.0.0.0 netmask 255.255.255.0 {
range 10.0.0.10 10.0.0.100; # ip 范围
range 10.0.0.110 10.0.0.200; # ip 范围
option routers 10.0.0.2; # 路由
# 提供引导文件的服务器IP地址,一般是一个tftp服务器。与pxe自动化安装有关
next-server 10.0.0.8;
# 指明引导文件名称:pxelinux.0
filename "pxelinux.0";
}
# 主机设置,DHCP服务器给指定主机分配固定IP
host testclient {
hardware ethernet 00:0c:29:33:b4:1a;
fixed-address 10.0.0.106; # 指定ip,与MAC地址绑定
default-lease-time 86400;
max-lease-time 864000;
option routers 10.0.0.254;
option domain-name-servers 114.114.114.114,8.8.8.8 ;
option domain-name "magedu.net";
}

dhclient 命令

1
2
3
dhclient -d  # 强制dhclient作为前台进程运行
dhclient -l # 尝试获得一次租约
dhclient -r # 释放当前租约并停止正在运行的DHCP客户端

TFTP

OpenSSH 服务中,我们学习了 SFTP 是安全版的 FTP,而 TFTP 是简化版的 FTP:

  1. FTP 基于 TCP、TFTP 基于 UDP

  2. FTP 支持账号登录,TFTP 不支持账号登录

  3. FTP 使用 2 个端口:TCP 端口 21,是个侦听端口;TCP 端口 20 或更高 TCP 端口 1024 以上用于源连接。

    FTP 仅使用一个具有停止和等待模式的端口:端口:69/udp

  4. FTP 有许多可以执行的命令(get,put,ls,dir,lcd)并且可以列出目录等
    TFTP 只有 5 个指令可以执行(rrq,wrq,data,ack,error)

安装和使用 TFTP

服务器包 tftp-server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos8 ~]$dnf -y install tftp-server  # 安装dftp服务器包
[root@centos8 ~]$rpm -ql tftp-server
/usr/lib/.build-id
/usr/lib/.build-id/8c
/usr/lib/.build-id/8c/6921a9fb21d66da4fb299d516bce9ee6afea34
/usr/lib/systemd/system/tftp.service # tftp service文件
/usr/lib/systemd/system/tftp.socket # tftp socket文件
/usr/sbin/in.tftpd # tftp主程序
/usr/share/doc/tftp-server
/usr/share/doc/tftp-server/CHANGES
/usr/share/doc/tftp-server/README
/usr/share/doc/tftp-server/README.security
/usr/share/man/man8/in.tftpd.8.gz
/usr/share/man/man8/tftpd.8.gz
/var/lib/tftpboot # TFTP服务数据目录

# 启动服务并设置开机自启
[root@centos8 ~]$systemctl enable --now tftp.service
[root@centos8 ~]$ss -ntulp

# 准备测试文件
[root@centos8 ~]$cd /var/lib/tftpboot/
[root@centos8 tftpboot]$echo 'hello tftp' > test.txt

客户端包 tftp:

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
[root@centos7 ~]#yum -y install tftp
[root@centos7 ~]#tftp 10.0.0.8
tftp> ?
tftp-hpa 5.2
Commands may be abbreviated. Commands are:

connect connect to remote tftp
mode set file transfer mode
put send file
get receive file
quit exit tftp
verbose toggle verbose mode
trace toggle packet tracing
literal toggle literal mode, ignore ':' in file name
status show current status
binary set mode to octet
ascii set mode to netascii
rexmt set per-packet transmission timeout
timeout set total retransmission timeout
? print help information
help print help information
tftp> get test.txt # 注意,目录不能get,ftp也不行
tftp> quit
[root@centos7 ~]#ll test.txt
-rw-r--r--. 1 root root 12 Sep 15 19:12 test.txt
[root@centos7 ~]#cat test.txt
hello tftp

利用 PXE 实现自动化安装部署

PXE:Preboot Excution Environment,预启动执行环境,是由 Intel 公司研发,基于 Client/Server 的网络模式。

Intel PXE 规范定义了一些机制和协议,可让 PXE 设备使用其网络接口卡 (NIC) 来查找位于网络服务器上的引导程序。在 PXE 规范中,这些程序被称为“网络引导程序”(NBP)。

复习:MBR 硬盘的 0 号扇区的前 446 个字符是主引导程序 BootLoader,作用是查找和装载可引导的操作系统。
NBP 类似 BootLoader,只不过 NMP 是在网络中查找配置文件,根据配置文件引导系统。

syslinux 是一个启动加载器集合,支持从硬盘、光盘或通过 PXE 的网络引导启动系统,支持 ext4、FAT 等文件系统。Server 安装 syslinux 后,会在/usr/share/syslinux 目录下生成很多文件,其中 pxelinux.0 就是 NBP,pxelinux.cfg 就是配置文件。

PXE 启动工作原理:

在 CentOS8 上实现 PXE 自动化安装 CentOS6、7、8

安装前准备:

  • 关闭 Vmware 软件中的 DHCP 服务,设置为基于 NAT 模式
  • 关闭 CentOS 8 的防火墙和 SELINUX
  • 确保 CentOS 8 的 IP 是静态指定,不是 DHCP 获取
  • 使用 1G 以下内存的主机安装 CentOS 7,8 会提示空间不足,建议 2G 以上
  1. 安装相关软件包并启动
1
2
[root@centos8 ~]#dnf -y install dhcp-server tftp-server httpd syslinux-nonlinux
[root@centos8 ~]#systemctl enable --now httpd tftp dhcpd
  1. 配置 DHCP 服务
1
2
3
4
5
6
7
8
9
10
11
12
option domain-name "magedu.org";
option domain-name-servers 180.76.76.76, 223.6.6.6;
default-lease-time 600;
max-lease-time 7200;
log-facility local7;
subnet 10.0.0.0 netmask 255.255.255.0 {
range 10.0.0.10 10.0.0.100;
range 10.0.0.110 10.0.0.200;
option routers 10.0.0.2;
next-server 10.0.0.8;
filename "pxelinux.0";
}
  1. 配置 TFTP 服务,参考上文
  2. 准备 yum 源和相关目录
1
2
3
4
[root@centos8 ~]#mkdir -pv /var/www/html/centos/{6,7,8}/os/x86_64/
[root@centos8 ~]#mount /dev/sr0 /var/www/html/centos/6/os/x86_64/
[root@centos8 ~]#mount /dev/sr1 /var/www/html/centos/8/os/x86_64/
[root@centos8 ~]#mount /dev/sr2 /var/www/html/centos/7/os/x86_64/
  1. 准备应答文件

centos6:

1
# 参考 14.1 CentOS6、7、8应答文件.md

centos7:

1
# 参考 14.1 CentOS6、7、8应答文件.md

centos8:

1
# 参考 14.1 CentOS6、7、8应答文件.md
  1. 准备 PXE 启动相关文件
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
[root@centos8 tftpboot]$mkdir /var/lib/tftpboot/centos{6,7,8}
[root@centos8 centos]$pwd
/var/www/html/centos
[root@centos8 centos]$cp 6/os/x86_64/isolinux/{vmlinuz,initrd.img} /var/lib/tftpboot/centos6/
[root@centos8 centos]$cp 7/os/x86_64/isolinux/{vmlinuz,initrd.img} /var/lib/tftpboot/centos7/
[root@centos8 centos]$cp 8/os/x86_64/isolinux/{vmlinuz,initrd.img} /var/lib/tftpboot/centos8/
[root@centos8 centos]$cp /usr/share/syslinux/{pxelinux.0,menu.c32} /var/lib/tftpboot/

#以下三个文件是CentOS8安装所必须文件,CentOS6,7则不需要
[root@centos8 centos]$cp 8/os/x86_64/isolinux/{ldlinux.c32,libcom32.c32,libutil.c32} /var/lib/tftpboot/

#生成安装菜单文件
[root@centos8 centos]$mkdir /var/lib/tftpboot/pxelinux.cfg
[root@centos8 centos]$cp 8/os/x86_64/isolinux/{ldlinux.c32,libcom32.c32,libutil.c32} /var/lib/tftpboot/

#最终目录结构如下
[root@centos8 centos]$cd /var/lib/tftpboot/
[root@centos8 tftpboot]$tree
.
├── centos6
│   ├── initrd.img
│   └── vmlinuz
├── centos7
│   ├── initrd.img
│   └── vmlinuz
├── centos8
│   ├── initrd.img
│   └── vmlinuz
├── ldlinux.c32
├── libcom32.c32
├── libutil.c32
├── menu.c32
├── pxelinux.0
└── pxelinux.cfg
└── default

4 directories, 12 files
  1. 准备启动菜单文件

    修改/var/lib/tftpboot/pxelinux.cfg/default

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
default menu.c32
timeout 600
menu title Install CentOS Linux

label linux8
menu label Auto Install CentOS Linux ^8
kernel centos8/vmlinuz
append initrd=centos8/initrd.img inst.ks=http://10.0.0.8/ks/centos8.cfg

label linux7
menu label Auto Install CentOS Linux ^7
kernel centos7/vmlinuz
append initrd=centos7/initrd.img inst.ks=http://10.0.0.8/ks/centos7.cfg

label linux6
menu label Auto Install CentOS Linux ^6
kernel centos6/vmlinuz
append initrd=centos6/initrd.img inst.ks=http://10.0.0.8/ks/centos6.cfg

label manual
menu label ^Manual Install CentOS Linux 8.0
kernel centos8/vmlinuz
append initrd=centos8/initrd.img inst.repo=http://10.0.0.8/centos/8/os/x86_64/

label rescue
menu label ^Rescue a CentOS Linux system 8
kernel centos8/vmlinuz
append initrd=centos8/initrd.img inst.repo=http://10.0.0.8/centos/8/os/x86_64/ rescue

label local
menu default
menu label Boot from ^local drive
localboot 0xffff
  1. 新准备一台主机,设置网卡引导,可看到看启动菜单,并实现自动安装

    安装成功:

利用 cobbler 实现自动安装

控制 shell 程序的资源

1
ulimit [-SHacdefilmnpqrstuvx] [limit]

配置文件

ulimit 命令,立即生效,但无法保存,永久保存需要修改配置文件:

1
2
3
4
5
6
7
8
/etc/security/limits.conf
/etc/security/limits.d/*.conf

# 配置文件格式:每行一个定义
<domain> <type> <item> <value>

* soft nproc 4096
root soft nproc unlimited

配置文件详细信息:man 5 limits.conf

domain

domain description
username 一个用户
@group 组内所有用户
* 所有用户
% 限制最多有多少用户登录,%对*生效,限制组使用%group
:
@:
%:

type

type description
hard 软限制,普通用户自己可以修改
soft 硬限制,由 root 用户设定,且通过 kernel 强制生效
- 二者同时限定

item

item default description
core
data
fsize
memlock 最大锁定内存地址空间
nofile 1024 所能够同时打开的最大文件数量
rss
stack
cpu
nproc 1024 所能够同时运行的进程的最大数量
as
maxlogins
maxsyslogins
priority
locks
sigpending
msgqueue POSIX 消息队列所使用的最大内存
nice
rtprio

范例:

1
2
3
4
5
6
7
8
9
10
*               soft    core            0
* hard nofile 512
@student hard nproc 20
@faculty soft nproc 20
@faculty hard nproc 50
ftp hard nproc 0
@student - maxlogins 4
:123 hard cpu 5000
@500: soft cpu 10000
600:700 hard locks 10

生产案例:

1
2
3
4
5
* - core unlimited
* - nproc 1000000
* - nofile 1000000
* - memlock 32000
* - msgqueue 8192000

案例:ulimit 命令修改用户打开的文件个数

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
[root@centos8 ~]#ulimit -n
1024
[root@centos8 ~]#ulimit -n 1048577
-bash: ulimit: open files: cannot modify limit: Operation not permitted
[root@centos8 ~]#ulimit -n 1048576
[root@centos8 ~]#ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7111
max locked memory (kbytes, -l) 16384
max memory size (kbytes, -m) unlimited
open files (-n) 1048576
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7111
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
[root@centos8 ~]#echo 2^20|bc
1048576

案例:限制用户最多打开的文件数和运行进程数,并持久保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat /etc/pam.d/system-auth
session required pam_limits.so

vim /etc/security/limits.conf
#用户apache可打开10240个文件
apache - nofile 10240
#用户student不能运行超过20个进程
student hard nproc 10

#用student登录多次运行bash,观察结果

[root@centos8 ~]#vim /etc/security/limits.conf
wang - nofile 66666
wang - nproc 5
mage - nofile 88888

[root@centos8 ~]#su - wang
Last login: Mon May 25 14:40:38 CST 2020 on pts/0
[wang@centos8 ~]$ulimit -n
66666

案例:限制 mage 用户最大的同时登录次数

1
2
3
4
5
6
7
8
[root@centos8 ~]#tail -n1 /etc/security/limits.conf
mage - maxlogins 2

[root@centos8 ~]#who
mage tty1 2020-05-25 14:35
root pts/0 2020-05-25 14:35 (10.0.0.1)
root pts/3 2020-05-25 14:06 (10.0.0.1)
mage tty3 2020-05-25 14:35

1
openssl command [ command_opts ] [ command_args ]

两种运行模式:交互模式、批处理模式

直接输入 openssl 回车进入交互模式,输入带命令选项的 openssl 进入批处理模式。

OpenSSL 整个软件包大概可以分成三个主要的功能部分:密码算法库SSL 协议库以及应用程序。OpenSSL 的目录结构自然也是围绕这三个功能部分进行规划的。

openssl 子命令分为三类:1、标准命令、2、消息摘要命令、3、加密命令

标准命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@centos8 data]$openssl list -commands
asn1parse ca ciphers cms
crl crl2pkcs7 dgst dhparam
dsa dsaparam ec ecparam
enc engine errstr gendsa
genpkey genrsa help list
nseq ocsp passwd pkcs12
pkcs7 pkcs8 pkey pkeyparam
pkeyutl prime rand rehash
req rsa rsautl s_client
s_server s_time sess_id smime
speed spkac srp storeutl
ts verify version x509

消息摘要命令:

1
2
3
4
5
6
7
[root@centos8 data]$openssl list -digest-commands
blake2b512 blake2s256 gost md2
md4 md5 rmd160 sha1
sha224 sha256 sha3-224 sha3-256
sha3-384 sha3-512 sha384 sha512
sha512-224 sha512-256 shake128 shake256
sm3

加密命令:对称加密和 base64 编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos8 data]$openssl list -cipher-commands
aes-128-cbc aes-128-ecb aes-192-cbc aes-192-ecb
aes-256-cbc aes-256-ecb aria-128-cbc aria-128-cfb
aria-128-cfb1 aria-128-cfb8 aria-128-ctr aria-128-ecb
aria-128-ofb aria-192-cbc aria-192-cfb aria-192-cfb1
aria-192-cfb8 aria-192-ctr aria-192-ecb aria-192-ofb
aria-256-cbc aria-256-cfb aria-256-cfb1 aria-256-cfb8
aria-256-ctr aria-256-ecb aria-256-ofb base64
bf bf-cbc bf-cfb bf-ecb
bf-ofb camellia-128-cbc camellia-128-ecb camellia-192-cbc
camellia-192-ecb camellia-256-cbc camellia-256-ecb cast
cast-cbc cast5-cbc cast5-cfb cast5-ecb
cast5-ofb des des-cbc des-cfb
des-ecb des-ede des-ede-cbc des-ede-cfb
des-ede-ofb des-ede3 des-ede3-cbc des-ede3-cfb
des-ede3-ofb des-ofb des3 desx
idea idea-cbc idea-cfb idea-ecb
idea-ofb rc2 rc2-40-cbc rc2-64-cbc
rc2-cbc rc2-cfb rc2-ecb rc2-ofb
rc4 rc4-40 rc5 rc5-cbc
rc5-cfb rc5-ecb rc5-ofb seed
seed-cbc seed-cfb seed-ecb seed-ofb
zlib

消息摘要命令和加密命令初学不推荐直接使用,推荐通过标准命令 dgst 和 enc 调用。

command

1
2
3
4
5
6
7
8
9
10
11
12
version  # 查看版本信息
enc # 对称加密
dgst # 摘要
dhparam # 生成DH参数文件
dsaparam # 生成DSA参数文件
gendsa # 从DSA参数文件生成DSA私钥
dsa # 管理DSA密钥,可以给解密加密的私钥,可以给私钥设置密码,还可以从DSA私钥中生成公钥
passwd # 生成密码
rand # 生成随机数
ca # CA管理,例如对证书进行签名
req # 主要用于创建和处理PKCS#10格式的证书请求,可以创建自签名证书
x509 # 显示证书信息、将证书转换为各种形式、签署类似“迷你CA”的证书请求或编辑证书信任设置

http://linux.51yip.com/search/openssl

1. 对称加密:

OpenSSL 一共提供了 8 种对称加密算法,DES 是最常用的。

2. 非对称加密:

OpenSSL 一共实现了 4 种非对称加密算法,包括 DH 算法、RSA 算法、DSA 算法和椭圆曲线算法(EC)。DH 算法一般用户密钥交换。RSA 算法既可以用于密钥交换,也可以用于数字签名,当然,如果你能够忍受其缓慢的速度,那么也可以用于数据加密。DSA 算法则一般只用于数字签名。

3. 信息摘要算法:

OpenSSL 实现了 5 种信息摘要算法,分别是 MD2、MD5、MDC2、SHA(SHA1)和 RIPEMD。SHA 算法事实上包括了 SHA 和 SHA1 两种信息摘要算法,此外,OpenSSL 还实现了 DSS 标准中规定的两种信息摘要算法 DSS 和 DSS1。

4. 密钥和证书管理:

OpenSSL 为密钥和证书提供了丰富的功能,支持多种标准。

首先,OpenSSL 实现了 ASN.1 的证书和密钥相关标准,提供了对证书、公钥、私钥、证书请求以及 CRL 等数据对象的 DER、PEM 和 BASE64 的编解码功能。OpenSSL 提供了产生各种公开密钥对和对称密钥的方法、函数和应用程序,同时提供了对公钥和私钥的 DER 编解码功能。并实现了私钥的 PKCS#12 和 PKCS#8 的编解码功能。OpenSSL 在标准中提供了对私钥的加密保护功能,使得密钥可以安全地进行存储和分发。

在此基础上,OpenSSL 实现了对证书的 X.509 标准编解码、PKCS#12 格式的编解码以及 PKCS#7 的编解码功能。并提供了一种文本数据库,支持证书的管理功能,包括证书密钥产生、请求产生、证书签发、吊销和验证等功能。

事实上,OpenSSL 提供的 CA 应用程序就是一个小型的证书管理中心(CA),实现了证书签发的整个流程和证书管理的大部分机制。

对称加密

openssl 提供了两种方式调用对称加密算法:

一种就是直接调用对称加密指令;一种是使用 enc,对称加密指令作为 enc 的参数。例如下面两种写法是完全一样的:

1
openssl des-cbc -in plain.txt -out encrypt.txt -pass pass:12345678
1
openssl enc -des-cbc -in plain.txt -out encrypt.txt -pass pass:12345678

初学推荐使用 enc,更宜读

1
openssl enc [option]...
  • -help

  • -ciphers:列出所有 enc 的对称加密算法 option,上面的示例中的 -des-cba 就在其中

  • -in file:从 file 中读取内容,默认是从标准输入中读取

  • -out file:输出到 file,默认是输出到标准输出

  • -pass arg:设置加密或解密密码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 加密
    openssl enc -des3 -in a.log -out b.bin -pass pass:123456
    # 解密
    openssl enc -des3 -d -in b.bin -out b.log -pass pass:123456
    # 命令行输入密码
    pass:123456
    # 文件输入密码
    file:passwd.txt
    # 环境变量输入密码
    export passwd=123456
    env:passwd
    # 从文件描述符输入密码
    fd:1
    # 从标准输入输入密码
    # stdin
  • -e:加密,默认选项,可以省略

  • -d:解密

  • -a:base64 虽然不是加密,但是 enc 也支持了 base64 编码

  • -base64:同 -a

  • -A:如果设置了-a,则-A 让 base64 输出的结果不分行

  • -k:已淘汰

  • -md:指定 hash 算法,将 hash 处理后的 passwd 作为密码

  • -S:手动指定盐值

摘要算法

支持的摘要算法命令使用openssl list -digest-commands查看,可以作为 openssl 的子命令直接调用,也可以作为 dgst 的短选项调用。

1
2
3
4
5
6
7
[root@centos8 data]$openssl list -digest-commands
blake2b512 blake2s256 gost md2
md4 md5 rmd160 sha1
sha224 sha256 sha3-224 sha3-256
sha3-384 sha3-512 sha384 sha512
sha512-224 sha512-256 shake128 shake256
sm3
1
dgst [options] [file...]

file…:files to digest (default is stdin)

  • -help Display this summary
  • -c 输出的摘要信息以分号隔离,和-hex 同时使用
  • -out outfile 指定输出文件,默认标准输出
  • -、、、
1
2
3
4
5
6
7
8
9
10
11
12
# 标准输入 生成摘要
[root@centos8 data]$echo {0..10} | openssl md5
(stdin)= da0feb828a99053332f6d08e0a58f33e
[root@centos8 data]$echo {0..10} | openssl dgst -md5
(stdin)= da0feb828a99053332f6d08e0a58f33e
# 文件内容 生成摘要
[root@centos8 data]$echo {0..10} > a.log
[root@centos8 data]$openssl dgst -md5 ./a.log
MD5(./a.log)= da0feb828a99053332f6d08e0a58f33e
[root@centos8 data]$openssl dgst -out a.md5 -md5 ./a.log
[root@centos8 data]$cat a.md5
MD5(./a.log)= da0feb828a99053332f6d08e0a58f33e

补充:coreutils 包提供了很多核心工具,例如 cat、cp、ls 等等,其中也有摘要算法相关的工具:md5sum,sha1sum,sha224sum,sha256sum ,sha384sum,sha512sum 等,以 md5 为例:

1
md5sum [OPTION]... [FILE]...

非对称加密

非对称加密相关命令都是 openssl 的标准命令,没有额外再单独设置一个命令管理它们。

  • dhparam:生成 DH 参数文件
  • dsaparam:生成 DSA 参数文件
  • gendsa:从 DSA 参数文件生成 DSA 私钥
  • dsa:管理 DSA 密钥,可以给解密加密的私钥,可以给私钥设置密码,还可以从 DSA 私钥中生成公钥
  • genrsa:生成 RSA 私钥
  • rsa:管理 RSA 密钥

DH(Diffie-Hellman)

1
2
3
4
5
6
7
# 使用生成因子2和随机的1024-bit的素数产生D0ffie-Hellman参数
# 输出保存到文件dhparam.pem
openssl dhparam -out dhparam.pem -2 1024

# 从dhparam.pem中读取Diffie-Hell参数,以C代码的形式
# 输出到stdout
penssl dhparam -in dhparam.pem -noout -C

DSA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 生成1024位DSA参数集,并输出到文件dsaparam.pem
openssl dsaparam -out dsaparam.pem 1024

# 使用参数文件dsaparam.pem生成DSA私钥匙,
# 采用3DES加密后输出到文件dsaprivatekey.pem
openssl gendsa -out dsaprivatekey.pem -des3 dsaparam.pem

# 使用私钥匙dsaprivatekey.pem生成公钥匙,
# 输出到dsapublickey.pem
openssl dsa -in dsaprivatekey.pem -pubout -out dsapublickey.pem

# 从dsaprivatekey.pem中读取私钥匙,解密并输入新口令进行加密,
# 然后写回文件dsaprivatekey.pem
openssl dsa -in dsaprivatekey.pem -out dsaprivatekey.pem -des3 -passin

RSA

生成私钥,并 3des 加密:

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
[root@centos8 data]$openssl genrsa -out rsa.key -des3 1024
Generating RSA private key, 1024 bit long modulus (2 primes)
...............+++++
............+++++
e is 65537 (0x010001)
Enter pass phrase for rsa.key:
Verifying - Enter pass phrase for rsa.key:
[root@centos8 data]$cat rsa.key
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,B81F9992681E3346

fi2XSHajJUAhKHNeUFB+nJqog0R8qiBfQ3yEljQHadyq7U7P+7evDV9JQxpwtDC1
gZ3BpjY1+4NmN1lXbzoADLwx/tRIgHqQXgkIgY/tSeKTOtaaFbRsg7DZXjcq3YMX
seo8DoZEbyyzAVIYVdR/Z40niYNoaEWiXiwzT8Wz11BORftqJER+07s2NRDSpQNj
8Q7R9L6uT+RWnde5IeCZjZ9X9cj9ZBlMw1trKpiwbqTeq1YtRHcpKLSyg69f5ERy
sW9qaf00qNHR6LTHL3Hgw+EBo9JYmspflvhGWO0QmWKNbBLnjEGS6QgbxgxQT0mP
d0Imwn/3Xit8c/ACdoHZiqwYkzrreR9K7CHD2USiMlun08vcWyal3EzPOAfshZWq
g6MXdLSs3nOA03dkP4mjjb3iCca8xxSFKQPWb3viZzg14gWRAlVo1Fp9+0d+U1An
hHpqX/64MiPZakRPyGfPA6uycJgxIxKzhnam/mKSAFkQoxt61zKst9JFKWwCnEAv
ucKZXIGPJGsMW5bXkVFdMdnx3iUZDHFBQmFLuA5+w8CQTBw3wLY1PKfvtpoLY2S3
qP9xnqqjPzg+RzBgNvWPIQ+y5SECZq5CdnGK/vLrysUBy3eUdfgiyZ5h7i2Bugnu
wieFH5FvaGHAljN6tcGo4xPjfPb+SEujkl+Ot6R1lCzHXvEgvaCBVBN5qx5rFH9H
CQ8ZeIKjs/jscCvgMhqhzRfOkhzvKruShQ1G3t1prvZ5JNZCy0ePkh/g9PMxIaVv
DIZbEiA+Yy5NrXmkCxHmVIACg4mxetPJCtg7yTG4P/dQKE/3PPLwZA==
-----END RSA PRIVATE KEY-----

解密私钥:

不需要指定加密用的算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@centos8 data]$openssl rsa -in rsa.key
Enter pass phrase for rsa.key:
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC3SFoUHD+Jk8dt2l7MWqDvhDUF/jMlzCMs2VhnWytPdWWjTHeO
zNj0rt6L8uMq45C3CmTzaBz6v6fdAH5RKLkv78ZqjhxfRwIgRtUCRCa+b+lqq0qc
9paKTFl+gAnSAkx0ua90XAx65DAs9XpL5HkGHI7Jj7Cb0ERluZNV0Sv8YwIDAQAB
AoGAfB61UfUnWiYH4m8Fz+J4Jnwj5GEXhjtOfurZoXTuSas5H3ODa+Nx8ZITCDd+
e+cMc8jIQMZ7CZyNM29IG/I2JgkXEk6apyLy7UAiQF0CbYljLOy7qBkrcYCo2RBa
IGUoTX7RJwC+3Dxa3/12Tx4ufqZ5/+RAJoqqV90rx8EFLnECQQDat+/KaEte9xJ0
+NgmOGna68p4B1zNR/LQ5/n3yEweBLaseOke2o3BnsIAcL5LLNC5d3jun9UWyl2R
Xtflcly/AkEA1oYgAs//M4M/o3bZxYBaQkWiC+u+YsKMwMA+DPYs/H2fzwprP+4N
mjoKYrjL214nchuDE1fM1YErcvkn5AN1XQJBAJCDVBbizloS2ckb2oV2ZMrXXNHt
221vioppnAoR9+klqCVRRoayVVOHOBveYn19QPQqcmcIiF0knKo+hlv+MjUCQDw/
syHXFM983xSjvomvgKn4MIi0juXhyfIgi8zMHtpS1d0qCfEMhJl6D4ymZeqYSO/N
NkTqdcbI3lEOFNv+9KkCQHGLnBuVcZ6IaneTIl6Dj5fuSH4JcTgWpeXJNbfWO+iR
7bpy5LnVqTM2rrvEjAdhMOD5xLbaxwRxSAMnG9lDyD8=
-----END RSA PRIVATE KEY-----

从私钥中生成公钥:

1
2
3
4
5
6
7
8
9
10
[root@centos8 data]$openssl rsa -in rsa.key -pubout -out rsa.key.pub
Enter pass phrase for rsa.key:
writing RSA key
[root@centos8 data]$cat rsa.key.pub
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3SFoUHD+Jk8dt2l7MWqDvhDUF
/jMlzCMs2VhnWytPdWWjTHeOzNj0rt6L8uMq45C3CmTzaBz6v6fdAH5RKLkv78Zq
jhxfRwIgRtUCRCa+b+lqq0qc9paKTFl+gAnSAkx0ua90XAx65DAs9XpL5HkGHI7J
j7Cb0ERluZNV0Sv8YwIDAQAB
-----END PUBLIC KEY-----

生成用户密码

1
passwd [options] {passwords}

默认使用 标准 Unix 密码算法 生成密码:

1
2
3
4
5
6
7
[root@centos8 data]$openssl passwd 123456
pPVKzob9muWhI
[root@centos8 data]$openssl passwd 123456
RGMogKjCYuqRo
[root@centos8 data]$openssl passwd 123456 admin
RmxJ4CJbLmGUs
ziknRF1V5vFSs

options:

  • -in file:从文件中读入密码,默认交互式输入
  • -stdin:从标准输入读取密码,默认交互式输入
  • -salt val:加盐,指定盐值
  • -6:SHA512
  • -5:SHA256
  • -1:MD5
  • -、、、

生成随机数

/dev/random 和/dev/urandom 根据硬件信息生成随机数,是伪随机数生成器,openssl rand 是真随机数生成器

1
rand [options] num

num:指定随机数的字节数。

options:

  • -out file:将输出的随机数写入到文件
  • -base64:将输出的随机数转成 base64 格式
  • -hex:将输出的随机数转成 16 进制格式

范例:生成 10 位随机数

1
2
3
4
[root@centos7 ~]#openssl rand -hex 5
2ce882e4e7
[root@centos7 ~]#openssl rand -base64 9 |head -c10
7yTjuE+FAC

在 CentOS8 上实现私有 CA 和证书申请颁发

相关文件:

1
2
3
4
5
6
7
[root@centos8 tls]$rpm -ql openssl-libs
/etc/pki/tls
/etc/pki/tls/certs
/etc/pki/tls/ct_log_list.cnf
/etc/pki/tls/misc
/etc/pki/tls/openssl.cnf # 配置文件
/etc/pki/tls/private
1
2
3
4
5
6
7
8
9
10
11
# 私有CA 目录

./demoCA - main CA directory
./demoCA/cacert.pem - 证书,也就是自签名公钥
./demoCA/private/cakey.pem - 自签名私钥
./demoCA/serial - CA serial number file
./demoCA/serial.old - CA serial number backup file
./demoCA/index.txt - 数据库文件
./demoCA/index.txt.old - CA text database backup file
./demoCA/certs - 颁发的证书存放目录
./demoCA/.rnd - CA random seed information

1. 创建私有 CA

私有 CA 的目录位置在 openssl.conf 中可以更改,默认设置如下,我们就不改了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[ CA_default ]

dir = /etc/pki/CA # Where everything is kept
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # database index file.
#unique_subject = no # Set to 'no' to allow creation of
# several certs with same subject.
new_certs_dir = $dir/newcerts # default place for new certs.

certificate = $dir/cacert.pem # The CA certificate
serial = $dir/serial # The current serial number
crlnumber = $dir/crlnumber # the current crl number
# must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/cakey.pem# The private key
  1. 按照 openssl.conf 中的设置,创建私有 CA 目录及相关文件
1
2
3
4
5
[root@centos8 ~]$mkdir /etc/pki/CA
[root@centos8 ~]$cd /etc/pki/CA
[root@centos8 CA]$mkdir certs crl newcerts private
[root@centos8 CA]$touch index.txt
[root@centos8 CA]$echo 01 > serial # 指定第一个颁发证书的序列号
  1. 生成私钥,这里选择 RSA 私钥,比较简单
1
2
3
4
5
[root@centos8 CA]$openssl genrsa -out private/cakey.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.......................+++++
..................+++++
e is 65537 (0x010001)
  1. 生成 CA 自签名证书
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
[root@centos8 CA]$openssl req -new -x509 -key private/cakey.pem -days 3650 -out cacert.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:henan
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:ljk
Email Address []:ljk@qq.com
[root@centos8 CA]$
[root@centos8 CA]$openssl x509 -noout -in cacert.pem -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
26:c2:97:d3:b0:0e:f4:8d:d7:3e:1c:a1:e2:95:08:31:20:aa:f1:b7
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = CN, ST = henan, L = Default City, O = Default Company Ltd, CN = ljk, emailAddress = ljk@qq.com
Validity
Not Before: Sep 8 08:17:04 2020 GMT
Not After : Sep 6 08:17:04 2030 GMT
Subject: C = CN, ST = henan, L = Default City, O = Default Company Ltd, CN = ljk, emailAddress = ljk@qq.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:bc:30:93:e4:81:8f:aa:ed:c7:27:c3:66:6b:17:
7d:f2:40:f2:1c:5e:12:86:89:5a:ca:e1:d2:6d:61:
fa:3c:0e:36:d6:88:bc:c3:1c:e9:a3:a4:f7:28:14:
4b:7f:5a:48:e0:1f:3e:3a:dc:45:12:27:a9:ef:94:
51:95:1b:84:79:ae:6b:11:3d:77:92:a4:72:ee:4a:
47:c9:c6:13:84:03:f7:be:48:48:8d:ac:d4:b5:7b:
fd:36:04:6a:90:22:6f:5d:06:cc:52:c6:21:a2:0f:
48:fb:d1:cb:5b:66:f7:05:e5:35:10:14:6a:07:bc:
35:66:fd:d9:c4:30:35:91:bb:ca:6c:bb:77:79:4d:
e2:9e:03:71:72:e4:bd:7b:cf:2e:96:30:0e:7e:2d:
10:c4:5a:b3:66:03:7a:68:95:78:e1:31:28:86:35:
43:f6:be:7c:b8:d2:36:8f:ed:d0:0e:0a:98:49:59:
63:42:45:70:f2:a1:8d:30:b9:6b:6f:b2:49:c9:e2:
ae:0c:08:b2:47:bf:48:c7:be:d6:e8:26:8c:21:07:
e4:a9:16:e9:f1:c6:30:e0:41:de:3c:d8:81:fd:fe:
86:b2:6f:b1:04:35:73:ac:67:36:a8:39:ee:ff:15:
6b:4c:30:7e:6f:dd:9e:02:16:af:ae:46:e1:5c:e9:
b1:e5
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
62:CE:34:13:29:DA:F0:67:D8:CB:8D:6C:90:1A:3C:C8:E5:15:4D:D9
X509v3 Authority Key Identifier:
keyid:62:CE:34:13:29:DA:F0:67:D8:CB:8D:6C:90:1A:3C:C8:E5:15:4D:D9

X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
4c:87:4d:25:3f:3d:5f:b9:9e:b1:35:2f:7e:be:26:da:6b:e0:
4a:77:56:79:80:9e:a7:8b:80:02:71:75:1c:12:28:e7:73:49:
c8:b4:44:41:3f:1a:b4:b6:db:8e:33:8e:29:8f:01:2f:9e:dc:
34:1c:45:78:a2:8c:82:25:9e:da:5f:69:fb:3c:15:98:db:36:
d7:a7:41:09:bc:b0:36:b7:ae:77:10:7a:7a:0e:00:ed:cd:22:
28:99:d3:a1:28:47:cd:6a:01:88:e5:d4:cc:42:be:d5:2a:16:
72:af:44:d8:b0:b9:83:99:e9:f3:08:c1:ea:f6:b1:11:ee:51:
d7:83:b8:1e:7c:45:47:25:0d:bc:5e:9d:78:cc:c1:26:0c:33:
5b:78:e5:1a:5f:31:79:11:54:a7:42:3a:dc:ed:43:66:b5:6c:
e1:f5:61:82:d5:92:19:f6:6c:e8:20:01:b4:0a:07:9f:5b:63:
1e:29:49:f0:58:4a:ed:ef:1a:67:a3:f1:ec:e3:e6:b3:50:3a:
c4:5b:ef:23:55:13:69:0a:ac:42:77:22:4b:0b:34:c1:f4:e9:
80:89:ff:ff:43:af:84:4e:a7:ef:f0:28:7a:14:c0:ce:f9:4b:
aa:db:29:4b:fb:5c:ac:bf:c9:5f:ce:cd:45:62:dc:6e:18:a0:
cc:f5:07:e9
[root@centos8 CA]$
[root@centos8 CA]$tree
.
├── cacert.pem
├── certs
├── crl
├── index.txt
├── newcerts
├── private
│   └── cakey.pem
└── serial

4 directories, 4 files

req:主要用于创建和处理 PKCS#10 格式的证书请求,可以创建自签名证书

  • -new:生成新证书签署请求
  • -x509:专用于 CA 生成自签证书
  • -key:生成请求时用到的私钥文件
  • -days n:证书的有效期限
  • -out /PATH/TO/SOMECERTFILE: 证书的保存路径
  • -key val:指定已有的秘钥文件生成秘钥请求,只与-new 配合
  • -newkey:与 -key 互斥,在生成证书请求或者自签名证书的时候自动生成密钥,生成的密钥名称由 -keyout 参数指定。当指定-newkey 选项时,后面指定 rsa:bits 说明产生 rsa 密钥,位数由 bits 指定。 如果没有指定选项-key-newkey,默认-newkey
  • -nodes:如果指定 -newkey,那么 -nodes 选项说明生成的秘钥不需要加密

x509:显示证书信息、将证书转换为各种形式、签署类似“迷你 CA”的证书请求或编辑证书信任设置

2. 申请证书并颁发证书

  1. 申请证书,就是申请一个认证过的公钥,首先生成私钥
1
2
3
4
5
[root@centos8 data]$openssl genrsa -out test.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.............................................................................................+++++
........................................................................................................+++++
e is 65537 (0x010001)
  1. 使用私钥生成证书申请文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@centos8 data]$openssl req -new -key ./test.key -out ./test.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:henan
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:ljk
Email Address []:ljk@qq.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:123456
An optional company name []:ljk

三种策略:默认是 match

  • match:要求申请填写的信息跟 CA 设置信息必须一致,
  • optional:可有可无,跟 CA 设置信息可不一致
  • supplied:必须填写这项申请信息
  1. CA 签署证书
1
[root@centos8 data]$openssl ca -in ./test.csr -out /etc/pki/CA/certs/test.pem -days 100
  1. 吊销证书
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
# 查看serial与subject信息,对比检验是否与index.txt文件中的信息一致,一致则删除
[root@centos8 certs]$openssl x509 -in ./test.crt -noout -serial -subject
serial=01
subject=C = CN, ST = henan, O = Default Company Ltd, CN = ljk, emailAddress = ljk@qq.com
[root@centos8 certs]$
[root@centos8 certs]$cat ../index.txt
V 201217092506Z 01 unknown /C=CN/ST=henan/O=Default Company Ltd/CN=ljk/emailAddress=ljk@qq.com

# 吊销之前,查看一下状态
[root@centos8 certs]$openssl ca -status 1
Using configuration from /etc/pki/tls/openssl.cnf
01=Valid (V)

# 吊销,注意目录
[root@centos8 certs]$openssl ca -revoke ../newcerts/01.pem
Using configuration from /etc/pki/tls/openssl.cnf
Revoking Certificate 01.
Data Base Updated

# 吊销之后,再看一下状态
[root@centos8 certs]$openssl ca -status 1
Using configuration from /etc/pki/tls/openssl.cnf
01=Revoked (R)

# 指定第一个吊销证书的编号,注意:第一次更新证书吊销列表前,才需要执行
[root@centos8 certs]$echo 01 > /etc/pki/CA/crlnumber
# 更新证书吊销列表
[root@centos8 certs]$openssl ca -gencrl -out /etc/pki/CA/crl.pem
Using configuration from /etc/pki/tls/openssl.cnf

# 查看crl文件
[root@centos8 certs]$openssl crl -in /etc/pki/CA/crl.pem -noout -text
Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = CN, ST = henan, L = Default City, O = Default Company Ltd, CN = ljk, emailAddress = ljk@qq.com
Last Update: Sep 8 09:39:42 2020 GMT
Next Update: Oct 8 09:39:42 2020 GMT
CRL extensions:
X509v3 CRL Number:
1
Revoked Certificates:
Serial Number: 01
Revocation Date: Sep 8 09:35:55 2020 GMT
Signature Algorithm: sha256WithRSAEncryption
09:a1:00:89:ed:5a:4d:71:3d:39:f0:34:72:7a:df:49:91:a1:
82:86:4f:76:7f:f9:b9:54:13:b1:b5:05:18:aa:4b:88:28:33:
7a:cf:0e:e5:ff:e4:41:36:33:62:95:82:20:aa:de:1e:76:f5:
61:10:11:d7:1e:da:19:2e:cc:b5:3a:96:17:4d:98:b2:f3:23:
78:1a:af:20:7b:e9:f9:eb:3a:a7:b5:58:46:67:c1:60:8c:e6:
2f:79:3b:16:24:f6:09:fc:09:12:96:de:60:09:2e:78:60:e4:
18:1a:36:aa:b2:eb:a1:31:23:7c:33:9a:dc:59:7c:b0:dd:a6:
fa:a6:72:23:9b:35:b7:4e:d3:98:44:49:44:66:9c:d4:82:56:
07:0d:23:da:1e:62:3e:6e:87:de:9e:6e:88:0f:0d:e7:50:3f:
67:9f:3f:86:89:c3:6a:bc:b8:bc:89:c4:8e:e8:d6:7b:12:81:
7f:85:07:3b:e0:34:d7:29:fd:67:fb:cb:7f:f3:51:f2:3f:a4:
68:ce:e2:f1:3c:c2:49:fd:72:e0:27:f5:e6:23:e8:ae:a6:8f:
b4:ba:eb:bc:1b:c3:4b:dd:1b:9e:39:5e:a8:ed:87:1d:5b:9f:
ef:42:02:68:2a:b4:c3:2d:31:24:3c:85:e7:d7:66:40:e2:07:
2e:61:77:0f

3. 说明

以上是常规的步骤,为了方便,还有其他简单的操作,例如在一个目录中创建 CA,并给 harbor 主机颁发证书,只需要三步:

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
root@u1:~$ mkdir -p /usr/local/harbor/certs
root@u1:~$ cd /usr/local/harbor/certs
root@u1:/usr/local/harbor/certs$
# 1. 创建私有CA
root@u1:/usr/local/harbor/certs$ openssl req \
-newkey rsa:1024 -nodes -sha256 -keyout ca.key -x509 -subj "/CN=ca.ljk.org" -days 3650 -out ca.crt
Generating a RSA private key
..............+++++
...................................+++++
writing new private key to 'ca.key'
-----
root@u1:/usr/local/harbor/certs$ ls
ca.crt ca.key
# 2. 生成harbor主机的证书申请
root@u1:/usr/local/harbor/certs$ openssl req \
-newkey rsa:1024 -nodes -sha256 -subj "/CN=harbor.ljk.org" -keyout harbor.ljk.org.key -out harbor.ljk.org.csr
Generating a RSA private key
.....................................................................+++++
..........+++++
writing new private key to 'harbor.ljk.org.key'
-----
root@u1:/usr/local/harbor/certs$ ls
ca.crt ca.key harbor.ljk.org.csr harbor.ljk.org.key
# 3. 给harbor主机颁发证书
root@u1:/usr/local/harbor/certs$ openssl x509 -req -in harbor.ljk.org.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out harbor.ljk.org.crt
Signature ok
subject=CN = harbor.ljk.org
Getting CA Private Key
root@u1:/usr/local/harbor/certs$ tree
.
├── ca.crt
├── ca.key
├── ca.srl
├── harbor.ljk.org.crt
├── harbor.ljk.org.csr
└── harbor.ljk.org.key

0 directories, 6 files

CentOS7 创建自签名证书

CentOS7 和 8 步骤不一样。

说明:ssh 客户端(后面简称 ssh)、ssh 服务端(后面简称 sshd)

SSH 认证过程

Client 10.0.0.8 ssh 远程连接 Server 10.0.0.7:

1
2
3
4
5
6
7
8
[root@centos8 ~]$ssh 10.0.0.7
The authenticity of host '10.0.0.7 (10.0.0.7)' can't be established.
ECDSA key fingerprint is SHA256:CfDVSb/UU590ZKQ0DZhZp7/76rLHeKak/pHGp2af4kw.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.0.7' (ECDSA) to the list of known hosts.
root@10.0.0.7's password:
Last login: Wed Sep 9 19:05:15 2020 from 10.0.0.8
[root@centos7 ~]#

  1. no.6、no.8:协商版本号,可以看到 Client 的 OpenSSH 版本更高,则以 Server 为准

  2. no.10、no.11:相互交换各自支持的算法,协商各种算法,以 Client 优先

  3. no.12 - 14:使用 DH 算法交换密钥,之后的通信都使用会话密钥加密,具体参考笔记[13.2 DH.md](./13.2 DH.md),

  4. 认证:SSH 支持多种认证,最常用的是口令认证和密钥认证,这个过程已经加密了,用 Wireshark 抓包看不出任何信息

相关文件

一台主机,既可以是 ssh Client,也可以是 ssh Server

ssh Client 相关文件

文件 说明
/etc/ssh/ssh_config 客户端全局配置文件
~/.ssh/config 客户端的用户配置文件,默认不存在,权限只能是 644
~/.ssh/known_hosts 保存服务端的公钥
~/.ssh/id_rsa 客户端私钥,由 ssh-keygen 生成,权限只能是 600
~/.ssh/id_rsa.pub 客户端公钥,由 ssh-keygen 生成

/etc/ssh/ssh_config:

需要说明的是,客户端配置文件有很多配置项和服务端配置项名称相同,但它们一个是在连接时采取的配置(客户端配置文件),一个是 sshd 启动时开关性的设置(服务端配置文件)。例如,两配置文件都有 GSSAPIAuthentication 项,在客户端将其设置为 no,表示连接时将直接跳过该身份验证机制,而在服务端设置为 no 则表示 sshd 启动时不开启 GSSAPI 身份验证的机制。即使客户端使用了 GSSAPI 认证机制,只要服务端没有开启,就绝对不可能认证通过。

选项 默认值 说明
PasswordAuthentication yes 是否启用基于密码的身份认证机制
HostbasedAuthentication no 是否启用基于主机的身份认证机制
GSSAPIAuthentication no 是否启用基于 GSSAPI 的身份认证机制
BatchMode no yes:将禁止 passphrase/password 询问
StrictHostKeyChecking ask yes:拒绝连接那些未知的主机,它将强制用户手动添加 host key 到~/.ssh/known_hosts 中;
ask:询问是否保存到~/.ssh/known_hosts 文件;
no:自动添加到~/.ssh/known_hosts 文件
Port 22 当命令行中不指定端口时,默认连接的远程主机上的端口

ssh Server 相关文件

文件 说明
/etc/ssh/sshd_config 服务端全局配置文件
/etc/ssh/sshhost* sshd 启动时生成的服务端公钥和私钥文件,其中私钥文件权限只能是 600
~/.ssh/authorized_keys 保存客户端的公钥,用于 key 验证,免密登录

/etc/ssh/sshd_config:

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
#Port 22                # 服务端SSH端口,可以指定多条表示监听在多个端口上
#ListenAddress 0.0.0.0 # 监听的IP地址。0.0.0.0表示监听所有IP
Protocol 2 # 使用SSH 2版本

#####################################
# 私钥保存位置 #
#####################################
# HostKey for protocol version 1
#HostKey /etc/ssh/ssh_host_key # SSH 1保存位置/etc/ssh/ssh_host_key
# HostKeys for protocol version 2
#HostKey /etc/ssh/ssh_host_rsa_key # SSH 2保存RSA位置/etc/ssh/ssh_host_rsa _key
#HostKey /etc/ssh/ssh_host_dsa_key # SSH 2保存DSA位置/etc/ssh/ssh_host_dsa _key


###################################
# 杂项配置 #
###################################
#PidFile /var/run/sshd.pid # 服务程序sshd的PID的文件路径
#ServerKeyBits 1024 # 服务器生成的密钥长度
#SyslogFacility AUTH # 使用哪个syslog设施记录ssh日志。日志路径默认为/var/log/secure
#LogLevel INFO # 记录SSH的日志级别为INFO

###################################
# 以下项影响认证速度 #
###################################
#UseDNS yes # 指定是否将客户端主机名解析为IP,以检查此主机名是否与其IP地址真实 对应。默认yes。
# 由此可知该项影响的是主机验证阶段。建议在未配置DNS解析时,将其设置 为no,否则主机验证阶段会很慢

###################################
# 以下是和安全有关的配置 #
###################################
#PermitRootLogin yes # 是否允许root用户登录
#GSSAPIAuthentication no # 是否开启GSSAPI身份认证机制,默认为yes
#PubkeyAuthentication yes # 是否开启基于公钥认证机制
#AuthorizedKeysFile .ssh/authorized_keys # 基于公钥认证机制时,来自客户端的公钥的存放位置
PasswordAuthentication yes # 是否使用密码验证,如果使用密钥对验证可以关了它
#PermitEmptyPasswords no # 是否允许空密码,如果上面的那项是yes,这里最好设置no
#MaxSessions 10 # 最大客户端连接数量
#LoginGraceTime 2m # 身份验证阶段的超时时间,若在此超时期间内未完成身份验证将自动断开
#MaxAuthTries 6 # 指定每个连接最大允许的认证次数。默认值是6。
# 如果失败认证次数超过该值一半,将被强制断开,且生成额外日志消息。
MaxStartups 10 # 最大允许保持多少个未认证的连接。默认值10。

###################################
# 以下可以自行添加到配置文件 #
###################################
DenyGroups hellogroup testgroup # 表示hellogroup和testgroup组中的成员不允许使用sshd服务,即拒绝 这些用户连接
DenyUsers hello test # 表示用户hello和test不能使用sshd服务,即拒绝这些用户连接

###################################
# 以下一项和远程端口转发有关 #
###################################
#GatewayPorts no # 设置为yes表示sshd允许被远程主机所设置的本地转发端口绑定在非环回 地址上
# 默认值为no,表示远程主机设置的本地转发端口只能绑定在环回地址上,见 后文"远程端口转发"

一般来说,如非有特殊需求,只需修改下监听端口和 UseDNS 为 no 以加快主机验证阶段的速度即可。

ssh 客户端工具

ssh

1
2
3
4
5
ssh [options] [user@]remote_host [command]

-p port # 远程服务监听的端口,默认是22
-o option # 临时修改配置
-i <file > # 指定私钥文件,默认使用 ~/.ssh/id_xxx

ssh-keygen

在客户端生成密钥对,默认存放在~/.ssh 目录下

1
2
3
ssh-keygen -t rsa # 提示保存位置,输入密码

ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa # 不提示直接生成密钥

ssh-copy-id

使用 SSH 协议,将公钥追加到服务器的 ~/.ssh/authorized_keys 文件中

1
ssh-copy-id -i ~/.ssh/id_rsa.pub remote_host

scp

scp 命令用于 Linux 之间复制文件和目录,基于 SSH 协议。

1
2
3
4
5
6
7
scp [options] SRC... DEST/

-C # 压缩数据流
-r # 递归复制
-p # 保持原文件的属性信息
-q # 静默模式
-P # PORT 指明remote host的监听的端口

范例:

1
2
3
4
5
6
7
8
9
# 从本地复制到远程:
scp local_file root@to2b.cn:remote_file
scp local_file root@lujinkai:remote_file
scp local_file root@test.to2b.cn:remote_file

# 从远程复制到本地:
scp root@to2b.cn:remote_file local_file
scp root@lujinkai.cn:remote_file local_file
scp root@test.to2b.cn:remote_file local_file

rsync

rsync 工具基于 SSH 和 RSYNC 协议实现高效率的远程系统之间复制文件,比 scp 更快,基于增量数据同步,此工具来自于 rsync 包

注意:通信双方都需要安装 rsync 软件

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
rsync -av /etc server1:/tmp #复制目录和目录下文件
rsync -av /etc/ server1:/tmp #只复制目录下文件

-n # 模拟复制过程
-v # 显示详细过程,-vvvv显示更详细的信息
-r # 递归复制目录树
-p # 保留权限(不包括特殊权限)
-t # 保留修改时间戳(mtime),强烈建议任何时候都加上"-t",否则目标文件mtime会设置为系统时间,导致下次更新
-g # 保留组信息
-o # 保留所有者信息
-l # 将软链接文件本身进行复制(默认)
-L # 将软链接文件指向的文件复制
-u # 如果接收者的文件比发送者的文件较新,将忽略同步
-z # 压缩,节约网络带宽
-D # "--device --specials"选项的组合,即也拷贝设备文件和特殊文件
-a # 存档,相当于-rlptgoD,但不保留ACL(-A)和SELinux属性(-X)
-b # 对目标上已存在的文件做一个备份,备份的文件名后默认使用"~"做后缀
--delete # 源数据删除,目标数据也自动同步删除
--max-size #
--min-size # 限制rsync传输的最小文件大小。这可以用于禁止传输小文件或那些垃圾文件
--exclude # 排除不需要传输的文件
--port # 连接daemon时使用的端口号,默认为873端口
--existing # 只更新目标端已存在的文件,注意:使用相对路径时如果上层目录不存在也不会传输
--ignore-existing # 要求只更新目标端不存在的文件。和"--existing"结合使用有特殊功能
--remove-source-files # 要求删除源端已经成功传输的文件

范例:

1
[root@centos8 ~]#rsync -auv --delete /data/test 10.0.0.7:/data

sftp

交互式文件传输工具,利用 ssh 服务实现安全的文件上传和下载,使用 ls cd mkdir rmdir pwd get put 等指令,详细说明可以使用 help 查看

范例:

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
[lujinkai@ubuntu1804 data]$sftp root@10.0.0.7
root@10.0.0.7's password:
Connected to 10.0.0.7.
sftp> help # 查看帮助
Available commands:
bye Quit sftp
cd path Change remote directory to 'path'
chgrp grp path Change group of file 'path' to 'grp'
chmod mode path Change permissions of file 'path' to 'mode'
chown own path Change owner of file 'path' to 'own'
df [-hi] [path] Display statistics for current directory or
filesystem containing 'path'
exit Quit sftp
get [-afPpRr] remote [local] Download file
reget [-fPpRr] remote [local] Resume download file
reput [-fPpRr] [local] remote Resume upload file
help Display this help text
lcd path Change local directory to 'path'
lls [ls-options [path]] Display local directory listing
lmkdir path Create local directory
ln [-s] oldpath newpath Link remote file (-s for symlink)
lpwd Print local working directory
ls [-1afhlnrSt] [path] Display remote directory listing
lumask umask Set local umask to 'umask'
mkdir path Create remote directory
progress Toggle display of progress meter
put [-afPpRr] local [remote] Upload file
pwd Display remote working directory
quit Quit sftp
rename oldpath newpath Rename remote file
rm path Delete remote file
rmdir path Remove remote directory
symlink oldpath newpath Symlink remote file
version Show SFTP version
!command Execute 'command' in local shell
! Escape to local shell
? Synonym for help
sftp> pwd # 查看远程服务器目录
Remote working directory: /root
sftp> lpwd # 查看本机目录
Local working directory: /home/lujinkai/data
sftp> ls
ELS.txt a.log
sftp> get a.log /tmp/ # get 下载文件
Fetching /root/data/a.log to /tmp/a.log
/root/data/a.log 100% 21 6.5KB/s 00:00
sftp> put /etc/passwd ./ # 上传文件
Uploading /etc/passwd to /root/data/./passwd
/etc/passwd 100% 1567 544.2KB/s 00:00

高级应用

SSH 端口转发能够提供两大功能:

  • 加密 SSH Client 端至 SSH Server 端之间的通讯数据
  • 突破防火墙的限制完成一些之前无法建立的 TCP 连接

SSH 本地端口转发

三台主机:

host1:家用电脑,公网中
host2:业务服务器,内网中
host3:中转服务器,内网中

host1 不能直接连接 host2,但是可以通过 host3 中转,在 host1 上执行下面命令:

1
2
3
4
5
6
ssh -fNL 1234:host2_ip:80 [host3_user@]host3_ip

-N # 不打开远程shell,适用于转发端口
-f # 后台启用,避免占用终端前台
-L [bind_address:]port:host:hostport # 本机端口:目标主机:目标主机端口,默认ssh只监听127.0.0.1的port,指定bind_address,则监听bind_address的port,如果bind_address设置为0.0.0.0,表示监听本机全部地址的port
host3 # 中转服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# host1:CentOS6 10.0.0.6
# host2:CentOS7 10.0.0.7
# host3:CentOS 10.0.0.8

# host1 ssh监听本机的1234端口,将1234端口的请求通过10.0.0.8转发到10.0.0.7的80端口
[root@centos6 ~]# ssh -fNL 1234:10.0.0.7:80 root@10.0.0.8
[root@centos6 ~]# curl 127.0.0.1:1234
hello nginx!

# host2
[root@centos7 ~]#cat /usr/local/nginx/html/index.html
hello nginx!
[root@centos7 ~]#tail -f /usr/local/nginx/logs/access.log
10.0.0.8 - - [12/Sep/2020:14:15:53 +0800] "GET / HTTP/1.1" 200 13 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"

绑定本地端口,指定数据传送的目标主机,们把这种情况称为”本地端口转发”(Local forwarding)。

因为 host1 和 host3 通信使用 SSH 加密,仿佛形成一个数据传输的秘密隧道,因此又被称为”SSH 隧道”。

SSH 远程端口转发

三台主机:

host1:家用电脑,公网中
host2:业务服务器,内网中
host3:中转服务器,内网中

host1 不能直接连接 host2,也不能直接连接 host3,但是 host3 既可以连接 host1,又可以连接 host2,所有使用 host3 做中转。

远程端口转发 和 本地端口转发 逻辑上刚好相反:

  • 本地端口转发是 host1 主动监听本机的 1234 端口(因为主动,所以是 ssh 监听 1234),要求 host3 帮忙转发;
  • 远程端口转发是 host3 通知 host1,让其监听自身的 1234 端口(因为被动,所以是 sshd 监听 1234),host3 主动拿帮 host1 转发。
1
2
3
ssh -fNR 1234:host2_ip:80 [host1_user@]host1_ip

-R [bind_address:]port:host:hostport # 远程主机端口:目标主机:目标主机端口
1
2
3
4
5
6
7
8
9
10
# host1:CentOS6 10.0.0.6
# host2:CentOS7 10.0.0.7
# host3:CentOS 10.0.0.8

# host3
[root@centos8 ~]$ssh -fNR 1234:10.0.0.7:80 root@10.0.0.6

# host1,成功访问到host2,
[root@centos6 ~]# curl 127.0.0.1:1234
hello nginx!

SSH 动态端口转发

1
ssh -D 1080 root@10.0.0.8

SSH 创建 SOCKS 代理服务,去监听本地的 1080 端口。一旦有数据传向那个端口,就自动把它转移到 SSH 连接上面,发往远程主机 10.0.0.8。可以想象,如果 1080 端口原来是一个不加密端口,现在将变成一个加密端口。

目前 SOCKS4 和 SOCKS5 协议支持端口转发:

1
2
3
4
5
# CentOS6 配置 SOCKS代理
[root@centos6 ~]# ssh -fND 1080 root@10.0.0.8
# CentOS8替CentOS6访问http://10.0.0.7,将返回数据加密,发送回CentOS6
[root@centos6 ~]# curl --socks5 127.0.0.1:1080 http://10.0.0.7
hello nginx!

X 协议转发

同余定理

两个不同的整数 a、b,被一个整数 m 相除时,得到相同的余数,那么我就可以称 a、b 对 m 同余。

换言之,(a-b)/m 得到一个整数,因为 a、b 相减时把余数抵消掉了。

a、b 对 m 同余,记做 a≡b(mod m)

例如:4≡7(mod 3)

欧拉函数

  (其中 p1, p2……pn 为 x 的所有质因数,x 是不为 0 的整数)

欧拉函数表示小于或等于 n 的正整数中与 n 互质的数的数目。定义:φ(1) = 1

举例:φ(8)=4,因为 1,3,5,7 均和 8 互质

设 m>1,且 a、m 互质,那么使得 ar≡1(mod m)成立的最小的正整数 r 称为 a 对模 m 的阶,记为 δm(a)

原根

原根是一种数学符号,设 m 是正整数,a 是整数,若 a 模 m 的阶等于 φ(m),则称 a 为模 m 的一个原根

离散对数

离散 就是 不连续的意思

在整数中,离散对数(英语:Discrete logarithm)是一种基于同余运算和原根的一种对数运算

logba:对于给定的 a 和 b 实数,有一个实数 x,使得 bx=a

logba:对于给定的 a 和 b 整数,有一个整数 k,使得 bk=a

DSA 算法

该算法的安全性依赖于计算模数的离散对数的难度。

公开参数:

参数 说明
p 512~1024 位的素数(可以在一组用户中共享)
q 160 位长,并与 p-1 互素的因子(可以在一组用户中共享)
g g=h(p-1)/q mod p,其中 h 小于 p-1 并且 h(p-1)/q mod p > 1
y y=gx mod p (一个 p 位的数)

私密参数:

参数 说明
x x<q

签名过程:

  1. 对于报文 m, 挑选秘密随机数 k: k ∈ (0, q)
  2. r = ( g^k mod p ) mod q
  3. s = ( k^(-1) (H(m) + xr)) mod q,H()应该是 hash 算法,dsa 只支持 sha
  4. 签名结果即为(m, r, s)

校验过程:

  1. w = s^(-1)mod q
  2. a = ( H( m ) * w ) mod q
  3. b = ( r * w ) mod q
  4. v = (( g^a * y^b ) mod p ) mod q
  5. 若 v = r,则认为签名有效。

openssl 实现

生成 1024 位 DSA 参数集 p、q、g,并输出到参数文件 dsa.param:

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
[root@centos8 data]$openssl dsaparam -out dsa.param 1024
Generating DSA parameters, 1024 bit long prime
This could take some time
....................++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*
....................+......................+.+...........+....+............+..........+...+...+.+........................+..........+.......+.+.+.............+.......+....+......+.....+....+.+....+..................+.+.+....+...+.......+..........................................+........+..................+.+....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*
[root@centos8 data]$cat dsa.param
-----BEGIN DSA PARAMETERS-----
MIIBHgKBgQCXWtR0zjsNLT010pR/Oelvbk1N+vSv2k3bUahkWMuZ6Z5BYQP7+QvK
6D1/+RWD/zHIicOLGYNkOFnX3PBPlx218KqFX1wNkmYz0vqPu1+RYeIVqaVFDEB2
dibqiFhKDhKipR7G3tCwqC94AkTJtduQNnBiM/YRz86nE6nqYVTfrwIVAK+NYZGg
cv5MtoJVdu/rr2wEypp1AoGAZgDa/zIKudSI99nlIUJOODkS064l+rT0JdEIbdcv
TEKeCmWhRMzWdU8y56AWsiTCmChuXHlMsgt6VKcgBMO7FgGTPeIiDegGZvZmXG8B
bL6/gejSrMQmEG1CrJZ6vCqkDRkaKvVSX6ysOMbPk9GcjqAw8KwdkUi4Bxv2wyNL
JK8=
-----END DSA PARAMETERS-----
[root@centos8 data]$openssl dsaparam -in dsa.param --noout -C
static DSA *get_dsa1024(void)
{
static unsigned char dsap_1024[] = {
0x97, 0x5A, 0xD4, 0x74, 0xCE, 0x3B, 0x0D, 0x2D, 0x3D, 0x35,
0xD2, 0x94, 0x7F, 0x39, 0xE9, 0x6F, 0x6E, 0x4D, 0x4D, 0xFA,
0xF4, 0xAF, 0xDA, 0x4D, 0xDB, 0x51, 0xA8, 0x64, 0x58, 0xCB,
0x99, 0xE9, 0x9E, 0x41, 0x61, 0x03, 0xFB, 0xF9, 0x0B, 0xCA,
0xE8, 0x3D, 0x7F, 0xF9, 0x15, 0x83, 0xFF, 0x31, 0xC8, 0x89,
0xC3, 0x8B, 0x19, 0x83, 0x64, 0x38, 0x59, 0xD7, 0xDC, 0xF0,
0x4F, 0x97, 0x1D, 0xB5, 0xF0, 0xAA, 0x85, 0x5F, 0x5C, 0x0D,
0x92, 0x66, 0x33, 0xD2, 0xFA, 0x8F, 0xBB, 0x5F, 0x91, 0x61,
0xE2, 0x15, 0xA9, 0xA5, 0x45, 0x0C, 0x40, 0x76, 0x76, 0x26,
0xEA, 0x88, 0x58, 0x4A, 0x0E, 0x12, 0xA2, 0xA5, 0x1E, 0xC6,
0xDE, 0xD0, 0xB0, 0xA8, 0x2F, 0x78, 0x02, 0x44, 0xC9, 0xB5,
0xDB, 0x90, 0x36, 0x70, 0x62, 0x33, 0xF6, 0x11, 0xCF, 0xCE,
0xA7, 0x13, 0xA9, 0xEA, 0x61, 0x54, 0xDF, 0xAF
};
static unsigned char dsaq_1024[] = {
0xAF, 0x8D, 0x61, 0x91, 0xA0, 0x72, 0xFE, 0x4C, 0xB6, 0x82,
0x55, 0x76, 0xEF, 0xEB, 0xAF, 0x6C, 0x04, 0xCA, 0x9A, 0x75
};
static unsigned char dsag_1024[] = {
0x66, 0x00, 0xDA, 0xFF, 0x32, 0x0A, 0xB9, 0xD4, 0x88, 0xF7,
0xD9, 0xE5, 0x21, 0x42, 0x4E, 0x38, 0x39, 0x12, 0xD3, 0xAE,
0x25, 0xFA, 0xB4, 0xF4, 0x25, 0xD1, 0x08, 0x6D, 0xD7, 0x2F,
0x4C, 0x42, 0x9E, 0x0A, 0x65, 0xA1, 0x44, 0xCC, 0xD6, 0x75,
0x4F, 0x32, 0xE7, 0xA0, 0x16, 0xB2, 0x24, 0xC2, 0x98, 0x28,
0x6E, 0x5C, 0x79, 0x4C, 0xB2, 0x0B, 0x7A, 0x54, 0xA7, 0x20,
0x04, 0xC3, 0xBB, 0x16, 0x01, 0x93, 0x3D, 0xE2, 0x22, 0x0D,
0xE8, 0x06, 0x66, 0xF6, 0x66, 0x5C, 0x6F, 0x01, 0x6C, 0xBE,
0xBF, 0x81, 0xE8, 0xD2, 0xAC, 0xC4, 0x26, 0x10, 0x6D, 0x42,
0xAC, 0x96, 0x7A, 0xBC, 0x2A, 0xA4, 0x0D, 0x19, 0x1A, 0x2A,
0xF5, 0x52, 0x5F, 0xAC, 0xAC, 0x38, 0xC6, 0xCF, 0x93, 0xD1,
0x9C, 0x8E, 0xA0, 0x30, 0xF0, 0xAC, 0x1D, 0x91, 0x48, 0xB8,
0x07, 0x1B, 0xF6, 0xC3, 0x23, 0x4B, 0x24, 0xAF
};
DSA *dsa = DSA_new();
BIGNUM *p, *q, *g;

if (dsa == NULL)
return NULL;
if (!DSA_set0_pqg(dsa, p = BN_bin2bn(dsap_1024, sizeof(dsap_1024), NULL),
q = BN_bin2bn(dsaq_1024, sizeof(dsaq_1024), NULL),
g = BN_bin2bn(dsag_1024, sizeof(dsag_1024), NULL))) {
DSA_free(dsa);
BN_free(p);
BN_free(q);
BN_free(g);
return NULL;
}
return dsa;
}

使用参数文件 dsa.param 生成 DSA 私钥文件 dsa.key,并将私钥 3DES 加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@centos8 data]$openssl gendsa -out dsa.key -des3 dsa.param
Generating DSA key, 1024 bits
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
[root@centos8 data]$cat dsa.key
-----BEGIN DSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,5E11A0F348D0EAB7

xlH02528Xuza/YkK4jMWiFoFTMkfvLEPFPdhlh1Zpdb4MGi6pWeHsQf0bXkQ7s1H
JNrcsiPNvYK6eiyYw0QTvGivmSs1IvkeGJPJQnHPkclzn7A6QYG4PXFGQGWxSNr9
6kbSMrlBqSZyo05TN+Hkv76HFIb2Haszo6b5n+h0LMqancDh8hzO4qCLtgL2GSp1
WcsjjVlox7askeZPMHA0FKKx2Sku0yGQtxqBIR2Z/Rf2ePW2aJd3gLRYMTWZO/B2
lIz+ttBZJ84LJsfAZ0oRyRUTpse3pHlp4ohdwFJf4mVkZMbg4BIqGhZjamOuLJeP
RhnfM4MXeI1JZGViUanpipAHmuSxDH12+/UsFdGMeqY2PXIenpLCVna443BV3avh
sP3srdYcKxhOgRa8XAJsrJ/K/T6xR28uE3zAVQ52USo5ifgx4gtu3rEZ0/XqUJ1q
T5I8WwInPzvCMEhyIFL/n/gqhMMaRkAJXaMrZvhnFnCYQg7fFvgKbhJB7CEu1+j3
lfFod1Sb3/IgMlMmwMG87N3DpDn4Q7P+ypY4KDIScc7OJQy+Oy1y7Mm7aWB4xP/R
i/AAXrsOWcdeY2zt/LSWzg==
-----END DSA PRIVATE KEY-----

使用私钥生成公钥文件 dsapublickey.pem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@centos8 data]$openssl dsa -in dsa.key -pubout -out dsa.key.pub
read DSA key
Enter pass phrase for dsa.key:
writing DSA key
[root@centos8 data]$cat dsa.key.pub
-----BEGIN PUBLIC KEY-----
MIIBtjCCASsGByqGSM44BAEwggEeAoGBAJda1HTOOw0tPTXSlH856W9uTU369K/a
TdtRqGRYy5npnkFhA/v5C8roPX/5FYP/MciJw4sZg2Q4Wdfc8E+XHbXwqoVfXA2S
ZjPS+o+7X5Fh4hWppUUMQHZ2JuqIWEoOEqKlHsbe0LCoL3gCRMm125A2cGIz9hHP
zqcTqephVN+vAhUAr41hkaBy/ky2glV27+uvbATKmnUCgYBmANr/Mgq51Ij32eUh
Qk44ORLTriX6tPQl0Qht1y9MQp4KZaFEzNZ1TzLnoBayJMKYKG5ceUyyC3pUpyAE
w7sWAZM94iIN6AZm9mZcbwFsvr+B6NKsxCYQbUKslnq8KqQNGRoq9VJfrKw4xs+T
0ZyOoDDwrB2RSLgHG/bDI0skrwOBhAACgYBJ3oCNHNh7xflq3VaHL30MMl+4Qqrm
Jv0WfHTiOqhYWkezA/ueMcJe/X/pW05ARESVbkrrQx/wxPuR8CO1pFTCPqyI02Rc
AXCz7ejp4OmrdF6RNHds+uT2YZ5sRI7zWX1YXdSIG0TGJ9+jOgeRVpPdNGgVdUxS
SgeCn5UKw6nJew==
-----END PUBLIC KEY-----

从 dsa.key 中读取私钥,解密并输入新口令进行加密,然后写回文件 dsa.key

1
openssl dsa -in  dsa.key -out  dsa.key -des3 -passin

RSA 既可以用于密钥交换,又可以用于数字签名;ECC 这边就分得比较清楚了:ECDHE 用于密钥交换,ECDSA 用于数字签名。

ECDHE 密钥交换使用的是 DH(Diffie-Hellman)算法。

Deffie-Hellman(简称 DH) 密钥交换是最早的密钥交换算法之一,它使得通信的双方能在非安全的信道中安全的交换密钥,用于加密后续的通信消息。 Whitfield Diffie 和 Martin Hellman 于 1976 提出该算法,之后被应用于安全领域,比如 Https 协议的 TSL(Transport Layer Security)以 DH 算法作为密钥交换算法。

session key 是主密钥,HTTPS 建立连接时使用 SSL/TLS 来交换 session key,后续的传输数据使用的是对称加密,对称加密的密钥就是通过 session key 生成的。

前面我们说过,session key 需要使用非对称加密的方式来传送,非对称加密算法常用的是 RSA,RSA 会把一方生成的 session key 用公钥加密后再发送给对方,可是,万一私钥泄露了,那 session key 也就泄露了。所以 SSL/TLS 使用另一种非对称加密算法来传送 session key,这种算法就是HD,RSA 是“传送”,而 HD 描述为“交换”更准确一些。

HD 的实现机制:

假设 A、B 通信

  1. A、B 相互通信一次,确定两个参数:整数 g 和 大素数 p,这个过程叫做“协商”
  2. A 生成隐私数据 a,a < p,计算得出 ga mod p,发送给 B
  3. B 生成隐私数据 b,b < p,计算得出 gb mod p,发送给 A
  4. A 计算 [(gb mod p)^a] %p,得到的结果是预备主密钥 session key
  5. B 计算 [(ga mod p)^b] %p,得到的结果是预备主密钥 session key

[(gb mod p)^a] %p = [(ga mod p)^b] %p = gab mod p,证明过程略,总之 A、B 生成的 session key 是相同的。

通信过程不加密,就是说 g、p、ga mod p、gb mod p 都是公开的,只有公开的这个四个数据,没有 a、b 就算不出来 gab mod p,这样就做到了 session key 由通信双方各自生成,不经过网络传递。

可以抽象的认为:A 的私钥是 a,公钥是 ga mod p;B 的私钥是 b,公钥是 gb mod p,gab mod p 是预备主密钥 session key,最终用于加密的密钥是会话密钥,通过 session key 和 H 派生出来,具体参看下面的 SSHv2 协议中的 DH

openssl 实现

使用生成因子(generator,即整数 g,2 或者 5,默认是 2)2 和随机的 1024-bit 的素数(大素数 p)产生 D0ffie-Hellman 参数,输出保存到参数文件 dh.param:

1
2
3
4
5
6
7
8
9
10
[root@centos8 data]$openssl dhparam -out dh.param -2 1024
Generating DH parameters, 1024 bit long safe prime, generator 2
This is going to take a long time
...................................+......................................................................................................................................................+......................................................................................................................+...................................................................................+......+.................+......................................+...............+.............................................................................................+.......+........................................+....................................+........................................................................................++*++*++*++*++*
[root@centos8 data]$cat dh.param
-----BEGIN DH PARAMETERS-----
MIGHAoGBAIHnVCwnbBAsIz43ylC5jSXdvbEysPNhe51MiYhjOYiIphx3eKgKOf9h
w47mAlaL9pFTsgtCfwq0gPOOABufWZY3cO0Xqwazfv84LFOH5BcH8M3trGyAc4C5
0kDJ3A2L5pmlVDYXrBhikCtfEMgixqbbMkCpqmKvk1YHvRfSarejAgEC
-----END DH PARAMETERS-----

从 dh.param 中读取 Diffie-Hell 参数,以 C 代码的形式输出到 stdout:

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
[root@centos8 data]$openssl dhparam -in dh.param -noout -C
static DH *get_dh1024(void)
{
static unsigned char dhp_1024[] = {
0x81, 0xE7, 0x54, 0x2C, 0x27, 0x6C, 0x10, 0x2C, 0x23, 0x3E,
0x37, 0xCA, 0x50, 0xB9, 0x8D, 0x25, 0xDD, 0xBD, 0xB1, 0x32,
0xB0, 0xF3, 0x61, 0x7B, 0x9D, 0x4C, 0x89, 0x88, 0x63, 0x39,
0x88, 0x88, 0xA6, 0x1C, 0x77, 0x78, 0xA8, 0x0A, 0x39, 0xFF,
0x61, 0xC3, 0x8E, 0xE6, 0x02, 0x56, 0x8B, 0xF6, 0x91, 0x53,
0xB2, 0x0B, 0x42, 0x7F, 0x0A, 0xB4, 0x80, 0xF3, 0x8E, 0x00,
0x1B, 0x9F, 0x59, 0x96, 0x37, 0x70, 0xED, 0x17, 0xAB, 0x06,
0xB3, 0x7E, 0xFF, 0x38, 0x2C, 0x53, 0x87, 0xE4, 0x17, 0x07,
0xF0, 0xCD, 0xED, 0xAC, 0x6C, 0x80, 0x73, 0x80, 0xB9, 0xD2,
0x40, 0xC9, 0xDC, 0x0D, 0x8B, 0xE6, 0x99, 0xA5, 0x54, 0x36,
0x17, 0xAC, 0x18, 0x62, 0x90, 0x2B, 0x5F, 0x10, 0xC8, 0x22,
0xC6, 0xA6, 0xDB, 0x32, 0x40, 0xA9, 0xAA, 0x62, 0xAF, 0x93,
0x56, 0x07, 0xBD, 0x17, 0xD2, 0x6A, 0xB7, 0xA3
};
static unsigned char dhg_1024[] = {
0x02
};
DH *dh = DH_new();
BIGNUM *p, *g;

if (dh == NULL)
return NULL;
p = BN_bin2bn(dhp_1024, sizeof(dhp_1024), NULL);
g = BN_bin2bn(dhg_1024, sizeof(dhg_1024), NULL);
if (p == NULL || g == NULL
|| !DH_set0_pqg(dh, p, NULL, g)) {
DH_free(dh);
BN_free(p);
BN_free(g);
return NULL;
}
return dh;
}

SSHv2 协议中的 DH

  1. Client 发送 e 值给 Server:

SSHv2 中 DH 的 p 和 g 固定,没有协商阶段

e = ga mod p

  1. Server 发送自身公钥、f、s 给 Client

f = gb mod p

H = hash(V_C||V_S||I_C||I_S||K_S||e||f||K)

类型 说明
string V_C 客户端的初始报文(版本信息:SSH-2.0-xxx,不含结尾的 CR 和 LF)
string V_S 服务器的初始报文
string I_C 客户端 SSH_MSG_KEX_INIT 的有效载荷(不含开头的数据长度值)
string I_S 服务器的同上
string K_S 主机秘钥(dh gex reply(33)过程服务器发送 host key (RSA 公钥))
mpint e 客户端 DH 公钥
mpint f 服务器 DH 公钥
mpint K 共同 DH 计算结果

s 是用 Server 私钥对 H 加密后的结果

  1. Client 根据/.ssh/known_hosts 文件,匹配 Server 公钥,如果/.ssh/known_hosts 文件中没有 Server 公钥,说明是第一次建立连接,需要用户手动允许保存 Server 公钥
  2. Client 计算预备主密钥 session key、同样的方式计算 H。使用 Server 公钥解密 s,解密得出的 H 和自己算出的 H 如果一样,则向 Server 发送 NEW KEYS,标志着密钥交换成功
  3. 自此,Client 和 Server 交换了 预备主密钥 K 和 H 值,通过前面协商的算法中的 密钥派生算法 导出各个业务的密钥:加密、MAC、IV,同时把 H 作为会话 ID(session ID)

TLS1.2 协议中的 DH

  1. Client 和 Server 相互协商版本号 和 各种算法,并互相发送随机数 和 session ID

  1. Server 发送数字证书给 Client
  2. Server 发送 Pubkey 值给 Client,数字签名用来确保 Pubkey 的合法完整

TLS1.2 中 DH 的 p 和 g 固定,没有协商阶段

Pubkey = gb mod p

  1. Client 校验证书的合法性;Client 计算出预备主密钥 session key;Client 发送 Pubkey 值给 Server

Pubkey = ga mod p

  1. Server 计算出预备主密钥 session key;Server 发送 New Session Ticket、Change Cipher Spec、Encrypted Handshake Message 给 Client:

    1. Encrypted Handshake Message:使用密钥加密的信息,目的一个是告诉服务端整个握手过程收到了什么数据,发送了什么数据,保证中间没人篡改报文,二是确认密钥的正确性,如果这个报文加解密校验成功,那么对称密钥就是正确的

    2. Change Cipher Spec:编码改变通知,告知客户端,服务端已经切换到选定的加密套件(Cipher Suite),表示随后的信息都将用双方商定的加密方法和密钥发送。

    3. New Session Ticket:这里有必要比较一下 session ticket 和 session ID 这两个角色:

      如果出于某种原因,对话中断,就需要建立 SSL 连接,Client 在 client hello 阶段携带之前的 session ID,服务端确认 session ID 存在,双方就不再进行握手阶段剩余的步骤,而直接用已有的对话密钥进行加密通信,提高了重连的效率。

      可是,session ID 往往只保留在一台服务器上(session ID 的存储及复用共享需要使用 redis 或 memcache 来实现),假如轮询到集群中其它服务器,无法识别该 session id,就无法恢复对话,session ticket 就是为了解决这个问题而诞生的,目前只有 Firefox 和 Chrome 浏览器支持。客户端不再发送 session ID,而是发送一个服务器在上一次对话中发送过来的 session ticket。这个 session ticket 是加密的,只有服务器才能解密,其中包括本次对话的主要信息,比如对话密钥和加密方法。当服务器收到 session ticket 以后,解密后就不必重新生成对话密钥了。

  2. 自此 Client 和 Server 都有了三个值:Client 随机数、Server 随机数、session key。这三个值可以计算出会话密钥,后面的通信使用会话密钥加密。

安全机制

加密算法

  • 对称加密
  • 非对称(公钥)加密
  • 单向加密
  • 认证协议

对称加密

将原始数据分割成固定大小的块,逐个进行加密;加密和解密使用同一个密钥。

优点:效率高。

缺点:因为加密和解密使用同一个密钥,所以需要通信的一方生成密钥并发送给对方,才能进行通信。万一密钥被截获,那别人就即可以给你发送加密信息,也可以破解你的加密信息。

常见的对称加算法:DES、3DES、AES、Blowfish,Twofish、IDEA,RC6,CAST5。

非对称加密

密钥成对出现:公钥和私钥成,公钥加密只有对应的私钥才能解密,反之依然。

  1. 通信双方 A 和 B,分别生成自己的公钥和私钥。
  2. A 将公钥 A 发送给 B,B 将公钥 B 发送给 A。
  3. A 将发送给 B 的信息用公钥 B 加密;B 将发送给 A 的信息用公钥 A 加密。
  4. B 收到信息后用私钥 B 解密;A 接收到信息用私钥 A 解密。

默认:数据使用公钥加密,私钥解密,签名使用私钥加密,公钥解密。

优点:私钥只存储在本机不发送给任何人,所以即使公钥被截获,没有对应的私钥也无法破解信息。可以说非对称加密是绝对安全的。

缺点:密钥长,算法复杂;加密解密效率低。

常见非对称加密算法:RSA、ECC、DSA
RSA:国际标准算法,算法简单,应用早,兼容性好,一般采用 2048 位的加密长度。
ECC:椭圆加密算法,算法复杂,除了兼容性,其他各方面都优于 RSA,未来肯定会取代 RSA。
DSA:只能用于签名/认证,不能用于加密/解密,性能比 RSA 好,RSA 既能签名/认证,也能加密/解密。

综上:使用对称加密加密信息,效率高,只有在发送密钥的时候使用非对称加密,保证安全。

加密过的信息即使被截获,也看不懂里面的内容,因为没有对应的私钥,是无法解密的,但是因为公钥是公开的,所以非法用户可以冒充正常用户发送信息,这个问题可以使用数字签名解决。

hash

哈希算法:也称为散列算法,将任意数据缩小成固定大小的“指纹”,称为 digest,即摘要。

特性:

  • 任意长度输入,固定长度输出
  • 若修改数据,指纹也会改变,且有雪崩效应,数据的一点微小改变,生成的指纹值变化非常大。
  • 无法从指纹中重新生成数据,即不要逆,具有单向性

功能:数据完整性

常见算法:md5: 128bits、sha1: 160bits、sha224、sha256、sha384、sha512

数字签名

B 收到信息后,使用私钥 B 解密,解密后的信息中有 token 或 id 或 cookie 证明这条信息来自 A,但这是不可信的,因为这条信息完全可能是 C 发送的。而数字签名可以解决这问题。

生成数字签名的算法有两种:RSA 和DSA

数字签名有两个作用:1.证明消息来源;2.校验发送数据的完整性。

A 把发送的数据的哈希值使用私钥 A 加密,生成数字签名。数字签名和数据拼接在一起发送给 B,有两种方式拼接方式:1. 数字签名和加密后的数据拼接;2. 数字签名和明文拼接,再总体加密。

假设使用第一种拼接方式,那 B 收到信息后把数字签名取下,使用公钥 A 进行校验,下面以 DSA 算法为例简要介绍生成数字签名和校验数字签名的过程:

公钥 A 中包含 pA、gA、qA、yA 四个值
私钥 A 是 xA 一个值
发送的数据是 m

生成数字签名:
针对 m 生成一个随机数 k(key),把 pA、gA、qA、yA、k 带入生成算法中出 r、s 两个值,所以数字签名由 r、s、k 三部分组成

校验数字签名:
把 pA、gA、qA、yA、r、s、m 带入校验算法中得出 v,如果 v=r 则说明校验通过,否则校验不通过。
因为 pA、gA、qA、yA、r、s、m 中有公钥 A、数据 m 以及基于 m 和 k 生成的 r、s,所以如果通过校验,就说明信息一定来自 A(除非 A 把自己的私钥泄露了)、数据一定是完整的。

注意:每次个签名都需要一个新值 k,并且该值必须是随机选择的。如果不同的 m 使用了重复的 k,那么就会被破解出 k 值,然后推算出私钥 x

综上:使用对称加密加密信息,效率高,只有在发送对称加密密钥的时候使用非对称加密(RSA)加密密钥,为密钥生成数字签名,确保密钥安全发送。

签名证书 CA

数字签名通过校验的公钥确定信息来源。可是如果公钥被替换了呢,例如中间人攻击:

数字证书一般由数字证书认证机构(certificate authority,简称 CA)颁发,数字证书使用 CA 的私钥加密,其中包含了申请者的公钥和网址等信息。

A 给 B 发送证书,B 用 CA 公钥解密后取出公钥,以及网址等其他信息,进行校验,如果证书中的公钥 A 和本机存储的公钥 A 一致,然后网址等其他信息都对,说明本机存储的公钥 A 确实是真正的公钥 A,以后只要用公钥 A 校验通过的数字签名,就能证明信息来源是真的 A。

有同学问了,如果 B 储存的公钥 A 被替换成了公钥 C,然后 C 把 A 的证书截获下来,给 B 发送自己的证书 C,以此来冒充 A 不可以吗?答:显然不可以,因为证书中不只有公钥,即使证书中的公钥和 B 本机的公钥一致,其他的信息也校验不过关。

证书

lujinkai.com使用 openssl 生成了一对证书和私钥,证书中包含了以下信息:

  • 公钥
  • host
  • 地区
  • 过期时间
  • 使用私钥(注意是此证书签发机构的私钥,而非此证书中公钥对应的私钥)加密以上所有信息的哈希,生成签名

https

  1. 用户浏览器访问https://lujinkai.comlujinkai.com将以下数据返回给浏览器:

    1. 证书
    2. server 随机数
    3. server DH 参数:这里应该是不用私钥加密的,毕竟公钥是公开的,加密没有意义。
    4. server DH 参数的签名:使用私钥加密 server DH 参数的哈希值。签名保证了 server DH 合法完整。
  2. 浏览器确认证书的有效性,如果证书有问题,浏览器会告警提示用户;

  3. 如果证书没有问题,浏览器从证书中解析出公钥,使用公钥对 server DH 参数进行验签,确认 server DH 参数没问题后,将以下数据发送给服务器:

    1. client DH 参数:用公钥解密 server DH 参数,使用 server DH 参数生成 client DH 参数
    2. client 随机数

    这里不太清楚 client DH 参数和 client 随机数是否需要公钥加密。我估计应该是加密的。

  4. 双方现在都有以下数据:

    • client 随机数
    • server 随机数
    • client DH 参数
    • server DH 参数

    双方根据 client DH 参数和 server DH 参数生成 Session key,然后 Session key、client 随机数、server 随机数生成“对话密钥”,这个“对话密钥”其实就是对称加密的口令。

  5. 后续的通信均使用“对话密钥”进行加解密。

gpg

gpg 实现加密通信,对称加密和非对称加密。下面的示范是 centos7 下的 gpg

1
gpg [--homedir dir] [--options file] [options] command [args]

–homedir dir:指定家目录

-option file:因为 gpg 的 option 众多,所以支持从文件中读取 options

options:

gpg.conf:在 homefir 下新建 gpg.conf,将长 option 写入其中,以后每次执行 gpg 命令都会自动加上 gpg.conf 中的 option,但是不能将 command 写入其中,没有意义。

范例:默认加密成二进制,修改为默认加密成 ASCII

1
2
3
4
5
6
7
8
9
10
11
12
lujinkai@Z510:~/data/test$ gpg -c a.log
lujinkai@Z510:~/data/test$ cat a.log.gpg
 H���ݒ��O�v=��F���#�`u&����in��1��P_14޷1�N��kh�D�>�4���
��F��v�i����I�]�$����
lujinkai@Z510:~/data/test$ echo 'armor' >> ~/.gnupg/gpg.conf
lujinkai@Z510:~/data/test$ cat a.log.asc
-----BEGIN PGP MESSAGE-----

jA0ECQMCJBg8BY1yU6H/0k8BkJltvEvtfDi1XjjUfcRg6UC+TBU+YPighRYT9KXD
7pzT6/vdM46wigfOLHB33Ip8rlvl2tALTAt/YMcUKyEEKbnnf/b3j9K/RhGd5t3c
=VKIs
-----END PGP MESSAGE-----
  • -a|–armor:Create ASCII armored output. The default is to create the binary OpenPGP format.
  • –cipher-algo name:指定对称加密的算法,算法列表使用--version查看
  • -r|–recipient name name:指定加密使用的公钥,为了防止重名,不要使用 uid,推荐使用 uid 加密后的字符串代替 uid。如果不指定则使用默认公钥,如果没有设置默认公钥,则交互式要求指定公钥
  • -o|–output file:输出到文件

command:

  • –version:查看 gpg 版本、支持的加密算法等信息
  • –dump-options:列出所有的 command 和 options
  • -e|–encrypt [file]:非对称加密,如果指定 file 则表示加密文件,如果不指定,我也不知道。。。
  • -c|–symmetric [file]:对称加密
  • -d|–decrypt:解密,会自动判读对称加密还是非对称加密,对称加密会自动识加密算法,非对称加密会自动判断使用哪个私钥解密(一台主机可以生成多个密钥对)
  • -k|–list-keys|–list-public-keys:列出已有公钥
  • -K|–list-secret-keys:列出已有私钥
  • –export [name]:导出公钥,不指定公钥则导出所有公钥到一个文件
  • –export-secret-keys|–export-secret-subkeys:导出私钥,注意只有 root 用户才能导出私钥,普通用户使用 sudo 也不行
  • –impor|–fast-import [密钥文件]:除了生成自己的密钥,还需要将他人的公钥或者你的其他密钥输入系统
  • –delete-keys name:删除指定公钥
  • –delete-secret-keys name:删除指定私钥
  • –delete-secret-and-public-key name:删除指定的密钥对,即公钥和私钥都删除

对称加密:指定对称加密的算法,然后输入密码即可生成加密文件,解密的时候指定同样的算法,然后输入密码即可生成解密文件。可以认为对称加密的密钥就是 算法 + 密码

1
2
3
4
5
# 使用AES 加密 a.log
gpg --cipher-algo AES -c a.log

# 解密
gpg -d a.log

非对称加密:公钥加密,私钥解密,加密需要指定公钥,解密不需要指定私钥,会自动判断

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
# 导出公钥lujinkai到文件lujinkai.pubkey
lujinkai@Z510:~/data/test$ gpg -o lujinkai.pubkey --export lujinkai
# 导出私钥lujinkai到文件lujinkai.subkey
lujinkai@Z510:~/data/test$ gpg -o lujinkai.subkey --export-secret-keys lujinkai

# 将公钥发送给centos7
lujinkai@Z510:~/data/test$ scp lujinkai.pubkey root@10.0.0.7:~/data
# centos7将公钥导入自己的系统,然后用它加密文件,再将文件发给Z510
[root@centos7 data]#gpg --import ./lujinkai.pubkey
[root@centos7 data]#seq 10 > a.log
lujinkai@Z510:~/data/test$ scp root@10.0.0.7:/root/data/a.log.asc ./
a.log.asc 100% 409 804.2KB/s 00:00

# Z510解密成功
lujinkai@Z510:~/data/test$ gpg -d a.log.asc
gpg: encrypted with 1024-bit RSA key, ID 1BB25A5FFEAB69DD, created 2020-09-06
"lujinkai <ljkk@qq.com>"
1
2
3
4
5
6
7
8
9
10

SSL / TLS

PKI

PKI:Public Key Infrastructure 公共密钥加密体系,构成 PKI 的三个要素:

  1. 证书:一对密钥,通常把公钥叫做证书
  2. 认证机关:CA
  3. 证书库:

证书类型:

  • 证书授权机构的证书:根证书和中间证书,跟证书颁发中间证书,机构给自己颁发根证书。
  • 服务器证书:ssl 证书,就是一对密钥,私钥保存,公钥公开,通常把公钥叫做证书。
  • 用户证书:以支付宝的数字证书为例,是支付宝签发给用户的一对密钥,使用的是 RSA 算法,2048 位,私钥安装到 windows 系统,所以只能安装到 ie 浏览器,不可到处,所以只能在这一台电脑上使用。

X.509:定义证书的结构以及认证协议标准

  • 版本号
  • 序列号
  • 签名算法
  • 颁发者
  • 有效期限
  • 主体名称

获取证书两种方法:

  1. 自签名的证书: 自已签发自己的公钥

  2. 使用证书授权机构:

    1. 生成证书请求(csr)

    2. 将证书请求 csr 发送给 CA

    3. CA 签名颁发证书

SSL/TLS 组成:

SSL/TLS 位于传输层与应用层之间,主要是传输层,对网络传输进行加密。

  • Handshake 协议:包括协商安全参数和密码套件、服务器身份认证(客户端身份认证可选)、密钥交换

  • ChangeCipherSpec 协议:一条消息表明握手协议已经完成

  • Alert 协议:对握手协议中一些异常的错误提醒,分为 fatal 和 warning 两个级别,fatal 类型错误会直接中断 SSL 链接,而 warning 级别的错误 SSL 链接仍可继续,只是会给出错误警告

  • Record 协议:包括对消息的分段、压缩、消息认证和完整性保护、加密等

TLS 实现过程

1
[root@centos7 data]#curl https://www.baidu.com

HTTPS

Base64 编码

base64 是编码,不是加密。

编号 0-63,一共 64 个,26=64,将数据的二进制以每 6 位一组划分,高位添俩 0,凑 8 位就是一个字节,将字节转成十进制,然后对照上表,生成 base64 编码。

OpenSSL

OpenSSL 是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连线者的身份。这个包广泛被应用在互联网的网页服务器上。

其主要库是以 C 语言所写成,实现了基本的加密功能,实现了 SSL 与 TLS 协议。

包括三个组件:

  • libcrypto:用于实现加密和解密的库
  • libssl:用于实现 ssl 通信协议的安全库
  • openssl:多用途命令行工具

openssl

openssl 是 OpenSSL 的命令行工具。

ssh 服务

知识补充:

  • MAC: Message Authentication Code,单向加密的一种延伸应用,用于实现网络通信中保证所传输数据的完整性机制
  • HMAC:hash-based MAC,使用哈希算法

SSH: secure shell protocol, 22/tcp, 安全的远程登录,实现加密通信,代替传统的 telnet 协议。

SSH 协议位于应用层与传输层之间,默认监听 22 端口号,安全的远程登录。

软件实现:1. OpenSSH,CentOS 默认安装;2. dropbear,更加轻量级。

SSH 协议 和 SSL/TLS 协议

SSH:

SSH 协议比较简单,简单的代价就是 SSH 无法判断 Server 公钥的真伪,需要人工判断,手动保存。

SSH 是专为 shell 设计的一种通信协议,只有 SSH 客户端和 SSH 服务端之间通信才使用这个协议。

SSL/TLS:

SSL/LTS 依赖 KPI 体系,利用数字证书确保 Server 公钥的合法性。

而 SSL/TLS 是一种更加通用的安全协议,它的目标是建立一种对上层应用协议透明的,可以依赖的安全协议。

HTTP、FTP、电子邮件协议以及其他所有应用层协议,都可结合 SSL/TLS 实现加密通信。

OpenSSH

CentOS 默认安装 OpenSSH,比较常用

Dropbear

Dropbear 是一个相对较小的 SSH 服务器和客户端。它运行在一个基于 POSIX 的各种平台。 Dropbear 是开源软件,期望在存储器与运算能力有限的情况下取代 OpenSSH,尤其是嵌入式系统

ssh 其他相关工具

sshfs

EPEL 源提供,目前 CentOS8 还没有提供,可以利用 ssh 协议挂载远程目录

1
2
3
4
5
[root@centos7 ~]#yum install fuse-sshfs
[root@centos7 ~]#sshfs 10.0.0.8:/data /mnt
[root@centos7 ~]#df /mnt
Filesystem 1K-blocks Used Available Use% Mounted on
10.0.0.8:/data 52403200 398576 52004624 1% /mnt

sshpass

EPEL 源提供,ssh 登陆不能在命令行中指定密码。sshpass 的出现,解决了这一问题。sshpass 用于非交互 SSH 的密码验证,一般用在 sh 脚本中,无须再次输入密码(本机 known_hosts 文件中有的主机才能生效)。使用 -p 参数指定明文密码,然后直接登录远程服务器,支持密码从命令行、文件、环境变量中读取

1
sshpass [option] command parameters
  • -p password #后跟密码它允许你用 -p 参数指定明文密码,然后直接登录远程服务器
  • -f filename #后跟保存密码的文件名,密码是文件内容的第一行。
  • -e #将环境变量 SSHPASS 作为密码

范例:

1
2
3
4
5
[root@centos8 ~]$sshpass -p 123456 ssh -o StrictHostKeyChecking=no root@10.0.0.7
Warning: Permanently added '10.0.0.7' (ECDSA) to the list of known hosts.
Last login: Thu Sep 10 14:59:43 2020 from 10.0.0.1
[root@centos7 ~]#
[root@centos7 ~]#

范例:批量修改多台主机的 root 密码为随机密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@centos8 ~]#cat sshpass.sh
#!/bin/bash
HOSTS="
10.0.0.6
10.0.0.7
10.0.0.18
10.0.0.28
"
PASS=magedu
ssh-keygen -P "" -f /root/.ssh/id_rsa &>/dev/null
rpm -q sshpass &>/dev/null || yum -y install sshpass &>/dev/null
for i in $HOSTS; do
{
sshpass -p $PASS ssh-copy-id -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa.pub $i &>/dev/null
} &
done
wait # 等待所有子后台进程结束
# 说明:使用 & + wait 实现“多进程”并发执行

自动化运维工具 pssh

EPEL 源中提供了多个自动化运维工具:pssh、pdsh、mussh

如果机器不多,这些自动化运维工具还是好用的。

pssh

基于 python 编写,可在多台服务器上执行命令的工具,也可实现文件复制,提供了基于 ssh 和 scp 的多个并行工具,项目:http://code.google.com/p/parallel-ssh/

ssh 能用的选项 pssh 也能用,pssh 命令选项如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
-H        # 主机字符串,内容格式”[user@]host[:port]”
-h file # 主机列表文件,内容格式”[user@]host[:port]”
-A # 手动输入密码模式
-i # 每个服务器内部处理信息输出
-l # 登录使用的用户名
-p # 并发的线程数【可选】
-o # 输出的文件目录【可选】
-e # 错误输出文件【可选】
-t # TIMEOUT 超时时间设置,0无限制【可选】
-O # SSH的选项
-P # 打印出服务器返回信息
-v # 详细模式
--version # 查看版本

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 最好先设置免密登录,否则需要使用sshpass

# 一台主机:用 ssh remote_host command 也能实现同样的效果
[root@centos8 ~]$pssh -H root@10.0.0.7 -i hostname
[1] 05:56:05 [SUCCESS] root@10.0.0.7
centos7.magedu.org

# 多台主机
[root@centos8 test]$cat host.list
root@10.0.0.7:22
lujinkai@10.0.0.101:22
[root@centos8 test]$pssh -h host.list -i hostname
[1] 06:05:10 [SUCCESS] root@10.0.0.7:22
centos7.magedu.org
[2] 06:05:10 [SUCCESS] lujinkai@10.0.0.101:22
ubuntu1804
pdsh

Parallel remote shell program,是一个多线程远程 shell 客户端,可以并行执行多个远程主机上的命令。 可使用几种不同的远程 shell 服务,包括 rsh,Kerberos IV 和 ssh,项目: https://pdsh.googlecode.com/

mussh

Multihost SSH wrapper,是一个 shell 脚本,允许使用命令在多个主机上通过 ssh 执行命令。 可使用 ssh-agent 和 RSA/DSA 密钥,以减少输入密码,项目:http://www.sourceforge.net/projects/mussh

pscp.pssh

pscp.pssh 功能是将本地文件批量复制到远程主机

范例:

1
2
3
4
5
6
#将本地curl.sh 复制到/app/目录
pscp.pssh -h host.txt /root/test/curl.sh /app/
#将本地多个文件批量复制到/app/目录
pscp.pssh -h host.txt /root/f1.sh /root/f2.sh /app/
#将本地目录批量复制到/app/目录
pscp.pssh -h host.txt -r /root/test/ /app/
pslurp

pslurp 功能是将远程主机的文件批量复制到本地

范例:

1

文件完整性调查 AIDE

AIDE(Advanced Intrusion Detection Environment 高级入侵检测环境)是一个入侵检测工具,主要用途是检查文件的完整性,审计计算机上的那些文件被更改过了

AIDE 能够构造一个指定文件的数据库,它使用 aide.conf 作为其配置文件。AIDE 数据库能够保存文件的各种属性,包括:权限(permission)、索引节点序号(inode number)、所属用户(user)、所属用户组(group)、文件大小、最后修改时间(mtime)、创建时间(ctime)、最后访问时间(atime)、增加的大小以及连接数。AIDE 还能够使用下列算法:sha1、md5、rmd160、tiger,以密文形式建立每个文件的校验码或散列号

这个数据库不应该保存那些经常变动的文件信息,例如:日志文件、邮件、/proc 文件系统、用户起始目录以及临时目录

配置文件:

1
/etc/aide.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@@define DBDIR /var/lib/aide # 数据库目录
@@define LOGDIR /var/log/aide # 日志目录

database=file:@@{DBDIR}/aide.db.gz # 数据库文件,真正生效的文件
database_out=file:@@{DBDIR}/aide.db.new.gz # 命令生成数据库文件,需要改成aide.db.gz才能生效

# 默认属性
#p: permissions
#i: inode:
#n: number of links
#u: user
#g: group
#s: size
#b: block count
#m: mtime
#a: atime
#c: ctime
#S: check for growing size
#acl: Access Control Lists
#selinux SELinux security context
#xattrs: Extended file attributes
#md5: md5 checksum
#sha1: sha1 checksum
#sha256: sha256 checksum
#sha512: sha512 checksum
#rmd160: rmd160 checksum
#tiger: tiger checksum

#haval: haval checksum (MHASH only)
#gost: gost checksum (MHASH only)
#crc32: crc32 checksum (MHASH only)
#whirlpool: whirlpool checksum (MHASH only)
#R: p+i+n+u+g+s+m+c+acl+selinux+xattrs+md5
#L: p+i+n+u+g+acl+selinux+xattrs
#E: Empty group
#>: Growing logfile p+u+g+i+n+S+acl+selinux+xattrs


# 各种预定义规则,指定需要保存的属性,可以删除,可以参考格式自定义
ALLXTRAHASHES = sha1+rmd160+sha256+sha512+tiger
EVERYTHING = R+ALLXTRAHASHES
NORMAL = p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha512
DIR = p+i+n+u+g+acl+selinux+xattrs
...

# 默认监控的文件和目录,按照指定的规则保存文件的各种属性,可以删除,可以自定义
/boot CONTENT_EX
/opt/ CONTENT
/root/\..* PERMS # 支持正则
/usr/ CONTENT_EX
!/usr/src/ # !表示不监控此文件或目录
!/usr/tmp/
/etc/hosts$ CONTENT_EX # $匹配结尾,说明只监控单个文件
/etc/host.conf$ CONTENT_EX
/etc/hostname$ CONTENT_EX

aide

1
aide [options] command

command:

  • -i:初始化,按照 aide.conf 中的设置 对指定目录 按照指定规则 保存快照到数据库
  • -C:检查,把数据库中保存的文件状态 和 现在的文件 做对比,发现那些文件的那些属性发生了变化
  • -u:更新数据库
  • -E:比较两个数据库
1
2
3
4
5
6
7
8
9
10
11
12
# 初始化默认的AIDE的库
/usr/local/bin/aide -i | --init

# 生成检查数据库(建议初始数据库存放到安全的地方)
cd /var/lib/aide
mv aide.db.new.gz aide.db.gz

# 检测
/usr/local/bin/aide -C | --check

# 更新数据库
aide -u | --update

sudo

sudo 即 superuser do,允许系统管理员让普通用户执行一些或者全部的 root 命令的一个工具,相比 su 命令,sudo 不需要告知普通用户 root 用户的密码。

sudo 特性

  • sudo 能够授权指定用户在指定主机上运行某些命令。如果未授权用户尝试使用 sudo,会提示联系管理员
  • sudo 提供了丰富的日志,详细地记录了每个用户干了什么。它能够将日志传到中心主机或者日志服务器
  • sudo 使用时间戳文件来执行类似的“检票”系统。当用户调用 sudo 并且输入它的密码时,用户获得了一张存活期为 5 分钟的票
  • sudo 的配置文件是 sudoers 文件,它允许系统管理员集中的管理用户的使用权限和使用的主机。它所存放的位置默认是在/etc/sudoers,属性必须为 0440

sudo 组成

  • 配置文件:/etc/sudo.conf

  • 授权规则配置文件:/etc/sudoers、/etc/sudoers.d

  • 安全编辑授权规则文件和语法检查工具:/usr/sbin/visudo

    1
    2
    3
    4
    5
    # 检查语法
    visudo -c

    # 检查指定配置文件语法
    visudo -f /etc/sudoers.d/test
  • 授权编辑规则文件的工具:/usr/bin/sudoedit

    相当于用默认编辑器(可以修改)打开配置文件,只不过有检查语法格式的功能

    1
    root@ubuntu1804:~# export EDITOR=vim # 修改ubuntu的visudo的默认编辑器为vim
  • 执行授权命令:/usr/bin/sudo

  • 时间戳文件:/var/db/sudo

  • 日志文件:/var/log/secure

sudo 命令

1
2
3
4
sudo -h | -K | -k | -V
sudo -l [-AknS] [-g group] [-h host] [-p prompt] [-U user] [-u user] [command]
sudo [-u user] COMMAND
sudo -i -u wang
  • -V:显示版本信息等配置信息
  • -K:第一次使用 sudo 需要输入密码,输入密码后会在/run/sudo/ts 目录下生成一个时间戳文件,这个时间戳文件缓存密码,超时缓存会失效。-K 会删除对应的时间戳缓存文件,也就是说下次使用 sudo 需要输入密码。
  • -k:让对应的时间戳文件失效,也是也是下次使用 sudo 需要输入密码。
  • -i:登录,默认登录到 root 用户,如果是登录到其它用户,需配合 -u user 指定用户
  • -l|ll:列出用户在主机上可用的和被禁止的命令
  • -u:指定用户,默认是 root 用户
  • -b:后台执行命令
1
2
3
4
5
6
7
[root@centos8 etc]$whoami
root
[root@centos8 etc]$sudo -u lujinkai whoami # 指定lujinkai用户执行whoami命令
lujinkai

[root@centos8 etc]$sudo -i -u lujinkai # 切换到lujinkai用户
[lujinkai@centos8 ~]$

sudo 授权规则配置

配置文件格式说明:/etc/sudoers, /etc/sudoers.d/

配置文件中支持使用通配符 glob

配置文件规则有两类:

  1. 别名定义:用于应对比较复杂的授权情况,不是必须的
  2. 授权规则:必须的

sudoers 授权规则格式:

1
2
用户 登入主机=(代表用户) 命令
user host=(runas) command
  • user 和 (runas)
  • 用户: 用户名 username,或者 uid #uid
    • 用户组:组名 %group_name,或者 gid %#id
    • user_alias|runas_alias
  • host
    • ip 或 域名
    • 网段,例如:10.0.0.0/24 表示 10 网段
    • host_alias
  • command
    • 命令
    • directory
    • sudoedit
    • Cmnd_Alias
1
2
# root用户可以登入任何主机,代表任何用户,执行任何命令,当然这条规则是多余的,root用户本来就拥有无限的权限
root ALL=(ALL) ALL

别名有四种,别名必须是大写字母开头:

  • User_Alias
  • Runas_Alias
  • Host_Alias
  • Cmnd_Alias
1
Alias_Type NAME1 = item1,item2,item3 : NAME2 = item4, item5

范例

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
Student ALL=(ALL) ALL # Student用户
%wheel ALL=(ALL) ALL # wheel用户组

User_Alias NETADMIN= netuser1,netuser2 # 定义用户别名:NETADMIN
Cmnd_Alias NETCMD = /usr/sbin/ip,/usr/sbin/ifconfig # 定义命令别名:NETCMD
NETADMIN ALL=(root) NETCMD # NETADMIN 中的用户可以在所有主机上,代表root执行NETCMD 中的命令

User_Alias SYSADER=wang,mage,%admins # 定义用户别名:SYSADER
User_Alias DISKADER=tom # 定义用户别名:DISKADER
Host_Alias SERS=www.magedu.com,172.16.0.0/24 # 定义主机别名:SERS
Runas_Alias OP=root # 定义代表用户:OP
Cmnd_Alias SYDCMD=/bin/chown,/bin/chmod # 定义命令别名:SYDCMD
Cmnd_Alias DSKCMD=/sbin/parted,/sbin/fdisk # 定义命令别名:DSKCMD
# wang,mage,%admins 这三个用户可以在 www.magedu.com,172.16.0.0/24 主机上代表任何用户(空缺表示ALL)执行 /bin/chown,/bin/chmod 和 /sbin/parted,/sbin/fdisk 这些命令
SYSADER SERS= SYDCMD,DSKCMD
# tom 可以在所有主机上代表 root 执行 /sbin/parted,/sbin/fdisk 这两个命令
DISKADER ALL=(OP) DSKCMD

User_Alias ADMINUSER = adminuser1,adminuser2
# 可以使用正则,!表示禁止执行这条命令
Cmnd_Alias ADMINCMD = /usr/sbin/useradd,/usr/sbin/usermod, /usr/bin/passwd [a-zA-Z]*, !/usr/bin/passwd root
# NOPASSWD: 表示不需要输入密码
# adminuser1,adminuser2 可以在任何主机代表root执行useradd、usermod、passwd这三个命令(passwd命令修改的密码只能是字母开头,且不能修改root的密码),而且不需要输入密码
ADMINUSER ALL=(root) NOPASSWD:ADMINCMD,PASSWD:/usr/sbin/userdel

# wang用户可以代表tom和jerry用户,如果-u不指定则默认表示tom
Defaults:wang runas_default=tom
wang ALL=(tom,jerry) ALL

# wang可以代表root在192.168.1.6,192.168.1.8两台主机上执行/usr/sbin/目录下的所有命令,除了useradd
wang 192.168.1.6,192.168.1.8=(root) /usr/sbin/,!/usr/sbin/useradd
1
2
# 这时一条危险的授权命令,如何解决?
wang ALL=(ALL) /bin/cat /var/log/messages*
1
2
# wang可以使用sudoedit命令,表示wang可以修改授权文件,通过修改授权文件,wang可以拥有所有权限
Wang ALL=(ALL) sudoedit
1
2
3
4
5
[wang@centos8 ~]$sudo cat /var/log/messages
[sudo] password for wang:
# 修改sudo 提示符格式
[wang@centos8 ~]$sudo -p "password on %h for user %p: " cat /var/log/messages
password on centos8 for user wang:

ubuntu 用户默认拥有 sudo 权限

1
2
3
4
5
6
7
8
9
10
11
12
# admin组的成员可以获得root权限
# sudo组的成员可以执行任何命令
[lujinkai@ubuntu1804 sudo]$sudo sed -n '/^[^#]/p' /etc/sudoers
Defaults env_reset
Defaults mail_badpass
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
root ALL=(ALL:ALL) ALL
%admin ALL=(ALL) ALL
%sudo ALL=(ALL:ALL) ALL
[lujinkai@ubuntu1804 sudo]$
[lujinkai@ubuntu1804 sudo]$getent group sudo
sudo:x:27:lujinkai

PAM

PAM:Pluggable Authentication Modules,插件式的验证模块,Sun 公司于 1995 年开发的一种与认证相关的通用框架机制。PAM 只关注如何为服务验证用户的 API,通过提供一些动态链接库和一套统一的 API,将系统提供的服务和该服务的认证方式分开,使得系统管理员可以灵活地根据需要给不同的服务配置不同的认证方式而无需更改服务程序一种认证框架,自身不做认证。

PAM 架构

PAM 提供了对所有服务进行认证的中央机制,适用于本地登录,远程登录,如:telnet、rlogin、fsh、ftp、点对点协议 PPP、su 等应用程序中。系统管理员通过 PAM 配置文件来制定不同应用程序的不同认证策略;应用程序开发者通过在服务程序中使用 PAM API(pam_xxxx( ))来实现对认证方法的调用;而 PAM 服务模块的开发者则利用 PAM SPI 来编写模块(主要调用函数 pam_sm_xxxx( )供 PAM 接口库调用,将不同的认证机制加入到系统中;PAM 接口库(libpam)则读取配置文件,将应用程序和相应的 PAM 服务模块联系起来

PAM 相关文件

  • /lib64/security/*.so
  • /etc/security/
  • /etc/pam.conf
  • /etc/pam.d/APP_NAME

/lib64/security/*.so

模块文件,使用 man 可以查看模块的详细信息,例如:man 8 pam_ftp.so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@centos7 ~]#ls /lib64/security/
pam_access.so pam_ftp.so pam_permit.so pam_tally2.so
pam_cap.so pam_group.so pam_postgresok.so pam_time.so
pam_chroot.so pam_issue.so pam_pwhistory.so pam_timestamp.so
pam_console.so pam_keyinit.so pam_pwquality.so pam_tty_audit.so
pam_cracklib.so pam_lastlog.so pam_rhosts.so pam_umask.so
pam_debug.so pam_limits.so pam_rootok.so pam_unix_acct.so
pam_deny.so pam_listfile.so pam_securetty.so pam_unix_auth.so
pam_echo.so pam_localuser.so pam_selinux_permit.so pam_unix_passwd.so
pam_env.so pam_loginuid.so pam_selinux.so pam_unix_session.so
pam_exec.so pam_mail.so pam_sepermit.so pam_unix.so
pam_faildelay.so pam_mkhomedir.so pam_shells.so pam_userdb.so
pam_faillock.so pam_motd.so pam_stress.so pam_warn.so
pam_filter pam_namespace.so pam_succeed_if.so pam_wheel.so
pam_filter.so pam_nologin.so pam_systemd.so pam_xauth.so

/etc/security/

有些模块比较复杂,需要配备专门的配置文件

1
2
3
4
5
[root@centos7 ~]#ls /etc/security/
access.conf console.perms limits.d opasswd time.conf
chroot.conf console.perms.d namespace.conf pam_env.conf
console.apps group.conf namespace.d pwquality.conf
console.handlers limits.conf namespace.init sepermit.conf

/etc/pam.conf

主配置文件,默认不存在,一般不使用主配置

/etc/pam.d/APP_NAME

应用程序单独的 pam 认证配置文件,以应用名命名;
/etc/pam.d 目录存在,/etc/pam.conf 将失效。

1
2
3
4
5
6
7
8
9
[root@centos7 ~]#ls /etc/pam.d/
chfn passwd runuser-l sudo-i
chsh password-auth smartcard-auth su-l
config-util password-auth-ac smartcard-auth-ac system-auth
crond polkit-1 smtp system-auth-ac
fingerprint-auth postlogin smtp.postfix systemd-user
fingerprint-auth-ac postlogin-ac sshd vlock
login remote su
other runuser sudo

PAM 工作原理

PAM 认证一般遵循这样的顺序:Service(服务)→PAM(配置文件)→pam_*.so

PAM 认证首先要确定那一项服务,然后加载相应的 PAM 的配置文件(位于/etc/pam.d 下),最后调用认证文件(位于/lib64/security 下)进行安全认证

查看程序是否支持 PAM:

1
2
3
4
5
6
7
8
9
# 支持PAM
[root@centos8 ~]#ldd `which sshd` |grep libpam
libpam.so.0 => /lib64/libpam.so.0 (0x00007fea8e70d000)
[root@centos8 ~]#ldd `which passwd` |grep pam
libpam.so.0 => /lib64/libpam.so.0 (0x00007f045b805000)
libpam_misc.so.0 => /lib64/libpam_misc.so.0 (0x00007f045b601000)
# 不支持PAM
[root@centos6 ~]#ldd /usr/sbin/httpd |grep pam
[root@centos6 ~]#

以 passwd 命令为例,说明 pam 认证过程:

  1. 使用者执行/usr/bin/passwd 程序,并输入密码
  2. passwd 根据其 PAM 配置文件(一般是/etc/pam.d/passwd),调用相关 PAM 模块
  3. 将验证结果回传给 passwd 这个程序,而 passwd 这个程序会根据 PAM 回传的结果决定下一个动作(重新输入密码或者通过验证)

PAM 配置文件格式说明

通用配置文件/etc/pam.conf 格式,此格式不使用

1
service type control module-path arguments

专用配置文件/etc/pam.d/ 格式

1
type control module-path module-arguments
  • service:指服务名,如:telnet、login、ftp 等,服务名字“OTHER”代表所有没有在该文件中明确配置的其它服务
  • type:module-type,指模块类型,即功能
  • control :PAM 库该如何处理与该服务相关的 PAM 模块的成功或失败情况,一个关健词实现
  • module-path: 指定模块,绝对路径和相对路径都可以,相对路径是相对于/lib64/security/
  • arguments: 用来传递给该模块的参数
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
[root@centos7 pam.d]#cat passwd
#%PAM-1.0
auth include system-auth
account include system-auth
password substack system-auth
-password optional pam_gnome_keyring.so use_authtok
password substack postlogin

[root@centos7 pam.d]#cat system-auth
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required pam_env.so
auth required pam_faildelay.so delay=2000000
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so

account required pam_unix.so
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account required pam_permit.so

password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so

type

  • auth:账号的认证和授权
  • account:帐户的有效性,与账号管理相关的非认证类的功能,如:用来限制/允许用户对某个服务的访问时间,限制用户的位置(例如:root 用户只能从控制台登录)
  • password:用户修改密码时密码复杂度检查机制等功能
  • session:用户会话期间的控制,如:最多打开的文件数,最多的进程数等

-type:表示因为缺失而不能加载的模块将不记录到系统日志,对于那些不总是安装在系统上的模块有用,例如-password、-session

control

  • required:必要条件。此关不过,仍需检测同一个栈中的其他模块,最后返回 failure,认证失败。拥有参考其他模块意见基础之上的一票否决权。可以通过其它模块来检查为什么验证没有通过。
  • requisite:必要条件。一票否决,该模块必须返回成功才能通过认证,但是一旦该模块返回失败,将不再执行同一 type 内的任何模块,而是直接将控制权返回给应用程序。
  • sufficient:充分条件,验证成功则立即返回 OK,不再继续验证,否则忽略 sufficient 的结果并继续其它。换句话说,sufficient 的验证失败对整个验证没有任何影响。
  • optional:可选条件,无论验证结果如何,均不会影响。通常用于 session 类型。
  • include:包含另外一个配置文件中类型相同的行。换句话说,包含进来指定的其他配置文件中同名栈中的规则,并以之进行验证。
  • substack:俗称子栈,这个控制流程俺没有用过,基本上也用不到,我们可以忽略。

arguments

  • debug :该模块应当用 syslog( )将调试信息写入到系统日志文件中
  • no_warn :表明该模块不应把警告信息发送给应用程序
  • use_first_pass :该模块不能提示用户输入密码,只能从前一个模块得到输入密码
  • try_first_pass :该模块首先用前一个模块从用户得到密码,如果该密码验证不通过,再提示用户输入新密码
  • use_mapped_pass 该模块不能提示用户输入密码,而是使用映射过的密码
  • expose_account 允许该模块显示用户的帐号名等信息,一般只能在安全的环境下使用,因为泄漏用户名会对安全造成一定程度的威胁

注意:修改 PAM 配置文件将马上生效
建议:编辑 pam 规则时,保持至少打开一个 root 会话,以防止 root 身份验证错误

常用的 PAM 模块

pam_rootok.so

功能:用户 UID 是 0,返回成功

案例展示:(限制 root 切换用户也需要密码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1 [root@yinzhengjie ~]# more /etc/pam.d/su | grep  pam_rootok.so
2 #auth sufficient pam_rootok.so
3 [root@yinzhengjie ~]#
4 [root@yinzhengjie ~]# su yinzhengjie
5 Password:
6 [yinzhengjie@yinzhengjie root]$ exit
7 exit
8 [root@yinzhengjie ~]#
9 [root@yinzhengjie ~]# more /etc/pam.d/su | grep pam_rootok.so
10 auth sufficient pam_rootok.so
11 [root@yinzhengjie ~]# su yinzhengjie
12 [yinzhengjie@yinzhengjie root]$
13 [yinzhengjie@yinzhengjie root]$ exit
14 exit
15 [root@yinzhengjie ~]#

pam_access.so

功能:访问控制,默认配置文件为“/etc/security/access.conf ”,通常作用于登陆程序,如 su、login、gdm、sshd 等等。

案例展示:(控制访问的源 IP 地址)

1
2
3
4
5
6
7
8
9
1 [root@yinzhengjie ~]# more /etc/pam.d/sshd | grep pam_access.so
2 auth required pam_access.so
3 [root@yinzhengjie ~]#
4 [root@yinzhengjie ~]# tail -4 /etc/security/access.conf
5 #Add by yinzhengjie
6 -:root:ALL EXCEPT 10.0.0.0/24 #只让root从10.0.0.0/24的网段访问
7 #-:root:10.0.0.0/24 #拒绝从10.0.0.0/24这个C的地址访问过来。
8 #-:root:ALL EXCEPT 10.10.0.161 #拒绝从10.10.0.161这个IP访问过来。
9 [root@yinzhengjie ~]#

pam_listfile.so

功能:基于自定义文件允许或拒绝访问资源限制

案例展示:(sshd 的黑名单或白名单)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1 [root@yinzhengjie ~]# grep listfile /etc/pam.d/sshd
2 auth required pam_listfile.so item=user sense=allow file=/etc/yinzhengjie_ssh_users onerr=fail #注意,“onerr=fail”表示当文件“/etc/yinzhengjie_ssh_users”不存在时,默认为fail。
3 [root@yinzhengjie ~]# > /var/log/secure
4 [root@yinzhengjie ~]# tail -10f /var/log/secure
5 Feb 4 23:13:36 yinzhengjie sshd[6509]: reverse mapping checking getaddrinfo for bogon [10.0.0.161] failed - POSSIBLE BREAK-IN ATTEMPT!
6 Feb 4 23:13:36 yinzhengjie sshd[6509]: pam_listfile(sshd:auth): Couldn't open /etc/yinzhengjie_ssh_users
7 Feb 4 23:13:38 yinzhengjie sshd[6509]: Failed password for root from 10.0.0.161 port 55937 ssh2
8 Feb 4 23:13:44 yinzhengjie sshd[6510]: Received disconnect from 10.0.0.161: 13: The user canceled authentication.
9 ^C
10 [root@yinzhengjie ~]# echo root > /etc/yinzhengjie_ssh_users
11 [root@yinzhengjie ~]#
12 [root@yinzhengjie ~]# tail -10f /var/log/secure
13 Feb 4 23:13:36 yinzhengjie sshd[6509]: reverse mapping checking getaddrinfo for bogon [10.0.0.161] failed - POSSIBLE BREAK-IN ATTEMPT!
14 Feb 4 23:13:36 yinzhengjie sshd[6509]: pam_listfile(sshd:auth): Couldn't open /etc/yinzhengjie_ssh_users
15 Feb 4 23:13:38 yinzhengjie sshd[6509]: Failed password for root from 10.0.0.161 port 55937 ssh2
16 Feb 4 23:13:44 yinzhengjie sshd[6510]: Received disconnect from 10.0.0.161: 13: The user canceled authentication.
17 Feb 4 23:14:13 yinzhengjie sshd[6513]: Accepted password for root from 10.0.0.161 port 55960 ssh2
18 Feb 4 23:14:13 yinzhengjie sshd[6513]: pam_unix(sshd:session): session opened for user root by (uid=0)
19 ^C
20 [root@yinzhengjie ~]#

pam_time.so

功能:基于时间的访问控制,默认文件“/etc/security/time.conf ”

案例展示:(基于时间限制 sshd 的访问)

1
2
3
4
1 [root@yinzhengjie ~]# tail -2 /etc/security/time.conf
2 #Add by yinzhengjie
3 sshd;*;*;SaSu0900-1800 #表示sshd服务的所有终端的所有用户只能在8-18点可以正登陆。
4 [root@yinzhengjie ~]#

pam_tally2.so

功能:登陆统计

案例展示:(实现防止对 sshd 暴力破解)

1
2
3
4
5
6
7
1 [root@yinzhengjie ~]# grep pam_tally2.so /etc/pam.d/sshd
2 auth required pam_tally2.so deny=2 even_deny_root root_unlock_time=60 unlock_time=60 #表示当用户输入次数超过2次时,锁定用户60秒,因此这里的“even_deny_root ”表示包括root用户,“root_unlock_time”表示root用户锁定的时间,“unlock_time”表示其他用户锁定的时间。
3 [root@yinzhengjie ~]#
4 [root@yinzhengjie ~]# pam_tally2 --reset -u root #这里表示手动解锁。
5 Login Failures Latest failure From
6 root 0
7 [root@yinzhengjie ~]#

pam_limits.so

功能:限制用户会话过程中对各种资源的使用情况。缺省情况下该模块的配置文件是/etc/security/limits.conf/etc/security/limits.d/*.conf。在用户级别实现对其可使用的资源的限制,例如:可打开的文件数量,可运行的进程数量,可用内存空间

pam_shells.so

功能:检查有效 shell,登录用户的 shell 不在/etc/shells 中则为非法 shell

案例:不允许使用/bin/csh 的用户本地登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@centos8 ~]#yum -y install csh

# 新建用户并设置密码
[root@centos8 ~]#useradd -s /bin/csh testuser
[root@centos8 ~]$echo 123456 | passwd --stdin testuser

# auth required pam_shells.so 表示校验登录用户的shell
[root@centos8 ~]#echo "auth required pam_shells.so" >> /etc/pam.d/login

# 将shells中的csh注释
[root@centos8 ~]$sed -Ei 's/^\/bin\/csh/#&/' /etc/shells

# testuser因为shell为csh,不在/etc/shells中,pam_shells.so模块校验不通过,所以无法登录

pam_securetty.so

功能:只允许 root 用户在/etc/securetty 列出的安全终端上登陆

pam_nologin.so

功能:如果/etc/nologin 文件存在,将导致非 root 用户不能登陆,当该用户登陆时,会显示/etc/nologin 文
件内容,并拒绝登陆

pam_succeed_if.so

功能:根据参数中的所有条件都满足才返回成功

案例:ubuntu 默认不允许 root 登录桌面图形

1
2
3
4
用root登录桌面失败,查看日志,可看到Pam原因
Vim /etc/pam.d/gdm-passwd
#将下面行注释
#auth requried pam_succeed_if.so user !=root quiet_success

pam_google_authenticator

实现 SSH 登录的两次身份验证,先验证 APP 的数字码,再验证 root 用户的密码,都通过才可以登录。目前只支持口令验证,不支持基于 key 验证

需要安装,官方网站:https://github.com/google/google-authenticator-android

时间同步服务

多主机协作工作时,各个主机的时间同步很重要,时间不一致会造成很多重要应用的故障,如:加密协议,日志,集群等, 利用 NTP(Network Time Protocol) 协议使网络中的各个计算机时间达到同步。目前 NTP 协议属于运维基础架构中必备的基本服务之一。

ntp 和 chrony 都可以搭建时间同步服务,chrony 更优秀。

chrony

chrony 组成文件

两个主要程序:chronyc、chronyd

  • /usr/bin/chronyc:命令行用户工具,用于监控性能并进行多样化的配置。
  • /usr/sbin/chronyd:后台运行的守护进程,调整内核中运行的系统时钟和时钟服务器同步。它确定计算机增减时间的比率,并对此进行补偿。

服务 unit 文件: /usr/lib/systemd/system/chronyd.service
监听端口: 323/udp,123/udp
配置文件: /etc/chrony.conf

配置文件 chrony.conf

  • server hostname [option]…

    指定单个 NTP 服务器。iburst:可加快初始同步速度

  • pool name [option]…

    指定 NTP 服务器池

  • rtcsync

    启用内核模式,系统时间每 11 分钟会拷贝到实时时钟(RTC)

  • allow [all] [subnet]

    指定一台主机、子网,或者网络以允许或拒绝访问本服务器

chronyc

ntp 客户端工具,chronyc 可以运行在交互式和非交互式两种方式,支持以下命令:

  • help 命令可以查看更多 chronyc 的交互命令
  • accheck 检查是否对特定主机可访问当前服务器
  • activity 显示有多少 NTP 源在线/离线
  • sources [-v] 显示当前时间源的同步信息
  • sourcestats [-v]显示当前时间源的同步统计信息
  • add server 手动添加一台新的 NTP 服务器
  • clients 报告已访问本服务器的客户端列表
  • delete 手动移除 NTP 服务器或对等服务器
  • settime 手动设置守护进程时间
  • tracking 显示系统时间信息

公共 ntp 服务

  • 阿里云公共 NTP 服务器:ntp.aliyun.com,ntp1-7.aliyun.com
  • 腾讯公共 NTP 服务器:time1-5.cloud.tencent.com
  • 大学 ntp 服务:
    • s1a.time.edu.cn 北京邮电大学
    • s1b.time.edu.cn 清华大学
    • s1c.time.edu.cn 北京大学
  • 国家授时中心服务器:210.72.145.44

范例:centos7 和 8 作为 ntp 服务器,其他服务器就不从公共 ntp 服务器同步时间,而是从 centos7 和 8 同步时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# centos7、8
# 修改/etc/chrony.conf的server为公共ntp服务器,allow为允许同步的其他服务器
[root@centos8 ~]$sed -n '/^[^#]/p' /etc/chrony.conf
server ntp.aliyun.com iburst
server time1.cloud.tencent.com iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
allow 10.0.0.0/24
keyfile /etc/chrony.keys
leapsectz right/UTC
logdir /var/log/chrony

# 其他服务器,修改/etc/chrony.conf的server为centos7、8
[root@centos6 ~]#grep '^[^#]' /etc/chrony.conf
server 10.0.0.7 iburst
server 10.0.0.8 iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony

TCP Warpper

SELinux

程序创建进程,一个进程至少创建一个线程,程序的运行最终是靠线程来完成的。

有人说在 Linux 中,进程和线程是一样的,这种说法是不对的,线程和进程都有自己的 id

初步理解各种 id

  • pid: 进程 id

  • lwp: 线程 id,light weight process (thread) ID,也叫 spid 或 tid

    使用 ps 等工具查询的时候,线程的的 PID 是线程所在进程 id,SPID 才是线程 id

  • tgid: 线程组 id, 也就是线程组 leader 的进程 id, 等于 pid

  • ——- 分割线(以下内容不重要)———

  • pgid: 进程组 id, 也就是进程 leader 的进程 id

  • pthread id: pthread 库提供的 id, 生效范围不在系统级别, 可以忽略

  • sid: ession ID for the session leader

  • tpgid: tty process group ID for the process group leader

从上面可以看出: 各种 id 最后都归结到 pid 和 lwp 上。所以理解各种 id,最终归结为理解 pid 和 lwd 的联系和区别

描述父子进程,线程之间关系图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
               USER VIEW
<-- PID 43 --> <----------------- PID 42 ----------------->
+---------+
| process |
_| pid=42 |_
_/ | tgid=42 | \_ (new thread) _
_ (fork) _/ +---------+ \
/ +---------+
+---------+ | process |
| process | | pid=44 |
| pid=43 | | tgid=42 |
| tgid=43 | +---------+
+---------+
<-- PID 43 --> <--------- PID 42 --------> <--- PID 44 --->
KERNEL VIEW

上图很好地描述了用户视角(user view)和内核视角(kernel view)看到线程的差别:

  • 从用户视角出发,在 pid 42 中产生的 tid 44 线程,属于 tgid(线程组 leader 的进程 ID) 42。甚至用 ps 和 top 的默认参数,你都无法看到 tid 44 线程
  • 从内核视角出发,tid 42 和 tid 44 是独立的调度单元,可以把他们视为”pid 42”和”pid 44”

需要指出的是,有时候在 Linux 中进程和线程的区分也是不是十分严格的。即使线程和进程混用,pid 和 tid 混用,根据上下文,还是可以清楚地区分对方想要表达的意思。上图中,从内核视角出发看到了 pid 44,是从调度单元的角度出发,但是在 top 或 ps 命令中,你是绝对找不到一个 pid 为 44 的进程的,只能看到一个 lwp(tid)为 44 的线程

Linux 通过进程查看线程的方法

  1. htop按 t(显示进程线程嵌套关系)和 H(显示线程) ,然后 F4 过滤进程名
  2. ps -eLf | grep java(快照,带线程命令,e 是显示全部进程,L 是显示线程,f 全格式输出)
  3. pstree -p <pid>(显示进程树,不加 pid 显示所有)
  4. top -Hp <pid> (实时)
  5. ps -T -p <pid>(快照) 推荐程度按数字从小到大

pstree, ps, pidof, pgrep, top, htop, glance, pmap, vmstat, dstat,kill, pkill, job, bg, fg, nohup

pstree

进程树,来自 psmisc 软件包(fuser 和 killall 也来自 psmisc ) connecting.asciidoc

1
2
3
4
5
6
7
pstree [OPTION] [ PID | USER ]

OPTION:
-p # 显示PID
-T # 不显示线程,只显示进程
-u # 显示进程所属用户
-H pid # 高亮显示指定进程及其前辈进程

ps

process state,默认显示当前终端中的进程,Linux 系统各进程的相关信息均保存在/proc/PID目录下的各个文件中。ps 显示的是进程快照,如果要动态显示实时的进程信息,可以使用 top 命令

1
ps [options]

ps 的参数有非常多,三种风格都支持

大致分以下五类,这五类是我划分的,和 man 文档不太一样,下面是常见的 option:

  1. 简单选择范围

    1
    2
    3
    a     # 显示所有终端的进程
    x # 显示不链接终端的进程
    -e # 显示所有进程,和ax显示的进程一样多,但是不如ax显示的属性多
  2. 按条件查询
    查询条件之间用逗号分割

    1
    2
    3
    4
    5
    6
    7
    8
    -p | p pidlist  # 指定pid列表
    --ppid pidlist # 显示属于pid的子进程
    -C cmdlist # 指定命令列表
    -u userlist # 指定 effective user 列表,可以写用户名或用户id
    -U userlist # 指定 real user 列表
    -g grplist # 指定effective group 列表,可以写组名或组id
    -G grplist # 指定real group 列表
    -t ttylist # 指定tty
  3. 控制输出属性

    1
    2
    3
    4
    o 属性列表  # 指定属性,属性之间用逗号分割
    u # 显示user,以及%cpu、%mem等信息,适合面向用户
    -f # 显示UID、PPID、C与STIME
    -F # 比-f多显示SZ、RSS、PSR
  4. 格式化输出

    1
    2
    3
    4
    5
    6
    f                                   # 显示进程树,相当于 --forest
    k|--sort [+|-]key[,[+|-]key[,...]] # 按照属性排序,如果多个属性,用逗号分割,属性前加-表示倒序
    -H # 显示进程层级格式,和 f 差不多

    ps aux k %cpu 等于 ps aux --sort %cpu 等于 ps aux --sort=%cpu
    ps aux k -%cpu 等于 ps aux --sort -%cpu 等于 ps aux --sort=-%cpu
  5. 显示线程

    1
    -L    # 显示线程
  6. 其他

    1
    -L   # 显示所有属性,小写的那列是属性code,大写的是属性header,`ps | head -1`输出的就是header

很多 option 是冲突的,有的冲突会报错,有的结果无意义我也算它冲突,大概有以下两种情况:

  1. 按条件查询 和 简单选择范围:我们的预期是从指定的范围内按条件查询,然后输出查询的结果,有的命令确实是这个逻辑,但是 ps 会把所有的进程和查询到的进程都输出,这样的结果是没有意义的

  2. 控制输出的属性:当多个这种类型的 option 组合在一起,如果指定的属性列表是包含关系,则正常,否则报错,例如-f 和-F 会按照-F 输出结果,但是-f 和 u 就会报错。
    简单选择范围 的 option 默认展示若干属性,它们和控制输出的属性不冲突

常用组合

常见属性

ps L可以查看所有进程属性,有 170 个,我叫它属性,实际 man 文档称之为 SPECIFIERS(说明符),o 选项可以指定 ps 输出哪些进程属性,下面是常用的进程属性:

  • user|euser:effective user,有效的用户
  • ruser:real user 真正的用户,例如普通用户 lujinkai 使用 sudo 执行命令,那 effective user 就是 lujinkai,real user 就是 root
  • fuser:
  • suser:
  • %cpu:cpu 占用率,精确到小数点后一位
  • c:cpu 占有率,精确到个位数
  • %mem:内存占有率
  • pid:进程 id,如果是线程,则是线程所在进程的 id
  • ppid:父进程 id
  • lwp|spid|tid:线程 id
  • s|state:minimal state display (one character)
  • stat:multi-character process state
  • ni|nice:nice 优先级
  • pri:priority 优先级
  • rtprio:rtprio 优先级
  • sz:物理内存大小,包括文本、数据和堆栈空间
  • psr:processor,CPU 编号,表示进程占用哪个 CPU 核心
  • vsz:虚拟内存大小,单位 k
  • rss:常驻内存大小,包括了共享库内存的大小(不同进程引用同一个库文件,那这些进程的 rss 中都包含了这个共享库的大小),单位 k
  • comm:进程名,例如:mysqld
  • command:完整命令,例如:/usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir
  • time|cputiime:进程占用 CPU 的累计时间
  • lstart:开始时间
  • etime:运行时间
  • stime:启动进程的时间

进程状态码

1
2
3
4
5
6
7
8
9
D    # 睡眠状态,不可中断 (通常是IO)
R # 可执行状态,进程处于运行或就绪状态 (on run queue)
S # 睡眠状态(sleeping) 可中断 (waiting for an event to complete)
T # stopped by job control signal
t # stopped by debugger during the tracing
W # paging (not valid since the 2.6.xx kernel)
X # 退出状态,进程即将被销毁 (should never be seen)
Z # 退出状态(zombie),终止但是未被父进程收回,进程成为僵尸进程
I # Idle kernel thread,CentOS 8 新特性

对于 BSD 风格,stat 还会显示下面的状态码:

1
2
3
4
5
6
<    # 高优先级(对其他用户不好)
N # 低优先级(对其他用户很好)
L # 内存分页并带锁(用于实时和自定义IO)
s # is a session leader
l # 多线程 (using CLONE_THREAD, like NPTL pthreads do)
+ # 在前台进程组中,前台进程

prtstat

查看进程详细信息,ps 适合查找进程,确定 pid,然后使用 prtstat 查看详细信息。

1
2
3
prtstat [options] PID ...

-r # raw格式显示
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
[root@4710419222 ~]# prtstat 24954
Process: php-fpm State: S (sleeping)
CPU#: 0 TTY: 0:0 Threads: 1
Process, Group and Session IDs
Process ID: 24954 Parent ID: 24953
Group ID: 24953 Session ID: 24953
T Group ID: -1

Page Faults
This Process (minor major): 8195 0
Child Processes (minor major): 0 0
CPU Times
This Process (user system guest blkio): 0.15 0.02 0.00 0.00
Child processes (user system guest): 0.00 0.00 0.00
Memory
Vsize: 185 MB
RSS: 17 MB RSS Limit: 18446744073709 MB
Code Start: 0x400000 Code Stop: 0xeab9c1
Stack Start: 0x7fff3beae6e0
Stack Pointer (ESP): 0x7fff3beae198 Inst Pointer (EIP): 0x7f67ad7f8840
Scheduling
Policy: normal
Nice: 0 RT Priority: 0 (non RT)
[root@4710419222 ~]#
[root@4710419222 ~]#
[root@4710419222 ~]# prtstat -r 24954
pid: 24954 comm: php-fpm
state: S ppid: 24953
pgrp: 24953 session: 24953
tty_nr: 0 tpgid: -1
flags: 402140 minflt: 8195
cminflt: 0 majflt: 0
cmajflt: 0 utime: 15
stime: 2 cutime: 0
cstime: 0 priority: 20
nice: 0 num_threads: 1
itrealvalue: 0 starttime: 2798685522
vsize: 185421824 rss: 4316
rsslim: 18446744073709551615 startcode: 4194304
endcode: 15382977 startstack: 140734198638304
kstkesp: 7FFF3BEAE198 kstkeip: 7F67AD7F8840
wchan: 18446744071584583552 nswap: 0
cnswap: 18446744071584583552 exit_signal: 17
processor: 0 rt_priority: 0
policy: 0 delayaccr_blkio_ticks: 0
guest_time: 0 cguest_time: 0

设置和调整进程优先级

静态优先级:在创建进程时确定的,且在进程的整个运行期间保持不变,静态优先级的范围是 100 到 139

nice

进程默认启动时的 nice 值是 0,对应 的优先级是 120,只有根用户才能降低 nice 值

1
2
3
nice [OPTION] [COMMAND [ARG]...]

-n # 指定优先级, -20到19

以指定的优先级来启动进程:

1
[root@centos8 ~]#nice -n -10 ping 127.0.0.1

renice

调整正在执行中的进程的优先级

1
[root@centos8 ~]#renice -n -20 2118

搜索进程

按条件搜索进程:

  • ps options | grep 'pattern' 最灵活
  • pgrep 按预定义的模式
  • pidof 按确切的程序名称查看 pid

pgrep

顾名思义,过滤进程

1
2
3
4
5
6
7
8
 pgrep [options] <pattern>

-u uidlist # effective user
-U uidlist # real user
-t terminal # 与指定终端相关的进程
-l # 显示进程名
-a # 显示完整格式的进程名
-P pid # 显示指定进程的子进程

pidof

1
2
3
pidof [options] [program [...]]

-x # 按脚本名称查找pid
1
2
[root@centos8 ~]#pidof -x ping.sh
19035

uptime 和 w

uptime 显示:当前时间、系统启动时间、当前在线人数、系统负载(1、5、15 分钟的平均负载,一般不会超过 1,超过 5 时建议警报)

1
2
[root@4710419222 ~]# uptime
22:04:28 up 325 days, 4:05, 2 users, load average: 0.01, 0.04, 0.05

w 除了显示 uptime 显示的内容外,还可以在线人员的信息

1
2
3
4
5
[root@4710419222 ~]# w
22:07:46 up 325 days, 4:08, 2 users, load average: 0.05, 0.03, 0.05
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/1 39.164.140.134 20:17 3:14 0.49s 0.49s -bash
root pts/4 39.164.140.134 21:17 2.00s 0.04s 0.00s w

mpstat

来自于 sysstat 包,显示 CPU 相关统计

1
2
3
mpstat [options] [ interval [ count ]

-p # 指定查看的cpu核心
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
lujinkai@Z510:~$ mpstat
Linux 5.4.0-46-generic (Z510) 09/03/20 _x86_64_ (4 CPU)

22:15:39 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
22:15:39 all 10.43 0.51 3.06 0.07 0.00 0.86 0.00 0.00 0.00 85.08
lujinkai@Z510:~$ mpstat 1 3
Linux 5.4.0-46-generic (Z510) 09/03/20 _x86_64_ (4 CPU)

22:15:45 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
22:15:46 all 10.97 0.00 3.83 0.00 0.00 0.51 0.00 0.00 0.00 84.69
22:15:47 all 6.72 0.00 3.98 0.00 0.00 1.99 0.00 0.00 0.00 87.31
22:15:48 all 6.08 0.00 3.54 0.00 0.00 1.27 0.00 0.00 0.00 89.11
Average: all 7.91 0.00 3.78 0.00 0.00 1.26 0.00 0.00 0.00 87.05
lujinkai@Z510:~$ mpstat -P 0 1 2
Linux 5.4.0-46-generic (Z510) 09/03/20 _x86_64_ (4 CPU)

22:15:52 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
22:15:53 0 7.22 0.00 5.15 1.03 0.00 0.00 0.00 0.00 0.00 86.60

22:15:53 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
22:15:54 0 7.14 0.00 7.14 0.00 0.00 1.02 0.00 0.00 0.00 84.69

Average: CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
Average: 0 7.18 0.00 6.15 0.51 0.00 0.51 0.00 0.00 0.00 85.64

top

查看进程实时状态

htop

top 的升级版

cpu4 核 4 线程和 4 核 8 线程的区别:

1
4核 就是有4个物理核心,4线程 就是一个核心对应一个线程,8线程就是两个线程对应一个核心,是intel使用超线程技术,把一个物理核心模拟成两个逻辑核心。所以任务管理器或者htop会显示会显示出8张CPU表

关于 PR、NI、PRI:

1
2
3
PR:priority 优先级,系统调节
NI:nice 反应一个进程“优先级”状态的值,其取值范围是-20至19,一共40个级别。这个值越小,表示进程“优先级”越高。就像人一样,越nice的人,抢占资源的能力就越差,而越不nice的人抢占能力就越强。这个值用户可以调节
PRI:PR + NI,PRI才是进程最终的优先级,用户可以通过调节NI来影响它

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
1-8             # cpu每个核心负载
Mem # 物理内存,共计7.51G,已用4.95G
Swp # 交换内存,共计2G,已使用4M
Tasks # 进程、线程、正在运行的进程,当前系统有268个进程,910个线程,正在运行的进程有1个
Load average # 系统1分钟、5分钟、10分钟的平均负载情况
Uptime # 系统运行时间

PID # 进程id
USER # 运行此进程的用户
PRI # 运行此进程的优先级
NI # 进程的优先级别值,默认的为0,可以进行调整
VIRT # 进程占用的虚拟内存
RES # 进程当前使用的内存大小,但不包括swap out,包含其他进程的共享,如果申请100m,实际只使用10m,它只增长10m,与virt相反
SHR # 进程占用的共享内存值,计算某个进程所占用的物理内存大小公式:RES - SHRS
# 进程的状态,其中S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值是负数
O # 进程正在处理器运行
S # 睡眠状态(sleeping) 可中断
D # 睡眠状态(disk sleep),有时候也叫不可中断睡眠状态,在这个状态的进程通常会等待I/O的结束
R # 可执行状态(runable)Running or runnable (on run queue) 进程处于运行或就绪状态
I # 空闲状态(idle)
Z # 退出状态(zombie)进程成为僵尸进程
T # 暂停状态或跟踪状态(stoped or traced)
B # 进程正在等待更多的内存页
X # 退出状态,进程即将被销毁
%CPU # 该进程占用的CPU使用率
%MEM # 该进程占用的物理内存和总内存的百分比
TIME+ # 该进程启动后占用的总的CPU时间
COMMAND # 进程启动的启动命令名称

VIRT:进程占用的虚拟内存,这个虚拟内存和 swp 没有关系,是进程“需要的”虚拟内存大小,包括进程使用的库,代码,数据等。

假如进程申请 100m 的内存,但实际上只使用 了 10m,那么它会增长 100m,而不是实际的使用量

现代操作系统里面分配虚拟地址空间操作不同于分配物理内存。在 64 位操作系统上,可用的最大虚拟地址空间有 16EB,即大概 180 亿 GB。那么在一台只有 16G 的物理内存的机器上,我也能要求获得 4TB 的地址空间以备将来使用。例如:

1
void *mem = mmap(0, 4ul * 1024ul * 1024ul * 1024ul * 1024ul, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);

当使用 mmap 并设置 MAP_NORESERVE 标志时,并不会要求实际的物理内存和 swap 空间存在。所以上述代码可以在 top 中看到使用了 4096g 的 VIRT 虚拟内存,这当然是不可能的,它只是表示使用了 4096GB 的地址空间而已

一般来说不用太在意 VIRT 太高,因为你有 16EB 的空间可以使用

free

显示内存使用情况

1
2
3
4
5
6
7
8
9
10
free [options]

-b #以字节为单位
-m #以MB为单位
-g #以GB为单位
-h #易读格式
-o #不显示-/+buffers/cache行
-t #显示RAM + swap的总和
-s n #刷新间隔为n秒
-c n #刷新n次后即退出

pmap

进程对应的内存映射

vmstat

虚拟内存信息

iostat

统计 CPU 和设备 IO 信息

dstat

系统资源统计

iotop

监视磁盘 I/O

iftop

显示网络带宽使用情况

iftop 和 nethogs

nload

查看网络实时吞吐量

glances

综合监控工具

lsof

查看进程打开文件

cockpit

CentOS8 新特性

kill

信号发送

作业管理

nohup

no hang up,不挂断的意思,如果当前 terminal 关闭了,命令就转为后台继续运行

nohup 和 & 的区别:

如果不是通过 exit 退出 terminal,而是点击 × 强行关闭 terminal,通过 & 进入后台的命令会被杀死,而通过 nohup 会将命令转后台继续运行,所以二者通常搭配一起使用

范例:

1
2
[root@elk2-ljk bin]$nohup ./cerebro >/dev/null 2>&1 &
[1] 4149

screen

  • 创建新会话: screen -S <session-name>
  • 加入会话:screen -x <session-name>
  • 退出并关闭会话:exit
  • 剥离当前会话:ctrl + a,d
  • 显示所有已经打开的会话:screen -ls
  • 恢复某会话:screen -r <session-name>

关于 -x-r : -x 是加入会话,多个终端都可以加入,-r 是恢复会话,当一个终端恢复会话,其他终端就无法恢复,只能加入

1
2
3
4
5
screen -S new
# ctrl + a,d
screen -r new
# 另起一个终端
screen -x new

tmux

Tmux 是一个终端复用器(terminal multiplexer),类似 screen,但是更易用,也更强大

tmux 好像不能共享会话

  • 新建会话:tmux new -s <session-name>

  • 查看所有会话:tmux ls

    1
    2
    3
    4
    [root@centos7 lujinkai]# tmux ls
    jin: 1 windows (created Sat Jul 25 13:51:23 2020) [80x45] (attached) #当前所处的会话是jin
    kai: 1 windows (created Sat Jul 25 13:51:34 2020) [80x45]
    lu: 1 windows (created Sat Jul 25 13:49:33 2020) [80x45]
  • 退出会话:exit

  • 分离会话:ctrl + b d 或者tmux detach

  • 接入会话:tmux attach -t <session-name> 用于重新接入某个已经存在的会话。

  • 切换会话:tmux switch -t <session-name>

  • 杀死会话:tmux kill-session -t <session-name>

并行运行